From bf214c1e1dc05d30980149c1a5d24d73d1feb003 Mon Sep 17 00:00:00 2001 From: Harshit Jain <48647625+Harshit28j@users.noreply.github.com> Date: Thu, 5 Feb 2026 12:43:10 +0530 Subject: [PATCH] fix(otel): guard against None message in set_attributes tool_calls (#20339) --- .circleci/config.yml | 340 +- .circleci/requirements.txt | 3 +- .github/workflows/test-linting.yml | 2 +- .github/workflows/test-litellm.yml | 2 +- .github/workflows/test-model-map.yaml | 15 + .gitignore | 9 +- .trivyignore | 12 + AGENTS.md | 8 +- Dockerfile | 5 +- README.md | 15 +- ci_cd/security_scans.sh | 6 +- cookbook/anthropic_agent_sdk/README.md | 144 + .../anthropic_agent_sdk/agent_with_mcp.py | 140 + cookbook/anthropic_agent_sdk/common.py | 160 + .../anthropic_agent_sdk/config.example.yaml | 25 + cookbook/anthropic_agent_sdk/main.py | 95 + cookbook/anthropic_agent_sdk/requirements.txt | 2 + cookbook/nova_sonic_realtime.py | 284 + .../litellm-helm/templates/deployment.yaml | 4 + .../templates/migrations-job.yaml | 4 + deploy/charts/litellm-helm/values.yaml | 1 + docker/Dockerfile.custom_ui | 3 +- docker/Dockerfile.database | 3 +- docker/Dockerfile.dev | 3 +- docker/Dockerfile.non_root | 9 +- .../index.md | 1 + docs/my-website/blog/gemini_3/index.md | 1 + docs/my-website/blog/gemini_3_flash/index.md | 1 + .../sub_millisecond_proxy_overhead/index.md | 92 + docs/my-website/docs/a2a.md | 2 +- docs/my-website/docs/benchmarks.md | 22 + docs/my-website/docs/enterprise.md | 12 + docs/my-website/docs/mcp_semantic_filter.md | 158 + docs/my-website/docs/observability/datadog.md | 52 +- .../docs/pass_through/openai_passthrough.md | 25 +- .../docs/providers/anthropic_tool_search.md | 450 +- .../providers/azure_ai/azure_model_router.md | 163 +- docs/my-website/docs/providers/bedrock.md | 2 +- .../providers/bedrock_realtime_with_audio.md | 362 + docs/my-website/docs/providers/gemini.md | 51 + docs/my-website/docs/providers/sarvam.md | 92 + .../docs/providers/vercel_ai_gateway.md | 36 +- .../docs/providers/vertex_speech.md | 5 +- docs/my-website/docs/proxy/call_hooks.md | 41 + docs/my-website/docs/proxy/cli_sso.md | 31 + docs/my-website/docs/proxy/config_settings.md | 26 + docs/my-website/docs/proxy/custom_pricing.md | 46 + docs/my-website/docs/proxy/deploy.md | 1 + docs/my-website/docs/proxy/embedding.md | 10 + .../docs/proxy/guardrails/onyx_security.md | 3 + .../docs/proxy/guardrails/quick_start.md | 6 +- .../docs/proxy/keys_teams_router_settings.md | 150 + .../docs/proxy/litellm_managed_files.md | 2 +- docs/my-website/docs/proxy/load_balancing.md | 61 + docs/my-website/docs/proxy/prometheus.md | 15 +- docs/my-website/docs/proxy/request_tags.md | 58 + .../docs/proxy/ui/page_visibility.md | 121 + docs/my-website/docs/proxy/ui_logs.md | 44 +- .../docs/proxy/ui_spend_log_settings.md | 92 + docs/my-website/docs/rag_ingest.md | 78 +- docs/my-website/docs/realtime.md | 1 + docs/my-website/docs/routing.md | 41 +- docs/my-website/docs/traffic_mirroring.md | 83 + .../docs/troubleshoot/prisma_migrations.md | 113 + .../docs/tutorials/claude_agent_sdk.md | 115 + .../claude_code_plugin_marketplace.md | 4 +- .../img/ui_granular_router_settings.png | Bin 0 -> 359348 bytes .../my-website/img/ui_spend_logs_settings.png | Bin 0 -> 351095 bytes docs/my-website/img/ui_tools.png | Bin 0 -> 430362 bytes .../my-website/release_notes/v1.81.0/index.md | 4 +- .../release_notes/v1.81.3-stable/index.md | 423 + docs/my-website/release_notes/v1.81.6.md | 384 + docs/my-website/sidebars.js | 38 +- .../src/theme/BlogListPage/index.js | 123 + .../src/theme/BlogListPage/styles.module.css | 163 + .../proxy/auth/route_checks.py | 2 +- .../proxy/hooks/managed_files.py | 87 +- .../proxy/vector_stores/endpoints.py | 2 + ...tellm_proxy_extras-0.4.27-py3-none-any.whl | Bin 0 -> 50073 bytes .../dist/litellm_proxy_extras-0.4.27.tar.gz | Bin 0 -> 23269 bytes ...tellm_proxy_extras-0.4.28-py3-none-any.whl | Bin 0 -> 50208 bytes .../dist/litellm_proxy_extras-0.4.28.tar.gz | Bin 0 -> 23405 bytes ...tellm_proxy_extras-0.4.29-py3-none-any.whl | Bin 0 -> 50734 bytes .../dist/litellm_proxy_extras-0.4.29.tar.gz | Bin 0 -> 23561 bytes .../litellm_proxy_extras/_logging.py | 40 +- .../migration.sql | 10 +- .../migration.sql | 10 + .../litellm_proxy_extras/schema.prisma | 6 + .../litellm_proxy_extras/utils.py | 109 +- litellm-proxy-extras/pyproject.toml | 4 +- litellm/__init__.py | 6 +- litellm/_lazy_imports_registry.py | 1044 +- litellm/_logging.py | 60 + litellm/a2a_protocol/card_resolver.py | 97 + litellm/a2a_protocol/main.py | 9 +- litellm/batches/batch_utils.py | 14 +- litellm/batches/main.py | 9 +- .../transformation.py | 30 + litellm/constants.py | 28 + litellm/cost_calculator.py | 121 +- litellm/experimental_mcp_client/client.py | 172 +- litellm/files/main.py | 19 +- .../SlackAlerting/slack_alerting.py | 5 + litellm/integrations/braintrust_logging.py | 12 + .../integrations/braintrust_mock_client.py | 131 + litellm/integrations/callback_configs.json | 29 +- litellm/integrations/custom_guardrail.py | 16 +- litellm/integrations/custom_logger.py | 22 + litellm/integrations/datadog/datadog.py | 45 +- .../datadog/datadog_cost_management.py | 204 + .../integrations/datadog/datadog_handler.py | 8 + .../integrations/datadog/datadog_llm_obs.py | 97 +- .../datadog/datadog_mock_client.py | 28 + .../gcs_bucket/gcs_bucket_mock_client.py | 186 +- litellm/integrations/helicone.py | 15 +- litellm/integrations/helicone_mock_client.py | 32 + litellm/integrations/langfuse/langfuse.py | 14 +- .../langfuse/langfuse_mock_client.py | 124 +- .../langfuse/langfuse_prompt_management.py | 92 +- litellm/integrations/langsmith.py | 23 +- litellm/integrations/langsmith_mock_client.py | 29 + litellm/integrations/mock_client_factory.py | 216 + litellm/integrations/opentelemetry.py | 39 +- litellm/integrations/posthog.py | 38 +- litellm/integrations/posthog_mock_client.py | 30 + litellm/integrations/prometheus.py | 232 +- litellm/integrations/prometheus_services.py | 5 + .../litellm_core_utils/get_litellm_params.py | 9 +- litellm/litellm_core_utils/litellm_logging.py | 120 +- .../litellm_core_utils/llm_cost_calc/utils.py | 53 +- .../convert_dict_to_response.py | 18 + litellm/litellm_core_utils/logging_worker.py | 22 + .../litellm_core_utils/model_param_helper.py | 12 +- .../prompt_templates/factory.py | 126 +- .../prompt_templates/image_handling.py | 14 +- .../chat/guardrail_translation/handler.py | 2 +- litellm/llms/anthropic/chat/transformation.py | 19 +- .../adapters/handler.py | 27 +- .../adapters/streaming_iterator.py | 37 +- .../adapters/transformation.py | 382 +- litellm/llms/azure/batches/handler.py | 23 +- .../llms/azure/chat/gpt_5_transformation.py | 8 +- litellm/llms/azure/cost_calculation.py | 37 +- .../anthropic/count_tokens/transformation.py | 14 +- .../azure_ai/azure_model_router/__init__.py | 4 + .../azure_model_router/transformation.py | 125 + litellm/llms/azure_ai/common_utils.py | 85 +- litellm/llms/azure_ai/cost_calculator.py | 121 + .../llms/azure_ai/rerank/transformation.py | 32 +- litellm/llms/base_llm/chat/transformation.py | 20 + .../base_llm/vector_store/transformation.py | 26 +- litellm/llms/bedrock/base_aws_llm.py | 2 +- .../bedrock/chat/converse_transformation.py | 133 +- litellm/llms/bedrock/chat/invoke_handler.py | 5 + .../anthropic_claude3_transformation.py | 37 +- litellm/llms/bedrock/common_utils.py | 23 +- .../bedrock/embed/cohere_transformation.py | 4 +- .../anthropic_claude3_transformation.py | 68 +- litellm/llms/bedrock/realtime/handler.py | 307 + .../llms/bedrock/realtime/transformation.py | 1156 + litellm/llms/cerebras/chat.py | 11 +- litellm/llms/custom_httpx/http_handler.py | 28 +- litellm/llms/custom_httpx/httpx_handler.py | 16 +- litellm/llms/custom_httpx/llm_http_handler.py | 36 +- litellm/llms/gemini/chat/transformation.py | 2 +- litellm/llms/gemini/files/transformation.py | 152 +- .../llms/gemini/image_edit/transformation.py | 5 +- .../gemini/image_generation/transformation.py | 2 + litellm/llms/gigachat/chat/transformation.py | 56 +- litellm/llms/groq/chat/transformation.py | 8 + .../llms/hosted_vllm/chat/transformation.py | 23 +- .../hosted_vllm/embedding/transformation.py | 180 + litellm/llms/minimax/chat/transformation.py | 29 +- litellm/llms/oci/chat/transformation.py | 3 +- .../llms/openai/chat/gpt_5_transformation.py | 4 +- .../image_generation/cost_calculator.py | 20 +- litellm/llms/openai/openai.py | 11 +- litellm/llms/openai_like/embedding/handler.py | 3 +- .../llms/openrouter/chat/transformation.py | 4 + litellm/llms/s3_vectors/__init__.py | 1 + .../llms/s3_vectors/vector_stores/__init__.py | 1 + .../vector_stores/transformation.py | 254 + .../vercel_ai_gateway/embedding/__init__.py | 0 .../embedding/transformation.py | 176 + litellm/llms/vertex_ai/common_utils.py | 2 +- .../vertex_ai_context_caching.py | 172 +- .../vertex_and_google_ai_studio_gemini.py | 17 + .../vertex_gemini_transformation.py | 2 + .../transformation.py | 25 + litellm/llms/vertex_ai/vertex_llm_base.py | 19 +- litellm/llms/watsonx/common_utils.py | 2 + litellm/llms/xai/responses/transformation.py | 129 +- litellm/llms/zai/chat/transformation.py | 16 +- litellm/main.py | 209 +- ...odel_prices_and_context_window_backup.json | 388 +- .../mcp_server/discoverable_endpoints.py | 172 +- .../mcp_server/mcp_server_manager.py | 43 +- .../mcp_server/semantic_tool_filter.py | 250 + .../proxy/_experimental/mcp_server/server.py | 81 +- litellm/proxy/_experimental/out/404.html | 1 + .../_buildManifest.js | 0 .../_ssgManifest.js | 0 .../static/chunks/1059-26bdac09bbb12a4b.js | 1 + .../static/chunks/1070-ab9dafb0fc6e0b85.js | 1 + .../static/chunks/1098-a1702da59647cf14.js | 1 + .../static/chunks/1098-c3e95c9684ff5e95.js | 1 - .../static/chunks/1108-8b678b0704cb239b.js | 1 + .../static/chunks/1108-c2d0c742b6e72436.js | 1 - .../static/chunks/1112-0b9bd4ebde18e77b.js | 1 + .../static/chunks/1128-64fa4a41ccaf67ea.js | 1 - .../static/chunks/1208-5caf6d9856cc3f13.js | 1 + .../static/chunks/137-c6f74fedf576a11b.js | 1 - .../static/chunks/1414-2770d1155b664522.js | 1 - .../static/chunks/1442-024f7e51804e0d7e.js | 1 - .../static/chunks/1658-c301cddaf7772753.js | 1 + .../static/chunks/1789-c534ff8966aa231a.js | 1 + .../static/chunks/1901-4d02d1f2a71cdbf7.js | 1 - .../static/chunks/1994-6637a121c9ee1602.js | 1 - .../static/chunks/2136-2c0d6e8c18d2c5c4.js | 1 - .../static/chunks/2202-a83ad035a17401aa.js | 1 - .../static/chunks/2227-5ae3f36b0a81c5b4.js | 1 - .../static/chunks/2318-8bec43289448e95d.js | 1 + .../static/chunks/2353-c94748c0aac514ff.js | 1 + .../static/chunks/2378-252212b7a5e313ce.js | 1 - .../static/chunks/2500-811f2612ec5f6830.js | 1 - .../static/chunks/2652-61deef051e2dc3b2.js | 1 + .../static/chunks/2699-38ff37315d78ae04.js | 1 - .../static/chunks/2820-592c2b4ff874a913.js | 1 - .../static/chunks/292-aaba6c4e7c8d416d.js | 1 - .../static/chunks/2926-a9cb83e61fc8ad20.js | 1 - .../static/chunks/2926-ac542d9fa707b8a4.js | 1 + .../static/chunks/3138-faa6fb0b1d7f2d67.js | 1 + .../static/chunks/3178-47bc3b9e8cf9bf6c.js | 1 + .../static/chunks/3242-6e6ec7e18f5d698d.js | 1 - .../static/chunks/3331-37f4428be6db0332.js | 1 + ...87e9e5b9fa.js => 3367-33bb84b3d3d247b2.js} | 2 +- ...149e9f461b3.js => 337-929caaa1bd1d68cc.js} | 0 .../static/chunks/3507-14fb4e6cd377d7da.js | 1 - ...6ea4730f9d4.js => 353-347e4836f09d94a0.js} | 0 .../static/chunks/3567-9a29feedd7b63950.js | 5 - .../static/chunks/3634-5083d080185955ff.js | 1 + ...32d3a3ac26.js => 3709-7f9257c8a6221d7f.js} | 0 .../static/chunks/3862-064a3fb795c75b62.js | 1 - .../static/chunks/3871-be6e9adb966e0429.js | 1 + .../static/chunks/3885-e5f4fc4a4724e9b8.js | 1 + .../static/chunks/4077-50cf2a28a79fdcd4.js | 1 - .../static/chunks/4105-9c3c0ee7c494102f.js | 1 - .../static/chunks/4306-f891b96cf0ee333b.js | 1 - .../static/chunks/4341-3e3f04c866417786.js | 1 + .../static/chunks/4388-2f4ca3419d20af67.js | 1 - .../static/chunks/4470-3ef8ade20eaf2875.js | 1 - .../static/chunks/4509-5bbcd014724651a9.js | 1 + .../static/chunks/4559-52ca85b2d8893149.js | 1 - .../static/chunks/4817-59d642defb0e86f2.js | 1 - .../static/chunks/4851-0dc9f6cfeabb43d0.js | 1 + .../static/chunks/4934-d937980b64b5dd57.js | 1 + .../static/chunks/5105-2998cbe1c9fc8ee4.js | 1 - .../static/chunks/5144-bbc18c43eade9aef.js | 1 - .../static/chunks/5144-ddfa7a8f89c5d465.js | 1 + .../static/chunks/5188-c6270da3b1debeb8.js | 1 + .../static/chunks/5202-60292daf4bc5c8fb.js | 1 - .../static/chunks/5238-3fa69435be59fb79.js | 1 + .../static/chunks/5276-22fb90a28ebcab8b.js | 1 + ...f2dc450f99.js => 5319-7f07d87ef011d5c9.js} | 0 .../static/chunks/5333-1540faf81c7d7006.js | 1 - .../static/chunks/536-8fae454c1d779890.js | 1 + ...56eb7463d6.js => 5510-99fb91d9d17e6ab4.js} | 2 +- .../static/chunks/5631-586d726ad939cea0.js | 1 + .../static/chunks/5695-dbbcbf2da21d2bab.js | 1 - .../static/chunks/5706-1e314cef9ea5c5d6.js | 1 - .../static/chunks/5706-b92e3cca4b167e71.js | 1 + .../static/chunks/5720-a8df9dd74eea4daa.js | 1 + .../static/chunks/5733-6e7eac59c8bc246c.js | 1 + .../static/chunks/5733-aa80f52062105ad2.js | 1 - .../static/chunks/5736-9031c5108cb49a26.js | 1 + .../static/chunks/5752-f504fb38ff5e13e8.js | 1 - ...ba6ad0ce0c.js => 5869-a383009914cbdb01.js} | 2 +- .../static/chunks/5945-8b3b7713d7f416a2.js | 1 - .../static/chunks/5975-60599e8984464729.js | 1 + .../static/chunks/5975-758334d6641b9c63.js | 1 - .../static/chunks/5992-287cec06808c74ae.js | 1 - .../static/chunks/5992-ee986583db978ba0.js | 1 + .../static/chunks/605-102c0e6d8bb7517c.js | 1 - .../static/chunks/6057-4eacff4874db3ebb.js | 1 + .../static/chunks/6213-6c1fab5854e4401f.js | 1 + .../static/chunks/6285-68f82c874b184eba.js | 1 - .../static/chunks/6399-3ed249931e03bab9.js | 1 + .../static/chunks/6399-ccf9cdbdcd5f7abb.js | 1 - .../static/chunks/6537-f70f2c4278e93458.js | 1 - .../static/chunks/6554-265013ca56622e1f.js | 1 + ...dc66d8b41a.js => 6600-077d81439e75d3a3.js} | 2 +- ...58ffbe3786.js => 6609-a69ca4ee5a2c4a9d.js} | 2 +- .../static/chunks/665-d94073042ee5b874.js | 1 - .../static/chunks/665-f361bd1c21e3bf25.js | 1 + .../static/chunks/6653-e61fdc06093fc0a8.js | 1 - .../static/chunks/6697-c1306587e479be83.js | 1 + .../static/chunks/6728-a6b270885bc8863f.js | 1 + .../static/chunks/6868-c5f994b9d687f7b6.js | 1 + .../static/chunks/6891-4d6d997a2bca3514.js | 1 + ...c32dc0de5f.js => 6894-8c74216e23aa271e.js} | 4 +- .../static/chunks/6988-27c1a5ab5702ba23.js | 1 + .../static/chunks/7138-5b134dc8ad670770.js | 1 - .../static/chunks/7187-ee86be841e859eb1.js | 1 + .../static/chunks/730-6158e287ec72cfda.js | 1 + .../static/chunks/7474-79e3343f32c7e661.js | 1 + .../static/chunks/7526-da6b2857a3ca248d.js | 1 - .../static/chunks/7526-f6a7e2b51a17dd02.js | 1 + .../static/chunks/766-baf0336e8ba5c686.js | 1 - .../static/chunks/7688-ca173ea41812cf94.js | 1 - .../static/chunks/7794-37e92993b04b6bb9.js | 1 + .../static/chunks/7799-a8559d23e5deb5b9.js | 1 + ...c6f11bb939.js => 7840-0952e7293502ce83.js} | 2 +- .../static/chunks/7851-c10cbe6fcac2f9d6.js | 1 - ...0db59c8efa.js => 7906-1b1cdd8da2773bb2.js} | 2 +- .../static/chunks/7914-25af99af34bee64b.js | 1 + .../static/chunks/7926-108623e14caeb770.js | 1 - .../static/chunks/7967-1ac5097c3d83016f.js | 1 + .../static/chunks/7971-76912e9c9a840367.js | 1 + .../static/chunks/7980-b52a05c1635a1a59.js | 1 + .../static/chunks/8014-d6138fce46bba1e2.js | 1 - .../static/chunks/8049-98da62d72b2b7dad.js | 1 + .../static/chunks/8049-cb52b16664f13e28.js | 1 - .../static/chunks/8071-afd8213d652a649a.js | 1 + .../static/chunks/8135-881fe2cea0032570.js | 1 - ...f059e9ed27.js => 8143-774574f553d5fa4b.js} | 2 +- ...b39f4e7ece1.js => 816-924f34bbf6b36a05.js} | 2 +- .../static/chunks/8211-8dd5691abf54d0ca.js | 1 - .../static/chunks/831-26544e9debf34eba.js | 1 + .../static/chunks/8473-7749355a9a4b1818.js | 1 - .../static/chunks/8529-6b66d5dba2148164.js | 1 - .../static/chunks/8565-5c05f6bbb9d0662f.js | 1 - .../static/chunks/8582-3a775364dbf07fa8.js | 1 - .../static/chunks/8745-83ff3a8036a70abb.js | 1 + .../static/chunks/896-94547c54b334065c.js | 1 + .../static/chunks/9039-2037889778daf211.js | 1 + .../static/chunks/9039-e44ff08ca4f37a12.js | 1 - .../static/chunks/9078-e3b627680692b3fd.js | 5 + .../static/chunks/9120-dc2d8129a3d2175b.js | 1 + .../static/chunks/9140-09af618948244b82.js | 1 - .../static/chunks/9145-9507437d5b599cea.js | 1 - .../static/chunks/9190-e32c76b5b1affa7b.js | 1 + .../static/chunks/9258-6907841794d6c1e1.js | 1 + .../static/chunks/9264-5009b962427411a5.js | 1 + .../static/chunks/9264-fd8ab51d702e9535.js | 1 - .../static/chunks/9271-e8c50ba458178f1c.js | 1 + .../static/chunks/9409-6eefc92a7f8433ff.js | 1 - .../static/chunks/9584-4d5bef7e60cfea45.js | 1 + .../static/chunks/9584-9d4fd7b3d6a7c9e7.js | 1 - .../static/chunks/9818-6f03d7efd4fb8533.js | 1 - .../static/chunks/9841-721a173be76941d1.js | 1 - ...e67b671303.js => page-2a4be488cfb5b0d1.js} | 2 +- .../api-playground/page-67c4f150eba92e64.js | 1 - .../api-playground/page-b8b443caa67af654.js | 1 + .../budgets/page-ae754695901b9376.js | 1 + .../budgets/page-e471b9b73cfc894b.js | 1 - .../caching/page-a570d0f7ab5db7bf.js | 1 + .../caching/page-d31cc105402ab7e0.js | 1 - .../page-84a3290b0c10981d.js | 1 + .../old-usage/page-1e4535b4f65e91c3.js | 1 + .../old-usage/page-5e097dbb8ce40bb4.js | 1 - .../prompts/page-67bc04a61159c00a.js | 1 + .../prompts/page-8236f1efda3366f0.js | 1 - .../tag-management/page-5627fd94813402eb.js | 1 - .../tag-management/page-f6f7f1dd17bed0fe.js | 1 + .../guardrails/page-060e61cb783d32ef.js | 1 - .../guardrails/page-1528b2c6a3288963.js | 1 + .../(dashboard)/layout-534e351316fbcd53.js | 1 + .../(dashboard)/layout-f7f722423efd1c5b.js | 1 - ...6ca991b83e.js => page-a6b6031fb32f8582.js} | 2 +- .../model-hub/page-1479dcb217587498.js | 1 - .../model-hub/page-37f3c43872246b40.js | 1 + .../page-b69988590beaa5c8.js | 1 - .../page-c3af9027b254a3f0.js | 1 + .../organizations/page-56a03e123f452d60.js | 1 - .../organizations/page-95fa0a5eac5056b4.js | 1 + .../playground/page-e2680b62dbb22cd9.js | 1 + .../playground/page-fc3dff494dc4db08.js | 1 - .../policies/page-43fedb527f6a4b39.js | 1 + .../admin-settings/page-746658933a633902.js | 1 - .../admin-settings/page-d0bae1a3ceef1920.js | 1 + .../page-194a2419931e7649.js | 1 + .../page-e3df74ef5ac0dcd5.js | 1 - .../router-settings/page-53d06fb7df656af3.js | 1 + .../router-settings/page-ffa3245ebcbbc02a.js | 1 - .../ui-theme/page-612e275485550e83.js | 1 + .../ui-theme/page-d833946961b065a5.js | 1 - .../teams/page-35fb23c26a99119e.js | 1 + .../teams/page-8d1a71afa9e9ff16.js | 1 - .../test-key/page-63cd64b722408984.js | 1 - .../test-key/page-a02455ca29fab29f.js | 1 + .../mcp-servers/page-7dd2ea6f1433d41f.js | 1 - .../mcp-servers/page-eeef4bac80ed234b.js | 1 + .../vector-stores/page-0f618cc2d6cae794.js | 1 - .../vector-stores/page-a8da9d9d1d928bc0.js | 1 + ...354d44803b.js => page-f5988c9f9087fca8.js} | 2 +- .../users/page-6d3643cef6c068ee.js | 1 - .../users/page-993c131fdcb59c92.js | 1 + ...cb0cfe86b7.js => page-85cb1e2f0392d6e5.js} | 2 +- .../chunks/app/login/page-61bfa80619b62f6b.js | 1 - .../chunks/app/login/page-e40d110cdbc26a70.js | 1 + .../app/model_hub/page-649f32c699b27a45.js | 1 + .../app/model_hub/page-92347d2021ca8580.js | 1 - .../model_hub_table/page-2ad344049541235f.js | 1 - .../model_hub_table/page-81008adc04402b54.js | 1 + ...bf068f8157.js => page-e8604d757e270b09.js} | 2 +- .../chunks/app/page-587b0acf34f8c747.js | 1 - .../chunks/app/page-850191a6e6250635.js | 1 + ...e4dfadea03.js => main-3f67160ac20e4399.js} | 2 +- .../out/_next/static/css/4fd2d0c1b251ee22.css | 3 + .../out/_next/static/css/83c095d0528a2e35.css | 3 - .../_experimental/out/api-reference.html | 1 + .../proxy/_experimental/out/api-reference.txt | 6 +- .../out/assets/logos/s3_vector.png | Bin 0 -> 191076 bytes .../index.html => api-playground.html} | 2 +- .../out/experimental/api-playground.txt | 6 +- .../out/experimental/budgets.html | 1 + .../out/experimental/budgets.txt | 6 +- .../out/experimental/budgets/index.html | 1 - .../out/experimental/caching.html | 1 + .../out/experimental/caching.txt | 6 +- .../out/experimental/caching/index.html | 1 - .../out/experimental/claude-code-plugins.html | 1 + .../out/experimental/claude-code-plugins.txt | 13 + .../out/experimental/old-usage.html | 1 + .../out/experimental/old-usage.txt | 6 +- .../out/experimental/old-usage/index.html | 1 - .../out/experimental/prompts.html | 1 + .../out/experimental/prompts.txt | 6 +- .../out/experimental/tag-management.html | 1 + .../out/experimental/tag-management.txt | 6 +- .../experimental/tag-management/index.html | 1 - .../proxy/_experimental/out/guardrails.html | 1 + .../proxy/_experimental/out/guardrails.txt | 6 +- litellm/proxy/_experimental/out/index.html | 2 +- litellm/proxy/_experimental/out/index.txt | 4 +- litellm/proxy/_experimental/out/login.html | 1 + litellm/proxy/_experimental/out/login.txt | 4 +- .../proxy/_experimental/out/login/index.html | 1 - litellm/proxy/_experimental/out/logs.html | 1 + litellm/proxy/_experimental/out/logs.txt | 6 +- .../proxy/_experimental/out/logs/index.html | 1 - .../{callback/index.html => callback.html} | 2 +- .../_experimental/out/mcp/oauth/callback.txt | 2 +- .../index.html => model-hub.html} | 2 +- litellm/proxy/_experimental/out/model-hub.txt | 6 +- .../proxy/_experimental/out/model_hub.html | 1 + litellm/proxy/_experimental/out/model_hub.txt | 4 +- .../index.html => model_hub_table.html} | 2 +- .../_experimental/out/model_hub_table.txt | 4 +- .../index.html => models-and-endpoints.html} | 2 +- .../out/models-and-endpoints.txt | 6 +- .../proxy/_experimental/out/onboarding.html | 1 + .../proxy/_experimental/out/onboarding.txt | 4 +- .../_experimental/out/organizations.html | 1 + .../proxy/_experimental/out/organizations.txt | 6 +- .../out/organizations/index.html | 1 - .../proxy/_experimental/out/playground.html | 1 + .../proxy/_experimental/out/playground.txt | 6 +- .../_experimental/out/playground/index.html | 1 - .../index.html => policies.html} | 2 +- litellm/proxy/_experimental/out/policies.txt | 13 + .../out/settings/admin-settings.html | 1 + .../out/settings/admin-settings.txt | 6 +- .../out/settings/admin-settings/index.html | 1 - .../out/settings/logging-and-alerts.html | 1 + .../out/settings/logging-and-alerts.txt | 6 +- .../settings/logging-and-alerts/index.html | 1 - .../router-settings.html} | 2 +- .../out/settings/router-settings.txt | 6 +- .../_experimental/out/settings/ui-theme.html | 1 + .../_experimental/out/settings/ui-theme.txt | 6 +- .../out/settings/ui-theme/index.html | 1 - litellm/proxy/_experimental/out/teams.html | 1 + litellm/proxy/_experimental/out/teams.txt | 6 +- .../proxy/_experimental/out/teams/index.html | 1 - .../index.html => test-key.html} | 2 +- litellm/proxy/_experimental/out/test-key.txt | 6 +- .../_experimental/out/test-key/index.html | 1 - .../_experimental/out/tools/mcp-servers.html | 1 + .../_experimental/out/tools/mcp-servers.txt | 6 +- .../out/tools/mcp-servers/index.html | 1 - .../out/tools/vector-stores.html | 1 + .../_experimental/out/tools/vector-stores.txt | 6 +- .../out/tools/vector-stores/index.html | 1 - litellm/proxy/_experimental/out/usage.html | 1 + litellm/proxy/_experimental/out/usage.txt | 6 +- .../out/{model-hub/index.html => users.html} | 2 +- litellm/proxy/_experimental/out/users.txt | 6 +- .../proxy/_experimental/out/users/index.html | 1 - .../proxy/_experimental/out/virtual-keys.html | 1 + .../proxy/_experimental/out/virtual-keys.txt | 6 +- .../_experimental/out/virtual-keys/index.html | 1 - litellm/proxy/_types.py | 46 +- .../proxy/agent_endpoints/a2a_endpoints.py | 9 + litellm/proxy/auth/auth_checks.py | 195 +- litellm/proxy/auth/auth_utils.py | 20 +- litellm/proxy/auth/login_utils.py | 62 - litellm/proxy/auth/route_checks.py | 10 +- litellm/proxy/auth/user_api_key_auth.py | 174 +- litellm/proxy/batches_endpoints/endpoints.py | 95 +- litellm/proxy/cached_logo.jpg | Bin 50535 -> 24694 bytes litellm/proxy/client/cli/commands/auth.py | 239 +- litellm/proxy/common_request_processing.py | 200 +- .../proxy/common_utils/cache_coordinator.py | 192 + litellm/proxy/common_utils/callback_utils.py | 45 +- .../proxy/common_utils/http_parsing_utils.py | 97 + .../example_config_yaml/otel_test_config.yaml | 4 +- .../spend_tracking_config.yaml | 2 +- litellm/proxy/google_endpoints/endpoints.py | 2 +- .../litellm_content_filter/content_filter.py | 170 +- .../litellm_content_filter/patterns.json | 12 +- .../model_armor/model_armor.py | 44 +- .../guardrails/guardrail_hooks/onyx/onyx.py | 7 +- .../guardrails/guardrail_hooks/presidio.py | 143 +- .../health_endpoints/_health_endpoints.py | 47 +- .../hooks/mcp_semantic_filter/ARCHITECTURE.md | 96 + .../hooks/mcp_semantic_filter/__init__.py | 9 + .../proxy/hooks/mcp_semantic_filter/hook.py | 353 + litellm/proxy/litellm_pre_call_utils.py | 65 +- .../internal_user_endpoints.py | 4 +- .../key_management_endpoints.py | 531 + .../mcp_management_endpoints.py | 23 +- .../management_endpoints/scim/scim_v2.py | 302 + .../management_endpoints/team_endpoints.py | 140 +- litellm/proxy/management_endpoints/ui_sso.py | 97 +- .../openai_files_endpoints/files_endpoints.py | 69 +- .../llm_passthrough_endpoints.py | 153 +- litellm/proxy/proxy_cli.py | 5 +- litellm/proxy/proxy_config.yaml | 149 +- litellm/proxy/proxy_server.py | 1116 +- litellm/proxy/rag_endpoints/endpoints.py | 210 + .../proxy/response_api_endpoints/endpoints.py | 2 + .../proxy/response_polling/polling_handler.py | 12 +- litellm/proxy/route_llm_request.py | 13 +- litellm/proxy/schema.prisma | 6 + litellm/proxy/search_endpoints/endpoints.py | 82 +- .../spend_management_endpoints.py | 9 + .../spend_tracking/spend_tracking_utils.py | 17 +- .../proxy_setting_endpoints.py | 18 +- litellm/proxy/utils.py | 91 +- .../proxy/vector_store_endpoints/endpoints.py | 53 +- .../management_endpoints.py | 418 +- litellm/rag/ingestion/base_ingestion.py | 17 +- .../rag/ingestion/file_parsers/__init__.py | 9 + .../rag/ingestion/file_parsers/pdf_parser.py | 70 + litellm/rag/ingestion/s3_vectors_ingestion.py | 573 + litellm/rag/main.py | 2 + litellm/realtime_api/main.py | 39 +- .../transformation.py | 9 +- litellm/responses/main.py | 10 + litellm/responses/utils.py | 17 +- litellm/router.py | 340 +- litellm/router_utils/get_retry_from_policy.py | 10 +- .../pre_call_checks/model_rate_limit_check.py | 373 + litellm/router_utils/search_api_router.py | 3 +- litellm/secret_managers/main.py | 20 +- litellm/types/guardrails.py | 25 +- .../integrations/datadog_cost_management.py | 27 + litellm/types/integrations/prometheus.py | 100 + litellm/types/llms/anthropic.py | 8 +- litellm/types/llms/bedrock.py | 86 + litellm/types/llms/custom_http.py | 1 + litellm/types/llms/oci.py | 9 +- litellm/types/llms/openai.py | 6 +- litellm/types/llms/xai.py | 23 + .../proxy/guardrails/guardrail_hooks/onyx.py | 5 + .../key_management_endpoints.py | 42 + .../proxy/management_endpoints/scim_v2.py | 66 +- .../proxy/management_endpoints/ui_sso.py | 20 + litellm/types/rag.py | 46 +- litellm/types/router.py | 42 +- litellm/types/utils.py | 37 +- litellm/types/vector_stores.py | 4 + litellm/utils.py | 158 +- model_prices_and_context_window.json | 388 +- poetry.lock | 114 +- provider_endpoints_support.json | 20 + proxy_config.yaml | 101 - proxy_server_config.yaml | 2 +- pyproject.toml | 11 +- requirements.txt | 5 +- schema.prisma | 6 + .../test_batches_logging_unit_tests.py | 339 + tests/code_coverage_tests/liccheck.ini | 1 + tests/code_coverage_tests/license_cache.json | 2 +- .../router_code_coverage.py | 5 +- .../test_prometheus_logging_callbacks.py | 48 +- .../integrations/test_prometheus.py | 118 + .../proxy/auth/test_route_checks.py | 65 + .../proxy/hooks/test_managed_files.py | 650 +- tests/guardrails_tests/test_lakera_v2.py | 74 +- .../test_litellm_proxy_extras_utils.py | 5 + .../oci/chat/test_oci_chat_transformation.py | 37 +- .../mcp_server/test_discoverable_endpoints.py | 1108 + .../test_stream_chunk_builder_images.py | 242 + .../test_bedrock_converse_dedup_factory.py | 332 + tests/llm_translation/test_azure_ai.py | 28 +- .../test_bedrock_anthropic_regression.py | 526 + .../test_bedrock_completion.py | 282 + tests/llm_translation/test_gemini.py | 17 + tests/llm_translation/test_gigachat.py | 108 +- tests/llm_translation/test_groq.py | 126 +- tests/llm_translation/test_optional_params.py | 2 +- tests/llm_translation/test_xai.py | 26 + tests/local_testing/test_add_update_models.py | 2 + .../test_amazing_vertex_completion.py | 130 +- tests/local_testing/test_auth_utils.py | 19 + ...test_batch_completion_return_exceptions.py | 12 +- tests/local_testing/test_completion.py | 7 +- .../test_completion_with_retries.py | 1 + tests/local_testing/test_literalai.py | 72 - tests/local_testing/test_timeout.py | 91 - .../mcp_tests/test_aresponses_api_with_mcp.py | 6 +- tests/mcp_tests/test_mcp_client_unit.py | 4 +- tests/mcp_tests/test_mcp_server.py | 21 +- .../test_semantic_tool_filter_e2e.py | 89 + tests/otel_tests/test_prometheus.py | 134 +- .../test_anthropic_messages_tool_search.py | 20 +- .../test_key_management.py | 56 +- .../test_claude_agent_sdk.py | 124 + .../test_config.yaml | 31 + tests/proxy_unit_tests/test_auth_checks.py | 4 +- tests/proxy_unit_tests/test_get_image.py | 89 + .../test_proxy_custom_logger.py | 107 +- tests/proxy_unit_tests/test_proxy_server.py | 18 +- .../test_response_polling_handler.py | 136 + .../proxy_unit_tests/test_server_root_path.py | 64 + .../test_zero_cost_model_budget_bypass.py | 590 + .../test_router_acancel_batch.py | 53 + .../test_router_helper_utils.py | 29 + .../a2a_protocol/test_card_resolver.py | 69 + .../containers/test_container_api.py | 122 +- .../containers/test_container_integration.py | 24 +- .../send_emails/test_resend_email.py | 128 +- .../send_emails/test_sendgrid_email.py | 66 +- .../test_mcp_client.py | 72 +- .../google_genai/test_google_genai_adapter.py | 4 +- .../google_genai/test_google_genai_handler.py | 19 +- .../integrations/arize/test_arize_utils.py | 21 +- .../datadog/test_datadog_cost_management.py | 169 + .../datadog/test_datadog_llm_obs_agent.py | 62 + .../test_custom_guardrail_recursion.py | 73 + .../test_prometheus_client_ip_user_agent.py | 203 + .../integrations/test_prometheus_labels.py | 164 +- .../test_prometheus_missing_metrics.py | 77 + .../integrations/test_prometheus_services.py | 58 + .../llm_cost_calc/test_llm_cost_calc_utils.py | 6 +- ...llm_core_utils_prompt_templates_factory.py | 83 +- .../litellm_core_utils/test_image_handling.py | 79 + .../test_litellm_logging.py | 297 + .../test_anthropic_chat_transformation.py | 55 +- ...al_pass_through_adapters_transformation.py | 598 + .../chat/test_azure_gpt5_transformation.py | 11 + .../llms/azure/test_azure_common_utils.py | 3 +- ...e_anthropic_count_tokens_transformation.py | 111 + .../test_azure_ai_rerank_transformation.py | 100 + .../llms/azure_ai/test_cost_calculator.py | 346 + ...ations_anthropic_claude3_transformation.py | 290 + .../test_bedrock_realtime_transformation.py | 646 + .../bedrock/test_anthropic_beta_support.py | 100 + .../llms/custom_httpx/test_http_handler.py | 84 + .../test_gemini_image_edit_transformation.py | 2 +- .../test_hosted_vllm_chat_transformation.py | 96 +- .../chat/test_hosted_vllm_ssl_verify.py | 152 + .../test_hosted_vllm_embedding_ssl_verify.py | 140 + .../llms/openai/test_gpt5_transformation.py | 17 + .../llms/openai_like/embedding/__init__.py | 1 + .../embedding/test_openai_like_embedding.py | 378 + .../test_litellm/llms/s3_vectors/__init__.py | 1 + .../llms/s3_vectors/vector_stores/__init__.py | 1 + .../test_s3_vectors_transformation.py | 115 + .../llms/test_cache_control_and_reasoning.py | 281 + .../vercel_ai_gateway/embedding/__init__.py | 0 .../test_vercel_ai_gateway_embedding.py | 218 + .../test_vertex_ai_context_caching.py | 353 + ...test_vertex_and_google_ai_studio_gemini.py | 105 + ...rtex_ai_image_generation_transformation.py | 41 + .../vertex_ai/test_vertex_ai_common_utils.py | 30 + ..._vertex_ai_anthropic_image_url_handling.py | 192 + ...partner_models_anthropic_transformation.py | 33 +- .../vertex_ai_partner_models/qwen/__init__.py | 0 .../test_vertex_ai_qwen_global_endpoint.py | 263 + .../llms/watsonx/test_watsonx_common_utils.py | 242 + .../test_xai_responses_transformation.py | 213 +- .../mcp_server/test_mcp_server_manager.py | 2 +- .../mcp_server/test_mcp_stale_session.py | 289 + .../mcp_server/test_semantic_tool_filter.py | 394 + .../proxy/auth/test_auth_checks.py | 117 +- .../proxy/auth/test_auth_utils.py | 23 + .../test_litellm/proxy/auth/test_cli_auth.py | 203 + .../proxy/auth/test_login_utils.py | 411 +- .../proxy/auth/test_route_checks.py | 4 + .../test_interactions_agent_param.py | 75 + .../content_filter/test_content_filter.py | 84 +- .../guardrail_hooks/test_model_armor.py | 308 +- .../guardrails/guardrail_hooks/test_onyx.py | 302 +- .../guardrail_hooks/test_presidio.py | 147 +- .../health_endpoints/test_health_endpoints.py | 170 + .../test_post_call_response_headers_hook.py | 197 + ...st_post_call_streaming_hook_integration.py | 273 + ...test_post_call_success_hook_integration.py | 260 + .../scim/test_scim_v2_discovery.py | 300 + .../test_internal_user_endpoints.py | 54 + .../test_key_management_endpoints.py | 1174 +- .../test_team_endpoints.py | 214 + .../proxy/management_endpoints/test_ui_sso.py | 113 + .../test_files_endpoint.py | 178 + .../test_llm_pass_through_endpoints.py | 176 + .../test_vertex_passthrough_load_balancing.py | 72 + .../test_spend_management_endpoints.py | 83 + .../test_spend_tracking_utils.py | 46 + .../proxy/test_common_request_processing.py | 305 +- .../proxy/test_litellm_pre_call_utils.py | 47 + tests/test_litellm/proxy/test_proxy_cli.py | 7 +- tests/test_litellm/proxy/test_proxy_server.py | 1033 +- .../proxy/test_response_model_sanitization.py | 217 + .../proxy/test_route_llm_request.py | 82 +- .../test_vector_store_access_control.py | 87 + .../test_vector_store_endpoints.py | 469 + .../test_litellm_completion_responses.py | 71 + .../responses/test_responses_utils.py | 43 + .../test_secret_managers_main.py | 69 +- tests/test_litellm/test_cost_calculator.py | 345 +- .../test_litellm/test_eager_tiktoken_load.py | 29 + .../test_gpt_image_cost_calculator.py | 68 + tests/test_litellm/test_main.py | 53 +- tests/test_litellm/test_model_param_helper.py | 33 + .../test_responses_api_bridge_non_stream.py | 380 +- .../test_enforce_model_rate_limits.py | 315 + .../test_router_per_deployment_num_retries.py | 57 +- tests/test_litellm/test_router_redis_init.py | 56 + .../test_router_silent_experiment.py | 214 + tests/test_litellm/test_utils.py | 69 +- tests/test_litellm/test_video_generation.py | 86 +- .../test_xai_responses_auto_routing.py | 264 + ...test_vector_store_create_provider_logic.py | 19 +- .../test_vector_store_registry.py | 34 +- tests/test_otel_thread_leak.py | 90 + tests/test_presidio_latency.py | 73 + tests/test_team.py | 1 + .../rag/test_rag_s3_vectors.py | 107 + .../test_s3_vectors_vector_store.py | 42 + ui/litellm-dashboard/next.config.mjs | 16 +- ui/litellm-dashboard/package-lock.json | 17384 ++-------------- ui/litellm-dashboard/package.json | 16 +- .../public/assets/logos/s3_vector.png | Bin 0 -> 191076 bytes .../components/SidebarProvider.tsx | 44 +- .../hooks/models/useModels.test.ts | 14 +- .../app/(dashboard)/hooks/models/useModels.ts | 9 +- .../hooks/proxyConfig/useProxyConfig.test.ts | 554 + .../hooks/proxyConfig/useProxyConfig.ts | 180 + .../(dashboard)/hooks/sso/useSSOSettings.ts | 5 + .../useStoreRequestInSpendLogs.ts | 63 + .../(dashboard)/hooks/useAuthorized.test.ts | 61 +- .../app/(dashboard)/hooks/useAuthorized.ts | 16 +- .../hooks/useDisableShowPrompts.ts | 35 + .../src/app/(dashboard)/layout.tsx | 16 +- .../ModelsAndEndpointsView.tsx | 1 - .../components/AllModelsTab.test.tsx | 22 +- .../components/AllModelsTab.tsx | 162 +- .../src/app/(dashboard)/policies/page.tsx | 20 - .../components/modals/CreateTeamModal.tsx | 48 +- ui/litellm-dashboard/src/app/globals.css | 28 - .../src/app/mcp/oauth/callback/page.tsx | 12 +- .../src/app/model_hub/page.tsx | 17 +- .../src/app/model_hub_table/page.tsx | 17 +- .../src/app/onboarding/page.tsx | 12 +- ui/litellm-dashboard/src/app/page.tsx | 445 +- .../src/components/BulkEditUsers.test.tsx | 343 + .../{bulk_edit_user.tsx => BulkEditUsers.tsx} | 10 +- .../EntityUsageExport/ExportTypeSelector.tsx | 10 +- .../src/components/EntityUsageExport/types.ts | 2 +- .../EntityUsageExport/utils.test.ts | 560 + .../src/components/EntityUsageExport/utils.ts | 91 + .../ModelSelect/ModelSelect.test.tsx | 682 +- .../components/ModelSelect/ModelSelect.tsx | 22 +- .../CommunityEngagementButtons.test.tsx | 50 + .../CommunityEngagementButtons.tsx | 36 + .../Navbar/UserDropdown/UserDropdown.test.tsx | 289 + .../Navbar/UserDropdown/UserDropdown.tsx | 161 + .../src/components/OldTeams.tsx | 37 + .../Modals/BaseSSOSettingsForm.test.tsx | 111 + .../Modals/BaseSSOSettingsForm.tsx | 37 + .../Modals/DeleteSSOSettingsModal.tsx | 1 + .../Modals/EditSSOSettingsModal.test.tsx | 106 + .../Modals/EditSSOSettingsModal.tsx | 11 + .../AdminSettings/SSOSettings/SSOSettings.tsx | 22 +- .../AdminSettings/SSOSettings/utils.test.ts | 144 + .../AdminSettings/SSOSettings/utils.ts | 14 +- .../UISettings/PageVisibilitySettings.tsx | 146 + .../AdminSettings/UISettings/UISettings.tsx | 25 +- .../Fallbacks/Fallbacks.test.tsx | 6 +- .../src/components/TeamSSOSettings.test.tsx | 636 +- .../src/components/TeamSSOSettings.tsx | 22 +- .../components/KeyModelUsageView.test.tsx | 298 + .../components/KeyModelUsageView.tsx | 108 + .../src/components/UsagePage/types.ts | 10 + .../VirtualKeysPage/VirtualKeysTable.test.tsx | 89 +- .../VirtualKeysPage/VirtualKeysTable.tsx | 75 +- .../src/components/activity_metrics.tsx | 82 +- .../DeleteResourceModal.test.tsx | 187 +- .../common_components/DeleteResourceModal.tsx | 34 +- .../KeyLifecycleSettings.test.tsx | 383 + .../KeyLifecycleSettings.tsx | 12 +- .../common_components/NewBadge.test.tsx | 35 + .../components/common_components/NewBadge.tsx | 12 +- .../RouterSettingsAccordion.tsx | 373 + .../TableHeaderSortDropdown.test.tsx | 148 + .../TableHeaderSortDropdown.tsx | 85 + .../guardrails/guardrail_info.test.tsx | 119 + .../components/guardrails/guardrail_info.tsx | 18 +- .../src/components/leftnav.tsx | 587 +- .../src/components/mcp_tools/mcp_connect.tsx | 28 +- .../model_dashboard/all_models_table.tsx | 204 + .../src/components/model_info_view.test.tsx | 304 +- .../src/components/model_info_view.tsx | 119 +- .../src/components/molecules/filter.test.tsx | 127 + .../src/components/molecules/filter.tsx | 1 + .../components/molecules/models/columns.tsx | 561 +- .../src/components/navbar.test.tsx | 83 +- .../src/components/navbar.tsx | 204 +- .../src/components/networking.tsx | 165 +- .../organisms/create_key_button.tsx | 91 +- .../organization/organization_view.tsx | 36 +- .../src/components/page_metadata.ts | 44 + .../src/components/page_utils.test.ts | 241 + .../src/components/page_utils.ts | 75 + .../playground/chat_ui/CodeSnippets.test.tsx | 3 +- .../playground/compareUI/CompareUI.tsx | 1 + .../llm_calls/chat_completion.test.tsx | 4 +- .../llm_calls/responses_api.test.tsx | 29 +- .../policies/add_attachment_form.tsx | 48 +- .../components/policies/add_policy_form.tsx | 40 +- .../src/components/policies/index.tsx | 73 +- .../src/components/policies/policy_info.tsx | 8 +- .../prompts/prompt_editor_view/utils.ts | 211 +- .../src/components/public_model_hub.tsx | 4 + .../ReliabilityRetriesSection.tsx | 8 +- .../router_settings/RouterSettingsForm.tsx | 84 + .../src/components/router_settings/index.tsx | 88 +- .../components/survey/NudgePrompt.test.tsx | 101 + .../src/components/survey/NudgePrompt.tsx | 73 +- .../src/components/team/team_info.test.tsx | 858 +- .../src/components/team/team_info.tsx | 158 +- .../components/templates/key_edit_view.tsx | 23 + .../components/templates/key_info_view.tsx | 89 +- .../components/templates/model_dashboard.tsx | 25 +- .../CreateVectorStore.test.tsx | 276 + .../CreateVectorStore.tsx | 434 + .../DocumentsTable.test.tsx | 102 + .../DocumentsTable.tsx | 98 + .../S3VectorsConfig.test.tsx | 203 + .../S3VectorsConfig.tsx | 192 + .../TestVectorStoreTab.test.tsx | 90 + .../TestVectorStoreTab.tsx | 75 + .../VectorStoreForm.tsx | 9 +- .../VectorStoreTable.test.tsx | 12 +- .../VectorStoreTable.tsx | 26 + .../vector_store_management/index.tsx | 56 +- .../vector_store_management/types.tsx | 45 +- .../src/components/vector_store_providers.tsx | 37 + .../view_logs/ConfigInfoMessage.tsx | 16 +- .../view_logs/CostBreakdownViewer.tsx | 50 +- .../GuardrailViewer/GuardrailViewer.test.tsx | 24 +- .../GuardrailViewer/GuardrailViewer.tsx | 92 +- .../LogDetailsDrawer/CollapsibleMessage.tsx | 88 + .../LogDetailsDrawer/DrawerHeader.tsx | 205 + .../LogDetailsDrawer/HistorySection.tsx | 62 + .../LogDetailsDrawer/HistoryTree.tsx | 83 + .../view_logs/LogDetailsDrawer/InputCard.tsx | 92 + .../view_logs/LogDetailsDrawer/JsonViewer.tsx | 35 + .../LogDetailsDrawer/LogDetailsDrawer.tsx | 507 + .../LogDetailsDrawer/MessageBlock.tsx | 104 + .../LogDetailsDrawer/MessageCard.tsx | 199 + .../view_logs/LogDetailsDrawer/OutputCard.tsx | 104 + .../LogDetailsDrawer/PrettyMessagesView.tsx | 41 + .../LogDetailsDrawer/SectionHeader.tsx | 100 + .../LogDetailsDrawer/SimpleMessageBlock.tsx | 74 + .../LogDetailsDrawer/SimpleToolCallBlock.tsx | 65 + .../view_logs/LogDetailsDrawer/TokenFlow.tsx | 22 + .../LogDetailsDrawer/ToolCallBlock.tsx | 78 + .../LogDetailsDrawer/ToolCallCard.tsx | 79 + .../LogDetailsDrawer/TruncatedValue.tsx | 35 + .../view_logs/LogDetailsDrawer/constants.ts | 42 + .../view_logs/LogDetailsDrawer/index.ts | 2 + .../LogDetailsDrawer/prettyMessagesTypes.ts | 28 + .../LogDetailsDrawer/prettyMessagesUtils.ts | 126 + .../LogDetailsDrawer/useKeyboardNavigation.ts | 87 + .../view_logs/LogDetailsDrawer/utils.ts | 93 + .../SpendLogsSettingsModal.test.tsx | 484 + .../SpendLogsSettingsModal.tsx | 157 + .../ToolsSection/FormattedToolView.tsx | 124 + .../view_logs/ToolsSection/JsonToolView.tsx | 39 + .../ToolsSection/ToolExpandedContent.tsx | 52 + .../view_logs/ToolsSection/ToolItem.tsx | 74 + .../ToolsSection/ToolsSection.test.tsx | 117 + .../view_logs/ToolsSection/ToolsSection.tsx | 65 + .../view_logs/ToolsSection/index.ts | 7 + .../view_logs/ToolsSection/types.ts | 42 + .../view_logs/ToolsSection/utils.test.ts | 293 + .../view_logs/ToolsSection/utils.ts | 130 + .../view_logs/VectorStoreViewer.tsx | 38 +- .../src/components/view_logs/columns.tsx | 40 - .../src/components/view_logs/index.tsx | 123 +- .../components/view_logs/log_filter_logic.tsx | 6 +- .../src/components/view_logs/table.tsx | 24 +- .../src/components/view_users.tsx | 6 +- ui/litellm-dashboard/tsconfig.json | 29 +- ui/litellm-dashboard/tsconfig.tsbuildinfo | 2 +- ui/litellm-dashboard/vitest.config.ts | 1 + 910 files changed, 58767 insertions(+), 23869 deletions(-) create mode 100644 .github/workflows/test-model-map.yaml create mode 100644 .trivyignore create mode 100644 cookbook/anthropic_agent_sdk/README.md create mode 100644 cookbook/anthropic_agent_sdk/agent_with_mcp.py create mode 100644 cookbook/anthropic_agent_sdk/common.py create mode 100644 cookbook/anthropic_agent_sdk/config.example.yaml create mode 100644 cookbook/anthropic_agent_sdk/main.py create mode 100644 cookbook/anthropic_agent_sdk/requirements.txt create mode 100644 cookbook/nova_sonic_realtime.py create mode 100644 docs/my-website/blog/sub_millisecond_proxy_overhead/index.md create mode 100644 docs/my-website/docs/mcp_semantic_filter.md create mode 100644 docs/my-website/docs/providers/bedrock_realtime_with_audio.md create mode 100644 docs/my-website/docs/providers/sarvam.md create mode 100644 docs/my-website/docs/proxy/keys_teams_router_settings.md create mode 100644 docs/my-website/docs/proxy/request_tags.md create mode 100644 docs/my-website/docs/proxy/ui/page_visibility.md create mode 100644 docs/my-website/docs/proxy/ui_spend_log_settings.md create mode 100644 docs/my-website/docs/traffic_mirroring.md create mode 100644 docs/my-website/docs/troubleshoot/prisma_migrations.md create mode 100644 docs/my-website/docs/tutorials/claude_agent_sdk.md create mode 100644 docs/my-website/img/ui_granular_router_settings.png create mode 100644 docs/my-website/img/ui_spend_logs_settings.png create mode 100644 docs/my-website/img/ui_tools.png create mode 100644 docs/my-website/release_notes/v1.81.3-stable/index.md create mode 100644 docs/my-website/release_notes/v1.81.6.md create mode 100644 docs/my-website/src/theme/BlogListPage/index.js create mode 100644 docs/my-website/src/theme/BlogListPage/styles.module.css create mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.4.27-py3-none-any.whl create mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.4.27.tar.gz create mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.4.28-py3-none-any.whl create mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.4.28.tar.gz create mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.4.29-py3-none-any.whl create mode 100644 litellm-proxy-extras/dist/litellm_proxy_extras-0.4.29.tar.gz create mode 100644 litellm-proxy-extras/litellm_proxy_extras/migrations/20260131150814_add_team_user_to_vector_stores/migration.sql create mode 100644 litellm/a2a_protocol/card_resolver.py create mode 100644 litellm/integrations/braintrust_mock_client.py create mode 100644 litellm/integrations/datadog/datadog_cost_management.py create mode 100644 litellm/integrations/datadog/datadog_mock_client.py create mode 100644 litellm/integrations/helicone_mock_client.py create mode 100644 litellm/integrations/langsmith_mock_client.py create mode 100644 litellm/integrations/mock_client_factory.py create mode 100644 litellm/integrations/posthog_mock_client.py create mode 100644 litellm/llms/azure_ai/azure_model_router/__init__.py create mode 100644 litellm/llms/azure_ai/azure_model_router/transformation.py create mode 100644 litellm/llms/azure_ai/cost_calculator.py create mode 100644 litellm/llms/bedrock/realtime/handler.py create mode 100644 litellm/llms/bedrock/realtime/transformation.py create mode 100644 litellm/llms/hosted_vllm/embedding/transformation.py create mode 100644 litellm/llms/s3_vectors/__init__.py create mode 100644 litellm/llms/s3_vectors/vector_stores/__init__.py create mode 100644 litellm/llms/s3_vectors/vector_stores/transformation.py create mode 100644 litellm/llms/vercel_ai_gateway/embedding/__init__.py create mode 100644 litellm/llms/vercel_ai_gateway/embedding/transformation.py create mode 100644 litellm/proxy/_experimental/mcp_server/semantic_tool_filter.py create mode 100644 litellm/proxy/_experimental/out/404.html rename litellm/proxy/_experimental/out/_next/static/{zHD7JXLXiWgn1NPp82VmF => MkHZcSjEBwlJY7dIHtt6n}/_buildManifest.js (100%) rename litellm/proxy/_experimental/out/_next/static/{zHD7JXLXiWgn1NPp82VmF => MkHZcSjEBwlJY7dIHtt6n}/_ssgManifest.js (100%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1059-26bdac09bbb12a4b.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1070-ab9dafb0fc6e0b85.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1098-a1702da59647cf14.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1098-c3e95c9684ff5e95.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1108-8b678b0704cb239b.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1108-c2d0c742b6e72436.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1112-0b9bd4ebde18e77b.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1128-64fa4a41ccaf67ea.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1208-5caf6d9856cc3f13.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/137-c6f74fedf576a11b.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1414-2770d1155b664522.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1442-024f7e51804e0d7e.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1658-c301cddaf7772753.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1789-c534ff8966aa231a.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1901-4d02d1f2a71cdbf7.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1994-6637a121c9ee1602.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2136-2c0d6e8c18d2c5c4.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2202-a83ad035a17401aa.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2227-5ae3f36b0a81c5b4.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2318-8bec43289448e95d.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2353-c94748c0aac514ff.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2378-252212b7a5e313ce.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2500-811f2612ec5f6830.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2652-61deef051e2dc3b2.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2699-38ff37315d78ae04.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2820-592c2b4ff874a913.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/292-aaba6c4e7c8d416d.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2926-a9cb83e61fc8ad20.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2926-ac542d9fa707b8a4.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3138-faa6fb0b1d7f2d67.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3178-47bc3b9e8cf9bf6c.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3242-6e6ec7e18f5d698d.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3331-37f4428be6db0332.js rename litellm/proxy/_experimental/out/_next/static/chunks/{3367-58830187e9e5b9fa.js => 3367-33bb84b3d3d247b2.js} (98%) rename litellm/proxy/_experimental/out/_next/static/chunks/{337-bb33d149e9f461b3.js => 337-929caaa1bd1d68cc.js} (100%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3507-14fb4e6cd377d7da.js rename litellm/proxy/_experimental/out/_next/static/chunks/{353-e55516ea4730f9d4.js => 353-347e4836f09d94a0.js} (100%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3567-9a29feedd7b63950.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3634-5083d080185955ff.js rename litellm/proxy/_experimental/out/_next/static/chunks/{3709-34dbb332d3a3ac26.js => 3709-7f9257c8a6221d7f.js} (100%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3862-064a3fb795c75b62.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3871-be6e9adb966e0429.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3885-e5f4fc4a4724e9b8.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4077-50cf2a28a79fdcd4.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4105-9c3c0ee7c494102f.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4306-f891b96cf0ee333b.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4341-3e3f04c866417786.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4388-2f4ca3419d20af67.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4470-3ef8ade20eaf2875.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4509-5bbcd014724651a9.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4559-52ca85b2d8893149.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4817-59d642defb0e86f2.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4851-0dc9f6cfeabb43d0.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/4934-d937980b64b5dd57.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5105-2998cbe1c9fc8ee4.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5144-bbc18c43eade9aef.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5144-ddfa7a8f89c5d465.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5188-c6270da3b1debeb8.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5202-60292daf4bc5c8fb.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5238-3fa69435be59fb79.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5276-22fb90a28ebcab8b.js rename litellm/proxy/_experimental/out/_next/static/chunks/{5319-5b2d4bf2dc450f99.js => 5319-7f07d87ef011d5c9.js} (100%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5333-1540faf81c7d7006.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/536-8fae454c1d779890.js rename litellm/proxy/_experimental/out/_next/static/chunks/{2901-0cdd0656eb7463d6.js => 5510-99fb91d9d17e6ab4.js} (84%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5631-586d726ad939cea0.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5695-dbbcbf2da21d2bab.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5706-1e314cef9ea5c5d6.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5706-b92e3cca4b167e71.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5720-a8df9dd74eea4daa.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5733-6e7eac59c8bc246c.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5733-aa80f52062105ad2.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5736-9031c5108cb49a26.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5752-f504fb38ff5e13e8.js rename litellm/proxy/_experimental/out/_next/static/chunks/{5869-426268ba6ad0ce0c.js => 5869-a383009914cbdb01.js} (99%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5945-8b3b7713d7f416a2.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5975-60599e8984464729.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5975-758334d6641b9c63.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5992-287cec06808c74ae.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/5992-ee986583db978ba0.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/605-102c0e6d8bb7517c.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6057-4eacff4874db3ebb.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6213-6c1fab5854e4401f.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6285-68f82c874b184eba.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6399-3ed249931e03bab9.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6399-ccf9cdbdcd5f7abb.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6537-f70f2c4278e93458.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6554-265013ca56622e1f.js rename litellm/proxy/_experimental/out/_next/static/chunks/{6600-0ec5e2dc66d8b41a.js => 6600-077d81439e75d3a3.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{6609-3e081758ffbe3786.js => 6609-a69ca4ee5a2c4a9d.js} (99%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/665-d94073042ee5b874.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/665-f361bd1c21e3bf25.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6653-e61fdc06093fc0a8.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6697-c1306587e479be83.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6728-a6b270885bc8863f.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6868-c5f994b9d687f7b6.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6891-4d6d997a2bca3514.js rename litellm/proxy/_experimental/out/_next/static/chunks/{2068-2c78bfc32dc0de5f.js => 6894-8c74216e23aa271e.js} (89%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/6988-27c1a5ab5702ba23.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7138-5b134dc8ad670770.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7187-ee86be841e859eb1.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/730-6158e287ec72cfda.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7474-79e3343f32c7e661.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7526-da6b2857a3ca248d.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7526-f6a7e2b51a17dd02.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/766-baf0336e8ba5c686.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7688-ca173ea41812cf94.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7794-37e92993b04b6bb9.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7799-a8559d23e5deb5b9.js rename litellm/proxy/_experimental/out/_next/static/chunks/{2409-e94c05c6f11bb939.js => 7840-0952e7293502ce83.js} (78%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7851-c10cbe6fcac2f9d6.js rename litellm/proxy/_experimental/out/_next/static/chunks/{7906-59ba450db59c8efa.js => 7906-1b1cdd8da2773bb2.js} (99%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7914-25af99af34bee64b.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7926-108623e14caeb770.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7967-1ac5097c3d83016f.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7971-76912e9c9a840367.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/7980-b52a05c1635a1a59.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8014-d6138fce46bba1e2.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8049-98da62d72b2b7dad.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8049-cb52b16664f13e28.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8071-afd8213d652a649a.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8135-881fe2cea0032570.js rename litellm/proxy/_experimental/out/_next/static/chunks/{8143-9e4312f059e9ed27.js => 8143-774574f553d5fa4b.js} (99%) rename litellm/proxy/_experimental/out/_next/static/chunks/{816-37c57b39f4e7ece1.js => 816-924f34bbf6b36a05.js} (99%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8211-8dd5691abf54d0ca.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/831-26544e9debf34eba.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8473-7749355a9a4b1818.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8529-6b66d5dba2148164.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8565-5c05f6bbb9d0662f.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8582-3a775364dbf07fa8.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/8745-83ff3a8036a70abb.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/896-94547c54b334065c.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9039-2037889778daf211.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9039-e44ff08ca4f37a12.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9078-e3b627680692b3fd.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9120-dc2d8129a3d2175b.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9140-09af618948244b82.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9145-9507437d5b599cea.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9190-e32c76b5b1affa7b.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9258-6907841794d6c1e1.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9264-5009b962427411a5.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9264-fd8ab51d702e9535.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9271-e8c50ba458178f1c.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9409-6eefc92a7f8433ff.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9584-4d5bef7e60cfea45.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9584-9d4fd7b3d6a7c9e7.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9818-6f03d7efd4fb8533.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/9841-721a173be76941d1.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/api-reference/{page-a6a3e9e67b671303.js => page-2a4be488cfb5b0d1.js} (99%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/api-playground/page-67c4f150eba92e64.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/api-playground/page-b8b443caa67af654.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/budgets/page-ae754695901b9376.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/budgets/page-e471b9b73cfc894b.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/caching/page-a570d0f7ab5db7bf.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/caching/page-d31cc105402ab7e0.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/claude-code-plugins/page-84a3290b0c10981d.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/old-usage/page-1e4535b4f65e91c3.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/old-usage/page-5e097dbb8ce40bb4.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/prompts/page-67bc04a61159c00a.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/prompts/page-8236f1efda3366f0.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/tag-management/page-5627fd94813402eb.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/experimental/tag-management/page-f6f7f1dd17bed0fe.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/guardrails/page-060e61cb783d32ef.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/guardrails/page-1528b2c6a3288963.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/layout-534e351316fbcd53.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/logs/{page-5a10d46ca991b83e.js => page-a6b6031fb32f8582.js} (75%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/model-hub/page-1479dcb217587498.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/model-hub/page-37f3c43872246b40.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/models-and-endpoints/page-b69988590beaa5c8.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/models-and-endpoints/page-c3af9027b254a3f0.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/organizations/page-56a03e123f452d60.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/organizations/page-95fa0a5eac5056b4.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/playground/page-e2680b62dbb22cd9.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/playground/page-fc3dff494dc4db08.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/policies/page-43fedb527f6a4b39.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/admin-settings/page-746658933a633902.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/admin-settings/page-d0bae1a3ceef1920.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/logging-and-alerts/page-194a2419931e7649.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/logging-and-alerts/page-e3df74ef5ac0dcd5.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/router-settings/page-53d06fb7df656af3.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/router-settings/page-ffa3245ebcbbc02a.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/ui-theme/page-612e275485550e83.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/settings/ui-theme/page-d833946961b065a5.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/teams/page-35fb23c26a99119e.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/teams/page-8d1a71afa9e9ff16.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/test-key/page-63cd64b722408984.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/test-key/page-a02455ca29fab29f.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/tools/mcp-servers/page-7dd2ea6f1433d41f.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/tools/mcp-servers/page-eeef4bac80ed234b.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/tools/vector-stores/page-0f618cc2d6cae794.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/tools/vector-stores/page-a8da9d9d1d928bc0.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/usage/{page-d7532f354d44803b.js => page-f5988c9f9087fca8.js} (76%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/users/page-6d3643cef6c068ee.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/users/page-993c131fdcb59c92.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/(dashboard)/virtual-keys/{page-73444bcb0cfe86b7.js => page-85cb1e2f0392d6e5.js} (66%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/login/page-61bfa80619b62f6b.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/login/page-e40d110cdbc26a70.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/model_hub/page-649f32c699b27a45.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/model_hub/page-92347d2021ca8580.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/model_hub_table/page-2ad344049541235f.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/model_hub_table/page-81008adc04402b54.js rename litellm/proxy/_experimental/out/_next/static/chunks/app/onboarding/{page-17ecf5bf068f8157.js => page-e8604d757e270b09.js} (97%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/page-587b0acf34f8c747.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/app/page-850191a6e6250635.js rename litellm/proxy/_experimental/out/_next/static/chunks/{main-e4e168e4dfadea03.js => main-3f67160ac20e4399.js} (67%) create mode 100644 litellm/proxy/_experimental/out/_next/static/css/4fd2d0c1b251ee22.css delete mode 100644 litellm/proxy/_experimental/out/_next/static/css/83c095d0528a2e35.css create mode 100644 litellm/proxy/_experimental/out/api-reference.html create mode 100644 litellm/proxy/_experimental/out/assets/logos/s3_vector.png rename litellm/proxy/_experimental/out/experimental/{prompts/index.html => api-playground.html} (62%) create mode 100644 litellm/proxy/_experimental/out/experimental/budgets.html delete mode 100644 litellm/proxy/_experimental/out/experimental/budgets/index.html create mode 100644 litellm/proxy/_experimental/out/experimental/caching.html delete mode 100644 litellm/proxy/_experimental/out/experimental/caching/index.html create mode 100644 litellm/proxy/_experimental/out/experimental/claude-code-plugins.html create mode 100644 litellm/proxy/_experimental/out/experimental/claude-code-plugins.txt create mode 100644 litellm/proxy/_experimental/out/experimental/old-usage.html delete mode 100644 litellm/proxy/_experimental/out/experimental/old-usage/index.html create mode 100644 litellm/proxy/_experimental/out/experimental/prompts.html create mode 100644 litellm/proxy/_experimental/out/experimental/tag-management.html delete mode 100644 litellm/proxy/_experimental/out/experimental/tag-management/index.html create mode 100644 litellm/proxy/_experimental/out/guardrails.html create mode 100644 litellm/proxy/_experimental/out/login.html delete mode 100644 litellm/proxy/_experimental/out/login/index.html create mode 100644 litellm/proxy/_experimental/out/logs.html delete mode 100644 litellm/proxy/_experimental/out/logs/index.html rename litellm/proxy/_experimental/out/mcp/oauth/{callback/index.html => callback.html} (95%) rename litellm/proxy/_experimental/out/{experimental/api-playground/index.html => model-hub.html} (60%) create mode 100644 litellm/proxy/_experimental/out/model_hub.html rename litellm/proxy/_experimental/out/{model_hub_table/index.html => model_hub_table.html} (78%) rename litellm/proxy/_experimental/out/{models-and-endpoints/index.html => models-and-endpoints.html} (73%) create mode 100644 litellm/proxy/_experimental/out/onboarding.html create mode 100644 litellm/proxy/_experimental/out/organizations.html delete mode 100644 litellm/proxy/_experimental/out/organizations/index.html create mode 100644 litellm/proxy/_experimental/out/playground.html delete mode 100644 litellm/proxy/_experimental/out/playground/index.html rename litellm/proxy/_experimental/out/{settings/router-settings/index.html => policies.html} (61%) create mode 100644 litellm/proxy/_experimental/out/policies.txt create mode 100644 litellm/proxy/_experimental/out/settings/admin-settings.html delete mode 100644 litellm/proxy/_experimental/out/settings/admin-settings/index.html create mode 100644 litellm/proxy/_experimental/out/settings/logging-and-alerts.html delete mode 100644 litellm/proxy/_experimental/out/settings/logging-and-alerts/index.html rename litellm/proxy/_experimental/out/{usage/index.html => settings/router-settings.html} (57%) create mode 100644 litellm/proxy/_experimental/out/settings/ui-theme.html delete mode 100644 litellm/proxy/_experimental/out/settings/ui-theme/index.html create mode 100644 litellm/proxy/_experimental/out/teams.html delete mode 100644 litellm/proxy/_experimental/out/teams/index.html rename litellm/proxy/_experimental/out/{api-reference/index.html => test-key.html} (62%) delete mode 100644 litellm/proxy/_experimental/out/test-key/index.html create mode 100644 litellm/proxy/_experimental/out/tools/mcp-servers.html delete mode 100644 litellm/proxy/_experimental/out/tools/mcp-servers/index.html create mode 100644 litellm/proxy/_experimental/out/tools/vector-stores.html delete mode 100644 litellm/proxy/_experimental/out/tools/vector-stores/index.html create mode 100644 litellm/proxy/_experimental/out/usage.html rename litellm/proxy/_experimental/out/{model-hub/index.html => users.html} (59%) delete mode 100644 litellm/proxy/_experimental/out/users/index.html create mode 100644 litellm/proxy/_experimental/out/virtual-keys.html delete mode 100644 litellm/proxy/_experimental/out/virtual-keys/index.html create mode 100644 litellm/proxy/common_utils/cache_coordinator.py create mode 100644 litellm/proxy/hooks/mcp_semantic_filter/ARCHITECTURE.md create mode 100644 litellm/proxy/hooks/mcp_semantic_filter/__init__.py create mode 100644 litellm/proxy/hooks/mcp_semantic_filter/hook.py create mode 100644 litellm/rag/ingestion/file_parsers/__init__.py create mode 100644 litellm/rag/ingestion/file_parsers/pdf_parser.py create mode 100644 litellm/rag/ingestion/s3_vectors_ingestion.py create mode 100644 litellm/router_utils/pre_call_checks/model_rate_limit_check.py create mode 100644 litellm/types/integrations/datadog_cost_management.py create mode 100644 litellm/types/llms/xai.py create mode 100644 litellm/types/proxy/management_endpoints/key_management_endpoints.py delete mode 100644 proxy_config.yaml create mode 100644 tests/litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py create mode 100644 tests/litellm/test_stream_chunk_builder_images.py create mode 100644 tests/litellm_core_utils/test_bedrock_converse_dedup_factory.py create mode 100644 tests/llm_translation/test_bedrock_anthropic_regression.py delete mode 100644 tests/local_testing/test_literalai.py create mode 100644 tests/mcp_tests/test_semantic_tool_filter_e2e.py create mode 100644 tests/proxy_e2e_anthropic_messages_tests/test_claude_agent_sdk.py create mode 100644 tests/proxy_e2e_anthropic_messages_tests/test_config.yaml create mode 100644 tests/proxy_unit_tests/test_get_image.py create mode 100644 tests/proxy_unit_tests/test_server_root_path.py create mode 100644 tests/proxy_unit_tests/test_zero_cost_model_budget_bypass.py create mode 100644 tests/router_unit_tests/test_router_acancel_batch.py create mode 100644 tests/test_litellm/a2a_protocol/test_card_resolver.py create mode 100644 tests/test_litellm/integrations/datadog/test_datadog_cost_management.py create mode 100644 tests/test_litellm/integrations/datadog/test_datadog_llm_obs_agent.py create mode 100644 tests/test_litellm/integrations/test_custom_guardrail_recursion.py create mode 100644 tests/test_litellm/integrations/test_prometheus_client_ip_user_agent.py create mode 100644 tests/test_litellm/integrations/test_prometheus_missing_metrics.py create mode 100644 tests/test_litellm/llms/azure_ai/claude/test_azure_anthropic_count_tokens_transformation.py create mode 100644 tests/test_litellm/llms/azure_ai/rerank/test_azure_ai_rerank_transformation.py create mode 100644 tests/test_litellm/llms/azure_ai/test_cost_calculator.py create mode 100644 tests/test_litellm/llms/bedrock/realtime/test_bedrock_realtime_transformation.py create mode 100644 tests/test_litellm/llms/hosted_vllm/chat/test_hosted_vllm_ssl_verify.py create mode 100644 tests/test_litellm/llms/hosted_vllm/embedding/test_hosted_vllm_embedding_ssl_verify.py create mode 100644 tests/test_litellm/llms/openai_like/embedding/__init__.py create mode 100644 tests/test_litellm/llms/openai_like/embedding/test_openai_like_embedding.py create mode 100644 tests/test_litellm/llms/s3_vectors/__init__.py create mode 100644 tests/test_litellm/llms/s3_vectors/vector_stores/__init__.py create mode 100644 tests/test_litellm/llms/s3_vectors/vector_stores/test_s3_vectors_transformation.py create mode 100644 tests/test_litellm/llms/test_cache_control_and_reasoning.py create mode 100644 tests/test_litellm/llms/vercel_ai_gateway/embedding/__init__.py create mode 100644 tests/test_litellm/llms/vercel_ai_gateway/embedding/test_vercel_ai_gateway_embedding.py create mode 100644 tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/qwen/__init__.py create mode 100644 tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/qwen/test_vertex_ai_qwen_global_endpoint.py create mode 100644 tests/test_litellm/llms/watsonx/test_watsonx_common_utils.py create mode 100644 tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_stale_session.py create mode 100644 tests/test_litellm/proxy/_experimental/mcp_server/test_semantic_tool_filter.py create mode 100644 tests/test_litellm/proxy/auth/test_cli_auth.py create mode 100644 tests/test_litellm/proxy/google_endpoints/test_interactions_agent_param.py create mode 100644 tests/test_litellm/proxy/hooks/test_post_call_response_headers_hook.py create mode 100644 tests/test_litellm/proxy/hooks/test_post_call_streaming_hook_integration.py create mode 100644 tests/test_litellm/proxy/hooks/test_post_call_success_hook_integration.py create mode 100644 tests/test_litellm/proxy/management_endpoints/scim/test_scim_v2_discovery.py create mode 100644 tests/test_litellm/proxy/test_response_model_sanitization.py create mode 100644 tests/test_litellm/proxy/vector_store_endpoints/test_vector_store_access_control.py create mode 100644 tests/test_litellm/test_model_param_helper.py create mode 100644 tests/test_litellm/test_router/test_enforce_model_rate_limits.py create mode 100644 tests/test_litellm/test_router_redis_init.py create mode 100644 tests/test_litellm/test_router_silent_experiment.py create mode 100644 tests/test_litellm/test_xai_responses_auto_routing.py create mode 100644 tests/test_otel_thread_leak.py create mode 100644 tests/test_presidio_latency.py create mode 100644 tests/vector_store_tests/rag/test_rag_s3_vectors.py create mode 100644 tests/vector_store_tests/test_s3_vectors_vector_store.py create mode 100644 ui/litellm-dashboard/public/assets/logos/s3_vector.png create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/proxyConfig/useProxyConfig.test.ts create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/proxyConfig/useProxyConfig.ts create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/storeRequestInSpendLogs/useStoreRequestInSpendLogs.ts create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/hooks/useDisableShowPrompts.ts create mode 100644 ui/litellm-dashboard/src/components/BulkEditUsers.test.tsx rename ui/litellm-dashboard/src/components/{bulk_edit_user.tsx => BulkEditUsers.tsx} (98%) create mode 100644 ui/litellm-dashboard/src/components/Navbar/CommunityEngagementButtons/CommunityEngagementButtons.test.tsx create mode 100644 ui/litellm-dashboard/src/components/Navbar/CommunityEngagementButtons/CommunityEngagementButtons.tsx create mode 100644 ui/litellm-dashboard/src/components/Navbar/UserDropdown/UserDropdown.test.tsx create mode 100644 ui/litellm-dashboard/src/components/Navbar/UserDropdown/UserDropdown.tsx create mode 100644 ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/PageVisibilitySettings.tsx create mode 100644 ui/litellm-dashboard/src/components/UsagePage/components/KeyModelUsageView.test.tsx create mode 100644 ui/litellm-dashboard/src/components/UsagePage/components/KeyModelUsageView.tsx create mode 100644 ui/litellm-dashboard/src/components/common_components/KeyLifecycleSettings.test.tsx create mode 100644 ui/litellm-dashboard/src/components/common_components/RouterSettingsAccordion.tsx create mode 100644 ui/litellm-dashboard/src/components/common_components/TableHeaderSortDropdown/TableHeaderSortDropdown.test.tsx create mode 100644 ui/litellm-dashboard/src/components/common_components/TableHeaderSortDropdown/TableHeaderSortDropdown.tsx create mode 100644 ui/litellm-dashboard/src/components/model_dashboard/all_models_table.tsx create mode 100644 ui/litellm-dashboard/src/components/molecules/filter.test.tsx create mode 100644 ui/litellm-dashboard/src/components/page_metadata.ts create mode 100644 ui/litellm-dashboard/src/components/page_utils.test.ts create mode 100644 ui/litellm-dashboard/src/components/page_utils.ts create mode 100644 ui/litellm-dashboard/src/components/router_settings/RouterSettingsForm.tsx create mode 100644 ui/litellm-dashboard/src/components/survey/NudgePrompt.test.tsx create mode 100644 ui/litellm-dashboard/src/components/vector_store_management/CreateVectorStore.test.tsx create mode 100644 ui/litellm-dashboard/src/components/vector_store_management/CreateVectorStore.tsx create mode 100644 ui/litellm-dashboard/src/components/vector_store_management/DocumentsTable.test.tsx create mode 100644 ui/litellm-dashboard/src/components/vector_store_management/DocumentsTable.tsx create mode 100644 ui/litellm-dashboard/src/components/vector_store_management/S3VectorsConfig.test.tsx create mode 100644 ui/litellm-dashboard/src/components/vector_store_management/S3VectorsConfig.tsx create mode 100644 ui/litellm-dashboard/src/components/vector_store_management/TestVectorStoreTab.test.tsx create mode 100644 ui/litellm-dashboard/src/components/vector_store_management/TestVectorStoreTab.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/CollapsibleMessage.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/DrawerHeader.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/HistorySection.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/HistoryTree.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/InputCard.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/JsonViewer.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/LogDetailsDrawer.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/MessageBlock.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/MessageCard.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/OutputCard.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/PrettyMessagesView.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/SectionHeader.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/SimpleMessageBlock.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/SimpleToolCallBlock.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/TokenFlow.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/ToolCallBlock.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/ToolCallCard.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/TruncatedValue.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/constants.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/index.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/prettyMessagesTypes.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/prettyMessagesUtils.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/useKeyboardNavigation.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/LogDetailsDrawer/utils.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/SpendLogsSettingsModal/SpendLogsSettingsModal.test.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/SpendLogsSettingsModal/SpendLogsSettingsModal.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/ToolsSection/FormattedToolView.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/ToolsSection/JsonToolView.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/ToolsSection/ToolExpandedContent.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/ToolsSection/ToolItem.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/ToolsSection/ToolsSection.test.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/ToolsSection/ToolsSection.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/ToolsSection/index.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/ToolsSection/types.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/ToolsSection/utils.test.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/ToolsSection/utils.ts diff --git a/.circleci/config.yml b/.circleci/config.yml index 0b369477c28..d99c485af94 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -112,7 +112,7 @@ jobs: python -m mypy . cd .. no_output_timeout: 10m - local_testing: + local_testing_part1: docker: - image: cimg/python:3.12 auth: @@ -205,13 +205,15 @@ jobs: # Run pytest and generate JUnit XML report - run: - name: Run tests + name: Run tests (Part 1 - A-M) command: | mkdir test-results - # Discover test files - TEST_FILES=$(circleci tests glob "tests/local_testing/**/test_*.py") + + # Discover test files (A-M) + TEST_FILES=$(circleci tests glob "tests/local_testing/**/test_[a-mA-M]*.py") + echo "$TEST_FILES" | circleci tests run \ - --split-by=filesize \ + --split-by=timings \ --verbose \ --command="xargs python -m pytest \ -vv \ @@ -227,8 +229,8 @@ jobs: - run: name: Rename the coverage files command: | - mv coverage.xml local_testing_coverage.xml - mv .coverage local_testing_coverage + mv coverage.xml local_testing_part1_coverage.xml + mv .coverage local_testing_part1_coverage # Store test results - store_test_results: @@ -236,8 +238,136 @@ jobs: - persist_to_workspace: root: . paths: - - local_testing_coverage.xml - - local_testing_coverage + - local_testing_part1_coverage.xml + - local_testing_part1_coverage + local_testing_part2: + docker: + - image: cimg/python:3.12 + auth: + username: ${DOCKERHUB_USERNAME} + password: ${DOCKERHUB_PASSWORD} + working_directory: ~/project + parallelism: 4 + steps: + - checkout + - setup_google_dns + - run: + name: Show git commit hash + command: | + echo "Git commit hash: $CIRCLE_SHA1" + + - restore_cache: + keys: + - v1-dependencies-{{ checksum ".circleci/requirements.txt" }} + - run: + name: Install Dependencies + command: | + python -m pip install --upgrade pip + python -m pip install -r .circleci/requirements.txt + pip install "pytest==7.3.1" + pip install "pytest-retry==1.6.3" + pip install "pytest-asyncio==0.21.1" + pip install "pytest-cov==5.0.0" + pip install "mypy==1.18.2" + pip install "google-generativeai==0.3.2" + pip install "google-cloud-aiplatform==1.43.0" + pip install pyarrow + pip install "boto3==1.36.0" + pip install "aioboto3==13.4.0" + pip install langchain + pip install lunary==0.2.5 + pip install "azure-identity==1.16.1" + pip install "langfuse==2.59.7" + pip install "logfire==0.29.0" + pip install numpydoc + pip install traceloop-sdk==0.21.1 + pip install opentelemetry-api==1.25.0 + pip install opentelemetry-sdk==1.25.0 + pip install opentelemetry-exporter-otlp==1.25.0 + pip install openai==1.100.1 + pip install prisma==0.11.0 + pip install "detect_secrets==1.5.0" + pip install "httpx==0.24.1" + pip install "respx==0.22.0" + pip install fastapi + pip install "gunicorn==21.2.0" + pip install "anyio==4.2.0" + pip install "aiodynamo==23.10.1" + pip install "asyncio==3.4.3" + pip install "apscheduler==3.10.4" + pip install "PyGithub==1.59.1" + pip install argon2-cffi + pip install "pytest-mock==3.12.0" + pip install python-multipart + pip install google-cloud-aiplatform + pip install prometheus-client==0.20.0 + pip install "pydantic==2.10.2" + pip install "diskcache==5.6.1" + pip install "Pillow==10.3.0" + pip install "jsonschema==4.22.0" + pip install "pytest-xdist==3.6.1" + pip install "pytest-timeout==2.2.0" + pip install "websockets==13.1.0" + pip install semantic_router --no-deps + pip install aurelio_sdk --no-deps + pip uninstall posthog -y + - setup_litellm_enterprise_pip + - save_cache: + paths: + - ./venv + key: v1-dependencies-{{ checksum ".circleci/requirements.txt" }} + - run: + name: Run prisma ./docker/entrypoint.sh + command: | + set +e + chmod +x docker/entrypoint.sh + ./docker/entrypoint.sh + set -e + - run: + name: Black Formatting + command: | + cd litellm + python -m pip install black + python -m black . + cd .. + + # Run pytest and generate JUnit XML report + - run: + name: Run tests (Part 2 - N-Z) + command: | + mkdir test-results + + # Discover test files (N-Z) + TEST_FILES=$(circleci tests glob "tests/local_testing/**/test_[n-zN-Z]*.py") + + echo "$TEST_FILES" | circleci tests run \ + --split-by=timings \ + --verbose \ + --command="xargs python -m pytest \ + -vv \ + --cov=litellm \ + --cov-report=xml \ + --junitxml=test-results/junit.xml \ + --durations=20 \ + -k \"not test_python_38.py and not test_basic_python_version.py and not router and not assistants and not langfuse and not caching and not cache\" \ + -n 4 \ + --timeout=300 \ + --timeout_method=thread" + no_output_timeout: 120m + - run: + name: Rename the coverage files + command: | + mv coverage.xml local_testing_part2_coverage.xml + mv .coverage local_testing_part2_coverage + + # Store test results + - store_test_results: + path: test-results + - persist_to_workspace: + root: . + paths: + - local_testing_part2_coverage.xml + - local_testing_part2_coverage langfuse_logging_unit_tests: docker: - image: cimg/python:3.11 @@ -509,7 +639,6 @@ jobs: username: ${DOCKERHUB_USERNAME} password: ${DOCKERHUB_PASSWORD} working_directory: ~/project - parallelism: 4 steps: - checkout - setup_google_dns @@ -531,21 +660,9 @@ jobs: - run: name: Run tests command: | - mkdir test-results - # Find test files only in local_testing - TEST_FILES=$(circleci tests glob "tests/local_testing/**/test_*.py") - echo "$TEST_FILES" | circleci tests run \ - --split-by=filesize \ - --verbose \ - --command="xargs python -m pytest -o junit_family=legacy \ - -k 'router' \ - --cov=litellm \ - --cov-report=xml \ - -n 4 \ - --dist=loadscope \ - --junitxml=test-results/junit.xml \ - --durations=5 \ - -vv" + pwd + ls + python -m pytest tests/local_testing --cov=litellm --cov-report=xml -vv -k "router" -v --junitxml=test-results/junit.xml --durations=5 no_output_timeout: 120m - run: name: Rename the coverage files @@ -598,8 +715,8 @@ jobs: - run: name: Rename the coverage files command: | - mv coverage.xml litellm_router_coverage.xml - mv .coverage litellm_router_coverage + mv coverage.xml litellm_router_unit_coverage.xml + mv .coverage litellm_router_unit_coverage # Store test results - store_test_results: path: test-results @@ -607,8 +724,8 @@ jobs: - persist_to_workspace: root: . paths: - - litellm_router_coverage.xml - - litellm_router_coverage + - litellm_router_unit_coverage.xml + - litellm_router_unit_coverage litellm_security_tests: machine: image: ubuntu-2204:2023.10.1 @@ -1816,6 +1933,7 @@ jobs: pip install "mlflow==2.17.2" pip install "anthropic==0.52.0" pip install "blockbuster==1.5.24" + pip install "pytest-xdist==3.6.1" # Run pytest and generate JUnit XML report - setup_litellm_enterprise_pip - run: @@ -1823,7 +1941,7 @@ jobs: command: | pwd ls - python -m pytest -vv tests/logging_callback_tests --cov=litellm --cov-report=xml -s -v --junitxml=test-results/junit.xml --durations=5 + python -m pytest -vv tests/logging_callback_tests --cov=litellm -n 4 --cov-report=xml -s -v --junitxml=test-results/junit.xml --durations=5 no_output_timeout: 120m - run: name: Rename the coverage files @@ -3289,6 +3407,110 @@ jobs: - store_test_results: path: test-results + proxy_e2e_anthropic_messages_tests: + machine: + image: ubuntu-2204:2023.10.1 + resource_class: xlarge + working_directory: ~/project + steps: + - checkout + - setup_google_dns + - run: + name: Install Docker CLI (In case it's not already installed) + command: | + curl -fsSL https://get.docker.com | sh + sudo usermod -aG docker $USER + docker version + - run: + name: Install Python 3.10 + command: | + curl https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh --output miniconda.sh + bash miniconda.sh -b -p $HOME/miniconda + export PATH="$HOME/miniconda/bin:$PATH" + conda init bash + source ~/.bashrc + conda create -n myenv python=3.10 -y + conda activate myenv + python --version + - run: + name: Install Dependencies + command: | + export PATH="$HOME/miniconda/bin:$PATH" + source $HOME/miniconda/etc/profile.d/conda.sh + conda activate myenv + pip install "pytest==7.3.1" + pip install "pytest-asyncio==0.21.1" + pip install "boto3==1.36.0" + pip install "httpx==0.27.0" + pip install "claude-agent-sdk" + pip install -r requirements.txt + - run: + name: Install dockerize + command: | + wget https://github.com/jwilder/dockerize/releases/download/v0.6.1/dockerize-linux-amd64-v0.6.1.tar.gz + sudo tar -C /usr/local/bin -xzvf dockerize-linux-amd64-v0.6.1.tar.gz + rm dockerize-linux-amd64-v0.6.1.tar.gz + - run: + name: Start PostgreSQL Database + command: | + docker run -d \ + --name postgres-db \ + -e POSTGRES_USER=postgres \ + -e POSTGRES_PASSWORD=postgres \ + -e POSTGRES_DB=circle_test \ + -p 5432:5432 \ + postgres:14 + - run: + name: Wait for PostgreSQL to be ready + command: dockerize -wait tcp://localhost:5432 -timeout 1m + - attach_workspace: + at: ~/project + - run: + name: Load Docker Database Image + command: | + gunzip -c litellm-docker-database.tar.gz | docker load + docker images | grep litellm-docker-database + - run: + name: Run Docker container with test config + command: | + docker run -d \ + -p 4000:4000 \ + -e DATABASE_URL=postgresql://postgres:postgres@host.docker.internal:5432/circle_test \ + -e LITELLM_MASTER_KEY="sk-1234" \ + -e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \ + -e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \ + -e AWS_REGION_NAME="us-east-1" \ + --add-host host.docker.internal:host-gateway \ + --name my-app \ + -v $(pwd)/tests/proxy_e2e_anthropic_messages_tests/test_config.yaml:/app/config.yaml \ + litellm-docker-database:ci \ + --config /app/config.yaml \ + --port 4000 \ + --detailed_debug + - run: + name: Start outputting logs + command: docker logs -f my-app + background: true + - run: + name: Wait for app to be ready + command: dockerize -wait http://localhost:4000 -timeout 5m + - run: + name: Run Claude Agent SDK E2E Tests + command: | + export PATH="$HOME/miniconda/bin:$PATH" + source $HOME/miniconda/etc/profile.d/conda.sh + conda activate myenv + export LITELLM_PROXY_URL="http://localhost:4000" + export LITELLM_API_KEY="sk-1234" + pwd + ls + python -m pytest -vv tests/proxy_e2e_anthropic_messages_tests/ -x -s --junitxml=test-results/junit.xml --durations=5 + no_output_timeout: 120m + + # Store test results + - store_test_results: + path: test-results + upload-coverage: docker: - image: cimg/python:3.9 @@ -3310,7 +3532,7 @@ jobs: python -m venv venv . venv/bin/activate pip install coverage - coverage combine llm_translation_coverage llm_responses_api_coverage ocr_coverage search_coverage mcp_coverage logging_coverage audio_coverage litellm_router_coverage local_testing_coverage litellm_assistants_api_coverage auth_ui_unit_tests_coverage langfuse_coverage caching_coverage litellm_proxy_unit_tests_part1_coverage litellm_proxy_unit_tests_part2_coverage image_gen_coverage pass_through_unit_tests_coverage batches_coverage litellm_security_tests_coverage guardrails_coverage litellm_mapped_tests_coverage + coverage combine llm_translation_coverage llm_responses_api_coverage ocr_coverage search_coverage mcp_coverage logging_coverage audio_coverage litellm_router_coverage litellm_router_unit_coverage local_testing_part1_coverage local_testing_part2_coverage litellm_assistants_api_coverage auth_ui_unit_tests_coverage langfuse_coverage caching_coverage litellm_proxy_unit_tests_part1_coverage litellm_proxy_unit_tests_part2_coverage image_gen_coverage pass_through_unit_tests_coverage batches_coverage litellm_security_tests_coverage guardrails_coverage litellm_mapped_tests_coverage coverage xml - codecov/upload: file: ./coverage.xml @@ -3360,8 +3582,22 @@ jobs: ls dist/ twine upload --verbose dist/* else - echo "Version ${VERSION} of package is already published on PyPI. Skipping PyPI publish." - circleci step halt + echo "Version ${VERSION} of package is already published on PyPI." + + # Check if corresponding Docker nightly image exists + NIGHTLY_TAG="v${VERSION}-nightly" + echo "Checking for Docker nightly image: litellm/litellm:${NIGHTLY_TAG}" + + # Check Docker Hub for the nightly image + if curl -s "https://hub.docker.com/v2/repositories/litellm/litellm/tags/${NIGHTLY_TAG}" | grep -q "name"; then + echo "Docker nightly image ${NIGHTLY_TAG} exists. This release was already completed successfully." + echo "Skipping PyPI publish and continuing to ensure Docker images are up to date." + circleci step halt + else + echo "ERROR: PyPI package ${VERSION} exists but Docker nightly image ${NIGHTLY_TAG} does not exist!" + echo "This indicates an incomplete release. Please investigate." + exit 1 + fi fi - run: name: Trigger Github Action for new Docker Container + Trigger Load Testing @@ -3370,11 +3606,21 @@ jobs: python3 -m pip install toml VERSION=$(python3 -c "import toml; print(toml.load('pyproject.toml')['tool']['poetry']['version'])") echo "LiteLLM Version ${VERSION}" + + # Determine which branch to use for Docker build + if [[ "$CIRCLE_BRANCH" =~ ^litellm_release_day_.* ]]; then + BUILD_BRANCH="$CIRCLE_BRANCH" + echo "Using release branch: $BUILD_BRANCH" + else + BUILD_BRANCH="main" + echo "Using default branch: $BUILD_BRANCH" + fi + curl -X POST \ -H "Accept: application/vnd.github.v3+json" \ -H "Authorization: Bearer $GITHUB_TOKEN" \ "https://api.github.com/repos/BerriAI/litellm/actions/workflows/ghcr_deploy.yml/dispatches" \ - -d "{\"ref\":\"main\", \"inputs\":{\"tag\":\"v${VERSION}-nightly\", \"commit_hash\":\"$CIRCLE_SHA1\"}}" + -d "{\"ref\":\"${BUILD_BRANCH}\", \"inputs\":{\"tag\":\"v${VERSION}-nightly\", \"commit_hash\":\"$CIRCLE_SHA1\"}}" echo "triggering load testing server for version ${VERSION} and commit ${CIRCLE_SHA1}" curl -X POST "https://proxyloadtester-production.up.railway.app/start/load/test?version=${VERSION}&commit_hash=${CIRCLE_SHA1}&release_type=nightly" @@ -3508,6 +3754,9 @@ jobs: cd ui/litellm-dashboard + # Remove node_modules and package-lock to ensure clean install (fixes dependency resolution issues) + rm -rf node_modules package-lock.json + # Install dependencies first npm install @@ -3765,7 +4014,13 @@ workflows: only: - main - /litellm_.*/ - - local_testing: + - local_testing_part1: + filters: + branches: + only: + - main + - /litellm_.*/ + - local_testing_part2: filters: branches: only: @@ -3927,6 +4182,14 @@ workflows: only: - main - /litellm_.*/ + - proxy_e2e_anthropic_messages_tests: + requires: + - build_docker_database_image + filters: + branches: + only: + - main + - /litellm_.*/ - llm_translation_testing: filters: branches: @@ -4070,7 +4333,8 @@ workflows: - litellm_proxy_unit_testing_part2 - litellm_security_tests - langfuse_logging_unit_tests - - local_testing + - local_testing_part1 + - local_testing_part2 - litellm_assistants_api_testing - auth_ui_unit_tests - db_migration_disable_update_check: @@ -4110,10 +4374,12 @@ workflows: branches: only: - main + - /litellm_release_day_.*/ - publish_to_pypi: requires: - mypy_linting - - local_testing + - local_testing_part1 + - local_testing_part2 - build_and_test - e2e_openai_endpoints - test_bad_database_url diff --git a/.circleci/requirements.txt b/.circleci/requirements.txt index 8c44dc18305..a5ec74424fe 100644 --- a/.circleci/requirements.txt +++ b/.circleci/requirements.txt @@ -16,4 +16,5 @@ uvloop==0.21.0 mcp==1.25.0 # for MCP server semantic_router==0.1.10 # for auto-routing with litellm fastuuid==0.12.0 -responses==0.25.7 # for proxy client tests \ No newline at end of file +responses==0.25.7 # for proxy client tests +pytest-retry==1.6.3 # for automatic test retries \ No newline at end of file diff --git a/.github/workflows/test-linting.yml b/.github/workflows/test-linting.yml index 35ebffeada3..7c5c269f899 100644 --- a/.github/workflows/test-linting.yml +++ b/.github/workflows/test-linting.yml @@ -73,4 +73,4 @@ jobs: - name: Check import safety run: | - poetry run python -c "from litellm import *" || (echo '🚨 import failed, this means you introduced unprotected imports! 🚨'; exit 1) \ No newline at end of file + poetry run python -c "from litellm import *" || (echo '🚨 import failed, this means you introduced unprotected imports! 🚨'; exit 1) diff --git a/.github/workflows/test-litellm.yml b/.github/workflows/test-litellm.yml index ba32dc1bf54..d9cf2e74a11 100644 --- a/.github/workflows/test-litellm.yml +++ b/.github/workflows/test-litellm.yml @@ -34,7 +34,7 @@ jobs: poetry run pip install "google-genai==1.22.0" poetry run pip install "google-cloud-aiplatform>=1.38" poetry run pip install "fastapi-offline==1.7.3" - poetry run pip install "python-multipart==0.0.18" + poetry run pip install "python-multipart==0.0.22" poetry run pip install "openapi-core" - name: Setup litellm-enterprise as local package run: | diff --git a/.github/workflows/test-model-map.yaml b/.github/workflows/test-model-map.yaml new file mode 100644 index 00000000000..ae5ac402e23 --- /dev/null +++ b/.github/workflows/test-model-map.yaml @@ -0,0 +1,15 @@ +name: Validate model_prices_and_context_window.json + +on: + pull_request: + branches: [ main ] + +jobs: + validate-model-prices-json: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Validate model_prices_and_context_window.json + run: | + jq empty model_prices_and_context_window.json diff --git a/.gitignore b/.gitignore index 0248d68c1e1..ddf5f6279b3 100644 --- a/.gitignore +++ b/.gitignore @@ -60,10 +60,6 @@ litellm/proxy/_super_secret_config.yaml litellm/proxy/myenv/bin/activate litellm/proxy/myenv/bin/Activate.ps1 myenv/* -litellm/proxy/_experimental/out/_next/ -litellm/proxy/_experimental/out/404/index.html -litellm/proxy/_experimental/out/model_hub/index.html -litellm/proxy/_experimental/out/onboarding/index.html litellm/tests/log.txt litellm/tests/langfuse.log litellm/tests/langfuse.log @@ -76,9 +72,6 @@ tests/local_testing/log.txt litellm/proxy/_new_new_secret_config.yaml litellm/proxy/custom_guardrail.py .mypy_cache/* -litellm/proxy/_experimental/out/404.html -litellm/proxy/_experimental/out/404.html -litellm/proxy/_experimental/out/model_hub.html .mypy_cache/* litellm/proxy/application.log tests/llm_translation/vertex_test_account.json @@ -100,9 +93,9 @@ litellm_config.yaml litellm/proxy/to_delete_loadtest_work/* update_model_cost_map.py tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py -litellm/proxy/_experimental/out/guardrails/index.html scripts/test_vertex_ai_search.py LAZY_LOADING_IMPROVEMENTS.md +STABILIZATION_TODO.md **/test-results **/playwright-report **/*.storageState.json diff --git a/.trivyignore b/.trivyignore new file mode 100644 index 00000000000..0d04ecacdb5 --- /dev/null +++ b/.trivyignore @@ -0,0 +1,12 @@ +# LiteLLM Trivy Ignore File +# CVEs listed here are temporarily allowlisted pending fixes + +# Next.js vulnerabilities in UI dashboard (next@14.2.35) +# Allowlisted: 2026-01-31, 7-day fix timeline +# Fix: Upgrade to Next.js 15.5.10+ or 16.1.5+ + +# HIGH: DoS via request deserialization +GHSA-h25m-26qc-wcjf + +# MEDIUM: Image Optimizer DoS +CVE-2025-59471 diff --git a/AGENTS.md b/AGENTS.md index 61afbd035fe..5a48049ef45 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -51,12 +51,14 @@ LiteLLM is a unified interface for 100+ LLMs that: ### MAKING CODE CHANGES FOR THE UI (IGNORE FOR BACKEND) -1. **Use Common Components as much as possible**: +1. **Tremor is DEPRECATED, do not use Tremor components in new features/changes** + - The only exception is the Tremor Table component and its required Tremor Table sub components. + +2. **Use Common Components as much as possible**: - These are usually defined in the `common_components` directory - Use these components as much as possible and avoid building new components unless needed - - Tremor components are deprecated; prefer using Ant Design (AntD) as much as possible -2. **Testing**: +3. **Testing**: - The codebase uses **Vitest** and **React Testing Library** - **Query Priority Order**: Use query methods in this order: `getByRole`, `getByLabelText`, `getByPlaceholderText`, `getByText`, `getByTestId` - **Always use `screen`** instead of destructuring from `render()` (e.g., use `screen.getByText()` not `getByText`) diff --git a/Dockerfile b/Dockerfile index 2c54e2dec28..4bfda939110 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,8 +46,9 @@ FROM $LITELLM_RUNTIME_IMAGE AS runtime # Ensure runtime stage runs as root USER root -# Install runtime dependencies -RUN apk add --no-cache bash openssl tzdata nodejs npm python3 py3-pip +# Install runtime dependencies (libsndfile needed for audio processing on ARM64) +RUN apk add --no-cache bash openssl tzdata nodejs npm python3 py3-pip libsndfile && \ + npm install -g npm@latest tar@latest WORKDIR /app # Copy the current directory contents into the container at /app diff --git a/README.md b/README.md index dee5c7167ff..77adddf8978 100644 --- a/README.md +++ b/README.md @@ -259,12 +259,17 @@ LiteLLM Performance: **8ms P95 latency** at 1k RPS (See benchmarks [here](https: Support for more providers. Missing a provider or LLM Platform, raise a [feature request](https://github.com/BerriAI/litellm/issues/new?assignees=&labels=enhancement&projects=&template=feature_request.yml&title=%5BFeature%5D%3A+). ## OSS Adopters -Stripe wordmark - Blurple - Small - -download__1_-removebg-preview - - + + + + + + + + + +
StripeGoogle ADKGreptileOpenHands

Netflix

OpenAI Agents SDK
## Supported Providers ([Website Supported Models](https://models.litellm.ai/) | [Docs](https://docs.litellm.ai/docs/providers)) diff --git a/ci_cd/security_scans.sh b/ci_cd/security_scans.sh index cf026eb5263..340f8e96063 100755 --- a/ci_cd/security_scans.sh +++ b/ci_cd/security_scans.sh @@ -81,10 +81,10 @@ run_trivy_scans() { echo "Running Trivy scans..." echo "Scanning LiteLLM Docs..." - trivy fs --scanners vuln --dependency-tree --exit-code 1 --severity HIGH,CRITICAL,MEDIUM ./docs/ + trivy fs --ignorefile .trivyignore --scanners vuln --dependency-tree --exit-code 1 --severity HIGH,CRITICAL,MEDIUM ./docs/ echo "Scanning LiteLLM UI..." - trivy fs --scanners vuln --dependency-tree --exit-code 1 --severity HIGH,CRITICAL,MEDIUM ./ui/ + trivy fs --ignorefile .trivyignore --scanners vuln --dependency-tree --exit-code 1 --severity HIGH,CRITICAL,MEDIUM ./ui/ echo "Trivy scans completed successfully" } @@ -137,6 +137,7 @@ run_grype_scans() { "CVE-2019-1010025" # glibc pthread heap address leak - awaiting patched Wolfi glibc build "CVE-2026-22184" # zlib untgz buffer overflow - untgz unused + no fixed Wolfi build yet "GHSA-58pv-8j8x-9vj2" # jaraco.context path traversal - setuptools vendored only (v5.3.0), not used in application code (using v6.1.0+) + "GHSA-34x7-hfp2-rc4v" # node-tar hardlink path traversal - not applicable, tar CLI not exposed in application code "GHSA-r6q2-hw4h-h46w" # node-tar not used by application runtime, Linux-only container, not affect by macOS APFS-specific exploit "GHSA-8rrh-rw8j-w5fx" # wheel is from chainguard and will be handled by then TODO: Remove this after Chainguard updates the wheel "CVE-2025-59465" # We do not use Node in application runtime, only used for building Admin UI @@ -153,6 +154,7 @@ run_grype_scans() { "CVE-2025-15367" # No fix available yet "CVE-2025-12781" # No fix available yet "CVE-2025-11468" # No fix available yet + "CVE-2026-1299" # Python 3.13 email module header injection - not applicable, LiteLLM doesn't use BytesGenerator for email serialization ) # Build JSON array of allowlisted CVE IDs for jq diff --git a/cookbook/anthropic_agent_sdk/README.md b/cookbook/anthropic_agent_sdk/README.md new file mode 100644 index 00000000000..294d949e24e --- /dev/null +++ b/cookbook/anthropic_agent_sdk/README.md @@ -0,0 +1,144 @@ +# Claude Agent SDK with LiteLLM Gateway + +A simple example showing how to use Claude's Agent SDK with LiteLLM as a proxy. This lets you use any LLM provider (OpenAI, Bedrock, Azure, etc.) through the Agent SDK. + +## Quick Start + +### 1. Install dependencies + +```bash +pip install anthropic claude-agent-sdk litellm +``` + +### 2. Start LiteLLM proxy + +```bash +# Simple start with Claude +litellm --model claude-sonnet-4-20250514 + +# Or with a config file +litellm --config config.yaml +``` + +### 3. Run the chat + +**Basic Agent (no MCP):** + +```bash +python main.py +``` + +**Agent with MCP (DeepWiki2 for research):** + +```bash +python agent_with_mcp.py +``` + +If MCP connection fails, you can disable it: + +```bash +USE_MCP=false python agent_with_mcp.py +``` + +That's it! You can now chat with the agent in your terminal. + +### Chat Commands + +While chatting, you can use these commands: +- `models` - List all available models (fetched from your LiteLLM proxy) +- `model` - Switch to a different model +- `clear` - Start a new conversation +- `quit` or `exit` - End the chat + +The chat automatically fetches available models from your LiteLLM proxy's `/models` endpoint, so you'll always see what's currently configured. + +## Configuration + +Set these environment variables if needed: + +```bash +export LITELLM_PROXY_URL="http://localhost:4000" +export LITELLM_API_KEY="sk-1234" +export LITELLM_MODEL="bedrock-claude-sonnet-4.5" +``` + +Or just use the defaults - it'll connect to `http://localhost:4000` by default. + +## Files + +- `main.py` - Basic interactive agent without MCP +- `agent_with_mcp.py` - Agent with MCP server integration (DeepWiki2) +- `common.py` - Shared utilities and functions +- `config.example.yaml` - Example LiteLLM configuration +- `requirements.txt` - Python dependencies + +## Example Config File + +If you want to use multiple models, create a `config.yaml` (see `config.example.yaml`): + +```yaml +model_list: + - model_name: bedrock-claude-sonnet-4 + litellm_params: + model: "bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0" + aws_region_name: "us-east-1" + + - model_name: bedrock-claude-sonnet-4.5 + litellm_params: + model: "bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0" + aws_region_name: "us-east-1" +``` + +Then start LiteLLM with: `litellm --config config.yaml` + +## How It Works + +The key is pointing the Agent SDK to LiteLLM instead of directly to Anthropic: + +```python +# Point to LiteLLM gateway (not Anthropic) +os.environ["ANTHROPIC_BASE_URL"] = "http://localhost:4000" +os.environ["ANTHROPIC_API_KEY"] = "sk-1234" # Your LiteLLM key + +# Use any model configured in LiteLLM +options = ClaudeAgentOptions( + model="bedrock-claude-sonnet-4", # or gpt-4, or anything else + system_prompt="You are a helpful assistant.", + max_turns=50, +) +``` + +Note: Don't add `/anthropic` to the base URL - LiteLLM handles the routing automatically. + +## Why Use This? + +- **Switch providers easily**: Use the same code with OpenAI, Bedrock, Azure, etc. +- **Cost tracking**: LiteLLM tracks spending across all your agent conversations +- **Rate limiting**: Set budgets and limits on your agent usage +- **Load balancing**: Distribute requests across multiple API keys or regions +- **Fallbacks**: Automatically retry with a different model if one fails + +## Troubleshooting + +**Connection errors?** +- Make sure LiteLLM is running: `litellm --model your-model` +- Check the URL is correct (default: `http://localhost:4000`) + +**Authentication errors?** +- Verify your LiteLLM API key is correct +- Make sure the model is configured in your LiteLLM setup + +**Model not found?** +- Check the model name matches what's in your LiteLLM config +- Run `litellm --model your-model` to test it works + +**Agent with MCP stuck or failing?** +- The MCP server might not be available at `http://localhost:4000/mcp/deepwiki2` +- Try disabling MCP: `USE_MCP=false python agent_with_mcp.py` +- Or use the basic agent: `python main.py` + +## Learn More + +- [LiteLLM Docs](https://docs.litellm.ai/) +- [Claude Agent SDK](https://github.com/anthropics/anthropic-agent-sdk) +- [LiteLLM Proxy Guide](https://docs.litellm.ai/docs/proxy/quick_start) diff --git a/cookbook/anthropic_agent_sdk/agent_with_mcp.py b/cookbook/anthropic_agent_sdk/agent_with_mcp.py new file mode 100644 index 00000000000..ff25feb777f --- /dev/null +++ b/cookbook/anthropic_agent_sdk/agent_with_mcp.py @@ -0,0 +1,140 @@ +""" +Interactive Claude Agent SDK CLI with MCP Support + +This example demonstrates an interactive CLI chat with the Anthropic Agent SDK using LiteLLM as a proxy, +with MCP (Model Context Protocol) server integration for enhanced capabilities. +""" + +import asyncio +import os +from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions +from common import ( + Config, + fetch_available_models, + setup_litellm_env, + print_header, + handle_model_list, + handle_model_switch, + stream_response, +) + + +async def interactive_chat_with_mcp(): + """ + Interactive CLI chat with the agent and MCP server + """ + config = Config() + + # Configure Anthropic SDK to point to LiteLLM gateway + litellm_base_url = setup_litellm_env(config) + + # Fetch available models from proxy + available_models = await fetch_available_models(litellm_base_url, config.LITELLM_API_KEY) + + current_model = config.LITELLM_MODEL + + # MCP server configuration + mcp_server_url = f"{litellm_base_url}/mcp/deepwiki2" + use_mcp = os.getenv("USE_MCP", "true").lower() == "true" + + if not use_mcp: + print("⚠️ MCP disabled via USE_MCP=false") + + print_header(litellm_base_url, current_model, has_mcp=use_mcp) + + while True: + # Configure agent options + if use_mcp: + try: + # Try with MCP server (HTTP transport) + # Using McpHttpServerConfig format from Agent SDK + options = ClaudeAgentOptions( + system_prompt="You are a helpful AI assistant with access to DeepWiki for research. Be concise, accurate, and friendly.", + model=current_model, + max_turns=50, + mcp_servers={ + "deepwiki2": { + "type": "http", + "url": mcp_server_url, + "headers": { + "Authorization": f"Bearer {config.LITELLM_API_KEY}" + } + } + }, + ) + except Exception as e: + print(f"⚠️ Warning: Could not configure MCP server: {e}") + print("Continuing without MCP...\n") + use_mcp = False + options = ClaudeAgentOptions( + system_prompt="You are a helpful AI assistant. Be concise, accurate, and friendly.", + model=current_model, + max_turns=50, + ) + else: + # Without MCP + options = ClaudeAgentOptions( + system_prompt="You are a helpful AI assistant. Be concise, accurate, and friendly.", + model=current_model, + max_turns=50, + ) + + # Create agent client + try: + async with ClaudeSDKClient(options=options) as client: + conversation_active = True + + while conversation_active: + # Get user input + try: + user_input = input("\n👤 You: ").strip() + except (EOFError, KeyboardInterrupt): + print("\n\n👋 Goodbye!") + return + + # Handle commands + if user_input.lower() in ['quit', 'exit']: + print("\n👋 Goodbye!") + return + + if user_input.lower() == 'clear': + print("\n🔄 Starting new conversation...\n") + conversation_active = False + continue + + if user_input.lower() == 'models': + handle_model_list(available_models, current_model) + continue + + if user_input.lower() == 'model': + new_model, should_restart = handle_model_switch(available_models, current_model) + if should_restart: + current_model = new_model + conversation_active = False + continue + + if not user_input: + continue + + # Stream response from agent + await stream_response(client, user_input) + + except Exception as e: + print(f"\n❌ Error creating agent client: {e}") + print("This might be an MCP configuration issue. Try running without MCP:") + print(" USE_MCP=false python agent_with_mcp.py") + print("\nOr use the basic agent:") + print(" python main.py") + return + + +def main(): + """Run interactive chat with MCP""" + try: + asyncio.run(interactive_chat_with_mcp()) + except KeyboardInterrupt: + print("\n\n👋 Goodbye!") + + +if __name__ == "__main__": + main() diff --git a/cookbook/anthropic_agent_sdk/common.py b/cookbook/anthropic_agent_sdk/common.py new file mode 100644 index 00000000000..d9ee65cb58d --- /dev/null +++ b/cookbook/anthropic_agent_sdk/common.py @@ -0,0 +1,160 @@ +""" +Common utilities for Claude Agent SDK examples +""" + +import os +import httpx + + +class Config: + """Configuration for LiteLLM Gateway connection""" + + # LiteLLM proxy URL (default to local instance) + LITELLM_PROXY_URL = os.getenv("LITELLM_PROXY_URL", "http://localhost:4000") + + # LiteLLM API key (master key or virtual key) + LITELLM_API_KEY = os.getenv("LITELLM_API_KEY", "sk-1234") + + # Model name as configured in LiteLLM (e.g., "bedrock-claude-sonnet-4", "gpt-4", etc.) + LITELLM_MODEL = os.getenv("LITELLM_MODEL", "bedrock-claude-sonnet-4.5") + + +async def fetch_available_models(base_url: str, api_key: str) -> list[str]: + """ + Fetch available models from LiteLLM proxy /models endpoint + """ + try: + async with httpx.AsyncClient() as client: + response = await client.get( + f"{base_url}/models", + headers={"Authorization": f"Bearer {api_key}"}, + timeout=10.0 + ) + response.raise_for_status() + data = response.json() + return [model["id"] for model in data.get("data", [])] + except Exception as e: + print(f"⚠️ Warning: Could not fetch models from proxy: {e}") + print("Using default model list...") + # Fallback to default models + return [ + "bedrock-claude-sonnet-3.5", + "bedrock-claude-sonnet-4", + "bedrock-claude-sonnet-4.5", + "bedrock-claude-opus-4.5", + "bedrock-nova-premier", + ] + + +def setup_litellm_env(config: Config): + """ + Configure environment variables to point Agent SDK to LiteLLM + """ + litellm_base_url = config.LITELLM_PROXY_URL.rstrip('/') + os.environ["ANTHROPIC_BASE_URL"] = litellm_base_url + os.environ["ANTHROPIC_API_KEY"] = config.LITELLM_API_KEY + return litellm_base_url + + +def print_header(base_url: str, current_model: str, has_mcp: bool = False): + """ + Print the chat header + """ + mcp_indicator = " + MCP" if has_mcp else "" + print("=" * 70) + print(f"🤖 Claude Agent SDK with LiteLLM Gateway{mcp_indicator} - Interactive Chat") + print("=" * 70) + print(f"🚀 Connected to: {base_url}") + print(f"📦 Current model: {current_model}") + if has_mcp: + print("🔌 MCP: deepwiki2 enabled") + print("\nType your messages below. Commands:") + print(" - 'quit' or 'exit' to end the conversation") + print(" - 'clear' to start a new conversation") + print(" - 'model' to switch models") + print(" - 'models' to list available models") + print("=" * 70) + print() + + +def handle_model_list(available_models: list[str], current_model: str): + """ + Display available models + """ + print("\n📋 Available models:") + for i, model in enumerate(available_models, 1): + marker = "✓" if model == current_model else " " + print(f" {marker} {i}. {model}") + + +def handle_model_switch(available_models: list[str], current_model: str) -> tuple[str, bool]: + """ + Handle model switching + + Returns: + tuple: (new_model, should_restart_conversation) + """ + print("\n📋 Select a model:") + for i, model in enumerate(available_models, 1): + marker = "✓" if model == current_model else " " + print(f" {marker} {i}. {model}") + + try: + choice = input("\nEnter number (or press Enter to cancel): ").strip() + if choice: + idx = int(choice) - 1 + if 0 <= idx < len(available_models): + new_model = available_models[idx] + print(f"\n✅ Switched to: {new_model}") + print("🔄 Starting new conversation with new model...\n") + return new_model, True + else: + print("❌ Invalid choice") + except (ValueError, IndexError): + print("❌ Invalid input") + + return current_model, False + + +async def stream_response(client, user_input: str): + """ + Stream response from the agent + """ + print("\n🤖 Assistant: ", end='', flush=True) + + try: + await client.query(user_input) + + # Show loading indicator + print("⏳ thinking...", end='', flush=True) + + # Stream the response + first_chunk = True + async for msg in client.receive_response(): + # Clear loading indicator on first message + if first_chunk: + print("\r🤖 Assistant: ", end='', flush=True) + first_chunk = False + + # Handle different message types + if hasattr(msg, 'type'): + if msg.type == 'content_block_delta': + # Streaming text delta + if hasattr(msg, 'delta') and hasattr(msg.delta, 'text'): + print(msg.delta.text, end='', flush=True) + elif msg.type == 'content_block_start': + # Start of content block + if hasattr(msg, 'content_block') and hasattr(msg.content_block, 'text'): + print(msg.content_block.text, end='', flush=True) + + # Fallback to original content handling + if hasattr(msg, 'content'): + for content_block in msg.content: + if hasattr(content_block, 'text'): + print(content_block.text, end='', flush=True) + + print() # New line after response + + except Exception as e: + print(f"\r\n❌ Error: {e}") + print("Please check your LiteLLM gateway is running and configured correctly.") diff --git a/cookbook/anthropic_agent_sdk/config.example.yaml b/cookbook/anthropic_agent_sdk/config.example.yaml new file mode 100644 index 00000000000..eb1984fc4ea --- /dev/null +++ b/cookbook/anthropic_agent_sdk/config.example.yaml @@ -0,0 +1,25 @@ +model_list: + - model_name: bedrock-claude-sonnet-3.5 + litellm_params: + model: "bedrock/us.anthropic.claude-3-5-sonnet-20240620-v1:0" + aws_region_name: "us-east-1" + + - model_name: bedrock-claude-sonnet-4 + litellm_params: + model: "bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0" + aws_region_name: "us-east-1" + + - model_name: bedrock-claude-sonnet-4.5 + litellm_params: + model: "bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0" + aws_region_name: "us-east-1" + + - model_name: bedrock-claude-opus-4.5 + litellm_params: + model: "bedrock/us.anthropic.claude-opus-4-5-20251101-v1:0" + aws_region_name: "us-east-1" + + - model_name: bedrock-nova-premier + litellm_params: + model: "bedrock/amazon.nova-premier-v1:0" + aws_region_name: "us-east-1" diff --git a/cookbook/anthropic_agent_sdk/main.py b/cookbook/anthropic_agent_sdk/main.py new file mode 100644 index 00000000000..231b57ca97b --- /dev/null +++ b/cookbook/anthropic_agent_sdk/main.py @@ -0,0 +1,95 @@ +""" +Simple Interactive Claude Agent SDK CLI using LiteLLM Gateway + +This example demonstrates an interactive CLI chat with the Anthropic Agent SDK using LiteLLM as a proxy. +LiteLLM acts as a unified interface, allowing you to use any LLM provider (OpenAI, Azure, Bedrock, etc.) +through the Claude Agent SDK by pointing it to the LiteLLM gateway. +""" + +import asyncio +from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions +from common import ( + Config, + fetch_available_models, + setup_litellm_env, + print_header, + handle_model_list, + handle_model_switch, + stream_response, +) + + +async def interactive_chat(): + """ + Interactive CLI chat with the agent + """ + config = Config() + + # Configure Anthropic SDK to point to LiteLLM gateway + litellm_base_url = setup_litellm_env(config) + + # Fetch available models from proxy + available_models = await fetch_available_models(litellm_base_url, config.LITELLM_API_KEY) + + current_model = config.LITELLM_MODEL + + print_header(litellm_base_url, current_model) + + while True: + # Configure agent options for each conversation + options = ClaudeAgentOptions( + system_prompt="You are a helpful AI assistant. Be concise, accurate, and friendly.", + model=current_model, + max_turns=50, + ) + + # Create agent client + async with ClaudeSDKClient(options=options) as client: + conversation_active = True + + while conversation_active: + # Get user input + try: + user_input = input("\n👤 You: ").strip() + except (EOFError, KeyboardInterrupt): + print("\n\n👋 Goodbye!") + return + + # Handle commands + if user_input.lower() in ['quit', 'exit']: + print("\n👋 Goodbye!") + return + + if user_input.lower() == 'clear': + print("\n🔄 Starting new conversation...\n") + conversation_active = False + continue + + if user_input.lower() == 'models': + handle_model_list(available_models, current_model) + continue + + if user_input.lower() == 'model': + new_model, should_restart = handle_model_switch(available_models, current_model) + if should_restart: + current_model = new_model + conversation_active = False + continue + + if not user_input: + continue + + # Stream response from agent + await stream_response(client, user_input) + + +def main(): + """Run interactive chat""" + try: + asyncio.run(interactive_chat()) + except KeyboardInterrupt: + print("\n\n👋 Goodbye!") + + +if __name__ == "__main__": + main() diff --git a/cookbook/anthropic_agent_sdk/requirements.txt b/cookbook/anthropic_agent_sdk/requirements.txt new file mode 100644 index 00000000000..1e810bb7d99 --- /dev/null +++ b/cookbook/anthropic_agent_sdk/requirements.txt @@ -0,0 +1,2 @@ +claude-agent-sdk +httpx>=0.27.0 diff --git a/cookbook/nova_sonic_realtime.py b/cookbook/nova_sonic_realtime.py new file mode 100644 index 00000000000..0ea0badfb01 --- /dev/null +++ b/cookbook/nova_sonic_realtime.py @@ -0,0 +1,284 @@ +""" +Client script to test Nova Sonic realtime API through LiteLLM proxy. + +This script connects to LiteLLM proxy's realtime endpoint and enables +speech-to-speech conversation with Bedrock Nova Sonic. + +Prerequisites: +- LiteLLM proxy running with Bedrock configured +- pyaudio installed: pip install pyaudio +- websockets installed: pip install websockets + +Usage: + python nova_sonic_realtime.py +""" + +import asyncio +import base64 +import json +import pyaudio +import websockets +from typing import Optional + +# Audio configuration (matching Nova Sonic requirements) +INPUT_SAMPLE_RATE = 16000 # Nova Sonic expects 16kHz input +OUTPUT_SAMPLE_RATE = 24000 # Nova Sonic outputs 24kHz +CHANNELS = 1 +FORMAT = pyaudio.paInt16 +CHUNK_SIZE = 1024 + +# LiteLLM proxy configuration +LITELLM_PROXY_URL = "ws://localhost:4000/v1/realtime?model=bedrock-sonic" +LITELLM_API_KEY = "sk-12345" # Your LiteLLM API key + + +class RealtimeClient: + """Client for LiteLLM realtime API with audio support.""" + + def __init__(self, url: str, api_key: str): + self.url = url + self.api_key = api_key + self.ws: Optional[websockets.WebSocketClientProtocol] = None + self.is_active = False + self.audio_queue = asyncio.Queue() + self.pyaudio = pyaudio.PyAudio() + self.input_stream = None + self.output_stream = None + + async def connect(self): + """Connect to LiteLLM proxy realtime endpoint.""" + print(f"Connecting to {self.url}...") + + headers = {} + if self.api_key: + headers["Authorization"] = f"Bearer {self.api_key}" + + self.ws = await websockets.connect( + self.url, + additional_headers=headers, + max_size=10 * 1024 * 1024, # 10MB max message size + ) + self.is_active = True + print("✓ Connected to LiteLLM proxy") + + async def send_session_update(self): + """Send session configuration.""" + session_update = { + "type": "session.update", + "session": { + "instructions": "You are a friendly assistant. Keep your responses short and conversational.", + "voice": "matthew", + "temperature": 0.8, + "max_response_output_tokens": 1024, + "modalities": ["text", "audio"], + "input_audio_format": "pcm16", + "output_audio_format": "pcm16", + "turn_detection": { + "type": "server_vad", + "threshold": 0.5, + "prefix_padding_ms": 300, + "silence_duration_ms": 500, + }, + }, + } + await self.ws.send(json.dumps(session_update)) + print("✓ Session configuration sent") + + async def receive_messages(self): + """Receive and process messages from the server.""" + try: + async for message in self.ws: + if not self.is_active: + break + + try: + data = json.loads(message) + event_type = data.get("type") + + if event_type == "session.created": + print(f"✓ Session created: {data.get('session', {}).get('id')}") + + elif event_type == "response.created": + print("🤖 Assistant is responding...") + + elif event_type == "response.text.delta": + # Print text transcription + delta = data.get("delta", "") + print(delta, end="", flush=True) + + elif event_type == "response.audio.delta": + # Queue audio for playback + audio_b64 = data.get("delta", "") + if audio_b64: + audio_bytes = base64.b64decode(audio_b64) + await self.audio_queue.put(audio_bytes) + + elif event_type == "response.text.done": + print() # New line after text + + elif event_type == "response.done": + print("✓ Response complete") + + elif event_type == "error": + print(f"❌ Error: {data.get('error', {})}") + + else: + # Debug: print other event types + print(f"[{event_type}]", end=" ") + + except json.JSONDecodeError: + print(f"Failed to parse message: {message[:100]}") + + except websockets.exceptions.ConnectionClosed: + print("\n✗ Connection closed") + except Exception as e: + print(f"\n✗ Error receiving messages: {e}") + finally: + self.is_active = False + + async def send_audio_chunk(self, audio_bytes: bytes): + """Send audio chunk to server.""" + if not self.is_active or not self.ws: + return + + audio_b64 = base64.b64encode(audio_bytes).decode("utf-8") + message = { + "type": "input_audio_buffer.append", + "audio": audio_b64, + } + await self.ws.send(json.dumps(message)) + + async def commit_audio_buffer(self): + """Commit the audio buffer to trigger processing.""" + if not self.is_active or not self.ws: + return + + message = {"type": "input_audio_buffer.commit"} + await self.ws.send(json.dumps(message)) + + async def capture_audio(self): + """Capture audio from microphone and send to server.""" + print("\n🎤 Starting audio capture...") + print("Speak into your microphone. Press Ctrl+C to stop.\n") + + self.input_stream = self.pyaudio.open( + format=FORMAT, + channels=CHANNELS, + rate=INPUT_SAMPLE_RATE, + input=True, + frames_per_buffer=CHUNK_SIZE, + ) + + try: + while self.is_active: + audio_data = self.input_stream.read(CHUNK_SIZE, exception_on_overflow=False) + await self.send_audio_chunk(audio_data) + await asyncio.sleep(0.01) # Small delay to prevent overwhelming + except Exception as e: + print(f"Error capturing audio: {e}") + finally: + if self.input_stream: + self.input_stream.stop_stream() + self.input_stream.close() + + async def play_audio(self): + """Play audio responses from the server.""" + print("🔊 Starting audio playback...") + + self.output_stream = self.pyaudio.open( + format=FORMAT, + channels=CHANNELS, + rate=OUTPUT_SAMPLE_RATE, + output=True, + frames_per_buffer=CHUNK_SIZE, + ) + + try: + while self.is_active: + try: + audio_data = await asyncio.wait_for( + self.audio_queue.get(), timeout=0.1 + ) + if audio_data: + self.output_stream.write(audio_data) + except asyncio.TimeoutError: + continue + except Exception as e: + print(f"Error playing audio: {e}") + finally: + if self.output_stream: + self.output_stream.stop_stream() + self.output_stream.close() + + async def close(self): + """Close the connection and cleanup.""" + self.is_active = False + + if self.ws: + await self.ws.close() + + if self.input_stream: + self.input_stream.stop_stream() + self.input_stream.close() + + if self.output_stream: + self.output_stream.stop_stream() + self.output_stream.close() + + self.pyaudio.terminate() + print("\n✓ Connection closed") + + +async def main(): + """Main function to run the realtime client.""" + print("=" * 80) + print("Bedrock Nova Sonic Realtime Client") + print("=" * 80) + print() + + client = RealtimeClient(LITELLM_PROXY_URL, LITELLM_API_KEY) + + try: + # Connect to server + await client.connect() + + # Send session configuration + await client.send_session_update() + + # Wait a moment for session to be established + await asyncio.sleep(0.5) + + # Start tasks + receive_task = asyncio.create_task(client.receive_messages()) + capture_task = asyncio.create_task(client.capture_audio()) + playback_task = asyncio.create_task(client.play_audio()) + + # Wait for user to interrupt + await asyncio.gather( + receive_task, + capture_task, + playback_task, + return_exceptions=True, + ) + + except KeyboardInterrupt: + print("\n\n⚠ Interrupted by user") + except Exception as e: + print(f"\n❌ Error: {e}") + import traceback + traceback.print_exc() + finally: + await client.close() + + +if __name__ == "__main__": + print("\nMake sure:") + print("1. LiteLLM proxy is running on port 4000") + print("2. Bedrock is configured in proxy_server_config.yaml") + print("3. AWS credentials are set") + print() + + try: + asyncio.run(main()) + except KeyboardInterrupt: + print("\n\nGoodbye!") diff --git a/deploy/charts/litellm-helm/templates/deployment.yaml b/deploy/charts/litellm-helm/templates/deployment.yaml index c3e0055e380..4ac5582d060 100644 --- a/deploy/charts/litellm-helm/templates/deployment.yaml +++ b/deploy/charts/litellm-helm/templates/deployment.yaml @@ -38,6 +38,10 @@ spec: serviceAccountName: {{ include "litellm.serviceAccountName" . }} securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} + {{- with .Values.extraInitContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} containers: - name: {{ include "litellm.name" . }} securityContext: diff --git a/deploy/charts/litellm-helm/templates/migrations-job.yaml b/deploy/charts/litellm-helm/templates/migrations-job.yaml index f8893a47afe..3459fa12d1c 100644 --- a/deploy/charts/litellm-helm/templates/migrations-job.yaml +++ b/deploy/charts/litellm-helm/templates/migrations-job.yaml @@ -35,6 +35,10 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "litellm.serviceAccountName" . }} + {{- with .Values.migrationJob.extraInitContainers }} + initContainers: + {{- toYaml . | nindent 8 }} + {{- end }} containers: - name: prisma-migrations image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default (printf "main-%s" .Chart.AppVersion) }}" diff --git a/deploy/charts/litellm-helm/values.yaml b/deploy/charts/litellm-helm/values.yaml index b75ce640370..cea25974bb0 100644 --- a/deploy/charts/litellm-helm/values.yaml +++ b/deploy/charts/litellm-helm/values.yaml @@ -281,6 +281,7 @@ migrationJob: # cpu: 100m # memory: 100Mi extraContainers: [] + extraInitContainers: [] # Hook configuration hooks: diff --git a/docker/Dockerfile.custom_ui b/docker/Dockerfile.custom_ui index c437929a27e..57926bcd170 100644 --- a/docker/Dockerfile.custom_ui +++ b/docker/Dockerfile.custom_ui @@ -5,7 +5,8 @@ FROM ghcr.io/berriai/litellm:litellm_fwd_server_root_path-dev WORKDIR /app # Install Node.js and npm (adjust version as needed) -RUN apt-get update && apt-get install -y nodejs npm +RUN apt-get update && apt-get install -y nodejs npm && \ + npm install -g npm@latest tar@latest # Copy the UI source into the container COPY ./ui/litellm-dashboard /app/ui/litellm-dashboard diff --git a/docker/Dockerfile.database b/docker/Dockerfile.database index 49655129506..24bf706434d 100644 --- a/docker/Dockerfile.database +++ b/docker/Dockerfile.database @@ -49,7 +49,8 @@ FROM $LITELLM_RUNTIME_IMAGE AS runtime USER root # Install runtime dependencies -RUN apk add --no-cache bash openssl tzdata nodejs npm python3 py3-pip libsndfile +RUN apk add --no-cache bash openssl tzdata nodejs npm python3 py3-pip libsndfile && \ + npm install -g npm@latest tar@latest WORKDIR /app # Copy the current directory contents into the container at /app diff --git a/docker/Dockerfile.dev b/docker/Dockerfile.dev index 67966f9c739..ae557d4647f 100644 --- a/docker/Dockerfile.dev +++ b/docker/Dockerfile.dev @@ -61,7 +61,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ libatomic1 \ nodejs \ npm \ - && rm -rf /var/lib/apt/lists/* + && rm -rf /var/lib/apt/lists/* \ + && npm install -g npm@latest tar@latest WORKDIR /app diff --git a/docker/Dockerfile.non_root b/docker/Dockerfile.non_root index 8c795f3b17f..9ff27e07494 100644 --- a/docker/Dockerfile.non_root +++ b/docker/Dockerfile.non_root @@ -104,7 +104,8 @@ RUN for i in 1 2 3; do \ done \ && for i in 1 2 3; do \ apk add --no-cache python3 py3-pip bash openssl tzdata nodejs npm supervisor && break || sleep 5; \ - done + done \ + && npm install -g npm@latest tar@latest # Copy artifacts from builder COPY --from=builder /app/requirements.txt /app/requirements.txt @@ -170,12 +171,14 @@ RUN sed -i 's/\r$//' docker/entrypoint.sh && \ [ -n "$LITELLM_PROXY_EXTRAS_PATH" ] && chmod -R g+w $LITELLM_PROXY_EXTRAS_PATH || true && \ chmod -R g+rX $PRISMA_PATH && \ chmod -R g+rX /app/.cache && \ - mkdir -p /tmp/.npm /nonexistent /.npm && \ - prisma generate + mkdir -p /tmp/.npm /nonexistent /.npm # Switch to non-root user for runtime USER nobody +# Generate Prisma client as nobody user to ensure correct file ownership +RUN prisma generate + # Prisma runtime knobs for offline containers ENV PRISMA_SKIP_POSTINSTALL_GENERATE=1 \ PRISMA_HIDE_UPDATE_MESSAGE=1 \ diff --git a/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md b/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md index 7015918e924..8a54426dfb0 100644 --- a/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md +++ b/docs/my-website/blog/anthropic_opus_4_5_and_advanced_features/index.md @@ -15,6 +15,7 @@ authors: title: "CTO, LiteLLM" url: https://www.linkedin.com/in/reffajnaahsi/ image_url: https://pbs.twimg.com/profile_images/1613813310264340481/lz54oEiB_400x400.jpg +description: "Guide to Claude Opus 4.5 and advanced features in LiteLLM: Tool Search, Programmatic Tool Calling, and Effort Parameter." tags: [anthropic, claude, tool search, programmatic tool calling, effort, advanced features] hide_table_of_contents: false --- diff --git a/docs/my-website/blog/gemini_3/index.md b/docs/my-website/blog/gemini_3/index.md index 26dbc2d02b5..7263acc12c9 100644 --- a/docs/my-website/blog/gemini_3/index.md +++ b/docs/my-website/blog/gemini_3/index.md @@ -15,6 +15,7 @@ authors: title: "CTO, LiteLLM" url: https://www.linkedin.com/in/reffajnaahsi/ image_url: https://pbs.twimg.com/profile_images/1613813310264340481/lz54oEiB_400x400.jpg +description: "Common questions and best practices for using gemini-3-pro-preview with LiteLLM Proxy and SDK." tags: [gemini, day 0 support, llms] hide_table_of_contents: false --- diff --git a/docs/my-website/blog/gemini_3_flash/index.md b/docs/my-website/blog/gemini_3_flash/index.md index 6cb8ddad992..830c21e5f66 100644 --- a/docs/my-website/blog/gemini_3_flash/index.md +++ b/docs/my-website/blog/gemini_3_flash/index.md @@ -15,6 +15,7 @@ authors: title: "CTO, LiteLLM" url: https://www.linkedin.com/in/reffajnaahsi/ image_url: https://pbs.twimg.com/profile_images/1613813310264340481/lz54oEiB_400x400.jpg +description: "Guide to using Gemini 3 Flash on LiteLLM Proxy and SDK with day 0 support." tags: [gemini, day 0 support, llms] hide_table_of_contents: false --- diff --git a/docs/my-website/blog/sub_millisecond_proxy_overhead/index.md b/docs/my-website/blog/sub_millisecond_proxy_overhead/index.md new file mode 100644 index 00000000000..1857383363c --- /dev/null +++ b/docs/my-website/blog/sub_millisecond_proxy_overhead/index.md @@ -0,0 +1,92 @@ +--- +slug: sub-millisecond-proxy-overhead +title: "Achieving Sub-Millisecond Proxy Overhead" +date: 2026-02-02T10:00:00 +authors: + - name: Alexsander Hamir + title: "Performance Engineer, LiteLLM" + url: https://www.linkedin.com/in/alexsander-baptista/ + image_url: https://github.com/AlexsanderHamir.png + - name: Krrish Dholakia + title: "CEO, LiteLLM" + url: https://www.linkedin.com/in/krish-d/ + image_url: https://pbs.twimg.com/profile_images/1298587542745358340/DZv3Oj-h_400x400.jpg + - name: Ishaan Jaff + title: "CTO, LiteLLM" + url: https://www.linkedin.com/in/reffajnaahsi/ + image_url: https://pbs.twimg.com/profile_images/1613813310264340481/lz54oEiB_400x400.jpg +description: "Our Q1 performance target and architectural direction for achieving sub-millisecond proxy overhead on modest hardware." +tags: [performance, architecture] +hide_table_of_contents: false +--- + +![Sidecar architecture: Python control plane vs. sidecar hot path](https://raw.githubusercontent.com/AlexsanderHamir/assets/main/Screenshot%202026-02-02%20172554.png) + +# Achieving Sub-Millisecond Proxy Overhead + +## Introduction + +Our Q1 performance target is to aggressively move toward sub-millisecond proxy overhead on a single instance with 4 CPUs and 8 GB of RAM, and to continue pushing that boundary over time. Our broader goal is to make LiteLLM inexpensive to deploy, lightweight, and fast. This post outlines the architectural direction behind that effort. + +Proxy overhead refers to the latency introduced by LiteLLM itself, independent of the upstream provider. + +To measure it, we run the same workload directly against the provider and through LiteLLM at identical QPS (for example, 1,000 QPS) and compare the latency delta. To reduce noise, the load generator, LiteLLM, and a mock LLM endpoint all run on the same machine, ensuring the difference reflects proxy overhead rather than network latency. + +--- + +## Where We're Coming From + +Under the same benchmark originally conducted by [TensorZero](https://www.tensorzero.com/docs/gateway/benchmarks), LiteLLM previously failed at around 1,000 QPS. + +That is no longer the case. Today, LiteLLM can be stress-tested at 1,000 QPS with no failures and can scale up to 5,000 QPS without failures on a 4-CPU, 8-GB RAM single instance setup. + +This establishes a more up to date baseline and provides useful context as we continue working on proxy overhead and overall performance. + +--- + +## Design Choice + +Achieving sub-millisecond proxy overhead with a Python-based system requires being deliberate about where work happens. + +Python is a strong fit for flexibility and extensibility: provider abstraction, configuration-driven routing, and a rich callback ecosystem. These are areas where development velocity and correctness matter more than raw throughput. + +At higher request rates, however, certain classes of work become expensive when executed inside the Python process on every request. Rather than rewriting LiteLLM or introducing complex deployment requirements, we adopt an optional **sidecar architecture**. + +This architectural change is how we intend to make LiteLLM **permanently fast**. While it supports our near-term performance targets, it is a long-term investment. + +Python continues to own: + +- Request validation and normalization +- Model and provider selection +- Callbacks and integrations + +The sidecar owns **performance-critical execution**, such as: + +- Efficient request forwarding +- Connection reuse and pooling +- Enforcing timeouts and limits +- Aggregating high-frequency metrics + +This separation allows each component to focus on what it does best: Python acts as the control plane, while the sidecar handles the hot path. + +--- + +### Why the Sidecar Is Optional + +The sidecar is intentionally **optional**. + +This allows us to ship it incrementally, validate it under real-world workloads, and avoid making it a hard dependency before it is fully battle-tested across all LiteLLM features. + +Just as importantly, this ensures that self-hosting LiteLLM remains simple. The sidecar is bundled and started automatically, requires no additional infrastructure, and can be disabled entirely. From a user's perspective, LiteLLM continues to behave like a single service. + +As of today, the sidecar is an optimization, not a requirement. + +--- + +## Conclusion + +Sub-millisecond proxy overhead is not achieved through a single optimization, but through architectural changes. + +By keeping Python focused on orchestration and extensibility, and offloading performance-critical execution to a sidecar, we establish a foundation for making LiteLLM **permanently fast over time**—even on modest hardware such as a 1-CPU, 2-GB RAM instance, while keeping deployment and self-hosting simple. + +This work extends beyond Q1, and we will continue sharing benchmarks and updates as the architecture evolves. diff --git a/docs/my-website/docs/a2a.md b/docs/my-website/docs/a2a.md index 7b863f185d0..a7e8b52d99a 100644 --- a/docs/my-website/docs/a2a.md +++ b/docs/my-website/docs/a2a.md @@ -68,7 +68,7 @@ Follow [this guide, to add your pydantic ai agent to LiteLLM Agent Gateway](./pr ## Invoking your Agents -Use the [A2A Python SDK](https://pypi.org/project/a2a/) to invoke agents through LiteLLM. +Use the [A2A Python SDK](https://pypi.org/project/a2a-sdk) to invoke agents through LiteLLM. This example shows how to: 1. **List available agents** - Query `/v1/agents` to see which agents your key can access diff --git a/docs/my-website/docs/benchmarks.md b/docs/my-website/docs/benchmarks.md index 640212808bd..a1489081b4c 100644 --- a/docs/my-website/docs/benchmarks.md +++ b/docs/my-website/docs/benchmarks.md @@ -48,6 +48,28 @@ In these tests the baseline latency characteristics are measured against a fake- - High-percentile latencies drop significantly: P95 630 ms → 150 ms, P99 1,200 ms → 240 ms. - Setting workers equal to CPU count gives optimal performance. +## `/realtime` API Benchmarks + +End-to-end latency benchmarks for the `/realtime` endpoint tested against a fake realtime endpoint. + +### Performance Metrics + +| Metric | Value | +| --------------- | ---------- | +| Median latency | 59 ms | +| p95 latency | 67 ms | +| p99 latency | 99 ms | +| Average latency | 63 ms | +| RPS | 1,207 | + +### Test Setup + +| Category | Specification | +|----------|---------------| +| **Load Testing** | Locust: 1,000 concurrent users, 500 ramp-up | +| **System** | 4 vCPUs, 8 GB RAM, 4 workers, 4 instances | +| **Database** | PostgreSQL (Redis unused) | + ## Machine Spec used for testing Each machine deploying LiteLLM had the following specs: diff --git a/docs/my-website/docs/enterprise.md b/docs/my-website/docs/enterprise.md index 2eed0f53e59..0a1b47f0621 100644 --- a/docs/my-website/docs/enterprise.md +++ b/docs/my-website/docs/enterprise.md @@ -74,6 +74,18 @@ You can find [supported data regions litellm here](../docs/data_security#support ## Frequently Asked Questions +### How to set up and verify your Enterprise License + +1. Add your license key to the environment: + +```env +LITELLM_LICENSE="eyJ..." +``` + +2. Restart LiteLLM Proxy. + +3. Open `http://:/` — the Swagger page should show **"Enterprise Edition"** in the description. If it doesn't, check that the key is correct, unexpired, and that the proxy was fully restarted. + ### SLA's + Professional Support Professional Support can assist with LLM/Provider integrations, deployment, upgrade management, and LLM Provider troubleshooting. We can’t solve your own infrastructure-related issues but we will guide you to fix them. diff --git a/docs/my-website/docs/mcp_semantic_filter.md b/docs/my-website/docs/mcp_semantic_filter.md new file mode 100644 index 00000000000..c58be80a680 --- /dev/null +++ b/docs/my-website/docs/mcp_semantic_filter.md @@ -0,0 +1,158 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# MCP Semantic Tool Filter + +Automatically filter MCP tools by semantic relevance. When you have many MCP tools registered, LiteLLM semantically matches the user's query against tool descriptions and sends only the most relevant tools to the LLM. + +## How It Works + +Tool search shifts tool selection from a prompt-engineering problem to a retrieval problem. Instead of injecting a large static list of tools into every prompt, the semantic filter: + +1. Builds a semantic index of all available MCP tools on startup +2. On each request, semantically matches the user's query against tool descriptions +3. Returns only the top-K most relevant tools to the LLM + +This approach improves context efficiency, increases reliability by reducing tool confusion, and enables scalability to ecosystems with hundreds or thousands of MCP tools. + +```mermaid +sequenceDiagram + participant Client + participant LiteLLM as LiteLLM Proxy + participant SemanticFilter as Semantic Filter + participant MCP as MCP Registry + participant LLM as LLM Provider + + Note over LiteLLM,MCP: Startup: Build Semantic Index + LiteLLM->>MCP: Fetch all registered MCP tools + MCP->>LiteLLM: Return all tools (e.g., 50 tools) + LiteLLM->>SemanticFilter: Build semantic router with embeddings + SemanticFilter->>LLM: Generate embeddings for tool descriptions + LLM->>SemanticFilter: Return embeddings + Note over SemanticFilter: Index ready for fast lookup + + Note over Client,LLM: Request: Semantic Tool Filtering + Client->>LiteLLM: POST /v1/responses with MCP tools + LiteLLM->>SemanticFilter: Expand MCP references (50 tools available) + SemanticFilter->>SemanticFilter: Extract user query from request + SemanticFilter->>LLM: Generate query embedding + LLM->>SemanticFilter: Return query embedding + SemanticFilter->>SemanticFilter: Match query against tool embeddings + SemanticFilter->>LiteLLM: Return top-K tools (e.g., 3 most relevant) + LiteLLM->>LLM: Forward request with filtered tools (3 tools) + LLM->>LiteLLM: Return response + LiteLLM->>Client: Response with headers
x-litellm-semantic-filter: 50->3
x-litellm-semantic-filter-tools: tool1,tool2,tool3 +``` + +## Configuration + +Enable semantic filtering in your LiteLLM config: + +```yaml title="config.yaml" showLineNumbers +litellm_settings: + mcp_semantic_tool_filter: + enabled: true + embedding_model: "text-embedding-3-small" # Model for semantic matching + top_k: 5 # Max tools to return + similarity_threshold: 0.3 # Min similarity score +``` + +**Configuration Options:** +- `enabled` - Enable/disable semantic filtering (default: `false`) +- `embedding_model` - Model for generating embeddings (default: `"text-embedding-3-small"`) +- `top_k` - Maximum number of tools to return (default: `10`) +- `similarity_threshold` - Minimum similarity score for matches (default: `0.3`) + +## Usage + +Use MCP tools normally with the Responses API or Chat Completions. The semantic filter runs automatically: + + + + +```bash title="Responses API with Semantic Filtering" showLineNumbers +curl --location 'http://localhost:4000/v1/responses' \ +--header 'Content-Type: application/json' \ +--header "Authorization: Bearer sk-1234" \ +--data '{ + "model": "gpt-4o", + "input": [ + { + "role": "user", + "content": "give me TLDR of what BerriAI/litellm repo is about", + "type": "message" + } + ], + "tools": [ + { + "type": "mcp", + "server_url": "litellm_proxy", + "require_approval": "never" + } + ], + "tool_choice": "required" +}' +``` + + + + +```bash title="Chat Completions with Semantic Filtering" showLineNumbers +curl --location 'http://localhost:4000/v1/chat/completions' \ +--header 'Content-Type: application/json' \ +--header "Authorization: Bearer sk-1234" \ +--data '{ + "model": "gpt-4o", + "messages": [ + {"role": "user", "content": "Search Wikipedia for LiteLLM"} + ], + "tools": [ + { + "type": "mcp", + "server_url": "litellm_proxy" + } + ] +}' +``` + + + + +## Response Headers + +The semantic filter adds diagnostic headers to every response: + +``` +x-litellm-semantic-filter: 10->3 +x-litellm-semantic-filter-tools: wikipedia-fetch,github-search,slack-post +``` + +- **`x-litellm-semantic-filter`** - Shows before→after tool count (e.g., `10->3` means 10 tools were filtered down to 3) +- **`x-litellm-semantic-filter-tools`** - CSV list of the filtered tool names (max 150 chars, clipped with `...` if longer) + +These headers help you understand which tools were selected for each request and verify the filter is working correctly. + +## Example + +If you have 50 MCP tools registered and make a request asking about Wikipedia, the semantic filter will: + +1. Semantically match your query `"Search Wikipedia for LiteLLM"` against all 50 tool descriptions +2. Select the top 5 most relevant tools (e.g., `wikipedia-fetch`, `wikipedia-search`, etc.) +3. Pass only those 5 tools to the LLM +4. Add headers showing `x-litellm-semantic-filter: 50->5` + +This dramatically reduces prompt size while ensuring the LLM has access to the right tools for the task. + +## Performance + +The semantic filter is optimized for production: +- Router builds once on startup (no per-request overhead) +- Semantic matching typically takes under 50ms +- Fails gracefully - returns all tools if filtering fails +- No impact on latency for requests without MCP tools + +## Related + +- [MCP Overview](./mcp.md) - Learn about MCP in LiteLLM +- [MCP Permission Management](./mcp_control.md) - Control tool access by key/team +- [Using MCP](./mcp_usage.md) - Complete MCP usage guide diff --git a/docs/my-website/docs/observability/datadog.md b/docs/my-website/docs/observability/datadog.md index 7cf91ced34c..6f785be1013 100644 --- a/docs/my-website/docs/observability/datadog.md +++ b/docs/my-website/docs/observability/datadog.md @@ -7,6 +7,7 @@ import TabItem from '@theme/TabItem'; LiteLLM Supports logging to the following Datdog Integrations: - `datadog` [Datadog Logs](https://docs.datadoghq.com/logs/) - `datadog_llm_observability` [Datadog LLM Observability](https://www.datadoghq.com/product/llm-observability/) +- `datadog_cost_management` [Datadog Cloud Cost Management](#datadog-cloud-cost-management) - `ddtrace-run` [Datadog Tracing](#datadog-tracing) ## Datadog Logs @@ -73,7 +74,7 @@ Send logs through a local DataDog agent (useful for containerized environments): ```shell LITELLM_DD_AGENT_HOST="localhost" # hostname or IP of DataDog agent LITELLM_DD_AGENT_PORT="10518" # [OPTIONAL] port of DataDog agent (default: 10518) -DD_API_KEY="5f2d0f310***********" # [OPTIONAL] your datadog API Key (agent handles auth) +DD_API_KEY="5f2d0f310***********" # [OPTIONAL] your datadog API Key (Agent handles auth for Logs. REQUIRED for LLM Observability) DD_SOURCE="litellm_dev" # [OPTIONAL] your datadog source ``` @@ -84,6 +85,9 @@ When `LITELLM_DD_AGENT_HOST` is set, logs are sent to the agent instead of direc **Note:** We use `LITELLM_DD_AGENT_HOST` instead of `DD_AGENT_HOST` to avoid conflicts with `ddtrace` which automatically sets `DD_AGENT_HOST` for APM tracing. +> [!IMPORTANT] +> **Datadog LLM Observability**: `DD_API_KEY` is **REQUIRED** even when using the Datadog Agent (`LITELLM_DD_AGENT_HOST`). The agent acts as a proxy but the API key header is mandatory for the LLM Observability endpoint. + **Step 3**: Start the proxy, make a test request Start proxy @@ -161,6 +165,50 @@ On the Datadog LLM Observability page, you should see that both input messages a + + + +## Datadog Cloud Cost Management + +| Feature | Details | +|---------|---------| +| **What is logged** | Aggregated LLM Costs (FOCUS format) | +| **Events** | Periodic Uploads of Aggregated Cost Data | +| **Product Link** | [Datadog Cloud Cost Management](https://docs.datadoghq.com/cost_management/) | + +We will use the `--config` to set `litellm.callbacks = ["datadog_cost_management"]`. This will periodically upload aggregated LLM cost data to Datadog. + +**Step 1**: Create a `config.yaml` file and set `litellm_settings`: `success_callback` + +```yaml +model_list: + - model_name: gpt-3.5-turbo + litellm_params: + model: gpt-3.5-turbo +litellm_settings: + callbacks: ["datadog_cost_management"] +``` + +**Step 2**: Set Required env variables + +```shell +DD_API_KEY="your-api-key" +DD_APP_KEY="your-app-key" # REQUIRED for Cost Management +DD_SITE="us5.datadoghq.com" +``` + +**Step 3**: Start the proxy + +```shell +litellm --config config.yaml +``` + +**How it works** +* LiteLLM aggregates costs in-memory by Provider, Model, Date, and Tags. +* Requires `DD_APP_KEY` for the Custom Costs API. +* Costs are uploaded periodically (flushed). + + ### Datadog Tracing Use `ddtrace-run` to enable [Datadog Tracing](https://ddtrace.readthedocs.io/en/stable/installation_quickstart.html) on litellm proxy @@ -203,5 +251,5 @@ LiteLLM supports customizing the following Datadog environment variables | `POD_NAME` | Pod name tag (useful for Kubernetes deployments) | "unknown" | ❌ No | \* **Required when using Direct API** (default): `DD_API_KEY` and `DD_SITE` are required -\* **Optional when using DataDog Agent**: Set `LITELLM_DD_AGENT_HOST` to use agent mode; `DD_API_KEY` and `DD_SITE` are not required +\* **Optional when using DataDog Agent**: Set `LITELLM_DD_AGENT_HOST` to use agent mode; `DD_API_KEY` and `DD_SITE` are not required for **Datadog Logs**. (**Note: `DD_API_KEY` IS REQUIRED for Datadog LLM Observability**) diff --git a/docs/my-website/docs/pass_through/openai_passthrough.md b/docs/my-website/docs/pass_through/openai_passthrough.md index d7c98eba7b3..49026f8aa2d 100644 --- a/docs/my-website/docs/pass_through/openai_passthrough.md +++ b/docs/my-website/docs/pass_through/openai_passthrough.md @@ -1,6 +1,6 @@ # OpenAI Passthrough -Pass-through endpoints for `/openai` +Pass-through endpoints for direct OpenAI API access ## Overview @@ -10,12 +10,27 @@ Pass-through endpoints for `/openai` | Logging | ✅ | Works across all integrations | | Streaming | ✅ | Fully supported | -### When to use this? +## Available Endpoints + +### `/openai_passthrough` - Recommended +Dedicated passthrough endpoint that guarantees direct routing to OpenAI without conflicts. + +**Use this for:** +- OpenAI Responses API (`/v1/responses`) +- Any endpoint where you need guaranteed passthrough +- When `/openai` routes are conflicting with LiteLLM's native implementations + +### `/openai` - Legacy +Standard passthrough endpoint that may conflict with LiteLLM's native implementations. + +**Note:** Some endpoints like `/openai/v1/responses` will be routed to LiteLLM's native implementation instead of OpenAI. + +## When to use this? - For 90% of your use cases, you should use the [native LiteLLM OpenAI Integration](https://docs.litellm.ai/docs/providers/openai) (`/chat/completions`, `/embeddings`, `/completions`, `/images`, `/batches`, etc.) -- Use this passthrough to call less popular or newer OpenAI endpoints that LiteLLM doesn't fully support yet, such as `/assistants`, `/threads`, `/vector_stores` +- Use `/openai_passthrough` to call less popular or newer OpenAI endpoints that LiteLLM doesn't fully support yet, such as `/assistants`, `/threads`, `/vector_stores`, `/responses` -Simply replace `https://api.openai.com` with `LITELLM_PROXY_BASE_URL/openai` +Simply replace `https://api.openai.com` with `LITELLM_PROXY_BASE_URL/openai_passthrough` ## Usage Examples @@ -34,7 +49,7 @@ Make sure you do the following: import openai client = openai.OpenAI( - base_url="http://0.0.0.0:4000/openai", # /openai + base_url="http://0.0.0.0:4000/openai_passthrough", # /openai_passthrough api_key="sk-anything" # ) ``` diff --git a/docs/my-website/docs/providers/anthropic_tool_search.md b/docs/my-website/docs/providers/anthropic_tool_search.md index 28ce5688eeb..203a2947ebc 100644 --- a/docs/my-website/docs/providers/anthropic_tool_search.md +++ b/docs/my-website/docs/providers/anthropic_tool_search.md @@ -1,43 +1,46 @@ -# Anthropic Tool Search +# Tool Search Tool search enables Claude to dynamically discover and load tools on-demand from large tool catalogs (10,000+ tools). Instead of loading all tool definitions into the context window upfront, Claude searches your tool catalog and loads only the tools it needs. +## Supported Providers + +| Provider | Chat Completions API | Messages API | +|----------|---------------------|--------------| +| **Anthropic API** | ✅ | ✅ | +| **Azure Anthropic** (Microsoft Foundry) | ✅ | ✅ | +| **Google Cloud Vertex AI** | ✅ | ✅ | +| **Amazon Bedrock** | ✅ (Invoke API only, Opus 4.5 only) | ✅ (Invoke API only, Opus 4.5 only) | + + ## Benefits - **Context efficiency**: Avoid consuming massive portions of your context window with tool definitions - **Better tool selection**: Claude's tool selection accuracy degrades with more than 30-50 tools. Tool search maintains accuracy even with thousands of tools - **On-demand loading**: Tools are only loaded when Claude needs them -## Supported Models - -Tool search is available on: -- Claude Opus 4.5 -- Claude Sonnet 4.5 - -## Supported Platforms - -- Anthropic API (direct) -- Azure Anthropic (Microsoft Foundry) -- Google Cloud Vertex AI -- Amazon Bedrock (invoke API only, not converse API) - ## Tool Search Variants LiteLLM supports both tool search variants: ### 1. Regex Tool Search (`tool_search_tool_regex_20251119`) -Claude constructs regex patterns to search for tools. +Claude constructs regex patterns to search for tools. Best for exact pattern matching (faster). ### 2. BM25 Tool Search (`tool_search_tool_bm25_20251119`) -Claude uses natural language queries to search for tools using the BM25 algorithm. +Claude uses natural language queries to search for tools using the BM25 algorithm. Best for natural language semantic search. -## Quick Start +**Note**: BM25 variant is not supported on Bedrock. -### Basic Example with Regex Tool Search +--- -```python +## Chat Completions API + +### SDK Usage + +#### Basic Example with Regex Tool Search + +```python showLineNumbers title="Basic Tool Search Example" import litellm response = litellm.completion( @@ -70,26 +73,6 @@ response = litellm.completion( } }, "defer_loading": True # Mark for deferred loading - }, - # Another deferred tool - { - "type": "function", - "function": { - "name": "search_files", - "description": "Search through files in the workspace", - "parameters": { - "type": "object", - "properties": { - "query": {"type": "string"}, - "file_types": { - "type": "array", - "items": {"type": "string"} - } - }, - "required": ["query"] - } - }, - "defer_loading": True } ] ) @@ -97,9 +80,9 @@ response = litellm.completion( print(response.choices[0].message.content) ``` -### BM25 Tool Search Example +#### BM25 Tool Search Example -```python +```python showLineNumbers title="BM25 Tool Search" import litellm response = litellm.completion( @@ -134,9 +117,9 @@ response = litellm.completion( ) ``` -## Using with Azure Anthropic +#### Azure Anthropic Example -```python +```python showLineNumbers title="Azure Anthropic Tool Search" import litellm response = litellm.completion( @@ -170,9 +153,9 @@ response = litellm.completion( ) ``` -## Using with Vertex AI +#### Vertex AI Example -```python +```python showLineNumbers title="Vertex AI Tool Search" import litellm response = litellm.completion( @@ -192,11 +175,9 @@ response = litellm.completion( ) ``` -## Streaming Support +#### Streaming Support -Tool search works with streaming: - -```python +```python showLineNumbers title="Streaming with Tool Search" import litellm response = litellm.completion( @@ -233,13 +214,13 @@ for chunk in response: print(chunk.choices[0].delta.content, end="") ``` -## LiteLLM Proxy +### AI Gateway Usage -Tool search works automatically through the LiteLLM proxy: +Tool search works automatically through the LiteLLM proxy. -### Proxy Config +#### Proxy Configuration -```yaml +```yaml showLineNumbers title="config.yaml" model_list: - model_name: claude-sonnet litellm_params: @@ -247,18 +228,19 @@ model_list: api_key: os.environ/ANTHROPIC_API_KEY ``` -### Client Request +#### Client Request -```python -import openai +```python showLineNumbers title="Client Request via Proxy" +from anthropic import Anthropic -client = openai.OpenAI( +client = Anthropic( api_key="your-litellm-proxy-key", base_url="http://0.0.0.0:4000" ) -response = client.chat.completions.create( +response = client.messages.create( model="claude-sonnet", + max_tokens=1024, messages=[ {"role": "user", "content": "What's the weather?"} ], @@ -268,17 +250,14 @@ response = client.chat.completions.create( "name": "tool_search_tool_regex" }, { - "type": "function", - "function": { - "name": "get_weather", - "description": "Get weather information", - "parameters": { - "type": "object", - "properties": { - "location": {"type": "string"} - }, - "required": ["location"] - } + "name": "get_weather", + "description": "Get weather information", + "input_schema": { + "type": "object", + "properties": { + "location": {"type": "string"} + }, + "required": ["location"] }, "defer_loading": True } @@ -286,127 +265,278 @@ response = client.chat.completions.create( ) ``` -## Important Notes +--- -### Beta Header +## Messages API -LiteLLM automatically detects tool search tools and adds the appropriate beta header based on your provider: +The Messages API provides native Anthropic-style tool search support via the `litellm.anthropic.messages` interface. -- **Anthropic API & Microsoft Foundry**: `advanced-tool-use-2025-11-20` -- **Google Cloud Vertex AI**: `tool-search-tool-2025-10-19` -- **Amazon Bedrock** (Invoke API, Opus 4.5 only): `tool-search-tool-2025-10-19` +### SDK Usage -You don't need to manually specify beta headers—LiteLLM handles this automatically. +#### Basic Example -### Deferred Loading +```python showLineNumbers title="Messages API - Basic Tool Search" +import litellm -- Tools with `defer_loading: true` are only loaded when Claude discovers them via search -- At least one tool must be non-deferred (the tool search tool itself) -- Keep your 3-5 most frequently used tools as non-deferred for optimal performance - -### Tool Descriptions - -Write clear, descriptive tool names and descriptions that match how users describe tasks. The search algorithm uses: -- Tool names -- Tool descriptions -- Argument names -- Argument descriptions - -### Usage Tracking - -Tool search requests are tracked in the usage object: - -```python -response = litellm.completion( - model="anthropic/claude-sonnet-4-5-20250929", - messages=[{"role": "user", "content": "Search for tools"}], - tools=[...] +response = await litellm.anthropic.messages.acreate( + model="anthropic/claude-sonnet-4-20250514", + messages=[ + { + "role": "user", + "content": "What's the weather in San Francisco?" + } + ], + tools=[ + { + "type": "tool_search_tool_regex_20251119", + "name": "tool_search_tool_regex" + }, + { + "name": "get_weather", + "description": "Get the current weather for a location", + "input_schema": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The city and state, e.g. San Francisco, CA" + } + }, + "required": ["location"] + }, + "defer_loading": True + } + ], + max_tokens=1024, + extra_headers={"anthropic-beta": "advanced-tool-use-2025-11-20"} ) -# Check tool search usage -if response.usage.server_tool_use: - print(f"Tool search requests: {response.usage.server_tool_use.tool_search_requests}") +print(response) ``` -## Error Handling +#### Azure Anthropic Messages Example -### All Tools Deferred +```python showLineNumbers title="Azure Anthropic Messages API" +import litellm -```python -# ❌ This will fail - at least one tool must be non-deferred -tools = [ - { - "type": "function", - "function": {...}, - "defer_loading": True - } -] - -# ✅ Correct - tool search tool is non-deferred -tools = [ - { - "type": "tool_search_tool_regex_20251119", - "name": "tool_search_tool_regex" - }, - { - "type": "function", - "function": {...}, - "defer_loading": True - } -] +response = await litellm.anthropic.messages.acreate( + model="azure_anthropic/claude-sonnet-4-20250514", + messages=[ + { + "role": "user", + "content": "What's the stock price of Apple?" + } + ], + tools=[ + { + "type": "tool_search_tool_regex_20251119", + "name": "tool_search_tool_regex" + }, + { + "name": "get_stock_price", + "description": "Get the current stock price for a ticker symbol", + "input_schema": { + "type": "object", + "properties": { + "ticker": { + "type": "string", + "description": "The stock ticker symbol, e.g. AAPL" + } + }, + "required": ["ticker"] + }, + "defer_loading": True + } + ], + max_tokens=1024, + extra_headers={"anthropic-beta": "advanced-tool-use-2025-11-20"} +) ``` -### Missing Tool Definition +#### Vertex AI Messages Example -If Claude references a tool that isn't in your deferred tools list, you'll get an error. Make sure all tools that might be discovered are included in the tools parameter with `defer_loading: true`. +```python showLineNumbers title="Vertex AI Messages API" +import litellm -## Best Practices +response = await litellm.anthropic.messages.acreate( + model="vertex_ai/claude-sonnet-4@20250514", + messages=[ + { + "role": "user", + "content": "Search the web for information about AI" + } + ], + tools=[ + { + "type": "tool_search_tool_bm25_20251119", + "name": "tool_search_tool_bm25" + }, + { + "name": "search_web", + "description": "Search the web for information", + "input_schema": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "The search query" + } + }, + "required": ["query"] + }, + "defer_loading": True + } + ], + max_tokens=1024, + extra_headers={"anthropic-beta": "tool-search-tool-2025-10-19"} +) +``` -1. **Keep frequently used tools non-deferred**: Your 3-5 most common tools should not have `defer_loading: true` +#### Bedrock Messages Example -2. **Use semantic descriptions**: Tool descriptions should use natural language that matches user queries +```python showLineNumbers title="Bedrock Messages API (Invoke)" +import litellm -3. **Choose the right variant**: - - Use **regex** for exact pattern matching (faster) - - Use **BM25** for natural language semantic search +response = await litellm.anthropic.messages.acreate( + model="bedrock/invoke/anthropic.claude-opus-4-20250514-v1:0", + messages=[ + { + "role": "user", + "content": "What's the weather?" + } + ], + tools=[ + { + "type": "tool_search_tool_regex_20251119", + "name": "tool_search_tool_regex" + }, + { + "name": "get_weather", + "description": "Get weather information", + "input_schema": { + "type": "object", + "properties": { + "location": {"type": "string"} + }, + "required": ["location"] + }, + "defer_loading": True + } + ], + max_tokens=1024, + extra_headers={"anthropic-beta": "tool-search-tool-2025-10-19"} +) +``` -4. **Monitor usage**: Track `tool_search_requests` in the usage object to understand search patterns +#### Streaming Support -5. **Optimize tool catalog**: Remove unused tools and consolidate similar functionality +```python showLineNumbers title="Messages API - Streaming" +import litellm +import json -## When to Use Tool Search +response = await litellm.anthropic.messages.acreate( + model="anthropic/claude-sonnet-4-20250514", + messages=[ + { + "role": "user", + "content": "What's the weather in Tokyo?" + } + ], + tools=[ + { + "type": "tool_search_tool_regex_20251119", + "name": "tool_search_tool_regex" + }, + { + "name": "get_weather", + "description": "Get weather information", + "input_schema": { + "type": "object", + "properties": { + "location": {"type": "string"} + }, + "required": ["location"] + }, + "defer_loading": True + } + ], + max_tokens=1024, + stream=True, + extra_headers={"anthropic-beta": "advanced-tool-use-2025-11-20"} +) -**Good use cases:** -- 10+ tools available in your system -- Tool definitions consuming >10K tokens -- Experiencing tool selection accuracy issues -- Building systems with multiple tool categories -- Tool library growing over time +async for chunk in response: + if isinstance(chunk, bytes): + chunk_str = chunk.decode("utf-8") + for line in chunk_str.split("\n"): + if line.startswith("data: "): + try: + json_data = json.loads(line[6:]) + print(json_data) + except json.JSONDecodeError: + pass +``` -**When traditional tool calling is better:** -- Less than 10 tools total -- All tools are frequently used -- Very small tool definitions (\<100 tokens total) +### AI Gateway Usage -## Limitations +Configure the proxy to use Messages API endpoints. -- Not compatible with tool use examples -- Requires Claude Opus 4.5 or Sonnet 4.5 -- On Bedrock, only available via invoke API (not converse API) -- On Bedrock, only supported for Claude Opus 4.5 (not Sonnet 4.5) -- BM25 variant (`tool_search_tool_bm25_20251119`) is not supported on Bedrock -- Maximum 10,000 tools in catalog -- Returns 3-5 most relevant tools per search +#### Proxy Configuration -### Bedrock-Specific Notes +```yaml showLineNumbers title="config.yaml" +model_list: + - model_name: claude-sonnet-messages + litellm_params: + model: anthropic/claude-sonnet-4-20250514 + api_key: os.environ/ANTHROPIC_API_KEY +``` -When using Bedrock's Invoke API: -- The regex variant (`tool_search_tool_regex_20251119`) is automatically normalized to `tool_search_tool_regex` -- The BM25 variant (`tool_search_tool_bm25_20251119`) is automatically filtered out as it's not supported -- Tool search is only available for Claude Opus 4.5 models +#### Client Request + +```python showLineNumbers title="Client Request via Proxy (Messages API)" +from anthropic import Anthropic + +client = Anthropic( + api_key="your-litellm-proxy-key", + base_url="http://0.0.0.0:4000" +) + +response = client.messages.create( + model="claude-sonnet-messages", + max_tokens=1024, + messages=[ + { + "role": "user", + "content": "What's the weather?" + } + ], + tools=[ + { + "type": "tool_search_tool_regex_20251119", + "name": "tool_search_tool_regex" + }, + { + "name": "get_weather", + "description": "Get weather information", + "input_schema": { + "type": "object", + "properties": { + "location": {"type": "string"} + }, + "required": ["location"] + }, + "defer_loading": True + } + ], + extra_headers={"anthropic-beta": "advanced-tool-use-2025-11-20"} +) + +print(response) +``` + +--- ## Additional Resources - [Anthropic Tool Search Documentation](https://docs.anthropic.com/en/docs/build-with-claude/tool-use/tool-search) - [LiteLLM Tool Calling Guide](https://docs.litellm.ai/docs/completion/function_call) - diff --git a/docs/my-website/docs/providers/azure_ai/azure_model_router.md b/docs/my-website/docs/providers/azure_ai/azure_model_router.md index 5e14c7283f6..16bc1afb70e 100644 --- a/docs/my-website/docs/providers/azure_ai/azure_model_router.md +++ b/docs/my-website/docs/providers/azure_ai/azure_model_router.md @@ -5,19 +5,38 @@ Azure Model Router is a feature in Azure AI Foundry that automatically routes yo ## Key Features - **Automatic Model Selection**: Azure Model Router dynamically selects the best model for your request -- **Cost Tracking**: LiteLLM automatically tracks costs based on the actual model used (e.g., `gpt-4.1-nano`), not the router endpoint +- **Cost Tracking**: LiteLLM automatically tracks costs based on the actual model used (e.g., `gpt-4.1-nano`), plus the Model Router infrastructure fee - **Streaming Support**: Full support for streaming responses with accurate cost calculation +- **Simple Configuration**: Easy to set up via UI or config file + +## Model Naming Pattern + +Use the pattern: `azure_ai/model_router/` + +**Components:** +- `azure_ai` - The provider identifier +- `model_router` - Indicates this is a Model Router deployment +- `` - Your actual deployment name from Azure AI Foundry (e.g., `azure-model-router`) + +**Example:** `azure_ai/model_router/azure-model-router` + +**How it works:** +- LiteLLM automatically strips the `model_router/` prefix when sending requests to Azure +- Only your deployment name (e.g., `azure-model-router`) is sent to the Azure API +- The full path is preserved in responses and logs for proper cost tracking ## LiteLLM Python SDK ### Basic Usage +Use the pattern `azure_ai/model_router/` where `` is your Azure deployment name: + ```python import litellm import os response = litellm.completion( - model="azure_ai/azure-model-router", + model="azure_ai/model_router/azure-model-router", # Use your deployment name messages=[{"role": "user", "content": "Hello!"}], api_base="https://your-endpoint.cognitiveservices.azure.com/openai/v1/", api_key=os.getenv("AZURE_MODEL_ROUTER_API_KEY"), @@ -26,6 +45,13 @@ response = litellm.completion( print(response) ``` +**Pattern Explanation:** +- `azure_ai` - The provider +- `model_router` - Indicates this is a model router deployment +- `azure-model-router` - Your actual deployment name from Azure AI Foundry + +LiteLLM will automatically strip the `model_router/` prefix when sending the request to Azure, so only `azure-model-router` is sent to the API. + ### Streaming with Usage Tracking ```python @@ -33,7 +59,7 @@ import litellm import os response = await litellm.acompletion( - model="azure_ai/azure-model-router", + model="azure_ai/model_router/azure-model-router", # Use your deployment name messages=[{"role": "user", "content": "hi"}], api_base="https://your-endpoint.cognitiveservices.azure.com/openai/v1/", api_key=os.getenv("AZURE_MODEL_ROUTER_API_KEY"), @@ -51,13 +77,15 @@ async for chunk in response: ```yaml model_list: - - model_name: azure-model-router + - model_name: azure-model-router # Public name for your users litellm_params: - model: azure_ai/azure-model-router + model: azure_ai/model_router/azure-model-router # Use your deployment name api_base: https://your-endpoint.cognitiveservices.azure.com/openai/v1/ api_key: os.environ/AZURE_MODEL_ROUTER_API_KEY ``` +**Note:** Replace `azure-model-router` in the model path with your actual deployment name from Azure AI Foundry. + ### Start Proxy ```bash @@ -80,49 +108,42 @@ curl -X POST http://localhost:4000/chat/completions \ This walkthrough shows how to add an Azure Model Router endpoint to LiteLLM using the Admin Dashboard. -### Select Provider +### Quick Start + +1. Navigate to the **Models** page in the LiteLLM UI +2. Select **"Azure AI Foundry (Studio)"** as the provider +3. Enter your deployment name (e.g., `azure-model-router`) +4. LiteLLM will automatically format it as `azure_ai/model_router/azure-model-router` +5. Add your API base URL and API key +6. Test and save + +### Detailed Walkthrough + +#### Step 1: Select Provider Navigate to the Models page and select "Azure AI Foundry (Studio)" as the provider. -#### Navigate to Models Page +##### Navigate to Models Page ![Navigate to Models](./img/azure_model_router_01.jpeg) -#### Click Provider Dropdown +##### Click Provider Dropdown ![Click Provider](./img/azure_model_router_02.jpeg) -#### Choose Azure AI Foundry +##### Choose Azure AI Foundry ![Select Azure AI Foundry](./img/azure_model_router_03.jpeg) -### Configure Model Name +#### Step 2: Enter Deployment Name -Set up the model name by entering `azure_ai/` followed by your model router deployment name from Azure. +**New Simplified Method:** Just enter your deployment name directly in the text field. If your deployment name contains "model-router" or "model_router", LiteLLM will automatically format it as `azure_ai/model_router/`. -#### Click Model Name Field +**Example:** +- Enter: `azure-model-router` +- LiteLLM creates: `azure_ai/model_router/azure-model-router` -![Click Model Field](./img/azure_model_router_04.jpeg) - -#### Select Custom Model Name - -![Select Custom Model](./img/azure_model_router_05.jpeg) - -#### Enter LiteLLM Model Name - -![LiteLLM Model Name](./img/azure_model_router_06.jpeg) - -#### Click Custom Model Name Field - -![Enter Custom Name Field](./img/azure_model_router_07.jpeg) - -#### Type Model Prefix - -Type `azure_ai/` as the prefix. - -![Type azure_ai prefix](./img/azure_model_router_08.jpeg) - -#### Copy Model Name from Azure Portal +##### Copy Deployment Name from Azure Portal Switch to Azure AI Foundry and copy your model router deployment name. @@ -130,73 +151,79 @@ Switch to Azure AI Foundry and copy your model router deployment name. ![Copy Model Name](./img/azure_model_router_10.jpeg) -#### Paste Model Name +##### Enter Deployment Name in LiteLLM -Paste to get `azure_ai/azure-model-router`. +Paste your deployment name (e.g., `azure-model-router`) directly into the text field. -![Paste Model Name](./img/azure_model_router_11.jpeg) +![Enter Deployment Name](./img/azure_model_router_04.jpeg) -### Configure API Base and Key +**What happens behind the scenes:** +- You enter: `azure-model-router` +- LiteLLM automatically detects this is a model router deployment +- The full model path becomes: `azure_ai/model_router/azure-model-router` +- When making API calls, only `azure-model-router` is sent to Azure + +#### Step 3: Configure API Base and Key Copy the endpoint URL and API key from Azure portal. -#### Copy API Base URL from Azure +##### Copy API Base URL from Azure ![Copy API Base](./img/azure_model_router_12.jpeg) -#### Enter API Base in LiteLLM +##### Enter API Base in LiteLLM ![Click API Base Field](./img/azure_model_router_13.jpeg) ![Paste API Base](./img/azure_model_router_14.jpeg) -#### Copy API Key from Azure +##### Copy API Key from Azure ![Copy API Key](./img/azure_model_router_15.jpeg) -#### Enter API Key in LiteLLM +##### Enter API Key in LiteLLM ![Enter API Key](./img/azure_model_router_16.jpeg) -### Test and Add Model +#### Step 4: Test and Add Model Verify your configuration works and save the model. -#### Test Connection +##### Test Connection ![Test Connection](./img/azure_model_router_17.jpeg) -#### Close Test Dialog +##### Close Test Dialog ![Close Dialog](./img/azure_model_router_18.jpeg) -#### Add Model +##### Add Model ![Add Model](./img/azure_model_router_19.jpeg) -### Verify in Playground +#### Step 5: Verify in Playground Test your model and verify cost tracking is working. -#### Open Playground +##### Open Playground ![Go to Playground](./img/azure_model_router_20.jpeg) -#### Select Model +##### Select Model ![Select Model](./img/azure_model_router_21.jpeg) -#### Send Test Message +##### Send Test Message ![Send Message](./img/azure_model_router_22.jpeg) -#### View Logs +##### View Logs ![View Logs](./img/azure_model_router_23.jpeg) -#### Verify Cost Tracking +##### Verify Cost Tracking -Cost is tracked based on the actual model used (e.g., `gpt-4.1-nano`). +Cost is tracked based on the actual model used (e.g., `gpt-4.1-nano`), plus a flat infrastructure cost of $0.14 per million input tokens for using the Model Router. ![Verify Cost](./img/azure_model_router_24.jpeg) @@ -205,28 +232,50 @@ Cost is tracked based on the actual model used (e.g., `gpt-4.1-nano`). LiteLLM automatically handles cost tracking for Azure Model Router by: 1. **Detecting the actual model**: When Azure Model Router routes your request to a specific model (e.g., `gpt-4.1-nano-2025-04-14`), LiteLLM extracts this from the response -2. **Calculating accurate costs**: Costs are calculated based on the actual model used, not the router endpoint name +2. **Calculating accurate costs**: Costs are calculated based on: + - The actual model used (e.g., `gpt-4.1-nano` token costs) + - Plus a flat infrastructure cost of **$0.14 per million input tokens** for using the Model Router 3. **Streaming support**: Cost tracking works correctly for both streaming and non-streaming requests +### Cost Breakdown + +When you use Azure Model Router, the total cost includes: + +- **Model Cost**: Based on the actual model that handled your request (e.g., `gpt-4.1-nano`) +- **Router Flat Cost**: $0.14 per million input tokens (Azure AI Foundry infrastructure fee) + ### Example Response with Cost ```python import litellm response = litellm.completion( - model="azure_ai/azure-model-router", + model="azure_ai/model_router/azure-model-router", messages=[{"role": "user", "content": "Hello!"}], api_base="https://your-endpoint.cognitiveservices.azure.com/openai/v1/", api_key="your-api-key", ) # The response will show the actual model used -print(f"Model used: {response.model}") # e.g., "gpt-4.1-nano-2025-04-14" +print(f"Model used: {response.model}") # e.g., "azure_ai/gpt-4.1-nano-2025-04-14" -# Get cost +# Get cost (includes both model cost and router flat cost) from litellm import completion_cost cost = completion_cost(completion_response=response) -print(f"Cost: ${cost}") +print(f"Total cost: ${cost}") + +# Access detailed cost breakdown +if hasattr(response, '_hidden_params') and 'response_cost' in response._hidden_params: + print(f"Response cost: ${response._hidden_params['response_cost']}") ``` +### Viewing Cost Breakdown in UI + +When viewing logs in the LiteLLM UI, you'll see: +- **Model Cost**: The cost for the actual model used +- **Azure Model Router Flat Cost**: The $0.14/M input tokens infrastructure fee +- **Total Cost**: Sum of both costs + +This breakdown helps you understand exactly what you're paying for when using the Model Router. + diff --git a/docs/my-website/docs/providers/bedrock.md b/docs/my-website/docs/providers/bedrock.md index 487212ad655..e546ed97656 100644 --- a/docs/my-website/docs/providers/bedrock.md +++ b/docs/my-website/docs/providers/bedrock.md @@ -9,7 +9,7 @@ ALL Bedrock models (Anthropic, Meta, Deepseek, Mistral, Amazon, etc.) are Suppor | Description | Amazon Bedrock is a fully managed service that offers a choice of high-performing foundation models (FMs). | | Provider Route on LiteLLM | `bedrock/`, [`bedrock/converse/`](#set-converse--invoke-route), [`bedrock/invoke/`](#set-invoke-route), [`bedrock/converse_like/`](#calling-via-internal-proxy), [`bedrock/llama/`](#deepseek-not-r1), [`bedrock/deepseek_r1/`](#deepseek-r1), [`bedrock/qwen3/`](#qwen3-imported-models), [`bedrock/qwen2/`](./bedrock_imported.md#qwen2-imported-models), [`bedrock/openai/`](./bedrock_imported.md#openai-compatible-imported-models-qwen-25-vl-etc), [`bedrock/moonshot`](./bedrock_imported.md#moonshot-kimi-k2-thinking) | | Provider Doc | [Amazon Bedrock ↗](https://docs.aws.amazon.com/bedrock/latest/userguide/what-is-bedrock.html) | -| Supported OpenAI Endpoints | `/chat/completions`, `/completions`, `/embeddings`, `/images/generations` | +| Supported OpenAI Endpoints | `/chat/completions`, `/completions`, `/embeddings`, `/images/generations`, `/v1/realtime`| | Rerank Endpoint | `/rerank` | | Pass-through Endpoint | [Supported](../pass_through/bedrock.md) | diff --git a/docs/my-website/docs/providers/bedrock_realtime_with_audio.md b/docs/my-website/docs/providers/bedrock_realtime_with_audio.md new file mode 100644 index 00000000000..a2d9813ffd9 --- /dev/null +++ b/docs/my-website/docs/providers/bedrock_realtime_with_audio.md @@ -0,0 +1,362 @@ +# Bedrock Realtime API + +## Overview + +Amazon Bedrock's Nova Sonic model supports real-time bidirectional audio streaming for voice conversations. This tutorial shows how to use it through LiteLLM Proxy. + +## Setup + +### 1. Configure LiteLLM Proxy + +Create a `config.yaml` file: + +```yaml +model_list: + - model_name: "bedrock-sonic" + litellm_params: + model: bedrock/amazon.nova-sonic-v1:0 + aws_region_name: us-east-1 # or your preferred region + model_info: + mode: realtime +``` + +### 2. Start LiteLLM Proxy + +```bash +litellm --config config.yaml +``` + +## Basic Text Interaction + +```python +import asyncio +import websockets +import json + +LITELLM_API_KEY = "sk-1234" # Your LiteLLM API key +LITELLM_URL = 'ws://localhost:4000/v1/realtime?model=bedrock-sonic' + +async def test_text_conversation(): + async with websockets.connect( + LITELLM_URL, + additional_headers={ + "Authorization": f"Bearer {LITELLM_API_KEY}" + } + ) as ws: + # Wait for session.created + response = await ws.recv() + print(f"Connected: {json.loads(response)['type']}") + + # Configure session + session_update = { + "type": "session.update", + "session": { + "instructions": "You are a helpful assistant.", + "modalities": ["text"], + "temperature": 0.8 + } + } + await ws.send(json.dumps(session_update)) + + # Send a message + message = { + "type": "conversation.item.create", + "item": { + "type": "message", + "role": "user", + "content": [{"type": "input_text", "text": "Hello!"}] + } + } + await ws.send(json.dumps(message)) + + # Trigger response + await ws.send(json.dumps({"type": "response.create"})) + + # Listen for response + while True: + response = await ws.recv() + event = json.loads(response) + + if event['type'] == 'response.text.delta': + print(event['delta'], end='', flush=True) + elif event['type'] == 'response.done': + print("\n✓ Complete") + break + +if __name__ == "__main__": + asyncio.run(test_text_conversation()) +``` + +## Audio Streaming with Voice Conversation + +```python +import asyncio +import websockets +import json +import base64 +import pyaudio + +LITELLM_API_KEY = "sk-1234" +LITELLM_URL = 'ws://localhost:4000/v1/realtime?model=bedrock-sonic' + +# Audio configuration +INPUT_RATE = 16000 # Nova Sonic expects 16kHz input +OUTPUT_RATE = 24000 # Nova Sonic outputs 24kHz +CHUNK = 1024 + +async def audio_conversation(): + # Initialize PyAudio + p = pyaudio.PyAudio() + + # Input stream (microphone) + input_stream = p.open( + format=pyaudio.paInt16, + channels=1, + rate=INPUT_RATE, + input=True, + frames_per_buffer=CHUNK + ) + + # Output stream (speakers) + output_stream = p.open( + format=pyaudio.paInt16, + channels=1, + rate=OUTPUT_RATE, + output=True, + frames_per_buffer=CHUNK + ) + + async with websockets.connect( + LITELLM_URL, + additional_headers={"Authorization": f"Bearer {LITELLM_API_KEY}"} + ) as ws: + # Wait for session.created + await ws.recv() + print("✓ Connected") + + # Configure session with audio + session_update = { + "type": "session.update", + "session": { + "instructions": "You are a friendly voice assistant.", + "modalities": ["text", "audio"], + "voice": "matthew", + "input_audio_format": "pcm16", + "output_audio_format": "pcm16" + } + } + await ws.send(json.dumps(session_update)) + print("🎤 Speak into your microphone...") + + async def send_audio(): + """Capture and send audio from microphone""" + while True: + audio_data = input_stream.read(CHUNK, exception_on_overflow=False) + audio_b64 = base64.b64encode(audio_data).decode('utf-8') + await ws.send(json.dumps({ + "type": "input_audio_buffer.append", + "audio": audio_b64 + })) + await asyncio.sleep(0.01) + + async def receive_audio(): + """Receive and play audio responses""" + while True: + response = await ws.recv() + event = json.loads(response) + + if event['type'] == 'response.audio.delta': + audio_b64 = event.get('delta', '') + if audio_b64: + audio_bytes = base64.b64decode(audio_b64) + output_stream.write(audio_bytes) + + elif event['type'] == 'response.text.delta': + print(event['delta'], end='', flush=True) + + elif event['type'] == 'response.done': + print("\n✓ Response complete") + + # Run both tasks concurrently + await asyncio.gather(send_audio(), receive_audio()) + +if __name__ == "__main__": + try: + asyncio.run(audio_conversation()) + except KeyboardInterrupt: + print("\n\nGoodbye!") +``` + +## Using Tools/Function Calling + +```python +import asyncio +import websockets +import json +from datetime import datetime + +LITELLM_API_KEY = "sk-1234" +LITELLM_URL = 'ws://localhost:4000/v1/realtime?model=bedrock-sonic' + +# Define tools +TOOLS = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get current weather for a location", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "City name" + } + }, + "required": ["location"] + } + } + } +] + +def get_weather(location: str) -> dict: + """Simulated weather function""" + return { + "location": location, + "temperature": 72, + "conditions": "sunny" + } + +async def conversation_with_tools(): + async with websockets.connect( + LITELLM_URL, + additional_headers={"Authorization": f"Bearer {LITELLM_API_KEY}"} + ) as ws: + # Wait for session.created + await ws.recv() + + # Configure session with tools + session_update = { + "type": "session.update", + "session": { + "instructions": "You are a helpful assistant with access to tools.", + "modalities": ["text"], + "tools": TOOLS + } + } + await ws.send(json.dumps(session_update)) + + # Send a message that requires a tool + message = { + "type": "conversation.item.create", + "item": { + "type": "message", + "role": "user", + "content": [{"type": "input_text", "text": "What's the weather in San Francisco?"}] + } + } + await ws.send(json.dumps(message)) + await ws.send(json.dumps({"type": "response.create"})) + + # Handle responses and tool calls + while True: + response = await ws.recv() + event = json.loads(response) + + if event['type'] == 'response.text.delta': + print(event['delta'], end='', flush=True) + + elif event['type'] == 'response.function_call_arguments.done': + # Execute the tool + function_name = event['name'] + arguments = json.loads(event['arguments']) + + print(f"\n🔧 Calling {function_name}({arguments})") + result = get_weather(**arguments) + + # Send tool result back + tool_result = { + "type": "conversation.item.create", + "item": { + "type": "function_call_output", + "call_id": event['call_id'], + "output": json.dumps(result) + } + } + await ws.send(json.dumps(tool_result)) + await ws.send(json.dumps({"type": "response.create"})) + + elif event['type'] == 'response.done': + print("\n✓ Complete") + break + +if __name__ == "__main__": + asyncio.run(conversation_with_tools()) +``` + +## Configuration Options + +### Voice Options +Available voices: `matthew`, `joanna`, `ruth`, `stephen`, `gregory`, `amy` + +### Audio Formats +- **Input**: 16kHz PCM16 (mono) +- **Output**: 24kHz PCM16 (mono) + +### Modalities +- `["text"]` - Text only +- `["audio"]` - Audio only +- `["text", "audio"]` - Both text and audio + +## Example Test Scripts + +Complete working examples are available in the LiteLLM repository: + +- **Basic audio streaming**: `test_bedrock_realtime_client.py` +- **Simple text test**: `test_bedrock_realtime_simple.py` +- **Tool calling**: `test_bedrock_realtime_tools.py` + +## Requirements + +```bash +pip install litellm websockets pyaudio +``` + +## AWS Configuration + +Ensure your AWS credentials are configured: + +```bash +export AWS_ACCESS_KEY_ID=your_access_key +export AWS_SECRET_ACCESS_KEY=your_secret_key +export AWS_REGION_NAME=us-east-1 +``` + +Or use AWS CLI configuration: + +```bash +aws configure +``` + +## Troubleshooting + +### Connection Issues +- Ensure LiteLLM proxy is running on the correct port +- Verify AWS credentials are properly configured +- Check that the Bedrock model is available in your region + +### Audio Issues +- Verify PyAudio is properly installed +- Check microphone/speaker permissions +- Ensure correct sample rates (16kHz input, 24kHz output) + +### Tool Calling Issues +- Ensure tools are properly defined in session.update +- Verify tool results are sent back with correct call_id +- Check that response.create is sent after tool result + +## Related Resources + +- [OpenAI Realtime API Documentation](https://platform.openai.com/docs/guides/realtime) +- [Amazon Bedrock Nova Sonic Documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/nova-sonic.html) +- [LiteLLM Realtime API Documentation](/docs/realtime) diff --git a/docs/my-website/docs/providers/gemini.md b/docs/my-website/docs/providers/gemini.md index 23a02f7365c..b9ad7820dd4 100644 --- a/docs/my-website/docs/providers/gemini.md +++ b/docs/my-website/docs/providers/gemini.md @@ -1840,6 +1840,57 @@ content = response.get('choices', [{}])[0].get('message', {}).get('content') print(content) ``` +## gemini-robotics-er-1.5-preview Usage + +```python +from litellm import api_base +from openai import OpenAI +import os +import base64 + +client = OpenAI(base_url="http://0.0.0.0:4000", api_key="sk-12345") +base64_image = base64.b64encode(open("closeup-object-on-table-many-260nw-1216144471.webp", "rb").read()).decode() + +import json +import re +tools = [{"codeExecution": {}}] +response = client.chat.completions.create( + model="gemini/gemini-robotics-er-1.5-preview", + messages=[ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "Point to no more than 10 items in the image. The label returned should be an identifying name for the object detected. The answer should follow the json format: [{\"point\": [y, x], \"label\": }, ...]. The points are in [y, x] format normalized to 0-1000." + }, + { + "type": "image_url", + "image_url": {"url": f"data:image/jpeg;base64,{base64_image}"} + } + ] + } + ], + tools=tools +) + +# Extract JSON from markdown code block if present +content = response.choices[0].message.content +# Look for triple-backtick JSON block +match = re.search(r'```json\s*(.*?)\s*```', content, re.DOTALL) +if match: + json_str = match.group(1) +else: + json_str = content + +try: + data = json.loads(json_str) + print(json.dumps(data, indent=2)) +except Exception as e: + print("Error parsing response as JSON:", e) + print("Response content:", content) +``` + ## Usage - PDF / Videos / etc. Files ### Inline Data (e.g. audio stream) diff --git a/docs/my-website/docs/providers/sarvam.md b/docs/my-website/docs/providers/sarvam.md new file mode 100644 index 00000000000..6a292456781 --- /dev/null +++ b/docs/my-website/docs/providers/sarvam.md @@ -0,0 +1,92 @@ +# Sarvam.ai + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +LiteLLM supports all the text models from [Sarvam ai](https://docs.sarvam.ai/api-reference-docs/chat/chat-completions) + +## Usage + +```python +import os +from litellm import completion + +# Set your Sarvam API key +os.environ["SARVAM_API_KEY"] = "" + +messages = [{"role": "user", "content": "Hello"}] + +response = completion( + model="sarvam/sarvam-m", + messages=messages, +) +print(response) +``` + +## Usage with LiteLLM Proxy Server + +Here's how to call a Sarvam.ai model with the LiteLLM Proxy Server + +1. **Modify the `config.yaml`:** + + ```yaml + model_list: + - model_name: my-model + litellm_params: + model: sarvam/ # add sarvam/ prefix to route as Sarvam provider + api_key: api-key # api key to send your model + ``` + +2. **Start the proxy:** + + ```bash + $ litellm --config /path/to/config.yaml + ``` + +3. **Send a request to LiteLLM Proxy Server:** + + + + + + ```python + import openai + + client = openai.OpenAI( + api_key="sk-1234", # pass litellm proxy key, if you're using virtual keys + base_url="http://0.0.0.0:4000" # litellm-proxy-base url + ) + + response = client.chat.completions.create( + model="my-model", + messages=[ + { + "role": "user", + "content": "what llm are you" + } + ], + ) + + print(response) + ``` + + + + + ```shell + curl --location 'http://0.0.0.0:4000/chat/completions' \ + --header 'Authorization: Bearer sk-1234' \ + --header 'Content-Type: application/json' \ + --data '{ + "model": "my-model", + "messages": [ + { + "role": "user", + "content": "what llm are you" + } + ] + }' + ``` + + + diff --git a/docs/my-website/docs/providers/vercel_ai_gateway.md b/docs/my-website/docs/providers/vercel_ai_gateway.md index 91f0a18ea1c..3ff007171ed 100644 --- a/docs/my-website/docs/providers/vercel_ai_gateway.md +++ b/docs/my-website/docs/providers/vercel_ai_gateway.md @@ -11,7 +11,7 @@ import TabItem from '@theme/TabItem'; | Provider Route on LiteLLM | `vercel_ai_gateway/` | | Link to Provider Doc | [Vercel AI Gateway Documentation ↗](https://vercel.com/docs/ai-gateway) | | Base URL | `https://ai-gateway.vercel.sh/v1` | -| Supported Operations | `/chat/completions`, `/models` | +| Supported Operations | `/chat/completions`, `/embeddings`, `/models` |

@@ -73,7 +73,7 @@ messages = [{"content": "Hello, how are you?", "role": "user"}] # Vercel AI Gateway call with streaming response = completion( - model="vercel_ai_gateway/openai/gpt-4o", + model="vercel_ai_gateway/openai/gpt-4o", messages=messages, stream=True ) @@ -82,6 +82,33 @@ for chunk in response: print(chunk) ``` +### Embeddings + +```python showLineNumbers title="Vercel AI Gateway Embeddings" +import os +from litellm import embedding + +os.environ["VERCEL_AI_GATEWAY_API_KEY"] = "your-api-key" + +# Vercel AI Gateway embedding call +response = embedding( + model="vercel_ai_gateway/openai/text-embedding-3-small", + input="Hello world" +) + +print(response.data[0]["embedding"][:5]) # Print first 5 dimensions +``` + +You can also specify the `dimensions` parameter: + +```python showLineNumbers title="Vercel AI Gateway Embeddings with Dimensions" +response = embedding( + model="vercel_ai_gateway/openai/text-embedding-3-small", + input=["Hello world", "Goodbye world"], + dimensions=768 +) +``` + ## Usage - LiteLLM Proxy Add the following to your LiteLLM Proxy configuration file: @@ -97,6 +124,11 @@ model_list: litellm_params: model: vercel_ai_gateway/anthropic/claude-4-sonnet api_key: os.environ/VERCEL_AI_GATEWAY_API_KEY + + - model_name: text-embedding-3-small-gateway + litellm_params: + model: vercel_ai_gateway/openai/text-embedding-3-small + api_key: os.environ/VERCEL_AI_GATEWAY_API_KEY ``` Start your LiteLLM Proxy server: diff --git a/docs/my-website/docs/providers/vertex_speech.md b/docs/my-website/docs/providers/vertex_speech.md index d0acacb5aec..751782a323c 100644 --- a/docs/my-website/docs/providers/vertex_speech.md +++ b/docs/my-website/docs/providers/vertex_speech.md @@ -312,6 +312,7 @@ Gemini models with audio output capabilities using the chat completions API. - Only supports `pcm16` audio format - Streaming not yet supported - Must set `modalities: ["audio"]` +- When using via LiteLLM Proxy, must include `"allowed_openai_params": ["audio", "modalities"]` in the request body to enable audio parameters ::: ### Quick Start @@ -372,7 +373,8 @@ curl http://0.0.0.0:4000/v1/chat/completions \ "model": "gemini-tts", "messages": [{"role": "user", "content": "Say hello in a friendly voice"}], "modalities": ["audio"], - "audio": {"voice": "Kore", "format": "pcm16"} + "audio": {"voice": "Kore", "format": "pcm16"}, + "allowed_openai_params": ["audio", "modalities"] }' ``` @@ -389,6 +391,7 @@ response = client.chat.completions.create( messages=[{"role": "user", "content": "Say hello in a friendly voice"}], modalities=["audio"], audio={"voice": "Kore", "format": "pcm16"}, + extra_body={"allowed_openai_params": ["audio", "modalities"]} ) print(response) ``` diff --git a/docs/my-website/docs/proxy/call_hooks.md b/docs/my-website/docs/proxy/call_hooks.md index fe865f67e09..17354725fd5 100644 --- a/docs/my-website/docs/proxy/call_hooks.md +++ b/docs/my-website/docs/proxy/call_hooks.md @@ -19,6 +19,7 @@ import Image from '@theme/IdealImage'; | `async_post_call_success_hook` | Modify outgoing response (non-streaming) | After successful LLM API call, for non-streaming responses | | `async_post_call_failure_hook` | Transform error responses sent to clients | After failed LLM API call | | `async_post_call_streaming_hook` | Modify outgoing response (streaming) | After successful LLM API call, for streaming responses | +| `async_post_call_response_headers_hook` | Inject custom HTTP response headers | After LLM API call (both success and failure) | See a complete example with our [parallel request rate limiter](https://github.com/BerriAI/litellm/blob/main/litellm/proxy/hooks/parallel_request_limiter.py) @@ -115,6 +116,18 @@ class MyCustomHandler(CustomLogger): # https://docs.litellm.ai/docs/observabilit async for item in response: yield item + async def async_post_call_response_headers_hook( + self, + data: dict, + user_api_key_dict: UserAPIKeyAuth, + response: Any, + request_headers: Optional[Dict[str, str]] = None, + ) -> Optional[Dict[str, str]]: + """ + Inject custom headers into HTTP response (runs for both success and failure). + """ + return {"x-custom-header": "custom-value"} + proxy_handler_instance = MyCustomHandler() ``` @@ -389,3 +402,31 @@ proxy_handler_instance = MyErrorTransformer() ``` **Result:** Clients receive `"Your prompt is too long..."` instead of `"ContextWindowExceededError: Prompt exceeds context window"`. + +## Advanced - Inject Custom HTTP Response Headers + +Use `async_post_call_response_headers_hook` to inject custom HTTP headers into responses. This hook runs for **both successful and failed** LLM API calls. + +```python +from litellm.integrations.custom_logger import CustomLogger +from litellm.proxy.proxy_server import UserAPIKeyAuth +from typing import Any, Dict, Optional + +class CustomHeaderLogger(CustomLogger): + def __init__(self): + super().__init__() + + async def async_post_call_response_headers_hook( + self, + data: dict, + user_api_key_dict: UserAPIKeyAuth, + response: Any, + request_headers: Optional[Dict[str, str]] = None, + ) -> Optional[Dict[str, str]]: + """ + Inject custom headers into all responses (success and failure). + """ + return {"x-custom-header": "custom-value"} + +proxy_handler_instance = CustomHeaderLogger() +``` diff --git a/docs/my-website/docs/proxy/cli_sso.md b/docs/my-website/docs/proxy/cli_sso.md index cde6bf266d4..ad0f033f802 100644 --- a/docs/my-website/docs/proxy/cli_sso.md +++ b/docs/my-website/docs/proxy/cli_sso.md @@ -28,6 +28,37 @@ EXPERIMENTAL_UI_LOGIN="True" litellm --config config.yaml ::: +### Configuration + +#### JWT Token Expiration + +By default, CLI authentication tokens expire after **24 hours**. You can customize this expiration time by setting the `LITELLM_CLI_JWT_EXPIRATION_HOURS` environment variable when starting your LiteLLM Proxy: + +```bash +# Set CLI JWT tokens to expire after 48 hours +export LITELLM_CLI_JWT_EXPIRATION_HOURS=48 +export EXPERIMENTAL_UI_LOGIN="True" +litellm --config config.yaml +``` + +Or in a single command: + +```bash +LITELLM_CLI_JWT_EXPIRATION_HOURS=48 EXPERIMENTAL_UI_LOGIN="True" litellm --config config.yaml +``` + +**Examples:** +- `LITELLM_CLI_JWT_EXPIRATION_HOURS=12` - Tokens expire after 12 hours +- `LITELLM_CLI_JWT_EXPIRATION_HOURS=168` - Tokens expire after 7 days (168 hours) +- `LITELLM_CLI_JWT_EXPIRATION_HOURS=720` - Tokens expire after 30 days (720 hours) + +:::tip +You can check your current token's age and expiration status using: +```bash +litellm-proxy whoami +``` +::: + ### Steps 1. **Install the CLI** diff --git a/docs/my-website/docs/proxy/config_settings.md b/docs/my-website/docs/proxy/config_settings.md index 6d3fa206113..385b4b0de32 100644 --- a/docs/my-website/docs/proxy/config_settings.md +++ b/docs/my-website/docs/proxy/config_settings.md @@ -321,6 +321,7 @@ router_settings: | redis_host | string | The host address for the Redis server. **Only set this if you have multiple instances of LiteLLM Proxy and want current tpm/rpm tracking to be shared across them** | | redis_password | string | The password for the Redis server. **Only set this if you have multiple instances of LiteLLM Proxy and want current tpm/rpm tracking to be shared across them** | | redis_port | string | The port number for the Redis server. **Only set this if you have multiple instances of LiteLLM Proxy and want current tpm/rpm tracking to be shared across them**| +| redis_db | int | The database number for the Redis server. **Only set this if you have multiple instances of LiteLLM Proxy and want current tpm/rpm tracking to be shared across them**| | enable_pre_call_check | boolean | If true, checks if a call is within the model's context window before making the call. [More information here](reliability) | | content_policy_fallbacks | array of objects | Specifies fallback models for content policy violations. [More information here](reliability) | | fallbacks | array of objects | Specifies fallback models for all types of errors. [More information here](reliability) | @@ -452,6 +453,8 @@ router_settings: | BERRISPEND_ACCOUNT_ID | Account ID for BerriSpend service | BRAINTRUST_API_KEY | API key for Braintrust integration | BRAINTRUST_API_BASE | Base URL for Braintrust API. Default is https://api.braintrustdata.com/v1 +| BRAINTRUST_MOCK | Enable mock mode for Braintrust integration testing. When set to true, intercepts Braintrust API calls and returns mock responses without making actual network calls. Default is false +| BRAINTRUST_MOCK_LATENCY_MS | Mock latency in milliseconds for Braintrust API calls when mock mode is enabled. Simulates network round-trip time. Default is 100ms | CACHED_STREAMING_CHUNK_DELAY | Delay in seconds for cached streaming chunks. Default is 0.02 | CHATGPT_API_BASE | Base URL for ChatGPT API. Default is https://chatgpt.com/backend-api/codex | CHATGPT_AUTH_FILE | Filename for ChatGPT authentication data. Default is "auth.json" @@ -462,6 +465,7 @@ router_settings: | CHATGPT_USER_AGENT_SUFFIX | Suffix to append to the ChatGPT user agent string | CIRCLE_OIDC_TOKEN | OpenID Connect token for CircleCI | CIRCLE_OIDC_TOKEN_V2 | Version 2 of the OpenID Connect token for CircleCI +| CLI_JWT_EXPIRATION_HOURS | Expiration time in hours for CLI-generated JWT tokens. Default is 24 hours. Can also be set via LITELLM_CLI_JWT_EXPIRATION_HOURS | CLOUDZERO_API_KEY | CloudZero API key for authentication | CLOUDZERO_CONNECTION_ID | CloudZero connection ID for data submission | CLOUDZERO_EXPORT_INTERVAL_MINUTES | Interval in minutes for CloudZero data export operations @@ -504,12 +508,15 @@ router_settings: | DD_AGENT_HOST | Hostname or IP of DataDog agent (e.g., "localhost"). When set, logs are sent to agent instead of direct API | DD_AGENT_PORT | Port of DataDog agent for log intake. Default is 10518 | DD_API_KEY | API key for Datadog integration +| DD_APP_KEY | Application key for Datadog Cost Management integration. Required along with DD_API_KEY for cost metrics | DD_SITE | Site URL for Datadog (e.g., datadoghq.com) | DD_SOURCE | Source identifier for Datadog logs | DD_TRACER_STREAMING_CHUNK_YIELD_RESOURCE | Resource name for Datadog tracing of streaming chunk yields. Default is "streaming.chunk.yield" | DD_ENV | Environment identifier for Datadog logs. Only supported for `datadog_llm_observability` callback | DD_SERVICE | Service identifier for Datadog logs. Defaults to "litellm-server" | DD_VERSION | Version identifier for Datadog logs. Defaults to "unknown" +| DATADOG_MOCK | Enable mock mode for Datadog integration testing. When set to true, intercepts Datadog API calls and returns mock responses without making actual network calls. Default is false +| DATADOG_MOCK_LATENCY_MS | Mock latency in milliseconds for Datadog API calls when mock mode is enabled. Simulates network round-trip time. Default is 100ms | DEBUG_OTEL | Enable debug mode for OpenTelemetry | DEFAULT_ALLOWED_FAILS | Maximum failures allowed before cooling down a model. Default is 3 | DEFAULT_A2A_AGENT_TIMEOUT | Default timeout in seconds for A2A (Agent-to-Agent) protocol requests. Default is 6000 @@ -538,6 +545,9 @@ router_settings: | DEFAULT_MAX_TOKENS | Default maximum tokens for LLM calls. Default is 4096 | DEFAULT_MAX_TOKENS_FOR_TRITON | Default maximum tokens for Triton models. Default is 2000 | DEFAULT_MAX_REDIS_BATCH_CACHE_SIZE | Default maximum size for redis batch cache. Default is 1000 +| DEFAULT_MCP_SEMANTIC_FILTER_EMBEDDING_MODEL | Default embedding model for MCP semantic tool filtering. Default is "text-embedding-3-small" +| DEFAULT_MCP_SEMANTIC_FILTER_SIMILARITY_THRESHOLD | Default similarity threshold for MCP semantic tool filtering. Default is 0.3 +| DEFAULT_MCP_SEMANTIC_FILTER_TOP_K | Default number of top results to return for MCP semantic tool filtering. Default is 10 | DEFAULT_MOCK_RESPONSE_COMPLETION_TOKEN_COUNT | Default token count for mock response completions. Default is 20 | DEFAULT_MOCK_RESPONSE_PROMPT_TOKEN_COUNT | Default token count for mock response prompts. Default is 10 | DEFAULT_MODEL_CREATED_AT_TIME | Default creation timestamp for models. Default is 1677610602 @@ -638,6 +648,10 @@ router_settings: | GENERIC_USERINFO_ENDPOINT | Endpoint to fetch user information in generic OAuth | GENERIC_LOGGER_ENDPOINT | Endpoint URL for the Generic Logger callback to send logs to | GENERIC_LOGGER_HEADERS | JSON string of headers to include in Generic Logger callback requests +| GENERIC_ROLE_MAPPINGS_DEFAULT_ROLE | Default LiteLLM role to assign when no role mapping matches in generic SSO. Used with GENERIC_ROLE_MAPPINGS_ROLES +| GENERIC_ROLE_MAPPINGS_GROUP_CLAIM | The claim/attribute name in the SSO token that contains the user's groups. Used for role mapping +| GENERIC_ROLE_MAPPINGS_ROLES | Python dict string mapping LiteLLM roles to SSO group names. Example: `{"proxy_admin": ["admin-group"], "internal_user": ["users"]}` +| GENERIC_USER_ROLE_MAPPINGS | Alternative to GENERIC_ROLE_MAPPINGS_ROLES for configuring user role mappings from SSO | GEMINI_API_BASE | Base URL for Gemini API. Default is https://generativelanguage.googleapis.com | GALILEO_BASE_URL | Base URL for Galileo platform | GALILEO_PASSWORD | Password for Galileo authentication @@ -674,6 +688,8 @@ router_settings: | HCP_VAULT_CERT_ROLE | Role for [Hashicorp Vault Secret Manager Auth](../secret.md#hashicorp-vault) | HELICONE_API_KEY | API key for Helicone service | HELICONE_API_BASE | Base URL for Helicone service, defaults to `https://api.helicone.ai` +| HELICONE_MOCK | Enable mock mode for Helicone integration testing. When set to true, intercepts Helicone API calls and returns mock responses without making actual network calls. Default is false +| HELICONE_MOCK_LATENCY_MS | Mock latency in milliseconds for Helicone API calls when mock mode is enabled. Simulates network round-trip time. Default is 100ms | HOSTNAME | Hostname for the server, this will be [emitted to `datadog` logs](https://docs.litellm.ai/docs/proxy/logging#datadog) | HOURS_IN_A_DAY | Hours in a day for calculation purposes. Default is 24 | HIDDENLAYER_API_BASE | Base URL for HiddenLayer API. Defaults to `https://api.hiddenlayer.ai` @@ -712,6 +728,8 @@ router_settings: | LANGSMITH_PROJECT | Project name for Langsmith integration | LANGSMITH_SAMPLING_RATE | Sampling rate for Langsmith logging | LANGSMITH_TENANT_ID | Tenant ID for Langsmith multi-tenant deployments +| LANGSMITH_MOCK | Enable mock mode for Langsmith integration testing. When set to true, intercepts Langsmith API calls and returns mock responses without making actual network calls. Default is false +| LANGSMITH_MOCK_LATENCY_MS | Mock latency in milliseconds for Langsmith API calls when mock mode is enabled. Simulates network round-trip time. Default is 100ms | LANGTRACE_API_KEY | API key for Langtrace service | LASSO_API_BASE | Base URL for Lasso API | LASSO_API_KEY | API key for Lasso service @@ -723,8 +741,10 @@ router_settings: | LITERAL_API_URL | API URL for Literal service | LITERAL_BATCH_SIZE | Batch size for Literal operations | LITELLM_ANTHROPIC_DISABLE_URL_SUFFIX | Disable automatic URL suffix appending for Anthropic API base URLs. When set to `true`, prevents LiteLLM from automatically adding `/v1/messages` or `/v1/complete` to custom Anthropic API endpoints +| LITELLM_CLI_JWT_EXPIRATION_HOURS | Expiration time in hours for CLI-generated JWT tokens. Default is 24 hours | LITELLM_DD_AGENT_HOST | Hostname or IP of DataDog agent for LiteLLM-specific logging. When set, logs are sent to agent instead of direct API | LITELLM_DD_AGENT_PORT | Port of DataDog agent for LiteLLM-specific log intake. Default is 10518 +| LITELLM_DD_LLM_OBS_PORT | Port for Datadog LLM Observability agent. Default is 8126 | LITELLM_DONT_SHOW_FEEDBACK_BOX | Flag to hide feedback box in LiteLLM UI | LITELLM_DROP_PARAMS | Parameters to drop in LiteLLM requests | LITELLM_MODIFY_PARAMS | Parameters to modify in LiteLLM requests @@ -785,6 +805,7 @@ router_settings: | MAXIMUM_TRACEBACK_LINES_TO_LOG | Maximum number of lines to log in traceback in LiteLLM Logs UI. Default is 100 | MAX_RETRY_DELAY | Maximum delay in seconds for retrying requests. Default is 8.0 | MAX_LANGFUSE_INITIALIZED_CLIENTS | Maximum number of Langfuse clients to initialize on proxy. Default is 50. This is set since langfuse initializes 1 thread everytime a client is initialized. We've had an incident in the past where we reached 100% cpu utilization because Langfuse was initialized several times. +| MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH | Maximum header length for MCP semantic filter tools. Default is 150 | MIN_NON_ZERO_TEMPERATURE | Minimum non-zero temperature value. Default is 0.0001 | MINIMUM_PROMPT_CACHE_TOKEN_COUNT | Minimum token count for caching a prompt. Default is 1024 | MISTRAL_API_BASE | Base URL for Mistral API. Default is https://api.mistral.ai @@ -820,6 +841,7 @@ router_settings: | OPENMETER_EVENT_TYPE | Type of events sent to OpenMeter | ONYX_API_BASE | Base URL for Onyx Security AI Guard service (defaults to https://ai-guard.onyx.security) | ONYX_API_KEY | API key for Onyx Security AI Guard service +| ONYX_TIMEOUT | Timeout in seconds for Onyx Guard server requests. Default is 10 | OTEL_ENDPOINT | OpenTelemetry endpoint for traces | OTEL_EXPORTER_OTLP_ENDPOINT | OpenTelemetry endpoint for traces | OTEL_ENVIRONMENT_NAME | Environment name for OpenTelemetry @@ -843,6 +865,8 @@ router_settings: | POD_NAME | Pod name for the server, this will be [emitted to `datadog` logs](https://docs.litellm.ai/docs/proxy/logging#datadog) as `POD_NAME` | POSTHOG_API_KEY | API key for PostHog analytics integration | POSTHOG_API_URL | Base URL for PostHog API (defaults to https://us.i.posthog.com) +| POSTHOG_MOCK | Enable mock mode for PostHog integration testing. When set to true, intercepts PostHog API calls and returns mock responses without making actual network calls. Default is false +| POSTHOG_MOCK_LATENCY_MS | Mock latency in milliseconds for PostHog API calls when mock mode is enabled. Simulates network round-trip time. Default is 100ms | PREDIBASE_API_BASE | Base URL for Predibase API | PRESIDIO_ANALYZER_API_BASE | Base URL for Presidio Analyzer service | PRESIDIO_ANONYMIZER_API_BASE | Base URL for Presidio Anonymizer service @@ -880,6 +904,8 @@ router_settings: | ROUTER_MAX_FALLBACKS | Maximum number of fallbacks for router. Default is 5 | RUNWAYML_DEFAULT_API_VERSION | Default API version for RunwayML service. Default is "2024-11-06" | RUNWAYML_POLLING_TIMEOUT | Timeout in seconds for RunwayML image generation polling. Default is 600 (10 minutes) +| S3_VECTORS_DEFAULT_DIMENSION | Default vector dimension for S3 Vectors RAG ingestion. Default is 1024 +| S3_VECTORS_DEFAULT_DISTANCE_METRIC | Default distance metric for S3 Vectors RAG ingestion. Options: "cosine", "euclidean". Default is "cosine" | SECRET_MANAGER_REFRESH_INTERVAL | Refresh interval in seconds for secret manager. Default is 86400 (24 hours) | SEPARATE_HEALTH_APP | If set to '1', runs health endpoints on a separate ASGI app and port. Default: '0'. | SEPARATE_HEALTH_PORT | Port for the separate health endpoints app. Only used if SEPARATE_HEALTH_APP=1. Default: 4001. diff --git a/docs/my-website/docs/proxy/custom_pricing.md b/docs/my-website/docs/proxy/custom_pricing.md index 8f4a4c450f5..b61da85bb1d 100644 --- a/docs/my-website/docs/proxy/custom_pricing.md +++ b/docs/my-website/docs/proxy/custom_pricing.md @@ -9,6 +9,7 @@ LiteLLM provides flexible cost tracking and pricing customization for all LLM pr - **Custom Pricing** - Override default model costs or set pricing for custom models - **Cost Per Token** - Track costs based on input/output tokens (most common) - **Cost Per Second** - Track costs based on runtime (e.g., Sagemaker) +- **Zero-Cost Models** - Bypass budget checks for free/on-premises models by setting costs to 0 - **[Provider Discounts](./provider_discounts.md)** - Apply percentage-based discounts to specific providers - **[Provider Margins](./provider_margins.md)** - Add fees/margins to LLM costs for internal billing - **Base Model Mapping** - Ensure accurate cost tracking for Azure deployments @@ -106,6 +107,51 @@ There are other keys you can use to specify costs for different scenarios and mo These keys evolve based on how new models handle multimodality. The latest version can be found at [https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json](https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json). +## Zero-Cost Models (Bypass Budget Checks) + +**Use Case**: You have on-premises or free models that should be accessible even when users exceed their budget limits. + +**Solution** ✅: Set both `input_cost_per_token` and `output_cost_per_token` to `0` (explicitly) to bypass all budget checks for that model. + +:::info + +When a model is configured with zero cost, LiteLLM will automatically skip ALL budget checks (user, team, team member, end-user, organization, and global proxy budget) for requests to that model. + +**Important**: Both costs must be **explicitly set to 0**. If costs are `null` or undefined, the model will be treated as having cost and budget checks will apply. + +::: + +### Configuration Example + +```yaml +model_list: + # On-premises model - free to use + - model_name: on-prem-llama + litellm_params: + model: ollama/llama3 + api_base: http://localhost:11434 + model_info: + input_cost_per_token: 0 # 👈 Explicitly set to 0 + output_cost_per_token: 0 # 👈 Explicitly set to 0 + + # Paid cloud model - budget checks apply + - model_name: gpt-4 + litellm_params: + model: gpt-4 + api_key: os.environ/OPENAI_API_KEY + # No model_info - uses default pricing from cost map +``` + +### Behavior + +With the above configuration: + +- **User over budget** → Can still use `on-prem-llama` ✅, but blocked from `gpt-4` ❌ +- **Team over budget** → Can still use `on-prem-llama` ✅, but blocked from `gpt-4` ❌ +- **End-user over budget** → Can still use `on-prem-llama` ✅, but blocked from `gpt-4` ❌ + +This ensures your free/on-premises models remain accessible regardless of budget constraints, while paid models are still properly governed. + ## Set 'base_model' for Cost Tracking (e.g. Azure deployments) **Problem**: Azure returns `gpt-4` in the response when `azure/gpt-4-1106-preview` is used. This leads to inaccurate cost tracking diff --git a/docs/my-website/docs/proxy/deploy.md b/docs/my-website/docs/proxy/deploy.md index 7393e73ba87..0761e0e9fa8 100644 --- a/docs/my-website/docs/proxy/deploy.md +++ b/docs/my-website/docs/proxy/deploy.md @@ -200,6 +200,7 @@ Example `requirements.txt` ```shell litellm[proxy]==1.57.3 # Specify the litellm version you want to use +litellm-enterprise prometheus_client langfuse prisma diff --git a/docs/my-website/docs/proxy/embedding.md b/docs/my-website/docs/proxy/embedding.md index 2adaaa24735..0e7c2d55c44 100644 --- a/docs/my-website/docs/proxy/embedding.md +++ b/docs/my-website/docs/proxy/embedding.md @@ -6,6 +6,16 @@ import TabItem from '@theme/TabItem'; See supported Embedding Providers & Models [here](https://docs.litellm.ai/docs/embedding/supported_embedding) +## Supported Input Formats + +The `/v1/embeddings` endpoint follows the [OpenAI embeddings API specification](https://platform.openai.com/docs/api-reference/embeddings/create). The following input formats are supported: + +| Format | Example | +|--------|---------| +| String | `"input": "Hello"` | +| Array of strings | `"input": ["Hello", "World"]` | +| Array of tokens (integers) | `"input": [1234, 5678, 9012]` | +| Array of token arrays | `"input": [[1234, 5678], [9012, 3456]]` | ## Quick start Here's how to route between GPT-J embedding (sagemaker endpoint), Amazon Titan embedding (Bedrock) and Azure OpenAI embedding on the proxy server: diff --git a/docs/my-website/docs/proxy/guardrails/onyx_security.md b/docs/my-website/docs/proxy/guardrails/onyx_security.md index 85b0ba9f830..d240902eb52 100644 --- a/docs/my-website/docs/proxy/guardrails/onyx_security.md +++ b/docs/my-website/docs/proxy/guardrails/onyx_security.md @@ -128,6 +128,7 @@ guardrails: mode: ["pre_call", "post_call", "during_call"] # Run at multiple stages api_key: os.environ/ONYX_API_KEY api_base: os.environ/ONYX_API_BASE + timeout: 10.0 # Optional, defaults to 10 seconds ``` ### Required Parameters @@ -137,6 +138,7 @@ guardrails: ### Optional Parameters - **`api_base`**: Onyx API base URL (defaults to `https://ai-guard.onyx.security`) +- **`timeout`**: Request timeout in seconds (defaults to `10.0`) ## Environment Variables @@ -145,4 +147,5 @@ You can set these environment variables instead of hardcoding values in your con ```shell export ONYX_API_KEY="your-api-key-here" export ONYX_API_BASE="https://ai-guard.onyx.security" # Optional +export ONYX_TIMEOUT=10 # Optional, timeout in seconds ``` diff --git a/docs/my-website/docs/proxy/guardrails/quick_start.md b/docs/my-website/docs/proxy/guardrails/quick_start.md index cb6379d49f4..ddb215fcb66 100644 --- a/docs/my-website/docs/proxy/guardrails/quick_start.md +++ b/docs/my-website/docs/proxy/guardrails/quick_start.md @@ -405,14 +405,10 @@ curl --location 'http://0.0.0.0:4000/chat/completions' \ ## **Proxy Admin Controls** -### ✨ Monitoring Guardrails +### Monitoring Guardrails Monitor which guardrails were executed and whether they passed or failed. e.g. guardrail going rogue and failing requests we don't intend to fail -:::info - -✨ This is an Enterprise only feature [Get a free trial](https://www.litellm.ai/enterprise#trial) - ::: #### Setup diff --git a/docs/my-website/docs/proxy/keys_teams_router_settings.md b/docs/my-website/docs/proxy/keys_teams_router_settings.md new file mode 100644 index 00000000000..ec59e8f271b --- /dev/null +++ b/docs/my-website/docs/proxy/keys_teams_router_settings.md @@ -0,0 +1,150 @@ +import Image from '@theme/IdealImage'; + +# UI - Router Settings for Keys and Teams + +Configure router settings at the key and team level to achieve granular control over routing behavior, fallbacks, retries, and other router configurations. This enables you to customize routing behavior for specific keys or teams without affecting global settings. + +## Overview + +Router Settings for Keys and Teams allows you to configure router behavior at different levels of granularity. Previously, router settings could only be configured globally, applying the same routing strategy, fallbacks, timeouts, and retry policies to all requests across your entire proxy instance. + +With key-level and team-level router settings, you can now: + +- **Customize routing strategies** per key or team (e.g., use `least-busy` for high-priority keys, `latency-based-routing` for others) +- **Configure different fallback chains** for different keys or teams +- **Set key-specific or team-specific timeouts** and retry policies +- **Apply different reliability settings** (cooldowns, allowed failures) per key or team +- **Override global settings** when needed for specific use cases + + + +## Summary + +Router settings follow a **hierarchical resolution order**: **Keys > Teams > Global**. When a request is made: + +1. **Key-level settings** are checked first. If router settings are configured for the API key being used, those settings are applied. +2. **Team-level settings** are checked next. If the key belongs to a team and that team has router settings configured, those settings are used (unless key-level settings exist). +3. **Global settings** are used as the final fallback. If neither key nor team settings are found, the global router settings from your proxy configuration are applied. + +This hierarchical approach ensures that the most specific settings take precedence, allowing you to fine-tune routing behavior for individual keys or teams while maintaining sensible defaults at the global level. + +## How Router Settings Resolution Works + +Router settings are resolved in the following priority order: + +### Resolution Order: Key > Team > Global + +1. **Key-level router settings** (highest priority) + - Applied when router settings are configured directly on an API key + - Takes precedence over all other settings + - Useful for individual key customization + +2. **Team-level router settings** (medium priority) + - Applied when the API key belongs to a team with router settings configured + - Only used if no key-level settings exist + - Useful for applying consistent settings across multiple keys in a team + +3. **Global router settings** (lowest priority) + - Applied from your proxy configuration file or database + - Used as the default when no key or team settings are found + - Previously, this was the only option available + +## How to Configure Router Settings + +### Configuring Router Settings for Keys + +Follow these steps to configure router settings for an API key: + +1. Navigate to [http://localhost:4000/ui/?login=success](http://localhost:4000/ui/?login=success) + +![](https://colony-recorder.s3.amazonaws.com/files/2026-01-24/61889da3-32de-4ebf-9cf3-7dc1db2fc993/ascreenshot_2492cf6d916a4ab98197cc8336e3a371_text_export.jpeg) + +2. Click "+ Create New Key" (or edit an existing key) + +![](https://colony-recorder.s3.amazonaws.com/files/2026-01-24/61889da3-32de-4ebf-9cf3-7dc1db2fc993/ascreenshot_5a25380cf5044b4f93c146139d84403a_text_export.jpeg) + +3. Click "Optional Settings" + +![](https://colony-recorder.s3.amazonaws.com/files/2026-01-24/e5eb5858-1cc1-4273-90bd-19ad139feebd/ascreenshot_33888989cfb9445bb83660f702ba32e0_text_export.jpeg) + +4. Click "Router Settings" + +![](https://colony-recorder.s3.amazonaws.com/files/2026-01-24/d9eeca83-1f76-4fcf-bf61-d89edf3454d3/ascreenshot_825c7993f4b24949aee9b31d4a788d8a_text_export.jpeg) + +5. Configure your desired router settings. For example, click "Fallbacks" to configure fallback models: + +![](https://colony-recorder.s3.amazonaws.com/files/2026-01-24/30ff647f-0254-4410-8311-660eef7ec0c4/ascreenshot_16966c8a0160473eb03e0f2c3b5c3afa_text_export.jpeg) + +6. Click "Select a model to begin configuring fallbacks" and configure your fallback chain: + +![](https://colony-recorder.s3.amazonaws.com/files/2026-01-24/918f1b5b-c656-4864-98bd-d8c58924b6d9/ascreenshot_79ca6cd93be04033929f080e0c8d040a_text_export.jpeg) + +### Configuring Router Settings for Teams + +Follow these steps to configure router settings for a team: + +1. Navigate to [http://localhost:4000/ui/?login=success](http://localhost:4000/ui/?login=success) + +![](https://colony-recorder.s3.amazonaws.com/files/2026-01-24/60a33a8c-2e48-4788-a1a2-e5bcffa98cca/ascreenshot_9e255ba48f914c72ae57db7d3c1c7cd5_text_export.jpeg) + +2. Click "Teams" + +![](https://colony-recorder.s3.amazonaws.com/files/2026-01-24/60a33a8c-2e48-4788-a1a2-e5bcffa98cca/ascreenshot_070934fa9c17453987f21f58117e673b_text_export.jpeg) + +3. Click "+ Create New Team" (or edit an existing team) + +![](https://colony-recorder.s3.amazonaws.com/files/2026-01-24/6f964ce2-f458-4719-a070-1af444ad92f5/ascreenshot_10f427f3106a4032a65d1046668880bd_text_export.jpeg) + +4. Click "Router Settings" + +![](https://colony-recorder.s3.amazonaws.com/files/2026-01-24/a923c4ae-29f2-42b5-93ae-12f62d442691/ascreenshot_144520f2dd2f419dad79dffb1579ec04_text_export.jpeg) + +5. Configure your desired router settings. For example, click "Fallbacks" to configure fallback models: + +![](https://colony-recorder.s3.amazonaws.com/files/2026-01-24/b062ecfa-bf5b-4c99-93a1-84b8b56fdb4c/ascreenshot_ea9acbc4e75448709b64a22addfb4157_text_export.jpeg) + +6. Click "Select a model to begin configuring fallbacks" and configure your fallback chain: + +![](https://colony-recorder.s3.amazonaws.com/files/2026-01-24/67ca2655-4e82-4f93-be9a-7244ad22640f/ascreenshot_4fdbed826cd546d784e8738626be835d_text_export.jpeg) + +## Use Cases + +### Different Routing Strategies per Key + +Configure different routing strategies for different use cases: + +- **High-priority production keys**: Use `latency-based-routing` for optimal performance +- **Development keys**: Use `simple-shuffle` for simplicity +- **Cost-sensitive keys**: Use `cost-based-routing` to minimize expenses + +### Team-Level Consistency + +Apply consistent router settings across all keys in a team: + +- Set team-wide fallback chains for reliability +- Configure team-specific timeout policies +- Apply uniform retry policies across team members + +### Override Global Settings + +Override global settings for specific scenarios: + +- Production keys may need stricter timeout policies than development +- Certain teams may require different fallback models +- Individual keys may need custom retry policies for specific use cases + +### Gradual Rollout + +Test new router settings on specific keys or teams before applying globally: + +- Configure new routing strategies on a test key first +- Validate fallback chains on a small team before global rollout +- A/B test different timeout values across different keys + +## Related Features + +- [Router Settings Reference](./config_settings.md#router_settings---reference) - Complete reference of all router settings +- [Load Balancing](./load_balancing.md) - Learn about routing strategies and load balancing +- [Reliability](./reliability.md) - Configure fallbacks, retries, and error handling +- [Keys](./keys.md) - Manage API keys and their settings +- [Teams](./teams.md) - Organize keys into teams diff --git a/docs/my-website/docs/proxy/litellm_managed_files.md b/docs/my-website/docs/proxy/litellm_managed_files.md index 7aba173f35b..6272180bd40 100644 --- a/docs/my-website/docs/proxy/litellm_managed_files.md +++ b/docs/my-website/docs/proxy/litellm_managed_files.md @@ -11,7 +11,7 @@ import Image from '@theme/IdealImage'; This is a free LiteLLM Enterprise feature. -Available via the `litellm[proxy]` package or any `litellm` docker image. +Available via the `litellm` docker image. If you are using the pip package, you must install [`litellm-enterprise`](https://pypi.org/project/litellm-enterprise/). ::: diff --git a/docs/my-website/docs/proxy/load_balancing.md b/docs/my-website/docs/proxy/load_balancing.md index 42f6ef1aa51..186307d6498 100644 --- a/docs/my-website/docs/proxy/load_balancing.md +++ b/docs/my-website/docs/proxy/load_balancing.md @@ -69,6 +69,67 @@ router_settings: redis_port: 1992 ``` +## Enforce Model Rate Limits + +Strictly enforce RPM/TPM limits set on deployments. When limits are exceeded, requests are blocked **before** reaching the LLM provider with a `429 Too Many Requests` error. + +:::info +By default, `rpm` and `tpm` values are only used for **routing decisions** (picking deployments with capacity). With `enforce_model_rate_limits`, they become **hard limits**. +::: + +### Quick Start + +```yaml +model_list: + - model_name: gpt-4 + litellm_params: + model: openai/gpt-4 + api_key: os.environ/OPENAI_API_KEY + rpm: 60 # 60 requests per minute + tpm: 90000 # 90k tokens per minute + +router_settings: + optional_pre_call_checks: + - enforce_model_rate_limits # 👈 Enables strict enforcement +``` + +### How It Works + +| Limit Type | Enforcement | Accuracy | +|------------|-------------|----------| +| **RPM** | Hard limit - blocked at exact threshold | 100% accurate | +| **TPM** | Best-effort - may slightly exceed | Blocked when already over limit | + +**Why TPM is best-effort:** Token count is unknown until the LLM responds. TPM is checked before each request (blocks if already over), and tracked after (adds actual tokens used). + +### Error Response + +```json +{ + "error": { + "message": "Model rate limit exceeded. RPM limit=60, current usage=60", + "type": "rate_limit_error", + "code": 429 + } +} +``` + +Response includes `retry-after: 60` header. + +### Multi-Instance Deployment + +For multiple LiteLLM proxy instances, add Redis to share rate limit state: + +```yaml +router_settings: + optional_pre_call_checks: + - enforce_model_rate_limits + redis_host: redis.example.com + redis_port: 6379 + redis_password: your-password +``` + + :::info Detailed information about [routing strategies can be found here](../routing) ::: diff --git a/docs/my-website/docs/proxy/prometheus.md b/docs/my-website/docs/proxy/prometheus.md index cd2b3b68f37..93a0675f097 100644 --- a/docs/my-website/docs/proxy/prometheus.md +++ b/docs/my-website/docs/proxy/prometheus.md @@ -121,8 +121,8 @@ Use this to track overall LiteLLM Proxy usage. | Metric Name | Description | |----------------------|--------------------------------------| -| `litellm_proxy_failed_requests_metric` | Total number of failed responses from proxy - the client did not get a success response from litellm proxy. Labels: `"end_user", "hashed_api_key", "api_key_alias", "requested_model", "team", "team_alias", "user", "exception_status", "exception_class", "route"` | -| `litellm_proxy_total_requests_metric` | Total number of requests made to the proxy server - track number of client side requests. Labels: `"end_user", "hashed_api_key", "api_key_alias", "requested_model", "team", "team_alias", "user", "status_code", "user_email", "route"` | +| `litellm_proxy_failed_requests_metric` | Total number of failed responses from proxy - the client did not get a success response from litellm proxy. Labels: `"end_user", "hashed_api_key", "api_key_alias", "requested_model", "team", "team_alias", "user", "user_email", "exception_status", "exception_class", "route", "model_id"` | +| `litellm_proxy_total_requests_metric` | Total number of requests made to the proxy server - track number of client side requests. Labels: `"end_user", "hashed_api_key", "api_key_alias", "requested_model", "team", "team_alias", "user", "status_code", "user_email", "route", "model_id"` | ### Callback Logging Metrics @@ -130,7 +130,12 @@ Monitor failures while shipping logs to downstream callbacks like `s3_v3` cold s | Metric Name | Description | |----------------------|--------------------------------------| -| `litellm_callback_logging_failures_metric` | Total number of failed attempts to emit logs to a configured callback. Labels: `"callback_name"`. Use this to alert on callback delivery issues such as repeated failures when writing to `s3_v3`. | +| `litellm_callback_logging_failures_metric` | Total number of failed attempts to emit logs to a configured callback. Labels: `"callback_name"`. Use this to alert on callback delivery issues such as repeated failures when writing to `s3_v3`, `langfuse`, or `langfuse_otel` and other otel providers | + +**Supported Callbacks:** +- `S3Logger` - S3 v2 cold storage failures +- `langfuse` - Langfuse logging failures +- `otel` - OpenTelemetry logging failures ## LLM Provider Metrics @@ -191,10 +196,10 @@ Use this for LLM API Error monitoring and tracking remaining rate limits and tok | Metric Name | Description | |----------------------|--------------------------------------| -| `litellm_request_total_latency_metric` | Total latency (seconds) for a request to LiteLLM Proxy Server - tracked for labels "end_user", "hashed_api_key", "api_key_alias", "requested_model", "team", "team_alias", "user", "model" | +| `litellm_request_total_latency_metric` | Total latency (seconds) for a request to LiteLLM Proxy Server - tracked for labels "end_user", "hashed_api_key", "api_key_alias", "requested_model", "team", "team_alias", "user", "model", "model_id" | | `litellm_overhead_latency_metric` | Latency overhead (seconds) added by LiteLLM processing - tracked for labels "model_group", "api_provider", "api_base", "litellm_model_name", "hashed_api_key", "api_key_alias" | | `litellm_llm_api_latency_metric` | Latency (seconds) for just the LLM API call - tracked for labels "model", "hashed_api_key", "api_key_alias", "team", "team_alias", "requested_model", "end_user", "user" | -| `litellm_llm_api_time_to_first_token_metric` | Time to first token for LLM API call - tracked for labels `model`, `hashed_api_key`, `api_key_alias`, `team`, `team_alias` [Note: only emitted for streaming requests] | +| `litellm_llm_api_time_to_first_token_metric` | Time to first token for LLM API call - tracked for labels `model`, `hashed_api_key`, `api_key_alias`, `team`, `team_alias`, `requested_model`, `end_user`, `user`, `model_id` [Note: only emitted for streaming requests] | ## Tracking `end_user` on Prometheus diff --git a/docs/my-website/docs/proxy/request_tags.md b/docs/my-website/docs/proxy/request_tags.md new file mode 100644 index 00000000000..c78c48229b4 --- /dev/null +++ b/docs/my-website/docs/proxy/request_tags.md @@ -0,0 +1,58 @@ +# Request Tags for Spend Tracking + +Add tags to model deployments to track spend by environment, AWS account, or any custom label. + +Tags appear in the `request_tags` field of LiteLLM spend logs. + +## Config Setup + +Set tags on model deployments in `config.yaml`: + +```yaml title="config.yaml" +model_list: + - model_name: gpt-4 + litellm_params: + model: azure/gpt-4-prod + api_key: os.environ/AZURE_PROD_API_KEY + api_base: https://prod.openai.azure.com/ + tags: ["AWS_IAM_PROD"] # 👈 Tag for production + + - model_name: gpt-4-dev + litellm_params: + model: azure/gpt-4-dev + api_key: os.environ/AZURE_DEV_API_KEY + api_base: https://dev.openai.azure.com/ + tags: ["AWS_IAM_DEV"] # 👈 Tag for development +``` + +## Make Request + +Requests just specify the model - tags are automatically applied: + +```bash +curl -X POST 'http://0.0.0.0:4000/chat/completions' \ + -H 'Authorization: Bearer sk-1234' \ + -H 'Content-Type: application/json' \ + -d '{ + "model": "gpt-4", + "messages": [{"role": "user", "content": "Hello"}] + }' +``` + +## Spend Logs + +The tag from the model config appears in `LiteLLM_SpendLogs`: + +```json +{ + "request_id": "chatcmpl-abc123", + "request_tags": ["AWS_IAM_PROD"], + "spend": 0.002, + "model": "gpt-4" +} +``` + +## Related + +- [Spend Tracking Overview](cost_tracking.md) +- [Tag Budgets](tag_budgets.md) - Set budget limits per tag diff --git a/docs/my-website/docs/proxy/ui/page_visibility.md b/docs/my-website/docs/proxy/ui/page_visibility.md new file mode 100644 index 00000000000..06b06f33219 --- /dev/null +++ b/docs/my-website/docs/proxy/ui/page_visibility.md @@ -0,0 +1,121 @@ +import Image from '@theme/IdealImage'; + +# Control Page Visibility for Internal Users + +Configure which navigation tabs and pages are visible to internal users (non-admin developers) in the LiteLLM UI. + +Use this feature to simplify the UI and control which pages your internal users/developers can see when signing in. + +## Overview + +By default, all pages accessible to internal users are visible in the navigation sidebar. The page visibility control allows admins to restrict which pages internal users can see, creating a more focused and streamlined experience. + + +## Configure Page Visibility + +### 1. Navigate to Settings + +Click the **Settings** icon in the sidebar. + +![Navigate to Settings](https://colony-recorder.s3.amazonaws.com/files/2026-01-28/cbb6f272-ab18-4996-b57d-7ed4aad721ea/ascreenshot_ab80f3175b1a41b0bdabdd2cd3980573_text_export.jpeg) + +### 2. Go to Admin Settings + +Click **Admin Settings** from the settings menu. + +![Go to Admin Settings](https://colony-recorder.s3.amazonaws.com/files/2026-01-28/e2b327bf-1cfd-4519-a9ce-8a6ecb2de53a/ascreenshot_23bb1577b3f84d22be78e0faa58dee3d_text_export.jpeg) + +### 3. Select UI Settings + +Click **UI Settings** to access the page visibility controls. + +![Select UI Settings](https://colony-recorder.s3.amazonaws.com/files/2026-01-28/fff0366a-4944-457a-8f6a-e22018dde108/ascreenshot_0e268e8651654e75bb9fb40d2ed366a9_text_export.jpeg) + +### 4. Open Page Visibility Configuration + +Click **Configure Page Visibility** to expand the configuration panel. + +![Open Configuration](https://colony-recorder.s3.amazonaws.com/files/2026-01-28/3a4761d6-145a-4afd-8abf-d92744b9ac9f/ascreenshot_23c16eb79c32481887b879d961f1f00a_text_export.jpeg) + +### 5. Select Pages to Make Visible + +Check the boxes for the pages you want internal users to see. Pages are organized by category for easy navigation. + +![Select Pages](https://colony-recorder.s3.amazonaws.com/files/2026-01-28/b9c96b54-6c20-484f-8b0b-3a86decb5717/ascreenshot_3347ade01ebe4ea390bc7b57e53db43f_text_export.jpeg) + +**Available pages include:** +- Virtual Keys +- Playground +- Models + Endpoints +- Agents +- MCP Servers +- Search Tools +- Vector Stores +- Logs +- Teams +- Organizations +- Usage +- Budgets +- And more... + +### 6. Save Your Configuration + +Click **Save Page Visibility Settings** to apply the changes. + +![Save Settings](https://colony-recorder.s3.amazonaws.com/files/2026-01-28/8a215378-44f5-4bb8-b984-06fa2aa03903/ascreenshot_44e7aeebe25a477ba92f73a3ed3df644_text_export.jpeg) + +### 7. Verify Changes + +Internal users will now only see the selected pages in their navigation sidebar. + +![Verify Changes](https://colony-recorder.s3.amazonaws.com/files/2026-01-28/493a7718-b276-40b9-970f-5814054932d9/ascreenshot_ad23b8691f824095ba60256f91ad24f8_text_export.jpeg) + +## Reset to Default + +To restore all pages to internal users: + +1. Open the Page Visibility configuration +2. Click **Reset to Default (All Pages)** +3. Click **Save Page Visibility Settings** + +This will clear the restriction and show all accessible pages to internal users. + +## API Configuration + +You can also configure page visibility programmatically using the API: + +### Get Current Settings + +```bash +curl -X GET 'http://localhost:4000/ui_settings/get' \ + -H 'Authorization: Bearer ' +``` + +### Update Page Visibility + +```bash +curl -X PATCH 'http://localhost:4000/ui_settings/update' \ + -H 'Authorization: Bearer ' \ + -H 'Content-Type: application/json' \ + -d '{ + "enabled_ui_pages_internal_users": [ + "api-keys", + "agents", + "mcp-servers", + "logs", + "teams" + ] + }' +``` + +### Clear Page Visibility Restrictions + +```bash +curl -X PATCH 'http://localhost:4000/ui_settings/update' \ + -H 'Authorization: Bearer ' \ + -H 'Content-Type: application/json' \ + -d '{ + "enabled_ui_pages_internal_users": null + }' +``` + diff --git a/docs/my-website/docs/proxy/ui_logs.md b/docs/my-website/docs/proxy/ui_logs.md index 61f328011c3..8cfe818ebfd 100644 --- a/docs/my-website/docs/proxy/ui_logs.md +++ b/docs/my-website/docs/proxy/ui_logs.md @@ -25,7 +25,10 @@ View Spend, Token Usage, Key, Team Name for Each Request to LiteLLM ## Tracking - Request / Response Content in Logs Page -If you want to view request and response content on LiteLLM Logs, you need to opt in with this setting +If you want to view request and response content on LiteLLM Logs, you can enable it in either place: + +- **From the UI (no restart):** Use [UI Spend Log Settings](./ui_spend_log_settings.md) — open Logs → Settings → enable "Store Prompts in Spend Logs" → Save. Takes effect immediately and overrides config. +- **From config:** Add this to your `proxy_config.yaml` (requires restart): ```yaml general_settings: @@ -34,6 +37,40 @@ general_settings: +## Tracing Tools + +View which tools were provided and called in your completion requests. + + + +**Example:** Make a completion request with tools: + +```bash +curl -X POST 'http://localhost:4000/chat/completions' \ + -H 'Authorization: Bearer sk-1234' \ + -H 'Content-Type: application/json' \ + -d '{ + "model": "gpt-4", + "messages": [{"role": "user", "content": "What is the weather?"}], + "tools": [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the current weather", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string"} + } + } + } + } + ] + }' +``` + +Check the Logs page to see all tools provided and which ones were called. ## Stop storing Error Logs in DB @@ -57,7 +94,10 @@ general_settings: If you're storing spend logs, it might be a good idea to delete them regularly to keep the database fast. -LiteLLM lets you configure this in your `proxy_config.yaml`: +You can set the retention period in either place: + +- **From the UI (no restart):** [UI Spend Log Settings](./ui_spend_log_settings.md) — Logs → Settings → set Retention Period → Save. +- **From config:** Add the following to your `proxy_config.yaml` (requires restart): ```yaml general_settings: diff --git a/docs/my-website/docs/proxy/ui_spend_log_settings.md b/docs/my-website/docs/proxy/ui_spend_log_settings.md new file mode 100644 index 00000000000..5e04974e3a7 --- /dev/null +++ b/docs/my-website/docs/proxy/ui_spend_log_settings.md @@ -0,0 +1,92 @@ +import Image from '@theme/IdealImage'; + +# UI Spend Log Settings + +Configure spend log behavior directly from the Admin UI—no config file edits or proxy restart required. This is especially useful for cloud deployments where updating the config is difficult or requires a long release process. + +## Overview + +Previously, spend log options (such as storing request/response content and retention period) had to be set in `proxy_config.yaml` under `general_settings`. Changing them required editing the config and restarting the proxy, which was a pain point for users-especially in cloud environments—who don't have easy access to the config or whose deployment process makes config updates slow. + + + +**UI Spend Log Settings** lets you: + +- **Store prompts in spend logs** – Enable or disable storing request and response content in the spend logs table (only affects logs created after you change the setting) +- **Set retention period** – Configure how long spend logs are kept before automatic cleanup (e.g. `7d`, `30d`) +- **Apply changes immediately** – No proxy restart needed; settings take effect for new requests as soon as you save + +:::warning UI overrides config +Settings changed in the UI **override** the values in your config file. For example, if `store_prompts_in_spend_logs` is explicitly set to `false` in `general_settings`, turning it on in the UI will still enable storing prompts. Use the UI when you want runtime control without redeploying. +::: + +## Settings You Can Configure + +| Setting | Description | +| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Store Prompts in Spend Logs** | When enabled, request messages and response content are stored for **new** spend logs so you can view them in the Logs UI. Logs created before you enabled this will not have request/response content. When disabled, only metadata (e.g. tokens, cost, model) is stored for new logs. | +| **Retention Period** | Maximum time to keep spend logs before they are automatically deleted (e.g. `7d`, `30d`). Optional; if not set, logs are retained according to your config or default behavior. | + +The same options can be set in config via [general_settings](./config_settings.md#general_settings---reference) (`store_prompts_in_spend_logs`, `maximum_spend_logs_retention_period`). Values set in the UI take precedence. + +## How to Configure Spend Log Settings in the UI + +### 1. Open the Logs page + +Navigate to the Admin UI (e.g. `http://localhost:4000/ui` or your `PROXY_BASE_URL/ui`) and click **Logs**. + +![](https://colony-recorder.s3.amazonaws.com/files/2026-01-31/815f4ab2-4b8c-4dfe-be39-689fd6e12167/ascreenshot_eaaeba1507b441408e0df8bf94bc70cc_text_export.jpeg) + +![](https://colony-recorder.s3.amazonaws.com/files/2026-01-31/815f4ab2-4b8c-4dfe-be39-689fd6e12167/ascreenshot_666628f5e62443688a58b7cee7d7559b_text_export.jpeg) + +### 2. Open Logs settings + +Click the **Settings** (gear) icon on the Logs page to open the spend log settings panel. + +![](https://colony-recorder.s3.amazonaws.com/files/2026-01-31/303077bd-80a0-4f3b-9dc1-4abb90af117f/ascreenshot_63f5dc21a545489ea9266f3bd3dc8455_text_export.jpeg) + +### 3. Enable Store Prompts in Spend Logs (optional) + +Turn on **Store Prompts in Spend Logs** if you want request and response content to be stored for new requests and visible when you open those log entries. This only affects logs created after you enable it; existing logs will not gain request/response content. Leave it off if you only need metadata (tokens, cost, model, etc.). + +![](https://colony-recorder.s3.amazonaws.com/files/2026-01-31/a25d0051-4b34-4270-99d6-6e8ae0d2936a/ascreenshot_374605862aad42c89a98da7bad910f58_text_export.jpeg) + +### 4. Set the retention period (optional) + +Optionally set the **Retention Period** (e.g. `7d`, `30d`) to control how long spend logs are kept before automatic cleanup. Uses the same format as the config option `maximum_spend_logs_retention_period`. + +![](https://colony-recorder.s3.amazonaws.com/files/2026-01-31/87086197-b082-4339-b798-37410f47d9ac/ascreenshot_564da14f492540ae8b0b782cfedceff9_text_export.jpeg) + +### 5. Save settings + +Click **Save Settings**. Changes take effect immediately for new requests; no proxy restart is required. Existing logs are not updated. + +![](https://colony-recorder.s3.amazonaws.com/files/2026-01-31/8cfd82c1-0ff4-4561-a806-33a7998cf0fd/ascreenshot_673f6155b17f45ee9b80fabdfc42a4ee_text_export.jpeg) + +### 6. Verify: view request and response in a log + +After enabling **Store Prompts in Spend Logs**, make a new request through the proxy, then open that log entry (or any other log created after you enabled the setting). The log details view will include the request and response content. Logs that existed before you turned the setting on will not have this content. + +![](https://colony-recorder.s3.amazonaws.com/files/2026-01-31/0fbec553-9a11-4f4f-8a1d-f969bb316c70/ascreenshot_62ecbcea97ea4a4abaa460d76e2cf924_text_export.jpeg) + +![](https://colony-recorder.s3.amazonaws.com/files/2026-01-31/30e7ea4d-2c03-4b96-88a9-eeee565eaf16/ascreenshot_c00ad6aa75b54b4988a1450647a76f6b_text_export.jpeg) + +## Use Cases + +### Cloud and managed deployments + +When the proxy runs in a managed or cloud environment, config may be in a separate repo, require a long release, or be controlled by another team. Using the UI lets you change spend log behavior (e.g. enable prompt storage for debugging or set retention) without going through that process. + +### Quick toggles for debugging + +Temporarily enable **Store Prompts in Spend Logs** to inspect request/response content on new requests when debugging, then turn it off again from the UI without editing config or restarting. Only logs created while the setting was on will contain the content. + +### Retention without redeploying + +Adjust how long spend logs are retained (e.g. shorten to reduce storage or extend for compliance) and have the new retention period and cleanup job take effect immediately. + +## Related Documentation + +- [Getting Started with UI Logs](./ui_logs.md) – Overview of what gets logged and config-based options +- [Config Settings](./config_settings.md) – `store_prompts_in_spend_logs`, `disable_spend_logs`, `maximum_spend_logs_retention_period` in `general_settings` +- [Spend Logs Deletion](./spend_logs_deletion.md) – How retention and cleanup work diff --git a/docs/my-website/docs/rag_ingest.md b/docs/my-website/docs/rag_ingest.md index 1133b85f206..7adc2d70b5b 100644 --- a/docs/my-website/docs/rag_ingest.md +++ b/docs/my-website/docs/rag_ingest.md @@ -5,7 +5,7 @@ All-in-one document ingestion pipeline: **Upload → Chunk → Embed → Vector | Feature | Supported | |---------|-----------| | Logging | Yes | -| Supported Providers | `openai`, `bedrock`, `vertex_ai`, `gemini` | +| Supported Providers | `openai`, `bedrock`, `vertex_ai`, `gemini`, `s3_vectors` | :::tip After ingesting documents, use [/rag/query](./rag_query.md) to search and generate responses with your ingested content. @@ -75,6 +75,31 @@ curl -X POST "http://localhost:4000/v1/rag/ingest" \ }" ``` +### AWS S3 Vectors + +```bash showLineNumbers title="Ingest to S3 Vectors" +curl -X POST "http://localhost:4000/v1/rag/ingest" \ + -H "Authorization: Bearer sk-1234" \ + -H "Content-Type: application/json" \ + -d "{ + \"file\": { + \"filename\": \"document.txt\", + \"content\": \"$(base64 -i document.txt)\", + \"content_type\": \"text/plain\" + }, + \"ingest_options\": { + \"embedding\": { + \"model\": \"text-embedding-3-small\" + }, + \"vector_store\": { + \"custom_llm_provider\": \"s3_vectors\", + \"vector_bucket_name\": \"my-embeddings\", + \"aws_region_name\": \"us-west-2\" + } + } + }" +``` + ## Response ```json @@ -265,6 +290,57 @@ When `vector_store_id` is omitted, LiteLLM automatically creates: 4. Install: `pip install 'google-cloud-aiplatform>=1.60.0'` ::: +### vector_store (AWS S3 Vectors) + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `custom_llm_provider` | string | - | `"s3_vectors"` | +| `vector_bucket_name` | string | **required** | S3 vector bucket name | +| `index_name` | string | auto-create | Vector index name | +| `dimension` | integer | auto-detect | Vector dimension (auto-detected from embedding model) | +| `distance_metric` | string | `cosine` | Distance metric: `cosine` or `euclidean` | +| `non_filterable_metadata_keys` | array | `["source_text"]` | Metadata keys excluded from filtering | +| `aws_region_name` | string | `us-west-2` | AWS region | +| `aws_access_key_id` | string | env | AWS access key | +| `aws_secret_access_key` | string | env | AWS secret key | + +:::info S3 Vectors Auto-Creation +When `index_name` is omitted, LiteLLM automatically creates: +- S3 vector bucket (if it doesn't exist) +- Vector index with auto-detected dimensions from your embedding model + +**Dimension Auto-Detection**: The vector dimension is automatically detected by making a test embedding request to your specified model. No need to manually specify dimensions! + +**Supported Embedding Models**: Works with any LiteLLM-supported embedding model (OpenAI, Cohere, Bedrock, Azure, etc.) +::: + +**Example with auto-detection:** +```json +{ + "embedding": { + "model": "text-embedding-3-small" // Dimension auto-detected as 1536 + }, + "vector_store": { + "custom_llm_provider": "s3_vectors", + "vector_bucket_name": "my-embeddings" + } +} +``` + +**Example with custom embedding provider:** +```json +{ + "embedding": { + "model": "cohere/embed-english-v3.0" // Dimension auto-detected as 1024 + }, + "vector_store": { + "custom_llm_provider": "s3_vectors", + "vector_bucket_name": "my-embeddings", + "distance_metric": "cosine" + } +} +``` + ## Input Examples ### File (Base64) diff --git a/docs/my-website/docs/realtime.md b/docs/my-website/docs/realtime.md index 0b3c823f5db..f4627c78da3 100644 --- a/docs/my-website/docs/realtime.md +++ b/docs/my-website/docs/realtime.md @@ -10,6 +10,7 @@ Supported Providers: - Azure - Google AI Studio (Gemini) - Vertex AI +- Bedrock ## Proxy Usage diff --git a/docs/my-website/docs/routing.md b/docs/my-website/docs/routing.md index 47967775e1e..67e7f681147 100644 --- a/docs/my-website/docs/routing.md +++ b/docs/my-website/docs/routing.md @@ -830,6 +830,12 @@ asyncio.run(router_acompletion()) +## Traffic Mirroring / Silent Experiments + +Traffic mirroring allows you to "mimic" production traffic to a secondary (silent) model for evaluation purposes. The silent model's response is gathered in the background and does not affect the latency or result of the primary request. + +[**See detailed guide on A/B Testing - Traffic Mirroring here**](./traffic_mirroring.md) + ## Basic Reliability ### Deployment Ordering (Priority) @@ -1582,11 +1588,13 @@ Get a slack webhook url from https://api.slack.com/messaging/webhooks Initialize an `AlertingConfig` and pass it to `litellm.Router`. The following code will trigger an alert because `api_key=bad-key` which is invalid ```python -from litellm.router import AlertingConfig import litellm +from litellm.router import Router +from litellm.types.router import AlertingConfig import os +import asyncio -router = litellm.Router( +router = Router( model_list=[ { "model_name": "gpt-3.5-turbo", @@ -1597,17 +1605,28 @@ router = litellm.Router( } ], alerting_config= AlertingConfig( - alerting_threshold=10, # threshold for slow / hanging llm responses (in seconds). Defaults to 300 seconds - webhook_url= os.getenv("SLACK_WEBHOOK_URL") # webhook you want to send alerts to + alerting_threshold=10, + webhook_url= "https:/..." ), ) -try: - await router.acompletion( - model="gpt-3.5-turbo", - messages=[{"role": "user", "content": "Hey, how's it going?"}], - ) -except: - pass + +async def main(): + print(f"\n=== Configuration ===") + print(f"Slack logger exists: {router.slack_alerting_logger is not None}") + + try: + await router.acompletion( + model="gpt-3.5-turbo", + messages=[{"role": "user", "content": "Hey, how's it going?"}], + ) + except Exception as e: + print(f"\n=== Exception caught ===") + print(f"Waiting 10 seconds for alerts to be sent via periodic flush...") + await asyncio.sleep(10) + print(f"\n=== After waiting ===") + print(f"Alert should have been sent to Slack!") + +asyncio.run(main()) ``` ## Track cost for Azure Deployments diff --git a/docs/my-website/docs/traffic_mirroring.md b/docs/my-website/docs/traffic_mirroring.md new file mode 100644 index 00000000000..3bdcb0f1614 --- /dev/null +++ b/docs/my-website/docs/traffic_mirroring.md @@ -0,0 +1,83 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# A/B Testing - Traffic Mirroring + +Traffic mirroring allows you to "mimic" production traffic to a secondary (silent) model for evaluation purposes. The silent model's response is gathered in the background and does not affect the latency or result of the primary request. + +This is useful for: +- Testing a new model's performance on production prompts before switching. +- Comparing costs and latency between different providers. +- Debugging issues by mirroring traffic to a more verbose model. + +## Quick Start + +To enable traffic mirroring, add `silent_model` to the `litellm_params` of a deployment. + + + + +```python +from litellm import Router + +model_list = [ + { + "model_name": "gpt-3.5-turbo", + "litellm_params": { + "model": "azure/chatgpt-v-2", + "api_key": "...", + "silent_model": "gpt-4" # 👈 Mirror traffic to gpt-4 + }, + }, + { + "model_name": "gpt-4", + "litellm_params": { + "model": "openai/gpt-4", + "api_key": "..." + }, + } +] + +router = Router(model_list=model_list) + +# The request to "gpt-3.5-turbo" will trigger a background call to "gpt-4" +response = await router.acompletion( + model="gpt-3.5-turbo", + messages=[{"role": "user", "content": "How does traffic mirroring work?"}] +) +``` + + + + +Add `silent_model` to your `config.yaml`: + +```yaml +model_list: + - model_name: primary-model + litellm_params: + model: azure/gpt-35-turbo + api_key: os.environ/AZURE_API_KEY + silent_model: evaluation-model # 👈 Mirror traffic here + - model_name: evaluation-model + litellm_params: + model: openai/gpt-4o + api_key: os.environ/OPENAI_API_KEY +``` + + + + +## How it works +1. **Request Received**: A request is made to a model group (e.g. `primary-model`). +2. **Deployment Picked**: LiteLLM picks a deployment from the group. +3. **Primary Call**: LiteLLM makes the call to the primary deployment. +4. **Mirroring**: If `silent_model` is present, LiteLLM triggers a background call to that model. + - For **Sync** calls: Uses a shared thread pool. + - For **Async** calls: Uses `asyncio.create_task`. +5. **Isolation**: The background call uses a `deepcopy` of the original request parameters and sets `metadata["is_silent_experiment"] = True`. It also strips out logging IDs to prevent collisions in usage tracking. + +## Key Features +- **Latency Isolation**: The primary request returns as soon as it's ready. The background (silent) call does not block. +- **Unified Logging**: Background calls are processed via the Router, meaning they are automatically logged to your configured observability tools (Langfuse, S3, etc.). +- **Evaluation**: Use the `is_silent_experiment: True` flag in your logs to filter and compare results between the primary and mirrored calls. diff --git a/docs/my-website/docs/troubleshoot/prisma_migrations.md b/docs/my-website/docs/troubleshoot/prisma_migrations.md new file mode 100644 index 00000000000..9d9cb585b2b --- /dev/null +++ b/docs/my-website/docs/troubleshoot/prisma_migrations.md @@ -0,0 +1,113 @@ +# Troubleshooting Prisma Migration Errors + +Common Prisma migration issues encountered when upgrading or downgrading LiteLLM proxy versions, and how to fix them. + +## How Prisma Migrations Work in LiteLLM + +- LiteLLM uses [Prisma](https://www.prisma.io/) to manage its PostgreSQL database schema. +- Migration history is tracked in the `_prisma_migrations` table in your database. +- When LiteLLM starts, it runs `prisma migrate deploy` to apply any new migrations. +- Upgrading LiteLLM applies all migrations added since your last applied version. + +## Common Errors + +### 1. `relation "X" does not exist` + +**Example error:** + +``` +ERROR: relation "LiteLLM_DeletedTeamTable" does not exist +Migration: 20260116142756_update_deleted_keys_teams_table_routing_settings +``` + +**Cause:** This typically happens after a version rollback. The `_prisma_migrations` table still records migrations from the newer version as "applied," but the underlying database tables were modified, dropped, or never fully created. + +**How to fix:** + +#### Step 1 — Delete the failed migration entry and restart + +Remove the problematic migration from the history so it can be re-applied: + +```sql +-- View recent migrations +SELECT migration_name, finished_at, rolled_back_at, logs +FROM "_prisma_migrations" +ORDER BY started_at DESC +LIMIT 10; + +-- Delete the failed migration entry +DELETE FROM "_prisma_migrations" +WHERE migration_name = ''; +``` + +After deleting the entry, restart LiteLLM — it will re-apply the migration on startup. + +#### Step 2 — If that doesn't work, use `prisma db push` + +If deleting the migration entry and restarting doesn't resolve the issue, sync the schema directly: + +```bash +DATABASE_URL="" prisma db push +``` + +This bypasses migration history and forces the database schema to match the Prisma schema. + +--- + +### 2. `New migrations cannot be applied before the error is recovered from` + +**Cause:** A previous migration failed (recorded with an error in `_prisma_migrations`), and Prisma refuses to apply any new migrations until the failure is resolved. + +**How to fix:** + +1. Find the failed migration: + +```sql +SELECT migration_name, finished_at, rolled_back_at, logs +FROM "_prisma_migrations" +WHERE finished_at IS NULL OR rolled_back_at IS NOT NULL +ORDER BY started_at DESC; +``` + +2. Delete the failed entry and restart LiteLLM: + +```sql +DELETE FROM "_prisma_migrations" +WHERE migration_name = ''; +``` + +3. If that doesn't work, use `prisma db push`: + +```bash +DATABASE_URL="" prisma db push +``` + +--- + +### 3. Migration state mismatch after version rollback + +**Cause:** You upgraded to version X (new migrations applied), rolled back to version Y, then upgraded again. The `_prisma_migrations` table has stale entries for migrations that were partially applied or correspond to a schema state that no longer exists. + +**Fix:** + +1. Inspect the migration table for problematic entries: + +```sql +SELECT migration_name, started_at, finished_at, rolled_back_at, logs +FROM "_prisma_migrations" +ORDER BY started_at DESC +LIMIT 20; +``` + +2. For each migration that shouldn't be there (i.e., from the version you rolled back from), delete the entry: + ```sql + DELETE FROM "_prisma_migrations" WHERE migration_name = ''; + ``` + +3. Restart LiteLLM to re-run migrations. + +4. If that doesn't work, use `prisma db push`: + +```bash +DATABASE_URL="" prisma db push +``` diff --git a/docs/my-website/docs/tutorials/claude_agent_sdk.md b/docs/my-website/docs/tutorials/claude_agent_sdk.md new file mode 100644 index 00000000000..c56784ba2df --- /dev/null +++ b/docs/my-website/docs/tutorials/claude_agent_sdk.md @@ -0,0 +1,115 @@ +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +# Claude Agent SDK with LiteLLM + +Use Anthropic's Claude Agent SDK with any LLM provider through LiteLLM Proxy. + +The Claude Agent SDK provides a high-level interface for building AI agents. By pointing it to LiteLLM, you can use the same agent code with OpenAI, Bedrock, Azure, Vertex AI, or any other provider. + +## Quick Start + +### 1. Install Dependencies + +```bash +pip install claude-agent-sdk +``` + +### 2. Start LiteLLM Proxy + +```yaml title="config.yaml" showLineNumbers +model_list: + - model_name: bedrock-claude-sonnet-3.5 + litellm_params: + model: "bedrock/us.anthropic.claude-3-5-sonnet-20240620-v1:0" + aws_region_name: "us-east-1" + + - model_name: bedrock-claude-sonnet-4 + litellm_params: + model: "bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0" + aws_region_name: "us-east-1" + + - model_name: bedrock-claude-sonnet-4.5 + litellm_params: + model: "bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0" + aws_region_name: "us-east-1" + + - model_name: bedrock-claude-opus-4.5 + litellm_params: + model: "bedrock/us.anthropic.claude-opus-4-5-20251101-v1:0" + aws_region_name: "us-east-1" + + - model_name: bedrock-nova-premier + litellm_params: + model: "bedrock/amazon.nova-premier-v1:0" + aws_region_name: "us-east-1" +``` + +```bash +litellm --config config.yaml +``` + +### 3. Point Agent SDK to LiteLLM + +| Environment Variable | Value | Description | +|---------------------|-------|-------------| +| `ANTHROPIC_BASE_URL` | `http://localhost:4000` | LiteLLM proxy URL | +| `ANTHROPIC_API_KEY` | `sk-1234` | Your LiteLLM API key (not Anthropic key) | + +```python title="agent.py" showLineNumbers +import os +from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions + +# Point to LiteLLM proxy (not Anthropic) +os.environ["ANTHROPIC_BASE_URL"] = "http://localhost:4000" +os.environ["ANTHROPIC_API_KEY"] = "sk-1234" # Your LiteLLM key + +# Configure agent with any model from your config +options = ClaudeAgentOptions( + system_prompt="You are a helpful AI assistant.", + model="bedrock-claude-sonnet-4", # Use any model from config.yaml + max_turns=20, +) + +async with ClaudeSDKClient(options=options) as client: + await client.query("What is LiteLLM?") + + async for msg in client.receive_response(): + if hasattr(msg, 'content'): + for content_block in msg.content: + if hasattr(content_block, 'text'): + print(content_block.text, end='', flush=True) +``` + + + +## Why Use LiteLLM with Agent SDK? + +| Feature | Benefit | +|---------|---------| +| **Multi-Provider** | Use the same agent code with OpenAI, Bedrock, Azure, Vertex AI, etc. | +| **Cost Tracking** | Track spending across all agent conversations | +| **Rate Limiting** | Set budgets and limits on agent usage | +| **Load Balancing** | Distribute requests across multiple API keys or regions | +| **Fallbacks** | Automatically retry with different models if one fails | + +## Complete Example + +See our [cookbook example](https://github.com/BerriAI/litellm/tree/main/cookbook/anthropic_agent_sdk) for a complete interactive CLI agent that: +- Streams responses in real-time +- Switches between models dynamically +- Fetches available models from the proxy + +```bash +# Clone and run the example +git clone https://github.com/BerriAI/litellm.git +cd litellm/cookbook/anthropic_agent_sdk +pip install -r requirements.txt +python main.py +``` + +## Related Resources + +- [Claude Agent SDK Documentation](https://github.com/anthropics/anthropic-agent-sdk) +- [LiteLLM Proxy Quick Start](../proxy/quick_start) +- [Complete Cookbook Example](https://github.com/BerriAI/litellm/tree/main/cookbook/anthropic_agent_sdk) diff --git a/docs/my-website/docs/tutorials/claude_code_plugin_marketplace.md b/docs/my-website/docs/tutorials/claude_code_plugin_marketplace.md index 946fb47d92a..9d93c717c4f 100644 --- a/docs/my-website/docs/tutorials/claude_code_plugin_marketplace.md +++ b/docs/my-website/docs/tutorials/claude_code_plugin_marketplace.md @@ -2,7 +2,7 @@ import Image from '@theme/IdealImage'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -# Claude Code Plugin Marketplace +# Claude Code Plugin Marketplace (Managed Skills) LiteLLM AI Gateway acts as a central registry for Claude Code plugins. Admins can govern which plugins are available across the organization, and engineers can discover and install approved plugins from a single source. @@ -252,7 +252,7 @@ curl -X POST http://localhost:4000/claude-code/plugins \ }' ``` -### 3. Share with Your Team +### 3. Use in Claude Code Send engineers the marketplace URL: diff --git a/docs/my-website/img/ui_granular_router_settings.png b/docs/my-website/img/ui_granular_router_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..6242679956c763435d313620e372efd6051672ad GIT binary patch literal 359348 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

IUTGW+bd_A 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

ylV?_U5_f%(nTUcR1VIH3k}U^dGi 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 z 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 z

    Z7pp^)v&V*qPpnu&@&hIN4fQM- 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%0DPgH#z;sT0h|XE68dt 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

    `(3c@)d?`pMNKuI;J{UG3x!l^nm45y zn>8~(e+)F2x$av(`%9U4PG0it?71^QWLRI|D0})LWf5(B|Bg!E@DC#t(KsKX{;X%1)LMzQRc|7Z_f9A++zE1Ay9il{*)YcV1ryi0MreUBvTo<&ClZJfi6ak%)Knz2iCY@Ry#~?#g zj^p*|!@HN4#n9%bPGRA!*<2rEP&LWm3-pNJ4PtER6OYVe)KJgRY zvlkU}Ly0#XisUnNLWi{+0-_mTTI~3ktjSu7IYhPEE0(FV{?%eo>U%6>!@;SChwO*9 z%}~c|1g)4tZ`k|}m3O)oMV;&t1dVSr{d%Tl+N4PzFUi29gP{G@Wv_p*YdQf>ce#H{ zLPy*mDI!rJlrw9Wr4v3Ofk|rN*Pm|W)`tqYw*NqGzq^A9(7Lf^E&DA`m?D=0XQ%oZ zE6ZYDn1~V)SiT{YW20g`cO@cX{aMOV(HXNhVdYQDmTD{nd+pQ%$AZD9g>s#C$lG^` zOTr>J=)2%|BG107%z+AhL*_7UvE0SGO9Q*A!)5H`WrcG6cgS0PSR~)Ec;#HD@cw3h zI8J$GH2?a15P~dt-$1y_$-33@dLgZ>s0TG!VD8 z%g{CU7d{R(e5tTITY4X6e<4t8?9I$C^jhVz7mr1P?`z5baH{of!**)=7>)eUZEebs zG|*~nYw8&Henj8KvTJFeFM1p zrtkoW@v~5Sw#1Z1viouDhG4>0jH9Fu^M%~gIi(Y+Kk&-|xig5qGWT!p77I6mRSO9x zEh)R6TftSHg%ER&34fW!2NBsXAzVzM-n}vf1Ct?VJ~{E)&m$pXgM-VLcRVP}(azud zxYgZrM?Tb9!!~S8q)_jvpzHGmJD2bs9|;|~2fWHAo7hqNkmn^Oh2nQy|5Xv=>LM8= zb}b&nT#pa@wIabIOc^cm18TphNmf?QEiom~9)=td#Wys#90*=AR?;T5dCIuDi#;&j zyS=)i!N?Nsxk?6vr_!?zyy6G1Up|5{QUlE`OyyY9pN=!djm}LGMZ2d*wfmc0W z2PBE+s1w6kN6?>m$ZV9@MV>RA>9uA5k8&kks@l_0wRUvnAvUf-R#kNQx*BjV! zoQ-?8=*5>0#X;O@8U>v1{>qTIp~P!Dlz(`I%9!_fgy2i-TJ!IdvDc=GHAYevaW;O# zMCmZHxJhub#PY0Rx7&{MN8m0|x^Y5!NfX~j1BKSFPhA@&=If3A;JMC?rmV%Iw0Y=4 zCrZ()&VmQIFCqy1jvuckfpCdeW2`to*jax3oD-j$xYIGg?gXm3S?A=>gkFOx3!ofY1@myQOv_dc?e|1Nq=i-kp#NKI!X zwHD)k+={p<3RurqKSCPd0s`ACCtgbKjhYv+A_vM_Z$)mhr)Vxt;<_keHI`FL4Cj|( z=l&pPZPTKC*1+@q{rFYdM`q<&{_D4`={h_8ZX*ny#6|jQ^&FU<;T0CvG-uYH7fsnH zGsqu)I^GLkhKPvOJ$$4F>KvXFYplfCeNnDaR)qGt#CJeIKjVH}l&krg(v^*6j)FpZ z8S%vcv};C0M}br_@2!dsG0v)W>M3Fw7;HZ2ho;vj(48*comq@LWK+X+J(T3UHQHrO z$Td5f_Yobok6ZmQff_u1jO}Nyp{O^Azmf)1$V{ESwyf}AZv3R(s^E}LF>asCm>)6Z zbl0wAspyRq5Q;QLcef6{uPfyw1o{-*I)`A!EKFFSoh(z_C@)crkY7b2IwB05$HErJ zbk!t98^6t9CFJ~%7jLc#DYVxNXH_|jU>W(W*WJ&8P5p>^XS-^M0W;OqtF5QK#;Q0r z^Rm${PC|O&QL9t9K*T9pSK{5M<^7q zKkf>^ZdqQC5#)kr^)R^ytnq0G~vQ)vLPQ*t`R?3lnL zqaW6XBey?6N0zlLtJ>*uG8w+{Ks*`&Yj}JGYK4SHiaP{VC}UkCDptgj{hioj6D)fu zRd%@Nt@Em`{3V3OijyfqeZ@{GqNYg#*_KAUv}r3YSA38S1^{Q2n!t=O;c z-@aFvm&S{$H|w4*3q9M_em=?zsZsRPZfyTvC@C`flhg8qNMHzhYVVQj;1DJUQEo?QFo2K4cO?j zyFCY+*$*K-*;*ceqXkbgwfiS{mm)kN$E?r<0`|G_95^zDg@kN8Tjp?d5aUA=1 zkE4QF!CO8X>(AB>PSG44a!DN!35}>mC~>sERCda6Bs@I4cxbi4hJ&=0-O^TL$*eZ@ zmiW4X+oABFRUgWuojbSXwoWmB<+==x?2kGSC7Y@SKNd6s7141XFD^m;vf=?MTUw+5 zF@)#c8oFpHH$vAxSFBcB7U$)S>*!^Fj}fA$PK_jj?P?RBdEaa|3qGT#{dA*fuF<;s#TC z)r)kY+^fjMrbN0L1CvqP&5Y_HlfHec%*e`6Bh!a&g*V8h#$|x)?)7R!&OY#CETGEE z6mZzD6TkS+ST=FAvq#3@itXI=mKhuS&^o!nIF_@OVo9)D7kN}AH_3E$II%`;5PwwM z<5`)5g63@`G6VrcXunwY7}v?3N7+9smud%_7v^!^WaNO!X7$vf*?PUguMJpnTOKDi zR$wDyZ%>T47Kdycr%NsdhkyIpV*p;Gw!-#7aveIEVbP z;(0wiAF$gMef@)rgkbSmSZ=;Fbvs!c>m@-uk;edhZi-n3i9x5wi2lbl+vZvRuVORq zNmk-C=5%hKd|y`e*uyX=yq2JHUc3QhR2|E)OKk5uDKH+=69vdDdQHl5sh#u2!**yf zy+ZGe=#dKJb+iN~qf%o~d|t9WH4O9lF(YbG&(R>5_hr@f>ySrEf>7>F`9&(^QH`XY zA+Bva#B(EqT7)a8U^HQsP0lx`)+YpT%NlOUZzXnU0w=o zsu3FINKILZYu6&~I`q2DlOB0Yk(w@ZqBqyyX60RrC|{cURRh|!LIoU)N7E5KW;z8| z!Glw+%T13U@wSYQ%&kVwaSs`Q9$z1m`tM{-Qiu-~m*xGgS8qw%|TtvIG_rN`DPR?G$Q58-2 zNCZB&A#8(mAvnN&q@f`tH1yuW+Wae@Gn<=hmp}s`_KbW;smW`c*WR0mtE6x9KkM0k ztO@Te$J|c!bITNa4;uDU%a+*8I<*4X-ZI+?;g8Ux5{7*J{J?9n`1d+GQ>tP5Xs}^I z!frJB;jFUO>CuRHFW7-0g|lG2@a>tF!eG+9*IJN)7e)#=Z*=t z*OjW*rKyw7Yb#ckaT~tW^}3jE<15cgeF#^OV^;K4`(r-$6<(ROVl&{Gnc-xp<~4FC z@2yxKkfZOw<}!t{pxF%{WzaWJGxGT4^Gf@YApwc@E{Xt|d{gdhr>twFSb;8Fn!Dak zRs-EITNGqn@BC?f^4gCxcIU#vmziIGy(^xgkux6s4Ejdhv42}KvwNAI(-zs)Z4hFN zHY+EYAWEdoR%(XVEGBrXO$!Z;M}z7~g!A4v9*-*FOSoAb$#%F%}^?oSdbu_@T{q^;iC6giOGpy=mQ_HY z_yErE$&(G}!&jpQM|qtw4@Yh0wfFjp|tjl!`5ajY}K^(tf?rxs^C#;Y31Fy>A8}E`YM#aXqi1OQM?$V zU^(2Oc#SvNk~AnFrkEMl=J>N*{D=#i4J7GS8oeV%{L)%9t8v2^I-Kt##=wxsJmhYuF2y@Lylsv-UTKTjdtsy(C ztfN&}In~T|&*p!iFS<`Ra_O;8r?RBvN;sA_s>fA|0xvILs`{ew@_GipZBybR?cEUeZh!G$aq&$-(Vb-zM9Yc~ zac;Xir`<59?RK1$g}GitF=d%4bRuZ{NGMtC-U{`|uIb{!GqPK!5E8a?2 zN=^uEJ^(*a2+u_0bEh1vt4u`2x2e3NSh`m|r%C_3{qVSmq%G{_T?;9JQDM&=1w5`- z(EfKK9kQ`Pza%Yi*U{cN=K}26S-azRUAOp956Row6bmc-CR2OINl*rGL8x5SP1z-i z@XEhwfx6f$V!wRH%F%${7r=YBQ*Qg2^qQt;h)ICgHDXc*_j9^)A1A1Q|S-q{A-4eNpCoGwwxG0e{I~GxmsC z8-w@RT7#bpiIGv#p2w>sF!UvHr=XRNx${flAPjfQocG92DUu0A?v)s$isp3aTOB=V zFToO)&{~M}Roaoc%&fX;ap zb>3?hHwbVOKsc|>?>iW|yCITXFq&q_5Qz7XrVCTbh)Ume2JJ`O-P|sdSViVK*P``w zB@=ccLuhiMa2RFejPt0{trG^e{@AGHap5ud$RnAgx?FR=cSsrThZ;PJm-3tcZWnxF*s-5HRU0H%w@sMK|njZmdL)o7dh2vANfH+T)S(vxy?1Rjnvbge$4E;I8km5^0`f+jV(#G*|z| zn~fe2_ZWYQf8YB-!I%Z@8-1Cr`Nu@dmbTcRnOc!6IuZ@HD%|?+ zz@7Afl8CMoM@iL9q@4J9(@Nf^px_PgrO_l$tYR$5vZkFVCe!1K6X7}BLWueAPy*Vx zuS_ZlWK3VC!TpLsjblZn@{LgM3$l9w6Sf30m z+dq^ev!oU~OK#}CXkHkq#nN`F2g!;IIm%n~4z}e%)Qz{9VNogi^Su!aet);BjFB3E zO~U^Vd+!<7)V8gSyNlZ{RuDn3fQU2^0jb$GkRm8u2t+|ZsnQ9M#4WOst|A>oK|pF~ z2?R)Dp-K}F0trc^hL(hugb~8G!H3S=;Png1Q)g2xwa`})P2@T z^Q+!Wd4%B0afd`3%hJr}#3gdiVr~Gbwe0!0vs=&s+dGa0?^bkyX#_9{sdiN>kz->Q zEJsbxm9O7_^sr84-$XDXQ5tk7vHS_xg{d~$pjRSbH-hf>a$8D$0UZ?ADr0toqKT+scy(GL{g%1EL?)1S(^BRbgy%Wn!JxFESK}m@LvvY7T>M-nCgm%g~$h20+y1DVmRhRza zMT~k%$0*O-?ODu>q%5;qm93gr2qA&o=BJnh|5r0TuGZ2M#%nqwg7^OSz1&wZE4eOa z&3Y8b?x*BwtFwb^ekTn+1iTWub4&m>Y;gm9xnO`JP{cilcR$w?Tiz%kRzA`~&Msmt zzq+=3A2{4-^WFj%gui{t`?Zud*Y})Wx0-2_c7b&XOVcgbhqAiAhOa#o+cI7xRa4(o z3+O?Y&XcN2ID*o;OBCwIJ9j_emTDy;TlNb~354S9~8ul42;*Ziwui~n;oD;a8 zrnPwyI>CS#($JB{)YUcTHl-?UW&?BoI}G`Z3dN@F#sQTWRLgKl+sHLA?9m2Jf%YyZ z?iW85WQJKm6CuH=N>tEWD;p9G_|TJ1NKXocMhI*uV;D>y*Jyigo3PDNfH66h=4%{Q zaYKF!3Dsm}w`89ZG~E+VX)Odl*$mSW4z8#`>kzOBXEiQeMm-7XAcV;9M1$u5F;Ygp5KPNAY|hnLGJ8{-cTtVou9Heg~!L9S9k$Uohu`IBz2- z!9?bmXAn+%J6{aa1X8v^a9d$Pldhd-_7(+-7H8$wxU!2yzV2O0+tj-fiQMO!Lu7A~ zVjhli9yXjN9a2#Y7-R8)~Ff3cP_>))!}6FRd%se?D*|_&<<2w z%S?T{E<&}*M>%5d@C1cxTS4&i)JlMC0qV_Hc9I}Cy*54SHT`C%ldp3cr{!LlQ(d``5TuR1PLGQ0*Y+Z*7t zfC^MSoGepUK4ovy6Sq1rSXksGp~cJKE)8S~3wX(7wkT%%|L zw71F0!LxL3ZsCkK#_W{n!--=PfvO9>v(ChX+sB#R7i=K5^#Nsd9X%bkz1<{x2XLbi zL~g`|lI`oi@)Pt(BLq`e=5ep5k+!xHlXH35K|OwNhHw~@IcWxD0-*!VcMha+3upAf zrTSv2lF2jLO0gXe^+5ubZ~jt{pWnSHzN2r1Pbg9|rHRIO0%)Po1a)v$Sz@1ke%v4W zGmF3ev%}!&;}c8=(r-g3evnD^si}{H5&$@OeF##|h6q?8)6!s_@AGCPftR%TAiuS#8 z+sQJ(429mZBbc3g^%P`2ePWq26?$b?c>yZV%sp?mmZ1La}Kptg=S zSSQ0QBkDmOy5~y1v&YHPcUcp;xIBIRkiJ^l^t8=^3m%t6O)*jVyAly6v#fK*j%ze7 zHc7?wvC+4Lu6=>3U~ofa?VDD)$O(|1TI0ADpAW0Z@vgjoF3q=}c03gP>bezx>hQ?F z!;9V$xkBut6gowWiE|nu8_U;FlA?J}>}r>W38eBcfJ(`0AB&u8=><(l)zsEnabP%3 zWNr!TaBjLk-Izs*>ngyTnSKUUAlPY$(xFT1`9guh_PN&sC3*`k7NN31X?fa1`;+wZ zf@8B2A&sB}QLdcr!Jmy?2^Na7dqpvJ)}PJvLWx0;>dkKU_H#7-o6koHr2R1Fk(!%1 zZLJ^Rbg}dG5;{IHS~>Fz&TNEG4pBa2BC6SV=xNjtN1wlI%3pj?GEqgKZ_KPzP3;p- zS1ZId54vn8KH3q{Jv5ScwDe(@G}V4J98jdp47SW)x`vOym$7r zkskNY45vBN8L$XPfIV|3LI@I8g=aUH>1ihEMvTUbk57JRTP64nn6*1Iz3pmofVBjO z`vko$is1ooQKIjj;B+)J_Ikw$H~8Pnn;Q9#nY(GbZhASu&~xC1z_7-dyW8e97!~Qemc;! z6#|KE8<9Lg(%FOa39(J{cwN*;LS;2w{2Aw5JbXq(P$wkg0esIDuc53hE82z2x4{5u zM988>^rHCO#l=q#IwJvd>RdaeOwePsH?6;Ni8giUA$j^Xxe&Y8gL=ZDFB@wH4z`~6 zUwa^dcy0HgE%#@F?%AP{T$9}M5qI&#av!R=C9^4{wKi>4y`vYg)WCDh|1mm2{l!x@ zD;UVkhUP(PhtiDPN9$E4)(cE*$;{Kcwl8(CP~EED-sL4@JkIKz{Iw3C$}4V6Ec5h% zRNKi;2~A(XXYelq+Y*0#+wJ6ca4Aw6zd-Z{$S?CCeCW8F(S>x>X#Q~71w@4B6P~OH zF+jd;oyY=6+fn8xPj0Bn5;@&YofQQy3sm&_wH+hnd60)pfT0KS<& zJ-cT|u&J-F_aNug!+>J}pA>A7S?rma`t`|(Cx$Z78v>>vHC!NEMB=CXYr0h2-XBU) zUF(@izh`0=F*AI70Ov5)Q7*$Dbn4m%NLFNc<*zgkg9Rq1F8M{ zcQJQdrZX6Ko3gXBZ`5SmY9cqfzhMMStvBdNdNP8U_k1b<`UO_ny4S41Y$;B^yx)^yHF$QSe#08N#EKF+q+u|!!t!#RV1rbFO^&_Y z!OB#s0QAsZ+{_dw*9UV;xin_;(uoTGJcL&*kzDYgGRJT&fKs(&!~)Gl)Jdt;)Bsyt zu16rU&X8(I-%UI0i6JNuWcLDAV8!x~7k{K;^i9yT-AaGWyZqDp zJ^xl`_1l-w*7kpu>^d53i=_I~O#q@`OUso3eO9|4%02W`?f#gVB>>&ZT8uft;NgV{ z02vr07{J@CtN_H_)2Y@ddo?=lg%pr6@f%Q1>-12Lh+o4Ca)njIHvr*>1rp5rVwmQD zF2Gdv%>lvtxZYwwWnjRpsQ+}%Zh_>~Ac1;AI87B|?#U%@L!~q{YUg=pWmp3-wRzsLa!V$XrOy5rA$N-0I%W2ZW2url%hUOy@QtBW5?V76y_sf(@jp zXtd2h>dG=zXfCAaDQ9(-JR+r)>rLC3;SEDV+{tz6>43?0I13Bd$ga_VFU;GEUyIqZPDX}FCv-c!>jGuYE4ZkGLBWgx4*<6}SuQg!zCjxTZH(Gr9W>>pcJ6O@gwR zNf@4Xt??xZWxrZ~Ir0f6DEu=3X>7DQ9k0sO?5p%8*zg;}WhEpJb?BIMb{6Jz+zAwz z0E&WEHc@Bf4t{9bpJX4&o(5=h=}VUsm6ZrEQrR{_^bRva&cR^oD}S`x0QBsF#mX3XR)6Og^okoe5N%V z>S4)1MzW8u=w)yQPpDd!l3@$aw9?6j$Fx3%2}a}ydZ0?a!Z3Q~e6NaPgH}p5o_(J} zE1koX18YNhwk6s3h_>xW0FYPc=|MLnA!hT3z zXXkgu9J_Jqz`Xv+Qrf13qk#aDzAHd;19TO_v#}NX^C$KzqXpACAS)&!)TX@c z^J;unG;7AQ)^vTqi~N|VvZ^R z;0-xgK^KZ5!s^yGqbyU3gQp;p5BtsaSwEd$g^coAYAZbbwKgI}kvm zy$D!cyil`iy!M3<7O2s#S?5FT(~>(>N3vfj6h;iOIIMa!0`e9Q3f3G5Gp&8`$;CmR zdWXoSCOm^XFtrQh)4bQRd9a-2fi#G%&J|D3&qaJ_V^=i2UWKR3QEZ+IZvm|>sepN! zcy_@-*yo;CnX7Axki6Hq!q3S`zc639v^m+ve3c>56amr#X#1^5sEi0{LWM*k3>3^k z`Q;oiZ|3Pw)9V+nGOiNDsWJpt?23J<4+HlY!ED3tkm229^4o)ltI#DhYUh}$vZ4xd zR!?e(a8)FlU?gBgPrh3j8#Ubk3JMlgD7oa&-Bt93Wj&r-OdAU@Eqc=rdRBy1wD3|q z-mpBnD}>G(mz@hpJI0ohxe+FZI=1Lr3^Bvh9)wnkAZHuP>@Oq_4!#2T44y=niR3w~h`xU>=KgF6fFWdO z5%vJcDobEqWU?2!nldv08IyN7Db^qaDj$0BTCs7#+>6}qJKelYZ=)hB?Ub|htUBf! zhq^XXUUFQTRk_WM7gB;o>#w0^qI++SZD;3|0iO<6V2x63B59NfUj&dUY%$12_S`&OmCogA0W!H>L@{NEwU~;N zD!mL2H#bis0%BOMK@&E68f9T%_)@1Pa3V`Vr5JEWNMQ}y*1l`{b%F80k6?1t&v(Wz zybf#{)<4$33MdvbtIo6$eKS_MRjVq1=~fu(7%lfpw>VB%2-=3}jBAY5>Y zBN*`tYvBQzo$%@UII$OqR=P-5`)6b0>TIUWC020mhPo0892%|wEbi`^jqIXifCI=J$QD$H)ywDnlkslQ= zsEGC(DdE{1(>0jZTIcVNc|=s|znA0rf$vDqPwL4!w2v1=k(yzu(-mxeXPOR6;$u3D zP}Y-`7?-j<7exT~VdYRY=v|XApOBuBVLh;J0&eiX#tPiBl6NI07UQc!-^nD^S0mTJ-dS}f*HH^dhY;JCuxLE(p8yfVWYcxswQi50g%tQkkZSeJ=HF@+N+`6seGiI4hoT%oi76-~D^Qk36PFwd04|iBGwgF#Eb4 zoS-ZvfL$3kP@)EpH~RvZ$G>=;5zv{gw;ZZu(gE)mD4xi?zv(D?x${S3r_2 zcLaVEx7Hs}L9ReJZQ~`yzjN9DZf$Atk|B#w1<+^Wd(+tq8%hXKAo^-$o$zb@D-7<)FVKnUZXFN*v+Xe- zK19wv?*0&Slxpvb4=2V;U-Vz2R0>hygvZrPjgXH)4{~(?7NmDv;%1$bh|p=5AeP7d zZUur!u#dcI@Ga_9vUjRoaNEtK1on9d&fhT!dqJ_MtEVJqt_>94g3{5pA4M%J`@aK< zQ$vM6!MrYK>9;271~voCnKeQ+8o5nq&#v#e^epZXzy=_AA~;xSAi_kb4qxTg0YNn& zuK|2uVi7hu_&t-ua#En;HqQNum(&3Gv38`t`NRTd2SW=Ya6V__jwPf>iaK^W1^Owu zFdn}c$b>PyAbwbkFSAnX_i4KsfyR;6UmIUYk3iFMQCqdP zRIzl(;|jV=66w6BH+hs_``bHxO?@6g9w25}M6QPn`81j>8A)elr=Ru41PeY%NJBye z_td6i5=Ku~akgLSZ+vkRoQm!tMYbzWO|atj|A{m02iVd zkax-qz9vLk>>78qf~+(x&t);R&N^Ydjmi;D3sjy_7{>-VJKr662(c^`Zt#%v6v}mA z;(Uq0I6DO1z;VNIh+{Wt62DWVxL0rGYhwCYgzE?|^bX<%GRw%`); z#eycB=UL%+N->Kd#)REB;LLyFPYE7i4Zm>G20396#JnTWF`O{5z}=9oD-QvHo|Mj+ zPelw@04jG4Ur;M2GC`E#v@uZXBaN8XLbt=B{! zST}99M<0!O(xM!^WPCQ`dT_#X#n`I2%tBVUV(5;~8|Wsq(D)_en0I+7e$;4Y8e=9|?3$^307%QCUtYfhiaP25|Y<_Ir z12hJ6g?@R^-g@wTOTWJ<-7m$(TP(Gc@s%HmR`hb(zT=R$Sd2zEV+w+JHGj8$&8c0h zYDu95x!@eEB^Eq(r>5>q#EI9ZvJyO@u*SzG&y8JIWM99ztUYRWQ}S-y`>XNwF4LAPO8kE<2qJ#o-UAS?%$wdq&jSl#OiTV}nbdx?6pbl-G>uepH z@A|`PcHNp;Y>wTslsbXq4t{rDOL=fH;y}S$gZi!4z_>6ns&p_mJeyiouqjRncM@Oc zC*N|uBulrKlPsx%fBeMP-@`zi7CCw%?x?p}sz6Ise6lZN1d4Lq7jtkkV*{rWcbD!* z@p^Yb-p3Wq`bt-66_v#_Rj7bfaB5L@{C(;<<74r zTa|9_kolQpbGit?Tc z^!AFPo{ay>4!Uw$oMqdgfIe!W+Er9qFd%Nb)hrP!jX+C3eh5!WtxRekcAc1G=lB6M zkW&%|ek%i%G6>Z?6{hb*b-DM{d6lSBB|Gl~2tv>v zEK@?Vej-A1L0cA9MRk$ZmddAf&%WaHw%Ea4<6Wz7T*#Z(^l5Avw`l*UT%R_sn>s1h z2Os30G7I;!?Rwobe+G z%P%Jr!l2h%Ujwncp4_P1EiJ%5*>R$4F!SXHe8&!jnc3u(&^q0_6U5%gvqM9jkrt+R zu+Zy?9pvc`L$TaSMl@^sgM_?A=Hj&6Gl;ijXVaZv<;*QcHzJ2BMU3}e&>^G*+)2vl zqI*}Ux5NYLZ<@tW?qJeS0rCAea8+91u27eUO8&J{`S~tB6jUuZaOni5uXXLACgY>l zBJaY9vtqTqzDwHy%KK|<@pN-@?R>t)`46p)kS8k19Sv|LWQ}%+y@gkEYd@gi(3!Ju z$C2Bwj>vDi$=d#CO-3+x#C=wp4N%O9F|F@^tjq()Zg&w|9k$(?lspO3(d@i4zyN<# zwTCHr^3nlQPkTc_l^xi)7pOEZw#%cvF%`x1HR@A!nE8k|Jj%O+)S7HsonujI;}M; z)}-$~u?vFS4%2SvIh30n*yeSaI`O!b78|bAA;CV>0S20*n=wC}t$=iqPQ#hJvk^+Q zRm0)xqdr=08p_>47Q?9ld9Xaqx?dw)lOZfEB4KJ;Nt09Bx8Jdm4+MY_D|Q&EiWx4nO&GNSDW4 z1t%;&{F##!0q8oclMoYf0`{>rWm3xFlNzQY`;RI_!F36U&;O(l{|{bO?=%{&+XMKuO2IU2SgO= zbL;9*4Y`_A)pK82c~t&d5k8PkpoS4(VLU+m@ARWwE6};~FJFW$-xy%b+zm?Vb?g7i zdcrVOoEq{xPOmldNFfH~qR+OT*4E`elXUgeN-c`NSduI|%K7o=BCI#YZvt%(2A`z| zpe^ zGjwL!q~KY}BflHUX@iSjeyzvOgA5d?Alf(re)|Hdwze!(7@&VrZRy&e+P>CxeWn_Q)(eFz7ne^Tsj@y3}|Yr(x1cm42Ci%8y5b% zqfUxF724riqd*Y?JIdJ}ZU|yA`3-*7WzPRmj~JWpu=N5KX zJ14l!ZjDa|biXijcX#-+YXIp-06+gyqvRUiO#8?}P4l}_Bp#^8_@n)(E!4{bKzt9)aQ5t7 zh?~3npq<7Ho~Dm^qYQ5HAi72D2&j&4{`NO)NnIVO)*P|;G$VX# z2M`mdGdCk8aOlC25yo=Kb$bbW|uqwt|hf# zIUn?^bQ^{8lIheGajm;&GpVVv{*B85sG*I!Qc>I?uBNibFiFyi$Jq>kWRWDjLIBd= z%@bXXU=jzj7$KmwJ{41+oouzt(ynfm`fS}ZpP17ypq$i{22jHWPew+3ZaH@S?5Q&5`Gkt&X;0iz;w>BA@ z&>vbE$*S|&LRw~Q@hPjAtv_|(A@>0&cVF?OJ8k8OG-BaVhR$|C6s2i+NsesI3pbF;2D^BI&8EG z>UDzPPxtm-AN{2l0ZC#OfzG=S3oNdre1JdiuL_^}0ywMs$GAhr1@n*{ozQ!`>MWgT zpa|L)M#wg%w(WG2Muls{{5IPC;Ov92Fu`pAm_+{Mds%?j?K(v@AXJyO^`O?Qnf7efK|;T8X$b3( zC&iL!S$wGZO|~>hSpx8e_e%KEn&2`p-`=bD!S+pBX@{hw?lY6S4FJm4bXA+hbd%2D zN#BgJPm^S^Px*4&g-Qg}fLfpADuB3~AnK8Gv~z#f z(gtF#UdPY915cq z5$bLjjnOYQ?9ZrcK^#UatHJPJ+0qWwy{*hDE5ol)U+>)iTIcxPYCaibyNn^-)4UMt&inXC! zgu_jL@*b(P1GZi`>CXfs4Jwa^TXzB$FPK7u+$?!Mr7HgbTTDol!L&BZ!Wy)=(tM)&2BN^Gqzh@Kj0G5@NnaOHlt)84(%E&=w&(+46w5euRPA$u@f~8XjDCnipV@u+I0n_GHIA(aQFi@d8GxMD zGlh(BkRKfoTPyaje`zDSG~z6=4+$H4I72M?QF>n1Pa*DBlwzgFa;#mBUP#|T^F<>t z=||J*^bkOlEnJDZ%@?n~wpeypB#s~9Keim3XOOx;*z5ZW$YmFK$`PPWcd9ak0-!*U z@=Q|zJdXkIa9)Ak#y|1Cw<@UqwTIo8n{=(7|12Pg5mEW_+IlI~Jr|*WTIN{P-m^im ztsOs}kAWPc4T|!fAgC|6Z}k|OaC&EB$)?oBxY+g|JxoN)iC1C>zL$4!WpTW)$U}8; zKh!-uK&foA8+K9bnXj*PUl{JYv_GsGCY190#^=l4S@o27ra~I@+ zPY3t#Leltn`xw81E`37&)Mqi}RBS=gJ$=R9GME*8b4J;g;q2E$ohzsR+_>iRx1X(eNi)v5H@ihKW@P z179ed3lp1A?CUF)+8Mitwpty9%&6p_qujfk<>Lckwjv?%dx1jxg3!ZLE?Qs ztfY<()1)FZi5=Ra|9VehLg!c6c5eE{Bp|-4>fLb-{rL6bO2UEL<#kGhk4@{Zf@9kf zLnj_HKm7))*b~!SLFcK}Xj+Y3Jfm;IsJViEZTA?mcIAZwC@eU4FS0T#cS~EVoatEM z;H2HcQU%mz@&ayZt;Pe*TaI&($btFs!FjWaROPn_2;qY+&) zph1O7SrTcle%pa(cH4VG3?TWgH99nHZ>84=qCQaPbYZ*c)xL2x>6}tt_J`=*^~0uK zFCSEA8v?be?;BzDk3&B>)pUgK3T-Jq{M4^gl=S3~J|E84Kl_q*DNP?H7> z)$CBfpBh@>c2vHNIgnPXMF#vyAKvEUz~a%Is060>mQUJKoXpRapSF-$@=c0@WE)Ng zu^Z|OFgNK}9U{WzCRm+MpB_^a5miaTRu~vYLXo3ll~uG}o#r?S><9S=lVU_lynx~a z%h}iEyfJ^EL(dk;q|6`4J)n}dA@^D&F}&qwrow)sjr?}+?rV7Ng|4eXIyS*18|G>5G!uAji|F!Gq zqLW2_JR~9F-*Dir!siaAzuAVfkzv!tFF;*r8kP6d)70TQGajR&x(-;oDvC5iI5)pZ zsA#+~60W!`2}HLh0tD|5TAL$nXN>);vFH_=MY%S9Bs|)?0d(~O#MAWx;W5~Dq^iad zVmlJS^ko9ME&SS)gK`KE;&L@b6Z{2OryCXfbxM`QQppI1C;E4L3lIr^B(0=$b90h5 z{)$-mi+A<)?c0hc|4>JB*hWu}4CLh3nmLBj(5t&vIT^tKn4o3RlD^-l;2EW;H>4JY z3AQyX`Z)E_yv95#_c-1@0A~a+C>L-DMur?1*eI3Sm%9PFT&=Ku{C9kwm0|dzeun9) zyX-RW)jK$cpPZB%nCk;6w;_9et?X;=p>klm2mE$NZNR!-LxVP;&VY`SlM27QYxmDQ zDq$f1Yr#qy9|lnxgK6;J{lsIbDrB;6A&~IiF9Q?ZqEVz2qathfHNmnGdl}1d?uprN zXzlL4SbqVF=flW_-bqG!BDv?siQemyu4>`~K0ZCAEDxWKQjdbeUY$!H)>#i-{81qYB@krPyQsSNqR~$zE1|KAo#c?(hE)g5;7Y6(G zE`%h(o3b4S*gY;6x7>4T%V4c0CpED9`=6bj4;2-MnObxlZC%!8kcbZnC9^_nPixfe zm@kL6&YBA_PrNIvA_)~x=CPzs*GOEQp>$edgz{)wb8H&hW%cR)HY4?1wHbZ z=WGCVhEHNmczNkYY`po3sQ_HFlI<-I0s0-3wes;-M3QGwKT9}jI{s2&K?j)XO&Q&* z(uB{ihfb73Ox96B#x#>gp+p9GodC_ zber8d?bd33s$^Wg;X>8bQB9}xCmwc95d8QXKMv-3jyRjTj3m>$=Vkm!!` z_r2t2TfzZ}GgAD)!7rieWZB(!%I@u6$((G&j-5Z*Ru4{GzNIWSbsl6`^lV;edL0nr z!&LHSlZ@brPXt7Q#HwaTx4E9*M}EbP_>lS0d+T_c;*WL^!RPQS&Qj0xSR^k{cQe=j zZe?#mvHq;DV2yZJn7ob{y!>RxmcWQyZSM6-eprtxCPX_d#23V2l3Wr|Ph6Vn4>Sqe z+pm*3PuAwx=&6jNJk{G!%-!kp(|$k=RB5fm50@@5x@fj+O$nXxs${v?>dZaRvcICPLX>)B7MJ*-ay z_9;G6@&Sn#&oi&iVV=Wq&4x&V;pyr6q4yKdt>;k`3>0R>6QCwH8f=suW2zm7AA~Me8A=DQ z7=T1h2Kk`c26`iBtzltTq;!KW0zv+CbncdJxJJ52$nPtzy3*fD7dZZ$x;YFHW*E74cM&4+zR16deO_~{IY_M+`_@MwTJWCm>F{U zhrB&vD{?0rn%$CsY)3uR+?MdsrOC;Ao!G%h%MQ`fV6`8*g)CYRQXJ-Mt^fgeTS(Cw zdqMo{irz7TXH7uBSaRN4+Q#vu{cs9SqT>Qq?}Bt1g7+RhiS}2YJFzcLBh_IwV4>+4 zCM=78h~@9zs2RQDVzr)@d!+u4FE67C~Lo~k6V&^GF*VeewcIGm{6g$ zNa5Yc_yvB+Ak@m)y#>09=Ez$O%1Q9k>G66V@4~jb3QwKTR^ArG3>OO-7##Frh;PdPnVFhp zqOo~BbL$Rl2OtFx`NlK!ngDhgE~#%oA|BaK;6rV@c|w1IXxc+>qgkz5Y|mXx zC)NTKqPx7fbSgwq8N;pA0*&2%riDY~e4cS)8tk0sg|{r?LRv@DwlX$zS0XS5_X+;% zaDYdDjydzGZLr5rHe{rJj=&)-fW7_vZnjlp+pHY?^alDU3>0Y;tTfA#)nc>t!h&{( z0n?um0w5PFutFK0r@6ulFQC4dd-X( zo|o$&v*84T3fI3a;cwjyFH&EY{8=CV>oJ>=g>j!>N1OB*qKu#*bUF0hRAe*c%kj9q z?zD$jkk!Aof}DIBB!Q7+~<`O^<%hse7&mb!B_e}*9@FosTqM3rTGcps{K0l7~n z_%>9zG0b#${q^;g*!iuqY1@#YE%uawo?{Pb&B^r)j^y?A8>2313;Cek&p$-1UKGXn zPH)&YU6fmv3I@6Anigr|AQ#L;oV>~>=8IY%-7llWLZg))W}b^14G~D5QQidz^MsBz zb5jY+NN@0~8z}A}78iQG!eqnwZcMsaA$GSI0B)|}qg^Pm4~J$2UfKtMa9&3 z7O}CXjmPr3o0&GPZzNqnns!-7_A{dMY|AVRjv{u~WE=-u{5&A4eA7><^mF0zj6Ijo zUoD-HPK&g|pJ(nU3wH=CBdT(10&|X!H}r%8%ujGtR{%|=q_1z=Kx($2n<~h(7tvA< z-ksUUeMhmxlG4X}iv&VpxA3LD7NVmT0t7jLq~hD#=A89dQ)NYEgQ$m7a6`LKM*h6u z(X>V6t~=7DPEw>U1Z@$SJUOU939_*C34rxdboM@*_f|B)n|U3d1|-?~ZO>+;Y<|j| z>KT5-eY@{qDj zyw>Au@SXosHuUS)|Nd90|GedwKR*Dk^+}VK!&8>jD~q#Bvx_vNm13yh)xu||Z|oq& zun^1P7q9fH=1Tb@Uex1rTTYU!cZ8TGdY&boL1lFmQHv}tD9VI%;I1kXJbg*ia;E2g zW~#sxb6B2y3|d*E3b8qSTSD~mE4{-u z?VO7Jni25^BBq6-dJu(r5phY80Dk4~yj?BMM%M2+WwQu+K>xb7ep+{CutwI6`4r>A zRn(1hTWSfx9p14wgv4JdI>k-(qYIuS!W(b8Pi6m;<|orbUg)>VwQolbJF3CfSW{Z?g0YDKrX=}KXVoK_#}$YIj$%q-cksUhTH*rFO|Bx#Ljn9=Jfy!b`alS zPh4aqYz`>ZSg3baw_Yhg_HUD_az$@gddq*Kbo9X6@Sn@5{$)09+T>Mwxyr8WA6Bxj z1rCSHvg+EbF<@1CMUO^G_dZ*opqUJS?Jlv|C5%7wN~rSmOn0Dm`Pucat}e&$n$R3t zs#jR(4sNW;ca%+bSb@&O#hH{?oz6gyigdZv6e(y=I#C zW&~^JSbT^Cs#YrUsIq0{TotK*xT&unz93wgW)EaBOkSn1$bFj3qr4dYi35wQ3w``W z_Sspbo7Hz5g1zKi<)*B_epg}ug{>Ef=`jfSwwk3E|`$BjA5evz|Bn@!wQ< zJ5+4{y{*bd`zI!T*z}+-*H~b_(fer{Gm_U+y%M6b+R+hnMpd;>*kiou%aJmt(gN4# zL&9sBFJA1#I^o%xQLko;i!Iml^0a`9Js|9T@ZAQdof76>+VlX}dy9vmoHm$|0)=C5 zTgEPKP}h(`GlXTRp3w0tt?qo5zW{Xfdt3f|cmJ2K&EERw$3I{5W&RBo_rcSBfBEG< zzxwv-wzq#|o&P-Y_4U^4pKtwZFyxHIzwIRXAME~m?jP^tpKnt9*P{l;n?(Qh=n2{X z*W68;{$KC@ZDjnv!SJ;k{(l$@CEsS+DaRs`!kkTwcmo+urUza0U397=C^>zTFL)`L zVk~P_^YP{GW1Z`&x+xo|s;aOm%KalRCBIL2gO>z{9dBcA+mX#cvnAx&NhWLFM-WzY z;)idr|CgqN9|99_Afd0N;M^Ou*pH~)3kKiFIH%@Fo9hKjv4$hX$@ zMUk#JmfKt^x4Fg|4pkywI1?+B8v1tf^AkPHxn1)xkp0nknr4E-Uy*%~*h+>EB6F{?B-;LrI-Iy`kPK5-kHpSAga{Hz_sL#niueRy_Sj(oe@!zAC+m_U%5}Px zrDO4Akj=KYn2vAv{_TWK@lXEkn)Q7m4L@o+%k)8Z-3ePZJi%}>4Z#6J!NKBGE@>tT#)_q#0PPXvYi)$&OplgceX@S zkDa*hGa7v0&;NdVeem@A#MO8`K#wvjA|d*B9Fw4Q_%43ii@a?qwJ(0eSTs^Ou+mX- zQ*#r|?AzM^_7_$d_7j~QOB+=D{_Mer$Gi8H&ikaF&o)Q<*o)ua-G%$t-SYnxTayxO zUl!I$f8qQTDq~_DY}USx;R3YlqMCuVk!>x%NYz8zF9-{I5QI z8v}sNx4Ng1g(ct*$z>wT%3jCNXwF_9owBUk#!!z-%fD3 zR3lbuch+*i#`sL>gq+#$U#BK!WHut>FHFw?BO_ z5e&~q#o5~wKiU9!_LdIjReYb~UoO638E3z{`=j>%JS2cC{}{@%M;!4+5nI$72N;EW z%HnSB!~+_8kq!Ii=KCwF756{anE%c#kwbhEPef7Vgo@AwP&&cwvT zD@9hA-v5^ee;mmE7L3Y60lRePrs#kE`11*L!2Vb(W;@lOGz|Q@zBTAU-&M6{IXSt3 z%d0E0aGQ*f;OU$z|LehgZTs8U5M%w^{jC%37~a`S|Lb1yioH4R*T2S1e+0+rN5{qe zSBB^xOw=ajCLhg5!Ho{UZ2^UQTHg@9uVrr^e;xv5q^EBMcGl+(CwwA(iO1FFbkLgi z{%6((9I6*%=8l5L$A2HT@n&?EO7pf!;MtW`RQzsJda&mTKw3>sPCnLIy~+3T|GL}E z=z%J&F_V^-maj`C*?oujBk&0Qz(`wffbj_ zXxldJ_uI<<+BEHS{mYv8=T!}^e%n0!c?fv@zrk0u`Yw`@)^x%!NsLv84>3OV0@^DTG1E8B-JwEPngafq)JA7~q8O@xtFF&l&-Nff`mZU)kffd^VM&*k{i9s0BH5}Z0;$kUc zSM7x!y z%nhat-WOgnKfqK{LLOiZ58Jg@8<$XFwDPfKa@uC)_z#AO2UxyB9#zB~#0QrkmXZ(} zqkg3FVjMfO^nFSib6fUYd!NggCw2Y*u=nOsO`dDtD6QS{*WJY94AknQr0fAZtWG*1{5C)kOf?$Y%5D`L{LIh+=fDl3mAtX6Bd%r;6 zbNZg{KhC$-x4v~;&sqst@p+!y&o%z8>v!FztW5^ZwiG>)C~z2hb@!BmcvkpEYGp&i zbGjOuBRfGfGs>hx`GsiRg<&!@r0|qtdqHZwNNBV&cVr?df^Q*s1R0mgLQW7YwbSF5 z$GKxk^w2T4!NqP7pCk&piRV&7B4c7=Ed55C{+gzN) zUCz5X0NF^Dc-+`HY$@u>UZ0xi)%X2--f9n1=w$0)XUa)wS*>0EWMLMq&6tJq1D*JT z<;B8Q{&?n&Vw&z|Y7fj;n{cbrkmO{WI7;-ycYUGF^3}>FL0(_IAVl`f-;dRcN(LLV zz0}*AteMu6Z5M4(E)lG+j{i#aa?jf2tYs&Pj!t4skMy}}-!nOKaf6w#{sm$`C46m+K$-tnKQ>A>k(s&#tJ;w{9>+pop zMZYRtOCU^R>-p%s1UY?4SXdA`wB``RNQPdED6yeLhHvXy<)2GN?T(bp{*X5;-s7f3 zzLKCIfrN*tkb$`Xrmf#^AkM_^2KV#U)8Ug#646s3?>CF})UWG&hkv6RHV5z2sZeRVNn3ARaPtxCeLvVJN}5Kcz^QO7 zMc<{tDjfH<*yZ`OQjE9_Jjk^KgI`PA93e|MrDeR>9~fBkuwE6m7M&u@BPK-im(Zul zEI-1~&`@ffjIz{a@p}%Aiz0bOa1()H<4~m-zWxZZU$)Af+dE+PG;2GF)WQ8lf}OFi zJXl{@1@p3L*fa8Kz6b%LgV8!~)j@s#xt~7vFz)NkSM_;3h8V7Uur`!1OD4o`>~7sU zGdKz~lq{O*`n{^oXH>>$G%O8?6 zcup_p2@i3@D6aZ#*R^5j5g(L4ZO_4(-j1}OE(W*99AOg>`oZ32lt*)qm}oPULETG)cUcx}f~ z#i2wdMywhaS4`uB?!Cv%dI|yzgExO{U|x|}Bj)xWypkxdHpm|!LetP|KbFYGyUCQ! z0lV6Z9yKX$2`4T2&o0(W;$0~f-KP9Z5`NjdMXkFS#hS)VyzR^Tw@o8`n%Z>ZPM42} zMjkHHTPWs&PtVMvBlvvIr#C;YkC8+URpa<|8+@a0>D7RYvrenlDK>024w_81)5M!{ zFqzFEOYe|VBbA3)BeJ9(56?F)691)h%ckG%;NlP1-$nH)W6BEjqCScnUo-db?v*DX zKCc|d9<8{rzbhu^8V&R_5s>Kn%)~ajCwencK|eX+hga_0ib^D<*YaOll;C4;0g$O) zFR{Tr66KKr5&UMf^dW^M$SjY?!>Vn3W`3c+9~d~Ftsh^pY%&o@&~iW2iclV7KjK?B zmRiM$<)`~L2s1vfY}trsKE9%W{Eke4hg@$Ej(C|zP~__~)x{XmjL$1kza}!C<`K0}O}# zi}dvC93pDXm{+xsI1L!We>>P3`-3^=;KhzZ3VL!ayhUcd-ddLq?ZqyH#zrSe& z$F9!J`g_0o{#N>@QM|cI8HpTk%FN|!EpRb7E8C@e6KAWS3MGI>?;Gj8tfd>YcG zO0XeTCIt`ImD35;A=7w5!--<|tm_;_=vvJLj@fd7tp36)FXtvBkuibwYgr}x73jer z*%3WoiJh59;l%}yRh$eW*FSJdAdLwr{-rR z-ir8`bH!XRm2Gfr?O@wG=dVUmKd(G{X062BK(?0L;n`=y0i;TUa}d6)UxVtl(e+3D zX)IK<<9a?C6uFVlSz`(jU<|;*P@N_jU{L3bU=r9MB_*YaX&j~ifpYp58~pJ$3w9EI zO7WCA&Zj&1{d_Qp7szdsljbFfa=J1KB9@sskc>7f8`$yj*mmCR!NrJaa;|TP9xni5 z!WkR$w)+q=NGCAu9PAE}3`G~KdRA7h-ZZ=RBwCq48^utU9+NNU;nfyBi^zP7EW1TF z9jP9-3m{A_XzKF-p{nNk*xDJ!B6{)ro6J$J-A(PiH2Le-O&_PG&Rd$zaQa+`eA-j= zQq>f({c?tmIwV27E|I2fkgUY>lSf_AfB`e7bGiIAbDZDRviz(t`#NW^JiXnge$28= zTdw)0E^whRM3`TRR7!H&yZa(;{b+e^!lZ3Q#Tt=4J)o9k2@jgia?S>c8S32z@w5VW z!sxQ1l$3E{KQp^LEHPwXM3PmtbGANjt=*;Z753;MIXz2nygQ{LH-U&A+EaC9@@s-{ zE(lq?#6dXafl0(S9H81_%$2y`I>t7^Ype6otxHj;LjubIL{s89S z=TEgrJyDCJ8~Bq6v>W2q8;{h`d7;AQY|+J=%yFC&go3IZir0o^cWCMPf4s3ifrDNR z5|`6ULz6BWtS{$HFlZaF5(x;{d6kVrnj30r9!Ci;WS<>OPtR29m{fxEFRM$MLD>%L z9U(2*bhC)fyomzY3X`Cd(0y0rA8_8fBt|P{E$k~Luy}J*V|2l+dkKNDghzh^eeusVp5yv!B#qT$|0(3F91#2Brw+s}-tre(uV zqfE+dWFL9CPrsv~jezITQ%6_t|JWpY7~PH+EP|Ly(*}Or2*tf(E{{1_PYrho)sJ<$ zLu7kdSaxGHYXSud5or@dC%2wgBd$VeiQ4=_E@4W~6baj$hSbMa?Whduw1CgM=I4h1AdhYU~wSJj5TxGD7?3vR)|Cl10ned88njAR{ z8RS|SNP1%1n$^O`Gc`kS_kC1$Fs$M89J<&px0#QA}N zI}JmbZ%GU{AWu_6Hr2waj2AE7V)h8U+Ll?1MIa+TNxwfr(3K=LPXsK2_)}MsUzNn= zy$42;9mF@pzra8n2k@6EM7opro7rs?zg7R?9|K|*tLhaNCVnL5Gfk|nl2R#eowLUZ zHq5nKpQ*>E)1^^C+$&J?GAbx&(+nEWyqvHVMKdXe z54B0x`$zEyjY~~lv9qF4(Pl{O296nKGX(NmGCAnA$36-9LnH%!cnNxC;KBrCvt3j= zG}A}SR;DvB_*0_IzUw}q`RPkMp9eji@1kF8tN*ev#6tZ- zIgtoGEZm@NHWRc;l}ZZ*S)mHz#@WD8J)4Mw3lSwQzYeOQmTHO<9{!AG_-$_7t;a78 zruR!>~0+$UR(e%$UyA!!Y<`Q>u$x**uZL`WOI~WJ&A3&eP z1~1<(k&x4oUTazLEP)1aqvL{{Vdk|X(6toD;Nde7K}4VW$E+%$rW<=^80_e4U;bUu z92>rVZ`~fvL|+dQw?eL0L87Z8rbcN8pjhTZOX!x3dL6CsbC3a4IODgP=qj$mUnn$E zPz@G=+wUZ?q|628sKw(DW;BtS3~9!%*@)sSF%mD3&8gX7-@F7)`vMFxpXC4P?Ndwq z+Q`HEJ+WIbLH*c$;}VxdLy!X#)}RgCmhayCpvR{uj>w=ACRUK_Pg$(r$&5@nYneZ9 zXDGXB+j{BeKgCk4cA$aQ(LoTrwT;_w^AWU`2m%9?G$)o4!z)9LG<5?;7BSdxvmvm& zmiSr6kSI8x3_m-bo=&wKAmNsshkmI-7>?cR=MdPBq7O-NnBhW+i#2^eaXx%7_t3H@ zv|yJfyNi9b(hI}KJzZ*xkN_*SFYG_0R4QOP*uf6%lTbK2&gYTBONxMhRB&@E05vAQ4lrX&-#miMGMpH+_f;K7u1{Ps<7e`@kp0n#aweR<6IbFW67s+)tS4a#^%N*m zM6--wz6~q_%!5%WKnOxfY8m2Yh63*r_kzN9A8nc?mRn2`TpjwQ+`jIy{!&A2s1PqQ zL-Wf<7vAw1jv?dYBH?S>%--u^cyC&j3K^`p(0}C)8lF}j>>-g5W?dSYnc`y>R7(Dm z&1MtUXq#)0sHCJMONf4e!K=|lOKq8+S3kB3V-Yx*N14{e}TBCd0%0o;X-;nP|BHlq2tgm0Qk||3=#E=F!8*OTE@^cGB^?p z;kRUt*Ir=07NV$v8pu_X=hKvj#l^*z!~jFYs%@13E`kV&n|2?d+IYaqkA{Hsq#Hd! z5)}V;vD$?@#Kt@HsHR_j9-H=vm?NEhUkjAKRQlfNmf;Vpf8pg3p<|iXw;zmhErvNb zh?hS4D%XXWAT?o%kpO)Jm-4hWd)IZuEn+)}#JHv`=G3_{58V35^0a3yqRTep=fY8y z1lQT1Z{eV~T|m*N9z7bYr#Lek_r8Jy1fJW-3J}<~tk8v(hfTVboVZJlvLgk_Op*V) z4q``d!}$8TtzE9CwV~@~bkIVcPS(vaj1%*+`lgp_K3>E=aNyGK7U6PhLo45W<5g7F zVt32&@3c;q*O3?3E|yYFgPQwwr*WROO-r{e1+TPOwjfTC182&&sIzp47AyVB?x3{ziW)bAc-+)Tb2 zggn<`%o!PJ$m*8gtCV!Lh}>&*th9gW@#UIfQEw2j8itW_(a=>t4E(6z)u@@7nG_z( zem|qq*P~7SDwsK*Ltr%C7aXwj_55nEq9@)PZtV=A}r)2AOxrlvr8QIcX~-4>~Vue`At z)!3N2yuel~(8~gh*+8qIqFglXV*_V0Ox?CBq#%lPcD7tC%EfTC)-POJ&gh;7IeKqm zpC>BbRwwK{nPk8X*YTgbp_4Aycj%>;@#-RT{74o$hjzdQ78bTX7Y+TD9-82Qrjot* zE5(V?@eLbvr8xE?NB9mSjLA)y(vwRrPoFhKCJ+PBr=V)lzBpb-B?ExCs83ajZ81Ea zC5fc=lJ!eTm;^C!rexheCQ)O;81FD#zt}*vKz>xT@QHsl9!4d%NQ zA7V#`xuKSkB+wVqx_tYh%kLk~{p#-K*0leT3<>Hk@Eg|MKNThj$?CL3g>U7!m)~Hz z_tH{vuXQZX-m=?EGHo^$U)Kw8{uW?(at9L)S$A0DU~{l2ew_<(?VM2`q)gt zE7z*|Wx2JIN*_q_4x4KkwD?&w57HQ!y7NAnY_ik^3yVT57l0jY6DO{iQ#~}|C@z>o z#L4v--Z{MgN*S%Dsagi(U5r}%m~-R!7g;^M<$trbs-d6Bu}CQ&O$n?D>Q26}chE7U zhwO9$20=*?Jwg*Yh-5Nq&PpzNf-(Fztna@!6A%-_QZu>_ez}cOZmSAt8O0XwBtIX0 zyxb6H9qL?=CBXbUV8`cQwU_eOq)4NSYj^A6Fxbx~_c!0fEO!BT24uPI@!Afu<7J?J zf&x(rfjvBY{gNOKy>MY7R%z`ef<4oCPE)r}B%EUNxLh>5MSzI}iNR(W%>qMiTLzriVO?WmD9VglW|=xZ}21c8iwD}iEZH|r<)sIFqAjqRb?A4uEo36 z3}GU}AycT!`XNjEn?oFv>7foaul=5&mX3sAfa2+-gIsU8(Ld*n%jUoZQa%EOnt0Ad zaZ-$=w0!8?VX_T86;jh_>7Jx~=g}0rlgrA2HfcZiVa%vpQX2~bxP7c)vxQ)8%50`P z(IVp05NnyBhBhPz*3?WyF)CE+Al*nrLxc*GrL%MwUg8yj9r1X4;z~jRA_9*c2k-&P zmqUshI#GB8KOfE^4A}~vb4M24niO7|(aSTR@mIWh1QR*0xtL6SzdIA3IxDNI)99fQ z!`cn7YZ3`T8oHWK6Q?x^?5zrivlcVw`i1_&2xf?pXy09fegu!FhrK*@zdeY@S!wcD z#fw5wKCx%R%uKJXVj@_fM6coHplylR*lk#C9kms284epxuw=?Uwz;%uI{RF#%(Omm zWYJ}vyoE{$Hsg@(I4-aR((4-f`W9t2)Z3>`#IYdC_4>@V{dVr0lo2;|c|3&1vlv~V zp}*N&S2Qa`-1|y7Pz$tY?H`Nz3>GZ``I!T(!TUwdBP0w`dZaxk>b1>cFMxGCFX>zl z&q#APFccOf8qvXM$P|W?&b=w@pO2dP#`|;v>viD%)>xOLcx^j~$Iz^;nl9>)Q$LAK-uA7 zz)&73&Z24@sq>>H)0Mm1;y?{;5IIRSi|nbXDbC7B`!dN>G#-j%2f;@8S4+5y(66%4 zLV~3qwdtg2tkaqAmUmby*SxcBplLCYlzp0>!fikWOovtIf=k-sS7>Cda`FG2MJ6>EW+1U%bPX` zO~!?XZKTec8|pWoG2pG%5D*u5Rb_4h7IF*IcCAHcBz&3*S$^BM1!GN57(+t+N~UbZ ziNegHWUDw7^S&Oi$89ER(Yzld98kP0X9EcZz2DTL%>@Jfq{y2u($d5^RfrPp>DlRp zk7La#L$i1l;1i6-L?2HO)*!r({1wuRtDJYM*XB4I7_)0-oSzn& zXMqP@PbkcG9IbcDa~x2^ZC(uxg5%w7l1byt+0kg!jKJw`fG+vU61IGDGrW%QFw;Rl2erJnH!_$a?|mTVeS|s(ZFX3UfS_&SJ&f&$=8y zH3mh*Q6|*?+&dDf5J61v0o7^9P-A1rlJ0iv(v4HQ4%b0PopNQFESvU1+RWsY&*-2( zwU+X73wGqAR&Q60r{IGfzwC?&Uc#0Q&47Z*^>0o4CMTVB!W$ri)u4`385XSaeER)F zeB|JrTPGuR4jPIk<1Pt?rp%(ADe`k}s=q7H+dqZaj8SY)Y1!zp%r?1gdDIxUCjL)% zP|by574}pLd;A2ZC5op`wOuh<7f#}6!cEB094r1BE_jgn%V)OCFm5J+9SwA#mijMR&2!)a@U8y49O`VMFxbb3`))ueXI z5m&OO(OyAoP1$dmE8~W|fETG*rkrwZ+EF)mzCgHqB5{^jhE`5cFqFLP)IHea6plqF zEMmQ{pbYpf!q*a5wQC1aQwP*qjtb|Ye4Aol4cQ5ih)PqdLvtPLr9QK@XOq%Wk4HIO zmtp`6{dBk}3u%G7>vjqhoy}O|F}&Jy4WOd-4W~VF#YVUS7xyCo#9bPpDbC}w@oP#G z?&G4Yb60I(0bwZR4dO@PK7}vU4qn{r^8GODh~Rc10`Alrd2-426cecix-UqCy_6_c=gb zD5paG02Gi5Lq9QlM~Xh8X!^3r8WU+(n?^USx0e%5Pfz=+f+($hVWGg%?7HdEh_9hu zu0fh)+-5e&Nso@c3AkIYUnsy{%|9P%z~MB$4tdE;O;e@=5K5)5Ge7@afOCsz!!vJa z7SWQ(NF>ldhTxupkk2Jml;+F_5L#uWf3+=huF56Hn!yYN5gWHOlg_F_3{~gj1@Bhx z>!svYg{+5ZBr_=m0AVM#$diy^9svQxMTyf>A3SS8(54mJQZ$3pGEnua#nYaw>qi+H zX=)}Vdz^>1F!;^^cN=wPSRvLmLf!MJ-1V78VHmlGi%;iN z6FU7Jh8k3Onko>ECDG^!@dSN(3g0lm#1ExhNmMWi!tTvPC>OMSyqBIZwJg&p+}M6R ztunh>{QPWGpZFffT&Tb#`yf`=#xWoaWDDK2>sz&fb{c=5s3?@lpl$ZveUxIppL7TM zeqO*thOE~@1&RPOy#*?oc4Z+S-%o%-+6&!v5m%t$JM~@P=hHc4s15BYfSMX~y}N=UR_J?SJ3Z<+;AZT{<(!r&C(22DZ}R z)XKa4*GeQ548BTEj`~b9>Gi!5X%vSdEX6w*pl5iqaU0`&h5crRoNM?N?iB)~IT$AC z+ce){X-2vh?bxiUG|nFkX@T_4T*%(AZQ#2@U(mtmBlXQG^a=^U^|e69L^g#~Hv~d& zz*BGr4iU{ow-Z-RHqFqL+ClAGu1m7L9c)u_x_=8AzzB-rrW_E?d{~$GS0OL0XU5Ej zUD3R>pV`Dtuq3v=0*15z;q4eY*jVy7h`)Yn4aAofW+tl(fohMXV4-Ko-JnL4PTTOU z8kZ7&EOlbf^b`vh3ZYrt{sc?(qzmNWg$sgnb;PXD^Wm&+xCv*gJOkySg<(~KCCBF2 zjXHb3FY9S2lM)DGy{eL%WJBt1aflVNMGMfMU%T}AEe#!BLVldEgo@hYCPL861MXB| zO}6mP;orIcG6xhm+Nb2g)OIl7Z?rVz1uAJAG1Z}?QeD7UnZm6xFsdiyvv4FZ1HJwCmZA?!~+h0r9=c4X` zK896j1z%kZTQ zM)#Zb(6_z)T#fVqhh_nD;l6Rm4T@LoCbFZACz?2G?75iQKe#~^r%8%FwiMb0!QlC`6%=^`rJ(eWSv#I6RZ zFmYZLP1Ux32KZAt+gWEy7B7+zK2LIq3A6=$yKz5coRFIBx&&b8h2dJmR(rC{yVuf) z+1{b;WNLTuKFa?k2&fMOXb!!&GZc=6mDG5za^I$BrHNBvn03db3E8BpI`GL^-?UWK zEDVjrZY0&(ZI%gV$qZi6T8NTmHM?GoVYQ-OLLMv#>hT@193YS68&bXQ-P?RVoAd+J z>g=>!+yNC%+wUpJFkBdW> zL0s#`$0U$9sJ@gowOUgPjksZXOZCSGLmMu)FqAzJ4C8UtzZD-Cj0}%=d;oi?7Dvn( zfl87+lRvN8UWHJ`yI=mFiRJ$X{pGtYVg7x^cX;NvkmHQMnYsw1cdwRrVyCqK@w=me znVz+tEG(H0!d$=_==$bwN=Kr0(p#|;&vxH$-@Mo#=l~ycEj!OX)VA)dzM+0?=L_fV z?J_7-FRBj}!|c;#K6t;{11*P@Oa9>W-?}i*d2K7<&i@ztp9q7yv=OPyt1Xr_waC2d zW@bkL3VI0i#FI0FweRoa5K4ZNxLj^6Kag|-gSnpC2}EJH(f9U1_3mYM-ktb!*5WSN zKoxPTE;#!Y@N+l09c?Fb%yk9N{(z&Yez9vNQXW|RGiz|)_dh?_HV|TBst^9ON$3CP zKY}Olzl^TAD=f0+J!f;QWyeCwzP{KzDxb_lE?wD%M8*8o8Ec5UKcCHcVV3NgB$lDS z`zU1rwc^A10i{#tQ?WZD^fsKW?`BZ<%WS>5o_MJPX)u zTzN*2u}3Xn&RCUxd*{F3KyXtIb^fJZ@3*qOilvtCAdlj|+F_Gb{%|W_rsPrBRUqN7 z>;*B`ZWTSgv8yr;Z}il~yw3S4_kLUIMz(bK{e8de- z)yOOw%NKRCaAbF$v%?qy=c~STvhI>wB{j-ovfu1ji`$PnIy`eIrCWU!kICUNXOm*K zPcGIXBKJ8w?xR(rgMmQ5pA0k+%~bQ9IL$~3uA z`wvv2+k&x57$#&y_s$=!@6hj_*0*eIjLS!||Jk>AzkRdKxdebKV+*_H=VK;_r0*U6 zC%~~|!(0Lm4R7eWwl)T)-`u%}oj3mjD|q)7v3FfuYR_3S&0c%djXDL@>V_7|fJJj$oU@z=kKh#|> z=#Z_NctLbN&nfBJ$}z0fEzKn zqP?{!GYGO5Cn!k8@_ZonWBAu6cX?{tcl1Z?n03tmx@~A^get10Z|I9S7YqA;wyHnO zl(xl+?|lEXc)@)Rg7;z3Y#fj>ArK|&Vk<9bt&zWQ*Daq0tNiz=DFCC9O>2ulN8Ma- zQ?akMeh!5}q56t<(04RmtjnU@`eHI53I^h$ez3Wvc>oX-2#8uw$pO*6e??YT!Iz5!w`ks;BlmZRjx6>ITHvRT&lC!Bz_`B+|A_|G~dDm{u>h>2U z!y_+?=nZFf1)w!wWjn^$z2N^uLHu7!ci+6SCj7#-LJ22fVCL&bt7Lxse2I`*7GaeH zF#MClzq#J>G^uuNY|dX>9qMxlB6^G+NywT{m`_OcCl0AE&HHvImlMt=mAw4TB;W2Q z$vEX|+p#$Q=U>0MywJKwVR^he#e^1kf!*>2?|ZR-^q%WCNqhffYJjrR!9Q?m zw$EW}E%PVS2rA1ob{LJ@-vpBj{&&9N5pT?BQE;qVDwqqMa2{zs1Fwnr=*1;*=gS9wt;6Nc2ZLz0p zJ+3=2Fp!5)%T8P`eHU;Tfqk9&R_aXW|NBAVr|-3O*e`Qi#j{RwwhgqNzV{7Y(jM)3 zPdAcE-&Ng$ajUs#3?Rt|l1djc* zsnyv?^J0sX(M9deWk&v}*nwo@Mw?Ys7%wxHd04927|Q$ZXm1`RgT<`u@u1u~r^0a#DB^5Ei!B`lU%&1?jRV^J2ErLlzz+_jZ4XmQWUTerE0~aa;wryhB~%2EeBL9!ygj}vjI0?r&%~Ku)lq7&O_`k zegOD|pU?ebG11oM++P~m;e4U&YU9^kNv1A%yK*~?_Sm?*5*gu++4u+9qc)aP0lGg2 zX7#;rD?xBh3*?`R_FwDk++b{Wee3`FxeKK+GBVOX2T&IeG!^5 z_Ut^Qm)2*JE|%J-fNWo8u8bNB9kMG=jssc{=jAdx!yw3DK+btK=^o&-dv4}`vP|o7 z`}pA${N+FX;%LoR?X?PcYH^gJOD;*8Y-CkE-T6ta&1zcJC98tlPPNqygI=e_k8Xip zaZN3iUyUr9I;y5DhmpTfX`u6eaApqzZ~AaYx=!ByOmF4V)xS9Y z<`4^_6ZI>#)lCBetJZZIXbjxB%TP2OzGu+8&RFUGU{}Yq|&Hsj95}7=dH2w|=i8 zerWJ>e_>ZZmFrm?Tx0sf$-h`nu;4p)CMK2<_wryRfX4pAN0CeQRU2<}mQUvp8Y@F~ z(2(V5!%>H-(1C#vGyw5ZG{X5|^T3XM053Ln@hau2H zkxc6#Ufy09F)^v=hErwrMr96Xq!=BOGS%aVYHKC2*&G8M9clAovgbpS^uLf~AnH4s z2AIgIT_C9KR68jlF8&Gc*_D6&FN_|riho!&9VyV2BKmzSTdV!{U7L9!wXIO zc<`v%O?Gi?D;10qFbdoMK2BPf15Yt_OIu5y@?|{Y1jZ!4!EJEEM#qM%ZS5gsoWu z4gL4!K8AmaORsp^l*-=}ul&E!=n4}hn$wIjz!`^yt#-7suTNt}_<^N-%$p4MQ zUh?)MZoPz|+JN~63lDowlrJ!Y^&JL)sd_7{55=3Gp_^}iO-yY3;BIX1@cBxCjpKm> z@$Z*{ah_UsVMhR^-y3CBZrv1gW?-YqDXH7_`fh&k(ecKi-4kuUt9(+jZ6P1TnKxrV zW7Q=py;SYkqV=rDE0aP zx*BGvbK^jMHSO8XeLa}IP1r{I|9I*@PpAIdscSEKXxKfh2hO@iRL4gAzMGXjaLwJx z(ONH+P<{7}+lkcgz1q3JZQk>pJJ|V9O!>tBA$i%3fQrR{!5aiU<-zISXnshHN3Go@ z!@w2Gn>M9M3xl`ZEYH|n%%9A_-2@$%2ZqZV@RhcSiHVUWKg%)7t(!$pbHTX8&qC30 zF4&Ok7>d-z2&bG-Uk{JUqJ5sV+so#!wJX}cT6#ZM*VncggU@*#($LfUx+HB9t0Qf76trTU%bD;jJ;WE3r3MXoh0VsurKJ z4G1?ggQzWO+xO(cY7j}MY?qC+_4TDpdx9(GF)ei$3|5z2!VEQZq>b*+`VbhtfQP<& zQ>cW)+kfo5{!giU$xit9@$(nim}nlt5W|J&$Yn(YAZKF$qg7cWmQq^-HOx2HeQuJE z1x(K#Cvw-ZFfjM>sCD^^FO{=F6ZkPsig1dE|`Xk16R?*cu^W^vKS&97Tp*~G!)*^32;8;uwOJx6V)z1m7t|kN^5kz zLFoON>dRmxtEn-NlC}IEG(d3PzQ1AH92~?Niw0fTFN5g=-a4V{{xImq`Vr9VfESEn zvq1k$`>=3zEPRUNq)TX=9a?2!vobT45^lP~efJw1KMKPcrH4fA-P=YefS%YQTyYJT z+pz#+cIxlo5CR3n?HF+Uz^<+N_|IQ@r0mK~_e2ftXMF0kpc0KeG-OPCE$CYK*qND` zI7+QudFzU&R{AASy_70Q43UY+FwnHu&|;tu7RC{a3n`DMod-R%+Qv4+VGw;Sc^EnZ z4I`e?h$g)k^P2hOtv<8G?khN@xYC6G0d^Jitf|l=_!2XG|8M0CgO&M*4unxPLY~@Y zB)Yqq&IHx;i32a}q+2^uK52JuG^G5Oo<#&DHpIjaKS{&693`Obl{L)N)wJAx%9mV9%^=(YZ>GKBVUxhsJhwxw$x8fjsUs!44n$1Dhp zv*fEcA%?*QV6guuP8M!ooAA+<6$Cpf$f*ZBvrwi^_^Pdkmj2WR3BMNa&&6?PgTU+y zS?!8$hShL`;LR1IR}D9{gh;>+MYgSi=Oc{6Z`#cgc`2rfHyY^pv6=-vFr2$BSO|qdDYq_;A&Q%~7=I zN*L~ZbKQ6VUxmOh;;|ARn~)uFSpt1$ILr(Jg~{a=*_B2eqoSkBUZlMvE^G+`lDnwZ zBD$@m7(qW0*SOw#kP?~mEqi;Q7E^xLJox|cuD2)p1hIgvr_~wLBYkiBp0sq)F-%-f zcJy}ELmW>9yt@{#uY#>HV zT+KhHqw_1}(Vjg{qsQ!HeZWtrdF$&2p4E^95PR)NCO4LO0gefQ!Ouq9^_)-1a;r%d z8A72D1=MQiB+Idx`#AFQtZi>^uOvnm0y*H~;u3~KQAhl#nl{rxC-o(itgl6e)6cJb5nFv zva9uUT_1{Oio3Jp{iyPwxA#|yQVnyI-^-v>{GH-}^Xc?a2%UQhSfiYQ;$g7SY+m1- z9OanBGpDHzPFTdp)|~zXLgO5X&ze$fvoM3EP~sQlS_TCzJoiDVST3yI_qtZaUiL;c zV7I#3Yt4c{Sf#`MD%cf)&^U&5wP;``jIL~qEa|*-z)sOQ=b+kfY(+}x)29=g$jeFv1#O@Q#nSu2c;=CD z4`1H^?9#j5n|=!f(l{Q^7t{pW0hW%>B(K6+p9=<`L``0BQJpHUhZ|nFpxB-r&QXiy z+xRr+7BPmg5X?y&GiWnZ@dPlP!v=%89V}*Pg~J%=ORR8MY2KVGc5h$8VP=C2C^)aK z<@fLJ-U{99ER1auA$@>byyiM(Fj9$fw8gj7T+evaC3wBW-e?gJR?uK%v^jLdiP%e}GC9{+X*v}AW>+aeAQI4oKim|p zyF;zs)gKuS`UbVsoG;pBhWgm(gn#mYK@7FzgPFtW6qX!;*?&P(_tt6wHesz4q9Qo*L5p@ztpmnItv#kxz*3rW;VxCh~gagPh8PlH*f5#h=mi7 zcWX#jSYpDH$9>+y|4M!zOuM;$v#p$d_WE^qJ=C;X>6+d}mP$^Qqx;?Ngr4?hXhnwR z*#s{fx}azV@KLQJ$sP}^$}Kfo>0Rlg_Y(uWyY4du5|X3{(LRaj#>PghQTV5y#_ybn z?YZmBPCl)*VcYK$d^mAF(tC??8`smYBwUJc61_GKGc+`mHw{WpdvofCQk&p3S)Y*x zqfPJiV(0V=+N~Qm8$=HwGeO?f{a!Z$d#px#i$5ELNARdDbOt9Q>^FPzDT zzGV!F(?O%*Sy;%unL7c!el7D+xvcI&BjM8phu{QP@2G0!gO$~6TTF-y9z0%Q|GHbv&7_d$vPr?$YIY?ibVPy1t!F5mtXZsmz`%_+Ba%vIU9`&*fnh&9pi`R z%$z&_;jZKnS?X%qh5Qe9A85kIMBRSlRs$phcKG;nMbEXK_Nt%9j->>pNx(obq|A(q z&+Q!(^HP+aXi_S6(ah^Xkn<^sY@g#Wk~8y*1y%hh zfj2q57}pao^W_gZMdUR#i0V*8wq${MW6RLZ}rNZsmY z4#3Jp9Nd_cX2ej+k5N0k1E9$G&B-9JBe{^XG!@sgG8sxIy@kjL`*XwK-cLdnH8U9> zM>I<{w@XkT9dG_=GPdSV*8Meb6=VR4}q*5!{!hCF~ui_a3ZJc)}5 zay<+i*zn<{jV12NzXqWWV|B40lRn_Y&7#Ed|4lq1$tfuGGOEI}N5ZI{b?swgAZRqW ze|mFy;!2#45!m|a7zdE%PN(SO7p36Pz?C~No0@B@D;n@@iP_14VhdgseV}HT-*b#l zvml-4f#@TS4`x9)-vopx6d13nRPAlOg764E%@-n@F~!lw(Grnt7O>1t-0_G^JwBhX z)Tr!=wsy+~CRF{_z!q7!pJ?`p1N@-W)%;%d5$s*#1~Bz1mcmJ(MoM1j_YUr0kVjS3 z)&l!G4myQ;6tRbs?s?p=p>)AU^+WoU^kD~xi4YRQye{zs^$9Sd?!;hMm_(v@BsX?y zxC%AXD3ubv)TN|Kn#Fp2H!2!%_JxFPc1{W_sK{7^U_;Z1L+@pI)!OG!kNfh*WCA5? zYc^g(jLf~+Y$(6~67Kykv!ykCHN#x$a1W=SWFQs1L=QLB>=tw!WW@PQhAj=_Mc=PA zW-g+a+pok$G$Tgq{5e)OSXSvLe$dLN;z?ng8qQ(B*f?$$+Pf#c+M6q9IFd@F4-cW2 zbE4yj=oKQ=Vj#&IYC`ogm!s6w)NI+XiP!B{nmEPhwDjz}YXSt5T)m8*q{eTuPsi0EzRCC9Fo?(fT!DPDtRy)%sKVG(o76AQL-N-chVa+mDum3!w| z7xwMjyP`+V@UPfPDvCm8$8_*Uqw+Syg2e3La@)pxB||dg4g(|HnU*D~IDy`>re}AT~&up*b#5f6A$nCQzIk5*u@{MwOQbt7>^jDA8M;Ax$Ik`Oe`EK#aZp2>O93A>yipAI<1!4*GG zw=`;vyy&6eK+Jnc>UgoGWZ*WPp!?@txMsX==s_VR%d@-SAaBvz`B_ZX&{ugB+{$uKyVa7Z)d(753N?R5F29bBE6`vEvYqZ# z_cypwn`!~UuI>}3nOal`_lB*OH@7r@3e4txy3)20#8}arIa@ot5E?OWhPNI=Z^l55 zanv?9Ns>LO8`H-bZef%P!-=8Q$rtl}z>~t?_QDIRh7E@^?r}qy-Hag}ObVmci}Fb~ zZnAMnQ!KWHSKi##xdd1{bN8iXWxE-84jX!;0_*Gr0@ zgXNv~2Jg-dKttXvLeP^S5D_u_M0vRhF0~(U32&l8st(@(oS>)J&+L!Rc8q6y9kB%F zjnMgc(WA;6Obt)Od0ARv)4^Ah*nY3)nwq*dHCkg2PE0zyXYUhDn6(M_Wb#gF*^~sQ zYi+R}Cq}#~x)|%jqxJYSN`S(~lp4BrNJumiuyR|LMoQb?gt{x>Hmg;@PFV|-18O)q z7>bt!0-jF9)afR{V0xS$O6XCqYv~6aS5b4N)rY)e3YP>E6IXyO92#Pj_}GBx=|K=V zDeho@%NhrNmX6uv!SAqtRt%J__M(*dvT;LmRoaR~p4(_$?-~n>+X{o%s(ywu(?ZEf zDN@&K2HlS{VtOzN^S#Aj;M)PXuxm23nO7ijG(LAS>^L(kWfmS6+&otKoO+jY(4QKWnA zSKEGd_ci5l2-h9A{dWWC1YgWG($ud1$&d$)=hcci7|-ZGMu3aV$VKXJw!4BvrN+Od4F8u2amNUh!CD-!co z4Z>AA`^DUX$r|t3k*Tl<=X>AbA)pgBdkqHIaLc|Rcdjn#1}W?@hi13y!5R8QO$FjH zi%bFhZ2}d<4(yG&s(JrULw(Na!KJuQ?yK3|JYJ}(*)bAG%0HnK#U*BE%iO`9l(G_; zg4C{F#um&&bir4?*F|#{G6tJx!Q|S^U|6zoUe&MY?74x3Vt-&1urOPX$9wi2I)-70 zgkIK|FF`3DGZm7^mrpWbF8$(HYsXVb%g)vp$i+W78dPiE({aO{=ZJ=xm#8TxQ0#mH z=4qg@9fizGQYvf%2_kUm{^yWAL%%WgQzA&;rQAxEiNDOf;2AcPd+gb>OKUYXFK`97 zyrD)$uy?}&rpM>p)R+AH5{g{|`KbvOOr6VK)#{o?gTo{%C7V0L(mjkL7cMw?R$nO0 zNXtCHY2i8h`nD8BM@6*_KS?+gU0Z4+#Vss26^r1cSq+bkG+rjKf;{Uk&^n%wf-#N) z-Cs*gd;b@6 zy?KxJJR*2?O<$Fkwv}VzC_l0?R%a6K@u>1PwGTbj)#6=eUxnaoGv%YnRF}gT_h%7& ze6F9u7#@wjsa>qWNps`V;u^j9lGc8S3I^{XfY)*&mzcK44b8o@{P5)4*dANcAld4L5jJAImrCnl47E-^%U-u- zHKEtcM4fl97a?E~KJG)TkqRtoV_2$9yCuj5MD_|KBko03(~$CP_#3xVTh#dg5qg~| zynB;|8dY&3#F|cJTrIZLm>WWWydBEQUknqBvDKmgy?>n)=aUsqAI=@GTU_Z+OMIzy zA@F=SuM~ZA4BqH`>85B6Dl7*sM0+O4Up9R?g|-fe zOl@sN1px;p!_aDhR#ZfphoB;Z%*YVJ6g>_o2-H$Q2*Yti#sCpQAOngJ8Ip($VN93< z1QJ3BA%rB~=Da1?_q5+>-|PMJUDx-^81g*Ne)hE1y4St#jcDZ4CQ15rqI}M+`YN{< z7l-=iBLwb>ii$S}>O}SKHN?6jYtc4bys+f-3aIc&2YyB+lV8WA{4CLvPGMecrFo>& zDqG&on;g}3RpjC{Oe2g@o^jI|^C=)sGZpXC*rCMnrZ%vs<4Z`x;C(X>lHG!_ z+|vwi(USUz$Rhe{)R;ufVu-|-kP4bHFP?)!LdZh1OUT3ms0bpiGNoQ&2;MWkCEkn4 zu6P?Hw`3tkNQj`uhfo;33LL6-mbNZqHyad<WECGO zD$AoaJ+s`lEw35Mt)m4Tt_hJ|pHC((6zm+3wm9u%J_NybFsp|Gm%L&kO8rXtg?;XP zC!psFC7DdG1fXMe{J}(WXa%S-Sj5f z4!(tC4&i#QEw5g%od({t?#sdGF@TZ<^0;SNhA~N~6qi}yyh4cHZh!Hzuese(y;+Pn zLw!V$Ahl@{OAakbmqVn;ieQhzj~#LwW3Z+_r&RXJ9a@>huoa~!og;Q?v50Q+nJy%2 zLCmNw+|?F_h%Ml~goJEG2%Mx#Ka)mJ!03)$iB+7Q0F7Nos;Xkk#IH%_+s&6clqUHn zEaHyFP1{O!P+q(e$-Py)r?AO8ul`r-G8|a?`3C7>-Q@fc`>d#!B%jG^axM;BJQ zW&@WeJp{^Bpz8XFUUlWfE)|VdfNlhS3^wg8xxvzxH!hL_>K9hJ)geiw#KhaLfd1_C zXadKW(6J*ch#b<97C$pKLR=*iiQ}yNge769xx9aMC{LkWn++sxpFc-PE#gd0*#f5^ zJ3AZD4GL%=1JFWfq~OYFtU^3vb}-Nm<5u>QE4uFuI-4`rl75kzk8cXMkQyaCdu|jw z`03ug&5Rdffa@kCKTESs1VdCA&3>q9Ue#rN8=$SX#UPo$zrIN!nKV`@8q<@yK$X0i z{HIx-E>}e~f%==&PIr))h$@X<1*UEu6eCaad#C(rB{?O@%yO)7&q!RUJNB!%Dat^y zNXjCvA_^jrr&W6rxs{bTAQ>Dq(yFc3#sc6hzzC8V6K)KwR_?KGlSv{EA?2JX8KST} z#kLa1(}*72Tk-;(c+a!n&gDB zCQu1EzAQj2PT*4EbAmKUGU9C4wlOc8U}-;PNO&-Y4GQZ)?}>S#=H}v-G+#{G#F~_; zin;hMMF^n$L4XwE*aGRijitDRDDk}MUya^AzX>=xhJnsmG_Hsw>PXsge&k4j@b%9H z!okgYzAEf{w8z%U;nj0VZD1s6!uFRhUxw~2SY5VG;>Llx^4ma9kjfIfG{<6bF#|hB zTG^(HRv1Q9xE&}Vz3KXN;Tf&ez51)cFwtqahpMqyXf{Mmru>ptD=l#!Gxo0ZJFQIsl6oybzg5CmG_4%b>1NgWl@Cv_cci zX|n}1tuwhFDYvfelE>l%u?#8;kK#b)9^|#We*OJgf?8ZJZIh~*N0Z7%=IRy}n!=@l zhPq3-z)b>jzF)>oP^E`aV{skz!QZEuu6%E&Io;+R{}Ae~w{!RAkv!)&lDv0*5t6d2 z<8M0VXG$QMaYG?ehNM1liy~nn7EcI4*wI*i^9hc>3%_h++t!!p8b#Pamg07o1Cqus zT+2XqWIi-{sc8=G;pzNQiWNF~XflPBywYEyt28$DcrBl)7L9f+QQx^c1s8l2S!m`| zWWU1Bcy$%HQc9lUexoE2QN)pe1h7KlaqK%`5COP5H}7(RQ2~m)NTZN;5YhRp38uUf z-XVQOkKtzn@074|iqri`iVo85aco?ye|OVZj4n~%8IXdjW0syelLOU&- z&e*E)#EFY@Io&qF7RQdc&a&FuTUK`hG}7T><84aqZVoH>%m85wz4akV=MN0_ZTI6?yZSg3h0Zltav zvh5i~f^u~+H9vZESAv+9fUUJKIAqsFB`#hyzMoP^fn_VA=K3>eTw@T3!kGN=@yoT- z%>aCn^l?_xAEoka@Q|G`;nDU7vXVLQ75DHu|4fWyNv!FL{HY_cD7uIc#y%D=YVuMc zy*t<1HsBD;BxIt40yb6S%DX5*eV0u{l(4|bfnTWe;(SF*Lk!8e{o7D=TiuHeM3mWV(wqUY?f+*H0j2dG_6>9a6Sf9@*1m_%Be8A!sBFii~4?9YOTO!PqwETXXQM?B$E zdMY%te=aIoYjm>g2#$7z#8*?|Vsn#?&11ZtNRN1h7eG6oU4MBUr1YYjsZ&9ks-N?B zE2N~P>>Tsgk1deIyI`VD+ZD7ON=}*Dt3^Teoi#|)MP<&190n~6j9^Rh&J(~}InP`+yM)e$p2(RV)2hOjbUp}* zj8bzd=(*`XRm#~xwc)ro`&DvLq?Oy zBcsd}RU|&+<@J}Nv9uMzM9`hFdKmuBR!$5pxi>WAH0HZQt1`a;fJ_jAWCs>%#R9y-(wnR zIw5W{U2{Efo=<>DEN;o1k&Qi()6j=#UjLOG++7==?LqLgN>tic4I z$;-y~haQ2T^!6Nz-42HI;NaD^ts`@;&dwjLS&J}4Q_>Dgv66(&_}I-bsGU_AC{>V` zPZBf!6_`YlDD&fK`NvqyuHtI_qepv%x>`O}oWtofq+$8y6i028O<&nL=<2}+0L04sj0Lx@KtrC_r|K4AU(M3dM!DoJ z`^;(f(zf^lBNLO)fP;T*4JmN#6rT0j&Tik2PwUFO9I7PK{G< z-uyg(az3Gm=xgSHwbB(P#|!;hOKxLm?`8 z@V;FAK7mP^`O;-O3+{N@tHD9{==$B8Q!3t?oe_uskc(=apk%30C-Frs6lPWxY1DaE zIBF~Y-Wi_h6&kVfeJ>`&QxG3;v2Bvv6pjzeC9Ya z;p(V^A~Y+KDwTvt{`nj>mrBc_!0w=?QzrbEvhmnL&(%7y$`uSXVO_f5+QbtX$xcrB z^&)qh<({-&wddWgbuEA?R{2@u!#&nnae&yO{mK~=b}H~}*nXpi-5R?OSiT9DI)R=~PumZ>Jw_(rFXDOY|+3V^_r1=d!# zlUGs)f_$4aci zr>S>SykkvDtD;g!wVB&S4swE{_g_3~t4`F0l4iPe844oWd3W=@_fk`D7PYkC%oA3lxy$$WSU0Z4=_sJc?2PdN zBt9=&Q}xM7hzZ0165B()LyBHo-{PTI!FK-RJ_)5gw;iCmdmGIC%osC({En5}00}u0iXMAzE?wB8Zq$!IB=ozM_qE|Letmdk4m+92&YZk28mT9Wq zn#z!zNaV$U)Xm5qO`I8Ota3_Sv^fZP*cT(7@Wcx8{!P)0`+2ljPOLr(V4989P9dzo zvq@4nHKIk_;@vEE0x46yvjF=g@`i9ws@UV zH=^MHrei*MhWrH3F>b}O@xKuCJ2$dx!I*Vw{rv5}M% z4ZH1>&Al95t9Uo@SFa;6wb0shjyO< z*IvN*GK}DA(v2m&9)tov4p;6wHZuPZNSFa2@l=?4(%3&8%OVIihJ?}Hdy1EtBe$}S zkr2^-`v$L$DJ0F&e8YdFo=7v*P}F4O1Q!g`9JTY>OFin34FaxM6U()%Nz6$Zd;YU} zA$^zn?%n&EueM*}xT>ve9RdHr~up0rLu&=nCK~xfTxKkj8I#~#sFb0c&Psp z(pnR7IAnA?xxM}74F#nMK1evf>Uw?Y@smvoR8BO>>YK{D=c!9ho(E?%&G>=F9cI+Z z&#a#g++!6L7HsvDmT<=T*rWH7E}ci!qU=yttf87FyCq^x$&XN_ty{sVD9e;ltX@1| zWj%94hGF^%;G&u{D zUgW>={T$i1xVt~lnW*ay*ef{m`*~UQY^)JV3(cvYYi&yo0wfL-`+z)eC}X+DV}u{C zaEuYaVOe%Qqb5wI1BPE-&?T>5=;lzr%F6CId}l4QoRgrZ(*T&q8suH9Rpzeamu@AsTcT!%g8?>+5B=_8(!5(IvnCF%5(b_YUh0l=4D?T`8Re7iNO z<4kUWNR{67%**UhNLer@&j92jP_b7;+QNf?p3r!OoVPp0#||;aVr&7K>dA-_^qf-! z@NEUTQE`KJHmmUbp(_&wj>pKA~3x` z21@e19V23l-&A>TYenl&<6X<117^!tOXGEbWp8VtJy z?|HnZi9g!A7-5dEMiEwAqlXpJ#v@HaW?ww0v=Fw^l46$BcB?cGqCD&Oq6Wl6kB>8t z^{OgsJC?s;GN!;82->?Zl)2y!48lca@c1=R)L5caGii>v;47{4n~frsVJ)Qch6)fkv=62q!mIb$CHP)KTv$`!~ z&|DDWV=boNE6)0LT{V1DBr{aQAA(%&$E==mMdepB8Rswh`RStoQQ<@N#yuxMQnhA+ zzXtf#g!^&_0S7qrlFwcgb8_-{S?pk{n)z;kyh4=}w*dbEe+&iCXk`GG&Yy7yyT_x1 znQY(W7%5;zf@5w@JauhhHG)XOAmEsa3D`{^m?xO#lE=rRd#m7HP$B@U09H$3UB!M5 zAb=d&A?*RrKD)s4ShMgmpX04TMM;SD7$H;E5}) z5k$<)4R_agz%6|lMj@YgJqFm+PocOiN=n`wMoW7$pY_VX3ARK8@`qMNURt*K2Pl>a zcSMSd?Jenj0jha(U<0OHWX1-)p$A;OWQwx3wtjULlplmfnL?OXR+dk3mxtee3l3yE z0TooO1mCp|K7@KY8f`6xGk7bkJ}yU%X8f!qgAz7`4S)d#Ww2q;V8u9y%euiyxV)I5 zI)gR;tcA75CDm*4kq>$YG!_u{FlY-{SO0PM1)d)ECmS5@6O!Wg%>pba+z=yghZIeS2B+beBm8@t`2SD9J5wwYcu@Iroqr)=B4<|TiMwZAdOsp9*Dxf zVciNekB%e-05|lger@#W8BIhOdy0(r{T%giHG6ft6cBNPxbE6*g4sY{ky-50PTtfS z9!Pub)m7gE4yf7I}pG#hhrKg1U zVET(gB1xW!7F0Vm0tGm?zPROuCE(vMgF-{6S>d@Hg?g#ED>`Q4XVhJ3@QnA@P^pr- z$vIODLi#ea6jC0NO{lDmdJSLq(&$?yngQ$o(Y;er-kZbtLCcV^jFNIK+F55hpEOyW zXJDE1Z&YvIyLWH<>Bnr`SPw|y%o~AJuHV#)X6;xg0}$W4#=l?w7r8{lsF%KcB$};B;%xon2;s7nCwa!f2=7vTMG_Z;DMmuXJ8Ic4cf;+F2EJX zrgO~Bb|IKq631PdoTKCLAZEZwbcbKb_Dp}kSy7VwZP2E{`nUd&2w z1mF*Au7xS=?ts7XSi}{Zz6980G7E)2wk8FB(PRiY!EXva@d!AIJJU$=XNdFR7U{Ic zzFlymCNGCM4OC=_(=F)?NVbe(5epD$>GQQ6^AJ!dC0-Hr0^`(v&~QmuCY_}vGh5@g z7Oen){+yx3>Uf&e8H!+)$2MX(1S1px#TcR+7K%1lAViqd>?ak5th(M$H@4mqnRYR- zqC239AN9-0iL*15Jx|XGw^I2j)86b915l0tYItssN6&x`xQrM52%d($G8L@e>nnwy~W2?h>b+ zDtAxM=10$Dueml8)D!gs4Z6_uwh79Q_Yise!+%{fKbpHjo zvyihT-5=u4#|qRxNwGSDaypfgc>Q|v9w+OxypQ-R$Dy`o?)hLU=b@c`FJ2Y(VH>cj zDk`6Jw~7RUaX^{y!$54V`!FX@p!-x}0r4I18TaWFt|bMI*I0DS&z}uDy+7mRLsx=6 z$vA-lHne2HT!H6pd4&_g5980^HPQk~c-dq}zepC)h>NFuTl;p@Brqb)V@=xpt$!qi z)iwS)R>NF@kudo=4o<*#U?a`PfL8$#dx3E z3oGDIJH{uR#CyN?K`6I#LxxB9!$PC)if+Qr2UgsVQKI+mJCFf;jJ1_wOPk8wID5hv zj1bXkf)fQffAh&Gyv#3?SCb)9{pK4S~WZ zF=u>)b16HwfA_5>uMN0dN2z2<)(Nr?e^glh-jmPluXkuoCZ-r3J7(XtR^SYLrMf=9 zTaFllb4Jw-yZ@?i_D{C)Y3)07V8aK#fX(>#-v7gDT-n42)z|LO)w~xkue;h|6o68q zz3a-E{jvtjapd27=MM@EVrD|*o^Dz%Op}wF*WLH;dSPGR!i%|IXOz+hME?$$o{|t zCO@k3re>oPp$|qPo?ttUY zZHPX)59XBlR0iw(a`NH1vT<*0;8vC2wg#NIn{g6?VltVHz;CBbT-|U3Lc;=djwVNX z-MF#o;)M%eplrU`S}`2w9J$U9-)Kxj)drP^;>O8Z0fn)3dEzV8u7F?YO*}2zD?$%V zd8+mVW3t>&$meRl-z!huXG*jmx=!o7^fPd~2qG>hY0@cuo0qJ1_L_jW?MPwLUS|+8 zIUDVy{lrJ@-$cnPQ2D(9`+a$~VnTO1kTnJ;>4D_k@sjS=5W6D#PyVGF)eW4Oc zT_4U zKc+1HM?t6R1@E!vuKV-fGknA1iF3i%cF-Et&7!^nK=8@l+Jwk!F#n4eM_!1!jR;>z zMi)SGNWyAk9thsXFxEYuW@NnWFG)^a*k5SS0Eiw`-uAHU&a(&9*4I63E7X}h7-M^?kt|Z^+Wzuf4 zF)4Beu!3@iNV4Vo!{c-NC&=ZGK)1P0m-7!)Hx7TxP||d4{osef+)G?Ib#*oFPw7<2 z=Ea)6)n5`Dlk?q)_@+Wj;(DQeu$6Un^;Sa^1U1mdC<~T3UEl7WI9KG{xkIX()n++F z?AiQ7NzPvo7#nUsFwtabm+{hnqm{M4WDI~|fv*6g1E!}zc3 zo!x)&fg_Mq{OdYtXRtruT+8HjgZ`R?6n3AIqT+3P|7zF2KB`xX>`yM(v~1e6>7l!> z!e4&!8>c%;N=nSl9eTekL-!;4vIWY2XX8o);{txalG$B4)<1b#G&oJoaT+E-)AltZ&T<(uW zP!#dXQ#cUE19FrXFJAl-ESKutw-XajVBHl%4gd3S@+Dw%{O^(d2Uyv5a!pVNND9F6 zy=Px$QUY4D0Z zCsA5rAGP{<5w}gW&DzJ&!N~!}Hxq0zX%ub8x*zX<`a{x_ryg%BwrrN)a>DRJ^wv_r zjlb(v$nUJU*l>G9>Gsy%ii?}IIj!$refqaeHOkHdRqaoIxLbHz?dMU0Mw1q7Y+ZdF zt98#SmHQ5M-HrL#?EHlEP7k6dEJCtp&sW+<7K$W`!|)H}g3aVF+-&b zI|T*b_B_4UL3W3q&UF$qsOK^*Gs#z>G^fQF%U#$0zJEeyR%Abv3%8efnMNMyPlkJX zDh4!DtUcKL)_t+)nv|Ev^^zVtndoGw|ADp%E0UD z=`QYRcls&SCU#`st;SMa)Z$yIJ;z8W;10s{Px&bv47$P zgB*Ife-(01{Ue$-bus+AlL?I*4PF2AphVVDwfpy`AFz5HhAHqCkuuYCBC$@HonGi0 zmf0{9NVM$kazAc&+1VXTN&2cvQVJ_6XYFglK|+kxBH^*Vz5Hl?jQ4g+jRvHbtkDgp z4`-k1-&<(a$?VcjX7{!(Y-o(lC)2i69Z=M9cHSw%LpOJD7KEEw9vZ1ub~JP@Gy4PQ z&~3{HWov!C(Q+5cTh~7i-*#}=k^W;flJy4W^!Gr=WtKoBYimQ<^aUknhpht%l`iF% zj_$@__bt5PnOgmu_b_G3A@_xn>f(u2dCq_Pw8nR7K+vPRXKsm*>|QgWcU zE@xts+2#8N2((;z%Es!W6%`8-FPz?+zRR{bh|S+QIyG}u_Jq(_7Z>Qv!mZ>iFW;kq z;fGqhK#|z;WtW||s<(~g2qR@rkzH}2e9QVqA^Q#xt>5dxy4gS_ufy3dK<5}&z37@u zu+^5$;)c0k{VD5wW>9cUON}9BzBipJwIwD<_mhD8>wl9QWVaqpGcDk!1O+)A(nuZ1_2w@!zLTRh%l5TvEC*X@ zPoyeZ=PQP4T=%T31)*+j3#JQ!#Kb^UL7?D+TB}U;--*ZGf8$fd$C}6dYtCD5#~Qqm z-A6I`nQv)1tB-ayKo`0oxHa3GAr*3{izP%kOkO;3UpQ+9BcG=m-J{;B;(U$xO zlIiOr8*4eSy`8TUJz1wM2jQ~;YFvXKlG&bN!>)U=#l_<9YzGD!jZ0^y z$-6YcF>D&Pb+{MCcXSF;2(7%qPd8KzAEcYg8YBB~A$6kC27$mSo7)5dda&O3ZI7PbRN05(+O zhQTs6*2#r48^c+9x;s)GN4Oz)GbG#CuL)cJ{fTmuZ)8hf_M&ofldb=ceOtU`z4bqU z&85V=!27*fn3%S)!+hPnh1lOuH(&lo!!jJX9hR1ip6Sy`c(DDlYaq=MY5KPz0QBG-SzjQ$>|&TA)!#27VA z4>i#Bq)#ouo>kk^B9Q_O=JdOXHZC|7t({J5ZhmnFGiFuh=`OvMAVVv!VLrF{NH&vX zoulvMz5co3^gJklZGN0)(LGsKHut=^m<-Qvnm(^p$D65RFWlnW0DIHF+Ue_gPuVl! zDH)b;rmmH$xmhtS#B}wCEU7@sD4vGi96SX^kfa(DL( z;fV!9+kC0H!My(d%INtZCEQ4WcjpR&XQR3D?RLvUvF2^ECJyT#kuCmf$V&mSS`^O* zTR-ua|7dROXRTGH@;4G-^p_*Y;h9Zz`UIK$2IF($`WZEg-Dq3H8oM4jAG{bj;Sl*#amHS91^96_Hf&cM*rj86eOB5|2+-|%>&ld}4UytR?lou(v6DuoKz{3Yn z#r}fI)VlJh0&+jiCkk=9@gfp~fsK|hqH7^b=mIn4G43pMvQtEaQQ25g`E-;ol3GCg z)YgKBHZ)>%^!K~M#B0J8x)v+FV`0+^R>*>wm26`{tI)8(Ec^gn`1VE+y-%eXqah52 zk#8j{*T+q^Pdo40(!culMo3y`p=}NPAtP;%b*2x~vv!w~#R8t46JoZIr^|_NZZ;wa ze{d(@`X<5enzDyzu5OT>nxZ#}dv49;<>@+ks9UKO&{2~KhU`3Z^Tj0v`h2xw4|`@N z$yMe6^?J)(MLDCu!7UBNQP!^BE{h`cL#NF*5=zxf-c7zdY7xp9a>7fvTKRWv>Kjw` zI_%)H^~-#gyP>J2>eqL)%U_t=>RVSq&;XN3bc@|fBS=C6LqfnN$+6C)vitN9tEg(X zF)a)DSA^9J6b7quYH9fT2WOnNH00z9!0fFNQo$dVxn34mcnSeNsTCdYeT<@zFbN9lEQ^NV?vK2!G93JFyn&G-q_BA zPyLSgt-kffg4=okLcC>YNDaRaY)M4P4-CaeU)$Us%(-2}f)^-y*@ zB|yP=tIv&p$cw)l&;IValpC8KQ1W8Gk#IFv?U2Y19F!2O`|Ms%X|J`H{)4Rz$KY`X zSHyjCRhp9J%H`24$s;luud&rSD3~qCgAImt7hv&tbyZbl5TTYpSA5}rG>suyf(_tm zYj+i_zD7mRa?Qhw+tz9(2L%1m5~1?Ib@vpiq#xA`ibh5ZeO-1c4I*GLvbrxNLb$4} zoC6z}M8k4ZFlM0)odOdxGtS~HGw+y6kvm;Ho6-@iZUhX_=xP(+a80w7yS&YoN>ND} z9lgb8ul6*e^&Q5S!}T0iSLJ3!+T3u$BL(W9-PvI zMOh71a--v#uzhSVm+c)KLBIQ{BS*;i+*5mlG`1#o#$r1s^YSk096Sg%M$<4;`OTYy zpl%-7^tJ?~ZK--dnt`rIotkpu<665LFTaqlN7dKXeyWv^#t5_b|s7Se>l=ORwM5h$MSqng&$?M4VVt8PwaoHPkWx+h;a zjSiBeTZompV$Id(#&hCfNaV{Pg~S>$cag^t_8V!WR^?V- z5mSg#PBRL~u-4NSz4WJK9&5wJzW`osfI*?@Tjj-50^hRz-5nFlt0+VjWN9#24<;m~ znX6>!hP)8QG7v9@b}J3WI>iA2M!)cUW+!c?=^}r$jXByu(g@+D+nRNn5y1>75G@ui z36yu1$B5$v^r7ICENF}g{a!4aut%~K5?4bO(*qD%n$iHxyGl5LSr9mu8L^!qeVNuD zH!>O%Crz{n<_*DyR5V3XiDOm(r)V5`PPT|$!Y+5Aq?3Gi4cHLe7blJ91oOZ~#PwV) zlvY4`Sjrxf1`%UhKme(Kwa9k$B#!q`VIXd)y(pRq(ZJ0FNNtG%1rG^FlE?_MfQ_+~ z5Rj@%OZ}X7oI&;;doh3;I(FHhTEa9Xv2_UtcY&$9(}ow!F=HdB{BC+cb3HndXb#+HKD6&VD=q zwdJD<1wpaCq9k!iQ892me?*e%(~J%z$E_qJ4G92C8SZWOeIK%VF9<};Je|51AsTap ztS}tb=t#U#J?CNt7zSart%k8P2H{D)yNy&B35OxmG(7O5z4dhNg)Au^!Ne3+n*{P& z#z>EeB6S_L=VMNyi8REEHln~*@`bLudkC*huOPX=RAP^`EjYHJv7HDIruCi~tN`XZ z6!y=Kr){cX?2OGp=2b6x6~FUejK%M36B}k|Uq$7zsT5U3^d=PK3V_$DOp_Z+RlabT zrMH<<1xj6?r5DFEvXC#GheAt_(SMmFG3z&#RnbJQRhyVilDiIOs&Bdp88c*jw$4&w zfWamjCD*)LJ+|!P1qF3T%+b_xioEARy@MWMf!=UovCtK4RIA0*kdV38&COQG#Kw)& z`P6*g3k}xW-=HSLS>%lT!Y+R5?b04Kb0;OG%rH1aseVwq4UjQup0%O-4UI4ZrXap# zih%#3?ly!c+-VGqjQoaO$4WIME-thU@ft_;35!h6xg`r;3-L4WkRHXZL7r(rS&Cpe z-L}aq&5S;&xw_mDeMHwCw>^zb=y^4I95;7ego>dbv51XY87jvJN1xgZ@oa38!$|7B z{c&+Y2g@zbE3d79^p=DafLJxLeN7L?4l~^y?8`NX z9oo@`e?D!dP%7GxNC20Tua)}zv%A}!-s`7ef_HV{rMW27_5$9;2%(P!O7KOQd+gpmujXN0`7SRH~$W6sHdk1C-Zd6 zJyVrE%`vfLiC};OiUB&hho}qvG);SlNTcQA@^ZDn3}1AMKlzK2w74m87#h?<9PyAS zd#;=`BGoh7c@mZR9 zyv2~XJZfWi$`fU`-O?SkS(!F=pA&~6YiqZ0=|h5dZx+OyX$V7H5*2})qZGSq?ZxXbCt*8!#) zFxu)%=?ck%ZmKWGnw{?CQW7-+c{8mUn;O=8LQWC+yXa9lLdAOhH?>Jnm*!U&-cC;J zu?90Iz216eR~l&u9*IYsw%dN+7mbUW_0!SSeSu|Y{>>t!;%!75WbnP=)=ty1LJS zR_SqAX{uN+>TuCB#zi8TEa(qR0W%*GgNGv9s%l58X+5b?aBuH#Za3NqT$Od*19>)j zdC3K4f;c4UaoCf?yB6!?r165+lRb`L%G&)a>}%_c753Wt2&(dT@-H98&IFi+O7Wc{ z!x9$S*A|BI$aKZ5zxB1o)5m&{L=N^L}PGA{XFec~b@@l0LQQg{>g}LPGTS&%3Rp6Nz;U-QjEN))Rxxy=0MId%c z`3R9k4OXQgB9Dg}RaYXItvM0p{|e(Y#S+aZQVpi29@zP5XUH*CKfW7Ls7KO^U)~gC z9zSzg4N?=+O_NL|TJ%o9%oi`Y9p z>1rAC04h#A&oo~BrrHFll5`(op&EEVEJmC$P}V0zb+c^qjXNfYQV2rtjGAWvZ$Ps) zWTjtAhE-g<^Sje!!%JQ7zTadNuH9VFNd~j$AF4+{=BJDcYzE0oz+UrfnIJOt6t1FR7E+E>0fB+k zfW0HjDKqVR7pP~zyLY3PvJilGi>fm%FMiGfU`4$*I=9Jv^_>EPF=2}sb*hUKE?D8> z*coj6QGplL491WRu^f)@KY2Addewqy2*wIEYYE*RM8H+5NM{SAoot+i_?7bGUbB#k z7e9p?LO2>U+BEy1X@UP6J7<1K-X1d5<&BJ5*YNmG>`b9xexsqu( z4=RdLUv;-m)oB_XJud6hP_K=SzJ{r|aU!cK4nc&wdlI|UW7$~cy6g6UF$A{aIPPeS zdu?>%m@VfU~pn9bgeR z1<~pB)Vz`BkfkAx_=`0(2^MvGNWXNOR?oA6z_UFbu>2I@Y7Yfbc&3?IY3_Wc8YJpy z(j!0K39z1edV2Z?w>109`x{i@Ki;sFp__CXWr6tmyUM-yvJF96ZF?3>SxY6wiV5_W zvA;BxJ=JN89uNznMAn%*|CJnrm%@m=rfDAN;vlGpOX{s#-yzYn)fQX;pL%)2;Zfqn zelnSCsA)miiLZ)lWXYn};S`WA{`Ik;s{U5nc39HqoVveWhLhz=@!y@n^qUqsDK#8> zz$o>R>&dTYo2tqh8H)ao=>{F@KWJGK@XNs)#Oe0;@>ZCKJ{9LtgCSdi%GHemns!p~ zS?6%7>;cz=Z>wc7m+af$_RYXhop^Q_8t+{^wv_1s*MlN9(8C7`8?a*Vd3Zhye&x#B z36NX>=fT7ns5sdTO>P5Fr4#T*uL3L)!#LbIxM$-MxeJm7*;8d7u9+P0M)#p5)JOLDr`1?PveDw|A^5jhNFI~#}i}cRD z_XpWN2DT?#Lp4Q~lSSXroRFZP>J%pJ5radzp#Sy`#lY@Q+^R0pU{lL5Ic78{f9b{e?bGhyy$!%!I)~_PttE1i8s!BMQWs@Lu3^<4R!KxsU zI-Uc?4t&?Shql;i&{}l|Le1Y2jRu_A9ioX6TR%s4um*yiJPD3u1M+60(^F)5l;!{R zE&uEFHxHMMC4FbBpzv+B-1=;jlUx1J9h`m+j8c#}@w3y7EA;A|x7gi(&|SajN7-P= z$w6%h%1e&W1auC~Z+G9w7k~9~TZmjqaWV2;9HH>aw;|ANyMJ%2e)DiJi0?fgcRxzb zu`>=ziyYJ;2LuHL{r%{_4fTJ&988Y)?hw5Pjy|{GcQgqZWU9gFdKMLY`Y*P@J=xTf zCj@%5dBdUS|HOQGZ|V)Zj+0Ne6rI|e{y({NKk{`+@h6H#f3efcEkSoZdGvw$U%u{l z;|ihu6L7wDXzg#;*Y9*XSdp?h4^F{6__JaI0E3MO|Kfnv?u57Q~z6On(sGiCQLC6#JYu%;4#2ry>M$)QQ4#)n*Zj0XIkrmx}G~r0j+mIesXzZWa***-0 zS>{{1OwR3ed0kuFA9Q#~)+M!ghsYs*ihb^Wp<5kCH|&f zgB+Pdg`54E)zmOoDnu0bEk#JF-Z@#ahk(3}T*EH%k&1}hE+cE1C;e&oD3x`w;M+SbgKcoYT%|hVDTKzjyTWEvEun?Cp`*srk=_l zlYjmjld&-|-nS(~F*zgN^FC&x)wWKC=tm?iwd{K$BDSl$c``~Qb$M(d_L%1T2K>Z- zCe0esEs!Bh*GdLdQZJci^P5v{kL_aXZXZ}|)YWvkH}EoaMTP2F+cjCGGfF{gZK9Vt z=@qZm$qb`0ZcD?cPpyZ-1%TbZXk6WGc}R7XHYI3vcHV$UmmW-UI&8eA9%NJB(MNO&gj@@OY$ z-^l(h`w;FR^F?VvAx{aD+~MN*F>Y;}cagz{ml&m=ed-NS6g0Q~lk( zq*C>$Zoe3O>m>|LyTt3QMaBHZ`wBm$GW2E7lzo`o0KR{2LzlLRp(+k`7_3v49nAsB znm+H~u`!0zN5$^32R&SnJYCIM{)g`l%kuaa6ceuOW!AHQ{ji7G$;Bq*J*+}7B3zYxSdo%0X!+;;0v?)ceyVT$r zrRvRMIBwjKNxVJy)SdzQ7j4%WmZ113+-cm{p zIb77Q(QrG#;@n?gO39}7weMGMd}r^_J;{y!{4uwdd?P_GscNHf4Q`Fmhu6YxK=)|6 zaPjS=DRtAivm4ex_7;CY65rS-?{0~~KZk+S6RzH~P6ug!nIQySNqSQJFADDLj!J!J z|L{!j5B6`w#b2M_4)2QHR`Kj%amkSL#*&-wvD2L5CD+~giaOvY*-s()G7r zS>Ctypk8(P#cpoWC!-eRe386q6a2Z&GLs_LKHz71*Z=U9@Xi4z_7X3{lD;H&{A8W= zuQ$GSxk?8aj2_%lcc9qi$f+aSORnTxSKKb-G~2sI7Uf^|1E(f!%U)-yYy! z&&P8w6AMuLlBNVcd+sJ3R{DV~L$5FB%8L^gW6{sDfx+_%_pOb6qM4r1{mf_4!EWb9 z*F3RvaF``G>`rM0tO)-#V8+??|Fm=UK}nx+9Iqy|I99i&X=*QbZY~kbd`Z*1t4Qra zb6uKF(`=@qg}F!pMa!G6Fuh)k(uoth+{{60hVM|OrW~mwR1DK}fP{+T3%Y#SPcQh< zojCW`wyu939-e!i=llM9zI>k#zku-4dc+;*sdkV%bx#YlED3(JNfd=Hr$5U_Q0%1y zgZ|Mt)lUdzOri@DQM45_49>@8wlvJ<*M;-dp(cSe8;`*S*+)*k9GXsk2$roR^oTTJ zDNrM>zKw58JJ{dZDZ&Cbu5Aj_@UUn2Ry79@PPWewfK2HWrD0W*Ukw zq7mEd1e#m{ToCX#U}NjBWHPhcxbF3CW&EQQ-@uO1<^|M2{(Z|pgxzc$ za1ss$1+ZU;fx1oyi}kR4{VZ+gftipc9S%g~Yxsdcz>J22I>PjQimiAdDJ;ylC>idY zUPrC029yBYpiS5fsOSg^xbAdAzsDfV?F^%E$0Js1cdto z^#e95tWrO#h$gAt({9^4D3u)VmN>luH=DG}!Q)-z%t+!ee6rVckQbQ}G-TzzmhE1+ z9-Q^AmsHW5QkQ4${myTu9BWRXf?s82N*iu?u-Ii4eDUizlhO$p-R}y+vEnLk$9a-v zo0u=9R1Yw|zA{_(xWpO61eG6qA(#vQx#{kk7=D)^3Yec+lXs8Gc%~u^7*O>C zk(BKvhrLY49E`6rI#KF~?6smt{36SQb{QF{Hb;HAIUBe>y{AVnJk4_kP^vYNN<>Bx ztLMSdjnHhOtz08Sw53pS@je_@XPohsx1iLlvMnxNK(1^6>Zepv=!OxWC37!tBlQM2 zZvqWZIC--5w{IK(;BP<&&;DB>!;xfbjPFQmc*{Hny1K}zBzez7CRB;Q^*;$-FC4y1 zpt<>N4BXq948@_rvm57!z7|U;9*8A4Gb;0ZdDcZa06v0uq{vb~7_$Hf*1+o^)dDe; zI6#Dc&+dSiFI+JXWR~vS2Y~JTBzmk*1KPEL4*Lf`dtUu%(i`n|dIITQfPuMpGHqj6pa@^dErV z)V=>Zvz*lsj{Aow7+|1}E(5Xt_x-zUnx9B(kw&n4yc+w@Ipug#TSChGG4cswSCl>f zLRF4Lm~N0;fI9Ri{jQEiyK0Lq>ru40&PO}iCMG1*n9zFqWU>e%nS9~L=a_M{N6<}O zO;Cr8yH>&y{aI?=>r@3B7HowJAd^T{)d|zPZn11oZUbB>*-iI8H^(Q+-g_1ei=}!+ zJXO$zs%`+<)^i-*&P$q-GEO9rsH2FQ+3W^GsE@rIpy>hxlStD9NF)s;t?mVH k)n-UN`Z1mQAacJqsI@b$Azd)6^@3qN(4Gev`+_h11)OXXhX4Qo literal 0 HcmV?d00001 diff --git a/docs/my-website/img/ui_spend_logs_settings.png b/docs/my-website/img/ui_spend_logs_settings.png new file mode 100644 index 0000000000000000000000000000000000000000..334f5b1d93e3122b075fe985650cf68d329442c8 GIT binary patch literal 351095 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%0AOv1E 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 z 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 EeSD1$*n??R#k(OaUoVYCR*d+(xm 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

    rloJO`@dX7HG|vPBlzCG%+jw-ni0qau8|AZ3<%z zYpAJOc568?5odbu@g3jMjVR0)L({U8q0^&?NQHL?Ol<67J6ap>RJw29yTq#{Qo%QN zT8kl9YtYb{K&(9oNJGJ9WMrk0j-ru0PJ6V!k-!ES^Vz(Lu$!pge1kM!4!So@Epq_7 zgd^Q^$Sd#e$Of$txm>CXD#!DU>cL#nQAKLiSUe442Vx_ZG;*kQ8>arnXi;>nh!pzW zc&QgyQ?A{qW<{!OkrUF(UheU88GOO!di|~HxFIW)-}AjL^YKSJ(tPTeE(pOh&b{2x zEQ)GDq?XNuohpD2Ujqyy5AO|>a}z72r1^n|<{Y9^M%YCtnBxK%$IfM$Bi&|tcE7_ zXE$DF5}m6IW=i>>WZt+Yvfp#DZmpBzHCKrYIfgQI@0gsi?P$yzuDWKn-g1z5Vx-si z1VVp)5VBQwuWbVEkBYR>y1}0rbM9sHZ!J^FT;l;at=73^yI#J_u zWPJWIx-mVL;kFkScgKya;Wy>8r_*f?3s~o=1AHpXw%&(TDCghL)@gT#+$+p^Mk8{a zLsl|84%%XmU}b8_3tSaVa_&$w_f6#jqkrD@S){fbPZZqb*ZiZvu_%RiTJP;`Q0`7S zC&b3@I2RXNlk1H#w%r^og@k0=IJHVpDiujapN?T-~KS<~f4}zbhPcnl1_B@f}qpZbYvUhVA7MmJ`)A z_g}*%j>KWzs%XvcUyT^DcDJc3;vdPcJhKY73*)_azxGpI8e4nb9un`C2w~_!BB7l8 z__}wkWjl)}hpEqF(m>J7_(45fJOv*QW=?{Y-FCHO<6(v64Ln5a$2-WFgc?O25ZZ1KF8fj7mN<=c3wPPGa_bPazRrDzY-Vj#zj`n9>d*3CLb0T#CrK2ffI*j*mwoEx7J=#4UCq7nq&+hh&?}p?HKOIw!F1?!(Ae+33 zVz`9o$?Zx>QpHB=Dt%2078%9k(Jt~Glds*imY@5vhygtJe%7E#PV{TW*RWHqc&3;N z;CRRg!Y1%#{gsQJb?Zv(VoY)Nak6B$wniDAg1{&y+ZW|UVLtmsVzGg_iVW6ZO@&}* zL%|v(Z9x*9*?FZSsIb`vCl4|gZA98f=;~VUHmW zcLf>LRfJPas58n`hiMMRX^#>$jxwBKuZT$O!I-?Rnp@8>>Jmbu*$@*!nd~cwq@F9L z5Sr^4!Z8z2>>W%w=MKi0dGuTflG@er!L9$g#<}Dk=PMw8=7hxF-+9oLR$TxC5#O-v z%**KgrE^!{Fh4k_wa0UF1W%vVE-c%-+-8+T1&ZhL!==YL>I;^Z_{<2*L9`2&S66J; zS5~QHWWZdI$2`HbC{;1=Yo$D8E){0^6t-2NEp_i9Goq&a(9nbPRiGBFqkeT{p&|F= zh-ipjBK|Rf8B`#e9_5#~j^YE6+~OFC*K1sX=#RfdsGmZ9=~N(-n(xj3aj_+E9JATA zxr`X0bk`$l1`Hv?x>YN(`RWM?rcQcR9pu6%eE&h$!6xNR3MG2n3`AL_}1&l!!?0 zT@qSoQIHmTCsdVA=!6zXa^K*deP;G{#(U1}b=SIUoxfZ}4d3^^zh0iFU}3uY&{Oi_ zT>QD3p4!-~rPI8dt6-$498^YH(N?{-DRZKhxO{ZgpgV4BQ92je^Nd>eOyQ-uM?M#e z27zuIbhCiK1aSa4=ai_vO_YEig9z>|KVc*)>hDVwEM(eAEBaD{hPSeW>)K* zuQiLkUZwQDnh!sGv|}^-6tyI-8&Xqc5;PpF3#^}r097yJKnI@!?U5+NbDtwrFlTz6 z($Vt6{e@rRhv`4O2PGHlYc+QW>8m%V%A=l~FI?JsM^qKwYiXGeW`jHlHaQ!6_-S8H zBMqf$!bkh7ydt1?v>wIo%74X$P>xYMT5qs6J27B47(-f<*5(zZ_{ z=RxE1_2NE)L2@)LRF&~<$CNiQCrS3)ujJCy+%a17%q~7lf>d5K&6@uy!h5!0ROMb% zTbSU{q{=oOPEE6^=cETpquI{%I9$EF>sMRW!R;r7>(V^$Z>yB(_u9f$3|(xePyCZ5+@5 zZe&|`Rhf}8b9u6$7bGuB+n*{flPAbT-9v3}aOGq;< zK$*bhdZJZg>EpiePif0z_1oCS?zR@O(V@{Y-Z`gK@HI~y!OVca+69h5@x#;@=ddsM zE{%i5J+ z59u#f=nvs{yiYl*@Qb$_I?gaIymu1h>y5vAz=7QZ|FG6^=S#T8g;J`&(XpM|tV!l+#J4AwynXdSHzj63A392j3*24T{!|(lk^~&#aypVT3B2~p-JaE^d z*-RU2gKT;I)xx$bX>ed>yL_)snS-5Af&#txXaC5_#Vcg;C5seYeRuryS26CJq?IPY z?O&XqsI)AhzUG1`Ajs@dkDmv>Zb{yu!Ok&<8`0A1{Wbo1W3cK=3c%mZt!EN;(&P+y z`0@sIXPHt!ZRMgA%@y9;CvzC~nNRG;RBe7cn0`7giZbTc$U*(N?lHF2wLJ1sg`w%e zu9?RkAm-B}=(Vf5z5CT=CKWCahaS+zB(*KkqAY&3SF>*GF&7PLF?$8F9vDgMfl5Bx zsM&DbXs5&>of+gJzy$2*%tOucAIQq*hz*%_Juy-SMlNE____}{&zA2EU3Tm1eXP55 z`zs4o@3&R*OnUsf@UPZ#5999@PZf)V-_`fM2~_A#{9Hx8S%PZ5aX_^R0jmkg{(8vh zd-#<~(#z0!DCCE^6F@3eUWCvp^4`x#&8@%O<8=|rAiMK{%wj03kF&m3VmxeHGCY)( zoi`hwhmbcV?BiIwJ1SOlDc*%=>LdcB?v^ti4^K4H+oQ6kKKx5iw)|u_?x; zU6pF9zAiam7~X|e<|71v2)&|13<~CNne0kRRByG+R=@PReNv$8WyV1jLV7^;yB;}} zliYWQDOu1!yX`Y)pUVcvg*pWzudKf0k31Bit8b;xZ10@S05s9aIZ>T?9QsuS_C~{# z_m-h^Z>=@|yF?Y0zC6S5`K_Q1Z>A+4FwOMk=|IPuD_!r3tzQTko<6kCa{dT+mo@HL zx_~zKQ;QN7txA4ElXHuKc5bTnK2z4kRPPE54CMG;(@N~ibR|RWXBGr?M!$@>ZNMO| z4Pxs~y!ls~WqZAu4R`#jZE4bvH@%BKXU!sI!QyI>NattF2Wp4@|EL`v2MAd}fuk!$ zv5!cqC)^hjs^<;jVY7Rz)36d%YN;-pOY>6)7t;-gg2c!*v75xLPr5xE>p?^?`P0^0 zGrsaUocwtET{zm1xm4 zQ@O~Dn-}$*4UvDqw1N=jyOg#9Iy3X;SBq@*aI3Xonr}Y4TMQOrZTTW13JmGVqy{YO z6%V`Ub9k=%QIWCpt2qLP2Bw9B%z8SGLf)4U*RV?Q{P+=Dq}$Bf9Z;uOFSm#~fvPp` zvsH!i%JPGeHl#j%mVIVI{Ij#PfzuW3b83W>)+JosPIr9H`*|Jb?f`L|YFo*w@-eS3Dc$y$dE0zrb#nlOMXKoc-!VaEVb@HC>2neUikl zic*phqo^eaP{FUuEvrT;eMKPrqsHLZQa9A8>hNx8F#ITb9bLT00QR#gL;TksE`9k# zf+gLeVLpERgm$~K%B*&&OPr<0RpdiZh#0VJdK7;IPt>?(RJrj&ScJzUZ7fF-!Ycv! zrdy2>_pg@I<>}9RT3B^qT3UWlmNwQepp}t7Q49H-mRhh$?;wfFhrc~2=U%A9=%4Di*GU?7P`Uwi_L9^=@)EvOSShBn%BGZm|51S zQx9G%Ye-RKe$ytk+WZZ&seeEr)v(3n z3_7(*4})p2NK}Yz&h-RUTSJFWRHWj|x}QX-%339GeB{n|i8b=SfKy2w)SfNANKkR{ z+ovu$FM{*Bg^Xczr9U-&wwi*)dfcMh52`QH^-jR*o^j@c9JZ@XYHFcs%1d^x1zlV) z_-IvaXp7Su*YvJvj^=`QdZS`kjD2NSkeel`Hir}K_JOTzF4XMWWohiQhgFGG6ZZ^D z4MS`>a(|78rvI*=gE#WZJBsb9Kex?gE_~g#gJ$QJIToChDJA>dUigr#&aG|xVZ_0; zZ@!PPg0rufC>|hX>$CBET$}bKa}TA>WG_nAUSK-)Uih25Ya_>G+szchV!ZQv$$9?^ z1VhZP5nI%>WVIO4VY*QVeX$~ku->RC+J`7pTB22ctpF2l?fAQwqmqoF1s_XC%CA_5 zn4Rg!xd?!3pIpYrUTIRc;$sr|U`u<_;(UP&jIjCPgPC-vq6PnQ?aUp4UTd>UenmwV zlNdVD{*V5{7c}DLe5K2{?FWy`Bd~3r6VJ%UEszIWvpRE+Ur|x1c+O0!VZq>~l}irw zOdaDV#O3LG&aOVIa&6RL&b0--w=5(}EB?N){)}j-1U0QQ@43{rn|j{{=PjxGz^X!n zJ+go|FnRD`T`>u~ZjDLp)k!jzv38_)Zc zF|+#5W~p}?u9ckWPHKM|98UUht=PU|`NriB`STM18sKA$jo6Tb0$GpX9vWMHm)2l< zb&4|QvJ;oR%zY?D(YOPZ&>p!S897Yfxv06B51MkbtL1(VlI>vF&Lr824~LDP#8IAf zYrUunOw2rC-kS03*?wwJ^f|F$Nz|40fhF~WmOD(OlO1=w5wmMWZmTU~%&yocV?nS% z?IW#28D&w`5L*^_wUekZvic6;UEj8!-Aslp(?}1l&PS+xEuq-9pq9z2j@$1Rzowr0 z;mq7c#dVocP|<_EDUpB3U&&xC^);z&n;@bTvFwM~3sO~L0 zZto2WDYxwo& z(ESh>$8ry`RUe()$64D~-U~JJnfQ->vSKNI^ zWefehwd{$OhL4b;Tvx56VY}>DpzsbAJp=R31)cJVaJy@12b1)iA8&&QVvaOmOggCit@w&SC~$S+*}*9YUe|NEb_kHJPuP{2Ry9WX zIbdA|?Xb_sK_|Z4dsvK|G(<@nMF*XeA023R>Gn$z_d7UI>AqgbgAVbl%@N`o9CJ`h zXim8wBbN!HdNPtsOx+%vST(o4<5v5y348}s;cI19$?ICvq0*4ysKI&cxr8g@KeceDr#^MRceVaNrZ6GSyd?jw?&3@t@v+J$ ztNCMsOd^^OlU00*n1 zb?*f;Q+3;%@4aM^Ogzq43dU#x&TMU>czETCM!mKf6*!AShQGC7zUm3LOgn`I!%lJ^ zG`Blwp7?7#1T%JrQhDq^rDCJ_Ucv_i=`;Gp!v=KD+=5ZxewsW4BEAwirP-o;=?*We zPJLr}TpemWivrA1L;(Gow#?m}{EPbL$mxUZye&+glmViL^;4*4-n6~X+X7(6_jlOt zsey=2zqMkLjVZv9k**h(wC+uj>*LQoKfJCbcU(tP^V(2R@q#u^+toT-JJ+zNyk{Yo z_^L-1tZWn&#T08jR+mqg3y6)RerLux(q#fqaG8qsa?= z4lW~_GyXM&L0+*aGlH=CJ}XucWM5qc$NDO_-3uXh_zRPH=5(9)UYUIb6H)q;+2KR= zo);9W9-~JTgq3E!*>HkPGugS;3)6j}AZsvxN%9!zMITJmESF?U_v-x+Ed_o5czftW zw3qD`GHzzM;z!$t156-YEvEBO)&%)0F6Iu8kC2!}DueY7O&#IsG=?`n(Ck}#w!=PC z3CXwAEh5+~+Pb zN6K>Z+Y1Q4T<5qsoef|=VIT503^~rf2ZPuAr}8Y~%WRtMy;j*^Y{c{4=KQeZ(CHm5 zjE@leWIWkjzx5IEjv1#IOBiJD>cMzPl@|%vT^$A4mCms ztOvI1ZCx=r%#f&uD1RrKIr5(955y;XO;Mr#tx3tkoYOW66%yw)RTFGl-;0g!qdAHF zcD-z89BUrOvprs|M{shfs|itTs*(}2hP}Pdw$bM29rt8>Vd#z@PN7yW{Y;Y+y5b>L zC0BCn*o8)=5Yo%JvZj=wwr@j6ZG}k}n8};z{Ld^G9O3S&fXYGP?Ohky54AZ~66IL) zHgV1kCs@L%Q!qM1w;m{{9iU$pujPwGRXCfEiGv2q>Yllsgc0v#q|pqzd_louI91jf z%5;J}`jGs@dytOR;-W@^6XTg82NhVf^jqR6P?F4LKAHL`T!B^Qf}*XcAY z657o8JB`;sJ2v^NC2SEbay*$~({@X7Q1Zqx;*KIyfN zj8|M7=9(z1mOoUSW7gfUrK0eZ0G)_aakXB$Ro~Y;RYJjfD-&FJEaCCKRXrs>z}B)g z-*bYu6^mHyedZxy{hp-S{w1k3XT0_NBxniFI-^JI>CDRn>8NMkl(JGkt}*Go0k7-H ze|+$pWhHtirHDVR0^|a#vLSf*g&medYox&($MpA(%J@N-M3SVVp?jbgyG4J4kyR#y zQ(~}xluX*lkT~wp16gQ~cUwoM^~*)Kh@>LDwwmf)$IZjGoxJiZ*AY#xl!<|sO{=qQ zh;~7yqN^XV?eX@6QAP>lgzd@{S)Da=-}MPWrEGuuobB7q=-Y%*Zvt|la+Dx!=q)gf zK0+AvUC5U9L6mM!ulI~>5Tw({H*mc8#fa8~^h8bAOka=}@wpNE7OSH-6Aq1Cw}%g+ z<1~EjNE?|0vuH~~XS6zWOWC7#zAGNWI@e7WOv;VEmCK?0qqx2o#PfO}uCJ#2Ir=`2 zK7t+EeH-P!VcV9M#DI0aaR^5ILL>XK`j!M|lnmHriYX3EY>k>AF(KHvoqRP2A3!0C zA<&Cck0W9F&1o!vt(Qj@fH%)2Rh*D;w)mt8)gPm$qQj3KB)UR; ze_=h;ZrUy}mMZIuAEkxe%o*!#J6Y^`-yAD*a4x>1q88SvuL7%{AdS!RX38 zGDKIyw#<6mRK9#)u5jqAbo-KjD^J9_N`3xAIilDtS^eTEYxzOkW5Sht)9&F0@!QbU z_zis3b9oJtIR0wo1&OgN(w%4gx28*)FvUgzRH9d2PWwyXC5Es_`9zg+Z6eD9NWN9_ zX#Tw0OrJl=vvWHa!s$+yTtSRUI@%0fW=9EBQWK53qdC^Bv){t?GG3h;jLUC)L|B+^ zJ4-(49-m*nP?n)*u3N|wLUi#fUNcV;Fg0l2=&Y;a@XNclfrC=iiP)umx=5P1X^Wo< zGO|}PB2;!W$|{6=(P1}U=*_fkwYQxtHzLZk>r9UZm%?V##^zq*N(yfOpm{i~1)Qd9 zC2|;F1}5363Jlq~tzmUhSHP-K6yW*b&+5h=aMo=qHY%&iYvoQ91QQQrC4>Mx6Mba3 zSA`?#J(zIkH$!yN(1RT#OBsAhJ>hFwF`>)<6iJoEwy4KF*036KHa&Me^(c4O^2|Q< z*aC%p2DX*-(s<1dSyB_jFlu}4;adTnB;?J_N!EKv7G?oZUlfI_A-Tkdy@(qG%`9Gy zXRa;@%^YZSWo>`UO4C}=1vY*5PJP-@H7kRC+M2orwTnJ?@QGz?Wudyleg<{#>wsAr%XCe| z7P)0+JYM5JCzyOAs8AuvYo}_+)?+bhKW4?V!VQuDeBdYPHM}Znp~O2vpS)$CZFM`& zW%kudCQ?RVFle+`u@apKiD%{BZdoLq3z6LlL`%LCUuqVXIUg@IA2HNc(2KOo!w?U# ziaoA5YE))3nR*x9mlf~tgrqg{nz(|xG#`*_cCL#{U}L~ZY->`i>I5vmNTQTY=P);K zWlVO{Cqr-ITE|EErk%)%B(8?SClWf=EhVI{+&By*=u zYvrTdPVkqi67<0#%5KvNg=5*~_@V{;L4A_G*p>Y&wNO_)4LO=jKxX1n=Al{wv25_HYwro1xf)hR?Ed zKZJX{7vN7yJZI>VRjG2ftbXJ9hJ=3f$T7Ob8Cb_e+P5gi$7&)eDOJ8S3@+J{^Oftl z5aF`pxAax`paYHXgC#(VYgqRSrdaY0QoPiR#-ZFo31qlZN$ zZVc1+U^i_c1~co4Dy`G&-I;fD1K#iq<1{&m$)m?um3wFsD>{#4WM)QE9b|4ys#Qrz zsyNH+K6G7_U7DxF(?F|ZpI()xAD=|{2n`lEGzC6m;3cwToZ zW>C_}{zJHn_Ux1g(f(O*g~*|FGXJM+995b|ynu%E+C6*#512A?>Wly2A9qdGSic{6 ze=pIVk1ldq{$G@M_s4`2pzKg<(IE=;;RAyfZLzm=gfB}vxgc6kGQ-Yj&h8xs`n}E+ z=eXz#NgqsRK(`hs&K3cdR+|Qd%hipXZE4m?k9(#TqKZ z&yV}b73^g#^qPuwO@iELUn(NK@h81`=@oCC5y%W+xn!;J=B=RLaA28*mg@(y^2^>~ zIp8nEN?%FO97UYsMtY<+)t0K%-G>!`PzcMSV$S4YG{D(z2=4)H7yZ6(z5hlQ1KY`t3=Ric%KVCr@=kccFM-8Iiel>|PVYv?kkrrvL_kh^&627q4s z>tmP!6MHJ;QKV5yzd8J zoqH;P3e?KBPW47xUF7A(y&=tj>s3}sK%sQC`*One5Vu`09}Ceb`Hu((iq>#Dw(Rk{ z3SQ1M$gEQa6S;Clk*EfEu*$eIJ1gnw0HYpn=3vc(-{1eg-UdbO683|ejQptf;K2N` z`xVa7&2iE?>%-<7>+G5)+3yC5?(~%3abL^gUb*>>^QjyY6)P)is-0ma9ygeN_l5nn z)N`?n6w?fuvd8uBNKgzhw7;sfbDqLc@uPV1U*ro5h&iHKwtG31h(kH>)U2!$8!r5;5OTf`0cT0PVQ=j}pH> z8ky5EK=2W6qwYf)&|8S|1vkz0?RF!S=|xF%Hwlj03Wu*0StpTn*E{L5+3m{47xE5?gQ+gGte_J1=4cFy~-Yy}h|zCR*vOH!Xa!&so*t zk67XQwgx3c(ds1%P#5-M!93?}3SV}_zZ~tq|0pL4woy!1tJX4Zx9NYl2^O)n#2v`6 z6a+#CZdCr_EJe;{Whzs3^7Ev@CkJA|jmPX|RX0Ss7}|+e&l-3Y=CrufE&xW2f_l_^ zzzk=1Z~kh@nt@lWo&T(j!T;n@-rlx;JC~Ur4`-OGj5)+1YLaYFB9O0pw_KZ`t#gED zko(*F{XRh#5*{K3u0T8Pz!@21k=?05BYSmjEB_`Wpyg}U>N!mN?VSc!nJSl^?2_KU_nrH-5J?NNcj z6a@GNlq4io({vtzOVRmF5ZGDMi;wT;6iNEQ z{>6jcz#zElvRhG|r^zYTWb;IcI3F&EJ9|UYevQRwcb6{EJ z-pI&EA&UpzESDd>4a7P};1~N!D*at7CFzv9#Y86MF9toj_DVqd3`KI%4_Is>D=MUz zsdF3r68G9CT&mhHU)Oe`_vipCSu-KqG z+`6lW?E-ra@)Tu1W(#`# zo5&V^7ulEp1ChOKMR^LJqsM?6ym|PauzqzJyHpFxssM%e){EbS>F-z-w&d?jN0$N1 z0vC(+W!Uop0+du7+>+lluNiCTKOew5EJLB#{&5iZd(#n0VY0wh*az-1X(W6G^ih!} z_x}@S=zkCLe-HAH5{Y2t)<>AU5)l<0FunVOV$|>D5&&3A>pj3vcihp{y~xPO*b$FF zAR1~gcRpT>5w+EGx_IdMskL;YM0&u@meU~AX#;y%D0rw-Bp{;%Zg zKZ+0k@EHP-p7F(O1l|6Q%KjFxp1nDg{ZA)6x$A`UYW^$Z{A0}Zhwu9S=nWK#GMf>x z8{c`<9swwDWuct^JxEG4^S=lAzY_W5xbgoJ)s{2cYdX43Vu(TBYW^PLxId$q3UPNKLXMqrK~1u`b~O5k=D&TjNEdP~UCmz}`WQTnj6^*Dl0446hzumxpZu3VgzA zQf7WW-0vVWW^;K=#q@39PwPg9Z>oVL%L@PgJN2dHulF@CHeO-uq47b`-;OB&1zm~j z4cGp~tNh!K?JZP7$GFQk$!fOZFl90HWrI_cDurC}e!h$dlWw2D`!T~*ZBx+0R%5#c zP~B!X3(;S39N;3ieb2}DnsPy{PaOJLbtB>>Fo#D`M-J`2+Udc)O!t%>8I4Zy6=#3h zs-Ty*{hnes?$$d0jvH86c~D^ZhljTN@25_Gj54F(Gy(HGPh|3aHQB!BrjGxEeEg68 zuR`j+jn1c4H}>ZP-}8j<-8|vHS6ujGcs()k=}|dtTpR9mcinC@96R)P{S}`e1Jj_^ zlBW?(qUd)&J^r`z3OBb8{dv!(RwWJ;E$&ofN*j@ z=JolpU%n@ldKB8xwYc@ZC*aig`Tp?KRkTG#L~a@y8amr&Wo4xR7N8FQCX-4>k)J(5 z3V%YI&b(U!b@QCtgMl%}HjRYlo-hCCw6Grsu_+Bh2>t4xD`a269eyyT&pUe`{B{I7 zti2$6jQeMqumcqy_2u0a>!hh?fcEEN^sVy;B2cccHojXoXHAf5vP(%xH2=9w7%Zm> zSZ8uDt@$Rk-mKf@J^n!e_fH4q{^Dut{j_5m(<#Tw&Y2Ae?lrX>-P0OEse`riQ{v{p8adAP4fXS6ml9)FE ze_^-#s_w`h>h0-KL_ch=;OU9qVCE3pvh zQSOsn97TJ7tt9`HCGT_@iJsnCqb&LELGC>P3 z>o!$%L*cV>_=esx{aX<5PriijIc0nD{#hJOF&;hJ31{0@Ot3KyaFW@r3)x+y$OQA$ zpSzhnhfHMnj3eCC)YOlyYwyo;B=?A?GX6cLg3u^L5Mb5oZT!Z7#>?s=?Q(4-Od?cI zN+yPTq)YDp?mLVWAzA(l-@$8;r&pv*edvT{=^6e%ImdrKP=XR|%9?7JobCm0)UX3N z-o%h7fpr{@a$FkUCxLP;+uf(-*4nKx#s7u0R8L?cc})R0tRGwF&tEr1POt4a032U| zv-u#@sZ%MhcA9fHF*NuKN9)(LG2)>?4X|$in{Rf68)u@q`z}IfcT?oGKUZg+QdL|H zRPhUg%+UZ>L*I0&#D*@fzJ<7HV8c?kweD*9axb?3d@2vGlyARK2^DT0%cp(XO z?z&p#$}jL7o$CcgAwgIKd4od$8Zr7s}R@;N_cIk-O|ZWpZq{ z@Zby>xFb!T|D{}8!ZvxOg!F#jN&HcBLhj-LaM_oUU>fxaaok#?F-HvYrfx&wkvnHo z6UKDAr8s=Je(`+dqJPC4wIIwsdT70WzGL#X$H_Ci+WDdIsY;$EZ2{zYA?SG1;IwUR)`T6DRN3lKY}I0A+0dc~rZ1TUAlziH**KG% zY_j~ib_KJeGZDM7527D3phy3{5JDoAfx(?QL#+P#iIVcbX1_KoTyF!Xpe)Z~PX$}i zs0&^cL00F$aAeyr#JHg=3O0i>>>8dX3SD+Jl6LvDqPVgXBY3lApr=EI*u)SqOqibE zif(rqbm`;1nkJmlVz`nfwT>C$Ez$+dxP*D$@IkY2yJ=KdeNJ1Wdb zw(do5w@v#F>8|L{>RVr4@kbSR`wxJR77+*+)9y*ma`y8v^z7`F8~aQzOa(*X#X0si zp|a8rP9imL1r*#mtLmrFqC>{pYctIA+1&XJix6N{hop#tTN7f#a=M0gzc$Mbk<&Bg zKNlP`iJ#xVaHQKCxoXN1ro> ze(;_Q0_UIXVwYeO|EovS=av$C=#G<9JiOjqmZitr6MZ+OEK9x%b(G! zDvt)}=`w6(9tmSD1K3TwL&LPycH3E@-@7`+f8}va^*BhhA%TAx$H=uYX4oHfv%DSN zY&dd`xA&&NzRn@1%dblv4fiHdFJr~mJ4J6_eNL|ddO5EUfk zC>I^ba__wFcGQG60vF9xYXrS3;JRUFi){AJ*JaALih`A7g%&7$9 zpYm@$O^rvF~VGx}2G!sMLz=1%&U#&_4XL& zI08k>5XZ-18lfvIQ3vx!fa5{fOdv8^{9VPbi*i)y;$?0n*esW;jIY;|dnVnk@VRv1 z(Kn6nDs$GY&C$hjp$hVaWZH^6UV0s%!RPoqQSpO>B(1-b}JI!{dgIwNt9`sj4@`AMD?lInAyTLWLw@nuSrXkpn|kI!G!OwtREnT~%6LzPhptp-hvSH9awf)T*z ztA2sIqQHJv>%x3jQ+upywTzJ2bQ&VfE4QN3CQeFxd&cwdd^k<)`AQ!5iS4huvsZGW zyA9sLza3Iat9&Wer9%v#pAZsZ_WIW2wztXYOr=hgNaJP6H@-6&R^J1daI-MU_$o<` zlLr$SST5a@DVqwrbK59vG*(2jx4r)`RgHG8ZscMYO!&j%M}tc_x2<}63lBtC#%!C7 z2-@>`)!2^A5*JXZ;uof(rpLeQo%F7lqW%M7x=B1n=h*+8_TmMN-b}Hoi||DhF7?LN z_yQ;#)5ZXsVMC2^#IO#zZ>B0u6j9~hy6a?jS%0dirye#}55!fbfJ;AxIB^%@&@B%>kOFJlVKEH^bd-aSQs z;!d|vWD(Rl=`wE4vc^$3GZK!Z&L7sG=-BD(-E7Y5=P`zEX)Pd`VV{31W^YvW3BWAG zdbjOHl>f|8|EtXKz4((2#=6QDT90t6m{BgMnFjNw!08_~x>!z1epmUI>GAgrLa(^} zG#0Er$ri}zHdUUz5bONLrpgVc3n9A1qK4bsPlFQ8ZMc?}*7H@#V|&!taXQhG05{66RSeMupd3k+2lx|SS>;6egxch(1CvJsU0y*l)gqJYn9 z{-QAVRVbIi+fS)D(T}?El-qciryo^4pF!FnEBD~rd}gC3*IK1F45B4n?VJYyMVwAu z5L?M*|Bt9UN81l44&#~x(FVb?q|JyM)s}x+k1C6iyGVl>(I4h6vSu@Fcxu#{ZbhVh zqSkUYU{sfR_(eyJhbwmezSbajwv#tuJu~zgkypYw?hb&D7tYhSLvcm^dQ9RuT#_5a z3_w}j>#x#(zcM8dA**Y3*aP!AxBPoo|6i5;-^?pczn{ixc)&a!b{$^gjN7>sy@33v z>(O>wRgySB01R=0%!bal18TsWvE<}iV-vv77>n{=EBZNl-Cw0_o^C_fuY`ePCJNijCXWsSvTO8R*eEjM#$q+YPM+elGS{0uy17h4m6jl9s1yh|E<>eikuux}V$mNuZ>~Z^y~}XAf#}2ej}ui=ibf zm^q$d&1f`FNYTYep|z5sWQ_=GxC;`&fEPuTv^`43=$gkeLI<$`n$T$q10k(2M@xGYclY!(mOdUE z2LyV-^0t5ms=Q)nrAf$TuHWL7GPw*NM*>KH0Z|_eDxMS^;*gQjt6l~$SrHFwUKCk) zwOr|YNu6O=wPof;8M7x+wlBNQ04G?OFTF&D;`X|Y6ySSV0pG2xr4{M9900L_z6_qH z>>BVF_n)ODZ_#;y53hf!1gq;Bl+BzSJGwQ%8D^M7v@X?LtOW!ryc7nS@o@X;%4lJ8 zb&>2L6~W4L2buU@4yeDsdV77?6~ZEHR#Vu|tDO@HArtz-N}>n@0L#dvO?o46B%ACq zXnTQl$dp1YD7E>;cLn^CMt}p)1pxSt7IiIe%=Yj~^pdmtt=#}mQL^=7S9*xW%Vhwy z6a~Jw;jV@>BaGbupb(HQ_WvoNZ3CF!M=Yt~@&IOpo_>K+$vo=qJn!GQ*)K6vBGBsWa_>|71#oVY_%#w?K(bWp@^-g!_RIM<&0sgFU4)UhI z=aMf+1O-5yf51``mysNdX-nySWNxRhPZ#(fSd}{fXAF8~vPC=9f;ErlA0;lh#=cvx ztelGT81ITFId^XY2#!V%OGVI`I$qitJtA^g)M>JrDUsJu+Zk~5g#`CJz$(eCgiXV2 zZF4>6j5z`Zlo&@KU1@6J$JDPTD&~?-mI5LmfG8|rdE1`Noy2BWdaYXyGE2d>Qd(5- zCeG@h-zYFh7b+&qB?Smz{5N)touz=(L`T5kCH*~N)*XlA_$RVd-o$||@-3MwHW?7% zAFjjiRw@vJ?SPj)QPzTgRAu>+M0*)9*7u8`2c-wX(2@LX)T1qs7a$ zCtD%BoGPtxl{1M6b566KO0Y(66tJ1{luI?BG5DI75jv{uM*yum_$pBC#vCkx8?g%I6^nLr4k^8q}r zydUnRHsiJi@SAlM_lV&X%m+F@##D-MoB9akc6}T)m)*u%YyiGEPC(iFO5P$K5O=Kh zB^Y;NYncQLPDD?au0gxh#2P`U!(Q2k?xrk+vr4-=AEzEP30E8c>dz5erJlr&LdSWo zG+Ho;v2TE_yAv&%>p%)c26A7B$`ti@4Y6QyLiRC8U0TsAcgfuWOjgbQc!7$g>YX(U zeY$SwxE?^Nh;;&s+i%!i`mcd*z-eIF;JNR>kn@jw0;g;C-r8rnA-$frv!r8BLq$Jg z{K0ekZp^Lx*9Z$T!WB1xWJU`F!1Jwps*);H^U5ArV=V`(wqUiO#JalavEmD;T=Q4htzQg5`D&p}Ic@l~#-sJWu9U!qBPk6{VTG-jL(4R4XA?rs~Ph!BI6N$!fX~X40GRGhNBXP%9Hx)J;RyWk8J*vJRV8;CO8|`C4>@ zjC*Sw{K|~nNrF=6qzJ}dqmbaj0T$HQH-Y*L6&oY>nbfTXXkC#YY({0vHg*|Tf+psw zMhkJh^HXyztv3K@PAjN3n84`Bl@6AAk(1hnhV1iwEY;5XaB2!Rm+5qS;?@|2cS})4 z$*Q)f&xnoxp_L@`*LHT-q00l^EHmn0Rcw$k(ko#OxX^-rp~QhNKq7;pXnP7Qr&Zv` zigc9nYw02^xtA#1Hv!R;Io^Ld%jjcs@Z7Hv(%}b9V9P|gUYT%eOaTpjWEG6ZQP`{U zf_Igx?*7%S@Ec9_GE!3R_IobV+05iX#zga@;G_3B#fA#5j~E#+w`jVIbuYVVT93xD zV9Z)FBiqzskFk498~ILTFwjK~JvUw{8M3ipJ=FDa06IFXT^BTetVp+_!bqLin~lh; zx>bBai_83ELJq(s!393&l9|ONEMzXT1K){XkKSm zAW~sd;LrHhUR50gKoG^F>)7Jrl8H-eFr6w1NzJrtmt!8{0Lz)W zmka*}tC82%gy&`>T|}-f^2T}9w}-FvnDRhT%H%Z=(+i<@#=u%%@1xAdR{98C2jvMh00)Q3)e*XZ=eE$}>-D zt<$O_0qfeP%MnpFnIAA-=PejX>S1(T4Jiq~M(iV#XWat@qPKG6(V6xh*Aw^v0~&;t zr^_L_3Xi%qkiQ#&s%*D9dF^%(?9S}ARmXnTJt+-CeZ6!r&~E_eZ$S(?#J_fee{8^? zaPv+d)`a+{uU7(m|BXecfrQ6f0c_?Op!tE1z#=D71cbXF z0%Pm3)LaM@ZhbjY?mZLukSVPT+wQDgSZqWF-XwRqT>cVBVu>}`+vVGLr^h7LhgMr}heLym9- znl(pPs!{Kyxv6CL*hHJvFs+VlAW@=Gqh8(yUQ9xg9f1aNb1|r={bn)u8r9%A@q5%j z6P!y>7CLuRe5|3D%&}QtwUyPZf2YqFNFq|mn~TF~sNZx~4Q=NnTefzcMOo_ZxWN0L z)msKwqqp~98T>6D*o;)TbtJ~v7VF;A3sn^pU8k7^I)y^&Z3(|rWZou0Ae+_9*WotK z`%KIBCrLI%4a(q+%-ayUmCoMgG#FMkw&bPzYHC3Su(ck?&qtJAz8H5Fy`=BB3Mk{l zDID=VUWR3kARX*DWR+jB^4fE=hJ)Tv{=>p}xQ1OpN?I#@+Sfu5sJ#1zFu?l)-I4g+J@HVWsup5lY;ZY$D?8mv<6T3bf;je3*SxQ?lQ{3zZfF=?iMXf9&?0VQ8mNW`UkJom?w(H8UnpQP$J(Xq{EELJ}qBr zI$HWIz{45_(AGe(*-V#A#jwI7ST=oDoQUsQEsAC5?g~Z|dh#&<8^(2O{DSO7+09Q3 zN!DG~g-)Pkf)1JG-|y1M1%D~flxf_7>i_}KZooTIG9^ftXZchOvcUI{Q~=m=!(B(* zE@X||_d%TNlTTY@=kQFd1d6e5@E9i=2Ye;VhC}8v=6DQ8Agl}kzy7)_kkTMk);=-W^B%(UFSZ;9eVfXm3C)IKO8MI<;p(r?-@W8p*z`ZMMzY=}^K{`e zd?v-ssq2Z0&|)Eev2_X(3xI}H1@Kj}WB{klb!Hgn2v>79=kDU-lU50#MsG&7fE`-2~3QaF5g`DCa-5%LUV(GdOc;ZZ@$42l# z{}uZjIbEfEMH;XeTxPoSXTw9z9zMa;7*^JpiKiNEuRTed-eaZ#9XNQV%|TW3v)c`B zf;Y-TBpu$q2W15oWFvu0Hh|X;2{JEr>L~3sU88Gp{9@kA;f>Qc-|)eOufUDqYTh(N zWj&D{-+wPy`oVo?yN&!d11TjV&&uLKYRu3pMtn3}=I*UAEZlkltwE`j36a%5>@EvD zm7@WbvK}9WRevzH=Lwru`~3vw`+rVg`Za-eC5n^xylNUc=r&J2Ff~2By%<2yoZK`B zT>5qM)vTNxo5!dHRTH}eRkDIY$y#bXcAJT&-sXNOCiW|Dnp?RGzhT)u7r?ZaF|Q^z ze7r>@?W;iL=sF@43*v9?^f-5`dqhf-+z`f@drRhJnhGq=o3yn>C>1MdBq zHrfLBZn-ZYY2T-jN2u%FDkeR zTXICTi3CX=5Lww~=j5om^gpC2A9^-H#cXg|LNdBaWE=`I`s>2BnYDm){33;1y@~-i zPzV_y-zIl=cVACk@5qOsJ@sM9*K&bU2xN6}aYN0-JlH+v3X+6VwtNH!M?3EL8q8jD zTN$@tX+yTjNTPGn*E2!fw_bv7Sp>L%^AmaH3s#TI08pzApb`$Ab_d~}C;(Qp)t~G8 zGT>UZ%5G_66DV&9DsvCIjY7KM0=cN&++3#6j`=1q?W9_yV4!%gFQ`1kuU z_k4ap+P*t!)=s2)vmfI{ZsS^s8SE8&==MEvqQ3EfwLxOc1DR}ko?+1ZrRVls%Xo_) zbmi4z1?xQ78!~Wb{hi2ZcQiNi-tD>WKBqHz1dDP`0KDYWw*w6u+FVS%tvQcbEqT*8 z)+dX+9~;9mKd6J|(m+?})rI6nje2?$$y4NpW-Z50Z6hyR-PSL2PT)C}Zo0WePpeu@ z84}8xqf0~w`iA$KmJB%tObefe&4c6W5KL2I9+tnTVSyfH~ z!RO!Tyz>}%BI1k&g(gEkrV*5Jd017g+N_7m!24-<%AfMyS$^PTcU8*q=P2NUxJBU(ha^+xH*CoSqMg?+|&U!}0 zSKFV%tMm|y&~-2^7Gd8NCZ=QX*l#+=YQ2m^z?q_n>qs@XrB5^yAbe}Er%++HL438U zmZ}dX38w=eX;0x`H34j1p#Wj_`k7J=(vrWB>%x!)86=U4`E9Z6YQ8R%5)o$r)j_60 z+R6XJ-dl!cxpixx1}GpXAc9DD2}p;ebR(s-#0%0Pof47)(jC%`ba%IOmk3IC*O@Qq z+TXXo<{>bw@bIviwJ;u1lJ;C0u3G=;u)H5JG4Q_--?Ji=r*?g6e zn=6S%zS36w-3Bfs;|A?7QtyNlXtI{kQ)*u5&lwW!D6T#vzd)a0^6s%>J{R_IWr2!u z*=rRiy-olq1kh>KF^X~6ic017DT8grmJ{cP2bl!+aF9Y46uLw5)VN;6+38shN?k1b zyk6?G6&n^{@RR@ApgH1Xk2U7hx%|5J+;XV<&O(@dxWGh(^WkjA=^-=D^wX%V?5D6D zlTRWgGo|&#R2EITPD}u`Ll^|@`#*yA=j))lZ8`h-lmOVZI-cGAe8)?kAC%He+MR8x=raUI>VrOLI3x5*=Lbwfku8(bp*S-N<(kbtuUn{}PLD6W*Mx+?W|0#=?$c|!;{5)$U6q&+ceN*Zf9a0 z3uyrquK9V3Kx@45?IQ#W^<}5;A3aGCLiC(*%FzKHj{V8u{@2FNmGL^l23U{O111mF4K952K)lx5#HS zZrW;e-0^~&PO!yKwRRF>VY*I>^ilOBT)@0HX=71WcmR2FL~24#Enc0BWu{urf7&EH z#`*Z+)8zDmkg%#EU5%7e?5@_!e$@Xi%8s?I-YhgWNEZ*+7_1Af5k2E8!a{^rFrfq7 zJ}o)UoZD?gedeLZYT*`>z?1G7JVeXnCW0O7Lq zV2oYpa?DXGrhJrTpG|{d*GAx3L?yl0Q?mbQC{X#3C-M6S&W< zJjllIHz&F(MM3n3_8*-l&r;CIqe0hGgw|{cT;XtuKX*1@Caw;f1(ZvO!^-IF6@I&O z#B&I#m$cBVC6xVUSj}V#$^7^V7ZU4|0Vq2aG@~o?Z%ozh z8rWZ)9R#@Q#Gg!lOI)CsI@?#9SSkqU45Ri9%f$x5$8vIVqwAyQd&@)pqod?!CkNrh z1|uycMh3cy>$T!1`)d=!>->A5h?@IYsfZjQPVZWN&b27}ATY5dDCWEz6j@;iu~%PC zpDWnjo-CTZd8_E)lT|5jfJK#W9{qCKS6ecH;U*-q@{k7ClmfP+;!vhC`D!^mxyh#0o{z;$^kHmFm~1VD>!NCyV@;&Xu-N+ z?suh&&uxry`{2SSDM|LFfL|pQoVxw2>)=8<2Lx0jLXqJmxt6`YQMk zLK~EGe?XJln?#3py;N-G-pRNbt@tK+0w1F+mr8kj49SVeD2UN2-e8u5MUCPuU5Ssn&HQVg9A3Iyopt%~J#% zx;8`zP7MUx>+*!w-2$i1D4pV5^{?l*T@L2?P5&Is%QgKIV)W-7m}#ZH#{=pnAr#Rl zlwx)VOyc`pRisCshab>e!CkK=j=O3lea$ty91FR2V*?C+QNj!V_2B=mkMXA;j_^9& zuw_yZlPy8K1(7ni-mx<|hC{Bh<{>!8(>X?AM;lC?U*x+BHWb|3$6;S2Jin}=Jt(7F zy=sppaDtPpFEvpzj`mYc$@c70P3hrZqn?*J(QldjoG*C7YaRaLbb2u^#HDRfK9*Kf zAmz&xD0!qrb6+sDunVG5U{x%9nVDHl;Xb;uCuCYsV?9*Zvj2Gs_g1fj5|aNtnf`O` zuW&*(+B%okC4I21>atGw|8}vVM~xiTS z)-V2V$^Vd!{;c=#Nv8JU?x!btFEMAXwGIGI`n7IFL zI`f)~9~e+pwJh%h1FlA^vO?d8q{9%N{yW#=*WPK33hU7$elXd&;`Zu9P=xBA00Pu? zk4D^Jr+Y_ZIhOSz&(C)Jv>YyEfx_lWc#@|TY}n8iu99ck%zY&#VEj`` zpnO04I2so0SBB$5!wX@(-wN%iQYQq<039(QDjdtb^6_P$%BD;|c!lDm-NAA9VNI4F z__=MIA1^24v10xzSe%IM4a_|G4sId-vt*h{4J-rxVR5N2#oq=sN%X5 z41Q;$yKK`~UyiUD{91#DD%a4^u*>-gv7AB-&hI9mzu&!aoIOOEn4X?4ycYx)lG4%J zewmn7_+Ca@sb3$FtrQQ?gX#om{nIIA%K`gW6KuaSVIWJVk) z=_ht-EaQz-yy@Ygy-ERp`AGavbTEt@sm>N6VrR07ii!nP=nzur{ce-<1Y501j-IQX z;Squ{FVAkuQz^=ta(r3W$yo<%BdKJXhwnOxHTJNl`ndF(&BV;$B-*>U+@ofxsgI#u z_E>3Q=>7e5!%zV|km8asNZxY&`<(D!-Z7cvLnO~wk>0@gMF=OgM2|1@Uwqbr1yUz4 z34gEj)b&3SpZtyL+o|(@&rL}m%9StH=J*U>u{~qlw2^sXZ+cZMR%uSET1%`{)LW{m zp1o|WNM-u=9-a>w4gSNQ{CI|ld3Y6vI81hV0`tSF$*fAu!*nAeHQ5Spl+4a>SM_t_>k zyT?3Bl;hd%GA!nlOe7?9ebAD)LCo_;cO$!Dt-hE|Q1sv#>Zv=B`5Y!UEzfAUMvM@>k`){kScyLuaB9hmpCl*py0`E$%8 zW8VxLS#_FGfA%_bYw2tCXHOZjfP+yT<#6C zPCP8*H?fXA%non%Kduey`ZxaH;r{>SaMQ3d*(`qhCZ?hB@myCICx-Jg!k?XX9tg6R zmlyJ>ua8gvg{Gz^F+F_&z17+Q7diP37AD3I_rxOtj*`52@+#+Fhspc$FwGbLWdpDZ z;BHJ|W8+*d7Be$5R2-ZfZk1a)p;>=u(a$8V9lYp1T zb5vj_<}@RnKMh=q?7#awl_UL6UG42CrsBfFa1&&x9o7~q0)OPEh+mPtJ#ify8#}If z<>25j3KI$ZoKslYPe->v4E)xDN^ba1hub3gA7*-CkX+p4Dc~T@&rgs3N1hkog~9C_ zVHm+CjH@I*iB9?*Sjb1No0Hs^8&{{t3$-w|hrjm2J9!=vkEy7{GUc;c(zVY&&Kv&G zV_B);X4MRIbW({)Nj+5L1bN5w%tW^Eue|Ov3FuR|8h>rq9cpgzN3R}YVk+*xixK&= zGG{RGDk%3e{$QSC7cYf)QiHKwsa@5i&c%Jmvs?++mxxN#FbXOo1 z==O49vL8OiDM59Ehu@M(613d2PWmX9r`8?S>$gdI`RPknpFY54^F*M;XeL8dcAKtK z;QN@tP-e!(h5JFx^Rj5mh}R|6Lk}6|(Lxk$I*VNHxs{ifvkyTc(M?hR%<${woDzcxC7Rk*O%bnv(iQ@q3A<%dXA8NISCR=S zgod}4?~izrxk%I+21=1R6WNyA=H9)FMb9G`$H}<*?t^ma+K1Aavr%>rdnfzo!5rBM zeNS_(QdHmSXTID+`=0REiP908 zaIQ{%H4KZ11ag#=bhM`RsolXk{d7&9~BPkag zDKWA8*~}O{HbUATVXJHKFy7bJD+vdl7wdn7f5ykF_(Ae(?v(vPG0XIhUXDL%x%lbc zo(we|Z?6Pf3}tWaX4JHmkA%Y`D#$Z|(Z`S%tGNv@=(LJ7vZ<0gwlK(ys*VdV@jK7h zt}oHd*luWz)o7LOuJ*~3zOTg(Sx1|&PH?5yT3x0h6>jxklL)qa2pn4`kIK#!0MH8; zkk&cHweCCWPkD2DpvYabB~00mYe0%O@?FojTQ3PNY(d}4v1bA1k8B;d7+)SiL605_ zbYQScwXeULiW^vUK39lqP}$qd6UFqP8im7Sf3;aG{PQ`EXQqT7^Tt6F{2O12*5wA(b;dl}WceFX!l~F82xb=#GH9tPrKQnfzPx)!_SO5szDE?xrc>et zm5bz#W?1dnI+yyRGck#FqjgNIj_Z|&}zRJljdOba;3s#2{}{dLh+2HR?Vu-+%kV~;g#f$D{p z><8ETeR04WT42#4gEE<^^xc$XQPB>K9QSN>ZV^yMl6f4OR{R!AL*&)W`OWR^i|G=F zlg{{ad^%>{o9Fn|yS1fXXY0{8zg6|=cFyviK z2N)>1(@|l$mMqZQBGn}#yWxFNb6Bwyv@&#&v}ujgJjz2{ck}k$_MR#GugS+)fyw-- z-BHYa8w6c+JLKerPmU*fEa;Ofi!PjpzY$9QVaCHs1UHNOxL=KwlvLq(O9mW*ugF8E zGXu0Q!;CBDDD|c?-wn0gf}zb2{osmb2M|(yNS;xchc(AuKw#?|IT^*uc@j z4QKP%iDWar^2N-~WDnGck0H&A3>UTn{jp5sjJw-T9}urEvRxh45)qKGyF2k4=H8?k>nbm z%k1?;N?CkF`;Eq4$j0wCi`Qfr{+QIohC6g+=0i%6rwgUS94CUk>#EwT5zj>< zP)#g1o|kZ+of@nt72g^Z1fgl6UyPO z$nt7dO9DzfdgzWjYxfGYmqe%dGex|(V*47DO7&?*)EsT;2;vk?@@}Gp)b|`CMSo3_ zswonmzVNKfKBydO_xpqo-CNfA9|3HiguJn;boy-6PAdM*J~CUDGoB;)L**LH;?pNa zI^KM~etv<0L>{6eG&mIGRJ^U$LUwi)cHASIoPi8~XwN;uH$GOR2?MuAQ`#B=M~#fda2uWQrwl3Lh^*vQf_?95oW0&g{%b)>xL>#3A{LYP@5e zl$s9`1tMMNySlpQB~)j2qP=Cn7^bG9p*!AndnGv=Pho4VUOW;Bxj|}m*e`7?Ge>Qx zab?}DOtxvLR8eqh(WQpL)-xAHqjKN&2GXqWJz!dL;3RwcR0!PURj#>XFq}<*>>nv3 zBtUf%E86Vsr9BM;VI6*`Ib58<^?w|u4n_cB+J5WuT#5_1AI1Oob5Vf z<&bpRJXX@v!%Igt^_)CQA#XtITMVZhxNKItdtUGmm1mchzqSy=tAw4nRDQ%zS#KgW z#bB43KSMd++)vFzSYw_RH)}weaBblPPojihJ*fwiw9x}`aHr;Y(O$8{A#~cq#-QQw zj>XrfaK)y6Nwdeb2iDf>=*B=;)D1^cmm>vUa}7JLWy$Ti;ZCRbGuhx8OKNy}kqrPnR708@YE#;SZF4f!c1#BToHA&H%$zo&(mJGdG)*d=cX1R5 zd}1c!xN#%hzrhnwFcgwJdiN) z!S3dsY?;r#@)5PfBr6UvMI;mr!DRT>kCV*n84$r#Q@H^Po9= z=JU?xw(s4ww!@q8OPhW)hrS)o~(N=+0pQK6NSDW9py*TSl-8}++}r7 z6h1(#-|-Yx5#^0+`LahMhj(7Z-pu=l0Pqt5a;CixS_teBYFk%Vh~^ech>R3s$w@TLB6G-1^zciTMtY9lntzc=h8tH;Md3KVg9 z6#-K8-_>4|Hp17J?L_aEi)w1=7ZwoG%7}uwOGSrI7@8xmw}xM59%f~v+*$lWJ+)a` z$Un8c@WQq^%KW^%`JKc&O2W~icpb%g=Zlq_N+H`*_ZYP68%Pwm6PGNPCZ9)TqD6Oy zzM|sdnts&S;^7y)&=^3b>}WHyyE-i|&}{G+0D$Kxjy`E#R1(r2ZMN~wbOIAg$%&Mz zmdzw#5{9fJk*~F^c*mqk-eMWuuhW=H)>SJa%%5^-lj~p_V)b1yZa*beF z1L{s+|Cc*8N=wLg4@fIA1t2@rAh$|R2&O5NXNWFL3Wg+#)3&a3DRQqE zRn3m`FDq4TI7M+qyVCoLFJXNIdOQ_E*aUy*KFNN#PgpfruWQ8;0>+BP`Z=hPMg{wW z0R5ML+1{#H6O#@L52qbS?F|y}4Od>7Vy0A*_Hd+8E2tWTGZc2T+25>Z_lfeRUsxnN zX@~}3KhrUbhR9sf*uK^csp#k^=3P%V;!8$NoFV}c}TJyjYn@N zW>Chiuv;(Vf6Gc&E|!9C#&>n`(qF6a>&d8?$}u3r%t2;`bjJZgoU-r5Vb@GhJlM`M=?o-_Lo2ND&k{PIp+xxh61sTSB_tljxFmOqwsD`2&hD82gx!o%Q!H6@3 z#S-$U48%*Ex1d_^ZdxwnwfHjZQ8Wckcfe{1DpRQ4K|s(RirOoX^ln-mDN+nDII*U5 zOy*R=gfaAr^Eir(RMg3omjX#BrKaLMYGBLz1Vay>I7Wkg4ohF~qUvTn`8wx+M`Y6Q2La@a8bJ19~#TCUngon&~E*K9*imzviZ!P zmTOPGTqB(xh$r7lJ#sVcmJiQd$tn*W-UemBlBVGN{QQ2w6c46gsWX$_0R?C254a_| z0lC{dRqvEwBzPk?s0e^das0~@QMF(9jV$eW(iGwH z)$A6ivrgu?&(*$!eCmo$=!(?YZdu9WuwFIc(Zv5%So^?)%th5eNx?lFHA@!3eCi|LDN;--Re^gkBBB^<+hp3rS*n<$GV~B6n#oRYLW8X!Ig%K8Y0~U(gA^1Dc zIN`WI50Al%$#1if>18@mx>4j%b#c$S+(*F|=OGpW!K)@^m(|IudRwXyMhHQ}Oqp-X zOXHf8bFu=hG%|rV^MZQ|saUxsEvdaR1_DP|_UGhF(>aAC%nT*Zqn%%R9ZZFoIBR!^ z`D*^J9T0Nv4Ssn0xe#*L#oWq8^Vs9Q2oLhFStd2%@>wHGH2h|fHl3ExAjsEBbp8xt~_nCa+5PpWZuL4qzP+}Sq)jlu)mO2u^|e?)Chx8Z)QTN9EHF{>C4N?KKLEmWht(G3?m-H(QiG5m2@qu7d#X}Qzi z(aX3DE}4ATf0*UMQT>I@-9&n;UVCf4D#)qGpML3GXKrq8iAHO0*Y^AyVz36^t!2)q zvf|J12R`vS^1@(F7L}&O^H`fFvfJO4#FIO4)z^SjhijqO1unroM&PpBK{sDxk#nRrW~^3V!tu0S%~Kplktx7 z;alM?Ly}k7>a}sJ6V^OV5zGTo_sqo7{MW?%q&>XxoR3qom1{-v>WlC4YJ<6+5NQM7 z2S<0@SX8XvIn8K3MRTP;94^zi=@QZP$(1&B)&!v`)0o1t8+-U83DjV1=6$X4-h9quj@I$_@~ScZuwT4r6&Aa#n47WWcWQ+2i5oa5|!n28*ik?s$og}m#v z{YdtQ!HfjjbPe*ZCe!Rs7IFE8TBw};)`*|zMDN&etin+UJ|De_cB;~{kLCMX19q89 zk;9C`i5A`5L&CQNImAs*W}FU!A1l{nMzntO$0dM0f~D6Q?yZ=iAm7m$Sbftms5kzC zKk?aanz86Avnh|d?f%h);xi^9^2Vr!hWqp;=Y)|ACK_}f?-5d2u76hR;l98Y5f}b^ zzL}zr%$hATAaSBsf{L)7-^0ey%4RfH6^V#Ds6JjqBg&%ZkMQh$vsl=Qm!K6L@$laF zVMRd-I?-niRgt*{$sV!vdqQL722%OwfXTn&Wlh=~huI~E((80+=8>&ZJZ!P_2;~DZ z#BlVpi$W2NeTUfL<~Q#1n$f7qNtMW?ej#Idlu+?q+Af2E%oOw>0POQe9V!!68}9Uk zo;%tiT*Mk zbf-Bl6J-_%A21|pM+GH=|06M8QJ^kD+U1k>U<%+qFN;K1U~=H&mEcu;8T*Kx?{{4- z(_1KYoGC>`jP24c2&UX4lZ|X{$T3!)2-M;#Dlr|R3aV2ZxkgjH+-rA>g6?^_(r?sd zxr2@(oN)X*xL5i4bnLJZviv&n1|88m=}rc+_*$DLaC5ieuP#fgMcBSAv$aPyo(h~&z9iTY1!@ia&j^|>e{?g%kA56#3(rM6l`1E2|S`!R{q#e<7kkU$+nzd zxxD>4jtb+tMKJQS?-yfr+0VcG`F-Rg6c_^jT*X2U64t^?{>P50=W=7bNiVD0@2MJ; z{@3`mZ{Nd}yUv!mgq>59HfAnLEBGCrDV#gI@|>jd9Daxi7~N z%Bsa>yuc2KzM{IMU-CMV{^~pb2Z`ZZ<=r7q?wL5VTfc97$st}RBF$2-twB)wqCOk# zv*o2g<;qinAQKW4^z6AJ@~j7VJ}iWn)s~U>#~}a|3OM+GXu|#v7Q>p!$&YgRyn+F8TY{e7#AXA^#PyjW~`FoS4oYpb3`(pN(SpNve%AD5GoTB!(5 z>>|@u_NtaEU&j8O=yDl&M(;!EG1xzB{qN{8GnVOGr$_tm|Da-we+a{DGX0oZxg-Q| z9%ix9nm-Z0J@I;Ky&eLF#ynG;6<^2Dsj0n3?7GfT0cxFnX%`*c@BQ$?vRAULAawSeD$MRHjQNK>ZJ(^LJdW zADgeZg^3(Q{qhsf`-JSn^tL>wJu1EFs`A2Uqu=N1|M=5R z3*ft5nqR`@;1a$=tG_CC-$?D*2`3mHs* zZVnU(BaDB;z2YSWw7jFYxZF-E;L^`ztF>`dJiI)=*|^D%e4e{jNrHYIx@Cx2^z`&F z7pV8|gZg?D7s^+Si*>CL`0 zp`NX7#@B&)S2Cdc@t&B3q`d&rdCDT)tiXc?&pQSQS0x3)ikX_z1FHYDb?NMYF-SF2 zetb}SK}}n|8#D>TjLd=3<7=YG$jJUAp?lTUdIO~1K0cK3W_VdZ&I)oiaRgKyNe^b6 zYXipC8|R4084fBkWp2e??s(Ukoeu2}w|6K22q>pYUn5q35cZOEgtG#$&hG{z50~S@Cb)Lj0V|Vm$eB+F?gxX*55{*kqza!nkTnkV+ruKRS{+p6m~4IZ)sMQ|@T> z%lc=luezU(;93yJX}^pX0F)$hEWUrPKe;a7PYw&Ihc&7#m4CnwxpKg!c~6zDT0pFP zmlo)`@pCu+ld}244Mch!;Tv)B&-)_yu+T+Ll&PV)htt*^KHD5bf=)3e3;3cpXA{CM ze$r^PnBhE_Bsy%BaRNNc(0H5|LH|gC4Uq&+qJPW=>tjL}R_wN4q`k(=wWQ^Ysw)8M;yhgvJhzk&P5o}v2RhcQBldR!jqE)PTCYga`{tOu zV^C=s+N#ZGb@tN*5Z_6lPN0;$sI%H9gcrJ!S6kDB{Or|yXSTB5OieX<%TW6Bq|qn+ zR)jaO+{KL{4es#J31S3(om8Gr9u~>u7r=EDr+2*9;70!NQ5yn*hfz!!!(}m!IbBh| zpxjRxqaklJtH6;e)*pgVFW7{XRYAb5*2iMoLod{+L6T%c$FNa*QNwY%*;uiW=X{nm z>9Et?+=6HO6$KhWlPy<<>EA4h8)S2w0v-=I<()w6S9`3`WLB)Oe0uHdCzbOTy%BOO z7akZ0vJNk{6$m9|=l;H)74eSyW19Xvv+ri=x2}~J&d%wb_89VCD!!o0vVAKbv+Uu; zs+FIwaaAs#^T7HFrqqM?Od0)ez9Are69#0r?>G^aUYxJzC3Hp8ZdC0^de2mt-J9zd zqqVF^gtY{+$8qSPvAM4F*hOc!FIjy$t{^a~3B1)0K>PLrq#qp@p#oGtF^HDbCGYh8 zi@#XZKNFA#j;2-!O{;k68m$iL%x9^pVU93w?h?Ap-9hRADyaBCVM3Q@aZ3(4PO<@m zJp`(*c-)T3LEzpdZxeJ2R_g8;kwMPO{AZgmXb3Pb7AXih{M?<%i3Hz&7*VsA0MNw} zYa`y5rb1YVm+y9>C4u>9z1wZeCi?ltwbB_HAOecF_o!+V7Lxj=?(OyCJESlM!YAJ3 zukMpcf)=RGcM9aFdafE)saW$EFGNR3?Xd5fX-XXt^^#uXyE({H`*+@RL(s54o#5ynw`au~R85UM8hUZ;Nj?(Tt&0T;F_Ta_$a5WtmQF-Gx?Lj@t2)jYHigklomzbt90a2ZGs|dBj7svmFQw`xt9Bdx0MGjm?tc_Ars@nVHCbG^P%PA0Rdi~~b z3a8d!-bLSDCydEp_~pG^tqFdq9U*BC&vhX9BvcNS#Wrz1iC*AhCD?&qSLb2$@X{l{ z0OIUlGt%PvW9kwCydQov*~nwT6f&SH?V&XZva+5YSEI=dSLFPH4SS85o~uK_)(bQ7 z1AI!~y>W<){*xq7h;(s9EZ3s@ceHD}(?xTa!IiG`=#a}omo<&~B zvL9!caFeAFi$JXvZV0+|3Xeqj3+PW;!t>XsDx;Sa9D&Su2axBp2%&fH z6Li5D<>Q5UtXn@IeAwVyV7?&AJ1&J*8E|N>Uvs!zFH4Ku%!}CYYIO?tKtteA2HbI}UdTTJsPp|9vSih3X!Q@Gzb9#6_>TqD!*y@M;yP?L zp9A+4HrMEB;Co2;zRiFfi`nqAdU|Ik9`mXzy=g~$K#7=hdRC~@y^H1y18E7zb2_Z| zd8V_k1*2*68tdNsq=$C{IJUhaPGEGnmZ@^fo`q2>TM5E-beTDWs~(Q0F~*VS-c>-W zS5OlF&i{bK53L`m>&4mL59WPRqT7pGJ?LOd_l(}+nTk#WW4KN&;W3KwILAA8T1J}& zGEh{XRh_#@A4m9%w<+^vqgLIsIv1#lWt42e+^exj#Aiy4#@obDKgP#a29nz=*oAJV zrw#y)j!03>`9492h$dG9+z624rtrGm(#gop$L(vD{(y=qbvLvagbu+^3o5>8smP?* z1PyCatZd(o^rHvE4udX$#x$Q)Yp^sx^of!a-u@u@Pbm1z zixM(d#q)SkVfFb+OorstVqw6K%=YgylLTUyS8yGhpUt9~jeIa!`r7clT%RC7zP1G( zTN`qO+zkvZoaNO$J%8`n-0e0MF`t)!n85%DMVa06r5A^XHs5kpCJs66_E*PTEg}Kk z;eocW%Vt-|HeAPt8r198q)oZ66EfS^ZycvaFhY6Fc)$>jbwFgK;qdpQ9&R{Gbd%N2&}W;pTG(oLrGYTqOl z)N5v|EwZg&7b2#BtCrq`+;&X_4CsW_TO*83dZdJ*bUsDF0D<@SRf*dPGgtvFe&vsu z67i+S-{PH1;vPRkqj8^zW6<^I!SOH1+IPsVewYk`(fL9L=7lS(41gB5 zv^f~43@nxZ?j1R4587=ndR?g{HoM)|)E#c7cbsLBYp@C0>u)!&VySNe#BMi*0p|>0 zFNTu?g%y>ap5Cz#%zIcW)VK+!QK5gTbEh#t_vV8VG`nbiGFPe!7Z1#p2(fWvqi1F} zf7I)9i$4w(5Ds;IY_FrLNEBi;Rv>U;jC7Ng%f3RaGhORut=|ORaLdLP2v$S)%4t=~ zqedo1S%@2m?!D_{WxeDJtM%&jZpXL`e&J6#OhAZLm}aiupo=tK;V zzS<&1@*d`ct8o+4@ynUHyIntvB>y>l<}d=7INx@=COow&BM_xDA{fIzxft4-h;qD&%>&4|1**3*UzfiNxQk`p1FJW~?NZ?e6#I zIFfcCb0JCn;;RBCXNESwcQi2GXX>FgN`VYQlIVdi{G$IXxKZ~M3G>OTx96z7X$8`z z{`J@2>kp=Dz*lZ2xxk;Pg0N>YOlU+NC;W!$Fvw_sukPl(HXs(c#O&wzBOd%>uu=_9 zE;V6wG1_GK9wi^LWuE{>=w?T9dy`2GfOkbLes!7IuJp-8tDir*sc~zyAfCM~f^}GW3iSSW;b#?hVLcnWvF;Jt-1>1+d}=b^euG;~0|&IIEW zH$a?xk@o^M*ui2}jR&NA0g4~7TF#z4;qh}lx%IK^LjIAZs$QaKH|E|+W5fF z<@N=*YFUffE}?%!9KmpZNe)GO&EnRFHW}i)m@1g8rV0KX`v6tmd3iB+&-sHGO*#K2 z3El!!rE7FT-v5Js($L7N6d!e=1>hj))N&;RrlznGhJ zxStLl<+Re`His+{IWy9aee!4;xmlqQqAg_f2&1MA0 zRnlPZFnuatLs3g$9J)DaW186XeOxN7zLW1-yhQ<*F%#WT~pY}LO3!y%pSf= z7U!+w$3G4oGXCYv@B80ntu_h|nzfNFBh$2yGmqv2xM>Ckm=+`4o)gKS*SrNUE#?`= zOhFJBw*@ML)zi%tmMa$SIR&dGlVxVE!YarN1cXrxXv_`5^PSi}+^+Opc+7?o`}ftg z2`JxCiG|aI#>8M(JMX~ALGE-Qdz=D=#KC9zgM)I^xNe_&Vhr5JYJf@_qqRGoGZ0dQ zkrF?f$GiA+H>2uQqYkd)lOOHMA?n8C8HXLe(?yorzNvkSlpb5df1a@pkxT%#+Zj$f zQhU)5Ere^!&uxRWh2Es?eAKB9Q_D#-L|l+Kil>6dZjtT;_b9M@^IFl4wS8$40|g76 z4n{if2g~LMFTOJdLsdwj_vIX0p9B&K=`-LtY`W}j%&42-ao9*dxqX8%WE-IaU(J3w zBW>4YN_b_g+TPme-T@1))1gKrkW~ZM^QZs;f6J)=v}l{RxUhPxc{RuS*U7xv)@x3T z>rK9przItHO;po6e%9E&LXq#M*FYyC+rSp(I9xCM?;0|QBhDdylappRfKa62lwp=tr6pr3hImQRC2M`o*x@5O_mqpve1^@zLt-P;FQ*4a=z%I z#n74qq*pH<<2mjI;x?SYB}X^i{7(MqX7nOgvpf8K3mvrgyqgd1-%mPgV0H2pqyV3J zuxyIR${d1Dtt1Qz>bs8jyqfB+-&>7ljE!*foRAnsem@_FfV(ZUG?W$YZ(~&f-+}DX zlU#<)_*&ZAp8R;W@hizyHz@edTjQ&QNX`g~OWK|v{-zH#8>z~ubMe7&Lx^?86eSqc zE~RLIW;*XKWe5}*4~NrCY1n)F`-cIQ6-8G(4op*2rV$k) zXwd*bTu~>d8g$1w--jJJrPG=5i}lRYx4(nRcyHg}Tqa6bg;uM-YnspI0%BmX^d@6} zuoPEDse}a$<7NsvN~ivCc1G>!oCy97F>1>hAjyH+^&2<1JW!WBZYSiY?(FOgZuZ4k zVQ$$JusT0ILdC%FoDv4MKg$OtzyFZ)MGuz9>)=t$`kH zawA+wz z2)fTdaNv>)6@3C3gp$r}aCZ$fLP^@f&tVX{G3gHq2h7*Nt<&MlcBooUrJQU|uscZ! z)$@()4@Q>V+XS%|;U(sCti@PxOOiPVa-tI@MtwyH>l<^t#-{WKB&Hj)pv*VaH?%2z z4DMd}IHKfxI$Gzp1BNx6veQR1tmrV;QQVb%#J=@;US=IV4D~;=rVwZ#4%NyiZu*Ik ztiE0<7O+D85?Z*d+B3wBHESVtpU)x?J|ipB5jeiF`s^S8->%z+R_f7K#n2kAD%3KBk{t! z!eAtK8`RL1=L)w3?y4)SmaO^Gl+E5a0{Fr*yw;Weu`518>zAA=aO(`cW)qx&DA@Q< zTJE1c(ve`*bz-ILp&b|{VJ|cH3+55Z@zfAc?%_wgW1z?hqwfEqXE3L(wH42K(#Y#K z$O(dv?3Iv>xL7UO-rXG<5`u=4ll55dao}}WgD>;*zTU-E>t6@)*RK;4ldj9#blSK$ z&VIB@o-9h6dfds?!A=IsQfYbav|jFuS+jFaheHXxH}h&v3bgR+Kcv&^xTnOV~N=4Gq*$ z9YQC|xybAyXMZ+WDzBIqlZ^jf02t$Bz>8XQoIAXJTz?y{C=RCJL}|Q4ReHaJDzELn z6vs!{SELdUy_MB7Xgv&`7%p5~oAEu-BxBsHOB)g)gKoX70Ju`5sOo4jy4m9ihSbKS z&Ai|764?oUzwMWYz~J;P=iB3<5fJnvzmIi9(l8Yki?Wg!Jle~TKkugxHw@}PPbG zTWt^h9=E-AGrs%06P0LQzN#QSVIma_h`2BWK-&@KUw_9TVCluze1=BFudhjP-5GxN z7!$yq+Rj~vgDKtB*3E%e=iUZORzG|8oiP0t@OD`xSa-2vfJHluOI3#+!Vf6kWVgX%yBJO$Dj z#@m_joP;1oLvo($OHb@m*n^udJA|O@b1-l?b3~bsphq(xrAyL5#e_%iW`U}7n2!O^9WN*)@*_p7GXo7=-JB!17|2ZmUqPwV8 zT8YH6TM~0|)#$THnsiG13nzxW)Q`bYGtCHe(!~tawoLF1a!t>8LVYs;lN-9 zn}`)%1e=i{heyfS{*{6b`SFjoSj;~+=P%RadZ5-J3@VS|pvffm?AfzTYaU+>0sN;= zm1SgPpnWJ*Oxt4vI!XkmtF?1yh_a`bDj4Dp%ap(;AXNA=SUXTHEp1g1uYs$s2ptt6 zfMB>`TK%tI=m8%-5fR^ATn^#qip{e~SXqMN!=E(xGC;d^^ici*4*aE)ay2d1j%f?K zL4oK>5(P)%^4dFSIWVmjAh%GtW8eeb3A@ z=dAPN`|I?jU;X^Es&?wxgh$CMkR^<8mih%sVxc2 zFW&}nj>$oyKMmV(tv|0HY5YS_Y2pv=&Bi|%$|=!BXf94nPNv675eu<6lF~Xfw&|co zdx?kYLP`B$rphS8hCLV&I63*rg6l?86pF`2WZ}ksjA$#9b(c#3ycy0CBO3A$AUX(J zePT!E+UdOuT(x01GMsR}Go(F~xUD zqzWT8@w|L!&07st1Xv3L9gB`smb!=1zN9(Y&BAE3v zz+fVm1^oI`m?-M!SfyU}Gs|>7s4*igo@A`BnJJI$STfNDrz}=Rd&82o_ zun?4~d4NH;HIF7YCd!0X3rv{NH#m^8HI%nSgeCgeEC!0hGRiJeJk+;75|qXhQL*hy zDMW|Gue{zSV$XdI&?v0oWUllR7NxMYDN7tQP@sZPJ zK@>d@bjkz4Ew0J=X z%~K-(Ez!+CUohU)Gj83MJEE%7Af4r@yw|q&K6CA4i~oB4z_7y-Oea0(k$3}BwAOB= zIy!Y)%po>|+~dIcOdfCf-6Bof!o+0EJPTDT6?gjU#QZ%5(87r!llu$Sy>V7~pC*=; zQOAdiZgblOB0s;qpVN>#=aV|oIx@t;R zaBTZh`|)T_bnMSXO|Mf+7KmGLX!M=MuwXmbA9RZA-9+!`$7p>jq7*F8&r1ifWcaG1 z*OL|J*xz0Gk-^bDYpWY$@->M?;@RX=J!zNgUPwMUm|AK2fzB(BpUCuyIM0*{(=wq| zOzv(DYIQY#HQ4Gm$J$BS(z6odYb^`zh$h8SMlo1ya46(e z@J_ptyLP6gj=em~>Fm}}6<#5NCQ^T>9b){}yWP=jHzbmGs>tXd7G=|vCavEse)vl# z59t4p+UBm<&@lvSgH z%O}DE^k4!Xva)h%9%=&_w5KPJdx+>YZXyU|HB@W9=i_*88)@qh0vbC!2vLtVy9O>_ z<#6SF*Z3~Gc7%I(T#R@A2QiK7}g%fl0qJvFH;;fB&MqPo_QP(`3cJAY^#{oH^9!sl26j(Ta(mq2E)>XuOgVJAsxKb++1c0`edF znyRXldIEYrZ!|3iH(aa6Muuj@-$E8%SG{f=!xKw8Z3e@uw|C@|qiRBBP0X+GBTGDs zGv8;=dG}a4P7Gywj)yTGXwX5$ZHf_XZx0c8W~}0jJ!c|5%*{Mc>T?Z`vPjOOF77QK zs&@f1+LTJ__iYO>8<{`E7aP(E)IBIiB9GT;(w>;qx@A$Ql{tW1H|=~w1DE0OW+L#M zDP^Uwv|KW1`HaX4G&Nmc+oj>0-{$3|iMea%tc-mj0lMsmRA$6bc6|TGtk}7g=5=}I zahd@%f=!G*)eh=;ZCmnw;jYJT`4yZ~-!NZgkgIY$G*ZtKf7d7_RihxMK~2`MD9tQy zxG1g0=^$10%0DG}V$vv*Xj#6hwE{~DZXI@_clorM+p9rqQ^=4@c_M%D>g24KCD8m? zG@!{~HuYzF#c#3GJg&m1OD?*zzIy*2=?B1iGxPU-od8gf_MQV{FBNwppi8L7hm>OF z#GL>UeN9j8t}Gl967xvtD<^oX62$3VOt1b`BQJ#`M;MwdI(#mxiiSkX7_*-5)8Y$}E`3{pxRtl+1&G^f zmgnZB@XT%~%&{lTWN072GdMa*=ZPamR?=@8k0g5PM2)WTNLD)nnqHP5KhKb>(<}F3 zW7D=<%yX!1u_hJq)Z{IZwb4yU1DE!j=b0B6xbp2SbVf_c5E4dw+`%;9E4lQw_?p|1 z+%Y?%uh8Y_U+81zQ9fpG4{MQ{y@5!_lB;kTsW)jG36%Q=9Hfps$XjJKX)I>82o7bkeh*PZZ$oK^GPj7QqChLlK3oF&1&lBc0jZ52p|~8Ed1omHe)2V>s7@3ea&LCHF^VrS&C2VZ+O!o>yL+GVYL z_TEzmu0Bz4tE|t}U6n@b9o^T(^TzZmW*eKL7HsoJ-K0!@SPe|Twmr^zA4+oSULbg_ zUnim+No>dDVqPi;U=~{UR^L?5BPK%*MxpG1NrZ~jUra$pPv+C8^K64BS4$qu05aUYj@At!yUC`3PX0N;{NyC`N8*gK_%xz>P6# z569vOl=OGWQGY8My;FW%L|#9_*m+t0Jf$saHfqe(NllghLnXDhzn(m8b+-1V9JCfU zNHbMQv!F)F7ekY~43`-_OIC^Q$KE8Tbov|z)3kn5-AipJohWLpQEjs-X%FfKS$mE= zKX8=SY?J@HdMAB6CKer}!SsqryFAJ=0!f2fdH)6%l|WLt?-7H~wiQ&v+>@`9DDAfz z{RW2xn6R9UswR2L6#b1A^uwaw74^80c>3NPm0qk45)5KQV?K!}3M=0zGZGOmUNUUU zXY8P#w4gS!tj*i3U1hz9I2?ANIWefANrY?kgY!cI8Tkn!zPA9$!0(l0e zQ9-R`n?mqBou=xui0y4B4a4S*sEc}WSTNNiWZhA2F+JOTwX`nhv^QTH9AS#B?#hko z+SKZE90rXFw?1-!`iv(U5JvZXIvt`RME)Pwj)+2L$x_=X9{)k4XkAKdMxCuKTfn;I)m;D@x5ex>nvJndJ zFDe%l>ZC^s-Yom5>o4>JC(Vy3L|9G6en@{U))(j5E2mE3salnt)LIz2WZ5>D_|%up zrMy2|#qteK!gKFs*=p>j-E=qEF6SnOSii*#S%~cra=-tW^{_x=QQ3zievqKTu%v0R z)Y{5O6OoH_D(hj}*3QEi#+Bykx%9$S(*y98Csz(sE@d=f#&? z+$rhIoQs7!MrAw2qyns~!)K%ti`x$FJ$3V`{P>s-DZ_@s28(<1Mfs2Rc~qUQuf@gE z2Hquk>w6(yj!HEqhQEqTjK{*A(dsUxk$2wG{*c625r-2Fi$-TI23RwhNNR+fjl>uu z`%=&^qqPVw2}~@S3%xPSx71MnEg7?R8Px)A%9BbQb1G^Ac^iPhHYAwkAuiK5i93Tq zU5>d+WKhPBhERb-qFKP&uqeKfsXvEz=Mkl~XBesTQ{U5 z56wO{$djU~<%-BJf68aD=wnu4*0f?Crt8`~;7+Z9<0Y;czUdfuQxebp?rCF7)2mhA2pJUGHgfb484`mY<~+z0|zujWgM|yw-+S)N&O0=Z4xu7OMbn>wK9X`elO zff_xIqBftyo@H$=YlTLotzRS)%2VW&8OsSWiQYAc*3|F!%lLx6fyEzl_>|!|R>N}A zf>*7S3Hx@)xUnok56d+-k)DyOoAFu@b(zwOrIPY|UD$*4B52n<-=|-`K_iIwI(0IC z;L1p9V(!o!u8G$q3J3Ds=CfEvn^i_^d|B|Kkn#sxpAc6-#j57XH#WP-m{`^ItPgcV z%#2(+^9A*pP3$I~g?Re|ZW7a%S^4VksUf6@uW*rldz^=OcZN88F|^J&D$!xP|vpnK%zq1xc~Mvq78>`jp^6W``z&4M1a` zMCek5G}HiXs*90#OdnjirHE%FLTYO1E`Gy)S->WlD1yI*xluhw815hM_U6r-toG~k zYajWhO~(%v)RnNISMXQyH3YE)RRc173cZV+Y z_E`5lmNf4}rcR+kAkJ%f;VV!SVyBVf zB`FU)Rjf~}eD?*LB{nEinu0Iryu?dO5$*Gn-eXMkWFo~6##&}!Nop#L36|3Mq{&+Q zCuqql)8W(+CY<2-%RbcavMg9Amaz!)H>a@r9+A-6=pJKY=r~I{(K{=cPC^W1bLOeF)I~TpWJ3rSeQUX#h9TAV;NuUS}(l!4@*UN zOvS$fcaT{zsBl9(x+)T+>yiJR?)Z617dC+4m+XmA@8(!ZK z&O_naR`D-FhfZor$PRAJB~2oNeWKiG1$2lSoN+zh+43*?{fV{gHxbnj|dRiYojcf88v z#e5V;LO)`OOISUhSkDZC@GN4uqbq%Dc9k4F7_?!ScL4f$lE<%SA}Su4|I~%83xAUM z!z=FX_^=01kX!8x9A*>l@jVCnd6s(~0AGxjmR5I9Mj?9;`=3=!fm|^Ujz%o4tQ5@h z7|KmRF@BYE(fHQ~SA3HvcL{{w$?% zLlG~L3QUbC7AoHpdKPPb)h6>5;RUWyxsP~=p?k87q1le@B+bGU)sdzR?M}8ljes^x zI!eJ;PQX)qQEC;&+yJ6UTF2S?tVjNdD8vNoEleR9r_|p=zCc@B?CC7YJ$B(C!j^)b zd>KyOt(SZRuVG|idS(=~QB`Vop^Dl*iN4C9vPV5)ILx)T#tVGqdYSA8tMA8Us^mv1 z?5Y(Au9ZDsr(dX3I<>0<#D4dfsh{S}q#>BXN{pqbpF5-6Tb@&ctaViG-0)%SD!I<+P+mBD;+BEP*YD+wj8g+KxmTgzkh2IV<;km7J^|`y>lrhTjk^$KSj1V3V{bv3Bwp1) zpHX@7{FIupzy-RTBBMG@kyX)9@oGTP4om~|M&#B`ATya+X`e6tG#spBrh^5Rk|Ess z&9K$7wBimFll6Lojt@^EJW_eb&-V%%D1uTJVM?ZO_*nhwiq@jvUeJ~&Ck<*bsy5bUA!u0wzc0Lh+)h{=+rRriQSJn|H~ z_lo40e4O-=FTlU^P*}5M$G9vnQefzvmUmEu{zZu)i6=f3>Oej*9wNrfobp*T@0EW! zq66ctmcCqpo@AOZhfZ;Ydvr61F!z#%F{6%N<)SD{buMuL#C~@`CS?5n$~7uFfZkOv z2xl#ZLh~e!Pu-q<@lfC>8l^|B;Rh<9{zls7;()ASp`|Sb%Zs^$z+cAiVsoW{a4rd?_Y#u{glX#RVH}4_ra_xXgNQJ74s&%gqm#W#OLH-l4- zf6B-rq#&ApReNLyD+tZx`bOH(AywF&S?68_2!c29+AlL=XtR(1)E?|k!T{aQVzve> z4MfwGDt}n%!w}!}=Rc<@H7Kt-JDfMt=doiGx;T9QG}>}s;GtHOVnHG|65o24Izq!? zroEiwjPaE_JU?9Y-V{Qr5meL1EI#ioF$TbsDE55-c?PbYClJd9x1Kk6!F8{m!(Llw zlZqtM-?pB|r>0Uj#EYOW_Ci;Epe6+fA_aH&cm%Rv**ju{T6w#LxE<4-^XZZqc?V@S z6mb%_%IsrSOSw~Ez+sKPO>^JHX+%~#RS*$I9%~|oRtSWcSv`$u|HHT$O@Xhy@eB1Z z_v6f=pXW_W4Ou)Fs3w-nkMXzc0;4WM%_#ULda!sLG9_w|atRO(7=h@YlpLl`a(n_& zlbX7T-e;;cvgAQ)j?)z(7-p=h~tMr_?>cbvXWC~y6G8({UiL8(Il6^%+W zJ_KC~L6QS`@$IM3d6yA|V}N4Te^CdFv-i`*H4AyJ^K*EvJ5FJyvPXpgy6+`asxsm@ z`vwhg@aj|W#By7H8e5%EUE6B@*ilRI(!IcrugqkJ+newAI_WO@wQN~$Cfz2r8MuA> z&_)xv``3aKP7RtmV8#pS6#*%@n9@>_6Jv^ossxzff)3)f;1{Qxx^Ldkb9hf-D9W_e zl$}^Ia5!Hb;tQoX)-ez+R^76BHq-B!!Yd-_9JX<|tR5< zX{l>hc)hzWsI@5#BmbQWR;X+p7^H4 zWqb^3rc^{gzU`Qlj~iU%uLVJAFC4J_ZmIB z_sclyv5(B7_BH?N=)0LFbK9K2km;yn@QINA0X$rEFW)4@T#b@T{+5?<0N=2f^+ zf+d$#vx%*+$3_@*VoDgqIRMEE;`E+#y)RmLCr8I?8(}aONoNDb@fxg4o4hHd6TQic zouMDI++iP(tlX`1Ll~AAdfl9q@;lh%g%95r= z3%jdTl}aH;Autg$_$~YGQ*b%FBu7zOB0|1$hir5sHN@e>^7U7)!SRyg_r;ixC*Ud! zA}Nu*bnM(E{Brd_-r};&3Qpvy8>$wymdMn5d1?52x-Zk(l5C4+PmD>TwmcQj--j&i zcUvNJxI@S=bIV+XF}cOkX7AS(4jlSNm&v7S+dh5{e|&D-8cQm&eRotpzaHx5(EX#< zh#+xr6smP}|KNLEuUmQ6I~tA%*IYE{CR`(zMlZxREps zX5-%YXCy?Bz=I_+v9xZ7N-xt;2hXfF&O)2wY#TxWF})u-))F_hR^)XKZB0!=O2&P7 z7wT)&`9Yi>e#>a{$=O&n4MG!I!!E`H$^1?hRC3LpUpKnW8^w*dDTG`rIvij;j>d9h z;^}i!cG?9!mJ^+8UPosw04w#>ueLYzy8U3S?QneJb8d@Kr`IU8kky4iXJ>2C!kf#F z)eGm5+U+CW(i!}oX7y#C`yWaH5#VVWnsTkiUi(~RNuJG6RwFe_WHd3cfks|Uyt1`t z<*6~gQnkGuBxPAe7CnYBGQQg=tbQP}4=BiDLR+uFyKrYENL+~a$%+T9YwQ)Uk5u?o zteyDdtf`O!QxM&$2FugiYmwsK<1{SSF+X+H@UOQNn2pZLzbcVc1{H?SlDG= zR9IM8dcPqdBJgkG)K8Ci_6>D_Ny^+j|Mfs(^$ZQ;&~KMVsL<|j8IAT3B?CT~LHLsp z=Ipqq&-;o5#*FHUM{EhZ)kxOLXGQH6!6k~u!)456_WK1~_ABqaJ&SQ|N*&h@LmQn( zgLrwH4wu#?RE`#|rOS+0CJ2U&U)7Cf!W=ViQNAy>jvP4bWC$&y4spi{zsHOUDNPiM z>@i;W0fGCt=|B(C_IbM8jI0$a8Pd;EUY)njlsaeTDj81`wACbAHSFv+JnWX}+?AAZ z$xTL-+!1`DdGeS*Wxw1x4ISy}hXTE!Oq2>NpHthNW5u;6Nye|u+tW_rv`0X7C%OuV_m)J zg#xxINqD&1QWbA?DxB)!*XbQz2YrPONZz(z9ns05g8R>v)+_N37L7N=j`Rnw=Tl7` zdM!*HIh-6$ZY1yj8s0mNS#H zQCo<=s}hKotEo|uLph^Ii8C=;(iikOSj)>QzU^Opd=XKl`M5vLwIS5gTX5=L#pDvI z6Vws}{VbeB7?q>wHbH_6T(>Fu$VTipbIyOC+}^&d#vXrTSGm})6_QUKiaewtuA2Pt zTMJfVsLgJ$OZ`>J6GC41fttrotSrJHSPn$lKeayoO;2bZD+^m4Gzwa=WBb{_Z3@NH zdF#vLk;>ZRX0r0i3p36U4viC%efFM0L`#XXFpKkPYHgepCd{Y8J)xb79Ik`hmhBEX zsL=I;BU?Qxv?qcbn&=>=H7yrkC0&xKBbrSKMdsmxyXA6b4hZwkEpteb{gi;ZwYzb} z`fpgLr7;i~O(CyZSZ`63gfK{q}>m4fVnOOB^T$EBiqV z&GtK&p06=uTWV=VS=^Swwdj7Jm6B0GJT~eR!UZL{h5)G*G@lnM5h==Os8^jYYF$@NZGy@v$A~1d?(f0WuOAfX zhdbVxlc~%Y6;GHTJmSA-ZyPW?8A6^)k}8KWfF zOL5CN6R`A4RRTs&jyV!kOiawI0B^lC{FFqz;Suxi+I_IUUgV#@ifA6OzKi(cAsr=) z+jStFT&0#GVxs6I0}zdKC#79YNI%{C9v7uz^qT+aB{PHCcsQ zfbBg)LY?{_f;Mw_zLw_Qd(-Y<3oJXClv71zjop_FJiM;m^5i607LtPNK zmOQRhhJQ3s#(|RY;337EBGiB2T`wsg{)Py8`o+DzH21uDriDU%m0neidjPtm5}<+# zy}HJ?^e3YO>@CL(*!wW(Kim6*#9yN0|Kwr28Nd(R`qJ7^-#9-pyFjYjEh&ojh^?$% zQtiK5hvp*!b^~A~q_JFEY9y#o1S!|QLE-=LJ^l%R_mdhmway~jSvw5J^9-Ny214?1 z${Crz!c9*($%7#6udK!qXo0nOtL2&1IH&!O7qi>ipkGdASK2_3l5j;37=jObFIjkP zG~Gl$YEShbFo+rpQD-?#)5^Z!fTTJ)g{R3248(+PL2-lMcO<3EC7lU=3IFT}Y(0(27K7sa?+^N595;`|<_D&o9%n{NMSqjRSf$JqHDmEU4>n z1H8fF*Uj!V^!05cCG+^gj?Rcsp@K2*aR10pa5ThE zTGz?3bN}*2uE@F4%qsR_!Ftf|Dc5y*C?h?+A~*MwNd^SM$-=~R%$KrU2mJ@h@9oP@ zH0uDKLlpT}1Q!oaMXGmy_)Du^z{dP@c?4ZzseW&Rgi2DPR<&{!E5n@mo*v`9cwwx4 z?*$&_m(wHBm=Im#&k~ZjBb^DS(5kO*{88lOe|q3xPasd#8vEs3f&3$&GGWeTIXrO9 z&C_*t<&*HN8qi65wJJLx#7O_QZI)lA3VieO<#iBn7x){Kn)degSDA`ufWQ|Zia6`p z)JEaQsEhDxZ=F#fh?Pu(@jd?Bvu@G9tW-hs%Szu=2?jhwQ$8y3MYpOY%5dWRlgjBC z>VSC~smj1!h`8Cd5EbLlcl=j=zY@|eA!5*%GNI_bcQNk%8y?4yZGvhI0V5+_8S9~6 zIT&M8FhxO8+rYpMfZj+h!SgM!DUyHU670Xh8)m1c@p`g=YVpWeX*B$I?7;ssD3K9? zYvZe&+S!d~m)1R)i_0+%P#w}vN-z;YfrkBV#~6HJfDdgjOyd5NuhK!RUuBlT)6&xN z#tewesW9=c(0%mFE}PLm!>`rN=)CM4pD`hao#U}!($q|-M@XZh_cU@4O_y^V;=Rjk;0ReUBlYq2pBx=SXLhruNpKA+zBlXj}>Pr6R+ss=Wkx7V& zZ%B~Igo2I#J*Ip6GJqimRR#nCtvr_!osG(2rI>#DyHT#leg)y~zYfAJv0JU!6(uFl z#Em@P5qA8E6aUl3{l8v5!$P%Q)rybByW*q(XhfnVT=c&Tawhcip@BpH!Y2W+>5_qgO_gc9ezQ8Uy_-){E2SA1v_dYev+=YT3SLH z{#G|B?@xo)5H#lZVRvc2E_0d9LFi*UK1AqgP2xQreP9ySlzLDzx$Vx>U_MXfvpH9eK! zK2b#E2ftiU{jb4P%i-ljzKp}}crHw6^5H+&%tY@IPa29Na#&?3_(EHRXVs&vAf(5K zN?a$JytHa^KZaI9Ms=}v!+(V>g@ZpI|Nk4dAUiU#q_w7>!$4nU|AEb4zIur>FqcIQ zz;FMhef@*V+y*9@*=JLWh|nnc%Dj?_rtpy=uk)A?^AQX$WHx?;t-=$v)HXZ-w${!+ zCE2^`J6H#euo)M-wzk&9{;HdSYHk2DcIt7O;%|BOf9^J=n z>x|w*?@q@%$k;pbe-c}$u>M%G;ceLLhW5@IS^tceQWHS^_~KRs*Z^O3QqMgSfs%x_ zw1kMY24nsCf7IitB4cwSCKqWF|KzDMBL3k`!R02)>Q+j#nGY3G2N(izSQhwvg&a$LFEqH?Zt$PT>mHqG4SekE?X~$M7jRaP7ZxZY2q6NjE^{< z$2CstMmKE#Dm*DorFgm3lE#?Mp!29uA0)2ev)>R?QD>!o)j&}?Cl^}q?M^ghNX}n}zY34*wt>#>0i7J_1OUD-vuMy9))@3{dI2oGN3@WQ{RZOvhB2 zIo|oT^a88eT`$4Tqa{uf(Ps0k5_C1yi|tPYzK4YgtTN3Fv^Y|{mb&q5naO8|cqVQt z$xVkgjanJX&2mGW<1q@*3P`*8VO=Uzk1Ggr$QWG1vcLu4pmL{n0Z+ z=puE*6+j~SyPy#mi>o@F@j3j7frm!}4>x!HV)cGAoeu$eAob3tqKb-P)(ob1k_Al7zlTbnNP6~10i6-Ws6qO7it|6P zdqp+3KJ61MZuh9nu^uf3L6Qzg92Pq*Sy?_*Hn8WxI-iZ4_fm%XE=W=cpm zw|z|~Pc>C;!24xNz%6Nmy;83PX#ksKm#MU8Ls6j+li;I^0U)-$-Wj?4JA?cLB0`9? z)QfGes{!nJNfG&;R+{?U0b#*Bk-w(TXcg#flK|?vgem86t*wBI-}7m(-71-wo|{|U z?d~W=v8BuC@FlP5p`@LSO{2*5YJH3WZ&8Tl*VcV{lIQl4WgI9i3_p!i&d0Yn6}K_%vmM-|74nXRHoc<{?Vi3Bllh8`J|O`M zwu@a1%xB~EFsx-uTfQbA%oDC7qj5V44B z-Q(DGH~(s()kny)V&T36Xwlb0p$VqRGg->2+6FpSKz2R#N72!`BJn-Pd;zI$lGD7K zOzA8z`$PvEBDLH-nT?xm1=I{Po|{WokMaRN*3l}9{If6dU*^6)-o-``Q{=TACMqxC zZ+U^g*KPWOQg!D7AGoA8u7nk3j`NSGe%wmKxOl$J?)z;o{Dzg?*gbwEb-|PCx`C{E z5qC^vZIN(9&jQ0Tdn3+3e>u!w!BwNZxw(T#l4Mg}iha{KkFlei)}csePSc=8<2pI{ z@yTu7ZCPpMbKu58?X1djCns0qxqUD~Lm32$@dN^%o}RX7IB)0-aLK!EUEkp&b^0d$ zHg+K{n}XvBFd}t^*Y&1s#g{sRThMf9JFESUC9}8^K01}G9(ro*p)u^M6^u*Majab@ z=cPS0zTZQ$Euj@lnpwk@naVuUKXIfFd(}|&^#?Xu+f8PIdGBR03x!M8^yVk~E?m2$ zHVp>;F-wgl{w^O*rifP8Pn0A=!nam_aTpS%HCf@Ag!R%*1yt-68wJmMi+Hl-+l$6C z7c;-JgY#>Lz`XX&28(*Q(Ur^u1g;ySmi^BzzdL^(r5|%E!0_;1*iFb@&c#Q|5UzsX zlpW|h?A#Fud9e=G^zg(Ht#_M0_HZMaYIG8$U#?uN$%h#)eK?EoIt|^@_KZ@<6F3rL zC8*TiPv*9@?jChF`v~PWKA=6p{VIZzp>!a0)INb(so3n%a^A)RLN6_K&J|j=lHYjb z*4;O?%!4z-V62Qz67Urgd+R%;$G$b1VaTq5#q81lpIU&Gi=Hr+13oZbc1r+Oj{!oh z8{vqX6Po5_V@b|o&?(`3I^i7bc;hIY_Qi>@rspSb4EymM_0<>e&Y`VJjlN^X1`V#> zuT{_HTTHW{K%be&^3lKP^#*z(Zl=BhWU<0hE6CE{=wVBO0G&L#8xUXBvmnUuwZ_!X}&57OP^p68h%`sC5DWNrFQ=gV$R> zqm!9b&~<>9U&ThSx~QtJDJ3~^q5bKp{n1%nWkEqD3d@N1=K~VMd!viKPCoUEBIR`3 zoe`lqWSz&#GoitAja`-;T~`n?K_7li*nlYwJm_+#c$Sp-EPFWJ!|w1LY@mLs23%d9 zV87Q*m!+}02=fDutn${_Ih@Hq)8eQ{`7mG{I(81A#VYeJMfo6Oz$XmXaI1Cs2f{Qn z@%E>MZU@O&KCD*{?L2`TvoC>UrmC9s4@-4oQEuh;>s^#zHqN0fBu=71Y0(W@j++Vj z3zeftGkKX!Q-8Gdb3=1z~d28qq5}20mf;V<$^7Z*&Q4`7`9_M)p z=5Wz*vmq7KLh5DpaC8)(V_2{s|F|Y7_Q{d!nv7jsQ#!x#8lv|x?HiRUA;;@D+NUy$ z(aR?so(MgWv8LR&v!N;?`-on9A0pV?+IvN^W;<6b4!}elS+qFQJ8D3Mo2d*T1x~SN zfktrqcCU{89EsDF26BsT6HORqljRQf>G&GW?55c=yD|S9{X2?9Wz}sJXF}H>HMuJi zY2U@S?<~u{0%)lTJ5l81$JFfZ(if4Y{lVR7^lkoUYOy&Luh=ng|NhM6)dMVkuWvbI zuOHh;1JY&>w1;q{fD2q6j*9hCk_foczHF5R4reT#Z?#n$y%+5+HupD+uBWGp79egM zt_YQfYbUMDjEkMV;UbWec#X+ejPY1)iiS4w79l#}=xlzMVac}rZkELeXqNw;o-gRM z)oV`ka@%eN^f~;IN|KI{%hCewdb1{I^GjKw&cbG7f`1I+gYRrzs~6<+BzqGV-#X?x z7%HtR9jea?0g6mCdViIXp(PFYHLEvq+jSapxtlnRH;(U=_l0^?RLSO#qm)su)DUx= zT+DL$)TsimG4*PDr!(?aXy#mT1_!gkZ=zlmGjEz&KczSss#_j-a>E=M@Dg(5JZVYw zh4ZHu<-nVdH__l1xTa4PmWj|u_yx|47#Bs%}=CDjuo~xqM!$PbAd5u1T#ZWiMy%3hYCw;(BWDmK| zC#F6_{p}FDJ>k6NogUMZ(IW1RdNT$D)<)#l9mXr$!l;7e)Xp1|oaGT-%xzXl*N4+h z3*0+{4k4$zMILj_kg6u(WTxdWRRPmO2AYNpV~%yo93EMl`VN(+S)Rgq&m~m1j~lM> z0dEZRGOG9c@{!8mc-WR>JW~+#0W4xA&1_K^qE+3wy@LEy1S%avx&ox(^*eP#zcues zxN{4kX{Et^8Qjl2j^b*eoc-9F$nNyO033sTvHaCfNU$MD;PI*EKlMnURS+IS^t>B7 z(~3rTj6>&}y&yVX-v#>s)-ekf6Td!+ z?5%b%7%rS@qtv#tWTR54w|3AULu@r?;sYk+9N-#_JNi*uosv?|l49s65&pRDp<~sP zYOiUxr~ATn%dT*?&ilJyCzwba-P|+y8COStome4glw{$t?;&h!Wwe6J3Koiya>{sfkSo=2xY{qC*)OIsY;|JMi;Mk&T>U-t`g!Ch*Q1qK8PuL|3MxnFZ6w+ z@-Mi&djBZgyY!Tq;9QbkY|Y8AMAO?jI(p@?z}gq* z*Zv#1lHe(pw%Z;7D;1{NOAQqt?nlFT;u_hXd(f7_l78q3>4~{(xrdr_0;u_U@Z(nN z%hIH=#oXZV%B9Hes`f+id#AEdt!(QqRYFW}0lRJP44=NeH;m_%m6dh<*@|}t0WdlR z!})13CXyB3w1d+^4*$XL9Q7uql|b^wUzjl!{d+KeW`&T!=hO%&xffiNUY+Xe8GLwq zu?bwZ<~UWXf+9zVM#(tzs0#4c`a@uTV}7GNc`<&U2F{46He0fmzfkW1u(r-gFJgzP znrfBj@h6`%5MR$c>}sr~G5P_~JB6R26)!&C897|TT^Xho&G_1F6-KKDBYU#47dny<_F zz`GFykuecI`fLilhQ$zOoh1?+77fvb`Fsf>Y{0O30%6)ve&aZGuA)QOqXt6+?Tuo3 zglFnBpk%)0Df}`rbbFtE@(uqA1Vs_30x>*q++%i`s|JS)80GJcp3b*SEn=*a8@eq$ zCUvNN{u#$J3B!a~1t@ZSRBTTaUk~+?`MOaW;u`seo@!1SzS-K&s~rIWydL)3G*^=^uAcYD9m(D4I zOlK(~h0)2Zjh}+c)p=`#=549 z1fQ6n1YIQfzS%#JH!2wu7@dMm%V~g}O}$8->(|gs=+;n;fll7iJ2^@$xv)>Z#7^w! zTttHyE1^RiU2`!~y8M>HD<6>;#PX{cd>4o_kUy@8K##dh$8~pD-VlG<`aVoK8z7J2 zR|SU1LRfSOqi^n^Mo@ar!R?PKfMx!tGfP3Lq1ndpE@+Vf@jmPk=G#+g*dBrAJ)%Tmo z`*=lvm($UXfcDyinC+LpPhYRZ!vyWKDBoN)oYE6)Z@7oT)xQxJg?$%zVih02skkQ0 z*YZBE->_LRR5|8C>we1c$ArQ*J<`uAV?146hl9Cp;(7IJwF#Oa&bZR$YRY#}ygdiv zdiTcn7k4uLbG^~qu#jVOjp9<3T|#E9Sac|Rn|q{Irn3m?)yns_e!uU9I~=ZAbSTC4 z?VX@D2hTOGY=d))XF96hsa>v!AaENuK#4wMI9%{J5d{JFae%qRo&NsB1DO?|r#w8J z1UNt|9zcnmX3|BND3!80y%|F91aGKpYh2idR~M!yvl(x9n`Uhdf$Oh%V(tdq8e*?!~-hY5kr^$&_zLFi_81ufLr>)GhxKa1Oa2ry;oUOmaUO zU}!O$69>o)>>2~zw=(*NYlI)*Izk*;`QRqzLgZp8^=3J5ZU95!2G6RlktXHk(!cK5 z5;yMrP*0befT%Kohnd6_r+e~li@ol42@RTjAb!H5uwb0<6vQ@7}%&yncu6!!GtvlhzV3Lw(ylb#D?(^O?;m;%;(z) zu`>>BQn7jYD=-}5CNREEQdr#op*cgp>@4QZjJWH{;IjztaiWR@MGBdCW0A#D#McZN zp=uSDsIo`w_k&- zl;4B{g~6G8!HZF#2XqC?Y}FQrB(dn|Vr9B{f((p8O1uUIoVss8n?ymCntEu#z5=CXmF<0!c* zKo)~EJtnJZqfiP9$>cubZrfhB^K-L80x2meU2L@74Q2aY=vgfy?POAr(Zi3OD7wY~ zsBT9S_PFV_(QQ?|e_oRB1-$k;+mBL<$Fe7N+U4fe!X}r)sGghp%uK_~#Ys^L9O#n> z9m`Dyt6K{Pta$Uj?psnlySxj&wukqn*!O1@G$mSZoTnICZS=p3)Asn`1(b%5Te0H0 zgs|72e{AuI$Bo5QLhr}3Ri#2n65o{~t6rG5HZV+;r}Mr~=k08c2`~Gi#mdYm%BTK>I5q|FDX=roT5*I zn)W4iHGnp+{Gu>wTVBIY&=MG)Y_M@Gov|lwteovBz0Z9fd7%*uReJ&=!%wXA_YQ`8 zyJcgNarTgV?>3b_N8h8Y&=MdAx`t2D2DK}6Mu!aeeC=m? zl`MOWB1M}WxrU}4&5{j~%guy~nd&z)DqpjFkC;kGZv37Dil%WLz#Uypz@2_DKtJ8z zpwT`pJBs|B=;ffw)dt1Q7nD0HCKERc!@rI;xADMfO_kA1zD%6HgF_`Ih3Jw%psb+m zgmGl&jyCa&PP?m!*x{o8!`@p)RoU%r-v%h55)vYk(kU%1-L>czVbR?zNPGUxp$!QaAI`W;I|ZXO`@Me@f41klxIZB}Jl?S#RyW*FJx$owh(_)o|mk zqOK6jb*$FMbQd62WaYq@5m3`s(qab2|B-v@<4M$x` zjO4C^-qS!i<0OxzW|D5ysa}aTid0qBF4V|oavETk-a+<_;~MghNkkSYcrUUAFH}TC zDSCjPx}^4cBZIQSp0GNNaCFg|*;hg7tqAf&R3cLGi$tT^rJU-5oR19J^NM2*!houz zS)0o$ROXcfNSdOqE+KTc>$zZ~7HW6ehwo0hS{{u+)5D=kkWe;67bkhwg(YdoWk5)?M)HwUXACy5>Q|5Asw1z^gNfE-vFF6 zSb>W0cw3u+DXMpQZ&V!&viDQqGQQ~?-*wTSvLGufD=0O;sJ8oNpXhc7kjhVFd8FWa z><8*j>Rz?!IzIunp@b_x+u)1qQ_tJ#Yxw+4q3+4d{Eg$~j#JZCNV3Z+ULC-2zTQ7r z>1mL+Nh#H9_;aKV3BTK*L)UAbH%6DR(?+A@i_z?D0OS9SBh5YBuAj4m$O>=;?EK>Qup;=3EQ&%JQE2 zPuFT(p1U{gIVe)dR?$Z5Y=r3ArM^jazLD^Jg==Nu?4DleW|L_Zh|-{8=}#x^o(=b-vS)(OHAQeqcU$>5#ZyaUq?< zlZ-sdc!XL?4|oE9TDZT4ETxkVg_Re~N_zMAC`;T^S*te|L43D~Tko0q9BMH+WX@p$OT*n1jJ`?{+KzJJsY1I#SoZMptM))l+Wx*aJCd|<&NDXbl?95=S_Hm zA1fuGUe0o!Pr2zX-Zzr)1&&E+$gPaB_{NvHVG4NwlpLs-_;XX}o0JP)M0Kfg0Hh_* zGs&By!hW%GFE*@@m@2=>tkQzAD2abU<=#EEItgI`rNa7+uDTg6+`Q3iyJg`J5q4MG zQrs1SA_-<5vk(4}$&g$Xow)0A3~i~ZC1R!SaYjYge+*Yfp%HR@kSliX_k1Rs%=%+> zAaTW)eDk&5SXQ5=dGC4{J7lzz0kwPFV$z)334W3)o9w>oFD=!lsreprys%P=hcZM2 z81Gsib6MG1Elv-F#z=FF5nx0h${9jJH*za%mP9Yt#rQXim0zUi<>dvHm;)}sC&1R7 z?C0Zd5a&VM{|bgd9bE{{E5GuTl3A4-0kR$fc!XHf`EKhc?+T11l=)A9q1;F&Z`RQI398l zS?2DPA^JMIE!He&xr(U!CUT!@F(%mFeAxb;>LaUkTtCJ(>T-$8SlekREo13O1QeEc zoX=!+8PRkOjryzo^(iMCA6}Y|NeyJJKF5Yie07*VxOAH-`O%W_af`>P-m271?$jsun zmaXxoplf9N6{RfxW_$QVX|_u79trE%Sy8@a8Agj}1x8`+?P58urih5|Dc4D7w^(6T zQC5&&Sq{|>P_mw}r!EP1{JP)<_pd;~`4QPxFg?igjL{D-W zQMBL}xfBku!zEG5mKSJWrZIoXB4e&y@IFvdYvDkvnp|QeI2FWZ3PJ5QkaLV=u+<}^ z43R;38p7!nDrnVRr=I!TV{RScJxPO2N?VdMC!VOyLIo(^0Rq}Ti9$_1FF{R>g*I0L zaeKqZF8%S9wI>HvKH7z@$bsHsh$;0BFGrsx;Nw!}f%? zj5N%nE&}p6*U|_!ACQ7*@T+xHcKBH~v!VTmu~u*Q%~Fto>Ibf_h84Ht&Iv-mobK{M zKYbyBvRE)7OHG@0y>*ey3;wD*(Uy8m_chT^zU_fR$N|Deb2p&Z*oFvOWszeBMlB_E zdB;dcxgJ;qJJl}{+yUs%5Z!83ee4J7hS^e1~JF;;!EPIlsTi@xixuvYir4yYTf^~%GJL6 zJG!S~TiGQIQ3NNxDBRrJe}@U}G8+?;jlr&mBFZVrNYKYh?2$1=jcjFx*9%FgVGmnV z7Ma>#lfMcV%U^v?Q~WJOntj)aaEMF=6h6VkSv=xGqFwqvl!yfy`8X^p*g-aTq@Fqf z%-Y5a&9wkJd#($|5r?m|6byV42;n*Sx^{S5<5`DkK*@X8K``%gdxm=u+o*pQ83n3$ zPBlG4IffAFJJLtZS}e%jquN*A(e&ysH+U=v@-6mLh1s%N9VCIB>08s)*sN_RwFLw# zGvj3ZeKud7uL33a%+=gOqXd<0L`SaRfI+44%5Wq=tr(ukr@2Uo3-yQdGPD|=&5GY! zC*9R3JPgK_0a7p4f23XpIo;CW$}XSD>1Ynf{n*{Dn>n0)Jt4ozG(;m!Wo2+QBDn0d zeTTzh?n2dKta}CKmU@jc3*EYX&jZbD5?m1Fnibv>5TTBUq0_G8r+(aY^wFh{ZNgb< z{;Zv2J1gGO)aV%SYN@{qFtUqvt1S~R=~OGXSkP(r~tI`rUa1l`IL^_d-E@WBTaWd*PzWuo4pVQ)EiqPUa;su5#_uN_WBX$^P{hX`Uj z0pkE29|TvuKY0-Eu-famw9Wr*e%wOGihF)`N>nY&X4%VJo=iIPi6q&{aC@}HM0_1c zXF#d=JaJ1<-;{SKLta0BqEOa%>50WbTGga)B~MEsbsSlg=6SPZV81SWNi?WD6E*s* zBys2_k&{r|o7pA|kV1Zk@j#E(vD9P;70q$`sV!bppjs%DD&)}Gr9LdSUMv^WG2d&> z0k+^Z85Lv^8+VwAeVmxmHk^{xzkX3-jc23ZzhCY_HkiRce^1Bpm~;)J$G>b*LZ?%u z!0ITKJKX|P*~G)Bb}(vx`9^ZebWH;8u+~PGME1GX`XmLaTIKADZqdir(265%=aWEc zB6IK=C`HdOiPo@b{S>r-ooWqps}&&tQ$llaVNJ<{0}3lE0tu9c8s?|`iL`P+rWjR$ zac{>_=ZA{<+a%1^++^X;Yc*_e8^Z0XT{BBSQ%U4ML}-)B=VL7AfSJ+9o8Fq_&PEM{ zzsy0USDz+ozxgo-9mAv&{g_W(T$bp0p#JF~bUAXVuKCoW)P-+jqvH@gb!D(`WMtwI z`il!;i%+%oHJgGBJc%2(*~M-vv_pERt8aT1Ilapib)&4G6Ci+73s}5y%Zw8(D^}VH zvMs-P9>&j40>j;gD=RCGfPwTS58owV^wM#&J#f>jy5$V3ciCr1WKdOAodH~QmwD4I zCQE+Q9VfDxMQH=ZUXFl~b>cAW1U@5U2dglNAmDcFrI1Z920XHkb;i;>Ck);Zj(hC+ z5IJiST@3nNxlaPYx5ZeVa+TAr4u3!3T5GNf7-~{?jQF$`dmPic?ziE>fBx`cci2>l z$Y%H7!>X;I2VF2Uu(y=s<`u6 z0g~H=Ex;$paMx{(8xA-CovwAc#;jfg3C5(RRRh>=t-tcHpL?L_+hTyDqmVCO$N=xD zRzOX2QhzK&au1ycx{Bvu8G0?@}Iw&(R;^Fy}Cl*;xH-&0a*lO26Y7Kbj3l1IW(N_TPfLq3^3? z8qFpc?y@OqSFGt>&2QGJ*EhSjjn_xn{Ku}Wo>v{7=E*ll*)s_vL(SubnilO}@d)6s z{Oby$R9=S662|fT=j-3!1L?QW=BamCiQ9D>kB0lGA1XeVf!eg^p5VN3(H<1+(>?9~ zBh7uW`3*2vDLa&m1QlJr$w)3Q)}4R37vF;@)}~K4||{nx+Dzb zZ}35EWGg{%rL`}Stzb!^M`$*wj5D`u)_Hbv%KU>szUlJ`3=B%0?WMkcgj-{VuQ9xa zDMCan>E>#!EC_Dj;TO#ap6@~SPkj8oX$6O&Y97pHxop-- zyrue_9hZMeyM0lmQTe>I%Qf-BkyS#TV%4W7u%P?;Nc2vT+Qx>I_dp*Q+!vWiU-NAo z;xQGvWzW9?Iz-elR5?6cRtVtiMpgkj9PBT?T#48S23+HPmEo-dLpP0OxSsCUTe{U& z9*6NBt-xYtVJA8mauqk6#9c2C-K1`iP-0k+t)fvg*_t-zn#O z1z~iPF%=m!{o^KJde_e&L8g&GK5XntR-y)@?R~WDSqGu zmJR*Sx_Pf8l0kr!@JaoG0k8$X{pP;wI&a1y;I3LkMLCqQ`|~)wT!%O$8*tyc?4%S> zI(h85_gF_7dpEk@vOc8y&d6$BRea(EFi4!dxhTG!4hRf%FCS0kMM7R&lwg#&U8dyU zUo0COR~Jyvs<}Gr;FpEN5iTzpZ=*CVk=v9Q1m-Fybhbpm*UIu)ZK9{&Q52Mb3-mZl z+V#zQ1h9#!9Jmi!6t3ezJ0}BrAWXhT_FL|= zd);(f?P(`!g*4}WI!7HA3wt$; z&qZQFi0MJyrLXE7C@x5TO{MKP1wH|c5_hQ=+?3>3xxeXh$vL7Ca2Y^KMrxsrhl#t+ z`!S5)bOFWANz!S3hpW6|Vyoi8B6-*t@K@K?id{Y(+L2H_kH1^f>}#MFaySzDM#@S> z;@-(#3tD!}^|lfJq0W>W)-#t!SpOua*Sfv6m#D{qVZK|G(za(Q7J=DPbd+qu{XsT=h6?Vra`2h9*{0cx-T z|4C`clnlq*a{9-0ksa>c`jKLRc^SXm(Btd!Mxp3z1`&*NAdNLOO0fBiYa@}8xm$S* zIsx1-wHc45tF4l$y+yx7kqk`(R%s2mnl2 ziP0n8ZvigjWz^w*lfWbH$}glnmCcx*#P1k&2?*H(zO!RuI4nqx!OcQJy0Gs#zyG_^@(5 zvvedA7%1Lk;nZtJprxljPG)omhMFa*mp4{tW`H49Nc`D70yKfCdkuSZuJ7*TruEyv z0Qng)!oP``f6Xw@^p?%N@jJ0*A?Pl{Z`L!!Pp^YR-i)rvdei!U!&(GMkiKzAN3nYD z;E(|L^75_d(Qf{bJUQ8&x3Y4bnYr?bgsFt9(q{to)r-d<-tQZ$8qMYF53_^~bgve1 z-RGOGdYUTu&m;KvJ8T4ETa_^teKQ_w`xYyK7`)WQA)%Kyr%k(om@e+CEXl|+S)owO zDMXU^mv;;I@JO}kLYY5U&}37lB=o2xm!t9soq;6&?PoI#Z0N=hCD^j}mJP4**3;6^ zyNYm}up3FCP6OAm;pqHT-+Tsa5A*Q+aP86(A|$Ct*YdEO#=*kd%(f4Fv_90xNZ8Hu z2q0wtX~K8i;Q_!!=!#G|OVU6c^ziy5e-W%Pui0}L9o{IXS`pS2K{*4s*o{#@zUYXL zpH4|m+YJVGkx-Lm2`dGNlM1Mdg*P5fNKM@N$$N70xZM`oRr06o9v*|7ozK-Vc#jCG z8&CPS3d%u3^;ZWyxwsP!uNw7tzotlEBBJq~rMLs3jI_v`p?ix*xpMB^ySn1W6d@*( zwwiw9ncDBc! zz4_xhQdMVPkQKRCQgXUIZd@q-lj*@d+-C6RJid#GA`LCAnN#q#FCzl0YM(l?Ky1f% z#y|H98e{NQbSth^rh z$6~^Nu?r|U=I!2NK6fDwEg{8^%js_EY~VJyqZh!)`;C1ifwev876#}A8vjl&xI6H> zN-RBO6eBW+McQ>&xeE*ANL@;JXhAM{Ot#9!j|F)@N>Ck7n-ymwC}L`Dq)IMU?U2n8 zZ_$&I*ThH3%Y4ZAxj>U>(eNYPPnO)bU3zU(71qGY@3I>{DdC>xU3Povd0Xt%L{Lq1 zj8bJW!#?kJb!J2KSo)|bRP zqe}e>>@1E3^cgDLwo5zGvd@;@3;^LnW_^GNHzHG#mR--ab5@4SW_hZ0>;f0flRTbu zYCygz9K!zRCFKR8y&UzhSu#8URUfSQ#mHKM>2;BmQKyH#}Pu>U5u z@u0Q%F4Q}zEOzgUuBD|KX~`6na%9~o%>3!dgVc=`#X7e}jnpl>axT474fw$-NKsOa z;45D|Sn#JzDk;>g&PoIr4HarRpZ?ep9?ZYHrxlI6oXMUeCy}`Y0093t(hCerVd53* z#DaA|@**>z>4(+7W!x#%a8~q%R8m8ibrxKLH$ZNCIlIOeoj2YC2W*{>7;E@}HEwLA z1@hYBV=9?szPYs_T?mQhe0o06>OX$qre?)+Wb{@}LFg#$^c^}BJk5y8a}f4>d7c$H z3T@fs+wo^K%A}+jhep;U-z1?!k56fPFSzc`RJaoGyqx^+M~acJT$-apfhiW&jHJf^ z7V0=q$*M;WulLq=KOXa-!6ZVwui~_N()l{Xc|T%Sup{D=945805?R~&E^yr8Q%1=_ zPTXL1otEV;S{f?F$!>C~s0wb%#1v^XXf+Iyzw1gv68AEF%~4f8YapQ6){#}DO&5@( zjrbJT*S7h3>UluWpDplVKoMz{|J-Og5KG}e1W(fr26kc!`y$~CMf$yoO+N26+77u{ z4*|&tydMJ|%A|R{37jLz#klE!Epb9w%UATvG2?9vlD0@-V!U4&o-N z8?_qLx-cANaC~I9oLv`D$23}@85rdU8&Z85tajjlje8LT~xm!!`x zKSPi#q@=aBmWv2OC``@+$YJjCb*wlLVI7u*KHxE(dm;T8f_Dj|G%W6vw?iQw6*8)D z&8wNF>-gIp4N`WIxwAb#esiD4XjNnRs+u-7HhE2ghBT4^AYe7Pc6oPQLK^m(Q9a-! z!~UC*F9A_?;LgP}-cL`TGFo=(KA#d7(j>=jYy_<*;j?@{yWxqv!@$%lN#=XbFr8QPL+4K(p>&P^FX)go+{j(^@Ynx4Ky`HSNqcy}Dk#tZh7vMS`={TPO# zTQxBwfaLQ!k-9a#am{n@$77%drAJK&@ltnvdeD2^CFfBlL_#OjDTC*aGD_At95{r+ za(LyOZ?#blldo%k_+-xM@bfJ!8wHW}; z&ak8t4l?JuQqj52p!aq$7c|a=gb-tE~|4GHF+^ z#}MKH?Qg-)(S74e74C7689f8#G5rH%+AbXOL)(c`)A=j!`*$t z-$ys{)b>i|tKgnA#ZYLgkfg0#V!yUME5b4mdXGWZXnaMWl(Y>dEI^`R^?HWUe~7Gk z*k@T6xLjPVREO-m-_iiX_CPsK+ZA5fs5xqpYABr|pw;`xd3nwvg-d00^1FF7^wxrA z(64__{#DGz4>c=?wvi708ym$J^1hgNZTC~>@-DU-Enl$~V^a%_^zg{h zh5+=%1E6?##5n4D5Ap0DD}APCTxOVWk}Cv?63XJWki;y@6ZVaql;8;G<)t_QxI{M+ zzaR^>|9P!xUIxO*#I~^U8_`=0^co?r#U%u@JkR_*_c%_L+Jg?!RC=NrPn#L%e_V%u zl(vu*xR|1Af@>^>iH5JsYg>@Ud5}Jh6LQUilXfRJs94-?+6-$Cl!Ruu;qpn1M-7dQ@zJNH#-LAO zcU5Rlqe2LB7DLT4=H;LtpTSy^t7Sz#KkP31*UD;~Pz8~pH;^UKZ-|(|AFf}X6Sohm z;p)1To`$kRj@F_6fa&}BwJ)%57?rCHx-f4W8c%SJc$;{9f{1`Wa(l>sTV7o#P8~p; zMi!#KAY#{WMEEq(1qh8Vqdm`YpXU>sj&R;(itd|=$p`b9`LDuHPLIkO-XfxzOgo+3 zRpV|GZ|{od5GJiPNAfPAnXh;(wYH`q3D2=3OcpAZCQ`Ip5V@%L)HY z$1PG@MNO+ArVllifw)n!dzEJn=$uyfJYWt25CS%!cW?tZQ1RN=V;7>F8gEe-2lQgc zmig(hVY0Uol!cVZZp`*((F2Lx6L+C^#;lQ(m^{T2W(gSbF@jiji5hqqP;f)OJl!c) zmW@8LJ_o8u++(B9fxTfZC7xxSvl0Hus_7bT<^J5e%>Gl(pXHN-bqIhaa%)E#po_Rc zpSo;Gl3}X%Q9lUiA3_~dC{-DMuKgH(pSmJT$3wLuT#-De)b-S|$q4;volD-0b}ql$ zSqW~A3B$psD0+go*56r+OZ7Vp6I|K3hfV-qsPA>D;4Eq&b0FdG*zt&Fd0lORaZ3}w z;Et1k!xUlm04zeTqN5}l)1&3nMu_m}%^e!znfHG?+j})1FeqArnA5%<5Qfjrkky%3p=uV5q@;XaPhq2;!`W)4SH-|9$&IzEr`z= zOY-^G(OE#wGY+yL_2 z*y}Of>&RkQm4#;g)F{J!Rr@4J{-#~>WkR8D6U=T%0`k7+=;w65^=Fit>8MLH7l}X$ zm+(tqcON=hIe?!-H(t45ZAo`Z1<}F|dU1|LvmgzbnH)nnbZRMSp3k=~wu(({~;A*1L01 z-^jA^uf<^ajlps8`zHqH*ZtUZ)|-}ACWsT!wsyT5Z*^fE4T+%oRQ>f78Qg%F!SSV* zCTqBIxTVb`JJApjdZQV#tOB9Gd^DFTLoPRsL60%3P`Z|hSv{b&aj~+?hD1=;D@6Vn zrH%(EF9jJH88UUP>a{pNkDeYZ(F}+UUpNFE?S|FE+ty-3IGKKvX8vbPO9jC0E1(bY z0@08OsOuUe{fe#K_eYu&esP^rej?o+bc*r+aL}VI4KX7NwQ7d3D3eB;kY{Z=2w<+N zsw&iIW(N<%{$hsrK9arf-PoG^*5w*NbAYk+{wh!GXmj*`{+;JJ|Dqbb`YQm-KX?)$ zH_|D9bTq_$TOXiPc~tQ)=~?_aY$G1@L14Fn-OY|}*b0XcCcMmY{qpeyqx!e3Wj7V$ zqG{?EZDH#+nO@~LEV1F;-4)657wEp|f5JP``JWH2S#|^yXltZ3JJPgLA^)jDcvpBH z#C~w$0crh$p`xP$iyYz1M#Ic0F0KaFduq8W;_~MWH%8*2&v&nCSeXDA%x=5f=~eoZP!0SGpK|=Y zt(ELon(~C_7q=+V>3?Qv{o)qg@g2a)c)WieMfv?bc94yUOaN){Z*_W3=k+}`I;sai zbD+xe)+3(FA&(r>-#boIH%|KZhscyew)>5R@+SX9Qhfh^;TJf1;L!T)e(yFpRJ&gr)64tyulH8_zwzF$yohP;FaR`^>@;>H z7=Zx+5X*OTrzrQ~cxEG-oYAR%bb%2APza)x8ilJ^vmT@h8$QqIO08C z;^SKkB=NHAw|~`1Gz1Vg5QnfQcPOYoR%qwS29QV;}z7oJTIdHmC2uaCrX?di&qmod5qj{GB8G|G8g@*ZEje zL$hEguAub)7BlVtWJ~i}AJ2`~k5v;^1JxA5lHe{abShe!(t|W+N*3B~U(P!WDmnnS z*+dB^p5E-N`OV6j3i@@PdP)8Z*!F+&=A4ZeGUMiz^vC@~c(6{}uk359%asMp3OiSk z*lV-wkARHPOYB$1c<`_2=f6P!e@8#tEa{K`Sl#-sM~ytvKL}-hs=6P_;-a`R3^)y@ zN3xca3uqL)atg8q^obrk{P>H=mit!$!PWXuI=lV);Qua>Ej=W@S&Pstw~a+T*!d(2 z&xHm*JcsNGY$a$!hjb+L>#36YSE#;2Ab*<*B@rrf%J@59_zf}gZ@N`5pQS_L@%Z30 z^tD;ch6NDuXR0VDjnA~Kg6l6tn@d*_d3c#I3L;3ESfCr>^aO<-FS1p?ZxV1>b03FuwcH-ZI;!!=22?`pY9sU3YH|!Dv zI4v3DtV+Ahm{pzY<-E~!P z{?~Ud)pz-qMNDvCsLo%+o&SXNyt4v$=K0OqD$2^oCxae50CeoTM@444MgiO7s5fD9 zceS`w|EnvJ?LG$}|CE{js@!=8IxCh1ng>Oy{{}h>UI8?0S%cB%wHVuzbQ^yj`ueX+ z{fOc|XO?_UPk4AZ>=ip34lp$wP(<_!7GImv_oMm!GFXLcdGL#p%N zO_99*Ht$wUOmZJwXX*B7dfVFLB1`kL}N?F-3lKYg6<$F&(|r} z1NRGybx0PSwCtNUNTwf_9!H;>gWGLR9#1a;<^$QF}R27dYENSC+UiI-A} zdM`SX;osoWEZEsD60faX{D+?$c&m90GAbMi4f4kdGzo_agc*v2M@37IjK&5~=-66v zYYx~28*$GhwA8$6K1FomoVn!Nz2tmMu)TEpR58gkJ`jpGE3pg#h5}z>8=3UJIdmxkb{gsu zg-o$jRNs?NF0qeKt~1IfC!Y;5lvDLac%Y0yX_tA@qK@L%G-y3p4_F?{H73&VIPS#P zN2#+Uy5=&~6)4D~Ml_q5<*5V%Nm>o@z{uqE=`>gtBst{nrjtID>=o1Zk#`L~>ePal zOS^V*D*8p`T+1TRc@-_9do7Q6LbMW_bD1;wLL=ZwRQp3D@FUjzCx#!O`XB0>+>TW^ z_;!yR!}Ds7qF2~V#$C5wpm*EzbTkgJ(jWXl2Thgkr#?jbc<(>I5?me}Xy@Yzr&3Wj zLT?B1edVL>e~(4)u5zk2F>w2&GNEb`NF-%fH>;$fCD`XcucQNt@{0ZNMG1iznn^0m z+5#H(%k}BZ6@tM=7Bd3LyE1d!ccyRqaWR4pI7_=XN)(f69)+&*SSS0LF*__)r-Q|-15jJVyj4{rJTXOk~{EmwI<$AwPOcL55VdN}XYF?D$x9$KeoKn^}@P>FR%}_R> zV{QU#W)uB_p`}QAD)N#>lV?w)J!-4HnFPwjp4T1)siK9;Gx)Zzv8}?$8mT6Oq_$bS z)}XOLdrU+|nD#;jTUc+qCnD!UFM3jKd}4w%4))6AWlFXWpX?R$_^is>pyD(c2E3;r zte2d5%BO!)gW2--XCp%da=vI$WDl~{h?Kzeg&C3|uc~_YQbk!r6}8U9nva8e2UoB> zg&;ug%VuKHhbjq94LW~ZRbh+j|9VU-5k)E(OV*`~L)+V5BxM?|Qq|dIYY0h1G7eIX zeZF1riWrK=N(|jUZZ|Op%+N*$2j4dcmmR3}`?<+OZ~3h2<|ZGo&>Gzr5x9r&?mxbM z+9P$IdSh~t)V+|{6K1cjsVF)tKmy<1yLa#Q_PcI+qboG{j z4GjVFwRbHN4;y6maV)K7vTvE^PGH#*)V?$9V9Al$_boP)Of$?Z<={t5Qu(*%iDT-C z1cgx*!$k%2U9bCqVKk%udKOzpBiMQ4H!&jj3V{z5ST)_vgzA8%gy&|MpJzty6{o<( z$tER(YC}Suz0R{&+{Bd*m5}R1sol^W?K;=hmmk z8XlEU6?SomWP;m!!vM0@y1F@=3fYt7q7l>yIf=0Cg0}-VN!rTf$0$4#(lV$w)MeS|c0n}DQycmX z)l?Y|uHtBZOxf)8nB#BS&%rb}9ddOr0k=!S1S)Lhr?%VmuIE0eiawi`V?{4HUB@zA zRFvw96YEy9Bqc#T@l6edqD9be2068l40Fv=GeP;moLwE@59OWGff1TxF6M#ZaijK6 zh0|_7KjKIpKf;zIYMkcUO6YlkzqzNVpiZ8d3C7HRV~~+81%bpFt*hy3YSLOYRf}2> zmpLz(OUTG5%qU6P*yLKq$A%SE1sxt4ovhf_*w4KV;aS{Zj56$f%hY!3v+twN>$H=w zyt2~PE5Eh5nNeG-Q7$c`a2rKVVG3hndH3uSg80Y3@gk;s;H+Yj^msAWEC50nlautg z9mHwB9(n|yQ3av|G7=Jj+xG=jhA&ZGSeINBS0!QcyM9w*vILQ1 z@v2+LitW;266utiE~-9|_q(<|sBdlhy&o@A+3ee35>1fRPQtCdq9UV_36&_x+5DLu z@z>m3L~0tvL5kiH3Y*X;s+ojej53T690V57veNPdtL45}d1&?TD@`bwVGvK=0QTC^ z-1&eb#CQr>4uqP{PNF8flo{F@5fMpTbB#$h#Pu{DZLB-2e8+4HcAl%VMH6X$H2m2# z$o?u+7%$sHn$x|DylsV{!FlsT0~W0StwbRqTS~4>eXUBz6Vqw+x0f^wR-r4B_k?1@ zqBA-|Tvn<(Y4P(j%i6CwSTa<-dkL3t67wRTpETFfutvnbjxbQ0&tgj1Y4wFnmCVxz z&oOa}3`281hkK@|^r&}jqVFV4Tdi%a{l$&J|5n)V>CL`{)s8Rgq408z~ozj*!6^9G%b8J z>qhq@%VPoaY3P1TSa|lKSu~}Qk zIh$|DFmlZ8fL(kUIY-VYHg=@1E<{$Hc}o|KHGyFi1T(h1D>|hQCTD8 zI*L~lJn=&taV59B##YnM3=2*k?V%Eef=TU5*F#lHElyZ-0jgM`^ z^oP`vZ}KPk;wFF`z}l1*)OGibP``(ZilY(+`STNoZCm{2k#4kFV0ND zV_`fflVK5J2B)cWAT`jk$$YvQS}dpD=e3wV6dtYlDyR5Yyzad$ga!D6t{;1oc%2MJ+PI;vL6iZzj*OO9 z)-rae;)WC3_et5)lRP1CJ;sj;geh%JQUrt$}J;m|z^dp7pePy)B0o{sz ztAWxWTSs7`D7Ybm;zmdhpK2tJY@E1$(7?6Kc;+KM{ zO^?!vwA$ZM;Of*Q44% zf?K3jqTxK?v6toQE@=iUbYpa2PwmAiB;NVDo=k<8Ys1yH6jts4%1Vozvl8+1{&@0L z5)GNHKsUrWHkA}Y@&Ok_U8Jf|Pb|+#&Kk;>- zn^%9mhN_8^1e?}KH(-r^`#J`1-*j=14EtCL&owW`Sj$~$TKdSJnVdsH^qGC}1UAMQ zc7VJgY3s4-gSiLf!vxA<8&}BSFC~U3{SVWAcyW+c#Cn_c$UZgQgj4rFF2aaNvL7xX z!?O@6DxaxxY%E6xs}^B(Uj%6$&+iWYcxbR4!(-8eU%ELMwiF&QrwBP&AD5<*Qu3TU zT$PTls8FKUgU>mM%O-PX%l#_-9D##}j<>dtdz-q2L2l7i&=wv!rd*U$+F0k|yBeNJT0YmXZ8>){Jl1ot zdAXzCN$^%$j7LgVQekLNw(BxpWy3@CDL%O!#lZMs>dmIKpn6UC_s~a}h_){>NU+sQ zbE(X>mqntVIA&1QgC4jPi1rv7KVf>3OQ1k(O0NWp;x{qw7~-NwMSSi;GwL483U%b_ zs)o`zDoaSFLlYtsUSy|7>J^+aN{@8}ZkG(?<>*m8hc2^KDI5%AZ%#Bu9G4w_<7SR- z@W_|E{qn{5?W(I176&&9Imn!^_(J%RI5C@Te4>tBtz;LcRwkCOR`mz5AWNsU1SJ;* z5t@fGU!BUryGhl(vAe%t!!5=j(qE1qnY;;=Hw+mO&cJ7TN6@nOz2n}3wNGu9HN(qt z!_A!wqmC_c3%;_8={i)`yX|GhVIJ0;FA|=YQ9>TLy6&lXAMHbASiuu%x*2Llv7p|_ zRG~+kV`{1j3e>a{-);+aE8a(CkWso`3=Z`u#fuTTwA$k;D%usqFbk?nC%%04`ETA^ zqDb;of#3a&gXBNY=~CB{(jKZx*r?l-551tyH>yt3*=ok<-tN>RQnN2V0)2?4&Xj!V zy3@CosfrfaXg-R7H|dm;02=CrS$s8obZ+h~nQVfRQHDG3A;B2@f_b)lr_D)c^utTi z=(#ty5dRRffRj(qsCXxF-4PKk=FCRZMyBrzB@)(9Ebubpt7E`I3yelW? zrPrR=L`$%0LS9JTPPyq35fRRN{fT)b_EXt45RP{+Ra1ep{hqX< zf<}YWD{Z&k6oqNZ`wA2!m@DLfe=1tZ31M^#&P_;0+8!oIHkg1kNKI94erV7)%%!&G zq?@_~V*ZdPw#sDJp+Mgvo)i$Sa}do7*PneYx?DM-Tq~R5DcrS{OspvO4Uw9geC>?H zpIP=grc8UE)4JAwz#VnAS-n*9vPSFJkJ(AO?1_Bys1$x)WvHLARgL@e){NFyX`m1N zD%4Dohl^pS(ziERp{u=TiEwX>?h^jJoE*zN%dng|Vgfs2iE25oR}g9#&+BvgwcBinlcmhLZAUdCL(Z?dYT(j~YTPAR@dZ$M{*N8wQ)lmV{eUsR>aeW8gOU2jZO#hkvB6crNa?!V!kqeWpDa~0`*nIG)bJV;?Q zUH~#pGcanCiju_FP+JA@?{GB;UPDrtEOH32pB-rXWA>gV#Kba?*S zfZlTp$ZB2Nv9Q&dM_xk-cpX8{h&UPI4<{zI+k5Nr__MIOKde?*6O+Ar@ONHJ!XJSM zR#uek6SM0J0N-zWbAZUHW3BZpnP*X*&2!T&@|oQ?ooC8aEkcJKy0+O-pDNg-GIO6O z!L+WU5*qAPLIVzVz)py!MIH5SV+SAMgYUazx|mHWxL*S91+L_tr&)$GB$?!CX%4&7 zbLGz-e7`)W<=tTKL+LaYuj7k3!Nd;+30T9{ec2r*zUjY(sc0XFx)o{XHaKKP%Tax4$XEM+`Z9Z(P&@>pip~>T z{gQjZXtRY!rhW!%Qfby( zS7l_?wkeK`N6V|ItCQ!CueOM%%NxG8mHb=F1uQAS*L%KYNC_q4sNj=O23(ymRh4gw zg3s3HbS-0?d~^u%1bc@?yHW5E`221)8S&V zdb)%`EkTxW4OQ&iEkkUQUR7ijDpVugOWE-rS#r-%qfb<8%z^Mi>{uE zHY|l92Hd&~agiQLIsXU`8W7%ciIGYjd2!oj%w>+gQ&bN*4UM{qkF54ooojut%IAud z$0(Z)tyS?^9Hq`0$oe~znman^`%l6K$V{&;ZxtxM^DA@D!HIv8C!wVi9*ohy;9&0!Ik>zcy=-v^U+B%8Zynl2G9*!OQ< z(<;BN=~&>Jj%u=tKv`ZKrp6Cz98zVFW3wA#z3ii6Cb5)Fdv@r{wgxR5F4CPmT4@O=XcFP@ zqsVf~J{5qn6IJR`o|Mnyx>crYQ9PH+w}^VGNWo>QTM`gvE}dk z4Y|;QPhcq(wz!a#Dc9up}Y!-nFG{lnw3{g0ZUI&BfKlpGsZWbwF+ z17kt{xc5{_0zxczmH$|eWOu$ih*0wLChAa^w1z~+mhiMJmeHp-+Ed-Ss|l1jLT5jl zSHrH%Q|V%~W0lhStHRK`LoW{4fM;_i{W90l%+#l{WBlswNLpW@5=19e9G_jpeOmbn zSh*3v)}pdR9CF$nHKF5jKSsX@2$gCSo9~DHyE z@CQ!U{Q@i;+^(a2MRMv~>di<3UpPA(%$!Xp<3U4?$L8jiLH8uJ;aiAlD>t3nc2Nk# z0J&K7J`PueyyK!^gZbnxPG-WQvQ&(Y!UlSevPLT%w%0Ze5k{x9Qfbt9`uuy}GV2rN zY|#J1-djdR*}rSUw~9zgh#(*x4oEqOG)OlLB@Ge-NOy;HNe?k}w{)jS5218-cX#++ z=)Ir)?zQ&+zwdXg{pDHf{S1p4uIu+Z<2aA=I3)R6I!PY%aUy5sx_$D|fFvX(QfDF~ zhuy8(1T_|Cm^Q>w76j)>4y@7jjwvP(-url`r_P7P$*m+x0{@8+-`jrqG* z*(~Wby}zh;6etjT3)pwGR-#hFMhlm}n!sk0Zj&M9bH$w-mc2PS;=wj6Cot6cwS z*~FbxZpgfdKDOi-MDK_0A;a zed`B1&gqM3Om}u^29O|aw{sfaa1`xlqOuVlO;?DH*90`4!)(UVR?au+coA9A@rfth_501u*}VxasHTVHrpUGjztE0{VXOaHv7ykPv5krc%1HS+0j6@qxgC8Y*Q9nWh$3L zK~{G5W}g1OxhlnK%&ImH!i>&h&6f}?*kg7`j~IZX8?82lzCQ7Ia!LY{T4xyj)AkV- zMa9LCe@GX8o#pR4Gg7D#{g;zk_RATUJkeMgQ_1mKc~s*rV!xCip=g7?1zI}O`+Pp* z^H2?aD7FN*2!g-YJhuBaMlo(?C{jWm^$o`6GxXk>$kx4-xB^ABghSoiW<~p`X?B4j zr2;9uK-)lk!|FE*(UZuE!gxY}v{vZmi$N^kT(=nYXB{fkZ%@$Go@VDpWyH!#eozSS zo&$}q)dJKC8Dr{vq_nCHX1tF@K&7~u^(f- zIYN8O^`;gD_y~0qvM&(ir>~W!Mk62LLIZ=H0!A|G^V0Rc_)~Pcqz3084;bNPj)s0g zHt6cTvFSVQI6)fLDwvS;8Y@-1SRT4q^Vn6D1KR=F#dsn88%gII-(>LW*#Ea|ZbxkR; zbs{>uKSnfIB*gCEOeanI*PAH>TundK!$)HDq!|(<9C)S>k>H(YhGJ}7%&2H7_5&#N z@@jKMzm%IM+Ydl1MUcvp8?cPMq%8~YW;<8s*a}0W;cq{H!9#8Mh@bQDigoCIvk-t> z$L#Tqc@WaZFa2u05O`Y+=;=skr)W%tRlTw0`6osG{qOutK?1=GlPm-pgxp1Bb|Gd`y=%Xx3`o`m|UuQ zNGU@|T(9BjPAo8SNy#(H>$=FvABpE6ZCDVv8*9?w-Q?)!5|&Ra7*c;K-(^71Z9`mb ziS2r$I^SSq3Fmc=m7V@ng9W|EPd+N9Q;K|5%*SVrtIgyh)W%NszYc^w#xd^IrP0wm zwFuuuUTg0iS(NaAf~a7Z!E?|v^PI&!^udz3Y5B~|FYsn7o@jM(d(@y!HX3%q{9dz1 zbY@4T)IGcF(_t0E-~@9P5tqJ8{b-sFxq1;{^yvV*&@q24W%|Kw+UyINVPiHdlXnV& zlW%AS;CZ^|c_@}2hZWx7AVjgGmHOP-eOYQ9EJHOJY@FY)xO;b0Hd2@5IXmF78~K7! zK9MO!_I)VKvoTUy;lL9G38k@IquCC#*^iEHS!QJjhCm57KSf0i;Z+S(w&EMk&EswN zxd?N${Yt&xkgRijq2JQa@g^IBum38bjpD&S0+s_&e*=j`loT+y?t*}ugj$d+LF{OD zp`_3{ze&X8(@i?XD8+=Hk3bzpN18=Ys05>vpo>b0CAHGvaqF5`QrfHQJLya71YCZp zvY$iMY1=Mto}Yx{#bNi=8`vER>zjTeF7?**AEK!Uk#r|~Sr~gIm4sj9+}88f{!YWg@%+4aVd@cZk^NpL)8 zq-g#3a`-uW)pZ)g5cL0{uZ#Zu*V%zT{`m!!77&^vpS9{nyypg>nl*p3-jB!)kzJ)` zYqv7Hd>|NMa3tLO6{TZ>vB|sb?j5hD{{(mbZQWY=LO5x+Qlaat4m^Om25!4#j6P(T$wxt+--m(K8TrB^dX;ngY5Mv#_p^!p5I zaYHxtw*fzyu5*BaPJCVT)A#o;n)?1ln~RdR{pXAJ=Mzzva#L-8($U`D{*!wF{~rcs zlBmBZV<`@hyk*Hr3@g#^TYIl9f1-RPeg=}n_v)JES$V0ZO%PDS*bkO)X4U&Ak!42M zSNdNcL?xX;d2ldVPj~l6rShLY%h>)A4ygGI>7b~pu~BnqcsP?OMjogJXjV5~(C_R;8}xo$t0Z*B=>7Xtz|&vc;NA2+ zX}h>E{q;+zHCk8dABCt5O{9ayQKXE(kdTn6Rz*+<_7%e4B;O)06k@rP(}bGUP9v$V zY2v#7j{Z6m5Z2zA`GkQBoK`-%Y79f!=>KvfTIC|wz;tv$pO}2aI%EIQ^SA29eSg&p zaOINk!0JyV(isF-h_eJFwVp32D8`0~kZUhxNx9Wuk?>Yj=;JIKOMFIh!(sUzQ)1sb z<~I~jQa!-64(?nmYAY|{Vl}K7_>7h6VQ)5~J6up6R(ZUkO+gfFwxWI2Gea0zPw~bFfSuFfo zF;MrVjB~qmr*Pn9fZDpH$+xB%;PcD=Y2Iddq+@s#VqWK!OF_|h}{tVA3g|Kk+h;B>H9N@o8&F1MyzQf?x{8k zVI0M@H~-1q@CC^WRx2zKDy5syEB?)Z40zEA_s_zI3dZMFWk1Fl_vW1uNv!)Qe3@nY z=1;V=@yZpqhhvj52T4eH5m!9^N7p0beJ&*g;Iug11&_q-YJp%?6xJ#}QYP85Cd6CH z?ckg7pZ>1;<98_JL~4y!%ydiwQA*O~7qw2F{dHmYkdGiO@O&cJE8n4HW9gXR*L*t& zwE(D&y`idM^4~Fx=>TuACt?rG^b|E-?H?v0@wA}7QrakYX)<6{j_HuGx%N8#-_O>| zH0t-Mu=;nWf`^fjF@{O29?7Enp9ZPqsP~mAbPAwtSR64{!f!N-dRnf9)fFiwbO_8cPtNl`+!Qb zDrX*Ybuc=nwyxPZkz}uWrfd|}R&@L)%XU9b!6zV)8ZXv*#BT79PniU{`VTxhm_4F5 z^oh)G^cDNlL(y6jnU;V1^g_o1TE0=~|I?3ACI9me=`HTqV*H6oBq_gN68ZnNA44Jv zx-QwjbrfPq0)=_~4Q~EtU>pg+ZB6Q^YqBNWO}5_qvkYts65Nc8jUno6*QuGAOSA$AzhZgrx4` zHg9k|o5M$1T3Vu}p%DSIvSw@4Sm2V8&Eq$0+PRBv!zumBWgYVmKLZ5>aK@&*?*qWF znr3ZjRoOAejNuXo|$SYLe zJmKHTWZ39`>udUdj_k3qv7u&U{A%v-T{}}2*lJJ!5|MB|!$Tv=-@#a9VTEm5kT=Ou zSy@6zMy5i!C=m0Zbx#j&Y688aZCL07Ydj!^{x1hmAVlzpae3U-LTE#Hrd4+a#i79B zGW4qVLsFVCuVNNtEI4}2blCsO#X2UAl?cwvQT8F%pxo)XAS)9E#h7U4A@H#5-v15& zKmTn$Bm&}^mssaNNW0k14^>LEV~ApK%ZwZxR=ADA-$*O~^NXmWhj)N%BNc7eI1cjj z->H()$=(<)z3L6!R7F~1Wu=R;FH-CrjCwDP%ko{>6q(@v&~_6mp7u6x^;yT;-{m-v z0bP3Cw_#)V3v?cEHTnP8HFQmrrhR_Sh+ZkOYpDuZuqWt_-=XpXwJm zDbxK~QK+YK6{h?ov!mq0GEs6ix^gn5P@u3`#`uq#?MwgfnhlWk=JHf*z(RxiYc71M zusRclv9M*EP z${dxP%m@gO&EHms`o-Lf4mTcl5`(Rmgq<%I1N3#pUq1SG4_f$sMjn{H10j(Ina*wo z{|OGuMP;D4EFFNI#b-w)S6g~23`udNHq-5Me8Qx40z9qnNG(Ppp=E+Ge*5uDc&e{ zIoqKF(tx>n&CDGUz1Psof|}uU{8wwMRh= zQ|#@tFn9)Ca>Uaaih5^VGhI%*3R7xbRc!K?aRNi4eOZ1z*b#psm0m=Z%otY12+RX) zhpV70ZPVTQs_%~fjuIXJ5hWbayA3WNBwwTrDdFyyy*f{;uLCQu&uUs2?^WV+OTs8I zosnl`S%Lo^77n16E|!HVC9wiN$W_BBnJvU<&_VtuB-ThZ^BxU5Gj)o44f}kThvA+x zI9Oi#e@gxTCJcGSQ@>AVXdFJONWGN?l-cJ|Q`+3lDpE(2VR3QWW(2DDCfU?^e{q@1 zdjRL9H#3}%{$7`LwNU5QM71GMBpu_4UW%8WJ!H35{_pE0QOwK~_MUl%5+8-yZkGtX z&3O0?1Z1k%xYPIun98m3solT-)c^MVFFg;!(2?z%CYl%{g!gqDsE(&t+7@Zl$nu?} zW9imJMMhFwED5JeFB_lr;v?oft^qnjuj$yD`qwmw!`GC_YF;KPnES#bvWWScHFom` z)-&0Ec`1_v--}@*?nS|+fbC4Ti#D=Y?IxFw_Q^MbgyouUd$y^=`WX=XyZo|Lb9yw1IATMYw9(Z!tf_}6-AOl zEX^%v_;8VeduPW*MT5oFB{d;hV`D*hDsNHL&->6U64SV#?L=&iKXkDt`fZ>z=f&eb$^e5}l>hXyu zVhvtocrVG(9#?5wjv%)HlC;pta;VML2p#;Lc>_=PMAv^;_(DX-@9lDlLljT})+ArUp> zbUB(QGoYjFoEoxiPX_Q{qVkZ+icXw1Q%RVGT-dQDgY;bt?PKPKqa=EU3N?&H;WmaV zSzNK`ey85IMvg&o9ws`RS{i##Fn}p`v{VNZo#M;rXUkcpE=enx;xJOJn-EAh%oP~M z!wPpHq9^EK4HhqVm`6{`j!$jYg1LTRSB^x~=N)1qWMKkLiDU#}M&pO-ElpUPBo`Aq zPqlkXVijm!hLI$Jp7|r6@Av-RF49QGRwh+l8F*xIP8f6v48sMerz`d&l6dSp(RI{K zf~8a6$=4egI&Dz|09)2Qk(sZu7W$k4mQUa{R|mCX6$!sXmfh}@NC;GvUg!xn;^4TA z?C$5V4nzNoLrg`MRyn|TFp1l-csBE$$ab3!%X*tz;5$LMT4yya8eA&?YbsE0Oq;Cs zUez8|kRh{3wMt-4CCuH;UZ+gAG z6YSnBF9+A{)o)X?v#ZRxpKGbLcr+#Px_)Q|#O-?J3q>3DI>&5hgbKI*{Z7t5;Z^c-a3bRo-~8Ww>s0?e&mIyGQOul-16 zmg6}Tu3+-!J)bDe%M%A;A^#@dz35;*MDO5wzoo=azOn8M5D}EWbxoNxBTH98a>ZPZ zPj%hnoMj8E$_6>#n;F04uzFwbxX+Y&v+}CR-{xpV;`ZG8=Wcd(_E3oqKb+UKnmkxl zQZiUYW4&zIb)Z1G@WlE1{1i}(ozC}0(MUB`vwF73@!wWz*4c>sBAN62bz9#Lqf>03 zoHNyD`Zv=Gr2BYJPML}cf?c-bMoP|-gne?1S@wy3ra~ME2a@>XK6OV0f;h^J7><{W^Y4d8%AE=x^%G0%7muj(IPEOQ{ z?oAc}70NgNMBgs>LOQggDd>~H#?_Ns31y%2sQl2QWC3RoFdvaYRRvL9zd|gCs%`#W za>{dfAzbJFAvo)GKIZCAxXYwV?dmRgcxeULjGP4nogS_nK z!cku~<~2XQW-yO%tX7tC&oo+|u~_+x1P9Mt3jiWq8PhoSZVhr{Xq47)o8d~l%^b=? zk%IS6UG=4^!bsN(e~_B!u!H;~`lP*P&Jdj@6&to(`UX)eXXXQKM*7MU=|wc){_W76 z5jUA*qI-<->}cv#16C@Z^=pqJa&gfWAPu}-z!K9!vNe*6wvfaYYIS%ei{CwW8!+PS zI*%HJ`Y^JML5L-h)wUqY6wIqYs6LkG`SG3QEZyrl_qHb8FSqZcS+XHxM?G)K;~4|t zTxL)taNlwp8(J1!LsPMtkB;m4UW|rCoyhqOpp7g8NX*^|-Frk<20@S?tb08n{rNqv zM9AyKqdEY0?5}Xy8p&4|z62Z)UK6uhz;>r=T*n0V>Huxy(<4AxZe>ivax|Zd7qf^^ zTuPP>)tlhTSYkqW>xU;`R^k07jpx_R@li~PTgTR@tHX4)`B0`9mfY%j^Et2c&0!aB z?^z%3`3x2R?WSBJs)j=%WR0R0UOF?dYxR;t*47+;`Vo22WPKnx2Vv?muI1Ph?s=on z*b9H|aIs8(VYycZcDN>Mz}s$f>CW>-by@f=ATk=m=MmU8V6&d=i1^`oXZyq6{LUkJxAX=u z`V(xvp54Dv@8J5^__-H3YNaU1^<+@&^j2s_qf@G@c0QP(@Gyf-JGk?<`Ql>j0o%b5 zXm1>_(3ChBVxo-bkAEWsC|d5>dy+ACWau21?*j%uMLJv^O)@i7SUC%G)Mr%|vs|}F zoYf++&l|D#7@TDNu3I_$0okN&g0F*jfTPwBv;XvN8xGY2^7Hm53c1ruZEBA{cVT&U~nLW*IZ;+gKjI8HLeL& z86=^2h?~?)4tuZaG_#BOQr@xZDu{!@sU&BMr_MJaLg!iB9>O=^kK9H+QUO?q*5~`1 zTal8Iz48j`O~J9Hie;nT z$dGzzfroH$bPQ+K%9dm(w(7|bVe-21^DfiLrHq8*7DI9xaXZ@F8LSFL^HI#Pr=O&pMTg$i8W5&s#7wU0m=@YmvyNcy#gO}q$XVA(nX51d=J}Ufxs{)6Pgasp!kbz$ zJ9B;U18eqJe08?ZN4IQ)Wvs#|yu1c+XfCxF?NirzXb>{W-;?O7A!9nB+!7+1wy`+C z!sxWz$U7rfG^i{kBQx=2{wltV>HR1KzKTx3+?&(SebPxKwuT6tp>Syi)@wg}?T@2B z4HtCyu?IHw=eEJE_JKHT1YTLOy9J)?kBen@O8YiK=iNYRTYt-B2-#x4a)Z`gM)8_~!5Oi7h+qz=%g6%XY z#())e1kgcjEq{yS#5+NcMDFqn)B^=p``=DP)?@GX37ZSHu+LceoNSFsc~bcPG(w}a zpqL`0jHieq%rF-474+(hj*iY;=f4^ZcWGw$bz0&X^XoG8*WhWOB=RAq8P0>JItKFO zxxXFZQY`zKg^%m^o3DAa7J3f>1)kG5vulon57^BknG2TqQ8+KsUl<0y^V|N59SOpB z4FA#1-h7D38?JNbTW%FL2{3l}SJ;KS1=|^g2foM4w8q)pV8Xqb`;5`+&^m6KK5`5D z2?=#uk)*c_@LyF7XZU1G~wYOLdhoZfUFYx4wOE(_RJG5ncQW)`A3} z1cL6{U??Dq?Lw+RYk`#hIlK<;od~`V5SYIAh`37VC@<0Pa;SaaZR~{2-0ytRZ|RZw z1HGE{bOU`H?{1HRKI1d7!cO_WNx8mqX>5Xo?F=(DZRoSz?-m$pe_a9gEklJw=V;cJ zq;Qd>$LGh^%tMWD!{Q_<6gfjmJd({zzhK}19y!1EOk>qT>1HO? zC#@TIbfX_e8;ApO(vWm)c}xjdR!%)1bdX1etv1BcC8w}0SNsHQ#<4t zaA)(0+Rx&jeknu_L1>>!MZNJ!AZ4HoCJl7@WpuXw|Tg=PT&hK$L~qbdmc{rB*8Z$~BB~ zP$*lgsv#isl(fiX@5%4?6E?NEGSe2cQ5VsusOS}cnq%Hb)zVatV|++(3otoxGV1Ev&oI@IPD=zob(dl&VC8>y-SV^I&l1pvr}9> zFvXyG0$985;yZhDIpEm5eCZw742kf-w!BQ67{ZGM@QJOku~zf5;^Y4@uBTR*y{4-E~CdUyNj!=R&k zw-uDg3H&7j%98Yn#^kQO6-@XuPaV)e3+u=t^0Us*w#bWL{tfw`9E=1|vvjGQoQasz z2%bOR#6Ye!3OgE6lylzWUGms&I-h%8OUB`PX4cAkO(EoO9E9$TUrw~Jy4pqRDm~xT z;&hmS_8me~D;M_glle+_q&f#Hv&r^zK95O3{IeheWr~7DwZF=#(|C@$sZ!!hLdpHF z`?^^o6BA#5^dU%Rx11{qf6%2D{3)D)l=k|`Ug8qe9&bj2o%xUwF$JhB=WMs4s_7hP zJ_01T%rU+b?b++6f;cQ@xRFcLDhxkW2kQA-lS_p}9v0iDV=zQq4K;Fv5p-J5PB4dM z%Dj~X(3Yiy_NGgqNpg2zBnnZ4mq7a767eKm1CTlh)_;5cqY?z;Oy7i_VhM#xj;|Dy zM6aA+1l6qkCFe>5WBWEIPa`Ywz~l*|C}=o;@Z)aU*YIW{rMFmGu*U}N>_6UZeGfMM zA`DTKXR;q_KAgF72lB^glh$QVZfpx?svV2rXNMm~LES^m7*tM7@Vi z`73pNrhX$B$gf;jgsD(jP_;GN<@schfQwyFv==8TR{^{sE@%F=<7jtXAyYEnaf)+( z%JVZ1h;!Eto`fP0W6+oy4EjlHoYr5vz=P(-6jAlrRVk+czA>k5wst3`?1;N=%LKn9 zwI8_K>IWU+Q)n&jqg?Lx_uMX%k3tf{>VOz6+(Biuy{aJJB)dJCE9SjI&$*wxJF1{Q zNv=QoMh=3TS&^iIA*)jy_q`l>v!IwN>D7jJp<_ec#Imi$bi%3<&6G&_i^T#qcmF?g z$_ld~t+f?{-oDK}tXRV&ao0K%8NiE+L$?xRXI{lK@9nk>+1ZI*E9Yd>`DY8T=N^WH zoYd%a61S&>!`=rRHj3mof^dO}rTfXI`pXD)-CkYM7ssQdWO;`Vh!o9y@>|ZDuTZL< ze#e~S6ZCp{3iu%D0Lu*;C{WP_FP_Jj9Vj0Rb2&Vn&^tH<__~V%*cLS%RVdt? zrSA12hG?}BWreUaf9 zICn{pe~~T_=~Nm=zJDoH7EU7FKOe0@?T7&KfPDEb+Gbj9SAYpQw`;b8q z669J$!cAPkyuy}251*$cw;zwYhs^SVBD;;aP}vQr*$S}lBjW&6MYuqW?y9;2Y4 zrKWG}#spSmyss0w-+J{?I!TPmHU$yrQy5bC`1XA8GJEQjj?diql#FktB4oC~(G~pe zcfLZ-W%j)6z3R{kGz^UQ_m1oH1T@T@v<-hCOwjav zP^zD3!M#8HYjKoaxYXYBs;QWm z!?^$>Wzl29v|2&|dGA8QB3kIRiumNJRDH5LkBZ#OI4QBGJ<3p&@xpEn5S+8gU?Qo` zMY=vC)Kea0`^ZBA6)HX%_%=u?rWA_s6O1dG*o<|PPA=K}v-*ofFRE#qQ;bnb0xa9) zX)}s|gi;BVD%p$<+1>@JV3+QQ>j$QsMMAfK4*5O%gjvs^@jVt39>e#lfzo4OXDALfeVrcYr8 zcF6|0CLO03X{V-j`vPMF1k?Lyhu?-{xeqKAvG%?mH-imDfj7rXYNX*KCtfe6h5Vh5 zlSk_dBc0pqxTGsc==&RaXFU%BWld3!-6n?~*JF;6{oWGgrBK^?bm_WZ`>Q2`J1kx2 z%$j92`#k9IRrI2eEr6-beG}FPQxeis#KD#9=bA(42 zQUI#zaGriz-57P;C_53GOC!6tY6+|urE z;C-I9M|xXfZkqG8&VRQ>nR_?8e;w#-N7z5Gh|Y9>({KfeH*IZ)zS2~zg+Cm`###8; z$#-@?Ft}nAo(($fZN`@a!rxb2Uoi6v&HrW|@n=x#g@POC3pvZ>zm>pe*4|;GpCJ=H zVbsX@E|ne8VO26`@U4{n>N`O*`Mr@u@Js*GMHJ|opPJ73?I90ZIk7h0{vOpr7%y!f zaVy68SwAZr;tOWQ=h0!2@9yLKLhf%w_=D&8OYysx2b3+&&U2Mrcz})CwwMvHroRr| zJ-q2PoE_rvE0682-6l+D@I`?}^i=a5dy>-i2I1?Cw{!2GWp$Y`r(-OOZ%@h4hlKD5 zfdwaHmb(moO+gwF}hwine& zS`l;^lQ@WrYfs2EJULITM0v83q0?16U`s-`KpMEWDHyG8t0OAk$=@ z9XYiU6#@K`n>Sso(V&52)dg?R)UY}%M!r#Zln6K!7^ z%^Q>|lR}27Ok?{}x0CgX7Wz0RtMyCv47(Q|fGwm`#3+TbH`}BOmGi!ztC&ONH@31J zOU&Rr)^qcg8sU@wHcOS-?Sk0ys=iRwbK2m1uTo{=H8p-y_??ad6jw+ zOgdKNmHDvC0lFuP1QuNp*XYJNRDVA(HHB|MeK!T!6rwx8W<^~Z1vu+DMM_82*o!iM<{bFaUqT_~b4vzy2g{E?oQF?*; zK0q0+J6kHQ(#dcNP&A-$r%9Pki&yf?;g@c0!;7Y6#eRk2d=HlAVk2&NnOSEtfc=WK zR4%`AY~ONsOQyKOS8~P~LPq_O+Rjuod=vsqqB!Ai(Drm(0U+SmDgc-@YPFeS$i*=E8LAH2HFa1i(-eeVW7X`n$D z9;BOj>Lam~erWxEQuguXqw;0wUc3RbKp#FLNxuV3-9(&2E9IiUB{wn|xosFO8%5ES zh*Z@dU}8@sU1^I3rIjSQ@^&&3X0vivlL-A72IR2**30QYd55No0R}QI8_iW&{(?j% zZ4NOJYl&mC*cRsi%~~fl=JVZXD#cgf=i&x=Z{6cJa~Qc-j#dR@#yDj0nd86C<%pl4 zKsBoOqIy-(%WxF6T~KOxo&lVi0i4TgE3;k$C-kLSkWc{-}H3Ycs){*b|`!?ff` z_oakUgl4s%7!M>|CWv1OyYW%zQ%u$Bw3Z?~WpkQ|14e&KqukzP?ru7M)SAxL-@ zzAW$tu6iQ4%-mQTy3VK?uufLnVi7hDzZy0RFk%5n7!z!autnz=DU2V3yjo-m-zU!B&wTuBRfN)zKiL4Ouur`bcQBB>_A>*tVjyhS8VCAgbMNSs=?N^u1$>}=GBq3ayL7PC0S$8ZuCQaG;loqY)xJF~zU;CgbOM8Q zsb538kyqvO7eU(7qAuwb=+OJcOIn)0(mAM9F7-%?fh9(jABAtDvHh`0(63GMC%(sh zdNgQSMum7W27nc%e5hbsm}84Wd+`%|dR9$)CCxeVUiJp6a)DxR13wh=(JD;v@5zb` zb~LW7cnK1mHz!|iwXd7+`DLH*1J>m>JiRGkwTx5(p>6cz2`-`(E9)ypHTA1biC~(^ zDQf3iC`wf}aPo zf?ra0k|=x<#~Iv7ah|hDW6T3qc#9uciNG^BLSO!*PEWV^8l2`|mI(n3zeu zS2Kz|Ts&Zm8N=NiTpA4`iivxjza_|7m=T-I?bu4S{}>k$F?6($uI~~TO&b=;*JR5B zCOoTh?v<@m$jg8zfGXsqLXH9G#|R6A2QT`ns6Mq^zK->hW8sq0-^H!Y8efAJyiHLy z12|a^&&0!g?`>*Ufk_UE3HMtxY8V9jeE1BLyODp^f{y zqg*_T06_8MkuU#}_beFVe^=ac=aMXYpig!kKTe=p6k>M?`1xfGn=JA8QRXc6-rUbR zcweH3)sj-Yb&#iXI9z>cX-$Yj7Xk5?{m#2)KtCINf^Uhc5d~=Ubx#EvV1xF!Dtr_1;e4_LTwL zsaV?Sf4SBFeg&m67jg#hQ8bMjKV#c(f311PjS@|JY^rme;C63ERmASCTq;PYoxsLy zQ3A}Skuix@HhagV)zsjzwWUv)6_(XRe|3K5anJ#ZJwU=d#R#?b7n%ekL?8h&qteBD zF>TTCRn2y0+P~fLYfhsbvda6;5Y?z2D?xy~3=rok_yc~v-WnC^s%8-TPy#y+6;zYD z?jht86lJPwzT85N8-b~aE34(hbn143AhGads^~oj{@PD+>1Jy}WY4l@D$z5dDTvhk z%PqthwG2y!j>YGLh8+7;dy-oqKWauxfNeD`CHcBJMfo~N1d;WjaId7v1E*R+!WAlU zaS*$l9BMr2u}Qw9Os=4*fGn4Nnf5@QQV_@|W`V5>x6v%!EGMefpN_DP$$|Rov{CK` zNTd4TtrV&;jby@9E~U%q1}<~25;YUdNJTja(Vjkt@&ah117h8p~IN7 z;n*-hoen!O;=@lSonKeut8k+f)Yq8VvvMrQB~51uwKNxX|$>`Vl+2gy7sm>_Jdef za|Iey1d4gIhVK~MYC$OeDV`Elz@QXE1?0-sqAoVO6@~wb43hs;i1Au(v^PxOV$yi= z!eDcJm{(N;RIx4d%TH3;)wWPWPo0ZX&8EH+T|t*``UM@je1BN7%JTy9$ND)$WWk&R zheEi}q$>eV`$3xRv%1HPoSD&3t9{y!fz78w{`4fzF?cK$hBT!*@H>HZbjn$9QwP0< zw`o3Bg9j}5giTH4MlkD*u(}XofdXVeAs@7>N6oUc%k*3s<47aTdwT`bJ~?gTJ83%$ zir($0vufHvKt*F?vpT&AQKN$PRF`?hyXlnrw2M8*<7{Au9q5lX?)#&p-qBI|qeR@c zU@1u)VQD}^0aO)WWbhN`mOK!lm(tUr&N6tOp9{9lk{HPd^SM)?>?QPD9P&mlJMgSK?g|~xP z7~tao#F`#p#BQ+G2fvbvvcO&#zc<^K({@^XJiC>jn}XXIsYk#qIegCqJfE=**Cr7M zwap`Ht(WbBMWrchIBDPji{=t=P0hN#zB_Lzp|YN&9Gm}j=NO)3(CO3Ls#PB?={6P0!!0*~=cB z;dKTu_XoE7mo0~U#ks-ee7e}P>uri;DU)sq`*;s&aB8MwSjMcbt!*7H){>vu?@lQ* zFB24_XR&rTip<6k_Jo0$aW}`R~sDCi!_*I0<2|KigEl9|gLKPzhfH-nUz@ zns~9it9ys`_)~i9whtxRUw?&p0K80-2XEr_&tJh7iP|v-%)} zQoJNEQs-{~k>EYhb=m&9WyM_=0##rw3*RBrO_a?l`UQRryo(vk%+=(y*351E@;tpy zS>^TiuO*L7LRaDVR zbsh?}s}p0~f}3R6cTs0db8HE8oT~41awT@TeU(%;Kq=W+)&pkga+Ow*BwE=ux1d18 z)=29ZzI^6SYjTO&EB^fQM%~dwRQQz;H5e9pZMu5l=ORx?vFan9x(O2Z2}02YoimD` zu6k|RSF1w9B}&XXN}?sZ)#pJ5vqb}R3Yt@^3T0B*x|lQ*$GIu?y~FhC#E1>AATS-f z{ExpyyR%a3k?{*mTh;EeFGfLl4!-5RCcpOI?#+*1V2d#RO4A?$7@*W!+MSQwjjVx< z$iX=^6+mumzsSlHJL!#i#tC9`b9C(}rZB4IQ-Ea1B)R+bNtDF1&W+gw4;D85E;;-> zSiBe=j8je;y%(q!0~RNS;%_M?i^Vu}jztRngaY9vjl4c6$`?xkHO@PFVjMoy57X)6 zA3ng;d*1v};Np86NL+@Ejm?{7P_>h5#}=S|M@22g1^{i>1bcH9x$ir)i)E<^fRuL{ zr|R40|JJ7&2cNhXy8ze+p&rha8=rnJ+!m*0W_z}yMzV;i^Hc_aY{bG|4i2W4k_wIi zgc)U$BO-k*PDe2$U#xMPK(Et2N(4lt8Y!p(K*k|559Ep@VB}MGXrf<1xn**S4LE5wY0yH zZdvRS?t1$laKDcaU3Z`>3i6>OgUjrqP~)ff+t?bXpezk%YsZ$Wa=cVtDgg6-#lG6R zxx4qIiuG=nDyoR|E?yy4Qg)`bX6h1bquM46`InY{~LEhH=6mv7TvT&mQlPJ|}!F zvCE!xloU+wFa9y2lRP|qLU3|r29PTGsqujuP*68Lucs*h2K&t{G`5$J@#Al-j)R1zITS4!wti^Y$Elr3S4dbg$JaajXDj~M=ynDK6ebA zyUQP*XwB{E@73Sv%-M;2*11qX9YpAOQCAudQp!aG8<21Pv;0zH(K>^V-vAhb+%vZa z?QuFfP{7X7TU^hnw}%dv4)F6z&V>)a)B6);LID$<;N~%@-L9 zDP&-hwDRPTZ4P6ZizM+>orH^&1PK-A?AD{Gg z0`%dWeDY)D3_nJl<{8BRg2=<~oZ1C`7k=Fz-Ieg!VX3z}M)mv5?we9qcU)Ump3l$K zNq6yQ9$b9Lc!oO`&* z?U0nxRbuOU-aS~g=jjUw>V|#QqB1()i$#LY$~OHHkdU>E8X+3t*L*x(^NH>$9dq>1 z5bw2V2prHx%n4cy{l(K097F^t2M*MK0P1YsFN|SYL|WbnvJep!j$U}s7X&eFn{B{Gq+Y|&5STH5t(GWDDoz^C z>7EfQE}d#fG=3qb8$160@b=bWQLby-_!3bGl@yRv8YBb-2`T9=>28UkLt2qe>2B#B zU}%u;&S5C&7(i-}{vL3xcklhaKey}M$M^mI;oz8K;EDUXue#6kyy(*l3kx;b*Q(AJ z_{4}oBYnX*if*Ny7>0NJskSc!*nwkqi_%rWa;?d5Uj9TIA|RK9#xZ*vlQjN_w;sXl zbUEeYf|k!tailbJ+V-YYV;80NT~0gjUrb#c0G!|H@y)SIk6a7(HVYzw!H> zWaW{|H)dg+W6s{Vo%puiw_Yh=oqlb6viB`0&rXMcEJGN&hNSZj>!=_x(D3Z~+!*6F z*0Rfyt!P6#?|0=#(@?AA_&r~jJ|NOk*N%gYdY3Uge|&;QrvTce=C`odT9`PDiHW>< zW@h-S?IIf=ZMSe9|8VO6V%Dx`8sD){ z-iz(;RsQBHnPeBaji`p3pEw_?*F4Id#)ONJU|6>4j-&U+u154^0{9x&O&X zf`(D0_sKkkYVo`!sa(D*E+!L`KZ4C!zwb_xeBY1Q(i@C+6xPZ9NdEpEq^YmnieJpH zZYj82))T`@WhR&KH}4Hv^HreXrr*aJbu8#Ir4{SvbZaK>hQ~8HNSJZgaOw=~&gV=% zNNtik4K@uNNq8yJh$EMyT+CIK3>;U_X+44%w$>d(xiw5TGr{s8glTOMj>IlO#+cFijl4w1oYQvU+%h}L6H=9q=jt7iEnRR)j$VO!x zppGwub?eJ_(bv-4>i0&dnY9)9)YO+>G%bEF`9@9a`u$s{mP=`o@|Mh7Ee87WtXHp^ z4LU>04hFR0Bb!FDTn%EL#!xhk@*QV<5Zt*OLUHENV>_)r>fwg1Hz7+pQRuL>YVJbEZ_P zFh-sExruhH_Aya9IyTmNG!alAR%xc$p?itLK)>qE7!`lA;XgH6i-}8Tnq%{=o<|-Q+><33(%OH#+Ky6e;4cyh z%(%r4Bu%&QpV8L!2_Gi7a9rp)VThX9;FkKxe65&5jo*iUV#-=RWn_A!^``F&h|+ch zVTbF^Fk&XIbMR^2B$J7b3#t&?!z#o&>X@0-C7wq}#VT1&Gf_PjE0Io66os+{9wky# zq@f>~S}G`)hh#{fXOKrGm#00&=V8?L^uvjwV^!mQ=9eaQsZw2$6vkN=+d-H}K)*P> zr%2M;wd0ySJHfeQn!OHM<1Ws`5S5oiIFIbP_Ur|1M=fogzus{qXJ<@>d*~}xyhMfT zV`-P;9OR`A+)~4gYR6iN06NY!E|ZENL<@ug_U29JH@tKyhDWIH!X%BL6B9clI-&OA zA4J|iRmD`x3u7quTXD;p@HxVcGClDnvh3n20MF|s(PKp^L6@H=Jrzvj)2Gb(@RnYz z5R4qx+Ou!6`>UN@42%(dFc>c?H__xQDi@PFUy2vCB073D%@muk^0hcwSy978nu$s- z8&YTe<(M%@hhk5)LE*5P_SFh&$l1bjf`A6s+AwZY{FT|aO+d5?VNVum!kxo z!MWX8K~l4wPhLs9qY(h~8gue-GbpI?w^jy~!{>0@8T{*Ui=>BJah(f4o5Dz3XPK)g zSeV&!(-2v-wwW&>t$Cd2+XYQqewk@feO_tw-xYa^7`4f)@J~C8;I{?To~Tq8j>Q9 zob`|m4ocK@o<-nv!D6o7SgCgaM(PLxJG>WKbJ^yOjh(El*Yr4lj!KhCg596cqT`~+M%Vs$_L|k@-ws;`W}NTisO|z;1sUI50&c}! zB0OYub#;qt8WzStStC~N7`HI8k$Yl^rN&d*EnfpM6<~cgHVnRF(gpzcz)-AuAMcO5 zdvTk3oH~!s_~r0u@tB@D?5VsrJOpOBKly9!>HpWSD>S7Boe#~;&C3IDXkz9$Ned#r zB7o4AUN%fE6(<+HM1=M!|K{>W9Nd^u(YN%YtNG=Wsc@Q~KY5M<*qPDOPW{pkJiyRc zjNX%;se4{v{xWN|yr8BQir>zJrsccUAgW$&93q|a8Y&V@Ab&}+P?nDZl)6K}xrSM} z$si;F5FaFj$q`Cr&C!w5K5JDQDKiP1tB!5PVWO9K5?MCQVnEO3-8|(i$-TCzYi&$6 zE(;7Tb_~^LQH`s7LaNBau=dP=45bIDy~QYt=@Kn0_;q;zv_{&MT@+(EE<@*b6L? zVg#>CKzTyjJ0QYv7ySUI%^)Ax+c&0~ruq+4{gpBPT&MfQAD_?@L1!!|X1PyAVzF74 zkiO~)CVrTE41qwVFE7q6F!aIueI|dtm~)33KL3H37}obK!c?eZFWJ;OF2Xk7y+Sq; zJ-ct`){f0D7en&YXT*BMKAXE2@j>V#_C0!XC~$Tl zW~W9y744GapH_GuXKLj#eQTz!>VAr~yf_kDrl^K7!Sc4SoN{-0u)7M*^>%^!1KY)8 z5gqZr-Yqu+#mD*66n1g3qM}U^+!&-4#SjRqb8EjYP8Tqly5Aq`Qv)mzjv=5XTv|R# ze?8ooo34z2K}r#rs!SSl5M73WZ1hq88(0C~(??VU;}=o%pLcBBMiF;YPa9YR8ZvWWC!yL~nan<&W5N}hL> z1H&pByCH^J29l7xlnK00io#x%4qnWtJGMT(5?bevNFVe3TVL?g9zU8rk^5=kug|pK zLOl7xdwTjD_-AVT4cRC}$*GAjt2unWey6}hR=MEZS8)4sBn_e3BGdp&A5}oCiS@J^ zSrH$B4CtAr^5$a6_pbF@{}=%OOHcIn_D8_I3dyl!mhWm0I6*2@RWxAy?>kK%qZkqr z7G~!Kyo@!{4M0cXe?W2MKOg zMz;$G#jD_wSd5N$=E&oUm;yl2Xii`5{rmXz^QIY4f9^d! z=USVgn~&rykp!&A$(pi$h;1cWNKUXp3==m^LEku9YJWOfASalqb1DfT<*Bt6{ayUM zF@@HMpD&9jCWq+WgPV_5sIRSjPvhS`%I$j(r~I)f7$UoIcclJ~C(%{K8giWi4k*>_ z><-e%UU`89Lf6|a>1L`;vl@ShtQ&bXAk;Wv__dCA@nl<&FOau1?GpZrR1en^SorqQUdgMVZOm> z@XV_pWyQ_Dt(SkgpLT4(%Jt83j-GY<;doIo*0_UOX zWNh}14yQ^wKhD=`PYPos+=xj?NU~EMbKvvUHkp7pkmrzJjpvm|Dgum+|M8PYDI62< znLoqlKl|=$Px_BfX#+oaDbmiabcIs{ZR+gE2#Uxd;s@Qb&tN;YkzdJv+G83SvX@*Q z8Iy~D!~Tf`Ob5)U7Ta`VVtVs8E>6H3%wJr+Ka?(*Jq9=&Oa{xSL$7~aG}2^qi?@sU;N`RDc6G`RU6MB>xpY7A4)) z+O2jaj59 zvI3rSk~9*OpHt~G?BnofLRb7l;r!4?z|O8502scvJoNtUbABBb;osG`M$Nq-He!tr zBq8+z2d0~J-mHW5ORO6&r}%3x7p?4QZ@Jr_r1M-MuKu^>qI4>{YFg9z+00=VwYB2xvlK0h+ zB{XXD>_<&eN4$GoKj4H$*gf^zI1%7<6pdQZ$* zYMl;)xu2GzVOe{Ac$~A^H#V7AL&j)uy=pz;*A~50{%h~jqZnvXR~IVBC3VqO*js+D z{;(&18=(^%r_A^!T_-X1a-hY{TDE7bjivPLvH@Ek7i1I^PVc)^T zN`{~P&b;hPFsmI2rjv}6@(bH9MoF7EpMV`Qq;qX~WPHC4QKG0fQRX-1t@{qh-IY6> z5A?zAXFcm!DxU<>jK^q&j6U5;aOYp3rMU?ieZLMFnZMt!!0kSNiTZ^0*<^hU{yvH8 zF?M{R=IeW-Vf&+@q|&Fo&4vSwPpomT{q`lX`mgt`{F~oe6YvFs1fXz?Hl5@8RpMTf z)lfiwW|V`O%Z?;{6c2t#cWp%#zqTUUKR3d^f0g&8fv#B3b1N+%66UQNPJO19Tgr$6 zl04r-7Tg!_k&W8rG6t=mTvrRdK()~4`p?w@Io03DRQ~vwE)LY6k5`KdD#y7r)PL57 za-(C0@f{r!5B1~_K=74&ULN_B|GG?#xGqy6|6Gs>OZepqZj!6d(49JT9GPUk;g zoCm-PJy8xoM>hJzhjmZA4$fboCYTJ3TkTuG_`vf}1F2OU&$YS`TZNv;Z3A^6gj$+<|vMS5lbBzv8IYqD;y*doBg$W~U zq7ah!GUw4Pi+UmrtBGl22>e74(z-Xb3n5yA#J`42$Z$X=f|h~Pig#uz9rM5e5-7-^ z+Fdq>#GaT-D-qc%k~rvO)cnFZ;`QnUqZY)}4`vz59Xn$ynYc8!P+hmZDpgP}Zs4+( zayTA)YKuI>l*LClqTG)&-7gk#;6hR!jFhM}e_JK0UbwmcKe3%v8=*R#gh(MQHd zGLndC^u*dC4~y}q%qg1_1WJ@>z*&?*{(IWZ2~Zg!vQd*q?zrPNA4ALrd##1hT!lKf z`DM7jOnOk8oD&b!n1n8oom+1#+Js%zZs}`aMvE%F+KvNxeIlv7-l_{rz(Z#4qG4)k zWPSbP7R20OvznsWwwEwZ%`rGJA`vUfC>+>Af5?OC9CyoHcRi<8>R&Q*6g%l5!=|T; zB+Ud&u;Y^LCN|jh&Zm-crM3YT&8ju!;xcZJ19PQ<0ecJ9XdqL*%$dF5vs&s1VfVZ| z&nIHO{rf!QIu1--JLiu-uHWa}5jU&?9`8k=Wer_ncb@a|x`-fw6subH?*1sU(q0fU zjZCI3$}H;Nxk9{hgYB)Qn7okTqo{$(tC59zu?mxOJH8q0(wBZ<9JFSSe=u_;RaZ1S z@{Pi(KklvhlIY}A%I#!ceQ0`X^MJ8bep!%=BN4vo_!HW*?uMd?>A4jOvw-$@_%@)? z(1)&i8YceTh9=wsP)!;J^i_LWMC6&eR3~Hi)MCvMZv+&B;s*-EAtXK z2z*FZQ&?UwN=QTmr@~K7)u0Khsug>u#U#Bdh%uuzutA>)WNSu(0Th43pciPVfYjnX z4tJavD{3Pv8&5kQSFn5}jmGFYOBPu;fZK8mS7VJQB(m#cK@ zuRxWM#EY4L4&09nbPp91WJ0tYp{5Vm9nQl+j~pcR>7dajFRRBwL?=!`FnESs*?mX4dwAx#g{j(&LUi zTUsGnMKpO~Idp^Z>QF?GSz3BH|Q6DUj8XrL(X{JgxSmMpc37T8^!26 zc;@mbDvLDGb#KOT;e&%CG1zxCTv;(TW8cOLTpVgKT_aV*EuUOkScPH~$YpS96C=Ji z?yzpH`qZiA-Zu?Pq6@uhJ~TDh#|y{Y%Kn7Tu;Zg2v8Po#v*sZbqS-qXI~qGH-w4tR zaRt|?;tCq+Qf_r6tFu?ttKZ!nzDMS!;1D=DwK#Y;@i|DVx5oF zHd>x$tgceCl)8r{SXG#j!3=0#$*2qmNPr3})coU}ky4faX zqU2guxM{0)KAONll|)tWkl2?i-uCIUMz<=qaPkBIB>iX_0w5&7?)c` z=|z#&rL0F%Vk)Yo1vU=p4l^m( zm$bsHtZ~$_O*n7_Mr%xXw?Wp8Sm29N2puy<%~dMBL{bNAYFz=f?rLpw5&JsL{$)?` zubhJYR>ST4ySI-XKCj0p%+3UgLtkjFh=3aG@&dB+?H=f$ytkI*+ZUYGslxKXGOVRg zlp4ok9TQmqP~SM8H`&-BOvepT5r9tLyVNBjbsaly_}<_--CyOcd+c#h(70XQZZchH zH*nOemw1Xr0gsAp>Pb19C4pGMl$)6Fy0%s8Iv~Yv1CE6{RwPV>K^9}nbdD`|B+ujA zTK~9uBRApn6`SsTJ^k5!T_4QC2G9sg9s^uUB2O=C)ywz4Tm#?+b)ug(f*A}5-Qnj{etoz}-( zy#gZxm4jTl8aTGH=-yP%GxUeLO4)U2d&tP9&z$xrBm7KYZinR+7maYZ2obk}>vn39 zjXp=f{D{Al-3Cu=(%2B=gDhlczS`cr0hh8>-(W3Bb+bramtMQ7m|tFFY|8ZK1Z}qd zF!Fl35twS{t`Xh&2!yT@RE^eS&$OOlIMTK!V-2;e)B3_%DV_9uV1Vyc;v9#6aobA< zKX6ge?2hMxJZ$Zf?}gqu2lraxs9S2OHdbXtaGKPi#H%uMQ&XEa12RP4E}PVfbSR@d z3@!dnPZ>DDYglOhbnn8oIh*=U4c(5G+lXw<7El53S4<-tVMp@px9+Y8sdWoRm2Oo` zYNkqtajF6U8iTee8%OKo;+fB0X%=S*;}AGR!*;vd2hhB=j9gXMAlTE=Z6&9sDyWnA_9zxg{B z^GM4%mgD%**~VI29;$P92#=eAw!k!#q^!b?BU2?jO~WEDeKVh*t=ukh91>nmSjuqQ zP{rMl#Hvlv_Usa6h%8Y_20(VmrKz)P=QZy1^wUaA?nb5DWLD+UOw;OCUOAtUUNU+? zW3-YHuukys%%)~rWu)*%eU@t|DLK)4}hIA)WBXa=OZvmRTG zb*_A)r&T=cl4N?_DH1}s(OslnH<#ja%Gu=h?T!hox-8RB_f#{ zbk%sf(JDFUjbluZ89TxnXkw8{HhIwLCothTr04n@68zp3JbC^ag;qZvNtSlqTR~rW z8pw0>?&eF&A^I3!!9pF(FT(qIcaaiVJwJ|DM}h$817tI$&b=-pio~fy&BOWm9i!=V z1h?g>eba7Pr_8}{59wKVn7bBMw{fa#3Y%=)yEBO8-f-XaZ10hJ>?=gyt43(f_94l= z^+{{os3Tu+)S)-**d7WBGae;Ov6d=IuKVuKpEtYVWm%uoF3K;F?K!icphk}r&p<~T zM3JJkm=V|>-bLvWG59ctEs-s2f=Sih&=W}ioo$pADxN(ZlYMAqH&V#O?ck_a)&V{u z(ZPV1XxOi!ITB$e_-_Q!Xjwyj6BF6##z8E8a3kkY!;%O?=i)rrz*G zriDS6+59!D+b;i`(tyn5&(DTErrhOy5Secyv0Fpz!Uu4L8IAW3YXvz4bQE-RPJ$`= zd`K+qwyVfe)_S$nN9npNvWqjxtF~?Qn;{hH0!>#3ck!7_XG^Vj;VeslxM@RY5u!;zx(kNx-4%vJt3^lxt!;+oXA}z zt1-#3$5gNU7M)*Ynv@r9K!Y)tKupH_N|H_6@B%LBH<@W0;@QPgpO~;dhoP-{OJa*@ zZDn}2>#JokzCC>tuYgaVp<;pUEAAGB8#TM?Ee_L?=-wlg&tTM$7wS!NWWOA>**H%VKnlkP(p z;021Ga6~R_$B2SGn635m)D$VG)Tf@%Q{9I-J?|5%$cSzC<%-pTK5$5sWOk{OtfQ-& z7UoX;?C(gus?W^rB+w8hvij_f$Hjt^n#}^IWg;h&m)eNO4tXt!_1YT?lYxR`tkp%x zd$kR|T?a4l6z#n>WHCi|Ts1&vIFkBTNq$$qcIJRP^=usHi#hUED^{{^88@J|Uk zhC8xrQQpSZh`nuyZ zs1w@G+L5wn1QUL>ofrS#88iZMNLU4z;N}uEAm4q4fwZe5e#1^MP6tgjJ`hR3=Y= zYC*lStd&$VWhJFU_M(-xmA%=Iys*d~Rsvlg@vgN`Pw*Rhb;#MfZIA*}89{LEX_&jl zR&w;utZIoWm-HE{VrCgmJ%PtP#7ku_4PIQkdB!_@eJ6}^TMOOBJ_$R0`as_#u9dU z@2tMmFoX~_{JxQA{SsU5v=@PJOuYmvbov3s((*k;Imeb0$td%}&Ro&jE+d1S!LCXx zjfN3+W({NRSFcmegYfC8XzPH)jS!<%SbnS2wP~-?64ap7L2MVC?6U39P{bt34M1r+ zxRb>!Mh9e=FKaGavbCXe$3V#niwJ3R2oL(f)+&4{kEB+F!_WDHD1%8myVkbd8~x$WR&VIVrC4` zf#c!#Cq0{Oe0;QR68i2~T9{OqJu-Njki?-?wcK@%2N;|?fZ`Ev_X3CaQ`GZIftp)v zH8+t~A@g(Fb+qoQho+s&Lhk?sP0gZ2nFR=Rl*beV^V0qs{k*TkGW?H2fvWf!UKA40 zavLc9vjk&^B&Q9+Fxb9*4D;=bSxvyjk{WIjk%@Z;n=Ia=V=fSun{dvZ-8oyGv@;-K zbC$5=tQmT3nbh9|4>Yt>12Q-;M=TfCRJjZ?wd)dy2YUd{w5 zm}WLiNW8?UF~V_obQ|Jf?j#U@>OB@nL#AUQL8lg6G{2}o!yEZXr{6)oNQS3zF30me z>~6ZI70|Tksofq{hOLZfZGJA15P$U8t$l?(HbF6ufvP28v&}3}84)+4&E>l!Vhm3x zC$gK3t$Q06R_g%fuvIG-jK_jlRfALA+Heq-f}4(^;tkUzA{zNLKq67|2`Y? zeRiwCNhv!soI+F?`4-SZ)-9nHQ1e4~cDkwpBQXYatTrv%nx|L`aeQB9_Oz_iace%M zr`0%KxQ#d;5q({0H@EL-?k_VF!{ZA7wtE$;<21AQ0yM$7cDhoDao&tNLweNPjbdb` zdAdYaYo7!IjiB0De2WIu?M@%u+rS4l+d8JRR7QRUtvWWncqt{2#WVnY&SNnpbIrpt zzG_-Su%VemNf5i!VabrE}EQ77J-4LrcIsr zk{?ad^=PW+cjvlV?uo>jy_N48$q1lpNmK~$k-&881wHNTHNWA6TtvAaPFwb4`9}ZJ{mdWIyclP4AfZBGZ%BpGBVfWKq zX8`)@bl+0;%IQavP2|0Gu1SP_q*k3>(w!twN|R%)WH^;(R}5Ee2-ibUDF1So2+<8}TLb0_*{DJEhZb7}RRi4otH+;&Fln$?}<2 z9vwNZ63tsY=ocM}*4i;T^UVr#kxx?4o(Ow4ux<4O5AY(RZ!Qn#EO7I`bV>H5I$%Mf zWaJ@3S+DQsVY>_Vl#nplP8Nk3`Uptku}$^Si3+J>Gl_qtl-zmd---)`bJ6%K^z^;K z8_^1N2rrGwXN9~c=0P@+kwUoAxS-{+o)KX^3ypFde;mSY(}y)2H)43%hYB;4U~?Yu zqR=mYUw@$$Lm->+TJ7nuPJIdHN1$YeSh)M~K)AS%+tGTGM7l|=f@EI-D~Y%Js)jh# zB(~=8OW-+V7wD?bCWQp%&{}s& z38FW_6T&1#c2`%ezin|`f1wTekW_J3C;HU3^!j+iUnMe)LFOr^$EuXZ`x7N)*pM!x z>+w|J{Om?%h>8~$-cO(0xItwH!0{46Z&Lc+4MJi>~& z40~s6x{EbubR%cVx+#kl4$E%K^icsEk~c!^McfzOE{SYM{_E19<^-ZJl-4mwbBNXk ze-4>U=lUhdu~*L5+*h}hl9cy@YmRF6dUKFXj+xD?UaXD7RAZUS`5doaY(pF+{bc8B zmcQKPw%iY28d06BaTu(r$O76mpSkaOT*$GnNb9XFjV6aUU6)j2_ws%-o=29GfevTOacr|;PuQ8HJ_tWVx3JDBNs9<|(L z`n;yRA&vp15@IoH9LD6{L+Y|%IbSmE;bWuxDNSm)J8DPa;Nvyy?H7mO|LDE>1K7~) z5p5uz=@J2P1QUoO@ekLaFsn*Xfyzp#R132(O{C9CBU;ZFLN&)T5X60ZGwG`I2@4QN z&2+MaXh#t?%jFBqOrp{$`Z*1!T)l1dS=EB^F@8a5454XM_7Z;UUWclFZ>ju1`I5)) z<0a1sU`1gS(9i}o8@ZVj%@_i+%R`Dc75K@RVpv}dBcy|gqRZT6 zx032a@;Ft_zQcDeY-BQU#jroE2;R)Rc;^XL_0D!F$0-%}hM0RIzEVagv=x3yWAKlt zkmhx*33AD{PiQ8!U#wMUJkoRFAoMjc z;&ga%PAJOlN-ckCnu%tSFl+gx0n0wr2a$GE^N(|!f~tlg_#$sO0LZE_1*=K^Bi<@V|Tfz#qx zU8HGqJtw=tDGMiy+7!MQt88WN^>mWT;Y?Bd@mQJPuk;51`1vO~)VlaVMViIrWmD376rs-3AytUmb&!J$6L0^(8iJTfqgSR#va!4b~!JpHgf(_1SfuHZQIk zR?J2u>?kM~6iqt^cGN@1v+b_~wdc~4KHB+L@im<*HgW!0RYQ@yV6<@LmPjgM-Ez63!NI7D4$=Jx3 zPt~f~RaAyB^~7Y@9x2ID3%n&}W02`3K$Ivf)pnt#6S|Vh6el9P zg%!Rg7wavuMkk!VL$f;1yOV0AhEyb~Tyd7G)9ryB*PozZZX*6UCxFkQ>@#x(TWzkL z1kVYqQ@z2ODP>8C*>Os7>)}e9wH{HlZQYP?3-`&7+N2?=Z8Telupqgr_I|6ucs6`z zCIgfHk}A4@ng=ayhTg3Gds|wR<*G9x-mJ&Fl{mgs(x6jlxl<~MZTp%;h7*cscl^;G8x9JSOX985oki0Ss@ zRo_{PaF9;RYLgPia^GVsewd^57P*$ivT^Y+yVF^dHcft|QoAp!a8!0Hj~dFC(f3n@As-^>Km2aCcwy^)bQ6 z#%my3qh-mq#9sKsD`-q%&bFX?#Kqz@Udc>L71$_0UZerskoK_8DSsm<@>BhZPiGx) zQ4#fX*nz2tgjEyX2Z%2sYS|FS=Z@{Xz*RHj_%Yuetzk<3c~NtVZuarf=@y&+LLV0Y zUFH;lDw0c1h4~~CZ0#jpQBe`koXP|E+#W9`GAon6tbsvhh%I3-kQJ&)OZW4`ji*iu ze{#5Z$Ln(~t~|)2qvN+eRNGy}jRzA4#98n!EaD=yhVf%+9<6o#-?%*vB5z z_W0;D97SB8(&8P?5Vv5!?c=(WStb|wMzXY8?Z`gTQSvY`iq!bRWV(>gEmxj z(hGgfi+{nCq@Ue5*LU&m@%8z$oD9-6%ro`ZFwcKtC;oe5ic0-3M^U6!-hmkzoeE-e zxt{@smYYOgg2`~+-cWK*Sxha^ejaw9AbTy)kDV#}7iv2$;s&+7=l2(s^=}&!E#F5# z^jkGiGJwNoGpJZ%q`H zJ0Gknt^Dw9NxN~;P&p260*af)+FPD$CeR@N@Ar!2?%w&q;MpT0c8e|t4nQ5tuJf%@fGs;27903SX&V_RCR2#l<061h~NFWesVrWRt(5m*=H6KCkHM4lO zW^1quE9%HEMeXKp0E&4T+s}UE*PFXpp#3%2{!jKBTnq!X8yIlhs40jUddDVy6FcgG zFA0}@*E9@dJ zFvt*Q6su)C|D)qoVRkn4@F)6Twmv^*`TCjboYs~HAowd>M%9oOn_>$g2MO*7nT)TJKbai|M*(z!BSSJzEZ=*@JkJz4X zny;F(?PY{me`^uK$p5vw2qgtVmm|lyF_U;tJ&_}VhgqpJ!dz=;4@o?Zypvjem@y>I8bAZ{=vb; z#U;guoo6vqZ8Kz71*m6~Ox1jjlfoZw_wRl~S&repF4$hnT$AcH|9&atPL=DnxsG}2 zNuLQCqM+kp6EMLMPO|&nk8E1YY1Rl3rY~H7UI2P6nwbV)BD<<~2Zq2Mz5yp%)&{J* zJmOh`a{g+SpAP{xdK6O13!V@?c2U^}1T5#dhdX<6k{uItv>V-O#wR8a{mvhY8UX?D z(_^Mx_g&ZRRLZ1@dy);l56~8=1u^6M)MhX7yq{)-MI9L7Va1B1k`7@A^}K zMG*1C@fR!tV4%`p9M8U5Ve-*_@gM4fFlvg(J1p@N50)p1uLr?x1GW!w0-hSNsnVog zC&}iX{Ni)?h=P-Pc8BU$2N4}N~XLV^(wa`{JTzc_ztV{T?M~?bYA|o zH51B?Bq;DxHE())T56XnTPj}Rks>Ctk>`<$r;#VZ#B;UNJFRe65vK`8h?W7qajFx? zcEOB)9=mY@PCeRuz4gss?&Ja84FQqB+n&DV0!QO`g<&8tKPw?Ge7L;GVViBNufk$QeW^>-30~0Wf z(D4`uI0&fTi^wS>6_VHaI_YhuxLqlI2@=>+zKMr-L4i=L8$%%O2jXEqU#IG?SvP-{ zHEOZ~YHso*8K2uhwL{9d%J9i81Kb_J-;=sYl&V$O7WK2ny~biW4B zo8vx%m%FR3Ei^GNIWLEc!dDZm*hvmP-oAHUmSQ)+iP(>Anh^;leyn*+dklvf@SQ;i z;bonq2f@*+2f$pk%JKoO-kguYuV^W06*s4^PD`%#6QxplXXk)n>Ya(lrfN^yj<;bZ zv09Gt$EDw!DY*T?c`W56(qYc?l5N>OG{uWyih_cahmyQST_0=2QV4?=$J+U_O*PP!3GL~i1hWxK@@AXC-Vw4^YP`7S+f`);}H z&j8q`W~}ow0FM16rRe@;vA?FwiUI;c{0!3k)H=1*G2*m1$HyT?r&&WI2Wx}Jb9bLT zGL)1IYFJ^kD)S@rsP2>}c7>NAN`N(YxQCu0+XNl z%7EV4nbP#-^%o;&ZI49B+{XargwF-XMglF2SHOBFf~!o%F! zE)V#wUi23QL?at0=E!W9c^qwyBYIRCN6_!M7TQ0!`i^m>>v`VknWPadD@V-&C7Szw zR|6Q@rA1h_~SSWz3$Y~I(C<;Nx|&K;g|1sGqBK9=zcsW?U^BqWe_rkm8eh@1hU zwbQ`hYbmU*LxBoj$i$01-K&fFtL+QlwOM9YXs^X$Tfk-OqJC=LW~TY})|~6^5!th? z%w@Ylo=K+q?=7W?n$Y24z3XYk7orP2Lm6x9o1Xn_lZQY28<@RVd6*|Tb{fwtn+AiJ zYP*_7hwlxTW``vl*&OV8Ub&8*@w(#{9aGMq0f+eT>z~tpfKY#hG&<00ET=wB$sFayoc#{VxEf^Zc{#efyL19Alpc;cpQ`{K837E_n5m1T-30U*5kN2ahYO(2 zT)QnoP9Z#oM*U4rtT(Sq?%S&DtMJosEticf1h6|>fwag1m`M@!0YtDbad0Aj-%=w!l^Xb_`bJTf2YH_zi&Ro22RWh`qqvWNO{yCuCC)Bx~% zf8s0Hmi93hGxaEe$M;WUQH*FRm_9zYOnDzUNDr^tGa;A7k{4V6KI)|~BYMn?@>=-7 zD3I5m8BCr`C#FeZbzCmXUbgL2FFo9qA#ZzDT;0O=L5`Ax=a2A>Tm0{<(`!lj&o@#p z^TOWe)DB4YS8_aiqdv6*7C%Z0=pTUcwX&Zt$5u*zH*sxzc`xoGbIpG*|SijaiXZ&4X4-O(DF|O9_yq>%o^`n-NsXqDL=wPtK#Z8 zAXW5e$oS@S58cIoKTp=Zr>ax`HQV|vpK1wB>SFBkBbD5Br#9wa6Dx$VX#shsx)D)m ziN{d^XtG#~m%=|~tM`zlh&-(OQ0k>yaN!!lMW!HZjA}80Uvg05=;4B-&S`|_y)=yU z=mw$Pr-m+bwZKS?D`GZ}fn619k9IGH?StB_;sY4n;-m@RncDmr09OA#*r9}q#DWTm z!ZI32;=KH{b)DxSh6FKA42H^((Jziz+`2+DOg63)(Ot0H$^MM%ePJut@dE7?Si3lx z_*M;=HD{f(5!u|OvCzH8ex`?-T>JEg28=c^z9XICu|Tj>1$s~0B9LAjlXaP^sX5!v zsG}@xc(z@fSuRjQ}w@NL?C44e}&4OdwZO7A7;1aph#RK*O4#c%|^vh-^JZ1 zbt9h3W!In~(V}ir7D^^$)L-;Aod;P>xY1TPD#&H8Yf9MBeT#2%MHA%(vb8S-9^(e` zaqrjl1V`)-nB-O5f;Vv>N{6gGFU88|kc|S#X_{k0H98-mA1#Ysd8|Kso%Ly45XXlk z)@S<=Q)sdMOZ)Hvu7uVl%dai&lW-a_03#YfBbzXragn8Z8ig!>I>au7sz_PvdAa7v z&XYOb*|JxDX#%J>dVVO_rV`;kS&pn+m!@N(${4ZJLG1}-;n`A-@h>Q@?YSJhI^mmD zu-a5B@>c6v#bUKzuc_gcYx=Pm1lpOYsnL23{B6yBTJ?v6a7nBvx*#83^-0(0>*S&D z1TW{h>bews572v9q!DCSdt`_WS>v)9KQDudJsZ>-GwS5FLsOK4@NT1_zOre- zO{efS%fSvt*(jY#-3&TuAs6aT6_)Nk)c)t!PrmZxMi@isH`(?PJJ?O1RqLhIRB5vn zAu_jsShh$&FK)60a8!fu?m%&tdUfD=ni#_|G76~LyVDxaGW0}&S!N7Re6c<0K$(`* zN5Y{BT-j>G?F@i>?U2-Y+zk{M-iZy&bjxTd*5@eYrXG(?~gUh#Ts1jo#)y4*`K{PiJ0H?8<0F@XEYM$ zhEfsB^&;z4J2#`xI$PJ{rNoc54O0mYO#Q>iko}iI9C?;Av@_Hg7+9ouLrk;FU6;#z z;w5pd_OD7;97uepumATUEx&7LUzz)#uh9 zy{*-$_AOIFBT)-4k0g`{hF@-q9ZG}|xRB^cNxda(9Bcf*z0?)8SIbg{n{l{FK?db3+)eJ0DIE^Z3zzD8W1_gaky&Jq}Z z79d)5Ha<46oJNThP@29cbkmN1=)mf3JsUPFZ_B0hGCnHdD*y~>{2Woi{tw;eKVdgk zThskdFTuk}9j52Fx!iImdu2v`(=@xo{h{K`Unl(+koL`n}OypMp{r1&=%D_ zTT{d~1&*H0A`Vkt4^xNCVC6NCJ~(NkAP$;Y(PtS{ejnU9P)itB|M~6YXG-NILt{z? z5?!ZG3kv0kpP8`<eeBoe{0RX4G%Uwp0$pgl%x0^h&*Tc+mKUILKfKm=drD@K?u1n-+h$Zm zHB0bVwZ0;H&O7fM9*MoiZ|&%pcE{gD=s}PdGpctXyngKy4*T^h>8oe?tt&U#xQC~+ z(eX`s+yX?lF7|o%{$BZRq24IpXlxgmpLd;#>^JJKYD^&X_NS1{X8;t?aI8fW{MqmX zo;U@}at@q5e`ioh&w8B3=l!aDsRTgT&NgjD2A7tcq35@}+XGNxs>x4HeQ=ZM;KM&uHe0#zCK5rz@R>2cT5-=VNt2;k;SkPCau%S-PgI%Zu8N*f6M~-S*iK_NA>FO z3Q6v!`N2JJKG4j!OeeUAfwI*FDYV&4FpD1Fr05qw;uzh~!`*tOaO1ppCqCtn`FzSd zinBObG9F$vVcEM^+- zVwRcD~-r!l1f%8+F zfYJT7=SLeYq!*iES@MHJHybbajyUj|Uxa{k5=AAr~CzywQYy`BQ z)idd5ivmc2kvr|Q0uBEvo?!kqhotS_BX@pQ0-wUOscA2^TCc3H^ZOP*h3x?QbS<9+ z*sTEG(y8d)^^(xl$MS~FV!JK5A`k*-jI*S_&uu$x8oEn;I|}famfpD7JpjAf=oHVo z^3_&?o?T@iIw3)$W#)y9KFm3ud)__wH2d2$F!%DHH?Z^YCi^*Q-t!ss>v6u=sVj&S z`O&VWiIzDxIcc#-SDC>SxsK0gVQG1TkW6xKbn05NnTR9@c;Q}sidl4y^97c%P60rW zvDT$7X3kR|e3;MJ*x2zmy8UrFp);9<^3;-hZU*$|GYJf!ky=6{?@Fvs<-sPn@%hQ$ zgX22FzP|9cbEd)mQ6+T2e(~+CP+BdK?ce8o43alD4oqQR^XUxNYTnc^WWpz_hG(qv zw5kek@|?ud$T^k(Og18+foDtM`d%$}1$upettQ)h*H?QxKrU!rSAPCMZ;u)1O*Y4^ zob%diJ~^ptofA6s1%S8G+FGs&BYJi*0t|s%E!d-ExG%7;#Atty?C6IUEUN5dR}Y0x z{&YOO>BeyW)nrBeDlzAZ{FKyqnciBa%iHQ~1j|89g-^c}R~S`|WiC))`#zeD+x8>^ zNF((6taL`(_TCrUq@-OJQDsl@N7Z<@K${Q&xXi! z8o&F%^I z`Kj-#1_c`}UQUGKGj1aoD~@()P%cIb1Xt{7Q&Uppx!p3qchGqUur9P+hIZwp)|OPX zOjoE{*d8mSv|DVIfX))K7?yJK5sT7V=f;~i_^#uK;F!w@eRisy>~a1W5FU;6M|H9e zWyt%86@C+4Is>rxOgU6yWW&m(8^4Br!Es-filI{L9eHi6vYbjYfKTIUo`J)eDRea^9y`wb=dknto7YZOgqOf(_Q!$lUEY; z77a26LG4ah*0$e|sOuLua8?uJ%LHYn+8fk$p{Yb+=qnzL;>j8iduLP)FbBR=lJuKt za4EUg8`847S1bvs3Vu=9cv6z9GJY~89REZy)40Ke@%daU<`TcD&B$cp5XwM}I! zi%yBaKIG-*U5nH>$r(Hg08ZvRHw3hwJb#+^z6q#8dGfzKy>CF%`a7jW@fAzK;wD^n3bUzz-GvPD76r<}^~lstA?cHbRxHNIQi zt9@(d2ps@V61IMqX#8=|TUK8`MZK;vEjgFf!_s`D%KCH|jw~G-kS}d~Mk!xpKkS@) z1DsM#rIoZU;#YK|<{675Uy)J|py9M0#mBVJ#!gCe``>KNywnnR%uAIH8GBQvUq4J8 z9J^6MDUqaVVHMFV1WQ%QFM#&!J>Z`G^3i^aRvU>YB+vOyDxNq#@u1_Fk0!Ph6R^C zT`9Is*JgAnqHOKK!&gG$2Bz+lT8B~=yv4y4+YGCY^4T`IY6~2f8`TCKHJrcrd;5iu zxUpZvRT%&vx^PuS)&hP3;>^zoyFYS&Cyam@O~YuDsI|ikEZ`qMDNFX45bX2)%Af4V z2e*+!o&3~McD>{u-38MSU|CVRz<$R=U!6A<=jWb6EM-~*XgWrKFVrdkfQO!=$L`bB zzzAz|+>j9ed5r*$^v%P3R-c}Ndt>l%Mo*nbneeXlvx6aOP}nC^PePcR@tDo;bUt&5 zf{WZON2461z z%KIaF|0o@SYS9(27j7IV^5=fa*-mWzkssxauc~K772-vW9s5mgkzR)hq-M}w;MTll zAh?*cX$cE9Sh(DEYxFtJJBkYpuQlsWu}kGYnKTi^bFJ&dWvu+dV5|0I0{B%cb~zQs zom4St$WCAHare7D7GX53sFw} z(Pees+W8UpofTy{cIHGvnU>765GUu|-OL`adiDUp=$55OK1XAwovn6s)}uXQT7?v-Q#MxZam)#@#yHJ-5QyNbRX| zfKj82NBY0qAZ}y=%yx+ma@+=?zA%!WJWC^7T^Sn!CdIEr)90$P-(eNu)ltuhqlW22 z4RouEjXQG%85&ER*4@Pq^>-K_L!#` ztGVR6J}~ksnkPhm0}g4&JCox+JZE4^`Yt04<9Sdd_p!lU@1=B=-&vyHKKDk?xhb8`x4t~K$B4NPRbq;c&B!GBk`zV-Sw`DZc_ z9lx%TP4J-YT*H{u76WZW{ydfi3FMVBTDPkF-m4>uYPbP)=94bp>L?(78uIfdEtm_x zICAp;EZ9@&`|YX7XBF@7W({(8ms zArcd*M#XM9cPZGD6kvsB$XgI)+yonI0tEhn>=geMZL}_T8>{GWhV?AIWR!)r6b>mw z1Uw6U9={g^48*!qUkItpoKK*!1I$gFQJJ){CAT&j{$(O?N-a_1e9d#~GRXNL!xVcT z8R|-PHdrd>6LT>!e!o3$Oszozfpv){Z%`GTfVf5+614SGvNvjfm+_-TenUAolH-1dj z+NKJT;VC>_I3tacz(R9o+2S8|W%vi|E}l|ZMnoq+GWjy)`g5}`GDPg9;P1Y4fB={x z^GchB1z^DJia)m5f!`@pe7ESTm&!&WG<`{YOYK%0FcfQiIKyRrIbb4J664b6!L8yK zE4aX*w+4U(`|S3c|I!UPlH?Ay(11Mt{iHMX_#41jAe6i5>w^;j>MkZpsIi_nR}a7y zp&yQ;^m~wJ>5HdR%t|XVtH3=snyB#h2bbR=w_hB(jNxXR_m?}5q2z@h2{O(x>Zv<=2~xeoDRn~PWU>{GQqXus|po0DT}b)^Y-S7 zSwLuefQeZj!2d0?z#RIw0~VcPS13KV$uu45lZL-sf5Suf3LBQ00USPGgNF47<+%!|1UPwS=rl$tkT11 zxlh&J^-AgNE>}ps7^2e|;OAu6gdY`@$0>(K8(j7FMWqxS@}*x?wB|LwD@l8&m;P7&AM<3g5WL>m_I`6PB%b(WbFxbYz z5?pm`w?BO5nf5^O*~4jqzEs=8)q#PK?&{Y8oP2J(wdR?65I(nhyutEAnXuKF@Qp6h zOUsLoLRP}ctCe`iB>@RA|J`y3RSNLzj4(6VF4DOfREZxafAacI3DPl0eaF)L3S-+ zxLhA4T;sa8hvIi&h0bs-J{BtdQTUa1l9~t4z>dkA)Dld>9GmzsPr?oxsx;6ZzFo^9 z>aC0RW}zig!#D7JMdjMLCA7uL$9y4em|BVg9@)Uu4PD?}ml;ei*E5EhD)Wjhr}v4B z1nJKPPe;r|K2k7^053B`PEhL;)>NQxI`^Rw~27OQ1tt~D04}E}Y_sIxJ z>Yq*3m+LAw(r>np&F6h@pTI<8tTb8G_u!#pHuYU0E4W>X<$383`HqKMjv8la)}Dnt zTw^(29F=>yIQi1JOMzuGb_{Yn&!;gZgT2gsef$1nrbG*5)aW0Bdr||t+rU><7V`5< zA0u4y+kbk!-c|VWeSc}V(apYRz^?Tc-CU}}X1YL&Nvro6{ELWP!gmzvtqjUQ`1f+H zZo?gB=o9^V{ej)7Cyw%U@oZuC(8VE>S*}1^MuB%qC(Zi}yX=xOmY-y04@*d3TfWCm zb6hqjkBK;3JSW*25S_c!(6XtOxTSpG*TP(O77YXum?cNCz;E5**Jb4!#X`<)Y!|LJ1~nFW_EF`EENeVVOtGX^OsP88d3Tg zXypc$=+o$m1cyq3!q&7^g5v7Dvlr2|T-%(kleIS<@vRS~q zk`_BITfxYh4?*tj(X{&_;UFxGrZG!u8-l8iVxP@!%Cx4HKR-+ z8a6S^%}Glhrs`r8JFmZfVk`J1?vmjyh`QR7X88GT87jn9fS`j#{Io$o`#Xqs3gNL8 zzZb7^XM~!ykw%LB+G2BQ4KG*VwxryusZ6;E|3j62g>5n2VKu@Zo9A58`M8^3C@2gd z*(oH*LQRG_%q~`?=IWtp(3i5$QhDKLDKjP=?Q`=L%)N6mpei1sY(#Z)i+7p2re@EF z+dh!<(U69zqu0vg1@G#@>4_)=&<^fyp{NtcfEc&u)C_(1d7Ofw#r^dvAi1L6_MYfl zf4^_{=Z~E4AU)XZTmq&DFIVC`;r(eX%&>-a~8nP7)#SE6;CgjZQGTcWDOfhMooacAoQTKUn$7_$LD8r~` zIV9@$--nNBFF>V~E=`_y)O`_@H9X)*2Dxa&t5(>g=;IFz>wIplZPJGy9CYkF8n_D4 z-&4dvq|N5wBgjR{rZozvRC&6DovY3m?yh_g%!ZH4kyx+DLWB<-cTFlY1a|+wE7&(|3p1*||6WkdP1)OXlY{pe?j;AHZdFTf5pXR|7q)FILql*RL_8t4ibZ zN=fMYFe0s0!=MW>lO%5-o6f;OSXo)oh!N#R@gGPxCBh6&+1DhqOAXkZ9R6h7{%q*} z^t}U(vWf}?uwW=xeO0z#i-S116xe7So0+-bqR515E}G56?~DdvLxZ@)$2;1L+~cWp zQ$m@_!J&ez*Hlp#XjOH)oPB4TIvz%ESXicUD*-|7=;9+H%l*_8?PhW=2ClzJiFO8| z)UJkPZq~Mqn00^FsXB&F(TNdt#_hgWJ7M(`V_<=9+SZc0ZLH(a3%oaCDQfERH4=92 zVclh$8j^h!0mc4m4@ zs3MbH#jiIf_|!f9RSuhY>q0jrlX-Z>Bukw1$~x*Da1e_E1eAWwEzH;g@(@BOsu&m; zx@PrhFE13s^<33|mWxWxlTe(_Hu(^4Ev_`JiFDuTtIpwVaJ4&s|B>bWWiebL5+LdX6}HyxY%e=26Q@O|v3syc>t<;(O_ zAh~?;qrzL9OzEk&c5%kX$BVb6J!yl$l!3I z|IR?*?>;fDAJwQpnZOWKwUQ+fCrT9~BJOGC5)t{Hv)VpIpIZ=ntlJ}|_DL~eUe0#% zR!m)gsG=;q>%I0kNUm;l6NYm+VtS%|x|HP~63*qSoFY#$<|%YE=QPVYgLy%1Br)&V zwf&)q5?Gf$2(^gHzru=Nn=i%hcEhgPRmOlnR^!VJfD7K0U3RQaHIY!uRK;CDJh^ST zqe)YM&%4ZlVv9YaoHo-o8F_9N=_XRsI^A(48E=J`WB7hitsCGL{L+|3ynBH6tF4zN86p32+ zf4pqTUy|j(WhPa{4Mf8))&4WD?xpf ze@uZ{uFqm_f{k!({gI>3>#C0R%zMCPxXv`(J)YkE>VqIpwP(DY6cpYzV!pWoxJL)! z$8{5&6YD0YkDn?_G0vPM798_Z#y-dtka9%7ke1l+c+%fjaIguZUwYYjXK%=o&!x-t zTq2vPNx9ZpE?u9=I@%Z?vPp|?i_qaB`_^-%=KEx5bN+01gK>ruDx&M<>r)oVdymMv zCyfcx#%n&*L|V42p)&#+Wb8p_8{NiB*`a_YA4MjuxGaGo7Ome@36MF&6~bywVI|jj zWBElYEK^kqMYI&Y%o(vn(0cMx<^|H|{-@6!QLdDRWr2bMiBCx8&Q*xspAJPW(ZFEnMIa+nOYFU=*uPFL%RSd3Q zN&E0P3`3)R9yS9uwn_PWg)0rEjnebx0QEf<^PPTgouKHTdl`>g0q*aU&_KT^hDbgT6aNUimqxH#He`2 zsBdH=Mm$W!0HH&S`wLEasp_~FljlSH;#VIQNyVs+uteXXKuyUZy%+7z;IbaWeyo~p z%BSyiF`bhsgKl;5a=bBe41L%tN1h~^!sLD(|B$$Y8!30f@h%IL*{H@4(aV^pHAo7! z-wnrhmZTKR)qyD!ya7=#_IMs9M)x>j|8F036NJBZrHd~03dKXwY%-MO!E*E&SPg-=vX_-1q3CqJ;KD6Wnd>5RfmOQR5-`7F9PlPVAJc7x zy6My-@UUAB#v>sSNfzzA#F;I^K=^nd7{$O(U*(~AW(=vmv%z~O*IH{tr+Xm0rqRc7 z+gmtN_bG+iJ$!C-0XJWdt-Yd3J-vvkzEA~ zJiwReXSMddYNfC(D?3_WFrsCxFR1B9wJcB`Z&1GXfp+d|$BPZH%w>n#A`XJ9NIm7g zI)efJRUxKB{Jp)#eIyokI|{0`~O zer0pK3km?&9fsDFH5IF{6sqU1Fu0h#c&#PH(3nLDXSSC0I(x-S7eAnzcrZEERXI*l(~z*E@Gh)!E89}#+oCe-Ws`2OYYfap}D!Nu}= zW!z?i*Yb%T!NxSqf*M?%3_*i%Wp^;qZS@-&n+>Y`kHqAUv}HO~81R*)JhwfFR}aWIBj56~{UKjlg}BYM?w5X754@DoBM4S z`rSKYa|Qb}bt0A9rq4~rQ+KR!S_Py_qtq&QuLEbczOQD+9|S$y}!@G$;xmgwXI zQVwC5JTW1(6GMn9=EaQ>Wp~Eax;duBfmc*9|0@^x&B#{M{mX`?$ciQ}ti`#sVxZ_o zcgotmS8+r~_cyO!I&1A28!4bR%-)_1I(9VPG833D_4BSUU0u1`RZh?N)38=xWpIfF z4WyJMMgf5(s-4~uoL!WNxK8krEZbD5AG z%(yFJDbHPOBuV+P0kE}Z>(u3n9?gQ{}kB&1=;xP`xZ(_Jq-oY*-W>!IO7B0 z2=sjTak9`>6+^vm<SI+yv*qP`Q4^%-a|)mm4LfBqF=3b}_0v4w0G1k4J56kR z%^*6^J&|<}UmxM1&4n4p!%lNq*oqiM&0K@&lv8$%Ze_#tZ`C8rEFhD~yoXxO5tB=8TF!HmV3B`4i9P zPzzWX_|(wXCD8Xt1PPz46gT^&N~#a`8HUPdQlBFgFDC?3sz**I|NfF1_ymAQ^MEh0 zwd7K6zbBrt{Ez-e-u`<)Bs_~8>!JjNIFzmE7}`mdVMgYKFA; z6Ie%vdY&G-Q$aO~q>c5|#o6#sp8FRlBxX!t#2!?cuY7CZYaWg5sC=n%W zM>%FhIXcB@w0(=FRVikav1n(q^VrdK(5~5b^o_)@-w)*GS;N+bROJ&Xnx#h~g`6;S zS~ElzrM<)9u8FV5mbCEEn+~<;EfvvNDMSgebFsKHQ$4K$x=LGmX`n?QT#IX>C;#Bp zBh#IEW1L0&$TiLI$DEp|dLxfFNaoJ@W^^B>rd_@x?XAR4-`bPyD>=TqF4n%8P0inJ z8cZ7GI1|paVQ{N`pRn8E*?qjnxrR?BTILTsA&6+-{t1Fh?AsWksr1dz(N#4;9MyDK z(Z=`Gd+(^AJIdh20a$gOnNXc)Rw$@eTE-Bt$g}j&z*u%;=-j4_Q@NIgMz3`*(zRw3 z3A?If#A5_rR=^WE;bM}owb=>%||TeU7F()#ZLUL^Q`p@{rOL7`pdjZyF@=1^xyY!l$J1d zA`mxiRO~UNSAtY;NHzf6ZMUbJ2P`ZHJTJ>r(c~94%k)((Zr2o3eqVwO zn^$0WH#rlg%(6@07b?S)@|DdVb*bKXHzp_3|3HQRYCHdJR$njUNRJChNCH6sr@ejl z_UGbwN3&B!fe{ZZK|GaWEARIr1ya5Jq-DUY3H@@ z;~<>rj!TZd+!mtLSw>xfDo;*cRUfU&%kSi5B=%z9Q0K%O^Y1aTanC8Xl z`VdwAcL(tQOn86$4@F|+I;VK_P?|5CpXWt&n$&F6DWZ$Z^$F~C8%zoDg6aANn4dQ- znFw{85KT={lAcOS?T1HrXc;wAZs<(K7Ma#WGT|Y3W4WRvka}o@vzb0DB((M6vkl{q z_dC9yCiNFh&VE$S!pfS6bbQT@{%cxv3-$faTSbG|X;F37s^cS!`5(AyA%md%5MiXb zx3>b}A8KkLPo0h<_=8-k9s_5QrECV0EH5c8rlzBl;+YDGgHw^k3;bdr$$RJa?AtQU z*BOpZPJG@CK>G74O;iVo9M+AEhfrM>bs(nxd>i=dcgbhKNo{t^Y^A9y4mASz0>NTz zjs|*q0Fy-vtgdTC@M~GtZQOULqZi(b_7>5lrA&?WVhOWDLLl##GG4_z6IVqogqm0icv8EdJ$d;8Bz3IJ4sDX#)0U8vq?!)UK=zc-h~ z{$^$#Mu+NQ&Ud~{4|U;qNy@5$3fzD`k={M_3B+Hn;D|GWsk^&ddOEdj-5inei@LU> za7%>VTQGCLd33(Z%GMhVQWg`E-K-LrYz<WCw6mM4>|yU*6JT01_Km#G1R-`D+jHc?{)?ueF9o{D8yq zzI&{zCm`zqEg}+xmZc?fpDiB4a#7vE?4MHOMuy%hAof8}n!9{hDQ|9MpCy#pKe^ZR zFT!675K_%kxr3kxW0S7=Upj`*_$XftOHjpQ5)u+d)pC>*KtC87g2_}6>ixq#D4<++ z1ztAE-+K<~Rh8HzE8 zo%hpjIZD%5CUcO=R8XbX%##gwzh!yVrhMVtF*BuIXT7lcLmk==G0z{ zpnSGk*qt1%NkAsGEAcsuI>AADM&_wbPn?`;U(A*Dr@j5x+0>MEK12bk zzvt7Y<`5ZOPftXXhs{j#OZ#8a+x2;52fwjdtN`JJ{N4L7-FsAMkez*E4Z`0=#yV<< z#|#POMr2C*6k+x9x8yNJkf^gzAAK!mer8Pcv80n`BVjjy^rjC(rpJ^`ueAWTIHoP4F?ZN3K0Wq~r)?_kyf9z7rGr`7fPBK0>-RF_? zhg3Ya{-qNKBzHXxP&b!>txlb|-!RZex2g1NAi9;|U&kk=R`t7P(`>cftl8II)CT@$ z3mi*j{`74B`@KYwEjgiA0#u6+){k;TL(*C1s`M0XQ9*_ZS_F9u& z8s|@=EF6mSv|U3FhX4Qb17GJ5oocnK%bS33`gGDR_$G!H zrh;BTRSCe*xh^I?YtHET`#Y=mA3TV7;5l;r<8+C_1cnIpA@yxF`nGg|C>6`wk zut4Dce^ulE$+-Ss)%bsJiy|k=Dzim8*5V|8JlC14HkZM@9VNwYSm6!^i;rR@=-fy* zdCX5M#b)x)4n?Us>f6QTd6!vw5*4r<`QMi59}L**Nb)bQ8*_Yzhlh`pf)2Er=r=2r zLDo@P0S!D0%4$>*E-pK4yj}9QZY!C=+^ec2F7s)hbKE#NVI*dWGWVoMmW(MrbLYG> zXPwoxM4ebLr>21Z7#e{#PKzD`rMD)rpI7`4Jtzg!j9lcBUR$<1NO zYn|RvD^do@eH*h-#}OxHU?hrQYSW*AJUf=!=B zuc1jjEI=IVa(DEBho1)zRUA#Ixm0BC_CWO;Bv|XV^eR6As1&Gp%TpUU%nlP^UdcUc{b0l zyO}EeqENF)+&b0638rLaWi8rZy~*w&V}&eccXHw?ovn}z@$>se9@s6bfwo3j)f5F^ z--P$ir-9Vg`Y+M_Ik6>6%p#bIit0RlGVT}8LJn8>TrP94V8n=a3mN#!ue?oc%Qy=8 zVF1YZOst8d*l zeTGoQL`PDi&IR=3)^SiMn&n?I%)`Sto}*aIZFkpD<>Be>u2 zFsy*p3?#e4nt#c|X7OQv@X<2mmDPNXh6X`(k3Z{0ICKfzrg|Bihx^T)x=bo6PcfSc zQ!%@g3Uv4gi1q;#Ljw+=7<1u&MKRjY&IS(~U-vyuzW1wj@=0U{^;ac;PiOZtct3 z8Ah5G^3TDdrWQ(p;ujv(M6hCzb0R4|MO@&o3FIqe8>IL5!>SX5RmX<{0aPd0%{99; zd!BCXWM_oC`XW9YCkpp&gY|};C=s}3mv1BSQ0vmM2Ci}d=U1KWW&C&!lM%s)#W7^P zx))QRIli7a5TVANUqMWlS7`D?a4S)b@KV)qtkBRtCyFf(WDs=ZbYweS+0>vCS?I%( z$c>UfQFPpWjEG+3AX1-o%#6!_aIP;Jxdo3+-i-L$~YSeJkFo*YbGs&{Q8z#mnIV+Lk+NAanAS{clS`|&XkA27oiLCoFVB|AmAlbp zmlnfR4CizUa@09xD97<7O4CT7c;kcgfjT~EVP8#iJy&#^tBW)lpw26rWB8hQvyQEbNhV1FBv4#bud0`>OYV-UR@jr$d*Uk%Zoai+xYvs-p^{U+lXL~F z^tt6lM|(T*BrwyIF=#10Gz0){nmBG`N_)ulXi{dX#=I5bL1dwzCTZ@|e4c8f`}Vo( z+&0m0u9UAbgP77!T&m?r;hYtwYD!nb@Ts6T_sAExNGJ5Dpv7^hsjUB=7f#X~L< zukRmhWQo*c(<_oW%ITr+*54od7{7)o1y>QI*EmDhHE>_NVll&zh=*K3hK}<7;P;i- zP{E)#JbjPSarj4hYX((nu$C)CBrJy;KQKZx9`G6yA#*}SBn)+)R_e@(a0wr0>g0Q^F3Mjl4>c@(iBb95Ii4sq3pZPgMY?|E*nmh{DP{!cikX z&HJTTS;p7NHcPOrX_dZ)9^JwAH1U%Fi&$B7B3r9&%|I}XrBfa68uT59gVi-os@VYI;M4De7y3#j0s8lRCfApqF^>E=Mev~b560)gv!;<3U zas9Ix7dTq^6qglUZqioCJVDP_5;~vcx}*W0O_wo*Ft?rjz13#>qipNX%&2f(&|zXxW=n@x0^>y z3vpYonu;ae>Q(Z4w`ZU%4~IJBFA85Lv=$P9=POKW34-0rWoK0}PA z+1r%FIcX77Yf0$q&2yzurHXC8E(xghHLyo#T#IE5wvy?^FFle)6Xu1^-TW3A>2p+)D}-)iHqN zKc1-;8Q7T{;a90q=P=I0Xm(9lKC&XHXCV`!Z8Y>0IZbU^w!_c25iSv(8alQ|^w{b- zF94Jx>F!DFwsHP_wK*>LX$&G>>smvu3)AK0?mWI-=w(cG-mzv#euB-3YFZa4^6(z3)SoA*B{EKa-hvCc#xm$w1rYB5uWJa_#vRe^hW zajWRVN~osTwaPZx7+ZrC74ZPEmpS=Gs)6COcF78JuwI=!)|}aCRbo`eVQ+)L>$5qq z0Ap!sz-6&D$x6i*63!GXvOD^vm}o9r_F(>jq)tn-qLl_RxB2UUAdfZ1HYWWLO^{qi zWqbce8-MlyCx&L?_bjj{=0kEJriqNiKtA>f^ zSlv|PQ+G8ZAr2P|Y_5@Z*%-#38!yqqN)~d1NkZ}%OOqgt^HqCUk9j;rC9u>*7ZuQ5 z$f_=nSFAg<*5ZI7as3M=&(vf(D>R5q>*KUv?Ru8_uw~vL0N%`yzC13r+x{raPDr(4gxi>CsEJ%Xd$ic#3Qmh1h+#=v0ODa} z@2B!o8V5FX=47gY#2BQ*LmWRY%RgyD_xoG`O{=xiA|IaY8zKUqrzGSg&iCy1iYtFC z-8F*FQPRM28z+(G1PP2ePIX)pwanCyLSQY(DYgzaC({9Mv9y$)n&PFdy^gKx)V?zujNY zaQt)=4b!`MJfrwE=X5*lRv;KXs2Auv5@_Tkpn6{a6pd@g_e`SRdArb%OmK~NLk}$3 z#4xsecIRUG^QLgeBXN2XegW(d7*!xx9<6J~rJvK&SKvOz=n$gza>cSaGT8K^`C!a! zyP&Fyr=b-4p@x>+@BAEI+k)Lm#ny%}W!Ft(|I%3;?0UQO7@9x^=4=B)tqqDnZV8gA zIx$iI@F3#?gt&7tp?X7?BYq&pCweG_976zHfDTEOD6j0^80TlbYEuq-Z!>9X4uR<2 ziZF)!#bM^x7A92Z1wGr9n0$SZOmoc%>2zLa>mpD@R**-tSpKbkOWgIRP@g8AP3@0k zY3v8I|wq|H10=OTHvX)m;Gsq30z^`irGvV*5T?YgUv+mX-)3s_Z+G8 zceQ&P&JYL3Cd7Y@@ml4S^8j@2>C1885@&$I|MrZZ^su@qT+dFQ+)`oVZ5#5Nu}zoM z<%5+HReg0(RYf{6w7bu>asHiUs{VTioqW!d?g!{=vO#b;wji7u;!!gG1Tv3hob3e1 zPkj$>2pWz2Y1qG(uDp_XC7OKqBLraB#3?7tH1cTV_ZK;=KPBSwDz_^Jd>*{YJa1?r zE5|c_a?W%lt~D+N4yJz$R_|Q!eLFu6c-P+B>0`Vn!i1#rg9s_68wU zBg`}o8q)>w3TMLXQptML!SV;<#9Jk*8Y}>vI{d(8Rn=cH&SH`!1xkPC0y#%Ec{KhG zdNW*Fg1D+To85Qn9V_;6A3ZdC0hNZWY(a|-JAE7AS5bu(n8~=VJGdr(fg9D^B#F5g z%Q?H=$pXC_t}?dT7VPK1Zb!n$P^8#a1)nRh2=w=^7qlvA&ZQ>2c&20=C_1 z+(%j*sNt?1joLbshe{oeZny|8yLlG#!3-_GHBx$}ZTyb66NIA!p`o%xDluJP_K|o8 z0_hi)yi-Wy*&n;0Nv_)VDZG%Tqdl$1&>ndq6o`h#7%=X5f&Hc#ZB-_lDW3(MPVAQ| zWI#0#xIJ{dl8~F*Gh;*5VIt+!?VNRYl1??Ww25v&FccMm5)om9Z*UL?VWE&4`W(v> zdT9UE+9|rK-Spj|9_$FF3MDckuaUA7+UD>hz-n|VMaQ5MY^t~kQ4f5(juJyW8AXYmJ@ghewh%2Jk zSN6GoQef_5P2B1HgL|)oIjrS_!XJHBT?Kc&`i{ibC$8A&;hBJUj1r$GZ`3GB&DE_D zEap0L5Tv#vWiXe+)mTWBi)g}S_rtY)f(nuzQq zYaG*}tM~FG0abiybr+$;EnE{dCK7B7%C@9MIHw z2j_3j=ljM|h>_U3#uc#;Bu60V0=~#U(Z2|H`>W`ym_4&9t$BTFb zMmXrGba^gjaQdFUJ<^Pfxd1-^Igkj}iN!jd2(x-hlJ*Uii zXE1fBR`)BQ18_K?l0w~vNdC-}ArK;op1nPLh7Pyvb!}ewB60_^_&xSHS=Ikx?XAPA z?$)(YMUZZ!k#3M~M7lerB}M6OkS;+QY3T;(1}SNfcG5_9x4<{hcdfnGclPpK=bY^y zTzH8(=lqTFjOV$>9WQ{-G`?oJTXXafK47;gI_XPICcij`e2)9iP!N)dd(rHV+nei& zo7IO`x3)>rL_x&RG(s$nbe}U7Cn;hF-493)T{^+7hQ_IM|Xj z!71hJVWo7-;QyS@QFH#vJ11U_I;>RRyV%3yf<=9*KO|-LalLCp_=3>3GP364ixfFrfJ?o&(5l zA1)0>d|lhY8FZt_V2J7tWhaga#E62-=agnt7XQ-sW(eb5b=rs9-q;XbpB(ftj%nlU z?eI8IAYRv<-4`_yzf>N3z0gnulcC}_oR1j;6Z3UV{hGa&eo$(*8YG7rPSQNOI*%)e z>E5==MSXjGJQ?xaPCRFFM@f;Sto6zl?^(`U_jxZ$j^*z4V_Jc~*q=%NIs3B?%}fzp zu~es??8y^gx}!~iWV$#SCztB~fFcXfgnEaE6ZHD_55#S>d3)bbdM5ov9!{9|K79)% z{>C%dk3OLvLKDE++CbZ~m+(*sar9*`GMBWKfq~5d(eRyIX_^4#A%aOaXK@0iHrMu{ z>CLh`B^whHx09S^qJ`U$vzzBZ6&|a@q(!rb!%ecZ%}i!ODth0yy3@@U1r8}VIcA-b zc<%Q7u?cS7C|46i0jykARQR}=0dqx}fr&>69M#I(=2)08{Z&~tUCi1vl{uyErTy^? zLDs6d{xvw9(EiUv#R)beyK0CFaIy#fJDa|>q$G|m?kk&4VV~P-!HugLNyT*2VIpg` zwM+&wkc$CXSqQ&+_9daxNcTJlgjU?sC=f`!(_orYrpAer+>mv}8|IkL&xDYU>RRPW z#OqACNh`V!5%snUls3Y1CTYrJ<^4i`T{!A_h#vMQ*%A0*Q&;FLHFc7EXvFuO_Xf^?@Ki(^1=P0VX zW~kN(9KV*UT)FBRb(Xb%%a&)sc{+dX&3IQN}>~@h>d!-44^?C?S=+?bYM! z^WIyNvhCHVW1PNs@R+BpUa6336_gUaf+-`=0GH`e<~i?1vd5WP0&Q8;z^tiMiIQgT z|IGr&>J#0OG>QqzqxX@~G?D8P&9uV|;w+*uyiU#xh6qD?5s@q2z9xZ!WJ)Enepbl; z?0Ax*Ke-DZtT43eu5e+po$;ln+)s_JI||Q~4&7W-)4iJZ2!a%siAl?(O%D*0DBjiL zC+8;!nXz5k;L+l8DS;i^EwdVa)VKC7e;sSprt~9hC{T>8;}Z)kC!AhGO>F`#FrBhy zzod=Tad@0qKxk&r6ryxgXkeh3QzY`lHHOr&oAGQUJC9~znsL7BrH&dN2eWQP2XF?U zh@F_4>ZdO&#p2)ep)q00l|zy?aWMkKU`C0E)X4Vv=4|g)L&nIsBCN!sNvqhef#S3@ zJqeI0`4I*5@50#J$z11V3)SoIQn+2U@;=WUm@eKuI+4mT98MS3q|?P4`dcI#Tn#X@ zYTDYCujE^pGw8+$IyzRz#>Xqu$$VMOQN(@^$U!F05F zs$#`jrOLWwZdkD-qSXCnXT2-G+43YoLVLa|0d_(;;U021;i@7VhKME;bWhgwla&qD z^5N7aLp~-Gp+%6u3Ay<$`r9q|T1Z(RwOB|Mcw3hZJl%d4BZOclzR>=Xst-SYq2TMF z*&H<{UlI;R=W~4GpKV@AlwNn>UNOA`W7WfxZir3nJ zvfDnzY`e9rY@$H6?WQJCQ>)b1qx!8zYKn3u@$ZFVB%94wqCT=nLhwwvK*oKQE8RZT zZV)A<6h|~GnXfcarkG);9+~c_&o?Bo4J(y<`q9+u?J4T3jH-BHHEY$@2GD5O3%G6- zTg=s{+@%UbZBJARj*g9~;VwxR6r#WdVsNViJmn? z^#!6551k}f)-3|8pd;(PI6Qv;4;DEE!hd6tTa*7sEOJ}6pq&UyojrMB2>g@YH`N$n ziX`C$55&cBMl6rN7My&z5zY!R=m1iDHR$|zl2ER2*TV9ZVT`dEp6erSkPI)`&BX1I zZr@kNeGZiQ;cqa;zn%51@z$$8Me~y-uAOGQ!u7AqQ^S{eZ`Da&>$zLQ6^r!P7Ku_0 zBK7>3KNE^gsy->J^-a1F#~D&t9|$|y%RT+y_kKRXhL465S$a*pyI6Zhp+9v!iisTf0}gWzw*D%@vbjq}h-Qh%x&)rwLL67s1Q)=Hl!g1?F6H@KoTC882 zd5KF~WxN)Wgx;sT(QZTcw+~7;?MseKLbiO4=#Gl&`08*RH7<#b5)5OXs!$|w#R&P! z6=Qo+7?PnzzdEp&n)D6ev7t0-9CU0Oi%DvBR{LorYQe_jYeO+buZMa7Tp&yqtIOl6 z?9_6Qvd0JX+&Q1DTh&$cGBLkOuvZ{TpoA$%!E4gx*^UG&(J`*E6E!1QikJ?>lCr(t zWFZ%$8A}~68})-dnFXMqRZ`UhE}Kzf#(Y;biqx}bVB)4%qa{tCec02Z9LOm8=@OGw zTl5F(45>M3z^dkxxdE&mX+%|C8O-J#Qve{)!tdHGR_WIioWy-!c!oA=S{NJKaTO4V z=I$ty8tO6Wf#go?4x!C7NG6)IIEFDIN5#`t)c;K3J%h6r)jDf16pxBCILXhBSpo z_C)zre1gUn)i;@*|0X$(I3ryE%x3~OWQP1ArdJZE-w z$^UP!bQ_p`miu5f<{E2_U%z%<>5O$y zEm0--*<*CL0xCqrrncCas51!c$MKh1Jkk9FaADX$ zrdBgnQCS;GHOKyo8TqHzCHNBR`g}iOsUs+8wkv{N!8;&oHAtpC1pj(#f3KKXN#r$vmC2K8@n&|qKi zN@z$N+W^&O+DR$qmq=}`jQ&Z+XxD=eJe1Gy#ygl*2S%En zNK&cBrv*LKZvrgIq6`g23m&#cI-r)!bygAn>M) z;n{uS*D5g9jRBDy9Y^)Up3h1#CB-t3s15ggx*U~>>Qw#`VvAW7m&84GWQ)nM$2_2m znTm;7RF@#s;Mt?cNZ-mvEN5zZ?+w`7U@>B+^u2XK3|~RQ@{Nhi3t+($ z+_e6DKg2mdcA*R~jZ_U@BbtB+wz%l%=xp7Dy_0EaeB*!eU;l@*fP@m>;c>%|h=JI4w}th6hsX!XUP5wx#=3Rxl%3Oho*^eECpzm^LBGl@$~;{%Jb_6+ z{@zkvxrpprCh!BY&mBm6uFbUIx}MsG8u<}%~Hv|9KN& zdg?~}lhY2xYd{c1i6Du=9-tKJgno5k!3IAVcsIBquK~=p>vXR=3_xOwZ#`OsHoEguQO` z*x+tYo0r8&#ZbX?hX$lVHoun}9SyJzZJi-S*5?gP--UqCeoqDAOSHADg)Pixe ziA>_dQ{-G3-3sFnBz%x})Dfd2ADGN(8Qls>9eDA{fhQkp=bVxUlDQQ4pMFSe@VL8P zzyZ{{^~N4KpaS0IT@NR+N#rSppH)BMhr{_Fmh(Sp;GYoDKPh4_bY&}I z6W5xZ-6!0gJb!k=;e(wCDI>rBZEqv0yZTl9kX$;kP?Hz$p`#HqkmRU1!|_2nd31qC zh$`7VcYT01o@MqI?e$}FB{JwC*7yf%{u0GK`>V)JGQ>iBKPC$XmLII)!;r2pzS z{EFyuz1qyH{$6Rf+6NuJC4khK1#)-iJ+t24K!&}jYG>`j=0Q9ZQoYMzCjZZa4z2m= z21j$7R_ZhtVXKx))3ycZ_}0ro>&LYaL|0c=E#CH*PAo>=_c3c25a1sA)_Sr(vhx;1 z7DyTZ^eY*#REWXv?n4$~XLewEY|%1ER1*P*#t%BEDvw*C)^|J$?m8R;k?*8?OqYtC zYjPKA35Cqy;e1wg#-gL&l!CcQe>@}U&s&g|s0%FW7wVk6K~x&;!sU>&uQJ}sXKRmT zf`)w%jG=b92>SxJVxe2$jD;~b<1n0S9<40YpJ3OV-k7BHj|~eB={M|*`|!}QMA?Jp zqrJICM@~$B)%QU)hk^$nm7}CJ$T741e8%O0dq*Jh)snR+Niv6dyq6h1YJT$3`incw z51s&h5(T3U~3svNxIXd#ZsT_k-=Xbv1WuMDhr!eA%m0-v}ed8FU%R1PByJw&xX z-=1Q+cvW{pBybiY0q(?q)}_H#w0vTC`B`vb43F0#U(fkgm0j z5u2!*!Ds=y7oUUkji0-pS&U`a>XN)66~^nNj|Dl!QtuBOfj9dmoUR$yC{Dc6y}eeJ z`RU%AHX4jM$dmQWH@Q1vy2F@7!7H&|Xt@$0q~KRUds392-&0(JOMqVb1qZ!?L~E%7 z2SH!}uK<2qLEtjm<1kFWGc%$%kj9ke;z1Z{E=^>T*Uh&6w#m=8ax@}UT%x2RJ{L}) znjnal={7pSh|yYHgPiPwtg5bdlp`X6*{oL`^yiZ^n4P(zPA zNn?0qT8?nMK(R8fAFv2hqe0}tF`oQ#^SF^@ySMp#-kfW5RlxIV!~Id6^=MoGYf5qb zRKV(4!$Ls$GVO0vJFV!6DKTT5nmVhpCaySbrPN&HX(7SK*k0weP#ZFeZw0@kT-50Q zURi|mtD}%)3p)vfSa4bA4)T8G*kmRrBA?s^n`f+Xv4Gs;LWEbi_TCYZs27RYW?JiD z$qoddx_y**>MDBgfnJ3>irqJVcYEHi(Q*{G7$h`?r@$zL?PqoZdS(s^{CJ{_ZP0)e z$QPNNRd(Nk)Sl8T|3pjBv_X|y?$0+jz*odZm7*~sDG-oSQYlL%IXSTZz>VMS(a9y6 zjpQ81@;KlmZehV*GwuU+5mo#_jwyU(?ui&7Kw+CWU#r~wI?x{ka}C45FugaBxV>t} zX(2&7iM%CgwIsjm_AjW zB+&7K2xCXM4nIHNxH%v>I&V3D-VD3Pu_%BLir#t9%+H$go{&fR%VSglYj7T?KN{-7 z8oL9T?W)Yjuq{U2%@1Ndug_ri6-eA>KtglDOxtDz-`=-#sKd{^oy>6N7emC|qHV6N z%S8L%DxN;I>J|t#40PoOSt~+#WkrZ4uWWyG!V4kDVi~WtZ9jIbkoncGntz}#vl`;c z_cjAm{b?hIh{OlKsQRX_*#=FZUfRKdBK6>2d>0_`?}dFjE4;vEu6=MIzT-Xf+9a`c z{^4VMjx!;m($*Q=k(zt#IbSKu!CADT9hPD|*ec*8{Nj@z%_}&ya9=nr@2jzN;ZDY8 z(y#g>k}t%sd>JOS=;V9EE(m&6_n)(}vI?8}(3p+oDNL>VQ%AM@TxEi-O)e+ZA&tf( zi-Eup{}ceN`F8F^kp`^F%cL7LUBm*;U0>H%0yMA8q|Jr#)?jZVGX^lVOo_)ovQf}y z&wE_1Cio?jm3Lg?3ZVktO+63d3X|lkVCBc8MI|ss0hoR7`GZjR>*6_y2?=HoD53Sd zgC7K;4&^TTagF+Bl1$K$)d_`m;JR4;z@MhXAZ}zz_!G1E6Fs`5zhYpJU+Za!HB9+p zqjj(%T93fpleap$&iqJ+e1Ze{|YmN zr_x`~+j^9bK#(%?3aOEYJ`xM!LQXR5TDsTOo3@TA7aGXyqc&Too$u%adp;(|a~=%B z5D9@J^qW^4PX%j(i`VdpAdC>;Ec7#Y9XlS$*{MbjT{?UZ0C^1vR5Z1?T)v^OG~4&i1Iicxg%s zNP0e^7+;b<*Tnxr7Hs78TNZ53GSfbBh6~V)`d?^9HD{#_E8aXiIR+-eC`Kl36jFa= z|9}d|O9kVC($aX_<*zV7{@IUwV2PV#+sqFZZg&?}KoV|=RXgn0`;;e(h@uGbE0!Z{ z0oj{w@1GLm-E9l-Lq$zZI^I295t6AF`gf=LZ7c)dT|#^Lo3Uo|v3G*CYGMN%-mg8P zJ?pA-5$+Y8spw|z2e!ASjg?<#+hB<~Nn?2e8YFiPmar5IGf3jHRpLeX(tftX8td$* z@;Nu03ALKZP-G0iS-9XMkNlWL7!4yMqHZz%f&=B$2D_=D=h|fz4GyF`cmWtw>)_7r zy?&E_KXd|tt28tQY3!207J*eADKz39I>}W$qmt)YW%@(~ZcA&o^Xkx1h=PakeT#kL zS?&3O{AtEO_HyU>DF|0Y<-w|WCFs;4vfk`|jsjV$F@$FGD6`z_?Iep$tY73a#|#)C z?1nR}gY^kIRzeCY19@n^`)#SKx7Yhq=M;cbWxBv*K76e&KeROY8^x$AYPb9gHbA2U z+g%JYSFQbz4R5RXTgeMO_^;iq60T5Ea{!=iHg&ne+D8WDPtf)kcpZE-Wz? zx@W)U!F1!{SAx}^DDtb2r~Izt@fhJJM%hgMA8)~|4cS^kA>z$`QAHMwJMTf>7gt$D z6bDTQhUQT%+i8abQ5yPCzWb>x(uAXYL8wh%KARvl<&=JiWZS+l$DMyo{7( z!<3f+tlDms>m$<@L_Q_e0#VZ)@Fo{rNDArox;VAA=(b4SY)%hx_@zw0J}~!}a;6_? zs+m1ABKQ+w%E1XL1g#JFhtq+SbT}b{%qXE?U)%uS{jR9Jqx06gX2A+OY<}+dJ5PG@ zeI!kDJLzIc9oKwe5Ks181bGP(M>A=9oXQQm5QG{AK%%TNIoDUA+OnSC_uQ_Qh zp3Nlk!0^kmykRA`^^FXIy=wGR#NP8tT#A1WtgY_-NS5CofBgMWbR&L=$PE7=BKw6T z*$*+yCL3u}b*SJ{4N`vn!kYG8Xl{NLB#Y%)Ojk(}n}T?D!yxUN58Td<0_UXHRV;%gmrlMBz=izk$iE3OX>a{6J_@^S<-%$rKCo z9R$1Q)dfiGQ}kX$akA19Twx|x=%R~Vl46R<9Oo&WxsqXO#=^)MDpAP3e_YD$;siB> z-EH;Del3-Yy|rpZ>YHnmqJkFh*Q!?ulehl_S^>iZ3Sk8^4w+jn}%i__1 z&!1ah2-XB3vJExf;AGl{JuXLn!*%Fc%cK8#OQCWBfN7<~pZ5BM@%Dn2yzt&)&311D zU+V|jX8(RHVhyab@_%u)l+b{@8%tMv7vmpo7~%Kd0!XQU3LwLD063Zcyz-?nE8lG8 zb8!g%{9|kjg_51Vg7q=`ls|Jlkr~Nh^uxi<(?5jSoPvN|5y2dm?&dZnr z`|8@-*0Tjr=2yYb+76)iF;6miU&`PKc+@dy*9;Jr>TZLlg7QA!*~PLMOxviJXvDkO z_kk2XqNWK6`d&N~97Pc9LG%6-lix!5e`6o38zKlSM(-nRj92^Oph&lf_FW_p-&4u0 z-CcX%DF~copu*G=w9#p0Aki`oiu7AI9R}VG@pFIdhiW1J%Ay!)bu{n39|z(v#LH%k zy2xWMJz5qYO}l(1h}O#X4a%`?-2nY{+AP337#m)kd=r5RDJudLl|=+VFOZ{6M8cu? zwOy(&>|Z_lYeRR+122N|eMr+kn-*J&-!-iG|C-QEO;g3FIy~+%1dSFjPj3~7%qDVL zCh>$vPGf(Hyc%6T^tbq6(iQL3a(h~aD4@s#jsc{R!T~S1)EjVyJ~xKb<~C=_7!V-I zu3++5LTuQ)M-p!%L!WSE6oWGfgd=5N{|G6)ACKK2w*kzH=2NA}^s)8jm839=n=qsehH?h+g`2^a!5e{0NC#I0d``tA!l`V^dcZ#L zWyEmHANPzVoI|!^;C_qufgiD%6hcL;?!nDZBInr*HV2X-*r)lNU2rD%kh3gswr3A2 zxzDib2f2f*{ra~RUN(@uCg**<%X=&WnCOq9rj=<{iqBuqpG$jrwRrXLF)}iKC!%HB zZ`gj;{PcrjAklu~zQE15=R-GVGuEgP;V{NK+)g_mv-9#`#{#_oP%v_CkPF0&54-F6)={wIkxl4a`0g zq$I3_x^&+j;{vVUcyeJ28TW6CB_DaQUlt<{f4eT&wDBt!C}wthkkb)Z6G=m`LL}pY zea#dlnYckd2rWH*%spBzaGW^&vGnMEynI0d$wbR$_OJ=75ftozWssBu4w$>uQ(M%* zc6N3rXkbx{jw&~!@+yG42X8?!hiWj7^oKFE7dgS0u3tb*LHK5?st)J=qTMnwg@I>h}|f6rX=B;Y{UG1 zrCL4hm>R(w0jL9LyIw~z=^K_My&i5xNUirsh(6bM7^NP8^ZMb7x^TclTcR-}R-u z$~2E^^U}R|Kh{6;eWI*lHQ&S&jCGDV(6O3}zMwC{DbnWL6Imi~d-x%Fuw+vIvQGm# zQCRWd0c(q&L_7-Cdq0+u2s<$4_MGj`oU-~e88>`s`b>n%nFUa&B|V~{hwFj9`v>5% zl*Z-aX*4ePqPt5>E~e)}q(IBHc-t8|xj6KC&^10d7>dItOM|5V?hEZq&E){wrS9s-;ZON zq2Cw`icSj8++S(9E(PG6tWnCuZ<7m;K`U=+?i_UJ?bW5OU*%Zo2t>f#@s zG6s7DP{-W2@wiS}GWN36)C0Yu@*?Sz{uM94aHrM8y3B1Zs?!0h1&r`8_|k`|?+-~| z-(-7>Vv+Vo-q*d%wO%7tKF0uMC&XGA5SXd49E&EIJR~K0BgLfGWX4&WPg*DR?8L3v zjX~0xCfLPPz`|%QqL2&VHH>KZ=;$-#heE`El!kA-y`M8ch+lEQ1Aj?<7e!n=$^nrt zfpZ{tm45ieUDHsg4=0>T&)VVe-OpiG`!(4y#+T_Yy!E`TQ8p1^76`~=#It{#U!Qi2 z_81!&vk)TUy+k7|$Gnj$nk8mIWg}+`PUXcsSM~b2OpN#vuK@Xa@%Hi4Z^S%y6zYC3=jV|`k5gp**(GgU zz7d}@N1j3_^6dm&`|(MXzhjY>md@OO-0BY8l&d!3Oin|1dLI}J@3P7{Tr8-6 zOT>JPG4I`W*3idrrJ?Py*Kda$Pn>HEaC2eQu%r&x#QXLBcd}QNi%Z1rAJRXDPqn1SqJcFv36NLGo=aK z<5r*2H`-#JUqpt*hUSXm&qUOnj><4?nrWNrzYjun&ENIS3%-5fzW-#_!4BX$!e#w&I^q-&%AZ+Dges5E0M3iD>;Ef^p5Q{_}$oUkyg>ux-o)-vmvl z8iV0J(GCnih`R1>2D=zcnXnsa5O;hp!cmx=0d0Wqyk5r4=X&Sb7v0O%QAS%B?<>hT z7c||^ir0v1Z9l&UnfoyZ=u6K<2NQiY9B^*E*1gvUi$@S{`0H$+V{b7V%5VqJhc2Yg2=on`&oRsB8yaS~kS5E`@|Uip3wiY?kBeq_Te zV0jk6_46a;LI37l2?t|6Wp*BNsE3Hy%#o)MVMw107MLt4Xh@?(8}~(Cw(~u z>f>_h^AY~NTL;WOu&hRTyK?$bW(wSLznlBBay)IOp7OG@`0Y6rDTt5uIa669el&{5 zF#PoVa^_`nE9&k`&okbg7ME&1Sn^m+yO3?w=F8)DE^$b)vKk2hDR=I6GzK4>_Hh!n!=jU$CWm!l`XIxZ)W z6%#9U^19!=MAa*klnBu$2@4CGY3@Ax+;P)6%MB}FX)>lb9Wy>TiGMj(!>7EyzFvD3 zfTMHJTLPk_IQK;WsWOGnc}VoD$ccbQXfdC(`-MZdD-ZRb=G<&wK{{|ftiv?Ci7pBB z>Rj_Ns?R~*z$Ae_qYm4zzbQD4{<&RE1AFq{d=e{gRmAQ)EPP^G6MK%#!kYahKB_ea z9gl_5T(B`DxuEoKwlul|vG+Ho(G2!L#~l$FpWlgMViVLjHv?P z{O@8yCM~KzOP~YiTIRBGU4^JXe&<&m2e(k+Z zi$}dmm~i26W@e^5QNQ1xZYbYDN6RdNrON8G^Aa8!syFpxWe(#*ehGftAO5mw{5+OZ zANLStCfLAnxOv;Rc=h|=a)Y^lRVL;C*(dU+(c#yi*Gp}4v?7Ja{OaAx+8UL&-O$H) z=(pzPIaO7v2M=xHhtMT|y@1q4eg4qDNIscxFPG>FEB(TCpzx&s;RO&efNoygI=t}J z?x&|&_g1A$?f>v0{jF+L@CaT9+&FJ-Z<~DkB-ERp!j?_`OKnq15ZP`h@vdC5p>$1-uE&kIyrRiiY)q=5WfK)`3e%FJ-C9 z=^E2vAGhz{GnJwh9t=l;n{XpxI&V1-%7fDk?-z5N8CrchLkH2uf$rq15 zp+M^yiYGZ@Ei(K1OX1W@@+zRHbYSA&?DqgxnP;}Cgb|q1K^Mo!|RgX+( zf}tZ=HIwdqkUr;3eDE|0Zy0HNYb(33u+Kw@N(SZPSJ{bAxyDJ%p~Z^+e7pQD?>I`p zDx3D<4YBg)Pl8Z*cyAg6p8=buPh7tW(&X&_(hF>cK9DT{))yoRLc$fpnGNT~Db0NF zf(ZVE!NJB|U0t&Fi4@;K9RODKQe0?bxQVjf-^)*)K?&U$h!+$)&#{%pLytxYvd*tK z8ArS{y79c_tovP$@Mb&@-#P;=e7w#NUNsQKQ}&+x&wKx$e>&wt_nGSck#}8RNb-KPw@(@5JBUKdvq3^d_DzzuIy9{{R1f z9-Tv-8kbb@^o%xLW`8b65}C@_*3^_HU@1MMG4sWtnD*gtOfHenY_Xv7lrdN_veW-N z59C(`+7u-DwT&p3_PE|yEieP;rjxf&LgC8TaF5+jIjUYC0veu3h|v`k;Iqx^I+-xL zFO={H*33oxJ^-*V|LXww`$c-c6H%al?+%PTFBRQXe{=H~W=z0+^Lb@e3TsHz1%uNBBcZvasfdS8Gyk5AWZnoE>_Jbs4wgA2m#<`9`8ld`$4WvS6Qldotmaoft_{E$2$25kn}*m46X-% z_uha0>f=BX`Kvfb+nkGNlu;RA%SWL=vxbI_8sN<}JVr2?3r&bQC;Ro8u7c0>?0@Ss z^#ra2?>msH+lH0KPEn_PI4CD;(R-XHtx$mv&~lxvDn>nqb&-RPjz%+Ty=JC#1r^!Bb z%08u@FuI@a=c>u_$4~F^l1MdKyow#x=DYcvCC-&-j)Uu2Kbn}`FD1^c=>}fc?Oo*D zGO47MJwB_W8b{(Y>$w)~XL=3q{Z^Xg%y%8$s5MSuA_PF|BO@c*o$Y8QvYJSLD6Q5} zF+JaZR1oiuMW-PC$3F5`Apx6z3eD&D>Ee&0Se>KafDv(`#zx zTFqEx%{Q3@=`=WWAps4`u5yO#xe84s&GIBrSB{qHx7Hce{il|8`xw95Jt>z#$69bJ zms-0z+tLiL!Fw97ZPK$+0N;HHtHbXgh|+W!xS3X{JdGANDtvlcv6QFqvBfqlp(nro z_$Ej<#u7C0)=cS$xk*tnVijAZh*Na9 zvBO2_)Zs_l)4lX`F|h-{N|QFdq*@58dX^0-#?(aNXTs57bBwAp+SNcd)pCI6o3ibj%`Dm*Mg@5H^xJWH0wZ;8;UQ* z_{1n)0Zp2ssR@lJS)&}e&E=Ri*;`Fhj3!1=YIl<&YIC6mEzV}K&vrRAhjdkp^(`jp z{oB#Gy*kZvVNcH%D`wTvn`X;XAvG4Z345Bcm?18K;|bPH`>D>8b+U;C`Za=)el$U> z@@3`)_PU%&_`t~RtB$QVR`cfqc{+Aa>dYulMSTLi!56vnFGgvu*(qs4UxerB>Pw8K zCWSi;YWbeD~S?`c>TZro>VF3;qkIDngLV z?pQHN%f#|j886}jD@Tey?k%aQS?(>KO~n5!`0xAt`*ZnIHyrz0 zr?yA|@4?;>l=)Fi#7~Ox(0$KoV;S#GUeAC8U%gNP=CUa`nkB_7<8>TK=1MWbkoOuH z#qfa<@edf#p7c+LDo*s8`kXqOY16;I_)F!m}{wJgb${*lHa>S7a(6TvC5}v`EY?Tx7 z;9z9G>f{$;e~X(LM8q>g1Iv-Ap{+ck7GB|~lclfAfGSg(D@({iBJy?1wpD*yO5xW5 zfiHyE6@I~CZ5S_0Fvnt#Q~_bpU5*R=EF}U_2&fmj@$=0$FXa!gkJCZupPoyLt+q3* zd5=#xxLx4rsGN87G@{kWOQuvJdzn9Lq(xDhzQgpXg~*N_;%ML%c~Jc|rsehvPgmtG zW*yAQK#u7OFKfS)y2TPwCO(pLCiBbmVILJzh*M`6yVHI&A#KlTwos2@3-}Me2gSc_V(O^iL0WOrhB2J-$QZCt#0bvIQdHMhqol5}hzFWu zG2Nv_cubQWe|ImE3d=_|5 z-K@vC2LmIBT0up+a!g$#=pXGO7f4H4d`&y}-oX8%saFwKZ*0;qms<`m!tf3`d z?N#5`Cz=k9-mN&b1;@J#$l)^hn z$6rLEPCh=Tj5Q!~73)&AuV$#A0LPnvlj>RXnRNC-E6ZOe z58*~tA`tl3OZ{a#nNP@Br-Z|~O~OEFp7iC0am?5E5+qLoK|AG%C1kEkFX>SX2$V|1 zQHLUnCJe|4Gf=yTGm7PJvhyZgm3k^vt$eHEN?aMv6WEjB4Cj?bgdu5|q^Xr{I>U6~ zF4fnfev7;!4e~v&xX+PvvmW<{(cv>Fr0MCKXX9u0rEmzygd#vGmyV1$J)J;{rsG?3 z$-Z>x-%cvUh$a**P^+vAC!G4UKJD2ghJT#*IDlWH5dE8CUtzo5r_Pvu63_ZoUK3+V zrrOr#nNn`2bttCOyxTh}3>J%vcb`(m%xr1SXCsYDac0yBMaH=#9cUytglBUfAw%^t zs614g;((86b&aT92sQe0wIP@0zlLD>5pnzBjD&i%5r1y_Oj5&n zUBs+_xw8L9!BM)(d%FU+>YI&1LDgr_j%;>=`v z#pDR;yJZMAar$N@_0HdHb^Tl$4|{nXmJB?Jag}D_Op;MKd!pD#$z3LjU>+}l*h$^} z$8rx{byNg}U9KY2`sFxW>zkzXX4Z1tbTZNFznU9A(|)U3JYAeQffqNaNTNe(m&jt7ipf8QH1jjT{XKPS$ zR+1(Q@*9atwntG@G483QicU8hvWa}o%1uQQAS-b=W^{^2PvRS*hs%#R;rnhh>#cp@1C(K34B3fRV*ck9Zhrp&Gy z?TLS`3IF`l25AswgU?<`%u19~cwZbY)3ExEV5_nV9TEg=cDV6sT?#e}%Sxk<>|X;s zYcgL??~0YrslV(leLOy){|k$e$<-V&^Bia?=^{=DE+eghzt$Zv3k1qo%*10C6a-@wU zn?pAAt#~rgD@p7!>VzTqy2){kpHYzPlasIXxiH?)LzG>{ z$XfQS48m_!Ni*!tRDB{zqZpeM`roz%FW=1veVTvFEDjn0;>_BNpH|`2A0V;u_sw~4 z7-_g5PVAE;s8b?2AvZ0Rs5N7lmA2J1#lR_OvNrHjoSArEXUk}rZCh*D^Ey3VX_l^V zF34-p*k6~Uh^k!A@udZGd&=g{_hUnlu49z0G8NTzb;@owC7mAU?IB}f4m^xtf!ChYh% z|6B}z`sR}Yx*&GJ*4EpYC7Q^Ae0HL2$&6yZFNVnb!1-(Pa)irx)Hu;?d$+{7h(WPT zSda=5vW>g)&Iu6lEBRRT zvDZDFaYX}8&i6GVk=|HrTbI$eq@8`0_emC2t)wa=d1fuz8$@5MKHVfk+jj`BX(Tu6 zc0W^d-YD7gidJS_CdWnn6i$4S92sh#>s7y6*k{hvyxyx=s#v3Ll7|tOs%Tg;BmGw0 zqn+D(y!s_SL)a}{htSuGuG}V3_e=z15%y4QWk+FFzb^s7CS1dZqjJ+mvklLM6O)t1 zpVyRFy3YlV2L@ZxL^e$3j|VvK<#yd#voCS|Gmf zP+!T@lH&@M_*vz?`{$B5sm*=xB$MSa-XJW;h2Srlr-`3PtM;j?ySau@yVffWlEetn z^^d1J600Zn=vy?1YS4(NmM)lOGsFhaRm!Os3^GR6q-m}1){bJFkt0lIjOk&q2Fsy+ znXR~X$r4vNwo}%s-}NZSO`Tm5Q|#Dq*$lc~h2h9d#?jkefsv!FpW?HEoI+TgUWPsJ zRZSkYIOt^H;>d2qwy2e$v7cIhA)xL)P1CH8k2e^O1nad36}6`q_l1Oo+>+9tJ9cLM zjdU-W^^JN1L8hgsi68E|IJ3K>d9zLdTt7+N`rbFySQ^ez+zTODzMKNt?t;jgDxM?) zvO3amy3o}KOLv{trgt7Twqf}}o;23^15QGeV&^*ZX)7N4G9bz`rulG^_mS2215#Go zSzJt826A#U(Q*y-R--e`jAEgZb`5=fqbmgVxAEtuU1LLU-?A5eS`!WkR3C3me_D1Y zk25gpy*%Hl($wn{d7lMCxh_uWik*Cf$wBeVgH&e+TiWfs?88yrQsL(Nu-5K^Sk+U`%mHgXe9bw6ayff4E0uBtGBx+Uub#+M0jAnhtJ12(^(b~)aLfw^EAcZLtFsLB$;hupw}Z*n{t@h zHt+GpPsJ&lr~A**qlvo5kIbJ%!tPIv$8%!fjmD7CJzePvN|iKLr?JmUh43iOzybCu zG{>lRKmII>E@V$e7&5ur2$Fq{f!d3LKA!P?ySj!(-qClhF&r71%B6lX+kvEx#QNQ2 zb;hV21_gd#9TZU8fgEh*k_8vh6Eb9GLL}bv=ug%7zaG;R+828v=+yf!X_6*MGC24F ztO?@OwPR8=h;L%!knfh^K|*xytVhubDj{xth3@bRdX3?f_#p%nYEJ8Dm6_V4OYEE8 zZpy|_B&^PsuJsm|Am4F!yI9!Z*`XYPn$3)9%?&URtH0@8c*{G~&+5`Hn&{-d z9h}R=hJOV{T*xG6zo;ZfaIBv$BL^Mc^Dzcj(o9IzNE)=$j*cYf-nntsOT!8C znXOej*OxzqAu2$K@4!G{+pn8WpxYm=xR!>viAzdXZ*o@;0HNzijjY6R?U^WzeYZ2nJJl`EV?WLQ zYe-{^TB|YR9S}*v##}{zGS99UN6TqQi2o_5dY!A^R5BnW_9IPQE2rHY=<-S|)2Z_e z77*kzcp7*DsHQo$5?^Q~Kk1u}J}PeFlv1&tXU;L%{m6!w!vt@#&1n%wuOV%+(qq8t zy*EJPA@LzKcFr9&N#MR`?+4QT+~H6o&I+8)DizAta{d)!i0esa#*ad;CDwpuS7;;$ z*#qif1##W_+KOagpn_}y*9YAO=BKj%5fJQ|@*wTu^tzAB)L{3=64QeZw2FGfyw(-XJn<)B|ep7qmtIrS(wEDfJFnM~x?`zNLY*3`*Kcx+caYSg-AuTK^x~-U2GhzFqq^Kon3)K)PW75$Oi$ zmhKW^=mF_wltyU=knZko5Re*5T3WgWkZyP{^nRZ2dA@IN_xx>HKo?wjku2fZtNEa&-ZXmxI8|4ZTI*)D0rjKW^|By{w z)q#7wqyL$z!!NlMg??8$djB15CV_aplFk(N7bpDdn)t0g+s) zbXL&13qk99T_IXga@iO)KNi2a8@ZkkobQFeIN)>Ij*6lKe1RLsIvO%g)y@XJa5#9j zlPUgq$jHQxl`-r?#%%RbkV?*NonOiL#bgWL2l@K54p&33}}> zyJO{BZV^IVEl>3^?bRJ30h{KhdR#IL14&UCMB{N@9-=qj#bxFAlzdsma`Xt8JSjT?3B=LF3_pG7TnZ811tNbX` z=nt6vTQ6`U$j^Roi%Ii&_TXp%i^E2*p{r>u{t-OH#YtL4wYiv{fr2@$cAs!cozUlt zW7j1yJ;P5tgY-46hUo;G^kV4H==b!|;IcSYOJd5qhMXm@%BP^^N+zMbk-}F^By{Wy z6X&bBx>Rab_e2>(lGMz%f#d077<{FF-P3u6iHN0tc76=P)vIpisIb%fgFq3*|`;mv@N-2NHp+9>pbFS|*<)418ch9~p;+dm&O^s%KZ+3O6j%fPH zE5fV9Sap`MPzN~n8iB3`*1@|O9r&)d9m;k3^HXL3Y(>vwNMTB}JkOHTq`WZuugS1*5s} zcV747Lb*6rnj^Gj)W5Y;+_q^u06h}T!h_5xMgQJOz~f%$a45A5C-d8z zz#E}+6|XDk#m?F?`ySoDGL~CIk`lJy!nGflG+2ZdfF8|rA_TfSp=^gjZQcM(0LSx4 z7sDw0IJDF7<8r8%q6JZWrpyX6qg9ys?(p|3{rSz zIHE5SC?f3EaS>bd`oLU8bBYCXsLf5vFBtwWlyWIn^yhhH{=S(L?)ZWaW4Sr*+z;rZz6tcGc&nNz z%5~{!-LQPfGg)R|;`;`{5lbARTJ18#uT^9C$zSzxPr9GI@p`zh(!qBgE6@DpV8oOs zxwUV(sAOpGuGayhdwga7wqt6xVvKr?|EF6Y;@|j@!U0;%k?*MLbwGUO$0R%r&NW&a z`wCFbG}W=u@1TH*qUhVAZt)Y!N#CsAm3)aeb???+srr_eM`BN2E!vwP5>ZGay+i}5 z;`Xt9UJ9j0=ahU zjO}3XQKi)3Ws*HNhqr4YD^E5MQuM*Kp7}U@l8OkFG7JAko>7OqC8}CxpT%?!-NYd8 zZET1>p6nY@_R1Qk(S*vN9gpyFbu+PI@0stGUwNzPF&Kp>jabfkLl&#vD90@Gb-nfE z3+4QLG+kCLVO%b*QbC2)Z`Gm$6jjNJ;34mvx*QydlnFHJ&{@_#QxKF}xv8sCbDrpi z*NpELg$3$;N7?u(0S+5YEkhkvZQk=@cP>rfW0B1cu}(U$Z;+^(hB9OZd-Ixpv1&LN zND2oJYfcGj$Q??8e7>CovU*4wFmrB_P{FG3`pBcyU>Y(>67QcgZ01Bfs0iUVp<(Qu zf=q9_a2!uw$+?=-m;WKx<=+Othq+rKC-#mG(DhByd#eJX9s_4#_jSW&ysoHI&orX+ z$iu)ZBECUP>kg|Pn$G=WtUY~0AZ3PvBjXKAkvQGWQejwG_Xg>z|xx+=CGl1pfMv!G_G4H7)BWbWgOt%>kH>HG)?)P0yfA;kPl`Vk?A zEh>@bZF>gkNaRatQ|mQ((8AQaFn}^8kfondx!^@{l_$?LmArJA(yTkou_#EDufxB( z9mhQMxf!-U3^wdwo?e5$u}*78{opvXF^ty zquv7m)zv0+U4p*mmxb*ABDndZocCWfey?_^fk3QBCn3OBL!RC9^eKFJS>hc5^|$-L zGV2F6tSsf9gJDqlj9;;jb_-fom29 zG(w+d;YMb_NCE`BfQ&8TH>dSqukAm1efav>I}|E(2&YBZpd`GO^!q z=t>|zEUW0a4~RDufP8l6FyQ9QaWICZL28zMOG3r}!sPzz#s0^yzwrUgZP_+YG!1nb z6%g$9tr#95v9eNYYJP^(9Cp{y!;T)BBxmB^exzAyjRzGq{r|Ji6u-Git*evp*E`wU z(}YJw{V%;!o8co3Rcb@p*AX)JT7fBuiJoouNWjh-6)fN#N4J4|`~7qEM7JU!75|d~ zFkACZK7@U2g3g@t^~ea)y~#=JzTT~kk?ds;Adl2LiB4Yhm)K|#5F2s+o7m|8a%Cys z(NT%=#mthkjM0}M#|%dgc}=Tbaoe+hW%@{SA-|$)srDZd*igqO!Fyr$)4;F}@pKXb zgw2ijT%P~Ev+JElnaQ^!d(J0YQK~IES~@X8K3yFCp|Tub-4*3iPicD6!2Eluh?IT(I2lk2VGo0HPwlkm*3c9G z%WUG`C4p4Bhe+%{v6@-gKan<X1!U zWCq^3R@xM5dw*>-3XL$~6K7m72dYEGO25RsD~|v91#W52e*X?~baY&uTV7t4GBL?^ zwV$lg`rCb|{6{~|@;yl(w)P2P$Q{+s9H@Zo&ENfl)5$CS1yv)7e}?#_dfuwo}-2qZKkeonwZtg7eZZGq%;fa~>9yj{!F2um*p92o}xBUb6x71tWm-Xb0 z-{qR0pI;D)J<{KsS+7zk6}7bJ&(6;1AH5b%D^zG2bt(J(p0nb>oYsqL`IEw%Fz*GM zw{TA%T!#Pi-xZjjKfPt$o4F z91CDV=t#T&hPmPQ`V{$Fs=pP*%%%TZQ1Gh!*JnETry<}!*s635n%vZY09tH}<-aSW z7L@)iia+X!C1U>VMqcCb}74520ObSwbixdW@m-% zrVElcc6jP_EN*xB^B&k6@@F>OClR+No`Bnr+>XMF^3VPG^~$gPSuf_lWw`nGOR2p$ zH`BLB*vu-TRTZl06=nmF+Pjjw`w&a>NC zZGkY~SlW1MR)s=POZa7&vX-;Y>Lv9z6u^1~WN-hk*e^DAfPF>0Ha?;!@@oZMj&NqV zSE~mA6_;M;$A6^j^K0>^E49w*9*cGB4&|n&;ZvIRN8Qqx=2Xa&`X< z8y!jIf7=aGpY4`6#2hmxPtF|9#B=Ug1T2{o=On8~Rj~kvFAT=$qvfPdDmrc5_4{c5 z@s}4>wvjdT-wGiA`b_`N*B^Fh-kZvR!C)X-nI71|BTSWN-PH%?l929wcz=5;H8f0n zijOb*(q$XvaN~8d)6OsTyvgn>{7v|~5O$edG`64L08uItvyGX3AS zvaFNd1Kf11#(~Q#dKY&M>I2@M4{EEZPyq zNXUzS#K39cLDnxX(Tf>&cYvlMn|?C!6khLy!5r-#asCnQUS!-*Rh^5&JdNT!NLg)! zI&Jk!*nPD7=MSjP_<`qcIiT*MEI~H+%?41fkIS=~jgHe948*47@P4jAN=$tyQEvx;oJ@>xv|B_mC6_w?yzS@BGJP94qQljoCp!th34 zGODE4EEdy$X?&+Rmetd?Fi)AasQ&9P%E40b*h*RG4 z)B?VfV}w6173YT$B-{Wzg#$`#nnnMsJ>T-pDO#%`C~XBM4a>}-L^N3bdOz@ z61KL>r0sT0z+lh)if8n}t=JW(!gy-^`=WXQ+NOVK6P-x;lBN|Y70LtfN*@9wjQ(~e zUeDrZl@j^lEH9VjM~-35#{GSFN36K#bd2P+T_D**$A>&jlYSy`k}wgi?rwVk-WW}< zl1+U9#&d24Jp7*;Ks*1n7W{)X<-7H76a+vJ6X45+9#6>2u2$+%zc-Dbdz~2G6Gi82 zo89d0^4qrAW?2j%hc^yPm-|C(ES{!*zlRc2YdnCB+gq&AX2B!Qe|tlxooZXfFe^MF z>0wP(8Ttd!`DNMQj}irh9Mq!ZaSMroV*YkF!^F`bojGn|3p_YxnWdDH;=bej*k-AO zBAVn^b}6%(+~8F_F(G&94(g|U{!%7BN2lTSI)S17jvg-HX%Q`*h(SEL;fLVuldZn9 z>GR{AWH%nip(Ulv^rtRQ^9$9}u2KmU@)A(7_-CThVr6}w{nS3iINaRJirY4~r9g?`G z1J2DOyp>9qMs8L%fN~;S`-Kp66+fbCAdXi@0-c0@;hofgK0n<>*iKlW3C4_53p$7p zlb|Nt^F!UX8}E#IvKBREHL^@o?RI-abw zg6jk#_bZsuz~SBbmKIs*>B>TPDU|_l_1T$ggigR!nXzkREJhDBOH@7>GNp znBm)Q*m`UfCV&^moWzOXRa&}fJ#DLE1+wlN95zSk*>W>8saah%hO;6d*5O~Oq*9xk z^~)N*-ZEAr#Ia%jpIqll#9`JZkPzSzo zyeTKXVG5cQ%wLLxv*nm+UmZrP@s!K#zIPp0_{hIOCqZSn4Jg*3Gh z`|$UD0;c_x6lQ%!h`4>brq1RO=apg7Nqw!pUqu~`PNr}sEixBuSVnJ+hS`E>S)@je zI06f|X<&9Ddb^ncNb%RFY&ao(d4uut^6&=vwB->}ncsn_w4vdL$u&S-ir*g%+1R-p8Z5+3!K04I|P~Ujm^*JG$Bhk|%kgh35Wss%N+dyL=RsfR`b{6Kb z93gp2xB(mvReN)HO1h3?Zefjm!Ph*)b4KoVGhEGkk0%>lD`uRgA^SiCOd6d6iDNMk zF{OQd(Q>2n0E6_o(8c6n`teb8=Tw#Diq8CbOx?^{3RG}E`RJBE;Q@rY1F<1>;>j+H zk8tyhT5@(66J3)2I4;$I`hLAksWYiUTA}4_qshZ-qENBgbI@tB`Nq@!=w!~W1fBFH z=pz!T3z?v;7}jsYoK z^^`o`IK}4iTyQXM-plCheO!SjW9Vg=IcQBRXshW&GDDNK0iGwdphPRCcB;b`y_ew>2Zb4>M^fdF|KumbNdH&odIz8|68 zo#L9;(nu`B3dzg|zT_I9Y0pL_C%^O2Ck z-iZccaLyARf@x9Xnz3Ix4HRx=094;OeY(dM*v&QOkaV5!$g8JtUVjqG$@8NvDikf- zJ~-G+6tp$452P6?Hy>A(R9!s1g<`BQ8l1^Abmdr(*9wE&0nUp!)n2v*!G6=OrvfST z(w2bTEF1tCae%j^R)V0wu!G+M4Jv#yB*P@WmJzU-NU24i%$-DQ=tr23WM|pM@pJvD zym#mZj6z;#wBFv{tonNGkDor#a805B0mka62}OVvRpi})lLk5=vNq8yR9&A*1Me^wxoQm<2<`?4&Uq~FV06L8(tByBTj#QQe-q$ZmQoTXFIH5~c`~8WKa^cQiWbjCAzRKdI({7~QK!7nV@$q< z3Rc$?g+4dzlMiy4R}Og-lQAwsguEqCBtoRjEMnpP)#;OjXhjFy{Ik(mq1B|P$jEo| za|t8_V;|b*KyA$UOQqIAMSs_Di{b1JL<$-E;^^tZ6hTI9+xXAB!g1Vf2^T-(BzSVn z$(7UxM%lxBbSUFBDJ=c6p<=4FQTT*eh56E7a3b1Q72uI?qc0+rtUk(!5s7}S^@67g zYY_`fn+vCcV4kqw%mD6-n^ts_$MpIt$O$k=Xb;i}ZR1Ludn7vf?eO zD$g8M)Ny0()6{b_X-4T36D3mc4C(FcytV<{h=~0aWvv$5g0mVnF9ax##q(oOn@>tX zpoQztpDYBdF5&2enlmz>-cjf?#+X@hAVSRqk=%r`GP+UWf0a}-Yi7Y{LBeY%xAu(u z=IrWb{jh0c;Qlu6wjh7yeUrr7w={F@K;12LI1jC*E>x2*@Mw{N*Z_{2^zc@hw?t^kOzNxthyvW1Yi} z|4$>Bni6Q#W2Jq+xY?e+miIbsCxgc}AJUx7tO=SvShzoAzv@D;^MsfildVDzG08iA z0Yu<;d%V16OF0YnIO{do`QF-}F`@)&A4VLMyw+32AQb~roDje;7KL}_=(}rMY+9gy zXiVgf~&JM)IGm9<1^syCRn>QpJWmW7TC~-(ET2-l7_6 z-Dx=zB+%qnq(U3(IpA$>%x@#Ea2cm05U{Hxzc%WKRt7{e%Yi-`_poH z@zK$d4dII`4%TT=XF=mmgGM{)WN2@Le^3V9=Rng*Egr(h+nJ43t|4)zQ@-ZqR&&Y~ zvqQJuO!tE;Hykyq{cb?X)4}GnC=RlxlBwHZ#LC<1=yz0By5G;J4dV0UdA^I-SJt1a zBbR(&!7EkMm}Xh~jd=3;__f<(>*GraW(GWE+bLEzDMh^31Dy0+koi@g_}BY#Qu`d9 zCZ4%Hfrd^gMW58-8)T`<>XhRcVUxDjkgrud48=UMCBugigOEbD_P0G6B{n6opETMT zL~iXf`V0!)x(b{*$RwjeX3EC(Yh}}nYMew_6BA?J7atBR$@A60N7Zh^ymCZw15W^% znlo9zQ{)v+dAd!!%v=x00_Xm+;|3!Dtm^6HgTGh&N(JI&X3OsE%%48Ja1^+|p1-4j z{PExrm=Uv&butfS;+FHDI&I1K_6@4Od#?$mVXRd8=A@$kI)PWIGmMC2QUmbh*pwW3 zjn6wQVfXY5g!UPx$8PL}2^@bWNU@$rRHggNNu(8S^Z6>6zQV4s$zJL)=8Popija0{ zWflu0#4RBf^w4ZR?O=|lxH-AGHaPoACx~U^qK()>6Cv<=;>kCXY6^hc^_X=x+od zrr!$gjg2|P7AubH)P6PU{Hyu&A0H{c5!`DM23>^RoMPmal*ER4B8*ra*1rwtnhb}P zi4Y|FT>{DPb#VX-ca3FwP5X1JAj;OI)hrZfsGSCvXr-aYde$Mz8=K5Sbc-6NK6*!& zKfuNKI5k0#))zv2+?ucxk;dROshl?aeb>NM+5gGtA$Y{3cO5HZB~yaUy@@BDV7X#4 zUT_3Y3arp_9BlrYl#9N6cVkR}h~;ra?~bNx1JZpaWprij<{HaSJgb88yF5}O?u%1c zcOQf2jt-3H4(m*a=1WWR>t#mXKZ&_)^cZP5`6)PA+aC37N8eRLmA6lYp$Pndt-oNY(4_IXQUE94@q|EKjOV=^96!I}z>_s5bCqebBJTT?TR5>VQJIRCpS_Ru z9}ZDeF9`iM(oc_pMYDy6TT$5Ow{B>&dq_>0NTk#u@x;QlKXowVqdzBVV{rlpD}fku zP2o`8z&PTYqLuHi%=gAGgS1#39b{EslyVm;HPAj8gk3WvQuyLf z7B@jFg~=<^`o`*t$wusg~j2@qx?r9DYcs?orF8$dVh zbyiiArBo!>Z5TxxS~2nrtB)jr#0(J64bvRR*UL6wT(FSA_i4~FOH0XRKtAt|PnYI7 zE=IN!n$$;u9HVBwV*UrxeUJ;Ps5+Z^3Ta14Sb4R^=Jr`Z%Q>Sfz^KYhP5qJ?A(pfQ zxX;gL(p*He%GCk?m=-k;#(%zkzp|nIM&w%##PY^J4P$OTO_xjJ2gr4rZc6?b627JG z+DZ47z%%P;)(r5l`5UOVpVVQPjahE2f#3-v+ioF!h4DuTZF^Z)wYoz6llqN%*C7Ui z60A;47m>m|LI>YUDmj~I@E50MkCfBtS(LJMV=2&RYY?I- zxQm#rh-UB}7!-^`_U;~Ypi$V|D5ZW~`P1VL2K~lrJf(pU3x6Z>kh=X?qJGF3V2m=+ z{FaGu$ZF7Bv%R>S(91TbIZ6Bm%ZxV6Q}QCLWy&Y-SPRgo*2PC^&fzG^Bk^}g(uyB{ zO3!b3m^{f^YM{8I`>ndp-<~pw9?p+@n$2@BC8@m#r~$GUB9_a|E%|N-6=Z4c=o52` zdA9aTXiV_4xqWlg;Ibt|-8Uz{@>>T?h6Z#gka&wmbAC9zKlQ zg=kym$QDc$lL36XYMT&G9Eec#2Ln~bOPkznZoVjJ%l7WTIgm1Fcd=cQpmGUdy_zm3 zQ?9gP6>1!})Ll2SVwb-L;S2*>l?u~APIF?7LQRUZv*hI+p&o!Ey1ZR89|Q1cdH`&X zF~E*u^*ncIWdZ0?NzhtqK$6|tyAo%b9C0!ao$uo^>3dcY>W7Aps{FDZxnyP6Ya)t! zx8=sm-r8BH8mnV_BPWG(_V#!l;t|k!%f-QcMHVaU^WahOv;iCrM@RGReXs@O0(S=! zur2!&S%+&?n27tw#>BvZ6jBPHf05n!q|kA1vBRVfNFJTu#JBl*1%Oh>KR2YOi%5hL zn=2Q10V(LP#Kf;RDWQptBt!tA{TAV{5R_{K05xKXY#NnzYL}h}vp1J`9Tk}1iM3bk z256M%LpsAq_{L|LZ_EL*3I!3DH4H#l#RMu0&HeS-jIZ67l@|#_rv9-MH5}DFYH)p_!)|hiu8_?yak`h1rmV`k| zXgz*893P7v+W!7*WUXgrtI5pR{mD**d;@0<|-eMUTlXOukPHL!o7;H^kO zLoQeFh}6(Mp@3m%=h@BiwF-rBzQR)?bM$HE0)CBbY!cLTj}AuJY;#mu+zRsCt7e`R za3RR%^U{QkdoTq;c!sZpaw36GJH57p593&#D4ODc)|E1;!O5L?9F^gmsLhM zX*vy*d93xTRKdu*rhZ9mF^cLcDvXwMv&JG@N+4Me{NL%4Vuc`+dG2Y>&?=?w8Noo~ zz_&4|X$a=)MdoEceEpZ+PpK2DeM^pYxjMipF!pS*2*G0*&eG5X>E*FV2YowzZPXceDe9H>~WY&(~ z6KtC4q31bJi}O}D=7U*9tE zya0RwApzQO43mq&)zvO^KG3Clu!vaIlmxF$LHqGj?nUXxFDFt}V@0lb4)!*=a- zbfZFfj*9|=E1B!lup932wEf#GYL*kn!zvTH-JT26!JdBvul@&JkJJ;Kbzr+_fiCT? zo=)%2HMxVgr>e^B*Ipl_?X2|1_TIJ0GV0sxz*@3<+$nY1grJ>Ky$ zGc&V3C6noFYyr4Z^78^GGe&~JAsAPk=MC=5uT1ZgeeI|lFVZm;3=B5fYqj(^oiX@X zW8#5HJnJEQ1{9 zfUfJ;77ZlAZlDc!Hy1!*u@fb&z4^{N?&e-!qVNd(Vn#MAcBPK12^OB=uSe%xSlf=+ z$yM}632AW|zi8z$ioE_7U(qcf-?xoF?20s3 zxZj8pz4NR=z_t%5PSCs_A_I2JLhzQ#K1tzEA4=tS?b^|0-+B z=5ak%RTUyUnQ^H>Cu40?gSzwHZrqihq*?+Ouk3BomX6#Y2sU#Djwb4l(MhWXrR-`Q zG&QOd96c38ZzVw$OALpJvG8`~G;=nr1=I*QIdjuk;_?2_vplNpvg>m$27SXKYq1wx z4C7RUj%H`1PsN+m8I~3j z91aC5f@&J=tA$x@D!Y)8bxYZ#b<6&Mng%9c@jV)GAg(Z@lWZiW~aQZWhR{DJbRQLSK7a zEMP!E^XKC=eG!4*{px^l=#{DoeYTc9LlnSQV5y`OL2z=uRU(<5z97#*ssEUScl0vS z-?rLnvRoXfvRWkoL1njvC#?9^&^_f9;Z zt@9Gw^Eums5B0kj0TrHk10~0`S4hi}b{+x%@WCSTDDTH}z3ty3^EuYBX`)HFKFl%N zjtqldF74MqFKQwR-_?pFPPfhqc;A}d2gtFh{b=+EQH&?GVf9#Xe;is|4{ z7j^Ho`==K-RnK7y-m`0OHfW=NckH``{03}bemXZl&3CO5oUF!CP7I^Uu{MZ|a1-*e zm1+f@=2JdXSo6Xqch3Ts*QzoN@^kaU=UetO3D&aV9y=jx%gU2r0v_Yc8ONohqSt-_ z5j&y{ObylD13nFXwF>+{_tZH{pp6O5b{jKUge+Ft1@_dNU+!P;!wU#MAbb(3t>|_H z#TQ{hhePUXUAk%Sq>5pB5u#lfz1K!_KGk~@A+ZUuGq814mXj0lwCA)BbL`(3zS`0K zY~+@#twHDbSYAn~6rtFy7G9`$i3LY|);v0IdTb{$qG_^quo$2&CJFz@F?UQcjGsxz zr993OQeUl(s6HVaSFbWLr{Q5wUvEo$0e>lZ%nXFbf>s=O!y zO~jOfyJ+(DT!HgQ#MCfTf?A;{bs2ERa4wuJ9*4yZS|4V%cBoVsw*{_C>z!HOTGZ6< zmS`zft5j;KfDXQCPUgug4*W{zwRXSiEIOxcP#x(8_d;q41HUglTMBKb|F#pXSnb;T zq)u6zw%z|p41TgZTnln;HDxI-57Ztm-p;c(h!99jd%0(Nib5_R(>uF6ge{GY=bHr* z=^skL(2cD=SBm=r0CRCwv6~;$cBb-^85F2Fvs_@+p|)J6{ZNtS=2BLs6kqxB;l8|3 zbDdJ67f_fGU*%?`CCjnLC}sLrx$&|q3=ErWbg~gT>)vKXrc`5sCxC%eWz}Xa%lMF1MJCL ze0SW!Z2~$|$`!(NJ1r?6pyb`CeIZ586WkUM12~5p42)%IB?2Oox8UA`9(f5B3xk%Y|8Z0 zv{P57)vEE4lfmbS^Z&u7{SWa%F~PkaO!%Gq1Ux^5SIf&;qxSAWQzTnaylrY19#lLw zMs?&v%BWM)OqqD^L=S z+U1Cs5*Upa5^n(wJWsG%nlEN|gNrzJehMZnS79Q3!q({BxCh?h3-@8-@>$%2&%O< zmcPsF(M{4t{*hq(Lra+s!LBww{L8&b7X(VxUNCwI(TUI0v#5_Ez#09>l2c@IYs(-^ zZ1TK&HcU#`OZaKfX1v8&zibPKr;%i6gih?7MKy*cL!WbXA;+|rtL1#eRr$izW0Mp{ z7{mjbfeHqT&)Iv8)j?$w(~p?js{sB&=b0K6_Vw zj5HP7l0;f>zs0-&TU6Cj0&esaOJzUBUt;~~W@R_E@gnHpOvX@#L^mRCBsljI3ueJ> zf|=+83|Onr11D|Jbj5CXA_I>RC7Jcy>Fb1vI5I?n~_3-pQ=R8L0&@Atqy! zk{}}2ZW&Fxf#V#|YY!@gg(wN=i8B;`5&|ZDX#Nm4#M|H+K^ID{yx?)|y{;H&?B{)k zX;;tZ%UVcsKq;dBTnS^M!tH2NeS;h)NF{K}godr#SUugY{IQ#gnfB9J$M~dFsSPt|3*goGpS|K144zhCLwxsc zA)0Qf6OpJOgLQgke*HS{u$N{F$YtnH69ki)$UpS}rGl&6H$0~aFTOrE{#F_%mF%%E z-HPG@pzI{#*v*xfpSm1>7)=6wuZE`V0*S8}8dL|Ug2Y$AvylKR7q{nztN|+-;D7fI zGzSR*+@uwNQSP+tb8vNW5=QJZnsj{s*i#r(f&U~A*o09(XVoi$K0#56fxAdTp z`+9P06djuB3-(1==ckC?EDht{9VTU!H34017HFLuflx;{1JHa{XxY)K4*bF4zG+3k zmkg!Jwu$f0noV0^tBHnCpAk=1T{5h${g%V@WsBD3LrCi@u3d)(>Im6e~(zyM@YbucA2NzoSc;f-QS zD6&n(%Q=OwOu_<6nN1gIYLr)*|8!n9-k&LPDp&tv>Be-IKXsX5z~e@Km>Nv+$QKh^LNW|1sIch?*^n*`1Ym`Ivn%gUu=kDk4;@eCZy9)Gc$h45Ah z$ZCqsn-#~a5R@X(8PWUGl8?6@24CMw#-GAp-5yfS%CH@R+^MCx>Vf*zUX zy9Fc;W2iM%T>$9OZ-?266k>A1#F?cZ-2&Cz062Uso_Ag%HOPH6&UE-i7A~*S+cFPj z=tKAuXT6~0L-HaN`A6{OIQbb7I8EZPPu^UcvUY`u{36;Nw=G$WE^K3IDYey|Bey7R z9i5RhE1G6B1In+E9)WY9uDDfjfCcMW(r{Uy&@s{0X+p-s{~%rx^+~qU=1|j;ac=^s z6I|hASzN4gwZ!46NFaml3|cdip2U+Vu{5*ZGeha|O?O6wbRTKk}yU+AeL_T(8<7lHGUq z=Cx1;4L3)KgtZ=a<>BY8^~FWm(r-&jBh=N@z<|)R$4r(1=|h};V=}*M`R*`|u-3Cr zl#NKt#tXc{HLBUKYH<*{bRaY$wW){D)1Uyz?CR0%>!?^3zb4h%?%Y&4 z5(E2t)g36vRbSTQLd@w;fe3agpk@pKoluklz&M0T1&zzRC=KSn`JoLpt=q?mQ! z4zNvSK8i|Lb0wn!vGvk|aJt=-o>_$9h|~9|#EHGZ4i;C@RXp}TAUvX@d#kJrt>KEt zOUc`rnQXU$f0bKMcxayI1fkNPO{Bvx-WS7@$L5;7k^9th5u-dStQ6CjMlb!!G3fxE z7Q;{m+v{p)52nEuP2)1$T!jRtFz=g4PNH{FN%#uE;K9=T+*C}nm%wi>ZI;bmUfeh5 zWWr#aTk@_Yrv7EhmhNW-n#p2E04dI6*tb7Qi=}B0$twyuMzb^kB%57Jw$)( zRLK`H9`EwLv7kBft`0-&V8~4EPzolgzIV7>^nFiJCT6{b$4O?VJ$(c#29b9VFqPMP zZAmVgUp)%DE#v0iM3%~9v}51tP!aTIZDPs_(5HEnx9r55GHT$>Dv-L)L@e-TQywJn zvr|O@8LY&jKBKOzCD(5HB2PePwwJlZ3P5IO?e5>I1wJ81okN$1=A{~l2PGA=1?{v* z*KGGX@ZBdCP_+mQ8a$A}Z;QCPsE1X|Mlv0^h2sR>_hI-7!Y}c0VZY}SehLcs-zL$& zOszLRaX`MQa`S1)@7f*_y!7TOzunDg4PlYZ6>7D;w+}7asaekvJ z!f}*hiO}n_YX{F70~HksYprdmr?DG#y^x7ENf(WpXfN%BklOkNRpSo1$Yev{&G{ zSQ}Jrc0DO*Wm*YU2*rYBiBQ0))J8vPz|#12dqyS_29`9Xut}4k@Y2eY@|qXVRz

    zkGEeCFSfsS!9`L@)4x}E@R~H{*^B)ijVUW196j+4xdr~9v}FoN{XzQbm`U2go96kT z3R17MF~WG7C>|KKKd;SBDSDV`{2*E49r@(1k&*mWOx#LoLQ&d!F09TWT}iL3at>sSjUqmoPIP=k$~iZJtMBt>|LJG1)CPnahzX|DauVn2;kQy6n^Iz zHLW<{uRcu;Xsit=ClCGiI%AZu}=8kNfDk>pqW`W4x@i8h`^c?_V@{ z(k&I4T3C}(^+C4<{m#?>_m1_C$@YQD`xONe7?p{~adQ-%6q{fA^eHY3+3<6l9&ldn zA-Ga|NLg7>Tn|57w3S+D^bpnGytrg6u5C*xK8ubu zcdMh_s6Zn}9AsI5_U@GbTy|c33v5zFj{s!$PYyx?uF=;3xV=Gz4WQMNtW14Q7$JEu z?z~`^t;hhZkH^v6i@>Zqp9-9Jev{(#6S5qrU2bS;?$7b3bhS#uYCKS+}Wx zQ)KFusxY`Z%shlE7etnPmg5>YACj+%3|--|NVUmLaoeQdC6O`uMkI==rWNRLG^XKR z@@7Kcy~y)w%V4lKvrXthS=8KNgmamSg1}g37+K2kQyO=-1`wHWoQMkpEGLpQ&_`!W z*Cz-Sehsvn1%|H5XPi*d5}#QF%fuIuJ~%36h#jMl4#XwZ?l=Reyhk={V`F1yFX{o| z|3MOIKqIV(3}6ilvS)~}ADd1A^y4J2^Nnllni;!KO-m%ZQjLjihMNm|^ajFOqu=Ts zw-7*Mx!T}*KUNR}qO@yKhXn48VPdO$tTCKmwU0!ru?)yMk59g0|MX+#N2LI5ZJR>K zCz#(+2mF7JWxoe`DDl1f^q`TG?T-)AcE8-x)t!BwwHa@U9|+wX2r1N{d{ArlX5HpW zau}Ar_V7H($1kg(@iZv31kukdhj7iAb&JwO)pb1ZE*GTMWm7b*#$U zzJEjxB6{;f_sKnR>i4P7k%br@p!H&jX)pvVS-Flr@GgJF;;dho(6Zj`MoG))_iT^Q z9g|o3PS(%&XZ4|>1AH5&(3{;G2=MPp=>M?ymSI(`Yum6QDxrXYfFdQ`NH@~mEnOll zATdcnLK^Ar?wm9z-AFe`=LBh()H^1uwZFahyO*x#d-l)wp9eo2V~+c}uRPE5x=tZa zRTLI>7%k_1-NiH-^U9#UdabkrpNZ!kWfu!r$Yc=nX~GII0Bq}HgKU>8rgp5nJ+Iv= z3Wz)C={TBH4uZ+tF!7K_)itE1mwo12c}*Z zCqHcdMZQhM5DKI)a)rB!q+wxdRB?xwm(z2{tOS{YPsk4y?8NxxvM8P+W6>atsvn%x zukmEzL+50 z_q8knA%*0izyCM5CPI8gju*x~>8xlG_x)=Q^k*-;zZlht2R8-DQ|U7zTEMosLn%r8 zY?^15`@~T#jT5Xz<~%cDjhkcC>z}A|t@D?&^Ji6_o;)l1FQZ4xXk}IR9qj;&M`oJ_ zA9OHeyBV22FiF1&I$FFvoP1SL+ykUd_mMOs_C6mZGTBYND zd31Gmua{|H8!JueWkmp8Ed^fOtLPLODprN+R)&Y}L?@D{Xwh2svGnbN+N`y5wYICh zdlviEPle1EW=8W>r&pqNb^?4c^1J$~=bnd8NHy0sa!0^GE3rI3fmw!=jhWk&Nw#}Av zZYNw;RcM}!&o=vvEmFn@WMg#xQ+WpS17DvA>Jr{Jf)=rCTe$$G6N6tP$>%RI+sRiO z$x=l4M_Lz`C-YY6B0&;NDwux2^5qzATazZjWV9%v<*Y!V`=;oI8+C@>u@@5^yl=08 znfJP|Ikt{VY17<*`<3nR2~@%7c7aog$~E+e+z|M8PG6Owb0>{!-in2Vg;(Xo z9;|JdOY&BbfzMr09>_Qf579 zETJPqG5fyd@mxc#fVS!7;ZOQknquDQ5l5@#SxqPRYTWetm;cguzVO40=jAcb17yWh z7MPEvX3nhl^#0I+=a8WjhGGXrQPO83buFeTjcjxnh&;S3PgA4l${z;!aME8mBMe1Z zm5VcWJ#<|5gxzVrsVXZ+5=2jtzP&LPXS=vht63SldrzMV&J7&SR;&hgDeEqa#9}#7ja~wWz zs=y;g(Vqo zXCvbE$0zRBC%%xpO2nl;Qi|{|@6>v9l@@CV%M=rNv7KzGu{o16+n>EPkvU3r@{^uB0Ss~rQa zB^oj$ zY$$5I>ixYlqSKoW5_e~!htzR zGVK6am=dos?@i!f$~F1A)vT?S3;ASpPriUh7$|O4NC&|85~as1@j%B}8oB5>6-c<+ z+8TK+)DzWc5lu&eVGs6Vx)<0r@GCu1XykemvA0&~18HU$@@eEJ z0p!eZD!Swz*_%Z$d9uq;Q(OA?Xdv~|qnh1ETb09aU*^vv7uPQ$t4h8LK-eG;g>@>YiEoExc1W>61Qm#@j`djQ6;onR|f%?hu}{;-gS zrMs_+JMg}&QQmO8nA1n4_;xmnDdy|fdEn4P!V3Up$nePzUkC=u@S)4%g>AZqz=o4U zcAVcGkK~{L@?nB+#F+Uw^(U+h3Y%~Nj{LMIk#2A;pK?I)wU*)- z^aRFAI|h1G=veq;)0!h}KKHQq+(lvQC@-&`LofVF9Y;gf#=E2-KYy!> z@|o*V3i+#O{J7;Qj^vWq&EkXQus1|#=W&O-K{KB3SwX~M? z+6@SUmf6Y9toCd5&%U-nvZzUM`Sq2;#SYchtBYyY-C0*)iS^i!z7{R&d1#}{SXrS? z<9If~|JBC*qbO!zM!-~*V`WB&UZs%~ms5%JDhtU2vO1t6R4l1on5dNqF3x{d-1vb? z3y!#Fv!K49{=-AHlrKaS)M#S8kDTCuT1*n>=_haD$A5k)!8G_ttH!fc-J*>Bb#9WW z?;-HC=x~oK@@IhzuML}oii%62)o_nP0lyx8*1D2nCu##DM@mFm!;ChdQmb~_G}KX4e* zz3#wEguO=7JTiLEQ~G6uO&gou%zTeCGJLa{TR`L1(g>q(EN$n@;61oZ!PnO^n2qIZ z;tNdV(s3j4iX0FB>iPifdZ@?OI2N<*)1{EAGREW8^Sf5!aU;Bo0PWDCJ@6SRN8>k! zoSQ2^{UbYlma`zW98p~TZtQUt<)u|#WBnLaVOkTtw=2Ty9|+`V@T#t{Z)MF%ZrZoc zHR@%6lAIyrdlYTR)N47c&XxWEyMt(F!^8hDCiug?Ziffnu&@XcJE<)o zqVNU)1sT}+#4?`IsQU-5i(g>%jnyGt+zrhsC@p^`Yjl5giePm)wxFo~!7Fw5uWe-T zK7<~G)tfyd&icf;R`qn+V-t&ZROF-HATRJAwItn5EjdG+{_w91{*P$}1-AzPlvX!0 z%cpFHH+hFPQDflyIKK!CsFt7vA)d6*+@2-@mf`|^eBGOZ%=`v|0)sL%_`Dvf!mNTg zhG_4Pevc7ef5kxX%y0a$?iZBNxL50_ZV)h@o1lcf`edqXMIINS8?I&sRBCx;6B3C3 zS7G=M3rqP4UlCGa4|xMY>-i<3~W;C`e8Hc1=h|{pk4C&U>MH_irwY z4?E0!kbROS#5Qq@1EN{Lj)~pkc6#`!w6{?|bho|_Edq;vi5zs|q@`~?5_ zD>VA!aRh)uG(e9Q_rg3*gP!E6?aIQT-2*aL9U%Jandtjk4-Pi#` z1tR7mS=FB>Q7CtFw@Tvv@hX4&@vv@Z1f0k1Z}lEEesuf)uOq^LQ`R9H!oU@GU9#IN zYsV^9`KsMZ2a$U zFU%@lQ=(CfB`u~riE!OH(AlQ)_SU~Wj5{7AaQyEhN208>Ld@BT)VSLQLZV0cw8@!d z6r%K~rpRA(*cx{xr$c|H`}Y^g%pV37yVJJ3&BaVRSYKafxU8tGoQ$H?FkkJBy~o)L zMA66VBsDm{@o=z@`rQn!ZA$S4vu7CqjU~0MsvFPYcyb#&G@MI5ioBooU(w|;O37W{IY}yMNq~se|_ElpN;=58$W?e8xxBL z_s)L#T?~O);nz@-+p8+Lzto;V1dDem-*4>P{#S9c5x{ffzS0-k3I1&~hg~Kgl3S01 z8%DrJ_V~tAc$2|AeChPBJPxoGIlr*567bkFi5tIjtT4ayh!Sqf*TU4athToHZZex^ znVZnum6Mq#Rkt^taq0k#I^wVILt3MZ> zN5NdRaW^JT>Vo=|QW*CrPJZ6!Y}Uc@=Ee5@`bF@@L8bq~LGg{p-v={sXJVq4u7xd=qH^}^j64Nao2KBXqGS9d;S&WSIV}~FN+Xs`#soJ! zQCr*vwezP8?GGPI75&}bq738mWhFHXmHZy5IhKq?ej@42WM#JKh6zd9e$=XW7h zfbyF#T`I}HNMWihj3Fl zrNAnu#eZqn^Yn0Wat^WieC~E?51#wIg|MR|yenp5Ps_>4q1cQ7%;S!`q-s7(4Z+Y4 zGC2*_-n`j?FG*+-cpKEc7yG0@xh6~nwC}b(9i>*@C)}1B6vy1rIZ_MsfVww=`=qT) z!tecu2fyTNJ-Yb#c-NR7{eUibAOpM0EAfyszF?LUNf!~LjYQ7mDQkT_I7SdB)Qp${ z7p+&*5Do3MT-2Kkp!%oLKnuxz5nO(o$+yj|=;z`JTNe}p8yes4GT31K-WndDm)|)uJilK|~QSw7#;}*#BUwqDVWb&m>bIWv1KQ5agQsD0{vgs3hmV>pO-% zfxSZs>>c#}Su?$Eq!EDe#7)mV6~c zkcUwU0&*%JM}ZMd${iI3c_es|XjFP{y~8aWw1^{W?LgF*u;M=8DNx+6{7V}|Ek=`j zdTL5)f#C?lNGpa~_cp!$_0_;ZfI|=25GA5LQUoqk*^>ak27X`wKlctWm}-?nkAPUL zm$72!+6e_I)}c^;xz7&gv*6V__I+DmtTK~KQPGIC9=weZqrQ;+ePYNci1uxpz86r{ zXNkj+j*534ZXeLs)c4468AUb44n8vtSc6qx-1u(%|K_Rw;}yoK06NA`bvKY`Da-Hb@O0 zzCX#?p?|L3q2Gl3eJe&{*?zkkiEAMSDf=Vc;nRcilioO1XCp=5AUiXsvyIWSM5V9V zz|k|L>z8~~wcGc%yZ#iyaX)ra7@NjsuU63v`0jEq|8fTX&vx+M5dAe^WOlS(Ddz4i zZqAk~wqT1j*6Fq2NYi{Xi{|P**0t7fNX(unwr)mDr|=zqbvl8!=+X^8gcQY1h9+#d z+;eaxNBQhWC_bo1IgM(<-uEYF~~l1GovP`b)`Pc z?S2`aa#CwP8C%@7^`oqHSK=mfh{~+a-3!IxA7sF_SZo=-+V8BJSsWI;K4fCph-at< zdZiR_9TARg&J8t?-83YkB5x{6gzj3V><7QUl0dR12kRl-$)8c>pUf(Zhoq9@+ax8S z(ARRDSCl7~nA_hK3Gg!qjMFIHV2*gwRkjYtn{Ms23o8e`z64W9VTm^r zik74u(h6umG53`O8Otb$gv(T^#*Uq7`q+LVzJGXz}OxBGp#Jbh840X{OT@t6oP;2azDSh+==HvWBEN*G{O9F_#uT1 z*M<_;+h&rj?d=lU>G=xf;$jKpzM`f>PFyBasapDn-sC_K;u>!2zZcDICZ1#00%Cc# zK>4YPva(@|mgSjtH;QrP=tiE~iDS7HZy#ti4r8x(bd&jA)45`vMIJ^tZJezbyL~Z6 zVAqn$P^Bk=bW$gsmYjuwXwG;lNwE>SlCYwnZS?G(kc8K2G0U_T^#SuPgvL#Kr2TRL z(6yOhCfDWCSR)|2bh0kDV^1Alhcedf2Y&w8KROFp&1A^%MGgnyg!>i-JNa@QWKhNL zG;4^2bAP3rAtj>N14yhp6?Or>(_mMQ`7>DqH>A5ZA-gF;RVr!+y_4=R9y}AK%hxS;^MRn3N455rDrDsQ#E)WC z#o5^w#i0#I{}!O<5imMF>b$_Oko-;y$Q@Z&*`QZXS7z6qLh_fQ#<1w&JICEFE*wEQ zNZr9)dCj14(<+E_ow?URH;c7YO(Gfwf!`>ke_GkVELz%nuCAYlLy>uOTY!45mW1?< z#xlI}{-Y@BhWgZ&BzK;b!Yv)??A# zI@0tFi*0nC0P}m|3cR*;`~D&La0N4DD`!z|&tdbZ5-{^BY>C^CVJ?j7rJWIQzANE}I?<{s?zwj#VpR%6i z>^}nYdr`c#;xLLiu*p7{;bhmu5K*1*>%XTpN)+*IoI)tI9TZM&h$N8NIPw3bq8fFU z=?xV&mDsLlrM1`C>xwiU6C21fr3)=vt{O^m_G7UJ+mmamYG(Q8zDdwdXZcBWd#24Rc)6B?yn@t!De@iiOKf==jWA*X*Dif`$5Myrt=6w~c4c)<*$Xr|E2NBug*+ z`rHoB3C=k|+#2i-dLrkz8gmSYqa<&U^;kt86RfnE4rfaZtmilHG}J+i)+nXdSeRK( zkMp{zxQtdQSsmKDRMgdDt%KVy_RZ+)u5c}e!O|OGlkn`xXW0psEn7a>Tg~6{w{$93 zifO85&yT&RaJfti674o)H#mGBfSN$|ZRN*PxW(BW>EJF5^69iQV`rIEE7^lo3JP4V zv>AJdtoNJ1{ji*E&zhEmk3Z!g^!Vb4?W4T^9KBkrB5RLnGx(}QC3 zmkQ7-^Fs(Js3~Lu|4yPON(hX5@2ib8B6N=xIFI5xlT~i;LMBY?uQt{=Jyb0DC9Uoo zZLwLEJdEaboKWw$|Lz=RyL_6Uo|u+9Aer6df~4*+L6T9(NVf4bqoLxhaJ^ZrON-I? zG$4gFykeZdzT^FAvHFA2@{b|m=!pn%MVBD2J0~=amBMB&ct#7rBl( ztw(Jstw+zuQEl-H81KvSvy%-*wyH`Hd8G@zjqFcUn>iozKf*g+oMO$lO==+NbTVl` zHdt$wwwYj?>66wT1`=4t7RjjX2ShU<%NIx-HQzvHiei0K4tSuIALli*P;JfW z9buE_#m(A2Bm<`CYm^XcfokVOLYWN#@_kM+vi+%@n2SBaaNwn`TDjeY+f2qeQLU(1 zE2dZR66abHg!G=THODGH(~~rMQ{Z-~R-PNeofDKtf8uIiR~nEl;G@Ddpv{(mi#QH? zFQwO~6v0;3)vu_bY!rK<1nvgC4|jZ3kO_t7j}4BLNl;pdQF8QH@;{qa|2(V-(KEeEz;v((scWzs(6fSh*O?cL}^I0o}qV{GTu0++T zKa@(KfrVG+FoB0UyU{l?J~qA4Z#(&FDFl2;x4ja!7M0QPNz0+DlGXlDjX-8!X53w; z#m&86Jv+gqLz=T6gdXNwvSvP;W!LC7SYK{&d^mZfmedu+d+o)?I|$<9nglTnC$Ki2 z`CM*Hk~X>l0D1e-`>IYvpSYJUPREi@oj~Gs%i96w%boJz;h>`sN&J0H5zYZ~Hf^`` z-6q#@l)8h3kr!D64xT5DtOa>fAi-%}RKA*t=~%mF8ZP6jm+mveI)ch__NB6B5U+_BOk(OO;8$tecoHL)h>S;1MjB_pi^?`G3aDD{{M64;8JAzoc2saj zj1W+h^ftUG#E_#5!d18AO2jVBh#+waIIcU%#iAF8Uum^Jz-YDK#BF`U$!qr}5~IrK z>RfvR-kP4Be`aCIRWPZE)pEC@LnQ;=8Hw`Q(I%GEJFVgmG$i1FtiAS7!L+PGGABIcD$nv9$jb1(*df52yK9lNd52n|_M z5M_9Fl|XhX8IUFqWm}vS@u(~b=|yWg24BZhWudFw|Dvp0V8gt;QV^asU6I>y_O_CR zm35LhB=c%a=QOHZt9nY$1b5CkcFPt3tv<9Rjr6UdB40KVr+xg%<8P!{5?%mQ0ggsU z2i~amqMwHg^2I2?V%MW#Rs=&xeMuyw=iOFN3e6_R_9k+18KTU@(;+wV=jVN{O_hiS zTBl{|*;CdXFN|>$*vk3v58smQP3F?MycI)_oOQakXk<6o^xFVy5LAyM4=)1(fd})Z zD@MLTgh(c5Bm3Ex9OJb=la@3cW&5=7zelVNBv~D-^&WIUGr>OX53nB`Qsn0s9vo5Y zxK1WH5|Qdx?_W?eHtRJm2$@B$M9%G$%|rld z=1@K?MXLD~skeB9aUmL~Vp4w*FZFQNuH(;WFKu*d%C8H72GrY!&t@AE?_0ei$}mDp z!CT{w?FyermpKG?`OT%xm0>kan8p(?V6E6egw~AmXWJBA_Kxf|>R4EgbAFfxP3~b(nb#tF zQ+x}nkJWOp3_|^O`WYjHG9j~BL%WWGR2w;I)Q}pXEX{uR1O6CEzrTMH$xc|Oq@k!&u`W3x^^18ss4e17F`u%7A`=dnAb>i`=d4?AlOhy}*?G=2&gS zup%1Df3M~Kc@&KP5?0m)$RqHpf53r2ERI)$3&HkFZ#2rUMn4Rn zArCk#FvuRRBq4QIm|QJQyDbhxzS}a#&SF%p-5uncScSw(r&3teU-)RwUdHUIyU6yt zZIInGZIke_O19Z5Qov zDxnr%$Ao(G?t6jHb8n! zaX0D%g@+}#rYvgC<(&q+a|ismJug>vJA1_YHOB8F&egi@iAiW;8$YSUYn7c8(Du1P zckY_j(9B>N04Ztg2xv>FVR3)7u+wkw)$Y`h!zwBc%|xG|r2Vi+UuerL+@dPd&?nC9 zSamQ#Hb-Ql^(l$DzCmEp}hS))t;OTYAX8^l0 z>8j|#7v&$hHbCt)ct4;Zi;Q&Uk!v`-k+gIl_*AwhLrhQ(OE>KAbx90wx+LF#7H9y9 zNykp2eTQIfXlOV&zFBmlL#)+KZc0Hz!>pUlx$I>Pd9q_!qTN*NU=IoY{ADK(*ltY) zm&v9=>w0Vp<6MZk%6wY?gx6lD*7PD~Z)557+aV?SL;}WIMm{Pte#uIH^mIP1w(VRf ziuZ=1FOw`s%W8i@{7(KSNF8_*IbXZtvBq3yj_N{Qm8OYk2w$y+EALaFZom?dXg@Q= zKoIvL)tlV8r=_ymDiYov>AV*uAWG$x0WXY>MM|1zJ3mYrqAMYOj|;$lU^$nrrG1%r$k|WK4J? zd3xKy156RSI)e`t7}VMyrcx_IjyHPtfR?!5uq!GJ7(B+x)#Y8E@XFQnxfaoVBYGP5 zNpO8lpGYu{9xCs92k7(64`}e#-FtE>64Whff9DD-5Z!cU27%5@8Ftc#U`VD=o7Ryf5W^*tYn$|%E>|1pp*GA)t9Yn$dR)ib72-?elTWQ` zxM%rZh<#8|2ZG#J?QJ?boGQ2?*9%dkEVFy0>cHK!z2|blI@?uRmnd*cm22-1@ z{Eju?hcG0nvEbcm+Hn9D}smr7!3Er#gWr7{q>LSF%GLaT<0DJl)< zg~B}6(+Hlu3k>jnWwh&MCAN;iQhOkf&W(#bV4P6)JueRhMC9+JSJxhNLcyl(4L7^$HpMk% z@vRHF`MnFimjji{vJYx@8~U?E8%!TSJR>a3f!#_w@`;tUysWl(Dsec&gS#=U{A`@5 zYBO9h{Tu5a7Dy8x(%o0&9s{vB_fYzdaar2tBio&ZB7<@;QG1>})z$(zRR-ZFp|^Ji z#K6l-pc9IJ#*_}wF+U>WAk`0#q0JyP2O>IdKMx7BVjXvpI87#Ivm8`lTNTKY5`+6@ zWh*BsF_|p3O8+a?z z=k76rUEhpe|9+yJb+)4hWWo9@~Jc8Iihx~`ZjeKNKA zhRbzlLz{gC9AL4wqjh{JDXRgil2<^;{gtu9Z=p-G&JNRs?)7~HJ)g#N$rDY($zB*t z+gp|!(euV8DJ#j5y&B^Q*xj>8_X)9qD`J}A5S`{-8#M1Km$ZmSCeA%P4k!Ze$^0Ch z88WZpeXjw9uF-Q3z>04xL%zCw4rilT>PB~Fk`*Ahlo+dUZ*9pYFwV-@E>-}n)qT$6 zY~!d?v^dlHtD!CP-kev@(pZjk9QOr)A>fgA2fijW6lew-I#LAqFZOdDNyRU;ny>5- zOv45?-^lUz#}jROt?f5BqM=JkSFpa$D}NswB?=+Gbdvt(TV8LXxxT3|7tT#Ac=H*& zd|8B@(78U^2gp0K(re47PI=R1EWP3o;Cx`Ej$Wtb`c@(%V{dlFO9W3wh`AozJLjgd zdg}R3YE2WIG9@ya-TD^S;lq>xeI~r=a7gaY#S(_%hQ~6@tE&afEq>o+k{7sbYlbnW z6jRw8AP%PV(PfC4`%fdp*}*n~btaGaKaBopReT=b>_Mfq-OMHPK@a~tu0jP?u6#`7 zGLDgs4K1sgM=ZbkDHU*V=iOXDe0_~K<|>ifc!IJ)e{7_z)y`&xXaXTUi$Hlbz-AV( zHPnhlm49}GmR&i&D&%QAz`J}zLq#$CM!U3R%DDg|-FLQfL?<5`J|y1Wek#33(F{k( zhDb9PI{QH{$V!zjGiQxO=gB$C2)t3;)d77`#bYb;V-l*Fmm&BZL&%ZGJJVL^bhHV4 zuE!ru;+fx%)>99v-0khqE_C@!(a|KH*T9t{K3bWwm{~d0m>xpa8mSVPdjp zCa6oYK%;LXgArwS-`T%k0t?3n+-*LjqEy;wL4OH1y39 z){4wcpw{FQH8MatqOjR=^b-Shy0&ktE|HAh0LeM`{TF%uSkA^(c=G^Mq>0+Zb*fc% zdP8F_D?=jjYWs0w2WO2`x;TJ7{;i1pnvtz2LQH7M>0*-9q*T4aiam+TpePZv`K)nb zquF)ts$<&Xq>(4JMx*h4KOC3Eu}R|;3PM5SR#4i&V5#gyuxkE&gTfCX}k|B~* zF6=#JMnIx!zTcB#mAvuOT?c(i#Qq4+97;fK#B`j4-f&+8OIlJDJXvc&DWA};%2m*d z_&o8)a!O)&NO6T?cAJbp4uWm`ihyu`h_`>>hl6?nc6mSW44=m#%Lx5W5UrD?YzXOR zMKwv#u|l4$ZP@T@#0dd^rih0nnf=phV(E#5#eI{59QxRft@n*K2_94Cl?my$%)B_M z*)pTqf1Nd;1nh8$-8_s9lY4cjI!mcYZBcYCFCe%2D#Lwa*MhqZ05Ts%KP<(LD&B`< zbZ;ytmP{83F1ddo1BOCtwUN6Gt%g_GEZVwN%_oQ2=3H_wk)$G`fzYvJYH4}AMeJb* z0?oH&W`ZC5tx1H~lh7qp;in^wy3#=}^Gw$2PXQkP-LVvU&$9Ptqs7JGQ8U%)U>+{P z$bpW3lFvB*{*KsX5?OZ2iJU4NyBs>@1eCjZiHrxBb$xynN^I`OF7bTE+CF{b^NzE= zNi)@UinZEvgR6Z>#^o~Xxzl#w-6>1zu>wG4)P-13Eo-au-eiebjKs4DvF$eH9*d=T z++pnjrQ*HB9RUMV-a(Q9hJgY{GQ41>imreu0qilGym&=sahj^bnoKObrYZ8^_;&%T z$&eO%k@J&fM+(Gl&pW2a){!tMg2jCN{w9ewI*Jw{z51)ts40BAqzYCJE1kg%K4WtB z5Dg?IP<4|9I*B)$&t!d!Fl1BhWg029Mgt*bh4`ba_pwNiVC&}YfucC;L5SpIH}mro)V{lt;k)pzG|-95 zWx4HBfm{6!!dA0w4>=GwYR0GP5iAmrbvWFVV);-ky4N`Hkqn*Eq-MwVO06V5!feT@7+F|dOWDG%(vD+bbhWOXjAi*} z?E)wWAeW9Fv*G{LXAGj(2y^wWj`(UWbon_UenX$sI(F#u0G?A_%yMkiEJd%I`o_eqVz?1i* zH0poieYw-QEwqQCMW5NGlm8y{70e7bWfe*64~0S#f>WWkJHY7jH8$`#!l=)P@J6I% zXpio}-Fpqp4UlY<9$}R{{1@UOpK6SxcXdHt_i&K@Rn`XLKeb-|>YtAo;gH;O*WHlZ z)e8Vj%AIB47v9nLivgfFFOk175Hj2WYWqw=(Kg+?&i{fYyReyk1g@w!WqY*G_33Zn zJio}ieiEFQpWk5MuiyI=_)R&xTjnJ)TT~xOr7*)^*F;Ws?nCua^j;Uu&a@lby?$-` zB@Bk7s1$->2rK>tLwL4Q`(z~kzE^E;2=?DVcrsujyqj145{wBg!+~9z9D;T8GW#ux z+b3r#PyF0ptU-Q}Qv=}mD26n%M*!ss$wE2c3y;x!?odMM_yb$2zoS`zc@02p7izeM z0aWfUdR@0497h2jSRkbf*qgAm3=FYG0Dyc|%xw5FzDcKm&1017l8JeUD7c1HW6oA! z;~;+};Gq-u|K(T{sYi^2K{d)M$}2lke+N;oEB54_nHdwLiRy6WH90W0B!mH<&ugC} z-vAdG%6fA-d%6QSyzbVpdY0Y&)+_wrE{x173}`Dfm;Kwrd$?kowW1;;|NgVT^A<+5}?3WeVSb zck_M$-c8({t{MQgYsA(O|2z7@943I{6#D5E$KKX0d^#3o)246vAvkt36rJAIwGoMMP2qRVRN-yyKAOx6xR0KQU62Ry*m=^z)8`3KuB+W&@a z=kxS`%(jC~b#M7PFFnf?)Lh$d@zbA@ZjScluZ|YR0lr~o{@!x{uoZxRBP6GS1hipp zf|Ty}8^WMe#-DtIAle%~LX7A?@e%$W2k*vkGGE`?tc2dtvwJt83E{$IG(-`(L!UIL`d{L%68a{|9m5|NjWE#an^BG^m5 z6)V@$((|X#aYpkf2_nvr;9!(tSq+VNi^;OK zxBUqmDJ?W(ee)k>!UBGS&GA6+ASC;P_A2s*ojVMHnT2KY7&84N@~N&oz{F*L2X6$3 zXi2VGI^0b$fV+*^auhcVHs`;%tN#-Q8$gDe5`9x=Ur0no%{{G9fg@xzoFz?sVMKtH za}yGvCGQfDZnFUdzzb1DIEVk^xL)0oSC&x!L0}!7_m&HVh9xJfVDeT3(E{@VIlh#M}p}`IR#3Ss+8$WyZ4{YcEhL>HU*YW_D>1`m; z!i6be2@CLf8`Sqfb<0Nq~#|_H#ow@W$cE?ZezK{XNKEGgSTul;^Pm zb%PE9_xi}l$lKrl@%YJjv5pnf+g^&Uy~jTMPhqUWN!`;A3?er)L<-Q2d-M1Ocj$lc z`O$Nv;#muS`jn~-!H*SbnK2pn(`*18r60Fk)Cj((fsb#j?ZM+4w(G}VQRrYYW;4W+ zJA3W8R;NBexNKQmG;b;>Tzl{Sre^3A0g^^mPEku-&$7lsi<<*KvY0JPn*A55TlZ9Q z`xUXArIE_cRX$aI2~~c@e%bz29ZnRGln{yE%%M_WGSZ^jHBkAU)s@%^J%IN&X5?f!gA^Z|Q~gRz-WQ(7$xKZp`m@@qTw z%+UyCuGnX#u>nob1Mk3zKDhaK=D(*3bBzcJl#xnCWl(#*r}^BY6qRD063%N3Z1OI# z+ZqGCng(_#6sD!=r}%z5`gPF<(6#}N)5oV9wN~@IX6EJXfI;Yd zz`s6fG;*BX-N^j)34sxfpzb;vOaY*``nLhSObOMm-3^c;Ja6V$OE5sPdR7<^&^3Yu zZ+xbmo_z`2cRkpOImj1L{8AqupTJ?*9^CVx=*E#MS?S?Jxs6VBhH3`Mj zbcR$O1M+PXfIOe&Ik1I_yHjM~{VxG|zT^zR;0>k@S6Ep&%KOs;b;1h~1BEa4t~sJ- znXJnq#xbC{HGb8N(Ct)p2N2CGZ^2jFrFlG~ew(AEu&BiJTqXuNl2M}#crepCo1Ois z1wbroMBKfF;LZ{Hy#@+Q3oP7?HhIn559@cXY~w~yDRU#MzYdlv%x z22ihID~OxMV6s^+b{o$v&$o^Fwx-*IaE)bo&kUOy{Fdm4LYh2Nwm0*O!B(zY8kdfw zQop>T52C*6qDAz@vkmwS8wuP=!L6)sF}>(HyVuJ{NYXWCL7lR z^lGaZcL!LKHp~m`ML16d&~)LlyfxFzV9Qxkm?m8DHEh!~0U+H~ceEkhmc-*MrO)D> zIs+5`1O%P!=Rj+dz&3Uh*mBcZ_v_2KZF0Udacb%w%sg;a)wJu$tTAkd@LGCk0w9Q+ z22*Wg1$5B^7hF#2=8vqnF9F*>)Z#7?z;k8^>m{(ZNx>cldzaWcHOVfuPRW1 z-H~nufP@(skPnTWFwu-t1#hi0odZ{=zx+FgD4tzJUn1A|9I$gQt{qpJ@A_=O0!VMJ zAcbZ{HDpuE*F`RXSE>SoS-Gw+4glfIf&O>%m1fbGfbLjTvGeu$yj8~w+d#gt`Dj2Z zY{FKj(WM3$_e}zeueJl3`hQyHG6l;0=j8b2uYmT)cK~TXTQ#GPbdVDoLakbKINz}4 z3^fSh8SCdc(X6|IZWY$opSxWH$q&SjJn844w6-&ZCH!F-(*XCddzB`TRldz=xHEYP z=vHrT71eH1UC&k>lg#Y}J7k<2aj7-AH8hPah4Ae(9gNpZ)qOwN-5VJk)N|t3wpA;C z8w5*xcirDz>j8^EpUpZ0ioHG1=f1}j5r?kzFA!h2U+g^t3+gSk2OY1chr}JD0Dsn= z8Fx5@HtK9&FVbJVywrDRC!co#7Wc6NnvX7ZsPCF|fVE(5sG``9gRvfbxC7EGxJeZE z)eU(jeNHx{U&cWU1+2+)&AZl1#gngK1y zE6QBTUCzE0Ph`5EmrZ*?RufHETTN4d)I@D!@xvY1rqS6}>$v1<^3u>Jh-+{A;v&H( zne?t*0nH>2hNpyvH%Id!`#tn0$}smdEKv1*4cIeU*#ymR+86ewPh`;Zga9oVOt&MC zhxiDyj5dW0V=tjUlLtNEevuJjCcXxS$LMdxUu5lu9EI6)Yq=hc9w!4E-kyy@Ej7RJ z1I@Tl`C`w;=lk_4zhQsi(5y_DilxW5|Q<$HwkaBZdUf@4nmR$(IGnN=m2p zeS)r}WC7U=ExyyR^_79qTh3GzJ*-j57t7=u4Tj&mp{Q+}w`7bQ<1YIfe3q!?|8cDc^aB0vf zE`imOvObRDbA!uYq^y3fFNj!u@5BX!_{Y`&A=`uTh{RzTu0G^4CHi7R>sKmDa#>aW zm{A9)?x&v!ngNBroq66N4nUBSV7R$d=VB2X{BH2&yeR5>bhFDJ5BYvV_I1f*-%9L% zB?b0p%k!(3q*o;z`V8*O*3oL}SinT$q)ojk`^C4^dtAZ03E5~QZG{pVD+54 zS=GE-9bXihtd(t-L4xCEA7iHbMTh$#YDMGMzZAU%+A|a!aNcMUCJIv+-%rN;`}Y6= zz&Xo02rHke>u%BRNF>RWg4bi$s>0nxK49(61dtdue3pcxbu}*Dt@}Uhy>(cWeb+W> z0Fu%m-Q6vc4&5C?qYT~M2uMn&fPi#|bccX+gD|wDba#ChdcV*2y!+da_x1B4DP3J5}}1DqCE}Os2q!} zYp>=MHjTYATV~h3_($aXI0?(Jx^9}c2J%uzJmMINC!dQfgXIjvkEx2dsG+`-;ZuERe`gUjo zRE*5n7NQwxF@wsda#rqU_G5MzvVV>2*&a0S8iGzi4N|{j7v;R1hL6)u3TND(T|YMX z$no1D+L1oY%vFj2X6By%U%*vh7R(wxxRhXhgxES)1neyGazJP5r(C1xTXzqdI^*xB zr%Sjxto^yfO(*YYG$y^|FlYjTxOC4S1Wd=Nsa{<7x8C$4+kk-+`R!nh#RqzRf08GZ zdRCKzC=ex8obyaAWFZZhVSO^IU_O~P`Ztmc=ua@3aT%3#n zP7KIsD?>x7G(F2R-EBSTL|bO`_=J=I6#K2YPi(h0_p7IPhn_S)t=-b%&EVqn!|YzL zic0KYnt--$vw^GyW=v;b1r4ej#t)()fACL*=fCsq|69k>Me(3P!fzI^zr=;PtFp;w z>~&wdzv`dJ>NQ-Fn35-(w5+d542&~zUj`;1OSft=vCYiYJB(W)hR{VC16s$mxN$iI z6@;e4!Yso2&QQo*AM2>&hSYCBMiXQa<5;>hwg*779R zdi#5c^SB&OPII7`mERi@B|odowY@g}HTQ z(_`^kZ)O3I0Wb&B_?8dE>Qs+BAEE}}klpWWV<9>MV9=c=>a9DHG-_=+5CwXsHf1nr zmbZ~SJJeD|J`Z5=B-_0laItTtq_+Tw8)On49Zsecf9Npx9V2@<045G<09oFP`hv#GS-6bXo%;47c&X zM^#9|;%kFkNzzyvg);#Ua$2OfN?7qg_@~1R--)$%tkHTEU-q2vahGvg^jFi@~DA^-0ss)x}_K?GoKwj}H1H3V5Y{w5f* zq^;!)HZ_f(lo_$acc7SevU){&-j|U5@4OSZ0W2|mFL7Gb~+pLcOM#llD_t%oNlK+V@9;hd_@ISuV$jT6y%fVrQwy5 z$H;pbk?MI#!~05pXIlOUN+%d1G;7B=`-QSlu|hPOoDQe_d;v+aJj2D{(8tHc4%g+` z2Ur}YsZmOv$002`EP{T>^2=;GRSTVRTp+ zGM{Sa)z8r^zt$uQi(X*p_Zsl)#Q7`vPQ_?NuB2fe3AcBwTo5#Bw{_s8P*>8(qaAr) zbdWs9%NS_Z=SDkk-8l_b?cPo!52%pT$Vr@~jXTAdApOaW6~NxAzY20onJYf(PsNv8 zC`?nAH;xG-h$dGFRpum@nL^T-nr7~1sg;IUHBRtU%Q%b22rngbk*TqJ>AKW;j+d>} zIuYe(btNWHI#B00H_kU^$Zgz=Gll0~js|S9n`^cacGTZc$~L;(YZjkxXc~AaC234P zyLfHcr@>}@Rj1m)hI72V{bI0$d}{w+i*T}OjOTpiFD-0F} zmI&l6MeHnGkP#sXZ-$+k7H$GklCIaM3Cxh$lTh{vto3c~ZN08$)wQ(fU7x_g+K&O6 z3PiE;Yy~SqbUiw*esClcisiRJh{N6QbqTco$zvj*T!W1KZ?H;lfV@5t7`>~{mQoCe z+Zk9)m7+`uSP~5NqYMh5f$)^w$Ym>Qm!9&ZErY&*@B~eL9_IZ!?>gTCF|{%4RdWB6 zDch5uqrX&sk4WwhNsdf_DDvoX*hi2y9l9HA6qs*q5Zq%@vxEv`J)=}b(M31rEO5!p z+`fO1K}WQ%xjyAyqhfdAzg&$q_W6{avd8{1-{i56hs(Iq^8P$5EbRLJLY;)~7;Xk~ zckQ`Eu0r}AuQ};T_vP^h?5yqmQ`G(0kutaJwel1K|QfT3AHR_^$L5wasXg9t-+l@(2UtEil91PAgJ)j)%gGGWNLA%zH|MU|5=HS`o zCV>8-@L>rWcmujlth2?6aA}fs(AlGh#K;q^^7a{s)?+PRBRf00lbBsI2=ljk{DI+@ zT37`;uZLml^M@Xf*@;1J6jx9{O@xX&{wDwuyv?kFT3Cp2dj5p-$zDCN?;O{PA~+i`D17JLNrSG~BkB9)0>%+cJ6JK_C~!?K2cn zy&1bNYHZASZ@SJd%Z+uxgVCiKXcCarG41F$dVm?i2zZ7Z?Yg%SNhDSLY>C=!8c#o=BG^j@7qg=8&ClD@$VhO*fN@HSRq-44%7{M=WPG4D#np zKO;O2{;urECY9|(B>D(}jO8eXxJmA+{*)X!IEzBK!sJ5qfpP_CDmSmjcVi>+pC-d4 zJyBsN@>lA#B9!ce-P$8AqZ14L;{IS-4=3$+ZfW9sPd?~`-#F=B>Ne+${&>|kR|bM| zV)rjA$-BTqvVv%2481Fi%sT4}-CK1Ue(;R`WX&7JN8dvaN8U(C_nDv#whm-BQw^1! zC@Xx`YQQhbD+10po-X2W%;2!NUay=%D zd?IDH(Bvjg>V9{*LD44{6=kW)&?A91a-}?16|aBs6?foSgYC-AH=CQlOSAwhby|P^ zYNK9^@!N+G(Ep8A-EBuTRkC6rR>DP8aD~Y*F&;45yUxsI_G|+f!+rFC(Ng2%k;lBI z?TA%>frP$JT1%#dH$$>xW3`(h>Vv+i{RQ3#AH-doPr$O)d;4@=3*N9@cuYA7qe+DD?m>~|OzFmc}h zpcqIbbXuU$dv|+1SikbxrWUB(^#UU8a^Fx>xKAwfuaoO*m%}FlZ};Zv-x99}!MKzm zLKc<~dk$huQOkaLLiY2#yvY7r?7q^3#NKbI1UWg?*ZIO!NYbi|DU;oJ_O%NbFQh?G zUhHqueB|LwjVyybhRihCj-u3To>2Q8mj%rru2B6 z82nL0h-A{m(wriCZv>H?z{h|Jfjq4+OUSt6%(1ny<8}=XJ{>B?RAv9u(a$?)zJ={L ziTu1_=WF5`*ArXF6eMr-g5}Tl7T%e{Cs}sw$n%yJx-0)5gJ; z!UR-sQw)<8P6E@};ZD}@$^b?u5~uSk&%NA+wyVra#%tOViV9~eF@D_FctU5pmGj1@vS+Z^=6 z?rVzfcdxX`7QA>@XFP~MPL&J~xgf1}KQ*r;0R_&kNp{=UHec^G`sF2}2@)vyKWMwg zJPdG8NYwX+IySnW7G8dSez~u`c$Gl<2F&^9g&xTRP_PE1G4*&h$LoXaW@D5th&q3e zt_573BpM(z6!$~W*Gu!R>~5<}Rvo!VJF52%0Lws8%joBi03fJ2YJm2F@8#XM$X2VB zjN^su@+vwWUnuqjsZsOEn2aB1>;v3neJB1r1O-n+@j&>j0@6QV@8(v&`SdYLP8p!a zTitqp-OBS7pFMUUU1%{ZXn^EFEoo2yb&f?i5C!Z@&)^ z7ci=s{{RAKdqCE+F;}&l_UM#pKeQu#76RdcaJ=tNNnlLAEVW!JbD9@l+*;Z1t~>GC z?TIRKdfa{4Fmk;+Fwkf2Olwob==;8F%8s7*&6Kh*2=_$(Xr_dH0xw)Na2Zid)5g?1 zamqHj86kL9Iec0ipHK)Pr3X>ID0quwM&l|eLAq3Hhj0%j;y-LLT=ZR`t`$fRC{-WxO zBeYyx)$2|Psqs|6sFT;(^J26`Q#{jZYFxQC^3Dl+DV=eNugynUI=2T&jPW;7D$b>l zuMHWlIKt+MN$xMYVHMUs3o9n`Iv6Lmv)N?JbUq{ZeAKBTP-CFqqz4FpzD;p2)(24C z%MPI%k#W@SjvyIP;-a!L+#aLg!nIso#{hQEHoy09QiugSd2%T@_Dv}~Hhmf`N7(Jx z!k(;^B!r^~O4A3cXfWJ5d7AMph>!~}R+l{;hthnXqu6xEkei3taq)J~Wl;4ngRnr+ zs-USutWji<;N#s!B>NbT{}0POxT4groGCU>o_##YeV` zXpiV8$AB_Lq@2r1Dj-QZAJ2sl_2@r;grA_lkjy~N3vVS^*}tR>h7X*G+q?DWS#T9G z9!|5WnYFp_;4tFAnzMOldFs)6?&0iE%XL>8{;WaifalK7@5yU!Y^*SUw%v|xpxoz2 z_{y=z(*GbnA;EY(U^fb?ctsyac8oOicq7m+sIEJL$YQTj7~|=)o07%cP0{oRRk?O# zw&{qU5NGcXI85y4OnY1z$ml<9Z4e+`q1e=#@4!-RHir#)k{OI}EP;3M5RFp(=<}Lp z8{;qLhtf$({1iswCd{evOpUc4Yq~0N`@e#b1}a*0$5$?&nyt>E1v{I}eHN9HmXBG@ zR;=$hYe)Gy)1cLRaCw5O+F%u-IW;+zl;Vch5*B>B^G!Xx;d{OzW@Tl?V7Sigf^Il1 z=4tq8u8*|+aDt4ALu?4+`0QaLU#S%dJqVoDAXH|?$ru;3k=Or`)r^)2v(~vQgNBQU z`aOeo>EdC3uE^tppKS5rQOu(Wz-GT(kf5iB)!THg67K6dlCJKqa-@kPW6IhcuQ$MGIxJn?%8^RC2}&J2z0PNqsT(JAWg1 z=tx3~ZSD29OyWWi^b$#9wdYH$I1C5jshD3n8~hs?+~IMaXsB^Y%F&T81PQX}n>999 zEJQ;j6oocIT<4RKotFoj!ye|c`csOoQJp!5k2~cD<}ShAzR2tFdxpaR2O@LyJ{_|G zuh^X$icdD=Jw|nUfS7`DPY{=A?bjwUX5Cir`S;P^o^dg_zP|e!D@gkwbTxp5wkgUd^|_GR17bc z?j?LqB&ZpAJB8qb82=o;gh-L%fS`bUPD3`OXm%L@QnqpyP})VdF}WJxo#P#xyJEqs zpbB1m_5@<_ctE_NL3i*7U|hg#vXkj%EM>SDP5&%dN*jOphaX#GwegU;wW`hImkf8J zDJ0;6@(T*s&Y8cL>62J8(9yB^*huur1nUPQ8 zU`u;yre>Q!wC@lWmYI90Y@ml>NW$_Mp9>;Hk33^r>gvgET>w!78T$7tinv{&2K}@2 zmj?1HNfK$%(H#=+KVewArq{cw+jw46@$hM6CBlv|?ebP=JWoY_QPg_2ydiStYk79B zciUnn(pgF6koexR)pNH(Sk-^Pc-(S*W0e4GwbePIPdNQ0vk(+vk1|ohB!Py1H#4z5`f3 zW3v*qa%9&^uGXD6T;(g!qr_LCOsN{kbfo(8I8kw9RV<=8CvlX#b~%%x=#>JE zn0@5}D#H*xyTa`JS!bQ$FUsRAnf#ebgQUJtd$oWHCi=MW33#pGHwh`&ABRu*tn8(! z=&@|*B{XO|&K8J~yc!wGY;0oSn&EzsyE)Y=DC`Ce)%J~kc$~zjKWqVDBfUeCG3Mbj z&bh^EXk8u)x5lR)RK^4+MgrOR~Y(K*5A3_7Ar~FY0Aftg65);nCwC0 z8JV8TkJDF8jX1ThzRvFvd~l~lgj_!-jAqa%Bk1;?pen(uh1*WimMR81d5%<)Wqu(pHL&baw?6E1n13aMA!;>JIl!rkhNoLDWzj%TAhdicAb;*vF0SOIGj5i7MmgctJfCi_?%p5i$ z$y6`)=-lq#1eL);zJQ0iP4$X}`}!IVU37a(4qpbQqg~KdlV2w)UGmuX(fgnZiAZ9i zCVli$p$H{)bz@@s;o)J_shmZt9$?5mioUi2>SQG{ju$SjN@Vn>JBtc2d zLJ7g=Uv-T4*aO%T#i!Oh)M@#=euP=h6Vz?$@t10Il)W40Hqjd{HuD6r)wPgzvAwN;%c8+qz4ATF zH&_Rj{%&s6F?hkRM@@kV1^eB|hBMdd7h?)?!+8MFlVkF|ojhk}A2pTB+QeaqtGC*# z;1Q#)*%g|lBY*AQuZ(~Qt2{f~n4B#xMjN;Hp8dqz8xlA)?5JWVYBWDY&7l$eHj=LZ z7u^tPBWk-x-nW@lw7Spm7GyWB*Z=}^c2}&pZ?7cIthUt)pOnQ{jzgC76$2OUn!#l% zqx&cVCRp(AP={ou?oUz$uzz$Bc*~K^oN5{ztq;B9pT>CURk`y1s}cB>z7$H7OG1#&F|RfC~U^Ui=^;N89Mt2&&niv&e2+mRf-f?clA; zea0@5PvvC^ppfVZ_A}IdU!i&%P;TCJ8wV1QGhTcTWXO5^fIMTnRkLo}7iOJ0(u)x;>$#VhcqmGB-asj}Ke6S6 ziUU~?85}Yg@xgZDfnp?=EHNAC4$!ImB#nNgd49FPeDfVB!gd%W4{O3hg1?$Tg@5`@ zuQBaiE_u%Txjjf0a~PH#8jdlsG@rzf4w|?q9FT@n83|&+`<%H?vyqm|rwh&(FBg=P z+$=qFR*^(0K(kK{-wO<{JA28(?*=y-82}^s?mvG>?GQRuXk~Qh&+i5>faSGclRil6 zHTbLmG`FcWm;|tATY8>0PpN)0Wrbn)N@Djqa>NrdZokYT6-EkO8&>5yrtov2n;`yw{B~`pp`Z;J+nSr|)7^>)eB%QLuhZ&Xl4mM^av%oASA(?Wc z%*9%yuseuzCQciTcjsN7-bPh=ZQL)YKv%yq*boz$9f0JIX<@`n!y*0ARFD3K#&$pC^*xluR%&&PI15kbMaw5=2UaWUBRv(1e( zq!59uHw2j;8Fe7APo!E-^!WGv=Y((PB(Yo9ozqxgUWuY_TS-k>{M??NkDo}>Emckv zkKMMiqv(Wsm7jN{{j_!_Sa*}nY6G{_5S>`N>0ABwpd-;tCLt@Xea^h%{3s= zTTrJ$hesbEa5WP8`QnKbjJ|by13`x6x|d3h>k-U2?CjPy!D>f|@8T({fM#Y{{mIe` zYWJkG*U}B5jAuGZsHgCdv#UB~FnPa`b4v{~3k#3J?h*nXzuanky`|8EYYbdhO6Vx3 z^DzpEIay53_BNl+1k+BSY65(g0FZFGZI{$G;7NrA2Tpu?fn@ed={$ESgse#(9fLq9 zR&!70byb<9XC@w|(2A=cP=nN_#->R#4L$2EHjTh7Wl7rtb#n2S1jJ?jO$_Mo|0tlM z-7fWwK))zP-y1LMr) zj66tOsBX7BH%Hi+Htv_xu^Q2&>`dp8vXq-j? z$=YN0b}H;+U=Kv0*jpzi_7w_7cGaAhYb107EVs`K8uwV9Lw}3{87&s8Fg*iRhS^N3 zYv|{h2}?+;85rda&vRhsAw99vjQ5cEQi=ZA72^)h?^UB zRK%`+5fh9bpF{6pp_qYsTt6X?=Lug{;HoxGdByxAGqL`Ox(f*UG&#aSS}A`{UGGO? z__L?c&?Z0Zq21{m9Et|A(>#Ls{-Fgq=ubq*C~<#8&y^U@w0{RIE*-Y8DCxYcnj5_zg_!zi_+QpmF0eF%5T5k|j? z8Y#mBt!>m8X%g8L!O-pxSV6iTl=zvl*5oA~%* zON#Hh8J2FnZ({c_X03d}f+{MELi(7Y?QEne!Y{Aad0Gu?Xl8gkO7rRBob-q!A;DJx z{D+K>pGb}dn`+Vqv!C4@4?p8}^js`A^p`3LnH0M47h=^XBYM+x)Wzbh1V}rlpO~qN z8hflpOI%g4xRC#$HUoU+tBL+u5r-kh-+~Yka@(kf)kl8Zm>0nQ>6Nv)K4T-T33FOJ z!4MS#$9rF92n?d)AsC?`f~EqB3^ldc#GyaY>WAM3c{um~`70vS>; zWYb{cZGBR__r8KFdBJWcSUv;&ty(_mDS&jgt*1?un73L^PZ5SC1hzY}hBCy7IWSf_bl2jba8~M#yL{)VMuKV1OLUTB+mg23Ynf*nCQK*)z={8& zkL8RTC}+01LYnu^b_?diUDwl&spL}XcQuzztKO))OCk=c3v^GpaN-9>p`=1 z&hy}$0`S|x_0_HmH@>9|&TuRc5c|QGkm6X5z*g0RpduF1lq$y^S(;=Pw=CRAr)&K< z7Dm0F46dOLxNB9p!*jX~la>lqHqLjz5u%!zlq$X?eUh9)Tv=DA3DDb&l^Ka&IZB@G zAV?lb)?cir*!0EGsqAeboJi0Dl%;DPFwmEwn@aMG=5G#88vG|gFD+`hWUR_!d;y}! z*%gIH=c`dpMpq|_K9qMH1pFMp$-2~YYlh^0RoU3y0HJ1zG z9W0h$i2IRyd9Hu&3&kB%56LaqDQIe3!&P>!l#L2K>Z)crnV27NyInsOI1>p1P@3Ma zU|&-s2?e6~v5v-uouIgc;81RUA6HufZ>S~xE$c_?aqD+sfmJCEYOMVSu-VTVnUWte zGi#&r$z(mdrd+Ti5=@>G-;?FZBd{5}o1^ec4=l7&B^YchisK>%Hn@$YGSx+JQbsm9xQ}h89j|6Y@x)Qa^ z_YfhdWngAJyvKMWXDBprT%4SJ>ygrisYVuT0x!4} z$RO!-Ilu4iB0RRHc)m1p-x@P z>+aJnbamMOe2HOdX&Dz9irC(an9zbAJS89|ip5AzA5&GuiTRG6ii(QXpBnrn%O($N zyg4vVaXqF{%}#q^`h_F{ulLDIw>OiR#tv1W2T{TsgL%}!7Khxy=w~>L4yc&Um%FG_ zGMBsdcxgPcX+Hd%gDQ1IrSo;v`<7ljbp^JrhPKc~R;OjGBmpbu^7@(<_2=pQi~z7V zDJkx&8s@xG;db6Xhv%?cSYWeIPxefKFh*vIIjHLG+UB$L7`A}74OQp4hQJ9yp$}_b zAPWnSnffWMiaNZHRa`8qTI>f72{m4*0IP5$n1zZ5gVT3{xz~4BbQisg#hA@siAk!Z zyX7B;cNEPg%NC0AEGNvL8^?=d&5{HMPo^r?n3^XKd_tqo)NR>JjEsOgdnl}61`GiH zEMNOfQ^PU6gyItl%kkB@p@vWZVmb`mYE+rFxBDjdiJQy0SvLdY zEK=&m>b6rr9rhC1c>nhMylRq-=Rx3HVH49^r0%|0RA)I4m`z45HM!3b6&nY{ypCJt z;Oof^A>5efnBfa|)>nONNQoAm%XL+8A@k;Mo+B4h?>Z6l#q4Ht@@tb)~&Ki%A83JynRV<5DS=~r#8=jQGvtZZHAyEi+%f7-UX4v9W;9B~%5J4SA@YIT3^IoGd>O^H z-Ctk8?^j>i3O99_zYIay{X&b|H*3^=_^t<=IaI5v+g413r(|jb(N=&I17zTU zwEnLCvu1P|%iN)R^-9=)40rS@+9k4|Ug$baDi@m==_B>;u%gXoZ&j!$PI7kTWq>%P z#%n__+**}PP``oytf%m^0I^Jl!_S z;)OZPjk^dgstqmANh3?26W$W-`?PsNyj4^is6%4?rv4be40plRbJX6&$|B=J15p?P8Uj(C!)s#@Reae`@Fr2KV9_}8P;Pe z8~PWMpBd4|3WDM@DC5ZNvB@^@oHUlwJmTahyL@5#g# zh^4ORHt!}f(d%W@p|@G9)%L^5VdG5~d1N48$N$RoeA)ULZUAnJK@)GRGQ``i&D}5T zq;Fl=eQ>lR1`;j}p(A3MeG8x;OZ7v+Zh%K?7QnfJ23uj;*bwjB$o54vqMIDk<0A#e zv)`0`66-tE+P>CLS&dksz>9%Q?WW!iv;(erDZZa>WkWoXZjaWIm;RSIe;1lSbi@ zol(#}0OCS;_($qTFj>xkV% z6nvjO{X z|i6C$^bIp@@AgyiYHiC67<23f+THj znSzhbe8oK~C!!KCGBusLFTv>j#ND}7ov&)hOf%qEkoO|b028-*hnGR8u&9APn0p{Q zN_4D7Y%wu~WW=rgoq1F-w?1_PiwUSlBp7|Jc@sCgWp!Li^3qY3S(llkG5aTKM|l%J zMXZ|I6nq*yxWcp)D;#gqBm;S6hj#M*sjBiU5N4-pePz_9&QCgG2gXIvfc{8r0*INc z@8LBI82K1vD%7}-%%nRhoA-y=>kyXiv{hn_1&WF|^y7NpI0(?iiLJX1BSPwun!WCw zc}t=j$>R$!b9ZO?DbayHUGQ-Grsm8>B1NACR&DY8ysO>vM!s?@d zQ$idgBZ9SG-P!jbo0Knp;X_~Z{gjSYw`d&o9@oPh&(zp>C~`YAz~OGlr!(}gFWP*+ zP2O{u4|Gyi(#4zG)7516_`>+Hx{Y({`(&TO;%!o`OwUy;KAzQ1{ueR;E5wrq;e8aQ zpS8~hqc!(4MU|l4_J$inYAJS?h~;=gdQ=wJGg!e1vNEx&Eoa>bVm_+Bs+uVfzhn?NQsH0kdtTa%;QJLd;Z3D6I5RkdT9hA*S&;FL=hhskPgmoQ zirSq;CA42!WDI<=*H2uTYjRMDNE4(^EYp9E-fv4NN-ESl>^xN*A~tKiHU(Qz&L?+*4zM)gG3F2Q_0mLvjisu=ZE-;6mo z+-8A^%|S_!tb2`=iC9UKmy9&MqB9k<4MphHMOs+_)m1egSeT^jxsns)99~$b!-cD= z6}?f3y%OHsVj+`GNKs~+6Rbh$E-XHlF}IIFp1O2{P|vdHpgexAG>GUZq~VA(Rjah2 zp3^;gzrh)!Zr^!~Q0v3ljaYj>Z*4KYty6-&xOJjsRCE#qZ%Vfn7{E}c=d?9iy>%+5 zu8+!|rqPL1OA_(;dHA63?GdM6YMI4BeU$EXzkpFWt$LtVpUj7EEqFF#B;OjY+$jGX*D1ur{M+8y zW`S2eB1FLMhciYUE&{Ss#(bKxW-xxP{gYF-~!lXzHGeGv$=Cm!}Ivt$l&)M zP(JbfMGwXCFVf#E3vZ_-Bu9t;^|&_PsJ6C;$1Lsi86-5tcb8iyQcUi>!0Kb{O51jA zzT9>*-g*6ZuHpCVMZv=?J1Iniz{sWE)vfY3W5oW^FGcwgC}m<+sZ6>Z5TDxbe8Xu` ze+I*e8guZlrp(a{{?HG`Yi$27z1y};l)Bl6c1c3=G#F(nG5Z*f72bfyyxai~Q?~q( zpF9B=?zp}2?K_alqZi+I?Xzcy{6R@M(+)%c)*vt(Ch(^^&A*j!Fnopo;-*d7nA;@4 z-M+z_gJF*y>&nwVch69CF4H$m)3-rTq(zFdhp_e!^}W@s?T(>)YyYBeq6cF zHf?`QsYTiTF|*?~ZS~h8>kI=k->O&F)*LBbcKp{YfYZ6?f4aedNcQgw(~+oMR3(+# z>MIRKPFmQ__#6eHF_A9Jp4%0%^91l`k5BKgORpS~N5jxd%W4N3BkJ8|%ZW<|(}L!4 zelJ7ssm|2Igrv2#^)M0C|L@%CUrtpL;{k(0gs^$nzosub=V4a{ht)5H|3FJ(WZ?B@ zht9w2XOl93$9Px=d4XiMucxOqZc}VLHU!^0s-ef_v*f?;aDCeL)s=hiz(8C!rFhRy z63#!f7;Piu@2<;%ls6_mo@p`I z0K|h7`>8H%915o$apbT4T}q=g2q?Pyq{I34{q{Bxn7_4N;4TibsN68D6)T))qm2XG zHy3ShZOP8`yEY;0%p*fT{KJFs8DXh~VNBQ9#02#uWAKnRt_zkARr;5Gwk`X0EruBW za2ysUftjOz6N!iJ6w{613fzC@=G%lZgbfh08)W6=hQogZ+^Ha(N>z4;z0C zuL~)Yd>g=*_HYRffA4*y)&G9ovO}ZSTFKN#5#uTC3&U{&QvYU7|6q0ZxF5shn3|cf zB}MF=eQ6*_2Y~^GnEYQ3NeKQ)!b_v7Z-Gvw1BR*!(Io$wivlKe|HYqMd z4#zcy0y-%IsEtg@yAiK524X(61gZv}B;w)W$@<_42=4ter|qBcXiNtX7*FO*JWS|) zWH$m2@wxQ5Ydw4IG0AK9YpHxa(tg>vOr`J)@{hHy=jWwat+gl}`N}x#akJgb9u`DB zJO0ks0dPKziHUv}Z8Po@DnI)fQ7m-@@?h(y7;jctX*R**Q3po^b4G zyvO`U4$!wz;?X%Qy5kdY7@8}2ni=zaB_SsWI@Nl2I?_152ewW6>*_4awuMUHCW-%t zOZNg;qiy|g8noo(oc3XCTt!&D%EBW|EJR z0T0+S%Fu6uhr!5idrbO=3phQo6asI~ot)hNid$ld_8*3gPhUSedeIU!hM{j}W`>aR zB^U%1ZX$O@{_p$qFZ1?)`k*g=?g{wc8{yk7^$rAGYO?{V;_sgi{{=wH0{<(wh z-|BELJ^?`=%Fy;VaO|%vg7U?~sW#F2|9?1Cx5@fnJ}qSNUoH&@ohi3ltFp1AVpXN& zr6VR%XCI6usX1VCL)|4zg8pTJGJc!fJjlOHZt*`dI>7&CV2klt(HXZiWg$87+nF)o z;_Lt2#Vc@CW@{pMvpqcc^M|-eTuSwst-+Sxq1TJ4*}zjMjJMnOdZ>C0U;snP((5itTM`R^Fr_5>I2z$qX5=&ED+Tx(>Kn3<`qOhe*ECjh6}_w#OqoXxc6VJ zuUmDv*KvCg|37}m25U(N@JgA9QN@cx!X87k0!AL*51TB{FkVH90h1wq+4hM)*!H)7 z?mPUi>=Ljh{;@zX{zA7l@Jt-o$M8{8LgUW}7ct6I9CU9#qY@;paMiF9S?IJKe@2iX zr9fdre5+AxJDHB2^`9Nf|McNHJU)&3g|pjlSuuiwX?&Zq86QsW1{U1vG*%xR3CGRVEJpdul}fKeHrVOATDrJTm^?Uy!YzBuQ;9kC%BgE7CSjm9Y+&)js5x zlJ!7Ws>#2nm(hQ23i&s116dx(7T?&;o8O|0(Oa>-2odRw6)wz{l(#qL&toO+&Q;_l z{pc3;TKF9ZyhIK>sM%7TxZ?pF_!aG9{bl3+K0|KkLdxaxt;7&z47& zNc=(IiJpc%#m%zanVy8#QU;p#L!UpnaK-{C9tzW0bK*l{t21xw7Zcw-(!nh8E28aM z&&#c@Rs)Jw*S|kov*c`T&FK+vjixdU_;3l44@@`6cG>BYnZ? zOV&=#DXEsTrAUGp0IRo6uup-jl~d0}nl!*7zpcbc-?*E&+ntgl7Qj?{GbV!9J_#^- zvooM1I{Vg!qSm3Z_(aW*7pw;VZZ)oQ8uG=&<44IV}?b)$Mw|hBgTzX*@?znccKG4tLGoHmGpRnmNk|C}T|og8<${ z1QvEgNV_brr~+{5Pet>$&p04f71ydVwjD?e9G$rEMp%{(xXD%97PI(rs_a!8=0=4l zoZ-$n)?*a(bkL*YNPIQrN{jg^Oe7VfwNLQRcD$#oYnv(?>1bmw9j&?D)q4jxfmU3i zGN8FBp@aS%+ztM$k@Z% zEXAoBQ$F)@|D*7flJ+soetuC=Y;kdMoeTspUI*lxWs1(#O|sm3bNQpmuW|_YtE6$_ z@-?~pX9rGSU;?lo(TI5X(CO8gJTJ1xIc1zKtxYUZ_tUad1^y+ZKCJ}`68vgw(@10ycu&s6mq%YC^mP6*JNepLMYU(+!03NhS8EEVw zBku*6U9W9%koHno#InAHE-q3}S?eWYZpg!kRc?@Y$OmqqqMogRc7QwmA!2h~WKcO* zsex1;N~JZIHWb- z@i>``c(Tnq`4x%5pQBp>U8JovxN>O76EpZ*Uh0k;Ia_mtKRqas&S^X)QtB|4my8xu z;mfKclcUMz&DT4o&g;{;-ICjsr|oEAqW_-5Tk(;|<{pZ}YX{~r2Mh4%ev8~OGAyG| z(bLib^7J7l-NYu07w)bAc2-DxW(@{FUFSKW$3I%#HUY^;n=E#%IGZdhY5-*M&eP|8 z1yGUc@t9rUsnq;kbw+2;;z%6oOPJS|on3~0fcz+iHHHFsr?U^~kwV>E0otTuOiU~# zZ-L4ZYdFy&>7$A7lhT31c{~-3UySF79P3M4hTc#1A3$7d-s@XU=39iz$pL%}t4{xe z=2zN>B1((-bgflDN8<~=oXR4wxw$pFkZ3c}Vx>poWCnHIeBoJte2RC0^?{7Pcxm5& zWQ^Y@Bf2qKm4%b{dYbCVAoFg3+6z$#(!rLeZ7lB2FGLdUYjDhxBjqD|GdXaWO0H7T zqHdzFbD6x|ukKxwGn$?BNh^-Le{UNrr^%ryw?eyK{AXeQDZvjqjbT*alyKE!%%mne z(=`%ZB)wi_sD0uPV7Z1vI4pilFEC2JQCch2;Ou6nHkBTvQOhNzBGZT!DIXmJ&TuH6 z4AwL7qqT5Kj=LjR)p1C~mRN&qaNxEx%$TA38kYvGhpk@2 zQRk`oc)Hi<7ZsyqaWy2Gg8Gfo^fhET6a0R^#e!R=er(?f^snjeZErr|sXE!N|S;ejg0K|MGn^^^H(L0WJd!TkDfe1g(g4c2k)_6 zE(C(!WnDw=L!KJr4D%m_g%Jvp8;RHb@LFF6H-q;R=XP|$_0)}O_J4Kh0rV-K-LB!F zou|;9`{paJ@xWjcnRR9asQ2Xz#5W%b@iG)9v@bASKnjTyRVqrHZ31^+21$i#Kgv9T2>+f^yKJ{yyr4vPB~UFem76t z4DKqHcxO?9y({;B3i5hf>evcSb9KR=pxxDNKu`@hw~u2VSfidx4wK1e{p zU)#wQ=j-p6j)`R2p3}=6?dNt8WjWsSKLtjIfVpV?7j17HRps8U3oFtf9RkuwcS<*c zq@)Ol#H70$X^@t3(jX}%lWqYeCMDh7-Cf@^b*;Vk`<`#Fwa(e+jNu;+bPOM#=NI>V z-B$?H@EF(n2v<$Y+HDXt6rM6gVw!J>-c9Lb z73Wct(jvN>`PB1^3TEO%DXLndgWj(bR8qI;RKEyzygo5?|1XL=t4#|@i~(|5Yhvt* zY{<(ur%0aN*>$SiChCM)y*rs~w+5H)v=ZPjiM%qb6FqiY%}|xI|CHP94y@K$Uef}G z9(?|8koSeG`%}->zx^WshJ6EhA)T3%^am3=0_<)u2JVKmW;>bcy~i_7Yyjb|D5+Fk zoQXU6RV~Ar27SY87derMLvi4EPWfvIn)k>wPwibB?m*Ai8H3i-CdN+EotR}9;lZef zJR!`8E}0FoL2lL0w~OP!A5~01>0!j>Xt8+?TyY9`Wsc)- z0MQ+mhuCLd#lZsIIf%|`IZy`~{wS`fe<}wU1&mHSjR|6nuOVXS&L7MoP}+=mfS6?t zk@>b&*qBc>ZjE0@K_vV+wx%sXf5-fzV3pr8CGq4%Eb%5IPq!4KpnaVNszY2Pvgvur z&t`mL7W7O$>7jP?CQdx5C3&*sCeCUvEQ14kN9Jg2ZbtnT%9ta-7!8nJt=fV-_V zHFL+&**AXMbqd~``xR~E%UJvh$!Kd{sl&(Sw3N^+w;+y6Qh|BK7i?d!3sxsriCYzh z6SKKDS<7tDq$nlv>JZe1X!IPz(`$z;`a7M5Y+*O7pSxn&wVxx4YX0DUTP|HynYiE@ zAitp%Afu|8taGN@ne@I=!{o`@Iom~@Y<4WeMhK^iOyaLFg;&$0dO2RmwVrFYf=u*^ zy*OMYF*%v4NOPuaNi8^R!}(-_q0Z|xxwYl297)kyx@6dNVEak?>h%42#0!blCbzI(Ain8y9empblZvU8+#8%^6N>Xjht$W1H zIUR1P&wUFplAqbypr*Eupr|Q)uQ=h(kdbVlNpZ2}UG7cgRMTXBV*}%%0!BkpQT`(c zi`_a_Cna!^%~fN#-rFwD4UIKra+&1fQWB_L>4BWCH;+tpR%>7C&4#_U(ckC%p2O#Y zI7G*qoJ`9_=Y;=rMm;*bYQQ^P1S`$PES{yyZV{Er&RPg7^!*vkU89g=_D(a{pZ>Ia zGz+_V1UvSAa3schtI}`SG_}jJZ_ADnhY()G*8cMcX@P3j7Q1at-S+A#c`wGnAhP{O z!Y}L&Y+OHA)fIA%HpSMs@}RusqX+neZoBH^*9hz8JzsZd7{FkgA&)8EkwnTwQ$^W? zAT>e#2rL3h)@+KGMDUHTlYlzHyV59HeWoxTJ)w=v+brlZ4K)v|sMr`pWHbv1o6_UId7m6L3Pq{ zzsW{3Upbv%=4|S^jELiLY)|^@+gchr=a&?+=5^NxWEBGy$#af@dFS8jsNsmA+1la8 zgF8VcMDt%u09xws`>6QhUglw+onHG%cXkR6p{lFy7^hE38gJwkm3Wr(@}2Xz&@m>$ zg8=HObesK+YDyS$5VA&wZUXksm%AYB`mK~HfC-x13&z8$M|??N0YpikEJ4xbFqhwn zAqLgR49@_qGe*M?t@y8QBFb-qB&)7q0_=n2(NVF-mVeJ3UUMo0*pW9M^A?%VYVjMlxr zAzH`A@?;Ok$h-n4z-Em&Y|mKiXiJG+r>046OE7zI(mP2(IgV(#9_m}Fa(43pz1t20 ziD5rqhggxjWD-}6NA(SzzMzfoHn3uvj^{##zPMZbp|isLZ*^8O6HtsO?9lpA3YdAWB}i}F^E%&PO`oyCr4$GYw(l3$UnN$#NZwQdKd24Gn`jY#Co7cdU$w;3i<^1 zud=CLXvzl&o5W-a$^6F@d_GXGtQ4|meGdo4LmKM+cQ5SKUis|R4FKb+0<5Lo`Jo|Z zQLp6}Ci^gp0`$nrwZyG@3wPgX#Nm?z2#;zpb_W*1?hD(y8guz!^^6LhA??EpLwv&E zpe{3)r?N&a=u)hb@Z3_4J)^6yBYiMxzfjt@=A@q)mCHaf0pSk$U9**`n#jQb3;Zhwkop{!uOTUcU3>yq7&EuC2 z8~Pgg45qF~fi-}dTZ&y0zFKBi^X(+K1J{a7e+Mlr6wvhAzEKgZaXopR;GvKM(&MMQ zvNZ49EF}Yoi9Y%*xBW5!hTTiG1bU@!#d&%J z3g>|sE2+>6dhTVXj-7xTGtdMv*oj;qu9J@GOo&NPc<3)j1RRNzPT>D|F(l> zUL-ZFex^P*dxXZ&piL_)eYrG6=&@z9*64G5MRuLl4m5Pei^XExkk9#1FEH_c0mPW9={d#ztI<^LzJT zQh01nOh1r+aE6`xHmJgxbdwf)*P;?CSB)O1vX*D(r9y4toCG=QBuC74a8AM*?yqYQ zAkHclr>?T*AEI)#kU(HTRvqrlCDtAwS>4sF(&sgMBdf(9P@7a-JZQ2C$=^ShYtH+? zT=o+$6-n@Om@}X4w1^|%b(l;LQ--x_S@senOTNj{qh38d8ml@K8GQBu6+|zu@iJvE zjBzZ7ElH~eq1u%$-aXn;UM;7&46uyf8?Iy7`$RHUq7rDwo|XRHco&!tV2 zQ%J2!OUe@3=`ef1@}?REC<&>FeGU3+{hvs7!h=#=ZMs@rf5IpMPQfUr;M0C-l1Jp;%?W{k0ds^{NZT{h3jg@ovjW^xXjJ%AUPog0PV-9#=`B7{XNN3$P^(UsFt%82 z$$DNAB_LW?@-wZ7-}paRBghfZtD$xX5ol$}qxlvXow z%RV2XVHn{IqZUszUEi6Qo7==*bFZ$R6vhQSH)4n<74p(8)oY=2CwATrIG@KLqmCsg z#tIRb!-jtO@`XlJR5bR$j`bg&-6&Ep8X?RNRQf02Uv{#<`gmoDkAWn%nM1d{kP$m* z+gJ3uMS+lybk#K7M-IX%c$_;nGTJpTcS}AzpqOykfn?iN0-ffpwksC8T$o}F@pnp0 zo?=+o;ct9_ktSeub-Q((fhHFcCS&W%=e>Fq)Wc&q(aYMoQTUMWqR6s{8*WmXKgXYF zg){ZM%xfvB;VHRM)#dbx(kdxG;LMTw+_w!~A8_U%V|DLhesK-6%UCb1lH2TlUkgYc zV=8AL@-=CrB0zm*GryPE5dLlyutbgh_I4i&JQE+a7-c=I5=9PXE?SpiqXe|!XnfHC z1_B<5wyBrV>U#6})fnIb8Q;y`0U$P$9^6=w%!v}(wU^eS0Muby3C4?OllZw!?=okT z!`1Lu^$87I{9b9)v;|P#_*g041j;!;@JH&ZloAo z6Ku-ES&&oad%Mm1Ky@YEeqxF|%c}kCaVxzJoA9Z5cRv(^Kj13Z!yR<1tT3HZj=MUz z8lAk#m5l1xlU`yn$z)3^>M7y!bi$w-5YqNP5?q~_T+X|l1Wbpp+h2SFd0Cp7#gLcz zN~0d3887x;a=Lq!>()Am^CsGGdM8?bP{@g|MBrN)FU%bKIYGfitDFQl4}AhsMSc{i z=1RPdl|j~N@F>YU+{QE6+S-OcW_`->QU)K7*uCUS-b<)WyqvT~?u@8ySWd1ps+bL4 zwW(7xwR5v9f`??wo9Q>8!_*%x0(sR-q?#i4ZDoj{uoxxbVwiM(WRy3~2J3mF1ksS* z-Oc9?RtC0dZf`>)#|!dyv{fH|ClQK*61^qd3=`eneq?fqVxZ!PMJsW5-J&8=ie2i! zGo;}PT0bP<*C&%byhxR4f{tux4aUDl1|`{CfTqjeyPbYF%!ueh>&<#Y9n{psgtfSx zb-Ag!B1PB!)EbYMd0sKA7k_%q60e4zxUY|Z1fQ8n?{jboawM?{0{h8siOSfQq@`2R zNZN#azBf8Khg;k~wn#`BG?UD(6hi~6hz)inI;*7do=dq3dX(c&;y;si4DL_4wA#NfF06ew0st-exzdL5m5Xi zMYni#Gs4VBupgh1PP65+rgGlS#>WF9)p&qi4?Z);&4r5@4nu!tB0a1uZa(_U9d47B)aBX~W%_ z_L^IqOy}^EcGTIl3e0B>ivNM)-f9?G8{`Tnq2wC-xlwl@v8uaN?P1nVPy&$*{qb4( zwl_AEqL!yfc`St+T*@4T7cQF+8vmi1LSQ=bWlMao>Nbml{bX$+MY~f1Q_T&n#!79J ztdY+T#KmqI-(ysDtc+R= z$dtvxO3HXa2_gD${ieO1DSp+MwmXiI+K}}L+U9fnH1-iX!R(DX z#|rEXs}D*i*S(TNCVLwpM_-Se)jlN4tWT@pLhZJYFi7L%lX}pD@E$8ZXJ#AdGS(R7 zfzGo1VM}$z9B+_HntSE@zInnm2{FeM$z6gOP?&}Ge3Sc|@yMTJ@e_Z5UCioZt+q0^fK_0;L4y2fM`RWC#Vnm6yb%4Z^sB#C*dWEO6MABH+^!qzDAEEW%EFZ-} zzp|}x*VIZ(CQ>I4sjdp>SfOfst$wCe_OPE}l0TIxK8xH9eLxQNEnQ__N-Cq{DO!*O zxE1Thwr7q6l z=tizi!+n|;TyN)=r7+s12UrTBzkM?Okgru9bMIp~V*=#|Z5NYjyyy((W0T&vag8B~ z=|2zx7ecj+2Cx2{^hQ-59>5vX49%1!9;~*v?T`x8kv;55tWmbx?Y9z9F4_i9~dSx#orDk zN4%?KakXmL!5_Es1e~sm)8km1+53rUn50f1>&E9qS%#C3&U3px66#b8DdQ3CWibsZ87diRx;^#L!?)gOhmSkLGtpeemv^X8mdA&WmSaK4dlEafK5X>&5E6Fq? z7Hl?X0pr4{e!}nDagp_X7wX8?tIXNsLb4U4Hl#o-KdqS;+X;a6%Ey2ftV~18#wZX{ z>QD6gP1cvfLT%u#k9-Yy$1;fMn|@H6?%cJVZW}eL*bQ|GzRzgB8ZDcWPg1G+EvIod ze@^tzZl{Ns0J)2dB7M~JEq%p$ILYB?)oAT9n|s|QabG9Ox{+W|VQl(P(%v>zK+q6AAP;FCb)(;%udIYujL5jii~Z@SIN+}-%#O4MB^o4^*U5I1`JNr zPa_pmBxS9vtyloe-tsLQp-RkWS-lSLUk7ifg-Y=#e3pC`BWsaAQAQj)#<+b$yF^Tt zu)3L~*ga9^C+IRpPEEZkuG^#es;CB?glFSTLO)s~n)<>qRI~h33X!Cw^5PZMElm`5 zUpaHb$O;WEe=(g+=ugf3GP< zcSq;o{<#MbrU31of1y!%mM)*J zm92xzk^T`0B1Vh~>yM_l_GP+IS2>B!#T!zrpF*;2 zFbYJiR4KX%Tk`+0^vN~p!5)K#rshjP3;;N&Bkaq~&VF_{X^^8}V36|?$pCcNT|ihb zAbhyAA+za1|O!jy?>e_0gF2nM|=qEAiwYx zK>h{Mc1PcK;s4F_{+*-n4^?M&=g4AIOl)j0GRZLl3JNDq&jRgx&)}Fx+glc=MQ>%I z$*fl(t=^i48Q#AnP1pC6f|wW2qdD(I8@BWyEH$;~bGXy?sdFfsl;22Mo_5+-1_TH$ z+`-pUXX#?VQJy&Xp{1R?OZu+JebzVCW5#Dm8*Z((HgIaR>ecGUs8IAD8}9XUH`4n5M0qE+g|hI}e7@ORfXI;=;+?m9M>_oHh;DtUgGtE!UtTrg@!51yVLZ7&~qBZ5}njp$Rh zW$Ua1{5d5#Jkm?~dREn;PD?PZJ4EvJLG zu)(X}Pb6Iy^&UQFY@Wg=f$NkR?(Ly$SdGf9iNacXm7^{TiZQ`M>e`nk8ix~h+uOyC ziRHYeq);2@H4z1!oSABIaWEyJu=03H%Ldaw(R|4Y`vLaeFQuh}R@ToSo3a(sO&l}x z^6l>ciFrq|%o-4#pyV>zoLX=J!hA^iCZA-je55#*vGT!a&kG!uS5*3HuU zg5uVBDP<7D+7$lU0=W2RiZX|`*0)?moQ(kZhy_NlPIPCmj=pJHMMM?4?-iSX;XhLS92%mktE-cnT=?txr(Z7>u)R%o zU$~Y8`$JY_^S2d%{kC$(vSjA_jZMTHUq4gPow?oDFZ6bD!Wn)cvbX52=g?R#>PT+w z)UCmW^9`V4*ogb}R#X1|t!jG$&z#7nPxghqjf0w1lE@#jrv%t!K!D(MzZ&jZ+4Sf3 z*?_pm>74VqJ|Wh(7Rvhr%zFy?MCMpFpTqDYAmRanX0mB^1Oya%%mEpzKniO!wOYQ? z?nD^RU)RlYu;YWlOW1`S0n}}+_xs4PnScH#HJ%%`jSyc5@y=WcY^Q>X`?NzKI%##7 z!vwz%ye{cK4m^Kh3gYYIjQe6VLVd4tipE^Mb-cK&KIDGGH0n8@BCWDSATUCi*JftI zU<2lu4RBsg8M^!X_kMPKNSAyE*v>oLo{n!L5aQ!^1AdMTphM5|y#%JRM%95+KtZDc zBw&rR96R9r)oR<22hJdQs~SrSi=3=1acED^Gf`1}l2-6ccN`rMQ`j1S%k{LqpuS$9 zRoTEG3$S47V9z#6QoxA^SCppX`O)c_e!z=&_F}0B{!^&~TXy!Cm5ASs+DpnR`n5aM z`vu}>w|XJ}%Q=E}_Wl#P?H42LI3i_}`>V2h;-bsi>sGZXAXM`1tNrcuc@mKP9=K)F zpUMy|a#eswNEou^(S{%rs+hv7$aV_+wdNU@X=`DFNj*ph0Cm@$j_CklyDSa^!slI0 z=X2A~j-N-Ws;tqpasE|-k(2%a0Tjk@3xt`HUji$G>UqfJcRG=*$dU1Jw0`Lrm%SMV zZEfuZg1j_OVTHZ)o?=F%4x$FXz2DLjU{{(y^Ww``nD2`(MMb&#Ek1>uM57o8 zpjX)CkN)a4&?sioAw$VLHYfohQY@y|!Xr+*g>O0g{P>6wnds}}uu#@2IaDYPon?mg zDf}z#-%J07rz7@{9fL&${BN^9z_TuuFOH3CuQMCT{r>hxivNCx-~w3`eJN&MD&ik# zXdNxb15^*tsLJUwyi|2O0DH(4NDnJo#njjsEwcON$*}UaN3m~L45fiLXB}|NT)Bq$ zLr(S|YW){+#J_LapVufS@Zyuj+7(Wroj_)uCaqHHC+rjnaqX>|*3_U@$ng!+X~i>B zj$bSIS&i+}KXn+%uKXG z06I&tSPtqfZ6~pIf_8ukSAVByGb8oXW3!fGPMk1flZ zXj6EtKLT;!@<4Kr0+>^p6$q5yMXclUM8bF)1ItLA`r!nWo9Lth>IAI11x>}PtE)l5 zKrUi8oA0H(xsA;jKQXYk>5kV2#=UKtFV}KJicGqv_dd#)`yiOkyB2mv}Z zHltb3cGpFpC`oUSB1A}Y~IHL8XZe}T95&?$glQ@f<@UM!NNtarY_OS zghS(FC{+C~=)vl0YM-Ue*;;NOMd0(MeQ$^TrgIl8N%3!>8AU-PiTKkAqi8ufxiFil z7T*?KpUc&Ln=DSL7vpa|tqW4Seh@YvAh5*L1A&@=d*$X)k^U{C@!Dtd+XAoCBK=Vi za=M)|37-vm)ifcd(82o|_w_WF1cE+F8pa{uQSPB3qBjQK*7p)-L$EOPqr+%l-SEC0 zUR`~U&D=M-2(Oamv~=x*2+|CClUi;-YAlZH{i6~U172%faM8A~aY|!QRT)bwljxWJ1quALM0wJx!O8a|`f*Kjzkn%`rQ;aj@NK%+ zUMUT=Cc%p*au_^zYPt+>=5!S&>X@`Gc*Se`MX`zDGm8dF83S_G0p($o;2svP&#*bU zKfN2Kq`zH{qSeY%+(N66$?r(gUK4^qj85Ybl!@{c&UdAY2cV&=)9q6v&G^OH9c0(#E5Xv5UVFiRhkjTAMQtGa@imL2TgLG`0cs75g$*AN-7U{nxJTo6D zcy$%Pu=Xuv42VYaO$3USSFQoBtTSwlE{i^g1|*xXnCr93JhUys#;-);L|YWS{=zdP zLfwn)j5ueH$b%`*{0aF(8?e$xDwiJ>u(XBK(htjwvbnLfH@OLsv5d4MsnQ^KQY=7* z$D7Z1#B0@QydJ)9Vqjv5)6)0WaM_v2s$1X*Ucrn1(NOl)01c`l({{UzhI*3{HjZ)T z|Ca;vD;z&#co3G!;d*n?S>X^5*Waxe}_~O*{B9G;* zxOPs}%~?&$=zIyubl2fm{{DBVDD(Ntp5CdT6BBWwz3Eiv#d@axhIf2}@ugfp$f)TX z&MoJWX;71v{~$JJ7X4Zk$KOGcC^)Iz{BznezNVzT^m5vt7#gDMT-Km5WC0$>0jR#; z%2IqT0`@~Dd*l7R$*~3(I@gcQ7ZqGZiDF?r;~CWyiOI0doI(ocPO{yn!L`sDQc7k@ zu{I<^;Btuy4cj^8ECZ@cYYFcXckr*7TKiLBC$|1W$`m9IV?@# z(-AdQZEYe>Ol&YV|F68VUqP5vlo897=EF{xch~!bg6NJ|+`4SESFzB;-0KLOf%Nx{ELh{}+O)0b@syDw zC!n)xW=3}wqv4=A_$ehb_S#U+Yj{xq*E~f<#W%f(_D_a@IP|KokH?XZ%^RZ^3%R@w zf;ReSLN6mSF`cV3nFqa{QGXRLqz)s!-Kl@YqPU{+Q%8n=dpv)+^l*J^%i?zS1Xo!o z%YLcVOlTrJa6ek7#*M4M3Be~Co0n3Si`%%t9gNc`E zWZ-^3olgW5JO)l3w7Xd*?Ikt z#7nCg$cXdCtR;+7xF7J|bId}jv?t>L>`Z4t>-}kz<;0COvtGx2^<=3BdcyskSmFrt z(UM<-_s==#aQ3bNhuoUAAf!4D#J9a)qhMiElpJqu3FG~KWJ!j=Q?kcm{Xi-sS=m$q z8pRzBf6i42wwp@9S;3CWKeTS&sNMy>)YOWG&(B5_D^i-ynfyQmDHD_bRfk%#;pIO zHQmsbQhmDKxu`JF%%2na>>EjX_f8;Yd&d3Ey7MS-jNv<=L}4zA0yXl95LQyFb!{32 z^X)ezkxc9CIfD3S^<`(4U42H$!lGq=B?8PW!;%dG>feWJl;(Er{P=+?STT!P=F88} z(`)aJMeo&QBA#S@wg8&VK=x(bhnQ;A;t?ifS29GD9EVM}6$2tg2%v)S!~-mTDxU@M zE!Z_|fZ4cw?dYW+MNe>1`V?hea@lW9o~=o}Ba(!Wv&H>*O@#(eH-#(I4IUIKdo(1^ zwz=Pg-PBHFCozJZY9PjK(yw>GegTAD>-C{9zm%8n!y@Ixog0n0^3Uv@c`gNdek}o^ zuKIMg*FWr>Co8V)y3f5%{fiE^k?_024*wfez_}VCYslGXF5-3Zl}rF|dP^grBtpeX z0_R&e8GZ1jOnvtKHPE4sB_3vNCtH@r0xQpAaRP{`CO<+Cruz_cAl`FlSHNU+6Ew~( zB1G&*oPNO z1_Wi9=*li&y7~x~6)bGxq?zi1eW2tjJF{9p2{ehW8n-}$_7V^BrF&N)fJE;OqgOjf zY%BAZ|57-7A_rH96@R-#*zyyTcKX4g9yi&$j`nsLsUT!BELm?LsutP1%sTqA*HnZW zD2Gz-Z01FlWu;V1+U%DaHW}3NCWbf6_^}!&uReqwVUM-GVxON}e(3B-iyNKUTfF*% zP#@i$l1q(Qr&=N75yi}U#@z{>p7ryEhn_+dy9z*&RdaXORE7-QIfj&q+&e(_ihtu~ z0@K)Ie@L{%p29wB6eVeW$Ddty;P1|B>0i5U(VB!w+o-hk4hmYXJ@45Lf$N4-Icd`G zdTywQZO>}pTM2cQnaZl`-2udjvNciX-ptv}V;z>g++N>$%H0^=s&SGn9p2WMSrlL6wuh93P9T!$}BpzwFD4A?=G9q zo7jd_+o$GEOI^F$F-42FJ^VA#v*akbrLKmOnrZ9=_Y$jZ=S$lQv-^G>i+J&MuS`h) z`;uKupDqSfoJc}G3P^H7e+I9NB{J#HNsaD#)J+H48YwimeZ@}2E%oVQ5!3gH1>dsa zWA`v*(lrCqnT+x6$CFz-aO-Ftal}^(x}O1bLO{RHA(t#MG!y+eC6s8nGkTw0I6HB{ z&#Q8=!A|JFdT*TQY8d_Q?!2XJ0Kuaogflf{u@RmhrysHRGI}wlU+7l@pX)fz|J&(B ztc>y(=A!a9n9IOhZ6QO>>DdZv>bO#DMBH?UP5mZqYn*i9%Zv92`&vLEQ4)-cZP;cZ zp?(H5z+2`ACqn?k%W{*s(m1CoM@n z`(%f+gIFR-L2p|ggLPf3$Uj2i2Ta_)(2W7x9LSu# zlRsEw(bz=Ozwq~g7F5I}eL^CSqWE~dLMO@q3&S-^ug^z@Myv}6*&}FekVWUW3;SG8 z&yV>Fa8LL{q|OukAsC_Bf9Z`hl;#hC!3sw|B{9uo@xH6NNJ!yHt%xCay&!X!0{Trn z3toSK*A3<}RVok}LZI>k03?~aWzjoHk)zn?K<6KiwngtR2XY%5btw#Ws;$6Y8HGTQ=7g7N}LR}E}edu}o!j{)){{vpL z{(Z=MfBM(2@)|fzy87ujI9s_Uz0YUqS}uODagPwbbrXkWVjw;tzri{ha>Hmx%Kh-+ zg9^FVgMiSkzFg;mIA=dhb1a-i$*1WG#6oUgM~U&CiWdY0HrtgnIj$?!?bUBPk?LCb z9;eKYjGwX94g|m83he{1$<~;!g#b{4Rgb&5*RY?s@3k&?_BE%}2s<8jO?)&>cb6$c zD{bin>KoqmLbd#r``ea#IycU(+kM{_lSm9S*4|kxuQY=7(8E+(9p$;4}FlZ`MeX{rqyz#As+fds4EI7wg(E82-j_N zD;KeSZ(0h2lQ@FDa#Qjdd?zRnj|tkp#Wp$mg{)lm=#NqHrkYdS0;dk45LT4a(sz_u zBsgxGR8e#iUIl&Xs}5zKBi!3}f@Bykz5sH5V7+WvdL>y0nq!rD1MtJBKToQB?D*kQEqH4G1*d_ zA`&s{dw)BnR|Q~&%4n_2_~~!_zdnZJqq!j!YXk`;0i~XhbfC!VdstO-Yk=uJD`kY$ zu-H!i-Q?h_%VH4WR(=91Dk?k8B3?L9{f!>b5DdjoO0F>JUw*iIkdl#6mSG5|?=>Oq zPtjkyy~p4Wfi-OK#AoOz%xr1)3R7n#ISuzH?NnmQjQ>evkK0^ML# zeiWJKPPtOUyFJ*%!+`R+w@AhekTDN&+8k2qgw0{XXLj<_eiBj9h0gB;=7*LxoxN#b z+}j44clHl-5xuywX^KbR9$|K+xy}(;03VQ(dE$(lp#I@DoN_QYzuZcksGbH=h8M4G z=PvhGteJI`90`@PDa$fvtOu>bgl%$52DWbG6}VI%g>FxVul&qMB-x!ebCaM*q8H*| zKHKzJ4gCxuch!t%VopJU$b_K+I4qP_v$mr#9ePlY9$lvG@xJU9`6ld((ajr{RvJwNsP0={Oq zj&+zuL;wZro(SpL-t8oS`TAV+Xj}Z=0?<7G+h(8$rx}eswkH(nTG)Id^us8BwI`{k zQ{;?8FX(4T$1r#{zrqqZ{6IbGus_J>ljUMt&{aU;;Rq`?r$=KGp3= z+h+a{*mP>jn=fK_zD2Z`7PaMOmF+adh`mbmXPe+tk*|iLi+9sGK$43EZ^re^`iX}^ zQ@88>918&URk4cP-yCxS-QIA{@o16OHfD<(Y7dRjE&>O+L=-XMGV-|?Zb?L0Nm8mnB zmR`{7!fr$GrhhC|?({dyBB%^5w0r9|GX7(3jJZ}yo9L|c9NY(JJu&xgoESN*uIiq# z_`3~T$El;9*loz^Ig3nL=Y8$s*M*cmdZ3ObC71RLm;^$ElbpxKOUTHt7VJ^U*CGTh zYJ6Ns8{ZhDO_&Oa2hzD4%3YAOc`vqG0fU84NY$@9QbaD^zj^)I?1TO4g`NA&g?)El z->6sYc^-h5)QSo{4#xa>m?L%0lg>Yp^jFZC2a}%@z~^BaPPfws)6j3DG^ScA{3=uW z(`3I!uqRdBA$rit$LI@$w5n-`)x<2a4cDQhroekVgpI}5H2RI_-(PvhK9^mN&M6+rLPvBbnn*e2gRd*FCzP ztUzA#Z&a9Q1*({cOsn0gDh^MXo*d7$H-FWizK8b!eDR&-F^OsLRo><=@)TSlsV zd4&?-uD5`D%yA0J?|r7S#ei2v=TW@U+Y~UNDI>0Jl{!&$jtgThb;6iS_1KaBf6OIg zoQI9twl+3OW^!?K%1Y0BO(k$0u>RX-Z@0jmzUnu9{+>JE$SI!-;v1{GS5z(u$D5XP^v`3Uf< zrcM6G{3?RY`4->*;8#@w{3^MB<5wY+MG^ZpT|A3{ljn|Ag7K>gYv|MDHxla@=(h=a zQ*)58V4qJ8_ z3(uU=Pl99OjzxioHvIs9jbPP#gaFd}kksLc1uRDY6i|3;?-|kI6X==%4F1jRRf$N? z2i(0+68;G>)9JAvKo8-!UxJqg=>_l^6%%tzW{L;o)YhJFv)AKz{qYo~0O<`bl*rKE zAA&xKT!o(Tfy%Q~ghHDv_5;po5t^E$qZo0r^pT^~2Q-fX-9o9cHNrwm^bK#n*^ge) zE30`9!7e&1B~+&trPaq#9eFqMdxk53*j4!+C!)Egtm zs{HHZ>)uTvOS2Hy4lk3QdNaMT#DU<6TZRJcZrmabi45<~>6|ZPY_Bd zktUJ$>E{y$$h}lbs=#_ug+=4leHekA-cM2a4)ktSTb!wNQI8HbTPvGE{QU-@mL|$z zrqgw5L5!*`_pM~Ok14yc5Q%7oCy6Y#y5G$$T`yPCsIB1^hn$a(3_m^0CXU6`@Zm4q zTUChECr+SO+_b?%i$6xO?1iEVjlWi3{A=%ko3!i0fkfzL$wx~_ge zuDv3ceE@DQ?A2G@s&+agL~ORIlDNAzfN)m9?^tbkF-U z*m(W_;KSg21FHhg?_#=$*dO^lx2Ckso0w3<@R02(ggz!DC2{qFKD_(mY_NLA2oI_{ zt%CSWd+@uj<%|?q=J+?l5lytH=0&71N-`qlzsu#ujs&Ozu_)FP!;I4jDn%9W;$s>W z$GSo4eJ@F>jg1_U%45o%18^L~H;h%Y{UMCcvOV1O9J?A4%1w{L+9AgMwVC#ru6u^l&=H>Rl<5lyYXHX-9swnD3G_A`%+@4mqWe;$;fV>U&KFW zGlQR7g39}6w}ufK_jHN3T6&hvD6BHhPHI%NosXByA6ri8le_b$$JEp!#5q)|3|K30 zIs>bHa}rqXxWMm-i1m_S+jM*uo8=!_bv;1I`6%|sc4Vbxq8D^nDqz;GlkzHo{!pef zZ|y~R`I@&bPE#}g1MI!Xl}@b1-O^3Rce2GAJc6jxzbqT!zNqTtq}CNo>X9x(RADY zc!gf6hP4+Hcj6hR){5R{BfdXGHy5r!%sncAkiY9RD&0KJ$TY#V_PD)NW3DANn{Rr_ zsK@`BS9h}3*y5r&+2=r@#;`#dkUk5Io~o6F^R8xFMNa`f)zXYzr(KH2vp<5(Y*qc+ zf&a~-DyzCgjs2@Yn|h;b%HQKT@1OxoL!)e#L zUgOUVDIo`i*(G%cHwL;w2*(hcg2feF4&z;vjn~zm{8*1iV$K<~xJ9F_sd>hDbi~l9 z$G=#Uy*vH3iJ~;1t3O9-opX7)+xz8$$$kJ)xJsHlFJgOGn`bW_|3NSL!UsXY!P5OWDw% z6jYXGRv)LP1a4im)L+vF1tCx-rn%DtKa2-j0q1!`{9!U1%SejH{5X4LtOXPkCvNAFcwFM{%rrVlXTPFb4KC_}d(}&He%PBg^Ri@zAyMmp9 zJ>H5cF7+L3A+v&bAtuzc z?4|1t77J@UE3XHJr&A7~>y8bJP%(ID0 zl|(KX)1Hyo-TSu_=`#W((N~u0ulpb7mX}8xcTSEGB7nAetzz3>L|Nk1F}hewYuBMF zPtj_OuIFgn#`dNOD+Z&JO^B=HAr-LDvoVd5K`RM4bD1UQ!NYVRDA2dw-r$S)k9K`u za+OwyUgc#nQ&;INB!i%f)hILICfa-MJal5vFd44+oMrQFxS&G zk^~Qunc&Xmly_e|dMW4k0;|n_zg&~wgO`3TxvWhs%04jPiH;6pn4)}^VTIe}v)9b5 zA2KMZ@0R6p==B7uL$K04765HNk{bPq6C?3#q;1Agqf0v18buzU8Rzjh5<$%1S@)yYA|b*6S(AJe{C=mr{=Fz(Aa#a|6tmX_ zmCHNK{{6#K$K#yroHPXZ_Z^l`dci8WxbaEVsH;u=G;X|1Ocvy(TWNJH+>ItKrRXYi zs|sJfpvfi}MyTuUW1>7gtgjeGcm$zgYlPY;{eR?rWmJ{v-nSwR(jkq~APv&e-J5Rd z?vN0WE@?L1-Q6h-o9;#$NeKY~!S_aI=FEA{IdkS&>-qM6+k3MXcU<+4Uwmt>K!A*< z7R7AS%~`-1=7^)j7AaBs3#oQV&+)WeNp8QtpX|_cgA>$naf#PJs&NWZ@t6IlD5_wV$D4;1r zId2JL48QPgUOH0R&@qQnAxe#OQ#?FD-!DkgxZ^SHR(f7s)ia70)I2)Qn(3Nnwe=un z1@iN_-RkFHzdY4|HtlvVw*j8{GPRW#caU3z*~{8}Jte)8{^1O-Xk-#@Z8tmLpvsHt z9|K-G9d@%&NxUxA?SZD%*aWKk2lkMq1?BBdQn#WPN5OMy-6dCFUaulSuWYkBW9Pl)?aNOJhnw|BN^ zY|Y@d>i`ERp4dK_m@reu>ABPUeQ947*9pkbsS}!m&%)4wCiFVfEe-mNv!aOdXYYJU zQpPf*KvxlJ$aU=E3_pBPlT!(ZrU=@@6dXK|6sIa`qi{a|f>BE$zgJu8TQ0Vdg`Ksq zK)S{eF{oNGAVs@v3Kv60un>04m~w=$j5i@1`A18&sP1UxhnC3t!Q{8f%VOZQykYub zGS?ZlZ{0gIY6c}h@Y~&^Ss8t(U5ReH*jS+ALYD;u{0mpXT6K+ezFqtv~zCIgI+Lug=%IFIEpW4LmTq;t?IVu6z5eE^h$ro z!YIL)d>DW*i7lYGahY$ZqUw*cdwpvF zVqi6<&!OX$gRI=e5NiQ8J7i@1t^#|hv_3>R616TII3&0|atJsbe zD4Fb8eizd66mCO&!YzP^gy?)%zp`Y!-Ip1>9@sk<6wu9^D`L5@YGuvALB(N2l^zBh zHDNu*x1Fz!jOoNfs*YR9`9W$F188pUDm84hk%nl6ok$w-d2>SQvi@^y=B{~aCXpl2 zsaBDPJgcjLl~M4h<}vitT5J?*V#rqB^jVRoB0FCR^Vwch%et*C){cRq(>M^v3>{Tm z#cdmln4;&bad)M?ZdpV`)xrE73%LR6N@7MO3dtyH7Ld!>p_ID*y7Q^eR>$86ebg9D z0gy@6aKLSm(ng26Qu#)*X&K3;>e0!dZttSpi45F2Nzc1z5ODA}#6_J%VPL%wWP-P~ zPon^GRHF~`MXYizZ4V?a9k2VmxjdC3#OEjk94^WOSU2-`m`{2YIY zu$dw_{226{f=5~^^11Y@`T@yp9mKt4E;opuTBDg+?z>SoDHLg95On_G zZMh*W+x9_YP`@#3_ZD$Q5*C z`+o23CQnYh`4745WC1>EFgg#{bt8BM;A+X>{m|vV{f=R6a-6!yVs!E`O7bi7oeTw} zKJ)Nw2uDKD>*N#E)KwF%-qZ@NE}OVp{7aOD`I@WuhoE)Vxl&DwVPj>R*~nDi>JN+f z48QZC{&9-@53JI!uO9{9`?A2HiC4`bK@M-NtuF>or>KlRt$S7;T*Pa zCIoQaX2fk(e^#b&P_ zKdh)bArtzx98-lBD;MLaH<_~N+C=NTDO?zSyg(IJSW)QfWqH`52Ino+3+g3DnGkrD zdBxmQskK*Q&+QyXeOO?O1!=_&xB3P&b^&&hDP#~4@>@wtkE$;}C9|}uN^;fJ5?eb} zQDJA9DZxQjHXLIU++153`uF05_s}Af&iUU)dAA#ybJo}h&WVs###r``z~%4<}zY2 zP30s+&6}28My$&{4*@oX}E^sAEIeC^Zknt@b`_*M@On}*NC@8 zX|%VTCcYvWFZ!EjwKsA)U*=jZS4pA}^I)k)c7-RblKRoXy)BH)%LZVDYgPNF+p_qQ z9T*ia6|zu=;szk(WUs==sXaUr~r>g59LP7(v`CF&(c zf?E;Kl?O%xhnOn9ntV{IoWXX~Z+DGd53>KF=-XC$VvUd#rl#Pc z9?+;=ETr>7zoKfliu|Fn_(7cx$K-*Y<#HX6@Ci*XOrEkhDq*uf0#VlIwF^I6rj-YE zn2JKZ{kaR5%lF*4__%=*Mt*{1vRH=UwiogZLDh?knly@~iT`DF{;^~~hW{#@IWC2B z9u(g>+eesvX}8$4aJKt0_nZK|eS$to0esSR;iGl0`?jiEvHhyAC9g7*iLJK2UG@H~ zOV4=$1$;eSB0spd&&pEkP^j&_=bUxF1C?EKXtkZa|IXdxe5rabZKV0@`qFDG9289O z=)KD~NI(q^ZKQtes8%Rn_hWC_6|QZc!}nGoDAwR1yHDO%$5(F?NmQ4lpZd2 zXt^9NWl`aKcA7OAbZ*~puj+YPjwsbUHis`b)t#kf+2p5ZqgsYza%Delc%*;a|g=A^eQ&<2HS~iIZRNK8rz8r}h zmM=kBfCl$-=ID58c>^>`>@D}L*)Sf&ZnRJgUk36Io$BOdXZwd<&t3gatWz;YbP9VM zt8zIw8U~_Sy)JIBo2C#47qA_BtBFlBM%6iQd&#JI%DWufU0(Cz3+w`Le)!0QsEpn$d*_H}9<@o$beOTeR1Hoz+;ud-4Hb6UqTC-Wst zZY+9STlIHvR6<#ct!pF}HcmiUVOEvk{kP=|rn3ZzEWpQCS=1A@H<@r$Py zzRqfwB-BrCRV+>yn-(1%zHTWNajQL)7)rFa>pWPi+bUN|XL$t5aI*yDi!)~HRg+kq zBQzpL!%t8GBA4&i{ZT&OhGq`qU~&O@&fDvThGg!iZsx5CKIggXw5=QSpP@<$)YzqU z3_s2og4=Mkwv3S=`4PkMcsWbG5s3trBA@%Rq~5zEVProqbL*#SiG&rl6Y7CsPDoRw zMm4zw!?BXzwucWOU8s#fz&=vQ#vo56fq)=n#MNaXmt-#&Vrf^|ukyXxm#wGT9r;~m z#A@qo(V+FL!>?Q&myrAyd5Ep6Iyqel?n})3ZZGaaMHK?)q85P6>pOK%&`E9nJr{_0 zmY2m%B>tnYrnBcsd*9QM`0tnAAa0qA3`_>l;rR1WL^9=2M|Jh9Ul6H8xGuOyM2C*Zn@(^@X2w%>cPWO{e_!x(Ede2EbbirgP>Dp704bFobVG06;j zZmvF0=W#hqlu2aD(n?rZUM|&b(H&H^F-ZEGb4=nR<#_QZ^U`;(ZMNJTVWJj2HEkuJ zOI8fjFVQwmN7y@suYjgS5{GpIU1GqBxqoemCAS~e|G*eqegAd)0y9PDU-0bq|?4{kPw6ljcK*l6}Qb1 z2luLmF0BGjBF|L{Wg4KfBIqd>-5OHL4B7S6)qnDAHj6I|Uu3%$+-3bfi~J$h+*>b!BCL(52zAwqwtHJ78* zRBb6XyaLu$SX9!4kl#Glr#pAPy@u^k9sUFoJ*EKQ&eUb+_05q8`p z0~tbx{?scj*UyEN(RtdK@d@!3%nMD)niuYuL(2f>df+heUD zpJId1Gog9`H!@a)t~yg)Z`CChXP4KNp9Gq`PU|dm1ohEp>#SDJqUMK>Ez1kSGbnLK z8WnKwXqR_mD|l&C*k|&agkQgf5GHZC<(F5KRK)UkoFt%l9)=!(O>2RSHv`iMme0lEI? z+`$zx%^){e;Y5*GE5W29KeSSfy-9p6-_5nW{ba6ngTjQlx`DmqHTj+TE{)K)`kG`FbMqtA@X^Kk>rd7J_f9po&|1+w8Yx#aJ{f0NIP?pv zTdAHj)JPc222@le4b?w2Mv!?j^@&+S`Jv;tpi&PXz^G-Y_wynecpsr=xq z&eGQSsjz+*%kUFaqbAivsb~HYR#Oop4&T15N6oQL8+x8Ao5?IkrlDC_9nIEd*`?+` z)t%lcu8hx_=3788Xu>{F3vTnOMcwC@z!}xe>~>ka+iW_f^JQW3X|ycD5LKCL2y0g0 zw=s)9YupB_@OTnI8`rBNdGbg*DnAY5OTXzgQ|nD zM7G=5VkfaviU%px9$XfVC!;AY2(eG09K;5~!I+7enJi)?4Sfiw5{H+9hZM(MF*QJY zIreQ!D0*=snd*^IJ0jj6dQ~Ap)@F`Flh&aG41xH%P2P=~mH3kkkAs%c=dTJTJ(2a} zj4|*imp9o#EXxh(%Iq8s5^ZM@0UR$x17P%RK(Zt)9Yu~qil+Pes6V`K0IF*EI$pvs zceJT*W+~w7hjzoFV++k{tE!h#nG|X zx0kUzzt+sH&youptZbJ+xJ2K;fU1efjw$rUdIMvR2a>c8LG;8UD3P%I4AdvPA9qzd zK7D+|UaIZ34O&{tleA93;bOmq^1VjeXUWfXQZxl~7LSl9tai7i1nm-tBTcmH5ctro8B9k_zwzl+ z67!r%+5oGd0xXkjH7OMp)&7qw7T6cKf77{f+!%T-4r(#U*VmUJg)CYBCrm7h4iq$a@to`uDTtPvUGGb~UGhk3qaz~r|Q+^z8N z@IT?G2Jg69^O86MtK6x=ZDB9}rYG@$5frB-djxfLwQ_JR8zmqH^(e!)8cSdp4HGD+ z4T#y{k&i!pfN=@c_r8H``6txWH^*QI+C2+Df5tlFW`95WAEh_6SA^)lYXSW6LPHV-DQaBAQ%f4<`ZGmB< z8gzYl{)Pb@za&}K4d%EJ=U?hrS&8Z>@vb@W5y2sHw#Fg$ThxDREqz9Swqt-Vz~sdB z-wbgIY^rrrR4|#6IB6dJL|rx+zh;Iqgax$_L(#kHE2ANoQ0C`b%^)n@&G!N5VRb&O z>3w5@6#i#5HOhod1pl2KlM#M?M)~UM8rGc=Lx;D06aVJ%f3HRU<4@xa2y5?VQsIOI z;tNKf)XccOp#+Mgph|)f0En*4FT=uNwjm(iuxq}O$b$#z!*6&-Efan_}6eVYC{>v z^LgXfI|Oh?#ipX+KN2LV-6d=e#t?4D4j-I~?T`Ls<^-nznnJyNR(u*5n z`5n$r_`jP#2T8YmD!*>o=-E%pRxFtTo@`qaRUYsi}mg?rkf~&dZL#&dtGs^4qTE z69G(-VngXe_CMAQ#xE6&nO`axnZr@i47LEW3ph>NINaRaV$#yeK50{$f^+_gi1??Q znUsCBTe8E82F2<~oOtZKaIOR#?Q6el4UkI!OACXrJm`-}O8zfw&MLx}Dapyugh+^p zESDkvJ;xC_fAbYsL2=?*Sy@f*`d(jO<82!G`m%T=xc&}TM0k+Af(|;+|NYsSLw+(G z97+Co_Aq{Nb?H`(9s*HUa>EyYZ}0z10KHa)96`0_1ofPGDh)X1u2Af}ex40Jx`Gco zNmJ4v+{BcBxgujwVky8PMMOek-FRy7uEf;#Pshq%+<}h~!dhuvd;4p$QDi{=)rEsA z{kMJ%)=w=)vH!e#^``q}xEX%Fa5p3wNdKELN?YioQLXjh?7~9ovUNrP+poe%w~G8T zuNG+jxxCK%|1)drkgH4kMUSzZoZO+`{4bS&KTVl`Kf0M}7y{p|9tjLI93XMKi}Bc_?))zHHUN+HZR!pZL&{6HHPi;Zao(&CqR#^E$XMRuI2BWz`q6#xO_ox zS^QhRx<2#b;--0W9j9Vg!(3>i;R1`H((0JuLxfVt$P{{7Z3xf9xOQ zR0z301#q!imy@5+PZoM5y1(*)k}uyX%2-VYQi&s;;oC4Uki!Xr06I|RKP8gS`1s!* zr^bQ7qF)Ax|M5TnlqLP^kDh-3`-jSYWsbi{NWU^iCx$Goo&3yFEhfhp;)hIUjS?Uc z)K~-QQgu_rFI7vdoZnO}|L!wJAwUDoZa`fK(WRqLkI@U^m)!PyHD1s@z106T59{aC zCkc@L68m^_RdVM{Qz?X*B#^nRm5MO*d6Hd0VK&dzycO=Jy0^vy$tvTY$_4)mb?={h zpAnSLPsVP%?+O6+f?4P-Vdj~mIRVk zr<#2MOq*$EnQ07=1aN zO&pgY0FyN>Fgu4YO71Bc{Rnx*m0v>sSo4aCOfn0*B(#v#19W zlGH`7vrD{$d6t@(vdLhjl&!020!AO9!ufMBwwtkQ9P6!i(Gv(g<7FxAqT38KlGe2u z5Yc3)GBfa+z$?Dh5j21`mZD z-hCjpZ2h_q+Rm2Q3LV0c^72Zz$p?sNQm-E*$G6nvkp6Yf|Jz?y1b?3*ZJ($IKSs4$+#R)@~7h(o|6PjuDrRtSN-4yQAAe#o1~oBUs3A+@dl%q zew*wk@pY&&stPV!J&GFD4j~wZP7Eg+OpZPn@v=4IGZX2TSByRP19uBY|&Z%I8VUpW{NIhB@(v1hw$P#zc8j3NXSY#z$ zVDbhekm}WKwm!bZQ$7ZhnJ!Mt+l^qxHQJP8EGz(hR!A7H)U)}~!LL3~U9*OAf#|`G zgPI!X_Q&2cG~xWM=!4Y91*ovp)!4P1JrE7X8;p#k#1v)$?P370wKVQI@;k}LJB5(P zS=;4s1sx-K#pR+5q^OF%KNL2N`I*kQT4GBxuKd(?DA_3?$5 zZ*SP+=FqjG*+757AEdyd9&u~?qTcpjk^KJoBAQQ)O3$vK0D+maxz5N{=xzR0rl>#$4I zec=57>0T^mib8&SmM#;(`*DTmAF`S^E;(zSonC$^8X&6aiZ=Th^v}c};begLgGQ=) zJ9;__fX!Ykq1F$=Vl!auU51@gRKzC0@>->p+F<%83@d8Q=pmG^`4lsaWkrBo0K(5p zcp5y(={%|rr*oaLUK;iE)Q?&mRLd_jjRP6f(nkX6BK6=$g1;{Bn{dscx!L%?<<0Lh z6iS5kNtUcfLvaSUaY|WcJgXUdwr3OszUwbKH*nbdaOBc<8xFj@7S^o|=>^Z+ik*E88FX3)R-966$#jwPqMl zRc?;?()RYzHZ?HD=Zwh|0Nn!|k5i|(yS;*zInrSlzeT%Eb?;NAMQPV(qeqhlag1Qzo;zPQSHUz-6xvm@#^V`SB=d9_kyFD ztr?#g!7H=Gm#J+6jx3&=BQ(@X8;sb=)mapoqMB%yT$uj+(kcUeO1s>F9{v*h7})p@ zWJjCFc4~mHq#s83+Ce*KSS3YUQI)|OLS~|J!DX)E4ee+u zAN$-7Zr{7#S}=(?vp%!6RM~@iN_QZx8rjoSE~E68AZxayf)O+0=80yFg?VwqONvXN zi3uQPFml=cMsv*`^P`&*z#pETnUSn<02z+jZM^Dvj3@kPBK#~)I0w-10yHMn&?{bK zl$j29vA%l_a6VQub?E?qQ16D9It08Ae@UAg^kaVfVomseT0q{p6MENoK< zJli{iR1t$NEdCk)yZlxYM|uJP_f+TWV*KRTty7*|ccOco*UmuAQzl}-SfPf<1jdmc z0qgI!y<4dLAb{kI$?)yRVH(^!!kMN^y^9@MEeAmchiH#jp8BfCN`Uw()LwXPiWH>n zdeWwAgo6AzIR}EI&0+v~-1vv5&#bhW<2}B#4X)6}m#(&-r_N}k5se%dm)2SyxLv!M z?xX_V3*W~U4~KcrTbZRuamcWxNb3$79TJobXa~I3AjYY!XJH3MgE)~N_ldV%>voOm zQk#@ZL@gzxlUC_9DNTCKAIEy83LG57AuXIDvEQhpj-VW#8TSzk5FG`mT+qB`RS6w? zETx}kaFkBVAf2KAJ&exEJNGYB?irNw~>)DFA?QSlk($x?Bm}7?2EkY1`@W83mn-b6mT}2x+_+0oA zI*Y@zKyGh?*VF7&Qji0q33t?zb_m`vEW7FJj_zs6G=)83IQ4SNzAT0u)7oK>jbptM z0pX5IM&azS(@LD-JnuH4Nf$h1xV{a#pNrwxY5&Tfm;~Aa!CvvqhXx@W+EqeyF~_mC zur%~}WCSI(hxdvJ%>ZMZ2nAw`+vvqA$@VvWQxR2ImU`=;>h*3|>79>W&G^oFdB)Wi z;k%!1xHx2#itIPrJk73HW_C{dNjO$SrzcbC;(-lrdfJZZzaFzh?*ZGbyu3UbR=->n zyFQ|GEI>+cYoj*|3mov~21w$NPm{6Z)@#oZ&y8jRXB@5}+hSNBs7u5fh+jL?qsJ|b z>c1Ur5}pKC4vAD@gtvVkrHe@~o=kX3LU(ubH8vJ9Vx`fRg4bhesB$BNi3X-uziD12 zkK6!zr;K&W!#Qx9SxE$sDIjPQ&ISIwB2V zRYaok2y(J3b4AMHFRFLeI9cZ52zEMK9Q*fuXdo<(@hh-=H%UP}P!?Sl#J5M|)1KkL zCtTd$I8V*xQl3>FiNP!AghfrN>0#(cWlRn&VaFSWVD*f$6WTZd%rn0sOFS|2AsPaf zMJS4@uFfu&%SR52?esM0$?YL51R2Ox$cOB`8a;;$TJ=V_2XRpJ3F*0LtCqic7Ks4= z*`(OeMHQI)`T9~x7rx8fKqOPHc3Id%f8_T zq|j<$@8VExrNKpFkouBt2j7x2!F&?gkk&mbCwd*?!C&sr6OSQsbY!KcqC)dS{cGy+ zTbbEsgCIFe7c4N=V2fqKUN%9&aJsbUCGgp#Z z14;M`uH#d-rWBdYjuw7^xV3M(Kv54V!S+V6y{uw#fG<5f z?`{A5gjpD&^oDMiiPNcUagSySTkv&Mpp!w{GrPNXS}vD%F`kZ-LnTW-ofv?74)F^tJ^qNA8OWNfO5fVYj z_dH1XW>}VBc3wwpxEYWCVb>Y1wIlr-yN&?hOruO2E)Yq~$MbRF3`>v{5CPjzsn407 zR)#Wv4iw>_{91MZ7oI&eHMPMe>D_JhV`eU=jmid#*&YpAq8+rh6>UL0;$;5Yn@<<* zXx6ey=CppjJYVReS#ihUL5i#1OW)s&0O@UoE@3SZzENZGATwNc-eZ7`=L6%$?@nUH!DpSXt3m)N}XlBYToc~nmgvfm^76($hP zsjYCiMCPL?ox(Do3PM!eC9PVMMww!{Dm;7|scZFSjFkShjVGdXV|wS^FulX2s@qC= zax4Z*qA;HJm~OGWFoIfn4ut*b8Wk02*ujuwHl?kS+G8qyJZk8^XH_D$<5>_o!5wZK1oJ%ze#{Cpl56B~ScI}==rENr#pTTNgppU@)``F_0C855{egNOF7I6iwb0p3V#cZ}T^n`1wM z)&GVAst&aQo5u*xYIg0h`Kt3S zWbNM1Xe%mub%QQX&1lX?$Kl%+dJ)K%n3rKt5#c;G ziBxSrb3GTaX%4w6qa zia}%y$~2Vd^>%~D9yJbfJh_C^Xt8m^^9tLkGt=;t)WZBbtuh>cJ$ZkH)TxkbR)$ z7@(I2*A};N>M`fw^2sG|!F`kTfJ1|x=7icz73~bOU*4S4!IDNjh^skG$(+-=f!&l~ z_tIfo!bE#bVOPdBw5cogc^cJD4UPUzW{N{wO2P6M8A8rHP&)DHch4VaFvj>{=$3e5 zKJhlzX~vhAJYUM=Y4hF@=7V%mtW6+8`G^pg!$m67F`X!eG@hZex1SK%zE{rRQL0a7 zy|kM8_~gIBXq*I~f>4NJ9zTI~6{R6U6Aa=p=8fgC-|$B!+D}<8@Afh4MF2{g&sN!i z%&XEc$04w2hK3*-pjgQiOEubg9;Qd`Q@U_Ng1)#)#Kgw5wcsRR4Gn#1&2BhSP6~@> zjFAp-o8%~~SoZ3+UcEGd$*!57hm9WJ(Md7h?bK<$iM{gdq9US1OglRI3I?K8wFv!g z**RI=4U*Vwx&-i(1%!nnXcM%A^;Uj|xgY`}lC;_BSqU%wc0z71^-GeaipW?uo&04^ z>j+)jV{u@#t>^W&op;>dm}DCq%h^oaAPO#uak|N>>^)DxFqV4v)xGVF)qW)>-gLiO zluBWbmF-;Ef{k*7P8--UfrtkjHj|F|kOWzoq++;VAKNxKYjGIludd{5_t_sq6$Wrg zk*gpSrAkN{#JNzF;LhG~magPpe#yJBN1B53H$scM4x5`*HJfA1jcfHXG7J=3e$|@c zfKRa8xT@D-v#o`yHt4gA&zF%nm+yb4^Pc?p*c>6IG?CQGUdOS|baY`a&ZOYH$NpnG zUk({poR8(`4Lt-@FMy$(KGVc8^7US!zoX@wAzZ!I#si-YWss3NPIr%QdqlKi;bBAP zQhP~5ZMj+tc(6)+iAqU1Sg{9us#cxL|EXF0Z@b9?q)>Lekl|tEX@BQAQXfE7RZ>-T zmfh9l$WaP7?swjeAVT7|U!FHx@v(jJi-}3y5YDbGRo2!1&ZzS$)7HDm?PkRNu2L%C zdUc20eT8Oq@vTuu=~BDg9AJfau{1ZdN_OjX&Bw7q&)0l4e(;SNa1_GD>7XvsQ8NC1 zQpLhVrWCPDobaHp@M=mWR35JB z&st*;&5*X!(9olQ;3*St&Mn9{KV9737;@O@!lP-_rjG5GQC4+(>PxZ|aAd>2nr_i5 z0pl)sS5#gUf2g=%!s%}A|91Y{^mlppUX0h_zE(~^#aXsYs=E+`AtdLZ6KU3@FVjuP zXl?tIusZ!CaNWGKPb3YF9;xFJLb9LQjZMrWu=NYp#S$Urw zd%61QCRV@WwcqKe(oiz%_Fz%86C9#_TDW5~zWa8KuhB5p?Xpwox|c9(r+a_NucuB& z7blI^mDGHmC}yb zxgi|OPhNQ6Aqd%3#qltFLWe-^G_bx8Pb-L`X6}8Cp!4R)gG! z%;AOZ2ld~*xr&UM#3gbxbm$W(f9p;{biWPKe-Ewrg}uo1d%%ww%JBk3+!R1Z9O}Hq z1jbn6^xG=snDAAuKY0?HdknzA>UcItH;^#W!Xd38nv~DV>JeyU?5>;wlYnH@Sv}{> zMcCShLO|y^bvqEyid3z?E5;}bSd}o(Zp?%^>&(gNfZtnIFBUXl(TLZimK|*QN|A*$ z9`x~0$Re~u=pe?OEfpS-{FWOsgP+r~0${bSY-0WTQTH!hq##7MZwKP}w4AS_?){8Y zF$$a&`+X1QZ?znp5M`BDIj(f~R_j_K&7K}nMI=)Ls;`UAR@cqgAdu?Drh-Y>Mt(%v z3t?~~y(Y~&xeR`?p}C6j{-aL?8N+Q}Re8}v^Vs(=`Js4TvGaDwG!j-WMc!plbE=auq5oFXfW)Gr#0qyt>qrR0KR zJn}g>Jr*{*s=jk&3u0j%jY|p^8W|*Ei0dy)tZ;TaKud%ASs%}_H%r_P#OK0R$7 zV|A;mc25x3-ewe~xExf>uECUY3UqxF@8D|5>8xYot<9jAT_u}t_}qCnHw!TnU#f1A zXpizdjL)FZ0~^WXgA0kQuG%x4;&Y);so)=^@zP&rA05OG(7bNF zejoLcimJaQ7Ci#b9dEh5*j57nb+SbEO(&df&Qm$@%zn(!KB9_NEHP*u6)21^=qH z6l?@EvP91gYggu}sV#}L{#XbN;2L^y6q?;~0p`X}q)A!O#*`Ek#K9HX9iWH|t_d@a z#t+YrC?-W0eOcIev!Sg@4q&XpU6f(Sz(@;h+hQhAU3DC4ZF#pW#+NL7>^e3X&L#K> z;wt{YHkZ7Kow8s(a`QPW#~3m{=w73mcdHEuMPgxLC+~|5g^<-4F9+KcH2u! zAgHTCvQZl~1J>B0sI2anU?4pOHx?;`q(<#j)0m@Q(VmV+7^Vk(Lpya+r6Jxl$jNQ2 zRi)*l)C*57BaIi*l!k9X8$4BnI_HLOl3C6b(Uu4?GZ>7AwFlEUY6)3EQ_*9>^g5qK zU=+cG!GTYZd}%N#`N-?-bweyZ7+;t(VaAzRujaa{`QVNof!T?JILI7WJa4q{0dkU` zM+~=Wh{2O8oUXmCZ5r!^UD56IdhMnHyh%8!$rM)%ttSqf08!V&t~geU^Wb4u3!R!;+8r!O*s zNkQt}1?XbzaRU9uO17=x^>%1WRhAt%QG1>6!3F^03!AT&WO=lB=;w9*S(Bh6NSfsKsJI|;TY2&aRQ%T=@4n`cw9U#z@BU>*poR@(bDFPoL`6kGnwqpL&CansJt8Xz z>3-UcYs^pgfeAmvZPTTzR<4cyT~baio&i#tO@S`y%W__NU;+6EFx1v;SAdX_wZL=j z^J6Z%!}r>>!>pq2Nwa*iuyniS=zc*QQtQER>jW-VahOudu(&mGxmbh4&!`Qn`vwlA zas#S=t0H;c@Vf68%JC(6+}Ab`9-D27e@IyO3uNaW?fo8By*sYU<$N3$S&7{5^F*T+ zoR2{yL|*UsgJF;_;lPy&*mcVnTfvN=6mv19cUgC^&t z*$_Uj>8t$->3sRb{3(~kqm-J}34n&is^$oidO`m}L{ZGnm^Bb{{uy|7949D?xU1wU zPGaw!Pd^{z&~G}jM(mc`H0yY!4D3)e&``F}uWN#;TCRGBAEyta7MCd#GUpqhq!4(? z6InE!AdzKuw5)=#V}qZu#^IcOlF5NNP(irO5a`D0(}*T?1eUher=x({f5h?f3pO;E@~FU~XcP-(E>eLo;DK z?{!o+S8JKnFu~qF3A|;rSV86Zn3(JU<0o(^t$n761>%1Nk%*QB;8O7;V-ME5cNl!> z-R}Tv+Uc}4xBfpR)18o8v#7X_BQp+C%`XwI};evT3}_?jV?Pe_GmCSF!4h=4_}Qs^GbA- zf)puxC?p&}4uC_|36Wt~XlRVwU|KR;eQ~kWwzjiq*dSB8fX{vO16gjACaulJ>i+UE zUGNZE8jrJT5u>Ll!~G?a^25yXQD@6TIR+(2TYyP>H>CoUgX<3xv`Ea5CU6Ohnw>)@ zkJW6Bx>WPAbYUP>WyQ^MO$MTHGJ1HS%s54CS>L8a5$3q*y!I>9QbfZI^m0ebM4G(w zsOOjoS?9kX2BWOM465%0*}Vv$(e5Qh>%MwaB>x=R(t-N&^Jeu%bfyeyOTGw_h?O-q z5%V^MI=&!yJ=D5^rDb(>thO$}h2&cU-clt0edFGX?D)$@8tQX?_MIc-+K;^ejBS^JOGobsMKj4^Bgu;JW6}jiqnCPpd0l1aH6tQ>%j<|zU^8957 zJ7XD)fSE^}y!YIhm^fyfKEvrO7NOS&f!5sF_p{}{3I$0g5ww9i>8Jb420&uFD1$Y( zpg!>jL$Zp z|1$#&Et;AKfR>w|ZRO&kZLqJ#DN*$h&s- z;#s+A=(pR&0(p{a^yRJi}X%YRTE>HU54Cg^H^ro^~A z@TtT7cQ-s1qu|vqfH=qH!q}?W=xJpJfXF9YQV2rY(z0oSDG7(#XleN5rIp})?3fW- zuG0cJsOVq-sudQ~vl`3hCwj2BF71iXs*Y#$82)A>9sj;~fdDqJ>uJ8Y#B7{I_6`8R`4PxW0~ z>W5P}TQaRxp9A0Ii zdP+QW`E)L%RFVzeB>?VutA(hC=ldlU?vV)MuWMU?S>p-9=U9w}`Kwy%cmx2RvBa|i zj^DmA8jm)>?)ydqW)VLpApdYK-MZ^SCfc_eZP-Eo2#of%i{Zsj^-Lxt!Y2r|sKA-c zn+lBmbK4%n`Bw)%@h&8QE_G;U!uriA?z{iT+FOQYxovI3iXbf^(h?$F(%s!iH`3h= z0s>OfNH}pK)gCzb@)p4)91VLs93IEwKe_pu!H zDEKIqPZOh$Ujw!Ew@`bpWoi^?PgCq%J4qZq(^re z6PdUGD%+nmOX`@!4}xUo+iot~dfTq2+rC$K@Zxs!XCl?y^_zoA#HaJPxvDw*mY#Ad zUC;{Fq~16DGN+!n5h(gz4;}TV^PD2jvY7Uv&nQGiM%p^ddw6*K;J*OEqHv6s%W&I3 zjq(e?T)p)OfzWuOOWRt;*<-gEQa;a*b6~sE%tdo*rQaw@ZD*8 z-C=B7=IVTn`0xbz`+&er^DDl+M4(kW&;B*FrW1Mk8CuB5hfF`QcLj7h16ZJj_>rTw zpjOl}mjRZqBFr8$B!OxZGaa*^w7R#VxA;QW24|30vJ(hkfS?XE_sXVVL>Ww%z-vu; zD=L~&-PaG=?$$quX+Va2$h;1~pI(!wq2`z_u|^%!aTlJF#2=XOc&R6Iw4wOGPzk#B9M&YVAK9 z7n(qbcG-vsA;AiGfpQn85`#>9!{zw-^XJ)*8=fUVRnw0Gh_3|Ig-QaE1?$=a8AW9g z5E@&L)Au)AbO=RIYK(g@Tlg%7o_b@Tl0NQ7#5oNCs%#%e17+0Nm)`?zB|F*N;fMgI~2=BFKebf~Q|DXRn2o_+)=i7UK^JBd2jJmtUz4^7zsNmif2(ELE$mp6Bn#6G<-p3{Ni8a_4#j~TaU_)X`T@PRRp z*JStgz|EDyE_Ps8Hqfb^Q2nacohpSM_Hh-tkVIKoxy5^8?>X=_#4Zqd70_;4ZytEi z4J#@G6kpTc)qxXpU5^M^C}K11!&T`8>AB0bgx!cQNnpOifqxRDx@LM}8=Ze~34ez) z@)GfUnE(Yb*t+Bq5B z2}EB?uA8zp$mSbBuU)(}^z}WMt!7-Z5yFcMtxk9m=}np^Dn2lA0+`@=>Z2%QJvFp~;EJ(})CfLA|ov_tE!oR|!zpmKF{IOz^G93K1s z;sUV4W#k}xK*+h!LWz1_+qB=RofzPR0Oo<^ugr6Nha*0>U`}wTQ7Dnp% zVQkhkha^ZBL158)Wu*^qd-slqXajoX?1O?EO#GNPliX8TXXue*Ufdkcfp858NyJ(kyPSfxOPTVVBa{-)K|2pO{A za}LB{H4;T+O<`>svwq$)6bykO4G%Dn`K{eqe2NZ)X7xU?F)}Xe*xTU?Qz3jgV6JjN z40t#WK(9Rg``y>$!0CA@Jk}&Xh-HB>WWD5nreyf(8w%qc5yi2%cI1{Xoqjm6F)p3* zEumhY>i5H~aqHK^1<%l%S;KK4!6>3I$jF2Lw#|p)nTtyq40^D&Vk+(y1cnt*vBI+RRH%eF z1oSDL_O73?L?$3WWVz*=uXr3+Br_{UM@Gym2=pX5CYfL2bF%lfI5*Q-XY{a2D4KRv z0zX}joXph;BB9f3kDSX+@0YI^4}du6m&=m&>h7cLV4|U9J#O&iK6+zw18vrOXP&qF zZaN@LJJ!1e$$eMW_=wMb+#XXhruudda~|Wv5zh8_d15fJkA`}O+;)rv(V+!WlTD7lwdDfLs82^p;pOBmFaZJR zy_+;RAv`}gK5=ni;4*X_uHjhs#heY(1In!Ax0+R^9?FJ|yDZRjCA|{PME~beVvAi> z!|pO&huiSXOZ&0EiDLrDH*fbFzEI1=w#bchjzKm_m3% z>Pro%Fy|-f=WPlZycUh;c|9myi2lt`Z~gdFHr?PcGh_X;lGoeGPvA^QQ><6NKKWde zt5lp2UK}P^Eh@FXVSI|`;)0s?9*}Z`KuI}9Q-s*^(dugZnb+C7*62EFwUfEflyQFL z_oO(t`F(#3+z$uqu$}|#AStI%{tpivkP>zu`@x!g2n!m#U*WJUE~TL0_E?Wf`RxA(#4FoWPS0xkHH#-z#1%ZKbWEAAWPA&JqR09MVcMkBYE^7^rvUz5w$sfeHv zu#8HsVQWP969%VOk^m9oG2JK9Bu8$8MX#Wd47mujmkakidF5>O$IAEs0pO}1Y1;k_ z%n}P3b2Zo>Ao^Wx!(GzPq*q4;+^yP10cg=T<)1xspsku+pV`vEq-KoSTsoV!8*f^^+HnRM>N@5V z4xY#48RR*6c}NNFegx1>ii|oILV3)s!nlhz)7#ZMSRgVbH`BzvzAbxgadtUc?vuMe zuHXB>#GNQ;9arOt{S6=CKpTqFI`eH1=+rMtTU(qfE^XOMMQsJwaW?k|vodbuX=r@X z89!?j0Qc-8phOd6X=+YC?5(!xoNSG(`F%8J71Kmw8vLh~*iElPq>RoG*S6#i3Aneu?SzIr!+#jO86MwcdR+=iGjL4MSU;ylT z`x1yv2J=yI>R!KoEso&2H?z5FhYUIBngQ??$5(FnXs;wm4w$l z)!$TvCi4ZZEz;#+{0l%3RQ!!pE=6|6?RsXNf4!6grWiDTL=QOv*vQkDw{2s-t*wr+ zE}mAs%p3z}E86zx=jDFlc3G1=N6n>WXjVy45#+KDhH+LT2b(UM0&5^F!*h#zzs1}u zlkw#uGR)d|QC6Qt*{g8on5PsSI=}AV+5Btt5)lspz*Wb@ZiZJo3+eMnY7~@d0>xmIhJ{|>JXy$^v_2c-`jQA~ zK7SsA?VLRe)xoY;bnZ%Fvx3Eu4CyBJ)tT1hnPzpF-a|f{u-~Zcz z;wj@6aN)WZQ7L2Jz9sX%JUive2O7CO0ciXMy+G+!$#}`j?*gLU`UGmqPaD6tPaCe~ z!Gjh$JlMBU{67k8@y#P;^>t$ZYrn*=Zwl5UR_AB!`2Jk?brwf$eui2R_qg6IN*2%k z@M_X5Gaiq{)V(+&YbQ5RFLr0g+Rh{Ek?}^m_rk5VYoUTSB!Bbg_2nuMHh$}K7w-xC z3{KbeIZ#IJ0lDx~ioB0k_s=)>Th8@bd}UxwCNR$59b$6si4CN344Bb9di2N`LkuYXz087_NNgupU62~vhh&{IQmWF7W+oJZjisA zuu92OadIjtJYEPc6q6eCyfvvADZ|0q+Gfd41V&B?2GgZ|X~Biazi`B8WGId(5KcFU zX1tz?8?z#7T5umTF8gZ-n!rxY>mZ}Cjc@P2migCt*~f$+bHWbsOxYm|9XOwCJpY&~ zUr|vpcYRelLCu~CiUdHUJ5oV8CtP$(QWUrxdOo5^Vp+xeGWPW~e(;_8xIAaj;4X5O z0B+|)+i9uYrlSrDL2#8g>NfJilnG(q3nqT!FX)+hIE;EDFl>F_06b;j9kVr@l}xZp zO;r`>qFrZu>amS{!yqhp4q&(~mJc8swD3#C0}$en3wrqCWoX^Evt7XR4HU*DlTZ=O>1a9jId*+4$PHm2$i??!-v)U za^%PQ_Dj;y&CmIp;U~4JwvAH6``_0y69H1RaSA33w%OOdi^+iJ(2u(OfnKsm#8g>C z5-oMlpJOH;Xx7(W0mb&wRmqA_uResF#m;LWViJOKemd`a8BauaX z66y%MN47uHpj<9;@CXMdzdO(~WD=bowEt^9?!UVp)Go8~rN|E)l~BKXd&H@%>E_2L z!wOz3tyx5SCZ;dl*Y0*#SxJGOl%9i~r$b6QhVnbV(&!z{1@1rAUJWItsIhlz(f*gq zUMBeqmkko=ZW4{2FJI8FUWtoVCZQ}NgLIc5r}1@q4WdrT>gsB;9DSn)06TM?*8As} zb8I`|h+!h-*MtYA+Xs*s`s|-?VS^&Vqa6CqX^Cv~xJ6ugE~2yB579wq(5!?l{sH5* z+!ev^I^8JjO=7bOD%&RP*9??$DJvcK?U<7}(yw+;R^BQ=?xFE6lsERQ)_y_ur#lY9 zgVms@d7AdoKHC<4Zo+?;bLrx6+0(+eoejnI#hDw>N7b0W^Gx2z)vH8A(##!!L=EESL7`#<<*;)Gz)FYs2uVarkjfcaaGdO@l(kQ&J zYH9om0sBjyx<_ zuk9^p*x$G>LF}zEpD97Ar$oeIn^qfQNVV~d^g*kSlnTH0c2x#{Kz4}R>;flt=xi=KdR zBAE|a=MOSEKWr$G5%yl0o0U8=^$H&>^`jTl+c?%f81~21Z(oMKGmoR5uzAO zaOYxf!q%7yUZn3DoR^A1#-;uG(o(-^Hwrm zP^&>Z;ZxAfLXtDKGg_P|Dkk=Q7OuOU&tL|b{XF2B-PS!o=*^sXDfWeh?Dq%-i(;jzQ(;`neawq6= zX|sV*6{HFZ6P-}1tBw-9=GhkZ<_(z+zdk=&?Imc*UE%eys&&8!^MD5F4?Hxc7O`Na zT>Qoy9BhZI?^}+mxws>v;=I48!|3+~5rYp};C{XX8^N;|uWAL|*t@>M)+0QDHW@(U z2R%2R_EMBWhC@5#IjxRqBb472TtlA1EP{qA%-~4&1WC*|-H4rz+iF_z;q)h_UyahD zgiy!!wf%1+pt=|#m0H_b?%fxwY6F##LU&*oCSES@z6U*DNNM$-@a*jaUX+nn0I~SZ zu~KarSDSI~qvK<89v*FCr;CF{f9EvGTnoTiCE0ql%IH-(VIXHap6dGBNV9zu$`w;= zkRU7|4yuFQtiMqA*=C1-5pxYfIn>xV|8DW13~Uq@DGs=BCD`re=%6-eoF3VNpgV#9 zM-8hKBhF^Ba<#}|<45_;5615x@+s03p<{Pg62)UOd|j)jUuhi5VX|$;e~kD2BbpET z7}w&*-%+q-u|X@Es4c~A`e4}{ZnsuLb9*hq>yPI2R}_GMvh?G(H2#?>?%uR%+|(+R zXM@{qB@&GWLzu@Zx2qgs+h`JuD^8cNHOxgW^b;L`msEmw0(}`q) zg*_lV>XQ;{aIq+RmVeVfw^vW?5>%cpIM&~#%^mGC5vOBJ93k>aRo+xJZU?esij#oGp41g9K50}Jx`ugHFHa0@=r$SLWn6$G(0I8~WyrWz)UNbyw0?WxulI;!dK@+#BN(P4pdn=tdhyQ#B+ofi6E zBIATTjV{|=tcj%lk3okFR$QPt%p~gVM+$aRpuZt*`1cy{9B;GOzP$g;VxdBuUV3e~H+q9Ve7uo374x>ncK7{|ee&z?Ox0owl!vFoNt@*f9EOD%)r%wIIMb&1I zrwIkl?{$HORJgPvb5((&b2tC(8e*u5$tAWUgZy~2|<+skl6y<*fnE&jg|HtdV zUYaaVz^5*ySNAb?CPQYLigtq^uUy#>T~)_^c+@&c3YeL@$)LW9>-hiCRfj$j2`0iH zh(^2`6NSN+3EjFj<^M;<zCYKFHC0re z0=5Lix#)jjTyLMB`>EfrKpa-T?oFdKwX!d+I)`6QfxDEVZ`mmj`qIiY$k0~?iUMn- zCSe~@c>Y&29~}cj0w}b?+kN?`rsZ+$uRD+JUp^f_#ax(>@~aX`qGp(*?5xBYJjl=u zI0<#19h0cvg|=wCg;Fn81M0>9Xwk5+Cp^d$ua(McQTp%G!ob4maBm1FsB710zHmc|2g-c`U3bPBv`{%|IZ;AwypBbcS_ zoG*zho*L=rU&;Qtx4--eXdm^U=mok0n}81Twhj6g(Ehw2^TA(6GhcrE=Z%;D@QZ(V z*Y2i5eR?&IW;Ju6`95GHh3y1QR(CrQHvRo8Ym-BI$z7CkD?ho)hsVCYsO1XUJXMP6 zrvKoTPqlhwBu(b4sYwr}hbY-jO@j+9uM94Hg;xTQ77;*uZhJLg>|=zGxK1^C*J+LQ z)H8LEm(7Xw%4wM1Hn~nzn@CB8%6s8O68p&!kZQ;LXrI4nHYVL^2_y&9*DK1fy`F+0!lzR^phHN*ty)xrEeBM#WHD zqA#18kV85lzcxK+gjXzSl=pNEO*par7CXt)6@R8pK$|0ZDqtyC68<&Z#b#MeqXbs1 zd*lM?jTPK>MbRZg&$l4~dU56<{3wNMFPaSF?mZ<@ptK{7*2uf z_w&e>nPg^`BW^irG@~z_rQRqx;gD(KG1<@{Uur*kgc*K>Q1qpWpy9^frVof-As%*wdKFAs5bFFApv2g27MIU`e3xojSf4U=+&7_e;YE^xEn zcIjTTqvV?wlZH-SSG*sa$}`*8qUw0%|AV;^R@59i*7(NAnn6xmMN}vmK9&AH3(0eF z!0P$!46tI~fv>l}#4ZlXVXIr$5aKJ0*)UyQJ5qf;^b5fsd%)WP_YP;ahjQ&gvacq zzpn{+MA%CbBw~DWf-u3W{jPgnCzv^BX~yvb5`}D;1AChqO%ZR+xo&2`z<5nIP4#C3 z^M|sEYI@X_B;Cse>|C_+K_{n-V@GbXzN_CMVtazNMh=M0gL6egkAD@fsCsXX~C6$ifXxdF+Jn-IeEl8+6-^8k;3kk9qXz z(W^;GiZ0u+=C7T_5CE8eEC{HO#BXqUyv|AhjeFoi>gW~2GkEt75-u)HL`Q-qM)o#| zy?>BnLE~V1HcY{y-k~9-^hGKpi?vT!Aa=j=5`bT9Y-~q6+rH`8B@{rElICa-Bn@)KC@@GM!2Ks_DZGB`2&&nW^Om^%fHi7HcH@M}aN zpS-E^IlTfWa$tgJsrmwl;;%O*LdlSQ4rsb)7Ea$11{8{NRF8l&cp+!_XiK$>b%7hEwXFmmJ zi2=@uYe2XbOe$s)b5^?O{p5f?OzncjtjTDV8fpGnxhghhhjvsk!Ffjw$i zcQo0Yayw-*GaZO7k=1vL>!6JK;*~l#d^!nc1Fu3HJX>nz`974N@;19&VlIAUeL}-6 z{qFtDto*^VYcMq^rNX7YymW^tOCKUD~e!F&JmykfRCFDhhM_}OqiJ=J#pOjo3SK|)yKc21SF$waZ2b31En9-5cO zk#K3r>^271Y>ud2OWXu900=*N)14bekUTQ6T&LR1@3NViy8f*!zc`00bKQ+Uel5P`RmdX5|EAeGP#na9;uYTY-EuvXus z!;r7Q?lj##Q-vh;aehU?Ck;Wv_z5_PWK@b2;u{|_iFqg9ALl7G;Kwd>erIxOYj$tg z$oR*XpncRWX%)!xoa$!n+}Y}T^3L1mUR|7S!p-esj3eKk!-h zLvrvVb7n$9ng}6v;)=C41MHKvzD-1*XQ9LYP3H%A7)0*G{0^dg1?BNzRw~|0lUTRS z;~aa^<3+yS&B=A<(ImWKm6@KGTH?n;>!poQ{?q*a3`~Sk5>pln_9oY@qR*BAmuP1e zVG;hlaFq&Nxdv^qm6*E3I0Tf?`9UzfiqCh_m301iGJk3vvWcAEa&2O23|V1%o1^SV zX_u@C_=-O4aM#Nqy+M+T$#Ip7FEq+`ADC1TjA?KgP~&pkDsba1=-r4X1+ zzCQ-6y`S*4%L5uJmObl@Zdd$ij}oo3G;_(OP1*7WO|HhrO8vIBFHtG`N`?;^2HjJY z$1!e&R1;=~*ID!2;}vSX#%-0}6EYzqG0M~^rO2l})B15DZ}*gHn5Up;W2`33m1>y5 zc*~AwDw2TZlZ0aJ%TcXDB}gY=5@I#uo+`C@iVV4Pcew*i?3uW{b7OqXw0Z#iKN70& za|OVg|FiUVCm~#g*DPOiy^lZ@mQ|~M4qs?8dpX$TD>j(=@7rcJ8OyPV*gwN2kDPwa7trHwtR8> zHRoq6{>+5PFr+z}J84+tlnf(Zq*h6Sa3>W)NA7cA7j8;-cZ2W-vfB4n?>KuBRhUs* zF-SMM(n;2|M82(l-?#>ghLZmEu4Nvw4!>nw(^xOfDdm~HlLJfBir&eA(iCBXLT>46 z0(BB0z)^(shOdwiS;lfzux$M*|H1L9N4le<%mF=fu=VJWd=-~WLz&5igDwla1*nA~ zbU&*@8|RsNM5|uU!QuJZMH1)DNCJAKx8m2t(B^mCnvJ8wrX?x)26z>i#jUpX4%!tW z4QNH>>n?onSMQUvTZe7@iES~T<=CHPpNm41J+6((?WIQ; zw4eje%J~CZ77aFgsDN}sD>#x++1oiW-}ijFHmO$a+yWS$9Im)Y%3uz38sJm95GlVU zO-zJ*KQxbeyZsO0v#(^Y%wDZ|kiZ%vZh5>Y@g%~Pg4!U{_iUg=&ibXzCJc2k)i9Zq zv}7!hDwP)BN0E}*-Q?Ic0ajIy*2tGGImYud4S@5TAt z%cvYznMdKfsH3d~IU4&cE`3;=<8_(scWsIt)p&e<)H7uA`=o^9@(P!CBm>>gQ%~;@ zM;?M)!ajTXhIRR#OKm4-k8+}xmB`8ClK=*=Z(&rG#-{Ey^d<; z47zbF)7N#g<XcK*gG$Q6&9r;=+ARGK4;L7h^D%p2z`x z=;739ZPren1C3ZNW_hv(NR#rtI}g_fR4nH3PptAn2ZpdoQMVqGDF<^Dl!4aLE&09F zn2kp!q{utMtAuLLRVn%6kEiGpj#CEiZgE6rl*XXK^D^89>UHuP-#DVVorUBZUU12T zX0)SFgYh)T4$(rE>5vh>HRAFPd$yi_Q@(WA#f;9!naEKrp=1ZgFy$LxFTt;*hCORY zJ)l!`@!IeyKRbk0&pCrqN!wN_jZ{T63k{(MJw0yYz;rb$VMe@AfF}j?OuMA!lC&ve z##8OAagBR@mb$3$7jS(sE$_kga^gh~;e-N*kgkPAa*5XR_d1_`)ZwS3{BzU6u8hhy zM7~!JO}!zS9P4jPfB{2!cF?JlRAD%?Mkq_b@VlbcEsFCioO+UZr_<;nATHtJAr1~s z1zs5Fy!v=v2+kZKfB7>7H5(23#Bv>5R(vwZ*0<7N1zm93YMmz1o%W)g$#1~^TY zR@ugE6x?4#SJJYbs?pqcijv-+6v;jBZ0NX)%4E<9oBYhNS+5&@OIlh+1%EIjHZ88C z_nc^sa@);a6y?}-cJJ+j<2cOH&*I%%KFiB5Kg-X_2b4GiW;w+)r9CZz2P&R$y!REx zSEyE(;z>dugr`1G>se%x=F5fgXF}*kw?EdZEgeej$J{e=lb0KGxp?N!Yz7e-nS@~q z30^Q=Wh6loDUz>$1#-e3zTr5lC~`s2*eYLv_oG^Ok{>p|qzj*EKU;1}AtYt5vj6k6 zU@0zXJbe~&4G8QdR^DJ9vaylON!$0OPU3v3Qy+8J8DHRqhi`o0x7=2cwu%1mez%2R zl2fKC5ACY8PifM3HbdUNZ!N5s?2*o=5^CD6jU=l?I|PbLmhT$?BrPJ*}>)3K78O%TEpO!6(y%SbU9q7yHD+ zZI@Zsc{y$}wve!E!T)TAU|_qFGk(h{$C10Y?~BT(KwEx8)?qYJqdYXu#E*ATZEe#> zJ*nOS8)EIC%1FM1A5BCSeIkewSFXe?+{Gf6Yd|=XFI1tL%_@XE<_RmcfGSbOnR~^h zp5^$1CMGd-r2m`abmx85w-|9;$_DRR=rx3=Bre_a1cq%tM@px2V@%gkRXiVHXf-6$ zR$Ln!_h6Gh@vE0WOuypNe^lrkfy?qlJG3B(I+5ONqL7S~GvELP!xD_PX`x%Qu~o(? zTE8RCp7=P-R++0e;nKYVyP2}oJFts;c6ISE4lj`l;S2vWav^M*adyr~Jii4$Wwy%G z5DCSz_7c(4^h3fPSMv@X6m<)Q819oaZnGR#v3kUNd|q`wtH1*v?4N4el@AV5n1aTQ zmf@B=5rTc%S!#-=$dI+Z3<@b4eU4AR_!H~$6?)YAW7C^gB_cDTMQFU&@=&dy#~|s@ z2oNFUe0|XUTQZIR@m}bNpcIb$kq(SXn4-OJY?SWTuz>GUCP=WB=K5-#S{3n zEMB|5Ks5Qm%IN)d^bls5`D~Nx`Iyk14~`u)c8zk%cbV{qY$MfRsybn#Ha?E9nN5N$ z%H)yD(GTvZ7c^Ds;j#pa+bT6$h)Mla&(T)IB}EWp0OM()+I~?S#8P=l^GO7`PVNW& zwrcZauA+*pzRZcu&#FW*QyYF2%;MJ$VQB!Zd8cR0Ds!5i9l402t;p^r+kCRSIGrb$ zyZSPppFV&0!`cq~JKB(P^mCs6`D)!HWzt+}(crj9bgg8B8qlTlG1&-K>%oBHGTzWL zq9*#8*#*}%=jf_w&hb_%J1 zwYsy3s{MLR_U)F<2;>pKu}%Jm!kpq6r`sr|0Sftp&Ru$ z$4zTrd{vHmgUY}1;kp&MS!Va?)`L~5K&(Vp9hG-XIUhuCW7qs2NLA6?7@wGPXkQ~B zd@5xqiuw#0-L7;zcYIS4E|M%zSBMjrg|aws7kLvCZ4|kg!sK($VCyNix@UP18lcS* zB6w}%IA=>^?w<@hv}9Q|Vg?+EueyY} zrn9#V;{AuQ0h{mSluh3#Pb%LNVa_C>nen&}1Ecd-JABW{cp}-ZS|U_+=ZlrJ@hK}K z;ESB6dN~6Z?vf9Rs1%fsTS0nIVoG7{JL1s$A6(Y^i8`L~f7RUwk@*LY_1RG+RZa2^ z6-aM(0KBI|pps&((KtVs*Pg%g=8R@S+hxKzZcMcytOJw~G1fH5=x>Q{f0d3xu%K4@ z1%(#bE$boXhmozg2+8>kYzkF!oqB%P0ST9fJdam9+<2e%NcGI=hRM8HU3Js@Htkx} zC)SJ8llzHxGj&Yewr!E!&Z`RMo2IpOz=p|j+1JYB1qR*go=g74w+|i$_MZVl?9JtT z+U^gFdD|3>hp*Z;ASSDVuEMS~et4ZGocmETr;Fm<68UU3G<-&X#d-*+=6E#8rlUMz z1&`woC_iQ=S1R+%KZhy%=mV?6K0kECtyw=urs0^E%IaS^2l>3y<p=qxFXp>I%$=bw{jYgM{yiJOskrkU&es&6(}(ZU#x_`#j!Nm)l(qg zNMxz5`z+soJzGmkK~OAe(hvHcCxfn49Z%A_T~OaKgfyjKo;5Twi4jxA3p&`e2x3gj zGi5r6Z|5ml_X8R{jM{daM#nD%1!+fVheMW89|ktv*R98gb^)PwFW_-MIzoCXbRC%K zH7ra1;zW{2gqmu)-XnMg2TQvAB;#o9-MAJvn@uAaDzu{0*LOYYAq)<$paT4}Q-HAQ5<=w)rFqJHvk<6_W%O0Y5o1piJ z;_kr_3SVz>)ae$7slJFyV8pH7GFx4TdSh8Lu_IJ(!G_YE&!wc@C{`#l3yN|LNmPsuIX)fTbIKA!$$1Sa6#F7T z_?Dn=uQ|46jrkJohsjAy#rt5ZY=)nF!OG))A`;}KW<@7EF-1wWbgQcw-oYy>dwAF5 zrprIhPk2SA;NeVS*vSad{=O>(dYA-rpu%RbEJI)rUx~N1uIG4=kD649A|fLvP}yZG z##c|M)q1W)6G8(yp}NIN`{-Kq>>C8J>AZByJ3d{y-u*Zj7h7|Ezae19RDoFzz| z>UMTVguT>vssc^z*;Bb^s-ot;{dh%1)u>korc?)wQ>G}Z>LeyzTeH;Eb`C-|uDF7P zp4Jx3nS2dNDiL9`7~M1K3US|@FBVeS+{tQzHDT}Nq6su3mOxTouWzq-2O&!e(CFnH z@Jp5Ac+=)qTa}Knoz3``;?XtTtXB5y7sUtJ2FN=4y<*cI!XG;YkDf-o>=<$Sp6N0`=cBV zy0V1$`H#yKH?iWf>9~X%0;uKGD=;CX(Q8K;o2{QHbhG(ui3=0F^LZ(q$46i{<5n;x zlYivVkv&(u|4q+8Dt#@|Iq{=mxpXK}%!@3^=f`g6)%Pix5isWGbh1Ccm_QT$>`0Ey za=e{TcCxgpRh~+4CX|VuHaU+RGEd?!qNte1y9T+eXse4)TN_M~a z=WG|R%kGe7Wp`98a3|Q{2+wb_tQ!x^1ymZ$TFyc0~zrpbW!F! zLtTA`e~G65IqZbm8!y0IKnGi`O-98MEMR}~ijdP)km1o<@{<5c&G7@j~xWL z_MIXJs66ZtL~@#dFimNtH^!zYaZH*(yZ#OI=tpV7zXJEi@= zt@@CxtM@>t1OM^5;ZHH3epH^u?FNz12wHCUqnndf4t+q1Io;r!xRTL(+L)0|xSX1! zWVG~hr#;~6|A1022G(T3QL^EUjXE{?B7zA#tp3|LMXMK#!OXI3o(j1T-clvgsk@V- zS^P}QptR}jyMsT_h`}1MfkT_9!YzdmDoY}^w&QR(B-4;Fc~~aR7g`Gl@>{6ALz?Rw zb@pN0Ds7}mOw(!=5&UppTcIa6d@kNkv@)x;rGCzA=NO;?DJ^Ydt}hdE4o>2t(fg76 zJtMItb64?NU==7dGWqr9WN6^#)crD-c#dU0=-1vHefzc0Moq?cW2l~=yPV5E$Csq7cSKv)s z%jH$%GWRj>PiKx-fV(qER4GUDdAK2v5TntUG}3k?9LP(a6fj=vBgpW`uHt8Dy7bxQ zs#<>R6=y9|>-ba*jz-ZBsSgXl3=w;CmnzE^I#|f4tb=!&zReHawHq%j(yrqljdg$E zv)+I}^6C!EpT8*Hhk=DiA7DyjP*quMFfQ>l@dT`X($Ze5&%Nq;@P2}AYU?Pjr*TEq z;Mq;uf!Vc~XR!7gX3f;NuAnZ5@=^WP?HNM`rjeNm-t2~4rGTN)>Bp~ooP7e>AL}R9 zkG#^9QI=`NpVk;Jh!5&@wz9BwkkOa#vn)BRZkvKJ`Oz22hj7ONSF+=U7I~4>J@u3g zJR<`8&&AwcEDR;9eL5FK6ViGN-A{0IPnlZEMpeaA`{e?pAD3;Vs3}BF$9WDB)I`kd zKvzaD=n|a7s;|fLdxK4)nBe8@gWIQ95Lo6s&yQny&4dt_{Dl5L-unOAgxkL=Cg1MF z970_DoeJ3?@Ea%?{VhnI-2>lt`~p64f}S_;@~lB|J4z_456c4MH!HkdzaIn(y3BL& ztPjna~d{JOb@+8Cl^JM&&HLB#@@vFpa zuIrbLGJdM1`zr^eu=%-$F`Y_y4cA(8%J= z{B#pDyc2y~DN7u4h|sP+K{+YL>mgMthN-R6UFb_;>_H2dw5mEt+M8Y%E)}@%$E=(q z_Sf6?$EMOKzXIDfH!bL@_+ti5+XR`lbL;7*kLjg&mUV5LL5AHs=w~Z|#qZGl>xDm; z@?%cl9vqC$QKIJZ{!v|`+cbt?{`Wgj^yuIowCe#gs>$WXu%f+V%CS8Z%Cc$FB(G1A z8q;rW$HmDMOpQ?8g-=ctreN?IQXeO%Y0^w-%5@E0Mi*Gd$iMgfd7)ormDvCUQ0yK1 zA~GXu{q~qbpEoN7_T;<`jKju?g<(tC*p#AErGA>;?>*}kbTvmJv)`=2lI*g~AyxCG z&wplgPNB$dG%{1r2ZsQ1i)iBeRqCeyIwIlHC~+3?mA#3Ky+gS&e z=1jw`5N?hcoVoFaz)NR=qJfN#$VcEVLHHC+?{k}JA1U24X=!O0nwp|HJsrhhs6ChY z`}2DNbA5e1G&xBf5fPyd2Z2~L$fP@IGJ04Ph{Psj$P&je-z$8jMrJH`c)t*XW=N%d z+wcuST+Qu*dj4WTH1vvjn?F=&$j54HY9`+gKlv~B=0A2Dp)9e*} zGs9sKA8A-@J!qYypUJ;`?#<4^qgHU*U63P~+^-#Atd%W04Tgz(i&TptdyGP74FBM; zZek72XA^NbicU>W2aqH)xE1;_BfrIna2PfAOwy~)x7m$JCE0ehLW4j#fr;_pg z>OcF)|GJ~?);NRn=|n&Q7?7W4mdmffLr%UU#(h1ryE_mJwWNwlcae4EOa}kzwU8>B zKH#=XFAs!NODDtiR=>7txHkDRhxiX}mGs@~+0F0A>&HTsQ(Ed}7WLf8`q?FM=X9Sn zDkwAD{g3o=qm5CZ#=*}513bUVeMZyp0;dk|MelU&dWG}%KX$lwG_uEt-w(l4{WQ2a>uLoUzy5z32&)kzZophS000vu}91hYavR)#fmEK1ax2d1B z_W#v(=J8PX-`~FyWeI&NltLwveHSveQVfzp7*q_|W#1*CWGUO&mxLHg#yVq5$})o~ zYh<V}@+g7|Z>k-*w&h_kMh@pRRlHm;c_M_j#Xl-shap>p7p!)XT-i#rFiu#STs` z9fF6p&75s)@*xfW#uejXVQoOD0G0r`%a<%)&vGyf;PN@W_YfNl`wwCGHxH3Zi|+U| zTT&d_UPL4in7YZ1j*hA;quRd_F)QyRZ54@MD6wW2oyJ>k(KdXcM)#d}zG*prqX3MF} z9fKJk&sy8qI0AkkD<^j}0aMWk*`2gniedln3#CJ}g{B_^ISL!8izE_DVvU1?Lp%zF z8uxlvSy`$2qN7xMc2~3_E$FgVC}Ls9X>|=SmkN$)@3>Bi{AZ7CRx|!pt~IP@cFvYQ zl%$r~K{q(~m{ORJ9it6u5~LordUjCo>Q&NkvVi&HRKFc#j>6|P@>UjF4caJgDt^;q zh{L;m-XSgR1~Wnho5J=3Igy54D+N6r!P&gc3aSe&WnCgrCO}**H_#f)Gntl|&BbR0 zYBZ?Cn~IV+ZI}7XT|6wOMVQRBOWD#(4(#V)g4x=)7Wdhd6ENHI$vg*Dg7?CovUM!0 zKjLHuN}DQ_bHI*|pw$4ZoShAgrVz#D_TxI1HX?$7@5Zl;X3w~Pe)anMcfg0&27Cqr zuY3*t_yaPQW4OyfwT7lrE7YHn2Nrl86)IPcSvN^AMd2DM2c4A_2A0>Qly+Z^#;D+~ z=U!#qJMQrm`uL05_x_9~#*?I;?e`${ieK53C7uG>&;{_l7_V`L3WM`F{zV1&0OhTNQC!;6}~+Kf{zwaQ|uz zvukCn+&Nu-L66-NVMC^FgUVN8r~&>O2X4(;>*J?~6IA2IN?-pX?1VkWBU8l~*>Gky zbnhgN(?_z23x?(YxtxdSudLv+pA}jh7~(@SgNgzO`3G-GQ1v708@zurHDURf>`fXd zzv_N5qz8Cz&7S9;;awPZvGBH-W8Xh4KbCz*NpcL9bbEnVjrnorpg2DM{#39Q%Nt4y z+Zh3jl~io(1x;vAEW=LwCie5(nNu&o+LNTr!TV}ESfBRQUwck(-oNgbKJV9$g(7;^x(8>}KWLbsfHrn>jAyr zxr1-?ryt!J0qu)+vz1aZ=&e-`MMvPrcazMbE2Ap!i$0ee3s;e0+{=ns^Z#s1p!JFS z4c|YWq#KN2jmJyE%#zIo<8s)zSXyYAeo)W$LTU!D8^W8(-VlkonYEg7o8$RD$3 z>V_(jY=GgXy38=Q_Y9xDXL#`cf#FrM927jxD2nOn1S6s|irx)t6^pD(X!g%A{1oq` zsXuIt=Q>_c%4X~ILWeMtH^pT!-(g^xHgqa3<#UB@(BRR_dWpQi$Rq5Zjy%qbrvrU> z-d$MPDox)h0qRuk;ci@NLe#)OTPNIZAesva<>VyXzG;k1S17 zD$;6W-y|tQzYRYZc8itpyaX<7v43#9D!2x4qI;n3ue#gT+TsJoztZ`(dybASkKuxZ-5_2Nfh0Nj{5u$e-jobQR_NkJk_2o0r-A`EmC4l)C<)OHG4~RL8xI2G$ zz#58~_0TsZ=)ZP)#h6I3JT4%04Tw|{dJy}@2)OHaJRv18}jH|(J3A;DY9Q?u!_ zSQC~-8cT^sie8;{E|=FcEUFD7k8ay7dKEb-*I8E7I?MNAfmzElAh5J6#S*mInZz3L zC#)`#N6POm5Zn&k0;Xfv51uZZcM+kp4#+Bk=Yv68Tk7&&KhoS$Sd`4AvsM$&JaHea zbA)Xz{6)6mv!k?V#zs43Jb|ZchcBUq@h^BlfGt2*JUeF;XKs}o?#G5GASr()Q0FG(>>Xx=<0b(*W3k) z9L`H<-I=UYPRwD+|< z0%zfq!_hJ&YQ`nviMuJ_m1Gs;=r=0HnhmSXDOb?jAmZ+>pcPAR**#v= zQu1;@Hq&AHvEqK~Bhf0x6JDgFXzBReiK3Sd>~Fiv8|T!FIlH=bU`hUz97sZGr;xo> z9JNdbX4G5$HZjV7r~VMKJB}$~+reo2{l|Wu4dz=g(?I=@hn%yBTtQ_8=}Q)AZ3ZGe z5sw|sZ%C(ybls3+uEJMV)o-+CrFnv#7dEC83113RZNF5ma-Bii`b71HF(Lw7t!{x7 z6Ke-bqC5~CA_Uu1h3W_o-ggp!4e;r52ib|%A=VXV0v@B3A7D^<@;Nl)77 z-XXdt6FqthAIM!7HkD6SNj88JRpN0WciY)cIfjtdcD-wruR7KxI;NY9CcAm{31R9-j%7!1$AF>(&1~VJC>@k|rYmwVme$}rxdqCTGA^}!X;`9=^ zWp&pwM_M*oxV)$}C$VmkNQjG$E*T%5&C~F56jsg{5t`Zw?ZE==BDdelxr2uKd*$bY z2%2(PJfXz7`lgB}Rv1LjO~qu4W{d}}x4$$v$?0k^ zuzB{X$kx{(xr+1x+r82R8+uUlafKqj?~l442T$1Iw5% zpbf@Xl%(%@#xMm@EK1vH(OWa3&W#1KJokXH7%m`ai_7Sc!=*Xsh*2f#+$_~%PR%?p z6j2%mYwu;+(y|QvBCgaw|H(eaXF`Sx+^Svkpc6|fVWpAv&2Zx3)@pA$iO(__#Ez7` zkFZ5b-P}3na$_K4IF8ADq^^*k-W_*`Dc<&}?k*emt(hIAs;R-<@eS1ZVe5c6M-e^4 z4#j>yykw?xfPz4d4gXtkWCmBsG%ye2C+KD^F38zu7r|p7BPV~&8gkCb`|5+Xw=n0| z*diBpEV7nN4nL}P7!&U7)M=6caw65y6uNsevwb{^>dG1S*t^QIC;Cq7?KIE@&U-!L zsUqd_`98YG4NdDGt-p8km~(A~Qa%}I^$>hFeVI#O(^ z4x4OS@7hpjzP@axQ0B{^uM*q49KC3DJ)O!NWsabG+h*QdVVEm{jPRj1;$bUxj^^cA zh)1!Axrd0dULNKY3$kZzkiGi(%*w`d2iJrTpMr&yuWl?yzw2;*qAFPW==&oMh~iXY zOfS)|njfkg7k#>ZoyBU#l*6-IFVWgWdVI~y=n6)bDH>1TSR=>byLQ;oQJ+ zOzK2L-S^}Kj*U6NRNFrm^PnBC|@er1$yjztu8Kkf7GG2Pb}C~ zk}MqS^SXk$uE|Rade84U7La$-GXpX!933MuCPoF0eUm@bbA5?c)xcXmSHOGF?ao+Q zD-REul7$(FW^F_gtkk{jHI>7+wMqwJ_2bic<5XalaLvbnm$O~A%!2WB25rNQp}wTV zPD044h~CDEy)zh{fcJAAON|SVf$R@-|Kk|=e_nDQYxa`I#`-ts=}uPHMt2L35kZ^> z#tWiizjZ<Pk6f-1z6GTq1ZTSaFGmP0)`V=QYz) zs?3y`GPe6eT{#vh;M^Q%e*Q3=6u)`(l;~SwDe%J6Pj!$Ury%u@2t39Hkj0 zHd8faBek-@QBwG3DA2`EKgH}ZajIE;h4SMjzcgyAziJl4I9g*7>-aN%?O5efa%)*P>RlfC!P;M$OW8mI-2!emR*Ww9aO%9R! zm&R)6zMXH&N_Y~u-dh?HSRCuQ%SG0-C!9mh&u(^S7}3BCjgpC%S$8j8-QCJ%TA>=b zVL$j%m{q@&qXyr>!#pA80YC;$^N=fwQ>-Q)194Dq0@>&jM!7KAq71{zvW{eYSQ;QH z0EhL{CbZa@O==50<+_>@E2MIBK9p;2=+jhd;8VQ_Ctmx(A-W`9YiQ4|+s9Ajp z&J;<+5SJhpLP8u-C0a8^9gL!`L61KJdZp!VKT2vBaV=LnP!rRf--E!0evYk_@bS0N zXF&#-3a;Mt>W^W}G4sqWCz~Y^)ND#jQljA5sH0VLf^cJt?2TgVk(tkw;nOiFK}$EY zN{Jw#a+#2SJW~}E#LJN}^x>01cx^U6y{iXnc+J+6md&DUywN)(Lrhhq{02hf|bza=H8#=U=0TBowg4ri0bk}Ea`~NU1${hZ&-LytOeVZq8EhEH&8eLd zyvhU>NveI&Teeq1pdXGklKl{0>1rJauGwW{J9saIOM_{H!$>E>zui@yRG3^Z4e8Ww zwvnryk#g+O!`&6T#|iN3ZSUbwrD!ervJ`{+8o7;u+fai)= z2Xx@Ci`~EhsJ=gDBAN2JJjgx7Bh_1zM1ls1%{^@AE1ys?&0tkurK&;2*DDl0F^{dx zV@7vYu|A{5kFR1!Umcm2{vh*B2Np5>#L_8)U7);dOQOZMb(b_+YiK)EhtQBk@&v7p zB_uEWXh%^kshJ*E`trl!b@=MGM6q@3l`nHZDpT71WG^|5CfQzf=v+wf`Sg+&?KxVe zbf2UzhFJBuHqzkM%3|x}#K|Vm^ART&L{WY=wH5q7FR8!%@1r|?$AU`oGrX&1t1efZ zb2L|MoXu@>=xOr^SIJ<+SmVl3`5;Q2uu~7?!?D^0ZW9B3ZXSl!pWr(?>r=ZrCtiNb ze!EO5=*`-~l<#sz)&jMUF+s4HVrp|czwYM-Sal$vdW07a36&^*wo%%++o)<$Y`WkU z0(Y+h>L3nClA>Uw_0;3X9W7tcP?JMCaAai-Hurm>gx>7Y*Z$38b&2r!-0X|Y98uL~ zPcD?rjjY6?C>NxegX$;LtWYEKpL8vPQdtCFY<~^KxL;`ldN&_@^YD+O)krA!T%`Ze zkXILXeolHTynmD%PQpPhgE^*4$`&j*)w&;~7&4mT)oc{Q)onO8y1&Alf;^m^G_|+4 zIx-I7xjK$KMDU=X+}nJD+s943akuLGWgC5ApgED|Y*9t0;?A-!A~}?R>T}lKemJ_J zF67f8n(E;hbtfFA9A@^NIQ8-6Ly=@mo?%uUDdK(|5j44>nzYcr5 z)!wYky|nhA3tPW4Tr-Df7?N{{D<-)0wSlM$QaG>i5qJsEPgV*J*2%A~v`V)vFEk9S zU>pYzG3VeBsi}~UMIiYTrw~&o9f6)l)^Mgc$SqiMq@}QeO?JKc7QrkO_`Z+Z1?ghZ zjX73VRjJxpRgQQ&x8N%y(rdYx>kD}gOY(f9_@Ww($2(cU0fE9IJUs28uic+>!oq!r zMI3Dt+T4*E%z=HVf^GL2h3`*m*W*fJlbwdljuZGf+E(Otky{@UgSs@y$DCP|n-Ntt zi-Xdhq1Tu!Y*n;E^e1$hJvb4j*Th80j%WPRmObuJ4YP}qyd0L4&~FDsd^S5qhilgk zL*t&rq?C^3_f42{m2?;iw9Q%EU9_Qr%9jCwN~*QV?;V(HC=I271_W%cY?@M~-vH{Y z?_!tccn_#9fxtb*r)Ll>X)y~>@GFZ#NWKd&5tJ@BijY0-QWlKJ&b~ldm)bo6seRI^ zfegSn?l@jl-u+QIH@*0!T(Z=oWNW^O=SIeqR|d$-BqM0`Hl<|4he*Lru)>Xo&!6pg zX!G(Sdt!pcSli@USvApg3tPmh&T4dCW$Kowd=Ng(-+%d}eBkEi_kkmn(UBPRaMAa2|%uI8xMxl>wBX>2COg-|hR!qn(_0h_F zG14Vcw2_iOfwof|7(khG|&~x&3N(R_#GxsFWhsgejg6;zXJwL%0Ha zKPDcj?ibc+E4Ft_XjvS5WV(NiXZri89MLy^$%v)&O>e2-tcF|t5_xTak2y9C2QhKi z^IdUEz059s&Ka`nEklQ1L+-9k5@I>E+jX4UF=vfRJMP9y>TUc87Mos6cIzJOz9`oC zvvo`9=fMXE%zX;zfL_#LtaS;cCYt=9UBj$F!md15u##~gC}6RXHvcE)&d#J?&=uf< zq=0LEQPb5yn`R%D#1bfr7%Vzehy!X%)%Teug|E&H9c51M>x~q4{J9fMo{1}Ep$*@i zy&0rrC$CLBmboNmfpjD28c8^$Gb>FPhA^j(aFx*DBq*QhI;0_a*zL@q<;)UPBTGux zyP56;@XB7_GX5r{DZtRIT8mUWH9GEA76e?t+Lj8+BL={jL9(>DBw_$$)CsO`tP#0V z95og015#4BbaXhzDe(I(vdo9vCXtcA*)fcVEqyUoMD7fmFQe#-pUoELJ*8oJhswv0 zS;?cCb46~|jzu3$?S679VM{%&2i9x-2k$nsbyWB!8kTZ4 z6?cm1&=pdPy*-%;He>Aue+*3|ru9;^q2(&Z5=-9&-^uhN8ST8Hf8wvTq)_(`vxI}p ziVp44b{Q0dEbFTKavr(nE6V}uW%PPA*BZj7T^Rwn$Q`#XbmDE_WXaleiD`L-#sbqR zA9$4tF2f7-`W1&5Skm}1t3O%PCWT5y#Rnt2#&++Enl)T}#a`9UlM&!rH_-9qz;~9w z^nqVQ-2O0BjOcU40!k3$*+xps$VRCx5D5um*T}oi8h}aGYpT~MkN-}KNGPR+>U<=# zd7@a1g)6?EhsYdF0agb2-UKPH-T1ou-783dX;y?DsG|Y`0#=-{+ZwajWHHl$5n&-% zacs`z7_<=8oVg2!Pi*Vu<8$o!=Ryz6Su=%4^>z>YIA#m8l}{JO-_KNWrIqc8sbPCgp#pRBM_$xII_PjV&nOxFk*#5MxYo= zEh7{~F__pw;=12fUC&S1o*dnLKmulOJ5)vmwV->ZS&Q8>EIxi%za~XUgTu?=I$N{j zy?whG1Vo!fV8H#sp;_?IGm7YZS^}Un6sud@QJym2ybi0|T6|SH^SS+Qlh@A?uTlD! z<-hTo;;9OLZz84*^Y)*r9~K)q8_Vp9$uoU6ey@M~$nN9}cx5Ml2|T7^ao%TOW2tS> zz&->EQxwJA6;tB!?`JfZ8*?ugHmkYbWp?c{733o)wggP*BiDxsSi06(Y3!3jC8i@X zuX1|~q=(9bO4Hmhw-z=+s10I7(d9nnuVT0Bf4cAN)Orr{Vc5l(o*kho@9H%uY#O*U zG+vr%J*u3@3L4^BD>W`Nsj6`?f!D$ex%>zrN=+wHe3x&sUK3pfbnt8@J*!?0K>fqa ztlC!wBR5=B3Q)=sjHhJR3^_OGIJrNps(~X`_g*ZlD3;Qa&C0Bh*IecfH_biT1t#gq z3bq1;NDw=1ZVMx`($q7f`jV6+C(iM2_R^=PomrhqPZo2}INQROe7-Tc8!u}e29P%q zHe0aC!jpO!JJ#*S+Db4#F~)>lAAVnH>6y8#X9&EaZ)w&6>%LlOg7e;sfZ#*-%E z;KR@=d;m{v4ydMa<6i<&$q;yU=`uSYcMMu!`s4#!c}pY*yF z!Q}-%%AK)ov;imZ68^A;r(TGLiIV;LBb^JzOSjoc=l{-=9#gn;| z34$T@ghWd3gZM_g$Jvt7Cl@L>tBWSv-n;~+Bw(Mwc|j!CwKptvgL9vsVR>^<@}qiZ zg-7PeIvcB2qh+Ju5J%@gg-x@9=8{ei$^uYfH9Z<$=MLI=prW9zD&yCGcDS~0=El0& zkSE%JyC>oaDpV*Q@3k@KWZNM(T-R9tni_p4TieYnrx9zH&-NJvauKJ0jc*8RTq}|< z7C{6MGypAbntPUZICZ1<3}tb()M{*Wzyrqo-^y@)iF5!_X;NjdWY6ZxGHhk$jo8em zgjfA+6~xs+XeE5OGH@LQPju-OA8d#jORyBZmuJwC!kJ>#he+6)i2DqNR#Z3-Z8{w-Ub_G@;5$g?HMnkCjCg zJp`@yAn~;kj7z|1)-$}`&BGLxy&w-%Mzz%FQZU5sV^MAapGA~ok$taLGZa-RU0w6E`!n!+Ft@32 z;Qf1Bs3m=eMz9?)j8_Hk z?0hO&MFOI_8d-1m$%RaST;Q;h68<-eh}LTjoRT}&%B1%y2cQc9ETKU0b~3|1jeUTWIc+4d_t@6j?vLG9PZ zpVI=mp(H>z)I_;(WnYk#)EO)(C4Tu}c}3OSko1dd|vQ#iS2mkwZwE1OOKwFDsk^CJnICH%%t1aF)_$IrB6gm#<1hEdRy zea#gQA~zf2Xqd|sG*Owj1^^NuIw#Ggz6bcAw0NE)Qi{sgq5IY<=L3cuaQ-`)+aNcl zOagiJV5wmeLvR_*qkiY&E@%-}N&>4toCEAH*+E00ygt`kx1}bGrnpSwW$WaC%MmFz zdm5;vzk_qi);^(^59W8#^kN6lW;m5Tj5bNcl00CU*`3->Gt z-Q`S`_Dg8iPTbeOn}O3#?}LWPpc&PdMH(GJ;9V%4(?x)b}DA zUHDGC+_L!KuM9eP4lr{ut8qy?zejSO(Ryv240d)d1ti~X!$`NAbQnR2dxjgqh$5?8 zp&p?#0|^d@)EmBfaH0UsE*}@P*66^hqU`e`ju@!4pn=j%M zsMYPMnHp^=lg#?O45hG`J;nDA8g>oA9rPQazciamr}lNKsR_RX>2_HiK_I^{0BAXC z(6%Oa$KfD>_tTFKC%;Z|n$#E7*DKM#!LKB-^OskpWg_?Zn)iS5HIyOgYjVZf{ijPW zYa1R~inwx7kAa5$Rtg&OH%i10;{Cu^w;f*~oVRUyC8!+ToEY4{`p&%TFb{o-rFto>;VZ+%qL zyz4Xlrv&dDYt zzMXL_u>mMjIr(|@3}q}1ai7{-E-QXtL7nuMy7zxWCA0v~J!mfLroH9^Cr%%RvI_#6^{}AhKD}?- z!u`!&{kzSo0h?t{FBSEWzj{>wfbS3wnrQsof8&x`pcRdmg?s$*2jiILb=7<3bpil@ zr*CA$QM7CoEEy_Lt#^hET}UFdAKpuU349MOiG42X%73O@a$*54 z%ZNEX7lPX9NCtkf{7VD^+&kp=8+DPjno7{F-`3A3#&zChy(oi>6EB~CN5G&Q0@T{Z z-S9>2EEm>aHy}SRAYE^V*bkhN38Q9udox+^j1H_e2Pj0oijU$%E4PBeP381lqBe$N zJgpGP<6o6`d&F)=8S5&_T9}gbPj2MvrpFq<4HJ5#lbH^JTF$%Z(C&Tn3JeovHuUZm zghI~>_yKgh#%>ig#pTJ}Uyt;bPS}Ml!2MDEUoKcqzptk6{QisntXE6kJx7aP6!{I# zl9$F37~e*^7V5L}>dCHj|9rmp_De62hW%a&y5p~q`gbPc{b!YAQ6C>5c?ibizz3oi z!G-K!RdBI<;Aebd+lghM5o%Kf1q= z1aKBIvkWJ%>ixA;=r4NDow&yi{w52Ma0RH%CCAQ|@8dE8Qv2wc$zK-v_k0RyisgSJ zpGX4Ltii0GW&eICLHPkcF3^Vm=X?R6?ff?nybtO3(RN#ZXQ0f~m%LQqU?SP1jI*sf ztIl!a-{BVT>>l|&EbNpBuVC|v*ODWp}3e-7D<>dEhO-1fn*O~e5$hk3z_`CqfLH+_G77G?Y@!$*W0xoD~zby z1|agh4`(Cr`-P3lJ~-U_H-*jbp)v`fW`J(tyEMVoFNdm=(-cj-dskFfn;i;xRO zoOw9Q&vqVQ0m!jHYg#YM6oYRF5Css_-(w51_VPf&c)f?U?`L8Vyweo6dbHuO9(|1J4!J4J>JXioGBmveW zbZf8cgzj~n0sjzcQGqfZ2WWObpa7%!EP3Xc3a+E!MMH4W8cWT`h+4;s{F0?#wwnwS z0bb*lmxZBzGn`u0+j_~^$h**({K#*!ygL(}Ze$_UJuJSX$bHUr0O^+-&ZOMs9IbiH z{HJ-GyjGTQ{%m+t#~rHl>8jnF+h(Z=BB=wKj>Ue#{{-Op)5r-x-=D-8zousb@c3Hd zieUm8S%Zh6917*GTWR3c8pnyW=fd_p>61?^z?A7W7lp^izV87sJ6D@N+~tr$%)L_8 zoN-Fv!IzSXu|X_BSN-^S=SE_jz#Nxa>Tmws)L*i{zpywHnMP=9`_dKp_-faT)4NIu ze#b5+q6b5PX8dMJ+}Y9`Iibtg{F$`XSIBDvZ;=efq*3DaPiC84WG?OYCtmf9j^@U^ zsl(X&LvyX&O1Hdv3X=WqN2_|v?h Ld%Z;M!L$DXTAi`s literal 0 HcmV?d00001 diff --git a/docs/my-website/release_notes/v1.81.0/index.md b/docs/my-website/release_notes/v1.81.0/index.md index d69a62b6941..e61d7d2d593 100644 --- a/docs/my-website/release_notes/v1.81.0/index.md +++ b/docs/my-website/release_notes/v1.81.0/index.md @@ -1,5 +1,5 @@ --- -title: "v1.81.0 - Claude Code - Web Search Across All Providers" +title: "v1.81.0-stable - Claude Code - Web Search Across All Providers" slug: "v1-81-0" date: 2026-01-18T10:00:00 authors: @@ -27,7 +27,7 @@ import TabItem from '@theme/TabItem'; docker run \ -e STORE_MODEL_IN_DB=True \ -p 4000:4000 \ -docker.litellm.ai/berriai/litellm:v1.81.0.rc.1 +docker.litellm.ai/berriai/litellm:v1.81.0-stable ``` diff --git a/docs/my-website/release_notes/v1.81.3-stable/index.md b/docs/my-website/release_notes/v1.81.3-stable/index.md new file mode 100644 index 00000000000..22b6f43deef --- /dev/null +++ b/docs/my-website/release_notes/v1.81.3-stable/index.md @@ -0,0 +1,423 @@ +--- +title: "v1.81.3-stable - Performance - 25% CPU Usage Reduction" +slug: "v1-81-3" +date: 2026-01-26T10:00:00 +authors: + - name: Krrish Dholakia + title: CEO, LiteLLM + url: https://www.linkedin.com/in/krish-d/ + image_url: https://pbs.twimg.com/profile_images/1298587542745358340/DZv3Oj-h_400x400.jpg + - name: Ishaan Jaff + title: CTO, LiteLLM + url: https://www.linkedin.com/in/reffajnaahsi/ + image_url: https://pbs.twimg.com/profile_images/1613813310264340481/lz54oEiB_400x400.jpg +hide_table_of_contents: false +--- + +import Image from '@theme/IdealImage'; +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +## Deploy this version + + + + +``` showLineNumbers title="docker run litellm" +docker run \ +-e STORE_MODEL_IN_DB=True \ +-p 4000:4000 \ +docker.litellm.ai/berriai/litellm:v1.81.3.rc.2 +``` + + + + + +``` showLineNumbers title="pip install litellm" +pip install litellm==1.81.3.rc.2 +``` + + + + +--- + +## New Models / Updated Models + +### New Model Support + +| Provider | Model | Context Window | Input ($/1M tokens) | Output ($/1M tokens) | Deprecation Date | +| -------- | ----- | -------------- | ------------------- | -------------------- | ---------------- | +| OpenAI | `gpt-audio`, `gpt-audio-2025-08-28` | 128K | $32/1M audio tokens, $2.5/1M text tokens | $64/1M audio tokens, $10/1M text tokens | - | +| OpenAI | `gpt-audio-mini`, `gpt-audio-mini-2025-08-28` | 128K | $10/1M audio tokens, $0.6/1M text tokens | $20/1M audio tokens, $2.4/1M text tokens | - | +| Deepinfra, Vertex AI, Google AI Studio, OpenRouter, Vercel AI Gateway | `gemini-2.0-flash-001`, `gemini-2.0-flash` | - | - | - | 2026-03-31 | +| Groq | `openai/gpt-oss-120b` | 131K | 0.075/1M cache read | 0.6/1M output tokens | - | +| Groq | `groq/openai/gpt-oss-20b` | 131K | 0.0375/1M cache read, $0.075/1M text tokens | 0.3/1M output tokens | - | +| Vertex AI | `gemini-2.5-computer-use-preview-10-2025` | 128K | $1.25 | $10 | - | +| Azure AI | `claude-haiku-4-5` | $1.25/1M cache read, $2/1M cache read above 1 hr, $0.1/1M text tokens | $5/1M output tokens | - | +| Azure AI | `claude-sonnet-4-5` | $3.75/1M cache read, $6/1M cache read above 1 hr, $3/1M text tokens | $15/1M output tokens | - | +| Azure AI | `claude-opus-4-5` | $6.25/1M cache read, $10/1M cache read above 1 hr, $0.5/1M text tokens | $25/1M output tokens | - | +| Azure AI | `claude-opus-4-1` | $18.75/1M cache read, $30/1M cache read above 1 hr, $1.5/1M text tokens | $75/1M output tokens | - | + +### Features + +- **[OpenAI](../../docs/providers/openai)** + - Add gpt-audio and gpt-audio-mini models to pricing - [PR #19509](https://github.com/BerriAI/litellm/pull/19509) + - correct audio token costs for gpt-4o-audio-preview models - [PR #19500](https://github.com/BerriAI/litellm/pull/19500) + - Limit stop sequence as per openai spec (ensures JetBrains IDE compatibility) - [PR #19562](https://github.com/BerriAI/litellm/pull/19562) + +- **[VertexAI](../../docs/providers/vertex)** + - Docs - Google Workload Identity Federation (WIF) support - [PR #19320](https://github.com/BerriAI/litellm/pull/19320) + +- **[Agentcore](../../docs/providers/bedrock_agentcore)** + - Fixes streaming issues with AWS Bedrock AgentCore where responses would stop after the first chunk, particularly affecting OAuth-enabled agents - [PR #17141](https://github.com/BerriAI/litellm/pull/17141) + +- **[Chatgpt](../../docs/providers/chatgpt)** + - Adds support for calling chatgpt subscription via LiteLLM - [PR #19030](https://github.com/BerriAI/litellm/pull/19030) + - Adds responses API bridge support for chatgpt subscription provider - [PR #19030](https://github.com/BerriAI/litellm/pull/19030) + +- **[Bedrock](../../docs/providers/bedrock)** + - support for output format for bedrock invoke via v1/messages - [PR #19560](https://github.com/BerriAI/litellm/pull/19560) + +- **[Azure](../../docs/providers/azure/azure)** + - Add support for Azure OpenAI v1 API - [PR #19313](https://github.com/BerriAI/litellm/pull/19313) + - preserve content_policy_violation details for images (#19328) - [PR #19372](https://github.com/BerriAI/litellm/pull/19372) + - Support OpenAI-format nested tool definitions for Responses API - [PR #19526](https://github.com/BerriAI/litellm/pull/19526) + +- **Gemini([Vertex AI](../../docs/providers/vertex), [Google AI Studio](../../docs/providers/gemini))** + - use responseJsonSchema for Gemini 2.0+ models - [PR #19314](https://github.com/BerriAI/litellm/pull/19314) + +- **[Volcengine](../../docs/providers/volcano)** + - Support Volcengine responses api - [PR #18508](https://github.com/BerriAI/litellm/pull/18508) + +- **[Anthropic](../../docs/providers/anthropic)** + - Add Support for calling Claude Code Max subscriptions via LiteLLM - [PR #19453](https://github.com/BerriAI/litellm/pull/19453) + - Add Structured output for /v1/messages with Anthropic API, Azure Anthropic API, Bedrock Converse - [PR #19545](https://github.com/BerriAI/litellm/pull/19545) + +- **[Brave Search](../../docs/search/brave)** + - New Search provider - [PR #19433](https://github.com/BerriAI/litellm/pull/19433) + +- **Sarvam ai** + - Add support for new sarvam models - [PR #19479](https://github.com/BerriAI/litellm/pull/19479) + +- **[GMI](../../docs/providers/gmi)** + - add GMI Cloud provider support - [PR #19376](https://github.com/BerriAI/litellm/pull/19376) + + +### Bug Fixes + +- **[Anthropic](../../docs/providers/anthropic)** + - Fix anthropic-beta sent client side being overridden instead of appended to - [PR #19343](https://github.com/BerriAI/litellm/pull/19343) + - Filter out unsupported fields from JSON schema for Anthropic's output_format API - [PR #19482](https://github.com/BerriAI/litellm/pull/19482) + +- **[Bedrock](../../docs/providers/bedrock)** + - Expose stability models via /image_edits endpoint and ensure proper request transformation - [PR #19323](https://github.com/BerriAI/litellm/pull/19323) + - Claude Code x Bedrock Invoke fails with advanced-tool-use-2025-11-20 - [PR #19373](https://github.com/BerriAI/litellm/pull/19373) + - deduplicate tool calls in assistant history - [PR #19324](https://github.com/BerriAI/litellm/pull/19324) + - fix: correct us.anthropic.claude-opus-4-5 In-region pricing - [PR #19310](https://github.com/BerriAI/litellm/pull/19310) + - Fix request validation errors when using Claude 4 via bedrock invoke - [PR #19381](https://github.com/BerriAI/litellm/pull/19381) + - Handle thinking with tool calls for Claude 4 models - [PR #19506](https://github.com/BerriAI/litellm/pull/19506) + - correct streaming choice index for tool calls - [PR #19506](https://github.com/BerriAI/litellm/pull/19506) + +- **[Ollama](../../docs/providers/ollama)** + - Fix tool call errors due with improved message extraction - [PR #19369](https://github.com/BerriAI/litellm/pull/19369) + +- **[VertexAI](../../docs/providers/vertex)** + - Removed optional vertex_count_tokens_location param before request is sent to vertex - [PR #19359](https://github.com/BerriAI/litellm/pull/19359) + +- **Gemini([Vertex AI](../../docs/providers/vertex), [Google AI Studio](../../docs/providers/gemini))** + - Supports setting media_resolution and fps parameters on each video file, when using Gemini video understanding - [PR #19273](https://github.com/BerriAI/litellm/pull/19273) + - handle reasoning_effort as dict from OpenAI Agents SDK - [PR #19419](https://github.com/BerriAI/litellm/pull/19419) + - add file content support in tool results - [PR #19416](https://github.com/BerriAI/litellm/pull/19416) + +- **[Azure](../../docs/providers/azure_ai)** + - Fix Azure AI costs for Anthropic models - [PR #19530](https://github.com/BerriAI/litellm/pull/19530) + +- **[Giga Chat](../../docs/providers/gigachat)** + - Add tool choice mapping - [PR #19645](https://github.com/BerriAI/litellm/pull/19645) +--- + +## AI API Endpoints (LLMs, MCP, Agents) + +### Features + +- **[Files API](../../docs/files_endpoints)** + - Add managed files support when load_balancing is True - [PR #19338](https://github.com/BerriAI/litellm/pull/19338) + +- **[Claude Plugin Marketplace](../../docs/tutorials/claude_code_plugin_marketplace)** + - Add self hosted Claude Code Plugin Marketplace - [PR #19378](https://github.com/BerriAI/litellm/pull/19378) + +- **[MCP](../../docs/mcp)** + - Add MCP Protocol version 2025-11-25 support - [PR #19379](https://github.com/BerriAI/litellm/pull/19379) + - Log MCP tool calls and list tools in the LiteLLM Spend Logs table for easier debugging - [PR #19469](https://github.com/BerriAI/litellm/pull/19469) + +- **[Vertex AI](../../docs/providers/vertex)** + - Ensure only anthropic betas are forwarded down to LLM API (by default) - [PR #19542](https://github.com/BerriAI/litellm/pull/19542) + - Allow overriding to support forwarding incoming headers are forwarded down to target - [PR #19524](https://github.com/BerriAI/litellm/pull/19524) + +- **[Chat/Completions](../../docs/completion/input)** + - Add MCP tools response to chat completions - [PR #19552](https://github.com/BerriAI/litellm/pull/19552) + - Add custom vertex ai finish reasons to the output - [PR #19558](https://github.com/BerriAI/litellm/pull/19558) + - Return MCP execution in /chat/completions before model output during streaming - [PR #19623](https://github.com/BerriAI/litellm/pull/19623) + +### Bugs + +- **[Responses API](../../docs/response_api)** + - Fix duplicate messages during MCP streaming tool execution - [PR #19317](https://github.com/BerriAI/litellm/pull/19317) + - Fix pickle error when using OpenAI's Responses API with stream=True and tool_choice of type allowed_tools (an OpenAI-native parameter) - [PR #17205](https://github.com/BerriAI/litellm/pull/17205) + - stream tool call events for non-openai models - [PR #19368](https://github.com/BerriAI/litellm/pull/19368) + - preserve tool output ordering for gemini in responses bridge - [PR #19360](https://github.com/BerriAI/litellm/pull/19360) + - Add ID caching to prevent ID mismatch text-start and text-delta - [PR #19390](https://github.com/BerriAI/litellm/pull/19390) + - Include output_item, reasoning_summary_Text_done and reasoning_summary_part_done events for non-openai models - [PR #19472](https://github.com/BerriAI/litellm/pull/19472) + +- **[Chat/Completions](../../docs/completion/input)** + - fix: drop_params not dropping prompt_cache_key for non-OpenAI providers - [PR #19346](https://github.com/BerriAI/litellm/pull/19346) + +- **[Realtime API](../../docs/realtime)** + - disable SSL for ws:// WebSocket connections - [PR #19345](https://github.com/BerriAI/litellm/pull/19345) + +- **[Generate Content](../../docs/generateContent)** + - Log actual user input when google genai/vertex endpoints are called client-side - [PR #19156](https://github.com/BerriAI/litellm/pull/19156) + +- **[/messages/count_tokens Anthropic Token Counting](../../docs/anthropic_count_tokens)** + - ensure it works for Anthropic, Azure AI Anthropic on AI Gateway - [PR #19432](https://github.com/BerriAI/litellm/pull/19432) + +- **[MCP](../../docs/mcp)** + - forward static_headers to MCP servers - [PR #19366](https://github.com/BerriAI/litellm/pull/19366) + +- **[Batch API](../../docs/batches)** + - Fix: generation config empty for batch - [PR #19556](https://github.com/BerriAI/litellm/pull/19556) + +- **[Pass Through Endpoints](../../docs/proxy/pass_through)** + - Always reupdate registry - [PR #19420](https://github.com/BerriAI/litellm/pull/19420) +--- + +## Management Endpoints / UI + +### Features + +- **Cost Estimator** + - Fix model dropdown - [PR #19529](https://github.com/BerriAI/litellm/pull/19529) + +- **Claude Code Plugins** + - Allow Adding Claude Code Plugins via UI - [PR #19387](https://github.com/BerriAI/litellm/pull/19387) + +- **Guardrails** + - New Policy management UI - [PR #19668](https://github.com/BerriAI/litellm/pull/19668) + - Allow adding policies on Keys/Teams + Viewing on Info panels - [PR #19688](https://github.com/BerriAI/litellm/pull/19688) + +- **General** + - respects custom authentication header override - [PR #19276](https://github.com/BerriAI/litellm/pull/19276) + +- **Playground** + - Button to Fill Custom API Base - [PR #19440](https://github.com/BerriAI/litellm/pull/19440) + - display mcp output on the play ground - [PR #19553](https://github.com/BerriAI/litellm/pull/19553) + +- **Models** + - Paginate /v2/models/info - [PR #19521](https://github.com/BerriAI/litellm/pull/19521) + - All Model Tab Pagination - [PR #19525](https://github.com/BerriAI/litellm/pull/19525) + - Adding Optional scope Param to /models - [PR #19539](https://github.com/BerriAI/litellm/pull/19539) + - Model Search - [PR #19622](https://github.com/BerriAI/litellm/pull/19622) + - Filter by Model ID and Team ID - [PR #19713](https://github.com/BerriAI/litellm/pull/19713) + +- **MCP Servers** + - MCP Tools Tab Resetting to Overview - [PR #19468](https://github.com/BerriAI/litellm/pull/19468) + +- **Organizations** + - Prevent org admin from creating a new user with proxy_admin permissions - [PR #19296](https://github.com/BerriAI/litellm/pull/19296) + - Edit Page: Reusable Model Select - [PR #19601](https://github.com/BerriAI/litellm/pull/19601) + +- **Teams** + - Reusable Model Select - [PR #19543](https://github.com/BerriAI/litellm/pull/19543) + - [Fix] Team Update with Organization having All Proxy Models - [PR #19604](https://github.com/BerriAI/litellm/pull/19604) + +- **Logs** + - Include tool arguments in spend logs table - [PR #19640](https://github.com/BerriAI/litellm/pull/19640) + +- **Fallbacks / Loadbalancing** + - New fallbacks modal - [PR #19673](https://github.com/BerriAI/litellm/pull/19673) + - Set fallbacks/loadbalancing by team/key - [PR #19686](https://github.com/BerriAI/litellm/pull/19686) + +### Bugs + +- **Playground** + - increase model selector width in playground Compare view - [PR #19423](https://github.com/BerriAI/litellm/pull/19423) + +- **Virtual Keys** + - Sorting Shows Incorrect Entries - [PR #19534](https://github.com/BerriAI/litellm/pull/19534) + +- **General** + - UI 404 error when SERVER_ROOT_PATH is set - [PR #19467](https://github.com/BerriAI/litellm/pull/19467) + - Redirect to ui/login on expired JWT - [PR #19687](https://github.com/BerriAI/litellm/pull/19687) + +- **SSO** + - Fix SSO user roles not updating for existing users - [PR #19621](https://github.com/BerriAI/litellm/pull/19621) + +- **Guardrails** + - ensure guardrail patterns persist on edit and mode toggle - [PR #19265](https://github.com/BerriAI/litellm/pull/19265) +--- + +## AI Integrations + +### Logging + +- **General Logging** + - prevent printing duplicate StandardLoggingPayload logs - [PR #19325](https://github.com/BerriAI/litellm/pull/19325) + - Fix: log duplication when json_logs is enabled - [PR #19705](https://github.com/BerriAI/litellm/pull/19705) +- **Langfuse OTEL** + - ignore service logs and fix callback shadowing - [PR #19298](https://github.com/BerriAI/litellm/pull/19298) +- **Langfuse** + - Send litellm_trace_id - [PR #19528](https://github.com/BerriAI/litellm/pull/19528) + - Add Langfuse mock mode for testing without API calls - [PR #19676](https://github.com/BerriAI/litellm/pull/19676) +- **GCS Bucket** + - prevent unbounded queue growth due to slow API calls - [PR #19297](https://github.com/BerriAI/litellm/pull/19297) + - Add GCS mock mode for testing without API calls - [PR #19683](https://github.com/BerriAI/litellm/pull/19683) +- **Responses API Logging** + - Fix pydantic serialization error - [PR #19486](https://github.com/BerriAI/litellm/pull/19486) +- **Arize Phoenix** + - add openinference span kinds to arize phoenix - [PR #19267](https://github.com/BerriAI/litellm/pull/19267) +- **Prometheus** + - Added new prometheus metrics for user count and team count - [PR #19520](https://github.com/BerriAI/litellm/pull/19520) + +### Guardrails + +- **Bedrock Guardrails** + - Ensure post_call guardrail checks input+output - [PR #19151](https://github.com/BerriAI/litellm/pull/19151) +- **Prompt Security** + - fixing prompt-security's guardrail implementation - [PR #19374](https://github.com/BerriAI/litellm/pull/19374) +- **Presidio** + - Fixes crash in Presidio Guardrail when running in background threads (logging_hook) - [PR #19714](https://github.com/BerriAI/litellm/pull/19714) +- **Pillar Security** + - Migrate Pillar Security to Generic Guardrail API - [PR #19364](https://github.com/BerriAI/litellm/pull/19364) +- **Policy Engine** + - New LiteLLM Policy engine - create policies to manage guardrails, conditions - permissions per Key, Team - [PR #19612](https://github.com/BerriAI/litellm/pull/19612) +- **General** + - add case-insensitive support for guardrail mode and actions - [PR #19480](https://github.com/BerriAI/litellm/pull/19480) + +### Prompt Management + +- **General** + - fix prompt info lookup and delete using correct IDs - [PR #19358](https://github.com/BerriAI/litellm/pull/19358) + +### Secret Manager + +- **AWS Secret Manager** + - ensure auto-rotation updates existing AWS secret instead of creating new one - [PR #19455](https://github.com/BerriAI/litellm/pull/19455) +- **Hashicorp Vault** + - Ensure key rotations work with Vault - [PR #19634](https://github.com/BerriAI/litellm/pull/19634) + +--- + +## Spend Tracking, Budgets and Rate Limiting + +- **Pricing Updates** + - Add openai/dall-e base pricing entries - [PR #19133](https://github.com/BerriAI/litellm/pull/19133) + - Add `input_cost_per_video_per_second` in ModelInfoBase - [PR #19398](https://github.com/BerriAI/litellm/pull/19398) + +--- + +## Performance / Loadbalancing / Reliability improvements + + +- **General** + - Fix date overflow/division by zero in proxy utils - [PR #19527](https://github.com/BerriAI/litellm/pull/19527) + - Fix in-flight request termination on SIGTERM when health-check runs in a separate process - [PR #19427](https://github.com/BerriAI/litellm/pull/19427) + - Fix Pass through routes to work with server root path - [PR #19383](https://github.com/BerriAI/litellm/pull/19383) + - Fix logging error for stop iteration - [PR #19649](https://github.com/BerriAI/litellm/pull/19649) + - prevent retrying 4xx client errors - [PR #19275](https://github.com/BerriAI/litellm/pull/19275) + - add better error handling for misconfig on health check - [PR #19441](https://github.com/BerriAI/litellm/pull/19441) + +- **Router** + - Fix Azure RPM calculation formula - [PR #19513](https://github.com/BerriAI/litellm/pull/19513) + - Persist scheduler request queue to redis - [PR #19304](https://github.com/BerriAI/litellm/pull/19304) + - pass search_tools to Router during DB-triggered initialization - [PR #19388](https://github.com/BerriAI/litellm/pull/19388) + - Fixed PromptCachingCache to correctly handle messages where cache_control is a sibling key of string content - [PR #19266](https://github.com/BerriAI/litellm/pull/19266) + +- **Memory Leaks/OOM** + - prevent OOM with nested $defs in tool schemas - [PR #19112](https://github.com/BerriAI/litellm/pull/19112) + - fix: HTTP client memory leaks in Presidio, OpenAI, and Gemini - [PR #19190](https://github.com/BerriAI/litellm/pull/19190) + +- **Non root** + - fix logfile and pidfile of supervisor for non root environment - [PR #17267](https://github.com/BerriAI/litellm/pull/17267) + - resolve Read-only file system error in non-root images - [PR #19449](https://github.com/BerriAI/litellm/pull/19449) + +- **Dockerfile** + - Redis Semantic Caching - add missing redisvl dependency to requirements.txt - [PR #19417](https://github.com/BerriAI/litellm/pull/19417) + - Bump OTEL versions to support a2a dependency - resolves modulenotfounderror for Microsoft Agents by @Harshit28j in #18991 + +- **DB** + - Handle PostgreSQL cached plan errors during rolling deployments - [PR #19424](https://github.com/BerriAI/litellm/pull/19424) + +- **Timeouts** + - Fix: total timeout is not respected - [PR #19389](https://github.com/BerriAI/litellm/pull/19389) + +- **SDK** + - Field-Existence Checks to Type Classes to Prevent Attribute Errors - [PR #18321](https://github.com/BerriAI/litellm/pull/18321) + - add google-cloud-aiplatform as optional dependency with clear error message - [PR #19437](https://github.com/BerriAI/litellm/pull/19437) + - Make grpc dependency optional - [PR #19447](https://github.com/BerriAI/litellm/pull/19447) + - Add support for retry policies - [PR #19645](https://github.com/BerriAI/litellm/pull/19645) + +- **Performance** + - Cut chat_completion latency by ~21% by reducing pre-call processing time - [PR #19535](https://github.com/BerriAI/litellm/pull/19535) + - Optimize strip_trailing_slash with O(1) index check - [PR #19679](https://github.com/BerriAI/litellm/pull/19679) + - Optimize use_custom_pricing_for_model with set intersection - [PR #19677](https://github.com/BerriAI/litellm/pull/19677) + - perf: skip pattern_router.route() for non-wildcard models - [PR #19664](https://github.com/BerriAI/litellm/pull/19664) + - perf: Add LRU caching to get_model_info for faster cost lookups - [PR #19606](https://github.com/BerriAI/litellm/pull/19606) + +--- + +## General Proxy Improvements + +### Doc Improvements + - new tutorial for adding MCPs to Cursor via LiteLLM - [PR #19317](https://github.com/BerriAI/litellm/pull/19317) + - fix vertex_region to vertex_location in Vertex AI pass-through docs - [PR #19380](https://github.com/BerriAI/litellm/pull/19380) + - clarify Gemini and Vertex AI model prefix in json file - [PR #19443](https://github.com/BerriAI/litellm/pull/19443) + - update Claude Code integration guides - [PR #19415](https://github.com/BerriAI/litellm/pull/19415) + - adjust opencode tutorial - [PR #19605](https://github.com/BerriAI/litellm/pull/19605) + - add spend-queue-troubleshooting docs - [PR #19659](https://github.com/BerriAI/litellm/pull/19659) + - docs: add litellm-enterprise requirement for managed files - [PR #19689](https://github.com/BerriAI/litellm/pull/19689) + +### Helm + - Add support for keda in helm chart - [PR #19337](https://github.com/BerriAI/litellm/pull/19337) + - sync Helm chart version with LiteLLM release version - [PR #19438](https://github.com/BerriAI/litellm/pull/19438) + - Enable PreStop hook configuration in values.yaml - [PR #19613](https://github.com/BerriAI/litellm/pull/19613) + +### General + - Add health check scripts and parallel execution support - [PR #19295](https://github.com/BerriAI/litellm/pull/19295) + + +--- + +## New Contributors + + +* @dushyantzz made their first contribution in [PR #19158](https://github.com/BerriAI/litellm/pull/19158) +* @obod-mpw made their first contribution in [PR #19133](https://github.com/BerriAI/litellm/pull/19133) +* @msexxeta made their first contribution in [PR #19030](https://github.com/BerriAI/litellm/pull/19030) +* @rsicart made their first contribution in [PR #19337](https://github.com/BerriAI/litellm/pull/19337) +* @cluebbehusen made their first contribution in [PR #19311](https://github.com/BerriAI/litellm/pull/19311) +* @Lucky-Lodhi2004 made their first contribution in [PR #19315](https://github.com/BerriAI/litellm/pull/19315) +* @binbandit made their first contribution in [PR #19324](https://github.com/BerriAI/litellm/pull/19324) +* @flex-myeonghyeon made their first contribution in [PR #19381](https://github.com/BerriAI/litellm/pull/19381) +* @Lrakotoson made their first contribution in [PR #18321](https://github.com/BerriAI/litellm/pull/18321) +* @bensi94 made their first contribution in [PR #18787](https://github.com/BerriAI/litellm/pull/18787) +* @victorigualada made their first contribution in [PR #19368](https://github.com/BerriAI/litellm/pull/19368) +* @VedantMadane made their first contribution in #19266 +* @stiyyagura0901 made their first contribution in #19276 +* @kamilio made their first contribution in [PR #19447](https://github.com/BerriAI/litellm/pull/19447) +* @jonathansampson made their first contribution in [PR #19433](https://github.com/BerriAI/litellm/pull/19433) +* @rynecarbone made their first contribution in [PR #19416](https://github.com/BerriAI/litellm/pull/19416) +* @jayy-77 made their first contribution in #19366 +* @davida-ps made their first contribution in [PR #19374](https://github.com/BerriAI/litellm/pull/19374) +* @joaodinissf made their first contribution in [PR #19506](https://github.com/BerriAI/litellm/pull/19506) +* @ecao310 made their first contribution in [PR #19520](https://github.com/BerriAI/litellm/pull/19520) +* @mpcusack-altos made their first contribution in [PR #19577](https://github.com/BerriAI/litellm/pull/19577) +* @milan-berri made their first contribution in [PR #19602](https://github.com/BerriAI/litellm/pull/19602) +* @xqe2011 made their first contribution in #19621 + +--- + +## Full Changelog + +**[View complete changelog on GitHub](https://github.com/BerriAI/litellm/releases/tag/v1.81.3.rc)** diff --git a/docs/my-website/release_notes/v1.81.6.md b/docs/my-website/release_notes/v1.81.6.md new file mode 100644 index 00000000000..ef19276f2cf --- /dev/null +++ b/docs/my-website/release_notes/v1.81.6.md @@ -0,0 +1,384 @@ +--- +title: "v1.81.6 - Logs v2 with Tool Call Tracing" +slug: "v1-81-6" +date: 2026-01-31T00:00:00 +authors: + - name: Krrish Dholakia + title: CEO, LiteLLM + url: https://www.linkedin.com/in/krish-d/ + image_url: https://pbs.twimg.com/profile_images/1298587542745358340/DZv3Oj-h_400x400.jpg + - name: Ishaan Jaff + title: CTO, LiteLLM + url: https://www.linkedin.com/in/reffajnaahsi/ + image_url: https://pbs.twimg.com/profile_images/1613813310264340481/lz54oEiB_400x400.jpg +hide_table_of_contents: false +--- + +## Deploy this version + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; +import Image from '@theme/IdealImage'; + + + + +```bash +docker run \ +-e STORE_MODEL_IN_DB=True \ +-p 4000:4000 \ +docker.litellm.ai/berriai/litellm:main-v1.81.6 +``` + + + + +```bash +pip install litellm==1.81.6 +``` + + + + +## Key Highlights + +Logs View v2 with Tool Call Tracing - Redesigned logs interface with side panel, structured tool visualization, and error message search for faster debugging. + +Let's dive in. + +### Logs View v2 with Tool Call Tracing + +This release introduces comprehensive tool call tracing through LiteLLM's redesigned Logs View v2, enabling developers to debug and monitor AI agent workflows in production environments seamlessly. + +This means you can now onboard use cases like tracing complex multi-step agent interactions, debugging tool execution failures, and monitoring MCP server calls while maintaining full visibility into request/response payloads with syntax highlighting. + +Developers can access the new Logs View through LiteLLM's UI to inspect tool calls in structured format, search logs by error messages or request patterns, and correlate agent activities across sessions with collapsible side panel views. + +{/* TODO: Add image from Slack (group_7219.png) - save as logs_v2_tool_tracing.png */} +{/* */} + +[Get Started](../../docs/proxy/ui_logs) + +## New Models / Updated Models + +#### New Model Support + +| Provider | Model | Context Window | Input ($/1M tokens) | Output ($/1M tokens) | Features | +| -------- | ----- | -------------- | ------------------- | -------------------- | -------- | +| AWS Bedrock | `amazon.nova-2-pro-preview-20251202-v1:0` | 1M | $2.19 | $17.50 | Chat completions, vision, video, PDF, function calling, prompt caching, reasoning | +| Google Vertex AI | `gemini-robotics-er-1.5-preview` | 1M | $0.30 | $2.50 | Chat completions, multimodal (text, image, video, audio), function calling, reasoning | +| OpenRouter | `openrouter/xiaomi/mimo-v2-flash` | 262K | $0.09 | $0.29 | Chat completions, function calling, reasoning | +| OpenRouter | `openrouter/moonshotai/kimi-k2.5` | - | - | - | Chat completions | +| OpenRouter | `openrouter/z-ai/glm-4.7` | 202K | $0.40 | $1.50 | Chat completions, vision, function calling, reasoning | + +#### Features + +- **[AWS Bedrock](../../docs/providers/bedrock)** + - Messages API Bedrock Converse caching and PDF support - [PR #19785](https://github.com/BerriAI/litellm/pull/19785) + - Translate advanced-tool-use to Bedrock-specific headers for Claude Opus 4.5 - [PR #19841](https://github.com/BerriAI/litellm/pull/19841) + - Support tool search header translation for Sonnet 4.5 - [PR #19871](https://github.com/BerriAI/litellm/pull/19871) + - Filter unsupported beta headers for AWS Bedrock Invoke API - [PR #19877](https://github.com/BerriAI/litellm/pull/19877) + - Nova grounding improvements - [PR #19598](https://github.com/BerriAI/litellm/pull/19598), [PR #20159](https://github.com/BerriAI/litellm/pull/20159) + +- **[Anthropic](../../docs/providers/anthropic)** + - Remove explicit cache_control null in tool_result content - [PR #19919](https://github.com/BerriAI/litellm/pull/19919) + - Fix tool handling - [PR #19805](https://github.com/BerriAI/litellm/pull/19805) + +- **[Google Gemini / Vertex AI](../../docs/providers/gemini)** + - Add Gemini Robotics-ER 1.5 preview support - [PR #19845](https://github.com/BerriAI/litellm/pull/19845) + - Support file retrieval in GoogleAIStudioFilesHandle - [PR #20018](https://github.com/BerriAI/litellm/pull/20018) + - Add /delete endpoint support - [PR #20055](https://github.com/BerriAI/litellm/pull/20055) + - Add custom_llm_provider as gemini translation - [PR #19988](https://github.com/BerriAI/litellm/pull/19988) + - Subtract implicit cached tokens from text_tokens for correct cost calculation - [PR #19775](https://github.com/BerriAI/litellm/pull/19775) + - Remove unsupported prompt-caching-scope-2026-01-05 header for vertex ai - [PR #20058](https://github.com/BerriAI/litellm/pull/20058) + - Add disable flag for anthropic gemini cache translation - [PR #20052](https://github.com/BerriAI/litellm/pull/20052) + - Convert image URLs to base64 in tool messages for Anthropic on Vertex AI - [PR #19896](https://github.com/BerriAI/litellm/pull/19896) + +- **[xAI](../../docs/providers/xai)** + - Add grok reasoning content support - [PR #19850](https://github.com/BerriAI/litellm/pull/19850) + - Add websearch params support for Responses API - [PR #19915](https://github.com/BerriAI/litellm/pull/19915) + - Add routing of xai chat completions to responses when web search options is present - [PR #20051](https://github.com/BerriAI/litellm/pull/20051) + - Correct cached token cost calculation - [PR #19772](https://github.com/BerriAI/litellm/pull/19772) + +- **[Azure OpenAI](../../docs/providers/azure)** + - Use generic cost calculator for audio token pricing - [PR #19771](https://github.com/BerriAI/litellm/pull/19771) + - Allow tool_choice for Azure GPT-5 chat models - [PR #19813](https://github.com/BerriAI/litellm/pull/19813) + - Set gpt-5.2-codex mode to responses for Azure and OpenRouter - [PR #19770](https://github.com/BerriAI/litellm/pull/19770) + +- **[OpenAI](../../docs/providers/openai)** + - Fix max_input_tokens for gpt-5.2-codex - [PR #20009](https://github.com/BerriAI/litellm/pull/20009) + - Fix gpt-image-1.5 cost calculation not including output image tokens - [PR #19515](https://github.com/BerriAI/litellm/pull/19515) + +- **[Hosted VLLM](../../docs/providers/vllm)** + - Support thinking parameter in anthropic_messages() and .completion() - [PR #19787](https://github.com/BerriAI/litellm/pull/19787) + - Route through base_llm_http_handler to support ssl_verify - [PR #19893](https://github.com/BerriAI/litellm/pull/19893) + - Fix vllm embedding format - [PR #20056](https://github.com/BerriAI/litellm/pull/20056) + +- **[OCI GenAI](../../docs/providers/oci)** + - Serialize imageUrl as object for OCI GenAI API - [PR #19661](https://github.com/BerriAI/litellm/pull/19661) + +- **[Volcengine](../../docs/providers/volcano)** + - Add context for volcengine models (deepseek-v3-2, glm-4-7, kimi-k2-thinking) - [PR #19335](https://github.com/BerriAI/litellm/pull/19335) + +- **[Chinese Providers](../../docs/providers/)** + - Add prompt caching and reasoning support for MiniMax, GLM, Xiaomi - [PR #19924](https://github.com/BerriAI/litellm/pull/19924) + +- **[Vercel AI Gateway](../../docs/providers/vercel_ai_gateway)** + - Add embeddings support - [PR #19660](https://github.com/BerriAI/litellm/pull/19660) + +### Bug Fixes + +- **[Google](../../docs/providers/gemini)** + - Fix gemini-robotics-er-1.5-preview entry - [PR #19974](https://github.com/BerriAI/litellm/pull/19974) + +- **General** + - Fix output_tokens_details.reasoning_tokens None - [PR #19914](https://github.com/BerriAI/litellm/pull/19914) + - Fix stream_chunk_builder to preserve images from streaming chunks - [PR #19654](https://github.com/BerriAI/litellm/pull/19654) + - Fix aspectRatio mapping in image edit - [PR #20053](https://github.com/BerriAI/litellm/pull/20053) + - Handle unknown models in Azure AI cost calculator - [PR #20150](https://github.com/BerriAI/litellm/pull/20150) + +- **[GigaChat](../../docs/providers/gigachat)** + - Ensure function content is valid JSON - [PR #19232](https://github.com/BerriAI/litellm/pull/19232) + +## LLM API Endpoints + +#### Features + +- **[Messages API (/messages)](../../docs/mcp)** + - Add LiteLLM x Claude Agent SDK Integration - [PR #20035](https://github.com/BerriAI/litellm/pull/20035) + +- **[A2A / MCP Gateway API (/a2a, /mcp)](../../docs/mcp)** + - Add A2A agent header-based context propagation support - [PR #19504](https://github.com/BerriAI/litellm/pull/19504) + - Enable progress notifications for MCP tool calls - [PR #19809](https://github.com/BerriAI/litellm/pull/19809) + - Fix support for non-standard MCP URL patterns - [PR #19738](https://github.com/BerriAI/litellm/pull/19738) + - Add backward compatibility for legacy A2A card formats (/.well-known/agent.json) - [PR #19949](https://github.com/BerriAI/litellm/pull/19949) + - Add support for agent parameter in /interactions endpoint - [PR #19866](https://github.com/BerriAI/litellm/pull/19866) + +- **[Responses API (/responses)](../../docs/response_api)** + - Fix custom_llm_provider for provider-specific params - [PR #19798](https://github.com/BerriAI/litellm/pull/19798) + - Extract input tokens details as dict in ResponseAPILoggingUtils - [PR #20046](https://github.com/BerriAI/litellm/pull/20046) + +- **[Batch API (/batches)](../../docs/batches)** + - Fix /batches to return encoded ids (from managed objects table) - [PR #19040](https://github.com/BerriAI/litellm/pull/19040) + - Fix Batch and File user level permissions - [PR #19981](https://github.com/BerriAI/litellm/pull/19981) + - Add cost tracking and usage object in retrieve_batch call type - [PR #19986](https://github.com/BerriAI/litellm/pull/19986) + +- **[Embeddings API (/embeddings)](../../docs/embedding/supported_embedding)** + - Add supported input formats documentation - [PR #20073](https://github.com/BerriAI/litellm/pull/20073) + +- **[RAG API (/rag/ingest, /vector_store)](../../docs/rag_ingest)** + - Add UI for /rag/ingest API - Upload docs, pdfs etc to create vector stores - [PR #19822](https://github.com/BerriAI/litellm/pull/19822) + - Add support for using S3 Vectors as Vector Store Provider - [PR #19888](https://github.com/BerriAI/litellm/pull/19888) + - Add s3_vectors as provider on /vector_store/search API + UI for creating + PDF support - [PR #19895](https://github.com/BerriAI/litellm/pull/19895) + - Add permission management for users and teams on Vector Stores - [PR #19972](https://github.com/BerriAI/litellm/pull/19972) + - Enable router support for completions in RAG query pipeline - [PR #19550](https://github.com/BerriAI/litellm/pull/19550) + +- **[Search API (/search)](../../docs/search)** + - Add /list endpoint to list what search tools exist in router - [PR #19969](https://github.com/BerriAI/litellm/pull/19969) + - Fix router search tools v2 integration - [PR #19840](https://github.com/BerriAI/litellm/pull/19840) + +- **[Passthrough Endpoints (/\{provider\}_passthrough)](../../docs/pass_through/intro)** + - Add /openai_passthrough route for OpenAI passthrough requests - [PR #19989](https://github.com/BerriAI/litellm/pull/19989) + - Add support for configuring role_mappings via environment variables - [PR #19498](https://github.com/BerriAI/litellm/pull/19498) + - Add Vertex AI LLM credentials sensitive keyword "vertex_credentials" for masking - [PR #19551](https://github.com/BerriAI/litellm/pull/19551) + - Fix prevention of provider-prefixed model name leaks in responses - [PR #19943](https://github.com/BerriAI/litellm/pull/19943) + - Fix proxy support for slashes in Google Vertex generateContent model names - [PR #19737](https://github.com/BerriAI/litellm/pull/19737), [PR #19753](https://github.com/BerriAI/litellm/pull/19753) + - Support model names with slashes in Vertex AI passthrough URLs - [PR #19944](https://github.com/BerriAI/litellm/pull/19944) + - Fix regression in Vertex AI passthroughs for router models - [PR #19967](https://github.com/BerriAI/litellm/pull/19967) + - Add regression tests for Vertex AI passthrough model names - [PR #19855](https://github.com/BerriAI/litellm/pull/19855) + +#### Bugs + +- **General** + - Fix token calculations and refactor - [PR #19696](https://github.com/BerriAI/litellm/pull/19696) + +## Management Endpoints / UI + +#### Features + +- **Proxy CLI Auth** + - Add configurable CLI JWT expiration via environment variable - [PR #19780](https://github.com/BerriAI/litellm/pull/19780) + - Fix team cli auth flow - [PR #19666](https://github.com/BerriAI/litellm/pull/19666) + +- **Virtual Keys** + - UI: Auto Truncation of Table Values - [PR #19718](https://github.com/BerriAI/litellm/pull/19718) + - Fix Create Key: Expire Key Input Duration - [PR #19807](https://github.com/BerriAI/litellm/pull/19807) + - Bulk Update Keys Endpoint - [PR #19886](https://github.com/BerriAI/litellm/pull/19886) + +- **Logs View** + - **v2 Logs view with side panel and improved UX** - [PR #20091](https://github.com/BerriAI/litellm/pull/20091) + - New View to render "Tools" on Logs View - [PR #20093](https://github.com/BerriAI/litellm/pull/20093) + - Add Pretty print view of request/response - [PR #20096](https://github.com/BerriAI/litellm/pull/20096) + - Add error_message search in Spend Logs Endpoint - [PR #19960](https://github.com/BerriAI/litellm/pull/19960) + - UI: Adding Error message search to ui spend logs - [PR #19963](https://github.com/BerriAI/litellm/pull/19963) + - Spend Logs: Settings Modal - [PR #19918](https://github.com/BerriAI/litellm/pull/19918) + - Fix error_code in Spend Logs metadata - [PR #20015](https://github.com/BerriAI/litellm/pull/20015) + - Spend Logs: Show Current Store and Retention Status - [PR #20017](https://github.com/BerriAI/litellm/pull/20017) + - Allow Dynamic Setting of store_prompts_in_spend_logs - [PR #19913](https://github.com/BerriAI/litellm/pull/19913) + - [Docs: UI Spend Logs Settings](../../docs/proxy/ui_spend_log_settings) - [PR #20197](https://github.com/BerriAI/litellm/pull/20197) + +- **Models + Endpoints** + - Add sortBy and sortOrder params for /v2/model/info - [PR #19903](https://github.com/BerriAI/litellm/pull/19903) + - Fix Sorting for /v2/model/info - [PR #19971](https://github.com/BerriAI/litellm/pull/19971) + - UI: Model Page Server Sort - [PR #19908](https://github.com/BerriAI/litellm/pull/19908) + +- **Usage & Analytics** + - UI: Usage Export: Breakdown by Teams and Keys - [PR #19953](https://github.com/BerriAI/litellm/pull/19953) + - UI: Usage: Model Breakdown Per Key - [PR #20039](https://github.com/BerriAI/litellm/pull/20039) + +- **UI Improvements** + - UI: Allow Admins to control what pages are visible on LeftNav - [PR #19907](https://github.com/BerriAI/litellm/pull/19907) + - UI: Add Light/Dark Mode Switch for Development - [PR #19804](https://github.com/BerriAI/litellm/pull/19804) + - UI: Dark Mode: Delete Resource Modal - [PR #20098](https://github.com/BerriAI/litellm/pull/20098) + - UI: Tables: Reusable Table Sort Component - [PR #19970](https://github.com/BerriAI/litellm/pull/19970) + - UI: New Badge Dot Render - [PR #20024](https://github.com/BerriAI/litellm/pull/20024) + - UI: Feedback Prompts: Option To Hide Prompts - [PR #19831](https://github.com/BerriAI/litellm/pull/19831) + - UI: Navbar: Fixed Default Logo + Bound Logo Box - [PR #20092](https://github.com/BerriAI/litellm/pull/20092) + - UI: Navbar: User Dropdown - [PR #20095](https://github.com/BerriAI/litellm/pull/20095) + - Change default key type from 'Default' to 'LLM API' - [PR #19516](https://github.com/BerriAI/litellm/pull/19516) + +- **Team & User Management** + - Fix /team/member_add User Email and ID Verifications - [PR #19814](https://github.com/BerriAI/litellm/pull/19814) + - Fix SSO Email Case Sensitivity - [PR #19799](https://github.com/BerriAI/litellm/pull/19799) + - UI: Internal User: Bulk Add - [PR #19721](https://github.com/BerriAI/litellm/pull/19721) + +- **AI Gateway Features** + - Add support for making silent LLM calls without logging - [PR #19544](https://github.com/BerriAI/litellm/pull/19544) + - UI: Fix MCP tools instructions to display comma-separated strings - [PR #20101](https://github.com/BerriAI/litellm/pull/20101) + +#### Bugs + +- Fix Model Name During Fallback - [PR #20177](https://github.com/BerriAI/litellm/pull/20177) +- Fix Health Endpoints when Callback Objects Defined - [PR #20182](https://github.com/BerriAI/litellm/pull/20182) +- Fix Unable to reset user max budget to unlimited - [PR #19796](https://github.com/BerriAI/litellm/pull/19796) +- Fix Password comparison with non-ASCII characters - [PR #19568](https://github.com/BerriAI/litellm/pull/19568) +- Correct error message for DISABLE_ADMIN_ENDPOINTS - [PR #19861](https://github.com/BerriAI/litellm/pull/19861) +- Prevent clearing content filter patterns when editing guardrail - [PR #19671](https://github.com/BerriAI/litellm/pull/19671) +- Fix Prompt Studio history to load tools and system messages - [PR #19920](https://github.com/BerriAI/litellm/pull/19920) +- Add WATSONX_ZENAPIKEY to WatsonX credentials - [PR #20086](https://github.com/BerriAI/litellm/pull/20086) +- UI: Vector Store: Allow Config Defined Models to Be Selected - [PR #20031](https://github.com/BerriAI/litellm/pull/20031) + +## Logging / Guardrail / Prompt Management Integrations + +#### Features + +- **[DataDog](../../docs/proxy/logging#datadog)** + - Add agent support for LLM Observability - [PR #19574](https://github.com/BerriAI/litellm/pull/19574) + - Add datadog cost management support and fix startup callback issue - [PR #19584](https://github.com/BerriAI/litellm/pull/19584) + - Add datadog_llm_observability to /health/services allowed list - [PR #19952](https://github.com/BerriAI/litellm/pull/19952) + - Check for agent mode before requiring DD_API_KEY/DD_SITE - [PR #20156](https://github.com/BerriAI/litellm/pull/20156) + +- **[OpenTelemetry](../../docs/observability/opentelemetry_integration)** + - Propagate JWT auth metadata to OTEL spans - [PR #19627](https://github.com/BerriAI/litellm/pull/19627) + - Fix thread leak in dynamic header path - [PR #19946](https://github.com/BerriAI/litellm/pull/19946) + +- **[Prometheus](../../docs/proxy/logging#prometheus)** + - Add callbacks and labels - [PR #19708](https://github.com/BerriAI/litellm/pull/19708) + - Add clientip and user agent in metrics - [PR #19717](https://github.com/BerriAI/litellm/pull/19717) + - Add tpm-rpm limit metrics - [PR #19725](https://github.com/BerriAI/litellm/pull/19725) + - Add model_id label to metrics - [PR #19678](https://github.com/BerriAI/litellm/pull/19678) + - Safely handle None metadata in logging - [PR #19691](https://github.com/BerriAI/litellm/pull/19691) + - Resolve high CPU when router_settings in DB by avoiding REGISTRY.collect() - [PR #20087](https://github.com/BerriAI/litellm/pull/20087) + +- **[Langfuse](../../docs/proxy/logging#langfuse)** + - Add litellm_callback_logging_failures_metric for Langfuse, Langfuse Otel and other Otel providers - [PR #19636](https://github.com/BerriAI/litellm/pull/19636) + +- **General Logging** + - Use return value from CustomLogger.async_post_call_success_hook - [PR #19670](https://github.com/BerriAI/litellm/pull/19670) + - Add async_post_call_response_headers_hook to CustomLogger - [PR #20083](https://github.com/BerriAI/litellm/pull/20083) + - Add mock client factory pattern and mock support for PostHog, Helicone, and Braintrust integrations - [PR #19707](https://github.com/BerriAI/litellm/pull/19707) + +#### Guardrails + +- **[Presidio](../../docs/proxy/guardrails/pii_masking_v2)** + - Reuse HTTP connections to prevent performance degradation - [PR #19964](https://github.com/BerriAI/litellm/pull/19964) + +- **Onyx** + - Add timeout to onyx guardrail - [PR #19731](https://github.com/BerriAI/litellm/pull/19731) + +- **General** + - Add guardrail model argument feature - [PR #19619](https://github.com/BerriAI/litellm/pull/19619) + - Fix guardrails issues with streaming-response regex - [PR #19901](https://github.com/BerriAI/litellm/pull/19901) + - Remove enterprise requirement for guardrail monitoring (docs) - [PR #19833](https://github.com/BerriAI/litellm/pull/19833) + +## Spend Tracking, Budgets and Rate Limiting + +- Add event-driven coordination for global spend query to prevent cache stampede - [PR #20030](https://github.com/BerriAI/litellm/pull/20030) + +## Performance / Loadbalancing / Reliability improvements + +- **Resolve high CPU when router_settings in DB** - by avoiding REGISTRY.collect() in PrometheusServicesLogger - [PR #20087](https://github.com/BerriAI/litellm/pull/20087) +- **Reuse HTTP connections in Presidio** - to prevent performance degradation - [PR #19964](https://github.com/BerriAI/litellm/pull/19964) +- **Event-driven coordination for global spend query** - prevent cache stampede - [PR #20030](https://github.com/BerriAI/litellm/pull/20030) +- Fix recursive Pydantic validation issue - [PR #19531](https://github.com/BerriAI/litellm/pull/19531) +- Refactor argument handling into helper function to reduce code bloat - [PR #19720](https://github.com/BerriAI/litellm/pull/19720) +- Optimize logo fetching and resolve MCP import blockers - [PR #19719](https://github.com/BerriAI/litellm/pull/19719) +- Improve logo download performance using async HTTP client - [PR #20155](https://github.com/BerriAI/litellm/pull/20155) +- Fix server root path configuration - [PR #19790](https://github.com/BerriAI/litellm/pull/19790) +- Refactor: Extract transport context creation into separate method - [PR #19794](https://github.com/BerriAI/litellm/pull/19794) +- Add native_background_mode configuration to override polling_via_cache for specific models - [PR #19899](https://github.com/BerriAI/litellm/pull/19899) +- Initialize tiktoken environment at import time to enable offline usage - [PR #19882](https://github.com/BerriAI/litellm/pull/19882) +- Improve tiktoken performance using local cache in lazy loading - [PR #19774](https://github.com/BerriAI/litellm/pull/19774) +- Fix timeout errors in chat completion calls to be correctly reported in failure callbacks - [PR #19842](https://github.com/BerriAI/litellm/pull/19842) +- Fix environment variable type handling for NUM_RETRIES - [PR #19507](https://github.com/BerriAI/litellm/pull/19507) +- Use safe_deep_copy in silent experiment kwargs to prevent mutation - [PR #20170](https://github.com/BerriAI/litellm/pull/20170) +- Improve error handling by inspecting BadRequestError after all other policy types - [PR #19878](https://github.com/BerriAI/litellm/pull/19878) + +## Database Changes + +### Schema Updates + +| Table | Change Type | Description | PR | Migration | +| ----- | ----------- | ----------- | -- | --------- | +| `LiteLLM_ManagedVectorStoresTable` | New Columns | Added `team_id` and `user_id` fields for permission management | [PR #19972](https://github.com/BerriAI/litellm/pull/19972) | [Migration](https://github.com/BerriAI/litellm/blob/main/litellm-proxy-extras/litellm_proxy_extras/migrations/20260131150814_add_team_user_to_vector_stores/migration.sql) | + +### Migration Improvements + +- Fix Docker: Use correct schema path for Prisma generation - [PR #19631](https://github.com/BerriAI/litellm/pull/19631) +- Resolve 'relation does not exist' migration errors in setup_database - [PR #19281](https://github.com/BerriAI/litellm/pull/19281) +- Fix migration issue and improve Docker image stability - [PR #19843](https://github.com/BerriAI/litellm/pull/19843) +- Run Prisma generate as nobody user in non-root Docker container for security - [PR #20000](https://github.com/BerriAI/litellm/pull/20000) +- Bump litellm-proxy-extras version to 0.4.28 - [PR #20166](https://github.com/BerriAI/litellm/pull/20166) + +## Documentation Updates + +- **[Add Claude Agents SDK x LiteLLM Guide](../../docs/mcp)** - [PR #20036](https://github.com/BerriAI/litellm/pull/20036) +- **[Add Cookbook: Using Claude Agent SDK + MCPs with LiteLLM](https://github.com/BerriAI/litellm/tree/main/cookbook)** - [PR #20081](https://github.com/BerriAI/litellm/pull/20081) +- Fix A2A Python SDK URL in documentation - [PR #19832](https://github.com/BerriAI/litellm/pull/19832) +- **[Add Sarvam usage documentation](../../docs/providers/sarvam)** - [PR #19844](https://github.com/BerriAI/litellm/pull/19844) +- **[Add supported input formats for embeddings](../../docs/embedding/supported_embedding)** - [PR #20073](https://github.com/BerriAI/litellm/pull/20073) +- **[UI Spend Logs Settings Docs](../../docs/proxy/ui_spend_log_settings)** - [PR #20197](https://github.com/BerriAI/litellm/pull/20197) +- Add OpenAI Agents SDK to OSS Adopters list in README - [PR #19820](https://github.com/BerriAI/litellm/pull/19820) +- Update docs: Remove enterprise requirement for guardrail monitoring - [PR #19833](https://github.com/BerriAI/litellm/pull/19833) +- Add missing environment variable documentation - [PR #20138](https://github.com/BerriAI/litellm/pull/20138) +- Improve documentation blog index page - [PR #20188](https://github.com/BerriAI/litellm/pull/20188) + +## Infrastructure / Testing Improvements + +- Add test coverage for Router.get_valid_args and improve code coverage reporting - [PR #19797](https://github.com/BerriAI/litellm/pull/19797) +- Add validation of model cost map as CI job - [PR #19993](https://github.com/BerriAI/litellm/pull/19993) +- Add Realtime API benchmarks - [PR #20074](https://github.com/BerriAI/litellm/pull/20074) +- Add Init Containers support in community helm chart - [PR #19816](https://github.com/BerriAI/litellm/pull/19816) +- Add libsndfile to main Dockerfile for ARM64 audio processing support - [PR #19776](https://github.com/BerriAI/litellm/pull/19776) + +## New Contributors + +* @ruanjf made their first contribution in https://github.com/BerriAI/litellm/pull/19551 +* @moh-dev-stack made their first contribution in https://github.com/BerriAI/litellm/pull/19507 +* @formorter made their first contribution in https://github.com/BerriAI/litellm/pull/19498 +* @priyam-that made their first contribution in https://github.com/BerriAI/litellm/pull/19516 +* @marcosgriselli made their first contribution in https://github.com/BerriAI/litellm/pull/19550 +* @natimofeev made their first contribution in https://github.com/BerriAI/litellm/pull/19232 +* @zifeo made their first contribution in https://github.com/BerriAI/litellm/pull/19805 +* @pragyasardana made their first contribution in https://github.com/BerriAI/litellm/pull/19816 +* @ryewilson made their first contribution in https://github.com/BerriAI/litellm/pull/19833 +* @lizhen921 made their first contribution in https://github.com/BerriAI/litellm/pull/19919 +* @boarder7395 made their first contribution in https://github.com/BerriAI/litellm/pull/19666 +* @rushilchugh01 made their first contribution in https://github.com/BerriAI/litellm/pull/19938 +* @cfchase made their first contribution in https://github.com/BerriAI/litellm/pull/19893 +* @ayim made their first contribution in https://github.com/BerriAI/litellm/pull/19872 +* @varunsripad123 made their first contribution in https://github.com/BerriAI/litellm/pull/20018 +* @nht1206 made their first contribution in https://github.com/BerriAI/litellm/pull/20046 +* @genga6 made their first contribution in https://github.com/BerriAI/litellm/pull/20009 + +**Full Changelog**: https://github.com/BerriAI/litellm/compare/v1.81.3.rc...v1.81.6 diff --git a/docs/my-website/sidebars.js b/docs/my-website/sidebars.js index a72f295c597..d932b6af250 100644 --- a/docs/my-website/sidebars.js +++ b/docs/my-website/sidebars.js @@ -139,6 +139,20 @@ const sidebars = { "tutorials/openai_codex" ] }, + { + type: "category", + label: "Agent SDKs", + link: { + type: "generated-index", + title: "Agent SDKs", + description: "Use LiteLLM with agent frameworks and SDKs", + slug: "/agent_sdks" + }, + items: [ + "tutorials/claude_agent_sdk", + "tutorials/google_adk", + ] + }, ], // But you can create a sidebar manually @@ -274,11 +288,19 @@ const sidebars = { "proxy/custom_sso", "proxy/ai_hub", "proxy/model_compare_ui", - "proxy/public_teams", - "proxy/self_serve", - "proxy/ui/bulk_edit_users", "proxy/ui_credentials", "tutorials/scim_litellm", + { + type: "category", + label: "UI User/Team Management", + items: [ + "proxy/access_control", + "proxy/public_teams", + "proxy/self_serve", + "proxy/ui/bulk_edit_users", + "proxy/ui/page_visibility", + ] + }, { type: "category", label: "UI Usage Tracking", @@ -292,6 +314,7 @@ const sidebars = { label: "UI Logs", items: [ "proxy/ui_logs", + "proxy/ui_spend_log_settings", "proxy/ui_logs_sessions", "proxy/deleted_keys_teams" ] @@ -364,6 +387,7 @@ const sidebars = { label: "Load Balancing, Routing, Fallbacks", href: "https://docs.litellm.ai/docs/routing-load-balancing", }, + "traffic_mirroring", { type: "category", label: "Logging, Alerting, Metrics", @@ -418,6 +442,7 @@ const sidebars = { label: "Spend Tracking", items: [ "proxy/cost_tracking", + "proxy/request_tags", "proxy/custom_pricing", "proxy/pricing_calculator", "proxy/provider_margins", @@ -513,6 +538,7 @@ const sidebars = { items: [ "mcp", "mcp_usage", + "mcp_semantic_filter", "mcp_control", "mcp_cost", "mcp_guardrail", @@ -691,6 +717,7 @@ const sidebars = { "providers/bedrock_agents", "providers/bedrock_writer", "providers/bedrock_batches", + "providers/bedrock_realtime_with_audio", "providers/aws_polly", "providers/bedrock_vector_store", ] @@ -775,6 +802,7 @@ const sidebars = { "providers/oci", "providers/ollama", "providers/openrouter", + "providers/sarvam", "providers/ovhcloud", "providers/perplexity", "providers/petals", @@ -843,6 +871,7 @@ const sidebars = { "completion/image_generation_chat", "completion/json_mode", "completion/knowledgebase", + "providers/anthropic_tool_search", "guides/code_interpreter", "completion/message_trimming", "completion/model_alias", @@ -879,6 +908,7 @@ const sidebars = { "scheduler", "proxy/auto_routing", "proxy/load_balancing", + "proxy/keys_teams_router_settings", "proxy/provider_budget_routing", "proxy/reliability", "proxy/fallback_management", @@ -919,7 +949,6 @@ const sidebars = { type: "category", label: "LiteLLM Python SDK Tutorials", items: [ - 'tutorials/google_adk', 'tutorials/azure_openai', 'tutorials/instructor', "tutorials/gradio_integration", @@ -1015,6 +1044,7 @@ const sidebars = { type: "category", label: "Issue Reporting", items: [ + "troubleshoot/prisma_migrations", "troubleshoot/cpu_issues", "troubleshoot/memory_issues", "troubleshoot/spend_queue_warnings", diff --git a/docs/my-website/src/theme/BlogListPage/index.js b/docs/my-website/src/theme/BlogListPage/index.js new file mode 100644 index 00000000000..277556a3528 --- /dev/null +++ b/docs/my-website/src/theme/BlogListPage/index.js @@ -0,0 +1,123 @@ +import React from 'react'; +import Layout from '@theme/Layout'; +import Link from '@docusaurus/Link'; +import styles from './styles.module.css'; + +const TAG_COLORS = { + gemini: {bg: '#d2e3fc', text: '#174ea6', darkBg: '#1a3a5c', darkText: '#8ab4f8'}, + anthropic: {bg: '#fde0c4', text: '#b33d00', darkBg: '#4a2800', darkText: '#ffb74d'}, + claude: {bg: '#fde0c4', text: '#b33d00', darkBg: '#4a2800', darkText: '#ffb74d'}, + llms: {bg: '#c8e6c9', text: '#1b5e20', darkBg: '#1b3d1f', darkText: '#81c784'}, +}; + +function hashHue(str) { + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + return Math.abs(hash) % 360; +} + +function getTagColor(label) { + const key = label.toLowerCase(); + for (const [k, v] of Object.entries(TAG_COLORS)) { + if (key === k) return v; + } + const hue = hashHue(key); + return { + bg: `hsl(${hue}, 40%, 90%)`, + text: `hsl(${hue}, 60%, 25%)`, + darkBg: `hsl(${hue}, 40%, 20%)`, + darkText: `hsl(${hue}, 50%, 75%)`, + }; +} + +function formatDate(dateStr) { + const d = new Date(dateStr); + const now = new Date(); + const diffDays = Math.floor((now - d) / (1000 * 60 * 60 * 24)); + if (diffDays <= 0) return 'Today'; + if (diffDays === 1) return '1d ago'; + if (diffDays < 30) return `${diffDays}d ago`; + return d.toLocaleDateString('en-US', {month: 'short', day: 'numeric', year: 'numeric'}); +} + +function BlogCard({post, featured}) { + const {title, permalink, date, description, tags} = post; + const visibleTags = (tags || []).slice(0, 3); + + return ( + +

    + + ); +} + +function Pagination({metadata}) { + const {previousPage, nextPage} = metadata; + if (!previousPage && !nextPage) return null; + return ( + + ); +} + +export default function BlogListPage(props) { + const items = props.items || []; + const metadata = props.metadata || {}; + const [first, ...rest] = items; + + return ( + +
    +

    The LiteLLM Blog

    +

    Guides, announcements, and best practices from the LiteLLM team.

    +
    + +
    + {first && ( + + )} + {rest.map(({content}) => ( + + ))} +
    + + +
    + ); +} diff --git a/docs/my-website/src/theme/BlogListPage/styles.module.css b/docs/my-website/src/theme/BlogListPage/styles.module.css new file mode 100644 index 00000000000..747c9846a2c --- /dev/null +++ b/docs/my-website/src/theme/BlogListPage/styles.module.css @@ -0,0 +1,163 @@ +.hero { + max-width: 960px; + margin: 0 auto; + padding: 3rem 1.5rem 1rem; + text-align: center; +} + +.heroTitle { + font-size: 2.25rem; + font-weight: 700; + margin-bottom: 0.25rem; + letter-spacing: -0.02em; +} + +.heroSubtitle { + color: var(--ifm-color-emphasis-600); + font-size: 1.1rem; + margin-bottom: 0; +} + +.grid { + max-width: 960px; + margin: 0 auto; + padding: 1.5rem; + display: grid; + gap: 1rem; +} + +.cardLink { + display: block; + text-decoration: none; + color: inherit; +} + +.card { + position: relative; + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 12px; + padding: 1.5rem; + padding-right: 2.5rem; + height: 100%; + transition: border-color 0.15s, transform 0.15s, background 0.15s; + background: var(--ifm-background-surface-color, var(--ifm-background-color)); +} + +.card:hover { + border-color: var(--ifm-color-primary); + transform: translateY(-2px); + background: var(--ifm-color-emphasis-100); +} + +.cardFeatured { + composes: card; + border-color: var(--ifm-color-primary-lighter); + background: var(--ifm-color-emphasis-100); +} + +.meta { + display: flex; + align-items: center; + gap: 0.5rem; + margin-bottom: 0.5rem; +} + +.time { + font-size: 0.8rem; + font-weight: 500; + color: var(--ifm-color-emphasis-600); + text-transform: uppercase; + letter-spacing: 0.04em; +} + +.badge { + font-size: 0.65rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + padding: 2px 8px; + border-radius: 99px; + background: var(--ifm-color-primary); + color: #fff; +} + +.title { + font-size: 1.15rem; + font-weight: 600; + margin: 0 0 0.4rem; + line-height: 1.35; +} + +.desc { + font-size: 0.88rem; + color: var(--ifm-color-emphasis-700); + line-height: 1.5; + margin: 0 0 0.75rem; +} + +.tags { + display: flex; + gap: 6px; + flex-wrap: wrap; +} + +.tag { + font-size: 0.7rem; + font-weight: 500; + padding: 2px 10px; + border-radius: 99px; + background: var(--tag-bg); + color: var(--tag-text); +} + +:global([data-theme='dark']) .tag { + background: var(--tag-bg-dark); + color: var(--tag-text-dark); +} + +.arrow { + position: absolute; + right: 1rem; + top: 50%; + transform: translateY(-50%); + color: var(--ifm-color-emphasis-400); + transition: color 0.15s, transform 0.15s; +} + +.card:hover .arrow { + color: var(--ifm-color-primary); + transform: translateY(-50%) translateX(3px); +} + +.pagination { + max-width: 960px; + margin: 0 auto; + padding: 1rem 1.5rem 3rem; + display: flex; + justify-content: space-between; +} + +.paginationLink { + font-size: 0.9rem; + font-weight: 500; + color: var(--ifm-color-primary); + text-decoration: none; +} + +.paginationLink:hover { + text-decoration: underline; +} + +@media (min-width: 640px) { + .grid { + grid-template-columns: repeat(2, 1fr); + } + + .grid .cardLink:first-child { + grid-column: 1 / -1; + } + + .grid .cardLink:last-child:nth-child(even) { + grid-column: 1 / -1; + } +} diff --git a/enterprise/litellm_enterprise/proxy/auth/route_checks.py b/enterprise/litellm_enterprise/proxy/auth/route_checks.py index 6cce781faf3..6f7cf9143f4 100644 --- a/enterprise/litellm_enterprise/proxy/auth/route_checks.py +++ b/enterprise/litellm_enterprise/proxy/auth/route_checks.py @@ -36,7 +36,7 @@ class EnterpriseRouteChecks: if not premium_user: raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"🚨🚨🚨 DISABLING LLM API ENDPOINTS is an Enterprise feature\n🚨 {CommonProxyErrors.not_premium_user.value}", + detail=f"🚨🚨🚨 DISABLING ADMIN ENDPOINTS is an Enterprise feature\n🚨 {CommonProxyErrors.not_premium_user.value}", ) return get_secret_bool("DISABLE_ADMIN_ENDPOINTS") is True diff --git a/enterprise/litellm_enterprise/proxy/hooks/managed_files.py b/enterprise/litellm_enterprise/proxy/hooks/managed_files.py index 445d2b242b4..5ee3372cca7 100644 --- a/enterprise/litellm_enterprise/proxy/hooks/managed_files.py +++ b/enterprise/litellm_enterprise/proxy/hooks/managed_files.py @@ -244,6 +244,78 @@ class _PROXY_LiteLLMManagedFiles(CustomLogger, BaseFileEndpoints): return managed_object.created_by == user_id return True # don't raise error if managed object is not found + async def list_user_batches( + self, + user_api_key_dict: UserAPIKeyAuth, + limit: Optional[int] = None, + after: Optional[str] = None, + provider: Optional[str] = None, + target_model_names: Optional[str] = None, + llm_router: Optional[Router] = None, + ) -> Dict[str, Any]: + # Provider filtering is not supported for managed batches + # This is because the encoded object ids stored in the managed objects table do not contain the provider information + # To support provider filtering, we would need to store the provider information in the encoded object ids + if provider: + raise Exception( + "Filtering by 'provider' is not supported when using managed batches." + ) + + # Model name filtering is not supported for managed batches + # This is because the encoded object ids stored in the managed objects table do not contain the model name + # A hash of the model name + litellm_params for the model name is encoded as the model id. This is not sufficient to reliably map the target model names to the model ids. + if target_model_names: + raise Exception( + "Filtering by 'target_model_names' is not supported when using managed batches." + ) + + where_clause: Dict[str, Any] = {"file_purpose": "batch"} + + # Filter by user who created the batch + if user_api_key_dict.user_id: + where_clause["created_by"] = user_api_key_dict.user_id + + if after: + where_clause["id"] = {"gt": after} + + # Fetch more than needed to allow for post-fetch filtering + fetch_limit = limit or 20 + if target_model_names: + # Fetch extra to account for filtering + fetch_limit = max(fetch_limit * 3, 100) + + batches = await self.prisma_client.db.litellm_managedobjecttable.find_many( + where=where_clause, + take=fetch_limit, + order={"created_at": "desc"}, + ) + + batch_objects: List[LiteLLMBatch] = [] + for batch in batches: + try: + # Stop once we have enough after filtering + if len(batch_objects) >= (limit or 20): + break + + batch_data = json.loads(batch.file_object) if isinstance(batch.file_object, str) else batch.file_object + batch_obj = LiteLLMBatch(**batch_data) + batch_obj.id = batch.unified_object_id + batch_objects.append(batch_obj) + + except Exception as e: + verbose_logger.warning( + f"Failed to parse batch object {batch.unified_object_id}: {e}" + ) + continue + + return { + "object": "list", + "data": batch_objects, + "first_id": batch_objects[0].id if batch_objects else None, + "last_id": batch_objects[-1].id if batch_objects else None, + "has_more": len(batch_objects) == (limit or 20), + } + async def get_user_created_file_ids( self, user_api_key_dict: UserAPIKeyAuth, model_object_ids: List[str] ) -> List[OpenAIFileObject]: @@ -297,6 +369,8 @@ class _PROXY_LiteLLMManagedFiles(CustomLogger, BaseFileEndpoints): if ( call_type == CallTypes.afile_content.value or call_type == CallTypes.afile_delete.value + or call_type == CallTypes.afile_retrieve.value + or call_type == CallTypes.afile_content.value ): await self.check_managed_file_id_access(data, user_api_key_dict) @@ -361,12 +435,16 @@ class _PROXY_LiteLLMManagedFiles(CustomLogger, BaseFileEndpoints): data["model_file_id_mapping"] = model_file_id_mapping elif ( call_type == CallTypes.aretrieve_batch.value + or call_type == CallTypes.acancel_batch.value or call_type == CallTypes.acancel_fine_tuning_job.value or call_type == CallTypes.aretrieve_fine_tuning_job.value ): accessor_key: Optional[str] = None retrieve_object_id: Optional[str] = None - if call_type == CallTypes.aretrieve_batch.value: + if ( + call_type == CallTypes.aretrieve_batch.value + or call_type == CallTypes.acancel_batch.value + ): accessor_key = "batch_id" elif ( call_type == CallTypes.acancel_fine_tuning_job.value @@ -382,6 +460,8 @@ class _PROXY_LiteLLMManagedFiles(CustomLogger, BaseFileEndpoints): if retrieve_object_id else False ) + print(f"🔥potential_llm_object_id: {potential_llm_object_id}") + print(f"🔥retrieve_object_id: {retrieve_object_id}") if potential_llm_object_id and retrieve_object_id: ## VALIDATE USER HAS ACCESS TO THE OBJECT ## if not await self.can_user_call_unified_object_id( @@ -673,6 +753,7 @@ class _PROXY_LiteLLMManagedFiles(CustomLogger, BaseFileEndpoints): bytes=file_objects[0].bytes, filename=file_objects[0].filename, status="uploaded", + expires_at=file_objects[0].expires_at, ) return response @@ -893,8 +974,10 @@ class _PROXY_LiteLLMManagedFiles(CustomLogger, BaseFileEndpoints): delete_response = None specific_model_file_id_mapping = model_file_id_mapping.get(file_id) if specific_model_file_id_mapping: + # Remove conflicting keys from data to avoid duplicate keyword arguments + filtered_data = {k: v for k, v in data.items() if k not in ("model", "file_id")} for model_id, model_file_id in specific_model_file_id_mapping.items(): - delete_response = await llm_router.afile_delete(model=model_id, file_id=model_file_id, **data) # type: ignore + delete_response = await llm_router.afile_delete(model=model_id, file_id=model_file_id, **filtered_data) # type: ignore stored_file_object = await self.delete_unified_file_id( file_id, litellm_parent_otel_span diff --git a/enterprise/litellm_enterprise/proxy/vector_stores/endpoints.py b/enterprise/litellm_enterprise/proxy/vector_stores/endpoints.py index 21933165217..5e799599862 100644 --- a/enterprise/litellm_enterprise/proxy/vector_stores/endpoints.py +++ b/enterprise/litellm_enterprise/proxy/vector_stores/endpoints.py @@ -282,6 +282,8 @@ async def get_vector_store_info( updated_at=vector_store.get("updated_at") or None, litellm_credential_name=vector_store.get("litellm_credential_name"), litellm_params=vector_store.get("litellm_params") or None, + team_id=vector_store.get("team_id"), + user_id=vector_store.get("user_id"), ) return {"vector_store": vector_store_pydantic_obj} diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.27-py3-none-any.whl b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.27-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..f1dc450a0fc47e58436c5f40a1679a82d6ab5bc0 GIT binary patch literal 50073 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 Le4CJXUi|fc(Nseq literal 0 HcmV?d00001 diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.27.tar.gz b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.27.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..742b129eaa8b36f33a67d96b200588c819402cde GIT binary patch literal 23269 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 Ay8r+H literal 0 HcmV?d00001 diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.28-py3-none-any.whl b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.28-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..f119a977e7cd0bc1d9a474333f54ba3624dc9c43 GIT binary patch literal 50208 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 RJ&$}U>|uPH(6@#C^*?!-wub-! literal 0 HcmV?d00001 diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.28.tar.gz b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.28.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..e0ecd0c421496cd71924f92ad74b08442147091d GIT binary patch literal 23405 zcmXV$by!sG_w@lOK^mk%q`SLCx}|gI?v@%tL|Q^RrKKAr1_bHu9#Xm+hB+_K_xH}< zbIpC`y7$@Zv)10!G3e+BFCQep$==J_$;lby=Hcr18D#C}39 zJ7-DryS-awf%lwJ4BuP7|712$^GTCM`W*Ky)<{lne04Bm>|dem2Dpn-IIpl9kWi32>-M8pw?XAQJS3S}`yxsN6 z^As)dD1hdIk?yy<4E|&Celn2|GLY3~IeZp&(n zL@R83sXqB)b+Dv{kU;S%(pRk(eRcA)A!9SS(DopX%XTo5?Pes0#4`Qqm!_l?4~cej z!arGDq%zY2;EztWTmI+$Hz}t=Yx7xL)3g#wVA4%=gFCJ^N1#yV-#ID+4*R!Ulk{-v z4X>Zwl)Qtr^<4Nzg}dXGM;WwOyC>%?RM-fLvHQ!2`uvUo0RjAM?theK9pvF#0&b;( zc=oZr3Z-)gNqTCytvhb|6*;0kt5`DogaPgDNPCZ|Dz7kY%+8V&Hzqd|Tr15EkCO%& zQ+<`+s^NY>0YvJJCvHmmkVe^D91Q~fzx7X2x9fi4y&X`_hD0RO5%{9=x@z>9T$0^T zE=Qf{H;_q@K%?|_lF8Q)6s)BX>pL!-?6*n!jxt1jKjY}x92$>Sq|>)L<~WvQI`Shf`U%r~8{p8=tFX|^>r!4e%L-#6)n zAwo~~equrlH@(XDCQy1woSI;<0AJbszQ3CXbDTTJ{K8_-5wIXX{|;qTKnOx&Y?{;% zvwVK?Fyw|X@wHT*@hm>F)OC=x%-0`vRuq}+t6a@Ey3b%188$~V~#W{F+W?soP03cJtUzPB6AN+&7B_;mb!cS_$%V6 zMW%(Dc=&O5d<^O65(;QHl5zVG$GeO^D@s|bi#z+V2c577dd7j~9=_w}=7;2LVqu;A zlWDbb4HL*C8U$i`HZNABJzc;C571>-?@yPIg#+(c7_@(H(q{F|{hMS&g1pxPO1P>X zn~NjdV*T<3PiOieBxYGPLAagzF4RtoYX?+^-jUIm{9G#KHBsq8lGaeU)B{zVE` zK8+Qu9}q##{r5@~xoFg;olf7s+Zz0I(&EV4QO>>^;a5DrZ0Qw);#fP0;RsW5>|lh( z5EnGF4?}`Te7oNvTDWb7%-;2P7ud@SY2aEOuKu}BNBMSpn}3XclibE{@t1Ob^mz+n z_k6T2P8laP_D!zv-pk9%<1iu&9kHmYyx&bNLVno37N5{F1mn*%cBBD_pP4V zoI{e*($yDd6g^v|I2axXh7DL0tR8#$P4#Pd%&A-T02^JW+Ya z9c6g;ArW7ksB=xEcTvkO2sSdt7je4#djuG-_IH^{tUJW22olrW!KxHk!H9ih#8|pb z!q^mijy_u=p#-6ZI67Gz=~9io{c7y|$i)HWGROnp#3ORS33=P4*y54zP7XQ!I{(c^ zt=HSe<(M=bk3Q^=CM8{$+K%OD*ZGNP5Jt|Xyz4_to4u!nqx$f=Et z?whbr5+u9vsYGw?xR}ecdiLrXqz>j3no>g<1Qr|9u4EU#FQeAZhgU>Rj}Qyi#?>*VFfEc#Vb=grpQ!tQ8qWLm7^L z{)r9KY4eMO^a!a9}GI=W%yCx&n@7SY- z>!rb_qaBBHoPSIrLB1Y;cC>?Bf~e}=dwT@lVrPj$NGPy4BW+7N(ES-khRt{}SrK^0 zGC~_47V!J5^cj0+DtfYdP&f(M|Ir@WS0?NA3ZBPH;&|jv1d$si%YU2Rn5*XD4j8ga zS5*%}V_=6lO8>3cTekgB;RdKu2PvZSprW(~#q%|M6|wmq|#t6(j;viDPI>pNSY zn61W)C*zxThpk?dpdb*i#vPNSs0RbvQL&%b$7;?Zili4zbo;Hy9r%qfP%ff(*CcfR z4C98RHct1|B1PEWze?)a-LUZUOZtjPUyvRl=`oqcwl~)ui1M}sV;4Plr&{RolVNQ; zN{s17e{Q>}|K?cE1HXRPV=3(_JrxR^jWwl-HGqgiL;A@$nbC^jik@$_{V;!l7YyqO zJ?In>6(yx`u#q*PA~%xu1<|lxu22wleW0XUArWqi;WXu3ZepAt$G|){*+)J^GX9gi zilP9rAUbklDD|XqtH#u( zMV=^IV|8UX%_K-o3l{L0p!%UGSYiRP^wPaHx_d-nB_v(1Y%>{DRxXduKgK}Y)5wGzyood2~?*{v)mDm_N9j*`UMzVMJp zJHtfbi~qSIJML8(A^NjW%alR+e(3R<;2{ykgtm#%SYJyTLEwBZrp*6=n=nr}#ap{N zTvWvCOl*Jz@6&X{2qyhA=+NlLk~MRnR`o>o-Ap%#<1#un&3q>u+2YBA+GOSRW%T=b zcP)-HagM^(#3JM}HG=$4`=}`9;~Ms%Uru>Ykagte`9{62kDeG&lwQ4aO^F3L<7D(& zSS<;lYyFVC96PKv)77v=%=}i!nKbfhi@w78)%2x_?0Yl9UL_~9A^bcvof9R9*aYBdtzl9=zQP}M?jCxHQKHyT}J;}2NrXGEO zTK2IVqNO=`o^rG+mqk?K7ir^yl8M0@ZYbYLt5O(3eCVDWp6~<>6oFuzoaP) z=0|eh*P<3Cc|YO$D1R1YbyY6%-=Z@rV3b}xx}{sKvwp7iu0KST#YSMR@1s~k-;({Y zRy-JS$^p7*A0mSivHI&l)O=%AFHZJ>99><&^)40;A$o?aK-1yi$>4bhUqfxYz6cI% z+mKei+wSc-KzYhE6{uT-sviNA$$+clBe2P~l`GjP$y4>r3*H%OZz-jlU*X-WAaZ+Pe;3TWtb7IW^|yb3 zo@PFQnvVc-zmd50tV46Wt~YE zeu@`_$`}rL5^)5!?-vnJW{3?t*?EY`xx)rAXzMh8SNpO@+f=mA-OqaBSN=6K3vO~9 zhpX3sQvefMpyfRfFd;w>w4bQ8Lyyi9xQ4DHHCrhXa=0iAa>X-4UT-c&Sr2v~&U-n` zhS!+F(+WFP;J&w_qz_QuOTe)X(28mI(_=HqNFK2g=9T#3IuDn$BL{$>AwVra=7sM| z!tz&2;y2oM4g7*}&pz{ffa8q-7){O&g zh?wsd2G1btM^Jq@B*MO9Z{A|le_ovrV{A~wKY!5~ooOr0A? zWrD${6W*e)E2>lM`VC)wPA-<=g5((N34em`0R7|O!gqe_!Rt$D*hpGW-N$e-p9fIb zZT%Ir(+P%_B6tZ_zJzQ&xO%NtBXY!e~kO(G9W<@;FJxBf?WTr(_Ql%>|e`DVDPXAp9 zf7V0Zr2cJMB0>B|OzL9Hu5_7hyH_9xj8i(`;32^arq?p}pJxTXgF{!Jo{9+bF_yU)qAkvz0?+D$$k&@G+ zDubt@e$|4s)=}p3G*el+(rO<#>;7zP5C}d(XbF3e?tbty--19;9zcjE#h1XwJ4fIy z4h9yw1omM`%lE%#_k6&)9W6H1L@b|#)eSkrv-j;XX>2ypG!MPEjL~KG6!8Vb`my@C zTuim7?0stH){c&29Oij`lghSW>WmD|VGpMuXFqERQS@6Y|+NeL=Q1 zmh{7Y0#2Hj00`#;RD7`(W*ZgaLg9#dzF?9VK_8cYM{)Z<%vh_2uEGAw>M?riAy@5eK+$3GX>=X=bBpp=7Z`2Y{)2!16owtq& zHv)JNVokAIi7}`;MJ46vs3}dUD5-E*UePv4)k}Sg5&J!4o9gCs>7FPr_+ZSCjPjvm zUk~|BVN_82ZC)F&M|A>;TLWfX#T_X_SfJLS-Lq%m9H7iG>woHTy?2Sr|MgaJU~c>o zd>8k8r*RAkyLVk`cl^?t2pm0vpI+i0?n|&J9D3Tn16=vktexG+k4%!9w9h0S$reUY zm-)iq<#GS&woC$EerN)Kj`f4HuAn%w@KO)kdLD}_z8{hJ&VjYZI74%sd#^4X?rr@1 zn~bz7>^^Dg{z^@YtM}i{{*t0Yra_>hTV626ZBT5%FjiFki66(C!TU&j^Rz(Pb$h1{ z-@Q6S^kKQG2G!TGz|8rJE2Xa-0Bwc4UP`X#!6D_aglC_MwWI5RPVM&y`r6G`85KZ3 z&i(#1;L(`&2>3sdK0~Zw;FW1$6Nmuo3A4kma9me7BI)ICW) zolv2Nsnw{X^my5$_#k|hvO5I=cP=4>C%b!+4R_!c6?o~%Fz}P4Pmo)8ZoijgrA;$Z zF^X zM&3*4Ab>RlUB9I9cs~<{gJTUOl+yU0jAv(u6E0<= z9pEQt5M3z$WSGSM)`L-sI~`Y~MWZeLp@*>>DNP59LEY8B|FlolPWVm(B}Y?{M#Nl~ zLB6ek{_Tj_W~8vl^qNk#&ef+P#QG0p(QjG>eebeMPTu^gfBgOxfsBmM)r^ZdtV}3{ zk5s1%p&DO^C;qo+xTUv{HhDNUw%kW@ATJRBMZD79R-Ks&P44!dmwUWsU|i|X6^ zfl46lXD1(^ELsS(;EckdGE)HNHn6X=biFQAz~%CuC$0oXD#(8=)pNTfm1aUe&1Gp2 z2p9$CxgS8M%5Y!7RG{gq^0^XvRR8gv5|W!3PO31K(^d#o6zoOJ>|yFYK+TNcCGeuu z*>C9V&$7e(JyEg~fw>dl)!4JE`2Q$LG;0`WRwww}6tFh6h%q3gT6k0}?)`J>zW=i1 z-zb#!2S7Aw$^bl_nA!j}mODW6j^sLEN)OB$*#@5=Z^pH6%zK)?{Z*8okCNzZR~)G4 z0hU#)xolUa7mkMhvl8%^g~p7bw|OP?eY0ir=M#>Ng}rG03qd_s&KyV{iH$HI3F1j5 z=>JDxAK~E-c|QOX0-*h+lTb-r(f&Qed#{6)r#WdtL6=MojI75jZ}AWdy9WM3I=b=U zyG=9IX3WQZvmSzx_&UP=Z;x2Te7?bkyeH$Es&ao$m5nc$`9ZdXZJxGYHirjn&v5nk zh{Fpvch8Ucx{etBAgpQ^@gT8~f1#yqn23}eUyvf_`&6t~~Od15(+Kj8Oa2K-k4%#VR%^5?UzYsh-ZrR1q^cl?>J;gh|5 zi0nkrQ^EFMnm>FS7kLF|q{ZhI3&8M{=|5oU%=Sw1*64-I8sT|!ug2$KJRVX_93OUA zZ#kVs6`O|uyGu!z7aCnQxflF_zL<>~aC`}Qb5eXko%8Gj-+T3PZ*%}_wB_!7iX!r} zf&>GQH3T%jD1OKSLI}4XE(kd?gG|m6+?*ZoZworW0hIJ)uxeH&RWorsFl-^S?3GUTTu zwYvo=eKnlLIYuU*Hu$iiHhgY5J|Q&zzHQ={bB#ZPZXy0bD=3>verY1VcX5b%%y65vT>~KzvDr@L!$o}fZrrBcSF-z` z0uNItyNO+(Ak)8M<~q0oj^^83ra7uaIO^H%5IrfzHy*N5j4?qoA*SKB#5n2{4**h# zz%5kn81kID1axvf)pVBM*Sxa(HM4xycrTZK@;Im++jBQ~0(t3*xq#?kAGpcCAO2(r zgXnUZ&}Qr{=1s6qj4P8I`cZqscbueCCf;_6IPqb@cHdJeE&tL-Jz63pnxWo_-!?Cm z@*{bIWZs`uCjv%^z}otYKs5N2f+uZ=rOOqcx!)-a;aD_=4bc8)$GU)(iFLsG5-dsb zFtG&*{&{ql!~vxsE$bZWbwVV6g7m@99B;v@a0vSnkR<{DZze9hf3M9etg1S_?}Q-! z4_xlRO+0(R3MB#1^zt$V!+~sz=H)TcY>Z1&z2C>mjThZ6k4fFNZRG4Jawy03?PNR{ zWcc6Deq&y6je~8UfvTXFIIfE3Q5FR z{<8(29-u=zz**ziLyhwg(1KKGwT0IAaQrp#NcFWz@h0Y=Jy>_YyQ>gC$a@9lUCbdyq>Gq-tmS)9 zF--6QI8%qavH-xph|<)g#71;PAH3Z z!NfOUS^xsuJIIqRdUisg8Z0Mx;-fYBx3It&M|FS$ydQ8tPc7Do=(i$^PyC<5Ph!NSYRXTXv51eh}Y8E{?# ztUYcS8QmC9(|=wM0~>ysxZTz}2}qKE&iyYBkB_a~{K(q380Xo%>O%uaM%63wOVWJ? zY-1)WxeSW~jro3D=_%c08U4LMcu!rmB=~*epLzCw#P^@cXefZWI*%@OL0#`e_;6!Q zQAZ|5Y6H#wF?Cy}06LSVWIzk_1+JT4@ajd0rtiU|d;hj1UyMZ>5JUj@x?Vyc(hoq# zO$XEx<(!2ipNR%Jb_95F)A)@wG=OM70L%@7QO@Mxny&E(WDl;FfSBFG3-w5z=(ex<&mUcW%ukOR zPlrLn{)Z3GkQ^TYKz(Eocq!h?E3n?Ywm2-(ZX7bX;Pc0Qg9G*poV5e~EeNPPYCVEVS5BMqEXSfXr`+u)LkpK~99ckMW5 z0K*eC0iZAn2o!pR(*Hdq>h~@4&yPLrh{f^92+XPbWQtzgj+RRC9*edLo*K4rxb&W< zv#20zj3QCmlr|=bvM`YJ`HboY;-d>3kv_YMK7s;X&TENtkV>NuqcYkix8=L-jB=z1 zO)vylZE8mLrOGeq03(53&Dg0|X--tyzDYoM^OX@FD6>~@RarxbK|9M6stTz*Au3n)74F5G~4xr%MZhC7fip0uzy z7)T!OcnLJe!JyHwGnYdcsC~_m+UsfU6$?7j9!Qz7NM2D2dofkCy*RRWzmBR9N$mZ9 z#`dLgPOSotr@&{XOMs-#&svppDXK$dewAbwD)rm=rJsbrT+hz3VBkCY7f}#k0`i(} zMLVZxUdD*&@u>y;-YTsF+iCyVuoqEr5dgrL3y}XQ%du>5`7|d zmo{w>3$#QJW#7R7GYy4t-*nmuaj6KY(0+rH_^=KvRrw;-kI(yIURp1I z9r_rbG&$eLpU;Qas+nRz%|6NtpoH!dC{0j^tPShfRdSkjQ?;#IkXGp@%GK1eV(ON6 z$a$cA#MHa_+rf}GRXe|>P?r;Ude^jqj0*+JE8HZ4b)$Fy6T7xHne{CnayP`y*|26`nC23 zGXY39>IM=PyptpOs|zBxV=N~ zh41{WE&nE2JQijqX-!j2v{!mf$3EvkIqdj^{rnc~y7mJ_3cj zeyTw}v4%s#$Ablcm^Q%C5e`~mAatecAV36*YA6=3yKTHXni$#t+!2j)lOXpmh z7Ny^%J|=&zQ9)mPc{uC;l3|g-Wq{HWkR*Hn;R00*h-Ln=JaJ6<@H2-VD+f-7q|uK_ z;y)GYttg#X;#C~#-bJezf*t>USM27!=e z3m~Wv5O;c3CoeU7QEFg<>`Bsj>;Z_s0G$2;Gb{Qq`6pq8u)Q?_Ph=sp0UZG45NP%Qq%zueWG4*d(Z?*;%HVfSUEnLf4tj$a@3jQWVk*F>3Qi%2lGB;+e@Aj`_ z`OzeiP@Q%=8&yx0bo0zOt1+yC`uh)EMneXzsFL4%dc<>-be+@HxGyqa{@Xohs7pAe zH`>R@e`xQ1y$Nlckx9vT*v7$DIh$Kr8rsm{t%%~sr2Gr?BYr|`ZEStBbt#gvMY6uT zcwH}LSS(K`L{#~Cybyol`#H!%t*z@wvGJWpj15%+p}HN*)!~1ZTJCAJHPvfy(KtT5td@V zzG)e(i^f--+i%vV5-afA=naSJZsyvb)-e!NbbmP;-ZLAAGwOd> zO+F34`L~DT*AwW>2XdgFjxn<{z+GC zwtVHvP7D5sDLV9Lq3W(xdLbrzqp;Y8YOIor?60iH*J!b_%FzWzY@Ut)f+u2o#a_~s zewmMR1Zq31YyMo77qNacgC3|3bYdN+BgWAFwuQ}R_6K@^(6=b8B z@(ZVJ&1NnURZr-2-4kVb+8c~UHWE)GIVJ-~(4Dycj`@_-R2c!!bQD<$nDZ_n|7)^f z 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 literal 0 HcmV?d00001 diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.29-py3-none-any.whl b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.29-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..3e65fb66663d7d21e83bfad868d328212b8db10f GIT binary patch literal 50734 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 }+ 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 literal 0 HcmV?d00001 diff --git a/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.29.tar.gz b/litellm-proxy-extras/dist/litellm_proxy_extras-0.4.29.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..0439f3576b9c3d7b553de263c2bbfa42f55f5717 GIT binary patch literal 23561 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 V`IwLSn9u!x{(ptkv_JsB0{~wKq$&Ua literal 0 HcmV?d00001 diff --git a/litellm-proxy-extras/litellm_proxy_extras/_logging.py b/litellm-proxy-extras/litellm_proxy_extras/_logging.py index 118caecf488..15173005ce8 100644 --- a/litellm-proxy-extras/litellm_proxy_extras/_logging.py +++ b/litellm-proxy-extras/litellm_proxy_extras/_logging.py @@ -1,12 +1,40 @@ +import json import logging +import os +from datetime import datetime + + +class JsonFormatter(logging.Formatter): + def formatTime(self, record, datefmt=None): + dt = datetime.fromtimestamp(record.created) + return dt.isoformat() + + def format(self, record): + json_record = { + "message": record.getMessage(), + "level": record.levelname, + "timestamp": self.formatTime(record), + } + if record.exc_info: + json_record["stacktrace"] = self.formatException(record.exc_info) + return json.dumps(json_record) + + +def _is_json_enabled(): + try: + import litellm + return getattr(litellm, 'json_logs', False) + except (ImportError, AttributeError): + return os.getenv("JSON_LOGS", "false").lower() == "true" + -# Set up package logger logger = logging.getLogger("litellm_proxy_extras") -if not logger.handlers: # Only add handler if none exists + +if not logger.handlers: handler = logging.StreamHandler() - formatter = logging.Formatter( - "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - ) - handler.setFormatter(formatter) + if _is_json_enabled(): + handler.setFormatter(JsonFormatter()) + else: + handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")) logger.addHandler(handler) logger.setLevel(logging.INFO) diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20251119131227_add_prompt_versioning/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20251119131227_add_prompt_versioning/migration.sql index a9d9528bd24..43eb2401422 100644 --- a/litellm-proxy-extras/litellm_proxy_extras/migrations/20251119131227_add_prompt_versioning/migration.sql +++ b/litellm-proxy-extras/litellm_proxy_extras/migrations/20251119131227_add_prompt_versioning/migration.sql @@ -1,12 +1,12 @@ -- DropIndex -DROP INDEX "LiteLLM_PromptTable_prompt_id_key"; +DROP INDEX IF EXISTS "LiteLLM_PromptTable_prompt_id_key"; -- AlterTable -ALTER TABLE "LiteLLM_PromptTable" ADD COLUMN "version" INTEGER NOT NULL DEFAULT 1; +ALTER TABLE "LiteLLM_PromptTable" +ADD COLUMN "version" INTEGER NOT NULL DEFAULT 1; -- CreateIndex -CREATE INDEX "LiteLLM_PromptTable_prompt_id_idx" ON "LiteLLM_PromptTable"("prompt_id"); +CREATE INDEX "LiteLLM_PromptTable_prompt_id_idx" ON "LiteLLM_PromptTable" ("prompt_id"); -- CreateIndex -CREATE UNIQUE INDEX "LiteLLM_PromptTable_prompt_id_version_key" ON "LiteLLM_PromptTable"("prompt_id", "version"); - +CREATE UNIQUE INDEX "LiteLLM_PromptTable_prompt_id_version_key" ON "LiteLLM_PromptTable" ("prompt_id", "version"); \ No newline at end of file diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20260131150814_add_team_user_to_vector_stores/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260131150814_add_team_user_to_vector_stores/migration.sql new file mode 100644 index 00000000000..2032f76a5de --- /dev/null +++ b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260131150814_add_team_user_to_vector_stores/migration.sql @@ -0,0 +1,10 @@ +-- AlterTable +ALTER TABLE "LiteLLM_ManagedVectorStoresTable" ADD COLUMN "team_id" TEXT, +ADD COLUMN "user_id" TEXT; + +-- CreateIndex +CREATE INDEX "LiteLLM_ManagedVectorStoresTable_team_id_idx" ON "LiteLLM_ManagedVectorStoresTable"("team_id"); + +-- CreateIndex +CREATE INDEX "LiteLLM_ManagedVectorStoresTable_user_id_idx" ON "LiteLLM_ManagedVectorStoresTable"("user_id"); + diff --git a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma index d7aa6e9f0d0..b118400b620 100644 --- a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma +++ b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma @@ -5,6 +5,7 @@ datasource client { generator client { provider = "prisma-client-py" + binaryTargets = ["native", "debian-openssl-1.1.x", "debian-openssl-3.0.x", "linux-musl", "linux-musl-openssl-3.0.x"] } // Budget / Rate Limits for an org @@ -760,6 +761,11 @@ model LiteLLM_ManagedVectorStoresTable { updated_at DateTime @updatedAt litellm_credential_name String? litellm_params Json? + team_id String? + user_id String? + + @@index([team_id]) + @@index([user_id]) } // Guardrails table for storing guardrail configurations diff --git a/litellm-proxy-extras/litellm_proxy_extras/utils.py b/litellm-proxy-extras/litellm_proxy_extras/utils.py index 7ffbe95be13..f3155722187 100644 --- a/litellm-proxy-extras/litellm_proxy_extras/utils.py +++ b/litellm-proxy-extras/litellm_proxy_extras/utils.py @@ -18,14 +18,15 @@ def str_to_bool(value: Optional[str]) -> bool: return value.lower() in ("true", "1", "t", "y", "yes") - def _get_prisma_env() -> dict: """Get environment variables for Prisma, handling offline mode if configured.""" prisma_env = os.environ.copy() if str_to_bool(os.getenv("PRISMA_OFFLINE_MODE")): # These env vars prevent Prisma from attempting downloads prisma_env["NPM_CONFIG_PREFER_OFFLINE"] = "true" - prisma_env["NPM_CONFIG_CACHE"] = os.getenv("NPM_CONFIG_CACHE", "/app/.cache/npm") + prisma_env["NPM_CONFIG_CACHE"] = os.getenv( + "NPM_CONFIG_CACHE", "/app/.cache/npm" + ) return prisma_env @@ -34,29 +35,28 @@ def _get_prisma_command() -> str: if str_to_bool(os.getenv("PRISMA_OFFLINE_MODE")): # Primary location where Prisma Python package installs the CLI default_cli_path = "/app/.cache/prisma-python/binaries/node_modules/.bin/prisma" - + # Check if custom path is provided (for flexibility) custom_cli_path = os.getenv("PRISMA_CLI_PATH") if custom_cli_path and os.path.exists(custom_cli_path): logger.info(f"Using custom Prisma CLI at {custom_cli_path}") return custom_cli_path - + # Check the default location if os.path.exists(default_cli_path): logger.info(f"Using cached Prisma CLI at {default_cli_path}") return default_cli_path - + # If not found, log warning and fall back logger.warning( f"Prisma CLI not found at {default_cli_path}. " "Falling back to Python wrapper (may attempt downloads)" ) - + # Fall back to the Python wrapper (will work in online mode) return "prisma" - class ProxyExtrasDBManager: @staticmethod def _get_prisma_dir() -> str: @@ -119,7 +119,7 @@ class ProxyExtrasDBManager: stdout=open(migration_file, "w"), check=True, timeout=30, - env=prisma_env + env=prisma_env, ) # 3. Mark the migration as applied since it represents current state @@ -134,7 +134,7 @@ class ProxyExtrasDBManager: ], check=True, timeout=30, - env=prisma_env + env=prisma_env, ) return True @@ -159,14 +159,20 @@ class ProxyExtrasDBManager: @staticmethod def _roll_back_migration(migration_name: str): """Mark a specific migration as rolled back""" - # Set up environment for offline mode if configured + # Set up environment for offline mode if configured prisma_env = _get_prisma_env() subprocess.run( - [_get_prisma_command(), "migrate", "resolve", "--rolled-back", migration_name], + [ + _get_prisma_command(), + "migrate", + "resolve", + "--rolled-back", + migration_name, + ], timeout=60, check=True, capture_output=True, - env=prisma_env + env=prisma_env, ) @staticmethod @@ -178,7 +184,7 @@ class ProxyExtrasDBManager: timeout=60, check=True, capture_output=True, - env=prisma_env + env=prisma_env, ) @staticmethod @@ -228,6 +234,8 @@ class ProxyExtrasDBManager: r"duplicate key value violates", r"relation .* already exists", r"constraint .* already exists", + r"does not exist", + r"Can't drop database.* because it doesn't exist", ] for pattern in idempotent_patterns: @@ -248,7 +256,7 @@ class ProxyExtrasDBManager: if not database_url: logger.error("DATABASE_URL not set") return - + diff_dir = ( Path(migrations_dir) / "migrations" @@ -283,7 +291,7 @@ class ProxyExtrasDBManager: check=True, timeout=60, stdout=f, - env=_get_prisma_env() + env=_get_prisma_env(), ) except subprocess.CalledProcessError as e: logger.warning(f"Failed to generate migration diff: {e.stderr}") @@ -313,7 +321,7 @@ class ProxyExtrasDBManager: check=True, capture_output=True, text=True, - env=_get_prisma_env() + env=_get_prisma_env(), ) logger.info(f"prisma db execute stdout: {result.stdout}") logger.info("✅ Migration diff applied successfully") @@ -331,12 +339,18 @@ class ProxyExtrasDBManager: try: logger.info(f"Resolving migration: {migration_name}") subprocess.run( - [_get_prisma_command(), "migrate", "resolve", "--applied", migration_name], + [ + _get_prisma_command(), + "migrate", + "resolve", + "--applied", + migration_name, + ], timeout=60, check=True, capture_output=True, text=True, - env=_get_prisma_env() + env=_get_prisma_env(), ) logger.debug(f"Resolved migration: {migration_name}") except subprocess.CalledProcessError as e: @@ -375,7 +389,7 @@ class ProxyExtrasDBManager: check=True, capture_output=True, text=True, - env=_get_prisma_env() + env=_get_prisma_env(), ) logger.info(f"prisma migrate deploy stdout: {result.stdout}") @@ -397,27 +411,42 @@ class ProxyExtrasDBManager: ) if migration_match: failed_migration = migration_match.group(1) - logger.info( - f"Found failed migration: {failed_migration}, marking as rolled back" - ) - # Mark the failed migration as rolled back - subprocess.run( - [ - _get_prisma_command(), - "migrate", - "resolve", - "--rolled-back", - failed_migration, - ], - timeout=60, - check=True, - capture_output=True, - text=True, - env=_get_prisma_env() - ) - logger.info( - f"✅ Migration {failed_migration} marked as rolled back... retrying" - ) + if ProxyExtrasDBManager._is_idempotent_error(e.stderr): + logger.info( + f"Migration {failed_migration} failed due to idempotent error (e.g., column already exists), resolving as applied" + ) + ProxyExtrasDBManager._roll_back_migration( + failed_migration + ) + ProxyExtrasDBManager._resolve_specific_migration( + failed_migration + ) + logger.info( + f"✅ Migration {failed_migration} resolved." + ) + return True + else: + logger.info( + f"Found failed migration: {failed_migration}, marking as rolled back" + ) + # Mark the failed migration as rolled back + subprocess.run( + [ + _get_prisma_command(), + "migrate", + "resolve", + "--rolled-back", + failed_migration, + ], + timeout=60, + check=True, + capture_output=True, + text=True, + env=_get_prisma_env(), + ) + logger.info( + f"✅ Migration {failed_migration} marked as rolled back... retrying" + ) elif ( "P3005" in e.stderr and "database schema is not empty" in e.stderr diff --git a/litellm-proxy-extras/pyproject.toml b/litellm-proxy-extras/pyproject.toml index 862ebaffe2a..fb6996b71db 100644 --- a/litellm-proxy-extras/pyproject.toml +++ b/litellm-proxy-extras/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm-proxy-extras" -version = "0.4.26" +version = "0.4.29" description = "Additional files for the LiteLLM Proxy. Reduces the size of the main litellm package." authors = ["BerriAI"] readme = "README.md" @@ -22,7 +22,7 @@ requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "0.4.26" +version = "0.4.29" version_files = [ "pyproject.toml:version", "../requirements.txt:litellm-proxy-extras==", diff --git a/litellm/__init__.py b/litellm/__init__.py index e5c09702b9b..112d58d49d8 100644 --- a/litellm/__init__.py +++ b/litellm/__init__.py @@ -80,6 +80,8 @@ import dotenv litellm_mode = os.getenv("LITELLM_MODE", "DEV") # "PRODUCTION", "DEV" if litellm_mode == "DEV": dotenv.load_dotenv() + + #################################################### if set_verbose: _turn_on_debug() @@ -254,6 +256,7 @@ disable_streaming_logging: bool = False disable_token_counter: bool = False disable_add_transform_inline_image_block: bool = False disable_add_user_agent_to_request_tags: bool = False +disable_anthropic_gemini_context_caching_transform: bool = False extra_spend_tag_headers: Optional[List[str]] = None in_memory_llm_clients_cache: "LLMClientCache" safe_memory_mode: bool = False @@ -348,7 +351,7 @@ default_team_settings: Optional[List] = None max_user_budget: Optional[float] = None default_max_internal_user_budget: Optional[float] = None max_internal_user_budget: Optional[float] = None -max_ui_session_budget: Optional[float] = 10 # $10 USD budgets for UI Chat sessions +max_ui_session_budget: Optional[float] = 0.25 # $0.25 USD budgets for UI Chat sessions internal_user_budget_duration: Optional[str] = None tag_budget_config: Optional[Dict[str, "BudgetConfig"]] = None max_end_user_budget: Optional[float] = None @@ -1467,6 +1470,7 @@ if TYPE_CHECKING: from .llms.azure.chat.gpt_5_transformation import AzureOpenAIGPT5Config as AzureOpenAIGPT5Config from .llms.azure.completion.transformation import AzureOpenAITextConfig as AzureOpenAITextConfig from .llms.hosted_vllm.chat.transformation import HostedVLLMChatConfig as HostedVLLMChatConfig + from .llms.hosted_vllm.embedding.transformation import HostedVLLMEmbeddingConfig as HostedVLLMEmbeddingConfig from .llms.github_copilot.chat.transformation import GithubCopilotConfig as GithubCopilotConfig from .llms.github_copilot.responses.transformation import GithubCopilotResponsesAPIConfig as GithubCopilotResponsesAPIConfig from .llms.github_copilot.embedding.transformation import GithubCopilotEmbeddingConfig as GithubCopilotEmbeddingConfig diff --git a/litellm/_lazy_imports_registry.py b/litellm/_lazy_imports_registry.py index a92c6f95b0e..0e52e9a59eb 100644 --- a/litellm/_lazy_imports_registry.py +++ b/litellm/_lazy_imports_registry.py @@ -20,25 +20,53 @@ LITELLM_LOGGING_NAMES = ( # Utils names that support lazy loading via _lazy_import_utils UTILS_NAMES = ( - "exception_type", "get_optional_params", "get_response_string", "token_counter", - "create_pretrained_tokenizer", "create_tokenizer", "supports_function_calling", - "supports_web_search", "supports_url_context", "supports_response_schema", - "supports_parallel_function_calling", "supports_vision", "supports_audio_input", - "supports_audio_output", "supports_system_messages", "supports_reasoning", - "get_litellm_params", "acreate", "get_max_tokens", "get_model_info", - "register_prompt_template", "validate_environment", "check_valid_key", - "register_model", "encode", "decode", "_calculate_retry_after", "_should_retry", - "get_supported_openai_params", "get_api_base", "get_first_chars_messages", - "ModelResponse", "ModelResponseStream", "EmbeddingResponse", "ImageResponse", - "TranscriptionResponse", "TextCompletionResponse", "get_provider_fields", - "ModelResponseListIterator", "get_valid_models", "timeout", - "get_llm_provider", "remove_index_from_tool_calls", + "exception_type", + "get_optional_params", + "get_response_string", + "token_counter", + "create_pretrained_tokenizer", + "create_tokenizer", + "supports_function_calling", + "supports_web_search", + "supports_url_context", + "supports_response_schema", + "supports_parallel_function_calling", + "supports_vision", + "supports_audio_input", + "supports_audio_output", + "supports_system_messages", + "supports_reasoning", + "get_litellm_params", + "acreate", + "get_max_tokens", + "get_model_info", + "register_prompt_template", + "validate_environment", + "check_valid_key", + "register_model", + "encode", + "decode", + "_calculate_retry_after", + "_should_retry", + "get_supported_openai_params", + "get_api_base", + "get_first_chars_messages", + "ModelResponse", + "ModelResponseStream", + "EmbeddingResponse", + "ImageResponse", + "TranscriptionResponse", + "TextCompletionResponse", + "get_provider_fields", + "ModelResponseListIterator", + "get_valid_models", + "timeout", + "get_llm_provider", + "remove_index_from_tool_calls", ) # Token counter names that support lazy loading via _lazy_import_token_counter -TOKEN_COUNTER_NAMES = ( - "get_modified_max_tokens", -) +TOKEN_COUNTER_NAMES = ("get_modified_max_tokens",) # LLM client cache names that support lazy loading via _lazy_import_llm_client_cache LLM_CLIENT_CACHE_NAMES = ( @@ -47,9 +75,7 @@ LLM_CLIENT_CACHE_NAMES = ( ) # Bedrock type names that support lazy loading via _lazy_import_bedrock_types -BEDROCK_TYPES_NAMES = ( - "COHERE_EMBEDDING_INPUT_TYPES", -) +BEDROCK_TYPES_NAMES = ("COHERE_EMBEDDING_INPUT_TYPES",) # Common types from litellm.types.utils that support lazy loading via # _lazy_import_types_utils @@ -236,6 +262,7 @@ LLM_CONFIG_NAMES = ( "AzureOpenAIGPT5Config", "AzureOpenAITextConfig", "HostedVLLMChatConfig", + "HostedVLLMEmbeddingConfig", # Alias for backwards compatibility "VolcEngineConfig", # Alias for VolcEngineChatConfig "LlamafileChatConfig", @@ -388,7 +415,10 @@ _UTILS_IMPORT_MAP = { "supports_web_search": (".utils", "supports_web_search"), "supports_url_context": (".utils", "supports_url_context"), "supports_response_schema": (".utils", "supports_response_schema"), - "supports_parallel_function_calling": (".utils", "supports_parallel_function_calling"), + "supports_parallel_function_calling": ( + ".utils", + "supports_parallel_function_calling", + ), "supports_vision": (".utils", "supports_vision"), "supports_audio_input": (".utils", "supports_audio_input"), "supports_audio_output": (".utils", "supports_audio_output"), @@ -419,8 +449,14 @@ _UTILS_IMPORT_MAP = { "ModelResponseListIterator": (".utils", "ModelResponseListIterator"), "get_valid_models": (".utils", "get_valid_models"), "timeout": (".timeout", "timeout"), - "get_llm_provider": ("litellm.litellm_core_utils.get_llm_provider_logic", "get_llm_provider"), - "remove_index_from_tool_calls": ("litellm.litellm_core_utils.core_helpers", "remove_index_from_tool_calls"), + "get_llm_provider": ( + "litellm.litellm_core_utils.get_llm_provider_logic", + "get_llm_provider", + ), + "remove_index_from_tool_calls": ( + "litellm.litellm_core_utils.core_helpers", + "remove_index_from_tool_calls", + ), } _COST_CALCULATOR_IMPORT_MAP = { @@ -442,11 +478,17 @@ _TYPES_UTILS_IMPORT_MAP = { } _TOKEN_COUNTER_IMPORT_MAP = { - "get_modified_max_tokens": ("litellm.litellm_core_utils.token_counter", "get_modified_max_tokens"), + "get_modified_max_tokens": ( + "litellm.litellm_core_utils.token_counter", + "get_modified_max_tokens", + ), } _BEDROCK_TYPES_IMPORT_MAP = { - "COHERE_EMBEDDING_INPUT_TYPES": ("litellm.types.llms.bedrock", "COHERE_EMBEDDING_INPUT_TYPES"), + "COHERE_EMBEDDING_INPUT_TYPES": ( + "litellm.types.llms.bedrock", + "COHERE_EMBEDDING_INPUT_TYPES", + ), } _CACHING_IMPORT_MAP = { @@ -458,294 +500,868 @@ _CACHING_IMPORT_MAP = { _LITELLM_LOGGING_IMPORT_MAP = { "Logging": ("litellm.litellm_core_utils.litellm_logging", "Logging"), - "modify_integration": ("litellm.litellm_core_utils.litellm_logging", "modify_integration"), + "modify_integration": ( + "litellm.litellm_core_utils.litellm_logging", + "modify_integration", + ), } _DOTPROMPT_IMPORT_MAP = { - "global_prompt_manager": ("litellm.integrations.dotprompt", "global_prompt_manager"), - "global_prompt_directory": ("litellm.integrations.dotprompt", "global_prompt_directory"), - "set_global_prompt_directory": ("litellm.integrations.dotprompt", "set_global_prompt_directory"), + "global_prompt_manager": ( + "litellm.integrations.dotprompt", + "global_prompt_manager", + ), + "global_prompt_directory": ( + "litellm.integrations.dotprompt", + "global_prompt_directory", + ), + "set_global_prompt_directory": ( + "litellm.integrations.dotprompt", + "set_global_prompt_directory", + ), } _TYPES_IMPORT_MAP = { "GuardrailItem": ("litellm.types.guardrails", "GuardrailItem"), - "DefaultTeamSSOParams": ("litellm.types.proxy.management_endpoints.ui_sso", "DefaultTeamSSOParams"), - "LiteLLM_UpperboundKeyGenerateParams": ("litellm.types.proxy.management_endpoints.ui_sso", "LiteLLM_UpperboundKeyGenerateParams"), - "KeyManagementSystem": ("litellm.types.secret_managers.main", "KeyManagementSystem"), - "PriorityReservationSettings": ("litellm.types.utils", "PriorityReservationSettings"), + "DefaultTeamSSOParams": ( + "litellm.types.proxy.management_endpoints.ui_sso", + "DefaultTeamSSOParams", + ), + "LiteLLM_UpperboundKeyGenerateParams": ( + "litellm.types.proxy.management_endpoints.ui_sso", + "LiteLLM_UpperboundKeyGenerateParams", + ), + "KeyManagementSystem": ( + "litellm.types.secret_managers.main", + "KeyManagementSystem", + ), + "PriorityReservationSettings": ( + "litellm.types.utils", + "PriorityReservationSettings", + ), "CustomLogger": ("litellm.integrations.custom_logger", "CustomLogger"), - "LoggingCallbackManager": ("litellm.litellm_core_utils.logging_callback_manager", "LoggingCallbackManager"), - "DatadogLLMObsInitParams": ("litellm.types.integrations.datadog_llm_obs", "DatadogLLMObsInitParams"), + "LoggingCallbackManager": ( + "litellm.litellm_core_utils.logging_callback_manager", + "LoggingCallbackManager", + ), + "DatadogLLMObsInitParams": ( + "litellm.types.integrations.datadog_llm_obs", + "DatadogLLMObsInitParams", + ), } _LLM_PROVIDER_LOGIC_IMPORT_MAP = { - "get_llm_provider": ("litellm.litellm_core_utils.get_llm_provider_logic", "get_llm_provider"), - "remove_index_from_tool_calls": ("litellm.litellm_core_utils.core_helpers", "remove_index_from_tool_calls"), + "get_llm_provider": ( + "litellm.litellm_core_utils.get_llm_provider_logic", + "get_llm_provider", + ), + "remove_index_from_tool_calls": ( + "litellm.litellm_core_utils.core_helpers", + "remove_index_from_tool_calls", + ), } _LLM_CONFIGS_IMPORT_MAP = { - "AmazonConverseConfig": (".llms.bedrock.chat.converse_transformation", "AmazonConverseConfig"), + "AmazonConverseConfig": ( + ".llms.bedrock.chat.converse_transformation", + "AmazonConverseConfig", + ), "OpenAILikeChatConfig": (".llms.openai_like.chat.handler", "OpenAILikeChatConfig"), - "GaladrielChatConfig": (".llms.galadriel.chat.transformation", "GaladrielChatConfig"), + "GaladrielChatConfig": ( + ".llms.galadriel.chat.transformation", + "GaladrielChatConfig", + ), "GithubChatConfig": (".llms.github.chat.transformation", "GithubChatConfig"), - "AzureAnthropicConfig": (".llms.azure_ai.anthropic.transformation", "AzureAnthropicConfig"), + "AzureAnthropicConfig": ( + ".llms.azure_ai.anthropic.transformation", + "AzureAnthropicConfig", + ), "BytezChatConfig": (".llms.bytez.chat.transformation", "BytezChatConfig"), - "CompactifAIChatConfig": (".llms.compactifai.chat.transformation", "CompactifAIChatConfig"), + "CompactifAIChatConfig": ( + ".llms.compactifai.chat.transformation", + "CompactifAIChatConfig", + ), "EmpowerChatConfig": (".llms.empower.chat.transformation", "EmpowerChatConfig"), "MinimaxChatConfig": (".llms.minimax.chat.transformation", "MinimaxChatConfig"), - "AiohttpOpenAIChatConfig": (".llms.aiohttp_openai.chat.transformation", "AiohttpOpenAIChatConfig"), - "HuggingFaceChatConfig": (".llms.huggingface.chat.transformation", "HuggingFaceChatConfig"), - "HuggingFaceEmbeddingConfig": (".llms.huggingface.embedding.transformation", "HuggingFaceEmbeddingConfig"), + "AiohttpOpenAIChatConfig": ( + ".llms.aiohttp_openai.chat.transformation", + "AiohttpOpenAIChatConfig", + ), + "HuggingFaceChatConfig": ( + ".llms.huggingface.chat.transformation", + "HuggingFaceChatConfig", + ), + "HuggingFaceEmbeddingConfig": ( + ".llms.huggingface.embedding.transformation", + "HuggingFaceEmbeddingConfig", + ), "OobaboogaConfig": (".llms.oobabooga.chat.transformation", "OobaboogaConfig"), "MaritalkConfig": (".llms.maritalk", "MaritalkConfig"), "OpenrouterConfig": (".llms.openrouter.chat.transformation", "OpenrouterConfig"), "DataRobotConfig": (".llms.datarobot.chat.transformation", "DataRobotConfig"), "AnthropicConfig": (".llms.anthropic.chat.transformation", "AnthropicConfig"), - "AnthropicTextConfig": (".llms.anthropic.completion.transformation", "AnthropicTextConfig"), + "AnthropicTextConfig": ( + ".llms.anthropic.completion.transformation", + "AnthropicTextConfig", + ), "GroqSTTConfig": (".llms.groq.stt.transformation", "GroqSTTConfig"), "TritonConfig": (".llms.triton.completion.transformation", "TritonConfig"), - "TritonGenerateConfig": (".llms.triton.completion.transformation", "TritonGenerateConfig"), - "TritonInferConfig": (".llms.triton.completion.transformation", "TritonInferConfig"), - "TritonEmbeddingConfig": (".llms.triton.embedding.transformation", "TritonEmbeddingConfig"), - "HuggingFaceRerankConfig": (".llms.huggingface.rerank.transformation", "HuggingFaceRerankConfig"), + "TritonGenerateConfig": ( + ".llms.triton.completion.transformation", + "TritonGenerateConfig", + ), + "TritonInferConfig": ( + ".llms.triton.completion.transformation", + "TritonInferConfig", + ), + "TritonEmbeddingConfig": ( + ".llms.triton.embedding.transformation", + "TritonEmbeddingConfig", + ), + "HuggingFaceRerankConfig": ( + ".llms.huggingface.rerank.transformation", + "HuggingFaceRerankConfig", + ), "DatabricksConfig": (".llms.databricks.chat.transformation", "DatabricksConfig"), - "DatabricksEmbeddingConfig": (".llms.databricks.embed.transformation", "DatabricksEmbeddingConfig"), + "DatabricksEmbeddingConfig": ( + ".llms.databricks.embed.transformation", + "DatabricksEmbeddingConfig", + ), "PredibaseConfig": (".llms.predibase.chat.transformation", "PredibaseConfig"), "ReplicateConfig": (".llms.replicate.chat.transformation", "ReplicateConfig"), "SnowflakeConfig": (".llms.snowflake.chat.transformation", "SnowflakeConfig"), "CohereRerankConfig": (".llms.cohere.rerank.transformation", "CohereRerankConfig"), - "CohereRerankV2Config": (".llms.cohere.rerank_v2.transformation", "CohereRerankV2Config"), - "AzureAIRerankConfig": (".llms.azure_ai.rerank.transformation", "AzureAIRerankConfig"), - "InfinityRerankConfig": (".llms.infinity.rerank.transformation", "InfinityRerankConfig"), + "CohereRerankV2Config": ( + ".llms.cohere.rerank_v2.transformation", + "CohereRerankV2Config", + ), + "AzureAIRerankConfig": ( + ".llms.azure_ai.rerank.transformation", + "AzureAIRerankConfig", + ), + "InfinityRerankConfig": ( + ".llms.infinity.rerank.transformation", + "InfinityRerankConfig", + ), "JinaAIRerankConfig": (".llms.jina_ai.rerank.transformation", "JinaAIRerankConfig"), - "DeepinfraRerankConfig": (".llms.deepinfra.rerank.transformation", "DeepinfraRerankConfig"), - "HostedVLLMRerankConfig": (".llms.hosted_vllm.rerank.transformation", "HostedVLLMRerankConfig"), - "NvidiaNimRerankConfig": (".llms.nvidia_nim.rerank.transformation", "NvidiaNimRerankConfig"), - "NvidiaNimRankingConfig": (".llms.nvidia_nim.rerank.ranking_transformation", "NvidiaNimRankingConfig"), - "VertexAIRerankConfig": (".llms.vertex_ai.rerank.transformation", "VertexAIRerankConfig"), - "FireworksAIRerankConfig": (".llms.fireworks_ai.rerank.transformation", "FireworksAIRerankConfig"), + "DeepinfraRerankConfig": ( + ".llms.deepinfra.rerank.transformation", + "DeepinfraRerankConfig", + ), + "HostedVLLMRerankConfig": ( + ".llms.hosted_vllm.rerank.transformation", + "HostedVLLMRerankConfig", + ), + "NvidiaNimRerankConfig": ( + ".llms.nvidia_nim.rerank.transformation", + "NvidiaNimRerankConfig", + ), + "NvidiaNimRankingConfig": ( + ".llms.nvidia_nim.rerank.ranking_transformation", + "NvidiaNimRankingConfig", + ), + "VertexAIRerankConfig": ( + ".llms.vertex_ai.rerank.transformation", + "VertexAIRerankConfig", + ), + "FireworksAIRerankConfig": ( + ".llms.fireworks_ai.rerank.transformation", + "FireworksAIRerankConfig", + ), "VoyageRerankConfig": (".llms.voyage.rerank.transformation", "VoyageRerankConfig"), "ClarifaiConfig": (".llms.clarifai.chat.transformation", "ClarifaiConfig"), "AI21ChatConfig": (".llms.ai21.chat.transformation", "AI21ChatConfig"), "LlamaAPIConfig": (".llms.meta_llama.chat.transformation", "LlamaAPIConfig"), - "TogetherAITextCompletionConfig": (".llms.together_ai.completion.transformation", "TogetherAITextCompletionConfig"), - "CloudflareChatConfig": (".llms.cloudflare.chat.transformation", "CloudflareChatConfig"), + "TogetherAITextCompletionConfig": ( + ".llms.together_ai.completion.transformation", + "TogetherAITextCompletionConfig", + ), + "CloudflareChatConfig": ( + ".llms.cloudflare.chat.transformation", + "CloudflareChatConfig", + ), "NovitaConfig": (".llms.novita.chat.transformation", "NovitaConfig"), "PetalsConfig": (".llms.petals.completion.transformation", "PetalsConfig"), "OllamaChatConfig": (".llms.ollama.chat.transformation", "OllamaChatConfig"), "OllamaConfig": (".llms.ollama.completion.transformation", "OllamaConfig"), "SagemakerConfig": (".llms.sagemaker.completion.transformation", "SagemakerConfig"), - "SagemakerChatConfig": (".llms.sagemaker.chat.transformation", "SagemakerChatConfig"), + "SagemakerChatConfig": ( + ".llms.sagemaker.chat.transformation", + "SagemakerChatConfig", + ), "CohereChatConfig": (".llms.cohere.chat.transformation", "CohereChatConfig"), - "AnthropicMessagesConfig": (".llms.anthropic.experimental_pass_through.messages.transformation", "AnthropicMessagesConfig"), - "AmazonAnthropicClaudeMessagesConfig": (".llms.bedrock.messages.invoke_transformations.anthropic_claude3_transformation", "AmazonAnthropicClaudeMessagesConfig"), + "AnthropicMessagesConfig": ( + ".llms.anthropic.experimental_pass_through.messages.transformation", + "AnthropicMessagesConfig", + ), + "AmazonAnthropicClaudeMessagesConfig": ( + ".llms.bedrock.messages.invoke_transformations.anthropic_claude3_transformation", + "AmazonAnthropicClaudeMessagesConfig", + ), "TogetherAIConfig": (".llms.together_ai.chat", "TogetherAIConfig"), "NLPCloudConfig": (".llms.nlp_cloud.chat.handler", "NLPCloudConfig"), - "VertexGeminiConfig": (".llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini", "VertexGeminiConfig"), - "GoogleAIStudioGeminiConfig": (".llms.gemini.chat.transformation", "GoogleAIStudioGeminiConfig"), - "VertexAIAnthropicConfig": (".llms.vertex_ai.vertex_ai_partner_models.anthropic.transformation", "VertexAIAnthropicConfig"), - "VertexAILlama3Config": (".llms.vertex_ai.vertex_ai_partner_models.llama3.transformation", "VertexAILlama3Config"), - "VertexAIAi21Config": (".llms.vertex_ai.vertex_ai_partner_models.ai21.transformation", "VertexAIAi21Config"), - "AmazonCohereChatConfig": (".llms.bedrock.chat.invoke_handler", "AmazonCohereChatConfig"), - "AmazonBedrockGlobalConfig": (".llms.bedrock.common_utils", "AmazonBedrockGlobalConfig"), - "AmazonAI21Config": (".llms.bedrock.chat.invoke_transformations.amazon_ai21_transformation", "AmazonAI21Config"), - "AmazonInvokeNovaConfig": (".llms.bedrock.chat.invoke_transformations.amazon_nova_transformation", "AmazonInvokeNovaConfig"), - "AmazonQwen2Config": (".llms.bedrock.chat.invoke_transformations.amazon_qwen2_transformation", "AmazonQwen2Config"), - "AmazonQwen3Config": (".llms.bedrock.chat.invoke_transformations.amazon_qwen3_transformation", "AmazonQwen3Config"), + "VertexGeminiConfig": ( + ".llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini", + "VertexGeminiConfig", + ), + "GoogleAIStudioGeminiConfig": ( + ".llms.gemini.chat.transformation", + "GoogleAIStudioGeminiConfig", + ), + "VertexAIAnthropicConfig": ( + ".llms.vertex_ai.vertex_ai_partner_models.anthropic.transformation", + "VertexAIAnthropicConfig", + ), + "VertexAILlama3Config": ( + ".llms.vertex_ai.vertex_ai_partner_models.llama3.transformation", + "VertexAILlama3Config", + ), + "VertexAIAi21Config": ( + ".llms.vertex_ai.vertex_ai_partner_models.ai21.transformation", + "VertexAIAi21Config", + ), + "AmazonCohereChatConfig": ( + ".llms.bedrock.chat.invoke_handler", + "AmazonCohereChatConfig", + ), + "AmazonBedrockGlobalConfig": ( + ".llms.bedrock.common_utils", + "AmazonBedrockGlobalConfig", + ), + "AmazonAI21Config": ( + ".llms.bedrock.chat.invoke_transformations.amazon_ai21_transformation", + "AmazonAI21Config", + ), + "AmazonInvokeNovaConfig": ( + ".llms.bedrock.chat.invoke_transformations.amazon_nova_transformation", + "AmazonInvokeNovaConfig", + ), + "AmazonQwen2Config": ( + ".llms.bedrock.chat.invoke_transformations.amazon_qwen2_transformation", + "AmazonQwen2Config", + ), + "AmazonQwen3Config": ( + ".llms.bedrock.chat.invoke_transformations.amazon_qwen3_transformation", + "AmazonQwen3Config", + ), # Aliases for backwards compatibility - "VertexAIConfig": (".llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini", "VertexGeminiConfig"), # Alias - "GeminiConfig": (".llms.gemini.chat.transformation", "GoogleAIStudioGeminiConfig"), # Alias - "AmazonAnthropicConfig": (".llms.bedrock.chat.invoke_transformations.anthropic_claude2_transformation", "AmazonAnthropicConfig"), - "AmazonAnthropicClaudeConfig": (".llms.bedrock.chat.invoke_transformations.anthropic_claude3_transformation", "AmazonAnthropicClaudeConfig"), - "AmazonCohereConfig": (".llms.bedrock.chat.invoke_transformations.amazon_cohere_transformation", "AmazonCohereConfig"), - "AmazonLlamaConfig": (".llms.bedrock.chat.invoke_transformations.amazon_llama_transformation", "AmazonLlamaConfig"), - "AmazonDeepSeekR1Config": (".llms.bedrock.chat.invoke_transformations.amazon_deepseek_transformation", "AmazonDeepSeekR1Config"), - "AmazonMistralConfig": (".llms.bedrock.chat.invoke_transformations.amazon_mistral_transformation", "AmazonMistralConfig"), - "AmazonMoonshotConfig": (".llms.bedrock.chat.invoke_transformations.amazon_moonshot_transformation", "AmazonMoonshotConfig"), - "AmazonTitanConfig": (".llms.bedrock.chat.invoke_transformations.amazon_titan_transformation", "AmazonTitanConfig"), - "AmazonTwelveLabsPegasusConfig": (".llms.bedrock.chat.invoke_transformations.amazon_twelvelabs_pegasus_transformation", "AmazonTwelveLabsPegasusConfig"), - "AmazonInvokeConfig": (".llms.bedrock.chat.invoke_transformations.base_invoke_transformation", "AmazonInvokeConfig"), - "AmazonBedrockOpenAIConfig": (".llms.bedrock.chat.invoke_transformations.amazon_openai_transformation", "AmazonBedrockOpenAIConfig"), - "AmazonStabilityConfig": (".llms.bedrock.image_generation.amazon_stability1_transformation", "AmazonStabilityConfig"), - "AmazonStability3Config": (".llms.bedrock.image_generation.amazon_stability3_transformation", "AmazonStability3Config"), - "AmazonNovaCanvasConfig": (".llms.bedrock.image_generation.amazon_nova_canvas_transformation", "AmazonNovaCanvasConfig"), - "AmazonTitanG1Config": (".llms.bedrock.embed.amazon_titan_g1_transformation", "AmazonTitanG1Config"), - "AmazonTitanMultimodalEmbeddingG1Config": (".llms.bedrock.embed.amazon_titan_multimodal_transformation", "AmazonTitanMultimodalEmbeddingG1Config"), + "VertexAIConfig": ( + ".llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini", + "VertexGeminiConfig", + ), # Alias + "GeminiConfig": ( + ".llms.gemini.chat.transformation", + "GoogleAIStudioGeminiConfig", + ), # Alias + "AmazonAnthropicConfig": ( + ".llms.bedrock.chat.invoke_transformations.anthropic_claude2_transformation", + "AmazonAnthropicConfig", + ), + "AmazonAnthropicClaudeConfig": ( + ".llms.bedrock.chat.invoke_transformations.anthropic_claude3_transformation", + "AmazonAnthropicClaudeConfig", + ), + "AmazonCohereConfig": ( + ".llms.bedrock.chat.invoke_transformations.amazon_cohere_transformation", + "AmazonCohereConfig", + ), + "AmazonLlamaConfig": ( + ".llms.bedrock.chat.invoke_transformations.amazon_llama_transformation", + "AmazonLlamaConfig", + ), + "AmazonDeepSeekR1Config": ( + ".llms.bedrock.chat.invoke_transformations.amazon_deepseek_transformation", + "AmazonDeepSeekR1Config", + ), + "AmazonMistralConfig": ( + ".llms.bedrock.chat.invoke_transformations.amazon_mistral_transformation", + "AmazonMistralConfig", + ), + "AmazonMoonshotConfig": ( + ".llms.bedrock.chat.invoke_transformations.amazon_moonshot_transformation", + "AmazonMoonshotConfig", + ), + "AmazonTitanConfig": ( + ".llms.bedrock.chat.invoke_transformations.amazon_titan_transformation", + "AmazonTitanConfig", + ), + "AmazonTwelveLabsPegasusConfig": ( + ".llms.bedrock.chat.invoke_transformations.amazon_twelvelabs_pegasus_transformation", + "AmazonTwelveLabsPegasusConfig", + ), + "AmazonInvokeConfig": ( + ".llms.bedrock.chat.invoke_transformations.base_invoke_transformation", + "AmazonInvokeConfig", + ), + "AmazonBedrockOpenAIConfig": ( + ".llms.bedrock.chat.invoke_transformations.amazon_openai_transformation", + "AmazonBedrockOpenAIConfig", + ), + "AmazonStabilityConfig": ( + ".llms.bedrock.image_generation.amazon_stability1_transformation", + "AmazonStabilityConfig", + ), + "AmazonStability3Config": ( + ".llms.bedrock.image_generation.amazon_stability3_transformation", + "AmazonStability3Config", + ), + "AmazonNovaCanvasConfig": ( + ".llms.bedrock.image_generation.amazon_nova_canvas_transformation", + "AmazonNovaCanvasConfig", + ), + "AmazonTitanG1Config": ( + ".llms.bedrock.embed.amazon_titan_g1_transformation", + "AmazonTitanG1Config", + ), + "AmazonTitanMultimodalEmbeddingG1Config": ( + ".llms.bedrock.embed.amazon_titan_multimodal_transformation", + "AmazonTitanMultimodalEmbeddingG1Config", + ), "CohereV2ChatConfig": (".llms.cohere.chat.v2_transformation", "CohereV2ChatConfig"), - "BedrockCohereEmbeddingConfig": (".llms.bedrock.embed.cohere_transformation", "BedrockCohereEmbeddingConfig"), - "TwelveLabsMarengoEmbeddingConfig": (".llms.bedrock.embed.twelvelabs_marengo_transformation", "TwelveLabsMarengoEmbeddingConfig"), - "AmazonNovaEmbeddingConfig": (".llms.bedrock.embed.amazon_nova_transformation", "AmazonNovaEmbeddingConfig"), + "BedrockCohereEmbeddingConfig": ( + ".llms.bedrock.embed.cohere_transformation", + "BedrockCohereEmbeddingConfig", + ), + "TwelveLabsMarengoEmbeddingConfig": ( + ".llms.bedrock.embed.twelvelabs_marengo_transformation", + "TwelveLabsMarengoEmbeddingConfig", + ), + "AmazonNovaEmbeddingConfig": ( + ".llms.bedrock.embed.amazon_nova_transformation", + "AmazonNovaEmbeddingConfig", + ), "OpenAIConfig": (".llms.openai.openai", "OpenAIConfig"), "MistralEmbeddingConfig": (".llms.openai.openai", "MistralEmbeddingConfig"), - "OpenAIImageVariationConfig": (".llms.openai.image_variations.transformation", "OpenAIImageVariationConfig"), + "OpenAIImageVariationConfig": ( + ".llms.openai.image_variations.transformation", + "OpenAIImageVariationConfig", + ), "DeepInfraConfig": (".llms.deepinfra.chat.transformation", "DeepInfraConfig"), - "DeepgramAudioTranscriptionConfig": (".llms.deepgram.audio_transcription.transformation", "DeepgramAudioTranscriptionConfig"), - "TopazImageVariationConfig": (".llms.topaz.image_variations.transformation", "TopazImageVariationConfig"), - "OpenAITextCompletionConfig": ("litellm.llms.openai.completion.transformation", "OpenAITextCompletionConfig"), + "DeepgramAudioTranscriptionConfig": ( + ".llms.deepgram.audio_transcription.transformation", + "DeepgramAudioTranscriptionConfig", + ), + "TopazImageVariationConfig": ( + ".llms.topaz.image_variations.transformation", + "TopazImageVariationConfig", + ), + "OpenAITextCompletionConfig": ( + "litellm.llms.openai.completion.transformation", + "OpenAITextCompletionConfig", + ), "GroqChatConfig": (".llms.groq.chat.transformation", "GroqChatConfig"), - "GenAIHubOrchestrationConfig": (".llms.sap.chat.transformation", "GenAIHubOrchestrationConfig"), - "VoyageEmbeddingConfig": (".llms.voyage.embedding.transformation", "VoyageEmbeddingConfig"), - "VoyageContextualEmbeddingConfig": (".llms.voyage.embedding.transformation_contextual", "VoyageContextualEmbeddingConfig"), - "InfinityEmbeddingConfig": (".llms.infinity.embedding.transformation", "InfinityEmbeddingConfig"), - "AzureAIStudioConfig": (".llms.azure_ai.chat.transformation", "AzureAIStudioConfig"), + "GenAIHubOrchestrationConfig": ( + ".llms.sap.chat.transformation", + "GenAIHubOrchestrationConfig", + ), + "VoyageEmbeddingConfig": ( + ".llms.voyage.embedding.transformation", + "VoyageEmbeddingConfig", + ), + "VoyageContextualEmbeddingConfig": ( + ".llms.voyage.embedding.transformation_contextual", + "VoyageContextualEmbeddingConfig", + ), + "InfinityEmbeddingConfig": ( + ".llms.infinity.embedding.transformation", + "InfinityEmbeddingConfig", + ), + "AzureAIStudioConfig": ( + ".llms.azure_ai.chat.transformation", + "AzureAIStudioConfig", + ), "MistralConfig": (".llms.mistral.chat.transformation", "MistralConfig"), - "OpenAIResponsesAPIConfig": (".llms.openai.responses.transformation", "OpenAIResponsesAPIConfig"), - "AzureOpenAIResponsesAPIConfig": (".llms.azure.responses.transformation", "AzureOpenAIResponsesAPIConfig"), - "AzureOpenAIOSeriesResponsesAPIConfig": (".llms.azure.responses.o_series_transformation", "AzureOpenAIOSeriesResponsesAPIConfig"), - "XAIResponsesAPIConfig": (".llms.xai.responses.transformation", "XAIResponsesAPIConfig"), - "LiteLLMProxyResponsesAPIConfig": (".llms.litellm_proxy.responses.transformation", "LiteLLMProxyResponsesAPIConfig"), - "VolcEngineResponsesAPIConfig": (".llms.volcengine.responses.transformation", "VolcEngineResponsesAPIConfig"), - "ManusResponsesAPIConfig": (".llms.manus.responses.transformation", "ManusResponsesAPIConfig"), - "GoogleAIStudioInteractionsConfig": (".llms.gemini.interactions.transformation", "GoogleAIStudioInteractionsConfig"), - "OpenAIOSeriesConfig": (".llms.openai.chat.o_series_transformation", "OpenAIOSeriesConfig"), - "AnthropicSkillsConfig": (".llms.anthropic.skills.transformation", "AnthropicSkillsConfig"), - "BaseSkillsAPIConfig": (".llms.base_llm.skills.transformation", "BaseSkillsAPIConfig"), + "OpenAIResponsesAPIConfig": ( + ".llms.openai.responses.transformation", + "OpenAIResponsesAPIConfig", + ), + "AzureOpenAIResponsesAPIConfig": ( + ".llms.azure.responses.transformation", + "AzureOpenAIResponsesAPIConfig", + ), + "AzureOpenAIOSeriesResponsesAPIConfig": ( + ".llms.azure.responses.o_series_transformation", + "AzureOpenAIOSeriesResponsesAPIConfig", + ), + "XAIResponsesAPIConfig": ( + ".llms.xai.responses.transformation", + "XAIResponsesAPIConfig", + ), + "LiteLLMProxyResponsesAPIConfig": ( + ".llms.litellm_proxy.responses.transformation", + "LiteLLMProxyResponsesAPIConfig", + ), + "VolcEngineResponsesAPIConfig": ( + ".llms.volcengine.responses.transformation", + "VolcEngineResponsesAPIConfig", + ), + "ManusResponsesAPIConfig": ( + ".llms.manus.responses.transformation", + "ManusResponsesAPIConfig", + ), + "GoogleAIStudioInteractionsConfig": ( + ".llms.gemini.interactions.transformation", + "GoogleAIStudioInteractionsConfig", + ), + "OpenAIOSeriesConfig": ( + ".llms.openai.chat.o_series_transformation", + "OpenAIOSeriesConfig", + ), + "AnthropicSkillsConfig": ( + ".llms.anthropic.skills.transformation", + "AnthropicSkillsConfig", + ), + "BaseSkillsAPIConfig": ( + ".llms.base_llm.skills.transformation", + "BaseSkillsAPIConfig", + ), "GradientAIConfig": (".llms.gradient_ai.chat.transformation", "GradientAIConfig"), # Alias for backwards compatibility - "OpenAIO1Config": (".llms.openai.chat.o_series_transformation", "OpenAIOSeriesConfig"), # Alias + "OpenAIO1Config": ( + ".llms.openai.chat.o_series_transformation", + "OpenAIOSeriesConfig", + ), # Alias "OpenAIGPTConfig": (".llms.openai.chat.gpt_transformation", "OpenAIGPTConfig"), "OpenAIGPT5Config": (".llms.openai.chat.gpt_5_transformation", "OpenAIGPT5Config"), - "OpenAIWhisperAudioTranscriptionConfig": (".llms.openai.transcriptions.whisper_transformation", "OpenAIWhisperAudioTranscriptionConfig"), - "OpenAIGPTAudioTranscriptionConfig": (".llms.openai.transcriptions.gpt_transformation", "OpenAIGPTAudioTranscriptionConfig"), - "OpenAIGPTAudioConfig": (".llms.openai.chat.gpt_audio_transformation", "OpenAIGPTAudioConfig"), + "OpenAIWhisperAudioTranscriptionConfig": ( + ".llms.openai.transcriptions.whisper_transformation", + "OpenAIWhisperAudioTranscriptionConfig", + ), + "OpenAIGPTAudioTranscriptionConfig": ( + ".llms.openai.transcriptions.gpt_transformation", + "OpenAIGPTAudioTranscriptionConfig", + ), + "OpenAIGPTAudioConfig": ( + ".llms.openai.chat.gpt_audio_transformation", + "OpenAIGPTAudioConfig", + ), "NvidiaNimConfig": (".llms.nvidia_nim.chat.transformation", "NvidiaNimConfig"), "NvidiaNimEmbeddingConfig": (".llms.nvidia_nim.embed", "NvidiaNimEmbeddingConfig"), - "FeatherlessAIConfig": (".llms.featherless_ai.chat.transformation", "FeatherlessAIConfig"), + "FeatherlessAIConfig": ( + ".llms.featherless_ai.chat.transformation", + "FeatherlessAIConfig", + ), "CerebrasConfig": (".llms.cerebras.chat", "CerebrasConfig"), "BasetenConfig": (".llms.baseten.chat", "BasetenConfig"), "SambanovaConfig": (".llms.sambanova.chat", "SambanovaConfig"), - "SambaNovaEmbeddingConfig": (".llms.sambanova.embedding.transformation", "SambaNovaEmbeddingConfig"), - "FireworksAIConfig": (".llms.fireworks_ai.chat.transformation", "FireworksAIConfig"), - "FireworksAITextCompletionConfig": (".llms.fireworks_ai.completion.transformation", "FireworksAITextCompletionConfig"), - "FireworksAIAudioTranscriptionConfig": (".llms.fireworks_ai.audio_transcription.transformation", "FireworksAIAudioTranscriptionConfig"), - "FireworksAIEmbeddingConfig": (".llms.fireworks_ai.embed.fireworks_ai_transformation", "FireworksAIEmbeddingConfig"), - "FriendliaiChatConfig": (".llms.friendliai.chat.transformation", "FriendliaiChatConfig"), - "JinaAIEmbeddingConfig": (".llms.jina_ai.embedding.transformation", "JinaAIEmbeddingConfig"), + "SambaNovaEmbeddingConfig": ( + ".llms.sambanova.embedding.transformation", + "SambaNovaEmbeddingConfig", + ), + "FireworksAIConfig": ( + ".llms.fireworks_ai.chat.transformation", + "FireworksAIConfig", + ), + "FireworksAITextCompletionConfig": ( + ".llms.fireworks_ai.completion.transformation", + "FireworksAITextCompletionConfig", + ), + "FireworksAIAudioTranscriptionConfig": ( + ".llms.fireworks_ai.audio_transcription.transformation", + "FireworksAIAudioTranscriptionConfig", + ), + "FireworksAIEmbeddingConfig": ( + ".llms.fireworks_ai.embed.fireworks_ai_transformation", + "FireworksAIEmbeddingConfig", + ), + "FriendliaiChatConfig": ( + ".llms.friendliai.chat.transformation", + "FriendliaiChatConfig", + ), + "JinaAIEmbeddingConfig": ( + ".llms.jina_ai.embedding.transformation", + "JinaAIEmbeddingConfig", + ), "XAIChatConfig": (".llms.xai.chat.transformation", "XAIChatConfig"), "ZAIChatConfig": (".llms.zai.chat.transformation", "ZAIChatConfig"), "AIMLChatConfig": (".llms.aiml.chat.transformation", "AIMLChatConfig"), - "VolcEngineChatConfig": (".llms.volcengine.chat.transformation", "VolcEngineChatConfig"), - "CodestralTextCompletionConfig": (".llms.codestral.completion.transformation", "CodestralTextCompletionConfig"), - "AzureOpenAIAssistantsAPIConfig": (".llms.azure.azure", "AzureOpenAIAssistantsAPIConfig"), + "VolcEngineChatConfig": ( + ".llms.volcengine.chat.transformation", + "VolcEngineChatConfig", + ), + "CodestralTextCompletionConfig": ( + ".llms.codestral.completion.transformation", + "CodestralTextCompletionConfig", + ), + "AzureOpenAIAssistantsAPIConfig": ( + ".llms.azure.azure", + "AzureOpenAIAssistantsAPIConfig", + ), "HerokuChatConfig": (".llms.heroku.chat.transformation", "HerokuChatConfig"), "CometAPIConfig": (".llms.cometapi.chat.transformation", "CometAPIConfig"), "AzureOpenAIConfig": (".llms.azure.chat.gpt_transformation", "AzureOpenAIConfig"), - "AzureOpenAIGPT5Config": (".llms.azure.chat.gpt_5_transformation", "AzureOpenAIGPT5Config"), - "AzureOpenAITextConfig": (".llms.azure.completion.transformation", "AzureOpenAITextConfig"), - "HostedVLLMChatConfig": (".llms.hosted_vllm.chat.transformation", "HostedVLLMChatConfig"), + "AzureOpenAIGPT5Config": ( + ".llms.azure.chat.gpt_5_transformation", + "AzureOpenAIGPT5Config", + ), + "AzureOpenAITextConfig": ( + ".llms.azure.completion.transformation", + "AzureOpenAITextConfig", + ), + "HostedVLLMChatConfig": ( + ".llms.hosted_vllm.chat.transformation", + "HostedVLLMChatConfig", + ), + "HostedVLLMEmbeddingConfig": ( + ".llms.hosted_vllm.embedding.transformation", + "HostedVLLMEmbeddingConfig", + ), # Alias for backwards compatibility - "VolcEngineConfig": (".llms.volcengine.chat.transformation", "VolcEngineChatConfig"), # Alias - "LlamafileChatConfig": (".llms.llamafile.chat.transformation", "LlamafileChatConfig"), - "LiteLLMProxyChatConfig": (".llms.litellm_proxy.chat.transformation", "LiteLLMProxyChatConfig"), + "VolcEngineConfig": ( + ".llms.volcengine.chat.transformation", + "VolcEngineChatConfig", + ), # Alias + "LlamafileChatConfig": ( + ".llms.llamafile.chat.transformation", + "LlamafileChatConfig", + ), + "LiteLLMProxyChatConfig": ( + ".llms.litellm_proxy.chat.transformation", + "LiteLLMProxyChatConfig", + ), "VLLMConfig": (".llms.vllm.completion.transformation", "VLLMConfig"), "DeepSeekChatConfig": (".llms.deepseek.chat.transformation", "DeepSeekChatConfig"), "LMStudioChatConfig": (".llms.lm_studio.chat.transformation", "LMStudioChatConfig"), - "LmStudioEmbeddingConfig": (".llms.lm_studio.embed.transformation", "LmStudioEmbeddingConfig"), + "LmStudioEmbeddingConfig": ( + ".llms.lm_studio.embed.transformation", + "LmStudioEmbeddingConfig", + ), "NscaleConfig": (".llms.nscale.chat.transformation", "NscaleConfig"), - "PerplexityChatConfig": (".llms.perplexity.chat.transformation", "PerplexityChatConfig"), - "AzureOpenAIO1Config": (".llms.azure.chat.o_series_transformation", "AzureOpenAIO1Config"), - "IBMWatsonXAIConfig": (".llms.watsonx.completion.transformation", "IBMWatsonXAIConfig"), - "IBMWatsonXChatConfig": (".llms.watsonx.chat.transformation", "IBMWatsonXChatConfig"), - "IBMWatsonXEmbeddingConfig": (".llms.watsonx.embed.transformation", "IBMWatsonXEmbeddingConfig"), - "GenAIHubEmbeddingConfig": (".llms.sap.embed.transformation", "GenAIHubEmbeddingConfig"), - "IBMWatsonXAudioTranscriptionConfig": (".llms.watsonx.audio_transcription.transformation", "IBMWatsonXAudioTranscriptionConfig"), - "GithubCopilotConfig": (".llms.github_copilot.chat.transformation", "GithubCopilotConfig"), - "GithubCopilotResponsesAPIConfig": (".llms.github_copilot.responses.transformation", "GithubCopilotResponsesAPIConfig"), - "GithubCopilotEmbeddingConfig": (".llms.github_copilot.embedding.transformation", "GithubCopilotEmbeddingConfig"), + "PerplexityChatConfig": ( + ".llms.perplexity.chat.transformation", + "PerplexityChatConfig", + ), + "AzureOpenAIO1Config": ( + ".llms.azure.chat.o_series_transformation", + "AzureOpenAIO1Config", + ), + "IBMWatsonXAIConfig": ( + ".llms.watsonx.completion.transformation", + "IBMWatsonXAIConfig", + ), + "IBMWatsonXChatConfig": ( + ".llms.watsonx.chat.transformation", + "IBMWatsonXChatConfig", + ), + "IBMWatsonXEmbeddingConfig": ( + ".llms.watsonx.embed.transformation", + "IBMWatsonXEmbeddingConfig", + ), + "GenAIHubEmbeddingConfig": ( + ".llms.sap.embed.transformation", + "GenAIHubEmbeddingConfig", + ), + "IBMWatsonXAudioTranscriptionConfig": ( + ".llms.watsonx.audio_transcription.transformation", + "IBMWatsonXAudioTranscriptionConfig", + ), + "GithubCopilotConfig": ( + ".llms.github_copilot.chat.transformation", + "GithubCopilotConfig", + ), + "GithubCopilotResponsesAPIConfig": ( + ".llms.github_copilot.responses.transformation", + "GithubCopilotResponsesAPIConfig", + ), + "GithubCopilotEmbeddingConfig": ( + ".llms.github_copilot.embedding.transformation", + "GithubCopilotEmbeddingConfig", + ), "ChatGPTConfig": (".llms.chatgpt.chat.transformation", "ChatGPTConfig"), - "ChatGPTResponsesAPIConfig": (".llms.chatgpt.responses.transformation", "ChatGPTResponsesAPIConfig"), + "ChatGPTResponsesAPIConfig": ( + ".llms.chatgpt.responses.transformation", + "ChatGPTResponsesAPIConfig", + ), "NebiusConfig": (".llms.nebius.chat.transformation", "NebiusConfig"), "WandbConfig": (".llms.wandb.chat.transformation", "WandbConfig"), "GigaChatConfig": (".llms.gigachat.chat.transformation", "GigaChatConfig"), - "GigaChatEmbeddingConfig": (".llms.gigachat.embedding.transformation", "GigaChatEmbeddingConfig"), - "DashScopeChatConfig": (".llms.dashscope.chat.transformation", "DashScopeChatConfig"), + "GigaChatEmbeddingConfig": ( + ".llms.gigachat.embedding.transformation", + "GigaChatEmbeddingConfig", + ), + "DashScopeChatConfig": ( + ".llms.dashscope.chat.transformation", + "DashScopeChatConfig", + ), "MoonshotChatConfig": (".llms.moonshot.chat.transformation", "MoonshotChatConfig"), - "DockerModelRunnerChatConfig": (".llms.docker_model_runner.chat.transformation", "DockerModelRunnerChatConfig"), + "DockerModelRunnerChatConfig": ( + ".llms.docker_model_runner.chat.transformation", + "DockerModelRunnerChatConfig", + ), "V0ChatConfig": (".llms.v0.chat.transformation", "V0ChatConfig"), "OCIChatConfig": (".llms.oci.chat.transformation", "OCIChatConfig"), "MorphChatConfig": (".llms.morph.chat.transformation", "MorphChatConfig"), "RAGFlowConfig": (".llms.ragflow.chat.transformation", "RAGFlowConfig"), "LambdaAIChatConfig": (".llms.lambda_ai.chat.transformation", "LambdaAIChatConfig"), - "HyperbolicChatConfig": (".llms.hyperbolic.chat.transformation", "HyperbolicChatConfig"), - "VercelAIGatewayConfig": (".llms.vercel_ai_gateway.chat.transformation", "VercelAIGatewayConfig"), + "HyperbolicChatConfig": ( + ".llms.hyperbolic.chat.transformation", + "HyperbolicChatConfig", + ), + "VercelAIGatewayConfig": ( + ".llms.vercel_ai_gateway.chat.transformation", + "VercelAIGatewayConfig", + ), "OVHCloudChatConfig": (".llms.ovhcloud.chat.transformation", "OVHCloudChatConfig"), - "OVHCloudEmbeddingConfig": (".llms.ovhcloud.embedding.transformation", "OVHCloudEmbeddingConfig"), - "CometAPIEmbeddingConfig": (".llms.cometapi.embed.transformation", "CometAPIEmbeddingConfig"), + "OVHCloudEmbeddingConfig": ( + ".llms.ovhcloud.embedding.transformation", + "OVHCloudEmbeddingConfig", + ), + "CometAPIEmbeddingConfig": ( + ".llms.cometapi.embed.transformation", + "CometAPIEmbeddingConfig", + ), "LemonadeChatConfig": (".llms.lemonade.chat.transformation", "LemonadeChatConfig"), - "SnowflakeEmbeddingConfig": (".llms.snowflake.embedding.transformation", "SnowflakeEmbeddingConfig"), - "AmazonNovaChatConfig": (".llms.amazon_nova.chat.transformation", "AmazonNovaChatConfig"), + "SnowflakeEmbeddingConfig": ( + ".llms.snowflake.embedding.transformation", + "SnowflakeEmbeddingConfig", + ), + "AmazonNovaChatConfig": ( + ".llms.amazon_nova.chat.transformation", + "AmazonNovaChatConfig", + ), } # Import map for utils module lazy imports _UTILS_MODULE_IMPORT_MAP = { "encoding": ("litellm.main", "encoding"), - "BaseVectorStore": ("litellm.integrations.vector_store_integrations.base_vector_store", "BaseVectorStore"), - "CredentialAccessor": ("litellm.litellm_core_utils.credential_accessor", "CredentialAccessor"), - "exception_type": ("litellm.litellm_core_utils.exception_mapping_utils", "exception_type"), - "get_error_message": ("litellm.litellm_core_utils.exception_mapping_utils", "get_error_message"), - "_get_response_headers": ("litellm.litellm_core_utils.exception_mapping_utils", "_get_response_headers"), - "get_llm_provider": ("litellm.litellm_core_utils.get_llm_provider_logic", "get_llm_provider"), - "_is_non_openai_azure_model": ("litellm.litellm_core_utils.get_llm_provider_logic", "_is_non_openai_azure_model"), - "get_supported_openai_params": ("litellm.litellm_core_utils.get_supported_openai_params", "get_supported_openai_params"), - "LiteLLMResponseObjectHandler": ("litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response", "LiteLLMResponseObjectHandler"), - "_handle_invalid_parallel_tool_calls": ("litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response", "_handle_invalid_parallel_tool_calls"), - "convert_to_model_response_object": ("litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response", "convert_to_model_response_object"), - "convert_to_streaming_response": ("litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response", "convert_to_streaming_response"), - "convert_to_streaming_response_async": ("litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response", "convert_to_streaming_response_async"), - "get_api_base": ("litellm.litellm_core_utils.llm_response_utils.get_api_base", "get_api_base"), - "ResponseMetadata": ("litellm.litellm_core_utils.llm_response_utils.response_metadata", "ResponseMetadata"), - "_parse_content_for_reasoning": ("litellm.litellm_core_utils.prompt_templates.common_utils", "_parse_content_for_reasoning"), - "LiteLLMLoggingObject": ("litellm.litellm_core_utils.redact_messages", "LiteLLMLoggingObject"), - "redact_message_input_output_from_logging": ("litellm.litellm_core_utils.redact_messages", "redact_message_input_output_from_logging"), - "CustomStreamWrapper": ("litellm.litellm_core_utils.streaming_handler", "CustomStreamWrapper"), - "BaseGoogleGenAIGenerateContentConfig": ("litellm.llms.base_llm.google_genai.transformation", "BaseGoogleGenAIGenerateContentConfig"), + "BaseVectorStore": ( + "litellm.integrations.vector_store_integrations.base_vector_store", + "BaseVectorStore", + ), + "CredentialAccessor": ( + "litellm.litellm_core_utils.credential_accessor", + "CredentialAccessor", + ), + "exception_type": ( + "litellm.litellm_core_utils.exception_mapping_utils", + "exception_type", + ), + "get_error_message": ( + "litellm.litellm_core_utils.exception_mapping_utils", + "get_error_message", + ), + "_get_response_headers": ( + "litellm.litellm_core_utils.exception_mapping_utils", + "_get_response_headers", + ), + "get_llm_provider": ( + "litellm.litellm_core_utils.get_llm_provider_logic", + "get_llm_provider", + ), + "_is_non_openai_azure_model": ( + "litellm.litellm_core_utils.get_llm_provider_logic", + "_is_non_openai_azure_model", + ), + "get_supported_openai_params": ( + "litellm.litellm_core_utils.get_supported_openai_params", + "get_supported_openai_params", + ), + "LiteLLMResponseObjectHandler": ( + "litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response", + "LiteLLMResponseObjectHandler", + ), + "_handle_invalid_parallel_tool_calls": ( + "litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response", + "_handle_invalid_parallel_tool_calls", + ), + "convert_to_model_response_object": ( + "litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response", + "convert_to_model_response_object", + ), + "convert_to_streaming_response": ( + "litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response", + "convert_to_streaming_response", + ), + "convert_to_streaming_response_async": ( + "litellm.litellm_core_utils.llm_response_utils.convert_dict_to_response", + "convert_to_streaming_response_async", + ), + "get_api_base": ( + "litellm.litellm_core_utils.llm_response_utils.get_api_base", + "get_api_base", + ), + "ResponseMetadata": ( + "litellm.litellm_core_utils.llm_response_utils.response_metadata", + "ResponseMetadata", + ), + "_parse_content_for_reasoning": ( + "litellm.litellm_core_utils.prompt_templates.common_utils", + "_parse_content_for_reasoning", + ), + "LiteLLMLoggingObject": ( + "litellm.litellm_core_utils.redact_messages", + "LiteLLMLoggingObject", + ), + "redact_message_input_output_from_logging": ( + "litellm.litellm_core_utils.redact_messages", + "redact_message_input_output_from_logging", + ), + "CustomStreamWrapper": ( + "litellm.litellm_core_utils.streaming_handler", + "CustomStreamWrapper", + ), + "BaseGoogleGenAIGenerateContentConfig": ( + "litellm.llms.base_llm.google_genai.transformation", + "BaseGoogleGenAIGenerateContentConfig", + ), "BaseOCRConfig": ("litellm.llms.base_llm.ocr.transformation", "BaseOCRConfig"), - "BaseSearchConfig": ("litellm.llms.base_llm.search.transformation", "BaseSearchConfig"), - "BaseTextToSpeechConfig": ("litellm.llms.base_llm.text_to_speech.transformation", "BaseTextToSpeechConfig"), + "BaseSearchConfig": ( + "litellm.llms.base_llm.search.transformation", + "BaseSearchConfig", + ), + "BaseTextToSpeechConfig": ( + "litellm.llms.base_llm.text_to_speech.transformation", + "BaseTextToSpeechConfig", + ), "BedrockModelInfo": ("litellm.llms.bedrock.common_utils", "BedrockModelInfo"), "CohereModelInfo": ("litellm.llms.cohere.common_utils", "CohereModelInfo"), "MistralOCRConfig": ("litellm.llms.mistral.ocr.transformation", "MistralOCRConfig"), "Rules": ("litellm.litellm_core_utils.rules", "Rules"), "AsyncHTTPHandler": ("litellm.llms.custom_httpx.http_handler", "AsyncHTTPHandler"), "HTTPHandler": ("litellm.llms.custom_httpx.http_handler", "HTTPHandler"), - "get_num_retries_from_retry_policy": ("litellm.router_utils.get_retry_from_policy", "get_num_retries_from_retry_policy"), - "reset_retry_policy": ("litellm.router_utils.get_retry_from_policy", "reset_retry_policy"), + "get_num_retries_from_retry_policy": ( + "litellm.router_utils.get_retry_from_policy", + "get_num_retries_from_retry_policy", + ), + "reset_retry_policy": ( + "litellm.router_utils.get_retry_from_policy", + "reset_retry_policy", + ), "get_secret": ("litellm.secret_managers.main", "get_secret"), - "get_coroutine_checker": ("litellm.litellm_core_utils.cached_imports", "get_coroutine_checker"), - "get_litellm_logging_class": ("litellm.litellm_core_utils.cached_imports", "get_litellm_logging_class"), - "get_set_callbacks": ("litellm.litellm_core_utils.cached_imports", "get_set_callbacks"), - "get_litellm_metadata_from_kwargs": ("litellm.litellm_core_utils.core_helpers", "get_litellm_metadata_from_kwargs"), - "map_finish_reason": ("litellm.litellm_core_utils.core_helpers", "map_finish_reason"), - "process_response_headers": ("litellm.litellm_core_utils.core_helpers", "process_response_headers"), - "delete_nested_value": ("litellm.litellm_core_utils.dot_notation_indexing", "delete_nested_value"), - "is_nested_path": ("litellm.litellm_core_utils.dot_notation_indexing", "is_nested_path"), - "_get_base_model_from_litellm_call_metadata": ("litellm.litellm_core_utils.get_litellm_params", "_get_base_model_from_litellm_call_metadata"), - "get_litellm_params": ("litellm.litellm_core_utils.get_litellm_params", "get_litellm_params"), - "_ensure_extra_body_is_safe": ("litellm.litellm_core_utils.llm_request_utils", "_ensure_extra_body_is_safe"), - "get_formatted_prompt": ("litellm.litellm_core_utils.llm_response_utils.get_formatted_prompt", "get_formatted_prompt"), - "get_response_headers": ("litellm.litellm_core_utils.llm_response_utils.get_headers", "get_response_headers"), - "update_response_metadata": ("litellm.litellm_core_utils.llm_response_utils.response_metadata", "update_response_metadata"), + "get_coroutine_checker": ( + "litellm.litellm_core_utils.cached_imports", + "get_coroutine_checker", + ), + "get_litellm_logging_class": ( + "litellm.litellm_core_utils.cached_imports", + "get_litellm_logging_class", + ), + "get_set_callbacks": ( + "litellm.litellm_core_utils.cached_imports", + "get_set_callbacks", + ), + "get_litellm_metadata_from_kwargs": ( + "litellm.litellm_core_utils.core_helpers", + "get_litellm_metadata_from_kwargs", + ), + "map_finish_reason": ( + "litellm.litellm_core_utils.core_helpers", + "map_finish_reason", + ), + "process_response_headers": ( + "litellm.litellm_core_utils.core_helpers", + "process_response_headers", + ), + "delete_nested_value": ( + "litellm.litellm_core_utils.dot_notation_indexing", + "delete_nested_value", + ), + "is_nested_path": ( + "litellm.litellm_core_utils.dot_notation_indexing", + "is_nested_path", + ), + "_get_base_model_from_litellm_call_metadata": ( + "litellm.litellm_core_utils.get_litellm_params", + "_get_base_model_from_litellm_call_metadata", + ), + "get_litellm_params": ( + "litellm.litellm_core_utils.get_litellm_params", + "get_litellm_params", + ), + "_ensure_extra_body_is_safe": ( + "litellm.litellm_core_utils.llm_request_utils", + "_ensure_extra_body_is_safe", + ), + "get_formatted_prompt": ( + "litellm.litellm_core_utils.llm_response_utils.get_formatted_prompt", + "get_formatted_prompt", + ), + "get_response_headers": ( + "litellm.litellm_core_utils.llm_response_utils.get_headers", + "get_response_headers", + ), + "update_response_metadata": ( + "litellm.litellm_core_utils.llm_response_utils.response_metadata", + "update_response_metadata", + ), "executor": ("litellm.litellm_core_utils.thread_pool_executor", "executor"), - "BaseAnthropicMessagesConfig": ("litellm.llms.base_llm.anthropic_messages.transformation", "BaseAnthropicMessagesConfig"), - "BaseAudioTranscriptionConfig": ("litellm.llms.base_llm.audio_transcription.transformation", "BaseAudioTranscriptionConfig"), - "BaseBatchesConfig": ("litellm.llms.base_llm.batches.transformation", "BaseBatchesConfig"), - "BaseContainerConfig": ("litellm.llms.base_llm.containers.transformation", "BaseContainerConfig"), - "BaseEmbeddingConfig": ("litellm.llms.base_llm.embedding.transformation", "BaseEmbeddingConfig"), - "BaseImageEditConfig": ("litellm.llms.base_llm.image_edit.transformation", "BaseImageEditConfig"), - "BaseImageGenerationConfig": ("litellm.llms.base_llm.image_generation.transformation", "BaseImageGenerationConfig"), - "BaseImageVariationConfig": ("litellm.llms.base_llm.image_variations.transformation", "BaseImageVariationConfig"), - "BasePassthroughConfig": ("litellm.llms.base_llm.passthrough.transformation", "BasePassthroughConfig"), - "BaseRealtimeConfig": ("litellm.llms.base_llm.realtime.transformation", "BaseRealtimeConfig"), - "BaseRerankConfig": ("litellm.llms.base_llm.rerank.transformation", "BaseRerankConfig"), - "BaseVectorStoreConfig": ("litellm.llms.base_llm.vector_store.transformation", "BaseVectorStoreConfig"), - "BaseVectorStoreFilesConfig": ("litellm.llms.base_llm.vector_store_files.transformation", "BaseVectorStoreFilesConfig"), - "BaseVideoConfig": ("litellm.llms.base_llm.videos.transformation", "BaseVideoConfig"), - "ANTHROPIC_API_ONLY_HEADERS": ("litellm.types.llms.anthropic", "ANTHROPIC_API_ONLY_HEADERS"), - "AnthropicThinkingParam": ("litellm.types.llms.anthropic", "AnthropicThinkingParam"), + "BaseAnthropicMessagesConfig": ( + "litellm.llms.base_llm.anthropic_messages.transformation", + "BaseAnthropicMessagesConfig", + ), + "BaseAudioTranscriptionConfig": ( + "litellm.llms.base_llm.audio_transcription.transformation", + "BaseAudioTranscriptionConfig", + ), + "BaseBatchesConfig": ( + "litellm.llms.base_llm.batches.transformation", + "BaseBatchesConfig", + ), + "BaseContainerConfig": ( + "litellm.llms.base_llm.containers.transformation", + "BaseContainerConfig", + ), + "BaseEmbeddingConfig": ( + "litellm.llms.base_llm.embedding.transformation", + "BaseEmbeddingConfig", + ), + "BaseImageEditConfig": ( + "litellm.llms.base_llm.image_edit.transformation", + "BaseImageEditConfig", + ), + "BaseImageGenerationConfig": ( + "litellm.llms.base_llm.image_generation.transformation", + "BaseImageGenerationConfig", + ), + "BaseImageVariationConfig": ( + "litellm.llms.base_llm.image_variations.transformation", + "BaseImageVariationConfig", + ), + "BasePassthroughConfig": ( + "litellm.llms.base_llm.passthrough.transformation", + "BasePassthroughConfig", + ), + "BaseRealtimeConfig": ( + "litellm.llms.base_llm.realtime.transformation", + "BaseRealtimeConfig", + ), + "BaseRerankConfig": ( + "litellm.llms.base_llm.rerank.transformation", + "BaseRerankConfig", + ), + "BaseVectorStoreConfig": ( + "litellm.llms.base_llm.vector_store.transformation", + "BaseVectorStoreConfig", + ), + "BaseVectorStoreFilesConfig": ( + "litellm.llms.base_llm.vector_store_files.transformation", + "BaseVectorStoreFilesConfig", + ), + "BaseVideoConfig": ( + "litellm.llms.base_llm.videos.transformation", + "BaseVideoConfig", + ), + "ANTHROPIC_API_ONLY_HEADERS": ( + "litellm.types.llms.anthropic", + "ANTHROPIC_API_ONLY_HEADERS", + ), + "AnthropicThinkingParam": ( + "litellm.types.llms.anthropic", + "AnthropicThinkingParam", + ), "RerankResponse": ("litellm.types.rerank", "RerankResponse"), - "ChatCompletionDeltaToolCallChunk": ("litellm.types.llms.openai", "ChatCompletionDeltaToolCallChunk"), - "ChatCompletionToolCallChunk": ("litellm.types.llms.openai", "ChatCompletionToolCallChunk"), - "ChatCompletionToolCallFunctionChunk": ("litellm.types.llms.openai", "ChatCompletionToolCallFunctionChunk"), + "ChatCompletionDeltaToolCallChunk": ( + "litellm.types.llms.openai", + "ChatCompletionDeltaToolCallChunk", + ), + "ChatCompletionToolCallChunk": ( + "litellm.types.llms.openai", + "ChatCompletionToolCallChunk", + ), + "ChatCompletionToolCallFunctionChunk": ( + "litellm.types.llms.openai", + "ChatCompletionToolCallFunctionChunk", + ), "LiteLLM_Params": ("litellm.types.router", "LiteLLM_Params"), } diff --git a/litellm/_logging.py b/litellm/_logging.py index b3156b15ba7..e222627e76c 100644 --- a/litellm/_logging.py +++ b/litellm/_logging.py @@ -166,6 +166,66 @@ def _initialize_loggers_with_handler(handler: logging.Handler): lg.propagate = False # prevent bubbling to parent/root +def _get_uvicorn_json_log_config(): + """ + Generate a uvicorn log_config dictionary that applies JSON formatting to all loggers. + + This ensures that uvicorn's access logs, error logs, and all application logs + are formatted as JSON when json_logs is enabled. + """ + json_formatter_class = "litellm._logging.JsonFormatter" + + # Use the module-level log_level variable for consistency + uvicorn_log_level = log_level.upper() + + log_config = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "json": { + "()": json_formatter_class, + }, + "default": { + "()": json_formatter_class, + }, + "access": { + "()": json_formatter_class, + }, + }, + "handlers": { + "default": { + "formatter": "json", + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout", + }, + "access": { + "formatter": "access", + "class": "logging.StreamHandler", + "stream": "ext://sys.stdout", + }, + }, + "loggers": { + "uvicorn": { + "handlers": ["default"], + "level": uvicorn_log_level, + "propagate": False, + }, + "uvicorn.error": { + "handlers": ["default"], + "level": uvicorn_log_level, + "propagate": False, + }, + "uvicorn.access": { + "handlers": ["access"], + "level": uvicorn_log_level, + "propagate": False, + }, + }, + } + + return log_config + + def _turn_on_json(): """ Turn on JSON logging diff --git a/litellm/a2a_protocol/card_resolver.py b/litellm/a2a_protocol/card_resolver.py new file mode 100644 index 00000000000..7c4c5af149d --- /dev/null +++ b/litellm/a2a_protocol/card_resolver.py @@ -0,0 +1,97 @@ +""" +Custom A2A Card Resolver for LiteLLM. + +Extends the A2A SDK's card resolver to support multiple well-known paths. +""" + +from typing import TYPE_CHECKING, Any, Dict, Optional + +from litellm._logging import verbose_logger + +if TYPE_CHECKING: + from a2a.types import AgentCard + +# Runtime imports with availability check +_A2ACardResolver: Any = None +AGENT_CARD_WELL_KNOWN_PATH: str = "/.well-known/agent-card.json" +PREV_AGENT_CARD_WELL_KNOWN_PATH: str = "/.well-known/agent.json" + +try: + from a2a.client import A2ACardResolver as _A2ACardResolver # type: ignore[no-redef] + from a2a.utils.constants import ( # type: ignore[no-redef] + AGENT_CARD_WELL_KNOWN_PATH, + PREV_AGENT_CARD_WELL_KNOWN_PATH, + ) +except ImportError: + pass + + +class LiteLLMA2ACardResolver(_A2ACardResolver): # type: ignore[misc] + """ + Custom A2A card resolver that supports multiple well-known paths. + + Extends the base A2ACardResolver to try both: + - /.well-known/agent-card.json (standard) + - /.well-known/agent.json (previous/alternative) + """ + + async def get_agent_card( + self, + relative_card_path: Optional[str] = None, + http_kwargs: Optional[Dict[str, Any]] = None, + ) -> "AgentCard": + """ + Fetch the agent card, trying multiple well-known paths. + + First tries the standard path, then falls back to the previous path. + + Args: + relative_card_path: Optional path to the agent card endpoint. + If None, tries both well-known paths. + http_kwargs: Optional dictionary of keyword arguments to pass to httpx.get + + Returns: + AgentCard from the A2A agent + + Raises: + A2AClientHTTPError or A2AClientJSONError if both paths fail + """ + # If a specific path is provided, use the parent implementation + if relative_card_path is not None: + return await super().get_agent_card( + relative_card_path=relative_card_path, + http_kwargs=http_kwargs, + ) + + # Try both well-known paths + paths = [ + AGENT_CARD_WELL_KNOWN_PATH, + PREV_AGENT_CARD_WELL_KNOWN_PATH, + ] + + last_error = None + for path in paths: + try: + verbose_logger.debug( + f"Attempting to fetch agent card from {self.base_url}{path}" + ) + return await super().get_agent_card( + relative_card_path=path, + http_kwargs=http_kwargs, + ) + except Exception as e: + verbose_logger.debug( + f"Failed to fetch agent card from {self.base_url}{path}: {e}" + ) + last_error = e + continue + + # If we get here, all paths failed - re-raise the last error + if last_error is not None: + raise last_error + + # This shouldn't happen, but just in case + raise Exception( + f"Failed to fetch agent card from {self.base_url}. " + f"Tried paths: {', '.join(paths)}" + ) diff --git a/litellm/a2a_protocol/main.py b/litellm/a2a_protocol/main.py index ae95faede22..b326f9e7ed5 100644 --- a/litellm/a2a_protocol/main.py +++ b/litellm/a2a_protocol/main.py @@ -6,6 +6,7 @@ Provides standalone functions with @client decorator for LiteLLM logging integra import asyncio import datetime +import uuid from typing import TYPE_CHECKING, Any, AsyncIterator, Coroutine, Dict, Optional, Union import litellm @@ -20,7 +21,6 @@ from litellm.llms.custom_httpx.http_handler import ( ) from litellm.types.agents import LiteLLMSendMessageResponse from litellm.utils import client -import uuid if TYPE_CHECKING: from a2a.client import A2AClient as A2AClientType @@ -36,13 +36,18 @@ A2ACardResolver: Any = None _A2AClient: Any = None try: - from a2a.client import A2ACardResolver # type: ignore[no-redef] from a2a.client import A2AClient as _A2AClient # type: ignore[no-redef] A2A_SDK_AVAILABLE = True except ImportError: pass +# Import our custom card resolver that supports multiple well-known paths +from litellm.a2a_protocol.card_resolver import LiteLLMA2ACardResolver + +# Use our custom resolver instead of the default A2A SDK resolver +A2ACardResolver = LiteLLMA2ACardResolver + def _set_usage_on_logging_obj( kwargs: Dict[str, Any], diff --git a/litellm/batches/batch_utils.py b/litellm/batches/batch_utils.py index 8a078eeaca1..f80eae20f3b 100644 --- a/litellm/batches/batch_utils.py +++ b/litellm/batches/batch_utils.py @@ -192,6 +192,9 @@ async def _get_batch_output_file_content_as_dictionary( Get the batch output file content as a list of dictionaries """ from litellm.files.main import afile_content + from litellm.proxy.openai_files_endpoints.common_utils import ( + _is_base64_encoded_unified_file_id, + ) if custom_llm_provider == "vertex_ai": raise ValueError("Vertex AI does not support file content retrieval") @@ -199,8 +202,17 @@ async def _get_batch_output_file_content_as_dictionary( if batch.output_file_id is None: raise ValueError("Output file id is None cannot retrieve file content") + file_id = batch.output_file_id + is_base64_unified_file_id = _is_base64_encoded_unified_file_id(file_id) + if is_base64_unified_file_id: + try: + file_id = is_base64_unified_file_id.split("llm_output_file_id,")[1].split(";")[0] + verbose_logger.debug(f"Extracted LLM output file ID from unified file ID: {file_id}") + except (IndexError, AttributeError) as e: + verbose_logger.error(f"Failed to extract LLM output file ID from unified file ID: {batch.output_file_id}, error: {e}") + _file_content = await afile_content( - file_id=batch.output_file_id, + file_id=file_id, custom_llm_provider=custom_llm_provider, ) return _get_file_content_as_dictionary(_file_content.content) diff --git a/litellm/batches/main.py b/litellm/batches/main.py index 3367f567a7f..25f6e284bcd 100644 --- a/litellm/batches/main.py +++ b/litellm/batches/main.py @@ -31,7 +31,6 @@ from litellm.llms.openai.openai import OpenAIBatchesAPI from litellm.llms.vertex_ai.batches.handler import VertexAIBatchPrediction from litellm.secret_managers.main import get_secret_str from litellm.types.llms.openai import ( - Batch, CancelBatchRequest, CreateBatchRequest, RetrieveBatchRequest, @@ -868,7 +867,7 @@ async def acancel_batch( extra_headers: Optional[Dict[str, str]] = None, extra_body: Optional[Dict[str, str]] = None, **kwargs, -) -> Batch: +) -> LiteLLMBatch: """ Async: Cancels a batch. @@ -877,7 +876,9 @@ async def acancel_batch( try: loop = asyncio.get_event_loop() kwargs["acancel_batch"] = True - model = kwargs.pop("model", None) + # Preserve model parameter - only pop from kwargs if it exists there + # (to avoid passing it twice), otherwise keep the function parameter value + model = kwargs.pop("model", None) or model # Use a partial function to pass your keyword arguments func = partial( @@ -912,7 +913,7 @@ def cancel_batch( extra_headers: Optional[Dict[str, str]] = None, extra_body: Optional[Dict[str, str]] = None, **kwargs, -) -> Union[Batch, Coroutine[Any, Any, Batch]]: +) -> Union[LiteLLMBatch, Coroutine[Any, Any, LiteLLMBatch]]: """ Cancels a batch. diff --git a/litellm/completion_extras/litellm_responses_transformation/transformation.py b/litellm/completion_extras/litellm_responses_transformation/transformation.py index c98799d2542..57bd05124aa 100644 --- a/litellm/completion_extras/litellm_responses_transformation/transformation.py +++ b/litellm/completion_extras/litellm_responses_transformation/transformation.py @@ -277,6 +277,8 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): responses_api_request["previous_response_id"] = value elif key == "reasoning_effort": responses_api_request["reasoning"] = self._map_reasoning_effort(value) + elif key == "web_search_options": + self._add_web_search_tool(responses_api_request, value) # Get stream parameter from litellm_params if not in optional_params stream = optional_params.get("stream") or litellm_params.get("stream", False) @@ -727,6 +729,34 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): return Reasoning(effort="minimal", summary="detailed") if auto_summary_enabled else Reasoning(effort="minimal") return None + def _add_web_search_tool( + self, + responses_api_request: ResponsesAPIOptionalRequestParams, + web_search_options: Any, + ) -> None: + """ + Add web search tool to responses API request. + + Args: + responses_api_request: The responses API request dict to modify + web_search_options: Web search configuration (dict or other value) + """ + if "tools" not in responses_api_request or responses_api_request["tools"] is None: + responses_api_request["tools"] = [] + + # Get the tools list with proper type narrowing + tools = responses_api_request["tools"] + if tools is None: + tools = [] + responses_api_request["tools"] = tools + + web_search_tool: Dict[str, Any] = {"type": "web_search"} + if isinstance(web_search_options, dict): + web_search_tool.update(web_search_options) + + # Cast to Any to match the expected union type for tools list items + tools.append(cast(Any, web_search_tool)) + def _transform_response_format_to_text_format( self, response_format: Union[Dict[str, Any], Any] ) -> Optional[Dict[str, Any]]: diff --git a/litellm/constants.py b/litellm/constants.py index 49ca3a509b1..6427c367924 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -67,6 +67,20 @@ DEFAULT_REASONING_EFFORT_DISABLE_THINKING_BUDGET = int( os.getenv("DEFAULT_REASONING_EFFORT_DISABLE_THINKING_BUDGET", 0) ) +# MCP Semantic Tool Filter Defaults +DEFAULT_MCP_SEMANTIC_FILTER_EMBEDDING_MODEL = str( + os.getenv("DEFAULT_MCP_SEMANTIC_FILTER_EMBEDDING_MODEL", "text-embedding-3-small") +) +DEFAULT_MCP_SEMANTIC_FILTER_TOP_K = int( + os.getenv("DEFAULT_MCP_SEMANTIC_FILTER_TOP_K", 10) +) +DEFAULT_MCP_SEMANTIC_FILTER_SIMILARITY_THRESHOLD = float( + os.getenv("DEFAULT_MCP_SEMANTIC_FILTER_SIMILARITY_THRESHOLD", 0.3) +) +MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH = int( + os.getenv("MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH", 150) +) + # Gemini model-specific minimal thinking budget constants DEFAULT_REASONING_EFFORT_MINIMAL_THINKING_BUDGET_GEMINI_2_5_FLASH = int( os.getenv("DEFAULT_REASONING_EFFORT_MINIMAL_THINKING_BUDGET_GEMINI_2_5_FLASH", 1) @@ -980,6 +994,7 @@ BEDROCK_CONVERSE_MODELS = [ "meta.llama3-2-90b-instruct-v1:0", "amazon.nova-lite-v1:0", "amazon.nova-2-lite-v1:0", + "amazon.nova-2-pro-preview-20251202-v1:0", "amazon.nova-pro-v1:0", "writer.palmyra-x4-v1:0", "writer.palmyra-x5-v1:0", @@ -1165,6 +1180,12 @@ LITELLM_CLI_SOURCE_IDENTIFIER = "litellm-cli" LITELLM_CLI_SESSION_TOKEN_PREFIX = "litellm-session-token" CLI_SSO_SESSION_CACHE_KEY_PREFIX = "cli_sso_session" CLI_JWT_TOKEN_NAME = "cli-jwt-token" +# Support both CLI_JWT_EXPIRATION_HOURS and LITELLM_CLI_JWT_EXPIRATION_HOURS for backwards compatibility +CLI_JWT_EXPIRATION_HOURS = int( + os.getenv("CLI_JWT_EXPIRATION_HOURS") + or os.getenv("LITELLM_CLI_JWT_EXPIRATION_HOURS") + or 24 +) ########################### DB CRON JOB NAMES ########################### DB_SPEND_UPDATE_JOB_NAME = "db_spend_update_job" @@ -1326,6 +1347,13 @@ COROUTINE_CHECKER_MAX_SIZE_IN_MEMORY = int( DEFAULT_CHUNK_SIZE = int(os.getenv("DEFAULT_CHUNK_SIZE", 1000)) DEFAULT_CHUNK_OVERLAP = int(os.getenv("DEFAULT_CHUNK_OVERLAP", 200)) +########################### S3 Vectors RAG Constants ########################### +S3_VECTORS_DEFAULT_DIMENSION = int(os.getenv("S3_VECTORS_DEFAULT_DIMENSION", 1024)) +S3_VECTORS_DEFAULT_DISTANCE_METRIC = str( + os.getenv("S3_VECTORS_DEFAULT_DISTANCE_METRIC", "cosine") +) +S3_VECTORS_DEFAULT_NON_FILTERABLE_METADATA_KEYS = ["source_text"] + ########################### Microsoft SSO Constants ########################### MICROSOFT_USER_EMAIL_ATTRIBUTE = str( os.getenv("MICROSOFT_USER_EMAIL_ATTRIBUTE", "userPrincipalName") diff --git a/litellm/cost_calculator.py b/litellm/cost_calculator.py index 1ab7e260a83..bef4d52ce49 100644 --- a/litellm/cost_calculator.py +++ b/litellm/cost_calculator.py @@ -23,7 +23,11 @@ from litellm.litellm_core_utils.llm_cost_calc.usage_object_transformation import from litellm.litellm_core_utils.llm_cost_calc.utils import ( CostCalculatorUtils, _generic_cost_per_character, + _get_service_tier_cost_key, + _parse_prompt_tokens_details, + calculate_cost_component, generic_cost_per_token, + get_billable_input_tokens, select_cost_metric_for_model, ) from litellm.llms.anthropic.cost_calculation import ( @@ -32,6 +36,9 @@ from litellm.llms.anthropic.cost_calculation import ( from litellm.llms.azure.cost_calculation import ( cost_per_token as azure_openai_cost_per_token, ) +from litellm.llms.azure_ai.cost_calculator import ( + cost_per_token as azure_ai_cost_per_token, +) from litellm.llms.base_llm.search.transformation import SearchResponse from litellm.llms.bedrock.cost_calculation import ( cost_per_token as bedrock_cost_per_token, @@ -134,6 +141,51 @@ def _cost_per_token_custom_pricing_helper( return None +def _get_additional_costs( + model: str, + custom_llm_provider: Optional[str], + prompt_tokens: int, + completion_tokens: int, +) -> Optional[dict]: + """ + Calculate additional costs beyond standard token costs. + + This function delegates to provider-specific config classes to calculate + any additional costs like routing fees, infrastructure costs, etc. + + Args: + model: The model name + custom_llm_provider: The provider name (optional) + prompt_tokens: Number of prompt tokens + completion_tokens: Number of completion tokens + + Returns: + Optional dictionary with cost names and amounts, or None if no additional costs + """ + if not custom_llm_provider: + return None + + try: + config_class = None + if custom_llm_provider == "azure_ai": + from litellm.llms.azure_ai.common_utils import AzureFoundryModelInfo + config_class = AzureFoundryModelInfo.get_azure_ai_config_for_model(model) + # Add more providers here as needed + # elif custom_llm_provider == "other_provider": + # config_class = get_other_provider_config(model) + + if config_class and hasattr(config_class, 'calculate_additional_costs'): + return config_class.calculate_additional_costs( + model=model, + prompt_tokens=prompt_tokens, + completion_tokens=completion_tokens, + ) + except Exception as e: + verbose_logger.debug(f"Error calculating additional costs: {e}") + + return None + + def _transcription_usage_has_token_details( usage_block: Optional[Usage], ) -> bool: @@ -423,20 +475,26 @@ def cost_per_token( # noqa: PLR0915 return dashscope_cost_per_token(model=model, usage=usage_block) elif custom_llm_provider == "azure_ai": - return generic_cost_per_token( - model=model, usage=usage_block, custom_llm_provider=custom_llm_provider + return azure_ai_cost_per_token( + model=model, usage=usage_block, response_time_ms=response_time_ms ) else: model_info = _cached_get_model_info_helper( model=model, custom_llm_provider=custom_llm_provider ) - if model_info["input_cost_per_token"] > 0: - ## COST PER TOKEN ## - prompt_tokens_cost_usd_dollar = ( - model_info["input_cost_per_token"] * prompt_tokens + if ( + model_info.get("input_cost_per_token", 0) > 0 + or model_info.get("output_cost_per_token", 0) > 0 + ): + return generic_cost_per_token( + model=model, + usage=usage_block, + custom_llm_provider=custom_llm_provider, + service_tier=service_tier, ) - elif ( + + if ( model_info.get("input_cost_per_second", None) is not None and response_time_ms is not None ): @@ -451,11 +509,7 @@ def cost_per_token( # noqa: PLR0915 model_info["input_cost_per_second"] * response_time_ms / 1000 # type: ignore ) - if model_info["output_cost_per_token"] > 0: - completion_tokens_cost_usd_dollar = ( - model_info["output_cost_per_token"] * completion_tokens - ) - elif ( + if ( model_info.get("output_cost_per_second", None) is not None and response_time_ms is not None ): @@ -799,6 +853,7 @@ def _store_cost_breakdown_in_logging_obj( completion_tokens_cost_usd_dollar: float, cost_for_built_in_tools_cost_usd_dollar: float, total_cost_usd_dollar: float, + additional_costs: Optional[dict] = None, original_cost: Optional[float] = None, discount_percent: Optional[float] = None, discount_amount: Optional[float] = None, @@ -815,6 +870,7 @@ def _store_cost_breakdown_in_logging_obj( completion_tokens_cost_usd_dollar: Cost of completion tokens (includes reasoning if applicable) cost_for_built_in_tools_cost_usd_dollar: Cost of built-in tools total_cost_usd_dollar: Total cost of request + additional_costs: Free-form additional costs dict (e.g., {"azure_model_router_flat_cost": 0.00014}) original_cost: Cost before discount discount_percent: Discount percentage applied (0.05 = 5%) discount_amount: Discount amount in USD @@ -832,6 +888,7 @@ def _store_cost_breakdown_in_logging_obj( output_cost=completion_tokens_cost_usd_dollar, total_cost=total_cost_usd_dollar, cost_for_built_in_tools_cost_usd_dollar=cost_for_built_in_tools_cost_usd_dollar, + additional_costs=additional_costs, original_cost=original_cost, discount_percent=discount_percent, discount_amount=discount_amount, @@ -955,7 +1012,10 @@ def completion_cost( # noqa: PLR0915 router_model_id=router_model_id, ) - potential_model_names = [selected_model, _get_response_model(completion_response)] + potential_model_names = [ + selected_model, + _get_response_model(completion_response), + ] if model is not None: potential_model_names.append(model) @@ -1326,6 +1386,15 @@ def completion_cost( # noqa: PLR0915 service_tier=service_tier, response=completion_response, ) + + # Get additional costs from provider (e.g., routing fees, infrastructure costs) + additional_costs = _get_additional_costs( + model=model, + custom_llm_provider=custom_llm_provider, + prompt_tokens=prompt_tokens, + completion_tokens=completion_tokens, + ) + _final_cost = ( prompt_tokens_cost_usd_dollar + completion_tokens_cost_usd_dollar ) @@ -1365,6 +1434,7 @@ def completion_cost( # noqa: PLR0915 completion_tokens_cost_usd_dollar=completion_tokens_cost_usd_dollar, cost_for_built_in_tools_cost_usd_dollar=cost_for_built_in_tools, total_cost_usd_dollar=_final_cost, + additional_costs=additional_costs, original_cost=original_cost, discount_percent=discount_percent, discount_amount=discount_amount, @@ -1710,10 +1780,16 @@ def default_image_cost_calculator( ) # Priority 1: Use per-image pricing if available (for gpt-image-1 and similar models) - if "input_cost_per_image" in cost_info and cost_info["input_cost_per_image"] is not None: + if ( + "input_cost_per_image" in cost_info + and cost_info["input_cost_per_image"] is not None + ): return cost_info["input_cost_per_image"] * n # Priority 2: Fall back to per-pixel pricing for backward compatibility - elif "input_cost_per_pixel" in cost_info and cost_info["input_cost_per_pixel"] is not None: + elif ( + "input_cost_per_pixel" in cost_info + and cost_info["input_cost_per_pixel"] is not None + ): return cost_info["input_cost_per_pixel"] * height * width * n else: raise Exception( @@ -1833,9 +1909,22 @@ def batch_cost_calculator( if input_cost_per_token_batches: total_prompt_cost = usage.prompt_tokens * input_cost_per_token_batches elif input_cost_per_token: + # Subtract cached tokens from prompt_tokens before calculating cost + # Fixes issue where cached tokens are being charged again total_prompt_cost = ( - usage.prompt_tokens * (input_cost_per_token) / 2 + get_billable_input_tokens(usage) * (input_cost_per_token) / 2 ) # batch cost is usually half of the regular token cost + + # Add cache read cost if applicable + details = _parse_prompt_tokens_details(usage) + cache_read_tokens = details["cache_hit_tokens"] + cache_read_cost_key = _get_service_tier_cost_key( + "cache_read_input_token_cost", None + ) + total_prompt_cost += ( + calculate_cost_component(model_info, cache_read_cost_key, cache_read_tokens) + / 2 + ) if output_cost_per_token_batches: total_completion_cost = usage.completion_tokens * output_cost_per_token_batches elif output_cost_per_token: diff --git a/litellm/experimental_mcp_client/client.py b/litellm/experimental_mcp_client/client.py index 582d044023c..3e8f9bc337b 100644 --- a/litellm/experimental_mcp_client/client.py +++ b/litellm/experimental_mcp_client/client.py @@ -4,22 +4,28 @@ LiteLLM Proxy uses this MCP Client to connnect to other MCP servers. import asyncio import base64 -from typing import Awaitable, Callable, Dict, List, Optional, TypeVar, Union +from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple, TypeVar, Union import httpx from mcp import ClientSession, ReadResourceResult, Resource, StdioServerParameters from mcp.client.sse import sse_client from mcp.client.stdio import stdio_client -from mcp.client.streamable_http import streamable_http_client + +streamable_http_client: Optional[Any] = None +try: + import mcp.client.streamable_http as streamable_http_module # type: ignore + streamable_http_client = getattr(streamable_http_module, "streamable_http_client", None) +except ImportError: + pass +from mcp.types import CallToolRequestParams as MCPCallToolRequestParams +from mcp.types import CallToolResult as MCPCallToolResult from mcp.types import ( - CallToolRequestParams as MCPCallToolRequestParams, GetPromptRequestParams, GetPromptResult, Prompt, ResourceTemplate, + TextContent, ) -from mcp.types import CallToolResult as MCPCallToolResult -from mcp.types import TextContent from mcp.types import Tool as MCPTool from pydantic import AnyUrl @@ -74,57 +80,97 @@ class MCPClient: if auth_value: self.update_auth_value(auth_value) + def _create_transport_context( + self, + ) -> Tuple[Any, Optional[httpx.AsyncClient]]: + """ + Create the appropriate transport context based on transport type. + + Returns: + Tuple of (transport_context, http_client). + http_client is only set for HTTP transport and needs cleanup. + """ + http_client: Optional[httpx.AsyncClient] = None + + if self.transport_type == MCPTransport.stdio: + if not self.stdio_config: + raise ValueError("stdio_config is required for stdio transport") + server_params = StdioServerParameters( + command=self.stdio_config.get("command", ""), + args=self.stdio_config.get("args", []), + env=self.stdio_config.get("env", {}), + ) + return stdio_client(server_params), None + + if self.transport_type == MCPTransport.sse: + headers = self._get_auth_headers() + httpx_client_factory = self._create_httpx_client_factory() + return sse_client( + url=self.server_url, + timeout=self.timeout, + headers=headers, + httpx_client_factory=httpx_client_factory, + ), None + + # HTTP transport (default) + if streamable_http_client is None: + raise ImportError( + "streamable_http_client is not available. " + "Please install mcp with HTTP support." + ) + + headers = self._get_auth_headers() + httpx_client_factory = self._create_httpx_client_factory() + verbose_logger.debug( + "litellm headers for streamable_http_client: %s", headers + ) + http_client = httpx_client_factory( + headers=headers, + timeout=httpx.Timeout(self.timeout), + ) + transport_ctx = streamable_http_client( + url=self.server_url, + http_client=http_client, + ) + return transport_ctx, http_client + + async def _execute_session_operation( + self, + transport_ctx: Any, + operation: Callable[[ClientSession], Awaitable[TSessionResult]], + ) -> TSessionResult: + """ + Execute an operation within a transport and session context. + + Handles entering/exiting contexts and running the operation. + """ + transport = await transport_ctx.__aenter__() + try: + read_stream, write_stream = transport[0], transport[1] + session_ctx = ClientSession(read_stream, write_stream) + session = await session_ctx.__aenter__() + try: + await session.initialize() + return await operation(session) + finally: + try: + await session_ctx.__aexit__(None, None, None) + except BaseException as e: + verbose_logger.debug(f"Error during session context exit: {e}") + finally: + try: + await transport_ctx.__aexit__(None, None, None) + except BaseException as e: + verbose_logger.debug(f"Error during transport context exit: {e}") + async def run_with_session( self, operation: Callable[[ClientSession], Awaitable[TSessionResult]] ) -> TSessionResult: """Open a session, run the provided coroutine, and clean up.""" - transport_ctx = None http_client: Optional[httpx.AsyncClient] = None - try: - if self.transport_type == MCPTransport.stdio: - if not self.stdio_config: - raise ValueError("stdio_config is required for stdio transport") - - server_params = StdioServerParameters( - command=self.stdio_config.get("command", ""), - args=self.stdio_config.get("args", []), - env=self.stdio_config.get("env", {}), - ) - transport_ctx = stdio_client(server_params) - elif self.transport_type == MCPTransport.sse: - headers = self._get_auth_headers() - httpx_client_factory = self._create_httpx_client_factory() - transport_ctx = sse_client( - url=self.server_url, - timeout=self.timeout, - headers=headers, - httpx_client_factory=httpx_client_factory, - ) - else: - headers = self._get_auth_headers() - httpx_client_factory = self._create_httpx_client_factory() - verbose_logger.debug( - "litellm headers for streamable_http_client: %s", headers - ) - http_client = httpx_client_factory( - headers=headers, - timeout=httpx.Timeout(self.timeout), - ) - transport_ctx = streamable_http_client( - url=self.server_url, - http_client=http_client, - ) - - if transport_ctx is None: - raise RuntimeError("Failed to create transport context") - - async with transport_ctx as transport: - read_stream, write_stream = transport[0], transport[1] - session_ctx = ClientSession(read_stream, write_stream) - async with session_ctx as session: - await session.initialize() - return await operation(session) + transport_ctx, http_client = self._create_transport_context() + return await self._execute_session_operation(transport_ctx, operation) except Exception: verbose_logger.warning( "MCP client run_with_session failed for %s", self.server_url or "stdio" @@ -132,7 +178,10 @@ class MCPClient: raise finally: if http_client is not None: - await http_client.aclose() + try: + await http_client.aclose() + except BaseException as e: + verbose_logger.debug(f"Error during http_client cleanup: {e}") def update_auth_value(self, mcp_auth_value: Union[str, Dict[str, str]]): """ @@ -245,7 +294,9 @@ class MCPClient: return [] async def call_tool( - self, call_tool_request_params: MCPCallToolRequestParams + self, + call_tool_request_params: MCPCallToolRequestParams, + host_progress_callback: Optional[Callable] = None ) -> MCPCallToolResult: """ Call an MCP Tool. @@ -254,13 +305,28 @@ class MCPClient: f"MCP client calling tool '{call_tool_request_params.name}' with arguments: {call_tool_request_params.arguments}" ) + async def on_progress(progress: float, total: float | None, message: str | None): + percentage = (progress / total * 100) if total else 0 + verbose_logger.info( + f"MCP Tool '{call_tool_request_params.name}' progress: " + f"{progress}/{total} ({percentage:.0f}%) - {message or ''}" + ) + + # Forward to Host if callback provided + if host_progress_callback: + try: + await host_progress_callback(progress, total) + except Exception as e: + verbose_logger.warning(f"Failed to forward to Host: {e}") + async def _call_tool_operation(session: ClientSession): verbose_logger.debug("MCP client sending tool call to session") return await session.call_tool( name=call_tool_request_params.name, arguments=call_tool_request_params.arguments, - ) + progress_callback=on_progress, + ) try: tool_result = await self.run_with_session(_call_tool_operation) verbose_logger.info( diff --git a/litellm/files/main.py b/litellm/files/main.py index 913ec84626d..78e41bb5a68 100644 --- a/litellm/files/main.py +++ b/litellm/files/main.py @@ -9,6 +9,7 @@ import asyncio import contextvars import os import time +import uuid as uuid_module from functools import partial from typing import Any, Coroutine, Dict, Literal, Optional, Union, cast @@ -61,7 +62,7 @@ async def acreate_file( file: FileTypes, purpose: Literal["assistants", "batch", "fine-tune"], expires_after: Optional[FileExpiresAfter] = None, - custom_llm_provider: Literal["openai", "azure", "vertex_ai", "bedrock", "hosted_vllm", "manus"] = "openai", + custom_llm_provider: Literal["openai", "azure", "gemini", "vertex_ai", "bedrock", "hosted_vllm", "manus"] = "openai", extra_headers: Optional[Dict[str, str]] = None, extra_body: Optional[Dict[str, str]] = None, **kwargs, @@ -106,7 +107,7 @@ def create_file( file: FileTypes, purpose: Literal["assistants", "batch", "fine-tune"], expires_after: Optional[FileExpiresAfter] = None, - custom_llm_provider: Optional[Literal["openai", "azure", "vertex_ai", "bedrock", "hosted_vllm", "manus"]] = None, + custom_llm_provider: Optional[Literal["openai", "azure", "gemini", "vertex_ai", "bedrock", "hosted_vllm", "manus"]] = None, extra_headers: Optional[Dict[str, str]] = None, extra_body: Optional[Dict[str, str]] = None, **kwargs, @@ -294,7 +295,7 @@ def create_file( @client async def afile_retrieve( file_id: str, - custom_llm_provider: Literal["openai", "azure", "hosted_vllm", "manus"] = "openai", + custom_llm_provider: Literal["openai", "azure", "gemini", "hosted_vllm", "manus"] = "openai", extra_headers: Optional[Dict[str, str]] = None, extra_body: Optional[Dict[str, str]] = None, **kwargs, @@ -450,7 +451,7 @@ def file_retrieve( stream=False, call_type="afile_retrieve" if _is_async else "file_retrieve", start_time=time.time(), - litellm_call_id=kwargs.get("litellm_call_id", str(uuid.uuid4())), + litellm_call_id=kwargs.get("litellm_call_id", str(uuid_module.uuid4())), function_id=str(kwargs.get("id") or ""), ) @@ -493,7 +494,7 @@ def file_retrieve( @client async def afile_delete( file_id: str, - custom_llm_provider: Literal["openai", "azure", "manus"] = "openai", + custom_llm_provider: Literal["openai", "azure", "gemini", "manus"] = "openai", extra_headers: Optional[Dict[str, str]] = None, extra_body: Optional[Dict[str, str]] = None, **kwargs, @@ -537,7 +538,7 @@ async def afile_delete( def file_delete( file_id: str, model: Optional[str] = None, - custom_llm_provider: Union[Literal["openai", "azure", "manus"], str] = "openai", + custom_llm_provider: Union[Literal["openai", "azure", "gemini", "manus"], str] = "openai", extra_headers: Optional[Dict[str, str]] = None, extra_body: Optional[Dict[str, str]] = None, **kwargs, @@ -659,7 +660,7 @@ def file_delete( stream=False, call_type="afile_delete" if _is_async else "file_delete", start_time=time.time(), - litellm_call_id=kwargs.get("litellm_call_id", str(uuid.uuid4())), + litellm_call_id=kwargs.get("litellm_call_id", str(uuid_module.uuid4())), function_id=str(kwargs.get("id") or ""), ) @@ -680,7 +681,7 @@ def file_delete( ) else: raise litellm.exceptions.BadRequestError( - message="LiteLLM doesn't support {} for 'file_delete'. Only 'openai', 'azure', and 'manus' are supported.".format( + message="LiteLLM doesn't support {} for 'file_delete'. Only 'openai', 'azure', 'gemini', and 'manus' are supported.".format( custom_llm_provider ), model="n/a", @@ -792,7 +793,7 @@ def file_list( stream=False, call_type="afile_list" if _is_async else "file_list", start_time=time.time(), - litellm_call_id=kwargs.get("litellm_call_id", str(uuid.uuid4())), + litellm_call_id=kwargs.get("litellm_call_id", str(uuid_module.uuid4())), function_id=str(kwargs.get("id", "")), ) diff --git a/litellm/integrations/SlackAlerting/slack_alerting.py b/litellm/integrations/SlackAlerting/slack_alerting.py index 0c36e15db01..8fb3e132ded 100644 --- a/litellm/integrations/SlackAlerting/slack_alerting.py +++ b/litellm/integrations/SlackAlerting/slack_alerting.py @@ -1378,6 +1378,11 @@ Model Info: """ if self.alerting is None: return + + # Start periodic flush if not already started + if not self.periodic_started and self.alerting is not None and len(self.alerting) > 0: + asyncio.create_task(self.periodic_flush()) + self.periodic_started = True if ( "webhook" in self.alerting diff --git a/litellm/integrations/braintrust_logging.py b/litellm/integrations/braintrust_logging.py index 585de510e8b..42e9680a7fc 100644 --- a/litellm/integrations/braintrust_logging.py +++ b/litellm/integrations/braintrust_logging.py @@ -9,6 +9,10 @@ import httpx import litellm from litellm import verbose_logger +from litellm.integrations.braintrust_mock_client import ( + should_use_braintrust_mock, + create_mock_braintrust_client, +) from litellm.integrations.custom_logger import CustomLogger from litellm.llms.custom_httpx.http_handler import ( HTTPHandler, @@ -34,6 +38,10 @@ class BraintrustLogger(CustomLogger): self, api_key: Optional[str] = None, api_base: Optional[str] = None ) -> None: super().__init__() + self.is_mock_mode = should_use_braintrust_mock() + if self.is_mock_mode: + create_mock_braintrust_client() + verbose_logger.info("[BRAINTRUST MOCK] Braintrust logger initialized in mock mode") self.validate_environment(api_key=api_key) self.api_base = api_base or os.getenv("BRAINTRUST_API_BASE") or API_BASE self.default_project_id = None @@ -254,6 +262,8 @@ class BraintrustLogger(CustomLogger): json={"events": [request_data]}, headers=self.headers, ) + if self.is_mock_mode: + print_verbose("[BRAINTRUST MOCK] Sync event successfully mocked") except httpx.HTTPStatusError as e: raise Exception(e.response.text) except Exception as e: @@ -399,6 +409,8 @@ class BraintrustLogger(CustomLogger): json={"events": [request_data]}, headers=self.headers, ) + if self.is_mock_mode: + print_verbose("[BRAINTRUST MOCK] Async event successfully mocked") except httpx.HTTPStatusError as e: raise Exception(e.response.text) except Exception as e: diff --git a/litellm/integrations/braintrust_mock_client.py b/litellm/integrations/braintrust_mock_client.py new file mode 100644 index 00000000000..030aa62cd0f --- /dev/null +++ b/litellm/integrations/braintrust_mock_client.py @@ -0,0 +1,131 @@ +""" +Mock HTTP client for Braintrust integration testing. + +This module intercepts Braintrust API calls and returns successful mock responses, +allowing full code execution without making actual network calls. + +Usage: + Set BRAINTRUST_MOCK=true in environment variables or config to enable mock mode. +""" + +import os +import time +from urllib.parse import urlparse + +from litellm._logging import verbose_logger +from litellm.integrations.mock_client_factory import MockClientConfig, MockResponse, create_mock_client_factory + +# Use factory for should_use_mock and MockResponse +# Braintrust uses both HTTPHandler (sync) and AsyncHTTPHandler (async) +# Braintrust needs endpoint-specific responses, so we use custom HTTPHandler.post patching +_config = MockClientConfig( + "BRAINTRUST", + "BRAINTRUST_MOCK", + default_latency_ms=100, + default_status_code=200, + default_json_data={"id": "mock-project-id", "status": "success"}, + url_matchers=[ + ".braintrustdata.com", + "braintrustdata.com", + ".braintrust.dev", + "braintrust.dev", + ], + patch_async_handler=True, # Patch AsyncHTTPHandler.post for async calls + patch_sync_client=False, # HTTPHandler uses self.client.send(), not self.client.post() + patch_http_handler=False, # We use custom patching for endpoint-specific responses +) + +# Get should_use_mock and create_mock_client from factory +# We need to call the factory's create_mock_client to patch AsyncHTTPHandler.post +create_mock_braintrust_factory_client, should_use_braintrust_mock = create_mock_client_factory(_config) + +# Store original HTTPHandler.post method (Braintrust-specific for sync calls with custom logic) +_original_http_handler_post = None +_mocks_initialized = False + +# Default mock latency in seconds +_MOCK_LATENCY_SECONDS = float(os.getenv("BRAINTRUST_MOCK_LATENCY_MS", "100")) / 1000.0 + + +def _is_braintrust_url(url: str) -> bool: + """Check if URL is a Braintrust API URL.""" + if not isinstance(url, str): + return False + + parsed = urlparse(url) + host = (parsed.hostname or "").lower() + + if not host: + return False + + return ( + host == "braintrustdata.com" + or host.endswith(".braintrustdata.com") + or host == "braintrust.dev" + or host.endswith(".braintrust.dev") + ) + + +def _mock_http_handler_post(self, url, data=None, json=None, params=None, headers=None, timeout=None, stream=False, files=None, content=None, logging_obj=None): + """Monkey-patched HTTPHandler.post that intercepts Braintrust calls with endpoint-specific responses.""" + # Only mock Braintrust API calls + if isinstance(url, str) and _is_braintrust_url(url): + verbose_logger.info(f"[BRAINTRUST MOCK] POST to {url}") + time.sleep(_MOCK_LATENCY_SECONDS) + # Return appropriate mock response based on endpoint + if "/project" in url: + # Project creation/retrieval/register endpoint + project_name = json.get("name", "litellm") if json else "litellm" + mock_data = {"id": f"mock-project-id-{project_name}", "name": project_name} + elif "/project_logs" in url: + # Log insertion endpoint + mock_data = {"status": "success"} + else: + mock_data = _config.default_json_data + return MockResponse( + status_code=_config.default_status_code, + json_data=mock_data, + url=url, + elapsed_seconds=_MOCK_LATENCY_SECONDS + ) + if _original_http_handler_post is not None: + return _original_http_handler_post(self, url=url, data=data, json=json, params=params, headers=headers, timeout=timeout, stream=stream, files=files, content=content, logging_obj=logging_obj) + raise RuntimeError("Original HTTPHandler.post not available") + + +def create_mock_braintrust_client(): + """ + Monkey-patch HTTPHandler.post to intercept Braintrust sync calls. + + Braintrust uses HTTPHandler for sync calls and AsyncHTTPHandler for async calls. + HTTPHandler.post uses self.client.send(), not self.client.post(), so we need + custom patching for sync (similar to Helicone). + AsyncHTTPHandler.post is patched by the factory. + + We use custom patching instead of factory's patch_http_handler because we need + endpoint-specific responses (different for /project vs /project_logs). + + This function is idempotent - it only initializes mocks once, even if called multiple times. + """ + global _original_http_handler_post, _mocks_initialized + + if _mocks_initialized: + return + + verbose_logger.debug("[BRAINTRUST MOCK] Initializing Braintrust mock client...") + + from litellm.llms.custom_httpx.http_handler import HTTPHandler + + if _original_http_handler_post is None: + _original_http_handler_post = HTTPHandler.post + HTTPHandler.post = _mock_http_handler_post # type: ignore + verbose_logger.debug("[BRAINTRUST MOCK] Patched HTTPHandler.post") + + # CRITICAL: Call the factory's initialization function to patch AsyncHTTPHandler.post + # This is required for async calls to be mocked + create_mock_braintrust_factory_client() + + verbose_logger.debug(f"[BRAINTRUST MOCK] Mock latency set to {_MOCK_LATENCY_SECONDS*1000:.0f}ms") + verbose_logger.debug("[BRAINTRUST MOCK] Braintrust mock client initialization complete") + + _mocks_initialized = True diff --git a/litellm/integrations/callback_configs.json b/litellm/integrations/callback_configs.json index 6b30b6b736e..6a003b8c499 100644 --- a/litellm/integrations/callback_configs.json +++ b/litellm/integrations/callback_configs.json @@ -83,6 +83,33 @@ }, "description": "Datadog Logging Integration" }, + { + "id": "datadog_cost_management", + "displayName": "Datadog Cost Management", + "logo": "datadog.png", + "supports_key_team_logging": false, + "dynamic_params": { + "dd_api_key": { + "type": "password", + "ui_name": "API Key", + "description": "Datadog API key for authentication", + "required": true + }, + "dd_app_key": { + "type": "password", + "ui_name": "App Key", + "description": "Datadog Application Key for Cloud Cost Management", + "required": true + }, + "dd_site": { + "type": "text", + "ui_name": "Site", + "description": "Datadog site URL (e.g., us5.datadoghq.com)", + "required": true + } + }, + "description": "Datadog Cloud Cost Management Integration" + }, { "id": "lago", "displayName": "Lago", @@ -407,4 +434,4 @@ }, "description": "SQS Queue (AWS) Logging Integration" } -] +] \ No newline at end of file diff --git a/litellm/integrations/custom_guardrail.py b/litellm/integrations/custom_guardrail.py index 6a76b57e7f7..a5bb530fc56 100644 --- a/litellm/integrations/custom_guardrail.py +++ b/litellm/integrations/custom_guardrail.py @@ -516,7 +516,9 @@ class CustomGuardrail(CustomLogger): from litellm.types.utils import GuardrailMode # Use event_type if provided, otherwise fall back to self.event_hook - guardrail_mode: Union[GuardrailEventHooks, GuardrailMode, List[GuardrailEventHooks]] + guardrail_mode: Union[ + GuardrailEventHooks, GuardrailMode, List[GuardrailEventHooks] + ] if event_type is not None: guardrail_mode = event_type elif isinstance(self.event_hook, Mode): @@ -524,11 +526,21 @@ class CustomGuardrail(CustomLogger): else: guardrail_mode = self.event_hook # type: ignore[assignment] + from litellm.litellm_core_utils.core_helpers import ( + filter_exceptions_from_params, + ) + + # Sanitize the response to ensure it's JSON serializable and free of circular refs + # This prevents RecursionErrors in downstream loggers (Langfuse, Datadog, etc.) + clean_guardrail_response = filter_exceptions_from_params( + guardrail_json_response + ) + slg = StandardLoggingGuardrailInformation( guardrail_name=self.guardrail_name, guardrail_provider=guardrail_provider, guardrail_mode=guardrail_mode, - guardrail_response=guardrail_json_response, + guardrail_response=clean_guardrail_response, guardrail_status=guardrail_status, start_time=start_time, end_time=end_time, diff --git a/litellm/integrations/custom_logger.py b/litellm/integrations/custom_logger.py index 12243a19184..07d237c4758 100644 --- a/litellm/integrations/custom_logger.py +++ b/litellm/integrations/custom_logger.py @@ -371,6 +371,28 @@ class CustomLogger: # https://docs.litellm.ai/docs/observability/custom_callbac ]: # raise exception if invalid, return a str for the user to receive - if rejected, or return a modified dictionary for passing into litellm pass + async def async_post_call_response_headers_hook( + self, + data: dict, + user_api_key_dict: UserAPIKeyAuth, + response: Any, + request_headers: Optional[Dict[str, str]] = None, + ) -> Optional[Dict[str, str]]: + """ + Called after an LLM API call (success or failure) to allow injecting custom HTTP response headers. + + Args: + - data: dict - The request data. + - user_api_key_dict: UserAPIKeyAuth - The user API key dictionary. + - response: Any - The response object (None for failure cases). + - request_headers: Optional[Dict[str, str]] - The original request headers. + + Returns: + - Optional[Dict[str, str]]: A dictionary of headers to inject into the HTTP response. + Return None to not inject any headers. + """ + return None + async def async_post_call_failure_hook( self, request_data: dict, diff --git a/litellm/integrations/datadog/datadog.py b/litellm/integrations/datadog/datadog.py index 503e8d8c87a..127b0e53fa8 100644 --- a/litellm/integrations/datadog/datadog.py +++ b/litellm/integrations/datadog/datadog.py @@ -27,11 +27,16 @@ import litellm from litellm._logging import verbose_logger from litellm._uuid import uuid from litellm.integrations.custom_batch_logger import CustomBatchLogger +from litellm.integrations.datadog.datadog_mock_client import ( + should_use_datadog_mock, + create_mock_datadog_client, +) from litellm.integrations.datadog.datadog_handler import ( get_datadog_hostname, get_datadog_service, get_datadog_source, get_datadog_tags, + get_datadog_base_url_from_env, ) from litellm.litellm_core_utils.dd_tracing import tracer from litellm.llms.custom_httpx.http_handler import ( @@ -80,6 +85,12 @@ class DataDogLogger( """ try: verbose_logger.debug("Datadog: in init datadog logger") + + self.is_mock_mode = should_use_datadog_mock() + + if self.is_mock_mode: + create_mock_datadog_client() + verbose_logger.debug("[DATADOG MOCK] Datadog logger initialized in mock mode") ######################################################### # Handle datadog_params set as litellm.datadog_params @@ -100,7 +111,9 @@ class DataDogLogger( self._configure_dd_direct_api() # Optional override for testing - self._apply_dd_base_url_override() + dd_base_url = get_datadog_base_url_from_env() + if dd_base_url: + self.intake_url = f"{dd_base_url}/api/v2/logs" self.sync_client = _get_httpx_client() asyncio.create_task(self.periodic_flush()) self.flush_lock = asyncio.Lock() @@ -159,18 +172,6 @@ class DataDogLogger( self.DD_API_KEY = os.getenv("DD_API_KEY") self.intake_url = f"https://http-intake.logs.{os.getenv('DD_SITE')}/api/v2/logs" - def _apply_dd_base_url_override(self) -> None: - """ - Apply base URL override for testing purposes - """ - dd_base_url: Optional[str] = ( - os.getenv("_DATADOG_BASE_URL") - or os.getenv("DATADOG_BASE_URL") - or os.getenv("DD_BASE_URL") - ) - if dd_base_url is not None: - self.intake_url = f"{dd_base_url}/api/v2/logs" - async def async_log_success_event(self, kwargs, response_obj, start_time, end_time): """ Async Log success events to Datadog @@ -229,6 +230,9 @@ class DataDogLogger( len(self.log_queue), self.intake_url, ) + + if self.is_mock_mode: + verbose_logger.debug("[DATADOG MOCK] Mock mode enabled - API calls will be intercepted") response = await self.async_send_compressed_data(self.log_queue) if response.status_code == 413: @@ -241,11 +245,16 @@ class DataDogLogger( f"Response from datadog API status_code: {response.status_code}, text: {response.text}" ) - verbose_logger.debug( - "Datadog: Response from datadog API status_code: %s, text: %s", - response.status_code, - response.text, - ) + if self.is_mock_mode: + verbose_logger.debug( + f"[DATADOG MOCK] Batch of {len(self.log_queue)} events successfully mocked" + ) + else: + verbose_logger.debug( + "Datadog: Response from datadog API status_code: %s, text: %s", + response.status_code, + response.text, + ) except Exception as e: verbose_logger.exception( f"Datadog Error sending batch API - {str(e)}\n{traceback.format_exc()}" diff --git a/litellm/integrations/datadog/datadog_cost_management.py b/litellm/integrations/datadog/datadog_cost_management.py new file mode 100644 index 00000000000..2eb94b59dd8 --- /dev/null +++ b/litellm/integrations/datadog/datadog_cost_management.py @@ -0,0 +1,204 @@ +import asyncio +import os +import time +from datetime import datetime +from typing import Dict, List, Optional, Tuple + +from litellm._logging import verbose_logger +from litellm.integrations.custom_batch_logger import CustomBatchLogger +from litellm.llms.custom_httpx.http_handler import ( + get_async_httpx_client, + httpxSpecialProvider, +) +from litellm.types.integrations.datadog_cost_management import ( + DatadogFOCUSCostEntry, +) +from litellm.types.utils import StandardLoggingPayload + + +class DatadogCostManagementLogger(CustomBatchLogger): + def __init__(self, **kwargs): + self.dd_api_key = os.getenv("DD_API_KEY") + self.dd_app_key = os.getenv("DD_APP_KEY") + self.dd_site = os.getenv("DD_SITE", "datadoghq.com") + + if not self.dd_api_key or not self.dd_app_key: + verbose_logger.warning( + "Datadog Cost Management: DD_API_KEY and DD_APP_KEY are required. Integration will not work." + ) + + self.upload_url = f"https://api.{self.dd_site}/api/v2/cost/custom_costs" + + self.async_client = get_async_httpx_client( + llm_provider=httpxSpecialProvider.LoggingCallback + ) + + # Initialize lock and start periodic flush task + self.flush_lock = asyncio.Lock() + asyncio.create_task(self.periodic_flush()) + + # Check if flush_lock is already in kwargs to avoid double passing (unlikely but safe) + if "flush_lock" not in kwargs: + kwargs["flush_lock"] = self.flush_lock + + super().__init__(**kwargs) + + async def async_log_success_event(self, kwargs, response_obj, start_time, end_time): + try: + standard_logging_object: Optional[StandardLoggingPayload] = kwargs.get( + "standard_logging_object", None + ) + + if standard_logging_object is None: + return + + # Only log if there is a cost associated + if standard_logging_object.get("response_cost", 0) > 0: + self.log_queue.append(standard_logging_object) + + if len(self.log_queue) >= self.batch_size: + await self.async_send_batch() + + except Exception as e: + verbose_logger.exception( + f"Datadog Cost Management: Error in async_log_success_event: {str(e)}" + ) + + async def async_send_batch(self): + if not self.log_queue: + return + + try: + # Aggregate costs from the batch + aggregated_entries = self._aggregate_costs(self.log_queue) + + if not aggregated_entries: + return + + # Send to Datadog + await self._upload_to_datadog(aggregated_entries) + + # Clear queue only on success (or if we decide to drop on failure) + # CustomBatchLogger clears queue in flush_queue, so we just process here + + except Exception as e: + verbose_logger.exception( + f"Datadog Cost Management: Error in async_send_batch: {str(e)}" + ) + + def _aggregate_costs( + self, logs: List[StandardLoggingPayload] + ) -> List[DatadogFOCUSCostEntry]: + """ + Aggregates costs by Provider, Model, and Date. + Returns a list of DatadogFOCUSCostEntry. + """ + aggregator: Dict[Tuple[str, str, str, Tuple[Tuple[str, str], ...]], DatadogFOCUSCostEntry] = {} + + for log in logs: + try: + # Extract keys for aggregation + provider = log.get("custom_llm_provider") or "unknown" + model = log.get("model") or "unknown" + cost = log.get("response_cost", 0) + + if cost == 0: + continue + + # Get date strings (FOCUS format requires specific keys, but for aggregation we group by Day) + # UTC date + # We interpret "ChargePeriod" as the day of the request. + ts = log.get("startTime") or time.time() + dt = datetime.fromtimestamp(ts) + date_str = dt.strftime("%Y-%m-%d") + + # ChargePeriodStart and End + # If we want daily granularity, end date is usually same day or next day? + # Datadog Custom Costs usually expects periods. + # "ChargePeriodStart": "2023-01-01", "ChargePeriodEnd": "2023-12-31" in example. + # If we send daily, we can say Start=Date, End=Date. + + # Grouping Key: Provider + Model + Date + Tags? + # For simplicity, let's aggregate by Provider + Model + Date first. + # If we handle tags, we need to include them in the key. + + tags = self._extract_tags(log) + tags_key = tuple(sorted(tags.items())) if tags else () + + key = (provider, model, date_str, tags_key) + + if key not in aggregator: + aggregator[key] = { + "ProviderName": provider, + "ChargeDescription": f"LLM Usage for {model}", + "ChargePeriodStart": date_str, + "ChargePeriodEnd": date_str, + "BilledCost": 0.0, + "BillingCurrency": "USD", + "Tags": tags if tags else None, + } + + aggregator[key]["BilledCost"] += cost + + except Exception as e: + verbose_logger.warning( + f"Error processing log for cost aggregation: {e}" + ) + continue + + return list(aggregator.values()) + + def _extract_tags(self, log: StandardLoggingPayload) -> Dict[str, str]: + from litellm.integrations.datadog.datadog_handler import ( + get_datadog_env, + get_datadog_hostname, + get_datadog_pod_name, + get_datadog_service, + ) + + tags = { + "env": get_datadog_env(), + "service": get_datadog_service(), + "host": get_datadog_hostname(), + "pod_name": get_datadog_pod_name(), + } + + # Add metadata as tags + metadata = log.get("metadata", {}) + if metadata: + # Add user info + if "user_api_key_alias" in metadata: + tags["user"] = str(metadata["user_api_key_alias"]) + if "user_api_key_team_alias" in metadata: + tags["team"] = str(metadata["user_api_key_team_alias"]) + # model_group is not in StandardLoggingMetadata TypedDict, so we need to access it via dict.get() + model_group = metadata.get("model_group") # type: ignore[misc] + if model_group: + tags["model_group"] = str(model_group) + + return tags + + async def _upload_to_datadog(self, payload: List[Dict]): + if not self.dd_api_key or not self.dd_app_key: + return + + headers = { + "Content-Type": "application/json", + "DD-API-KEY": self.dd_api_key, + "DD-APPLICATION-KEY": self.dd_app_key, + } + + # The API endpoint expects a list of objects directly in the body (file content behavior) + from litellm.litellm_core_utils.safe_json_dumps import safe_dumps + + data_json = safe_dumps(payload) + + response = await self.async_client.put( + self.upload_url, content=data_json, headers=headers + ) + + response.raise_for_status() + + verbose_logger.debug( + f"Datadog Cost Management: Uploaded {len(payload)} cost entries. Status: {response.status_code}" + ) diff --git a/litellm/integrations/datadog/datadog_handler.py b/litellm/integrations/datadog/datadog_handler.py index 26fab77759e..e2f30f2f614 100644 --- a/litellm/integrations/datadog/datadog_handler.py +++ b/litellm/integrations/datadog/datadog_handler.py @@ -20,6 +20,14 @@ def get_datadog_hostname() -> str: return os.getenv("HOSTNAME", "") +def get_datadog_base_url_from_env() -> Optional[str]: + """ + Get base URL override from common DD_BASE_URL env var. + This is useful for testing or custom endpoints. + """ + return os.getenv("DD_BASE_URL") + + def get_datadog_env() -> str: return os.getenv("DD_ENV", "unknown") diff --git a/litellm/integrations/datadog/datadog_llm_obs.py b/litellm/integrations/datadog/datadog_llm_obs.py index 6ffdbc0a005..e5ce9997491 100644 --- a/litellm/integrations/datadog/datadog_llm_obs.py +++ b/litellm/integrations/datadog/datadog_llm_obs.py @@ -18,9 +18,14 @@ import httpx import litellm from litellm._logging import verbose_logger from litellm.integrations.custom_batch_logger import CustomBatchLogger +from litellm.integrations.datadog.datadog_mock_client import ( + should_use_datadog_mock, + create_mock_datadog_client, +) from litellm.integrations.datadog.datadog_handler import ( get_datadog_service, get_datadog_tags, + get_datadog_base_url_from_env, ) from litellm.litellm_core_utils.dd_tracing import tracer from litellm.litellm_core_utils.prompt_templates.common_utils import ( @@ -43,24 +48,37 @@ class DataDogLLMObsLogger(CustomBatchLogger): def __init__(self, **kwargs): try: verbose_logger.debug("DataDogLLMObs: Initializing logger") - if os.getenv("DD_API_KEY", None) is None: - raise Exception("DD_API_KEY is not set, set 'DD_API_KEY=<>'") - if os.getenv("DD_SITE", None) is None: - raise Exception( - "DD_SITE is not set, set 'DD_SITE=<>', example sit = `us5.datadoghq.com`" - ) + + self.is_mock_mode = should_use_datadog_mock() + + if self.is_mock_mode: + create_mock_datadog_client() + verbose_logger.debug("[DATADOG MOCK] DataDogLLMObs logger initialized in mock mode") + + # Configure DataDog endpoint (Agent or Direct API) + # Use LITELLM_DD_AGENT_HOST to avoid conflicts with ddtrace's DD_AGENT_HOST + # Check for agent mode FIRST - agent mode doesn't require DD_API_KEY or DD_SITE + dd_agent_host = os.getenv("LITELLM_DD_AGENT_HOST") self.async_client = get_async_httpx_client( llm_provider=httpxSpecialProvider.LoggingCallback ) self.DD_API_KEY = os.getenv("DD_API_KEY") - self.DD_SITE = os.getenv("DD_SITE") - self.intake_url = ( - f"https://api.{self.DD_SITE}/api/intake/llm-obs/v1/trace/spans" - ) - # testing base url - dd_base_url = os.getenv("DD_BASE_URL") + if dd_agent_host: + self._configure_dd_agent(dd_agent_host=dd_agent_host) + else: + # Only require DD_API_KEY and DD_SITE for direct API mode + if os.getenv("DD_API_KEY", None) is None: + raise Exception("DD_API_KEY is not set, set 'DD_API_KEY=<>'") + if os.getenv("DD_SITE", None) is None: + raise Exception( + "DD_SITE is not set, set 'DD_SITE=<>', example sit = `us5.datadoghq.com`" + ) + self._configure_dd_direct_api() + + # Optional override for testing + dd_base_url = get_datadog_base_url_from_env() if dd_base_url: self.intake_url = f"{dd_base_url}/api/intake/llm-obs/v1/trace/spans" @@ -78,6 +96,38 @@ class DataDogLLMObsLogger(CustomBatchLogger): verbose_logger.exception(f"DataDogLLMObs: Error initializing - {str(e)}") raise e + def _configure_dd_agent(self, dd_agent_host: str): + """ + Configure the Datadog logger to send traces to the Agent. + """ + # When using the Agent, LLM Observability Intake does NOT require the API Key + # Reference: https://docs.datadoghq.com/llm_observability/setup/sdk/#agent-setup + + # Use specific port for LLM Obs (Trace Agent) to avoid conflict with Logs Agent (10518) + agent_port = os.getenv("LITELLM_DD_LLM_OBS_PORT", "8126") + self.DD_SITE = "localhost" # Not used for URL construction in agent mode + self.intake_url = ( + f"http://{dd_agent_host}:{agent_port}/api/intake/llm-obs/v1/trace/spans" + ) + verbose_logger.debug(f"DataDogLLMObs: Using DD Agent at {self.intake_url}") + + def _configure_dd_direct_api(self): + """ + Configure the Datadog logger to send traces directly to the Datadog API. + """ + if not self.DD_API_KEY: + raise Exception("DD_API_KEY is not set, set 'DD_API_KEY=<>'") + + self.DD_SITE = os.getenv("DD_SITE") + if not self.DD_SITE: + raise Exception( + "DD_SITE is not set, set 'DD_SITE=<>', example site = `us5.datadoghq.com`" + ) + + self.intake_url = ( + f"https://api.{self.DD_SITE}/api/intake/llm-obs/v1/trace/spans" + ) + def _get_datadog_llm_obs_params(self) -> Dict: """ Get the datadog_llm_observability_params from litellm.datadog_llm_observability_params @@ -139,6 +189,9 @@ class DataDogLLMObsLogger(CustomBatchLogger): verbose_logger.debug( f"DataDogLLMObs: Flushing {len(self.log_queue)} events" ) + + if self.is_mock_mode: + verbose_logger.debug("[DATADOG MOCK] Mock mode enabled - API calls will be intercepted") # Prepare the payload payload = { @@ -164,13 +217,14 @@ class DataDogLLMObsLogger(CustomBatchLogger): json_payload = safe_dumps(payload) + headers = {"Content-Type": "application/json"} + if self.DD_API_KEY: + headers["DD-API-KEY"] = self.DD_API_KEY + response = await self.async_client.post( url=self.intake_url, content=json_payload, - headers={ - "DD-API-KEY": self.DD_API_KEY, - "Content-Type": "application/json", - }, + headers=headers, ) if response.status_code != 202: @@ -178,9 +232,14 @@ class DataDogLLMObsLogger(CustomBatchLogger): f"DataDogLLMObs: Unexpected response - status_code: {response.status_code}, text: {response.text}" ) - verbose_logger.debug( - f"DataDogLLMObs: Successfully sent batch - status_code: {response.status_code}" - ) + if self.is_mock_mode: + verbose_logger.debug( + f"[DATADOG MOCK] Batch of {len(self.log_queue)} events successfully mocked" + ) + else: + verbose_logger.debug( + f"DataDogLLMObs: Successfully sent batch - status_code: {response.status_code}" + ) self.log_queue.clear() except httpx.HTTPStatusError as e: verbose_logger.exception( diff --git a/litellm/integrations/datadog/datadog_mock_client.py b/litellm/integrations/datadog/datadog_mock_client.py new file mode 100644 index 00000000000..a0a760deb0b --- /dev/null +++ b/litellm/integrations/datadog/datadog_mock_client.py @@ -0,0 +1,28 @@ +""" +Mock client for Datadog integration testing. + +This module intercepts Datadog API calls and returns successful mock responses, +allowing full code execution without making actual network calls. + +Usage: + Set DATADOG_MOCK=true in environment variables or config to enable mock mode. +""" + +from litellm.integrations.mock_client_factory import MockClientConfig, create_mock_client_factory + +# Create mock client using factory +_config = MockClientConfig( + name="DATADOG", + env_var="DATADOG_MOCK", + default_latency_ms=100, + default_status_code=202, + default_json_data={"status": "ok"}, + url_matchers=[ + ".datadoghq.com", + "datadoghq.com", + ], + patch_async_handler=True, + patch_sync_client=True, +) + +create_mock_datadog_client, should_use_datadog_mock = create_mock_client_factory(_config) diff --git a/litellm/integrations/gcs_bucket/gcs_bucket_mock_client.py b/litellm/integrations/gcs_bucket/gcs_bucket_mock_client.py index 6201dc343dc..2d14f5eb962 100644 --- a/litellm/integrations/gcs_bucket/gcs_bucket_mock_client.py +++ b/litellm/integrations/gcs_bucket/gcs_bucket_mock_client.py @@ -8,20 +8,28 @@ Usage: Set GCS_MOCK=true in environment variables or config to enable mock mode. """ -import httpx -import json import asyncio -from datetime import timedelta -from typing import Dict, Optional from litellm._logging import verbose_logger +from litellm.integrations.mock_client_factory import MockClientConfig, create_mock_client_factory, MockResponse -# Store original methods for restoration -_original_async_handler_post = None +# Use factory for POST handler +_config = MockClientConfig( + name="GCS", + env_var="GCS_MOCK", + default_latency_ms=150, + default_status_code=200, + default_json_data={"kind": "storage#object", "name": "mock-object"}, + url_matchers=["storage.googleapis.com"], + patch_async_handler=True, + patch_sync_client=False, +) + +_create_mock_gcs_post, should_use_gcs_mock = create_mock_client_factory(_config) + +# Store original methods for GET/DELETE (GCS-specific) _original_async_handler_get = None _original_async_handler_delete = None - -# Track if mocks have been initialized to avoid duplicate initialization _mocks_initialized = False # Default mock latency in seconds (simulates network round-trip) @@ -29,84 +37,59 @@ _mocks_initialized = False _MOCK_LATENCY_SECONDS = float(__import__("os").getenv("GCS_MOCK_LATENCY_MS", "150")) / 1000.0 -class MockGCSResponse: - """Mock httpx.Response that satisfies GCS API requirements.""" - - def __init__(self, status_code: int = 200, json_data: Optional[Dict] = None, url: Optional[str] = None, elapsed_seconds: float = 0.0): - self.status_code = status_code - self._json_data = json_data or {"kind": "storage#object", "name": "mock-object"} - self.headers = httpx.Headers({}) - self.is_success = status_code < 400 - self.is_error = status_code >= 400 - self.is_redirect = 300 <= status_code < 400 - self.url = httpx.URL(url) if url else httpx.URL("") - # Set realistic elapsed time based on mock latency - elapsed_time = elapsed_seconds if elapsed_seconds > 0 else _MOCK_LATENCY_SECONDS - self.elapsed = timedelta(seconds=elapsed_time) - self._text = json.dumps(self._json_data) - self._content = self._text.encode("utf-8") - - @property - def text(self) -> str: - """Return response text.""" - return self._text - - @property - def content(self) -> bytes: - """Return response content.""" - return self._content - - def json(self) -> Dict: - """Return JSON response data.""" - return self._json_data - - def read(self) -> bytes: - """Read response content.""" - return self._content - - def raise_for_status(self): - """Raise exception for error status codes.""" - if self.status_code >= 400: - raise Exception(f"HTTP {self.status_code}") - - -async def _mock_async_handler_post(self, url, data=None, json=None, params=None, headers=None, timeout=None, stream=False, logging_obj=None, files=None, content=None): - """Monkey-patched AsyncHTTPHandler.post that intercepts GCS calls.""" - # Only mock GCS API calls - if isinstance(url, str) and "storage.googleapis.com" in url: - verbose_logger.info(f"[GCS MOCK] POST to {url}") - # Simulate network latency - await asyncio.sleep(_MOCK_LATENCY_SECONDS) - return MockGCSResponse( - status_code=200, - json_data={"kind": "storage#object", "name": "mock-object"}, - url=url, - elapsed_seconds=_MOCK_LATENCY_SECONDS - ) - # For non-GCS calls, use original method - if _original_async_handler_post is not None: - return await _original_async_handler_post(self, url=url, data=data, json=json, params=params, headers=headers, timeout=timeout, stream=stream, logging_obj=logging_obj, files=files, content=content) - # Fallback: if original not set, raise error - raise RuntimeError("Original AsyncHTTPHandler.post not available") - - async def _mock_async_handler_get(self, url, params=None, headers=None, follow_redirects=None): """Monkey-patched AsyncHTTPHandler.get that intercepts GCS calls.""" # Only mock GCS API calls if isinstance(url, str) and "storage.googleapis.com" in url: verbose_logger.info(f"[GCS MOCK] GET to {url}") - # Simulate network latency await asyncio.sleep(_MOCK_LATENCY_SECONDS) - return MockGCSResponse( - status_code=200, - json_data={"data": "mock-log-data"}, + # Return a minimal but valid StandardLoggingPayload JSON string as bytes + # This matches what GCS returns when downloading with ?alt=media + mock_payload = { + "id": "mock-request-id", + "trace_id": "mock-trace-id", + "call_type": "completion", + "stream": False, + "response_cost": 0.0, + "status": "success", + "status_fields": {"llm_api_status": "success"}, + "custom_llm_provider": "mock", + "total_tokens": 0, + "prompt_tokens": 0, + "completion_tokens": 0, + "startTime": 0.0, + "endTime": 0.0, + "completionStartTime": 0.0, + "response_time": 0.0, + "model_map_information": {"model": "mock-model"}, + "model": "mock-model", + "model_id": None, + "model_group": None, + "api_base": "https://api.mock.com", + "metadata": {}, + "cache_hit": None, + "cache_key": None, + "saved_cache_cost": 0.0, + "request_tags": [], + "end_user": None, + "requester_ip_address": None, + "messages": None, + "response": None, + "error_str": None, + "error_information": None, + "model_parameters": {}, + "hidden_params": {}, + "guardrail_information": None, + "standard_built_in_tools_params": None, + } + return MockResponse( + status_code=200, + json_data=mock_payload, url=url, elapsed_seconds=_MOCK_LATENCY_SECONDS ) - # For non-GCS calls, use original method if _original_async_handler_get is not None: return await _original_async_handler_get(self, url=url, params=params, headers=headers, follow_redirects=follow_redirects) - # Fallback: if original not set, raise error raise RuntimeError("Original AsyncHTTPHandler.get not available") @@ -115,18 +98,16 @@ async def _mock_async_handler_delete(self, url, data=None, json=None, params=Non # Only mock GCS API calls if isinstance(url, str) and "storage.googleapis.com" in url: verbose_logger.info(f"[GCS MOCK] DELETE to {url}") - # Simulate network latency await asyncio.sleep(_MOCK_LATENCY_SECONDS) - return MockGCSResponse( - status_code=204, - json_data={}, + # DELETE returns 204 No Content with empty body (not JSON) + return MockResponse( + status_code=204, + json_data=None, # Empty body for DELETE url=url, elapsed_seconds=_MOCK_LATENCY_SECONDS ) - # For non-GCS calls, use original method if _original_async_handler_delete is not None: return await _original_async_handler_delete(self, url=url, data=data, json=json, params=params, headers=headers, timeout=timeout, stream=stream, content=content) - # Fallback: if original not set, raise error raise RuntimeError("Original AsyncHTTPHandler.delete not available") @@ -139,30 +120,26 @@ def create_mock_gcs_client(): This function is idempotent - it only initializes mocks once, even if called multiple times. """ - global _original_async_handler_post, _original_async_handler_get, _original_async_handler_delete - global _mocks_initialized + global _original_async_handler_get, _original_async_handler_delete, _mocks_initialized - # If already initialized, skip + # Use factory for POST handler + _create_mock_gcs_post() + + # If already initialized, skip GET/DELETE patching if _mocks_initialized: return - verbose_logger.debug("[GCS MOCK] Initializing GCS mock client...") + verbose_logger.debug("[GCS MOCK] Initializing GCS GET/DELETE handlers...") - # Patch AsyncHTTPHandler methods (used by LiteLLM's custom httpx handler) - if _original_async_handler_post is None: - from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler - _original_async_handler_post = AsyncHTTPHandler.post - AsyncHTTPHandler.post = _mock_async_handler_post # type: ignore - verbose_logger.debug("[GCS MOCK] Patched AsyncHTTPHandler.post") + # Patch GET and DELETE handlers (GCS-specific) + from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler if _original_async_handler_get is None: - from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler _original_async_handler_get = AsyncHTTPHandler.get AsyncHTTPHandler.get = _mock_async_handler_get # type: ignore verbose_logger.debug("[GCS MOCK] Patched AsyncHTTPHandler.get") if _original_async_handler_delete is None: - from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler _original_async_handler_delete = AsyncHTTPHandler.delete AsyncHTTPHandler.delete = _mock_async_handler_delete # type: ignore verbose_logger.debug("[GCS MOCK] Patched AsyncHTTPHandler.delete") @@ -212,25 +189,4 @@ def mock_vertex_auth_methods(): verbose_logger.debug("[GCS MOCK] Patched Vertex AI auth methods") -def should_use_gcs_mock() -> bool: - """ - Determine if GCS should run in mock mode. - - Checks the GCS_MOCK environment variable. - - Returns: - bool: True if mock mode should be enabled - """ - import os - from litellm.secret_managers.main import str_to_bool - - mock_mode = os.getenv("GCS_MOCK", "false") - result = str_to_bool(mock_mode) - - # Ensure we return a bool, not None - result = bool(result) if result is not None else False - - if result: - verbose_logger.info("GCS Mock Mode: ENABLED - API calls will be mocked") - - return result +# should_use_gcs_mock is already created by the factory diff --git a/litellm/integrations/helicone.py b/litellm/integrations/helicone.py index 198cbaf4058..b996813b4e7 100644 --- a/litellm/integrations/helicone.py +++ b/litellm/integrations/helicone.py @@ -4,6 +4,11 @@ import os import traceback import litellm +from litellm._logging import verbose_logger +from litellm.integrations.helicone_mock_client import ( + should_use_helicone_mock, + create_mock_helicone_client, +) class HeliconeLogger: @@ -22,6 +27,11 @@ class HeliconeLogger: def __init__(self): # Instance variables + self.is_mock_mode = should_use_helicone_mock() + if self.is_mock_mode: + create_mock_helicone_client() + verbose_logger.info("[HELICONE MOCK] Helicone logger initialized in mock mode") + self.provider_url = "https://api.openai.com/v1" self.key = os.getenv("HELICONE_API_KEY") self.api_base = os.getenv("HELICONE_API_BASE") or "https://api.hconeai.com" @@ -185,7 +195,10 @@ class HeliconeLogger: } response = litellm.module_level_client.post(url, headers=headers, json=data) if response.status_code == 200: - print_verbose("Helicone Logging - Success!") + if self.is_mock_mode: + print_verbose("[HELICONE MOCK] Helicone Logging - Successfully mocked!") + else: + print_verbose("Helicone Logging - Success!") else: print_verbose( f"Helicone Logging - Error Request was not successful. Status Code: {response.status_code}" diff --git a/litellm/integrations/helicone_mock_client.py b/litellm/integrations/helicone_mock_client.py new file mode 100644 index 00000000000..0f4670a1d2c --- /dev/null +++ b/litellm/integrations/helicone_mock_client.py @@ -0,0 +1,32 @@ +""" +Mock HTTP client for Helicone integration testing. + +This module intercepts Helicone API calls and returns successful mock responses, +allowing full code execution without making actual network calls. + +Usage: + Set HELICONE_MOCK=true in environment variables or config to enable mock mode. +""" + +from litellm.integrations.mock_client_factory import MockClientConfig, create_mock_client_factory + +# Create mock client using factory +# Helicone uses HTTPHandler which internally uses httpx.Client.send(), not httpx.Client.post() +_config = MockClientConfig( + name="HELICONE", + env_var="HELICONE_MOCK", + default_latency_ms=100, + default_status_code=200, + default_json_data={"status": "success"}, + url_matchers=[ + ".hconeai.com", + "hconeai.com", + ".helicone.ai", + "helicone.ai", + ], + patch_async_handler=False, + patch_sync_client=False, # HTTPHandler uses self.client.send(), not self.client.post() + patch_http_handler=True, # Patch HTTPHandler.post directly +) + +create_mock_helicone_client, should_use_helicone_mock = create_mock_client_factory(_config) diff --git a/litellm/integrations/langfuse/langfuse.py b/litellm/integrations/langfuse/langfuse.py index 46ada3c3930..7bf97665fd2 100644 --- a/litellm/integrations/langfuse/langfuse.py +++ b/litellm/integrations/langfuse/langfuse.py @@ -23,6 +23,7 @@ from litellm.constants import MAX_LANGFUSE_INITIALIZED_CLIENTS from litellm.litellm_core_utils.core_helpers import ( safe_deep_copy, reconstruct_model_name, + filter_exceptions_from_params, ) from litellm.litellm_core_utils.redact_messages import redact_user_api_key_info from litellm.integrations.langfuse.langfuse_mock_client import ( @@ -75,9 +76,8 @@ def _extract_cache_read_input_tokens(usage_obj) -> int: # Check prompt_tokens_details.cached_tokens (used by Gemini and other providers) if hasattr(usage_obj, "prompt_tokens_details"): prompt_tokens_details = getattr(usage_obj, "prompt_tokens_details", None) - if ( - prompt_tokens_details is not None - and hasattr(prompt_tokens_details, "cached_tokens") + if prompt_tokens_details is not None and hasattr( + prompt_tokens_details, "cached_tokens" ): cached_tokens = getattr(prompt_tokens_details, "cached_tokens", None) if ( @@ -540,7 +540,6 @@ class LangFuseLogger: verbose_logger.debug("Langfuse Layer Logging - logging to langfuse v2") try: - metadata = metadata or {} standard_logging_object: Optional[StandardLoggingPayload] = cast( Optional[StandardLoggingPayload], kwargs.get("standard_logging_object", None), @@ -706,9 +705,10 @@ class LangFuseLogger: clean_metadata["litellm_response_cost"] = cost if standard_logging_object is not None: - clean_metadata["hidden_params"] = standard_logging_object[ - "hidden_params" - ] + hidden_params = standard_logging_object.get("hidden_params", {}) + clean_metadata["hidden_params"] = filter_exceptions_from_params( + hidden_params + ) if ( litellm.langfuse_default_tags is not None diff --git a/litellm/integrations/langfuse/langfuse_mock_client.py b/litellm/integrations/langfuse/langfuse_mock_client.py index 1dc739ea328..8ed6cff8d47 100644 --- a/litellm/integrations/langfuse/langfuse_mock_client.py +++ b/litellm/integrations/langfuse/langfuse_mock_client.py @@ -9,113 +9,27 @@ Usage: """ import httpx -import json -from datetime import timedelta -from typing import Dict, Optional +from litellm.integrations.mock_client_factory import MockClientConfig, create_mock_client_factory -from litellm._logging import verbose_logger - -_original_httpx_post = None - -# Default mock latency in seconds (simulates network round-trip) -# Typical Langfuse API calls take 50-150ms -_MOCK_LATENCY_SECONDS = float(__import__("os").getenv("LANGFUSE_MOCK_LATENCY_MS", "100")) / 1000.0 - - -class MockLangfuseResponse: - """Mock httpx.Response that satisfies Langfuse SDK requirements.""" - - def __init__(self, status_code: int = 200, json_data: Optional[Dict] = None, url: Optional[str] = None, elapsed_seconds: float = 0.0): - self.status_code = status_code - self._json_data = json_data or {"status": "success"} - self.headers = httpx.Headers({}) - self.is_success = status_code < 400 - self.is_error = status_code >= 400 - self.is_redirect = 300 <= status_code < 400 - self.url = httpx.URL(url) if url else httpx.URL("") - # Set realistic elapsed time based on mock latency - elapsed_time = elapsed_seconds if elapsed_seconds > 0 else _MOCK_LATENCY_SECONDS - self.elapsed = timedelta(seconds=elapsed_time) - self._text = json.dumps(self._json_data) - self._content = self._text.encode("utf-8") - - @property - def text(self) -> str: - return self._text - - @property - def content(self) -> bytes: - return self._content - - def json(self) -> Dict: - return self._json_data - - def read(self) -> bytes: - return self._content - - def raise_for_status(self): - if self.status_code >= 400: - raise Exception(f"HTTP {self.status_code}") - - -def _is_langfuse_url(url) -> bool: - """Check if URL is a Langfuse domain.""" - try: - parsed_url = httpx.URL(url) if isinstance(url, str) else url - hostname = parsed_url.host or "" - - return ( - hostname.endswith(".langfuse.com") or - hostname == "langfuse.com" or - (hostname in ("localhost", "127.0.0.1") and "langfuse" in str(parsed_url).lower()) - ) - except Exception: - return False - - -def _mock_httpx_post(self, url, **kwargs): - """Monkey-patched httpx.Client.post that intercepts Langfuse calls.""" - if _is_langfuse_url(url): - verbose_logger.info(f"[LANGFUSE MOCK] POST to {url}") - return MockLangfuseResponse(status_code=200, json_data={"status": "success"}, url=url, elapsed_seconds=_MOCK_LATENCY_SECONDS) - - if _original_httpx_post is not None: - return _original_httpx_post(self, url, **kwargs) +# Create mock client using factory +_config = MockClientConfig( + name="LANGFUSE", + env_var="LANGFUSE_MOCK", + default_latency_ms=100, + default_status_code=200, + default_json_data={"status": "success"}, + url_matchers=[ + ".langfuse.com", + "langfuse.com", + ], + patch_async_handler=False, + patch_sync_client=True, +) +_create_mock_langfuse_client_internal, should_use_langfuse_mock = create_mock_client_factory(_config) +# Langfuse needs to return an httpx.Client instance def create_mock_langfuse_client(): - """ - Monkey-patch httpx.Client.post to intercept Langfuse calls. - - Returns a real httpx.Client instance - the monkey-patch intercepts all calls. - """ - global _original_httpx_post - - if _original_httpx_post is None: - _original_httpx_post = httpx.Client.post - httpx.Client.post = _mock_httpx_post # type: ignore - verbose_logger.debug("[LANGFUSE MOCK] Patched httpx.Client.post") - + """Create and return an httpx.Client instance - the monkey-patch intercepts all calls.""" + _create_mock_langfuse_client_internal() return httpx.Client() - - -def should_use_langfuse_mock() -> bool: - """ - Determine if Langfuse should run in mock mode. - - Checks the LANGFUSE_MOCK environment variable. - - Returns: - bool: True if mock mode should be enabled - """ - import os - from litellm.secret_managers.main import str_to_bool - - mock_mode = os.getenv("LANGFUSE_MOCK", "false") - result = str_to_bool(mock_mode) - result = bool(result) if result is not None else False - - if result: - verbose_logger.info("Langfuse Mock Mode: ENABLED - API calls will be mocked") - - return result diff --git a/litellm/integrations/langfuse/langfuse_prompt_management.py b/litellm/integrations/langfuse/langfuse_prompt_management.py index 8f73eabad44..3986fc6a6ef 100644 --- a/litellm/integrations/langfuse/langfuse_prompt_management.py +++ b/litellm/integrations/langfuse/langfuse_prompt_management.py @@ -300,43 +300,59 @@ class LangfusePromptManagement(LangFuseLogger, PromptManagementBase, CustomLogge ) async def async_log_success_event(self, kwargs, response_obj, start_time, end_time): - standard_callback_dynamic_params = kwargs.get( - "standard_callback_dynamic_params" - ) - langfuse_logger_to_use = LangFuseHandler.get_langfuse_logger_for_request( - globalLangfuseLogger=self, - standard_callback_dynamic_params=standard_callback_dynamic_params, - in_memory_dynamic_logger_cache=in_memory_dynamic_logger_cache, - ) - langfuse_logger_to_use.log_event_on_langfuse( - kwargs=kwargs, - response_obj=response_obj, - start_time=start_time, - end_time=end_time, - user_id=kwargs.get("user", None), - ) + try: + standard_callback_dynamic_params = kwargs.get( + "standard_callback_dynamic_params" + ) + langfuse_logger_to_use = LangFuseHandler.get_langfuse_logger_for_request( + globalLangfuseLogger=self, + standard_callback_dynamic_params=standard_callback_dynamic_params, + in_memory_dynamic_logger_cache=in_memory_dynamic_logger_cache, + ) + langfuse_logger_to_use.log_event_on_langfuse( + kwargs=kwargs, + response_obj=response_obj, + start_time=start_time, + end_time=end_time, + user_id=kwargs.get("user", None), + ) + except Exception as e: + from litellm._logging import verbose_logger + + verbose_logger.exception( + f"Langfuse Layer Error - Exception occurred while logging success event: {str(e)}" + ) + self.handle_callback_failure(callback_name="langfuse") async def async_log_failure_event(self, kwargs, response_obj, start_time, end_time): - standard_callback_dynamic_params = kwargs.get( - "standard_callback_dynamic_params" - ) - langfuse_logger_to_use = LangFuseHandler.get_langfuse_logger_for_request( - globalLangfuseLogger=self, - standard_callback_dynamic_params=standard_callback_dynamic_params, - in_memory_dynamic_logger_cache=in_memory_dynamic_logger_cache, - ) - standard_logging_object = cast( - Optional[StandardLoggingPayload], - kwargs.get("standard_logging_object", None), - ) - if standard_logging_object is None: - return - langfuse_logger_to_use.log_event_on_langfuse( - start_time=start_time, - end_time=end_time, - response_obj=None, - user_id=kwargs.get("user", None), - status_message=standard_logging_object["error_str"], - level="ERROR", - kwargs=kwargs, - ) + try: + standard_callback_dynamic_params = kwargs.get( + "standard_callback_dynamic_params" + ) + langfuse_logger_to_use = LangFuseHandler.get_langfuse_logger_for_request( + globalLangfuseLogger=self, + standard_callback_dynamic_params=standard_callback_dynamic_params, + in_memory_dynamic_logger_cache=in_memory_dynamic_logger_cache, + ) + standard_logging_object = cast( + Optional[StandardLoggingPayload], + kwargs.get("standard_logging_object", None), + ) + if standard_logging_object is None: + return + langfuse_logger_to_use.log_event_on_langfuse( + start_time=start_time, + end_time=end_time, + response_obj=None, + user_id=kwargs.get("user", None), + status_message=standard_logging_object["error_str"], + level="ERROR", + kwargs=kwargs, + ) + except Exception as e: + from litellm._logging import verbose_logger + + verbose_logger.exception( + f"Langfuse Layer Error - Exception occurred while logging failure event: {str(e)}" + ) + self.handle_callback_failure(callback_name="langfuse") diff --git a/litellm/integrations/langsmith.py b/litellm/integrations/langsmith.py index 5893f14105d..ebd005f8804 100644 --- a/litellm/integrations/langsmith.py +++ b/litellm/integrations/langsmith.py @@ -15,6 +15,10 @@ from pydantic import BaseModel # type: ignore import litellm from litellm._logging import verbose_logger from litellm.integrations.custom_batch_logger import CustomBatchLogger +from litellm.integrations.langsmith_mock_client import ( + should_use_langsmith_mock, + create_mock_langsmith_client, +) from litellm.llms.custom_httpx.http_handler import ( get_async_httpx_client, httpxSpecialProvider, @@ -45,6 +49,12 @@ class LangsmithLogger(CustomBatchLogger): ): self.flush_lock = asyncio.Lock() super().__init__(**kwargs, flush_lock=self.flush_lock) + self.is_mock_mode = should_use_langsmith_mock() + + if self.is_mock_mode: + create_mock_langsmith_client() + verbose_logger.debug("[LANGSMITH MOCK] LangSmith logger initialized in mock mode") + self.default_credentials = self.get_credentials_from_env( langsmith_api_key=langsmith_api_key, langsmith_project=langsmith_project, @@ -388,6 +398,8 @@ class LangsmithLogger(CustomBatchLogger): verbose_logger.debug( "Sending batch of %s runs to Langsmith", len(elements_to_log) ) + if self.is_mock_mode: + verbose_logger.debug("[LANGSMITH MOCK] Mock mode enabled - API calls will be intercepted") response = await self.async_httpx_client.post( url=url, json={"post": elements_to_log}, @@ -400,9 +412,14 @@ class LangsmithLogger(CustomBatchLogger): f"Langsmith Error: {response.status_code} - {response.text}" ) else: - verbose_logger.debug( - f"Batch of {len(self.log_queue)} runs successfully created" - ) + if self.is_mock_mode: + verbose_logger.debug( + f"[LANGSMITH MOCK] Batch of {len(elements_to_log)} runs successfully mocked" + ) + else: + verbose_logger.debug( + f"Batch of {len(self.log_queue)} runs successfully created" + ) except httpx.HTTPStatusError as e: verbose_logger.exception( f"Langsmith HTTP Error: {e.response.status_code} - {e.response.text}" diff --git a/litellm/integrations/langsmith_mock_client.py b/litellm/integrations/langsmith_mock_client.py new file mode 100644 index 00000000000..ef602908231 --- /dev/null +++ b/litellm/integrations/langsmith_mock_client.py @@ -0,0 +1,29 @@ +""" +Mock client for LangSmith integration testing. + +This module intercepts LangSmith API calls and returns successful mock responses, +allowing full code execution without making actual network calls. + +Usage: + Set LANGSMITH_MOCK=true in environment variables or config to enable mock mode. +""" + +from litellm.integrations.mock_client_factory import MockClientConfig, create_mock_client_factory + +# Create mock client using factory +_config = MockClientConfig( + name="LANGSMITH", + env_var="LANGSMITH_MOCK", + default_latency_ms=100, + default_status_code=200, + default_json_data={"status": "success", "ids": ["mock-run-id"]}, + url_matchers=[ + ".smith.langchain.com", + "api.smith.langchain.com", + "smith.langchain.com", + ], + patch_async_handler=True, + patch_sync_client=False, +) + +create_mock_langsmith_client, should_use_langsmith_mock = create_mock_client_factory(_config) diff --git a/litellm/integrations/mock_client_factory.py b/litellm/integrations/mock_client_factory.py new file mode 100644 index 00000000000..2f04fae9f76 --- /dev/null +++ b/litellm/integrations/mock_client_factory.py @@ -0,0 +1,216 @@ +""" +Factory for creating mock HTTP clients for integration testing. + +This module provides a simple factory pattern to create mock clients that intercept +API calls and return successful mock responses, allowing full code execution without +making actual network calls. +""" + +import httpx +import json +import asyncio +from datetime import timedelta +from typing import Dict, Optional, List, cast +from dataclasses import dataclass + +from litellm._logging import verbose_logger + + +@dataclass +class MockClientConfig: + """Configuration for creating a mock client.""" + name: str # e.g., "GCS", "LANGFUSE", "LANGSMITH", "DATADOG" + env_var: str # e.g., "GCS_MOCK", "LANGFUSE_MOCK" + default_latency_ms: int = 100 # Default mock latency in milliseconds + default_status_code: int = 200 # Default HTTP status code + default_json_data: Optional[Dict] = None # Default JSON response data + url_matchers: Optional[List[str]] = None # List of strings to match in URLs (e.g., ["storage.googleapis.com"]) + patch_async_handler: bool = True # Whether to patch AsyncHTTPHandler.post + patch_sync_client: bool = False # Whether to patch httpx.Client.post + patch_http_handler: bool = False # Whether to patch HTTPHandler.post (for sync calls that use HTTPHandler) + + def __post_init__(self): + """Ensure url_matchers is a list.""" + if self.url_matchers is None: + self.url_matchers = [] + + +class MockResponse: + """Generic mock httpx.Response that satisfies API requirements.""" + + def __init__(self, status_code: int = 200, json_data: Optional[Dict] = None, url: Optional[str] = None, elapsed_seconds: float = 0.0): + self.status_code = status_code + self._json_data = json_data or {"status": "success"} + self.headers = httpx.Headers({}) + self.is_success = status_code < 400 + self.is_error = status_code >= 400 + self.is_redirect = 300 <= status_code < 400 + self.url = httpx.URL(url) if url else httpx.URL("") + self.elapsed = timedelta(seconds=elapsed_seconds) + self._text = json.dumps(self._json_data) if json_data else "" + self._content = self._text.encode("utf-8") + + @property + def text(self) -> str: + """Return response text.""" + return self._text + + @property + def content(self) -> bytes: + """Return response content.""" + return self._content + + def json(self) -> Dict: + """Return JSON response data.""" + return self._json_data + + def read(self) -> bytes: + """Read response content.""" + return self._content + + def raise_for_status(self): + """Raise exception for error status codes.""" + if self.status_code >= 400: + raise Exception(f"HTTP {self.status_code}") + + +def _is_url_match(url, matchers: List[str]) -> bool: + """Check if URL matches any of the provided matchers.""" + try: + parsed_url = httpx.URL(url) if isinstance(url, str) else url + url_str = str(parsed_url).lower() + hostname = parsed_url.host or "" + + for matcher in matchers: + if matcher.lower() in url_str or matcher.lower() in hostname.lower(): + return True + + # Also check for localhost with matcher in path + if hostname in ("localhost", "127.0.0.1"): + for matcher in matchers: + if matcher.lower() in url_str: + return True + + return False + except Exception: + return False + + +def create_mock_client_factory(config: MockClientConfig): # noqa: PLR0915 + """ + Factory function that creates mock client functions based on configuration. + + Returns: + tuple: (create_mock_client_func, should_use_mock_func) + """ + # Store original methods for restoration + _original_async_handler_post = None + _original_sync_client_post = None + _original_http_handler_post = None + _mocks_initialized = False + + # Calculate mock latency + import os + latency_env = f"{config.name.upper()}_MOCK_LATENCY_MS" + _MOCK_LATENCY_SECONDS = float(os.getenv(latency_env, str(config.default_latency_ms))) / 1000.0 + + # Create URL matcher function + def _is_mock_url(url) -> bool: + # url_matchers is guaranteed to be a list after __post_init__ + return _is_url_match(url, cast(List[str], config.url_matchers)) + + # Create async handler mock + async def _mock_async_handler_post(self, url, data=None, json=None, params=None, headers=None, timeout=None, stream=False, logging_obj=None, files=None, content=None): + """Monkey-patched AsyncHTTPHandler.post that intercepts API calls.""" + if isinstance(url, str) and _is_mock_url(url): + verbose_logger.info(f"[{config.name} MOCK] POST to {url}") + await asyncio.sleep(_MOCK_LATENCY_SECONDS) + return MockResponse( + status_code=config.default_status_code, + json_data=config.default_json_data, + url=url, + elapsed_seconds=_MOCK_LATENCY_SECONDS + ) + if _original_async_handler_post is not None: + return await _original_async_handler_post(self, url=url, data=data, json=json, params=params, headers=headers, timeout=timeout, stream=stream, logging_obj=logging_obj, files=files, content=content) + raise RuntimeError("Original AsyncHTTPHandler.post not available") + + # Create sync client mock + def _mock_sync_client_post(self, url, **kwargs): + """Monkey-patched httpx.Client.post that intercepts API calls.""" + if _is_mock_url(url): + verbose_logger.info(f"[{config.name} MOCK] POST to {url} (sync)") + return MockResponse( + status_code=config.default_status_code, + json_data=config.default_json_data, + url=url, + elapsed_seconds=_MOCK_LATENCY_SECONDS + ) + if _original_sync_client_post is not None: + return _original_sync_client_post(self, url, **kwargs) + + # Create HTTPHandler mock (for sync calls that use HTTPHandler.post) + def _mock_http_handler_post(self, url, data=None, json=None, params=None, headers=None, timeout=None, stream=False, files=None, content=None, logging_obj=None): + """Monkey-patched HTTPHandler.post that intercepts API calls.""" + if isinstance(url, str) and _is_mock_url(url): + verbose_logger.info(f"[{config.name} MOCK] POST to {url}") + import time + time.sleep(_MOCK_LATENCY_SECONDS) + return MockResponse( + status_code=config.default_status_code, + json_data=config.default_json_data, + url=url, + elapsed_seconds=_MOCK_LATENCY_SECONDS + ) + if _original_http_handler_post is not None: + return _original_http_handler_post(self, url=url, data=data, json=json, params=params, headers=headers, timeout=timeout, stream=stream, files=files, content=content, logging_obj=logging_obj) + raise RuntimeError("Original HTTPHandler.post not available") + + # Create mock client initialization function + def create_mock_client(): + """Initialize the mock client by patching HTTP handlers.""" + nonlocal _original_async_handler_post, _original_sync_client_post, _original_http_handler_post, _mocks_initialized + + if _mocks_initialized: + return + + verbose_logger.debug(f"[{config.name} MOCK] Initializing {config.name} mock client...") + + if config.patch_async_handler and _original_async_handler_post is None: + from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler + _original_async_handler_post = AsyncHTTPHandler.post + AsyncHTTPHandler.post = _mock_async_handler_post # type: ignore + verbose_logger.debug(f"[{config.name} MOCK] Patched AsyncHTTPHandler.post") + + if config.patch_sync_client and _original_sync_client_post is None: + _original_sync_client_post = httpx.Client.post + httpx.Client.post = _mock_sync_client_post # type: ignore + verbose_logger.debug(f"[{config.name} MOCK] Patched httpx.Client.post") + + if config.patch_http_handler and _original_http_handler_post is None: + from litellm.llms.custom_httpx.http_handler import HTTPHandler + _original_http_handler_post = HTTPHandler.post + HTTPHandler.post = _mock_http_handler_post # type: ignore + verbose_logger.debug(f"[{config.name} MOCK] Patched HTTPHandler.post") + + verbose_logger.debug(f"[{config.name} MOCK] Mock latency set to {_MOCK_LATENCY_SECONDS*1000:.0f}ms") + verbose_logger.debug(f"[{config.name} MOCK] {config.name} mock client initialization complete") + + _mocks_initialized = True + + # Create should_use_mock function + def should_use_mock() -> bool: + """Determine if mock mode should be enabled.""" + import os + from litellm.secret_managers.main import str_to_bool + + mock_mode = os.getenv(config.env_var, "false") + result = str_to_bool(mock_mode) + result = bool(result) if result is not None else False + + if result: + verbose_logger.info(f"{config.name} Mock Mode: ENABLED - API calls will be mocked") + + return result + + return create_mock_client, should_use_mock diff --git a/litellm/integrations/opentelemetry.py b/litellm/integrations/opentelemetry.py index a8a7fa77b3d..cb7d212d6b6 100644 --- a/litellm/integrations/opentelemetry.py +++ b/litellm/integrations/opentelemetry.py @@ -144,6 +144,7 @@ class OpenTelemetry(CustomLogger): self.OTEL_EXPORTER = self.config.exporter self.OTEL_ENDPOINT = self.config.endpoint self.OTEL_HEADERS = self.config.headers + self._tracer_provider_cache: Dict[str, Any] = {} self._init_tracing(tracer_provider) _debug_otel = str(os.getenv("DEBUG_OTEL", "False")).lower() @@ -598,9 +599,9 @@ class OpenTelemetry(CustomLogger): def _get_dynamic_otel_headers_from_kwargs(self, kwargs) -> Optional[dict]: """Extract dynamic headers from kwargs if available.""" - standard_callback_dynamic_params: Optional[StandardCallbackDynamicParams] = ( - kwargs.get("standard_callback_dynamic_params") - ) + standard_callback_dynamic_params: Optional[ + StandardCallbackDynamicParams + ] = kwargs.get("standard_callback_dynamic_params") if not standard_callback_dynamic_params: return None @@ -615,12 +616,22 @@ class OpenTelemetry(CustomLogger): """Create a temporary tracer with dynamic headers for this request only.""" from opentelemetry.sdk.trace import TracerProvider + # Prevents thread exhaustion by reusing providers for the same credential sets (e.g. per-team keys) + cache_key = str(sorted(dynamic_headers.items())) + if cache_key in self._tracer_provider_cache: + return self._tracer_provider_cache[cache_key].get_tracer( + LITELLM_TRACER_NAME + ) + # Create a temporary tracer provider with dynamic headers temp_provider = TracerProvider(resource=self._get_litellm_resource(self.config)) temp_provider.add_span_processor( self._get_span_processor(dynamic_headers=dynamic_headers) ) + # Store in cache for reuse + self._tracer_provider_cache[cache_key] = temp_provider + return temp_provider.get_tracer(LITELLM_TRACER_NAME) def construct_dynamic_otel_headers( @@ -665,7 +676,10 @@ class OpenTelemetry(CustomLogger): kwargs, response_obj, start_time, end_time, span ) # Ensure proxy-request parent span is annotated with the actual operation kind - if parent_span is not None and parent_span.name == LITELLM_PROXY_REQUEST_SPAN_NAME: + if ( + parent_span is not None + and parent_span.name == LITELLM_PROXY_REQUEST_SPAN_NAME + ): self.set_attributes(parent_span, kwargs, response_obj) else: # Do not create primary span (keep hierarchy shallow when parent exists) @@ -994,10 +1008,15 @@ class OpenTelemetry(CustomLogger): # TODO: Refactor to use the proper OTEL Logs API instead of directly creating SDK LogRecords from opentelemetry._logs import SeverityNumber, get_logger, get_logger_provider + try: - from opentelemetry.sdk._logs import LogRecord as SdkLogRecord # type: ignore[attr-defined] # OTEL < 1.39.0 + from opentelemetry.sdk._logs import ( + LogRecord as SdkLogRecord, # type: ignore[attr-defined] # OTEL < 1.39.0 + ) except ImportError: - from opentelemetry.sdk._logs._internal import LogRecord as SdkLogRecord # OTEL >= 1.39.0 + from opentelemetry.sdk._logs._internal import ( + LogRecord as SdkLogRecord, # OTEL >= 1.39.0 + ) otel_logger = get_logger(LITELLM_LOGGER_NAME) @@ -1605,9 +1624,8 @@ class OpenTelemetry(CustomLogger): for idx, choice in enumerate(response_obj.get("choices")): if choice.get("finish_reason"): - - message = choice.get("message") - tool_calls = message.get("tool_calls") + message = choice.get("message") or {} + tool_calls = message.get("tool_calls") or [] if tool_calls: kv_pairs = OpenTelemetry._tool_calls_kv_pair(tool_calls) # type: ignore for key, value in kv_pairs.items(): @@ -1618,6 +1636,9 @@ class OpenTelemetry(CustomLogger): ) except Exception as e: + self.handle_callback_failure( + callback_name=self.callback_name or "opentelemetry" + ) verbose_logger.exception( "OpenTelemetry logging error in set_attributes %s", str(e) ) diff --git a/litellm/integrations/posthog.py b/litellm/integrations/posthog.py index 468b1a441fb..dd7c3627b87 100644 --- a/litellm/integrations/posthog.py +++ b/litellm/integrations/posthog.py @@ -17,6 +17,10 @@ from typing import Any, Dict, Optional, Tuple from litellm._logging import verbose_logger from litellm._uuid import uuid from litellm.integrations.custom_batch_logger import CustomBatchLogger +from litellm.integrations.posthog_mock_client import ( + should_use_posthog_mock, + create_mock_posthog_client, +) from litellm.llms.custom_httpx.http_handler import ( _get_httpx_client, get_async_httpx_client, @@ -40,6 +44,12 @@ class PostHogLogger(CustomBatchLogger): """ try: verbose_logger.debug("PostHog: in init posthog logger") + + self.is_mock_mode = should_use_posthog_mock() + if self.is_mock_mode: + create_mock_posthog_client() + verbose_logger.debug("[POSTHOG MOCK] PostHog logger initialized in mock mode") + if os.getenv("POSTHOG_API_KEY", None) is None: raise Exception("POSTHOG_API_KEY is not set, set 'POSTHOG_API_KEY=<>'") @@ -100,7 +110,10 @@ class PostHogLogger(CustomBatchLogger): f"Response from PostHog API status_code: {response.status_code}, text: {response.text}" ) - verbose_logger.debug("PostHog: Sync event successfully sent") + if self.is_mock_mode: + verbose_logger.debug("[POSTHOG MOCK] Sync event successfully mocked") + else: + verbose_logger.debug("PostHog: Sync event successfully sent") except Exception as e: verbose_logger.exception(f"PostHog Sync Layer Error - {str(e)}") @@ -320,6 +333,9 @@ class PostHogLogger(CustomBatchLogger): verbose_logger.debug( f"PostHog: Sending batch of {len(self.log_queue)} events" ) + + if self.is_mock_mode: + verbose_logger.debug("[POSTHOG MOCK] Mock mode enabled - API calls will be intercepted") # Group events by credentials for batch sending batches_by_credentials: Dict[tuple[str, str], list] = {} @@ -350,9 +366,12 @@ class PostHogLogger(CustomBatchLogger): f"Response from PostHog API status_code: {response.status_code}, text: {response.text}" ) - verbose_logger.debug( - f"PostHog: Batch of {len(self.log_queue)} events successfully sent" - ) + if self.is_mock_mode: + verbose_logger.debug(f"[POSTHOG MOCK] Batch of {len(self.log_queue)} events successfully mocked") + else: + verbose_logger.debug( + f"PostHog: Batch of {len(self.log_queue)} events successfully sent" + ) except Exception as e: verbose_logger.exception(f"PostHog Error sending batch API - {str(e)}") @@ -429,9 +448,14 @@ class PostHogLogger(CustomBatchLogger): f"PostHog: Failed to flush on exit - status {response.status_code}" ) - verbose_logger.debug( - f"PostHog: Successfully flushed {len(self.log_queue)} events on exit" - ) + if self.is_mock_mode: + verbose_logger.debug( + f"[POSTHOG MOCK] Successfully flushed {len(self.log_queue)} events on exit" + ) + else: + verbose_logger.debug( + f"PostHog: Successfully flushed {len(self.log_queue)} events on exit" + ) self.log_queue.clear() except Exception as e: diff --git a/litellm/integrations/posthog_mock_client.py b/litellm/integrations/posthog_mock_client.py new file mode 100644 index 00000000000..b713587ed6f --- /dev/null +++ b/litellm/integrations/posthog_mock_client.py @@ -0,0 +1,30 @@ +""" +Mock httpx client for PostHog integration testing. + +This module intercepts PostHog API calls and returns successful mock responses, +allowing full code execution without making actual network calls. + +Usage: + Set POSTHOG_MOCK=true in environment variables or config to enable mock mode. +""" + +from litellm.integrations.mock_client_factory import MockClientConfig, create_mock_client_factory + +# Create mock client using factory +_config = MockClientConfig( + name="POSTHOG", + env_var="POSTHOG_MOCK", + default_latency_ms=100, + default_status_code=200, + default_json_data={"status": "success"}, + url_matchers=[ + ".posthog.com", + "posthog.com", + "us.i.posthog.com", + "app.posthog.com", + ], + patch_async_handler=True, + patch_sync_client=True, +) + +create_mock_posthog_client, should_use_posthog_mock = create_mock_client_factory(_config) diff --git a/litellm/integrations/prometheus.py b/litellm/integrations/prometheus.py index bafb0d88c82..2c897cb0692 100644 --- a/litellm/integrations/prometheus.py +++ b/litellm/integrations/prometheus.py @@ -229,14 +229,18 @@ class PrometheusLogger(CustomLogger): self.litellm_remaining_api_key_requests_for_model = self._gauge_factory( "litellm_remaining_api_key_requests_for_model", "Remaining Requests API Key can make for model (model based rpm limit on key)", - labelnames=["hashed_api_key", "api_key_alias", "model"], + labelnames=self.get_labels_for_metric( + "litellm_remaining_api_key_requests_for_model" + ), ) # Remaining MODEL TPM limit for API Key self.litellm_remaining_api_key_tokens_for_model = self._gauge_factory( "litellm_remaining_api_key_tokens_for_model", "Remaining Tokens API Key can make for model (model based tpm limit on key)", - labelnames=["hashed_api_key", "api_key_alias", "model"], + labelnames=self.get_labels_for_metric( + "litellm_remaining_api_key_tokens_for_model" + ), ) ######################################## @@ -312,6 +316,18 @@ class PrometheusLogger(CustomLogger): labelnames=self.get_labels_for_metric("litellm_deployment_state"), ) + self.litellm_deployment_tpm_limit = self._gauge_factory( + "litellm_deployment_tpm_limit", + "Deployment TPM limit found in config", + labelnames=self.get_labels_for_metric("litellm_deployment_tpm_limit"), + ) + + self.litellm_deployment_rpm_limit = self._gauge_factory( + "litellm_deployment_rpm_limit", + "Deployment RPM limit found in config", + labelnames=self.get_labels_for_metric("litellm_deployment_rpm_limit"), + ) + self.litellm_deployment_cooled_down = self._counter_factory( "litellm_deployment_cooled_down", "LLM Deployment Analytics - Number of times a deployment has been cooled down by LiteLLM load balancing logic. exception_status is the status of the exception that caused the deployment to be cooled down", @@ -373,15 +389,9 @@ class PrometheusLogger(CustomLogger): self.litellm_llm_api_failed_requests_metric = self._counter_factory( name="litellm_llm_api_failed_requests_metric", documentation="deprecated - use litellm_proxy_failed_requests_metric", - labelnames=[ - "end_user", - "hashed_api_key", - "api_key_alias", - "model", - "team", - "team_alias", - "user", - ], + labelnames=self.get_labels_for_metric( + "litellm_llm_api_failed_requests_metric" + ), ) self.litellm_requests_metric = self._counter_factory( @@ -891,7 +901,7 @@ class PrometheusLogger(CustomLogger): model = kwargs.get("model", "") litellm_params = kwargs.get("litellm_params", {}) or {} - _metadata = litellm_params.get("metadata", {}) + _metadata = litellm_params.get("metadata") or {} get_end_user_id_for_cost_tracking = _get_cached_end_user_id_for_cost_tracking() end_user_id = get_end_user_id_for_cost_tracking( @@ -954,6 +964,8 @@ class PrometheusLogger(CustomLogger): route=standard_logging_payload["metadata"].get( "user_api_key_request_route" ), + client_ip=standard_logging_payload["metadata"].get("requester_ip_address"), + user_agent=standard_logging_payload["metadata"].get("user_agent"), ) if ( @@ -1011,6 +1023,7 @@ class PrometheusLogger(CustomLogger): user_api_key_alias=user_api_key_alias, kwargs=kwargs, metadata=_metadata, + model_id=enum_values.model_id, ) # set latency metrics @@ -1165,26 +1178,15 @@ class PrometheusLogger(CustomLogger): response_cost: float, user_id: Optional[str] = None, ): - _team_spend = litellm_params.get("metadata", {}).get( - "user_api_key_team_spend", None - ) - _team_max_budget = litellm_params.get("metadata", {}).get( - "user_api_key_team_max_budget", None - ) + _metadata = litellm_params.get("metadata") or {} + _team_spend = _metadata.get("user_api_key_team_spend", None) + _team_max_budget = _metadata.get("user_api_key_team_max_budget", None) - _api_key_spend = litellm_params.get("metadata", {}).get( - "user_api_key_spend", None - ) - _api_key_max_budget = litellm_params.get("metadata", {}).get( - "user_api_key_max_budget", None - ) + _api_key_spend = _metadata.get("user_api_key_spend", None) + _api_key_max_budget = _metadata.get("user_api_key_max_budget", None) - _user_spend = litellm_params.get("metadata", {}).get( - "user_api_key_user_spend", None - ) - _user_max_budget = litellm_params.get("metadata", {}).get( - "user_api_key_user_max_budget", None - ) + _user_spend = _metadata.get("user_api_key_user_spend", None) + _user_max_budget = _metadata.get("user_api_key_user_max_budget", None) await self._set_api_key_budget_metrics_after_api_request( user_api_key=user_api_key, @@ -1245,6 +1247,7 @@ class PrometheusLogger(CustomLogger): user_api_key_alias: Optional[str], kwargs: dict, metadata: dict, + model_id: Optional[str] = None, ): from litellm.proxy.common_utils.callback_utils import ( get_model_group_from_litellm_kwargs, @@ -1266,11 +1269,11 @@ class PrometheusLogger(CustomLogger): ) self.litellm_remaining_api_key_requests_for_model.labels( - user_api_key, user_api_key_alias, model_group + user_api_key, user_api_key_alias, model_group, model_id ).set(remaining_requests) self.litellm_remaining_api_key_tokens_for_model.labels( - user_api_key, user_api_key_alias, model_group + user_api_key, user_api_key_alias, model_group, model_id ).set(remaining_tokens) def _set_latency_metrics( @@ -1296,12 +1299,14 @@ class PrometheusLogger(CustomLogger): time_to_first_token_seconds is not None and kwargs.get("stream", False) is True # only emit for streaming requests ): + _ttft_labels = prometheus_label_factory( + supported_enum_labels=self.get_labels_for_metric( + metric_name="litellm_llm_api_time_to_first_token_metric" + ), + enum_values=enum_values, + ) self.litellm_llm_api_time_to_first_token_metric.labels( - model, - user_api_key, - user_api_key_alias, - user_api_team, - user_api_team_alias, + **_ttft_labels ).observe(time_to_first_token_seconds) else: verbose_logger.debug( @@ -1341,7 +1346,7 @@ class PrometheusLogger(CustomLogger): # request queue time (time from arrival to processing start) _litellm_params = kwargs.get("litellm_params", {}) or {} - queue_time_seconds = _litellm_params.get("metadata", {}).get( + queue_time_seconds = (_litellm_params.get("metadata") or {}).get( "queue_time_seconds" ) if queue_time_seconds is not None and queue_time_seconds >= 0: @@ -1365,14 +1370,14 @@ class PrometheusLogger(CustomLogger): standard_logging_payload: StandardLoggingPayload = kwargs.get( "standard_logging_object", {} ) - + if self._should_skip_metrics_for_invalid_key( kwargs=kwargs, standard_logging_payload=standard_logging_payload ): return - + model = kwargs.get("model", "") - + litellm_params = kwargs.get("litellm_params", {}) or {} get_end_user_id_for_cost_tracking = _get_cached_end_user_id_for_cost_tracking() @@ -1396,6 +1401,7 @@ class PrometheusLogger(CustomLogger): user_api_team, user_api_team_alias, user_id, + standard_logging_payload.get("model_id", ""), ).inc() self.set_llm_deployment_failure_metrics(kwargs) except Exception as e: @@ -1413,49 +1419,57 @@ class PrometheusLogger(CustomLogger): ) -> Optional[int]: """ Extract HTTP status code from various input formats for validation. - + This is a centralized helper to extract status code from different callback function signatures. Handles both ProxyException (uses 'code') and standard exceptions (uses 'status_code'). - + Args: kwargs: Dictionary potentially containing 'exception' key enum_values: Object with 'status_code' attribute exception: Exception object to extract status code from directly - + Returns: Status code as integer if found, None otherwise """ status_code = None - + # Try from enum_values first (most common in our callbacks) - if enum_values and hasattr(enum_values, "status_code") and enum_values.status_code: + if ( + enum_values + and hasattr(enum_values, "status_code") + and enum_values.status_code + ): try: status_code = int(enum_values.status_code) except (ValueError, TypeError): pass - + if not status_code and exception: # ProxyException uses 'code' attribute, other exceptions may use 'status_code' - status_code = getattr(exception, "status_code", None) or getattr(exception, "code", None) + status_code = getattr(exception, "status_code", None) or getattr( + exception, "code", None + ) if status_code is not None: try: status_code = int(status_code) except (ValueError, TypeError): status_code = None - + if not status_code and kwargs: exception_in_kwargs = kwargs.get("exception") if exception_in_kwargs: - status_code = getattr(exception_in_kwargs, "status_code", None) or getattr(exception_in_kwargs, "code", None) + status_code = getattr( + exception_in_kwargs, "status_code", None + ) or getattr(exception_in_kwargs, "code", None) if status_code is not None: try: status_code = int(status_code) except (ValueError, TypeError): status_code = None - + return status_code - + def _is_invalid_api_key_request( self, status_code: Optional[int], @@ -1463,23 +1477,23 @@ class PrometheusLogger(CustomLogger): ) -> bool: """ Determine if a request has an invalid API key based on status code and exception. - + This method prevents invalid authentication attempts from being recorded in Prometheus metrics. A 401 status code is the definitive indicator of authentication failure. Additionally, we check exception messages for authentication error patterns to catch cases where the exception hasn't been converted to a ProxyException yet. - + Args: status_code: HTTP status code (401 indicates authentication error) exception: Exception object to check for auth-related error messages - + Returns: True if the request has an invalid API key and metrics should be skipped, False otherwise """ if status_code == 401: return True - + # Handle cases where AssertionError is raised before conversion to ProxyException if exception is not None: exception_str = str(exception).lower() @@ -1492,9 +1506,9 @@ class PrometheusLogger(CustomLogger): ] if any(pattern in exception_str for pattern in auth_error_patterns): return True - + return False - + def _should_skip_metrics_for_invalid_key( self, kwargs: Optional[dict] = None, @@ -1505,18 +1519,18 @@ class PrometheusLogger(CustomLogger): ) -> bool: """ Determine if Prometheus metrics should be skipped for invalid API key requests. - + This is a centralized validation method that extracts status code and exception information from various callback function signatures and determines if the request represents an invalid API key attempt that should be filtered from metrics. - + Args: kwargs: Dictionary potentially containing exception and other data user_api_key_dict: User API key authentication object (currently unused) enum_values: Object with status_code attribute standard_logging_payload: Standard logging payload dictionary exception: Exception object to check directly - + Returns: True if metrics should be skipped (invalid key detected), False otherwise """ @@ -1525,17 +1539,17 @@ class PrometheusLogger(CustomLogger): enum_values=enum_values, exception=exception, ) - + if exception is None and kwargs: exception = kwargs.get("exception") - + if self._is_invalid_api_key_request(status_code, exception=exception): verbose_logger.debug( "Skipping Prometheus metrics for invalid API key request: " f"status_code={status_code}, exception={type(exception).__name__ if exception else None}" ) return True - + return False async def async_post_call_failure_hook( @@ -1576,6 +1590,10 @@ class PrometheusLogger(CustomLogger): litellm_params=request_data, proxy_server_request=request_data.get("proxy_server_request", {}), ) + _metadata = request_data.get("metadata", {}) or {} + model_id = _metadata.get("model_info", {}).get("id") or request_data.get( + "model_info", {} + ).get("id") enum_values = UserAPIKeyLabelValues( end_user=user_api_key_dict.end_user_id, user=user_api_key_dict.user_id, @@ -1590,6 +1608,9 @@ class PrometheusLogger(CustomLogger): exception_class=self._get_exception_class_name(original_exception), tags=_tags, route=user_api_key_dict.request_route, + client_ip=_metadata.get("requester_ip_address"), + user_agent=_metadata.get("user_agent"), + model_id=model_id, ) _labels = prometheus_label_factory( supported_enum_labels=self.get_labels_for_metric( @@ -1629,6 +1650,7 @@ class PrometheusLogger(CustomLogger): ): return + _metadata = data.get("metadata", {}) or {} enum_values = UserAPIKeyLabelValues( end_user=user_api_key_dict.end_user_id, hashed_api_key=user_api_key_dict.api_key, @@ -1644,6 +1666,8 @@ class PrometheusLogger(CustomLogger): litellm_params=data, proxy_server_request=data.get("proxy_server_request", {}), ), + client_ip=_metadata.get("requester_ip_address"), + user_agent=_metadata.get("user_agent"), ) _labels = prometheus_label_factory( supported_enum_labels=self.get_labels_for_metric( @@ -1684,7 +1708,7 @@ class PrometheusLogger(CustomLogger): exception = request_kwargs.get("exception", None) llm_provider = _litellm_params.get("custom_llm_provider", None) - + if self._should_skip_metrics_for_invalid_key( kwargs=request_kwargs, standard_logging_payload=standard_logging_payload, @@ -1716,6 +1740,10 @@ class PrometheusLogger(CustomLogger): "user_api_key_team_alias" ], tags=standard_logging_payload.get("request_tags", []), + client_ip=standard_logging_payload["metadata"].get( + "requester_ip_address" + ), + user_agent=standard_logging_payload["metadata"].get("user_agent"), ) """ @@ -1753,6 +1781,49 @@ class PrometheusLogger(CustomLogger): ) ) + def _set_deployment_tpm_rpm_limit_metrics( + self, + model_info: dict, + litellm_params: dict, + litellm_model_name: Optional[str], + model_id: Optional[str], + api_base: Optional[str], + llm_provider: Optional[str], + ): + """ + Set the deployment TPM and RPM limits metrics + """ + tpm = model_info.get("tpm") or litellm_params.get("tpm") + rpm = model_info.get("rpm") or litellm_params.get("rpm") + + if tpm is not None: + _labels = prometheus_label_factory( + supported_enum_labels=self.get_labels_for_metric( + metric_name="litellm_deployment_tpm_limit" + ), + enum_values=UserAPIKeyLabelValues( + litellm_model_name=litellm_model_name, + model_id=model_id, + api_base=api_base, + api_provider=llm_provider, + ), + ) + self.litellm_deployment_tpm_limit.labels(**_labels).set(tpm) + + if rpm is not None: + _labels = prometheus_label_factory( + supported_enum_labels=self.get_labels_for_metric( + metric_name="litellm_deployment_rpm_limit" + ), + enum_values=UserAPIKeyLabelValues( + litellm_model_name=litellm_model_name, + model_id=model_id, + api_base=api_base, + api_provider=llm_provider, + ), + ) + self.litellm_deployment_rpm_limit.labels(**_labels).set(rpm) + def set_llm_deployment_success_metrics( self, request_kwargs: dict, @@ -1786,6 +1857,16 @@ class PrometheusLogger(CustomLogger): _model_info = _metadata.get("model_info") or {} model_id = _model_info.get("id", None) + if _model_info or _litellm_params: + self._set_deployment_tpm_rpm_limit_metrics( + model_info=_model_info, + litellm_params=_litellm_params, + litellm_model_name=litellm_model_name, + model_id=model_id, + api_base=api_base, + llm_provider=llm_provider, + ) + remaining_requests: Optional[int] = None remaining_tokens: Optional[int] = None if additional_headers := standard_logging_payload["hidden_params"][ @@ -2263,7 +2344,10 @@ class PrometheusLogger(CustomLogger): async def fetch_keys( page_size: int, page: int - ) -> Tuple[List[Union[str, UserAPIKeyAuth, LiteLLM_DeletedVerificationToken]], Optional[int]]: + ) -> Tuple[ + List[Union[str, UserAPIKeyAuth, LiteLLM_DeletedVerificationToken]], + Optional[int], + ]: key_list_response = await _list_key_helper( prisma_client=prisma_client, page=page, @@ -2379,12 +2463,16 @@ class PrometheusLogger(CustomLogger): # Get total user count total_users = await prisma_client.db.litellm_usertable.count() self.litellm_total_users_metric.set(total_users) - verbose_logger.debug(f"Prometheus: set litellm_total_users to {total_users}") + verbose_logger.debug( + f"Prometheus: set litellm_total_users to {total_users}" + ) # Get total team count total_teams = await prisma_client.db.litellm_teamtable.count() self.litellm_teams_count_metric.set(total_teams) - verbose_logger.debug(f"Prometheus: set litellm_teams_count to {total_teams}") + verbose_logger.debug( + f"Prometheus: set litellm_teams_count to {total_teams}" + ) except Exception as e: verbose_logger.exception( f"Error initializing user/team count metrics: {str(e)}" @@ -2412,8 +2500,8 @@ class PrometheusLogger(CustomLogger): self, user_api_team: Optional[str], user_api_team_alias: Optional[str], - team_spend: float, - team_max_budget: float, + team_spend: Optional[float], + team_max_budget: Optional[float], response_cost: float, ): """ @@ -2575,7 +2663,7 @@ class PrometheusLogger(CustomLogger): user_api_key: Optional[str], user_api_key_alias: Optional[str], response_cost: float, - key_max_budget: float, + key_max_budget: Optional[float], key_spend: Optional[float], ): if user_api_key: @@ -2592,7 +2680,7 @@ class PrometheusLogger(CustomLogger): self, user_api_key: str, user_api_key_alias: str, - key_max_budget: float, + key_max_budget: Optional[float], key_spend: Optional[float], response_cost: float, ) -> UserAPIKeyAuth: diff --git a/litellm/integrations/prometheus_services.py b/litellm/integrations/prometheus_services.py index a5f2f0b5c72..55ce758ece6 100644 --- a/litellm/integrations/prometheus_services.py +++ b/litellm/integrations/prometheus_services.py @@ -105,6 +105,11 @@ class PrometheusServicesLogger: return metrics def is_metric_registered(self, metric_name) -> bool: + # Use _names_to_collectors (O(1)) instead of REGISTRY.collect() (O(n)) to avoid + # perf regression when a new Router is created per request (e.g. router_settings in DB). + names_to_collectors = getattr(self.REGISTRY, "_names_to_collectors", None) + if names_to_collectors is not None: + return metric_name in names_to_collectors for metric in self.REGISTRY.collect(): if metric_name == metric.name: return True diff --git a/litellm/litellm_core_utils/get_litellm_params.py b/litellm/litellm_core_utils/get_litellm_params.py index e290101f8bf..060e98fd49f 100644 --- a/litellm/litellm_core_utils/get_litellm_params.py +++ b/litellm/litellm_core_utils/get_litellm_params.py @@ -93,8 +93,11 @@ def get_litellm_params( "text_completion": text_completion, "azure_ad_token_provider": azure_ad_token_provider, "user_continue_message": user_continue_message, - "base_model": base_model or ( - _get_base_model_from_litellm_call_metadata(metadata=metadata) if metadata else None + "base_model": base_model + or ( + _get_base_model_from_litellm_call_metadata(metadata=metadata) + if metadata + else None ), "litellm_trace_id": litellm_trace_id, "litellm_session_id": litellm_session_id, @@ -139,5 +142,7 @@ def get_litellm_params( "aws_sts_endpoint": kwargs.get("aws_sts_endpoint"), "aws_external_id": kwargs.get("aws_external_id"), "aws_bedrock_runtime_endpoint": kwargs.get("aws_bedrock_runtime_endpoint"), + "tpm": kwargs.get("tpm"), + "rpm": kwargs.get("rpm"), } return litellm_params diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index d3bcfe8200e..1b3a687f1f3 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -205,6 +205,11 @@ _in_memory_loggers: List[Any] = [] ### GLOBAL VARIABLES ### +# Cache custom pricing keys as frozenset for O(1) lookups instead of looping through 49 keys +_CUSTOM_PRICING_KEYS: frozenset = frozenset( + CustomPricingLiteLLMParams.model_fields.keys() +) + sentry_sdk_instance = None capture_exception = None add_breadcrumb = None @@ -330,7 +335,9 @@ class Logging(LiteLLMLoggingBaseClass): self.start_time = start_time # log the call start time self.call_type = call_type self.litellm_call_id = litellm_call_id - self.litellm_trace_id: str = litellm_trace_id if litellm_trace_id else str(uuid.uuid4()) + self.litellm_trace_id: str = ( + litellm_trace_id if litellm_trace_id else str(uuid.uuid4()) + ) self.function_id = function_id self.streaming_chunks: List[Any] = [] # for generating complete stream response self.sync_streaming_chunks: List[ @@ -539,10 +546,11 @@ class Logging(LiteLLMLoggingBaseClass): if "stream_options" in additional_params: self.stream_options = additional_params["stream_options"] ## check if custom pricing set ## - custom_pricing_keys = CustomPricingLiteLLMParams.model_fields.keys() - for key in custom_pricing_keys: - if litellm_params.get(key) is not None: - self.custom_pricing = True + if any( + litellm_params.get(key) is not None + for key in _CUSTOM_PRICING_KEYS & litellm_params.keys() + ): + self.custom_pricing = True if "custom_llm_provider" in self.model_call_details: self.custom_llm_provider = self.model_call_details["custom_llm_provider"] @@ -1289,6 +1297,7 @@ class Logging(LiteLLMLoggingBaseClass): output_cost: float, total_cost: float, cost_for_built_in_tools_cost_usd_dollar: float, + additional_costs: Optional[dict] = None, original_cost: Optional[float] = None, discount_percent: Optional[float] = None, discount_amount: Optional[float] = None, @@ -1304,6 +1313,7 @@ class Logging(LiteLLMLoggingBaseClass): output_cost: Cost of output/completion tokens cost_for_built_in_tools_cost_usd_dollar: Cost of built-in tools total_cost: Total cost of request + additional_costs: Free-form additional costs dict (e.g., {"azure_model_router_flat_cost": 0.00014}) original_cost: Cost before discount discount_percent: Discount percentage (0.05 = 5%) discount_amount: Discount amount in USD @@ -1319,6 +1329,10 @@ class Logging(LiteLLMLoggingBaseClass): tool_usage_cost=cost_for_built_in_tools_cost_usd_dollar, ) + # Store additional costs if provided (free-form dict for extensibility) + if additional_costs and isinstance(additional_costs, dict) and len(additional_costs) > 0: + self.cost_breakdown["additional_costs"] = additional_costs + # Store discount information if provided if original_cost is not None: self.cost_breakdown["original_cost"] = original_cost @@ -1630,11 +1644,19 @@ class Logging(LiteLLMLoggingBaseClass): "standard_logging_object" ) ) is not None: - standard_logging_payload["response"] = ( + response_dict = ( result.model_dump() if hasattr(result, "model_dump") else dict(result) ) + # Ensure usage is properly included with transformed chat format + if transformed_usage is not None: + response_dict["usage"] = ( + transformed_usage.model_dump() + if hasattr(transformed_usage, "model_dump") + else dict(transformed_usage) + ) + standard_logging_payload["response"] = response_dict elif isinstance(result, TranscriptionResponse): from litellm.litellm_core_utils.llm_cost_calc.usage_object_transformation import ( TranscriptionUsageObjectTransformation, @@ -2320,18 +2342,28 @@ class Logging(LiteLLMLoggingBaseClass): batch_cost = kwargs.get("batch_cost", None) batch_usage = kwargs.get("batch_usage", None) batch_models = kwargs.get("batch_models", None) - if all([batch_cost, batch_usage, batch_models]) is not None: + has_explicit_batch_data = all( + x is not None for x in (batch_cost, batch_usage, batch_models) + ) + + should_compute_batch_data = ( + not is_base64_unified_file_id + or not has_explicit_batch_data + and result.status == "completed" + ) + if has_explicit_batch_data: result._hidden_params["response_cost"] = batch_cost result._hidden_params["batch_models"] = batch_models result.usage = batch_usage - elif not is_base64_unified_file_id: # only run for non-unified file ids + elif should_compute_batch_data: ( response_cost, batch_usage, batch_models, ) = await _handle_completed_batch( - batch=result, custom_llm_provider=self.custom_llm_provider + batch=result, + custom_llm_provider=self.custom_llm_provider, ) result._hidden_params["response_cost"] = response_cost @@ -2403,6 +2435,36 @@ class Logging(LiteLLMLoggingBaseClass): standard_built_in_tools_params=self.standard_built_in_tools_params, ) + # print standard logging payload + if ( + standard_logging_payload := self.model_call_details.get( + "standard_logging_object" + ) + ) is not None: + emit_standard_logging_payload(standard_logging_payload) + elif self.call_type == "pass_through_endpoint": + print_verbose( + "Async success callbacks: Got a pass-through endpoint response" + ) + + self.model_call_details["async_complete_streaming_response"] = result + + # cost calculation not possible for pass-through + self.model_call_details["response_cost"] = None + + ## STANDARDIZED LOGGING PAYLOAD + self.model_call_details[ + "standard_logging_object" + ] = get_standard_logging_object_payload( + kwargs=self.model_call_details, + init_response_obj=result, + start_time=start_time, + end_time=end_time, + logging_obj=self, + status="success", + standard_built_in_tools_params=self.standard_built_in_tools_params, + ) + # print standard logging payload if ( standard_logging_payload := self.model_call_details.get( @@ -3296,6 +3358,7 @@ def _get_masked_values( "token", "key", "secret", + "vertex_credentials", ] return { k: ( @@ -4248,15 +4311,21 @@ def use_custom_pricing_for_model(litellm_params: Optional[dict]) -> bool: if litellm_params is None: return False + # Check litellm_params using set intersection (only check keys that exist in both) + matching_keys = _CUSTOM_PRICING_KEYS & litellm_params.keys() + for key in matching_keys: + if litellm_params.get(key) is not None: + return True + + # Check model_info metadata: dict = litellm_params.get("metadata", {}) or {} model_info: dict = metadata.get("model_info", {}) or {} - custom_pricing_keys = CustomPricingLiteLLMParams.model_fields.keys() - for key in custom_pricing_keys: - if litellm_params.get(key, None) is not None: - return True - elif model_info.get(key, None) is not None: - return True + if model_info: + matching_keys = _CUSTOM_PRICING_KEYS & model_info.keys() + for key in matching_keys: + if model_info.get(key) is not None: + return True return False @@ -4444,6 +4513,7 @@ class StandardLoggingPayloadSetup: user_api_key_request_route=None, spend_logs_metadata=None, requester_ip_address=None, + user_agent=None, requester_metadata=None, prompt_management_metadata=prompt_management_metadata, applied_guardrails=applied_guardrails, @@ -4524,6 +4594,10 @@ class StandardLoggingPayloadSetup: ) elif isinstance(usage, Usage): return usage + elif isinstance(usage, ResponseAPIUsage): + return ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage( + usage + ) elif isinstance(usage, dict): if ResponseAPILoggingUtils._is_response_api_usage(usage): return ( @@ -4652,7 +4726,10 @@ class StandardLoggingPayloadSetup: @staticmethod def strip_trailing_slash(api_base: Optional[str]) -> Optional[str]: if api_base: - return api_base.rstrip("/") + if api_base.endswith("//"): + return api_base.rstrip("/") + if api_base[-1] == "/": + return api_base[:-1] return api_base @staticmethod @@ -4721,7 +4798,14 @@ class StandardLoggingPayloadSetup: ) -> StandardLoggingPayloadErrorInformation: from litellm.constants import MAXIMUM_TRACEBACK_LINES_TO_LOG - error_status: str = str(getattr(original_exception, "status_code", "")) + # Check for 'code' first (used by ProxyException), then fall back to 'status_code' (used by LiteLLM exceptions) + # Ensure error_code is always a string for Prisma Python JSON field compatibility + error_code_attr = getattr(original_exception, "code", None) + if error_code_attr is not None and str(error_code_attr) not in ("", "None"): + error_status: str = str(error_code_attr) + else: + status_code_attr = getattr(original_exception, "status_code", None) + error_status = str(status_code_attr) if status_code_attr is not None else "" error_class: str = ( str(original_exception.__class__.__name__) if original_exception else "" ) @@ -5126,6 +5210,7 @@ def get_standard_logging_object_payload( model_group=_model_group, model_id=_model_id, requester_ip_address=clean_metadata.get("requester_ip_address", None), + user_agent=clean_metadata.get("user_agent", None), messages=StandardLoggingPayloadSetup.append_system_prompt_messages( kwargs=kwargs, messages=kwargs.get("messages") ), @@ -5191,6 +5276,7 @@ def get_standard_logging_metadata( user_api_key_team_alias=None, spend_logs_metadata=None, requester_ip_address=None, + user_agent=None, requester_metadata=None, user_api_key_end_user_id=None, prompt_management_metadata=None, diff --git a/litellm/litellm_core_utils/llm_cost_calc/utils.py b/litellm/litellm_core_utils/llm_cost_calc/utils.py index 785976ed319..2308dc7beca 100644 --- a/litellm/litellm_core_utils/llm_cost_calc/utils.py +++ b/litellm/litellm_core_utils/llm_cost_calc/utils.py @@ -23,6 +23,15 @@ def _is_above_128k(tokens: float) -> bool: return False +def get_billable_input_tokens(usage: Usage) -> int: + """ + Returns the number of billable input tokens. + Subtracts cached tokens from prompt tokens if applicable. + """ + details = _parse_prompt_tokens_details(usage) + return usage.prompt_tokens - details["cache_hit_tokens"] + + def select_cost_metric_for_model( model_info: ModelInfo, ) -> Literal["cost_per_character", "cost_per_token"]: @@ -190,7 +199,6 @@ def _get_token_base_cost( 1000 if "k" in threshold_str else 1 ) if usage.prompt_tokens > threshold: - prompt_base_cost = cast( float, _get_cost_per_unit(model_info, key, prompt_base_cost) ) @@ -207,6 +215,9 @@ def _get_token_base_cost( cache_creation_tiered_key = ( f"cache_creation_input_token_cost_above_{threshold_str}_tokens" ) + cache_creation_1hr_tiered_key = ( + f"cache_creation_input_token_cost_above_1hr_above_{threshold_str}_tokens" + ) cache_read_tiered_key = ( f"cache_read_input_token_cost_above_{threshold_str}_tokens" ) @@ -221,6 +232,16 @@ def _get_token_base_cost( ), ) + if cache_creation_1hr_tiered_key in model_info: + cache_creation_cost_above_1hr = cast( + float, + _get_cost_per_unit( + model_info, + cache_creation_1hr_tiered_key, + cache_creation_cost_above_1hr, + ), + ) + if cache_read_tiered_key in model_info: cache_read_cost = cast( float, @@ -566,14 +587,28 @@ def generic_cost_per_token( # noqa: PLR0915 if usage.prompt_tokens_details: prompt_tokens_details = _parse_prompt_tokens_details(usage) - ## EDGE CASE - text tokens not set inside PromptTokensDetails + ## EDGE CASE - text tokens not set or includes cached tokens (double-counting) + ## Some providers (like xAI) report text_tokens = prompt_tokens (including cached) + ## We detect this when: text_tokens + cached_tokens + other > prompt_tokens + ## Ref: https://github.com/BerriAI/litellm/issues/19680, #14874, #14875 - if prompt_tokens_details["text_tokens"] == 0: + cache_hit = prompt_tokens_details["cache_hit_tokens"] + text_tokens = prompt_tokens_details["text_tokens"] + audio_tokens = prompt_tokens_details["audio_tokens"] + cache_creation = prompt_tokens_details["cache_creation_tokens"] + image_tokens = prompt_tokens_details["image_tokens"] + + # Check for double-counting: sum of details > prompt_tokens means overlap + total_details = text_tokens + cache_hit + audio_tokens + cache_creation + image_tokens + has_double_counting = cache_hit > 0 and total_details > usage.prompt_tokens + + if text_tokens == 0 or has_double_counting: text_tokens = ( usage.prompt_tokens - - prompt_tokens_details["cache_hit_tokens"] - - prompt_tokens_details["audio_tokens"] - - prompt_tokens_details["cache_creation_tokens"] + - cache_hit + - audio_tokens + - cache_creation + - image_tokens ) prompt_tokens_details["text_tokens"] = text_tokens @@ -619,7 +654,11 @@ def generic_cost_per_token( # noqa: PLR0915 # Calculate text tokens as remainder when we have a breakdown # This handles cases like OpenAI's reasoning models where text_tokens isn't provided text_tokens = max( - 0, usage.completion_tokens - reasoning_tokens - audio_tokens - image_tokens + 0, + usage.completion_tokens + - reasoning_tokens + - audio_tokens + - image_tokens, ) else: # No breakdown at all, all tokens are text tokens diff --git a/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py b/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py index bbe28e3ec2c..25ad0a570cb 100644 --- a/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py +++ b/litellm/litellm_core_utils/llm_response_utils/convert_dict_to_response.py @@ -21,11 +21,13 @@ from litellm.types.utils import ( ChatCompletionMessageToolCall, ChatCompletionRedactedThinkingBlock, Choices, + CompletionTokensDetailsWrapper, Delta, EmbeddingResponse, Function, HiddenParams, ImageResponse, + PromptTokensDetailsWrapper, ) from litellm.types.utils import Logprobs as TextCompletionLogprobs from litellm.types.utils import ( @@ -304,6 +306,22 @@ class LiteLLMResponseObjectHandler: "text_tokens": 0, } + # Map Responses API naming to Chat Completions API naming for cost calculator + if usage.get("prompt_tokens") is None: + usage["prompt_tokens"] = usage.get("input_tokens", 0) + if usage.get("completion_tokens") is None: + usage["completion_tokens"] = usage.get("output_tokens", 0) + + # Convert dicts to wrapper objects so getattr() works in cost calculation + if isinstance(usage.get("input_tokens_details"), dict): + usage["prompt_tokens_details"] = PromptTokensDetailsWrapper( + **usage["input_tokens_details"] + ) + if isinstance(usage.get("output_tokens_details"), dict): + usage["completion_tokens_details"] = CompletionTokensDetailsWrapper( + **usage["output_tokens_details"] + ) + if model_response_object is None: model_response_object = ImageResponse(**response_object) return model_response_object diff --git a/litellm/litellm_core_utils/logging_worker.py b/litellm/litellm_core_utils/logging_worker.py index 13a83956edd..d5eca9eeb55 100644 --- a/litellm/litellm_core_utils/logging_worker.py +++ b/litellm/litellm_core_utils/logging_worker.py @@ -415,6 +415,28 @@ class LoggingWorker: """ Safely log a message during shutdown, suppressing errors if logging is closed. """ + # Check if logger has valid handlers before attempting to log + # During shutdown, handlers may be closed, causing ValueError when writing + if not hasattr(verbose_logger, 'handlers') or not verbose_logger.handlers: + return + + # Check if any handler has a valid stream + has_valid_handler = False + for handler in verbose_logger.handlers: + try: + if hasattr(handler, 'stream') and handler.stream and not handler.stream.closed: + has_valid_handler = True + break + elif not hasattr(handler, 'stream'): + # Non-stream handlers (like NullHandler) are always valid + has_valid_handler = True + break + except (AttributeError, ValueError): + continue + + if not has_valid_handler: + return + try: if level == "debug": verbose_logger.debug(message) diff --git a/litellm/litellm_core_utils/model_param_helper.py b/litellm/litellm_core_utils/model_param_helper.py index 91f2f1341cf..4d45c47c224 100644 --- a/litellm/litellm_core_utils/model_param_helper.py +++ b/litellm/litellm_core_utils/model_param_helper.py @@ -17,15 +17,16 @@ from litellm.types.rerank import RerankRequest class ModelParamHelper: + # Cached at class level — deterministic set built from static OpenAI type annotations + _relevant_logging_args: frozenset = frozenset() + @staticmethod def get_standard_logging_model_parameters( model_parameters: dict, ) -> dict: """ """ standard_logging_model_parameters: dict = {} - supported_model_parameters = ( - ModelParamHelper._get_relevant_args_to_use_for_logging() - ) + supported_model_parameters = ModelParamHelper._relevant_logging_args for key, value in model_parameters.items(): if key in supported_model_parameters: @@ -172,3 +173,8 @@ class ModelParamHelper: Get the kwargs to exclude from the cache key """ return set(["metadata"]) + + +ModelParamHelper._relevant_logging_args = frozenset( + ModelParamHelper._get_relevant_args_to_use_for_logging() +) diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index 30263543fc6..c4c56a8d335 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -1632,6 +1632,7 @@ def _sanitize_anthropic_tool_use_id(tool_use_id: str) -> str: def convert_to_anthropic_tool_result( message: Union[ChatCompletionToolMessage, ChatCompletionFunctionMessage], + force_base64: bool = False, ) -> AnthropicMessagesToolResultParam: """ OpenAI message with a tool result looks like: @@ -1677,13 +1678,16 @@ def convert_to_anthropic_tool_result( ] = [] for content in content_list: if content["type"] == "text": - anthropic_content_list.append( - AnthropicMessagesToolResultContent( - type="text", - text=content["text"], - cache_control=content.get("cache_control", None), - ) - ) + # Only include cache_control if explicitly set and not None + # to avoid sending "cache_control": null which breaks some API channels + text_content: AnthropicMessagesToolResultContent = { + "type": "text", + "text": content["text"], + } + cache_control_value = content.get("cache_control") + if cache_control_value is not None: + text_content["cache_control"] = cache_control_value + anthropic_content_list.append(text_content) elif content["type"] == "image_url": format = ( content["image_url"].get("format") @@ -1691,7 +1695,7 @@ def convert_to_anthropic_tool_result( else None ) _anthropic_image_param = create_anthropic_image_param( - content["image_url"], format=format + content["image_url"], format=format, is_bedrock_invoke=force_base64 ) _anthropic_image_param = add_cache_control_to_content( anthropic_content_element=_anthropic_image_param, @@ -2053,6 +2057,12 @@ def anthropic_messages_pt( # noqa: PLR0915 else: messages.append(DEFAULT_USER_CONTINUE_MESSAGE_TYPED) + # Bedrock invoke models have format: invoke/... + # Vertex AI Anthropic also doesn't support URL sources for images + is_bedrock_invoke = model.lower().startswith("invoke/") + is_vertex_ai = llm_provider.startswith("vertex_ai") if llm_provider else False + force_base64 = is_bedrock_invoke or is_vertex_ai + msg_i = 0 while msg_i < len(messages): user_content: List[AnthropicMessagesUserMessageValues] = [] @@ -2162,7 +2172,9 @@ def anthropic_messages_pt( # noqa: PLR0915 ): # OpenAI's tool message content will always be a string user_content.append( - convert_to_anthropic_tool_result(user_message_types_block) + convert_to_anthropic_tool_result( + user_message_types_block, force_base64=force_base64 + ) ) msg_i += 1 @@ -3387,6 +3399,59 @@ def _convert_to_bedrock_tool_call_result( return content_block +def _deduplicate_bedrock_content_blocks( + blocks: List[BedrockContentBlock], + block_key: str, + id_key: str = "toolUseId", +) -> List[BedrockContentBlock]: + """ + Remove duplicate content blocks that share the same ID under ``block_key``. + + Bedrock requires all toolResult and toolUse IDs within a single message to + be unique. When merging consecutive messages, duplicates can occur if the + same tool_call_id appears multiple times in conversation history. + + When duplicates exist, the first occurrence is retained and subsequent ones + are discarded. A warning is logged for every dropped block so that + upstream duplication bugs remain visible. + + Blocks that do not contain ``block_key`` (e.g., cachePoint, text) are + always preserved. + + Args: + blocks: The list of Bedrock content blocks to deduplicate. + block_key: The dict key to inspect (e.g. ``"toolResult"`` or ``"toolUse"``). + id_key: The nested key that holds the unique ID (default ``"toolUseId"``). + """ + seen_ids: Set[str] = set() + deduplicated: List[BedrockContentBlock] = [] + for block in blocks: + keyed = block.get(block_key) + if keyed is not None and isinstance(keyed, dict): + block_id = keyed.get(id_key) + if block_id: + if block_id in seen_ids: + verbose_logger.warning( + "Bedrock Converse: dropping duplicate %s block with " + "%s=%s. This may indicate duplicate tool messages in " + "conversation history.", + block_key, + id_key, + block_id, + ) + continue + seen_ids.add(block_id) + deduplicated.append(block) + return deduplicated + + +def _deduplicate_bedrock_tool_content( + tool_content: List[BedrockContentBlock], +) -> List[BedrockContentBlock]: + """Convenience wrapper: deduplicate ``toolResult`` blocks by ``toolUseId``.""" + return _deduplicate_bedrock_content_blocks(tool_content, "toolResult") + + def _insert_assistant_continue_message( messages: List[BedrockMessageBlock], assistant_continue_message: Optional[ @@ -3855,6 +3920,8 @@ class BedrockConverseMessagesProcessor: tool_content.append(cache_point_block) msg_i += 1 + # Deduplicate toolResult blocks with the same toolUseId + tool_content = _deduplicate_bedrock_tool_content(tool_content) if tool_content: # if last message was a 'user' message, then add a blank assistant message (bedrock requires alternating roles) if len(contents) > 0 and contents[-1]["role"] == "user": @@ -3968,6 +4035,8 @@ class BedrockConverseMessagesProcessor: msg_i += 1 + assistant_content = _deduplicate_bedrock_content_blocks(assistant_content, "toolUse") + if assistant_content: contents.append( BedrockMessageBlock(role="assistant", content=assistant_content) @@ -4218,6 +4287,8 @@ def _bedrock_converse_messages_pt( # noqa: PLR0915 tool_content.append(cache_point_block) msg_i += 1 + # Deduplicate toolResult blocks with the same toolUseId + tool_content = _deduplicate_bedrock_tool_content(tool_content) if tool_content: # if last message was a 'user' message, then add a blank assistant message (bedrock requires alternating roles) if len(contents) > 0 and contents[-1]["role"] == "user": @@ -4324,6 +4395,8 @@ def _bedrock_converse_messages_pt( # noqa: PLR0915 msg_i += 1 + assistant_content = _deduplicate_bedrock_content_blocks(assistant_content, "toolUse") + if assistant_content: contents.append( BedrockMessageBlock(role="assistant", content=assistant_content) @@ -4383,6 +4456,32 @@ def add_cache_point_tool_block(tool: dict) -> Optional[BedrockToolBlock]: return None +def _is_bedrock_tool_block(tool: dict) -> bool: + """ + Check if a tool is already a BedrockToolBlock. + + BedrockToolBlock has one of: systemTool, toolSpec, or cachePoint. + This is used to detect tools that are already in Bedrock format + (e.g., systemTool for Nova grounding) vs OpenAI-style function tools + that need transformation. + + Args: + tool: The tool dict to check + + Returns: + True if the tool is already a BedrockToolBlock, False otherwise + + Examples: + >>> _is_bedrock_tool_block({"systemTool": {"name": "nova_grounding"}}) + True + >>> _is_bedrock_tool_block({"type": "function", "function": {...}}) + False + """ + return isinstance(tool, dict) and ( + "systemTool" in tool or "toolSpec" in tool or "cachePoint" in tool + ) + + def _bedrock_tools_pt(tools: List) -> List[BedrockToolBlock]: """ OpenAI tools looks like: @@ -4408,7 +4507,7 @@ def _bedrock_tools_pt(tools: List) -> List[BedrockToolBlock]: ] """ """ - Bedrock toolConfig looks like: + Bedrock toolConfig looks like: "tools": [ { "toolSpec": { @@ -4436,6 +4535,13 @@ def _bedrock_tools_pt(tools: List) -> List[BedrockToolBlock]: tool_block_list: List[BedrockToolBlock] = [] for tool in tools: + # Check if tool is already a BedrockToolBlock (e.g., systemTool for Nova grounding) + if _is_bedrock_tool_block(tool): + # Already a BedrockToolBlock, pass it through + tool_block_list.append(tool) # type: ignore + continue + + # Handle regular OpenAI-style function tools parameters = tool.get("function", {}).get( "parameters", {"type": "object", "properties": {}} ) diff --git a/litellm/litellm_core_utils/prompt_templates/image_handling.py b/litellm/litellm_core_utils/prompt_templates/image_handling.py index 5d0bedb776d..7137a4e4222 100644 --- a/litellm/litellm_core_utils/prompt_templates/image_handling.py +++ b/litellm/litellm_core_utils/prompt_templates/image_handling.py @@ -31,15 +31,19 @@ def _process_image_response(response: Response, url: str) -> str: f"Error: Image size ({size_mb:.2f}MB) exceeds maximum allowed size ({MAX_IMAGE_URL_DOWNLOAD_SIZE_MB}MB). url={url}" ) - image_bytes = response.content + # Stream download with size checking to prevent downloading huge files + max_bytes = int(MAX_IMAGE_URL_DOWNLOAD_SIZE_MB * 1024 * 1024) + image_bytes = bytearray() + bytes_downloaded = 0 - # Check actual size after download if Content-Length was not available - if content_length is None: - size_mb = len(image_bytes) / (1024 * 1024) - if size_mb > MAX_IMAGE_URL_DOWNLOAD_SIZE_MB: + for chunk in response.iter_bytes(chunk_size=8192): + bytes_downloaded += len(chunk) + if bytes_downloaded > max_bytes: + size_mb = bytes_downloaded / (1024 * 1024) raise litellm.ImageFetchError( f"Error: Image size ({size_mb:.2f}MB) exceeds maximum allowed size ({MAX_IMAGE_URL_DOWNLOAD_SIZE_MB}MB). url={url}" ) + image_bytes.extend(chunk) base64_image = base64.b64encode(image_bytes).decode("utf-8") diff --git a/litellm/llms/anthropic/chat/guardrail_translation/handler.py b/litellm/llms/anthropic/chat/guardrail_translation/handler.py index 71d74121a30..8e1016bd5bd 100644 --- a/litellm/llms/anthropic/chat/guardrail_translation/handler.py +++ b/litellm/llms/anthropic/chat/guardrail_translation/handler.py @@ -74,7 +74,7 @@ class AnthropicMessagesHandler(BaseTranslation): if messages is None: return data - chat_completion_compatible_request = ( + chat_completion_compatible_request, tool_name_mapping = ( LiteLLMAnthropicMessagesAdapter().translate_anthropic_to_openai( anthropic_message_request=cast(AnthropicMessagesRequest, data) ) diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 82eccee596d..1b61b533275 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -290,10 +290,19 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): elif tool_choice == "none": _tool_choice = AnthropicMessagesToolChoice(type="none") elif isinstance(tool_choice, dict): - _tool_name = tool_choice.get("function", {}).get("name") - _tool_choice = AnthropicMessagesToolChoice(type="tool") - if _tool_name is not None: - _tool_choice["name"] = _tool_name + if "type" in tool_choice and "function" not in tool_choice: + tool_type = tool_choice.get("type") + if tool_type == "auto": + _tool_choice = AnthropicMessagesToolChoice(type="auto") + elif tool_type == "required" or tool_type == "any": + _tool_choice = AnthropicMessagesToolChoice(type="any") + elif tool_type == "none": + _tool_choice = AnthropicMessagesToolChoice(type="none") + else: + _tool_name = tool_choice.get("function", {}).get("name") + if _tool_name is not None: + _tool_choice = AnthropicMessagesToolChoice(type="tool") + _tool_choice["name"] = _tool_name if parallel_tool_use is not None: # Anthropic uses 'disable_parallel_tool_use' flag to determine if parallel tool use is allowed @@ -1369,7 +1378,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): else 0 ) completion_token_details = CompletionTokensDetailsWrapper( - reasoning_tokens=reasoning_tokens if reasoning_tokens > 0 else None, + reasoning_tokens=reasoning_tokens if reasoning_tokens > 0 else 0, text_tokens=completion_tokens - reasoning_tokens if reasoning_tokens > 0 else completion_tokens, ) total_tokens = prompt_tokens + completion_tokens diff --git a/litellm/llms/anthropic/experimental_pass_through/adapters/handler.py b/litellm/llms/anthropic/experimental_pass_through/adapters/handler.py index 8fa7bb7e65e..a17eba75b3b 100644 --- a/litellm/llms/anthropic/experimental_pass_through/adapters/handler.py +++ b/litellm/llms/anthropic/experimental_pass_through/adapters/handler.py @@ -6,6 +6,7 @@ from typing import ( Dict, List, Optional, + Tuple, Union, cast, ) @@ -47,8 +48,14 @@ class LiteLLMMessagesToCompletionTransformationHandler: top_p: Optional[float] = None, output_format: Optional[Dict] = None, extra_kwargs: Optional[Dict[str, Any]] = None, - ) -> Dict[str, Any]: - """Prepare kwargs for litellm.completion/acompletion""" + ) -> Tuple[Dict[str, Any], Dict[str, str]]: + """Prepare kwargs for litellm.completion/acompletion. + + Returns: + Tuple of (completion_kwargs, tool_name_mapping) + - tool_name_mapping maps truncated tool names back to original names + for tools that exceeded OpenAI's 64-char limit + """ from litellm.litellm_core_utils.litellm_logging import ( Logging as LiteLLMLoggingObject, ) @@ -80,7 +87,7 @@ class LiteLLMMessagesToCompletionTransformationHandler: if output_format: request_data["output_format"] = output_format - openai_request = ANTHROPIC_ADAPTER.translate_completion_input_params( + openai_request, tool_name_mapping = ANTHROPIC_ADAPTER.translate_completion_input_params_with_tool_mapping( request_data ) @@ -116,7 +123,7 @@ class LiteLLMMessagesToCompletionTransformationHandler: ): completion_kwargs[key] = value - return completion_kwargs + return completion_kwargs, tool_name_mapping @staticmethod async def async_anthropic_messages_handler( @@ -137,7 +144,7 @@ class LiteLLMMessagesToCompletionTransformationHandler: **kwargs, ) -> Union[AnthropicMessagesResponse, AsyncIterator]: """Handle non-Anthropic models asynchronously using the adapter""" - completion_kwargs = ( + completion_kwargs, tool_name_mapping = ( LiteLLMMessagesToCompletionTransformationHandler._prepare_completion_kwargs( max_tokens=max_tokens, messages=messages, @@ -164,6 +171,7 @@ class LiteLLMMessagesToCompletionTransformationHandler: ANTHROPIC_ADAPTER.translate_completion_output_params_streaming( completion_response, model=model, + tool_name_mapping=tool_name_mapping, ) ) if transformed_stream is not None: @@ -172,7 +180,8 @@ class LiteLLMMessagesToCompletionTransformationHandler: else: anthropic_response = ( ANTHROPIC_ADAPTER.translate_completion_output_params( - cast(ModelResponse, completion_response) + cast(ModelResponse, completion_response), + tool_name_mapping=tool_name_mapping, ) ) if anthropic_response is not None: @@ -222,7 +231,7 @@ class LiteLLMMessagesToCompletionTransformationHandler: **kwargs, ) - completion_kwargs = ( + completion_kwargs, tool_name_mapping = ( LiteLLMMessagesToCompletionTransformationHandler._prepare_completion_kwargs( max_tokens=max_tokens, messages=messages, @@ -249,6 +258,7 @@ class LiteLLMMessagesToCompletionTransformationHandler: ANTHROPIC_ADAPTER.translate_completion_output_params_streaming( completion_response, model=model, + tool_name_mapping=tool_name_mapping, ) ) if transformed_stream is not None: @@ -257,7 +267,8 @@ class LiteLLMMessagesToCompletionTransformationHandler: else: anthropic_response = ( ANTHROPIC_ADAPTER.translate_completion_output_params( - cast(ModelResponse, completion_response) + cast(ModelResponse, completion_response), + tool_name_mapping=tool_name_mapping, ) ) if anthropic_response is not None: diff --git a/litellm/llms/anthropic/experimental_pass_through/adapters/streaming_iterator.py b/litellm/llms/anthropic/experimental_pass_through/adapters/streaming_iterator.py index 24524233ddf..a86820f82e8 100644 --- a/litellm/llms/anthropic/experimental_pass_through/adapters/streaming_iterator.py +++ b/litellm/llms/anthropic/experimental_pass_through/adapters/streaming_iterator.py @@ -3,7 +3,7 @@ import json import traceback from collections import deque -from typing import TYPE_CHECKING, Any, AsyncIterator, Iterator, Literal, Optional +from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, Iterator, Literal, Optional from litellm import verbose_logger from litellm._uuid import uuid @@ -44,9 +44,16 @@ class AnthropicStreamWrapper(AdapterCompletionStreamWrapper): pending_new_content_block: bool = False chunk_queue: deque = deque() # Queue for buffering multiple chunks - def __init__(self, completion_stream: Any, model: str): + def __init__( + self, + completion_stream: Any, + model: str, + tool_name_mapping: Optional[Dict[str, str]] = None, + ): super().__init__(completion_stream) self.model = model + # Mapping of truncated tool names to original names (for OpenAI's 64-char limit) + self.tool_name_mapping = tool_name_mapping or {} def _create_initial_usage_delta(self) -> UsageDelta: """ @@ -401,6 +408,19 @@ class AnthropicStreamWrapper(AdapterCompletionStreamWrapper): choices=chunk.choices # type: ignore ) + # Restore original tool name if it was truncated for OpenAI's 64-char limit + if block_type == "tool_use": + # Type narrowing: content_block_start is ToolUseBlock when block_type is "tool_use" + from typing import cast + from litellm.types.llms.anthropic import ToolUseBlock + + tool_block = cast(ToolUseBlock, content_block_start) + + if tool_block.get("name"): + truncated_name = tool_block["name"] + original_name = self.tool_name_mapping.get(truncated_name, truncated_name) + tool_block["name"] = original_name + if block_type != self.current_content_block_type: self.current_content_block_type = block_type self.current_content_block_start = content_block_start @@ -408,9 +428,14 @@ class AnthropicStreamWrapper(AdapterCompletionStreamWrapper): # For parallel tool calls, we'll necessarily have a new content block # if we get a function name since it signals a new tool call - if block_type == "tool_use" and content_block_start.get("name"): - self.current_content_block_type = block_type - self.current_content_block_start = content_block_start - return True + if block_type == "tool_use": + from typing import cast + from litellm.types.llms.anthropic import ToolUseBlock + + tool_block = cast(ToolUseBlock, content_block_start) + if tool_block.get("name"): + self.current_content_block_type = block_type + self.current_content_block_start = content_block_start + return True return False diff --git a/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py b/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py index 1706f045f14..444f821c20a 100644 --- a/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py +++ b/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py @@ -1,3 +1,4 @@ +import hashlib import json from typing import ( TYPE_CHECKING, @@ -12,6 +13,54 @@ from typing import ( cast, ) +# OpenAI has a 64-character limit for function/tool names +# Anthropic does not have this limit, so we need to truncate long names +OPENAI_MAX_TOOL_NAME_LENGTH = 64 +TOOL_NAME_HASH_LENGTH = 8 +TOOL_NAME_PREFIX_LENGTH = OPENAI_MAX_TOOL_NAME_LENGTH - TOOL_NAME_HASH_LENGTH - 1 # 55 + + +def truncate_tool_name(name: str) -> str: + """ + Truncate tool names that exceed OpenAI's 64-character limit. + + Uses format: {55-char-prefix}_{8-char-hash} to avoid collisions + when multiple tools have similar long names. + + Args: + name: The original tool name + + Returns: + The original name if <= 64 chars, otherwise truncated with hash + """ + if len(name) <= OPENAI_MAX_TOOL_NAME_LENGTH: + return name + + # Create deterministic hash from full name to avoid collisions + name_hash = hashlib.sha256(name.encode()).hexdigest()[:TOOL_NAME_HASH_LENGTH] + return f"{name[:TOOL_NAME_PREFIX_LENGTH]}_{name_hash}" + + +def create_tool_name_mapping( + tools: List[Dict[str, Any]], +) -> Dict[str, str]: + """ + Create a mapping of truncated tool names to original names. + + Args: + tools: List of tool definitions with 'name' field + + Returns: + Dict mapping truncated names to original names (only for truncated tools) + """ + mapping: Dict[str, str] = {} + for tool in tools: + original_name = tool.get("name", "") + truncated_name = truncate_tool_name(original_name) + if truncated_name != original_name: + mapping[truncated_name] = original_name + return mapping + from openai.types.chat.chat_completion_chunk import Choice as OpenAIStreamingChoice from litellm.litellm_core_utils.prompt_templates.common_utils import ( @@ -77,8 +126,29 @@ class AnthropicAdapter: self, kwargs ) -> Optional[ChatCompletionRequest]: """ + Translate Anthropic request params to OpenAI format. + - translate params, where needed - pass rest, as is + + Note: Use translate_completion_input_params_with_tool_mapping() if you need + the tool name mapping for restoring original names in responses. + """ + result, _ = self.translate_completion_input_params_with_tool_mapping(kwargs) + return result + + def translate_completion_input_params_with_tool_mapping( + self, kwargs + ) -> Tuple[Optional[ChatCompletionRequest], Dict[str, str]]: + """ + Translate Anthropic request params to OpenAI format, returning tool name mapping. + + This method handles truncation of tool names that exceed OpenAI's 64-character + limit. The mapping allows restoring original names when translating responses. + + Returns: + Tuple of (openai_request, tool_name_mapping) + - tool_name_mapping maps truncated tool names back to original names """ ######################################################### @@ -102,26 +172,51 @@ class AnthropicAdapter: model=model, messages=messages, **kwargs ) - translated_body = ( + translated_body, tool_name_mapping = ( LiteLLMAnthropicMessagesAdapter().translate_anthropic_to_openai( anthropic_message_request=request_body ) ) - return translated_body + return translated_body, tool_name_mapping def translate_completion_output_params( - self, response: ModelResponse + self, + response: ModelResponse, + tool_name_mapping: Optional[Dict[str, str]] = None, ) -> Optional[AnthropicMessagesResponse]: + """ + Translate OpenAI response to Anthropic format. + + Args: + response: The OpenAI ModelResponse + tool_name_mapping: Optional mapping of truncated tool names to original names. + Used to restore original names for tools that exceeded + OpenAI's 64-char limit. + """ return LiteLLMAnthropicMessagesAdapter().translate_openai_response_to_anthropic( - response=response + response=response, + tool_name_mapping=tool_name_mapping, ) def translate_completion_output_params_streaming( - self, completion_stream: Any, model: str + self, + completion_stream: Any, + model: str, + tool_name_mapping: Optional[Dict[str, str]] = None, ) -> Union[AsyncIterator[bytes], None]: + """ + Translate OpenAI streaming response to Anthropic format. + + Args: + completion_stream: The OpenAI streaming response + model: The model name + tool_name_mapping: Optional mapping of truncated tool names to original names. + """ anthropic_wrapper = AnthropicStreamWrapper( - completion_stream=completion_stream, model=model + completion_stream=completion_stream, + model=model, + tool_name_mapping=tool_name_mapping, ) # Return the SSE-wrapped version for proper event formatting return anthropic_wrapper.async_anthropic_sse_wrapper() @@ -168,6 +263,36 @@ class LiteLLMAnthropicMessagesAdapter: return provider_specific_fields.get("signature") return None + def _add_cache_control_if_applicable( + self, + source: Any, + target: Any, + model: Optional[str], + ) -> None: + """ + Extract cache_control from source and add to target if it should be preserved. + + This method accepts Any type to support both regular dicts and TypedDict objects. + TypedDict objects (like ChatCompletionTextObject, ChatCompletionImageObject, etc.) + are dicts at runtime but have specific types at type-check time. Using Any allows + this method to work with both while maintaining runtime correctness. + + Args: + source: Dict or TypedDict containing potential cache_control field + target: Dict or TypedDict to add cache_control to + model: Model name to check if cache_control should be preserved + """ + # TypedDict objects are dicts at runtime, so .get() works + cache_control = source.get("cache_control") if isinstance(source, dict) else getattr(source, "cache_control", None) + if cache_control and model and self.is_anthropic_claude_model(model): + # TypedDict objects support dict operations at runtime + # Use type ignore consistent with codebase pattern (see anthropic/chat/transformation.py:432) + if isinstance(target, dict): + target["cache_control"] = cache_control # type: ignore[typeddict-item] + else: + # Fallback for non-dict objects (shouldn't happen in practice) + cast(Dict[str, Any], target)["cache_control"] = cache_control + def translatable_anthropic_params(self) -> List: """ Which anthropic params, we need to translate to the openai format. @@ -205,12 +330,8 @@ class LiteLLMAnthropicMessagesAdapter: text_obj = ChatCompletionTextObject( type="text", text=content.get("text", "") ) - # Preserve cache_control if present (for prompt caching) - # Only for Anthropic models that support prompt caching - cache_control = content.get("cache_control") - if cache_control and model and self.is_anthropic_claude_model(model): - text_obj["cache_control"] = cache_control # type: ignore - new_user_content_list.append(text_obj) + self._add_cache_control_if_applicable(content, text_obj, model) + new_user_content_list.append(text_obj) # type: ignore elif content.get("type") == "image": # Convert Anthropic image format to OpenAI format source = content.get("source", {}) @@ -225,7 +346,24 @@ class LiteLLMAnthropicMessagesAdapter: image_obj = ChatCompletionImageObject( type="image_url", image_url=image_url_obj ) - new_user_content_list.append(image_obj) + self._add_cache_control_if_applicable(content, image_obj, model) + new_user_content_list.append(image_obj) # type: ignore + elif content.get("type") == "document": + # Convert Anthropic document format (PDF, etc.) to OpenAI format + source = content.get("source", {}) + openai_image_url = ( + self._translate_anthropic_image_to_openai(cast(dict, source)) + ) + + if openai_image_url: + image_url_obj = ChatCompletionImageUrlObject( + url=openai_image_url + ) + doc_obj = ChatCompletionImageObject( + type="image_url", image_url=image_url_obj + ) + self._add_cache_control_if_applicable(content, doc_obj, model) + new_user_content_list.append(doc_obj) # type: ignore elif content.get("type") == "tool_result": if "content" not in content: tool_result = ChatCompletionToolMessage( @@ -233,14 +371,16 @@ class LiteLLMAnthropicMessagesAdapter: tool_call_id=content.get("tool_use_id", ""), content="", ) - tool_message_list.append(tool_result) + self._add_cache_control_if_applicable(content, tool_result, model) + tool_message_list.append(tool_result) # type: ignore[arg-type] elif isinstance(content.get("content"), str): tool_result = ChatCompletionToolMessage( role="tool", tool_call_id=content.get("tool_use_id", ""), content=str(content.get("content", "")), ) - tool_message_list.append(tool_result) + self._add_cache_control_if_applicable(content, tool_result, model) + tool_message_list.append(tool_result) # type: ignore[arg-type] elif isinstance(content.get("content"), list): # Combine all content items into a single tool message # to avoid creating multiple tool_result blocks with the same ID @@ -256,7 +396,8 @@ class LiteLLMAnthropicMessagesAdapter: tool_call_id=content.get("tool_use_id", ""), content=c, ) - tool_message_list.append(tool_result) + self._add_cache_control_if_applicable(content, tool_result, model) + tool_message_list.append(tool_result) # type: ignore[arg-type] elif isinstance(c, dict): if c.get("type") == "text": tool_result = ChatCompletionToolMessage( @@ -266,7 +407,8 @@ class LiteLLMAnthropicMessagesAdapter: ), content=c.get("text", ""), ) - tool_message_list.append(tool_result) + self._add_cache_control_if_applicable(content, tool_result, model) + tool_message_list.append(tool_result) # type: ignore[arg-type] elif c.get("type") == "image": source = c.get("source", {}) openai_image_url = ( @@ -282,7 +424,8 @@ class LiteLLMAnthropicMessagesAdapter: ), content=openai_image_url, ) - tool_message_list.append(tool_result) + self._add_cache_control_if_applicable(content, tool_result, model) + tool_message_list.append(tool_result) # type: ignore[arg-type] else: # For multiple content items, combine into a single tool message # with list content to preserve all items while having one tool_use_id @@ -331,7 +474,8 @@ class LiteLLMAnthropicMessagesAdapter: tool_call_id=content.get("tool_use_id", ""), content=combined_content_parts, # type: ignore ) - tool_message_list.append(tool_result) + self._add_cache_control_if_applicable(content, tool_result, model) + tool_message_list.append(tool_result) # type: ignore[arg-type] if len(tool_message_list) > 0: new_messages.extend(tool_message_list) @@ -344,6 +488,8 @@ class LiteLLMAnthropicMessagesAdapter: ## ASSISTANT MESSAGE ## assistant_message_str: Optional[str] = None + assistant_content_list: List[Dict[str, Any]] = [] # For content blocks with cache_control + has_cache_control_in_text = False tool_calls: List[ChatCompletionAssistantToolCall] = [] thinking_blocks: List[ Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock] @@ -357,13 +503,19 @@ class LiteLLMAnthropicMessagesAdapter: assistant_message_str = str(content) elif isinstance(content, dict): if content.get("type") == "text": - if assistant_message_str is None: - assistant_message_str = content.get("text", "") - else: - assistant_message_str += content.get("text", "") + text_block: Dict[str, Any] = { + "type": "text", + "text": content.get("text", ""), + } + self._add_cache_control_if_applicable(content, text_block, model) + if "cache_control" in text_block: + has_cache_control_in_text = True + assistant_content_list.append(text_block) elif content.get("type") == "tool_use": + # Truncate tool name for OpenAI's 64-char limit + tool_name = truncate_tool_name(content.get("name", "")) function_chunk: ChatCompletionToolCallFunctionChunk = { - "name": content.get("name", ""), + "name": tool_name, "arguments": json.dumps(content.get("input", {})), } signature = ( @@ -384,13 +536,13 @@ class LiteLLMAnthropicMessagesAdapter: provider_specific_fields ) - tool_calls.append( - ChatCompletionAssistantToolCall( - id=content.get("id", ""), - type="function", - function=function_chunk, - ) + tool_call = ChatCompletionAssistantToolCall( + id=content.get("id", ""), + type="function", + function=function_chunk, ) + self._add_cache_control_if_applicable(content, tool_call, model) + tool_calls.append(tool_call) elif content.get("type") == "thinking": thinking_block = ChatCompletionThinkingBlock( type="thinking", @@ -411,18 +563,30 @@ class LiteLLMAnthropicMessagesAdapter: if ( assistant_message_str is not None + or len(assistant_content_list) > 0 or len(tool_calls) > 0 or len(thinking_blocks) > 0 ): + # Use list format if any text block has cache_control, otherwise use string + if has_cache_control_in_text and len(assistant_content_list) > 0: + assistant_content: Any = assistant_content_list + elif len(assistant_content_list) > 0 and not has_cache_control_in_text: + # Concatenate text blocks into string when no cache_control + assistant_content = "".join( + block.get("text", "") for block in assistant_content_list + ) + else: + assistant_content = assistant_message_str + assistant_message = ChatCompletionAssistantMessage( role="assistant", - content=assistant_message_str, + content=assistant_content, thinking_blocks=( thinking_blocks if len(thinking_blocks) > 0 else None ), ) if len(tool_calls) > 0: - assistant_message["tool_calls"] = tool_calls + assistant_message["tool_calls"] = tool_calls # type: ignore if len(thinking_blocks) > 0: assistant_message["thinking_blocks"] = thinking_blocks # type: ignore new_messages.append(assistant_message) @@ -520,8 +684,11 @@ class LiteLLMAnthropicMessagesAdapter: elif tool_choice["type"] == "auto": return "auto" elif tool_choice["type"] == "tool": + # Truncate tool name if it exceeds OpenAI's 64-char limit + original_name = tool_choice.get("name", "") + truncated_name = truncate_tool_name(original_name) tc_function_param = ChatCompletionToolChoiceFunctionParam( - name=tool_choice.get("name", "") + name=truncated_name ) return ChatCompletionToolChoiceObjectParam( type="function", function=tc_function_param @@ -532,13 +699,29 @@ class LiteLLMAnthropicMessagesAdapter: ) def translate_anthropic_tools_to_openai( - self, tools: List[AllAnthropicToolsValues] - ) -> List[ChatCompletionToolParam]: + self, tools: List[AllAnthropicToolsValues], model: Optional[str] = None + ) -> Tuple[List[ChatCompletionToolParam], Dict[str, str]]: + """ + Translate Anthropic tools to OpenAI format. + + Returns: + Tuple of (translated_tools, tool_name_mapping) + - tool_name_mapping maps truncated names back to original names + for tools that exceeded OpenAI's 64-char limit + """ new_tools: List[ChatCompletionToolParam] = [] - mapped_tool_params = ["name", "input_schema", "description"] + tool_name_mapping: Dict[str, str] = {} + mapped_tool_params = ["name", "input_schema", "description", "cache_control"] for tool in tools: + original_name = tool["name"] + truncated_name = truncate_tool_name(original_name) + + # Store mapping if name was truncated + if truncated_name != original_name: + tool_name_mapping[truncated_name] = original_name + function_chunk = ChatCompletionToolParamFunctionChunk( - name=tool["name"], + name=truncated_name, ) if "input_schema" in tool: function_chunk["parameters"] = tool["input_schema"] # type: ignore @@ -548,11 +731,11 @@ class LiteLLMAnthropicMessagesAdapter: for k, v in tool.items(): if k not in mapped_tool_params: # pass additional computer kwargs function_chunk.setdefault("parameters", {}).update({k: v}) - new_tools.append( - ChatCompletionToolParam(type="function", function=function_chunk) - ) + tool_param = ChatCompletionToolParam(type="function", function=function_chunk) + self._add_cache_control_if_applicable(tool, tool_param, model) + new_tools.append(tool_param) # type: ignore[arg-type] - return new_tools + return new_tools, tool_name_mapping # type: ignore[return-value] def translate_anthropic_output_format_to_openai( self, output_format: Any @@ -590,14 +773,55 @@ class LiteLLMAnthropicMessagesAdapter: }, } + def _add_system_message_to_messages( + self, + new_messages: List[AllMessageValues], + anthropic_message_request: AnthropicMessagesRequest, + ) -> None: + """Add system message to messages list if present in request.""" + if "system" not in anthropic_message_request: + return + system_content = anthropic_message_request["system"] + if not system_content: + return + # Handle system as string or array of content blocks + if isinstance(system_content, str): + new_messages.insert( + 0, + ChatCompletionSystemMessage(role="system", content=system_content), + ) + elif isinstance(system_content, list): + # Convert Anthropic system content blocks to OpenAI format + openai_system_content: List[Dict[str, Any]] = [] + model_name = anthropic_message_request.get("model", "") + for block in system_content: + if isinstance(block, dict) and block.get("type") == "text": + text_block: Dict[str, Any] = { + "type": "text", + "text": block.get("text", ""), + } + self._add_cache_control_if_applicable(block, text_block, model_name) + openai_system_content.append(text_block) + if openai_system_content: + new_messages.insert( + 0, + ChatCompletionSystemMessage(role="system", content=openai_system_content), # type: ignore + ) + def translate_anthropic_to_openai( self, anthropic_message_request: AnthropicMessagesRequest - ) -> ChatCompletionRequest: + ) -> Tuple[ChatCompletionRequest, Dict[str, str]]: """ This is used by the beta Anthropic Adapter, for translating anthropic `/v1/messages` requests to the openai format. + + Returns: + Tuple of (openai_request, tool_name_mapping) + - tool_name_mapping maps truncated tool names back to original names + for tools that exceeded OpenAI's 64-char limit """ # Debug: Processing Anthropic message request new_messages: List[AllMessageValues] = [] + tool_name_mapping: Dict[str, str] = {} ## CONVERT ANTHROPIC MESSAGES TO OPENAI messages_list: List[ @@ -618,13 +842,7 @@ class LiteLLMAnthropicMessagesAdapter: model=anthropic_message_request.get("model"), ) ## ADD SYSTEM MESSAGE TO MESSAGES - if "system" in anthropic_message_request: - system_content = anthropic_message_request["system"] - if system_content: - new_messages.insert( - 0, - ChatCompletionSystemMessage(role="system", content=system_content), - ) + self._add_system_message_to_messages(new_messages, anthropic_message_request) new_kwargs: ChatCompletionRequest = { "model": anthropic_message_request["model"], @@ -654,8 +872,9 @@ class LiteLLMAnthropicMessagesAdapter: if "tools" in anthropic_message_request: tools = anthropic_message_request["tools"] if tools: - new_kwargs["tools"] = self.translate_anthropic_tools_to_openai( - tools=cast(List[AllAnthropicToolsValues], tools) + new_kwargs["tools"], tool_name_mapping = self.translate_anthropic_tools_to_openai( + tools=cast(List[AllAnthropicToolsValues], tools), + model=new_kwargs.get("model"), ) ## CONVERT THINKING @@ -687,7 +906,7 @@ class LiteLLMAnthropicMessagesAdapter: if k not in translatable_params: # pass remaining params as is new_kwargs[k] = v # type: ignore - return new_kwargs + return new_kwargs, tool_name_mapping def _translate_anthropic_image_to_openai(self, image_source: dict) -> Optional[str]: """ @@ -716,7 +935,11 @@ class LiteLLMAnthropicMessagesAdapter: return None - def _translate_openai_content_to_anthropic(self, choices: List[Choices]) -> List[ + def _translate_openai_content_to_anthropic( + self, + choices: List[Choices], + tool_name_mapping: Optional[Dict[str, str]] = None, + ) -> List[ Union[ AnthropicResponseContentBlockText, AnthropicResponseContentBlockToolUse, @@ -765,6 +988,18 @@ class LiteLLMAnthropicMessagesAdapter: data=str(data_value) if data_value is not None else "", ) ) + # Handle reasoning_content when thinking_blocks is not present + elif ( + hasattr(choice.message, "reasoning_content") + and choice.message.reasoning_content + ): + new_content.append( + AnthropicResponseContentBlockThinking( + type="thinking", + thinking=str(choice.message.reasoning_content), + signature=None, + ) + ) # Handle text content if choice.message.content is not None: @@ -786,13 +1021,21 @@ class LiteLLMAnthropicMessagesAdapter: if signature: provider_specific_fields["signature"] = signature + # Restore original tool name if it was truncated + truncated_name = tool_call.function.name or "" + original_name = ( + tool_name_mapping.get(truncated_name, truncated_name) + if tool_name_mapping + else truncated_name + ) + tool_use_block = AnthropicResponseContentBlockToolUse( type="tool_use", id=tool_call.id, - name=tool_call.function.name or "", + name=original_name, input=parse_tool_call_arguments( tool_call.function.arguments, - tool_name=tool_call.function.name, + tool_name=original_name, context="Anthropic pass-through adapter", ), ) @@ -817,10 +1060,24 @@ class LiteLLMAnthropicMessagesAdapter: return "end_turn" def translate_openai_response_to_anthropic( - self, response: ModelResponse + self, + response: ModelResponse, + tool_name_mapping: Optional[Dict[str, str]] = None, ) -> AnthropicMessagesResponse: + """ + Translate OpenAI response to Anthropic format. + + Args: + response: The OpenAI ModelResponse + tool_name_mapping: Optional mapping of truncated tool names to original names. + Used to restore original names for tools that exceeded + OpenAI's 64-char limit. + """ ## translate content block - anthropic_content = self._translate_openai_content_to_anthropic(choices=response.choices) # type: ignore + anthropic_content = self._translate_openai_content_to_anthropic( + choices=response.choices, # type: ignore + tool_name_mapping=tool_name_mapping, + ) ## extract finish reason anthropic_finish_reason = self._translate_openai_finish_reason_to_anthropic( openai_finish_reason=response.choices[0].finish_reason # type: ignore @@ -843,7 +1100,7 @@ class LiteLLMAnthropicMessagesAdapter: role="assistant", model=response.model or "unknown-model", stop_sequence=None, - usage=anthropic_usage, + usage=anthropic_usage, # type: ignore content=anthropic_content, # type: ignore stop_reason=anthropic_finish_reason, ) @@ -939,6 +1196,13 @@ class LiteLLMAnthropicMessagesAdapter: reasoning_content += thinking reasoning_signature += signature + # Handle reasoning_content when thinking_blocks is not present + # This handles providers like OpenRouter that return reasoning_content + elif isinstance(choice, StreamingChoices) and hasattr( + choice.delta, "reasoning_content" + ): + if choice.delta.reasoning_content is not None: + reasoning_content += choice.delta.reasoning_content if reasoning_content and reasoning_signature: raise ValueError( @@ -992,7 +1256,7 @@ class LiteLLMAnthropicMessagesAdapter: else: usage_delta = UsageDelta(input_tokens=0, output_tokens=0) return MessageBlockDelta( - type="message_delta", delta=delta, usage=usage_delta + type="message_delta", delta=delta, usage=usage_delta # type: ignore ) ( type_of_content, diff --git a/litellm/llms/azure/batches/handler.py b/litellm/llms/azure/batches/handler.py index 3996cb808e4..aaefe801687 100644 --- a/litellm/llms/azure/batches/handler.py +++ b/litellm/llms/azure/batches/handler.py @@ -5,12 +5,10 @@ Azure Batches API Handler from typing import Any, Coroutine, Optional, Union, cast import httpx - from openai import AsyncOpenAI, OpenAI from litellm.llms.azure.azure import AsyncAzureOpenAI, AzureOpenAI from litellm.types.llms.openai import ( - Batch, CancelBatchRequest, CreateBatchRequest, RetrieveBatchRequest, @@ -130,9 +128,9 @@ class AzureBatchesAPI(BaseAzureLLM): self, cancel_batch_data: CancelBatchRequest, client: Union[AsyncAzureOpenAI, AsyncOpenAI], - ) -> Batch: + ) -> LiteLLMBatch: response = await client.batches.cancel(**cancel_batch_data) - return response + return LiteLLMBatch(**response.model_dump()) def cancel_batch( self, @@ -160,8 +158,23 @@ class AzureBatchesAPI(BaseAzureLLM): raise ValueError( "OpenAI client is not initialized. Make sure api_key is passed or OPENAI_API_KEY is set in the environment." ) + + if _is_async is True: + if not isinstance(azure_client, (AsyncAzureOpenAI, AsyncOpenAI)): + raise ValueError( + "Azure client is not an instance of AsyncAzureOpenAI or AsyncOpenAI. Make sure you passed an async client." + ) + return self.acancel_batch( # type: ignore + cancel_batch_data=cancel_batch_data, client=azure_client + ) + + # At this point, azure_client is guaranteed to be a sync client + if not isinstance(azure_client, (AzureOpenAI, OpenAI)): + raise ValueError( + "Azure client is not an instance of AzureOpenAI or OpenAI. Make sure you passed a sync client." + ) response = azure_client.batches.cancel(**cancel_batch_data) - return response + return LiteLLMBatch(**response.model_dump()) async def alist_batches( self, diff --git a/litellm/llms/azure/chat/gpt_5_transformation.py b/litellm/llms/azure/chat/gpt_5_transformation.py index 506b7fdfe5e..eeb55911ecf 100644 --- a/litellm/llms/azure/chat/gpt_5_transformation.py +++ b/litellm/llms/azure/chat/gpt_5_transformation.py @@ -22,7 +22,8 @@ class AzureOpenAIGPT5Config(AzureOpenAIConfig, OpenAIGPT5Config): Accepts both explicit gpt-5 model names and the ``gpt5_series/`` prefix used for manual routing. """ - return "gpt-5" in model or "gpt5_series" in model + # gpt-5-chat* is a chat model and shouldn't go through GPT-5 reasoning restrictions. + return ("gpt-5" in model and "gpt-5-chat" not in model) or "gpt5_series" in model def get_supported_openai_params(self, model: str) -> List[str]: """Get supported parameters for Azure OpenAI GPT-5 models. @@ -37,6 +38,11 @@ class AzureOpenAIGPT5Config(AzureOpenAIConfig, OpenAIGPT5Config): """ params = OpenAIGPT5Config.get_supported_openai_params(self, model=model) + # Azure supports tool_choice for GPT-5 deployments, but the base GPT-5 config + # can drop it when the deployment name isn't in the OpenAI model registry. + if "tool_choice" not in params: + params.append("tool_choice") + # Only gpt-5.2 has been verified to support logprobs on Azure if self.is_model_gpt_5_2_model(model): azure_supported_params = ["logprobs", "top_logprobs"] diff --git a/litellm/llms/azure/cost_calculation.py b/litellm/llms/azure/cost_calculation.py index 96c58d95ff2..5b411095ea1 100644 --- a/litellm/llms/azure/cost_calculation.py +++ b/litellm/llms/azure/cost_calculation.py @@ -1,11 +1,12 @@ """ Helper util for handling azure openai-specific cost calculation -- e.g.: prompt caching +- e.g.: prompt caching, audio tokens """ from typing import Optional, Tuple from litellm._logging import verbose_logger +from litellm.litellm_core_utils.llm_cost_calc.utils import generic_cost_per_token from litellm.types.utils import Usage from litellm.utils import get_model_info @@ -18,34 +19,15 @@ def cost_per_token( Input: - model: str, the model name without provider prefix - - usage: LiteLLM Usage block, containing anthropic caching information + - usage: LiteLLM Usage block, containing caching and audio token information Returns: Tuple[float, float] - prompt_cost_in_usd, completion_cost_in_usd """ ## GET MODEL INFO model_info = get_model_info(model=model, custom_llm_provider="azure") - cached_tokens: Optional[int] = None - ## CALCULATE INPUT COST - non_cached_text_tokens = usage.prompt_tokens - if usage.prompt_tokens_details and usage.prompt_tokens_details.cached_tokens: - cached_tokens = usage.prompt_tokens_details.cached_tokens - non_cached_text_tokens = non_cached_text_tokens - cached_tokens - prompt_cost: float = non_cached_text_tokens * model_info["input_cost_per_token"] - ## CALCULATE OUTPUT COST - completion_cost: float = ( - usage["completion_tokens"] * model_info["output_cost_per_token"] - ) - - ## Prompt Caching cost calculation - if model_info.get("cache_read_input_token_cost") is not None and cached_tokens: - # Note: We read ._cache_read_input_tokens from the Usage - since cost_calculator.py standardizes the cache read tokens on usage._cache_read_input_tokens - prompt_cost += cached_tokens * ( - model_info.get("cache_read_input_token_cost", 0) or 0 - ) - - ## Speech / Audio cost calculation + ## Speech / Audio cost calculation (cost per second for TTS models) if ( "output_cost_per_second" in model_info and model_info["output_cost_per_second"] is not None @@ -55,7 +37,14 @@ def cost_per_token( f"For model={model} - output_cost_per_second: {model_info.get('output_cost_per_second')}; response time: {response_time_ms}" ) ## COST PER SECOND ## - prompt_cost = 0 + prompt_cost = 0.0 completion_cost = model_info["output_cost_per_second"] * response_time_ms / 1000 + return prompt_cost, completion_cost - return prompt_cost, completion_cost + ## Use generic cost calculator for all other cases + ## This properly handles: text tokens, audio tokens, cached tokens, reasoning tokens, etc. + return generic_cost_per_token( + model=model, + usage=usage, + custom_llm_provider="azure", + ) diff --git a/litellm/llms/azure_ai/anthropic/count_tokens/transformation.py b/litellm/llms/azure_ai/anthropic/count_tokens/transformation.py index e284595cc8a..09b83b7c971 100644 --- a/litellm/llms/azure_ai/anthropic/count_tokens/transformation.py +++ b/litellm/llms/azure_ai/anthropic/count_tokens/transformation.py @@ -30,30 +30,32 @@ class AzureAIAnthropicCountTokensConfig(AnthropicCountTokensConfig): """ Get the required headers for the Azure AI Anthropic CountTokens API. - Uses Azure authentication (api-key header) instead of Anthropic's x-api-key. + Azure AI Anthropic uses Anthropic's native API format, which requires the + x-api-key header for authentication (in addition to Azure's api-key header). Args: api_key: The Azure AI API key litellm_params: Optional LiteLLM parameters for additional auth config Returns: - Dictionary of required headers with Azure authentication + Dictionary of required headers with both x-api-key and Azure authentication """ - # Start with base headers + # Start with base headers including x-api-key for Anthropic API compatibility headers = { "Content-Type": "application/json", "anthropic-version": "2023-06-01", "anthropic-beta": ANTHROPIC_TOKEN_COUNTING_BETA_VERSION, + "x-api-key": api_key, # Azure AI Anthropic requires this header } - # Use Azure authentication + # Also set up Azure auth headers for flexibility litellm_params = litellm_params or {} if "api_key" not in litellm_params: litellm_params["api_key"] = api_key litellm_params_obj = GenericLiteLLMParams(**litellm_params) - # Get Azure auth headers + # Get Azure auth headers (api-key or Authorization) azure_headers = BaseAzureLLM._base_validate_azure_environment( headers={}, litellm_params=litellm_params_obj ) @@ -68,7 +70,7 @@ class AzureAIAnthropicCountTokensConfig(AnthropicCountTokensConfig): Get the Azure AI Anthropic CountTokens API endpoint. Args: - api_base: The Azure AI API base URL + api_base: The Azure AI API base URL (e.g., https://my-resource.services.ai.azure.com or https://my-resource.services.ai.azure.com/anthropic) diff --git a/litellm/llms/azure_ai/azure_model_router/__init__.py b/litellm/llms/azure_ai/azure_model_router/__init__.py new file mode 100644 index 00000000000..0165d60b643 --- /dev/null +++ b/litellm/llms/azure_ai/azure_model_router/__init__.py @@ -0,0 +1,4 @@ +"""Azure AI Foundry Model Router support.""" +from .transformation import AzureModelRouterConfig + +__all__ = ["AzureModelRouterConfig"] diff --git a/litellm/llms/azure_ai/azure_model_router/transformation.py b/litellm/llms/azure_ai/azure_model_router/transformation.py new file mode 100644 index 00000000000..3d6dc53c515 --- /dev/null +++ b/litellm/llms/azure_ai/azure_model_router/transformation.py @@ -0,0 +1,125 @@ +""" +Transformation for Azure AI Foundry Model Router. + +The Model Router is a special Azure AI deployment that automatically routes requests +to the best available model. It has specific cost tracking requirements. +""" +from typing import Any, List, Optional + +from httpx import Response + +from litellm.llms.azure_ai.chat.transformation import AzureAIStudioConfig +from litellm.llms.base_llm.chat.transformation import LiteLLMLoggingObj +from litellm.types.llms.openai import AllMessageValues +from litellm.types.utils import ModelResponse + + +class AzureModelRouterConfig(AzureAIStudioConfig): + """ + Configuration for Azure AI Foundry Model Router. + + Handles: + - Stripping model_router prefix before sending to Azure API + - Preserving full model path in responses for cost tracking + - Calculating flat infrastructure costs for Model Router + """ + + def transform_request( + self, + model: str, + messages: List[AllMessageValues], + optional_params: dict, + litellm_params: dict, + headers: dict, + ) -> dict: + """ + Transform request for Model Router. + + Strips the model_router/ prefix so only the deployment name is sent to Azure. + Example: model_router/azure-model-router -> azure-model-router + """ + from litellm.llms.azure_ai.common_utils import AzureFoundryModelInfo + + # Get base model name (strips routing prefixes like model_router/) + base_model: str = AzureFoundryModelInfo.get_base_model(model) + + return super().transform_request( + base_model, messages, optional_params, litellm_params, headers + ) + + def transform_response( + self, + model: str, + raw_response: Response, + model_response: ModelResponse, + logging_obj: LiteLLMLoggingObj, + request_data: dict, + messages: List[AllMessageValues], + optional_params: dict, + litellm_params: dict, + encoding: Any, + api_key: Optional[str] = None, + json_mode: Optional[bool] = None, + ) -> ModelResponse: + """ + Transform response for Model Router. + + Preserves the original model path (including model_router/ prefix) in the response + for proper cost tracking and logging. + """ + from litellm.llms.azure_ai.common_utils import AzureFoundryModelInfo + + # Preserve the original model from litellm_params (includes routing prefixes like model_router/) + # This ensures cost tracking and logging use the full model path + original_model: str = litellm_params.get("model") or model + if not original_model.startswith("azure_ai/"): + # Add provider prefix if not already present + model_response.model = f"azure_ai/{original_model}" + else: + model_response.model = original_model + + # Get base model for the parent call (strips routing prefixes for API compatibility) + base_model: str = AzureFoundryModelInfo.get_base_model(model) + + return super().transform_response( + model=base_model, + raw_response=raw_response, + model_response=model_response, + logging_obj=logging_obj, + request_data=request_data, + messages=messages, + optional_params=optional_params, + litellm_params=litellm_params, + encoding=encoding, + api_key=api_key, + json_mode=json_mode, + ) + + def calculate_additional_costs( + self, model: str, prompt_tokens: int, completion_tokens: int + ) -> Optional[dict]: + """ + Calculate additional costs for Azure Model Router. + + Adds a flat infrastructure cost of $0.14 per M input tokens for using the Model Router. + + Args: + model: The model name (should be a model router model) + prompt_tokens: Number of prompt tokens + completion_tokens: Number of completion tokens + + Returns: + Dictionary with additional costs, or None if not applicable. + """ + from litellm.llms.azure_ai.cost_calculator import ( + calculate_azure_model_router_flat_cost, + ) + + flat_cost = calculate_azure_model_router_flat_cost( + model=model, prompt_tokens=prompt_tokens + ) + + if flat_cost > 0: + return {"Azure Model Router Flat Cost": flat_cost} + + return None diff --git a/litellm/llms/azure_ai/common_utils.py b/litellm/llms/azure_ai/common_utils.py index 01a3f5766c6..47d397d6e98 100644 --- a/litellm/llms/azure_ai/common_utils.py +++ b/litellm/llms/azure_ai/common_utils.py @@ -13,14 +13,28 @@ class AzureFoundryModelInfo(BaseLLMModelInfo): self._model = model @staticmethod - def get_azure_ai_route(model: str) -> Literal["agents", "default"]: + def get_azure_ai_route(model: str) -> Literal["agents", "model_router", "default"]: """ Get the Azure AI route for the given model. Similar to BedrockModelInfo.get_bedrock_route(). + + Supported routes: + - agents: azure_ai/agents/ + - model_router: azure_ai/model_router/ or models with "model-router"/"model_router" in name + - default: standard models """ if "agents/" in model: return "agents" + # Detect model router by prefix (model_router/) or by name containing "model-router"/"model_router" + model_lower = model.lower() + if ( + "model_router/" in model_lower + or "model-router/" in model_lower + or "model-router" in model_lower + or "model_router" in model_lower + ): + return "model_router" return "default" @staticmethod @@ -75,8 +89,73 @@ class AzureFoundryModelInfo(BaseLLMModelInfo): ######################################################### @staticmethod - def get_base_model(model: str) -> Optional[str]: - raise NotImplementedError("Azure Foundry does not support base model") + def strip_model_router_prefix(model: str) -> str: + """ + Strip the model_router prefix from model name. + + Examples: + - "model_router/gpt-4o" -> "gpt-4o" + - "model-router/gpt-4o" -> "gpt-4o" + - "gpt-4o" -> "gpt-4o" + + Args: + model: Model name potentially with model_router prefix + + Returns: + Model name without the prefix + """ + if "model_router/" in model: + return model.split("model_router/", 1)[1] + if "model-router/" in model: + return model.split("model-router/", 1)[1] + return model + + @staticmethod + def get_base_model(model: str) -> str: + """ + Get the base model name, stripping any Azure AI routing prefixes. + + Args: + model: Model name potentially with routing prefixes + + Returns: + Base model name + """ + # Strip model_router prefix if present + model = AzureFoundryModelInfo.strip_model_router_prefix(model) + return model + + @staticmethod + def get_azure_ai_config_for_model(model: str): + """ + Get the appropriate Azure AI config class for the given model. + + Routes to specialized configs based on model type: + - Model Router: AzureModelRouterConfig + - Claude models: AzureAnthropicConfig + - Default: AzureAIStudioConfig + + Args: + model: The model name + + Returns: + The appropriate config instance + """ + azure_ai_route = AzureFoundryModelInfo.get_azure_ai_route(model) + + if azure_ai_route == "model_router": + from litellm.llms.azure_ai.azure_model_router.transformation import ( + AzureModelRouterConfig, + ) + return AzureModelRouterConfig() + elif "claude" in model.lower(): + from litellm.llms.azure_ai.anthropic.transformation import ( + AzureAnthropicConfig, + ) + return AzureAnthropicConfig() + else: + from litellm.llms.azure_ai.chat.transformation import AzureAIStudioConfig + return AzureAIStudioConfig() def validate_environment( self, diff --git a/litellm/llms/azure_ai/cost_calculator.py b/litellm/llms/azure_ai/cost_calculator.py new file mode 100644 index 00000000000..999f94da182 --- /dev/null +++ b/litellm/llms/azure_ai/cost_calculator.py @@ -0,0 +1,121 @@ +""" +Azure AI cost calculation helper. +Handles Azure AI Foundry Model Router flat cost and other Azure AI specific pricing. +""" + +from typing import Optional, Tuple + +from litellm._logging import verbose_logger +from litellm.litellm_core_utils.llm_cost_calc.utils import generic_cost_per_token +from litellm.types.utils import Usage +from litellm.utils import get_model_info + + +def _is_azure_model_router(model: str) -> bool: + """ + Check if the model is Azure AI Foundry Model Router. + + Detects patterns like: + - "azure-model-router" + - "model-router" + - "model_router/" + - "model-router/" + + Args: + model: The model name + + Returns: + bool: True if this is a model router model + """ + model_lower = model.lower() + return ( + "model-router" in model_lower + or "model_router" in model_lower + or model_lower == "azure-model-router" + ) + + +def calculate_azure_model_router_flat_cost(model: str, prompt_tokens: int) -> float: + """ + Calculate the flat cost for Azure AI Foundry Model Router. + + Args: + model: The model name (should be a model router model) + prompt_tokens: Number of prompt tokens + + Returns: + float: The flat cost in USD, or 0.0 if not applicable + """ + if not _is_azure_model_router(model): + return 0.0 + + # Get the model router pricing from model_prices_and_context_window.json + # Use "model_router" as the key (without actual model name suffix) + model_info = get_model_info(model="model_router", custom_llm_provider="azure_ai") + router_flat_cost_per_token = model_info.get("input_cost_per_token", 0) + + if router_flat_cost_per_token > 0: + return prompt_tokens * router_flat_cost_per_token + + return 0.0 + + +def cost_per_token( + model: str, usage: Usage, response_time_ms: Optional[float] = 0.0 +) -> Tuple[float, float]: + """ + Calculate the cost per token for Azure AI models. + + For Azure AI Foundry Model Router: + - Adds a flat cost of $0.14 per million input tokens (from model_prices_and_context_window.json) + - Plus the cost of the actual model used (handled by generic_cost_per_token) + + Args: + model: str, the model name without provider prefix + usage: LiteLLM Usage block + response_time_ms: Optional response time in milliseconds + + Returns: + Tuple[float, float] - prompt_cost_in_usd, completion_cost_in_usd + + Raises: + ValueError: If the model is not found in the cost map and cost cannot be calculated + (except for Model Router models where we return just the routing flat cost) + """ + prompt_cost = 0.0 + completion_cost = 0.0 + + # Calculate base cost using generic cost calculator + # This may raise an exception if the model is not in the cost map + try: + prompt_cost, completion_cost = generic_cost_per_token( + model=model, + usage=usage, + custom_llm_provider="azure_ai", + ) + except Exception as e: + # For Model Router, the model name (e.g., "azure-model-router") may not be in the cost map + # because it's a routing service, not an actual model. In this case, we continue + # to calculate just the routing flat cost. + if not _is_azure_model_router(model): + # Re-raise for non-router models - they should have pricing defined + raise + verbose_logger.debug( + f"Azure AI Model Router: model '{model}' not in cost map, calculating routing flat cost only. Error: {e}" + ) + + # Add flat cost for Azure Model Router + # The flat cost is defined in model_prices_and_context_window.json for azure_ai/model_router + if _is_azure_model_router(model): + router_flat_cost = calculate_azure_model_router_flat_cost(model, usage.prompt_tokens) + + if router_flat_cost > 0: + verbose_logger.debug( + f"Azure AI Model Router flat cost: ${router_flat_cost:.6f} " + f"({usage.prompt_tokens} tokens × ${router_flat_cost / usage.prompt_tokens:.9f}/token)" + ) + + # Add flat cost to prompt cost + prompt_cost += router_flat_cost + + return prompt_cost, completion_cost diff --git a/litellm/llms/azure_ai/rerank/transformation.py b/litellm/llms/azure_ai/rerank/transformation.py index a47b6082c37..f577a42ed58 100644 --- a/litellm/llms/azure_ai/rerank/transformation.py +++ b/litellm/llms/azure_ai/rerank/transformation.py @@ -11,6 +11,7 @@ from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLogging from litellm.llms.cohere.rerank.transformation import CohereRerankConfig from litellm.secret_managers.main import get_secret_str from litellm.types.utils import RerankResponse +from litellm.utils import _add_path_to_api_base class AzureAIRerankConfig(CohereRerankConfig): @@ -28,9 +29,34 @@ class AzureAIRerankConfig(CohereRerankConfig): raise ValueError( "Azure AI API Base is required. api_base=None. Set in call or via `AZURE_AI_API_BASE` env var." ) - if not api_base.endswith("/v1/rerank"): - api_base = f"{api_base}/v1/rerank" - return api_base + original_url = httpx.URL(api_base) + if not original_url.is_absolute_url: + raise ValueError( + "Azure AI API Base must be an absolute URL including scheme (e.g. " + "'https://.services.ai.azure.com'). " + f"Got api_base={api_base!r}." + ) + normalized_path = original_url.path.rstrip("/") + + # Allow callers to pass either full v1/v2 rerank endpoints: + # - https://.services.ai.azure.com/v1/rerank + # - https://.services.ai.azure.com/providers/cohere/v2/rerank + if normalized_path.endswith("/v1/rerank") or normalized_path.endswith("/v2/rerank"): + return str(original_url.copy_with(path=normalized_path or "/")) + + # If callers pass just the version path (e.g. ".../v2" or ".../providers/cohere/v2"), append "/rerank" + if ( + normalized_path.endswith("/v1") + or normalized_path.endswith("/v2") + or normalized_path.endswith("/providers/cohere/v2") + ): + return _add_path_to_api_base( + api_base=str(original_url.copy_with(path=normalized_path or "/")), + ending_path="/rerank", + ) + + # Backwards compatible default: Azure AI rerank was originally exposed under /v1/rerank + return _add_path_to_api_base(api_base=api_base, ending_path="/v1/rerank") def validate_environment( self, diff --git a/litellm/llms/base_llm/chat/transformation.py b/litellm/llms/base_llm/chat/transformation.py index 41a1797cebe..ac209904e6e 100644 --- a/litellm/llms/base_llm/chat/transformation.py +++ b/litellm/llms/base_llm/chat/transformation.py @@ -437,3 +437,23 @@ class BaseConfig(ABC): By default, this is true for almost all providers. """ return True + + def calculate_additional_costs( + self, model: str, prompt_tokens: int, completion_tokens: int + ) -> Optional[dict]: + """ + Calculate any additional costs beyond standard token costs. + + This is used for provider-specific infrastructure costs, routing fees, etc. + + Args: + model: The model name + prompt_tokens: Number of prompt tokens + completion_tokens: Number of completion tokens + + Returns: + Optional dictionary with cost names and amounts, e.g.: + {"Infrastructure Fee": 0.001, "Routing Cost": 0.0005} + Returns None if no additional costs apply. + """ + return None diff --git a/litellm/llms/base_llm/vector_store/transformation.py b/litellm/llms/base_llm/vector_store/transformation.py index 89f2094d5df..935fd53c199 100644 --- a/litellm/llms/base_llm/vector_store/transformation.py +++ b/litellm/llms/base_llm/vector_store/transformation.py @@ -5,8 +5,8 @@ import httpx from litellm.types.router import GenericLiteLLMParams from litellm.types.vector_stores import ( - BaseVectorStoreAuthCredentials, VECTOR_STORE_OPENAI_PARAMS, + BaseVectorStoreAuthCredentials, VectorStoreCreateOptionalRequestParams, VectorStoreCreateResponse, VectorStoreIndexEndpoints, @@ -64,6 +64,30 @@ class BaseVectorStoreConfig: pass + async def atransform_search_vector_store_request( + self, + vector_store_id: str, + query: Union[str, List[str]], + vector_store_search_optional_params: VectorStoreSearchOptionalRequestParams, + api_base: str, + litellm_logging_obj: LiteLLMLoggingObj, + litellm_params: dict, + ) -> Tuple[str, Dict]: + """ + Optional async version of transform_search_vector_store_request. + If not implemented, the handler will fall back to the sync version. + Providers that need to make async calls (e.g., generating embeddings) should override this. + """ + # Default implementation: call the sync version + return self.transform_search_vector_store_request( + vector_store_id=vector_store_id, + query=query, + vector_store_search_optional_params=vector_store_search_optional_params, + api_base=api_base, + litellm_logging_obj=litellm_logging_obj, + litellm_params=litellm_params, + ) + @abstractmethod def transform_search_vector_store_response( self, response: httpx.Response, litellm_logging_obj: LiteLLMLoggingObj diff --git a/litellm/llms/bedrock/base_aws_llm.py b/litellm/llms/bedrock/base_aws_llm.py index 642d15fe3ed..1de1c40c438 100644 --- a/litellm/llms/bedrock/base_aws_llm.py +++ b/litellm/llms/bedrock/base_aws_llm.py @@ -1163,7 +1163,7 @@ class BaseAWSLLM: def _sign_request( self, - service_name: Literal["bedrock", "sagemaker", "bedrock-agentcore"], + service_name: Literal["bedrock", "sagemaker", "bedrock-agentcore", "s3vectors"], headers: dict, optional_params: dict, request_data: dict, diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index cb26a22edfa..f6d7e128580 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -76,6 +76,13 @@ BEDROCK_COMPUTER_USE_TOOLS = [ "text_editor_", ] +# Beta header patterns that are not supported by Bedrock Converse API +# These will be filtered out to prevent errors +UNSUPPORTED_BEDROCK_CONVERSE_BETA_PATTERNS = [ + "advanced-tool-use", # Bedrock Converse doesn't support advanced-tool-use beta headers + "prompt-caching", # Prompt caching not supported in Converse API +] + class AmazonConverseConfig(BaseConfig): """ @@ -298,6 +305,39 @@ class AmazonConverseConfig(BaseConfig): # Check if the model is specifically Nova Lite 2 return "nova-2-lite" in model_without_region + def _map_web_search_options( + self, + web_search_options: dict, + model: str + ) -> Optional[BedrockToolBlock]: + """ + Map web_search_options to Nova grounding systemTool. + + Nova grounding (web search) is only supported on Amazon Nova models. + Returns None for non-Nova models. + + Args: + web_search_options: The web_search_options dict from the request + model: The model identifier string + + Returns: + BedrockToolBlock with systemTool for Nova models, None otherwise + + Reference: https://docs.aws.amazon.com/nova/latest/userguide/grounding.html + """ + # Only Nova models support nova_grounding + # Model strings can be like: "amazon.nova-pro-v1:0", "us.amazon.nova-pro-v1:0", etc. + if "nova" not in model.lower(): + verbose_logger.debug( + f"web_search_options passed but model {model} is not a Nova model. " + "Nova grounding is only supported on Amazon Nova models." + ) + return None + + # Nova doesn't support search_context_size or user_location params + # (unlike Anthropic), so we just enable grounding with no options + return BedrockToolBlock(systemTool={"name": "nova_grounding"}) + def _transform_reasoning_effort_to_reasoning_config( self, reasoning_effort: str ) -> dict: @@ -438,6 +478,10 @@ class AmazonConverseConfig(BaseConfig): ): supported_params.append("tools") + # Nova models support web_search_options (mapped to nova_grounding systemTool) + if base_model.startswith("amazon.nova"): + supported_params.append("web_search_options") + if litellm.utils.supports_tool_choice( model=model, custom_llm_provider=self.custom_llm_provider ) or litellm.utils.supports_tool_choice( @@ -573,6 +617,37 @@ class AmazonConverseConfig(BaseConfig): return transformed_tools + def _filter_unsupported_beta_headers_for_bedrock( + self, model: str, beta_list: list + ) -> list: + """ + Remove beta headers that are not supported on Bedrock Converse API for the given model. + + Extended thinking beta headers are only supported on specific Claude 4+ models. + Some beta headers are universally unsupported on Bedrock Converse API. + + Args: + model: The model name + beta_list: The list of beta headers to filter + + Returns: + Filtered list of beta headers + """ + filtered_betas = [] + + # 1. Filter out beta headers that are universally unsupported on Bedrock Converse + for beta in beta_list: + should_keep = True + for unsupported_pattern in UNSUPPORTED_BEDROCK_CONVERSE_BETA_PATTERNS: + if unsupported_pattern in beta.lower(): + should_keep = False + break + + if should_keep: + filtered_betas.append(beta) + + return filtered_betas + def _separate_computer_use_tools( self, tools: List[OpenAIChatCompletionToolParam], model: str ) -> Tuple[ @@ -730,6 +805,15 @@ class AmazonConverseConfig(BaseConfig): if bedrock_tier in ("default", "flex", "priority"): optional_params["serviceTier"] = {"type": bedrock_tier} + if param == "web_search_options" and isinstance(value, dict): + # Note: we use `isinstance(value, dict)` instead of `value and isinstance(value, dict)` + # because empty dict {} is falsy but is a valid way to enable Nova grounding + grounding_tool = self._map_web_search_options(value, model) + if grounding_tool is not None: + optional_params = self._add_tools_to_optional_params( + optional_params=optional_params, tools=[grounding_tool] + ) + # Only update thinking tokens for non-GPT-OSS models and non-Nova-Lite-2 models # Nova Lite 2 handles token budgeting differently through reasoningConfig if "gpt-oss" not in model and not self._is_nova_lite_2_model(model): @@ -997,10 +1081,16 @@ class AmazonConverseConfig(BaseConfig): user_betas = get_anthropic_beta_from_headers(headers) anthropic_beta_list.extend(user_betas) - # Filter out tool search tools - Bedrock Converse API doesn't support them + # Separate pre-formatted Bedrock tools (e.g. systemTool from web_search_options) + # from OpenAI-format tools that need transformation via _bedrock_tools_pt filtered_tools = [] + pre_formatted_tools: List[ToolBlock] = [] if original_tools: for tool in original_tools: + # Already-formatted Bedrock tools (e.g. systemTool for Nova grounding) + if "systemTool" in tool: + pre_formatted_tools.append(tool) + continue tool_type = tool.get("type", "") if tool_type in ( "tool_search_tool_regex_20251119", @@ -1032,6 +1122,9 @@ class AmazonConverseConfig(BaseConfig): # No computer use tools, process all tools as regular tools bedrock_tools = _bedrock_tools_pt(filtered_tools) + # Append pre-formatted tools (systemTool etc.) after transformation + bedrock_tools.extend(pre_formatted_tools) + # Set anthropic_beta in additional_request_params if we have any beta features # ONLY apply to Anthropic/Claude models - other models (e.g., Qwen, Llama) don't support this field # and will error with "unknown variant anthropic_beta" if included @@ -1044,7 +1137,14 @@ class AmazonConverseConfig(BaseConfig): if beta not in seen: unique_betas.append(beta) seen.add(beta) - additional_request_params["anthropic_beta"] = unique_betas + + # Filter out unsupported beta headers for Bedrock Converse API + filtered_betas = self._filter_unsupported_beta_headers_for_bedrock( + model=model, + beta_list=unique_betas, + ) + + additional_request_params["anthropic_beta"] = filtered_betas return bedrock_tools, anthropic_beta_list @@ -1388,20 +1488,23 @@ class AmazonConverseConfig(BaseConfig): str, List[ChatCompletionToolCallChunk], Optional[List[BedrockConverseReasoningContentBlock]], + Optional[List[CitationsContentBlock]], ]: """ - Translate the message content to a string and a list of tool calls and reasoning content blocks + Translate the message content to a string and a list of tool calls, reasoning content blocks, and citations. Returns: content_str: str tools: List[ChatCompletionToolCallChunk] reasoningContentBlocks: Optional[List[BedrockConverseReasoningContentBlock]] + citationsContentBlocks: Optional[List[CitationsContentBlock]] - Citations from Nova grounding """ content_str = "" tools: List[ChatCompletionToolCallChunk] = [] reasoningContentBlocks: Optional[List[BedrockConverseReasoningContentBlock]] = ( None ) + citationsContentBlocks: Optional[List[CitationsContentBlock]] = None for idx, content in enumerate(content_blocks): """ - Content is either a tool response or text @@ -1446,10 +1549,15 @@ class AmazonConverseConfig(BaseConfig): if reasoningContentBlocks is None: reasoningContentBlocks = [] reasoningContentBlocks.append(content["reasoningContent"]) + # Handle Nova grounding citations content + if "citationsContent" in content: + if citationsContentBlocks is None: + citationsContentBlocks = [] + citationsContentBlocks.append(content["citationsContent"]) - return content_str, tools, reasoningContentBlocks + return content_str, tools, reasoningContentBlocks, citationsContentBlocks - def _transform_response( + def _transform_response( # noqa: PLR0915 self, model: str, response: httpx.Response, @@ -1525,18 +1633,27 @@ class AmazonConverseConfig(BaseConfig): reasoningContentBlocks: Optional[List[BedrockConverseReasoningContentBlock]] = ( None ) + citationsContentBlocks: Optional[List[CitationsContentBlock]] = None if message is not None: ( content_str, tools, reasoningContentBlocks, + citationsContentBlocks, ) = self._translate_message_content(message["content"]) + # Initialize provider_specific_fields if we have any special content blocks + provider_specific_fields: dict = {} + if reasoningContentBlocks is not None: + provider_specific_fields["reasoningContentBlocks"] = reasoningContentBlocks + if citationsContentBlocks is not None: + provider_specific_fields["citationsContent"] = citationsContentBlocks + + if provider_specific_fields: + chat_completion_message["provider_specific_fields"] = provider_specific_fields + if reasoningContentBlocks is not None: - chat_completion_message["provider_specific_fields"] = { - "reasoningContentBlocks": reasoningContentBlocks, - } chat_completion_message["reasoning_content"] = ( self._transform_reasoning_content(reasoningContentBlocks) ) diff --git a/litellm/llms/bedrock/chat/invoke_handler.py b/litellm/llms/bedrock/chat/invoke_handler.py index 17474fa022b..1c58a11eebe 100644 --- a/litellm/llms/bedrock/chat/invoke_handler.py +++ b/litellm/llms/bedrock/chat/invoke_handler.py @@ -1476,6 +1476,11 @@ class AWSEventStreamDecoder: reasoning_content = ( "" # set to non-empty string to ensure consistency with Anthropic ) + elif "citationsContent" in delta_obj: + # Handle Nova grounding citations in streaming responses + provider_specific_fields = { + "citationsContent": delta_obj["citationsContent"], + } return ( text, tool_use, diff --git a/litellm/llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py b/litellm/llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py index 53e08229799..c936b2cd23c 100644 --- a/litellm/llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py +++ b/litellm/llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py @@ -53,13 +53,26 @@ class AmazonAnthropicClaudeConfig(AmazonInvokeConfig, AnthropicConfig): model: str, drop_params: bool, ) -> dict: - return AnthropicConfig.map_openai_params( + # Force tool-based structured outputs for Bedrock Invoke + # (similar to VertexAI fix in #19201) + # Bedrock Invoke doesn't support output_format parameter + original_model = model + if "response_format" in non_default_params: + # Use a model name that forces tool-based approach + model = "claude-3-sonnet-20240229" + + optional_params = AnthropicConfig.map_openai_params( self, non_default_params, optional_params, model, drop_params, ) + + # Restore original model name + model = original_model + + return optional_params def transform_request( @@ -90,6 +103,8 @@ class AmazonAnthropicClaudeConfig(AmazonInvokeConfig, AnthropicConfig): _anthropic_request.pop("model", None) _anthropic_request.pop("stream", None) + # Bedrock Invoke doesn't support output_format parameter + _anthropic_request.pop("output_format", None) if "anthropic_version" not in _anthropic_request: _anthropic_request["anthropic_version"] = self.anthropic_version @@ -117,6 +132,26 @@ class AmazonAnthropicClaudeConfig(AmazonInvokeConfig, AnthropicConfig): if "opus-4" in model.lower() or "opus_4" in model.lower(): beta_set.add("tool-search-tool-2025-10-19") + # Filter out beta headers that Bedrock Invoke doesn't support + # AWS Bedrock only supports a specific whitelist of beta flags + # Reference: https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages-request-response.html + BEDROCK_SUPPORTED_BETAS = { + "computer-use-2024-10-22", # Legacy computer use + "computer-use-2025-01-24", # Current computer use (Claude 3.7 Sonnet) + "token-efficient-tools-2025-02-19", # Tool use (Claude 3.7+ and Claude 4+) + "interleaved-thinking-2025-05-14", # Interleaved thinking (Claude 4+) + "output-128k-2025-02-19", # 128K output tokens (Claude 3.7 Sonnet) + "dev-full-thinking-2025-05-14", # Developer mode for raw thinking (Claude 4+) + "context-1m-2025-08-07", # 1 million tokens (Claude Sonnet 4) + "context-management-2025-06-27", # Context management (Claude Sonnet/Haiku 4.5) + "effort-2025-11-24", # Effort parameter (Claude Opus 4.5) + "tool-search-tool-2025-10-19", # Tool search (Claude Opus 4.5) + "tool-examples-2025-10-29", # Tool use examples (Claude Opus 4.5) + } + + # Only keep beta headers that Bedrock supports + beta_set = {beta for beta in beta_set if beta in BEDROCK_SUPPORTED_BETAS} + if beta_set: _anthropic_request["anthropic_beta"] = list(beta_set) diff --git a/litellm/llms/bedrock/common_utils.py b/litellm/llms/bedrock/common_utils.py index 89b42f5e947..65d237bdbdf 100644 --- a/litellm/llms/bedrock/common_utils.py +++ b/litellm/llms/bedrock/common_utils.py @@ -797,7 +797,7 @@ class BedrockEventStreamDecoderBase: def get_anthropic_beta_from_headers(headers: dict) -> List[str]: """ Extract anthropic-beta header values and convert them to a list. - Supports comma-separated values from user headers. + Supports both JSON array format and comma-separated values from user headers. Used by both converse and invoke transformations for consistent handling of anthropic-beta headers that should be passed to AWS Bedrock. @@ -812,8 +812,25 @@ def get_anthropic_beta_from_headers(headers: dict) -> List[str]: if not anthropic_beta_header: return [] - # Split comma-separated values and strip whitespace - return [beta.strip() for beta in anthropic_beta_header.split(",")] + # If it's already a list, return it + if isinstance(anthropic_beta_header, list): + return anthropic_beta_header + + # Try to parse as JSON array first (e.g., '["interleaved-thinking-2025-05-14", "claude-code-20250219"]') + if isinstance(anthropic_beta_header, str): + anthropic_beta_header = anthropic_beta_header.strip() + if anthropic_beta_header.startswith("[") and anthropic_beta_header.endswith("]"): + try: + parsed = json.loads(anthropic_beta_header) + if isinstance(parsed, list): + return [str(beta).strip() for beta in parsed] + except json.JSONDecodeError: + pass # Fall through to comma-separated parsing + + # Fall back to comma-separated values + return [beta.strip() for beta in anthropic_beta_header.split(",")] + + return [] class CommonBatchFilesUtils: diff --git a/litellm/llms/bedrock/embed/cohere_transformation.py b/litellm/llms/bedrock/embed/cohere_transformation.py index 490cd71b793..d00cb74aae0 100644 --- a/litellm/llms/bedrock/embed/cohere_transformation.py +++ b/litellm/llms/bedrock/embed/cohere_transformation.py @@ -15,7 +15,7 @@ class BedrockCohereEmbeddingConfig: pass def get_supported_openai_params(self) -> List[str]: - return ["encoding_format"] + return ["encoding_format", "dimensions"] def map_openai_params( self, non_default_params: dict, optional_params: dict @@ -23,6 +23,8 @@ class BedrockCohereEmbeddingConfig: for k, v in non_default_params.items(): if k == "encoding_format": optional_params["embedding_types"] = v + elif k == "dimensions": + optional_params["output_dimension"] = v return optional_params def _is_v3_model(self, model: str) -> bool: diff --git a/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py b/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py index a7065caece2..b1c45ea83a2 100644 --- a/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py +++ b/litellm/llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py @@ -54,6 +54,7 @@ class AmazonAnthropicClaudeMessagesConfig( # These will be filtered out to prevent 400 "invalid beta flag" errors UNSUPPORTED_BEDROCK_INVOKE_BETA_PATTERNS = [ "advanced-tool-use", # Bedrock Invoke doesn't support advanced-tool-use beta headers + "prompt-caching-scope" ] def __init__(self, **kwargs): @@ -162,6 +163,49 @@ class AmazonAnthropicClaudeMessagesConfig( return any(pattern in model_lower for pattern in supported_patterns) + def _is_claude_opus_4_5(self, model: str) -> bool: + """ + Check if the model is Claude Opus 4.5. + + Args: + model: The model name + + Returns: + True if the model is Claude Opus 4.5 + """ + model_lower = model.lower() + opus_4_5_patterns = [ + "opus-4.5", "opus_4.5", "opus-4-5", "opus_4_5", + ] + return any(pattern in model_lower for pattern in opus_4_5_patterns) + + def _supports_tool_search_on_bedrock(self, model: str) -> bool: + """ + Check if the model supports tool search on Bedrock. + + On Amazon Bedrock, server-side tool search is supported on Claude Opus 4.5 + and Claude Sonnet 4.5 with the tool-search-tool-2025-10-19 beta header. + + Ref: https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool + + Args: + model: The model name + + Returns: + True if the model supports tool search on Bedrock + """ + model_lower = model.lower() + + # Supported models for tool search on Bedrock + supported_patterns = [ + # Opus 4.5 + "opus-4.5", "opus_4.5", "opus-4-5", "opus_4_5", + # Sonnet 4.5 + "sonnet-4.5", "sonnet_4.5", "sonnet-4-5", "sonnet_4_5", + ] + + return any(pattern in model_lower for pattern in supported_patterns) + def _filter_unsupported_beta_headers_for_bedrock( self, model: str, beta_set: set ) -> None: @@ -169,25 +213,33 @@ class AmazonAnthropicClaudeMessagesConfig( Remove beta headers that are not supported on Bedrock for the given model. Extended thinking beta headers are only supported on specific Claude 4+ models. - Advanced tool use headers are not supported on Bedrock Invoke API. + Advanced tool use headers are not supported on Bedrock Invoke API, but need to be + translated to Bedrock-specific headers for models that support tool search + (Claude Opus 4.5, Sonnet 4.5). This prevents 400 "invalid beta flag" errors on Bedrock. Note: Bedrock Invoke API fails with a 400 error when unsupported beta headers are sent, returning: {"message":"invalid beta flag"} + Translation for models supporting tool search (Opus 4.5, Sonnet 4.5): + - advanced-tool-use-2025-11-20 -> tool-search-tool-2025-10-19 + tool-examples-2025-10-29 + Args: model: The model name beta_set: The set of beta headers to filter in-place """ beta_headers_to_remove = set() + has_advanced_tool_use = False - # 1. Filter out beta headers that are universally unsupported on Bedrock Invoke + # 1. Filter out beta headers that are universally unsupported on Bedrock Invoke and track if advanced-tool-use header is present for beta in beta_set: for unsupported_pattern in self.UNSUPPORTED_BEDROCK_INVOKE_BETA_PATTERNS: if unsupported_pattern in beta.lower(): beta_headers_to_remove.add(beta) + has_advanced_tool_use = True break - + + # 2. Filter out extended thinking headers for models that don't support them extended_thinking_patterns = [ "extended-thinking", @@ -204,6 +256,14 @@ class AmazonAnthropicClaudeMessagesConfig( for beta in beta_headers_to_remove: beta_set.discard(beta) + # 3. Translate advanced-tool-use to Bedrock-specific headers for models that support tool search + # Ref: https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages-request-response.html + # Ref: https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool + if has_advanced_tool_use and self._supports_tool_search_on_bedrock(model): + beta_set.add("tool-search-tool-2025-10-19") + beta_set.add("tool-examples-2025-10-29") + + def _get_tool_search_beta_header_for_bedrock( self, model: str, @@ -256,7 +316,7 @@ class AmazonAnthropicClaudeMessagesConfig( Ref: https://aws.amazon.com/blogs/machine-learning/structured-data-response-with-amazon-bedrock-prompt-engineering-and-tool-use/ """ import json - + # Extract schema from output_format schema = output_format.get("schema") if not schema: diff --git a/litellm/llms/bedrock/realtime/handler.py b/litellm/llms/bedrock/realtime/handler.py new file mode 100644 index 00000000000..9b6a80f4a2f --- /dev/null +++ b/litellm/llms/bedrock/realtime/handler.py @@ -0,0 +1,307 @@ +""" +This file contains the handler for AWS Bedrock Nova Sonic realtime API. + +This uses aws_sdk_bedrock_runtime for bidirectional streaming with Nova Sonic. +""" + +import asyncio +import json +from typing import Any, Optional + +from litellm._logging import verbose_proxy_logger +from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLogging + +from ..base_aws_llm import BaseAWSLLM +from .transformation import BedrockRealtimeConfig + + +class BedrockRealtime(BaseAWSLLM): + """Handler for Bedrock Nova Sonic realtime speech-to-speech API.""" + + def __init__(self): + super().__init__() + + async def async_realtime( + self, + model: str, + websocket: Any, + logging_obj: LiteLLMLogging, + api_base: Optional[str] = None, + api_key: Optional[str] = None, + timeout: Optional[float] = None, + aws_region_name: Optional[str] = None, + aws_access_key_id: Optional[str] = None, + aws_secret_access_key: Optional[str] = None, + aws_session_token: Optional[str] = None, + aws_role_name: Optional[str] = None, + aws_session_name: Optional[str] = None, + aws_profile_name: Optional[str] = None, + aws_web_identity_token: Optional[str] = None, + aws_sts_endpoint: Optional[str] = None, + aws_bedrock_runtime_endpoint: Optional[str] = None, + aws_external_id: Optional[str] = None, + **kwargs, + ): + """ + Establish bidirectional streaming connection with Bedrock Nova Sonic. + + Args: + model: Model ID (e.g., 'amazon.nova-sonic-v1:0') + websocket: Client WebSocket connection + logging_obj: LiteLLM logging object + aws_region_name: AWS region + Various AWS authentication parameters + """ + try: + from aws_sdk_bedrock_runtime.client import ( + BedrockRuntimeClient, + InvokeModelWithBidirectionalStreamOperationInput, + ) + from aws_sdk_bedrock_runtime.config import Config + from smithy_aws_core.identity.environment import ( + EnvironmentCredentialsResolver, + ) + except ImportError: + raise ImportError( + "Missing aws_sdk_bedrock_runtime. Install with: pip install aws-sdk-bedrock-runtime" + ) + + # Get AWS region + if aws_region_name is None: + optional_params = { + "aws_region_name": aws_region_name, + } + aws_region_name = self._get_aws_region_name(optional_params, model) + + # Get endpoint URL + if api_base is not None: + endpoint_uri = api_base + elif aws_bedrock_runtime_endpoint is not None: + endpoint_uri = aws_bedrock_runtime_endpoint + else: + endpoint_uri = f"https://bedrock-runtime.{aws_region_name}.amazonaws.com" + + verbose_proxy_logger.debug( + f"Bedrock Realtime: Connecting to {endpoint_uri} with model {model}" + ) + + # Initialize Bedrock client with aws_sdk_bedrock_runtime + config = Config( + endpoint_uri=endpoint_uri, + region=aws_region_name, + aws_credentials_identity_resolver=EnvironmentCredentialsResolver(), + ) + bedrock_client = BedrockRuntimeClient(config=config) + + transformation_config = BedrockRealtimeConfig() + + try: + # Initialize the bidirectional stream + bedrock_stream = await bedrock_client.invoke_model_with_bidirectional_stream( + InvokeModelWithBidirectionalStreamOperationInput(model_id=model) + ) + + verbose_proxy_logger.debug( + "Bedrock Realtime: Bidirectional stream established" + ) + + # Track state for transformation + session_state = { + "current_output_item_id": None, + "current_response_id": None, + "current_conversation_id": None, + "current_delta_chunks": None, + "current_item_chunks": None, + "current_delta_type": None, + "session_configuration_request": None, + } + + # Create tasks for bidirectional forwarding + client_to_bedrock_task = asyncio.create_task( + self._forward_client_to_bedrock( + websocket, + bedrock_stream, + transformation_config, + model, + session_state, + ) + ) + + bedrock_to_client_task = asyncio.create_task( + self._forward_bedrock_to_client( + bedrock_stream, + websocket, + transformation_config, + model, + logging_obj, + session_state, + ) + ) + + # Wait for both tasks to complete + await asyncio.gather( + client_to_bedrock_task, + bedrock_to_client_task, + return_exceptions=True, + ) + + except Exception as e: + verbose_proxy_logger.exception( + f"Error in BedrockRealtime.async_realtime: {e}" + ) + try: + await websocket.close(code=1011, reason=f"Internal error: {str(e)}") + except Exception: + pass + raise + + async def _forward_client_to_bedrock( + self, + client_ws: Any, + bedrock_stream: Any, + transformation_config: BedrockRealtimeConfig, + model: str, + session_state: dict, + ): + """Forward messages from client WebSocket to Bedrock stream.""" + try: + from aws_sdk_bedrock_runtime.models import ( + BidirectionalInputPayloadPart, + InvokeModelWithBidirectionalStreamInputChunk, + ) + + while True: + # Receive message from client + message = await client_ws.receive_text() + verbose_proxy_logger.debug( + f"Bedrock Realtime: Received from client: {message[:200]}" + ) + + # Transform OpenAI format to Bedrock format + transformed_messages = transformation_config.transform_realtime_request( + message=message, + model=model, + session_configuration_request=session_state.get( + "session_configuration_request" + ), + ) + + # Send transformed messages to Bedrock + for bedrock_message in transformed_messages: + event = InvokeModelWithBidirectionalStreamInputChunk( + value=BidirectionalInputPayloadPart( + bytes_=bedrock_message.encode("utf-8") + ) + ) + await bedrock_stream.input_stream.send(event) + verbose_proxy_logger.debug( + f"Bedrock Realtime: Sent to Bedrock: {bedrock_message[:200]}" + ) + + except Exception as e: + verbose_proxy_logger.debug( + f"Client to Bedrock forwarding ended: {e}", exc_info=True + ) + # Close the Bedrock stream input + try: + await bedrock_stream.input_stream.close() + except Exception: + pass + + async def _forward_bedrock_to_client( + self, + bedrock_stream: Any, + client_ws: Any, + transformation_config: BedrockRealtimeConfig, + model: str, + logging_obj: LiteLLMLogging, + session_state: dict, + ): + """Forward messages from Bedrock stream to client WebSocket.""" + try: + while True: + # Receive from Bedrock + output = await bedrock_stream.await_output() + result = await output[1].receive() + + if result.value and result.value.bytes_: + bedrock_response = result.value.bytes_.decode("utf-8") + verbose_proxy_logger.debug( + f"Bedrock Realtime: Received from Bedrock: {bedrock_response[:200]}" + ) + + # Transform Bedrock format to OpenAI format + from litellm.types.realtime import RealtimeResponseTransformInput + + realtime_response_transform_input: RealtimeResponseTransformInput = { + "current_output_item_id": session_state.get( + "current_output_item_id" + ), + "current_response_id": session_state.get("current_response_id"), + "current_conversation_id": session_state.get( + "current_conversation_id" + ), + "current_delta_chunks": session_state.get( + "current_delta_chunks" + ), + "current_item_chunks": session_state.get("current_item_chunks"), + "current_delta_type": session_state.get("current_delta_type"), + "session_configuration_request": session_state.get( + "session_configuration_request" + ), + } + + transformed_response = ( + transformation_config.transform_realtime_response( + message=bedrock_response, + model=model, + logging_obj=logging_obj, + realtime_response_transform_input=realtime_response_transform_input, + ) + ) + + # Update session state + session_state.update( + { + "current_output_item_id": transformed_response.get( + "current_output_item_id" + ), + "current_response_id": transformed_response.get( + "current_response_id" + ), + "current_conversation_id": transformed_response.get( + "current_conversation_id" + ), + "current_delta_chunks": transformed_response.get( + "current_delta_chunks" + ), + "current_item_chunks": transformed_response.get( + "current_item_chunks" + ), + "current_delta_type": transformed_response.get( + "current_delta_type" + ), + "session_configuration_request": transformed_response.get( + "session_configuration_request" + ), + } + ) + + # Send transformed messages to client + openai_messages = transformed_response.get("response", []) + for openai_message in openai_messages: + message_json = json.dumps(openai_message) + await client_ws.send_text(message_json) + verbose_proxy_logger.debug( + f"Bedrock Realtime: Sent to client: {message_json[:200]}" + ) + + except Exception as e: + verbose_proxy_logger.debug( + f"Bedrock to client forwarding ended: {e}", exc_info=True + ) + # Close the client WebSocket + try: + await client_ws.close() + except Exception: + pass diff --git a/litellm/llms/bedrock/realtime/transformation.py b/litellm/llms/bedrock/realtime/transformation.py new file mode 100644 index 00000000000..1dde1b47fe3 --- /dev/null +++ b/litellm/llms/bedrock/realtime/transformation.py @@ -0,0 +1,1156 @@ +""" +This file contains the transformation logic for Bedrock Nova Sonic realtime API. + +Transforms between OpenAI Realtime API format and Bedrock Nova Sonic format. +""" + +import json +import uuid as uuid_lib +from typing import Any, List, Optional, Union + +from litellm._logging import verbose_logger +from litellm._uuid import uuid +from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj +from litellm.llms.base_llm.realtime.transformation import BaseRealtimeConfig +from litellm.types.llms.openai import ( + OpenAIRealtimeContentPartDone, + OpenAIRealtimeDoneEvent, + OpenAIRealtimeEvents, + OpenAIRealtimeOutputItemDone, + OpenAIRealtimeResponseAudioDone, + OpenAIRealtimeResponseContentPartAdded, + OpenAIRealtimeResponseDelta, + OpenAIRealtimeResponseDoneObject, + OpenAIRealtimeResponseTextDone, + OpenAIRealtimeStreamResponseBaseObject, + OpenAIRealtimeStreamResponseOutputItemAdded, + OpenAIRealtimeStreamSession, + OpenAIRealtimeStreamSessionEvents, +) +from litellm.types.realtime import ( + ALL_DELTA_TYPES, + RealtimeResponseTransformInput, + RealtimeResponseTypedDict, +) +from litellm.utils import get_empty_usage + + +class BedrockRealtimeConfig(BaseRealtimeConfig): + """Configuration for Bedrock Nova Sonic realtime transformations.""" + + def __init__(self): + # Track session state + self.prompt_name = str(uuid_lib.uuid4()) + self.content_name = str(uuid_lib.uuid4()) + self.audio_content_name = str(uuid_lib.uuid4()) + + # Default configuration values + # Inference configuration + self.max_tokens = 1024 + self.top_p = 0.9 + self.temperature = 0.7 + + # Audio output configuration + self.output_sample_rate_hertz = 24000 + self.output_sample_size_bits = 16 + self.output_channel_count = 1 + self.voice_id = "matthew" + self.output_encoding = "base64" + self.output_audio_type = "SPEECH" + self.output_media_type = "audio/lpcm" + + # Audio input configuration + self.input_sample_rate_hertz = 16000 + self.input_sample_size_bits = 16 + self.input_channel_count = 1 + self.input_encoding = "base64" + self.input_audio_type = "SPEECH" + self.input_media_type = "audio/lpcm" + + # Text configuration + self.text_media_type = "text/plain" + + def validate_environment( + self, headers: dict, model: str, api_key: Optional[str] = None + ) -> dict: + """Validate environment - no special validation needed for Bedrock.""" + return headers + + def get_complete_url( + self, api_base: Optional[str], model: str, api_key: Optional[str] = None + ) -> str: + """Get complete URL - handled by aws_sdk_bedrock_runtime.""" + return api_base or "" + + def requires_session_configuration(self) -> bool: + """Bedrock requires session configuration.""" + return True + + def session_configuration_request(self, model: str, tools: Optional[List[dict]] = None) -> str: + """ + Create initial session configuration for Bedrock Nova Sonic. + + Args: + model: Model ID + tools: Optional list of tool definitions + + Returns JSON string with session start and prompt start events. + """ + session_start = { + "event": { + "sessionStart": { + "inferenceConfiguration": { + "maxTokens": self.max_tokens, + "topP": self.top_p, + "temperature": self.temperature, + } + } + } + } + + prompt_start_config = { + "promptName": self.prompt_name, + "textOutputConfiguration": {"mediaType": self.text_media_type}, + "audioOutputConfiguration": { + "mediaType": self.output_media_type, + "sampleRateHertz": self.output_sample_rate_hertz, + "sampleSizeBits": self.output_sample_size_bits, + "channelCount": self.output_channel_count, + "voiceId": self.voice_id, + "encoding": self.output_encoding, + "audioType": self.output_audio_type, + }, + } + + # Add tool configuration if tools are provided + if tools: + prompt_start_config["toolUseOutputConfiguration"] = { + "mediaType": "application/json" + } + prompt_start_config["toolConfiguration"] = { + "tools": self._transform_tools_to_bedrock_format(tools) + } + + prompt_start = {"event": {"promptStart": prompt_start_config}} + + # Return as a marker that we've sent the configuration + return json.dumps( + {"session_start": session_start, "prompt_start": prompt_start} + ) + + def _transform_tools_to_bedrock_format(self, tools: List[dict]) -> List[dict]: + """ + Transform OpenAI tool format to Bedrock tool format. + + Args: + tools: List of OpenAI format tools + + Returns: + List of Bedrock format tools + """ + bedrock_tools = [] + for tool in tools: + if tool.get("type") == "function": + function = tool.get("function", {}) + bedrock_tool = { + "toolSpec": { + "name": function.get("name", ""), + "description": function.get("description", ""), + "inputSchema": { + "json": json.dumps(function.get("parameters", {})) + } + } + } + bedrock_tools.append(bedrock_tool) + return bedrock_tools + + def _map_audio_format_to_sample_rate(self, audio_format: str, is_output: bool = True) -> int: + """ + Map OpenAI audio format to sample rate. + + Args: + audio_format: OpenAI audio format (pcm16, g711_ulaw, g711_alaw) + is_output: Whether this is for output (True) or input (False) + + Returns: + Sample rate in Hz + """ + # OpenAI uses 24kHz for output and can vary for input + # Bedrock Nova Sonic uses 24kHz for output and 16kHz for input by default + if audio_format == "pcm16": + return 24000 if is_output else 16000 + elif audio_format in ["g711_ulaw", "g711_alaw"]: + return 8000 # G.711 typically uses 8kHz + return 24000 if is_output else 16000 + + def transform_session_update_event(self, json_message: dict) -> List[str]: + """ + Transform session.update event to Bedrock session configuration. + + Args: + json_message: OpenAI session.update message + + Returns: + List of Bedrock format messages (JSON strings) + """ + verbose_logger.debug("Handling session.update") + messages: List[str] = [] + + session_config = json_message.get("session", {}) + + # Update inference configuration from session if provided + if "max_response_output_tokens" in session_config: + self.max_tokens = session_config["max_response_output_tokens"] + if "temperature" in session_config: + self.temperature = session_config["temperature"] + + # Update audio output configuration from session if provided + if "voice" in session_config: + self.voice_id = session_config["voice"] + if "output_audio_format" in session_config: + output_format = session_config["output_audio_format"] + self.output_sample_rate_hertz = self._map_audio_format_to_sample_rate( + output_format, is_output=True + ) + + # Update audio input configuration from session if provided + if "input_audio_format" in session_config: + input_format = session_config["input_audio_format"] + self.input_sample_rate_hertz = self._map_audio_format_to_sample_rate( + input_format, is_output=False + ) + + # Allow direct override of sample rates if provided (custom extension) + if "output_sample_rate_hertz" in session_config: + self.output_sample_rate_hertz = session_config["output_sample_rate_hertz"] + if "input_sample_rate_hertz" in session_config: + self.input_sample_rate_hertz = session_config["input_sample_rate_hertz"] + + # Send session start + session_start = { + "event": { + "sessionStart": { + "inferenceConfiguration": { + "maxTokens": self.max_tokens, + "topP": self.top_p, + "temperature": self.temperature, + } + } + } + } + messages.append(json.dumps(session_start)) + + # Send prompt start + prompt_start_config = { + "promptName": self.prompt_name, + "textOutputConfiguration": {"mediaType": self.text_media_type}, + "audioOutputConfiguration": { + "mediaType": self.output_media_type, + "sampleRateHertz": self.output_sample_rate_hertz, + "sampleSizeBits": self.output_sample_size_bits, + "channelCount": self.output_channel_count, + "voiceId": self.voice_id, + "encoding": self.output_encoding, + "audioType": self.output_audio_type, + }, + } + + # Add tool configuration if tools are provided + tools = session_config.get("tools") + if tools: + prompt_start_config["toolUseOutputConfiguration"] = { + "mediaType": "application/json" + } + prompt_start_config["toolConfiguration"] = { + "tools": self._transform_tools_to_bedrock_format(tools) + } + + prompt_start = {"event": {"promptStart": prompt_start_config}} + messages.append(json.dumps(prompt_start)) + + # Send system prompt if provided + instructions = session_config.get("instructions") + if instructions: + text_content_name = str(uuid_lib.uuid4()) + + # Content start + text_content_start = { + "event": { + "contentStart": { + "promptName": self.prompt_name, + "contentName": text_content_name, + "type": "TEXT", + "interactive": False, + "role": "SYSTEM", + "textInputConfiguration": {"mediaType": self.text_media_type}, + } + } + } + messages.append(json.dumps(text_content_start)) + + # Text input + text_input = { + "event": { + "textInput": { + "promptName": self.prompt_name, + "contentName": text_content_name, + "content": instructions, + } + } + } + messages.append(json.dumps(text_input)) + + # Content end + text_content_end = { + "event": { + "contentEnd": { + "promptName": self.prompt_name, + "contentName": text_content_name, + } + } + } + messages.append(json.dumps(text_content_end)) + + return messages + + def transform_input_audio_buffer_append_event(self, json_message: dict) -> List[str]: + """ + Transform input_audio_buffer.append event to Bedrock audio input. + + Args: + json_message: OpenAI input_audio_buffer.append message + + Returns: + List of Bedrock format messages (JSON strings) + """ + verbose_logger.debug("Handling input_audio_buffer.append") + messages: List[str] = [] + + # Check if we need to start audio content + if not hasattr(self, "_audio_content_started"): + audio_content_start = { + "event": { + "contentStart": { + "promptName": self.prompt_name, + "contentName": self.audio_content_name, + "type": "AUDIO", + "interactive": True, + "role": "USER", + "audioInputConfiguration": { + "mediaType": self.input_media_type, + "sampleRateHertz": self.input_sample_rate_hertz, + "sampleSizeBits": self.input_sample_size_bits, + "channelCount": self.input_channel_count, + "audioType": self.input_audio_type, + "encoding": self.input_encoding, + }, + } + } + } + messages.append(json.dumps(audio_content_start)) + self._audio_content_started = True + + # Send audio chunk + audio_data = json_message.get("audio", "") + audio_event = { + "event": { + "audioInput": { + "promptName": self.prompt_name, + "contentName": self.audio_content_name, + "content": audio_data, + } + } + } + messages.append(json.dumps(audio_event)) + + return messages + + def transform_input_audio_buffer_commit_event(self, json_message: dict) -> List[str]: + """ + Transform input_audio_buffer.commit event to Bedrock audio content end. + + Args: + json_message: OpenAI input_audio_buffer.commit message + + Returns: + List of Bedrock format messages (JSON strings) + """ + verbose_logger.debug("Handling input_audio_buffer.commit") + messages: List[str] = [] + + if hasattr(self, "_audio_content_started"): + audio_content_end = { + "event": { + "contentEnd": { + "promptName": self.prompt_name, + "contentName": self.audio_content_name, + } + } + } + messages.append(json.dumps(audio_content_end)) + delattr(self, "_audio_content_started") + + return messages + + def transform_conversation_item_create_event(self, json_message: dict) -> List[str]: + """ + Transform conversation.item.create event to Bedrock text input or tool result. + + Args: + json_message: OpenAI conversation.item.create message + + Returns: + List of Bedrock format messages (JSON strings) + """ + verbose_logger.debug("Handling conversation.item.create") + messages: List[str] = [] + + item = json_message.get("item", {}) + item_type = item.get("type") + + # Handle tool result + if item_type == "function_call_output": + return self.transform_conversation_item_create_tool_result_event(json_message) + + # Handle regular message + if item_type == "message": + content = item.get("content", []) + for content_part in content: + if content_part.get("type") == "input_text": + text_content_name = str(uuid_lib.uuid4()) + + # Content start + text_content_start = { + "event": { + "contentStart": { + "promptName": self.prompt_name, + "contentName": text_content_name, + "type": "TEXT", + "interactive": True, + "role": "USER", + "textInputConfiguration": { + "mediaType": self.text_media_type + }, + } + } + } + messages.append(json.dumps(text_content_start)) + + # Text input + text_input = { + "event": { + "textInput": { + "promptName": self.prompt_name, + "contentName": text_content_name, + "content": content_part.get("text", ""), + } + } + } + messages.append(json.dumps(text_input)) + + # Content end + text_content_end = { + "event": { + "contentEnd": { + "promptName": self.prompt_name, + "contentName": text_content_name, + } + } + } + messages.append(json.dumps(text_content_end)) + + return messages + + def transform_response_create_event(self, json_message: dict) -> List[str]: + """ + Transform response.create event to Bedrock format. + + Args: + json_message: OpenAI response.create message + + Returns: + List of Bedrock format messages (JSON strings) + """ + verbose_logger.debug("Handling response.create") + # Bedrock starts generating automatically, no explicit trigger needed + return [] + + def transform_response_cancel_event(self, json_message: dict) -> List[str]: + """ + Transform response.cancel event to Bedrock format. + + Args: + json_message: OpenAI response.cancel message + + Returns: + List of Bedrock format messages (JSON strings) + """ + verbose_logger.debug("Handling response.cancel") + # Send interrupt signal if needed + return [] + + def transform_realtime_request( + self, + message: str, + model: str, + session_configuration_request: Optional[str] = None, + ) -> List[str]: + """ + Transform OpenAI realtime request to Bedrock Nova Sonic format. + + Args: + message: OpenAI format message (JSON string) + model: Model ID + session_configuration_request: Previous session config + + Returns: + List of Bedrock format messages (JSON strings) + """ + try: + json_message = json.loads(message) + except json.JSONDecodeError: + verbose_logger.warning(f"Invalid JSON message: {message[:200]}") + return [] + + message_type = json_message.get("type") + + # Route to appropriate transformation method + if message_type == "session.update": + return self.transform_session_update_event(json_message) + elif message_type == "input_audio_buffer.append": + return self.transform_input_audio_buffer_append_event(json_message) + elif message_type == "input_audio_buffer.commit": + return self.transform_input_audio_buffer_commit_event(json_message) + elif message_type == "conversation.item.create": + return self.transform_conversation_item_create_event(json_message) + elif message_type == "response.create": + return self.transform_response_create_event(json_message) + elif message_type == "response.cancel": + return self.transform_response_cancel_event(json_message) + else: + verbose_logger.warning(f"Unknown message type: {message_type}") + return [] + + def transform_session_start_event( + self, + event: dict, + model: str, + logging_obj: LiteLLMLoggingObj, + ) -> OpenAIRealtimeStreamSessionEvents: + """ + Transform Bedrock sessionStart event to OpenAI session.created. + + Args: + event: Bedrock sessionStart event + model: Model ID + logging_obj: Logging object + + Returns: + OpenAI session.created event + """ + verbose_logger.debug("Handling sessionStart") + + session = OpenAIRealtimeStreamSession( + id=logging_obj.litellm_trace_id, + modalities=["text", "audio"], + ) + if model is not None and isinstance(model, str): + session["model"] = model + + return OpenAIRealtimeStreamSessionEvents( + type="session.created", + session=session, + event_id=str(uuid.uuid4()), + ) + + def transform_content_start_event( + self, + event: dict, + current_response_id: Optional[str], + current_output_item_id: Optional[str], + current_conversation_id: Optional[str], + ) -> tuple[ + List[OpenAIRealtimeEvents], + Optional[str], + Optional[str], + Optional[str], + Optional[ALL_DELTA_TYPES], + ]: + """ + Transform Bedrock contentStart event to OpenAI response events. + + Args: + event: Bedrock contentStart event + current_response_id: Current response ID + current_output_item_id: Current output item ID + current_conversation_id: Current conversation ID + + Returns: + Tuple of (events, response_id, output_item_id, conversation_id, delta_type) + """ + content_start = event["contentStart"] + role = content_start.get("role") + + if role != "ASSISTANT": + return [], current_response_id, current_output_item_id, current_conversation_id, None + + verbose_logger.debug("Handling ASSISTANT contentStart") + + # Initialize IDs if needed + if not current_response_id: + current_response_id = f"resp_{uuid.uuid4()}" + if not current_output_item_id: + current_output_item_id = f"item_{uuid.uuid4()}" + if not current_conversation_id: + current_conversation_id = f"conv_{uuid.uuid4()}" + + # Determine content type + content_type = content_start.get("type", "TEXT") + current_delta_type: ALL_DELTA_TYPES = "text" if content_type == "TEXT" else "audio" + + returned_messages: List[OpenAIRealtimeEvents] = [] + + # Send response.created + response_created = OpenAIRealtimeStreamResponseBaseObject( + type="response.created", + event_id=f"event_{uuid.uuid4()}", + response={ + "object": "realtime.response", + "id": current_response_id, + "status": "in_progress", + "output": [], + "conversation_id": current_conversation_id, + }, + ) + returned_messages.append(response_created) + + # Send response.output_item.added + output_item_added = OpenAIRealtimeStreamResponseOutputItemAdded( + type="response.output_item.added", + response_id=current_response_id, + output_index=0, + item={ + "id": current_output_item_id, + "object": "realtime.item", + "type": "message", + "status": "in_progress", + "role": "assistant", + "content": [], + }, + ) + returned_messages.append(output_item_added) + + # Send response.content_part.added + content_part_added = OpenAIRealtimeResponseContentPartAdded( + type="response.content_part.added", + content_index=0, + output_index=0, + event_id=f"event_{uuid.uuid4()}", + item_id=current_output_item_id, + part=( + {"type": "text", "text": ""} + if current_delta_type == "text" + else {"type": "audio", "transcript": ""} + ), + response_id=current_response_id, + ) + returned_messages.append(content_part_added) + + return ( + returned_messages, + current_response_id, + current_output_item_id, + current_conversation_id, + current_delta_type, + ) + + def transform_text_output_event( + self, + event: dict, + current_output_item_id: Optional[str], + current_response_id: Optional[str], + current_delta_chunks: Optional[List[OpenAIRealtimeResponseDelta]], + ) -> tuple[List[OpenAIRealtimeEvents], Optional[List[OpenAIRealtimeResponseDelta]]]: + """ + Transform Bedrock textOutput event to OpenAI response.text.delta. + + Args: + event: Bedrock textOutput event + current_output_item_id: Current output item ID + current_response_id: Current response ID + current_delta_chunks: Current delta chunks + + Returns: + Tuple of (events, updated_delta_chunks) + """ + verbose_logger.debug("Handling textOutput") + text_content = event["textOutput"].get("content", "") + + if not current_output_item_id or not current_response_id: + return [], current_delta_chunks + + text_delta = OpenAIRealtimeResponseDelta( + type="response.text.delta", + content_index=0, + event_id=f"event_{uuid.uuid4()}", + item_id=current_output_item_id, + output_index=0, + response_id=current_response_id, + delta=text_content, + ) + + # Track delta chunks + if current_delta_chunks is None: + current_delta_chunks = [] + current_delta_chunks.append(text_delta) + + return [text_delta], current_delta_chunks + + def transform_audio_output_event( + self, + event: dict, + current_output_item_id: Optional[str], + current_response_id: Optional[str], + ) -> List[OpenAIRealtimeEvents]: + """ + Transform Bedrock audioOutput event to OpenAI response.audio.delta. + + Args: + event: Bedrock audioOutput event + current_output_item_id: Current output item ID + current_response_id: Current response ID + + Returns: + List of OpenAI events + """ + verbose_logger.debug("Handling audioOutput") + audio_content = event["audioOutput"].get("content", "") + + if not current_output_item_id or not current_response_id: + return [] + + audio_delta = OpenAIRealtimeResponseDelta( + type="response.audio.delta", + content_index=0, + event_id=f"event_{uuid.uuid4()}", + item_id=current_output_item_id, + output_index=0, + response_id=current_response_id, + delta=audio_content, + ) + + return [audio_delta] + + def transform_content_end_event( + self, + event: dict, + current_output_item_id: Optional[str], + current_response_id: Optional[str], + current_delta_type: Optional[str], + current_delta_chunks: Optional[List[OpenAIRealtimeResponseDelta]], + ) -> tuple[List[OpenAIRealtimeEvents], Optional[List[OpenAIRealtimeResponseDelta]]]: + """ + Transform Bedrock contentEnd event to OpenAI response done events. + + Args: + event: Bedrock contentEnd event + current_output_item_id: Current output item ID + current_response_id: Current response ID + current_delta_type: Current delta type (text or audio) + current_delta_chunks: Current delta chunks + + Returns: + Tuple of (events, reset_delta_chunks) + """ + content_end = event["contentEnd"] + verbose_logger.debug(f"Handling contentEnd: {content_end}") + + if not current_output_item_id or not current_response_id: + return [], current_delta_chunks + + returned_messages: List[OpenAIRealtimeEvents] = [] + + # Send appropriate done event based on type + if current_delta_type == "text": + # Accumulate text + accumulated_text = "" + if current_delta_chunks: + accumulated_text = "".join( + [chunk.get("delta", "") for chunk in current_delta_chunks] + ) + + text_done = OpenAIRealtimeResponseTextDone( + type="response.text.done", + content_index=0, + event_id=f"event_{uuid.uuid4()}", + item_id=current_output_item_id, + output_index=0, + response_id=current_response_id, + text=accumulated_text, + ) + returned_messages.append(text_done) + + # Send content_part.done + content_part_done = OpenAIRealtimeContentPartDone( + type="response.content_part.done", + content_index=0, + event_id=f"event_{uuid.uuid4()}", + item_id=current_output_item_id, + output_index=0, + part={"type": "text", "text": accumulated_text}, + response_id=current_response_id, + ) + returned_messages.append(content_part_done) + + elif current_delta_type == "audio": + audio_done = OpenAIRealtimeResponseAudioDone( + type="response.audio.done", + content_index=0, + event_id=f"event_{uuid.uuid4()}", + item_id=current_output_item_id, + output_index=0, + response_id=current_response_id, + ) + returned_messages.append(audio_done) + + # Send content_part.done + content_part_done = OpenAIRealtimeContentPartDone( + type="response.content_part.done", + content_index=0, + event_id=f"event_{uuid.uuid4()}", + item_id=current_output_item_id, + output_index=0, + part={"type": "audio", "transcript": ""}, + response_id=current_response_id, + ) + returned_messages.append(content_part_done) + + # Send output_item.done + output_item_done = OpenAIRealtimeOutputItemDone( + type="response.output_item.done", + event_id=f"event_{uuid.uuid4()}", + output_index=0, + response_id=current_response_id, + item={ + "id": current_output_item_id, + "object": "realtime.item", + "type": "message", + "status": "completed", + "role": "assistant", + "content": [], + }, + ) + returned_messages.append(output_item_done) + + # Reset delta chunks + return returned_messages, None + + def transform_prompt_end_event( + self, + event: dict, + current_response_id: Optional[str], + current_conversation_id: Optional[str], + ) -> tuple[List[OpenAIRealtimeEvents], Optional[str], Optional[str], Optional[ALL_DELTA_TYPES]]: + """ + Transform Bedrock promptEnd event to OpenAI response.done. + + Args: + event: Bedrock promptEnd event + current_response_id: Current response ID + current_conversation_id: Current conversation ID + + Returns: + Tuple of (events, reset_output_item_id, reset_response_id, reset_delta_type) + """ + verbose_logger.debug("Handling promptEnd") + + if not current_response_id or not current_conversation_id: + return [], None, None, None + + usage_obj = get_empty_usage() + response_done = OpenAIRealtimeDoneEvent( + type="response.done", + event_id=f"event_{uuid.uuid4()}", + response=OpenAIRealtimeResponseDoneObject( + object="realtime.response", + id=current_response_id, + status="completed", + output=[], + conversation_id=current_conversation_id, + usage={ + "prompt_tokens": usage_obj.prompt_tokens, + "completion_tokens": usage_obj.completion_tokens, + "total_tokens": usage_obj.total_tokens, + }, + ), + ) + + # Reset state for next response + return [response_done], None, None, None + + def transform_tool_use_event( + self, + event: dict, + current_output_item_id: Optional[str], + current_response_id: Optional[str], + ) -> tuple[List[OpenAIRealtimeEvents], str, str]: + """ + Transform Bedrock toolUse event to OpenAI format. + + Args: + event: Bedrock toolUse event + current_output_item_id: Current output item ID + current_response_id: Current response ID + + Returns: + Tuple of (events, tool_call_id, tool_name) for tracking + """ + verbose_logger.debug("Handling toolUse") + tool_use = event["toolUse"] + + if not current_output_item_id or not current_response_id: + return [], "", "" + + # Parse the tool input + tool_input = {} + if "input" in tool_use: + try: + tool_input = json.loads(tool_use["input"]) if isinstance(tool_use["input"], str) else tool_use["input"] + except json.JSONDecodeError: + tool_input = {} + + tool_call_id = tool_use.get("toolUseId", "") + tool_name = tool_use.get("toolName", "") + + # Create a function call arguments done event + # This is a custom event format that matches what clients expect + from typing import cast + function_call_event: dict[str, Any] = { + "type": "response.function_call_arguments.done", + "event_id": f"event_{uuid.uuid4()}", + "response_id": current_response_id, + "item_id": current_output_item_id, + "output_index": 0, + "call_id": tool_call_id, + "name": tool_name, + "arguments": json.dumps(tool_input), + } + + return [cast(OpenAIRealtimeEvents, function_call_event)], tool_call_id, tool_name + + def transform_conversation_item_create_tool_result_event(self, json_message: dict) -> List[str]: + """ + Transform conversation.item.create with tool result to Bedrock format. + + Args: + json_message: OpenAI conversation.item.create message with tool result + + Returns: + List of Bedrock format messages (JSON strings) + """ + verbose_logger.debug("Handling conversation.item.create for tool result") + messages: List[str] = [] + + item = json_message.get("item", {}) + if item.get("type") == "function_call_output": + tool_content_name = str(uuid_lib.uuid4()) + call_id = item.get("call_id", "") + output = item.get("output", "") + + # Content start for tool result + tool_content_start = { + "event": { + "contentStart": { + "promptName": self.prompt_name, + "contentName": tool_content_name, + "interactive": False, + "type": "TOOL", + "role": "TOOL", + "toolResultInputConfiguration": { + "toolUseId": call_id, + "type": "TEXT", + "textInputConfiguration": { + "mediaType": "text/plain" + } + } + } + } + } + messages.append(json.dumps(tool_content_start)) + + # Tool result + tool_result = { + "event": { + "toolResult": { + "promptName": self.prompt_name, + "contentName": tool_content_name, + "content": output if isinstance(output, str) else json.dumps(output) + } + } + } + messages.append(json.dumps(tool_result)) + + # Content end + tool_content_end = { + "event": { + "contentEnd": { + "promptName": self.prompt_name, + "contentName": tool_content_name, + } + } + } + messages.append(json.dumps(tool_content_end)) + + return messages + + def transform_realtime_response( + self, + message: Union[str, bytes], + model: str, + logging_obj: LiteLLMLoggingObj, + realtime_response_transform_input: RealtimeResponseTransformInput, + ) -> RealtimeResponseTypedDict: + """ + Transform Bedrock Nova Sonic response to OpenAI realtime format. + + Args: + message: Bedrock format message (JSON string) + model: Model ID + logging_obj: Logging object + realtime_response_transform_input: Current state + + Returns: + Transformed response with updated state + """ + try: + json_message = json.loads(message) + except json.JSONDecodeError: + message_preview = message[:200].decode('utf-8', errors='replace') if isinstance(message, bytes) else message[:200] + verbose_logger.warning(f"Invalid JSON message: {message_preview}") + return { + "response": [], + "current_output_item_id": realtime_response_transform_input.get( + "current_output_item_id" + ), + "current_response_id": realtime_response_transform_input.get( + "current_response_id" + ), + "current_delta_chunks": realtime_response_transform_input.get( + "current_delta_chunks" + ), + "current_conversation_id": realtime_response_transform_input.get( + "current_conversation_id" + ), + "current_item_chunks": realtime_response_transform_input.get( + "current_item_chunks" + ), + "current_delta_type": realtime_response_transform_input.get( + "current_delta_type" + ), + "session_configuration_request": realtime_response_transform_input.get( + "session_configuration_request" + ), + } + + # Extract state + current_output_item_id = realtime_response_transform_input.get( + "current_output_item_id" + ) + current_response_id = realtime_response_transform_input.get( + "current_response_id" + ) + current_conversation_id = realtime_response_transform_input.get( + "current_conversation_id" + ) + current_delta_chunks = realtime_response_transform_input.get( + "current_delta_chunks" + ) + current_delta_type = realtime_response_transform_input.get("current_delta_type") + session_configuration_request = realtime_response_transform_input.get( + "session_configuration_request" + ) + + returned_messages: List[OpenAIRealtimeEvents] = [] + + # Parse Bedrock event + event = json_message.get("event", {}) + + # Route to appropriate transformation method + if "sessionStart" in event: + session_created = self.transform_session_start_event( + event, model, logging_obj + ) + returned_messages.append(session_created) + session_configuration_request = json.dumps({"configured": True}) + + elif "contentStart" in event: + ( + events, + current_response_id, + current_output_item_id, + current_conversation_id, + current_delta_type, + ) = self.transform_content_start_event( + event, + current_response_id, + current_output_item_id, + current_conversation_id, + ) + returned_messages.extend(events) + + elif "textOutput" in event: + events, current_delta_chunks = self.transform_text_output_event( + event, + current_output_item_id, + current_response_id, + current_delta_chunks, + ) + returned_messages.extend(events) + + elif "audioOutput" in event: + events = self.transform_audio_output_event( + event, current_output_item_id, current_response_id + ) + returned_messages.extend(events) + + elif "contentEnd" in event: + events, current_delta_chunks = self.transform_content_end_event( + event, + current_output_item_id, + current_response_id, + current_delta_type, + current_delta_chunks, + ) + returned_messages.extend(events) + + elif "toolUse" in event: + events, tool_call_id, tool_name = self.transform_tool_use_event( + event, current_output_item_id, current_response_id + ) + returned_messages.extend(events) + # Store tool call info for potential use + verbose_logger.debug(f"Tool use event: {tool_name} (ID: {tool_call_id})") + + elif "promptEnd" in event: + ( + events, + current_output_item_id, + current_response_id, + current_delta_type, + ) = self.transform_prompt_end_event( + event, current_response_id, current_conversation_id + ) + returned_messages.extend(events) + + return { + "response": returned_messages, + "current_output_item_id": current_output_item_id, + "current_response_id": current_response_id, + "current_delta_chunks": current_delta_chunks, + "current_conversation_id": current_conversation_id, + "current_item_chunks": realtime_response_transform_input.get( + "current_item_chunks" + ), + "current_delta_type": current_delta_type, + "session_configuration_request": session_configuration_request, + } diff --git a/litellm/llms/cerebras/chat.py b/litellm/llms/cerebras/chat.py index 4e9c6811a77..9929e2ab9a2 100644 --- a/litellm/llms/cerebras/chat.py +++ b/litellm/llms/cerebras/chat.py @@ -7,6 +7,7 @@ this is OpenAI compatible - no translation needed / occurs from typing import Optional from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig +from litellm.utils import supports_reasoning class CerebrasConfig(OpenAIGPTConfig): @@ -24,6 +25,7 @@ class CerebrasConfig(OpenAIGPTConfig): tool_choice: Optional[str] = None tools: Optional[list] = None user: Optional[str] = None + reasoning_effort: Optional[str] = None def __init__( self, @@ -37,6 +39,7 @@ class CerebrasConfig(OpenAIGPTConfig): tool_choice: Optional[str] = None, tools: Optional[list] = None, user: Optional[str] = None, + reasoning_effort: Optional[str] = None, ) -> None: locals_ = locals().copy() for key, value in locals_.items(): @@ -53,7 +56,7 @@ class CerebrasConfig(OpenAIGPTConfig): """ - return [ + supported_params = [ "max_tokens", "max_completion_tokens", "response_format", @@ -67,6 +70,12 @@ class CerebrasConfig(OpenAIGPTConfig): "user", ] + # Only add reasoning_effort for models that support it + if supports_reasoning(model=model, custom_llm_provider="cerebras"): + supported_params.append("reasoning_effort") + + return supported_params + def map_openai_params( self, non_default_params: dict, diff --git a/litellm/llms/custom_httpx/http_handler.py b/litellm/llms/custom_httpx/http_handler.py index 4f86877a6c0..ac9dd5998e2 100644 --- a/litellm/llms/custom_httpx/http_handler.py +++ b/litellm/llms/custom_httpx/http_handler.py @@ -50,9 +50,21 @@ try: except Exception: version = "0.0.0" -headers = { - "User-Agent": f"litellm/{version}", -} +def get_default_headers() -> dict: + """ + Get default headers for HTTP requests. + + - Default: `User-Agent: litellm/{version}` + - Override: set `LITELLM_USER_AGENT` to fully override the header value. + """ + user_agent = os.environ.get("LITELLM_USER_AGENT") + if user_agent is not None: + return {"User-Agent": user_agent} + + return {"User-Agent": f"litellm/{version}"} + +# Initialize headers (User-Agent) +headers = get_default_headers() # https://www.python-httpx.org/advanced/timeouts _DEFAULT_TIMEOUT = httpx.Timeout(timeout=5.0, connect=5.0) @@ -371,13 +383,16 @@ class AsyncHTTPHandler: shared_session=shared_session, ) + # Get default headers (User-Agent, overridable via LITELLM_USER_AGENT) + default_headers = get_default_headers() + return httpx.AsyncClient( transport=transport, event_hooks=event_hooks, timeout=timeout, verify=ssl_config, cert=cert, - headers=headers, + headers=default_headers, follow_redirects=True, ) @@ -899,6 +914,9 @@ class HTTPHandler: # /path/to/client.pem cert = os.getenv("SSL_CERTIFICATE", litellm.ssl_certificate) + # Get default headers (User-Agent, overridable via LITELLM_USER_AGENT) + default_headers = get_default_headers() if not disable_default_headers else None + if client is None: transport = self._create_sync_transport() @@ -908,7 +926,7 @@ class HTTPHandler: timeout=timeout, verify=ssl_config, cert=cert, - headers=headers if not disable_default_headers else None, + headers=default_headers, follow_redirects=True, ) else: diff --git a/litellm/llms/custom_httpx/httpx_handler.py b/litellm/llms/custom_httpx/httpx_handler.py index 6f684ba01c2..491cd97f7db 100644 --- a/litellm/llms/custom_httpx/httpx_handler.py +++ b/litellm/llms/custom_httpx/httpx_handler.py @@ -1,3 +1,4 @@ +import os from typing import Optional, Union import httpx @@ -7,13 +8,22 @@ try: except Exception: version = "0.0.0" -headers = { - "User-Agent": f"litellm/{version}", -} +def get_default_headers() -> dict: + """ + Get default headers for HTTP requests. + - Default: `User-Agent: litellm/{version}` + - Override: set `LITELLM_USER_AGENT` to fully override the header value. + """ + user_agent = os.environ.get("LITELLM_USER_AGENT") + if user_agent is not None: + return {"User-Agent": user_agent} + + return {"User-Agent": f"litellm/{version}"} class HTTPHandler: def __init__(self, concurrent_limit=1000): + headers = get_default_headers() # Create a client with a connection pool self.client = httpx.AsyncClient( limits=httpx.Limits( diff --git a/litellm/llms/custom_httpx/llm_http_handler.py b/litellm/llms/custom_httpx/llm_http_handler.py index 6a87967c3aa..d2ea7e872a2 100644 --- a/litellm/llms/custom_httpx/llm_http_handler.py +++ b/litellm/llms/custom_httpx/llm_http_handler.py @@ -7033,17 +7033,31 @@ class BaseLLMHTTPHandler: litellm_params=dict(litellm_params), ) - ( - url, - request_body, - ) = vector_store_provider_config.transform_search_vector_store_request( - vector_store_id=vector_store_id, - query=query, - vector_store_search_optional_params=vector_store_search_optional_params, - api_base=api_base, - litellm_logging_obj=logging_obj, - litellm_params=dict(litellm_params), - ) + # Check if provider has async transform method + if hasattr(vector_store_provider_config, "atransform_search_vector_store_request"): + ( + url, + request_body, + ) = await vector_store_provider_config.atransform_search_vector_store_request( + vector_store_id=vector_store_id, + query=query, + vector_store_search_optional_params=vector_store_search_optional_params, + api_base=api_base, + litellm_logging_obj=logging_obj, + litellm_params=dict(litellm_params), + ) + else: + ( + url, + request_body, + ) = vector_store_provider_config.transform_search_vector_store_request( + vector_store_id=vector_store_id, + query=query, + vector_store_search_optional_params=vector_store_search_optional_params, + api_base=api_base, + litellm_logging_obj=logging_obj, + litellm_params=dict(litellm_params), + ) all_optional_params: Dict[str, Any] = dict(litellm_params) all_optional_params.update(vector_store_search_optional_params or {}) headers, signed_json_body = vector_store_provider_config.sign_request( diff --git a/litellm/llms/gemini/chat/transformation.py b/litellm/llms/gemini/chat/transformation.py index f6d075392b2..d5a5ab667a6 100644 --- a/litellm/llms/gemini/chat/transformation.py +++ b/litellm/llms/gemini/chat/transformation.py @@ -92,7 +92,7 @@ class GoogleAIStudioGeminiConfig(VertexGeminiConfig): "parallel_tool_calls", "web_search_options", ] - if supports_reasoning(model): + if supports_reasoning(model, custom_llm_provider="gemini"): supported_params.append("reasoning_effort") supported_params.append("thinking") if self.is_model_gemini_audio_model(model): diff --git a/litellm/llms/gemini/files/transformation.py b/litellm/llms/gemini/files/transformation.py index d9ebf69a97a..37f1376c2b1 100644 --- a/litellm/llms/gemini/files/transformation.py +++ b/litellm/llms/gemini/files/transformation.py @@ -4,7 +4,7 @@ Supports writing files to Google AI Studio Files API. For vertex ai, check out the vertex_ai/files/handler.py file. """ import time -from typing import List, Optional +from typing import Any, List, Literal, Optional import httpx from openai.types.file_deleted import FileDeleted @@ -17,6 +17,7 @@ from litellm.llms.base_llm.files.transformation import ( ) from litellm.types.llms.gemini import GeminiCreateFilesResponseObject from litellm.types.llms.openai import ( + AllMessageValues, CreateFileRequest, HttpxBinaryResponseContent, OpenAICreateFileRequestOptionalParams, @@ -35,6 +36,27 @@ class GoogleAIStudioFilesHandler(GeminiModelInfo, BaseFilesConfig): def custom_llm_provider(self) -> LlmProviders: return LlmProviders.GEMINI + def validate_environment( + self, + headers: dict[Any, Any], + model: str, + messages: List[AllMessageValues], + optional_params: dict[Any, Any], + litellm_params: dict[Any, Any], + api_key: Optional[str] = None, + api_base: Optional[str] = None, + ) -> dict[Any, Any]: + """ + Validate environment and add Gemini API key to headers. + Google AI Studio uses x-goog-api-key header for authentication. + """ + resolved_api_key = self.get_api_key(api_key) + if not resolved_api_key: + raise ValueError("GEMINI_API_KEY is required for Google AI Studio file operations") + + headers["x-goog-api-key"] = resolved_api_key + return headers + def get_complete_url( self, api_base: Optional[str], @@ -56,10 +78,12 @@ class GoogleAIStudioFilesHandler(GeminiModelInfo, BaseFilesConfig): if not api_base: raise ValueError("api_base is required") - if not api_key: + # Get API key from multiple sources + final_api_key = api_key or litellm_params.get("api_key") or self.get_api_key() + if not final_api_key: raise ValueError("api_key is required") - url = "{}/{}?key={}".format(api_base, endpoint, api_key) + url = "{}/{}?key={}".format(api_base, endpoint, final_api_key) return url def get_supported_openai_params( @@ -180,7 +204,25 @@ class GoogleAIStudioFilesHandler(GeminiModelInfo, BaseFilesConfig): optional_params: dict, litellm_params: dict, ) -> tuple[str, dict]: - raise NotImplementedError("GoogleAIStudioFilesHandler does not support file retrieval") + """ + Get the URL to retrieve a file from Google AI Studio. + + We expect file_id to be the URI (e.g. https://generativelanguage.googleapis.com/v1beta/files/...) + as returned by the upload response. + """ + api_key = litellm_params.get("api_key") + if not api_key: + raise ValueError("api_key is required") + + if file_id.startswith("http"): + url = "{}?key={}".format(file_id, api_key) + else: + # Fallback for just file name (files/...) + api_base = self.get_api_base(litellm_params.get("api_base")) or "https://generativelanguage.googleapis.com" + api_base = api_base.rstrip("/") + url = "{}/v1beta/{}?key={}".format(api_base, file_id, api_key) + + return url, {"Content-Type": "application/json"} def transform_retrieve_file_response( self, @@ -188,7 +230,42 @@ class GoogleAIStudioFilesHandler(GeminiModelInfo, BaseFilesConfig): logging_obj: LiteLLMLoggingObj, litellm_params: dict, ) -> OpenAIFileObject: - raise NotImplementedError("GoogleAIStudioFilesHandler does not support file retrieval") + """ + Transform Gemini's file retrieval response into OpenAI-style FileObject + """ + try: + response_json = raw_response.json() + + # Map Gemini state to OpenAI status + gemini_state = response_json.get("state", "STATE_UNSPECIFIED") + # Explicitly type status as the Literal union + if gemini_state == "ACTIVE": + status: Literal["uploaded", "processed", "error"] = "processed" + elif gemini_state == "FAILED": + status = "error" + else: + status = "uploaded" + + return OpenAIFileObject( + id=response_json.get("uri", ""), + bytes=int(response_json.get("sizeBytes", 0)), + created_at=int( + time.mktime( + time.strptime( + response_json["createTime"].replace("Z", "+00:00"), + "%Y-%m-%dT%H:%M:%S.%f%z", + ) + ) + ), + filename=response_json.get("displayName", ""), + object="file", + purpose="user_data", + status=status, + status_details=str(response_json.get("error", "")) if gemini_state == "FAILED" else None, + ) + except Exception as e: + verbose_logger.exception(f"Error parsing file retrieve response: {str(e)}") + raise ValueError(f"Error parsing file retrieve response: {str(e)}") def transform_delete_file_request( self, @@ -196,7 +273,41 @@ class GoogleAIStudioFilesHandler(GeminiModelInfo, BaseFilesConfig): optional_params: dict, litellm_params: dict, ) -> tuple[str, dict]: - raise NotImplementedError("GoogleAIStudioFilesHandler does not support file deletion") + """ + Transform delete file request for Google AI Studio. + + Args: + file_id: The file URI (e.g., "files/abc123" or full URI) + optional_params: Optional parameters + litellm_params: LiteLLM parameters containing api_key + + Returns: + tuple[str, dict]: (url, params) for the DELETE request + """ + api_base = self.get_api_base(litellm_params.get("api_base")) + if not api_base: + raise ValueError("api_base is required") + + # Get API key from multiple sources (same pattern as get_complete_url) + api_key = litellm_params.get("api_key") or self.get_api_key() + if not api_key: + raise ValueError("api_key is required") + + # Extract file name from URI if full URI is provided + # file_id could be "files/abc123" or "https://generativelanguage.googleapis.com/v1beta/files/abc123" + if file_id.startswith("http"): + # Extract the file path from full URI + file_name = file_id.split("/v1beta/")[-1] + else: + file_name = file_id + + # Construct the delete URL + url = f"{api_base}/v1beta/{file_name}" + + # Add API key as header (Google AI Studio uses x-goog-api-key header) + params: dict = {} + + return url, params def transform_delete_file_response( self, @@ -204,7 +315,34 @@ class GoogleAIStudioFilesHandler(GeminiModelInfo, BaseFilesConfig): logging_obj: LiteLLMLoggingObj, litellm_params: dict, ) -> FileDeleted: - raise NotImplementedError("GoogleAIStudioFilesHandler does not support file deletion") + """ + Transform Gemini's file delete response into OpenAI-style FileDeleted. + + Google AI Studio returns an empty JSON object {} on successful deletion. + """ + try: + # Google AI Studio returns {} on successful deletion + if raw_response.status_code == 200: + # Extract file ID from the request URL if possible + file_id = "deleted" + if hasattr(raw_response, "request") and raw_response.request: + url = str(raw_response.request.url) + if "/files/" in url: + file_id = url.split("/files/")[-1].split("?")[0] + # Add the files/ prefix if not present + if not file_id.startswith("files/"): + file_id = f"files/{file_id}" + + return FileDeleted( + id=file_id, + deleted=True, + object="file" + ) + else: + raise ValueError(f"Failed to delete file: {raw_response.text}") + except Exception as e: + verbose_logger.exception(f"Error parsing file delete response: {str(e)}") + raise ValueError(f"Error parsing file delete response: {str(e)}") def transform_list_files_request( self, diff --git a/litellm/llms/gemini/image_edit/transformation.py b/litellm/llms/gemini/image_edit/transformation.py index 16541138217..c3ea63ad43b 100644 --- a/litellm/llms/gemini/image_edit/transformation.py +++ b/litellm/llms/gemini/image_edit/transformation.py @@ -106,7 +106,10 @@ class GeminiImageEditConfig(BaseImageEditConfig): generation_config: Dict[str, Any] = {} if "aspectRatio" in image_edit_optional_request_params: - generation_config["aspectRatio"] = image_edit_optional_request_params[ + # Move aspectRatio into imageConfig inside generationConfig + if "imageConfig" not in generation_config: + generation_config["imageConfig"] = {} + generation_config["imageConfig"]["aspectRatio"] = image_edit_optional_request_params[ "aspectRatio" ] diff --git a/litellm/llms/gemini/image_generation/transformation.py b/litellm/llms/gemini/image_generation/transformation.py index 63b835df9d0..73aef15e4c7 100644 --- a/litellm/llms/gemini/image_generation/transformation.py +++ b/litellm/llms/gemini/image_generation/transformation.py @@ -255,9 +255,11 @@ class GoogleImageGenConfig(BaseImageGenerationConfig): if "inlineData" in part: inline_data = part["inlineData"] if "data" in inline_data: + thought_sig = part.get("thoughtSignature") model_response.data.append(ImageObject( b64_json=inline_data["data"], url=None, + provider_specific_fields={"thought_signature": thought_sig} if thought_sig else None, )) # Extract usage metadata for Gemini models diff --git a/litellm/llms/gigachat/chat/transformation.py b/litellm/llms/gigachat/chat/transformation.py index 90cf67da6b2..ba14de1f65d 100644 --- a/litellm/llms/gigachat/chat/transformation.py +++ b/litellm/llms/gigachat/chat/transformation.py @@ -31,6 +31,16 @@ else: GIGACHAT_BASE_URL = "https://gigachat.devices.sberbank.ru/api/v1" +def is_valid_json(value: str) -> bool: + """Checks whether the value passed is a valid serialized JSON string""" + try: + json.loads(value) + except json.JSONDecodeError: + return False + else: + return True + + class GigaChatError(BaseLLMException): """GigaChat API error.""" @@ -101,7 +111,11 @@ class GigaChatConfig(BaseConfig): Set up headers with OAuth token. """ # Get access token - credentials = api_key or get_secret_str("GIGACHAT_CREDENTIALS") or get_secret_str("GIGACHAT_API_KEY") + credentials = ( + api_key + or get_secret_str("GIGACHAT_CREDENTIALS") + or get_secret_str("GIGACHAT_API_KEY") + ) access_token = get_access_token(credentials=credentials) # Store credentials for image uploads @@ -193,11 +207,13 @@ class GigaChatConfig(BaseConfig): for tool in tools: if tool.get("type") == "function": func = tool.get("function", {}) - functions.append({ - "name": func.get("name", ""), - "description": func.get("description", ""), - "parameters": func.get("parameters", {}), - }) + functions.append( + { + "name": func.get("name", ""), + "description": func.get("description", ""), + "parameters": func.get("parameters", {}), + } + ) return functions def _map_tool_choice( @@ -281,8 +297,14 @@ class GigaChatConfig(BaseConfig): } # Add optional params - for key in ["temperature", "top_p", "max_tokens", "stream", - "repetition_penalty", "profanity_check"]: + for key in [ + "temperature", + "top_p", + "max_tokens", + "stream", + "repetition_penalty", + "profanity_check", + ]: if key in optional_params: request_data[key] = optional_params[key] @@ -314,7 +336,7 @@ class GigaChatConfig(BaseConfig): elif role == "tool": message["role"] = "function" content = message.get("content", "") - if not isinstance(content, str): + if not isinstance(content, str) or not is_valid_json(content): message["content"] = json.dumps(content, ensure_ascii=False) # Handle None content @@ -441,14 +463,16 @@ class GigaChatConfig(BaseConfig): # Convert to tool_calls format if isinstance(args, dict): args = json.dumps(args, ensure_ascii=False) - message_data["tool_calls"] = [{ - "id": f"call_{uuid.uuid4().hex[:24]}", - "type": "function", - "function": { - "name": func_call.get("name", ""), - "arguments": args, + message_data["tool_calls"] = [ + { + "id": f"call_{uuid.uuid4().hex[:24]}", + "type": "function", + "function": { + "name": func_call.get("name", ""), + "arguments": args, + }, } - }] + ] message_data.pop("function_call", None) finish_reason = "tool_calls" diff --git a/litellm/llms/groq/chat/transformation.py b/litellm/llms/groq/chat/transformation.py index a75ecd8cc7b..34ea7b03dd9 100644 --- a/litellm/llms/groq/chat/transformation.py +++ b/litellm/llms/groq/chat/transformation.py @@ -323,4 +323,12 @@ class GroqChatCompletionStreamingHandler(OpenAIChatCompletionStreamingHandler): status_code=error.get("code"), message=error.get("message"), body=error ) + # Map Groq's 'reasoning' field to LiteLLM's 'reasoning_content' field + # Groq returns delta.reasoning, but LiteLLM expects delta.reasoning_content + choices = chunk.get("choices", []) + for choice in choices: + delta = choice.get("delta", {}) + if "reasoning" in delta: + delta["reasoning_content"] = delta.pop("reasoning") + return super().chunk_parser(chunk) diff --git a/litellm/llms/hosted_vllm/chat/transformation.py b/litellm/llms/hosted_vllm/chat/transformation.py index 1d21490ea31..e955800b947 100644 --- a/litellm/llms/hosted_vllm/chat/transformation.py +++ b/litellm/llms/hosted_vllm/chat/transformation.py @@ -23,7 +23,7 @@ from ...openai.chat.gpt_transformation import OpenAIGPTConfig class HostedVLLMChatConfig(OpenAIGPTConfig): def get_supported_openai_params(self, model: str) -> List[str]: params = super().get_supported_openai_params(model) - params.append("reasoning_effort") + params.extend(["reasoning_effort", "thinking"]) return params def map_openai_params( @@ -41,6 +41,27 @@ class HostedVLLMChatConfig(OpenAIGPTConfig): _tools = _remove_strict_from_schema(_tools) if _tools is not None: non_default_params["tools"] = _tools + + # Handle thinking parameter - convert Anthropic-style to OpenAI-style reasoning_effort + # vLLM is OpenAI-compatible, so it understands reasoning_effort, not thinking + # Reference: https://github.com/BerriAI/litellm/issues/19761 + thinking = non_default_params.pop("thinking", None) + if thinking is not None and isinstance(thinking, dict): + if thinking.get("type") == "enabled": + # Only convert if reasoning_effort not already set + if "reasoning_effort" not in non_default_params: + budget_tokens = thinking.get("budget_tokens", 0) + # Map budget_tokens to reasoning_effort level + # Same logic as Anthropic adapter (translate_anthropic_thinking_to_reasoning_effort) + if budget_tokens >= 10000: + non_default_params["reasoning_effort"] = "high" + elif budget_tokens >= 5000: + non_default_params["reasoning_effort"] = "medium" + elif budget_tokens >= 2000: + non_default_params["reasoning_effort"] = "low" + else: + non_default_params["reasoning_effort"] = "minimal" + return super().map_openai_params( non_default_params, optional_params, model, drop_params ) diff --git a/litellm/llms/hosted_vllm/embedding/transformation.py b/litellm/llms/hosted_vllm/embedding/transformation.py new file mode 100644 index 00000000000..9c3e8c6c7cc --- /dev/null +++ b/litellm/llms/hosted_vllm/embedding/transformation.py @@ -0,0 +1,180 @@ +""" +Hosted VLLM Embedding API Configuration. + +This module provides the configuration for hosted VLLM's Embedding API. +VLLM is OpenAI-compatible and supports embeddings via the /v1/embeddings endpoint. + +Docs: https://docs.vllm.ai/en/latest/serving/openai_compatible_server.html +""" + +from typing import TYPE_CHECKING, Any, List, Optional, Union + +import httpx + +from litellm.llms.base_llm.chat.transformation import BaseLLMException +from litellm.llms.base_llm.embedding.transformation import BaseEmbeddingConfig +from litellm.secret_managers.main import get_secret_str +from litellm.types.llms.openai import AllEmbeddingInputValues, AllMessageValues +from litellm.types.utils import EmbeddingResponse +from litellm.utils import convert_to_model_response_object + +if TYPE_CHECKING: + from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj + + LiteLLMLoggingObj = _LiteLLMLoggingObj +else: + LiteLLMLoggingObj = Any + + +class HostedVLLMEmbeddingError(BaseLLMException): + """Exception class for Hosted VLLM Embedding errors.""" + + pass + + +class HostedVLLMEmbeddingConfig(BaseEmbeddingConfig): + """ + Configuration for Hosted VLLM's Embedding API. + + Reference: https://docs.vllm.ai/en/latest/serving/openai_compatible_server.html + """ + + def validate_environment( + self, + headers: dict, + model: str, + messages: List[AllMessageValues], + optional_params: dict, + litellm_params: dict, + api_key: Optional[str] = None, + api_base: Optional[str] = None, + ) -> dict: + """ + Validate environment and set up headers for Hosted VLLM API. + """ + if api_key is None: + api_key = get_secret_str("HOSTED_VLLM_API_KEY") or "fake-api-key" + + default_headers = { + "Content-Type": "application/json", + } + + # Only add Authorization header if api_key is not "fake-api-key" + if api_key and api_key != "fake-api-key": + default_headers["Authorization"] = f"Bearer {api_key}" + + # Merge with existing headers (user's headers take priority) + return {**default_headers, **headers} + + def get_complete_url( + self, + api_base: Optional[str], + api_key: Optional[str], + model: str, + optional_params: dict, + litellm_params: dict, + stream: Optional[bool] = None, + ) -> str: + """ + Get the complete URL for Hosted VLLM Embedding API endpoint. + """ + if api_base is None: + api_base = get_secret_str("HOSTED_VLLM_API_BASE") + if api_base is None: + raise ValueError("api_base is required for hosted_vllm embeddings") + + # Remove trailing slashes + api_base = api_base.rstrip("/") + + # Ensure the URL ends with /embeddings + if not api_base.endswith("/embeddings"): + api_base = f"{api_base}/embeddings" + + return api_base + + def transform_embedding_request( + self, + model: str, + input: AllEmbeddingInputValues, + optional_params: dict, + headers: dict, + ) -> dict: + """ + Transform embedding request to Hosted VLLM format (OpenAI-compatible). + """ + # Ensure input is a list + if isinstance(input, str): + input = [input] + + # Strip 'hosted_vllm/' prefix if present + if model.startswith("hosted_vllm/"): + model = model.replace("hosted_vllm/", "", 1) + + return { + "model": model, + "input": input, + **optional_params, + } + + def transform_embedding_response( + self, + model: str, + raw_response: httpx.Response, + model_response: EmbeddingResponse, + logging_obj: LiteLLMLoggingObj, + api_key: Optional[str], + request_data: dict, + optional_params: dict, + litellm_params: dict, + ) -> EmbeddingResponse: + """ + Transform embedding response from Hosted VLLM format (OpenAI-compatible). + """ + logging_obj.post_call(original_response=raw_response.text) + + # VLLM returns standard OpenAI-compatible embedding response + response_json = raw_response.json() + + return convert_to_model_response_object( + response_object=response_json, + model_response_object=model_response, + response_type="embedding", + ) + + def get_supported_openai_params(self, model: str) -> list: + """ + Get list of supported OpenAI parameters for Hosted VLLM embeddings. + """ + return [ + "timeout", + "dimensions", + "encoding_format", + "user", + ] + + def map_openai_params( + self, + non_default_params: dict, + optional_params: dict, + model: str, + drop_params: bool, + ) -> dict: + """ + Map OpenAI parameters to Hosted VLLM format. + """ + for param, value in non_default_params.items(): + if param in self.get_supported_openai_params(model): + optional_params[param] = value + return optional_params + + def get_error_class( + self, error_message: str, status_code: int, headers: Union[dict, httpx.Headers] + ) -> BaseLLMException: + """ + Get the error class for Hosted VLLM errors. + """ + return HostedVLLMEmbeddingError( + message=error_message, + status_code=status_code, + headers=headers, + ) diff --git a/litellm/llms/minimax/chat/transformation.py b/litellm/llms/minimax/chat/transformation.py index ed80ff8aed1..3e9dc0209f2 100644 --- a/litellm/llms/minimax/chat/transformation.py +++ b/litellm/llms/minimax/chat/transformation.py @@ -1,11 +1,12 @@ """ MiniMax OpenAI transformation config - extends OpenAI chat config for MiniMax's OpenAI-compatible API """ -from typing import Optional +from typing import List, Optional, Tuple import litellm from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig from litellm.secret_managers.main import get_secret_str +from litellm.types.llms.openai import AllMessageValues, ChatCompletionToolParam class MinimaxChatConfig(OpenAIGPTConfig): @@ -73,11 +74,33 @@ class MinimaxChatConfig(OpenAIGPTConfig): else: return f"{base_url}/v1/chat/completions" + def remove_cache_control_flag_from_messages_and_tools( + self, + model: str, + messages: List[AllMessageValues], + tools: Optional[List[ChatCompletionToolParam]] = None, + ) -> Tuple[List[AllMessageValues], Optional[List[ChatCompletionToolParam]]]: + """ + Override to preserve cache_control for MiniMax. + MiniMax supports cache_control - don't strip it. + """ + # MiniMax supports cache_control, so return messages and tools unchanged + return messages, tools + def get_supported_openai_params(self, model: str) -> list: """ Get supported OpenAI parameters for MiniMax. - Adds reasoning_split to the list of supported params. + Adds reasoning_split and thinking to the list of supported params. """ base_params = super().get_supported_openai_params(model=model) - return base_params + ["reasoning_split"] + additional_params = ["reasoning_split"] + + # Add thinking parameter if model supports reasoning + try: + if litellm.supports_reasoning(model=model, custom_llm_provider="minimax"): + additional_params.append("thinking") + except Exception: + pass + + return base_params + additional_params diff --git a/litellm/llms/oci/chat/transformation.py b/litellm/llms/oci/chat/transformation.py index 7af7be2094a..84f39ef2525 100644 --- a/litellm/llms/oci/chat/transformation.py +++ b/litellm/llms/oci/chat/transformation.py @@ -32,6 +32,7 @@ from litellm.types.llms.oci import ( OCICompletionResponse, OCIContentPartUnion, OCIImageContentPart, + OCIImageUrl, OCIMessage, OCIRoles, OCIServingMode, @@ -1129,7 +1130,7 @@ def adapt_messages_to_generic_oci_standard_content_message( image_url = image_url.get("url") if not isinstance(image_url, str): raise Exception("Prop `image_url` must be a string or an object with a `url` property") - new_content.append(OCIImageContentPart(imageUrl=image_url)) + new_content.append(OCIImageContentPart(imageUrl=OCIImageUrl(url=image_url))) return OCIMessage( role=open_ai_to_generic_oci_role_map[role], diff --git a/litellm/llms/openai/chat/gpt_5_transformation.py b/litellm/llms/openai/chat/gpt_5_transformation.py index 3fffa335fdc..05c003c8b7a 100644 --- a/litellm/llms/openai/chat/gpt_5_transformation.py +++ b/litellm/llms/openai/chat/gpt_5_transformation.py @@ -19,7 +19,9 @@ class OpenAIGPT5Config(OpenAIGPTConfig): @classmethod def is_model_gpt_5_model(cls, model: str) -> bool: - return "gpt-5" in model + # gpt-5-chat* behaves like a regular chat model (supports temperature, etc.) + # Don't route it through GPT-5 reasoning-specific parameter restrictions. + return "gpt-5" in model and "gpt-5-chat" not in model @classmethod def is_model_gpt_5_codex_model(cls, model: str) -> bool: diff --git a/litellm/llms/openai/image_generation/cost_calculator.py b/litellm/llms/openai/image_generation/cost_calculator.py index 35caaf6e9b1..988d5626134 100644 --- a/litellm/llms/openai/image_generation/cost_calculator.py +++ b/litellm/llms/openai/image_generation/cost_calculator.py @@ -8,8 +8,7 @@ from typing import Optional from litellm import verbose_logger from litellm.litellm_core_utils.llm_cost_calc.utils import generic_cost_per_token -from litellm.responses.utils import ResponseAPILoggingUtils -from litellm.types.utils import ImageResponse +from litellm.types.utils import ImageResponse, Usage def cost_calculator( @@ -39,11 +38,18 @@ def cost_calculator( ) return 0.0 - # Transform ImageUsage to Usage using the existing helper - # ImageUsage has the same format as ResponseAPIUsage - chat_usage = ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage( - usage - ) + # If usage is already a Usage object with completion_tokens_details set, + # use it directly (it was already transformed in convert_to_image_response) + if isinstance(usage, Usage) and usage.completion_tokens_details is not None: + chat_usage = usage + else: + # Transform ImageUsage to Usage using the existing helper + # ImageUsage has the same format as ResponseAPIUsage + from litellm.responses.utils import ResponseAPILoggingUtils + + chat_usage = ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage( + usage + ) # Use generic_cost_per_token for cost calculation prompt_cost, completion_cost = generic_cost_per_token( diff --git a/litellm/llms/openai/openai.py b/litellm/llms/openai/openai.py index 4d623097478..8a8070240da 100644 --- a/litellm/llms/openai/openai.py +++ b/litellm/llms/openai/openai.py @@ -1923,10 +1923,10 @@ class OpenAIBatchesAPI(BaseLLM): self, cancel_batch_data: CancelBatchRequest, openai_client: AsyncOpenAI, - ) -> Batch: + ) -> LiteLLMBatch: verbose_logger.debug("async cancelling batch, args= %s", cancel_batch_data) response = await openai_client.batches.cancel(**cancel_batch_data) - return response + return LiteLLMBatch(**response.model_dump()) def cancel_batch( self, @@ -1962,8 +1962,13 @@ class OpenAIBatchesAPI(BaseLLM): cancel_batch_data=cancel_batch_data, openai_client=openai_client ) + # At this point, openai_client is guaranteed to be a sync OpenAI client + if not isinstance(openai_client, OpenAI): + raise ValueError( + "OpenAI client is not an instance of OpenAI. Make sure you passed a sync OpenAI client." + ) response = openai_client.batches.cancel(**cancel_batch_data) - return response + return LiteLLMBatch(**response.model_dump()) async def alist_batches( self, diff --git a/litellm/llms/openai_like/embedding/handler.py b/litellm/llms/openai_like/embedding/handler.py index 95a4aa854ad..d0d26d5959f 100644 --- a/litellm/llms/openai_like/embedding/handler.py +++ b/litellm/llms/openai_like/embedding/handler.py @@ -105,7 +105,8 @@ class OpenAILikeEmbeddingHandler(OpenAILikeBase): custom_endpoint=custom_endpoint, ) model = model - data = {"model": model, "input": input, **optional_params} + filtered_optional_params = {k: v for k, v in optional_params.items() if v not in (None, '')} + data = {"model": model, "input": input, **filtered_optional_params} ## LOGGING logging_obj.pre_call( diff --git a/litellm/llms/openrouter/chat/transformation.py b/litellm/llms/openrouter/chat/transformation.py index b5610852fd2..e3770dbbf49 100644 --- a/litellm/llms/openrouter/chat/transformation.py +++ b/litellm/llms/openrouter/chat/transformation.py @@ -26,6 +26,9 @@ class CacheControlSupportedModels(str, Enum): """Models that support cache_control in content blocks.""" CLAUDE = "claude" GEMINI = "gemini" + MINIMAX = "minimax" + GLM = "glm" + ZAI = "z-ai" class OpenrouterConfig(OpenAIGPTConfig): @@ -39,6 +42,7 @@ class OpenrouterConfig(OpenAIGPTConfig): model=model, custom_llm_provider="openrouter" ) or litellm.supports_reasoning(model=model): supported_params.append("reasoning_effort") + supported_params.append("thinking") except Exception: pass return list(dict.fromkeys(supported_params)) diff --git a/litellm/llms/s3_vectors/__init__.py b/litellm/llms/s3_vectors/__init__.py new file mode 100644 index 00000000000..e8367949c3e --- /dev/null +++ b/litellm/llms/s3_vectors/__init__.py @@ -0,0 +1 @@ +# S3 Vectors LLM integration diff --git a/litellm/llms/s3_vectors/vector_stores/__init__.py b/litellm/llms/s3_vectors/vector_stores/__init__.py new file mode 100644 index 00000000000..ac24b4a38da --- /dev/null +++ b/litellm/llms/s3_vectors/vector_stores/__init__.py @@ -0,0 +1 @@ +# S3 Vectors vector store integration diff --git a/litellm/llms/s3_vectors/vector_stores/transformation.py b/litellm/llms/s3_vectors/vector_stores/transformation.py new file mode 100644 index 00000000000..df81a78289a --- /dev/null +++ b/litellm/llms/s3_vectors/vector_stores/transformation.py @@ -0,0 +1,254 @@ +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union + +import httpx + +from litellm.llms.base_llm.vector_store.transformation import BaseVectorStoreConfig +from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM +from litellm.types.router import GenericLiteLLMParams +from litellm.types.vector_stores import ( + VECTOR_STORE_OPENAI_PARAMS, + BaseVectorStoreAuthCredentials, + VectorStoreIndexEndpoints, + VectorStoreResultContent, + VectorStoreSearchOptionalRequestParams, + VectorStoreSearchResponse, + VectorStoreSearchResult, +) + +if TYPE_CHECKING: + from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj +else: + LiteLLMLoggingObj = Any + + +class S3VectorsVectorStoreConfig(BaseVectorStoreConfig, BaseAWSLLM): + """Vector store configuration for AWS S3 Vectors.""" + + def __init__(self) -> None: + BaseVectorStoreConfig.__init__(self) + BaseAWSLLM.__init__(self) + + def get_auth_credentials( + self, litellm_params: dict + ) -> BaseVectorStoreAuthCredentials: + return {} + + def get_vector_store_endpoints_by_type(self) -> VectorStoreIndexEndpoints: + return { + "read": [("POST", "/QueryVectors")], + "write": [], + } + + def get_supported_openai_params( + self, model: str + ) -> List[VECTOR_STORE_OPENAI_PARAMS]: + return ["max_num_results"] + + def map_openai_params( + self, + non_default_params: dict, + optional_params: dict, + drop_params: bool, + ) -> dict: + for param, value in non_default_params.items(): + if param == "max_num_results": + optional_params["maxResults"] = value + return optional_params + + def validate_environment( + self, headers: dict, litellm_params: Optional[GenericLiteLLMParams] + ) -> dict: + headers = headers or {} + headers.setdefault("Content-Type", "application/json") + return headers + + def get_complete_url(self, api_base: Optional[str], litellm_params: dict) -> str: + aws_region_name = litellm_params.get("aws_region_name") + if not aws_region_name: + raise ValueError("aws_region_name is required for S3 Vectors") + return f"https://s3vectors.{aws_region_name}.api.aws" + + def transform_search_vector_store_request( + self, + vector_store_id: str, + query: Union[str, List[str]], + vector_store_search_optional_params: VectorStoreSearchOptionalRequestParams, + api_base: str, + litellm_logging_obj: LiteLLMLoggingObj, + litellm_params: dict, + ) -> Tuple[str, Dict]: + """Sync version - generates embedding synchronously.""" + # For S3 Vectors, vector_store_id should be in format: bucket_name:index_name + # If not in that format, try to construct it from litellm_params + bucket_name: str + index_name: str + + if ":" in vector_store_id: + bucket_name, index_name = vector_store_id.split(":", 1) + else: + # Try to get bucket_name from litellm_params + bucket_name_from_params = litellm_params.get("vector_bucket_name") + if not bucket_name_from_params or not isinstance(bucket_name_from_params, str): + raise ValueError( + "vector_store_id must be in format 'bucket_name:index_name' for S3 Vectors, " + "or vector_bucket_name must be provided in litellm_params" + ) + bucket_name = bucket_name_from_params + index_name = vector_store_id + + if isinstance(query, list): + query = " ".join(query) + + # Generate embedding for the query + embedding_model = litellm_params.get("embedding_model", "text-embedding-3-small") + + import litellm as litellm_module + embedding_response = litellm_module.embedding(model=embedding_model, input=[query]) + query_embedding = embedding_response.data[0]["embedding"] + + url = f"{api_base}/QueryVectors" + + request_body: Dict[str, Any] = { + "vectorBucketName": bucket_name, + "indexName": index_name, + "queryVector": {"float32": query_embedding}, + "topK": vector_store_search_optional_params.get("max_num_results", 5), # Default to 5 + "returnDistance": True, + "returnMetadata": True, + } + + litellm_logging_obj.model_call_details["query"] = query + return url, request_body + + async def atransform_search_vector_store_request( + self, + vector_store_id: str, + query: Union[str, List[str]], + vector_store_search_optional_params: VectorStoreSearchOptionalRequestParams, + api_base: str, + litellm_logging_obj: LiteLLMLoggingObj, + litellm_params: dict, + ) -> Tuple[str, Dict]: + """Async version - generates embedding asynchronously.""" + # For S3 Vectors, vector_store_id should be in format: bucket_name:index_name + # If not in that format, try to construct it from litellm_params + bucket_name: str + index_name: str + + if ":" in vector_store_id: + bucket_name, index_name = vector_store_id.split(":", 1) + else: + # Try to get bucket_name from litellm_params + bucket_name_from_params = litellm_params.get("vector_bucket_name") + if not bucket_name_from_params or not isinstance(bucket_name_from_params, str): + raise ValueError( + "vector_store_id must be in format 'bucket_name:index_name' for S3 Vectors, " + "or vector_bucket_name must be provided in litellm_params" + ) + bucket_name = bucket_name_from_params + index_name = vector_store_id + + if isinstance(query, list): + query = " ".join(query) + + # Generate embedding for the query asynchronously + embedding_model = litellm_params.get("embedding_model", "text-embedding-3-small") + + import litellm as litellm_module + embedding_response = await litellm_module.aembedding(model=embedding_model, input=[query]) + query_embedding = embedding_response.data[0]["embedding"] + + url = f"{api_base}/QueryVectors" + + request_body: Dict[str, Any] = { + "vectorBucketName": bucket_name, + "indexName": index_name, + "queryVector": {"float32": query_embedding}, + "topK": vector_store_search_optional_params.get("max_num_results", 5), # Default to 5 + "returnDistance": True, + "returnMetadata": True, + } + + litellm_logging_obj.model_call_details["query"] = query + return url, request_body + + def sign_request( + self, + headers: dict, + optional_params: Dict, + request_data: Dict, + api_base: str, + api_key: Optional[str] = None, + ) -> Tuple[dict, Optional[bytes]]: + return self._sign_request( + service_name="s3vectors", + headers=headers, + optional_params=optional_params, + request_data=request_data, + api_base=api_base, + api_key=api_key, + ) + + def transform_search_vector_store_response( + self, response: httpx.Response, litellm_logging_obj: LiteLLMLoggingObj + ) -> VectorStoreSearchResponse: + try: + response_data = response.json() + results: List[VectorStoreSearchResult] = [] + + for item in response_data.get("vectors", []) or []: + metadata = item.get("metadata", {}) or {} + source_text = metadata.get("source_text", "") + + if not source_text: + continue + + # Extract file information from metadata + chunk_index = metadata.get("chunk_index", "0") + file_id = f"s3-vectors-chunk-{chunk_index}" + filename = metadata.get("filename", f"document-{chunk_index}") + + # S3 Vectors returns distance, convert to similarity score (0-1) + # Lower distance = higher similarity + # We'll normalize using 1 / (1 + distance) to get a 0-1 score + distance = item.get("distance") + score = None + if distance is not None: + # Convert distance to similarity score between 0 and 1 + # For cosine distance: similarity = 1 - distance + # For euclidean: use 1 / (1 + distance) + # Assuming cosine distance here + score = max(0.0, min(1.0, 1.0 - float(distance))) + + results.append( + VectorStoreSearchResult( + score=score, + content=[VectorStoreResultContent(text=source_text, type="text")], + file_id=file_id, + filename=filename, + attributes=metadata, + ) + ) + + return VectorStoreSearchResponse( + object="vector_store.search_results.page", + search_query=litellm_logging_obj.model_call_details.get("query", ""), + data=results, + ) + except Exception as e: + raise self.get_error_class( + error_message=str(e), + status_code=response.status_code, + headers=response.headers, + ) + + # Vector store creation is not yet implemented + def transform_create_vector_store_request( + self, + vector_store_create_optional_params, + api_base: str, + ) -> Tuple[str, Dict]: + raise NotImplementedError + + def transform_create_vector_store_response(self, response: httpx.Response): + raise NotImplementedError diff --git a/litellm/llms/vercel_ai_gateway/embedding/__init__.py b/litellm/llms/vercel_ai_gateway/embedding/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/litellm/llms/vercel_ai_gateway/embedding/transformation.py b/litellm/llms/vercel_ai_gateway/embedding/transformation.py new file mode 100644 index 00000000000..7238b05f10d --- /dev/null +++ b/litellm/llms/vercel_ai_gateway/embedding/transformation.py @@ -0,0 +1,176 @@ +""" +Vercel AI Gateway Embedding API Configuration. + +This module provides the configuration for Vercel AI Gateway's Embedding API. +Vercel AI Gateway is OpenAI-compatible and supports embeddings via the /v1/embeddings endpoint. + +Docs: https://vercel.com/docs/ai-gateway/openai-compat/embeddings +""" + +from typing import TYPE_CHECKING, Any, Optional + +import httpx + +from litellm.llms.base_llm.embedding.transformation import BaseEmbeddingConfig +from litellm.secret_managers.main import get_secret_str +from litellm.types.llms.openai import AllEmbeddingInputValues +from litellm.types.utils import EmbeddingResponse +from litellm.utils import convert_to_model_response_object + +from ..common_utils import VercelAIGatewayException + +if TYPE_CHECKING: + from litellm.litellm_core_utils.litellm_logging import Logging as _LiteLLMLoggingObj + + LiteLLMLoggingObj = _LiteLLMLoggingObj +else: + LiteLLMLoggingObj = Any + + +class VercelAIGatewayEmbeddingConfig(BaseEmbeddingConfig): + """ + Configuration for Vercel AI Gateway's Embedding API. + + Reference: https://vercel.com/docs/ai-gateway/openai-compat/embeddings + """ + + def validate_environment( + self, + headers: dict, + model: str, + messages: list, + optional_params: dict, + litellm_params: dict, + api_key: Optional[str] = None, + api_base: Optional[str] = None, + ) -> dict: + """ + Validate environment and set up headers for Vercel AI Gateway API. + + Vercel AI Gateway requires: + - Authorization header with Bearer token (API key or OIDC token) + """ + vercel_headers = { + "Content-Type": "application/json", + } + + # Add Authorization header if api_key is provided + if api_key: + vercel_headers["Authorization"] = f"Bearer {api_key}" + + # Merge with existing headers (user's extra_headers take priority) + merged_headers = {**vercel_headers, **headers} + + return merged_headers + + def get_complete_url( + self, + api_base: Optional[str], + api_key: Optional[str], + model: str, + optional_params: dict, + litellm_params: dict, + stream: Optional[bool] = None, + ) -> str: + """ + Get the complete URL for Vercel AI Gateway Embedding API endpoint. + """ + if api_base: + api_base = api_base.rstrip("/") + else: + api_base = ( + get_secret_str("VERCEL_AI_GATEWAY_API_BASE") + or "https://ai-gateway.vercel.sh/v1" + ) + + return f"{api_base}/embeddings" + + def transform_embedding_request( + self, + model: str, + input: AllEmbeddingInputValues, + optional_params: dict, + headers: dict, + ) -> dict: + """ + Transform embedding request to Vercel AI Gateway format (OpenAI-compatible). + """ + # Ensure input is a list + if isinstance(input, str): + input = [input] + + # Strip 'vercel_ai_gateway/' prefix if present + if model.startswith("vercel_ai_gateway/"): + model = model.replace("vercel_ai_gateway/", "", 1) + + return { + "model": model, + "input": input, + **optional_params, + } + + def transform_embedding_response( + self, + model: str, + raw_response: httpx.Response, + model_response: EmbeddingResponse, + logging_obj: LiteLLMLoggingObj, + api_key: Optional[str], + request_data: dict, + optional_params: dict, + litellm_params: dict, + ) -> EmbeddingResponse: + """ + Transform embedding response from Vercel AI Gateway format (OpenAI-compatible). + """ + logging_obj.post_call(original_response=raw_response.text) + + # Vercel AI Gateway returns standard OpenAI-compatible embedding response + response_json = raw_response.json() + + return convert_to_model_response_object( + response_object=response_json, + model_response_object=model_response, + response_type="embedding", + ) + + def get_supported_openai_params(self, model: str) -> list: + """ + Get list of supported OpenAI parameters for Vercel AI Gateway embeddings. + + Vercel AI Gateway supports the standard OpenAI embeddings parameters + and auto-maps 'dimensions' to each provider's expected field. + """ + return [ + "timeout", + "dimensions", + "encoding_format", + "user", + ] + + def map_openai_params( + self, + non_default_params: dict, + optional_params: dict, + model: str, + drop_params: bool, + ) -> dict: + """ + Map OpenAI parameters to Vercel AI Gateway format. + """ + for param, value in non_default_params.items(): + if param in self.get_supported_openai_params(model): + optional_params[param] = value + return optional_params + + def get_error_class( + self, error_message: str, status_code: int, headers: Any + ) -> Any: + """ + Get the error class for Vercel AI Gateway errors. + """ + return VercelAIGatewayException( + message=error_message, + status_code=status_code, + headers=headers, + ) diff --git a/litellm/llms/vertex_ai/common_utils.py b/litellm/llms/vertex_ai/common_utils.py index 152b99ca4db..a0e2ddf5e98 100644 --- a/litellm/llms/vertex_ai/common_utils.py +++ b/litellm/llms/vertex_ai/common_utils.py @@ -849,7 +849,7 @@ def get_vertex_model_id_from_url(url: str) -> Optional[str]: `https://${LOCATION}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${LOCATION}/publishers/google/models/${MODEL_ID}:streamGenerateContent` """ - match = re.search(r"/models/([^/:]+)", url) + match = re.search(r"/models/([^:]+)", url) return match.group(1) if match else None diff --git a/litellm/llms/vertex_ai/context_caching/vertex_ai_context_caching.py b/litellm/llms/vertex_ai/context_caching/vertex_ai_context_caching.py index 289963e917a..ed4d2d6a740 100644 --- a/litellm/llms/vertex_ai/context_caching/vertex_ai_context_caching.py +++ b/litellm/llms/vertex_ai/context_caching/vertex_ai_context_caching.py @@ -27,6 +27,8 @@ local_cache_obj = Cache( type=LiteLLMCacheType.LOCAL ) # only used for calling 'get_cache_key' function +MAX_PAGINATION_PAGES = 100 # Reasonable upper bound for pagination + class ContextCachingEndpoints(VertexBase): """ @@ -115,7 +117,7 @@ class ContextCachingEndpoints(VertexBase): - None """ - _, url = self._get_token_and_url_context_caching( + _, base_url = self._get_token_and_url_context_caching( gemini_api_key=api_key, custom_llm_provider=custom_llm_provider, api_base=api_base, @@ -123,43 +125,63 @@ class ContextCachingEndpoints(VertexBase): vertex_location=vertex_location, vertex_auth_header=vertex_auth_header ) - try: - ## LOGGING - logging_obj.pre_call( - input="", - api_key="", - additional_args={ - "complete_input_dict": {}, - "api_base": url, - "headers": headers, - }, - ) - resp = client.get(url=url, headers=headers) - resp.raise_for_status() - except httpx.HTTPStatusError as e: - if e.response.status_code == 403: + page_token: Optional[str] = None + + # Iterate through all pages + for _ in range(MAX_PAGINATION_PAGES): + # Build URL with pagination token if present + if page_token: + separator = "&" if "?" in base_url else "?" + url = f"{base_url}{separator}pageToken={page_token}" + else: + url = base_url + + try: + ## LOGGING + logging_obj.pre_call( + input="", + api_key="", + additional_args={ + "complete_input_dict": {}, + "api_base": url, + "headers": headers, + }, + ) + + resp = client.get(url=url, headers=headers) + resp.raise_for_status() + except httpx.HTTPStatusError as e: + if e.response.status_code == 403: + return None + raise VertexAIError( + status_code=e.response.status_code, message=e.response.text + ) + except Exception as e: + raise VertexAIError(status_code=500, message=str(e)) + + raw_response = resp.json() + logging_obj.post_call(original_response=raw_response) + + if "cachedContents" not in raw_response: return None - raise VertexAIError( - status_code=e.response.status_code, message=e.response.text - ) - except Exception as e: - raise VertexAIError(status_code=500, message=str(e)) - raw_response = resp.json() - logging_obj.post_call(original_response=raw_response) - if "cachedContents" not in raw_response: - return None + all_cached_items = CachedContentListAllResponseBody(**raw_response) - all_cached_items = CachedContentListAllResponseBody(**raw_response) + if "cachedContents" not in all_cached_items: + return None - if "cachedContents" not in all_cached_items: - return None + # Check current page for matching cache_key + for cached_item in all_cached_items["cachedContents"]: + display_name = cached_item.get("displayName") + if display_name is not None and display_name == cache_key: + return cached_item.get("name") - for cached_item in all_cached_items["cachedContents"]: - display_name = cached_item.get("displayName") - if display_name is not None and display_name == cache_key: - return cached_item.get("name") + # Check if there are more pages + page_token = all_cached_items.get("nextPageToken") + if not page_token: + # No more pages, cache not found + break return None @@ -187,7 +209,7 @@ class ContextCachingEndpoints(VertexBase): - None """ - _, url = self._get_token_and_url_context_caching( + _, base_url = self._get_token_and_url_context_caching( gemini_api_key=api_key, custom_llm_provider=custom_llm_provider, api_base=api_base, @@ -195,43 +217,63 @@ class ContextCachingEndpoints(VertexBase): vertex_location=vertex_location, vertex_auth_header=vertex_auth_header ) - try: - ## LOGGING - logging_obj.pre_call( - input="", - api_key="", - additional_args={ - "complete_input_dict": {}, - "api_base": url, - "headers": headers, - }, - ) - resp = await client.get(url=url, headers=headers) - resp.raise_for_status() - except httpx.HTTPStatusError as e: - if e.response.status_code == 403: + page_token: Optional[str] = None + + # Iterate through all pages + for _ in range(MAX_PAGINATION_PAGES): + # Build URL with pagination token if present + if page_token: + separator = "&" if "?" in base_url else "?" + url = f"{base_url}{separator}pageToken={page_token}" + else: + url = base_url + + try: + ## LOGGING + logging_obj.pre_call( + input="", + api_key="", + additional_args={ + "complete_input_dict": {}, + "api_base": url, + "headers": headers, + }, + ) + + resp = await client.get(url=url, headers=headers) + resp.raise_for_status() + except httpx.HTTPStatusError as e: + if e.response.status_code == 403: + return None + raise VertexAIError( + status_code=e.response.status_code, message=e.response.text + ) + except Exception as e: + raise VertexAIError(status_code=500, message=str(e)) + + raw_response = resp.json() + logging_obj.post_call(original_response=raw_response) + + if "cachedContents" not in raw_response: return None - raise VertexAIError( - status_code=e.response.status_code, message=e.response.text - ) - except Exception as e: - raise VertexAIError(status_code=500, message=str(e)) - raw_response = resp.json() - logging_obj.post_call(original_response=raw_response) - if "cachedContents" not in raw_response: - return None + all_cached_items = CachedContentListAllResponseBody(**raw_response) - all_cached_items = CachedContentListAllResponseBody(**raw_response) + if "cachedContents" not in all_cached_items: + return None - if "cachedContents" not in all_cached_items: - return None + # Check current page for matching cache_key + for cached_item in all_cached_items["cachedContents"]: + display_name = cached_item.get("displayName") + if display_name is not None and display_name == cache_key: + return cached_item.get("name") - for cached_item in all_cached_items["cachedContents"]: - display_name = cached_item.get("displayName") - if display_name is not None and display_name == cache_key: - return cached_item.get("name") + # Check if there are more pages + page_token = all_cached_items.get("nextPageToken") + if not page_token: + # No more pages, cache not found + break return None @@ -501,4 +543,4 @@ class ContextCachingEndpoints(VertexBase): pass async def async_get_cache(self): - pass + pass \ No newline at end of file diff --git a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py index b78ac8f9e98..b5a6949f272 100644 --- a/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py +++ b/litellm/llms/vertex_ai/gemini/vertex_and_google_ai_studio_gemini.py @@ -478,6 +478,13 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): if "type" in tool and tool["type"] == "computer_use": computer_use_config = {k: v for k, v in tool.items() if k != "type"} tool = {VertexToolName.COMPUTER_USE.value: computer_use_config} + # Handle OpenAI-style web_search and web_search_preview tools + # Transform them to Gemini's googleSearch tool + elif "type" in tool and tool["type"] in ("web_search", "web_search_preview"): + verbose_logger.info( + f"Gemini: Transforming OpenAI-style '{tool['type']}' tool to googleSearch" + ) + tool = {VertexToolName.GOOGLE_SEARCH.value: {}} # Handle tools with 'type' field (OpenAI spec compliance) Ignore this field -> https://github.com/BerriAI/litellm/issues/14644#issuecomment-3342061838 elif "type" in tool: tool = {k: tool[k] for k in tool if k != "type"} @@ -1657,7 +1664,17 @@ class VertexGeminiConfig(VertexAIBaseConfig, BaseConfig): ## This is necessary because promptTokensDetails includes both cached and non-cached tokens ## See: https://github.com/BerriAI/litellm/issues/18750 if cached_text_tokens is not None and prompt_text_tokens is not None: + # Explicit caching: subtract cached tokens per modality from cacheTokensDetails prompt_text_tokens = prompt_text_tokens - cached_text_tokens + elif ( + cached_tokens is not None + and prompt_text_tokens is not None + and cached_text_tokens is None + ): + # Implicit caching: only cachedContentTokenCount is provided (no cacheTokensDetails) + # Subtract from text tokens since implicit caching is primarily for text content + # See: https://github.com/BerriAI/litellm/issues/16341 + prompt_text_tokens = prompt_text_tokens - cached_tokens if cached_audio_tokens is not None and prompt_audio_tokens is not None: prompt_audio_tokens = prompt_audio_tokens - cached_audio_tokens if cached_image_tokens is not None and prompt_image_tokens is not None: diff --git a/litellm/llms/vertex_ai/image_generation/vertex_gemini_transformation.py b/litellm/llms/vertex_ai/image_generation/vertex_gemini_transformation.py index 89ed9f1a8a5..ba3df88be14 100644 --- a/litellm/llms/vertex_ai/image_generation/vertex_gemini_transformation.py +++ b/litellm/llms/vertex_ai/image_generation/vertex_gemini_transformation.py @@ -295,9 +295,11 @@ class VertexAIGeminiImageGenerationConfig(BaseImageGenerationConfig, VertexLLM): if "inlineData" in part: inline_data = part["inlineData"] if "data" in inline_data: + thought_sig = part.get("thoughtSignature") model_response.data.append(ImageObject( b64_json=inline_data["data"], url=None, + provider_specific_fields={"thought_signature": thought_sig} if thought_sig else None, )) if usage_metadata := response_data.get("usageMetadata", None): diff --git a/litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/experimental_pass_through/transformation.py b/litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/experimental_pass_through/transformation.py index fc75376c0cb..9b8ff3ecc2d 100644 --- a/litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/experimental_pass_through/transformation.py +++ b/litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/experimental_pass_through/transformation.py @@ -7,6 +7,7 @@ from litellm.llms.anthropic.experimental_pass_through.messages.transformation im from litellm.types.llms.anthropic import ( ANTHROPIC_BETA_HEADER_VALUES, ANTHROPIC_HOSTED_TOOLS, + ANTHROPIC_PROMPT_CACHING_SCOPE_BETA_HEADER, ) from litellm.types.llms.anthropic_tool_search import get_tool_search_beta_header from litellm.types.llms.vertex_ai import VertexPartnerProvider @@ -65,6 +66,10 @@ class VertexAIPartnerModelsAnthropicMessagesConfig(AnthropicMessagesConfig, Vert if existing_beta: beta_values.update(b.strip() for b in existing_beta.split(",")) + # Use the helper to remove unsupported beta headers + self.remove_unsupported_beta(headers) + beta_values.discard(ANTHROPIC_PROMPT_CACHING_SCOPE_BETA_HEADER) + # Check for web search tool for tool in tools: if isinstance(tool, dict) and tool.get("type", "").startswith(ANTHROPIC_HOSTED_TOOLS.WEB_SEARCH.value): @@ -123,3 +128,23 @@ class VertexAIPartnerModelsAnthropicMessagesConfig(AnthropicMessagesConfig, Vert ) # do not pass output_format in request body to vertex ai - vertex ai does not support output_format as yet return anthropic_messages_request + + def remove_unsupported_beta(self, headers: dict) -> None: + """ + Helper method to remove unsupported beta headers from the beta headers. + Modifies headers in place. + """ + unsupported_beta_headers = [ + ANTHROPIC_PROMPT_CACHING_SCOPE_BETA_HEADER + ] + existing_beta = headers.get("anthropic-beta") + if existing_beta: + filtered_beta = [ + b.strip() + for b in existing_beta.split(",") + if b.strip() not in unsupported_beta_headers + ] + if filtered_beta: + headers["anthropic-beta"] = ",".join(filtered_beta) + elif "anthropic-beta" in headers: + del headers["anthropic-beta"] diff --git a/litellm/llms/vertex_ai/vertex_llm_base.py b/litellm/llms/vertex_ai/vertex_llm_base.py index a185370e376..4613b6a5715 100644 --- a/litellm/llms/vertex_ai/vertex_llm_base.py +++ b/litellm/llms/vertex_ai/vertex_llm_base.py @@ -20,6 +20,7 @@ from .common_utils import ( _get_vertex_url, all_gemini_url_modes, get_vertex_base_model_name, + get_vertex_base_url, is_global_only_vertex_model, ) @@ -200,12 +201,7 @@ class VertexBase: ) -> str: if api_base: return api_base - elif vertex_location == "global": - return "https://aiplatform.googleapis.com" - elif vertex_location: - return f"https://{vertex_location}-aiplatform.googleapis.com" - else: - return f"https://{self.get_default_vertex_location()}-aiplatform.googleapis.com" + return get_vertex_base_url(vertex_location or self.get_default_vertex_location()) @staticmethod def create_vertex_url( @@ -218,7 +214,8 @@ class VertexBase: ) -> str: """Return the base url for the vertex partner models""" - api_base = api_base or f"https://{vertex_location}-aiplatform.googleapis.com" + if api_base is None: + api_base = get_vertex_base_url(vertex_location) if partner == VertexPartnerProvider.llama: return f"{api_base}/v1/projects/{vertex_project}/locations/{vertex_location}/endpoints/openapi/chat/completions" elif partner == VertexPartnerProvider.mistralai: @@ -247,11 +244,13 @@ class VertexBase: stream: Optional[bool], model: str, ) -> str: + # Use get_vertex_region to handle global-only models + resolved_location = self.get_vertex_region(vertex_location, model) api_base = self.get_api_base( - api_base=custom_api_base, vertex_location=vertex_location + api_base=custom_api_base, vertex_location=resolved_location ) default_api_base = VertexBase.create_vertex_url( - vertex_location=vertex_location or "us-central1", + vertex_location=resolved_location, vertex_project=vertex_project or project_id, partner=partner, stream=stream, @@ -274,7 +273,7 @@ class VertexBase: url=default_api_base, model=model, vertex_project=vertex_project or project_id, - vertex_location=vertex_location or "us-central1", + vertex_location=resolved_location, vertex_api_version="v1", # Partner models typically use v1 ) return api_base diff --git a/litellm/llms/watsonx/common_utils.py b/litellm/llms/watsonx/common_utils.py index 774f6dc1f3d..230c9f4cf6e 100644 --- a/litellm/llms/watsonx/common_utils.py +++ b/litellm/llms/watsonx/common_utils.py @@ -42,6 +42,7 @@ def generate_iam_token(api_key=None, **params) -> str: get_secret_str("WX_API_KEY") or get_secret_str("WATSONX_API_KEY") or get_secret_str("WATSONX_APIKEY") + or get_secret_str("WATSONX_ZENAPIKEY") ) if api_key is None: raise ValueError("API key is required") @@ -319,6 +320,7 @@ class IBMWatsonXMixin: or get_secret_str("WATSONX_APIKEY") or get_secret_str("WATSONX_API_KEY") or get_secret_str("WX_API_KEY") + or get_secret_str("WATSONX_ZENAPIKEY") ) api_base = ( diff --git a/litellm/llms/xai/responses/transformation.py b/litellm/llms/xai/responses/transformation.py index bd422c8d81e..82b4771fb4d 100644 --- a/litellm/llms/xai/responses/transformation.py +++ b/litellm/llms/xai/responses/transformation.py @@ -1,10 +1,11 @@ -from typing import TYPE_CHECKING, Any, Dict, List, Optional +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union import litellm from litellm._logging import verbose_logger from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig from litellm.secret_managers.main import get_secret_str from litellm.types.llms.openai import ResponsesAPIOptionalRequestParams +from litellm.types.llms.xai import XAIWebSearchTool, XAIXSearchTool from litellm.types.router import GenericLiteLLMParams from litellm.types.utils import LlmProviders @@ -49,6 +50,85 @@ class XAIResponsesAPIConfig(OpenAIResponsesAPIConfig): return supported_params + def _transform_web_search_tool(self, tool: Dict[str, Any]) -> Union[XAIWebSearchTool, Dict[str, Any]]: + """ + Transform web_search tool to XAI format. + + XAI supports web_search with specific filters: + - allowed_domains (max 5) + - excluded_domains (max 5) + - enable_image_understanding + + XAI does NOT support search_context_size (OpenAI-specific). + """ + xai_tool: Dict[str, Any] = {"type": "web_search"} + + # Remove search_context_size if present (not supported by XAI) + if "search_context_size" in tool: + verbose_logger.info( + "XAI does not support 'search_context_size' parameter. Removing it from web_search tool." + ) + + # Handle filters (XAI-specific structure) + filters = {} + if "allowed_domains" in tool: + allowed_domains = tool["allowed_domains"] + filters["allowed_domains"] = allowed_domains + + if "excluded_domains" in tool: + excluded_domains = tool["excluded_domains"] + filters["excluded_domains"] = excluded_domains + + # Add filters if any were specified + if filters: + xai_tool["filters"] = filters + + # Handle enable_image_understanding (top-level in XAI format) + if "enable_image_understanding" in tool: + xai_tool["enable_image_understanding"] = tool["enable_image_understanding"] + + return xai_tool + + def _transform_x_search_tool(self, tool: Dict[str, Any]) -> Union[XAIXSearchTool, Dict[str, Any]]: + """ + Transform x_search tool to XAI format. + + XAI supports x_search with specific parameters: + - allowed_x_handles (max 10) + - excluded_x_handles (max 10) + - from_date (ISO8601: YYYY-MM-DD) + - to_date (ISO8601: YYYY-MM-DD) + - enable_image_understanding + - enable_video_understanding + """ + xai_tool: Dict[str, Any] = {"type": "x_search"} + + # Handle allowed_x_handles + if "allowed_x_handles" in tool: + allowed_handles = tool["allowed_x_handles"] + xai_tool["allowed_x_handles"] = allowed_handles + + # Handle excluded_x_handles + if "excluded_x_handles" in tool: + excluded_handles = tool["excluded_x_handles"] + xai_tool["excluded_x_handles"] = excluded_handles + + # Handle date range + if "from_date" in tool: + xai_tool["from_date"] = tool["from_date"] + + if "to_date" in tool: + xai_tool["to_date"] = tool["to_date"] + + # Handle media understanding flags + if "enable_image_understanding" in tool: + xai_tool["enable_image_understanding"] = tool["enable_image_understanding"] + + if "enable_video_understanding" in tool: + xai_tool["enable_video_understanding"] = tool["enable_video_understanding"] + + return xai_tool + def map_openai_params( self, response_api_optional_params: ResponsesAPIOptionalRequestParams, @@ -61,7 +141,9 @@ class XAIResponsesAPIConfig(OpenAIResponsesAPIConfig): Handles XAI-specific transformations: 1. Drops 'instructions' parameter (not supported) 2. Transforms code_interpreter tools to remove 'container' field - 3. Sets store=false when images are detected (recommended by XAI) + 3. Transforms web_search tools to XAI format (removes search_context_size, adds filters) + 4. Transforms x_search tools to XAI format + 5. Sets store=false when images are detected (recommended by XAI) """ params = dict(response_api_optional_params) @@ -72,7 +154,13 @@ class XAIResponsesAPIConfig(OpenAIResponsesAPIConfig): ) params.pop("instructions") - # Transform code_interpreter tools - remove container field + if "metadata" in params: + verbose_logger.debug( + "XAI Responses API does not support 'metadata' parameter. Dropping it." + ) + params.pop("metadata") + + # Transform tools if "tools" in params and params["tools"]: tools_list = params["tools"] # Ensure tools is a list for iteration @@ -81,15 +169,36 @@ class XAIResponsesAPIConfig(OpenAIResponsesAPIConfig): transformed_tools: List[Any] = [] for tool in tools_list: - if isinstance(tool, dict) and tool.get("type") == "code_interpreter": - # XAI supports code_interpreter but doesn't use the container field - # Keep only the type field - verbose_logger.debug( - "XAI: Transforming code_interpreter tool, removing container field" - ) - transformed_tools.append({"type": "code_interpreter"}) + if isinstance(tool, dict): + tool_type = tool.get("type") + + if tool_type == "code_interpreter": + # XAI supports code_interpreter but doesn't use the container field + verbose_logger.debug( + "XAI: Transforming code_interpreter tool, removing container field" + ) + transformed_tools.append({"type": "code_interpreter"}) + + elif tool_type == "web_search": + # Transform web_search to XAI format + verbose_logger.debug( + "XAI: Transforming web_search tool to XAI format" + ) + transformed_tools.append(self._transform_web_search_tool(tool)) + + elif tool_type == "x_search": + # Transform x_search to XAI format + verbose_logger.debug( + "XAI: Transforming x_search tool to XAI format" + ) + transformed_tools.append(self._transform_x_search_tool(tool)) + + else: + # Keep other tools as-is + transformed_tools.append(tool) else: transformed_tools.append(tool) + params["tools"] = transformed_tools return params diff --git a/litellm/llms/zai/chat/transformation.py b/litellm/llms/zai/chat/transformation.py index 4380256f0a4..fb1d67df357 100644 --- a/litellm/llms/zai/chat/transformation.py +++ b/litellm/llms/zai/chat/transformation.py @@ -1,6 +1,7 @@ -from typing import Optional, Tuple +from typing import List, Optional, Tuple from litellm.secret_managers.main import get_secret_str +from litellm.types.llms.openai import AllMessageValues, ChatCompletionToolParam from ...openai.chat.gpt_transformation import OpenAIGPTConfig @@ -19,6 +20,19 @@ class ZAIChatConfig(OpenAIGPTConfig): dynamic_api_key = api_key or get_secret_str("ZAI_API_KEY") return api_base, dynamic_api_key + def remove_cache_control_flag_from_messages_and_tools( + self, + model: str, + messages: List[AllMessageValues], + tools: Optional[List[ChatCompletionToolParam]] = None, + ) -> Tuple[List[AllMessageValues], Optional[List[ChatCompletionToolParam]]]: + """ + Override to preserve cache_control for GLM/ZAI. + GLM supports cache_control - don't strip it. + """ + # GLM/ZAI supports cache_control, so return messages and tools unchanged + return messages, tools + def get_supported_openai_params(self, model: str) -> list: base_params = [ "max_tokens", diff --git a/litellm/main.py b/litellm/main.py index ce84c8988e0..7d591f76882 100644 --- a/litellm/main.py +++ b/litellm/main.py @@ -148,7 +148,7 @@ from litellm.utils import ( validate_and_fix_openai_messages, validate_and_fix_openai_tools, validate_chat_completion_tool_choice, - validate_openai_optional_params + validate_openai_optional_params, ) from ._logging import verbose_logger @@ -368,7 +368,7 @@ class AsyncCompletions: @tracer.wrap() @client -async def acompletion( # noqa: PLR0915 +async def acompletion( # noqa: PLR0915 model: str, # Optional OpenAI params: see https://platform.openai.com/docs/api-reference/chat/create messages: List = [], @@ -599,16 +599,8 @@ async def acompletion( # noqa: PLR0915 # Add the context to the function ctx = contextvars.copy_context() func_with_context = partial(ctx.run, func) - - if timeout is not None and isinstance(timeout, (int, float)): - timeout_value = float(timeout) - init_response = await asyncio.wait_for( - loop.run_in_executor(None, func_with_context), - timeout=timeout_value - ) - else: - init_response = await loop.run_in_executor(None, func_with_context) + init_response = await loop.run_in_executor(None, func_with_context) if isinstance(init_response, dict) or isinstance( init_response, ModelResponse ): ## CACHING SCENARIO @@ -616,11 +608,7 @@ async def acompletion( # noqa: PLR0915 response = ModelResponse(**init_response) response = init_response elif asyncio.iscoroutine(init_response): - if timeout is not None and isinstance(timeout, (int, float)): - timeout_value = float(timeout) - response = await asyncio.wait_for(init_response, timeout=timeout_value) - else: - response = await init_response + response = await init_response else: response = init_response # type: ignore @@ -637,14 +625,6 @@ async def acompletion( # noqa: PLR0915 loop=loop ) # sets the logging event loop if the user does sync streaming (e.g. on proxy for sagemaker calls) return response - except asyncio.TimeoutError: - custom_llm_provider = custom_llm_provider or "openai" - from litellm.exceptions import Timeout - raise Timeout( - message=f"Request timed out after {timeout} seconds", - model=model, - llm_provider=custom_llm_provider, - ) except Exception as e: custom_llm_provider = custom_llm_provider or "openai" raise exception_type( @@ -945,6 +925,7 @@ def mock_completion( def responses_api_bridge_check( model: str, custom_llm_provider: str, + web_search_options: Optional[OpenAIWebSearchOptions] = None, ) -> Tuple[dict, str]: model_info: Dict[str, Any] = {} try: @@ -958,6 +939,10 @@ def responses_api_bridge_check( model = model.replace("responses/", "") mode = "responses" model_info["mode"] = mode + + if web_search_options is not None and custom_llm_provider == "xai": + model_info["mode"] = "responses" + model = model.replace("responses/", "") except Exception as e: verbose_logger.debug("Error getting model info: {}".format(e)) @@ -1118,7 +1103,6 @@ def completion( # type: ignore # noqa: PLR0915 # validate optional params stop = validate_openai_optional_params(stop=stop) - ######### unpacking kwargs ##################### args = locals() @@ -1135,7 +1119,9 @@ def completion( # type: ignore # noqa: PLR0915 # Check if MCP tools are present (following responses pattern) # Cast tools to Optional[Iterable[ToolParam]] for type checking tools_for_mcp = cast(Optional[Iterable[ToolParam]], tools) - if LiteLLM_Proxy_MCP_Handler._should_use_litellm_mcp_gateway(tools=tools_for_mcp): + if LiteLLM_Proxy_MCP_Handler._should_use_litellm_mcp_gateway( + tools=tools_for_mcp + ): # Return coroutine - acompletion will await it # completion() can return a coroutine when MCP tools are present, which acompletion() awaits return acompletion_with_mcp( # type: ignore[return-value] @@ -1536,6 +1522,8 @@ def completion( # type: ignore # noqa: PLR0915 max_retries=max_retries, timeout=timeout, litellm_request_debug=kwargs.get("litellm_request_debug", False), + tpm=kwargs.get("tpm"), + rpm=kwargs.get("rpm"), ) cast(LiteLLMLoggingObj, logging).update_environment_variables( model=model, @@ -1563,7 +1551,7 @@ def completion( # type: ignore # noqa: PLR0915 ## RESPONSES API BRIDGE LOGIC ## - check if model has 'mode: responses' in litellm.model_cost map model_info, model = responses_api_bridge_check( - model=model, custom_llm_provider=custom_llm_provider + model=model, custom_llm_provider=custom_llm_provider, web_search_options=web_search_options ) if model_info.get("mode") == "responses": @@ -2361,11 +2349,7 @@ def completion( # type: ignore # noqa: PLR0915 input=messages, api_key=api_key, original_response=response ) elif custom_llm_provider == "minimax": - api_key = ( - api_key - or get_secret_str("MINIMAX_API_KEY") - or litellm.api_key - ) + api_key = api_key or get_secret_str("MINIMAX_API_KEY") or litellm.api_key api_base = ( api_base @@ -2374,6 +2358,33 @@ def completion( # type: ignore # noqa: PLR0915 or "https://api.minimax.io/v1" ) + response = base_llm_http_handler.completion( + model=model, + messages=messages, + api_base=api_base, + custom_llm_provider=custom_llm_provider, + model_response=model_response, + encoding=_get_encoding(), + logging_obj=logging, + optional_params=optional_params, + timeout=timeout, + litellm_params=litellm_params, + shared_session=shared_session, + acompletion=acompletion, + stream=stream, + api_key=api_key, + headers=headers, + client=client, + provider_config=provider_config, + ) + logging.post_call( + input=messages, api_key=api_key, original_response=response + ) + elif custom_llm_provider == "hosted_vllm": + api_base = ( + api_base or litellm.api_base or get_secret_str("HOSTED_VLLM_API_BASE") + ) + response = base_llm_http_handler.completion( model=model, messages=messages, @@ -2413,7 +2424,9 @@ def completion( # type: ignore # noqa: PLR0915 or custom_llm_provider == "wandb" or custom_llm_provider == "clarifai" or custom_llm_provider in litellm.openai_compatible_providers - or JSONProviderRegistry.exists(custom_llm_provider) # JSON-configured providers + or JSONProviderRegistry.exists( + custom_llm_provider + ) # JSON-configured providers or "ft:gpt-3.5-turbo" in model # finetune gpt-3.5-turbo ): # allow user to make an openai call with a custom base # note: if a user sets a custom base - we should ensure this works @@ -3610,9 +3623,9 @@ def completion( # type: ignore # noqa: PLR0915 "aws_region_name" not in optional_params or optional_params["aws_region_name"] is None ): - optional_params[ - "aws_region_name" - ] = aws_bedrock_client.meta.region_name + optional_params["aws_region_name"] = ( + aws_bedrock_client.meta.region_name + ) bedrock_route = BedrockModelInfo.get_bedrock_route(model) if bedrock_route == "converse": @@ -4696,11 +4709,11 @@ def embedding( # noqa: PLR0915 litellm_params=litellm_params_dict, ) elif ( - model in litellm.open_ai_embedding_models - or custom_llm_provider == "openai" + custom_llm_provider == "openai" or custom_llm_provider == "together_ai" or custom_llm_provider == "nvidia_nim" or custom_llm_provider == "litellm_proxy" + or (model in litellm.open_ai_embedding_models and custom_llm_provider is None) ): api_base = ( api_base @@ -4724,7 +4737,7 @@ def embedding( # noqa: PLR0915 if headers is not None and headers != {}: optional_params["extra_headers"] = headers - + if encoding_format is not None: optional_params["encoding_format"] = encoding_format else: @@ -4772,9 +4785,32 @@ def embedding( # noqa: PLR0915 client=client, aembedding=aembedding, ) + elif custom_llm_provider == "hosted_vllm": + api_base = ( + api_base or litellm.api_base or get_secret_str("HOSTED_VLLM_API_BASE") + ) + + # set API KEY + if api_key is None: + api_key = litellm.api_key or get_secret_str("HOSTED_VLLM_API_KEY") + + response = base_llm_http_handler.embedding( + model=model, + input=input, + custom_llm_provider=custom_llm_provider, + api_base=api_base, + api_key=api_key, + logging_obj=logging, + timeout=timeout, + model_response=EmbeddingResponse(), + optional_params=optional_params, + client=client, + aembedding=aembedding, + litellm_params=litellm_params_dict, + headers=headers or {}, + ) elif ( custom_llm_provider == "openai_like" - or custom_llm_provider == "hosted_vllm" or custom_llm_provider == "llamafile" or custom_llm_provider == "lm_studio" ): @@ -4866,6 +4902,36 @@ def embedding( # noqa: PLR0915 headers = openrouter_headers + response = base_llm_http_handler.embedding( + model=model, + input=input, + custom_llm_provider=custom_llm_provider, + api_base=api_base, + api_key=api_key, + logging_obj=logging, + timeout=timeout, + model_response=EmbeddingResponse(), + optional_params=optional_params, + client=client, + aembedding=aembedding, + litellm_params=litellm_params_dict, + headers=headers, + ) + elif custom_llm_provider == "vercel_ai_gateway": + api_base = ( + api_base + or litellm.api_base + or get_secret_str("VERCEL_AI_GATEWAY_API_BASE") + or "https://ai-gateway.vercel.sh/v1" + ) + + api_key = ( + api_key + or litellm.api_key + or get_secret_str("VERCEL_AI_GATEWAY_API_KEY") + or get_secret_str("VERCEL_OIDC_TOKEN") + ) + response = base_llm_http_handler.embedding( model=model, input=input, @@ -5917,9 +5983,9 @@ def adapter_completion( new_kwargs = translation_obj.translate_completion_input_params(kwargs=kwargs) response: Union[ModelResponse, CustomStreamWrapper] = completion(**new_kwargs) # type: ignore - translated_response: Optional[ - Union[BaseModel, AdapterCompletionStreamWrapper] - ] = None + translated_response: Optional[Union[BaseModel, AdapterCompletionStreamWrapper]] = ( + None + ) if isinstance(response, ModelResponse): translated_response = translation_obj.translate_completion_output_params( response=response @@ -6624,9 +6690,9 @@ def speech( # noqa: PLR0915 ElevenLabsTextToSpeechConfig.ELEVENLABS_QUERY_PARAMS_KEY ] = query_params - litellm_params_dict[ - ElevenLabsTextToSpeechConfig.ELEVENLABS_VOICE_ID_KEY - ] = voice_id + litellm_params_dict[ElevenLabsTextToSpeechConfig.ELEVENLABS_VOICE_ID_KEY] = ( + voice_id + ) if api_base is not None: litellm_params_dict["api_base"] = api_base @@ -6759,9 +6825,7 @@ def speech( # noqa: PLR0915 if text_to_speech_provider_config is None: text_to_speech_provider_config = MinimaxTextToSpeechConfig() - minimax_config = cast( - MinimaxTextToSpeechConfig, text_to_speech_provider_config - ) + minimax_config = cast(MinimaxTextToSpeechConfig, text_to_speech_provider_config) if api_base is not None: litellm_params_dict["api_base"] = api_base @@ -6901,7 +6965,7 @@ async def ahealth_check( custom_llm_provider_from_params = model_params.get("custom_llm_provider", None) api_base_from_params = model_params.get("api_base", None) api_key_from_params = model_params.get("api_key", None) - + model, custom_llm_provider, _, _ = get_llm_provider( model=model, custom_llm_provider=custom_llm_provider_from_params, @@ -7134,9 +7198,9 @@ def stream_chunk_builder( # noqa: PLR0915 ] if len(content_chunks) > 0: - response["choices"][0]["message"][ - "content" - ] = processor.get_combined_content(content_chunks) + response["choices"][0]["message"]["content"] = ( + processor.get_combined_content(content_chunks) + ) thinking_blocks = [ chunk @@ -7147,9 +7211,9 @@ def stream_chunk_builder( # noqa: PLR0915 ] if len(thinking_blocks) > 0: - response["choices"][0]["message"][ - "thinking_blocks" - ] = processor.get_combined_thinking_content(thinking_blocks) + response["choices"][0]["message"]["thinking_blocks"] = ( + processor.get_combined_thinking_content(thinking_blocks) + ) reasoning_chunks = [ chunk @@ -7160,9 +7224,9 @@ def stream_chunk_builder( # noqa: PLR0915 ] if len(reasoning_chunks) > 0: - response["choices"][0]["message"][ - "reasoning_content" - ] = processor.get_combined_reasoning_content(reasoning_chunks) + response["choices"][0]["message"]["reasoning_content"] = ( + processor.get_combined_reasoning_content(reasoning_chunks) + ) annotation_chunks = [ chunk @@ -7188,6 +7252,23 @@ def stream_chunk_builder( # noqa: PLR0915 _choice = cast(Choices, response.choices[0]) _choice.message.audio = processor.get_combined_audio_content(audio_chunks) + # Handle image chunks from models like gemini-2.5-flash-image + # See: https://github.com/BerriAI/litellm/issues/19478 + image_chunks = [ + chunk + for chunk in chunks + if len(chunk["choices"]) > 0 + and "images" in chunk["choices"][0]["delta"] + and chunk["choices"][0]["delta"]["images"] is not None + ] + + if len(image_chunks) > 0: + # Images come complete in a single chunk, collect all images from all chunks + all_images = [] + for chunk in image_chunks: + all_images.extend(chunk["choices"][0]["delta"]["images"]) + response["choices"][0]["message"]["images"] = all_images + # Combine provider_specific_fields from streaming chunks (e.g., web_search_results, citations) # See: https://github.com/BerriAI/litellm/issues/17737 provider_specific_chunks = [ @@ -7271,12 +7352,16 @@ def _get_encoding(): def __getattr__(name: str) -> Any: """Lazy import handler for main module""" if name == "encoding": - # Lazy load encoding to avoid heavy tiktoken import at module load time - _encoding = tiktoken.get_encoding("cl100k_base") + # Use _get_default_encoding which properly sets TIKTOKEN_CACHE_DIR + # before loading tiktoken, ensuring the local cache is used + # instead of downloading from the internet + from litellm._lazy_imports import _get_default_encoding + _encoding = _get_default_encoding() # Cache it in the module's __dict__ for subsequent accesses import sys + sys.modules[__name__].__dict__["encoding"] = _encoding global _encoding_cache _encoding_cache = _encoding return _encoding - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") \ No newline at end of file + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index 209c0794e50..485bee4f191 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -354,6 +354,25 @@ "supports_video_input": true, "supports_vision": true }, + "amazon.nova-2-pro-preview-20251202-v1:0": { + "cache_read_input_token_cost": 5.46875e-07, + "input_cost_per_token": 2.1875e-06, + "input_cost_per_image_token": 2.1875e-06, + "input_cost_per_audio_token": 2.1875e-06, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 1.75e-05, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_video_input": true, + "supports_vision": true + }, "apac.amazon.nova-2-lite-v1:0": { "cache_read_input_token_cost": 8.25e-08, "input_cost_per_token": 3.3e-07, @@ -371,6 +390,25 @@ "supports_video_input": true, "supports_vision": true }, + "apac.amazon.nova-2-pro-preview-20251202-v1:0": { + "cache_read_input_token_cost": 5.46875e-07, + "input_cost_per_token": 2.1875e-06, + "input_cost_per_image_token": 2.1875e-06, + "input_cost_per_audio_token": 2.1875e-06, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 1.75e-05, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_video_input": true, + "supports_vision": true + }, "eu.amazon.nova-2-lite-v1:0": { "cache_read_input_token_cost": 8.25e-08, "input_cost_per_token": 3.3e-07, @@ -388,6 +426,25 @@ "supports_video_input": true, "supports_vision": true }, + "eu.amazon.nova-2-pro-preview-20251202-v1:0": { + "cache_read_input_token_cost": 5.46875e-07, + "input_cost_per_token": 2.1875e-06, + "input_cost_per_image_token": 2.1875e-06, + "input_cost_per_audio_token": 2.1875e-06, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 1.75e-05, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_video_input": true, + "supports_vision": true + }, "us.amazon.nova-2-lite-v1:0": { "cache_read_input_token_cost": 8.25e-08, "input_cost_per_token": 3.3e-07, @@ -405,6 +462,25 @@ "supports_video_input": true, "supports_vision": true }, + "us.amazon.nova-2-pro-preview-20251202-v1:0": { + "cache_read_input_token_cost": 5.46875e-07, + "input_cost_per_token": 2.1875e-06, + "input_cost_per_image_token": 2.1875e-06, + "input_cost_per_audio_token": 2.1875e-06, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 1.75e-05, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_video_input": true, + "supports_vision": true + }, "amazon.nova-2-multimodal-embeddings-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 8172, @@ -673,7 +749,7 @@ "anthropic.claude-3-5-sonnet-20240620-v1:0": { "input_cost_per_token": 3e-06, "litellm_provider": "bedrock", - "max_input_tokens": 200000, + "max_input_tokens": 1000000, "max_output_tokens": 4096, "max_tokens": 4096, "mode": "chat", @@ -682,14 +758,22 @@ "supports_pdf_input": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "input_cost_per_token_above_200k_tokens": 6e-06, + "output_cost_per_token_above_200k_tokens": 3e-05, + "cache_creation_input_token_cost_above_200k_tokens": 7.5e-06, + "cache_read_input_token_cost_above_200k_tokens": 6e-07, + "cache_creation_input_token_cost_above_1hr": 7.5e-06, + "cache_creation_input_token_cost_above_1hr_above_200k_tokens": 1.5e-05, + "cache_creation_input_token_cost": 3.75e-06, + "cache_read_input_token_cost": 3e-07 }, "anthropic.claude-3-5-sonnet-20241022-v2:0": { "cache_creation_input_token_cost": 3.75e-06, "cache_read_input_token_cost": 3e-07, "input_cost_per_token": 3e-06, "litellm_provider": "bedrock", - "max_input_tokens": 200000, + "max_input_tokens": 1000000, "max_output_tokens": 8192, "max_tokens": 8192, "mode": "chat", @@ -701,7 +785,13 @@ "supports_prompt_caching": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "input_cost_per_token_above_200k_tokens": 6e-06, + "output_cost_per_token_above_200k_tokens": 3e-05, + "cache_creation_input_token_cost_above_200k_tokens": 7.5e-06, + "cache_read_input_token_cost_above_200k_tokens": 6e-07, + "cache_creation_input_token_cost_above_1hr": 7.5e-06, + "cache_creation_input_token_cost_above_1hr_above_200k_tokens": 1.5e-05 }, "anthropic.claude-3-7-sonnet-20240620-v1:0": { "cache_creation_input_token_cost": 4.5e-06, @@ -1441,6 +1531,14 @@ "supports_response_schema": true, "supports_tool_choice": true }, + "azure_ai/model_router": { + "input_cost_per_token": 1.4e-07, + "output_cost_per_token": 0, + "litellm_provider": "azure_ai", + "mode": "chat", + "source": "https://azure.microsoft.com/en-us/pricing/details/ai-services/", + "comment": "Flat cost of $0.14 per M input tokens for Azure AI Foundry Model Router infrastructure. Use pattern: azure_ai/model_router/ where deployment-name is your Azure deployment (e.g., azure-model-router)" + }, "azure/eu/gpt-4o-2024-08-06": { "deprecation_date": "2026-02-27", "cache_read_input_token_cost": 1.375e-06, @@ -3130,7 +3228,7 @@ "supports_reasoning": true, "supports_response_schema": true, "supports_system_messages": true, - "supports_tool_choice": false, + "supports_tool_choice": true, "supports_vision": true }, "azure/gpt-5-chat-latest": { @@ -3162,7 +3260,7 @@ "supports_reasoning": true, "supports_response_schema": true, "supports_system_messages": true, - "supports_tool_choice": false, + "supports_tool_choice": true, "supports_vision": true }, "azure/gpt-5-codex": { @@ -3650,13 +3748,12 @@ "cache_read_input_token_cost": 1.75e-07, "input_cost_per_token": 1.75e-06, "litellm_provider": "azure", - "max_input_tokens": 128000, - "max_output_tokens": 16384, - "max_tokens": 16384, - "mode": "chat", + "max_input_tokens": 272000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "responses", "output_cost_per_token": 1.4e-05, "supported_endpoints": [ - "/v1/chat/completions", "/v1/responses" ], "supported_modalities": [ @@ -6632,13 +6729,13 @@ "supports_tool_choice": true }, "cerebras/gpt-oss-120b": { - "input_cost_per_token": 2.5e-07, + "input_cost_per_token": 3.5e-07, "litellm_provider": "cerebras", "max_input_tokens": 131072, "max_output_tokens": 32768, "max_tokens": 32768, "mode": "chat", - "output_cost_per_token": 6.9e-07, + "output_cost_per_token": 7.5e-07, "source": "https://www.cerebras.ai/blog/openai-gpt-oss-120b-runs-fastest-on-cerebras", "supports_function_calling": true, "supports_parallel_function_calling": true, @@ -6656,6 +6753,7 @@ "output_cost_per_token": 8e-07, "source": "https://inference-docs.cerebras.ai/support/pricing", "supports_function_calling": true, + "supports_reasoning": true, "supports_tool_choice": true }, "cerebras/zai-glm-4.6": { @@ -9788,6 +9886,7 @@ "supports_tool_choice": true }, "deepinfra/google/gemini-2.0-flash-001": { + "deprecation_date": "2026-03-31", "max_tokens": 1000000, "max_input_tokens": 1000000, "max_output_tokens": 1000000, @@ -10231,6 +10330,48 @@ "mode": "completion", "output_cost_per_token": 5e-07 }, + "deepseek-v3-2-251201": { + "input_cost_per_token": 0.0, + "litellm_provider": "volcengine", + "max_input_tokens": 98304, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "output_cost_per_token": 0.0, + "supports_assistant_prefill": true, + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_tool_choice": true + }, + "glm-4-7-251222": { + "input_cost_per_token": 0.0, + "litellm_provider": "volcengine", + "max_input_tokens": 204800, + "max_output_tokens": 131072, + "max_tokens": 131072, + "mode": "chat", + "output_cost_per_token": 0.0, + "supports_assistant_prefill": true, + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_tool_choice": true + }, + "kimi-k2-thinking-251104": { + "input_cost_per_token": 0.0, + "litellm_provider": "volcengine", + "max_input_tokens": 229376, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "output_cost_per_token": 0.0, + "supports_assistant_prefill": true, + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_tool_choice": true + }, "doubao-embedding": { "input_cost_per_token": 0.0, "litellm_provider": "volcengine", @@ -12105,6 +12246,7 @@ }, "gemini-2.0-flash": { "cache_read_input_token_cost": 2.5e-08, + "deprecation_date": "2026-03-31", "input_cost_per_audio_token": 7e-07, "input_cost_per_token": 1e-07, "litellm_provider": "vertex_ai-language-models", @@ -12144,7 +12286,7 @@ }, "gemini-2.0-flash-001": { "cache_read_input_token_cost": 3.75e-08, - "deprecation_date": "2026-02-05", + "deprecation_date": "2026-03-31", "input_cost_per_audio_token": 1e-06, "input_cost_per_token": 1.5e-07, "litellm_provider": "vertex_ai-language-models", @@ -12230,6 +12372,7 @@ }, "gemini-2.0-flash-lite": { "cache_read_input_token_cost": 1.875e-08, + "deprecation_date": "2026-03-31", "input_cost_per_audio_token": 7.5e-08, "input_cost_per_token": 7.5e-08, "litellm_provider": "vertex_ai-language-models", @@ -12265,7 +12408,7 @@ }, "gemini-2.0-flash-lite-001": { "cache_read_input_token_cost": 1.875e-08, - "deprecation_date": "2026-02-25", + "deprecation_date": "2026-03-31", "input_cost_per_audio_token": 7.5e-08, "input_cost_per_token": 7.5e-08, "litellm_provider": "vertex_ai-language-models", @@ -13477,6 +13620,79 @@ "supports_vision": true, "supports_web_search": true }, + "gemini-robotics-er-1.5-preview": { + "cache_read_input_token_cost": 0, + "input_cost_per_token": 3e-07, + "input_cost_per_audio_token": 1e-06, + "litellm_provider": "vertex_ai-language-models", + "max_input_tokens": 1048576, + "max_output_tokens": 65535, + "max_tokens": 65535, + "mode": "chat", + "output_cost_per_token": 2.5e-06, + "output_cost_per_reasoning_token": 2.5e-06, + "source": "https://ai.google.dev/gemini-api/docs/models#gemini-robotics-er-1-5-preview", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/completions" + ], + "supported_modalities": [ + "text", + "image", + "video", + "audio" + ], + "supported_output_modalities": [ + "text" + ], + "supports_audio_output": false, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_prompt_caching": false, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_url_context": true, + "supports_vision": true + }, + "gemini/gemini-robotics-er-1.5-preview": { + "cache_read_input_token_cost": 0, + "input_cost_per_token": 3e-07, + "input_cost_per_audio_token": 1e-06, + "litellm_provider": "gemini", + "max_input_tokens": 1048576, + "max_output_tokens": 65535, + "max_tokens": 65535, + "mode": "chat", + "output_cost_per_token": 2.5e-06, + "output_cost_per_reasoning_token": 2.5e-06, + "source": "https://ai.google.dev/gemini-api/docs/models#gemini-robotics-er-1-5-preview", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/completions" + ], + "supported_modalities": [ + "text", + "image", + "video", + "audio" + ], + "supported_output_modalities": [ + "text" + ], + "supports_audio_output": false, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_prompt_caching": false, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_url_context": true, + "supports_vision": true, + "supports_web_search": true + }, "gemini-2.5-computer-use-preview-10-2025": { "input_cost_per_token": 1.25e-06, "input_cost_per_token_above_200k_tokens": 2.5e-06, @@ -13930,6 +14146,7 @@ }, "gemini/gemini-2.0-flash": { "cache_read_input_token_cost": 2.5e-08, + "deprecation_date": "2026-03-31", "input_cost_per_audio_token": 7e-07, "input_cost_per_token": 1e-07, "litellm_provider": "gemini", @@ -13970,6 +14187,7 @@ }, "gemini/gemini-2.0-flash-001": { "cache_read_input_token_cost": 2.5e-08, + "deprecation_date": "2026-03-31", "input_cost_per_audio_token": 7e-07, "input_cost_per_token": 1e-07, "litellm_provider": "gemini", @@ -14057,6 +14275,7 @@ }, "gemini/gemini-2.0-flash-lite": { "cache_read_input_token_cost": 1.875e-08, + "deprecation_date": "2026-03-31", "input_cost_per_audio_token": 7.5e-08, "input_cost_per_token": 7.5e-08, "litellm_provider": "gemini", @@ -18603,7 +18822,7 @@ "input_cost_per_token": 1.75e-06, "input_cost_per_token_priority": 3.5e-06, "litellm_provider": "openai", - "max_input_tokens": 400000, + "max_input_tokens": 272000, "max_output_tokens": 128000, "max_tokens": 128000, "mode": "responses", @@ -20543,6 +20762,7 @@ "supports_function_calling": true, "supports_tool_choice": true, "supports_prompt_caching": true, + "supports_reasoning": true, "supports_system_messages": true, "max_input_tokens": 1000000, "max_output_tokens": 8192 @@ -20557,6 +20777,7 @@ "supports_function_calling": true, "supports_tool_choice": true, "supports_prompt_caching": true, + "supports_reasoning": true, "supports_system_messages": true, "max_input_tokens": 1000000, "max_output_tokens": 8192 @@ -20571,6 +20792,7 @@ "supports_function_calling": true, "supports_tool_choice": true, "supports_prompt_caching": true, + "supports_reasoning": true, "supports_system_messages": true, "max_input_tokens": 200000, "max_output_tokens": 8192 @@ -21266,6 +21488,20 @@ "supports_tool_choice": true, "supports_web_search": true }, + "moonshot/kimi-k2.5": { + "cache_read_input_token_cost": 1e-07, + "input_cost_per_token": 6e-07, + "litellm_provider": "moonshot", + "max_input_tokens": 262144, + "max_output_tokens": 262144, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 3e-06, + "source": "https://platform.moonshot.ai/docs/pricing/chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_vision": true + }, "moonshot/kimi-latest": { "cache_read_input_token_cost": 1.5e-07, "input_cost_per_token": 2e-06, @@ -23158,6 +23394,7 @@ "supports_tool_choice": true }, "openrouter/google/gemini-2.0-flash-001": { + "deprecation_date": "2026-03-31", "input_cost_per_audio_token": 7e-07, "input_cost_per_token": 1e-07, "litellm_provider": "openrouter", @@ -23454,7 +23691,7 @@ "mode": "chat", "output_cost_per_token": 1.02e-06, "supports_function_calling": true, - "supports_prompt_caching": false, + "supports_prompt_caching": true, "supports_reasoning": true, "supports_tool_choice": true }, @@ -23590,6 +23827,20 @@ "output_cost_per_token": 6.5e-07, "supports_tool_choice": true }, + "openrouter/moonshotai/kimi-k2.5": { + "cache_read_input_token_cost": 1e-07, + "input_cost_per_token": 6e-07, + "litellm_provider": "openrouter", + "max_input_tokens": 262144, + "max_output_tokens": 262144, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 3e-06, + "source": "https://openrouter.ai/moonshotai/kimi-k2.5", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_vision": true + }, "openrouter/nousresearch/nous-hermes-llama2-13b": { "input_cost_per_token": 2e-07, "litellm_provider": "openrouter", @@ -23803,11 +24054,14 @@ "cache_read_input_token_cost": 1.75e-07, "input_cost_per_token": 1.75e-06, "litellm_provider": "openrouter", - "max_input_tokens": 400000, + "max_input_tokens": 272000, "max_output_tokens": 128000, "max_tokens": 128000, - "mode": "chat", + "mode": "responses", "output_cost_per_token": 1.4e-05, + "supported_endpoints": [ + "/v1/responses" + ], "supported_modalities": [ "text", "image" @@ -24146,6 +24400,7 @@ "output_cost_per_token": 1.75e-06, "source": "https://openrouter.ai/z-ai/glm-4.6", "supports_function_calling": true, + "supports_prompt_caching": true, "supports_reasoning": true, "supports_tool_choice": true }, @@ -24159,9 +24414,76 @@ "output_cost_per_token": 1.9e-06, "source": "https://openrouter.ai/z-ai/glm-4.6:exacto", "supports_function_calling": true, + "supports_prompt_caching": true, "supports_reasoning": true, "supports_tool_choice": true }, + "openrouter/xiaomi/mimo-v2-flash": { + "input_cost_per_token": 9e-08, + "output_cost_per_token": 2.9e-07, + "cache_creation_input_token_cost": 0.0, + "cache_read_input_token_cost": 0.0, + "litellm_provider": "openrouter", + "max_input_tokens": 262144, + "max_output_tokens": 16384, + "max_tokens": 16384, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "supports_vision": false, + "supports_prompt_caching": false + }, + "openrouter/z-ai/glm-4.7": { + "input_cost_per_token": 4e-07, + "output_cost_per_token": 1.5e-06, + "cache_creation_input_token_cost": 0.0, + "cache_read_input_token_cost": 0.0, + "litellm_provider": "openrouter", + "max_input_tokens": 202752, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "supports_vision": true, + "supports_prompt_caching": false, + "supports_assistant_prefill": true + }, + "openrouter/z-ai/glm-4.7-flash": { + "input_cost_per_token": 7e-08, + "output_cost_per_token": 4e-07, + "cache_creation_input_token_cost": 0.0, + "cache_read_input_token_cost": 0.0, + "litellm_provider": "openrouter", + "max_input_tokens": 200000, + "max_output_tokens": 32000, + "max_tokens": 32000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "supports_vision": true, + "supports_prompt_caching": false + }, + "openrouter/minimax/minimax-m2.1": { + "input_cost_per_token": 2.7e-07, + "output_cost_per_token": 1.2e-06, + "cache_creation_input_token_cost": 0.0, + "cache_read_input_token_cost": 0.0, + "litellm_provider": "openrouter", + "max_input_tokens": 204000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "supports_vision": true, + "supports_prompt_caching": false, + "supports_computer_use": false + }, "ovhcloud/DeepSeek-R1-Distill-Llama-70B": { "input_cost_per_token": 6.7e-07, "litellm_provider": "ovhcloud", @@ -26026,13 +26348,13 @@ "litellm_provider": "bedrock", "max_input_tokens": 77, "mode": "image_edit", - "output_cost_per_image": 0.40 + "output_cost_per_image": 0.4 }, "stability.stable-creative-upscale-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, "mode": "image_edit", - "output_cost_per_image": 0.60 + "output_cost_per_image": 0.6 }, "stability.stable-fast-upscale-v1:0": { "litellm_provider": "bedrock", @@ -27695,6 +28017,7 @@ "output_cost_per_token": 9e-07 }, "vercel_ai_gateway/google/gemini-2.0-flash": { + "deprecation_date": "2026-03-31", "input_cost_per_token": 1.5e-07, "litellm_provider": "vercel_ai_gateway", "max_input_tokens": 1048576, @@ -27704,6 +28027,7 @@ "output_cost_per_token": 6e-07 }, "vercel_ai_gateway/google/gemini-2.0-flash-lite": { + "deprecation_date": "2026-03-31", "input_cost_per_token": 7.5e-08, "litellm_provider": "vercel_ai_gateway", "max_input_tokens": 1048576, @@ -29475,6 +29799,7 @@ "mode": "chat", "output_cost_per_token": 1e-06, "source": "https://cloud.google.com/vertex-ai/generative-ai/pricing", + "supported_regions": ["global"], "supports_function_calling": true, "supports_tool_choice": true }, @@ -29487,6 +29812,7 @@ "mode": "chat", "output_cost_per_token": 4e-06, "source": "https://cloud.google.com/vertex-ai/generative-ai/pricing", + "supported_regions": ["global"], "supports_function_calling": true, "supports_tool_choice": true }, @@ -29499,6 +29825,7 @@ "mode": "chat", "output_cost_per_token": 1.2e-06, "source": "https://cloud.google.com/vertex-ai/generative-ai/pricing", + "supported_regions": ["global"], "supports_function_calling": true, "supports_tool_choice": true }, @@ -29511,6 +29838,7 @@ "mode": "chat", "output_cost_per_token": 1.2e-06, "source": "https://cloud.google.com/vertex-ai/generative-ai/pricing", + "supported_regions": ["global"], "supports_function_calling": true, "supports_tool_choice": true }, @@ -30373,6 +30701,7 @@ "supports_web_search": true }, "xai/grok-3": { + "cache_read_input_token_cost": 7.5e-07, "input_cost_per_token": 3e-06, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30387,6 +30716,7 @@ "supports_web_search": true }, "xai/grok-3-beta": { + "cache_read_input_token_cost": 7.5e-07, "input_cost_per_token": 3e-06, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30401,6 +30731,7 @@ "supports_web_search": true }, "xai/grok-3-fast-beta": { + "cache_read_input_token_cost": 1.25e-06, "input_cost_per_token": 5e-06, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30415,6 +30746,7 @@ "supports_web_search": true }, "xai/grok-3-fast-latest": { + "cache_read_input_token_cost": 1.25e-06, "input_cost_per_token": 5e-06, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30429,6 +30761,7 @@ "supports_web_search": true }, "xai/grok-3-latest": { + "cache_read_input_token_cost": 7.5e-07, "input_cost_per_token": 3e-06, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30443,6 +30776,7 @@ "supports_web_search": true }, "xai/grok-3-mini": { + "cache_read_input_token_cost": 7.5e-08, "input_cost_per_token": 3e-07, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30458,6 +30792,7 @@ "supports_web_search": true }, "xai/grok-3-mini-beta": { + "cache_read_input_token_cost": 7.5e-08, "input_cost_per_token": 3e-07, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30473,6 +30808,7 @@ "supports_web_search": true }, "xai/grok-3-mini-fast": { + "cache_read_input_token_cost": 1.5e-07, "input_cost_per_token": 6e-07, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30488,6 +30824,7 @@ "supports_web_search": true }, "xai/grok-3-mini-fast-beta": { + "cache_read_input_token_cost": 1.5e-07, "input_cost_per_token": 6e-07, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30503,6 +30840,7 @@ "supports_web_search": true }, "xai/grok-3-mini-fast-latest": { + "cache_read_input_token_cost": 1.5e-07, "input_cost_per_token": 6e-07, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30518,6 +30856,7 @@ "supports_web_search": true }, "xai/grok-3-mini-latest": { + "cache_read_input_token_cost": 7.5e-08, "input_cost_per_token": 3e-07, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30784,11 +31123,14 @@ "max_output_tokens": 128000, "mode": "chat", "supports_function_calling": true, + "supports_prompt_caching": true, "supports_reasoning": true, "supports_tool_choice": true, "source": "https://docs.z.ai/guides/overview/pricing" }, "zai/glm-4.6": { + "cache_creation_input_token_cost": 0, + "cache_read_input_token_cost": 1.1e-07, "input_cost_per_token": 6e-07, "output_cost_per_token": 2.2e-06, "litellm_provider": "zai", @@ -30796,6 +31138,8 @@ "max_output_tokens": 128000, "mode": "chat", "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, "supports_tool_choice": true, "source": "https://docs.z.ai/guides/overview/pricing" }, @@ -34443,4 +34787,4 @@ "output_cost_per_token": 0, "supports_reasoning": true } -} +} \ No newline at end of file diff --git a/litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py b/litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py index ded591a8f53..56feff548ad 100644 --- a/litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py +++ b/litellm/proxy/_experimental/mcp_server/discoverable_endpoints.py @@ -387,25 +387,57 @@ async def callback(code: str, state: str): 1. Try resource_metadata from WWW-Authenticate header (if present) 2. Fall back to path-based well-known URI: /.well-known/oauth-protected-resource/{path} ( - If the resource identifier value contains a path or query component, any terminating slash (/) - following the host component MUST be removed before inserting /.well-known/ and the well-known - URI path suffix between the host component and the path(include root path) and/or query components. + If the resource identifier value contains a path or query component, any terminating slash (/) + following the host component MUST be removed before inserting /.well-known/ and the well-known + URI path suffix between the host component and the path(include root path) and/or query components. https://datatracker.ietf.org/doc/html/rfc9728#section-3.1) 3. Fall back to root-based well-known URI: /.well-known/oauth-protected-resource + + Dual Pattern Support: + - Standard MCP pattern: /mcp/{server_name} (recommended, used by mcp-inspector, VSCode Copilot) + - LiteLLM legacy pattern: /{server_name}/mcp (backward compatibility) + + The resource URL returned matches the pattern used in the discovery request. """ -@router.get(f"/.well-known/oauth-protected-resource{'' if get_server_root_path() == '/' else get_server_root_path()}/{{mcp_server_name}}/mcp") -@router.get("/.well-known/oauth-protected-resource") -async def oauth_protected_resource_mcp( - request: Request, mcp_server_name: Optional[str] = None -): + + +def _build_oauth_protected_resource_response( + request: Request, + mcp_server_name: Optional[str], + use_standard_pattern: bool, +) -> dict: + """ + Build OAuth protected resource response with the appropriate URL pattern. + + Args: + request: FastAPI Request object + mcp_server_name: Name of the MCP server + use_standard_pattern: If True, use /mcp/{server_name} pattern; + if False, use /{server_name}/mcp pattern + + Returns: + OAuth protected resource metadata dict + """ from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( global_mcp_server_manager, ) - # Get the correct base URL considering X-Forwarded-* headers + request_base_url = get_request_base_url(request) mcp_server: Optional[MCPServer] = None if mcp_server_name: mcp_server = global_mcp_server_manager.get_mcp_server_by_name(mcp_server_name) + + # Build resource URL based on the pattern + if mcp_server_name: + if use_standard_pattern: + # Standard MCP pattern: /mcp/{server_name} + resource_url = f"{request_base_url}/mcp/{mcp_server_name}" + else: + # LiteLLM legacy pattern: /{server_name}/mcp + resource_url = f"{request_base_url}/{mcp_server_name}/mcp" + else: + resource_url = f"{request_base_url}/mcp" + return { "authorization_servers": [ ( @@ -414,14 +446,55 @@ async def oauth_protected_resource_mcp( else f"{request_base_url}" ) ], - "resource": ( - f"{request_base_url}/{mcp_server_name}/mcp" - if mcp_server_name - else f"{request_base_url}/mcp" - ), # this is what Claude will call + "resource": resource_url, "scopes_supported": mcp_server.scopes if mcp_server else [], } + +# Standard MCP pattern: /.well-known/oauth-protected-resource/mcp/{server_name} +# This is the pattern expected by standard MCP clients (mcp-inspector, VSCode Copilot) +@router.get(f"/.well-known/oauth-protected-resource{'' if get_server_root_path() == '/' else get_server_root_path()}/mcp/{{mcp_server_name}}") +async def oauth_protected_resource_mcp_standard( + request: Request, mcp_server_name: str +): + """ + OAuth protected resource discovery endpoint using standard MCP URL pattern. + + Standard pattern: /mcp/{server_name} + Discovery path: /.well-known/oauth-protected-resource/mcp/{server_name} + + This endpoint is compliant with MCP specification and works with standard + MCP clients like mcp-inspector and VSCode Copilot. + """ + return _build_oauth_protected_resource_response( + request=request, + mcp_server_name=mcp_server_name, + use_standard_pattern=True, + ) + + +# LiteLLM legacy pattern: /.well-known/oauth-protected-resource/{server_name}/mcp +# Kept for backward compatibility with existing deployments +@router.get(f"/.well-known/oauth-protected-resource{'' if get_server_root_path() == '/' else get_server_root_path()}/{{mcp_server_name}}/mcp") +@router.get("/.well-known/oauth-protected-resource") +async def oauth_protected_resource_mcp( + request: Request, mcp_server_name: Optional[str] = None +): + """ + OAuth protected resource discovery endpoint using LiteLLM legacy URL pattern. + + Legacy pattern: /{server_name}/mcp + Discovery path: /.well-known/oauth-protected-resource/{server_name}/mcp + + This endpoint is kept for backward compatibility. New integrations should + use the standard MCP pattern (/mcp/{server_name}) instead. + """ + return _build_oauth_protected_resource_response( + request=request, + mcp_server_name=mcp_server_name, + use_standard_pattern=False, + ) + """ https://datatracker.ietf.org/doc/html/rfc8414#section-3.1 RFC 8414: Path-aware OAuth discovery @@ -430,15 +503,26 @@ async def oauth_protected_resource_mcp( the well-known URI suffix between the host component and the path(include root path) component. """ -@router.get(f"/.well-known/oauth-authorization-server{'' if get_server_root_path() == '/' else get_server_root_path()}/{{mcp_server_name}}") -@router.get("/.well-known/oauth-authorization-server") -async def oauth_authorization_server_mcp( - request: Request, mcp_server_name: Optional[str] = None -): + + +def _build_oauth_authorization_server_response( + request: Request, + mcp_server_name: Optional[str], +) -> dict: + """ + Build OAuth authorization server metadata response. + + Args: + request: FastAPI Request object + mcp_server_name: Name of the MCP server + + Returns: + OAuth authorization server metadata dict + """ from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( global_mcp_server_manager, ) - # Get the correct base URL considering X-Forwarded-* headers + request_base_url = get_request_base_url(request) authorization_endpoint = ( @@ -470,18 +554,58 @@ async def oauth_authorization_server_mcp( } +# Standard MCP pattern: /.well-known/oauth-authorization-server/mcp/{server_name} +@router.get(f"/.well-known/oauth-authorization-server{'' if get_server_root_path() == '/' else get_server_root_path()}/mcp/{{mcp_server_name}}") +async def oauth_authorization_server_mcp_standard( + request: Request, mcp_server_name: str +): + """ + OAuth authorization server discovery endpoint using standard MCP URL pattern. + + Standard pattern: /mcp/{server_name} + Discovery path: /.well-known/oauth-authorization-server/mcp/{server_name} + """ + return _build_oauth_authorization_server_response( + request=request, + mcp_server_name=mcp_server_name, + ) + + +# LiteLLM legacy pattern and root endpoint +@router.get(f"/.well-known/oauth-authorization-server{'' if get_server_root_path() == '/' else get_server_root_path()}/{{mcp_server_name}}") +@router.get("/.well-known/oauth-authorization-server") +async def oauth_authorization_server_mcp( + request: Request, mcp_server_name: Optional[str] = None +): + """ + OAuth authorization server discovery endpoint. + + Supports both legacy pattern (/{server_name}) and root endpoint. + """ + return _build_oauth_authorization_server_response( + request=request, + mcp_server_name=mcp_server_name, + ) + + # Alias for standard OpenID discovery @router.get("/.well-known/openid-configuration") async def openid_configuration(request: Request): return await oauth_authorization_server_mcp(request) +# Additional legacy pattern support @router.get("/.well-known/oauth-authorization-server/{mcp_server_name}/mcp") -@router.get("/.well-known/oauth-authorization-server") -async def oauth_authorization_server_root( - request: Request, mcp_server_name: Optional[str] = None +async def oauth_authorization_server_legacy( + request: Request, mcp_server_name: str ): - return await oauth_authorization_server_mcp(request, mcp_server_name) + """ + OAuth authorization server discovery for legacy /{server_name}/mcp pattern. + """ + return _build_oauth_authorization_server_response( + request=request, + mcp_server_name=mcp_server_name, + ) @router.post("/{mcp_server_name}/register") diff --git a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py index e0217cd9e00..4c17a2ff3e0 100644 --- a/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py +++ b/litellm/proxy/_experimental/mcp_server/mcp_server_manager.py @@ -11,7 +11,7 @@ import datetime import hashlib import json import re -from typing import Any, Dict, List, Literal, Optional, Set, Tuple, Union, cast +from typing import Any, Callable, Dict, List, Literal, Optional, Set, Tuple, Union, cast from urllib.parse import urlparse from fastapi import HTTPException @@ -30,7 +30,6 @@ from pydantic import AnyUrl import litellm from litellm._logging import verbose_logger -from litellm.types.utils import CallTypes from litellm.exceptions import BlockedPiiEntityError, GuardrailRaisedException from litellm.experimental_mcp_client.client import MCPClient from litellm.llms.custom_httpx.http_handler import get_async_httpx_client @@ -63,7 +62,23 @@ from litellm.types.mcp_server.mcp_server_manager import ( MCPOAuthMetadata, MCPServer, ) -from mcp.shared.tool_name_validation import SEP_986_URL, validate_tool_name +from litellm.types.utils import CallTypes + +try: + from mcp.shared.tool_name_validation import ( # type: ignore + SEP_986_URL, + validate_tool_name, + ) +except ImportError: + from pydantic import BaseModel + SEP_986_URL = "https://github.com/modelcontextprotocol/protocol/blob/main/proposals/0001-tool-name-validation.md" + + class ToolNameValidationResult(BaseModel): + is_valid: bool = True + warnings: list = [] + + def validate_tool_name(name: str) -> ToolNameValidationResult: # type: ignore[misc] + return ToolNameValidationResult() # Probe includes characters on both sides of the separator to mimic real prefixed tool names. @@ -90,7 +105,9 @@ def _warn_on_server_name_fields( if result.is_valid: return - warning_text = "; ".join(result.warnings) if result.warnings else "Validation failed" + warning_text = ( + "; ".join(result.warnings) if result.warnings else "Validation failed" + ) verbose_logger.warning( "MCP server '%s' has invalid %s '%s': %s", server_id, @@ -103,7 +120,6 @@ def _warn_on_server_name_fields( _warn("server_name", server_name) - def _deserialize_json_dict(data: Any) -> Optional[Dict[str, str]]: """ Deserialize optional JSON mappings stored in the database. @@ -391,10 +407,13 @@ class MCPServerManager: # Note: `extra_headers` on MCPServer is a List[str] of header names to forward # from the client request (not available in this OpenAPI tool generation step). # `static_headers` is a dict of concrete headers to always send. - headers = merge_mcp_headers( - extra_headers=headers, - static_headers=server.static_headers, - ) or {} + headers = ( + merge_mcp_headers( + extra_headers=headers, + static_headers=server.static_headers, + ) + or {} + ) verbose_logger.debug( f"Using headers for OpenAPI tools (excluding sensitive values): " @@ -1825,6 +1844,7 @@ class MCPServerManager: oauth2_headers: Optional[Dict[str, str]], raw_headers: Optional[Dict[str, str]], proxy_logging_obj: Optional[ProxyLogging], + host_progress_callback: Optional[Callable] = None, ) -> CallToolResult: """ Call a regular MCP tool using the MCP client. @@ -1909,7 +1929,7 @@ class MCPServerManager: ) async def _call_tool_via_client(client, params): - return await client.call_tool(params) + return await client.call_tool(params, host_progress_callback=host_progress_callback) tasks.append( asyncio.create_task(_call_tool_via_client(client, call_tool_params)) @@ -1946,6 +1966,8 @@ class MCPServerManager: proxy_logging_obj: Optional[ProxyLogging] = None, oauth2_headers: Optional[Dict[str, str]] = None, raw_headers: Optional[Dict[str, str]] = None, + host_progress_callback: Optional[Callable] = None, + ) -> CallToolResult: """ Call a tool with the given name and arguments @@ -2021,6 +2043,7 @@ class MCPServerManager: oauth2_headers=oauth2_headers, raw_headers=raw_headers, proxy_logging_obj=proxy_logging_obj, + host_progress_callback=host_progress_callback, ) # For OpenAPI tools, await outside the client context diff --git a/litellm/proxy/_experimental/mcp_server/semantic_tool_filter.py b/litellm/proxy/_experimental/mcp_server/semantic_tool_filter.py new file mode 100644 index 00000000000..e5cb6a0098d --- /dev/null +++ b/litellm/proxy/_experimental/mcp_server/semantic_tool_filter.py @@ -0,0 +1,250 @@ +""" +Semantic MCP Tool Filtering using semantic-router + +Filters MCP tools semantically for /chat/completions and /responses endpoints. +""" +from typing import TYPE_CHECKING, Any, Dict, List, Optional + +from litellm._logging import verbose_logger + +if TYPE_CHECKING: + from semantic_router.routers import SemanticRouter + + from litellm.router import Router + + +class SemanticMCPToolFilter: + """Filters MCP tools using semantic similarity to reduce context window size.""" + + def __init__( + self, + embedding_model: str, + litellm_router_instance: "Router", + top_k: int = 10, + similarity_threshold: float = 0.3, + enabled: bool = True, + ): + """ + Initialize the semantic tool filter. + + Args: + embedding_model: Model to use for embeddings (e.g., "text-embedding-3-small") + litellm_router_instance: Router instance for embedding generation + top_k: Maximum number of tools to return + similarity_threshold: Minimum similarity score for filtering + enabled: Whether filtering is enabled + """ + self.enabled = enabled + self.top_k = top_k + self.similarity_threshold = similarity_threshold + self.embedding_model = embedding_model + self.router_instance = litellm_router_instance + self.tool_router: Optional["SemanticRouter"] = None + self._tool_map: Dict[str, Any] = {} # MCPTool objects or OpenAI function dicts + + async def build_router_from_mcp_registry(self) -> None: + """Build semantic router from all MCP tools in the registry (no auth checks).""" + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + global_mcp_server_manager, + ) + + try: + # Get all servers from registry without auth checks + registry = global_mcp_server_manager.get_registry() + if not registry: + verbose_logger.warning("MCP registry is empty") + self.tool_router = None + return + + # Fetch tools from all servers in parallel + all_tools = [] + for server_id, server in registry.items(): + try: + tools = await global_mcp_server_manager.get_tools_for_server(server_id) + all_tools.extend(tools) + except Exception as e: + verbose_logger.warning(f"Failed to fetch tools from server {server_id}: {e}") + continue + + if not all_tools: + verbose_logger.warning("No MCP tools found in registry") + self.tool_router = None + return + + verbose_logger.info(f"Fetched {len(all_tools)} tools from {len(registry)} MCP servers") + self._build_router(all_tools) + + except Exception as e: + verbose_logger.error(f"Failed to build router from MCP registry: {e}") + self.tool_router = None + raise + + def _extract_tool_info(self, tool) -> tuple[str, str]: + """Extract name and description from MCP tool or OpenAI function dict.""" + name: str + description: str + + if isinstance(tool, dict): + # OpenAI function format + name = tool.get("name", "") + description = tool.get("description", name) + else: + # MCPTool object + name = str(tool.name) + description = str(tool.description) if tool.description else str(tool.name) + + return name, description + + def _build_router(self, tools: List) -> None: + """Build semantic router with tools (MCPTool objects or OpenAI function dicts).""" + from semantic_router.routers import SemanticRouter + from semantic_router.routers.base import Route + + from litellm.router_strategy.auto_router.litellm_encoder import ( + LiteLLMRouterEncoder, + ) + + if not tools: + self.tool_router = None + return + + try: + # Convert tools to routes + routes = [] + self._tool_map = {} + + for tool in tools: + name, description = self._extract_tool_info(tool) + self._tool_map[name] = tool + + routes.append( + Route( + name=name, + description=description, + utterances=[description], + score_threshold=self.similarity_threshold, + ) + ) + + self.tool_router = SemanticRouter( + routes=routes, + encoder=LiteLLMRouterEncoder( + litellm_router_instance=self.router_instance, + model_name=self.embedding_model, + score_threshold=self.similarity_threshold, + ), + auto_sync="local", + ) + + verbose_logger.info( + f"Built semantic router with {len(routes)} tools" + ) + + except Exception as e: + verbose_logger.error(f"Failed to build semantic router: {e}") + self.tool_router = None + raise + + async def filter_tools( + self, + query: str, + available_tools: List[Any], + top_k: Optional[int] = None, + ) -> List[Any]: + """ + Filter tools semantically based on query. + + Args: + query: User query to match against tools + available_tools: Full list of available MCP tools + top_k: Override default top_k (optional) + + Returns: + Filtered and ordered list of tools (up to top_k) + """ + # Early returns for cases where we can't/shouldn't filter + if not self.enabled: + return available_tools + + if not available_tools: + return available_tools + + if not query or not query.strip(): + return available_tools + + # Router should be built on startup - if not, something went wrong + if self.tool_router is None: + verbose_logger.warning("Router not initialized - was build_router_from_mcp_registry() called on startup?") + return available_tools + + # Run semantic filtering + try: + limit = top_k or self.top_k + matches = self.tool_router(text=query, limit=limit) + matched_tool_names = self._extract_tool_names_from_matches(matches) + + if not matched_tool_names: + return available_tools + + return self._get_tools_by_names(matched_tool_names, available_tools) + + except Exception as e: + verbose_logger.error(f"Semantic tool filter failed: {e}", exc_info=True) + return available_tools + + def _extract_tool_names_from_matches(self, matches) -> List[str]: + """Extract tool names from semantic router match results.""" + if not matches: + return [] + + # Handle single match + if hasattr(matches, "name") and matches.name: + return [matches.name] + + # Handle list of matches + if isinstance(matches, list): + return [m.name for m in matches if hasattr(m, "name") and m.name] + + return [] + + def _get_tools_by_names( + self, tool_names: List[str], available_tools: List[Any] + ) -> List[Any]: + """Get tools from available_tools by their names, preserving order.""" + # Match tools from available_tools (preserves format - dict or MCPTool) + matched_tools = [] + for tool in available_tools: + tool_name, _ = self._extract_tool_info(tool) + if tool_name in tool_names: + matched_tools.append(tool) + + # Reorder to match semantic router's ordering + tool_map = {self._extract_tool_info(t)[0]: t for t in matched_tools} + return [tool_map[name] for name in tool_names if name in tool_map] + + def extract_user_query(self, messages: List[Dict[str, Any]]) -> str: + """ + Extract user query from messages for /chat/completions or /responses. + + Args: + messages: List of message dictionaries (from 'messages' or 'input' field) + + Returns: + Extracted query string + """ + for msg in reversed(messages): + if msg.get("role") == "user": + content = msg.get("content", "") + + if isinstance(content, str): + return content + + if isinstance(content, list): + texts = [ + block.get("text", "") if isinstance(block, dict) else str(block) + for block in content + if isinstance(block, (dict, str)) + ] + return " ".join(texts) + + return "" diff --git a/litellm/proxy/_experimental/mcp_server/server.py b/litellm/proxy/_experimental/mcp_server/server.py index 03652ae155e..79cd88227a9 100644 --- a/litellm/proxy/_experimental/mcp_server/server.py +++ b/litellm/proxy/_experimental/mcp_server/server.py @@ -8,8 +8,7 @@ import contextlib from datetime import datetime import traceback import uuid -from typing import Any, AsyncIterator, Dict, List, Optional, Tuple, Union, cast - +from typing import Any, AsyncIterator, Dict, List, Optional, Tuple, Union, cast, Callable from fastapi import FastAPI, HTTPException from pydantic import AnyUrl, ConfigDict from starlette.types import Receive, Scope, Send @@ -74,7 +73,11 @@ if MCP_AVAILABLE: AuthContextMiddleware, auth_context_var, ) - from mcp.server.streamable_http_manager import StreamableHTTPSessionManager + + try: + from mcp.server.streamable_http_manager import StreamableHTTPSessionManager + except ImportError: + StreamableHTTPSessionManager = None # type: ignore from mcp.types import ( CallToolResult, EmbeddedResource, @@ -124,8 +127,8 @@ if MCP_AVAILABLE: session_manager = StreamableHTTPSessionManager( app=server, event_store=None, - json_response=True, # Use JSON responses instead of SSE by default - stateless=True, + json_response=False, # enables SSE streaming + stateless=False, # enables session state ) # Create SSE session manager @@ -278,6 +281,30 @@ if MCP_AVAILABLE: verbose_logger.debug( f"MCP mcp_server_tool_call - User API Key Auth from context: {user_api_key_auth}" ) + host_progress_callback = None + try: + host_ctx = server.request_context + if host_ctx and hasattr(host_ctx, 'meta') and host_ctx.meta: + host_token = getattr(host_ctx.meta, 'progressToken', None) + if host_token and hasattr(host_ctx, 'session') and host_ctx.session: + host_session = host_ctx.session + + async def forward_progress(progress: float, total: float | None): + """Forward progress notifications from external MCP to Host""" + try: + await host_session.send_progress_notification( + progress_token=host_token, + progress=progress, + total=total + ) + verbose_logger.debug(f"Forwarded progress {progress}/{total} to Host") + except Exception as e: + verbose_logger.error(f"Failed to forward progress to Host: {e}") + + host_progress_callback = forward_progress + verbose_logger.debug(f"Host progressToken captured: {host_token[:8]}...") + except Exception as e: + verbose_logger.warning(f"Could not capture host progress context: {e}") try: # Create a body date for logging body_data = {"name": name, "arguments": arguments} @@ -307,6 +334,7 @@ if MCP_AVAILABLE: mcp_server_auth_headers=mcp_server_auth_headers, oauth2_headers=oauth2_headers, raw_headers=raw_headers, + host_progress_callback=host_progress_callback, **data, # for logging ) except BlockedPiiEntityError as e: @@ -1341,6 +1369,7 @@ if MCP_AVAILABLE: mcp_server_auth_headers: Optional[Dict[str, Dict[str, str]]] = None, oauth2_headers: Optional[Dict[str, str]] = None, raw_headers: Optional[Dict[str, str]] = None, + host_progress_callback: Optional[Callable] = None, **kwargs: Any, ) -> CallToolResult: """ @@ -1438,6 +1467,7 @@ if MCP_AVAILABLE: oauth2_headers=oauth2_headers, raw_headers=raw_headers, litellm_logging_obj=litellm_logging_obj, + host_progress_callback=host_progress_callback, ) # Fall back to local tool registry with original name (legacy support) @@ -1685,6 +1715,7 @@ if MCP_AVAILABLE: oauth2_headers: Optional[Dict[str, str]] = None, raw_headers: Optional[Dict[str, str]] = None, litellm_logging_obj: Optional[Any] = None, + host_progress_callback: Optional[Callable] = None, ) -> CallToolResult: """Handle tool execution for managed server tools""" # Import here to avoid circular import @@ -1700,6 +1731,7 @@ if MCP_AVAILABLE: oauth2_headers=oauth2_headers, raw_headers=raw_headers, proxy_logging_obj=proxy_logging_obj, + host_progress_callback=host_progress_callback, ) verbose_logger.debug("CALL TOOL RESULT: %s", call_tool_result) return call_tool_result @@ -1808,6 +1840,43 @@ if MCP_AVAILABLE: raw_headers, ) + def _strip_stale_mcp_session_header( + scope: Scope, + mgr: "StreamableHTTPSessionManager", + ) -> None: + """ + Strip stale ``mcp-session-id`` headers so the session manager + creates a fresh session instead of returning 404 "Session not found". + + When clients like VSCode reconnect after a reload they may resend a + session id that has already been cleaned up. Rather than letting the + SDK return a 404 error loop, we detect the stale id and remove the + header so a brand-new session is created transparently. + + Fixes https://github.com/BerriAI/litellm/issues/20292 + """ + _mcp_session_header = b"mcp-session-id" + _session_id: Optional[str] = None + for header_name, header_value in scope.get("headers", []): + if header_name == _mcp_session_header: + _session_id = header_value.decode("utf-8", errors="replace") + break + + if _session_id is None: + return + + known_sessions = getattr(mgr, "_server_instances", None) + if known_sessions is not None and _session_id not in known_sessions: + verbose_logger.warning( + "MCP session ID '%s' not found in active sessions. " + "Stripping stale header to force new session creation.", + _session_id, + ) + scope["headers"] = [ + (k, v) for k, v in scope["headers"] + if k != _mcp_session_header + ] + async def handle_streamable_http_mcp( scope: Scope, receive: Receive, send: Send ) -> None: @@ -1864,6 +1933,8 @@ if MCP_AVAILABLE: # Give it a moment to start up await asyncio.sleep(0.1) + _strip_stale_mcp_session_header(scope, session_manager) + await session_manager.handle_request(scope, receive, send) except Exception as e: raise e diff --git a/litellm/proxy/_experimental/out/404.html b/litellm/proxy/_experimental/out/404.html new file mode 100644 index 00000000000..a8bd30680ab --- /dev/null +++ b/litellm/proxy/_experimental/out/404.html @@ -0,0 +1 @@ +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/static/zHD7JXLXiWgn1NPp82VmF/_buildManifest.js b/litellm/proxy/_experimental/out/_next/static/MkHZcSjEBwlJY7dIHtt6n/_buildManifest.js similarity index 100% rename from litellm/proxy/_experimental/out/_next/static/zHD7JXLXiWgn1NPp82VmF/_buildManifest.js rename to litellm/proxy/_experimental/out/_next/static/MkHZcSjEBwlJY7dIHtt6n/_buildManifest.js diff --git a/litellm/proxy/_experimental/out/_next/static/zHD7JXLXiWgn1NPp82VmF/_ssgManifest.js b/litellm/proxy/_experimental/out/_next/static/MkHZcSjEBwlJY7dIHtt6n/_ssgManifest.js similarity index 100% rename from litellm/proxy/_experimental/out/_next/static/zHD7JXLXiWgn1NPp82VmF/_ssgManifest.js rename to litellm/proxy/_experimental/out/_next/static/MkHZcSjEBwlJY7dIHtt6n/_ssgManifest.js diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1059-26bdac09bbb12a4b.js b/litellm/proxy/_experimental/out/_next/static/chunks/1059-26bdac09bbb12a4b.js new file mode 100644 index 00000000000..cd4f5e4e326 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/1059-26bdac09bbb12a4b.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1059],{83669:function(t,e,r){r.d(e,{Z:function(){return c}});var n=r(1119),o=r(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M699 353h-46.9c-10.2 0-19.9 4.9-25.9 13.3L469 584.3l-71.2-98.8c-6-8.3-15.6-13.3-25.9-13.3H325c-6.5 0-10.3 7.4-6.5 12.7l124.6 172.8a31.8 31.8 0 0051.7 0l210.6-292c3.9-5.3.1-12.7-6.4-12.7z"}},{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}}]},name:"check-circle",theme:"outlined"},i=r(55015),c=o.forwardRef(function(t,e){return o.createElement(i.Z,(0,n.Z)({},t,{ref:e,icon:a}))})},62670:function(t,e,r){r.d(e,{Z:function(){return c}});var n=r(1119),o=r(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372zm47.7-395.2l-25.4-5.9V348.6c38 5.2 61.5 29 65.5 58.2.5 4 3.9 6.9 7.9 6.9h44.9c4.7 0 8.4-4.1 8-8.8-6.1-62.3-57.4-102.3-125.9-109.2V263c0-4.4-3.6-8-8-8h-28.1c-4.4 0-8 3.6-8 8v33c-70.8 6.9-126.2 46-126.2 119 0 67.6 49.8 100.2 102.1 112.7l24.7 6.3v142.7c-44.2-5.9-69-29.5-74.1-61.3-.6-3.8-4-6.6-7.9-6.6H363c-4.7 0-8.4 4-8 8.7 4.5 55 46.2 105.6 135.2 112.1V761c0 4.4 3.6 8 8 8h28.4c4.4 0 8-3.6 8-8.1l-.2-31.7c78.3-6.9 134.3-48.8 134.3-124-.1-69.4-44.2-100.4-109-116.4zm-68.6-16.2c-5.6-1.6-10.3-3.1-15-5-33.8-12.2-49.5-31.9-49.5-57.3 0-36.3 27.5-57 64.5-61.7v124zM534.3 677V543.3c3.1.9 5.9 1.6 8.8 2.2 47.3 14.4 63.2 34.4 63.2 65.1 0 39.1-29.4 62.6-72 66.4z"}}]},name:"dollar",theme:"outlined"},i=r(55015),c=o.forwardRef(function(t,e){return o.createElement(i.Z,(0,n.Z)({},t,{ref:e,icon:a}))})},45246:function(t,e,r){r.d(e,{Z:function(){return c}});var n=r(1119),o=r(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M696 480H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h368c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z"}},{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}}]},name:"minus-circle",theme:"outlined"},i=r(55015),c=o.forwardRef(function(t,e){return o.createElement(i.Z,(0,n.Z)({},t,{ref:e,icon:a}))})},89245:function(t,e,r){r.d(e,{Z:function(){return c}});var n=r(1119),o=r(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M909.1 209.3l-56.4 44.1C775.8 155.1 656.2 92 521.9 92 290 92 102.3 279.5 102 511.5 101.7 743.7 289.8 932 521.9 932c181.3 0 335.8-115 394.6-276.1 1.5-4.2-.7-8.9-4.9-10.3l-56.7-19.5a8 8 0 00-10.1 4.8c-1.8 5-3.8 10-5.9 14.9-17.3 41-42.1 77.8-73.7 109.4A344.77 344.77 0 01655.9 829c-42.3 17.9-87.4 27-133.8 27-46.5 0-91.5-9.1-133.8-27A341.5 341.5 0 01279 755.2a342.16 342.16 0 01-73.7-109.4c-17.9-42.4-27-87.4-27-133.9s9.1-91.5 27-133.9c17.3-41 42.1-77.8 73.7-109.4 31.6-31.6 68.4-56.4 109.3-73.8 42.3-17.9 87.4-27 133.8-27 46.5 0 91.5 9.1 133.8 27a341.5 341.5 0 01109.3 73.8c9.9 9.9 19.2 20.4 27.8 31.4l-60.2 47a8 8 0 003 14.1l175.6 43c5 1.2 9.9-2.6 9.9-7.7l.8-180.9c-.1-6.6-7.8-10.3-13-6.2z"}}]},name:"reload",theme:"outlined"},i=r(55015),c=o.forwardRef(function(t,e){return o.createElement(i.Z,(0,n.Z)({},t,{ref:e,icon:a}))})},77565:function(t,e,r){r.d(e,{Z:function(){return c}});var n=r(1119),o=r(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M765.7 486.8L314.9 134.7A7.97 7.97 0 00302 141v77.3c0 4.9 2.3 9.6 6.1 12.6l360 281.1-360 281.1c-3.9 3-6.1 7.7-6.1 12.6V883c0 6.7 7.7 10.4 12.9 6.3l450.8-352.1a31.96 31.96 0 000-50.4z"}}]},name:"right",theme:"outlined"},i=r(55015),c=o.forwardRef(function(t,e){return o.createElement(i.Z,(0,n.Z)({},t,{ref:e,icon:a}))})},69993:function(t,e,r){r.d(e,{Z:function(){return c}});var n=r(1119),o=r(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M300 328a60 60 0 10120 0 60 60 0 10-120 0zM852 64H172c-17.7 0-32 14.3-32 32v660c0 17.7 14.3 32 32 32h680c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-32 660H204V128h616v596zM604 328a60 60 0 10120 0 60 60 0 10-120 0zm250.2 556H169.8c-16.5 0-29.8 14.3-29.8 32v36c0 4.4 3.3 8 7.4 8h729.1c4.1 0 7.4-3.6 7.4-8v-36c.1-17.7-13.2-32-29.7-32zM664 508H360c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h304c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"}}]},name:"robot",theme:"outlined"},i=r(55015),c=o.forwardRef(function(t,e){return o.createElement(i.Z,(0,n.Z)({},t,{ref:e,icon:a}))})},58630:function(t,e,r){r.d(e,{Z:function(){return c}});var n=r(1119),o=r(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M876.6 239.5c-.5-.9-1.2-1.8-2-2.5-5-5-13.1-5-18.1 0L684.2 409.3l-67.9-67.9L788.7 169c.8-.8 1.4-1.6 2-2.5 3.6-6.1 1.6-13.9-4.5-17.5-98.2-58-226.8-44.7-311.3 39.7-67 67-89.2 162-66.5 247.4l-293 293c-3 3-2.8 7.9.3 11l169.7 169.7c3.1 3.1 8.1 3.3 11 .3l292.9-292.9c85.5 22.8 180.5.7 247.6-66.4 84.4-84.5 97.7-213.1 39.7-311.3zM786 499.8c-58.1 58.1-145.3 69.3-214.6 33.6l-8.8 8.8-.1-.1-274 274.1-79.2-79.2 230.1-230.1s0 .1.1.1l52.8-52.8c-35.7-69.3-24.5-156.5 33.6-214.6a184.2 184.2 0 01144-53.5L537 318.9a32.05 32.05 0 000 45.3l124.5 124.5a32.05 32.05 0 0045.3 0l132.8-132.8c3.7 51.8-14.4 104.8-53.6 143.9z"}}]},name:"tool",theme:"outlined"},i=r(55015),c=o.forwardRef(function(t,e){return o.createElement(i.Z,(0,n.Z)({},t,{ref:e,icon:a}))})},47323:function(t,e,r){r.d(e,{Z:function(){return b}});var n=r(5853),o=r(2265),a=r(47187),i=r(7084),c=r(13241),l=r(1153),s=r(26898);let d={xs:{paddingX:"px-1.5",paddingY:"py-1.5"},sm:{paddingX:"px-1.5",paddingY:"py-1.5"},md:{paddingX:"px-2",paddingY:"py-2"},lg:{paddingX:"px-2",paddingY:"py-2"},xl:{paddingX:"px-2.5",paddingY:"py-2.5"}},u={xs:{height:"h-3",width:"w-3"},sm:{height:"h-5",width:"w-5"},md:{height:"h-5",width:"w-5"},lg:{height:"h-7",width:"w-7"},xl:{height:"h-9",width:"w-9"}},m={simple:{rounded:"",border:"",ring:"",shadow:""},light:{rounded:"rounded-tremor-default",border:"",ring:"",shadow:""},shadow:{rounded:"rounded-tremor-default",border:"border",ring:"",shadow:"shadow-tremor-card dark:shadow-dark-tremor-card"},solid:{rounded:"rounded-tremor-default",border:"border-2",ring:"ring-1",shadow:""},outlined:{rounded:"rounded-tremor-default",border:"border",ring:"ring-2",shadow:""}},g=(t,e)=>{switch(t){case"simple":return{textColor:e?(0,l.bM)(e,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:"",borderColor:"",ringColor:""};case"light":return{textColor:e?(0,l.bM)(e,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:e?(0,c.q)((0,l.bM)(e,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand-muted dark:bg-dark-tremor-brand-muted",borderColor:"",ringColor:""};case"shadow":return{textColor:e?(0,l.bM)(e,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:e?(0,c.q)((0,l.bM)(e,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:"border-tremor-border dark:border-dark-tremor-border",ringColor:""};case"solid":return{textColor:e?(0,l.bM)(e,s.K.text).textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",bgColor:e?(0,c.q)((0,l.bM)(e,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand dark:bg-dark-tremor-brand",borderColor:"border-tremor-brand-inverted dark:border-dark-tremor-brand-inverted",ringColor:"ring-tremor-ring dark:ring-dark-tremor-ring"};case"outlined":return{textColor:e?(0,l.bM)(e,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:e?(0,c.q)((0,l.bM)(e,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:e?(0,l.bM)(e,s.K.ring).borderColor:"border-tremor-brand-subtle dark:border-dark-tremor-brand-subtle",ringColor:e?(0,c.q)((0,l.bM)(e,s.K.ring).ringColor,"ring-opacity-40"):"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"}}},p=(0,l.fn)("Icon"),b=o.forwardRef((t,e)=>{let{icon:r,variant:s="simple",tooltip:b,size:f=i.u8.SM,color:h,className:v}=t,y=(0,n._T)(t,["icon","variant","tooltip","size","color","className"]),w=g(s,h),{tooltipProps:x,getReferenceProps:k}=(0,a.l)();return o.createElement("span",Object.assign({ref:(0,l.lq)([e,x.refs.setReference]),className:(0,c.q)(p("root"),"inline-flex shrink-0 items-center justify-center",w.bgColor,w.textColor,w.borderColor,w.ringColor,m[s].rounded,m[s].border,m[s].shadow,m[s].ring,d[f].paddingX,d[f].paddingY,v)},k,y),o.createElement(a.Z,Object.assign({text:b},x)),o.createElement(r,{className:(0,c.q)(p("icon"),"shrink-0",u[f].height,u[f].width)}))});b.displayName="Icon"},67101:function(t,e,r){r.d(e,{Z:function(){return d}});var n=r(5853),o=r(13241),a=r(1153),i=r(2265),c=r(9496);let l=(0,a.fn)("Grid"),s=(t,e)=>t&&Object.keys(e).includes(String(t))?e[t]:"",d=i.forwardRef((t,e)=>{let{numItems:r=1,numItemsSm:a,numItemsMd:d,numItemsLg:u,children:m,className:g}=t,p=(0,n._T)(t,["numItems","numItemsSm","numItemsMd","numItemsLg","children","className"]),b=s(r,c._m),f=s(a,c.LH),h=s(d,c.l5),v=s(u,c.N4),y=(0,o.q)(b,f,h,v);return i.createElement("div",Object.assign({ref:e,className:(0,o.q)(l("root"),"grid",y,g)},p),m)});d.displayName="Grid"},9496:function(t,e,r){r.d(e,{LH:function(){return o},N4:function(){return i},PT:function(){return c},SP:function(){return l},VS:function(){return s},_m:function(){return n},_w:function(){return d},l5:function(){return a}});let n={0:"grid-cols-none",1:"grid-cols-1",2:"grid-cols-2",3:"grid-cols-3",4:"grid-cols-4",5:"grid-cols-5",6:"grid-cols-6",7:"grid-cols-7",8:"grid-cols-8",9:"grid-cols-9",10:"grid-cols-10",11:"grid-cols-11",12:"grid-cols-12"},o={0:"sm:grid-cols-none",1:"sm:grid-cols-1",2:"sm:grid-cols-2",3:"sm:grid-cols-3",4:"sm:grid-cols-4",5:"sm:grid-cols-5",6:"sm:grid-cols-6",7:"sm:grid-cols-7",8:"sm:grid-cols-8",9:"sm:grid-cols-9",10:"sm:grid-cols-10",11:"sm:grid-cols-11",12:"sm:grid-cols-12"},a={0:"md:grid-cols-none",1:"md:grid-cols-1",2:"md:grid-cols-2",3:"md:grid-cols-3",4:"md:grid-cols-4",5:"md:grid-cols-5",6:"md:grid-cols-6",7:"md:grid-cols-7",8:"md:grid-cols-8",9:"md:grid-cols-9",10:"md:grid-cols-10",11:"md:grid-cols-11",12:"md:grid-cols-12"},i={0:"lg:grid-cols-none",1:"lg:grid-cols-1",2:"lg:grid-cols-2",3:"lg:grid-cols-3",4:"lg:grid-cols-4",5:"lg:grid-cols-5",6:"lg:grid-cols-6",7:"lg:grid-cols-7",8:"lg:grid-cols-8",9:"lg:grid-cols-9",10:"lg:grid-cols-10",11:"lg:grid-cols-11",12:"lg:grid-cols-12"},c={1:"col-span-1",2:"col-span-2",3:"col-span-3",4:"col-span-4",5:"col-span-5",6:"col-span-6",7:"col-span-7",8:"col-span-8",9:"col-span-9",10:"col-span-10",11:"col-span-11",12:"col-span-12",13:"col-span-13"},l={1:"sm:col-span-1",2:"sm:col-span-2",3:"sm:col-span-3",4:"sm:col-span-4",5:"sm:col-span-5",6:"sm:col-span-6",7:"sm:col-span-7",8:"sm:col-span-8",9:"sm:col-span-9",10:"sm:col-span-10",11:"sm:col-span-11",12:"sm:col-span-12",13:"sm:col-span-13"},s={1:"md:col-span-1",2:"md:col-span-2",3:"md:col-span-3",4:"md:col-span-4",5:"md:col-span-5",6:"md:col-span-6",7:"md:col-span-7",8:"md:col-span-8",9:"md:col-span-9",10:"md:col-span-10",11:"md:col-span-11",12:"md:col-span-12",13:"md:col-span-13"},d={1:"lg:col-span-1",2:"lg:col-span-2",3:"lg:col-span-3",4:"lg:col-span-4",5:"lg:col-span-5",6:"lg:col-span-6",7:"lg:col-span-7",8:"lg:col-span-8",9:"lg:col-span-9",10:"lg:col-span-10",11:"lg:col-span-11",12:"lg:col-span-12",13:"lg:col-span-13"}},96761:function(t,e,r){r.d(e,{Z:function(){return l}});var n=r(5853),o=r(26898),a=r(13241),i=r(1153),c=r(2265);let l=c.forwardRef((t,e)=>{let{color:r,children:l,className:s}=t,d=(0,n._T)(t,["color","children","className"]);return c.createElement("p",Object.assign({ref:e,className:(0,a.q)("font-medium text-tremor-title",r?(0,i.bM)(r,o.K.darkText).textColor:"text-tremor-content-strong dark:text-dark-tremor-content-strong",s)},d),l)});l.displayName="Title"},33866:function(t,e,r){r.d(e,{Z:function(){return I}});var n=r(2265),o=r(36760),a=r.n(o),i=r(66632),c=r(93350),l=r(19722),s=r(71744),d=r(93463),u=r(12918),m=r(18536),g=r(71140),p=r(99320);let b=new d.E4("antStatusProcessing",{"0%":{transform:"scale(0.8)",opacity:.5},"100%":{transform:"scale(2.4)",opacity:0}}),f=new d.E4("antZoomBadgeIn",{"0%":{transform:"scale(0) translate(50%, -50%)",opacity:0},"100%":{transform:"scale(1) translate(50%, -50%)"}}),h=new d.E4("antZoomBadgeOut",{"0%":{transform:"scale(1) translate(50%, -50%)"},"100%":{transform:"scale(0) translate(50%, -50%)",opacity:0}}),v=new d.E4("antNoWrapperZoomBadgeIn",{"0%":{transform:"scale(0)",opacity:0},"100%":{transform:"scale(1)"}}),y=new d.E4("antNoWrapperZoomBadgeOut",{"0%":{transform:"scale(1)"},"100%":{transform:"scale(0)",opacity:0}}),w=new d.E4("antBadgeLoadingCircle",{"0%":{transformOrigin:"50%"},"100%":{transform:"translate(50%, -50%) rotate(360deg)",transformOrigin:"50%"}}),x=t=>{let{componentCls:e,iconCls:r,antCls:n,badgeShadowSize:o,textFontSize:a,textFontSizeSM:i,statusSize:c,dotSize:l,textFontWeight:s,indicatorHeight:g,indicatorHeightSM:p,marginXS:x,calc:k}=t,O="".concat(n,"-scroll-number"),C=(0,m.Z)(t,(t,r)=>{let{darkColor:n}=r;return{["&".concat(e," ").concat(e,"-color-").concat(t)]:{background:n,["&:not(".concat(e,"-count)")]:{color:n},"a:hover &":{background:n}}}});return{[e]:Object.assign(Object.assign(Object.assign(Object.assign({},(0,u.Wf)(t)),{position:"relative",display:"inline-block",width:"fit-content",lineHeight:1,["".concat(e,"-count")]:{display:"inline-flex",justifyContent:"center",zIndex:t.indicatorZIndex,minWidth:g,height:g,color:t.badgeTextColor,fontWeight:s,fontSize:a,lineHeight:(0,d.bf)(g),whiteSpace:"nowrap",textAlign:"center",background:t.badgeColor,borderRadius:k(g).div(2).equal(),boxShadow:"0 0 0 ".concat((0,d.bf)(o)," ").concat(t.badgeShadowColor),transition:"background ".concat(t.motionDurationMid),a:{color:t.badgeTextColor},"a:hover":{color:t.badgeTextColor},"a:hover &":{background:t.badgeColorHover}},["".concat(e,"-count-sm")]:{minWidth:p,height:p,fontSize:i,lineHeight:(0,d.bf)(p),borderRadius:k(p).div(2).equal()},["".concat(e,"-multiple-words")]:{padding:"0 ".concat((0,d.bf)(t.paddingXS)),bdi:{unicodeBidi:"plaintext"}},["".concat(e,"-dot")]:{zIndex:t.indicatorZIndex,width:l,minWidth:l,height:l,background:t.badgeColor,borderRadius:"100%",boxShadow:"0 0 0 ".concat((0,d.bf)(o)," ").concat(t.badgeShadowColor)},["".concat(e,"-count, ").concat(e,"-dot, ").concat(O,"-custom-component")]:{position:"absolute",top:0,insetInlineEnd:0,transform:"translate(50%, -50%)",transformOrigin:"100% 0%",["&".concat(r,"-spin")]:{animationName:w,animationDuration:"1s",animationIterationCount:"infinite",animationTimingFunction:"linear"}},["&".concat(e,"-status")]:{lineHeight:"inherit",verticalAlign:"baseline",["".concat(e,"-status-dot")]:{position:"relative",top:-1,display:"inline-block",width:c,height:c,verticalAlign:"middle",borderRadius:"50%"},["".concat(e,"-status-success")]:{backgroundColor:t.colorSuccess},["".concat(e,"-status-processing")]:{overflow:"visible",color:t.colorInfo,backgroundColor:t.colorInfo,borderColor:"currentcolor","&::after":{position:"absolute",top:0,insetInlineStart:0,width:"100%",height:"100%",borderWidth:o,borderStyle:"solid",borderColor:"inherit",borderRadius:"50%",animationName:b,animationDuration:t.badgeProcessingDuration,animationIterationCount:"infinite",animationTimingFunction:"ease-in-out",content:'""'}},["".concat(e,"-status-default")]:{backgroundColor:t.colorTextPlaceholder},["".concat(e,"-status-error")]:{backgroundColor:t.colorError},["".concat(e,"-status-warning")]:{backgroundColor:t.colorWarning},["".concat(e,"-status-text")]:{marginInlineStart:x,color:t.colorText,fontSize:t.fontSize}}}),C),{["".concat(e,"-zoom-appear, ").concat(e,"-zoom-enter")]:{animationName:f,animationDuration:t.motionDurationSlow,animationTimingFunction:t.motionEaseOutBack,animationFillMode:"both"},["".concat(e,"-zoom-leave")]:{animationName:h,animationDuration:t.motionDurationSlow,animationTimingFunction:t.motionEaseOutBack,animationFillMode:"both"},["&".concat(e,"-not-a-wrapper")]:{["".concat(e,"-zoom-appear, ").concat(e,"-zoom-enter")]:{animationName:v,animationDuration:t.motionDurationSlow,animationTimingFunction:t.motionEaseOutBack},["".concat(e,"-zoom-leave")]:{animationName:y,animationDuration:t.motionDurationSlow,animationTimingFunction:t.motionEaseOutBack},["&:not(".concat(e,"-status)")]:{verticalAlign:"middle"},["".concat(O,"-custom-component, ").concat(e,"-count")]:{transform:"none"},["".concat(O,"-custom-component, ").concat(O)]:{position:"relative",top:"auto",display:"block",transformOrigin:"50% 50%"}},[O]:{overflow:"hidden",transition:"all ".concat(t.motionDurationMid," ").concat(t.motionEaseOutBack),["".concat(O,"-only")]:{position:"relative",display:"inline-block",height:g,transition:"all ".concat(t.motionDurationSlow," ").concat(t.motionEaseOutBack),WebkitTransformStyle:"preserve-3d",WebkitBackfaceVisibility:"hidden",["> p".concat(O,"-only-unit")]:{height:g,margin:0,WebkitTransformStyle:"preserve-3d",WebkitBackfaceVisibility:"hidden"}},["".concat(O,"-symbol")]:{verticalAlign:"top"}},"&-rtl":{direction:"rtl",["".concat(e,"-count, ").concat(e,"-dot, ").concat(O,"-custom-component")]:{transform:"translate(-50%, -50%)"}}})}},k=t=>{let{fontHeight:e,lineWidth:r,marginXS:n,colorBorderBg:o}=t,a=t.colorTextLightSolid,i=t.colorError,c=t.colorErrorHover;return(0,g.IX)(t,{badgeFontHeight:e,badgeShadowSize:r,badgeTextColor:a,badgeColor:i,badgeColorHover:c,badgeShadowColor:o,badgeProcessingDuration:"1.2s",badgeRibbonOffset:n,badgeRibbonCornerTransform:"scaleY(0.75)",badgeRibbonCornerFilter:"brightness(75%)"})},O=t=>{let{fontSize:e,lineHeight:r,fontSizeSM:n,lineWidth:o}=t;return{indicatorZIndex:"auto",indicatorHeight:Math.round(e*r)-2*o,indicatorHeightSM:e,dotSize:n/2,textFontSize:n,textFontSizeSM:n,textFontWeight:"normal",statusSize:n/2}};var C=(0,p.I$)("Badge",t=>x(k(t)),O);let E=t=>{let{antCls:e,badgeFontHeight:r,marginXS:n,badgeRibbonOffset:o,calc:a}=t,i="".concat(e,"-ribbon"),c=(0,m.Z)(t,(t,e)=>{let{darkColor:r}=e;return{["&".concat(i,"-color-").concat(t)]:{background:r,color:r}}});return{["".concat(e,"-ribbon-wrapper")]:{position:"relative"},[i]:Object.assign(Object.assign(Object.assign(Object.assign({},(0,u.Wf)(t)),{position:"absolute",top:n,padding:"0 ".concat((0,d.bf)(t.paddingXS)),color:t.colorPrimary,lineHeight:(0,d.bf)(r),whiteSpace:"nowrap",backgroundColor:t.colorPrimary,borderRadius:t.borderRadiusSM,["".concat(i,"-text")]:{color:t.badgeTextColor},["".concat(i,"-corner")]:{position:"absolute",top:"100%",width:o,height:o,color:"currentcolor",border:"".concat((0,d.bf)(a(o).div(2).equal())," solid"),transform:t.badgeRibbonCornerTransform,transformOrigin:"top",filter:t.badgeRibbonCornerFilter}}),c),{["&".concat(i,"-placement-end")]:{insetInlineEnd:a(o).mul(-1).equal(),borderEndEndRadius:0,["".concat(i,"-corner")]:{insetInlineEnd:0,borderInlineEndColor:"transparent",borderBlockEndColor:"transparent"}},["&".concat(i,"-placement-start")]:{insetInlineStart:a(o).mul(-1).equal(),borderEndStartRadius:0,["".concat(i,"-corner")]:{insetInlineStart:0,borderBlockEndColor:"transparent",borderInlineStartColor:"transparent"}},"&-rtl":{direction:"rtl"}})}};var S=(0,p.I$)(["Badge","Ribbon"],t=>E(k(t)),O);let N=t=>{let e;let{prefixCls:r,value:o,current:i,offset:c=0}=t;return c&&(e={position:"absolute",top:"".concat(c,"00%"),left:0}),n.createElement("span",{style:e,className:a()("".concat(r,"-only-unit"),{current:i})},o)};var j=t=>{let e,r;let{prefixCls:o,count:a,value:i}=t,c=Number(i),l=Math.abs(a),[s,d]=n.useState(c),[u,m]=n.useState(l),g=()=>{d(c),m(l)};if(n.useEffect(()=>{let t=setTimeout(g,1e3);return()=>clearTimeout(t)},[c]),s===c||Number.isNaN(c)||Number.isNaN(s))e=[n.createElement(N,Object.assign({},t,{key:c,current:!0}))],r={transition:"none"};else{e=[];let o=c+10,a=[];for(let t=c;t<=o;t+=1)a.push(t);let i=ut%10===s);e=(i<0?a.slice(0,d+1):a.slice(d)).map((e,r)=>n.createElement(N,Object.assign({},t,{key:e,value:e%10,offset:i<0?r-d:r,current:r===d}))),r={transform:"translateY(".concat(-function(t,e,r){let n=t,o=0;for(;(n+10)%10!==e;)n+=r,o+=r;return o}(s,c,i),"00%)")}}return n.createElement("span",{className:"".concat(o,"-only"),style:r,onTransitionEnd:g},e)},M=function(t,e){var r={};for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&0>e.indexOf(n)&&(r[n]=t[n]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,n=Object.getOwnPropertySymbols(t);oe.indexOf(n[o])&&Object.prototype.propertyIsEnumerable.call(t,n[o])&&(r[n[o]]=t[n[o]]);return r};let z=n.forwardRef((t,e)=>{let{prefixCls:r,count:o,className:i,motionClassName:c,style:d,title:u,show:m,component:g="sup",children:p}=t,b=M(t,["prefixCls","count","className","motionClassName","style","title","show","component","children"]),{getPrefixCls:f}=n.useContext(s.E_),h=f("scroll-number",r),v=Object.assign(Object.assign({},b),{"data-show":m,style:d,className:a()(h,i,c),title:u}),y=o;if(o&&Number(o)%1==0){let t=String(o).split("");y=n.createElement("bdi",null,t.map((e,r)=>n.createElement(j,{prefixCls:h,count:Number(o),value:e,key:t.length-r})))}return((null==d?void 0:d.borderColor)&&(v.style=Object.assign(Object.assign({},d),{boxShadow:"0 0 0 1px ".concat(d.borderColor," inset")})),p)?(0,l.Tm)(p,t=>({className:a()("".concat(h,"-custom-component"),null==t?void 0:t.className,c)})):n.createElement(g,Object.assign({},v,{ref:e}),y)});var Z=function(t,e){var r={};for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&0>e.indexOf(n)&&(r[n]=t[n]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,n=Object.getOwnPropertySymbols(t);oe.indexOf(n[o])&&Object.prototype.propertyIsEnumerable.call(t,n[o])&&(r[n[o]]=t[n[o]]);return r};let R=n.forwardRef((t,e)=>{var r,o,d,u,m;let{prefixCls:g,scrollNumberPrefixCls:p,children:b,status:f,text:h,color:v,count:y=null,overflowCount:w=99,dot:x=!1,size:k="default",title:O,offset:E,style:S,className:N,rootClassName:j,classNames:M,styles:R,showZero:I=!1}=t,L=Z(t,["prefixCls","scrollNumberPrefixCls","children","status","text","color","count","overflowCount","dot","size","title","offset","style","className","rootClassName","classNames","styles","showZero"]),{getPrefixCls:B,direction:T,badge:P}=n.useContext(s.E_),W=B("badge",g),[A,q,H]=C(W),G=y>w?"".concat(w,"+"):y,D="0"===G||0===G||"0"===h||0===h,K=null===y||D&&!I,V=(null!=f||null!=v)&&K,F=null!=f||!D,_=x&&!D,X=_?"":G,Y=(0,n.useMemo)(()=>((null==X||""===X)&&(null==h||""===h)||D&&!I)&&!_,[X,D,I,_,h]),$=(0,n.useRef)(y);Y||($.current=y);let U=$.current,J=(0,n.useRef)(X);Y||(J.current=X);let Q=J.current,tt=(0,n.useRef)(_);Y||(tt.current=_);let te=(0,n.useMemo)(()=>{if(!E)return Object.assign(Object.assign({},null==P?void 0:P.style),S);let t={marginTop:E[1]};return"rtl"===T?t.left=Number.parseInt(E[0],10):t.right=-Number.parseInt(E[0],10),Object.assign(Object.assign(Object.assign({},t),null==P?void 0:P.style),S)},[T,E,S,null==P?void 0:P.style]),tr=null!=O?O:"string"==typeof U||"number"==typeof U?U:void 0,tn=!Y&&(0===h?I:!!h&&!0!==h),to=tn?n.createElement("span",{className:"".concat(W,"-status-text")},h):null,ta=U&&"object"==typeof U?(0,l.Tm)(U,t=>({style:Object.assign(Object.assign({},te),t.style)})):void 0,ti=(0,c.o2)(v,!1),tc=a()(null==M?void 0:M.indicator,null===(r=null==P?void 0:P.classNames)||void 0===r?void 0:r.indicator,{["".concat(W,"-status-dot")]:V,["".concat(W,"-status-").concat(f)]:!!f,["".concat(W,"-color-").concat(v)]:ti}),tl={};v&&!ti&&(tl.color=v,tl.background=v);let ts=a()(W,{["".concat(W,"-status")]:V,["".concat(W,"-not-a-wrapper")]:!b,["".concat(W,"-rtl")]:"rtl"===T},N,j,null==P?void 0:P.className,null===(o=null==P?void 0:P.classNames)||void 0===o?void 0:o.root,null==M?void 0:M.root,q,H);if(!b&&V&&(h||F||!K)){let t=te.color;return A(n.createElement("span",Object.assign({},L,{className:ts,style:Object.assign(Object.assign(Object.assign({},null==R?void 0:R.root),null===(d=null==P?void 0:P.styles)||void 0===d?void 0:d.root),te)}),n.createElement("span",{className:tc,style:Object.assign(Object.assign(Object.assign({},null==R?void 0:R.indicator),null===(u=null==P?void 0:P.styles)||void 0===u?void 0:u.indicator),tl)}),tn&&n.createElement("span",{style:{color:t},className:"".concat(W,"-status-text")},h)))}return A(n.createElement("span",Object.assign({ref:e},L,{className:ts,style:Object.assign(Object.assign({},null===(m=null==P?void 0:P.styles)||void 0===m?void 0:m.root),null==R?void 0:R.root)}),b,n.createElement(i.ZP,{visible:!Y,motionName:"".concat(W,"-zoom"),motionAppear:!1,motionDeadline:1e3},t=>{var e,r;let{className:o}=t,i=B("scroll-number",p),c=tt.current,l=a()(null==M?void 0:M.indicator,null===(e=null==P?void 0:P.classNames)||void 0===e?void 0:e.indicator,{["".concat(W,"-dot")]:c,["".concat(W,"-count")]:!c,["".concat(W,"-count-sm")]:"small"===k,["".concat(W,"-multiple-words")]:!c&&Q&&Q.toString().length>1,["".concat(W,"-status-").concat(f)]:!!f,["".concat(W,"-color-").concat(v)]:ti}),s=Object.assign(Object.assign(Object.assign({},null==R?void 0:R.indicator),null===(r=null==P?void 0:P.styles)||void 0===r?void 0:r.indicator),te);return v&&!ti&&((s=s||{}).background=v),n.createElement(z,{prefixCls:i,show:!Y,motionClassName:o,className:l,count:Q,title:tr,style:s,key:"scrollNumber"},ta)}),to))});R.Ribbon=t=>{let{className:e,prefixCls:r,style:o,color:i,children:l,text:d,placement:u="end",rootClassName:m}=t,{getPrefixCls:g,direction:p}=n.useContext(s.E_),b=g("ribbon",r),f="".concat(b,"-wrapper"),[h,v,y]=S(b,f),w=(0,c.o2)(i,!1),x=a()(b,"".concat(b,"-placement-").concat(u),{["".concat(b,"-rtl")]:"rtl"===p,["".concat(b,"-color-").concat(i)]:w},e),k={},O={};return i&&!w&&(k.background=i,O.color=i),h(n.createElement("div",{className:a()(f,m,v,y)},l,n.createElement("div",{className:a()(x,v),style:Object.assign(Object.assign({},k),o)},n.createElement("span",{className:"".concat(b,"-text")},d),n.createElement("div",{className:"".concat(b,"-corner"),style:O}))))};var I=R},58760:function(t,e,r){r.d(e,{Z:function(){return S}});var n=r(2265),o=r(36760),a=r.n(o),i=r(45287);function c(t){return["small","middle","large"].includes(t)}function l(t){return!!t&&"number"==typeof t&&!Number.isNaN(t)}var s=r(71744),d=r(77685),u=r(17691),m=r(99320);let g=t=>{let{componentCls:e,borderRadius:r,paddingSM:n,colorBorder:o,paddingXS:a,fontSizeLG:i,fontSizeSM:c,borderRadiusLG:l,borderRadiusSM:s,colorBgContainerDisabled:d,lineWidth:m}=t;return{[e]:[{display:"inline-flex",alignItems:"center",gap:0,paddingInline:n,margin:0,background:d,borderWidth:m,borderStyle:"solid",borderColor:o,borderRadius:r,"&-large":{fontSize:i,borderRadius:l},"&-small":{paddingInline:a,borderRadius:s,fontSize:c},"&-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,u.c)(t,{focus:!1})]}};var p=(0,m.I$)(["Space","Addon"],t=>[g(t)]),b=function(t,e){var r={};for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&0>e.indexOf(n)&&(r[n]=t[n]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,n=Object.getOwnPropertySymbols(t);oe.indexOf(n[o])&&Object.prototype.propertyIsEnumerable.call(t,n[o])&&(r[n[o]]=t[n[o]]);return r};let f=n.forwardRef((t,e)=>{let{className:r,children:o,style:i,prefixCls:c}=t,l=b(t,["className","children","style","prefixCls"]),{getPrefixCls:u,direction:m}=n.useContext(s.E_),g=u("space-addon",c),[f,h,v]=p(g),{compactItemClassnames:y,compactSize:w}=(0,d.ri)(g,m),x=a()(g,h,y,v,{["".concat(g,"-").concat(w)]:w},r);return f(n.createElement("div",Object.assign({ref:e,className:x,style:i},l),o))}),h=n.createContext({latestIndex:0}),v=h.Provider;var y=t=>{let{className:e,index:r,children:o,split:a,style:i}=t,{latestIndex:c}=n.useContext(h);return null==o?null:n.createElement(n.Fragment,null,n.createElement("div",{className:e,style:i},o),r{let{componentCls:e,antCls:r}=t;return{[e]:{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"}},["".concat(e,"-item:empty")]:{display:"none"},["".concat(e,"-item > ").concat(r,"-badge-not-a-wrapper:only-child")]:{display:"block"}}}},k=t=>{let{componentCls:e}=t;return{[e]:{"&-gap-row-small":{rowGap:t.spaceGapSmallSize},"&-gap-row-middle":{rowGap:t.spaceGapMiddleSize},"&-gap-row-large":{rowGap:t.spaceGapLargeSize},"&-gap-col-small":{columnGap:t.spaceGapSmallSize},"&-gap-col-middle":{columnGap:t.spaceGapMiddleSize},"&-gap-col-large":{columnGap:t.spaceGapLargeSize}}}};var O=(0,m.I$)("Space",t=>{let e=(0,w.IX)(t,{spaceGapSmallSize:t.paddingXS,spaceGapMiddleSize:t.padding,spaceGapLargeSize:t.paddingLG});return[x(e),k(e)]},()=>({}),{resetStyle:!1}),C=function(t,e){var r={};for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&0>e.indexOf(n)&&(r[n]=t[n]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,n=Object.getOwnPropertySymbols(t);oe.indexOf(n[o])&&Object.prototype.propertyIsEnumerable.call(t,n[o])&&(r[n[o]]=t[n[o]]);return r};let E=n.forwardRef((t,e)=>{var r;let{getPrefixCls:o,direction:d,size:u,className:m,style:g,classNames:p,styles:b}=(0,s.dj)("space"),{size:f=null!=u?u:"small",align:h,className:w,rootClassName:x,children:k,direction:E="horizontal",prefixCls:S,split:N,style:j,wrap:M=!1,classNames:z,styles:Z}=t,R=C(t,["size","align","className","rootClassName","children","direction","prefixCls","split","style","wrap","classNames","styles"]),[I,L]=Array.isArray(f)?f:[f,f],B=c(L),T=c(I),P=l(L),W=l(I),A=(0,i.Z)(k,{keepEmpty:!0}),q=void 0===h&&"horizontal"===E?"center":h,H=o("space",S),[G,D,K]=O(H),V=a()(H,m,D,"".concat(H,"-").concat(E),{["".concat(H,"-rtl")]:"rtl"===d,["".concat(H,"-align-").concat(q)]:q,["".concat(H,"-gap-row-").concat(L)]:B,["".concat(H,"-gap-col-").concat(I)]:T},w,x,K),F=a()("".concat(H,"-item"),null!==(r=null==z?void 0:z.item)&&void 0!==r?r:p.item),_=Object.assign(Object.assign({},b.item),null==Z?void 0:Z.item),X=A.map((t,e)=>{let r=(null==t?void 0:t.key)||"".concat(F,"-").concat(e);return n.createElement(y,{className:F,key:r,index:e,split:N,style:_},t)}),Y=n.useMemo(()=>({latestIndex:A.reduce((t,e,r)=>null!=e?r:t,0)}),[A]);if(0===A.length)return null;let $={};return M&&($.flexWrap="wrap"),!T&&W&&($.columnGap=I),!B&&P&&($.rowGap=L),G(n.createElement("div",Object.assign({ref:e,className:V,style:Object.assign(Object.assign(Object.assign({},$),g),j)},R),n.createElement(v,{value:Y},X)))});E.Compact=d.ZP,E.Addon=f;var S=E},79205:function(t,e,r){r.d(e,{Z:function(){return u}});var n=r(2265);let o=t=>t.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase(),a=t=>t.replace(/^([A-Z])|[\s-_]+(\w)/g,(t,e,r)=>r?r.toUpperCase():e.toLowerCase()),i=t=>{let e=a(t);return e.charAt(0).toUpperCase()+e.slice(1)},c=function(){for(var t=arguments.length,e=Array(t),r=0;r!!t&&""!==t.trim()&&r.indexOf(t)===e).join(" ").trim()},l=t=>{for(let e in t)if(e.startsWith("aria-")||"role"===e||"title"===e)return!0};var s={xmlns:"http://www.w3.org/2000/svg",width:24,height:24,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:2,strokeLinecap:"round",strokeLinejoin:"round"};let d=(0,n.forwardRef)((t,e)=>{let{color:r="currentColor",size:o=24,strokeWidth:a=2,absoluteStrokeWidth:i,className:d="",children:u,iconNode:m,...g}=t;return(0,n.createElement)("svg",{ref:e,...s,width:o,height:o,stroke:r,strokeWidth:i?24*Number(a)/Number(o):a,className:c("lucide",d),...!u&&!l(g)&&{"aria-hidden":"true"},...g},[...m.map(t=>{let[e,r]=t;return(0,n.createElement)(e,r)}),...Array.isArray(u)?u:[u]])}),u=(t,e)=>{let r=(0,n.forwardRef)((r,a)=>{let{className:l,...s}=r;return(0,n.createElement)(d,{ref:a,iconNode:e,className:c("lucide-".concat(o(i(t))),"lucide-".concat(t),l),...s})});return r.displayName=i(t),r}},30401:function(t,e,r){r.d(e,{Z:function(){return n}});let n=(0,r(79205).Z)("check",[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]])},64935:function(t,e,r){r.d(e,{Z:function(){return n}});let n=(0,r(79205).Z)("code",[["path",{d:"m16 18 6-6-6-6",key:"eg8j8"}],["path",{d:"m8 6-6 6 6 6",key:"ppft3o"}]])},78867:function(t,e,r){r.d(e,{Z:function(){return n}});let n=(0,r(79205).Z)("copy",[["rect",{width:"14",height:"14",x:"8",y:"8",rx:"2",ry:"2",key:"17jyea"}],["path",{d:"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2",key:"zix9uf"}]])},96362:function(t,e,r){r.d(e,{Z:function(){return n}});let n=(0,r(79205).Z)("external-link",[["path",{d:"M15 3h6v6",key:"1q9fwt"}],["path",{d:"M10 14 21 3",key:"gplh6r"}],["path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6",key:"a6xqqp"}]])},29202:function(t,e,r){r.d(e,{Z:function(){return n}});let n=(0,r(79205).Z)("globe",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20",key:"13o1zl"}],["path",{d:"M2 12h20",key:"9i4pu4"}]])},54001:function(t,e,r){r.d(e,{Z:function(){return n}});let n=(0,r(79205).Z)("key",[["path",{d:"m15.5 7.5 2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4",key:"g0fldk"}],["path",{d:"m21 2-9.6 9.6",key:"1j0ho8"}],["circle",{cx:"7.5",cy:"15.5",r:"5.5",key:"yqb3hr"}]])},96137:function(t,e,r){r.d(e,{Z:function(){return n}});let n=(0,r(79205).Z)("server",[["rect",{width:"20",height:"8",x:"2",y:"2",rx:"2",ry:"2",key:"ngkwjq"}],["rect",{width:"20",height:"8",x:"2",y:"14",rx:"2",ry:"2",key:"iecqi9"}],["line",{x1:"6",x2:"6.01",y1:"6",y2:"6",key:"16zg32"}],["line",{x1:"6",x2:"6.01",y1:"18",y2:"18",key:"nzw8ys"}]])},80221:function(t,e,r){r.d(e,{Z:function(){return n}});let n=(0,r(79205).Z)("terminal",[["path",{d:"M12 19h8",key:"baeox8"}],["path",{d:"m4 17 6-6-6-6",key:"1yngyt"}]])},11239:function(t,e,r){r.d(e,{Z:function(){return n}});let n=(0,r(79205).Z)("zap",[["path",{d:"M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z",key:"1xq2db"}]])},10900:function(t,e,r){var n=r(2265);let o=n.forwardRef(function(t,e){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:e},t),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M10 19l-7-7m0 0l7-7m-7 7h18"}))});e.Z=o},71437:function(t,e,r){var n=r(2265);let o=n.forwardRef(function(t,e){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:e},t),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M15 12a3 3 0 11-6 0 3 3 0 016 0z"}),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"}))});e.Z=o},82376:function(t,e,r){var n=r(2265);let o=n.forwardRef(function(t,e){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:e},t),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21"}))});e.Z=o},53410:function(t,e,r){var n=r(2265);let o=n.forwardRef(function(t,e){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:e},t),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"}))});e.Z=o},74998:function(t,e,r){var n=r(2265);let o=n.forwardRef(function(t,e){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:e},t),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"}))});e.Z=o},21770:function(t,e,r){r.d(e,{D:function(){return d}});var n=r(2265),o=r(2894),a=r(18238),i=r(24112),c=r(45345),l=class extends i.l{#t;#e=void 0;#r;#n;constructor(t,e){super(),this.#t=t,this.setOptions(e),this.bindMethods(),this.#o()}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(t){let e=this.options;this.options=this.#t.defaultMutationOptions(t),(0,c.VS)(this.options,e)||this.#t.getMutationCache().notify({type:"observerOptionsUpdated",mutation:this.#r,observer:this}),e?.mutationKey&&this.options.mutationKey&&(0,c.Ym)(e.mutationKey)!==(0,c.Ym)(this.options.mutationKey)?this.reset():this.#r?.state.status==="pending"&&this.#r.setOptions(this.options)}onUnsubscribe(){this.hasListeners()||this.#r?.removeObserver(this)}onMutationUpdate(t){this.#o(),this.#a(t)}getCurrentResult(){return this.#e}reset(){this.#r?.removeObserver(this),this.#r=void 0,this.#o(),this.#a()}mutate(t,e){return this.#n=e,this.#r?.removeObserver(this),this.#r=this.#t.getMutationCache().build(this.#t,this.options),this.#r.addObserver(this),this.#r.execute(t)}#o(){let t=this.#r?.state??(0,o.R)();this.#e={...t,isPending:"pending"===t.status,isSuccess:"success"===t.status,isError:"error"===t.status,isIdle:"idle"===t.status,mutate:this.mutate,reset:this.reset}}#a(t){a.Vr.batch(()=>{if(this.#n&&this.hasListeners()){let e=this.#e.variables,r=this.#e.context,n={client:this.#t,meta:this.options.meta,mutationKey:this.options.mutationKey};t?.type==="success"?(this.#n.onSuccess?.(t.data,e,r,n),this.#n.onSettled?.(t.data,null,e,r,n)):t?.type==="error"&&(this.#n.onError?.(t.error,e,r,n),this.#n.onSettled?.(void 0,t.error,e,r,n))}this.listeners.forEach(t=>{t(this.#e)})})}},s=r(29827);function d(t,e){let r=(0,s.NL)(e),[o]=n.useState(()=>new l(r,t));n.useEffect(()=>{o.setOptions(t)},[o,t]);let i=n.useSyncExternalStore(n.useCallback(t=>o.subscribe(a.Vr.batchCalls(t)),[o]),()=>o.getCurrentResult(),()=>o.getCurrentResult()),d=n.useCallback((t,e)=>{o.mutate(t,e).catch(c.ZT)},[o]);if(i.error&&(0,c.L3)(o.options.throwOnError,[i.error]))throw i.error;return{...i,mutate:d,mutateAsync:i.mutate}}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1070-ab9dafb0fc6e0b85.js b/litellm/proxy/_experimental/out/_next/static/chunks/1070-ab9dafb0fc6e0b85.js new file mode 100644 index 00000000000..a35f71fe0e6 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/1070-ab9dafb0fc6e0b85.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1070],{88009:function(e,t,c){c.d(t,{Z:function(){return i}});var n=c(1119),a=c(2265),r={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M464 144H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H212V212h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V160c0-8.8-7.2-16-16-16zm-52 268H612V212h200v200zM464 544H160c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H212V612h200v200zm452-268H560c-8.8 0-16 7.2-16 16v304c0 8.8 7.2 16 16 16h304c8.8 0 16-7.2 16-16V560c0-8.8-7.2-16-16-16zm-52 268H612V612h200v200z"}}]},name:"appstore",theme:"outlined"},o=c(55015),i=a.forwardRef(function(e,t){return a.createElement(o.Z,(0,n.Z)({},e,{ref:t,icon:r}))})},93750:function(e,t,c){c.d(t,{Z:function(){return i}});var n=c(1119),a=c(2265),r={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M296 250c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h384c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8H296zm184 144H296c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h184c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8zm-48 458H208V148h560v320c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V108c0-17.7-14.3-32-32-32H168c-17.7 0-32 14.3-32 32v784c0 17.7 14.3 32 32 32h264c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm440-88H728v-36.6c46.3-13.8 80-56.6 80-107.4 0-61.9-50.1-112-112-112s-112 50.1-112 112c0 50.7 33.7 93.6 80 107.4V764H520c-8.8 0-16 7.2-16 16v152c0 8.8 7.2 16 16 16h352c8.8 0 16-7.2 16-16V780c0-8.8-7.2-16-16-16zM646 620c0-27.6 22.4-50 50-50s50 22.4 50 50-22.4 50-50 50-50-22.4-50-50zm180 266H566v-60h260v60z"}}]},name:"audit",theme:"outlined"},o=c(55015),i=a.forwardRef(function(e,t){return a.createElement(o.Z,(0,n.Z)({},e,{ref:t,icon:r}))})},37527:function(e,t,c){c.d(t,{Z:function(){return i}});var n=c(1119),a=c(2265),r={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M894 462c30.9 0 43.8-39.7 18.7-58L530.8 126.2a31.81 31.81 0 00-37.6 0L111.3 404c-25.1 18.2-12.2 58 18.8 58H192v374h-72c-4.4 0-8 3.6-8 8v52c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-52c0-4.4-3.6-8-8-8h-72V462h62zM512 196.7l271.1 197.2H240.9L512 196.7zM264 462h117v374H264V462zm189 0h117v374H453V462zm307 374H642V462h118v374z"}}]},name:"bank",theme:"outlined"},o=c(55015),i=a.forwardRef(function(e,t){return a.createElement(o.Z,(0,n.Z)({},e,{ref:t,icon:r}))})},9775:function(e,t,c){c.d(t,{Z:function(){return i}});var n=c(1119),a=c(2265),r={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M888 792H200V168c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v688c0 4.4 3.6 8 8 8h752c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm-600-80h56c4.4 0 8-3.6 8-8V560c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v144c0 4.4 3.6 8 8 8zm152 0h56c4.4 0 8-3.6 8-8V384c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v320c0 4.4 3.6 8 8 8zm152 0h56c4.4 0 8-3.6 8-8V462c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v242c0 4.4 3.6 8 8 8zm152 0h56c4.4 0 8-3.6 8-8V304c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v400c0 4.4 3.6 8 8 8z"}}]},name:"bar-chart",theme:"outlined"},o=c(55015),i=a.forwardRef(function(e,t){return a.createElement(o.Z,(0,n.Z)({},e,{ref:t,icon:r}))})},68208:function(e,t,c){c.d(t,{Z:function(){return i}});var n=c(1119),a=c(2265),r={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M856 376H648V168c0-8.8-7.2-16-16-16H168c-8.8 0-16 7.2-16 16v464c0 8.8 7.2 16 16 16h208v208c0 8.8 7.2 16 16 16h464c8.8 0 16-7.2 16-16V392c0-8.8-7.2-16-16-16zm-480 16v188H220V220h360v156H392c-8.8 0-16 7.2-16 16zm204 52v136H444V444h136zm224 360H444V648h188c8.8 0 16-7.2 16-16V444h156v360z"}}]},name:"block",theme:"outlined"},o=c(55015),i=a.forwardRef(function(e,t){return a.createElement(o.Z,(0,n.Z)({},e,{ref:t,icon:r}))})},41169:function(e,t,c){c.d(t,{Z:function(){return i}});var n=c(1119),a=c(2265),r={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 472a40 40 0 1080 0 40 40 0 10-80 0zm367 352.9L696.3 352V178H768v-68H256v68h71.7v174L145 824.9c-2.8 7.4-4.3 15.2-4.3 23.1 0 35.3 28.7 64 64 64h614.6c7.9 0 15.7-1.5 23.1-4.3 33-12.7 49.4-49.8 36.6-82.8zM395.7 364.7V180h232.6v184.7L719.2 600c-20.7-5.3-42.1-8-63.9-8-61.2 0-119.2 21.5-165.3 60a188.78 188.78 0 01-121.3 43.9c-32.7 0-64.1-8.3-91.8-23.7l118.8-307.5zM210.5 844l41.7-107.8c35.7 18.1 75.4 27.8 116.6 27.8 61.2 0 119.2-21.5 165.3-60 33.9-28.2 76.3-43.9 121.3-43.9 35 0 68.4 9.5 97.6 27.1L813.5 844h-603z"}}]},name:"experiment",theme:"outlined"},o=c(55015),i=a.forwardRef(function(e,t){return a.createElement(o.Z,(0,n.Z)({},e,{ref:t,icon:r}))})},48231:function(e,t,c){c.d(t,{Z:function(){return i}});var n=c(1119),a=c(2265),r={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M888 792H200V168c0-4.4-3.6-8-8-8h-56c-4.4 0-8 3.6-8 8v688c0 4.4 3.6 8 8 8h752c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM305.8 637.7c3.1 3.1 8.1 3.1 11.3 0l138.3-137.6L583 628.5c3.1 3.1 8.2 3.1 11.3 0l275.4-275.3c3.1-3.1 3.1-8.2 0-11.3l-39.6-39.6a8.03 8.03 0 00-11.3 0l-230 229.9L461.4 404a8.03 8.03 0 00-11.3 0L266.3 586.7a8.03 8.03 0 000 11.3l39.5 39.7z"}}]},name:"line-chart",theme:"outlined"},o=c(55015),i=a.forwardRef(function(e,t){return a.createElement(o.Z,(0,n.Z)({},e,{ref:t,icon:r}))})},28595:function(e,t,c){c.d(t,{Z:function(){return i}});var n=c(1119),a=c(2265),r={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}},{tag:"path",attrs:{d:"M719.4 499.1l-296.1-215A15.9 15.9 0 00398 297v430c0 13.1 14.8 20.5 25.3 12.9l296.1-215a15.9 15.9 0 000-25.8zm-257.6 134V390.9L628.5 512 461.8 633.1z"}}]},name:"play-circle",theme:"outlined"},o=c(55015),i=a.forwardRef(function(e,t){return a.createElement(o.Z,(0,n.Z)({},e,{ref:t,icon:r}))})},41361:function(e,t,c){c.d(t,{Z:function(){return i}});var n=c(1119),a=c(2265),r={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M824.2 699.9a301.55 301.55 0 00-86.4-60.4C783.1 602.8 812 546.8 812 484c0-110.8-92.4-201.7-203.2-200-109.1 1.7-197 90.6-197 200 0 62.8 29 118.8 74.2 155.5a300.95 300.95 0 00-86.4 60.4C345 754.6 314 826.8 312 903.8a8 8 0 008 8.2h56c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5A226.62 226.62 0 01612 684c60.9 0 118.2 23.7 161.3 66.8C814.5 792 838 846.3 840 904.3c.1 4.3 3.7 7.7 8 7.7h56a8 8 0 008-8.2c-2-77-33-149.2-87.8-203.9zM612 612c-34.2 0-66.4-13.3-90.5-37.5a126.86 126.86 0 01-37.5-91.8c.3-32.8 13.4-64.5 36.3-88 24-24.6 56.1-38.3 90.4-38.7 33.9-.3 66.8 12.9 91 36.6 24.8 24.3 38.4 56.8 38.4 91.4 0 34.2-13.3 66.3-37.5 90.5A127.3 127.3 0 01612 612zM361.5 510.4c-.9-8.7-1.4-17.5-1.4-26.4 0-15.9 1.5-31.4 4.3-46.5.7-3.6-1.2-7.3-4.5-8.8-13.6-6.1-26.1-14.5-36.9-25.1a127.54 127.54 0 01-38.7-95.4c.9-32.1 13.8-62.6 36.3-85.6 24.7-25.3 57.9-39.1 93.2-38.7 31.9.3 62.7 12.6 86 34.4 7.9 7.4 14.7 15.6 20.4 24.4 2 3.1 5.9 4.4 9.3 3.2 17.6-6.1 36.2-10.4 55.3-12.4 5.6-.6 8.8-6.6 6.3-11.6-32.5-64.3-98.9-108.7-175.7-109.9-110.9-1.7-203.3 89.2-203.3 199.9 0 62.8 28.9 118.8 74.2 155.5-31.8 14.7-61.1 35-86.5 60.4-54.8 54.7-85.8 126.9-87.8 204a8 8 0 008 8.2h56.1c4.3 0 7.9-3.4 8-7.7 1.9-58 25.4-112.3 66.7-153.5 29.4-29.4 65.4-49.8 104.7-59.7 3.9-1 6.5-4.7 6-8.7z"}}]},name:"team",theme:"outlined"},o=c(55015),i=a.forwardRef(function(e,t){return a.createElement(o.Z,(0,n.Z)({},e,{ref:t,icon:r}))})},13817:function(e,t,c){c.d(t,{default:function(){return H}});var n=c(83145),a=c(2265),r=c(36760),o=c.n(r),i=c(18694),f=c(71744),l=c(80856),u=c(45287),s=c(32186),h=c(25437),d=function(e,t){var c={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&0>t.indexOf(n)&&(c[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var a=0,n=Object.getOwnPropertySymbols(e);at.indexOf(n[a])&&Object.prototype.propertyIsEnumerable.call(e,n[a])&&(c[n[a]]=e[n[a]]);return c};function m(e){let{suffixCls:t,tagName:c,displayName:n}=e;return e=>a.forwardRef((n,r)=>a.createElement(e,Object.assign({ref:r,suffixCls:t,tagName:c},n)))}let v=a.forwardRef((e,t)=>{let{prefixCls:c,suffixCls:n,className:r,tagName:i}=e,l=d(e,["prefixCls","suffixCls","className","tagName"]),{getPrefixCls:u}=a.useContext(f.E_),s=u("layout",c),[m,v,p]=(0,h.ZP)(s),g=n?"".concat(s,"-").concat(n):s;return m(a.createElement(i,Object.assign({className:o()(c||g,r,v,p),ref:t},l)))}),p=a.forwardRef((e,t)=>{let{direction:c}=a.useContext(f.E_),[r,m]=a.useState([]),{prefixCls:v,className:p,rootClassName:g,children:Z,hasSider:y,tagName:z,style:H}=e,V=d(e,["prefixCls","className","rootClassName","children","hasSider","tagName","style"]),b=(0,i.Z)(V,["suffixCls"]),{getPrefixCls:M,className:w,style:x}=(0,f.dj)("layout"),k=M("layout",v),C="boolean"==typeof y?y:!!r.length||(0,u.Z)(Z).some(e=>e.type===s.Z),[N,E,O]=(0,h.ZP)(k),L=o()(k,{["".concat(k,"-has-sider")]:C,["".concat(k,"-rtl")]:"rtl"===c},w,p,g,E,O),j=a.useMemo(()=>({siderHook:{addSider:e=>{m(t=>[].concat((0,n.Z)(t),[e]))},removeSider:e=>{m(t=>t.filter(t=>t!==e))}}}),[]);return N(a.createElement(l.V.Provider,{value:j},a.createElement(z,Object.assign({ref:t,className:L,style:Object.assign(Object.assign({},x),H)},b),Z)))}),g=m({tagName:"div",displayName:"Layout"})(p),Z=m({suffixCls:"header",tagName:"header",displayName:"Header"})(v),y=m({suffixCls:"footer",tagName:"footer",displayName:"Footer"})(v),z=m({suffixCls:"content",tagName:"main",displayName:"Content"})(v);g.Header=Z,g.Footer=y,g.Content=z,g.Sider=s.Z,g._InternalSiderContext=s.D;var H=g},40875:function(e,t,c){c.d(t,{Z:function(){return n}});let n=(0,c(79205).Z)("chevron-down",[["path",{d:"m6 9 6 6 6-6",key:"qrunsl"}]])},22135:function(e,t,c){c.d(t,{Z:function(){return n}});let n=(0,c(79205).Z)("chevron-up",[["path",{d:"m18 15-6-6-6 6",key:"153udz"}]])},51817:function(e,t,c){c.d(t,{Z:function(){return n}});let n=(0,c(79205).Z)("loader-circle",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]])},21047:function(e,t,c){c.d(t,{Z:function(){return n}});let n=(0,c(79205).Z)("minus",[["path",{d:"M5 12h14",key:"1ays0h"}]])},70525:function(e,t,c){c.d(t,{Z:function(){return n}});let n=(0,c(79205).Z)("trending-up",[["path",{d:"M16 7h6v6",key:"box55l"}],["path",{d:"m22 7-8.5 8.5-5-5L2 17",key:"1t1m79"}]])},76865:function(e,t,c){c.d(t,{Z:function(){return n}});let n=(0,c(79205).Z)("triangle-alert",[["path",{d:"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3",key:"wmoenq"}],["path",{d:"M12 9v4",key:"juzpu7"}],["path",{d:"M12 17h.01",key:"p32p05"}]])},49663:function(e,t,c){c.d(t,{Z:function(){return n}});let n=(0,c(79205).Z)("user-check",[["path",{d:"m16 11 2 2 4-4",key:"9rsbq5"}],["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}]])},95805:function(e,t,c){c.d(t,{Z:function(){return n}});let n=(0,c(79205).Z)("users",[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["path",{d:"M16 3.128a4 4 0 0 1 0 7.744",key:"16gr8j"}],["path",{d:"M22 21v-2a4 4 0 0 0-3-3.87",key:"kshegd"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}]])}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1098-a1702da59647cf14.js b/litellm/proxy/_experimental/out/_next/static/chunks/1098-a1702da59647cf14.js new file mode 100644 index 00000000000..8bdb738f169 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/1098-a1702da59647cf14.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1098],{30280:function(e,t,l){l.d(t,{EX:function(){return c},Km:function(){return o},Tv:function(){return u}});var s=l(11713),a=l(45345),r=l(90246),i=l(19250),n=l(39760);let o=(0,r.n)("keys"),d=async function(e,t,l){let s=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};try{let a=(0,i.getProxyBaseUrl)(),r=new URLSearchParams(Object.entries({team_id:s.teamID,organization_id:s.organizationID,key_alias:s.selectedKeyAlias,key_hash:s.keyHash,user_id:s.userID,page:t,size:l,sort_by:s.sortBy,sort_order:s.sortOrder,expand:s.expand,status:s.status,return_full_object:"true",include_team_keys:"true",include_created_by_keys:"true"}).filter(e=>{let[,t]=e;return null!=t}).map(e=>{let[t,l]=e;return[t,String(l)]})),n="".concat(a?"".concat(a,"/key/list"):"/key/list","?").concat(r),o=await fetch(n,{method:"GET",headers:{[(0,i.getGlobalLitellmHeaderName)()]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.json(),t=(0,i.deriveErrorMessage)(e);throw(0,i.handleError)(t),Error(t)}let d=await o.json();return console.log("/key/list API Response:",d),d}catch(e){throw console.error("Failed to list keys:",e),e}},c=function(e,t){let l=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},{accessToken:r}=(0,n.Z)();return(0,s.a)({queryKey:o.list({page:e,limit:t,...l}),queryFn:async()=>await d(r,e,t,l),enabled:!!r,staleTime:3e4,placeholderData:a.Wk})},m=(0,r.n)("deletedKeys"),u=function(e,t){let l=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},{accessToken:r}=(0,n.Z)();return(0,s.a)({queryKey:m.list({page:e,limit:t,...l}),queryFn:async()=>await d(r,e,t,{...l,status:"deleted"}),enabled:!!r,staleTime:3e4,placeholderData:a.Wk})}},89348:function(e,t,l){l.d(t,{$:function(){return x}});var s=l(57437),a=l(16312),r=l(42264),i=l(65869),n=l(99397),o=l(2265),d=l(37592),c=l(99981),m=l(49322),u=l(15051),h=l(32489);function g(e){let{group:t,onChange:l,availableModels:a,maxFallbacks:r}=e,i=a.filter(e=>e!==t.primaryModel),n=e=>{let s=t.fallbackModels.filter((t,l)=>l!==e);l({...t,fallbackModels:s})},o=t.fallbackModels.length{let s=[...t.fallbackModels];s.includes(e)&&(s=s.filter(t=>t!==e)),l({...t,primaryModel:e,fallbackModels:s})},showSearch:!0,filterOption:(e,t)=>{var l;return(null!==(l=null==t?void 0:t.label)&&void 0!==l?l:"").toLowerCase().includes(e.toLowerCase())},options:a.map(e=>({label:e,value:e}))}),!t.primaryModel&&(0,s.jsxs)("div",{className:"mt-2 flex items-center gap-2 text-amber-600 text-xs bg-amber-50 p-2 rounded",children:[(0,s.jsx)(m.Z,{className:"w-4 h-4"}),(0,s.jsx)("span",{children:"Select a model to begin configuring fallbacks"})]})]}),(0,s.jsx)("div",{className:"flex items-center justify-center -my-4 z-10",children:(0,s.jsxs)("div",{className:"bg-indigo-50 text-indigo-500 px-4 py-1 rounded-full text-xs font-bold border border-indigo-100 flex items-center gap-2 shadow-sm",children:[(0,s.jsx)(u.Z,{className:"w-4 h-4"}),"IF FAILS, TRY..."]})}),(0,s.jsxs)("div",{className:"transition-opacity duration-300 ".concat(t.primaryModel?"opacity-100":"opacity-50 pointer-events-none"),children:[(0,s.jsxs)("label",{className:"block text-sm font-semibold text-gray-700 mb-2",children:["Fallback Chain ",(0,s.jsx)("span",{className:"text-red-500",children:"*"}),(0,s.jsxs)("span",{className:"text-xs text-gray-500 font-normal ml-2",children:["(Max ",r," fallbacks at a time)"]})]}),(0,s.jsxs)("div",{className:"bg-gray-50 rounded-xl p-4 border border-gray-200",children:[(0,s.jsxs)("div",{className:"mb-4",children:[(0,s.jsx)(d.default,{mode:"multiple",className:"w-full",size:"large",placeholder:o?"Select fallback models to add...":"Maximum ".concat(r," fallbacks reached"),value:t.fallbackModels,onChange:e=>{let s=e.slice(0,r);l({...t,fallbackModels:s})},disabled:!t.primaryModel,options:i.map(e=>({label:e,value:e})),optionRender:(e,l)=>{let a=t.fallbackModels.includes(e.value),r=a?t.fallbackModels.indexOf(e.value)+1:null;return(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[a&&null!==r&&(0,s.jsx)("span",{className:"flex items-center justify-center w-5 h-5 rounded bg-indigo-100 text-indigo-600 text-xs font-bold",children:r}),(0,s.jsx)("span",{children:e.label})]})},maxTagCount:"responsive",maxTagPlaceholder:e=>(0,s.jsx)(c.Z,{styles:{root:{pointerEvents:"none"}},title:e.map(e=>{let{value:t}=e;return t}).join(", "),children:(0,s.jsxs)("span",{children:["+",e.length," more"]})}),showSearch:!0,filterOption:(e,t)=>{var l;return(null!==(l=null==t?void 0:t.label)&&void 0!==l?l:"").toLowerCase().includes(e.toLowerCase())}}),(0,s.jsx)("p",{className:"text-xs text-gray-500 mt-1 ml-1",children:o?"Search and select multiple models. Selected models will appear below in order. (".concat(t.fallbackModels.length,"/").concat(r," used)"):"Maximum ".concat(r," fallbacks reached. Remove some to add more.")})]}),(0,s.jsx)("div",{className:"space-y-2 min-h-[100px]",children:0===t.fallbackModels.length?(0,s.jsxs)("div",{className:"h-32 border-2 border-dashed border-gray-300 rounded-lg flex flex-col items-center justify-center text-gray-400",children:[(0,s.jsx)("span",{className:"text-sm",children:"No fallback models selected"}),(0,s.jsx)("span",{className:"text-xs mt-1",children:"Add models from the dropdown above"})]}):t.fallbackModels.map((e,t)=>(0,s.jsxs)("div",{className:"group flex items-center justify-between p-3 bg-white rounded-lg border border-gray-200 hover:border-indigo-300 hover:shadow-sm transition-all",children:[(0,s.jsxs)("div",{className:"flex items-center gap-3",children:[(0,s.jsx)("div",{className:"flex items-center justify-center w-6 h-6 rounded bg-gray-100 text-gray-400 group-hover:text-indigo-500 group-hover:bg-indigo-50",children:(0,s.jsx)("span",{className:"text-xs font-bold",children:t+1})}),(0,s.jsx)("div",{children:(0,s.jsx)("span",{className:"font-medium text-gray-800",children:e})})]}),(0,s.jsx)("button",{type:"button",onClick:()=>n(t),className:"opacity-0 group-hover:opacity-100 transition-opacity text-gray-400 hover:text-red-500 p-1",children:(0,s.jsx)(h.Z,{className:"w-4 h-4"})})]},"".concat(e,"-").concat(t)))})]})]})]})}function x(e){let{groups:t,onGroupsChange:l,availableModels:d,maxFallbacks:c=5,maxGroups:m=5}=e,[u,h]=(0,o.useState)(t.length>0?t[0].id:"1");(0,o.useEffect)(()=>{t.length>0?t.some(e=>e.id===u)||h(t[0].id):h("1")},[t]);let x=()=>{if(t.length>=m)return;let e=Date.now().toString();l([...t,{id:e,primaryModel:null,fallbackModels:[]}]),h(e)},p=e=>{if(1===t.length){r.ZP.warning("At least one group is required");return}let s=t.filter(t=>t.id!==e);l(s),u===e&&s.length>0&&h(s[s.length-1].id)},y=e=>{l(t.map(t=>t.id===e.id?e:t))},f=t.map((e,l)=>{let a=e.primaryModel?e.primaryModel:"Group ".concat(l+1);return{key:e.id,label:a,closable:t.length>1,children:(0,s.jsx)(g,{group:e,onChange:y,availableModels:d,maxFallbacks:c})}});return 0===t.length?(0,s.jsxs)("div",{className:"text-center py-12 bg-gray-50 rounded-lg border border-dashed border-gray-300",children:[(0,s.jsx)("p",{className:"text-gray-500 mb-4",children:"No fallback groups configured"}),(0,s.jsx)(a.z,{variant:"primary",onClick:x,icon:()=>(0,s.jsx)(n.Z,{className:"w-4 h-4"}),children:"Create First Group"})]}):(0,s.jsx)(i.default,{type:"editable-card",activeKey:u,onChange:h,onEdit:(e,l)=>{"add"===l?x():"remove"===l&&t.length>1&&p(e)},items:f,className:"fallback-tabs",tabBarStyle:{marginBottom:0},hideAdd:t.length>=m})}},62099:function(e,t,l){var s=l(57437),a=l(2265),r=l(37592),i=l(99981),n=l(23496),o=l(63709),d=l(15424),c=l(31283);let{Option:m}=r.default;t.Z=e=>{var t;let{form:l,autoRotationEnabled:u,onAutoRotationChange:h,rotationInterval:g,onRotationIntervalChange:x,isCreateMode:p=!1}=e,y=g&&!["7d","30d","90d","180d","365d"].includes(g),[f,j]=(0,a.useState)(y),[b,v]=(0,a.useState)(y?g:""),[_,N]=(0,a.useState)((null==l?void 0:null===(t=l.getFieldValue)||void 0===t?void 0:t.call(l,"duration"))||"");return(0,s.jsxs)("div",{className:"space-y-6",children:[(0,s.jsxs)("div",{className:"space-y-4",children:[(0,s.jsx)("span",{className:"text-sm font-medium text-gray-700",children:"Key Expiry Settings"}),(0,s.jsxs)("div",{className:"space-y-2",children:[(0,s.jsxs)("label",{className:"text-sm font-medium text-gray-700 flex items-center space-x-1",children:[(0,s.jsx)("span",{children:"Expire Key"}),(0,s.jsx)(i.Z,{title:p?"Set when this key should expire. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days). Leave empty to never expire.":"Set when this key should expire. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days). Use -1 to never expire.",children:(0,s.jsx)(d.Z,{className:"text-gray-400 cursor-help text-xs"})})]}),(0,s.jsx)(c.o,{name:"duration",placeholder:p?"e.g., 30d or leave empty to never expire":"e.g., 30d or -1 to never expire",className:"w-full",value:_,onValueChange:e=>{N(e),l&&"function"==typeof l.setFieldValue?l.setFieldValue("duration",e):l&&"function"==typeof l.setFieldsValue&&l.setFieldsValue({duration:e})}})]})]}),(0,s.jsx)(n.Z,{}),(0,s.jsxs)("div",{className:"space-y-4",children:[(0,s.jsx)("span",{className:"text-sm font-medium text-gray-700",children:"Auto-Rotation Settings"}),(0,s.jsxs)("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-4",children:[(0,s.jsxs)("div",{className:"space-y-2",children:[(0,s.jsxs)("label",{className:"text-sm font-medium text-gray-700 flex items-center space-x-1",children:[(0,s.jsx)("span",{children:"Enable Auto-Rotation"}),(0,s.jsx)(i.Z,{title:"Key will automatically regenerate at the specified interval for enhanced security.",children:(0,s.jsx)(d.Z,{className:"text-gray-400 cursor-help text-xs"})})]}),(0,s.jsx)(o.Z,{checked:u,onChange:h,size:"default",className:u?"":"bg-gray-400"})]}),u&&(0,s.jsxs)("div",{className:"space-y-2",children:[(0,s.jsxs)("label",{className:"text-sm font-medium text-gray-700 flex items-center space-x-1",children:[(0,s.jsx)("span",{children:"Rotation Interval"}),(0,s.jsx)(i.Z,{title:"How often the key should be automatically rotated. Choose the interval that best fits your security requirements.",children:(0,s.jsx)(d.Z,{className:"text-gray-400 cursor-help text-xs"})})]}),(0,s.jsxs)("div",{className:"space-y-2",children:[(0,s.jsxs)(r.default,{value:f?"custom":g,onChange:e=>{"custom"===e?j(!0):(j(!1),v(""),x(e))},className:"w-full",placeholder:"Select interval",children:[(0,s.jsx)(m,{value:"7d",children:"7 days"}),(0,s.jsx)(m,{value:"30d",children:"30 days"}),(0,s.jsx)(m,{value:"90d",children:"90 days"}),(0,s.jsx)(m,{value:"180d",children:"180 days"}),(0,s.jsx)(m,{value:"365d",children:"365 days"}),(0,s.jsx)(m,{value:"custom",children:"Custom interval"})]}),f&&(0,s.jsxs)("div",{className:"space-y-1",children:[(0,s.jsx)(c.o,{value:b,onChange:e=>{let t=e.target.value;v(t),x(t)},placeholder:"e.g., 1s, 5m, 2h, 14d"}),(0,s.jsx)("div",{className:"text-xs text-gray-500",children:"Supported formats: seconds (s), minutes (m), hours (h), days (d)"})]})]})]})]}),u&&(0,s.jsx)("div",{className:"bg-blue-50 p-3 rounded-md text-sm text-blue-700",children:"When rotation occurs, you'll receive a notification with the new key. The old key will be deactivated after a brief grace period."})]})]})}},72885:function(e,t,l){var s=l(57437),a=l(2265),r=l(77355),i=l(93416),n=l(74998),o=l(95704),d=l(76593),c=l(9114);t.Z=e=>{let{accessToken:t,initialModelAliases:l={},onAliasUpdate:m,showExampleConfig:u=!0}=e,[h,g]=(0,a.useState)([]),[x,p]=(0,a.useState)({aliasName:"",targetModel:""}),[y,f]=(0,a.useState)(null);(0,a.useEffect)(()=>{g(Object.entries(l).map((e,t)=>{let[l,s]=e;return{id:"".concat(t,"-").concat(l),aliasName:l,targetModel:s}}))},[l]);let j=e=>{f({...e})},b=()=>{if(!y)return;if(!y.aliasName||!y.targetModel){c.Z.fromBackend("Please provide both alias name and target model");return}if(h.some(e=>e.id!==y.id&&e.aliasName===y.aliasName)){c.Z.fromBackend("An alias with this name already exists");return}let e=h.map(e=>e.id===y.id?y:e);g(e),f(null);let t={};e.forEach(e=>{t[e.aliasName]=e.targetModel}),m&&m(t),c.Z.success("Alias updated successfully")},v=()=>{f(null)},_=e=>{let t=h.filter(t=>t.id!==e);g(t);let l={};t.forEach(e=>{l[e.aliasName]=e.targetModel}),m&&m(l),c.Z.success("Alias deleted successfully")},N=h.reduce((e,t)=>(e[t.aliasName]=t.targetModel,e),{});return(0,s.jsxs)("div",{className:"mt-4",children:[(0,s.jsxs)("div",{className:"mb-6",children:[(0,s.jsx)(o.xv,{className:"text-sm font-medium text-gray-700 mb-2",children:"Add New Alias"}),(0,s.jsxs)("div",{className:"grid grid-cols-3 gap-4",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)("label",{className:"block text-xs text-gray-500 mb-1",children:"Alias Name"}),(0,s.jsx)("input",{type:"text",value:x.aliasName,onChange:e=>p({...x,aliasName:e.target.value}),placeholder:"e.g., gpt-4o",className:"w-full px-3 py-2 border border-gray-300 rounded-md text-sm"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)("label",{className:"block text-xs text-gray-500 mb-1",children:"Target Model"}),(0,s.jsx)(d.Z,{accessToken:t,value:x.targetModel,placeholder:"Select target model",onChange:e=>p({...x,targetModel:e}),showLabel:!1})]}),(0,s.jsx)("div",{className:"flex items-end",children:(0,s.jsxs)("button",{onClick:()=>{if(!x.aliasName||!x.targetModel){c.Z.fromBackend("Please provide both alias name and target model");return}if(h.some(e=>e.aliasName===x.aliasName)){c.Z.fromBackend("An alias with this name already exists");return}let e=[...h,{id:"".concat(Date.now(),"-").concat(x.aliasName),aliasName:x.aliasName,targetModel:x.targetModel}];g(e),p({aliasName:"",targetModel:""});let t={};e.forEach(e=>{t[e.aliasName]=e.targetModel}),m&&m(t),c.Z.success("Alias added successfully")},disabled:!x.aliasName||!x.targetModel,className:"flex items-center px-4 py-2 rounded-md text-sm ".concat(x.aliasName&&x.targetModel?"bg-green-600 text-white hover:bg-green-700":"bg-gray-300 text-gray-500 cursor-not-allowed"),children:[(0,s.jsx)(r.Z,{className:"w-4 h-4 mr-1"}),"Add Alias"]})})]})]}),(0,s.jsx)(o.xv,{className:"text-sm font-medium text-gray-700 mb-2",children:"Manage Existing Aliases"}),(0,s.jsx)("div",{className:"rounded-lg custom-border relative mb-6",children:(0,s.jsx)("div",{className:"overflow-x-auto",children:(0,s.jsxs)(o.iA,{className:"[&_td]:py-0.5 [&_th]:py-1",children:[(0,s.jsx)(o.ss,{children:(0,s.jsxs)(o.SC,{children:[(0,s.jsx)(o.xs,{className:"py-1 h-8",children:"Alias Name"}),(0,s.jsx)(o.xs,{className:"py-1 h-8",children:"Target Model"}),(0,s.jsx)(o.xs,{className:"py-1 h-8",children:"Actions"})]})}),(0,s.jsxs)(o.RM,{children:[h.map(e=>(0,s.jsx)(o.SC,{className:"h-8",children:y&&y.id===e.id?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(o.pj,{className:"py-0.5",children:(0,s.jsx)("input",{type:"text",value:y.aliasName,onChange:e=>f({...y,aliasName:e.target.value}),className:"w-full px-2 py-1 border border-gray-300 rounded-md text-sm"})}),(0,s.jsx)(o.pj,{className:"py-0.5",children:(0,s.jsx)(d.Z,{accessToken:t,value:y.targetModel,onChange:e=>f({...y,targetModel:e}),showLabel:!1,style:{height:"32px"}})}),(0,s.jsx)(o.pj,{className:"py-0.5 whitespace-nowrap",children:(0,s.jsxs)("div",{className:"flex space-x-2",children:[(0,s.jsx)("button",{onClick:b,className:"text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded hover:bg-blue-100",children:"Save"}),(0,s.jsx)("button",{onClick:v,className:"text-xs bg-gray-50 text-gray-600 px-2 py-1 rounded hover:bg-gray-100",children:"Cancel"})]})})]}):(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(o.pj,{className:"py-0.5 text-sm text-gray-900",children:e.aliasName}),(0,s.jsx)(o.pj,{className:"py-0.5 text-sm text-gray-500",children:e.targetModel}),(0,s.jsx)(o.pj,{className:"py-0.5 whitespace-nowrap",children:(0,s.jsxs)("div",{className:"flex space-x-2",children:[(0,s.jsx)("button",{onClick:()=>j(e),className:"text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded hover:bg-blue-100",children:(0,s.jsx)(i.Z,{className:"w-3 h-3"})}),(0,s.jsx)("button",{onClick:()=>_(e.id),className:"text-xs bg-red-50 text-red-600 px-2 py-1 rounded hover:bg-red-100",children:(0,s.jsx)(n.Z,{className:"w-3 h-3"})})]})})]})},e.id)),0===h.length&&(0,s.jsx)(o.SC,{children:(0,s.jsx)(o.pj,{colSpan:3,className:"py-0.5 text-sm text-gray-500 text-center",children:"No aliases added yet. Add a new alias above."})})]})]})})}),u&&(0,s.jsxs)(o.Zb,{children:[(0,s.jsx)(o.Dx,{className:"mb-4",children:"Configuration Example"}),(0,s.jsx)(o.xv,{className:"text-gray-600 mb-4",children:"Here's how your current aliases would look in the config:"}),(0,s.jsx)("div",{className:"bg-gray-100 rounded-lg p-4 font-mono text-sm",children:(0,s.jsxs)("div",{className:"text-gray-700",children:["model_aliases:",0===Object.keys(N).length?(0,s.jsxs)("span",{className:"text-gray-500",children:[(0,s.jsx)("br",{}),"\xa0\xa0# No aliases configured yet"]}):Object.entries(N).map(e=>{let[t,l]=e;return(0,s.jsxs)("span",{children:[(0,s.jsx)("br",{}),'\xa0\xa0"',t,'": "',l,'"']},t)})]})})]})]})}},76593:function(e,t,l){var s=l(57437),a=l(2265),r=l(56522),i=l(37592),n=l(69993),o=l(10703);t.Z=e=>{let{accessToken:t,value:l,placeholder:d="Select a Model",onChange:c,disabled:m=!1,style:u,className:h,showLabel:g=!0,labelText:x="Select Model"}=e,[p,y]=(0,a.useState)(l),[f,j]=(0,a.useState)(!1),[b,v]=(0,a.useState)([]),_=(0,a.useRef)(null);return(0,a.useEffect)(()=>{y(l)},[l]),(0,a.useEffect)(()=>{t&&(async()=>{try{let e=await (0,o.p)(t);console.log("Fetched models for selector:",e),e.length>0&&v(e)}catch(e){console.error("Error fetching model info:",e)}})()},[t]),(0,s.jsxs)("div",{children:[g&&(0,s.jsxs)(r.x,{className:"font-medium block mb-2 text-gray-700 flex items-center",children:[(0,s.jsx)(n.Z,{className:"mr-2"})," ",x]}),(0,s.jsx)(i.default,{value:p,placeholder:d,onChange:e=>{"custom"===e?(j(!0),y(void 0)):(j(!1),y(e),c&&c(e))},options:[...Array.from(new Set(b.map(e=>e.model_group))).map((e,t)=>({value:e,label:e,key:t})),{value:"custom",label:"Enter custom model",key:"custom"}],style:{width:"100%",...u},showSearch:!0,className:"rounded-md ".concat(h||""),disabled:m}),f&&(0,s.jsx)(r.o,{className:"mt-2",placeholder:"Enter custom model name",onValueChange:e=>{_.current&&clearTimeout(_.current),_.current=setTimeout(()=>{y(e),c&&c(e)},500)},disabled:m})]})}},2597:function(e,t,l){var s=l(57437);l(2265);var a=l(92280),r=l(54507);t.Z=function(e){let{value:t,onChange:l,premiumUser:i=!1,disabledCallbacks:n=[],onDisabledCallbacksChange:o}=e;return i?(0,s.jsx)(r.Z,{value:t,onChange:l,disabledCallbacks:n,onDisabledCallbacksChange:o}):(0,s.jsxs)("div",{children:[(0,s.jsxs)("div",{className:"flex flex-wrap gap-2 mb-3",children:[(0,s.jsx)("div",{className:"inline-flex items-center px-3 py-1.5 rounded-lg bg-green-50 border border-green-200 text-green-800 text-sm font-medium opacity-50",children:"✨ langfuse-logging"}),(0,s.jsx)("div",{className:"inline-flex items-center px-3 py-1.5 rounded-lg bg-green-50 border border-green-200 text-green-800 text-sm font-medium opacity-50",children:"✨ datadog-logging"})]}),(0,s.jsx)("div",{className:"p-3 bg-yellow-50 border border-yellow-200 rounded-lg",children:(0,s.jsxs)(a.x,{className:"text-sm text-yellow-800",children:["Setting Key/Team logging settings is a LiteLLM Enterprise feature. Global Logging Settings are available for all free users. Get a trial key"," ",(0,s.jsx)("a",{href:"https://www.litellm.ai/#pricing",target:"_blank",rel:"noopener noreferrer",className:"underline",children:"here"}),"."]})})]})}},65895:function(e,t,l){var s=l(57437);l(2265);var a=l(37592),r=l(10032),i=l(99981),n=l(15424);let{Option:o}=a.default;t.Z=e=>{let{type:t,name:l,showDetailedDescriptions:d=!0,className:c="",initialValue:m=null,form:u,onChange:h}=e,g=t.toUpperCase(),x=t.toLowerCase(),p="Select 'guaranteed_throughput' to prevent overallocating ".concat(g," limit when the key belongs to a Team with specific ").concat(g," limits.");return(0,s.jsx)(r.Z.Item,{label:(0,s.jsxs)("span",{children:[g," Rate Limit Type"," ",(0,s.jsx)(i.Z,{title:p,children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:l,initialValue:m,className:c,children:(0,s.jsx)(a.default,{defaultValue:d?"default":void 0,placeholder:"Select rate limit type",style:{width:"100%"},optionLabelProp:d?"label":void 0,onChange:e=>{u&&u.setFieldValue(l,e),h&&h(e)},children:d?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(o,{value:"best_effort_throughput",label:"Default",children:(0,s.jsxs)("div",{style:{padding:"4px 0"},children:[(0,s.jsx)("div",{style:{fontWeight:500},children:"Default"}),(0,s.jsxs)("div",{style:{fontSize:"11px",color:"#6b7280",marginTop:"2px"},children:["Best effort throughput - no error if we're overallocating ",x," (Team/Key Limits checked at runtime)."]})]})}),(0,s.jsx)(o,{value:"guaranteed_throughput",label:"Guaranteed throughput",children:(0,s.jsxs)("div",{style:{padding:"4px 0"},children:[(0,s.jsx)("div",{style:{fontWeight:500},children:"Guaranteed throughput"}),(0,s.jsxs)("div",{style:{fontSize:"11px",color:"#6b7280",marginTop:"2px"},children:["Guaranteed throughput - raise an error if we're overallocating ",x," (also checks model-specific limits)"]})]})}),(0,s.jsx)(o,{value:"dynamic",label:"Dynamic",children:(0,s.jsxs)("div",{style:{padding:"4px 0"},children:[(0,s.jsx)("div",{style:{fontWeight:500},children:"Dynamic"}),(0,s.jsxs)("div",{style:{fontSize:"11px",color:"#6b7280",marginTop:"2px"},children:["If the key has a set ",g," (e.g. 2 ",g,") and there are no 429 errors, it can dynamically exceed the limit when the model being called is not erroring."]})]})})]}):(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(o,{value:"best_effort_throughput",children:"Best effort throughput"}),(0,s.jsx)(o,{value:"guaranteed_throughput",children:"Guaranteed throughput"}),(0,s.jsx)(o,{value:"dynamic",children:"Dynamic"})]})})})}},76364:function(e,t,l){var s=l(57437),a=l(2265),r=l(58643),i=l(19250),n=l(56334),o=l(89348),d=l(10703);let c=(0,a.forwardRef)((e,t)=>{let{accessToken:l,value:c,onChange:m,modelData:u}=e,[h,g]=(0,a.useState)({routerSettings:{},selectedStrategy:null,enableTagFiltering:!1}),[x,p]=(0,a.useState)([]),[y,f]=(0,a.useState)([]),[j,b]=(0,a.useState)([]),[v,_]=(0,a.useState)([]),[N,w]=(0,a.useState)({}),[k,S]=(0,a.useState)({}),Z=(0,a.useRef)(!1),C=(0,a.useRef)(null),M=e=>e&&0!==e.length?e.map((e,t)=>{let[l,s]=Object.entries(e)[0];return{id:(t+1).toString(),primaryModel:l||null,fallbackModels:s||[]}}):[{id:"1",primaryModel:null,fallbackModels:[]}],T=e=>e.filter(e=>e.primaryModel&&e.fallbackModels.length>0).map(e=>({[e.primaryModel]:e.fallbackModels}));(0,a.useEffect)(()=>{let e=(null==c?void 0:c.router_settings)?JSON.stringify({routing_strategy:c.router_settings.routing_strategy,fallbacks:c.router_settings.fallbacks,enable_tag_filtering:c.router_settings.enable_tag_filtering}):null;if(Z.current&&e===C.current){Z.current=!1;return}if(Z.current&&e!==C.current&&(Z.current=!1),e!==C.current){if(C.current=e,null==c?void 0:c.router_settings){var t;let e=c.router_settings,{fallbacks:l,...s}=e;g({routerSettings:s,selectedStrategy:e.routing_strategy||null,enableTagFiltering:null!==(t=e.enable_tag_filtering)&&void 0!==t&&t});let a=e.fallbacks||[];p(a),f(M(a))}else g({routerSettings:{},selectedStrategy:null,enableTagFiltering:!1}),p([]),f([{id:"1",primaryModel:null,fallbackModels:[]}])}},[c]),(0,a.useEffect)(()=>{l&&(0,i.getRouterSettingsCall)(l).then(e=>{if(e.fields){let t={};e.fields.forEach(e=>{t[e.field_name]={ui_field_name:e.ui_field_name,field_description:e.field_description,options:e.options,link:e.link}}),w(t);let l=e.fields.find(e=>"routing_strategy"===e.field_name);(null==l?void 0:l.options)&&_(l.options),e.routing_strategy_descriptions&&S(e.routing_strategy_descriptions)}})},[l]),(0,a.useEffect)(()=>{l&&(async()=>{try{let e=await (0,d.p)(l);b(e)}catch(e){console.error("Error fetching model info for fallbacks:",e)}})()},[l]);let L=()=>{let e=new Set(["allowed_fails","cooldown_time","num_retries","timeout","retry_after"]),t=new Set(["model_group_alias","retry_policy"]),l=(l,s,a)=>{if(null==s)return a;let r=String(s).trim();if(""===r||"null"===r.toLowerCase())return null;if(e.has(l)){let e=Number(r);return Number.isNaN(e)?a:e}if(t.has(l)){if(""===r)return null;try{return JSON.parse(r)}catch(e){return a}}return"true"===r.toLowerCase()||"false"!==r.toLowerCase()&&r},s=Object.fromEntries(Object.entries({...h.routerSettings,enable_tag_filtering:h.enableTagFiltering,routing_strategy:h.selectedStrategy,fallbacks:x.length>0?x:null}).map(e=>{let[t,s]=e;if("routing_strategy_args"!==t&&"routing_strategy"!==t&&"enable_tag_filtering"!==t&&"fallbacks"!==t){let e=document.querySelector('input[name="'.concat(t,'"]'));if(e&&void 0!==e.value&&""!==e.value){let a=l(t,e.value,s);return[t,a]}}else if("routing_strategy"===t)return[t,h.selectedStrategy];else if("enable_tag_filtering"===t)return[t,h.enableTagFiltering];else if("fallbacks"===t)return[t,x.length>0?x:null];else if("routing_strategy_args"===t&&"latency-based-routing"===h.selectedStrategy){let e=document.querySelector('input[name="lowest_latency_buffer"]'),t=document.querySelector('input[name="ttl"]'),l={};return(null==e?void 0:e.value)&&(l.lowest_latency_buffer=Number(e.value)),(null==t?void 0:t.value)&&(l.ttl=Number(t.value)),["routing_strategy_args",Object.keys(l).length>0?l:null]}return[t,s]}).filter(e=>null!=e)),a=function(e){let t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];return null==e||"object"==typeof e&&!Array.isArray(e)&&0===Object.keys(e).length||t&&("number"!=typeof e||Number.isNaN(e))?null:e};return{routing_strategy:a(s.routing_strategy),allowed_fails:a(s.allowed_fails,!0),cooldown_time:a(s.cooldown_time,!0),num_retries:a(s.num_retries,!0),timeout:a(s.timeout,!0),retry_after:a(s.retry_after,!0),fallbacks:x.length>0?x:null,context_window_fallbacks:a(s.context_window_fallbacks),retry_policy:a(s.retry_policy),model_group_alias:a(s.model_group_alias),enable_tag_filtering:h.enableTagFiltering,routing_strategy_args:a(s.routing_strategy_args)}};(0,a.useEffect)(()=>{if(!m)return;let e=setTimeout(()=>{Z.current=!0,m({router_settings:L()})},100);return()=>clearTimeout(e)},[h,x]);let F=Array.from(new Set(j.map(e=>e.model_group))).sort();return((0,a.useImperativeHandle)(t,()=>({getValue:()=>({router_settings:L()})})),l)?(0,s.jsx)("div",{className:"w-full",children:(0,s.jsxs)(r.v0,{className:"w-full",children:[(0,s.jsxs)(r.td,{variant:"line",defaultValue:"1",className:"px-8 pt-4",children:[(0,s.jsx)(r.OK,{value:"1",children:"Loadbalancing"}),(0,s.jsx)(r.OK,{value:"2",children:"Fallbacks"})]}),(0,s.jsxs)(r.nP,{className:"px-8 py-6",children:[(0,s.jsx)(r.x4,{children:(0,s.jsx)(n.Z,{value:h,onChange:g,routerFieldsMetadata:N,availableRoutingStrategies:v,routingStrategyDescriptions:k})}),(0,s.jsx)(r.x4,{children:(0,s.jsx)(o.$,{groups:y,onGroupsChange:e=>{f(e),p(T(e))},availableModels:F,maxFallbacks:5,maxGroups:5})})]})]})}):null});c.displayName="RouterSettingsAccordion",t.Z=c},71098:function(e,t,l){l.d(t,{ZP:function(){return et},wk:function(){return X},Nr:function(){return ee}});var s=l(57437),a=l(30280),r=l(39760),i=l(59872),n=l(15424),o=l(29827),d=l(87452),c=l(88829),m=l(72208),u=l(78489),h=l(49804),g=l(67101),x=l(84264),p=l(49566),y=l(96761),f=l(37592),j=l(10032),b=l(22116),v=l(99981),_=l(29967),N=l(5545),w=l(63709),k=l(4260),S=l(7310),Z=l.n(S),C=l(2265),M=l(29233),T=l(20347),L=l(82586),F=l(97434),P=l(65925),A=l(63610),E=l(62099),I=l(72885),V=l(95096),R=l(2597),O=l(65895),D=l(76364),K=l(84376),U=l(7765),q=l(46468),B=l(97492),G=l(68473),z=l(9114),J=l(19250),W=l(24199),H=l(97415);let Y=e=>{let t;if(!(t=!e||"object"!=typeof e||e instanceof Error?String(e):JSON.stringify(e)).includes("/key/generate")&&!t.includes("KeyManagementRoutes.KEY_GENERATE"))return"Error creating the key: ".concat(e);let l=t;try{if(!e||"object"!=typeof e||e instanceof Error){let e=t.match(/\{[\s\S]*\}/);if(e){let t=JSON.parse(e[0]),s=(null==t?void 0:t.error)||t;(null==s?void 0:s.message)&&(l=s.message)}}else{let t=(null==e?void 0:e.error)||e;(null==t?void 0:t.message)&&(l=t.message)}}catch(e){}return t.includes("team_member_permission_error")||l.includes("Team member does not have permissions")?"Team member does not have permission to generate key for this team. Ask your proxy admin to configure the team member permission settings.":"Error creating the key: ".concat(e)},{Option:$}=f.default,Q=e=>{let t=[];if(console.log("data:",JSON.stringify(e)),e)for(let l of e)l.metadata&&l.metadata.tags&&t.push(...l.metadata.tags);let l=Array.from(new Set(t)).map(e=>({value:e,label:e}));return console.log("uniqueTags:",l),l},X=async(e,t,l,s)=>{try{if(null===e||null===t)return[];if(null!==l){let a=(await (0,J.modelAvailableCall)(l,e,t,!0,s,!0)).data.map(e=>e.id);return console.log("available_model_names:",a),a}return[]}catch(e){return console.error("Error fetching user models:",e),[]}},ee=async(e,t,l,s)=>{try{if(null===e||null===t)return;if(null!==l){let a=(await (0,J.modelAvailableCall)(l,e,t)).data.map(e=>e.id);console.log("available_model_names:",a),s(a)}}catch(e){console.error("Error fetching user models:",e)}};var et=e=>{let{team:t,teams:l,data:S,addKey:et}=e,{accessToken:el,userId:es,userRole:ea,premiumUser:er}=(0,r.Z)(),ei=(0,o.NL)(),[en]=j.Z.useForm(),[eo,ed]=(0,C.useState)(!1),[ec,em]=(0,C.useState)(null),[eu,eh]=(0,C.useState)(null),[eg,ex]=(0,C.useState)([]),[ep,ey]=(0,C.useState)([]),[ef,ej]=(0,C.useState)("you"),[eb,ev]=(0,C.useState)(Q(S)),[e_,eN]=(0,C.useState)([]),[ew,ek]=(0,C.useState)([]),[eS,eZ]=(0,C.useState)([]),[eC,eM]=(0,C.useState)([]),[eT,eL]=(0,C.useState)(t),[eF,eP]=(0,C.useState)(!1),[eA,eE]=(0,C.useState)(null),[eI,eV]=(0,C.useState)({}),[eR,eO]=(0,C.useState)([]),[eD,eK]=(0,C.useState)(!1),[eU,eq]=(0,C.useState)([]),[eB,eG]=(0,C.useState)([]),[ez,eJ]=(0,C.useState)("llm_api"),[eW,eH]=(0,C.useState)({}),[eY,e$]=(0,C.useState)(!1),[eQ,eX]=(0,C.useState)("30d"),[e0,e4]=(0,C.useState)(null),[e1,e2]=(0,C.useState)(0),e5=()=>{ed(!1),en.resetFields(),eM([]),eG([]),eJ("llm_api"),eH({}),e$(!1),eX("30d"),e4(null),e2(e=>e+1)},e3=()=>{ed(!1),em(null),eL(null),en.resetFields(),eM([]),eG([]),eJ("llm_api"),eH({}),e$(!1),eX("30d"),e4(null),e2(e=>e+1)};(0,C.useEffect)(()=>{es&&ea&&el&&ee(es,ea,el,ex)},[el,es,ea]),(0,C.useEffect)(()=>{let e=async()=>{try{let e=(await (0,J.getPoliciesList)(el)).policies.map(e=>e.policy_name);ek(e)}catch(e){console.error("Failed to fetch policies:",e)}},t=async()=>{try{let e=await (0,J.getPromptsList)(el);eZ(e.prompts.map(e=>e.prompt_id))}catch(e){console.error("Failed to fetch prompts:",e)}};(async()=>{try{let e=(await (0,J.getGuardrailsList)(el)).guardrails.map(e=>e.guardrail_name);eN(e)}catch(e){console.error("Failed to fetch guardrails:",e)}})(),e(),t()},[el]),(0,C.useEffect)(()=>{(async()=>{try{if(el){let e=sessionStorage.getItem("possibleUserRoles");if(e)eV(JSON.parse(e));else{let e=await (0,J.getPossibleUserRoles)(el);sessionStorage.setItem("possibleUserRoles",JSON.stringify(e)),eV(e)}}}catch(e){console.error("Error fetching possible user roles:",e)}})()},[el]);let e7=ep.includes("no-default-models")&&!eT,e6=async e=>{try{var t,l,s,r,i,n,o;let d;let c=null!==(i=null==e?void 0:e.key_alias)&&void 0!==i?i:"",m=null!==(n=null==e?void 0:e.team_id)&&void 0!==n?n:null;if((null!==(o=null==S?void 0:S.filter(e=>e.team_id===m).map(e=>e.key_alias))&&void 0!==o?o:[]).includes(c))throw Error("Key alias ".concat(c," already exists for team with ID ").concat(m,", please provide another key alias"));z.Z.info("Making API Call"),ed(!0),"you"===ef&&(e.user_id=es);let u={};try{u=JSON.parse(e.metadata||"{}")}catch(e){console.error("Error parsing metadata:",e)}if("service_account"===ef&&(u.service_account_id=e.key_alias),eC.length>0&&(u={...u,logging:eC.filter(e=>e.callback_name)}),eB.length>0){let e=(0,F.Z3)(eB);u={...u,litellm_disabled_callbacks:e}}if(eY&&(e.auto_rotate=!0,e.rotation_interval=eQ),e.duration&&""!==e.duration.trim()||(e.duration=null),e.metadata=JSON.stringify(u),e.allowed_vector_store_ids&&e.allowed_vector_store_ids.length>0&&(e.object_permission={vector_stores:e.allowed_vector_store_ids},delete e.allowed_vector_store_ids),e.allowed_mcp_servers_and_groups&&((null===(t=e.allowed_mcp_servers_and_groups.servers)||void 0===t?void 0:t.length)>0||(null===(l=e.allowed_mcp_servers_and_groups.accessGroups)||void 0===l?void 0:l.length)>0)){e.object_permission||(e.object_permission={});let{servers:t,accessGroups:l}=e.allowed_mcp_servers_and_groups;t&&t.length>0&&(e.object_permission.mcp_servers=t),l&&l.length>0&&(e.object_permission.mcp_access_groups=l),delete e.allowed_mcp_servers_and_groups}let h=e.mcp_tool_permissions||{};if(Object.keys(h).length>0&&(e.object_permission||(e.object_permission={}),e.object_permission.mcp_tool_permissions=h),delete e.mcp_tool_permissions,e.allowed_mcp_access_groups&&e.allowed_mcp_access_groups.length>0&&(e.object_permission||(e.object_permission={}),e.object_permission.mcp_access_groups=e.allowed_mcp_access_groups,delete e.allowed_mcp_access_groups),e.allowed_agents_and_groups&&((null===(s=e.allowed_agents_and_groups.agents)||void 0===s?void 0:s.length)>0||(null===(r=e.allowed_agents_and_groups.accessGroups)||void 0===r?void 0:r.length)>0)){e.object_permission||(e.object_permission={});let{agents:t,accessGroups:l}=e.allowed_agents_and_groups;t&&t.length>0&&(e.object_permission.agents=t),l&&l.length>0&&(e.object_permission.agent_access_groups=l),delete e.allowed_agents_and_groups}Object.keys(eW).length>0&&(e.aliases=JSON.stringify(eW)),(null==e0?void 0:e0.router_settings)&&Object.values(e0.router_settings).some(e=>null!=e&&""!==e)&&(e.router_settings=e0.router_settings),d="service_account"===ef?await (0,J.keyCreateServiceAccountCall)(el,e):await (0,J.keyCreateCall)(el,es,e),console.log("key create Response:",d),et(d),ei.invalidateQueries({queryKey:a.Km.lists()}),em(d.key),eh(d.soft_budget),z.Z.success("Virtual Key Created"),en.resetFields(),localStorage.removeItem("userData"+es)}catch(t){console.log("error in create key:",t);let e=Y(t);z.Z.fromBackend(e)}};(0,C.useEffect)(()=>{if(es&&ea&&el){var e;X(es,ea,el,null!==(e=null==eT?void 0:eT.team_id)&&void 0!==e?e:null).then(e=>{var t;ey(Array.from(new Set([...null!==(t=null==eT?void 0:eT.models)&&void 0!==t?t:[],...e])))})}en.setFieldValue("models",[])},[eT,el,es,ea]);let e9=async e=>{if(!e){eO([]);return}eK(!0);try{let t=new URLSearchParams;if(t.append("user_email",e),null==el)return;let l=(await (0,J.userFilterUICall)(el,t)).map(e=>({label:"".concat(e.user_email," (").concat(e.user_id,")"),value:e.user_id,user:e}));eO(l)}catch(e){console.error("Error fetching users:",e),z.Z.fromBackend("Failed to search for users")}finally{eK(!1)}},e8=(0,C.useCallback)(Z()(e=>e9(e),300),[el]),te=(e,t)=>{let l=t.user;en.setFieldsValue({user_id:l.user_id})};return(0,s.jsxs)("div",{children:[ea&&T.LQ.includes(ea)&&(0,s.jsx)(u.Z,{className:"mx-auto",onClick:()=>ed(!0),children:"+ Create New Key"}),(0,s.jsx)(b.Z,{open:eo,width:1e3,footer:null,onOk:e5,onCancel:e3,children:(0,s.jsxs)(j.Z,{form:en,onFinish:e6,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",children:[(0,s.jsxs)("div",{className:"mb-8",children:[(0,s.jsx)(y.Z,{className:"mb-4",children:"Key Ownership"}),(0,s.jsx)(j.Z.Item,{label:(0,s.jsxs)("span",{children:["Owned By"," ",(0,s.jsx)(v.Z,{title:"Select who will own this Virtual Key",children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),className:"mb-4",children:(0,s.jsxs)(_.ZP.Group,{onChange:e=>ej(e.target.value),value:ef,children:[(0,s.jsx)(_.ZP,{value:"you",children:"You"}),(0,s.jsx)(_.ZP,{value:"service_account",children:"Service Account"}),"Admin"===ea&&(0,s.jsx)(_.ZP,{value:"another_user",children:"Another User"})]})}),"another_user"===ef&&(0,s.jsx)(j.Z.Item,{label:(0,s.jsxs)("span",{children:["User ID"," ",(0,s.jsx)(v.Z,{title:"The user who will own this key and be responsible for its usage",children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"user_id",className:"mt-4",rules:[{required:"another_user"===ef,message:"Please input the user ID of the user you are assigning the key to"}],children:(0,s.jsxs)("div",{children:[(0,s.jsxs)("div",{style:{display:"flex",marginBottom:"8px"},children:[(0,s.jsx)(f.default,{showSearch:!0,placeholder:"Type email to search for users",filterOption:!1,onSearch:e=>{e8(e)},onSelect:(e,t)=>te(e,t),options:eR,loading:eD,allowClear:!0,style:{width:"100%"},notFoundContent:eD?"Searching...":"No users found"}),(0,s.jsx)(N.ZP,{onClick:()=>eP(!0),style:{marginLeft:"8px"},children:"Create User"})]}),(0,s.jsx)("div",{className:"text-xs text-gray-500",children:"Search by email to find users"})]})}),(0,s.jsx)(j.Z.Item,{label:(0,s.jsxs)("span",{children:["Team"," ",(0,s.jsx)(v.Z,{title:"The team this key belongs to, which determines available models and budget limits",children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"team_id",initialValue:t?t.team_id:null,className:"mt-4",rules:[{required:"service_account"===ef,message:"Please select a team for the service account"}],help:"service_account"===ef?"required":"",children:(0,s.jsx)(K.Z,{teams:l,onChange:e=>{eL((null==l?void 0:l.find(t=>t.team_id===e))||null)}})})]}),e7&&(0,s.jsx)("div",{className:"mb-8 p-4 bg-blue-50 border border-blue-200 rounded-md",children:(0,s.jsx)(x.Z,{className:"text-blue-800 text-sm",children:"Please select a team to continue configuring your Virtual Key. If you do not see any teams, please contact your Proxy Admin to either provide you with access to models or to add you to a team."})}),!e7&&(0,s.jsxs)("div",{className:"mb-8",children:[(0,s.jsx)(y.Z,{className:"mb-4",children:"Key Details"}),(0,s.jsx)(j.Z.Item,{label:(0,s.jsxs)("span",{children:["you"===ef||"another_user"===ef?"Key Name":"Service Account ID"," ",(0,s.jsx)(v.Z,{title:"you"===ef||"another_user"===ef?"A descriptive name to identify this key":"Unique identifier for this service account",children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"key_alias",rules:[{required:!0,message:"Please input a ".concat("you"===ef?"key name":"service account ID")}],help:"required",children:(0,s.jsx)(p.Z,{placeholder:""})}),(0,s.jsx)(j.Z.Item,{label:(0,s.jsxs)("span",{children:["Models"," ",(0,s.jsx)(v.Z,{title:"Select which models this key can access. Choose 'All Team Models' to grant access to all models available to the team",children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"models",rules:"management"===ez||"read_only"===ez?[]:[{required:!0,message:"Please select a model"}],help:"management"===ez||"read_only"===ez?"Models field is disabled for this key type":"required",className:"mt-4",children:(0,s.jsxs)(f.default,{mode:"multiple",placeholder:"Select models",style:{width:"100%"},disabled:"management"===ez||"read_only"===ez,onChange:e=>{e.includes("all-team-models")&&en.setFieldsValue({models:["all-team-models"]})},children:[(0,s.jsx)($,{value:"all-team-models",children:"All Team Models"},"all-team-models"),ep.map(e=>(0,s.jsx)($,{value:e,children:(0,q.W0)(e)},e))]})}),(0,s.jsx)(j.Z.Item,{label:(0,s.jsxs)("span",{children:["Key Type"," ",(0,s.jsx)(v.Z,{title:"Select the type of key to determine what routes and operations this key can access",children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"key_type",initialValue:"llm_api",className:"mt-4",children:(0,s.jsxs)(f.default,{defaultValue:"llm_api",placeholder:"Select key type",style:{width:"100%"},optionLabelProp:"label",onChange:e=>{eJ(e),("management"===e||"read_only"===e)&&en.setFieldsValue({models:[]})},children:[(0,s.jsx)($,{value:"default",label:"Default",children:(0,s.jsxs)("div",{style:{padding:"4px 0"},children:[(0,s.jsx)("div",{style:{fontWeight:500},children:"Default"}),(0,s.jsx)("div",{style:{fontSize:"11px",color:"#6b7280",marginTop:"2px"},children:"Can call LLM API + Management routes"})]})}),(0,s.jsx)($,{value:"llm_api",label:"LLM API",children:(0,s.jsxs)("div",{style:{padding:"4px 0"},children:[(0,s.jsx)("div",{style:{fontWeight:500},children:"LLM API"}),(0,s.jsx)("div",{style:{fontSize:"11px",color:"#6b7280",marginTop:"2px"},children:"Can call only LLM API routes (chat/completions, embeddings, etc.)"})]})}),(0,s.jsx)($,{value:"management",label:"Management",children:(0,s.jsxs)("div",{style:{padding:"4px 0"},children:[(0,s.jsx)("div",{style:{fontWeight:500},children:"Management"}),(0,s.jsx)("div",{style:{fontSize:"11px",color:"#6b7280",marginTop:"2px"},children:"Can call only management routes (user/team/key management)"})]})})]})})]}),!e7&&(0,s.jsx)("div",{className:"mb-8",children:(0,s.jsxs)(d.Z,{className:"mt-4 mb-4",children:[(0,s.jsx)(m.Z,{children:(0,s.jsx)(y.Z,{className:"m-0",children:"Optional Settings"})}),(0,s.jsxs)(c.Z,{children:[(0,s.jsx)(j.Z.Item,{className:"mt-4",label:(0,s.jsxs)("span",{children:["Max Budget (USD)"," ",(0,s.jsx)(v.Z,{title:"Maximum amount in USD this key can spend. When reached, the key will be blocked from making further requests",children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"max_budget",help:"Budget cannot exceed team max budget: $".concat((null==t?void 0:t.max_budget)!==null&&(null==t?void 0:t.max_budget)!==void 0?null==t?void 0:t.max_budget:"unlimited"),rules:[{validator:async(e,l)=>{if(l&&t&&null!==t.max_budget&&l>t.max_budget)throw Error("Budget cannot exceed team max budget: $".concat((0,i.pw)(t.max_budget,4)))}}],children:(0,s.jsx)(W.Z,{step:.01,precision:2,width:200})}),(0,s.jsx)(j.Z.Item,{className:"mt-4",label:(0,s.jsxs)("span",{children:["Reset Budget"," ",(0,s.jsx)(v.Z,{title:"How often the budget should reset. For example, setting 'daily' will reset the budget every 24 hours",children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"budget_duration",help:"Team Reset Budget: ".concat((null==t?void 0:t.budget_duration)!==null&&(null==t?void 0:t.budget_duration)!==void 0?null==t?void 0:t.budget_duration:"None"),children:(0,s.jsx)(P.Z,{onChange:e=>en.setFieldValue("budget_duration",e)})}),(0,s.jsx)(j.Z.Item,{className:"mt-4",label:(0,s.jsxs)("span",{children:["Tokens per minute Limit (TPM)"," ",(0,s.jsx)(v.Z,{title:"Maximum number of tokens this key can process per minute. Helps control usage and costs",children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"tpm_limit",help:"TPM cannot exceed team TPM limit: ".concat((null==t?void 0:t.tpm_limit)!==null&&(null==t?void 0:t.tpm_limit)!==void 0?null==t?void 0:t.tpm_limit:"unlimited"),rules:[{validator:async(e,l)=>{if(l&&t&&null!==t.tpm_limit&&l>t.tpm_limit)throw Error("TPM limit cannot exceed team TPM limit: ".concat(t.tpm_limit))}}],children:(0,s.jsx)(W.Z,{step:1,width:400})}),(0,s.jsx)(O.Z,{type:"tpm",name:"tpm_limit_type",className:"mt-4",initialValue:null,form:en,showDetailedDescriptions:!0}),(0,s.jsx)(j.Z.Item,{className:"mt-4",label:(0,s.jsxs)("span",{children:["Requests per minute Limit (RPM)"," ",(0,s.jsx)(v.Z,{title:"Maximum number of API requests this key can make per minute. Helps prevent abuse and manage load",children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"rpm_limit",help:"RPM cannot exceed team RPM limit: ".concat((null==t?void 0:t.rpm_limit)!==null&&(null==t?void 0:t.rpm_limit)!==void 0?null==t?void 0:t.rpm_limit:"unlimited"),rules:[{validator:async(e,l)=>{if(l&&t&&null!==t.rpm_limit&&l>t.rpm_limit)throw Error("RPM limit cannot exceed team RPM limit: ".concat(t.rpm_limit))}}],children:(0,s.jsx)(W.Z,{step:1,width:400})}),(0,s.jsx)(O.Z,{type:"rpm",name:"rpm_limit_type",className:"mt-4",initialValue:null,form:en,showDetailedDescriptions:!0}),(0,s.jsx)(j.Z.Item,{label:(0,s.jsxs)("span",{children:["Guardrails"," ",(0,s.jsx)(v.Z,{title:"Apply safety guardrails to this key to filter content or enforce policies",children:(0,s.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/guardrails/quick_start",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})})]}),name:"guardrails",className:"mt-4",help:er?"Select existing guardrails or enter new ones":"Premium feature - Upgrade to set guardrails by key",children:(0,s.jsx)(f.default,{mode:"tags",style:{width:"100%"},disabled:!er,placeholder:er?"Select or enter guardrails":"Premium feature - Upgrade to set guardrails by key",options:e_.map(e=>({value:e,label:e}))})}),(0,s.jsx)(j.Z.Item,{label:(0,s.jsxs)("span",{children:["Disable Global Guardrails"," ",(0,s.jsx)(v.Z,{title:"When enabled, this key will bypass any guardrails configured to run on every request (global guardrails)",children:(0,s.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/guardrails/quick_start",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})})]}),name:"disable_global_guardrails",className:"mt-4",valuePropName:"checked",help:er?"Bypass global guardrails for this key":"Premium feature - Upgrade to disable global guardrails by key",children:(0,s.jsx)(w.Z,{disabled:!er,checkedChildren:"Yes",unCheckedChildren:"No"})}),(0,s.jsx)(j.Z.Item,{label:(0,s.jsxs)("span",{children:["Policies"," ",(0,s.jsx)(v.Z,{title:"Apply policies to this key to control guardrails and other settings",children:(0,s.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/guardrails/guardrail_policies",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})})]}),name:"policies",className:"mt-4",help:er?"Select existing policies or enter new ones":"Premium feature - Upgrade to set policies by key",children:(0,s.jsx)(f.default,{mode:"tags",style:{width:"100%"},disabled:!er,placeholder:er?"Select or enter policies":"Premium feature - Upgrade to set policies by key",options:ew.map(e=>({value:e,label:e}))})}),(0,s.jsx)(j.Z.Item,{label:(0,s.jsxs)("span",{children:["Prompts"," ",(0,s.jsx)(v.Z,{title:"Allow this key to use specific prompt templates",children:(0,s.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/prompt_management",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})})]}),name:"prompts",className:"mt-4",help:er?"Select existing prompts or enter new ones":"Premium feature - Upgrade to set prompts by key",children:(0,s.jsx)(f.default,{mode:"tags",style:{width:"100%"},disabled:!er,placeholder:er?"Select or enter prompts":"Premium feature - Upgrade to set prompts by key",options:eS.map(e=>({value:e,label:e}))})}),(0,s.jsx)(j.Z.Item,{label:(0,s.jsxs)("span",{children:["Allowed Pass Through Routes"," ",(0,s.jsx)(v.Z,{title:"Allow this key to use specific pass through routes",children:(0,s.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/pass_through",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})})]}),name:"allowed_passthrough_routes",className:"mt-4",help:er?"Select existing pass through routes or enter new ones":"Premium feature - Upgrade to set pass through routes by key",children:(0,s.jsx)(V.Z,{onChange:e=>en.setFieldValue("allowed_passthrough_routes",e),value:en.getFieldValue("allowed_passthrough_routes"),accessToken:el,placeholder:er?"Select or enter pass through routes":"Premium feature - Upgrade to set pass through routes by key",disabled:!er,teamId:eT?eT.team_id:null})}),(0,s.jsx)(j.Z.Item,{label:(0,s.jsxs)("span",{children:["Allowed Vector Stores"," ",(0,s.jsx)(v.Z,{title:"Select which vector stores this key can access. If none selected, the key will have access to all available vector stores",children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"allowed_vector_store_ids",className:"mt-4",help:"Select vector stores this key can access. Leave empty for access to all vector stores",children:(0,s.jsx)(H.Z,{onChange:e=>en.setFieldValue("allowed_vector_store_ids",e),value:en.getFieldValue("allowed_vector_store_ids"),accessToken:el,placeholder:"Select vector stores (optional)"})}),(0,s.jsx)(j.Z.Item,{label:(0,s.jsxs)("span",{children:["Metadata"," ",(0,s.jsx)(v.Z,{title:"JSON object with additional information about this key. Used for tracking or custom logic",children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"metadata",className:"mt-4",children:(0,s.jsx)(k.default.TextArea,{rows:4,placeholder:"Enter metadata as JSON"})}),(0,s.jsx)(j.Z.Item,{label:(0,s.jsxs)("span",{children:["Tags"," ",(0,s.jsx)(v.Z,{title:"Tags for tracking spend and/or doing tag-based routing. Used for analytics and filtering",children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"tags",className:"mt-4",help:"Tags for tracking spend and/or doing tag-based routing.",children:(0,s.jsx)(f.default,{mode:"tags",style:{width:"100%"},placeholder:"Enter tags",tokenSeparators:[","],options:eb})}),(0,s.jsxs)(d.Z,{className:"mt-4 mb-4",children:[(0,s.jsx)(m.Z,{children:(0,s.jsx)("b",{children:"MCP Settings"})}),(0,s.jsxs)(c.Z,{children:[(0,s.jsx)(j.Z.Item,{label:(0,s.jsxs)("span",{children:["Allowed MCP Servers"," ",(0,s.jsx)(v.Z,{title:"Select which MCP servers or access groups this key can access",children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"allowed_mcp_servers_and_groups",help:"Select MCP servers or access groups this key can access",children:(0,s.jsx)(B.Z,{onChange:e=>en.setFieldValue("allowed_mcp_servers_and_groups",e),value:en.getFieldValue("allowed_mcp_servers_and_groups"),accessToken:el,placeholder:"Select MCP servers or access groups (optional)"})}),(0,s.jsx)(j.Z.Item,{name:"mcp_tool_permissions",initialValue:{},hidden:!0,children:(0,s.jsx)(k.default,{type:"hidden"})}),(0,s.jsx)(j.Z.Item,{noStyle:!0,shouldUpdate:(e,t)=>e.allowed_mcp_servers_and_groups!==t.allowed_mcp_servers_and_groups||e.mcp_tool_permissions!==t.mcp_tool_permissions,children:()=>{var e;return(0,s.jsx)("div",{className:"mt-6",children:(0,s.jsx)(G.Z,{accessToken:el,selectedServers:(null===(e=en.getFieldValue("allowed_mcp_servers_and_groups"))||void 0===e?void 0:e.servers)||[],toolPermissions:en.getFieldValue("mcp_tool_permissions")||{},onChange:e=>en.setFieldsValue({mcp_tool_permissions:e})})})}})]})]}),(0,s.jsxs)(d.Z,{className:"mt-4 mb-4",children:[(0,s.jsx)(m.Z,{children:(0,s.jsx)("b",{children:"Agent Settings"})}),(0,s.jsx)(c.Z,{children:(0,s.jsx)(j.Z.Item,{label:(0,s.jsxs)("span",{children:["Allowed Agents"," ",(0,s.jsx)(v.Z,{title:"Select which agents or access groups this key can access",children:(0,s.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"allowed_agents_and_groups",help:"Select agents or access groups this key can access",children:(0,s.jsx)(L.Z,{onChange:e=>en.setFieldValue("allowed_agents_and_groups",e),value:en.getFieldValue("allowed_agents_and_groups"),accessToken:el,placeholder:"Select agents or access groups (optional)"})})})]}),er?(0,s.jsxs)(d.Z,{className:"mt-4 mb-4",children:[(0,s.jsx)(m.Z,{children:(0,s.jsx)("b",{children:"Logging Settings"})}),(0,s.jsx)(c.Z,{children:(0,s.jsx)("div",{className:"mt-4",children:(0,s.jsx)(R.Z,{value:eC,onChange:eM,premiumUser:!0,disabledCallbacks:eB,onDisabledCallbacksChange:eG})})})]}):(0,s.jsx)(v.Z,{title:(0,s.jsxs)("span",{children:["Key-level logging settings is an enterprise feature, get in touch -",(0,s.jsx)("a",{href:"https://www.litellm.ai/enterprise",target:"_blank",children:"https://www.litellm.ai/enterprise"})]}),placement:"top",children:(0,s.jsxs)("div",{style:{position:"relative"},children:[(0,s.jsx)("div",{style:{opacity:.5},children:(0,s.jsxs)(d.Z,{className:"mt-4 mb-4",children:[(0,s.jsx)(m.Z,{children:(0,s.jsx)("b",{children:"Logging Settings"})}),(0,s.jsx)(c.Z,{children:(0,s.jsx)("div",{className:"mt-4",children:(0,s.jsx)(R.Z,{value:eC,onChange:eM,premiumUser:!1,disabledCallbacks:eB,onDisabledCallbacksChange:eG})})})]})}),(0,s.jsx)("div",{style:{position:"absolute",inset:0,cursor:"not-allowed"}})]})}),(0,s.jsxs)(d.Z,{className:"mt-4 mb-4",children:[(0,s.jsx)(m.Z,{children:(0,s.jsx)("b",{children:"Router Settings"})}),(0,s.jsx)(c.Z,{children:(0,s.jsx)("div",{className:"mt-4 w-full",children:(0,s.jsx)(D.Z,{accessToken:el||"",value:e0||void 0,onChange:e4,modelData:eg.length>0?{data:eg.map(e=>({model_name:e}))}:void 0},e1)})})]},"router-settings-accordion-".concat(e1)),(0,s.jsxs)(d.Z,{className:"mt-4 mb-4",children:[(0,s.jsx)(m.Z,{children:(0,s.jsx)("b",{children:"Model Aliases"})}),(0,s.jsx)(c.Z,{children:(0,s.jsxs)("div",{className:"mt-4",children:[(0,s.jsx)(x.Z,{className:"text-sm text-gray-600 mb-4",children:"Create custom aliases for models that can be used in API calls. This allows you to create shortcuts for specific models."}),(0,s.jsx)(I.Z,{accessToken:el,initialModelAliases:eW,onAliasUpdate:eH,showExampleConfig:!1})]})})]}),(0,s.jsxs)(d.Z,{className:"mt-4 mb-4",children:[(0,s.jsx)(m.Z,{children:(0,s.jsx)("b",{children:"Key Lifecycle"})}),(0,s.jsx)(c.Z,{children:(0,s.jsx)("div",{className:"mt-4",children:(0,s.jsx)(E.Z,{form:en,autoRotationEnabled:eY,onAutoRotationChange:e$,rotationInterval:eQ,onRotationIntervalChange:eX,isCreateMode:!0})})}),(0,s.jsx)(j.Z.Item,{name:"duration",hidden:!0,initialValue:null,children:(0,s.jsx)(k.default,{})})]}),(0,s.jsxs)(d.Z,{className:"mt-4 mb-4",children:[(0,s.jsx)(m.Z,{children:(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)("b",{children:"Advanced Settings"}),(0,s.jsx)(v.Z,{title:(0,s.jsxs)("span",{children:["Learn more about advanced settings in our"," ",(0,s.jsx)("a",{href:J.proxyBaseUrl?"".concat(J.proxyBaseUrl,"/#/key%20management/generate_key_fn_key_generate_post"):"/#/key%20management/generate_key_fn_key_generate_post",target:"_blank",rel:"noopener noreferrer",className:"text-blue-400 hover:text-blue-300",children:"documentation"})]}),children:(0,s.jsx)(n.Z,{className:"text-gray-400 hover:text-gray-300 cursor-help"})})]})}),(0,s.jsx)(c.Z,{children:(0,s.jsx)(A.Z,{schemaComponent:"GenerateKeyRequest",form:en,excludedFields:["key_alias","team_id","models","duration","metadata","tags","guardrails","max_budget","budget_duration","tpm_limit","rpm_limit"]})})]})]})]})}),(0,s.jsx)("div",{style:{textAlign:"right",marginTop:"10px"},children:(0,s.jsx)(N.ZP,{htmlType:"submit",disabled:e7,style:{opacity:e7?.5:1},children:"Create Key"})})]})}),eF&&(0,s.jsx)(b.Z,{title:"Create New User",visible:eF,onCancel:()=>eP(!1),footer:null,width:800,children:(0,s.jsx)(U.Z,{userID:es,accessToken:el,teams:l,possibleUIRoles:eI,onUserCreated:e=>{eE(e),en.setFieldsValue({user_id:e}),eP(!1)},isEmbedded:!0})}),ec&&(0,s.jsx)(b.Z,{visible:eo,onOk:e5,onCancel:e3,footer:null,children:(0,s.jsxs)(g.Z,{numItems:1,className:"gap-2 w-full",children:[(0,s.jsx)(y.Z,{children:"Save your Key"}),(0,s.jsx)(h.Z,{numColSpan:1,children:(0,s.jsxs)("p",{children:["Please save this secret key somewhere safe and accessible. For security reasons,"," ",(0,s.jsx)("b",{children:"you will not be able to view it again"})," through your LiteLLM account. If you lose this secret key, you will need to generate a new one."]})}),(0,s.jsx)(h.Z,{numColSpan:1,children:null!=ec?(0,s.jsxs)("div",{children:[(0,s.jsx)(x.Z,{className:"mt-3",children:"Virtual Key:"}),(0,s.jsx)("div",{style:{background:"#f8f8f8",padding:"10px",borderRadius:"5px",marginBottom:"10px"},children:(0,s.jsx)("pre",{style:{wordWrap:"break-word",whiteSpace:"normal"},children:ec})}),(0,s.jsx)(M.CopyToClipboard,{text:ec,onCopy:()=>{z.Z.success("Virtual Key copied to clipboard")},children:(0,s.jsx)(u.Z,{className:"mt-3",children:"Copy Virtual Key"})})]}):(0,s.jsx)(x.Z,{children:"Key being created, this might take 30s"})})]})})]})}},56334:function(e,t,l){l.d(t,{Z:function(){return u}});var s=l(57437);l(2265);var a=l(31283);let r={ttl:3600,lowest_latency_buffer:0};var i=e=>{let{routingStrategyArgs:t}=e,l={ttl:"Sliding window to look back over when calculating the average latency of a deployment. Default - 1 hour (in seconds).",lowest_latency_buffer:"Shuffle between deployments within this % of the lowest latency. Default - 0 (i.e. always pick lowest latency)."};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)("div",{className:"space-y-6",children:[(0,s.jsxs)("div",{className:"max-w-3xl",children:[(0,s.jsx)("h3",{className:"text-sm font-medium text-gray-900",children:"Latency-Based Configuration"}),(0,s.jsx)("p",{className:"text-xs text-gray-500 mt-1",children:"Fine-tune latency-based routing behavior"})]}),(0,s.jsx)("div",{className:"grid grid-cols-1 gap-6 lg:grid-cols-2 xl:grid-cols-3",children:Object.entries(t||r).map(e=>{let[t,r]=e;return(0,s.jsx)("div",{className:"space-y-2",children:(0,s.jsxs)("label",{className:"block",children:[(0,s.jsx)("span",{className:"text-xs font-medium text-gray-700 uppercase tracking-wide",children:t.replace(/_/g," ")}),(0,s.jsx)("p",{className:"text-xs text-gray-500 mt-0.5 mb-2",children:l[t]||""}),(0,s.jsx)(a.o,{name:t,defaultValue:"object"==typeof r?JSON.stringify(r,null,2):null==r?void 0:r.toString(),className:"font-mono text-sm w-full"})]})},t)})})]}),(0,s.jsx)("div",{className:"border-t border-gray-200"})]})},n=e=>{let{routerSettings:t,routerFieldsMetadata:l}=e;return(0,s.jsxs)("div",{className:"space-y-6",children:[(0,s.jsxs)("div",{className:"max-w-3xl",children:[(0,s.jsx)("h3",{className:"text-sm font-medium text-gray-900",children:"Reliability & Retries"}),(0,s.jsx)("p",{className:"text-xs text-gray-500 mt-1",children:"Configure retry logic and failure handling"})]}),(0,s.jsx)("div",{className:"grid grid-cols-1 gap-6 lg:grid-cols-2 xl:grid-cols-3",children:Object.entries(t).filter(e=>{let[t,l]=e;return"fallbacks"!=t&&"context_window_fallbacks"!=t&&"routing_strategy_args"!=t&&"routing_strategy"!=t&&"enable_tag_filtering"!=t}).map(e=>{var t,r;let[i,n]=e;return(0,s.jsx)("div",{className:"space-y-2",children:(0,s.jsxs)("label",{className:"block",children:[(0,s.jsx)("span",{className:"text-xs font-medium text-gray-700 uppercase tracking-wide",children:(null===(t=l[i])||void 0===t?void 0:t.ui_field_name)||i}),(0,s.jsx)("p",{className:"text-xs text-gray-500 mt-0.5 mb-2",children:(null===(r=l[i])||void 0===r?void 0:r.field_description)||""}),(0,s.jsx)(a.o,{name:i,defaultValue:null==n||"null"===n?"":"object"==typeof n?JSON.stringify(n,null,2):(null==n?void 0:n.toString())||"",placeholder:"—",className:"font-mono text-sm w-full"})]})},i)})})]})},o=l(37592),d=e=>{var t,l;let{selectedStrategy:a,availableStrategies:r,routingStrategyDescriptions:i,routerFieldsMetadata:n,onStrategyChange:d}=e;return(0,s.jsxs)("div",{className:"space-y-2 max-w-3xl",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)("label",{className:"text-xs font-medium text-gray-700 uppercase tracking-wide",children:(null===(t=n.routing_strategy)||void 0===t?void 0:t.ui_field_name)||"Routing Strategy"}),(0,s.jsx)("p",{className:"text-xs text-gray-500 mt-0.5 mb-2",children:(null===(l=n.routing_strategy)||void 0===l?void 0:l.field_description)||""})]}),(0,s.jsx)("div",{className:"routing-strategy-select max-w-3xl",children:(0,s.jsx)(o.default,{value:a,onChange:d,style:{width:"100%"},size:"large",children:r.map(e=>(0,s.jsx)(o.default.Option,{value:e,label:e,children:(0,s.jsxs)("div",{className:"flex flex-col gap-0.5 py-1",children:[(0,s.jsx)("span",{className:"font-mono text-sm font-medium",children:e}),i[e]&&(0,s.jsx)("span",{className:"text-xs text-gray-500 font-normal",children:i[e]})]})},e))})})]})},c=l(59341),m=e=>{var t,l,a;let{enabled:r,routerFieldsMetadata:i,onToggle:n}=e;return(0,s.jsx)("div",{className:"space-y-3 max-w-3xl",children:(0,s.jsxs)("div",{className:"flex items-start justify-between",children:[(0,s.jsxs)("div",{className:"flex-1",children:[(0,s.jsx)("label",{className:"text-xs font-medium text-gray-700 uppercase tracking-wide",children:(null===(t=i.enable_tag_filtering)||void 0===t?void 0:t.ui_field_name)||"Enable Tag Filtering"}),(0,s.jsxs)("p",{className:"text-xs text-gray-500 mt-0.5",children:[(null===(l=i.enable_tag_filtering)||void 0===l?void 0:l.field_description)||"",(null===(a=i.enable_tag_filtering)||void 0===a?void 0:a.link)&&(0,s.jsxs)(s.Fragment,{children:[" ",(0,s.jsx)("a",{href:i.enable_tag_filtering.link,target:"_blank",rel:"noopener noreferrer",className:"text-blue-600 hover:text-blue-800 underline",children:"Learn more"})]})]})]}),(0,s.jsx)(c.Z,{checked:r,onChange:n,className:"ml-4"})]})})},u=e=>{let{value:t,onChange:l,routerFieldsMetadata:a,availableRoutingStrategies:r,routingStrategyDescriptions:o}=e;return(0,s.jsxs)("div",{className:"w-full space-y-8 py-2",children:[(0,s.jsxs)("div",{className:"space-y-6",children:[(0,s.jsxs)("div",{className:"max-w-3xl",children:[(0,s.jsx)("h3",{className:"text-sm font-medium text-gray-900",children:"Routing Settings"}),(0,s.jsx)("p",{className:"text-xs text-gray-500 mt-1",children:"Configure how requests are routed to deployments"})]}),r.length>0&&(0,s.jsx)(d,{selectedStrategy:t.selectedStrategy||t.routerSettings.routing_strategy||null,availableStrategies:r,routingStrategyDescriptions:o,routerFieldsMetadata:a,onStrategyChange:e=>{l({...t,selectedStrategy:e})}}),(0,s.jsx)(m,{enabled:t.enableTagFiltering,routerFieldsMetadata:a,onToggle:e=>{l({...t,enableTagFiltering:e})}})]}),(0,s.jsx)("div",{className:"border-t border-gray-200"}),"latency-based-routing"===t.selectedStrategy&&(0,s.jsx)(i,{routingStrategyArgs:t.routerSettings.routing_strategy_args}),(0,s.jsx)(n,{routerSettings:t.routerSettings,routerFieldsMetadata:a})]})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1098-c3e95c9684ff5e95.js b/litellm/proxy/_experimental/out/_next/static/chunks/1098-c3e95c9684ff5e95.js deleted file mode 100644 index 6f31d1f0843..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/1098-c3e95c9684ff5e95.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1098],{30280:function(e,s,t){t.d(s,{EX:function(){return c},Km:function(){return o},Tv:function(){return u}});var a=t(11713),l=t(45345),r=t(90246),i=t(19250),n=t(80443);let o=(0,r.n)("keys"),d=async function(e,s,t){let a=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{};try{let l=(0,i.getProxyBaseUrl)(),r=new URLSearchParams(Object.entries({team_id:a.teamID,organization_id:a.organizationID,key_alias:a.selectedKeyAlias,key_hash:a.keyHash,user_id:a.userID,page:s,size:t,sort_by:a.sortBy,sort_order:a.sortOrder,expand:a.expand,status:a.status,return_full_object:"true",include_team_keys:"true",include_created_by_keys:"true"}).filter(e=>{let[,s]=e;return null!=s}).map(e=>{let[s,t]=e;return[s,String(t)]})),n="".concat(l?"".concat(l,"/key/list"):"/key/list","?").concat(r),o=await fetch(n,{method:"GET",headers:{[(0,i.getGlobalLitellmHeaderName)()]:"Bearer ".concat(e),"Content-Type":"application/json"}});if(!o.ok){let e=await o.json(),s=(0,i.deriveErrorMessage)(e);throw(0,i.handleError)(s),Error(s)}let d=await o.json();return console.log("/key/list API Response:",d),d}catch(e){throw console.error("Failed to list keys:",e),e}},c=(e,s)=>{let{accessToken:t}=(0,n.Z)();return(0,a.a)({queryKey:o.list({page:e,limit:s}),queryFn:async()=>await d(t,e,s),enabled:!!t,staleTime:3e4,placeholderData:l.Wk})},m=(0,r.n)("deletedKeys"),u=function(e,s){let t=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},{accessToken:r}=(0,n.Z)();return(0,a.a)({queryKey:m.list({page:e,limit:s,...t}),queryFn:async()=>await d(r,e,s,{...t,status:"deleted"}),enabled:!!r,staleTime:3e4,placeholderData:l.Wk})}},62099:function(e,s,t){var a=t(57437),l=t(2265),r=t(37592),i=t(99981),n=t(23496),o=t(63709),d=t(15424),c=t(31283);let{Option:m}=r.default;s.Z=e=>{var s;let{form:t,autoRotationEnabled:u,onAutoRotationChange:h,rotationInterval:p,onRotationIntervalChange:x}=e,g=p&&!["7d","30d","90d","180d","365d"].includes(p),[y,j]=(0,l.useState)(g),[f,b]=(0,l.useState)(g?p:""),[v,_]=(0,l.useState)((null==t?void 0:null===(s=t.getFieldValue)||void 0===s?void 0:s.call(t,"duration"))||"");return(0,a.jsxs)("div",{className:"space-y-6",children:[(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsx)("span",{className:"text-sm font-medium text-gray-700",children:"Key Expiry Settings"}),(0,a.jsxs)("div",{className:"space-y-2",children:[(0,a.jsxs)("label",{className:"text-sm font-medium text-gray-700 flex items-center space-x-1",children:[(0,a.jsx)("span",{children:"Expire Key"}),(0,a.jsx)(i.Z,{title:"Set when this key should expire. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days). Use -1 to never expire.",children:(0,a.jsx)(d.Z,{className:"text-gray-400 cursor-help text-xs"})})]}),(0,a.jsx)(c.o,{name:"duration",placeholder:"e.g., 30d or -1 to never expire",className:"w-full",value:v,onValueChange:e=>{_(e),t&&"function"==typeof t.setFieldValue?t.setFieldValue("duration",e):t&&"function"==typeof t.setFieldsValue&&t.setFieldsValue({duration:e})}})]})]}),(0,a.jsx)(n.Z,{}),(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsx)("span",{className:"text-sm font-medium text-gray-700",children:"Auto-Rotation Settings"}),(0,a.jsxs)("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-4",children:[(0,a.jsxs)("div",{className:"space-y-2",children:[(0,a.jsxs)("label",{className:"text-sm font-medium text-gray-700 flex items-center space-x-1",children:[(0,a.jsx)("span",{children:"Enable Auto-Rotation"}),(0,a.jsx)(i.Z,{title:"Key will automatically regenerate at the specified interval for enhanced security.",children:(0,a.jsx)(d.Z,{className:"text-gray-400 cursor-help text-xs"})})]}),(0,a.jsx)(o.Z,{checked:u,onChange:h,size:"default",className:u?"":"bg-gray-400"})]}),u&&(0,a.jsxs)("div",{className:"space-y-2",children:[(0,a.jsxs)("label",{className:"text-sm font-medium text-gray-700 flex items-center space-x-1",children:[(0,a.jsx)("span",{children:"Rotation Interval"}),(0,a.jsx)(i.Z,{title:"How often the key should be automatically rotated. Choose the interval that best fits your security requirements.",children:(0,a.jsx)(d.Z,{className:"text-gray-400 cursor-help text-xs"})})]}),(0,a.jsxs)("div",{className:"space-y-2",children:[(0,a.jsxs)(r.default,{value:y?"custom":p,onChange:e=>{"custom"===e?j(!0):(j(!1),b(""),x(e))},className:"w-full",placeholder:"Select interval",children:[(0,a.jsx)(m,{value:"7d",children:"7 days"}),(0,a.jsx)(m,{value:"30d",children:"30 days"}),(0,a.jsx)(m,{value:"90d",children:"90 days"}),(0,a.jsx)(m,{value:"180d",children:"180 days"}),(0,a.jsx)(m,{value:"365d",children:"365 days"}),(0,a.jsx)(m,{value:"custom",children:"Custom interval"})]}),y&&(0,a.jsxs)("div",{className:"space-y-1",children:[(0,a.jsx)(c.o,{value:f,onChange:e=>{let s=e.target.value;b(s),x(s)},placeholder:"e.g., 1s, 5m, 2h, 14d"}),(0,a.jsx)("div",{className:"text-xs text-gray-500",children:"Supported formats: seconds (s), minutes (m), hours (h), days (d)"})]})]})]})]}),u&&(0,a.jsx)("div",{className:"bg-blue-50 p-3 rounded-md text-sm text-blue-700",children:"When rotation occurs, you'll receive a notification with the new key. The old key will be deactivated after a brief grace period."})]})]})}},72885:function(e,s,t){var a=t(57437),l=t(2265),r=t(77355),i=t(93416),n=t(74998),o=t(95704),d=t(76593),c=t(9114);s.Z=e=>{let{accessToken:s,initialModelAliases:t={},onAliasUpdate:m,showExampleConfig:u=!0}=e,[h,p]=(0,l.useState)([]),[x,g]=(0,l.useState)({aliasName:"",targetModel:""}),[y,j]=(0,l.useState)(null);(0,l.useEffect)(()=>{p(Object.entries(t).map((e,s)=>{let[t,a]=e;return{id:"".concat(s,"-").concat(t),aliasName:t,targetModel:a}}))},[t]);let f=e=>{j({...e})},b=()=>{if(!y)return;if(!y.aliasName||!y.targetModel){c.Z.fromBackend("Please provide both alias name and target model");return}if(h.some(e=>e.id!==y.id&&e.aliasName===y.aliasName)){c.Z.fromBackend("An alias with this name already exists");return}let e=h.map(e=>e.id===y.id?y:e);p(e),j(null);let s={};e.forEach(e=>{s[e.aliasName]=e.targetModel}),m&&m(s),c.Z.success("Alias updated successfully")},v=()=>{j(null)},_=e=>{let s=h.filter(s=>s.id!==e);p(s);let t={};s.forEach(e=>{t[e.aliasName]=e.targetModel}),m&&m(t),c.Z.success("Alias deleted successfully")},w=h.reduce((e,s)=>(e[s.aliasName]=s.targetModel,e),{});return(0,a.jsxs)("div",{className:"mt-4",children:[(0,a.jsxs)("div",{className:"mb-6",children:[(0,a.jsx)(o.xv,{className:"text-sm font-medium text-gray-700 mb-2",children:"Add New Alias"}),(0,a.jsxs)("div",{className:"grid grid-cols-3 gap-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)("label",{className:"block text-xs text-gray-500 mb-1",children:"Alias Name"}),(0,a.jsx)("input",{type:"text",value:x.aliasName,onChange:e=>g({...x,aliasName:e.target.value}),placeholder:"e.g., gpt-4o",className:"w-full px-3 py-2 border border-gray-300 rounded-md text-sm"})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)("label",{className:"block text-xs text-gray-500 mb-1",children:"Target Model"}),(0,a.jsx)(d.Z,{accessToken:s,value:x.targetModel,placeholder:"Select target model",onChange:e=>g({...x,targetModel:e}),showLabel:!1})]}),(0,a.jsx)("div",{className:"flex items-end",children:(0,a.jsxs)("button",{onClick:()=>{if(!x.aliasName||!x.targetModel){c.Z.fromBackend("Please provide both alias name and target model");return}if(h.some(e=>e.aliasName===x.aliasName)){c.Z.fromBackend("An alias with this name already exists");return}let e=[...h,{id:"".concat(Date.now(),"-").concat(x.aliasName),aliasName:x.aliasName,targetModel:x.targetModel}];p(e),g({aliasName:"",targetModel:""});let s={};e.forEach(e=>{s[e.aliasName]=e.targetModel}),m&&m(s),c.Z.success("Alias added successfully")},disabled:!x.aliasName||!x.targetModel,className:"flex items-center px-4 py-2 rounded-md text-sm ".concat(x.aliasName&&x.targetModel?"bg-green-600 text-white hover:bg-green-700":"bg-gray-300 text-gray-500 cursor-not-allowed"),children:[(0,a.jsx)(r.Z,{className:"w-4 h-4 mr-1"}),"Add Alias"]})})]})]}),(0,a.jsx)(o.xv,{className:"text-sm font-medium text-gray-700 mb-2",children:"Manage Existing Aliases"}),(0,a.jsx)("div",{className:"rounded-lg custom-border relative mb-6",children:(0,a.jsx)("div",{className:"overflow-x-auto",children:(0,a.jsxs)(o.iA,{className:"[&_td]:py-0.5 [&_th]:py-1",children:[(0,a.jsx)(o.ss,{children:(0,a.jsxs)(o.SC,{children:[(0,a.jsx)(o.xs,{className:"py-1 h-8",children:"Alias Name"}),(0,a.jsx)(o.xs,{className:"py-1 h-8",children:"Target Model"}),(0,a.jsx)(o.xs,{className:"py-1 h-8",children:"Actions"})]})}),(0,a.jsxs)(o.RM,{children:[h.map(e=>(0,a.jsx)(o.SC,{className:"h-8",children:y&&y.id===e.id?(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(o.pj,{className:"py-0.5",children:(0,a.jsx)("input",{type:"text",value:y.aliasName,onChange:e=>j({...y,aliasName:e.target.value}),className:"w-full px-2 py-1 border border-gray-300 rounded-md text-sm"})}),(0,a.jsx)(o.pj,{className:"py-0.5",children:(0,a.jsx)(d.Z,{accessToken:s,value:y.targetModel,onChange:e=>j({...y,targetModel:e}),showLabel:!1,style:{height:"32px"}})}),(0,a.jsx)(o.pj,{className:"py-0.5 whitespace-nowrap",children:(0,a.jsxs)("div",{className:"flex space-x-2",children:[(0,a.jsx)("button",{onClick:b,className:"text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded hover:bg-blue-100",children:"Save"}),(0,a.jsx)("button",{onClick:v,className:"text-xs bg-gray-50 text-gray-600 px-2 py-1 rounded hover:bg-gray-100",children:"Cancel"})]})})]}):(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(o.pj,{className:"py-0.5 text-sm text-gray-900",children:e.aliasName}),(0,a.jsx)(o.pj,{className:"py-0.5 text-sm text-gray-500",children:e.targetModel}),(0,a.jsx)(o.pj,{className:"py-0.5 whitespace-nowrap",children:(0,a.jsxs)("div",{className:"flex space-x-2",children:[(0,a.jsx)("button",{onClick:()=>f(e),className:"text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded hover:bg-blue-100",children:(0,a.jsx)(i.Z,{className:"w-3 h-3"})}),(0,a.jsx)("button",{onClick:()=>_(e.id),className:"text-xs bg-red-50 text-red-600 px-2 py-1 rounded hover:bg-red-100",children:(0,a.jsx)(n.Z,{className:"w-3 h-3"})})]})})]})},e.id)),0===h.length&&(0,a.jsx)(o.SC,{children:(0,a.jsx)(o.pj,{colSpan:3,className:"py-0.5 text-sm text-gray-500 text-center",children:"No aliases added yet. Add a new alias above."})})]})]})})}),u&&(0,a.jsxs)(o.Zb,{children:[(0,a.jsx)(o.Dx,{className:"mb-4",children:"Configuration Example"}),(0,a.jsx)(o.xv,{className:"text-gray-600 mb-4",children:"Here's how your current aliases would look in the config:"}),(0,a.jsx)("div",{className:"bg-gray-100 rounded-lg p-4 font-mono text-sm",children:(0,a.jsxs)("div",{className:"text-gray-700",children:["model_aliases:",0===Object.keys(w).length?(0,a.jsxs)("span",{className:"text-gray-500",children:[(0,a.jsx)("br",{}),"\xa0\xa0# No aliases configured yet"]}):Object.entries(w).map(e=>{let[s,t]=e;return(0,a.jsxs)("span",{children:[(0,a.jsx)("br",{}),'\xa0\xa0"',s,'": "',t,'"']},s)})]})})]})]})}},76593:function(e,s,t){var a=t(57437),l=t(2265),r=t(56522),i=t(37592),n=t(69993),o=t(10703);s.Z=e=>{let{accessToken:s,value:t,placeholder:d="Select a Model",onChange:c,disabled:m=!1,style:u,className:h,showLabel:p=!0,labelText:x="Select Model"}=e,[g,y]=(0,l.useState)(t),[j,f]=(0,l.useState)(!1),[b,v]=(0,l.useState)([]),_=(0,l.useRef)(null);return(0,l.useEffect)(()=>{y(t)},[t]),(0,l.useEffect)(()=>{s&&(async()=>{try{let e=await (0,o.p)(s);console.log("Fetched models for selector:",e),e.length>0&&v(e)}catch(e){console.error("Error fetching model info:",e)}})()},[s]),(0,a.jsxs)("div",{children:[p&&(0,a.jsxs)(r.x,{className:"font-medium block mb-2 text-gray-700 flex items-center",children:[(0,a.jsx)(n.Z,{className:"mr-2"})," ",x]}),(0,a.jsx)(i.default,{value:g,placeholder:d,onChange:e=>{"custom"===e?(f(!0),y(void 0)):(f(!1),y(e),c&&c(e))},options:[...Array.from(new Set(b.map(e=>e.model_group))).map((e,s)=>({value:e,label:e,key:s})),{value:"custom",label:"Enter custom model",key:"custom"}],style:{width:"100%",...u},showSearch:!0,className:"rounded-md ".concat(h||""),disabled:m}),j&&(0,a.jsx)(r.o,{className:"mt-2",placeholder:"Enter custom model name",onValueChange:e=>{_.current&&clearTimeout(_.current),_.current=setTimeout(()=>{y(e),c&&c(e)},500)},disabled:m})]})}},2597:function(e,s,t){var a=t(57437);t(2265);var l=t(92280),r=t(54507);s.Z=function(e){let{value:s,onChange:t,premiumUser:i=!1,disabledCallbacks:n=[],onDisabledCallbacksChange:o}=e;return i?(0,a.jsx)(r.Z,{value:s,onChange:t,disabledCallbacks:n,onDisabledCallbacksChange:o}):(0,a.jsxs)("div",{children:[(0,a.jsxs)("div",{className:"flex flex-wrap gap-2 mb-3",children:[(0,a.jsx)("div",{className:"inline-flex items-center px-3 py-1.5 rounded-lg bg-green-50 border border-green-200 text-green-800 text-sm font-medium opacity-50",children:"✨ langfuse-logging"}),(0,a.jsx)("div",{className:"inline-flex items-center px-3 py-1.5 rounded-lg bg-green-50 border border-green-200 text-green-800 text-sm font-medium opacity-50",children:"✨ datadog-logging"})]}),(0,a.jsx)("div",{className:"p-3 bg-yellow-50 border border-yellow-200 rounded-lg",children:(0,a.jsxs)(l.x,{className:"text-sm text-yellow-800",children:["Setting Key/Team logging settings is a LiteLLM Enterprise feature. Global Logging Settings are available for all free users. Get a trial key"," ",(0,a.jsx)("a",{href:"https://www.litellm.ai/#pricing",target:"_blank",rel:"noopener noreferrer",className:"underline",children:"here"}),"."]})})]})}},65895:function(e,s,t){var a=t(57437);t(2265);var l=t(37592),r=t(10032),i=t(99981),n=t(15424);let{Option:o}=l.default;s.Z=e=>{let{type:s,name:t,showDetailedDescriptions:d=!0,className:c="",initialValue:m=null,form:u,onChange:h}=e,p=s.toUpperCase(),x=s.toLowerCase(),g="Select 'guaranteed_throughput' to prevent overallocating ".concat(p," limit when the key belongs to a Team with specific ").concat(p," limits.");return(0,a.jsx)(r.Z.Item,{label:(0,a.jsxs)("span",{children:[p," Rate Limit Type"," ",(0,a.jsx)(i.Z,{title:g,children:(0,a.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:t,initialValue:m,className:c,children:(0,a.jsx)(l.default,{defaultValue:d?"default":void 0,placeholder:"Select rate limit type",style:{width:"100%"},optionLabelProp:d?"label":void 0,onChange:e=>{u&&u.setFieldValue(t,e),h&&h(e)},children:d?(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(o,{value:"best_effort_throughput",label:"Default",children:(0,a.jsxs)("div",{style:{padding:"4px 0"},children:[(0,a.jsx)("div",{style:{fontWeight:500},children:"Default"}),(0,a.jsxs)("div",{style:{fontSize:"11px",color:"#6b7280",marginTop:"2px"},children:["Best effort throughput - no error if we're overallocating ",x," (Team/Key Limits checked at runtime)."]})]})}),(0,a.jsx)(o,{value:"guaranteed_throughput",label:"Guaranteed throughput",children:(0,a.jsxs)("div",{style:{padding:"4px 0"},children:[(0,a.jsx)("div",{style:{fontWeight:500},children:"Guaranteed throughput"}),(0,a.jsxs)("div",{style:{fontSize:"11px",color:"#6b7280",marginTop:"2px"},children:["Guaranteed throughput - raise an error if we're overallocating ",x," (also checks model-specific limits)"]})]})}),(0,a.jsx)(o,{value:"dynamic",label:"Dynamic",children:(0,a.jsxs)("div",{style:{padding:"4px 0"},children:[(0,a.jsx)("div",{style:{fontWeight:500},children:"Dynamic"}),(0,a.jsxs)("div",{style:{fontSize:"11px",color:"#6b7280",marginTop:"2px"},children:["If the key has a set ",p," (e.g. 2 ",p,") and there are no 429 errors, it can dynamically exceed the limit when the model being called is not erroring."]})]})})]}):(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(o,{value:"best_effort_throughput",children:"Best effort throughput"}),(0,a.jsx)(o,{value:"guaranteed_throughput",children:"Guaranteed throughput"}),(0,a.jsx)(o,{value:"dynamic",children:"Dynamic"})]})})})}},71098:function(e,s,t){t.d(s,{ZP:function(){return ee},wk:function(){return $},Nr:function(){return X}});var a=t(57437),l=t(30280),r=t(80443),i=t(59872),n=t(15424),o=t(29827),d=t(87452),c=t(88829),m=t(72208),u=t(78489),h=t(49804),p=t(67101),x=t(84264),g=t(49566),y=t(96761),j=t(37592),f=t(10032),b=t(22116),v=t(99981),_=t(29967),w=t(5545),Z=t(63709),N=t(4260),k=t(7310),S=t.n(k),C=t(2265),M=t(29233),P=t(20347),A=t(82586),L=t(97434),T=t(65925),I=t(63610),E=t(62099),F=t(72885),V=t(95096),U=t(2597),D=t(65895),K=t(84376),R=t(7765),O=t(46468),q=t(97492),B=t(68473),G=t(9114),W=t(19250),z=t(24199),J=t(97415);let H=e=>{let s;if(!(s=!e||"object"!=typeof e||e instanceof Error?String(e):JSON.stringify(e)).includes("/key/generate")&&!s.includes("KeyManagementRoutes.KEY_GENERATE"))return"Error creating the key: ".concat(e);let t=s;try{if(!e||"object"!=typeof e||e instanceof Error){let e=s.match(/\{[\s\S]*\}/);if(e){let s=JSON.parse(e[0]),a=(null==s?void 0:s.error)||s;(null==a?void 0:a.message)&&(t=a.message)}}else{let s=(null==e?void 0:e.error)||e;(null==s?void 0:s.message)&&(t=s.message)}}catch(e){}return s.includes("team_member_permission_error")||t.includes("Team member does not have permissions")?"Team member does not have permission to generate key for this team. Ask your proxy admin to configure the team member permission settings.":"Error creating the key: ".concat(e)},{Option:Y}=j.default,Q=e=>{let s=[];if(console.log("data:",JSON.stringify(e)),e)for(let t of e)t.metadata&&t.metadata.tags&&s.push(...t.metadata.tags);let t=Array.from(new Set(s)).map(e=>({value:e,label:e}));return console.log("uniqueTags:",t),t},$=async(e,s,t,a)=>{try{if(null===e||null===s)return[];if(null!==t){let l=(await (0,W.modelAvailableCall)(t,e,s,!0,a,!0)).data.map(e=>e.id);return console.log("available_model_names:",l),l}return[]}catch(e){return console.error("Error fetching user models:",e),[]}},X=async(e,s,t,a)=>{try{if(null===e||null===s)return;if(null!==t){let l=(await (0,W.modelAvailableCall)(t,e,s)).data.map(e=>e.id);console.log("available_model_names:",l),a(l)}}catch(e){console.error("Error fetching user models:",e)}};var ee=e=>{let{team:s,teams:t,data:k,addKey:ee}=e,{accessToken:es,userId:et,userRole:ea,premiumUser:el}=(0,r.Z)(),er=(0,o.NL)(),[ei]=f.Z.useForm(),[en,eo]=(0,C.useState)(!1),[ed,ec]=(0,C.useState)(null),[em,eu]=(0,C.useState)(null),[eh,ep]=(0,C.useState)([]),[ex,eg]=(0,C.useState)([]),[ey,ej]=(0,C.useState)("you"),[ef,eb]=(0,C.useState)(Q(k)),[ev,e_]=(0,C.useState)([]),[ew,eZ]=(0,C.useState)([]),[eN,ek]=(0,C.useState)([]),[eS,eC]=(0,C.useState)(s),[eM,eP]=(0,C.useState)(!1),[eA,eL]=(0,C.useState)(null),[eT,eI]=(0,C.useState)({}),[eE,eF]=(0,C.useState)([]),[eV,eU]=(0,C.useState)(!1),[eD,eK]=(0,C.useState)([]),[eR,eO]=(0,C.useState)([]),[eq,eB]=(0,C.useState)("default"),[eG,eW]=(0,C.useState)({}),[ez,eJ]=(0,C.useState)(!1),[eH,eY]=(0,C.useState)("30d"),eQ=()=>{eo(!1),ei.resetFields(),ek([]),eO([]),eB("default"),eW({}),eJ(!1),eY("30d")},e$=()=>{eo(!1),ec(null),eC(null),ei.resetFields(),ek([]),eO([]),eB("default"),eW({}),eJ(!1),eY("30d")};(0,C.useEffect)(()=>{et&&ea&&es&&X(et,ea,es,ep)},[es,et,ea]),(0,C.useEffect)(()=>{let e=async()=>{try{let e=await (0,W.getPromptsList)(es);eZ(e.prompts.map(e=>e.prompt_id))}catch(e){console.error("Failed to fetch prompts:",e)}};(async()=>{try{let e=(await (0,W.getGuardrailsList)(es)).guardrails.map(e=>e.guardrail_name);e_(e)}catch(e){console.error("Failed to fetch guardrails:",e)}})(),e()},[es]),(0,C.useEffect)(()=>{(async()=>{try{if(es){let e=sessionStorage.getItem("possibleUserRoles");if(e)eI(JSON.parse(e));else{let e=await (0,W.getPossibleUserRoles)(es);sessionStorage.setItem("possibleUserRoles",JSON.stringify(e)),eI(e)}}}catch(e){console.error("Error fetching possible user roles:",e)}})()},[es]);let eX=ex.includes("no-default-models")&&!eS,e0=async e=>{try{var s,t,a,r,i,n,o;let d;let c=null!==(i=null==e?void 0:e.key_alias)&&void 0!==i?i:"",m=null!==(n=null==e?void 0:e.team_id)&&void 0!==n?n:null;if((null!==(o=null==k?void 0:k.filter(e=>e.team_id===m).map(e=>e.key_alias))&&void 0!==o?o:[]).includes(c))throw Error("Key alias ".concat(c," already exists for team with ID ").concat(m,", please provide another key alias"));G.Z.info("Making API Call"),eo(!0),"you"===ey&&(e.user_id=et);let u={};try{u=JSON.parse(e.metadata||"{}")}catch(e){console.error("Error parsing metadata:",e)}if("service_account"===ey&&(u.service_account_id=e.key_alias),eN.length>0&&(u={...u,logging:eN.filter(e=>e.callback_name)}),eR.length>0){let e=(0,L.Z3)(eR);u={...u,litellm_disabled_callbacks:e}}if(ez&&(e.auto_rotate=!0,e.rotation_interval=eH),e.duration&&(e.duration=e.duration),e.metadata=JSON.stringify(u),e.allowed_vector_store_ids&&e.allowed_vector_store_ids.length>0&&(e.object_permission={vector_stores:e.allowed_vector_store_ids},delete e.allowed_vector_store_ids),e.allowed_mcp_servers_and_groups&&((null===(s=e.allowed_mcp_servers_and_groups.servers)||void 0===s?void 0:s.length)>0||(null===(t=e.allowed_mcp_servers_and_groups.accessGroups)||void 0===t?void 0:t.length)>0)){e.object_permission||(e.object_permission={});let{servers:s,accessGroups:t}=e.allowed_mcp_servers_and_groups;s&&s.length>0&&(e.object_permission.mcp_servers=s),t&&t.length>0&&(e.object_permission.mcp_access_groups=t),delete e.allowed_mcp_servers_and_groups}let h=e.mcp_tool_permissions||{};if(Object.keys(h).length>0&&(e.object_permission||(e.object_permission={}),e.object_permission.mcp_tool_permissions=h),delete e.mcp_tool_permissions,e.allowed_mcp_access_groups&&e.allowed_mcp_access_groups.length>0&&(e.object_permission||(e.object_permission={}),e.object_permission.mcp_access_groups=e.allowed_mcp_access_groups,delete e.allowed_mcp_access_groups),e.allowed_agents_and_groups&&((null===(a=e.allowed_agents_and_groups.agents)||void 0===a?void 0:a.length)>0||(null===(r=e.allowed_agents_and_groups.accessGroups)||void 0===r?void 0:r.length)>0)){e.object_permission||(e.object_permission={});let{agents:s,accessGroups:t}=e.allowed_agents_and_groups;s&&s.length>0&&(e.object_permission.agents=s),t&&t.length>0&&(e.object_permission.agent_access_groups=t),delete e.allowed_agents_and_groups}Object.keys(eG).length>0&&(e.aliases=JSON.stringify(eG)),d="service_account"===ey?await (0,W.keyCreateServiceAccountCall)(es,e):await (0,W.keyCreateCall)(es,et,e),console.log("key create Response:",d),ee(d),er.invalidateQueries({queryKey:l.Km.lists()}),ec(d.key),eu(d.soft_budget),G.Z.success("Virtual Key Created"),ei.resetFields(),localStorage.removeItem("userData"+et)}catch(s){console.log("error in create key:",s);let e=H(s);G.Z.fromBackend(e)}};(0,C.useEffect)(()=>{if(et&&ea&&es){var e;$(et,ea,es,null!==(e=null==eS?void 0:eS.team_id)&&void 0!==e?e:null).then(e=>{var s;eg(Array.from(new Set([...null!==(s=null==eS?void 0:eS.models)&&void 0!==s?s:[],...e])))})}ei.setFieldValue("models",[])},[eS,es,et,ea]);let e4=async e=>{if(!e){eF([]);return}eU(!0);try{let s=new URLSearchParams;if(s.append("user_email",e),null==es)return;let t=(await (0,W.userFilterUICall)(es,s)).map(e=>({label:"".concat(e.user_email," (").concat(e.user_id,")"),value:e.user_id,user:e}));eF(t)}catch(e){console.error("Error fetching users:",e),G.Z.fromBackend("Failed to search for users")}finally{eU(!1)}},e1=(0,C.useCallback)(S()(e=>e4(e),300),[es]),e2=(e,s)=>{let t=s.user;ei.setFieldsValue({user_id:t.user_id})};return(0,a.jsxs)("div",{children:[ea&&P.LQ.includes(ea)&&(0,a.jsx)(u.Z,{className:"mx-auto",onClick:()=>eo(!0),children:"+ Create New Key"}),(0,a.jsx)(b.Z,{open:en,width:1e3,footer:null,onOk:eQ,onCancel:e$,children:(0,a.jsxs)(f.Z,{form:ei,onFinish:e0,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",children:[(0,a.jsxs)("div",{className:"mb-8",children:[(0,a.jsx)(y.Z,{className:"mb-4",children:"Key Ownership"}),(0,a.jsx)(f.Z.Item,{label:(0,a.jsxs)("span",{children:["Owned By"," ",(0,a.jsx)(v.Z,{title:"Select who will own this Virtual Key",children:(0,a.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),className:"mb-4",children:(0,a.jsxs)(_.ZP.Group,{onChange:e=>ej(e.target.value),value:ey,children:[(0,a.jsx)(_.ZP,{value:"you",children:"You"}),(0,a.jsx)(_.ZP,{value:"service_account",children:"Service Account"}),"Admin"===ea&&(0,a.jsx)(_.ZP,{value:"another_user",children:"Another User"})]})}),"another_user"===ey&&(0,a.jsx)(f.Z.Item,{label:(0,a.jsxs)("span",{children:["User ID"," ",(0,a.jsx)(v.Z,{title:"The user who will own this key and be responsible for its usage",children:(0,a.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"user_id",className:"mt-4",rules:[{required:"another_user"===ey,message:"Please input the user ID of the user you are assigning the key to"}],children:(0,a.jsxs)("div",{children:[(0,a.jsxs)("div",{style:{display:"flex",marginBottom:"8px"},children:[(0,a.jsx)(j.default,{showSearch:!0,placeholder:"Type email to search for users",filterOption:!1,onSearch:e=>{e1(e)},onSelect:(e,s)=>e2(e,s),options:eE,loading:eV,allowClear:!0,style:{width:"100%"},notFoundContent:eV?"Searching...":"No users found"}),(0,a.jsx)(w.ZP,{onClick:()=>eP(!0),style:{marginLeft:"8px"},children:"Create User"})]}),(0,a.jsx)("div",{className:"text-xs text-gray-500",children:"Search by email to find users"})]})}),(0,a.jsx)(f.Z.Item,{label:(0,a.jsxs)("span",{children:["Team"," ",(0,a.jsx)(v.Z,{title:"The team this key belongs to, which determines available models and budget limits",children:(0,a.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"team_id",initialValue:s?s.team_id:null,className:"mt-4",rules:[{required:"service_account"===ey,message:"Please select a team for the service account"}],help:"service_account"===ey?"required":"",children:(0,a.jsx)(K.Z,{teams:t,onChange:e=>{eC((null==t?void 0:t.find(s=>s.team_id===e))||null)}})})]}),eX&&(0,a.jsx)("div",{className:"mb-8 p-4 bg-blue-50 border border-blue-200 rounded-md",children:(0,a.jsx)(x.Z,{className:"text-blue-800 text-sm",children:"Please select a team to continue configuring your Virtual Key. If you do not see any teams, please contact your Proxy Admin to either provide you with access to models or to add you to a team."})}),!eX&&(0,a.jsxs)("div",{className:"mb-8",children:[(0,a.jsx)(y.Z,{className:"mb-4",children:"Key Details"}),(0,a.jsx)(f.Z.Item,{label:(0,a.jsxs)("span",{children:["you"===ey||"another_user"===ey?"Key Name":"Service Account ID"," ",(0,a.jsx)(v.Z,{title:"you"===ey||"another_user"===ey?"A descriptive name to identify this key":"Unique identifier for this service account",children:(0,a.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"key_alias",rules:[{required:!0,message:"Please input a ".concat("you"===ey?"key name":"service account ID")}],help:"required",children:(0,a.jsx)(g.Z,{placeholder:""})}),(0,a.jsx)(f.Z.Item,{label:(0,a.jsxs)("span",{children:["Models"," ",(0,a.jsx)(v.Z,{title:"Select which models this key can access. Choose 'All Team Models' to grant access to all models available to the team",children:(0,a.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"models",rules:"management"===eq||"read_only"===eq?[]:[{required:!0,message:"Please select a model"}],help:"management"===eq||"read_only"===eq?"Models field is disabled for this key type":"required",className:"mt-4",children:(0,a.jsxs)(j.default,{mode:"multiple",placeholder:"Select models",style:{width:"100%"},disabled:"management"===eq||"read_only"===eq,onChange:e=>{e.includes("all-team-models")&&ei.setFieldsValue({models:["all-team-models"]})},children:[(0,a.jsx)(Y,{value:"all-team-models",children:"All Team Models"},"all-team-models"),ex.map(e=>(0,a.jsx)(Y,{value:e,children:(0,O.W0)(e)},e))]})}),(0,a.jsx)(f.Z.Item,{label:(0,a.jsxs)("span",{children:["Key Type"," ",(0,a.jsx)(v.Z,{title:"Select the type of key to determine what routes and operations this key can access",children:(0,a.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"key_type",initialValue:"default",className:"mt-4",children:(0,a.jsxs)(j.default,{defaultValue:"default",placeholder:"Select key type",style:{width:"100%"},optionLabelProp:"label",onChange:e=>{eB(e),("management"===e||"read_only"===e)&&ei.setFieldsValue({models:[]})},children:[(0,a.jsx)(Y,{value:"default",label:"Default",children:(0,a.jsxs)("div",{style:{padding:"4px 0"},children:[(0,a.jsx)("div",{style:{fontWeight:500},children:"Default"}),(0,a.jsx)("div",{style:{fontSize:"11px",color:"#6b7280",marginTop:"2px"},children:"Can call LLM API + Management routes"})]})}),(0,a.jsx)(Y,{value:"llm_api",label:"LLM API",children:(0,a.jsxs)("div",{style:{padding:"4px 0"},children:[(0,a.jsx)("div",{style:{fontWeight:500},children:"LLM API"}),(0,a.jsx)("div",{style:{fontSize:"11px",color:"#6b7280",marginTop:"2px"},children:"Can call only LLM API routes (chat/completions, embeddings, etc.)"})]})}),(0,a.jsx)(Y,{value:"management",label:"Management",children:(0,a.jsxs)("div",{style:{padding:"4px 0"},children:[(0,a.jsx)("div",{style:{fontWeight:500},children:"Management"}),(0,a.jsx)("div",{style:{fontSize:"11px",color:"#6b7280",marginTop:"2px"},children:"Can call only management routes (user/team/key management)"})]})})]})})]}),!eX&&(0,a.jsx)("div",{className:"mb-8",children:(0,a.jsxs)(d.Z,{className:"mt-4 mb-4",children:[(0,a.jsx)(m.Z,{children:(0,a.jsx)(y.Z,{className:"m-0",children:"Optional Settings"})}),(0,a.jsxs)(c.Z,{children:[(0,a.jsx)(f.Z.Item,{className:"mt-4",label:(0,a.jsxs)("span",{children:["Max Budget (USD)"," ",(0,a.jsx)(v.Z,{title:"Maximum amount in USD this key can spend. When reached, the key will be blocked from making further requests",children:(0,a.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"max_budget",help:"Budget cannot exceed team max budget: $".concat((null==s?void 0:s.max_budget)!==null&&(null==s?void 0:s.max_budget)!==void 0?null==s?void 0:s.max_budget:"unlimited"),rules:[{validator:async(e,t)=>{if(t&&s&&null!==s.max_budget&&t>s.max_budget)throw Error("Budget cannot exceed team max budget: $".concat((0,i.pw)(s.max_budget,4)))}}],children:(0,a.jsx)(z.Z,{step:.01,precision:2,width:200})}),(0,a.jsx)(f.Z.Item,{className:"mt-4",label:(0,a.jsxs)("span",{children:["Reset Budget"," ",(0,a.jsx)(v.Z,{title:"How often the budget should reset. For example, setting 'daily' will reset the budget every 24 hours",children:(0,a.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"budget_duration",help:"Team Reset Budget: ".concat((null==s?void 0:s.budget_duration)!==null&&(null==s?void 0:s.budget_duration)!==void 0?null==s?void 0:s.budget_duration:"None"),children:(0,a.jsx)(T.Z,{onChange:e=>ei.setFieldValue("budget_duration",e)})}),(0,a.jsx)(f.Z.Item,{className:"mt-4",label:(0,a.jsxs)("span",{children:["Tokens per minute Limit (TPM)"," ",(0,a.jsx)(v.Z,{title:"Maximum number of tokens this key can process per minute. Helps control usage and costs",children:(0,a.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"tpm_limit",help:"TPM cannot exceed team TPM limit: ".concat((null==s?void 0:s.tpm_limit)!==null&&(null==s?void 0:s.tpm_limit)!==void 0?null==s?void 0:s.tpm_limit:"unlimited"),rules:[{validator:async(e,t)=>{if(t&&s&&null!==s.tpm_limit&&t>s.tpm_limit)throw Error("TPM limit cannot exceed team TPM limit: ".concat(s.tpm_limit))}}],children:(0,a.jsx)(z.Z,{step:1,width:400})}),(0,a.jsx)(D.Z,{type:"tpm",name:"tpm_limit_type",className:"mt-4",initialValue:null,form:ei,showDetailedDescriptions:!0}),(0,a.jsx)(f.Z.Item,{className:"mt-4",label:(0,a.jsxs)("span",{children:["Requests per minute Limit (RPM)"," ",(0,a.jsx)(v.Z,{title:"Maximum number of API requests this key can make per minute. Helps prevent abuse and manage load",children:(0,a.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"rpm_limit",help:"RPM cannot exceed team RPM limit: ".concat((null==s?void 0:s.rpm_limit)!==null&&(null==s?void 0:s.rpm_limit)!==void 0?null==s?void 0:s.rpm_limit:"unlimited"),rules:[{validator:async(e,t)=>{if(t&&s&&null!==s.rpm_limit&&t>s.rpm_limit)throw Error("RPM limit cannot exceed team RPM limit: ".concat(s.rpm_limit))}}],children:(0,a.jsx)(z.Z,{step:1,width:400})}),(0,a.jsx)(D.Z,{type:"rpm",name:"rpm_limit_type",className:"mt-4",initialValue:null,form:ei,showDetailedDescriptions:!0}),(0,a.jsx)(f.Z.Item,{label:(0,a.jsxs)("span",{children:["Guardrails"," ",(0,a.jsx)(v.Z,{title:"Apply safety guardrails to this key to filter content or enforce policies",children:(0,a.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/guardrails/quick_start",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,a.jsx)(n.Z,{style:{marginLeft:"4px"}})})})]}),name:"guardrails",className:"mt-4",help:el?"Select existing guardrails or enter new ones":"Premium feature - Upgrade to set guardrails by key",children:(0,a.jsx)(j.default,{mode:"tags",style:{width:"100%"},disabled:!el,placeholder:el?"Select or enter guardrails":"Premium feature - Upgrade to set guardrails by key",options:ev.map(e=>({value:e,label:e}))})}),(0,a.jsx)(f.Z.Item,{label:(0,a.jsxs)("span",{children:["Disable Global Guardrails"," ",(0,a.jsx)(v.Z,{title:"When enabled, this key will bypass any guardrails configured to run on every request (global guardrails)",children:(0,a.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/guardrails/quick_start",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,a.jsx)(n.Z,{style:{marginLeft:"4px"}})})})]}),name:"disable_global_guardrails",className:"mt-4",valuePropName:"checked",help:el?"Bypass global guardrails for this key":"Premium feature - Upgrade to disable global guardrails by key",children:(0,a.jsx)(Z.Z,{disabled:!el,checkedChildren:"Yes",unCheckedChildren:"No"})}),(0,a.jsx)(f.Z.Item,{label:(0,a.jsxs)("span",{children:["Prompts"," ",(0,a.jsx)(v.Z,{title:"Allow this key to use specific prompt templates",children:(0,a.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/prompt_management",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,a.jsx)(n.Z,{style:{marginLeft:"4px"}})})})]}),name:"prompts",className:"mt-4",help:el?"Select existing prompts or enter new ones":"Premium feature - Upgrade to set prompts by key",children:(0,a.jsx)(j.default,{mode:"tags",style:{width:"100%"},disabled:!el,placeholder:el?"Select or enter prompts":"Premium feature - Upgrade to set prompts by key",options:ew.map(e=>({value:e,label:e}))})}),(0,a.jsx)(f.Z.Item,{label:(0,a.jsxs)("span",{children:["Allowed Pass Through Routes"," ",(0,a.jsx)(v.Z,{title:"Allow this key to use specific pass through routes",children:(0,a.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/pass_through",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,a.jsx)(n.Z,{style:{marginLeft:"4px"}})})})]}),name:"allowed_passthrough_routes",className:"mt-4",help:el?"Select existing pass through routes or enter new ones":"Premium feature - Upgrade to set pass through routes by key",children:(0,a.jsx)(V.Z,{onChange:e=>ei.setFieldValue("allowed_passthrough_routes",e),value:ei.getFieldValue("allowed_passthrough_routes"),accessToken:es,placeholder:el?"Select or enter pass through routes":"Premium feature - Upgrade to set pass through routes by key",disabled:!el,teamId:eS?eS.team_id:null})}),(0,a.jsx)(f.Z.Item,{label:(0,a.jsxs)("span",{children:["Allowed Vector Stores"," ",(0,a.jsx)(v.Z,{title:"Select which vector stores this key can access. If none selected, the key will have access to all available vector stores",children:(0,a.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"allowed_vector_store_ids",className:"mt-4",help:"Select vector stores this key can access. Leave empty for access to all vector stores",children:(0,a.jsx)(J.Z,{onChange:e=>ei.setFieldValue("allowed_vector_store_ids",e),value:ei.getFieldValue("allowed_vector_store_ids"),accessToken:es,placeholder:"Select vector stores (optional)"})}),(0,a.jsx)(f.Z.Item,{label:(0,a.jsxs)("span",{children:["Metadata"," ",(0,a.jsx)(v.Z,{title:"JSON object with additional information about this key. Used for tracking or custom logic",children:(0,a.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"metadata",className:"mt-4",children:(0,a.jsx)(N.default.TextArea,{rows:4,placeholder:"Enter metadata as JSON"})}),(0,a.jsx)(f.Z.Item,{label:(0,a.jsxs)("span",{children:["Tags"," ",(0,a.jsx)(v.Z,{title:"Tags for tracking spend and/or doing tag-based routing. Used for analytics and filtering",children:(0,a.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"tags",className:"mt-4",help:"Tags for tracking spend and/or doing tag-based routing.",children:(0,a.jsx)(j.default,{mode:"tags",style:{width:"100%"},placeholder:"Enter tags",tokenSeparators:[","],options:ef})}),(0,a.jsxs)(d.Z,{className:"mt-4 mb-4",children:[(0,a.jsx)(m.Z,{children:(0,a.jsx)("b",{children:"MCP Settings"})}),(0,a.jsxs)(c.Z,{children:[(0,a.jsx)(f.Z.Item,{label:(0,a.jsxs)("span",{children:["Allowed MCP Servers"," ",(0,a.jsx)(v.Z,{title:"Select which MCP servers or access groups this key can access",children:(0,a.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"allowed_mcp_servers_and_groups",help:"Select MCP servers or access groups this key can access",children:(0,a.jsx)(q.Z,{onChange:e=>ei.setFieldValue("allowed_mcp_servers_and_groups",e),value:ei.getFieldValue("allowed_mcp_servers_and_groups"),accessToken:es,placeholder:"Select MCP servers or access groups (optional)"})}),(0,a.jsx)(f.Z.Item,{name:"mcp_tool_permissions",initialValue:{},hidden:!0,children:(0,a.jsx)(N.default,{type:"hidden"})}),(0,a.jsx)(f.Z.Item,{noStyle:!0,shouldUpdate:(e,s)=>e.allowed_mcp_servers_and_groups!==s.allowed_mcp_servers_and_groups||e.mcp_tool_permissions!==s.mcp_tool_permissions,children:()=>{var e;return(0,a.jsx)("div",{className:"mt-6",children:(0,a.jsx)(B.Z,{accessToken:es,selectedServers:(null===(e=ei.getFieldValue("allowed_mcp_servers_and_groups"))||void 0===e?void 0:e.servers)||[],toolPermissions:ei.getFieldValue("mcp_tool_permissions")||{},onChange:e=>ei.setFieldsValue({mcp_tool_permissions:e})})})}})]})]}),(0,a.jsxs)(d.Z,{className:"mt-4 mb-4",children:[(0,a.jsx)(m.Z,{children:(0,a.jsx)("b",{children:"Agent Settings"})}),(0,a.jsx)(c.Z,{children:(0,a.jsx)(f.Z.Item,{label:(0,a.jsxs)("span",{children:["Allowed Agents"," ",(0,a.jsx)(v.Z,{title:"Select which agents or access groups this key can access",children:(0,a.jsx)(n.Z,{style:{marginLeft:"4px"}})})]}),name:"allowed_agents_and_groups",help:"Select agents or access groups this key can access",children:(0,a.jsx)(A.Z,{onChange:e=>ei.setFieldValue("allowed_agents_and_groups",e),value:ei.getFieldValue("allowed_agents_and_groups"),accessToken:es,placeholder:"Select agents or access groups (optional)"})})})]}),el?(0,a.jsxs)(d.Z,{className:"mt-4 mb-4",children:[(0,a.jsx)(m.Z,{children:(0,a.jsx)("b",{children:"Logging Settings"})}),(0,a.jsx)(c.Z,{children:(0,a.jsx)("div",{className:"mt-4",children:(0,a.jsx)(U.Z,{value:eN,onChange:ek,premiumUser:!0,disabledCallbacks:eR,onDisabledCallbacksChange:eO})})})]}):(0,a.jsx)(v.Z,{title:(0,a.jsxs)("span",{children:["Key-level logging settings is an enterprise feature, get in touch -",(0,a.jsx)("a",{href:"https://www.litellm.ai/enterprise",target:"_blank",children:"https://www.litellm.ai/enterprise"})]}),placement:"top",children:(0,a.jsxs)("div",{style:{position:"relative"},children:[(0,a.jsx)("div",{style:{opacity:.5},children:(0,a.jsxs)(d.Z,{className:"mt-4 mb-4",children:[(0,a.jsx)(m.Z,{children:(0,a.jsx)("b",{children:"Logging Settings"})}),(0,a.jsx)(c.Z,{children:(0,a.jsx)("div",{className:"mt-4",children:(0,a.jsx)(U.Z,{value:eN,onChange:ek,premiumUser:!1,disabledCallbacks:eR,onDisabledCallbacksChange:eO})})})]})}),(0,a.jsx)("div",{style:{position:"absolute",inset:0,cursor:"not-allowed"}})]})}),(0,a.jsxs)(d.Z,{className:"mt-4 mb-4",children:[(0,a.jsx)(m.Z,{children:(0,a.jsx)("b",{children:"Model Aliases"})}),(0,a.jsx)(c.Z,{children:(0,a.jsxs)("div",{className:"mt-4",children:[(0,a.jsx)(x.Z,{className:"text-sm text-gray-600 mb-4",children:"Create custom aliases for models that can be used in API calls. This allows you to create shortcuts for specific models."}),(0,a.jsx)(F.Z,{accessToken:es,initialModelAliases:eG,onAliasUpdate:eW,showExampleConfig:!1})]})})]}),(0,a.jsxs)(d.Z,{className:"mt-4 mb-4",children:[(0,a.jsx)(m.Z,{children:(0,a.jsx)("b",{children:"Key Lifecycle"})}),(0,a.jsx)(c.Z,{children:(0,a.jsx)("div",{className:"mt-4",children:(0,a.jsx)(E.Z,{form:ei,autoRotationEnabled:ez,onAutoRotationChange:eJ,rotationInterval:eH,onRotationIntervalChange:eY})})}),(0,a.jsx)(f.Z.Item,{name:"duration",hidden:!0,initialValue:null,children:(0,a.jsx)(N.default,{})})]}),(0,a.jsxs)(d.Z,{className:"mt-4 mb-4",children:[(0,a.jsx)(m.Z,{children:(0,a.jsxs)("div",{className:"flex items-center gap-2",children:[(0,a.jsx)("b",{children:"Advanced Settings"}),(0,a.jsx)(v.Z,{title:(0,a.jsxs)("span",{children:["Learn more about advanced settings in our"," ",(0,a.jsx)("a",{href:W.proxyBaseUrl?"".concat(W.proxyBaseUrl,"/#/key%20management/generate_key_fn_key_generate_post"):"/#/key%20management/generate_key_fn_key_generate_post",target:"_blank",rel:"noopener noreferrer",className:"text-blue-400 hover:text-blue-300",children:"documentation"})]}),children:(0,a.jsx)(n.Z,{className:"text-gray-400 hover:text-gray-300 cursor-help"})})]})}),(0,a.jsx)(c.Z,{children:(0,a.jsx)(I.Z,{schemaComponent:"GenerateKeyRequest",form:ei,excludedFields:["key_alias","team_id","models","duration","metadata","tags","guardrails","max_budget","budget_duration","tpm_limit","rpm_limit"]})})]})]})]})}),(0,a.jsx)("div",{style:{textAlign:"right",marginTop:"10px"},children:(0,a.jsx)(w.ZP,{htmlType:"submit",disabled:eX,style:{opacity:eX?.5:1},children:"Create Key"})})]})}),eM&&(0,a.jsx)(b.Z,{title:"Create New User",visible:eM,onCancel:()=>eP(!1),footer:null,width:800,children:(0,a.jsx)(R.Z,{userID:et,accessToken:es,teams:t,possibleUIRoles:eT,onUserCreated:e=>{eL(e),ei.setFieldsValue({user_id:e}),eP(!1)},isEmbedded:!0})}),ed&&(0,a.jsx)(b.Z,{visible:en,onOk:eQ,onCancel:e$,footer:null,children:(0,a.jsxs)(p.Z,{numItems:1,className:"gap-2 w-full",children:[(0,a.jsx)(y.Z,{children:"Save your Key"}),(0,a.jsx)(h.Z,{numColSpan:1,children:(0,a.jsxs)("p",{children:["Please save this secret key somewhere safe and accessible. For security reasons,"," ",(0,a.jsx)("b",{children:"you will not be able to view it again"})," through your LiteLLM account. If you lose this secret key, you will need to generate a new one."]})}),(0,a.jsx)(h.Z,{numColSpan:1,children:null!=ed?(0,a.jsxs)("div",{children:[(0,a.jsx)(x.Z,{className:"mt-3",children:"Virtual Key:"}),(0,a.jsx)("div",{style:{background:"#f8f8f8",padding:"10px",borderRadius:"5px",marginBottom:"10px"},children:(0,a.jsx)("pre",{style:{wordWrap:"break-word",whiteSpace:"normal"},children:ed})}),(0,a.jsx)(M.CopyToClipboard,{text:ed,onCopy:()=>{G.Z.success("Virtual Key copied to clipboard")},children:(0,a.jsx)(u.Z,{className:"mt-3",children:"Copy Virtual Key"})})]}):(0,a.jsx)(x.Z,{children:"Key being created, this might take 30s"})})]})})]})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1108-8b678b0704cb239b.js b/litellm/proxy/_experimental/out/_next/static/chunks/1108-8b678b0704cb239b.js new file mode 100644 index 00000000000..89c5291477d --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/1108-8b678b0704cb239b.js @@ -0,0 +1 @@ +(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1108],{40278:function(t,e,r){"use strict";r.d(e,{Z:function(){return S}});var n=r(5853),o=r(7084),i=r(26898),a=r(13241),u=r(1153),c=r(2265),l=r(47625),s=r(93765),f=r(31699),p=r(97059),h=r(62994),d=r(25311),y=(0,s.z)({chartName:"BarChart",GraphicalChild:f.$,defaultTooltipEventType:"axis",validateTooltipEventTypes:["axis","item"],axisComponents:[{axisType:"xAxis",AxisComp:p.K},{axisType:"yAxis",AxisComp:h.B}],formatAxisMap:d.t9}),v=r(56940),m=r(26680),b=r(8147),g=r(22190),x=r(65278),w=r(98593),O=r(92666),j=r(32644);let S=c.forwardRef((t,e)=>{let{data:r=[],categories:s=[],index:d,colors:S=i.s,valueFormatter:P=u.Cj,layout:E="horizontal",stack:k=!1,relative:A=!1,startEndOnly:M=!1,animationDuration:_=900,showAnimation:T=!1,showXAxis:C=!0,showYAxis:N=!0,yAxisWidth:D=56,intervalType:I="equidistantPreserveStart",showTooltip:L=!0,showLegend:B=!0,showGridLines:R=!0,autoMinValue:z=!1,minValue:U,maxValue:F,allowDecimals:$=!0,noDataText:q,onValueChange:Z,enableLegendSlider:W=!1,customTooltip:Y,rotateLabelX:H,barCategoryGap:X,tickGap:G=5,xAxisLabel:V,yAxisLabel:K,className:Q,padding:J=C||N?{left:20,right:20}:{left:0,right:0}}=t,tt=(0,n._T)(t,["data","categories","index","colors","valueFormatter","layout","stack","relative","startEndOnly","animationDuration","showAnimation","showXAxis","showYAxis","yAxisWidth","intervalType","showTooltip","showLegend","showGridLines","autoMinValue","minValue","maxValue","allowDecimals","noDataText","onValueChange","enableLegendSlider","customTooltip","rotateLabelX","barCategoryGap","tickGap","xAxisLabel","yAxisLabel","className","padding"]),[te,tr]=(0,c.useState)(60),tn=(0,j.me)(s,S),[to,ti]=c.useState(void 0),[ta,tu]=(0,c.useState)(void 0),tc=!!Z;function tl(t,e,r){var n,o,i,a;r.stopPropagation(),Z&&((0,j.vZ)(to,Object.assign(Object.assign({},t.payload),{value:t.value}))?(tu(void 0),ti(void 0),null==Z||Z(null)):(tu(null===(o=null===(n=t.tooltipPayload)||void 0===n?void 0:n[0])||void 0===o?void 0:o.dataKey),ti(Object.assign(Object.assign({},t.payload),{value:t.value})),null==Z||Z(Object.assign({eventType:"bar",categoryClicked:null===(a=null===(i=t.tooltipPayload)||void 0===i?void 0:i[0])||void 0===a?void 0:a.dataKey},t.payload))))}let ts=(0,j.i4)(z,U,F);return c.createElement("div",Object.assign({ref:e,className:(0,a.q)("w-full h-80",Q)},tt),c.createElement(l.h,{className:"h-full w-full"},(null==r?void 0:r.length)?c.createElement(y,{barCategoryGap:X,data:r,stackOffset:k?"sign":A?"expand":"none",layout:"vertical"===E?"vertical":"horizontal",onClick:tc&&(ta||to)?()=>{ti(void 0),tu(void 0),null==Z||Z(null)}:void 0,margin:{bottom:V?30:void 0,left:K?20:void 0,right:K?5:void 0,top:5}},R?c.createElement(v.q,{className:(0,a.q)("stroke-1","stroke-tremor-border","dark:stroke-dark-tremor-border"),horizontal:"vertical"!==E,vertical:"vertical"===E}):null,"vertical"!==E?c.createElement(p.K,{padding:J,hide:!C,dataKey:d,interval:M?"preserveStartEnd":I,tick:{transform:"translate(0, 6)"},ticks:M?[r[0][d],r[r.length-1][d]]:void 0,fill:"",stroke:"",className:(0,a.q)("mt-4 text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickLine:!1,axisLine:!1,angle:null==H?void 0:H.angle,dy:null==H?void 0:H.verticalShift,height:null==H?void 0:H.xAxisHeight,minTickGap:G},V&&c.createElement(m._,{position:"insideBottom",offset:-20,className:"fill-tremor-content-emphasis text-tremor-default font-medium dark:fill-dark-tremor-content-emphasis"},V)):c.createElement(p.K,{hide:!C,type:"number",tick:{transform:"translate(-3, 0)"},domain:ts,fill:"",stroke:"",className:(0,a.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickLine:!1,axisLine:!1,tickFormatter:P,minTickGap:G,allowDecimals:$,angle:null==H?void 0:H.angle,dy:null==H?void 0:H.verticalShift,height:null==H?void 0:H.xAxisHeight},V&&c.createElement(m._,{position:"insideBottom",offset:-20,className:"fill-tremor-content-emphasis text-tremor-default font-medium dark:fill-dark-tremor-content-emphasis"},V)),"vertical"!==E?c.createElement(h.B,{width:D,hide:!N,axisLine:!1,tickLine:!1,type:"number",domain:ts,tick:{transform:"translate(-3, 0)"},fill:"",stroke:"",className:(0,a.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickFormatter:A?t=>"".concat((100*t).toString()," %"):P,allowDecimals:$},K&&c.createElement(m._,{position:"insideLeft",style:{textAnchor:"middle"},angle:-90,offset:-15,className:"fill-tremor-content-emphasis text-tremor-default font-medium dark:fill-dark-tremor-content-emphasis"},K)):c.createElement(h.B,{width:D,hide:!N,dataKey:d,axisLine:!1,tickLine:!1,ticks:M?[r[0][d],r[r.length-1][d]]:void 0,type:"category",interval:"preserveStartEnd",tick:{transform:"translate(0, 6)"},fill:"",stroke:"",className:(0,a.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content")},K&&c.createElement(m._,{position:"insideLeft",style:{textAnchor:"middle"},angle:-90,offset:-15,className:"fill-tremor-content-emphasis text-tremor-default font-medium dark:fill-dark-tremor-content-emphasis"},K)),c.createElement(b.u,{wrapperStyle:{outline:"none"},isAnimationActive:!1,cursor:{fill:"#d1d5db",opacity:"0.15"},content:L?t=>{let{active:e,payload:r,label:n}=t;return Y?c.createElement(Y,{payload:null==r?void 0:r.map(t=>{var e;return Object.assign(Object.assign({},t),{color:null!==(e=tn.get(t.dataKey))&&void 0!==e?e:o.fr.Gray})}),active:e,label:n}):c.createElement(w.ZP,{active:e,payload:r,label:n,valueFormatter:P,categoryColors:tn})}:c.createElement(c.Fragment,null),position:{y:0}}),B?c.createElement(g.D,{verticalAlign:"top",height:te,content:t=>{let{payload:e}=t;return(0,x.Z)({payload:e},tn,tr,ta,tc?t=>{tc&&(t!==ta||to?(tu(t),null==Z||Z({eventType:"category",categoryClicked:t})):(tu(void 0),null==Z||Z(null)),ti(void 0))}:void 0,W)}}):null,s.map(t=>{var e;return c.createElement(f.$,{className:(0,a.q)((0,u.bM)(null!==(e=tn.get(t))&&void 0!==e?e:o.fr.Gray,i.K.background).fillColor,Z?"cursor-pointer":""),key:t,name:t,type:"linear",stackId:k||A?"a":void 0,dataKey:t,fill:"",isAnimationActive:T,animationDuration:_,shape:t=>((t,e,r,n)=>{let{fillOpacity:o,name:i,payload:a,value:u}=t,{x:l,width:s,y:f,height:p}=t;return"horizontal"===n&&p<0?(f+=p,p=Math.abs(p)):"vertical"===n&&s<0&&(l+=s,s=Math.abs(s)),c.createElement("rect",{x:l,y:f,width:s,height:p,opacity:e||r&&r!==i?(0,j.vZ)(e,Object.assign(Object.assign({},a),{value:u}))?o:.3:o})})(t,to,ta,E),onClick:tl})})):c.createElement(O.Z,{noDataText:q})))});S.displayName="BarChart"},65278:function(t,e,r){"use strict";r.d(e,{Z:function(){return y}});var n=r(2265);let o=t=>{n.useEffect(()=>{let e=()=>{t()};return e(),window.addEventListener("resize",e),()=>window.removeEventListener("resize",e)},[t])};var i=r(5853),a=r(26898),u=r(13241),c=r(1153);let l=t=>{var e=(0,i._T)(t,[]);return n.createElement("svg",Object.assign({},e,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"}),n.createElement("path",{d:"M8 12L14 6V18L8 12Z"}))},s=t=>{var e=(0,i._T)(t,[]);return n.createElement("svg",Object.assign({},e,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"}),n.createElement("path",{d:"M16 12L10 18V6L16 12Z"}))},f=(0,c.fn)("Legend"),p=t=>{let{name:e,color:r,onClick:o,activeLegend:i}=t,l=!!o;return n.createElement("li",{className:(0,u.q)(f("legendItem"),"group inline-flex items-center px-2 py-0.5 rounded-tremor-small transition whitespace-nowrap",l?"cursor-pointer":"cursor-default","text-tremor-content",l?"hover:bg-tremor-background-subtle":"","dark:text-dark-tremor-content",l?"dark:hover:bg-dark-tremor-background-subtle":""),onClick:t=>{t.stopPropagation(),null==o||o(e,r)}},n.createElement("svg",{className:(0,u.q)("flex-none h-2 w-2 mr-1.5",(0,c.bM)(r,a.K.text).textColor,i&&i!==e?"opacity-40":"opacity-100"),fill:"currentColor",viewBox:"0 0 8 8"},n.createElement("circle",{cx:4,cy:4,r:4})),n.createElement("p",{className:(0,u.q)("whitespace-nowrap truncate text-tremor-default","text-tremor-content",l?"group-hover:text-tremor-content-emphasis":"","dark:text-dark-tremor-content",i&&i!==e?"opacity-40":"opacity-100",l?"dark:group-hover:text-dark-tremor-content-emphasis":"")},e))},h=t=>{let{icon:e,onClick:r,disabled:o}=t,[i,a]=n.useState(!1),c=n.useRef(null);return n.useEffect(()=>(i?c.current=setInterval(()=>{null==r||r()},300):clearInterval(c.current),()=>clearInterval(c.current)),[i,r]),(0,n.useEffect)(()=>{o&&(clearInterval(c.current),a(!1))},[o]),n.createElement("button",{type:"button",className:(0,u.q)(f("legendSliderButton"),"w-5 group inline-flex items-center truncate rounded-tremor-small transition",o?"cursor-not-allowed":"cursor-pointer",o?"text-tremor-content-subtle":"text-tremor-content hover:text-tremor-content-emphasis hover:bg-tremor-background-subtle",o?"dark:text-dark-tremor-subtle":"dark:text-dark-tremor dark:hover:text-tremor-content-emphasis dark:hover:bg-dark-tremor-background-subtle"),disabled:o,onClick:t=>{t.stopPropagation(),null==r||r()},onMouseDown:t=>{t.stopPropagation(),a(!0)},onMouseUp:t=>{t.stopPropagation(),a(!1)}},n.createElement(e,{className:"w-full"}))},d=n.forwardRef((t,e)=>{let{categories:r,colors:o=a.s,className:c,onClickLegendItem:d,activeLegend:y,enableLegendSlider:v=!1}=t,m=(0,i._T)(t,["categories","colors","className","onClickLegendItem","activeLegend","enableLegendSlider"]),b=n.useRef(null),g=n.useRef(null),[x,w]=n.useState(null),[O,j]=n.useState(null),S=n.useRef(null),P=(0,n.useCallback)(()=>{let t=null==b?void 0:b.current;t&&w({left:t.scrollLeft>0,right:t.scrollWidth-t.clientWidth>t.scrollLeft})},[w]),E=(0,n.useCallback)(t=>{var e,r;let n=null==b?void 0:b.current,o=null==g?void 0:g.current,i=null!==(e=null==n?void 0:n.clientWidth)&&void 0!==e?e:0,a=null!==(r=null==o?void 0:o.clientWidth)&&void 0!==r?r:0;n&&v&&(n.scrollTo({left:"left"===t?n.scrollLeft-i+a:n.scrollLeft+i-a,behavior:"smooth"}),setTimeout(()=>{P()},400))},[v,P]);n.useEffect(()=>{let t=t=>{"ArrowLeft"===t?E("left"):"ArrowRight"===t&&E("right")};return O?(t(O),S.current=setInterval(()=>{t(O)},300)):clearInterval(S.current),()=>clearInterval(S.current)},[O,E]);let k=t=>{t.stopPropagation(),"ArrowLeft"!==t.key&&"ArrowRight"!==t.key||(t.preventDefault(),j(t.key))},A=t=>{t.stopPropagation(),j(null)};return n.useEffect(()=>{let t=null==b?void 0:b.current;return v&&(P(),null==t||t.addEventListener("keydown",k),null==t||t.addEventListener("keyup",A)),()=>{null==t||t.removeEventListener("keydown",k),null==t||t.removeEventListener("keyup",A)}},[P,v]),n.createElement("ol",Object.assign({ref:e,className:(0,u.q)(f("root"),"relative overflow-hidden",c)},m),n.createElement("div",{ref:b,tabIndex:0,className:(0,u.q)("h-full flex",v?(null==x?void 0:x.right)||(null==x?void 0:x.left)?"pl-4 pr-12 items-center overflow-auto snap-mandatory [&::-webkit-scrollbar]:hidden [scrollbar-width:none]":"":"flex-wrap")},r.map((t,e)=>n.createElement(p,{key:"item-".concat(e),name:t,color:o[e%o.length],onClick:d,activeLegend:y}))),v&&((null==x?void 0:x.right)||(null==x?void 0:x.left))?n.createElement(n.Fragment,null,n.createElement("div",{className:(0,u.q)("bg-tremor-background","dark:bg-dark-tremor-background","absolute flex top-0 pr-1 bottom-0 right-0 items-center justify-center h-full"),ref:g},n.createElement(h,{icon:l,onClick:()=>{j(null),E("left")},disabled:!(null==x?void 0:x.left)}),n.createElement(h,{icon:s,onClick:()=>{j(null),E("right")},disabled:!(null==x?void 0:x.right)}))):null)});d.displayName="Legend";let y=(t,e,r,i,a,u)=>{let{payload:c}=t,l=(0,n.useRef)(null);o(()=>{var t,e;r((e=null===(t=l.current)||void 0===t?void 0:t.clientHeight)?Number(e)+20:60)});let s=c.filter(t=>"none"!==t.type);return n.createElement("div",{ref:l,className:"flex items-center justify-end"},n.createElement(d,{categories:s.map(t=>t.value),colors:s.map(t=>e.get(t.value)),onClickLegendItem:a,activeLegend:i,enableLegendSlider:u}))}},98593:function(t,e,r){"use strict";r.d(e,{$B:function(){return c},ZP:function(){return s},zX:function(){return l}});var n=r(2265),o=r(7084),i=r(26898),a=r(13241),u=r(1153);let c=t=>{let{children:e}=t;return n.createElement("div",{className:(0,a.q)("rounded-tremor-default text-tremor-default border","bg-tremor-background shadow-tremor-dropdown border-tremor-border","dark:bg-dark-tremor-background dark:shadow-dark-tremor-dropdown dark:border-dark-tremor-border")},e)},l=t=>{let{value:e,name:r,color:o}=t;return n.createElement("div",{className:"flex items-center justify-between space-x-8"},n.createElement("div",{className:"flex items-center space-x-2"},n.createElement("span",{className:(0,a.q)("shrink-0 rounded-tremor-full border-2 h-3 w-3","border-tremor-background shadow-tremor-card","dark:border-dark-tremor-background dark:shadow-dark-tremor-card",(0,u.bM)(o,i.K.background).bgColor)}),n.createElement("p",{className:(0,a.q)("text-right whitespace-nowrap","text-tremor-content","dark:text-dark-tremor-content")},r)),n.createElement("p",{className:(0,a.q)("font-medium tabular-nums text-right whitespace-nowrap","text-tremor-content-emphasis","dark:text-dark-tremor-content-emphasis")},e))},s=t=>{let{active:e,payload:r,label:i,categoryColors:u,valueFormatter:s}=t;if(e&&r){let t=r.filter(t=>"none"!==t.type);return n.createElement(c,null,n.createElement("div",{className:(0,a.q)("border-tremor-border border-b px-4 py-2","dark:border-dark-tremor-border")},n.createElement("p",{className:(0,a.q)("font-medium","text-tremor-content-emphasis","dark:text-dark-tremor-content-emphasis")},i)),n.createElement("div",{className:(0,a.q)("px-4 py-2 space-y-1")},t.map((t,e)=>{var r;let{value:i,name:a}=t;return n.createElement(l,{key:"id-".concat(e),value:s(i),name:a,color:null!==(r=u.get(a))&&void 0!==r?r:o.fr.Blue})})))}return null}},92666:function(t,e,r){"use strict";r.d(e,{Z:function(){return i}});var n=r(13241),o=r(2265);let i=t=>{let{className:e,noDataText:r="No data"}=t;return o.createElement("div",{className:(0,n.q)("flex items-center justify-center w-full h-full border border-dashed rounded-tremor-default","border-tremor-border","dark:border-dark-tremor-border",e)},o.createElement("p",{className:(0,n.q)("text-tremor-content text-tremor-default","dark:text-dark-tremor-content")},r))}},32644:function(t,e,r){"use strict";r.d(e,{FB:function(){return i},i4:function(){return o},me:function(){return n},vZ:function(){return function t(e,r){if(e===r)return!0;if("object"!=typeof e||"object"!=typeof r||null===e||null===r)return!1;let n=Object.keys(e),o=Object.keys(r);if(n.length!==o.length)return!1;for(let i of n)if(!o.includes(i)||!t(e[i],r[i]))return!1;return!0}}});let n=(t,e)=>{let r=new Map;return t.forEach((t,n)=>{r.set(t,e[n%e.length])}),r},o=(t,e,r)=>[t?"auto":null!=e?e:0,null!=r?r:"auto"];function i(t,e){let r=[];for(let n of t)if(Object.prototype.hasOwnProperty.call(n,e)&&(r.push(n[e]),r.length>1))return!1;return!0}},49804:function(t,e,r){"use strict";r.d(e,{Z:function(){return l}});var n=r(5853),o=r(13241),i=r(1153),a=r(2265),u=r(9496);let c=(0,i.fn)("Col"),l=a.forwardRef((t,e)=>{let{numColSpan:r=1,numColSpanSm:i,numColSpanMd:l,numColSpanLg:s,children:f,className:p}=t,h=(0,n._T)(t,["numColSpan","numColSpanSm","numColSpanMd","numColSpanLg","children","className"]),d=(t,e)=>t&&Object.keys(e).includes(String(t))?e[t]:"";return a.createElement("div",Object.assign({ref:e,className:(0,o.q)(c("root"),(()=>{let t=d(r,u.PT),e=d(i,u.SP),n=d(l,u.VS),a=d(s,u._w);return(0,o.q)(t,e,n,a)})(),p)},h),f)});l.displayName="Col"},97765:function(t,e,r){"use strict";r.d(e,{Z:function(){return c}});var n=r(5853),o=r(26898),i=r(13241),a=r(1153),u=r(2265);let c=u.forwardRef((t,e)=>{let{color:r,children:c,className:l}=t,s=(0,n._T)(t,["color","children","className"]);return u.createElement("p",Object.assign({ref:e,className:(0,i.q)(r?(0,a.bM)(r,o.K.lightText).textColor:"text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis",l)},s),c)});c.displayName="Subtitle"},61134:function(t,e,r){var n;!function(o){"use strict";var i,a={precision:20,rounding:4,toExpNeg:-7,toExpPos:21,LN10:"2.302585092994045684017991454684364207601101488628772976033327900967572609677352480235997205089598298341967784042286"},u=!0,c="[DecimalError] ",l=c+"Invalid argument: ",s=c+"Exponent out of range: ",f=Math.floor,p=Math.pow,h=/^(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,d=f(1286742750677284.5),y={};function v(t,e){var r,n,o,i,a,c,l,s,f=t.constructor,p=f.precision;if(!t.s||!e.s)return e.s||(e=new f(t)),u?E(e,p):e;if(l=t.d,s=e.d,a=t.e,o=e.e,l=l.slice(),i=a-o){for(i<0?(n=l,i=-i,c=s.length):(n=s,o=a,c=l.length),i>(c=(a=Math.ceil(p/7))>c?a+1:c+1)&&(i=c,n.length=1),n.reverse();i--;)n.push(0);n.reverse()}for((c=l.length)-(i=s.length)<0&&(i=c,n=s,s=l,l=n),r=0;i;)r=(l[--i]=l[i]+s[i]+r)/1e7|0,l[i]%=1e7;for(r&&(l.unshift(r),++o),c=l.length;0==l[--c];)l.pop();return e.d=l,e.e=o,u?E(e,p):e}function m(t,e,r){if(t!==~~t||tr)throw Error(l+t)}function b(t){var e,r,n,o=t.length-1,i="",a=t[0];if(o>0){for(i+=a,e=1;et.e^this.s<0?1:-1;for(e=0,r=(n=this.d.length)<(o=t.d.length)?n:o;et.d[e]^this.s<0?1:-1;return n===o?0:n>o^this.s<0?1:-1},y.decimalPlaces=y.dp=function(){var t=this.d.length-1,e=(t-this.e)*7;if(t=this.d[t])for(;t%10==0;t/=10)e--;return e<0?0:e},y.dividedBy=y.div=function(t){return g(this,new this.constructor(t))},y.dividedToIntegerBy=y.idiv=function(t){var e=this.constructor;return E(g(this,new e(t),0,1),e.precision)},y.equals=y.eq=function(t){return!this.cmp(t)},y.exponent=function(){return w(this)},y.greaterThan=y.gt=function(t){return this.cmp(t)>0},y.greaterThanOrEqualTo=y.gte=function(t){return this.cmp(t)>=0},y.isInteger=y.isint=function(){return this.e>this.d.length-2},y.isNegative=y.isneg=function(){return this.s<0},y.isPositive=y.ispos=function(){return this.s>0},y.isZero=function(){return 0===this.s},y.lessThan=y.lt=function(t){return 0>this.cmp(t)},y.lessThanOrEqualTo=y.lte=function(t){return 1>this.cmp(t)},y.logarithm=y.log=function(t){var e,r=this.constructor,n=r.precision,o=n+5;if(void 0===t)t=new r(10);else if((t=new r(t)).s<1||t.eq(i))throw Error(c+"NaN");if(this.s<1)throw Error(c+(this.s?"NaN":"-Infinity"));return this.eq(i)?new r(0):(u=!1,e=g(S(this,o),S(t,o),o),u=!0,E(e,n))},y.minus=y.sub=function(t){return t=new this.constructor(t),this.s==t.s?k(this,t):v(this,(t.s=-t.s,t))},y.modulo=y.mod=function(t){var e,r=this.constructor,n=r.precision;if(!(t=new r(t)).s)throw Error(c+"NaN");return this.s?(u=!1,e=g(this,t,0,1).times(t),u=!0,this.minus(e)):E(new r(this),n)},y.naturalExponential=y.exp=function(){return x(this)},y.naturalLogarithm=y.ln=function(){return S(this)},y.negated=y.neg=function(){var t=new this.constructor(this);return t.s=-t.s||0,t},y.plus=y.add=function(t){return t=new this.constructor(t),this.s==t.s?v(this,t):k(this,(t.s=-t.s,t))},y.precision=y.sd=function(t){var e,r,n;if(void 0!==t&&!!t!==t&&1!==t&&0!==t)throw Error(l+t);if(e=w(this)+1,r=7*(n=this.d.length-1)+1,n=this.d[n]){for(;n%10==0;n/=10)r--;for(n=this.d[0];n>=10;n/=10)r++}return t&&e>r?e:r},y.squareRoot=y.sqrt=function(){var t,e,r,n,o,i,a,l=this.constructor;if(this.s<1){if(!this.s)return new l(0);throw Error(c+"NaN")}for(t=w(this),u=!1,0==(o=Math.sqrt(+this))||o==1/0?(((e=b(this.d)).length+t)%2==0&&(e+="0"),o=Math.sqrt(e),t=f((t+1)/2)-(t<0||t%2),n=new l(e=o==1/0?"5e"+t:(e=o.toExponential()).slice(0,e.indexOf("e")+1)+t)):n=new l(o.toString()),o=a=(r=l.precision)+3;;)if(n=(i=n).plus(g(this,i,a+2)).times(.5),b(i.d).slice(0,a)===(e=b(n.d)).slice(0,a)){if(e=e.slice(a-3,a+1),o==a&&"4999"==e){if(E(i,r+1,0),i.times(i).eq(this)){n=i;break}}else if("9999"!=e)break;a+=4}return u=!0,E(n,r)},y.times=y.mul=function(t){var e,r,n,o,i,a,c,l,s,f=this.constructor,p=this.d,h=(t=new f(t)).d;if(!this.s||!t.s)return new f(0);for(t.s*=this.s,r=this.e+t.e,(l=p.length)<(s=h.length)&&(i=p,p=h,h=i,a=l,l=s,s=a),i=[],n=a=l+s;n--;)i.push(0);for(n=s;--n>=0;){for(e=0,o=l+n;o>n;)c=i[o]+h[n]*p[o-n-1]+e,i[o--]=c%1e7|0,e=c/1e7|0;i[o]=(i[o]+e)%1e7|0}for(;!i[--a];)i.pop();return e?++r:i.shift(),t.d=i,t.e=r,u?E(t,f.precision):t},y.toDecimalPlaces=y.todp=function(t,e){var r=this,n=r.constructor;return(r=new n(r),void 0===t)?r:(m(t,0,1e9),void 0===e?e=n.rounding:m(e,0,8),E(r,t+w(r)+1,e))},y.toExponential=function(t,e){var r,n=this,o=n.constructor;return void 0===t?r=A(n,!0):(m(t,0,1e9),void 0===e?e=o.rounding:m(e,0,8),r=A(n=E(new o(n),t+1,e),!0,t+1)),r},y.toFixed=function(t,e){var r,n,o=this.constructor;return void 0===t?A(this):(m(t,0,1e9),void 0===e?e=o.rounding:m(e,0,8),r=A((n=E(new o(this),t+w(this)+1,e)).abs(),!1,t+w(n)+1),this.isneg()&&!this.isZero()?"-"+r:r)},y.toInteger=y.toint=function(){var t=this.constructor;return E(new t(this),w(this)+1,t.rounding)},y.toNumber=function(){return+this},y.toPower=y.pow=function(t){var e,r,n,o,a,l,s=this,p=s.constructor,h=+(t=new p(t));if(!t.s)return new p(i);if(!(s=new p(s)).s){if(t.s<1)throw Error(c+"Infinity");return s}if(s.eq(i))return s;if(n=p.precision,t.eq(i))return E(s,n);if(l=(e=t.e)>=(r=t.d.length-1),a=s.s,l){if((r=h<0?-h:h)<=9007199254740991){for(o=new p(i),e=Math.ceil(n/7+4),u=!1;r%2&&M((o=o.times(s)).d,e),0!==(r=f(r/2));)M((s=s.times(s)).d,e);return u=!0,t.s<0?new p(i).div(o):E(o,n)}}else if(a<0)throw Error(c+"NaN");return a=a<0&&1&t.d[Math.max(e,r)]?-1:1,s.s=1,u=!1,o=t.times(S(s,n+12)),u=!0,(o=x(o)).s=a,o},y.toPrecision=function(t,e){var r,n,o=this,i=o.constructor;return void 0===t?(r=w(o),n=A(o,r<=i.toExpNeg||r>=i.toExpPos)):(m(t,1,1e9),void 0===e?e=i.rounding:m(e,0,8),r=w(o=E(new i(o),t,e)),n=A(o,t<=r||r<=i.toExpNeg,t)),n},y.toSignificantDigits=y.tosd=function(t,e){var r=this.constructor;return void 0===t?(t=r.precision,e=r.rounding):(m(t,1,1e9),void 0===e?e=r.rounding:m(e,0,8)),E(new r(this),t,e)},y.toString=y.valueOf=y.val=y.toJSON=function(){var t=w(this),e=this.constructor;return A(this,t<=e.toExpNeg||t>=e.toExpPos)};var g=function(){function t(t,e){var r,n=0,o=t.length;for(t=t.slice();o--;)r=t[o]*e+n,t[o]=r%1e7|0,n=r/1e7|0;return n&&t.unshift(n),t}function e(t,e,r,n){var o,i;if(r!=n)i=r>n?1:-1;else for(o=i=0;oe[o]?1:-1;break}return i}function r(t,e,r){for(var n=0;r--;)t[r]-=n,n=t[r]1;)t.shift()}return function(n,o,i,a){var u,l,s,f,p,h,d,y,v,m,b,g,x,O,j,S,P,k,A=n.constructor,M=n.s==o.s?1:-1,_=n.d,T=o.d;if(!n.s)return new A(n);if(!o.s)throw Error(c+"Division by zero");for(s=0,l=n.e-o.e,P=T.length,j=_.length,y=(d=new A(M)).d=[];T[s]==(_[s]||0);)++s;if(T[s]>(_[s]||0)&&--l,(g=null==i?i=A.precision:a?i+(w(n)-w(o))+1:i)<0)return new A(0);if(g=g/7+2|0,s=0,1==P)for(f=0,T=T[0],g++;(s1&&(T=t(T,f),_=t(_,f),P=T.length,j=_.length),O=P,m=(v=_.slice(0,P)).length;m=1e7/2&&++S;do f=0,(u=e(T,v,P,m))<0?(b=v[0],P!=m&&(b=1e7*b+(v[1]||0)),(f=b/S|0)>1?(f>=1e7&&(f=1e7-1),h=(p=t(T,f)).length,m=v.length,1==(u=e(p,v,h,m))&&(f--,r(p,P16)throw Error(s+w(t));if(!t.s)return new h(i);for(null==e?(u=!1,c=d):c=e,a=new h(.03125);t.abs().gte(.1);)t=t.times(a),f+=5;for(c+=Math.log(p(2,f))/Math.LN10*2+5|0,r=n=o=new h(i),h.precision=c;;){if(n=E(n.times(t),c),r=r.times(++l),b((a=o.plus(g(n,r,c))).d).slice(0,c)===b(o.d).slice(0,c)){for(;f--;)o=E(o.times(o),c);return h.precision=d,null==e?(u=!0,E(o,d)):o}o=a}}function w(t){for(var e=7*t.e,r=t.d[0];r>=10;r/=10)e++;return e}function O(t,e,r){if(e>t.LN10.sd())throw u=!0,r&&(t.precision=r),Error(c+"LN10 precision limit exceeded");return E(new t(t.LN10),e)}function j(t){for(var e="";t--;)e+="0";return e}function S(t,e){var r,n,o,a,l,s,f,p,h,d=1,y=t,v=y.d,m=y.constructor,x=m.precision;if(y.s<1)throw Error(c+(y.s?"NaN":"-Infinity"));if(y.eq(i))return new m(0);if(null==e?(u=!1,p=x):p=e,y.eq(10))return null==e&&(u=!0),O(m,p);if(p+=10,m.precision=p,n=(r=b(v)).charAt(0),!(15e14>Math.abs(a=w(y))))return f=O(m,p+2,x).times(a+""),y=S(new m(n+"."+r.slice(1)),p-10).plus(f),m.precision=x,null==e?(u=!0,E(y,x)):y;for(;n<7&&1!=n||1==n&&r.charAt(1)>3;)n=(r=b((y=y.times(t)).d)).charAt(0),d++;for(a=w(y),n>1?(y=new m("0."+r),a++):y=new m(n+"."+r.slice(1)),s=l=y=g(y.minus(i),y.plus(i),p),h=E(y.times(y),p),o=3;;){if(l=E(l.times(h),p),b((f=s.plus(g(l,new m(o),p))).d).slice(0,p)===b(s.d).slice(0,p))return s=s.times(2),0!==a&&(s=s.plus(O(m,p+2,x).times(a+""))),s=g(s,new m(d),p),m.precision=x,null==e?(u=!0,E(s,x)):s;s=f,o+=2}}function P(t,e){var r,n,o;for((r=e.indexOf("."))>-1&&(e=e.replace(".","")),(n=e.search(/e/i))>0?(r<0&&(r=n),r+=+e.slice(n+1),e=e.substring(0,n)):r<0&&(r=e.length),n=0;48===e.charCodeAt(n);)++n;for(o=e.length;48===e.charCodeAt(o-1);)--o;if(e=e.slice(n,o)){if(o-=n,r=r-n-1,t.e=f(r/7),t.d=[],n=(r+1)%7,r<0&&(n+=7),nd||t.e<-d))throw Error(s+r)}else t.s=0,t.e=0,t.d=[0];return t}function E(t,e,r){var n,o,i,a,c,l,h,y,v=t.d;for(a=1,i=v[0];i>=10;i/=10)a++;if((n=e-a)<0)n+=7,o=e,h=v[y=0];else{if((y=Math.ceil((n+1)/7))>=(i=v.length))return t;for(a=1,h=i=v[y];i>=10;i/=10)a++;n%=7,o=n-7+a}if(void 0!==r&&(c=h/(i=p(10,a-o-1))%10|0,l=e<0||void 0!==v[y+1]||h%i,l=r<4?(c||l)&&(0==r||r==(t.s<0?3:2)):c>5||5==c&&(4==r||l||6==r&&(n>0?o>0?h/p(10,a-o):0:v[y-1])%10&1||r==(t.s<0?8:7))),e<1||!v[0])return l?(i=w(t),v.length=1,e=e-i-1,v[0]=p(10,(7-e%7)%7),t.e=f(-e/7)||0):(v.length=1,v[0]=t.e=t.s=0),t;if(0==n?(v.length=y,i=1,y--):(v.length=y+1,i=p(10,7-n),v[y]=o>0?(h/p(10,a-o)%p(10,o)|0)*i:0),l)for(;;){if(0==y){1e7==(v[0]+=i)&&(v[0]=1,++t.e);break}if(v[y]+=i,1e7!=v[y])break;v[y--]=0,i=1}for(n=v.length;0===v[--n];)v.pop();if(u&&(t.e>d||t.e<-d))throw Error(s+w(t));return t}function k(t,e){var r,n,o,i,a,c,l,s,f,p,h=t.constructor,d=h.precision;if(!t.s||!e.s)return e.s?e.s=-e.s:e=new h(t),u?E(e,d):e;if(l=t.d,p=e.d,n=e.e,s=t.e,l=l.slice(),a=s-n){for((f=a<0)?(r=l,a=-a,c=p.length):(r=p,n=s,c=l.length),a>(o=Math.max(Math.ceil(d/7),c)+2)&&(a=o,r.length=1),r.reverse(),o=a;o--;)r.push(0);r.reverse()}else{for((f=(o=l.length)<(c=p.length))&&(c=o),o=0;o0;--o)l[c++]=0;for(o=p.length;o>a;){if(l[--o]0?i=i.charAt(0)+"."+i.slice(1)+j(n):a>1&&(i=i.charAt(0)+"."+i.slice(1)),i=i+(o<0?"e":"e+")+o):o<0?(i="0."+j(-o-1)+i,r&&(n=r-a)>0&&(i+=j(n))):o>=a?(i+=j(o+1-a),r&&(n=r-o-1)>0&&(i=i+"."+j(n))):((n=o+1)0&&(o+1===a&&(i+="."),i+=j(n))),t.s<0?"-"+i:i}function M(t,e){if(t.length>e)return t.length=e,!0}function _(t){if(!t||"object"!=typeof t)throw Error(c+"Object expected");var e,r,n,o=["precision",1,1e9,"rounding",0,8,"toExpNeg",-1/0,0,"toExpPos",0,1/0];for(e=0;e=o[e+1]&&n<=o[e+2])this[r]=n;else throw Error(l+r+": "+n)}if(void 0!==(n=t[r="LN10"])){if(n==Math.LN10)this[r]=new this(n);else throw Error(l+r+": "+n)}return this}(a=function t(e){var r,n,o;function i(t){if(!(this instanceof i))return new i(t);if(this.constructor=i,t instanceof i){this.s=t.s,this.e=t.e,this.d=(t=t.d)?t.slice():t;return}if("number"==typeof t){if(0*t!=0)throw Error(l+t);if(t>0)this.s=1;else if(t<0)t=-t,this.s=-1;else{this.s=0,this.e=0,this.d=[0];return}if(t===~~t&&t<1e7){this.e=0,this.d=[t];return}return P(this,t.toString())}if("string"!=typeof t)throw Error(l+t);if(45===t.charCodeAt(0)?(t=t.slice(1),this.s=-1):this.s=1,h.test(t))P(this,t);else throw Error(l+t)}if(i.prototype=y,i.ROUND_UP=0,i.ROUND_DOWN=1,i.ROUND_CEIL=2,i.ROUND_FLOOR=3,i.ROUND_HALF_UP=4,i.ROUND_HALF_DOWN=5,i.ROUND_HALF_EVEN=6,i.ROUND_HALF_CEIL=7,i.ROUND_HALF_FLOOR=8,i.clone=t,i.config=i.set=_,void 0===e&&(e={}),e)for(r=0,o=["precision","rounding","toExpNeg","toExpPos","LN10"];r-1}},56883:function(t){t.exports=function(t,e,r){for(var n=-1,o=null==t?0:t.length;++n0&&i(s)?r>1?t(s,r-1,i,a,u):n(u,s):a||(u[u.length]=s)}return u}},63321:function(t,e,r){var n=r(33023)();t.exports=n},98060:function(t,e,r){var n=r(63321),o=r(43228);t.exports=function(t,e){return t&&n(t,e,o)}},92167:function(t,e,r){var n=r(67906),o=r(70235);t.exports=function(t,e){e=n(e,t);for(var r=0,i=e.length;null!=t&&re}},93012:function(t){t.exports=function(t,e){return null!=t&&e in Object(t)}},47909:function(t,e,r){var n=r(8235),o=r(31953),i=r(35281);t.exports=function(t,e,r){return e==e?i(t,e,r):n(t,o,r)}},90370:function(t,e,r){var n=r(54506),o=r(10303);t.exports=function(t){return o(t)&&"[object Arguments]"==n(t)}},56318:function(t,e,r){var n=r(6791),o=r(10303);t.exports=function t(e,r,i,a,u){return e===r||(null!=e&&null!=r&&(o(e)||o(r))?n(e,r,i,a,t,u):e!=e&&r!=r)}},6791:function(t,e,r){var n=r(85885),o=r(97638),i=r(88030),a=r(64974),u=r(81690),c=r(25614),l=r(98051),s=r(9792),f="[object Arguments]",p="[object Array]",h="[object Object]",d=Object.prototype.hasOwnProperty;t.exports=function(t,e,r,y,v,m){var b=c(t),g=c(e),x=b?p:u(t),w=g?p:u(e);x=x==f?h:x,w=w==f?h:w;var O=x==h,j=w==h,S=x==w;if(S&&l(t)){if(!l(e))return!1;b=!0,O=!1}if(S&&!O)return m||(m=new n),b||s(t)?o(t,e,r,y,v,m):i(t,e,x,r,y,v,m);if(!(1&r)){var P=O&&d.call(t,"__wrapped__"),E=j&&d.call(e,"__wrapped__");if(P||E){var k=P?t.value():t,A=E?e.value():e;return m||(m=new n),v(k,A,r,y,m)}}return!!S&&(m||(m=new n),a(t,e,r,y,v,m))}},62538:function(t,e,r){var n=r(85885),o=r(56318);t.exports=function(t,e,r,i){var a=r.length,u=a,c=!i;if(null==t)return!u;for(t=Object(t);a--;){var l=r[a];if(c&&l[2]?l[1]!==t[l[0]]:!(l[0]in t))return!1}for(;++ao?0:o+e),(r=r>o?o:r)<0&&(r+=o),o=e>r?0:r-e>>>0,e>>>=0;for(var i=Array(o);++n=200){var y=e?null:u(t);if(y)return c(y);p=!1,s=a,d=new n}else d=e?[]:h;t:for(;++l=o?t:n(t,e,r)}},1536:function(t,e,r){var n=r(78371);t.exports=function(t,e){if(t!==e){var r=void 0!==t,o=null===t,i=t==t,a=n(t),u=void 0!==e,c=null===e,l=e==e,s=n(e);if(!c&&!s&&!a&&t>e||a&&u&&l&&!c&&!s||o&&u&&l||!r&&l||!i)return 1;if(!o&&!a&&!s&&t=c)return l;return l*("desc"==r[o]?-1:1)}}return t.index-e.index}},92077:function(t,e,r){var n=r(74288)["__core-js_shared__"];t.exports=n},97930:function(t,e,r){var n=r(5629);t.exports=function(t,e){return function(r,o){if(null==r)return r;if(!n(r))return t(r,o);for(var i=r.length,a=e?i:-1,u=Object(r);(e?a--:++a-1?u[c?e[l]:l]:void 0}}},35464:function(t,e,r){var n=r(19608),o=r(49639),i=r(175);t.exports=function(t){return function(e,r,a){return a&&"number"!=typeof a&&o(e,r,a)&&(r=a=void 0),e=i(e),void 0===r?(r=e,e=0):r=i(r),a=void 0===a?es))return!1;var p=c.get(t),h=c.get(e);if(p&&h)return p==e&&h==t;var d=-1,y=!0,v=2&r?new n:void 0;for(c.set(t,e),c.set(e,t);++d-1&&t%1==0&&t-1}},13368:function(t,e,r){var n=r(24457);t.exports=function(t,e){var r=this.__data__,o=n(r,t);return o<0?(++this.size,r.push([t,e])):r[o][1]=e,this}},38764:function(t,e,r){var n=r(9855),o=r(99078),i=r(88675);t.exports=function(){this.size=0,this.__data__={hash:new n,map:new(i||o),string:new n}}},78615:function(t,e,r){var n=r(1507);t.exports=function(t){var e=n(this,t).delete(t);return this.size-=e?1:0,e}},83391:function(t,e,r){var n=r(1507);t.exports=function(t){return n(this,t).get(t)}},53483:function(t,e,r){var n=r(1507);t.exports=function(t){return n(this,t).has(t)}},74724:function(t,e,r){var n=r(1507);t.exports=function(t,e){var r=n(this,t),o=r.size;return r.set(t,e),this.size+=r.size==o?0:1,this}},22523:function(t){t.exports=function(t){var e=-1,r=Array(t.size);return t.forEach(function(t,n){r[++e]=[n,t]}),r}},47073:function(t){t.exports=function(t,e){return function(r){return null!=r&&r[t]===e&&(void 0!==e||t in Object(r))}}},23787:function(t,e,r){var n=r(50967);t.exports=function(t){var e=n(t,function(t){return 500===r.size&&r.clear(),t}),r=e.cache;return e}},20453:function(t,e,r){var n=r(39866)(Object,"create");t.exports=n},77184:function(t,e,r){var n=r(45070)(Object.keys,Object);t.exports=n},39931:function(t,e,r){t=r.nmd(t);var n=r(17071),o=e&&!e.nodeType&&e,i=o&&t&&!t.nodeType&&t,a=i&&i.exports===o&&n.process,u=function(){try{var t=i&&i.require&&i.require("util").types;if(t)return t;return a&&a.binding&&a.binding("util")}catch(t){}}();t.exports=u},45070:function(t){t.exports=function(t,e){return function(r){return t(e(r))}}},49478:function(t,e,r){var n=r(60493),o=Math.max;t.exports=function(t,e,r){return e=o(void 0===e?t.length-1:e,0),function(){for(var i=arguments,a=-1,u=o(i.length-e,0),c=Array(u);++a0){if(++r>=800)return arguments[0]}else r=0;return t.apply(void 0,arguments)}}},84092:function(t,e,r){var n=r(99078);t.exports=function(){this.__data__=new n,this.size=0}},31663:function(t){t.exports=function(t){var e=this.__data__,r=e.delete(t);return this.size=e.size,r}},69135:function(t){t.exports=function(t){return this.__data__.get(t)}},39552:function(t){t.exports=function(t){return this.__data__.has(t)}},63960:function(t,e,r){var n=r(99078),o=r(88675),i=r(76219);t.exports=function(t,e){var r=this.__data__;if(r instanceof n){var a=r.__data__;if(!o||a.length<199)return a.push([t,e]),this.size=++r.size,this;r=this.__data__=new i(a)}return r.set(t,e),this.size=r.size,this}},35281:function(t){t.exports=function(t,e,r){for(var n=r-1,o=t.length;++n-1&&t%1==0&&t<=9007199254740991}},82559:function(t,e,r){var n=r(22345);t.exports=function(t){return n(t)&&t!=+t}},77571:function(t){t.exports=function(t){return null==t}},22345:function(t,e,r){var n=r(54506),o=r(10303);t.exports=function(t){return"number"==typeof t||o(t)&&"[object Number]"==n(t)}},90231:function(t,e,r){var n=r(54506),o=r(62602),i=r(10303),a=Object.prototype,u=Function.prototype.toString,c=a.hasOwnProperty,l=u.call(Object);t.exports=function(t){if(!i(t)||"[object Object]"!=n(t))return!1;var e=o(t);if(null===e)return!0;var r=c.call(e,"constructor")&&e.constructor;return"function"==typeof r&&r instanceof r&&u.call(r)==l}},42715:function(t,e,r){var n=r(54506),o=r(25614),i=r(10303);t.exports=function(t){return"string"==typeof t||!o(t)&&i(t)&&"[object String]"==n(t)}},9792:function(t,e,r){var n=r(59332),o=r(23305),i=r(39931),a=i&&i.isTypedArray,u=a?o(a):n;t.exports=u},43228:function(t,e,r){var n=r(28579),o=r(4578),i=r(5629);t.exports=function(t){return i(t)?n(t):o(t)}},86185:function(t){t.exports=function(t){var e=null==t?0:t.length;return e?t[e-1]:void 0}},89238:function(t,e,r){var n=r(73819),o=r(88157),i=r(24240),a=r(25614);t.exports=function(t,e){return(a(t)?n:i)(t,o(e,3))}},41443:function(t,e,r){var n=r(83023),o=r(98060),i=r(88157);t.exports=function(t,e){var r={};return e=i(e,3),o(t,function(t,o,i){n(r,o,e(t,o,i))}),r}},95645:function(t,e,r){var n=r(67646),o=r(58905),i=r(79586);t.exports=function(t){return t&&t.length?n(t,i,o):void 0}},50967:function(t,e,r){var n=r(76219);function o(t,e){if("function"!=typeof t||null!=e&&"function"!=typeof e)throw TypeError("Expected a function");var r=function(){var n=arguments,o=e?e.apply(this,n):n[0],i=r.cache;if(i.has(o))return i.get(o);var a=t.apply(this,n);return r.cache=i.set(o,a)||i,a};return r.cache=new(o.Cache||n),r}o.Cache=n,t.exports=o},99008:function(t,e,r){var n=r(67646),o=r(20121),i=r(79586);t.exports=function(t){return t&&t.length?n(t,i,o):void 0}},93810:function(t){t.exports=function(){}},22350:function(t,e,r){var n=r(18155),o=r(73584),i=r(67352),a=r(70235);t.exports=function(t){return i(t)?n(a(t)):o(t)}},99676:function(t,e,r){var n=r(35464)();t.exports=n},33645:function(t,e,r){var n=r(25253),o=r(88157),i=r(12327),a=r(25614),u=r(49639);t.exports=function(t,e,r){var c=a(t)?n:i;return r&&u(t,e,r)&&(e=void 0),c(t,o(e,3))}},34935:function(t,e,r){var n=r(72569),o=r(84046),i=r(44843),a=r(49639),u=i(function(t,e){if(null==t)return[];var r=e.length;return r>1&&a(t,e[0],e[1])?e=[]:r>2&&a(e[0],e[1],e[2])&&(e=[e[0]]),o(t,n(e,1),[])});t.exports=u},55716:function(t){t.exports=function(){return[]}},7406:function(t){t.exports=function(){return!1}},37065:function(t,e,r){var n=r(7310),o=r(28302);t.exports=function(t,e,r){var i=!0,a=!0;if("function"!=typeof t)throw TypeError("Expected a function");return o(r)&&(i="leading"in r?!!r.leading:i,a="trailing"in r?!!r.trailing:a),n(t,e,{leading:i,maxWait:e,trailing:a})}},175:function(t,e,r){var n=r(6660),o=1/0;t.exports=function(t){return t?(t=n(t))===o||t===-o?(t<0?-1:1)*17976931348623157e292:t==t?t:0:0===t?t:0}},85759:function(t,e,r){var n=r(175);t.exports=function(t){var e=n(t),r=e%1;return e==e?r?e-r:e:0}},3641:function(t,e,r){var n=r(65020);t.exports=function(t){return null==t?"":n(t)}},47230:function(t,e,r){var n=r(88157),o=r(13826);t.exports=function(t,e){return t&&t.length?o(t,n(e,2)):[]}},75551:function(t,e,r){var n=r(80675)("toUpperCase");t.exports=n},48049:function(t,e,r){"use strict";var n=r(14397);function o(){}function i(){}i.resetWarningCache=o,t.exports=function(){function t(t,e,r,o,i,a){if(a!==n){var u=Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw u.name="Invariant Violation",u}}function e(){return t}t.isRequired=t;var r={array:t,bigint:t,bool:t,func:t,number:t,object:t,string:t,symbol:t,any:t,arrayOf:e,element:t,elementType:t,instanceOf:e,node:t,objectOf:e,oneOf:e,oneOfType:e,shape:e,exact:e,checkPropTypes:i,resetWarningCache:o};return r.PropTypes=r,r}},40718:function(t,e,r){t.exports=r(48049)()},14397:function(t){"use strict";t.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},84735:function(t,e,r){"use strict";r.d(e,{ZP:function(){return tS}});var n=r(2265),o=r(40718),i=r.n(o),a=Object.getOwnPropertyNames,u=Object.getOwnPropertySymbols,c=Object.prototype.hasOwnProperty;function l(t,e){return function(r,n,o){return t(r,n,o)&&e(r,n,o)}}function s(t){return function(e,r,n){if(!e||!r||"object"!=typeof e||"object"!=typeof r)return t(e,r,n);var o=n.cache,i=o.get(e),a=o.get(r);if(i&&a)return i===r&&a===e;o.set(e,r),o.set(r,e);var u=t(e,r,n);return o.delete(e),o.delete(r),u}}function f(t){return a(t).concat(u(t))}var p=Object.hasOwn||function(t,e){return c.call(t,e)};function h(t,e){return t===e||!t&&!e&&t!=t&&e!=e}var d=Object.getOwnPropertyDescriptor,y=Object.keys;function v(t,e,r){var n=t.length;if(e.length!==n)return!1;for(;n-- >0;)if(!r.equals(t[n],e[n],n,n,t,e,r))return!1;return!0}function m(t,e){return h(t.getTime(),e.getTime())}function b(t,e){return t.name===e.name&&t.message===e.message&&t.cause===e.cause&&t.stack===e.stack}function g(t,e){return t===e}function x(t,e,r){var n,o,i=t.size;if(i!==e.size)return!1;if(!i)return!0;for(var a=Array(i),u=t.entries(),c=0;(n=u.next())&&!n.done;){for(var l=e.entries(),s=!1,f=0;(o=l.next())&&!o.done;){if(a[f]){f++;continue}var p=n.value,h=o.value;if(r.equals(p[0],h[0],c,f,t,e,r)&&r.equals(p[1],h[1],p[0],h[0],t,e,r)){s=a[f]=!0;break}f++}if(!s)return!1;c++}return!0}function w(t,e,r){var n=y(t),o=n.length;if(y(e).length!==o)return!1;for(;o-- >0;)if(!A(t,e,r,n[o]))return!1;return!0}function O(t,e,r){var n,o,i,a=f(t),u=a.length;if(f(e).length!==u)return!1;for(;u-- >0;)if(!A(t,e,r,n=a[u])||(o=d(t,n),i=d(e,n),(o||i)&&(!o||!i||o.configurable!==i.configurable||o.enumerable!==i.enumerable||o.writable!==i.writable)))return!1;return!0}function j(t,e){return h(t.valueOf(),e.valueOf())}function S(t,e){return t.source===e.source&&t.flags===e.flags}function P(t,e,r){var n,o,i=t.size;if(i!==e.size)return!1;if(!i)return!0;for(var a=Array(i),u=t.values();(n=u.next())&&!n.done;){for(var c=e.values(),l=!1,s=0;(o=c.next())&&!o.done;){if(!a[s]&&r.equals(n.value,o.value,n.value,o.value,t,e,r)){l=a[s]=!0;break}s++}if(!l)return!1}return!0}function E(t,e){var r=t.length;if(e.length!==r)return!1;for(;r-- >0;)if(t[r]!==e[r])return!1;return!0}function k(t,e){return t.hostname===e.hostname&&t.pathname===e.pathname&&t.protocol===e.protocol&&t.port===e.port&&t.hash===e.hash&&t.username===e.username&&t.password===e.password}function A(t,e,r,n){return("_owner"===n||"__o"===n||"__v"===n)&&(!!t.$$typeof||!!e.$$typeof)||p(e,n)&&r.equals(t[n],e[n],n,n,t,e,r)}var M=Array.isArray,_="undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView?ArrayBuffer.isView:null,T=Object.assign,C=Object.prototype.toString.call.bind(Object.prototype.toString),N=D();function D(t){void 0===t&&(t={});var e,r,n,o,i,a,u,c,f,p,d,y,A,N,D=t.circular,I=t.createInternalComparator,L=t.createState,B=t.strict,R=(r=(e=function(t){var e=t.circular,r=t.createCustomConfig,n=t.strict,o={areArraysEqual:n?O:v,areDatesEqual:m,areErrorsEqual:b,areFunctionsEqual:g,areMapsEqual:n?l(x,O):x,areNumbersEqual:h,areObjectsEqual:n?O:w,arePrimitiveWrappersEqual:j,areRegExpsEqual:S,areSetsEqual:n?l(P,O):P,areTypedArraysEqual:n?O:E,areUrlsEqual:k,unknownTagComparators:void 0};if(r&&(o=T({},o,r(o))),e){var i=s(o.areArraysEqual),a=s(o.areMapsEqual),u=s(o.areObjectsEqual),c=s(o.areSetsEqual);o=T({},o,{areArraysEqual:i,areMapsEqual:a,areObjectsEqual:u,areSetsEqual:c})}return o}(t)).areArraysEqual,n=e.areDatesEqual,o=e.areErrorsEqual,i=e.areFunctionsEqual,a=e.areMapsEqual,u=e.areNumbersEqual,c=e.areObjectsEqual,f=e.arePrimitiveWrappersEqual,p=e.areRegExpsEqual,d=e.areSetsEqual,y=e.areTypedArraysEqual,A=e.areUrlsEqual,N=e.unknownTagComparators,function(t,e,l){if(t===e)return!0;if(null==t||null==e)return!1;var s=typeof t;if(s!==typeof e)return!1;if("object"!==s)return"number"===s?u(t,e,l):"function"===s&&i(t,e,l);var h=t.constructor;if(h!==e.constructor)return!1;if(h===Object)return c(t,e,l);if(M(t))return r(t,e,l);if(null!=_&&_(t))return y(t,e,l);if(h===Date)return n(t,e,l);if(h===RegExp)return p(t,e,l);if(h===Map)return a(t,e,l);if(h===Set)return d(t,e,l);var v=C(t);if("[object Date]"===v)return n(t,e,l);if("[object RegExp]"===v)return p(t,e,l);if("[object Map]"===v)return a(t,e,l);if("[object Set]"===v)return d(t,e,l);if("[object Object]"===v)return"function"!=typeof t.then&&"function"!=typeof e.then&&c(t,e,l);if("[object URL]"===v)return A(t,e,l);if("[object Error]"===v)return o(t,e,l);if("[object Arguments]"===v)return c(t,e,l);if("[object Boolean]"===v||"[object Number]"===v||"[object String]"===v)return f(t,e,l);if(N){var m=N[v];if(!m){var b=null!=t?t[Symbol.toStringTag]:void 0;b&&(m=N[b])}if(m)return m(t,e,l)}return!1}),z=I?I(R):function(t,e,r,n,o,i,a){return R(t,e,a)};return function(t){var e=t.circular,r=t.comparator,n=t.createState,o=t.equals,i=t.strict;if(n)return function(t,a){var u=n(),c=u.cache;return r(t,a,{cache:void 0===c?e?new WeakMap:void 0:c,equals:o,meta:u.meta,strict:i})};if(e)return function(t,e){return r(t,e,{cache:new WeakMap,equals:o,meta:void 0,strict:i})};var a={cache:void 0,equals:o,meta:void 0,strict:i};return function(t,e){return r(t,e,a)}}({circular:void 0!==D&&D,comparator:R,createState:L,equals:z,strict:void 0!==B&&B})}function I(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,r=-1;requestAnimationFrame(function n(o){if(r<0&&(r=o),o-r>e)t(o),r=-1;else{var i;i=n,"undefined"!=typeof requestAnimationFrame&&requestAnimationFrame(i)}})}function L(t){return(L="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function B(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=Array(e);rt.length)&&(e=t.length);for(var r=0,n=Array(e);r=0&&t<=1}),"[configBezier]: arguments should be x1, y1, x2, y2 of [0, 1] instead received %s",n);var p=V(i,u),h=V(a,c),d=(t=i,e=u,function(r){var n;return G([].concat(function(t){if(Array.isArray(t))return H(t)}(n=X(t,e).map(function(t,e){return t*e}).slice(1))||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(n)||Y(n)||function(){throw TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}(),[0]),r)}),y=function(t){for(var e=t>1?1:t,r=e,n=0;n<8;++n){var o,i=p(r)-e,a=d(r);if(1e-4>Math.abs(i-e)||a<1e-4)break;r=(o=r-i/a)>1?1:o<0?0:o}return h(r)};return y.isStepper=!1,y},Q=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.stiff,r=void 0===e?100:e,n=t.damping,o=void 0===n?8:n,i=t.dt,a=void 0===i?17:i,u=function(t,e,n){var i=n+(-(t-e)*r-n*o)*a/1e3,u=n*a/1e3+t;return 1e-4>Math.abs(u-e)&&1e-4>Math.abs(i)?[e,0]:[u,i]};return u.isStepper=!0,u.dt=a,u},J=function(){for(var t=arguments.length,e=Array(t),r=0;rt.length)&&(e=t.length);for(var r=0,n=Array(e);rt.length)&&(e=t.length);for(var r=0,n=Array(e);r0?r[o-1]:n,p=l||Object.keys(c);if("function"==typeof u||"spring"===u)return[].concat(th(t),[e.runJSAnimation.bind(e,{from:f.style,to:c,duration:i,easing:u}),i]);var h=Z(p,i,u),d=tv(tv(tv({},f.style),c),{},{transition:h});return[].concat(th(t),[d,i,s]).filter($)},[a,Math.max(void 0===u?0:u,n)])),[t.onAnimationEnd]))}},{key:"runAnimation",value:function(t){if(!this.manager){var e,r,n;this.manager=(e=function(){return null},r=!1,n=function t(n){if(!r){if(Array.isArray(n)){if(!n.length)return;var o=function(t){if(Array.isArray(t))return t}(n)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(n)||function(t,e){if(t){if("string"==typeof t)return B(t,void 0);var r=Object.prototype.toString.call(t).slice(8,-1);if("Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return B(t,void 0)}}(n)||function(){throw TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}(),i=o[0],a=o.slice(1);if("number"==typeof i){I(t.bind(null,a),i);return}t(i),I(t.bind(null,a));return}"object"===L(n)&&e(n),"function"==typeof n&&n()}},{stop:function(){r=!0},start:function(t){r=!1,n(t)},subscribe:function(t){return e=t,function(){e=function(){return null}}}})}var o=t.begin,i=t.duration,a=t.attributeName,u=t.to,c=t.easing,l=t.onAnimationStart,s=t.onAnimationEnd,f=t.steps,p=t.children,h=this.manager;if(this.unSubscribe=h.subscribe(this.handleStyleChange),"function"==typeof c||"function"==typeof p||"spring"===c){this.runJSAnimation(t);return}if(f.length>1){this.runStepAnimation(t);return}var d=a?tm({},a,u):u,y=Z(Object.keys(d),i,c);h.start([l,o,tv(tv({},d),{},{transition:y}),i,s])}},{key:"render",value:function(){var t=this.props,e=t.children,r=(t.begin,t.duration),o=(t.attributeName,t.easing,t.isActive),i=(t.steps,t.from,t.to,t.canBegin,t.onAnimationEnd,t.shouldReAnimate,t.onAnimationReStart,function(t,e){if(null==t)return{};var r,n,o=function(t,e){if(null==t)return{};var r,n,o={},i=Object.keys(t);for(n=0;n=0||(o[r]=t[r]);return o}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(t,tp)),a=n.Children.count(e),u=this.state.style;if("function"==typeof e)return e(u);if(!o||0===a||r<=0)return e;var c=function(t){var e=t.props,r=e.style,o=e.className;return(0,n.cloneElement)(t,tv(tv({},i),{},{style:tv(tv({},void 0===r?{}:r),u),className:o}))};return 1===a?c(n.Children.only(e)):n.createElement("div",null,n.Children.map(e,function(t){return c(t)}))}}],function(t,e){for(var r=0;r=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(t,w),i=parseInt("".concat(r),10),a=parseInt("".concat(n),10),u=parseInt("".concat(e.height||o.height),10),c=parseInt("".concat(e.width||o.width),10);return P(P(P(P(P({},e),o),i?{x:i}:{}),a?{y:a}:{}),{},{height:u,width:c,name:e.name,radius:e.radius})}function k(t){return n.createElement(x.bn,j({shapeType:"rectangle",propTransformer:E,activeClassName:"recharts-active-bar"},t))}var A=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return function(r,n){if("number"==typeof t)return t;var o=(0,d.hj)(r)||(0,d.Rw)(r);return o?t(r,n):(o||(0,g.Z)(!1),e)}},M=["value","background"];function _(t){return(_="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function T(){return(T=Object.assign?Object.assign.bind():function(t){for(var e=1;e=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(e,M);if(!u)return null;var l=N(N(N(N(N({},c),{},{fill:"#eee"},u),a),(0,b.bw)(t.props,e,r)),{},{onAnimationStart:t.handleAnimationStart,onAnimationEnd:t.handleAnimationEnd,dataKey:o,index:r,className:"recharts-bar-background-rectangle"});return n.createElement(k,T({key:"background-bar-".concat(r),option:t.props.background,isActive:r===i},l))})}},{key:"renderErrorBar",value:function(t,e){if(this.props.isAnimationActive&&!this.state.isAnimationFinished)return null;var r=this.props,o=r.data,i=r.xAxis,a=r.yAxis,u=r.layout,c=r.children,l=(0,y.NN)(c,f.W);if(!l)return null;var p="vertical"===u?o[0].height/2:o[0].width/2,h=function(t,e){var r=Array.isArray(t.value)?t.value[1]:t.value;return{x:t.x,y:t.y,value:r,errorVal:(0,m.F$)(t,e)}};return n.createElement(s.m,{clipPath:t?"url(#clipPath-".concat(e,")"):null},l.map(function(t){return n.cloneElement(t,{key:"error-bar-".concat(e,"-").concat(t.props.dataKey),data:o,xAxis:i,yAxis:a,layout:u,offset:p,dataPointFormatter:h})}))}},{key:"render",value:function(){var t=this.props,e=t.hide,r=t.data,i=t.className,a=t.xAxis,u=t.yAxis,c=t.left,f=t.top,p=t.width,d=t.height,y=t.isAnimationActive,v=t.background,m=t.id;if(e||!r||!r.length)return null;var b=this.state.isAnimationFinished,g=(0,o.Z)("recharts-bar",i),x=a&&a.allowDataOverflow,w=u&&u.allowDataOverflow,O=x||w,j=l()(m)?this.id:m;return n.createElement(s.m,{className:g},x||w?n.createElement("defs",null,n.createElement("clipPath",{id:"clipPath-".concat(j)},n.createElement("rect",{x:x?c:c-p/2,y:w?f:f-d/2,width:x?p:2*p,height:w?d:2*d}))):null,n.createElement(s.m,{className:"recharts-bar-rectangles",clipPath:O?"url(#clipPath-".concat(j,")"):null},v?this.renderBackground():null,this.renderRectangles()),this.renderErrorBar(O,j),(!y||b)&&h.e.renderCallByParent(this.props,r))}}],r=[{key:"getDerivedStateFromProps",value:function(t,e){return t.animationId!==e.prevAnimationId?{prevAnimationId:t.animationId,curData:t.data,prevData:e.curData}:t.data!==e.curData?{curData:t.data}:null}}],e&&D(a.prototype,e),r&&D(a,r),Object.defineProperty(a,"prototype",{writable:!1}),a}(n.PureComponent);R(U,"displayName","Bar"),R(U,"defaultProps",{xAxisId:0,yAxisId:0,legendType:"rect",minPointSize:0,hide:!1,data:[],layout:"vertical",activeBar:!1,isAnimationActive:!v.x.isSsr,animationBegin:0,animationDuration:400,animationEasing:"ease"}),R(U,"getComposedData",function(t){var e=t.props,r=t.item,n=t.barPosition,o=t.bandSize,i=t.xAxis,a=t.yAxis,u=t.xAxisTicks,c=t.yAxisTicks,l=t.stackedData,s=t.dataStartIndex,f=t.displayedData,h=t.offset,v=(0,m.Bu)(n,r);if(!v)return null;var b=e.layout,g=r.type.defaultProps,x=void 0!==g?N(N({},g),r.props):r.props,w=x.dataKey,O=x.children,j=x.minPointSize,S="horizontal"===b?a:i,P=l?S.scale.domain():null,E=(0,m.Yj)({numericAxis:S}),k=(0,y.NN)(O,p.b),M=f.map(function(t,e){l?f=(0,m.Vv)(l[s+e],P):Array.isArray(f=(0,m.F$)(t,w))||(f=[E,f]);var n=A(j,U.defaultProps.minPointSize)(f[1],e);if("horizontal"===b){var f,p,h,y,g,x,O,S=[a.scale(f[0]),a.scale(f[1])],M=S[0],_=S[1];p=(0,m.Fy)({axis:i,ticks:u,bandSize:o,offset:v.offset,entry:t,index:e}),h=null!==(O=null!=_?_:M)&&void 0!==O?O:void 0,y=v.size;var T=M-_;if(g=Number.isNaN(T)?0:T,x={x:p,y:a.y,width:y,height:a.height},Math.abs(n)>0&&Math.abs(g)0&&Math.abs(y)=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}function P(t,e){for(var r=0;r0?this.props:d)),o<=0||a<=0||!y||!y.length)?null:n.createElement(s.m,{className:(0,c.Z)("recharts-cartesian-axis",l),ref:function(e){t.layerReference=e}},r&&this.renderAxisLine(),this.renderTicks(y,this.state.fontSize,this.state.letterSpacing),p._.renderCallByParent(this.props))}}],r=[{key:"renderTickItem",value:function(t,e,r){var o=(0,c.Z)(e.className,"recharts-cartesian-axis-tick-value");return n.isValidElement(t)?n.cloneElement(t,j(j({},e),{},{className:o})):i()(t)?t(j(j({},e),{},{className:o})):n.createElement(f.x,w({},e,{className:"recharts-cartesian-axis-tick-value"}),r)}}],e&&P(o.prototype,e),r&&P(o,r),Object.defineProperty(o,"prototype",{writable:!1}),o}(n.Component);M(T,"displayName","CartesianAxis"),M(T,"defaultProps",{x:0,y:0,width:0,height:0,viewBox:{x:0,y:0,width:0,height:0},orientation:"bottom",ticks:[],stroke:"#666",tickLine:!0,axisLine:!0,tick:!0,mirror:!1,minTickGap:5,tickSize:6,tickMargin:2,interval:"preserveEnd"})},56940:function(t,e,r){"use strict";r.d(e,{q:function(){return M}});var n=r(2265),o=r(86757),i=r.n(o),a=r(1175),u=r(16630),c=r(82944),l=r(85355),s=r(78242),f=r(80285),p=r(25739),h=["x1","y1","x2","y2","key"],d=["offset"];function y(t){return(y="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function v(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),r.push.apply(r,n)}return r}function m(t){for(var e=1;e=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}var x=function(t){var e=t.fill;if(!e||"none"===e)return null;var r=t.fillOpacity,o=t.x,i=t.y,a=t.width,u=t.height,c=t.ry;return n.createElement("rect",{x:o,y:i,ry:c,width:a,height:u,stroke:"none",fill:e,fillOpacity:r,className:"recharts-cartesian-grid-bg"})};function w(t,e){var r;if(n.isValidElement(t))r=n.cloneElement(t,e);else if(i()(t))r=t(e);else{var o=e.x1,a=e.y1,u=e.x2,l=e.y2,s=e.key,f=g(e,h),p=(0,c.L6)(f,!1),y=(p.offset,g(p,d));r=n.createElement("line",b({},y,{x1:o,y1:a,x2:u,y2:l,fill:"none",key:s}))}return r}function O(t){var e=t.x,r=t.width,o=t.horizontal,i=void 0===o||o,a=t.horizontalPoints;if(!i||!a||!a.length)return null;var u=a.map(function(n,o){return w(i,m(m({},t),{},{x1:e,y1:n,x2:e+r,y2:n,key:"line-".concat(o),index:o}))});return n.createElement("g",{className:"recharts-cartesian-grid-horizontal"},u)}function j(t){var e=t.y,r=t.height,o=t.vertical,i=void 0===o||o,a=t.verticalPoints;if(!i||!a||!a.length)return null;var u=a.map(function(n,o){return w(i,m(m({},t),{},{x1:n,y1:e,x2:n,y2:e+r,key:"line-".concat(o),index:o}))});return n.createElement("g",{className:"recharts-cartesian-grid-vertical"},u)}function S(t){var e=t.horizontalFill,r=t.fillOpacity,o=t.x,i=t.y,a=t.width,u=t.height,c=t.horizontalPoints,l=t.horizontal;if(!(void 0===l||l)||!e||!e.length)return null;var s=c.map(function(t){return Math.round(t+i-i)}).sort(function(t,e){return t-e});i!==s[0]&&s.unshift(0);var f=s.map(function(t,c){var l=s[c+1]?s[c+1]-t:i+u-t;if(l<=0)return null;var f=c%e.length;return n.createElement("rect",{key:"react-".concat(c),y:t,x:o,height:l,width:a,stroke:"none",fill:e[f],fillOpacity:r,className:"recharts-cartesian-grid-bg"})});return n.createElement("g",{className:"recharts-cartesian-gridstripes-horizontal"},f)}function P(t){var e=t.vertical,r=t.verticalFill,o=t.fillOpacity,i=t.x,a=t.y,u=t.width,c=t.height,l=t.verticalPoints;if(!(void 0===e||e)||!r||!r.length)return null;var s=l.map(function(t){return Math.round(t+i-i)}).sort(function(t,e){return t-e});i!==s[0]&&s.unshift(0);var f=s.map(function(t,e){var l=s[e+1]?s[e+1]-t:i+u-t;if(l<=0)return null;var f=e%r.length;return n.createElement("rect",{key:"react-".concat(e),x:t,y:a,width:l,height:c,stroke:"none",fill:r[f],fillOpacity:o,className:"recharts-cartesian-grid-bg"})});return n.createElement("g",{className:"recharts-cartesian-gridstripes-vertical"},f)}var E=function(t,e){var r=t.xAxis,n=t.width,o=t.height,i=t.offset;return(0,l.Rf)((0,s.f)(m(m(m({},f.O.defaultProps),r),{},{ticks:(0,l.uY)(r,!0),viewBox:{x:0,y:0,width:n,height:o}})),i.left,i.left+i.width,e)},k=function(t,e){var r=t.yAxis,n=t.width,o=t.height,i=t.offset;return(0,l.Rf)((0,s.f)(m(m(m({},f.O.defaultProps),r),{},{ticks:(0,l.uY)(r,!0),viewBox:{x:0,y:0,width:n,height:o}})),i.top,i.top+i.height,e)},A={horizontal:!0,vertical:!0,stroke:"#ccc",fill:"none",verticalFill:[],horizontalFill:[]};function M(t){var e,r,o,c,l,s,f=(0,p.zn)(),h=(0,p.Mw)(),d=(0,p.qD)(),v=m(m({},t),{},{stroke:null!==(e=t.stroke)&&void 0!==e?e:A.stroke,fill:null!==(r=t.fill)&&void 0!==r?r:A.fill,horizontal:null!==(o=t.horizontal)&&void 0!==o?o:A.horizontal,horizontalFill:null!==(c=t.horizontalFill)&&void 0!==c?c:A.horizontalFill,vertical:null!==(l=t.vertical)&&void 0!==l?l:A.vertical,verticalFill:null!==(s=t.verticalFill)&&void 0!==s?s:A.verticalFill,x:(0,u.hj)(t.x)?t.x:d.left,y:(0,u.hj)(t.y)?t.y:d.top,width:(0,u.hj)(t.width)?t.width:d.width,height:(0,u.hj)(t.height)?t.height:d.height}),g=v.x,w=v.y,M=v.width,_=v.height,T=v.syncWithTicks,C=v.horizontalValues,N=v.verticalValues,D=(0,p.CW)(),I=(0,p.Nf)();if(!(0,u.hj)(M)||M<=0||!(0,u.hj)(_)||_<=0||!(0,u.hj)(g)||g!==+g||!(0,u.hj)(w)||w!==+w)return null;var L=v.verticalCoordinatesGenerator||E,B=v.horizontalCoordinatesGenerator||k,R=v.horizontalPoints,z=v.verticalPoints;if((!R||!R.length)&&i()(B)){var U=C&&C.length,F=B({yAxis:I?m(m({},I),{},{ticks:U?C:I.ticks}):void 0,width:f,height:h,offset:d},!!U||T);(0,a.Z)(Array.isArray(F),"horizontalCoordinatesGenerator should return Array but instead it returned [".concat(y(F),"]")),Array.isArray(F)&&(R=F)}if((!z||!z.length)&&i()(L)){var $=N&&N.length,q=L({xAxis:D?m(m({},D),{},{ticks:$?N:D.ticks}):void 0,width:f,height:h,offset:d},!!$||T);(0,a.Z)(Array.isArray(q),"verticalCoordinatesGenerator should return Array but instead it returned [".concat(y(q),"]")),Array.isArray(q)&&(z=q)}return n.createElement("g",{className:"recharts-cartesian-grid"},n.createElement(x,{fill:v.fill,fillOpacity:v.fillOpacity,x:v.x,y:v.y,width:v.width,height:v.height,ry:v.ry}),n.createElement(O,b({},v,{offset:d,horizontalPoints:R,xAxis:D,yAxis:I})),n.createElement(j,b({},v,{offset:d,verticalPoints:z,xAxis:D,yAxis:I})),n.createElement(S,b({},v,{horizontalPoints:R})),n.createElement(P,b({},v,{verticalPoints:z})))}M.displayName="CartesianGrid"},13137:function(t,e,r){"use strict";r.d(e,{W:function(){return v}});var n=r(2265),o=r(69398),i=r(9841),a=r(82944),u=["offset","layout","width","dataKey","data","dataPointFormatter","xAxis","yAxis"];function c(t){return(c="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function l(){return(l=Object.assign?Object.assign.bind():function(t){for(var e=1;et.length)&&(e=t.length);for(var r=0,n=Array(e);r=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(t,u),m=(0,a.L6)(v,!1);"x"===this.props.direction&&"number"!==d.type&&(0,o.Z)(!1);var b=p.map(function(t){var o,a,u=h(t,f),p=u.x,v=u.y,b=u.value,g=u.errorVal;if(!g)return null;var x=[];if(Array.isArray(g)){var w=function(t){if(Array.isArray(t))return t}(g)||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=r){var n,o,i,a,u=[],c=!0,l=!1;try{for(i=(r=r.call(t)).next;!(c=(n=i.call(r)).done)&&(u.push(n.value),2!==u.length);c=!0);}catch(t){l=!0,o=t}finally{try{if(!c&&null!=r.return&&(a=r.return(),Object(a)!==a))return}finally{if(l)throw o}}return u}}(g,2)||function(t,e){if(t){if("string"==typeof t)return s(t,2);var r=Object.prototype.toString.call(t).slice(8,-1);if("Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return s(t,2)}}(g,2)||function(){throw TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}();o=w[0],a=w[1]}else o=a=g;if("vertical"===r){var O=d.scale,j=v+e,S=j+c,P=j-c,E=O(b-o),k=O(b+a);x.push({x1:k,y1:S,x2:k,y2:P}),x.push({x1:E,y1:j,x2:k,y2:j}),x.push({x1:E,y1:S,x2:E,y2:P})}else if("horizontal"===r){var A=y.scale,M=p+e,_=M-c,T=M+c,C=A(b-o),N=A(b+a);x.push({x1:_,y1:N,x2:T,y2:N}),x.push({x1:M,y1:C,x2:M,y2:N}),x.push({x1:_,y1:C,x2:T,y2:C})}return n.createElement(i.m,l({className:"recharts-errorBar",key:"bar-".concat(x.map(function(t){return"".concat(t.x1,"-").concat(t.x2,"-").concat(t.y1,"-").concat(t.y2)}))},m),x.map(function(t){return n.createElement("line",l({},t,{key:"line-".concat(t.x1,"-").concat(t.x2,"-").concat(t.y1,"-").concat(t.y2)}))}))});return n.createElement(i.m,{className:"recharts-errorBars"},b)}}],function(t,e){for(var r=0;rt*o)return!1;var i=r();return t*(e-t*i/2-n)>=0&&t*(e+t*i/2-o)<=0}function f(t){return(f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function p(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),r.push.apply(r,n)}return r}function h(t){for(var e=1;e=2?(0,i.uY)(m[1].coordinate-m[0].coordinate):1,M=(n="width"===P,f=b.x,p=b.y,d=b.width,y=b.height,1===A?{start:n?f:p,end:n?f+d:p+y}:{start:n?f+d:p+y,end:n?f:p});return"equidistantPreserveStart"===w?function(t,e,r,n,o){for(var i,a=(n||[]).slice(),u=e.start,c=e.end,f=0,p=1,h=u;p<=a.length;)if(i=function(){var e,i=null==n?void 0:n[f];if(void 0===i)return{v:l(n,p)};var a=f,d=function(){return void 0===e&&(e=r(i,a)),e},y=i.coordinate,v=0===f||s(t,y,d,h,c);v||(f=0,h=u,p+=1),v&&(h=y+t*(d()/2+o),f+=p)}())return i.v;return[]}(A,M,k,m,g):("preserveStart"===w||"preserveStartEnd"===w?function(t,e,r,n,o,i){var a=(n||[]).slice(),u=a.length,c=e.start,l=e.end;if(i){var f=n[u-1],p=r(f,u-1),d=t*(f.coordinate+t*p/2-l);a[u-1]=f=h(h({},f),{},{tickCoord:d>0?f.coordinate-d*t:f.coordinate}),s(t,f.tickCoord,function(){return p},c,l)&&(l=f.tickCoord-t*(p/2+o),a[u-1]=h(h({},f),{},{isShow:!0}))}for(var y=i?u-1:u,v=function(e){var n,i=a[e],u=function(){return void 0===n&&(n=r(i,e)),n};if(0===e){var f=t*(i.coordinate-t*u()/2-c);a[e]=i=h(h({},i),{},{tickCoord:f<0?i.coordinate-f*t:i.coordinate})}else a[e]=i=h(h({},i),{},{tickCoord:i.coordinate});s(t,i.tickCoord,u,c,l)&&(c=i.tickCoord+t*(u()/2+o),a[e]=h(h({},i),{},{isShow:!0}))},m=0;m0?l.coordinate-p*t:l.coordinate})}else i[e]=l=h(h({},l),{},{tickCoord:l.coordinate});s(t,l.tickCoord,f,u,c)&&(c=l.tickCoord-t*(f()/2+o),i[e]=h(h({},l),{},{isShow:!0}))},f=a-1;f>=0;f--)l(f);return i}(A,M,k,m,g)).filter(function(t){return t.isShow})}},93765:function(t,e,r){"use strict";r.d(e,{z:function(){return eD}});var n,o,i=r(2265),a=r(77571),u=r.n(a),c=r(86757),l=r.n(c),s=r(99676),f=r.n(s),p=r(13735),h=r.n(p),d=r(34935),y=r.n(d),v=r(37065),m=r.n(v),b=r(61994),g=r(69398),x=r(48777),w=r(9841),O=r(8147),j=r(22190),S=r(81889),P=r(73649),E=r(82944),k=r(55284),A=r(58811),M=r(85355),_=r(16630);function T(t){return(T="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function C(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),r.push.apply(r,n)}return r}function N(t){for(var e=1;e0&&e.handleDrag(t.changedTouches[0])}),W(e,"handleDragEnd",function(){e.setState({isTravellerMoving:!1,isSlideMoving:!1},function(){var t=e.props,r=t.endIndex,n=t.onDragEnd,o=t.startIndex;null==n||n({endIndex:r,startIndex:o})}),e.detachDragEndListener()}),W(e,"handleLeaveWrapper",function(){(e.state.isTravellerMoving||e.state.isSlideMoving)&&(e.leaveTimer=window.setTimeout(e.handleDragEnd,e.props.leaveTimeOut))}),W(e,"handleEnterSlideOrTraveller",function(){e.setState({isTextActive:!0})}),W(e,"handleLeaveSlideOrTraveller",function(){e.setState({isTextActive:!1})}),W(e,"handleSlideDragStart",function(t){var r=X(t)?t.changedTouches[0]:t;e.setState({isTravellerMoving:!1,isSlideMoving:!0,slideMoveStartX:r.pageX}),e.attachDragEndListener()}),e.travellerDragStartHandlers={startX:e.handleTravellerDragStart.bind(e,"startX"),endX:e.handleTravellerDragStart.bind(e,"endX")},e.state={},e}return!function(t,e){if("function"!=typeof e&&null!==e)throw TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),Object.defineProperty(t,"prototype",{writable:!1}),e&&Z(t,e)}(n,t),e=[{key:"componentWillUnmount",value:function(){this.leaveTimer&&(clearTimeout(this.leaveTimer),this.leaveTimer=null),this.detachDragEndListener()}},{key:"getIndex",value:function(t){var e=t.startX,r=t.endX,o=this.state.scaleValues,i=this.props,a=i.gap,u=i.data.length-1,c=n.getIndexInRange(o,Math.min(e,r)),l=n.getIndexInRange(o,Math.max(e,r));return{startIndex:c-c%a,endIndex:l===u?u:l-l%a}}},{key:"getTextOfTick",value:function(t){var e=this.props,r=e.data,n=e.tickFormatter,o=e.dataKey,i=(0,M.F$)(r[t],o,t);return l()(n)?n(i,t):i}},{key:"attachDragEndListener",value:function(){window.addEventListener("mouseup",this.handleDragEnd,!0),window.addEventListener("touchend",this.handleDragEnd,!0),window.addEventListener("mousemove",this.handleDrag,!0)}},{key:"detachDragEndListener",value:function(){window.removeEventListener("mouseup",this.handleDragEnd,!0),window.removeEventListener("touchend",this.handleDragEnd,!0),window.removeEventListener("mousemove",this.handleDrag,!0)}},{key:"handleSlideDrag",value:function(t){var e=this.state,r=e.slideMoveStartX,n=e.startX,o=e.endX,i=this.props,a=i.x,u=i.width,c=i.travellerWidth,l=i.startIndex,s=i.endIndex,f=i.onChange,p=t.pageX-r;p>0?p=Math.min(p,a+u-c-o,a+u-c-n):p<0&&(p=Math.max(p,a-n,a-o));var h=this.getIndex({startX:n+p,endX:o+p});(h.startIndex!==l||h.endIndex!==s)&&f&&f(h),this.setState({startX:n+p,endX:o+p,slideMoveStartX:t.pageX})}},{key:"handleTravellerDragStart",value:function(t,e){var r=X(e)?e.changedTouches[0]:e;this.setState({isSlideMoving:!1,isTravellerMoving:!0,movingTravellerId:t,brushMoveStartX:r.pageX}),this.attachDragEndListener()}},{key:"handleTravellerMove",value:function(t){var e=this.state,r=e.brushMoveStartX,n=e.movingTravellerId,o=e.endX,i=e.startX,a=this.state[n],u=this.props,c=u.x,l=u.width,s=u.travellerWidth,f=u.onChange,p=u.gap,h=u.data,d={startX:this.state.startX,endX:this.state.endX},y=t.pageX-r;y>0?y=Math.min(y,c+l-s-a):y<0&&(y=Math.max(y,c-a)),d[n]=a+y;var v=this.getIndex(d),m=v.startIndex,b=v.endIndex,g=function(){var t=h.length-1;return"startX"===n&&(o>i?m%p==0:b%p==0)||oi?b%p==0:m%p==0)||o>i&&b===t};this.setState(W(W({},n,a+y),"brushMoveStartX",t.pageX),function(){f&&g()&&f(v)})}},{key:"handleTravellerMoveKeyboard",value:function(t,e){var r=this,n=this.state,o=n.scaleValues,i=n.startX,a=n.endX,u=this.state[e],c=o.indexOf(u);if(-1!==c){var l=c+t;if(-1!==l&&!(l>=o.length)){var s=o[l];"startX"===e&&s>=a||"endX"===e&&s<=i||this.setState(W({},e,s),function(){r.props.onChange(r.getIndex({startX:r.state.startX,endX:r.state.endX}))})}}}},{key:"renderBackground",value:function(){var t=this.props,e=t.x,r=t.y,n=t.width,o=t.height,a=t.fill,u=t.stroke;return i.createElement("rect",{stroke:u,fill:a,x:e,y:r,width:n,height:o})}},{key:"renderPanorama",value:function(){var t=this.props,e=t.x,r=t.y,n=t.width,o=t.height,a=t.data,u=t.children,c=t.padding,l=i.Children.only(u);return l?i.cloneElement(l,{x:e,y:r,width:n,height:o,margin:c,compact:!0,data:a}):null}},{key:"renderTravellerLayer",value:function(t,e){var r,o,a=this,u=this.props,c=u.y,l=u.travellerWidth,s=u.height,f=u.traveller,p=u.ariaLabel,h=u.data,d=u.startIndex,y=u.endIndex,v=Math.max(t,this.props.x),m=U(U({},(0,E.L6)(this.props,!1)),{},{x:v,y:c,width:l,height:s}),b=p||"Min value: ".concat(null===(r=h[d])||void 0===r?void 0:r.name,", Max value: ").concat(null===(o=h[y])||void 0===o?void 0:o.name);return i.createElement(w.m,{tabIndex:0,role:"slider","aria-label":b,"aria-valuenow":t,className:"recharts-brush-traveller",onMouseEnter:this.handleEnterSlideOrTraveller,onMouseLeave:this.handleLeaveSlideOrTraveller,onMouseDown:this.travellerDragStartHandlers[e],onTouchStart:this.travellerDragStartHandlers[e],onKeyDown:function(t){["ArrowLeft","ArrowRight"].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),a.handleTravellerMoveKeyboard("ArrowRight"===t.key?1:-1,e))},onFocus:function(){a.setState({isTravellerFocused:!0})},onBlur:function(){a.setState({isTravellerFocused:!1})},style:{cursor:"col-resize"}},n.renderTraveller(f,m))}},{key:"renderSlide",value:function(t,e){var r=this.props,n=r.y,o=r.height,a=r.stroke,u=r.travellerWidth;return i.createElement("rect",{className:"recharts-brush-slide",onMouseEnter:this.handleEnterSlideOrTraveller,onMouseLeave:this.handleLeaveSlideOrTraveller,onMouseDown:this.handleSlideDragStart,onTouchStart:this.handleSlideDragStart,style:{cursor:"move"},stroke:"none",fill:a,fillOpacity:.2,x:Math.min(t,e)+u,y:n,width:Math.max(Math.abs(e-t)-u,0),height:o})}},{key:"renderText",value:function(){var t=this.props,e=t.startIndex,r=t.endIndex,n=t.y,o=t.height,a=t.travellerWidth,u=t.stroke,c=this.state,l=c.startX,s=c.endX,f={pointerEvents:"none",fill:u};return i.createElement(w.m,{className:"recharts-brush-texts"},i.createElement(A.x,R({textAnchor:"end",verticalAnchor:"middle",x:Math.min(l,s)-5,y:n+o/2},f),this.getTextOfTick(e)),i.createElement(A.x,R({textAnchor:"start",verticalAnchor:"middle",x:Math.max(l,s)+a+5,y:n+o/2},f),this.getTextOfTick(r)))}},{key:"render",value:function(){var t=this.props,e=t.data,r=t.className,n=t.children,o=t.x,a=t.y,u=t.width,c=t.height,l=t.alwaysShowText,s=this.state,f=s.startX,p=s.endX,h=s.isTextActive,d=s.isSlideMoving,y=s.isTravellerMoving,v=s.isTravellerFocused;if(!e||!e.length||!(0,_.hj)(o)||!(0,_.hj)(a)||!(0,_.hj)(u)||!(0,_.hj)(c)||u<=0||c<=0)return null;var m=(0,b.Z)("recharts-brush",r),g=1===i.Children.count(n),x=L("userSelect","none");return i.createElement(w.m,{className:m,onMouseLeave:this.handleLeaveWrapper,onTouchMove:this.handleTouchMove,style:x},this.renderBackground(),g&&this.renderPanorama(),this.renderSlide(f,p),this.renderTravellerLayer(f,"startX"),this.renderTravellerLayer(p,"endX"),(h||d||y||v||l)&&this.renderText())}}],r=[{key:"renderDefaultTraveller",value:function(t){var e=t.x,r=t.y,n=t.width,o=t.height,a=t.stroke,u=Math.floor(r+o/2)-1;return i.createElement(i.Fragment,null,i.createElement("rect",{x:e,y:r,width:n,height:o,fill:a,stroke:"none"}),i.createElement("line",{x1:e+1,y1:u,x2:e+n-1,y2:u,fill:"none",stroke:"#fff"}),i.createElement("line",{x1:e+1,y1:u+2,x2:e+n-1,y2:u+2,fill:"none",stroke:"#fff"}))}},{key:"renderTraveller",value:function(t,e){return i.isValidElement(t)?i.cloneElement(t,e):l()(t)?t(e):n.renderDefaultTraveller(e)}},{key:"getDerivedStateFromProps",value:function(t,e){var r=t.data,n=t.width,o=t.x,i=t.travellerWidth,a=t.updateId,u=t.startIndex,c=t.endIndex;if(r!==e.prevData||a!==e.prevUpdateId)return U({prevData:r,prevTravellerWidth:i,prevUpdateId:a,prevX:o,prevWidth:n},r&&r.length?H({data:r,width:n,x:o,travellerWidth:i,startIndex:u,endIndex:c}):{scale:null,scaleValues:null});if(e.scale&&(n!==e.prevWidth||o!==e.prevX||i!==e.prevTravellerWidth)){e.scale.range([o,o+n-i]);var l=e.scale.domain().map(function(t){return e.scale(t)});return{prevData:r,prevTravellerWidth:i,prevUpdateId:a,prevX:o,prevWidth:n,startX:e.scale(t.startIndex),endX:e.scale(t.endIndex),scaleValues:l}}return null}},{key:"getIndexInRange",value:function(t,e){for(var r=t.length,n=0,o=r-1;o-n>1;){var i=Math.floor((n+o)/2);t[i]>e?o=i:n=i}return e>=t[o]?o:n}}],e&&F(n.prototype,e),r&&F(n,r),Object.defineProperty(n,"prototype",{writable:!1}),n}(i.PureComponent);W(G,"displayName","Brush"),W(G,"defaultProps",{height:40,travellerWidth:5,gap:1,fill:"#fff",stroke:"#666",padding:{top:1,right:1,bottom:1,left:1},leaveTimeOut:1e3,alwaysShowText:!1});var V=r(4094),K=r(38569),Q=r(26680),J=function(t,e){var r=t.alwaysShow,n=t.ifOverflow;return r&&(n="extendDomain"),n===e},tt=r(25311),te=r(1175);function tr(){return(tr=Object.assign?Object.assign.bind():function(t){for(var e=1;et.length)&&(e=t.length);for(var r=0,n=Array(e);rt.length)&&(e=t.length);for(var r=0,n=Array(e);r=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(t,t2));return(0,_.hj)(r)&&(0,_.hj)(o)&&(0,_.hj)(f)&&(0,_.hj)(h)&&(0,_.hj)(u)&&(0,_.hj)(l)?i.createElement("path",t5({},(0,E.L6)(y,!0),{className:(0,b.Z)("recharts-cross",d),d:"M".concat(r,",").concat(u,"v").concat(h,"M").concat(l,",").concat(o,"h").concat(f)})):null};function t7(t){var e=t.cx,r=t.cy,n=t.radius,o=t.startAngle,i=t.endAngle;return{points:[(0,tq.op)(e,r,n,o),(0,tq.op)(e,r,n,i)],cx:e,cy:r,radius:n,startAngle:o,endAngle:i}}var t4=r(60474);function t8(t){return(t8="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function t9(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),r.push.apply(r,n)}return r}function et(t){for(var e=1;e=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}function ec(){try{var t=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(t){}return(ec=function(){return!!t})()}function el(t){return(el=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}function es(t,e){return(es=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t})(t,e)}function ef(t){return function(t){if(Array.isArray(t))return eh(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||ep(t)||function(){throw TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function ep(t,e){if(t){if("string"==typeof t)return eh(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);if("Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return eh(t,e)}}function eh(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=Array(e);r0?i:t&&t.length&&(0,_.hj)(n)&&(0,_.hj)(o)?t.slice(n,o+1):[]};function eS(t){return"number"===t?[0,"auto"]:void 0}var eP=function(t,e,r,n){var o=t.graphicalItems,i=t.tooltipAxis,a=ej(e,t);return r<0||!o||!o.length||r>=a.length?null:o.reduce(function(o,u){var c,l,s=null!==(c=u.props.data)&&void 0!==c?c:e;if(s&&t.dataStartIndex+t.dataEndIndex!==0&&t.dataEndIndex-t.dataStartIndex>=r&&(s=s.slice(t.dataStartIndex,t.dataEndIndex+1)),i.dataKey&&!i.allowDuplicatedCategory){var f=void 0===s?a:s;l=(0,_.Ap)(f,i.dataKey,n)}else l=s&&s[r]||a[r];return l?[].concat(ef(o),[(0,M.Qo)(u,l)]):o},[])},eE=function(t,e,r,n){var o=n||{x:t.chartX,y:t.chartY},i="horizontal"===r?o.x:"vertical"===r?o.y:"centric"===r?o.angle:o.radius,a=t.orderedTooltipTicks,u=t.tooltipAxis,c=t.tooltipTicks,l=(0,M.VO)(i,a,c,u);if(l>=0&&c){var s=c[l]&&c[l].value,f=eP(t,e,l,s),p=eO(r,a,l,o);return{activeTooltipIndex:l,activeLabel:s,activePayload:f,activeCoordinate:p}}return null},ek=function(t,e){var r=e.axes,n=e.graphicalItems,o=e.axisType,i=e.axisIdKey,a=e.stackGroups,c=e.dataStartIndex,l=e.dataEndIndex,s=t.layout,p=t.children,h=t.stackOffset,d=(0,M.NA)(s,o);return r.reduce(function(e,r){var y=void 0!==r.type.defaultProps?ey(ey({},r.type.defaultProps),r.props):r.props,v=y.type,m=y.dataKey,b=y.allowDataOverflow,g=y.allowDuplicatedCategory,x=y.scale,w=y.ticks,O=y.includeHidden,j=y[i];if(e[j])return e;var S=ej(t.data,{graphicalItems:n.filter(function(t){var e;return(i in t.props?t.props[i]:null===(e=t.type.defaultProps)||void 0===e?void 0:e[i])===j}),dataStartIndex:c,dataEndIndex:l}),P=S.length;(function(t,e,r){if("number"===r&&!0===e&&Array.isArray(t)){var n=null==t?void 0:t[0],o=null==t?void 0:t[1];if(n&&o&&(0,_.hj)(n)&&(0,_.hj)(o))return!0}return!1})(y.domain,b,v)&&(A=(0,M.LG)(y.domain,null,b),d&&("number"===v||"auto"!==x)&&(C=(0,M.gF)(S,m,"category")));var E=eS(v);if(!A||0===A.length){var k,A,T,C,N,D=null!==(N=y.domain)&&void 0!==N?N:E;if(m){if(A=(0,M.gF)(S,m,v),"category"===v&&d){var I=(0,_.bv)(A);g&&I?(T=A,A=f()(0,P)):g||(A=(0,M.ko)(D,A,r).reduce(function(t,e){return t.indexOf(e)>=0?t:[].concat(ef(t),[e])},[]))}else if("category"===v)A=g?A.filter(function(t){return""!==t&&!u()(t)}):(0,M.ko)(D,A,r).reduce(function(t,e){return t.indexOf(e)>=0||""===e||u()(e)?t:[].concat(ef(t),[e])},[]);else if("number"===v){var L=(0,M.ZI)(S,n.filter(function(t){var e,r,n=i in t.props?t.props[i]:null===(e=t.type.defaultProps)||void 0===e?void 0:e[i],o="hide"in t.props?t.props.hide:null===(r=t.type.defaultProps)||void 0===r?void 0:r.hide;return n===j&&(O||!o)}),m,o,s);L&&(A=L)}d&&("number"===v||"auto"!==x)&&(C=(0,M.gF)(S,m,"category"))}else A=d?f()(0,P):a&&a[j]&&a[j].hasStack&&"number"===v?"expand"===h?[0,1]:(0,M.EB)(a[j].stackGroups,c,l):(0,M.s6)(S,n.filter(function(t){var e=i in t.props?t.props[i]:t.type.defaultProps[i],r="hide"in t.props?t.props.hide:t.type.defaultProps.hide;return e===j&&(O||!r)}),v,s,!0);"number"===v?(A=t$(p,A,j,o,w),D&&(A=(0,M.LG)(D,A,b))):"category"===v&&D&&A.every(function(t){return D.indexOf(t)>=0})&&(A=D)}return ey(ey({},e),{},ev({},j,ey(ey({},y),{},{axisType:o,domain:A,categoricalDomain:C,duplicateDomain:T,originalDomain:null!==(k=y.domain)&&void 0!==k?k:E,isCategorical:d,layout:s})))},{})},eA=function(t,e){var r=e.graphicalItems,n=e.Axis,o=e.axisType,i=e.axisIdKey,a=e.stackGroups,u=e.dataStartIndex,c=e.dataEndIndex,l=t.layout,s=t.children,p=ej(t.data,{graphicalItems:r,dataStartIndex:u,dataEndIndex:c}),d=p.length,y=(0,M.NA)(l,o),v=-1;return r.reduce(function(t,e){var m,b=(void 0!==e.type.defaultProps?ey(ey({},e.type.defaultProps),e.props):e.props)[i],g=eS("number");return t[b]?t:(v++,m=y?f()(0,d):a&&a[b]&&a[b].hasStack?t$(s,m=(0,M.EB)(a[b].stackGroups,u,c),b,o):t$(s,m=(0,M.LG)(g,(0,M.s6)(p,r.filter(function(t){var e,r,n=i in t.props?t.props[i]:null===(e=t.type.defaultProps)||void 0===e?void 0:e[i],o="hide"in t.props?t.props.hide:null===(r=t.type.defaultProps)||void 0===r?void 0:r.hide;return n===b&&!o}),"number",l),n.defaultProps.allowDataOverflow),b,o),ey(ey({},t),{},ev({},b,ey(ey({axisType:o},n.defaultProps),{},{hide:!0,orientation:h()(eb,"".concat(o,".").concat(v%2),null),domain:m,originalDomain:g,isCategorical:y,layout:l}))))},{})},eM=function(t,e){var r=e.axisType,n=void 0===r?"xAxis":r,o=e.AxisComp,i=e.graphicalItems,a=e.stackGroups,u=e.dataStartIndex,c=e.dataEndIndex,l=t.children,s="".concat(n,"Id"),f=(0,E.NN)(l,o),p={};return f&&f.length?p=ek(t,{axes:f,graphicalItems:i,axisType:n,axisIdKey:s,stackGroups:a,dataStartIndex:u,dataEndIndex:c}):i&&i.length&&(p=eA(t,{Axis:o,graphicalItems:i,axisType:n,axisIdKey:s,stackGroups:a,dataStartIndex:u,dataEndIndex:c})),p},e_=function(t){var e=(0,_.Kt)(t),r=(0,M.uY)(e,!1,!0);return{tooltipTicks:r,orderedTooltipTicks:y()(r,function(t){return t.coordinate}),tooltipAxis:e,tooltipAxisBandSize:(0,M.zT)(e,r)}},eT=function(t){var e=t.children,r=t.defaultShowTooltip,n=(0,E.sP)(e,G),o=0,i=0;return t.data&&0!==t.data.length&&(i=t.data.length-1),n&&n.props&&(n.props.startIndex>=0&&(o=n.props.startIndex),n.props.endIndex>=0&&(i=n.props.endIndex)),{chartX:0,chartY:0,dataStartIndex:o,dataEndIndex:i,activeTooltipIndex:-1,isTooltipActive:!!r}},eC=function(t){return"horizontal"===t?{numericAxisName:"yAxis",cateAxisName:"xAxis"}:"vertical"===t?{numericAxisName:"xAxis",cateAxisName:"yAxis"}:"centric"===t?{numericAxisName:"radiusAxis",cateAxisName:"angleAxis"}:{numericAxisName:"angleAxis",cateAxisName:"radiusAxis"}},eN=function(t,e){var r=t.props,n=t.graphicalItems,o=t.xAxisMap,i=void 0===o?{}:o,a=t.yAxisMap,u=void 0===a?{}:a,c=r.width,l=r.height,s=r.children,f=r.margin||{},p=(0,E.sP)(s,G),d=(0,E.sP)(s,j.D),y=Object.keys(u).reduce(function(t,e){var r=u[e],n=r.orientation;return r.mirror||r.hide?t:ey(ey({},t),{},ev({},n,t[n]+r.width))},{left:f.left||0,right:f.right||0}),v=Object.keys(i).reduce(function(t,e){var r=i[e],n=r.orientation;return r.mirror||r.hide?t:ey(ey({},t),{},ev({},n,h()(t,"".concat(n))+r.height))},{top:f.top||0,bottom:f.bottom||0}),m=ey(ey({},v),y),b=m.bottom;p&&(m.bottom+=p.props.height||G.defaultProps.height),d&&e&&(m=(0,M.By)(m,n,r,e));var g=c-m.left-m.right,x=l-m.top-m.bottom;return ey(ey({brushBottom:b},m),{},{width:Math.max(g,0),height:Math.max(x,0)})},eD=function(t){var e=t.chartName,r=t.GraphicalChild,n=t.defaultTooltipEventType,o=void 0===n?"axis":n,a=t.validateTooltipEventTypes,c=void 0===a?["axis"]:a,s=t.axisComponents,f=t.legendContent,p=t.formatAxisMap,d=t.defaultProps,y=function(t,e){var r=e.graphicalItems,n=e.stackGroups,o=e.offset,i=e.updateId,a=e.dataStartIndex,c=e.dataEndIndex,l=t.barSize,f=t.layout,p=t.barGap,h=t.barCategoryGap,d=t.maxBarSize,y=eC(f),v=y.numericAxisName,m=y.cateAxisName,b=!!r&&!!r.length&&r.some(function(t){var e=(0,E.Gf)(t&&t.type);return e&&e.indexOf("Bar")>=0}),x=[];return r.forEach(function(r,y){var w=ej(t.data,{graphicalItems:[r],dataStartIndex:a,dataEndIndex:c}),O=void 0!==r.type.defaultProps?ey(ey({},r.type.defaultProps),r.props):r.props,j=O.dataKey,S=O.maxBarSize,P=O["".concat(v,"Id")],k=O["".concat(m,"Id")],A=s.reduce(function(t,r){var n=e["".concat(r.axisType,"Map")],o=O["".concat(r.axisType,"Id")];n&&n[o]||"zAxis"===r.axisType||(0,g.Z)(!1);var i=n[o];return ey(ey({},t),{},ev(ev({},r.axisType,i),"".concat(r.axisType,"Ticks"),(0,M.uY)(i)))},{}),_=A[m],T=A["".concat(m,"Ticks")],C=n&&n[P]&&n[P].hasStack&&(0,M.O3)(r,n[P].stackGroups),N=(0,E.Gf)(r.type).indexOf("Bar")>=0,D=(0,M.zT)(_,T),I=[],L=b&&(0,M.pt)({barSize:l,stackGroups:n,totalSize:"xAxis"===m?A[m].width:"yAxis"===m?A[m].height:void 0});if(N){var B,R,z=u()(S)?d:S,U=null!==(B=null!==(R=(0,M.zT)(_,T,!0))&&void 0!==R?R:z)&&void 0!==B?B:0;I=(0,M.qz)({barGap:p,barCategoryGap:h,bandSize:U!==D?U:D,sizeList:L[k],maxBarSize:z}),U!==D&&(I=I.map(function(t){return ey(ey({},t),{},{position:ey(ey({},t.position),{},{offset:t.position.offset-U/2})})}))}var F=r&&r.type&&r.type.getComposedData;F&&x.push({props:ey(ey({},F(ey(ey({},A),{},{displayedData:w,props:t,dataKey:j,item:r,bandSize:D,barPosition:I,offset:o,stackedData:C,layout:f,dataStartIndex:a,dataEndIndex:c}))),{},ev(ev(ev({key:r.key||"item-".concat(y)},v,A[v]),m,A[m]),"animationId",i)),childIndex:(0,E.$R)(r,t.children),item:r})}),x},v=function(t,n){var o=t.props,i=t.dataStartIndex,a=t.dataEndIndex,u=t.updateId;if(!(0,E.TT)({props:o}))return null;var c=o.children,l=o.layout,f=o.stackOffset,h=o.data,d=o.reverseStackOrder,v=eC(l),m=v.numericAxisName,b=v.cateAxisName,g=(0,E.NN)(c,r),x=(0,M.wh)(h,g,"".concat(m,"Id"),"".concat(b,"Id"),f,d),w=s.reduce(function(t,e){var r="".concat(e.axisType,"Map");return ey(ey({},t),{},ev({},r,eM(o,ey(ey({},e),{},{graphicalItems:g,stackGroups:e.axisType===m&&x,dataStartIndex:i,dataEndIndex:a}))))},{}),O=eN(ey(ey({},w),{},{props:o,graphicalItems:g}),null==n?void 0:n.legendBBox);Object.keys(w).forEach(function(t){w[t]=p(o,w[t],O,t.replace("Map",""),e)});var j=e_(w["".concat(b,"Map")]),S=y(o,ey(ey({},w),{},{dataStartIndex:i,dataEndIndex:a,updateId:u,graphicalItems:g,stackGroups:x,offset:O}));return ey(ey({formattedGraphicalItems:S,graphicalItems:g,offset:O,stackGroups:x},j),w)},j=function(t){var r;function n(t){var r,o,a,c,s;return!function(t,e){if(!(t instanceof e))throw TypeError("Cannot call a class as a function")}(this,n),c=n,s=[t],c=el(c),ev(a=function(t,e){if(e&&("object"===eo(e)||"function"==typeof e))return e;if(void 0!==e)throw TypeError("Derived constructors may only return object or undefined");return function(t){if(void 0===t)throw ReferenceError("this hasn't been initialised - super() hasn't been called");return t}(t)}(this,ec()?Reflect.construct(c,s||[],el(this).constructor):c.apply(this,s)),"eventEmitterSymbol",Symbol("rechartsEventEmitter")),ev(a,"accessibilityManager",new tQ),ev(a,"handleLegendBBoxUpdate",function(t){if(t){var e=a.state,r=e.dataStartIndex,n=e.dataEndIndex,o=e.updateId;a.setState(ey({legendBBox:t},v({props:a.props,dataStartIndex:r,dataEndIndex:n,updateId:o},ey(ey({},a.state),{},{legendBBox:t}))))}}),ev(a,"handleReceiveSyncEvent",function(t,e,r){a.props.syncId===t&&(r!==a.eventEmitterSymbol||"function"==typeof a.props.syncMethod)&&a.applySyncEvent(e)}),ev(a,"handleBrushChange",function(t){var e=t.startIndex,r=t.endIndex;if(e!==a.state.dataStartIndex||r!==a.state.dataEndIndex){var n=a.state.updateId;a.setState(function(){return ey({dataStartIndex:e,dataEndIndex:r},v({props:a.props,dataStartIndex:e,dataEndIndex:r,updateId:n},a.state))}),a.triggerSyncEvent({dataStartIndex:e,dataEndIndex:r})}}),ev(a,"handleMouseEnter",function(t){var e=a.getMouseInfo(t);if(e){var r=ey(ey({},e),{},{isTooltipActive:!0});a.setState(r),a.triggerSyncEvent(r);var n=a.props.onMouseEnter;l()(n)&&n(r,t)}}),ev(a,"triggeredAfterMouseMove",function(t){var e=a.getMouseInfo(t),r=e?ey(ey({},e),{},{isTooltipActive:!0}):{isTooltipActive:!1};a.setState(r),a.triggerSyncEvent(r);var n=a.props.onMouseMove;l()(n)&&n(r,t)}),ev(a,"handleItemMouseEnter",function(t){a.setState(function(){return{isTooltipActive:!0,activeItem:t,activePayload:t.tooltipPayload,activeCoordinate:t.tooltipPosition||{x:t.cx,y:t.cy}}})}),ev(a,"handleItemMouseLeave",function(){a.setState(function(){return{isTooltipActive:!1}})}),ev(a,"handleMouseMove",function(t){t.persist(),a.throttleTriggeredAfterMouseMove(t)}),ev(a,"handleMouseLeave",function(t){a.throttleTriggeredAfterMouseMove.cancel();var e={isTooltipActive:!1};a.setState(e),a.triggerSyncEvent(e);var r=a.props.onMouseLeave;l()(r)&&r(e,t)}),ev(a,"handleOuterEvent",function(t){var e,r=(0,E.Bh)(t),n=h()(a.props,"".concat(r));r&&l()(n)&&n(null!==(e=/.*touch.*/i.test(r)?a.getMouseInfo(t.changedTouches[0]):a.getMouseInfo(t))&&void 0!==e?e:{},t)}),ev(a,"handleClick",function(t){var e=a.getMouseInfo(t);if(e){var r=ey(ey({},e),{},{isTooltipActive:!0});a.setState(r),a.triggerSyncEvent(r);var n=a.props.onClick;l()(n)&&n(r,t)}}),ev(a,"handleMouseDown",function(t){var e=a.props.onMouseDown;l()(e)&&e(a.getMouseInfo(t),t)}),ev(a,"handleMouseUp",function(t){var e=a.props.onMouseUp;l()(e)&&e(a.getMouseInfo(t),t)}),ev(a,"handleTouchMove",function(t){null!=t.changedTouches&&t.changedTouches.length>0&&a.throttleTriggeredAfterMouseMove(t.changedTouches[0])}),ev(a,"handleTouchStart",function(t){null!=t.changedTouches&&t.changedTouches.length>0&&a.handleMouseDown(t.changedTouches[0])}),ev(a,"handleTouchEnd",function(t){null!=t.changedTouches&&t.changedTouches.length>0&&a.handleMouseUp(t.changedTouches[0])}),ev(a,"handleDoubleClick",function(t){var e=a.props.onDoubleClick;l()(e)&&e(a.getMouseInfo(t),t)}),ev(a,"handleContextMenu",function(t){var e=a.props.onContextMenu;l()(e)&&e(a.getMouseInfo(t),t)}),ev(a,"triggerSyncEvent",function(t){void 0!==a.props.syncId&&tY.emit(tH,a.props.syncId,t,a.eventEmitterSymbol)}),ev(a,"applySyncEvent",function(t){var e=a.props,r=e.layout,n=e.syncMethod,o=a.state.updateId,i=t.dataStartIndex,u=t.dataEndIndex;if(void 0!==t.dataStartIndex||void 0!==t.dataEndIndex)a.setState(ey({dataStartIndex:i,dataEndIndex:u},v({props:a.props,dataStartIndex:i,dataEndIndex:u,updateId:o},a.state)));else if(void 0!==t.activeTooltipIndex){var c=t.chartX,l=t.chartY,s=t.activeTooltipIndex,f=a.state,p=f.offset,h=f.tooltipTicks;if(!p)return;if("function"==typeof n)s=n(h,t);else if("value"===n){s=-1;for(var d=0;d=0){if(s.dataKey&&!s.allowDuplicatedCategory){var A="function"==typeof s.dataKey?function(t){return"function"==typeof s.dataKey?s.dataKey(t.payload):null}:"payload.".concat(s.dataKey.toString());C=(0,_.Ap)(v,A,p),N=m&&b&&(0,_.Ap)(b,A,p)}else C=null==v?void 0:v[f],N=m&&b&&b[f];if(S||j){var T=void 0!==t.props.activeIndex?t.props.activeIndex:f;return[(0,i.cloneElement)(t,ey(ey(ey({},n.props),P),{},{activeIndex:T})),null,null]}if(!u()(C))return[k].concat(ef(a.renderActivePoints({item:n,activePoint:C,basePoint:N,childIndex:f,isRange:m})))}else{var C,N,D,I=(null!==(D=a.getItemByXY(a.state.activeCoordinate))&&void 0!==D?D:{graphicalItem:k}).graphicalItem,L=I.item,B=void 0===L?t:L,R=I.childIndex,z=ey(ey(ey({},n.props),P),{},{activeIndex:R});return[(0,i.cloneElement)(B,z),null,null]}}return m?[k,null,null]:[k,null]}),ev(a,"renderCustomized",function(t,e,r){return(0,i.cloneElement)(t,ey(ey({key:"recharts-customized-".concat(r)},a.props),a.state))}),ev(a,"renderMap",{CartesianGrid:{handler:ew,once:!0},ReferenceArea:{handler:a.renderReferenceElement},ReferenceLine:{handler:ew},ReferenceDot:{handler:a.renderReferenceElement},XAxis:{handler:ew},YAxis:{handler:ew},Brush:{handler:a.renderBrush,once:!0},Bar:{handler:a.renderGraphicChild},Line:{handler:a.renderGraphicChild},Area:{handler:a.renderGraphicChild},Radar:{handler:a.renderGraphicChild},RadialBar:{handler:a.renderGraphicChild},Scatter:{handler:a.renderGraphicChild},Pie:{handler:a.renderGraphicChild},Funnel:{handler:a.renderGraphicChild},Tooltip:{handler:a.renderCursor,once:!0},PolarGrid:{handler:a.renderPolarGrid,once:!0},PolarAngleAxis:{handler:a.renderPolarAxis},PolarRadiusAxis:{handler:a.renderPolarAxis},Customized:{handler:a.renderCustomized}}),a.clipPathId="".concat(null!==(r=t.id)&&void 0!==r?r:(0,_.EL)("recharts"),"-clip"),a.throttleTriggeredAfterMouseMove=m()(a.triggeredAfterMouseMove,null!==(o=t.throttleDelay)&&void 0!==o?o:1e3/60),a.state={},a}return!function(t,e){if("function"!=typeof e&&null!==e)throw TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),Object.defineProperty(t,"prototype",{writable:!1}),e&&es(t,e)}(n,t),r=[{key:"componentDidMount",value:function(){var t,e;this.addListener(),this.accessibilityManager.setDetails({container:this.container,offset:{left:null!==(t=this.props.margin.left)&&void 0!==t?t:0,top:null!==(e=this.props.margin.top)&&void 0!==e?e:0},coordinateList:this.state.tooltipTicks,mouseHandlerCallback:this.triggeredAfterMouseMove,layout:this.props.layout}),this.displayDefaultTooltip()}},{key:"displayDefaultTooltip",value:function(){var t=this.props,e=t.children,r=t.data,n=t.height,o=t.layout,i=(0,E.sP)(e,O.u);if(i){var a=i.props.defaultIndex;if("number"==typeof a&&!(a<0)&&!(a>this.state.tooltipTicks.length-1)){var u=this.state.tooltipTicks[a]&&this.state.tooltipTicks[a].value,c=eP(this.state,r,a,u),l=this.state.tooltipTicks[a].coordinate,s=(this.state.offset.top+n)/2,f="horizontal"===o?{x:l,y:s}:{y:l,x:s},p=this.state.formattedGraphicalItems.find(function(t){return"Scatter"===t.item.type.name});p&&(f=ey(ey({},f),p.props.points[a].tooltipPosition),c=p.props.points[a].tooltipPayload);var h={activeTooltipIndex:a,isTooltipActive:!0,activeLabel:u,activePayload:c,activeCoordinate:f};this.setState(h),this.renderCursor(i),this.accessibilityManager.setIndex(a)}}}},{key:"getSnapshotBeforeUpdate",value:function(t,e){if(!this.props.accessibilityLayer)return null;if(this.state.tooltipTicks!==e.tooltipTicks&&this.accessibilityManager.setDetails({coordinateList:this.state.tooltipTicks}),this.props.layout!==t.layout&&this.accessibilityManager.setDetails({layout:this.props.layout}),this.props.margin!==t.margin){var r,n;this.accessibilityManager.setDetails({offset:{left:null!==(r=this.props.margin.left)&&void 0!==r?r:0,top:null!==(n=this.props.margin.top)&&void 0!==n?n:0}})}return null}},{key:"componentDidUpdate",value:function(t){(0,E.rL)([(0,E.sP)(t.children,O.u)],[(0,E.sP)(this.props.children,O.u)])||this.displayDefaultTooltip()}},{key:"componentWillUnmount",value:function(){this.removeListener(),this.throttleTriggeredAfterMouseMove.cancel()}},{key:"getTooltipEventType",value:function(){var t=(0,E.sP)(this.props.children,O.u);if(t&&"boolean"==typeof t.props.shared){var e=t.props.shared?"axis":"item";return c.indexOf(e)>=0?e:o}return o}},{key:"getMouseInfo",value:function(t){if(!this.container)return null;var e=this.container,r=e.getBoundingClientRect(),n=(0,V.os)(r),o={chartX:Math.round(t.pageX-n.left),chartY:Math.round(t.pageY-n.top)},i=r.width/e.offsetWidth||1,a=this.inRange(o.chartX,o.chartY,i);if(!a)return null;var u=this.state,c=u.xAxisMap,l=u.yAxisMap,s=this.getTooltipEventType(),f=eE(this.state,this.props.data,this.props.layout,a);if("axis"!==s&&c&&l){var p=(0,_.Kt)(c).scale,h=(0,_.Kt)(l).scale,d=p&&p.invert?p.invert(o.chartX):null,y=h&&h.invert?h.invert(o.chartY):null;return ey(ey({},o),{},{xValue:d,yValue:y},f)}return f?ey(ey({},o),f):null}},{key:"inRange",value:function(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1,n=this.props.layout,o=t/r,i=e/r;if("horizontal"===n||"vertical"===n){var a=this.state.offset;return o>=a.left&&o<=a.left+a.width&&i>=a.top&&i<=a.top+a.height?{x:o,y:i}:null}var u=this.state,c=u.angleAxisMap,l=u.radiusAxisMap;if(c&&l){var s=(0,_.Kt)(c);return(0,tq.z3)({x:o,y:i},s)}return null}},{key:"parseEventsOfWrapper",value:function(){var t=this.props.children,e=this.getTooltipEventType(),r=(0,E.sP)(t,O.u),n={};return r&&"axis"===e&&(n="click"===r.props.trigger?{onClick:this.handleClick}:{onMouseEnter:this.handleMouseEnter,onDoubleClick:this.handleDoubleClick,onMouseMove:this.handleMouseMove,onMouseLeave:this.handleMouseLeave,onTouchMove:this.handleTouchMove,onTouchStart:this.handleTouchStart,onTouchEnd:this.handleTouchEnd,onContextMenu:this.handleContextMenu}),ey(ey({},(0,tX.Ym)(this.props,this.handleOuterEvent)),n)}},{key:"addListener",value:function(){tY.on(tH,this.handleReceiveSyncEvent)}},{key:"removeListener",value:function(){tY.removeListener(tH,this.handleReceiveSyncEvent)}},{key:"filterFormatItem",value:function(t,e,r){for(var n=this.state.formattedGraphicalItems,o=0,i=n.length;ot.length)&&(e=t.length);for(var r=0,n=Array(e);r=0?1:-1;"insideStart"===u?(o=b+S*l,a=w):"insideEnd"===u?(o=g-S*l,a=!w):"end"===u&&(o=g+S*l,a=w),a=j<=0?a:!a;var P=(0,d.op)(p,y,O,o),E=(0,d.op)(p,y,O,o+(a?1:-1)*359),k="M".concat(P.x,",").concat(P.y,"\n A").concat(O,",").concat(O,",0,1,").concat(a?0:1,",\n ").concat(E.x,",").concat(E.y),A=i()(t.id)?(0,h.EL)("recharts-radial-line-"):t.id;return n.createElement("text",x({},r,{dominantBaseline:"central",className:(0,s.Z)("recharts-radial-bar-label",f)}),n.createElement("defs",null,n.createElement("path",{id:A,d:k})),n.createElement("textPath",{xlinkHref:"#".concat(A)},e))},j=function(t){var e=t.viewBox,r=t.offset,n=t.position,o=e.cx,i=e.cy,a=e.innerRadius,u=e.outerRadius,c=(e.startAngle+e.endAngle)/2;if("outside"===n){var l=(0,d.op)(o,i,u+r,c),s=l.x;return{x:s,y:l.y,textAnchor:s>=o?"start":"end",verticalAnchor:"middle"}}if("center"===n)return{x:o,y:i,textAnchor:"middle",verticalAnchor:"middle"};if("centerTop"===n)return{x:o,y:i,textAnchor:"middle",verticalAnchor:"start"};if("centerBottom"===n)return{x:o,y:i,textAnchor:"middle",verticalAnchor:"end"};var f=(0,d.op)(o,i,(a+u)/2,c);return{x:f.x,y:f.y,textAnchor:"middle",verticalAnchor:"middle"}},S=function(t){var e=t.viewBox,r=t.parentViewBox,n=t.offset,o=t.position,i=e.x,a=e.y,u=e.width,c=e.height,s=c>=0?1:-1,f=s*n,p=s>0?"end":"start",d=s>0?"start":"end",y=u>=0?1:-1,v=y*n,m=y>0?"end":"start",b=y>0?"start":"end";if("top"===o)return g(g({},{x:i+u/2,y:a-s*n,textAnchor:"middle",verticalAnchor:p}),r?{height:Math.max(a-r.y,0),width:u}:{});if("bottom"===o)return g(g({},{x:i+u/2,y:a+c+f,textAnchor:"middle",verticalAnchor:d}),r?{height:Math.max(r.y+r.height-(a+c),0),width:u}:{});if("left"===o){var x={x:i-v,y:a+c/2,textAnchor:m,verticalAnchor:"middle"};return g(g({},x),r?{width:Math.max(x.x-r.x,0),height:c}:{})}if("right"===o){var w={x:i+u+v,y:a+c/2,textAnchor:b,verticalAnchor:"middle"};return g(g({},w),r?{width:Math.max(r.x+r.width-w.x,0),height:c}:{})}var O=r?{width:u,height:c}:{};return"insideLeft"===o?g({x:i+v,y:a+c/2,textAnchor:b,verticalAnchor:"middle"},O):"insideRight"===o?g({x:i+u-v,y:a+c/2,textAnchor:m,verticalAnchor:"middle"},O):"insideTop"===o?g({x:i+u/2,y:a+f,textAnchor:"middle",verticalAnchor:d},O):"insideBottom"===o?g({x:i+u/2,y:a+c-f,textAnchor:"middle",verticalAnchor:p},O):"insideTopLeft"===o?g({x:i+v,y:a+f,textAnchor:b,verticalAnchor:d},O):"insideTopRight"===o?g({x:i+u-v,y:a+f,textAnchor:m,verticalAnchor:d},O):"insideBottomLeft"===o?g({x:i+v,y:a+c-f,textAnchor:b,verticalAnchor:p},O):"insideBottomRight"===o?g({x:i+u-v,y:a+c-f,textAnchor:m,verticalAnchor:p},O):l()(o)&&((0,h.hj)(o.x)||(0,h.hU)(o.x))&&((0,h.hj)(o.y)||(0,h.hU)(o.y))?g({x:i+(0,h.h1)(o.x,u),y:a+(0,h.h1)(o.y,c),textAnchor:"end",verticalAnchor:"end"},O):g({x:i+u/2,y:a+c/2,textAnchor:"middle",verticalAnchor:"middle"},O)};function P(t){var e,r=t.offset,o=g({offset:void 0===r?5:r},function(t,e){if(null==t)return{};var r,n,o=function(t,e){if(null==t)return{};var r={};for(var n in t)if(Object.prototype.hasOwnProperty.call(t,n)){if(e.indexOf(n)>=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(t,v)),a=o.viewBox,c=o.position,l=o.value,d=o.children,y=o.content,m=o.className,b=o.textBreakAll;if(!a||i()(l)&&i()(d)&&!(0,n.isValidElement)(y)&&!u()(y))return null;if((0,n.isValidElement)(y))return(0,n.cloneElement)(y,o);if(u()(y)){if(e=(0,n.createElement)(y,o),(0,n.isValidElement)(e))return e}else e=w(o);var P="cx"in a&&(0,h.hj)(a.cx),E=(0,p.L6)(o,!0);if(P&&("insideStart"===c||"insideEnd"===c||"end"===c))return O(o,e,E);var k=P?j(o):S(o);return n.createElement(f.x,x({className:(0,s.Z)("recharts-label",void 0===m?"":m)},E,k,{breakAll:b}),e)}P.displayName="Label";var E=function(t){var e=t.cx,r=t.cy,n=t.angle,o=t.startAngle,i=t.endAngle,a=t.r,u=t.radius,c=t.innerRadius,l=t.outerRadius,s=t.x,f=t.y,p=t.top,d=t.left,y=t.width,v=t.height,m=t.clockWise,b=t.labelViewBox;if(b)return b;if((0,h.hj)(y)&&(0,h.hj)(v)){if((0,h.hj)(s)&&(0,h.hj)(f))return{x:s,y:f,width:y,height:v};if((0,h.hj)(p)&&(0,h.hj)(d))return{x:p,y:d,width:y,height:v}}return(0,h.hj)(s)&&(0,h.hj)(f)?{x:s,y:f,width:0,height:0}:(0,h.hj)(e)&&(0,h.hj)(r)?{cx:e,cy:r,startAngle:o||n||0,endAngle:i||n||0,innerRadius:c||0,outerRadius:l||u||a||0,clockWise:m}:t.viewBox?t.viewBox:{}};P.parseViewBox=E,P.renderCallByParent=function(t,e){var r,o,i=!(arguments.length>2)||void 0===arguments[2]||arguments[2];if(!t||!t.children&&i&&!t.label)return null;var a=t.children,c=E(t),s=(0,p.NN)(a,P).map(function(t,r){return(0,n.cloneElement)(t,{viewBox:e||c,key:"label-".concat(r)})});return i?[(r=t.label,o=e||c,r?!0===r?n.createElement(P,{key:"label-implicit",viewBox:o}):(0,h.P2)(r)?n.createElement(P,{key:"label-implicit",viewBox:o,value:r}):(0,n.isValidElement)(r)?r.type===P?(0,n.cloneElement)(r,{key:"label-implicit",viewBox:o}):n.createElement(P,{key:"label-implicit",content:r,viewBox:o}):u()(r)?n.createElement(P,{key:"label-implicit",content:r,viewBox:o}):l()(r)?n.createElement(P,x({viewBox:o},r,{key:"label-implicit"})):null:null)].concat(function(t){if(Array.isArray(t))return m(t)}(s)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(s)||function(t,e){if(t){if("string"==typeof t)return m(t,void 0);var r=Object.prototype.toString.call(t).slice(8,-1);if("Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return m(t,void 0)}}(s)||function(){throw TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()):s}},58772:function(t,e,r){"use strict";r.d(e,{e:function(){return P}});var n=r(2265),o=r(77571),i=r.n(o),a=r(28302),u=r.n(a),c=r(86757),l=r.n(c),s=r(86185),f=r.n(s),p=r(26680),h=r(9841),d=r(82944),y=r(85355);function v(t){return(v="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var m=["valueAccessor"],b=["data","dataKey","clockWise","id","textBreakAll"];function g(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=Array(e);r=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}var S=function(t){return Array.isArray(t.value)?f()(t.value):t.value};function P(t){var e=t.valueAccessor,r=void 0===e?S:e,o=j(t,m),a=o.data,u=o.dataKey,c=o.clockWise,l=o.id,s=o.textBreakAll,f=j(o,b);return a&&a.length?n.createElement(h.m,{className:"recharts-label-list"},a.map(function(t,e){var o=i()(u)?r(t,e):(0,y.F$)(t&&t.payload,u),a=i()(l)?{}:{id:"".concat(l,"-").concat(e)};return n.createElement(p._,x({},(0,d.L6)(t,!0),f,a,{parentViewBox:t.parentViewBox,value:o,textBreakAll:s,viewBox:p._.parseViewBox(i()(c)?t:O(O({},t),{},{clockWise:c})),key:"label-".concat(e),index:e}))})):null}P.displayName="LabelList",P.renderCallByParent=function(t,e){var r,o=!(arguments.length>2)||void 0===arguments[2]||arguments[2];if(!t||!t.children&&o&&!t.label)return null;var i=t.children,a=(0,d.NN)(i,P).map(function(t,r){return(0,n.cloneElement)(t,{data:e,key:"labelList-".concat(r)})});return o?[(r=t.label)?!0===r?n.createElement(P,{key:"labelList-implicit",data:e}):n.isValidElement(r)||l()(r)?n.createElement(P,{key:"labelList-implicit",data:e,content:r}):u()(r)?n.createElement(P,x({data:e},r,{key:"labelList-implicit"})):null:null].concat(function(t){if(Array.isArray(t))return g(t)}(a)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(a)||function(t,e){if(t){if("string"==typeof t)return g(t,void 0);var r=Object.prototype.toString.call(t).slice(8,-1);if("Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return g(t,void 0)}}(a)||function(){throw TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()):a}},22190:function(t,e,r){"use strict";r.d(e,{D:function(){return N}});var n=r(2265),o=r(86757),i=r.n(o),a=r(61994),u=r(1175),c=r(48777),l=r(14870),s=r(41637);function f(t){return(f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function p(){return(p=Object.assign?Object.assign.bind():function(t){for(var e=1;e');var x=e.inactive?h:e.color;return n.createElement("li",p({className:b,style:y,key:"legend-item-".concat(r)},(0,s.bw)(t.props,e,r)),n.createElement(c.T,{width:o,height:o,viewBox:d,style:v},t.renderIcon(e)),n.createElement("span",{className:"recharts-legend-item-text",style:{color:x}},l?l(g,e,r):g))})}},{key:"render",value:function(){var t=this.props,e=t.payload,r=t.layout,o=t.align;return e&&e.length?n.createElement("ul",{className:"recharts-default-legend",style:{padding:0,margin:0,textAlign:"horizontal"===r?o:"left"}},this.renderItems()):null}}],function(t,e){for(var r=0;r1||Math.abs(e.height-this.lastBoundingBox.height)>1)&&(this.lastBoundingBox.width=e.width,this.lastBoundingBox.height=e.height,t&&t(e)):(-1!==this.lastBoundingBox.width||-1!==this.lastBoundingBox.height)&&(this.lastBoundingBox.width=-1,this.lastBoundingBox.height=-1,t&&t(null))}},{key:"getBBoxSnapshot",value:function(){return this.lastBoundingBox.width>=0&&this.lastBoundingBox.height>=0?P({},this.lastBoundingBox):{width:0,height:0}}},{key:"getDefaultPosition",value:function(t){var e,r,n=this.props,o=n.layout,i=n.align,a=n.verticalAlign,u=n.margin,c=n.chartWidth,l=n.chartHeight;return t&&(void 0!==t.left&&null!==t.left||void 0!==t.right&&null!==t.right)||(e="center"===i&&"vertical"===o?{left:((c||0)-this.getBBoxSnapshot().width)/2}:"right"===i?{right:u&&u.right||0}:{left:u&&u.left||0}),t&&(void 0!==t.top&&null!==t.top||void 0!==t.bottom&&null!==t.bottom)||(r="middle"===a?{top:((l||0)-this.getBBoxSnapshot().height)/2}:"bottom"===a?{bottom:u&&u.bottom||0}:{top:u&&u.top||0}),P(P({},e),r)}},{key:"render",value:function(){var t=this,e=this.props,r=e.content,o=e.width,i=e.height,a=e.wrapperStyle,u=e.payloadUniqBy,c=e.payload,l=P(P({position:"absolute",width:o||"auto",height:i||"auto"},this.getDefaultPosition(a)),a);return n.createElement("div",{className:"recharts-legend-wrapper",style:l,ref:function(e){t.wrapperNode=e}},function(t,e){if(n.isValidElement(t))return n.cloneElement(t,e);if("function"==typeof t)return n.createElement(t,e);e.ref;var r=function(t,e){if(null==t)return{};var r,n,o=function(t,e){if(null==t)return{};var r={};for(var n in t)if(Object.prototype.hasOwnProperty.call(t,n)){if(e.indexOf(n)>=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(e,j);return n.createElement(g,r)}(r,P(P({},this.props),{},{payload:(0,w.z)(c,u,C)})))}}],r=[{key:"getWithHeight",value:function(t,e){var r=P(P({},this.defaultProps),t.props).layout;return"vertical"===r&&(0,x.hj)(t.props.height)?{height:t.props.height}:"horizontal"===r?{width:t.props.width||e}:null}}],e&&E(o.prototype,e),r&&E(o,r),Object.defineProperty(o,"prototype",{writable:!1}),o}(n.PureComponent);_(N,"displayName","Legend"),_(N,"defaultProps",{iconSize:14,layout:"horizontal",align:"center",verticalAlign:"bottom"})},47625:function(t,e,r){"use strict";r.d(e,{h:function(){return d}});var n=r(61994),o=r(2265),i=r(37065),a=r.n(i),u=r(16630),c=r(1175),l=r(82944);function s(t){return(s="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function f(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),r.push.apply(r,n)}return r}function p(t){for(var e=1;et.length)&&(e=t.length);for(var r=0,n=Array(e);r0&&(t=a()(t,S,{trailing:!0,leading:!1}));var e=new ResizeObserver(t),r=M.current.getBoundingClientRect();return D(r.width,r.height),e.observe(M.current),function(){e.disconnect()}},[D,S]);var I=(0,o.useMemo)(function(){var t=C.containerWidth,e=C.containerHeight;if(t<0||e<0)return null;(0,c.Z)((0,u.hU)(y)||(0,u.hU)(m),"The width(%s) and height(%s) are both fixed numbers,\n maybe you don't need to use a ResponsiveContainer.",y,m),(0,c.Z)(!i||i>0,"The aspect(%s) must be greater than zero.",i);var r=(0,u.hU)(y)?t:y,n=(0,u.hU)(m)?e:m;i&&i>0&&(r?n=r/i:n&&(r=n*i),w&&n>w&&(n=w)),(0,c.Z)(r>0||n>0,"The width(%s) and height(%s) of chart should be greater than 0,\n please check the style of container, or the props width(%s) and height(%s),\n or add a minWidth(%s) or minHeight(%s) or use aspect(%s) to control the\n height and width.",r,n,y,m,g,x,i);var a=!Array.isArray(O)&&(0,l.Gf)(O.type).endsWith("Chart");return o.Children.map(O,function(t){return o.isValidElement(t)?(0,o.cloneElement)(t,p({width:r,height:n},a?{style:p({height:"100%",width:"100%",maxHeight:n,maxWidth:r},t.props.style)}:{})):t})},[i,O,m,w,x,g,C,y]);return o.createElement("div",{id:P?"".concat(P):void 0,className:(0,n.Z)("recharts-responsive-container",E),style:p(p({},void 0===A?{}:A),{},{width:y,height:m,minWidth:g,minHeight:x,maxHeight:w}),ref:M},I)})},58811:function(t,e,r){"use strict";r.d(e,{x:function(){return B}});var n=r(2265),o=r(77571),i=r.n(o),a=r(61994),u=r(16630),c=r(34067),l=r(82944),s=r(4094);function f(t){return(f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function p(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=r){var n,o,i,a,u=[],c=!0,l=!1;try{if(i=(r=r.call(t)).next,0===e){if(Object(r)!==r)return;c=!1}else for(;!(c=(n=i.call(r)).done)&&(u.push(n.value),u.length!==e);c=!0);}catch(t){l=!0,o=t}finally{try{if(!c&&null!=r.return&&(a=r.return(),Object(a)!==a))return}finally{if(l)throw o}}return u}}(t,e)||function(t,e){if(t){if("string"==typeof t)return h(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);if("Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return h(t,e)}}(t,e)||function(){throw TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function h(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=Array(e);r=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}function M(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=r){var n,o,i,a,u=[],c=!0,l=!1;try{if(i=(r=r.call(t)).next,0===e){if(Object(r)!==r)return;c=!1}else for(;!(c=(n=i.call(r)).done)&&(u.push(n.value),u.length!==e);c=!0);}catch(t){l=!0,o=t}finally{try{if(!c&&null!=r.return&&(a=r.return(),Object(a)!==a))return}finally{if(l)throw o}}return u}}(t,e)||function(t,e){if(t){if("string"==typeof t)return _(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);if("Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return _(t,e)}}(t,e)||function(){throw TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function _(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=Array(e);r0&&void 0!==arguments[0]?arguments[0]:[];return t.reduce(function(t,e){var i=e.word,a=e.width,u=t[t.length-1];return u&&(null==n||o||u.width+a+ra||e.reduce(function(t,e){return t.width>e.width?t:e}).width>Number(n),e]},y=0,v=c.length-1,m=0;y<=v&&m<=c.length-1;){var b=Math.floor((y+v)/2),g=M(d(b-1),2),x=g[0],w=g[1],O=M(d(b),1)[0];if(x||O||(y=b+1),x&&O&&(v=b-1),!x&&O){i=w;break}m++}return i||h},D=function(t){return[{words:i()(t)?[]:t.toString().split(T)}]},I=function(t){var e=t.width,r=t.scaleToFit,n=t.children,o=t.style,i=t.breakAll,a=t.maxLines;if((e||r)&&!c.x.isSsr){var u=C({breakAll:i,children:n,style:o});return u?N({breakAll:i,children:n,maxLines:a,style:o},u.wordsWithComputedWidth,u.spaceWidth,e,r):D(n)}return D(n)},L="#808080",B=function(t){var e,r=t.x,o=void 0===r?0:r,i=t.y,c=void 0===i?0:i,s=t.lineHeight,f=void 0===s?"1em":s,p=t.capHeight,h=void 0===p?"0.71em":p,d=t.scaleToFit,y=void 0!==d&&d,v=t.textAnchor,m=t.verticalAnchor,b=t.fill,g=void 0===b?L:b,x=A(t,P),w=(0,n.useMemo)(function(){return I({breakAll:x.breakAll,children:x.children,maxLines:x.maxLines,scaleToFit:y,style:x.style,width:x.width})},[x.breakAll,x.children,x.maxLines,y,x.style,x.width]),O=x.dx,j=x.dy,M=x.angle,_=x.className,T=x.breakAll,C=A(x,E);if(!(0,u.P2)(o)||!(0,u.P2)(c))return null;var N=o+((0,u.hj)(O)?O:0),D=c+((0,u.hj)(j)?j:0);switch(void 0===m?"end":m){case"start":e=S("calc(".concat(h,")"));break;case"middle":e=S("calc(".concat((w.length-1)/2," * -").concat(f," + (").concat(h," / 2))"));break;default:e=S("calc(".concat(w.length-1," * -").concat(f,")"))}var B=[];if(y){var R=w[0].width,z=x.width;B.push("scale(".concat(((0,u.hj)(z)?z/R:1)/R,")"))}return M&&B.push("rotate(".concat(M,", ").concat(N,", ").concat(D,")")),B.length&&(C.transform=B.join(" ")),n.createElement("text",k({},(0,l.L6)(C,!0),{x:N,y:D,className:(0,a.Z)("recharts-text",_),textAnchor:void 0===v?"start":v,fill:g.includes("url")?L:g}),w.map(function(t,r){var o=t.words.join(T?"":" ");return n.createElement("tspan",{x:N,dy:0===r?e:f,key:"".concat(o,"-").concat(r)},o)}))}},8147:function(t,e,r){"use strict";r.d(e,{u:function(){return $}});var n=r(2265),o=r(34935),i=r.n(o),a=r(77571),u=r.n(a),c=r(61994),l=r(16630);function s(t){return(s="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function f(){return(f=Object.assign?Object.assign.bind():function(t){for(var e=1;et.length)&&(e=t.length);for(var r=0,n=Array(e);rc[n]+s?Math.max(f,c[n]):Math.max(p,c[n])}function O(t){return(O="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function j(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),r.push.apply(r,n)}return r}function S(t){for(var e=1;e1||Math.abs(t.height-this.state.lastBoundingBox.height)>1)&&this.setState({lastBoundingBox:{width:t.width,height:t.height}})}else(-1!==this.state.lastBoundingBox.width||-1!==this.state.lastBoundingBox.height)&&this.setState({lastBoundingBox:{width:-1,height:-1}})}},{key:"componentDidMount",value:function(){document.addEventListener("keydown",this.handleKeyDown),this.updateBBox()}},{key:"componentWillUnmount",value:function(){document.removeEventListener("keydown",this.handleKeyDown)}},{key:"componentDidUpdate",value:function(){var t,e;this.props.active&&this.updateBBox(),this.state.dismissed&&((null===(t=this.props.coordinate)||void 0===t?void 0:t.x)!==this.state.dismissedAtCoordinate.x||(null===(e=this.props.coordinate)||void 0===e?void 0:e.y)!==this.state.dismissedAtCoordinate.y)&&(this.state.dismissed=!1)}},{key:"render",value:function(){var t,e,r,o,i,a,u,s,f,p,h,d,y,v,m,O,j,P,E,k=this,A=this.props,M=A.active,_=A.allowEscapeViewBox,T=A.animationDuration,C=A.animationEasing,N=A.children,D=A.coordinate,I=A.hasPayload,L=A.isAnimationActive,B=A.offset,R=A.position,z=A.reverseDirection,U=A.useTranslate3d,F=A.viewBox,$=A.wrapperStyle,q=(d=(t={allowEscapeViewBox:_,coordinate:D,offsetTopLeft:B,position:R,reverseDirection:z,tooltipBox:this.state.lastBoundingBox,useTranslate3d:U,viewBox:F}).allowEscapeViewBox,y=t.coordinate,v=t.offsetTopLeft,m=t.position,O=t.reverseDirection,j=t.tooltipBox,P=t.useTranslate3d,E=t.viewBox,j.height>0&&j.width>0&&y?(r=(e={translateX:p=w({allowEscapeViewBox:d,coordinate:y,key:"x",offsetTopLeft:v,position:m,reverseDirection:O,tooltipDimension:j.width,viewBox:E,viewBoxDimension:E.width}),translateY:h=w({allowEscapeViewBox:d,coordinate:y,key:"y",offsetTopLeft:v,position:m,reverseDirection:O,tooltipDimension:j.height,viewBox:E,viewBoxDimension:E.height}),useTranslate3d:P}).translateX,o=e.translateY,f={transform:e.useTranslate3d?"translate3d(".concat(r,"px, ").concat(o,"px, 0)"):"translate(".concat(r,"px, ").concat(o,"px)")}):f=x,{cssProperties:f,cssClasses:(a=(i={translateX:p,translateY:h,coordinate:y}).coordinate,u=i.translateX,s=i.translateY,(0,c.Z)(g,b(b(b(b({},"".concat(g,"-right"),(0,l.hj)(u)&&a&&(0,l.hj)(a.x)&&u>=a.x),"".concat(g,"-left"),(0,l.hj)(u)&&a&&(0,l.hj)(a.x)&&u=a.y),"".concat(g,"-top"),(0,l.hj)(s)&&a&&(0,l.hj)(a.y)&&s0;return n.createElement(_,{allowEscapeViewBox:i,animationDuration:a,animationEasing:u,isAnimationActive:f,active:o,coordinate:l,hasPayload:O,offset:p,position:y,reverseDirection:m,useTranslate3d:b,viewBox:g,wrapperStyle:x},(t=I(I({},this.props),{},{payload:w}),n.isValidElement(c)?n.cloneElement(c,t):"function"==typeof c?n.createElement(c,t):n.createElement(v,t)))}}],function(t,e){for(var r=0;r=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(t,a),s=(0,o.Z)("recharts-layer",c);return n.createElement("g",u({className:s},(0,i.L6)(l,!0),{ref:e}),r)})},48777:function(t,e,r){"use strict";r.d(e,{T:function(){return c}});var n=r(2265),o=r(61994),i=r(82944),a=["children","width","height","viewBox","className","style","title","desc"];function u(){return(u=Object.assign?Object.assign.bind():function(t){for(var e=1;e=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(t,a),y=l||{width:r,height:c,x:0,y:0},v=(0,o.Z)("recharts-surface",s);return n.createElement("svg",u({},(0,i.L6)(d,!0,"svg"),{className:v,width:r,height:c,style:f,viewBox:"".concat(y.x," ").concat(y.y," ").concat(y.width," ").concat(y.height)}),n.createElement("title",null,p),n.createElement("desc",null,h),e)}},25739:function(t,e,r){"use strict";r.d(e,{br:function(){return g},CW:function(){return O},Mw:function(){return A},zn:function(){return k},sp:function(){return x},qD:function(){return E},d2:function(){return P},bH:function(){return w},Ud:function(){return S},Nf:function(){return j}});var n=r(2265),o=r(69398),i=r(84173),a=r.n(i),u=r(32242),c=r.n(u),l=r(50967),s=r.n(l)()(function(t){return{x:t.left,y:t.top,width:t.width,height:t.height}},function(t){return["l",t.left,"t",t.top,"w",t.width,"h",t.height].join("")}),f=r(16630),p=(0,n.createContext)(void 0),h=(0,n.createContext)(void 0),d=(0,n.createContext)(void 0),y=(0,n.createContext)({}),v=(0,n.createContext)(void 0),m=(0,n.createContext)(0),b=(0,n.createContext)(0),g=function(t){var e=t.state,r=e.xAxisMap,o=e.yAxisMap,i=e.offset,a=t.clipPathId,u=t.children,c=t.width,l=t.height,f=s(i);return n.createElement(p.Provider,{value:r},n.createElement(h.Provider,{value:o},n.createElement(y.Provider,{value:i},n.createElement(d.Provider,{value:f},n.createElement(v.Provider,{value:a},n.createElement(m.Provider,{value:l},n.createElement(b.Provider,{value:c},u)))))))},x=function(){return(0,n.useContext)(v)},w=function(t){var e=(0,n.useContext)(p);null!=e||(0,o.Z)(!1);var r=e[t];return null!=r||(0,o.Z)(!1),r},O=function(){var t=(0,n.useContext)(p);return(0,f.Kt)(t)},j=function(){var t=(0,n.useContext)(h);return a()(t,function(t){return c()(t.domain,Number.isFinite)})||(0,f.Kt)(t)},S=function(t){var e=(0,n.useContext)(h);null!=e||(0,o.Z)(!1);var r=e[t];return null!=r||(0,o.Z)(!1),r},P=function(){return(0,n.useContext)(d)},E=function(){return(0,n.useContext)(y)},k=function(){return(0,n.useContext)(b)},A=function(){return(0,n.useContext)(m)}},57165:function(t,e,r){"use strict";r.d(e,{H:function(){return H}});var n=r(2265);function o(){}function i(t,e,r){t._context.bezierCurveTo((2*t._x0+t._x1)/3,(2*t._y0+t._y1)/3,(t._x0+2*t._x1)/3,(t._y0+2*t._y1)/3,(t._x0+4*t._x1+e)/6,(t._y0+4*t._y1+r)/6)}function a(t){this._context=t}function u(t){this._context=t}function c(t){this._context=t}a.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:i(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:i(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}},u.prototype={areaStart:o,areaEnd:o,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x2=t,this._y2=e;break;case 1:this._point=2,this._x3=t,this._y3=e;break;case 2:this._point=3,this._x4=t,this._y4=e,this._context.moveTo((this._x0+4*this._x1+t)/6,(this._y0+4*this._y1+e)/6);break;default:i(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}},c.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var r=(this._x0+4*this._x1+t)/6,n=(this._y0+4*this._y1+e)/6;this._line?this._context.lineTo(r,n):this._context.moveTo(r,n);break;case 3:this._point=4;default:i(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};class l{constructor(t,e){this._context=t,this._x=e}areaStart(){this._line=0}areaEnd(){this._line=NaN}lineStart(){this._point=0}lineEnd(){(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line}point(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;default:this._x?this._context.bezierCurveTo(this._x0=(this._x0+t)/2,this._y0,this._x0,e,t,e):this._context.bezierCurveTo(this._x0,this._y0=(this._y0+e)/2,t,this._y0,t,e)}this._x0=t,this._y0=e}}function s(t){this._context=t}function f(t){this._context=t}function p(t){return new f(t)}function h(t,e,r){var n=t._x1-t._x0,o=e-t._x1,i=(t._y1-t._y0)/(n||o<0&&-0),a=(r-t._y1)/(o||n<0&&-0);return((i<0?-1:1)+(a<0?-1:1))*Math.min(Math.abs(i),Math.abs(a),.5*Math.abs((i*o+a*n)/(n+o)))||0}function d(t,e){var r=t._x1-t._x0;return r?(3*(t._y1-t._y0)/r-e)/2:e}function y(t,e,r){var n=t._x0,o=t._y0,i=t._x1,a=t._y1,u=(i-n)/3;t._context.bezierCurveTo(n+u,o+u*e,i-u,a-u*r,i,a)}function v(t){this._context=t}function m(t){this._context=new b(t)}function b(t){this._context=t}function g(t){this._context=t}function x(t){var e,r,n=t.length-1,o=Array(n),i=Array(n),a=Array(n);for(o[0]=0,i[0]=2,a[0]=t[0]+2*t[1],e=1;e=0;--e)o[e]=(a[e]-o[e+1])/i[e];for(e=0,i[n-1]=(t[n]+o[n-1])/2;e=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,e),this._context.lineTo(t,e);else{var r=this._x*(1-this._t)+t*this._t;this._context.lineTo(r,this._y),this._context.lineTo(r,e)}}this._x=t,this._y=e}};var O=r(22516),j=r(76115),S=r(67790);function P(t){return t[0]}function E(t){return t[1]}function k(t,e){var r=(0,j.Z)(!0),n=null,o=p,i=null,a=(0,S.d)(u);function u(u){var c,l,s,f=(u=(0,O.Z)(u)).length,p=!1;for(null==n&&(i=o(s=a())),c=0;c<=f;++c)!(c=f;--p)u.point(m[p],b[p]);u.lineEnd(),u.areaEnd()}}v&&(m[s]=+t(h,s,l),b[s]=+e(h,s,l),u.point(n?+n(h,s,l):m[s],r?+r(h,s,l):b[s]))}if(d)return u=null,d+""||null}function s(){return k().defined(o).curve(a).context(i)}return t="function"==typeof t?t:void 0===t?P:(0,j.Z)(+t),e="function"==typeof e?e:void 0===e?(0,j.Z)(0):(0,j.Z)(+e),r="function"==typeof r?r:void 0===r?E:(0,j.Z)(+r),l.x=function(e){return arguments.length?(t="function"==typeof e?e:(0,j.Z)(+e),n=null,l):t},l.x0=function(e){return arguments.length?(t="function"==typeof e?e:(0,j.Z)(+e),l):t},l.x1=function(t){return arguments.length?(n=null==t?null:"function"==typeof t?t:(0,j.Z)(+t),l):n},l.y=function(t){return arguments.length?(e="function"==typeof t?t:(0,j.Z)(+t),r=null,l):e},l.y0=function(t){return arguments.length?(e="function"==typeof t?t:(0,j.Z)(+t),l):e},l.y1=function(t){return arguments.length?(r=null==t?null:"function"==typeof t?t:(0,j.Z)(+t),l):r},l.lineX0=l.lineY0=function(){return s().x(t).y(e)},l.lineY1=function(){return s().x(t).y(r)},l.lineX1=function(){return s().x(n).y(e)},l.defined=function(t){return arguments.length?(o="function"==typeof t?t:(0,j.Z)(!!t),l):o},l.curve=function(t){return arguments.length?(a=t,null!=i&&(u=a(i)),l):a},l.context=function(t){return arguments.length?(null==t?i=u=null:u=a(i=t),l):i},l}var M=r(75551),_=r.n(M),T=r(86757),C=r.n(T),N=r(61994),D=r(41637),I=r(82944),L=r(16630);function B(t){return(B="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function R(){return(R=Object.assign?Object.assign.bind():function(t){for(var e=1;et.length)&&(e=t.length);for(var r=0,n=Array(e);r=0?1:-1,c=r>=0?1:-1,l=n>=0&&r>=0||n<0&&r<0?1:0;if(a>0&&o instanceof Array){for(var s=[0,0,0,0],f=0;f<4;f++)s[f]=o[f]>a?a:o[f];i="M".concat(t,",").concat(e+u*s[0]),s[0]>0&&(i+="A ".concat(s[0],",").concat(s[0],",0,0,").concat(l,",").concat(t+c*s[0],",").concat(e)),i+="L ".concat(t+r-c*s[1],",").concat(e),s[1]>0&&(i+="A ".concat(s[1],",").concat(s[1],",0,0,").concat(l,",\n ").concat(t+r,",").concat(e+u*s[1])),i+="L ".concat(t+r,",").concat(e+n-u*s[2]),s[2]>0&&(i+="A ".concat(s[2],",").concat(s[2],",0,0,").concat(l,",\n ").concat(t+r-c*s[2],",").concat(e+n)),i+="L ".concat(t+c*s[3],",").concat(e+n),s[3]>0&&(i+="A ".concat(s[3],",").concat(s[3],",0,0,").concat(l,",\n ").concat(t,",").concat(e+n-u*s[3])),i+="Z"}else if(a>0&&o===+o&&o>0){var p=Math.min(a,o);i="M ".concat(t,",").concat(e+u*p,"\n A ").concat(p,",").concat(p,",0,0,").concat(l,",").concat(t+c*p,",").concat(e,"\n L ").concat(t+r-c*p,",").concat(e,"\n A ").concat(p,",").concat(p,",0,0,").concat(l,",").concat(t+r,",").concat(e+u*p,"\n L ").concat(t+r,",").concat(e+n-u*p,"\n A ").concat(p,",").concat(p,",0,0,").concat(l,",").concat(t+r-c*p,",").concat(e+n,"\n L ").concat(t+c*p,",").concat(e+n,"\n A ").concat(p,",").concat(p,",0,0,").concat(l,",").concat(t,",").concat(e+n-u*p," Z")}else i="M ".concat(t,",").concat(e," h ").concat(r," v ").concat(n," h ").concat(-r," Z");return i},h=function(t,e){if(!t||!e)return!1;var r=t.x,n=t.y,o=e.x,i=e.y,a=e.width,u=e.height;return!!(Math.abs(a)>0&&Math.abs(u)>0)&&r>=Math.min(o,o+a)&&r<=Math.max(o,o+a)&&n>=Math.min(i,i+u)&&n<=Math.max(i,i+u)},d={x:0,y:0,width:0,height:0,radius:0,isAnimationActive:!1,isUpdateAnimationActive:!1,animationBegin:0,animationDuration:1500,animationEasing:"ease"},y=function(t){var e,r=f(f({},d),t),u=(0,n.useRef)(),s=function(t){if(Array.isArray(t))return t}(e=(0,n.useState)(-1))||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=r){var n,o,i,a,u=[],c=!0,l=!1;try{for(i=(r=r.call(t)).next;!(c=(n=i.call(r)).done)&&(u.push(n.value),2!==u.length);c=!0);}catch(t){l=!0,o=t}finally{try{if(!c&&null!=r.return&&(a=r.return(),Object(a)!==a))return}finally{if(l)throw o}}return u}}(e,2)||function(t,e){if(t){if("string"==typeof t)return l(t,2);var r=Object.prototype.toString.call(t).slice(8,-1);if("Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return l(t,2)}}(e,2)||function(){throw TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}(),h=s[0],y=s[1];(0,n.useEffect)(function(){if(u.current&&u.current.getTotalLength)try{var t=u.current.getTotalLength();t&&y(t)}catch(t){}},[]);var v=r.x,m=r.y,b=r.width,g=r.height,x=r.radius,w=r.className,O=r.animationEasing,j=r.animationDuration,S=r.animationBegin,P=r.isAnimationActive,E=r.isUpdateAnimationActive;if(v!==+v||m!==+m||b!==+b||g!==+g||0===b||0===g)return null;var k=(0,o.Z)("recharts-rectangle",w);return E?n.createElement(i.ZP,{canBegin:h>0,from:{width:b,height:g,x:v,y:m},to:{width:b,height:g,x:v,y:m},duration:j,animationEasing:O,isActive:E},function(t){var e=t.width,o=t.height,l=t.x,s=t.y;return n.createElement(i.ZP,{canBegin:h>0,from:"0px ".concat(-1===h?1:h,"px"),to:"".concat(h,"px 0px"),attributeName:"strokeDasharray",begin:S,duration:j,isActive:P,easing:O},n.createElement("path",c({},(0,a.L6)(r,!0),{className:k,d:p(l,s,e,o,x),ref:u})))}):n.createElement("path",c({},(0,a.L6)(r,!0),{className:k,d:p(v,m,b,g,x)}))}},60474:function(t,e,r){"use strict";r.d(e,{L:function(){return v}});var n=r(2265),o=r(61994),i=r(82944),a=r(39206),u=r(16630);function c(t){return(c="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function l(){return(l=Object.assign?Object.assign.bind():function(t){for(var e=1;e180),",").concat(+(c>s),",\n ").concat(p.x,",").concat(p.y,"\n ");if(o>0){var d=(0,a.op)(r,n,o,c),y=(0,a.op)(r,n,o,s);h+="L ".concat(y.x,",").concat(y.y,"\n A ").concat(o,",").concat(o,",0,\n ").concat(+(Math.abs(l)>180),",").concat(+(c<=s),",\n ").concat(d.x,",").concat(d.y," Z")}else h+="L ".concat(r,",").concat(n," Z");return h},d=function(t){var e=t.cx,r=t.cy,n=t.innerRadius,o=t.outerRadius,i=t.cornerRadius,a=t.forceCornerRadius,c=t.cornerIsExternal,l=t.startAngle,s=t.endAngle,f=(0,u.uY)(s-l),d=p({cx:e,cy:r,radius:o,angle:l,sign:f,cornerRadius:i,cornerIsExternal:c}),y=d.circleTangency,v=d.lineTangency,m=d.theta,b=p({cx:e,cy:r,radius:o,angle:s,sign:-f,cornerRadius:i,cornerIsExternal:c}),g=b.circleTangency,x=b.lineTangency,w=b.theta,O=c?Math.abs(l-s):Math.abs(l-s)-m-w;if(O<0)return a?"M ".concat(v.x,",").concat(v.y,"\n a").concat(i,",").concat(i,",0,0,1,").concat(2*i,",0\n a").concat(i,",").concat(i,",0,0,1,").concat(-(2*i),",0\n "):h({cx:e,cy:r,innerRadius:n,outerRadius:o,startAngle:l,endAngle:s});var j="M ".concat(v.x,",").concat(v.y,"\n A").concat(i,",").concat(i,",0,0,").concat(+(f<0),",").concat(y.x,",").concat(y.y,"\n A").concat(o,",").concat(o,",0,").concat(+(O>180),",").concat(+(f<0),",").concat(g.x,",").concat(g.y,"\n A").concat(i,",").concat(i,",0,0,").concat(+(f<0),",").concat(x.x,",").concat(x.y,"\n ");if(n>0){var S=p({cx:e,cy:r,radius:n,angle:l,sign:f,isExternal:!0,cornerRadius:i,cornerIsExternal:c}),P=S.circleTangency,E=S.lineTangency,k=S.theta,A=p({cx:e,cy:r,radius:n,angle:s,sign:-f,isExternal:!0,cornerRadius:i,cornerIsExternal:c}),M=A.circleTangency,_=A.lineTangency,T=A.theta,C=c?Math.abs(l-s):Math.abs(l-s)-k-T;if(C<0&&0===i)return"".concat(j,"L").concat(e,",").concat(r,"Z");j+="L".concat(_.x,",").concat(_.y,"\n A").concat(i,",").concat(i,",0,0,").concat(+(f<0),",").concat(M.x,",").concat(M.y,"\n A").concat(n,",").concat(n,",0,").concat(+(C>180),",").concat(+(f>0),",").concat(P.x,",").concat(P.y,"\n A").concat(i,",").concat(i,",0,0,").concat(+(f<0),",").concat(E.x,",").concat(E.y,"Z")}else j+="L".concat(e,",").concat(r,"Z");return j},y={cx:0,cy:0,innerRadius:0,outerRadius:0,startAngle:0,endAngle:0,cornerRadius:0,forceCornerRadius:!1,cornerIsExternal:!1},v=function(t){var e,r=f(f({},y),t),a=r.cx,c=r.cy,s=r.innerRadius,p=r.outerRadius,v=r.cornerRadius,m=r.forceCornerRadius,b=r.cornerIsExternal,g=r.startAngle,x=r.endAngle,w=r.className;if(p0&&360>Math.abs(g-x)?d({cx:a,cy:c,innerRadius:s,outerRadius:p,cornerRadius:Math.min(S,j/2),forceCornerRadius:m,cornerIsExternal:b,startAngle:g,endAngle:x}):h({cx:a,cy:c,innerRadius:s,outerRadius:p,startAngle:g,endAngle:x}),n.createElement("path",l({},(0,i.L6)(r,!0),{className:O,d:e,role:"img"}))}},14870:function(t,e,r){"use strict";r.d(e,{v:function(){return N}});var n=r(2265),o=r(75551),i=r.n(o);let a=Math.cos,u=Math.sin,c=Math.sqrt,l=Math.PI,s=2*l;var f={draw(t,e){let r=c(e/l);t.moveTo(r,0),t.arc(0,0,r,0,s)}};let p=c(1/3),h=2*p,d=u(l/10)/u(7*l/10),y=u(s/10)*d,v=-a(s/10)*d,m=c(3),b=c(3)/2,g=1/c(12),x=(g/2+1)*3;var w=r(76115),O=r(67790);c(3),c(3);var j=r(61994),S=r(82944);function P(t){return(P="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var E=["type","size","sizeType"];function k(){return(k=Object.assign?Object.assign.bind():function(t){for(var e=1;e=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(t,E)),{},{type:o,size:u,sizeType:l}),p=s.className,h=s.cx,d=s.cy,y=(0,S.L6)(s,!0);return h===+h&&d===+d&&u===+u?n.createElement("path",k({},y,{className:(0,j.Z)("recharts-symbols",p),transform:"translate(".concat(h,", ").concat(d,")"),d:(e=_["symbol".concat(i()(o))]||f,(function(t,e){let r=null,n=(0,O.d)(o);function o(){let o;if(r||(r=o=n()),t.apply(this,arguments).draw(r,+e.apply(this,arguments)),o)return r=null,o+""||null}return t="function"==typeof t?t:(0,w.Z)(t||f),e="function"==typeof e?e:(0,w.Z)(void 0===e?64:+e),o.type=function(e){return arguments.length?(t="function"==typeof e?e:(0,w.Z)(e),o):t},o.size=function(t){return arguments.length?(e="function"==typeof t?t:(0,w.Z)(+t),o):e},o.context=function(t){return arguments.length?(r=null==t?null:t,o):r},o})().type(e).size(C(u,l,o))())})):null};N.registerSymbol=function(t,e){_["symbol".concat(i()(t))]=e}},11638:function(t,e,r){"use strict";r.d(e,{bn:function(){return C},a3:function(){return z},lT:function(){return N},V$:function(){return D},w7:function(){return I}});var n=r(2265),o=r(86757),i=r.n(o),a=r(90231),u=r.n(a),c=r(24342),l=r.n(c),s=r(21652),f=r.n(s),p=r(73649),h=r(61994),d=r(84735),y=r(82944);function v(t){return(v="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function m(){return(m=Object.assign?Object.assign.bind():function(t){for(var e=1;et.length)&&(e=t.length);for(var r=0,n=Array(e);r0,from:{upperWidth:0,lowerWidth:0,height:p,x:c,y:l},to:{upperWidth:s,lowerWidth:f,height:p,x:c,y:l},duration:j,animationEasing:g,isActive:P},function(t){var e=t.upperWidth,i=t.lowerWidth,u=t.height,c=t.x,l=t.y;return n.createElement(d.ZP,{canBegin:a>0,from:"0px ".concat(-1===a?1:a,"px"),to:"".concat(a,"px 0px"),attributeName:"strokeDasharray",begin:S,duration:j,easing:g},n.createElement("path",m({},(0,y.L6)(r,!0),{className:E,d:w(c,l,e,i,u),ref:o})))}):n.createElement("g",null,n.createElement("path",m({},(0,y.L6)(r,!0),{className:E,d:w(c,l,s,f,p)})))},S=r(60474),P=r(9841),E=r(14870),k=["option","shapeType","propTransformer","activeClassName","isActive"];function A(t){return(A="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function M(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),r.push.apply(r,n)}return r}function _(t){for(var e=1;e=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(t,k);if((0,n.isValidElement)(r))e=(0,n.cloneElement)(r,_(_({},f),(0,n.isValidElement)(r)?r.props:r));else if(i()(r))e=r(f);else if(u()(r)&&!l()(r)){var p=(void 0===a?function(t,e){return _(_({},e),t)}:a)(r,f);e=n.createElement(T,{shapeType:o,elementProps:p})}else e=n.createElement(T,{shapeType:o,elementProps:f});return s?n.createElement(P.m,{className:void 0===c?"recharts-active-shape":c},e):e}function N(t,e){return null!=e&&"trapezoids"in t.props}function D(t,e){return null!=e&&"sectors"in t.props}function I(t,e){return null!=e&&"points"in t.props}function L(t,e){var r,n,o=t.x===(null==e||null===(r=e.labelViewBox)||void 0===r?void 0:r.x)||t.x===e.x,i=t.y===(null==e||null===(n=e.labelViewBox)||void 0===n?void 0:n.y)||t.y===e.y;return o&&i}function B(t,e){var r=t.endAngle===e.endAngle,n=t.startAngle===e.startAngle;return r&&n}function R(t,e){var r=t.x===e.x,n=t.y===e.y,o=t.z===e.z;return r&&n&&o}function z(t){var e,r,n,o=t.activeTooltipItem,i=t.graphicalItem,a=t.itemData,u=(N(i,o)?e="trapezoids":D(i,o)?e="sectors":I(i,o)&&(e="points"),e),c=N(i,o)?null===(r=o.tooltipPayload)||void 0===r||null===(r=r[0])||void 0===r||null===(r=r.payload)||void 0===r?void 0:r.payload:D(i,o)?null===(n=o.tooltipPayload)||void 0===n||null===(n=n[0])||void 0===n||null===(n=n.payload)||void 0===n?void 0:n.payload:I(i,o)?o.payload:{},l=a.filter(function(t,e){var r=f()(c,t),n=i.props[u].filter(function(t){var e;return(N(i,o)?e=L:D(i,o)?e=B:I(i,o)&&(e=R),e)(t,o)}),a=i.props[u].indexOf(n[n.length-1]);return r&&e===a});return a.indexOf(l[l.length-1])}},25311:function(t,e,r){"use strict";r.d(e,{Ky:function(){return w},O1:function(){return b},_b:function(){return g},t9:function(){return m},xE:function(){return O}});var n=r(41443),o=r.n(n),i=r(32242),a=r.n(i),u=r(85355),c=r(82944),l=r(16630),s=r(31699);function f(t){return(f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function p(t,e){for(var r=0;r0&&(A=Math.min((t||0)-(M[e-1]||0),A))}),Number.isFinite(A)){var _=A/k,T="vertical"===g.layout?r.height:r.width;if("gap"===g.padding&&(c=_*T/2),"no-gap"===g.padding){var C=(0,l.h1)(t.barCategoryGap,_*T),N=_*T/2;c=N-C-(N-C)/T*C}}}s="xAxis"===n?[r.left+(j.left||0)+(c||0),r.left+r.width-(j.right||0)-(c||0)]:"yAxis"===n?"horizontal"===f?[r.top+r.height-(j.bottom||0),r.top+(j.top||0)]:[r.top+(j.top||0)+(c||0),r.top+r.height-(j.bottom||0)-(c||0)]:g.range,P&&(s=[s[1],s[0]]);var D=(0,u.Hq)(g,o,m),I=D.scale,L=D.realScaleType;I.domain(w).range(s),(0,u.zF)(I);var B=(0,u.g$)(I,d(d({},g),{},{realScaleType:L}));"xAxis"===n?(b="top"===x&&!S||"bottom"===x&&S,p=r.left,h=v[E]-b*g.height):"yAxis"===n&&(b="left"===x&&!S||"right"===x&&S,p=v[E]-b*g.width,h=r.top);var R=d(d(d({},g),B),{},{realScaleType:L,x:p,y:h,scale:I,width:"xAxis"===n?r.width:g.width,height:"yAxis"===n?r.height:g.height});return R.bandSize=(0,u.zT)(R,B),g.hide||"xAxis"!==n?g.hide||(v[E]+=(b?-1:1)*R.width):v[E]+=(b?-1:1)*R.height,d(d({},i),{},y({},a,R))},{})},b=function(t,e){var r=t.x,n=t.y,o=e.x,i=e.y;return{x:Math.min(r,o),y:Math.min(n,i),width:Math.abs(o-r),height:Math.abs(i-n)}},g=function(t){return b({x:t.x1,y:t.y1},{x:t.x2,y:t.y2})},x=function(){var t,e;function r(t){!function(t,e){if(!(t instanceof e))throw TypeError("Cannot call a class as a function")}(this,r),this.scale=t}return t=[{key:"domain",get:function(){return this.scale.domain}},{key:"range",get:function(){return this.scale.range}},{key:"rangeMin",get:function(){return this.range()[0]}},{key:"rangeMax",get:function(){return this.range()[1]}},{key:"bandwidth",get:function(){return this.scale.bandwidth}},{key:"apply",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=e.bandAware,n=e.position;if(void 0!==t){if(n)switch(n){case"start":default:return this.scale(t);case"middle":var o=this.bandwidth?this.bandwidth()/2:0;return this.scale(t)+o;case"end":var i=this.bandwidth?this.bandwidth():0;return this.scale(t)+i}if(r){var a=this.bandwidth?this.bandwidth()/2:0;return this.scale(t)+a}return this.scale(t)}}},{key:"isInRange",value:function(t){var e=this.range(),r=e[0],n=e[e.length-1];return r<=n?t>=r&&t<=n:t>=n&&t<=r}}],e=[{key:"create",value:function(t){return new r(t)}}],t&&p(r.prototype,t),e&&p(r,e),Object.defineProperty(r,"prototype",{writable:!1}),r}();y(x,"EPS",1e-4);var w=function(t){var e=Object.keys(t).reduce(function(e,r){return d(d({},e),{},y({},r,x.create(t[r])))},{});return d(d({},e),{},{apply:function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=r.bandAware,i=r.position;return o()(t,function(t,r){return e[r].apply(t,{bandAware:n,position:i})})},isInRange:function(t){return a()(t,function(t,r){return e[r].isInRange(t)})}})},O=function(t){var e=t.width,r=t.height,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,o=(n%180+180)%180*Math.PI/180,i=Math.atan(r/e);return Math.abs(o>i&&otx(e,t()).base(e.base()),tj.o.apply(e,arguments),e}},scaleOrdinal:function(){return tX.Z},scalePoint:function(){return f.x},scalePow:function(){return tJ},scaleQuantile:function(){return function t(){var e,r=[],n=[],o=[];function i(){var t=0,e=Math.max(1,n.length);for(o=Array(e-1);++t=1)return+r(t[n-1],n-1,t);var n,o=(n-1)*e,i=Math.floor(o),a=+r(t[i],i,t);return a+(+r(t[i+1],i+1,t)-a)*(o-i)}}(r,t/e);return a}function a(t){return null==t||isNaN(t=+t)?e:n[P(o,t)]}return a.invertExtent=function(t){var e=n.indexOf(t);return e<0?[NaN,NaN]:[e>0?o[e-1]:r[0],e=o?[i[o-1],n]:[i[e-1],i[e]]},u.unknown=function(t){return arguments.length&&(e=t),u},u.thresholds=function(){return i.slice()},u.copy=function(){return t().domain([r,n]).range(a).unknown(e)},tj.o.apply(tI(u),arguments)}},scaleRadial:function(){return function t(){var e,r=tO(),n=[0,1],o=!1;function i(t){var n,i=Math.sign(n=r(t))*Math.sqrt(Math.abs(n));return isNaN(i)?e:o?Math.round(i):i}return i.invert=function(t){return r.invert(t1(t))},i.domain=function(t){return arguments.length?(r.domain(t),i):r.domain()},i.range=function(t){return arguments.length?(r.range((n=Array.from(t,td)).map(t1)),i):n.slice()},i.rangeRound=function(t){return i.range(t).round(!0)},i.round=function(t){return arguments.length?(o=!!t,i):o},i.clamp=function(t){return arguments.length?(r.clamp(t),i):r.clamp()},i.unknown=function(t){return arguments.length?(e=t,i):e},i.copy=function(){return t(r.domain(),n).round(o).clamp(r.clamp()).unknown(e)},tj.o.apply(i,arguments),tI(i)}},scaleSequential:function(){return function t(){var e=tI(rX()(tv));return e.copy=function(){return rG(e,t())},tj.O.apply(e,arguments)}},scaleSequentialLog:function(){return function t(){var e=tZ(rX()).domain([1,10]);return e.copy=function(){return rG(e,t()).base(e.base())},tj.O.apply(e,arguments)}},scaleSequentialPow:function(){return rV},scaleSequentialQuantile:function(){return function t(){var e=[],r=tv;function n(t){if(null!=t&&!isNaN(t=+t))return r((P(e,t,1)-1)/(e.length-1))}return n.domain=function(t){if(!arguments.length)return e.slice();for(let r of(e=[],t))null==r||isNaN(r=+r)||e.push(r);return e.sort(g),n},n.interpolator=function(t){return arguments.length?(r=t,n):r},n.range=function(){return e.map((t,n)=>r(n/(e.length-1)))},n.quantiles=function(t){return Array.from({length:t+1},(r,n)=>(function(t,e,r){if(!(!(n=(t=Float64Array.from(function*(t,e){if(void 0===e)for(let e of t)null!=e&&(e=+e)>=e&&(yield e);else{let r=-1;for(let n of t)null!=(n=e(n,++r,t))&&(n=+n)>=n&&(yield n)}}(t,void 0))).length)||isNaN(e=+e))){if(e<=0||n<2)return t5(t);if(e>=1)return t2(t);var n,o=(n-1)*e,i=Math.floor(o),a=t2((function t(e,r,n=0,o=1/0,i){if(r=Math.floor(r),n=Math.floor(Math.max(0,n)),o=Math.floor(Math.min(e.length-1,o)),!(n<=r&&r<=o))return e;for(i=void 0===i?t6:function(t=g){if(t===g)return t6;if("function"!=typeof t)throw TypeError("compare is not a function");return(e,r)=>{let n=t(e,r);return n||0===n?n:(0===t(r,r))-(0===t(e,e))}}(i);o>n;){if(o-n>600){let a=o-n+1,u=r-n+1,c=Math.log(a),l=.5*Math.exp(2*c/3),s=.5*Math.sqrt(c*l*(a-l)/a)*(u-a/2<0?-1:1),f=Math.max(n,Math.floor(r-u*l/a+s)),p=Math.min(o,Math.floor(r+(a-u)*l/a+s));t(e,r,f,p,i)}let a=e[r],u=n,c=o;for(t3(e,n,r),i(e[o],a)>0&&t3(e,n,o);ui(e[u],a);)++u;for(;i(e[c],a)>0;)--c}0===i(e[n],a)?t3(e,n,c):t3(e,++c,o),c<=r&&(n=c+1),r<=c&&(o=c-1)}return e})(t,i).subarray(0,i+1));return a+(t5(t.subarray(i+1))-a)*(o-i)}})(e,n/t))},n.copy=function(){return t(r).domain(e)},tj.O.apply(n,arguments)}},scaleSequentialSqrt:function(){return rK},scaleSequentialSymlog:function(){return function t(){var e=tH(rX());return e.copy=function(){return rG(e,t()).constant(e.constant())},tj.O.apply(e,arguments)}},scaleSqrt:function(){return t0},scaleSymlog:function(){return function t(){var e=tH(tw());return e.copy=function(){return tx(e,t()).constant(e.constant())},tj.o.apply(e,arguments)}},scaleThreshold:function(){return function t(){var e,r=[.5],n=[0,1],o=1;function i(t){return null!=t&&t<=t?n[P(r,t,0,o)]:e}return i.domain=function(t){return arguments.length?(o=Math.min((r=Array.from(t)).length,n.length-1),i):r.slice()},i.range=function(t){return arguments.length?(n=Array.from(t),o=Math.min(r.length,n.length-1),i):n.slice()},i.invertExtent=function(t){var e=n.indexOf(t);return[r[e-1],r[e]]},i.unknown=function(t){return arguments.length?(e=t,i):e},i.copy=function(){return t().domain(r).range(n).unknown(e)},tj.o.apply(i,arguments)}},scaleTime:function(){return rY},scaleUtc:function(){return rH},tickFormat:function(){return tD}});var f=r(55284);let p=Math.sqrt(50),h=Math.sqrt(10),d=Math.sqrt(2);function y(t,e,r){let n,o,i;let a=(e-t)/Math.max(0,r),u=Math.floor(Math.log10(a)),c=a/Math.pow(10,u),l=c>=p?10:c>=h?5:c>=d?2:1;return(u<0?(n=Math.round(t*(i=Math.pow(10,-u)/l)),o=Math.round(e*i),n/ie&&--o,i=-i):(n=Math.round(t/(i=Math.pow(10,u)*l)),o=Math.round(e/i),n*ie&&--o),o0))return[];if(t===e)return[t];let n=e=o))return[];let u=i-o+1,c=Array(u);if(n){if(a<0)for(let t=0;te?1:t>=e?0:NaN}function x(t,e){return null==t||null==e?NaN:et?1:e>=t?0:NaN}function w(t){let e,r,n;function o(t,n,o=0,i=t.length){if(o>>1;0>r(t[e],n)?o=e+1:i=e}while(og(t(e),r),n=(e,r)=>t(e)-r):(e=t===g||t===x?t:O,r=t,n=t),{left:o,center:function(t,e,r=0,i=t.length){let a=o(t,e,r,i-1);return a>r&&n(t[a-1],e)>-n(t[a],e)?a-1:a},right:function(t,n,o=0,i=t.length){if(o>>1;0>=r(t[e],n)?o=e+1:i=e}while(o>8&15|e>>4&240,e>>4&15|240&e,(15&e)<<4|15&e,1):8===r?Z(e>>24&255,e>>16&255,e>>8&255,(255&e)/255):4===r?Z(e>>12&15|e>>8&240,e>>8&15|e>>4&240,e>>4&15|240&e,((15&e)<<4|15&e)/255):null):(e=N.exec(t))?new Y(e[1],e[2],e[3],1):(e=D.exec(t))?new Y(255*e[1]/100,255*e[2]/100,255*e[3]/100,1):(e=I.exec(t))?Z(e[1],e[2],e[3],e[4]):(e=L.exec(t))?Z(255*e[1]/100,255*e[2]/100,255*e[3]/100,e[4]):(e=B.exec(t))?Q(e[1],e[2]/100,e[3]/100,1):(e=R.exec(t))?Q(e[1],e[2]/100,e[3]/100,e[4]):z.hasOwnProperty(t)?q(z[t]):"transparent"===t?new Y(NaN,NaN,NaN,0):null}function q(t){return new Y(t>>16&255,t>>8&255,255&t,1)}function Z(t,e,r,n){return n<=0&&(t=e=r=NaN),new Y(t,e,r,n)}function W(t,e,r,n){var o;return 1==arguments.length?((o=t)instanceof A||(o=$(o)),o)?new Y((o=o.rgb()).r,o.g,o.b,o.opacity):new Y:new Y(t,e,r,null==n?1:n)}function Y(t,e,r,n){this.r=+t,this.g=+e,this.b=+r,this.opacity=+n}function H(){return`#${K(this.r)}${K(this.g)}${K(this.b)}`}function X(){let t=G(this.opacity);return`${1===t?"rgb(":"rgba("}${V(this.r)}, ${V(this.g)}, ${V(this.b)}${1===t?")":`, ${t})`}`}function G(t){return isNaN(t)?1:Math.max(0,Math.min(1,t))}function V(t){return Math.max(0,Math.min(255,Math.round(t)||0))}function K(t){return((t=V(t))<16?"0":"")+t.toString(16)}function Q(t,e,r,n){return n<=0?t=e=r=NaN:r<=0||r>=1?t=e=NaN:e<=0&&(t=NaN),new tt(t,e,r,n)}function J(t){if(t instanceof tt)return new tt(t.h,t.s,t.l,t.opacity);if(t instanceof A||(t=$(t)),!t)return new tt;if(t instanceof tt)return t;var e=(t=t.rgb()).r/255,r=t.g/255,n=t.b/255,o=Math.min(e,r,n),i=Math.max(e,r,n),a=NaN,u=i-o,c=(i+o)/2;return u?(a=e===i?(r-n)/u+(r0&&c<1?0:a,new tt(a,u,c,t.opacity)}function tt(t,e,r,n){this.h=+t,this.s=+e,this.l=+r,this.opacity=+n}function te(t){return(t=(t||0)%360)<0?t+360:t}function tr(t){return Math.max(0,Math.min(1,t||0))}function tn(t,e,r){return(t<60?e+(r-e)*t/60:t<180?r:t<240?e+(r-e)*(240-t)/60:e)*255}function to(t,e,r,n,o){var i=t*t,a=i*t;return((1-3*t+3*i-a)*e+(4-6*i+3*a)*r+(1+3*t+3*i-3*a)*n+a*o)/6}E(A,$,{copy(t){return Object.assign(new this.constructor,this,t)},displayable(){return this.rgb().displayable()},hex:U,formatHex:U,formatHex8:function(){return this.rgb().formatHex8()},formatHsl:function(){return J(this).formatHsl()},formatRgb:F,toString:F}),E(Y,W,k(A,{brighter(t){return t=null==t?1.4285714285714286:Math.pow(1.4285714285714286,t),new Y(this.r*t,this.g*t,this.b*t,this.opacity)},darker(t){return t=null==t?.7:Math.pow(.7,t),new Y(this.r*t,this.g*t,this.b*t,this.opacity)},rgb(){return this},clamp(){return new Y(V(this.r),V(this.g),V(this.b),G(this.opacity))},displayable(){return -.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:H,formatHex:H,formatHex8:function(){return`#${K(this.r)}${K(this.g)}${K(this.b)}${K((isNaN(this.opacity)?1:this.opacity)*255)}`},formatRgb:X,toString:X})),E(tt,function(t,e,r,n){return 1==arguments.length?J(t):new tt(t,e,r,null==n?1:n)},k(A,{brighter(t){return t=null==t?1.4285714285714286:Math.pow(1.4285714285714286,t),new tt(this.h,this.s,this.l*t,this.opacity)},darker(t){return t=null==t?.7:Math.pow(.7,t),new tt(this.h,this.s,this.l*t,this.opacity)},rgb(){var t=this.h%360+(this.h<0)*360,e=isNaN(t)||isNaN(this.s)?0:this.s,r=this.l,n=r+(r<.5?r:1-r)*e,o=2*r-n;return new Y(tn(t>=240?t-240:t+120,o,n),tn(t,o,n),tn(t<120?t+240:t-120,o,n),this.opacity)},clamp(){return new tt(te(this.h),tr(this.s),tr(this.l),G(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){let t=G(this.opacity);return`${1===t?"hsl(":"hsla("}${te(this.h)}, ${100*tr(this.s)}%, ${100*tr(this.l)}%${1===t?")":`, ${t})`}`}}));var ti=t=>()=>t;function ta(t,e){var r=e-t;return r?function(e){return t+e*r}:ti(isNaN(t)?e:t)}var tu=function t(e){var r,n=1==(r=+(r=e))?ta:function(t,e){var n,o,i;return e-t?(n=t,o=e,n=Math.pow(n,i=r),o=Math.pow(o,i)-n,i=1/i,function(t){return Math.pow(n+t*o,i)}):ti(isNaN(t)?e:t)};function o(t,e){var r=n((t=W(t)).r,(e=W(e)).r),o=n(t.g,e.g),i=n(t.b,e.b),a=ta(t.opacity,e.opacity);return function(e){return t.r=r(e),t.g=o(e),t.b=i(e),t.opacity=a(e),t+""}}return o.gamma=t,o}(1);function tc(t){return function(e){var r,n,o=e.length,i=Array(o),a=Array(o),u=Array(o);for(r=0;r=1?(r=1,e-1):Math.floor(r*e),o=t[n],i=t[n+1],a=n>0?t[n-1]:2*o-i,u=nu&&(a=e.slice(u,a),l[c]?l[c]+=a:l[++c]=a),(o=o[0])===(i=i[0])?l[c]?l[c]+=i:l[++c]=i:(l[++c]=null,s.push({i:c,x:tl(o,i)})),u=tf.lastIndex;return ue&&(r=t,t=e,e=r),l=function(r){return Math.max(t,Math.min(e,r))}),n=c>2?tg:tb,o=i=null,f}function f(e){return null==e||isNaN(e=+e)?r:(o||(o=n(a.map(t),u,c)))(t(l(e)))}return f.invert=function(r){return l(e((i||(i=n(u,a.map(t),tl)))(r)))},f.domain=function(t){return arguments.length?(a=Array.from(t,td),s()):a.slice()},f.range=function(t){return arguments.length?(u=Array.from(t),s()):u.slice()},f.rangeRound=function(t){return u=Array.from(t),c=th,s()},f.clamp=function(t){return arguments.length?(l=!!t||tv,s()):l!==tv},f.interpolate=function(t){return arguments.length?(c=t,s()):c},f.unknown=function(t){return arguments.length?(r=t,f):r},function(r,n){return t=r,e=n,s()}}function tO(){return tw()(tv,tv)}var tj=r(89999),tS=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function tP(t){var e;if(!(e=tS.exec(t)))throw Error("invalid format: "+t);return new tE({fill:e[1],align:e[2],sign:e[3],symbol:e[4],zero:e[5],width:e[6],comma:e[7],precision:e[8]&&e[8].slice(1),trim:e[9],type:e[10]})}function tE(t){this.fill=void 0===t.fill?" ":t.fill+"",this.align=void 0===t.align?">":t.align+"",this.sign=void 0===t.sign?"-":t.sign+"",this.symbol=void 0===t.symbol?"":t.symbol+"",this.zero=!!t.zero,this.width=void 0===t.width?void 0:+t.width,this.comma=!!t.comma,this.precision=void 0===t.precision?void 0:+t.precision,this.trim=!!t.trim,this.type=void 0===t.type?"":t.type+""}function tk(t,e){if((r=(t=e?t.toExponential(e-1):t.toExponential()).indexOf("e"))<0)return null;var r,n=t.slice(0,r);return[n.length>1?n[0]+n.slice(2):n,+t.slice(r+1)]}function tA(t){return(t=tk(Math.abs(t)))?t[1]:NaN}function tM(t,e){var r=tk(t,e);if(!r)return t+"";var n=r[0],o=r[1];return o<0?"0."+Array(-o).join("0")+n:n.length>o+1?n.slice(0,o+1)+"."+n.slice(o+1):n+Array(o-n.length+2).join("0")}tP.prototype=tE.prototype,tE.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(void 0===this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(void 0===this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};var t_={"%":(t,e)=>(100*t).toFixed(e),b:t=>Math.round(t).toString(2),c:t=>t+"",d:function(t){return Math.abs(t=Math.round(t))>=1e21?t.toLocaleString("en").replace(/,/g,""):t.toString(10)},e:(t,e)=>t.toExponential(e),f:(t,e)=>t.toFixed(e),g:(t,e)=>t.toPrecision(e),o:t=>Math.round(t).toString(8),p:(t,e)=>tM(100*t,e),r:tM,s:function(t,e){var r=tk(t,e);if(!r)return t+"";var o=r[0],i=r[1],a=i-(n=3*Math.max(-8,Math.min(8,Math.floor(i/3))))+1,u=o.length;return a===u?o:a>u?o+Array(a-u+1).join("0"):a>0?o.slice(0,a)+"."+o.slice(a):"0."+Array(1-a).join("0")+tk(t,Math.max(0,e+a-1))[0]},X:t=>Math.round(t).toString(16).toUpperCase(),x:t=>Math.round(t).toString(16)};function tT(t){return t}var tC=Array.prototype.map,tN=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"];function tD(t,e,r,n){var o,u,c=b(t,e,r);switch((n=tP(null==n?",f":n)).type){case"s":var l=Math.max(Math.abs(t),Math.abs(e));return null!=n.precision||isNaN(u=Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(tA(l)/3)))-tA(Math.abs(c))))||(n.precision=u),a(n,l);case"":case"e":case"g":case"p":case"r":null!=n.precision||isNaN(u=Math.max(0,tA(Math.abs(Math.max(Math.abs(t),Math.abs(e)))-(o=Math.abs(o=c)))-tA(o))+1)||(n.precision=u-("e"===n.type));break;case"f":case"%":null!=n.precision||isNaN(u=Math.max(0,-tA(Math.abs(c))))||(n.precision=u-("%"===n.type)*2)}return i(n)}function tI(t){var e=t.domain;return t.ticks=function(t){var r=e();return v(r[0],r[r.length-1],null==t?10:t)},t.tickFormat=function(t,r){var n=e();return tD(n[0],n[n.length-1],null==t?10:t,r)},t.nice=function(r){null==r&&(r=10);var n,o,i=e(),a=0,u=i.length-1,c=i[a],l=i[u],s=10;for(l0;){if((o=m(c,l,r))===n)return i[a]=c,i[u]=l,e(i);if(o>0)c=Math.floor(c/o)*o,l=Math.ceil(l/o)*o;else if(o<0)c=Math.ceil(c*o)/o,l=Math.floor(l*o)/o;else break;n=o}return t},t}function tL(){var t=tO();return t.copy=function(){return tx(t,tL())},tj.o.apply(t,arguments),tI(t)}function tB(t,e){t=t.slice();var r,n=0,o=t.length-1,i=t[n],a=t[o];return a-t(-e,r)}function tZ(t){let e,r;let n=t(tR,tz),o=n.domain,a=10;function u(){var i,u;return e=(i=a)===Math.E?Math.log:10===i&&Math.log10||2===i&&Math.log2||(i=Math.log(i),t=>Math.log(t)/i),r=10===(u=a)?t$:u===Math.E?Math.exp:t=>Math.pow(u,t),o()[0]<0?(e=tq(e),r=tq(r),t(tU,tF)):t(tR,tz),n}return n.base=function(t){return arguments.length?(a=+t,u()):a},n.domain=function(t){return arguments.length?(o(t),u()):o()},n.ticks=t=>{let n,i;let u=o(),c=u[0],l=u[u.length-1],s=l0){for(;f<=p;++f)for(n=1;nl)break;d.push(i)}}else for(;f<=p;++f)for(n=a-1;n>=1;--n)if(!((i=f>0?n/r(-f):n*r(f))l)break;d.push(i)}2*d.length{if(null==t&&(t=10),null==o&&(o=10===a?"s":","),"function"!=typeof o&&(a%1||null!=(o=tP(o)).precision||(o.trim=!0),o=i(o)),t===1/0)return o;let u=Math.max(1,a*t/n.ticks().length);return t=>{let n=t/r(Math.round(e(t)));return n*ao(tB(o(),{floor:t=>r(Math.floor(e(t))),ceil:t=>r(Math.ceil(e(t)))})),n}function tW(t){return function(e){return Math.sign(e)*Math.log1p(Math.abs(e/t))}}function tY(t){return function(e){return Math.sign(e)*Math.expm1(Math.abs(e))*t}}function tH(t){var e=1,r=t(tW(1),tY(e));return r.constant=function(r){return arguments.length?t(tW(e=+r),tY(e)):e},tI(r)}i=(o=function(t){var e,r,o,i=void 0===t.grouping||void 0===t.thousands?tT:(e=tC.call(t.grouping,Number),r=t.thousands+"",function(t,n){for(var o=t.length,i=[],a=0,u=e[0],c=0;o>0&&u>0&&(c+u+1>n&&(u=Math.max(1,n-c)),i.push(t.substring(o-=u,o+u)),!((c+=u+1)>n));)u=e[a=(a+1)%e.length];return i.reverse().join(r)}),a=void 0===t.currency?"":t.currency[0]+"",u=void 0===t.currency?"":t.currency[1]+"",c=void 0===t.decimal?".":t.decimal+"",l=void 0===t.numerals?tT:(o=tC.call(t.numerals,String),function(t){return t.replace(/[0-9]/g,function(t){return o[+t]})}),s=void 0===t.percent?"%":t.percent+"",f=void 0===t.minus?"−":t.minus+"",p=void 0===t.nan?"NaN":t.nan+"";function h(t){var e=(t=tP(t)).fill,r=t.align,o=t.sign,h=t.symbol,d=t.zero,y=t.width,v=t.comma,m=t.precision,b=t.trim,g=t.type;"n"===g?(v=!0,g="g"):t_[g]||(void 0===m&&(m=12),b=!0,g="g"),(d||"0"===e&&"="===r)&&(d=!0,e="0",r="=");var x="$"===h?a:"#"===h&&/[boxX]/.test(g)?"0"+g.toLowerCase():"",w="$"===h?u:/[%p]/.test(g)?s:"",O=t_[g],j=/[defgprs%]/.test(g);function S(t){var a,u,s,h=x,S=w;if("c"===g)S=O(t)+S,t="";else{var P=(t=+t)<0||1/t<0;if(t=isNaN(t)?p:O(Math.abs(t),m),b&&(t=function(t){e:for(var e,r=t.length,n=1,o=-1;n0&&(o=0)}return o>0?t.slice(0,o)+t.slice(e+1):t}(t)),P&&0==+t&&"+"!==o&&(P=!1),h=(P?"("===o?o:f:"-"===o||"("===o?"":o)+h,S=("s"===g?tN[8+n/3]:"")+S+(P&&"("===o?")":""),j){for(a=-1,u=t.length;++a(s=t.charCodeAt(a))||s>57){S=(46===s?c+t.slice(a+1):t.slice(a))+S,t=t.slice(0,a);break}}}v&&!d&&(t=i(t,1/0));var E=h.length+t.length+S.length,k=E>1)+h+t+S+k.slice(E);break;default:t=k+h+t+S}return l(t)}return m=void 0===m?6:/[gprs]/.test(g)?Math.max(1,Math.min(21,m)):Math.max(0,Math.min(20,m)),S.toString=function(){return t+""},S}return{format:h,formatPrefix:function(t,e){var r=h(((t=tP(t)).type="f",t)),n=3*Math.max(-8,Math.min(8,Math.floor(tA(e)/3))),o=Math.pow(10,-n),i=tN[8+n/3];return function(t){return r(o*t)+i}}}}({thousands:",",grouping:[3],currency:["$",""]})).format,a=o.formatPrefix;var tX=r(36967);function tG(t){return function(e){return e<0?-Math.pow(-e,t):Math.pow(e,t)}}function tV(t){return t<0?-Math.sqrt(-t):Math.sqrt(t)}function tK(t){return t<0?-t*t:t*t}function tQ(t){var e=t(tv,tv),r=1;return e.exponent=function(e){return arguments.length?1==(r=+e)?t(tv,tv):.5===r?t(tV,tK):t(tG(r),tG(1/r)):r},tI(e)}function tJ(){var t=tQ(tw());return t.copy=function(){return tx(t,tJ()).exponent(t.exponent())},tj.o.apply(t,arguments),t}function t0(){return tJ.apply(null,arguments).exponent(.5)}function t1(t){return Math.sign(t)*t*t}function t2(t,e){let r;if(void 0===e)for(let e of t)null!=e&&(r=e)&&(r=e);else{let n=-1;for(let o of t)null!=(o=e(o,++n,t))&&(r=o)&&(r=o)}return r}function t5(t,e){let r;if(void 0===e)for(let e of t)null!=e&&(r>e||void 0===r&&e>=e)&&(r=e);else{let n=-1;for(let o of t)null!=(o=e(o,++n,t))&&(r>o||void 0===r&&o>=o)&&(r=o)}return r}function t6(t,e){return(null==t||!(t>=t))-(null==e||!(e>=e))||(te?1:0)}function t3(t,e,r){let n=t[e];t[e]=t[r],t[r]=n}let t7=new Date,t4=new Date;function t8(t,e,r,n){function o(e){return t(e=0==arguments.length?new Date:new Date(+e)),e}return o.floor=e=>(t(e=new Date(+e)),e),o.ceil=r=>(t(r=new Date(r-1)),e(r,1),t(r),r),o.round=t=>{let e=o(t),r=o.ceil(t);return t-e(e(t=new Date(+t),null==r?1:Math.floor(r)),t),o.range=(r,n,i)=>{let a;let u=[];if(r=o.ceil(r),i=null==i?1:Math.floor(i),!(r0))return u;do u.push(a=new Date(+r)),e(r,i),t(r);while(at8(e=>{if(e>=e)for(;t(e),!r(e);)e.setTime(e-1)},(t,n)=>{if(t>=t){if(n<0)for(;++n<=0;)for(;e(t,-1),!r(t););else for(;--n>=0;)for(;e(t,1),!r(t););}}),r&&(o.count=(e,n)=>(t7.setTime(+e),t4.setTime(+n),t(t7),t(t4),Math.floor(r(t7,t4))),o.every=t=>isFinite(t=Math.floor(t))&&t>0?t>1?o.filter(n?e=>n(e)%t==0:e=>o.count(0,e)%t==0):o:null),o}let t9=t8(()=>{},(t,e)=>{t.setTime(+t+e)},(t,e)=>e-t);t9.every=t=>isFinite(t=Math.floor(t))&&t>0?t>1?t8(e=>{e.setTime(Math.floor(e/t)*t)},(e,r)=>{e.setTime(+e+r*t)},(e,r)=>(r-e)/t):t9:null,t9.range;let et=t8(t=>{t.setTime(t-t.getMilliseconds())},(t,e)=>{t.setTime(+t+1e3*e)},(t,e)=>(e-t)/1e3,t=>t.getUTCSeconds());et.range;let ee=t8(t=>{t.setTime(t-t.getMilliseconds()-1e3*t.getSeconds())},(t,e)=>{t.setTime(+t+6e4*e)},(t,e)=>(e-t)/6e4,t=>t.getMinutes());ee.range;let er=t8(t=>{t.setUTCSeconds(0,0)},(t,e)=>{t.setTime(+t+6e4*e)},(t,e)=>(e-t)/6e4,t=>t.getUTCMinutes());er.range;let en=t8(t=>{t.setTime(t-t.getMilliseconds()-1e3*t.getSeconds()-6e4*t.getMinutes())},(t,e)=>{t.setTime(+t+36e5*e)},(t,e)=>(e-t)/36e5,t=>t.getHours());en.range;let eo=t8(t=>{t.setUTCMinutes(0,0,0)},(t,e)=>{t.setTime(+t+36e5*e)},(t,e)=>(e-t)/36e5,t=>t.getUTCHours());eo.range;let ei=t8(t=>t.setHours(0,0,0,0),(t,e)=>t.setDate(t.getDate()+e),(t,e)=>(e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*6e4)/864e5,t=>t.getDate()-1);ei.range;let ea=t8(t=>{t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCDate(t.getUTCDate()+e)},(t,e)=>(e-t)/864e5,t=>t.getUTCDate()-1);ea.range;let eu=t8(t=>{t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCDate(t.getUTCDate()+e)},(t,e)=>(e-t)/864e5,t=>Math.floor(t/864e5));function ec(t){return t8(e=>{e.setDate(e.getDate()-(e.getDay()+7-t)%7),e.setHours(0,0,0,0)},(t,e)=>{t.setDate(t.getDate()+7*e)},(t,e)=>(e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*6e4)/6048e5)}eu.range;let el=ec(0),es=ec(1),ef=ec(2),ep=ec(3),eh=ec(4),ed=ec(5),ey=ec(6);function ev(t){return t8(e=>{e.setUTCDate(e.getUTCDate()-(e.getUTCDay()+7-t)%7),e.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCDate(t.getUTCDate()+7*e)},(t,e)=>(e-t)/6048e5)}el.range,es.range,ef.range,ep.range,eh.range,ed.range,ey.range;let em=ev(0),eb=ev(1),eg=ev(2),ex=ev(3),ew=ev(4),eO=ev(5),ej=ev(6);em.range,eb.range,eg.range,ex.range,ew.range,eO.range,ej.range;let eS=t8(t=>{t.setDate(1),t.setHours(0,0,0,0)},(t,e)=>{t.setMonth(t.getMonth()+e)},(t,e)=>e.getMonth()-t.getMonth()+(e.getFullYear()-t.getFullYear())*12,t=>t.getMonth());eS.range;let eP=t8(t=>{t.setUTCDate(1),t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCMonth(t.getUTCMonth()+e)},(t,e)=>e.getUTCMonth()-t.getUTCMonth()+(e.getUTCFullYear()-t.getUTCFullYear())*12,t=>t.getUTCMonth());eP.range;let eE=t8(t=>{t.setMonth(0,1),t.setHours(0,0,0,0)},(t,e)=>{t.setFullYear(t.getFullYear()+e)},(t,e)=>e.getFullYear()-t.getFullYear(),t=>t.getFullYear());eE.every=t=>isFinite(t=Math.floor(t))&&t>0?t8(e=>{e.setFullYear(Math.floor(e.getFullYear()/t)*t),e.setMonth(0,1),e.setHours(0,0,0,0)},(e,r)=>{e.setFullYear(e.getFullYear()+r*t)}):null,eE.range;let ek=t8(t=>{t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCFullYear(t.getUTCFullYear()+e)},(t,e)=>e.getUTCFullYear()-t.getUTCFullYear(),t=>t.getUTCFullYear());function eA(t,e,r,n,o,i){let a=[[et,1,1e3],[et,5,5e3],[et,15,15e3],[et,30,3e4],[i,1,6e4],[i,5,3e5],[i,15,9e5],[i,30,18e5],[o,1,36e5],[o,3,108e5],[o,6,216e5],[o,12,432e5],[n,1,864e5],[n,2,1728e5],[r,1,6048e5],[e,1,2592e6],[e,3,7776e6],[t,1,31536e6]];function u(e,r,n){let o=Math.abs(r-e)/n,i=w(([,,t])=>t).right(a,o);if(i===a.length)return t.every(b(e/31536e6,r/31536e6,n));if(0===i)return t9.every(Math.max(b(e,r,n),1));let[u,c]=a[o/a[i-1][2]isFinite(t=Math.floor(t))&&t>0?t8(e=>{e.setUTCFullYear(Math.floor(e.getUTCFullYear()/t)*t),e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)},(e,r)=>{e.setUTCFullYear(e.getUTCFullYear()+r*t)}):null,ek.range;let[eM,e_]=eA(ek,eP,em,eu,eo,er),[eT,eC]=eA(eE,eS,el,ei,en,ee);function eN(t){if(0<=t.y&&t.y<100){var e=new Date(-1,t.m,t.d,t.H,t.M,t.S,t.L);return e.setFullYear(t.y),e}return new Date(t.y,t.m,t.d,t.H,t.M,t.S,t.L)}function eD(t){if(0<=t.y&&t.y<100){var e=new Date(Date.UTC(-1,t.m,t.d,t.H,t.M,t.S,t.L));return e.setUTCFullYear(t.y),e}return new Date(Date.UTC(t.y,t.m,t.d,t.H,t.M,t.S,t.L))}function eI(t,e,r){return{y:t,m:e,d:r,H:0,M:0,S:0,L:0}}var eL={"-":"",_:" ",0:"0"},eB=/^\s*\d+/,eR=/^%/,ez=/[\\^$*+?|[\]().{}]/g;function eU(t,e,r){var n=t<0?"-":"",o=(n?-t:t)+"",i=o.length;return n+(i[t.toLowerCase(),e]))}function eZ(t,e,r){var n=eB.exec(e.slice(r,r+1));return n?(t.w=+n[0],r+n[0].length):-1}function eW(t,e,r){var n=eB.exec(e.slice(r,r+1));return n?(t.u=+n[0],r+n[0].length):-1}function eY(t,e,r){var n=eB.exec(e.slice(r,r+2));return n?(t.U=+n[0],r+n[0].length):-1}function eH(t,e,r){var n=eB.exec(e.slice(r,r+2));return n?(t.V=+n[0],r+n[0].length):-1}function eX(t,e,r){var n=eB.exec(e.slice(r,r+2));return n?(t.W=+n[0],r+n[0].length):-1}function eG(t,e,r){var n=eB.exec(e.slice(r,r+4));return n?(t.y=+n[0],r+n[0].length):-1}function eV(t,e,r){var n=eB.exec(e.slice(r,r+2));return n?(t.y=+n[0]+(+n[0]>68?1900:2e3),r+n[0].length):-1}function eK(t,e,r){var n=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(e.slice(r,r+6));return n?(t.Z=n[1]?0:-(n[2]+(n[3]||"00")),r+n[0].length):-1}function eQ(t,e,r){var n=eB.exec(e.slice(r,r+1));return n?(t.q=3*n[0]-3,r+n[0].length):-1}function eJ(t,e,r){var n=eB.exec(e.slice(r,r+2));return n?(t.m=n[0]-1,r+n[0].length):-1}function e0(t,e,r){var n=eB.exec(e.slice(r,r+2));return n?(t.d=+n[0],r+n[0].length):-1}function e1(t,e,r){var n=eB.exec(e.slice(r,r+3));return n?(t.m=0,t.d=+n[0],r+n[0].length):-1}function e2(t,e,r){var n=eB.exec(e.slice(r,r+2));return n?(t.H=+n[0],r+n[0].length):-1}function e5(t,e,r){var n=eB.exec(e.slice(r,r+2));return n?(t.M=+n[0],r+n[0].length):-1}function e6(t,e,r){var n=eB.exec(e.slice(r,r+2));return n?(t.S=+n[0],r+n[0].length):-1}function e3(t,e,r){var n=eB.exec(e.slice(r,r+3));return n?(t.L=+n[0],r+n[0].length):-1}function e7(t,e,r){var n=eB.exec(e.slice(r,r+6));return n?(t.L=Math.floor(n[0]/1e3),r+n[0].length):-1}function e4(t,e,r){var n=eR.exec(e.slice(r,r+1));return n?r+n[0].length:-1}function e8(t,e,r){var n=eB.exec(e.slice(r));return n?(t.Q=+n[0],r+n[0].length):-1}function e9(t,e,r){var n=eB.exec(e.slice(r));return n?(t.s=+n[0],r+n[0].length):-1}function rt(t,e){return eU(t.getDate(),e,2)}function re(t,e){return eU(t.getHours(),e,2)}function rr(t,e){return eU(t.getHours()%12||12,e,2)}function rn(t,e){return eU(1+ei.count(eE(t),t),e,3)}function ro(t,e){return eU(t.getMilliseconds(),e,3)}function ri(t,e){return ro(t,e)+"000"}function ra(t,e){return eU(t.getMonth()+1,e,2)}function ru(t,e){return eU(t.getMinutes(),e,2)}function rc(t,e){return eU(t.getSeconds(),e,2)}function rl(t){var e=t.getDay();return 0===e?7:e}function rs(t,e){return eU(el.count(eE(t)-1,t),e,2)}function rf(t){var e=t.getDay();return e>=4||0===e?eh(t):eh.ceil(t)}function rp(t,e){return t=rf(t),eU(eh.count(eE(t),t)+(4===eE(t).getDay()),e,2)}function rh(t){return t.getDay()}function rd(t,e){return eU(es.count(eE(t)-1,t),e,2)}function ry(t,e){return eU(t.getFullYear()%100,e,2)}function rv(t,e){return eU((t=rf(t)).getFullYear()%100,e,2)}function rm(t,e){return eU(t.getFullYear()%1e4,e,4)}function rb(t,e){var r=t.getDay();return eU((t=r>=4||0===r?eh(t):eh.ceil(t)).getFullYear()%1e4,e,4)}function rg(t){var e=t.getTimezoneOffset();return(e>0?"-":(e*=-1,"+"))+eU(e/60|0,"0",2)+eU(e%60,"0",2)}function rx(t,e){return eU(t.getUTCDate(),e,2)}function rw(t,e){return eU(t.getUTCHours(),e,2)}function rO(t,e){return eU(t.getUTCHours()%12||12,e,2)}function rj(t,e){return eU(1+ea.count(ek(t),t),e,3)}function rS(t,e){return eU(t.getUTCMilliseconds(),e,3)}function rP(t,e){return rS(t,e)+"000"}function rE(t,e){return eU(t.getUTCMonth()+1,e,2)}function rk(t,e){return eU(t.getUTCMinutes(),e,2)}function rA(t,e){return eU(t.getUTCSeconds(),e,2)}function rM(t){var e=t.getUTCDay();return 0===e?7:e}function r_(t,e){return eU(em.count(ek(t)-1,t),e,2)}function rT(t){var e=t.getUTCDay();return e>=4||0===e?ew(t):ew.ceil(t)}function rC(t,e){return t=rT(t),eU(ew.count(ek(t),t)+(4===ek(t).getUTCDay()),e,2)}function rN(t){return t.getUTCDay()}function rD(t,e){return eU(eb.count(ek(t)-1,t),e,2)}function rI(t,e){return eU(t.getUTCFullYear()%100,e,2)}function rL(t,e){return eU((t=rT(t)).getUTCFullYear()%100,e,2)}function rB(t,e){return eU(t.getUTCFullYear()%1e4,e,4)}function rR(t,e){var r=t.getUTCDay();return eU((t=r>=4||0===r?ew(t):ew.ceil(t)).getUTCFullYear()%1e4,e,4)}function rz(){return"+0000"}function rU(){return"%"}function rF(t){return+t}function r$(t){return Math.floor(+t/1e3)}function rq(t){return new Date(t)}function rZ(t){return t instanceof Date?+t:+new Date(+t)}function rW(t,e,r,n,o,i,a,u,c,l){var s=tO(),f=s.invert,p=s.domain,h=l(".%L"),d=l(":%S"),y=l("%I:%M"),v=l("%I %p"),m=l("%a %d"),b=l("%b %d"),g=l("%B"),x=l("%Y");function w(t){return(c(t)1)for(var r,n,o,i=1,a=t[e[0]],u=a.length;i=12)]},q:function(t){return 1+~~(t.getMonth()/3)},Q:rF,s:r$,S:rc,u:rl,U:rs,V:rp,w:rh,W:rd,x:null,X:null,y:ry,Y:rm,Z:rg,"%":rU},x={a:function(t){return a[t.getUTCDay()]},A:function(t){return i[t.getUTCDay()]},b:function(t){return c[t.getUTCMonth()]},B:function(t){return u[t.getUTCMonth()]},c:null,d:rx,e:rx,f:rP,g:rL,G:rR,H:rw,I:rO,j:rj,L:rS,m:rE,M:rk,p:function(t){return o[+(t.getUTCHours()>=12)]},q:function(t){return 1+~~(t.getUTCMonth()/3)},Q:rF,s:r$,S:rA,u:rM,U:r_,V:rC,w:rN,W:rD,x:null,X:null,y:rI,Y:rB,Z:rz,"%":rU},w={a:function(t,e,r){var n=h.exec(e.slice(r));return n?(t.w=d.get(n[0].toLowerCase()),r+n[0].length):-1},A:function(t,e,r){var n=f.exec(e.slice(r));return n?(t.w=p.get(n[0].toLowerCase()),r+n[0].length):-1},b:function(t,e,r){var n=m.exec(e.slice(r));return n?(t.m=b.get(n[0].toLowerCase()),r+n[0].length):-1},B:function(t,e,r){var n=y.exec(e.slice(r));return n?(t.m=v.get(n[0].toLowerCase()),r+n[0].length):-1},c:function(t,r,n){return S(t,e,r,n)},d:e0,e:e0,f:e7,g:eV,G:eG,H:e2,I:e2,j:e1,L:e3,m:eJ,M:e5,p:function(t,e,r){var n=l.exec(e.slice(r));return n?(t.p=s.get(n[0].toLowerCase()),r+n[0].length):-1},q:eQ,Q:e8,s:e9,S:e6,u:eW,U:eY,V:eH,w:eZ,W:eX,x:function(t,e,n){return S(t,r,e,n)},X:function(t,e,r){return S(t,n,e,r)},y:eV,Y:eG,Z:eK,"%":e4};function O(t,e){return function(r){var n,o,i,a=[],u=-1,c=0,l=t.length;for(r instanceof Date||(r=new Date(+r));++u53)return null;"w"in i||(i.w=1),"Z"in i?(n=(o=(n=eD(eI(i.y,0,1))).getUTCDay())>4||0===o?eb.ceil(n):eb(n),n=ea.offset(n,(i.V-1)*7),i.y=n.getUTCFullYear(),i.m=n.getUTCMonth(),i.d=n.getUTCDate()+(i.w+6)%7):(n=(o=(n=eN(eI(i.y,0,1))).getDay())>4||0===o?es.ceil(n):es(n),n=ei.offset(n,(i.V-1)*7),i.y=n.getFullYear(),i.m=n.getMonth(),i.d=n.getDate()+(i.w+6)%7)}else("W"in i||"U"in i)&&("w"in i||(i.w="u"in i?i.u%7:"W"in i?1:0),o="Z"in i?eD(eI(i.y,0,1)).getUTCDay():eN(eI(i.y,0,1)).getDay(),i.m=0,i.d="W"in i?(i.w+6)%7+7*i.W-(o+5)%7:i.w+7*i.U-(o+6)%7);return"Z"in i?(i.H+=i.Z/100|0,i.M+=i.Z%100,eD(i)):eN(i)}}function S(t,e,r,n){for(var o,i,a=0,u=e.length,c=r.length;a=c)return -1;if(37===(o=e.charCodeAt(a++))){if(!(i=w[(o=e.charAt(a++))in eL?e.charAt(a++):o])||(n=i(t,r,n))<0)return -1}else if(o!=r.charCodeAt(n++))return -1}return n}return g.x=O(r,g),g.X=O(n,g),g.c=O(e,g),x.x=O(r,x),x.X=O(n,x),x.c=O(e,x),{format:function(t){var e=O(t+="",g);return e.toString=function(){return t},e},parse:function(t){var e=j(t+="",!1);return e.toString=function(){return t},e},utcFormat:function(t){var e=O(t+="",x);return e.toString=function(){return t},e},utcParse:function(t){var e=j(t+="",!0);return e.toString=function(){return t},e}}}({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]})).format,u.parse,l=u.utcFormat,u.utcParse;var r2=r(22516),r5=r(76115);function r6(t){for(var e=t.length,r=Array(e);--e>=0;)r[e]=e;return r}function r3(t,e){return t[e]}function r7(t){let e=[];return e.key=t,e}var r4=r(95645),r8=r.n(r4),r9=r(99008),nt=r.n(r9),ne=r(77571),nr=r.n(ne),nn=r(86757),no=r.n(nn),ni=r(42715),na=r.n(ni),nu=r(13735),nc=r.n(nu),nl=r(11314),ns=r.n(nl),nf=r(82559),np=r.n(nf),nh=r(75551),nd=r.n(nh),ny=r(21652),nv=r.n(ny),nm=r(34935),nb=r.n(nm),ng=r(61134),nx=r.n(ng);function nw(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=Array(e);r=e?r.apply(void 0,o):t(e-a,nP(function(){for(var t=arguments.length,e=Array(t),n=0;nt.length)&&(e=t.length);for(var r=0,n=Array(e);rn&&(o=n,i=r),[o,i]}function nR(t,e,r){if(t.lte(0))return new(nx())(0);var n=nC.getDigitCount(t.toNumber()),o=new(nx())(10).pow(n),i=t.div(o),a=1!==n?.05:.1,u=new(nx())(Math.ceil(i.div(a).toNumber())).add(r).mul(a).mul(o);return e?u:new(nx())(Math.ceil(u))}function nz(t,e,r){var n=1,o=new(nx())(t);if(!o.isint()&&r){var i=Math.abs(t);i<1?(n=new(nx())(10).pow(nC.getDigitCount(t)-1),o=new(nx())(Math.floor(o.div(n).toNumber())).mul(n)):i>1&&(o=new(nx())(Math.floor(t)))}else 0===t?o=new(nx())(Math.floor((e-1)/2)):r||(o=new(nx())(Math.floor(t)));var a=Math.floor((e-1)/2);return nM(nA(function(t){return o.add(new(nx())(t-a).mul(n)).toNumber()}),nk)(0,e)}var nU=nT(function(t){var e=nD(t,2),r=e[0],n=e[1],o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:6,i=!(arguments.length>2)||void 0===arguments[2]||arguments[2],a=Math.max(o,2),u=nD(nB([r,n]),2),c=u[0],l=u[1];if(c===-1/0||l===1/0){var s=l===1/0?[c].concat(nN(nk(0,o-1).map(function(){return 1/0}))):[].concat(nN(nk(0,o-1).map(function(){return-1/0})),[l]);return r>n?n_(s):s}if(c===l)return nz(c,o,i);var f=function t(e,r,n,o){var i,a=arguments.length>4&&void 0!==arguments[4]?arguments[4]:0;if(!Number.isFinite((r-e)/(n-1)))return{step:new(nx())(0),tickMin:new(nx())(0),tickMax:new(nx())(0)};var u=nR(new(nx())(r).sub(e).div(n-1),o,a),c=Math.ceil((i=e<=0&&r>=0?new(nx())(0):(i=new(nx())(e).add(r).div(2)).sub(new(nx())(i).mod(u))).sub(e).div(u).toNumber()),l=Math.ceil(new(nx())(r).sub(i).div(u).toNumber()),s=c+l+1;return s>n?t(e,r,n,o,a+1):(s0?l+(n-s):l,c=r>0?c:c+(n-s)),{step:u,tickMin:i.sub(new(nx())(c).mul(u)),tickMax:i.add(new(nx())(l).mul(u))})}(c,l,a,i),p=f.step,h=f.tickMin,d=f.tickMax,y=nC.rangeStep(h,d.add(new(nx())(.1).mul(p)),p);return r>n?n_(y):y});nT(function(t){var e=nD(t,2),r=e[0],n=e[1],o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:6,i=!(arguments.length>2)||void 0===arguments[2]||arguments[2],a=Math.max(o,2),u=nD(nB([r,n]),2),c=u[0],l=u[1];if(c===-1/0||l===1/0)return[r,n];if(c===l)return nz(c,o,i);var s=nR(new(nx())(l).sub(c).div(a-1),i,0),f=nM(nA(function(t){return new(nx())(c).add(new(nx())(t).mul(s)).toNumber()}),nk)(0,a).filter(function(t){return t>=c&&t<=l});return r>n?n_(f):f});var nF=nT(function(t,e){var r=nD(t,2),n=r[0],o=r[1],i=!(arguments.length>2)||void 0===arguments[2]||arguments[2],a=nD(nB([n,o]),2),u=a[0],c=a[1];if(u===-1/0||c===1/0)return[n,o];if(u===c)return[u];var l=nR(new(nx())(c).sub(u).div(Math.max(e,2)-1),i,0),s=[].concat(nN(nC.rangeStep(new(nx())(u),new(nx())(c).sub(new(nx())(.99).mul(l)),l)),[c]);return n>o?n_(s):s}),n$=r(13137),nq=r(16630),nZ=r(82944),nW=r(38569);function nY(t){return(nY="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function nH(t){return function(t){if(Array.isArray(t))return nX(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||function(t,e){if(t){if("string"==typeof t)return nX(t,void 0);var r=Object.prototype.toString.call(t).slice(8,-1);if("Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return nX(t,void 0)}}(t)||function(){throw TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function nX(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=Array(e);r1&&void 0!==arguments[1]?arguments[1]:[],n=arguments.length>2?arguments[2]:void 0,o=arguments.length>3?arguments[3]:void 0,i=-1,a=null!==(e=null==r?void 0:r.length)&&void 0!==e?e:0;if(a<=1)return 0;if(o&&"angleAxis"===o.axisType&&1e-6>=Math.abs(Math.abs(o.range[1]-o.range[0])-360))for(var u=o.range,c=0;c0?n[c-1].coordinate:n[a-1].coordinate,s=n[c].coordinate,f=c>=a-1?n[0].coordinate:n[c+1].coordinate,p=void 0;if((0,nq.uY)(s-l)!==(0,nq.uY)(f-s)){var h=[];if((0,nq.uY)(f-s)===(0,nq.uY)(u[1]-u[0])){p=f;var d=s+u[1]-u[0];h[0]=Math.min(d,(d+l)/2),h[1]=Math.max(d,(d+l)/2)}else{p=l;var y=f+u[1]-u[0];h[0]=Math.min(s,(y+s)/2),h[1]=Math.max(s,(y+s)/2)}var v=[Math.min(s,(p+s)/2),Math.max(s,(p+s)/2)];if(t>v[0]&&t<=v[1]||t>=h[0]&&t<=h[1]){i=n[c].index;break}}else{var m=Math.min(l,f),b=Math.max(l,f);if(t>(m+s)/2&&t<=(b+s)/2){i=n[c].index;break}}}else for(var g=0;g0&&g(r[g].coordinate+r[g-1].coordinate)/2&&t<=(r[g].coordinate+r[g+1].coordinate)/2||g===a-1&&t>(r[g].coordinate+r[g-1].coordinate)/2){i=r[g].index;break}return i},n1=function(t){var e,r,n=t.type.displayName,o=null!==(e=t.type)&&void 0!==e&&e.defaultProps?nV(nV({},t.type.defaultProps),t.props):t.props,i=o.stroke,a=o.fill;switch(n){case"Line":r=i;break;case"Area":case"Radar":r=i&&"none"!==i?i:a;break;default:r=a}return r},n2=function(t){var e=t.barSize,r=t.totalSize,n=t.stackGroups,o=void 0===n?{}:n;if(!o)return{};for(var i={},a=Object.keys(o),u=0,c=a.length;u=0});if(v&&v.length){var m=v[0].type.defaultProps,b=void 0!==m?nV(nV({},m),v[0].props):v[0].props,g=b.barSize,x=b[y];i[x]||(i[x]=[]);var w=nr()(g)?e:g;i[x].push({item:v[0],stackList:v.slice(1),barSize:nr()(w)?void 0:(0,nq.h1)(w,r,0)})}}return i},n5=function(t){var e,r=t.barGap,n=t.barCategoryGap,o=t.bandSize,i=t.sizeList,a=void 0===i?[]:i,u=t.maxBarSize,c=a.length;if(c<1)return null;var l=(0,nq.h1)(r,o,0,!0),s=[];if(a[0].barSize===+a[0].barSize){var f=!1,p=o/c,h=a.reduce(function(t,e){return t+e.barSize||0},0);(h+=(c-1)*l)>=o&&(h-=(c-1)*l,l=0),h>=o&&p>0&&(f=!0,p*=.9,h=c*p);var d={offset:((o-h)/2>>0)-l,size:0};e=a.reduce(function(t,e){var r={item:e.item,position:{offset:d.offset+d.size+l,size:f?p:e.barSize}},n=[].concat(nH(t),[r]);return d=n[n.length-1].position,e.stackList&&e.stackList.length&&e.stackList.forEach(function(t){n.push({item:t,position:d})}),n},s)}else{var y=(0,nq.h1)(n,o,0,!0);o-2*y-(c-1)*l<=0&&(l=0);var v=(o-2*y-(c-1)*l)/c;v>1&&(v>>=0);var m=u===+u?Math.min(v,u):v;e=a.reduce(function(t,e,r){var n=[].concat(nH(t),[{item:e.item,position:{offset:y+(v+l)*r+(v-m)/2,size:m}}]);return e.stackList&&e.stackList.length&&e.stackList.forEach(function(t){n.push({item:t,position:n[n.length-1].position})}),n},s)}return e},n6=function(t,e,r,n){var o=r.children,i=r.width,a=r.margin,u=i-(a.left||0)-(a.right||0),c=(0,nW.z)({children:o,legendWidth:u});if(c){var l=n||{},s=l.width,f=l.height,p=c.align,h=c.verticalAlign,d=c.layout;if(("vertical"===d||"horizontal"===d&&"middle"===h)&&"center"!==p&&(0,nq.hj)(t[p]))return nV(nV({},t),{},nK({},p,t[p]+(s||0)));if(("horizontal"===d||"vertical"===d&&"center"===p)&&"middle"!==h&&(0,nq.hj)(t[h]))return nV(nV({},t),{},nK({},h,t[h]+(f||0)))}return t},n3=function(t,e,r,n,o){var i=e.props.children,a=(0,nZ.NN)(i,n$.W).filter(function(t){var e;return e=t.props.direction,!!nr()(o)||("horizontal"===n?"yAxis"===o:"vertical"===n||"x"===e?"xAxis"===o:"y"!==e||"yAxis"===o)});if(a&&a.length){var u=a.map(function(t){return t.props.dataKey});return t.reduce(function(t,e){var n=nQ(e,r);if(nr()(n))return t;var o=Array.isArray(n)?[nt()(n),r8()(n)]:[n,n],i=u.reduce(function(t,r){var n=nQ(e,r,0),i=o[0]-Math.abs(Array.isArray(n)?n[0]:n),a=o[1]+Math.abs(Array.isArray(n)?n[1]:n);return[Math.min(i,t[0]),Math.max(a,t[1])]},[1/0,-1/0]);return[Math.min(i[0],t[0]),Math.max(i[1],t[1])]},[1/0,-1/0])}return null},n7=function(t,e,r,n,o){var i=e.map(function(e){return n3(t,e,r,o,n)}).filter(function(t){return!nr()(t)});return i&&i.length?i.reduce(function(t,e){return[Math.min(t[0],e[0]),Math.max(t[1],e[1])]},[1/0,-1/0]):null},n4=function(t,e,r,n,o){var i=e.map(function(e){var i=e.props.dataKey;return"number"===r&&i&&n3(t,e,i,n)||nJ(t,i,r,o)});if("number"===r)return i.reduce(function(t,e){return[Math.min(t[0],e[0]),Math.max(t[1],e[1])]},[1/0,-1/0]);var a={};return i.reduce(function(t,e){for(var r=0,n=e.length;r=2?2*(0,nq.uY)(a[0]-a[1])*c:c,e&&(t.ticks||t.niceTicks))?(t.ticks||t.niceTicks).map(function(t){return{coordinate:n(o?o.indexOf(t):t)+c,value:t,offset:c}}).filter(function(t){return!np()(t.coordinate)}):t.isCategorical&&t.categoricalDomain?t.categoricalDomain.map(function(t,e){return{coordinate:n(t)+c,value:t,index:e,offset:c}}):n.ticks&&!r?n.ticks(t.tickCount).map(function(t){return{coordinate:n(t)+c,value:t,offset:c}}):n.domain().map(function(t,e){return{coordinate:n(t)+c,value:o?o[t]:t,index:e,offset:c}})},oe=new WeakMap,or=function(t,e){if("function"!=typeof e)return t;oe.has(t)||oe.set(t,new WeakMap);var r=oe.get(t);if(r.has(e))return r.get(e);var n=function(){t.apply(void 0,arguments),e.apply(void 0,arguments)};return r.set(e,n),n},on=function(t,e,r){var n=t.scale,o=t.type,i=t.layout,a=t.axisType;if("auto"===n)return"radial"===i&&"radiusAxis"===a?{scale:f.Z(),realScaleType:"band"}:"radial"===i&&"angleAxis"===a?{scale:tL(),realScaleType:"linear"}:"category"===o&&e&&(e.indexOf("LineChart")>=0||e.indexOf("AreaChart")>=0||e.indexOf("ComposedChart")>=0&&!r)?{scale:f.x(),realScaleType:"point"}:"category"===o?{scale:f.Z(),realScaleType:"band"}:{scale:tL(),realScaleType:"linear"};if(na()(n)){var u="scale".concat(nd()(n));return{scale:(s[u]||f.x)(),realScaleType:s[u]?u:"point"}}return no()(n)?{scale:n}:{scale:f.x(),realScaleType:"point"}},oo=function(t){var e=t.domain();if(e&&!(e.length<=2)){var r=e.length,n=t.range(),o=Math.min(n[0],n[1])-1e-4,i=Math.max(n[0],n[1])+1e-4,a=t(e[0]),u=t(e[r-1]);(ai||ui)&&t.domain([e[0],e[r-1]])}},oi=function(t,e){if(!t)return null;for(var r=0,n=t.length;rn)&&(o[1]=n),o[0]>n&&(o[0]=n),o[1]=0?(t[a][r][0]=o,t[a][r][1]=o+u,o=t[a][r][1]):(t[a][r][0]=i,t[a][r][1]=i+u,i=t[a][r][1])}},expand:function(t,e){if((n=t.length)>0){for(var r,n,o,i=0,a=t[0].length;i0){for(var r,n=0,o=t[e[0]],i=o.length;n0&&(n=(r=t[e[0]]).length)>0){for(var r,n,o,i=0,a=1;a=0?(t[i][r][0]=o,t[i][r][1]=o+a,o=t[i][r][1]):(t[i][r][0]=0,t[i][r][1]=0)}}},oc=function(t,e,r){var n=e.map(function(t){return t.props.dataKey}),o=ou[r];return(function(){var t=(0,r5.Z)([]),e=r6,r=r1,n=r3;function o(o){var i,a,u=Array.from(t.apply(this,arguments),r7),c=u.length,l=-1;for(let t of o)for(i=0,++l;i=0?0:o<0?o:n}return r[0]},od=function(t,e){var r,n=(null!==(r=t.type)&&void 0!==r&&r.defaultProps?nV(nV({},t.type.defaultProps),t.props):t.props).stackId;if((0,nq.P2)(n)){var o=e[n];if(o){var i=o.items.indexOf(t);return i>=0?o.stackedData[i]:null}}return null},oy=function(t,e,r){return Object.keys(t).reduce(function(n,o){var i=t[o].stackedData.reduce(function(t,n){var o=n.slice(e,r+1).reduce(function(t,e){return[nt()(e.concat([t[0]]).filter(nq.hj)),r8()(e.concat([t[1]]).filter(nq.hj))]},[1/0,-1/0]);return[Math.min(t[0],o[0]),Math.max(t[1],o[1])]},[1/0,-1/0]);return[Math.min(i[0],n[0]),Math.max(i[1],n[1])]},[1/0,-1/0]).map(function(t){return t===1/0||t===-1/0?0:t})},ov=/^dataMin[\s]*-[\s]*([0-9]+([.]{1}[0-9]+){0,1})$/,om=/^dataMax[\s]*\+[\s]*([0-9]+([.]{1}[0-9]+){0,1})$/,ob=function(t,e,r){if(no()(t))return t(e,r);if(!Array.isArray(t))return e;var n=[];if((0,nq.hj)(t[0]))n[0]=r?t[0]:Math.min(t[0],e[0]);else if(ov.test(t[0])){var o=+ov.exec(t[0])[1];n[0]=e[0]-o}else no()(t[0])?n[0]=t[0](e[0]):n[0]=e[0];if((0,nq.hj)(t[1]))n[1]=r?t[1]:Math.max(t[1],e[1]);else if(om.test(t[1])){var i=+om.exec(t[1])[1];n[1]=e[1]+i}else no()(t[1])?n[1]=t[1](e[1]):n[1]=e[1];return n},og=function(t,e,r){if(t&&t.scale&&t.scale.bandwidth){var n=t.scale.bandwidth();if(!r||n>0)return n}if(t&&e&&e.length>=2){for(var o=nb()(e,function(t){return t.coordinate}),i=1/0,a=1,u=o.length;a1&&void 0!==arguments[1]?arguments[1]:{};if(null==t||n.x.isSsr)return{width:0,height:0};var o=(Object.keys(e=a({},r)).forEach(function(t){e[t]||delete e[t]}),e),i=JSON.stringify({text:t,copyStyle:o});if(u.widthCache[i])return u.widthCache[i];try{var s=document.getElementById(l);s||((s=document.createElement("span")).setAttribute("id",l),s.setAttribute("aria-hidden","true"),document.body.appendChild(s));var f=a(a({},c),o);Object.assign(s.style,f),s.textContent="".concat(t);var p=s.getBoundingClientRect(),h={width:p.width,height:p.height};return u.widthCache[i]=h,++u.cacheCount>2e3&&(u.cacheCount=0,u.widthCache={}),h}catch(t){return{width:0,height:0}}},f=function(t){return{top:t.top+window.scrollY-document.documentElement.clientTop,left:t.left+window.scrollX-document.documentElement.clientLeft}}},16630:function(t,e,r){"use strict";r.d(e,{Ap:function(){return S},EL:function(){return g},Kt:function(){return w},P2:function(){return m},Rw:function(){return v},bv:function(){return O},fC:function(){return P},h1:function(){return x},hU:function(){return d},hj:function(){return y},k4:function(){return j},uY:function(){return h}});var n=r(42715),o=r.n(n),i=r(82559),a=r.n(i),u=r(13735),c=r.n(u),l=r(22345),s=r.n(l),f=r(77571),p=r.n(f),h=function(t){return 0===t?0:t>0?1:-1},d=function(t){return o()(t)&&t.indexOf("%")===t.length-1},y=function(t){return s()(t)&&!a()(t)},v=function(t){return p()(t)},m=function(t){return y(t)||o()(t)},b=0,g=function(t){var e=++b;return"".concat(t||"").concat(e)},x=function(t,e){var r,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,i=arguments.length>3&&void 0!==arguments[3]&&arguments[3];if(!y(t)&&!o()(t))return n;if(d(t)){var u=t.indexOf("%");r=e*parseFloat(t.slice(0,u))/100}else r=+t;return a()(r)&&(r=n),i&&r>e&&(r=e),r},w=function(t){if(!t)return null;var e=Object.keys(t);return e&&e.length?t[e[0]]:null},O=function(t){if(!Array.isArray(t))return!1;for(var e=t.length,r={},n=0;n2?r-2:0),o=2;ot.length)&&(e=t.length);for(var r=0,n=Array(e);r2&&void 0!==arguments[2]?arguments[2]:{top:0,right:0,bottom:0,left:0};return Math.min(Math.abs(t-(r.left||0)-(r.right||0)),Math.abs(e-(r.top||0)-(r.bottom||0)))/2},b=function(t,e,r,n,i){var a=t.width,u=t.height,s=t.startAngle,f=t.endAngle,y=(0,c.h1)(t.cx,a,a/2),v=(0,c.h1)(t.cy,u,u/2),b=m(a,u,r),g=(0,c.h1)(t.innerRadius,b,0),x=(0,c.h1)(t.outerRadius,b,.8*b);return Object.keys(e).reduce(function(t,r){var a,u=e[r],c=u.domain,m=u.reversed;if(o()(u.range))"angleAxis"===n?a=[s,f]:"radiusAxis"===n&&(a=[g,x]),m&&(a=[a[1],a[0]]);else{var b,w=function(t){if(Array.isArray(t))return t}(b=a=u.range)||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=r){var n,o,i,a,u=[],c=!0,l=!1;try{for(i=(r=r.call(t)).next;!(c=(n=i.call(r)).done)&&(u.push(n.value),2!==u.length);c=!0);}catch(t){l=!0,o=t}finally{try{if(!c&&null!=r.return&&(a=r.return(),Object(a)!==a))return}finally{if(l)throw o}}return u}}(b,2)||function(t,e){if(t){if("string"==typeof t)return d(t,2);var r=Object.prototype.toString.call(t).slice(8,-1);if("Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return d(t,2)}}(b,2)||function(){throw TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}();s=w[0],f=w[1]}var O=(0,l.Hq)(u,i),j=O.realScaleType,S=O.scale;S.domain(c).range(a),(0,l.zF)(S);var P=(0,l.g$)(S,p(p({},u),{},{realScaleType:j})),E=p(p(p({},u),P),{},{range:a,radius:x,realScaleType:j,scale:S,cx:y,cy:v,innerRadius:g,outerRadius:x,startAngle:s,endAngle:f});return p(p({},t),{},h({},r,E))},{})},g=function(t,e){var r=t.x,n=t.y;return Math.sqrt(Math.pow(r-e.x,2)+Math.pow(n-e.y,2))},x=function(t,e){var r=t.x,n=t.y,o=e.cx,i=e.cy,a=g({x:r,y:n},{x:o,y:i});if(a<=0)return{radius:a};var u=Math.acos((r-o)/a);return n>i&&(u=2*Math.PI-u),{radius:a,angle:180*u/Math.PI,angleInRadian:u}},w=function(t){var e=t.startAngle,r=t.endAngle,n=Math.min(Math.floor(e/360),Math.floor(r/360));return{startAngle:e-360*n,endAngle:r-360*n}},O=function(t,e){var r,n=x({x:t.x,y:t.y},e),o=n.radius,i=n.angle,a=e.innerRadius,u=e.outerRadius;if(ou)return!1;if(0===o)return!0;var c=w(e),l=c.startAngle,s=c.endAngle,f=i;if(l<=s){for(;f>s;)f-=360;for(;f=l&&f<=s}else{for(;f>l;)f-=360;for(;f=s&&f<=l}return r?p(p({},e),{},{radius:o,angle:f+360*Math.min(Math.floor(e.startAngle/360),Math.floor(e.endAngle/360))}):null},j=function(t){return(0,i.isValidElement)(t)||u()(t)||"boolean"==typeof t?"":t.className}},82944:function(t,e,r){"use strict";r.d(e,{$R:function(){return R},Bh:function(){return B},Gf:function(){return j},L6:function(){return N},NN:function(){return k},TT:function(){return M},eu:function(){return L},jf:function(){return T},rL:function(){return D},sP:function(){return A}});var n=r(13735),o=r.n(n),i=r(77571),a=r.n(i),u=r(42715),c=r.n(u),l=r(86757),s=r.n(l),f=r(28302),p=r.n(f),h=r(2265),d=r(14326),y=r(16630),v=r(46485),m=r(41637),b=["children"],g=["children"];function x(t,e){if(null==t)return{};var r,n,o=function(t,e){if(null==t)return{};var r={};for(var n in t)if(Object.prototype.hasOwnProperty.call(t,n)){if(e.indexOf(n)>=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}function w(t){return(w="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var O={click:"onClick",mousedown:"onMouseDown",mouseup:"onMouseUp",mouseover:"onMouseOver",mousemove:"onMouseMove",mouseout:"onMouseOut",mouseenter:"onMouseEnter",mouseleave:"onMouseLeave",touchcancel:"onTouchCancel",touchend:"onTouchEnd",touchmove:"onTouchMove",touchstart:"onTouchStart",contextmenu:"onContextMenu",dblclick:"onDoubleClick"},j=function(t){return"string"==typeof t?t:t?t.displayName||t.name||"Component":""},S=null,P=null,E=function t(e){if(e===S&&Array.isArray(P))return P;var r=[];return h.Children.forEach(e,function(e){a()(e)||((0,d.isFragment)(e)?r=r.concat(t(e.props.children)):r.push(e))}),P=r,S=e,r};function k(t,e){var r=[],n=[];return n=Array.isArray(e)?e.map(function(t){return j(t)}):[j(e)],E(t).forEach(function(t){var e=o()(t,"type.displayName")||o()(t,"type.name");-1!==n.indexOf(e)&&r.push(t)}),r}function A(t,e){var r=k(t,e);return r&&r[0]}var M=function(t){if(!t||!t.props)return!1;var e=t.props,r=e.width,n=e.height;return!!(0,y.hj)(r)&&!(r<=0)&&!!(0,y.hj)(n)&&!(n<=0)},_=["a","altGlyph","altGlyphDef","altGlyphItem","animate","animateColor","animateMotion","animateTransform","circle","clipPath","color-profile","cursor","defs","desc","ellipse","feBlend","feColormatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence","filter","font","font-face","font-face-format","font-face-name","font-face-url","foreignObject","g","glyph","glyphRef","hkern","image","line","lineGradient","marker","mask","metadata","missing-glyph","mpath","path","pattern","polygon","polyline","radialGradient","rect","script","set","stop","style","svg","switch","symbol","text","textPath","title","tref","tspan","use","view","vkern"],T=function(t){return t&&"object"===w(t)&&"clipDot"in t},C=function(t,e,r,n){var o,i=null!==(o=null===m.ry||void 0===m.ry?void 0:m.ry[n])&&void 0!==o?o:[];return e.startsWith("data-")||!s()(t)&&(n&&i.includes(e)||m.Yh.includes(e))||r&&m.nv.includes(e)},N=function(t,e,r){if(!t||"function"==typeof t||"boolean"==typeof t)return null;var n=t;if((0,h.isValidElement)(t)&&(n=t.props),!p()(n))return null;var o={};return Object.keys(n).forEach(function(t){var i;C(null===(i=n)||void 0===i?void 0:i[t],t,e,r)&&(o[t]=n[t])}),o},D=function t(e,r){if(e===r)return!0;var n=h.Children.count(e);if(n!==h.Children.count(r))return!1;if(0===n)return!0;if(1===n)return I(Array.isArray(e)?e[0]:e,Array.isArray(r)?r[0]:r);for(var o=0;o=0)r.push(t);else if(t){var i=j(t.type),a=e[i]||{},u=a.handler,l=a.once;if(u&&(!l||!n[i])){var s=u(t,i,o);r.push(s),n[i]=!0}}}),r},B=function(t){var e=t&&t.type;return e&&O[e]?O[e]:null},R=function(t,e){return E(e).indexOf(t)}},46485:function(t,e,r){"use strict";function n(t,e){for(var r in t)if(({}).hasOwnProperty.call(t,r)&&(!({}).hasOwnProperty.call(e,r)||t[r]!==e[r]))return!1;for(var n in e)if(({}).hasOwnProperty.call(e,n)&&!({}).hasOwnProperty.call(t,n))return!1;return!0}r.d(e,{w:function(){return n}})},38569:function(t,e,r){"use strict";r.d(e,{z:function(){return l}});var n=r(22190),o=r(85355),i=r(82944);function a(t){return(a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function u(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),r.push.apply(r,n)}return r}function c(t){for(var e=1;e=0))throw Error(`invalid digits: ${t}`);if(e>15)return a;let r=10**e;return function(t){this._+=t[0];for(let e=1,n=t.length;e1e-6){if(Math.abs(f*c-l*s)>1e-6&&i){let h=r-a,d=o-u,y=c*c+l*l,v=Math.sqrt(y),m=Math.sqrt(p),b=i*Math.tan((n-Math.acos((y+p-(h*h+d*d))/(2*v*m)))/2),g=b/m,x=b/v;Math.abs(g-1)>1e-6&&this._append`L${t+g*s},${e+g*f}`,this._append`A${i},${i},0,0,${+(f*h>s*d)},${this._x1=t+x*c},${this._y1=e+x*l}`}else this._append`L${this._x1=t},${this._y1=e}`}}arc(t,e,r,a,u,c){if(t=+t,e=+e,c=!!c,(r=+r)<0)throw Error(`negative radius: ${r}`);let l=r*Math.cos(a),s=r*Math.sin(a),f=t+l,p=e+s,h=1^c,d=c?a-u:u-a;null===this._x1?this._append`M${f},${p}`:(Math.abs(this._x1-f)>1e-6||Math.abs(this._y1-p)>1e-6)&&this._append`L${f},${p}`,r&&(d<0&&(d=d%o+o),d>i?this._append`A${r},${r},0,1,${h},${t-l},${e-s}A${r},${r},0,1,${h},${this._x1=f},${this._y1=p}`:d>1e-6&&this._append`A${r},${r},0,${+(d>=n)},${h},${this._x1=t+r*Math.cos(u)},${this._y1=e+r*Math.sin(u)}`)}rect(t,e,r,n){this._append`M${this._x0=this._x1=+t},${this._y0=this._y1=+e}h${r=+r}v${+n}h${-r}Z`}toString(){return this._}}function c(t){let e=3;return t.digits=function(r){if(!arguments.length)return e;if(null==r)e=null;else{let t=Math.floor(r);if(!(t>=0))throw RangeError(`invalid digits: ${r}`);e=t}return t},()=>new u(e)}u.prototype},59121:function(t,e,r){"use strict";r.d(e,{E:function(){return i}});var n=r(99649),o=r(63497);function i(t,e){let r=(0,n.Q)(t);return isNaN(e)?(0,o.L)(t,NaN):(e&&r.setDate(r.getDate()+e),r)}},31091:function(t,e,r){"use strict";r.d(e,{z:function(){return i}});var n=r(99649),o=r(63497);function i(t,e){let r=(0,n.Q)(t);if(isNaN(e))return(0,o.L)(t,NaN);if(!e)return r;let i=r.getDate(),a=(0,o.L)(t,r.getTime());return(a.setMonth(r.getMonth()+e+1,0),i>=a.getDate())?a:(r.setFullYear(a.getFullYear(),a.getMonth(),i),r)}},63497:function(t,e,r){"use strict";function n(t,e){return t instanceof Date?new t.constructor(e):new Date(e)}r.d(e,{L:function(){return n}})},99649:function(t,e,r){"use strict";function n(t){let e=Object.prototype.toString.call(t);return t instanceof Date||"object"==typeof t&&"[object Date]"===e?new t.constructor(+t):new Date("number"==typeof t||"[object Number]"===e||"string"==typeof t||"[object String]"===e?t:NaN)}r.d(e,{Q:function(){return n}})},69398:function(t,e,r){"use strict";function n(t,e){if(!t)throw Error("Invariant failed")}r.d(e,{Z:function(){return n}})}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1108-c2d0c742b6e72436.js b/litellm/proxy/_experimental/out/_next/static/chunks/1108-c2d0c742b6e72436.js deleted file mode 100644 index efdd028104e..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/1108-c2d0c742b6e72436.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1108],{40278:function(t,e,r){"use strict";r.d(e,{Z:function(){return S}});var n=r(5853),o=r(7084),i=r(26898),a=r(13241),u=r(1153),c=r(2265),l=r(47625),s=r(93765),f=r(31699),p=r(97059),h=r(62994),d=r(25311),y=(0,s.z)({chartName:"BarChart",GraphicalChild:f.$,defaultTooltipEventType:"axis",validateTooltipEventTypes:["axis","item"],axisComponents:[{axisType:"xAxis",AxisComp:p.K},{axisType:"yAxis",AxisComp:h.B}],formatAxisMap:d.t9}),v=r(56940),m=r(26680),b=r(8147),g=r(22190),x=r(65278),w=r(98593),O=r(92666),j=r(32644);let S=c.forwardRef((t,e)=>{let{data:r=[],categories:s=[],index:d,colors:S=i.s,valueFormatter:P=u.Cj,layout:E="horizontal",stack:k=!1,relative:A=!1,startEndOnly:M=!1,animationDuration:_=900,showAnimation:T=!1,showXAxis:C=!0,showYAxis:N=!0,yAxisWidth:D=56,intervalType:I="equidistantPreserveStart",showTooltip:L=!0,showLegend:B=!0,showGridLines:R=!0,autoMinValue:z=!1,minValue:U,maxValue:F,allowDecimals:$=!0,noDataText:q,onValueChange:Z,enableLegendSlider:W=!1,customTooltip:Y,rotateLabelX:H,barCategoryGap:X,tickGap:G=5,xAxisLabel:V,yAxisLabel:K,className:Q,padding:J=C||N?{left:20,right:20}:{left:0,right:0}}=t,tt=(0,n._T)(t,["data","categories","index","colors","valueFormatter","layout","stack","relative","startEndOnly","animationDuration","showAnimation","showXAxis","showYAxis","yAxisWidth","intervalType","showTooltip","showLegend","showGridLines","autoMinValue","minValue","maxValue","allowDecimals","noDataText","onValueChange","enableLegendSlider","customTooltip","rotateLabelX","barCategoryGap","tickGap","xAxisLabel","yAxisLabel","className","padding"]),[te,tr]=(0,c.useState)(60),tn=(0,j.me)(s,S),[to,ti]=c.useState(void 0),[ta,tu]=(0,c.useState)(void 0),tc=!!Z;function tl(t,e,r){var n,o,i,a;r.stopPropagation(),Z&&((0,j.vZ)(to,Object.assign(Object.assign({},t.payload),{value:t.value}))?(tu(void 0),ti(void 0),null==Z||Z(null)):(tu(null===(o=null===(n=t.tooltipPayload)||void 0===n?void 0:n[0])||void 0===o?void 0:o.dataKey),ti(Object.assign(Object.assign({},t.payload),{value:t.value})),null==Z||Z(Object.assign({eventType:"bar",categoryClicked:null===(a=null===(i=t.tooltipPayload)||void 0===i?void 0:i[0])||void 0===a?void 0:a.dataKey},t.payload))))}let ts=(0,j.i4)(z,U,F);return c.createElement("div",Object.assign({ref:e,className:(0,a.q)("w-full h-80",Q)},tt),c.createElement(l.h,{className:"h-full w-full"},(null==r?void 0:r.length)?c.createElement(y,{barCategoryGap:X,data:r,stackOffset:k?"sign":A?"expand":"none",layout:"vertical"===E?"vertical":"horizontal",onClick:tc&&(ta||to)?()=>{ti(void 0),tu(void 0),null==Z||Z(null)}:void 0,margin:{bottom:V?30:void 0,left:K?20:void 0,right:K?5:void 0,top:5}},R?c.createElement(v.q,{className:(0,a.q)("stroke-1","stroke-tremor-border","dark:stroke-dark-tremor-border"),horizontal:"vertical"!==E,vertical:"vertical"===E}):null,"vertical"!==E?c.createElement(p.K,{padding:J,hide:!C,dataKey:d,interval:M?"preserveStartEnd":I,tick:{transform:"translate(0, 6)"},ticks:M?[r[0][d],r[r.length-1][d]]:void 0,fill:"",stroke:"",className:(0,a.q)("mt-4 text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickLine:!1,axisLine:!1,angle:null==H?void 0:H.angle,dy:null==H?void 0:H.verticalShift,height:null==H?void 0:H.xAxisHeight,minTickGap:G},V&&c.createElement(m._,{position:"insideBottom",offset:-20,className:"fill-tremor-content-emphasis text-tremor-default font-medium dark:fill-dark-tremor-content-emphasis"},V)):c.createElement(p.K,{hide:!C,type:"number",tick:{transform:"translate(-3, 0)"},domain:ts,fill:"",stroke:"",className:(0,a.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickLine:!1,axisLine:!1,tickFormatter:P,minTickGap:G,allowDecimals:$,angle:null==H?void 0:H.angle,dy:null==H?void 0:H.verticalShift,height:null==H?void 0:H.xAxisHeight},V&&c.createElement(m._,{position:"insideBottom",offset:-20,className:"fill-tremor-content-emphasis text-tremor-default font-medium dark:fill-dark-tremor-content-emphasis"},V)),"vertical"!==E?c.createElement(h.B,{width:D,hide:!N,axisLine:!1,tickLine:!1,type:"number",domain:ts,tick:{transform:"translate(-3, 0)"},fill:"",stroke:"",className:(0,a.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickFormatter:A?t=>"".concat((100*t).toString()," %"):P,allowDecimals:$},K&&c.createElement(m._,{position:"insideLeft",style:{textAnchor:"middle"},angle:-90,offset:-15,className:"fill-tremor-content-emphasis text-tremor-default font-medium dark:fill-dark-tremor-content-emphasis"},K)):c.createElement(h.B,{width:D,hide:!N,dataKey:d,axisLine:!1,tickLine:!1,ticks:M?[r[0][d],r[r.length-1][d]]:void 0,type:"category",interval:"preserveStartEnd",tick:{transform:"translate(0, 6)"},fill:"",stroke:"",className:(0,a.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content")},K&&c.createElement(m._,{position:"insideLeft",style:{textAnchor:"middle"},angle:-90,offset:-15,className:"fill-tremor-content-emphasis text-tremor-default font-medium dark:fill-dark-tremor-content-emphasis"},K)),c.createElement(b.u,{wrapperStyle:{outline:"none"},isAnimationActive:!1,cursor:{fill:"#d1d5db",opacity:"0.15"},content:L?t=>{let{active:e,payload:r,label:n}=t;return Y?c.createElement(Y,{payload:null==r?void 0:r.map(t=>{var e;return Object.assign(Object.assign({},t),{color:null!==(e=tn.get(t.dataKey))&&void 0!==e?e:o.fr.Gray})}),active:e,label:n}):c.createElement(w.ZP,{active:e,payload:r,label:n,valueFormatter:P,categoryColors:tn})}:c.createElement(c.Fragment,null),position:{y:0}}),B?c.createElement(g.D,{verticalAlign:"top",height:te,content:t=>{let{payload:e}=t;return(0,x.Z)({payload:e},tn,tr,ta,tc?t=>{tc&&(t!==ta||to?(tu(t),null==Z||Z({eventType:"category",categoryClicked:t})):(tu(void 0),null==Z||Z(null)),ti(void 0))}:void 0,W)}}):null,s.map(t=>{var e;return c.createElement(f.$,{className:(0,a.q)((0,u.bM)(null!==(e=tn.get(t))&&void 0!==e?e:o.fr.Gray,i.K.background).fillColor,Z?"cursor-pointer":""),key:t,name:t,type:"linear",stackId:k||A?"a":void 0,dataKey:t,fill:"",isAnimationActive:T,animationDuration:_,shape:t=>((t,e,r,n)=>{let{fillOpacity:o,name:i,payload:a,value:u}=t,{x:l,width:s,y:f,height:p}=t;return"horizontal"===n&&p<0?(f+=p,p=Math.abs(p)):"vertical"===n&&s<0&&(l+=s,s=Math.abs(s)),c.createElement("rect",{x:l,y:f,width:s,height:p,opacity:e||r&&r!==i?(0,j.vZ)(e,Object.assign(Object.assign({},a),{value:u}))?o:.3:o})})(t,to,ta,E),onClick:tl})})):c.createElement(O.Z,{noDataText:q})))});S.displayName="BarChart"},65278:function(t,e,r){"use strict";r.d(e,{Z:function(){return y}});var n=r(2265);let o=t=>{n.useEffect(()=>{let e=()=>{t()};return e(),window.addEventListener("resize",e),()=>window.removeEventListener("resize",e)},[t])};var i=r(5853),a=r(26898),u=r(13241),c=r(1153);let l=t=>{var e=(0,i._T)(t,[]);return n.createElement("svg",Object.assign({},e,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"}),n.createElement("path",{d:"M8 12L14 6V18L8 12Z"}))},s=t=>{var e=(0,i._T)(t,[]);return n.createElement("svg",Object.assign({},e,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"}),n.createElement("path",{d:"M16 12L10 18V6L16 12Z"}))},f=(0,c.fn)("Legend"),p=t=>{let{name:e,color:r,onClick:o,activeLegend:i}=t,l=!!o;return n.createElement("li",{className:(0,u.q)(f("legendItem"),"group inline-flex items-center px-2 py-0.5 rounded-tremor-small transition whitespace-nowrap",l?"cursor-pointer":"cursor-default","text-tremor-content",l?"hover:bg-tremor-background-subtle":"","dark:text-dark-tremor-content",l?"dark:hover:bg-dark-tremor-background-subtle":""),onClick:t=>{t.stopPropagation(),null==o||o(e,r)}},n.createElement("svg",{className:(0,u.q)("flex-none h-2 w-2 mr-1.5",(0,c.bM)(r,a.K.text).textColor,i&&i!==e?"opacity-40":"opacity-100"),fill:"currentColor",viewBox:"0 0 8 8"},n.createElement("circle",{cx:4,cy:4,r:4})),n.createElement("p",{className:(0,u.q)("whitespace-nowrap truncate text-tremor-default","text-tremor-content",l?"group-hover:text-tremor-content-emphasis":"","dark:text-dark-tremor-content",i&&i!==e?"opacity-40":"opacity-100",l?"dark:group-hover:text-dark-tremor-content-emphasis":"")},e))},h=t=>{let{icon:e,onClick:r,disabled:o}=t,[i,a]=n.useState(!1),c=n.useRef(null);return n.useEffect(()=>(i?c.current=setInterval(()=>{null==r||r()},300):clearInterval(c.current),()=>clearInterval(c.current)),[i,r]),(0,n.useEffect)(()=>{o&&(clearInterval(c.current),a(!1))},[o]),n.createElement("button",{type:"button",className:(0,u.q)(f("legendSliderButton"),"w-5 group inline-flex items-center truncate rounded-tremor-small transition",o?"cursor-not-allowed":"cursor-pointer",o?"text-tremor-content-subtle":"text-tremor-content hover:text-tremor-content-emphasis hover:bg-tremor-background-subtle",o?"dark:text-dark-tremor-subtle":"dark:text-dark-tremor dark:hover:text-tremor-content-emphasis dark:hover:bg-dark-tremor-background-subtle"),disabled:o,onClick:t=>{t.stopPropagation(),null==r||r()},onMouseDown:t=>{t.stopPropagation(),a(!0)},onMouseUp:t=>{t.stopPropagation(),a(!1)}},n.createElement(e,{className:"w-full"}))},d=n.forwardRef((t,e)=>{let{categories:r,colors:o=a.s,className:c,onClickLegendItem:d,activeLegend:y,enableLegendSlider:v=!1}=t,m=(0,i._T)(t,["categories","colors","className","onClickLegendItem","activeLegend","enableLegendSlider"]),b=n.useRef(null),g=n.useRef(null),[x,w]=n.useState(null),[O,j]=n.useState(null),S=n.useRef(null),P=(0,n.useCallback)(()=>{let t=null==b?void 0:b.current;t&&w({left:t.scrollLeft>0,right:t.scrollWidth-t.clientWidth>t.scrollLeft})},[w]),E=(0,n.useCallback)(t=>{var e,r;let n=null==b?void 0:b.current,o=null==g?void 0:g.current,i=null!==(e=null==n?void 0:n.clientWidth)&&void 0!==e?e:0,a=null!==(r=null==o?void 0:o.clientWidth)&&void 0!==r?r:0;n&&v&&(n.scrollTo({left:"left"===t?n.scrollLeft-i+a:n.scrollLeft+i-a,behavior:"smooth"}),setTimeout(()=>{P()},400))},[v,P]);n.useEffect(()=>{let t=t=>{"ArrowLeft"===t?E("left"):"ArrowRight"===t&&E("right")};return O?(t(O),S.current=setInterval(()=>{t(O)},300)):clearInterval(S.current),()=>clearInterval(S.current)},[O,E]);let k=t=>{t.stopPropagation(),"ArrowLeft"!==t.key&&"ArrowRight"!==t.key||(t.preventDefault(),j(t.key))},A=t=>{t.stopPropagation(),j(null)};return n.useEffect(()=>{let t=null==b?void 0:b.current;return v&&(P(),null==t||t.addEventListener("keydown",k),null==t||t.addEventListener("keyup",A)),()=>{null==t||t.removeEventListener("keydown",k),null==t||t.removeEventListener("keyup",A)}},[P,v]),n.createElement("ol",Object.assign({ref:e,className:(0,u.q)(f("root"),"relative overflow-hidden",c)},m),n.createElement("div",{ref:b,tabIndex:0,className:(0,u.q)("h-full flex",v?(null==x?void 0:x.right)||(null==x?void 0:x.left)?"pl-4 pr-12 items-center overflow-auto snap-mandatory [&::-webkit-scrollbar]:hidden [scrollbar-width:none]":"":"flex-wrap")},r.map((t,e)=>n.createElement(p,{key:"item-".concat(e),name:t,color:o[e%o.length],onClick:d,activeLegend:y}))),v&&((null==x?void 0:x.right)||(null==x?void 0:x.left))?n.createElement(n.Fragment,null,n.createElement("div",{className:(0,u.q)("bg-tremor-background","dark:bg-dark-tremor-background","absolute flex top-0 pr-1 bottom-0 right-0 items-center justify-center h-full"),ref:g},n.createElement(h,{icon:l,onClick:()=>{j(null),E("left")},disabled:!(null==x?void 0:x.left)}),n.createElement(h,{icon:s,onClick:()=>{j(null),E("right")},disabled:!(null==x?void 0:x.right)}))):null)});d.displayName="Legend";let y=(t,e,r,i,a,u)=>{let{payload:c}=t,l=(0,n.useRef)(null);o(()=>{var t,e;r((e=null===(t=l.current)||void 0===t?void 0:t.clientHeight)?Number(e)+20:60)});let s=c.filter(t=>"none"!==t.type);return n.createElement("div",{ref:l,className:"flex items-center justify-end"},n.createElement(d,{categories:s.map(t=>t.value),colors:s.map(t=>e.get(t.value)),onClickLegendItem:a,activeLegend:i,enableLegendSlider:u}))}},98593:function(t,e,r){"use strict";r.d(e,{$B:function(){return c},ZP:function(){return s},zX:function(){return l}});var n=r(2265),o=r(7084),i=r(26898),a=r(13241),u=r(1153);let c=t=>{let{children:e}=t;return n.createElement("div",{className:(0,a.q)("rounded-tremor-default text-tremor-default border","bg-tremor-background shadow-tremor-dropdown border-tremor-border","dark:bg-dark-tremor-background dark:shadow-dark-tremor-dropdown dark:border-dark-tremor-border")},e)},l=t=>{let{value:e,name:r,color:o}=t;return n.createElement("div",{className:"flex items-center justify-between space-x-8"},n.createElement("div",{className:"flex items-center space-x-2"},n.createElement("span",{className:(0,a.q)("shrink-0 rounded-tremor-full border-2 h-3 w-3","border-tremor-background shadow-tremor-card","dark:border-dark-tremor-background dark:shadow-dark-tremor-card",(0,u.bM)(o,i.K.background).bgColor)}),n.createElement("p",{className:(0,a.q)("text-right whitespace-nowrap","text-tremor-content","dark:text-dark-tremor-content")},r)),n.createElement("p",{className:(0,a.q)("font-medium tabular-nums text-right whitespace-nowrap","text-tremor-content-emphasis","dark:text-dark-tremor-content-emphasis")},e))},s=t=>{let{active:e,payload:r,label:i,categoryColors:u,valueFormatter:s}=t;if(e&&r){let t=r.filter(t=>"none"!==t.type);return n.createElement(c,null,n.createElement("div",{className:(0,a.q)("border-tremor-border border-b px-4 py-2","dark:border-dark-tremor-border")},n.createElement("p",{className:(0,a.q)("font-medium","text-tremor-content-emphasis","dark:text-dark-tremor-content-emphasis")},i)),n.createElement("div",{className:(0,a.q)("px-4 py-2 space-y-1")},t.map((t,e)=>{var r;let{value:i,name:a}=t;return n.createElement(l,{key:"id-".concat(e),value:s(i),name:a,color:null!==(r=u.get(a))&&void 0!==r?r:o.fr.Blue})})))}return null}},92666:function(t,e,r){"use strict";r.d(e,{Z:function(){return i}});var n=r(13241),o=r(2265);let i=t=>{let{className:e,noDataText:r="No data"}=t;return o.createElement("div",{className:(0,n.q)("flex items-center justify-center w-full h-full border border-dashed rounded-tremor-default","border-tremor-border","dark:border-dark-tremor-border",e)},o.createElement("p",{className:(0,n.q)("text-tremor-content text-tremor-default","dark:text-dark-tremor-content")},r))}},32644:function(t,e,r){"use strict";r.d(e,{FB:function(){return i},i4:function(){return o},me:function(){return n},vZ:function(){return function t(e,r){if(e===r)return!0;if("object"!=typeof e||"object"!=typeof r||null===e||null===r)return!1;let n=Object.keys(e),o=Object.keys(r);if(n.length!==o.length)return!1;for(let i of n)if(!o.includes(i)||!t(e[i],r[i]))return!1;return!0}}});let n=(t,e)=>{let r=new Map;return t.forEach((t,n)=>{r.set(t,e[n%e.length])}),r},o=(t,e,r)=>[t?"auto":null!=e?e:0,null!=r?r:"auto"];function i(t,e){let r=[];for(let n of t)if(Object.prototype.hasOwnProperty.call(n,e)&&(r.push(n[e]),r.length>1))return!1;return!0}},49804:function(t,e,r){"use strict";r.d(e,{Z:function(){return l}});var n=r(5853),o=r(13241),i=r(1153),a=r(2265),u=r(9496);let c=(0,i.fn)("Col"),l=a.forwardRef((t,e)=>{let{numColSpan:r=1,numColSpanSm:i,numColSpanMd:l,numColSpanLg:s,children:f,className:p}=t,h=(0,n._T)(t,["numColSpan","numColSpanSm","numColSpanMd","numColSpanLg","children","className"]),d=(t,e)=>t&&Object.keys(e).includes(String(t))?e[t]:"";return a.createElement("div",Object.assign({ref:e,className:(0,o.q)(c("root"),(()=>{let t=d(r,u.PT),e=d(i,u.SP),n=d(l,u.VS),a=d(s,u._w);return(0,o.q)(t,e,n,a)})(),p)},h),f)});l.displayName="Col"},97765:function(t,e,r){"use strict";r.d(e,{Z:function(){return c}});var n=r(5853),o=r(26898),i=r(13241),a=r(1153),u=r(2265);let c=u.forwardRef((t,e)=>{let{color:r,children:c,className:l}=t,s=(0,n._T)(t,["color","children","className"]);return u.createElement("p",Object.assign({ref:e,className:(0,i.q)(r?(0,a.bM)(r,o.K.lightText).textColor:"text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis",l)},s),c)});c.displayName="Subtitle"},61134:function(t,e,r){var n;!function(o){"use strict";var i,a={precision:20,rounding:4,toExpNeg:-7,toExpPos:21,LN10:"2.302585092994045684017991454684364207601101488628772976033327900967572609677352480235997205089598298341967784042286"},u=!0,c="[DecimalError] ",l=c+"Invalid argument: ",s=c+"Exponent out of range: ",f=Math.floor,p=Math.pow,h=/^(\d+(\.\d*)?|\.\d+)(e[+-]?\d+)?$/i,d=f(1286742750677284.5),y={};function v(t,e){var r,n,o,i,a,c,l,s,f=t.constructor,p=f.precision;if(!t.s||!e.s)return e.s||(e=new f(t)),u?E(e,p):e;if(l=t.d,s=e.d,a=t.e,o=e.e,l=l.slice(),i=a-o){for(i<0?(n=l,i=-i,c=s.length):(n=s,o=a,c=l.length),i>(c=(a=Math.ceil(p/7))>c?a+1:c+1)&&(i=c,n.length=1),n.reverse();i--;)n.push(0);n.reverse()}for((c=l.length)-(i=s.length)<0&&(i=c,n=s,s=l,l=n),r=0;i;)r=(l[--i]=l[i]+s[i]+r)/1e7|0,l[i]%=1e7;for(r&&(l.unshift(r),++o),c=l.length;0==l[--c];)l.pop();return e.d=l,e.e=o,u?E(e,p):e}function m(t,e,r){if(t!==~~t||tr)throw Error(l+t)}function b(t){var e,r,n,o=t.length-1,i="",a=t[0];if(o>0){for(i+=a,e=1;et.e^this.s<0?1:-1;for(e=0,r=(n=this.d.length)<(o=t.d.length)?n:o;et.d[e]^this.s<0?1:-1;return n===o?0:n>o^this.s<0?1:-1},y.decimalPlaces=y.dp=function(){var t=this.d.length-1,e=(t-this.e)*7;if(t=this.d[t])for(;t%10==0;t/=10)e--;return e<0?0:e},y.dividedBy=y.div=function(t){return g(this,new this.constructor(t))},y.dividedToIntegerBy=y.idiv=function(t){var e=this.constructor;return E(g(this,new e(t),0,1),e.precision)},y.equals=y.eq=function(t){return!this.cmp(t)},y.exponent=function(){return w(this)},y.greaterThan=y.gt=function(t){return this.cmp(t)>0},y.greaterThanOrEqualTo=y.gte=function(t){return this.cmp(t)>=0},y.isInteger=y.isint=function(){return this.e>this.d.length-2},y.isNegative=y.isneg=function(){return this.s<0},y.isPositive=y.ispos=function(){return this.s>0},y.isZero=function(){return 0===this.s},y.lessThan=y.lt=function(t){return 0>this.cmp(t)},y.lessThanOrEqualTo=y.lte=function(t){return 1>this.cmp(t)},y.logarithm=y.log=function(t){var e,r=this.constructor,n=r.precision,o=n+5;if(void 0===t)t=new r(10);else if((t=new r(t)).s<1||t.eq(i))throw Error(c+"NaN");if(this.s<1)throw Error(c+(this.s?"NaN":"-Infinity"));return this.eq(i)?new r(0):(u=!1,e=g(S(this,o),S(t,o),o),u=!0,E(e,n))},y.minus=y.sub=function(t){return t=new this.constructor(t),this.s==t.s?k(this,t):v(this,(t.s=-t.s,t))},y.modulo=y.mod=function(t){var e,r=this.constructor,n=r.precision;if(!(t=new r(t)).s)throw Error(c+"NaN");return this.s?(u=!1,e=g(this,t,0,1).times(t),u=!0,this.minus(e)):E(new r(this),n)},y.naturalExponential=y.exp=function(){return x(this)},y.naturalLogarithm=y.ln=function(){return S(this)},y.negated=y.neg=function(){var t=new this.constructor(this);return t.s=-t.s||0,t},y.plus=y.add=function(t){return t=new this.constructor(t),this.s==t.s?v(this,t):k(this,(t.s=-t.s,t))},y.precision=y.sd=function(t){var e,r,n;if(void 0!==t&&!!t!==t&&1!==t&&0!==t)throw Error(l+t);if(e=w(this)+1,r=7*(n=this.d.length-1)+1,n=this.d[n]){for(;n%10==0;n/=10)r--;for(n=this.d[0];n>=10;n/=10)r++}return t&&e>r?e:r},y.squareRoot=y.sqrt=function(){var t,e,r,n,o,i,a,l=this.constructor;if(this.s<1){if(!this.s)return new l(0);throw Error(c+"NaN")}for(t=w(this),u=!1,0==(o=Math.sqrt(+this))||o==1/0?(((e=b(this.d)).length+t)%2==0&&(e+="0"),o=Math.sqrt(e),t=f((t+1)/2)-(t<0||t%2),n=new l(e=o==1/0?"5e"+t:(e=o.toExponential()).slice(0,e.indexOf("e")+1)+t)):n=new l(o.toString()),o=a=(r=l.precision)+3;;)if(n=(i=n).plus(g(this,i,a+2)).times(.5),b(i.d).slice(0,a)===(e=b(n.d)).slice(0,a)){if(e=e.slice(a-3,a+1),o==a&&"4999"==e){if(E(i,r+1,0),i.times(i).eq(this)){n=i;break}}else if("9999"!=e)break;a+=4}return u=!0,E(n,r)},y.times=y.mul=function(t){var e,r,n,o,i,a,c,l,s,f=this.constructor,p=this.d,h=(t=new f(t)).d;if(!this.s||!t.s)return new f(0);for(t.s*=this.s,r=this.e+t.e,(l=p.length)<(s=h.length)&&(i=p,p=h,h=i,a=l,l=s,s=a),i=[],n=a=l+s;n--;)i.push(0);for(n=s;--n>=0;){for(e=0,o=l+n;o>n;)c=i[o]+h[n]*p[o-n-1]+e,i[o--]=c%1e7|0,e=c/1e7|0;i[o]=(i[o]+e)%1e7|0}for(;!i[--a];)i.pop();return e?++r:i.shift(),t.d=i,t.e=r,u?E(t,f.precision):t},y.toDecimalPlaces=y.todp=function(t,e){var r=this,n=r.constructor;return(r=new n(r),void 0===t)?r:(m(t,0,1e9),void 0===e?e=n.rounding:m(e,0,8),E(r,t+w(r)+1,e))},y.toExponential=function(t,e){var r,n=this,o=n.constructor;return void 0===t?r=A(n,!0):(m(t,0,1e9),void 0===e?e=o.rounding:m(e,0,8),r=A(n=E(new o(n),t+1,e),!0,t+1)),r},y.toFixed=function(t,e){var r,n,o=this.constructor;return void 0===t?A(this):(m(t,0,1e9),void 0===e?e=o.rounding:m(e,0,8),r=A((n=E(new o(this),t+w(this)+1,e)).abs(),!1,t+w(n)+1),this.isneg()&&!this.isZero()?"-"+r:r)},y.toInteger=y.toint=function(){var t=this.constructor;return E(new t(this),w(this)+1,t.rounding)},y.toNumber=function(){return+this},y.toPower=y.pow=function(t){var e,r,n,o,a,l,s=this,p=s.constructor,h=+(t=new p(t));if(!t.s)return new p(i);if(!(s=new p(s)).s){if(t.s<1)throw Error(c+"Infinity");return s}if(s.eq(i))return s;if(n=p.precision,t.eq(i))return E(s,n);if(l=(e=t.e)>=(r=t.d.length-1),a=s.s,l){if((r=h<0?-h:h)<=9007199254740991){for(o=new p(i),e=Math.ceil(n/7+4),u=!1;r%2&&M((o=o.times(s)).d,e),0!==(r=f(r/2));)M((s=s.times(s)).d,e);return u=!0,t.s<0?new p(i).div(o):E(o,n)}}else if(a<0)throw Error(c+"NaN");return a=a<0&&1&t.d[Math.max(e,r)]?-1:1,s.s=1,u=!1,o=t.times(S(s,n+12)),u=!0,(o=x(o)).s=a,o},y.toPrecision=function(t,e){var r,n,o=this,i=o.constructor;return void 0===t?(r=w(o),n=A(o,r<=i.toExpNeg||r>=i.toExpPos)):(m(t,1,1e9),void 0===e?e=i.rounding:m(e,0,8),r=w(o=E(new i(o),t,e)),n=A(o,t<=r||r<=i.toExpNeg,t)),n},y.toSignificantDigits=y.tosd=function(t,e){var r=this.constructor;return void 0===t?(t=r.precision,e=r.rounding):(m(t,1,1e9),void 0===e?e=r.rounding:m(e,0,8)),E(new r(this),t,e)},y.toString=y.valueOf=y.val=y.toJSON=function(){var t=w(this),e=this.constructor;return A(this,t<=e.toExpNeg||t>=e.toExpPos)};var g=function(){function t(t,e){var r,n=0,o=t.length;for(t=t.slice();o--;)r=t[o]*e+n,t[o]=r%1e7|0,n=r/1e7|0;return n&&t.unshift(n),t}function e(t,e,r,n){var o,i;if(r!=n)i=r>n?1:-1;else for(o=i=0;oe[o]?1:-1;break}return i}function r(t,e,r){for(var n=0;r--;)t[r]-=n,n=t[r]1;)t.shift()}return function(n,o,i,a){var u,l,s,f,p,h,d,y,v,m,b,g,x,O,j,S,P,k,A=n.constructor,M=n.s==o.s?1:-1,_=n.d,T=o.d;if(!n.s)return new A(n);if(!o.s)throw Error(c+"Division by zero");for(s=0,l=n.e-o.e,P=T.length,j=_.length,y=(d=new A(M)).d=[];T[s]==(_[s]||0);)++s;if(T[s]>(_[s]||0)&&--l,(g=null==i?i=A.precision:a?i+(w(n)-w(o))+1:i)<0)return new A(0);if(g=g/7+2|0,s=0,1==P)for(f=0,T=T[0],g++;(s1&&(T=t(T,f),_=t(_,f),P=T.length,j=_.length),O=P,m=(v=_.slice(0,P)).length;m=1e7/2&&++S;do f=0,(u=e(T,v,P,m))<0?(b=v[0],P!=m&&(b=1e7*b+(v[1]||0)),(f=b/S|0)>1?(f>=1e7&&(f=1e7-1),h=(p=t(T,f)).length,m=v.length,1==(u=e(p,v,h,m))&&(f--,r(p,P16)throw Error(s+w(t));if(!t.s)return new h(i);for(null==e?(u=!1,c=d):c=e,a=new h(.03125);t.abs().gte(.1);)t=t.times(a),f+=5;for(c+=Math.log(p(2,f))/Math.LN10*2+5|0,r=n=o=new h(i),h.precision=c;;){if(n=E(n.times(t),c),r=r.times(++l),b((a=o.plus(g(n,r,c))).d).slice(0,c)===b(o.d).slice(0,c)){for(;f--;)o=E(o.times(o),c);return h.precision=d,null==e?(u=!0,E(o,d)):o}o=a}}function w(t){for(var e=7*t.e,r=t.d[0];r>=10;r/=10)e++;return e}function O(t,e,r){if(e>t.LN10.sd())throw u=!0,r&&(t.precision=r),Error(c+"LN10 precision limit exceeded");return E(new t(t.LN10),e)}function j(t){for(var e="";t--;)e+="0";return e}function S(t,e){var r,n,o,a,l,s,f,p,h,d=1,y=t,v=y.d,m=y.constructor,x=m.precision;if(y.s<1)throw Error(c+(y.s?"NaN":"-Infinity"));if(y.eq(i))return new m(0);if(null==e?(u=!1,p=x):p=e,y.eq(10))return null==e&&(u=!0),O(m,p);if(p+=10,m.precision=p,n=(r=b(v)).charAt(0),!(15e14>Math.abs(a=w(y))))return f=O(m,p+2,x).times(a+""),y=S(new m(n+"."+r.slice(1)),p-10).plus(f),m.precision=x,null==e?(u=!0,E(y,x)):y;for(;n<7&&1!=n||1==n&&r.charAt(1)>3;)n=(r=b((y=y.times(t)).d)).charAt(0),d++;for(a=w(y),n>1?(y=new m("0."+r),a++):y=new m(n+"."+r.slice(1)),s=l=y=g(y.minus(i),y.plus(i),p),h=E(y.times(y),p),o=3;;){if(l=E(l.times(h),p),b((f=s.plus(g(l,new m(o),p))).d).slice(0,p)===b(s.d).slice(0,p))return s=s.times(2),0!==a&&(s=s.plus(O(m,p+2,x).times(a+""))),s=g(s,new m(d),p),m.precision=x,null==e?(u=!0,E(s,x)):s;s=f,o+=2}}function P(t,e){var r,n,o;for((r=e.indexOf("."))>-1&&(e=e.replace(".","")),(n=e.search(/e/i))>0?(r<0&&(r=n),r+=+e.slice(n+1),e=e.substring(0,n)):r<0&&(r=e.length),n=0;48===e.charCodeAt(n);)++n;for(o=e.length;48===e.charCodeAt(o-1);)--o;if(e=e.slice(n,o)){if(o-=n,r=r-n-1,t.e=f(r/7),t.d=[],n=(r+1)%7,r<0&&(n+=7),nd||t.e<-d))throw Error(s+r)}else t.s=0,t.e=0,t.d=[0];return t}function E(t,e,r){var n,o,i,a,c,l,h,y,v=t.d;for(a=1,i=v[0];i>=10;i/=10)a++;if((n=e-a)<0)n+=7,o=e,h=v[y=0];else{if((y=Math.ceil((n+1)/7))>=(i=v.length))return t;for(a=1,h=i=v[y];i>=10;i/=10)a++;n%=7,o=n-7+a}if(void 0!==r&&(c=h/(i=p(10,a-o-1))%10|0,l=e<0||void 0!==v[y+1]||h%i,l=r<4?(c||l)&&(0==r||r==(t.s<0?3:2)):c>5||5==c&&(4==r||l||6==r&&(n>0?o>0?h/p(10,a-o):0:v[y-1])%10&1||r==(t.s<0?8:7))),e<1||!v[0])return l?(i=w(t),v.length=1,e=e-i-1,v[0]=p(10,(7-e%7)%7),t.e=f(-e/7)||0):(v.length=1,v[0]=t.e=t.s=0),t;if(0==n?(v.length=y,i=1,y--):(v.length=y+1,i=p(10,7-n),v[y]=o>0?(h/p(10,a-o)%p(10,o)|0)*i:0),l)for(;;){if(0==y){1e7==(v[0]+=i)&&(v[0]=1,++t.e);break}if(v[y]+=i,1e7!=v[y])break;v[y--]=0,i=1}for(n=v.length;0===v[--n];)v.pop();if(u&&(t.e>d||t.e<-d))throw Error(s+w(t));return t}function k(t,e){var r,n,o,i,a,c,l,s,f,p,h=t.constructor,d=h.precision;if(!t.s||!e.s)return e.s?e.s=-e.s:e=new h(t),u?E(e,d):e;if(l=t.d,p=e.d,n=e.e,s=t.e,l=l.slice(),a=s-n){for((f=a<0)?(r=l,a=-a,c=p.length):(r=p,n=s,c=l.length),a>(o=Math.max(Math.ceil(d/7),c)+2)&&(a=o,r.length=1),r.reverse(),o=a;o--;)r.push(0);r.reverse()}else{for((f=(o=l.length)<(c=p.length))&&(c=o),o=0;o0;--o)l[c++]=0;for(o=p.length;o>a;){if(l[--o]0?i=i.charAt(0)+"."+i.slice(1)+j(n):a>1&&(i=i.charAt(0)+"."+i.slice(1)),i=i+(o<0?"e":"e+")+o):o<0?(i="0."+j(-o-1)+i,r&&(n=r-a)>0&&(i+=j(n))):o>=a?(i+=j(o+1-a),r&&(n=r-o-1)>0&&(i=i+"."+j(n))):((n=o+1)0&&(o+1===a&&(i+="."),i+=j(n))),t.s<0?"-"+i:i}function M(t,e){if(t.length>e)return t.length=e,!0}function _(t){if(!t||"object"!=typeof t)throw Error(c+"Object expected");var e,r,n,o=["precision",1,1e9,"rounding",0,8,"toExpNeg",-1/0,0,"toExpPos",0,1/0];for(e=0;e=o[e+1]&&n<=o[e+2])this[r]=n;else throw Error(l+r+": "+n)}if(void 0!==(n=t[r="LN10"])){if(n==Math.LN10)this[r]=new this(n);else throw Error(l+r+": "+n)}return this}(a=function t(e){var r,n,o;function i(t){if(!(this instanceof i))return new i(t);if(this.constructor=i,t instanceof i){this.s=t.s,this.e=t.e,this.d=(t=t.d)?t.slice():t;return}if("number"==typeof t){if(0*t!=0)throw Error(l+t);if(t>0)this.s=1;else if(t<0)t=-t,this.s=-1;else{this.s=0,this.e=0,this.d=[0];return}if(t===~~t&&t<1e7){this.e=0,this.d=[t];return}return P(this,t.toString())}if("string"!=typeof t)throw Error(l+t);if(45===t.charCodeAt(0)?(t=t.slice(1),this.s=-1):this.s=1,h.test(t))P(this,t);else throw Error(l+t)}if(i.prototype=y,i.ROUND_UP=0,i.ROUND_DOWN=1,i.ROUND_CEIL=2,i.ROUND_FLOOR=3,i.ROUND_HALF_UP=4,i.ROUND_HALF_DOWN=5,i.ROUND_HALF_EVEN=6,i.ROUND_HALF_CEIL=7,i.ROUND_HALF_FLOOR=8,i.clone=t,i.config=i.set=_,void 0===e&&(e={}),e)for(r=0,o=["precision","rounding","toExpNeg","toExpPos","LN10"];r-1}},56883:function(t){t.exports=function(t,e,r){for(var n=-1,o=null==t?0:t.length;++n0&&i(s)?r>1?t(s,r-1,i,a,u):n(u,s):a||(u[u.length]=s)}return u}},63321:function(t,e,r){var n=r(33023)();t.exports=n},98060:function(t,e,r){var n=r(63321),o=r(43228);t.exports=function(t,e){return t&&n(t,e,o)}},92167:function(t,e,r){var n=r(67906),o=r(70235);t.exports=function(t,e){e=n(e,t);for(var r=0,i=e.length;null!=t&&re}},93012:function(t){t.exports=function(t,e){return null!=t&&e in Object(t)}},47909:function(t,e,r){var n=r(8235),o=r(31953),i=r(35281);t.exports=function(t,e,r){return e==e?i(t,e,r):n(t,o,r)}},90370:function(t,e,r){var n=r(54506),o=r(10303);t.exports=function(t){return o(t)&&"[object Arguments]"==n(t)}},56318:function(t,e,r){var n=r(6791),o=r(10303);t.exports=function t(e,r,i,a,u){return e===r||(null!=e&&null!=r&&(o(e)||o(r))?n(e,r,i,a,t,u):e!=e&&r!=r)}},6791:function(t,e,r){var n=r(85885),o=r(97638),i=r(88030),a=r(64974),u=r(81690),c=r(25614),l=r(98051),s=r(9792),f="[object Arguments]",p="[object Array]",h="[object Object]",d=Object.prototype.hasOwnProperty;t.exports=function(t,e,r,y,v,m){var b=c(t),g=c(e),x=b?p:u(t),w=g?p:u(e);x=x==f?h:x,w=w==f?h:w;var O=x==h,j=w==h,S=x==w;if(S&&l(t)){if(!l(e))return!1;b=!0,O=!1}if(S&&!O)return m||(m=new n),b||s(t)?o(t,e,r,y,v,m):i(t,e,x,r,y,v,m);if(!(1&r)){var P=O&&d.call(t,"__wrapped__"),E=j&&d.call(e,"__wrapped__");if(P||E){var k=P?t.value():t,A=E?e.value():e;return m||(m=new n),v(k,A,r,y,m)}}return!!S&&(m||(m=new n),a(t,e,r,y,v,m))}},62538:function(t,e,r){var n=r(85885),o=r(56318);t.exports=function(t,e,r,i){var a=r.length,u=a,c=!i;if(null==t)return!u;for(t=Object(t);a--;){var l=r[a];if(c&&l[2]?l[1]!==t[l[0]]:!(l[0]in t))return!1}for(;++ao?0:o+e),(r=r>o?o:r)<0&&(r+=o),o=e>r?0:r-e>>>0,e>>>=0;for(var i=Array(o);++n=200){var y=e?null:u(t);if(y)return c(y);p=!1,s=a,d=new n}else d=e?[]:h;t:for(;++l=o?t:n(t,e,r)}},1536:function(t,e,r){var n=r(78371);t.exports=function(t,e){if(t!==e){var r=void 0!==t,o=null===t,i=t==t,a=n(t),u=void 0!==e,c=null===e,l=e==e,s=n(e);if(!c&&!s&&!a&&t>e||a&&u&&l&&!c&&!s||o&&u&&l||!r&&l||!i)return 1;if(!o&&!a&&!s&&t=c)return l;return l*("desc"==r[o]?-1:1)}}return t.index-e.index}},92077:function(t,e,r){var n=r(74288)["__core-js_shared__"];t.exports=n},97930:function(t,e,r){var n=r(5629);t.exports=function(t,e){return function(r,o){if(null==r)return r;if(!n(r))return t(r,o);for(var i=r.length,a=e?i:-1,u=Object(r);(e?a--:++a-1?u[c?e[l]:l]:void 0}}},35464:function(t,e,r){var n=r(19608),o=r(49639),i=r(175);t.exports=function(t){return function(e,r,a){return a&&"number"!=typeof a&&o(e,r,a)&&(r=a=void 0),e=i(e),void 0===r?(r=e,e=0):r=i(r),a=void 0===a?es))return!1;var p=c.get(t),h=c.get(e);if(p&&h)return p==e&&h==t;var d=-1,y=!0,v=2&r?new n:void 0;for(c.set(t,e),c.set(e,t);++d-1&&t%1==0&&t-1}},13368:function(t,e,r){var n=r(24457);t.exports=function(t,e){var r=this.__data__,o=n(r,t);return o<0?(++this.size,r.push([t,e])):r[o][1]=e,this}},38764:function(t,e,r){var n=r(9855),o=r(99078),i=r(88675);t.exports=function(){this.size=0,this.__data__={hash:new n,map:new(i||o),string:new n}}},78615:function(t,e,r){var n=r(1507);t.exports=function(t){var e=n(this,t).delete(t);return this.size-=e?1:0,e}},83391:function(t,e,r){var n=r(1507);t.exports=function(t){return n(this,t).get(t)}},53483:function(t,e,r){var n=r(1507);t.exports=function(t){return n(this,t).has(t)}},74724:function(t,e,r){var n=r(1507);t.exports=function(t,e){var r=n(this,t),o=r.size;return r.set(t,e),this.size+=r.size==o?0:1,this}},22523:function(t){t.exports=function(t){var e=-1,r=Array(t.size);return t.forEach(function(t,n){r[++e]=[n,t]}),r}},47073:function(t){t.exports=function(t,e){return function(r){return null!=r&&r[t]===e&&(void 0!==e||t in Object(r))}}},23787:function(t,e,r){var n=r(50967);t.exports=function(t){var e=n(t,function(t){return 500===r.size&&r.clear(),t}),r=e.cache;return e}},20453:function(t,e,r){var n=r(39866)(Object,"create");t.exports=n},77184:function(t,e,r){var n=r(45070)(Object.keys,Object);t.exports=n},39931:function(t,e,r){t=r.nmd(t);var n=r(17071),o=e&&!e.nodeType&&e,i=o&&t&&!t.nodeType&&t,a=i&&i.exports===o&&n.process,u=function(){try{var t=i&&i.require&&i.require("util").types;if(t)return t;return a&&a.binding&&a.binding("util")}catch(t){}}();t.exports=u},45070:function(t){t.exports=function(t,e){return function(r){return t(e(r))}}},49478:function(t,e,r){var n=r(60493),o=Math.max;t.exports=function(t,e,r){return e=o(void 0===e?t.length-1:e,0),function(){for(var i=arguments,a=-1,u=o(i.length-e,0),c=Array(u);++a0){if(++r>=800)return arguments[0]}else r=0;return t.apply(void 0,arguments)}}},84092:function(t,e,r){var n=r(99078);t.exports=function(){this.__data__=new n,this.size=0}},31663:function(t){t.exports=function(t){var e=this.__data__,r=e.delete(t);return this.size=e.size,r}},69135:function(t){t.exports=function(t){return this.__data__.get(t)}},39552:function(t){t.exports=function(t){return this.__data__.has(t)}},8381:function(t,e,r){var n=r(99078),o=r(88675),i=r(76219);t.exports=function(t,e){var r=this.__data__;if(r instanceof n){var a=r.__data__;if(!o||a.length<199)return a.push([t,e]),this.size=++r.size,this;r=this.__data__=new i(a)}return r.set(t,e),this.size=r.size,this}},35281:function(t){t.exports=function(t,e,r){for(var n=r-1,o=t.length;++n-1&&t%1==0&&t<=9007199254740991}},82559:function(t,e,r){var n=r(22345);t.exports=function(t){return n(t)&&t!=+t}},77571:function(t){t.exports=function(t){return null==t}},22345:function(t,e,r){var n=r(54506),o=r(10303);t.exports=function(t){return"number"==typeof t||o(t)&&"[object Number]"==n(t)}},90231:function(t,e,r){var n=r(54506),o=r(62602),i=r(10303),a=Object.prototype,u=Function.prototype.toString,c=a.hasOwnProperty,l=u.call(Object);t.exports=function(t){if(!i(t)||"[object Object]"!=n(t))return!1;var e=o(t);if(null===e)return!0;var r=c.call(e,"constructor")&&e.constructor;return"function"==typeof r&&r instanceof r&&u.call(r)==l}},42715:function(t,e,r){var n=r(54506),o=r(25614),i=r(10303);t.exports=function(t){return"string"==typeof t||!o(t)&&i(t)&&"[object String]"==n(t)}},9792:function(t,e,r){var n=r(59332),o=r(23305),i=r(39931),a=i&&i.isTypedArray,u=a?o(a):n;t.exports=u},43228:function(t,e,r){var n=r(28579),o=r(4578),i=r(5629);t.exports=function(t){return i(t)?n(t):o(t)}},86185:function(t){t.exports=function(t){var e=null==t?0:t.length;return e?t[e-1]:void 0}},89238:function(t,e,r){var n=r(73819),o=r(88157),i=r(24240),a=r(25614);t.exports=function(t,e){return(a(t)?n:i)(t,o(e,3))}},41443:function(t,e,r){var n=r(83023),o=r(98060),i=r(88157);t.exports=function(t,e){var r={};return e=i(e,3),o(t,function(t,o,i){n(r,o,e(t,o,i))}),r}},95645:function(t,e,r){var n=r(67646),o=r(58905),i=r(79586);t.exports=function(t){return t&&t.length?n(t,i,o):void 0}},50967:function(t,e,r){var n=r(76219);function o(t,e){if("function"!=typeof t||null!=e&&"function"!=typeof e)throw TypeError("Expected a function");var r=function(){var n=arguments,o=e?e.apply(this,n):n[0],i=r.cache;if(i.has(o))return i.get(o);var a=t.apply(this,n);return r.cache=i.set(o,a)||i,a};return r.cache=new(o.Cache||n),r}o.Cache=n,t.exports=o},99008:function(t,e,r){var n=r(67646),o=r(20121),i=r(79586);t.exports=function(t){return t&&t.length?n(t,i,o):void 0}},93810:function(t){t.exports=function(){}},22350:function(t,e,r){var n=r(18155),o=r(73584),i=r(67352),a=r(70235);t.exports=function(t){return i(t)?n(a(t)):o(t)}},99676:function(t,e,r){var n=r(35464)();t.exports=n},33645:function(t,e,r){var n=r(25253),o=r(88157),i=r(12327),a=r(25614),u=r(49639);t.exports=function(t,e,r){var c=a(t)?n:i;return r&&u(t,e,r)&&(e=void 0),c(t,o(e,3))}},34935:function(t,e,r){var n=r(72569),o=r(84046),i=r(44843),a=r(49639),u=i(function(t,e){if(null==t)return[];var r=e.length;return r>1&&a(t,e[0],e[1])?e=[]:r>2&&a(e[0],e[1],e[2])&&(e=[e[0]]),o(t,n(e,1),[])});t.exports=u},55716:function(t){t.exports=function(){return[]}},7406:function(t){t.exports=function(){return!1}},37065:function(t,e,r){var n=r(7310),o=r(28302);t.exports=function(t,e,r){var i=!0,a=!0;if("function"!=typeof t)throw TypeError("Expected a function");return o(r)&&(i="leading"in r?!!r.leading:i,a="trailing"in r?!!r.trailing:a),n(t,e,{leading:i,maxWait:e,trailing:a})}},175:function(t,e,r){var n=r(6660),o=1/0;t.exports=function(t){return t?(t=n(t))===o||t===-o?(t<0?-1:1)*17976931348623157e292:t==t?t:0:0===t?t:0}},85759:function(t,e,r){var n=r(175);t.exports=function(t){var e=n(t),r=e%1;return e==e?r?e-r:e:0}},3641:function(t,e,r){var n=r(65020);t.exports=function(t){return null==t?"":n(t)}},47230:function(t,e,r){var n=r(88157),o=r(13826);t.exports=function(t,e){return t&&t.length?o(t,n(e,2)):[]}},75551:function(t,e,r){var n=r(80675)("toUpperCase");t.exports=n},48049:function(t,e,r){"use strict";var n=r(14397);function o(){}function i(){}i.resetWarningCache=o,t.exports=function(){function t(t,e,r,o,i,a){if(a!==n){var u=Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw u.name="Invariant Violation",u}}function e(){return t}t.isRequired=t;var r={array:t,bigint:t,bool:t,func:t,number:t,object:t,string:t,symbol:t,any:t,arrayOf:e,element:t,elementType:t,instanceOf:e,node:t,objectOf:e,oneOf:e,oneOfType:e,shape:e,exact:e,checkPropTypes:i,resetWarningCache:o};return r.PropTypes=r,r}},40718:function(t,e,r){t.exports=r(48049)()},14397:function(t){"use strict";t.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},84735:function(t,e,r){"use strict";r.d(e,{ZP:function(){return tS}});var n=r(2265),o=r(40718),i=r.n(o),a=Object.getOwnPropertyNames,u=Object.getOwnPropertySymbols,c=Object.prototype.hasOwnProperty;function l(t,e){return function(r,n,o){return t(r,n,o)&&e(r,n,o)}}function s(t){return function(e,r,n){if(!e||!r||"object"!=typeof e||"object"!=typeof r)return t(e,r,n);var o=n.cache,i=o.get(e),a=o.get(r);if(i&&a)return i===r&&a===e;o.set(e,r),o.set(r,e);var u=t(e,r,n);return o.delete(e),o.delete(r),u}}function f(t){return a(t).concat(u(t))}var p=Object.hasOwn||function(t,e){return c.call(t,e)};function h(t,e){return t===e||!t&&!e&&t!=t&&e!=e}var d=Object.getOwnPropertyDescriptor,y=Object.keys;function v(t,e,r){var n=t.length;if(e.length!==n)return!1;for(;n-- >0;)if(!r.equals(t[n],e[n],n,n,t,e,r))return!1;return!0}function m(t,e){return h(t.getTime(),e.getTime())}function b(t,e){return t.name===e.name&&t.message===e.message&&t.cause===e.cause&&t.stack===e.stack}function g(t,e){return t===e}function x(t,e,r){var n,o,i=t.size;if(i!==e.size)return!1;if(!i)return!0;for(var a=Array(i),u=t.entries(),c=0;(n=u.next())&&!n.done;){for(var l=e.entries(),s=!1,f=0;(o=l.next())&&!o.done;){if(a[f]){f++;continue}var p=n.value,h=o.value;if(r.equals(p[0],h[0],c,f,t,e,r)&&r.equals(p[1],h[1],p[0],h[0],t,e,r)){s=a[f]=!0;break}f++}if(!s)return!1;c++}return!0}function w(t,e,r){var n=y(t),o=n.length;if(y(e).length!==o)return!1;for(;o-- >0;)if(!A(t,e,r,n[o]))return!1;return!0}function O(t,e,r){var n,o,i,a=f(t),u=a.length;if(f(e).length!==u)return!1;for(;u-- >0;)if(!A(t,e,r,n=a[u])||(o=d(t,n),i=d(e,n),(o||i)&&(!o||!i||o.configurable!==i.configurable||o.enumerable!==i.enumerable||o.writable!==i.writable)))return!1;return!0}function j(t,e){return h(t.valueOf(),e.valueOf())}function S(t,e){return t.source===e.source&&t.flags===e.flags}function P(t,e,r){var n,o,i=t.size;if(i!==e.size)return!1;if(!i)return!0;for(var a=Array(i),u=t.values();(n=u.next())&&!n.done;){for(var c=e.values(),l=!1,s=0;(o=c.next())&&!o.done;){if(!a[s]&&r.equals(n.value,o.value,n.value,o.value,t,e,r)){l=a[s]=!0;break}s++}if(!l)return!1}return!0}function E(t,e){var r=t.length;if(e.length!==r)return!1;for(;r-- >0;)if(t[r]!==e[r])return!1;return!0}function k(t,e){return t.hostname===e.hostname&&t.pathname===e.pathname&&t.protocol===e.protocol&&t.port===e.port&&t.hash===e.hash&&t.username===e.username&&t.password===e.password}function A(t,e,r,n){return("_owner"===n||"__o"===n||"__v"===n)&&(!!t.$$typeof||!!e.$$typeof)||p(e,n)&&r.equals(t[n],e[n],n,n,t,e,r)}var M=Array.isArray,_="undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView?ArrayBuffer.isView:null,T=Object.assign,C=Object.prototype.toString.call.bind(Object.prototype.toString),N=D();function D(t){void 0===t&&(t={});var e,r,n,o,i,a,u,c,f,p,d,y,A,N,D=t.circular,I=t.createInternalComparator,L=t.createState,B=t.strict,R=(r=(e=function(t){var e=t.circular,r=t.createCustomConfig,n=t.strict,o={areArraysEqual:n?O:v,areDatesEqual:m,areErrorsEqual:b,areFunctionsEqual:g,areMapsEqual:n?l(x,O):x,areNumbersEqual:h,areObjectsEqual:n?O:w,arePrimitiveWrappersEqual:j,areRegExpsEqual:S,areSetsEqual:n?l(P,O):P,areTypedArraysEqual:n?O:E,areUrlsEqual:k,unknownTagComparators:void 0};if(r&&(o=T({},o,r(o))),e){var i=s(o.areArraysEqual),a=s(o.areMapsEqual),u=s(o.areObjectsEqual),c=s(o.areSetsEqual);o=T({},o,{areArraysEqual:i,areMapsEqual:a,areObjectsEqual:u,areSetsEqual:c})}return o}(t)).areArraysEqual,n=e.areDatesEqual,o=e.areErrorsEqual,i=e.areFunctionsEqual,a=e.areMapsEqual,u=e.areNumbersEqual,c=e.areObjectsEqual,f=e.arePrimitiveWrappersEqual,p=e.areRegExpsEqual,d=e.areSetsEqual,y=e.areTypedArraysEqual,A=e.areUrlsEqual,N=e.unknownTagComparators,function(t,e,l){if(t===e)return!0;if(null==t||null==e)return!1;var s=typeof t;if(s!==typeof e)return!1;if("object"!==s)return"number"===s?u(t,e,l):"function"===s&&i(t,e,l);var h=t.constructor;if(h!==e.constructor)return!1;if(h===Object)return c(t,e,l);if(M(t))return r(t,e,l);if(null!=_&&_(t))return y(t,e,l);if(h===Date)return n(t,e,l);if(h===RegExp)return p(t,e,l);if(h===Map)return a(t,e,l);if(h===Set)return d(t,e,l);var v=C(t);if("[object Date]"===v)return n(t,e,l);if("[object RegExp]"===v)return p(t,e,l);if("[object Map]"===v)return a(t,e,l);if("[object Set]"===v)return d(t,e,l);if("[object Object]"===v)return"function"!=typeof t.then&&"function"!=typeof e.then&&c(t,e,l);if("[object URL]"===v)return A(t,e,l);if("[object Error]"===v)return o(t,e,l);if("[object Arguments]"===v)return c(t,e,l);if("[object Boolean]"===v||"[object Number]"===v||"[object String]"===v)return f(t,e,l);if(N){var m=N[v];if(!m){var b=null!=t?t[Symbol.toStringTag]:void 0;b&&(m=N[b])}if(m)return m(t,e,l)}return!1}),z=I?I(R):function(t,e,r,n,o,i,a){return R(t,e,a)};return function(t){var e=t.circular,r=t.comparator,n=t.createState,o=t.equals,i=t.strict;if(n)return function(t,a){var u=n(),c=u.cache;return r(t,a,{cache:void 0===c?e?new WeakMap:void 0:c,equals:o,meta:u.meta,strict:i})};if(e)return function(t,e){return r(t,e,{cache:new WeakMap,equals:o,meta:void 0,strict:i})};var a={cache:void 0,equals:o,meta:void 0,strict:i};return function(t,e){return r(t,e,a)}}({circular:void 0!==D&&D,comparator:R,createState:L,equals:z,strict:void 0!==B&&B})}function I(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,r=-1;requestAnimationFrame(function n(o){if(r<0&&(r=o),o-r>e)t(o),r=-1;else{var i;i=n,"undefined"!=typeof requestAnimationFrame&&requestAnimationFrame(i)}})}function L(t){return(L="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function B(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=Array(e);rt.length)&&(e=t.length);for(var r=0,n=Array(e);r=0&&t<=1}),"[configBezier]: arguments should be x1, y1, x2, y2 of [0, 1] instead received %s",n);var p=V(i,u),h=V(a,c),d=(t=i,e=u,function(r){var n;return G([].concat(function(t){if(Array.isArray(t))return H(t)}(n=X(t,e).map(function(t,e){return t*e}).slice(1))||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(n)||Y(n)||function(){throw TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}(),[0]),r)}),y=function(t){for(var e=t>1?1:t,r=e,n=0;n<8;++n){var o,i=p(r)-e,a=d(r);if(1e-4>Math.abs(i-e)||a<1e-4)break;r=(o=r-i/a)>1?1:o<0?0:o}return h(r)};return y.isStepper=!1,y},Q=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},e=t.stiff,r=void 0===e?100:e,n=t.damping,o=void 0===n?8:n,i=t.dt,a=void 0===i?17:i,u=function(t,e,n){var i=n+(-(t-e)*r-n*o)*a/1e3,u=n*a/1e3+t;return 1e-4>Math.abs(u-e)&&1e-4>Math.abs(i)?[e,0]:[u,i]};return u.isStepper=!0,u.dt=a,u},J=function(){for(var t=arguments.length,e=Array(t),r=0;rt.length)&&(e=t.length);for(var r=0,n=Array(e);rt.length)&&(e=t.length);for(var r=0,n=Array(e);r0?r[o-1]:n,p=l||Object.keys(c);if("function"==typeof u||"spring"===u)return[].concat(th(t),[e.runJSAnimation.bind(e,{from:f.style,to:c,duration:i,easing:u}),i]);var h=Z(p,i,u),d=tv(tv(tv({},f.style),c),{},{transition:h});return[].concat(th(t),[d,i,s]).filter($)},[a,Math.max(void 0===u?0:u,n)])),[t.onAnimationEnd]))}},{key:"runAnimation",value:function(t){if(!this.manager){var e,r,n;this.manager=(e=function(){return null},r=!1,n=function t(n){if(!r){if(Array.isArray(n)){if(!n.length)return;var o=function(t){if(Array.isArray(t))return t}(n)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(n)||function(t,e){if(t){if("string"==typeof t)return B(t,void 0);var r=Object.prototype.toString.call(t).slice(8,-1);if("Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return B(t,void 0)}}(n)||function(){throw TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}(),i=o[0],a=o.slice(1);if("number"==typeof i){I(t.bind(null,a),i);return}t(i),I(t.bind(null,a));return}"object"===L(n)&&e(n),"function"==typeof n&&n()}},{stop:function(){r=!0},start:function(t){r=!1,n(t)},subscribe:function(t){return e=t,function(){e=function(){return null}}}})}var o=t.begin,i=t.duration,a=t.attributeName,u=t.to,c=t.easing,l=t.onAnimationStart,s=t.onAnimationEnd,f=t.steps,p=t.children,h=this.manager;if(this.unSubscribe=h.subscribe(this.handleStyleChange),"function"==typeof c||"function"==typeof p||"spring"===c){this.runJSAnimation(t);return}if(f.length>1){this.runStepAnimation(t);return}var d=a?tm({},a,u):u,y=Z(Object.keys(d),i,c);h.start([l,o,tv(tv({},d),{},{transition:y}),i,s])}},{key:"render",value:function(){var t=this.props,e=t.children,r=(t.begin,t.duration),o=(t.attributeName,t.easing,t.isActive),i=(t.steps,t.from,t.to,t.canBegin,t.onAnimationEnd,t.shouldReAnimate,t.onAnimationReStart,function(t,e){if(null==t)return{};var r,n,o=function(t,e){if(null==t)return{};var r,n,o={},i=Object.keys(t);for(n=0;n=0||(o[r]=t[r]);return o}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(t,tp)),a=n.Children.count(e),u=this.state.style;if("function"==typeof e)return e(u);if(!o||0===a||r<=0)return e;var c=function(t){var e=t.props,r=e.style,o=e.className;return(0,n.cloneElement)(t,tv(tv({},i),{},{style:tv(tv({},void 0===r?{}:r),u),className:o}))};return 1===a?c(n.Children.only(e)):n.createElement("div",null,n.Children.map(e,function(t){return c(t)}))}}],function(t,e){for(var r=0;r=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(t,w),i=parseInt("".concat(r),10),a=parseInt("".concat(n),10),u=parseInt("".concat(e.height||o.height),10),c=parseInt("".concat(e.width||o.width),10);return P(P(P(P(P({},e),o),i?{x:i}:{}),a?{y:a}:{}),{},{height:u,width:c,name:e.name,radius:e.radius})}function k(t){return n.createElement(x.bn,j({shapeType:"rectangle",propTransformer:E,activeClassName:"recharts-active-bar"},t))}var A=function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return function(r,n){if("number"==typeof t)return t;var o=(0,d.hj)(r)||(0,d.Rw)(r);return o?t(r,n):(o||(0,g.Z)(!1),e)}},M=["value","background"];function _(t){return(_="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function T(){return(T=Object.assign?Object.assign.bind():function(t){for(var e=1;e=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(e,M);if(!u)return null;var l=N(N(N(N(N({},c),{},{fill:"#eee"},u),a),(0,b.bw)(t.props,e,r)),{},{onAnimationStart:t.handleAnimationStart,onAnimationEnd:t.handleAnimationEnd,dataKey:o,index:r,className:"recharts-bar-background-rectangle"});return n.createElement(k,T({key:"background-bar-".concat(r),option:t.props.background,isActive:r===i},l))})}},{key:"renderErrorBar",value:function(t,e){if(this.props.isAnimationActive&&!this.state.isAnimationFinished)return null;var r=this.props,o=r.data,i=r.xAxis,a=r.yAxis,u=r.layout,c=r.children,l=(0,y.NN)(c,f.W);if(!l)return null;var p="vertical"===u?o[0].height/2:o[0].width/2,h=function(t,e){var r=Array.isArray(t.value)?t.value[1]:t.value;return{x:t.x,y:t.y,value:r,errorVal:(0,m.F$)(t,e)}};return n.createElement(s.m,{clipPath:t?"url(#clipPath-".concat(e,")"):null},l.map(function(t){return n.cloneElement(t,{key:"error-bar-".concat(e,"-").concat(t.props.dataKey),data:o,xAxis:i,yAxis:a,layout:u,offset:p,dataPointFormatter:h})}))}},{key:"render",value:function(){var t=this.props,e=t.hide,r=t.data,i=t.className,a=t.xAxis,u=t.yAxis,c=t.left,f=t.top,p=t.width,d=t.height,y=t.isAnimationActive,v=t.background,m=t.id;if(e||!r||!r.length)return null;var b=this.state.isAnimationFinished,g=(0,o.Z)("recharts-bar",i),x=a&&a.allowDataOverflow,w=u&&u.allowDataOverflow,O=x||w,j=l()(m)?this.id:m;return n.createElement(s.m,{className:g},x||w?n.createElement("defs",null,n.createElement("clipPath",{id:"clipPath-".concat(j)},n.createElement("rect",{x:x?c:c-p/2,y:w?f:f-d/2,width:x?p:2*p,height:w?d:2*d}))):null,n.createElement(s.m,{className:"recharts-bar-rectangles",clipPath:O?"url(#clipPath-".concat(j,")"):null},v?this.renderBackground():null,this.renderRectangles()),this.renderErrorBar(O,j),(!y||b)&&h.e.renderCallByParent(this.props,r))}}],r=[{key:"getDerivedStateFromProps",value:function(t,e){return t.animationId!==e.prevAnimationId?{prevAnimationId:t.animationId,curData:t.data,prevData:e.curData}:t.data!==e.curData?{curData:t.data}:null}}],e&&D(a.prototype,e),r&&D(a,r),Object.defineProperty(a,"prototype",{writable:!1}),a}(n.PureComponent);R(U,"displayName","Bar"),R(U,"defaultProps",{xAxisId:0,yAxisId:0,legendType:"rect",minPointSize:0,hide:!1,data:[],layout:"vertical",activeBar:!1,isAnimationActive:!v.x.isSsr,animationBegin:0,animationDuration:400,animationEasing:"ease"}),R(U,"getComposedData",function(t){var e=t.props,r=t.item,n=t.barPosition,o=t.bandSize,i=t.xAxis,a=t.yAxis,u=t.xAxisTicks,c=t.yAxisTicks,l=t.stackedData,s=t.dataStartIndex,f=t.displayedData,h=t.offset,v=(0,m.Bu)(n,r);if(!v)return null;var b=e.layout,g=r.type.defaultProps,x=void 0!==g?N(N({},g),r.props):r.props,w=x.dataKey,O=x.children,j=x.minPointSize,S="horizontal"===b?a:i,P=l?S.scale.domain():null,E=(0,m.Yj)({numericAxis:S}),k=(0,y.NN)(O,p.b),M=f.map(function(t,e){l?f=(0,m.Vv)(l[s+e],P):Array.isArray(f=(0,m.F$)(t,w))||(f=[E,f]);var n=A(j,U.defaultProps.minPointSize)(f[1],e);if("horizontal"===b){var f,p,h,y,g,x,O,S=[a.scale(f[0]),a.scale(f[1])],M=S[0],_=S[1];p=(0,m.Fy)({axis:i,ticks:u,bandSize:o,offset:v.offset,entry:t,index:e}),h=null!==(O=null!=_?_:M)&&void 0!==O?O:void 0,y=v.size;var T=M-_;if(g=Number.isNaN(T)?0:T,x={x:p,y:a.y,width:y,height:a.height},Math.abs(n)>0&&Math.abs(g)0&&Math.abs(y)=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}function P(t,e){for(var r=0;r0?this.props:d)),o<=0||a<=0||!y||!y.length)?null:n.createElement(s.m,{className:(0,c.Z)("recharts-cartesian-axis",l),ref:function(e){t.layerReference=e}},r&&this.renderAxisLine(),this.renderTicks(y,this.state.fontSize,this.state.letterSpacing),p._.renderCallByParent(this.props))}}],r=[{key:"renderTickItem",value:function(t,e,r){var o=(0,c.Z)(e.className,"recharts-cartesian-axis-tick-value");return n.isValidElement(t)?n.cloneElement(t,j(j({},e),{},{className:o})):i()(t)?t(j(j({},e),{},{className:o})):n.createElement(f.x,w({},e,{className:"recharts-cartesian-axis-tick-value"}),r)}}],e&&P(o.prototype,e),r&&P(o,r),Object.defineProperty(o,"prototype",{writable:!1}),o}(n.Component);M(T,"displayName","CartesianAxis"),M(T,"defaultProps",{x:0,y:0,width:0,height:0,viewBox:{x:0,y:0,width:0,height:0},orientation:"bottom",ticks:[],stroke:"#666",tickLine:!0,axisLine:!0,tick:!0,mirror:!1,minTickGap:5,tickSize:6,tickMargin:2,interval:"preserveEnd"})},56940:function(t,e,r){"use strict";r.d(e,{q:function(){return M}});var n=r(2265),o=r(86757),i=r.n(o),a=r(1175),u=r(16630),c=r(82944),l=r(85355),s=r(78242),f=r(80285),p=r(25739),h=["x1","y1","x2","y2","key"],d=["offset"];function y(t){return(y="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function v(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),r.push.apply(r,n)}return r}function m(t){for(var e=1;e=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}var x=function(t){var e=t.fill;if(!e||"none"===e)return null;var r=t.fillOpacity,o=t.x,i=t.y,a=t.width,u=t.height,c=t.ry;return n.createElement("rect",{x:o,y:i,ry:c,width:a,height:u,stroke:"none",fill:e,fillOpacity:r,className:"recharts-cartesian-grid-bg"})};function w(t,e){var r;if(n.isValidElement(t))r=n.cloneElement(t,e);else if(i()(t))r=t(e);else{var o=e.x1,a=e.y1,u=e.x2,l=e.y2,s=e.key,f=g(e,h),p=(0,c.L6)(f,!1),y=(p.offset,g(p,d));r=n.createElement("line",b({},y,{x1:o,y1:a,x2:u,y2:l,fill:"none",key:s}))}return r}function O(t){var e=t.x,r=t.width,o=t.horizontal,i=void 0===o||o,a=t.horizontalPoints;if(!i||!a||!a.length)return null;var u=a.map(function(n,o){return w(i,m(m({},t),{},{x1:e,y1:n,x2:e+r,y2:n,key:"line-".concat(o),index:o}))});return n.createElement("g",{className:"recharts-cartesian-grid-horizontal"},u)}function j(t){var e=t.y,r=t.height,o=t.vertical,i=void 0===o||o,a=t.verticalPoints;if(!i||!a||!a.length)return null;var u=a.map(function(n,o){return w(i,m(m({},t),{},{x1:n,y1:e,x2:n,y2:e+r,key:"line-".concat(o),index:o}))});return n.createElement("g",{className:"recharts-cartesian-grid-vertical"},u)}function S(t){var e=t.horizontalFill,r=t.fillOpacity,o=t.x,i=t.y,a=t.width,u=t.height,c=t.horizontalPoints,l=t.horizontal;if(!(void 0===l||l)||!e||!e.length)return null;var s=c.map(function(t){return Math.round(t+i-i)}).sort(function(t,e){return t-e});i!==s[0]&&s.unshift(0);var f=s.map(function(t,c){var l=s[c+1]?s[c+1]-t:i+u-t;if(l<=0)return null;var f=c%e.length;return n.createElement("rect",{key:"react-".concat(c),y:t,x:o,height:l,width:a,stroke:"none",fill:e[f],fillOpacity:r,className:"recharts-cartesian-grid-bg"})});return n.createElement("g",{className:"recharts-cartesian-gridstripes-horizontal"},f)}function P(t){var e=t.vertical,r=t.verticalFill,o=t.fillOpacity,i=t.x,a=t.y,u=t.width,c=t.height,l=t.verticalPoints;if(!(void 0===e||e)||!r||!r.length)return null;var s=l.map(function(t){return Math.round(t+i-i)}).sort(function(t,e){return t-e});i!==s[0]&&s.unshift(0);var f=s.map(function(t,e){var l=s[e+1]?s[e+1]-t:i+u-t;if(l<=0)return null;var f=e%r.length;return n.createElement("rect",{key:"react-".concat(e),x:t,y:a,width:l,height:c,stroke:"none",fill:r[f],fillOpacity:o,className:"recharts-cartesian-grid-bg"})});return n.createElement("g",{className:"recharts-cartesian-gridstripes-vertical"},f)}var E=function(t,e){var r=t.xAxis,n=t.width,o=t.height,i=t.offset;return(0,l.Rf)((0,s.f)(m(m(m({},f.O.defaultProps),r),{},{ticks:(0,l.uY)(r,!0),viewBox:{x:0,y:0,width:n,height:o}})),i.left,i.left+i.width,e)},k=function(t,e){var r=t.yAxis,n=t.width,o=t.height,i=t.offset;return(0,l.Rf)((0,s.f)(m(m(m({},f.O.defaultProps),r),{},{ticks:(0,l.uY)(r,!0),viewBox:{x:0,y:0,width:n,height:o}})),i.top,i.top+i.height,e)},A={horizontal:!0,vertical:!0,stroke:"#ccc",fill:"none",verticalFill:[],horizontalFill:[]};function M(t){var e,r,o,c,l,s,f=(0,p.zn)(),h=(0,p.Mw)(),d=(0,p.qD)(),v=m(m({},t),{},{stroke:null!==(e=t.stroke)&&void 0!==e?e:A.stroke,fill:null!==(r=t.fill)&&void 0!==r?r:A.fill,horizontal:null!==(o=t.horizontal)&&void 0!==o?o:A.horizontal,horizontalFill:null!==(c=t.horizontalFill)&&void 0!==c?c:A.horizontalFill,vertical:null!==(l=t.vertical)&&void 0!==l?l:A.vertical,verticalFill:null!==(s=t.verticalFill)&&void 0!==s?s:A.verticalFill,x:(0,u.hj)(t.x)?t.x:d.left,y:(0,u.hj)(t.y)?t.y:d.top,width:(0,u.hj)(t.width)?t.width:d.width,height:(0,u.hj)(t.height)?t.height:d.height}),g=v.x,w=v.y,M=v.width,_=v.height,T=v.syncWithTicks,C=v.horizontalValues,N=v.verticalValues,D=(0,p.CW)(),I=(0,p.Nf)();if(!(0,u.hj)(M)||M<=0||!(0,u.hj)(_)||_<=0||!(0,u.hj)(g)||g!==+g||!(0,u.hj)(w)||w!==+w)return null;var L=v.verticalCoordinatesGenerator||E,B=v.horizontalCoordinatesGenerator||k,R=v.horizontalPoints,z=v.verticalPoints;if((!R||!R.length)&&i()(B)){var U=C&&C.length,F=B({yAxis:I?m(m({},I),{},{ticks:U?C:I.ticks}):void 0,width:f,height:h,offset:d},!!U||T);(0,a.Z)(Array.isArray(F),"horizontalCoordinatesGenerator should return Array but instead it returned [".concat(y(F),"]")),Array.isArray(F)&&(R=F)}if((!z||!z.length)&&i()(L)){var $=N&&N.length,q=L({xAxis:D?m(m({},D),{},{ticks:$?N:D.ticks}):void 0,width:f,height:h,offset:d},!!$||T);(0,a.Z)(Array.isArray(q),"verticalCoordinatesGenerator should return Array but instead it returned [".concat(y(q),"]")),Array.isArray(q)&&(z=q)}return n.createElement("g",{className:"recharts-cartesian-grid"},n.createElement(x,{fill:v.fill,fillOpacity:v.fillOpacity,x:v.x,y:v.y,width:v.width,height:v.height,ry:v.ry}),n.createElement(O,b({},v,{offset:d,horizontalPoints:R,xAxis:D,yAxis:I})),n.createElement(j,b({},v,{offset:d,verticalPoints:z,xAxis:D,yAxis:I})),n.createElement(S,b({},v,{horizontalPoints:R})),n.createElement(P,b({},v,{verticalPoints:z})))}M.displayName="CartesianGrid"},13137:function(t,e,r){"use strict";r.d(e,{W:function(){return v}});var n=r(2265),o=r(69398),i=r(9841),a=r(82944),u=["offset","layout","width","dataKey","data","dataPointFormatter","xAxis","yAxis"];function c(t){return(c="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function l(){return(l=Object.assign?Object.assign.bind():function(t){for(var e=1;et.length)&&(e=t.length);for(var r=0,n=Array(e);r=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(t,u),m=(0,a.L6)(v,!1);"x"===this.props.direction&&"number"!==d.type&&(0,o.Z)(!1);var b=p.map(function(t){var o,a,u=h(t,f),p=u.x,v=u.y,b=u.value,g=u.errorVal;if(!g)return null;var x=[];if(Array.isArray(g)){var w=function(t){if(Array.isArray(t))return t}(g)||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=r){var n,o,i,a,u=[],c=!0,l=!1;try{for(i=(r=r.call(t)).next;!(c=(n=i.call(r)).done)&&(u.push(n.value),2!==u.length);c=!0);}catch(t){l=!0,o=t}finally{try{if(!c&&null!=r.return&&(a=r.return(),Object(a)!==a))return}finally{if(l)throw o}}return u}}(g,2)||function(t,e){if(t){if("string"==typeof t)return s(t,2);var r=Object.prototype.toString.call(t).slice(8,-1);if("Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return s(t,2)}}(g,2)||function(){throw TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}();o=w[0],a=w[1]}else o=a=g;if("vertical"===r){var O=d.scale,j=v+e,S=j+c,P=j-c,E=O(b-o),k=O(b+a);x.push({x1:k,y1:S,x2:k,y2:P}),x.push({x1:E,y1:j,x2:k,y2:j}),x.push({x1:E,y1:S,x2:E,y2:P})}else if("horizontal"===r){var A=y.scale,M=p+e,_=M-c,T=M+c,C=A(b-o),N=A(b+a);x.push({x1:_,y1:N,x2:T,y2:N}),x.push({x1:M,y1:C,x2:M,y2:N}),x.push({x1:_,y1:C,x2:T,y2:C})}return n.createElement(i.m,l({className:"recharts-errorBar",key:"bar-".concat(x.map(function(t){return"".concat(t.x1,"-").concat(t.x2,"-").concat(t.y1,"-").concat(t.y2)}))},m),x.map(function(t){return n.createElement("line",l({},t,{key:"line-".concat(t.x1,"-").concat(t.x2,"-").concat(t.y1,"-").concat(t.y2)}))}))});return n.createElement(i.m,{className:"recharts-errorBars"},b)}}],function(t,e){for(var r=0;rt*o)return!1;var i=r();return t*(e-t*i/2-n)>=0&&t*(e+t*i/2-o)<=0}function f(t){return(f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function p(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),r.push.apply(r,n)}return r}function h(t){for(var e=1;e=2?(0,i.uY)(m[1].coordinate-m[0].coordinate):1,M=(n="width"===P,f=b.x,p=b.y,d=b.width,y=b.height,1===A?{start:n?f:p,end:n?f+d:p+y}:{start:n?f+d:p+y,end:n?f:p});return"equidistantPreserveStart"===w?function(t,e,r,n,o){for(var i,a=(n||[]).slice(),u=e.start,c=e.end,f=0,p=1,h=u;p<=a.length;)if(i=function(){var e,i=null==n?void 0:n[f];if(void 0===i)return{v:l(n,p)};var a=f,d=function(){return void 0===e&&(e=r(i,a)),e},y=i.coordinate,v=0===f||s(t,y,d,h,c);v||(f=0,h=u,p+=1),v&&(h=y+t*(d()/2+o),f+=p)}())return i.v;return[]}(A,M,k,m,g):("preserveStart"===w||"preserveStartEnd"===w?function(t,e,r,n,o,i){var a=(n||[]).slice(),u=a.length,c=e.start,l=e.end;if(i){var f=n[u-1],p=r(f,u-1),d=t*(f.coordinate+t*p/2-l);a[u-1]=f=h(h({},f),{},{tickCoord:d>0?f.coordinate-d*t:f.coordinate}),s(t,f.tickCoord,function(){return p},c,l)&&(l=f.tickCoord-t*(p/2+o),a[u-1]=h(h({},f),{},{isShow:!0}))}for(var y=i?u-1:u,v=function(e){var n,i=a[e],u=function(){return void 0===n&&(n=r(i,e)),n};if(0===e){var f=t*(i.coordinate-t*u()/2-c);a[e]=i=h(h({},i),{},{tickCoord:f<0?i.coordinate-f*t:i.coordinate})}else a[e]=i=h(h({},i),{},{tickCoord:i.coordinate});s(t,i.tickCoord,u,c,l)&&(c=i.tickCoord+t*(u()/2+o),a[e]=h(h({},i),{},{isShow:!0}))},m=0;m0?l.coordinate-p*t:l.coordinate})}else i[e]=l=h(h({},l),{},{tickCoord:l.coordinate});s(t,l.tickCoord,f,u,c)&&(c=l.tickCoord-t*(f()/2+o),i[e]=h(h({},l),{},{isShow:!0}))},f=a-1;f>=0;f--)l(f);return i}(A,M,k,m,g)).filter(function(t){return t.isShow})}},93765:function(t,e,r){"use strict";r.d(e,{z:function(){return eD}});var n,o,i=r(2265),a=r(77571),u=r.n(a),c=r(86757),l=r.n(c),s=r(99676),f=r.n(s),p=r(13735),h=r.n(p),d=r(34935),y=r.n(d),v=r(37065),m=r.n(v),b=r(61994),g=r(69398),x=r(48777),w=r(9841),O=r(8147),j=r(22190),S=r(81889),P=r(73649),E=r(82944),k=r(55284),A=r(58811),M=r(85355),_=r(16630);function T(t){return(T="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function C(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),r.push.apply(r,n)}return r}function N(t){for(var e=1;e0&&e.handleDrag(t.changedTouches[0])}),W(e,"handleDragEnd",function(){e.setState({isTravellerMoving:!1,isSlideMoving:!1},function(){var t=e.props,r=t.endIndex,n=t.onDragEnd,o=t.startIndex;null==n||n({endIndex:r,startIndex:o})}),e.detachDragEndListener()}),W(e,"handleLeaveWrapper",function(){(e.state.isTravellerMoving||e.state.isSlideMoving)&&(e.leaveTimer=window.setTimeout(e.handleDragEnd,e.props.leaveTimeOut))}),W(e,"handleEnterSlideOrTraveller",function(){e.setState({isTextActive:!0})}),W(e,"handleLeaveSlideOrTraveller",function(){e.setState({isTextActive:!1})}),W(e,"handleSlideDragStart",function(t){var r=X(t)?t.changedTouches[0]:t;e.setState({isTravellerMoving:!1,isSlideMoving:!0,slideMoveStartX:r.pageX}),e.attachDragEndListener()}),e.travellerDragStartHandlers={startX:e.handleTravellerDragStart.bind(e,"startX"),endX:e.handleTravellerDragStart.bind(e,"endX")},e.state={},e}return!function(t,e){if("function"!=typeof e&&null!==e)throw TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),Object.defineProperty(t,"prototype",{writable:!1}),e&&Z(t,e)}(n,t),e=[{key:"componentWillUnmount",value:function(){this.leaveTimer&&(clearTimeout(this.leaveTimer),this.leaveTimer=null),this.detachDragEndListener()}},{key:"getIndex",value:function(t){var e=t.startX,r=t.endX,o=this.state.scaleValues,i=this.props,a=i.gap,u=i.data.length-1,c=n.getIndexInRange(o,Math.min(e,r)),l=n.getIndexInRange(o,Math.max(e,r));return{startIndex:c-c%a,endIndex:l===u?u:l-l%a}}},{key:"getTextOfTick",value:function(t){var e=this.props,r=e.data,n=e.tickFormatter,o=e.dataKey,i=(0,M.F$)(r[t],o,t);return l()(n)?n(i,t):i}},{key:"attachDragEndListener",value:function(){window.addEventListener("mouseup",this.handleDragEnd,!0),window.addEventListener("touchend",this.handleDragEnd,!0),window.addEventListener("mousemove",this.handleDrag,!0)}},{key:"detachDragEndListener",value:function(){window.removeEventListener("mouseup",this.handleDragEnd,!0),window.removeEventListener("touchend",this.handleDragEnd,!0),window.removeEventListener("mousemove",this.handleDrag,!0)}},{key:"handleSlideDrag",value:function(t){var e=this.state,r=e.slideMoveStartX,n=e.startX,o=e.endX,i=this.props,a=i.x,u=i.width,c=i.travellerWidth,l=i.startIndex,s=i.endIndex,f=i.onChange,p=t.pageX-r;p>0?p=Math.min(p,a+u-c-o,a+u-c-n):p<0&&(p=Math.max(p,a-n,a-o));var h=this.getIndex({startX:n+p,endX:o+p});(h.startIndex!==l||h.endIndex!==s)&&f&&f(h),this.setState({startX:n+p,endX:o+p,slideMoveStartX:t.pageX})}},{key:"handleTravellerDragStart",value:function(t,e){var r=X(e)?e.changedTouches[0]:e;this.setState({isSlideMoving:!1,isTravellerMoving:!0,movingTravellerId:t,brushMoveStartX:r.pageX}),this.attachDragEndListener()}},{key:"handleTravellerMove",value:function(t){var e=this.state,r=e.brushMoveStartX,n=e.movingTravellerId,o=e.endX,i=e.startX,a=this.state[n],u=this.props,c=u.x,l=u.width,s=u.travellerWidth,f=u.onChange,p=u.gap,h=u.data,d={startX:this.state.startX,endX:this.state.endX},y=t.pageX-r;y>0?y=Math.min(y,c+l-s-a):y<0&&(y=Math.max(y,c-a)),d[n]=a+y;var v=this.getIndex(d),m=v.startIndex,b=v.endIndex,g=function(){var t=h.length-1;return"startX"===n&&(o>i?m%p==0:b%p==0)||oi?b%p==0:m%p==0)||o>i&&b===t};this.setState(W(W({},n,a+y),"brushMoveStartX",t.pageX),function(){f&&g()&&f(v)})}},{key:"handleTravellerMoveKeyboard",value:function(t,e){var r=this,n=this.state,o=n.scaleValues,i=n.startX,a=n.endX,u=this.state[e],c=o.indexOf(u);if(-1!==c){var l=c+t;if(-1!==l&&!(l>=o.length)){var s=o[l];"startX"===e&&s>=a||"endX"===e&&s<=i||this.setState(W({},e,s),function(){r.props.onChange(r.getIndex({startX:r.state.startX,endX:r.state.endX}))})}}}},{key:"renderBackground",value:function(){var t=this.props,e=t.x,r=t.y,n=t.width,o=t.height,a=t.fill,u=t.stroke;return i.createElement("rect",{stroke:u,fill:a,x:e,y:r,width:n,height:o})}},{key:"renderPanorama",value:function(){var t=this.props,e=t.x,r=t.y,n=t.width,o=t.height,a=t.data,u=t.children,c=t.padding,l=i.Children.only(u);return l?i.cloneElement(l,{x:e,y:r,width:n,height:o,margin:c,compact:!0,data:a}):null}},{key:"renderTravellerLayer",value:function(t,e){var r,o,a=this,u=this.props,c=u.y,l=u.travellerWidth,s=u.height,f=u.traveller,p=u.ariaLabel,h=u.data,d=u.startIndex,y=u.endIndex,v=Math.max(t,this.props.x),m=U(U({},(0,E.L6)(this.props,!1)),{},{x:v,y:c,width:l,height:s}),b=p||"Min value: ".concat(null===(r=h[d])||void 0===r?void 0:r.name,", Max value: ").concat(null===(o=h[y])||void 0===o?void 0:o.name);return i.createElement(w.m,{tabIndex:0,role:"slider","aria-label":b,"aria-valuenow":t,className:"recharts-brush-traveller",onMouseEnter:this.handleEnterSlideOrTraveller,onMouseLeave:this.handleLeaveSlideOrTraveller,onMouseDown:this.travellerDragStartHandlers[e],onTouchStart:this.travellerDragStartHandlers[e],onKeyDown:function(t){["ArrowLeft","ArrowRight"].includes(t.key)&&(t.preventDefault(),t.stopPropagation(),a.handleTravellerMoveKeyboard("ArrowRight"===t.key?1:-1,e))},onFocus:function(){a.setState({isTravellerFocused:!0})},onBlur:function(){a.setState({isTravellerFocused:!1})},style:{cursor:"col-resize"}},n.renderTraveller(f,m))}},{key:"renderSlide",value:function(t,e){var r=this.props,n=r.y,o=r.height,a=r.stroke,u=r.travellerWidth;return i.createElement("rect",{className:"recharts-brush-slide",onMouseEnter:this.handleEnterSlideOrTraveller,onMouseLeave:this.handleLeaveSlideOrTraveller,onMouseDown:this.handleSlideDragStart,onTouchStart:this.handleSlideDragStart,style:{cursor:"move"},stroke:"none",fill:a,fillOpacity:.2,x:Math.min(t,e)+u,y:n,width:Math.max(Math.abs(e-t)-u,0),height:o})}},{key:"renderText",value:function(){var t=this.props,e=t.startIndex,r=t.endIndex,n=t.y,o=t.height,a=t.travellerWidth,u=t.stroke,c=this.state,l=c.startX,s=c.endX,f={pointerEvents:"none",fill:u};return i.createElement(w.m,{className:"recharts-brush-texts"},i.createElement(A.x,R({textAnchor:"end",verticalAnchor:"middle",x:Math.min(l,s)-5,y:n+o/2},f),this.getTextOfTick(e)),i.createElement(A.x,R({textAnchor:"start",verticalAnchor:"middle",x:Math.max(l,s)+a+5,y:n+o/2},f),this.getTextOfTick(r)))}},{key:"render",value:function(){var t=this.props,e=t.data,r=t.className,n=t.children,o=t.x,a=t.y,u=t.width,c=t.height,l=t.alwaysShowText,s=this.state,f=s.startX,p=s.endX,h=s.isTextActive,d=s.isSlideMoving,y=s.isTravellerMoving,v=s.isTravellerFocused;if(!e||!e.length||!(0,_.hj)(o)||!(0,_.hj)(a)||!(0,_.hj)(u)||!(0,_.hj)(c)||u<=0||c<=0)return null;var m=(0,b.Z)("recharts-brush",r),g=1===i.Children.count(n),x=L("userSelect","none");return i.createElement(w.m,{className:m,onMouseLeave:this.handleLeaveWrapper,onTouchMove:this.handleTouchMove,style:x},this.renderBackground(),g&&this.renderPanorama(),this.renderSlide(f,p),this.renderTravellerLayer(f,"startX"),this.renderTravellerLayer(p,"endX"),(h||d||y||v||l)&&this.renderText())}}],r=[{key:"renderDefaultTraveller",value:function(t){var e=t.x,r=t.y,n=t.width,o=t.height,a=t.stroke,u=Math.floor(r+o/2)-1;return i.createElement(i.Fragment,null,i.createElement("rect",{x:e,y:r,width:n,height:o,fill:a,stroke:"none"}),i.createElement("line",{x1:e+1,y1:u,x2:e+n-1,y2:u,fill:"none",stroke:"#fff"}),i.createElement("line",{x1:e+1,y1:u+2,x2:e+n-1,y2:u+2,fill:"none",stroke:"#fff"}))}},{key:"renderTraveller",value:function(t,e){return i.isValidElement(t)?i.cloneElement(t,e):l()(t)?t(e):n.renderDefaultTraveller(e)}},{key:"getDerivedStateFromProps",value:function(t,e){var r=t.data,n=t.width,o=t.x,i=t.travellerWidth,a=t.updateId,u=t.startIndex,c=t.endIndex;if(r!==e.prevData||a!==e.prevUpdateId)return U({prevData:r,prevTravellerWidth:i,prevUpdateId:a,prevX:o,prevWidth:n},r&&r.length?H({data:r,width:n,x:o,travellerWidth:i,startIndex:u,endIndex:c}):{scale:null,scaleValues:null});if(e.scale&&(n!==e.prevWidth||o!==e.prevX||i!==e.prevTravellerWidth)){e.scale.range([o,o+n-i]);var l=e.scale.domain().map(function(t){return e.scale(t)});return{prevData:r,prevTravellerWidth:i,prevUpdateId:a,prevX:o,prevWidth:n,startX:e.scale(t.startIndex),endX:e.scale(t.endIndex),scaleValues:l}}return null}},{key:"getIndexInRange",value:function(t,e){for(var r=t.length,n=0,o=r-1;o-n>1;){var i=Math.floor((n+o)/2);t[i]>e?o=i:n=i}return e>=t[o]?o:n}}],e&&F(n.prototype,e),r&&F(n,r),Object.defineProperty(n,"prototype",{writable:!1}),n}(i.PureComponent);W(G,"displayName","Brush"),W(G,"defaultProps",{height:40,travellerWidth:5,gap:1,fill:"#fff",stroke:"#666",padding:{top:1,right:1,bottom:1,left:1},leaveTimeOut:1e3,alwaysShowText:!1});var V=r(4094),K=r(38569),Q=r(26680),J=function(t,e){var r=t.alwaysShow,n=t.ifOverflow;return r&&(n="extendDomain"),n===e},tt=r(25311),te=r(1175);function tr(){return(tr=Object.assign?Object.assign.bind():function(t){for(var e=1;et.length)&&(e=t.length);for(var r=0,n=Array(e);rt.length)&&(e=t.length);for(var r=0,n=Array(e);r=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(t,t2));return(0,_.hj)(r)&&(0,_.hj)(o)&&(0,_.hj)(f)&&(0,_.hj)(h)&&(0,_.hj)(u)&&(0,_.hj)(l)?i.createElement("path",t5({},(0,E.L6)(y,!0),{className:(0,b.Z)("recharts-cross",d),d:"M".concat(r,",").concat(u,"v").concat(h,"M").concat(l,",").concat(o,"h").concat(f)})):null};function t7(t){var e=t.cx,r=t.cy,n=t.radius,o=t.startAngle,i=t.endAngle;return{points:[(0,tq.op)(e,r,n,o),(0,tq.op)(e,r,n,i)],cx:e,cy:r,radius:n,startAngle:o,endAngle:i}}var t4=r(60474);function t8(t){return(t8="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function t9(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),r.push.apply(r,n)}return r}function et(t){for(var e=1;e=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}function ec(){try{var t=!Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){}))}catch(t){}return(ec=function(){return!!t})()}function el(t){return(el=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}function es(t,e){return(es=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(t,e){return t.__proto__=e,t})(t,e)}function ef(t){return function(t){if(Array.isArray(t))return eh(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||ep(t)||function(){throw TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function ep(t,e){if(t){if("string"==typeof t)return eh(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);if("Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return eh(t,e)}}function eh(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=Array(e);r0?i:t&&t.length&&(0,_.hj)(n)&&(0,_.hj)(o)?t.slice(n,o+1):[]};function eS(t){return"number"===t?[0,"auto"]:void 0}var eP=function(t,e,r,n){var o=t.graphicalItems,i=t.tooltipAxis,a=ej(e,t);return r<0||!o||!o.length||r>=a.length?null:o.reduce(function(o,u){var c,l,s=null!==(c=u.props.data)&&void 0!==c?c:e;if(s&&t.dataStartIndex+t.dataEndIndex!==0&&t.dataEndIndex-t.dataStartIndex>=r&&(s=s.slice(t.dataStartIndex,t.dataEndIndex+1)),i.dataKey&&!i.allowDuplicatedCategory){var f=void 0===s?a:s;l=(0,_.Ap)(f,i.dataKey,n)}else l=s&&s[r]||a[r];return l?[].concat(ef(o),[(0,M.Qo)(u,l)]):o},[])},eE=function(t,e,r,n){var o=n||{x:t.chartX,y:t.chartY},i="horizontal"===r?o.x:"vertical"===r?o.y:"centric"===r?o.angle:o.radius,a=t.orderedTooltipTicks,u=t.tooltipAxis,c=t.tooltipTicks,l=(0,M.VO)(i,a,c,u);if(l>=0&&c){var s=c[l]&&c[l].value,f=eP(t,e,l,s),p=eO(r,a,l,o);return{activeTooltipIndex:l,activeLabel:s,activePayload:f,activeCoordinate:p}}return null},ek=function(t,e){var r=e.axes,n=e.graphicalItems,o=e.axisType,i=e.axisIdKey,a=e.stackGroups,c=e.dataStartIndex,l=e.dataEndIndex,s=t.layout,p=t.children,h=t.stackOffset,d=(0,M.NA)(s,o);return r.reduce(function(e,r){var y=void 0!==r.type.defaultProps?ey(ey({},r.type.defaultProps),r.props):r.props,v=y.type,m=y.dataKey,b=y.allowDataOverflow,g=y.allowDuplicatedCategory,x=y.scale,w=y.ticks,O=y.includeHidden,j=y[i];if(e[j])return e;var S=ej(t.data,{graphicalItems:n.filter(function(t){var e;return(i in t.props?t.props[i]:null===(e=t.type.defaultProps)||void 0===e?void 0:e[i])===j}),dataStartIndex:c,dataEndIndex:l}),P=S.length;(function(t,e,r){if("number"===r&&!0===e&&Array.isArray(t)){var n=null==t?void 0:t[0],o=null==t?void 0:t[1];if(n&&o&&(0,_.hj)(n)&&(0,_.hj)(o))return!0}return!1})(y.domain,b,v)&&(A=(0,M.LG)(y.domain,null,b),d&&("number"===v||"auto"!==x)&&(C=(0,M.gF)(S,m,"category")));var E=eS(v);if(!A||0===A.length){var k,A,T,C,N,D=null!==(N=y.domain)&&void 0!==N?N:E;if(m){if(A=(0,M.gF)(S,m,v),"category"===v&&d){var I=(0,_.bv)(A);g&&I?(T=A,A=f()(0,P)):g||(A=(0,M.ko)(D,A,r).reduce(function(t,e){return t.indexOf(e)>=0?t:[].concat(ef(t),[e])},[]))}else if("category"===v)A=g?A.filter(function(t){return""!==t&&!u()(t)}):(0,M.ko)(D,A,r).reduce(function(t,e){return t.indexOf(e)>=0||""===e||u()(e)?t:[].concat(ef(t),[e])},[]);else if("number"===v){var L=(0,M.ZI)(S,n.filter(function(t){var e,r,n=i in t.props?t.props[i]:null===(e=t.type.defaultProps)||void 0===e?void 0:e[i],o="hide"in t.props?t.props.hide:null===(r=t.type.defaultProps)||void 0===r?void 0:r.hide;return n===j&&(O||!o)}),m,o,s);L&&(A=L)}d&&("number"===v||"auto"!==x)&&(C=(0,M.gF)(S,m,"category"))}else A=d?f()(0,P):a&&a[j]&&a[j].hasStack&&"number"===v?"expand"===h?[0,1]:(0,M.EB)(a[j].stackGroups,c,l):(0,M.s6)(S,n.filter(function(t){var e=i in t.props?t.props[i]:t.type.defaultProps[i],r="hide"in t.props?t.props.hide:t.type.defaultProps.hide;return e===j&&(O||!r)}),v,s,!0);"number"===v?(A=t$(p,A,j,o,w),D&&(A=(0,M.LG)(D,A,b))):"category"===v&&D&&A.every(function(t){return D.indexOf(t)>=0})&&(A=D)}return ey(ey({},e),{},ev({},j,ey(ey({},y),{},{axisType:o,domain:A,categoricalDomain:C,duplicateDomain:T,originalDomain:null!==(k=y.domain)&&void 0!==k?k:E,isCategorical:d,layout:s})))},{})},eA=function(t,e){var r=e.graphicalItems,n=e.Axis,o=e.axisType,i=e.axisIdKey,a=e.stackGroups,u=e.dataStartIndex,c=e.dataEndIndex,l=t.layout,s=t.children,p=ej(t.data,{graphicalItems:r,dataStartIndex:u,dataEndIndex:c}),d=p.length,y=(0,M.NA)(l,o),v=-1;return r.reduce(function(t,e){var m,b=(void 0!==e.type.defaultProps?ey(ey({},e.type.defaultProps),e.props):e.props)[i],g=eS("number");return t[b]?t:(v++,m=y?f()(0,d):a&&a[b]&&a[b].hasStack?t$(s,m=(0,M.EB)(a[b].stackGroups,u,c),b,o):t$(s,m=(0,M.LG)(g,(0,M.s6)(p,r.filter(function(t){var e,r,n=i in t.props?t.props[i]:null===(e=t.type.defaultProps)||void 0===e?void 0:e[i],o="hide"in t.props?t.props.hide:null===(r=t.type.defaultProps)||void 0===r?void 0:r.hide;return n===b&&!o}),"number",l),n.defaultProps.allowDataOverflow),b,o),ey(ey({},t),{},ev({},b,ey(ey({axisType:o},n.defaultProps),{},{hide:!0,orientation:h()(eb,"".concat(o,".").concat(v%2),null),domain:m,originalDomain:g,isCategorical:y,layout:l}))))},{})},eM=function(t,e){var r=e.axisType,n=void 0===r?"xAxis":r,o=e.AxisComp,i=e.graphicalItems,a=e.stackGroups,u=e.dataStartIndex,c=e.dataEndIndex,l=t.children,s="".concat(n,"Id"),f=(0,E.NN)(l,o),p={};return f&&f.length?p=ek(t,{axes:f,graphicalItems:i,axisType:n,axisIdKey:s,stackGroups:a,dataStartIndex:u,dataEndIndex:c}):i&&i.length&&(p=eA(t,{Axis:o,graphicalItems:i,axisType:n,axisIdKey:s,stackGroups:a,dataStartIndex:u,dataEndIndex:c})),p},e_=function(t){var e=(0,_.Kt)(t),r=(0,M.uY)(e,!1,!0);return{tooltipTicks:r,orderedTooltipTicks:y()(r,function(t){return t.coordinate}),tooltipAxis:e,tooltipAxisBandSize:(0,M.zT)(e,r)}},eT=function(t){var e=t.children,r=t.defaultShowTooltip,n=(0,E.sP)(e,G),o=0,i=0;return t.data&&0!==t.data.length&&(i=t.data.length-1),n&&n.props&&(n.props.startIndex>=0&&(o=n.props.startIndex),n.props.endIndex>=0&&(i=n.props.endIndex)),{chartX:0,chartY:0,dataStartIndex:o,dataEndIndex:i,activeTooltipIndex:-1,isTooltipActive:!!r}},eC=function(t){return"horizontal"===t?{numericAxisName:"yAxis",cateAxisName:"xAxis"}:"vertical"===t?{numericAxisName:"xAxis",cateAxisName:"yAxis"}:"centric"===t?{numericAxisName:"radiusAxis",cateAxisName:"angleAxis"}:{numericAxisName:"angleAxis",cateAxisName:"radiusAxis"}},eN=function(t,e){var r=t.props,n=t.graphicalItems,o=t.xAxisMap,i=void 0===o?{}:o,a=t.yAxisMap,u=void 0===a?{}:a,c=r.width,l=r.height,s=r.children,f=r.margin||{},p=(0,E.sP)(s,G),d=(0,E.sP)(s,j.D),y=Object.keys(u).reduce(function(t,e){var r=u[e],n=r.orientation;return r.mirror||r.hide?t:ey(ey({},t),{},ev({},n,t[n]+r.width))},{left:f.left||0,right:f.right||0}),v=Object.keys(i).reduce(function(t,e){var r=i[e],n=r.orientation;return r.mirror||r.hide?t:ey(ey({},t),{},ev({},n,h()(t,"".concat(n))+r.height))},{top:f.top||0,bottom:f.bottom||0}),m=ey(ey({},v),y),b=m.bottom;p&&(m.bottom+=p.props.height||G.defaultProps.height),d&&e&&(m=(0,M.By)(m,n,r,e));var g=c-m.left-m.right,x=l-m.top-m.bottom;return ey(ey({brushBottom:b},m),{},{width:Math.max(g,0),height:Math.max(x,0)})},eD=function(t){var e=t.chartName,r=t.GraphicalChild,n=t.defaultTooltipEventType,o=void 0===n?"axis":n,a=t.validateTooltipEventTypes,c=void 0===a?["axis"]:a,s=t.axisComponents,f=t.legendContent,p=t.formatAxisMap,d=t.defaultProps,y=function(t,e){var r=e.graphicalItems,n=e.stackGroups,o=e.offset,i=e.updateId,a=e.dataStartIndex,c=e.dataEndIndex,l=t.barSize,f=t.layout,p=t.barGap,h=t.barCategoryGap,d=t.maxBarSize,y=eC(f),v=y.numericAxisName,m=y.cateAxisName,b=!!r&&!!r.length&&r.some(function(t){var e=(0,E.Gf)(t&&t.type);return e&&e.indexOf("Bar")>=0}),x=[];return r.forEach(function(r,y){var w=ej(t.data,{graphicalItems:[r],dataStartIndex:a,dataEndIndex:c}),O=void 0!==r.type.defaultProps?ey(ey({},r.type.defaultProps),r.props):r.props,j=O.dataKey,S=O.maxBarSize,P=O["".concat(v,"Id")],k=O["".concat(m,"Id")],A=s.reduce(function(t,r){var n=e["".concat(r.axisType,"Map")],o=O["".concat(r.axisType,"Id")];n&&n[o]||"zAxis"===r.axisType||(0,g.Z)(!1);var i=n[o];return ey(ey({},t),{},ev(ev({},r.axisType,i),"".concat(r.axisType,"Ticks"),(0,M.uY)(i)))},{}),_=A[m],T=A["".concat(m,"Ticks")],C=n&&n[P]&&n[P].hasStack&&(0,M.O3)(r,n[P].stackGroups),N=(0,E.Gf)(r.type).indexOf("Bar")>=0,D=(0,M.zT)(_,T),I=[],L=b&&(0,M.pt)({barSize:l,stackGroups:n,totalSize:"xAxis"===m?A[m].width:"yAxis"===m?A[m].height:void 0});if(N){var B,R,z=u()(S)?d:S,U=null!==(B=null!==(R=(0,M.zT)(_,T,!0))&&void 0!==R?R:z)&&void 0!==B?B:0;I=(0,M.qz)({barGap:p,barCategoryGap:h,bandSize:U!==D?U:D,sizeList:L[k],maxBarSize:z}),U!==D&&(I=I.map(function(t){return ey(ey({},t),{},{position:ey(ey({},t.position),{},{offset:t.position.offset-U/2})})}))}var F=r&&r.type&&r.type.getComposedData;F&&x.push({props:ey(ey({},F(ey(ey({},A),{},{displayedData:w,props:t,dataKey:j,item:r,bandSize:D,barPosition:I,offset:o,stackedData:C,layout:f,dataStartIndex:a,dataEndIndex:c}))),{},ev(ev(ev({key:r.key||"item-".concat(y)},v,A[v]),m,A[m]),"animationId",i)),childIndex:(0,E.$R)(r,t.children),item:r})}),x},v=function(t,n){var o=t.props,i=t.dataStartIndex,a=t.dataEndIndex,u=t.updateId;if(!(0,E.TT)({props:o}))return null;var c=o.children,l=o.layout,f=o.stackOffset,h=o.data,d=o.reverseStackOrder,v=eC(l),m=v.numericAxisName,b=v.cateAxisName,g=(0,E.NN)(c,r),x=(0,M.wh)(h,g,"".concat(m,"Id"),"".concat(b,"Id"),f,d),w=s.reduce(function(t,e){var r="".concat(e.axisType,"Map");return ey(ey({},t),{},ev({},r,eM(o,ey(ey({},e),{},{graphicalItems:g,stackGroups:e.axisType===m&&x,dataStartIndex:i,dataEndIndex:a}))))},{}),O=eN(ey(ey({},w),{},{props:o,graphicalItems:g}),null==n?void 0:n.legendBBox);Object.keys(w).forEach(function(t){w[t]=p(o,w[t],O,t.replace("Map",""),e)});var j=e_(w["".concat(b,"Map")]),S=y(o,ey(ey({},w),{},{dataStartIndex:i,dataEndIndex:a,updateId:u,graphicalItems:g,stackGroups:x,offset:O}));return ey(ey({formattedGraphicalItems:S,graphicalItems:g,offset:O,stackGroups:x},j),w)},j=function(t){var r;function n(t){var r,o,a,c,s;return!function(t,e){if(!(t instanceof e))throw TypeError("Cannot call a class as a function")}(this,n),c=n,s=[t],c=el(c),ev(a=function(t,e){if(e&&("object"===eo(e)||"function"==typeof e))return e;if(void 0!==e)throw TypeError("Derived constructors may only return object or undefined");return function(t){if(void 0===t)throw ReferenceError("this hasn't been initialised - super() hasn't been called");return t}(t)}(this,ec()?Reflect.construct(c,s||[],el(this).constructor):c.apply(this,s)),"eventEmitterSymbol",Symbol("rechartsEventEmitter")),ev(a,"accessibilityManager",new tQ),ev(a,"handleLegendBBoxUpdate",function(t){if(t){var e=a.state,r=e.dataStartIndex,n=e.dataEndIndex,o=e.updateId;a.setState(ey({legendBBox:t},v({props:a.props,dataStartIndex:r,dataEndIndex:n,updateId:o},ey(ey({},a.state),{},{legendBBox:t}))))}}),ev(a,"handleReceiveSyncEvent",function(t,e,r){a.props.syncId===t&&(r!==a.eventEmitterSymbol||"function"==typeof a.props.syncMethod)&&a.applySyncEvent(e)}),ev(a,"handleBrushChange",function(t){var e=t.startIndex,r=t.endIndex;if(e!==a.state.dataStartIndex||r!==a.state.dataEndIndex){var n=a.state.updateId;a.setState(function(){return ey({dataStartIndex:e,dataEndIndex:r},v({props:a.props,dataStartIndex:e,dataEndIndex:r,updateId:n},a.state))}),a.triggerSyncEvent({dataStartIndex:e,dataEndIndex:r})}}),ev(a,"handleMouseEnter",function(t){var e=a.getMouseInfo(t);if(e){var r=ey(ey({},e),{},{isTooltipActive:!0});a.setState(r),a.triggerSyncEvent(r);var n=a.props.onMouseEnter;l()(n)&&n(r,t)}}),ev(a,"triggeredAfterMouseMove",function(t){var e=a.getMouseInfo(t),r=e?ey(ey({},e),{},{isTooltipActive:!0}):{isTooltipActive:!1};a.setState(r),a.triggerSyncEvent(r);var n=a.props.onMouseMove;l()(n)&&n(r,t)}),ev(a,"handleItemMouseEnter",function(t){a.setState(function(){return{isTooltipActive:!0,activeItem:t,activePayload:t.tooltipPayload,activeCoordinate:t.tooltipPosition||{x:t.cx,y:t.cy}}})}),ev(a,"handleItemMouseLeave",function(){a.setState(function(){return{isTooltipActive:!1}})}),ev(a,"handleMouseMove",function(t){t.persist(),a.throttleTriggeredAfterMouseMove(t)}),ev(a,"handleMouseLeave",function(t){a.throttleTriggeredAfterMouseMove.cancel();var e={isTooltipActive:!1};a.setState(e),a.triggerSyncEvent(e);var r=a.props.onMouseLeave;l()(r)&&r(e,t)}),ev(a,"handleOuterEvent",function(t){var e,r=(0,E.Bh)(t),n=h()(a.props,"".concat(r));r&&l()(n)&&n(null!==(e=/.*touch.*/i.test(r)?a.getMouseInfo(t.changedTouches[0]):a.getMouseInfo(t))&&void 0!==e?e:{},t)}),ev(a,"handleClick",function(t){var e=a.getMouseInfo(t);if(e){var r=ey(ey({},e),{},{isTooltipActive:!0});a.setState(r),a.triggerSyncEvent(r);var n=a.props.onClick;l()(n)&&n(r,t)}}),ev(a,"handleMouseDown",function(t){var e=a.props.onMouseDown;l()(e)&&e(a.getMouseInfo(t),t)}),ev(a,"handleMouseUp",function(t){var e=a.props.onMouseUp;l()(e)&&e(a.getMouseInfo(t),t)}),ev(a,"handleTouchMove",function(t){null!=t.changedTouches&&t.changedTouches.length>0&&a.throttleTriggeredAfterMouseMove(t.changedTouches[0])}),ev(a,"handleTouchStart",function(t){null!=t.changedTouches&&t.changedTouches.length>0&&a.handleMouseDown(t.changedTouches[0])}),ev(a,"handleTouchEnd",function(t){null!=t.changedTouches&&t.changedTouches.length>0&&a.handleMouseUp(t.changedTouches[0])}),ev(a,"handleDoubleClick",function(t){var e=a.props.onDoubleClick;l()(e)&&e(a.getMouseInfo(t),t)}),ev(a,"handleContextMenu",function(t){var e=a.props.onContextMenu;l()(e)&&e(a.getMouseInfo(t),t)}),ev(a,"triggerSyncEvent",function(t){void 0!==a.props.syncId&&tY.emit(tH,a.props.syncId,t,a.eventEmitterSymbol)}),ev(a,"applySyncEvent",function(t){var e=a.props,r=e.layout,n=e.syncMethod,o=a.state.updateId,i=t.dataStartIndex,u=t.dataEndIndex;if(void 0!==t.dataStartIndex||void 0!==t.dataEndIndex)a.setState(ey({dataStartIndex:i,dataEndIndex:u},v({props:a.props,dataStartIndex:i,dataEndIndex:u,updateId:o},a.state)));else if(void 0!==t.activeTooltipIndex){var c=t.chartX,l=t.chartY,s=t.activeTooltipIndex,f=a.state,p=f.offset,h=f.tooltipTicks;if(!p)return;if("function"==typeof n)s=n(h,t);else if("value"===n){s=-1;for(var d=0;d=0){if(s.dataKey&&!s.allowDuplicatedCategory){var A="function"==typeof s.dataKey?function(t){return"function"==typeof s.dataKey?s.dataKey(t.payload):null}:"payload.".concat(s.dataKey.toString());C=(0,_.Ap)(v,A,p),N=m&&b&&(0,_.Ap)(b,A,p)}else C=null==v?void 0:v[f],N=m&&b&&b[f];if(S||j){var T=void 0!==t.props.activeIndex?t.props.activeIndex:f;return[(0,i.cloneElement)(t,ey(ey(ey({},n.props),P),{},{activeIndex:T})),null,null]}if(!u()(C))return[k].concat(ef(a.renderActivePoints({item:n,activePoint:C,basePoint:N,childIndex:f,isRange:m})))}else{var C,N,D,I=(null!==(D=a.getItemByXY(a.state.activeCoordinate))&&void 0!==D?D:{graphicalItem:k}).graphicalItem,L=I.item,B=void 0===L?t:L,R=I.childIndex,z=ey(ey(ey({},n.props),P),{},{activeIndex:R});return[(0,i.cloneElement)(B,z),null,null]}}return m?[k,null,null]:[k,null]}),ev(a,"renderCustomized",function(t,e,r){return(0,i.cloneElement)(t,ey(ey({key:"recharts-customized-".concat(r)},a.props),a.state))}),ev(a,"renderMap",{CartesianGrid:{handler:ew,once:!0},ReferenceArea:{handler:a.renderReferenceElement},ReferenceLine:{handler:ew},ReferenceDot:{handler:a.renderReferenceElement},XAxis:{handler:ew},YAxis:{handler:ew},Brush:{handler:a.renderBrush,once:!0},Bar:{handler:a.renderGraphicChild},Line:{handler:a.renderGraphicChild},Area:{handler:a.renderGraphicChild},Radar:{handler:a.renderGraphicChild},RadialBar:{handler:a.renderGraphicChild},Scatter:{handler:a.renderGraphicChild},Pie:{handler:a.renderGraphicChild},Funnel:{handler:a.renderGraphicChild},Tooltip:{handler:a.renderCursor,once:!0},PolarGrid:{handler:a.renderPolarGrid,once:!0},PolarAngleAxis:{handler:a.renderPolarAxis},PolarRadiusAxis:{handler:a.renderPolarAxis},Customized:{handler:a.renderCustomized}}),a.clipPathId="".concat(null!==(r=t.id)&&void 0!==r?r:(0,_.EL)("recharts"),"-clip"),a.throttleTriggeredAfterMouseMove=m()(a.triggeredAfterMouseMove,null!==(o=t.throttleDelay)&&void 0!==o?o:1e3/60),a.state={},a}return!function(t,e){if("function"!=typeof e&&null!==e)throw TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),Object.defineProperty(t,"prototype",{writable:!1}),e&&es(t,e)}(n,t),r=[{key:"componentDidMount",value:function(){var t,e;this.addListener(),this.accessibilityManager.setDetails({container:this.container,offset:{left:null!==(t=this.props.margin.left)&&void 0!==t?t:0,top:null!==(e=this.props.margin.top)&&void 0!==e?e:0},coordinateList:this.state.tooltipTicks,mouseHandlerCallback:this.triggeredAfterMouseMove,layout:this.props.layout}),this.displayDefaultTooltip()}},{key:"displayDefaultTooltip",value:function(){var t=this.props,e=t.children,r=t.data,n=t.height,o=t.layout,i=(0,E.sP)(e,O.u);if(i){var a=i.props.defaultIndex;if("number"==typeof a&&!(a<0)&&!(a>this.state.tooltipTicks.length-1)){var u=this.state.tooltipTicks[a]&&this.state.tooltipTicks[a].value,c=eP(this.state,r,a,u),l=this.state.tooltipTicks[a].coordinate,s=(this.state.offset.top+n)/2,f="horizontal"===o?{x:l,y:s}:{y:l,x:s},p=this.state.formattedGraphicalItems.find(function(t){return"Scatter"===t.item.type.name});p&&(f=ey(ey({},f),p.props.points[a].tooltipPosition),c=p.props.points[a].tooltipPayload);var h={activeTooltipIndex:a,isTooltipActive:!0,activeLabel:u,activePayload:c,activeCoordinate:f};this.setState(h),this.renderCursor(i),this.accessibilityManager.setIndex(a)}}}},{key:"getSnapshotBeforeUpdate",value:function(t,e){if(!this.props.accessibilityLayer)return null;if(this.state.tooltipTicks!==e.tooltipTicks&&this.accessibilityManager.setDetails({coordinateList:this.state.tooltipTicks}),this.props.layout!==t.layout&&this.accessibilityManager.setDetails({layout:this.props.layout}),this.props.margin!==t.margin){var r,n;this.accessibilityManager.setDetails({offset:{left:null!==(r=this.props.margin.left)&&void 0!==r?r:0,top:null!==(n=this.props.margin.top)&&void 0!==n?n:0}})}return null}},{key:"componentDidUpdate",value:function(t){(0,E.rL)([(0,E.sP)(t.children,O.u)],[(0,E.sP)(this.props.children,O.u)])||this.displayDefaultTooltip()}},{key:"componentWillUnmount",value:function(){this.removeListener(),this.throttleTriggeredAfterMouseMove.cancel()}},{key:"getTooltipEventType",value:function(){var t=(0,E.sP)(this.props.children,O.u);if(t&&"boolean"==typeof t.props.shared){var e=t.props.shared?"axis":"item";return c.indexOf(e)>=0?e:o}return o}},{key:"getMouseInfo",value:function(t){if(!this.container)return null;var e=this.container,r=e.getBoundingClientRect(),n=(0,V.os)(r),o={chartX:Math.round(t.pageX-n.left),chartY:Math.round(t.pageY-n.top)},i=r.width/e.offsetWidth||1,a=this.inRange(o.chartX,o.chartY,i);if(!a)return null;var u=this.state,c=u.xAxisMap,l=u.yAxisMap,s=this.getTooltipEventType(),f=eE(this.state,this.props.data,this.props.layout,a);if("axis"!==s&&c&&l){var p=(0,_.Kt)(c).scale,h=(0,_.Kt)(l).scale,d=p&&p.invert?p.invert(o.chartX):null,y=h&&h.invert?h.invert(o.chartY):null;return ey(ey({},o),{},{xValue:d,yValue:y},f)}return f?ey(ey({},o),f):null}},{key:"inRange",value:function(t,e){var r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1,n=this.props.layout,o=t/r,i=e/r;if("horizontal"===n||"vertical"===n){var a=this.state.offset;return o>=a.left&&o<=a.left+a.width&&i>=a.top&&i<=a.top+a.height?{x:o,y:i}:null}var u=this.state,c=u.angleAxisMap,l=u.radiusAxisMap;if(c&&l){var s=(0,_.Kt)(c);return(0,tq.z3)({x:o,y:i},s)}return null}},{key:"parseEventsOfWrapper",value:function(){var t=this.props.children,e=this.getTooltipEventType(),r=(0,E.sP)(t,O.u),n={};return r&&"axis"===e&&(n="click"===r.props.trigger?{onClick:this.handleClick}:{onMouseEnter:this.handleMouseEnter,onDoubleClick:this.handleDoubleClick,onMouseMove:this.handleMouseMove,onMouseLeave:this.handleMouseLeave,onTouchMove:this.handleTouchMove,onTouchStart:this.handleTouchStart,onTouchEnd:this.handleTouchEnd,onContextMenu:this.handleContextMenu}),ey(ey({},(0,tX.Ym)(this.props,this.handleOuterEvent)),n)}},{key:"addListener",value:function(){tY.on(tH,this.handleReceiveSyncEvent)}},{key:"removeListener",value:function(){tY.removeListener(tH,this.handleReceiveSyncEvent)}},{key:"filterFormatItem",value:function(t,e,r){for(var n=this.state.formattedGraphicalItems,o=0,i=n.length;ot.length)&&(e=t.length);for(var r=0,n=Array(e);r=0?1:-1;"insideStart"===u?(o=b+S*l,a=w):"insideEnd"===u?(o=g-S*l,a=!w):"end"===u&&(o=g+S*l,a=w),a=j<=0?a:!a;var P=(0,d.op)(p,y,O,o),E=(0,d.op)(p,y,O,o+(a?1:-1)*359),k="M".concat(P.x,",").concat(P.y,"\n A").concat(O,",").concat(O,",0,1,").concat(a?0:1,",\n ").concat(E.x,",").concat(E.y),A=i()(t.id)?(0,h.EL)("recharts-radial-line-"):t.id;return n.createElement("text",x({},r,{dominantBaseline:"central",className:(0,s.Z)("recharts-radial-bar-label",f)}),n.createElement("defs",null,n.createElement("path",{id:A,d:k})),n.createElement("textPath",{xlinkHref:"#".concat(A)},e))},j=function(t){var e=t.viewBox,r=t.offset,n=t.position,o=e.cx,i=e.cy,a=e.innerRadius,u=e.outerRadius,c=(e.startAngle+e.endAngle)/2;if("outside"===n){var l=(0,d.op)(o,i,u+r,c),s=l.x;return{x:s,y:l.y,textAnchor:s>=o?"start":"end",verticalAnchor:"middle"}}if("center"===n)return{x:o,y:i,textAnchor:"middle",verticalAnchor:"middle"};if("centerTop"===n)return{x:o,y:i,textAnchor:"middle",verticalAnchor:"start"};if("centerBottom"===n)return{x:o,y:i,textAnchor:"middle",verticalAnchor:"end"};var f=(0,d.op)(o,i,(a+u)/2,c);return{x:f.x,y:f.y,textAnchor:"middle",verticalAnchor:"middle"}},S=function(t){var e=t.viewBox,r=t.parentViewBox,n=t.offset,o=t.position,i=e.x,a=e.y,u=e.width,c=e.height,s=c>=0?1:-1,f=s*n,p=s>0?"end":"start",d=s>0?"start":"end",y=u>=0?1:-1,v=y*n,m=y>0?"end":"start",b=y>0?"start":"end";if("top"===o)return g(g({},{x:i+u/2,y:a-s*n,textAnchor:"middle",verticalAnchor:p}),r?{height:Math.max(a-r.y,0),width:u}:{});if("bottom"===o)return g(g({},{x:i+u/2,y:a+c+f,textAnchor:"middle",verticalAnchor:d}),r?{height:Math.max(r.y+r.height-(a+c),0),width:u}:{});if("left"===o){var x={x:i-v,y:a+c/2,textAnchor:m,verticalAnchor:"middle"};return g(g({},x),r?{width:Math.max(x.x-r.x,0),height:c}:{})}if("right"===o){var w={x:i+u+v,y:a+c/2,textAnchor:b,verticalAnchor:"middle"};return g(g({},w),r?{width:Math.max(r.x+r.width-w.x,0),height:c}:{})}var O=r?{width:u,height:c}:{};return"insideLeft"===o?g({x:i+v,y:a+c/2,textAnchor:b,verticalAnchor:"middle"},O):"insideRight"===o?g({x:i+u-v,y:a+c/2,textAnchor:m,verticalAnchor:"middle"},O):"insideTop"===o?g({x:i+u/2,y:a+f,textAnchor:"middle",verticalAnchor:d},O):"insideBottom"===o?g({x:i+u/2,y:a+c-f,textAnchor:"middle",verticalAnchor:p},O):"insideTopLeft"===o?g({x:i+v,y:a+f,textAnchor:b,verticalAnchor:d},O):"insideTopRight"===o?g({x:i+u-v,y:a+f,textAnchor:m,verticalAnchor:d},O):"insideBottomLeft"===o?g({x:i+v,y:a+c-f,textAnchor:b,verticalAnchor:p},O):"insideBottomRight"===o?g({x:i+u-v,y:a+c-f,textAnchor:m,verticalAnchor:p},O):l()(o)&&((0,h.hj)(o.x)||(0,h.hU)(o.x))&&((0,h.hj)(o.y)||(0,h.hU)(o.y))?g({x:i+(0,h.h1)(o.x,u),y:a+(0,h.h1)(o.y,c),textAnchor:"end",verticalAnchor:"end"},O):g({x:i+u/2,y:a+c/2,textAnchor:"middle",verticalAnchor:"middle"},O)};function P(t){var e,r=t.offset,o=g({offset:void 0===r?5:r},function(t,e){if(null==t)return{};var r,n,o=function(t,e){if(null==t)return{};var r={};for(var n in t)if(Object.prototype.hasOwnProperty.call(t,n)){if(e.indexOf(n)>=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(t,v)),a=o.viewBox,c=o.position,l=o.value,d=o.children,y=o.content,m=o.className,b=o.textBreakAll;if(!a||i()(l)&&i()(d)&&!(0,n.isValidElement)(y)&&!u()(y))return null;if((0,n.isValidElement)(y))return(0,n.cloneElement)(y,o);if(u()(y)){if(e=(0,n.createElement)(y,o),(0,n.isValidElement)(e))return e}else e=w(o);var P="cx"in a&&(0,h.hj)(a.cx),E=(0,p.L6)(o,!0);if(P&&("insideStart"===c||"insideEnd"===c||"end"===c))return O(o,e,E);var k=P?j(o):S(o);return n.createElement(f.x,x({className:(0,s.Z)("recharts-label",void 0===m?"":m)},E,k,{breakAll:b}),e)}P.displayName="Label";var E=function(t){var e=t.cx,r=t.cy,n=t.angle,o=t.startAngle,i=t.endAngle,a=t.r,u=t.radius,c=t.innerRadius,l=t.outerRadius,s=t.x,f=t.y,p=t.top,d=t.left,y=t.width,v=t.height,m=t.clockWise,b=t.labelViewBox;if(b)return b;if((0,h.hj)(y)&&(0,h.hj)(v)){if((0,h.hj)(s)&&(0,h.hj)(f))return{x:s,y:f,width:y,height:v};if((0,h.hj)(p)&&(0,h.hj)(d))return{x:p,y:d,width:y,height:v}}return(0,h.hj)(s)&&(0,h.hj)(f)?{x:s,y:f,width:0,height:0}:(0,h.hj)(e)&&(0,h.hj)(r)?{cx:e,cy:r,startAngle:o||n||0,endAngle:i||n||0,innerRadius:c||0,outerRadius:l||u||a||0,clockWise:m}:t.viewBox?t.viewBox:{}};P.parseViewBox=E,P.renderCallByParent=function(t,e){var r,o,i=!(arguments.length>2)||void 0===arguments[2]||arguments[2];if(!t||!t.children&&i&&!t.label)return null;var a=t.children,c=E(t),s=(0,p.NN)(a,P).map(function(t,r){return(0,n.cloneElement)(t,{viewBox:e||c,key:"label-".concat(r)})});return i?[(r=t.label,o=e||c,r?!0===r?n.createElement(P,{key:"label-implicit",viewBox:o}):(0,h.P2)(r)?n.createElement(P,{key:"label-implicit",viewBox:o,value:r}):(0,n.isValidElement)(r)?r.type===P?(0,n.cloneElement)(r,{key:"label-implicit",viewBox:o}):n.createElement(P,{key:"label-implicit",content:r,viewBox:o}):u()(r)?n.createElement(P,{key:"label-implicit",content:r,viewBox:o}):l()(r)?n.createElement(P,x({viewBox:o},r,{key:"label-implicit"})):null:null)].concat(function(t){if(Array.isArray(t))return m(t)}(s)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(s)||function(t,e){if(t){if("string"==typeof t)return m(t,void 0);var r=Object.prototype.toString.call(t).slice(8,-1);if("Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return m(t,void 0)}}(s)||function(){throw TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()):s}},58772:function(t,e,r){"use strict";r.d(e,{e:function(){return P}});var n=r(2265),o=r(77571),i=r.n(o),a=r(28302),u=r.n(a),c=r(86757),l=r.n(c),s=r(86185),f=r.n(s),p=r(26680),h=r(9841),d=r(82944),y=r(85355);function v(t){return(v="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var m=["valueAccessor"],b=["data","dataKey","clockWise","id","textBreakAll"];function g(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=Array(e);r=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}var S=function(t){return Array.isArray(t.value)?f()(t.value):t.value};function P(t){var e=t.valueAccessor,r=void 0===e?S:e,o=j(t,m),a=o.data,u=o.dataKey,c=o.clockWise,l=o.id,s=o.textBreakAll,f=j(o,b);return a&&a.length?n.createElement(h.m,{className:"recharts-label-list"},a.map(function(t,e){var o=i()(u)?r(t,e):(0,y.F$)(t&&t.payload,u),a=i()(l)?{}:{id:"".concat(l,"-").concat(e)};return n.createElement(p._,x({},(0,d.L6)(t,!0),f,a,{parentViewBox:t.parentViewBox,value:o,textBreakAll:s,viewBox:p._.parseViewBox(i()(c)?t:O(O({},t),{},{clockWise:c})),key:"label-".concat(e),index:e}))})):null}P.displayName="LabelList",P.renderCallByParent=function(t,e){var r,o=!(arguments.length>2)||void 0===arguments[2]||arguments[2];if(!t||!t.children&&o&&!t.label)return null;var i=t.children,a=(0,d.NN)(i,P).map(function(t,r){return(0,n.cloneElement)(t,{data:e,key:"labelList-".concat(r)})});return o?[(r=t.label)?!0===r?n.createElement(P,{key:"labelList-implicit",data:e}):n.isValidElement(r)||l()(r)?n.createElement(P,{key:"labelList-implicit",data:e,content:r}):u()(r)?n.createElement(P,x({data:e},r,{key:"labelList-implicit"})):null:null].concat(function(t){if(Array.isArray(t))return g(t)}(a)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(a)||function(t,e){if(t){if("string"==typeof t)return g(t,void 0);var r=Object.prototype.toString.call(t).slice(8,-1);if("Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return g(t,void 0)}}(a)||function(){throw TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()):a}},22190:function(t,e,r){"use strict";r.d(e,{D:function(){return N}});var n=r(2265),o=r(86757),i=r.n(o),a=r(61994),u=r(1175),c=r(48777),l=r(14870),s=r(41637);function f(t){return(f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function p(){return(p=Object.assign?Object.assign.bind():function(t){for(var e=1;e');var x=e.inactive?h:e.color;return n.createElement("li",p({className:b,style:y,key:"legend-item-".concat(r)},(0,s.bw)(t.props,e,r)),n.createElement(c.T,{width:o,height:o,viewBox:d,style:v},t.renderIcon(e)),n.createElement("span",{className:"recharts-legend-item-text",style:{color:x}},l?l(g,e,r):g))})}},{key:"render",value:function(){var t=this.props,e=t.payload,r=t.layout,o=t.align;return e&&e.length?n.createElement("ul",{className:"recharts-default-legend",style:{padding:0,margin:0,textAlign:"horizontal"===r?o:"left"}},this.renderItems()):null}}],function(t,e){for(var r=0;r1||Math.abs(e.height-this.lastBoundingBox.height)>1)&&(this.lastBoundingBox.width=e.width,this.lastBoundingBox.height=e.height,t&&t(e)):(-1!==this.lastBoundingBox.width||-1!==this.lastBoundingBox.height)&&(this.lastBoundingBox.width=-1,this.lastBoundingBox.height=-1,t&&t(null))}},{key:"getBBoxSnapshot",value:function(){return this.lastBoundingBox.width>=0&&this.lastBoundingBox.height>=0?P({},this.lastBoundingBox):{width:0,height:0}}},{key:"getDefaultPosition",value:function(t){var e,r,n=this.props,o=n.layout,i=n.align,a=n.verticalAlign,u=n.margin,c=n.chartWidth,l=n.chartHeight;return t&&(void 0!==t.left&&null!==t.left||void 0!==t.right&&null!==t.right)||(e="center"===i&&"vertical"===o?{left:((c||0)-this.getBBoxSnapshot().width)/2}:"right"===i?{right:u&&u.right||0}:{left:u&&u.left||0}),t&&(void 0!==t.top&&null!==t.top||void 0!==t.bottom&&null!==t.bottom)||(r="middle"===a?{top:((l||0)-this.getBBoxSnapshot().height)/2}:"bottom"===a?{bottom:u&&u.bottom||0}:{top:u&&u.top||0}),P(P({},e),r)}},{key:"render",value:function(){var t=this,e=this.props,r=e.content,o=e.width,i=e.height,a=e.wrapperStyle,u=e.payloadUniqBy,c=e.payload,l=P(P({position:"absolute",width:o||"auto",height:i||"auto"},this.getDefaultPosition(a)),a);return n.createElement("div",{className:"recharts-legend-wrapper",style:l,ref:function(e){t.wrapperNode=e}},function(t,e){if(n.isValidElement(t))return n.cloneElement(t,e);if("function"==typeof t)return n.createElement(t,e);e.ref;var r=function(t,e){if(null==t)return{};var r,n,o=function(t,e){if(null==t)return{};var r={};for(var n in t)if(Object.prototype.hasOwnProperty.call(t,n)){if(e.indexOf(n)>=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(e,j);return n.createElement(g,r)}(r,P(P({},this.props),{},{payload:(0,w.z)(c,u,C)})))}}],r=[{key:"getWithHeight",value:function(t,e){var r=P(P({},this.defaultProps),t.props).layout;return"vertical"===r&&(0,x.hj)(t.props.height)?{height:t.props.height}:"horizontal"===r?{width:t.props.width||e}:null}}],e&&E(o.prototype,e),r&&E(o,r),Object.defineProperty(o,"prototype",{writable:!1}),o}(n.PureComponent);_(N,"displayName","Legend"),_(N,"defaultProps",{iconSize:14,layout:"horizontal",align:"center",verticalAlign:"bottom"})},47625:function(t,e,r){"use strict";r.d(e,{h:function(){return d}});var n=r(61994),o=r(2265),i=r(37065),a=r.n(i),u=r(16630),c=r(1175),l=r(82944);function s(t){return(s="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function f(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),r.push.apply(r,n)}return r}function p(t){for(var e=1;et.length)&&(e=t.length);for(var r=0,n=Array(e);r0&&(t=a()(t,S,{trailing:!0,leading:!1}));var e=new ResizeObserver(t),r=M.current.getBoundingClientRect();return D(r.width,r.height),e.observe(M.current),function(){e.disconnect()}},[D,S]);var I=(0,o.useMemo)(function(){var t=C.containerWidth,e=C.containerHeight;if(t<0||e<0)return null;(0,c.Z)((0,u.hU)(y)||(0,u.hU)(m),"The width(%s) and height(%s) are both fixed numbers,\n maybe you don't need to use a ResponsiveContainer.",y,m),(0,c.Z)(!i||i>0,"The aspect(%s) must be greater than zero.",i);var r=(0,u.hU)(y)?t:y,n=(0,u.hU)(m)?e:m;i&&i>0&&(r?n=r/i:n&&(r=n*i),w&&n>w&&(n=w)),(0,c.Z)(r>0||n>0,"The width(%s) and height(%s) of chart should be greater than 0,\n please check the style of container, or the props width(%s) and height(%s),\n or add a minWidth(%s) or minHeight(%s) or use aspect(%s) to control the\n height and width.",r,n,y,m,g,x,i);var a=!Array.isArray(O)&&(0,l.Gf)(O.type).endsWith("Chart");return o.Children.map(O,function(t){return o.isValidElement(t)?(0,o.cloneElement)(t,p({width:r,height:n},a?{style:p({height:"100%",width:"100%",maxHeight:n,maxWidth:r},t.props.style)}:{})):t})},[i,O,m,w,x,g,C,y]);return o.createElement("div",{id:P?"".concat(P):void 0,className:(0,n.Z)("recharts-responsive-container",E),style:p(p({},void 0===A?{}:A),{},{width:y,height:m,minWidth:g,minHeight:x,maxHeight:w}),ref:M},I)})},58811:function(t,e,r){"use strict";r.d(e,{x:function(){return B}});var n=r(2265),o=r(77571),i=r.n(o),a=r(61994),u=r(16630),c=r(34067),l=r(82944),s=r(4094);function f(t){return(f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function p(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=r){var n,o,i,a,u=[],c=!0,l=!1;try{if(i=(r=r.call(t)).next,0===e){if(Object(r)!==r)return;c=!1}else for(;!(c=(n=i.call(r)).done)&&(u.push(n.value),u.length!==e);c=!0);}catch(t){l=!0,o=t}finally{try{if(!c&&null!=r.return&&(a=r.return(),Object(a)!==a))return}finally{if(l)throw o}}return u}}(t,e)||function(t,e){if(t){if("string"==typeof t)return h(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);if("Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return h(t,e)}}(t,e)||function(){throw TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function h(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=Array(e);r=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}function M(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=r){var n,o,i,a,u=[],c=!0,l=!1;try{if(i=(r=r.call(t)).next,0===e){if(Object(r)!==r)return;c=!1}else for(;!(c=(n=i.call(r)).done)&&(u.push(n.value),u.length!==e);c=!0);}catch(t){l=!0,o=t}finally{try{if(!c&&null!=r.return&&(a=r.return(),Object(a)!==a))return}finally{if(l)throw o}}return u}}(t,e)||function(t,e){if(t){if("string"==typeof t)return _(t,e);var r=Object.prototype.toString.call(t).slice(8,-1);if("Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return _(t,e)}}(t,e)||function(){throw TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function _(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=Array(e);r0&&void 0!==arguments[0]?arguments[0]:[];return t.reduce(function(t,e){var i=e.word,a=e.width,u=t[t.length-1];return u&&(null==n||o||u.width+a+ra||e.reduce(function(t,e){return t.width>e.width?t:e}).width>Number(n),e]},y=0,v=c.length-1,m=0;y<=v&&m<=c.length-1;){var b=Math.floor((y+v)/2),g=M(d(b-1),2),x=g[0],w=g[1],O=M(d(b),1)[0];if(x||O||(y=b+1),x&&O&&(v=b-1),!x&&O){i=w;break}m++}return i||h},D=function(t){return[{words:i()(t)?[]:t.toString().split(T)}]},I=function(t){var e=t.width,r=t.scaleToFit,n=t.children,o=t.style,i=t.breakAll,a=t.maxLines;if((e||r)&&!c.x.isSsr){var u=C({breakAll:i,children:n,style:o});return u?N({breakAll:i,children:n,maxLines:a,style:o},u.wordsWithComputedWidth,u.spaceWidth,e,r):D(n)}return D(n)},L="#808080",B=function(t){var e,r=t.x,o=void 0===r?0:r,i=t.y,c=void 0===i?0:i,s=t.lineHeight,f=void 0===s?"1em":s,p=t.capHeight,h=void 0===p?"0.71em":p,d=t.scaleToFit,y=void 0!==d&&d,v=t.textAnchor,m=t.verticalAnchor,b=t.fill,g=void 0===b?L:b,x=A(t,P),w=(0,n.useMemo)(function(){return I({breakAll:x.breakAll,children:x.children,maxLines:x.maxLines,scaleToFit:y,style:x.style,width:x.width})},[x.breakAll,x.children,x.maxLines,y,x.style,x.width]),O=x.dx,j=x.dy,M=x.angle,_=x.className,T=x.breakAll,C=A(x,E);if(!(0,u.P2)(o)||!(0,u.P2)(c))return null;var N=o+((0,u.hj)(O)?O:0),D=c+((0,u.hj)(j)?j:0);switch(void 0===m?"end":m){case"start":e=S("calc(".concat(h,")"));break;case"middle":e=S("calc(".concat((w.length-1)/2," * -").concat(f," + (").concat(h," / 2))"));break;default:e=S("calc(".concat(w.length-1," * -").concat(f,")"))}var B=[];if(y){var R=w[0].width,z=x.width;B.push("scale(".concat(((0,u.hj)(z)?z/R:1)/R,")"))}return M&&B.push("rotate(".concat(M,", ").concat(N,", ").concat(D,")")),B.length&&(C.transform=B.join(" ")),n.createElement("text",k({},(0,l.L6)(C,!0),{x:N,y:D,className:(0,a.Z)("recharts-text",_),textAnchor:void 0===v?"start":v,fill:g.includes("url")?L:g}),w.map(function(t,r){var o=t.words.join(T?"":" ");return n.createElement("tspan",{x:N,dy:0===r?e:f,key:"".concat(o,"-").concat(r)},o)}))}},8147:function(t,e,r){"use strict";r.d(e,{u:function(){return $}});var n=r(2265),o=r(34935),i=r.n(o),a=r(77571),u=r.n(a),c=r(61994),l=r(16630);function s(t){return(s="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function f(){return(f=Object.assign?Object.assign.bind():function(t){for(var e=1;et.length)&&(e=t.length);for(var r=0,n=Array(e);rc[n]+s?Math.max(f,c[n]):Math.max(p,c[n])}function O(t){return(O="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function j(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),r.push.apply(r,n)}return r}function S(t){for(var e=1;e1||Math.abs(t.height-this.state.lastBoundingBox.height)>1)&&this.setState({lastBoundingBox:{width:t.width,height:t.height}})}else(-1!==this.state.lastBoundingBox.width||-1!==this.state.lastBoundingBox.height)&&this.setState({lastBoundingBox:{width:-1,height:-1}})}},{key:"componentDidMount",value:function(){document.addEventListener("keydown",this.handleKeyDown),this.updateBBox()}},{key:"componentWillUnmount",value:function(){document.removeEventListener("keydown",this.handleKeyDown)}},{key:"componentDidUpdate",value:function(){var t,e;this.props.active&&this.updateBBox(),this.state.dismissed&&((null===(t=this.props.coordinate)||void 0===t?void 0:t.x)!==this.state.dismissedAtCoordinate.x||(null===(e=this.props.coordinate)||void 0===e?void 0:e.y)!==this.state.dismissedAtCoordinate.y)&&(this.state.dismissed=!1)}},{key:"render",value:function(){var t,e,r,o,i,a,u,s,f,p,h,d,y,v,m,O,j,P,E,k=this,A=this.props,M=A.active,_=A.allowEscapeViewBox,T=A.animationDuration,C=A.animationEasing,N=A.children,D=A.coordinate,I=A.hasPayload,L=A.isAnimationActive,B=A.offset,R=A.position,z=A.reverseDirection,U=A.useTranslate3d,F=A.viewBox,$=A.wrapperStyle,q=(d=(t={allowEscapeViewBox:_,coordinate:D,offsetTopLeft:B,position:R,reverseDirection:z,tooltipBox:this.state.lastBoundingBox,useTranslate3d:U,viewBox:F}).allowEscapeViewBox,y=t.coordinate,v=t.offsetTopLeft,m=t.position,O=t.reverseDirection,j=t.tooltipBox,P=t.useTranslate3d,E=t.viewBox,j.height>0&&j.width>0&&y?(r=(e={translateX:p=w({allowEscapeViewBox:d,coordinate:y,key:"x",offsetTopLeft:v,position:m,reverseDirection:O,tooltipDimension:j.width,viewBox:E,viewBoxDimension:E.width}),translateY:h=w({allowEscapeViewBox:d,coordinate:y,key:"y",offsetTopLeft:v,position:m,reverseDirection:O,tooltipDimension:j.height,viewBox:E,viewBoxDimension:E.height}),useTranslate3d:P}).translateX,o=e.translateY,f={transform:e.useTranslate3d?"translate3d(".concat(r,"px, ").concat(o,"px, 0)"):"translate(".concat(r,"px, ").concat(o,"px)")}):f=x,{cssProperties:f,cssClasses:(a=(i={translateX:p,translateY:h,coordinate:y}).coordinate,u=i.translateX,s=i.translateY,(0,c.Z)(g,b(b(b(b({},"".concat(g,"-right"),(0,l.hj)(u)&&a&&(0,l.hj)(a.x)&&u>=a.x),"".concat(g,"-left"),(0,l.hj)(u)&&a&&(0,l.hj)(a.x)&&u=a.y),"".concat(g,"-top"),(0,l.hj)(s)&&a&&(0,l.hj)(a.y)&&s0;return n.createElement(_,{allowEscapeViewBox:i,animationDuration:a,animationEasing:u,isAnimationActive:f,active:o,coordinate:l,hasPayload:O,offset:p,position:y,reverseDirection:m,useTranslate3d:b,viewBox:g,wrapperStyle:x},(t=I(I({},this.props),{},{payload:w}),n.isValidElement(c)?n.cloneElement(c,t):"function"==typeof c?n.createElement(c,t):n.createElement(v,t)))}}],function(t,e){for(var r=0;r=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(t,a),s=(0,o.Z)("recharts-layer",c);return n.createElement("g",u({className:s},(0,i.L6)(l,!0),{ref:e}),r)})},48777:function(t,e,r){"use strict";r.d(e,{T:function(){return c}});var n=r(2265),o=r(61994),i=r(82944),a=["children","width","height","viewBox","className","style","title","desc"];function u(){return(u=Object.assign?Object.assign.bind():function(t){for(var e=1;e=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(t,a),y=l||{width:r,height:c,x:0,y:0},v=(0,o.Z)("recharts-surface",s);return n.createElement("svg",u({},(0,i.L6)(d,!0,"svg"),{className:v,width:r,height:c,style:f,viewBox:"".concat(y.x," ").concat(y.y," ").concat(y.width," ").concat(y.height)}),n.createElement("title",null,p),n.createElement("desc",null,h),e)}},25739:function(t,e,r){"use strict";r.d(e,{br:function(){return g},CW:function(){return O},Mw:function(){return A},zn:function(){return k},sp:function(){return x},qD:function(){return E},d2:function(){return P},bH:function(){return w},Ud:function(){return S},Nf:function(){return j}});var n=r(2265),o=r(69398),i=r(84173),a=r.n(i),u=r(32242),c=r.n(u),l=r(50967),s=r.n(l)()(function(t){return{x:t.left,y:t.top,width:t.width,height:t.height}},function(t){return["l",t.left,"t",t.top,"w",t.width,"h",t.height].join("")}),f=r(16630),p=(0,n.createContext)(void 0),h=(0,n.createContext)(void 0),d=(0,n.createContext)(void 0),y=(0,n.createContext)({}),v=(0,n.createContext)(void 0),m=(0,n.createContext)(0),b=(0,n.createContext)(0),g=function(t){var e=t.state,r=e.xAxisMap,o=e.yAxisMap,i=e.offset,a=t.clipPathId,u=t.children,c=t.width,l=t.height,f=s(i);return n.createElement(p.Provider,{value:r},n.createElement(h.Provider,{value:o},n.createElement(y.Provider,{value:i},n.createElement(d.Provider,{value:f},n.createElement(v.Provider,{value:a},n.createElement(m.Provider,{value:l},n.createElement(b.Provider,{value:c},u)))))))},x=function(){return(0,n.useContext)(v)},w=function(t){var e=(0,n.useContext)(p);null!=e||(0,o.Z)(!1);var r=e[t];return null!=r||(0,o.Z)(!1),r},O=function(){var t=(0,n.useContext)(p);return(0,f.Kt)(t)},j=function(){var t=(0,n.useContext)(h);return a()(t,function(t){return c()(t.domain,Number.isFinite)})||(0,f.Kt)(t)},S=function(t){var e=(0,n.useContext)(h);null!=e||(0,o.Z)(!1);var r=e[t];return null!=r||(0,o.Z)(!1),r},P=function(){return(0,n.useContext)(d)},E=function(){return(0,n.useContext)(y)},k=function(){return(0,n.useContext)(b)},A=function(){return(0,n.useContext)(m)}},57165:function(t,e,r){"use strict";r.d(e,{H:function(){return H}});var n=r(2265);function o(){}function i(t,e,r){t._context.bezierCurveTo((2*t._x0+t._x1)/3,(2*t._y0+t._y1)/3,(t._x0+2*t._x1)/3,(t._y0+2*t._y1)/3,(t._x0+4*t._x1+e)/6,(t._y0+4*t._y1+r)/6)}function a(t){this._context=t}function u(t){this._context=t}function c(t){this._context=t}a.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){switch(this._point){case 3:i(this,this._x1,this._y1);case 2:this._context.lineTo(this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;break;case 2:this._point=3,this._context.lineTo((5*this._x0+this._x1)/6,(5*this._y0+this._y1)/6);default:i(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}},u.prototype={areaStart:o,areaEnd:o,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._y0=this._y1=this._y2=this._y3=this._y4=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x2,this._y2),this._context.closePath();break;case 2:this._context.moveTo((this._x2+2*this._x3)/3,(this._y2+2*this._y3)/3),this._context.lineTo((this._x3+2*this._x2)/3,(this._y3+2*this._y2)/3),this._context.closePath();break;case 3:this.point(this._x2,this._y2),this.point(this._x3,this._y3),this.point(this._x4,this._y4)}},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._x2=t,this._y2=e;break;case 1:this._point=2,this._x3=t,this._y3=e;break;case 2:this._point=3,this._x4=t,this._y4=e,this._context.moveTo((this._x0+4*this._x1+t)/6,(this._y0+4*this._y1+e)/6);break;default:i(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}},c.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3;var r=(this._x0+4*this._x1+t)/6,n=(this._y0+4*this._y1+e)/6;this._line?this._context.lineTo(r,n):this._context.moveTo(r,n);break;case 3:this._point=4;default:i(this,t,e)}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=e}};class l{constructor(t,e){this._context=t,this._x=e}areaStart(){this._line=0}areaEnd(){this._line=NaN}lineStart(){this._point=0}lineEnd(){(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line}point(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;default:this._x?this._context.bezierCurveTo(this._x0=(this._x0+t)/2,this._y0,this._x0,e,t,e):this._context.bezierCurveTo(this._x0,this._y0=(this._y0+e)/2,t,this._y0,t,e)}this._x0=t,this._y0=e}}function s(t){this._context=t}function f(t){this._context=t}function p(t){return new f(t)}function h(t,e,r){var n=t._x1-t._x0,o=e-t._x1,i=(t._y1-t._y0)/(n||o<0&&-0),a=(r-t._y1)/(o||n<0&&-0);return((i<0?-1:1)+(a<0?-1:1))*Math.min(Math.abs(i),Math.abs(a),.5*Math.abs((i*o+a*n)/(n+o)))||0}function d(t,e){var r=t._x1-t._x0;return r?(3*(t._y1-t._y0)/r-e)/2:e}function y(t,e,r){var n=t._x0,o=t._y0,i=t._x1,a=t._y1,u=(i-n)/3;t._context.bezierCurveTo(n+u,o+u*e,i-u,a-u*r,i,a)}function v(t){this._context=t}function m(t){this._context=new b(t)}function b(t){this._context=t}function g(t){this._context=t}function x(t){var e,r,n=t.length-1,o=Array(n),i=Array(n),a=Array(n);for(o[0]=0,i[0]=2,a[0]=t[0]+2*t[1],e=1;e=0;--e)o[e]=(a[e]-o[e+1])/i[e];for(e=0,i[n-1]=(t[n]+o[n-1])/2;e=0&&(this._t=1-this._t,this._line=1-this._line)},point:function(t,e){switch(t=+t,e=+e,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,e):this._context.moveTo(t,e);break;case 1:this._point=2;default:if(this._t<=0)this._context.lineTo(this._x,e),this._context.lineTo(t,e);else{var r=this._x*(1-this._t)+t*this._t;this._context.lineTo(r,this._y),this._context.lineTo(r,e)}}this._x=t,this._y=e}};var O=r(22516),j=r(76115),S=r(67790);function P(t){return t[0]}function E(t){return t[1]}function k(t,e){var r=(0,j.Z)(!0),n=null,o=p,i=null,a=(0,S.d)(u);function u(u){var c,l,s,f=(u=(0,O.Z)(u)).length,p=!1;for(null==n&&(i=o(s=a())),c=0;c<=f;++c)!(c=f;--p)u.point(m[p],b[p]);u.lineEnd(),u.areaEnd()}}v&&(m[s]=+t(h,s,l),b[s]=+e(h,s,l),u.point(n?+n(h,s,l):m[s],r?+r(h,s,l):b[s]))}if(d)return u=null,d+""||null}function s(){return k().defined(o).curve(a).context(i)}return t="function"==typeof t?t:void 0===t?P:(0,j.Z)(+t),e="function"==typeof e?e:void 0===e?(0,j.Z)(0):(0,j.Z)(+e),r="function"==typeof r?r:void 0===r?E:(0,j.Z)(+r),l.x=function(e){return arguments.length?(t="function"==typeof e?e:(0,j.Z)(+e),n=null,l):t},l.x0=function(e){return arguments.length?(t="function"==typeof e?e:(0,j.Z)(+e),l):t},l.x1=function(t){return arguments.length?(n=null==t?null:"function"==typeof t?t:(0,j.Z)(+t),l):n},l.y=function(t){return arguments.length?(e="function"==typeof t?t:(0,j.Z)(+t),r=null,l):e},l.y0=function(t){return arguments.length?(e="function"==typeof t?t:(0,j.Z)(+t),l):e},l.y1=function(t){return arguments.length?(r=null==t?null:"function"==typeof t?t:(0,j.Z)(+t),l):r},l.lineX0=l.lineY0=function(){return s().x(t).y(e)},l.lineY1=function(){return s().x(t).y(r)},l.lineX1=function(){return s().x(n).y(e)},l.defined=function(t){return arguments.length?(o="function"==typeof t?t:(0,j.Z)(!!t),l):o},l.curve=function(t){return arguments.length?(a=t,null!=i&&(u=a(i)),l):a},l.context=function(t){return arguments.length?(null==t?i=u=null:u=a(i=t),l):i},l}var M=r(75551),_=r.n(M),T=r(86757),C=r.n(T),N=r(61994),D=r(41637),I=r(82944),L=r(16630);function B(t){return(B="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function R(){return(R=Object.assign?Object.assign.bind():function(t){for(var e=1;et.length)&&(e=t.length);for(var r=0,n=Array(e);r=0?1:-1,c=r>=0?1:-1,l=n>=0&&r>=0||n<0&&r<0?1:0;if(a>0&&o instanceof Array){for(var s=[0,0,0,0],f=0;f<4;f++)s[f]=o[f]>a?a:o[f];i="M".concat(t,",").concat(e+u*s[0]),s[0]>0&&(i+="A ".concat(s[0],",").concat(s[0],",0,0,").concat(l,",").concat(t+c*s[0],",").concat(e)),i+="L ".concat(t+r-c*s[1],",").concat(e),s[1]>0&&(i+="A ".concat(s[1],",").concat(s[1],",0,0,").concat(l,",\n ").concat(t+r,",").concat(e+u*s[1])),i+="L ".concat(t+r,",").concat(e+n-u*s[2]),s[2]>0&&(i+="A ".concat(s[2],",").concat(s[2],",0,0,").concat(l,",\n ").concat(t+r-c*s[2],",").concat(e+n)),i+="L ".concat(t+c*s[3],",").concat(e+n),s[3]>0&&(i+="A ".concat(s[3],",").concat(s[3],",0,0,").concat(l,",\n ").concat(t,",").concat(e+n-u*s[3])),i+="Z"}else if(a>0&&o===+o&&o>0){var p=Math.min(a,o);i="M ".concat(t,",").concat(e+u*p,"\n A ").concat(p,",").concat(p,",0,0,").concat(l,",").concat(t+c*p,",").concat(e,"\n L ").concat(t+r-c*p,",").concat(e,"\n A ").concat(p,",").concat(p,",0,0,").concat(l,",").concat(t+r,",").concat(e+u*p,"\n L ").concat(t+r,",").concat(e+n-u*p,"\n A ").concat(p,",").concat(p,",0,0,").concat(l,",").concat(t+r-c*p,",").concat(e+n,"\n L ").concat(t+c*p,",").concat(e+n,"\n A ").concat(p,",").concat(p,",0,0,").concat(l,",").concat(t,",").concat(e+n-u*p," Z")}else i="M ".concat(t,",").concat(e," h ").concat(r," v ").concat(n," h ").concat(-r," Z");return i},h=function(t,e){if(!t||!e)return!1;var r=t.x,n=t.y,o=e.x,i=e.y,a=e.width,u=e.height;return!!(Math.abs(a)>0&&Math.abs(u)>0)&&r>=Math.min(o,o+a)&&r<=Math.max(o,o+a)&&n>=Math.min(i,i+u)&&n<=Math.max(i,i+u)},d={x:0,y:0,width:0,height:0,radius:0,isAnimationActive:!1,isUpdateAnimationActive:!1,animationBegin:0,animationDuration:1500,animationEasing:"ease"},y=function(t){var e,r=f(f({},d),t),u=(0,n.useRef)(),s=function(t){if(Array.isArray(t))return t}(e=(0,n.useState)(-1))||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=r){var n,o,i,a,u=[],c=!0,l=!1;try{for(i=(r=r.call(t)).next;!(c=(n=i.call(r)).done)&&(u.push(n.value),2!==u.length);c=!0);}catch(t){l=!0,o=t}finally{try{if(!c&&null!=r.return&&(a=r.return(),Object(a)!==a))return}finally{if(l)throw o}}return u}}(e,2)||function(t,e){if(t){if("string"==typeof t)return l(t,2);var r=Object.prototype.toString.call(t).slice(8,-1);if("Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return l(t,2)}}(e,2)||function(){throw TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}(),h=s[0],y=s[1];(0,n.useEffect)(function(){if(u.current&&u.current.getTotalLength)try{var t=u.current.getTotalLength();t&&y(t)}catch(t){}},[]);var v=r.x,m=r.y,b=r.width,g=r.height,x=r.radius,w=r.className,O=r.animationEasing,j=r.animationDuration,S=r.animationBegin,P=r.isAnimationActive,E=r.isUpdateAnimationActive;if(v!==+v||m!==+m||b!==+b||g!==+g||0===b||0===g)return null;var k=(0,o.Z)("recharts-rectangle",w);return E?n.createElement(i.ZP,{canBegin:h>0,from:{width:b,height:g,x:v,y:m},to:{width:b,height:g,x:v,y:m},duration:j,animationEasing:O,isActive:E},function(t){var e=t.width,o=t.height,l=t.x,s=t.y;return n.createElement(i.ZP,{canBegin:h>0,from:"0px ".concat(-1===h?1:h,"px"),to:"".concat(h,"px 0px"),attributeName:"strokeDasharray",begin:S,duration:j,isActive:P,easing:O},n.createElement("path",c({},(0,a.L6)(r,!0),{className:k,d:p(l,s,e,o,x),ref:u})))}):n.createElement("path",c({},(0,a.L6)(r,!0),{className:k,d:p(v,m,b,g,x)}))}},60474:function(t,e,r){"use strict";r.d(e,{L:function(){return v}});var n=r(2265),o=r(61994),i=r(82944),a=r(39206),u=r(16630);function c(t){return(c="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function l(){return(l=Object.assign?Object.assign.bind():function(t){for(var e=1;e180),",").concat(+(c>s),",\n ").concat(p.x,",").concat(p.y,"\n ");if(o>0){var d=(0,a.op)(r,n,o,c),y=(0,a.op)(r,n,o,s);h+="L ".concat(y.x,",").concat(y.y,"\n A ").concat(o,",").concat(o,",0,\n ").concat(+(Math.abs(l)>180),",").concat(+(c<=s),",\n ").concat(d.x,",").concat(d.y," Z")}else h+="L ".concat(r,",").concat(n," Z");return h},d=function(t){var e=t.cx,r=t.cy,n=t.innerRadius,o=t.outerRadius,i=t.cornerRadius,a=t.forceCornerRadius,c=t.cornerIsExternal,l=t.startAngle,s=t.endAngle,f=(0,u.uY)(s-l),d=p({cx:e,cy:r,radius:o,angle:l,sign:f,cornerRadius:i,cornerIsExternal:c}),y=d.circleTangency,v=d.lineTangency,m=d.theta,b=p({cx:e,cy:r,radius:o,angle:s,sign:-f,cornerRadius:i,cornerIsExternal:c}),g=b.circleTangency,x=b.lineTangency,w=b.theta,O=c?Math.abs(l-s):Math.abs(l-s)-m-w;if(O<0)return a?"M ".concat(v.x,",").concat(v.y,"\n a").concat(i,",").concat(i,",0,0,1,").concat(2*i,",0\n a").concat(i,",").concat(i,",0,0,1,").concat(-(2*i),",0\n "):h({cx:e,cy:r,innerRadius:n,outerRadius:o,startAngle:l,endAngle:s});var j="M ".concat(v.x,",").concat(v.y,"\n A").concat(i,",").concat(i,",0,0,").concat(+(f<0),",").concat(y.x,",").concat(y.y,"\n A").concat(o,",").concat(o,",0,").concat(+(O>180),",").concat(+(f<0),",").concat(g.x,",").concat(g.y,"\n A").concat(i,",").concat(i,",0,0,").concat(+(f<0),",").concat(x.x,",").concat(x.y,"\n ");if(n>0){var S=p({cx:e,cy:r,radius:n,angle:l,sign:f,isExternal:!0,cornerRadius:i,cornerIsExternal:c}),P=S.circleTangency,E=S.lineTangency,k=S.theta,A=p({cx:e,cy:r,radius:n,angle:s,sign:-f,isExternal:!0,cornerRadius:i,cornerIsExternal:c}),M=A.circleTangency,_=A.lineTangency,T=A.theta,C=c?Math.abs(l-s):Math.abs(l-s)-k-T;if(C<0&&0===i)return"".concat(j,"L").concat(e,",").concat(r,"Z");j+="L".concat(_.x,",").concat(_.y,"\n A").concat(i,",").concat(i,",0,0,").concat(+(f<0),",").concat(M.x,",").concat(M.y,"\n A").concat(n,",").concat(n,",0,").concat(+(C>180),",").concat(+(f>0),",").concat(P.x,",").concat(P.y,"\n A").concat(i,",").concat(i,",0,0,").concat(+(f<0),",").concat(E.x,",").concat(E.y,"Z")}else j+="L".concat(e,",").concat(r,"Z");return j},y={cx:0,cy:0,innerRadius:0,outerRadius:0,startAngle:0,endAngle:0,cornerRadius:0,forceCornerRadius:!1,cornerIsExternal:!1},v=function(t){var e,r=f(f({},y),t),a=r.cx,c=r.cy,s=r.innerRadius,p=r.outerRadius,v=r.cornerRadius,m=r.forceCornerRadius,b=r.cornerIsExternal,g=r.startAngle,x=r.endAngle,w=r.className;if(p0&&360>Math.abs(g-x)?d({cx:a,cy:c,innerRadius:s,outerRadius:p,cornerRadius:Math.min(S,j/2),forceCornerRadius:m,cornerIsExternal:b,startAngle:g,endAngle:x}):h({cx:a,cy:c,innerRadius:s,outerRadius:p,startAngle:g,endAngle:x}),n.createElement("path",l({},(0,i.L6)(r,!0),{className:O,d:e,role:"img"}))}},14870:function(t,e,r){"use strict";r.d(e,{v:function(){return N}});var n=r(2265),o=r(75551),i=r.n(o);let a=Math.cos,u=Math.sin,c=Math.sqrt,l=Math.PI,s=2*l;var f={draw(t,e){let r=c(e/l);t.moveTo(r,0),t.arc(0,0,r,0,s)}};let p=c(1/3),h=2*p,d=u(l/10)/u(7*l/10),y=u(s/10)*d,v=-a(s/10)*d,m=c(3),b=c(3)/2,g=1/c(12),x=(g/2+1)*3;var w=r(76115),O=r(67790);c(3),c(3);var j=r(61994),S=r(82944);function P(t){return(P="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var E=["type","size","sizeType"];function k(){return(k=Object.assign?Object.assign.bind():function(t){for(var e=1;e=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(t,E)),{},{type:o,size:u,sizeType:l}),p=s.className,h=s.cx,d=s.cy,y=(0,S.L6)(s,!0);return h===+h&&d===+d&&u===+u?n.createElement("path",k({},y,{className:(0,j.Z)("recharts-symbols",p),transform:"translate(".concat(h,", ").concat(d,")"),d:(e=_["symbol".concat(i()(o))]||f,(function(t,e){let r=null,n=(0,O.d)(o);function o(){let o;if(r||(r=o=n()),t.apply(this,arguments).draw(r,+e.apply(this,arguments)),o)return r=null,o+""||null}return t="function"==typeof t?t:(0,w.Z)(t||f),e="function"==typeof e?e:(0,w.Z)(void 0===e?64:+e),o.type=function(e){return arguments.length?(t="function"==typeof e?e:(0,w.Z)(e),o):t},o.size=function(t){return arguments.length?(e="function"==typeof t?t:(0,w.Z)(+t),o):e},o.context=function(t){return arguments.length?(r=null==t?null:t,o):r},o})().type(e).size(C(u,l,o))())})):null};N.registerSymbol=function(t,e){_["symbol".concat(i()(t))]=e}},11638:function(t,e,r){"use strict";r.d(e,{bn:function(){return C},a3:function(){return z},lT:function(){return N},V$:function(){return D},w7:function(){return I}});var n=r(2265),o=r(86757),i=r.n(o),a=r(90231),u=r.n(a),c=r(24342),l=r.n(c),s=r(21652),f=r.n(s),p=r(73649),h=r(61994),d=r(84735),y=r(82944);function v(t){return(v="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function m(){return(m=Object.assign?Object.assign.bind():function(t){for(var e=1;et.length)&&(e=t.length);for(var r=0,n=Array(e);r0,from:{upperWidth:0,lowerWidth:0,height:p,x:c,y:l},to:{upperWidth:s,lowerWidth:f,height:p,x:c,y:l},duration:j,animationEasing:g,isActive:P},function(t){var e=t.upperWidth,i=t.lowerWidth,u=t.height,c=t.x,l=t.y;return n.createElement(d.ZP,{canBegin:a>0,from:"0px ".concat(-1===a?1:a,"px"),to:"".concat(a,"px 0px"),attributeName:"strokeDasharray",begin:S,duration:j,easing:g},n.createElement("path",m({},(0,y.L6)(r,!0),{className:E,d:w(c,l,e,i,u),ref:o})))}):n.createElement("g",null,n.createElement("path",m({},(0,y.L6)(r,!0),{className:E,d:w(c,l,s,f,p)})))},S=r(60474),P=r(9841),E=r(14870),k=["option","shapeType","propTransformer","activeClassName","isActive"];function A(t){return(A="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function M(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),r.push.apply(r,n)}return r}function _(t){for(var e=1;e=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}(t,k);if((0,n.isValidElement)(r))e=(0,n.cloneElement)(r,_(_({},f),(0,n.isValidElement)(r)?r.props:r));else if(i()(r))e=r(f);else if(u()(r)&&!l()(r)){var p=(void 0===a?function(t,e){return _(_({},e),t)}:a)(r,f);e=n.createElement(T,{shapeType:o,elementProps:p})}else e=n.createElement(T,{shapeType:o,elementProps:f});return s?n.createElement(P.m,{className:void 0===c?"recharts-active-shape":c},e):e}function N(t,e){return null!=e&&"trapezoids"in t.props}function D(t,e){return null!=e&&"sectors"in t.props}function I(t,e){return null!=e&&"points"in t.props}function L(t,e){var r,n,o=t.x===(null==e||null===(r=e.labelViewBox)||void 0===r?void 0:r.x)||t.x===e.x,i=t.y===(null==e||null===(n=e.labelViewBox)||void 0===n?void 0:n.y)||t.y===e.y;return o&&i}function B(t,e){var r=t.endAngle===e.endAngle,n=t.startAngle===e.startAngle;return r&&n}function R(t,e){var r=t.x===e.x,n=t.y===e.y,o=t.z===e.z;return r&&n&&o}function z(t){var e,r,n,o=t.activeTooltipItem,i=t.graphicalItem,a=t.itemData,u=(N(i,o)?e="trapezoids":D(i,o)?e="sectors":I(i,o)&&(e="points"),e),c=N(i,o)?null===(r=o.tooltipPayload)||void 0===r||null===(r=r[0])||void 0===r||null===(r=r.payload)||void 0===r?void 0:r.payload:D(i,o)?null===(n=o.tooltipPayload)||void 0===n||null===(n=n[0])||void 0===n||null===(n=n.payload)||void 0===n?void 0:n.payload:I(i,o)?o.payload:{},l=a.filter(function(t,e){var r=f()(c,t),n=i.props[u].filter(function(t){var e;return(N(i,o)?e=L:D(i,o)?e=B:I(i,o)&&(e=R),e)(t,o)}),a=i.props[u].indexOf(n[n.length-1]);return r&&e===a});return a.indexOf(l[l.length-1])}},25311:function(t,e,r){"use strict";r.d(e,{Ky:function(){return w},O1:function(){return b},_b:function(){return g},t9:function(){return m},xE:function(){return O}});var n=r(41443),o=r.n(n),i=r(32242),a=r.n(i),u=r(85355),c=r(82944),l=r(16630),s=r(31699);function f(t){return(f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function p(t,e){for(var r=0;r0&&(A=Math.min((t||0)-(M[e-1]||0),A))}),Number.isFinite(A)){var _=A/k,T="vertical"===g.layout?r.height:r.width;if("gap"===g.padding&&(c=_*T/2),"no-gap"===g.padding){var C=(0,l.h1)(t.barCategoryGap,_*T),N=_*T/2;c=N-C-(N-C)/T*C}}}s="xAxis"===n?[r.left+(j.left||0)+(c||0),r.left+r.width-(j.right||0)-(c||0)]:"yAxis"===n?"horizontal"===f?[r.top+r.height-(j.bottom||0),r.top+(j.top||0)]:[r.top+(j.top||0)+(c||0),r.top+r.height-(j.bottom||0)-(c||0)]:g.range,P&&(s=[s[1],s[0]]);var D=(0,u.Hq)(g,o,m),I=D.scale,L=D.realScaleType;I.domain(w).range(s),(0,u.zF)(I);var B=(0,u.g$)(I,d(d({},g),{},{realScaleType:L}));"xAxis"===n?(b="top"===x&&!S||"bottom"===x&&S,p=r.left,h=v[E]-b*g.height):"yAxis"===n&&(b="left"===x&&!S||"right"===x&&S,p=v[E]-b*g.width,h=r.top);var R=d(d(d({},g),B),{},{realScaleType:L,x:p,y:h,scale:I,width:"xAxis"===n?r.width:g.width,height:"yAxis"===n?r.height:g.height});return R.bandSize=(0,u.zT)(R,B),g.hide||"xAxis"!==n?g.hide||(v[E]+=(b?-1:1)*R.width):v[E]+=(b?-1:1)*R.height,d(d({},i),{},y({},a,R))},{})},b=function(t,e){var r=t.x,n=t.y,o=e.x,i=e.y;return{x:Math.min(r,o),y:Math.min(n,i),width:Math.abs(o-r),height:Math.abs(i-n)}},g=function(t){return b({x:t.x1,y:t.y1},{x:t.x2,y:t.y2})},x=function(){var t,e;function r(t){!function(t,e){if(!(t instanceof e))throw TypeError("Cannot call a class as a function")}(this,r),this.scale=t}return t=[{key:"domain",get:function(){return this.scale.domain}},{key:"range",get:function(){return this.scale.range}},{key:"rangeMin",get:function(){return this.range()[0]}},{key:"rangeMax",get:function(){return this.range()[1]}},{key:"bandwidth",get:function(){return this.scale.bandwidth}},{key:"apply",value:function(t){var e=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=e.bandAware,n=e.position;if(void 0!==t){if(n)switch(n){case"start":default:return this.scale(t);case"middle":var o=this.bandwidth?this.bandwidth()/2:0;return this.scale(t)+o;case"end":var i=this.bandwidth?this.bandwidth():0;return this.scale(t)+i}if(r){var a=this.bandwidth?this.bandwidth()/2:0;return this.scale(t)+a}return this.scale(t)}}},{key:"isInRange",value:function(t){var e=this.range(),r=e[0],n=e[e.length-1];return r<=n?t>=r&&t<=n:t>=n&&t<=r}}],e=[{key:"create",value:function(t){return new r(t)}}],t&&p(r.prototype,t),e&&p(r,e),Object.defineProperty(r,"prototype",{writable:!1}),r}();y(x,"EPS",1e-4);var w=function(t){var e=Object.keys(t).reduce(function(e,r){return d(d({},e),{},y({},r,x.create(t[r])))},{});return d(d({},e),{},{apply:function(t){var r=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=r.bandAware,i=r.position;return o()(t,function(t,r){return e[r].apply(t,{bandAware:n,position:i})})},isInRange:function(t){return a()(t,function(t,r){return e[r].isInRange(t)})}})},O=function(t){var e=t.width,r=t.height,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,o=(n%180+180)%180*Math.PI/180,i=Math.atan(r/e);return Math.abs(o>i&&otx(e,t()).base(e.base()),tj.o.apply(e,arguments),e}},scaleOrdinal:function(){return tX.Z},scalePoint:function(){return f.x},scalePow:function(){return tJ},scaleQuantile:function(){return function t(){var e,r=[],n=[],o=[];function i(){var t=0,e=Math.max(1,n.length);for(o=Array(e-1);++t=1)return+r(t[n-1],n-1,t);var n,o=(n-1)*e,i=Math.floor(o),a=+r(t[i],i,t);return a+(+r(t[i+1],i+1,t)-a)*(o-i)}}(r,t/e);return a}function a(t){return null==t||isNaN(t=+t)?e:n[P(o,t)]}return a.invertExtent=function(t){var e=n.indexOf(t);return e<0?[NaN,NaN]:[e>0?o[e-1]:r[0],e=o?[i[o-1],n]:[i[e-1],i[e]]},u.unknown=function(t){return arguments.length&&(e=t),u},u.thresholds=function(){return i.slice()},u.copy=function(){return t().domain([r,n]).range(a).unknown(e)},tj.o.apply(tI(u),arguments)}},scaleRadial:function(){return function t(){var e,r=tO(),n=[0,1],o=!1;function i(t){var n,i=Math.sign(n=r(t))*Math.sqrt(Math.abs(n));return isNaN(i)?e:o?Math.round(i):i}return i.invert=function(t){return r.invert(t1(t))},i.domain=function(t){return arguments.length?(r.domain(t),i):r.domain()},i.range=function(t){return arguments.length?(r.range((n=Array.from(t,td)).map(t1)),i):n.slice()},i.rangeRound=function(t){return i.range(t).round(!0)},i.round=function(t){return arguments.length?(o=!!t,i):o},i.clamp=function(t){return arguments.length?(r.clamp(t),i):r.clamp()},i.unknown=function(t){return arguments.length?(e=t,i):e},i.copy=function(){return t(r.domain(),n).round(o).clamp(r.clamp()).unknown(e)},tj.o.apply(i,arguments),tI(i)}},scaleSequential:function(){return function t(){var e=tI(rX()(tv));return e.copy=function(){return rG(e,t())},tj.O.apply(e,arguments)}},scaleSequentialLog:function(){return function t(){var e=tZ(rX()).domain([1,10]);return e.copy=function(){return rG(e,t()).base(e.base())},tj.O.apply(e,arguments)}},scaleSequentialPow:function(){return rV},scaleSequentialQuantile:function(){return function t(){var e=[],r=tv;function n(t){if(null!=t&&!isNaN(t=+t))return r((P(e,t,1)-1)/(e.length-1))}return n.domain=function(t){if(!arguments.length)return e.slice();for(let r of(e=[],t))null==r||isNaN(r=+r)||e.push(r);return e.sort(g),n},n.interpolator=function(t){return arguments.length?(r=t,n):r},n.range=function(){return e.map((t,n)=>r(n/(e.length-1)))},n.quantiles=function(t){return Array.from({length:t+1},(r,n)=>(function(t,e,r){if(!(!(n=(t=Float64Array.from(function*(t,e){if(void 0===e)for(let e of t)null!=e&&(e=+e)>=e&&(yield e);else{let r=-1;for(let n of t)null!=(n=e(n,++r,t))&&(n=+n)>=n&&(yield n)}}(t,void 0))).length)||isNaN(e=+e))){if(e<=0||n<2)return t5(t);if(e>=1)return t2(t);var n,o=(n-1)*e,i=Math.floor(o),a=t2((function t(e,r,n=0,o=1/0,i){if(r=Math.floor(r),n=Math.floor(Math.max(0,n)),o=Math.floor(Math.min(e.length-1,o)),!(n<=r&&r<=o))return e;for(i=void 0===i?t6:function(t=g){if(t===g)return t6;if("function"!=typeof t)throw TypeError("compare is not a function");return(e,r)=>{let n=t(e,r);return n||0===n?n:(0===t(r,r))-(0===t(e,e))}}(i);o>n;){if(o-n>600){let a=o-n+1,u=r-n+1,c=Math.log(a),l=.5*Math.exp(2*c/3),s=.5*Math.sqrt(c*l*(a-l)/a)*(u-a/2<0?-1:1),f=Math.max(n,Math.floor(r-u*l/a+s)),p=Math.min(o,Math.floor(r+(a-u)*l/a+s));t(e,r,f,p,i)}let a=e[r],u=n,c=o;for(t3(e,n,r),i(e[o],a)>0&&t3(e,n,o);ui(e[u],a);)++u;for(;i(e[c],a)>0;)--c}0===i(e[n],a)?t3(e,n,c):t3(e,++c,o),c<=r&&(n=c+1),r<=c&&(o=c-1)}return e})(t,i).subarray(0,i+1));return a+(t5(t.subarray(i+1))-a)*(o-i)}})(e,n/t))},n.copy=function(){return t(r).domain(e)},tj.O.apply(n,arguments)}},scaleSequentialSqrt:function(){return rK},scaleSequentialSymlog:function(){return function t(){var e=tH(rX());return e.copy=function(){return rG(e,t()).constant(e.constant())},tj.O.apply(e,arguments)}},scaleSqrt:function(){return t0},scaleSymlog:function(){return function t(){var e=tH(tw());return e.copy=function(){return tx(e,t()).constant(e.constant())},tj.o.apply(e,arguments)}},scaleThreshold:function(){return function t(){var e,r=[.5],n=[0,1],o=1;function i(t){return null!=t&&t<=t?n[P(r,t,0,o)]:e}return i.domain=function(t){return arguments.length?(o=Math.min((r=Array.from(t)).length,n.length-1),i):r.slice()},i.range=function(t){return arguments.length?(n=Array.from(t),o=Math.min(r.length,n.length-1),i):n.slice()},i.invertExtent=function(t){var e=n.indexOf(t);return[r[e-1],r[e]]},i.unknown=function(t){return arguments.length?(e=t,i):e},i.copy=function(){return t().domain(r).range(n).unknown(e)},tj.o.apply(i,arguments)}},scaleTime:function(){return rY},scaleUtc:function(){return rH},tickFormat:function(){return tD}});var f=r(55284);let p=Math.sqrt(50),h=Math.sqrt(10),d=Math.sqrt(2);function y(t,e,r){let n,o,i;let a=(e-t)/Math.max(0,r),u=Math.floor(Math.log10(a)),c=a/Math.pow(10,u),l=c>=p?10:c>=h?5:c>=d?2:1;return(u<0?(n=Math.round(t*(i=Math.pow(10,-u)/l)),o=Math.round(e*i),n/ie&&--o,i=-i):(n=Math.round(t/(i=Math.pow(10,u)*l)),o=Math.round(e/i),n*ie&&--o),o0))return[];if(t===e)return[t];let n=e=o))return[];let u=i-o+1,c=Array(u);if(n){if(a<0)for(let t=0;te?1:t>=e?0:NaN}function x(t,e){return null==t||null==e?NaN:et?1:e>=t?0:NaN}function w(t){let e,r,n;function o(t,n,o=0,i=t.length){if(o>>1;0>r(t[e],n)?o=e+1:i=e}while(og(t(e),r),n=(e,r)=>t(e)-r):(e=t===g||t===x?t:O,r=t,n=t),{left:o,center:function(t,e,r=0,i=t.length){let a=o(t,e,r,i-1);return a>r&&n(t[a-1],e)>-n(t[a],e)?a-1:a},right:function(t,n,o=0,i=t.length){if(o>>1;0>=r(t[e],n)?o=e+1:i=e}while(o>8&15|e>>4&240,e>>4&15|240&e,(15&e)<<4|15&e,1):8===r?Z(e>>24&255,e>>16&255,e>>8&255,(255&e)/255):4===r?Z(e>>12&15|e>>8&240,e>>8&15|e>>4&240,e>>4&15|240&e,((15&e)<<4|15&e)/255):null):(e=N.exec(t))?new Y(e[1],e[2],e[3],1):(e=D.exec(t))?new Y(255*e[1]/100,255*e[2]/100,255*e[3]/100,1):(e=I.exec(t))?Z(e[1],e[2],e[3],e[4]):(e=L.exec(t))?Z(255*e[1]/100,255*e[2]/100,255*e[3]/100,e[4]):(e=B.exec(t))?Q(e[1],e[2]/100,e[3]/100,1):(e=R.exec(t))?Q(e[1],e[2]/100,e[3]/100,e[4]):z.hasOwnProperty(t)?q(z[t]):"transparent"===t?new Y(NaN,NaN,NaN,0):null}function q(t){return new Y(t>>16&255,t>>8&255,255&t,1)}function Z(t,e,r,n){return n<=0&&(t=e=r=NaN),new Y(t,e,r,n)}function W(t,e,r,n){var o;return 1==arguments.length?((o=t)instanceof A||(o=$(o)),o)?new Y((o=o.rgb()).r,o.g,o.b,o.opacity):new Y:new Y(t,e,r,null==n?1:n)}function Y(t,e,r,n){this.r=+t,this.g=+e,this.b=+r,this.opacity=+n}function H(){return`#${K(this.r)}${K(this.g)}${K(this.b)}`}function X(){let t=G(this.opacity);return`${1===t?"rgb(":"rgba("}${V(this.r)}, ${V(this.g)}, ${V(this.b)}${1===t?")":`, ${t})`}`}function G(t){return isNaN(t)?1:Math.max(0,Math.min(1,t))}function V(t){return Math.max(0,Math.min(255,Math.round(t)||0))}function K(t){return((t=V(t))<16?"0":"")+t.toString(16)}function Q(t,e,r,n){return n<=0?t=e=r=NaN:r<=0||r>=1?t=e=NaN:e<=0&&(t=NaN),new tt(t,e,r,n)}function J(t){if(t instanceof tt)return new tt(t.h,t.s,t.l,t.opacity);if(t instanceof A||(t=$(t)),!t)return new tt;if(t instanceof tt)return t;var e=(t=t.rgb()).r/255,r=t.g/255,n=t.b/255,o=Math.min(e,r,n),i=Math.max(e,r,n),a=NaN,u=i-o,c=(i+o)/2;return u?(a=e===i?(r-n)/u+(r0&&c<1?0:a,new tt(a,u,c,t.opacity)}function tt(t,e,r,n){this.h=+t,this.s=+e,this.l=+r,this.opacity=+n}function te(t){return(t=(t||0)%360)<0?t+360:t}function tr(t){return Math.max(0,Math.min(1,t||0))}function tn(t,e,r){return(t<60?e+(r-e)*t/60:t<180?r:t<240?e+(r-e)*(240-t)/60:e)*255}function to(t,e,r,n,o){var i=t*t,a=i*t;return((1-3*t+3*i-a)*e+(4-6*i+3*a)*r+(1+3*t+3*i-3*a)*n+a*o)/6}E(A,$,{copy(t){return Object.assign(new this.constructor,this,t)},displayable(){return this.rgb().displayable()},hex:U,formatHex:U,formatHex8:function(){return this.rgb().formatHex8()},formatHsl:function(){return J(this).formatHsl()},formatRgb:F,toString:F}),E(Y,W,k(A,{brighter(t){return t=null==t?1.4285714285714286:Math.pow(1.4285714285714286,t),new Y(this.r*t,this.g*t,this.b*t,this.opacity)},darker(t){return t=null==t?.7:Math.pow(.7,t),new Y(this.r*t,this.g*t,this.b*t,this.opacity)},rgb(){return this},clamp(){return new Y(V(this.r),V(this.g),V(this.b),G(this.opacity))},displayable(){return -.5<=this.r&&this.r<255.5&&-.5<=this.g&&this.g<255.5&&-.5<=this.b&&this.b<255.5&&0<=this.opacity&&this.opacity<=1},hex:H,formatHex:H,formatHex8:function(){return`#${K(this.r)}${K(this.g)}${K(this.b)}${K((isNaN(this.opacity)?1:this.opacity)*255)}`},formatRgb:X,toString:X})),E(tt,function(t,e,r,n){return 1==arguments.length?J(t):new tt(t,e,r,null==n?1:n)},k(A,{brighter(t){return t=null==t?1.4285714285714286:Math.pow(1.4285714285714286,t),new tt(this.h,this.s,this.l*t,this.opacity)},darker(t){return t=null==t?.7:Math.pow(.7,t),new tt(this.h,this.s,this.l*t,this.opacity)},rgb(){var t=this.h%360+(this.h<0)*360,e=isNaN(t)||isNaN(this.s)?0:this.s,r=this.l,n=r+(r<.5?r:1-r)*e,o=2*r-n;return new Y(tn(t>=240?t-240:t+120,o,n),tn(t,o,n),tn(t<120?t+240:t-120,o,n),this.opacity)},clamp(){return new tt(te(this.h),tr(this.s),tr(this.l),G(this.opacity))},displayable(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1},formatHsl(){let t=G(this.opacity);return`${1===t?"hsl(":"hsla("}${te(this.h)}, ${100*tr(this.s)}%, ${100*tr(this.l)}%${1===t?")":`, ${t})`}`}}));var ti=t=>()=>t;function ta(t,e){var r=e-t;return r?function(e){return t+e*r}:ti(isNaN(t)?e:t)}var tu=function t(e){var r,n=1==(r=+(r=e))?ta:function(t,e){var n,o,i;return e-t?(n=t,o=e,n=Math.pow(n,i=r),o=Math.pow(o,i)-n,i=1/i,function(t){return Math.pow(n+t*o,i)}):ti(isNaN(t)?e:t)};function o(t,e){var r=n((t=W(t)).r,(e=W(e)).r),o=n(t.g,e.g),i=n(t.b,e.b),a=ta(t.opacity,e.opacity);return function(e){return t.r=r(e),t.g=o(e),t.b=i(e),t.opacity=a(e),t+""}}return o.gamma=t,o}(1);function tc(t){return function(e){var r,n,o=e.length,i=Array(o),a=Array(o),u=Array(o);for(r=0;r=1?(r=1,e-1):Math.floor(r*e),o=t[n],i=t[n+1],a=n>0?t[n-1]:2*o-i,u=nu&&(a=e.slice(u,a),l[c]?l[c]+=a:l[++c]=a),(o=o[0])===(i=i[0])?l[c]?l[c]+=i:l[++c]=i:(l[++c]=null,s.push({i:c,x:tl(o,i)})),u=tf.lastIndex;return ue&&(r=t,t=e,e=r),l=function(r){return Math.max(t,Math.min(e,r))}),n=c>2?tg:tb,o=i=null,f}function f(e){return null==e||isNaN(e=+e)?r:(o||(o=n(a.map(t),u,c)))(t(l(e)))}return f.invert=function(r){return l(e((i||(i=n(u,a.map(t),tl)))(r)))},f.domain=function(t){return arguments.length?(a=Array.from(t,td),s()):a.slice()},f.range=function(t){return arguments.length?(u=Array.from(t),s()):u.slice()},f.rangeRound=function(t){return u=Array.from(t),c=th,s()},f.clamp=function(t){return arguments.length?(l=!!t||tv,s()):l!==tv},f.interpolate=function(t){return arguments.length?(c=t,s()):c},f.unknown=function(t){return arguments.length?(r=t,f):r},function(r,n){return t=r,e=n,s()}}function tO(){return tw()(tv,tv)}var tj=r(89999),tS=/^(?:(.)?([<>=^]))?([+\-( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?(~)?([a-z%])?$/i;function tP(t){var e;if(!(e=tS.exec(t)))throw Error("invalid format: "+t);return new tE({fill:e[1],align:e[2],sign:e[3],symbol:e[4],zero:e[5],width:e[6],comma:e[7],precision:e[8]&&e[8].slice(1),trim:e[9],type:e[10]})}function tE(t){this.fill=void 0===t.fill?" ":t.fill+"",this.align=void 0===t.align?">":t.align+"",this.sign=void 0===t.sign?"-":t.sign+"",this.symbol=void 0===t.symbol?"":t.symbol+"",this.zero=!!t.zero,this.width=void 0===t.width?void 0:+t.width,this.comma=!!t.comma,this.precision=void 0===t.precision?void 0:+t.precision,this.trim=!!t.trim,this.type=void 0===t.type?"":t.type+""}function tk(t,e){if((r=(t=e?t.toExponential(e-1):t.toExponential()).indexOf("e"))<0)return null;var r,n=t.slice(0,r);return[n.length>1?n[0]+n.slice(2):n,+t.slice(r+1)]}function tA(t){return(t=tk(Math.abs(t)))?t[1]:NaN}function tM(t,e){var r=tk(t,e);if(!r)return t+"";var n=r[0],o=r[1];return o<0?"0."+Array(-o).join("0")+n:n.length>o+1?n.slice(0,o+1)+"."+n.slice(o+1):n+Array(o-n.length+2).join("0")}tP.prototype=tE.prototype,tE.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(void 0===this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(void 0===this.precision?"":"."+Math.max(0,0|this.precision))+(this.trim?"~":"")+this.type};var t_={"%":(t,e)=>(100*t).toFixed(e),b:t=>Math.round(t).toString(2),c:t=>t+"",d:function(t){return Math.abs(t=Math.round(t))>=1e21?t.toLocaleString("en").replace(/,/g,""):t.toString(10)},e:(t,e)=>t.toExponential(e),f:(t,e)=>t.toFixed(e),g:(t,e)=>t.toPrecision(e),o:t=>Math.round(t).toString(8),p:(t,e)=>tM(100*t,e),r:tM,s:function(t,e){var r=tk(t,e);if(!r)return t+"";var o=r[0],i=r[1],a=i-(n=3*Math.max(-8,Math.min(8,Math.floor(i/3))))+1,u=o.length;return a===u?o:a>u?o+Array(a-u+1).join("0"):a>0?o.slice(0,a)+"."+o.slice(a):"0."+Array(1-a).join("0")+tk(t,Math.max(0,e+a-1))[0]},X:t=>Math.round(t).toString(16).toUpperCase(),x:t=>Math.round(t).toString(16)};function tT(t){return t}var tC=Array.prototype.map,tN=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"];function tD(t,e,r,n){var o,u,c=b(t,e,r);switch((n=tP(null==n?",f":n)).type){case"s":var l=Math.max(Math.abs(t),Math.abs(e));return null!=n.precision||isNaN(u=Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(tA(l)/3)))-tA(Math.abs(c))))||(n.precision=u),a(n,l);case"":case"e":case"g":case"p":case"r":null!=n.precision||isNaN(u=Math.max(0,tA(Math.abs(Math.max(Math.abs(t),Math.abs(e)))-(o=Math.abs(o=c)))-tA(o))+1)||(n.precision=u-("e"===n.type));break;case"f":case"%":null!=n.precision||isNaN(u=Math.max(0,-tA(Math.abs(c))))||(n.precision=u-("%"===n.type)*2)}return i(n)}function tI(t){var e=t.domain;return t.ticks=function(t){var r=e();return v(r[0],r[r.length-1],null==t?10:t)},t.tickFormat=function(t,r){var n=e();return tD(n[0],n[n.length-1],null==t?10:t,r)},t.nice=function(r){null==r&&(r=10);var n,o,i=e(),a=0,u=i.length-1,c=i[a],l=i[u],s=10;for(l0;){if((o=m(c,l,r))===n)return i[a]=c,i[u]=l,e(i);if(o>0)c=Math.floor(c/o)*o,l=Math.ceil(l/o)*o;else if(o<0)c=Math.ceil(c*o)/o,l=Math.floor(l*o)/o;else break;n=o}return t},t}function tL(){var t=tO();return t.copy=function(){return tx(t,tL())},tj.o.apply(t,arguments),tI(t)}function tB(t,e){t=t.slice();var r,n=0,o=t.length-1,i=t[n],a=t[o];return a-t(-e,r)}function tZ(t){let e,r;let n=t(tR,tz),o=n.domain,a=10;function u(){var i,u;return e=(i=a)===Math.E?Math.log:10===i&&Math.log10||2===i&&Math.log2||(i=Math.log(i),t=>Math.log(t)/i),r=10===(u=a)?t$:u===Math.E?Math.exp:t=>Math.pow(u,t),o()[0]<0?(e=tq(e),r=tq(r),t(tU,tF)):t(tR,tz),n}return n.base=function(t){return arguments.length?(a=+t,u()):a},n.domain=function(t){return arguments.length?(o(t),u()):o()},n.ticks=t=>{let n,i;let u=o(),c=u[0],l=u[u.length-1],s=l0){for(;f<=p;++f)for(n=1;nl)break;d.push(i)}}else for(;f<=p;++f)for(n=a-1;n>=1;--n)if(!((i=f>0?n/r(-f):n*r(f))l)break;d.push(i)}2*d.length{if(null==t&&(t=10),null==o&&(o=10===a?"s":","),"function"!=typeof o&&(a%1||null!=(o=tP(o)).precision||(o.trim=!0),o=i(o)),t===1/0)return o;let u=Math.max(1,a*t/n.ticks().length);return t=>{let n=t/r(Math.round(e(t)));return n*ao(tB(o(),{floor:t=>r(Math.floor(e(t))),ceil:t=>r(Math.ceil(e(t)))})),n}function tW(t){return function(e){return Math.sign(e)*Math.log1p(Math.abs(e/t))}}function tY(t){return function(e){return Math.sign(e)*Math.expm1(Math.abs(e))*t}}function tH(t){var e=1,r=t(tW(1),tY(e));return r.constant=function(r){return arguments.length?t(tW(e=+r),tY(e)):e},tI(r)}i=(o=function(t){var e,r,o,i=void 0===t.grouping||void 0===t.thousands?tT:(e=tC.call(t.grouping,Number),r=t.thousands+"",function(t,n){for(var o=t.length,i=[],a=0,u=e[0],c=0;o>0&&u>0&&(c+u+1>n&&(u=Math.max(1,n-c)),i.push(t.substring(o-=u,o+u)),!((c+=u+1)>n));)u=e[a=(a+1)%e.length];return i.reverse().join(r)}),a=void 0===t.currency?"":t.currency[0]+"",u=void 0===t.currency?"":t.currency[1]+"",c=void 0===t.decimal?".":t.decimal+"",l=void 0===t.numerals?tT:(o=tC.call(t.numerals,String),function(t){return t.replace(/[0-9]/g,function(t){return o[+t]})}),s=void 0===t.percent?"%":t.percent+"",f=void 0===t.minus?"−":t.minus+"",p=void 0===t.nan?"NaN":t.nan+"";function h(t){var e=(t=tP(t)).fill,r=t.align,o=t.sign,h=t.symbol,d=t.zero,y=t.width,v=t.comma,m=t.precision,b=t.trim,g=t.type;"n"===g?(v=!0,g="g"):t_[g]||(void 0===m&&(m=12),b=!0,g="g"),(d||"0"===e&&"="===r)&&(d=!0,e="0",r="=");var x="$"===h?a:"#"===h&&/[boxX]/.test(g)?"0"+g.toLowerCase():"",w="$"===h?u:/[%p]/.test(g)?s:"",O=t_[g],j=/[defgprs%]/.test(g);function S(t){var a,u,s,h=x,S=w;if("c"===g)S=O(t)+S,t="";else{var P=(t=+t)<0||1/t<0;if(t=isNaN(t)?p:O(Math.abs(t),m),b&&(t=function(t){e:for(var e,r=t.length,n=1,o=-1;n0&&(o=0)}return o>0?t.slice(0,o)+t.slice(e+1):t}(t)),P&&0==+t&&"+"!==o&&(P=!1),h=(P?"("===o?o:f:"-"===o||"("===o?"":o)+h,S=("s"===g?tN[8+n/3]:"")+S+(P&&"("===o?")":""),j){for(a=-1,u=t.length;++a(s=t.charCodeAt(a))||s>57){S=(46===s?c+t.slice(a+1):t.slice(a))+S,t=t.slice(0,a);break}}}v&&!d&&(t=i(t,1/0));var E=h.length+t.length+S.length,k=E>1)+h+t+S+k.slice(E);break;default:t=k+h+t+S}return l(t)}return m=void 0===m?6:/[gprs]/.test(g)?Math.max(1,Math.min(21,m)):Math.max(0,Math.min(20,m)),S.toString=function(){return t+""},S}return{format:h,formatPrefix:function(t,e){var r=h(((t=tP(t)).type="f",t)),n=3*Math.max(-8,Math.min(8,Math.floor(tA(e)/3))),o=Math.pow(10,-n),i=tN[8+n/3];return function(t){return r(o*t)+i}}}}({thousands:",",grouping:[3],currency:["$",""]})).format,a=o.formatPrefix;var tX=r(36967);function tG(t){return function(e){return e<0?-Math.pow(-e,t):Math.pow(e,t)}}function tV(t){return t<0?-Math.sqrt(-t):Math.sqrt(t)}function tK(t){return t<0?-t*t:t*t}function tQ(t){var e=t(tv,tv),r=1;return e.exponent=function(e){return arguments.length?1==(r=+e)?t(tv,tv):.5===r?t(tV,tK):t(tG(r),tG(1/r)):r},tI(e)}function tJ(){var t=tQ(tw());return t.copy=function(){return tx(t,tJ()).exponent(t.exponent())},tj.o.apply(t,arguments),t}function t0(){return tJ.apply(null,arguments).exponent(.5)}function t1(t){return Math.sign(t)*t*t}function t2(t,e){let r;if(void 0===e)for(let e of t)null!=e&&(r=e)&&(r=e);else{let n=-1;for(let o of t)null!=(o=e(o,++n,t))&&(r=o)&&(r=o)}return r}function t5(t,e){let r;if(void 0===e)for(let e of t)null!=e&&(r>e||void 0===r&&e>=e)&&(r=e);else{let n=-1;for(let o of t)null!=(o=e(o,++n,t))&&(r>o||void 0===r&&o>=o)&&(r=o)}return r}function t6(t,e){return(null==t||!(t>=t))-(null==e||!(e>=e))||(te?1:0)}function t3(t,e,r){let n=t[e];t[e]=t[r],t[r]=n}let t7=new Date,t4=new Date;function t8(t,e,r,n){function o(e){return t(e=0==arguments.length?new Date:new Date(+e)),e}return o.floor=e=>(t(e=new Date(+e)),e),o.ceil=r=>(t(r=new Date(r-1)),e(r,1),t(r),r),o.round=t=>{let e=o(t),r=o.ceil(t);return t-e(e(t=new Date(+t),null==r?1:Math.floor(r)),t),o.range=(r,n,i)=>{let a;let u=[];if(r=o.ceil(r),i=null==i?1:Math.floor(i),!(r0))return u;do u.push(a=new Date(+r)),e(r,i),t(r);while(at8(e=>{if(e>=e)for(;t(e),!r(e);)e.setTime(e-1)},(t,n)=>{if(t>=t){if(n<0)for(;++n<=0;)for(;e(t,-1),!r(t););else for(;--n>=0;)for(;e(t,1),!r(t););}}),r&&(o.count=(e,n)=>(t7.setTime(+e),t4.setTime(+n),t(t7),t(t4),Math.floor(r(t7,t4))),o.every=t=>isFinite(t=Math.floor(t))&&t>0?t>1?o.filter(n?e=>n(e)%t==0:e=>o.count(0,e)%t==0):o:null),o}let t9=t8(()=>{},(t,e)=>{t.setTime(+t+e)},(t,e)=>e-t);t9.every=t=>isFinite(t=Math.floor(t))&&t>0?t>1?t8(e=>{e.setTime(Math.floor(e/t)*t)},(e,r)=>{e.setTime(+e+r*t)},(e,r)=>(r-e)/t):t9:null,t9.range;let et=t8(t=>{t.setTime(t-t.getMilliseconds())},(t,e)=>{t.setTime(+t+1e3*e)},(t,e)=>(e-t)/1e3,t=>t.getUTCSeconds());et.range;let ee=t8(t=>{t.setTime(t-t.getMilliseconds()-1e3*t.getSeconds())},(t,e)=>{t.setTime(+t+6e4*e)},(t,e)=>(e-t)/6e4,t=>t.getMinutes());ee.range;let er=t8(t=>{t.setUTCSeconds(0,0)},(t,e)=>{t.setTime(+t+6e4*e)},(t,e)=>(e-t)/6e4,t=>t.getUTCMinutes());er.range;let en=t8(t=>{t.setTime(t-t.getMilliseconds()-1e3*t.getSeconds()-6e4*t.getMinutes())},(t,e)=>{t.setTime(+t+36e5*e)},(t,e)=>(e-t)/36e5,t=>t.getHours());en.range;let eo=t8(t=>{t.setUTCMinutes(0,0,0)},(t,e)=>{t.setTime(+t+36e5*e)},(t,e)=>(e-t)/36e5,t=>t.getUTCHours());eo.range;let ei=t8(t=>t.setHours(0,0,0,0),(t,e)=>t.setDate(t.getDate()+e),(t,e)=>(e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*6e4)/864e5,t=>t.getDate()-1);ei.range;let ea=t8(t=>{t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCDate(t.getUTCDate()+e)},(t,e)=>(e-t)/864e5,t=>t.getUTCDate()-1);ea.range;let eu=t8(t=>{t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCDate(t.getUTCDate()+e)},(t,e)=>(e-t)/864e5,t=>Math.floor(t/864e5));function ec(t){return t8(e=>{e.setDate(e.getDate()-(e.getDay()+7-t)%7),e.setHours(0,0,0,0)},(t,e)=>{t.setDate(t.getDate()+7*e)},(t,e)=>(e-t-(e.getTimezoneOffset()-t.getTimezoneOffset())*6e4)/6048e5)}eu.range;let el=ec(0),es=ec(1),ef=ec(2),ep=ec(3),eh=ec(4),ed=ec(5),ey=ec(6);function ev(t){return t8(e=>{e.setUTCDate(e.getUTCDate()-(e.getUTCDay()+7-t)%7),e.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCDate(t.getUTCDate()+7*e)},(t,e)=>(e-t)/6048e5)}el.range,es.range,ef.range,ep.range,eh.range,ed.range,ey.range;let em=ev(0),eb=ev(1),eg=ev(2),ex=ev(3),ew=ev(4),eO=ev(5),ej=ev(6);em.range,eb.range,eg.range,ex.range,ew.range,eO.range,ej.range;let eS=t8(t=>{t.setDate(1),t.setHours(0,0,0,0)},(t,e)=>{t.setMonth(t.getMonth()+e)},(t,e)=>e.getMonth()-t.getMonth()+(e.getFullYear()-t.getFullYear())*12,t=>t.getMonth());eS.range;let eP=t8(t=>{t.setUTCDate(1),t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCMonth(t.getUTCMonth()+e)},(t,e)=>e.getUTCMonth()-t.getUTCMonth()+(e.getUTCFullYear()-t.getUTCFullYear())*12,t=>t.getUTCMonth());eP.range;let eE=t8(t=>{t.setMonth(0,1),t.setHours(0,0,0,0)},(t,e)=>{t.setFullYear(t.getFullYear()+e)},(t,e)=>e.getFullYear()-t.getFullYear(),t=>t.getFullYear());eE.every=t=>isFinite(t=Math.floor(t))&&t>0?t8(e=>{e.setFullYear(Math.floor(e.getFullYear()/t)*t),e.setMonth(0,1),e.setHours(0,0,0,0)},(e,r)=>{e.setFullYear(e.getFullYear()+r*t)}):null,eE.range;let ek=t8(t=>{t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)},(t,e)=>{t.setUTCFullYear(t.getUTCFullYear()+e)},(t,e)=>e.getUTCFullYear()-t.getUTCFullYear(),t=>t.getUTCFullYear());function eA(t,e,r,n,o,i){let a=[[et,1,1e3],[et,5,5e3],[et,15,15e3],[et,30,3e4],[i,1,6e4],[i,5,3e5],[i,15,9e5],[i,30,18e5],[o,1,36e5],[o,3,108e5],[o,6,216e5],[o,12,432e5],[n,1,864e5],[n,2,1728e5],[r,1,6048e5],[e,1,2592e6],[e,3,7776e6],[t,1,31536e6]];function u(e,r,n){let o=Math.abs(r-e)/n,i=w(([,,t])=>t).right(a,o);if(i===a.length)return t.every(b(e/31536e6,r/31536e6,n));if(0===i)return t9.every(Math.max(b(e,r,n),1));let[u,c]=a[o/a[i-1][2]isFinite(t=Math.floor(t))&&t>0?t8(e=>{e.setUTCFullYear(Math.floor(e.getUTCFullYear()/t)*t),e.setUTCMonth(0,1),e.setUTCHours(0,0,0,0)},(e,r)=>{e.setUTCFullYear(e.getUTCFullYear()+r*t)}):null,ek.range;let[eM,e_]=eA(ek,eP,em,eu,eo,er),[eT,eC]=eA(eE,eS,el,ei,en,ee);function eN(t){if(0<=t.y&&t.y<100){var e=new Date(-1,t.m,t.d,t.H,t.M,t.S,t.L);return e.setFullYear(t.y),e}return new Date(t.y,t.m,t.d,t.H,t.M,t.S,t.L)}function eD(t){if(0<=t.y&&t.y<100){var e=new Date(Date.UTC(-1,t.m,t.d,t.H,t.M,t.S,t.L));return e.setUTCFullYear(t.y),e}return new Date(Date.UTC(t.y,t.m,t.d,t.H,t.M,t.S,t.L))}function eI(t,e,r){return{y:t,m:e,d:r,H:0,M:0,S:0,L:0}}var eL={"-":"",_:" ",0:"0"},eB=/^\s*\d+/,eR=/^%/,ez=/[\\^$*+?|[\]().{}]/g;function eU(t,e,r){var n=t<0?"-":"",o=(n?-t:t)+"",i=o.length;return n+(i[t.toLowerCase(),e]))}function eZ(t,e,r){var n=eB.exec(e.slice(r,r+1));return n?(t.w=+n[0],r+n[0].length):-1}function eW(t,e,r){var n=eB.exec(e.slice(r,r+1));return n?(t.u=+n[0],r+n[0].length):-1}function eY(t,e,r){var n=eB.exec(e.slice(r,r+2));return n?(t.U=+n[0],r+n[0].length):-1}function eH(t,e,r){var n=eB.exec(e.slice(r,r+2));return n?(t.V=+n[0],r+n[0].length):-1}function eX(t,e,r){var n=eB.exec(e.slice(r,r+2));return n?(t.W=+n[0],r+n[0].length):-1}function eG(t,e,r){var n=eB.exec(e.slice(r,r+4));return n?(t.y=+n[0],r+n[0].length):-1}function eV(t,e,r){var n=eB.exec(e.slice(r,r+2));return n?(t.y=+n[0]+(+n[0]>68?1900:2e3),r+n[0].length):-1}function eK(t,e,r){var n=/^(Z)|([+-]\d\d)(?::?(\d\d))?/.exec(e.slice(r,r+6));return n?(t.Z=n[1]?0:-(n[2]+(n[3]||"00")),r+n[0].length):-1}function eQ(t,e,r){var n=eB.exec(e.slice(r,r+1));return n?(t.q=3*n[0]-3,r+n[0].length):-1}function eJ(t,e,r){var n=eB.exec(e.slice(r,r+2));return n?(t.m=n[0]-1,r+n[0].length):-1}function e0(t,e,r){var n=eB.exec(e.slice(r,r+2));return n?(t.d=+n[0],r+n[0].length):-1}function e1(t,e,r){var n=eB.exec(e.slice(r,r+3));return n?(t.m=0,t.d=+n[0],r+n[0].length):-1}function e2(t,e,r){var n=eB.exec(e.slice(r,r+2));return n?(t.H=+n[0],r+n[0].length):-1}function e5(t,e,r){var n=eB.exec(e.slice(r,r+2));return n?(t.M=+n[0],r+n[0].length):-1}function e6(t,e,r){var n=eB.exec(e.slice(r,r+2));return n?(t.S=+n[0],r+n[0].length):-1}function e3(t,e,r){var n=eB.exec(e.slice(r,r+3));return n?(t.L=+n[0],r+n[0].length):-1}function e7(t,e,r){var n=eB.exec(e.slice(r,r+6));return n?(t.L=Math.floor(n[0]/1e3),r+n[0].length):-1}function e4(t,e,r){var n=eR.exec(e.slice(r,r+1));return n?r+n[0].length:-1}function e8(t,e,r){var n=eB.exec(e.slice(r));return n?(t.Q=+n[0],r+n[0].length):-1}function e9(t,e,r){var n=eB.exec(e.slice(r));return n?(t.s=+n[0],r+n[0].length):-1}function rt(t,e){return eU(t.getDate(),e,2)}function re(t,e){return eU(t.getHours(),e,2)}function rr(t,e){return eU(t.getHours()%12||12,e,2)}function rn(t,e){return eU(1+ei.count(eE(t),t),e,3)}function ro(t,e){return eU(t.getMilliseconds(),e,3)}function ri(t,e){return ro(t,e)+"000"}function ra(t,e){return eU(t.getMonth()+1,e,2)}function ru(t,e){return eU(t.getMinutes(),e,2)}function rc(t,e){return eU(t.getSeconds(),e,2)}function rl(t){var e=t.getDay();return 0===e?7:e}function rs(t,e){return eU(el.count(eE(t)-1,t),e,2)}function rf(t){var e=t.getDay();return e>=4||0===e?eh(t):eh.ceil(t)}function rp(t,e){return t=rf(t),eU(eh.count(eE(t),t)+(4===eE(t).getDay()),e,2)}function rh(t){return t.getDay()}function rd(t,e){return eU(es.count(eE(t)-1,t),e,2)}function ry(t,e){return eU(t.getFullYear()%100,e,2)}function rv(t,e){return eU((t=rf(t)).getFullYear()%100,e,2)}function rm(t,e){return eU(t.getFullYear()%1e4,e,4)}function rb(t,e){var r=t.getDay();return eU((t=r>=4||0===r?eh(t):eh.ceil(t)).getFullYear()%1e4,e,4)}function rg(t){var e=t.getTimezoneOffset();return(e>0?"-":(e*=-1,"+"))+eU(e/60|0,"0",2)+eU(e%60,"0",2)}function rx(t,e){return eU(t.getUTCDate(),e,2)}function rw(t,e){return eU(t.getUTCHours(),e,2)}function rO(t,e){return eU(t.getUTCHours()%12||12,e,2)}function rj(t,e){return eU(1+ea.count(ek(t),t),e,3)}function rS(t,e){return eU(t.getUTCMilliseconds(),e,3)}function rP(t,e){return rS(t,e)+"000"}function rE(t,e){return eU(t.getUTCMonth()+1,e,2)}function rk(t,e){return eU(t.getUTCMinutes(),e,2)}function rA(t,e){return eU(t.getUTCSeconds(),e,2)}function rM(t){var e=t.getUTCDay();return 0===e?7:e}function r_(t,e){return eU(em.count(ek(t)-1,t),e,2)}function rT(t){var e=t.getUTCDay();return e>=4||0===e?ew(t):ew.ceil(t)}function rC(t,e){return t=rT(t),eU(ew.count(ek(t),t)+(4===ek(t).getUTCDay()),e,2)}function rN(t){return t.getUTCDay()}function rD(t,e){return eU(eb.count(ek(t)-1,t),e,2)}function rI(t,e){return eU(t.getUTCFullYear()%100,e,2)}function rL(t,e){return eU((t=rT(t)).getUTCFullYear()%100,e,2)}function rB(t,e){return eU(t.getUTCFullYear()%1e4,e,4)}function rR(t,e){var r=t.getUTCDay();return eU((t=r>=4||0===r?ew(t):ew.ceil(t)).getUTCFullYear()%1e4,e,4)}function rz(){return"+0000"}function rU(){return"%"}function rF(t){return+t}function r$(t){return Math.floor(+t/1e3)}function rq(t){return new Date(t)}function rZ(t){return t instanceof Date?+t:+new Date(+t)}function rW(t,e,r,n,o,i,a,u,c,l){var s=tO(),f=s.invert,p=s.domain,h=l(".%L"),d=l(":%S"),y=l("%I:%M"),v=l("%I %p"),m=l("%a %d"),b=l("%b %d"),g=l("%B"),x=l("%Y");function w(t){return(c(t)1)for(var r,n,o,i=1,a=t[e[0]],u=a.length;i=12)]},q:function(t){return 1+~~(t.getMonth()/3)},Q:rF,s:r$,S:rc,u:rl,U:rs,V:rp,w:rh,W:rd,x:null,X:null,y:ry,Y:rm,Z:rg,"%":rU},x={a:function(t){return a[t.getUTCDay()]},A:function(t){return i[t.getUTCDay()]},b:function(t){return c[t.getUTCMonth()]},B:function(t){return u[t.getUTCMonth()]},c:null,d:rx,e:rx,f:rP,g:rL,G:rR,H:rw,I:rO,j:rj,L:rS,m:rE,M:rk,p:function(t){return o[+(t.getUTCHours()>=12)]},q:function(t){return 1+~~(t.getUTCMonth()/3)},Q:rF,s:r$,S:rA,u:rM,U:r_,V:rC,w:rN,W:rD,x:null,X:null,y:rI,Y:rB,Z:rz,"%":rU},w={a:function(t,e,r){var n=h.exec(e.slice(r));return n?(t.w=d.get(n[0].toLowerCase()),r+n[0].length):-1},A:function(t,e,r){var n=f.exec(e.slice(r));return n?(t.w=p.get(n[0].toLowerCase()),r+n[0].length):-1},b:function(t,e,r){var n=m.exec(e.slice(r));return n?(t.m=b.get(n[0].toLowerCase()),r+n[0].length):-1},B:function(t,e,r){var n=y.exec(e.slice(r));return n?(t.m=v.get(n[0].toLowerCase()),r+n[0].length):-1},c:function(t,r,n){return S(t,e,r,n)},d:e0,e:e0,f:e7,g:eV,G:eG,H:e2,I:e2,j:e1,L:e3,m:eJ,M:e5,p:function(t,e,r){var n=l.exec(e.slice(r));return n?(t.p=s.get(n[0].toLowerCase()),r+n[0].length):-1},q:eQ,Q:e8,s:e9,S:e6,u:eW,U:eY,V:eH,w:eZ,W:eX,x:function(t,e,n){return S(t,r,e,n)},X:function(t,e,r){return S(t,n,e,r)},y:eV,Y:eG,Z:eK,"%":e4};function O(t,e){return function(r){var n,o,i,a=[],u=-1,c=0,l=t.length;for(r instanceof Date||(r=new Date(+r));++u53)return null;"w"in i||(i.w=1),"Z"in i?(n=(o=(n=eD(eI(i.y,0,1))).getUTCDay())>4||0===o?eb.ceil(n):eb(n),n=ea.offset(n,(i.V-1)*7),i.y=n.getUTCFullYear(),i.m=n.getUTCMonth(),i.d=n.getUTCDate()+(i.w+6)%7):(n=(o=(n=eN(eI(i.y,0,1))).getDay())>4||0===o?es.ceil(n):es(n),n=ei.offset(n,(i.V-1)*7),i.y=n.getFullYear(),i.m=n.getMonth(),i.d=n.getDate()+(i.w+6)%7)}else("W"in i||"U"in i)&&("w"in i||(i.w="u"in i?i.u%7:"W"in i?1:0),o="Z"in i?eD(eI(i.y,0,1)).getUTCDay():eN(eI(i.y,0,1)).getDay(),i.m=0,i.d="W"in i?(i.w+6)%7+7*i.W-(o+5)%7:i.w+7*i.U-(o+6)%7);return"Z"in i?(i.H+=i.Z/100|0,i.M+=i.Z%100,eD(i)):eN(i)}}function S(t,e,r,n){for(var o,i,a=0,u=e.length,c=r.length;a=c)return -1;if(37===(o=e.charCodeAt(a++))){if(!(i=w[(o=e.charAt(a++))in eL?e.charAt(a++):o])||(n=i(t,r,n))<0)return -1}else if(o!=r.charCodeAt(n++))return -1}return n}return g.x=O(r,g),g.X=O(n,g),g.c=O(e,g),x.x=O(r,x),x.X=O(n,x),x.c=O(e,x),{format:function(t){var e=O(t+="",g);return e.toString=function(){return t},e},parse:function(t){var e=j(t+="",!1);return e.toString=function(){return t},e},utcFormat:function(t){var e=O(t+="",x);return e.toString=function(){return t},e},utcParse:function(t){var e=j(t+="",!0);return e.toString=function(){return t},e}}}({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]})).format,u.parse,l=u.utcFormat,u.utcParse;var r2=r(22516),r5=r(76115);function r6(t){for(var e=t.length,r=Array(e);--e>=0;)r[e]=e;return r}function r3(t,e){return t[e]}function r7(t){let e=[];return e.key=t,e}var r4=r(95645),r8=r.n(r4),r9=r(99008),nt=r.n(r9),ne=r(77571),nr=r.n(ne),nn=r(86757),no=r.n(nn),ni=r(42715),na=r.n(ni),nu=r(13735),nc=r.n(nu),nl=r(11314),ns=r.n(nl),nf=r(82559),np=r.n(nf),nh=r(75551),nd=r.n(nh),ny=r(21652),nv=r.n(ny),nm=r(34935),nb=r.n(nm),ng=r(61134),nx=r.n(ng);function nw(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=Array(e);r=e?r.apply(void 0,o):t(e-a,nP(function(){for(var t=arguments.length,e=Array(t),n=0;nt.length)&&(e=t.length);for(var r=0,n=Array(e);rn&&(o=n,i=r),[o,i]}function nR(t,e,r){if(t.lte(0))return new(nx())(0);var n=nC.getDigitCount(t.toNumber()),o=new(nx())(10).pow(n),i=t.div(o),a=1!==n?.05:.1,u=new(nx())(Math.ceil(i.div(a).toNumber())).add(r).mul(a).mul(o);return e?u:new(nx())(Math.ceil(u))}function nz(t,e,r){var n=1,o=new(nx())(t);if(!o.isint()&&r){var i=Math.abs(t);i<1?(n=new(nx())(10).pow(nC.getDigitCount(t)-1),o=new(nx())(Math.floor(o.div(n).toNumber())).mul(n)):i>1&&(o=new(nx())(Math.floor(t)))}else 0===t?o=new(nx())(Math.floor((e-1)/2)):r||(o=new(nx())(Math.floor(t)));var a=Math.floor((e-1)/2);return nM(nA(function(t){return o.add(new(nx())(t-a).mul(n)).toNumber()}),nk)(0,e)}var nU=nT(function(t){var e=nD(t,2),r=e[0],n=e[1],o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:6,i=!(arguments.length>2)||void 0===arguments[2]||arguments[2],a=Math.max(o,2),u=nD(nB([r,n]),2),c=u[0],l=u[1];if(c===-1/0||l===1/0){var s=l===1/0?[c].concat(nN(nk(0,o-1).map(function(){return 1/0}))):[].concat(nN(nk(0,o-1).map(function(){return-1/0})),[l]);return r>n?n_(s):s}if(c===l)return nz(c,o,i);var f=function t(e,r,n,o){var i,a=arguments.length>4&&void 0!==arguments[4]?arguments[4]:0;if(!Number.isFinite((r-e)/(n-1)))return{step:new(nx())(0),tickMin:new(nx())(0),tickMax:new(nx())(0)};var u=nR(new(nx())(r).sub(e).div(n-1),o,a),c=Math.ceil((i=e<=0&&r>=0?new(nx())(0):(i=new(nx())(e).add(r).div(2)).sub(new(nx())(i).mod(u))).sub(e).div(u).toNumber()),l=Math.ceil(new(nx())(r).sub(i).div(u).toNumber()),s=c+l+1;return s>n?t(e,r,n,o,a+1):(s0?l+(n-s):l,c=r>0?c:c+(n-s)),{step:u,tickMin:i.sub(new(nx())(c).mul(u)),tickMax:i.add(new(nx())(l).mul(u))})}(c,l,a,i),p=f.step,h=f.tickMin,d=f.tickMax,y=nC.rangeStep(h,d.add(new(nx())(.1).mul(p)),p);return r>n?n_(y):y});nT(function(t){var e=nD(t,2),r=e[0],n=e[1],o=arguments.length>1&&void 0!==arguments[1]?arguments[1]:6,i=!(arguments.length>2)||void 0===arguments[2]||arguments[2],a=Math.max(o,2),u=nD(nB([r,n]),2),c=u[0],l=u[1];if(c===-1/0||l===1/0)return[r,n];if(c===l)return nz(c,o,i);var s=nR(new(nx())(l).sub(c).div(a-1),i,0),f=nM(nA(function(t){return new(nx())(c).add(new(nx())(t).mul(s)).toNumber()}),nk)(0,a).filter(function(t){return t>=c&&t<=l});return r>n?n_(f):f});var nF=nT(function(t,e){var r=nD(t,2),n=r[0],o=r[1],i=!(arguments.length>2)||void 0===arguments[2]||arguments[2],a=nD(nB([n,o]),2),u=a[0],c=a[1];if(u===-1/0||c===1/0)return[n,o];if(u===c)return[u];var l=nR(new(nx())(c).sub(u).div(Math.max(e,2)-1),i,0),s=[].concat(nN(nC.rangeStep(new(nx())(u),new(nx())(c).sub(new(nx())(.99).mul(l)),l)),[c]);return n>o?n_(s):s}),n$=r(13137),nq=r(16630),nZ=r(82944),nW=r(38569);function nY(t){return(nY="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function nH(t){return function(t){if(Array.isArray(t))return nX(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||function(t,e){if(t){if("string"==typeof t)return nX(t,void 0);var r=Object.prototype.toString.call(t).slice(8,-1);if("Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return nX(t,void 0)}}(t)||function(){throw TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function nX(t,e){(null==e||e>t.length)&&(e=t.length);for(var r=0,n=Array(e);r1&&void 0!==arguments[1]?arguments[1]:[],n=arguments.length>2?arguments[2]:void 0,o=arguments.length>3?arguments[3]:void 0,i=-1,a=null!==(e=null==r?void 0:r.length)&&void 0!==e?e:0;if(a<=1)return 0;if(o&&"angleAxis"===o.axisType&&1e-6>=Math.abs(Math.abs(o.range[1]-o.range[0])-360))for(var u=o.range,c=0;c0?n[c-1].coordinate:n[a-1].coordinate,s=n[c].coordinate,f=c>=a-1?n[0].coordinate:n[c+1].coordinate,p=void 0;if((0,nq.uY)(s-l)!==(0,nq.uY)(f-s)){var h=[];if((0,nq.uY)(f-s)===(0,nq.uY)(u[1]-u[0])){p=f;var d=s+u[1]-u[0];h[0]=Math.min(d,(d+l)/2),h[1]=Math.max(d,(d+l)/2)}else{p=l;var y=f+u[1]-u[0];h[0]=Math.min(s,(y+s)/2),h[1]=Math.max(s,(y+s)/2)}var v=[Math.min(s,(p+s)/2),Math.max(s,(p+s)/2)];if(t>v[0]&&t<=v[1]||t>=h[0]&&t<=h[1]){i=n[c].index;break}}else{var m=Math.min(l,f),b=Math.max(l,f);if(t>(m+s)/2&&t<=(b+s)/2){i=n[c].index;break}}}else for(var g=0;g0&&g(r[g].coordinate+r[g-1].coordinate)/2&&t<=(r[g].coordinate+r[g+1].coordinate)/2||g===a-1&&t>(r[g].coordinate+r[g-1].coordinate)/2){i=r[g].index;break}return i},n1=function(t){var e,r,n=t.type.displayName,o=null!==(e=t.type)&&void 0!==e&&e.defaultProps?nV(nV({},t.type.defaultProps),t.props):t.props,i=o.stroke,a=o.fill;switch(n){case"Line":r=i;break;case"Area":case"Radar":r=i&&"none"!==i?i:a;break;default:r=a}return r},n2=function(t){var e=t.barSize,r=t.totalSize,n=t.stackGroups,o=void 0===n?{}:n;if(!o)return{};for(var i={},a=Object.keys(o),u=0,c=a.length;u=0});if(v&&v.length){var m=v[0].type.defaultProps,b=void 0!==m?nV(nV({},m),v[0].props):v[0].props,g=b.barSize,x=b[y];i[x]||(i[x]=[]);var w=nr()(g)?e:g;i[x].push({item:v[0],stackList:v.slice(1),barSize:nr()(w)?void 0:(0,nq.h1)(w,r,0)})}}return i},n5=function(t){var e,r=t.barGap,n=t.barCategoryGap,o=t.bandSize,i=t.sizeList,a=void 0===i?[]:i,u=t.maxBarSize,c=a.length;if(c<1)return null;var l=(0,nq.h1)(r,o,0,!0),s=[];if(a[0].barSize===+a[0].barSize){var f=!1,p=o/c,h=a.reduce(function(t,e){return t+e.barSize||0},0);(h+=(c-1)*l)>=o&&(h-=(c-1)*l,l=0),h>=o&&p>0&&(f=!0,p*=.9,h=c*p);var d={offset:((o-h)/2>>0)-l,size:0};e=a.reduce(function(t,e){var r={item:e.item,position:{offset:d.offset+d.size+l,size:f?p:e.barSize}},n=[].concat(nH(t),[r]);return d=n[n.length-1].position,e.stackList&&e.stackList.length&&e.stackList.forEach(function(t){n.push({item:t,position:d})}),n},s)}else{var y=(0,nq.h1)(n,o,0,!0);o-2*y-(c-1)*l<=0&&(l=0);var v=(o-2*y-(c-1)*l)/c;v>1&&(v>>=0);var m=u===+u?Math.min(v,u):v;e=a.reduce(function(t,e,r){var n=[].concat(nH(t),[{item:e.item,position:{offset:y+(v+l)*r+(v-m)/2,size:m}}]);return e.stackList&&e.stackList.length&&e.stackList.forEach(function(t){n.push({item:t,position:n[n.length-1].position})}),n},s)}return e},n6=function(t,e,r,n){var o=r.children,i=r.width,a=r.margin,u=i-(a.left||0)-(a.right||0),c=(0,nW.z)({children:o,legendWidth:u});if(c){var l=n||{},s=l.width,f=l.height,p=c.align,h=c.verticalAlign,d=c.layout;if(("vertical"===d||"horizontal"===d&&"middle"===h)&&"center"!==p&&(0,nq.hj)(t[p]))return nV(nV({},t),{},nK({},p,t[p]+(s||0)));if(("horizontal"===d||"vertical"===d&&"center"===p)&&"middle"!==h&&(0,nq.hj)(t[h]))return nV(nV({},t),{},nK({},h,t[h]+(f||0)))}return t},n3=function(t,e,r,n,o){var i=e.props.children,a=(0,nZ.NN)(i,n$.W).filter(function(t){var e;return e=t.props.direction,!!nr()(o)||("horizontal"===n?"yAxis"===o:"vertical"===n||"x"===e?"xAxis"===o:"y"!==e||"yAxis"===o)});if(a&&a.length){var u=a.map(function(t){return t.props.dataKey});return t.reduce(function(t,e){var n=nQ(e,r);if(nr()(n))return t;var o=Array.isArray(n)?[nt()(n),r8()(n)]:[n,n],i=u.reduce(function(t,r){var n=nQ(e,r,0),i=o[0]-Math.abs(Array.isArray(n)?n[0]:n),a=o[1]+Math.abs(Array.isArray(n)?n[1]:n);return[Math.min(i,t[0]),Math.max(a,t[1])]},[1/0,-1/0]);return[Math.min(i[0],t[0]),Math.max(i[1],t[1])]},[1/0,-1/0])}return null},n7=function(t,e,r,n,o){var i=e.map(function(e){return n3(t,e,r,o,n)}).filter(function(t){return!nr()(t)});return i&&i.length?i.reduce(function(t,e){return[Math.min(t[0],e[0]),Math.max(t[1],e[1])]},[1/0,-1/0]):null},n4=function(t,e,r,n,o){var i=e.map(function(e){var i=e.props.dataKey;return"number"===r&&i&&n3(t,e,i,n)||nJ(t,i,r,o)});if("number"===r)return i.reduce(function(t,e){return[Math.min(t[0],e[0]),Math.max(t[1],e[1])]},[1/0,-1/0]);var a={};return i.reduce(function(t,e){for(var r=0,n=e.length;r=2?2*(0,nq.uY)(a[0]-a[1])*c:c,e&&(t.ticks||t.niceTicks))?(t.ticks||t.niceTicks).map(function(t){return{coordinate:n(o?o.indexOf(t):t)+c,value:t,offset:c}}).filter(function(t){return!np()(t.coordinate)}):t.isCategorical&&t.categoricalDomain?t.categoricalDomain.map(function(t,e){return{coordinate:n(t)+c,value:t,index:e,offset:c}}):n.ticks&&!r?n.ticks(t.tickCount).map(function(t){return{coordinate:n(t)+c,value:t,offset:c}}):n.domain().map(function(t,e){return{coordinate:n(t)+c,value:o?o[t]:t,index:e,offset:c}})},oe=new WeakMap,or=function(t,e){if("function"!=typeof e)return t;oe.has(t)||oe.set(t,new WeakMap);var r=oe.get(t);if(r.has(e))return r.get(e);var n=function(){t.apply(void 0,arguments),e.apply(void 0,arguments)};return r.set(e,n),n},on=function(t,e,r){var n=t.scale,o=t.type,i=t.layout,a=t.axisType;if("auto"===n)return"radial"===i&&"radiusAxis"===a?{scale:f.Z(),realScaleType:"band"}:"radial"===i&&"angleAxis"===a?{scale:tL(),realScaleType:"linear"}:"category"===o&&e&&(e.indexOf("LineChart")>=0||e.indexOf("AreaChart")>=0||e.indexOf("ComposedChart")>=0&&!r)?{scale:f.x(),realScaleType:"point"}:"category"===o?{scale:f.Z(),realScaleType:"band"}:{scale:tL(),realScaleType:"linear"};if(na()(n)){var u="scale".concat(nd()(n));return{scale:(s[u]||f.x)(),realScaleType:s[u]?u:"point"}}return no()(n)?{scale:n}:{scale:f.x(),realScaleType:"point"}},oo=function(t){var e=t.domain();if(e&&!(e.length<=2)){var r=e.length,n=t.range(),o=Math.min(n[0],n[1])-1e-4,i=Math.max(n[0],n[1])+1e-4,a=t(e[0]),u=t(e[r-1]);(ai||ui)&&t.domain([e[0],e[r-1]])}},oi=function(t,e){if(!t)return null;for(var r=0,n=t.length;rn)&&(o[1]=n),o[0]>n&&(o[0]=n),o[1]=0?(t[a][r][0]=o,t[a][r][1]=o+u,o=t[a][r][1]):(t[a][r][0]=i,t[a][r][1]=i+u,i=t[a][r][1])}},expand:function(t,e){if((n=t.length)>0){for(var r,n,o,i=0,a=t[0].length;i0){for(var r,n=0,o=t[e[0]],i=o.length;n0&&(n=(r=t[e[0]]).length)>0){for(var r,n,o,i=0,a=1;a=0?(t[i][r][0]=o,t[i][r][1]=o+a,o=t[i][r][1]):(t[i][r][0]=0,t[i][r][1]=0)}}},oc=function(t,e,r){var n=e.map(function(t){return t.props.dataKey}),o=ou[r];return(function(){var t=(0,r5.Z)([]),e=r6,r=r1,n=r3;function o(o){var i,a,u=Array.from(t.apply(this,arguments),r7),c=u.length,l=-1;for(let t of o)for(i=0,++l;i=0?0:o<0?o:n}return r[0]},od=function(t,e){var r,n=(null!==(r=t.type)&&void 0!==r&&r.defaultProps?nV(nV({},t.type.defaultProps),t.props):t.props).stackId;if((0,nq.P2)(n)){var o=e[n];if(o){var i=o.items.indexOf(t);return i>=0?o.stackedData[i]:null}}return null},oy=function(t,e,r){return Object.keys(t).reduce(function(n,o){var i=t[o].stackedData.reduce(function(t,n){var o=n.slice(e,r+1).reduce(function(t,e){return[nt()(e.concat([t[0]]).filter(nq.hj)),r8()(e.concat([t[1]]).filter(nq.hj))]},[1/0,-1/0]);return[Math.min(t[0],o[0]),Math.max(t[1],o[1])]},[1/0,-1/0]);return[Math.min(i[0],n[0]),Math.max(i[1],n[1])]},[1/0,-1/0]).map(function(t){return t===1/0||t===-1/0?0:t})},ov=/^dataMin[\s]*-[\s]*([0-9]+([.]{1}[0-9]+){0,1})$/,om=/^dataMax[\s]*\+[\s]*([0-9]+([.]{1}[0-9]+){0,1})$/,ob=function(t,e,r){if(no()(t))return t(e,r);if(!Array.isArray(t))return e;var n=[];if((0,nq.hj)(t[0]))n[0]=r?t[0]:Math.min(t[0],e[0]);else if(ov.test(t[0])){var o=+ov.exec(t[0])[1];n[0]=e[0]-o}else no()(t[0])?n[0]=t[0](e[0]):n[0]=e[0];if((0,nq.hj)(t[1]))n[1]=r?t[1]:Math.max(t[1],e[1]);else if(om.test(t[1])){var i=+om.exec(t[1])[1];n[1]=e[1]+i}else no()(t[1])?n[1]=t[1](e[1]):n[1]=e[1];return n},og=function(t,e,r){if(t&&t.scale&&t.scale.bandwidth){var n=t.scale.bandwidth();if(!r||n>0)return n}if(t&&e&&e.length>=2){for(var o=nb()(e,function(t){return t.coordinate}),i=1/0,a=1,u=o.length;a1&&void 0!==arguments[1]?arguments[1]:{};if(null==t||n.x.isSsr)return{width:0,height:0};var o=(Object.keys(e=a({},r)).forEach(function(t){e[t]||delete e[t]}),e),i=JSON.stringify({text:t,copyStyle:o});if(u.widthCache[i])return u.widthCache[i];try{var s=document.getElementById(l);s||((s=document.createElement("span")).setAttribute("id",l),s.setAttribute("aria-hidden","true"),document.body.appendChild(s));var f=a(a({},c),o);Object.assign(s.style,f),s.textContent="".concat(t);var p=s.getBoundingClientRect(),h={width:p.width,height:p.height};return u.widthCache[i]=h,++u.cacheCount>2e3&&(u.cacheCount=0,u.widthCache={}),h}catch(t){return{width:0,height:0}}},f=function(t){return{top:t.top+window.scrollY-document.documentElement.clientTop,left:t.left+window.scrollX-document.documentElement.clientLeft}}},16630:function(t,e,r){"use strict";r.d(e,{Ap:function(){return S},EL:function(){return g},Kt:function(){return w},P2:function(){return m},Rw:function(){return v},bv:function(){return O},fC:function(){return P},h1:function(){return x},hU:function(){return d},hj:function(){return y},k4:function(){return j},uY:function(){return h}});var n=r(42715),o=r.n(n),i=r(82559),a=r.n(i),u=r(13735),c=r.n(u),l=r(22345),s=r.n(l),f=r(77571),p=r.n(f),h=function(t){return 0===t?0:t>0?1:-1},d=function(t){return o()(t)&&t.indexOf("%")===t.length-1},y=function(t){return s()(t)&&!a()(t)},v=function(t){return p()(t)},m=function(t){return y(t)||o()(t)},b=0,g=function(t){var e=++b;return"".concat(t||"").concat(e)},x=function(t,e){var r,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,i=arguments.length>3&&void 0!==arguments[3]&&arguments[3];if(!y(t)&&!o()(t))return n;if(d(t)){var u=t.indexOf("%");r=e*parseFloat(t.slice(0,u))/100}else r=+t;return a()(r)&&(r=n),i&&r>e&&(r=e),r},w=function(t){if(!t)return null;var e=Object.keys(t);return e&&e.length?t[e[0]]:null},O=function(t){if(!Array.isArray(t))return!1;for(var e=t.length,r={},n=0;n2?r-2:0),o=2;ot.length)&&(e=t.length);for(var r=0,n=Array(e);r2&&void 0!==arguments[2]?arguments[2]:{top:0,right:0,bottom:0,left:0};return Math.min(Math.abs(t-(r.left||0)-(r.right||0)),Math.abs(e-(r.top||0)-(r.bottom||0)))/2},b=function(t,e,r,n,i){var a=t.width,u=t.height,s=t.startAngle,f=t.endAngle,y=(0,c.h1)(t.cx,a,a/2),v=(0,c.h1)(t.cy,u,u/2),b=m(a,u,r),g=(0,c.h1)(t.innerRadius,b,0),x=(0,c.h1)(t.outerRadius,b,.8*b);return Object.keys(e).reduce(function(t,r){var a,u=e[r],c=u.domain,m=u.reversed;if(o()(u.range))"angleAxis"===n?a=[s,f]:"radiusAxis"===n&&(a=[g,x]),m&&(a=[a[1],a[0]]);else{var b,w=function(t){if(Array.isArray(t))return t}(b=a=u.range)||function(t,e){var r=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=r){var n,o,i,a,u=[],c=!0,l=!1;try{for(i=(r=r.call(t)).next;!(c=(n=i.call(r)).done)&&(u.push(n.value),2!==u.length);c=!0);}catch(t){l=!0,o=t}finally{try{if(!c&&null!=r.return&&(a=r.return(),Object(a)!==a))return}finally{if(l)throw o}}return u}}(b,2)||function(t,e){if(t){if("string"==typeof t)return d(t,2);var r=Object.prototype.toString.call(t).slice(8,-1);if("Object"===r&&t.constructor&&(r=t.constructor.name),"Map"===r||"Set"===r)return Array.from(t);if("Arguments"===r||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return d(t,2)}}(b,2)||function(){throw TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}();s=w[0],f=w[1]}var O=(0,l.Hq)(u,i),j=O.realScaleType,S=O.scale;S.domain(c).range(a),(0,l.zF)(S);var P=(0,l.g$)(S,p(p({},u),{},{realScaleType:j})),E=p(p(p({},u),P),{},{range:a,radius:x,realScaleType:j,scale:S,cx:y,cy:v,innerRadius:g,outerRadius:x,startAngle:s,endAngle:f});return p(p({},t),{},h({},r,E))},{})},g=function(t,e){var r=t.x,n=t.y;return Math.sqrt(Math.pow(r-e.x,2)+Math.pow(n-e.y,2))},x=function(t,e){var r=t.x,n=t.y,o=e.cx,i=e.cy,a=g({x:r,y:n},{x:o,y:i});if(a<=0)return{radius:a};var u=Math.acos((r-o)/a);return n>i&&(u=2*Math.PI-u),{radius:a,angle:180*u/Math.PI,angleInRadian:u}},w=function(t){var e=t.startAngle,r=t.endAngle,n=Math.min(Math.floor(e/360),Math.floor(r/360));return{startAngle:e-360*n,endAngle:r-360*n}},O=function(t,e){var r,n=x({x:t.x,y:t.y},e),o=n.radius,i=n.angle,a=e.innerRadius,u=e.outerRadius;if(ou)return!1;if(0===o)return!0;var c=w(e),l=c.startAngle,s=c.endAngle,f=i;if(l<=s){for(;f>s;)f-=360;for(;f=l&&f<=s}else{for(;f>l;)f-=360;for(;f=s&&f<=l}return r?p(p({},e),{},{radius:o,angle:f+360*Math.min(Math.floor(e.startAngle/360),Math.floor(e.endAngle/360))}):null},j=function(t){return(0,i.isValidElement)(t)||u()(t)||"boolean"==typeof t?"":t.className}},82944:function(t,e,r){"use strict";r.d(e,{$R:function(){return R},Bh:function(){return B},Gf:function(){return j},L6:function(){return N},NN:function(){return k},TT:function(){return M},eu:function(){return L},jf:function(){return T},rL:function(){return D},sP:function(){return A}});var n=r(13735),o=r.n(n),i=r(77571),a=r.n(i),u=r(42715),c=r.n(u),l=r(86757),s=r.n(l),f=r(28302),p=r.n(f),h=r(2265),d=r(14326),y=r(16630),v=r(46485),m=r(41637),b=["children"],g=["children"];function x(t,e){if(null==t)return{};var r,n,o=function(t,e){if(null==t)return{};var r={};for(var n in t)if(Object.prototype.hasOwnProperty.call(t,n)){if(e.indexOf(n)>=0)continue;r[n]=t[n]}return r}(t,e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(t);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(t,r)&&(o[r]=t[r])}return o}function w(t){return(w="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}var O={click:"onClick",mousedown:"onMouseDown",mouseup:"onMouseUp",mouseover:"onMouseOver",mousemove:"onMouseMove",mouseout:"onMouseOut",mouseenter:"onMouseEnter",mouseleave:"onMouseLeave",touchcancel:"onTouchCancel",touchend:"onTouchEnd",touchmove:"onTouchMove",touchstart:"onTouchStart",contextmenu:"onContextMenu",dblclick:"onDoubleClick"},j=function(t){return"string"==typeof t?t:t?t.displayName||t.name||"Component":""},S=null,P=null,E=function t(e){if(e===S&&Array.isArray(P))return P;var r=[];return h.Children.forEach(e,function(e){a()(e)||((0,d.isFragment)(e)?r=r.concat(t(e.props.children)):r.push(e))}),P=r,S=e,r};function k(t,e){var r=[],n=[];return n=Array.isArray(e)?e.map(function(t){return j(t)}):[j(e)],E(t).forEach(function(t){var e=o()(t,"type.displayName")||o()(t,"type.name");-1!==n.indexOf(e)&&r.push(t)}),r}function A(t,e){var r=k(t,e);return r&&r[0]}var M=function(t){if(!t||!t.props)return!1;var e=t.props,r=e.width,n=e.height;return!!(0,y.hj)(r)&&!(r<=0)&&!!(0,y.hj)(n)&&!(n<=0)},_=["a","altGlyph","altGlyphDef","altGlyphItem","animate","animateColor","animateMotion","animateTransform","circle","clipPath","color-profile","cursor","defs","desc","ellipse","feBlend","feColormatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence","filter","font","font-face","font-face-format","font-face-name","font-face-url","foreignObject","g","glyph","glyphRef","hkern","image","line","lineGradient","marker","mask","metadata","missing-glyph","mpath","path","pattern","polygon","polyline","radialGradient","rect","script","set","stop","style","svg","switch","symbol","text","textPath","title","tref","tspan","use","view","vkern"],T=function(t){return t&&"object"===w(t)&&"clipDot"in t},C=function(t,e,r,n){var o,i=null!==(o=null===m.ry||void 0===m.ry?void 0:m.ry[n])&&void 0!==o?o:[];return e.startsWith("data-")||!s()(t)&&(n&&i.includes(e)||m.Yh.includes(e))||r&&m.nv.includes(e)},N=function(t,e,r){if(!t||"function"==typeof t||"boolean"==typeof t)return null;var n=t;if((0,h.isValidElement)(t)&&(n=t.props),!p()(n))return null;var o={};return Object.keys(n).forEach(function(t){var i;C(null===(i=n)||void 0===i?void 0:i[t],t,e,r)&&(o[t]=n[t])}),o},D=function t(e,r){if(e===r)return!0;var n=h.Children.count(e);if(n!==h.Children.count(r))return!1;if(0===n)return!0;if(1===n)return I(Array.isArray(e)?e[0]:e,Array.isArray(r)?r[0]:r);for(var o=0;o=0)r.push(t);else if(t){var i=j(t.type),a=e[i]||{},u=a.handler,l=a.once;if(u&&(!l||!n[i])){var s=u(t,i,o);r.push(s),n[i]=!0}}}),r},B=function(t){var e=t&&t.type;return e&&O[e]?O[e]:null},R=function(t,e){return E(e).indexOf(t)}},46485:function(t,e,r){"use strict";function n(t,e){for(var r in t)if(({}).hasOwnProperty.call(t,r)&&(!({}).hasOwnProperty.call(e,r)||t[r]!==e[r]))return!1;for(var n in e)if(({}).hasOwnProperty.call(e,n)&&!({}).hasOwnProperty.call(t,n))return!1;return!0}r.d(e,{w:function(){return n}})},38569:function(t,e,r){"use strict";r.d(e,{z:function(){return l}});var n=r(22190),o=r(85355),i=r(82944);function a(t){return(a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function u(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),r.push.apply(r,n)}return r}function c(t){for(var e=1;e=0))throw Error(`invalid digits: ${t}`);if(e>15)return a;let r=10**e;return function(t){this._+=t[0];for(let e=1,n=t.length;e1e-6){if(Math.abs(f*c-l*s)>1e-6&&i){let h=r-a,d=o-u,y=c*c+l*l,v=Math.sqrt(y),m=Math.sqrt(p),b=i*Math.tan((n-Math.acos((y+p-(h*h+d*d))/(2*v*m)))/2),g=b/m,x=b/v;Math.abs(g-1)>1e-6&&this._append`L${t+g*s},${e+g*f}`,this._append`A${i},${i},0,0,${+(f*h>s*d)},${this._x1=t+x*c},${this._y1=e+x*l}`}else this._append`L${this._x1=t},${this._y1=e}`}}arc(t,e,r,a,u,c){if(t=+t,e=+e,c=!!c,(r=+r)<0)throw Error(`negative radius: ${r}`);let l=r*Math.cos(a),s=r*Math.sin(a),f=t+l,p=e+s,h=1^c,d=c?a-u:u-a;null===this._x1?this._append`M${f},${p}`:(Math.abs(this._x1-f)>1e-6||Math.abs(this._y1-p)>1e-6)&&this._append`L${f},${p}`,r&&(d<0&&(d=d%o+o),d>i?this._append`A${r},${r},0,1,${h},${t-l},${e-s}A${r},${r},0,1,${h},${this._x1=f},${this._y1=p}`:d>1e-6&&this._append`A${r},${r},0,${+(d>=n)},${h},${this._x1=t+r*Math.cos(u)},${this._y1=e+r*Math.sin(u)}`)}rect(t,e,r,n){this._append`M${this._x0=this._x1=+t},${this._y0=this._y1=+e}h${r=+r}v${+n}h${-r}Z`}toString(){return this._}}function c(t){let e=3;return t.digits=function(r){if(!arguments.length)return e;if(null==r)e=null;else{let t=Math.floor(r);if(!(t>=0))throw RangeError(`invalid digits: ${r}`);e=t}return t},()=>new u(e)}u.prototype},59121:function(t,e,r){"use strict";r.d(e,{E:function(){return i}});var n=r(99649),o=r(63497);function i(t,e){let r=(0,n.Q)(t);return isNaN(e)?(0,o.L)(t,NaN):(e&&r.setDate(r.getDate()+e),r)}},31091:function(t,e,r){"use strict";r.d(e,{z:function(){return i}});var n=r(99649),o=r(63497);function i(t,e){let r=(0,n.Q)(t);if(isNaN(e))return(0,o.L)(t,NaN);if(!e)return r;let i=r.getDate(),a=(0,o.L)(t,r.getTime());return(a.setMonth(r.getMonth()+e+1,0),i>=a.getDate())?a:(r.setFullYear(a.getFullYear(),a.getMonth(),i),r)}},63497:function(t,e,r){"use strict";function n(t,e){return t instanceof Date?new t.constructor(e):new Date(e)}r.d(e,{L:function(){return n}})},99649:function(t,e,r){"use strict";function n(t){let e=Object.prototype.toString.call(t);return t instanceof Date||"object"==typeof t&&"[object Date]"===e?new t.constructor(+t):new Date("number"==typeof t||"[object Number]"===e||"string"==typeof t||"[object String]"===e?t:NaN)}r.d(e,{Q:function(){return n}})},69398:function(t,e,r){"use strict";function n(t,e){if(!t)throw Error("Invariant failed")}r.d(e,{Z:function(){return n}})}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1112-0b9bd4ebde18e77b.js b/litellm/proxy/_experimental/out/_next/static/chunks/1112-0b9bd4ebde18e77b.js new file mode 100644 index 00000000000..ea4e968a055 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/1112-0b9bd4ebde18e77b.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1112],{41112:function(e,l,s){s.d(l,{Z:function(){return B}});var a=s(57437),t=s(2265),r=s(16312),i=s(22116),n=s(19250),o=s(4260),c=s(37592),d=s(10032),m=s(42264),x=s(43769);let{TextArea:u}=o.default,{Option:h}=c.default,g=["Development","Productivity","Learning","Security","Data & Analytics","Integration","Testing","Documentation"];var p=e=>{let{visible:l,onClose:s,accessToken:p,onSuccess:j}=e,[y]=d.Z.useForm(),[b,N]=(0,t.useState)(!1),[Z,f]=(0,t.useState)("github"),v=async e=>{if(!p){m.ZP.error("No access token available");return}if(!(0,x.$L)(e.name)){m.ZP.error("Plugin name must be kebab-case (lowercase letters, numbers, and hyphens only)");return}if(e.version&&!(0,x.Nq)(e.version)){m.ZP.error("Version must be in semantic versioning format (e.g., 1.0.0)");return}if(e.authorEmail&&!(0,x.vV)(e.authorEmail)){m.ZP.error("Invalid email format");return}if(e.homepage&&!(0,x.jv)(e.homepage)){m.ZP.error("Invalid homepage URL format");return}N(!0);try{let l={name:e.name.trim(),source:"github"===Z?{source:"github",repo:e.repo.trim()}:{source:"url",url:e.url.trim()}};e.version&&(l.version=e.version.trim()),e.description&&(l.description=e.description.trim()),(e.authorName||e.authorEmail)&&(l.author={},e.authorName&&(l.author.name=e.authorName.trim()),e.authorEmail&&(l.author.email=e.authorEmail.trim())),e.homepage&&(l.homepage=e.homepage.trim()),e.category&&(l.category=e.category),e.keywords&&(l.keywords=(0,x.jE)(e.keywords)),await (0,n.registerClaudeCodePlugin)(p,l),m.ZP.success("Plugin registered successfully"),y.resetFields(),f("github"),j(),s()}catch(e){console.error("Error registering plugin:",e),m.ZP.error("Failed to register plugin")}finally{N(!1)}},C=()=>{y.resetFields(),f("github"),s()};return(0,a.jsx)(i.Z,{title:"Add New Claude Code Plugin",open:l,onCancel:C,footer:null,width:700,className:"top-8",children:(0,a.jsxs)(d.Z,{form:y,layout:"vertical",onFinish:v,className:"mt-4",children:[(0,a.jsx)(d.Z.Item,{label:"Plugin Name",name:"name",rules:[{required:!0,message:"Please enter plugin name"},{pattern:/^[a-z0-9-]+$/,message:"Name must be kebab-case (lowercase, numbers, hyphens only)"}],tooltip:"Unique identifier in kebab-case format (e.g., my-awesome-plugin)",children:(0,a.jsx)(o.default,{placeholder:"my-awesome-plugin",className:"rounded-lg"})}),(0,a.jsx)(d.Z.Item,{label:"Source Type",name:"sourceType",initialValue:"github",rules:[{required:!0,message:"Please select source type"}],children:(0,a.jsxs)(c.default,{onChange:e=>{f(e),y.setFieldsValue({repo:void 0,url:void 0})},className:"rounded-lg",children:[(0,a.jsx)(h,{value:"github",children:"GitHub"}),(0,a.jsx)(h,{value:"url",children:"URL"})]})}),"github"===Z&&(0,a.jsx)(d.Z.Item,{label:"GitHub Repository",name:"repo",rules:[{required:!0,message:"Please enter repository"},{pattern:/^[a-zA-Z0-9_-]+\/[a-zA-Z0-9_-]+$/,message:"Repository must be in format: org/repo"}],tooltip:"Format: organization/repository (e.g., anthropics/claude-code)",children:(0,a.jsx)(o.default,{placeholder:"anthropics/claude-code",className:"rounded-lg"})}),"url"===Z&&(0,a.jsx)(d.Z.Item,{label:"Git URL",name:"url",rules:[{required:!0,message:"Please enter git URL"}],tooltip:"Full git URL to the repository",children:(0,a.jsx)(o.default,{type:"url",placeholder:"https://github.com/org/repo.git",className:"rounded-lg"})}),(0,a.jsx)(d.Z.Item,{label:"Version (Optional)",name:"version",tooltip:"Semantic version (e.g., 1.0.0)",children:(0,a.jsx)(o.default,{placeholder:"1.0.0",className:"rounded-lg"})}),(0,a.jsx)(d.Z.Item,{label:"Description (Optional)",name:"description",tooltip:"Brief description of what the plugin does",children:(0,a.jsx)(u,{rows:3,placeholder:"A plugin that helps with...",maxLength:500,className:"rounded-lg"})}),(0,a.jsx)(d.Z.Item,{label:"Category (Optional)",name:"category",tooltip:"Select a category or enter a custom one",children:(0,a.jsx)(c.default,{placeholder:"Select or type a category",allowClear:!0,showSearch:!0,optionFilterProp:"children",className:"rounded-lg",children:g.map(e=>(0,a.jsx)(h,{value:e,children:e},e))})}),(0,a.jsx)(d.Z.Item,{label:"Keywords (Optional)",name:"keywords",tooltip:"Comma-separated list of keywords for search",children:(0,a.jsx)(o.default,{placeholder:"search, web, api",className:"rounded-lg"})}),(0,a.jsx)(d.Z.Item,{label:"Author Name (Optional)",name:"authorName",tooltip:"Name of the plugin author or organization",children:(0,a.jsx)(o.default,{placeholder:"Your Name or Organization",className:"rounded-lg"})}),(0,a.jsx)(d.Z.Item,{label:"Author Email (Optional)",name:"authorEmail",rules:[{type:"email",message:"Please enter a valid email"}],tooltip:"Contact email for the plugin author",children:(0,a.jsx)(o.default,{type:"email",placeholder:"author@example.com",className:"rounded-lg"})}),(0,a.jsx)(d.Z.Item,{label:"Homepage (Optional)",name:"homepage",rules:[{type:"url",message:"Please enter a valid URL"}],tooltip:"URL to the plugin's homepage or documentation",children:(0,a.jsx)(o.default,{type:"url",placeholder:"https://example.com",className:"rounded-lg"})}),(0,a.jsx)(d.Z.Item,{className:"mb-0 mt-6",children:(0,a.jsxs)("div",{className:"flex justify-end gap-2",children:[(0,a.jsx)(r.z,{variant:"secondary",onClick:C,disabled:b,children:"Cancel"}),(0,a.jsx)(r.z,{type:"submit",loading:b,children:b?"Registering...":"Register Plugin"})]})})]})})},j=s(23639),y=s(74998),b=s(44633),N=s(86462),Z=s(49084),f=s(71594),v=s(24525),C=s(41649),w=s(78489),P=s(21626),k=s(97214),S=s(28241),_=s(58834),z=s(69552),I=s(71876),E=s(99981),A=s(63709),D=s(9114),L=e=>{let{pluginsList:l,isLoading:s,onDeleteClick:r,accessToken:i,onPluginUpdated:o,isAdmin:c,onPluginClick:d}=e,[m,u]=(0,t.useState)([{id:"created_at",desc:!0}]),[h,g]=(0,t.useState)(null),p=e=>e?new Date(e).toLocaleString():"-",L=e=>{navigator.clipboard.writeText(e),D.Z.success("Copied to clipboard!")},R=async e=>{if(i){g(e.id);try{e.enabled?(await (0,n.disableClaudeCodePlugin)(i,e.name),D.Z.success('Plugin "'.concat(e.name,'" disabled'))):(await (0,n.enableClaudeCodePlugin)(i,e.name),D.Z.success('Plugin "'.concat(e.name,'" enabled'))),o()}catch(e){D.Z.error("Failed to toggle plugin status")}finally{g(null)}}},F=[{header:"Plugin Name",accessorKey:"name",cell:e=>{let{row:l}=e,s=l.original,t=s.name||"";return(0,a.jsxs)("div",{className:"flex items-center gap-2",children:[(0,a.jsx)(E.Z,{title:t,children:(0,a.jsx)(w.Z,{size:"xs",variant:"light",className:"font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left overflow-hidden truncate min-w-[150px] justify-start",onClick:()=>d(s.id),children:t})}),(0,a.jsx)(E.Z,{title:"Copy Plugin ID",children:(0,a.jsx)(j.Z,{onClick:e=>{e.stopPropagation(),L(s.id)},className:"cursor-pointer text-gray-500 hover:text-blue-500 text-xs"})})]})}},{header:"Version",accessorKey:"version",cell:e=>{let{row:l}=e,s=l.original.version||"N/A";return(0,a.jsx)("span",{className:"text-xs text-gray-600",children:s})}},{header:"Description",accessorKey:"description",cell:e=>{let{row:l}=e,s=l.original.description||"No description";return(0,a.jsx)(E.Z,{title:s,children:(0,a.jsx)("span",{className:"text-xs text-gray-600 block max-w-[300px] truncate",children:s})})}},{header:"Category",accessorKey:"category",cell:e=>{let{row:l}=e,s=l.original.category;if(!s)return(0,a.jsx)(C.Z,{color:"gray",className:"text-xs font-normal",size:"xs",children:"Uncategorized"});let t=(0,x.LH)(s);return(0,a.jsx)(C.Z,{color:t,className:"text-xs font-normal",size:"xs",children:s})}},{header:"Enabled",accessorKey:"enabled",cell:e=>{let{row:l}=e,s=l.original;return(0,a.jsxs)("div",{className:"flex items-center gap-2",children:[(0,a.jsx)(C.Z,{color:s.enabled?"green":"gray",className:"text-xs font-normal",size:"xs",children:s.enabled?"Yes":"No"}),c&&(0,a.jsx)(E.Z,{title:s.enabled?"Disable plugin":"Enable plugin",children:(0,a.jsx)(A.Z,{size:"small",checked:s.enabled,loading:h===s.id,onChange:()=>R(s)})})]})}},{header:"Created At",accessorKey:"created_at",cell:e=>{let{row:l}=e,s=l.original;return(0,a.jsx)(E.Z,{title:s.created_at,children:(0,a.jsx)("span",{className:"text-xs",children:p(s.created_at)})})}},...c?[{header:"Actions",id:"actions",enableSorting:!1,cell:e=>{let{row:l}=e,s=l.original;return(0,a.jsx)("div",{className:"flex items-center gap-1",children:(0,a.jsx)(E.Z,{title:"Delete plugin",children:(0,a.jsx)(w.Z,{size:"xs",variant:"light",color:"red",onClick:e=>{e.stopPropagation(),r(s.name,s.name)},icon:y.Z,className:"text-red-500 hover:text-red-700 hover:bg-red-50"})})})}}]:[]],U=(0,f.b7)({data:l,columns:F,state:{sorting:m},onSortingChange:u,getCoreRowModel:(0,v.sC)(),getSortedRowModel:(0,v.tj)(),enableSorting:!0});return(0,a.jsx)("div",{className:"rounded-lg custom-border relative",children:(0,a.jsx)("div",{className:"overflow-x-auto",children:(0,a.jsxs)(P.Z,{className:"[&_td]:py-0.5 [&_th]:py-1",children:[(0,a.jsx)(_.Z,{children:U.getHeaderGroups().map(e=>(0,a.jsx)(I.Z,{children:e.headers.map(e=>(0,a.jsx)(z.Z,{className:"py-1 h-8 ".concat("actions"===e.id?"sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]":""),onClick:e.column.getCanSort()?e.column.getToggleSortingHandler():void 0,children:(0,a.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,a.jsx)("div",{className:"flex items-center",children:e.isPlaceholder?null:(0,f.ie)(e.column.columnDef.header,e.getContext())}),e.column.getCanSort()&&(0,a.jsx)("div",{className:"w-4",children:e.column.getIsSorted()?({asc:(0,a.jsx)(b.Z,{className:"h-4 w-4 text-blue-500"}),desc:(0,a.jsx)(N.Z,{className:"h-4 w-4 text-blue-500"})})[e.column.getIsSorted()]:(0,a.jsx)(Z.Z,{className:"h-4 w-4 text-gray-400"})})]})},e.id))},e.id))}),(0,a.jsx)(k.Z,{children:s?(0,a.jsx)(I.Z,{children:(0,a.jsx)(S.Z,{colSpan:F.length,className:"h-8 text-center",children:(0,a.jsx)("div",{className:"text-center text-gray-500",children:(0,a.jsx)("p",{children:"Loading..."})})})}):l&&l.length>0?U.getRowModel().rows.map(e=>(0,a.jsx)(I.Z,{className:"h-8",children:e.getVisibleCells().map(e=>(0,a.jsx)(S.Z,{className:"py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap ".concat("actions"===e.column.id?"sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]":""),children:(0,f.ie)(e.column.columnDef.cell,e.getContext())},e.id))},e.id)):(0,a.jsx)(I.Z,{children:(0,a.jsx)(S.Z,{colSpan:F.length,className:"h-8 text-center",children:(0,a.jsx)("div",{className:"text-center text-gray-500",children:(0,a.jsx)("p",{children:"No plugins found. Add one to get started."})})})})})]})})})},R=s(20347),F=s(10900),U=s(3477),O=s(12514),T=s(67101),H=s(84264),K=s(96761),V=s(10353),q=e=>{let{pluginId:l,onClose:s,accessToken:r,isAdmin:i,onPluginUpdated:o}=e,[c,d]=(0,t.useState)(null),[m,u]=(0,t.useState)(!0),[h,g]=(0,t.useState)(!1);(0,t.useEffect)(()=>{p()},[l,r]);let p=async()=>{if(r){u(!0);try{let e=await (0,n.getClaudeCodePluginDetails)(r,l);d(e.plugin)}catch(e){console.error("Error fetching plugin info:",e),D.Z.error("Failed to load plugin information")}finally{u(!1)}}},y=async()=>{if(r&&c){g(!0);try{c.enabled?(await (0,n.disableClaudeCodePlugin)(r,c.name),D.Z.success('Plugin "'.concat(c.name,'" disabled'))):(await (0,n.enableClaudeCodePlugin)(r,c.name),D.Z.success('Plugin "'.concat(c.name,'" enabled'))),o(),p()}catch(e){D.Z.error("Failed to toggle plugin status")}finally{g(!1)}}},b=e=>{navigator.clipboard.writeText(e),D.Z.success("Copied to clipboard!")};if(m)return(0,a.jsx)("div",{className:"flex items-center justify-center p-8",children:(0,a.jsx)(V.Z,{size:"large"})});if(!c)return(0,a.jsxs)("div",{className:"p-8 text-center text-gray-500",children:[(0,a.jsx)("p",{children:"Plugin not found"}),(0,a.jsx)(w.Z,{className:"mt-4",onClick:s,children:"Go Back"})]});let N=(0,x.aB)(c),Z=(0,x.OB)(c.source),f=(0,x.LH)(c.category);return(0,a.jsxs)("div",{className:"space-y-4",children:[(0,a.jsxs)("div",{className:"flex items-center gap-3 mb-6",children:[(0,a.jsx)(F.Z,{className:"h-5 w-5 cursor-pointer text-gray-500 hover:text-gray-700",onClick:s}),(0,a.jsx)("h2",{className:"text-2xl font-bold",children:c.name}),c.version&&(0,a.jsxs)(C.Z,{color:"blue",size:"xs",children:["v",c.version]}),c.category&&(0,a.jsx)(C.Z,{color:f,size:"xs",children:c.category}),(0,a.jsx)(C.Z,{color:c.enabled?"green":"gray",size:"xs",children:c.enabled?"Enabled":"Disabled"})]}),(0,a.jsx)(O.Z,{children:(0,a.jsxs)("div",{className:"flex items-center justify-between",children:[(0,a.jsxs)("div",{className:"flex-1",children:[(0,a.jsx)(H.Z,{className:"text-gray-600 text-xs mb-2",children:"Install Command"}),(0,a.jsx)("div",{className:"font-mono bg-gray-100 px-3 py-2 rounded text-sm",children:N})]}),(0,a.jsx)(E.Z,{title:"Copy install command",children:(0,a.jsx)(w.Z,{size:"xs",variant:"secondary",icon:j.Z,onClick:()=>b(N),className:"ml-4",children:"Copy"})})]})}),(0,a.jsxs)(O.Z,{children:[(0,a.jsx)(K.Z,{children:"Plugin Details"}),(0,a.jsxs)(T.Z,{className:"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6 mt-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(H.Z,{className:"text-gray-600 text-xs",children:"Plugin ID"}),(0,a.jsxs)("div",{className:"flex items-center gap-2 mt-1",children:[(0,a.jsx)(H.Z,{className:"font-mono text-xs",children:c.id}),(0,a.jsx)(j.Z,{className:"cursor-pointer text-gray-500 hover:text-blue-500 text-xs",onClick:()=>b(c.id)})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(H.Z,{className:"text-gray-600 text-xs",children:"Name"}),(0,a.jsx)(H.Z,{className:"font-semibold mt-1",children:c.name})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(H.Z,{className:"text-gray-600 text-xs",children:"Version"}),(0,a.jsx)(H.Z,{className:"font-semibold mt-1",children:c.version||"N/A"})]}),(0,a.jsxs)("div",{className:"col-span-2",children:[(0,a.jsx)(H.Z,{className:"text-gray-600 text-xs",children:"Source"}),(0,a.jsxs)("div",{className:"flex items-center gap-2 mt-1",children:[(0,a.jsx)(H.Z,{className:"font-semibold",children:(0,x.i5)(c.source)}),Z&&(0,a.jsx)("a",{href:Z,target:"_blank",rel:"noopener noreferrer",className:"text-blue-500 hover:text-blue-700",children:(0,a.jsx)(U.Z,{className:"h-4 w-4"})})]})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(H.Z,{className:"text-gray-600 text-xs",children:"Category"}),(0,a.jsx)("div",{className:"mt-1",children:c.category?(0,a.jsx)(C.Z,{color:f,size:"xs",children:c.category}):(0,a.jsx)(H.Z,{className:"text-gray-400",children:"Uncategorized"})})]}),i&&(0,a.jsxs)("div",{className:"col-span-3",children:[(0,a.jsx)(H.Z,{className:"text-gray-600 text-xs",children:"Status"}),(0,a.jsxs)("div",{className:"flex items-center gap-3 mt-2",children:[(0,a.jsx)(A.Z,{checked:c.enabled,loading:h,onChange:y}),(0,a.jsx)(H.Z,{className:"text-sm",children:c.enabled?"Plugin is enabled and visible in marketplace":"Plugin is disabled and hidden from marketplace"})]})]})]})]}),c.description&&(0,a.jsxs)(O.Z,{children:[(0,a.jsx)(K.Z,{children:"Description"}),(0,a.jsx)(H.Z,{className:"mt-2",children:c.description})]}),c.keywords&&c.keywords.length>0&&(0,a.jsxs)(O.Z,{children:[(0,a.jsx)(K.Z,{children:"Keywords"}),(0,a.jsx)("div",{className:"flex flex-wrap gap-2 mt-2",children:c.keywords.map((e,l)=>(0,a.jsx)(C.Z,{color:"gray",size:"xs",children:e},l))})]}),c.author&&(0,a.jsxs)(O.Z,{children:[(0,a.jsx)(K.Z,{children:"Author Information"}),(0,a.jsxs)(T.Z,{className:"grid grid-cols-1 sm:grid-cols-2 gap-4 mt-4",children:[c.author.name&&(0,a.jsxs)("div",{children:[(0,a.jsx)(H.Z,{className:"text-gray-600 text-xs",children:"Name"}),(0,a.jsx)(H.Z,{className:"font-semibold mt-1",children:c.author.name})]}),c.author.email&&(0,a.jsxs)("div",{children:[(0,a.jsx)(H.Z,{className:"text-gray-600 text-xs",children:"Email"}),(0,a.jsx)(H.Z,{className:"font-semibold mt-1",children:(0,a.jsx)("a",{href:"mailto:".concat(c.author.email),className:"text-blue-500 hover:text-blue-700",children:c.author.email})})]})]})]}),c.homepage&&(0,a.jsxs)(O.Z,{children:[(0,a.jsx)(K.Z,{children:"Homepage"}),(0,a.jsxs)("a",{href:c.homepage,target:"_blank",rel:"noopener noreferrer",className:"text-blue-500 hover:text-blue-700 flex items-center gap-2 mt-2",children:[c.homepage,(0,a.jsx)(U.Z,{className:"h-4 w-4"})]})]}),(0,a.jsxs)(O.Z,{children:[(0,a.jsx)(K.Z,{children:"Metadata"}),(0,a.jsxs)(T.Z,{className:"grid grid-cols-1 sm:grid-cols-2 gap-4 mt-4",children:[(0,a.jsxs)("div",{children:[(0,a.jsx)(H.Z,{className:"text-gray-600 text-xs",children:"Created At"}),(0,a.jsx)(H.Z,{className:"font-semibold mt-1",children:(0,x.ie)(c.created_at)})]}),(0,a.jsxs)("div",{children:[(0,a.jsx)(H.Z,{className:"text-gray-600 text-xs",children:"Updated At"}),(0,a.jsx)(H.Z,{className:"font-semibold mt-1",children:(0,x.ie)(c.updated_at)})]}),c.created_by&&(0,a.jsxs)("div",{className:"col-span-2",children:[(0,a.jsx)(H.Z,{className:"text-gray-600 text-xs",children:"Created By"}),(0,a.jsx)(H.Z,{className:"font-semibold mt-1",children:c.created_by})]})]})]})]})},B=e=>{let{accessToken:l,userRole:s}=e,[o,c]=(0,t.useState)([]),[d,m]=(0,t.useState)(!1),[x,u]=(0,t.useState)(!1),[h,g]=(0,t.useState)(!1),[j,y]=(0,t.useState)(null),[b,N]=(0,t.useState)(null),Z=!!s&&(0,R.tY)(s),f=async()=>{if(l){u(!0);try{let e=await (0,n.getClaudeCodePluginsList)(l,!1);console.log("Claude Code plugins: ".concat(JSON.stringify(e))),c(e.plugins)}catch(e){console.error("Error fetching Claude Code plugins:",e)}finally{u(!1)}}};(0,t.useEffect)(()=>{f()},[l]);let v=async()=>{if(j&&l){g(!0);try{await (0,n.deleteClaudeCodePlugin)(l,j.name),D.Z.success('Plugin "'.concat(j.displayName,'" deleted successfully')),f()}catch(e){console.error("Error deleting plugin:",e),D.Z.error("Failed to delete plugin")}finally{g(!1),y(null)}}};return(0,a.jsxs)("div",{className:"w-full mx-auto flex-auto overflow-y-auto m-8 p-2",children:[(0,a.jsxs)("div",{className:"flex flex-col gap-2 mb-4",children:[(0,a.jsx)("h1",{className:"text-2xl font-bold",children:"Claude Code Plugins"}),(0,a.jsxs)("p",{className:"text-sm text-gray-600",children:["Manage Claude Code marketplace plugins. Add, enable, disable, or delete plugins that will be available in your marketplace catalog. Enabled plugins will appear in the public marketplace at"," ",(0,a.jsx)("code",{className:"bg-gray-100 px-1 rounded",children:"/claude-code/marketplace.json"}),"."]}),(0,a.jsx)("div",{className:"mt-2",children:(0,a.jsx)(r.z,{onClick:()=>{b&&N(null),m(!0)},disabled:!l||!Z,children:"+ Add New Plugin"})})]}),b?(0,a.jsx)(q,{pluginId:b,onClose:()=>N(null),accessToken:l,isAdmin:Z,onPluginUpdated:f}):(0,a.jsx)(L,{pluginsList:o,isLoading:x,onDeleteClick:(e,l)=>{y({name:e,displayName:l})},accessToken:l,onPluginUpdated:f,isAdmin:Z,onPluginClick:e=>N(e)}),(0,a.jsx)(p,{visible:d,onClose:()=>{m(!1)},accessToken:l,onSuccess:()=>{f()}}),j&&(0,a.jsxs)(i.Z,{title:"Delete Plugin",open:null!==j,onOk:v,onCancel:()=>{y(null)},confirmLoading:h,okText:"Delete",okButtonProps:{danger:!0},children:[(0,a.jsxs)("p",{children:["Are you sure you want to delete plugin:"," ",(0,a.jsx)("strong",{children:j.displayName}),"?"]}),(0,a.jsx)("p",{children:"This action cannot be undone."})]})]})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1128-64fa4a41ccaf67ea.js b/litellm/proxy/_experimental/out/_next/static/chunks/1128-64fa4a41ccaf67ea.js deleted file mode 100644 index b9f027c5bee..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/1128-64fa4a41ccaf67ea.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1128],{29271:function(e,t,r){r.d(t,{Z:function(){return l}});var n=r(1119),o=r(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}},{tag:"path",attrs:{d:"M464 688a48 48 0 1096 0 48 48 0 10-96 0zm24-112h48c4.4 0 8-3.6 8-8V296c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v272c0 4.4 3.6 8 8 8z"}}]},name:"exclamation-circle",theme:"outlined"},i=r(55015),l=o.forwardRef(function(e,t){return o.createElement(i.Z,(0,n.Z)({},e,{ref:t,icon:a}))})},92403:function(e,t,r){r.d(t,{Z:function(){return l}});var n=r(1119),o=r(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M608 112c-167.9 0-304 136.1-304 304 0 70.3 23.9 135 63.9 186.5l-41.1 41.1-62.3-62.3a8.15 8.15 0 00-11.4 0l-39.8 39.8a8.15 8.15 0 000 11.4l62.3 62.3-44.9 44.9-62.3-62.3a8.15 8.15 0 00-11.4 0l-39.8 39.8a8.15 8.15 0 000 11.4l62.3 62.3-65.3 65.3a8.03 8.03 0 000 11.3l42.3 42.3c3.1 3.1 8.2 3.1 11.3 0l253.6-253.6A304.06 304.06 0 00608 720c167.9 0 304-136.1 304-304S775.9 112 608 112zm161.2 465.2C726.2 620.3 668.9 644 608 644c-60.9 0-118.2-23.7-161.2-66.8-43.1-43-66.8-100.3-66.8-161.2 0-60.9 23.7-118.2 66.8-161.2 43-43.1 100.3-66.8 161.2-66.8 60.9 0 118.2 23.7 161.2 66.8 43.1 43 66.8 100.3 66.8 161.2 0 60.9-23.7 118.2-66.8 161.2z"}}]},name:"key",theme:"outlined"},i=r(55015),l=o.forwardRef(function(e,t){return o.createElement(i.Z,(0,n.Z)({},e,{ref:t,icon:a}))})},62272:function(e,t,r){r.d(t,{Z:function(){return l}});var n=r(1119),o=r(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M574 665.4a8.03 8.03 0 00-11.3 0L446.5 781.6c-53.8 53.8-144.6 59.5-204 0-59.5-59.5-53.8-150.2 0-204l116.2-116.2c3.1-3.1 3.1-8.2 0-11.3l-39.8-39.8a8.03 8.03 0 00-11.3 0L191.4 526.5c-84.6 84.6-84.6 221.5 0 306s221.5 84.6 306 0l116.2-116.2c3.1-3.1 3.1-8.2 0-11.3L574 665.4zm258.6-474c-84.6-84.6-221.5-84.6-306 0L410.3 307.6a8.03 8.03 0 000 11.3l39.7 39.7c3.1 3.1 8.2 3.1 11.3 0l116.2-116.2c53.8-53.8 144.6-59.5 204 0 59.5 59.5 53.8 150.2 0 204L665.3 562.6a8.03 8.03 0 000 11.3l39.8 39.8c3.1 3.1 8.2 3.1 11.3 0l116.2-116.2c84.5-84.6 84.5-221.5 0-306.1zM610.1 372.3a8.03 8.03 0 00-11.3 0L372.3 598.7a8.03 8.03 0 000 11.3l39.6 39.6c3.1 3.1 8.2 3.1 11.3 0l226.4-226.4c3.1-3.1 3.1-8.2 0-11.3l-39.5-39.6z"}}]},name:"link",theme:"outlined"},i=r(55015),l=o.forwardRef(function(e,t){return o.createElement(i.Z,(0,n.Z)({},e,{ref:t,icon:a}))})},34419:function(e,t,r){r.d(t,{Z:function(){return l}});var n=r(1119),o=r(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M696 480H544V328c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8v152H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h152v152c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V544h152c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z"}},{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}}]},name:"plus-circle",theme:"outlined"},i=r(55015),l=o.forwardRef(function(e,t){return o.createElement(i.Z,(0,n.Z)({},e,{ref:t,icon:a}))})},58747:function(e,t,r){r.d(t,{Z:function(){return a}});var n=r(5853),o=r(2265);let a=e=>{var t=(0,n._T)(e,[]);return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),o.createElement("path",{d:"M11.9999 13.1714L16.9497 8.22168L18.3639 9.63589L11.9999 15.9999L5.63599 9.63589L7.0502 8.22168L11.9999 13.1714Z"}))}},4537:function(e,t,r){r.d(t,{Z:function(){return a}});var n=r(5853),o=r(2265);let a=e=>{var t=(0,n._T)(e,[]);return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},t),o.createElement("path",{d:"M12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 10.5858L9.17157 7.75736L7.75736 9.17157L10.5858 12L7.75736 14.8284L9.17157 16.2426L12 13.4142L14.8284 16.2426L16.2426 14.8284L13.4142 12L16.2426 9.17157L14.8284 7.75736L12 10.5858Z"}))}},27281:function(e,t,r){r.d(t,{Z:function(){return p}});var n=r(5853),o=r(58747),a=r(2265),i=r(4537),l=r(13241),s=r(1153),c=r(96398),u=r(51975),d=r(85238),h=r(44140);let f=(0,s.fn)("Select"),p=a.forwardRef((e,t)=>{let{defaultValue:r="",value:s,onValueChange:p,placeholder:m="Select...",disabled:b=!1,icon:v,enableClear:y=!1,required:g,children:C,name:k,error:w=!1,errorMessage:x,className:O,id:E}=e,M=(0,n._T)(e,["defaultValue","value","onValueChange","placeholder","disabled","icon","enableClear","required","children","name","error","errorMessage","className","id"]),S=(0,a.useRef)(null),N=a.Children.toArray(C),[P,j]=(0,h.Z)(r,s),R=(0,a.useMemo)(()=>{let e=a.Children.toArray(C).filter(a.isValidElement);return(0,c.sl)(e)},[C]);return a.createElement("div",{className:(0,l.q)("w-full min-w-[10rem] text-tremor-default",O)},a.createElement("div",{className:"relative"},a.createElement("select",{title:"select-hidden",required:g,className:(0,l.q)("h-full w-full absolute left-0 top-0 -z-10 opacity-0"),value:P,onChange:e=>{e.preventDefault()},name:k,disabled:b,id:E,onFocus:()=>{let e=S.current;e&&e.focus()}},a.createElement("option",{className:"hidden",value:"",disabled:!0,hidden:!0},m),N.map(e=>{let t=e.props.value,r=e.props.children;return a.createElement("option",{className:"hidden",key:t,value:t},r)})),a.createElement(u.Ri,Object.assign({as:"div",ref:t,defaultValue:P,value:P,onChange:e=>{null==p||p(e),j(e)},disabled:b,id:E},M),e=>{var t;let{value:r}=e;return a.createElement(a.Fragment,null,a.createElement(u.Y4,{ref:S,className:(0,l.q)("w-full outline-none text-left whitespace-nowrap truncate rounded-tremor-default focus:ring-2 transition duration-100 border pr-8 py-2","border-tremor-border shadow-tremor-input focus:border-tremor-brand-subtle focus:ring-tremor-brand-muted","dark:border-dark-tremor-border dark:shadow-dark-tremor-input dark:focus:border-dark-tremor-brand-subtle dark:focus:ring-dark-tremor-brand-muted",v?"pl-10":"pl-3",(0,c.um)((0,c.Uh)(r),b,w))},v&&a.createElement("span",{className:(0,l.q)("absolute inset-y-0 left-0 flex items-center ml-px pl-2.5")},a.createElement(v,{className:(0,l.q)(f("Icon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})),a.createElement("span",{className:"w-[90%] block truncate"},r&&null!==(t=R.get(r))&&void 0!==t?t:m),a.createElement("span",{className:(0,l.q)("absolute inset-y-0 right-0 flex items-center mr-3")},a.createElement(o.Z,{className:(0,l.q)(f("arrowDownIcon"),"flex-none h-5 w-5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")}))),y&&P?a.createElement("button",{type:"button",className:(0,l.q)("absolute inset-y-0 right-0 flex items-center mr-8"),onClick:e=>{e.preventDefault(),j(""),null==p||p("")}},a.createElement(i.Z,{className:(0,l.q)(f("clearIcon"),"flex-none h-4 w-4","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")})):null,a.createElement(d.u,{enter:"transition ease duration-100 transform",enterFrom:"opacity-0 -translate-y-4",enterTo:"opacity-100 translate-y-0",leave:"transition ease duration-100 transform",leaveFrom:"opacity-100 translate-y-0",leaveTo:"opacity-0 -translate-y-4"},a.createElement(u.O_,{anchor:"bottom start",className:(0,l.q)("z-10 w-[var(--button-width)] divide-y overflow-y-auto outline-none rounded-tremor-default max-h-[228px] border [--anchor-gap:4px]","bg-tremor-background border-tremor-border divide-tremor-border shadow-tremor-dropdown","dark:bg-dark-tremor-background dark:border-dark-tremor-border dark:divide-dark-tremor-border dark:shadow-dark-tremor-dropdown")},C)))})),w&&x?a.createElement("p",{className:(0,l.q)("errorMessage","text-sm text-rose-500 mt-1")},x):null)});p.displayName="Select"},67982:function(e,t,r){r.d(t,{Z:function(){return s}});var n=r(5853),o=r(13241),a=r(1153),i=r(2265);let l=(0,a.fn)("Divider"),s=i.forwardRef((e,t)=>{let{className:r,children:a}=e,s=(0,n._T)(e,["className","children"]);return i.createElement("div",Object.assign({ref:t,className:(0,o.q)(l("root"),"w-full mx-auto my-6 flex justify-between gap-3 items-center text-tremor-default","text-tremor-content","dark:text-dark-tremor-content",r)},s),a?i.createElement(i.Fragment,null,i.createElement("div",{className:(0,o.q)("w-full h-[1px] bg-tremor-border dark:bg-dark-tremor-border")}),i.createElement("div",{className:(0,o.q)("text-inherit whitespace-nowrap")},a),i.createElement("div",{className:(0,o.q)("w-full h-[1px] bg-tremor-border dark:bg-dark-tremor-border")})):i.createElement("div",{className:(0,o.q)("w-full h-[1px] bg-tremor-border dark:bg-dark-tremor-border")}))});s.displayName="Divider"},94789:function(e,t,r){r.d(t,{Z:function(){return c}});var n=r(5853),o=r(2265),a=r(26898),i=r(13241),l=r(1153);let s=(0,l.fn)("Callout"),c=o.forwardRef((e,t)=>{let{title:r,icon:c,color:u,className:d,children:h}=e,f=(0,n._T)(e,["title","icon","color","className","children"]);return o.createElement("div",Object.assign({ref:t,className:(0,i.q)(s("root"),"flex flex-col overflow-hidden rounded-tremor-default text-tremor-default border-l-4 py-3 pr-3 pl-4",u?(0,i.q)((0,l.bM)(u,a.K.background).bgColor,(0,l.bM)(u,a.K.darkBorder).borderColor,(0,l.bM)(u,a.K.darkText).textColor,"dark:bg-opacity-10 bg-opacity-10"):(0,i.q)("bg-tremor-brand-faint border-tremor-brand-emphasis text-tremor-brand-emphasis","dark:bg-dark-tremor-brand-muted/70 dark:border-dark-tremor-brand-emphasis dark:text-dark-tremor-brand-emphasis"),d)},f),o.createElement("div",{className:(0,i.q)(s("header"),"flex items-start")},c?o.createElement(c,{className:(0,i.q)(s("icon"),"flex-none h-5 w-5 mr-1.5")}):null,o.createElement("h4",{className:(0,i.q)(s("title"),"font-semibold")},r)),o.createElement("p",{className:(0,i.q)(s("body"),"overflow-y-auto",h?"mt-2":"")},h))});c.displayName="Callout"},96761:function(e,t,r){r.d(t,{Z:function(){return s}});var n=r(5853),o=r(26898),a=r(13241),i=r(1153),l=r(2265);let s=l.forwardRef((e,t)=>{let{color:r,children:s,className:c}=e,u=(0,n._T)(e,["color","children","className"]);return l.createElement("p",Object.assign({ref:t,className:(0,a.q)("font-medium text-tremor-title",r?(0,i.bM)(r,o.K.darkText).textColor:"text-tremor-content-strong dark:text-dark-tremor-content-strong",c)},u),s)});s.displayName="Title"},44140:function(e,t,r){r.d(t,{Z:function(){return o}});var n=r(2265);let o=(e,t)=>{let r=void 0!==t,[o,a]=(0,n.useState)(e);return[r?t:o,e=>{r||a(e)}]}},3810:function(e,t,r){r.d(t,{Z:function(){return j}});var n=r(2265),o=r(36760),a=r.n(o),i=r(18694),l=r(93350),s=r(53445),c=r(19722),u=r(6694),d=r(71744),h=r(93463),f=r(54558),p=r(12918),m=r(71140),b=r(99320);let v=e=>{let{paddingXXS:t,lineWidth:r,tagPaddingHorizontal:n,componentCls:o,calc:a}=e,i=a(n).sub(r).equal(),l=a(t).sub(r).equal();return{[o]:Object.assign(Object.assign({},(0,p.Wf)(e)),{display:"inline-block",height:"auto",marginInlineEnd:e.marginXS,paddingInline:i,fontSize:e.tagFontSize,lineHeight:e.tagLineHeight,whiteSpace:"nowrap",background:e.defaultBg,border:"".concat((0,h.bf)(e.lineWidth)," ").concat(e.lineType," ").concat(e.colorBorder),borderRadius:e.borderRadiusSM,opacity:1,transition:"all ".concat(e.motionDurationMid),textAlign:"start",position:"relative",["&".concat(o,"-rtl")]:{direction:"rtl"},"&, a, a:hover":{color:e.defaultColor},["".concat(o,"-close-icon")]:{marginInlineStart:l,fontSize:e.tagIconSize,color:e.colorIcon,cursor:"pointer",transition:"all ".concat(e.motionDurationMid),"&:hover":{color:e.colorTextHeading}},["&".concat(o,"-has-color")]:{borderColor:"transparent",["&, a, a:hover, ".concat(e.iconCls,"-close, ").concat(e.iconCls,"-close:hover")]:{color:e.colorTextLightSolid}},"&-checkable":{backgroundColor:"transparent",borderColor:"transparent",cursor:"pointer",["&:not(".concat(o,"-checkable-checked):hover")]:{color:e.colorPrimary,backgroundColor:e.colorFillSecondary},"&:active, &-checked":{color:e.colorTextLightSolid},"&-checked":{backgroundColor:e.colorPrimary,"&:hover":{backgroundColor:e.colorPrimaryHover}},"&:active":{backgroundColor:e.colorPrimaryActive}},"&-hidden":{display:"none"},["> ".concat(e.iconCls," + span, > span + ").concat(e.iconCls)]:{marginInlineStart:i}}),["".concat(o,"-borderless")]:{borderColor:"transparent",background:e.tagBorderlessBg}}},y=e=>{let{lineWidth:t,fontSizeIcon:r,calc:n}=e,o=e.fontSizeSM;return(0,m.IX)(e,{tagFontSize:o,tagLineHeight:(0,h.bf)(n(e.lineHeightSM).mul(o).equal()),tagIconSize:n(r).sub(n(t).mul(2)).equal(),tagPaddingHorizontal:8,tagBorderlessBg:e.defaultBg})},g=e=>({defaultBg:new f.t(e.colorFillQuaternary).onBackground(e.colorBgContainer).toHexString(),defaultColor:e.colorText});var C=(0,b.I$)("Tag",e=>v(y(e)),g),k=function(e,t){var r={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&0>t.indexOf(n)&&(r[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,n=Object.getOwnPropertySymbols(e);ot.indexOf(n[o])&&Object.prototype.propertyIsEnumerable.call(e,n[o])&&(r[n[o]]=e[n[o]]);return r};let w=n.forwardRef((e,t)=>{let{prefixCls:r,style:o,className:i,checked:l,children:s,icon:c,onChange:u,onClick:h}=e,f=k(e,["prefixCls","style","className","checked","children","icon","onChange","onClick"]),{getPrefixCls:p,tag:m}=n.useContext(d.E_),b=p("tag",r),[v,y,g]=C(b),w=a()(b,"".concat(b,"-checkable"),{["".concat(b,"-checkable-checked")]:l},null==m?void 0:m.className,i,y,g);return v(n.createElement("span",Object.assign({},f,{ref:t,style:Object.assign(Object.assign({},o),null==m?void 0:m.style),className:w,onClick:e=>{null==u||u(!l),null==h||h(e)}}),c,n.createElement("span",null,s)))});var x=r(18536);let O=e=>(0,x.Z)(e,(t,r)=>{let{textColor:n,lightBorderColor:o,lightColor:a,darkColor:i}=r;return{["".concat(e.componentCls).concat(e.componentCls,"-").concat(t)]:{color:n,background:a,borderColor:o,"&-inverse":{color:e.colorTextLightSolid,background:i,borderColor:i},["&".concat(e.componentCls,"-borderless")]:{borderColor:"transparent"}}}});var E=(0,b.bk)(["Tag","preset"],e=>O(y(e)),g);let M=(e,t,r)=>{let n="string"!=typeof r?r:r.charAt(0).toUpperCase()+r.slice(1);return{["".concat(e.componentCls).concat(e.componentCls,"-").concat(t)]:{color:e["color".concat(r)],background:e["color".concat(n,"Bg")],borderColor:e["color".concat(n,"Border")],["&".concat(e.componentCls,"-borderless")]:{borderColor:"transparent"}}}};var S=(0,b.bk)(["Tag","status"],e=>{let t=y(e);return[M(t,"success","Success"),M(t,"processing","Info"),M(t,"error","Error"),M(t,"warning","Warning")]},g),N=function(e,t){var r={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&0>t.indexOf(n)&&(r[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,n=Object.getOwnPropertySymbols(e);ot.indexOf(n[o])&&Object.prototype.propertyIsEnumerable.call(e,n[o])&&(r[n[o]]=e[n[o]]);return r};let P=n.forwardRef((e,t)=>{let{prefixCls:r,className:o,rootClassName:h,style:f,children:p,icon:m,color:b,onClose:v,bordered:y=!0,visible:g}=e,k=N(e,["prefixCls","className","rootClassName","style","children","icon","color","onClose","bordered","visible"]),{getPrefixCls:w,direction:x,tag:O}=n.useContext(d.E_),[M,P]=n.useState(!0),j=(0,i.Z)(k,["closeIcon","closable"]);n.useEffect(()=>{void 0!==g&&P(g)},[g]);let R=(0,l.o2)(b),T=(0,l.yT)(b),Z=R||T,L=Object.assign(Object.assign({backgroundColor:b&&!Z?b:void 0},null==O?void 0:O.style),f),q=w("tag",r),[z,_,F]=C(q),B=a()(q,null==O?void 0:O.className,{["".concat(q,"-").concat(b)]:Z,["".concat(q,"-has-color")]:b&&!Z,["".concat(q,"-hidden")]:!M,["".concat(q,"-rtl")]:"rtl"===x,["".concat(q,"-borderless")]:!y},o,h,_,F),I=e=>{e.stopPropagation(),null==v||v(e),e.defaultPrevented||P(!1)},[,V]=(0,s.b)((0,s.w)(e),(0,s.w)(O),{closable:!1,closeIconRender:e=>{let t=n.createElement("span",{className:"".concat(q,"-close-icon"),onClick:I},e);return(0,c.wm)(e,t,e=>({onClick:t=>{var r;null===(r=null==e?void 0:e.onClick)||void 0===r||r.call(e,t),I(t)},className:a()(null==e?void 0:e.className,"".concat(q,"-close-icon"))}))}}),H="function"==typeof k.onClick||p&&"a"===p.type,D=m||null,A=D?n.createElement(n.Fragment,null,D,p&&n.createElement("span",null,p)):p,K=n.createElement("span",Object.assign({},j,{ref:t,className:B,style:L}),A,V,R&&n.createElement(E,{key:"preset",prefixCls:q}),T&&n.createElement(S,{key:"status",prefixCls:q}));return z(H?n.createElement(u.Z,{component:"Tag"},K):K)});P.CheckableTag=w;var j=P},87769:function(e,t,r){r.d(t,{Z:function(){return n}});let n=(0,r(79205).Z)("eye-off",[["path",{d:"M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49",key:"ct8e1f"}],["path",{d:"M14.084 14.158a3 3 0 0 1-4.242-4.242",key:"151rxh"}],["path",{d:"M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143",key:"13bj9a"}],["path",{d:"m2 2 20 20",key:"1ooewy"}]])},42208:function(e,t,r){r.d(t,{Z:function(){return n}});let n=(0,r(79205).Z)("eye",[["path",{d:"M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0",key:"1nclc0"}],["circle",{cx:"12",cy:"12",r:"3",key:"1v7zrd"}]])},88906:function(e,t,r){r.d(t,{Z:function(){return n}});let n=(0,r(79205).Z)("shield",[["path",{d:"M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z",key:"oel41y"}]])},15868:function(e,t,r){r.d(t,{Z:function(){return n}});let n=(0,r(79205).Z)("square-pen",[["path",{d:"M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7",key:"1m0v6g"}],["path",{d:"M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z",key:"ohrbg2"}]])},18930:function(e,t,r){r.d(t,{Z:function(){return n}});let n=(0,r(79205).Z)("trash-2",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}],["line",{x1:"10",x2:"10",y1:"11",y2:"17",key:"1uufr5"}],["line",{x1:"14",x2:"14",y1:"11",y2:"17",key:"xtxkd"}]])},95805:function(e,t,r){r.d(t,{Z:function(){return n}});let n=(0,r(79205).Z)("users",[["path",{d:"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",key:"1yyitq"}],["path",{d:"M16 3.128a4 4 0 0 1 0 7.744",key:"16gr8j"}],["path",{d:"M22 21v-2a4 4 0 0 0-3-3.87",key:"kshegd"}],["circle",{cx:"9",cy:"7",r:"4",key:"nufk8"}]])},6337:function(e,t,r){function n(e){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}Object.defineProperty(t,"__esModule",{value:!0}),t.CopyToClipboard=void 0;var o=l(r(2265)),a=l(r(49211)),i=["text","onCopy","options","children"];function l(e){return e&&e.__esModule?e:{default:e}}function s(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,n)}return r}function c(e){for(var t=1;t=0||(o[r]=e[r]);return o}(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(n=0;n=0)&&Object.prototype.propertyIsEnumerable.call(e,r)&&(o[r]=e[r])}return o}(e,i),n=o.default.Children.only(t);return o.default.cloneElement(n,c(c({},r),{},{onClick:this.onClick}))}}],function(e,t){for(var r=0;rt!==e),this.scheduleGc(),this.#r.notify({type:"observerRemoved",mutation:this,observer:e})}optionalRemove(){this.#t.length||("pending"===this.state.status?this.scheduleGc():this.#r.remove(this))}continue(){return this.#n?.continue()??this.execute(this.state.variables)}async execute(e){let t=()=>{this.#o({type:"continue"})},r={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};this.#n=(0,a.Mz)({fn:()=>this.options.mutationFn?this.options.mutationFn(e,r):Promise.reject(Error("No mutationFn found")),onFail:(e,t)=>{this.#o({type:"failed",failureCount:e,error:t})},onPause:()=>{this.#o({type:"pause"})},onContinue:t,retry:this.options.retry??0,retryDelay:this.options.retryDelay,networkMode:this.options.networkMode,canRun:()=>this.#r.canRun(this)});let n="pending"===this.state.status,o=!this.#n.canStart();try{if(n)t();else{this.#o({type:"pending",variables:e,isPaused:o}),await this.#r.config.onMutate?.(e,this,r);let t=await this.options.onMutate?.(e,r);t!==this.state.context&&this.#o({type:"pending",context:t,variables:e,isPaused:o})}let a=await this.#n.start();return await this.#r.config.onSuccess?.(a,e,this.state.context,this,r),await this.options.onSuccess?.(a,e,this.state.context,r),await this.#r.config.onSettled?.(a,null,this.state.variables,this.state.context,this,r),await this.options.onSettled?.(a,null,e,this.state.context,r),this.#o({type:"success",data:a}),a}catch(t){try{throw await this.#r.config.onError?.(t,e,this.state.context,this,r),await this.options.onError?.(t,e,this.state.context,r),await this.#r.config.onSettled?.(void 0,t,this.state.variables,this.state.context,this,r),await this.options.onSettled?.(void 0,t,e,this.state.context,r),t}finally{this.#o({type:"error",error:t})}}finally{this.#r.runNext(this)}}#o(e){this.state=(t=>{switch(e.type){case"failed":return{...t,failureCount:e.failureCount,failureReason:e.error};case"pause":return{...t,isPaused:!0};case"continue":return{...t,isPaused:!1};case"pending":return{...t,context:e.context,data:void 0,failureCount:0,failureReason:null,error:null,isPaused:e.isPaused,status:"pending",variables:e.variables,submittedAt:Date.now()};case"success":return{...t,data:e.data,failureCount:0,failureReason:null,error:null,status:"success",isPaused:!1};case"error":return{...t,data:void 0,error:e.error,failureCount:t.failureCount+1,failureReason:e.error,isPaused:!1,status:"error"}}})(this.state),n.Vr.batch(()=>{this.#t.forEach(t=>{t.onMutationUpdate(e)}),this.#r.notify({mutation:this,type:"updated",action:e})})}};function l(){return{context:void 0,data:void 0,error:null,failureCount:0,failureReason:null,isPaused:!1,status:"idle",variables:void 0,submittedAt:0}}},21770:function(e,t,r){r.d(t,{D:function(){return u}});var n=r(2265),o=r(2894),a=r(18238),i=r(24112),l=r(45345),s=class extends i.l{#e;#a=void 0;#i;#l;constructor(e,t){super(),this.#e=e,this.setOptions(t),this.bindMethods(),this.#s()}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,l.VS)(this.options,t)||this.#e.getMutationCache().notify({type:"observerOptionsUpdated",mutation:this.#i,observer:this}),t?.mutationKey&&this.options.mutationKey&&(0,l.Ym)(t.mutationKey)!==(0,l.Ym)(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.#s(),this.#c(e)}getCurrentResult(){return this.#a}reset(){this.#i?.removeObserver(this),this.#i=void 0,this.#s(),this.#c()}mutate(e,t){return this.#l=t,this.#i?.removeObserver(this),this.#i=this.#e.getMutationCache().build(this.#e,this.options),this.#i.addObserver(this),this.#i.execute(e)}#s(){let e=this.#i?.state??(0,o.R)();this.#a={...e,isPending:"pending"===e.status,isSuccess:"success"===e.status,isError:"error"===e.status,isIdle:"idle"===e.status,mutate:this.mutate,reset:this.reset}}#c(e){a.Vr.batch(()=>{if(this.#l&&this.hasListeners()){let t=this.#a.variables,r=this.#a.context,n={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};e?.type==="success"?(this.#l.onSuccess?.(e.data,t,r,n),this.#l.onSettled?.(e.data,null,t,r,n)):e?.type==="error"&&(this.#l.onError?.(e.error,t,r,n),this.#l.onSettled?.(void 0,e.error,t,r,n))}this.listeners.forEach(e=>{e(this.#a)})})}},c=r(29827);function u(e,t){let r=(0,c.NL)(t),[o]=n.useState(()=>new s(r,e));n.useEffect(()=>{o.setOptions(e)},[o,e]);let i=n.useSyncExternalStore(n.useCallback(e=>o.subscribe(a.Vr.batchCalls(e)),[o]),()=>o.getCurrentResult(),()=>o.getCurrentResult()),u=n.useCallback((e,t)=>{o.mutate(e,t).catch(l.ZT)},[o]);if(i.error&&(0,l.L3)(o.options.throwOnError,[i.error]))throw i.error;return{...i,mutate:u,mutateAsync:i.mutate}}},85238:function(e,t,r){let n;r.d(t,{u:function(){return N}});var o=r(2265),a=r(59456),i=r(93980),l=r(25289),s=r(73389),c=r(43507),u=r(180),d=r(67561),h=r(98218),f=r(28294),p=r(95504),m=r(72468),b=r(38929);function v(e){var t;return!!(e.enter||e.enterFrom||e.enterTo||e.leave||e.leaveFrom||e.leaveTo)||(null!=(t=e.as)?t:x)!==o.Fragment||1===o.Children.count(e.children)}let y=(0,o.createContext)(null);y.displayName="TransitionContext";var g=((n=g||{}).Visible="visible",n.Hidden="hidden",n);let C=(0,o.createContext)(null);function k(e){return"children"in e?k(e.children):e.current.filter(e=>{let{el:t}=e;return null!==t.current}).filter(e=>{let{state:t}=e;return"visible"===t}).length>0}function w(e,t){let r=(0,c.E)(e),n=(0,o.useRef)([]),s=(0,l.t)(),u=(0,a.G)(),d=(0,i.z)(function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:b.l4.Hidden,o=n.current.findIndex(t=>{let{el:r}=t;return r===e});-1!==o&&((0,m.E)(t,{[b.l4.Unmount](){n.current.splice(o,1)},[b.l4.Hidden](){n.current[o].state="hidden"}}),u.microTask(()=>{var e;!k(n)&&s.current&&(null==(e=r.current)||e.call(r))}))}),h=(0,i.z)(e=>{let t=n.current.find(t=>{let{el:r}=t;return r===e});return t?"visible"!==t.state&&(t.state="visible"):n.current.push({el:e,state:"visible"}),()=>d(e,b.l4.Unmount)}),f=(0,o.useRef)([]),p=(0,o.useRef)(Promise.resolve()),v=(0,o.useRef)({enter:[],leave:[]}),y=(0,i.z)((e,r,n)=>{f.current.splice(0),t&&(t.chains.current[r]=t.chains.current[r].filter(t=>{let[r]=t;return r!==e})),null==t||t.chains.current[r].push([e,new Promise(e=>{f.current.push(e)})]),null==t||t.chains.current[r].push([e,new Promise(e=>{Promise.all(v.current[r].map(e=>{let[t,r]=e;return r})).then(()=>e())})]),"enter"===r?p.current=p.current.then(()=>null==t?void 0:t.wait.current).then(()=>n(r)):n(r)}),g=(0,i.z)((e,t,r)=>{Promise.all(v.current[t].splice(0).map(e=>{let[t,r]=e;return r})).then(()=>{var e;null==(e=f.current.shift())||e()}).then(()=>r(t))});return(0,o.useMemo)(()=>({children:n,register:h,unregister:d,onStart:y,onStop:g,wait:p,chains:v}),[h,d,n,y,g,v,p])}C.displayName="NestingContext";let x=o.Fragment,O=b.VN.RenderStrategy,E=(0,b.yV)(function(e,t){let{show:r,appear:n=!1,unmount:a=!0,...l}=e,c=(0,o.useRef)(null),h=v(e),p=(0,d.T)(...h?[c,t]:null===t?[]:[t]);(0,u.H)();let m=(0,f.oJ)();if(void 0===r&&null!==m&&(r=(m&f.ZM.Open)===f.ZM.Open),void 0===r)throw Error("A is used but it is missing a `show={true | false}` prop.");let[g,x]=(0,o.useState)(r?"visible":"hidden"),E=w(()=>{r||x("hidden")}),[S,N]=(0,o.useState)(!0),P=(0,o.useRef)([r]);(0,s.e)(()=>{!1!==S&&P.current[P.current.length-1]!==r&&(P.current.push(r),N(!1))},[P,r]);let j=(0,o.useMemo)(()=>({show:r,appear:n,initial:S}),[r,n,S]);(0,s.e)(()=>{r?x("visible"):k(E)||null===c.current||x("hidden")},[r,E]);let R={unmount:a},T=(0,i.z)(()=>{var t;S&&N(!1),null==(t=e.beforeEnter)||t.call(e)}),Z=(0,i.z)(()=>{var t;S&&N(!1),null==(t=e.beforeLeave)||t.call(e)}),L=(0,b.L6)();return o.createElement(C.Provider,{value:E},o.createElement(y.Provider,{value:j},L({ourProps:{...R,as:o.Fragment,children:o.createElement(M,{ref:p,...R,...l,beforeEnter:T,beforeLeave:Z})},theirProps:{},defaultTag:o.Fragment,features:O,visible:"visible"===g,name:"Transition"})))}),M=(0,b.yV)(function(e,t){var r,n;let{transition:a=!0,beforeEnter:l,afterEnter:c,beforeLeave:g,afterLeave:E,enter:M,enterFrom:S,enterTo:N,entered:P,leave:j,leaveFrom:R,leaveTo:T,...Z}=e,[L,q]=(0,o.useState)(null),z=(0,o.useRef)(null),_=v(e),F=(0,d.T)(..._?[z,t,q]:null===t?[]:[t]),B=null==(r=Z.unmount)||r?b.l4.Unmount:b.l4.Hidden,{show:I,appear:V,initial:H}=function(){let e=(0,o.useContext)(y);if(null===e)throw Error("A is used but it is missing a parent or .");return e}(),[D,A]=(0,o.useState)(I?"visible":"hidden"),K=function(){let e=(0,o.useContext)(C);if(null===e)throw Error("A is used but it is missing a parent or .");return e}(),{register:U,unregister:G}=K;(0,s.e)(()=>U(z),[U,z]),(0,s.e)(()=>{if(B===b.l4.Hidden&&z.current){if(I&&"visible"!==D){A("visible");return}return(0,m.E)(D,{hidden:()=>G(z),visible:()=>U(z)})}},[D,z,U,G,I,B]);let Y=(0,u.H)();(0,s.e)(()=>{if(_&&Y&&"visible"===D&&null===z.current)throw Error("Did you forget to passthrough the `ref` to the actual DOM node?")},[z,D,Y,_]);let W=H&&!V,X=V&&I&&H,J=(0,o.useRef)(!1),Q=w(()=>{J.current||(A("hidden"),G(z))},K),$=(0,i.z)(e=>{J.current=!0,Q.onStart(z,e?"enter":"leave",e=>{"enter"===e?null==l||l():"leave"===e&&(null==g||g())})}),ee=(0,i.z)(e=>{let t=e?"enter":"leave";J.current=!1,Q.onStop(z,t,e=>{"enter"===e?null==c||c():"leave"===e&&(null==E||E())}),"leave"!==t||k(Q)||(A("hidden"),G(z))});(0,o.useEffect)(()=>{_&&a||($(I),ee(I))},[I,_,a]);let et=!(!a||!_||!Y||W),[,er]=(0,h.Y)(et,L,I,{start:$,end:ee}),en=(0,b.oA)({ref:F,className:(null==(n=(0,p.A)(Z.className,X&&M,X&&S,er.enter&&M,er.enter&&er.closed&&S,er.enter&&!er.closed&&N,er.leave&&j,er.leave&&!er.closed&&R,er.leave&&er.closed&&T,!er.transition&&I&&P))?void 0:n.trim())||void 0,...(0,h.X)(er)}),eo=0;"visible"===D&&(eo|=f.ZM.Open),"hidden"===D&&(eo|=f.ZM.Closed),er.enter&&(eo|=f.ZM.Opening),er.leave&&(eo|=f.ZM.Closing);let ea=(0,b.L6)();return o.createElement(C.Provider,{value:Q},o.createElement(f.up,{value:eo},ea({ourProps:en,theirProps:Z,defaultTag:x,features:O,visible:"visible"===D,name:"Transition.Child"})))}),S=(0,b.yV)(function(e,t){let r=null!==(0,o.useContext)(y),n=null!==(0,f.oJ)();return o.createElement(o.Fragment,null,!r&&n?o.createElement(E,{ref:t,...e}):o.createElement(M,{ref:t,...e}))}),N=Object.assign(E,{Child:S,Root:E})}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1208-5caf6d9856cc3f13.js b/litellm/proxy/_experimental/out/_next/static/chunks/1208-5caf6d9856cc3f13.js new file mode 100644 index 00000000000..de5590f1965 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/1208-5caf6d9856cc3f13.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1208],{44625:function(t,e,o){o.d(e,{Z:function(){return l}});var r=o(1119),n=o(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M832 64H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-600 72h560v208H232V136zm560 480H232V408h560v208zm0 272H232V680h560v208zM304 240a40 40 0 1080 0 40 40 0 10-80 0zm0 272a40 40 0 1080 0 40 40 0 10-80 0zm0 272a40 40 0 1080 0 40 40 0 10-80 0z"}}]},name:"database",theme:"outlined"},i=o(55015),l=n.forwardRef(function(t,e){return n.createElement(i.Z,(0,r.Z)({},t,{ref:e,icon:a}))})},46783:function(t,e,o){o.d(e,{Z:function(){return l}});var r=o(1119),n=o(2265),a={icon:{tag:"svg",attrs:{viewBox:"0 0 1024 1024",focusable:"false"},children:[{tag:"path",attrs:{d:"M885.2 446.3l-.2-.8-112.2-285.1c-5-16.1-19.9-27.2-36.8-27.2H281.2c-17 0-32.1 11.3-36.9 27.6L139.4 443l-.3.7-.2.8c-1.3 4.9-1.7 9.9-1 14.8-.1 1.6-.2 3.2-.2 4.8V830a60.9 60.9 0 0060.8 60.8h627.2c33.5 0 60.8-27.3 60.9-60.8V464.1c0-1.3 0-2.6-.1-3.7.4-4.9 0-9.6-1.3-14.1zm-295.8-43l-.3 15.7c-.8 44.9-31.8 75.1-77.1 75.1-22.1 0-41.1-7.1-54.8-20.6S436 441.2 435.6 419l-.3-15.7H229.5L309 210h399.2l81.7 193.3H589.4zm-375 76.8h157.3c24.3 57.1 76 90.8 140.4 90.8 33.7 0 65-9.4 90.3-27.2 22.2-15.6 39.5-37.4 50.7-63.6h156.5V814H214.4V480.1z"}}]},name:"inbox",theme:"outlined"},i=o(55015),l=n.forwardRef(function(t,e){return n.createElement(i.Z,(0,r.Z)({},t,{ref:e,icon:a}))})},23907:function(t,e,o){o.d(e,{Z:function(){return l}});var r=o(1119),n=o(2265),a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"defs",attrs:{},children:[{tag:"style",attrs:{}}]},{tag:"path",attrs:{d:"M931.4 498.9L94.9 79.5c-3.4-1.7-7.3-2.1-11-1.2a15.99 15.99 0 00-11.7 19.3l86.2 352.2c1.3 5.3 5.2 9.6 10.4 11.3l147.7 50.7-147.6 50.7c-5.2 1.8-9.1 6-10.3 11.3L72.2 926.5c-.9 3.7-.5 7.6 1.2 10.9 3.9 7.9 13.5 11.1 21.5 7.2l836.5-417c3.1-1.5 5.6-4.1 7.2-7.1 3.9-8 .7-17.6-7.2-21.6zM170.8 826.3l50.3-205.6 295.2-101.3c2.3-.8 4.2-2.6 5-5 1.4-4.2-.8-8.7-5-10.2L221.1 403 171 198.2l628 314.9-628.2 313.2z"}}]},name:"send",theme:"outlined"},i=o(55015),l=n.forwardRef(function(t,e){return n.createElement(i.Z,(0,r.Z)({},t,{ref:e,icon:a}))})},47323:function(t,e,o){o.d(e,{Z:function(){return f}});var r=o(5853),n=o(2265),a=o(47187),i=o(7084),l=o(13241),c=o(1153),s=o(26898);let d={xs:{paddingX:"px-1.5",paddingY:"py-1.5"},sm:{paddingX:"px-1.5",paddingY:"py-1.5"},md:{paddingX:"px-2",paddingY:"py-2"},lg:{paddingX:"px-2",paddingY:"py-2"},xl:{paddingX:"px-2.5",paddingY:"py-2.5"}},u={xs:{height:"h-3",width:"w-3"},sm:{height:"h-5",width:"w-5"},md:{height:"h-5",width:"w-5"},lg:{height:"h-7",width:"w-7"},xl:{height:"h-9",width:"w-9"}},m={simple:{rounded:"",border:"",ring:"",shadow:""},light:{rounded:"rounded-tremor-default",border:"",ring:"",shadow:""},shadow:{rounded:"rounded-tremor-default",border:"border",ring:"",shadow:"shadow-tremor-card dark:shadow-dark-tremor-card"},solid:{rounded:"rounded-tremor-default",border:"border-2",ring:"ring-1",shadow:""},outlined:{rounded:"rounded-tremor-default",border:"border",ring:"ring-2",shadow:""}},g=(t,e)=>{switch(t){case"simple":return{textColor:e?(0,c.bM)(e,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:"",borderColor:"",ringColor:""};case"light":return{textColor:e?(0,c.bM)(e,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:e?(0,l.q)((0,c.bM)(e,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand-muted dark:bg-dark-tremor-brand-muted",borderColor:"",ringColor:""};case"shadow":return{textColor:e?(0,c.bM)(e,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:e?(0,l.q)((0,c.bM)(e,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:"border-tremor-border dark:border-dark-tremor-border",ringColor:""};case"solid":return{textColor:e?(0,c.bM)(e,s.K.text).textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",bgColor:e?(0,l.q)((0,c.bM)(e,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand dark:bg-dark-tremor-brand",borderColor:"border-tremor-brand-inverted dark:border-dark-tremor-brand-inverted",ringColor:"ring-tremor-ring dark:ring-dark-tremor-ring"};case"outlined":return{textColor:e?(0,c.bM)(e,s.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:e?(0,l.q)((0,c.bM)(e,s.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:e?(0,c.bM)(e,s.K.ring).borderColor:"border-tremor-brand-subtle dark:border-dark-tremor-brand-subtle",ringColor:e?(0,l.q)((0,c.bM)(e,s.K.ring).ringColor,"ring-opacity-40"):"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"}}},b=(0,c.fn)("Icon"),f=n.forwardRef((t,e)=>{let{icon:o,variant:s="simple",tooltip:f,size:p=i.u8.SM,color:h,className:v}=t,w=(0,r._T)(t,["icon","variant","tooltip","size","color","className"]),k=g(s,h),{tooltipProps:x,getReferenceProps:C}=(0,a.l)();return n.createElement("span",Object.assign({ref:(0,c.lq)([e,x.refs.setReference]),className:(0,l.q)(b("root"),"inline-flex shrink-0 items-center justify-center",k.bgColor,k.textColor,k.borderColor,k.ringColor,m[s].rounded,m[s].border,m[s].shadow,m[s].ring,d[p].paddingX,d[p].paddingY,v)},C,w),n.createElement(a.Z,Object.assign({text:f},x)),n.createElement(o,{className:(0,l.q)(b("icon"),"shrink-0",u[p].height,u[p].width)}))});f.displayName="Icon"},49804:function(t,e,o){o.d(e,{Z:function(){return s}});var r=o(5853),n=o(13241),a=o(1153),i=o(2265),l=o(9496);let c=(0,a.fn)("Col"),s=i.forwardRef((t,e)=>{let{numColSpan:o=1,numColSpanSm:a,numColSpanMd:s,numColSpanLg:d,children:u,className:m}=t,g=(0,r._T)(t,["numColSpan","numColSpanSm","numColSpanMd","numColSpanLg","children","className"]),b=(t,e)=>t&&Object.keys(e).includes(String(t))?e[t]:"";return i.createElement("div",Object.assign({ref:e,className:(0,n.q)(c("root"),(()=>{let t=b(o,l.PT),e=b(a,l.SP),r=b(s,l.VS),i=b(d,l._w);return(0,n.q)(t,e,r,i)})(),m)},g),u)});s.displayName="Col"},33866:function(t,e,o){o.d(e,{Z:function(){return R}});var r=o(2265),n=o(36760),a=o.n(n),i=o(66632),l=o(93350),c=o(19722),s=o(71744),d=o(93463),u=o(12918),m=o(18536),g=o(71140),b=o(99320);let f=new d.E4("antStatusProcessing",{"0%":{transform:"scale(0.8)",opacity:.5},"100%":{transform:"scale(2.4)",opacity:0}}),p=new d.E4("antZoomBadgeIn",{"0%":{transform:"scale(0) translate(50%, -50%)",opacity:0},"100%":{transform:"scale(1) translate(50%, -50%)"}}),h=new d.E4("antZoomBadgeOut",{"0%":{transform:"scale(1) translate(50%, -50%)"},"100%":{transform:"scale(0) translate(50%, -50%)",opacity:0}}),v=new d.E4("antNoWrapperZoomBadgeIn",{"0%":{transform:"scale(0)",opacity:0},"100%":{transform:"scale(1)"}}),w=new d.E4("antNoWrapperZoomBadgeOut",{"0%":{transform:"scale(1)"},"100%":{transform:"scale(0)",opacity:0}}),k=new d.E4("antBadgeLoadingCircle",{"0%":{transformOrigin:"50%"},"100%":{transform:"translate(50%, -50%) rotate(360deg)",transformOrigin:"50%"}}),x=t=>{let{componentCls:e,iconCls:o,antCls:r,badgeShadowSize:n,textFontSize:a,textFontSizeSM:i,statusSize:l,dotSize:c,textFontWeight:s,indicatorHeight:g,indicatorHeightSM:b,marginXS:x,calc:C}=t,y="".concat(r,"-scroll-number"),O=(0,m.Z)(t,(t,o)=>{let{darkColor:r}=o;return{["&".concat(e," ").concat(e,"-color-").concat(t)]:{background:r,["&:not(".concat(e,"-count)")]:{color:r},"a:hover &":{background:r}}}});return{[e]:Object.assign(Object.assign(Object.assign(Object.assign({},(0,u.Wf)(t)),{position:"relative",display:"inline-block",width:"fit-content",lineHeight:1,["".concat(e,"-count")]:{display:"inline-flex",justifyContent:"center",zIndex:t.indicatorZIndex,minWidth:g,height:g,color:t.badgeTextColor,fontWeight:s,fontSize:a,lineHeight:(0,d.bf)(g),whiteSpace:"nowrap",textAlign:"center",background:t.badgeColor,borderRadius:C(g).div(2).equal(),boxShadow:"0 0 0 ".concat((0,d.bf)(n)," ").concat(t.badgeShadowColor),transition:"background ".concat(t.motionDurationMid),a:{color:t.badgeTextColor},"a:hover":{color:t.badgeTextColor},"a:hover &":{background:t.badgeColorHover}},["".concat(e,"-count-sm")]:{minWidth:b,height:b,fontSize:i,lineHeight:(0,d.bf)(b),borderRadius:C(b).div(2).equal()},["".concat(e,"-multiple-words")]:{padding:"0 ".concat((0,d.bf)(t.paddingXS)),bdi:{unicodeBidi:"plaintext"}},["".concat(e,"-dot")]:{zIndex:t.indicatorZIndex,width:c,minWidth:c,height:c,background:t.badgeColor,borderRadius:"100%",boxShadow:"0 0 0 ".concat((0,d.bf)(n)," ").concat(t.badgeShadowColor)},["".concat(e,"-count, ").concat(e,"-dot, ").concat(y,"-custom-component")]:{position:"absolute",top:0,insetInlineEnd:0,transform:"translate(50%, -50%)",transformOrigin:"100% 0%",["&".concat(o,"-spin")]:{animationName:k,animationDuration:"1s",animationIterationCount:"infinite",animationTimingFunction:"linear"}},["&".concat(e,"-status")]:{lineHeight:"inherit",verticalAlign:"baseline",["".concat(e,"-status-dot")]:{position:"relative",top:-1,display:"inline-block",width:l,height:l,verticalAlign:"middle",borderRadius:"50%"},["".concat(e,"-status-success")]:{backgroundColor:t.colorSuccess},["".concat(e,"-status-processing")]:{overflow:"visible",color:t.colorInfo,backgroundColor:t.colorInfo,borderColor:"currentcolor","&::after":{position:"absolute",top:0,insetInlineStart:0,width:"100%",height:"100%",borderWidth:n,borderStyle:"solid",borderColor:"inherit",borderRadius:"50%",animationName:f,animationDuration:t.badgeProcessingDuration,animationIterationCount:"infinite",animationTimingFunction:"ease-in-out",content:'""'}},["".concat(e,"-status-default")]:{backgroundColor:t.colorTextPlaceholder},["".concat(e,"-status-error")]:{backgroundColor:t.colorError},["".concat(e,"-status-warning")]:{backgroundColor:t.colorWarning},["".concat(e,"-status-text")]:{marginInlineStart:x,color:t.colorText,fontSize:t.fontSize}}}),O),{["".concat(e,"-zoom-appear, ").concat(e,"-zoom-enter")]:{animationName:p,animationDuration:t.motionDurationSlow,animationTimingFunction:t.motionEaseOutBack,animationFillMode:"both"},["".concat(e,"-zoom-leave")]:{animationName:h,animationDuration:t.motionDurationSlow,animationTimingFunction:t.motionEaseOutBack,animationFillMode:"both"},["&".concat(e,"-not-a-wrapper")]:{["".concat(e,"-zoom-appear, ").concat(e,"-zoom-enter")]:{animationName:v,animationDuration:t.motionDurationSlow,animationTimingFunction:t.motionEaseOutBack},["".concat(e,"-zoom-leave")]:{animationName:w,animationDuration:t.motionDurationSlow,animationTimingFunction:t.motionEaseOutBack},["&:not(".concat(e,"-status)")]:{verticalAlign:"middle"},["".concat(y,"-custom-component, ").concat(e,"-count")]:{transform:"none"},["".concat(y,"-custom-component, ").concat(y)]:{position:"relative",top:"auto",display:"block",transformOrigin:"50% 50%"}},[y]:{overflow:"hidden",transition:"all ".concat(t.motionDurationMid," ").concat(t.motionEaseOutBack),["".concat(y,"-only")]:{position:"relative",display:"inline-block",height:g,transition:"all ".concat(t.motionDurationSlow," ").concat(t.motionEaseOutBack),WebkitTransformStyle:"preserve-3d",WebkitBackfaceVisibility:"hidden",["> p".concat(y,"-only-unit")]:{height:g,margin:0,WebkitTransformStyle:"preserve-3d",WebkitBackfaceVisibility:"hidden"}},["".concat(y,"-symbol")]:{verticalAlign:"top"}},"&-rtl":{direction:"rtl",["".concat(e,"-count, ").concat(e,"-dot, ").concat(y,"-custom-component")]:{transform:"translate(-50%, -50%)"}}})}},C=t=>{let{fontHeight:e,lineWidth:o,marginXS:r,colorBorderBg:n}=t,a=t.colorTextLightSolid,i=t.colorError,l=t.colorErrorHover;return(0,g.IX)(t,{badgeFontHeight:e,badgeShadowSize:o,badgeTextColor:a,badgeColor:i,badgeColorHover:l,badgeShadowColor:n,badgeProcessingDuration:"1.2s",badgeRibbonOffset:r,badgeRibbonCornerTransform:"scaleY(0.75)",badgeRibbonCornerFilter:"brightness(75%)"})},y=t=>{let{fontSize:e,lineHeight:o,fontSizeSM:r,lineWidth:n}=t;return{indicatorZIndex:"auto",indicatorHeight:Math.round(e*o)-2*n,indicatorHeightSM:e,dotSize:r/2,textFontSize:r,textFontSizeSM:r,textFontWeight:"normal",statusSize:r/2}};var O=(0,b.I$)("Badge",t=>x(C(t)),y);let j=t=>{let{antCls:e,badgeFontHeight:o,marginXS:r,badgeRibbonOffset:n,calc:a}=t,i="".concat(e,"-ribbon"),l=(0,m.Z)(t,(t,e)=>{let{darkColor:o}=e;return{["&".concat(i,"-color-").concat(t)]:{background:o,color:o}}});return{["".concat(e,"-ribbon-wrapper")]:{position:"relative"},[i]:Object.assign(Object.assign(Object.assign(Object.assign({},(0,u.Wf)(t)),{position:"absolute",top:r,padding:"0 ".concat((0,d.bf)(t.paddingXS)),color:t.colorPrimary,lineHeight:(0,d.bf)(o),whiteSpace:"nowrap",backgroundColor:t.colorPrimary,borderRadius:t.borderRadiusSM,["".concat(i,"-text")]:{color:t.badgeTextColor},["".concat(i,"-corner")]:{position:"absolute",top:"100%",width:n,height:n,color:"currentcolor",border:"".concat((0,d.bf)(a(n).div(2).equal())," solid"),transform:t.badgeRibbonCornerTransform,transformOrigin:"top",filter:t.badgeRibbonCornerFilter}}),l),{["&".concat(i,"-placement-end")]:{insetInlineEnd:a(n).mul(-1).equal(),borderEndEndRadius:0,["".concat(i,"-corner")]:{insetInlineEnd:0,borderInlineEndColor:"transparent",borderBlockEndColor:"transparent"}},["&".concat(i,"-placement-start")]:{insetInlineStart:a(n).mul(-1).equal(),borderEndStartRadius:0,["".concat(i,"-corner")]:{insetInlineStart:0,borderBlockEndColor:"transparent",borderInlineStartColor:"transparent"}},"&-rtl":{direction:"rtl"}})}};var S=(0,b.I$)(["Badge","Ribbon"],t=>j(C(t)),y);let E=t=>{let e;let{prefixCls:o,value:n,current:i,offset:l=0}=t;return l&&(e={position:"absolute",top:"".concat(l,"00%"),left:0}),r.createElement("span",{style:e,className:a()("".concat(o,"-only-unit"),{current:i})},n)};var N=t=>{let e,o;let{prefixCls:n,count:a,value:i}=t,l=Number(i),c=Math.abs(a),[s,d]=r.useState(l),[u,m]=r.useState(c),g=()=>{d(l),m(c)};if(r.useEffect(()=>{let t=setTimeout(g,1e3);return()=>clearTimeout(t)},[l]),s===l||Number.isNaN(l)||Number.isNaN(s))e=[r.createElement(E,Object.assign({},t,{key:l,current:!0}))],o={transition:"none"};else{e=[];let n=l+10,a=[];for(let t=l;t<=n;t+=1)a.push(t);let i=ut%10===s);e=(i<0?a.slice(0,d+1):a.slice(d)).map((e,o)=>r.createElement(E,Object.assign({},t,{key:e,value:e%10,offset:i<0?o-d:o,current:o===d}))),o={transform:"translateY(".concat(-function(t,e,o){let r=t,n=0;for(;(r+10)%10!==e;)r+=o,n+=o;return n}(s,l,i),"00%)")}}return r.createElement("span",{className:"".concat(n,"-only"),style:o,onTransitionEnd:g},e)},M=function(t,e){var o={};for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&0>e.indexOf(r)&&(o[r]=t[r]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols)for(var n=0,r=Object.getOwnPropertySymbols(t);ne.indexOf(r[n])&&Object.prototype.propertyIsEnumerable.call(t,r[n])&&(o[r[n]]=t[r[n]]);return o};let z=r.forwardRef((t,e)=>{let{prefixCls:o,count:n,className:i,motionClassName:l,style:d,title:u,show:m,component:g="sup",children:b}=t,f=M(t,["prefixCls","count","className","motionClassName","style","title","show","component","children"]),{getPrefixCls:p}=r.useContext(s.E_),h=p("scroll-number",o),v=Object.assign(Object.assign({},f),{"data-show":m,style:d,className:a()(h,i,l),title:u}),w=n;if(n&&Number(n)%1==0){let t=String(n).split("");w=r.createElement("bdi",null,t.map((e,o)=>r.createElement(N,{prefixCls:h,count:Number(n),value:e,key:t.length-o})))}return((null==d?void 0:d.borderColor)&&(v.style=Object.assign(Object.assign({},d),{boxShadow:"0 0 0 1px ".concat(d.borderColor," inset")})),b)?(0,c.Tm)(b,t=>({className:a()("".concat(h,"-custom-component"),null==t?void 0:t.className,l)})):r.createElement(g,Object.assign({},v,{ref:e}),w)});var B=function(t,e){var o={};for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&0>e.indexOf(r)&&(o[r]=t[r]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols)for(var n=0,r=Object.getOwnPropertySymbols(t);ne.indexOf(r[n])&&Object.prototype.propertyIsEnumerable.call(t,r[n])&&(o[r[n]]=t[r[n]]);return o};let Z=r.forwardRef((t,e)=>{var o,n,d,u,m;let{prefixCls:g,scrollNumberPrefixCls:b,children:f,status:p,text:h,color:v,count:w=null,overflowCount:k=99,dot:x=!1,size:C="default",title:y,offset:j,style:S,className:E,rootClassName:N,classNames:M,styles:Z,showZero:R=!1}=t,L=B(t,["prefixCls","scrollNumberPrefixCls","children","status","text","color","count","overflowCount","dot","size","title","offset","style","className","rootClassName","classNames","styles","showZero"]),{getPrefixCls:T,direction:I,badge:H}=r.useContext(s.E_),P=T("badge",g),[W,D,F]=O(P),q=w>k?"".concat(k,"+"):w,A="0"===q||0===q||"0"===h||0===h,X=null===w||A&&!R,_=(null!=p||null!=v)&&X,V=null!=p||!A,K=x&&!A,Y=K?"":q,G=(0,r.useMemo)(()=>((null==Y||""===Y)&&(null==h||""===h)||A&&!R)&&!K,[Y,A,R,K,h]),Q=(0,r.useRef)(w);G||(Q.current=w);let $=Q.current,J=(0,r.useRef)(Y);G||(J.current=Y);let U=J.current,tt=(0,r.useRef)(K);G||(tt.current=K);let te=(0,r.useMemo)(()=>{if(!j)return Object.assign(Object.assign({},null==H?void 0:H.style),S);let t={marginTop:j[1]};return"rtl"===I?t.left=Number.parseInt(j[0],10):t.right=-Number.parseInt(j[0],10),Object.assign(Object.assign(Object.assign({},t),null==H?void 0:H.style),S)},[I,j,S,null==H?void 0:H.style]),to=null!=y?y:"string"==typeof $||"number"==typeof $?$:void 0,tr=!G&&(0===h?R:!!h&&!0!==h),tn=tr?r.createElement("span",{className:"".concat(P,"-status-text")},h):null,ta=$&&"object"==typeof $?(0,c.Tm)($,t=>({style:Object.assign(Object.assign({},te),t.style)})):void 0,ti=(0,l.o2)(v,!1),tl=a()(null==M?void 0:M.indicator,null===(o=null==H?void 0:H.classNames)||void 0===o?void 0:o.indicator,{["".concat(P,"-status-dot")]:_,["".concat(P,"-status-").concat(p)]:!!p,["".concat(P,"-color-").concat(v)]:ti}),tc={};v&&!ti&&(tc.color=v,tc.background=v);let ts=a()(P,{["".concat(P,"-status")]:_,["".concat(P,"-not-a-wrapper")]:!f,["".concat(P,"-rtl")]:"rtl"===I},E,N,null==H?void 0:H.className,null===(n=null==H?void 0:H.classNames)||void 0===n?void 0:n.root,null==M?void 0:M.root,D,F);if(!f&&_&&(h||V||!X)){let t=te.color;return W(r.createElement("span",Object.assign({},L,{className:ts,style:Object.assign(Object.assign(Object.assign({},null==Z?void 0:Z.root),null===(d=null==H?void 0:H.styles)||void 0===d?void 0:d.root),te)}),r.createElement("span",{className:tl,style:Object.assign(Object.assign(Object.assign({},null==Z?void 0:Z.indicator),null===(u=null==H?void 0:H.styles)||void 0===u?void 0:u.indicator),tc)}),tr&&r.createElement("span",{style:{color:t},className:"".concat(P,"-status-text")},h)))}return W(r.createElement("span",Object.assign({ref:e},L,{className:ts,style:Object.assign(Object.assign({},null===(m=null==H?void 0:H.styles)||void 0===m?void 0:m.root),null==Z?void 0:Z.root)}),f,r.createElement(i.ZP,{visible:!G,motionName:"".concat(P,"-zoom"),motionAppear:!1,motionDeadline:1e3},t=>{var e,o;let{className:n}=t,i=T("scroll-number",b),l=tt.current,c=a()(null==M?void 0:M.indicator,null===(e=null==H?void 0:H.classNames)||void 0===e?void 0:e.indicator,{["".concat(P,"-dot")]:l,["".concat(P,"-count")]:!l,["".concat(P,"-count-sm")]:"small"===C,["".concat(P,"-multiple-words")]:!l&&U&&U.toString().length>1,["".concat(P,"-status-").concat(p)]:!!p,["".concat(P,"-color-").concat(v)]:ti}),s=Object.assign(Object.assign(Object.assign({},null==Z?void 0:Z.indicator),null===(o=null==H?void 0:H.styles)||void 0===o?void 0:o.indicator),te);return v&&!ti&&((s=s||{}).background=v),r.createElement(z,{prefixCls:i,show:!G,motionClassName:n,className:c,count:U,title:to,style:s,key:"scrollNumber"},ta)}),tn))});Z.Ribbon=t=>{let{className:e,prefixCls:o,style:n,color:i,children:c,text:d,placement:u="end",rootClassName:m}=t,{getPrefixCls:g,direction:b}=r.useContext(s.E_),f=g("ribbon",o),p="".concat(f,"-wrapper"),[h,v,w]=S(f,p),k=(0,l.o2)(i,!1),x=a()(f,"".concat(f,"-placement-").concat(u),{["".concat(f,"-rtl")]:"rtl"===b,["".concat(f,"-color-").concat(i)]:k},e),C={},y={};return i&&!k&&(C.background=i,y.color=i),h(r.createElement("div",{className:a()(p,m,v,w)},c,r.createElement("div",{className:a()(x,v),style:Object.assign(Object.assign({},C),n)},r.createElement("span",{className:"".concat(f,"-text")},d),r.createElement("div",{className:"".concat(f,"-corner"),style:y}))))};var R=Z},2651:function(t,e,o){o.d(e,{Z:function(){return w}});var r=o(93463),n=o(11938),a=o(70774),i=o(73602),l=o(91691),c=o(25119),s=o(37628),d=o(32417),u=o(4877),m=o(57943),g=o(12789),b=o(54558);let f=(t,e)=>new b.t(t).setA(e).toRgbString(),p=(t,e)=>new b.t(t).lighten(e).toHexString(),h=t=>{let e=(0,m.R_)(t,{theme:"dark"});return{1:e[0],2:e[1],3:e[2],4:e[3],5:e[6],6:e[5],7:e[4],8:e[6],9:e[5],10:e[4]}},v=(t,e)=>{let o=t||"#000",r=e||"#fff";return{colorBgBase:o,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:p(o,12),colorBgContainer:p(o,8),colorBgLayout:p(o,0),colorBgSpotlight:p(o,26),colorBgBlur:f(r,.04),colorBorder:p(o,26),colorBorderSecondary:p(o,19)}};var w={defaultSeed:c.u_.token,useToken:function(){let[t,e,o]=(0,l.ZP)();return{theme:t,token:e,hashId:o}},defaultAlgorithm:s.Z,darkAlgorithm:(t,e)=>{let o=Object.keys(a.M).map(e=>{let o=(0,m.R_)(t[e],{theme:"dark"});return Array.from({length:10},()=>1).reduce((t,r,n)=>(t["".concat(e,"-").concat(n+1)]=o[n],t["".concat(e).concat(n+1)]=o[n],t),{})}).reduce((t,e)=>t=Object.assign(Object.assign({},t),e),{}),r=null!=e?e:(0,s.Z)(t),n=(0,g.Z)(t,{generateColorPalettes:h,generateNeutralColorPalettes:v});return Object.assign(Object.assign(Object.assign(Object.assign({},r),o),n),{colorPrimaryBg:n.colorPrimaryBorder,colorPrimaryBgHover:n.colorPrimaryBorderHover})},compactAlgorithm:(t,e)=>{let o=null!=e?e:(0,s.Z)(t),r=o.fontSizeSM,n=o.controlHeight-4;return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},o),function(t){let{sizeUnit:e,sizeStep:o}=t,r=o-2;return{sizeXXL:e*(r+10),sizeXL:e*(r+6),sizeLG:e*(r+2),sizeMD:e*(r+2),sizeMS:e*(r+1),size:e*r,sizeSM:e*r,sizeXS:e*(r-1),sizeXXS:e*(r-1)}}(null!=e?e:t)),(0,u.Z)(r)),{controlHeight:n}),(0,d.Z)(Object.assign(Object.assign({},o),{controlHeight:n})))},getDesignToken:t=>{let e=(null==t?void 0:t.algorithm)?(0,r.jG)(t.algorithm):n.Z,o=Object.assign(Object.assign({},a.Z),null==t?void 0:t.token);return(0,r.t2)(o,{override:null==t?void 0:t.token},e,i.Z)},defaultConfig:c.u_,_internalContext:c.Mj}},10900:function(t,e,o){var r=o(2265);let n=r.forwardRef(function(t,e){return r.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:e},t),r.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M10 19l-7-7m0 0l7-7m-7 7h18"}))});e.Z=n},86462:function(t,e,o){var r=o(2265);let n=r.forwardRef(function(t,e){return r.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:e},t),r.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M19 9l-7 7-7-7"}))});e.Z=n},44633:function(t,e,o){var r=o(2265);let n=r.forwardRef(function(t,e){return r.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:e},t),r.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M5 15l7-7 7 7"}))});e.Z=n},3477:function(t,e,o){var r=o(2265);let n=r.forwardRef(function(t,e){return r.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:e},t),r.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"}))});e.Z=n},53410:function(t,e,o){var r=o(2265);let n=r.forwardRef(function(t,e){return r.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:e},t),r.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"}))});e.Z=n},91126:function(t,e,o){var r=o(2265);let n=r.forwardRef(function(t,e){return r.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:e},t),r.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"}),r.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M21 12a9 9 0 11-18 0 9 9 0 0118 0z"}))});e.Z=n},23628:function(t,e,o){var r=o(2265);let n=r.forwardRef(function(t,e){return r.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:e},t),r.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"}))});e.Z=n},49084:function(t,e,o){var r=o(2265);let n=r.forwardRef(function(t,e){return r.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:e},t),r.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"}))});e.Z=n},74998:function(t,e,o){var r=o(2265);let n=r.forwardRef(function(t,e){return r.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:e},t),r.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"}))});e.Z=n}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/137-c6f74fedf576a11b.js b/litellm/proxy/_experimental/out/_next/static/chunks/137-c6f74fedf576a11b.js deleted file mode 100644 index fe98281d76f..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/137-c6f74fedf576a11b.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[137],{1309:function(e,l,a){a.d(l,{C:function(){return t.Z}});var t=a(41649)},10137:function(e,l,a){a.d(l,{Z:function(){return lF}});var t,i,r,s,n=a(57437),o=a(2265),d=a(78489),c=a(12485),u=a(18135),m=a(35242),x=a(29706),p=a(77991),h=a(19250),g=a(57840),f=a(37592),j=a(15690),v=a(10032),y=a(3810),_=a(22116),b=a(64504);(t=r||(r={})).PresidioPII="Presidio PII",t.Bedrock="Bedrock Guardrail",t.Lakera="Lakera";let N={},w=e=>{let l={};return l.PresidioPII="Presidio PII",l.Bedrock="Bedrock Guardrail",l.Lakera="Lakera",Object.entries(e).forEach(e=>{let[a,t]=e;t&&"object"==typeof t&&"ui_friendly_name"in t&&(l[a.split("_").map((e,l)=>e.charAt(0).toUpperCase()+e.slice(1)).join("")]=t.ui_friendly_name)}),N=l,l},k=()=>Object.keys(N).length>0?N:r,C={PresidioPII:"presidio",Bedrock:"bedrock",Lakera:"lakera_v2",LitellmContentFilter:"litellm_content_filter",ToolPermission:"tool_permission"},S=e=>{Object.entries(e).forEach(e=>{let[l,a]=e;a&&"object"==typeof a&&"ui_friendly_name"in a&&(C[l.split("_").map((e,l)=>e.charAt(0).toUpperCase()+e.slice(1)).join("")]=l)})},Z=e=>!!e&&"Presidio PII"===k()[e],P=e=>!!e&&"LiteLLM Content Filter"===k()[e],A="../ui/assets/logos/",O={"Presidio PII":"".concat(A,"presidio.png"),"Bedrock Guardrail":"".concat(A,"bedrock.svg"),Lakera:"".concat(A,"lakeraai.jpeg"),"Azure Content Safety Prompt Shield":"".concat(A,"presidio.png"),"Azure Content Safety Text Moderation":"".concat(A,"presidio.png"),"Aporia AI":"".concat(A,"aporia.png"),"PANW Prisma AIRS":"".concat(A,"palo_alto_networks.jpeg"),"Noma Security":"".concat(A,"noma_security.png"),"Javelin Guardrails":"".concat(A,"javelin.png"),"Pillar Guardrail":"".concat(A,"pillar.jpeg"),"Google Cloud Model Armor":"".concat(A,"google.svg"),"Guardrails AI":"".concat(A,"guardrails_ai.jpeg"),"Lasso Guardrail":"".concat(A,"lasso.png"),"Pangea Guardrail":"".concat(A,"pangea.png"),"AIM Guardrail":"".concat(A,"aim_security.jpeg"),"OpenAI Moderation":"".concat(A,"openai_small.svg"),EnkryptAI:"".concat(A,"enkrypt_ai.avif"),"Prompt Security":"".concat(A,"prompt_security.png"),"LiteLLM Content Filter":"".concat(A,"litellm_logo.jpg")},I=e=>{if(!e)return{logo:"",displayName:"-"};let l=Object.keys(C).find(l=>C[l].toLowerCase()===e.toLowerCase());if(!l)return{logo:"",displayName:e};let a=k()[l];return{logo:O[a]||"",displayName:a||e}};var L=a(99981),T=a(5545),z=a(4156),E=a(97416),B=a(8881),M=a(10798),F=a(49638);let{Text:G}=g.default,{Option:K}=f.default,D=e=>e.replace(/_/g," "),R=e=>{switch(e){case"MASK":return(0,n.jsx)(E.Z,{style:{marginRight:4}});case"BLOCK":return(0,n.jsx)(B.Z,{style:{marginRight:4}});default:return null}},J=e=>{let{categories:l,selectedCategories:a,onChange:t}=e;return(0,n.jsxs)("div",{children:[(0,n.jsxs)("div",{className:"flex items-center mb-2",children:[(0,n.jsx)(M.Z,{className:"text-gray-500 mr-1"}),(0,n.jsx)(G,{className:"text-gray-500 font-medium",children:"Filter by category"})]}),(0,n.jsx)(f.default,{mode:"multiple",placeholder:"Select categories to filter by",style:{width:"100%"},onChange:t,value:a,allowClear:!0,showSearch:!0,optionFilterProp:"children",className:"mb-4",tagRender:e=>(0,n.jsx)(y.Z,{color:"blue",closable:e.closable,onClose:e.onClose,className:"mr-2 mb-2",children:e.label}),children:l.map(e=>(0,n.jsx)(K,{value:e.category,children:e.category},e.category))})]})},V=e=>{let{onSelectAll:l,onUnselectAll:a,hasSelectedEntities:t}=e;return(0,n.jsxs)("div",{className:"bg-gray-50 p-5 rounded-lg mb-6 border border-gray-200 shadow-sm",children:[(0,n.jsxs)("div",{className:"flex items-center justify-between mb-4",children:[(0,n.jsxs)("div",{className:"flex items-center",children:[(0,n.jsx)(G,{strong:!0,className:"text-gray-700 text-base",children:"Quick Actions"}),(0,n.jsx)(L.Z,{title:"Apply action to all PII types at once",children:(0,n.jsx)("div",{className:"ml-2 text-gray-400 cursor-help text-xs",children:"ⓘ"})})]}),(0,n.jsx)(T.ZP,{color:"danger",variant:"outlined",onClick:a,disabled:!t,icon:(0,n.jsx)(F.Z,{}),children:"Unselect All"})]}),(0,n.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,n.jsx)(T.ZP,{color:"primary",variant:"outlined",onClick:()=>l("MASK"),className:"h-10",block:!0,icon:(0,n.jsx)(E.Z,{}),children:"Select All & Mask"}),(0,n.jsx)(T.ZP,{color:"danger",variant:"outlined",onClick:()=>l("BLOCK"),className:"h-10 hover:bg-red-100",block:!0,icon:(0,n.jsx)(B.Z,{}),children:"Select All & Block"})]})]})},U=e=>{let{entities:l,selectedEntities:a,selectedActions:t,actions:i,onEntitySelect:r,onActionSelect:s,entityToCategoryMap:o}=e;return(0,n.jsxs)("div",{className:"border rounded-lg overflow-hidden shadow-sm",children:[(0,n.jsxs)("div",{className:"bg-gray-50 px-5 py-3 border-b flex",children:[(0,n.jsx)(G,{strong:!0,className:"flex-1 text-gray-700",children:"PII Type"}),(0,n.jsx)(G,{strong:!0,className:"w-32 text-right text-gray-700",children:"Action"})]}),(0,n.jsx)("div",{className:"max-h-[400px] overflow-y-auto",children:0===l.length?(0,n.jsx)("div",{className:"py-10 text-center text-gray-500",children:"No PII types match your filter criteria"}):l.map(e=>(0,n.jsxs)("div",{className:"px-5 py-3 flex items-center justify-between hover:bg-gray-50 border-b ".concat(a.includes(e)?"bg-blue-50":""),children:[(0,n.jsxs)("div",{className:"flex items-center flex-1",children:[(0,n.jsx)(z.Z,{checked:a.includes(e),onChange:()=>r(e),className:"mr-3"}),(0,n.jsx)(G,{className:a.includes(e)?"font-medium text-gray-900":"text-gray-700",children:D(e)}),o.get(e)&&(0,n.jsx)(y.Z,{className:"ml-2 text-xs",color:"blue",children:o.get(e)})]}),(0,n.jsx)("div",{className:"w-32",children:(0,n.jsx)(f.default,{value:a.includes(e)&&t[e]||"MASK",onChange:l=>s(e,l),style:{width:120},disabled:!a.includes(e),className:"".concat(a.includes(e)?"":"opacity-50"),dropdownMatchSelectWidth:!1,children:i.map(e=>(0,n.jsx)(K,{value:e,children:(0,n.jsxs)("div",{className:"flex items-center",children:[R(e),e]})},e))})})]},e))})]})},{Title:W,Text:Y}=g.default;var q=e=>{let{entities:l,actions:a,selectedEntities:t,selectedActions:i,onEntitySelect:r,onActionSelect:s,entityCategories:d=[]}=e,[c,u]=(0,o.useState)([]),m=new Map;d.forEach(e=>{e.entities.forEach(l=>{m.set(l,e.category)})});let x=l.filter(e=>0===c.length||c.includes(m.get(e)||""));return(0,n.jsxs)("div",{className:"pii-configuration",children:[(0,n.jsxs)("div",{className:"flex justify-between items-center mb-5",children:[(0,n.jsx)("div",{className:"flex items-center",children:(0,n.jsx)(W,{level:4,className:"!m-0 font-semibold text-gray-800",children:"Configure PII Protection"})}),(0,n.jsxs)(Y,{className:"text-gray-500",children:[t.length," items selected"]})]}),(0,n.jsxs)("div",{className:"mb-6",children:[(0,n.jsx)(J,{categories:d,selectedCategories:c,onChange:u}),(0,n.jsx)(V,{onSelectAll:e=>{l.forEach(l=>{t.includes(l)||r(l),s(l,e)})},onUnselectAll:()=>{t.forEach(e=>{r(e)})},hasSelectedEntities:t.length>0})]}),(0,n.jsx)(U,{entities:x,selectedEntities:t,selectedActions:i,actions:a,onEntitySelect:r,onActionSelect:s,entityToCategoryMap:m})]})},H=a(10353),$=a(31283),Q=a(24199),X=e=>{var l;let{selectedProvider:a,accessToken:t,providerParams:i=null,value:r=null}=e,[s,d]=(0,o.useState)(!1),[c,u]=(0,o.useState)(i),[m,x]=(0,o.useState)(null);if((0,o.useEffect)(()=>{if(i){u(i);return}let e=async()=>{if(t){d(!0),x(null);try{let e=await (0,h.getGuardrailProviderSpecificParams)(t);console.log("Provider params API response:",e),u(e),w(e),S(e)}catch(e){console.error("Error fetching provider params:",e),x("Failed to load provider parameters")}finally{d(!1)}}};i||e()},[t,i]),!a)return null;if(s)return(0,n.jsx)(H.Z,{tip:"Loading provider parameters..."});if(m)return(0,n.jsx)("div",{className:"text-red-500",children:m});let p=null===(l=C[a])||void 0===l?void 0:l.toLowerCase(),g=c&&c[p];if(console.log("Provider key:",p),console.log("Provider fields:",g),!g||0===Object.keys(g).length)return(0,n.jsx)("div",{children:"No configuration fields available for this provider."});console.log("Value:",r);let j=new Set(["patterns","blocked_words","blocked_words_file","categories","severity_threshold","pattern_redaction_format","keyword_redaction_tag"]),y=P(a),_=function(e){let l=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",a=arguments.length>2?arguments[2]:void 0;return Object.entries(e).map(e=>{let[t,i]=e,s=l?"".concat(l,".").concat(t):t,o=a?a[t]:null==r?void 0:r[t];return(console.log("Field value:",o),"ui_friendly_name"===t||"optional_params"===t&&"nested"===i.type&&i.fields||y&&j.has(t))?null:"nested"===i.type&&i.fields?(0,n.jsxs)("div",{children:[(0,n.jsx)("div",{className:"mb-2 font-medium",children:t}),(0,n.jsx)("div",{className:"ml-4 border-l-2 border-gray-200 pl-4",children:_(i.fields,s,o)})]},s):(0,n.jsx)(v.Z.Item,{name:s,label:t,tooltip:i.description,rules:i.required?[{required:!0,message:"".concat(t," is required")}]:void 0,children:"select"===i.type&&i.options?(0,n.jsx)(f.default,{placeholder:i.description,defaultValue:o||i.default_value,children:i.options.map(e=>(0,n.jsx)(f.default.Option,{value:e,children:e},e))}):"multiselect"===i.type&&i.options?(0,n.jsx)(f.default,{mode:"multiple",placeholder:i.description,defaultValue:o||i.default_value,children:i.options.map(e=>(0,n.jsx)(f.default.Option,{value:e,children:e},e))}):"bool"===i.type||"boolean"===i.type?(0,n.jsxs)(f.default,{placeholder:i.description,defaultValue:void 0!==o?String(o):i.default_value,children:[(0,n.jsx)(f.default.Option,{value:"true",children:"True"}),(0,n.jsx)(f.default.Option,{value:"false",children:"False"})]}):"number"===i.type?(0,n.jsx)(Q.Z,{step:1,width:400,placeholder:i.description,defaultValue:void 0!==o?Number(o):void 0}):t.includes("password")||t.includes("secret")||t.includes("key")?(0,n.jsx)($.o,{placeholder:i.description,type:"password",defaultValue:o||""}):(0,n.jsx)($.o,{placeholder:i.description,type:"text",defaultValue:o||""})},s)})};return(0,n.jsx)(n.Fragment,{children:_(g)})};let{Title:ee}=g.default,el=e=>{let{field:l,fieldKey:a,fullFieldKey:t,value:i}=e,[r,s]=o.useState([]),[d,c]=o.useState(l.dict_key_options||[]);o.useEffect(()=>{if(i&&"object"==typeof i){let e=Object.keys(i);s(e.map(e=>({key:e,id:"".concat(e,"_").concat(Date.now(),"_").concat(Math.random())}))),c((l.dict_key_options||[]).filter(l=>!e.includes(l)))}},[i,l.dict_key_options]);let u=e=>{e&&(s([...r,{key:e,id:"".concat(e,"_").concat(Date.now())}]),c(d.filter(l=>l!==e)))},m=(e,l)=>{s(r.filter(l=>l.id!==e)),c([...d,l].sort())};return(0,n.jsxs)("div",{className:"space-y-3",children:[r.map(e=>(0,n.jsxs)("div",{className:"flex items-center space-x-3 p-3 border rounded-lg",children:[(0,n.jsx)("div",{className:"w-24 font-medium text-sm",children:e.key}),(0,n.jsx)("div",{className:"flex-1",children:(0,n.jsx)(v.Z.Item,{name:Array.isArray(t)?[...t,e.key]:[t,e.key],style:{marginBottom:0},initialValue:i&&"object"==typeof i?i[e.key]:void 0,normalize:"number"===l.dict_value_type?e=>{if(null==e||""===e)return;let l=Number(e);return isNaN(l)?e:l}:void 0,children:"number"===l.dict_value_type?(0,n.jsx)(Q.Z,{step:1,width:200,placeholder:"Enter ".concat(e.key," value")}):"boolean"===l.dict_value_type?(0,n.jsxs)(f.default,{placeholder:"Select ".concat(e.key," value"),children:[(0,n.jsx)(f.default.Option,{value:!0,children:"True"}),(0,n.jsx)(f.default.Option,{value:!1,children:"False"})]}):(0,n.jsx)($.o,{placeholder:"Enter ".concat(e.key," value"),type:"text"})})}),(0,n.jsx)("button",{type:"button",className:"text-red-500 hover:text-red-700 text-sm",onClick:()=>m(e.id,e.key),children:"Remove"})]},e.id)),d.length>0&&(0,n.jsxs)("div",{className:"flex items-center space-x-3 mt-2",children:[(0,n.jsx)(f.default,{placeholder:"Select category to configure",style:{width:200},onSelect:e=>e&&u(e),value:void 0,children:d.map(e=>(0,n.jsx)(f.default.Option,{value:e,children:e},e))}),(0,n.jsx)("span",{className:"text-sm text-gray-500",children:"Select a category to add threshold configuration"})]})]})};var ea=e=>{let{optionalParams:l,parentFieldKey:a,values:t}=e,i=(e,l)=>{let i="".concat(a,".").concat(e),r=null==t?void 0:t[e];return(console.log("value",r),"dict"===l.type&&l.dict_key_options)?(0,n.jsxs)("div",{className:"mb-8 p-6 bg-gray-50 rounded-lg border border-gray-200",children:[(0,n.jsx)("div",{className:"mb-4 font-medium text-gray-900 text-base",children:e}),(0,n.jsx)("p",{className:"text-sm text-gray-600 mb-4",children:l.description}),(0,n.jsx)(el,{field:l,fieldKey:e,fullFieldKey:[a,e],value:r})]},i):(0,n.jsx)("div",{className:"mb-8 p-6 bg-white rounded-lg border border-gray-200 shadow-sm",children:(0,n.jsx)(v.Z.Item,{name:[a,e],label:(0,n.jsxs)("div",{className:"mb-2",children:[(0,n.jsx)("div",{className:"font-medium text-gray-900 text-base",children:e}),(0,n.jsx)("p",{className:"text-sm text-gray-600 mt-1",children:l.description})]}),rules:l.required?[{required:!0,message:"".concat(e," is required")}]:void 0,className:"mb-0",initialValue:void 0!==r?r:l.default_value,normalize:"number"===l.type?e=>{if(null==e||""===e)return;let l=Number(e);return isNaN(l)?e:l}:void 0,children:"select"===l.type&&l.options?(0,n.jsx)(f.default,{placeholder:l.description,children:l.options.map(e=>(0,n.jsx)(f.default.Option,{value:e,children:e},e))}):"multiselect"===l.type&&l.options?(0,n.jsx)(f.default,{mode:"multiple",placeholder:l.description,children:l.options.map(e=>(0,n.jsx)(f.default.Option,{value:e,children:e},e))}):"bool"===l.type||"boolean"===l.type?(0,n.jsxs)(f.default,{placeholder:l.description,children:[(0,n.jsx)(f.default.Option,{value:"true",children:"True"}),(0,n.jsx)(f.default.Option,{value:"false",children:"False"})]}):"number"===l.type?(0,n.jsx)(Q.Z,{step:1,width:400,placeholder:l.description}):e.includes("password")||e.includes("secret")||e.includes("key")?(0,n.jsx)($.o,{placeholder:l.description,type:"password"}):(0,n.jsx)($.o,{placeholder:l.description,type:"text"})})},i)};return l.fields&&0!==Object.keys(l.fields).length?(0,n.jsxs)("div",{className:"guardrail-optional-params",children:[(0,n.jsxs)("div",{className:"mb-8 pb-4 border-b border-gray-100",children:[(0,n.jsx)(ee,{level:3,className:"mb-2 font-semibold text-gray-900",children:"Optional Parameters"}),(0,n.jsx)("p",{className:"text-gray-600 text-sm",children:l.description||"Configure additional settings for this guardrail provider"})]}),(0,n.jsx)("div",{className:"space-y-8",children:Object.entries(l.fields).map(e=>{let[l,a]=e;return i(l,a)})})]}):null},et=a(9114),ei=a(5945),er=a(58760),es=a(65319),en=a(96473),eo=a(3632),ed=a(16312);let{Text:ec}=g.default,{Option:eu}=f.default;var em=e=>{let{visible:l,prebuiltPatterns:a,categories:t,selectedPatternName:i,patternAction:r,onPatternNameChange:s,onActionChange:o,onAdd:d,onCancel:c}=e;return(0,n.jsxs)(_.Z,{title:"Add prebuilt pattern",open:l,onCancel:c,footer:null,width:800,children:[(0,n.jsxs)(er.Z,{direction:"vertical",style:{width:"100%"},size:"large",children:[(0,n.jsxs)("div",{children:[(0,n.jsx)(ec,{strong:!0,children:"Pattern type"}),(0,n.jsx)(f.default,{placeholder:"Choose pattern type",value:i,onChange:s,style:{width:"100%",marginTop:8},showSearch:!0,filterOption:(e,l)=>{let t=a.find(e=>e.name===(null==l?void 0:l.value));return!!t&&(t.display_name.toLowerCase().includes(e.toLowerCase())||t.name.toLowerCase().includes(e.toLowerCase()))},children:t.map(e=>{let l=a.filter(l=>l.category===e);return 0===l.length?null:(0,n.jsx)(f.default.OptGroup,{label:e,children:l.map(e=>(0,n.jsx)(eu,{value:e.name,children:e.display_name},e.name))},e)})})]}),(0,n.jsxs)("div",{children:[(0,n.jsx)(ec,{strong:!0,children:"Action"}),(0,n.jsx)(ec,{type:"secondary",style:{display:"block",marginTop:4,marginBottom:8},children:"Choose what action the guardrail should take when this pattern is detected"}),(0,n.jsxs)(f.default,{value:r,onChange:o,style:{width:"100%"},children:[(0,n.jsx)(eu,{value:"BLOCK",children:"Block"}),(0,n.jsx)(eu,{value:"MASK",children:"Mask"})]})]})]}),(0,n.jsxs)("div",{style:{display:"flex",justifyContent:"flex-end",gap:"8px",marginTop:"24px"},children:[(0,n.jsx)(ed.z,{variant:"secondary",onClick:c,children:"Cancel"}),(0,n.jsx)(ed.z,{onClick:d,children:"Add"})]})]})};let{Text:ex}=g.default,{Option:ep}=f.default;var eh=e=>{let{visible:l,patternName:a,patternRegex:t,patternAction:i,onNameChange:r,onRegexChange:s,onActionChange:o,onAdd:d,onCancel:c}=e;return(0,n.jsxs)(_.Z,{title:"Add custom regex pattern",open:l,onCancel:c,footer:null,width:800,children:[(0,n.jsxs)(er.Z,{direction:"vertical",style:{width:"100%"},size:"large",children:[(0,n.jsxs)("div",{children:[(0,n.jsx)(ex,{strong:!0,children:"Pattern name"}),(0,n.jsx)(b.o,{placeholder:"e.g., internal_id, employee_code",value:a,onValueChange:r,style:{marginTop:8}})]}),(0,n.jsxs)("div",{children:[(0,n.jsx)(ex,{strong:!0,children:"Regex pattern"}),(0,n.jsx)(b.o,{placeholder:"e.g., ID-[0-9]{6}",value:t,onValueChange:s,style:{marginTop:8}}),(0,n.jsx)(ex,{type:"secondary",style:{fontSize:12},children:"Enter a valid regular expression to match sensitive data"})]}),(0,n.jsxs)("div",{children:[(0,n.jsx)(ex,{strong:!0,children:"Action"}),(0,n.jsx)(ex,{type:"secondary",style:{display:"block",marginTop:4,marginBottom:8},children:"Choose what action the guardrail should take when this pattern is detected"}),(0,n.jsxs)(f.default,{value:i,onChange:o,style:{width:"100%"},children:[(0,n.jsx)(ep,{value:"BLOCK",children:"Block"}),(0,n.jsx)(ep,{value:"MASK",children:"Mask"})]})]})]}),(0,n.jsxs)("div",{style:{display:"flex",justifyContent:"flex-end",gap:"8px",marginTop:"24px"},children:[(0,n.jsx)(b.z,{variant:"secondary",onClick:c,children:"Cancel"}),(0,n.jsx)(b.z,{onClick:d,children:"Add"})]})]})},eg=a(49566),ef=a(16853);let{Text:ej}=g.default,{Option:ev}=f.default;var ey=e=>{let{visible:l,keyword:a,action:t,description:i,onKeywordChange:r,onActionChange:s,onDescriptionChange:o,onAdd:c,onCancel:u}=e;return(0,n.jsxs)(_.Z,{title:"Add blocked keyword",open:l,onCancel:u,footer:null,width:800,children:[(0,n.jsxs)(er.Z,{direction:"vertical",style:{width:"100%"},size:"large",children:[(0,n.jsxs)("div",{children:[(0,n.jsx)(ej,{strong:!0,children:"Keyword"}),(0,n.jsx)(eg.Z,{placeholder:"Enter sensitive keyword or phrase",value:a,onValueChange:r,style:{marginTop:8}})]}),(0,n.jsxs)("div",{children:[(0,n.jsx)(ej,{strong:!0,children:"Action"}),(0,n.jsx)(ej,{type:"secondary",style:{display:"block",marginTop:4,marginBottom:8},children:"Choose what action the guardrail should take when this keyword is detected"}),(0,n.jsxs)(f.default,{value:t,onChange:s,style:{width:"100%"},children:[(0,n.jsx)(ev,{value:"BLOCK",children:"Block"}),(0,n.jsx)(ev,{value:"MASK",children:"Mask"})]})]}),(0,n.jsxs)("div",{children:[(0,n.jsx)(ej,{strong:!0,children:"Description (optional)"}),(0,n.jsx)(ef.Z,{placeholder:"Explain why this keyword is sensitive",value:i,onValueChange:o,rows:3,style:{marginTop:8}})]})]}),(0,n.jsxs)("div",{style:{display:"flex",justifyContent:"flex-end",gap:"8px",marginTop:"24px"},children:[(0,n.jsx)(d.Z,{variant:"secondary",onClick:u,children:"Cancel"}),(0,n.jsx)(d.Z,{onClick:c,children:"Add"})]})]})},e_=a(56609),eb=a(26349);let{Text:eN}=g.default,{Option:ew}=f.default;var ek=e=>{let{patterns:l,onActionChange:a,onRemove:t}=e,i=[{title:"Type",dataIndex:"type",key:"type",width:100,render:e=>(0,n.jsx)(y.Z,{color:"prebuilt"===e?"blue":"green",children:"prebuilt"===e?"Prebuilt":"Custom"})},{title:"Pattern name",dataIndex:"name",key:"name",render:(e,l)=>l.display_name||l.name},{title:"Regex pattern",dataIndex:"pattern",key:"pattern",render:e=>e?(0,n.jsxs)(eN,{code:!0,style:{fontSize:12},children:[e.substring(0,40),"..."]}):"-"},{title:"Action",dataIndex:"action",key:"action",width:150,render:(e,l)=>(0,n.jsxs)(f.default,{value:e,onChange:e=>a(l.id,e),style:{width:120},size:"small",children:[(0,n.jsx)(ew,{value:"BLOCK",children:"Block"}),(0,n.jsx)(ew,{value:"MASK",children:"Mask"})]})},{title:"",key:"actions",width:100,render:(e,l)=>(0,n.jsx)(ed.z,{type:"button",variant:"light",color:"red",size:"xs",icon:eb.Z,onClick:()=>t(l.id),children:"Delete"})}];return 0===l.length?(0,n.jsx)("div",{style:{textAlign:"center",padding:"40px 0",color:"#999"},children:"No patterns added."}):(0,n.jsx)(e_.Z,{dataSource:l,columns:i,rowKey:"id",pagination:!1,size:"small"})};let{Text:eC}=g.default,{Option:eS}=f.default;var eZ=e=>{let{keywords:l,onActionChange:a,onRemove:t}=e,i=[{title:"Keyword",dataIndex:"keyword",key:"keyword"},{title:"Action",dataIndex:"action",key:"action",width:150,render:(e,l)=>(0,n.jsxs)(f.default,{value:e,onChange:e=>a(l.id,"action",e),style:{width:120},size:"small",children:[(0,n.jsx)(eS,{value:"BLOCK",children:"Block"}),(0,n.jsx)(eS,{value:"MASK",children:"Mask"})]})},{title:"Description",dataIndex:"description",key:"description",render:e=>e||"-"},{title:"",key:"actions",width:100,render:(e,l)=>(0,n.jsx)(ed.z,{type:"button",variant:"light",color:"red",size:"xs",icon:eb.Z,onClick:()=>t(l.id),children:"Delete"})}];return 0===l.length?(0,n.jsx)("div",{style:{textAlign:"center",padding:"40px 0",color:"#999"},children:"No keywords added."}):(0,n.jsx)(e_.Z,{dataSource:l,columns:i,rowKey:"id",pagination:!1,size:"small"})},eP=a(44851),eA=a(38434);let{Title:eO,Text:eI}=g.default,{Option:eL}=f.default,{Panel:eT}=eP.default;var ez=e=>{var l;let{availableCategories:a,selectedCategories:t,onCategoryAdd:i,onCategoryRemove:r,onCategoryUpdate:s,accessToken:d}=e,[c,u]=o.useState(""),[m,x]=o.useState({}),[p,g]=o.useState({}),[j,v]=o.useState([]),[_,b]=o.useState(""),[N,w]=o.useState(!1),k=async e=>{if(d&&!m[e]){g(l=>({...l,[e]:!0}));try{let l=await (0,h.getCategoryYaml)(d,e);x(a=>({...a,[e]:l.yaml_content}))}catch(l){console.error("Failed to fetch YAML for category ".concat(e,":"),l)}finally{g(l=>({...l,[e]:!1}))}}};o.useEffect(()=>{if(c&&d){let e=m[c];if(e){b(e);return}w(!0),console.log("Fetching YAML for category: ".concat(c),{accessToken:d?"present":"missing"}),(0,h.getCategoryYaml)(d,c).then(e=>{console.log("Successfully fetched YAML for ".concat(c,":"),e),b(e.yaml_content),x(l=>({...l,[c]:e.yaml_content}))}).catch(e=>{console.error("Failed to fetch preview YAML for category ".concat(c,":"),e),b("")}).finally(()=>{w(!1)})}else b(""),w(!1)},[c,d]);let C=[{title:"Category",dataIndex:"display_name",key:"display_name",render:(e,l)=>{let t=a.find(e=>e.name===l.category);return(0,n.jsxs)("div",{children:[(0,n.jsx)("div",{style:{fontWeight:500},children:e}),(null==t?void 0:t.description)&&(0,n.jsx)("div",{style:{fontSize:"12px",color:"#888",marginTop:"4px"},children:t.description})]})}},{title:"Action",dataIndex:"action",key:"action",width:150,render:(e,l)=>(0,n.jsxs)(f.default,{value:e,onChange:e=>s(l.id,"action",e),style:{width:"100%"},children:[(0,n.jsx)(eL,{value:"BLOCK",children:(0,n.jsx)(y.Z,{color:"red",children:"BLOCK"})}),(0,n.jsx)(eL,{value:"MASK",children:(0,n.jsx)(y.Z,{color:"orange",children:"MASK"})})]})},{title:"Severity Threshold",dataIndex:"severity_threshold",key:"severity_threshold",width:180,render:(e,l)=>(0,n.jsxs)(f.default,{value:e,onChange:e=>s(l.id,"severity_threshold",e),style:{width:"100%"},children:[(0,n.jsx)(eL,{value:"low",children:"Low"}),(0,n.jsx)(eL,{value:"medium",children:"Medium"}),(0,n.jsx)(eL,{value:"high",children:"High"})]})},{title:"",key:"actions",width:80,render:(e,l)=>(0,n.jsx)(ed.z,{icon:eb.Z,onClick:()=>r(l.id),variant:"secondary",size:"xs",children:"Remove"})}],S=a.filter(e=>!t.some(l=>l.category===e.name));return(0,n.jsxs)(ei.Z,{title:(0,n.jsxs)("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center"},children:[(0,n.jsx)(eO,{level:5,style:{margin:0},children:"Content Categories"}),(0,n.jsx)(eI,{type:"secondary",style:{fontSize:14,fontWeight:400},children:"Detect harmful content, bias, and inappropriate advice using semantic analysis"})]}),size:"small",children:[(0,n.jsxs)("div",{style:{marginBottom:16,display:"flex",gap:8},children:[(0,n.jsx)(f.default,{placeholder:"Select a content category",value:c||void 0,onChange:u,style:{flex:1},showSearch:!0,optionLabelProp:"label",filterOption:(e,l)=>{var a,t;return(null!==(t=null==l?void 0:null===(a=l.label)||void 0===a?void 0:a.toString().toLowerCase())&&void 0!==t?t:"").includes(e.toLowerCase())},children:S.map(e=>(0,n.jsx)(eL,{value:e.name,label:e.display_name,children:(0,n.jsxs)("div",{children:[(0,n.jsx)("div",{style:{fontWeight:500},children:e.display_name}),(0,n.jsx)("div",{style:{fontSize:"12px",color:"#666",marginTop:"2px"},children:e.description})]})},e.name))}),(0,n.jsx)(ed.z,{onClick:()=>{if(!c)return;let e=a.find(e=>e.name===c);!e||t.some(e=>e.category===c)||(i({id:"category-".concat(Date.now()),category:e.name,display_name:e.display_name,action:e.default_action,severity_threshold:"medium"}),u(""),b(""))},disabled:!c,icon:en.Z,children:"Add"})]}),c&&(0,n.jsxs)("div",{style:{marginBottom:16,padding:"12px",background:"#f9f9f9",border:"1px solid #e0e0e0",borderRadius:"4px"},children:[(0,n.jsxs)("div",{style:{marginBottom:8,fontWeight:500,fontSize:"14px"},children:["Preview: ",null===(l=a.find(e=>e.name===c))||void 0===l?void 0:l.display_name]}),N?(0,n.jsx)("div",{style:{padding:"16px",textAlign:"center",color:"#888"},children:"Loading YAML..."}):_?(0,n.jsx)("pre",{style:{background:"#fff",padding:"12px",borderRadius:"4px",overflow:"auto",maxHeight:"300px",fontSize:"12px",lineHeight:"1.5",margin:0,border:"1px solid #e0e0e0"},children:(0,n.jsx)("code",{children:_})}):(0,n.jsx)("div",{style:{padding:"8px",textAlign:"center",color:"#888",fontSize:"12px"},children:"Unable to load YAML content"})]}),t.length>0?(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(e_.Z,{dataSource:t,columns:C,pagination:!1,size:"small",rowKey:"id"}),(0,n.jsx)("div",{style:{marginTop:16},children:(0,n.jsx)(eP.default,{activeKey:j,onChange:e=>{let l=Array.isArray(e)?e:e?[e]:[],a=new Set(j);l.forEach(e=>{a.has(e)||m[e]||k(e)}),v(l)},ghost:!0,children:t.map(e=>(0,n.jsx)(eT,{header:(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:8},children:[(0,n.jsx)(eA.Z,{}),(0,n.jsxs)("span",{children:["View YAML for ",e.display_name]})]}),children:p[e.category]?(0,n.jsx)("div",{style:{padding:"16px",textAlign:"center",color:"#888"},children:"Loading YAML..."}):m[e.category]?(0,n.jsx)("pre",{style:{background:"#f5f5f5",padding:"16px",borderRadius:"4px",overflow:"auto",maxHeight:"400px",fontSize:"12px",lineHeight:"1.5",margin:0},children:(0,n.jsx)("code",{children:m[e.category]})}):(0,n.jsx)("div",{style:{padding:"16px",textAlign:"center",color:"#888"},children:"YAML will load when expanded"})},e.category))})})]}):(0,n.jsx)("div",{style:{textAlign:"center",padding:"24px",color:"#888",border:"1px dashed #d9d9d9",borderRadius:"4px"},children:"No content categories selected. Add categories to detect harmful content, bias, or inappropriate advice."})]})};let{Title:eE,Text:eB}=g.default;var eM=e=>{let{prebuiltPatterns:l,categories:a,selectedPatterns:t,blockedWords:i,onPatternAdd:r,onPatternRemove:s,onPatternActionChange:d,onBlockedWordAdd:c,onBlockedWordRemove:u,onBlockedWordUpdate:m,onFileUpload:x,accessToken:p,showStep:g,contentCategories:f=[],selectedContentCategories:j=[],onContentCategoryAdd:v,onContentCategoryRemove:y,onContentCategoryUpdate:_}=e,[b,N]=(0,o.useState)(!1),[w,k]=(0,o.useState)(!1),[C,S]=(0,o.useState)(!1),[Z,P]=(0,o.useState)(""),[A,O]=(0,o.useState)("BLOCK"),[I,L]=(0,o.useState)(""),[T,z]=(0,o.useState)(""),[E,B]=(0,o.useState)("BLOCK"),[M,F]=(0,o.useState)(""),[G,K]=(0,o.useState)("BLOCK"),[D,R]=(0,o.useState)(""),[J,V]=(0,o.useState)(!1),U=async e=>{V(!0);try{let l=await e.text();if(p){let e=await (0,h.validateBlockedWordsFile)(p,l);if(e.valid)x&&x(l),et.Z.success(e.message||"File uploaded successfully");else{let l=e.error||e.errors&&e.errors.join(", ")||"Invalid file";et.Z.error("Validation failed: ".concat(l))}}}catch(e){et.Z.error("Failed to upload file: ".concat(e))}finally{V(!1)}return!1};return(0,n.jsxs)("div",{className:"space-y-6",children:[!g&&(0,n.jsx)("div",{children:(0,n.jsx)(eB,{type:"secondary",children:"Configure patterns, keywords, and content categories to detect and filter sensitive information in requests and responses."})}),(!g||"patterns"===g)&&(0,n.jsxs)(ei.Z,{title:(0,n.jsxs)("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center"},children:[(0,n.jsx)(eE,{level:5,style:{margin:0},children:"Pattern Detection"}),(0,n.jsx)(eB,{type:"secondary",style:{fontSize:14,fontWeight:400},children:"Detect sensitive information using regex patterns (SSN, credit cards, API keys, etc.)"})]}),size:"small",children:[(0,n.jsx)("div",{style:{marginBottom:16},children:(0,n.jsxs)(er.Z,{children:[(0,n.jsx)(ed.z,{type:"button",onClick:()=>N(!0),icon:en.Z,children:"Add prebuilt pattern"}),(0,n.jsx)(ed.z,{type:"button",onClick:()=>S(!0),variant:"secondary",icon:en.Z,children:"Add custom regex"})]})}),(0,n.jsx)(ek,{patterns:t,onActionChange:d,onRemove:s})]}),(!g||"keywords"===g)&&(0,n.jsxs)(ei.Z,{title:(0,n.jsxs)("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center"},children:[(0,n.jsx)(eE,{level:5,style:{margin:0},children:"Blocked Keywords"}),(0,n.jsx)(eB,{type:"secondary",style:{fontSize:14,fontWeight:400},children:"Block or mask specific sensitive terms and phrases"})]}),size:"small",children:[(0,n.jsx)("div",{style:{marginBottom:16},children:(0,n.jsxs)(er.Z,{children:[(0,n.jsx)(ed.z,{type:"button",onClick:()=>k(!0),icon:en.Z,children:"Add keyword"}),(0,n.jsx)(es.default,{beforeUpload:U,accept:".yaml,.yml",showUploadList:!1,children:(0,n.jsx)(ed.z,{type:"button",variant:"secondary",icon:eo.Z,loading:J,children:"Upload YAML file"})})]})}),(0,n.jsx)(eZ,{keywords:i,onActionChange:m,onRemove:u})]}),(!g||"categories"===g)&&f.length>0&&v&&y&&_&&(0,n.jsx)(ez,{availableCategories:f,selectedCategories:j,onCategoryAdd:v,onCategoryRemove:y,onCategoryUpdate:_,accessToken:p}),(0,n.jsx)(em,{visible:b,prebuiltPatterns:l,categories:a,selectedPatternName:Z,patternAction:A,onPatternNameChange:P,onActionChange:e=>O(e),onAdd:()=>{if(!Z){et.Z.error("Please select a pattern");return}let e=l.find(e=>e.name===Z);r({id:"pattern-".concat(Date.now()),type:"prebuilt",name:Z,display_name:null==e?void 0:e.display_name,action:A}),N(!1),P(""),O("BLOCK")},onCancel:()=>{N(!1),P(""),O("BLOCK")}}),(0,n.jsx)(eh,{visible:C,patternName:I,patternRegex:T,patternAction:E,onNameChange:L,onRegexChange:z,onActionChange:e=>B(e),onAdd:()=>{if(!I||!T){et.Z.error("Please provide pattern name and regex");return}r({id:"custom-".concat(Date.now()),type:"custom",name:I,pattern:T,action:E}),S(!1),L(""),z(""),B("BLOCK")},onCancel:()=>{S(!1),L(""),z(""),B("BLOCK")}}),(0,n.jsx)(ey,{visible:w,keyword:M,action:G,description:D,onKeywordChange:F,onActionChange:e=>K(e),onDescriptionChange:R,onAdd:()=>{if(!M){et.Z.error("Please enter a keyword");return}c({id:"word-".concat(Date.now()),keyword:M,action:G,description:D||void 0}),k(!1),F(""),R(""),K("BLOCK")},onCancel:()=>{k(!1),F(""),R(""),K("BLOCK")}})]})},eF=a(78801),eG=a(4260),eK=a(23496),eD=a(85180),eR=a(15424);let eJ={rules:[],default_action:"deny",on_disallowed_action:"block",violation_message_template:""},eV=e=>({...eJ,...e||{},rules:(null==e?void 0:e.rules)?[...e.rules]:[]});var eU=e=>{let{value:l,onChange:a,disabled:t=!1}=e,i=eV(l),r=e=>{let l={...i,...e};null==a||a(l)},s=(e,l)=>{r({rules:i.rules.map((a,t)=>t===e?{...a,...l}:a)})},o=e=>{r({rules:i.rules.filter((l,a)=>a!==e)})},d=(e,l)=>{let a=i.rules[e];if(!a)return;let t=Object.entries(a.allowed_param_patterns||{});l(t);let r={};t.forEach(e=>{let[l,a]=e;r[l]=a}),s(e,{allowed_param_patterns:Object.keys(r).length>0?r:void 0})},c=(e,l,a)=>{d(e,e=>{if(!e[l])return;let[,t]=e[l];e[l]=[a,t]})},u=(e,l,a)=>{d(e,e=>{if(!e[l])return;let[t]=e[l];e[l]=[t,a]})},m=(e,l)=>{let a=Object.entries(e.allowed_param_patterns||{});return 0===a.length?(0,n.jsx)(T.ZP,{disabled:t,size:"small",onClick:()=>s(l,{allowed_param_patterns:{"":""}}),children:"+ Restrict tool arguments (optional)"}):(0,n.jsxs)("div",{className:"space-y-2",children:[(0,n.jsx)(eF.x,{className:"text-sm text-gray-600",children:"Argument constraints (dot or array paths)"}),a.map((a,i)=>{let[r,s]=a;return(0,n.jsxs)(er.Z,{align:"start",children:[(0,n.jsx)(eG.default,{disabled:t,placeholder:"messages[0].content",value:r,onChange:e=>c(l,i,e.target.value)}),(0,n.jsx)(eG.default,{disabled:t,placeholder:"^email@.*$",value:s,onChange:e=>u(l,i,e.target.value)}),(0,n.jsx)(T.ZP,{disabled:t,icon:(0,n.jsx)(eb.Z,{}),danger:!0,onClick:()=>d(l,e=>{e.splice(i,1)})})]},"".concat(e.id||l,"-").concat(i))}),(0,n.jsx)(T.ZP,{disabled:t,size:"small",onClick:()=>s(l,{allowed_param_patterns:{...e.allowed_param_patterns||{},"":""}}),children:"+ Add another constraint"})]})};return(0,n.jsxs)(eF.Z,{children:[(0,n.jsxs)("div",{className:"flex items-center justify-between",children:[(0,n.jsxs)("div",{children:[(0,n.jsx)(eF.x,{className:"text-lg font-semibold",children:"LiteLLM Tool Permission Guardrail"}),(0,n.jsx)(eF.x,{className:"text-sm text-gray-500",children:"Provide regex patterns (e.g., ^mcp__github_.*$) for tool names or types and optionally constrain payload fields."})]}),!t&&(0,n.jsx)(T.ZP,{icon:(0,n.jsx)(en.Z,{}),type:"primary",onClick:()=>{r({rules:[...i.rules,{id:"rule_".concat(Math.random().toString(36).slice(2,8)),decision:"allow",allowed_param_patterns:void 0}]})},className:"!bg-blue-600 !text-white hover:!bg-blue-500",children:"Add Rule"})]}),(0,n.jsx)(eK.Z,{}),0===i.rules.length?(0,n.jsx)(eD.Z,{description:"No tool rules added yet"}):(0,n.jsx)("div",{className:"space-y-4",children:i.rules.map((e,l)=>{var a,i;return(0,n.jsxs)(eF.Z,{className:"bg-gray-50",children:[(0,n.jsxs)("div",{className:"flex items-center justify-between mb-3",children:[(0,n.jsxs)(eF.x,{className:"font-semibold",children:["Rule ",l+1]}),(0,n.jsx)(T.ZP,{icon:(0,n.jsx)(eb.Z,{}),danger:!0,type:"text",disabled:t,onClick:()=>o(l),children:"Remove"})]}),(0,n.jsxs)("div",{className:"grid grid-cols-1 gap-4 md:grid-cols-2",children:[(0,n.jsxs)("div",{children:[(0,n.jsx)(eF.x,{className:"text-sm font-medium",children:"Rule ID"}),(0,n.jsx)(eG.default,{disabled:t,placeholder:"unique_rule_id",value:e.id,onChange:e=>s(l,{id:e.target.value})})]}),(0,n.jsxs)("div",{children:[(0,n.jsx)(eF.x,{className:"text-sm font-medium",children:"Tool Name (optional)"}),(0,n.jsx)(eG.default,{disabled:t,placeholder:"^mcp__github_.*$",value:null!==(a=e.tool_name)&&void 0!==a?a:"",onChange:e=>s(l,{tool_name:""===e.target.value.trim()?void 0:e.target.value})})]})]}),(0,n.jsx)("div",{className:"grid grid-cols-1 gap-4 md:grid-cols-2 mt-4",children:(0,n.jsxs)("div",{children:[(0,n.jsx)(eF.x,{className:"text-sm font-medium",children:"Tool Type (optional)"}),(0,n.jsx)(eG.default,{disabled:t,placeholder:"^function$",value:null!==(i=e.tool_type)&&void 0!==i?i:"",onChange:e=>s(l,{tool_type:""===e.target.value.trim()?void 0:e.target.value})})]})}),(0,n.jsxs)("div",{className:"mt-4 flex flex-col gap-2",children:[(0,n.jsx)(eF.x,{className:"text-sm font-medium",children:"Decision"}),(0,n.jsxs)(f.default,{disabled:t,value:e.decision,style:{width:200},onChange:e=>s(l,{decision:e}),children:[(0,n.jsx)(f.default.Option,{value:"allow",children:"Allow"}),(0,n.jsx)(f.default.Option,{value:"deny",children:"Deny"})]})]}),(0,n.jsx)("div",{className:"mt-4",children:m(e,l)})]},e.id||l)})}),(0,n.jsx)(eK.Z,{}),(0,n.jsxs)("div",{className:"grid gap-4 md:grid-cols-2",children:[(0,n.jsxs)("div",{children:[(0,n.jsx)(eF.x,{className:"text-sm font-medium",children:"Default action"}),(0,n.jsxs)(f.default,{disabled:t,value:i.default_action,onChange:e=>r({default_action:e}),children:[(0,n.jsx)(f.default.Option,{value:"allow",children:"Allow"}),(0,n.jsx)(f.default.Option,{value:"deny",children:"Deny"})]})]}),(0,n.jsxs)("div",{children:[(0,n.jsxs)(eF.x,{className:"text-sm font-medium flex items-center gap-1",children:["On disallowed action",(0,n.jsx)(L.Z,{title:"Block returns an error when a forbidden tool is invoked. Rewrite strips the tool call but lets the rest of the response continue.",children:(0,n.jsx)(eR.Z,{})})]}),(0,n.jsxs)(f.default,{disabled:t,value:i.on_disallowed_action,onChange:e=>r({on_disallowed_action:e}),children:[(0,n.jsx)(f.default.Option,{value:"block",children:"Block"}),(0,n.jsx)(f.default.Option,{value:"rewrite",children:"Rewrite"})]})]})]}),(0,n.jsxs)("div",{className:"mt-4",children:[(0,n.jsx)(eF.x,{className:"text-sm font-medium",children:"Violation message (optional)"}),(0,n.jsx)(eG.default.TextArea,{disabled:t,rows:3,placeholder:"This violates our org policy...",value:i.violation_message_template,onChange:e=>r({violation_message_template:e.target.value})})]})]})};let{Title:eW,Text:eY,Link:eq}=g.default,{Option:eH}=f.default,{Step:e$}=j.default,eQ={pre_call:"Before LLM Call - Runs before the LLM call and checks the input (Recommended)",during_call:"During LLM Call - Runs in parallel with the LLM call, with response held until check completes",post_call:"After LLM Call - Runs after the LLM call and checks only the output",logging_only:"Logging Only - Only runs on logging callbacks without affecting the LLM call",pre_mcp_call:"Before MCP Tool Call - Runs before MCP tool execution and validates tool calls",during_mcp_call:"During MCP Tool Call - Runs in parallel with MCP tool execution for monitoring"};var eX=e=>{let{visible:l,onClose:a,accessToken:t,onSuccess:i}=e,[r]=v.Z.useForm(),[s,d]=(0,o.useState)(!1),[c,u]=(0,o.useState)(null),[m,x]=(0,o.useState)(null),[p,g]=(0,o.useState)([]),[N,A]=(0,o.useState)({}),[I,L]=(0,o.useState)(0),[T,z]=(0,o.useState)(null),[E,B]=(0,o.useState)([]),[M,F]=(0,o.useState)(2),[G,K]=(0,o.useState)({}),[D,R]=(0,o.useState)([]),[J,V]=(0,o.useState)([]),[U,W]=(0,o.useState)([]),[Y,H]=(0,o.useState)({rules:[],default_action:"deny",on_disallowed_action:"block",violation_message_template:""}),$=(0,o.useMemo)(()=>!!c&&"tool_permission"===(C[c]||"").toLowerCase(),[c]);(0,o.useEffect)(()=>{t&&(async()=>{try{let[e,l]=await Promise.all([(0,h.getGuardrailUISettings)(t),(0,h.getGuardrailProviderSpecificParams)(t)]);x(e),z(l),w(l),S(l)}catch(e){console.error("Error fetching guardrail data:",e),et.Z.fromBackend("Failed to load guardrail configuration")}})()},[t]);let Q=e=>{u(e),r.setFieldsValue({config:void 0,presidio_analyzer_api_base:void 0,presidio_anonymizer_api_base:void 0}),g([]),A({}),B([]),F(2),K({}),H({rules:[],default_action:"deny",on_disallowed_action:"block",violation_message_template:""})},ee=e=>{g(l=>l.includes(e)?l.filter(l=>l!==e):[...l,e])},el=(e,l)=>{A(a=>({...a,[e]:l}))},ei=async()=>{try{if(0===I&&(await r.validateFields(["guardrail_name","provider","mode","default_on"]),c)){let e=["guardrail_name","provider","mode","default_on"];"PresidioPII"===c&&e.push("presidio_analyzer_api_base","presidio_anonymizer_api_base"),await r.validateFields(e)}if(1===I&&Z(c)&&0===p.length){et.Z.fromBackend("Please select at least one PII entity to continue");return}L(I+1)}catch(e){console.error("Form validation failed:",e)}},er=()=>{L(I-1)},es=()=>{r.resetFields(),u(null),g([]),A({}),B([]),F(2),K({}),R([]),V([]),W([]),H({rules:[],default_action:"deny",on_disallowed_action:"block",violation_message_template:""}),L(0)},en=()=>{es(),a()},eo=async()=>{try{d(!0),await r.validateFields();let l=r.getFieldsValue(!0),s=C[l.provider],n={guardrail_name:l.guardrail_name,litellm_params:{guardrail:s,mode:l.mode,default_on:l.default_on},guardrail_info:{}};if("PresidioPII"===l.provider&&p.length>0){let e={};p.forEach(l=>{e[l]=N[l]||"MASK"}),n.litellm_params.pii_entities_config=e,l.presidio_analyzer_api_base&&(n.litellm_params.presidio_analyzer_api_base=l.presidio_analyzer_api_base),l.presidio_anonymizer_api_base&&(n.litellm_params.presidio_anonymizer_api_base=l.presidio_anonymizer_api_base)}if(P(l.provider))D.length>0&&(n.litellm_params.patterns=D.map(e=>({pattern_type:"prebuilt"===e.type?"prebuilt":"regex",pattern_name:"prebuilt"===e.type?e.name:void 0,pattern:"custom"===e.type?e.pattern:void 0,name:e.name,action:e.action}))),J.length>0&&(n.litellm_params.blocked_words=J.map(e=>({keyword:e.keyword,action:e.action,description:e.description}))),U.length>0&&(n.litellm_params.categories=U.map(e=>({category:e.category,enabled:!0,action:e.action,severity_threshold:e.severity_threshold||"medium"})));else if(l.config)try{let e=JSON.parse(l.config);n.guardrail_info=e}catch(e){et.Z.fromBackend("Invalid JSON in configuration"),d(!1);return}if("tool_permission"===s){if(0===Y.rules.length){et.Z.fromBackend("Add at least one tool permission rule"),d(!1);return}n.litellm_params.rules=Y.rules,n.litellm_params.default_action=Y.default_action,n.litellm_params.on_disallowed_action=Y.on_disallowed_action,Y.violation_message_template&&(n.litellm_params.violation_message_template=Y.violation_message_template)}if(console.log("values: ",JSON.stringify(l)),T&&c){var e;let a=null===(e=C[c])||void 0===e?void 0:e.toLowerCase();console.log("providerKey: ",a);let t=T[a]||{},i=new Set;console.log("providerSpecificParams: ",JSON.stringify(t)),Object.keys(t).forEach(e=>{"optional_params"!==e&&i.add(e)}),t.optional_params&&t.optional_params.fields&&Object.keys(t.optional_params.fields).forEach(e=>{i.add(e)}),console.log("allowedParams: ",i),i.forEach(e=>{let a=l[e];if(null==a||""===a){var t;a=null===(t=l.optional_params)||void 0===t?void 0:t[e]}null!=a&&""!==a&&(n.litellm_params[e]=a)})}if(!t)throw Error("No access token available");console.log("Sending guardrail data:",JSON.stringify(n)),await (0,h.createGuardrailCall)(t,n),et.Z.success("Guardrail created successfully"),es(),i(),a()}catch(e){console.error("Failed to create guardrail:",e),et.Z.fromBackend("Failed to create guardrail: "+(e instanceof Error?e.message:String(e)))}finally{d(!1)}},ed=()=>{var e;return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(v.Z.Item,{name:"guardrail_name",label:"Guardrail Name",rules:[{required:!0,message:"Please enter a guardrail name"}],children:(0,n.jsx)(b.o,{placeholder:"Enter a name for this guardrail"})}),(0,n.jsx)(v.Z.Item,{name:"provider",label:"Guardrail Provider",rules:[{required:!0,message:"Please select a provider"}],children:(0,n.jsx)(f.default,{placeholder:"Select a guardrail provider",onChange:Q,labelInValue:!1,optionLabelProp:"label",dropdownRender:e=>e,showSearch:!0,children:Object.entries(k()).map(e=>{let[l,a]=e;return(0,n.jsx)(eH,{value:l,label:(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center"},children:[O[a]&&(0,n.jsx)("img",{src:O[a],alt:"",style:{height:"20px",width:"20px",marginRight:"8px",objectFit:"contain"},onError:e=>{e.currentTarget.style.display="none"}}),(0,n.jsx)("span",{children:a})]}),children:(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center"},children:[O[a]&&(0,n.jsx)("img",{src:O[a],alt:"",style:{height:"20px",width:"20px",marginRight:"8px",objectFit:"contain"},onError:e=>{e.currentTarget.style.display="none"}}),(0,n.jsx)("span",{children:a})]})},l)})})}),(0,n.jsx)(v.Z.Item,{name:"mode",label:"Mode",tooltip:"How the guardrail should be applied",rules:[{required:!0,message:"Please select a mode"}],children:(0,n.jsx)(f.default,{optionLabelProp:"label",mode:"multiple",children:(null==m?void 0:null===(e=m.supported_modes)||void 0===e?void 0:e.map(e=>(0,n.jsx)(eH,{value:e,label:e,children:(0,n.jsxs)("div",{children:[(0,n.jsxs)("div",{children:[(0,n.jsx)("strong",{children:e}),"pre_call"===e&&(0,n.jsx)(y.Z,{color:"green",style:{marginLeft:"8px"},children:"Recommended"})]}),(0,n.jsx)("div",{style:{fontSize:"12px",color:"#888"},children:eQ[e]})]})},e)))||(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(eH,{value:"pre_call",label:"pre_call",children:(0,n.jsxs)("div",{children:[(0,n.jsxs)("div",{children:[(0,n.jsx)("strong",{children:"pre_call"})," ",(0,n.jsx)(y.Z,{color:"green",children:"Recommended"})]}),(0,n.jsx)("div",{style:{fontSize:"12px",color:"#888"},children:eQ.pre_call})]})}),(0,n.jsx)(eH,{value:"during_call",label:"during_call",children:(0,n.jsxs)("div",{children:[(0,n.jsx)("div",{children:(0,n.jsx)("strong",{children:"during_call"})}),(0,n.jsx)("div",{style:{fontSize:"12px",color:"#888"},children:eQ.during_call})]})}),(0,n.jsx)(eH,{value:"post_call",label:"post_call",children:(0,n.jsxs)("div",{children:[(0,n.jsx)("div",{children:(0,n.jsx)("strong",{children:"post_call"})}),(0,n.jsx)("div",{style:{fontSize:"12px",color:"#888"},children:eQ.post_call})]})}),(0,n.jsx)(eH,{value:"logging_only",label:"logging_only",children:(0,n.jsxs)("div",{children:[(0,n.jsx)("div",{children:(0,n.jsx)("strong",{children:"logging_only"})}),(0,n.jsx)("div",{style:{fontSize:"12px",color:"#888"},children:eQ.logging_only})]})})]})})}),(0,n.jsx)(v.Z.Item,{name:"default_on",label:"Always On",tooltip:"If enabled, this guardrail will be applied to all requests by default.",children:(0,n.jsxs)(f.default,{children:[(0,n.jsx)(f.default.Option,{value:!0,children:"Yes"}),(0,n.jsx)(f.default.Option,{value:!1,children:"No"})]})}),!$&&!P(c)&&(0,n.jsx)(X,{selectedProvider:c,accessToken:t,providerParams:T})]})},ec=()=>m&&"PresidioPII"===c?(0,n.jsx)(q,{entities:m.supported_entities,actions:m.supported_actions,selectedEntities:p,selectedActions:N,onEntitySelect:ee,onActionSelect:el,entityCategories:m.pii_entity_categories}):null,eu=e=>{if(!m||!P(c))return null;let l=m.content_filter_settings;return l?(0,n.jsx)(eM,{prebuiltPatterns:l.prebuilt_patterns||[],categories:l.pattern_categories||[],selectedPatterns:D,blockedWords:J,onPatternAdd:e=>R([...D,e]),onPatternRemove:e=>R(D.filter(l=>l.id!==e)),onPatternActionChange:(e,l)=>{R(D.map(a=>a.id===e?{...a,action:l}:a))},onBlockedWordAdd:e=>V([...J,e]),onBlockedWordRemove:e=>V(J.filter(l=>l.id!==e)),onBlockedWordUpdate:(e,l,a)=>{V(J.map(t=>t.id===e?{...t,[l]:a}:t))},contentCategories:l.content_categories||[],selectedContentCategories:U,onContentCategoryAdd:e=>W([...U,e]),onContentCategoryRemove:e=>W(U.filter(l=>l.id!==e)),onContentCategoryUpdate:(e,l,a)=>{W(U.map(t=>t.id===e?{...t,[l]:a}:t))},accessToken:t,showStep:e}):null},em=()=>{var e;if(!c)return null;if($)return(0,n.jsx)(eU,{value:Y,onChange:H});if(!T)return null;console.log("guardrail_provider_map: ",C),console.log("selectedProvider: ",c);let l=null===(e=C[c])||void 0===e?void 0:e.toLowerCase(),a=T&&T[l];return a&&a.optional_params?(0,n.jsx)(ea,{optionalParams:a.optional_params,parentFieldKey:"optional_params"}):null};return(0,n.jsx)(_.Z,{title:"Add Guardrail",open:l,onCancel:en,footer:null,width:800,children:(0,n.jsxs)(v.Z,{form:r,layout:"vertical",initialValues:{mode:"pre_call",default_on:!1},children:[(0,n.jsxs)(j.default,{current:I,className:"mb-6",style:{overflow:"visible"},children:[(0,n.jsx)(e$,{title:"Basic Info"}),(0,n.jsx)(e$,{title:Z(c)?"PII Configuration":P(c)?"Default Categories":"Provider Configuration"}),P(c)&&(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(e$,{title:"Patterns"}),(0,n.jsx)(e$,{title:"Keywords"})]})]}),(()=>{switch(I){case 0:return ed();case 1:if(Z(c))return ec();if(P(c))return eu("categories");return em();case 2:if(P(c))return eu("patterns");return null;case 3:if(P(c))return eu("keywords");return null;default:return null}})(),(()=>{let e=I===(P(c)?4:2)-1;return(0,n.jsxs)("div",{className:"flex justify-end space-x-2 mt-4",children:[I>0&&(0,n.jsx)(b.z,{variant:"secondary",onClick:er,children:"Previous"}),!e&&(0,n.jsx)(b.z,{onClick:ei,children:"Next"}),e&&(0,n.jsx)(b.z,{onClick:eo,loading:s,children:"Create Guardrail"}),(0,n.jsx)(b.z,{variant:"secondary",onClick:en,children:"Cancel"})]})})()]})})},e0=a(47323),e1=a(21626),e4=a(97214),e2=a(28241),e8=a(58834),e5=a(69552),e6=a(71876),e3=a(74998),e9=a(44633),e7=a(86462),le=a(49084),ll=a(1309),la=a(71594),lt=a(24525),li=a(63709);let{Title:lr,Text:ls}=g.default,{Option:ln}=f.default;var lo=e=>{var l;let{visible:a,onClose:t,accessToken:i,onSuccess:r,guardrailId:s,initialValues:d}=e,[c]=v.Z.useForm(),[u,m]=(0,o.useState)(!1),[x,p]=(0,o.useState)((null==d?void 0:d.provider)||null),[g,j]=(0,o.useState)(null),[y,N]=(0,o.useState)([]),[w,S]=(0,o.useState)({});(0,o.useEffect)(()=>{(async()=>{try{if(!i)return;let e=await (0,h.getGuardrailUISettings)(i);j(e)}catch(e){console.error("Error fetching guardrail settings:",e),et.Z.fromBackend("Failed to load guardrail settings")}})()},[i]),(0,o.useEffect)(()=>{(null==d?void 0:d.pii_entities_config)&&Object.keys(d.pii_entities_config).length>0&&(N(Object.keys(d.pii_entities_config)),S(d.pii_entities_config))},[d]);let Z=e=>{N(l=>l.includes(e)?l.filter(l=>l!==e):[...l,e])},P=(e,l)=>{S(a=>({...a,[e]:l}))},A=async()=>{try{m(!0);let e=await c.validateFields(),l=C[e.provider],a={guardrail_id:s,guardrail:{guardrail_name:e.guardrail_name,litellm_params:{guardrail:l,mode:e.mode,default_on:e.default_on},guardrail_info:{}}};if("PresidioPII"===e.provider&&y.length>0){let e={};y.forEach(l=>{e[l]=w[l]||"MASK"}),a.guardrail.litellm_params.pii_entities_config=e}else if(e.config)try{let l=JSON.parse(e.config);"Bedrock"===e.provider&&l?(l.guardrail_id&&(a.guardrail.litellm_params.guardrailIdentifier=l.guardrail_id),l.guardrail_version&&(a.guardrail.litellm_params.guardrailVersion=l.guardrail_version)):a.guardrail.guardrail_info=l}catch(e){et.Z.fromBackend("Invalid JSON in configuration"),m(!1);return}if(!i)throw Error("No access token available");console.log("Sending guardrail update data:",JSON.stringify(a));let n=await fetch("/guardrails/".concat(s),{method:"PUT",headers:{Authorization:"Bearer ".concat(i),"Content-Type":"application/json"},body:JSON.stringify(a)});if(!n.ok){let e=await n.text();throw Error(e||"Failed to update guardrail")}et.Z.success("Guardrail updated successfully"),r(),t()}catch(e){console.error("Failed to update guardrail:",e),et.Z.fromBackend("Failed to update guardrail: "+(e instanceof Error?e.message:String(e)))}finally{m(!1)}},I=()=>g&&x&&"PresidioPII"===x?(0,n.jsx)(q,{entities:g.supported_entities,actions:g.supported_actions,selectedEntities:y,selectedActions:w,onEntitySelect:Z,onActionSelect:P,entityCategories:g.pii_entity_categories}):null;return(0,n.jsx)(_.Z,{title:"Edit Guardrail",open:a,onCancel:t,footer:null,width:700,children:(0,n.jsxs)(v.Z,{form:c,layout:"vertical",initialValues:d,children:[(0,n.jsx)(v.Z.Item,{name:"guardrail_name",label:"Guardrail Name",rules:[{required:!0,message:"Please enter a guardrail name"}],children:(0,n.jsx)(b.o,{placeholder:"Enter a name for this guardrail"})}),(0,n.jsx)(v.Z.Item,{name:"provider",label:"Guardrail Provider",rules:[{required:!0,message:"Please select a provider"}],children:(0,n.jsx)(f.default,{placeholder:"Select a guardrail provider",onChange:e=>{p(e),c.setFieldsValue({config:void 0}),N([]),S({})},disabled:!0,optionLabelProp:"label",children:Object.entries(k()).map(e=>{let[l,a]=e;return(0,n.jsx)(ln,{value:l,label:a,children:(0,n.jsxs)("div",{style:{display:"flex",alignItems:"center"},children:[O[a]&&(0,n.jsx)("img",{src:O[a],alt:"",style:{height:"20px",width:"20px",marginRight:"8px",objectFit:"contain"},onError:e=>{e.currentTarget.style.display="none"}}),(0,n.jsx)("span",{children:a})]})},l)})})}),(0,n.jsx)(v.Z.Item,{name:"mode",label:"Mode",tooltip:"How the guardrail should be applied",rules:[{required:!0,message:"Please select a mode"}],children:(0,n.jsx)(f.default,{children:(null==g?void 0:null===(l=g.supported_modes)||void 0===l?void 0:l.map(e=>(0,n.jsx)(ln,{value:e,children:e},e)))||(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(ln,{value:"pre_call",children:"pre_call"}),(0,n.jsx)(ln,{value:"post_call",children:"post_call"})]})})}),(0,n.jsx)(v.Z.Item,{name:"default_on",label:"Always On",tooltip:"If enabled, this guardrail will be applied to all requests by default",valuePropName:"checked",children:(0,n.jsx)(li.Z,{})}),(()=>{if(!x)return null;if("PresidioPII"===x)return I();switch(x){case"Aporia":return(0,n.jsx)(v.Z.Item,{label:"Aporia Configuration",name:"config",tooltip:"JSON configuration for Aporia",children:(0,n.jsx)(eG.default.TextArea,{rows:4,placeholder:'{\n "api_key": "your_aporia_api_key",\n "project_name": "your_project_name"\n}'})});case"AimSecurity":return(0,n.jsx)(v.Z.Item,{label:"Aim Security Configuration",name:"config",tooltip:"JSON configuration for Aim Security",children:(0,n.jsx)(eG.default.TextArea,{rows:4,placeholder:'{\n "api_key": "your_aim_api_key"\n}'})});case"Bedrock":return(0,n.jsx)(v.Z.Item,{label:"Amazon Bedrock Configuration",name:"config",tooltip:"JSON configuration for Amazon Bedrock guardrails",children:(0,n.jsx)(eG.default.TextArea,{rows:4,placeholder:'{\n "guardrail_id": "your_guardrail_id",\n "guardrail_version": "your_guardrail_version"\n}'})});case"GuardrailsAI":return(0,n.jsx)(v.Z.Item,{label:"Guardrails.ai Configuration",name:"config",tooltip:"JSON configuration for Guardrails.ai",children:(0,n.jsx)(eG.default.TextArea,{rows:4,placeholder:'{\n "api_key": "your_guardrails_api_key",\n "guardrail_id": "your_guardrail_id"\n}'})});case"LakeraAI":return(0,n.jsx)(v.Z.Item,{label:"Lakera AI Configuration",name:"config",tooltip:"JSON configuration for Lakera AI",children:(0,n.jsx)(eG.default.TextArea,{rows:4,placeholder:'{\n "api_key": "your_lakera_api_key"\n}'})});case"PromptInjection":return(0,n.jsx)(v.Z.Item,{label:"Prompt Injection Configuration",name:"config",tooltip:"JSON configuration for prompt injection detection",children:(0,n.jsx)(eG.default.TextArea,{rows:4,placeholder:'{\n "threshold": 0.8\n}'})});default:return(0,n.jsx)(v.Z.Item,{label:"Custom Configuration",name:"config",tooltip:"JSON configuration for your custom guardrail",children:(0,n.jsx)(eG.default.TextArea,{rows:4,placeholder:'{\n "key1": "value1",\n "key2": "value2"\n}'})})}})(),(0,n.jsxs)("div",{className:"flex justify-end space-x-2 mt-4",children:[(0,n.jsx)(b.z,{variant:"secondary",onClick:t,children:"Cancel"}),(0,n.jsx)(b.z,{onClick:A,loading:u,children:"Update Guardrail"})]})]})})};(i=s||(s={})).DB="db",i.CONFIG="config";var ld=e=>{let{guardrailsList:l,isLoading:a,onDeleteClick:t,accessToken:i,onGuardrailUpdated:r,isAdmin:c=!1,onGuardrailClick:u}=e,[m,x]=(0,o.useState)([{id:"created_at",desc:!0}]),[p,h]=(0,o.useState)(!1),[g,f]=(0,o.useState)(null),j=e=>e?new Date(e).toLocaleString():"-",v=[{header:"Guardrail ID",accessorKey:"guardrail_id",cell:e=>(0,n.jsx)(L.Z,{title:String(e.getValue()||""),children:(0,n.jsx)(d.Z,{size:"xs",variant:"light",className:"font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left overflow-hidden truncate max-w-[200px]",onClick:()=>e.getValue()&&u(e.getValue()),children:e.getValue()?"".concat(String(e.getValue()).slice(0,7),"..."):""})})},{header:"Name",accessorKey:"guardrail_name",cell:e=>{let{row:l}=e,a=l.original;return(0,n.jsx)(L.Z,{title:a.guardrail_name,children:(0,n.jsx)("span",{className:"text-xs font-medium",children:a.guardrail_name||"-"})})}},{header:"Provider",accessorKey:"litellm_params.guardrail",cell:e=>{let{row:l}=e,{logo:a,displayName:t}=I(l.original.litellm_params.guardrail);return(0,n.jsxs)("div",{className:"flex items-center space-x-2",children:[a&&(0,n.jsx)("img",{src:a,alt:"".concat(t," logo"),className:"w-4 h-4",onError:e=>{e.target.style.display="none"}}),(0,n.jsx)("span",{className:"text-xs",children:t})]})}},{header:"Mode",accessorKey:"litellm_params.mode",cell:e=>{let{row:l}=e,a=l.original;return(0,n.jsx)("span",{className:"text-xs",children:a.litellm_params.mode})}},{header:"Default On",accessorKey:"litellm_params.default_on",cell:e=>{var l,a;let{row:t}=e,i=t.original;return(0,n.jsx)(ll.C,{color:(null===(l=i.litellm_params)||void 0===l?void 0:l.default_on)?"green":"gray",className:"text-xs font-normal",size:"xs",children:(null===(a=i.litellm_params)||void 0===a?void 0:a.default_on)?"Default On":"Default Off"})}},{header:"Created At",accessorKey:"created_at",cell:e=>{let{row:l}=e,a=l.original;return(0,n.jsx)(L.Z,{title:a.created_at,children:(0,n.jsx)("span",{className:"text-xs",children:j(a.created_at)})})}},{header:"Updated At",accessorKey:"updated_at",cell:e=>{let{row:l}=e,a=l.original;return(0,n.jsx)(L.Z,{title:a.updated_at,children:(0,n.jsx)("span",{className:"text-xs",children:j(a.updated_at)})})}},{id:"actions",header:"Actions",cell:e=>{let{row:l}=e,a=l.original,i=a.guardrail_definition_location===s.CONFIG;return(0,n.jsx)("div",{className:"flex space-x-2",children:i?(0,n.jsx)(L.Z,{title:"Config guardrail cannot be deleted on the dashboard. Please delete it from the config file.",children:(0,n.jsx)(e0.Z,{"data-testid":"config-delete-icon",icon:e3.Z,size:"sm",className:"cursor-not-allowed text-gray-400",title:"Config guardrail cannot be deleted on the dashboard. Please delete it from the config file.","aria-label":"Delete guardrail (config)"})}):(0,n.jsx)(L.Z,{title:"Delete guardrail",children:(0,n.jsx)(e0.Z,{icon:e3.Z,size:"sm",onClick:()=>a.guardrail_id&&t(a.guardrail_id,a.guardrail_name||"Unnamed Guardrail"),className:"cursor-pointer hover:text-red-500"})})})}}],y=(0,la.b7)({data:l,columns:v,state:{sorting:m},onSortingChange:x,getCoreRowModel:(0,lt.sC)(),getSortedRowModel:(0,lt.tj)(),enableSorting:!0});return(0,n.jsxs)("div",{className:"rounded-lg custom-border relative",children:[(0,n.jsx)("div",{className:"overflow-x-auto",children:(0,n.jsxs)(e1.Z,{className:"[&_td]:py-0.5 [&_th]:py-1",children:[(0,n.jsx)(e8.Z,{children:y.getHeaderGroups().map(e=>(0,n.jsx)(e6.Z,{children:e.headers.map(e=>(0,n.jsx)(e5.Z,{className:"py-1 h-8 ".concat("actions"===e.id?"sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]":""),onClick:e.column.getToggleSortingHandler(),children:(0,n.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,n.jsx)("div",{className:"flex items-center",children:e.isPlaceholder?null:(0,la.ie)(e.column.columnDef.header,e.getContext())}),"actions"!==e.id&&(0,n.jsx)("div",{className:"w-4",children:e.column.getIsSorted()?({asc:(0,n.jsx)(e9.Z,{className:"h-4 w-4 text-blue-500"}),desc:(0,n.jsx)(e7.Z,{className:"h-4 w-4 text-blue-500"})})[e.column.getIsSorted()]:(0,n.jsx)(le.Z,{className:"h-4 w-4 text-gray-400"})})]})},e.id))},e.id))}),(0,n.jsx)(e4.Z,{children:a?(0,n.jsx)(e6.Z,{children:(0,n.jsx)(e2.Z,{colSpan:v.length,className:"h-8 text-center",children:(0,n.jsx)("div",{className:"text-center text-gray-500",children:(0,n.jsx)("p",{children:"Loading..."})})})}):l.length>0?y.getRowModel().rows.map(e=>(0,n.jsx)(e6.Z,{className:"h-8",children:e.getVisibleCells().map(e=>(0,n.jsx)(e2.Z,{className:"py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap ".concat("actions"===e.column.id?"sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]":""),children:(0,la.ie)(e.column.columnDef.cell,e.getContext())},e.id))},e.id)):(0,n.jsx)(e6.Z,{children:(0,n.jsx)(e2.Z,{colSpan:v.length,className:"h-8 text-center",children:(0,n.jsx)("div",{className:"text-center text-gray-500",children:(0,n.jsx)("p",{children:"No guardrails found"})})})})})]})}),g&&(0,n.jsx)(lo,{visible:p,onClose:()=>h(!1),accessToken:i,onSuccess:()=>{h(!1),f(null),r()},guardrailId:g.guardrail_id||"",initialValues:{guardrail_name:g.guardrail_name||"",provider:Object.keys(C).find(e=>C[e]===(null==g?void 0:g.litellm_params.guardrail))||"",mode:g.litellm_params.mode,default_on:g.litellm_params.default_on,pii_entities_config:g.litellm_params.pii_entities_config,...g.guardrail_info}})]})},lc=a(20347),lu=a(30078),lm=a(41649),lx=a(12514),lp=a(84264),lh=e=>{let{patterns:l,blockedWords:a,readOnly:t=!0,onPatternActionChange:i,onPatternRemove:r,onBlockedWordUpdate:s,onBlockedWordRemove:o}=e;if(0===l.length&&0===a.length)return null;let d=()=>{};return(0,n.jsxs)(n.Fragment,{children:[l.length>0&&(0,n.jsxs)(lx.Z,{className:"mt-6",children:[(0,n.jsxs)("div",{className:"flex justify-between items-center mb-4",children:[(0,n.jsx)(lp.Z,{className:"text-lg font-semibold",children:"Pattern Detection"}),(0,n.jsxs)(lm.Z,{color:"blue",children:[l.length," patterns configured"]})]}),(0,n.jsx)(ek,{patterns:l,onActionChange:t?d:i||d,onRemove:t?d:r||d})]}),a.length>0&&(0,n.jsxs)(lx.Z,{className:"mt-6",children:[(0,n.jsxs)("div",{className:"flex justify-between items-center mb-4",children:[(0,n.jsx)(lp.Z,{className:"text-lg font-semibold",children:"Blocked Keywords"}),(0,n.jsxs)(lm.Z,{color:"blue",children:[a.length," keywords configured"]})]}),(0,n.jsx)(eZ,{keywords:a,onActionChange:t?d:s||d,onRemove:t?d:o||d})]})]})},lg=e=>{var l;let{guardrailData:a,guardrailSettings:t,isEditing:i,accessToken:r,onDataChange:s,onUnsavedChanges:d}=e,[c,u]=(0,o.useState)([]),[m,x]=(0,o.useState)([]),[p,h]=(0,o.useState)([]),[g,f]=(0,o.useState)([]);(0,o.useEffect)(()=>{var e,l;if(null==a?void 0:null===(e=a.litellm_params)||void 0===e?void 0:e.patterns){let e=a.litellm_params.patterns.map((e,l)=>({id:"pattern-".concat(l),type:"prebuilt"===e.pattern_type?"prebuilt":"custom",name:e.pattern_name||e.name,display_name:e.display_name,pattern:e.pattern,action:e.action||"BLOCK"}));u(e),h(e)}else u([]),h([]);if(null==a?void 0:null===(l=a.litellm_params)||void 0===l?void 0:l.blocked_words){let e=a.litellm_params.blocked_words.map((e,l)=>({id:"word-".concat(l),keyword:e.keyword,action:e.action||"BLOCK",description:e.description}));x(e),f(e)}else x([]),f([])},[a]),(0,o.useEffect)(()=>{s&&s(c,m)},[c,m,s]);let j=o.useMemo(()=>{let e=JSON.stringify(c)!==JSON.stringify(p),l=JSON.stringify(m)!==JSON.stringify(g);return e||l},[c,m,p,g]);return((0,o.useEffect)(()=>{i&&d&&d(j)},[j,i,d]),(null==a?void 0:null===(l=a.litellm_params)||void 0===l?void 0:l.guardrail)!=="litellm_content_filter")?null:i?(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(eK.Z,{orientation:"left",children:"Content Filter Configuration"}),j&&(0,n.jsx)("div",{className:"mb-4 px-4 py-3 bg-yellow-50 border border-yellow-200 rounded-md",children:(0,n.jsx)("p",{className:"text-sm text-yellow-800 font-medium",children:'⚠️ You have unsaved changes to patterns or keywords. Remember to click "Save Changes" at the bottom.'})}),(0,n.jsx)("div",{className:"mb-6",children:t&&t.content_filter_settings&&(0,n.jsx)(eM,{prebuiltPatterns:t.content_filter_settings.prebuilt_patterns||[],categories:t.content_filter_settings.pattern_categories||[],selectedPatterns:c,blockedWords:m,onPatternAdd:e=>u([...c,e]),onPatternRemove:e=>u(c.filter(l=>l.id!==e)),onPatternActionChange:(e,l)=>u(c.map(a=>a.id===e?{...a,action:l}:a)),onBlockedWordAdd:e=>x([...m,e]),onBlockedWordRemove:e=>x(m.filter(l=>l.id!==e)),onBlockedWordUpdate:(e,l,a)=>x(m.map(t=>t.id===e?{...t,[l]:a}:t)),onFileUpload:e=>{console.log("File uploaded:",e)},accessToken:r})})]}):(0,n.jsx)(lh,{patterns:c,blockedWords:m,readOnly:!0})};let lf=(e,l)=>({patterns:e.map(e=>({pattern_type:"prebuilt"===e.type?"prebuilt":"regex",pattern_name:"prebuilt"===e.type?e.name:void 0,pattern:"custom"===e.type?e.pattern:void 0,name:e.name,action:e.action})),blocked_words:l.map(e=>({keyword:e.keyword,action:e.action,description:e.description}))});var lj=a(10900),lv=a(59872),ly=a(30401),l_=a(78867),lb=e=>{var l,a,t,i,r,s,d,c,u,m,x,p,g,j,y,_;let{guardrailId:b,onClose:N,accessToken:w,isAdmin:k}=e,[S,Z]=(0,o.useState)(null),[P,A]=(0,o.useState)(null),[O,z]=(0,o.useState)(!0),[M,F]=(0,o.useState)(!1),[G]=v.Z.useForm(),[K,D]=(0,o.useState)([]),[R,J]=(0,o.useState)({}),[V,U]=(0,o.useState)(null),[W,Y]=(0,o.useState)({}),[H,$]=(0,o.useState)(!1),Q={rules:[],default_action:"deny",on_disallowed_action:"block",violation_message_template:""},[ee,el]=(0,o.useState)(Q),[ei,er]=(0,o.useState)(!1),es=o.useRef({patterns:[],blockedWords:[]}),en=(0,o.useCallback)((e,l)=>{es.current={patterns:e,blockedWords:l}},[]),eo=async()=>{try{var e;if(z(!0),!w)return;let l=await (0,h.getGuardrailInfo)(w,b);if(Z(l),null===(e=l.litellm_params)||void 0===e?void 0:e.pii_entities_config){let e=l.litellm_params.pii_entities_config;if(D([]),J({}),Object.keys(e).length>0){let l=[],a={};Object.entries(e).forEach(e=>{let[t,i]=e;l.push(t),a[t]="string"==typeof i?i:"MASK"}),D(l),J(a)}}else D([]),J({})}catch(e){et.Z.fromBackend("Failed to load guardrail information"),console.error("Error fetching guardrail info:",e)}finally{z(!1)}},ed=async()=>{try{if(!w)return;let e=await (0,h.getGuardrailProviderSpecificParams)(w);A(e)}catch(e){console.error("Error fetching guardrail provider specific params:",e)}},ec=async()=>{try{if(!w)return;let e=await (0,h.getGuardrailUISettings)(w);U(e)}catch(e){console.error("Error fetching guardrail UI settings:",e)}};(0,o.useEffect)(()=>{ed()},[w]),(0,o.useEffect)(()=>{eo(),ec()},[b,w]),(0,o.useEffect)(()=>{if(S&&G){var e;G.setFieldsValue({guardrail_name:S.guardrail_name,...S.litellm_params,guardrail_info:S.guardrail_info?JSON.stringify(S.guardrail_info,null,2):"",...(null===(e=S.litellm_params)||void 0===e?void 0:e.optional_params)&&{optional_params:S.litellm_params.optional_params}})}},[S,P,G]);let eu=(0,o.useCallback)(()=>{var e,l,a,t,i;(null==S?void 0:null===(e=S.litellm_params)||void 0===e?void 0:e.guardrail)==="tool_permission"?el({rules:(null===(l=S.litellm_params)||void 0===l?void 0:l.rules)||[],default_action:((null===(a=S.litellm_params)||void 0===a?void 0:a.default_action)||"deny").toLowerCase(),on_disallowed_action:((null===(t=S.litellm_params)||void 0===t?void 0:t.on_disallowed_action)||"block").toLowerCase(),violation_message_template:(null===(i=S.litellm_params)||void 0===i?void 0:i.violation_message_template)||""}):el(Q),er(!1)},[S]);(0,o.useEffect)(()=>{eu()},[eu]);let em=async e=>{try{var l,a,t,i,r,s,n,o,d,c,u,m;if(!w)return;let x={litellm_params:{}};e.guardrail_name!==S.guardrail_name&&(x.guardrail_name=e.guardrail_name),e.default_on!==(null===(l=S.litellm_params)||void 0===l?void 0:l.default_on)&&(x.litellm_params.default_on=e.default_on);let p=S.guardrail_info,g=e.guardrail_info?JSON.parse(e.guardrail_info):void 0;JSON.stringify(p)!==JSON.stringify(g)&&(x.guardrail_info=g);let f=(null===(a=S.litellm_params)||void 0===a?void 0:a.pii_entities_config)||{},j={};if(K.forEach(e=>{j[e]=R[e]||"MASK"}),JSON.stringify(f)!==JSON.stringify(j)&&(x.litellm_params.pii_entities_config=j),(null===(t=S.litellm_params)||void 0===t?void 0:t.guardrail)==="litellm_content_filter"){let e=(null===(s=S.litellm_params)||void 0===s?void 0:s.patterns)||[],l=(null===(n=S.litellm_params)||void 0===n?void 0:n.blocked_words)||[],a=lf(es.current.patterns,es.current.blockedWords);JSON.stringify(e)!==JSON.stringify(a.patterns)&&(x.litellm_params.patterns=a.patterns),JSON.stringify(l)!==JSON.stringify(a.blocked_words)&&(x.litellm_params.blocked_words=a.blocked_words)}if((null===(i=S.litellm_params)||void 0===i?void 0:i.guardrail)==="tool_permission"){let e=(null===(o=S.litellm_params)||void 0===o?void 0:o.rules)||[],l=ee.rules||[],a=JSON.stringify(e)!==JSON.stringify(l),t=((null===(d=S.litellm_params)||void 0===d?void 0:d.default_action)||"deny").toLowerCase(),i=(ee.default_action||"deny").toLowerCase(),r=t!==i,s=((null===(c=S.litellm_params)||void 0===c?void 0:c.on_disallowed_action)||"block").toLowerCase(),n=(ee.on_disallowed_action||"block").toLowerCase(),m=s!==n,p=(null===(u=S.litellm_params)||void 0===u?void 0:u.violation_message_template)||"",h=ee.violation_message_template||"",g=p!==h;(ei||a||r||m||g)&&(x.litellm_params.rules=l,x.litellm_params.default_action=i,x.litellm_params.on_disallowed_action=n,x.litellm_params.violation_message_template=h||null)}let v=Object.keys(C).find(e=>{var l;return C[e]===(null===(l=S.litellm_params)||void 0===l?void 0:l.guardrail)});console.log("values: ",JSON.stringify(e)),console.log("currentProvider: ",v);let y=(null===(r=S.litellm_params)||void 0===r?void 0:r.guardrail)==="tool_permission";if(P&&v&&!y){let l=P[null===(m=C[v])||void 0===m?void 0:m.toLowerCase()]||{},a=new Set;console.log("providerSpecificParams: ",JSON.stringify(l)),Object.keys(l).forEach(e=>{"optional_params"!==e&&a.add(e)}),l.optional_params&&l.optional_params.fields&&Object.keys(l.optional_params.fields).forEach(e=>{a.add(e)}),console.log("allowedParams: ",a),a.forEach(l=>{var a,t;let i=e[l];(null==i||""===i)&&(i=null===(t=e.optional_params)||void 0===t?void 0:t[l]);let r=null===(a=S.litellm_params)||void 0===a?void 0:a[l];JSON.stringify(i)!==JSON.stringify(r)&&(null!=i&&""!==i?x.litellm_params[l]=i:null!=r&&""!==r&&(x.litellm_params[l]=null))})}if(0===Object.keys(x.litellm_params).length&&delete x.litellm_params,0===Object.keys(x).length){et.Z.info("No changes detected"),F(!1);return}await (0,h.updateGuardrailCall)(w,b,x),et.Z.success("Guardrail updated successfully"),$(!1),eo(),F(!1)}catch(e){console.error("Error updating guardrail:",e),et.Z.fromBackend("Failed to update guardrail")}};if(O)return(0,n.jsx)("div",{className:"p-4",children:"Loading..."});if(!S)return(0,n.jsx)("div",{className:"p-4",children:"Guardrail not found"});let ex=e=>e?new Date(e).toLocaleString():"-",{logo:ep,displayName:eh}=I((null===(l=S.litellm_params)||void 0===l?void 0:l.guardrail)||""),eg=async(e,l)=>{await (0,lv.vQ)(e)&&(Y(e=>({...e,[l]:!0})),setTimeout(()=>{Y(e=>({...e,[l]:!1}))},2e3))},ef="config"===S.guardrail_definition_location;return(0,n.jsxs)("div",{className:"p-4",children:[(0,n.jsxs)("div",{children:[(0,n.jsx)(lu.zx,{icon:lj.Z,variant:"light",onClick:N,className:"mb-4",children:"Back to Guardrails"}),(0,n.jsx)(lu.Dx,{children:S.guardrail_name||"Unnamed Guardrail"}),(0,n.jsxs)("div",{className:"flex items-center cursor-pointer",children:[(0,n.jsx)(lu.xv,{className:"text-gray-500 font-mono",children:S.guardrail_id}),(0,n.jsx)(T.ZP,{type:"text",size:"small",icon:W["guardrail-id"]?(0,n.jsx)(ly.Z,{size:12}):(0,n.jsx)(l_.Z,{size:12}),onClick:()=>eg(S.guardrail_id,"guardrail-id"),className:"left-2 z-10 transition-all duration-200 ".concat(W["guardrail-id"]?"text-green-600 bg-green-50 border-green-200":"text-gray-500 hover:text-gray-700 hover:bg-gray-100")})]})]}),(0,n.jsxs)(lu.v0,{children:[(0,n.jsxs)(lu.td,{className:"mb-4",children:[(0,n.jsx)(lu.OK,{children:"Overview"},"overview"),k?(0,n.jsx)(lu.OK,{children:"Settings"},"settings"):(0,n.jsx)(n.Fragment,{})]}),(0,n.jsxs)(lu.nP,{children:[(0,n.jsxs)(lu.x4,{children:[(0,n.jsxs)(lu.rj,{numItems:1,numItemsSm:2,numItemsLg:3,className:"gap-6",children:[(0,n.jsxs)(lu.Zb,{children:[(0,n.jsx)(lu.xv,{children:"Provider"}),(0,n.jsxs)("div",{className:"mt-2 flex items-center space-x-2",children:[ep&&(0,n.jsx)("img",{src:ep,alt:"".concat(eh," logo"),className:"w-6 h-6",onError:e=>{e.target.style.display="none"}}),(0,n.jsx)(lu.Dx,{children:eh})]})]}),(0,n.jsxs)(lu.Zb,{children:[(0,n.jsx)(lu.xv,{children:"Mode"}),(0,n.jsxs)("div",{className:"mt-2",children:[(0,n.jsx)(lu.Dx,{children:(null===(a=S.litellm_params)||void 0===a?void 0:a.mode)||"-"}),(0,n.jsx)(lu.Ct,{color:(null===(t=S.litellm_params)||void 0===t?void 0:t.default_on)?"green":"gray",children:(null===(i=S.litellm_params)||void 0===i?void 0:i.default_on)?"Default On":"Default Off"})]})]}),(0,n.jsxs)(lu.Zb,{children:[(0,n.jsx)(lu.xv,{children:"Created At"}),(0,n.jsxs)("div",{className:"mt-2",children:[(0,n.jsx)(lu.Dx,{children:ex(S.created_at)}),(0,n.jsxs)(lu.xv,{children:["Last Updated: ",ex(S.updated_at)]})]})]})]}),(null===(r=S.litellm_params)||void 0===r?void 0:r.pii_entities_config)&&Object.keys(S.litellm_params.pii_entities_config).length>0&&(0,n.jsx)(lu.Zb,{className:"mt-6",children:(0,n.jsxs)("div",{className:"flex justify-between items-center",children:[(0,n.jsx)(lu.xv,{className:"font-medium",children:"PII Protection"}),(0,n.jsxs)(lu.Ct,{color:"blue",children:[Object.keys(S.litellm_params.pii_entities_config).length," PII entities configured"]})]})}),(null===(s=S.litellm_params)||void 0===s?void 0:s.pii_entities_config)&&Object.keys(S.litellm_params.pii_entities_config).length>0&&(0,n.jsxs)(lu.Zb,{className:"mt-6",children:[(0,n.jsx)(lu.xv,{className:"mb-4 text-lg font-semibold",children:"PII Entity Configuration"}),(0,n.jsxs)("div",{className:"border rounded-lg overflow-hidden shadow-sm",children:[(0,n.jsxs)("div",{className:"bg-gray-50 px-5 py-3 border-b flex",children:[(0,n.jsx)(lu.xv,{className:"flex-1 font-semibold text-gray-700",children:"Entity Type"}),(0,n.jsx)(lu.xv,{className:"flex-1 font-semibold text-gray-700",children:"Configuration"})]}),(0,n.jsx)("div",{className:"max-h-[400px] overflow-y-auto",children:Object.entries(null===(d=S.litellm_params)||void 0===d?void 0:d.pii_entities_config).map(e=>{let[l,a]=e;return(0,n.jsxs)("div",{className:"px-5 py-3 flex border-b hover:bg-gray-50 transition-colors",children:[(0,n.jsx)(lu.xv,{className:"flex-1 font-medium text-gray-900",children:l}),(0,n.jsx)(lu.xv,{className:"flex-1",children:(0,n.jsxs)("span",{className:"inline-flex items-center gap-1.5 ".concat("MASK"===a?"text-blue-600":"text-red-600"),children:["MASK"===a?(0,n.jsx)(E.Z,{}):(0,n.jsx)(B.Z,{}),String(a)]})})]},l)})})]})]}),(null===(c=S.litellm_params)||void 0===c?void 0:c.guardrail)==="tool_permission"&&(0,n.jsx)(lu.Zb,{className:"mt-6",children:(0,n.jsx)(eU,{value:ee,disabled:!0})}),(0,n.jsx)(lg,{guardrailData:S,guardrailSettings:V,isEditing:!1,accessToken:w})]}),k&&(0,n.jsx)(lu.x4,{children:(0,n.jsxs)(lu.Zb,{children:[(0,n.jsxs)("div",{className:"flex justify-between items-center mb-4",children:[(0,n.jsx)(lu.Dx,{children:"Guardrail Settings"}),ef&&(0,n.jsx)(L.Z,{title:"Guardrail is defined in the config file and cannot be edited.",children:(0,n.jsx)(eR.Z,{})}),!M&&!ef&&(0,n.jsx)(lu.zx,{onClick:()=>F(!0),children:"Edit Settings"})]}),M?(0,n.jsxs)(v.Z,{form:G,onFinish:em,initialValues:{guardrail_name:S.guardrail_name,...S.litellm_params,guardrail_info:S.guardrail_info?JSON.stringify(S.guardrail_info,null,2):"",...(null===(u=S.litellm_params)||void 0===u?void 0:u.optional_params)&&{optional_params:S.litellm_params.optional_params}},layout:"vertical",children:[(0,n.jsx)(v.Z.Item,{label:"Guardrail Name",name:"guardrail_name",rules:[{required:!0,message:"Please input a guardrail name"}],children:(0,n.jsx)(lu.oi,{})}),(0,n.jsx)(v.Z.Item,{label:"Default On",name:"default_on",children:(0,n.jsxs)(f.default,{children:[(0,n.jsx)(f.default.Option,{value:!0,children:"Yes"}),(0,n.jsx)(f.default.Option,{value:!1,children:"No"})]})}),(null===(m=S.litellm_params)||void 0===m?void 0:m.guardrail)==="presidio"&&(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(eK.Z,{orientation:"left",children:"PII Protection"}),(0,n.jsx)("div",{className:"mb-6",children:V&&(0,n.jsx)(q,{entities:V.supported_entities,actions:V.supported_actions,selectedEntities:K,selectedActions:R,onEntitySelect:e=>{D(l=>l.includes(e)?l.filter(l=>l!==e):[...l,e])},onActionSelect:(e,l)=>{J(a=>({...a,[e]:l}))},entityCategories:V.pii_entity_categories})})]}),(0,n.jsx)(lg,{guardrailData:S,guardrailSettings:V,isEditing:!0,accessToken:w,onDataChange:en,onUnsavedChanges:$}),(0,n.jsx)(eK.Z,{orientation:"left",children:"Provider Settings"}),(null===(x=S.litellm_params)||void 0===x?void 0:x.guardrail)==="tool_permission"?(0,n.jsx)(eU,{value:ee,onChange:el}):(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(X,{selectedProvider:Object.keys(C).find(e=>{var l;return C[e]===(null===(l=S.litellm_params)||void 0===l?void 0:l.guardrail)})||null,accessToken:w,providerParams:P,value:S.litellm_params}),P&&(()=>{var e;let l=Object.keys(C).find(e=>{var l;return C[e]===(null===(l=S.litellm_params)||void 0===l?void 0:l.guardrail)});if(!l)return null;let a=P[null===(e=C[l])||void 0===e?void 0:e.toLowerCase()];return a&&a.optional_params?(0,n.jsx)(ea,{optionalParams:a.optional_params,parentFieldKey:"optional_params",values:S.litellm_params}):null})()]}),(0,n.jsx)(eK.Z,{orientation:"left",children:"Advanced Settings"}),(0,n.jsx)(v.Z.Item,{label:"Guardrail Information",name:"guardrail_info",children:(0,n.jsx)(eG.default.TextArea,{rows:5})}),(0,n.jsxs)("div",{className:"flex justify-end gap-2 mt-6",children:[(0,n.jsx)(T.ZP,{onClick:()=>{F(!1),$(!1),eu()},children:"Cancel"}),(0,n.jsx)(lu.zx,{children:"Save Changes"})]})]}):(0,n.jsxs)("div",{className:"space-y-4",children:[(0,n.jsxs)("div",{children:[(0,n.jsx)(lu.xv,{className:"font-medium",children:"Guardrail ID"}),(0,n.jsx)("div",{className:"font-mono",children:S.guardrail_id})]}),(0,n.jsxs)("div",{children:[(0,n.jsx)(lu.xv,{className:"font-medium",children:"Guardrail Name"}),(0,n.jsx)("div",{children:S.guardrail_name||"Unnamed Guardrail"})]}),(0,n.jsxs)("div",{children:[(0,n.jsx)(lu.xv,{className:"font-medium",children:"Provider"}),(0,n.jsx)("div",{children:eh})]}),(0,n.jsxs)("div",{children:[(0,n.jsx)(lu.xv,{className:"font-medium",children:"Mode"}),(0,n.jsx)("div",{children:(null===(p=S.litellm_params)||void 0===p?void 0:p.mode)||"-"})]}),(0,n.jsxs)("div",{children:[(0,n.jsx)(lu.xv,{className:"font-medium",children:"Default On"}),(0,n.jsx)(lu.Ct,{color:(null===(g=S.litellm_params)||void 0===g?void 0:g.default_on)?"green":"gray",children:(null===(j=S.litellm_params)||void 0===j?void 0:j.default_on)?"Yes":"No"})]}),(null===(y=S.litellm_params)||void 0===y?void 0:y.pii_entities_config)&&Object.keys(S.litellm_params.pii_entities_config).length>0&&(0,n.jsxs)("div",{children:[(0,n.jsx)(lu.xv,{className:"font-medium",children:"PII Protection"}),(0,n.jsx)("div",{className:"mt-2",children:(0,n.jsxs)(lu.Ct,{color:"blue",children:[Object.keys(S.litellm_params.pii_entities_config).length," PII entities configured"]})})]}),(0,n.jsxs)("div",{children:[(0,n.jsx)(lu.xv,{className:"font-medium",children:"Created At"}),(0,n.jsx)("div",{children:ex(S.created_at)})]}),(0,n.jsxs)("div",{children:[(0,n.jsx)(lu.xv,{className:"font-medium",children:"Last Updated"}),(0,n.jsx)("div",{children:ex(S.updated_at)})]}),(null===(_=S.litellm_params)||void 0===_?void 0:_.guardrail)==="tool_permission"&&(0,n.jsx)(eU,{value:ee,disabled:!0})]})]})})]})]})]})},lN=a(96761),lw=a(35631),lk=a(29436),lC=a(41169),lS=a(23639),lZ=a(77565),lP=a(70464),lA=a(83669),lO=a(5540);let{Text:lI}=g.default;var lL=function(e){let{results:l,errors:a}=e,[t,i]=(0,o.useState)(new Set),r=e=>{let l=new Set(t);l.has(e)?l.delete(e):l.add(e),i(l)},s=async e=>{try{if(navigator.clipboard&&window.isSecureContext)return await navigator.clipboard.writeText(e),!0;{let l=document.createElement("textarea");l.value=e,l.style.position="fixed",l.style.opacity="0",document.body.appendChild(l),l.focus(),l.select();let a=document.execCommand("copy");if(document.body.removeChild(l),!a)throw Error("execCommand failed");return!0}}catch(e){return console.error("Copy failed:",e),!1}};return l||a?(0,n.jsxs)("div",{className:"space-y-3 pt-4 border-t border-gray-200",children:[(0,n.jsx)("h3",{className:"text-sm font-semibold text-gray-900",children:"Results"}),l&&l.map(e=>{let l=t.has(e.guardrailName);return(0,n.jsx)(lx.Z,{className:"bg-green-50 border-green-200",children:(0,n.jsxs)("div",{className:"space-y-3",children:[(0,n.jsxs)("div",{className:"flex items-center justify-between",children:[(0,n.jsxs)("div",{className:"flex items-center space-x-2 cursor-pointer flex-1",onClick:()=>r(e.guardrailName),children:[l?(0,n.jsx)(lZ.Z,{className:"text-gray-500 text-xs"}):(0,n.jsx)(lP.Z,{className:"text-gray-500 text-xs"}),(0,n.jsx)(lA.Z,{className:"text-green-600 text-lg"}),(0,n.jsx)("span",{className:"text-sm font-medium text-green-800",children:e.guardrailName})]}),(0,n.jsxs)("div",{className:"flex items-center gap-3",children:[(0,n.jsxs)("div",{className:"flex items-center space-x-1 text-xs text-gray-600",children:[(0,n.jsx)(lO.Z,{}),(0,n.jsxs)("span",{className:"font-medium",children:[e.latency,"ms"]})]}),!l&&(0,n.jsx)(d.Z,{size:"xs",variant:"secondary",icon:lS.Z,onClick:async()=>{await s(e.response_text)?et.Z.success("Result copied to clipboard"):et.Z.fromBackend("Failed to copy result")},children:"Copy"})]})]}),!l&&(0,n.jsxs)(n.Fragment,{children:[(0,n.jsxs)("div",{className:"bg-white border border-green-200 rounded p-3",children:[(0,n.jsx)("label",{className:"text-xs font-medium text-gray-600 mb-2 block",children:"Output Text"}),(0,n.jsx)("div",{className:"font-mono text-sm text-gray-900 whitespace-pre-wrap break-words",children:e.response_text})]}),(0,n.jsxs)("div",{className:"text-xs text-gray-600",children:[(0,n.jsx)("span",{className:"font-medium",children:"Characters:"})," ",e.response_text.length]})]})]})},e.guardrailName)}),a&&a.map(e=>{let l=t.has(e.guardrailName);return(0,n.jsx)(lx.Z,{className:"bg-red-50 border-red-200",children:(0,n.jsxs)("div",{className:"flex items-start space-x-2",children:[(0,n.jsx)("div",{className:"cursor-pointer mt-0.5",onClick:()=>r(e.guardrailName),children:l?(0,n.jsx)(lZ.Z,{className:"text-gray-500 text-xs"}):(0,n.jsx)(lP.Z,{className:"text-gray-500 text-xs"})}),(0,n.jsx)("div",{className:"text-red-600 mt-0.5",children:(0,n.jsx)("svg",{className:"w-5 h-5",fill:"currentColor",viewBox:"0 0 20 20",children:(0,n.jsx)("path",{fillRule:"evenodd",d:"M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z",clipRule:"evenodd"})})}),(0,n.jsxs)("div",{className:"flex-1",children:[(0,n.jsxs)("div",{className:"flex items-center justify-between mb-1",children:[(0,n.jsxs)("p",{className:"text-sm font-medium text-red-800 cursor-pointer",onClick:()=>r(e.guardrailName),children:[e.guardrailName," - Error"]}),(0,n.jsxs)("div",{className:"flex items-center space-x-1 text-xs text-gray-600",children:[(0,n.jsx)(lO.Z,{}),(0,n.jsxs)("span",{className:"font-medium",children:[e.latency,"ms"]})]})]}),!l&&(0,n.jsx)("p",{className:"text-sm text-red-700 mt-1",children:e.error.message})]})]})},e.guardrailName)})]}):null};let{TextArea:lT}=eG.default,{Text:lz}=g.default;var lE=function(e){let{guardrailNames:l,onSubmit:a,isLoading:t,results:i,errors:r,onClose:s}=e,[d,c]=(0,o.useState)(""),u=()=>{if(!d.trim()){et.Z.fromBackend("Please enter text to test");return}a(d)},m=async e=>{try{if(navigator.clipboard&&window.isSecureContext)return await navigator.clipboard.writeText(e),!0;{let l=document.createElement("textarea");l.value=e,l.style.position="fixed",l.style.opacity="0",document.body.appendChild(l),l.focus(),l.select();let a=document.execCommand("copy");if(document.body.removeChild(l),!a)throw Error("execCommand failed");return!0}}catch(e){return console.error("Copy failed:",e),!1}},x=async()=>{await m(d)?et.Z.success("Input copied to clipboard"):et.Z.fromBackend("Failed to copy input")};return(0,n.jsxs)("div",{className:"space-y-4 h-full flex flex-col",children:[(0,n.jsx)("div",{className:"flex items-center justify-between pb-3 border-b border-gray-200",children:(0,n.jsx)("div",{className:"flex items-center space-x-3",children:(0,n.jsxs)("div",{className:"flex-1 min-w-0",children:[(0,n.jsxs)("div",{className:"flex items-center space-x-2 mb-1",children:[(0,n.jsx)("h2",{className:"text-lg font-semibold text-gray-900",children:"Test Guardrails:"}),(0,n.jsx)("div",{className:"flex flex-wrap gap-2",children:l.map(e=>(0,n.jsx)("div",{className:"inline-flex items-center space-x-1 bg-blue-50 px-3 py-1 rounded-md border border-blue-200",children:(0,n.jsx)("span",{className:"font-mono text-blue-700 font-medium text-sm",children:e})},e))})]}),(0,n.jsxs)("p",{className:"text-sm text-gray-500",children:["Test ",l.length>1?"guardrails":"guardrail"," and compare results"]})]})})}),(0,n.jsxs)("div",{className:"flex-1 overflow-auto space-y-4",children:[(0,n.jsxs)("div",{className:"space-y-3",children:[(0,n.jsxs)("div",{children:[(0,n.jsxs)("div",{className:"flex justify-between items-center mb-2",children:[(0,n.jsxs)("div",{className:"flex items-center gap-2",children:[(0,n.jsx)("label",{className:"text-sm font-medium text-gray-700",children:"Input Text"}),(0,n.jsx)(L.Z,{title:"Press Enter to submit. Use Shift+Enter for new line.",children:(0,n.jsx)(eR.Z,{className:"text-gray-400 cursor-help"})})]}),d&&(0,n.jsx)(ed.z,{size:"xs",variant:"secondary",icon:lS.Z,onClick:x,children:"Copy Input"})]}),(0,n.jsx)(lT,{value:d,onChange:e=>c(e.target.value),onKeyDown:e=>{"Enter"!==e.key||e.shiftKey||e.ctrlKey||e.metaKey||(e.preventDefault(),u())},placeholder:"Enter text to test with guardrails...",rows:8,className:"font-mono text-sm"}),(0,n.jsxs)("div",{className:"flex justify-between items-center mt-1",children:[(0,n.jsxs)(lz,{className:"text-xs text-gray-500",children:["Press ",(0,n.jsx)("kbd",{className:"px-1 py-0.5 bg-gray-100 border border-gray-300 rounded text-xs",children:"Enter"})," to submit • ",(0,n.jsx)("kbd",{className:"px-1 py-0.5 bg-gray-100 border border-gray-300 rounded text-xs",children:"Shift+Enter"})," for new line"]}),(0,n.jsxs)(lz,{className:"text-xs text-gray-500",children:["Characters: ",d.length]})]})]}),(0,n.jsx)("div",{className:"pt-2",children:(0,n.jsx)(ed.z,{onClick:u,loading:t,disabled:!d.trim(),className:"w-full",children:t?"Testing ".concat(l.length," guardrail").concat(l.length>1?"s":"","..."):"Test ".concat(l.length," guardrail").concat(l.length>1?"s":"")})})]}),(0,n.jsx)(lL,{results:i,errors:r})]})]})},lB=e=>{let{guardrailsList:l,isLoading:a,accessToken:t,onClose:i}=e,[r,s]=(0,o.useState)(new Set),[d,c]=(0,o.useState)(""),[u,m]=(0,o.useState)([]),[x,p]=(0,o.useState)([]),[g,f]=(0,o.useState)(!1),j=l.filter(e=>{var l;return null===(l=e.guardrail_name)||void 0===l?void 0:l.toLowerCase().includes(d.toLowerCase())}),v=e=>{let l=new Set(r);l.has(e)?l.delete(e):l.add(e),s(l)},y=async e=>{if(0===r.size||!t)return;f(!0),m([]),p([]);let l=[],a=[];await Promise.all(Array.from(r).map(async i=>{let r=Date.now();try{let a=await (0,h.applyGuardrail)(t,i,e,null,null),s=Date.now()-r;l.push({guardrailName:i,response_text:a.response_text,latency:s})}catch(l){let e=Date.now()-r;console.error("Error testing guardrail ".concat(i,":"),l),a.push({guardrailName:i,error:l,latency:e})}})),m(l),p(a),f(!1),l.length>0&&et.Z.success("".concat(l.length," guardrail").concat(l.length>1?"s":""," applied successfully")),a.length>0&&et.Z.fromBackend("".concat(a.length," guardrail").concat(a.length>1?"s":""," failed"))};return(0,n.jsx)("div",{className:"w-full h-[calc(100vh-200px)]",children:(0,n.jsx)(lx.Z,{className:"h-full",children:(0,n.jsxs)("div",{className:"flex h-full",children:[(0,n.jsxs)("div",{className:"w-1/4 border-r border-gray-200 flex flex-col overflow-hidden",children:[(0,n.jsx)("div",{className:"p-4 border-b border-gray-200",children:(0,n.jsxs)("div",{className:"mb-3",children:[(0,n.jsx)(lN.Z,{className:"text-lg font-semibold mb-3",children:"Guardrails"}),(0,n.jsx)(eg.Z,{icon:lk.Z,placeholder:"Search guardrails...",value:d,onValueChange:c})]})}),(0,n.jsx)("div",{className:"flex-1 overflow-auto",children:a?(0,n.jsx)("div",{className:"flex items-center justify-center h-32",children:(0,n.jsx)(H.Z,{})}):0===j.length?(0,n.jsx)("div",{className:"p-4",children:(0,n.jsx)(eD.Z,{description:d?"No guardrails match your search":"No guardrails available"})}):(0,n.jsx)(lw.Z,{dataSource:j,renderItem:e=>(0,n.jsx)(lw.Z.Item,{onClick:()=>{e.guardrail_name&&v(e.guardrail_name)},className:"cursor-pointer hover:bg-gray-50 transition-colors px-4 ".concat(r.has(e.guardrail_name||"")?"bg-blue-50 border-l-4 border-l-blue-500":"border-l-4 border-l-transparent"),children:(0,n.jsx)(lw.Z.Item.Meta,{avatar:(0,n.jsx)(z.Z,{checked:r.has(e.guardrail_name||""),onClick:l=>{l.stopPropagation(),e.guardrail_name&&v(e.guardrail_name)}}),title:(0,n.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,n.jsx)(lC.Z,{className:"text-gray-400"}),(0,n.jsx)("span",{className:"font-medium text-gray-900",children:e.guardrail_name})]}),description:(0,n.jsxs)("div",{className:"text-xs space-y-1 mt-1",children:[(0,n.jsxs)("div",{children:[(0,n.jsx)("span",{className:"font-medium",children:"Type: "}),(0,n.jsx)("span",{className:"text-gray-600",children:e.litellm_params.guardrail})]}),(0,n.jsxs)("div",{children:[(0,n.jsx)("span",{className:"font-medium",children:"Mode: "}),(0,n.jsx)("span",{className:"text-gray-600",children:e.litellm_params.mode})]})]})})})})}),(0,n.jsx)("div",{className:"p-3 border-t border-gray-200 bg-gray-50",children:(0,n.jsxs)(lp.Z,{className:"text-xs text-gray-600",children:[r.size," of ",j.length," selected"]})})]}),(0,n.jsxs)("div",{className:"w-3/4 flex flex-col bg-white",children:[(0,n.jsx)("div",{className:"p-4 border-b border-gray-200 flex justify-between items-center",children:(0,n.jsx)(lN.Z,{className:"text-xl font-semibold mb-0",children:"Guardrail Testing Playground"})}),(0,n.jsx)("div",{className:"flex-1 overflow-auto p-4",children:0===r.size?(0,n.jsxs)("div",{className:"h-full flex flex-col items-center justify-center text-gray-400",children:[(0,n.jsx)(lC.Z,{style:{fontSize:"48px",marginBottom:"16px"}}),(0,n.jsx)(lp.Z,{className:"text-lg font-medium text-gray-600 mb-2",children:"Select Guardrails to Test"}),(0,n.jsx)(lp.Z,{className:"text-center text-gray-500 max-w-md",children:"Choose one or more guardrails from the left sidebar to start testing and comparing results."})]}):(0,n.jsx)("div",{className:"h-full",children:(0,n.jsx)(lE,{guardrailNames:Array.from(r),onSubmit:y,results:u.length>0?u:null,errors:x.length>0?x:null,isLoading:g,onClose:()=>s(new Set)})})})]})]})})})},lM=a(21609),lF=e=>{let{accessToken:l,userRole:a}=e,[t,i]=(0,o.useState)([]),[r,s]=(0,o.useState)(!1),[g,f]=(0,o.useState)(!1),[j,v]=(0,o.useState)(!1),[y,_]=(0,o.useState)(null),[b,N]=(0,o.useState)(!1),[w,k]=(0,o.useState)(null),[C,S]=(0,o.useState)(0),Z=!!a&&(0,lc.tY)(a),P=async()=>{if(l){f(!0);try{let e=await (0,h.getGuardrailsList)(l);console.log("guardrails: ".concat(JSON.stringify(e))),i(e.guardrails)}catch(e){console.error("Error fetching guardrails:",e)}finally{f(!1)}}};(0,o.useEffect)(()=>{P()},[l]);let A=async()=>{if(y&&l){v(!0);try{await (0,h.deleteGuardrailCall)(l,y.guardrail_id),et.Z.success('Guardrail "'.concat(y.guardrail_name,'" deleted successfully')),await P()}catch(e){console.error("Error deleting guardrail:",e),et.Z.fromBackend("Failed to delete guardrail")}finally{v(!1),N(!1),_(null)}}},O=y&&y.litellm_params?I(y.litellm_params.guardrail).displayName:void 0;return(0,n.jsx)("div",{className:"w-full mx-auto flex-auto overflow-y-auto m-8 p-2",children:(0,n.jsxs)(u.Z,{index:C,onIndexChange:S,children:[(0,n.jsxs)(m.Z,{className:"mb-4",children:[(0,n.jsx)(c.Z,{children:"Guardrails"}),(0,n.jsx)(c.Z,{disabled:!l||0===t.length,children:"Test Playground"})]}),(0,n.jsxs)(p.Z,{children:[(0,n.jsxs)(x.Z,{children:[(0,n.jsx)("div",{className:"flex justify-between items-center mb-4",children:(0,n.jsx)(d.Z,{onClick:()=>{w&&k(null),s(!0)},disabled:!l,children:"+ Add New Guardrail"})}),w?(0,n.jsx)(lb,{guardrailId:w,onClose:()=>k(null),accessToken:l,isAdmin:Z}):(0,n.jsx)(ld,{guardrailsList:t,isLoading:g,onDeleteClick:(e,l)=>{_(t.find(l=>l.guardrail_id===e)||null),N(!0)},accessToken:l,onGuardrailUpdated:P,isAdmin:Z,onGuardrailClick:e=>k(e)}),(0,n.jsx)(eX,{visible:r,onClose:()=>{s(!1)},accessToken:l,onSuccess:()=>{P()}}),(0,n.jsx)(lM.Z,{isOpen:b,title:"Delete Guardrail",message:"Are you sure you want to delete guardrail: ".concat(null==y?void 0:y.guardrail_name,"? This action cannot be undone."),resourceInformationTitle:"Guardrail Information",resourceInformation:[{label:"Name",value:null==y?void 0:y.guardrail_name},{label:"ID",value:null==y?void 0:y.guardrail_id,code:!0},{label:"Provider",value:O},{label:"Mode",value:null==y?void 0:y.litellm_params.mode},{label:"Default On",value:(null==y?void 0:y.litellm_params.default_on)?"Yes":"No"}],onCancel:()=>{N(!1),_(null)},onOk:A,confirmLoading:j})]}),(0,n.jsx)(x.Z,{children:(0,n.jsx)(lB,{guardrailsList:t,isLoading:g,accessToken:l,onClose:()=>S(0)})})]})]})})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1414-2770d1155b664522.js b/litellm/proxy/_experimental/out/_next/static/chunks/1414-2770d1155b664522.js deleted file mode 100644 index 937cb546ae2..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/1414-2770d1155b664522.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1414],{41649:function(e,t,r){r.d(t,{Z:function(){return h}});var o=r(5853),n=r(2265),a=r(47187),i=r(7084),s=r(26898),l=r(13241),c=r(1153);let d={xs:{paddingX:"px-2",paddingY:"py-0.5",fontSize:"text-xs"},sm:{paddingX:"px-2.5",paddingY:"py-0.5",fontSize:"text-sm"},md:{paddingX:"px-3",paddingY:"py-0.5",fontSize:"text-md"},lg:{paddingX:"px-3.5",paddingY:"py-0.5",fontSize:"text-lg"},xl:{paddingX:"px-4",paddingY:"py-1",fontSize:"text-xl"}},u={xs:{height:"h-4",width:"w-4"},sm:{height:"h-4",width:"w-4"},md:{height:"h-4",width:"w-4"},lg:{height:"h-5",width:"w-5"},xl:{height:"h-6",width:"w-6"}},p=(0,c.fn)("Badge"),h=n.forwardRef((e,t)=>{let{color:r,icon:h,size:m=i.u8.SM,tooltip:g,className:b,children:f}=e,v=(0,o._T)(e,["color","icon","size","tooltip","className","children"]),y=h||null,{tooltipProps:k,getReferenceProps:x}=(0,a.l)();return n.createElement("span",Object.assign({ref:(0,c.lq)([t,k.refs.setReference]),className:(0,l.q)(p("root"),"w-max shrink-0 inline-flex justify-center items-center cursor-default rounded-tremor-small ring-1 ring-inset",r?(0,l.q)((0,c.bM)(r,s.K.background).bgColor,(0,c.bM)(r,s.K.iconText).textColor,(0,c.bM)(r,s.K.iconRing).ringColor,"bg-opacity-10 ring-opacity-20","dark:bg-opacity-5 dark:ring-opacity-60"):(0,l.q)("bg-tremor-brand-faint text-tremor-brand-emphasis ring-tremor-brand/20","dark:bg-dark-tremor-brand-muted/50 dark:text-dark-tremor-brand dark:ring-dark-tremor-subtle/20"),d[m].paddingX,d[m].paddingY,d[m].fontSize,b)},x,v),n.createElement(a.Z,Object.assign({text:g},k)),y?n.createElement(y,{className:(0,l.q)(p("icon"),"shrink-0 -ml-1 mr-1.5",u[m].height,u[m].width)}):null,n.createElement("span",{className:(0,l.q)(p("text"),"whitespace-nowrap")},f))});h.displayName="Badge"},47323:function(e,t,r){r.d(t,{Z:function(){return g}});var o=r(5853),n=r(2265),a=r(47187),i=r(7084),s=r(13241),l=r(1153),c=r(26898);let d={xs:{paddingX:"px-1.5",paddingY:"py-1.5"},sm:{paddingX:"px-1.5",paddingY:"py-1.5"},md:{paddingX:"px-2",paddingY:"py-2"},lg:{paddingX:"px-2",paddingY:"py-2"},xl:{paddingX:"px-2.5",paddingY:"py-2.5"}},u={xs:{height:"h-3",width:"w-3"},sm:{height:"h-5",width:"w-5"},md:{height:"h-5",width:"w-5"},lg:{height:"h-7",width:"w-7"},xl:{height:"h-9",width:"w-9"}},p={simple:{rounded:"",border:"",ring:"",shadow:""},light:{rounded:"rounded-tremor-default",border:"",ring:"",shadow:""},shadow:{rounded:"rounded-tremor-default",border:"border",ring:"",shadow:"shadow-tremor-card dark:shadow-dark-tremor-card"},solid:{rounded:"rounded-tremor-default",border:"border-2",ring:"ring-1",shadow:""},outlined:{rounded:"rounded-tremor-default",border:"border",ring:"ring-2",shadow:""}},h=(e,t)=>{switch(e){case"simple":return{textColor:t?(0,l.bM)(t,c.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:"",borderColor:"",ringColor:""};case"light":return{textColor:t?(0,l.bM)(t,c.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,s.q)((0,l.bM)(t,c.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand-muted dark:bg-dark-tremor-brand-muted",borderColor:"",ringColor:""};case"shadow":return{textColor:t?(0,l.bM)(t,c.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,s.q)((0,l.bM)(t,c.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:"border-tremor-border dark:border-dark-tremor-border",ringColor:""};case"solid":return{textColor:t?(0,l.bM)(t,c.K.text).textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",bgColor:t?(0,s.q)((0,l.bM)(t,c.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand dark:bg-dark-tremor-brand",borderColor:"border-tremor-brand-inverted dark:border-dark-tremor-brand-inverted",ringColor:"ring-tremor-ring dark:ring-dark-tremor-ring"};case"outlined":return{textColor:t?(0,l.bM)(t,c.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,s.q)((0,l.bM)(t,c.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:t?(0,l.bM)(t,c.K.ring).borderColor:"border-tremor-brand-subtle dark:border-dark-tremor-brand-subtle",ringColor:t?(0,s.q)((0,l.bM)(t,c.K.ring).ringColor,"ring-opacity-40"):"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"}}},m=(0,l.fn)("Icon"),g=n.forwardRef((e,t)=>{let{icon:r,variant:c="simple",tooltip:g,size:b=i.u8.SM,color:f,className:v}=e,y=(0,o._T)(e,["icon","variant","tooltip","size","color","className"]),k=h(c,f),{tooltipProps:x,getReferenceProps:w}=(0,a.l)();return n.createElement("span",Object.assign({ref:(0,l.lq)([t,x.refs.setReference]),className:(0,s.q)(m("root"),"inline-flex shrink-0 items-center justify-center",k.bgColor,k.textColor,k.borderColor,k.ringColor,p[c].rounded,p[c].border,p[c].shadow,p[c].ring,d[b].paddingX,d[b].paddingY,v)},w,y),n.createElement(a.Z,Object.assign({text:g},x)),n.createElement(r,{className:(0,s.q)(m("icon"),"shrink-0",u[b].height,u[b].width)}))});g.displayName="Icon"},59341:function(e,t,r){r.d(t,{Z:function(){return R}});var o=r(5853),n=r(71049),a=r(11323),i=r(2265),s=r(66797),l=r(40099),c=r(74275),d=r(59456),u=r(93980),p=r(65573),h=r(67561),m=r(87550),g=r(628),b=r(80281),f=r(31370),v=r(20131),y=r(38929),k=r(52307),x=r(52724),w=r(7935);let C=(0,i.createContext)(null);C.displayName="GroupContext";let O=i.Fragment,E=Object.assign((0,y.yV)(function(e,t){var r;let o=(0,i.useId)(),O=(0,b.Q)(),E=(0,m.B)(),{id:j=O||"headlessui-switch-".concat(o),disabled:S=E||!1,checked:M,defaultChecked:N,onChange:P,name:Z,value:R,form:L,autoFocus:z=!1,...I}=e,q=(0,i.useContext)(C),[T,B]=(0,i.useState)(null),K=(0,i.useRef)(null),W=(0,h.T)(K,t,null===q?null:q.setSwitch,B),D=(0,c.L)(N),[F,H]=(0,l.q)(M,P,null!=D&&D),_=(0,d.G)(),[V,X]=(0,i.useState)(!1),Y=(0,u.z)(()=>{X(!0),null==H||H(!F),_.nextFrame(()=>{X(!1)})}),A=(0,u.z)(e=>{if((0,f.P)(e.currentTarget))return e.preventDefault();e.preventDefault(),Y()}),G=(0,u.z)(e=>{e.key===x.R.Space?(e.preventDefault(),Y()):e.key===x.R.Enter&&(0,v.g)(e.currentTarget)}),U=(0,u.z)(e=>e.preventDefault()),$=(0,w.wp)(),Q=(0,k.zH)(),{isFocusVisible:J,focusProps:ee}=(0,n.F)({autoFocus:z}),{isHovered:et,hoverProps:er}=(0,a.X)({isDisabled:S}),{pressed:eo,pressProps:en}=(0,s.x)({disabled:S}),ea=(0,i.useMemo)(()=>({checked:F,disabled:S,hover:et,focus:J,active:eo,autofocus:z,changing:V}),[F,et,J,eo,S,V,z]),ei=(0,y.dG)({id:j,ref:W,role:"switch",type:(0,p.f)(e,T),tabIndex:-1===e.tabIndex?0:null!=(r=e.tabIndex)?r:0,"aria-checked":F,"aria-labelledby":$,"aria-describedby":Q,disabled:S||void 0,autoFocus:z,onClick:A,onKeyUp:G,onKeyPress:U},ee,er,en),es=(0,i.useCallback)(()=>{if(void 0!==D)return null==H?void 0:H(D)},[H,D]),el=(0,y.L6)();return i.createElement(i.Fragment,null,null!=Z&&i.createElement(g.Mt,{disabled:S,data:{[Z]:R||"on"},overrides:{type:"checkbox",checked:F},form:L,onReset:es}),el({ourProps:ei,theirProps:I,slot:ea,defaultTag:"button",name:"Switch"}))}),{Group:function(e){var t;let[r,o]=(0,i.useState)(null),[n,a]=(0,w.bE)(),[s,l]=(0,k.fw)(),c=(0,i.useMemo)(()=>({switch:r,setSwitch:o}),[r,o]),d=(0,y.L6)();return i.createElement(l,{name:"Switch.Description",value:s},i.createElement(a,{name:"Switch.Label",value:n,props:{htmlFor:null==(t=c.switch)?void 0:t.id,onClick(e){r&&(e.currentTarget instanceof HTMLLabelElement&&e.preventDefault(),r.click(),r.focus({preventScroll:!0}))}}},i.createElement(C.Provider,{value:c},d({ourProps:{},theirProps:e,slot:{},defaultTag:O,name:"Switch.Group"}))))},Label:w.__,Description:k.dk});var j=r(44140),S=r(26898),M=r(13241),N=r(1153),P=r(47187);let Z=(0,N.fn)("Switch"),R=i.forwardRef((e,t)=>{let{checked:r,defaultChecked:n=!1,onChange:a,color:s,name:l,error:c,errorMessage:d,disabled:u,required:p,tooltip:h,id:m}=e,g=(0,o._T)(e,["checked","defaultChecked","onChange","color","name","error","errorMessage","disabled","required","tooltip","id"]),b={bgColor:s?(0,N.bM)(s,S.K.background).bgColor:"bg-tremor-brand dark:bg-dark-tremor-brand",ringColor:s?(0,N.bM)(s,S.K.ring).ringColor:"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"},[f,v]=(0,j.Z)(n,r),[y,k]=(0,i.useState)(!1),{tooltipProps:x,getReferenceProps:w}=(0,P.l)(300);return i.createElement("div",{className:"flex flex-row items-center justify-start"},i.createElement(P.Z,Object.assign({text:h},x)),i.createElement("div",Object.assign({ref:(0,N.lq)([t,x.refs.setReference]),className:(0,M.q)(Z("root"),"flex flex-row relative h-5")},g,w),i.createElement("input",{type:"checkbox",className:(0,M.q)(Z("input"),"absolute w-5 h-5 cursor-pointer left-0 top-0 opacity-0"),name:l,required:p,checked:f,onChange:e=>{e.preventDefault()}}),i.createElement(E,{checked:f,onChange:e=>{v(e),null==a||a(e)},disabled:u,className:(0,M.q)(Z("switch"),"w-10 h-5 group relative inline-flex shrink-0 cursor-pointer items-center justify-center rounded-tremor-full","focus:outline-none",u?"cursor-not-allowed":""),onFocus:()=>k(!0),onBlur:()=>k(!1),id:m},i.createElement("span",{className:(0,M.q)(Z("sr-only"),"sr-only")},"Switch ",f?"on":"off"),i.createElement("span",{"aria-hidden":"true",className:(0,M.q)(Z("background"),f?b.bgColor:"bg-tremor-border dark:bg-dark-tremor-border","pointer-events-none absolute mx-auto h-3 w-9 rounded-tremor-full transition-colors duration-100 ease-in-out")}),i.createElement("span",{"aria-hidden":"true",className:(0,M.q)(Z("round"),f?(0,M.q)(b.bgColor,"translate-x-5 border-tremor-background dark:border-dark-tremor-background"):"translate-x-0 bg-tremor-border dark:bg-dark-tremor-border border-tremor-background dark:border-dark-tremor-background","pointer-events-none absolute left-0 inline-block h-5 w-5 transform rounded-tremor-full border-2 shadow-tremor-input duration-100 ease-in-out transition",y?(0,M.q)("ring-2",b.ringColor):"")}))),c&&d?i.createElement("p",{className:(0,M.q)(Z("errorMessage"),"text-sm text-red-500 mt-1 ")},d):null)});R.displayName="Switch"},44140:function(e,t,r){r.d(t,{Z:function(){return n}});var o=r(2265);let n=(e,t)=>{let r=void 0!==t,[n,a]=(0,o.useState)(e);return[r?t:n,e=>{r||a(e)}]}},92570:function(e,t,r){r.d(t,{Z:function(){return o}});let o=e=>e?"function"==typeof e?e():e:null},867:function(e,t,r){r.d(t,{Z:function(){return E}});var o=r(2265),n=r(54537),a=r(36760),i=r.n(a),s=r(50506),l=r(18694),c=r(71744),d=r(79326),u=r(59367),p=r(92570),h=r(5545),m=r(51248),g=r(55274),b=r(37381),f=r(20435),v=r(99320);let y=e=>{let{componentCls:t,iconCls:r,antCls:o,zIndexPopup:n,colorText:a,colorWarning:i,marginXXS:s,marginXS:l,fontSize:c,fontWeightStrong:d,colorTextHeading:u}=e;return{[t]:{zIndex:n,["&".concat(o,"-popover")]:{fontSize:c},["".concat(t,"-message")]:{marginBottom:l,display:"flex",flexWrap:"nowrap",alignItems:"start",["> ".concat(t,"-message-icon ").concat(r)]:{color:i,fontSize:c,lineHeight:1,marginInlineEnd:l},["".concat(t,"-title")]:{fontWeight:d,color:u,"&:only-child":{fontWeight:"normal"}},["".concat(t,"-description")]:{marginTop:s,color:a}},["".concat(t,"-buttons")]:{textAlign:"end",whiteSpace:"nowrap",button:{marginInlineStart:l}}}}};var k=(0,v.I$)("Popconfirm",e=>y(e),e=>{let{zIndexPopupBase:t}=e;return{zIndexPopup:t+60}},{resetStyle:!1}),x=function(e,t){var r={};for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&0>t.indexOf(o)&&(r[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var n=0,o=Object.getOwnPropertySymbols(e);nt.indexOf(o[n])&&Object.prototype.propertyIsEnumerable.call(e,o[n])&&(r[o[n]]=e[o[n]]);return r};let w=e=>{let{prefixCls:t,okButtonProps:r,cancelButtonProps:a,title:i,description:s,cancelText:l,okText:d,okType:f="primary",icon:v=o.createElement(n.Z,null),showCancel:y=!0,close:k,onConfirm:x,onCancel:w,onPopupClick:C}=e,{getPrefixCls:O}=o.useContext(c.E_),[E]=(0,g.Z)("Popconfirm",b.Z.Popconfirm),j=(0,p.Z)(i),S=(0,p.Z)(s);return o.createElement("div",{className:"".concat(t,"-inner-content"),onClick:C},o.createElement("div",{className:"".concat(t,"-message")},v&&o.createElement("span",{className:"".concat(t,"-message-icon")},v),o.createElement("div",{className:"".concat(t,"-message-text")},j&&o.createElement("div",{className:"".concat(t,"-title")},j),S&&o.createElement("div",{className:"".concat(t,"-description")},S))),o.createElement("div",{className:"".concat(t,"-buttons")},y&&o.createElement(h.ZP,Object.assign({onClick:w,size:"small"},a),l||(null==E?void 0:E.cancelText)),o.createElement(u.Z,{buttonProps:Object.assign(Object.assign({size:"small"},(0,m.nx)(f)),r),actionFn:x,close:k,prefixCls:O("btn"),quitOnNullishReturnValue:!0,emitEvent:!0},d||(null==E?void 0:E.okText))))};var C=function(e,t){var r={};for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&0>t.indexOf(o)&&(r[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var n=0,o=Object.getOwnPropertySymbols(e);nt.indexOf(o[n])&&Object.prototype.propertyIsEnumerable.call(e,o[n])&&(r[o[n]]=e[o[n]]);return r};let O=o.forwardRef((e,t)=>{var r,a;let{prefixCls:u,placement:p="top",trigger:h="click",okType:m="primary",icon:g=o.createElement(n.Z,null),children:b,overlayClassName:f,onOpenChange:v,onVisibleChange:y,overlayStyle:x,styles:O,classNames:E}=e,j=C(e,["prefixCls","placement","trigger","okType","icon","children","overlayClassName","onOpenChange","onVisibleChange","overlayStyle","styles","classNames"]),{getPrefixCls:S,className:M,style:N,classNames:P,styles:Z}=(0,c.dj)("popconfirm"),[R,L]=(0,s.Z)(!1,{value:null!==(r=e.open)&&void 0!==r?r:e.visible,defaultValue:null!==(a=e.defaultOpen)&&void 0!==a?a:e.defaultVisible}),z=(e,t)=>{L(e,!0),null==y||y(e),null==v||v(e,t)},I=S("popconfirm",u),q=i()(I,M,f,P.root,null==E?void 0:E.root),T=i()(P.body,null==E?void 0:E.body),[B]=k(I);return B(o.createElement(d.Z,Object.assign({},(0,l.Z)(j,["title"]),{trigger:h,placement:p,onOpenChange:(t,r)=>{let{disabled:o=!1}=e;o||z(t,r)},open:R,ref:t,classNames:{root:q,body:T},styles:{root:Object.assign(Object.assign(Object.assign(Object.assign({},Z.root),N),x),null==O?void 0:O.root),body:Object.assign(Object.assign({},Z.body),null==O?void 0:O.body)},content:o.createElement(w,Object.assign({okType:m,icon:g},e,{prefixCls:I,close:e=>{z(!1,e)},onConfirm:t=>{var r;return null===(r=e.onConfirm)||void 0===r?void 0:r.call(void 0,t)},onCancel:t=>{var r;z(!1,t),null===(r=e.onCancel)||void 0===r||r.call(void 0,t)}})),"data-popover-inject":!0}),b))});O._InternalPanelDoNotUseOrYouWillBeFired=e=>{let{prefixCls:t,placement:r,className:n,style:a}=e,s=x(e,["prefixCls","placement","className","style"]),{getPrefixCls:l}=o.useContext(c.E_),d=l("popconfirm",t),[u]=k(d);return u(o.createElement(f.ZP,{placement:r,className:i()(d,n),style:a,content:o.createElement(w,Object.assign({prefixCls:d},s))}))};var E=O},20435:function(e,t,r){r.d(t,{aV:function(){return u}});var o=r(2265),n=r(36760),a=r.n(n),i=r(5769),s=r(92570),l=r(71744),c=r(72262),d=function(e,t){var r={};for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&0>t.indexOf(o)&&(r[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var n=0,o=Object.getOwnPropertySymbols(e);nt.indexOf(o[n])&&Object.prototype.propertyIsEnumerable.call(e,o[n])&&(r[o[n]]=e[o[n]]);return r};let u=e=>{let{title:t,content:r,prefixCls:n}=e;return t||r?o.createElement(o.Fragment,null,t&&o.createElement("div",{className:"".concat(n,"-title")},t),r&&o.createElement("div",{className:"".concat(n,"-inner-content")},r)):null},p=e=>{let{hashId:t,prefixCls:r,className:n,style:l,placement:c="top",title:d,content:p,children:h}=e,m=(0,s.Z)(d),g=(0,s.Z)(p),b=a()(t,r,"".concat(r,"-pure"),"".concat(r,"-placement-").concat(c),n);return o.createElement("div",{className:b,style:l},o.createElement("div",{className:"".concat(r,"-arrow")}),o.createElement(i.G,Object.assign({},e,{className:t,prefixCls:r}),h||o.createElement(u,{prefixCls:r,title:m,content:g})))};t.ZP=e=>{let{prefixCls:t,className:r}=e,n=d(e,["prefixCls","className"]),{getPrefixCls:i}=o.useContext(l.E_),s=i("popover",t),[u,h,m]=(0,c.Z)(s);return u(o.createElement(p,Object.assign({},n,{prefixCls:s,hashId:h,className:a()(r,m)})))}},79326:function(e,t,r){var o=r(2265),n=r(36760),a=r.n(n),i=r(50506),s=r(95814),l=r(92570),c=r(68710),d=r(19722),u=r(71744),p=r(99981),h=r(20435),m=r(72262),g=function(e,t){var r={};for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&0>t.indexOf(o)&&(r[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var n=0,o=Object.getOwnPropertySymbols(e);nt.indexOf(o[n])&&Object.prototype.propertyIsEnumerable.call(e,o[n])&&(r[o[n]]=e[o[n]]);return r};let b=o.forwardRef((e,t)=>{var r,n;let{prefixCls:b,title:f,content:v,overlayClassName:y,placement:k="top",trigger:x="hover",children:w,mouseEnterDelay:C=.1,mouseLeaveDelay:O=.1,onOpenChange:E,overlayStyle:j={},styles:S,classNames:M}=e,N=g(e,["prefixCls","title","content","overlayClassName","placement","trigger","children","mouseEnterDelay","mouseLeaveDelay","onOpenChange","overlayStyle","styles","classNames"]),{getPrefixCls:P,className:Z,style:R,classNames:L,styles:z}=(0,u.dj)("popover"),I=P("popover",b),[q,T,B]=(0,m.Z)(I),K=P(),W=a()(y,T,B,Z,L.root,null==M?void 0:M.root),D=a()(L.body,null==M?void 0:M.body),[F,H]=(0,i.Z)(!1,{value:null!==(r=e.open)&&void 0!==r?r:e.visible,defaultValue:null!==(n=e.defaultOpen)&&void 0!==n?n:e.defaultVisible}),_=(e,t)=>{H(e,!0),null==E||E(e,t)},V=e=>{e.keyCode===s.Z.ESC&&_(!1,e)},X=(0,l.Z)(f),Y=(0,l.Z)(v);return q(o.createElement(p.Z,Object.assign({placement:k,trigger:x,mouseEnterDelay:C,mouseLeaveDelay:O},N,{prefixCls:I,classNames:{root:W,body:D},styles:{root:Object.assign(Object.assign(Object.assign(Object.assign({},z.root),R),j),null==S?void 0:S.root),body:Object.assign(Object.assign({},z.body),null==S?void 0:S.body)},ref:t,open:F,onOpenChange:e=>{_(e)},overlay:X||Y?o.createElement(h.aV,{prefixCls:I,title:X,content:Y}):null,transitionName:(0,c.m)(K,"zoom-big",N.transitionName),"data-popover-inject":!0}),(0,d.Tm)(w,{onKeyDown:e=>{var t,r;(0,o.isValidElement)(w)&&(null===(r=null==w?void 0:(t=w.props).onKeyDown)||void 0===r||r.call(t,e)),V(e)}})))});b._InternalPanelDoNotUseOrYouWillBeFired=h.ZP,t.Z=b},72262:function(e,t,r){var o=r(12918),n=r(691),a=r(88260),i=r(34442),s=r(53454),l=r(99320),c=r(71140);let d=e=>{let{componentCls:t,popoverColor:r,titleMinWidth:n,fontWeightStrong:i,innerPadding:s,boxShadowSecondary:l,colorTextHeading:c,borderRadiusLG:d,zIndexPopup:u,titleMarginBottom:p,colorBgElevated:h,popoverBg:m,titleBorderBottom:g,innerContentPadding:b,titlePadding:f}=e;return[{[t]:Object.assign(Object.assign({},(0,o.Wf)(e)),{position:"absolute",top:0,left:{_skip_check_:!0,value:0},zIndex:u,fontWeight:"normal",whiteSpace:"normal",textAlign:"start",cursor:"auto",userSelect:"text","--valid-offset-x":"var(--arrow-offset-horizontal, var(--arrow-x))",transformOrigin:"var(--valid-offset-x, 50%) var(--arrow-y, 50%)","--antd-arrow-background-color":h,width:"max-content",maxWidth:"100vw","&-rtl":{direction:"rtl"},"&-hidden":{display:"none"},["".concat(t,"-content")]:{position:"relative"},["".concat(t,"-inner")]:{backgroundColor:m,backgroundClip:"padding-box",borderRadius:d,boxShadow:l,padding:s},["".concat(t,"-title")]:{minWidth:n,marginBottom:p,color:c,fontWeight:i,borderBottom:g,padding:f},["".concat(t,"-inner-content")]:{color:r,padding:b}})},(0,a.ZP)(e,"var(--antd-arrow-background-color)"),{["".concat(t,"-pure")]:{position:"relative",maxWidth:"none",margin:e.sizePopupArrow,display:"inline-block",["".concat(t,"-content")]:{display:"inline-block"}}}]},u=e=>{let{componentCls:t}=e;return{[t]:s.i.map(r=>{let o=e["".concat(r,"6")];return{["&".concat(t,"-").concat(r)]:{"--antd-arrow-background-color":o,["".concat(t,"-inner")]:{backgroundColor:o},["".concat(t,"-arrow")]:{background:"transparent"}}}})}};t.Z=(0,l.I$)("Popover",e=>{let{colorBgElevated:t,colorText:r}=e,o=(0,c.IX)(e,{popoverBg:t,popoverColor:r});return[d(o),u(o),(0,n._y)(o,"zoom-big")]},e=>{let{lineWidth:t,controlHeight:r,fontHeight:o,padding:n,wireframe:s,zIndexPopupBase:l,borderRadiusLG:c,marginXS:d,lineType:u,colorSplit:p,paddingSM:h}=e,m=r-o;return Object.assign(Object.assign(Object.assign({titleMinWidth:177,zIndexPopup:l+30},(0,i.w)(e)),(0,a.wZ)({contentRadius:c,limitVerticalRadius:!0})),{innerPadding:s?0:12,titleMarginBottom:s?0:d,titlePadding:s?"".concat(m/2,"px ").concat(n,"px ").concat(m/2-t,"px"):0,titleBorderBottom:s?"".concat(t,"px ").concat(u," ").concat(p):"none",innerContentPadding:s?"".concat(h,"px ").concat(n,"px"):0})},{resetStyle:!1,deprecatedTokens:[["width","titleMinWidth"],["minWidth","titleMinWidth"]]})},3810:function(e,t,r){r.d(t,{Z:function(){return P}});var o=r(2265),n=r(36760),a=r.n(n),i=r(18694),s=r(93350),l=r(53445),c=r(19722),d=r(6694),u=r(71744),p=r(93463),h=r(54558),m=r(12918),g=r(71140),b=r(99320);let f=e=>{let{paddingXXS:t,lineWidth:r,tagPaddingHorizontal:o,componentCls:n,calc:a}=e,i=a(o).sub(r).equal(),s=a(t).sub(r).equal();return{[n]:Object.assign(Object.assign({},(0,m.Wf)(e)),{display:"inline-block",height:"auto",marginInlineEnd:e.marginXS,paddingInline:i,fontSize:e.tagFontSize,lineHeight:e.tagLineHeight,whiteSpace:"nowrap",background:e.defaultBg,border:"".concat((0,p.bf)(e.lineWidth)," ").concat(e.lineType," ").concat(e.colorBorder),borderRadius:e.borderRadiusSM,opacity:1,transition:"all ".concat(e.motionDurationMid),textAlign:"start",position:"relative",["&".concat(n,"-rtl")]:{direction:"rtl"},"&, a, a:hover":{color:e.defaultColor},["".concat(n,"-close-icon")]:{marginInlineStart:s,fontSize:e.tagIconSize,color:e.colorIcon,cursor:"pointer",transition:"all ".concat(e.motionDurationMid),"&:hover":{color:e.colorTextHeading}},["&".concat(n,"-has-color")]:{borderColor:"transparent",["&, a, a:hover, ".concat(e.iconCls,"-close, ").concat(e.iconCls,"-close:hover")]:{color:e.colorTextLightSolid}},"&-checkable":{backgroundColor:"transparent",borderColor:"transparent",cursor:"pointer",["&:not(".concat(n,"-checkable-checked):hover")]:{color:e.colorPrimary,backgroundColor:e.colorFillSecondary},"&:active, &-checked":{color:e.colorTextLightSolid},"&-checked":{backgroundColor:e.colorPrimary,"&:hover":{backgroundColor:e.colorPrimaryHover}},"&:active":{backgroundColor:e.colorPrimaryActive}},"&-hidden":{display:"none"},["> ".concat(e.iconCls," + span, > span + ").concat(e.iconCls)]:{marginInlineStart:i}}),["".concat(n,"-borderless")]:{borderColor:"transparent",background:e.tagBorderlessBg}}},v=e=>{let{lineWidth:t,fontSizeIcon:r,calc:o}=e,n=e.fontSizeSM;return(0,g.IX)(e,{tagFontSize:n,tagLineHeight:(0,p.bf)(o(e.lineHeightSM).mul(n).equal()),tagIconSize:o(r).sub(o(t).mul(2)).equal(),tagPaddingHorizontal:8,tagBorderlessBg:e.defaultBg})},y=e=>({defaultBg:new h.t(e.colorFillQuaternary).onBackground(e.colorBgContainer).toHexString(),defaultColor:e.colorText});var k=(0,b.I$)("Tag",e=>f(v(e)),y),x=function(e,t){var r={};for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&0>t.indexOf(o)&&(r[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var n=0,o=Object.getOwnPropertySymbols(e);nt.indexOf(o[n])&&Object.prototype.propertyIsEnumerable.call(e,o[n])&&(r[o[n]]=e[o[n]]);return r};let w=o.forwardRef((e,t)=>{let{prefixCls:r,style:n,className:i,checked:s,children:l,icon:c,onChange:d,onClick:p}=e,h=x(e,["prefixCls","style","className","checked","children","icon","onChange","onClick"]),{getPrefixCls:m,tag:g}=o.useContext(u.E_),b=m("tag",r),[f,v,y]=k(b),w=a()(b,"".concat(b,"-checkable"),{["".concat(b,"-checkable-checked")]:s},null==g?void 0:g.className,i,v,y);return f(o.createElement("span",Object.assign({},h,{ref:t,style:Object.assign(Object.assign({},n),null==g?void 0:g.style),className:w,onClick:e=>{null==d||d(!s),null==p||p(e)}}),c,o.createElement("span",null,l)))});var C=r(18536);let O=e=>(0,C.Z)(e,(t,r)=>{let{textColor:o,lightBorderColor:n,lightColor:a,darkColor:i}=r;return{["".concat(e.componentCls).concat(e.componentCls,"-").concat(t)]:{color:o,background:a,borderColor:n,"&-inverse":{color:e.colorTextLightSolid,background:i,borderColor:i},["&".concat(e.componentCls,"-borderless")]:{borderColor:"transparent"}}}});var E=(0,b.bk)(["Tag","preset"],e=>O(v(e)),y);let j=(e,t,r)=>{let o="string"!=typeof r?r:r.charAt(0).toUpperCase()+r.slice(1);return{["".concat(e.componentCls).concat(e.componentCls,"-").concat(t)]:{color:e["color".concat(r)],background:e["color".concat(o,"Bg")],borderColor:e["color".concat(o,"Border")],["&".concat(e.componentCls,"-borderless")]:{borderColor:"transparent"}}}};var S=(0,b.bk)(["Tag","status"],e=>{let t=v(e);return[j(t,"success","Success"),j(t,"processing","Info"),j(t,"error","Error"),j(t,"warning","Warning")]},y),M=function(e,t){var r={};for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&0>t.indexOf(o)&&(r[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var n=0,o=Object.getOwnPropertySymbols(e);nt.indexOf(o[n])&&Object.prototype.propertyIsEnumerable.call(e,o[n])&&(r[o[n]]=e[o[n]]);return r};let N=o.forwardRef((e,t)=>{let{prefixCls:r,className:n,rootClassName:p,style:h,children:m,icon:g,color:b,onClose:f,bordered:v=!0,visible:y}=e,x=M(e,["prefixCls","className","rootClassName","style","children","icon","color","onClose","bordered","visible"]),{getPrefixCls:w,direction:C,tag:O}=o.useContext(u.E_),[j,N]=o.useState(!0),P=(0,i.Z)(x,["closeIcon","closable"]);o.useEffect(()=>{void 0!==y&&N(y)},[y]);let Z=(0,s.o2)(b),R=(0,s.yT)(b),L=Z||R,z=Object.assign(Object.assign({backgroundColor:b&&!L?b:void 0},null==O?void 0:O.style),h),I=w("tag",r),[q,T,B]=k(I),K=a()(I,null==O?void 0:O.className,{["".concat(I,"-").concat(b)]:L,["".concat(I,"-has-color")]:b&&!L,["".concat(I,"-hidden")]:!j,["".concat(I,"-rtl")]:"rtl"===C,["".concat(I,"-borderless")]:!v},n,p,T,B),W=e=>{e.stopPropagation(),null==f||f(e),e.defaultPrevented||N(!1)},[,D]=(0,l.b)((0,l.w)(e),(0,l.w)(O),{closable:!1,closeIconRender:e=>{let t=o.createElement("span",{className:"".concat(I,"-close-icon"),onClick:W},e);return(0,c.wm)(e,t,e=>({onClick:t=>{var r;null===(r=null==e?void 0:e.onClick)||void 0===r||r.call(e,t),W(t)},className:a()(null==e?void 0:e.className,"".concat(I,"-close-icon"))}))}}),F="function"==typeof x.onClick||m&&"a"===m.type,H=g||null,_=H?o.createElement(o.Fragment,null,H,m&&o.createElement("span",null,m)):m,V=o.createElement("span",Object.assign({},P,{ref:t,className:K,style:z}),_,D,Z&&o.createElement(E,{key:"preset",prefixCls:I}),R&&o.createElement(S,{key:"status",prefixCls:I}));return q(F?o.createElement(d.Z,{component:"Tag"},V):V)});N.CheckableTag=w;var P=N},41671:function(e,t,r){r.d(t,{Z:function(){return o}});let o=(0,r(79205).Z)("circle-check-big",[["path",{d:"M21.801 10A10 10 0 1 1 17 3.335",key:"yps3ct"}],["path",{d:"m9 11 3 3L22 4",key:"1pflzl"}]])},33276:function(e,t,r){r.d(t,{Z:function(){return o}});let o=(0,r(79205).Z)("play",[["polygon",{points:"6 3 20 12 6 21 6 3",key:"1oa8hb"}]])},15868:function(e,t,r){r.d(t,{Z:function(){return o}});let o=(0,r(79205).Z)("square-pen",[["path",{d:"M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7",key:"1m0v6g"}],["path",{d:"M18.375 2.625a1 1 0 0 1 3 3l-9.013 9.014a2 2 0 0 1-.853.505l-2.873.84a.5.5 0 0 1-.62-.62l.84-2.873a2 2 0 0 1 .506-.852z",key:"ohrbg2"}]])},18930:function(e,t,r){r.d(t,{Z:function(){return o}});let o=(0,r(79205).Z)("trash-2",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}],["line",{x1:"10",x2:"10",y1:"11",y2:"17",key:"1uufr5"}],["line",{x1:"14",x2:"14",y1:"11",y2:"17",key:"xtxkd"}]])},17689:function(e,t,r){r.d(t,{Z:function(){return o}});let o=(0,r(79205).Z)("upload",[["path",{d:"M12 3v12",key:"1x0j5s"}],["path",{d:"m17 8-5-5-5 5",key:"7q97r8"}],["path",{d:"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",key:"ih7n3h"}]])},44643:function(e,t,r){var o=r(2265);let n=o.forwardRef(function(e,t){return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),o.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"}))});t.Z=n},86462:function(e,t,r){var o=r(2265);let n=o.forwardRef(function(e,t){return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),o.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M19 9l-7 7-7-7"}))});t.Z=n},44633:function(e,t,r){var o=r(2265);let n=o.forwardRef(function(e,t){return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),o.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M5 15l7-7 7 7"}))});t.Z=n},3477:function(e,t,r){var o=r(2265);let n=o.forwardRef(function(e,t){return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),o.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"}))});t.Z=n},53410:function(e,t,r){var o=r(2265);let n=o.forwardRef(function(e,t){return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),o.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"}))});t.Z=n},91126:function(e,t,r){var o=r(2265);let n=o.forwardRef(function(e,t){return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),o.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"}),o.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M21 12a9 9 0 11-18 0 9 9 0 0118 0z"}))});t.Z=n},23628:function(e,t,r){var o=r(2265);let n=o.forwardRef(function(e,t){return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),o.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"}))});t.Z=n},74998:function(e,t,r){var o=r(2265);let n=o.forwardRef(function(e,t){return o.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),o.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"}))});t.Z=n},2894:function(e,t,r){r.d(t,{R:function(){return s},m:function(){return i}});var o=r(18238),n=r(7989),a=r(11255),i=class extends n.F{#e;#t;#r;#o;constructor(e){super(),this.#e=e.client,this.mutationId=e.mutationId,this.#r=e.mutationCache,this.#t=[],this.state=e.state||s(),this.setOptions(e.options),this.scheduleGc()}setOptions(e){this.options=e,this.updateGcTime(this.options.gcTime)}get meta(){return this.options.meta}addObserver(e){this.#t.includes(e)||(this.#t.push(e),this.clearGcTimeout(),this.#r.notify({type:"observerAdded",mutation:this,observer:e}))}removeObserver(e){this.#t=this.#t.filter(t=>t!==e),this.scheduleGc(),this.#r.notify({type:"observerRemoved",mutation:this,observer:e})}optionalRemove(){this.#t.length||("pending"===this.state.status?this.scheduleGc():this.#r.remove(this))}continue(){return this.#o?.continue()??this.execute(this.state.variables)}async execute(e){let t=()=>{this.#n({type:"continue"})},r={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};this.#o=(0,a.Mz)({fn:()=>this.options.mutationFn?this.options.mutationFn(e,r):Promise.reject(Error("No mutationFn found")),onFail:(e,t)=>{this.#n({type:"failed",failureCount:e,error:t})},onPause:()=>{this.#n({type:"pause"})},onContinue:t,retry:this.options.retry??0,retryDelay:this.options.retryDelay,networkMode:this.options.networkMode,canRun:()=>this.#r.canRun(this)});let o="pending"===this.state.status,n=!this.#o.canStart();try{if(o)t();else{this.#n({type:"pending",variables:e,isPaused:n}),await this.#r.config.onMutate?.(e,this,r);let t=await this.options.onMutate?.(e,r);t!==this.state.context&&this.#n({type:"pending",context:t,variables:e,isPaused:n})}let a=await this.#o.start();return await this.#r.config.onSuccess?.(a,e,this.state.context,this,r),await this.options.onSuccess?.(a,e,this.state.context,r),await this.#r.config.onSettled?.(a,null,this.state.variables,this.state.context,this,r),await this.options.onSettled?.(a,null,e,this.state.context,r),this.#n({type:"success",data:a}),a}catch(t){try{throw await this.#r.config.onError?.(t,e,this.state.context,this,r),await this.options.onError?.(t,e,this.state.context,r),await this.#r.config.onSettled?.(void 0,t,this.state.variables,this.state.context,this,r),await this.options.onSettled?.(void 0,t,e,this.state.context,r),t}finally{this.#n({type:"error",error:t})}}finally{this.#r.runNext(this)}}#n(e){this.state=(t=>{switch(e.type){case"failed":return{...t,failureCount:e.failureCount,failureReason:e.error};case"pause":return{...t,isPaused:!0};case"continue":return{...t,isPaused:!1};case"pending":return{...t,context:e.context,data:void 0,failureCount:0,failureReason:null,error:null,isPaused:e.isPaused,status:"pending",variables:e.variables,submittedAt:Date.now()};case"success":return{...t,data:e.data,failureCount:0,failureReason:null,error:null,status:"success",isPaused:!1};case"error":return{...t,data:void 0,error:e.error,failureCount:t.failureCount+1,failureReason:e.error,isPaused:!1,status:"error"}}})(this.state),o.Vr.batch(()=>{this.#t.forEach(t=>{t.onMutationUpdate(e)}),this.#r.notify({mutation:this,type:"updated",action:e})})}};function s(){return{context:void 0,data:void 0,error:null,failureCount:0,failureReason:null,isPaused:!1,status:"idle",variables:void 0,submittedAt:0}}},21770:function(e,t,r){r.d(t,{D:function(){return d}});var o=r(2265),n=r(2894),a=r(18238),i=r(24112),s=r(45345),l=class extends i.l{#e;#a=void 0;#i;#s;constructor(e,t){super(),this.#e=e,this.setOptions(t),this.bindMethods(),this.#l()}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(e){let t=this.options;this.options=this.#e.defaultMutationOptions(e),(0,s.VS)(this.options,t)||this.#e.getMutationCache().notify({type:"observerOptionsUpdated",mutation:this.#i,observer:this}),t?.mutationKey&&this.options.mutationKey&&(0,s.Ym)(t.mutationKey)!==(0,s.Ym)(this.options.mutationKey)?this.reset():this.#i?.state.status==="pending"&&this.#i.setOptions(this.options)}onUnsubscribe(){this.hasListeners()||this.#i?.removeObserver(this)}onMutationUpdate(e){this.#l(),this.#c(e)}getCurrentResult(){return this.#a}reset(){this.#i?.removeObserver(this),this.#i=void 0,this.#l(),this.#c()}mutate(e,t){return this.#s=t,this.#i?.removeObserver(this),this.#i=this.#e.getMutationCache().build(this.#e,this.options),this.#i.addObserver(this),this.#i.execute(e)}#l(){let e=this.#i?.state??(0,n.R)();this.#a={...e,isPending:"pending"===e.status,isSuccess:"success"===e.status,isError:"error"===e.status,isIdle:"idle"===e.status,mutate:this.mutate,reset:this.reset}}#c(e){a.Vr.batch(()=>{if(this.#s&&this.hasListeners()){let t=this.#a.variables,r=this.#a.context,o={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};e?.type==="success"?(this.#s.onSuccess?.(e.data,t,r,o),this.#s.onSettled?.(e.data,null,t,r,o)):e?.type==="error"&&(this.#s.onError?.(e.error,t,r,o),this.#s.onSettled?.(void 0,e.error,t,r,o))}this.listeners.forEach(e=>{e(this.#a)})})}},c=r(29827);function d(e,t){let r=(0,c.NL)(t),[n]=o.useState(()=>new l(r,e));o.useEffect(()=>{n.setOptions(e)},[n,e]);let i=o.useSyncExternalStore(o.useCallback(e=>n.subscribe(a.Vr.batchCalls(e)),[n]),()=>n.getCurrentResult(),()=>n.getCurrentResult()),d=o.useCallback((e,t)=>{n.mutate(e,t).catch(s.ZT)},[n]);if(i.error&&(0,s.L3)(n.options.throwOnError,[i.error]))throw i.error;return{...i,mutate:d,mutateAsync:i.mutate}}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1442-024f7e51804e0d7e.js b/litellm/proxy/_experimental/out/_next/static/chunks/1442-024f7e51804e0d7e.js deleted file mode 100644 index 3c42e191d4b..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/1442-024f7e51804e0d7e.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1442],{42698:function(e,t,n){n.d(t,{Z:function(){return i}});var r=n(2265),o=n(7084);n(13241);let i=(0,r.createContext)(o.fr.Blue)},64016:function(e,t,n){n.d(t,{Z:function(){return r}});let r=(0,n(2265).createContext)(0)},8710:function(e,t,n){n.d(t,{Z:function(){return r}});let r=(0,n(2265).createContext)(void 0)},33232:function(e,t,n){n.d(t,{Z:function(){return r}});let r=(0,n(2265).createContext)({selectedValue:void 0,handleValueChange:void 0})},71049:function(e,t,n){n.d(t,{F:function(){return D}});var r,o=n(2265);let i="undefined"!=typeof document?o.useLayoutEffect:()=>{},u=null!==(r=o.useInsertionEffect)&&void 0!==r?r:i;function a(e){return e.nativeEvent=e,e.isDefaultPrevented=()=>e.defaultPrevented,e.isPropagationStopped=()=>e.cancelBubble,e.persist=()=>{},e}function l(e){let t=(0,o.useRef)({isFocused:!1,observer:null});i(()=>{let e=t.current;return()=>{e.observer&&(e.observer.disconnect(),e.observer=null)}},[]);let n=function(e){let t=(0,o.useRef)(null);return u(()=>{t.current=e},[e]),(0,o.useCallback)((...e)=>{let n=t.current;return null==n?void 0:n(...e)},[])}(t=>{null==e||e(t)});return(0,o.useCallback)(e=>{if(e.target instanceof HTMLButtonElement||e.target instanceof HTMLInputElement||e.target instanceof HTMLTextAreaElement||e.target instanceof HTMLSelectElement){t.current.isFocused=!0;let r=e.target;r.addEventListener("focusout",e=>{t.current.isFocused=!1,r.disabled&&n(a(e)),t.current.observer&&(t.current.observer.disconnect(),t.current.observer=null)},{once:!0}),t.current.observer=new MutationObserver(()=>{if(t.current.isFocused&&r.disabled){var e;null===(e=t.current.observer)||void 0===e||e.disconnect();let n=r===document.activeElement?null:document.activeElement;r.dispatchEvent(new FocusEvent("blur",{relatedTarget:n})),r.dispatchEvent(new FocusEvent("focusout",{bubbles:!0,relatedTarget:n}))}}),t.current.observer.observe(r,{attributes:!0,attributeFilter:["disabled"]})}},[n])}function c(e){var t;if("undefined"==typeof window||null==window.navigator)return!1;let n=null===(t=window.navigator.userAgentData)||void 0===t?void 0:t.brands;return Array.isArray(n)&&n.some(t=>e.test(t.brand))||e.test(window.navigator.userAgent)}function s(e){var t;return"undefined"!=typeof window&&null!=window.navigator&&e.test((null===(t=window.navigator.userAgentData)||void 0===t?void 0:t.platform)||window.navigator.platform)}function d(e){let t=null;return()=>(null==t&&(t=e()),t)}let f=d(function(){return s(/^Mac/i)}),v=d(function(){return s(/^iPhone/i)}),p=d(function(){return s(/^iPad/i)||f()&&navigator.maxTouchPoints>1}),g=d(function(){return v()||p()});d(function(){return f()||g()}),d(function(){return c(/AppleWebKit/i)&&!m()});let m=d(function(){return c(/Chrome/i)}),h=d(function(){return c(/Android/i)});d(function(){return c(/Firefox/i)});var y=n(18064);let b=null,E=new Set,w=new Map,T=!1,A=!1,F={Tab:!0,Escape:!0};function L(e,t){for(let n of E)n(e,t)}function k(e){T=!0,e.metaKey||!f()&&e.altKey||e.ctrlKey||"Control"===e.key||"Shift"===e.key||"Meta"===e.key||(b="keyboard",L("keyboard",e))}function N(e){b="pointer",("mousedown"===e.type||"pointerdown"===e.type)&&(T=!0,L("pointer",e))}function P(e){(""===e.pointerType&&e.isTrusted||(h()&&e.pointerType?"click"===e.type&&1===e.buttons:0===e.detail&&!e.pointerType))&&(T=!0,b="virtual")}function O(e){e.target!==window&&e.target!==document&&e.isTrusted&&(T||A||(b="virtual",L("virtual",e)),T=!1,A=!1)}function S(){T=!1,A=!0}function M(e){if("undefined"==typeof window||"undefined"==typeof document||w.get((0,y.kR)(e)))return;let t=(0,y.kR)(e),n=(0,y.r3)(e),r=t.HTMLElement.prototype.focus;t.HTMLElement.prototype.focus=function(){T=!0,r.apply(this,arguments)},n.addEventListener("keydown",k,!0),n.addEventListener("keyup",k,!0),n.addEventListener("click",P,!0),t.addEventListener("focus",O,!0),t.addEventListener("blur",S,!1),"undefined"!=typeof PointerEvent&&(n.addEventListener("pointerdown",N,!0),n.addEventListener("pointermove",N,!0),n.addEventListener("pointerup",N,!0)),t.addEventListener("beforeunload",()=>{C(e)},{once:!0}),w.set(t,{focus:r})}let C=(e,t)=>{let n=(0,y.kR)(e),r=(0,y.r3)(e);t&&r.removeEventListener("DOMContentLoaded",t),w.has(n)&&(n.HTMLElement.prototype.focus=w.get(n).focus,r.removeEventListener("keydown",k,!0),r.removeEventListener("keyup",k,!0),r.removeEventListener("click",P,!0),n.removeEventListener("focus",O,!0),n.removeEventListener("blur",S,!1),"undefined"!=typeof PointerEvent&&(r.removeEventListener("pointerdown",N,!0),r.removeEventListener("pointermove",N,!0),r.removeEventListener("pointerup",N,!0)),w.delete(n))};function x(){return"pointer"!==b}"undefined"!=typeof document&&function(e){let t;let n=(0,y.r3)(void 0);"loading"!==n.readyState?M(void 0):(t=()=>{M(void 0)},n.addEventListener("DOMContentLoaded",t)),()=>C(e,t)}();let H=new Set(["checkbox","radio","range","color","file","image","button","submit","reset"]);var R=n(26428),j=n(66852);function D(e={}){var t,n,r;let{autoFocus:i=!1,isTextInput:u,within:c}=e,s=(0,o.useRef)({isFocused:!1,isFocusVisible:i||x()}),[d,f]=(0,o.useState)(!1),[v,p]=(0,o.useState)(()=>s.current.isFocused&&s.current.isFocusVisible),g=(0,o.useCallback)(()=>p(s.current.isFocused&&s.current.isFocusVisible),[]),m=(0,o.useCallback)(e=>{s.current.isFocused=e,f(e),g()},[g]);t=e=>{s.current.isFocusVisible=e,g()},n=[],r={isTextInput:u},M(),(0,o.useEffect)(()=>{let e=(e,n)=>{(function(e,t,n){let r=(0,y.r3)(null==n?void 0:n.target),o="undefined"!=typeof window?(0,y.kR)(null==n?void 0:n.target).HTMLInputElement:HTMLInputElement,i="undefined"!=typeof window?(0,y.kR)(null==n?void 0:n.target).HTMLTextAreaElement:HTMLTextAreaElement,u="undefined"!=typeof window?(0,y.kR)(null==n?void 0:n.target).HTMLElement:HTMLElement,a="undefined"!=typeof window?(0,y.kR)(null==n?void 0:n.target).KeyboardEvent:KeyboardEvent;return!((e=e||r.activeElement instanceof o&&!H.has(r.activeElement.type)||r.activeElement instanceof i||r.activeElement instanceof u&&r.activeElement.isContentEditable)&&"keyboard"===t&&n instanceof a&&!F[n.key])})(!!(null==r?void 0:r.isTextInput),e,n)&&t(x())};return E.add(e),()=>{E.delete(e)}},n);let{focusProps:h}=function(e){let{isDisabled:t,onFocus:n,onBlur:r,onFocusChange:i}=e,u=(0,o.useCallback)(e=>{if(e.target===e.currentTarget)return r&&r(e),i&&i(!1),!0},[r,i]),a=l(u),c=(0,o.useCallback)(e=>{let t=(0,y.r3)(e.target),r=t?(0,R.vY)(t):(0,R.vY)();e.target===e.currentTarget&&r===(0,R.NI)(e.nativeEvent)&&(n&&n(e),i&&i(!0),a(e))},[i,n,a]);return{focusProps:{onFocus:!t&&(n||i||r)?c:void 0,onBlur:!t&&(r||i)?u:void 0}}}({isDisabled:c,onFocusChange:m}),{focusWithinProps:b}=function(e){let{isDisabled:t,onBlurWithin:n,onFocusWithin:r,onFocusWithinChange:i}=e,u=(0,o.useRef)({isFocusWithin:!1}),{addGlobalListener:c,removeAllGlobalListeners:s}=(0,j.x)(),d=(0,o.useCallback)(e=>{e.currentTarget.contains(e.target)&&u.current.isFocusWithin&&!e.currentTarget.contains(e.relatedTarget)&&(u.current.isFocusWithin=!1,s(),n&&n(e),i&&i(!1))},[n,i,u,s]),f=l(d),v=(0,o.useCallback)(e=>{if(!e.currentTarget.contains(e.target))return;let t=(0,y.r3)(e.target),n=(0,R.vY)(t);if(!u.current.isFocusWithin&&n===(0,R.NI)(e.nativeEvent)){r&&r(e),i&&i(!0),u.current.isFocusWithin=!0,f(e);let n=e.currentTarget;c(t,"focus",e=>{if(u.current.isFocusWithin&&!(0,R.bE)(n,e.target)){let r=new t.defaultView.FocusEvent("blur",{relatedTarget:e.target});Object.defineProperty(r,"target",{value:n}),Object.defineProperty(r,"currentTarget",{value:n}),d(a(r))}},{capture:!0})}},[r,i,f,c,d]);return t?{focusWithinProps:{onFocus:void 0,onBlur:void 0}}:{focusWithinProps:{onFocus:v,onBlur:d}}}({isDisabled:!c,onFocusWithinChange:m});return{isFocused:d,isFocusVisible:v,focusProps:c?b:h}}},11323:function(e,t,n){n.d(t,{X:function(){return d}});var r=n(66852),o=n(18064),i=n(26428),u=n(2265);let a=!1,l=0;function c(e){"touch"===e.pointerType&&(a=!0,setTimeout(()=>{a=!1},50))}function s(){if("undefined"!=typeof document)return 0===l&&"undefined"!=typeof PointerEvent&&document.addEventListener("pointerup",c),l++,()=>{--l>0||"undefined"==typeof PointerEvent||document.removeEventListener("pointerup",c)}}function d(e){let{onHoverStart:t,onHoverChange:n,onHoverEnd:l,isDisabled:c}=e,[d,f]=(0,u.useState)(!1),v=(0,u.useRef)({isHovered:!1,ignoreEmulatedMouseEvents:!1,pointerType:"",target:null}).current;(0,u.useEffect)(s,[]);let{addGlobalListener:p,removeAllGlobalListeners:g}=(0,r.x)(),{hoverProps:m,triggerHoverEnd:h}=(0,u.useMemo)(()=>{let e=(e,u)=>{if(v.pointerType=u,c||"touch"===u||v.isHovered||!e.currentTarget.contains(e.target))return;v.isHovered=!0;let a=e.currentTarget;v.target=a,p((0,o.r3)(e.target),"pointerover",e=>{v.isHovered&&v.target&&!(0,i.bE)(v.target,e.target)&&r(e,e.pointerType)},{capture:!0}),t&&t({type:"hoverstart",target:a,pointerType:u}),n&&n(!0),f(!0)},r=(e,t)=>{let r=v.target;v.pointerType="",v.target=null,"touch"!==t&&v.isHovered&&r&&(v.isHovered=!1,g(),l&&l({type:"hoverend",target:r,pointerType:t}),n&&n(!1),f(!1))},u={};return"undefined"!=typeof PointerEvent&&(u.onPointerEnter=t=>{a&&"mouse"===t.pointerType||e(t,t.pointerType)},u.onPointerLeave=e=>{!c&&e.currentTarget.contains(e.target)&&r(e,e.pointerType)}),{hoverProps:u,triggerHoverEnd:r}},[t,n,l,c,v,p,g]);return(0,u.useEffect)(()=>{c&&h({currentTarget:v.target},v.pointerType)},[c]),{hoverProps:m,isHovered:d}}},26428:function(e,t,n){function r(e,t){return!!t&&!!e&&e.contains(t)}n.d(t,{vY:function(){return o},NI:function(){return i},bE:function(){return r}}),n(18064);let o=(e=document)=>e.activeElement;function i(e){return e.target}},18064:function(e,t,n){n.d(t,{Zq:function(){return i},kR:function(){return o},r3:function(){return r}});let r=e=>{var t;return null!==(t=null==e?void 0:e.ownerDocument)&&void 0!==t?t:document},o=e=>e&&"window"in e&&e.window===e?e:r(e).defaultView||window;function i(e){return null!==e&&"object"==typeof e&&"nodeType"in e&&"number"==typeof e.nodeType&&e.nodeType===Node.DOCUMENT_FRAGMENT_NODE&&"host"in e}},66852:function(e,t,n){n.d(t,{x:function(){return o}});var r=n(2265);function o(){let e=(0,r.useRef)(new Map),t=(0,r.useCallback)((t,n,r,o)=>{let i=(null==o?void 0:o.once)?(...t)=>{e.current.delete(r),r(...t)}:r;e.current.set(r,{type:n,eventTarget:t,fn:i,options:o}),t.addEventListener(n,i,o)},[]),n=(0,r.useCallback)((t,n,r,o)=>{var i;let u=(null===(i=e.current.get(r))||void 0===i?void 0:i.fn)||r;t.removeEventListener(n,u,o),e.current.delete(r)},[]),o=(0,r.useCallback)(()=>{e.current.forEach((e,t)=>{n(e.eventTarget,e.type,t,e.options)})},[n]);return(0,r.useEffect)(()=>o,[o]),{addGlobalListener:t,removeGlobalListener:n,removeAllGlobalListeners:o}}},52724:function(e,t,n){let r;n.d(t,{R:function(){return o}});var o=((r=o||{}).Space=" ",r.Enter="Enter",r.Escape="Escape",r.Backspace="Backspace",r.Delete="Delete",r.ArrowLeft="ArrowLeft",r.ArrowUp="ArrowUp",r.ArrowRight="ArrowRight",r.ArrowDown="ArrowDown",r.Home="Home",r.End="End",r.PageUp="PageUp",r.PageDown="PageDown",r.Tab="Tab",r)},66797:function(e,t,n){n.d(t,{x:function(){return a}});var r=n(2265),o=n(5664),i=n(59456),u=n(93980);function a(){let{disabled:e=!1}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=(0,r.useRef)(null),[n,a]=(0,r.useState)(!1),l=(0,i.G)(),c=(0,u.z)(()=>{t.current=null,a(!1),l.dispose()}),s=(0,u.z)(e=>{if(l.dispose(),null===t.current){t.current=e.currentTarget,a(!0);{let n=(0,o.r)(e.currentTarget);l.addEventListener(n,"pointerup",c,!1),l.addEventListener(n,"pointermove",e=>{if(t.current){var n,r;let o,i;a((o=e.width/2,i=e.height/2,n={top:e.clientY-i,right:e.clientX+o,bottom:e.clientY+i,left:e.clientX-o},r=t.current.getBoundingClientRect(),!(!n||!r||n.rightr.right||n.bottomr.bottom)))}},!1),l.addEventListener(n,"pointercancel",c,!1)}}});return{pressed:n,pressProps:e?{}:{onPointerDown:s,onPointerUp:c,onClick:c}}}},59456:function(e,t,n){n.d(t,{G:function(){return i}});var r=n(2265),o=n(36933);function i(){let[e]=(0,r.useState)(o.k);return(0,r.useEffect)(()=>()=>e.dispose(),[e]),e}},93980:function(e,t,n){n.d(t,{z:function(){return i}});var r=n(2265),o=n(43507);let i=function(e){let t=(0,o.E)(e);return r.useCallback(function(){for(var e=arguments.length,n=Array(e),r=0;r{o.O.isServer?(0,r.useEffect)(e,t):(0,r.useLayoutEffect)(e,t)}},43507:function(e,t,n){n.d(t,{E:function(){return i}});var r=n(2265),o=n(73389);function i(e){let t=(0,r.useRef)(e);return(0,o.e)(()=>{t.current=e},[e]),t}},65573:function(e,t,n){n.d(t,{f:function(){return o}});var r=n(2265);function o(e,t){return(0,r.useMemo)(()=>{var n;if(e.type)return e.type;let r=null!=(n=e.as)?n:"button";if("string"==typeof r&&"button"===r.toLowerCase()||(null==t?void 0:t.tagName)==="BUTTON"&&!t.hasAttribute("type"))return"button"},[e.type,e.as,t])}},67561:function(e,t,n){n.d(t,{T:function(){return a},h:function(){return u}});var r=n(2265),o=n(93980);let i=Symbol();function u(e){let t=!(arguments.length>1)||void 0===arguments[1]||arguments[1];return Object.assign(e,{[i]:t})}function a(){for(var e=arguments.length,t=Array(e),n=0;n{u.current=t},[t]);let a=(0,o.z)(e=>{for(let t of u.current)null!=t&&("function"==typeof t?t(e):t.current=e)});return t.every(e=>null==e||(null==e?void 0:e[i]))?void 0:a}},65639:function(e,t,n){let r;n.d(t,{_:function(){return u},x:function(){return i}});var o=n(38929),i=((r=i||{})[r.None=1]="None",r[r.Focusable=2]="Focusable",r[r.Hidden=4]="Hidden",r);let u=(0,o.yV)(function(e,t){var n;let{features:r=1,...i}=e,u={ref:t,"aria-hidden":(2&r)==2||(null!=(n=i["aria-hidden"])?n:void 0),hidden:(4&r)==4||void 0,style:{position:"fixed",top:1,left:1,width:1,height:0,padding:0,margin:-1,overflow:"hidden",clip:"rect(0, 0, 0, 0)",whiteSpace:"nowrap",borderWidth:"0",...(4&r)==4&&(2&r)!=2&&{display:"none"}}};return(0,o.L6)()({ourProps:u,theirProps:i,slot:{},defaultTag:"span",name:"Hidden"})})},95504:function(e,t,n){n.d(t,{A:function(){return r}});function r(){for(var e=arguments.length,t=Array(e),n=0;n"string"==typeof e?e.split(" "):[]))).filter(Boolean).join(" ")}},36933:function(e,t,n){n.d(t,{k:function(){return function e(){let t=[],n={addEventListener:(e,t,r,o)=>(e.addEventListener(t,r,o),n.add(()=>e.removeEventListener(t,r,o))),requestAnimationFrame(){for(var e=arguments.length,t=Array(e),r=0;rcancelAnimationFrame(o))},nextFrame(){for(var e=arguments.length,t=Array(e),r=0;rn.requestAnimationFrame(...t))},setTimeout(){for(var e=arguments.length,t=Array(e),r=0;rclearTimeout(o))},microTask(){for(var e=arguments.length,t=Array(e),o=0;o{i.current&&t[0]()}),n.add(()=>{i.current=!1})},style(e,t,n){let r=e.style.getPropertyValue(t);return Object.assign(e.style,{[t]:n}),this.add(()=>{Object.assign(e.style,{[t]:r})})},group(t){let n=e();return t(n),this.add(()=>n.dispose())},add:e=>(t.includes(e)||t.push(e),()=>{let n=t.indexOf(e);if(n>=0)for(let e of t.splice(n,1))e()}),dispose(){for(let e of t.splice(0))e()}};return n}}});var r=n(24310)},60415:function(e,t,n){n.d(t,{O:function(){return a}});var r=Object.defineProperty,o=(e,t,n)=>t in e?r(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,i=(e,t,n)=>(o(e,"symbol"!=typeof t?t+"":t,n),n);class u{set(e){this.current!==e&&(this.handoffState="pending",this.currentId=0,this.current=e)}reset(){this.set(this.detect())}nextId(){return++this.currentId}get isServer(){return"server"===this.current}get isClient(){return"client"===this.current}detect(){return"undefined"==typeof window||"undefined"==typeof document?"server":"client"}handoff(){"pending"===this.handoffState&&(this.handoffState="complete")}get isHandoffComplete(){return"complete"===this.handoffState}constructor(){i(this,"current",this.detect()),i(this,"handoffState","pending"),i(this,"currentId",0)}}let a=new u},93698:function(e,t,n){let r,o,i,u,a;n.d(t,{EO:function(){return E},GO:function(){return g},TO:function(){return f},fE:function(){return v},jA:function(){return w},sP:function(){return h},tJ:function(){return m},z2:function(){return b}});var l=n(72468),c=n(5664);let s=["[contentEditable=true]","[tabindex]","a[href]","area[href]","button:not([disabled])","iframe","input:not([disabled])","select:not([disabled])","textarea:not([disabled])"].map(e=>"".concat(e,":not([tabindex='-1'])")).join(","),d=["[data-autofocus]"].map(e=>"".concat(e,":not([tabindex='-1'])")).join(",");var f=((r=f||{})[r.First=1]="First",r[r.Previous=2]="Previous",r[r.Next=4]="Next",r[r.Last=8]="Last",r[r.WrapAround=16]="WrapAround",r[r.NoScroll=32]="NoScroll",r[r.AutoFocus=64]="AutoFocus",r),v=((o=v||{})[o.Error=0]="Error",o[o.Overflow=1]="Overflow",o[o.Success=2]="Success",o[o.Underflow=3]="Underflow",o),p=((i=p||{})[i.Previous=-1]="Previous",i[i.Next=1]="Next",i);function g(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:document.body;return null==e?[]:Array.from(e.querySelectorAll(s)).sort((e,t)=>Math.sign((e.tabIndex||Number.MAX_SAFE_INTEGER)-(t.tabIndex||Number.MAX_SAFE_INTEGER)))}var m=((u=m||{})[u.Strict=0]="Strict",u[u.Loose=1]="Loose",u);function h(e){var t;let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return e!==(null==(t=(0,c.r)(e))?void 0:t.body)&&(0,l.E)(n,{0:()=>e.matches(s),1(){let t=e;for(;null!==t;){if(t.matches(s))return!0;t=t.parentElement}return!1}})}var y=((a=y||{})[a.Keyboard=0]="Keyboard",a[a.Mouse=1]="Mouse",a);function b(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:e=>e;return e.slice().sort((e,n)=>{let r=t(e),o=t(n);if(null===r||null===o)return 0;let i=r.compareDocumentPosition(o);return i&Node.DOCUMENT_POSITION_FOLLOWING?-1:i&Node.DOCUMENT_POSITION_PRECEDING?1:0})}function E(e,t){return w(g(),t,{relativeTo:e})}function w(e,t){var n,r,o;let{sorted:i=!0,relativeTo:u=null,skipElements:a=[]}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},l=Array.isArray(e)?e.length>0?e[0].ownerDocument:document:e.ownerDocument,c=Array.isArray(e)?i?b(e):e:64&t?function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:document.body;return null==e?[]:Array.from(e.querySelectorAll(d)).sort((e,t)=>Math.sign((e.tabIndex||Number.MAX_SAFE_INTEGER)-(t.tabIndex||Number.MAX_SAFE_INTEGER)))}(e):g(e);a.length>0&&c.length>1&&(c=c.filter(e=>!a.some(t=>null!=t&&"current"in t?(null==t?void 0:t.current)===e:t===e))),u=null!=u?u:l.activeElement;let s=(()=>{if(5&t)return 1;if(10&t)return -1;throw Error("Missing Focus.First, Focus.Previous, Focus.Next or Focus.Last")})(),f=(()=>{if(1&t)return 0;if(2&t)return Math.max(0,c.indexOf(u))-1;if(4&t)return Math.max(0,c.indexOf(u))+1;if(8&t)return c.length-1;throw Error("Missing Focus.First, Focus.Previous, Focus.Next or Focus.Last")})(),v=32&t?{preventScroll:!0}:{},p=0,m=c.length,h;do{if(p>=m||p+m<=0)return 0;let e=f+p;if(16&t)e=(e+m)%m;else{if(e<0)return 3;if(e>=m)return 1}null==(h=c[e])||h.focus(v),p+=s}while(h!==l.activeElement);return 6&t&&null!=(o=null==(r=null==(n=h)?void 0:n.matches)?void 0:r.call(n,"textarea,input"))&&o&&h.select(),2}"undefined"!=typeof window&&"undefined"!=typeof document&&(document.addEventListener("keydown",e=>{e.metaKey||e.altKey||e.ctrlKey||(document.documentElement.dataset.headlessuiFocusVisible="")},!0),document.addEventListener("click",e=>{1===e.detail?delete document.documentElement.dataset.headlessuiFocusVisible:0===e.detail&&(document.documentElement.dataset.headlessuiFocusVisible="")},!0))},72468:function(e,t,n){n.d(t,{E:function(){return r}});function r(e,t){for(var n=arguments.length,o=Array(n>2?n-2:0),i=2;i'"'.concat(e,'"')).join(", "),"."));throw Error.captureStackTrace&&Error.captureStackTrace(u,r),u}},24310:function(e,t,n){n.d(t,{Y:function(){return r}});function r(e){"function"==typeof queueMicrotask?queueMicrotask(e):Promise.resolve().then(e).catch(e=>setTimeout(()=>{throw e}))}},5664:function(e,t,n){n.d(t,{r:function(){return o}});var r=n(60415);function o(e){return r.O.isServer?null:e instanceof Node?e.ownerDocument:null!=e&&e.hasOwnProperty("current")&&e.current instanceof Node?e.current.ownerDocument:document}},38929:function(e,t,n){let r,o;n.d(t,{L6:function(){return s},VN:function(){return l},dG:function(){return p},l4:function(){return c},oA:function(){return m},yV:function(){return g}});var i=n(2265),u=n(95504),a=n(72468),l=((r=l||{})[r.None=0]="None",r[r.RenderStrategy=1]="RenderStrategy",r[r.Static=2]="Static",r),c=((o=c||{})[o.Unmount=0]="Unmount",o[o.Hidden=1]="Hidden",o);function s(){let e,t;let n=(e=(0,i.useRef)([]),t=(0,i.useCallback)(t=>{for(let n of e.current)null!=n&&("function"==typeof n?n(t):n.current=t)},[]),function(){for(var n=arguments.length,r=Array(n),o=0;onull==e))return e.current=r,t});return(0,i.useCallback)(e=>(function(e){let{ourProps:t,theirProps:n,slot:r,defaultTag:o,features:i,visible:u=!0,name:l,mergeRefs:c}=e;c=null!=c?c:f;let s=v(n,t);if(u)return d(s,r,o,l,c);let p=null!=i?i:0;if(2&p){let{static:e=!1,...t}=s;if(e)return d(t,r,o,l,c)}if(1&p){let{unmount:e=!0,...t}=s;return(0,a.E)(e?0:1,{0:()=>null,1:()=>d({...t,hidden:!0,style:{display:"none"}},r,o,l,c)})}return d(s,r,o,l,c)})({mergeRefs:n,...e}),[n])}function d(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments.length>2?arguments[2]:void 0,r=arguments.length>3?arguments[3]:void 0,o=arguments.length>4?arguments[4]:void 0,{as:a=n,children:l,refName:c="ref",...s}=h(e,["unmount","static"]),d=void 0!==e.ref?{[c]:e.ref}:{},f="function"==typeof l?l(t):l;"className"in s&&s.className&&"function"==typeof s.className&&(s.className=s.className(t)),s["aria-labelledby"]&&s["aria-labelledby"]===s.id&&(s["aria-labelledby"]=void 0);let p={};if(t){let e=!1,n=[];for(let[r,o]of Object.entries(t))"boolean"==typeof o&&(e=!0),!0===o&&n.push(r.replace(/([A-Z])/g,e=>"-".concat(e.toLowerCase())));if(e)for(let e of(p["data-headlessui-state"]=n.join(" "),n))p["data-".concat(e)]=""}if(a===i.Fragment&&(Object.keys(m(s)).length>0||Object.keys(m(p)).length>0)){if(!(0,i.isValidElement)(f)||Array.isArray(f)&&f.length>1){if(Object.keys(m(s)).length>0)throw Error(['Passing props on "Fragment"!',"","The current component <".concat(r,' /> is rendering a "Fragment".'),"However we need to passthrough the following props:",Object.keys(m(s)).concat(Object.keys(m(p))).map(e=>" - ".concat(e)).join("\n"),"","You can apply a few solutions:",['Add an `as="..."` prop, to ensure that we render an actual element instead of a "Fragment".',"Render a single element as the child so that we can forward the props onto that element."].map(e=>" - ".concat(e)).join("\n")].join("\n"))}else{let e=f.props,t=null==e?void 0:e.className,n="function"==typeof t?function(){for(var e=arguments.length,n=Array(e),r=0;r="19"?f.props.ref:f.ref,d.ref)},n?{className:n}:{}))}}return(0,i.createElement)(a,Object.assign({},h(s,["ref"]),a!==i.Fragment&&d,a!==i.Fragment&&p),f)}function f(){for(var e=arguments.length,t=Array(e),n=0;nnull==e)?void 0:e=>{for(let n of t)null!=n&&("function"==typeof n?n(e):n.current=e)}}function v(){for(var e=arguments.length,t=Array(e),n=0;n{var t;return null==(t=null==e?void 0:e.preventDefault)?void 0:t.call(e)}]);for(let e in o)Object.assign(r,{[e](t){for(var n=arguments.length,r=Array(n>1?n-1:0),i=1;i1&&void 0!==arguments[1]?arguments[1]:[],n=Object.assign({},e);for(let e of t)e in n&&delete n[e];return n}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1658-c301cddaf7772753.js b/litellm/proxy/_experimental/out/_next/static/chunks/1658-c301cddaf7772753.js new file mode 100644 index 00000000000..730804d53de --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/1658-c301cddaf7772753.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1658],{71658:function(e,l,t){t.d(l,{Z:function(){return l1}});var s=t(57437),a=t(19250),r=t(11713),i=t(90246),n=t(39760);let o=(0,i.n)("credentials"),d=()=>{let{accessToken:e}=(0,n.Z)();return(0,r.a)({queryKey:o.list({}),queryFn:async()=>await (0,a.credentialListCall)(e),enabled:!!e})},c=(0,i.n)("modelCostMap"),m=()=>(0,r.a)({queryKey:c.list({}),queryFn:async()=>await (0,a.modelCostMap)(),staleTime:6e4,gcTime:6e4});var u=t(52178),h=t(55584),x=t(47359),p=t(71594),g=t(24525),f=t(2265),j=t(19130),v=t(73705),_=t(5545),b=t(44633),y=t(86462),N=t(3837),w=t(49084);let Z=e=>{let{sortState:l,onSortChange:t}=e,a=[{key:"asc",label:"Ascending",icon:(0,s.jsx)(b.Z,{className:"h-4 w-4"})},{key:"desc",label:"Descending",icon:(0,s.jsx)(y.Z,{className:"h-4 w-4"})},{key:"reset",label:"Reset",icon:(0,s.jsx)(N.Z,{className:"h-4 w-4"})}];return(0,s.jsx)(v.Z,{menu:{items:a,onClick:e=>{let{key:l}=e;"asc"===l?t("asc"):"desc"===l?t("desc"):"reset"===l&&t(!1)},selectable:!0,selectedKeys:l?[l]:[]},trigger:["click"],autoAdjustOverflow:!0,children:(0,s.jsx)(_.ZP,{type:"text",onClick:e=>e.stopPropagation(),icon:"asc"===l?(0,s.jsx)(b.Z,{className:"h-4 w-4"}):"desc"===l?(0,s.jsx)(y.Z,{className:"h-4 w-4"}):(0,s.jsx)(w.Z,{className:"h-4 w-4"}),className:l?"text-blue-500 hover:text-blue-600":"text-gray-400 hover:text-blue-500"})})};function C(e){let{data:l=[],columns:t,isLoading:a=!1,sorting:r=[],onSortingChange:i,pagination:n,onPaginationChange:o,enablePagination:d=!1}=e,[c]=f.useState("onChange"),[m,u]=f.useState({}),[h,x]=f.useState({}),v=(0,p.b7)({data:l,columns:t,state:{sorting:r,columnSizing:m,columnVisibility:h,...d&&n?{pagination:n}:{}},columnResizeMode:c,onSortingChange:i,onColumnSizingChange:u,onColumnVisibilityChange:x,...d&&o?{onPaginationChange:o}:{},getCoreRowModel:(0,g.sC)(),...d?{getPaginationRowModel:(0,g.G_)()}:{},enableSorting:!0,enableColumnResizing:!0,manualSorting:!0,defaultColumn:{minSize:40,maxSize:500}});return(0,s.jsx)("div",{className:"rounded-lg custom-border relative",children:(0,s.jsx)("div",{className:"overflow-x-auto",children:(0,s.jsx)("div",{className:"relative min-w-full",children:(0,s.jsxs)(j.iA,{className:"[&_td]:py-2 [&_th]:py-2 w-full",children:[(0,s.jsx)(j.ss,{children:v.getHeaderGroups().map(e=>(0,s.jsx)(j.SC,{children:e.headers.map(e=>{var l;return(0,s.jsxs)(j.xs,{className:"py-1 h-8 relative ".concat("actions"===e.id?"sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)] w-[120px] ml-8":""," ").concat((null===(l=e.column.columnDef.meta)||void 0===l?void 0:l.className)||""),style:{width:"actions"===e.id?120:e.getSize(),position:"actions"===e.id?"sticky":"relative",right:"actions"===e.id?0:"auto"},children:[(0,s.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,s.jsx)("div",{className:"flex items-center",children:e.isPlaceholder?null:(0,p.ie)(e.column.columnDef.header,e.getContext())}),"actions"!==e.id&&e.column.getCanSort()&&i&&(0,s.jsx)(Z,{sortState:!1!==e.column.getIsSorted()&&e.column.getIsSorted(),onSortChange:l=>{!1===l?i([]):i([{id:e.column.id,desc:"desc"===l}])},columnId:e.column.id})]}),e.column.getCanResize()&&(0,s.jsx)("div",{onMouseDown:e.getResizeHandler(),onTouchStart:e.getResizeHandler(),className:"absolute right-0 top-0 h-full w-2 cursor-col-resize select-none touch-none ".concat(e.column.getIsResizing()?"bg-blue-500":"hover:bg-blue-200")})]},e.id)})},e.id))}),(0,s.jsx)(j.RM,{children:a?(0,s.jsx)(j.SC,{children:(0,s.jsx)(j.pj,{colSpan:t.length,className:"h-8 text-center",children:(0,s.jsx)("div",{className:"text-center text-gray-500",children:(0,s.jsx)("p",{children:"\uD83D\uDE85 Loading models..."})})})}):v.getRowModel().rows.length>0?v.getRowModel().rows.map(e=>(0,s.jsx)(j.SC,{children:e.getVisibleCells().map(e=>{var l;return(0,s.jsx)(j.pj,{className:"py-0.5 ".concat("actions"===e.column.id?"sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)] w-[120px] ml-8":""," ").concat((null===(l=e.column.columnDef.meta)||void 0===l?void 0:l.className)||""),style:{width:"actions"===e.column.id?120:e.column.getSize(),position:"actions"===e.column.id?"sticky":"relative",right:"actions"===e.column.id?0:"auto"},children:(0,p.ie)(e.column.columnDef.cell,e.getContext())},e.id)})},e.id)):(0,s.jsx)(j.SC,{children:(0,s.jsx)(j.pj,{colSpan:t.length,className:"h-8 text-center",children:(0,s.jsx)("div",{className:"text-center text-gray-500",children:(0,s.jsx)("p",{children:"No models found"})})})})})]})})})})}var k=t(45589),S=t(74998),A=t(41649),E=t(78489),P=t(47323),M=t(99981),L=t(42673);let F=e=>{let{provider:l,className:t="w-4 h-4"}=e,[a,r]=(0,f.useState)(!1),{logo:i}=(0,L.dr)(l);return a||!i?(0,s.jsx)("div",{className:"".concat(t," rounded-full bg-gray-200 flex items-center justify-center text-xs"),children:(null==l?void 0:l.charAt(0))||"-"}):(0,s.jsx)("img",{src:i,alt:"".concat(l," logo"),className:t,onError:()=>r(!0)})},I=(e,l,t,a,r,i,n,o,d,c)=>[{header:()=>(0,s.jsx)("span",{className:"text-sm font-semibold",children:"Model ID"}),accessorKey:"model_info.id",enableSorting:!1,cell:e=>{let{row:l}=e,t=l.original;return(0,s.jsx)(M.Z,{title:t.model_info.id,children:(0,s.jsx)("div",{className:"font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left w-full truncate whitespace-nowrap cursor-pointer max-w-[15ch]",onClick:()=>a(t.model_info.id),children:t.model_info.id})})}},{header:()=>(0,s.jsx)("span",{className:"text-sm font-semibold",children:"Model Information"}),accessorKey:"model_name",size:250,cell:e=>{let{row:l}=e,t=l.original,a=i(l.original)||"-",r=(0,s.jsxs)("div",{children:[(0,s.jsxs)("div",{children:[(0,s.jsx)("strong",{children:"Provider:"})," ",t.provider||"-"]}),(0,s.jsxs)("div",{children:[(0,s.jsx)("strong",{children:"Public Model Name:"})," ",a]}),(0,s.jsxs)("div",{children:[(0,s.jsx)("strong",{children:"LiteLLM Model Name:"})," ",t.litellm_model_name||"-"]})]});return(0,s.jsx)(M.Z,{title:r,children:(0,s.jsxs)("div",{className:"flex items-start space-x-2 min-w-0 w-full max-w-[250px]",children:[(0,s.jsx)("div",{className:"flex-shrink-0 mt-0.5",children:t.provider?(0,s.jsx)(F,{provider:t.provider}):(0,s.jsx)("div",{className:"w-4 h-4 rounded-full bg-gray-200 flex items-center justify-center text-xs",children:"-"})}),(0,s.jsxs)("div",{className:"flex flex-col min-w-0 flex-1",children:[(0,s.jsx)("div",{className:"text-xs font-medium text-gray-900 truncate max-w-[210px]",children:a}),(0,s.jsx)("div",{className:"text-xs text-gray-500 truncate mt-0.5 max-w-[210px]",children:t.litellm_model_name||"-"})]})]})})}},{header:()=>(0,s.jsx)("span",{className:"text-sm font-semibold",children:"Credentials"}),accessorKey:"litellm_credential_name",enableSorting:!1,size:180,cell:e=>{var l;let{row:t}=e,a=null===(l=t.original.litellm_params)||void 0===l?void 0:l.litellm_credential_name;return a?(0,s.jsx)(M.Z,{title:"Credential: ".concat(a),children:(0,s.jsxs)("div",{className:"flex items-center space-x-2 max-w-[180px]",children:[(0,s.jsx)(k.Z,{className:"w-4 h-4 text-blue-500 flex-shrink-0"}),(0,s.jsx)("span",{className:"text-xs truncate",title:a,children:a})]})}):(0,s.jsxs)("div",{className:"flex items-center space-x-2 max-w-[180px]",children:[(0,s.jsx)(k.Z,{className:"w-4 h-4 text-gray-300 flex-shrink-0"}),(0,s.jsx)("span",{className:"text-xs text-gray-400",children:"No credentials"})]})}},{header:()=>(0,s.jsx)("span",{className:"text-sm font-semibold",children:"Created By"}),accessorKey:"model_info.created_by",sortingFn:"datetime",size:160,cell:e=>{var l;let{row:t}=e,a=t.original,r=!(null===(l=a.model_info)||void 0===l?void 0:l.db_model),i=a.model_info.created_by,n=a.model_info.created_at?new Date(a.model_info.created_at).toLocaleDateString():null;return(0,s.jsxs)("div",{className:"flex flex-col min-w-0 max-w-[160px]",children:[(0,s.jsx)("div",{className:"text-xs font-medium text-gray-900 truncate",title:r?"Defined in config":i||"Unknown",children:r?"Defined in config":i||"Unknown"}),(0,s.jsx)("div",{className:"text-xs text-gray-500 truncate mt-0.5",title:r?"Config file":n||"Unknown date",children:r?"-":n||"Unknown date"})]})}},{header:()=>(0,s.jsx)("span",{className:"text-sm font-semibold",children:"Updated At"}),accessorKey:"model_info.updated_at",sortingFn:"datetime",cell:e=>{let{row:l}=e,t=l.original;return(0,s.jsx)("span",{className:"text-xs",children:t.model_info.updated_at?new Date(t.model_info.updated_at).toLocaleDateString():"-"})}},{header:()=>(0,s.jsx)("span",{className:"text-sm font-semibold",children:"Costs"}),accessorKey:"input_cost",size:120,cell:e=>{let{row:l}=e,t=l.original,a=t.input_cost,r=t.output_cost;return a||r?(0,s.jsx)(M.Z,{title:"Cost per 1M tokens",children:(0,s.jsxs)("div",{className:"flex flex-col min-w-0 max-w-[120px]",children:[a&&(0,s.jsxs)("div",{className:"text-xs font-medium text-gray-900 truncate",children:["In: $",a]}),r&&(0,s.jsxs)("div",{className:"text-xs text-gray-500 truncate mt-0.5",children:["Out: $",r]})]})}):(0,s.jsx)("div",{className:"max-w-[120px]",children:(0,s.jsx)("span",{className:"text-xs text-gray-400",children:"-"})})}},{header:()=>(0,s.jsx)("span",{className:"text-sm font-semibold",children:"Team ID"}),accessorKey:"model_info.team_id",enableSorting:!1,cell:e=>{let{row:l}=e,t=l.original;return t.model_info.team_id?(0,s.jsx)("div",{className:"overflow-hidden",children:(0,s.jsx)(M.Z,{title:t.model_info.team_id,children:(0,s.jsxs)(E.Z,{size:"xs",variant:"light",className:"font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left overflow-hidden truncate max-w-[200px]",onClick:()=>r(t.model_info.team_id),children:[t.model_info.team_id.slice(0,7),"..."]})})}):"-"}},{header:()=>(0,s.jsx)("span",{className:"text-sm font-semibold",children:"Model Access Group"}),accessorKey:"model_info.model_access_group",enableSorting:!1,cell:e=>{let{row:l}=e,t=l.original,a=t.model_info.access_groups;if(!a||0===a.length)return"-";let r=t.model_info.id,i=d.has(r),n=a.length>1,o=()=>{let e=new Set(d);i?e.delete(r):e.add(r),c(e)};return(0,s.jsxs)("div",{className:"flex items-center gap-1 overflow-hidden",children:[(0,s.jsx)(A.Z,{size:"xs",color:"blue",className:"text-xs px-1.5 py-0.5 h-5 leading-tight flex-shrink-0",children:a[0]}),(i||!n&&2===a.length)&&a.slice(1).map((e,l)=>(0,s.jsx)(A.Z,{size:"xs",color:"blue",className:"text-xs px-1.5 py-0.5 h-5 leading-tight flex-shrink-0",children:e},l+1)),n&&(0,s.jsx)("button",{onClick:e=>{e.stopPropagation(),o()},className:"text-xs text-blue-600 hover:text-blue-800 px-1 py-0.5 rounded hover:bg-blue-50 h-5 leading-tight flex-shrink-0 whitespace-nowrap",children:i?"−":"+".concat(a.length-1)})]})}},{header:()=>(0,s.jsx)("span",{className:"text-sm font-semibold",children:"Status"}),accessorKey:"model_info.db_model",cell:e=>{let{row:l}=e,t=l.original;return(0,s.jsx)("div",{className:"\n inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium\n ".concat(t.model_info.db_model?"bg-blue-50 text-blue-600":"bg-gray-100 text-gray-600","\n "),children:t.model_info.db_model?"DB Model":"Config Model"})}},{id:"actions",header:()=>(0,s.jsx)("span",{className:"text-sm font-semibold",children:"Actions"}),cell:t=>{var r,i;let{row:n}=t,o=n.original,d="Admin"===e||(null===(r=o.model_info)||void 0===r?void 0:r.created_by)===l,c=!(null===(i=o.model_info)||void 0===i?void 0:i.db_model);return(0,s.jsx)("div",{className:"flex items-center justify-end gap-2 pr-4",children:c?(0,s.jsx)(M.Z,{title:"Config model cannot be deleted on the dashboard. Please delete it from the config file.",children:(0,s.jsx)(P.Z,{icon:S.Z,size:"sm",className:"opacity-50 cursor-not-allowed"})}):(0,s.jsx)(M.Z,{title:"Delete model",children:(0,s.jsx)(P.Z,{icon:S.Z,size:"sm",onClick:()=>{d&&a(o.model_info.id)},className:d?"cursor-pointer hover:text-red-600":"opacity-50 cursor-not-allowed"})})})}}],T=e=>{var l;return(null==e?void 0:null===(l=e.model_info)||void 0===l?void 0:l.team_public_model_name)?e.model_info.team_public_model_name:(null==e?void 0:e.model_name)||"-"};var R=t(15424),O=t(67101),V=t(27281),q=t(57365),z=t(29706),D=t(84264),B=t(50337),G=t(10353),U=t(7310),H=t.n(U);let K=(e,l)=>{if(!(null==e?void 0:e.data))return{data:[]};let t=JSON.parse(JSON.stringify(e.data));for(let e=0;e{let[l]=e;return"model"!==l&&"api_base"!==l}))),t[e].provider=c,t[e].input_cost=m,t[e].output_cost=u,t[e].litellm_model_name=n,t[e].input_cost&&(t[e].input_cost=(1e6*Number(t[e].input_cost)).toFixed(2)),t[e].output_cost&&(t[e].output_cost=(1e6*Number(t[e].output_cost)).toFixed(2)),t[e].max_tokens=h,t[e].max_input_tokens=x,t[e].api_base=null==i?void 0:null===(r=i.litellm_params)||void 0===r?void 0:r.api_base,t[e].cleanedLitellmParams=p}return{data:t}};var J=e=>{let{selectedModelGroup:l,setSelectedModelGroup:t,availableModelGroups:a,availableModelAccessGroups:r,setSelectedModelId:i,setSelectedTeamId:o}=e,{data:d,isLoading:c}=m(),{userId:h,userRole:p,premiumUser:g}=(0,n.Z)(),{data:j,isLoading:v}=(0,x.y2)(),[_,b]=(0,f.useState)(""),[y,N]=(0,f.useState)(""),[w,Z]=(0,f.useState)("current_team"),[k,S]=(0,f.useState)("personal"),[A,E]=(0,f.useState)(!1),[P,M]=(0,f.useState)(null),[L,F]=(0,f.useState)(new Set),[U,J]=(0,f.useState)(1),[W]=(0,f.useState)(50),[Y,$]=(0,f.useState)({pageIndex:0,pageSize:50}),[X,Q]=(0,f.useState)([]),ee=(0,f.useMemo)(()=>H()(e=>{N(e),J(1),$(e=>({...e,pageIndex:0}))},200),[]);(0,f.useEffect)(()=>(ee(_),()=>{ee.cancel()}),[_,ee]);let el="personal"===k?void 0:k.team_id,et=(0,f.useMemo)(()=>{if(0===X.length)return;let e=X[0];return({input_cost:"costs",model_info_db_model:"status",model_info_created_by:"created_at",model_info_updated_at:"updated_at"})[e.id]||e.id},[X]),es=(0,f.useMemo)(()=>{if(0!==X.length)return X[0].desc?"desc":"asc"},[X]),{data:ea,isLoading:er}=(0,u.XP)(U,W,y||void 0,void 0,el,et,es),ei=er||c,en=e=>null!=d&&"object"==typeof d&&e in d?d[e].litellm_provider:"openai",eo=(0,f.useMemo)(()=>ea?K(ea,en):{data:[]},[ea,d]),ed=(0,f.useMemo)(()=>{var e,l,t,s;return ea?{total_count:null!==(e=ea.total_count)&&void 0!==e?e:0,current_page:null!==(l=ea.current_page)&&void 0!==l?l:1,total_pages:null!==(t=ea.total_pages)&&void 0!==t?t:1,size:null!==(s=ea.size)&&void 0!==s?s:W}:{total_count:0,current_page:1,total_pages:1,size:W}},[ea,W]),ec=(0,f.useMemo)(()=>eo&&eo.data&&0!==eo.data.length?eo.data.filter(e=>{var t,s;let a="all"===l||e.model_name===l||!l||"wildcard"===l&&(null===(t=e.model_name)||void 0===t?void 0:t.includes("*")),r="all"===P||(null===(s=e.model_info.access_groups)||void 0===s?void 0:s.includes(P))||!P;return a&&r}):[],[eo,l,P]);return(0,f.useEffect)(()=>{$(e=>({...e,pageIndex:0})),J(1)},[l,P]),(0,f.useEffect)(()=>{J(1),$(e=>({...e,pageIndex:0}))},[el]),(0,f.useEffect)(()=>{J(1),$(e=>({...e,pageIndex:0}))},[X]),(0,s.jsx)(z.Z,{children:(0,s.jsx)(O.Z,{children:(0,s.jsx)("div",{className:"flex flex-col space-y-4",children:(0,s.jsxs)("div",{className:"bg-white rounded-lg shadow",children:[(0,s.jsxs)("div",{className:"border-b px-6 py-4 bg-gray-50",children:[(0,s.jsxs)("div",{className:"flex items-center justify-between",children:[(0,s.jsxs)("div",{className:"flex items-center gap-4",children:[(0,s.jsx)(D.Z,{className:"text-lg font-semibold text-gray-900",children:"Current Team:"}),ei?(0,s.jsx)(B.Z.Input,{active:!0,style:{width:320,height:36}}):(0,s.jsxs)(V.Z,{className:"w-80",defaultValue:"personal",value:"personal"===k?"personal":k.team_id,onValueChange:e=>{if("personal"===e)S("personal"),J(1),$(e=>({...e,pageIndex:0}));else{let l=null==j?void 0:j.find(l=>l.team_id===e);l&&(S(l),J(1),$(e=>({...e,pageIndex:0})))}},children:[(0,s.jsx)(q.Z,{value:"personal",children:(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)("div",{className:"w-2 h-2 bg-blue-500 rounded-full"}),(0,s.jsx)("span",{className:"font-medium",children:"Personal"})]})}),v?(0,s.jsx)(q.Z,{value:"loading",children:(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(G.Z,{size:"small"}),(0,s.jsx)("span",{className:"font-medium text-gray-500",children:"Loading teams..."})]})}):null==j?void 0:j.filter(e=>e.team_id).map(e=>(0,s.jsx)(q.Z,{value:e.team_id,children:(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)("div",{className:"w-2 h-2 bg-green-500 rounded-full"}),(0,s.jsx)("span",{className:"font-medium",children:e.team_alias?"".concat(e.team_alias.slice(0,30),"..."):"Team ".concat(e.team_id.slice(0,30),"...")})]})},e.team_id))]})]}),(0,s.jsxs)("div",{className:"flex items-center gap-4",children:[(0,s.jsx)(D.Z,{className:"text-lg font-semibold text-gray-900",children:"View:"}),ei?(0,s.jsx)(B.Z.Input,{active:!0,style:{width:256,height:36}}):(0,s.jsxs)(V.Z,{className:"w-64",defaultValue:"current_team",value:w,onValueChange:e=>Z(e),children:[(0,s.jsx)(q.Z,{value:"current_team",children:(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)("div",{className:"w-2 h-2 bg-purple-500 rounded-full"}),(0,s.jsx)("span",{className:"font-medium",children:"Current Team Models"})]})}),(0,s.jsx)(q.Z,{value:"all",children:(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)("div",{className:"w-2 h-2 bg-gray-500 rounded-full"}),(0,s.jsx)("span",{className:"font-medium",children:"All Available Models"})]})})]})]})]}),"current_team"===w&&(0,s.jsxs)("div",{className:"flex items-start gap-2 mt-3",children:[(0,s.jsx)(R.Z,{className:"text-gray-400 mt-0.5 flex-shrink-0 text-xs"}),(0,s.jsx)("div",{className:"text-xs text-gray-500",children:"personal"===k?(0,s.jsxs)("span",{children:["To access these models: Create a Virtual Key without selecting a team on the"," ",(0,s.jsx)("a",{href:"/public?login=success&page=api-keys",className:"text-gray-600 hover:text-gray-800 underline",children:"Virtual Keys page"})]}):(0,s.jsxs)("span",{children:['To access these models: Create a Virtual Key and select Team as "',"string"!=typeof k?k.team_alias||k.team_id:"",'" on the'," ",(0,s.jsx)("a",{href:"/public?login=success&page=api-keys",className:"text-gray-600 hover:text-gray-800 underline",children:"Virtual Keys page"})]})})]})]}),(0,s.jsx)("div",{className:"border-b px-6 py-4",children:(0,s.jsxs)("div",{className:"flex flex-col space-y-4",children:[(0,s.jsxs)("div",{className:"flex flex-wrap items-center gap-3",children:[(0,s.jsxs)("div",{className:"relative w-64",children:[(0,s.jsx)("input",{type:"text",placeholder:"Search model names...",className:"w-full px-3 py-2 pl-8 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500",value:_,onChange:e=>b(e.target.value)}),(0,s.jsx)("svg",{className:"absolute left-2.5 top-2.5 h-4 w-4 text-gray-500",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,s.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"})})]}),(0,s.jsxs)("button",{className:"px-3 py-2 text-sm border rounded-md hover:bg-gray-50 flex items-center gap-2 ".concat(A?"bg-gray-100":""),onClick:()=>E(!A),children:[(0,s.jsx)("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,s.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"})}),"Filters"]}),(0,s.jsxs)("button",{className:"px-3 py-2 text-sm border rounded-md hover:bg-gray-50 flex items-center gap-2",onClick:()=>{b(""),t("all"),M(null),S("personal"),Z("current_team"),J(1),$({pageIndex:0,pageSize:50}),Q([])},children:[(0,s.jsx)("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,s.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"})}),"Reset Filters"]})]}),A&&(0,s.jsxs)("div",{className:"flex flex-wrap items-center gap-3 mt-3",children:[(0,s.jsx)("div",{className:"w-64",children:(0,s.jsxs)(V.Z,{value:null!=l?l:"all",onValueChange:e=>t("all"===e?"all":e),placeholder:"Filter by Public Model Name",children:[(0,s.jsx)(q.Z,{value:"all",children:"All Models"}),(0,s.jsx)(q.Z,{value:"wildcard",children:"Wildcard Models (*)"}),a.map((e,l)=>(0,s.jsx)(q.Z,{value:e,children:e},l))]})}),(0,s.jsx)("div",{className:"w-64",children:(0,s.jsxs)(V.Z,{value:null!=P?P:"all",onValueChange:e=>M("all"===e?null:e),placeholder:"Filter by Model Access Group",children:[(0,s.jsx)(q.Z,{value:"all",children:"All Model Access Groups"}),r.map((e,l)=>(0,s.jsx)(q.Z,{value:e,children:e},l))]})})]}),(0,s.jsxs)("div",{className:"flex justify-between items-center",children:[ei?(0,s.jsx)(B.Z.Input,{active:!0,style:{width:184,height:20}}):(0,s.jsx)("span",{className:"text-sm text-gray-700",children:ed.total_count>0?"Showing ".concat((U-1)*W+1," - ").concat(Math.min(U*W,ed.total_count)," of ").concat(ed.total_count," results"):"Showing 0 results"}),(0,s.jsxs)("div",{className:"flex items-center space-x-2",children:[ei?(0,s.jsx)(B.Z.Button,{active:!0,style:{width:84,height:30}}):(0,s.jsx)("button",{onClick:()=>{J(U-1),$(e=>({...e,pageIndex:0}))},disabled:1===U,className:"px-3 py-1 text-sm border rounded-md ".concat(1===U?"bg-gray-100 text-gray-400 cursor-not-allowed":"hover:bg-gray-50"),children:"Previous"}),ei?(0,s.jsx)(B.Z.Button,{active:!0,style:{width:56,height:30}}):(0,s.jsx)("button",{onClick:()=>{J(U+1),$(e=>({...e,pageIndex:0}))},disabled:U>=ed.total_pages,className:"px-3 py-1 text-sm border rounded-md ".concat(U>=ed.total_pages?"bg-gray-100 text-gray-400 cursor-not-allowed":"hover:bg-gray-50"),children:"Next"})]})]})]})}),(0,s.jsx)(C,{columns:I(p,h,g,i,o,T,()=>{},()=>{},L,F),data:ec,isLoading:er,sorting:X,onSortingChange:Q,pagination:Y,onPaginationChange:$,enablePagination:!0})]})})})})},W=t(96761),Y=t(12221);let $={"BadRequestError (400)":"BadRequestErrorRetries","AuthenticationError (401)":"AuthenticationErrorRetries","TimeoutError (408)":"TimeoutErrorRetries","RateLimitError (429)":"RateLimitErrorRetries","ContentPolicyViolationError (400)":"ContentPolicyViolationErrorRetries","InternalServerError (500)":"InternalServerErrorRetries"};var X=e=>{let{selectedModelGroup:l,setSelectedModelGroup:t,availableModelGroups:a,globalRetryPolicy:r,setGlobalRetryPolicy:i,defaultRetry:n,modelGroupRetryPolicy:o,setModelGroupRetryPolicy:d,handleSaveRetrySettings:c}=e;return(0,s.jsxs)(z.Z,{children:[(0,s.jsx)("div",{className:"flex items-center gap-4 mb-6",children:(0,s.jsxs)("div",{className:"flex items-center",children:[(0,s.jsx)(D.Z,{children:"Retry Policy Scope:"}),(0,s.jsxs)(V.Z,{className:"ml-2 w-48",defaultValue:"global",value:"global"===l?"global":l||a[0],onValueChange:e=>t(e),children:[(0,s.jsx)(q.Z,{value:"global",children:"Global Default"}),a.map((e,l)=>(0,s.jsx)(q.Z,{value:e,onClick:()=>t(e),children:e},l))]})]})}),"global"===l?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(W.Z,{children:"Global Retry Policy"}),(0,s.jsx)(D.Z,{className:"mb-6",children:"Default retry settings applied to all model groups unless overridden"})]}):(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(W.Z,{children:["Retry Policy for ",l]}),(0,s.jsx)(D.Z,{className:"mb-6",children:"Model-specific retry settings. Falls back to global defaults if not set."})]}),$&&(0,s.jsx)("table",{children:(0,s.jsx)("tbody",{children:Object.entries($).map((e,t)=>{var a,c,m,u;let h,[x,p]=e;if("global"===l)h=null!==(a=null==r?void 0:r[p])&&void 0!==a?a:n;else{let e=null==o?void 0:null===(c=o[l])||void 0===c?void 0:c[p];h=null!=e?e:null!==(m=null==r?void 0:r[p])&&void 0!==m?m:n}return(0,s.jsxs)("tr",{className:"flex justify-between items-center mt-2",children:[(0,s.jsxs)("td",{children:[(0,s.jsx)(D.Z,{children:x}),"global"!==l&&(0,s.jsxs)(D.Z,{className:"text-xs text-gray-500 ml-2",children:["(Global: ",null!==(u=null==r?void 0:r[p])&&void 0!==u?u:n,")"]})]}),(0,s.jsx)("td",{children:(0,s.jsx)(Y.Z,{className:"ml-5",value:h,min:0,step:1,onChange:e=>{"global"===l?i(l=>null==e?l:{...null!=l?l:{},[p]:e}):d(t=>{var s;let a=null!==(s=null==t?void 0:t[l])&&void 0!==s?s:{};return{...null!=t?t:{},[l]:{...a,[p]:e}}})}})})]},t)})})}),(0,s.jsx)(E.Z,{className:"mt-6 mr-8",onClick:c,children:"Save"})]})},Q=t(57840),ee=t(58760),el=t(867),et=t(5945),es=t(3810),ea=t(22116),er=t(89245),ei=t(5540),en=t(8881),eo=t(9114);let{Text:ed}=Q.default;var ec=e=>{let{accessToken:l,onReloadSuccess:t,buttonText:r="Reload Price Data",showIcon:i=!0,size:n="middle",type:o="primary",className:d=""}=e,[c,m]=(0,f.useState)(!1),[u,h]=(0,f.useState)(!1),[x,p]=(0,f.useState)(!1),[g,j]=(0,f.useState)(!1),[v,b]=(0,f.useState)(6),[y,N]=(0,f.useState)(null),[w,Z]=(0,f.useState)(!1);(0,f.useEffect)(()=>{C();let e=setInterval(()=>{C()},3e4);return()=>clearInterval(e)},[l]);let C=async()=>{if(l){Z(!0);try{console.log("Fetching reload status...");let e=await (0,a.getModelCostMapReloadStatus)(l);console.log("Received status:",e),N(e)}catch(e){console.error("Failed to fetch reload status:",e),N({scheduled:!1,interval_hours:null,last_run:null,next_run:null})}finally{Z(!1)}}},k=async()=>{if(!l){eo.Z.fromBackend("No access token available");return}m(!0);try{let e=await (0,a.reloadModelCostMap)(l);"success"===e.status?(eo.Z.success("Price data reloaded successfully! ".concat(e.models_count||0," models updated.")),null==t||t(),await C()):eo.Z.fromBackend("Failed to reload price data")}catch(e){console.error("Error reloading price data:",e),eo.Z.fromBackend("Failed to reload price data. Please try again.")}finally{m(!1)}},S=async()=>{if(!l){eo.Z.fromBackend("No access token available");return}if(v<=0){eo.Z.fromBackend("Hours must be greater than 0");return}h(!0);try{let e=await (0,a.scheduleModelCostMapReload)(l,v);"success"===e.status?(eo.Z.success("Periodic reload scheduled for every ".concat(v," hours")),j(!1),await C()):eo.Z.fromBackend("Failed to schedule periodic reload")}catch(e){console.error("Error scheduling reload:",e),eo.Z.fromBackend("Failed to schedule periodic reload. Please try again.")}finally{h(!1)}},A=async()=>{if(!l){eo.Z.fromBackend("No access token available");return}p(!0);try{let e=await (0,a.cancelModelCostMapReload)(l);"success"===e.status?(eo.Z.success("Periodic reload cancelled successfully"),await C()):eo.Z.fromBackend("Failed to cancel periodic reload")}catch(e){console.error("Error cancelling reload:",e),eo.Z.fromBackend("Failed to cancel periodic reload. Please try again.")}finally{p(!1)}},E=e=>{if(!e)return"Never";try{return new Date(e).toLocaleString()}catch(l){return e}};return(0,s.jsxs)("div",{className:d,children:[(0,s.jsxs)(ee.Z,{direction:"horizontal",size:"middle",style:{marginBottom:16},children:[(0,s.jsx)(el.Z,{title:"Hard Refresh Price Data",description:"This will immediately fetch the latest pricing information from the remote source. Continue?",onConfirm:k,okText:"Yes",cancelText:"No",okButtonProps:{style:{backgroundColor:"#6366f1",borderColor:"#6366f1",color:"white",fontWeight:"500",borderRadius:"0.375rem",padding:"0.375rem 0.75rem",height:"auto",fontSize:"0.875rem",lineHeight:"1.25rem",transition:"all 0.2s ease-in-out"},onMouseEnter:e=>{e.currentTarget.style.backgroundColor="#4f46e5"},onMouseLeave:e=>{e.currentTarget.style.backgroundColor="#6366f1"}},children:(0,s.jsx)(_.ZP,{type:o,size:n,loading:c,icon:i?(0,s.jsx)(er.Z,{}):void 0,style:{backgroundColor:"#6366f1",borderColor:"#6366f1",color:"white",fontWeight:"500",borderRadius:"0.375rem",padding:"0.375rem 0.75rem",height:"auto",fontSize:"0.875rem",lineHeight:"1.25rem",transition:"all 0.2s ease-in-out"},onMouseEnter:e=>{e.currentTarget.style.backgroundColor="#4f46e5"},onMouseLeave:e=>{e.currentTarget.style.backgroundColor="#6366f1"},children:r})}),(null==y?void 0:y.scheduled)?(0,s.jsx)(_.ZP,{type:"default",size:n,danger:!0,icon:(0,s.jsx)(en.Z,{}),loading:x,onClick:A,style:{borderColor:"#ff4d4f",color:"#ff4d4f",fontWeight:"500",borderRadius:"0.375rem",padding:"0.375rem 0.75rem",height:"auto",fontSize:"0.875rem",lineHeight:"1.25rem"},children:"Cancel Periodic Reload"}):(0,s.jsx)(_.ZP,{type:"default",size:n,icon:(0,s.jsx)(ei.Z,{}),onClick:()=>j(!0),style:{borderColor:"#d9d9d9",color:"#6366f1",fontWeight:"500",borderRadius:"0.375rem",padding:"0.375rem 0.75rem",height:"auto",fontSize:"0.875rem",lineHeight:"1.25rem"},children:"Set Up Periodic Reload"})]}),y&&(0,s.jsx)(et.Z,{size:"small",style:{backgroundColor:"#f8f9fa",border:"1px solid #e9ecef",borderRadius:8},children:(0,s.jsxs)(ee.Z,{direction:"vertical",size:"small",style:{width:"100%"},children:[y.scheduled?(0,s.jsx)("div",{children:(0,s.jsxs)(es.Z,{color:"green",icon:(0,s.jsx)(ei.Z,{}),children:["Scheduled every ",y.interval_hours," hours"]})}):(0,s.jsx)(ed,{type:"secondary",children:"No periodic reload scheduled"}),(0,s.jsxs)("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center"},children:[(0,s.jsx)(ed,{type:"secondary",style:{fontSize:"12px"},children:"Last run:"}),(0,s.jsx)(ed,{style:{fontSize:"12px"},children:E(y.last_run)})]}),y.scheduled&&(0,s.jsxs)(s.Fragment,{children:[y.next_run&&(0,s.jsxs)("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center"},children:[(0,s.jsx)(ed,{type:"secondary",style:{fontSize:"12px"},children:"Next run:"}),(0,s.jsx)(ed,{style:{fontSize:"12px"},children:E(y.next_run)})]}),(0,s.jsxs)("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center"},children:[(0,s.jsx)(ed,{type:"secondary",style:{fontSize:"12px"},children:"Status:"}),(0,s.jsx)(es.Z,{color:(null==y?void 0:y.scheduled)?y.last_run?"success":"processing":"default",children:(null==y?void 0:y.scheduled)?y.last_run?"Active":"Ready":"Not scheduled"})]})]})]})}),(0,s.jsxs)(ea.Z,{title:"Set Up Periodic Reload",open:g,onOk:S,onCancel:()=>j(!1),confirmLoading:u,okText:"Schedule",cancelText:"Cancel",okButtonProps:{style:{backgroundColor:"#6366f1",borderColor:"#6366f1",color:"white"}},children:[(0,s.jsx)("div",{style:{marginBottom:16},children:(0,s.jsx)(ed,{children:"Set up automatic reload of price data every:"})}),(0,s.jsx)("div",{style:{marginBottom:16},children:(0,s.jsx)(Y.Z,{min:1,max:168,value:v,onChange:e=>b(e||6),addonAfter:"hours",style:{width:"100%"}})}),(0,s.jsx)("div",{children:(0,s.jsxs)(ed,{type:"secondary",children:["This will automatically fetch the latest pricing data from the remote source every ",v," hours."]})})]})]})},em=()=>{let{accessToken:e}=(0,n.Z)(),{refetch:l}=m();return(0,s.jsx)(z.Z,{children:(0,s.jsxs)("div",{className:"p-6",children:[(0,s.jsxs)("div",{className:"mb-6",children:[(0,s.jsx)(W.Z,{children:"Price Data Management"}),(0,s.jsx)(D.Z,{className:"text-tremor-content",children:"Manage model pricing data and configure automatic reload schedules"})]}),(0,s.jsx)(ec,{accessToken:e,onReloadSuccess:()=>{l()},buttonText:"Reload Price Data",size:"middle",type:"primary",className:"w-full"})]})})};let eu=async(e,l,t)=>{try{var s,a;console.log("handling submit for formValues:",e);let l=e.model_mappings||[];if("model_mappings"in e&&delete e.model_mappings,e.model&&e.model.includes("all-wildcard")){let t=e.custom_llm_provider,a=(null!==(s=L.fK[t])&&void 0!==s?s:t.toLowerCase())+"/*";e.model_name=a,l.push({public_name:a,litellm_model:a}),e.model=a}let t=[];for(let s of l){let l={},r={},i=s.public_name;for(let[t,i]of(l.model=s.litellm_model,e.input_cost_per_token&&(e.input_cost_per_token=Number(e.input_cost_per_token)/1e6),e.output_cost_per_token&&(e.output_cost_per_token=Number(e.output_cost_per_token)/1e6),l.model=s.litellm_model,console.log("formValues add deployment:",e),Object.entries(e)))if(""!==i&&"custom_pricing"!==t&&"pricing_model"!==t&&"cache_control"!==t){if("model_name"==t)l.model=i;else if("custom_llm_provider"==t){console.log("custom_llm_provider:",i);let e=null!==(a=L.fK[i])&&void 0!==a?a:i.toLowerCase();l.custom_llm_provider=e,console.log("custom_llm_provider mappingResult:",e)}else if("model"==t)continue;else if("base_model"===t)r[t]=i;else if("team_id"===t)r.team_id=i;else if("model_access_group"===t)r.access_groups=i;else if("mode"==t)console.log("placing mode in modelInfo"),r.mode=i,delete l.mode;else if("custom_model_name"===t)l.model=i;else if("litellm_extra_params"==t){console.log("litellm_extra_params:",i);let e={};if(i&&void 0!=i){try{e=JSON.parse(i)}catch(e){throw eo.Z.fromBackend("Failed to parse LiteLLM Extra Params: "+e),Error("Failed to parse litellm_extra_params: "+e)}for(let[t,s]of Object.entries(e))l[t]=s}}else if("model_info_params"==t){console.log("model_info_params:",i);let e={};if(i&&void 0!=i){try{e=JSON.parse(i)}catch(e){throw eo.Z.fromBackend("Failed to parse LiteLLM Extra Params: "+e),Error("Failed to parse litellm_extra_params: "+e)}for(let[l,t]of Object.entries(e))r[l]=t}}else if("input_cost_per_token"===t||"output_cost_per_token"===t||"input_cost_per_second"===t){i&&(l[t]=Number(i));continue}else l[t]=i}t.push({litellmParamsObj:l,modelInfoObj:r,modelName:i})}return t}catch(e){eo.Z.fromBackend("Failed to create model: "+e)}},eh=async(e,l,t,s)=>{try{let r=await eu(e,l,t);if(!r||0===r.length)return;for(let e of r){let{litellmParamsObj:t,modelInfoObj:s,modelName:r}=e,i={model_name:r,litellm_params:t,model_info:s},n=await (0,a.modelCreateCall)(l,i);console.log("response for model create call: ".concat(n.data))}s&&s(),t.resetFields()}catch(e){eo.Z.fromBackend("Failed to add model: "+e)}};var ex=t(53410),ep=t(62490),eg=t(10032),ef=t(21609),ej=t(31283),ev=t(37592);let e_=(0,i.n)("providerFields"),eb=()=>(0,r.a)({queryKey:e_.list({}),queryFn:async()=>await (0,a.getProviderCreateMetadata)(),staleTime:864e5,gcTime:864e5});var ey=t(3632),eN=t(56522),ew=t(47451),eZ=t(69410),eC=t(65319),ek=t(4260);let{Link:eS}=Q.default,eA=e=>{var l,t,s,a,r;let i="password"===e.field_type?"password":"select"===e.field_type?"select":"upload"===e.field_type?"upload":"textarea"===e.field_type?"textarea":"text";return{key:e.key,label:e.label,placeholder:null!==(l=e.placeholder)&&void 0!==l?l:void 0,tooltip:null!==(t=e.tooltip)&&void 0!==t?t:void 0,required:null!==(s=e.required)&&void 0!==s&&s,type:i,options:null!==(a=e.options)&&void 0!==a?a:void 0,defaultValue:null!==(r=e.default_value)&&void 0!==r?r:void 0}},eE={};var eP=e=>{let{selectedProvider:l,uploadProps:t}=e,a=L.Cl[l],r=eg.Z.useFormInstance(),{data:i,isLoading:n,error:o}=eb(),d=f.useMemo(()=>{if(!i)return null;let e={};return i.forEach(l=>{let t=l.provider_display_name,s=l.credential_fields.map(eA);e[t]=s,l.provider&&(e[l.provider]=s),l.litellm_provider&&(e[l.litellm_provider]=s)}),e},[i]);f.useEffect(()=>{d&&Object.assign(eE,d)},[d]);let c=f.useMemo(()=>{var e;let t=null!==(e=eE[a])&&void 0!==e?e:eE[l];if(t)return t;if(!i)return[];let s=i.find(e=>e.provider_display_name===a||e.provider===l||e.litellm_provider===l);if(!s)return[];let r=s.credential_fields.map(eA);return eE[s.provider_display_name]=r,s.provider&&(eE[s.provider]=r),s.litellm_provider&&(eE[s.litellm_provider]=r),r},[a,l,i]),m={name:"file",accept:".json",beforeUpload:e=>{if("application/json"===e.type){let l=new FileReader;l.onload=e=>{if(e.target){let l=e.target.result;console.log("Setting field value from JSON, length: ".concat(l.length)),r.setFieldsValue({vertex_credentials:l}),console.log("Form values after setting:",r.getFieldsValue())}},l.readAsText(e)}return!1},onChange(e){console.log("Upload onChange triggered in ProviderSpecificFields"),console.log("Current form values:",r.getFieldsValue()),"uploading"!==e.file.status&&console.log(e.file,e.fileList)}};return(0,s.jsxs)(s.Fragment,{children:[n&&0===c.length&&(0,s.jsx)(ew.Z,{children:(0,s.jsx)(eZ.Z,{span:24,children:(0,s.jsx)(eN.x,{className:"mb-2",children:"Loading provider fields..."})})}),o&&0===c.length&&(0,s.jsx)(ew.Z,{children:(0,s.jsx)(eZ.Z,{span:24,children:(0,s.jsx)(eN.x,{className:"mb-2 text-red-500",children:o instanceof Error?o.message:"Failed to load provider credential fields"})})}),c.map(e=>{var l;return(0,s.jsxs)(f.Fragment,{children:[(0,s.jsx)(eg.Z.Item,{label:e.label,name:e.key,rules:e.required?[{required:!0,message:"Required"}]:void 0,tooltip:e.tooltip,className:"vertex_credentials"===e.key?"mb-0":void 0,children:"select"===e.type?(0,s.jsx)(ev.default,{placeholder:e.placeholder,defaultValue:e.defaultValue,children:null===(l=e.options)||void 0===l?void 0:l.map(e=>(0,s.jsx)(ev.default.Option,{value:e,children:e},e))}):"upload"===e.type?(0,s.jsx)(eC.default,{...m,onChange:l=>{(null==t?void 0:t.onChange)&&t.onChange(l),setTimeout(()=>{let l=r.getFieldValue(e.key);console.log("".concat(e.key," value after upload:"),JSON.stringify(l))},500)},children:(0,s.jsx)(_.ZP,{icon:(0,s.jsx)(ey.Z,{}),children:"Click to Upload"})}):"textarea"===e.type?(0,s.jsx)(ek.default.TextArea,{placeholder:e.placeholder,defaultValue:e.defaultValue,rows:6,style:{fontFamily:"monospace",fontSize:"12px"}}):(0,s.jsx)(eN.o,{placeholder:e.placeholder,type:"password"===e.type?"password":"text",defaultValue:e.defaultValue})}),"vertex_credentials"===e.key&&(0,s.jsx)(ew.Z,{children:(0,s.jsx)(eZ.Z,{children:(0,s.jsx)(eN.x,{className:"mb-3 mt-1",children:"Give a gcp service account(.json file)"})})}),"base_model"===e.key&&(0,s.jsxs)(ew.Z,{children:[(0,s.jsx)(eZ.Z,{span:10}),(0,s.jsx)(eZ.Z,{span:10,children:(0,s.jsxs)(eN.x,{className:"mb-2",children:["The actual model your azure deployment uses. Used for accurate cost tracking. Select name from"," ",(0,s.jsx)(eS,{href:"https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json",target:"_blank",children:"here"})]})})]})]},e.key)})]})};let{Link:eM}=Q.default;var eL=e=>{let{open:l,onCancel:t,onAddCredential:a,uploadProps:r}=e,[i]=eg.Z.useForm(),[n,o]=(0,f.useState)(L.Cl.OpenAI);return(0,s.jsx)(ea.Z,{title:"Add New Credential",open:l,onCancel:()=>{t(),i.resetFields()},footer:null,width:600,children:(0,s.jsxs)(eg.Z,{form:i,onFinish:e=>{a(Object.entries(e).reduce((e,l)=>{let[t,s]=l;return""!==s&&null!=s&&(e[t]=s),e},{})),i.resetFields()},layout:"vertical",children:[(0,s.jsx)(eg.Z.Item,{label:"Credential Name:",name:"credential_name",rules:[{required:!0,message:"Credential name is required"}],children:(0,s.jsx)(ej.o,{placeholder:"Enter a friendly name for these credentials"})}),(0,s.jsx)(eg.Z.Item,{rules:[{required:!0,message:"Required"}],label:"Provider:",name:"custom_llm_provider",tooltip:"Helper to auto-populate provider specific fields",children:(0,s.jsx)(ev.default,{showSearch:!0,onChange:e=>{o(e),i.setFieldValue("custom_llm_provider",e)},children:Object.entries(L.Cl).map(e=>{let[l,t]=e;return(0,s.jsx)(ev.default.Option,{value:l,children:(0,s.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,s.jsx)("img",{src:L.cd[t],alt:"".concat(l," logo"),className:"w-5 h-5",onError:e=>{let l=e.target,s=l.parentElement;if(s){let e=document.createElement("div");e.className="w-5 h-5 rounded-full bg-gray-200 flex items-center justify-center text-xs",e.textContent=t.charAt(0),s.replaceChild(e,l)}}}),(0,s.jsx)("span",{children:t})]})},l)})})}),(0,s.jsx)(eP,{selectedProvider:n,uploadProps:r}),(0,s.jsxs)("div",{className:"flex justify-between items-center",children:[(0,s.jsx)(M.Z,{title:"Get help on our github",children:(0,s.jsx)(eM,{href:"https://github.com/BerriAI/litellm/issues",children:"Need Help?"})}),(0,s.jsxs)("div",{children:[(0,s.jsx)(_.ZP,{onClick:()=>{t(),i.resetFields()},style:{marginRight:10},children:"Cancel"}),(0,s.jsx)(_.ZP,{htmlType:"submit",children:"Add Credential"})]})]})]})})};let{Link:eF}=Q.default;function eI(e){let{open:l,onCancel:t,onUpdateCredential:a,uploadProps:r,existingCredential:i}=e,[n]=eg.Z.useForm(),[o,d]=(0,f.useState)(L.Cl.Anthropic);return(0,f.useEffect)(()=>{if(i){let e=Object.entries(i.credential_values||{}).reduce((e,l)=>{let[t,s]=l;return e[t]=null!=s?s:null,e},{});n.setFieldsValue({credential_name:i.credential_name,custom_llm_provider:i.credential_info.custom_llm_provider,...e}),d(i.credential_info.custom_llm_provider)}},[i]),(0,s.jsx)(ea.Z,{title:"Edit Credential",open:l,onCancel:()=>{t(),n.resetFields()},footer:null,width:600,destroyOnHidden:!0,children:(0,s.jsxs)(eg.Z,{form:n,onFinish:e=>{a(Object.entries(e).reduce((e,l)=>{let[t,s]=l;return""!==s&&null!=s&&(e[t]=s),e},{})),n.resetFields()},layout:"vertical",children:[(0,s.jsx)(eg.Z.Item,{label:"Credential Name:",name:"credential_name",rules:[{required:!0,message:"Credential name is required"}],initialValue:null==i?void 0:i.credential_name,children:(0,s.jsx)(ej.o,{placeholder:"Enter a friendly name for these credentials",disabled:null!=i&&!!i.credential_name})}),(0,s.jsx)(eg.Z.Item,{rules:[{required:!0,message:"Required"}],label:"Provider:",name:"custom_llm_provider",tooltip:"Helper to auto-populate provider specific fields",children:(0,s.jsx)(ev.default,{showSearch:!0,onChange:e=>{d(e),n.setFieldValue("custom_llm_provider",e)},children:Object.entries(L.Cl).map(e=>{let[l,t]=e;return(0,s.jsx)(ev.default.Option,{value:l,children:(0,s.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,s.jsx)("img",{src:L.cd[t],alt:"".concat(l," logo"),className:"w-5 h-5",onError:e=>{let l=e.target,s=l.parentElement;if(s){let e=document.createElement("div");e.className="w-5 h-5 rounded-full bg-gray-200 flex items-center justify-center text-xs",e.textContent=t.charAt(0),s.replaceChild(e,l)}}}),(0,s.jsx)("span",{children:t})]})},l)})})}),(0,s.jsx)(eP,{selectedProvider:o,uploadProps:r}),(0,s.jsxs)("div",{className:"flex justify-between items-center",children:[(0,s.jsx)(M.Z,{title:"Get help on our github",children:(0,s.jsx)(eF,{href:"https://github.com/BerriAI/litellm/issues",children:"Need Help?"})}),(0,s.jsxs)("div",{children:[(0,s.jsx)(_.ZP,{onClick:()=>{t(),n.resetFields()},style:{marginRight:10},children:"Cancel"}),(0,s.jsx)(_.ZP,{htmlType:"submit",children:"Update Credential"})]})]})]})})}var eT=e=>{var l;let{uploadProps:t}=e,{accessToken:r}=(0,n.Z)(),{data:i,refetch:o}=d(),c=(null==i?void 0:i.credentials)||[],[m,u]=(0,f.useState)(!1),[h,x]=(0,f.useState)(!1),[p,g]=(0,f.useState)(null),[j,v]=(0,f.useState)(null),[_,b]=(0,f.useState)(!1),[y,N]=(0,f.useState)(!1),[w]=eg.Z.useForm(),Z=["credential_name","custom_llm_provider"],C=async e=>{if(!r)return;let l=Object.entries(e).filter(e=>{let[l]=e;return!Z.includes(l)}).reduce((e,l)=>{let[t,s]=l;return{...e,[t]:s}},{}),t={credential_name:e.credential_name,credential_values:l,credential_info:{custom_llm_provider:e.custom_llm_provider}};await (0,a.credentialUpdateCall)(r,e.credential_name,t),eo.Z.success("Credential updated successfully"),x(!1),await o()},k=async e=>{if(!r)return;let l=Object.entries(e).filter(e=>{let[l]=e;return!Z.includes(l)}).reduce((e,l)=>{let[t,s]=l;return{...e,[t]:s}},{}),t={credential_name:e.credential_name,credential_values:l,credential_info:{custom_llm_provider:e.custom_llm_provider}};await (0,a.credentialCreateCall)(r,t),eo.Z.success("Credential added successfully"),u(!1),await o()},A=e=>{let l={openai:"blue",azure:"indigo",anthropic:"purple",default:"gray"},t=l[e.toLowerCase()]||l.default;return(0,s.jsx)(ep.Ct,{color:t,size:"xs",children:e})},E=async()=>{if(r&&j){N(!0);try{await (0,a.credentialDeleteCall)(r,j.credential_name),eo.Z.success("Credential deleted successfully"),await o()}catch(e){eo.Z.error("Failed to delete credential")}finally{v(null),b(!1),N(!1)}}},P=e=>{v(e),b(!0)};return(0,s.jsxs)("div",{className:"w-full mx-auto flex-auto overflow-y-auto p-2",children:[(0,s.jsx)(ep.zx,{onClick:()=>u(!0),children:"Add Credential"}),(0,s.jsx)("div",{className:"flex justify-between items-center mt-4 mb-4",children:(0,s.jsx)(ep.xv,{children:"Configured credentials for different AI providers. Add and manage your API credentials."})}),(0,s.jsx)(ep.Zb,{children:(0,s.jsxs)(ep.iA,{children:[(0,s.jsx)(ep.ss,{children:(0,s.jsxs)(ep.SC,{children:[(0,s.jsx)(ep.xs,{children:"Credential Name"}),(0,s.jsx)(ep.xs,{children:"Provider"}),(0,s.jsx)(ep.xs,{children:"Actions"})]})}),(0,s.jsx)(ep.RM,{children:c&&0!==c.length?c.map((e,l)=>{var t;return(0,s.jsxs)(ep.SC,{children:[(0,s.jsx)(ep.pj,{children:e.credential_name}),(0,s.jsx)(ep.pj,{children:A((null===(t=e.credential_info)||void 0===t?void 0:t.custom_llm_provider)||"-")}),(0,s.jsxs)(ep.pj,{children:[(0,s.jsx)(ep.zx,{icon:ex.Z,variant:"light",size:"sm",onClick:()=>{g(e),x(!0)}}),(0,s.jsx)(ep.zx,{icon:S.Z,variant:"light",size:"sm",onClick:()=>P(e),className:"ml-2"})]})]},l)}):(0,s.jsx)(ep.SC,{children:(0,s.jsx)(ep.pj,{colSpan:4,className:"text-center py-4 text-gray-500",children:"No credentials configured"})})})]})}),m&&(0,s.jsx)(eL,{onAddCredential:k,open:m,onCancel:()=>u(!1),uploadProps:t}),h&&(0,s.jsx)(eI,{open:h,existingCredential:p,onUpdateCredential:C,uploadProps:t,onCancel:()=>x(!1)}),(0,s.jsx)(ef.Z,{isOpen:_,onCancel:()=>{v(null),b(!1)},onOk:E,title:"Delete Credential?",message:"Are you sure you want to delete this credential? This action cannot be undone and may break existing integrations.",resourceInformationTitle:"Credential Information",resourceInformation:[{label:"Credential Name",value:null==j?void 0:j.credential_name},{label:"Provider",value:(null==j?void 0:null===(l=j.credential_info)||void 0===l?void 0:l.custom_llm_provider)||"-"}],confirmLoading:y,requiredConfirmation:null==j?void 0:j.credential_name})]})},eR=t(20347),eO=t(23628),eV=t(29827),eq=t(49804),ez=t(12485),eD=t(18135),eB=t(35242),eG=t(77991),eU=t(34419),eH=t(58643),eK=t(29),eJ=t.n(eK),eW=t(23496),eY=t(35291),e$=t(23639);let{Text:eX}=Q.default;var eQ=e=>{let{formValues:l,accessToken:t,testMode:r,modelName:i="this model",onClose:n,onTestComplete:o}=e,[d,c]=f.useState(null),[m,u]=f.useState(null),[h,x]=f.useState(null),[p,g]=f.useState(!0),[j,v]=f.useState(!1),[b,y]=f.useState(!1),N=async()=>{g(!0),y(!1),c(null),u(null),x(null),v(!1),await new Promise(e=>setTimeout(e,100));try{console.log("Testing connection with form values:",l);let r=await eu(l,t,null);if(!r){console.log("No result from prepareModelAddRequest"),c("Failed to prepare model data. Please check your form inputs."),v(!1),g(!1);return}console.log("Result from prepareModelAddRequest:",r);let{litellmParamsObj:i,modelInfoObj:n,modelName:o}=r[0],d=await (0,a.testConnectionRequest)(t,i,n,null==n?void 0:n.mode);if("success"===d.status)eo.Z.success("Connection test successful!"),c(null),v(!0);else{var e,s;let l=(null===(e=d.result)||void 0===e?void 0:e.error)||d.message||"Unknown error";c(l),u(i),x(null===(s=d.result)||void 0===s?void 0:s.raw_request_typed_dict),v(!1)}}catch(e){console.error("Test connection error:",e),c(e instanceof Error?e.message:String(e)),v(!1)}finally{g(!1),o&&o()}};f.useEffect(()=>{let e=setTimeout(()=>{N()},200);return()=>clearTimeout(e)},[]);let w=e=>e?e.split("stack trace:")[0].trim().replace(/^litellm\.(.*?)Error: /,""):"Unknown error",Z="string"==typeof d?w(d):(null==d?void 0:d.message)?w(d.message):"Unknown error",C=h?((e,l,t)=>{let s=JSON.stringify(l,null,2).split("\n").map(e=>" ".concat(e)).join("\n"),a=Object.entries(t).map(e=>{let[l,t]=e;return"-H '".concat(l,": ").concat(t,"'")}).join(" \\\n ");return"curl -X POST \\\n ".concat(e," \\\n ").concat(a?"".concat(a," \\\n "):"","-H 'Content-Type: application/json' \\\n -d '{\n").concat(s,"\n }'")})(h.raw_request_api_base,h.raw_request_body,h.raw_request_headers||{}):"";return(0,s.jsxs)("div",{style:{padding:"24px",borderRadius:"8px",backgroundColor:"#fff"},children:[p?(0,s.jsxs)("div",{style:{textAlign:"center",padding:"32px 20px"},className:"jsx-dc9a0e2d897fe63b",children:[(0,s.jsx)("div",{style:{marginBottom:"16px"},className:"jsx-dc9a0e2d897fe63b loading-spinner",children:(0,s.jsx)("div",{style:{border:"3px solid #f3f3f3",borderTop:"3px solid #1890ff",borderRadius:"50%",width:"30px",height:"30px",animation:"spin 1s linear infinite",margin:"0 auto"},className:"jsx-dc9a0e2d897fe63b"})}),(0,s.jsxs)(eX,{style:{fontSize:"16px"},children:["Testing connection to ",i,"..."]}),(0,s.jsx)(eJ(),{id:"dc9a0e2d897fe63b",children:"@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg);transform:rotate(0deg)}100%{-moz-transform:rotate(360deg);transform:rotate(360deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg);transform:rotate(0deg)}100%{-o-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);-moz-transform:rotate(0deg);-o-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);transform:rotate(360deg)}}"})]}):j?(0,s.jsxs)("div",{style:{display:"flex",alignItems:"center",justifyContent:"center",padding:"32px 20px"},children:[(0,s.jsx)("div",{style:{color:"#52c41a",fontSize:"24px",display:"flex",alignItems:"center"},children:(0,s.jsx)("svg",{viewBox:"64 64 896 896",focusable:"false","data-icon":"check-circle",width:"1em",height:"1em",fill:"currentColor","aria-hidden":"true",children:(0,s.jsx)("path",{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm193.5 301.7l-210.6 292a31.8 31.8 0 01-51.7 0L318.5 484.9c-3.8-5.3 0-12.7 6.5-12.7h46.9c10.2 0 19.9 4.9 25.9 13.3l71.2 98.8 157.2-218c6-8.3 15.6-13.3 25.9-13.3H699c6.5 0 10.3 7.4 6.5 12.7z"})})}),(0,s.jsxs)(eX,{type:"success",style:{fontSize:"18px",fontWeight:500,marginLeft:"10px"},children:["Connection to ",i," successful!"]})]}):(0,s.jsx)(s.Fragment,{children:(0,s.jsxs)("div",{children:[(0,s.jsxs)("div",{style:{display:"flex",alignItems:"center",marginBottom:"20px"},children:[(0,s.jsx)(eY.Z,{style:{color:"#ff4d4f",fontSize:"24px",marginRight:"12px"}}),(0,s.jsxs)(eX,{type:"danger",style:{fontSize:"18px",fontWeight:500},children:["Connection to ",i," failed"]})]}),(0,s.jsxs)("div",{style:{backgroundColor:"#fff2f0",border:"1px solid #ffccc7",borderRadius:"8px",padding:"16px",marginBottom:"20px",boxShadow:"0 1px 2px rgba(0, 0, 0, 0.03)"},children:[(0,s.jsxs)(eX,{strong:!0,style:{display:"block",marginBottom:"8px"},children:["Error:"," "]}),(0,s.jsx)(eX,{type:"danger",style:{fontSize:"14px",lineHeight:"1.5"},children:Z}),d&&(0,s.jsx)("div",{style:{marginTop:"12px"},children:(0,s.jsx)(_.ZP,{type:"link",onClick:()=>y(!b),style:{paddingLeft:0,height:"auto"},children:b?"Hide Details":"Show Details"})})]}),b&&(0,s.jsxs)("div",{style:{marginBottom:"20px"},children:[(0,s.jsx)(eX,{strong:!0,style:{display:"block",marginBottom:"8px",fontSize:"15px"},children:"Troubleshooting Details"}),(0,s.jsx)("pre",{style:{backgroundColor:"#f5f5f5",padding:"16px",borderRadius:"8px",fontSize:"13px",maxHeight:"200px",overflow:"auto",border:"1px solid #e8e8e8",lineHeight:"1.5"},children:"string"==typeof d?d:JSON.stringify(d,null,2)})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(eX,{strong:!0,style:{display:"block",marginBottom:"8px",fontSize:"15px"},children:"API Request"}),(0,s.jsx)("pre",{style:{backgroundColor:"#f5f5f5",padding:"16px",borderRadius:"8px",fontSize:"13px",maxHeight:"250px",overflow:"auto",border:"1px solid #e8e8e8",lineHeight:"1.5"},children:C||"No request data available"}),(0,s.jsx)(_.ZP,{style:{marginTop:"8px"},icon:(0,s.jsx)(e$.Z,{}),onClick:()=>{navigator.clipboard.writeText(C||""),eo.Z.success("Copied to clipboard")},children:"Copy to Clipboard"})]})]})}),(0,s.jsx)(eW.Z,{style:{margin:"24px 0 16px"}}),(0,s.jsx)("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center"},children:(0,s.jsx)(_.ZP,{type:"link",href:"https://docs.litellm.ai/docs/providers",target:"_blank",icon:(0,s.jsx)(R.Z,{}),children:"View Documentation"})})]})};let e0=async(e,l,t,s)=>{try{console.log("=== AUTO ROUTER SUBMIT HANDLER CALLED ==="),console.log("handling auto router submit for formValues:",e),console.log("Access token:",l?"Present":"Missing"),console.log("Form:",t?"Present":"Missing"),console.log("Callback:",s?"Present":"Missing");let r={model_name:e.auto_router_name,litellm_params:{model:"auto_router/".concat(e.auto_router_name),auto_router_config:JSON.stringify(e.auto_router_config),auto_router_default_model:e.auto_router_default_model},model_info:{}};e.auto_router_embedding_model&&"custom"!==e.auto_router_embedding_model?r.litellm_params.auto_router_embedding_model=e.auto_router_embedding_model:e.custom_embedding_model&&(r.litellm_params.auto_router_embedding_model=e.custom_embedding_model),e.team_id&&(r.model_info.team_id=e.team_id),e.model_access_group&&e.model_access_group.length>0&&(r.model_info.access_groups=e.model_access_group),console.log("Auto router configuration to be created:",r),console.log("Auto router config (stringified):",r.litellm_params.auto_router_config),console.log("Calling modelCreateCall with:",{accessToken:l?"Present":"Missing",config:r});let i=await (0,a.modelCreateCall)(l,r);console.log("response for auto router create call:",i),t.resetFields()}catch(e){console.error("Failed to add auto router:",e),eo.Z.fromBackend("Failed to add auto router: "+e)}};var e1=t(10703),e2=t(44851),e4=t(96473),e5=t(70464),e6=t(26349),e3=t(92280);let{TextArea:e8}=ek.default,{Panel:e7}=e2.default;var e9=e=>{let{modelInfo:l,value:t,onChange:a}=e,[r,i]=(0,f.useState)([]),[n,o]=(0,f.useState)(!1),[d,c]=(0,f.useState)([]);(0,f.useEffect)(()=>{if(null==t?void 0:t.routes){let e=t.routes.map((e,l)=>({id:e.id||"route-".concat(l,"-").concat(Date.now()),model:e.name||e.model||"",utterances:e.utterances||[],description:e.description||"",score_threshold:e.score_threshold||.5}));i(e),c(e.map(e=>e.id))}else i([]),c([])},[t]);let m=e=>{let l=r.filter(l=>l.id!==e);i(l),h(l),c(l=>l.filter(l=>l!==e))},u=(e,l,t)=>{let s=r.map(s=>s.id===e?{...s,[l]:t}:s);i(s),h(s)},h=e=>{let l={routes:e.map(e=>({name:e.model,utterances:e.utterances,description:e.description,score_threshold:e.score_threshold}))};null==a||a(l)},x=l.map(e=>({value:e.model_group,label:e.model_group}));return(0,s.jsxs)("div",{className:"w-full max-w-none",children:[(0,s.jsxs)("div",{className:"flex justify-between items-center mb-6 w-full",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(e3.x,{className:"text-lg font-semibold",children:"Routes Configuration"}),(0,s.jsx)(M.Z,{title:"Configure routing logic to automatically select the best model based on user input patterns",children:(0,s.jsx)(R.Z,{className:"text-gray-400"})})]}),(0,s.jsx)(_.ZP,{type:"primary",icon:(0,s.jsx)(e4.Z,{}),onClick:()=>{let e="route-".concat(Date.now()),l=[...r,{id:e,model:"",utterances:[],description:"",score_threshold:.5}];i(l),h(l),c(l=>[...l,e])},className:"bg-blue-600 hover:bg-blue-700",children:"Add Route"})]}),0===r.length?(0,s.jsx)("div",{className:"text-center py-12 text-gray-500 bg-gray-50 rounded-lg border-2 border-dashed border-gray-200 mb-6",children:(0,s.jsx)(e3.x,{children:"No routes configured. Click “Add Route” to get started."})}):(0,s.jsx)("div",{className:"space-y-3 mb-6 w-full",children:r.map((e,l)=>(0,s.jsx)(et.Z,{className:"border border-gray-200 shadow-sm w-full",bodyStyle:{padding:0},children:(0,s.jsx)(e2.default,{ghost:!0,expandIcon:e=>{let{isActive:l}=e;return(0,s.jsx)(e5.Z,{rotate:l?180:0})},activeKey:d,onChange:e=>c(Array.isArray(e)?e:[e].filter(Boolean)),items:[{key:e.id,label:(0,s.jsxs)("div",{className:"flex justify-between items-center py-2",children:[(0,s.jsxs)(e3.x,{className:"font-medium text-base",children:["Route ",l+1,": ",e.model||"Unnamed"]}),(0,s.jsx)(_.ZP,{type:"text",danger:!0,icon:(0,s.jsx)(e6.Z,{}),onClick:l=>{l.stopPropagation(),m(e.id)},className:"mr-2"})]}),children:(0,s.jsxs)("div",{className:"px-6 pb-6 w-full",children:[(0,s.jsxs)("div",{className:"mb-4 w-full",children:[(0,s.jsx)(e3.x,{className:"text-sm font-medium mb-2 block",children:"Model"}),(0,s.jsx)(ev.default,{value:e.model,onChange:l=>u(e.id,"model",l),placeholder:"Select model",showSearch:!0,style:{width:"100%"},options:x})]}),(0,s.jsxs)("div",{className:"mb-4 w-full",children:[(0,s.jsx)(e3.x,{className:"text-sm font-medium mb-2 block",children:"Description"}),(0,s.jsx)(e8,{value:e.description,onChange:l=>u(e.id,"description",l.target.value),placeholder:"Describe when this route should be used...",rows:2,style:{width:"100%"}})]}),(0,s.jsxs)("div",{className:"mb-4 w-full",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2 mb-2",children:[(0,s.jsx)(e3.x,{className:"text-sm font-medium",children:"Score Threshold"}),(0,s.jsx)(M.Z,{title:"Minimum similarity score to route to this model (0-1)",children:(0,s.jsx)(R.Z,{className:"text-gray-400"})})]}),(0,s.jsx)(Y.Z,{value:e.score_threshold,onChange:l=>u(e.id,"score_threshold",l||0),min:0,max:1,step:.1,style:{width:"100%"},placeholder:"0.5"})]}),(0,s.jsxs)("div",{className:"w-full",children:[(0,s.jsxs)("div",{className:"flex items-center gap-2 mb-2",children:[(0,s.jsx)(e3.x,{className:"text-sm font-medium",children:"Example Utterances"}),(0,s.jsx)(M.Z,{title:"Training examples for this route. Type an utterance and press Enter to add it.",children:(0,s.jsx)(R.Z,{className:"text-gray-400"})})]}),(0,s.jsx)(e3.x,{className:"text-xs text-gray-500 mb-2",children:"Type an utterance and press Enter to add it. You can also paste multiple lines."}),(0,s.jsx)(ev.default,{mode:"tags",value:e.utterances,onChange:l=>u(e.id,"utterances",l),placeholder:"Type an utterance and press Enter...",style:{width:"100%"},tokenSeparators:["\n"],maxTagCount:"responsive",allowClear:!0})]})]})}]})},e.id))}),(0,s.jsxs)("div",{className:"border-t pt-6 w-full",children:[(0,s.jsxs)("div",{className:"flex justify-between items-center mb-4 w-full",children:[(0,s.jsx)(e3.x,{className:"text-lg font-semibold",children:"JSON Preview"}),(0,s.jsx)(_.ZP,{type:"link",onClick:()=>o(!n),className:"text-blue-600 p-0",children:n?"Hide":"Show"})]}),n&&(0,s.jsx)(et.Z,{className:"bg-gray-50 w-full",children:(0,s.jsx)("pre",{className:"text-sm overflow-auto max-h-64 w-full",children:JSON.stringify({routes:r.map(e=>({name:e.model,utterances:e.utterances,description:e.description,score_threshold:e.score_threshold}))},null,2)})})]})]})};let{Title:le,Link:ll}=Q.default;var lt=e=>{let{form:l,handleOk:t,accessToken:r,userRole:i}=e,[n,o]=(0,f.useState)(!1),[d,c]=(0,f.useState)(!1),[m,u]=(0,f.useState)(""),[h,x]=(0,f.useState)([]),[p,g]=(0,f.useState)([]),[j,v]=(0,f.useState)(!1),[b,y]=(0,f.useState)(!1),[N,w]=(0,f.useState)(null);(0,f.useEffect)(()=>{(async()=>{x((await (0,a.modelAvailableCall)(r,"","",!1,null,!0,!0)).data.map(e=>e.id))})()},[r]),(0,f.useEffect)(()=>{(async()=>{try{let e=await (0,e1.p)(r);console.log("Fetched models for auto router:",e),g(e)}catch(e){console.error("Error fetching model info for auto router:",e)}})()},[r]);let Z=eR.ZL.includes(i),C=async()=>{c(!0),u("test-".concat(Date.now())),o(!0)},k=()=>{console.log("Auto router submit triggered!"),console.log("Router config:",N);let e=l.getFieldsValue();if(console.log("Form values:",e),!e.auto_router_name){eo.Z.fromBackend("Please enter an Auto Router Name");return}if(!e.auto_router_default_model){eo.Z.fromBackend("Please select a Default Model");return}if(l.setFieldsValue({custom_llm_provider:"auto_router",model:e.auto_router_name,api_key:"not_required_for_auto_router"}),!N||!N.routes||0===N.routes.length){eo.Z.fromBackend("Please configure at least one route for the auto router");return}if(N.routes.filter(e=>!e.name||!e.description||0===e.utterances.length).length>0){eo.Z.fromBackend("Please ensure all routes have a target model, description, and at least one utterance");return}l.validateFields().then(e=>{console.log("Form validation passed, submitting with values:",e);let s={...e,auto_router_config:N};console.log("Final submit values:",s),e0(s,r,l,t)}).catch(e=>{console.error("Validation failed:",e);let l=e.errorFields||[];if(l.length>0){let e=l.map(e=>{let l=e.name[0];return({auto_router_name:"Auto Router Name",auto_router_default_model:"Default Model",auto_router_embedding_model:"Embedding Model"})[l]||l});eo.Z.fromBackend("Please fill in the following required fields: ".concat(e.join(", ")))}else eo.Z.fromBackend("Please fill in all required fields")})};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(le,{level:2,children:"Add Auto Router"}),(0,s.jsx)(eN.x,{className:"text-gray-600 mb-6",children:"Create an auto router with intelligent routing logic that automatically selects the best model based on user input patterns and semantic matching."}),(0,s.jsx)(et.Z,{children:(0,s.jsxs)(eg.Z,{form:l,onFinish:k,labelCol:{span:10},wrapperCol:{span:16},labelAlign:"left",children:[(0,s.jsx)(eg.Z.Item,{rules:[{required:!0,message:"Auto router name is required"}],label:"Auto Router Name",name:"auto_router_name",tooltip:"Unique name for this auto router configuration",labelCol:{span:10},labelAlign:"left",children:(0,s.jsx)(eN.o,{placeholder:"e.g., auto_router_1, smart_routing"})}),(0,s.jsx)("div",{className:"w-full mb-4",children:(0,s.jsx)(e9,{modelInfo:p,value:N,onChange:e=>{w(e),l.setFieldValue("auto_router_config",e)}})}),(0,s.jsx)(eg.Z.Item,{rules:[{required:!0,message:"Default model is required"}],label:"Default Model",name:"auto_router_default_model",tooltip:"Fallback model to use when auto routing logic cannot determine the best model",labelCol:{span:10},labelAlign:"left",children:(0,s.jsx)(ev.default,{placeholder:"Select a default model",onChange:e=>{v("custom"===e)},options:[...Array.from(new Set(p.map(e=>e.model_group))).map(e=>({value:e,label:e})),{value:"custom",label:"Enter custom model name"}],style:{width:"100%"},showSearch:!0})}),(0,s.jsx)(eg.Z.Item,{label:"Embedding Model",name:"auto_router_embedding_model",tooltip:"Optional: Embedding model to use for semantic routing decisions",labelCol:{span:10},labelAlign:"left",children:(0,s.jsx)(ev.default,{value:l.getFieldValue("auto_router_embedding_model"),placeholder:"Select an embedding model (optional)",onChange:e=>{y("custom"===e),l.setFieldValue("auto_router_embedding_model",e)},options:[...Array.from(new Set(p.map(e=>e.model_group))).map(e=>({value:e,label:e})),{value:"custom",label:"Enter custom model name"}],style:{width:"100%"},showSearch:!0,allowClear:!0})}),(0,s.jsxs)("div",{className:"flex items-center my-4",children:[(0,s.jsx)("div",{className:"flex-grow border-t border-gray-200"}),(0,s.jsx)("span",{className:"px-4 text-gray-500 text-sm",children:"Additional Settings"}),(0,s.jsx)("div",{className:"flex-grow border-t border-gray-200"})]}),Z&&(0,s.jsx)(eg.Z.Item,{label:"Model Access Group",name:"model_access_group",className:"mb-4",tooltip:"Use model access groups to control who can access this auto router",children:(0,s.jsx)(ev.default,{mode:"tags",showSearch:!0,placeholder:"Select existing groups or type to create new ones",optionFilterProp:"children",tokenSeparators:[","],options:h.map(e=>({value:e,label:e})),maxTagCount:"responsive",allowClear:!0})}),(0,s.jsxs)("div",{className:"flex justify-between items-center mb-4",children:[(0,s.jsx)(M.Z,{title:"Get help on our github",children:(0,s.jsx)(Q.default.Link,{href:"https://github.com/BerriAI/litellm/issues",children:"Need Help?"})}),(0,s.jsxs)("div",{className:"space-x-2",children:[(0,s.jsx)(_.ZP,{onClick:C,loading:d,children:"Test Connect"}),(0,s.jsx)(_.ZP,{onClick:()=>{console.log("Add Auto Router button clicked!"),console.log("Current router config:",N),console.log("Current form values:",l.getFieldsValue()),k()},children:"Add Auto Router"})]})]})]})}),(0,s.jsx)(ea.Z,{title:"Connection Test Results",open:n,onCancel:()=>{o(!1),c(!1)},footer:[(0,s.jsx)(_.ZP,{onClick:()=>{o(!1),c(!1)},children:"Close"},"close")],width:700,children:n&&(0,s.jsx)(eQ,{formValues:l.getFieldsValue(),accessToken:r,testMode:"chat",modelName:l.getFieldValue("auto_router_name"),onClose:()=>{o(!1),c(!1)},onTestComplete:()=>c(!1)},m)})]})};let ls=(0,i.n)("guardrails"),la=()=>{let{accessToken:e,userId:l,userRole:t}=(0,n.Z)();return(0,r.a)({queryKey:ls.list({}),queryFn:async()=>(await (0,a.getGuardrailsList)(e)).guardrails.map(e=>e.guardrail_name),enabled:!!(e&&l&&t)})},lr=(0,i.n)("tags"),li=()=>{let{accessToken:e,userId:l,userRole:t}=(0,n.Z)();return(0,r.a)({queryKey:lr.list({}),queryFn:async()=>await (0,a.tagListCall)(e),enabled:!!(e&&l&&t)})};var ln=t(59341),lo=t(51653),ld=t(84376),lc=t(63709),lm=t(26210),lu=t(34766),lh=t(45246),lx=t(24199);let{Text:lp}=Q.default;var lg=e=>{let{form:l,showCacheControl:t,onCacheControlChange:a}=e,r=e=>{let t=l.getFieldValue("litellm_extra_params");try{let s=t?JSON.parse(t):{};e.length>0?s.cache_control_injection_points=e:delete s.cache_control_injection_points,Object.keys(s).length>0?l.setFieldValue("litellm_extra_params",JSON.stringify(s,null,2)):l.setFieldValue("litellm_extra_params","")}catch(e){console.error("Error updating cache control points:",e)}};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(eg.Z.Item,{label:"Cache Control Injection Points",name:"cache_control",valuePropName:"checked",className:"mb-4",tooltip:"Tell litellm where to inject cache control checkpoints. You can specify either by role (to apply to all messages of that role) or by specific message index.",children:(0,s.jsx)(lc.Z,{onChange:a,className:"bg-gray-600"})}),t&&(0,s.jsxs)("div",{className:"ml-6 pl-4 border-l-2 border-gray-200",children:[(0,s.jsx)(lp,{className:"text-sm text-gray-500 block mb-4",children:"Providers like Anthropic, Bedrock API require users to specify where to inject cache control checkpoints, litellm can automatically add them for you as a cost saving feature."}),(0,s.jsx)(eg.Z.List,{name:"cache_control_injection_points",initialValue:[{location:"message"}],children:(e,t)=>{let{add:a,remove:i}=t;return(0,s.jsxs)(s.Fragment,{children:[e.map((t,a)=>(0,s.jsxs)("div",{className:"flex items-center mb-4 gap-4",children:[(0,s.jsx)(eg.Z.Item,{...t,label:"Type",name:[t.name,"location"],initialValue:"message",className:"mb-0",style:{width:"180px"},children:(0,s.jsx)(ev.default,{disabled:!0,options:[{value:"message",label:"Message"}]})}),(0,s.jsx)(eg.Z.Item,{...t,label:"Role",name:[t.name,"role"],className:"mb-0",style:{width:"180px"},tooltip:"LiteLLM will mark all messages of this role as cacheable",children:(0,s.jsx)(ev.default,{placeholder:"Select a role",allowClear:!0,options:[{value:"user",label:"User"},{value:"system",label:"System"},{value:"assistant",label:"Assistant"}],onChange:()=>{r(l.getFieldValue("cache_control_points"))}})}),(0,s.jsx)(eg.Z.Item,{...t,label:"Index",name:[t.name,"index"],className:"mb-0",style:{width:"180px"},tooltip:"(Optional) If set litellm will mark the message at this index as cacheable",children:(0,s.jsx)(lx.Z,{type:"number",placeholder:"Optional",step:1,onChange:()=>{r(l.getFieldValue("cache_control_points"))}})}),e.length>1&&(0,s.jsx)(lh.Z,{className:"text-red-500 cursor-pointer text-lg ml-12",onClick:()=>{i(t.name),setTimeout(()=>{r(l.getFieldValue("cache_control_points"))},0)}})]},t.key)),(0,s.jsx)(eg.Z.Item,{children:(0,s.jsxs)("button",{type:"button",className:"flex items-center justify-center w-full border border-dashed border-gray-300 py-2 px-4 text-gray-600 hover:text-blue-600 hover:border-blue-300 transition-all rounded",onClick:()=>a(),children:[(0,s.jsx)(e4.Z,{className:"mr-2"}),"Add Injection Point"]})})]})}})]})]})},lf=t(9309);let{Link:lj}=Q.default;var lv=e=>{let{showAdvancedSettings:l,setShowAdvancedSettings:t,teams:a,guardrailsList:r,tagsList:i}=e,[n]=eg.Z.useForm(),[o,d]=f.useState(!1),[c,m]=f.useState("per_token"),[u,h]=f.useState(!1),x=(e,l)=>l&&(isNaN(Number(l))||0>Number(l))?Promise.reject("Please enter a valid positive number"):Promise.resolve();return(0,s.jsx)(s.Fragment,{children:(0,s.jsxs)(lm.UQ,{className:"mt-2 mb-4",children:[(0,s.jsx)(lm._m,{children:(0,s.jsx)("b",{children:"Advanced Settings"})}),(0,s.jsx)(lm.X1,{children:(0,s.jsxs)("div",{className:"bg-white rounded-lg",children:[(0,s.jsx)(eg.Z.Item,{label:"Custom Pricing",name:"custom_pricing",valuePropName:"checked",className:"mb-4",children:(0,s.jsx)(lc.Z,{onChange:e=>{d(e),e||n.setFieldsValue({input_cost_per_token:void 0,output_cost_per_token:void 0,input_cost_per_second:void 0})},className:"bg-gray-600"})}),(0,s.jsx)(eg.Z.Item,{label:(0,s.jsxs)("span",{children:["Guardrails"," ",(0,s.jsx)(M.Z,{title:"Apply safety guardrails to this key to filter content or enforce policies",children:(0,s.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/guardrails/quick_start",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,s.jsx)(R.Z,{style:{marginLeft:"4px"}})})})]}),name:"guardrails",className:"mt-4",help:"Select existing guardrails. Go to 'Guardrails' tab to create new guardrails.",children:(0,s.jsx)(ev.default,{mode:"tags",style:{width:"100%"},placeholder:"Select or enter guardrails",options:r.map(e=>({value:e,label:e}))})}),(0,s.jsx)(eg.Z.Item,{label:"Tags",name:"tags",className:"mb-4",children:(0,s.jsx)(ev.default,{mode:"tags",style:{width:"100%"},placeholder:"Select or enter tags",options:Object.values(i).map(e=>({value:e.name,label:e.name,title:e.description||e.name}))})}),o&&(0,s.jsxs)("div",{className:"ml-6 pl-4 border-l-2 border-gray-200",children:[(0,s.jsx)(eg.Z.Item,{label:"Pricing Model",name:"pricing_model",className:"mb-4",children:(0,s.jsx)(ev.default,{defaultValue:"per_token",onChange:e=>m(e),options:[{value:"per_token",label:"Per Million Tokens"},{value:"per_second",label:"Per Second"}]})}),"per_token"===c?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(eg.Z.Item,{label:"Input Cost (per 1M tokens)",name:"input_cost_per_token",rules:[{validator:x}],className:"mb-4",children:(0,s.jsx)(lm.oi,{})}),(0,s.jsx)(eg.Z.Item,{label:"Output Cost (per 1M tokens)",name:"output_cost_per_token",rules:[{validator:x}],className:"mb-4",children:(0,s.jsx)(lm.oi,{})})]}):(0,s.jsx)(eg.Z.Item,{label:"Cost Per Second",name:"input_cost_per_second",rules:[{validator:x}],className:"mb-4",children:(0,s.jsx)(lm.oi,{})})]}),(0,s.jsx)(eg.Z.Item,{label:"Use in pass through routes",name:"use_in_pass_through",valuePropName:"checked",className:"mb-4 mt-4",tooltip:(0,s.jsxs)("span",{children:["Allow using these credentials in pass through routes."," ",(0,s.jsx)(lj,{href:"https://docs.litellm.ai/docs/pass_through/vertex_ai",target:"_blank",children:"Learn more"})]}),children:(0,s.jsx)(lc.Z,{onChange:e=>{let l=n.getFieldValue("litellm_extra_params");try{let t=l?JSON.parse(l):{};e?t.use_in_pass_through=!0:delete t.use_in_pass_through,Object.keys(t).length>0?n.setFieldValue("litellm_extra_params",JSON.stringify(t,null,2)):n.setFieldValue("litellm_extra_params","")}catch(l){e?n.setFieldValue("litellm_extra_params",JSON.stringify({use_in_pass_through:!0},null,2)):n.setFieldValue("litellm_extra_params","")}},className:"bg-gray-600"})}),(0,s.jsx)(lg,{form:n,showCacheControl:u,onCacheControlChange:e=>{if(h(e),!e){let e=n.getFieldValue("litellm_extra_params");try{let l=e?JSON.parse(e):{};delete l.cache_control_injection_points,Object.keys(l).length>0?n.setFieldValue("litellm_extra_params",JSON.stringify(l,null,2)):n.setFieldValue("litellm_extra_params","")}catch(e){n.setFieldValue("litellm_extra_params","")}}}}),(0,s.jsx)(eg.Z.Item,{label:"LiteLLM Params",name:"litellm_extra_params",tooltip:"Optional litellm params used for making a litellm.completion() call.",className:"mb-4 mt-4",rules:[{validator:lf.Ac}],children:(0,s.jsx)(lu.Z,{rows:4,placeholder:'{ "rpm": 100, "timeout": 0, "stream_timeout": 0 }'})}),(0,s.jsxs)(ew.Z,{className:"mb-4",children:[(0,s.jsx)(eZ.Z,{span:10}),(0,s.jsx)(eZ.Z,{span:10,children:(0,s.jsxs)(lm.xv,{className:"text-gray-600 text-sm",children:["Pass JSON of litellm supported params"," ",(0,s.jsx)(lj,{href:"https://docs.litellm.ai/docs/completion/input",target:"_blank",children:"litellm.completion() call"})]})})]}),(0,s.jsx)(eg.Z.Item,{label:"Model Info",name:"model_info_params",tooltip:"Optional model info params. Returned when calling `/model/info` endpoint.",className:"mb-0",rules:[{validator:lf.Ac}],children:(0,s.jsx)(lu.Z,{rows:4,placeholder:'{ "mode": "chat" }'})})]})})]})})},l_=t(56609),lb=t(67187);let ly=e=>{let{content:l,children:t,width:a="auto",className:r=""}=e,[i,n]=(0,f.useState)(!1),[o,d]=(0,f.useState)("top"),c=(0,f.useRef)(null),m=()=>{if(c.current){let e=c.current.getBoundingClientRect(),l=e.top,t=window.innerHeight-e.bottom;l<300&&t>300?d("bottom"):d("top")}};return(0,s.jsxs)("div",{className:"relative inline-block",ref:c,children:[t||(0,s.jsx)(lb.Z,{className:"ml-1 text-gray-500 cursor-help",onMouseEnter:()=>{m(),n(!0)},onMouseLeave:()=>n(!1)}),i&&(0,s.jsxs)("div",{className:"absolute left-1/2 -translate-x-1/2 z-50 bg-black/90 text-white p-2 rounded-md text-sm font-normal shadow-lg ".concat(r),style:{["top"===o?"bottom":"top"]:"100%",width:a,marginBottom:"top"===o?"8px":"0",marginTop:"bottom"===o?"8px":"0"},children:[l,(0,s.jsx)("div",{className:"absolute left-1/2 -translate-x-1/2 w-0 h-0",style:{top:"top"===o?"100%":"auto",bottom:"bottom"===o?"100%":"auto",borderTop:"top"===o?"6px solid rgba(0, 0, 0, 0.9)":"6px solid transparent",borderBottom:"bottom"===o?"6px solid rgba(0, 0, 0, 0.9)":"6px solid transparent",borderLeft:"6px solid transparent",borderRight:"6px solid transparent"}})]})]})};var lN=()=>{let e=eg.Z.useFormInstance(),[l,t]=(0,f.useState)(0),a=eg.Z.useWatch("model",e)||[],r=Array.isArray(a)?a:[a],i=eg.Z.useWatch("custom_model_name",e),n=!r.includes("all-wildcard"),o=eg.Z.useWatch("custom_llm_provider",e);if((0,f.useEffect)(()=>{if(i&&r.includes("custom")){let l=(e.getFieldValue("model_mappings")||[]).map(e=>"custom"===e.public_name||"custom"===e.litellm_model?o===L.Cl.Azure?{public_name:i,litellm_model:"azure/".concat(i)}:{public_name:i,litellm_model:i}:e);e.setFieldValue("model_mappings",l),t(e=>e+1)}},[i,r,o,e]),(0,f.useEffect)(()=>{if(r.length>0&&!r.includes("all-wildcard")){let l=e.getFieldValue("model_mappings")||[];if(l.length!==r.length||!r.every(e=>l.some(l=>"custom"===e?"custom"===l.litellm_model||l.litellm_model===i:o===L.Cl.Azure?l.litellm_model==="azure/".concat(e):l.litellm_model===e))){let l=r.map(e=>"custom"===e&&i?o===L.Cl.Azure?{public_name:i,litellm_model:"azure/".concat(i)}:{public_name:i,litellm_model:i}:o===L.Cl.Azure?{public_name:e,litellm_model:"azure/".concat(e)}:{public_name:e,litellm_model:e});e.setFieldValue("model_mappings",l),t(e=>e+1)}}},[r,i,o,e]),!n)return null;let d=(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)("div",{className:"mb-2 font-normal",children:"The name you specify in your API calls to LiteLLM Proxy"}),(0,s.jsxs)("div",{className:"mb-2 font-normal",children:[(0,s.jsx)("strong",{children:"Example:"})," If you name your public model"," ",(0,s.jsx)("code",{className:"bg-gray-700 px-1 py-0.5 rounded text-xs",children:"example-name"}),", and choose"," ",(0,s.jsx)("code",{className:"bg-gray-700 px-1 py-0.5 rounded text-xs",children:"openai/qwen-plus-latest"})," as the LiteLLM model"]}),(0,s.jsxs)("div",{className:"mb-2 font-normal",children:[(0,s.jsx)("strong",{children:"Usage:"})," You make an API call to the LiteLLM proxy with"," ",(0,s.jsx)("code",{className:"bg-gray-700 px-1 py-0.5 rounded text-xs",children:'model = "example-name"'})]}),(0,s.jsxs)("div",{className:"font-normal",children:[(0,s.jsx)("strong",{children:"Result:"})," LiteLLM sends"," ",(0,s.jsx)("code",{className:"bg-gray-700 px-1 py-0.5 rounded text-xs",children:"qwen-plus-latest"})," to the provider"]})]}),c=(0,s.jsx)("div",{children:"The model name LiteLLM will send to the LLM API"}),m=[{title:(0,s.jsxs)("span",{className:"flex items-center",children:["Public Model Name",(0,s.jsx)(ly,{content:d,width:"500px"})]}),dataIndex:"public_name",key:"public_name",render:(l,t,a)=>(0,s.jsx)(ej.o,{value:l,onChange:l=>{let t=l.target.value,s=[...e.getFieldValue("model_mappings")],r=o===L.Cl.Anthropic,i=t.endsWith("-1m"),n=e.getFieldValue("litellm_extra_params"),d=!n||""===n.trim(),c=t;if(r&&i&&d){let l=JSON.stringify({extra_headers:{"anthropic-beta":"context-1m-2025-08-07"}},null,2);e.setFieldValue("litellm_extra_params",l),c=t.slice(0,-3)}s[a].public_name=c,e.setFieldValue("model_mappings",s)}})},{title:(0,s.jsxs)("span",{className:"flex items-center",children:["LiteLLM Model Name",(0,s.jsx)(ly,{content:c,width:"360px"})]}),dataIndex:"litellm_model",key:"litellm_model"}];return(0,s.jsx)(s.Fragment,{children:(0,s.jsx)(eg.Z.Item,{label:"Model Mappings",name:"model_mappings",tooltip:"Map public model names to LiteLLM model names for load balancing",labelCol:{span:10},wrapperCol:{span:16},labelAlign:"left",rules:[{required:!0,validator:async(e,l)=>{if(!l||0===l.length)throw Error("At least one model mapping is required");if(l.filter(e=>!e.public_name||""===e.public_name.trim()).length>0)throw Error("All model mappings must have valid public names")}}],children:(0,s.jsx)(l_.Z,{dataSource:e.getFieldValue("model_mappings"),columns:m,pagination:!1,size:"small"},l)})})},lw=e=>{let{selectedProvider:l,providerModels:t,getPlaceholder:a}=e,r=eg.Z.useFormInstance(),i=e=>{let t=e.target.value,s=(r.getFieldValue("model_mappings")||[]).map(e=>"custom"===e.public_name||"custom"===e.litellm_model?l===L.Cl.Azure?{public_name:t,litellm_model:"azure/".concat(t)}:{public_name:t,litellm_model:t}:e);r.setFieldsValue({model_mappings:s})};return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)(eg.Z.Item,{label:"LiteLLM Model Name(s)",tooltip:"The model name LiteLLM will send to the LLM API",className:"mb-0",children:[(0,s.jsx)(eg.Z.Item,{name:"model",rules:[{required:!0,message:"Please enter ".concat(l===L.Cl.Azure?"a deployment name":"at least one model",".")}],noStyle:!0,children:l===L.Cl.Azure||l===L.Cl.OpenAI_Compatible||l===L.Cl.Ollama?(0,s.jsx)(s.Fragment,{children:(0,s.jsx)(eN.o,{placeholder:a(l),onChange:l===L.Cl.Azure?e=>{let l=e.target.value,t=l?[{public_name:l,litellm_model:"azure/".concat(l)}]:[];r.setFieldsValue({model:l,model_mappings:t})}:void 0})}):t.length>0?(0,s.jsx)(ev.default,{mode:"multiple",allowClear:!0,showSearch:!0,placeholder:"Select models",onChange:e=>{let t=Array.isArray(e)?e:[e];if(t.includes("all-wildcard"))r.setFieldsValue({model_name:void 0,model_mappings:[]});else if(JSON.stringify(r.getFieldValue("model"))!==JSON.stringify(t)){let e=t.map(e=>l===L.Cl.Azure?{public_name:e,litellm_model:"azure/".concat(e)}:{public_name:e,litellm_model:e});r.setFieldsValue({model:t,model_mappings:e})}},optionFilterProp:"children",filterOption:(e,l)=>{var t;return(null!==(t=null==l?void 0:l.label)&&void 0!==t?t:"").toLowerCase().includes(e.toLowerCase())},options:[{label:"Custom Model Name (Enter below)",value:"custom"},{label:"All ".concat(l," Models (Wildcard)"),value:"all-wildcard"},...t.map(e=>({label:e,value:e}))],style:{width:"100%"}}):(0,s.jsx)(eN.o,{placeholder:a(l)})}),(0,s.jsx)(eg.Z.Item,{noStyle:!0,shouldUpdate:(e,l)=>e.model!==l.model,children:e=>{let{getFieldValue:t}=e,a=t("model")||[];return(Array.isArray(a)?a:[a]).includes("custom")&&(0,s.jsx)(eg.Z.Item,{name:"custom_model_name",rules:[{required:!0,message:"Please enter a custom model name."}],className:"mt-2",children:(0,s.jsx)(eN.o,{placeholder:l===L.Cl.Azure?"Enter Azure deployment name":"Enter custom model name",onChange:i})})}})]}),(0,s.jsxs)(ew.Z,{children:[(0,s.jsx)(eZ.Z,{span:10}),(0,s.jsx)(eZ.Z,{span:14,children:(0,s.jsx)(eN.x,{className:"mb-3 mt-1",children:l===L.Cl.Azure?"Your deployment name will be saved as the public model name, and LiteLLM will use 'azure/deployment-name' internally":"The model name LiteLLM will send to the LLM API"})})]})]})};let lZ=[{value:"chat",label:"Chat - /chat/completions"},{value:"completion",label:"Completion - /completions"},{value:"embedding",label:"Embedding - /embeddings"},{value:"audio_speech",label:"Audio Speech - /audio/speech"},{value:"audio_transcription",label:"Audio Transcription - /audio/transcriptions"},{value:"image_generation",label:"Image Generation - /images/generations"},{value:"video_generation",label:"Video Generation - /videos"},{value:"rerank",label:"Rerank - /rerank"},{value:"realtime",label:"Realtime - /realtime"},{value:"batch",label:"Batch - /batch"},{value:"ocr",label:"OCR - /ocr"}],{Title:lC,Link:lk}=Q.default;var lS=e=>{let{form:l,handleOk:t,selectedProvider:r,setSelectedProvider:i,providerModels:o,setProviderModelsFn:d,getPlaceholder:c,uploadProps:m,showAdvancedSettings:u,setShowAdvancedSettings:h,teams:x,credentials:p}=e,[g,j]=(0,f.useState)("chat"),[v,b]=(0,f.useState)(!1),[y,N]=(0,f.useState)(!1),[w,Z]=(0,f.useState)(""),{accessToken:C,userRole:k,premiumUser:S,userId:A}=(0,n.Z)(),{data:E,isLoading:P,error:I}=eb(),{data:T,isLoading:R,error:O}=la(),{data:V,isLoading:q,error:z}=li(),B=async()=>{N(!0),Z("test-".concat(Date.now())),b(!0)},[G,U]=(0,f.useState)(!1),[H,K]=(0,f.useState)([]),[J,W]=(0,f.useState)(null);(0,f.useEffect)(()=>{(async()=>{K((await (0,a.modelAvailableCall)(C,"","",!1,null,!0,!0)).data.map(e=>e.id))})()},[C]);let Y=(0,f.useMemo)(()=>E?[...E].sort((e,l)=>e.provider_display_name.localeCompare(l.provider_display_name)):[],[E]),$=I?I instanceof Error?I.message:"Failed to load providers":null,X=eR.ZL.includes(k),ee=(0,eR.yV)(x,A);return(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(lC,{level:2,children:"Add Model"}),(0,s.jsx)(et.Z,{children:(0,s.jsx)(eg.Z,{form:l,onFinish:async e=>{console.log("\uD83D\uDD25 Form onFinish triggered with values:",e),await t().then(()=>{W(null)})},onFinishFailed:e=>{console.log("\uD83D\uDCA5 Form onFinishFailed triggered:",e)},labelCol:{span:10},wrapperCol:{span:16},labelAlign:"left",children:(0,s.jsxs)(s.Fragment,{children:[ee&&!X&&(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(eg.Z.Item,{label:"Select Team",name:"team_id",rules:[{required:!0,message:"Please select a team to continue"}],tooltip:"Select the team for which you want to add this model",children:(0,s.jsx)(ld.Z,{teams:x,onChange:e=>{W(e)}})}),!J&&(0,s.jsx)(lo.Z,{message:"Team Selection Required",description:"As a team admin, you need to select your team first before adding models.",type:"info",showIcon:!0,className:"mb-4"})]}),(X||ee&&J)&&(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(eg.Z.Item,{rules:[{required:!0,message:"Required"}],label:"Provider:",name:"custom_llm_provider",tooltip:"E.g. OpenAI, Azure OpenAI, Anthropic, Bedrock, etc.",labelCol:{span:10},labelAlign:"left",children:(0,s.jsxs)(ev.default,{virtual:!1,showSearch:!0,loading:P,placeholder:P?"Loading providers...":"Select a provider",optionFilterProp:"data-label",onChange:e=>{i(e),d(e),l.setFieldsValue({custom_llm_provider:e}),l.setFieldsValue({model:[],model_name:void 0})},children:[$&&0===Y.length&&(0,s.jsx)(ev.default.Option,{value:"",children:$},"__error"),Y.map(e=>{let l=e.provider_display_name,t=e.provider;return L.cd[l],(0,s.jsx)(ev.default.Option,{value:t,"data-label":l,children:(0,s.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,s.jsx)(F,{provider:t,className:"w-5 h-5"}),(0,s.jsx)("span",{children:l})]})},t)})]})}),(0,s.jsx)(lw,{selectedProvider:r,providerModels:o,getPlaceholder:c}),(0,s.jsx)(lN,{}),(0,s.jsx)(eg.Z.Item,{label:"Mode",name:"mode",className:"mb-1",children:(0,s.jsx)(ev.default,{style:{width:"100%"},value:g,onChange:e=>j(e),options:lZ})}),(0,s.jsxs)(ew.Z,{children:[(0,s.jsx)(eZ.Z,{span:10}),(0,s.jsx)(eZ.Z,{span:10,children:(0,s.jsxs)(D.Z,{className:"mb-5 mt-1",children:[(0,s.jsx)("strong",{children:"Optional"})," - LiteLLM endpoint to use when health checking this model"," ",(0,s.jsx)(lk,{href:"https://docs.litellm.ai/docs/proxy/health#health",target:"_blank",children:"Learn more"})]})})]}),(0,s.jsx)("div",{className:"mb-4",children:(0,s.jsx)(Q.default.Text,{className:"text-sm text-gray-500 mb-2",children:"Either select existing credentials OR enter new provider credentials below"})}),(0,s.jsx)(eg.Z.Item,{label:"Existing Credentials",name:"litellm_credential_name",initialValue:null,children:(0,s.jsx)(ev.default,{showSearch:!0,placeholder:"Select or search for existing credentials",optionFilterProp:"children",filterOption:(e,l)=>{var t;return(null!==(t=null==l?void 0:l.label)&&void 0!==t?t:"").toLowerCase().includes(e.toLowerCase())},options:[{value:null,label:"None"},...p.map(e=>({value:e.credential_name,label:e.credential_name}))],allowClear:!0})}),(0,s.jsx)(eg.Z.Item,{noStyle:!0,shouldUpdate:(e,l)=>e.litellm_credential_name!==l.litellm_credential_name||e.provider!==l.provider,children:e=>{let{getFieldValue:l}=e,t=l("litellm_credential_name");return(console.log("\uD83D\uDD11 Credential Name Changed:",t),t)?null:(0,s.jsxs)(s.Fragment,{children:[(0,s.jsxs)("div",{className:"flex items-center my-4",children:[(0,s.jsx)("div",{className:"flex-grow border-t border-gray-200"}),(0,s.jsx)("span",{className:"px-4 text-gray-500 text-sm",children:"OR"}),(0,s.jsx)("div",{className:"flex-grow border-t border-gray-200"})]}),(0,s.jsx)(eP,{selectedProvider:r,uploadProps:m})]})}}),(0,s.jsxs)("div",{className:"flex items-center my-4",children:[(0,s.jsx)("div",{className:"flex-grow border-t border-gray-200"}),(0,s.jsx)("span",{className:"px-4 text-gray-500 text-sm",children:"Additional Model Info Settings"}),(0,s.jsx)("div",{className:"flex-grow border-t border-gray-200"})]}),(X||!ee)&&(0,s.jsx)(eg.Z.Item,{label:"Team-BYOK Model",tooltip:"Only use this model + credential combination for this team. Useful when teams want to onboard their own OpenAI keys.",className:"mb-4",children:(0,s.jsx)(M.Z,{title:S?"":"This is an enterprise-only feature. Upgrade to premium to restrict model+credential combinations to a specific team.",placement:"top",children:(0,s.jsx)(ln.Z,{checked:G,onChange:e=>{U(e),e||l.setFieldValue("team_id",void 0)},disabled:!S})})}),G&&(X||!ee)&&(0,s.jsx)(eg.Z.Item,{label:"Select Team",name:"team_id",className:"mb-4",tooltip:"Only keys for this team will be able to call this model.",rules:[{required:G&&!X,message:"Please select a team."}],children:(0,s.jsx)(ld.Z,{teams:x,disabled:!S})}),X&&(0,s.jsx)(s.Fragment,{children:(0,s.jsx)(eg.Z.Item,{label:"Model Access Group",name:"model_access_group",className:"mb-4",tooltip:"Use model access groups to give users access to select models, and add new ones to the group over time.",children:(0,s.jsx)(ev.default,{mode:"tags",showSearch:!0,placeholder:"Select existing groups or type to create new ones",optionFilterProp:"children",tokenSeparators:[","],options:H.map(e=>({value:e,label:e})),maxTagCount:"responsive",allowClear:!0})})}),(0,s.jsx)(lv,{showAdvancedSettings:u,setShowAdvancedSettings:h,teams:x,guardrailsList:T||[],tagsList:V||{}})]}),(0,s.jsxs)("div",{className:"flex justify-between items-center mb-4",children:[(0,s.jsx)(M.Z,{title:"Get help on our github",children:(0,s.jsx)(Q.default.Link,{href:"https://github.com/BerriAI/litellm/issues",children:"Need Help?"})}),(0,s.jsxs)("div",{className:"space-x-2",children:[(0,s.jsx)(_.ZP,{onClick:B,loading:y,children:"Test Connect"}),(0,s.jsx)(_.ZP,{htmlType:"submit",children:"Add Model"})]})]})]})})}),(0,s.jsx)(ea.Z,{title:"Connection Test Results",open:v,onCancel:()=>{b(!1),N(!1)},footer:[(0,s.jsx)(_.ZP,{onClick:()=>{b(!1),N(!1)},children:"Close"},"close")],width:700,children:v&&(0,s.jsx)(eQ,{formValues:l.getFieldsValue(),accessToken:C,testMode:g,modelName:l.getFieldValue("model_name")||l.getFieldValue("model"),onClose:()=>{b(!1),N(!1)},onTestComplete:()=>N(!1)},w)})]})},lA=e=>{let{form:l,handleOk:t,selectedProvider:a,setSelectedProvider:r,providerModels:i,setProviderModelsFn:n,getPlaceholder:o,uploadProps:d,showAdvancedSettings:c,setShowAdvancedSettings:m,teams:u,credentials:h,accessToken:x,userRole:p}=e,[g]=eg.Z.useForm();return(0,s.jsx)(s.Fragment,{children:(0,s.jsxs)(eH.v0,{className:"w-full",children:[(0,s.jsxs)(eH.td,{className:"mb-4",children:[(0,s.jsx)(eH.OK,{children:"Add Model"}),(0,s.jsx)(eH.OK,{children:"Add Auto Router"})]}),(0,s.jsxs)(eH.nP,{children:[(0,s.jsx)(eH.x4,{children:(0,s.jsx)(lS,{form:l,handleOk:t,selectedProvider:a,setSelectedProvider:r,providerModels:i,setProviderModelsFn:n,getPlaceholder:o,uploadProps:d,showAdvancedSettings:c,setShowAdvancedSettings:m,teams:u,credentials:h})}),(0,s.jsx)(eH.x4,{children:(0,s.jsx)(lt,{form:g,handleOk:()=>{g.validateFields().then(e=>{e0(e,x,g,t)}).catch(e=>{console.error("Validation failed:",e)})},accessToken:x,userRole:p})})]})]})})},lE=t(8048),lP=t(4156),lM=t(15731),lL=t(91126);let lF=(e,l,t,a,r,i,n,o,d,c,m)=>[{header:()=>(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(lP.Z,{checked:t,indeterminate:l.length>0&&!t,onChange:e=>r(e.target.checked),onClick:e=>e.stopPropagation()}),(0,s.jsx)("span",{children:"Model ID"})]}),accessorKey:"model_info.id",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:t}=e,r=t.original,i=r.model_name,n=l.includes(i);return(0,s.jsxs)("div",{className:"flex items-center gap-2",children:[(0,s.jsx)(lP.Z,{checked:n,onChange:e=>a(i,e.target.checked),onClick:e=>e.stopPropagation()}),(0,s.jsx)(M.Z,{title:r.model_info.id,children:(0,s.jsx)("div",{className:"font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left w-full truncate whitespace-nowrap cursor-pointer max-w-[15ch]",onClick:()=>m&&m(r.model_info.id),children:r.model_info.id})})]})}},{header:"Model Name",accessorKey:"model_name",enableSorting:!0,sortingFn:"alphanumeric",cell:e=>{let{row:l}=e,t=l.original,a=o(t)||t.model_name;return(0,s.jsx)("div",{className:"font-medium text-sm",children:(0,s.jsx)(M.Z,{title:a,children:(0,s.jsx)("div",{className:"truncate max-w-[200px]",children:a})})})}},{header:"Health Status",accessorKey:"health_status",enableSorting:!0,sortingFn:(e,l,t)=>{var s,a;let r=e.getValue("health_status")||"unknown",i=l.getValue("health_status")||"unknown",n={healthy:0,checking:1,unknown:2,unhealthy:3};return(null!==(s=n[r])&&void 0!==s?s:4)-(null!==(a=n[i])&&void 0!==a?a:4)},cell:l=>{var t;let{row:a}=l,r=a.original,i={status:r.health_status,loading:r.health_loading,error:r.health_error};if(i.loading)return(0,s.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,s.jsxs)("div",{className:"flex space-x-1",children:[(0,s.jsx)("div",{className:"w-2 h-2 bg-indigo-500 rounded-full animate-pulse"}),(0,s.jsx)("div",{className:"w-2 h-2 bg-indigo-500 rounded-full animate-pulse",style:{animationDelay:"0.2s"}}),(0,s.jsx)("div",{className:"w-2 h-2 bg-indigo-500 rounded-full animate-pulse",style:{animationDelay:"0.4s"}})]}),(0,s.jsx)(e3.x,{className:"text-gray-600 text-sm",children:"Checking..."})]});let o=r.model_name,d="healthy"===i.status&&(null===(t=e[o])||void 0===t?void 0:t.successResponse);return(0,s.jsxs)("div",{className:"flex items-center space-x-2",children:[n(i.status),d&&c&&(0,s.jsx)(M.Z,{title:"View response details",placement:"top",children:(0,s.jsx)("button",{onClick:()=>{var l;return c(o,null===(l=e[o])||void 0===l?void 0:l.successResponse)},className:"p-1 text-green-600 hover:text-green-800 hover:bg-green-50 rounded cursor-pointer transition-colors",children:(0,s.jsx)(lM.Z,{className:"h-4 w-4"})})})]})}},{header:"Error Details",accessorKey:"health_error",enableSorting:!1,cell:l=>{let{row:t}=l,a=t.original.model_name,r=e[a];if(!(null==r?void 0:r.error))return(0,s.jsx)(e3.x,{className:"text-gray-400 text-sm",children:"No errors"});let i=r.error,n=r.fullError||r.error;return(0,s.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,s.jsx)("div",{className:"max-w-[200px]",children:(0,s.jsx)(M.Z,{title:i,placement:"top",children:(0,s.jsx)(e3.x,{className:"text-red-600 text-sm truncate",children:i})})}),d&&n!==i&&(0,s.jsx)(M.Z,{title:"View full error details",placement:"top",children:(0,s.jsx)("button",{onClick:()=>d(a,i,n),className:"p-1 text-red-600 hover:text-red-800 hover:bg-red-50 rounded cursor-pointer transition-colors",children:(0,s.jsx)(lM.Z,{className:"h-4 w-4"})})})]})}},{header:"Last Check",accessorKey:"last_check",enableSorting:!0,sortingFn:(e,l,t)=>{let s=e.getValue("last_check")||"Never checked",a=l.getValue("last_check")||"Never checked";if("Never checked"===s&&"Never checked"===a)return 0;if("Never checked"===s)return 1;if("Never checked"===a)return -1;if("Check in progress..."===s&&"Check in progress..."===a)return 0;if("Check in progress..."===s)return -1;if("Check in progress..."===a)return 1;let r=new Date(s),i=new Date(a);return isNaN(r.getTime())&&isNaN(i.getTime())?0:isNaN(r.getTime())?1:isNaN(i.getTime())?-1:i.getTime()-r.getTime()},cell:e=>{let{row:l}=e,t=l.original;return(0,s.jsx)(e3.x,{className:"text-gray-600 text-sm",children:t.health_loading?"Check in progress...":t.last_check})}},{header:"Last Success",accessorKey:"last_success",enableSorting:!0,sortingFn:(e,l,t)=>{let s=e.getValue("last_success")||"Never succeeded",a=l.getValue("last_success")||"Never succeeded";if("Never succeeded"===s&&"Never succeeded"===a)return 0;if("Never succeeded"===s)return 1;if("Never succeeded"===a)return -1;if("None"===s&&"None"===a)return 0;if("None"===s)return 1;if("None"===a)return -1;let r=new Date(s),i=new Date(a);return isNaN(r.getTime())&&isNaN(i.getTime())?0:isNaN(r.getTime())?1:isNaN(i.getTime())?-1:i.getTime()-r.getTime()},cell:l=>{let{row:t}=l,a=e[t.original.model_name],r=(null==a?void 0:a.lastSuccess)||"None";return(0,s.jsx)(e3.x,{className:"text-gray-600 text-sm",children:r})}},{header:"Actions",id:"actions",cell:e=>{let{row:l}=e,t=l.original,a=t.model_name,r=t.health_status&&"none"!==t.health_status,n=t.health_loading?"Checking...":r?"Re-run Health Check":"Run Health Check";return(0,s.jsx)(M.Z,{title:n,placement:"top",children:(0,s.jsx)("button",{className:"p-2 rounded-md transition-colors ".concat(t.health_loading?"text-gray-400 cursor-not-allowed bg-gray-100":"text-indigo-600 hover:text-indigo-700 hover:bg-indigo-50"),onClick:()=>{t.health_loading||i(a)},disabled:t.health_loading,children:t.health_loading?(0,s.jsxs)("div",{className:"flex space-x-1",children:[(0,s.jsx)("div",{className:"w-1 h-1 bg-gray-400 rounded-full animate-pulse"}),(0,s.jsx)("div",{className:"w-1 h-1 bg-gray-400 rounded-full animate-pulse",style:{animationDelay:"0.2s"}}),(0,s.jsx)("div",{className:"w-1 h-1 bg-gray-400 rounded-full animate-pulse",style:{animationDelay:"0.4s"}})]}):r?(0,s.jsx)(eO.Z,{className:"h-4 w-4"}):(0,s.jsx)(lL.Z,{className:"h-4 w-4"})})})},enableSorting:!1}],lI=[{pattern:/Missing .* API Key/i,replacement:"Missing API Key"},{pattern:/Connection timeout/i,replacement:"Connection timeout"},{pattern:/Network.*not.*ok/i,replacement:"Network connection failed"},{pattern:/403.*Forbidden/i,replacement:"Access forbidden - check API key permissions"},{pattern:/401.*Unauthorized/i,replacement:"Unauthorized - invalid API key"},{pattern:/429.*rate limit/i,replacement:"Rate limit exceeded"},{pattern:/500.*Internal Server Error/i,replacement:"Provider internal server error"},{pattern:/litellm\.AuthenticationError/i,replacement:"Authentication failed"},{pattern:/litellm\.RateLimitError/i,replacement:"Rate limit exceeded"},{pattern:/litellm\.APIError/i,replacement:"API error"}];var lT=e=>{let{accessToken:l,modelData:t,all_models_on_proxy:r,getDisplayModelName:i,setSelectedModelId:n}=e,[o,d]=(0,f.useState)({}),[c,m]=(0,f.useState)([]),[u,h]=(0,f.useState)(!1),[x,p]=(0,f.useState)(!1),[g,j]=(0,f.useState)(null),[v,b]=(0,f.useState)(!1),[y,N]=(0,f.useState)(null);(0,f.useRef)(null),(0,f.useEffect)(()=>{l&&(null==t?void 0:t.data)&&(async()=>{let e={};t.data.forEach(l=>{e[l.model_name]={status:"none",lastCheck:"None",lastSuccess:"None",loading:!1,error:void 0,fullError:void 0,successResponse:void 0}});try{let s=await (0,a.latestHealthChecksCall)(l);s&&s.latest_health_checks&&"object"==typeof s.latest_health_checks&&Object.entries(s.latest_health_checks).forEach(l=>{let[s,a]=l;if(!a)return;let r=null,i=t.data.find(e=>e.model_name===s);if(i)r=i.model_name;else{let e=t.data.find(e=>e.model_info&&e.model_info.id===s);if(e)r=e.model_name;else if(a.model_name){let e=t.data.find(e=>e.model_name===a.model_name);e&&(r=e.model_name)}}if(r){let l=a.error_message||void 0;e[r]={status:a.status||"unknown",lastCheck:a.checked_at?new Date(a.checked_at).toLocaleString():"None",lastSuccess:"healthy"===a.status&&a.checked_at?new Date(a.checked_at).toLocaleString():"None",loading:!1,error:l?w(l):void 0,fullError:l,successResponse:"healthy"===a.status?a:void 0}}})}catch(e){console.warn("Failed to load health check history (using default states):",e)}d(e)})()},[l,t]);let w=e=>{var l;if(!e)return"Health check failed";let t="string"==typeof e?e:JSON.stringify(e),s=t.match(/(\w+Error):\s*(\d{3})/i);if(s)return"".concat(s[1],": ").concat(s[2]);let a=t.match(/(AuthenticationError|RateLimitError|BadRequestError|InternalServerError|TimeoutError|NotFoundError|ForbiddenError|ServiceUnavailableError|BadGatewayError|ContentPolicyViolationError|\w+Error)/i),r=t.match(/\b(400|401|403|404|408|429|500|502|503|504)\b/);if(a&&r)return"".concat(a[1],": ").concat(r[1]);if(r){let e=r[1];return"".concat({400:"BadRequestError",401:"AuthenticationError",403:"ForbiddenError",404:"NotFoundError",408:"TimeoutError",429:"RateLimitError",500:"InternalServerError",502:"BadGatewayError",503:"ServiceUnavailableError",504:"GatewayTimeoutError"}[e],": ").concat(e)}if(a){let e=a[1],l={AuthenticationError:"401",RateLimitError:"429",BadRequestError:"400",InternalServerError:"500",TimeoutError:"408",NotFoundError:"404",ForbiddenError:"403",ServiceUnavailableError:"503",BadGatewayError:"502",GatewayTimeoutError:"504",ContentPolicyViolationError:"400"}[e];return l?"".concat(e,": ").concat(l):e}for(let{pattern:e,replacement:l}of lI)if(e.test(t))return l;if(/missing.*api.*key|invalid.*key|unauthorized/i.test(t))return"AuthenticationError: 401";if(/rate.*limit|too.*many.*requests/i.test(t))return"RateLimitError: 429";if(/timeout|timed.*out/i.test(t))return"TimeoutError: 408";if(/not.*found/i.test(t))return"NotFoundError: 404";if(/forbidden|access.*denied/i.test(t))return"ForbiddenError: 403";if(/internal.*server.*error/i.test(t))return"InternalServerError: 500";let i=t.replace(/[\n\r]+/g," ").replace(/\s+/g," ").trim(),n=null===(l=i.split(/[.!?]/)[0])||void 0===l?void 0:l.trim();return n&&n.length>0?n.length>100?n.substring(0,97)+"...":n:i.length>100?i.substring(0,97)+"...":i},Z=async e=>{if(l){d(l=>({...l,[e]:{...l[e],loading:!0,status:"checking"}}));try{var s,r;let i=await (0,a.individualModelHealthCheckCall)(l,e),n=new Date().toLocaleString();if(i.unhealthy_count>0&&i.unhealthy_endpoints&&i.unhealthy_endpoints.length>0){let l=(null===(s=i.unhealthy_endpoints[0])||void 0===s?void 0:s.error)||"Health check failed",t=w(l);d(s=>{var a;return{...s,[e]:{status:"unhealthy",lastCheck:n,lastSuccess:(null===(a=s[e])||void 0===a?void 0:a.lastSuccess)||"None",loading:!1,error:t,fullError:l}}})}else d(l=>({...l,[e]:{status:"healthy",lastCheck:n,lastSuccess:n,loading:!1,successResponse:i}}));try{let s=await (0,a.latestHealthChecksCall)(l),i=t.data.find(l=>l.model_name===e);if(i){let l=i.model_info.id,t=null===(r=s.latest_health_checks)||void 0===r?void 0:r[l];if(t){let l=t.error_message||void 0;d(s=>{var a,r,i,n,o,d,c;return{...s,[e]:{status:t.status||(null===(a=s[e])||void 0===a?void 0:a.status)||"unknown",lastCheck:t.checked_at?new Date(t.checked_at).toLocaleString():(null===(r=s[e])||void 0===r?void 0:r.lastCheck)||"None",lastSuccess:"healthy"===t.status?t.checked_at?new Date(t.checked_at).toLocaleString():(null===(i=s[e])||void 0===i?void 0:i.lastSuccess)||"None":(null===(n=s[e])||void 0===n?void 0:n.lastSuccess)||"None",loading:!1,error:l?w(l):null===(o=s[e])||void 0===o?void 0:o.error,fullError:l||(null===(d=s[e])||void 0===d?void 0:d.fullError),successResponse:"healthy"===t.status?t:null===(c=s[e])||void 0===c?void 0:c.successResponse}}})}}}catch(e){console.debug("Could not fetch updated status from database (non-critical):",e)}}catch(a){let l=new Date().toLocaleString(),t=a instanceof Error?a.message:String(a),s=w(t);d(a=>{var r;return{...a,[e]:{status:"unhealthy",lastCheck:l,lastSuccess:(null===(r=a[e])||void 0===r?void 0:r.lastSuccess)||"None",loading:!1,error:s,fullError:t}}})}}},C=async()=>{let e=c.length>0?c:r,s=e.reduce((e,l)=>(e[l]={...o[l],loading:!0,status:"checking"},e),{});d(e=>({...e,...s}));let i={},n=e.map(async e=>{if(l)try{let s=await (0,a.individualModelHealthCheckCall)(l,e);i[e]=s;let r=new Date().toLocaleString();if(s.unhealthy_count>0&&s.unhealthy_endpoints&&s.unhealthy_endpoints.length>0){var t;let l=(null===(t=s.unhealthy_endpoints[0])||void 0===t?void 0:t.error)||"Health check failed",a=w(l);d(t=>{var s;return{...t,[e]:{status:"unhealthy",lastCheck:r,lastSuccess:(null===(s=t[e])||void 0===s?void 0:s.lastSuccess)||"None",loading:!1,error:a,fullError:l}}})}else d(l=>({...l,[e]:{status:"healthy",lastCheck:r,lastSuccess:r,loading:!1,successResponse:s}}))}catch(a){console.error("Health check failed for ".concat(e,":"),a);let l=new Date().toLocaleString(),t=a instanceof Error?a.message:String(a),s=w(t);d(a=>{var r;return{...a,[e]:{status:"unhealthy",lastCheck:l,lastSuccess:(null===(r=a[e])||void 0===r?void 0:r.lastSuccess)||"None",loading:!1,error:s,fullError:t}}})}});await Promise.allSettled(n);try{if(!l)return;let s=await (0,a.latestHealthChecksCall)(l);s.latest_health_checks&&Object.entries(s.latest_health_checks).forEach(l=>{let[s,a]=l,r=t.data.find(e=>e.model_info.id===s);if(r&&e.includes(r.model_name)&&a){let e=r.model_name,l=a.error_message||void 0;d(t=>{let s=t[e];return{...t,[e]:{status:a.status||(null==s?void 0:s.status)||"unknown",lastCheck:a.checked_at?new Date(a.checked_at).toLocaleString():(null==s?void 0:s.lastCheck)||"None",lastSuccess:"healthy"===a.status&&a.checked_at?new Date(a.checked_at).toLocaleString():(null==s?void 0:s.lastSuccess)||"None",loading:!1,error:l?w(l):null==s?void 0:s.error,fullError:l||(null==s?void 0:s.fullError),successResponse:"healthy"===a.status?a:null==s?void 0:s.successResponse}}})}})}catch(e){console.warn("Failed to fetch updated health statuses from database (non-critical):",e)}},k=e=>{h(e),e?m(r):m([])},S=()=>{p(!1),j(null)},P=()=>{b(!1),N(null)};return(0,s.jsxs)("div",{children:[(0,s.jsx)("div",{className:"mb-6",children:(0,s.jsxs)("div",{className:"flex justify-between items-center",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)(W.Z,{children:"Model Health Status"}),(0,s.jsx)(D.Z,{className:"text-gray-600 mt-1",children:"Run health checks on individual models to verify they are working correctly"})]}),(0,s.jsxs)("div",{className:"flex items-center gap-3",children:[c.length>0&&(0,s.jsx)(E.Z,{size:"sm",variant:"light",onClick:()=>k(!1),className:"px-3 py-1 text-sm",children:"Clear Selection"}),(0,s.jsx)(E.Z,{size:"sm",variant:"secondary",onClick:C,disabled:Object.values(o).some(e=>e.loading),className:"px-3 py-1 text-sm",children:c.length>0&&c.length{l?m(l=>[...l,e]):(m(l=>l.filter(l=>l!==e)),h(!1))},k,Z,e=>{switch(e){case"healthy":return(0,s.jsx)(A.Z,{color:"emerald",children:"healthy"});case"unhealthy":return(0,s.jsx)(A.Z,{color:"red",children:"unhealthy"});case"checking":return(0,s.jsx)(A.Z,{color:"blue",children:"checking"});case"none":return(0,s.jsx)(A.Z,{color:"gray",children:"none"});default:return(0,s.jsx)(A.Z,{color:"gray",children:"unknown"})}},i,(e,l,t)=>{j({modelName:e,cleanedError:l,fullError:t}),p(!0)},(e,l)=>{N({modelName:e,response:l}),b(!0)},n),data:t.data.map(e=>{let l=o[e.model_name]||{status:"none",lastCheck:"None",loading:!1};return{model_name:e.model_name,model_info:e.model_info,provider:e.provider,litellm_model_name:e.litellm_model_name,health_status:l.status,last_check:l.lastCheck,last_success:l.lastSuccess||"None",health_loading:l.loading,health_error:l.error,health_full_error:l.fullError}}),isLoading:!1})}),(0,s.jsx)(ea.Z,{title:g?"Health Check Error - ".concat(g.modelName):"Error Details",open:x,onCancel:S,footer:[(0,s.jsx)(_.ZP,{onClick:S,children:"Close"},"close")],width:800,children:g&&(0,s.jsxs)("div",{className:"space-y-4",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"Error:"}),(0,s.jsx)("div",{className:"mt-2 p-3 bg-red-50 border border-red-200 rounded-md",children:(0,s.jsx)(D.Z,{className:"text-red-800",children:g.cleanedError})})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"Full Error Details:"}),(0,s.jsx)("div",{className:"mt-2 p-3 bg-gray-50 border border-gray-200 rounded-md max-h-96 overflow-y-auto",children:(0,s.jsx)("pre",{className:"text-sm text-gray-800 whitespace-pre-wrap",children:g.fullError})})]})]})}),(0,s.jsx)(ea.Z,{title:y?"Health Check Response - ".concat(y.modelName):"Response Details",open:v,onCancel:P,footer:[(0,s.jsx)(_.ZP,{onClick:P,children:"Close"},"close")],width:800,children:y&&(0,s.jsxs)("div",{className:"space-y-4",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"Status:"}),(0,s.jsx)("div",{className:"mt-2 p-3 bg-green-50 border border-green-200 rounded-md",children:(0,s.jsx)(D.Z,{className:"text-green-800",children:"Health check passed successfully"})})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"Response Details:"}),(0,s.jsx)("div",{className:"mt-2 p-3 bg-gray-50 border border-gray-200 rounded-md max-h-96 overflow-y-auto",children:(0,s.jsx)("pre",{className:"text-sm text-gray-800 whitespace-pre-wrap",children:JSON.stringify(y.response,null,2)})})]})]})})]})},lR=t(47686),lO=t(77355),lV=t(93416),lq=t(95704),lz=e=>{let{accessToken:l,initialModelGroupAlias:t={},onAliasUpdate:r}=e,[i,n]=(0,f.useState)([]),[o,d]=(0,f.useState)({aliasName:"",targetModelGroup:""}),[c,m]=(0,f.useState)(null),[u,h]=(0,f.useState)(!0);(0,f.useEffect)(()=>{n(Object.entries(t).map((e,l)=>{var t;let[s,a]=e;return{id:"".concat(l,"-").concat(s),aliasName:s,targetModelGroup:"string"==typeof a?a:null!==(t=null==a?void 0:a.model)&&void 0!==t?t:""}}))},[t]);let x=async e=>{if(!l)return console.error("Access token is missing"),!1;try{let t={};return e.forEach(e=>{t[e.aliasName]=e.targetModelGroup}),console.log("Saving model group alias:",t),await (0,a.setCallbacksCall)(l,{router_settings:{model_group_alias:t}}),r&&r(t),!0}catch(e){return console.error("Failed to save model group alias settings:",e),eo.Z.fromBackend("Failed to save model group alias settings"),!1}},p=async()=>{if(!o.aliasName||!o.targetModelGroup){eo.Z.fromBackend("Please provide both alias name and target model group");return}if(i.some(e=>e.aliasName===o.aliasName)){eo.Z.fromBackend("An alias with this name already exists");return}let e=[...i,{id:"".concat(Date.now(),"-").concat(o.aliasName),aliasName:o.aliasName,targetModelGroup:o.targetModelGroup}];await x(e)&&(n(e),d({aliasName:"",targetModelGroup:""}),eo.Z.success("Alias added successfully"))},g=e=>{m({...e})},j=async()=>{if(!c)return;if(!c.aliasName||!c.targetModelGroup){eo.Z.fromBackend("Please provide both alias name and target model group");return}if(i.some(e=>e.id!==c.id&&e.aliasName===c.aliasName)){eo.Z.fromBackend("An alias with this name already exists");return}let e=i.map(e=>e.id===c.id?c:e);await x(e)&&(n(e),m(null),eo.Z.success("Alias updated successfully"))},v=()=>{m(null)},_=async e=>{let l=i.filter(l=>l.id!==e);await x(l)&&(n(l),eo.Z.success("Alias deleted successfully"))},b=i.reduce((e,l)=>(e[l.aliasName]=l.targetModelGroup,e),{});return(0,s.jsxs)(lq.Zb,{className:"mb-6",children:[(0,s.jsxs)("div",{className:"flex items-center justify-between cursor-pointer",onClick:()=>h(!u),children:[(0,s.jsxs)("div",{className:"flex flex-col",children:[(0,s.jsx)(lq.Dx,{className:"mb-0",children:"Model Group Alias Settings"}),(0,s.jsx)("p",{className:"text-sm text-gray-500",children:"Create aliases for your model groups to simplify API calls. For example, you can create an alias 'gpt-4o' that points to 'gpt-4o-mini-openai' model group."})]}),(0,s.jsx)("div",{className:"flex items-center",children:u?(0,s.jsx)(y.Z,{className:"w-5 h-5 text-gray-500"}):(0,s.jsx)(lR.Z,{className:"w-5 h-5 text-gray-500"})})]}),u&&(0,s.jsxs)("div",{className:"mt-4",children:[(0,s.jsxs)("div",{className:"mb-6",children:[(0,s.jsx)(lq.xv,{className:"text-sm font-medium text-gray-700 mb-2",children:"Add New Alias"}),(0,s.jsxs)("div",{className:"grid grid-cols-3 gap-4",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)("label",{className:"block text-xs text-gray-500 mb-1",children:"Alias Name"}),(0,s.jsx)("input",{type:"text",value:o.aliasName,onChange:e=>d({...o,aliasName:e.target.value}),placeholder:"e.g., gpt-4o",className:"w-full px-3 py-2 border border-gray-300 rounded-md text-sm"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)("label",{className:"block text-xs text-gray-500 mb-1",children:"Target Model Group"}),(0,s.jsx)("input",{type:"text",value:o.targetModelGroup,onChange:e=>d({...o,targetModelGroup:e.target.value}),placeholder:"e.g., gpt-4o-mini-openai",className:"w-full px-3 py-2 border border-gray-300 rounded-md text-sm"})]}),(0,s.jsx)("div",{className:"flex items-end",children:(0,s.jsxs)("button",{onClick:p,disabled:!o.aliasName||!o.targetModelGroup,className:"flex items-center px-4 py-2 rounded-md text-sm ".concat(o.aliasName&&o.targetModelGroup?"bg-green-600 text-white hover:bg-green-700":"bg-gray-300 text-gray-500 cursor-not-allowed"),children:[(0,s.jsx)(lO.Z,{className:"w-4 h-4 mr-1"}),"Add Alias"]})})]})]}),(0,s.jsx)(lq.xv,{className:"text-sm font-medium text-gray-700 mb-2",children:"Manage Existing Aliases"}),(0,s.jsx)("div",{className:"rounded-lg custom-border relative mb-6",children:(0,s.jsx)("div",{className:"overflow-x-auto",children:(0,s.jsxs)(lq.iA,{className:"[&_td]:py-0.5 [&_th]:py-1",children:[(0,s.jsx)(lq.ss,{children:(0,s.jsxs)(lq.SC,{children:[(0,s.jsx)(lq.xs,{className:"py-1 h-8",children:"Alias Name"}),(0,s.jsx)(lq.xs,{className:"py-1 h-8",children:"Target Model Group"}),(0,s.jsx)(lq.xs,{className:"py-1 h-8",children:"Actions"})]})}),(0,s.jsxs)(lq.RM,{children:[i.map(e=>(0,s.jsx)(lq.SC,{className:"h-8",children:c&&c.id===e.id?(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(lq.pj,{className:"py-0.5",children:(0,s.jsx)("input",{type:"text",value:c.aliasName,onChange:e=>m({...c,aliasName:e.target.value}),className:"w-full px-2 py-1 border border-gray-300 rounded-md text-sm"})}),(0,s.jsx)(lq.pj,{className:"py-0.5",children:(0,s.jsx)("input",{type:"text",value:c.targetModelGroup,onChange:e=>m({...c,targetModelGroup:e.target.value}),className:"w-full px-2 py-1 border border-gray-300 rounded-md text-sm"})}),(0,s.jsx)(lq.pj,{className:"py-0.5 whitespace-nowrap",children:(0,s.jsxs)("div",{className:"flex space-x-2",children:[(0,s.jsx)("button",{onClick:j,className:"text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded hover:bg-blue-100",children:"Save"}),(0,s.jsx)("button",{onClick:v,className:"text-xs bg-gray-50 text-gray-600 px-2 py-1 rounded hover:bg-gray-100",children:"Cancel"})]})})]}):(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(lq.pj,{className:"py-0.5 text-sm text-gray-900",children:e.aliasName}),(0,s.jsx)(lq.pj,{className:"py-0.5 text-sm text-gray-500",children:e.targetModelGroup}),(0,s.jsx)(lq.pj,{className:"py-0.5 whitespace-nowrap",children:(0,s.jsxs)("div",{className:"flex space-x-2",children:[(0,s.jsx)("button",{onClick:()=>g(e),className:"text-xs bg-blue-50 text-blue-600 px-2 py-1 rounded hover:bg-blue-100",children:(0,s.jsx)(lV.Z,{className:"w-3 h-3"})}),(0,s.jsx)("button",{onClick:()=>_(e.id),className:"text-xs bg-red-50 text-red-600 px-2 py-1 rounded hover:bg-red-100",children:(0,s.jsx)(S.Z,{className:"w-3 h-3"})})]})})]})},e.id)),0===i.length&&(0,s.jsx)(lq.SC,{children:(0,s.jsx)(lq.pj,{colSpan:3,className:"py-0.5 text-sm text-gray-500 text-center",children:"No aliases added yet. Add a new alias above."})})]})]})})}),(0,s.jsxs)(lq.Zb,{children:[(0,s.jsx)(lq.Dx,{className:"mb-4",children:"Configuration Example"}),(0,s.jsx)(lq.xv,{className:"text-gray-600 mb-4",children:"Here's how your current aliases would look in the config.yaml:"}),(0,s.jsx)("div",{className:"bg-gray-100 rounded-lg p-4 font-mono text-sm",children:(0,s.jsxs)("div",{className:"text-gray-700",children:["router_settings:",(0,s.jsx)("br",{}),"\xa0\xa0model_group_alias:",0===Object.keys(b).length?(0,s.jsxs)("span",{className:"text-gray-500",children:[(0,s.jsx)("br",{}),"\xa0\xa0\xa0\xa0# No aliases configured yet"]}):Object.entries(b).map(e=>{let[l,t]=e;return(0,s.jsxs)("span",{children:[(0,s.jsx)("br",{}),'\xa0\xa0\xa0\xa0"',l,'": "',t,'"']},l)})]})})]})]})]})},lD=t(10900),lB=t(12514),lG=t(49566),lU=t(30401),lH=t(78867),lK=t(59872),lJ=e=>{let{isVisible:l,onCancel:t,onSuccess:r,modelData:i,accessToken:n,userRole:o}=e,[d]=eg.Z.useForm(),[c,m]=(0,f.useState)(!1),[u,h]=(0,f.useState)([]),[x,p]=(0,f.useState)([]),[g,j]=(0,f.useState)(!1),[v,b]=(0,f.useState)(!1),[y,N]=(0,f.useState)(null);(0,f.useEffect)(()=>{l&&i&&w()},[l,i]),(0,f.useEffect)(()=>{let e=async()=>{if(n)try{let e=await (0,a.modelAvailableCall)(n,"","",!1,null,!0,!0);h(e.data.map(e=>e.id))}catch(e){console.error("Error fetching model access groups:",e)}},t=async()=>{if(n)try{let e=await (0,e1.p)(n);p(e)}catch(e){console.error("Error fetching model info:",e)}};l&&(e(),t())},[l,n]);let w=()=>{try{var e,l,t,s,a,r;let n=null;(null===(e=i.litellm_params)||void 0===e?void 0:e.auto_router_config)&&(n="string"==typeof i.litellm_params.auto_router_config?JSON.parse(i.litellm_params.auto_router_config):i.litellm_params.auto_router_config),N(n),d.setFieldsValue({auto_router_name:i.model_name,auto_router_default_model:(null===(l=i.litellm_params)||void 0===l?void 0:l.auto_router_default_model)||"",auto_router_embedding_model:(null===(t=i.litellm_params)||void 0===t?void 0:t.auto_router_embedding_model)||"",model_access_group:(null===(s=i.model_info)||void 0===s?void 0:s.access_groups)||[]});let o=new Set(x.map(e=>e.model_group));j(!o.has(null===(a=i.litellm_params)||void 0===a?void 0:a.auto_router_default_model)),b(!o.has(null===(r=i.litellm_params)||void 0===r?void 0:r.auto_router_embedding_model))}catch(e){console.error("Error parsing auto router config:",e),eo.Z.fromBackend("Error loading auto router configuration")}},Z=async()=>{try{m(!0);let e=await d.validateFields(),l={...i.litellm_params,auto_router_config:JSON.stringify(y),auto_router_default_model:e.auto_router_default_model,auto_router_embedding_model:e.auto_router_embedding_model||void 0},s={...i.model_info,access_groups:e.model_access_group||[]},o={model_name:e.auto_router_name,litellm_params:l,model_info:s};await (0,a.modelPatchUpdateCall)(n,o,i.model_info.id);let c={...i,model_name:e.auto_router_name,litellm_params:l,model_info:s};eo.Z.success("Auto router configuration updated successfully"),r(c),t()}catch(e){console.error("Error updating auto router:",e),eo.Z.fromBackend("Failed to update auto router configuration")}finally{m(!1)}},C=x.map(e=>({value:e.model_group,label:e.model_group}));return(0,s.jsx)(ea.Z,{title:"Edit Auto Router Configuration",open:l,onCancel:t,footer:[(0,s.jsx)(_.ZP,{onClick:t,children:"Cancel"},"cancel"),(0,s.jsx)(_.ZP,{loading:c,onClick:Z,children:"Save Changes"},"submit")],width:1e3,destroyOnClose:!0,children:(0,s.jsxs)("div",{className:"space-y-6",children:[(0,s.jsx)(eN.x,{className:"text-gray-600",children:"Edit the auto router configuration including routing logic, default models, and access settings."}),(0,s.jsxs)(eg.Z,{form:d,layout:"vertical",className:"space-y-4",children:[(0,s.jsx)(eg.Z.Item,{label:"Auto Router Name",name:"auto_router_name",rules:[{required:!0,message:"Auto router name is required"}],children:(0,s.jsx)(eN.o,{placeholder:"e.g., auto_router_1, smart_routing"})}),(0,s.jsx)("div",{className:"w-full",children:(0,s.jsx)(e9,{modelInfo:x,value:y,onChange:e=>{N(e)}})}),(0,s.jsx)(eg.Z.Item,{label:"Default Model",name:"auto_router_default_model",rules:[{required:!0,message:"Default model is required"}],children:(0,s.jsx)(ev.default,{placeholder:"Select a default model",onChange:e=>{j("custom"===e)},options:[...C,{value:"custom",label:"Enter custom model name"}],showSearch:!0})}),(0,s.jsx)(eg.Z.Item,{label:"Embedding Model",name:"auto_router_embedding_model",children:(0,s.jsx)(ev.default,{placeholder:"Select an embedding model (optional)",onChange:e=>{b("custom"===e)},options:[...C,{value:"custom",label:"Enter custom model name"}],showSearch:!0,allowClear:!0})}),"Admin"===o&&(0,s.jsx)(eg.Z.Item,{label:"Model Access Groups",name:"model_access_group",tooltip:"Control who can access this auto router",children:(0,s.jsx)(ev.default,{mode:"tags",showSearch:!0,placeholder:"Select existing groups or type to create new ones",optionFilterProp:"children",tokenSeparators:[","],options:u.map(e=>({value:e,label:e})),maxTagCount:"responsive",allowClear:!0})})]})]})})};let{Title:lW,Link:lY}=Q.default;var l$=e=>{let{isVisible:l,onCancel:t,onAddCredential:a,existingCredential:r,setIsCredentialModalOpen:i}=e,[n]=eg.Z.useForm();return console.log("existingCredential in add credentials tab: ".concat(JSON.stringify(r))),(0,s.jsx)(ea.Z,{title:"Reuse Credentials",visible:l,onCancel:()=>{t(),n.resetFields()},footer:null,width:600,children:(0,s.jsxs)(eg.Z,{form:n,onFinish:e=>{a(e),n.resetFields(),i(!1)},layout:"vertical",children:[(0,s.jsx)(eg.Z.Item,{label:"Credential Name:",name:"credential_name",rules:[{required:!0,message:"Credential name is required"}],initialValue:null==r?void 0:r.credential_name,children:(0,s.jsx)(ej.o,{placeholder:"Enter a friendly name for these credentials"})}),Object.entries((null==r?void 0:r.credential_values)||{}).map(e=>{let[l,t]=e;return(0,s.jsx)(eg.Z.Item,{label:l,name:l,initialValue:t,children:(0,s.jsx)(ej.o,{placeholder:"Enter ".concat(l),disabled:!0})},l)}),(0,s.jsxs)("div",{className:"flex justify-between items-center",children:[(0,s.jsx)(M.Z,{title:"Get help on our github",children:(0,s.jsx)(lY,{href:"https://github.com/BerriAI/litellm/issues",children:"Need Help?"})}),(0,s.jsxs)("div",{children:[(0,s.jsx)(_.ZP,{onClick:()=>{t(),n.resetFields()},style:{marginRight:10},children:"Cancel"}),(0,s.jsx)(_.ZP,{htmlType:"submit",children:"Reuse Credentials"})]})]})]})})};function lX(e){var l,t,r,i,n,o,d,c,h,x,p,g,j,v,b,y,N,w,Z,C,A,P,F,I,V,q,B,G,U,H,J,Y,$;let{modelId:X,onClose:Q,accessToken:ee,userID:el,userRole:et,onModelUpdate:es,modelAccessGroups:er}=e,[ei]=eg.Z.useForm(),[en,ed]=(0,f.useState)(null),[ec,em]=(0,f.useState)(!1),[eu,eh]=(0,f.useState)(!1),[ex,ep]=(0,f.useState)(!1),[ej,e_]=(0,f.useState)(!1),[eb,ey]=(0,f.useState)(!1),[eN,ew]=(0,f.useState)(!1),[eZ,eC]=(0,f.useState)(null),[eS,eA]=(0,f.useState)(!1),[eE,eP]=(0,f.useState)({}),[eM,eL]=(0,f.useState)(!1),[eF,eI]=(0,f.useState)([]),[eT,eR]=(0,f.useState)({}),{data:eV,isLoading:eq}=(0,u.XP)(1,50,void 0,X),{data:eU}=m(),{data:eH}=(0,u.VI)(),eK=e=>null!=eU&&"object"==typeof eU&&e in eU?eU[e].litellm_provider:"openai",eJ=(0,f.useMemo)(()=>(null==eV?void 0:eV.data)&&0!==eV.data.length&&K(eV,eK).data[0]||null,[eV,eU]),eW=("Admin"===et||(null==eJ?void 0:null===(l=eJ.model_info)||void 0===l?void 0:l.created_by)===el)&&(null==eJ?void 0:null===(t=eJ.model_info)||void 0===t?void 0:t.db_model),eY="Admin"===et,e$=(null==eJ?void 0:null===(r=eJ.litellm_params)||void 0===r?void 0:r.auto_router_config)!=null,eX=(null==eJ?void 0:null===(i=eJ.litellm_params)||void 0===i?void 0:i.litellm_credential_name)!=null&&(null==eJ?void 0:null===(n=eJ.litellm_params)||void 0===n?void 0:n.litellm_credential_name)!=void 0;(0,f.useEffect)(()=>{if(eJ&&!en){var e,l,t,s,a,r,i;let n=eJ;n.litellm_model_name||(n={...n,litellm_model_name:null!==(i=null!==(r=null!==(a=null==n?void 0:null===(l=n.litellm_params)||void 0===l?void 0:l.litellm_model_name)&&void 0!==a?a:null==n?void 0:null===(t=n.litellm_params)||void 0===t?void 0:t.model)&&void 0!==r?r:null==n?void 0:null===(s=n.model_info)||void 0===s?void 0:s.key)&&void 0!==i?i:null}),ed(n),(null==n?void 0:null===(e=n.litellm_params)||void 0===e?void 0:e.cache_control_injection_points)&&eA(!0)}},[eJ,en]),(0,f.useEffect)(()=>{let e=async()=>{var e,l,t,s,r,i,n;if(!ee||eJ)return;let o=await (0,a.modelInfoV1Call)(ee,X);console.log("modelInfoResponse, ",o);let d=o.data[0];d&&!d.litellm_model_name&&(d={...d,litellm_model_name:null!==(n=null!==(i=null!==(r=null==d?void 0:null===(l=d.litellm_params)||void 0===l?void 0:l.litellm_model_name)&&void 0!==r?r:null==d?void 0:null===(t=d.litellm_params)||void 0===t?void 0:t.model)&&void 0!==i?i:null==d?void 0:null===(s=d.model_info)||void 0===s?void 0:s.key)&&void 0!==n?n:null}),ed(d),(null==d?void 0:null===(e=d.litellm_params)||void 0===e?void 0:e.cache_control_injection_points)&&eA(!0)},l=async()=>{if(ee)try{let e=(await (0,a.getGuardrailsList)(ee)).guardrails.map(e=>e.guardrail_name);eI(e)}catch(e){console.error("Failed to fetch guardrails:",e)}},t=async()=>{if(ee)try{let e=await (0,a.tagListCall)(ee);eR(e)}catch(e){console.error("Failed to fetch tags:",e)}};(async()=>{if(console.log("accessToken, ",ee),!ee||eX)return;let e=await (0,a.credentialGetCall)(ee,null,X);console.log("existingCredentialResponse, ",e),eC({credential_name:e.credential_name,credential_values:e.credential_values,credential_info:e.credential_info})})(),e(),l(),t()},[ee,X]);let eQ=async e=>{var l;if(console.log("values, ",e),!ee)return;let t={credential_name:e.credential_name,model_id:X,credential_info:{custom_llm_provider:null===(l=en.litellm_params)||void 0===l?void 0:l.custom_llm_provider}};eo.Z.info("Storing credential.."),console.log("credentialResponse, ",await (0,a.credentialCreateCall)(ee,t)),eo.Z.success("Credential stored successfully")},e0=async e=>{try{var l;let t;if(!ee)return;ey(!0),console.log("values.model_name, ",e.model_name);let s={};try{s=e.litellm_extra_params?JSON.parse(e.litellm_extra_params):{}}catch(e){eo.Z.fromBackend("Invalid JSON in LiteLLM Params"),ey(!1);return}let r={...e.litellm_params,...s,model:e.litellm_model_name,api_base:e.api_base,custom_llm_provider:e.custom_llm_provider,organization:e.organization,tpm:e.tpm,rpm:e.rpm,max_retries:e.max_retries,timeout:e.timeout,stream_timeout:e.stream_timeout,input_cost_per_token:e.input_cost/1e6,output_cost_per_token:e.output_cost/1e6,tags:e.tags};e.guardrails&&(r.guardrails=e.guardrails),e.cache_control&&(null===(l=e.cache_control_injection_points)||void 0===l?void 0:l.length)>0?r.cache_control_injection_points=e.cache_control_injection_points:delete r.cache_control_injection_points;try{t=e.model_info?JSON.parse(e.model_info):eJ.model_info,e.model_access_group&&(t={...t,access_groups:e.model_access_group}),void 0!==e.health_check_model&&(t={...t,health_check_model:e.health_check_model})}catch(e){eo.Z.fromBackend("Invalid JSON in Model Info");return}let i={model_name:e.model_name,litellm_params:r,model_info:t};await (0,a.modelPatchUpdateCall)(ee,i,X);let n={...en,model_name:e.model_name,litellm_model_name:e.litellm_model_name,litellm_params:r,model_info:t};ed(n),es&&es(n),eo.Z.success("Model settings updated successfully"),e_(!1),ew(!1)}catch(e){console.error("Error updating model:",e),eo.Z.fromBackend("Failed to update model settings")}finally{ey(!1)}};if(eq)return(0,s.jsxs)("div",{className:"p-4",children:[(0,s.jsx)(E.Z,{icon:lD.Z,variant:"light",onClick:Q,className:"mb-4",children:"Back to Models"}),(0,s.jsx)(D.Z,{children:"Loading..."})]});if(!eJ)return(0,s.jsxs)("div",{className:"p-4",children:[(0,s.jsx)(E.Z,{icon:lD.Z,variant:"light",onClick:Q,className:"mb-4",children:"Back to Models"}),(0,s.jsx)(D.Z,{children:"Model not found"})]});let e1=async()=>{if(ee)try{var e,l,t;eo.Z.info("Testing connection...");let s=await (0,a.testConnectionRequest)(ee,{custom_llm_provider:en.litellm_params.custom_llm_provider,litellm_credential_name:en.litellm_params.litellm_credential_name,model:en.litellm_model_name},{mode:null===(e=en.model_info)||void 0===e?void 0:e.mode},null===(l=en.model_info)||void 0===l?void 0:l.mode);if("success"===s.status)eo.Z.success("Connection test successful!");else throw Error((null==s?void 0:null===(t=s.result)||void 0===t?void 0:t.error)||(null==s?void 0:s.message)||"Unknown error")}catch(e){e instanceof Error?eo.Z.error("Error testing connection: "+(0,lf.aS)(e.message,100)):eo.Z.error("Error testing connection: "+String(e))}},e2=async()=>{try{if(eh(!0),!ee)return;await (0,a.modelDeleteCall)(ee,X),eo.Z.success("Model deleted successfully"),es&&es({deleted:!0,model_info:{id:X}}),Q()}catch(e){console.error("Error deleting the model:",e),eo.Z.fromBackend("Failed to delete model")}finally{eh(!1),em(!1)}},e4=async(e,l)=>{await (0,lK.vQ)(e)&&(eP(e=>({...e,[l]:!0})),setTimeout(()=>{eP(e=>({...e,[l]:!1}))},2e3))},e5=eJ.litellm_model_name.includes("*");return console.log("isWildcardModel, ",e5),(0,s.jsxs)("div",{className:"p-4",children:[(0,s.jsxs)("div",{className:"flex justify-between items-center mb-6",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)(E.Z,{icon:lD.Z,variant:"light",onClick:Q,className:"mb-4",children:"Back to Models"}),(0,s.jsxs)(W.Z,{children:["Public Model Name: ",T(eJ)]}),(0,s.jsxs)("div",{className:"flex items-center cursor-pointer",children:[(0,s.jsx)(D.Z,{className:"text-gray-500 font-mono",children:eJ.model_info.id}),(0,s.jsx)(_.ZP,{type:"text",size:"small",icon:eE["model-id"]?(0,s.jsx)(lU.Z,{size:12}):(0,s.jsx)(lH.Z,{size:12}),onClick:()=>e4(eJ.model_info.id,"model-id"),className:"left-2 z-10 transition-all duration-200 ".concat(eE["model-id"]?"text-green-600 bg-green-50 border-green-200":"text-gray-500 hover:text-gray-700 hover:bg-gray-100")})]})]}),(0,s.jsxs)("div",{className:"flex gap-2",children:[(0,s.jsx)(E.Z,{variant:"secondary",icon:eO.Z,onClick:e1,className:"flex items-center gap-2","data-testid":"test-connection-button",children:"Test Connection"}),(0,s.jsx)(E.Z,{icon:k.Z,variant:"secondary",onClick:()=>ep(!0),className:"flex items-center",disabled:!eY,"data-testid":"reuse-credentials-button",children:"Re-use Credentials"}),(0,s.jsx)(E.Z,{icon:S.Z,variant:"secondary",onClick:()=>em(!0),className:"flex items-center text-red-500 border-red-500 hover:text-red-700",disabled:!eW,"data-testid":"delete-model-button",children:"Delete Model"})]})]}),(0,s.jsxs)(eD.Z,{children:[(0,s.jsxs)(eB.Z,{className:"mb-6",children:[(0,s.jsx)(ez.Z,{children:"Overview"}),(0,s.jsx)(ez.Z,{children:"Raw JSON"})]}),(0,s.jsxs)(eG.Z,{children:[(0,s.jsxs)(z.Z,{children:[(0,s.jsxs)(O.Z,{numItems:1,numItemsSm:2,numItemsLg:3,className:"gap-6 mb-6",children:[(0,s.jsxs)(lB.Z,{children:[(0,s.jsx)(D.Z,{children:"Provider"}),(0,s.jsxs)("div",{className:"mt-2 flex items-center space-x-2",children:[eJ.provider&&(0,s.jsx)("img",{src:(0,L.dr)(eJ.provider).logo,alt:"".concat(eJ.provider," logo"),className:"w-4 h-4",onError:e=>{let l=e.currentTarget,t=l.parentElement;if(t&&t.contains(l))try{var s;let e=document.createElement("div");e.className="w-4 h-4 rounded-full bg-gray-200 flex items-center justify-center text-xs",e.textContent=(null===(s=eJ.provider)||void 0===s?void 0:s.charAt(0))||"-",t.replaceChild(e,l)}catch(e){console.error("Failed to replace provider logo fallback:",e)}}}),(0,s.jsx)(W.Z,{children:eJ.provider||"Not Set"})]})]}),(0,s.jsxs)(lB.Z,{children:[(0,s.jsx)(D.Z,{children:"LiteLLM Model"}),(0,s.jsx)("div",{className:"mt-2 overflow-hidden",children:(0,s.jsx)(M.Z,{title:eJ.litellm_model_name||"Not Set",children:(0,s.jsx)("div",{className:"break-all text-sm font-medium leading-relaxed cursor-pointer",children:eJ.litellm_model_name||"Not Set"})})})]}),(0,s.jsxs)(lB.Z,{children:[(0,s.jsx)(D.Z,{children:"Pricing"}),(0,s.jsxs)("div",{className:"mt-2",children:[(0,s.jsxs)(D.Z,{children:["Input: $",eJ.input_cost,"/1M tokens"]}),(0,s.jsxs)(D.Z,{children:["Output: $",eJ.output_cost,"/1M tokens"]})]})]})]}),(0,s.jsxs)("div",{className:"mb-6 text-sm text-gray-500 flex items-center gap-x-6",children:[(0,s.jsxs)("div",{className:"flex items-center gap-x-2",children:[(0,s.jsx)("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,s.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:"2",d:"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"})}),"Created At"," ",eJ.model_info.created_at?new Date(eJ.model_info.created_at).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"}):"Not Set"]}),(0,s.jsxs)("div",{className:"flex items-center gap-x-2",children:[(0,s.jsx)("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,s.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:"2",d:"M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"})}),"Created By ",eJ.model_info.created_by||"Not Set"]})]}),(0,s.jsxs)(lB.Z,{children:[(0,s.jsxs)("div",{className:"flex justify-between items-center mb-4",children:[(0,s.jsx)(W.Z,{children:"Model Settings"}),(0,s.jsxs)("div",{className:"flex gap-2",children:[e$&&eW&&!eN&&(0,s.jsx)(E.Z,{onClick:()=>eL(!0),className:"flex items-center",children:"Edit Auto Router"}),eW?!eN&&(0,s.jsx)(E.Z,{onClick:()=>ew(!0),className:"flex items-center",children:"Edit Settings"}):(0,s.jsx)(M.Z,{title:"Only DB models can be edited. You must be an admin or the creator of the model to edit it.",children:(0,s.jsx)(R.Z,{})})]})]}),en?(0,s.jsx)(eg.Z,{form:ei,onFinish:e0,initialValues:{model_name:en.model_name,litellm_model_name:en.litellm_model_name,api_base:en.litellm_params.api_base,custom_llm_provider:en.litellm_params.custom_llm_provider,organization:en.litellm_params.organization,tpm:en.litellm_params.tpm,rpm:en.litellm_params.rpm,max_retries:en.litellm_params.max_retries,timeout:en.litellm_params.timeout,stream_timeout:en.litellm_params.stream_timeout,input_cost:en.litellm_params.input_cost_per_token?1e6*en.litellm_params.input_cost_per_token:(null===(o=en.model_info)||void 0===o?void 0:o.input_cost_per_token)*1e6||null,output_cost:(null===(d=en.litellm_params)||void 0===d?void 0:d.output_cost_per_token)?1e6*en.litellm_params.output_cost_per_token:(null===(c=en.model_info)||void 0===c?void 0:c.output_cost_per_token)*1e6||null,cache_control:null!==(h=en.litellm_params)&&void 0!==h&&!!h.cache_control_injection_points,cache_control_injection_points:(null===(x=en.litellm_params)||void 0===x?void 0:x.cache_control_injection_points)||[],model_access_group:Array.isArray(null===(p=en.model_info)||void 0===p?void 0:p.access_groups)?en.model_info.access_groups:[],guardrails:Array.isArray(null===(g=en.litellm_params)||void 0===g?void 0:g.guardrails)?en.litellm_params.guardrails:[],tags:Array.isArray(null===(j=en.litellm_params)||void 0===j?void 0:j.tags)?en.litellm_params.tags:[],health_check_model:e5?null===(v=en.model_info)||void 0===v?void 0:v.health_check_model:null,litellm_extra_params:JSON.stringify(en.litellm_params||{},null,2)},layout:"vertical",onValuesChange:()=>e_(!0),children:(0,s.jsxs)("div",{className:"space-y-4",children:[(0,s.jsxs)("div",{className:"space-y-4",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"Model Name"}),eN?(0,s.jsx)(eg.Z.Item,{name:"model_name",className:"mb-0",children:(0,s.jsx)(lG.Z,{placeholder:"Enter model name"})}):(0,s.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:en.model_name})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"LiteLLM Model Name"}),eN?(0,s.jsx)(eg.Z.Item,{name:"litellm_model_name",className:"mb-0",children:(0,s.jsx)(lG.Z,{placeholder:"Enter LiteLLM model name"})}):(0,s.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:en.litellm_model_name})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"Input Cost (per 1M tokens)"}),eN?(0,s.jsx)(eg.Z.Item,{name:"input_cost",className:"mb-0",children:(0,s.jsx)(lx.Z,{placeholder:"Enter input cost"})}):(0,s.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:(null==en?void 0:null===(b=en.litellm_params)||void 0===b?void 0:b.input_cost_per_token)?((null===(y=en.litellm_params)||void 0===y?void 0:y.input_cost_per_token)*1e6).toFixed(4):(null==en?void 0:null===(N=en.model_info)||void 0===N?void 0:N.input_cost_per_token)?(1e6*en.model_info.input_cost_per_token).toFixed(4):null})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"Output Cost (per 1M tokens)"}),eN?(0,s.jsx)(eg.Z.Item,{name:"output_cost",className:"mb-0",children:(0,s.jsx)(lx.Z,{placeholder:"Enter output cost"})}):(0,s.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:(null==en?void 0:null===(w=en.litellm_params)||void 0===w?void 0:w.output_cost_per_token)?(1e6*en.litellm_params.output_cost_per_token).toFixed(4):(null==en?void 0:null===(Z=en.model_info)||void 0===Z?void 0:Z.output_cost_per_token)?(1e6*en.model_info.output_cost_per_token).toFixed(4):null})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"API Base"}),eN?(0,s.jsx)(eg.Z.Item,{name:"api_base",className:"mb-0",children:(0,s.jsx)(lG.Z,{placeholder:"Enter API base"})}):(0,s.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:(null===(C=en.litellm_params)||void 0===C?void 0:C.api_base)||"Not Set"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"Custom LLM Provider"}),eN?(0,s.jsx)(eg.Z.Item,{name:"custom_llm_provider",className:"mb-0",children:(0,s.jsx)(lG.Z,{placeholder:"Enter custom LLM provider"})}):(0,s.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:(null===(A=en.litellm_params)||void 0===A?void 0:A.custom_llm_provider)||"Not Set"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"Organization"}),eN?(0,s.jsx)(eg.Z.Item,{name:"organization",className:"mb-0",children:(0,s.jsx)(lG.Z,{placeholder:"Enter organization"})}):(0,s.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:(null===(P=en.litellm_params)||void 0===P?void 0:P.organization)||"Not Set"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"TPM (Tokens per Minute)"}),eN?(0,s.jsx)(eg.Z.Item,{name:"tpm",className:"mb-0",children:(0,s.jsx)(lx.Z,{placeholder:"Enter TPM"})}):(0,s.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:(null===(F=en.litellm_params)||void 0===F?void 0:F.tpm)||"Not Set"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"RPM (Requests per Minute)"}),eN?(0,s.jsx)(eg.Z.Item,{name:"rpm",className:"mb-0",children:(0,s.jsx)(lx.Z,{placeholder:"Enter RPM"})}):(0,s.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:(null===(I=en.litellm_params)||void 0===I?void 0:I.rpm)||"Not Set"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"Max Retries"}),eN?(0,s.jsx)(eg.Z.Item,{name:"max_retries",className:"mb-0",children:(0,s.jsx)(lx.Z,{placeholder:"Enter max retries"})}):(0,s.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:(null===(V=en.litellm_params)||void 0===V?void 0:V.max_retries)||"Not Set"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"Timeout (seconds)"}),eN?(0,s.jsx)(eg.Z.Item,{name:"timeout",className:"mb-0",children:(0,s.jsx)(lx.Z,{placeholder:"Enter timeout"})}):(0,s.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:(null===(q=en.litellm_params)||void 0===q?void 0:q.timeout)||"Not Set"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"Stream Timeout (seconds)"}),eN?(0,s.jsx)(eg.Z.Item,{name:"stream_timeout",className:"mb-0",children:(0,s.jsx)(lx.Z,{placeholder:"Enter stream timeout"})}):(0,s.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:(null===(B=en.litellm_params)||void 0===B?void 0:B.stream_timeout)||"Not Set"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"Model Access Groups"}),eN?(0,s.jsx)(eg.Z.Item,{name:"model_access_group",className:"mb-0",children:(0,s.jsx)(ev.default,{mode:"tags",showSearch:!0,placeholder:"Select existing groups or type to create new ones",optionFilterProp:"children",tokenSeparators:[","],maxTagCount:"responsive",allowClear:!0,style:{width:"100%"},options:null==er?void 0:er.map(e=>({value:e,label:e}))})}):(0,s.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:(null===(G=en.model_info)||void 0===G?void 0:G.access_groups)?Array.isArray(en.model_info.access_groups)?en.model_info.access_groups.length>0?(0,s.jsx)("div",{className:"flex flex-wrap gap-1",children:en.model_info.access_groups.map((e,l)=>(0,s.jsx)("span",{className:"inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800",children:e},l))}):"No groups assigned":en.model_info.access_groups:"Not Set"})]}),(0,s.jsxs)("div",{children:[(0,s.jsxs)(D.Z,{className:"font-medium",children:["Guardrails",(0,s.jsx)(M.Z,{title:"Apply safety guardrails to this model to filter content or enforce policies",children:(0,s.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/guardrails/quick_start",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,s.jsx)(R.Z,{style:{marginLeft:"4px"}})})})]}),eN?(0,s.jsx)(eg.Z.Item,{name:"guardrails",className:"mb-0",children:(0,s.jsx)(ev.default,{mode:"tags",showSearch:!0,placeholder:"Select existing guardrails or type to create new ones",optionFilterProp:"children",tokenSeparators:[","],maxTagCount:"responsive",allowClear:!0,style:{width:"100%"},options:eF.map(e=>({value:e,label:e}))})}):(0,s.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:(null===(U=en.litellm_params)||void 0===U?void 0:U.guardrails)?Array.isArray(en.litellm_params.guardrails)?en.litellm_params.guardrails.length>0?(0,s.jsx)("div",{className:"flex flex-wrap gap-1",children:en.litellm_params.guardrails.map((e,l)=>(0,s.jsx)("span",{className:"inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800",children:e},l))}):"No guardrails assigned":en.litellm_params.guardrails:"Not Set"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"Tags"}),eN?(0,s.jsx)(eg.Z.Item,{name:"tags",className:"mb-0",children:(0,s.jsx)(ev.default,{mode:"tags",showSearch:!0,placeholder:"Select existing tags or type to create new ones",optionFilterProp:"children",tokenSeparators:[","],maxTagCount:"responsive",allowClear:!0,style:{width:"100%"},options:Object.values(eT).map(e=>({value:e.name,label:e.name,title:e.description||e.name}))})}):(0,s.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:(null===(H=en.litellm_params)||void 0===H?void 0:H.tags)?Array.isArray(en.litellm_params.tags)?en.litellm_params.tags.length>0?(0,s.jsx)("div",{className:"flex flex-wrap gap-1",children:en.litellm_params.tags.map((e,l)=>(0,s.jsx)("span",{className:"inline-flex items-center px-2 py-1 rounded-full text-xs font-medium bg-purple-100 text-purple-800",children:e},l))}):"No tags assigned":en.litellm_params.tags:"Not Set"})]}),e5&&(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"Health Check Model"}),eN?(0,s.jsx)(eg.Z.Item,{name:"health_check_model",className:"mb-0",children:(0,s.jsx)(ev.default,{showSearch:!0,placeholder:"Select existing health check model",optionFilterProp:"children",allowClear:!0,options:(()=>{var e;let l=eJ.litellm_model_name.split("/")[0];return(null==eH?void 0:null===(e=eH.data)||void 0===e?void 0:e.filter(e=>{var t;return(null===(t=e.providers)||void 0===t?void 0:t.includes(l))&&e.model_group!==eJ.litellm_model_name}).map(e=>({value:e.model_group,label:e.model_group})))||[]})()})}):(0,s.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:(null===(J=en.model_info)||void 0===J?void 0:J.health_check_model)||"Not Set"})]}),eN?(0,s.jsx)(lg,{form:ei,showCacheControl:eS,onCacheControlChange:e=>eA(e)}):(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"Cache Control"}),(0,s.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:(null===(Y=en.litellm_params)||void 0===Y?void 0:Y.cache_control_injection_points)?(0,s.jsxs)("div",{children:[(0,s.jsx)("p",{children:"Enabled"}),(0,s.jsx)("div",{className:"mt-2",children:en.litellm_params.cache_control_injection_points.map((e,l)=>(0,s.jsxs)("div",{className:"text-sm text-gray-600 mb-1",children:["Location: ",e.location,",",e.role&&(0,s.jsxs)("span",{children:[" Role: ",e.role]}),void 0!==e.index&&(0,s.jsxs)("span",{children:[" Index: ",e.index]})]},l))})]}):"Disabled"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"Model Info"}),eN?(0,s.jsx)(eg.Z.Item,{name:"model_info",className:"mb-0",children:(0,s.jsx)(ek.default.TextArea,{rows:4,placeholder:'{"gpt-4": 100, "claude-v1": 200}',defaultValue:JSON.stringify(eJ.model_info,null,2)})}):(0,s.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:(0,s.jsx)("pre",{className:"bg-gray-100 p-2 rounded text-xs overflow-auto mt-1",children:JSON.stringify(en.model_info,null,2)})})]}),(0,s.jsxs)("div",{children:[(0,s.jsxs)(D.Z,{className:"font-medium",children:["LiteLLM Params",(0,s.jsx)(M.Z,{title:"Optional litellm params used for making a litellm.completion() call. Some params are automatically added by LiteLLM.",children:(0,s.jsx)("a",{href:"https://docs.litellm.ai/docs/completion/input",target:"_blank",rel:"noopener noreferrer",onClick:e=>e.stopPropagation(),children:(0,s.jsx)(R.Z,{style:{marginLeft:"4px"}})})})]}),eN?(0,s.jsx)(eg.Z.Item,{name:"litellm_extra_params",rules:[{validator:lf.Ac}],children:(0,s.jsx)(ek.default.TextArea,{rows:4,placeholder:'{ "rpm": 100, "timeout": 0, "stream_timeout": 0 }'})}):(0,s.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:(0,s.jsx)("pre",{className:"bg-gray-100 p-2 rounded text-xs overflow-auto mt-1",children:JSON.stringify(en.litellm_params,null,2)})})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(D.Z,{className:"font-medium",children:"Team ID"}),(0,s.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:eJ.model_info.team_id||"Not Set"})]})]}),eN&&(0,s.jsxs)("div",{className:"mt-6 flex justify-end gap-2",children:[(0,s.jsx)(E.Z,{variant:"secondary",onClick:()=>{ei.resetFields(),e_(!1),ew(!1)},disabled:eb,children:"Cancel"}),(0,s.jsx)(E.Z,{variant:"primary",onClick:()=>ei.submit(),loading:eb,children:"Save Changes"})]})]})}):(0,s.jsx)(D.Z,{children:"Loading..."})]})]}),(0,s.jsx)(z.Z,{children:(0,s.jsx)(lB.Z,{children:(0,s.jsx)("pre",{className:"bg-gray-100 p-4 rounded text-xs overflow-auto",children:JSON.stringify(eJ,null,2)})})})]})]}),(0,s.jsx)(ef.Z,{isOpen:ec,title:"Delete Model",alertMessage:"This action cannot be undone.",message:"Are you sure you want to delete this model?",resourceInformationTitle:"Model Information",resourceInformation:[{label:"Model Name",value:(null==eJ?void 0:eJ.model_name)||"Not Set"},{label:"LiteLLM Model Name",value:(null==eJ?void 0:eJ.litellm_model_name)||"Not Set"},{label:"Provider",value:(null==eJ?void 0:eJ.provider)||"Not Set"},{label:"Created By",value:(null==eJ?void 0:null===($=eJ.model_info)||void 0===$?void 0:$.created_by)||"Not Set"}],onCancel:()=>em(!1),onOk:e2,confirmLoading:eu}),ex&&!eX?(0,s.jsx)(l$,{isVisible:ex,onCancel:()=>ep(!1),onAddCredential:eQ,existingCredential:eZ,setIsCredentialModalOpen:ep}):(0,s.jsx)(ea.Z,{open:ex,onCancel:()=>ep(!1),title:"Using Existing Credential",children:(0,s.jsx)(D.Z,{children:eJ.litellm_params.litellm_credential_name})}),(0,s.jsx)(lJ,{isVisible:eM,onCancel:()=>eL(!1),onSuccess:e=>{ed(e),es&&es(e)},modelData:en||eJ,accessToken:ee||"",userRole:et||""})]})}var lQ=t(27593),l0=t(56147),l1=e=>{var l;let{premiumUser:t,teams:r}=e,{accessToken:i,token:o,userRole:c,userId:x}=(0,n.Z)(),[p]=eg.Z.useForm(),[g,j]=(0,f.useState)(""),[v,_]=(0,f.useState)([]),[b,y]=(0,f.useState)(L.Cl.Anthropic),[N,w]=(0,f.useState)(null),[Z,C]=(0,f.useState)(null),[k,S]=(0,f.useState)(null),[A,E]=(0,f.useState)(0),[M,F]=(0,f.useState)({}),[I,R]=(0,f.useState)(!1),[V,q]=(0,f.useState)(null),[B,G]=(0,f.useState)(null),[U,H]=(0,f.useState)(0),W=(0,eV.NL)(),{data:Y,isLoading:$,refetch:ee}=(0,u.XP)(),{data:el,isLoading:et}=m(),{data:es,isLoading:ea}=d(),er=(null==es?void 0:es.credentials)||[],{data:ei,isLoading:en}=(0,h.L)(),ed=(0,f.useMemo)(()=>{if(!(null==Y?void 0:Y.data))return[];let e=new Set;for(let l of Y.data)e.add(l.model_name);return Array.from(e).sort()},[null==Y?void 0:Y.data]),ec=(0,f.useMemo)(()=>{if(!(null==Y?void 0:Y.data))return[];let e=new Set;for(let l of Y.data){let t=l.model_info;if(null==t?void 0:t.access_groups)for(let l of t.access_groups)e.add(l)}return Array.from(e)},[null==Y?void 0:Y.data]),eu=(0,f.useMemo)(()=>(null==Y?void 0:Y.data)?Y.data.map(e=>e.model_name):[],[null==Y?void 0:Y.data]),ex=e=>null!=el&&"object"==typeof el&&e in el?el[e].litellm_provider:"openai",ep=(0,f.useMemo)(()=>(null==Y?void 0:Y.data)?K(Y,ex):{data:[]},[null==Y?void 0:Y.data,ex]),ef=c&&(0,eR.P4)(c),ej=c&&eR.lo.includes(c),ev=x&&(0,eR.yV)(r,x),e_=ej&&(null==ei?void 0:null===(l=ei.values)||void 0===l?void 0:l.disable_model_add_for_internal_users)===!0,eb=!ef&&(e_||!ev),ey={name:"file",accept:".json",pastable:!1,beforeUpload:e=>{if("application/json"===e.type){let l=new FileReader;l.onload=e=>{if(e.target){let l=e.target.result;p.setFieldsValue({vertex_credentials:l})}},l.readAsText(e)}return!1},onChange(e){"done"===e.file.status?eo.Z.success("".concat(e.file.name," file uploaded successfully")):"error"===e.file.status&&eo.Z.fromBackend("".concat(e.file.name," file upload failed."))}},eN=()=>{j(new Date().toLocaleString()),W.invalidateQueries({queryKey:["models","list"]}),ee()},ew=async()=>{if(i)try{let e={router_settings:{}};"global"===N?(k&&(e.router_settings.retry_policy=k),eo.Z.success("Global retry settings saved successfully")):(Z&&(e.router_settings.model_group_retry_policy=Z),eo.Z.success("Retry settings saved successfully for ".concat(N))),await (0,a.setCallbacksCall)(i,e)}catch(e){eo.Z.fromBackend("Failed to save retry settings")}};if((0,f.useEffect)(()=>{if(!i||!o||!c||!x||!Y)return;let e=async()=>{try{let e=(await (0,a.getCallbacksCall)(i,x,c)).router_settings,l=e.model_group_retry_policy,t=e.num_retries;C(l),S(e.retry_policy),E(t);let s=e.model_group_alias||{};F(s)}catch(e){console.error("Error fetching model data:",e)}};i&&o&&c&&x&&Y&&e()},[i,o,c,x,Y]),c&&"Admin Viewer"==c){let{Title:e,Paragraph:l}=Q.default;return(0,s.jsxs)("div",{children:[(0,s.jsx)(e,{level:1,children:"Access Denied"}),(0,s.jsx)(l,{children:"Ask your proxy admin for access to view all models"})]})}let eZ=async()=>{try{let e=await p.validateFields();await eh(e,i,p,eN)}catch(t){var e;let l=(null===(e=t.errorFields)||void 0===e?void 0:e.map(e=>"".concat(e.name.join("."),": ").concat(e.errors.join(", "))).join(" | "))||"Unknown validation error";eo.Z.fromBackend("Please fill in the following required fields: ".concat(l))}};return(Object.keys(L.Cl).find(e=>L.Cl[e]===b),B)?(0,s.jsx)("div",{className:"w-full h-full",children:(0,s.jsx)(l0.Z,{teamId:B,onClose:()=>G(null),accessToken:i,is_team_admin:"Admin"===c,is_proxy_admin:"Proxy Admin"===c,userModels:eu,editTeam:!1,onUpdate:eN,premiumUser:t})}):(0,s.jsx)("div",{className:"w-full mx-4 h-[75vh]",children:(0,s.jsx)(O.Z,{numItems:1,className:"gap-2 p-8 w-full mt-2",children:(0,s.jsxs)(eq.Z,{numColSpan:1,className:"flex flex-col gap-2",children:[(0,s.jsx)("div",{className:"flex justify-between items-center mb-4",children:(0,s.jsxs)("div",{children:[(0,s.jsx)("h2",{className:"text-lg font-semibold",children:"Model Management"}),eR.ZL.includes(c)?(0,s.jsx)("p",{className:"text-sm text-gray-600",children:"Add and manage models for the proxy"}):(0,s.jsx)("p",{className:"text-sm text-gray-600",children:"Add models for teams you are an admin for."})]})}),(0,s.jsxs)("div",{className:"mb-4 px-4 py-3 bg-blue-50 rounded-lg border border-blue-100 flex items-center gap-4",children:[(0,s.jsx)("div",{className:"flex-shrink-0 w-10 h-10 bg-white rounded-full flex items-center justify-center border border-blue-200",children:(0,s.jsx)(eU.Z,{style:{fontSize:"18px",color:"#6366f1"}})}),(0,s.jsxs)("div",{className:"flex-1 min-w-0",children:[(0,s.jsx)("h4",{className:"text-gray-900 font-semibold text-sm m-0",children:"Missing a provider?"}),(0,s.jsx)("p",{className:"text-gray-500 text-xs m-0 mt-0.5",children:"The LiteLLM engineering team is constantly adding support for new LLM models, providers, endpoints. If you don't see the one you need, let us know and we'll prioritize it."})]}),(0,s.jsxs)("a",{href:"https://models.litellm.ai/?request=true",target:"_blank",rel:"noopener noreferrer",className:"flex-shrink-0 inline-flex items-center gap-2 px-4 py-2 bg-[#6366f1] hover:bg-[#5558e3] text-white text-sm font-medium rounded-lg transition-colors",children:["Request Provider",(0,s.jsx)("svg",{xmlns:"http://www.w3.org/2000/svg",className:"h-4 w-4",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:2,children:(0,s.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"})})]})]}),V&&!($||et||ea||en)?(0,s.jsx)(lX,{modelId:V,onClose:()=>{q(null)},accessToken:i,userID:x,userRole:c,onModelUpdate:e=>{W.invalidateQueries({queryKey:["models","list"]}),eN()},modelAccessGroups:ec}):(0,s.jsxs)(eD.Z,{index:U,onIndexChange:H,className:"gap-2 h-[75vh] w-full ",children:[(0,s.jsxs)(eB.Z,{className:"flex justify-between mt-2 w-full items-center",children:[(0,s.jsxs)("div",{className:"flex",children:[eR.ZL.includes(c)?(0,s.jsx)(ez.Z,{children:"All Models"}):(0,s.jsx)(ez.Z,{children:"Your Models"}),!eb&&(0,s.jsx)(ez.Z,{children:"Add Model"}),eR.ZL.includes(c)&&(0,s.jsx)(ez.Z,{children:"LLM Credentials"}),eR.ZL.includes(c)&&(0,s.jsx)(ez.Z,{children:"Pass-Through Endpoints"}),eR.ZL.includes(c)&&(0,s.jsx)(ez.Z,{children:"Health Status"}),eR.ZL.includes(c)&&(0,s.jsx)(ez.Z,{children:"Model Retry Settings"}),eR.ZL.includes(c)&&(0,s.jsx)(ez.Z,{children:"Model Group Alias"}),eR.ZL.includes(c)&&(0,s.jsx)(ez.Z,{children:"Price Data Reload"})]}),(0,s.jsxs)("div",{className:"flex items-center space-x-2",children:[g&&(0,s.jsxs)(D.Z,{children:["Last Refreshed: ",g]}),(0,s.jsx)(P.Z,{icon:eO.Z,variant:"shadow",size:"xs",className:"self-center",onClick:eN})]})]}),(0,s.jsxs)(eG.Z,{children:[(0,s.jsx)(J,{selectedModelGroup:N,setSelectedModelGroup:w,availableModelGroups:ed,availableModelAccessGroups:ec,setSelectedModelId:q,setSelectedTeamId:G}),!eb&&(0,s.jsx)(z.Z,{className:"h-full",children:(0,s.jsx)(lA,{form:p,handleOk:eZ,selectedProvider:b,setSelectedProvider:y,providerModels:v,setProviderModelsFn:e=>{_((0,L.bK)(e,el))},getPlaceholder:L.ph,uploadProps:ey,showAdvancedSettings:I,setShowAdvancedSettings:R,teams:r,credentials:er,accessToken:i,userRole:c})}),(0,s.jsx)(z.Z,{children:(0,s.jsx)(eT,{uploadProps:ey})}),(0,s.jsx)(z.Z,{children:(0,s.jsx)(lQ.Z,{accessToken:i,userRole:c,userID:x,modelData:ep,premiumUser:t})}),(0,s.jsx)(z.Z,{children:(0,s.jsx)(lT,{accessToken:i,modelData:ep,all_models_on_proxy:eu,getDisplayModelName:T,setSelectedModelId:q})}),(0,s.jsx)(X,{selectedModelGroup:N,setSelectedModelGroup:w,availableModelGroups:ed,globalRetryPolicy:k,setGlobalRetryPolicy:S,defaultRetry:A,modelGroupRetryPolicy:Z,setModelGroupRetryPolicy:C,handleSaveRetrySettings:ew}),(0,s.jsx)(z.Z,{children:(0,s.jsx)(lz,{accessToken:i,initialModelGroupAlias:M,onAliasUpdate:F})}),(0,s.jsx)(em,{})]})]})]})})})}},27593:function(e,l,t){t.d(l,{Z:function(){return Y}});var s=t(57437),a=t(2265),r=t(78489),i=t(47323),n=t(84264),o=t(96761),d=t(19250),c=t(99981),m=t(33866),u=t(15731),h=t(53410),x=t(74998),p=t(59341),g=t(49566),f=t(12514),j=t(97765),v=t(37592),_=t(10032),b=t(22116),y=t(51653),N=t(24199),w=t(12660),Z=t(15424),C=t(58760),k=t(5545),S=t(45246),A=t(96473),E=t(31283),P=e=>{let{value:l={},onChange:t}=e,[r,i]=(0,a.useState)(Object.entries(l)),n=e=>{let l=r.filter((l,t)=>t!==e);i(l),null==t||t(Object.fromEntries(l))},o=(e,l,s)=>{let a=[...r];a[e]=[l,s],i(a),null==t||t(Object.fromEntries(a))};return(0,s.jsxs)("div",{children:[r.map((e,l)=>{let[t,a]=e;return(0,s.jsxs)(C.Z,{style:{display:"flex",marginBottom:8},align:"center",children:[(0,s.jsx)(E.o,{placeholder:"Header Name",value:t,onChange:e=>o(l,e.target.value,a)}),(0,s.jsx)(E.o,{placeholder:"Header Value",value:a,onChange:e=>o(l,t,e.target.value)}),(0,s.jsx)("div",{style:{display:"flex",alignItems:"center",justifyContent:"center",height:"100%"},children:(0,s.jsx)(S.Z,{onClick:()=>n(l),style:{cursor:"pointer"}})})]},l)}),(0,s.jsx)(k.ZP,{type:"dashed",onClick:()=>{i([...r,["",""]])},icon:(0,s.jsx)(A.Z,{}),children:"Add Header"})]})},M=t(77565),L=e=>{let{pathValue:l,targetValue:t,includeSubpath:a}=e,r=(0,d.getProxyBaseUrl)();return l&&t?(0,s.jsxs)(f.Z,{className:"p-5",children:[(0,s.jsx)(o.Z,{className:"text-lg font-semibold text-gray-900 mb-2",children:"Route Preview"}),(0,s.jsx)(j.Z,{className:"text-gray-600 mb-5",children:"How your requests will be routed"}),(0,s.jsxs)("div",{className:"space-y-5",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)("div",{className:"text-base font-semibold text-gray-900 mb-3",children:"Basic routing:"}),(0,s.jsxs)("div",{className:"flex items-center gap-4",children:[(0,s.jsxs)("div",{className:"flex-1 bg-gray-50 border border-gray-200 rounded-lg p-3",children:[(0,s.jsx)("div",{className:"text-sm text-gray-600 mb-2",children:"Your endpoint"}),(0,s.jsx)("code",{className:"font-mono text-sm text-gray-900",children:l?"".concat(r).concat(l):""})]}),(0,s.jsx)("div",{className:"text-gray-400",children:(0,s.jsx)(M.Z,{className:"text-lg"})}),(0,s.jsxs)("div",{className:"flex-1 bg-gray-50 border border-gray-200 rounded-lg p-3",children:[(0,s.jsx)("div",{className:"text-sm text-gray-600 mb-2",children:"Forwards to"}),(0,s.jsx)("code",{className:"font-mono text-sm text-gray-900",children:t})]})]})]}),a&&(0,s.jsx)(s.Fragment,{children:(0,s.jsxs)("div",{children:[(0,s.jsx)("div",{className:"text-base font-semibold text-gray-900 mb-3",children:"With subpaths:"}),(0,s.jsxs)("div",{className:"flex items-center gap-4",children:[(0,s.jsxs)("div",{className:"flex-1 bg-gray-50 border border-gray-200 rounded-lg p-3",children:[(0,s.jsx)("div",{className:"text-sm text-gray-600 mb-2",children:"Your endpoint + subpath"}),(0,s.jsxs)("code",{className:"font-mono text-sm text-gray-900",children:[l&&"".concat(r).concat(l),(0,s.jsx)("span",{className:"text-blue-600",children:"/v1/text-to-image/base/model"})]})]}),(0,s.jsx)("div",{className:"text-gray-400",children:(0,s.jsx)(M.Z,{className:"text-lg"})}),(0,s.jsxs)("div",{className:"flex-1 bg-gray-50 border border-gray-200 rounded-lg p-3",children:[(0,s.jsx)("div",{className:"text-sm text-gray-600 mb-2",children:"Forwards to"}),(0,s.jsxs)("code",{className:"font-mono text-sm text-gray-900",children:[t,(0,s.jsx)("span",{className:"text-blue-600",children:"/v1/text-to-image/base/model"})]})]})]}),(0,s.jsxs)("div",{className:"mt-3 text-sm text-gray-600",children:["Any path after ",l," will be appended to the target URL"]})]})}),!a&&(0,s.jsx)("div",{className:"mt-4 p-3 bg-blue-50 rounded-md border border-blue-200",children:(0,s.jsxs)("div",{className:"flex items-start",children:[(0,s.jsx)(Z.Z,{className:"text-blue-500 mt-0.5 mr-2 flex-shrink-0"}),(0,s.jsxs)("div",{className:"text-sm text-blue-700",children:[(0,s.jsx)("span",{className:"font-medium",children:"Not seeing the routing you wanted?"})," Try enabling - Include Subpaths - above - this allows subroutes like"," ",(0,s.jsx)("code",{className:"bg-blue-100 px-1 py-0.5 rounded font-mono text-xs",children:"/api/v1/models"})," to be forwarded automatically."]})]})})]})]}):null},F=t(9114),I=t(63709),T=e=>{let{premiumUser:l,authEnabled:t,onAuthChange:a}=e;return(0,s.jsxs)(f.Z,{className:"p-6",children:[(0,s.jsx)(o.Z,{className:"text-lg font-semibold text-gray-900 mb-2",children:"Security"}),(0,s.jsx)(j.Z,{className:"text-gray-600 mb-4",children:"When enabled, requests to this endpoint will require a valid LiteLLM Virtual Key"}),l?(0,s.jsx)(_.Z.Item,{name:"auth",valuePropName:"checked",className:"mb-0",children:(0,s.jsx)(I.Z,{checked:t,onChange:e=>{a(e)}})}):(0,s.jsxs)("div",{children:[(0,s.jsxs)("div",{className:"flex items-center mb-3",children:[(0,s.jsx)(I.Z,{disabled:!0,checked:!1,style:{outline:"2px solid #d1d5db",outlineOffset:"2px"}}),(0,s.jsx)("span",{className:"ml-2 text-sm text-gray-400",children:"Authentication (Premium)"})]}),(0,s.jsx)("div",{className:"p-3 bg-yellow-50 border border-yellow-200 rounded-lg",children:(0,s.jsxs)(n.Z,{className:"text-sm text-yellow-800",children:["Setting authentication for pass-through endpoints is a LiteLLM Enterprise feature. Get a trial key"," ",(0,s.jsx)("a",{href:"https://www.litellm.ai/#pricing",target:"_blank",rel:"noopener noreferrer",className:"underline",children:"here"}),"."]})})]})]})},R=t(67479),O=e=>{let{accessToken:l,value:t={},onChange:r,disabled:i=!1}=e,[n,d]=(0,a.useState)(Object.keys(t)),[m,u]=(0,a.useState)(t);(0,a.useEffect)(()=>{u(t),d(Object.keys(t))},[t]);let h=(e,l,t)=>{var s,a;let i=m[e]||{},n={...m,[e]:{...i,[l]:t.length>0?t:void 0}};(null===(s=n[e])||void 0===s?void 0:s.request_fields)||(null===(a=n[e])||void 0===a?void 0:a.response_fields)||(n[e]=null),u(n),r&&r(n)};return(0,s.jsxs)(f.Z,{className:"p-6",children:[(0,s.jsx)(o.Z,{className:"text-lg font-semibold text-gray-900 mb-2",children:"Guardrails"}),(0,s.jsx)(j.Z,{className:"text-gray-600 mb-6",children:"Configure guardrails to enforce policies on requests and responses. Guardrails are opt-in for passthrough endpoints."}),(0,s.jsx)(y.Z,{message:(0,s.jsxs)("span",{children:["Field-Level Targeting"," ",(0,s.jsx)("a",{href:"https://docs.litellm.ai/docs/proxy/pass_through_guardrails#field-level-targeting",target:"_blank",rel:"noopener noreferrer",className:"text-blue-600 hover:text-blue-800 underline",children:"(Learn More)"})]}),description:(0,s.jsxs)("div",{className:"space-y-2",children:[(0,s.jsx)("div",{children:"Optionally specify which fields to check. If left empty, the entire request/response is sent to the guardrail."}),(0,s.jsxs)("div",{className:"text-xs space-y-1 mt-2",children:[(0,s.jsx)("div",{className:"font-medium",children:"Common Examples:"}),(0,s.jsxs)("div",{children:["• ",(0,s.jsx)("code",{className:"bg-gray-100 px-1 rounded",children:"query"})," - Single field"]}),(0,s.jsxs)("div",{children:["• ",(0,s.jsx)("code",{className:"bg-gray-100 px-1 rounded",children:"documents[*].text"})," - All text in documents array"]}),(0,s.jsxs)("div",{children:["• ",(0,s.jsx)("code",{className:"bg-gray-100 px-1 rounded",children:"messages[*].content"})," - All message contents"]})]})]}),type:"info",showIcon:!0,className:"mb-4"}),(0,s.jsx)(_.Z.Item,{label:(0,s.jsxs)("span",{className:"text-sm font-medium text-gray-700 flex items-center",children:["Select Guardrails",(0,s.jsx)(c.Z,{title:"Choose which guardrails should run on this endpoint. Org/team/key level guardrails will also be included.",children:(0,s.jsx)(Z.Z,{className:"ml-2 text-blue-400 hover:text-blue-600 cursor-help"})})]}),children:(0,s.jsx)(R.Z,{accessToken:l,value:n,onChange:e=>{d(e);let l={};e.forEach(e=>{l[e]=m[e]||null}),u(l),r&&r(l)},disabled:i})}),n.length>0&&(0,s.jsxs)("div",{className:"mt-6 space-y-4",children:[(0,s.jsxs)("div",{className:"flex items-center justify-between mb-3",children:[(0,s.jsx)("div",{className:"text-sm font-medium text-gray-700",children:"Field Targeting (Optional)"}),(0,s.jsx)("div",{className:"text-xs text-gray-500",children:"\uD83D\uDCA1 Tip: Leave empty to check entire payload"})]}),n.map(e=>{var l,t;return(0,s.jsxs)(f.Z,{className:"p-4 bg-gray-50",children:[(0,s.jsx)("div",{className:"text-sm font-medium text-gray-900 mb-3",children:e}),(0,s.jsxs)("div",{className:"space-y-3",children:[(0,s.jsxs)("div",{children:[(0,s.jsxs)("div",{className:"flex items-center justify-between mb-1",children:[(0,s.jsxs)("label",{className:"text-xs text-gray-600 flex items-center",children:["Request Fields (pre_call)",(0,s.jsx)(c.Z,{title:(0,s.jsxs)("div",{children:[(0,s.jsx)("div",{className:"font-medium mb-1",children:"Specify which request fields to check"}),(0,s.jsxs)("div",{className:"text-xs space-y-1",children:[(0,s.jsx)("div",{children:"Examples:"}),(0,s.jsx)("div",{children:"• query"}),(0,s.jsx)("div",{children:"• documents[*].text"}),(0,s.jsx)("div",{children:"• messages[*].content"})]})]}),children:(0,s.jsx)(Z.Z,{className:"ml-1 text-gray-400"})})]}),(0,s.jsxs)("div",{className:"flex gap-1",children:[(0,s.jsx)("button",{type:"button",onClick:()=>{var l;let t=(null===(l=m[e])||void 0===l?void 0:l.request_fields)||[];h(e,"request_fields",[...t,"query"])},className:"text-xs px-2 py-1 bg-white border border-gray-300 rounded hover:bg-gray-50",disabled:i,children:"+ query"}),(0,s.jsx)("button",{type:"button",onClick:()=>{var l;let t=(null===(l=m[e])||void 0===l?void 0:l.request_fields)||[];h(e,"request_fields",[...t,"documents[*]"])},className:"text-xs px-2 py-1 bg-white border border-gray-300 rounded hover:bg-gray-50",disabled:i,children:"+ documents[*]"})]})]}),(0,s.jsx)(v.default,{mode:"tags",style:{width:"100%"},placeholder:"Type field name or use + buttons above (e.g., query, documents[*].text)",value:(null===(l=m[e])||void 0===l?void 0:l.request_fields)||[],onChange:l=>h(e,"request_fields",l),disabled:i,tokenSeparators:[","]})]}),(0,s.jsxs)("div",{children:[(0,s.jsxs)("div",{className:"flex items-center justify-between mb-1",children:[(0,s.jsxs)("label",{className:"text-xs text-gray-600 flex items-center",children:["Response Fields (post_call)",(0,s.jsx)(c.Z,{title:(0,s.jsxs)("div",{children:[(0,s.jsx)("div",{className:"font-medium mb-1",children:"Specify which response fields to check"}),(0,s.jsxs)("div",{className:"text-xs space-y-1",children:[(0,s.jsx)("div",{children:"Examples:"}),(0,s.jsx)("div",{children:"• results[*].text"}),(0,s.jsx)("div",{children:"• choices[*].message.content"})]})]}),children:(0,s.jsx)(Z.Z,{className:"ml-1 text-gray-400"})})]}),(0,s.jsx)("div",{className:"flex gap-1",children:(0,s.jsx)("button",{type:"button",onClick:()=>{var l;let t=(null===(l=m[e])||void 0===l?void 0:l.response_fields)||[];h(e,"response_fields",[...t,"results[*]"])},className:"text-xs px-2 py-1 bg-white border border-gray-300 rounded hover:bg-gray-50",disabled:i,children:"+ results[*]"})})]}),(0,s.jsx)(v.default,{mode:"tags",style:{width:"100%"},placeholder:"Type field name or use + buttons above (e.g., results[*].text)",value:(null===(t=m[e])||void 0===t?void 0:t.response_fields)||[],onChange:l=>h(e,"response_fields",l),disabled:i,tokenSeparators:[","]})]})]})]},e)})]})]})};let{Option:V}=v.default;var q=e=>{let{accessToken:l,setPassThroughItems:t,passThroughItems:i,premiumUser:n=!1}=e,[m]=_.Z.useForm(),[u,h]=(0,a.useState)(!1),[x,v]=(0,a.useState)(!1),[C,k]=(0,a.useState)(""),[S,A]=(0,a.useState)(""),[E,M]=(0,a.useState)(""),[I,R]=(0,a.useState)(!0),[V,q]=(0,a.useState)(!1),[z,D]=(0,a.useState)({}),B=()=>{m.resetFields(),A(""),M(""),R(!0),D({}),h(!1)},G=e=>{let l=e;e&&!e.startsWith("/")&&(l="/"+e),A(l),m.setFieldsValue({path:l})},U=async e=>{console.log("addPassThrough called with:",e),v(!0);try{!n&&"auth"in e&&delete e.auth,z&&Object.keys(z).length>0&&(e.guardrails=z),console.log("formValues: ".concat(JSON.stringify(e)));let s=(await (0,d.createPassThroughEndpoint)(l,e)).endpoints[0],a=[...i,s];t(a),F.Z.success("Pass-through endpoint created successfully"),m.resetFields(),A(""),M(""),R(!0),D({}),h(!1)}catch(e){F.Z.fromBackend("Error creating pass-through endpoint: "+e)}finally{v(!1)}};return(0,s.jsxs)("div",{children:[(0,s.jsx)(r.Z,{className:"mx-auto mb-4 mt-4",onClick:()=>h(!0),children:"+ Add Pass-Through Endpoint"}),(0,s.jsx)(b.Z,{title:(0,s.jsxs)("div",{className:"flex items-center space-x-3 pb-4 border-b border-gray-100",children:[(0,s.jsx)(w.Z,{className:"text-xl text-blue-500"}),(0,s.jsx)("h2",{className:"text-xl font-semibold text-gray-900",children:"Add Pass-Through Endpoint"})]}),open:u,width:1e3,onCancel:B,footer:null,className:"top-8",styles:{body:{padding:"24px"},header:{padding:"24px 24px 0 24px",border:"none"}},children:(0,s.jsxs)("div",{className:"mt-6",children:[(0,s.jsx)(y.Z,{message:"What is a Pass-Through Endpoint?",description:"Route requests from your LiteLLM proxy to any external API. Perfect for custom models, image generation APIs, or any service you want to proxy through LiteLLM.",type:"info",showIcon:!0,className:"mb-6"}),(0,s.jsxs)(_.Z,{form:m,onFinish:U,layout:"vertical",className:"space-y-6",initialValues:{include_subpath:!0,path:S,target:E},children:[(0,s.jsxs)(f.Z,{className:"p-5",children:[(0,s.jsx)(o.Z,{className:"text-lg font-semibold text-gray-900 mb-2",children:"Route Configuration"}),(0,s.jsx)(j.Z,{className:"text-gray-600 mb-5",children:"Configure how requests to your domain will be forwarded to the target API"}),(0,s.jsxs)("div",{className:"space-y-5",children:[(0,s.jsx)(_.Z.Item,{label:(0,s.jsx)("span",{className:"text-sm font-medium text-gray-700",children:"Path Prefix"}),name:"path",rules:[{required:!0,message:"Path is required",pattern:/^\//}],extra:(0,s.jsx)("div",{className:"text-xs text-gray-500 mt-1",children:"Example: /bria, /adobe-photoshop, /elasticsearch"}),className:"mb-4",children:(0,s.jsx)("div",{className:"flex items-center",children:(0,s.jsx)(g.Z,{placeholder:"bria",value:S,onChange:e=>G(e.target.value),className:"flex-1"})})}),(0,s.jsx)(_.Z.Item,{label:(0,s.jsx)("span",{className:"text-sm font-medium text-gray-700",children:"Target URL"}),name:"target",rules:[{required:!0,message:"Target URL is required"},{type:"url",message:"Please enter a valid URL"}],extra:(0,s.jsx)("div",{className:"text-xs text-gray-500 mt-1",children:"Example:https://engine.prod.bria-api.com"}),className:"mb-4",children:(0,s.jsx)(g.Z,{placeholder:"https://engine.prod.bria-api.com",value:E,onChange:e=>{M(e.target.value),m.setFieldsValue({target:e.target.value})}})}),(0,s.jsxs)("div",{className:"flex items-center justify-between py-3",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)("div",{className:"text-sm font-medium text-gray-700",children:"Include Subpaths"}),(0,s.jsx)("div",{className:"text-xs text-gray-500 mt-0.5",children:"Forward all subpaths to the target API (recommended for REST APIs)"})]}),(0,s.jsx)(_.Z.Item,{name:"include_subpath",valuePropName:"checked",className:"mb-0",children:(0,s.jsx)(p.Z,{checked:I,onChange:R})})]})]})]}),(0,s.jsx)(L,{pathValue:S,targetValue:E,includeSubpath:I}),(0,s.jsxs)(f.Z,{className:"p-6",children:[(0,s.jsx)(o.Z,{className:"text-lg font-semibold text-gray-900 mb-2",children:"Headers"}),(0,s.jsx)(j.Z,{className:"text-gray-600 mb-6",children:"Add headers that will be sent with every request to the target API"}),(0,s.jsx)(_.Z.Item,{label:(0,s.jsxs)("span",{className:"text-sm font-medium text-gray-700 flex items-center",children:["Authentication Headers",(0,s.jsx)(c.Z,{title:"Authentication and other headers to forward with requests",children:(0,s.jsx)(Z.Z,{className:"ml-2 text-blue-400 hover:text-blue-600 cursor-help"})})]}),name:"headers",rules:[{required:!0,message:"Please configure the headers"}],extra:(0,s.jsxs)("div",{className:"text-xs text-gray-500 mt-2",children:[(0,s.jsx)("div",{className:"font-medium mb-1",children:"Add authentication tokens and other required headers"}),(0,s.jsx)("div",{children:"Common examples: auth_token, Authorization, x-api-key"})]}),children:(0,s.jsx)(P,{})})]}),(0,s.jsx)(T,{premiumUser:n,authEnabled:V,onAuthChange:e=>{q(e),m.setFieldsValue({auth:e})}}),(0,s.jsx)(O,{accessToken:l,value:z,onChange:D}),(0,s.jsxs)(f.Z,{className:"p-6",children:[(0,s.jsx)(o.Z,{className:"text-lg font-semibold text-gray-900 mb-2",children:"Billing"}),(0,s.jsx)(j.Z,{className:"text-gray-600 mb-6",children:"Optional cost tracking for this endpoint"}),(0,s.jsx)(_.Z.Item,{label:(0,s.jsxs)("span",{className:"text-sm font-medium text-gray-700 flex items-center",children:["Cost Per Request (USD)",(0,s.jsx)(c.Z,{title:"Optional: Track costs for requests to this endpoint",children:(0,s.jsx)(Z.Z,{className:"ml-2 text-gray-400 hover:text-gray-600"})})]}),name:"cost_per_request",extra:(0,s.jsx)("div",{className:"text-xs text-gray-500 mt-2",children:"The cost charged for each request through this endpoint"}),children:(0,s.jsx)(N.Z,{min:0,step:.001,precision:4,placeholder:"2.0000",size:"large"})})]}),(0,s.jsxs)("div",{className:"flex items-center justify-end space-x-3 pt-6 border-t border-gray-100",children:[(0,s.jsx)(r.Z,{variant:"secondary",onClick:B,children:"Cancel"}),(0,s.jsx)(r.Z,{variant:"primary",loading:x,onClick:()=>{console.log("Submit button clicked"),m.submit()},children:x?"Creating...":"Add Pass-Through Endpoint"})]})]})]})})]})},z=t(30078),D=t(4260),B=t(12221),G=t(87769),U=t(42208);let H=e=>{let{value:l}=e,[t,r]=(0,a.useState)(!1),i=JSON.stringify(l,null,2);return(0,s.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,s.jsx)("pre",{className:"font-mono text-xs bg-gray-50 p-2 rounded max-w-md overflow-auto",children:t?i:"••••••••"}),(0,s.jsx)("button",{onClick:()=>r(!t),className:"p-1 hover:bg-gray-100 rounded",type:"button",children:t?(0,s.jsx)(G.Z,{className:"w-4 h-4 text-gray-500"}):(0,s.jsx)(U.Z,{className:"w-4 h-4 text-gray-500"})})]})};var K=e=>{let{endpointData:l,onClose:t,accessToken:r,isAdmin:i,premiumUser:n=!1,onEndpointUpdated:o}=e,[c,m]=(0,a.useState)(l),[u,h]=(0,a.useState)(!1),[x,p]=(0,a.useState)(!1),[g,f]=(0,a.useState)((null==l?void 0:l.auth)||!1),[j,v]=(0,a.useState)((null==l?void 0:l.guardrails)||{}),[b]=_.Z.useForm(),y=async e=>{try{if(!r||!(null==c?void 0:c.id))return;let l={};if(e.headers)try{l="string"==typeof e.headers?JSON.parse(e.headers):e.headers}catch(e){F.Z.fromBackend("Invalid JSON format for headers");return}let t={path:c.path,target:e.target,headers:l,include_subpath:e.include_subpath,cost_per_request:e.cost_per_request,auth:n?e.auth:void 0,guardrails:j&&Object.keys(j).length>0?j:void 0};await (0,d.updatePassThroughEndpoint)(r,c.id,t),m({...c,...t}),p(!1),o&&o()}catch(e){console.error("Error updating endpoint:",e),F.Z.fromBackend("Failed to update pass through endpoint")}},N=async()=>{try{if(!r||!(null==c?void 0:c.id))return;await (0,d.deletePassThroughEndpointsCall)(r,c.id),F.Z.success("Pass through endpoint deleted successfully"),t(),o&&o()}catch(e){console.error("Error deleting endpoint:",e),F.Z.fromBackend("Failed to delete pass through endpoint")}};return u?(0,s.jsx)("div",{className:"p-4",children:"Loading..."}):c?(0,s.jsxs)("div",{className:"p-4",children:[(0,s.jsx)("div",{className:"flex justify-between items-center mb-6",children:(0,s.jsxs)("div",{children:[(0,s.jsx)(k.ZP,{onClick:t,className:"mb-4",children:"← Back"}),(0,s.jsxs)(z.Dx,{children:["Pass Through Endpoint: ",c.path]}),(0,s.jsx)(z.xv,{className:"text-gray-500 font-mono",children:c.id})]})}),(0,s.jsxs)(z.v0,{children:[(0,s.jsxs)(z.td,{className:"mb-4",children:[(0,s.jsx)(z.OK,{children:"Overview"},"overview"),i?(0,s.jsx)(z.OK,{children:"Settings"},"settings"):(0,s.jsx)(s.Fragment,{})]}),(0,s.jsxs)(z.nP,{children:[(0,s.jsxs)(z.x4,{children:[(0,s.jsxs)(z.rj,{numItems:1,numItemsSm:2,numItemsLg:3,className:"gap-6",children:[(0,s.jsxs)(z.Zb,{children:[(0,s.jsx)(z.xv,{children:"Path"}),(0,s.jsx)("div",{className:"mt-2",children:(0,s.jsx)(z.Dx,{className:"font-mono",children:c.path})})]}),(0,s.jsxs)(z.Zb,{children:[(0,s.jsx)(z.xv,{children:"Target"}),(0,s.jsx)("div",{className:"mt-2",children:(0,s.jsx)(z.Dx,{children:c.target})})]}),(0,s.jsxs)(z.Zb,{children:[(0,s.jsx)(z.xv,{children:"Configuration"}),(0,s.jsxs)("div",{className:"mt-2 space-y-2",children:[(0,s.jsx)("div",{children:(0,s.jsx)(z.Ct,{color:c.include_subpath?"green":"gray",children:c.include_subpath?"Include Subpath":"Exact Path"})}),(0,s.jsx)("div",{children:(0,s.jsx)(z.Ct,{color:c.auth?"blue":"gray",children:c.auth?"Auth Required":"No Auth"})}),void 0!==c.cost_per_request&&(0,s.jsx)("div",{children:(0,s.jsxs)(z.xv,{children:["Cost per request: $",c.cost_per_request]})})]})]})]}),(0,s.jsx)("div",{className:"mt-6",children:(0,s.jsx)(L,{pathValue:c.path,targetValue:c.target,includeSubpath:c.include_subpath||!1})}),c.headers&&Object.keys(c.headers).length>0&&(0,s.jsxs)(z.Zb,{className:"mt-6",children:[(0,s.jsxs)("div",{className:"flex justify-between items-center",children:[(0,s.jsx)(z.xv,{className:"font-medium",children:"Headers"}),(0,s.jsxs)(z.Ct,{color:"blue",children:[Object.keys(c.headers).length," headers configured"]})]}),(0,s.jsx)("div",{className:"mt-4",children:(0,s.jsx)(H,{value:c.headers})})]}),c.guardrails&&Object.keys(c.guardrails).length>0&&(0,s.jsxs)(z.Zb,{className:"mt-6",children:[(0,s.jsxs)("div",{className:"flex justify-between items-center",children:[(0,s.jsx)(z.xv,{className:"font-medium",children:"Guardrails"}),(0,s.jsxs)(z.Ct,{color:"purple",children:[Object.keys(c.guardrails).length," guardrails configured"]})]}),(0,s.jsx)("div",{className:"mt-4 space-y-2",children:Object.entries(c.guardrails).map(e=>{let[l,t]=e;return(0,s.jsxs)("div",{className:"p-3 bg-gray-50 rounded",children:[(0,s.jsx)("div",{className:"font-medium text-sm",children:l}),t&&(t.request_fields||t.response_fields)&&(0,s.jsxs)("div",{className:"mt-2 text-xs text-gray-600 space-y-1",children:[t.request_fields&&(0,s.jsxs)("div",{children:["Request fields: ",t.request_fields.join(", ")]}),t.response_fields&&(0,s.jsxs)("div",{children:["Response fields: ",t.response_fields.join(", ")]})]}),!t&&(0,s.jsx)("div",{className:"text-xs text-gray-600 mt-1",children:"Uses entire payload"})]},l)})})]})]}),i&&(0,s.jsx)(z.x4,{children:(0,s.jsxs)(z.Zb,{children:[(0,s.jsxs)("div",{className:"flex justify-between items-center mb-4",children:[(0,s.jsx)(z.Dx,{children:"Pass Through Endpoint Settings"}),(0,s.jsx)("div",{className:"space-x-2",children:!x&&(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(z.zx,{onClick:()=>p(!0),children:"Edit Settings"}),(0,s.jsx)(z.zx,{onClick:N,variant:"secondary",color:"red",children:"Delete Endpoint"})]})})]}),x?(0,s.jsxs)(_.Z,{form:b,onFinish:y,initialValues:{target:c.target,headers:c.headers?JSON.stringify(c.headers,null,2):"",include_subpath:c.include_subpath||!1,cost_per_request:c.cost_per_request,auth:c.auth||!1},layout:"vertical",children:[(0,s.jsx)(_.Z.Item,{label:"Target URL",name:"target",rules:[{required:!0,message:"Please input a target URL"}],children:(0,s.jsx)(z.oi,{placeholder:"https://api.example.com"})}),(0,s.jsx)(_.Z.Item,{label:"Headers (JSON)",name:"headers",children:(0,s.jsx)(D.default.TextArea,{rows:5,placeholder:'{"Authorization": "Bearer your-token", "Content-Type": "application/json"}'})}),(0,s.jsx)(_.Z.Item,{label:"Include Subpath",name:"include_subpath",valuePropName:"checked",children:(0,s.jsx)(I.Z,{})}),(0,s.jsx)(_.Z.Item,{label:"Cost per Request",name:"cost_per_request",children:(0,s.jsx)(B.Z,{min:0,step:.01,precision:2,placeholder:"0.00",addonBefore:"$"})}),(0,s.jsx)(T,{premiumUser:n,authEnabled:g,onAuthChange:e=>{f(e),b.setFieldsValue({auth:e})}}),(0,s.jsx)("div",{className:"mt-4",children:(0,s.jsx)(O,{accessToken:r||"",value:j,onChange:v})}),(0,s.jsxs)("div",{className:"flex justify-end gap-2 mt-6",children:[(0,s.jsx)(k.ZP,{onClick:()=>p(!1),children:"Cancel"}),(0,s.jsx)(z.zx,{children:"Save Changes"})]})]}):(0,s.jsxs)("div",{className:"space-y-4",children:[(0,s.jsxs)("div",{children:[(0,s.jsx)(z.xv,{className:"font-medium",children:"Path"}),(0,s.jsx)("div",{className:"font-mono",children:c.path})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(z.xv,{className:"font-medium",children:"Target URL"}),(0,s.jsx)("div",{children:c.target})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(z.xv,{className:"font-medium",children:"Include Subpath"}),(0,s.jsx)(z.Ct,{color:c.include_subpath?"green":"gray",children:c.include_subpath?"Yes":"No"})]}),void 0!==c.cost_per_request&&(0,s.jsxs)("div",{children:[(0,s.jsx)(z.xv,{className:"font-medium",children:"Cost per Request"}),(0,s.jsxs)("div",{children:["$",c.cost_per_request]})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(z.xv,{className:"font-medium",children:"Authentication Required"}),(0,s.jsx)(z.Ct,{color:c.auth?"green":"gray",children:c.auth?"Yes":"No"})]}),(0,s.jsxs)("div",{children:[(0,s.jsx)(z.xv,{className:"font-medium",children:"Headers"}),c.headers&&Object.keys(c.headers).length>0?(0,s.jsx)("div",{className:"mt-2",children:(0,s.jsx)(H,{value:c.headers})}):(0,s.jsx)("div",{className:"text-gray-500",children:"No headers configured"})]})]})]})})]})]})]}):(0,s.jsx)("div",{className:"p-4",children:"Pass through endpoint not found"})},J=t(12322);let W=e=>{let{value:l}=e,[t,r]=(0,a.useState)(!1),i=JSON.stringify(l);return(0,s.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,s.jsx)("span",{className:"font-mono text-xs",children:t?i:"••••••••"}),(0,s.jsx)("button",{onClick:()=>r(!t),className:"p-1 hover:bg-gray-100 rounded",type:"button",children:t?(0,s.jsx)(G.Z,{className:"w-4 h-4 text-gray-500"}):(0,s.jsx)(U.Z,{className:"w-4 h-4 text-gray-500"})})]})};var Y=e=>{let{accessToken:l,userRole:t,userID:p,modelData:g,premiumUser:f}=e,[j,v]=(0,a.useState)([]),[_,b]=(0,a.useState)(null),[y,N]=(0,a.useState)(!1),[w,Z]=(0,a.useState)(null);(0,a.useEffect)(()=>{l&&t&&p&&(0,d.getPassThroughEndpointsCall)(l).then(e=>{v(e.endpoints)})},[l,t,p]);let C=async e=>{Z(e),N(!0)},k=async()=>{if(null!=w&&l){try{await (0,d.deletePassThroughEndpointsCall)(l,w);let e=j.filter(e=>e.id!==w);v(e),F.Z.success("Endpoint deleted successfully.")}catch(e){console.error("Error deleting the endpoint:",e),F.Z.fromBackend("Error deleting the endpoint: "+e)}N(!1),Z(null)}},S=(e,l)=>{C(e)},A=[{header:"ID",accessorKey:"id",cell:e=>(0,s.jsx)(c.Z,{title:e.row.original.id,children:(0,s.jsx)("div",{className:"font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal px-2 py-0.5 text-left w-full truncate whitespace-nowrap cursor-pointer max-w-[15ch]",onClick:()=>e.row.original.id&&b(e.row.original.id),children:e.row.original.id})})},{header:"Path",accessorKey:"path"},{header:"Target",accessorKey:"target",cell:e=>(0,s.jsx)(n.Z,{children:e.getValue()})},{header:()=>(0,s.jsxs)("div",{className:"flex items-center gap-1",children:[(0,s.jsx)("span",{children:"Authentication"}),(0,s.jsx)(c.Z,{title:"LiteLLM Virtual Key required to call endpoint",children:(0,s.jsx)(u.Z,{className:"w-4 h-4 text-gray-400 cursor-help"})})]}),accessorKey:"auth",cell:e=>(0,s.jsx)(m.Z,{color:e.getValue()?"green":"gray",children:e.getValue()?"Yes":"No"})},{header:"Headers",accessorKey:"headers",cell:e=>(0,s.jsx)(W,{value:e.getValue()||{}})},{header:"Actions",id:"actions",cell:e=>{let{row:l}=e;return(0,s.jsxs)("div",{className:"flex space-x-1",children:[(0,s.jsx)(i.Z,{icon:h.Z,size:"sm",onClick:()=>l.original.id&&b(l.original.id),title:"Edit"}),(0,s.jsx)(i.Z,{icon:x.Z,size:"sm",onClick:()=>S(l.original.id,l.index),title:"Delete"})]})}}];if(!l)return null;if(_){console.log("selectedEndpointId",_),console.log("generalSettings",j);let e=j.find(e=>e.id===_);return e?(0,s.jsx)(K,{endpointData:e,onClose:()=>b(null),accessToken:l,isAdmin:"Admin"===t||"admin"===t,premiumUser:f,onEndpointUpdated:()=>{l&&(0,d.getPassThroughEndpointsCall)(l).then(e=>{v(e.endpoints)})}}):(0,s.jsx)("div",{children:"Endpoint not found"})}return(0,s.jsxs)("div",{children:[(0,s.jsxs)("div",{children:[(0,s.jsx)(o.Z,{children:"Pass Through Endpoints"}),(0,s.jsx)(n.Z,{className:"text-tremor-content",children:"Configure and manage your pass-through endpoints"})]}),(0,s.jsx)(q,{accessToken:l,setPassThroughItems:v,passThroughItems:j,premiumUser:f}),(0,s.jsx)(J.w,{data:j,columns:A,renderSubComponent:()=>(0,s.jsx)("div",{}),getRowCanExpand:()=>!1,isLoading:!1,noDataMessage:"No pass-through endpoints configured"}),y&&(0,s.jsx)("div",{className:"fixed z-10 inset-0 overflow-y-auto",children:(0,s.jsxs)("div",{className:"flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0",children:[(0,s.jsx)("div",{className:"fixed inset-0 transition-opacity","aria-hidden":"true",children:(0,s.jsx)("div",{className:"absolute inset-0 bg-gray-500 opacity-75"})}),(0,s.jsx)("span",{className:"hidden sm:inline-block sm:align-middle sm:h-screen","aria-hidden":"true",children:"​"}),(0,s.jsxs)("div",{className:"inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full",children:[(0,s.jsx)("div",{className:"bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4",children:(0,s.jsx)("div",{className:"sm:flex sm:items-start",children:(0,s.jsxs)("div",{className:"mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left",children:[(0,s.jsx)("h3",{className:"text-lg leading-6 font-medium text-gray-900",children:"Delete Pass-Through Endpoint"}),(0,s.jsx)("div",{className:"mt-2",children:(0,s.jsx)("p",{className:"text-sm text-gray-500",children:"Are you sure you want to delete this pass-through endpoint? This action cannot be undone."})})]})})}),(0,s.jsxs)("div",{className:"bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse",children:[(0,s.jsx)(r.Z,{onClick:k,color:"red",className:"ml-2",children:"Delete"}),(0,s.jsx)(r.Z,{onClick:()=>{N(!1),Z(null)},children:"Cancel"})]})]})]})})]})}},12322:function(e,l,t){t.d(l,{w:function(){return o}});var s=t(57437),a=t(2265),r=t(71594),i=t(24525),n=t(19130);function o(e){let{data:l=[],columns:t,onRowClick:o,renderSubComponent:d,getRowCanExpand:c,isLoading:m=!1,loadingMessage:u="\uD83D\uDE85 Loading logs...",noDataMessage:h="No logs found"}=e,x=!!d&&!!c,p=(0,r.b7)({data:l,columns:t,...x&&{getRowCanExpand:c},getRowId:(e,l)=>{var t;return null!==(t=null==e?void 0:e.request_id)&&void 0!==t?t:String(l)},getCoreRowModel:(0,i.sC)(),...x&&{getExpandedRowModel:(0,i.rV)()}});return(0,s.jsx)("div",{className:"rounded-lg custom-border overflow-x-auto w-full max-w-full box-border",children:(0,s.jsxs)(n.iA,{className:"[&_td]:py-0.5 [&_th]:py-1 table-fixed w-full box-border",style:{minWidth:"400px"},children:[(0,s.jsx)(n.ss,{children:p.getHeaderGroups().map(e=>(0,s.jsx)(n.SC,{children:e.headers.map(e=>(0,s.jsx)(n.xs,{className:"py-1 h-8",children:e.isPlaceholder?null:(0,r.ie)(e.column.columnDef.header,e.getContext())},e.id))},e.id))}),(0,s.jsx)(n.RM,{children:m?(0,s.jsx)(n.SC,{children:(0,s.jsx)(n.pj,{colSpan:t.length,className:"h-8 text-center",children:(0,s.jsx)("div",{className:"text-center text-gray-500",children:(0,s.jsx)("p",{children:u})})})}):p.getRowModel().rows.length>0?p.getRowModel().rows.map(e=>(0,s.jsxs)(a.Fragment,{children:[(0,s.jsx)(n.SC,{className:"h-8 ".concat(x?"":"cursor-pointer hover:bg-gray-50"),onClick:()=>!x&&(null==o?void 0:o(e.original)),children:e.getVisibleCells().map(e=>(0,s.jsx)(n.pj,{className:"py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap",children:(0,r.ie)(e.column.columnDef.cell,e.getContext())},e.id))}),x&&e.getIsExpanded()&&d&&(0,s.jsx)(n.SC,{children:(0,s.jsx)(n.pj,{colSpan:e.getVisibleCells().length,className:"p-0",children:(0,s.jsx)("div",{className:"w-full max-w-full overflow-hidden box-border",children:d({row:e})})})})]},e.id)):(0,s.jsx)(n.SC,{children:(0,s.jsx)(n.pj,{colSpan:t.length,className:"h-8 text-center",children:(0,s.jsx)("div",{className:"text-center text-gray-500",children:(0,s.jsx)("p",{children:h})})})})})]})})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1789-c534ff8966aa231a.js b/litellm/proxy/_experimental/out/_next/static/chunks/1789-c534ff8966aa231a.js new file mode 100644 index 00000000000..a555310c8a2 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/1789-c534ff8966aa231a.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1789],{25512:function(e,s,l){l.d(s,{P:function(){return t.Z},Q:function(){return i.Z}});var t=l(27281),i=l(57365)},51789:function(e,s,l){l.d(s,{Z:function(){return e1}});var t=l(57437),i=l(2265),r=l(57840),n=l(51653),a=l(99376),o=l(10032),c=l(4260),d=l(5545),u=l(22116);l(25512);var m=l(78489),g=l(94789),p=l(12514),x=l(12485),h=l(18135),_=l(35242),f=l(29706),j=l(77991),y=l(21626),v=l(97214),b=l(28241),S=l(58834),Z=l(69552),w=l(71876),N=l(37592),I=l(4156),C=l(56522),k=l(19250),O=l(9114),E=l(85968);let T={google:"https://artificialanalysis.ai/img/logos/google_small.svg",microsoft:"https://upload.wikimedia.org/wikipedia/commons/a/a8/Microsoft_Azure_Logo.svg",okta:"https://www.okta.com/sites/default/files/Okta_Logo_BrightBlue_Medium.png",generic:""},L={google:{envVarMap:{google_client_id:"GOOGLE_CLIENT_ID",google_client_secret:"GOOGLE_CLIENT_SECRET"},fields:[{label:"Google Client ID",name:"google_client_id"},{label:"Google Client Secret",name:"google_client_secret"}]},microsoft:{envVarMap:{microsoft_client_id:"MICROSOFT_CLIENT_ID",microsoft_client_secret:"MICROSOFT_CLIENT_SECRET",microsoft_tenant:"MICROSOFT_TENANT"},fields:[{label:"Microsoft Client ID",name:"microsoft_client_id"},{label:"Microsoft Client Secret",name:"microsoft_client_secret"},{label:"Microsoft Tenant",name:"microsoft_tenant"}]},okta:{envVarMap:{generic_client_id:"GENERIC_CLIENT_ID",generic_client_secret:"GENERIC_CLIENT_SECRET",generic_authorization_endpoint:"GENERIC_AUTHORIZATION_ENDPOINT",generic_token_endpoint:"GENERIC_TOKEN_ENDPOINT",generic_userinfo_endpoint:"GENERIC_USERINFO_ENDPOINT"},fields:[{label:"Generic Client ID",name:"generic_client_id"},{label:"Generic Client Secret",name:"generic_client_secret"},{label:"Authorization Endpoint",name:"generic_authorization_endpoint",placeholder:"https://your-domain/authorize"},{label:"Token Endpoint",name:"generic_token_endpoint",placeholder:"https://your-domain/token"},{label:"Userinfo Endpoint",name:"generic_userinfo_endpoint",placeholder:"https://your-domain/userinfo"}]},generic:{envVarMap:{generic_client_id:"GENERIC_CLIENT_ID",generic_client_secret:"GENERIC_CLIENT_SECRET",generic_authorization_endpoint:"GENERIC_AUTHORIZATION_ENDPOINT",generic_token_endpoint:"GENERIC_TOKEN_ENDPOINT",generic_userinfo_endpoint:"GENERIC_USERINFO_ENDPOINT"},fields:[{label:"Generic Client ID",name:"generic_client_id"},{label:"Generic Client Secret",name:"generic_client_secret"},{label:"Authorization Endpoint",name:"generic_authorization_endpoint"},{label:"Token Endpoint",name:"generic_token_endpoint"},{label:"Userinfo Endpoint",name:"generic_userinfo_endpoint"}]}};var P=e=>{let{isAddSSOModalVisible:s,isInstructionsModalVisible:l,handleAddSSOOk:r,handleAddSSOCancel:n,handleShowInstructions:a,handleInstructionsOk:m,handleInstructionsCancel:g,form:p,accessToken:x,ssoConfigured:h=!1}=e,[_,f]=(0,i.useState)(!1);(0,i.useEffect)(()=>{(async()=>{if(s&&x)try{let s=await (0,k.getSSOSettings)(x);if(console.log("Raw SSO data received:",s),s&&s.values){var e,l,t,i,r,n;console.log("SSO values:",s.values),console.log("user_email from API:",s.values.user_email);let a=null;s.values.google_client_id?a="google":s.values.microsoft_client_id?a="microsoft":s.values.generic_client_id&&(a=(null===(e=s.values.generic_authorization_endpoint)||void 0===e?void 0:e.includes("okta"))||(null===(l=s.values.generic_authorization_endpoint)||void 0===l?void 0:l.includes("auth0"))?"okta":"generic");let o={};if(s.values.role_mappings){let e=s.values.role_mappings,l=e=>e&&0!==e.length?e.join(", "):"";o={use_role_mappings:!0,group_claim:e.group_claim,default_role:e.default_role||"internal_user",proxy_admin_teams:l(null===(t=e.roles)||void 0===t?void 0:t.proxy_admin),admin_viewer_teams:l(null===(i=e.roles)||void 0===i?void 0:i.proxy_admin_viewer),internal_user_teams:l(null===(r=e.roles)||void 0===r?void 0:r.internal_user),internal_viewer_teams:l(null===(n=e.roles)||void 0===n?void 0:n.internal_user_viewer)}}let c={sso_provider:a,proxy_base_url:s.values.proxy_base_url,user_email:s.values.user_email,...s.values,...o};console.log("Setting form values:",c),p.resetFields(),setTimeout(()=>{p.setFieldsValue(c),console.log("Form values set, current form values:",p.getFieldsValue())},100)}}catch(e){console.error("Failed to load SSO settings:",e)}})()},[s,x,p]);let j=async e=>{if(!x){O.Z.fromBackend("No access token available");return}try{let{proxy_admin_teams:s,admin_viewer_teams:l,internal_user_teams:t,internal_viewer_teams:i,default_role:r,group_claim:n,use_role_mappings:o,...c}=e,d={...c};if(o){let e=e=>e&&""!==e.trim()?e.split(",").map(e=>e.trim()).filter(e=>e.length>0):[];d.role_mappings={provider:"generic",group_claim:n,default_role:({internal_user_viewer:"internal_user_viewer",internal_user:"internal_user",proxy_admin_viewer:"proxy_admin_viewer",proxy_admin:"proxy_admin"})[r]||"internal_user",roles:{proxy_admin:e(s),proxy_admin_viewer:e(l),internal_user:e(t),internal_user_viewer:e(i)}}}await (0,k.updateSSOSettings)(x,d),a(e)}catch(e){O.Z.fromBackend("Failed to save SSO settings: "+(0,E.O)(e))}},y=async()=>{if(!x){O.Z.fromBackend("No access token available");return}try{await (0,k.updateSSOSettings)(x,{google_client_id:null,google_client_secret:null,microsoft_client_id:null,microsoft_client_secret:null,microsoft_tenant:null,generic_client_id:null,generic_client_secret:null,generic_authorization_endpoint:null,generic_token_endpoint:null,generic_userinfo_endpoint:null,proxy_base_url:null,user_email:null,sso_provider:null,role_mappings:null}),p.resetFields(),f(!1),r(),O.Z.success("SSO settings cleared successfully")}catch(e){console.error("Failed to clear SSO settings:",e),O.Z.fromBackend("Failed to clear SSO settings")}},v=e=>{let s=L[e];return s?s.fields.map(e=>(0,t.jsx)(o.Z.Item,{label:e.label,name:e.name,rules:[{required:!0,message:"Please enter the ".concat(e.label.toLowerCase())}],children:e.name.includes("client")?(0,t.jsx)(c.default.Password,{}):(0,t.jsx)(C.o,{placeholder:e.placeholder})},e.name)):null};return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(u.Z,{title:h?"Edit SSO Settings":"Add SSO",visible:s,width:800,footer:null,onOk:r,onCancel:n,children:(0,t.jsxs)(o.Z,{form:p,onFinish:j,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",children:[(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(o.Z.Item,{label:"SSO Provider",name:"sso_provider",rules:[{required:!0,message:"Please select an SSO provider"}],children:(0,t.jsx)(N.default,{children:Object.entries(T).map(e=>{let[s,l]=e;return(0,t.jsx)(N.default.Option,{value:s,children:(0,t.jsxs)("div",{style:{display:"flex",alignItems:"center",padding:"4px 0"},children:[l&&(0,t.jsx)("img",{src:l,alt:s,style:{height:24,width:24,marginRight:12,objectFit:"contain"}}),(0,t.jsxs)("span",{children:["okta"===s.toLowerCase()?"Okta / Auth0":s.charAt(0).toUpperCase()+s.slice(1)," ","SSO"]})]})},s)})})}),(0,t.jsx)(o.Z.Item,{noStyle:!0,shouldUpdate:(e,s)=>e.sso_provider!==s.sso_provider,children:e=>{let{getFieldValue:s}=e,l=s("sso_provider");return l?v(l):null}}),(0,t.jsx)(o.Z.Item,{label:"Proxy Admin Email",name:"user_email",rules:[{required:!0,message:"Please enter the email of the proxy admin"}],children:(0,t.jsx)(C.o,{})}),(0,t.jsx)(o.Z.Item,{label:"Proxy Base URL",name:"proxy_base_url",normalize:e=>null==e?void 0:e.trim(),rules:[{required:!0,message:"Please enter the proxy base url"},{pattern:/^https?:\/\/.+/,message:"URL must start with http:// or https://"},{validator:(e,s)=>s&&/^https?:\/\/.+/.test(s)&&s.endsWith("/")?Promise.reject("URL must not end with a trailing slash"):Promise.resolve()}],children:(0,t.jsx)(C.o,{placeholder:"https://example.com"})}),(0,t.jsx)(o.Z.Item,{noStyle:!0,shouldUpdate:(e,s)=>e.sso_provider!==s.sso_provider,children:e=>{let{getFieldValue:s}=e,l=s("sso_provider");return"okta"===l||"generic"===l?(0,t.jsx)(o.Z.Item,{label:"Use Role Mappings",name:"use_role_mappings",valuePropName:"checked",children:(0,t.jsx)(I.Z,{})}):null}}),(0,t.jsx)(o.Z.Item,{noStyle:!0,shouldUpdate:(e,s)=>e.use_role_mappings!==s.use_role_mappings,children:e=>{let{getFieldValue:s}=e;return s("use_role_mappings")?(0,t.jsx)(o.Z.Item,{label:"Group Claim",name:"group_claim",rules:[{required:!0,message:"Please enter the group claim"}],children:(0,t.jsx)(C.o,{})}):null}}),(0,t.jsx)(o.Z.Item,{noStyle:!0,shouldUpdate:(e,s)=>e.use_role_mappings!==s.use_role_mappings,children:e=>{let{getFieldValue:s}=e;return s("use_role_mappings")?(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(o.Z.Item,{label:"Default Role",name:"default_role",initialValue:"Internal User",children:(0,t.jsxs)(N.default,{children:[(0,t.jsx)(N.default.Option,{value:"internal_user_viewer",children:"Internal Viewer"}),(0,t.jsx)(N.default.Option,{value:"internal_user",children:"Internal User"}),(0,t.jsx)(N.default.Option,{value:"proxy_admin_viewer",children:"Admin Viewer"}),(0,t.jsx)(N.default.Option,{value:"proxy_admin",children:"Proxy Admin"})]})}),(0,t.jsx)(o.Z.Item,{label:"Proxy Admin Teams",name:"proxy_admin_teams",children:(0,t.jsx)(C.o,{})}),(0,t.jsx)(o.Z.Item,{label:"Admin Viewer Teams",name:"admin_viewer_teams",children:(0,t.jsx)(C.o,{})}),(0,t.jsx)(o.Z.Item,{label:"Internal User Teams",name:"internal_user_teams",children:(0,t.jsx)(C.o,{})}),(0,t.jsx)(o.Z.Item,{label:"Internal Viewer Teams",name:"internal_viewer_teams",children:(0,t.jsx)(C.o,{})})]}):null}})]}),(0,t.jsxs)("div",{style:{textAlign:"right",marginTop:"10px",display:"flex",justifyContent:"flex-end",alignItems:"center",gap:"8px"},children:[h&&(0,t.jsx)(d.ZP,{onClick:()=>f(!0),style:{backgroundColor:"#6366f1",borderColor:"#6366f1",color:"white"},onMouseEnter:e=>{e.currentTarget.style.backgroundColor="#5558eb",e.currentTarget.style.borderColor="#5558eb"},onMouseLeave:e=>{e.currentTarget.style.backgroundColor="#6366f1",e.currentTarget.style.borderColor="#6366f1"},children:"Clear"}),(0,t.jsx)(d.ZP,{htmlType:"submit",children:"Save"})]})]})}),(0,t.jsxs)(u.Z,{title:"Confirm Clear SSO Settings",visible:_,onOk:y,onCancel:()=>f(!1),okText:"Yes, Clear",cancelText:"Cancel",okButtonProps:{danger:!0,style:{backgroundColor:"#dc2626",borderColor:"#dc2626"}},children:[(0,t.jsx)("p",{children:"Are you sure you want to clear all SSO settings? This action cannot be undone."}),(0,t.jsx)("p",{children:"Users will no longer be able to login using SSO after this change."})]}),(0,t.jsxs)(u.Z,{title:"SSO Setup Instructions",visible:l,width:800,footer:null,onOk:m,onCancel:g,children:[(0,t.jsx)("p",{children:"Follow these steps to complete the SSO setup:"}),(0,t.jsx)(C.x,{className:"mt-2",children:"1. DO NOT Exit this TAB"}),(0,t.jsx)(C.x,{className:"mt-2",children:"2. Open a new tab, visit your proxy base url"}),(0,t.jsx)(C.x,{className:"mt-2",children:"3. Confirm your SSO is configured correctly and you can login on the new Tab"}),(0,t.jsx)(C.x,{className:"mt-2",children:"4. If Step 3 is successful, you can close this tab"}),(0,t.jsx)("div",{style:{textAlign:"right",marginTop:"10px"},children:(0,t.jsx)(d.ZP,{onClick:m,children:"Done"})})]})]})},A=l(67982),U=l(67101),R=l(84264),M=l(49566),z=l(96761),F=l(29233),G=l(62272),D=l(23639),B=l(92403),V=l(29271),q=l(34419),Y=e=>{let{accessToken:s,userID:l,proxySettings:r}=e,[n]=o.Z.useForm(),[a,c]=(0,i.useState)(!1),[d,u]=(0,i.useState)(null),[x,h]=(0,i.useState)("");(0,i.useEffect)(()=>{let e="";h(r&&r.PROXY_BASE_URL&&void 0!==r.PROXY_BASE_URL?r.PROXY_BASE_URL:window.location.origin)},[r]);let _="".concat(x,"/scim/v2"),f=async e=>{if(!s||!l){O.Z.fromBackend("You need to be logged in to create a SCIM token");return}try{c(!0);let t={key_alias:e.key_alias||"SCIM Access Token",team_id:null,models:[],allowed_routes:["/scim/*"]},i=await (0,k.keyCreateCall)(s,l,t);u(i),O.Z.success("SCIM token created successfully")}catch(e){console.error("Error creating SCIM token:",e),O.Z.fromBackend("Failed to create SCIM token: "+(0,E.O)(e))}finally{c(!1)}};return(0,t.jsx)(U.Z,{numItems:1,children:(0,t.jsxs)(p.Z,{children:[(0,t.jsx)("div",{className:"flex items-center mb-4",children:(0,t.jsx)(z.Z,{children:"SCIM Configuration"})}),(0,t.jsx)(R.Z,{className:"text-gray-600",children:"System for Cross-domain Identity Management (SCIM) allows you to automatically provision and manage users and groups in LiteLLM."}),(0,t.jsx)(A.Z,{}),(0,t.jsxs)("div",{className:"space-y-8",children:[(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"flex items-center mb-2",children:[(0,t.jsx)("div",{className:"flex items-center justify-center w-6 h-6 rounded-full bg-blue-100 text-blue-700 mr-2",children:"1"}),(0,t.jsxs)(z.Z,{className:"text-lg flex items-center",children:[(0,t.jsx)(G.Z,{className:"h-5 w-5 mr-2"}),"SCIM Tenant URL"]})]}),(0,t.jsx)(R.Z,{className:"text-gray-600 mb-3",children:"Use this URL in your identity provider SCIM integration settings."}),(0,t.jsxs)("div",{className:"flex items-center",children:[(0,t.jsx)(M.Z,{value:_,disabled:!0,className:"flex-grow"}),(0,t.jsx)(F.CopyToClipboard,{text:_,onCopy:()=>O.Z.success("URL copied to clipboard"),children:(0,t.jsxs)(m.Z,{variant:"primary",className:"ml-2 flex items-center",children:[(0,t.jsx)(D.Z,{className:"h-4 w-4 mr-1"}),"Copy"]})})]})]}),(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"flex items-center mb-2",children:[(0,t.jsx)("div",{className:"flex items-center justify-center w-6 h-6 rounded-full bg-blue-100 text-blue-700 mr-2",children:"2"}),(0,t.jsxs)(z.Z,{className:"text-lg flex items-center",children:[(0,t.jsx)(B.Z,{className:"h-5 w-5 mr-2"}),"Authentication Token"]})]}),(0,t.jsx)(g.Z,{title:"Using SCIM",color:"blue",className:"mb-4",children:"You need a SCIM token to authenticate with the SCIM API. Create one below and use it in your SCIM provider configuration."}),d?(0,t.jsxs)(p.Z,{className:"border border-yellow-300 bg-yellow-50",children:[(0,t.jsxs)("div",{className:"flex items-center mb-2 text-yellow-800",children:[(0,t.jsx)(V.Z,{className:"h-5 w-5 mr-2"}),(0,t.jsx)(z.Z,{className:"text-lg text-yellow-800",children:"Your SCIM Token"})]}),(0,t.jsx)(R.Z,{className:"text-yellow-800 mb-4 font-medium",children:"Make sure to copy this token now. You will not be able to see it again."}),(0,t.jsxs)("div",{className:"flex items-center",children:[(0,t.jsx)(M.Z,{value:d.key,className:"flex-grow mr-2 bg-white",type:"password",disabled:!0}),(0,t.jsx)(F.CopyToClipboard,{text:d.key,onCopy:()=>O.Z.success("Token copied to clipboard"),children:(0,t.jsxs)(m.Z,{variant:"primary",className:"flex items-center",children:[(0,t.jsx)(D.Z,{className:"h-4 w-4 mr-1"}),"Copy"]})})]}),(0,t.jsxs)(m.Z,{className:"mt-4 flex items-center",variant:"secondary",onClick:()=>u(null),children:[(0,t.jsx)(q.Z,{className:"h-4 w-4 mr-1"}),"Create Another Token"]})]}):(0,t.jsx)("div",{className:"bg-gray-50 p-4 rounded-lg",children:(0,t.jsxs)(o.Z,{form:n,onFinish:f,layout:"vertical",children:[(0,t.jsx)(o.Z.Item,{name:"key_alias",label:"Token Name",rules:[{required:!0,message:"Please enter a name for your token"}],children:(0,t.jsx)(M.Z,{placeholder:"SCIM Access Token"})}),(0,t.jsx)(o.Z.Item,{children:(0,t.jsxs)(m.Z,{variant:"primary",type:"submit",loading:a,className:"flex items-center",children:[(0,t.jsx)(B.Z,{className:"h-4 w-4 mr-1"}),"Create SCIM Token"]})})]})})]})]})]})})},K=e=>{let{accessToken:s,onSuccess:l}=e,[r]=o.Z.useForm(),[n,a]=(0,i.useState)(!1);(0,i.useEffect)(()=>{(async()=>{if(s)try{let e=await (0,k.getSSOSettings)(s);if(e&&e.values){let s=e.values.ui_access_mode,l={};s&&"object"==typeof s?l={ui_access_mode_type:s.type,restricted_sso_group:s.restricted_sso_group,sso_group_jwt_field:s.sso_group_jwt_field}:"string"==typeof s&&(l={ui_access_mode_type:s,restricted_sso_group:e.values.restricted_sso_group,sso_group_jwt_field:e.values.team_ids_jwt_field||e.values.sso_group_jwt_field}),r.setFieldsValue(l)}}catch(e){console.error("Failed to load UI access settings:",e)}})()},[s,r]);let c=async e=>{if(!s){O.Z.fromBackend("No access token available");return}a(!0);try{let t;t="all_authenticated_users"===e.ui_access_mode_type?{ui_access_mode:"none"}:{ui_access_mode:{type:e.ui_access_mode_type,restricted_sso_group:e.restricted_sso_group,sso_group_jwt_field:e.sso_group_jwt_field}},await (0,k.updateSSOSettings)(s,t),l()}catch(e){console.error("Failed to save UI access settings:",e),O.Z.fromBackend("Failed to save UI access settings")}finally{a(!1)}};return(0,t.jsxs)("div",{style:{padding:"16px"},children:[(0,t.jsx)("div",{style:{marginBottom:"16px"},children:(0,t.jsx)(C.x,{style:{fontSize:"14px",color:"#6b7280"},children:"Configure who can access the UI interface and how group information is extracted from JWT tokens."})}),(0,t.jsxs)(o.Z,{form:r,onFinish:c,layout:"vertical",children:[(0,t.jsx)(o.Z.Item,{label:"UI Access Mode",name:"ui_access_mode_type",tooltip:"Controls who can access the UI interface",children:(0,t.jsxs)(N.default,{placeholder:"Select access mode",children:[(0,t.jsx)(N.default.Option,{value:"all_authenticated_users",children:"All Authenticated Users"}),(0,t.jsx)(N.default.Option,{value:"restricted_sso_group",children:"Restricted SSO Group"})]})}),(0,t.jsx)(o.Z.Item,{noStyle:!0,shouldUpdate:(e,s)=>e.ui_access_mode_type!==s.ui_access_mode_type,children:e=>{let{getFieldValue:s}=e;return"restricted_sso_group"===s("ui_access_mode_type")?(0,t.jsx)(o.Z.Item,{label:"Restricted SSO Group",name:"restricted_sso_group",rules:[{required:!0,message:"Please enter the restricted SSO group"}],children:(0,t.jsx)(C.o,{placeholder:"ui-access-group"})}):null}}),(0,t.jsx)(o.Z.Item,{label:"SSO Group JWT Field",name:"sso_group_jwt_field",tooltip:"JWT field name that contains team/group information. Use dot notation to access nested fields.",children:(0,t.jsx)(C.o,{placeholder:"groups"})}),(0,t.jsx)("div",{style:{textAlign:"right",marginTop:"16px"},children:(0,t.jsx)(d.ZP,{type:"primary",htmlType:"submit",loading:n,style:{backgroundColor:"#6366f1",borderColor:"#6366f1"},children:"Update UI Access Control"})})]})]})},H=l(12363),W=l(55584),Q=l(29827),J=l(21770),X=l(90246);let $=(0,X.n)("uiSettings"),ee=e=>{let s=(0,Q.NL)();return(0,J.D)({mutationFn:async s=>{if(!e)throw Error("Access token is required");return(0,k.updateUiSettings)(e,s)},onSuccess:()=>{s.invalidateQueries({queryKey:$.all})}})};var es=l(39760),el=l(1633);let et={"api-keys":"Manage virtual keys for API access and authentication","llm-playground":"Interactive playground for testing LLM requests",models:"Configure and manage LLM models and endpoints",agents:"Create and manage AI agents","mcp-servers":"Configure Model Context Protocol servers",guardrails:"Set up content moderation and safety guardrails",policies:"Define access control and usage policies","search-tools":"Configure RAG search and retrieval tools","vector-stores":"Manage vector databases for embeddings",new_usage:"View usage analytics and metrics",logs:"Access request and response logs",users:"Manage internal user accounts and permissions",teams:"Create and manage teams for access control",organizations:"Manage organizations and their members",budgets:"Set and monitor spending budgets",api_ref:"Browse API documentation and endpoints","model-hub-table":"Explore available AI models and providers","learning-resources":"Access tutorials and documentation",caching:"Configure response caching settings","transform-request":"Set up request transformation rules","cost-tracking":"Track and analyze API costs","ui-theme":"Customize dashboard appearance","tag-management":"Organize resources with tags",prompts:"Manage and version prompt templates","claude-code-plugins":"Configure Claude Code plugins",usage:"View legacy usage dashboard","router-settings":"Configure routing and load balancing settings","logging-and-alerts":"Set up logging and alert configurations","admin-panel":"Access admin panel and settings"};var ei=l(20347);let er=e=>!e||0===e.length||e.some(e=>ei.lo.includes(e)),en=()=>{let e=[];return el.j.forEach(s=>{s.items.forEach(l=>{if(l.page&&"tools"!==l.page&&"experimental"!==l.page&&"settings"!==l.page&&er(l.roles)){let t="string"==typeof l.label?l.label:l.key;e.push({page:l.page,label:t,group:s.groupLabel,description:et[l.page]||"No description available"})}if(l.children){let t="string"==typeof l.label?l.label:l.key;l.children.forEach(l=>{if(er(l.roles)){let i="string"==typeof l.label?l.label:l.key;e.push({page:l.page,label:i,group:"".concat(s.groupLabel," > ").concat(t),description:et[l.page]||"No description available"})}})}})}),e};var ea=l(58760),eo=l(3810),ec=l(44851);function ed(e){let{enabledPagesInternalUsers:s,enabledPagesPropertyDescription:l,isUpdating:n,onUpdate:a}=e,o=null!=s,c=(0,i.useMemo)(()=>en(),[]),u=(0,i.useMemo)(()=>{let e={};return c.forEach(s=>{e[s.group]||(e[s.group]=[]),e[s.group].push(s)}),e},[c]),[m,g]=(0,i.useState)(s||[]);return(0,i.useMemo)(()=>{s?g(s):g([])},[s]),(0,t.jsxs)(ea.Z,{direction:"vertical",size:"middle",style:{width:"100%"},children:[(0,t.jsxs)(ea.Z,{direction:"vertical",size:4,children:[(0,t.jsxs)(ea.Z,{align:"center",children:[(0,t.jsx)(r.default.Text,{strong:!0,children:"Internal User Page Visibility"}),!o&&(0,t.jsx)(eo.Z,{color:"default",style:{marginLeft:"8px"},children:"Not set (all pages visible)"}),o&&(0,t.jsxs)(eo.Z,{color:"blue",style:{marginLeft:"8px"},children:[m.length," page",1!==m.length?"s":""," selected"]})]}),l&&(0,t.jsx)(r.default.Text,{type:"secondary",children:l}),(0,t.jsx)(r.default.Text,{type:"secondary",style:{fontSize:"12px",fontStyle:"italic"},children:"By default, all pages are visible to internal users. Select specific pages to restrict visibility."}),(0,t.jsx)(r.default.Text,{type:"secondary",style:{fontSize:"12px",color:"#8b5cf6"},children:"Note: Only pages accessible to internal user roles are shown here. Admin-only pages are excluded as they cannot be made visible to internal users regardless of this setting."})]}),(0,t.jsx)(ec.default,{items:[{key:"page-visibility",label:"Configure Page Visibility",children:(0,t.jsxs)(ea.Z,{direction:"vertical",size:"middle",style:{width:"100%"},children:[(0,t.jsx)(I.Z.Group,{value:m,onChange:g,style:{width:"100%"},children:(0,t.jsx)(ea.Z,{direction:"vertical",size:"middle",style:{width:"100%"},children:Object.entries(u).map(e=>{let[s,l]=e;return(0,t.jsxs)("div",{children:[(0,t.jsx)(r.default.Text,{strong:!0,style:{fontSize:"11px",color:"#6b7280",letterSpacing:"0.05em",display:"block",marginBottom:"8px"},children:s}),(0,t.jsx)(ea.Z,{direction:"vertical",size:"small",style:{marginLeft:"16px",width:"100%"},children:l.map(e=>(0,t.jsx)("div",{style:{marginBottom:"4px"},children:(0,t.jsx)(I.Z,{value:e.page,children:(0,t.jsxs)(ea.Z,{direction:"vertical",size:0,children:[(0,t.jsx)(r.default.Text,{children:e.label}),(0,t.jsx)(r.default.Text,{type:"secondary",style:{fontSize:"12px"},children:e.description})]})})},e.page))})]},s)})})}),(0,t.jsxs)(ea.Z,{children:[(0,t.jsx)(d.ZP,{type:"primary",onClick:()=>{a({enabled_ui_pages_internal_users:m.length>0?m:null})},loading:n,disabled:n,children:"Save Page Visibility Settings"}),o&&(0,t.jsx)(d.ZP,{onClick:()=>{g([]),a({enabled_ui_pages_internal_users:null})},loading:n,disabled:n,children:"Reset to Default (All Pages)"})]})]})}]})]})}var eu=l(5945),em=l(50337),eg=l(63709),ep=l(23496);function ex(){var e,s,l,i,a,o;let{accessToken:c}=(0,es.Z)(),{data:d,isLoading:u,isError:m,error:g}=(0,W.L)(),{mutate:p,isPending:x,error:h}=ee(c),_=null==d?void 0:d.field_schema,f=null==_?void 0:null===(e=_.properties)||void 0===e?void 0:e.disable_model_add_for_internal_users,j=null==_?void 0:null===(s=_.properties)||void 0===s?void 0:s.disable_team_admin_delete_team_user,y=null==_?void 0:null===(l=_.properties)||void 0===l?void 0:l.enabled_ui_pages_internal_users,v=null!==(i=null==d?void 0:d.values)&&void 0!==i?i:{},b=!!v.disable_model_add_for_internal_users,S=!!v.disable_team_admin_delete_team_user;return(0,t.jsx)(eu.Z,{title:"UI Settings",children:u?(0,t.jsx)(em.Z,{active:!0}):m?(0,t.jsx)(n.Z,{type:"error",message:"Could not load UI settings",description:g instanceof Error?g.message:void 0}):(0,t.jsxs)(ea.Z,{direction:"vertical",size:"large",style:{width:"100%"},children:[(null==_?void 0:_.description)&&(0,t.jsx)(r.default.Paragraph,{style:{marginBottom:0},children:_.description}),h&&(0,t.jsx)(n.Z,{type:"error",message:"Could not update UI settings",description:h instanceof Error?h.message:void 0}),(0,t.jsxs)(ea.Z,{align:"start",size:"middle",children:[(0,t.jsx)(eg.Z,{checked:b,disabled:x,loading:x,onChange:e=>{p({disable_model_add_for_internal_users:e},{onSuccess:()=>{O.Z.success("UI settings updated successfully")},onError:e=>{O.Z.fromBackend(e)}})},"aria-label":null!==(a=null==f?void 0:f.description)&&void 0!==a?a:"Disable model add for internal users"}),(0,t.jsxs)(ea.Z,{direction:"vertical",size:4,children:[(0,t.jsx)(r.default.Text,{strong:!0,children:"Disable model add for internal users"}),(null==f?void 0:f.description)&&(0,t.jsx)(r.default.Text,{type:"secondary",children:f.description})]})]}),(0,t.jsxs)(ea.Z,{align:"start",size:"middle",children:[(0,t.jsx)(eg.Z,{checked:S,disabled:x,loading:x,onChange:e=>{p({disable_team_admin_delete_team_user:e},{onSuccess:()=>{O.Z.success("UI settings updated successfully")},onError:e=>{O.Z.fromBackend(e)}})},"aria-label":null!==(o=null==j?void 0:j.description)&&void 0!==o?o:"Disable team admin delete team user"}),(0,t.jsxs)(ea.Z,{direction:"vertical",size:4,children:[(0,t.jsx)(r.default.Text,{strong:!0,children:"Disable team admin delete team user"}),(null==j?void 0:j.description)&&(0,t.jsx)(r.default.Text,{type:"secondary",children:j.description})]})]}),(0,t.jsx)(ep.Z,{}),(0,t.jsx)(ed,{enabledPagesInternalUsers:v.enabled_ui_pages_internal_users,enabledPagesPropertyDescription:null==y?void 0:y.description,isUpdating:x,onUpdate:e=>{p(e,{onSuccess:()=>{O.Z.success("Page visibility settings updated successfully")},onError:e=>{O.Z.fromBackend(e)}})}})]})})}var eh=l(11713);let e_=(0,X.n)("sso"),ef=()=>{let{accessToken:e,userId:s,userRole:l}=(0,es.Z)();return(0,eh.a)({queryKey:e_.detail("settings"),queryFn:async()=>await (0,k.getSSOSettings)(e),enabled:!!(e&&s&&l)})};var ej=l(76188),ey=l(88906),ev=l(15868),eb=l(18930);let eS={google:"https://artificialanalysis.ai/img/logos/google_small.svg",microsoft:"https://upload.wikimedia.org/wikipedia/commons/a/a8/Microsoft_Azure_Logo.svg",okta:"https://www.okta.com/sites/default/files/Okta_Logo_BrightBlue_Medium.png",generic:""},eZ={google:"Google SSO",microsoft:"Microsoft SSO",okta:"Okta / Auth0 SSO",generic:"Generic SSO"},ew={internal_user_viewer:"Internal Viewer",internal_user:"Internal User",proxy_admin_viewer:"Proxy Admin Viewer",proxy_admin:"Proxy Admin"};var eN=l(31283);let eI={google:{envVarMap:{google_client_id:"GOOGLE_CLIENT_ID",google_client_secret:"GOOGLE_CLIENT_SECRET"},fields:[{label:"Google Client ID",name:"google_client_id"},{label:"Google Client Secret",name:"google_client_secret"}]},microsoft:{envVarMap:{microsoft_client_id:"MICROSOFT_CLIENT_ID",microsoft_client_secret:"MICROSOFT_CLIENT_SECRET",microsoft_tenant:"MICROSOFT_TENANT"},fields:[{label:"Microsoft Client ID",name:"microsoft_client_id"},{label:"Microsoft Client Secret",name:"microsoft_client_secret"},{label:"Microsoft Tenant",name:"microsoft_tenant"}]},okta:{envVarMap:{generic_client_id:"GENERIC_CLIENT_ID",generic_client_secret:"GENERIC_CLIENT_SECRET",generic_authorization_endpoint:"GENERIC_AUTHORIZATION_ENDPOINT",generic_token_endpoint:"GENERIC_TOKEN_ENDPOINT",generic_userinfo_endpoint:"GENERIC_USERINFO_ENDPOINT"},fields:[{label:"Generic Client ID",name:"generic_client_id"},{label:"Generic Client Secret",name:"generic_client_secret"},{label:"Authorization Endpoint",name:"generic_authorization_endpoint",placeholder:"https://your-domain/authorize"},{label:"Token Endpoint",name:"generic_token_endpoint",placeholder:"https://your-domain/token"},{label:"Userinfo Endpoint",name:"generic_userinfo_endpoint",placeholder:"https://your-domain/userinfo"}]},generic:{envVarMap:{generic_client_id:"GENERIC_CLIENT_ID",generic_client_secret:"GENERIC_CLIENT_SECRET",generic_authorization_endpoint:"GENERIC_AUTHORIZATION_ENDPOINT",generic_token_endpoint:"GENERIC_TOKEN_ENDPOINT",generic_userinfo_endpoint:"GENERIC_USERINFO_ENDPOINT"},fields:[{label:"Generic Client ID",name:"generic_client_id"},{label:"Generic Client Secret",name:"generic_client_secret"},{label:"Authorization Endpoint",name:"generic_authorization_endpoint"},{label:"Token Endpoint",name:"generic_token_endpoint"},{label:"Userinfo Endpoint",name:"generic_userinfo_endpoint"}]}},eC=e=>{let s=eI[e];return s?s.fields.map(e=>(0,t.jsx)(o.Z.Item,{label:e.label,name:e.name,rules:[{required:!0,message:"Please enter the ".concat(e.label.toLowerCase())}],children:e.name.includes("client")?(0,t.jsx)(c.default.Password,{}):(0,t.jsx)(eN.o,{placeholder:e.placeholder})},e.name)):null};var ek=e=>{let{form:s,onFormSubmit:l}=e;return(0,t.jsx)("div",{children:(0,t.jsxs)(o.Z,{form:s,onFinish:l,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",children:[(0,t.jsx)(o.Z.Item,{label:"SSO Provider",name:"sso_provider",rules:[{required:!0,message:"Please select an SSO provider"}],children:(0,t.jsx)(N.default,{children:Object.entries(eS).map(e=>{let[s,l]=e;return(0,t.jsx)(N.default.Option,{value:s,children:(0,t.jsxs)("div",{style:{display:"flex",alignItems:"center",padding:"4px 0"},children:[l&&(0,t.jsx)("img",{src:l,alt:s,style:{height:24,width:24,marginRight:12,objectFit:"contain"}}),(0,t.jsx)("span",{children:eZ[s]||s.charAt(0).toUpperCase()+s.slice(1)+" SSO"})]})},s)})})}),(0,t.jsx)(o.Z.Item,{noStyle:!0,shouldUpdate:(e,s)=>e.sso_provider!==s.sso_provider,children:e=>{let{getFieldValue:s}=e,l=s("sso_provider");return l?eC(l):null}}),(0,t.jsx)(o.Z.Item,{label:"Proxy Admin Email",name:"user_email",rules:[{required:!0,message:"Please enter the email of the proxy admin"}],children:(0,t.jsx)(eN.o,{})}),(0,t.jsx)(o.Z.Item,{label:"Proxy Base URL",name:"proxy_base_url",normalize:e=>null==e?void 0:e.trim(),rules:[{required:!0,message:"Please enter the proxy base url"},{pattern:/^https?:\/\/.+/,message:"URL must start with http:// or https://"},{validator:(e,s)=>s&&/^https?:\/\/.+/.test(s)&&s.endsWith("/")?Promise.reject("URL must not end with a trailing slash"):Promise.resolve()}],children:(0,t.jsx)(eN.o,{placeholder:"https://example.com"})}),(0,t.jsx)(o.Z.Item,{noStyle:!0,shouldUpdate:(e,s)=>e.sso_provider!==s.sso_provider,children:e=>{let{getFieldValue:s}=e,l=s("sso_provider");return"okta"===l||"generic"===l?(0,t.jsx)(o.Z.Item,{label:"Use Role Mappings",name:"use_role_mappings",valuePropName:"checked",children:(0,t.jsx)(I.Z,{})}):null}}),(0,t.jsx)(o.Z.Item,{noStyle:!0,shouldUpdate:(e,s)=>e.use_role_mappings!==s.use_role_mappings||e.sso_provider!==s.sso_provider,children:e=>{let{getFieldValue:s}=e,l=s("use_role_mappings"),i=s("sso_provider");return l&&("okta"===i||"generic"===i)?(0,t.jsx)(o.Z.Item,{label:"Group Claim",name:"group_claim",rules:[{required:!0,message:"Please enter the group claim"}],children:(0,t.jsx)(eN.o,{})}):null}}),(0,t.jsx)(o.Z.Item,{noStyle:!0,shouldUpdate:(e,s)=>e.use_role_mappings!==s.use_role_mappings||e.sso_provider!==s.sso_provider,children:e=>{let{getFieldValue:s}=e,l=s("use_role_mappings"),i=s("sso_provider");return l&&("okta"===i||"generic"===i)?(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(o.Z.Item,{label:"Default Role",name:"default_role",initialValue:"Internal User",children:(0,t.jsxs)(N.default,{children:[(0,t.jsx)(N.default.Option,{value:"internal_user_viewer",children:"Internal Viewer"}),(0,t.jsx)(N.default.Option,{value:"internal_user",children:"Internal User"}),(0,t.jsx)(N.default.Option,{value:"proxy_admin_viewer",children:"Admin Viewer"}),(0,t.jsx)(N.default.Option,{value:"proxy_admin",children:"Proxy Admin"})]})}),(0,t.jsx)(o.Z.Item,{label:"Proxy Admin Teams",name:"proxy_admin_teams",children:(0,t.jsx)(eN.o,{})}),(0,t.jsx)(o.Z.Item,{label:"Admin Viewer Teams",name:"admin_viewer_teams",children:(0,t.jsx)(eN.o,{})}),(0,t.jsx)(o.Z.Item,{label:"Internal User Teams",name:"internal_user_teams",children:(0,t.jsx)(eN.o,{})}),(0,t.jsx)(o.Z.Item,{label:"Internal Viewer Teams",name:"internal_viewer_teams",children:(0,t.jsx)(eN.o,{})})]}):null}})]})})};let eO=()=>{let{accessToken:e}=(0,es.Z)();return(0,J.D)({mutationFn:async s=>{if(!e)throw Error("Access token is required");return await (0,k.updateSSOSettings)(e,s)}})},eE=e=>{let{proxy_admin_teams:s,admin_viewer_teams:l,internal_user_teams:t,internal_viewer_teams:i,default_role:r,group_claim:n,use_role_mappings:a,...o}=e,c={...o};if(a){let e=e=>e&&""!==e.trim()?e.split(",").map(e=>e.trim()).filter(e=>e.length>0):[];c.role_mappings={provider:"generic",group_claim:n,default_role:({internal_user_viewer:"internal_user_viewer",internal_user:"internal_user",proxy_admin_viewer:"proxy_admin_viewer",proxy_admin:"proxy_admin"})[r]||"internal_user",roles:{proxy_admin:e(s),proxy_admin_viewer:e(l),internal_user:e(t),internal_user_viewer:e(i)}}}return c},eT=e=>{if(e.google_client_id)return"google";if(e.microsoft_client_id)return"microsoft";if(e.generic_client_id){var s,l;return(null===(s=e.generic_authorization_endpoint)||void 0===s?void 0:s.includes("okta"))||(null===(l=e.generic_authorization_endpoint)||void 0===l?void 0:l.includes("auth0"))?"okta":"generic"}return null};var eL=e=>{let{isVisible:s,onCancel:l,onSuccess:i}=e,[r]=o.Z.useForm(),{mutateAsync:n,isPending:a}=eO(),c=async e=>{let s=eE(e);await n(s,{onSuccess:()=>{O.Z.success("SSO settings added successfully"),i()},onError:e=>{O.Z.fromBackend("Failed to save SSO settings: "+(0,E.O)(e))}})},m=()=>{r.resetFields(),l()};return(0,t.jsx)(u.Z,{title:"Add SSO",open:s,width:800,footer:(0,t.jsxs)(ea.Z,{children:[(0,t.jsx)(d.ZP,{onClick:m,disabled:a,children:"Cancel"}),(0,t.jsx)(d.ZP,{loading:a,onClick:()=>r.submit(),children:a?"Adding...":"Add SSO"})]}),onCancel:m,children:(0,t.jsx)(ek,{form:r,onFormSubmit:c})})},eP=l(21609),eA=e=>{let{isVisible:s,onCancel:l,onSuccess:i}=e,{data:r}=ef(),{mutateAsync:n,isPending:a}=eO(),o=async()=>{await n({google_client_id:null,google_client_secret:null,microsoft_client_id:null,microsoft_client_secret:null,microsoft_tenant:null,generic_client_id:null,generic_client_secret:null,generic_authorization_endpoint:null,generic_token_endpoint:null,generic_userinfo_endpoint:null,proxy_base_url:null,user_email:null,sso_provider:null,role_mappings:null},{onSuccess:()=>{O.Z.success("SSO settings cleared successfully"),l(),i()},onError:e=>{O.Z.fromBackend("Failed to clear SSO settings: "+(0,E.O)(e))}})};return(0,t.jsx)(eP.Z,{isOpen:s,title:"Confirm Clear SSO Settings",alertMessage:"This action cannot be undone.",message:"Are you sure you want to clear all SSO settings? Users will no longer be able to login using SSO after this change.",resourceInformationTitle:"SSO Settings",resourceInformation:[{label:"Provider",value:(null==r?void 0:r.values)&&eT(null==r?void 0:r.values)||"Generic"}],onCancel:l,onOk:o,confirmLoading:a})},eU=e=>{let{isVisible:s,onCancel:l,onSuccess:r}=e,[n]=o.Z.useForm(),a=ef(),{mutateAsync:c,isPending:m}=eO();(0,i.useEffect)(()=>{if(s&&a.data&&a.data.values){var e,l,t,i,r,o;let s=a.data;console.log("Raw SSO data received:",s),console.log("SSO values:",s.values),console.log("user_email from API:",s.values.user_email);let c=null;s.values.google_client_id?c="google":s.values.microsoft_client_id?c="microsoft":s.values.generic_client_id&&(c=(null===(e=s.values.generic_authorization_endpoint)||void 0===e?void 0:e.includes("okta"))||(null===(l=s.values.generic_authorization_endpoint)||void 0===l?void 0:l.includes("auth0"))?"okta":"generic");let d={};if(s.values.role_mappings){let e=s.values.role_mappings,l=e=>e&&0!==e.length?e.join(", "):"";d={use_role_mappings:!0,group_claim:e.group_claim,default_role:e.default_role||"internal_user",proxy_admin_teams:l(null===(t=e.roles)||void 0===t?void 0:t.proxy_admin),admin_viewer_teams:l(null===(i=e.roles)||void 0===i?void 0:i.proxy_admin_viewer),internal_user_teams:l(null===(r=e.roles)||void 0===r?void 0:r.internal_user),internal_viewer_teams:l(null===(o=e.roles)||void 0===o?void 0:o.internal_user_viewer)}}let u={sso_provider:c,...s.values,...d};console.log("Setting form values:",u),n.resetFields(),setTimeout(()=>{n.setFieldsValue(u),console.log("Form values set, current form values:",n.getFieldsValue())},100)}},[s,a.data,n]);let g=async e=>{try{let s=eE(e);await c(s,{onSuccess:()=>{O.Z.success("SSO settings updated successfully"),r()},onError:e=>{O.Z.fromBackend("Failed to save SSO settings: "+(0,E.O)(e))}})}catch(e){O.Z.fromBackend("Failed to process SSO settings: "+(0,E.O)(e))}},p=()=>{n.resetFields(),l()};return(0,t.jsx)(u.Z,{title:"Edit SSO Settings",open:s,width:800,footer:(0,t.jsxs)(ea.Z,{children:[(0,t.jsx)(d.ZP,{onClick:p,disabled:m,children:"Cancel"}),(0,t.jsx)(d.ZP,{loading:m,onClick:()=>n.submit(),children:m?"Saving...":"Save"})]}),onCancel:p,children:(0,t.jsx)(ek,{form:n,onFormSubmit:g})})},eR=l(42208),eM=l(87769);function ez(e){let{defaultHidden:s=!0,value:l}=e,[r,n]=(0,i.useState)(s);return(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{className:"font-mono text-gray-600 flex-1",children:l?r?"•".repeat(l.length):l:(0,t.jsx)("span",{className:"text-gray-400 italic",children:"Not configured"})}),l&&(0,t.jsx)(d.ZP,{type:"text",size:"small",icon:r?(0,t.jsx)(eR.Z,{className:"w-4 h-4"}):(0,t.jsx)(eM.Z,{className:"w-4 h-4"}),onClick:()=>n(!r),className:"text-gray-400 hover:text-gray-600"})]})}var eF=l(56609),eG=l(95805);let{Title:eD,Text:eB}=r.default;function eV(e){let{roleMappings:s}=e;if(!s)return null;let l=[{title:"Role",dataIndex:"role",key:"role",render:e=>(0,t.jsx)(eB,{strong:!0,children:ew[e]})},{title:"Mapped Groups",dataIndex:"groups",key:"groups",render:e=>(0,t.jsx)(t.Fragment,{children:e.length>0?e.map((e,s)=>(0,t.jsx)(eo.Z,{color:"blue",children:e},s)):(0,t.jsx)(eB,{className:"text-gray-400 italic",children:"No groups mapped"})})}];return(0,t.jsxs)(eu.Z,{children:[(0,t.jsxs)("div",{className:"flex items-center gap-3",children:[(0,t.jsx)(eG.Z,{className:"w-6 h-6 text-gray-400 mb-2"}),(0,t.jsx)(eD,{level:3,children:"Role Mappings"})]}),(0,t.jsxs)("div",{className:"space-y-8",children:[(0,t.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)(eD,{level:5,children:"Group Claim"}),(0,t.jsx)("div",{children:(0,t.jsx)(eB,{code:!0,children:s.group_claim})})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(eD,{level:5,children:"Default Role"}),(0,t.jsx)("div",{children:(0,t.jsx)(eB,{strong:!0,children:ew[s.default_role]})})]})]}),(0,t.jsx)(ep.Z,{}),(0,t.jsx)(eF.Z,{columns:l,dataSource:Object.entries(s.roles).map(e=>{let[s,l]=e;return{role:s,groups:l}}),pagination:!1,bordered:!0,size:"small",className:"w-full"})]})]})}var eq=l(85180);let{Title:eY,Paragraph:eK}=r.default;function eH(e){let{onAdd:s}=e;return(0,t.jsx)("div",{className:"bg-white p-12 rounded-lg border border-dashed border-gray-300 text-center w-full",children:(0,t.jsx)(eq.Z,{image:eq.Z.PRESENTED_IMAGE_SIMPLE,description:(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsx)(eY,{level:4,children:"No SSO Configuration Found"}),(0,t.jsx)(eK,{type:"secondary",className:"max-w-md mx-auto",children:"Configure Single Sign-On (SSO) to enable seamless authentication for your team members using your identity provider."})]}),children:(0,t.jsx)(d.ZP,{type:"primary",size:"large",onClick:s,className:"flex items-center gap-2 mx-auto mt-4",children:"Configure SSO"})})})}let{Title:eW,Text:eQ}=r.default;function eJ(){return(0,t.jsx)(eu.Z,{children:(0,t.jsxs)(ea.Z,{direction:"vertical",size:"large",className:"w-full",children:[(0,t.jsxs)("div",{className:"flex items-center justify-between",children:[(0,t.jsxs)("div",{className:"flex items-center gap-3",children:[(0,t.jsx)(ey.Z,{className:"w-6 h-6 text-gray-400"}),(0,t.jsxs)("div",{children:[(0,t.jsx)(eW,{level:3,children:"SSO Configuration"}),(0,t.jsx)(eQ,{type:"secondary",children:"Manage Single Sign-On authentication settings"})]})]}),(0,t.jsxs)("div",{className:"flex items-center gap-3",children:[(0,t.jsx)(em.Z.Button,{active:!0,size:"default",style:{width:170,height:32}}),(0,t.jsx)(em.Z.Button,{active:!0,size:"default",style:{width:190,height:32}})]})]}),(0,t.jsxs)(ej.Z,{bordered:!0,column:{xxl:1,xl:1,lg:1,md:1,sm:1,xs:1},children:[(0,t.jsx)(ej.Z.Item,{label:(0,t.jsx)(em.Z.Node,{active:!0,style:{width:80,height:16}}),children:(0,t.jsx)("div",{style:{display:"flex",alignItems:"center",gap:"8px"},children:(0,t.jsx)(em.Z.Node,{active:!0,style:{width:100,height:16}})})}),(0,t.jsx)(ej.Z.Item,{label:(0,t.jsx)(em.Z.Node,{active:!0,style:{width:80,height:16}}),children:(0,t.jsx)(em.Z.Node,{active:!0,style:{width:200,height:16}})}),(0,t.jsx)(ej.Z.Item,{label:(0,t.jsx)(em.Z.Node,{active:!0,style:{width:80,height:16}}),children:(0,t.jsx)(em.Z.Node,{active:!0,style:{width:250,height:16}})}),(0,t.jsx)(ej.Z.Item,{label:(0,t.jsx)(em.Z.Node,{active:!0,style:{width:80,height:16}}),children:(0,t.jsx)(em.Z.Node,{active:!0,style:{width:180,height:16}})}),(0,t.jsx)(ej.Z.Item,{label:(0,t.jsx)(em.Z.Node,{active:!0,style:{width:80,height:16}}),children:(0,t.jsx)(em.Z.Node,{active:!0,style:{width:220,height:16}})})]})]})})}let{Title:eX,Text:e$}=r.default;function e0(){let{data:e,refetch:s,isLoading:l}=ef(),[r,n]=(0,i.useState)(!1),[a,o]=(0,i.useState)(!1),[c,u]=(0,i.useState)(!1),m=!!(null==e?void 0:e.values.google_client_id)||!!(null==e?void 0:e.values.microsoft_client_id)||!!(null==e?void 0:e.values.generic_client_id),g=(null==e?void 0:e.values)?eT(e.values):null,p=!!(null==e?void 0:e.values.role_mappings),x=e=>(0,t.jsx)(e$,{className:"font-mono text-gray-600 text-sm",copyable:!!e,children:e||"-"}),h=e=>e||(0,t.jsx)("span",{className:"text-gray-400 italic",children:"Not configured"}),_={column:{xxl:1,xl:1,lg:1,md:1,sm:1,xs:1}},f={google:{providerText:eZ.google,fields:[{label:"Client ID",render:e=>(0,t.jsx)(ez,{value:e.google_client_id})},{label:"Client Secret",render:e=>(0,t.jsx)(ez,{value:e.google_client_secret})},{label:"Proxy Base URL",render:e=>h(e.proxy_base_url)}]},microsoft:{providerText:eZ.microsoft,fields:[{label:"Client ID",render:e=>(0,t.jsx)(ez,{value:e.microsoft_client_id})},{label:"Client Secret",render:e=>(0,t.jsx)(ez,{value:e.microsoft_client_secret})},{label:"Tenant",render:e=>h(e.microsoft_tenant)},{label:"Proxy Base URL",render:e=>h(e.proxy_base_url)}]},okta:{providerText:eZ.okta,fields:[{label:"Client ID",render:e=>(0,t.jsx)(ez,{value:e.generic_client_id})},{label:"Client Secret",render:e=>(0,t.jsx)(ez,{value:e.generic_client_secret})},{label:"Authorization Endpoint",render:e=>x(e.generic_authorization_endpoint)},{label:"Token Endpoint",render:e=>x(e.generic_token_endpoint)},{label:"User Info Endpoint",render:e=>x(e.generic_userinfo_endpoint)},{label:"Proxy Base URL",render:e=>h(e.proxy_base_url)}]},generic:{providerText:eZ.generic,fields:[{label:"Client ID",render:e=>(0,t.jsx)(ez,{value:e.generic_client_id})},{label:"Client Secret",render:e=>(0,t.jsx)(ez,{value:e.generic_client_secret})},{label:"Authorization Endpoint",render:e=>x(e.generic_authorization_endpoint)},{label:"Token Endpoint",render:e=>x(e.generic_token_endpoint)},{label:"User Info Endpoint",render:e=>x(e.generic_userinfo_endpoint)},{label:"Proxy Base URL",render:e=>h(e.proxy_base_url)}]}};return(0,t.jsxs)(t.Fragment,{children:[l?(0,t.jsx)(eJ,{}):(0,t.jsxs)(ea.Z,{direction:"vertical",size:"large",className:"w-full",children:[(0,t.jsx)(eu.Z,{children:(0,t.jsxs)(ea.Z,{direction:"vertical",size:"large",className:"w-full",children:[(0,t.jsxs)("div",{className:"flex items-center justify-between",children:[(0,t.jsxs)("div",{className:"flex items-center gap-3",children:[(0,t.jsx)(ey.Z,{className:"w-6 h-6 text-gray-400"}),(0,t.jsxs)("div",{children:[(0,t.jsx)(eX,{level:3,children:"SSO Configuration"}),(0,t.jsx)(e$,{type:"secondary",children:"Manage Single Sign-On authentication settings"})]})]}),(0,t.jsx)("div",{className:"flex items-center gap-3",children:m&&(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(d.ZP,{icon:(0,t.jsx)(ev.Z,{className:"w-4 h-4"}),onClick:()=>u(!0),children:"Edit SSO Settings"}),(0,t.jsx)(d.ZP,{danger:!0,icon:(0,t.jsx)(eb.Z,{className:"w-4 h-4"}),onClick:()=>n(!0),children:"Delete SSO Settings"})]})})]}),m?(()=>{if(!(null==e?void 0:e.values)||!g)return null;let{values:s}=e,l=f[g];return l?(0,t.jsxs)(ej.Z,{bordered:!0,..._,children:[(0,t.jsx)(ej.Z.Item,{label:"Provider",children:(0,t.jsxs)("div",{style:{display:"flex",alignItems:"center",gap:"8px"},children:[eS[g]&&(0,t.jsx)("img",{src:eS[g],alt:g,style:{height:24,width:24,objectFit:"contain"}}),(0,t.jsx)("span",{children:l.providerText})]})}),l.fields.map((e,l)=>(0,t.jsx)(ej.Z.Item,{label:e.label,children:e.render(s)},l))]}):null})():(0,t.jsx)(eH,{onAdd:()=>o(!0)})]})}),p&&(0,t.jsx)(eV,{roleMappings:null==e?void 0:e.values.role_mappings})]}),(0,t.jsx)(eA,{isVisible:r,onCancel:()=>n(!1),onSuccess:()=>s()}),(0,t.jsx)(eL,{isVisible:a,onCancel:()=>o(!1),onSuccess:()=>{o(!1),s()}}),(0,t.jsx)(eU,{isVisible:c,onCancel:()=>u(!1),onSuccess:()=>{u(!1),s()}})]})}var e1=e=>{let{searchParams:s,accessToken:l,userID:N,showSSOBanner:I,premiumUser:C,proxySettings:E,userRole:T}=e,[L]=o.Z.useForm(),[A]=o.Z.useForm(),{Title:U,Paragraph:R}=r.default,[M,z]=(0,i.useState)(""),[F,G]=(0,i.useState)(null),[D,B]=(0,i.useState)(null),[V,q]=(0,i.useState)(!1),[W,Q]=(0,i.useState)(!1),[J,X]=(0,i.useState)(!1),[$,ee]=(0,i.useState)(!1),[es,el]=(0,i.useState)(!1),[et,ei]=(0,i.useState)(!1),[er,en]=(0,i.useState)(!1),[ea,eo]=(0,i.useState)(!1),[ec,ed]=(0,i.useState)(!1),[eu,em]=(0,i.useState)(!1),[eg,ep]=(0,i.useState)([]),[eh,e_]=(0,i.useState)(null),[ef,ej]=(0,i.useState)(!1);(0,a.useRouter)();let[ey,ev]=(0,i.useState)(null);console.log=function(){};let eb=(0,H.n)(),eS="All IP Addresses Allowed",eZ=eb;eZ+="/fallback/login";let ew=async()=>{if(l)try{let e=await (0,k.getSSOSettings)(l);if(console.log("SSO data:",e),e&&e.values){let s=e.values.google_client_id&&e.values.google_client_secret,l=e.values.microsoft_client_id&&e.values.microsoft_client_secret,t=e.values.generic_client_id&&e.values.generic_client_secret;ej(s||l||t)}else ej(!1)}catch(e){console.error("Error checking SSO configuration:",e),ej(!1)}},eN=async()=>{try{if(!0!==C){O.Z.fromBackend("This feature is only available for premium users. Please upgrade your account.");return}if(l){let e=await (0,k.getAllowedIPs)(l);ep(e&&e.length>0?e:[eS])}else ep([eS])}catch(e){console.error("Error fetching allowed IPs:",e),O.Z.fromBackend("Failed to fetch allowed IPs ".concat(e)),ep([eS])}finally{!0===C&&en(!0)}},eI=async e=>{try{if(l){await (0,k.addAllowedIP)(l,e.ip);let s=await (0,k.getAllowedIPs)(l);ep(s),O.Z.success("IP address added successfully")}}catch(e){console.error("Error adding IP:",e),O.Z.fromBackend("Failed to add IP address ".concat(e))}finally{eo(!1)}},eC=async e=>{e_(e),ed(!0)},ek=async()=>{if(eh&&l)try{await (0,k.deleteAllowedIP)(l,eh);let e=await (0,k.getAllowedIPs)(l);ep(e.length>0?e:[eS]),O.Z.success("IP address deleted successfully")}catch(e){console.error("Error deleting IP:",e),O.Z.fromBackend("Failed to delete IP address ".concat(e))}finally{ed(!1),e_(null)}};(0,i.useEffect)(()=>{(async()=>{if(null!=l){let e=[],s=await (0,k.userGetAllUsersCall)(l,"proxy_admin_viewer");console.log("proxy admin viewer response: ",s);let t=s.users;console.log("proxy viewers response: ".concat(t)),t.forEach(s=>{e.push({user_role:s.user_role,user_id:s.user_id,user_email:s.user_email})}),console.log("proxy viewers: ".concat(t));let i=(await (0,k.userGetAllUsersCall)(l,"proxy_admin")).users;i.forEach(s=>{e.push({user_role:s.user_role,user_id:s.user_id,user_email:s.user_email})}),console.log("proxy admins: ".concat(i)),console.log("combinedList: ".concat(e)),G(e),ev(await (0,k.getPossibleUserRoles)(l))}})()},[l]),(0,i.useEffect)(()=>{ew()},[l,C]);let eO=()=>{em(!1)};return console.log("admins: ".concat(null==F?void 0:F.length)),(0,t.jsxs)("div",{className:"w-full m-2 mt-2 p-8",children:[(0,t.jsx)(U,{level:4,children:"Admin Access "}),(0,t.jsx)(R,{children:"Go to 'Internal Users' page to add other admins."}),(0,t.jsxs)(h.Z,{children:[(0,t.jsxs)(_.Z,{children:[(0,t.jsx)(x.Z,{children:"SSO Settings"}),(0,t.jsx)(x.Z,{children:"Security Settings"}),(0,t.jsx)(x.Z,{children:"SCIM"}),(0,t.jsx)(x.Z,{children:"UI Settings"})]}),(0,t.jsxs)(j.Z,{children:[(0,t.jsx)(f.Z,{children:(0,t.jsx)(e0,{})}),(0,t.jsxs)(f.Z,{children:[(0,t.jsxs)(p.Z,{children:[(0,t.jsx)(U,{level:4,children:" ✨ Security Settings"}),(0,t.jsx)(n.Z,{message:"SSO Configuration Deprecated",description:"Editing SSO Settings on this page is deprecated and will be removed in a future version. Please use the SSO Settings tab for SSO configuration.",type:"warning",showIcon:!0}),(0,t.jsxs)("div",{style:{display:"flex",flexDirection:"column",gap:"1rem",marginTop:"1rem",marginLeft:"0.5rem"},children:[(0,t.jsx)("div",{children:(0,t.jsx)(m.Z,{style:{width:"150px"},onClick:()=>el(!0),children:ef?"Edit SSO Settings":"Add SSO"})}),(0,t.jsx)("div",{children:(0,t.jsx)(m.Z,{style:{width:"150px"},onClick:eN,children:"Allowed IPs"})}),(0,t.jsx)("div",{children:(0,t.jsx)(m.Z,{style:{width:"150px"},onClick:()=>!0===C?em(!0):O.Z.fromBackend("Only premium users can configure UI access control"),children:"UI Access Control"})})]})]}),(0,t.jsxs)("div",{className:"flex justify-start mb-4",children:[(0,t.jsx)(P,{isAddSSOModalVisible:es,isInstructionsModalVisible:et,handleAddSSOOk:()=>{el(!1),L.resetFields(),l&&C&&ew()},handleAddSSOCancel:()=>{el(!1),L.resetFields()},handleShowInstructions:e=>{el(!1),ei(!0)},handleInstructionsOk:()=>{ei(!1),l&&C&&ew()},handleInstructionsCancel:()=>{ei(!1),l&&C&&ew()},form:L,accessToken:l,ssoConfigured:ef}),(0,t.jsx)(u.Z,{title:"Manage Allowed IP Addresses",width:800,visible:er,onCancel:()=>en(!1),footer:[(0,t.jsx)(m.Z,{className:"mx-1",onClick:()=>eo(!0),children:"Add IP Address"},"add"),(0,t.jsx)(m.Z,{onClick:()=>en(!1),children:"Close"},"close")],children:(0,t.jsxs)(y.Z,{children:[(0,t.jsx)(S.Z,{children:(0,t.jsxs)(w.Z,{children:[(0,t.jsx)(Z.Z,{children:"IP Address"}),(0,t.jsx)(Z.Z,{className:"text-right",children:"Action"})]})}),(0,t.jsx)(v.Z,{children:eg.map((e,s)=>(0,t.jsxs)(w.Z,{children:[(0,t.jsx)(b.Z,{children:e}),(0,t.jsx)(b.Z,{className:"text-right",children:e!==eS&&(0,t.jsx)(m.Z,{onClick:()=>eC(e),color:"red",size:"xs",children:"Delete"})})]},s))})]})}),(0,t.jsx)(u.Z,{title:"Add Allowed IP Address",visible:ea,onCancel:()=>eo(!1),footer:null,children:(0,t.jsxs)(o.Z,{onFinish:eI,children:[(0,t.jsx)(o.Z.Item,{name:"ip",rules:[{required:!0,message:"Please enter an IP address"}],children:(0,t.jsx)(c.default,{placeholder:"Enter IP address"})}),(0,t.jsx)(o.Z.Item,{children:(0,t.jsx)(d.ZP,{htmlType:"submit",children:"Add IP Address"})})]})}),(0,t.jsx)(u.Z,{title:"Confirm Delete",visible:ec,onCancel:()=>ed(!1),onOk:ek,footer:[(0,t.jsx)(m.Z,{className:"mx-1",onClick:()=>ek(),children:"Yes"},"delete"),(0,t.jsx)(m.Z,{onClick:()=>ed(!1),children:"Close"},"close")],children:(0,t.jsxs)("p",{children:["Are you sure you want to delete the IP address: ",eh,"?"]})}),(0,t.jsx)(u.Z,{title:"UI Access Control Settings",visible:eu,width:600,footer:null,onOk:eO,onCancel:()=>{em(!1)},children:(0,t.jsx)(K,{accessToken:l,onSuccess:()=>{eO(),O.Z.success("UI Access Control settings updated successfully")}})})]}),(0,t.jsxs)(g.Z,{title:"Login without SSO",color:"teal",children:["If you need to login without sso, you can access"," ",(0,t.jsxs)("a",{href:eZ,target:"_blank",children:[(0,t.jsx)("b",{children:eZ})," "]})]})]}),(0,t.jsx)(f.Z,{children:(0,t.jsx)(Y,{accessToken:l,userID:N,proxySettings:E})}),(0,t.jsx)(f.Z,{children:(0,t.jsx)(ex,{})})]})]})]})}},1633:function(e,s,l){l.d(s,{j:function(){return R}});var t=l(57437),i=l(39823),r=l(39760),n=l(92403),a=l(28595),o=l(68208),c=l(69993),d=l(58630),u=l(57400),m=l(93750),g=l(29436),p=l(44625),x=l(9775),h=l(48231),_=l(15883),f=l(41361),j=l(37527),y=l(99458),v=l(12660),b=l(88009),S=l(71916),Z=l(41169),w=l(38434),N=l(71891),I=l(55322),C=l(11429),k=l(13817),O=l(18310),E=l(60985),T=l(2265),L=l(20347),P=l(79262),A=l(91027);let{Sider:U}=k.default,R=[{groupLabel:"AI GATEWAY",items:[{key:"api-keys",page:"api-keys",label:"Virtual Keys",icon:(0,t.jsx)(n.Z,{})},{key:"llm-playground",page:"llm-playground",label:"Playground",icon:(0,t.jsx)(a.Z,{}),roles:L.LQ},{key:"models",page:"models",label:"Models + Endpoints",icon:(0,t.jsx)(o.Z,{}),roles:L.LQ},{key:"agents",page:"agents",label:"Agents",icon:(0,t.jsx)(c.Z,{}),roles:L.LQ},{key:"mcp-servers",page:"mcp-servers",label:"MCP Servers",icon:(0,t.jsx)(d.Z,{})},{key:"guardrails",page:"guardrails",label:"Guardrails",icon:(0,t.jsx)(u.Z,{}),roles:L.ZL},{key:"policies",page:"policies",label:(0,t.jsx)("span",{className:"flex items-center gap-4",children:"Policies"}),icon:(0,t.jsx)(m.Z,{}),roles:L.ZL},{key:"tools",page:"tools",label:"Tools",icon:(0,t.jsx)(d.Z,{}),children:[{key:"search-tools",page:"search-tools",label:"Search Tools",icon:(0,t.jsx)(g.Z,{})},{key:"vector-stores",page:"vector-stores",label:"Vector Stores",icon:(0,t.jsx)(p.Z,{})}]}]},{groupLabel:"OBSERVABILITY",items:[{key:"new_usage",page:"new_usage",icon:(0,t.jsx)(x.Z,{}),roles:[...L.ZL,...L.lo],label:"Usage"},{key:"logs",page:"logs",label:(0,t.jsxs)("span",{className:"flex items-center gap-4",children:["Logs ",(0,t.jsx)(A.Z,{})]}),icon:(0,t.jsx)(h.Z,{})}]},{groupLabel:"ACCESS CONTROL",items:[{key:"users",page:"users",label:"Internal Users",icon:(0,t.jsx)(_.Z,{}),roles:L.ZL},{key:"teams",page:"teams",label:"Teams",icon:(0,t.jsx)(f.Z,{})},{key:"organizations",page:"organizations",label:"Organizations",icon:(0,t.jsx)(j.Z,{}),roles:L.ZL},{key:"budgets",page:"budgets",label:"Budgets",icon:(0,t.jsx)(y.Z,{}),roles:L.ZL}]},{groupLabel:"DEVELOPER TOOLS",items:[{key:"api_ref",page:"api_ref",label:"API Reference",icon:(0,t.jsx)(v.Z,{})},{key:"model-hub-table",page:"model-hub-table",label:"AI Hub",icon:(0,t.jsx)(b.Z,{})},{key:"learning-resources",page:"learning-resources",label:"Learning Resources",icon:(0,t.jsx)(S.Z,{}),external_url:"https://models.litellm.ai/cookbook"},{key:"experimental",page:"experimental",label:"Experimental",icon:(0,t.jsx)(Z.Z,{}),children:[{key:"caching",page:"caching",label:"Caching",icon:(0,t.jsx)(p.Z,{}),roles:L.ZL},{key:"prompts",page:"prompts",label:"Prompts",icon:(0,t.jsx)(w.Z,{}),roles:L.ZL},{key:"transform-request",page:"transform-request",label:"API Playground",icon:(0,t.jsx)(v.Z,{}),roles:[...L.ZL,...L.lo]},{key:"tag-management",page:"tag-management",label:"Tag Management",icon:(0,t.jsx)(N.Z,{}),roles:L.ZL},{key:"claude-code-plugins",page:"claude-code-plugins",label:"Claude Code Plugins",icon:(0,t.jsx)(d.Z,{}),roles:L.ZL},{key:"4",page:"usage",label:"Old Usage",icon:(0,t.jsx)(x.Z,{})}]}]},{groupLabel:"SETTINGS",roles:L.ZL,items:[{key:"settings",page:"settings",label:(0,t.jsx)("span",{className:"flex items-center gap-4",children:"Settings"}),icon:(0,t.jsx)(I.Z,{}),roles:L.ZL,children:[{key:"router-settings",page:"router-settings",label:"Router Settings",icon:(0,t.jsx)(I.Z,{}),roles:L.ZL},{key:"logging-and-alerts",page:"logging-and-alerts",label:"Logging & Alerts",icon:(0,t.jsx)(I.Z,{}),roles:L.ZL},{key:"admin-panel",page:"admin-panel",label:"Admin Settings",icon:(0,t.jsx)(I.Z,{}),roles:L.ZL},{key:"cost-tracking",page:"cost-tracking",label:"Cost Tracking",icon:(0,t.jsx)(x.Z,{}),roles:L.ZL},{key:"ui-theme",page:"ui-theme",label:"UI Theme",icon:(0,t.jsx)(C.Z,{}),roles:L.ZL}]}]}];s.Z=e=>{let{setPage:s,defaultSelectedKey:l,collapsed:n=!1,enabledPagesInternalUsers:a}=e,{userId:o,accessToken:c,userRole:d}=(0,r.Z)(),{data:u}=(0,i.q)(),m=(0,T.useMemo)(()=>!!o&&!!u&&u.some(e=>{var s;return null===(s=e.members)||void 0===s?void 0:s.some(e=>e.user_id===o&&"org_admin"===e.user_role)}),[o,u]),g=e=>{let l=new URLSearchParams(window.location.search);l.set("page",e),window.history.pushState(null,"","?".concat(l.toString())),s(e)},p=e=>{let s=(0,L.tY)(d);return null!=a&&console.log("[LeftNav] Filtering with enabled pages:",{userRole:d,isAdmin:s,enabledPagesInternalUsers:a}),e.map(e=>({...e,children:e.children?p(e.children):void 0})).filter(e=>{if("organizations"===e.key){if(!(!e.roles||e.roles.includes(d)||m))return!1;if(!s&&null!=a){let s=a.includes(e.page);return console.log('[LeftNav] Page "'.concat(e.page,'" (').concat(e.key,"): ").concat(s?"VISIBLE":"HIDDEN")),s}return!0}if(e.roles&&!e.roles.includes(d))return!1;if(!s&&null!=a){if(e.children&&e.children.length>0&&e.children.some(e=>a.includes(e.page)))return console.log('[LeftNav] Parent "'.concat(e.page,'" (').concat(e.key,"): VISIBLE (has visible children)")),!0;let s=a.includes(e.page);return console.log('[LeftNav] Page "'.concat(e.page,'" (').concat(e.key,"): ").concat(s?"VISIBLE":"HIDDEN")),s}return!0})},x=(e=>{for(let s of R)for(let l of s.items){if(l.page===e)return l.key;if(l.children){let s=l.children.find(s=>s.page===e);if(s)return s.key}}return"api-keys"})(l);return(0,t.jsx)(k.default,{children:(0,t.jsxs)(U,{theme:"light",width:220,collapsed:n,collapsedWidth:80,collapsible:!0,trigger:null,style:{transition:"all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",position:"relative"},children:[(0,t.jsx)(O.ZP,{theme:{components:{Menu:{iconSize:15,fontSize:13,itemMarginInline:4,itemPaddingInline:8,itemHeight:30,itemBorderRadius:6,subMenuItemBorderRadius:6,groupTitleFontSize:10,groupTitleLineHeight:1.5}}},children:(0,t.jsx)(E.Z,{mode:"inline",selectedKeys:[x],defaultOpenKeys:[],inlineCollapsed:n,className:"custom-sidebar-menu",style:{borderRight:0,backgroundColor:"transparent",fontSize:"13px",paddingTop:"4px"},items:(()=>{let e=[];return R.forEach(s=>{if(s.roles&&!s.roles.includes(d))return;let l=p(s.items);0!==l.length&&e.push({type:"group",label:n?null:(0,t.jsx)("span",{style:{fontSize:"10px",fontWeight:600,color:"#6b7280",letterSpacing:"0.05em",padding:"12px 0 4px 12px",display:"block",marginBottom:"2px"},children:s.groupLabel}),children:l.map(e=>{var s;return{key:e.key,icon:e.icon,label:e.label,children:null===(s=e.children)||void 0===s?void 0:s.map(e=>({key:e.key,icon:e.icon,label:e.label,onClick:()=>{e.external_url?window.open(e.external_url,"_blank"):g(e.page)}})),onClick:e.children?void 0:()=>{e.external_url?window.open(e.external_url,"_blank"):g(e.page)}}})})}),e})()})}),(0,L.tY)(d)&&!n&&(0,t.jsx)(P.Z,{accessToken:c,width:220})]})})}},79262:function(e,s,l){l.d(s,{Z:function(){return g}});var t=l(57437);l(1309);var i=l(76865),r=l(70525),n=l(95805),a=l(51817),o=l(21047);l(22135),l(40875);var c=l(49663),d=l(2265),u=l(19250);let m=function(){for(var e=arguments.length,s=Array(e),l=0;l{(async()=>{if(s){y(!0),b(null);try{let e=await (0,u.getRemainingUsers)(s);f(e)}catch(e){console.error("Failed to fetch usage data:",e),b("Failed to load usage data")}finally{y(!1)}}})()},[s]);let{isOverLimit:S,isNearLimit:Z,usagePercentage:w,userMetrics:N,teamMetrics:I}=(e=>{if(!e)return{isOverLimit:!1,isNearLimit:!1,usagePercentage:0,userMetrics:{isOverLimit:!1,isNearLimit:!1,usagePercentage:0},teamMetrics:{isOverLimit:!1,isNearLimit:!1,usagePercentage:0}};let s=e.total_users?e.total_users_used/e.total_users*100:0,l=s>100,t=s>=80&&s<=100,i=e.total_teams?e.total_teams_used/e.total_teams*100:0,r=i>100,n=i>=80&&i<=100,a=l||r;return{isOverLimit:a,isNearLimit:(t||n)&&!a,usagePercentage:Math.max(s,i),userMetrics:{isOverLimit:l,isNearLimit:t,usagePercentage:s},teamMetrics:{isOverLimit:r,isNearLimit:n,usagePercentage:i}}})(_),C=()=>S?(0,t.jsx)(i.Z,{className:"h-3 w-3"}):Z?(0,t.jsx)(r.Z,{className:"h-3 w-3"}):null;return s&&((null==_?void 0:_.total_users)!==null||(null==_?void 0:_.total_teams)!==null)?(0,t.jsx)("div",{className:"fixed bottom-4 left-4 z-50",style:{width:"".concat(Math.min(l,220),"px")},children:(0,t.jsx)(()=>x?(0,t.jsx)("button",{onClick:()=>h(!1),className:m("bg-white border border-gray-200 rounded-lg shadow-sm p-3 hover:shadow-md transition-all w-full"),title:"Show usage details",children:(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)(n.Z,{className:"h-4 w-4 flex-shrink-0"}),(S||Z)&&(0,t.jsx)("span",{className:"flex-shrink-0",children:C()}),(0,t.jsxs)("div",{className:"flex items-center gap-2 text-sm font-medium truncate",children:[_&&null!==_.total_users&&(0,t.jsxs)("span",{className:m("flex-shrink-0 px-1.5 py-0.5 rounded text-xs border",N.isOverLimit&&"bg-red-50 text-red-700 border-red-200",N.isNearLimit&&"bg-yellow-50 text-yellow-700 border-yellow-200",!N.isOverLimit&&!N.isNearLimit&&"bg-gray-50 text-gray-700 border-gray-200"),children:["U: ",_.total_users_used,"/",_.total_users]}),_&&null!==_.total_teams&&(0,t.jsxs)("span",{className:m("flex-shrink-0 px-1.5 py-0.5 rounded text-xs border",I.isOverLimit&&"bg-red-50 text-red-700 border-red-200",I.isNearLimit&&"bg-yellow-50 text-yellow-700 border-yellow-200",!I.isOverLimit&&!I.isNearLimit&&"bg-gray-50 text-gray-700 border-gray-200"),children:["T: ",_.total_teams_used,"/",_.total_teams]}),!_||null===_.total_users&&null===_.total_teams&&(0,t.jsx)("span",{className:"truncate",children:"Usage"})]})]})}):j?(0,t.jsx)("div",{className:"bg-white border border-gray-200 rounded-lg shadow-sm p-4 w-full",children:(0,t.jsxs)("div",{className:"flex items-center justify-center gap-2 py-2",children:[(0,t.jsx)(a.Z,{className:"h-4 w-4 animate-spin"}),(0,t.jsx)("span",{className:"text-sm text-gray-500 truncate",children:"Loading..."})]})}):v||!_?(0,t.jsx)("div",{className:"bg-white border border-gray-200 rounded-lg shadow-sm p-4 group w-full",children:(0,t.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,t.jsx)("div",{className:"flex-1 min-w-0",children:(0,t.jsx)("span",{className:"text-sm text-gray-500 truncate block",children:v||"No data"})}),(0,t.jsx)("button",{onClick:()=>h(!0),className:"opacity-0 group-hover:opacity-100 p-1 hover:bg-gray-100 rounded transition-all flex-shrink-0",title:"Minimize",children:(0,t.jsx)(o.Z,{className:"h-3 w-3 text-gray-400"})})]})}):(0,t.jsxs)("div",{className:m("bg-white border rounded-lg shadow-sm p-3 transition-all duration-200 group w-full"),children:[(0,t.jsxs)("div",{className:"flex items-center justify-between gap-2 mb-3",children:[(0,t.jsxs)("div",{className:"flex items-center gap-2 min-w-0 flex-1",children:[(0,t.jsx)(n.Z,{className:"h-4 w-4 flex-shrink-0"}),(0,t.jsx)("span",{className:"font-medium text-sm truncate",children:"Usage"})]}),(0,t.jsx)("button",{onClick:()=>h(!0),className:"opacity-0 group-hover:opacity-100 p-1 hover:bg-gray-100 rounded transition-all flex-shrink-0",title:"Minimize",children:(0,t.jsx)(o.Z,{className:"h-3 w-3 text-gray-400"})})]}),(0,t.jsxs)("div",{className:"space-y-3 text-sm",children:[null!==_.total_users&&(0,t.jsxs)("div",{className:m("space-y-1 border rounded-md p-2",N.isOverLimit&&"border-red-200 bg-red-50",N.isNearLimit&&"border-yellow-200 bg-yellow-50"),children:[(0,t.jsxs)("div",{className:"flex items-center gap-2 text-xs text-gray-600 mb-1",children:[(0,t.jsx)(n.Z,{className:"h-3 w-3"}),(0,t.jsx)("span",{className:"font-medium",children:"Users"}),(0,t.jsx)("span",{className:m("ml-1 px-1.5 py-0.5 rounded border",N.isOverLimit&&"bg-red-50 text-red-700 border-red-200",N.isNearLimit&&"bg-yellow-50 text-yellow-700 border-yellow-200",!N.isOverLimit&&!N.isNearLimit&&"bg-gray-50 text-gray-600 border-gray-200"),children:N.isOverLimit?"Over limit":N.isNearLimit?"Near limit":"OK"})]}),(0,t.jsxs)("div",{className:"flex justify-between items-center",children:[(0,t.jsx)("span",{className:"text-gray-600 text-xs",children:"Used:"}),(0,t.jsxs)("span",{className:"font-medium text-right",children:[_.total_users_used,"/",_.total_users]})]}),(0,t.jsxs)("div",{className:"flex justify-between items-center",children:[(0,t.jsx)("span",{className:"text-gray-600 text-xs",children:"Remaining:"}),(0,t.jsx)("span",{className:m("font-medium text-right",N.isOverLimit&&"text-red-600",N.isNearLimit&&"text-yellow-600"),children:_.total_users_remaining})]}),(0,t.jsxs)("div",{className:"flex justify-between items-center",children:[(0,t.jsx)("span",{className:"text-gray-600 text-xs",children:"Usage:"}),(0,t.jsxs)("span",{className:"font-medium text-right",children:[Math.round(N.usagePercentage),"%"]})]}),(0,t.jsx)("div",{className:"w-full bg-gray-200 rounded-full h-2",children:(0,t.jsx)("div",{className:m("h-2 rounded-full transition-all duration-300",N.isOverLimit&&"bg-red-500",N.isNearLimit&&"bg-yellow-500",!N.isOverLimit&&!N.isNearLimit&&"bg-green-500"),style:{width:"".concat(Math.min(N.usagePercentage,100),"%")}})})]}),null!==_.total_teams&&(0,t.jsxs)("div",{className:m("space-y-1 border rounded-md p-2",I.isOverLimit&&"border-red-200 bg-red-50",I.isNearLimit&&"border-yellow-200 bg-yellow-50"),children:[(0,t.jsxs)("div",{className:"flex items-center gap-2 text-xs text-gray-600 mb-1",children:[(0,t.jsx)(c.Z,{className:"h-3 w-3"}),(0,t.jsx)("span",{className:"font-medium",children:"Teams"}),(0,t.jsx)("span",{className:m("ml-1 px-1.5 py-0.5 rounded border",I.isOverLimit&&"bg-red-50 text-red-700 border-red-200",I.isNearLimit&&"bg-yellow-50 text-yellow-700 border-yellow-200",!I.isOverLimit&&!I.isNearLimit&&"bg-gray-50 text-gray-600 border-gray-200"),children:I.isOverLimit?"Over limit":I.isNearLimit?"Near limit":"OK"})]}),(0,t.jsxs)("div",{className:"flex justify-between items-center",children:[(0,t.jsx)("span",{className:"text-gray-600 text-xs",children:"Used:"}),(0,t.jsxs)("span",{className:"font-medium text-right",children:[_.total_teams_used,"/",_.total_teams]})]}),(0,t.jsxs)("div",{className:"flex justify-between items-center",children:[(0,t.jsx)("span",{className:"text-gray-600 text-xs",children:"Remaining:"}),(0,t.jsx)("span",{className:m("font-medium text-right",I.isOverLimit&&"text-red-600",I.isNearLimit&&"text-yellow-600"),children:_.total_teams_remaining})]}),(0,t.jsxs)("div",{className:"flex justify-between items-center",children:[(0,t.jsx)("span",{className:"text-gray-600 text-xs",children:"Usage:"}),(0,t.jsxs)("span",{className:"font-medium text-right",children:[Math.round(I.usagePercentage),"%"]})]}),(0,t.jsx)("div",{className:"w-full bg-gray-200 rounded-full h-2",children:(0,t.jsx)("div",{className:m("h-2 rounded-full transition-all duration-300",I.isOverLimit&&"bg-red-500",I.isNearLimit&&"bg-yellow-500",!I.isOverLimit&&!I.isNearLimit&&"bg-green-500"),style:{width:"".concat(Math.min(I.usagePercentage,100),"%")}})})]})]})]}),{})}):null}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1901-4d02d1f2a71cdbf7.js b/litellm/proxy/_experimental/out/_next/static/chunks/1901-4d02d1f2a71cdbf7.js deleted file mode 100644 index f19de7216fa..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/1901-4d02d1f2a71cdbf7.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1901],{91027:function(e,s,a){a.d(s,{Z:function(){return o}});var t=a(57437),l=a(33866),r=a(2265),n=a(9245);function i(e){let s=s=>{"disableShowNewBadge"===s.key&&e()},a=s=>{let{key:a}=s.detail;"disableShowNewBadge"===a&&e()};return window.addEventListener("storage",s),window.addEventListener(n.Qg,a),()=>{window.removeEventListener("storage",s),window.removeEventListener(n.Qg,a)}}function d(){return"true"===(0,n.le)("disableShowNewBadge")}function o(e){let{children:s}=e;return(0,r.useSyncExternalStore)(i,d)?s?(0,t.jsx)(t.Fragment,{children:s}):null:s?(0,t.jsx)(l.Z,{color:"blue",count:"New",children:s}):(0,t.jsx)(l.Z,{color:"blue",count:"New"})}},12363:function(e,s,a){a.d(s,{d:function(){return r},n:function(){return l}});var t=a(2265);let l=()=>{let[e,s]=(0,t.useState)("http://localhost:4000");return(0,t.useEffect)(()=>{{let{protocol:e,host:a}=window.location;s("".concat(e,"//").concat(a))}},[]),e},r=25},30841:function(e,s,a){a.d(s,{IE:function(){return r},LO:function(){return l},cT:function(){return n}});var t=a(19250);let l=async e=>{if(!e)return[];try{let{aliases:s}=await (0,t.keyAliasesCall)(e);return Array.from(new Set((s||[]).filter(Boolean)))}catch(e){return console.error("Error fetching all key aliases:",e),[]}},r=async(e,s)=>{if(!e)return[];try{let a=[],l=1,r=!0;for(;r;){let n=await (0,t.teamListCall)(e,s||null,null);a=[...a,...n],l{if(!e)return[];try{let s=[],a=1,l=!0;for(;l;){let r=await (0,t.organizationListCall)(e);s=[...s,...r],a{let{options:s,onApplyFilters:a,onResetFilters:o,initialValues:m={},buttonLabel:x="Filters"}=e,[u,h]=(0,l.useState)(!1),[g,p]=(0,l.useState)(m),[j,f]=(0,l.useState)({}),[v,y]=(0,l.useState)({}),[b,N]=(0,l.useState)({}),[w,_]=(0,l.useState)({}),k=(0,l.useCallback)(c()(async(e,s)=>{if(s.isSearchable&&s.searchFn){y(e=>({...e,[s.name]:!0}));try{let a=await s.searchFn(e);f(e=>({...e,[s.name]:a}))}catch(e){console.error("Error searching:",e),f(e=>({...e,[s.name]:[]}))}finally{y(e=>({...e,[s.name]:!1}))}}},300),[]),S=(0,l.useCallback)(async e=>{if(e.isSearchable&&e.searchFn&&!w[e.name]){y(s=>({...s,[e.name]:!0})),_(s=>({...s,[e.name]:!0}));try{let s=await e.searchFn("");f(a=>({...a,[e.name]:s}))}catch(s){console.error("Error loading initial options:",s),f(s=>({...s,[e.name]:[]}))}finally{y(s=>({...s,[e.name]:!1}))}}},[w]);(0,l.useEffect)(()=>{u&&s.forEach(e=>{e.isSearchable&&!w[e.name]&&S(e)})},[u,s,S,w]);let C=(e,s)=>{let t={...g,[e]:s};p(t),a(t)},L=(e,s)=>{e&&s.isSearchable&&!w[s.name]&&S(s)};return(0,t.jsxs)("div",{className:"w-full",children:[(0,t.jsxs)("div",{className:"flex items-center gap-2 mb-6",children:[(0,t.jsx)(r.ZP,{icon:(0,t.jsx)(d.Z,{className:"h-4 w-4"}),onClick:()=>h(!u),className:"flex items-center gap-2",children:x}),(0,t.jsx)(r.ZP,{onClick:()=>{let e={};s.forEach(s=>{e[s.name]=""}),p(e),o()},children:"Reset Filters"})]}),u&&(0,t.jsx)("div",{className:"grid grid-cols-3 gap-x-6 gap-y-4 mb-6",children:["Team ID","Status","Organization ID","Key Alias","User ID","End User","Error Code","Key Hash","Model"].map(e=>{let a=s.find(s=>s.label===e||s.name===e);return a?(0,t.jsxs)("div",{className:"flex flex-col gap-2",children:[(0,t.jsx)("label",{className:"text-sm text-gray-600",children:a.label||a.name}),a.isSearchable?(0,t.jsx)(n.default,{showSearch:!0,className:"w-full",placeholder:"Search ".concat(a.label||a.name,"..."),value:g[a.name]||void 0,onChange:e=>C(a.name,e),onDropdownVisibleChange:e=>L(e,a),onSearch:e=>{N(s=>({...s,[a.name]:e})),a.searchFn&&k(e,a)},filterOption:!1,loading:v[a.name],options:j[a.name]||[],allowClear:!0,notFoundContent:v[a.name]?"Loading...":"No results found"}):a.options?(0,t.jsx)(n.default,{className:"w-full",placeholder:"Select ".concat(a.label||a.name,"..."),value:g[a.name]||void 0,onChange:e=>C(a.name,e),allowClear:!0,children:a.options.map(e=>(0,t.jsx)(n.default.Option,{value:e.value,children:e.label},e.value))}):(0,t.jsx)(i.default,{className:"w-full",placeholder:"Enter ".concat(a.label||a.name,"..."),value:g[a.name]||"",onChange:e=>C(a.name,e.target.value),allowClear:!0})]},a.name):null})})]})}},31901:function(e,s,a){a.d(s,{I:function(){return eU},Z:function(){return eV}});var t=a(57437),l=a(77398),r=a.n(l),n=a(11713),i=a(2265),d=a(29827),o=a(19250),c=a(12322),m=a(59872),x=a(41649),u=a(78489),h=a(99981),g=a(42673);let p=e=>{try{return new Date(e).toLocaleString("en-US",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!0}).replace(",","")}catch(e){return"Error converting time"}},j=e=>{let{utcTime:s}=e;return(0,t.jsx)("span",{style:{fontFamily:"monospace",width:"180px",display:"inline-block"},children:p(s)})},f=(e,s)=>{var a,t;return(null===(t=e.metadata)||void 0===t?void 0:null===(a=t.mcp_tool_call_metadata)||void 0===a?void 0:a.mcp_server_logo_url)?e.metadata.mcp_tool_call_metadata.mcp_server_logo_url:s?(0,g.dr)(s).logo:""},v=[{id:"expander",header:()=>null,cell:e=>{let{row:s}=e;return(0,t.jsx)(()=>{let[e,a]=i.useState(s.getIsExpanded()),l=i.useCallback(()=>{a(e=>!e),s.getToggleExpandedHandler()()},[s]);return s.getCanExpand()?(0,t.jsx)("button",{onClick:l,style:{cursor:"pointer"},"aria-label":e?"Collapse row":"Expand row",className:"w-6 h-6 flex items-center justify-center focus:outline-none",children:(0,t.jsx)("svg",{className:"w-4 h-4 transform transition-transform duration-75 ".concat(e?"rotate-90":""),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg",children:(0,t.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M9 5l7 7-7 7"})})}):(0,t.jsx)("span",{className:"w-6 h-6 flex items-center justify-center",children:"●"})},{})}},{header:"Time",accessorKey:"startTime",cell:e=>(0,t.jsx)(j,{utcTime:e.getValue()})},{header:"Status",accessorKey:"metadata.status",cell:e=>{let s="failure"!==(e.getValue()||"Success").toLowerCase();return(0,t.jsx)("span",{className:"px-2 py-1 rounded-md text-xs font-medium inline-block text-center w-16 ".concat(s?"bg-green-100 text-green-800":"bg-red-100 text-red-800"),children:s?"Success":"Failure"})}},{header:"Session ID",accessorKey:"session_id",cell:e=>{let s=String(e.getValue()||""),a=e.row.original.onSessionClick;return(0,t.jsx)(h.Z,{title:String(e.getValue()||""),children:(0,t.jsx)(u.Z,{size:"xs",variant:"light",className:"font-mono text-blue-500 bg-blue-50 hover:bg-blue-100 text-xs font-normal text-xs max-w-[15ch] truncate block",onClick:()=>null==a?void 0:a(s),children:String(e.getValue()||"")})})}},{header:"Request ID",accessorKey:"request_id",cell:e=>(0,t.jsx)(h.Z,{title:String(e.getValue()||""),children:(0,t.jsx)("span",{className:"font-mono text-xs max-w-[15ch] truncate block",children:String(e.getValue()||"")})})},{header:"Cost",accessorKey:"spend",cell:e=>(0,t.jsx)(h.Z,{title:"$".concat(String(e.getValue()||0)," "),children:(0,t.jsx)("span",{children:(0,m.GS)(e.getValue()||0)})})},{header:"Duration (s)",accessorKey:"duration",cell:e=>(0,t.jsx)(h.Z,{title:String(e.getValue()||"-"),children:(0,t.jsx)("span",{className:"max-w-[15ch] truncate block",children:String(e.getValue()||"-")})})},{header:"Team Name",accessorKey:"metadata.user_api_key_team_alias",cell:e=>(0,t.jsx)(h.Z,{title:String(e.getValue()||"-"),children:(0,t.jsx)("span",{className:"max-w-[15ch] truncate block",children:String(e.getValue()||"-")})})},{header:"Key Hash",accessorKey:"metadata.user_api_key",cell:e=>{let s=String(e.getValue()||"-"),a=e.row.original.onKeyHashClick;return(0,t.jsx)(h.Z,{title:s,children:(0,t.jsx)("span",{className:"font-mono max-w-[15ch] truncate block cursor-pointer hover:text-blue-600",onClick:()=>null==a?void 0:a(s),children:s})})}},{header:"Key Name",accessorKey:"metadata.user_api_key_alias",cell:e=>(0,t.jsx)(h.Z,{title:String(e.getValue()||"-"),children:(0,t.jsx)("span",{className:"max-w-[15ch] truncate block",children:String(e.getValue()||"-")})})},{header:"Model",accessorKey:"model",cell:e=>{let s=e.row.original,a=s.custom_llm_provider,l=String(e.getValue()||"");return(0,t.jsxs)("div",{className:"flex items-center space-x-2",children:[a&&(0,t.jsx)("img",{src:f(s,a),alt:"",className:"w-4 h-4",onError:e=>{e.target.style.display="none"}}),(0,t.jsx)(h.Z,{title:l,children:(0,t.jsx)("span",{className:"max-w-[15ch] truncate block",children:l})})]})}},{header:"Tokens",accessorKey:"total_tokens",cell:e=>{let s=e.row.original;return(0,t.jsxs)("span",{className:"text-sm",children:[String(s.total_tokens||"0"),(0,t.jsxs)("span",{className:"text-gray-400 text-xs ml-1",children:["(",String(s.prompt_tokens||"0"),"+",String(s.completion_tokens||"0"),")"]})]})}},{header:"Internal User",accessorKey:"user",cell:e=>(0,t.jsx)(h.Z,{title:String(e.getValue()||"-"),children:(0,t.jsx)("span",{className:"max-w-[15ch] truncate block",children:String(e.getValue()||"-")})})},{header:"End User",accessorKey:"end_user",cell:e=>(0,t.jsx)(h.Z,{title:String(e.getValue()||"-"),children:(0,t.jsx)("span",{className:"max-w-[15ch] truncate block",children:String(e.getValue()||"-")})})},{header:"Tags",accessorKey:"request_tags",cell:e=>{let s=e.getValue();if(!s||0===Object.keys(s).length)return"-";let a=Object.entries(s),l=a[0],r=a.slice(1);return(0,t.jsx)("div",{className:"flex flex-wrap gap-1",children:(0,t.jsx)(h.Z,{title:(0,t.jsx)("div",{className:"flex flex-col gap-1",children:a.map(e=>{let[s,a]=e;return(0,t.jsxs)("span",{children:[s,": ",String(a)]},s)})}),children:(0,t.jsxs)("span",{className:"px-2 py-1 bg-gray-100 rounded-full text-xs",children:[l[0],": ",String(l[1]),r.length>0&&" +".concat(r.length)]})})})}}],y=e=>(0,t.jsx)(x.Z,{color:"gray",className:"flex items-center gap-1",children:(0,t.jsx)("span",{className:"whitespace-nowrap text-xs",children:e})}),b=[{id:"expander",header:()=>null,cell:e=>{let{row:s}=e;return(0,t.jsx)(()=>{let[e,a]=i.useState(s.getIsExpanded()),l=i.useCallback(()=>{a(e=>!e),s.getToggleExpandedHandler()()},[s]);return s.getCanExpand()?(0,t.jsx)("button",{onClick:l,style:{cursor:"pointer"},"aria-label":e?"Collapse row":"Expand row",className:"w-6 h-6 flex items-center justify-center focus:outline-none",children:(0,t.jsx)("svg",{className:"w-4 h-4 transform transition-transform ".concat(e?"rotate-90":""),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg",children:(0,t.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M9 5l7 7-7 7"})})}):(0,t.jsx)("span",{className:"w-6 h-6 flex items-center justify-center",children:"●"})},{})}},{header:"Timestamp",accessorKey:"updated_at",cell:e=>(0,t.jsx)(j,{utcTime:e.getValue()})},{header:"Table Name",accessorKey:"table_name",cell:e=>{let s=e.getValue(),a=s;switch(s){case"LiteLLM_VerificationToken":a="Keys";break;case"LiteLLM_TeamTable":a="Teams";break;case"LiteLLM_OrganizationTable":a="Organizations";break;case"LiteLLM_UserTable":a="Users";break;case"LiteLLM_ProxyModelTable":a="Models";break;default:a=s}return(0,t.jsx)("span",{children:a})}},{header:"Action",accessorKey:"action",cell:e=>(0,t.jsx)("span",{children:y(e.getValue())})},{header:"Changed By",accessorKey:"changed_by",cell:e=>{let s=e.row.original.changed_by,a=e.row.original.changed_by_api_key;return(0,t.jsxs)("div",{className:"space-y-1",children:[(0,t.jsx)("div",{className:"font-medium",children:s}),a&&(0,t.jsx)(h.Z,{title:a,children:(0,t.jsxs)("div",{className:"text-xs text-muted-foreground max-w-[15ch] truncate",children:[" ",a]})})]})}},{header:"Affected Item ID",accessorKey:"object_id",cell:e=>(0,t.jsx)(()=>{let s=e.getValue(),[a,l]=(0,i.useState)(!1);if(!s)return(0,t.jsx)(t.Fragment,{children:"-"});let r=async()=>{try{await navigator.clipboard.writeText(String(s)),l(!0),setTimeout(()=>l(!1),1500)}catch(e){console.error("Failed to copy object ID: ",e)}};return(0,t.jsx)(h.Z,{title:a?"Copied!":String(s),children:(0,t.jsx)("span",{className:"max-w-[20ch] truncate block cursor-pointer hover:text-blue-600",onClick:r,children:String(s)})})},{})}],N=async(e,s,a,t)=>{console.log("prefetchLogDetails called with",e.length,"logs");let l=e.map(e=>{if(e.request_id)return console.log("Prefetching details for request_id:",e.request_id),t.prefetchQuery({queryKey:["logDetails",e.request_id,s],queryFn:async()=>{console.log("Fetching details for",e.request_id);let t=await (0,o.uiSpendLogDetailsCall)(a,e.request_id,s);return console.log("Received details for",e.request_id,":",t?"success":"failed"),t},staleTime:6e5,gcTime:6e5})});try{let e=await Promise.all(l);return console.log("All prefetch promises completed:",e.length),e}catch(e){throw console.error("Error in prefetchLogDetails:",e),e}};var w=a(9114),_=a(86669);function k(e){let{row:s,hasMessages:a,hasResponse:l,hasError:r,errorInfo:n,getRawRequest:i,formattedResponse:d}=e,o=async e=>{try{if(navigator.clipboard&&window.isSecureContext)return await navigator.clipboard.writeText(e),!0;{let s=document.createElement("textarea");s.value=e,s.style.position="fixed",s.style.opacity="0",document.body.appendChild(s),s.focus(),s.select();let a=document.execCommand("copy");if(document.body.removeChild(s),!a)throw Error("execCommand failed");return!0}}catch(e){return console.error("Copy failed:",e),!1}},c=async()=>{await o(JSON.stringify(i(),null,2))?w.Z.success("Request copied to clipboard"):w.Z.fromBackend("Failed to copy request")},m=async()=>{await o(JSON.stringify(d(),null,2))?w.Z.success("Response copied to clipboard"):w.Z.fromBackend("Failed to copy response")};return(0,t.jsxs)("div",{className:"grid grid-cols-1 lg:grid-cols-2 gap-4 w-full max-w-full overflow-hidden box-border",children:[(0,t.jsxs)("div",{className:"bg-white rounded-lg shadow w-full max-w-full overflow-hidden",children:[(0,t.jsxs)("div",{className:"flex justify-between items-center p-4 border-b",children:[(0,t.jsx)("h3",{className:"text-lg font-medium",children:"Request"}),(0,t.jsx)("button",{onClick:c,className:"p-1 hover:bg-gray-200 rounded",title:"Copy request",children:(0,t.jsxs)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[(0,t.jsx)("rect",{x:"9",y:"9",width:"13",height:"13",rx:"2",ry:"2"}),(0,t.jsx)("path",{d:"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"})]})})]}),(0,t.jsx)("div",{className:"p-4 overflow-auto max-h-96 w-full max-w-full box-border",children:(0,t.jsx)("div",{className:"[&_[role='tree']]:bg-white [&_[role='tree']]:text-slate-900",children:(0,t.jsx)(_.gc,{data:i(),style:_.jF,clickToExpandNode:!0})})})]}),(0,t.jsxs)("div",{className:"bg-white rounded-lg shadow w-full max-w-full overflow-hidden",children:[(0,t.jsxs)("div",{className:"flex justify-between items-center p-4 border-b",children:[(0,t.jsxs)("h3",{className:"text-lg font-medium",children:["Response",r&&(0,t.jsxs)("span",{className:"ml-2 text-sm text-red-600",children:["• HTTP code ",(null==n?void 0:n.error_code)||400]})]}),(0,t.jsx)("button",{onClick:m,className:"p-1 hover:bg-gray-200 rounded",title:"Copy response",disabled:!l,children:(0,t.jsxs)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[(0,t.jsx)("rect",{x:"9",y:"9",width:"13",height:"13",rx:"2",ry:"2"}),(0,t.jsx)("path",{d:"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"})]})})]}),(0,t.jsx)("div",{className:"p-4 overflow-auto max-h-96 w-full max-w-full box-border",children:l?(0,t.jsx)("div",{className:"[&_[role='tree']]:bg-white [&_[role='tree']]:text-slate-900",children:(0,t.jsx)(_.gc,{data:d(),style:_.jF,clickToExpandNode:!0})}):(0,t.jsx)("div",{className:"text-gray-500 text-sm italic text-center py-4",children:"Response data not available"})})]})]})}a(52621);let S=e=>{var s;let{errorInfo:a}=e,[l,r]=i.useState({}),[n,d]=i.useState(!1),o=e=>{r(s=>({...s,[e]:!s[e]}))},c=a.traceback&&(s=a.traceback)?Array.from(s.matchAll(/File "([^"]+)", line (\d+)/g)).map(e=>{let a=e[1],t=e[2],l=a.split("/").pop()||a,r=e.index||0,n=s.indexOf('File "',r+1),i=n>-1?s.substring(r,n).trim():s.substring(r).trim(),d=i.split("\n"),o="";return d.length>1&&(o=d[d.length-1].trim()),{filePath:a,fileName:l,lineNumber:t,code:o,inFunction:i.includes(" in ")?i.split(" in ")[1].split("\n")[0]:""}}):[];return(0,t.jsxs)("div",{className:"bg-white rounded-lg shadow",children:[(0,t.jsx)("div",{className:"p-4 border-b",children:(0,t.jsxs)("h3",{className:"text-lg font-medium flex items-center text-red-600",children:[(0,t.jsx)("svg",{className:"w-5 h-5 mr-2",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,t.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"})}),"Error Details"]})}),(0,t.jsxs)("div",{className:"p-4",children:[(0,t.jsxs)("div",{className:"bg-red-50 rounded-md p-4 mb-4",children:[(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"text-red-800 font-medium w-20",children:"Type:"}),(0,t.jsx)("span",{className:"text-red-700",children:a.error_class||"Unknown Error"})]}),(0,t.jsxs)("div",{className:"flex mt-2",children:[(0,t.jsx)("span",{className:"text-red-800 font-medium w-20 flex-shrink-0",children:"Message:"}),(0,t.jsx)("span",{className:"text-red-700 break-words whitespace-pre-wrap",children:a.error_message||"Unknown error occurred"})]})]}),a.traceback&&(0,t.jsxs)("div",{className:"mt-4",children:[(0,t.jsxs)("div",{className:"flex justify-between items-center mb-2",children:[(0,t.jsx)("h4",{className:"font-medium",children:"Traceback"}),(0,t.jsxs)("div",{className:"flex items-center space-x-4",children:[(0,t.jsx)("button",{onClick:()=>{let e=!n;if(d(e),c.length>0){let s={};c.forEach((a,t)=>{s[t]=e}),r(s)}},className:"text-gray-500 hover:text-gray-700 flex items-center text-sm",children:n?"Collapse All":"Expand All"}),(0,t.jsxs)("button",{onClick:()=>navigator.clipboard.writeText(a.traceback||""),className:"text-gray-500 hover:text-gray-700 flex items-center",title:"Copy traceback",children:[(0,t.jsxs)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[(0,t.jsx)("rect",{x:"9",y:"9",width:"13",height:"13",rx:"2",ry:"2"}),(0,t.jsx)("path",{d:"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"})]}),(0,t.jsx)("span",{className:"ml-1",children:"Copy"})]})]})]}),(0,t.jsx)("div",{className:"bg-white rounded-md border border-gray-200 overflow-hidden shadow-sm",children:c.map((e,s)=>(0,t.jsxs)("div",{className:"border-b border-gray-200 last:border-b-0",children:[(0,t.jsxs)("div",{className:"px-4 py-2 flex items-center justify-between cursor-pointer hover:bg-gray-50",onClick:()=>o(s),children:[(0,t.jsxs)("div",{className:"flex items-center",children:[(0,t.jsx)("span",{className:"text-gray-400 mr-2 w-12 text-right",children:e.lineNumber}),(0,t.jsx)("span",{className:"text-gray-600 font-medium",children:e.fileName}),(0,t.jsx)("span",{className:"text-gray-500 mx-1",children:"in"}),(0,t.jsx)("span",{className:"text-indigo-600 font-medium",children:e.inFunction||e.fileName})]}),(0,t.jsx)("svg",{className:"w-5 h-5 text-gray-500 transition-transform ".concat(l[s]?"transform rotate-180":""),fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",children:(0,t.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M19 9l-7 7-7-7"})})]}),(l[s]||!1)&&e.code&&(0,t.jsx)("div",{className:"px-12 py-2 font-mono text-sm text-gray-800 bg-gray-50 overflow-x-auto border-t border-gray-100",children:e.code})]},s))})]})]})]})};var C=a(20347);let L=e=>{let{show:s}=e;return s?(0,t.jsxs)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-4 flex items-start",children:[(0,t.jsx)("div",{className:"text-blue-500 mr-3 flex-shrink-0 mt-0.5",children:(0,t.jsxs)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[(0,t.jsx)("circle",{cx:"12",cy:"12",r:"10"}),(0,t.jsx)("line",{x1:"12",y1:"16",x2:"12",y2:"12"}),(0,t.jsx)("line",{x1:"12",y1:"8",x2:"12.01",y2:"8"})]})}),(0,t.jsxs)("div",{children:[(0,t.jsx)("h4",{className:"text-sm font-medium text-blue-800",children:"Request/Response Data Not Available"}),(0,t.jsxs)("p",{className:"text-sm text-blue-700 mt-1",children:["To view request and response details, enable prompt storage in your LiteLLM configuration by adding the following to your ",(0,t.jsx)("code",{className:"bg-blue-100 px-1 py-0.5 rounded",children:"proxy_config.yaml"})," file:"]}),(0,t.jsx)("pre",{className:"mt-2 bg-white p-3 rounded border border-blue-200 text-xs font-mono overflow-auto",children:"general_settings:\n store_model_in_db: true\n store_prompts_in_spend_logs: true"}),(0,t.jsx)("p",{className:"text-xs text-blue-700 mt-2",children:"Note: This will only affect new requests after the configuration change."})]})]}):null};var D=a(50665),M=a(12514),E=a(35829),T=a(84264),A=a(96761),R=a(10900),z=a(5545),O=a(30401),Z=a(78867);let I=e=>{let{sessionId:s,logs:a,onBack:l}=e,[r,n]=(0,i.useState)(null),[d,o]=(0,i.useState)({}),x=a.reduce((e,s)=>e+(s.spend||0),0),g=a.reduce((e,s)=>e+(s.total_tokens||0),0),p=a.reduce((e,s)=>{var a,t;return e+((null===(t=s.metadata)||void 0===t?void 0:null===(a=t.additional_usage_values)||void 0===a?void 0:a.cache_read_input_tokens)||0)},0),j=a.reduce((e,s)=>{var a,t;return e+((null===(t=s.metadata)||void 0===t?void 0:null===(a=t.additional_usage_values)||void 0===a?void 0:a.cache_creation_input_tokens)||0)},0),f=g+p+j,y=a.length>0?new Date(a[0].startTime):new Date;(((a.length>0?new Date(a[a.length-1].endTime):new Date).getTime()-y.getTime())/1e3).toFixed(2),a.map(e=>({time:new Date(e.startTime).toISOString(),tokens:e.total_tokens||0,cost:e.spend||0}));let b=async(e,s)=>{await (0,m.vQ)(e)&&(o(e=>({...e,[s]:!0})),setTimeout(()=>{o(e=>({...e,[s]:!1}))},2e3))};return(0,t.jsxs)("div",{className:"space-y-6",children:[(0,t.jsxs)("div",{className:"mb-8",children:[(0,t.jsx)(u.Z,{icon:R.Z,variant:"light",onClick:l,className:"mb-4",children:"Back to All Logs"}),(0,t.jsxs)("div",{className:"mt-4",children:[(0,t.jsx)("h1",{className:"text-2xl font-semibold text-gray-900",children:"Session Details"}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsxs)("div",{className:"flex items-center cursor-pointer",children:[(0,t.jsx)("p",{className:"text-sm text-gray-500 font-mono",children:s}),(0,t.jsx)(z.ZP,{type:"text",size:"small",icon:d["session-id"]?(0,t.jsx)(O.Z,{size:12}):(0,t.jsx)(Z.Z,{size:12}),onClick:()=>b(s,"session-id"),className:"left-2 z-10 transition-all duration-200 ".concat(d["session-id"]?"text-green-600 bg-green-50 border-green-200":"text-gray-500 hover:text-gray-700 hover:bg-gray-100")})]}),(0,t.jsxs)("a",{href:"https://docs.litellm.ai/docs/proxy/ui_logs_sessions",target:"_blank",rel:"noopener noreferrer",className:"text-sm text-blue-600 hover:text-blue-800 flex items-center gap-1",children:["Get started with session management here",(0,t.jsx)("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,t.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"})})]})]})]})]}),(0,t.jsxs)("div",{className:"grid grid-cols-1 md:grid-cols-4 gap-4",children:[(0,t.jsxs)(M.Z,{children:[(0,t.jsx)(T.Z,{children:"Total Requests"}),(0,t.jsx)(E.Z,{children:a.length})]}),(0,t.jsxs)(M.Z,{children:[(0,t.jsx)(T.Z,{children:"Total Cost"}),(0,t.jsxs)(E.Z,{children:["$",(0,m.pw)(x,6)]})]}),(0,t.jsx)(h.Z,{title:(0,t.jsxs)("div",{className:"text-white min-w-[200px]",children:[(0,t.jsx)("div",{className:"text-lg font-medium mb-3",children:"Usage breakdown"}),(0,t.jsxs)("div",{className:"space-y-4",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)("div",{className:"text-base font-medium mb-2",children:"Input usage:"}),(0,t.jsxs)("div",{className:"space-y-2 text-sm text-gray-300",children:[(0,t.jsxs)("div",{className:"flex justify-between",children:[(0,t.jsx)("span",{children:"input:"}),(0,t.jsx)("span",{className:"ml-8",children:(0,m.pw)(a.reduce((e,s)=>e+(s.prompt_tokens||0),0))})]}),p>0&&(0,t.jsxs)("div",{className:"flex justify-between",children:[(0,t.jsx)("span",{children:"input_cached_tokens:"}),(0,t.jsx)("span",{className:"ml-8",children:(0,m.pw)(p)})]}),j>0&&(0,t.jsxs)("div",{className:"flex justify-between",children:[(0,t.jsx)("span",{children:"input_cache_creation_tokens:"}),(0,t.jsx)("span",{className:"ml-8",children:(0,m.pw)(j)})]})]})]}),(0,t.jsxs)("div",{className:"border-t border-gray-600 pt-3",children:[(0,t.jsx)("div",{className:"text-base font-medium mb-2",children:"Output usage:"}),(0,t.jsx)("div",{className:"space-y-2 text-sm text-gray-300",children:(0,t.jsxs)("div",{className:"flex justify-between",children:[(0,t.jsx)("span",{children:"output:"}),(0,t.jsx)("span",{className:"ml-8",children:(0,m.pw)(a.reduce((e,s)=>e+(s.completion_tokens||0),0))})]})})]}),(0,t.jsx)("div",{className:"border-t border-gray-600 pt-3",children:(0,t.jsxs)("div",{className:"flex justify-between items-center",children:[(0,t.jsx)("span",{className:"text-base font-medium",children:"Total usage:"}),(0,t.jsx)("span",{className:"text-sm text-gray-300",children:(0,m.pw)(f)})]})})]})]}),placement:"top",overlayStyle:{minWidth:"300px"},children:(0,t.jsxs)(M.Z,{children:[(0,t.jsxs)("div",{className:"flex items-center justify-between",children:[(0,t.jsx)(T.Z,{children:"Total Tokens"}),(0,t.jsx)("span",{className:"text-gray-400 text-sm",children:"ⓘ"})]}),(0,t.jsx)(E.Z,{children:(0,m.pw)(f)})]})})]}),(0,t.jsx)(A.Z,{children:"Session Logs"}),(0,t.jsx)("div",{className:"mt-4",children:(0,t.jsx)(c.w,{columns:v,data:a,renderSubComponent:eU,getRowCanExpand:()=>!0,loadingMessage:"Loading logs...",noDataMessage:"No logs found"})})]})};function K(e){let{data:s}=e,[a,l]=(0,i.useState)(!0),[r,n]=(0,i.useState)({});if(!s||0===s.length)return null;let d=e=>new Date(1e3*e).toLocaleString(),o=(e,s)=>"".concat(((s-e)*1e3).toFixed(2),"ms"),c=(e,s)=>{let a="".concat(e,"-").concat(s);n(e=>({...e,[a]:!e[a]}))};return(0,t.jsxs)("div",{className:"bg-white rounded-lg shadow mb-6",children:[(0,t.jsxs)("div",{className:"flex justify-between items-center p-4 border-b cursor-pointer hover:bg-gray-50",onClick:()=>l(!a),children:[(0,t.jsxs)("div",{className:"flex items-center",children:[(0,t.jsx)("svg",{className:"w-5 h-5 mr-2 text-gray-600 transition-transform ".concat(a?"transform rotate-90":""),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,t.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M9 5l7 7-7 7"})}),(0,t.jsx)("h3",{className:"text-lg font-medium",children:"Vector Store Requests"})]}),(0,t.jsx)("span",{className:"text-sm text-gray-500",children:a?"Click to collapse":"Click to expand"})]}),a&&(0,t.jsx)("div",{className:"p-4",children:s.map((e,s)=>(0,t.jsxs)("div",{className:"mb-6 last:mb-0",children:[(0,t.jsx)("div",{className:"bg-white rounded-lg border p-4 mb-4",children:(0,t.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Query:"}),(0,t.jsx)("span",{className:"font-mono",children:e.query})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Vector Store ID:"}),(0,t.jsx)("span",{className:"font-mono",children:e.vector_store_id})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Provider:"}),(0,t.jsx)("span",{className:"flex items-center",children:(()=>{let{logo:s,displayName:a}=(0,g.dr)(e.custom_llm_provider);return(0,t.jsxs)(t.Fragment,{children:[s&&(0,t.jsx)("img",{src:s,alt:"".concat(a," logo"),className:"h-5 w-5 mr-2"}),a]})})()})]})]}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Start Time:"}),(0,t.jsx)("span",{children:d(e.start_time)})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"End Time:"}),(0,t.jsx)("span",{children:d(e.end_time)})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Duration:"}),(0,t.jsx)("span",{children:o(e.start_time,e.end_time)})]})]})]})}),(0,t.jsx)("h4",{className:"font-medium mb-2",children:"Search Results"}),(0,t.jsx)("div",{className:"space-y-2",children:e.vector_store_search_response.data.map((e,a)=>{let l=r["".concat(s,"-").concat(a)]||!1;return(0,t.jsxs)("div",{className:"border rounded-lg overflow-hidden",children:[(0,t.jsxs)("div",{className:"flex items-center p-3 bg-gray-50 cursor-pointer",onClick:()=>c(s,a),children:[(0,t.jsx)("svg",{className:"w-5 h-5 mr-2 transition-transform ".concat(l?"transform rotate-90":""),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,t.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M9 5l7 7-7 7"})}),(0,t.jsxs)("div",{className:"flex items-center",children:[(0,t.jsxs)("span",{className:"font-medium mr-2",children:["Result ",a+1]}),(0,t.jsxs)("span",{className:"text-gray-500 text-sm",children:["Score: ",(0,t.jsx)("span",{className:"font-mono",children:e.score.toFixed(4)})]})]})]}),l&&(0,t.jsx)("div",{className:"p-3 border-t bg-white",children:e.content.map((e,s)=>(0,t.jsxs)("div",{className:"mb-2 last:mb-0",children:[(0,t.jsx)("div",{className:"text-xs text-gray-500 mb-1",children:e.type}),(0,t.jsx)("pre",{className:"text-xs font-mono whitespace-pre-wrap break-all bg-gray-50 p-2 rounded",children:e.text})]},s))})]},a)})})]},s))})]})}let H=e=>e>=.8?"text-green-600":"text-yellow-600";var P=e=>{let{entities:s}=e,[a,l]=(0,i.useState)(!0),[r,n]=(0,i.useState)({}),d=e=>{n(s=>({...s,[e]:!s[e]}))};return s&&0!==s.length?(0,t.jsxs)("div",{className:"mt-4",children:[(0,t.jsxs)("div",{className:"flex items-center mb-2 cursor-pointer",onClick:()=>l(!a),children:[(0,t.jsx)("svg",{className:"w-5 h-5 mr-2 transition-transform ".concat(a?"transform rotate-90":""),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,t.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M9 5l7 7-7 7"})}),(0,t.jsxs)("h4",{className:"font-medium",children:["Detected Entities (",s.length,")"]})]}),a&&(0,t.jsx)("div",{className:"space-y-2",children:s.map((e,s)=>{let a=r[s]||!1;return(0,t.jsxs)("div",{className:"border rounded-lg overflow-hidden",children:[(0,t.jsxs)("div",{className:"flex items-center justify-between p-3 bg-gray-50 cursor-pointer hover:bg-gray-100",onClick:()=>d(s),children:[(0,t.jsxs)("div",{className:"flex items-center",children:[(0,t.jsx)("svg",{className:"w-5 h-5 mr-2 transition-transform ".concat(a?"transform rotate-90":""),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,t.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M9 5l7 7-7 7"})}),(0,t.jsx)("span",{className:"font-medium mr-2",children:e.entity_type}),(0,t.jsxs)("span",{className:"font-mono ".concat(H(e.score)),children:["Score: ",e.score.toFixed(2)]})]}),(0,t.jsxs)("span",{className:"text-xs text-gray-500",children:["Position: ",e.start,"-",e.end]})]}),a&&(0,t.jsx)("div",{className:"p-3 border-t bg-white",children:(0,t.jsxs)("div",{className:"grid grid-cols-2 gap-4 mb-2",children:[(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Entity Type:"}),(0,t.jsx)("span",{children:e.entity_type})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Position:"}),(0,t.jsxs)("span",{children:["Characters ",e.start,"-",e.end]})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Confidence:"}),(0,t.jsx)("span",{className:H(e.score),children:e.score.toFixed(2)})]})]}),(0,t.jsxs)("div",{className:"space-y-2",children:[e.recognition_metadata&&(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Recognizer:"}),(0,t.jsx)("span",{children:e.recognition_metadata.recognizer_name})]}),(0,t.jsxs)("div",{className:"flex overflow-hidden",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Identifier:"}),(0,t.jsx)("span",{className:"truncate text-xs font-mono",children:e.recognition_metadata.recognizer_identifier})]})]}),e.analysis_explanation&&(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Explanation:"}),(0,t.jsx)("span",{children:e.analysis_explanation})]})]})]})})]},s)})})]}):null};let F=function(e){let s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"slate";return(0,t.jsx)("span",{className:"px-2 py-1 rounded-md text-xs font-medium inline-block ".concat({green:"bg-green-100 text-green-800",red:"bg-red-100 text-red-800",blue:"bg-blue-50 text-blue-700",slate:"bg-slate-100 text-slate-800",amber:"bg-amber-100 text-amber-800"}[s]),children:e})},q=e=>e?F("detected","red"):F("not detected","slate"),Y=e=>{let{title:s,count:a,defaultOpen:l=!0,right:r,children:n}=e,[d,o]=(0,i.useState)(l);return(0,t.jsxs)("div",{className:"border rounded-lg overflow-hidden",children:[(0,t.jsxs)("div",{className:"flex items-center justify-between p-3 bg-gray-50 cursor-pointer hover:bg-gray-100",onClick:()=>o(e=>!e),children:[(0,t.jsxs)("div",{className:"flex items-center",children:[(0,t.jsx)("svg",{className:"w-5 h-5 mr-2 transition-transform ".concat(d?"transform rotate-90":""),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,t.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M9 5l7 7-7 7"})}),(0,t.jsxs)("h5",{className:"font-medium",children:[s," ","number"==typeof a&&(0,t.jsxs)("span",{className:"text-gray-500 font-normal",children:["(",a,")"]})]})]}),(0,t.jsx)("div",{children:r})]}),d&&(0,t.jsx)("div",{className:"p-3 border-t bg-white",children:n})]})},B=e=>{let{label:s,children:a,mono:l}=e;return(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:s}),(0,t.jsx)("span",{className:l?"font-mono text-sm break-all":"",children:a})]})},V=()=>(0,t.jsx)("div",{className:"my-3 border-t"});var U=e=>{var s,a,l,r,n,i,d,o,c,m;let{response:x}=e;if(!x)return null;let u=null!==(n=null!==(r=x.outputs)&&void 0!==r?r:x.output)&&void 0!==n?n:[],h="GUARDRAIL_INTERVENED"===x.action?"red":"green",g=(0,t.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(null===(s=x.guardrailCoverage)||void 0===s?void 0:s.textCharacters)&&F("text guarded ".concat(null!==(i=x.guardrailCoverage.textCharacters.guarded)&&void 0!==i?i:0,"/").concat(null!==(d=x.guardrailCoverage.textCharacters.total)&&void 0!==d?d:0),"blue"),(null===(a=x.guardrailCoverage)||void 0===a?void 0:a.images)&&F("images guarded ".concat(null!==(o=x.guardrailCoverage.images.guarded)&&void 0!==o?o:0,"/").concat(null!==(c=x.guardrailCoverage.images.total)&&void 0!==c?c:0),"blue")]}),p=x.usage&&(0,t.jsx)("div",{className:"flex flex-wrap gap-2",children:Object.entries(x.usage).map(e=>{let[s,a]=e;return"number"==typeof a?(0,t.jsxs)("span",{className:"px-2 py-1 bg-slate-100 text-slate-800 rounded-md text-xs font-medium",children:[s,": ",a]},s):null})});return(0,t.jsxs)("div",{className:"space-y-3",children:[(0,t.jsxs)("div",{className:"border rounded-lg p-4",children:[(0,t.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsx)(B,{label:"Action:",children:F(null!==(m=x.action)&&void 0!==m?m:"N/A",h)}),x.actionReason&&(0,t.jsx)(B,{label:"Action Reason:",children:x.actionReason}),x.blockedResponse&&(0,t.jsx)(B,{label:"Blocked Response:",children:(0,t.jsx)("span",{className:"italic",children:x.blockedResponse})})]}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsx)(B,{label:"Coverage:",children:g}),(0,t.jsx)(B,{label:"Usage:",children:p})]})]}),u.length>0&&(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(V,{}),(0,t.jsx)("h4",{className:"font-medium mb-2",children:"Outputs"}),(0,t.jsx)("div",{className:"space-y-2",children:u.map((e,s)=>{var a;return(0,t.jsx)("div",{className:"p-3 bg-gray-50 rounded-md",children:(0,t.jsx)("div",{className:"text-sm whitespace-pre-wrap",children:null!==(a=e.text)&&void 0!==a?a:(0,t.jsx)("em",{children:"(non-text output)"})})},s)})})]})]}),(null===(l=x.assessments)||void 0===l?void 0:l.length)?(0,t.jsx)("div",{className:"space-y-3",children:x.assessments.map((e,s)=>{var a,l,r,n,i,d,o,c,m,x,u,h,g,p,j,f,v,y,b,N,w,_,k,S;let C=(0,t.jsxs)("div",{className:"flex flex-wrap gap-1",children:[e.wordPolicy&&F("word","slate"),e.contentPolicy&&F("content","slate"),e.topicPolicy&&F("topic","slate"),e.sensitiveInformationPolicy&&F("sensitive-info","slate"),e.contextualGroundingPolicy&&F("contextual-grounding","slate"),e.automatedReasoningPolicy&&F("automated-reasoning","slate")]});return(0,t.jsxs)(Y,{title:"Assessment #".concat(s+1),defaultOpen:!0,right:(0,t.jsxs)("div",{className:"flex items-center gap-3",children:[(null===(a=e.invocationMetrics)||void 0===a?void 0:a.guardrailProcessingLatency)!=null&&F("".concat(e.invocationMetrics.guardrailProcessingLatency," ms"),"amber"),C]}),children:[e.wordPolicy&&(0,t.jsxs)("div",{className:"mb-3",children:[(0,t.jsx)("h6",{className:"font-medium mb-2",children:"Word Policy"}),(null!==(f=null===(l=e.wordPolicy.customWords)||void 0===l?void 0:l.length)&&void 0!==f?f:0)>0&&(0,t.jsx)(Y,{title:"Custom Words",defaultOpen:!0,children:(0,t.jsx)("div",{className:"space-y-2",children:e.wordPolicy.customWords.map((e,s)=>{var a;return(0,t.jsxs)("div",{className:"flex justify-between items-center p-2 bg-gray-50 rounded",children:[(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[F(null!==(a=e.action)&&void 0!==a?a:"N/A",e.detected?"red":"slate"),(0,t.jsx)("span",{className:"font-mono text-sm break-all",children:e.match})]}),q(e.detected)]},s)})})}),(null!==(v=null===(r=e.wordPolicy.managedWordLists)||void 0===r?void 0:r.length)&&void 0!==v?v:0)>0&&(0,t.jsx)(Y,{title:"Managed Word Lists",defaultOpen:!1,children:(0,t.jsx)("div",{className:"space-y-2",children:e.wordPolicy.managedWordLists.map((e,s)=>{var a;return(0,t.jsxs)("div",{className:"flex justify-between items-center p-2 bg-gray-50 rounded",children:[(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[F(null!==(a=e.action)&&void 0!==a?a:"N/A",e.detected?"red":"slate"),(0,t.jsx)("span",{className:"font-mono text-sm break-all",children:e.match}),e.type&&F(e.type,"slate")]}),q(e.detected)]},s)})})})]}),(null===(i=e.contentPolicy)||void 0===i?void 0:null===(n=i.filters)||void 0===n?void 0:n.length)?(0,t.jsxs)("div",{className:"mb-3",children:[(0,t.jsx)("h6",{className:"font-medium mb-2",children:"Content Policy"}),(0,t.jsx)("div",{className:"overflow-x-auto",children:(0,t.jsxs)("table",{className:"min-w-full text-sm",children:[(0,t.jsx)("thead",{children:(0,t.jsxs)("tr",{className:"text-left text-gray-600",children:[(0,t.jsx)("th",{className:"py-1 pr-4",children:"Type"}),(0,t.jsx)("th",{className:"py-1 pr-4",children:"Action"}),(0,t.jsx)("th",{className:"py-1 pr-4",children:"Detected"}),(0,t.jsx)("th",{className:"py-1 pr-4",children:"Strength"}),(0,t.jsx)("th",{className:"py-1 pr-4",children:"Confidence"})]})}),(0,t.jsx)("tbody",{children:e.contentPolicy.filters.map((e,s)=>{var a,l,r,n;return(0,t.jsxs)("tr",{className:"border-t",children:[(0,t.jsx)("td",{className:"py-1 pr-4",children:null!==(a=e.type)&&void 0!==a?a:"—"}),(0,t.jsx)("td",{className:"py-1 pr-4",children:F(null!==(l=e.action)&&void 0!==l?l:"—",e.detected?"red":"slate")}),(0,t.jsx)("td",{className:"py-1 pr-4",children:q(e.detected)}),(0,t.jsx)("td",{className:"py-1 pr-4",children:null!==(r=e.filterStrength)&&void 0!==r?r:"—"}),(0,t.jsx)("td",{className:"py-1 pr-4",children:null!==(n=e.confidence)&&void 0!==n?n:"—"})]},s)})})]})})]}):null,(null===(o=e.contextualGroundingPolicy)||void 0===o?void 0:null===(d=o.filters)||void 0===d?void 0:d.length)?(0,t.jsxs)("div",{className:"mb-3",children:[(0,t.jsx)("h6",{className:"font-medium mb-2",children:"Contextual Grounding"}),(0,t.jsx)("div",{className:"overflow-x-auto",children:(0,t.jsxs)("table",{className:"min-w-full text-sm",children:[(0,t.jsx)("thead",{children:(0,t.jsxs)("tr",{className:"text-left text-gray-600",children:[(0,t.jsx)("th",{className:"py-1 pr-4",children:"Type"}),(0,t.jsx)("th",{className:"py-1 pr-4",children:"Action"}),(0,t.jsx)("th",{className:"py-1 pr-4",children:"Detected"}),(0,t.jsx)("th",{className:"py-1 pr-4",children:"Score"}),(0,t.jsx)("th",{className:"py-1 pr-4",children:"Threshold"})]})}),(0,t.jsx)("tbody",{children:e.contextualGroundingPolicy.filters.map((e,s)=>{var a,l,r,n;return(0,t.jsxs)("tr",{className:"border-t",children:[(0,t.jsx)("td",{className:"py-1 pr-4",children:null!==(a=e.type)&&void 0!==a?a:"—"}),(0,t.jsx)("td",{className:"py-1 pr-4",children:F(null!==(l=e.action)&&void 0!==l?l:"—",e.detected?"red":"slate")}),(0,t.jsx)("td",{className:"py-1 pr-4",children:q(e.detected)}),(0,t.jsx)("td",{className:"py-1 pr-4",children:null!==(r=e.score)&&void 0!==r?r:"—"}),(0,t.jsx)("td",{className:"py-1 pr-4",children:null!==(n=e.threshold)&&void 0!==n?n:"—"})]},s)})})]})})]}):null,e.sensitiveInformationPolicy&&(0,t.jsxs)("div",{className:"mb-3",children:[(0,t.jsx)("h6",{className:"font-medium mb-2",children:"Sensitive Information"}),(null!==(y=null===(c=e.sensitiveInformationPolicy.piiEntities)||void 0===c?void 0:c.length)&&void 0!==y?y:0)>0&&(0,t.jsx)(Y,{title:"PII Entities",defaultOpen:!0,children:(0,t.jsx)("div",{className:"space-y-2",children:e.sensitiveInformationPolicy.piiEntities.map((e,s)=>{var a;return(0,t.jsxs)("div",{className:"flex justify-between items-center p-2 bg-gray-50 rounded",children:[(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[F(null!==(a=e.action)&&void 0!==a?a:"N/A",e.detected?"red":"slate"),e.type&&F(e.type,"slate"),(0,t.jsx)("span",{className:"font-mono text-xs break-all",children:e.match})]}),q(e.detected)]},s)})})}),(null!==(b=null===(m=e.sensitiveInformationPolicy.regexes)||void 0===m?void 0:m.length)&&void 0!==b?b:0)>0&&(0,t.jsx)(Y,{title:"Custom Regexes",defaultOpen:!1,children:(0,t.jsx)("div",{className:"space-y-2",children:e.sensitiveInformationPolicy.regexes.map((e,s)=>{var a,l;return(0,t.jsxs)("div",{className:"flex flex-col sm:flex-row sm:items-center sm:justify-between p-2 bg-gray-50 rounded gap-1",children:[(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[F(null!==(a=e.action)&&void 0!==a?a:"N/A",e.detected?"red":"slate"),(0,t.jsx)("span",{className:"font-medium",children:null!==(l=e.name)&&void 0!==l?l:"regex"}),(0,t.jsx)("span",{className:"font-mono text-xs break-all",children:e.regex})]}),(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[q(e.detected),e.match&&(0,t.jsx)("span",{className:"font-mono text-xs break-all",children:e.match})]})]},s)})})})]}),(null===(u=e.topicPolicy)||void 0===u?void 0:null===(x=u.topics)||void 0===x?void 0:x.length)?(0,t.jsxs)("div",{className:"mb-3",children:[(0,t.jsx)("h6",{className:"font-medium mb-2",children:"Topic Policy"}),(0,t.jsx)("div",{className:"flex flex-wrap gap-2",children:e.topicPolicy.topics.map((e,s)=>{var a,l;return(0,t.jsx)("div",{className:"px-3 py-1.5 bg-gray-50 rounded-md text-xs",children:(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[F(null!==(a=e.action)&&void 0!==a?a:"N/A",e.detected?"red":"slate"),(0,t.jsx)("span",{className:"font-medium",children:null!==(l=e.name)&&void 0!==l?l:"topic"}),e.type&&F(e.type,"slate"),q(e.detected)]})},s)})})]}):null,e.invocationMetrics&&(0,t.jsx)(Y,{title:"Invocation Metrics",defaultOpen:!1,children:(0,t.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsx)(B,{label:"Latency (ms)",children:null!==(N=e.invocationMetrics.guardrailProcessingLatency)&&void 0!==N?N:"—"}),(0,t.jsx)(B,{label:"Coverage:",children:(0,t.jsxs)("div",{className:"flex flex-wrap gap-2",children:[(null===(h=e.invocationMetrics.guardrailCoverage)||void 0===h?void 0:h.textCharacters)&&F("text ".concat(null!==(w=e.invocationMetrics.guardrailCoverage.textCharacters.guarded)&&void 0!==w?w:0,"/").concat(null!==(_=e.invocationMetrics.guardrailCoverage.textCharacters.total)&&void 0!==_?_:0),"blue"),(null===(g=e.invocationMetrics.guardrailCoverage)||void 0===g?void 0:g.images)&&F("images ".concat(null!==(k=e.invocationMetrics.guardrailCoverage.images.guarded)&&void 0!==k?k:0,"/").concat(null!==(S=e.invocationMetrics.guardrailCoverage.images.total)&&void 0!==S?S:0),"blue")]})})]}),(0,t.jsx)("div",{className:"space-y-2",children:(0,t.jsx)(B,{label:"Usage:",children:(0,t.jsx)("div",{className:"flex flex-wrap gap-2",children:e.invocationMetrics.usage&&Object.entries(e.invocationMetrics.usage).map(e=>{let[s,a]=e;return"number"==typeof a?(0,t.jsxs)("span",{className:"px-2 py-1 bg-slate-100 text-slate-800 rounded-md text-xs font-medium",children:[s,": ",a]},s):null})})})})]})}),(null===(j=e.automatedReasoningPolicy)||void 0===j?void 0:null===(p=j.findings)||void 0===p?void 0:p.length)?(0,t.jsx)(Y,{title:"Automated Reasoning Findings",defaultOpen:!1,children:(0,t.jsx)("div",{className:"space-y-2",children:e.automatedReasoningPolicy.findings.map((e,s)=>(0,t.jsx)("pre",{className:"bg-gray-50 rounded p-2 text-xs overflow-x-auto",children:JSON.stringify(e,null,2)},s))})}):null]},s)})}):null,(0,t.jsx)(Y,{title:"Raw Bedrock Guardrail Response",defaultOpen:!1,children:(0,t.jsx)("pre",{className:"bg-gray-50 rounded p-3 text-xs overflow-x-auto",children:JSON.stringify(x,null,2)})})]})};let W=function(e){let s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"slate";return(0,t.jsx)("span",{className:"px-2 py-1 rounded-md text-xs font-medium inline-block ".concat({green:"bg-green-100 text-green-800",red:"bg-red-100 text-red-800",blue:"bg-blue-50 text-blue-700",slate:"bg-slate-100 text-slate-800",amber:"bg-amber-100 text-amber-800"}[s]),children:e})},J=e=>{let{title:s,count:a,defaultOpen:l=!0,children:r}=e,[n,d]=(0,i.useState)(l);return(0,t.jsxs)("div",{className:"border rounded-lg overflow-hidden",children:[(0,t.jsx)("div",{className:"flex items-center justify-between p-3 bg-gray-50 cursor-pointer hover:bg-gray-100",onClick:()=>d(e=>!e),children:(0,t.jsxs)("div",{className:"flex items-center",children:[(0,t.jsx)("svg",{className:"w-5 h-5 mr-2 transition-transform ".concat(n?"transform rotate-90":""),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,t.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M9 5l7 7-7 7"})}),(0,t.jsxs)("h5",{className:"font-medium",children:[s," ","number"==typeof a&&(0,t.jsxs)("span",{className:"text-gray-500 font-normal",children:["(",a,")"]})]})]})}),n&&(0,t.jsx)("div",{className:"p-3 border-t bg-white",children:r})]})},G=e=>{let{label:s,children:a,mono:l}=e;return(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:s}),(0,t.jsx)("span",{className:l?"font-mono text-sm break-all":"",children:a})]})};var Q=e=>{let{response:s}=e;if(!s||"string"==typeof s)return"string"==typeof s&&s?(0,t.jsx)("div",{className:"bg-white rounded-lg border border-red-200 p-4",children:(0,t.jsxs)("div",{className:"text-red-800",children:[(0,t.jsx)("h5",{className:"font-medium mb-2",children:"Error"}),(0,t.jsx)("p",{className:"text-sm",children:s})]})}):null;let a=Array.isArray(s)?s:[];if(0===a.length)return(0,t.jsx)("div",{className:"bg-white rounded-lg border border-gray-200 p-4",children:(0,t.jsx)("div",{className:"text-gray-600 text-sm",children:"No detections found"})});let l=a.filter(e=>"pattern"===e.type),r=a.filter(e=>"blocked_word"===e.type),n=a.filter(e=>"category_keyword"===e.type),i=a.filter(e=>"BLOCK"===e.action).length,d=a.filter(e=>"MASK"===e.action).length,o=a.length;return(0,t.jsxs)("div",{className:"space-y-3",children:[(0,t.jsx)("div",{className:"bg-white rounded-lg border border-gray-200 p-4",children:(0,t.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsx)(G,{label:"Total Detections:",children:(0,t.jsx)("span",{className:"font-semibold",children:o})}),(0,t.jsx)(G,{label:"Actions:",children:(0,t.jsxs)("div",{className:"flex flex-wrap gap-2",children:[i>0&&W("".concat(i," blocked"),"red"),d>0&&W("".concat(d," masked"),"blue"),0===i&&0===d&&W("passed","green")]})})]}),(0,t.jsx)("div",{className:"space-y-2",children:(0,t.jsx)(G,{label:"By Type:",children:(0,t.jsxs)("div",{className:"flex flex-wrap gap-2",children:[l.length>0&&W("".concat(l.length," patterns"),"slate"),r.length>0&&W("".concat(r.length," keywords"),"slate"),n.length>0&&W("".concat(n.length," categories"),"slate")]})})})]})}),l.length>0&&(0,t.jsx)(J,{title:"Patterns Matched",count:l.length,defaultOpen:!0,children:(0,t.jsx)("div",{className:"space-y-2",children:l.map((e,s)=>(0,t.jsx)("div",{className:"p-3 bg-gray-50 rounded-md",children:(0,t.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,t.jsx)("div",{className:"space-y-1",children:(0,t.jsx)(G,{label:"Pattern:",children:e.pattern_name||"unknown"})}),(0,t.jsx)("div",{className:"space-y-1",children:(0,t.jsx)(G,{label:"Action:",children:W(e.action,"BLOCK"===e.action?"red":"blue")})})]})},s))})}),r.length>0&&(0,t.jsx)(J,{title:"Blocked Words Detected",count:r.length,defaultOpen:!0,children:(0,t.jsx)("div",{className:"space-y-2",children:r.map((e,s)=>(0,t.jsx)("div",{className:"p-3 bg-gray-50 rounded-md",children:(0,t.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,t.jsxs)("div",{className:"space-y-1",children:[(0,t.jsx)(G,{label:"Keyword:",mono:!0,children:e.keyword||"unknown"}),e.description&&(0,t.jsx)(G,{label:"Description:",children:e.description})]}),(0,t.jsx)("div",{className:"space-y-1",children:(0,t.jsx)(G,{label:"Action:",children:W(e.action,"BLOCK"===e.action?"red":"blue")})})]})},s))})}),n.length>0&&(0,t.jsx)(J,{title:"Category Keywords Detected",count:n.length,defaultOpen:!0,children:(0,t.jsx)("div",{className:"space-y-2",children:n.map((e,s)=>(0,t.jsx)("div",{className:"p-3 bg-gray-50 rounded-md",children:(0,t.jsxs)("div",{className:"grid grid-cols-2 gap-4",children:[(0,t.jsxs)("div",{className:"space-y-1",children:[(0,t.jsx)(G,{label:"Category:",children:e.category||"unknown"}),(0,t.jsx)(G,{label:"Keyword:",mono:!0,children:e.keyword||"unknown"}),e.severity&&(0,t.jsx)(G,{label:"Severity:",children:W(e.severity,"high"===e.severity?"red":"medium"===e.severity?"amber":"slate")})]}),(0,t.jsx)("div",{className:"space-y-1",children:(0,t.jsx)(G,{label:"Action:",children:W(e.action,"BLOCK"===e.action?"red":"blue")})})]})},s))})}),(0,t.jsx)(J,{title:"Raw Detection Data",defaultOpen:!1,children:(0,t.jsx)("pre",{className:"bg-gray-50 rounded p-3 text-xs overflow-x-auto",children:JSON.stringify(a,null,2)})})]})};let $=e=>new Date(1e3*e).toLocaleString(),X=new Set(["presidio","bedrock","litellm_content_filter"]),ee=e=>{let{response:s}=e,[a,l]=(0,i.useState)(!1);return(0,t.jsx)("div",{className:"mt-4",children:(0,t.jsxs)("div",{className:"border rounded-lg overflow-hidden",children:[(0,t.jsx)("div",{className:"flex items-center justify-between p-3 bg-gray-50 cursor-pointer hover:bg-gray-100",onClick:()=>l(!a),children:(0,t.jsxs)("div",{className:"flex items-center",children:[(0,t.jsx)("svg",{className:"w-5 h-5 mr-2 transition-transform ".concat(a?"transform rotate-90":""),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,t.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M9 5l7 7-7 7"})}),(0,t.jsx)("h5",{className:"font-medium",children:"Raw Guardrail Response"})]})}),a&&(0,t.jsx)("div",{className:"p-3 border-t bg-white",children:(0,t.jsx)("pre",{className:"bg-gray-50 rounded p-3 text-xs overflow-x-auto",children:JSON.stringify(s,null,2)})})]})})},es=e=>{var s,a;let{entry:l,index:r,total:n}=e,i=null!==(s=l.guardrail_provider)&&void 0!==s?s:"presidio",d=null!==(a=l.guardrail_status)&&void 0!==a?a:"unknown",o="success"===d.toLowerCase(),c=l.masked_entity_count||{},m=Object.values(c).reduce((e,s)=>e+("number"==typeof s?s:0),0),x=l.guardrail_response,u=Array.isArray(x)?x:[],g="bedrock"!==i||null===x||"object"!=typeof x||Array.isArray(x)?void 0:x;return(0,t.jsxs)("div",{className:"bg-white rounded-lg border border-gray-200 p-4",children:[n>1&&(0,t.jsxs)("div",{className:"flex items-center justify-between mb-4",children:[(0,t.jsxs)("h4",{className:"text-base font-semibold",children:["Guardrail #",r+1,(0,t.jsx)("span",{className:"ml-2 font-mono text-sm text-gray-600",children:l.guardrail_name})]}),(0,t.jsx)("span",{className:"px-2 py-0.5 bg-gray-100 text-gray-600 rounded-md text-xs capitalize",children:i})]}),(0,t.jsxs)("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-4",children:[(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Guardrail Name:"}),(0,t.jsx)("span",{className:"font-mono break-words",children:l.guardrail_name})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Mode:"}),(0,t.jsx)("span",{className:"font-mono break-words",children:l.guardrail_mode})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Status:"}),(0,t.jsx)(h.Z,{title:o?null:"Guardrail failed to run.",placement:"top",arrow:!0,destroyTooltipOnHide:!0,children:(0,t.jsx)("span",{className:"px-2 py-1 rounded-md text-xs font-medium inline-block ".concat(o?"bg-green-100 text-green-800":"bg-red-100 text-red-800 cursor-help"),children:d})})]})]}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Start Time:"}),(0,t.jsx)("span",{children:$(l.start_time)})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"End Time:"}),(0,t.jsx)("span",{children:$(l.end_time)})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Duration:"}),(0,t.jsxs)("span",{children:[l.duration.toFixed(4),"s"]})]})]})]}),m>0&&(0,t.jsxs)("div",{className:"mt-4 pt-4 border-t",children:[(0,t.jsx)("h5",{className:"font-medium mb-2",children:"Masked Entity Summary"}),(0,t.jsx)("div",{className:"flex flex-wrap gap-2",children:Object.entries(c).map(e=>{let[s,a]=e;return(0,t.jsxs)("span",{className:"px-3 py-1.5 bg-blue-50 text-blue-700 rounded-md text-xs font-medium",children:[s,": ",a]},s)})})]}),"presidio"===i&&u.length>0&&(0,t.jsx)("div",{className:"mt-4",children:(0,t.jsx)(P,{entities:u})}),"bedrock"===i&&g&&(0,t.jsx)("div",{className:"mt-4",children:(0,t.jsx)(U,{response:g})}),"litellm_content_filter"===i&&x&&(0,t.jsx)("div",{className:"mt-4",children:(0,t.jsx)(Q,{response:x})}),i&&!X.has(i)&&x&&(0,t.jsx)(ee,{response:x})]})};var ea=e=>{let{data:s}=e,a=Array.isArray(s)?s.filter(e=>!!e):s?[s]:[],[l,r]=(0,i.useState)(!0),n=1===a.length?a[0].guardrail_name:"".concat(a.length," guardrails"),d=Array.from(new Set(a.map(e=>e.guardrail_status))).every(e=>"success"===(null!=e?e:"").toLowerCase()),o=a.reduce((e,s)=>e+Object.values(s.masked_entity_count||{}).reduce((e,s)=>e+("number"==typeof s?s:0),0),0);return 0===a.length?null:(0,t.jsxs)("div",{className:"bg-white rounded-lg shadow mb-6",children:[(0,t.jsxs)("div",{className:"flex justify-between items-center p-4 border-b cursor-pointer hover:bg-gray-50",onClick:()=>r(!l),children:[(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("svg",{className:"w-5 h-5 text-gray-600 transition-transform ".concat(l?"transform rotate-90":""),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,t.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M9 5l7 7-7 7"})}),(0,t.jsx)("h3",{className:"text-lg font-medium",children:"Guardrail Information"}),(0,t.jsx)(h.Z,{title:d?null:"Guardrail failed to run.",placement:"top",arrow:!0,destroyTooltipOnHide:!0,children:(0,t.jsx)("span",{className:"ml-2 px-2 py-1 rounded-md text-xs font-medium inline-block ".concat(d?"bg-green-100 text-green-800":"bg-red-100 text-red-800 cursor-help"),children:d?"success":"failure"})}),(0,t.jsx)("span",{className:"ml-2 font-mono text-sm text-gray-600",children:n}),o>0&&(0,t.jsxs)("span",{className:"ml-2 px-2 py-1 bg-blue-50 text-blue-700 rounded-md text-xs font-medium",children:[o," masked ",1===o?"entity":"entities"]})]}),(0,t.jsx)("span",{className:"text-sm text-gray-500",children:l?"Click to collapse":"Click to expand"})]}),l&&(0,t.jsx)("div",{className:"p-4 space-y-6",children:a.map((e,s)=>{var l;return(0,t.jsx)(es,{entry:e,index:s,total:a.length},"".concat(null!==(l=e.guardrail_name)&&void 0!==l?l:"guardrail","-").concat(s))})})]})},et=a(87452),el=a(88829),er=a(72208);let en=e=>null==e?"-":"$".concat((0,m.pw)(e,8)),ei=e=>null==e?"-":"".concat((100*e).toFixed(2),"%"),ed=e=>{var s;let{costBreakdown:a,totalSpend:l}=e;if(!a)return null;let r=void 0!==a.discount_percent&&0!==a.discount_percent||void 0!==a.discount_amount&&0!==a.discount_amount,n=void 0!==a.margin_percent&&0!==a.margin_percent||void 0!==a.margin_fixed_amount&&0!==a.margin_fixed_amount||void 0!==a.margin_total_amount&&0!==a.margin_total_amount;return void 0!==a.input_cost||void 0!==a.output_cost||r||n?(0,t.jsx)("div",{className:"bg-white rounded-lg shadow w-full max-w-full overflow-hidden",children:(0,t.jsxs)(et.Z,{children:[(0,t.jsx)(er.Z,{className:"p-4 border-b hover:bg-gray-50 transition-colors text-left",children:(0,t.jsxs)("div",{className:"flex items-center justify-between w-full",children:[(0,t.jsx)("h3",{className:"text-lg font-medium text-gray-900",children:"Cost Breakdown"}),(0,t.jsxs)("div",{className:"flex items-center space-x-2 mr-4",children:[(0,t.jsx)("span",{className:"text-sm text-gray-500",children:"Total:"}),(0,t.jsx)("span",{className:"text-sm font-semibold text-gray-900",children:en(l)})]})]})}),(0,t.jsx)(el.Z,{className:"px-0",children:(0,t.jsxs)("div",{className:"p-6 space-y-4",children:[(0,t.jsxs)("div",{className:"space-y-2 max-w-2xl",children:[(0,t.jsxs)("div",{className:"flex text-sm",children:[(0,t.jsx)("span",{className:"text-gray-600 font-medium w-1/3",children:"Input Cost:"}),(0,t.jsx)("span",{className:"text-gray-900",children:en(a.input_cost)})]}),(0,t.jsxs)("div",{className:"flex text-sm",children:[(0,t.jsx)("span",{className:"text-gray-600 font-medium w-1/3",children:"Output Cost:"}),(0,t.jsx)("span",{className:"text-gray-900",children:en(a.output_cost)})]}),void 0!==a.tool_usage_cost&&a.tool_usage_cost>0&&(0,t.jsxs)("div",{className:"flex text-sm",children:[(0,t.jsx)("span",{className:"text-gray-600 font-medium w-1/3",children:"Tool Usage Cost:"}),(0,t.jsx)("span",{className:"text-gray-900",children:en(a.tool_usage_cost)})]})]}),(0,t.jsx)("div",{className:"pt-2 border-t border-gray-100 max-w-2xl",children:(0,t.jsxs)("div",{className:"flex text-sm font-semibold",children:[(0,t.jsx)("span",{className:"text-gray-900 w-1/3",children:"Original LLM Cost:"}),(0,t.jsx)("span",{className:"text-gray-900",children:en(a.original_cost)})]})}),(r||n)&&(0,t.jsxs)("div",{className:"pt-2 space-y-2 max-w-2xl",children:[r&&(0,t.jsxs)("div",{className:"space-y-2",children:[void 0!==a.discount_percent&&0!==a.discount_percent&&(0,t.jsxs)("div",{className:"flex text-sm text-gray-600",children:[(0,t.jsxs)("span",{className:"font-medium w-1/3",children:["Discount (",ei(a.discount_percent),"):"]}),(0,t.jsxs)("span",{className:"text-gray-900",children:["-",en(a.discount_amount)]})]}),void 0!==a.discount_amount&&void 0===a.discount_percent&&(0,t.jsxs)("div",{className:"flex text-sm text-gray-600",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Discount Amount:"}),(0,t.jsxs)("span",{className:"text-gray-900",children:["-",en(a.discount_amount)]})]})]}),n&&(0,t.jsxs)("div",{className:"space-y-2",children:[void 0!==a.margin_percent&&0!==a.margin_percent&&(0,t.jsxs)("div",{className:"flex text-sm text-gray-600",children:[(0,t.jsxs)("span",{className:"font-medium w-1/3",children:["Margin (",ei(a.margin_percent),"):"]}),(0,t.jsxs)("span",{className:"text-gray-900",children:["+",en((a.margin_total_amount||0)-(a.margin_fixed_amount||0))]})]}),void 0!==a.margin_fixed_amount&&0!==a.margin_fixed_amount&&(0,t.jsxs)("div",{className:"flex text-sm text-gray-600",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Margin:"}),(0,t.jsxs)("span",{className:"text-gray-900",children:["+",en(a.margin_fixed_amount)]})]})]})]}),(0,t.jsx)("div",{className:"mt-4 pt-4 border-t border-gray-200 max-w-2xl",children:(0,t.jsxs)("div",{className:"flex items-center",children:[(0,t.jsx)("span",{className:"font-bold text-sm text-gray-900 w-1/3",children:"Final Calculated Cost:"}),(0,t.jsx)("span",{className:"text-sm font-bold text-gray-900",children:en(null!==(s=a.total_cost)&&void 0!==s?s:l)})]})})]})})]})}):null};var eo=a(23048),ec=a(30841),em=a(7310),ex=a.n(em),eu=a(12363);let eh={TEAM_ID:"Team ID",KEY_HASH:"Key Hash",REQUEST_ID:"Request ID",MODEL:"Model",USER_ID:"User ID",END_USER:"End User",STATUS:"Status",KEY_ALIAS:"Key Alias",ERROR_CODE:"Error Code"};var eg=a(59341),ep=a(12485),ej=a(18135),ef=a(35242),ev=a(29706),ey=a(77991),eb=a(92280);let eN="".concat("../ui/assets/","audit-logs-preview.png");function ew(e){let{userID:s,userRole:a,token:l,accessToken:d,isActive:x,premiumUser:u,allTeams:h}=e,[g,p]=(0,i.useState)(r()().subtract(24,"hours").format("YYYY-MM-DDTHH:mm")),j=(0,i.useRef)(null),f=(0,i.useRef)(null),[v,y]=(0,i.useState)(1),[N]=(0,i.useState)(50),[w,_]=(0,i.useState)({}),[k,S]=(0,i.useState)(""),[C,L]=(0,i.useState)(""),[D,M]=(0,i.useState)(""),[E,T]=(0,i.useState)("all"),[A,R]=(0,i.useState)("all"),[z,O]=(0,i.useState)(!1),[Z,I]=(0,i.useState)(!1),K=(0,n.a)({queryKey:["all_audit_logs",d,l,a,s,g],queryFn:async()=>{if(!d||!l||!a||!s)return[];let e=r()(g).utc().format("YYYY-MM-DD HH:mm:ss"),t=r()().utc().format("YYYY-MM-DD HH:mm:ss"),n=[],i=1,c=1;do{let s=await (0,o.uiAuditLogsCall)(d,e,t,i,50);n=n.concat(s.audit_logs),c=s.total_pages,i++}while(i<=c);return n},enabled:!!d&&!!l&&!!a&&!!s&&x,refetchInterval:5e3,refetchIntervalInBackground:!0}),H=(0,i.useCallback)(async e=>{if(d)try{let s=(await (0,o.keyListCall)(d,null,null,e,null,null,1,10)).keys.find(s=>s.key_alias===e);s?L(s.token):L("")}catch(e){console.error("Error fetching key hash for alias:",e),L("")}},[d]);(0,i.useEffect)(()=>{if(!d)return;let e=!1,s=!1;w["Team ID"]?k!==w["Team ID"]&&(S(w["Team ID"]),e=!0):""!==k&&(S(""),e=!0),w["Key Hash"]?C!==w["Key Hash"]&&(L(w["Key Hash"]),s=!0):w["Key Alias"]?H(w["Key Alias"]):""!==C&&(L(""),s=!0),(e||s)&&y(1)},[w,d,H,k,C]),(0,i.useEffect)(()=>{y(1)},[k,C,g,D,E,A]),(0,i.useEffect)(()=>{function e(e){j.current&&!j.current.contains(e.target)&&O(!1),f.current&&!f.current.contains(e.target)&&I(!1)}return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[]);let P=(0,i.useMemo)(()=>K.data?K.data.filter(e=>{var s,a,t,l,r,n,i;let d=!0,o=!0,c=!0,m=!0,x=!0;if(k){let r="string"==typeof e.before_value?null===(s=JSON.parse(e.before_value))||void 0===s?void 0:s.team_id:null===(a=e.before_value)||void 0===a?void 0:a.team_id,n="string"==typeof e.updated_values?null===(t=JSON.parse(e.updated_values))||void 0===t?void 0:t.team_id:null===(l=e.updated_values)||void 0===l?void 0:l.team_id;d=r===k||n===k}if(C)try{let s="string"==typeof e.before_value?JSON.parse(e.before_value):e.before_value,a="string"==typeof e.updated_values?JSON.parse(e.updated_values):e.updated_values,t=null==s?void 0:s.token,l=null==a?void 0:a.token;o="string"==typeof t&&t.includes(C)||"string"==typeof l&&l.includes(C)}catch(e){o=!1}if(D&&(c=null===(r=e.object_id)||void 0===r?void 0:r.toLowerCase().includes(D.toLowerCase())),"all"!==E&&(m=(null===(n=e.action)||void 0===n?void 0:n.toLowerCase())===E.toLowerCase()),"all"!==A){let s="";switch(A){case"keys":s="litellm_verificationtoken";break;case"teams":s="litellm_teamtable";break;case"users":s="litellm_usertable";break;default:s=A}x=(null===(i=e.table_name)||void 0===i?void 0:i.toLowerCase())===s}return d&&o&&c&&m&&x}):[],[K.data,k,C,D,E,A]),F=P.length,q=Math.ceil(F/N)||1,Y=(0,i.useMemo)(()=>{let e=(v-1)*N,s=e+N;return P.slice(e,s)},[P,v,N]),B=!K.data||0===K.data.length,V=(0,i.useCallback)(e=>{let{row:s}=e;return(0,t.jsx)(e=>{let{rowData:s}=e,{before_value:a,updated_values:l,table_name:r,action:n}=s,i=(e,s)=>{if(!e||0===Object.keys(e).length)return(0,t.jsx)(eb.x,{children:"N/A"});if(s){let s=Object.keys(e),a=["token","spend","max_budget"];if(s.every(e=>a.includes(e))&&s.length>0)return(0,t.jsxs)("div",{children:[s.includes("token")&&(0,t.jsxs)("p",{children:[(0,t.jsx)("strong",{children:"Token:"})," ",e.token||"N/A"]}),s.includes("spend")&&(0,t.jsxs)("p",{children:[(0,t.jsx)("strong",{children:"Spend:"})," ",void 0!==e.spend?"$".concat((0,m.pw)(e.spend,6)):"N/A"]}),s.includes("max_budget")&&(0,t.jsxs)("p",{children:[(0,t.jsx)("strong",{children:"Max Budget:"})," ",void 0!==e.max_budget?"$".concat((0,m.pw)(e.max_budget,6)):"N/A"]})]});if(e["No differing fields detected in 'before' state"]||e["No differing fields detected in 'updated' state"]||e["No fields changed"])return(0,t.jsx)(eb.x,{children:e[Object.keys(e)[0]]})}return(0,t.jsx)("pre",{className:"p-2 bg-gray-50 border rounded text-xs overflow-auto max-h-60",children:JSON.stringify(e,null,2)})},d=a,o=l;if(("updated"===n||"rotated"===n)&&a&&l&&("LiteLLM_TeamTable"===r||"LiteLLM_UserTable"===r||"LiteLLM_VerificationToken"===r)){let e={},s={};new Set([...Object.keys(a),...Object.keys(l)]).forEach(t=>{JSON.stringify(a[t])!==JSON.stringify(l[t])&&(a.hasOwnProperty(t)&&(e[t]=a[t]),l.hasOwnProperty(t)&&(s[t]=l[t]))}),Object.keys(a).forEach(t=>{l.hasOwnProperty(t)||e.hasOwnProperty(t)||(e[t]=a[t],s[t]=void 0)}),Object.keys(l).forEach(t=>{a.hasOwnProperty(t)||s.hasOwnProperty(t)||(s[t]=l[t],e[t]=void 0)}),d=Object.keys(e).length>0?e:{"No differing fields detected in 'before' state":"N/A"},o=Object.keys(s).length>0?s:{"No differing fields detected in 'updated' state":"N/A"},0===Object.keys(e).length&&0===Object.keys(s).length&&(d={"No fields changed":"N/A"},o={"No fields changed":"N/A"})}return(0,t.jsxs)("div",{className:"-mx-4 p-4 bg-slate-100 border-y border-slate-300 grid grid-cols-1 md:grid-cols-2 gap-4",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)("h4",{className:"font-semibold mb-2 text-sm text-slate-700",children:"Before Value:"}),i(d,"LiteLLM_VerificationToken"===r)]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("h4",{className:"font-semibold mb-2 text-sm text-slate-700",children:"Updated Value:"}),i(o,"LiteLLM_VerificationToken"===r)]})]})},{rowData:s.original})},[]);if(!u)return(0,t.jsxs)("div",{style:{textAlign:"center",marginTop:"20px"},children:[(0,t.jsx)("h1",{style:{display:"block",marginBottom:"10px"},children:"✨ Enterprise Feature."}),(0,t.jsx)(eb.x,{style:{display:"block",marginBottom:"10px"},children:"This is a LiteLLM Enterprise feature, and requires a valid key to use."}),(0,t.jsx)(eb.x,{style:{display:"block",marginBottom:"20px",fontStyle:"italic"},children:"Here's a preview of what Audit Logs offer:"}),(0,t.jsx)("img",{src:eN,alt:"Audit Logs Preview",style:{maxWidth:"100%",maxHeight:"700px",borderRadius:"8px",boxShadow:"0 4px 8px rgba(0,0,0,0.1)",margin:"0 auto"},onError:e=>{console.error("Failed to load audit logs preview image"),e.target.style.display="none"}})]});let U=F>0?(v-1)*N+1:0,W=Math.min(v*N,F);return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)("div",{className:"flex items-center justify-between mb-4"}),(0,t.jsxs)("div",{className:"bg-white rounded-lg shadow",children:[(0,t.jsxs)("div",{className:"border-b px-6 py-4",children:[(0,t.jsx)("h1",{className:"text-xl font-semibold py-4",children:"Audit Logs"}),(0,t.jsx)(e=>{let{show:s}=e;return s?(0,t.jsxs)("div",{className:"bg-blue-50 border border-blue-200 rounded-lg p-4 flex items-start mb-6",children:[(0,t.jsx)("div",{className:"text-blue-500 mr-3 flex-shrink-0 mt-0.5",children:(0,t.jsxs)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"20",height:"20",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[(0,t.jsx)("circle",{cx:"12",cy:"12",r:"10"}),(0,t.jsx)("line",{x1:"12",y1:"16",x2:"12",y2:"12"}),(0,t.jsx)("line",{x1:"12",y1:"8",x2:"12.01",y2:"8"})]})}),(0,t.jsxs)("div",{children:[(0,t.jsx)("h4",{className:"text-sm font-medium text-blue-800",children:"Audit Logs Not Available"}),(0,t.jsx)("p",{className:"text-sm text-blue-700 mt-1",children:"To enable audit logging, add the following configuration to your LiteLLM proxy configuration file:"}),(0,t.jsx)("pre",{className:"mt-2 bg-white p-3 rounded border border-blue-200 text-xs font-mono overflow-auto",children:"litellm_settings:\n store_audit_logs: true"}),(0,t.jsx)("p",{className:"text-xs text-blue-700 mt-2",children:"Note: This will only affect new requests after the configuration change and proxy restart."})]})]}):null},{show:B}),(0,t.jsxs)("div",{className:"flex flex-col md:flex-row items-start md:items-center justify-between space-y-4 md:space-y-0",children:[(0,t.jsx)("div",{className:"flex flex-wrap items-center gap-3",children:(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("div",{className:"flex items-center",children:(0,t.jsx)("input",{type:"text",placeholder:"Search by Object ID...",value:D,onChange:e=>M(e.target.value),className:"px-3 py-2 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"})}),(0,t.jsxs)("button",{onClick:()=>{K.refetch()},className:"px-3 py-2 text-sm border rounded-md hover:bg-gray-50 flex items-center gap-2",title:"Refresh data",children:[(0,t.jsx)("svg",{className:"w-4 h-4 ".concat(K.isFetching?"animate-spin":""),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,t.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"})}),(0,t.jsx)("span",{children:"Refresh"})]})]})}),(0,t.jsxs)("div",{className:"flex items-center space-x-4",children:[(0,t.jsxs)("div",{className:"relative",ref:j,children:[(0,t.jsx)("label",{htmlFor:"actionFilterDisplay",className:"mr-2 text-sm font-medium text-gray-700 sr-only",children:"Action:"}),(0,t.jsxs)("button",{id:"actionFilterDisplay",onClick:()=>O(!z),className:"px-3 py-2 text-sm border rounded-md hover:bg-gray-50 flex items-center gap-2 bg-white w-40 text-left justify-between",children:[(0,t.jsxs)("span",{children:["all"===E&&"All Actions","created"===E&&"Created","updated"===E&&"Updated","deleted"===E&&"Deleted","rotated"===E&&"Rotated"]}),(0,t.jsx)("svg",{className:"w-4 h-4 text-gray-500",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg",children:(0,t.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:"2",d:"M19 9l-7 7-7-7"})})]}),z&&(0,t.jsx)("div",{className:"absolute left-0 mt-2 w-40 bg-white rounded-lg shadow-lg border p-1 z-50",children:(0,t.jsx)("div",{className:"space-y-1",children:[{label:"All Actions",value:"all"},{label:"Created",value:"created"},{label:"Updated",value:"updated"},{label:"Deleted",value:"deleted"},{label:"Rotated",value:"rotated"}].map(e=>(0,t.jsx)("button",{className:"w-full px-3 py-2 text-left text-sm hover:bg-gray-50 rounded-md ".concat(E===e.value?"bg-blue-50 text-blue-600 font-medium":"font-normal"),onClick:()=>{T(e.value),O(!1)},children:e.label},e.value))})})]}),(0,t.jsxs)("div",{className:"relative",ref:f,children:[(0,t.jsx)("label",{htmlFor:"tableFilterDisplay",className:"mr-2 text-sm font-medium text-gray-700 sr-only",children:"Table:"}),(0,t.jsxs)("button",{id:"tableFilterDisplay",onClick:()=>I(!Z),className:"px-3 py-2 text-sm border rounded-md hover:bg-gray-50 flex items-center gap-2 bg-white w-40 text-left justify-between",children:[(0,t.jsxs)("span",{children:["all"===A&&"All Tables","keys"===A&&"Keys","teams"===A&&"Teams","users"===A&&"Users"]}),(0,t.jsx)("svg",{className:"w-4 h-4 text-gray-500",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg",children:(0,t.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:"2",d:"M19 9l-7 7-7-7"})})]}),Z&&(0,t.jsx)("div",{className:"absolute left-0 mt-2 w-40 bg-white rounded-lg shadow-lg border p-1 z-50",children:(0,t.jsx)("div",{className:"space-y-1",children:[{label:"All Tables",value:"all"},{label:"Keys",value:"keys"},{label:"Teams",value:"teams"},{label:"Users",value:"users"}].map(e=>(0,t.jsx)("button",{className:"w-full px-3 py-2 text-left text-sm hover:bg-gray-50 rounded-md ".concat(A===e.value?"bg-blue-50 text-blue-600 font-medium":"font-normal"),onClick:()=>{R(e.value),I(!1)},children:e.label},e.value))})})]}),(0,t.jsxs)("span",{className:"text-sm text-gray-700",children:["Showing ",K.isLoading?"...":U," -"," ",K.isLoading?"...":W," of"," ",K.isLoading?"...":F," results"]}),(0,t.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,t.jsxs)("span",{className:"text-sm text-gray-700",children:["Page ",K.isLoading?"...":v," of"," ",K.isLoading?"...":q]}),(0,t.jsx)("button",{onClick:()=>y(e=>Math.max(1,e-1)),disabled:K.isLoading||1===v,className:"px-3 py-1 text-sm border rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed",children:"Previous"}),(0,t.jsx)("button",{onClick:()=>y(e=>Math.min(q,e+1)),disabled:K.isLoading||v===q,className:"px-3 py-1 text-sm border rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed",children:"Next"})]})]})]})]}),(0,t.jsx)(c.w,{columns:b,data:Y,renderSubComponent:V,getRowCanExpand:()=>!0})]})]})}let e_=(e,s,a)=>{if(e)return"".concat(r()(s).format("MMM D, h:mm A")," - ").concat(r()(a).format("MMM D, h:mm A"));let t=r()(),l=r()(s),n=t.diff(l,"minutes");if(n>=0&&n<2)return"Last 1 Minute";if(n>=2&&n<16)return"Last 15 Minutes";if(n>=16&&n<61)return"Last Hour";let i=t.diff(l,"hours");return i>=1&&i<5?"Last 4 Hours":i>=5&&i<25?"Last 24 Hours":i>=25&&i<169?"Last 7 Days":"".concat(l.format("MMM D")," - ").concat(t.format("MMM D"))};var ek=a(9309),eS=a(30280),eC=a(44633),eL=a(86462),eD=a(49084),eM=a(71594),eE=a(24525),eT=a(19130);function eA(e){let{keys:s,totalCount:a,isLoading:l,isFetching:r,pageIndex:n,pageSize:d,onPageChange:o}=e,[c,x]=(0,i.useState)([{id:"deleted_at",desc:!0}]),[u,g]=(0,i.useState)({pageIndex:n,pageSize:d});i.useEffect(()=>{g({pageIndex:n,pageSize:d})},[n,d]);let p=[{id:"token",accessorKey:"token",header:"Key ID",size:150,maxSize:250,cell:e=>{let s=e.getValue();return(0,t.jsx)(h.Z,{title:s,children:(0,t.jsx)("span",{className:"font-mono text-blue-500 text-xs truncate block max-w-[250px]",children:s||"-"})})}},{id:"key_alias",accessorKey:"key_alias",header:"Key Alias",size:150,maxSize:200,cell:e=>{let s=e.getValue();return(0,t.jsx)(h.Z,{title:s,children:(0,t.jsx)("span",{className:"font-mono text-xs truncate block max-w-[200px]",children:null!=s?s:"-"})})}},{id:"team_alias",accessorKey:"team_alias",header:"Team Alias",size:120,maxSize:180,cell:e=>{let s=e.getValue();return(0,t.jsx)("span",{className:"truncate block max-w-[180px]",children:s||"-"})}},{id:"spend",accessorKey:"spend",header:"Spend (USD)",size:100,maxSize:140,cell:e=>(0,t.jsx)("span",{className:"block max-w-[140px]",children:(0,m.pw)(e.getValue(),4)})},{id:"max_budget",accessorKey:"max_budget",header:"Budget (USD)",size:110,maxSize:150,cell:e=>{let s=e.getValue();return(0,t.jsx)("span",{className:"block max-w-[150px]",children:null===s?"Unlimited":"$".concat((0,m.pw)(s))})}},{id:"user_email",accessorKey:"user_email",header:"User Email",size:160,maxSize:250,cell:e=>{let s=e.getValue();return(0,t.jsx)(h.Z,{title:s,children:(0,t.jsx)("span",{className:"font-mono text-xs truncate block max-w-[250px]",children:null!=s?s:"-"})})}},{id:"user_id",accessorKey:"user_id",header:"User ID",size:120,maxSize:200,cell:e=>{let s=e.getValue();return(0,t.jsx)(h.Z,{title:s||void 0,children:(0,t.jsx)("span",{className:"truncate block max-w-[200px]",children:s||"-"})})}},{id:"created_at",accessorKey:"created_at",header:"Created At",size:120,maxSize:140,cell:e=>{let s=e.getValue();return(0,t.jsx)("span",{className:"block max-w-[140px]",children:s?new Date(s).toLocaleDateString():"-"})}},{id:"created_by",accessorKey:"created_by",header:"Created By",size:120,maxSize:180,cell:e=>{let s=e.row.original.created_by;return(0,t.jsx)(h.Z,{title:s||void 0,children:(0,t.jsx)("span",{className:"truncate block max-w-[180px]",children:s||"-"})})}},{id:"deleted_at",accessorKey:"deleted_at",header:"Deleted At",size:120,maxSize:140,cell:e=>{let s=e.row.original.deleted_at;return(0,t.jsx)("span",{className:"block max-w-[140px]",children:s?new Date(s).toLocaleDateString():"-"})}},{id:"deleted_by",accessorKey:"deleted_by",header:"Deleted By",size:120,maxSize:180,cell:e=>{let s=e.row.original.deleted_by;return(0,t.jsx)(h.Z,{title:s||void 0,children:(0,t.jsx)("span",{className:"truncate block max-w-[180px]",children:s||"-"})})}}],j=(0,eM.b7)({data:s,columns:p,columnResizeMode:"onChange",columnResizeDirection:"ltr",state:{sorting:c,pagination:u},onSortingChange:x,onPaginationChange:e=>{let s="function"==typeof e?e(u):e;g(s),o(s.pageIndex)},getCoreRowModel:(0,eE.sC)(),getSortedRowModel:(0,eE.tj)(),getPaginationRowModel:(0,eE.G_)(),enableSorting:!0,manualSorting:!1,manualPagination:!0,pageCount:Math.ceil(a/d)}),{pageIndex:f}=j.getState().pagination,v=f*d+1,y="".concat(v," - ").concat(Math.min((f+1)*d,a));return(0,t.jsx)("div",{className:"w-full h-full overflow-hidden",children:(0,t.jsxs)("div",{className:"border-b py-4 flex-1 overflow-hidden",children:[(0,t.jsxs)("div",{className:"flex items-center justify-between w-full mb-4",children:[l||r?(0,t.jsx)("span",{className:"inline-flex text-sm text-gray-700",children:"Loading..."}):(0,t.jsxs)("span",{className:"inline-flex text-sm text-gray-700",children:["Showing ",y," of ",a," results"]}),(0,t.jsxs)("div",{className:"inline-flex items-center gap-2",children:[l||r?(0,t.jsx)("span",{className:"text-sm text-gray-700",children:"Loading..."}):(0,t.jsxs)("span",{className:"text-sm text-gray-700",children:["Page ",f+1," of ",j.getPageCount()]}),(0,t.jsx)("button",{onClick:()=>j.previousPage(),disabled:l||r||!j.getCanPreviousPage(),className:"px-3 py-1 text-sm border rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed",children:"Previous"}),(0,t.jsx)("button",{onClick:()=>j.nextPage(),disabled:l||r||!j.getCanNextPage(),className:"px-3 py-1 text-sm border rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed",children:"Next"})]})]}),(0,t.jsx)("div",{className:"h-[75vh] overflow-auto",children:(0,t.jsx)("div",{className:"rounded-lg custom-border relative",children:(0,t.jsx)("div",{className:"overflow-x-auto",children:(0,t.jsxs)(eT.iA,{className:"[&_td]:py-0.5 [&_th]:py-1",style:{width:j.getCenterTotalSize()},children:[(0,t.jsx)(eT.ss,{children:j.getHeaderGroups().map(e=>(0,t.jsx)(eT.SC,{children:e.headers.map(e=>(0,t.jsx)(eT.xs,{"data-header-id":e.id,className:"py-1 h-8 relative hover:bg-gray-50",style:{width:e.getSize(),maxWidth:e.column.columnDef.maxSize,position:"relative"},onMouseEnter:()=>{let s=document.querySelector('[data-header-id="'.concat(e.id,'"] .resizer'));s&&(s.style.opacity="0.5")},onMouseLeave:()=>{let s=document.querySelector('[data-header-id="'.concat(e.id,'"] .resizer'));s&&!e.column.getIsResizing()&&(s.style.opacity="0")},onClick:e.column.getToggleSortingHandler(),children:(0,t.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,t.jsx)("div",{className:"flex items-center",children:e.isPlaceholder?null:(0,eM.ie)(e.column.columnDef.header,e.getContext())}),(0,t.jsx)("div",{className:"w-4",children:e.column.getIsSorted()?({asc:(0,t.jsx)(eC.Z,{className:"h-4 w-4 text-blue-500"}),desc:(0,t.jsx)(eL.Z,{className:"h-4 w-4 text-blue-500"})})[e.column.getIsSorted()]:(0,t.jsx)(eD.Z,{className:"h-4 w-4 text-gray-400"})}),(0,t.jsx)("div",{onDoubleClick:()=>e.column.resetSize(),onMouseDown:e.getResizeHandler(),onTouchStart:e.getResizeHandler(),className:"resizer ".concat(j.options.columnResizeDirection," ").concat(e.column.getIsResizing()?"isResizing":""),style:{position:"absolute",right:0,top:0,height:"100%",width:"5px",background:e.column.getIsResizing()?"#3b82f6":"transparent",cursor:"col-resize",userSelect:"none",touchAction:"none",opacity:e.column.getIsResizing()?1:0}})]})},e.id))},e.id))}),(0,t.jsx)(eT.RM,{children:l||r?(0,t.jsx)(eT.SC,{children:(0,t.jsx)(eT.pj,{colSpan:p.length,className:"h-8 text-center",children:(0,t.jsx)("div",{className:"text-center text-gray-500",children:(0,t.jsx)("p",{children:"\uD83D\uDE85 Loading keys..."})})})}):s.length>0?j.getRowModel().rows.map(e=>(0,t.jsx)(eT.SC,{className:"h-8",children:e.getVisibleCells().map(e=>(0,t.jsx)(eT.pj,{style:{width:e.column.getSize(),maxWidth:e.column.columnDef.maxSize,whiteSpace:"pre-wrap",overflow:"hidden"},className:"py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap",children:(0,eM.ie)(e.column.columnDef.cell,e.getContext())},e.id))},e.id)):(0,t.jsx)(eT.SC,{children:(0,t.jsx)(eT.pj,{colSpan:p.length,className:"h-8 text-center",children:(0,t.jsx)("div",{className:"text-center text-gray-500",children:(0,t.jsx)("p",{children:"No deleted keys found"})})})})})]})})})})]})})}function eR(){let[e,s]=(0,i.useState)(0),[a]=(0,i.useState)(50),{data:l,isPending:r,isFetching:n}=(0,eS.Tv)(e+1,a);return(0,t.jsx)(eA,{keys:(null==l?void 0:l.keys)||[],totalCount:(null==l?void 0:l.total_count)||0,isLoading:r,isFetching:n,pageIndex:e,pageSize:a,onPageChange:s})}var ez=a(47359),eO=a(21626),eZ=a(97214),eI=a(28241),eK=a(58834),eH=a(69552),eP=a(71876),eF=a(46468);function eq(e){let{teams:s,isLoading:a,isFetching:l}=e,[r,n]=(0,i.useState)([{id:"deleted_at",desc:!0}]),d=[{id:"team_alias",accessorKey:"team_alias",header:"Team Name",size:150,maxSize:200,cell:e=>{let s=e.getValue();return(0,t.jsx)(h.Z,{title:s||void 0,children:(0,t.jsx)("span",{className:"truncate block max-w-[200px]",children:s||"-"})})}},{id:"team_id",accessorKey:"team_id",header:"Team ID",size:150,maxSize:250,cell:e=>{let s=e.getValue();return(0,t.jsx)(h.Z,{title:s,children:(0,t.jsx)("span",{className:"font-mono text-blue-500 text-xs truncate block max-w-[250px]",children:s||"-"})})}},{id:"created_at",accessorKey:"created_at",header:"Created",size:120,maxSize:140,cell:e=>{let s=e.getValue();return(0,t.jsx)("span",{className:"block max-w-[140px]",children:s?new Date(s).toLocaleDateString():"-"})}},{id:"spend",accessorKey:"spend",header:"Spend (USD)",size:100,maxSize:140,cell:e=>{let s=e.row.original.spend;return(0,t.jsx)("span",{className:"block max-w-[140px]",children:void 0!==s?(0,m.pw)(s,4):"-"})}},{id:"max_budget",accessorKey:"max_budget",header:"Budget (USD)",size:110,maxSize:150,cell:e=>{let s=e.getValue();return(0,t.jsx)("span",{className:"block max-w-[150px]",children:null==s?"No limit":"$".concat((0,m.pw)(s))})}},{id:"models",accessorKey:"models",header:"Models",size:200,maxSize:300,cell:e=>{let s=e.getValue();return Array.isArray(s)&&0!==s.length?(0,t.jsxs)("div",{className:"flex flex-wrap gap-1 max-w-[300px]",children:[s.slice(0,3).map((e,s)=>"all-proxy-models"===e?(0,t.jsx)(x.Z,{size:"xs",color:"red",children:(0,t.jsx)(T.Z,{children:"All Proxy Models"})},s):(0,t.jsx)(x.Z,{size:"xs",color:"blue",children:(0,t.jsx)(T.Z,{children:e.length>30?"".concat((0,eF.W0)(e).slice(0,30),"..."):(0,eF.W0)(e)})},s)),s.length>3&&(0,t.jsx)(x.Z,{size:"xs",color:"gray",children:(0,t.jsxs)(T.Z,{children:["+",s.length-3," ",s.length-3==1?"more model":"more models"]})})]}):(0,t.jsx)(x.Z,{size:"xs",color:"red",children:(0,t.jsx)(T.Z,{children:"All Proxy Models"})})}},{id:"organization_id",accessorKey:"organization_id",header:"Organization",size:150,maxSize:200,cell:e=>{let s=e.getValue();return(0,t.jsx)(h.Z,{title:s||void 0,children:(0,t.jsx)("span",{className:"truncate block max-w-[200px]",children:s||"-"})})}},{id:"deleted_at",accessorKey:"deleted_at",header:"Deleted At",size:120,maxSize:140,cell:e=>{let s=e.row.original.deleted_at;return(0,t.jsx)("span",{className:"block max-w-[140px]",children:s?new Date(s).toLocaleDateString():"-"})}},{id:"deleted_by",accessorKey:"deleted_by",header:"Deleted By",size:120,maxSize:180,cell:e=>{let s=e.row.original.deleted_by;return(0,t.jsx)(h.Z,{title:s||void 0,children:(0,t.jsx)("span",{className:"truncate block max-w-[180px]",children:s||"-"})})}}],o=(0,eM.b7)({data:s,columns:d,columnResizeMode:"onChange",columnResizeDirection:"ltr",state:{sorting:r},onSortingChange:n,getCoreRowModel:(0,eE.sC)(),getSortedRowModel:(0,eE.tj)(),enableSorting:!0,manualSorting:!1});return(0,t.jsx)("div",{className:"w-full h-full overflow-hidden",children:(0,t.jsxs)("div",{className:"border-b py-4 flex-1 overflow-hidden",children:[(0,t.jsx)("div",{className:"flex items-center justify-between w-full mb-4",children:a||l?(0,t.jsx)("span",{className:"inline-flex text-sm text-gray-700",children:"Loading..."}):(0,t.jsxs)("span",{className:"inline-flex text-sm text-gray-700",children:["Showing ",s.length," ",1===s.length?"team":"teams"]})}),(0,t.jsx)("div",{className:"h-[75vh] overflow-auto",children:(0,t.jsx)("div",{className:"rounded-lg custom-border relative",children:(0,t.jsx)("div",{className:"overflow-x-auto",children:(0,t.jsxs)(eO.Z,{className:"[&_td]:py-0.5 [&_th]:py-1",style:{width:o.getCenterTotalSize()},children:[(0,t.jsx)(eK.Z,{children:o.getHeaderGroups().map(e=>(0,t.jsx)(eP.Z,{children:e.headers.map(e=>(0,t.jsx)(eH.Z,{"data-header-id":e.id,className:"py-1 h-8 relative hover:bg-gray-50",style:{width:e.getSize(),maxWidth:e.column.columnDef.maxSize,position:"relative"},onMouseEnter:()=>{let s=document.querySelector('[data-header-id="'.concat(e.id,'"] .resizer'));s&&(s.style.opacity="0.5")},onMouseLeave:()=>{let s=document.querySelector('[data-header-id="'.concat(e.id,'"] .resizer'));s&&!e.column.getIsResizing()&&(s.style.opacity="0")},onClick:e.column.getToggleSortingHandler(),children:(0,t.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,t.jsx)("div",{className:"flex items-center",children:e.isPlaceholder?null:(0,eM.ie)(e.column.columnDef.header,e.getContext())}),(0,t.jsx)("div",{className:"w-4",children:e.column.getIsSorted()?({asc:(0,t.jsx)(eC.Z,{className:"h-4 w-4 text-blue-500"}),desc:(0,t.jsx)(eL.Z,{className:"h-4 w-4 text-blue-500"})})[e.column.getIsSorted()]:(0,t.jsx)(eD.Z,{className:"h-4 w-4 text-gray-400"})}),(0,t.jsx)("div",{onDoubleClick:()=>e.column.resetSize(),onMouseDown:e.getResizeHandler(),onTouchStart:e.getResizeHandler(),className:"resizer ".concat(o.options.columnResizeDirection," ").concat(e.column.getIsResizing()?"isResizing":""),style:{position:"absolute",right:0,top:0,height:"100%",width:"5px",background:e.column.getIsResizing()?"#3b82f6":"transparent",cursor:"col-resize",userSelect:"none",touchAction:"none",opacity:e.column.getIsResizing()?1:0}})]})},e.id))},e.id))}),(0,t.jsx)(eZ.Z,{children:a||l?(0,t.jsx)(eP.Z,{children:(0,t.jsx)(eI.Z,{colSpan:d.length,className:"h-8 text-center",children:(0,t.jsx)("div",{className:"text-center text-gray-500",children:(0,t.jsx)("p",{children:"\uD83D\uDE85 Loading teams..."})})})}):s.length>0?o.getRowModel().rows.map(e=>(0,t.jsx)(eP.Z,{className:"h-8",children:e.getVisibleCells().map(e=>(0,t.jsx)(eI.Z,{style:{width:e.column.getSize(),maxWidth:e.column.columnDef.maxSize,whiteSpace:"pre-wrap",overflow:"hidden"},className:"py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap",children:(0,eM.ie)(e.column.columnDef.cell,e.getContext())},e.id))},e.id)):(0,t.jsx)(eP.Z,{children:(0,t.jsx)(eI.Z,{colSpan:d.length,className:"h-8 text-center",children:(0,t.jsx)("div",{className:"text-center text-gray-500",children:(0,t.jsx)("p",{children:"No deleted teams found"})})})})})]})})})})]})})}function eY(){let{data:e,isPending:s,isFetching:a}=(0,ez.iN)(1,100);return(0,t.jsx)(eq,{teams:e||[],isLoading:s,isFetching:a})}var eB=a(91027);function eV(e){var s,a,l;let{accessToken:m,token:x,userRole:u,userID:h,allTeams:g,premiumUser:p}=e,[j,f]=(0,i.useState)(""),[y,b]=(0,i.useState)(!1),[w,_]=(0,i.useState)(!1),[k,S]=(0,i.useState)(1),[L]=(0,i.useState)(50),M=(0,i.useRef)(null),E=(0,i.useRef)(null),T=(0,i.useRef)(null),[A,R]=(0,i.useState)(r()().subtract(24,"hours").format("YYYY-MM-DDTHH:mm")),[z,O]=(0,i.useState)(r()().format("YYYY-MM-DDTHH:mm")),[Z,K]=(0,i.useState)(!1),[H,P]=(0,i.useState)(!1),[F,q]=(0,i.useState)(""),[Y,B]=(0,i.useState)(""),[V,U]=(0,i.useState)(""),[W,J]=(0,i.useState)(""),[G,Q]=(0,i.useState)(""),[$,X]=(0,i.useState)(null),[ee,es]=(0,i.useState)(null),[ea,et]=(0,i.useState)(""),[el,er]=(0,i.useState)(""),[en,ei]=(0,i.useState)(u&&C.lo.includes(u)),[ed,em]=(0,i.useState)("request logs"),[eb,eN]=(0,i.useState)(null),[ek,eS]=(0,i.useState)(null),eC=(0,d.NL)(),[eL,eD]=(0,i.useState)(()=>{let e=sessionStorage.getItem("isLiveTail");return null===e||JSON.parse(e)});(0,i.useEffect)(()=>{sessionStorage.setItem("isLiveTail",JSON.stringify(eL))},[eL]);let[eM,eE]=(0,i.useState)({value:24,unit:"hours"});(0,i.useEffect)(()=>{(async()=>{ee&&m&&X({...(await (0,o.keyInfoV1Call)(m,ee)).info,token:ee,api_key:ee})})()},[ee,m]),(0,i.useEffect)(()=>{function e(e){M.current&&!M.current.contains(e.target)&&_(!1),E.current&&!E.current.contains(e.target)&&b(!1),T.current&&!T.current.contains(e.target)&&P(!1)}return document.addEventListener("mousedown",e),()=>document.removeEventListener("mousedown",e)},[]),(0,i.useEffect)(()=>{u&&C.lo.includes(u)&&ei(!0)},[u]);let eT=(0,n.a)({queryKey:["logs","table",k,L,A,z,V,W,en?h:null,ea,G],queryFn:async()=>{if(!m||!x||!u||!h)return{data:[],total:0,page:1,page_size:L,total_pages:0};let e=r()(A).utc().format("YYYY-MM-DD HH:mm:ss"),s=Z?r()(z).utc().format("YYYY-MM-DD HH:mm:ss"):r()().utc().format("YYYY-MM-DD HH:mm:ss"),a=await (0,o.uiSpendLogsCall)(m,W||void 0,V||void 0,void 0,e,s,k,L,en?h:void 0,el,ea,G);return await N(a.data,e,m,eC),a.data=a.data.map(s=>{let a=eC.getQueryData(["logDetails",s.request_id,e]);return(null==a?void 0:a.messages)&&(null==a?void 0:a.response)&&(s.messages=a.messages,s.response=a.response),s}),a},enabled:!!m&&!!x&&!!u&&!!h&&"request logs"===ed,refetchInterval:!!eL&&1===k&&15e3,refetchIntervalInBackground:!0}),eA=eT.data||{data:[],total:0,page:1,page_size:L||10,total_pages:1},{filters:ez,filteredLogs:eO,allTeams:eZ,allKeyAliases:eI,handleFilterChange:eK,handleFilterReset:eH}=function(e){let{logs:s,accessToken:a,startTime:t,endTime:l,pageSize:d=eu.d,isCustomDate:c,setCurrentPage:m,userID:x,userRole:u}=e,h=(0,i.useMemo)(()=>({[eh.TEAM_ID]:"",[eh.KEY_HASH]:"",[eh.REQUEST_ID]:"",[eh.MODEL]:"",[eh.USER_ID]:"",[eh.END_USER]:"",[eh.STATUS]:"",[eh.KEY_ALIAS]:"",[eh.ERROR_CODE]:""}),[]),[g,p]=(0,i.useState)(h),[j,f]=(0,i.useState)({data:[],total:0,page:1,page_size:50,total_pages:0}),v=(0,i.useRef)(0),y=(0,i.useCallback)(async function(e){let s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:1;if(!a)return;console.log("Filters being sent to API:",e);let n=Date.now();v.current=n;let i=r()(t).utc().format("YYYY-MM-DD HH:mm:ss"),m=c?r()(l).utc().format("YYYY-MM-DD HH:mm:ss"):r()().utc().format("YYYY-MM-DD HH:mm:ss");try{let t=await (0,o.uiSpendLogsCall)(a,e[eh.KEY_HASH]||void 0,e[eh.TEAM_ID]||void 0,e[eh.REQUEST_ID]||void 0,i,m,s,d,e[eh.USER_ID]||void 0,e[eh.END_USER]||void 0,e[eh.STATUS]||void 0,e[eh.MODEL]||void 0,e[eh.KEY_ALIAS]||void 0,e[eh.ERROR_CODE]||void 0);n===v.current&&t.data&&f(t)}catch(e){console.error("Error searching users:",e)}},[a,t,l,c,d]),b=(0,i.useMemo)(()=>ex()((e,s)=>y(e,s),300),[y]);(0,i.useEffect)(()=>()=>b.cancel(),[b]);let N=(0,n.a)({queryKey:["allKeys"],queryFn:async()=>{if(!a)throw Error("Access token required");return await (0,ec.LO)(a)},enabled:!!a}).data||[],w=(0,i.useMemo)(()=>!!(g[eh.KEY_ALIAS]||g[eh.KEY_HASH]||g[eh.REQUEST_ID]||g[eh.USER_ID]||g[eh.END_USER]||g[eh.ERROR_CODE]),[g]),_=(0,i.useMemo)(()=>{if(!s||!s.data)return{data:[],total:0,page:1,page_size:50,total_pages:0};if(w)return s;let e=[...s.data];return g[eh.TEAM_ID]&&(e=e.filter(e=>e.team_id===g[eh.TEAM_ID])),g[eh.STATUS]&&(e=e.filter(e=>"success"===g[eh.STATUS]?!e.status||"success"===e.status:e.status===g[eh.STATUS])),g[eh.MODEL]&&(e=e.filter(e=>e.model===g[eh.MODEL])),g[eh.KEY_HASH]&&(e=e.filter(e=>e.api_key===g[eh.KEY_HASH])),g[eh.END_USER]&&(e=e.filter(e=>e.end_user===g[eh.END_USER])),g[eh.ERROR_CODE]&&(e=e.filter(e=>{let s=(e.metadata||{}).error_information;return s&&s.error_code===g[eh.ERROR_CODE]})),{data:e,total:s.total,page:s.page,page_size:s.page_size,total_pages:s.total_pages}},[s,g,w]),k=(0,i.useMemo)(()=>w?j&&j.data&&j.data.length>0?j:s||{data:[],total:0,page:1,page_size:50,total_pages:0}:_,[w,j,_,s]),{data:S}=(0,n.a)({queryKey:["allTeamsForLogFilters",a],queryFn:async()=>a&&await (0,ec.IE)(a)||[],enabled:!!a});return{filters:g,filteredLogs:k,allKeyAliases:N,allTeams:S,handleFilterChange:e=>{p(s=>{let a={...s,...e};for(let e of Object.keys(h))e in a||(a[e]=h[e]);return JSON.stringify(a)!==JSON.stringify(s)&&(m(1),b(a,1)),a})},handleFilterReset:()=>{p(h),f({data:[],total:0,page:1,page_size:50,total_pages:0}),b(h,1)}}}({logs:eA,accessToken:m,startTime:A,endTime:z,pageSize:L,isCustomDate:Z,setCurrentPage:S,userID:h,userRole:u}),eP=(0,i.useCallback)(async e=>{if(m)try{let s=(await (0,o.keyListCall)(m,null,null,e,null,null,k,L)).keys.find(s=>s.key_alias===e);s&&J(s.token)}catch(e){console.error("Error fetching key hash for alias:",e)}},[m,k,L]);(0,i.useEffect)(()=>{m&&(ez["Team ID"]?U(ez["Team ID"]):U(""),et(ez.Status||""),Q(ez.Model||""),er(ez["End User"]||""),ez["Key Hash"]?J(ez["Key Hash"]):ez["Key Alias"]?eP(ez["Key Alias"]):J(""))},[ez,m,eP]);let eF=(0,n.a)({queryKey:["sessionLogs",ek],queryFn:async()=>{if(!m||!ek)return{data:[],total:0,page:1,page_size:50,total_pages:1};let e=await (0,o.sessionSpendLogsCall)(m,ek);return{data:e.data||e||[],total:(e.data||e||[]).length,page:1,page_size:1e3,total_pages:1}},enabled:!!m&&!!ek});if((0,i.useEffect)(()=>{var e;(null===(e=eT.data)||void 0===e?void 0:e.data)&&eb&&!eT.data.data.some(e=>e.request_id===eb)&&eN(null)},[null===(s=eT.data)||void 0===s?void 0:s.data,eb]),!m||!x||!u||!h)return null;let eq=eO.data.filter(e=>!j||e.request_id.includes(j)||e.model.includes(j)||e.user&&e.user.includes(j)).map(e=>({...e,duration:(Date.parse(e.endTime)-Date.parse(e.startTime))/1e3,onKeyHashClick:e=>es(e),onSessionClick:e=>{e&&eS(e)}}))||[],eV=(null===(l=eF.data)||void 0===l?void 0:null===(a=l.data)||void 0===a?void 0:a.map(e=>({...e,onKeyHashClick:e=>es(e),onSessionClick:e=>{}})))||[],eW=function(e){let s=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",a=new Set;return e.forEach(e=>{let t=e.metadata||{};if("failure"===t.status&&t.error_information){let e=t.error_information.error_code;e&&(!s||e.toLowerCase().includes(s.toLowerCase()))&&a.add(e)}}),Array.from(a).map(e=>({label:e,value:e}))},eJ=[{name:"Team ID",label:"Team ID",isSearchable:!0,searchFn:async e=>g&&0!==g.length?g.filter(s=>s.team_id.toLowerCase().includes(e.toLowerCase())||s.team_alias&&s.team_alias.toLowerCase().includes(e.toLowerCase())).map(e=>({label:"".concat(e.team_alias||e.team_id," (").concat(e.team_id,")"),value:e.team_id})):[]},{name:"Status",label:"Status",isSearchable:!1,options:[{label:"Success",value:"success"},{label:"Failure",value:"failure"}]},{name:"Model",label:"Model",isSearchable:!1},{name:"Key Alias",label:"Key Alias",isSearchable:!0,searchFn:async e=>m?(await (0,ec.LO)(m)).filter(s=>s.toLowerCase().includes(e.toLowerCase())).map(e=>({label:e,value:e})):[]},{name:"End User",label:"End User",isSearchable:!0,searchFn:async e=>{if(!m)return[];let s=await (0,o.allEndUsersCall)(m);return((null==s?void 0:s.map(e=>e.user_id))||[]).filter(s=>s.toLowerCase().includes(e.toLowerCase())).map(e=>({label:e,value:e}))}},{name:"Error Code",label:"Error Code",isSearchable:!0,searchFn:async e=>eW(eA.data,e)},{name:"Key Hash",label:"Key Hash",isSearchable:!1}];if(ek&&eF.data)return(0,t.jsx)("div",{className:"w-full p-6",children:(0,t.jsx)(I,{sessionId:ek,logs:eF.data.data,onBack:()=>eS(null)})});let eG=[{label:"Last 15 Minutes",value:15,unit:"minutes"},{label:"Last Hour",value:1,unit:"hours"},{label:"Last 4 Hours",value:4,unit:"hours"},{label:"Last 24 Hours",value:24,unit:"hours"},{label:"Last 7 Days",value:7,unit:"days"}],eQ=eG.find(e=>e.value===eM.value&&e.unit===eM.unit),e$=Z?e_(Z,A,z):null==eQ?void 0:eQ.label;return(0,t.jsx)("div",{className:"w-full max-w-screen p-6 overflow-x-hidden box-border",children:(0,t.jsxs)(ej.Z,{defaultIndex:0,onIndexChange:e=>em(0===e?"request logs":"audit logs"),children:[(0,t.jsxs)(ef.Z,{children:[(0,t.jsx)(ep.Z,{children:"Request Logs"}),(0,t.jsx)(ep.Z,{children:"Audit Logs"}),(0,t.jsx)(ep.Z,{children:(0,t.jsxs)(t.Fragment,{children:["Deleted Keys ",(0,t.jsx)(eB.Z,{})]})}),(0,t.jsx)(ep.Z,{children:(0,t.jsxs)(t.Fragment,{children:["Deleted Teams ",(0,t.jsx)(eB.Z,{})]})})]}),(0,t.jsxs)(ey.Z,{children:[(0,t.jsxs)(ev.Z,{children:[(0,t.jsx)("div",{className:"flex items-center justify-between mb-4",children:(0,t.jsx)("h1",{className:"text-xl font-semibold",children:ek?(0,t.jsxs)(t.Fragment,{children:["Session: ",(0,t.jsx)("span",{className:"font-mono",children:ek}),(0,t.jsx)("button",{className:"ml-4 px-3 py-1 text-sm border rounded hover:bg-gray-50",onClick:()=>eS(null),children:"← Back to All Logs"})]}):"Request Logs"})}),$&&ee&&$.api_key===ee?(0,t.jsx)(D.Z,{keyId:ee,keyData:$,teams:g,onClose:()=>es(null),backButtonText:"Back to Logs"}):ek?(0,t.jsx)("div",{className:"bg-white rounded-lg shadow",children:(0,t.jsx)(c.w,{columns:v,data:eV,renderSubComponent:eU,getRowCanExpand:()=>!0})}):(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(eo.Z,{options:eJ,onApplyFilters:eK,onResetFilters:eH}),(0,t.jsxs)("div",{className:"bg-white rounded-lg shadow w-full max-w-full box-border",children:[(0,t.jsx)("div",{className:"border-b px-6 py-4 w-full max-w-full box-border",children:(0,t.jsxs)("div",{className:"flex flex-col md:flex-row items-start md:items-center justify-between space-y-4 md:space-y-0 w-full max-w-full box-border",children:[(0,t.jsxs)("div",{className:"flex flex-wrap items-center gap-3 w-full max-w-full box-border",children:[(0,t.jsxs)("div",{className:"relative w-64 min-w-0 flex-shrink-0",children:[(0,t.jsx)("input",{type:"text",placeholder:"Search by Request ID",className:"w-full px-3 py-2 pl-8 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500",value:j,onChange:e=>f(e.target.value)}),(0,t.jsx)("svg",{className:"absolute left-2.5 top-2.5 h-4 w-4 text-gray-500",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,t.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"})})]}),(0,t.jsxs)("div",{className:"flex items-center gap-2 min-w-0 flex-shrink",children:[(0,t.jsxs)("div",{className:"relative z-50",ref:T,children:[(0,t.jsxs)("button",{onClick:()=>P(!H),className:"px-3 py-2 text-sm border rounded-md hover:bg-gray-50 flex items-center gap-2",children:[(0,t.jsx)("svg",{className:"w-4 h-4",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,t.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"})}),e$]}),H&&(0,t.jsx)("div",{className:"absolute right-0 mt-2 w-64 bg-white rounded-lg shadow-lg border p-2 z-50",children:(0,t.jsxs)("div",{className:"space-y-1",children:[eG.map(e=>(0,t.jsx)("button",{className:"w-full px-3 py-2 text-left text-sm hover:bg-gray-50 rounded-md ".concat(e$===e.label?"bg-blue-50 text-blue-600":""),onClick:()=>{O(r()().format("YYYY-MM-DDTHH:mm")),R(r()().subtract(e.value,e.unit).format("YYYY-MM-DDTHH:mm")),eE({value:e.value,unit:e.unit}),K(!1),P(!1)},children:e.label},e.label)),(0,t.jsx)("div",{className:"border-t my-2"}),(0,t.jsx)("button",{className:"w-full px-3 py-2 text-left text-sm hover:bg-gray-50 rounded-md ".concat(Z?"bg-blue-50 text-blue-600":""),onClick:()=>K(!Z),children:"Custom Range"})]})})]}),(0,t.jsx)(()=>(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{className:"text-sm font-medium text-gray-900",children:"Live Tail"}),(0,t.jsx)(eg.Z,{color:"green",checked:eL,defaultChecked:!0,onChange:eD})]}),{}),(0,t.jsxs)("button",{onClick:()=>{eT.refetch()},className:"px-3 py-2 text-sm border rounded-md hover:bg-gray-50 flex items-center gap-2",title:"Refresh data",children:[(0,t.jsx)("svg",{className:"w-4 h-4 ".concat(eT.isFetching?"animate-spin":""),fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:(0,t.jsx)("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"})}),(0,t.jsx)("span",{children:"Refresh"})]})]}),Z&&(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("div",{children:(0,t.jsx)("input",{type:"datetime-local",value:A,onChange:e=>{R(e.target.value),S(1)},className:"px-3 py-2 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"})}),(0,t.jsx)("span",{className:"text-gray-500",children:"to"}),(0,t.jsx)("div",{children:(0,t.jsx)("input",{type:"datetime-local",value:z,onChange:e=>{O(e.target.value),S(1)},className:"px-3 py-2 border rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"})})]})]}),(0,t.jsxs)("div",{className:"flex items-center space-x-4",children:[(0,t.jsxs)("span",{className:"text-sm text-gray-700 whitespace-nowrap",children:["Showing ",eT.isLoading?"...":eO?(k-1)*L+1:0," -"," ",eT.isLoading?"...":eO?Math.min(k*L,eO.total):0," ","of ",eT.isLoading?"...":eO?eO.total:0," results"]}),(0,t.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,t.jsxs)("span",{className:"text-sm text-gray-700 min-w-[90px]",children:["Page ",eT.isLoading?"...":k," of"," ",eT.isLoading?"...":eO?eO.total_pages:1]}),(0,t.jsx)("button",{onClick:()=>S(e=>Math.max(1,e-1)),disabled:eT.isLoading||1===k,className:"px-3 py-1 text-sm border rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed",children:"Previous"}),(0,t.jsx)("button",{onClick:()=>S(e=>Math.min(eO.total_pages||1,e+1)),disabled:eT.isLoading||k===(eO.total_pages||1),className:"px-3 py-1 text-sm border rounded-md hover:bg-gray-50 disabled:opacity-50 disabled:cursor-not-allowed",children:"Next"})]})]})]})}),eL&&1===k&&(0,t.jsxs)("div",{className:"mb-4 px-4 py-2 bg-green-50 border border-greem-200 rounded-md flex items-center justify-between",children:[(0,t.jsx)("div",{className:"flex items-center gap-2",children:(0,t.jsx)("span",{className:"text-sm text-green-700",children:"Auto-refreshing every 15 seconds"})}),(0,t.jsx)("button",{onClick:()=>eD(!1),className:"text-sm text-green-600 hover:text-green-800",children:"Stop"})]}),(0,t.jsx)(c.w,{columns:v,data:eq,renderSubComponent:eU,getRowCanExpand:()=>!0})]})]})]}),(0,t.jsx)(ev.Z,{children:(0,t.jsx)(ew,{userID:h,userRole:u,token:x,accessToken:m,isActive:"audit logs"===ed,premiumUser:p,allTeams:g})}),(0,t.jsx)(ev.Z,{children:(0,t.jsx)(eR,{})}),(0,t.jsx)(ev.Z,{children:(0,t.jsx)(eY,{})})]})]})})}function eU(e){var s,a,l,r,n,i,d,o,c,x,u,g;let{row:p}=e,j=e=>{if("string"==typeof e)try{return JSON.parse(e)}catch(e){}return e},f=p.original.metadata||{},v="failure"===f.status,y=v?f.error_information:null,b=p.original.messages&&(Array.isArray(p.original.messages)?p.original.messages.length>0:Object.keys(p.original.messages).length>0),N=p.original.response&&Object.keys(j(p.original.response)).length>0,w=f.vector_store_request_metadata&&Array.isArray(f.vector_store_request_metadata)&&f.vector_store_request_metadata.length>0,_=null===(s=p.original.metadata)||void 0===s?void 0:s.guardrail_information,C=Array.isArray(_)?_:_?[_]:[],D=C.length>0,M=C.reduce((e,s)=>{let a=null==s?void 0:s.masked_entity_count;return a?e+Object.values(a).reduce((e,s)=>"number"==typeof s?e+s:e,0):e},0),E=1===C.length?null!==(g=null===(a=C[0])||void 0===a?void 0:a.guardrail_name)&&void 0!==g?g:"-":C.length>1?"".concat(C.length," guardrails"):"-",T=(0,ek.aS)(p.original.request_id,64);return(0,t.jsxs)("div",{className:"p-6 bg-gray-50 space-y-6 w-full max-w-full overflow-hidden box-border",children:[(0,t.jsxs)("div",{className:"bg-white rounded-lg shadow w-full max-w-full overflow-hidden",children:[(0,t.jsx)("div",{className:"p-4 border-b",children:(0,t.jsx)("h3",{className:"text-lg font-medium",children:"Request Details"})}),(0,t.jsxs)("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-4 p-4 w-full max-w-full overflow-hidden",children:[(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Request ID:"}),p.original.request_id.length>64?(0,t.jsx)(h.Z,{title:p.original.request_id,children:(0,t.jsx)("span",{className:"font-mono text-sm",children:T})}):(0,t.jsx)("span",{className:"font-mono text-sm",children:p.original.request_id})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Model:"}),(0,t.jsx)("span",{children:p.original.model})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Model ID:"}),(0,t.jsx)("span",{children:p.original.model_id})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Call Type:"}),(0,t.jsx)("span",{children:p.original.call_type})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Provider:"}),(0,t.jsx)("span",{children:p.original.custom_llm_provider||"-"})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"API Base:"}),(0,t.jsx)(h.Z,{title:p.original.api_base||"-",children:(0,t.jsx)("span",{className:"max-w-[15ch] truncate block",children:p.original.api_base||"-"})})]}),(null==p?void 0:null===(l=p.original)||void 0===l?void 0:l.requester_ip_address)&&(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"IP Address:"}),(0,t.jsx)("span",{children:null==p?void 0:null===(r=p.original)||void 0===r?void 0:r.requester_ip_address})]}),D&&(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Guardrail:"}),(0,t.jsxs)("div",{children:[(0,t.jsx)("span",{className:"font-mono",children:E}),M>0&&(0,t.jsxs)("span",{className:"ml-2 px-2 py-0.5 bg-blue-50 text-blue-700 rounded-md text-xs font-medium",children:[M," masked"]})]})]})]}),(0,t.jsxs)("div",{className:"space-y-2",children:[(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Tokens:"}),(0,t.jsxs)("span",{children:[p.original.total_tokens," (",p.original.prompt_tokens," prompt tokens +"," ",p.original.completion_tokens," completion tokens)"]})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Cache Read Tokens:"}),(0,t.jsx)("span",{children:(0,m.pw)((null===(i=p.original.metadata)||void 0===i?void 0:null===(n=i.additional_usage_values)||void 0===n?void 0:n.cache_read_input_tokens)||0)})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Cache Creation Tokens:"}),(0,t.jsx)("span",{children:(0,m.pw)(null===(d=p.original.metadata)||void 0===d?void 0:d.additional_usage_values.cache_creation_input_tokens)})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Cost:"}),(0,t.jsxs)("span",{children:["$",(0,m.pw)(p.original.spend||0,6)]})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Cache Hit:"}),(0,t.jsx)("span",{children:p.original.cache_hit})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Status:"}),(0,t.jsx)("span",{className:"px-2 py-1 rounded-md text-xs font-medium inline-block text-center w-16 ".concat("failure"!==((null===(o=p.original.metadata)||void 0===o?void 0:o.status)||"Success").toLowerCase()?"bg-green-100 text-green-800":"bg-red-100 text-red-800"),children:"failure"!==((null===(c=p.original.metadata)||void 0===c?void 0:c.status)||"Success").toLowerCase()?"Success":"Failure"})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Start Time:"}),(0,t.jsx)("span",{children:p.original.startTime})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"End Time:"}),(0,t.jsx)("span",{children:p.original.endTime})]}),(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"Duration:"}),(0,t.jsxs)("span",{children:[p.original.duration," s."]})]}),(null===(x=p.original.metadata)||void 0===x?void 0:x.litellm_overhead_time_ms)!==void 0&&(0,t.jsxs)("div",{className:"flex",children:[(0,t.jsx)("span",{className:"font-medium w-1/3",children:"LiteLLM Overhead:"}),(0,t.jsxs)("span",{children:[p.original.metadata.litellm_overhead_time_ms," ms"]})]})]})]})]}),(0,t.jsx)(ed,{costBreakdown:null===(u=p.original.metadata)||void 0===u?void 0:u.cost_breakdown,totalSpend:p.original.spend||0}),(0,t.jsx)(L,{show:!b&&!N}),(0,t.jsx)("div",{className:"w-full max-w-full overflow-hidden",children:(0,t.jsx)(k,{row:p,hasMessages:b,hasResponse:N,hasError:v,errorInfo:y,getRawRequest:()=>{var e;return(null===(e=p.original)||void 0===e?void 0:e.proxy_server_request)?j(p.original.proxy_server_request):j(p.original.messages)},formattedResponse:()=>v&&y?{error:{message:y.error_message||"An error occurred",type:y.error_class||"error",code:y.error_code||"unknown",param:null}}:j(p.original.response)})}),D&&(0,t.jsx)(ea,{data:_}),w&&(0,t.jsx)(K,{data:f.vector_store_request_metadata}),v&&y&&(0,t.jsx)(S,{errorInfo:y}),p.original.request_tags&&Object.keys(p.original.request_tags).length>0&&(0,t.jsxs)("div",{className:"bg-white rounded-lg shadow",children:[(0,t.jsx)("div",{className:"flex justify-between items-center p-4 border-b",children:(0,t.jsx)("h3",{className:"text-lg font-medium",children:"Request Tags"})}),(0,t.jsx)("div",{className:"p-4",children:(0,t.jsx)("div",{className:"flex flex-wrap gap-2",children:Object.entries(p.original.request_tags).map(e=>{let[s,a]=e;return(0,t.jsxs)("span",{className:"px-2 py-1 bg-gray-100 rounded-full text-xs",children:[s,": ",String(a)]},s)})})})]}),p.original.metadata&&Object.keys(p.original.metadata).length>0&&(0,t.jsxs)("div",{className:"bg-white rounded-lg shadow",children:[(0,t.jsxs)("div",{className:"flex justify-between items-center p-4 border-b",children:[(0,t.jsx)("h3",{className:"text-lg font-medium",children:"Metadata"}),(0,t.jsx)("button",{onClick:()=>{navigator.clipboard.writeText(JSON.stringify(p.original.metadata,null,2))},className:"p-1 hover:bg-gray-200 rounded",title:"Copy metadata",children:(0,t.jsxs)("svg",{xmlns:"http://www.w3.org/2000/svg",width:"16",height:"16",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[(0,t.jsx)("rect",{x:"9",y:"9",width:"13",height:"13",rx:"2",ry:"2"}),(0,t.jsx)("path",{d:"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"})]})})]}),(0,t.jsx)("div",{className:"p-4 overflow-auto max-h-64",children:(0,t.jsx)("pre",{className:"text-xs font-mono whitespace-pre-wrap break-all",children:JSON.stringify(p.original.metadata,null,2)})})]})]})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1994-6637a121c9ee1602.js b/litellm/proxy/_experimental/out/_next/static/chunks/1994-6637a121c9ee1602.js deleted file mode 100644 index 90f29480d63..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/1994-6637a121c9ee1602.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1994],{4156:function(e,n,t){t.d(n,{Z:function(){return O}});var o=t(2265),a=t(36760),r=t.n(a),c=t(20873),l=t(28791),i=t(6694),s=t(34709),u=t(71744),d=t(86586),b=t(64024),p=t(39109);let f=o.createContext(null);var v=t(23159),m=t(66531),h=function(e,n){var t={};for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&0>n.indexOf(o)&&(t[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var a=0,o=Object.getOwnPropertySymbols(e);an.indexOf(o[a])&&Object.prototype.propertyIsEnumerable.call(e,o[a])&&(t[o[a]]=e[o[a]]);return t};let g=o.forwardRef((e,n)=>{var t;let{prefixCls:a,className:g,rootClassName:C,children:y,indeterminate:k=!1,style:x,onMouseEnter:O,onMouseLeave:E,skipGroup:S=!1,disabled:w}=e,Z=h(e,["prefixCls","className","rootClassName","children","indeterminate","style","onMouseEnter","onMouseLeave","skipGroup","disabled"]),{getPrefixCls:P,direction:N,checkbox:j}=o.useContext(u.E_),I=o.useContext(f),{isFormItemInput:R}=o.useContext(p.aM),z=o.useContext(d.Z),B=null!==(t=(null==I?void 0:I.disabled)||w)&&void 0!==t?t:z,D=o.useRef(Z.value),M=o.useRef(null),_=(0,l.sQ)(n,M);o.useEffect(()=>{null==I||I.registerValue(Z.value)},[]),o.useEffect(()=>{if(!S)return Z.value!==D.current&&(null==I||I.cancelValue(D.current),null==I||I.registerValue(Z.value),D.current=Z.value),()=>null==I?void 0:I.cancelValue(Z.value)},[Z.value]),o.useEffect(()=>{var e;(null===(e=M.current)||void 0===e?void 0:e.input)&&(M.current.input.indeterminate=k)},[k]);let W=P("checkbox",a),q=(0,b.Z)(W),[H,T,G]=(0,v.ZP)(W,q),V=Object.assign({},Z);I&&!S&&(V.onChange=function(){for(var e=arguments.length,n=Array(e),t=0;tn.indexOf(o)&&(t[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var a=0,o=Object.getOwnPropertySymbols(e);an.indexOf(o[a])&&Object.prototype.propertyIsEnumerable.call(e,o[a])&&(t[o[a]]=e[o[a]]);return t};let x=o.forwardRef((e,n)=>{let{defaultValue:t,children:a,options:c=[],prefixCls:l,className:i,rootClassName:s,style:d,onChange:p}=e,m=k(e,["defaultValue","children","options","prefixCls","className","rootClassName","style","onChange"]),{getPrefixCls:h,direction:x}=o.useContext(u.E_),[O,E]=o.useState(m.value||t||[]),[S,w]=o.useState([]);o.useEffect(()=>{"value"in m&&E(m.value||[])},[m.value]);let Z=o.useMemo(()=>c.map(e=>"string"==typeof e||"number"==typeof e?{label:e,value:e}:e),[c]),P=e=>{w(n=>n.filter(n=>n!==e))},N=e=>{w(n=>[].concat((0,C.Z)(n),[e]))},j=e=>{let n=O.indexOf(e.value),t=(0,C.Z)(O);-1===n?t.push(e.value):t.splice(n,1),"value"in m||E(t),null==p||p(t.filter(e=>S.includes(e)).sort((e,n)=>Z.findIndex(n=>n.value===e)-Z.findIndex(e=>e.value===n)))},I=h("checkbox",l),R="".concat(I,"-group"),z=(0,b.Z)(I),[B,D,M]=(0,v.ZP)(I,z),_=(0,y.Z)(m,["value","disabled"]),W=c.length?Z.map(e=>o.createElement(g,{prefixCls:I,key:e.value.toString(),disabled:"disabled"in e?e.disabled:m.disabled,value:e.value,checked:O.includes(e.value),onChange:e.onChange,className:r()("".concat(R,"-item"),e.className),style:e.style,title:e.title,id:e.id,required:e.required},e.label)):a,q=o.useMemo(()=>({toggleOption:j,value:O,disabled:m.disabled,name:m.name,registerValue:N,cancelValue:P}),[j,O,m.disabled,m.name,N,P]),H=r()(R,{["".concat(R,"-rtl")]:"rtl"===x},i,s,M,z,D);return B(o.createElement("div",Object.assign({className:H,style:d},_,{ref:n}),o.createElement(f.Provider,{value:q},W)))});g.Group=x,g.__ANT_CHECKBOX=!0;var O=g},23159:function(e,n,t){t.d(n,{C2:function(){return i}});var o=t(93463),a=t(12918),r=t(71140),c=t(99320);let l=e=>{let{checkboxCls:n}=e,t="".concat(n,"-wrapper");return[{["".concat(n,"-group")]:Object.assign(Object.assign({},(0,a.Wf)(e)),{display:"inline-flex",flexWrap:"wrap",columnGap:e.marginXS,["> ".concat(e.antCls,"-row")]:{flex:1}}),[t]:Object.assign(Object.assign({},(0,a.Wf)(e)),{display:"inline-flex",alignItems:"baseline",cursor:"pointer","&:after":{display:"inline-block",width:0,overflow:"hidden",content:"'\\a0'"},["& + ".concat(t)]:{marginInlineStart:0},["&".concat(t,"-in-form-item")]:{'input[type="checkbox"]':{width:14,height:14}}}),[n]:Object.assign(Object.assign({},(0,a.Wf)(e)),{position:"relative",whiteSpace:"nowrap",lineHeight:1,cursor:"pointer",borderRadius:e.borderRadiusSM,alignSelf:"center",["".concat(n,"-input")]:{position:"absolute",inset:0,zIndex:1,cursor:"pointer",opacity:0,margin:0,["&:focus-visible + ".concat(n,"-inner")]:(0,a.oN)(e)},["".concat(n,"-inner")]:{boxSizing:"border-box",display:"block",width:e.checkboxSize,height:e.checkboxSize,direction:"ltr",backgroundColor:e.colorBgContainer,border:"".concat((0,o.bf)(e.lineWidth)," ").concat(e.lineType," ").concat(e.colorBorder),borderRadius:e.borderRadiusSM,borderCollapse:"separate",transition:"all ".concat(e.motionDurationSlow),"&:after":{boxSizing:"border-box",position:"absolute",top:"50%",insetInlineStart:"25%",display:"table",width:e.calc(e.checkboxSize).div(14).mul(5).equal(),height:e.calc(e.checkboxSize).div(14).mul(8).equal(),border:"".concat((0,o.bf)(e.lineWidthBold)," solid ").concat(e.colorWhite),borderTop:0,borderInlineStart:0,transform:"rotate(45deg) scale(0) translate(-50%,-50%)",opacity:0,content:'""',transition:"all ".concat(e.motionDurationFast," ").concat(e.motionEaseInBack,", opacity ").concat(e.motionDurationFast)}},"& + span":{paddingInlineStart:e.paddingXS,paddingInlineEnd:e.paddingXS}})},{["\n ".concat(t,":not(").concat(t,"-disabled),\n ").concat(n,":not(").concat(n,"-disabled)\n ")]:{["&:hover ".concat(n,"-inner")]:{borderColor:e.colorPrimary}},["".concat(t,":not(").concat(t,"-disabled)")]:{["&:hover ".concat(n,"-checked:not(").concat(n,"-disabled) ").concat(n,"-inner")]:{backgroundColor:e.colorPrimaryHover,borderColor:"transparent"},["&:hover ".concat(n,"-checked:not(").concat(n,"-disabled):after")]:{borderColor:e.colorPrimaryHover}}},{["".concat(n,"-checked")]:{["".concat(n,"-inner")]:{backgroundColor:e.colorPrimary,borderColor:e.colorPrimary,"&:after":{opacity:1,transform:"rotate(45deg) scale(1) translate(-50%,-50%)",transition:"all ".concat(e.motionDurationMid," ").concat(e.motionEaseOutBack," ").concat(e.motionDurationFast)}}},["\n ".concat(t,"-checked:not(").concat(t,"-disabled),\n ").concat(n,"-checked:not(").concat(n,"-disabled)\n ")]:{["&:hover ".concat(n,"-inner")]:{backgroundColor:e.colorPrimaryHover,borderColor:"transparent"}}},{[n]:{"&-indeterminate":{"&":{["".concat(n,"-inner")]:{backgroundColor:"".concat(e.colorBgContainer),borderColor:"".concat(e.colorBorder),"&:after":{top:"50%",insetInlineStart:"50%",width:e.calc(e.fontSizeLG).div(2).equal(),height:e.calc(e.fontSizeLG).div(2).equal(),backgroundColor:e.colorPrimary,border:0,transform:"translate(-50%, -50%) scale(1)",opacity:1,content:'""'}},["&:hover ".concat(n,"-inner")]:{backgroundColor:"".concat(e.colorBgContainer),borderColor:"".concat(e.colorPrimary)}}}}},{["".concat(t,"-disabled")]:{cursor:"not-allowed"},["".concat(n,"-disabled")]:{["&, ".concat(n,"-input")]:{cursor:"not-allowed",pointerEvents:"none"},["".concat(n,"-inner")]:{background:e.colorBgContainerDisabled,borderColor:e.colorBorder,"&:after":{borderColor:e.colorTextDisabled}},"&:after":{display:"none"},"& + span":{color:e.colorTextDisabled},["&".concat(n,"-indeterminate ").concat(n,"-inner::after")]:{background:e.colorTextDisabled}}}]};function i(e,n){return l((0,r.IX)(n,{checkboxCls:".".concat(e),checkboxSize:n.controlInteractiveSize}))}n.ZP=(0,c.I$)("Checkbox",(e,n)=>{let{prefixCls:t}=n;return[i(t,e)]})},66531:function(e,n,t){t.d(n,{Z:function(){return r}});var o=t(2265),a=t(53346);function r(e){let n=o.useRef(null),t=()=>{a.Z.cancel(n.current),n.current=null};return[()=>{t(),n.current=(0,a.Z)(()=>{n.current=null})},o=>{n.current&&(o.stopPropagation(),t()),null==e||e(o)}]}},20873:function(e,n,t){var o=t(1119),a=t(31686),r=t(11993),c=t(26365),l=t(6989),i=t(36760),s=t.n(i),u=t(50506),d=t(2265),b=["prefixCls","className","style","checked","disabled","defaultChecked","type","title","onChange"],p=(0,d.forwardRef)(function(e,n){var t=e.prefixCls,i=void 0===t?"rc-checkbox":t,p=e.className,f=e.style,v=e.checked,m=e.disabled,h=e.defaultChecked,g=e.type,C=void 0===g?"checkbox":g,y=e.title,k=e.onChange,x=(0,l.Z)(e,b),O=(0,d.useRef)(null),E=(0,d.useRef)(null),S=(0,u.Z)(void 0!==h&&h,{value:v}),w=(0,c.Z)(S,2),Z=w[0],P=w[1];(0,d.useImperativeHandle)(n,function(){return{focus:function(e){var n;null===(n=O.current)||void 0===n||n.focus(e)},blur:function(){var e;null===(e=O.current)||void 0===e||e.blur()},input:O.current,nativeElement:E.current}});var N=s()(i,p,(0,r.Z)((0,r.Z)({},"".concat(i,"-checked"),Z),"".concat(i,"-disabled"),m));return d.createElement("span",{className:N,title:y,style:f,ref:E},d.createElement("input",(0,o.Z)({},x,{className:"".concat(i,"-input"),ref:O,onChange:function(n){m||("checked"in e||P(n.target.checked),null==k||k({target:(0,a.Z)((0,a.Z)({},e),{},{type:C,checked:n.target.checked}),stopPropagation:function(){n.stopPropagation()},preventDefault:function(){n.preventDefault()},nativeEvent:n.nativeEvent}))},disabled:m,checked:!!Z,type:C})),d.createElement("span",{className:"".concat(i,"-inner")}))});n.Z=p}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/2136-2c0d6e8c18d2c5c4.js b/litellm/proxy/_experimental/out/_next/static/chunks/2136-2c0d6e8c18d2c5c4.js deleted file mode 100644 index caf79b501e8..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/2136-2c0d6e8c18d2c5c4.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2136,1623],{69993:function(e,t,r){r.d(t,{Z:function(){return s}});var n=r(1119),a=r(2265),i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M300 328a60 60 0 10120 0 60 60 0 10-120 0zM852 64H172c-17.7 0-32 14.3-32 32v660c0 17.7 14.3 32 32 32h680c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-32 660H204V128h616v596zM604 328a60 60 0 10120 0 60 60 0 10-120 0zm250.2 556H169.8c-16.5 0-29.8 14.3-29.8 32v36c0 4.4 3.3 8 7.4 8h729.1c4.1 0 7.4-3.6 7.4-8v-36c.1-17.7-13.2-32-29.7-32zM664 508H360c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h304c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"}}]},name:"robot",theme:"outlined"},o=r(55015),s=a.forwardRef(function(e,t){return a.createElement(o.Z,(0,n.Z)({},e,{ref:t,icon:i}))})},47323:function(e,t,r){r.d(t,{Z:function(){return p}});var n=r(5853),a=r(2265),i=r(47187),o=r(7084),s=r(13241),u=r(1153),l=r(26898);let c={xs:{paddingX:"px-1.5",paddingY:"py-1.5"},sm:{paddingX:"px-1.5",paddingY:"py-1.5"},md:{paddingX:"px-2",paddingY:"py-2"},lg:{paddingX:"px-2",paddingY:"py-2"},xl:{paddingX:"px-2.5",paddingY:"py-2.5"}},d={xs:{height:"h-3",width:"w-3"},sm:{height:"h-5",width:"w-5"},md:{height:"h-5",width:"w-5"},lg:{height:"h-7",width:"w-7"},xl:{height:"h-9",width:"w-9"}},h={simple:{rounded:"",border:"",ring:"",shadow:""},light:{rounded:"rounded-tremor-default",border:"",ring:"",shadow:""},shadow:{rounded:"rounded-tremor-default",border:"border",ring:"",shadow:"shadow-tremor-card dark:shadow-dark-tremor-card"},solid:{rounded:"rounded-tremor-default",border:"border-2",ring:"ring-1",shadow:""},outlined:{rounded:"rounded-tremor-default",border:"border",ring:"ring-2",shadow:""}},f=(e,t)=>{switch(e){case"simple":return{textColor:t?(0,u.bM)(t,l.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:"",borderColor:"",ringColor:""};case"light":return{textColor:t?(0,u.bM)(t,l.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,s.q)((0,u.bM)(t,l.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand-muted dark:bg-dark-tremor-brand-muted",borderColor:"",ringColor:""};case"shadow":return{textColor:t?(0,u.bM)(t,l.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,s.q)((0,u.bM)(t,l.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:"border-tremor-border dark:border-dark-tremor-border",ringColor:""};case"solid":return{textColor:t?(0,u.bM)(t,l.K.text).textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",bgColor:t?(0,s.q)((0,u.bM)(t,l.K.background).bgColor,"bg-opacity-20"):"bg-tremor-brand dark:bg-dark-tremor-brand",borderColor:"border-tremor-brand-inverted dark:border-dark-tremor-brand-inverted",ringColor:"ring-tremor-ring dark:ring-dark-tremor-ring"};case"outlined":return{textColor:t?(0,u.bM)(t,l.K.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,s.q)((0,u.bM)(t,l.K.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:t?(0,u.bM)(t,l.K.ring).borderColor:"border-tremor-brand-subtle dark:border-dark-tremor-brand-subtle",ringColor:t?(0,s.q)((0,u.bM)(t,l.K.ring).ringColor,"ring-opacity-40"):"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"}}},m=(0,u.fn)("Icon"),p=a.forwardRef((e,t)=>{let{icon:r,variant:l="simple",tooltip:p,size:g=o.u8.SM,color:b,className:y}=e,v=(0,n._T)(e,["icon","variant","tooltip","size","color","className"]),w=f(l,b),{tooltipProps:C,getReferenceProps:k}=(0,i.l)();return a.createElement("span",Object.assign({ref:(0,u.lq)([t,C.refs.setReference]),className:(0,s.q)(m("root"),"inline-flex shrink-0 items-center justify-center",w.bgColor,w.textColor,w.borderColor,w.ringColor,h[l].rounded,h[l].border,h[l].shadow,h[l].ring,c[g].paddingX,c[g].paddingY,y)},k,v),a.createElement(i.Z,Object.assign({text:p},C)),a.createElement(r,{className:(0,s.q)(m("icon"),"shrink-0",d[g].height,d[g].width)}))});p.displayName="Icon"},49804:function(e,t,r){r.d(t,{Z:function(){return l}});var n=r(5853),a=r(13241),i=r(1153),o=r(2265),s=r(9496);let u=(0,i.fn)("Col"),l=o.forwardRef((e,t)=>{let{numColSpan:r=1,numColSpanSm:i,numColSpanMd:l,numColSpanLg:c,children:d,className:h}=e,f=(0,n._T)(e,["numColSpan","numColSpanSm","numColSpanMd","numColSpanLg","children","className"]),m=(e,t)=>e&&Object.keys(t).includes(String(e))?t[e]:"";return o.createElement("div",Object.assign({ref:t,className:(0,a.q)(u("root"),(()=>{let e=m(r,s.PT),t=m(i,s.SP),n=m(l,s.VS),o=m(c,s._w);return(0,a.q)(e,t,n,o)})(),h)},f),d)});l.displayName="Col"},94789:function(e,t,r){r.d(t,{Z:function(){return l}});var n=r(5853),a=r(2265),i=r(26898),o=r(13241),s=r(1153);let u=(0,s.fn)("Callout"),l=a.forwardRef((e,t)=>{let{title:r,icon:l,color:c,className:d,children:h}=e,f=(0,n._T)(e,["title","icon","color","className","children"]);return a.createElement("div",Object.assign({ref:t,className:(0,o.q)(u("root"),"flex flex-col overflow-hidden rounded-tremor-default text-tremor-default border-l-4 py-3 pr-3 pl-4",c?(0,o.q)((0,s.bM)(c,i.K.background).bgColor,(0,s.bM)(c,i.K.darkBorder).borderColor,(0,s.bM)(c,i.K.darkText).textColor,"dark:bg-opacity-10 bg-opacity-10"):(0,o.q)("bg-tremor-brand-faint border-tremor-brand-emphasis text-tremor-brand-emphasis","dark:bg-dark-tremor-brand-muted/70 dark:border-dark-tremor-brand-emphasis dark:text-dark-tremor-brand-emphasis"),d)},f),a.createElement("div",{className:(0,o.q)(u("header"),"flex items-start")},l?a.createElement(l,{className:(0,o.q)(u("icon"),"flex-none h-5 w-5 mr-1.5")}):null,a.createElement("h4",{className:(0,o.q)(u("title"),"font-semibold")},r)),a.createElement("p",{className:(0,o.q)(u("body"),"overflow-y-auto",h?"mt-2":"")},h))});l.displayName="Callout"},32489:function(e,t,r){r.d(t,{Z:function(){return n}});let n=(0,r(79205).Z)("x",[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]])},10900:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M10 19l-7-7m0 0l7-7m-7 7h18"}))});t.Z=a},91777:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M18.364 18.364A9 9 0 005.636 5.636m12.728 12.728A9 9 0 015.636 5.636m12.728 12.728L5.636 5.636"}))});t.Z=a},47686:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M9 5l7 7-7 7"}))});t.Z=a},58710:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"}))});t.Z=a},82182:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"}),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M15 12a3 3 0 11-6 0 3 3 0 016 0z"}))});t.Z=a},79814:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"}))});t.Z=a},2356:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M3 4a1 1 0 011-1h16a1 1 0 011 1v2.586a1 1 0 01-.293.707l-6.414 6.414a1 1 0 00-.293.707V17l-4 4v-6.586a1 1 0 00-.293-.707L3.293 7.293A1 1 0 013 6.586V4z"}))});t.Z=a},93416:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"}))});t.Z=a},77355:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z"}))});t.Z=a},22452:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M12 4v16m8-8H4"}))});t.Z=a},25327:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01"}))});t.Z=a},49084:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"}))});t.Z=a},3497:function(e,t,r){var n=r(2265);let a=n.forwardRef(function(e,t){return n.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),n.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"}))});t.Z=a},2894:function(e,t,r){r.d(t,{R:function(){return s},m:function(){return o}});var n=r(18238),a=r(7989),i=r(11255),o=class extends a.F{#e;#t;#r;#n;constructor(e){super(),this.#e=e.client,this.mutationId=e.mutationId,this.#r=e.mutationCache,this.#t=[],this.state=e.state||s(),this.setOptions(e.options),this.scheduleGc()}setOptions(e){this.options=e,this.updateGcTime(this.options.gcTime)}get meta(){return this.options.meta}addObserver(e){this.#t.includes(e)||(this.#t.push(e),this.clearGcTimeout(),this.#r.notify({type:"observerAdded",mutation:this,observer:e}))}removeObserver(e){this.#t=this.#t.filter(t=>t!==e),this.scheduleGc(),this.#r.notify({type:"observerRemoved",mutation:this,observer:e})}optionalRemove(){this.#t.length||("pending"===this.state.status?this.scheduleGc():this.#r.remove(this))}continue(){return this.#n?.continue()??this.execute(this.state.variables)}async execute(e){let t=()=>{this.#a({type:"continue"})},r={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};this.#n=(0,i.Mz)({fn:()=>this.options.mutationFn?this.options.mutationFn(e,r):Promise.reject(Error("No mutationFn found")),onFail:(e,t)=>{this.#a({type:"failed",failureCount:e,error:t})},onPause:()=>{this.#a({type:"pause"})},onContinue:t,retry:this.options.retry??0,retryDelay:this.options.retryDelay,networkMode:this.options.networkMode,canRun:()=>this.#r.canRun(this)});let n="pending"===this.state.status,a=!this.#n.canStart();try{if(n)t();else{this.#a({type:"pending",variables:e,isPaused:a}),await this.#r.config.onMutate?.(e,this,r);let t=await this.options.onMutate?.(e,r);t!==this.state.context&&this.#a({type:"pending",context:t,variables:e,isPaused:a})}let i=await this.#n.start();return await this.#r.config.onSuccess?.(i,e,this.state.context,this,r),await this.options.onSuccess?.(i,e,this.state.context,r),await this.#r.config.onSettled?.(i,null,this.state.variables,this.state.context,this,r),await this.options.onSettled?.(i,null,e,this.state.context,r),this.#a({type:"success",data:i}),i}catch(t){try{throw await this.#r.config.onError?.(t,e,this.state.context,this,r),await this.options.onError?.(t,e,this.state.context,r),await this.#r.config.onSettled?.(void 0,t,this.state.variables,this.state.context,this,r),await this.options.onSettled?.(void 0,t,e,this.state.context,r),t}finally{this.#a({type:"error",error:t})}}finally{this.#r.runNext(this)}}#a(e){this.state=(t=>{switch(e.type){case"failed":return{...t,failureCount:e.failureCount,failureReason:e.error};case"pause":return{...t,isPaused:!0};case"continue":return{...t,isPaused:!1};case"pending":return{...t,context:e.context,data:void 0,failureCount:0,failureReason:null,error:null,isPaused:e.isPaused,status:"pending",variables:e.variables,submittedAt:Date.now()};case"success":return{...t,data:e.data,failureCount:0,failureReason:null,error:null,status:"success",isPaused:!1};case"error":return{...t,data:void 0,error:e.error,failureCount:t.failureCount+1,failureReason:e.error,isPaused:!1,status:"error"}}})(this.state),n.Vr.batch(()=>{this.#t.forEach(t=>{t.onMutationUpdate(e)}),this.#r.notify({mutation:this,type:"updated",action:e})})}};function s(){return{context:void 0,data:void 0,error:null,failureCount:0,failureReason:null,isPaused:!1,status:"idle",variables:void 0,submittedAt:0}}},21623:function(e,t,r){r.d(t,{S:function(){return p}});var n=r(45345),a=r(21733),i=r(18238),o=r(24112),s=class extends o.l{constructor(e={}){super(),this.config=e,this.#i=new Map}#i;build(e,t,r){let i=t.queryKey,o=t.queryHash??(0,n.Rm)(i,t),s=this.get(o);return s||(s=new a.A({client:e,queryKey:i,queryHash:o,options:e.defaultQueryOptions(t),state:r,defaultOptions:e.getQueryDefaults(i)}),this.add(s)),s}add(e){this.#i.has(e.queryHash)||(this.#i.set(e.queryHash,e),this.notify({type:"added",query:e}))}remove(e){let t=this.#i.get(e.queryHash);t&&(e.destroy(),t===e&&this.#i.delete(e.queryHash),this.notify({type:"removed",query:e}))}clear(){i.Vr.batch(()=>{this.getAll().forEach(e=>{this.remove(e)})})}get(e){return this.#i.get(e)}getAll(){return[...this.#i.values()]}find(e){let t={exact:!0,...e};return this.getAll().find(e=>(0,n._x)(t,e))}findAll(e={}){let t=this.getAll();return Object.keys(e).length>0?t.filter(t=>(0,n._x)(e,t)):t}notify(e){i.Vr.batch(()=>{this.listeners.forEach(t=>{t(e)})})}onFocus(){i.Vr.batch(()=>{this.getAll().forEach(e=>{e.onFocus()})})}onOnline(){i.Vr.batch(()=>{this.getAll().forEach(e=>{e.onOnline()})})}},u=r(2894),l=class extends o.l{constructor(e={}){super(),this.config=e,this.#o=new Set,this.#s=new Map,this.#u=0}#o;#s;#u;build(e,t,r){let n=new u.m({client:e,mutationCache:this,mutationId:++this.#u,options:e.defaultMutationOptions(t),state:r});return this.add(n),n}add(e){this.#o.add(e);let t=c(e);if("string"==typeof t){let r=this.#s.get(t);r?r.push(e):this.#s.set(t,[e])}this.notify({type:"added",mutation:e})}remove(e){if(this.#o.delete(e)){let t=c(e);if("string"==typeof t){let r=this.#s.get(t);if(r){if(r.length>1){let t=r.indexOf(e);-1!==t&&r.splice(t,1)}else r[0]===e&&this.#s.delete(t)}}}this.notify({type:"removed",mutation:e})}canRun(e){let t=c(e);if("string"!=typeof t)return!0;{let r=this.#s.get(t),n=r?.find(e=>"pending"===e.state.status);return!n||n===e}}runNext(e){let t=c(e);if("string"!=typeof t)return Promise.resolve();{let r=this.#s.get(t)?.find(t=>t!==e&&t.state.isPaused);return r?.continue()??Promise.resolve()}}clear(){i.Vr.batch(()=>{this.#o.forEach(e=>{this.notify({type:"removed",mutation:e})}),this.#o.clear(),this.#s.clear()})}getAll(){return Array.from(this.#o)}find(e){let t={exact:!0,...e};return this.getAll().find(e=>(0,n.X7)(t,e))}findAll(e={}){return this.getAll().filter(t=>(0,n.X7)(e,t))}notify(e){i.Vr.batch(()=>{this.listeners.forEach(t=>{t(e)})})}resumePausedMutations(){let e=this.getAll().filter(e=>e.state.isPaused);return i.Vr.batch(()=>Promise.all(e.map(e=>e.continue().catch(n.ZT))))}};function c(e){return e.options.scope?.id}var d=r(87045),h=r(57853);function f(e){return{onFetch:(t,r)=>{let a=t.options,i=t.fetchOptions?.meta?.fetchMore?.direction,o=t.state.data?.pages||[],s=t.state.data?.pageParams||[],u={pages:[],pageParams:[]},l=0,c=async()=>{let r=!1,c=e=>{Object.defineProperty(e,"signal",{enumerable:!0,get:()=>(t.signal.aborted?r=!0:t.signal.addEventListener("abort",()=>{r=!0}),t.signal)})},d=(0,n.cG)(t.options,t.fetchOptions),h=async(e,a,i)=>{if(r)return Promise.reject();if(null==a&&e.pages.length)return Promise.resolve(e);let o=(()=>{let e={client:t.client,queryKey:t.queryKey,pageParam:a,direction:i?"backward":"forward",meta:t.options.meta};return c(e),e})(),s=await d(o),{maxPages:u}=t.options,l=i?n.Ht:n.VX;return{pages:l(e.pages,s,u),pageParams:l(e.pageParams,a,u)}};if(i&&o.length){let e="backward"===i,t={pages:o,pageParams:s},r=(e?function(e,{pages:t,pageParams:r}){return t.length>0?e.getPreviousPageParam?.(t[0],t,r[0],r):void 0}:m)(a,t);u=await h(t,r,e)}else{let t=e??o.length;do{let e=0===l?s[0]??a.initialPageParam:m(a,u);if(l>0&&null==e)break;u=await h(u,e),l++}while(lt.options.persister?.(c,{client:t.client,queryKey:t.queryKey,meta:t.options.meta,signal:t.signal},r):t.fetchFn=c}}}function m(e,{pages:t,pageParams:r}){let n=t.length-1;return t.length>0?e.getNextPageParam(t[n],t,r[n],r):void 0}var p=class{#l;#r;#c;#d;#h;#f;#m;#p;constructor(e={}){this.#l=e.queryCache||new s,this.#r=e.mutationCache||new l,this.#c=e.defaultOptions||{},this.#d=new Map,this.#h=new Map,this.#f=0}mount(){this.#f++,1===this.#f&&(this.#m=d.j.subscribe(async e=>{e&&(await this.resumePausedMutations(),this.#l.onFocus())}),this.#p=h.N.subscribe(async e=>{e&&(await this.resumePausedMutations(),this.#l.onOnline())}))}unmount(){this.#f--,0===this.#f&&(this.#m?.(),this.#m=void 0,this.#p?.(),this.#p=void 0)}isFetching(e){return this.#l.findAll({...e,fetchStatus:"fetching"}).length}isMutating(e){return this.#r.findAll({...e,status:"pending"}).length}getQueryData(e){let t=this.defaultQueryOptions({queryKey:e});return this.#l.get(t.queryHash)?.state.data}ensureQueryData(e){let t=this.defaultQueryOptions(e),r=this.#l.build(this,t),a=r.state.data;return void 0===a?this.fetchQuery(e):(e.revalidateIfStale&&r.isStaleByTime((0,n.KC)(t.staleTime,r))&&this.prefetchQuery(t),Promise.resolve(a))}getQueriesData(e){return this.#l.findAll(e).map(({queryKey:e,state:t})=>[e,t.data])}setQueryData(e,t,r){let a=this.defaultQueryOptions({queryKey:e}),i=this.#l.get(a.queryHash),o=i?.state.data,s=(0,n.SE)(t,o);if(void 0!==s)return this.#l.build(this,a).setData(s,{...r,manual:!0})}setQueriesData(e,t,r){return i.Vr.batch(()=>this.#l.findAll(e).map(({queryKey:e})=>[e,this.setQueryData(e,t,r)]))}getQueryState(e){let t=this.defaultQueryOptions({queryKey:e});return this.#l.get(t.queryHash)?.state}removeQueries(e){let t=this.#l;i.Vr.batch(()=>{t.findAll(e).forEach(e=>{t.remove(e)})})}resetQueries(e,t){let r=this.#l;return i.Vr.batch(()=>(r.findAll(e).forEach(e=>{e.reset()}),this.refetchQueries({type:"active",...e},t)))}cancelQueries(e,t={}){let r={revert:!0,...t};return Promise.all(i.Vr.batch(()=>this.#l.findAll(e).map(e=>e.cancel(r)))).then(n.ZT).catch(n.ZT)}invalidateQueries(e,t={}){return i.Vr.batch(()=>(this.#l.findAll(e).forEach(e=>{e.invalidate()}),e?.refetchType==="none")?Promise.resolve():this.refetchQueries({...e,type:e?.refetchType??e?.type??"active"},t))}refetchQueries(e,t={}){let r={...t,cancelRefetch:t.cancelRefetch??!0};return Promise.all(i.Vr.batch(()=>this.#l.findAll(e).filter(e=>!e.isDisabled()&&!e.isStatic()).map(e=>{let t=e.fetch(void 0,r);return r.throwOnError||(t=t.catch(n.ZT)),"paused"===e.state.fetchStatus?Promise.resolve():t}))).then(n.ZT)}fetchQuery(e){let t=this.defaultQueryOptions(e);void 0===t.retry&&(t.retry=!1);let r=this.#l.build(this,t);return r.isStaleByTime((0,n.KC)(t.staleTime,r))?r.fetch(t):Promise.resolve(r.state.data)}prefetchQuery(e){return this.fetchQuery(e).then(n.ZT).catch(n.ZT)}fetchInfiniteQuery(e){return e.behavior=f(e.pages),this.fetchQuery(e)}prefetchInfiniteQuery(e){return this.fetchInfiniteQuery(e).then(n.ZT).catch(n.ZT)}ensureInfiniteQueryData(e){return e.behavior=f(e.pages),this.ensureQueryData(e)}resumePausedMutations(){return h.N.isOnline()?this.#r.resumePausedMutations():Promise.resolve()}getQueryCache(){return this.#l}getMutationCache(){return this.#r}getDefaultOptions(){return this.#c}setDefaultOptions(e){this.#c=e}setQueryDefaults(e,t){this.#d.set((0,n.Ym)(e),{queryKey:e,defaultOptions:t})}getQueryDefaults(e){let t=[...this.#d.values()],r={};return t.forEach(t=>{(0,n.to)(e,t.queryKey)&&Object.assign(r,t.defaultOptions)}),r}setMutationDefaults(e,t){this.#h.set((0,n.Ym)(e),{mutationKey:e,defaultOptions:t})}getMutationDefaults(e){let t=[...this.#h.values()],r={};return t.forEach(t=>{(0,n.to)(e,t.mutationKey)&&Object.assign(r,t.defaultOptions)}),r}defaultQueryOptions(e){if(e._defaulted)return e;let t={...this.#c.queries,...this.getQueryDefaults(e.queryKey),...e,_defaulted:!0};return t.queryHash||(t.queryHash=(0,n.Rm)(t.queryKey,t)),void 0===t.refetchOnReconnect&&(t.refetchOnReconnect="always"!==t.networkMode),void 0===t.throwOnError&&(t.throwOnError=!!t.suspense),!t.networkMode&&t.persister&&(t.networkMode="offlineFirst"),t.queryFn===n.CN&&(t.enabled=!1),t}defaultMutationOptions(e){return e?._defaulted?e:{...this.#c.mutations,...e?.mutationKey&&this.getMutationDefaults(e.mutationKey),...e,_defaulted:!0}}clear(){this.#l.clear(),this.#r.clear()}}},92668:function(e,t,r){r.d(t,{I:function(){return s}});var n=r(59121),a=r(31091),i=r(63497),o=r(99649);function s(e,t){let{years:r=0,months:s=0,weeks:u=0,days:l=0,hours:c=0,minutes:d=0,seconds:h=0}=t,f=(0,o.Q)(e),m=s||r?(0,a.z)(f,s+12*r):f,p=l||u?(0,n.E)(m,l+7*u):m;return(0,i.L)(e,p.getTime()+1e3*(h+60*(d+60*c)))}},59121:function(e,t,r){r.d(t,{E:function(){return i}});var n=r(99649),a=r(63497);function i(e,t){let r=(0,n.Q)(e);return isNaN(t)?(0,a.L)(e,NaN):(t&&r.setDate(r.getDate()+t),r)}},31091:function(e,t,r){r.d(t,{z:function(){return i}});var n=r(99649),a=r(63497);function i(e,t){let r=(0,n.Q)(e);if(isNaN(t))return(0,a.L)(e,NaN);if(!t)return r;let i=r.getDate(),o=(0,a.L)(e,r.getTime());return(o.setMonth(r.getMonth()+t+1,0),i>=o.getDate())?o:(r.setFullYear(o.getFullYear(),o.getMonth(),i),r)}},63497:function(e,t,r){r.d(t,{L:function(){return n}});function n(e,t){return e instanceof Date?new e.constructor(t):new Date(t)}},99649:function(e,t,r){r.d(t,{Q:function(){return n}});function n(e){let t=Object.prototype.toString.call(e);return e instanceof Date||"object"==typeof e&&"[object Date]"===t?new e.constructor(+e):new Date("number"==typeof e||"[object Number]"===t||"string"==typeof e||"[object String]"===t?e:NaN)}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/2202-a83ad035a17401aa.js b/litellm/proxy/_experimental/out/_next/static/chunks/2202-a83ad035a17401aa.js deleted file mode 100644 index fe20b03d919..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/2202-a83ad035a17401aa.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2202],{19431:function(e,s,t){t.d(s,{x:function(){return a.Z},z:function(){return l.Z}});var l=t(78489),a=t(84264)},65925:function(e,s,t){t.d(s,{m:function(){return i}});var l=t(57437);t(2265);var a=t(37592);let{Option:r}=a.default,i=e=>e?({"24h":"daily","7d":"weekly","30d":"monthly"})[e]||e:"Not set";s.Z=e=>{let{value:s,onChange:t,className:i="",style:n={}}=e;return(0,l.jsxs)(a.default,{style:{width:"100%",...n},value:s||void 0,onChange:t,className:i,placeholder:"n/a",allowClear:!0,children:[(0,l.jsx)(r,{value:"24h",children:"daily"}),(0,l.jsx)(r,{value:"7d",children:"weekly"}),(0,l.jsx)(r,{value:"30d",children:"monthly"})]})}},84376:function(e,s,t){var l=t(57437);t(2265);var a=t(37592);s.Z=e=>{let{teams:s,value:t,onChange:r,disabled:i}=e;return console.log("disabled",i),(0,l.jsx)(a.default,{showSearch:!0,placeholder:"Search or select a team",value:t,onChange:r,disabled:i,allowClear:!0,filterOption:(e,t)=>{if(!t)return!1;let l=null==s?void 0:s.find(e=>e.team_id===t.key);if(!l)return!1;let a=e.toLowerCase().trim(),r=(l.team_alias||"").toLowerCase(),i=(l.team_id||"").toLowerCase();return r.includes(a)||i.includes(a)},optionFilterProp:"children",children:null==s?void 0:s.map(e=>(0,l.jsxs)(a.default.Option,{value:e.team_id,children:[(0,l.jsx)("span",{className:"font-medium",children:e.team_alias})," ",(0,l.jsxs)("span",{className:"text-gray-500",children:["(",e.team_id,")"]})]},e.team_id))})}},7765:function(e,s,t){t.d(s,{Z:function(){return K}});var l=t(57437),a=t(2265),r=t(37592),i=t(10032),n=t(4260),d=t(5545),o=t(22116),c=t(87452),m=t(88829),u=t(72208),x=t(78489),h=t(43227),f=t(84264),p=t(49566),j=t(96761),g=t(98187),v=t(19250),b=t(19431),y=t(57840),N=t(65319),w=t(56609),_=t(73879),C=t(34310),k=t(38434),S=t(26349),Z=t(35291),U=t(3632),I=t(15452),V=t.n(I),L=t(71157),P=t(44643),R=t(88532),E=t(29233),O=t(9114),T=e=>{let{accessToken:s,teams:t,possibleUIRoles:r,onUsersCreated:i}=e,[n,d]=(0,a.useState)(!1),[c,m]=(0,a.useState)([]),[u,x]=(0,a.useState)(!1),[h,f]=(0,a.useState)(null),[p,j]=(0,a.useState)(null),[g,I]=(0,a.useState)(null),[T,z]=(0,a.useState)(null),[F,B]=(0,a.useState)(null),[D,M]=(0,a.useState)("http://localhost:4000");(0,a.useEffect)(()=>{(async()=>{try{let e=await (0,v.getProxyUISettings)(s);B(e)}catch(e){console.error("Error fetching UI settings:",e)}})(),M(new URL("/",window.location.href).toString())},[s]);let A=async()=>{x(!0);let e=c.map(e=>({...e,status:"pending"}));m(e);let t=!1;for(let i=0;ie.trim()).filter(Boolean),0===e.teams.length&&delete e.teams),n.models&&"string"==typeof n.models&&""!==n.models.trim()&&(e.models=n.models.split(",").map(e=>e.trim()).filter(Boolean),0===e.models.length&&delete e.models),n.max_budget&&""!==n.max_budget.toString().trim()){let s=parseFloat(n.max_budget.toString());!isNaN(s)&&s>0&&(e.max_budget=s)}n.budget_duration&&""!==n.budget_duration.trim()&&(e.budget_duration=n.budget_duration.trim()),n.metadata&&"string"==typeof n.metadata&&""!==n.metadata.trim()&&(e.metadata=n.metadata.trim()),console.log("Sending user data:",e);let a=await (0,v.userCreateCall)(s,null,e);if(console.log("Full response:",a),a&&(a.key||a.user_id)){t=!0,console.log("Success case triggered");let e=(null===(l=a.data)||void 0===l?void 0:l.user_id)||a.user_id;try{if(null==F?void 0:F.SSO_ENABLED){let e=new URL("/ui",D).toString();m(s=>s.map((s,t)=>t===i?{...s,status:"success",key:a.key||a.user_id,invitation_link:e}:s))}else{let t=await (0,v.invitationCreateCall)(s,e),l=new URL("/ui?invitation_id=".concat(t.id),D).toString();m(e=>e.map((e,s)=>s===i?{...e,status:"success",key:a.key||a.user_id,invitation_link:l}:e))}}catch(e){console.error("Error creating invitation:",e),m(e=>e.map((e,s)=>s===i?{...e,status:"success",key:a.key||a.user_id,error:"User created but failed to generate invitation link"}:e))}}else{console.log("Error case triggered");let e=(null==a?void 0:a.error)||"Failed to create user";console.log("Error message:",e),m(s=>s.map((s,t)=>t===i?{...s,status:"failed",error:e}:s))}}catch(s){console.error("Caught error:",s);let e=(null==s?void 0:null===(r=s.response)||void 0===r?void 0:null===(a=r.data)||void 0===a?void 0:a.error)||(null==s?void 0:s.message)||String(s);m(s=>s.map((s,t)=>t===i?{...s,status:"failed",error:e}:s))}}x(!1),t&&i&&i()};return(0,l.jsxs)(l.Fragment,{children:[(0,l.jsx)(b.z,{className:"mb-0",onClick:()=>d(!0),children:"+ Bulk Invite Users"}),(0,l.jsx)(o.Z,{title:"Bulk Invite Users",visible:n,width:800,onCancel:()=>d(!1),bodyStyle:{maxHeight:"70vh",overflow:"auto"},footer:null,children:(0,l.jsx)("div",{className:"flex flex-col",children:0===c.length?(0,l.jsxs)("div",{className:"mb-6",children:[(0,l.jsxs)("div",{className:"flex items-center mb-4",children:[(0,l.jsx)("div",{className:"w-8 h-8 rounded-full bg-blue-500 text-white flex items-center justify-center mr-3",children:"1"}),(0,l.jsx)("h3",{className:"text-lg font-medium",children:"Download and fill the template"})]}),(0,l.jsxs)("div",{className:"ml-11 mb-6",children:[(0,l.jsx)("p",{className:"mb-4",children:"Add multiple users at once by following these steps:"}),(0,l.jsxs)("ol",{className:"list-decimal list-inside space-y-2 ml-2 mb-4",children:[(0,l.jsx)("li",{children:"Download our CSV template"}),(0,l.jsx)("li",{children:"Add your users' information to the spreadsheet"}),(0,l.jsx)("li",{children:"Save the file and upload it here"}),(0,l.jsx)("li",{children:"After creation, download the results file containing the Virtual Keys for each user"})]}),(0,l.jsxs)("div",{className:"bg-gray-50 p-4 rounded-md border border-gray-200 mb-4",children:[(0,l.jsx)("h4",{className:"font-medium mb-2",children:"Template Column Names"}),(0,l.jsxs)("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-3",children:[(0,l.jsxs)("div",{className:"flex items-start",children:[(0,l.jsx)("div",{className:"w-3 h-3 rounded-full bg-red-500 mt-1.5 mr-2 flex-shrink-0"}),(0,l.jsxs)("div",{children:[(0,l.jsx)("p",{className:"font-medium",children:"user_email"}),(0,l.jsx)("p",{className:"text-sm text-gray-600",children:"User's email address (required)"})]})]}),(0,l.jsxs)("div",{className:"flex items-start",children:[(0,l.jsx)("div",{className:"w-3 h-3 rounded-full bg-red-500 mt-1.5 mr-2 flex-shrink-0"}),(0,l.jsxs)("div",{children:[(0,l.jsx)("p",{className:"font-medium",children:"user_role"}),(0,l.jsx)("p",{className:"text-sm text-gray-600",children:'User\'s role (one of: "proxy_admin", "proxy_admin_viewer", "internal_user", "internal_user_viewer")'})]})]}),(0,l.jsxs)("div",{className:"flex items-start",children:[(0,l.jsx)("div",{className:"w-3 h-3 rounded-full bg-gray-300 mt-1.5 mr-2 flex-shrink-0"}),(0,l.jsxs)("div",{children:[(0,l.jsx)("p",{className:"font-medium",children:"teams"}),(0,l.jsx)("p",{className:"text-sm text-gray-600",children:'Comma-separated team IDs (e.g., "team-1,team-2")'})]})]}),(0,l.jsxs)("div",{className:"flex items-start",children:[(0,l.jsx)("div",{className:"w-3 h-3 rounded-full bg-gray-300 mt-1.5 mr-2 flex-shrink-0"}),(0,l.jsxs)("div",{children:[(0,l.jsx)("p",{className:"font-medium",children:"max_budget"}),(0,l.jsx)("p",{className:"text-sm text-gray-600",children:'Maximum budget as a number (e.g., "100")'})]})]}),(0,l.jsxs)("div",{className:"flex items-start",children:[(0,l.jsx)("div",{className:"w-3 h-3 rounded-full bg-gray-300 mt-1.5 mr-2 flex-shrink-0"}),(0,l.jsxs)("div",{children:[(0,l.jsx)("p",{className:"font-medium",children:"budget_duration"}),(0,l.jsx)("p",{className:"text-sm text-gray-600",children:'Budget reset period (e.g., "30d", "1mo")'})]})]}),(0,l.jsxs)("div",{className:"flex items-start",children:[(0,l.jsx)("div",{className:"w-3 h-3 rounded-full bg-gray-300 mt-1.5 mr-2 flex-shrink-0"}),(0,l.jsxs)("div",{children:[(0,l.jsx)("p",{className:"font-medium",children:"models"}),(0,l.jsx)("p",{className:"text-sm text-gray-600",children:'Comma-separated allowed models (e.g., "gpt-3.5-turbo,gpt-4")'})]})]})]})]}),(0,l.jsxs)(b.z,{onClick:()=>{let e=new Blob([V().unparse([["user_email","user_role","teams","max_budget","budget_duration","models"],["user@example.com","internal_user","team-id-1,team-id-2","100","30d","gpt-3.5-turbo,gpt-4"]])],{type:"text/csv"}),s=window.URL.createObjectURL(e),t=document.createElement("a");t.href=s,t.download="bulk_users_template.csv",document.body.appendChild(t),t.click(),document.body.removeChild(t),window.URL.revokeObjectURL(s)},size:"lg",className:"w-full md:w-auto",children:[(0,l.jsx)(_.Z,{className:"mr-2"})," Download CSV Template"]})]}),(0,l.jsxs)("div",{className:"flex items-center mb-4",children:[(0,l.jsx)("div",{className:"w-8 h-8 rounded-full bg-blue-500 text-white flex items-center justify-center mr-3",children:"2"}),(0,l.jsx)("h3",{className:"text-lg font-medium",children:"Upload your completed CSV"})]}),(0,l.jsxs)("div",{className:"ml-11",children:[T?(0,l.jsxs)("div",{className:"mb-4 p-4 rounded-md border ".concat(g?"bg-red-50 border-red-200":"bg-blue-50 border-blue-200"),children:[(0,l.jsxs)("div",{className:"flex items-center justify-between",children:[(0,l.jsxs)("div",{className:"flex items-center",children:[g?(0,l.jsx)(C.Z,{className:"text-red-500 text-xl mr-3"}):(0,l.jsx)(k.Z,{className:"text-blue-500 text-xl mr-3"}),(0,l.jsxs)("div",{children:[(0,l.jsx)(y.default.Text,{strong:!0,className:g?"text-red-800":"text-blue-800",children:T.name}),(0,l.jsxs)(y.default.Text,{className:"block text-xs ".concat(g?"text-red-600":"text-blue-600"),children:[(T.size/1024).toFixed(1)," KB • ",new Date().toLocaleDateString()]})]})]}),(0,l.jsxs)(b.z,{size:"xs",variant:"secondary",onClick:()=>{z(null),m([]),f(null),j(null),I(null)},className:"flex items-center",children:[(0,l.jsx)(S.Z,{className:"mr-1"})," Remove"]})]}),g?(0,l.jsxs)("div",{className:"mt-3 text-red-600 text-sm flex items-start",children:[(0,l.jsx)(Z.Z,{className:"mr-2 mt-0.5"}),(0,l.jsx)("span",{children:g})]}):!p&&(0,l.jsxs)("div",{className:"mt-3 flex items-center",children:[(0,l.jsx)("div",{className:"w-full bg-gray-200 rounded-full h-1.5",children:(0,l.jsx)("div",{className:"bg-blue-500 h-1.5 rounded-full w-full animate-pulse"})}),(0,l.jsx)("span",{className:"ml-2 text-xs text-blue-600",children:"Processing..."})]})]}):(0,l.jsx)(N.default,{beforeUpload:e=>((f(null),j(null),I(null),z(e),"text/csv"===e.type||e.name.endsWith(".csv"))?e.size>5242880?I("File is too large (".concat((e.size/1048576).toFixed(1)," MB). Please upload a CSV file smaller than 5MB.")):V().parse(e,{complete:e=>{if(!e.data||0===e.data.length){j("The CSV file appears to be empty. Please upload a file with data."),m([]);return}if(1===e.data.length){j("The CSV file only contains headers but no user data. Please add user data to your CSV."),m([]);return}let s=e.data[0];if(0===s.length||1===s.length&&""===s[0]){j("The CSV file doesn't contain any column headers. Please make sure your CSV has headers."),m([]);return}let l=["user_email","user_role"].filter(e=>!s.includes(e));if(l.length>0){j("Your CSV is missing these required columns: ".concat(l.join(", "),". Please add these columns to your CSV file.")),m([]);return}try{let l=e.data.slice(1).map((e,l)=>{var a,r,i,n,d,o;if(0===e.length||1===e.length&&""===e[0])return null;if(e.length=parseFloat(c.max_budget.toString())&&m.push("Max budget must be greater than 0")),c.budget_duration&&!c.budget_duration.match(/^\d+[dhmwy]$|^\d+mo$/)&&m.push('Invalid budget duration format "'.concat(c.budget_duration,'". Use format like "30d", "1mo", "2w", "6h"')),c.teams&&"string"==typeof c.teams&&t&&t.length>0){let e=t.map(e=>e.team_id),s=c.teams.split(",").map(e=>e.trim()).filter(s=>!e.includes(s));s.length>0&&m.push("Unknown team(s): ".concat(s.join(", ")))}return m.length>0&&(c.isValid=!1,c.error=m.join(", ")),c}).filter(Boolean),a=l.filter(e=>e.isValid);m(l),0===l.length?j("No valid data rows found in the CSV file. Please check your file format."):0===a.length?f("No valid users found in the CSV. Please check the errors below and fix your CSV file."):a.length{f("Failed to parse CSV file: ".concat(e.message)),m([])},header:!1}):(I("Invalid file type: ".concat(e.name,". Please upload a CSV file (.csv extension).")),O.Z.fromBackend("Invalid file type. Please upload a CSV file.")),!1),accept:".csv",maxCount:1,showUploadList:!1,children:(0,l.jsxs)("div",{className:"border-2 border-dashed border-gray-300 rounded-lg p-8 text-center hover:border-blue-500 transition-colors cursor-pointer",children:[(0,l.jsx)(U.Z,{className:"text-3xl text-gray-400 mb-2"}),(0,l.jsx)("p",{className:"mb-1",children:"Drag and drop your CSV file here"}),(0,l.jsx)("p",{className:"text-sm text-gray-500 mb-3",children:"or"}),(0,l.jsx)(b.z,{size:"sm",children:"Browse files"}),(0,l.jsx)("p",{className:"text-xs text-gray-500 mt-4",children:"Only CSV files (.csv) are supported"})]})}),p&&(0,l.jsx)("div",{className:"mb-4 p-4 bg-yellow-50 border border-yellow-200 rounded-md",children:(0,l.jsxs)("div",{className:"flex items-start",children:[(0,l.jsx)(R.Z,{className:"h-5 w-5 text-yellow-500 mr-2 mt-0.5"}),(0,l.jsxs)("div",{children:[(0,l.jsx)(y.default.Text,{strong:!0,className:"text-yellow-800",children:"CSV Structure Error"}),(0,l.jsx)(y.default.Paragraph,{className:"text-yellow-700 mt-1 mb-0",children:p}),(0,l.jsx)(y.default.Paragraph,{className:"text-yellow-700 mt-2 mb-0",children:"Please download our template and ensure your CSV follows the required format."})]})]})})]})]}):(0,l.jsxs)("div",{className:"mb-6",children:[(0,l.jsxs)("div",{className:"flex items-center mb-4",children:[(0,l.jsx)("div",{className:"w-8 h-8 rounded-full bg-blue-500 text-white flex items-center justify-center mr-3",children:"3"}),(0,l.jsx)("h3",{className:"text-lg font-medium",children:c.some(e=>"success"===e.status||"failed"===e.status)?"User Creation Results":"Review and create users"})]}),h&&(0,l.jsx)("div",{className:"ml-11 mb-4 p-4 bg-red-50 border border-red-200 rounded-md",children:(0,l.jsxs)("div",{className:"flex items-start",children:[(0,l.jsx)(Z.Z,{className:"text-red-500 mr-2 mt-1"}),(0,l.jsxs)("div",{children:[(0,l.jsx)(b.x,{className:"text-red-600 font-medium",children:h}),c.some(e=>!e.isValid)&&(0,l.jsxs)("ul",{className:"mt-2 list-disc list-inside text-red-600 text-sm",children:[(0,l.jsx)("li",{children:"Check the table below for specific errors in each row"}),(0,l.jsx)("li",{children:"Common issues include invalid email formats, missing required fields, or incorrect role values"}),(0,l.jsx)("li",{children:"Fix these issues in your CSV file and upload again"})]})]})]})}),(0,l.jsxs)("div",{className:"ml-11",children:[(0,l.jsxs)("div",{className:"flex justify-between items-center mb-3",children:[(0,l.jsx)("div",{className:"flex items-center",children:c.some(e=>"success"===e.status||"failed"===e.status)?(0,l.jsxs)("div",{className:"flex items-center",children:[(0,l.jsx)(b.x,{className:"text-lg font-medium mr-3",children:"Creation Summary"}),(0,l.jsxs)(b.x,{className:"text-sm bg-green-100 text-green-800 px-2 py-1 rounded mr-2",children:[c.filter(e=>"success"===e.status).length," Successful"]}),c.some(e=>"failed"===e.status)&&(0,l.jsxs)(b.x,{className:"text-sm bg-red-100 text-red-800 px-2 py-1 rounded",children:[c.filter(e=>"failed"===e.status).length," Failed"]})]}):(0,l.jsxs)("div",{className:"flex items-center",children:[(0,l.jsx)(b.x,{className:"text-lg font-medium mr-3",children:"User Preview"}),(0,l.jsxs)(b.x,{className:"text-sm bg-blue-100 text-blue-800 px-2 py-1 rounded",children:[c.filter(e=>e.isValid).length," of ",c.length," users valid"]})]})}),!c.some(e=>"success"===e.status||"failed"===e.status)&&(0,l.jsxs)("div",{className:"flex space-x-3",children:[(0,l.jsx)(b.z,{onClick:()=>{m([]),f(null)},variant:"secondary",children:"Back"}),(0,l.jsx)(b.z,{onClick:A,disabled:0===c.filter(e=>e.isValid).length||u,children:u?"Creating...":"Create ".concat(c.filter(e=>e.isValid).length," Users")})]})]}),c.some(e=>"success"===e.status)&&(0,l.jsx)("div",{className:"mb-4 p-4 bg-blue-50 border border-blue-200 rounded-md",children:(0,l.jsxs)("div",{className:"flex items-start",children:[(0,l.jsx)("div",{className:"mr-3 mt-1",children:(0,l.jsx)(P.Z,{className:"h-5 w-5 text-blue-500"})}),(0,l.jsxs)("div",{children:[(0,l.jsx)(b.x,{className:"font-medium text-blue-800",children:"User creation complete"}),(0,l.jsxs)(b.x,{className:"block text-sm text-blue-700 mt-1",children:[(0,l.jsx)("span",{className:"font-medium",children:"Next step:"})," Download the credentials file containing Virtual Keys and invitation links. Users will need these Virtual Keys to make LLM requests through LiteLLM."]})]})]})}),(0,l.jsx)(w.Z,{dataSource:c,columns:[{title:"Row",dataIndex:"rowNumber",key:"rowNumber",width:80},{title:"Email",dataIndex:"user_email",key:"user_email"},{title:"Role",dataIndex:"user_role",key:"user_role"},{title:"Teams",dataIndex:"teams",key:"teams"},{title:"Budget",dataIndex:"max_budget",key:"max_budget"},{title:"Status",key:"status",render:(e,s)=>s.isValid?s.status&&"pending"!==s.status?"success"===s.status?(0,l.jsxs)("div",{children:[(0,l.jsxs)("div",{className:"flex items-center",children:[(0,l.jsx)(P.Z,{className:"h-5 w-5 text-green-500 mr-2"}),(0,l.jsx)("span",{className:"text-green-500",children:"Success"})]}),s.invitation_link&&(0,l.jsx)("div",{className:"mt-1",children:(0,l.jsxs)("div",{className:"flex items-center",children:[(0,l.jsx)("span",{className:"text-xs text-gray-500 truncate max-w-[150px]",children:s.invitation_link}),(0,l.jsx)(E.CopyToClipboard,{text:s.invitation_link,onCopy:()=>O.Z.success("Invitation link copied!"),children:(0,l.jsx)("button",{className:"ml-1 text-blue-500 text-xs hover:text-blue-700",children:"Copy"})})]})})]}):(0,l.jsxs)("div",{children:[(0,l.jsxs)("div",{className:"flex items-center",children:[(0,l.jsx)(L.Z,{className:"h-5 w-5 text-red-500 mr-2"}),(0,l.jsx)("span",{className:"text-red-500",children:"Failed"})]}),s.error&&(0,l.jsx)("span",{className:"text-sm text-red-500 ml-7",children:JSON.stringify(s.error)})]}):(0,l.jsx)("span",{className:"text-gray-500",children:"Pending"}):(0,l.jsxs)("div",{children:[(0,l.jsxs)("div",{className:"flex items-center",children:[(0,l.jsx)(L.Z,{className:"h-5 w-5 text-red-500 mr-2"}),(0,l.jsx)("span",{className:"text-red-500",children:"Invalid"})]}),s.error&&(0,l.jsx)("span",{className:"text-sm text-red-500 ml-7",children:s.error})]})}],size:"small",pagination:{pageSize:5},scroll:{y:300},rowClassName:e=>e.isValid?"":"bg-red-50"}),!c.some(e=>"success"===e.status||"failed"===e.status)&&(0,l.jsxs)("div",{className:"flex justify-end mt-4",children:[(0,l.jsx)(b.z,{onClick:()=>{m([]),f(null)},variant:"secondary",className:"mr-3",children:"Back"}),(0,l.jsx)(b.z,{onClick:A,disabled:0===c.filter(e=>e.isValid).length||u,children:u?"Creating...":"Create ".concat(c.filter(e=>e.isValid).length," Users")})]}),c.some(e=>"success"===e.status||"failed"===e.status)&&(0,l.jsxs)("div",{className:"flex justify-end mt-4",children:[(0,l.jsx)(b.z,{onClick:()=>{m([]),f(null)},variant:"secondary",className:"mr-3",children:"Start New Bulk Import"}),(0,l.jsxs)(b.z,{onClick:()=>{let e=c.map(e=>({user_email:e.user_email,user_role:e.user_role,status:e.status,key:e.key||"",invitation_link:e.invitation_link||"",error:e.error||""})),s=new Blob([V().unparse(e)],{type:"text/csv"}),t=window.URL.createObjectURL(s),l=document.createElement("a");l.href=t,l.download="bulk_users_results.csv",document.body.appendChild(l),l.click(),document.body.removeChild(l),window.URL.revokeObjectURL(t)},variant:"primary",className:"flex items-center",children:[(0,l.jsx)(_.Z,{className:"mr-2"})," Download User Credentials"]})]})]})]})})})]})},z=t(99981),F=t(15424),B=t(46468),D=t(29827),M=t(84376);let{Option:A}=r.default,q=()=>"undefined"!=typeof crypto&&crypto.randomUUID?crypto.randomUUID():"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){let s=16*Math.random()|0;return("x"==e?s:3&s|8).toString(16)});var K=e=>{let{userID:s,accessToken:t,teams:b,possibleUIRoles:y,onUserCreated:N,isEmbedded:w=!1}=e,_=(0,D.NL)(),[C,k]=(0,a.useState)(null),[S]=i.Z.useForm(),[Z,U]=(0,a.useState)(!1),[I,V]=(0,a.useState)(!1),[L,P]=(0,a.useState)([]),[R,E]=(0,a.useState)(!1),[A,K]=(0,a.useState)(null),[J,W]=(0,a.useState)(null);(0,a.useEffect)(()=>{let e=async()=>{try{let e=await (0,v.modelAvailableCall)(t,s,"any"),l=[];for(let s=0;s{var l,a,r;try{O.Z.info("Making API Call"),w||U(!0),e.models&&0!==e.models.length||"proxy_admin"===e.user_role||(console.log("formValues.user_role",e.user_role),e.models=["no-default-models"]),console.log("formValues in create user:",e);let a=await (0,v.userCreateCall)(t,null,e);await _.invalidateQueries({queryKey:["userList"]}),console.log("user create Response:",a),V(!0);let r=(null===(l=a.data)||void 0===l?void 0:l.user_id)||a.user_id;if(N&&w){N(r),S.resetFields();return}if(null==C?void 0:C.SSO_ENABLED){let e={id:q(),user_id:r,is_accepted:!1,accepted_at:null,expires_at:new Date(Date.now()+6048e5),created_at:new Date,created_by:s,updated_at:new Date,updated_by:s,has_user_setup_sso:!0};K(e),E(!0)}else(0,v.invitationCreateCall)(t,r).then(e=>{e.has_user_setup_sso=!1,K(e),E(!0)});O.Z.success("API user Created"),S.resetFields(),localStorage.removeItem("userData"+s)}catch(s){let e=(null===(r=s.response)||void 0===r?void 0:null===(a=r.data)||void 0===a?void 0:a.detail)||(null==s?void 0:s.message)||"Error creating the user";O.Z.fromBackend(e),console.error("Error creating the user:",s)}};return w?(0,l.jsxs)(i.Z,{form:S,onFinish:$,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",children:[(0,l.jsx)(i.Z.Item,{label:"User Email",name:"user_email",children:(0,l.jsx)(p.Z,{placeholder:""})}),(0,l.jsx)(i.Z.Item,{label:"User Role",name:"user_role",children:(0,l.jsx)(r.default,{children:y&&Object.entries(y).map(e=>{let[s,{ui_label:t,description:a}]=e;return(0,l.jsx)(h.Z,{value:s,title:t,children:(0,l.jsxs)("div",{className:"flex",children:[t," ",(0,l.jsx)("p",{className:"ml-2",style:{color:"gray",fontSize:"12px"},children:a})]})},s)})})}),(0,l.jsx)(i.Z.Item,{label:"Team",name:"team_id",children:(0,l.jsx)(r.default,{placeholder:"Select Team",style:{width:"100%"},children:(0,l.jsx)(M.Z,{teams:b})})}),(0,l.jsx)(i.Z.Item,{label:"Metadata",name:"metadata",children:(0,l.jsx)(n.default.TextArea,{rows:4,placeholder:"Enter metadata as JSON"})}),(0,l.jsx)("div",{style:{textAlign:"right",marginTop:"10px"},children:(0,l.jsx)(d.ZP,{htmlType:"submit",children:"Create User"})})]}):(0,l.jsxs)("div",{className:"flex gap-2",children:[(0,l.jsx)(x.Z,{className:"mb-0",onClick:()=>U(!0),children:"+ Invite User"}),(0,l.jsx)(T,{accessToken:t,teams:b,possibleUIRoles:y}),(0,l.jsxs)(o.Z,{title:"Invite User",visible:Z,width:800,footer:null,onOk:()=>{U(!1),S.resetFields()},onCancel:()=>{U(!1),V(!1),S.resetFields()},children:[(0,l.jsx)(f.Z,{className:"mb-1",children:"Create a User who can own keys"}),(0,l.jsxs)(i.Z,{form:S,onFinish:$,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",children:[(0,l.jsx)(i.Z.Item,{label:"User Email",name:"user_email",children:(0,l.jsx)(p.Z,{placeholder:""})}),(0,l.jsx)(i.Z.Item,{label:(0,l.jsxs)("span",{children:["Global Proxy Role"," ",(0,l.jsx)(z.Z,{title:"This is the role that the user will globally on the proxy. This role is independent of any team/org specific roles.",children:(0,l.jsx)(F.Z,{})})]}),name:"user_role",children:(0,l.jsx)(r.default,{children:y&&Object.entries(y).map(e=>{let[s,{ui_label:t,description:a}]=e;return(0,l.jsx)(h.Z,{value:s,title:t,children:(0,l.jsxs)("div",{className:"flex",children:[t," ",(0,l.jsx)("p",{className:"ml-2",style:{color:"gray",fontSize:"12px"},children:a})]})},s)})})}),(0,l.jsx)(i.Z.Item,{label:"Team",className:"gap-2",name:"team_id",help:"If selected, user will be added as a 'user' role to the team.",children:(0,l.jsx)(M.Z,{teams:b})}),(0,l.jsx)(i.Z.Item,{label:"Metadata",name:"metadata",children:(0,l.jsx)(n.default.TextArea,{rows:4,placeholder:"Enter metadata as JSON"})}),(0,l.jsxs)(c.Z,{children:[(0,l.jsx)(u.Z,{children:(0,l.jsx)(j.Z,{children:"Personal Key Creation"})}),(0,l.jsx)(m.Z,{children:(0,l.jsx)(i.Z.Item,{className:"gap-2",label:(0,l.jsxs)("span",{children:["Models"," ",(0,l.jsx)(z.Z,{title:"Models user has access to, outside of team scope.",children:(0,l.jsx)(F.Z,{style:{marginLeft:"4px"}})})]}),name:"models",help:"Models user has access to, outside of team scope.",children:(0,l.jsxs)(r.default,{mode:"multiple",placeholder:"Select models",style:{width:"100%"},children:[(0,l.jsx)(r.default.Option,{value:"all-proxy-models",children:"All Proxy Models"},"all-proxy-models"),(0,l.jsx)(r.default.Option,{value:"no-default-models",children:"No Default Models"},"no-default-models"),L.map(e=>(0,l.jsx)(r.default.Option,{value:e,children:(0,B.W0)(e)},e))]})})})]}),(0,l.jsx)("div",{style:{textAlign:"right",marginTop:"10px"},children:(0,l.jsx)(d.ZP,{htmlType:"submit",children:"Create User"})})]})]}),I&&(0,l.jsx)(g.Z,{isInvitationLinkModalVisible:R,setIsInvitationLinkModalVisible:E,baseUrl:J||"",invitationLinkData:A})]})}},98187:function(e,s,t){t.d(s,{Z:function(){return o}});var l=t(57437);t(2265);var a=t(57840),r=t(22116),i=t(29233),n=t(19431),d=t(9114);function o(e){let{isInvitationLinkModalVisible:s,setIsInvitationLinkModalVisible:t,baseUrl:o,invitationLinkData:c,modalType:m="invitation"}=e,{Title:u,Paragraph:x}=a.default,h=()=>{if(!o)return"";let e=new URL(o).pathname,s=e&&"/"!==e?"".concat(e,"/ui"):"ui";if(null==c?void 0:c.has_user_setup_sso)return new URL(s,o).toString();let t="".concat(s,"?invitation_id=").concat(null==c?void 0:c.id);return"resetPassword"===m&&(t+="&action=reset_password"),new URL(t,o).toString()};return(0,l.jsxs)(r.Z,{title:"invitation"===m?"Invitation Link":"Reset Password Link",visible:s,width:800,footer:null,onOk:()=>{t(!1)},onCancel:()=>{t(!1)},children:[(0,l.jsx)(x,{children:"invitation"===m?"Copy and send the generated link to onboard this user to the proxy.":"Copy and send the generated link to the user to reset their password."}),(0,l.jsxs)("div",{className:"flex justify-between pt-5 pb-2",children:[(0,l.jsx)(n.x,{className:"text-base",children:"User ID"}),(0,l.jsx)(n.x,{children:null==c?void 0:c.user_id})]}),(0,l.jsxs)("div",{className:"flex justify-between pt-5 pb-2",children:[(0,l.jsx)(n.x,{children:"invitation"===m?"Invitation Link":"Reset Password Link"}),(0,l.jsx)(n.x,{children:(0,l.jsx)(n.x,{children:h()})})]}),(0,l.jsx)("div",{className:"flex justify-end mt-5",children:(0,l.jsx)(i.CopyToClipboard,{text:h(),onCopy:()=>d.Z.success("Copied!"),children:(0,l.jsx)(n.z,{variant:"primary",children:"invitation"===m?"Copy invitation link":"Copy password reset link"})})})]})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/2227-5ae3f36b0a81c5b4.js b/litellm/proxy/_experimental/out/_next/static/chunks/2227-5ae3f36b0a81c5b4.js deleted file mode 100644 index 58d3db1949b..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/2227-5ae3f36b0a81c5b4.js +++ /dev/null @@ -1 +0,0 @@ -(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2227],{75105:function(e,t,n){"use strict";n.d(t,{Z:function(){return eo}});var r=n(5853),o=n(2265),i=n(47625),a=n(93765),l=n(61994),c=n(84735),s=n(86757),u=n.n(s),d=n(95645),p=n.n(d),f=n(77571),m=n.n(f),h=n(82559),y=n.n(h),v=n(21652),b=n.n(v),g=n(57165),k=n(81889),x=n(9841),A=n(58772),O=n(34067),w=n(16630),j=n(85355),E=n(82944),P=["layout","type","stroke","connectNulls","isRange","ref"],S=["key"];function L(e){return(L="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function N(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n={};for(var r in e)if(Object.prototype.hasOwnProperty.call(e,r)){if(t.indexOf(r)>=0)continue;n[r]=e[r]}return n}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function C(){return(C=Object.assign?Object.assign.bind():function(e){for(var t=1;t0||!b()(l,r)||!b()(c,o))?this.renderAreaWithAnimation(e,t):this.renderAreaStatically(r,o,e,t)}},{key:"render",value:function(){var e,t=this.props,n=t.hide,r=t.dot,i=t.points,a=t.className,c=t.top,s=t.left,u=t.xAxis,d=t.yAxis,p=t.width,f=t.height,h=t.isAnimationActive,y=t.id;if(n||!i||!i.length)return null;var v=this.state.isAnimationFinished,b=1===i.length,g=(0,l.Z)("recharts-area",a),k=u&&u.allowDataOverflow,O=d&&d.allowDataOverflow,w=k||O,j=m()(y)?this.id:y,P=null!==(e=(0,E.L6)(r,!1))&&void 0!==e?e:{r:3,strokeWidth:2},S=P.r,L=P.strokeWidth,N=((0,E.jf)(r)?r:{}).clipDot,C=void 0===N||N,T=2*(void 0===S?3:S)+(void 0===L?2:L);return o.createElement(x.m,{className:g},k||O?o.createElement("defs",null,o.createElement("clipPath",{id:"clipPath-".concat(j)},o.createElement("rect",{x:k?s:s-p/2,y:O?c:c-f/2,width:k?p:2*p,height:O?f:2*f})),!C&&o.createElement("clipPath",{id:"clipPath-dots-".concat(j)},o.createElement("rect",{x:s-T/2,y:c-T/2,width:p+T,height:f+T}))):null,b?null:this.renderArea(w,j),(r||b)&&this.renderDots(w,C,j),(!h||v)&&A.e.renderCallByParent(this.props,i))}}],n=[{key:"getDerivedStateFromProps",value:function(e,t){return e.animationId!==t.prevAnimationId?{prevAnimationId:e.animationId,curPoints:e.points,curBaseLine:e.baseLine,prevPoints:t.curPoints,prevBaseLine:t.curBaseLine}:e.points!==t.curPoints||e.baseLine!==t.curBaseLine?{curPoints:e.points,curBaseLine:e.baseLine}:null}}],t&&D(r.prototype,t),n&&D(r,n),Object.defineProperty(r,"prototype",{writable:!1}),r}(o.PureComponent);B(K,"displayName","Area"),B(K,"defaultProps",{stroke:"#3182bd",fill:"#3182bd",fillOpacity:.6,xAxisId:0,yAxisId:0,legendType:"line",connectNulls:!1,points:[],dot:!1,activeDot:!0,hide:!1,isAnimationActive:!O.x.isSsr,animationBegin:0,animationDuration:1500,animationEasing:"ease"}),B(K,"getBaseValue",function(e,t,n,r){var o=e.layout,i=e.baseValue,a=t.props.baseValue,l=null!=a?a:i;if((0,w.hj)(l)&&"number"==typeof l)return l;var c="horizontal"===o?r:n,s=c.scale.domain();if("number"===c.type){var u=Math.max(s[0],s[1]),d=Math.min(s[0],s[1]);return"dataMin"===l?d:"dataMax"===l?u:u<0?u:Math.max(Math.min(s[0],s[1]),0)}return"dataMin"===l?s[0]:"dataMax"===l?s[1]:s[0]}),B(K,"getComposedData",function(e){var t,n=e.props,r=e.item,o=e.xAxis,i=e.yAxis,a=e.xAxisTicks,l=e.yAxisTicks,c=e.bandSize,s=e.dataKey,u=e.stackedData,d=e.dataStartIndex,p=e.displayedData,f=e.offset,m=n.layout,h=u&&u.length,y=K.getBaseValue(n,r,o,i),v="horizontal"===m,b=!1,g=p.map(function(e,t){h?n=u[d+t]:Array.isArray(n=(0,j.F$)(e,s))?b=!0:n=[y,n];var n,r=null==n[1]||h&&null==(0,j.F$)(e,s);return v?{x:(0,j.Hv)({axis:o,ticks:a,bandSize:c,entry:e,index:t}),y:r?null:i.scale(n[1]),value:n,payload:e}:{x:r?null:o.scale(n[1]),y:(0,j.Hv)({axis:i,ticks:l,bandSize:c,entry:e,index:t}),value:n,payload:e}});return t=h||b?g.map(function(e){var t=Array.isArray(e.value)?e.value[0]:null;return v?{x:e.x,y:null!=t&&null!=e.y?i.scale(t):null}:{x:null!=t?o.scale(t):null,y:e.y}}):v?i.scale(y):o.scale(y),R({points:g,baseLine:t,layout:m,isRange:b},f)}),B(K,"renderDotItem",function(e,t){var n;if(o.isValidElement(e))n=o.cloneElement(e,t);else if(u()(e))n=e(t);else{var r=(0,l.Z)("recharts-area-dot","boolean"!=typeof e?e.className:""),i=t.key,a=N(t,S);n=o.createElement(k.o,C({},a,{key:i,className:r}))}return n});var _=n(97059),H=n(62994),W=n(25311),z=(0,a.z)({chartName:"AreaChart",GraphicalChild:K,axisComponents:[{axisType:"xAxis",AxisComp:_.K},{axisType:"yAxis",AxisComp:H.B}],formatAxisMap:W.t9}),V=n(56940),G=n(26680),q=n(8147),$=n(22190),X=n(54061),U=n(65278),Y=n(98593),Q=n(92666),J=n(32644),ee=n(7084),et=n(26898),en=n(13241),er=n(1153);let eo=o.forwardRef((e,t)=>{let{data:n=[],categories:a=[],index:l,stack:c=!1,colors:s=et.s,valueFormatter:u=er.Cj,startEndOnly:d=!1,showXAxis:p=!0,showYAxis:f=!0,yAxisWidth:m=56,intervalType:h="equidistantPreserveStart",showAnimation:y=!1,animationDuration:v=900,showTooltip:b=!0,showLegend:g=!0,showGridLines:x=!0,showGradient:A=!0,autoMinValue:O=!1,curveType:w="linear",minValue:j,maxValue:E,connectNulls:P=!1,allowDecimals:S=!0,noDataText:L,className:N,onValueChange:C,enableLegendSlider:T=!1,customTooltip:R,rotateLabelX:D,padding:M=(p||f)&&(!d||f)?{left:20,right:20}:{left:0,right:0},tickGap:I=5,xAxisLabel:Z,yAxisLabel:B}=e,F=(0,r._T)(e,["data","categories","index","stack","colors","valueFormatter","startEndOnly","showXAxis","showYAxis","yAxisWidth","intervalType","showAnimation","animationDuration","showTooltip","showLegend","showGridLines","showGradient","autoMinValue","curveType","minValue","maxValue","connectNulls","allowDecimals","noDataText","className","onValueChange","enableLegendSlider","customTooltip","rotateLabelX","padding","tickGap","xAxisLabel","yAxisLabel"]),[W,eo]=(0,o.useState)(60),[ei,ea]=(0,o.useState)(void 0),[el,ec]=(0,o.useState)(void 0),es=(0,J.me)(a,s),eu=(0,J.i4)(O,j,E),ed=!!C;function ep(e){ed&&(e===el&&!ei||(0,J.FB)(n,e)&&ei&&ei.dataKey===e?(ec(void 0),null==C||C(null)):(ec(e),null==C||C({eventType:"category",categoryClicked:e})),ea(void 0))}return o.createElement("div",Object.assign({ref:t,className:(0,en.q)("w-full h-80",N)},F),o.createElement(i.h,{className:"h-full w-full"},(null==n?void 0:n.length)?o.createElement(z,{data:n,onClick:ed&&(el||ei)?()=>{ea(void 0),ec(void 0),null==C||C(null)}:void 0,margin:{bottom:Z?30:void 0,left:B?20:void 0,right:B?5:void 0,top:5}},x?o.createElement(V.q,{className:(0,en.q)("stroke-1","stroke-tremor-border","dark:stroke-dark-tremor-border"),horizontal:!0,vertical:!1}):null,o.createElement(_.K,{padding:M,hide:!p,dataKey:l,tick:{transform:"translate(0, 6)"},ticks:d?[n[0][l],n[n.length-1][l]]:void 0,fill:"",stroke:"",className:(0,en.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),interval:d?"preserveStartEnd":h,tickLine:!1,axisLine:!1,minTickGap:I,angle:null==D?void 0:D.angle,dy:null==D?void 0:D.verticalShift,height:null==D?void 0:D.xAxisHeight},Z&&o.createElement(G._,{position:"insideBottom",offset:-20,className:"fill-tremor-content-emphasis text-tremor-default font-medium dark:fill-dark-tremor-content-emphasis"},Z)),o.createElement(H.B,{width:m,hide:!f,axisLine:!1,tickLine:!1,type:"number",domain:eu,tick:{transform:"translate(-3, 0)"},fill:"",stroke:"",className:(0,en.q)("text-tremor-label","fill-tremor-content","dark:fill-dark-tremor-content"),tickFormatter:u,allowDecimals:S},B&&o.createElement(G._,{position:"insideLeft",style:{textAnchor:"middle"},angle:-90,offset:-15,className:"fill-tremor-content-emphasis text-tremor-default font-medium dark:fill-dark-tremor-content-emphasis"},B)),o.createElement(q.u,{wrapperStyle:{outline:"none"},isAnimationActive:!1,cursor:{stroke:"#d1d5db",strokeWidth:1},content:b?e=>{let{active:t,payload:n,label:r}=e;return R?o.createElement(R,{payload:null==n?void 0:n.map(e=>{var t;return Object.assign(Object.assign({},e),{color:null!==(t=es.get(e.dataKey))&&void 0!==t?t:ee.fr.Gray})}),active:t,label:r}):o.createElement(Y.ZP,{active:t,payload:n,label:r,valueFormatter:u,categoryColors:es})}:o.createElement(o.Fragment,null),position:{y:0}}),g?o.createElement($.D,{verticalAlign:"top",height:W,content:e=>{let{payload:t}=e;return(0,U.Z)({payload:t},es,eo,el,ed?e=>ep(e):void 0,T)}}):null,a.map(e=>{var t,n,r;let i=(null!==(t=es.get(e))&&void 0!==t?t:ee.fr.Gray).replace("#","");return o.createElement("defs",{key:e},A?o.createElement("linearGradient",{className:(0,er.bM)(null!==(n=es.get(e))&&void 0!==n?n:ee.fr.Gray,et.K.text).textColor,id:i,x1:"0",y1:"0",x2:"0",y2:"1"},o.createElement("stop",{offset:"5%",stopColor:"currentColor",stopOpacity:ei||el&&el!==e?.15:.4}),o.createElement("stop",{offset:"95%",stopColor:"currentColor",stopOpacity:0})):o.createElement("linearGradient",{className:(0,er.bM)(null!==(r=es.get(e))&&void 0!==r?r:ee.fr.Gray,et.K.text).textColor,id:i,x1:"0",y1:"0",x2:"0",y2:"1"},o.createElement("stop",{stopColor:"currentColor",stopOpacity:ei||el&&el!==e?.1:.3})))}),a.map(e=>{var t,r;let i=(null!==(t=es.get(e))&&void 0!==t?t:ee.fr.Gray).replace("#","");return o.createElement(K,{className:(0,er.bM)(null!==(r=es.get(e))&&void 0!==r?r:ee.fr.Gray,et.K.text).strokeColor,strokeOpacity:ei||el&&el!==e?.3:1,activeDot:e=>{var t;let{cx:r,cy:i,stroke:a,strokeLinecap:l,strokeLinejoin:c,strokeWidth:s,dataKey:u}=e;return o.createElement(k.o,{className:(0,en.q)("stroke-tremor-background dark:stroke-dark-tremor-background",C?"cursor-pointer":"",(0,er.bM)(null!==(t=es.get(u))&&void 0!==t?t:ee.fr.Gray,et.K.text).fillColor),cx:r,cy:i,r:5,fill:"",stroke:a,strokeLinecap:l,strokeLinejoin:c,strokeWidth:s,onClick:(t,r)=>{r.stopPropagation(),ed&&(e.index===(null==ei?void 0:ei.index)&&e.dataKey===(null==ei?void 0:ei.dataKey)||(0,J.FB)(n,e.dataKey)&&el&&el===e.dataKey?(ec(void 0),ea(void 0),null==C||C(null)):(ec(e.dataKey),ea({index:e.index,dataKey:e.dataKey}),null==C||C(Object.assign({eventType:"dot",categoryClicked:e.dataKey},e.payload))))}})},dot:t=>{var r;let{stroke:i,strokeLinecap:a,strokeLinejoin:l,strokeWidth:c,cx:s,cy:u,dataKey:d,index:p}=t;return(0,J.FB)(n,e)&&!(ei||el&&el!==e)||(null==ei?void 0:ei.index)===p&&(null==ei?void 0:ei.dataKey)===e?o.createElement(k.o,{key:p,cx:s,cy:u,r:5,stroke:i,fill:"",strokeLinecap:a,strokeLinejoin:l,strokeWidth:c,className:(0,en.q)("stroke-tremor-background dark:stroke-dark-tremor-background",C?"cursor-pointer":"",(0,er.bM)(null!==(r=es.get(d))&&void 0!==r?r:ee.fr.Gray,et.K.text).fillColor)}):o.createElement(o.Fragment,{key:p})},key:e,name:e,type:w,dataKey:e,stroke:"",fill:"url(#".concat(i,")"),strokeWidth:2,strokeLinejoin:"round",strokeLinecap:"round",isAnimationActive:y,animationDuration:v,stackId:c?"a":void 0,connectNulls:P})}),C?a.map(e=>o.createElement(X.x,{className:(0,en.q)("cursor-pointer"),strokeOpacity:0,key:e,name:e,type:w,dataKey:e,stroke:"transparent",fill:"transparent",legendType:"none",tooltipType:"none",strokeWidth:12,connectNulls:P,onClick:(e,t)=>{t.stopPropagation();let{name:n}=e;ep(n)}})):null):o.createElement(Q.Z,{noDataText:L})))});eo.displayName="AreaChart"},14042:function(e,t,n){"use strict";n.d(t,{Z:function(){return e_}});var r=n(5853),o=n(7084),i=n(26898),a=n(13241),l=n(1153),c=n(2265),s=n(60474),u=n(47625),d=n(93765),p=n(86757),f=n.n(p),m=n(61994),h=n(9841),y=n(81889),v=n(82944),b=["points","className","baseLinePoints","connectNulls"];function g(){return(g=Object.assign?Object.assign.bind():function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);n0&&void 0!==arguments[0]?arguments[0]:[],t=[[]];return e.forEach(function(e){A(e)?t[t.length-1].push(e):t[t.length-1].length>0&&t.push([])}),A(e[0])&&t[t.length-1].push(e[0]),t[t.length-1].length<=0&&(t=t.slice(0,-1)),t},w=function(e,t){var n=O(e);t&&(n=[n.reduce(function(e,t){return[].concat(k(e),k(t))},[])]);var r=n.map(function(e){return e.reduce(function(e,t,n){return"".concat(e).concat(0===n?"M":"L").concat(t.x,",").concat(t.y)},"")}).join("");return 1===n.length?"".concat(r,"Z"):r},j=function(e,t,n){var r=w(e,n);return"".concat("Z"===r.slice(-1)?r.slice(0,-1):r,"L").concat(w(t.reverse(),n).slice(1))},E=function(e){var t=e.points,n=e.className,r=e.baseLinePoints,o=e.connectNulls,i=function(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n={};for(var r in e)if(Object.prototype.hasOwnProperty.call(e,r)){if(t.indexOf(r)>=0)continue;n[r]=e[r]}return n}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}(e,b);if(!t||!t.length)return null;var a=(0,m.Z)("recharts-polygon",n);if(r&&r.length){var l=i.stroke&&"none"!==i.stroke,s=j(t,r,o);return c.createElement("g",{className:a},c.createElement("path",g({},(0,v.L6)(i,!0),{fill:"Z"===s.slice(-1)?i.fill:"none",stroke:"none",d:s})),l?c.createElement("path",g({},(0,v.L6)(i,!0),{fill:"none",d:w(t,o)})):null,l?c.createElement("path",g({},(0,v.L6)(i,!0),{fill:"none",d:w(r,o)})):null)}var u=w(t,o);return c.createElement("path",g({},(0,v.L6)(i,!0),{fill:"Z"===u.slice(-1)?i.fill:"none",className:a,d:u}))},P=n(58811),S=n(41637),L=n(39206);function N(e){return(N="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function C(){return(C=Object.assign?Object.assign.bind():function(e){for(var t=1;t1e-5?"outer"===t?"start":"end":n<-.00001?"outer"===t?"end":"start":"middle"}},{key:"renderAxisLine",value:function(){var e=this.props,t=e.cx,n=e.cy,r=e.radius,o=e.axisLine,i=e.axisLineType,a=R(R({},(0,v.L6)(this.props,!1)),{},{fill:"none"},(0,v.L6)(o,!1));if("circle"===i)return c.createElement(y.o,C({className:"recharts-polar-angle-axis-line"},a,{cx:t,cy:n,r:r}));var l=this.props.ticks.map(function(e){return(0,L.op)(t,n,r,e.coordinate)});return c.createElement(E,C({className:"recharts-polar-angle-axis-line"},a,{points:l}))}},{key:"renderTicks",value:function(){var e=this,t=this.props,n=t.ticks,o=t.tick,i=t.tickLine,a=t.tickFormatter,l=t.stroke,s=(0,v.L6)(this.props,!1),u=(0,v.L6)(o,!1),d=R(R({},s),{},{fill:"none"},(0,v.L6)(i,!1)),p=n.map(function(t,n){var p=e.getTickLineCoord(t),f=R(R(R({textAnchor:e.getTickTextAnchor(t)},s),{},{stroke:"none",fill:l},u),{},{index:n,payload:t,x:p.x2,y:p.y2});return c.createElement(h.m,C({className:(0,m.Z)("recharts-polar-angle-axis-tick",(0,L.$S)(o)),key:"tick-".concat(t.coordinate)},(0,S.bw)(e.props,t,n)),i&&c.createElement("line",C({className:"recharts-polar-angle-axis-tick-line"},d,p)),o&&r.renderTickItem(o,f,a?a(t.value,n):t.value))});return c.createElement(h.m,{className:"recharts-polar-angle-axis-ticks"},p)}},{key:"render",value:function(){var e=this.props,t=e.ticks,n=e.radius,r=e.axisLine;return!(n<=0)&&t&&t.length?c.createElement(h.m,{className:(0,m.Z)("recharts-polar-angle-axis",this.props.className)},r&&this.renderAxisLine(),this.renderTicks()):null}}],n=[{key:"renderTickItem",value:function(e,t,n){return c.isValidElement(e)?c.cloneElement(e,t):f()(e)?e(t):c.createElement(P.x,C({},t,{className:"recharts-polar-angle-axis-tick-value"}),n)}}],t&&D(r.prototype,t),n&&D(r,n),Object.defineProperty(r,"prototype",{writable:!1}),r}(c.PureComponent);B(_,"displayName","PolarAngleAxis"),B(_,"axisType","angleAxis"),B(_,"defaultProps",{type:"category",angleAxisId:0,scale:"auto",cx:0,cy:0,orientation:"outer",axisLine:!0,tickLine:!0,tickSize:8,tick:!0,hide:!1,allowDuplicatedCategory:!0});var H=n(35802),W=n.n(H),z=n(37891),V=n.n(z),G=n(26680),q=["cx","cy","angle","ticks","axisLine"],$=["ticks","tick","angle","tickFormatter","stroke"];function X(e){return(X="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function U(){return(U=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0)continue;n[r]=e[r]}return n}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function ee(e,t){for(var n=0;n0?es()(e,"paddingAngle",0):0;if(n){var l=(0,eb.k4)(n.endAngle-n.startAngle,e.endAngle-e.startAngle),c=ej(ej({},e),{},{startAngle:i+a,endAngle:i+l(r)+a});o.push(c),i=c.endAngle}else{var s=e.endAngle,d=e.startAngle,p=(0,eb.k4)(0,s-d)(r),f=ej(ej({},e),{},{startAngle:i+a,endAngle:i+p+a});o.push(f),i=f.endAngle}}),c.createElement(h.m,null,e.renderSectorsStatically(o))})}},{key:"attachKeyboardHandlers",value:function(e){var t=this;e.onkeydown=function(e){if(!e.altKey)switch(e.key){case"ArrowLeft":var n=++t.state.sectorToFocus%t.sectorRefs.length;t.sectorRefs[n].focus(),t.setState({sectorToFocus:n});break;case"ArrowRight":var r=--t.state.sectorToFocus<0?t.sectorRefs.length-1:t.state.sectorToFocus%t.sectorRefs.length;t.sectorRefs[r].focus(),t.setState({sectorToFocus:r});break;case"Escape":t.sectorRefs[t.state.sectorToFocus].blur(),t.setState({sectorToFocus:0})}}}},{key:"renderSectors",value:function(){var e=this.props,t=e.sectors,n=e.isAnimationActive,r=this.state.prevSectors;return n&&t&&t.length&&(!r||!ed()(r,t))?this.renderSectorsWithAnimation():this.renderSectorsStatically(t)}},{key:"componentDidMount",value:function(){this.pieRef&&this.attachKeyboardHandlers(this.pieRef)}},{key:"render",value:function(){var e=this,t=this.props,n=t.hide,r=t.sectors,o=t.className,i=t.label,a=t.cx,l=t.cy,s=t.innerRadius,u=t.outerRadius,d=t.isAnimationActive,p=this.state.isAnimationFinished;if(n||!r||!r.length||!(0,eb.hj)(a)||!(0,eb.hj)(l)||!(0,eb.hj)(s)||!(0,eb.hj)(u))return null;var f=(0,m.Z)("recharts-pie",o);return c.createElement(h.m,{tabIndex:this.props.rootTabIndex,className:f,ref:function(t){e.pieRef=t}},this.renderSectors(),i&&this.renderLabels(r),G._.renderCallByParent(this.props,null,!1),(!d||p)&&eh.e.renderCallByParent(this.props,r,!1))}}],n=[{key:"getDerivedStateFromProps",value:function(e,t){return t.prevIsAnimationActive!==e.isAnimationActive?{prevIsAnimationActive:e.isAnimationActive,prevAnimationId:e.animationId,curSectors:e.sectors,prevSectors:[],isAnimationFinished:!0}:e.isAnimationActive&&e.animationId!==t.prevAnimationId?{prevAnimationId:e.animationId,curSectors:e.sectors,prevSectors:t.curSectors,isAnimationFinished:!0}:e.sectors!==t.curSectors?{curSectors:e.sectors,isAnimationFinished:!0}:null}},{key:"getTextAnchor",value:function(e,t){return e>t?"start":e=360?k:k-1)*u,A=a.reduce(function(e,t){var n=(0,eg.F$)(t,g,0);return e+((0,eb.hj)(n)?n:0)},0);return A>0&&(t=a.map(function(e,t){var r,o=(0,eg.F$)(e,g,0),i=(0,eg.F$)(e,p,t),a=((0,eb.hj)(o)?o:0)/A,s=(r=t?n.endAngle+(0,eb.uY)(v)*u*(0!==o?1:0):c)+(0,eb.uY)(v)*((0!==o?h:0)+a*x),d=(r+s)/2,f=(y.innerRadius+y.outerRadius)/2,b=[{name:i,value:o,payload:e,dataKey:g,type:m}],k=(0,L.op)(y.cx,y.cy,f,d);return n=ej(ej(ej({percent:a,cornerRadius:l,name:i,tooltipPayload:b,midAngle:d,middleRadius:f,tooltipPosition:k},e),y),{},{value:(0,eg.F$)(e,g),startAngle:r,endAngle:s,payload:e,paddingAngle:(0,eb.uY)(v)*u})})),ej(ej({},y),{},{sectors:t,data:a})});var eR=(0,d.z)({chartName:"PieChart",GraphicalChild:eT,validateTooltipEventTypes:["item"],defaultTooltipEventType:"item",legendContent:"children",axisComponents:[{axisType:"angleAxis",AxisComp:_},{axisType:"radiusAxis",AxisComp:ea}],formatAxisMap:L.t9,defaultProps:{layout:"centric",startAngle:0,endAngle:360,cx:"50%",cy:"50%",innerRadius:0,outerRadius:"80%"}}),eD=n(8147),eM=n(92666),eI=n(98593);let eZ=e=>{let{active:t,payload:n,valueFormatter:r}=e;if(t&&(null==n?void 0:n[0])){let e=null==n?void 0:n[0];return c.createElement(eI.$B,null,c.createElement("div",{className:(0,a.q)("px-4 py-2")},c.createElement(eI.zX,{value:r(e.value),name:e.name,color:e.payload.color})))}return null},eB=(e,t)=>e.map((e,n)=>{let r=ne||t((0,l.vP)(n.map(e=>e[r]))),eK=e=>{let{cx:t,cy:n,innerRadius:r,outerRadius:o,startAngle:i,endAngle:a,className:l}=e;return c.createElement("g",null,c.createElement(s.L,{cx:t,cy:n,innerRadius:r,outerRadius:o,startAngle:i,endAngle:a,className:l,fill:"",opacity:.3,style:{outline:"none"}}))},e_=c.forwardRef((e,t)=>{let{data:n=[],category:s="value",index:d="name",colors:p=i.s,variant:f="donut",valueFormatter:m=l.Cj,label:h,showLabel:y=!0,animationDuration:v=900,showAnimation:b=!1,showTooltip:g=!0,noDataText:k,onValueChange:x,customTooltip:A,className:O}=e,w=(0,r._T)(e,["data","category","index","colors","variant","valueFormatter","label","showLabel","animationDuration","showAnimation","showTooltip","noDataText","onValueChange","customTooltip","className"]),j="donut"==f,E=eF(h,m,n,s),[P,S]=c.useState(void 0),L=!!x;return(0,c.useEffect)(()=>{let e=document.querySelectorAll(".recharts-pie-sector");e&&e.forEach(e=>{e.setAttribute("style","outline: none")})},[P]),c.createElement("div",Object.assign({ref:t,className:(0,a.q)("w-full h-40",O)},w),c.createElement(u.h,{className:"h-full w-full"},(null==n?void 0:n.length)?c.createElement(eR,{onClick:L&&P?()=>{S(void 0),null==x||x(null)}:void 0,margin:{top:0,left:0,right:0,bottom:0}},y&&j?c.createElement("text",{className:(0,a.q)("fill-tremor-content-emphasis","dark:fill-dark-tremor-content-emphasis"),x:"50%",y:"50%",textAnchor:"middle",dominantBaseline:"middle"},E):null,c.createElement(eT,{className:(0,a.q)("stroke-tremor-background dark:stroke-dark-tremor-background",x?"cursor-pointer":"cursor-default"),data:eB(n,p),cx:"50%",cy:"50%",startAngle:90,endAngle:-270,innerRadius:j?"75%":"0%",outerRadius:"100%",stroke:"",strokeLinejoin:"round",dataKey:s,nameKey:d,isAnimationActive:b,animationDuration:v,onClick:function(e,t,n){n.stopPropagation(),L&&(P===t?(S(void 0),null==x||x(null)):(S(t),null==x||x(Object.assign({eventType:"slice"},e.payload.payload))))},activeIndex:P,inactiveShape:eK,style:{outline:"none"}}),c.createElement(eD.u,{wrapperStyle:{outline:"none"},isAnimationActive:!1,content:g?e=>{var t;let{active:n,payload:r}=e;return A?c.createElement(A,{payload:null==r?void 0:r.map(e=>{var t,n,i;return Object.assign(Object.assign({},e),{color:null!==(i=null===(n=null===(t=null==r?void 0:r[0])||void 0===t?void 0:t.payload)||void 0===n?void 0:n.color)&&void 0!==i?i:o.fr.Gray})}),active:n,label:null===(t=null==r?void 0:r[0])||void 0===t?void 0:t.name}):c.createElement(eZ,{active:n,payload:r,valueFormatter:m})}:c.createElement(c.Fragment,null)})):c.createElement(eM.Z,{noDataText:k})))});e_.displayName="DonutChart"},10968:function(e,t,n){"use strict";n.d(t,{Z:function(){return I}});var r=n(2265),o=n(36760),i=n.n(o),a=n(1119),l=n(26365),c=n(6989),s=n(11993),u=n(31686),d=n(41154),p=n(50506),f=n(18694),m=n(28791),h=n(66632),y=n(27380),v=function(e,t){if(!e)return null;var n={left:e.offsetLeft,right:e.parentElement.clientWidth-e.clientWidth-e.offsetLeft,width:e.clientWidth,top:e.offsetTop,bottom:e.parentElement.clientHeight-e.clientHeight-e.offsetTop,height:e.clientHeight};return t?{left:0,right:0,width:0,top:n.top,bottom:n.bottom,height:n.height}:{left:n.left,right:n.right,width:n.width,top:0,bottom:0,height:0}},b=function(e){return void 0!==e?"".concat(e,"px"):void 0};function g(e){var t=e.prefixCls,n=e.containerRef,o=e.value,a=e.getValueIndex,c=e.motionName,s=e.onMotionStart,d=e.onMotionEnd,p=e.direction,f=e.vertical,g=void 0!==f&&f,k=r.useRef(null),x=r.useState(o),A=(0,l.Z)(x,2),O=A[0],w=A[1],j=function(e){var r,o=a(e),i=null===(r=n.current)||void 0===r?void 0:r.querySelectorAll(".".concat(t,"-item"))[o];return(null==i?void 0:i.offsetParent)&&i},E=r.useState(null),P=(0,l.Z)(E,2),S=P[0],L=P[1],N=r.useState(null),C=(0,l.Z)(N,2),T=C[0],R=C[1];(0,y.Z)(function(){if(O!==o){var e=j(O),t=j(o),n=v(e,g),r=v(t,g);w(o),L(n),R(r),e&&t?s():d()}},[o]);var D=r.useMemo(function(){if(g){var e;return b(null!==(e=null==S?void 0:S.top)&&void 0!==e?e:0)}return"rtl"===p?b(-(null==S?void 0:S.right)):b(null==S?void 0:S.left)},[g,p,S]),M=r.useMemo(function(){if(g){var e;return b(null!==(e=null==T?void 0:T.top)&&void 0!==e?e:0)}return"rtl"===p?b(-(null==T?void 0:T.right)):b(null==T?void 0:T.left)},[g,p,T]);return S&&T?r.createElement(h.ZP,{visible:!0,motionName:c,motionAppear:!0,onAppearStart:function(){return g?{transform:"translateY(var(--thumb-start-top))",height:"var(--thumb-start-height)"}:{transform:"translateX(var(--thumb-start-left))",width:"var(--thumb-start-width)"}},onAppearActive:function(){return g?{transform:"translateY(var(--thumb-active-top))",height:"var(--thumb-active-height)"}:{transform:"translateX(var(--thumb-active-left))",width:"var(--thumb-active-width)"}},onVisibleChanged:function(){L(null),R(null),d()}},function(e,n){var o=e.className,a=e.style,l=(0,u.Z)((0,u.Z)({},a),{},{"--thumb-start-left":D,"--thumb-start-width":b(null==S?void 0:S.width),"--thumb-active-left":M,"--thumb-active-width":b(null==T?void 0:T.width),"--thumb-start-top":D,"--thumb-start-height":b(null==S?void 0:S.height),"--thumb-active-top":M,"--thumb-active-height":b(null==T?void 0:T.height)}),c={ref:(0,m.sQ)(k,n),style:l,className:i()("".concat(t,"-thumb"),o)};return r.createElement("div",c)}):null}var k=["prefixCls","direction","vertical","options","disabled","defaultValue","value","name","onChange","className","motionName"],x=function(e){var t=e.prefixCls,n=e.className,o=e.disabled,a=e.checked,l=e.label,c=e.title,u=e.value,d=e.name,p=e.onChange,f=e.onFocus,m=e.onBlur,h=e.onKeyDown,y=e.onKeyUp,v=e.onMouseDown;return r.createElement("label",{className:i()(n,(0,s.Z)({},"".concat(t,"-item-disabled"),o)),onMouseDown:v},r.createElement("input",{name:d,className:"".concat(t,"-item-input"),type:"radio",disabled:o,checked:a,onChange:function(e){o||p(e,u)},onFocus:f,onBlur:m,onKeyDown:h,onKeyUp:y}),r.createElement("div",{className:"".concat(t,"-item-label"),title:c,"aria-selected":a},l))},A=r.forwardRef(function(e,t){var n,o,h=e.prefixCls,y=void 0===h?"rc-segmented":h,v=e.direction,b=e.vertical,A=e.options,O=void 0===A?[]:A,w=e.disabled,j=e.defaultValue,E=e.value,P=e.name,S=e.onChange,L=e.className,N=e.motionName,C=(0,c.Z)(e,k),T=r.useRef(null),R=r.useMemo(function(){return(0,m.sQ)(T,t)},[T,t]),D=r.useMemo(function(){return O.map(function(e){if("object"===(0,d.Z)(e)&&null!==e){var t=function(e){if(void 0!==e.title)return e.title;if("object"!==(0,d.Z)(e.label)){var t;return null===(t=e.label)||void 0===t?void 0:t.toString()}}(e);return(0,u.Z)((0,u.Z)({},e),{},{title:t})}return{label:null==e?void 0:e.toString(),title:null==e?void 0:e.toString(),value:e}})},[O]),M=(0,p.Z)(null===(n=D[0])||void 0===n?void 0:n.value,{value:E,defaultValue:j}),I=(0,l.Z)(M,2),Z=I[0],B=I[1],F=r.useState(!1),K=(0,l.Z)(F,2),_=K[0],H=K[1],W=function(e,t){B(t),null==S||S(t)},z=(0,f.Z)(C,["children"]),V=r.useState(!1),G=(0,l.Z)(V,2),q=G[0],$=G[1],X=r.useState(!1),U=(0,l.Z)(X,2),Y=U[0],Q=U[1],J=function(){Q(!0)},ee=function(){Q(!1)},et=function(){$(!1)},en=function(e){"Tab"===e.key&&$(!0)},er=function(e){var t=D.findIndex(function(e){return e.value===Z}),n=D.length,r=D[(t+e+n)%n];r&&(B(r.value),null==S||S(r.value))},eo=function(e){switch(e.key){case"ArrowLeft":case"ArrowUp":er(-1);break;case"ArrowRight":case"ArrowDown":er(1)}};return r.createElement("div",(0,a.Z)({role:"radiogroup","aria-label":"segmented control",tabIndex:w?void 0:0},z,{className:i()(y,(o={},(0,s.Z)(o,"".concat(y,"-rtl"),"rtl"===v),(0,s.Z)(o,"".concat(y,"-disabled"),w),(0,s.Z)(o,"".concat(y,"-vertical"),b),o),void 0===L?"":L),ref:R}),r.createElement("div",{className:"".concat(y,"-group")},r.createElement(g,{vertical:b,prefixCls:y,value:Z,containerRef:T,motionName:"".concat(y,"-").concat(void 0===N?"thumb-motion":N),direction:v,getValueIndex:function(e){return D.findIndex(function(t){return t.value===e})},onMotionStart:function(){H(!0)},onMotionEnd:function(){H(!1)}}),D.map(function(e){var t;return r.createElement(x,(0,a.Z)({},e,{name:P,key:e.value,prefixCls:y,className:i()(e.className,"".concat(y,"-item"),(t={},(0,s.Z)(t,"".concat(y,"-item-selected"),e.value===Z&&!_),(0,s.Z)(t,"".concat(y,"-item-focused"),Y&&q&&e.value===Z),t)),checked:e.value===Z,onChange:W,onFocus:J,onBlur:ee,onKeyDown:eo,onKeyUp:en,onMouseDown:et,disabled:!!w||!!e.disabled}))})))}),O=n(92491),w=n(71744),j=n(33759),E=n(93463),P=n(12918),S=n(99320),L=n(71140);function N(e,t){return{["".concat(e,", ").concat(e,":hover, ").concat(e,":focus")]:{color:t.colorTextDisabled,cursor:"not-allowed"}}}function C(e){return{background:e.itemSelectedBg,boxShadow:e.boxShadowTertiary}}let T=Object.assign({overflow:"hidden"},P.vS),R=e=>{let{componentCls:t}=e,n=e.calc(e.controlHeight).sub(e.calc(e.trackPadding).mul(2)).equal(),r=e.calc(e.controlHeightLG).sub(e.calc(e.trackPadding).mul(2)).equal(),o=e.calc(e.controlHeightSM).sub(e.calc(e.trackPadding).mul(2)).equal();return{[t]:Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},(0,P.Wf)(e)),{display:"inline-block",padding:e.trackPadding,color:e.itemColor,background:e.trackBg,borderRadius:e.borderRadius,transition:"all ".concat(e.motionDurationMid)}),(0,P.Qy)(e)),{["".concat(t,"-group")]:{position:"relative",display:"flex",alignItems:"stretch",justifyItems:"flex-start",flexDirection:"row",width:"100%"},["&".concat(t,"-rtl")]:{direction:"rtl"},["&".concat(t,"-vertical")]:{["".concat(t,"-group")]:{flexDirection:"column"},["".concat(t,"-thumb")]:{width:"100%",height:0,padding:"0 ".concat((0,E.bf)(e.paddingXXS))}},["&".concat(t,"-block")]:{display:"flex"},["&".concat(t,"-block ").concat(t,"-item")]:{flex:1,minWidth:0},["".concat(t,"-item")]:{position:"relative",textAlign:"center",cursor:"pointer",transition:"color ".concat(e.motionDurationMid),borderRadius:e.borderRadiusSM,transform:"translateZ(0)","&-selected":Object.assign(Object.assign({},C(e)),{color:e.itemSelectedColor}),"&-focused":(0,P.oN)(e),"&::after":{content:'""',position:"absolute",zIndex:-1,width:"100%",height:"100%",top:0,insetInlineStart:0,borderRadius:"inherit",opacity:0,transition:"opacity ".concat(e.motionDurationMid,", background-color ").concat(e.motionDurationMid),pointerEvents:"none"},["&:not(".concat(t,"-item-selected):not(").concat(t,"-item-disabled)")]:{"&:hover, &:active":{color:e.itemHoverColor},"&:hover::after":{opacity:1,backgroundColor:e.itemHoverBg},"&:active::after":{opacity:1,backgroundColor:e.itemActiveBg}},"&-label":Object.assign({minHeight:n,lineHeight:(0,E.bf)(n),padding:"0 ".concat((0,E.bf)(e.segmentedPaddingHorizontal))},T),"&-icon + *":{marginInlineStart:e.calc(e.marginSM).div(2).equal()},"&-input":{position:"absolute",insetBlockStart:0,insetInlineStart:0,width:0,height:0,opacity:0,pointerEvents:"none"}},["".concat(t,"-thumb")]:Object.assign(Object.assign({},C(e)),{position:"absolute",insetBlockStart:0,insetInlineStart:0,width:0,height:"100%",padding:"".concat((0,E.bf)(e.paddingXXS)," 0"),borderRadius:e.borderRadiusSM,["& ~ ".concat(t,"-item:not(").concat(t,"-item-selected):not(").concat(t,"-item-disabled)::after")]:{backgroundColor:"transparent"}}),["&".concat(t,"-lg")]:{borderRadius:e.borderRadiusLG,["".concat(t,"-item-label")]:{minHeight:r,lineHeight:(0,E.bf)(r),padding:"0 ".concat((0,E.bf)(e.segmentedPaddingHorizontal)),fontSize:e.fontSizeLG},["".concat(t,"-item, ").concat(t,"-thumb")]:{borderRadius:e.borderRadius}},["&".concat(t,"-sm")]:{borderRadius:e.borderRadiusSM,["".concat(t,"-item-label")]:{minHeight:o,lineHeight:(0,E.bf)(o),padding:"0 ".concat((0,E.bf)(e.segmentedPaddingHorizontalSM))},["".concat(t,"-item, ").concat(t,"-thumb")]:{borderRadius:e.borderRadiusXS}}}),N("&-disabled ".concat(t,"-item"),e)),N("".concat(t,"-item-disabled"),e)),{["".concat(t,"-thumb-motion-appear-active")]:{transition:"transform ".concat(e.motionDurationSlow," ").concat(e.motionEaseInOut,", width ").concat(e.motionDurationSlow," ").concat(e.motionEaseInOut),willChange:"transform, width"},["&".concat(t,"-shape-round")]:{borderRadius:9999,["".concat(t,"-item, ").concat(t,"-thumb")]:{borderRadius:9999}}})}};var D=(0,S.I$)("Segmented",e=>{let{lineWidth:t,calc:n}=e;return R((0,L.IX)(e,{segmentedPaddingHorizontal:n(e.controlPaddingHorizontal).sub(t).equal(),segmentedPaddingHorizontalSM:n(e.controlPaddingHorizontalSM).sub(t).equal()}))},e=>{let{colorTextLabel:t,colorText:n,colorFillSecondary:r,colorBgElevated:o,colorFill:i,lineWidthBold:a,colorBgLayout:l}=e;return{trackPadding:a,trackBg:l,itemColor:t,itemHoverColor:n,itemHoverBg:r,itemSelectedBg:o,itemActiveBg:i,itemSelectedColor:n}}),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 o=0,r=Object.getOwnPropertySymbols(e);ot.indexOf(r[o])&&Object.prototype.propertyIsEnumerable.call(e,r[o])&&(n[r[o]]=e[r[o]]);return n},I=r.forwardRef((e,t)=>{let n=(0,O.Z)(),{prefixCls:o,className:a,rootClassName:l,block:c,options:s=[],size:u="middle",style:d,vertical:p,shape:f="default",name:m=n}=e,h=M(e,["prefixCls","className","rootClassName","block","options","size","style","vertical","shape","name"]),{getPrefixCls:y,direction:v,className:b,style:g}=(0,w.dj)("segmented"),k=y("segmented",o),[x,E,P]=D(k),S=(0,j.Z)(u),L=r.useMemo(()=>s.map(e=>{if("object"==typeof e&&(null==e?void 0:e.icon)){let{icon:t,label:n}=e;return Object.assign(Object.assign({},M(e,["icon","label"])),{label:r.createElement(r.Fragment,null,r.createElement("span",{className:"".concat(k,"-item-icon")},t),n&&r.createElement("span",null,n))})}return e}),[s,k]),N=i()(a,l,b,{["".concat(k,"-block")]:c,["".concat(k,"-sm")]:"small"===S,["".concat(k,"-lg")]:"large"===S,["".concat(k,"-vertical")]:p,["".concat(k,"-shape-").concat(f)]:"round"===f},E,P),C=Object.assign(Object.assign({},g),d);return x(r.createElement(A,Object.assign({},h,{name:m,className:N,style:C,options:L,ref:t,prefixCls:k,direction:v,vertical:p})))})},35802:function(e,t,n){var r=n(67646),o=n(58905),i=n(88157);e.exports=function(e,t){return e&&e.length?r(e,i(t,2),o):void 0}},37891:function(e,t,n){var r=n(67646),o=n(88157),i=n(20121);e.exports=function(e,t){return e&&e.length?r(e,o(t,2),i):void 0}},32489:function(e,t,n){"use strict";n.d(t,{Z:function(){return r}});let r=(0,n(79205).Z)("x",[["path",{d:"M18 6 6 18",key:"1bl5f8"}],["path",{d:"m6 6 12 12",key:"d8bk6v"}]])},54061:function(e,t,n){"use strict";n.d(t,{x:function(){return I}});var r=n(2265),o=n(84735),i=n(86757),a=n.n(i),l=n(77571),c=n.n(l),s=n(21652),u=n.n(s),d=n(61994),p=n(57165),f=n(81889),m=n(9841),h=n(58772),y=n(13137),v=n(16630),b=n(82944),g=n(34067),k=n(85355),x=["type","layout","connectNulls","ref"],A=["key"];function O(e){return(O="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function w(e,t){if(null==e)return{};var n,r,o=function(e,t){if(null==e)return{};var n={};for(var r in e)if(Object.prototype.hasOwnProperty.call(e,r)){if(t.indexOf(r)>=0)continue;n[r]=e[r]}return n}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(o[n]=e[n])}return o}function j(){return(j=Object.assign?Object.assign.bind():function(e){for(var t=1;te.length)&&(t=e.length);for(var n=0,r=Array(t);na){c=[].concat(S(r.slice(0,s)),[a-u]);break}var d=c.length%2==0?[0,l]:[l];return[].concat(S(i.repeat(r,Math.floor(t/o))),S(c),d).map(function(e){return"".concat(e,"px")}).join(", ")}),D(e,"id",(0,v.EL)("recharts-line-")),D(e,"pathRef",function(t){e.mainCurve=t}),D(e,"handleAnimationEnd",function(){e.setState({isAnimationFinished:!0}),e.props.onAnimationEnd&&e.props.onAnimationEnd()}),D(e,"handleAnimationStart",function(){e.setState({isAnimationFinished:!1}),e.props.onAnimationStart&&e.props.onAnimationStart()}),e}return!function(e,t){if("function"!=typeof t&&null!==t)throw TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),Object.defineProperty(e,"prototype",{writable:!1}),t&&R(e,t)}(i,e),t=[{key:"componentDidMount",value:function(){if(this.props.isAnimationActive){var e=this.getTotalLength();this.setState({totalLength:e})}}},{key:"componentDidUpdate",value:function(){if(this.props.isAnimationActive){var e=this.getTotalLength();e!==this.state.totalLength&&this.setState({totalLength:e})}}},{key:"getTotalLength",value:function(){var e=this.mainCurve;try{return e&&e.getTotalLength&&e.getTotalLength()||0}catch(e){return 0}}},{key:"renderErrorBar",value:function(e,t){if(this.props.isAnimationActive&&!this.state.isAnimationFinished)return null;var n=this.props,o=n.points,i=n.xAxis,a=n.yAxis,l=n.layout,c=n.children,s=(0,b.NN)(c,y.W);if(!s)return null;var u=function(e,t){return{x:e.x,y:e.y,value:e.value,errorVal:(0,k.F$)(e.payload,t)}};return r.createElement(m.m,{clipPath:e?"url(#clipPath-".concat(t,")"):null},s.map(function(e){return r.cloneElement(e,{key:"bar-".concat(e.props.dataKey),data:o,xAxis:i,yAxis:a,layout:l,dataPointFormatter:u})}))}},{key:"renderDots",value:function(e,t,n){if(this.props.isAnimationActive&&!this.state.isAnimationFinished)return null;var o=this.props,a=o.dot,l=o.points,c=o.dataKey,s=(0,b.L6)(this.props,!1),u=(0,b.L6)(a,!0),d=l.map(function(e,t){var n=P(P(P({key:"dot-".concat(t),r:3},s),u),{},{index:t,cx:e.x,cy:e.y,value:e.value,dataKey:c,payload:e.payload,points:l});return i.renderDotItem(a,n)}),p={clipPath:e?"url(#clipPath-".concat(t?"":"dots-").concat(n,")"):null};return r.createElement(m.m,j({className:"recharts-line-dots",key:"dots"},p),d)}},{key:"renderCurveStatically",value:function(e,t,n,o){var i=this.props,a=i.type,l=i.layout,c=i.connectNulls,s=(i.ref,w(i,x)),u=P(P(P({},(0,b.L6)(s,!0)),{},{fill:"none",className:"recharts-line-curve",clipPath:t?"url(#clipPath-".concat(n,")"):null,points:e},o),{},{type:a,layout:l,connectNulls:c});return r.createElement(p.H,j({},u,{pathRef:this.pathRef}))}},{key:"renderCurveWithAnimation",value:function(e,t){var n=this,i=this.props,a=i.points,l=i.strokeDasharray,c=i.isAnimationActive,s=i.animationBegin,u=i.animationDuration,d=i.animationEasing,p=i.animationId,f=i.animateNewValues,m=i.width,h=i.height,y=this.state,b=y.prevPoints,g=y.totalLength;return r.createElement(o.ZP,{begin:s,duration:u,isActive:c,easing:d,from:{t:0},to:{t:1},key:"line-".concat(p),onAnimationEnd:this.handleAnimationEnd,onAnimationStart:this.handleAnimationStart},function(r){var o,i=r.t;if(b){var c=b.length/a.length,s=a.map(function(e,t){var n=Math.floor(t*c);if(b[n]){var r=b[n],o=(0,v.k4)(r.x,e.x),a=(0,v.k4)(r.y,e.y);return P(P({},e),{},{x:o(i),y:a(i)})}if(f){var l=(0,v.k4)(2*m,e.x),s=(0,v.k4)(h/2,e.y);return P(P({},e),{},{x:l(i),y:s(i)})}return P(P({},e),{},{x:e.x,y:e.y})});return n.renderCurveStatically(s,e,t)}var u=(0,v.k4)(0,g)(i);if(l){var d="".concat(l).split(/[,\s]+/gim).map(function(e){return parseFloat(e)});o=n.getStrokeDasharray(u,g,d)}else o=n.generateSimpleStrokeDasharray(g,u);return n.renderCurveStatically(a,e,t,{strokeDasharray:o})})}},{key:"renderCurve",value:function(e,t){var n=this.props,r=n.points,o=n.isAnimationActive,i=this.state,a=i.prevPoints,l=i.totalLength;return o&&r&&r.length&&(!a&&l>0||!u()(a,r))?this.renderCurveWithAnimation(e,t):this.renderCurveStatically(r,e,t)}},{key:"render",value:function(){var e,t=this.props,n=t.hide,o=t.dot,i=t.points,a=t.className,l=t.xAxis,s=t.yAxis,u=t.top,p=t.left,f=t.width,y=t.height,v=t.isAnimationActive,g=t.id;if(n||!i||!i.length)return null;var k=this.state.isAnimationFinished,x=1===i.length,A=(0,d.Z)("recharts-line",a),O=l&&l.allowDataOverflow,w=s&&s.allowDataOverflow,j=O||w,E=c()(g)?this.id:g,P=null!==(e=(0,b.L6)(o,!1))&&void 0!==e?e:{r:3,strokeWidth:2},S=P.r,L=P.strokeWidth,N=((0,b.jf)(o)?o:{}).clipDot,C=void 0===N||N,T=2*(void 0===S?3:S)+(void 0===L?2:L);return r.createElement(m.m,{className:A},O||w?r.createElement("defs",null,r.createElement("clipPath",{id:"clipPath-".concat(E)},r.createElement("rect",{x:O?p:p-f/2,y:w?u:u-y/2,width:O?f:2*f,height:w?y:2*y})),!C&&r.createElement("clipPath",{id:"clipPath-dots-".concat(E)},r.createElement("rect",{x:p-T/2,y:u-T/2,width:f+T,height:y+T}))):null,!x&&this.renderCurve(j,E),this.renderErrorBar(j,E),(x||o)&&this.renderDots(j,C,E),(!v||k)&&h.e.renderCallByParent(this.props,i))}}],n=[{key:"getDerivedStateFromProps",value:function(e,t){return e.animationId!==t.prevAnimationId?{prevAnimationId:e.animationId,curPoints:e.points,prevPoints:t.curPoints}:e.points!==t.curPoints?{curPoints:e.points}:null}},{key:"repeat",value:function(e,t){for(var n=e.length%2!=0?[].concat(S(e),[0]):e,r=[],o=0;o{var e,l,t,a,i,r,n;h.setFieldsValue({user_id:s.user_id,user_email:null===(e=s.user_info)||void 0===e?void 0:e.user_email,user_alias:null===(l=s.user_info)||void 0===l?void 0:l.user_alias,user_role:null===(t=s.user_info)||void 0===t?void 0:t.user_role,models:(null===(a=s.user_info)||void 0===a?void 0:a.models)||[],max_budget:null===(i=s.user_info)||void 0===i?void 0:i.max_budget,budget_duration:null===(r=s.user_info)||void 0===r?void 0:r.budget_duration,metadata:(null===(n=s.user_info)||void 0===n?void 0:n.metadata)?JSON.stringify(s.user_info.metadata,null,2):void 0})},[s,h]),(0,t.jsxs)(p.Z,{form:h,onFinish:e=>{if(e.metadata&&"string"==typeof e.metadata)try{e.metadata=JSON.parse(e.metadata)}catch(e){console.error("Error parsing metadata JSON:",e);return}a(e)},layout:"vertical",children:[!x&&(0,t.jsx)(p.Z.Item,{label:"User ID",name:"user_id",children:(0,t.jsx)(b.Z,{disabled:!0})}),!x&&(0,t.jsx)(p.Z.Item,{label:"Email",name:"user_email",children:(0,t.jsx)(b.Z,{})}),(0,t.jsx)(p.Z.Item,{label:"User Alias",name:"user_alias",children:(0,t.jsx)(b.Z,{})}),(0,t.jsx)(p.Z.Item,{label:(0,t.jsxs)("span",{children:["Global Proxy Role"," ",(0,t.jsx)(f.Z,{title:"This is the role that the user will globally on the proxy. This role is independent of any team/org specific roles.",children:(0,t.jsx)(w.Z,{})})]}),name:"user_role",children:(0,t.jsx)(g.default,{children:m&&Object.entries(m).map(e=>{let[s,{ui_label:l,description:a}]=e;return(0,t.jsx)(_.Z,{value:s,title:l,children:(0,t.jsxs)("div",{className:"flex",children:[l," ",(0,t.jsx)("p",{className:"ml-2",style:{color:"gray",fontSize:"12px"},children:a})]})},s)})})}),(0,t.jsx)(p.Z.Item,{label:(0,t.jsxs)("span",{children:["Personal Models"," ",(0,t.jsx)(f.Z,{title:"Select which models this user can access outside of team-scope. Choose 'All Proxy Models' to grant access to all models available on the proxy.",children:(0,t.jsx)(w.Z,{style:{marginLeft:"4px"}})})]}),name:"models",children:(0,t.jsxs)(g.default,{mode:"multiple",placeholder:"Select models",style:{width:"100%"},disabled:!Z.ZL.includes(c||""),children:[(0,t.jsx)(g.default.Option,{value:"all-proxy-models",children:"All Proxy Models"},"all-proxy-models"),(0,t.jsx)(g.default.Option,{value:"no-default-models",children:"No Default Models"},"no-default-models"),u.map(e=>(0,t.jsx)(g.default.Option,{value:e,children:(0,S.W0)(e)},e))]})}),(0,t.jsx)(p.Z.Item,{label:"Max Budget (USD)",name:"max_budget",children:(0,t.jsx)(y.Z,{step:.01,precision:2,style:{width:"100%"}})}),(0,t.jsx)(p.Z.Item,{label:"Reset Budget",name:"budget_duration",children:(0,t.jsx)(k.Z,{})}),(0,t.jsx)(p.Z.Item,{label:"Metadata",name:"metadata",children:(0,t.jsx)(N.Z,{rows:4,placeholder:"Enter metadata as JSON"})}),(0,t.jsxs)("div",{className:"flex justify-end space-x-2",children:[(0,t.jsx)(r.z,{variant:"secondary",type:"button",onClick:l,children:"Cancel"}),(0,t.jsx)(r.z,{type:"submit",children:"Save Changes"})]})]})}var U=l(9114);let{Text:I,Title:D}=n.default;var z=e=>{let{open:s,onCancel:l,selectedUsers:a,possibleUIRoles:r,accessToken:n,onSuccess:p,teams:f,userRole:y,userModels:_,allowAllUsers:b=!1}=e,[N,S]=(0,i.useState)(!1),[Z,w]=(0,i.useState)([]),[k,z]=(0,i.useState)(null),[A,B]=(0,i.useState)(!1),[E,T]=(0,i.useState)(!1),O=()=>{w([]),z(null),B(!1),T(!1),l()},L=i.useMemo(()=>({user_id:"bulk_edit",user_info:{user_email:"",user_role:"",teams:[],models:[],max_budget:null,spend:0,metadata:{},created_at:null,updated_at:null},keys:[],teams:f||[]}),[f,s]),F=async e=>{if(console.log("formValues",e),!n){U.Z.fromBackend("Access token not found");return}S(!0);try{let s=a.map(e=>e.user_id),t={};e.user_role&&""!==e.user_role&&(t.user_role=e.user_role),null!==e.max_budget&&void 0!==e.max_budget&&(t.max_budget=e.max_budget),e.models&&e.models.length>0&&(t.models=e.models),e.budget_duration&&""!==e.budget_duration&&(t.budget_duration=e.budget_duration),e.metadata&&Object.keys(e.metadata).length>0&&(t.metadata=e.metadata);let i=Object.keys(t).length>0,r=A&&Z.length>0;if(!i&&!r){U.Z.fromBackend("Please modify at least one field or select teams to add users to");return}let o=[];if(i){if(E){let e=await (0,j.userBulkUpdateUserCall)(n,t,void 0,!0);o.push("Updated all users (".concat(e.total_requested," total)"))}else await (0,j.userBulkUpdateUserCall)(n,t,s),o.push("Updated ".concat(s.length," user(s)"))}if(r){let e=[];for(let s of Z)try{let l=null;l=E?null:a.map(e=>({user_id:e.user_id,role:"user",user_email:e.user_email||null}));let t=await (0,j.teamBulkMemberAddCall)(n,s,l||null,k||void 0,E);console.log("result",t),e.push({teamId:s,success:!0,successfulAdditions:t.successful_additions,failedAdditions:t.failed_additions})}catch(l){console.error("Failed to add users to team ".concat(s,":"),l),e.push({teamId:s,success:!1,error:l})}let s=e.filter(e=>e.success),l=e.filter(e=>!e.success);if(s.length>0){let e=s.reduce((e,s)=>e+s.successfulAdditions,0);o.push("Added users to ".concat(s.length," team(s) (").concat(e," total additions)"))}l.length>0&&d.ZP.warning("Failed to add users to ".concat(l.length," team(s)"))}o.length>0&&U.Z.success(o.join(". ")),w([]),z(null),B(!1),T(!1),p(),l()}catch(e){console.error("Bulk operation failed:",e),U.Z.fromBackend("Failed to perform bulk operations")}finally{S(!1)}};return(0,t.jsxs)(o.Z,{open:s,onCancel:O,footer:null,title:E?"Bulk Edit All Users":"Bulk Edit ".concat(a.length," User(s)"),width:800,children:[b&&(0,t.jsxs)("div",{className:"mb-4",children:[(0,t.jsx)(c.Z,{checked:E,onChange:e=>T(e.target.checked),children:(0,t.jsx)(I,{strong:!0,children:"Update ALL users in the system"})}),E&&(0,t.jsx)("div",{style:{marginTop:8},children:(0,t.jsx)(I,{type:"warning",style:{fontSize:"12px"},children:"⚠️ This will apply changes to ALL users in the system, not just the selected ones."})})]}),!E&&(0,t.jsxs)("div",{className:"mb-4",children:[(0,t.jsxs)(D,{level:5,children:["Selected Users (",a.length,"):"]}),(0,t.jsx)(u.Z,{size:"small",bordered:!0,dataSource:a,pagination:!1,scroll:{y:200},rowKey:"user_id",columns:[{title:"User ID",dataIndex:"user_id",key:"user_id",width:"30%",render:e=>(0,t.jsx)(I,{strong:!0,style:{fontSize:"12px"},children:e.length>20?"".concat(e.slice(0,20),"..."):e})},{title:"Email",dataIndex:"user_email",key:"user_email",width:"25%",render:e=>(0,t.jsx)(I,{type:"secondary",style:{fontSize:"12px"},children:e||"No email"})},{title:"Current Role",dataIndex:"user_role",key:"user_role",width:"25%",render:e=>{var s;return(0,t.jsx)(I,{style:{fontSize:"12px"},children:(null==r?void 0:null===(s=r[e])||void 0===s?void 0:s.ui_label)||e})}},{title:"Budget",dataIndex:"max_budget",key:"max_budget",width:"20%",render:e=>(0,t.jsx)(I,{style:{fontSize:"12px"},children:null!==e?"$".concat(e):"Unlimited"})}]})]}),(0,t.jsx)(m.Z,{}),(0,t.jsx)("div",{className:"mb-4",children:(0,t.jsxs)(I,{children:[(0,t.jsx)("strong",{children:"Instructions:"})," Fill in the fields below with the values you want to apply to all selected users. You can bulk edit: role, budget, models, and metadata. You can also add users to teams."]})}),(0,t.jsx)(x.Z,{title:"Team Management",size:"small",className:"mb-4",style:{backgroundColor:"#fafafa"},children:(0,t.jsxs)(h.Z,{direction:"vertical",style:{width:"100%"},children:[(0,t.jsx)(c.Z,{checked:A,onChange:e=>B(e.target.checked),children:"Add selected users to teams"}),A&&(0,t.jsxs)(t.Fragment,{children:[(0,t.jsxs)("div",{children:[(0,t.jsx)(I,{strong:!0,children:"Select Teams:"}),(0,t.jsx)(g.default,{mode:"multiple",placeholder:"Select teams to add users to",value:Z,onChange:w,style:{width:"100%",marginTop:8},options:(null==f?void 0:f.map(e=>({label:e.team_alias||e.team_id,value:e.team_id})))||[]})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(I,{strong:!0,children:"Team Budget (Optional):"}),(0,t.jsx)(v.Z,{placeholder:"Max budget per user in team",value:k,onChange:e=>z(e),style:{width:"100%",marginTop:8},min:0,step:.01,precision:2}),(0,t.jsx)(I,{type:"secondary",style:{fontSize:"12px"},children:"Leave empty for unlimited budget within team limits"})]}),(0,t.jsx)(I,{type:"secondary",style:{fontSize:"12px"},children:'Users will be added with "user" role by default. All users will be added to each selected team.'})]})]})}),(0,t.jsx)(C,{userData:L,onCancel:O,onSubmit:F,teams:f,accessToken:n,userID:"bulk_edit",userRole:y,userModels:_,possibleUIRoles:r,isBulkEdit:!0}),N&&(0,t.jsx)("div",{style:{textAlign:"center",marginTop:"10px"},children:(0,t.jsxs)(I,{children:["Updating ",E?"all users":a.length," user(s)..."]})})]})},A=l(7765),B=l(5545),E=e=>{let{visible:s,possibleUIRoles:l,onCancel:a,user:r,onSubmit:n}=e,[d,c]=(0,i.useState)(r),[u]=p.Z.useForm();(0,i.useEffect)(()=>{u.resetFields()},[r]);let m=async()=>{u.resetFields(),a()},x=async e=>{n(e),u.resetFields(),a()};return r?(0,t.jsx)(o.Z,{visible:s,onCancel:m,footer:null,title:"Edit User "+r.user_id,width:1e3,children:(0,t.jsx)(p.Z,{form:u,onFinish:x,initialValues:r,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",children:(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(p.Z.Item,{className:"mt-8",label:"User Email",tooltip:"Email of the User",name:"user_email",children:(0,t.jsx)(b.Z,{})}),(0,t.jsx)(p.Z.Item,{label:"user_id",name:"user_id",hidden:!0,children:(0,t.jsx)(b.Z,{})}),(0,t.jsx)(p.Z.Item,{label:"User Role",name:"user_role",children:(0,t.jsx)(g.default,{children:l&&Object.entries(l).map(e=>{let[s,{ui_label:l,description:a}]=e;return(0,t.jsx)(_.Z,{value:s,title:l,children:(0,t.jsxs)("div",{className:"flex",children:[l," ",(0,t.jsx)("p",{className:"ml-2",style:{color:"gray",fontSize:"12px"},children:a})]})},s)})})}),(0,t.jsx)(p.Z.Item,{label:"Spend (USD)",name:"spend",tooltip:"(float) - Spend of all LLM calls completed by this user",help:"Across all keys (including keys with team_id).",children:(0,t.jsx)(v.Z,{min:0,step:.01})}),(0,t.jsx)(p.Z.Item,{label:"User Budget (USD)",name:"max_budget",tooltip:"(float) - Maximum budget of this user",help:"Maximum budget of this user.",children:(0,t.jsx)(y.Z,{min:0,step:.01})}),(0,t.jsx)(p.Z.Item,{label:"Reset Budget",name:"budget_duration",children:(0,t.jsx)(k.Z,{})}),(0,t.jsx)("div",{style:{textAlign:"right",marginTop:"10px"},children:(0,t.jsx)(B.ZP,{htmlType:"submit",children:"Save"})}),(0,t.jsx)("div",{style:{textAlign:"right",marginTop:"10px"},children:(0,t.jsx)(B.ZP,{htmlType:"submit",children:"Save"})})]})})}):null},T=l(98187),O=l(59872),L=l(19616),F=l(29827),R=l(11713),P=l(21609),M=l(88913),K=l(63709),V=l(10353),q=l(26349),G=l(96473),J=e=>{var s;let{accessToken:l,possibleUIRoles:a,userID:r,userRole:d}=e,[o,c]=(0,i.useState)(!0),[u,m]=(0,i.useState)(null),[x,h]=(0,i.useState)(!1),[p,f]=(0,i.useState)({}),[y,_]=(0,i.useState)(!1),[b,N]=(0,i.useState)([]),{Paragraph:Z}=n.default,{Option:w}=g.default;(0,i.useEffect)(()=>{(async()=>{if(!l){c(!1);return}try{let e=await (0,j.getInternalUserSettings)(l);if(m(e),f(e.values||{}),l)try{let e=await (0,j.modelAvailableCall)(l,r,d);if(e&&e.data){let s=e.data.map(e=>e.id);N(s)}}catch(e){console.error("Error fetching available models:",e)}}catch(e){console.error("Error fetching SSO settings:",e),U.Z.fromBackend("Failed to fetch SSO settings")}finally{c(!1)}})()},[l]);let C=async()=>{if(l){_(!0);try{let e=Object.entries(p).reduce((e,s)=>{let[l,t]=s;return e[l]=""===t?null:t,e},{}),s=await (0,j.updateInternalUserSettings)(l,e);m({...u,values:s.settings}),h(!1)}catch(e){console.error("Error updating SSO settings:",e),U.Z.fromBackend("Failed to update settings: "+e)}finally{_(!1)}}},I=(e,s)=>{f(l=>({...l,[e]:s}))},D=e=>e&&Array.isArray(e)?e.map(e=>"string"==typeof e?{team_id:e,user_role:"user"}:"object"==typeof e&&e.team_id?{team_id:e.team_id,max_budget_in_team:e.max_budget_in_team,user_role:e.user_role||"user"}:{team_id:"",user_role:"user"}):[],z=e=>{let s=D(e),l=(e,l,t)=>{let a=[...s];a[e]={...a[e],[l]:t},I("teams",a)},a=e=>{I("teams",s.filter((s,l)=>l!==e))};return(0,t.jsxs)("div",{className:"space-y-3",children:[s.map((e,s)=>(0,t.jsxs)("div",{className:"border rounded-lg p-4 bg-gray-50",children:[(0,t.jsxs)("div",{className:"flex items-center justify-between mb-3",children:[(0,t.jsxs)(M.xv,{className:"font-medium",children:["Team ",s+1]}),(0,t.jsx)(M.zx,{size:"sm",variant:"secondary",icon:q.Z,onClick:()=>a(s),className:"text-red-500 hover:text-red-700",children:"Remove"})]}),(0,t.jsxs)("div",{className:"grid grid-cols-1 md:grid-cols-3 gap-3",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)(M.xv,{className:"text-sm font-medium mb-1",children:"Team ID"}),(0,t.jsx)(M.oi,{value:e.team_id,onChange:e=>l(s,"team_id",e.target.value),placeholder:"Enter team ID"})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(M.xv,{className:"text-sm font-medium mb-1",children:"Max Budget in Team"}),(0,t.jsx)(v.Z,{style:{width:"100%"},value:e.max_budget_in_team,onChange:e=>l(s,"max_budget_in_team",e),placeholder:"Optional",min:0,step:.01,precision:2})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(M.xv,{className:"text-sm font-medium mb-1",children:"User Role"}),(0,t.jsxs)(g.default,{style:{width:"100%"},value:e.user_role,onChange:e=>l(s,"user_role",e),children:[(0,t.jsx)(w,{value:"user",children:"User"}),(0,t.jsx)(w,{value:"admin",children:"Admin"})]})]})]})]},s)),(0,t.jsx)(M.zx,{variant:"secondary",icon:G.Z,onClick:()=>{I("teams",[...s,{team_id:"",user_role:"user"}])},className:"w-full",children:"Add Team"})]})},A=(e,s,l)=>{var i;let r=s.type;if("teams"===e)return(0,t.jsx)("div",{className:"mt-2",children:z(p[e]||[])});if("user_role"===e&&a)return(0,t.jsx)(g.default,{style:{width:"100%"},value:p[e]||"",onChange:s=>I(e,s),className:"mt-2",children:Object.entries(a).filter(e=>{let[s]=e;return s.includes("internal_user")}).map(e=>{let[s,{ui_label:l,description:a}]=e;return(0,t.jsx)(w,{value:s,children:(0,t.jsxs)("div",{className:"flex items-center",children:[(0,t.jsx)("span",{children:l}),(0,t.jsx)("span",{className:"ml-2 text-xs text-gray-500",children:a})]})},s)})});if("budget_duration"===e)return(0,t.jsx)(k.Z,{value:p[e]||null,onChange:s=>I(e,s),className:"mt-2"});if("boolean"===r)return(0,t.jsx)("div",{className:"mt-2",children:(0,t.jsx)(K.Z,{checked:!!p[e],onChange:s=>I(e,s)})});if("array"===r&&(null===(i=s.items)||void 0===i?void 0:i.enum))return(0,t.jsx)(g.default,{mode:"multiple",style:{width:"100%"},value:p[e]||[],onChange:s=>I(e,s),className:"mt-2",children:s.items.enum.map(e=>(0,t.jsx)(w,{value:e,children:e},e))});if("models"===e)return(0,t.jsxs)(g.default,{mode:"multiple",style:{width:"100%"},value:p[e]||[],onChange:s=>I(e,s),className:"mt-2",children:[(0,t.jsx)(w,{value:"no-default-models",children:"No Default Models"},"no-default-models"),(0,t.jsx)(w,{value:"all-proxy-models",children:"All Proxy Models"},"all-proxy-models"),b.map(e=>(0,t.jsx)(w,{value:e,children:(0,S.W0)(e)},e))]});if("string"===r&&s.enum)return(0,t.jsx)(g.default,{style:{width:"100%"},value:p[e]||"",onChange:s=>I(e,s),className:"mt-2",children:s.enum.map(e=>(0,t.jsx)(w,{value:e,children:e},e))});else return(0,t.jsx)(M.oi,{value:void 0!==p[e]?String(p[e]):"",onChange:s=>I(e,s.target.value),placeholder:s.description||"",className:"mt-2"})},B=(e,s)=>{if(null==s)return(0,t.jsx)("span",{className:"text-gray-400",children:"Not set"});if("teams"===e&&Array.isArray(s)){if(0===s.length)return(0,t.jsx)("span",{className:"text-gray-400",children:"No teams assigned"});let e=D(s);return(0,t.jsx)("div",{className:"space-y-2 mt-1",children:e.map((e,s)=>(0,t.jsx)("div",{className:"border rounded-lg p-3 bg-white",children:(0,t.jsxs)("div",{className:"grid grid-cols-1 md:grid-cols-3 gap-2 text-sm",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)("span",{className:"font-medium text-gray-600",children:"Team ID:"}),(0,t.jsx)("p",{className:"text-gray-900",children:e.team_id||"Not specified"})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("span",{className:"font-medium text-gray-600",children:"Max Budget:"}),(0,t.jsx)("p",{className:"text-gray-900",children:void 0!==e.max_budget_in_team?"$".concat((0,O.pw)(e.max_budget_in_team,4)):"No limit"})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)("span",{className:"font-medium text-gray-600",children:"Role:"}),(0,t.jsx)("p",{className:"text-gray-900 capitalize",children:e.user_role})]})]})},s))})}if("user_role"===e&&a&&a[s]){let{ui_label:e,description:l}=a[s];return(0,t.jsxs)("div",{children:[(0,t.jsx)("span",{className:"font-medium",children:e}),l&&(0,t.jsx)("p",{className:"text-xs text-gray-500 mt-1",children:l})]})}return"budget_duration"===e?(0,t.jsx)("span",{children:(0,k.m)(s)}):"boolean"==typeof s?(0,t.jsx)("span",{children:s?"Enabled":"Disabled"}):"models"===e&&Array.isArray(s)?0===s.length?(0,t.jsx)("span",{className:"text-gray-400",children:"None"}):(0,t.jsx)("div",{className:"flex flex-wrap gap-2 mt-1",children:s.map((e,s)=>(0,t.jsx)("span",{className:"px-2 py-1 bg-blue-100 rounded text-xs",children:(0,S.W0)(e)},s))}):"object"==typeof s?Array.isArray(s)?0===s.length?(0,t.jsx)("span",{className:"text-gray-400",children:"None"}):(0,t.jsx)("div",{className:"flex flex-wrap gap-2 mt-1",children:s.map((e,s)=>(0,t.jsx)("span",{className:"px-2 py-1 bg-blue-100 rounded text-xs",children:"object"==typeof e?JSON.stringify(e):String(e)},s))}):(0,t.jsx)("pre",{className:"bg-gray-100 p-2 rounded text-xs overflow-auto mt-1",children:JSON.stringify(s,null,2)}):(0,t.jsx)("span",{children:String(s)})};return o?(0,t.jsx)("div",{className:"flex justify-center items-center h-64",children:(0,t.jsx)(V.Z,{size:"large"})}):u?(0,t.jsxs)(M.Zb,{children:[(0,t.jsxs)("div",{className:"flex justify-between items-center mb-4",children:[(0,t.jsx)(M.Dx,{children:"Default User Settings"}),!o&&u&&(x?(0,t.jsxs)("div",{className:"flex gap-2",children:[(0,t.jsx)(M.zx,{variant:"secondary",onClick:()=>{h(!1),f(u.values||{})},disabled:y,children:"Cancel"}),(0,t.jsx)(M.zx,{onClick:C,loading:y,children:"Save Changes"})]}):(0,t.jsx)(M.zx,{onClick:()=>h(!0),children:"Edit Settings"}))]}),(null==u?void 0:null===(s=u.field_schema)||void 0===s?void 0:s.description)&&(0,t.jsx)(Z,{className:"mb-4",children:u.field_schema.description}),(0,t.jsx)(M.iz,{}),(0,t.jsx)("div",{className:"mt-4 space-y-4",children:(()=>{let{values:e,field_schema:s}=u;return s&&s.properties?Object.entries(s.properties).map(s=>{let[l,a]=s,i=e[l],r=l.replace(/_/g," ").replace(/\b\w/g,e=>e.toUpperCase());return(0,t.jsxs)("div",{className:"mb-6 pb-6 border-b border-gray-200 last:border-0",children:[(0,t.jsx)(M.xv,{className:"font-medium text-lg",children:r}),(0,t.jsx)(Z,{className:"text-sm text-gray-500 mt-1",children:a.description||"No description available"}),x?(0,t.jsx)("div",{className:"mt-2",children:A(l,a,i)}):(0,t.jsx)("div",{className:"mt-1 p-2 bg-gray-50 rounded",children:B(l,i)})]},l)}):(0,t.jsx)(M.xv,{children:"No schema information available"})})()})]}):(0,t.jsx)(M.Zb,{children:(0,t.jsx)(M.xv,{children:"No settings available or you do not have permission to view them."})})},Q=l(41649),H=l(67101),$=l(47323),W=l(15731),Y=l(53410),X=l(74998),ee=l(23628);let es=(e,s,l,a,i,r)=>{let n=[{header:"User ID",accessorKey:"user_id",enableSorting:!0,cell:e=>{let{row:s}=e;return(0,t.jsx)(f.Z,{title:s.original.user_id,children:(0,t.jsx)("span",{className:"text-xs",children:s.original.user_id?"".concat(s.original.user_id.slice(0,7),"..."):"-"})})}},{header:"Email",accessorKey:"user_email",enableSorting:!0,cell:e=>{let{row:s}=e;return(0,t.jsx)("span",{className:"text-xs",children:s.original.user_email||"-"})}},{header:"Global Proxy Role",accessorKey:"user_role",enableSorting:!0,cell:s=>{var l;let{row:a}=s;return(0,t.jsx)("span",{className:"text-xs",children:(null==e?void 0:null===(l=e[a.original.user_role])||void 0===l?void 0:l.ui_label)||"-"})}},{header:"User Alias",accessorKey:"user_alias",enableSorting:!1,cell:e=>{let{row:s}=e;return(0,t.jsx)("span",{className:"text-xs",children:s.original.user_alias||"-"})}},{header:"Spend (USD)",accessorKey:"spend",enableSorting:!0,cell:e=>{let{row:s}=e;return(0,t.jsx)("span",{className:"text-xs",children:s.original.spend?(0,O.pw)(s.original.spend,4):"-"})}},{header:"Budget (USD)",accessorKey:"max_budget",enableSorting:!1,cell:e=>{let{row:s}=e;return(0,t.jsx)("span",{className:"text-xs",children:null!==s.original.max_budget?s.original.max_budget:"Unlimited"})}},{header:()=>(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{children:"SSO ID"}),(0,t.jsx)(f.Z,{title:"SSO ID is the ID of the user in the SSO provider. If the user is not using SSO, this will be null.",children:(0,t.jsx)(W.Z,{className:"w-4 h-4"})})]}),accessorKey:"sso_user_id",enableSorting:!1,cell:e=>{let{row:s}=e;return(0,t.jsx)("span",{className:"text-xs",children:null!==s.original.sso_user_id?s.original.sso_user_id:"-"})}},{header:"Virtual Keys",accessorKey:"key_count",enableSorting:!1,cell:e=>{let{row:s}=e;return(0,t.jsx)(H.Z,{numItems:2,children:s.original.key_count>0?(0,t.jsxs)(Q.Z,{size:"xs",color:"indigo",children:[s.original.key_count," ",1===s.original.key_count?"Key":"Keys"]}):(0,t.jsx)(Q.Z,{size:"xs",color:"gray",children:"No Keys"})})}},{header:"Created At",accessorKey:"created_at",enableSorting:!0,cell:e=>{let{row:s}=e;return(0,t.jsx)("span",{className:"text-xs",children:s.original.created_at?new Date(s.original.created_at).toLocaleDateString():"-"})}},{header:"Updated At",accessorKey:"updated_at",enableSorting:!1,cell:e=>{let{row:s}=e;return(0,t.jsx)("span",{className:"text-xs",children:s.original.updated_at?new Date(s.original.updated_at).toLocaleDateString():"-"})}},{id:"actions",header:"Actions",enableSorting:!1,cell:e=>{let{row:s}=e;return(0,t.jsxs)("div",{className:"flex gap-2",children:[(0,t.jsx)(f.Z,{title:"Edit user details",children:(0,t.jsx)($.Z,{icon:Y.Z,size:"sm",onClick:()=>i(s.original.user_id,!0),className:"cursor-pointer hover:text-blue-600"})}),(0,t.jsx)(f.Z,{title:"Delete user",children:(0,t.jsx)($.Z,{icon:X.Z,size:"sm",onClick:()=>l(s.original),className:"cursor-pointer hover:text-red-600"})}),(0,t.jsx)(f.Z,{title:"Reset Password",children:(0,t.jsx)($.Z,{icon:ee.Z,size:"sm",onClick:()=>a(s.original.user_id),className:"cursor-pointer hover:text-green-600"})})]})}}];if(r){let{onSelectUser:e,onSelectAll:s,isUserSelected:l,isAllSelected:a,isIndeterminate:i}=r;return[{id:"select",enableSorting:!1,header:()=>(0,t.jsx)(c.Z,{indeterminate:i,checked:a,onChange:e=>s(e.target.checked),onClick:e=>e.stopPropagation()}),cell:s=>{let{row:a}=s;return(0,t.jsx)(c.Z,{checked:l(a.original),onChange:s=>e(a.original,s.target.checked),onClick:e=>e.stopPropagation()})}},...n]}return n};var el=l(71594),et=l(24525),ea=l(27281),ei=l(21626),er=l(97214),en=l(28241),ed=l(58834),eo=l(69552),ec=l(71876),eu=l(44633),em=l(86462),ex=l(49084),eh=l(50337),eg=l(84717),ev=l(10900),ej=l(30401),ep=l(78867);function ef(e){var s,l,a,r,n,d,o,c,u,m,x,h,g,v,p,f,y,_,b,N,S,w,I,D,z,A,E,L,F,R,M,K,V,q,G,J,Q,H,$,W,Y,es,el,et,ea;let{userId:ei,onClose:er,accessToken:en,userRole:ed,onDelete:eo,possibleUIRoles:ec,initialTab:eu=0,startInEditMode:em=!1}=e,[ex,eh]=(0,i.useState)(null),[ef,ey]=(0,i.useState)(!1),[e_,eb]=(0,i.useState)(!1),[eN,eS]=(0,i.useState)(!0),[eZ,ew]=(0,i.useState)(em),[ek,eC]=(0,i.useState)([]),[eU,eI]=(0,i.useState)(!1),[eD,ez]=(0,i.useState)(null),[eA,eB]=(0,i.useState)(null),[eE,eT]=(0,i.useState)(eu),[eO,eL]=(0,i.useState)({}),[eF,eR]=(0,i.useState)(!1);i.useEffect(()=>{eB((0,j.getProxyBaseUrl)())},[]),i.useEffect(()=>{console.log("userId: ".concat(ei,", userRole: ").concat(ed,", accessToken: ").concat(en)),(async()=>{try{if(!en)return;let e=await (0,j.userInfoCall)(en,ei,ed||"",!1,null,null,!0);eh(e);let s=(await (0,j.modelAvailableCall)(en,ei,ed||"")).data.map(e=>e.id);eC(s)}catch(e){console.error("Error fetching user data:",e),U.Z.fromBackend("Failed to fetch user data")}finally{eS(!1)}})()},[en,ei,ed]);let eP=async()=>{if(!en){U.Z.fromBackend("Access token not found");return}try{U.Z.success("Generating password reset link...");let e=await (0,j.invitationCreateCall)(en,ei);ez(e),eI(!0)}catch(e){U.Z.fromBackend("Failed to generate password reset link")}},eM=async()=>{try{if(!en)return;eb(!0),await (0,j.userDeleteCall)(en,[ei]),U.Z.success("User deleted successfully"),eo&&eo(),er()}catch(e){console.error("Error deleting user:",e),U.Z.fromBackend("Failed to delete user")}finally{ey(!1),eb(!1)}},eK=async e=>{try{if(!en||!ex)return;await (0,j.userUpdateUserCall)(en,e,null),eh({...ex,user_info:{...ex.user_info,user_email:e.user_email,user_alias:e.user_alias,models:e.models,max_budget:e.max_budget,budget_duration:e.budget_duration,metadata:e.metadata}}),U.Z.success("User updated successfully"),ew(!1)}catch(e){console.error("Error updating user:",e),U.Z.fromBackend("Failed to update user")}};if(eN)return(0,t.jsxs)("div",{className:"p-4",children:[(0,t.jsx)(eg.zx,{icon:ev.Z,variant:"light",onClick:er,className:"mb-4",children:"Back to Users"}),(0,t.jsx)(eg.xv,{children:"Loading user data..."})]});if(!ex)return(0,t.jsxs)("div",{className:"p-4",children:[(0,t.jsx)(eg.zx,{icon:ev.Z,variant:"light",onClick:er,className:"mb-4",children:"Back to Users"}),(0,t.jsx)(eg.xv,{children:"User not found"})]});let eV=async(e,s)=>{await (0,O.vQ)(e)&&(eL(e=>({...e,[s]:!0})),setTimeout(()=>{eL(e=>({...e,[s]:!1}))},2e3))};return(0,t.jsxs)("div",{className:"p-4",children:[(0,t.jsxs)("div",{className:"flex justify-between items-center mb-6",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)(eg.zx,{icon:ev.Z,variant:"light",onClick:er,className:"mb-4",children:"Back to Users"}),(0,t.jsx)(eg.Dx,{children:(null===(s=ex.user_info)||void 0===s?void 0:s.user_email)||"User"}),(0,t.jsxs)("div",{className:"flex items-center cursor-pointer",children:[(0,t.jsx)(eg.xv,{className:"text-gray-500 font-mono",children:ex.user_id}),(0,t.jsx)(B.ZP,{type:"text",size:"small",icon:eO["user-id"]?(0,t.jsx)(ej.Z,{size:12}):(0,t.jsx)(ep.Z,{size:12}),onClick:()=>eV(ex.user_id,"user-id"),className:"left-2 z-10 transition-all duration-200 ".concat(eO["user-id"]?"text-green-600 bg-green-50 border-green-200":"text-gray-500 hover:text-gray-700 hover:bg-gray-100")})]})]}),ed&&Z.LQ.includes(ed)&&(0,t.jsxs)("div",{className:"flex items-center space-x-2",children:[(0,t.jsx)(eg.zx,{icon:ee.Z,variant:"secondary",onClick:eP,className:"flex items-center",children:"Reset Password"}),(0,t.jsx)(eg.zx,{icon:X.Z,variant:"secondary",onClick:()=>ey(!0),className:"flex items-center text-red-500 border-red-500 hover:text-red-600 hover:border-red-600",children:"Delete User"})]})]}),(0,t.jsx)(P.Z,{isOpen:ef,title:"Delete User?",message:"Are you sure you want to delete this user? This action cannot be undone.",resourceInformationTitle:"User Information",resourceInformation:[{label:"Email",value:null===(l=ex.user_info)||void 0===l?void 0:l.user_email},{label:"User ID",value:ex.user_id,code:!0},{label:"Global Proxy Role",value:(null===(a=ex.user_info)||void 0===a?void 0:a.user_role)&&(null==ec?void 0:null===(r=ec[ex.user_info.user_role])||void 0===r?void 0:r.ui_label)||(null===(n=ex.user_info)||void 0===n?void 0:n.user_role)||"-"},{label:"Total Spend (USD)",value:(null===(d=ex.user_info)||void 0===d?void 0:d.spend)!==null&&(null===(o=ex.user_info)||void 0===o?void 0:o.spend)!==void 0?ex.user_info.spend.toFixed(2):void 0}],onCancel:()=>{ey(!1)},onOk:eM,confirmLoading:e_}),(0,t.jsxs)(eg.v0,{defaultIndex:eE,onIndexChange:eT,children:[(0,t.jsxs)(eg.td,{className:"mb-4",children:[(0,t.jsx)(eg.OK,{children:"Overview"}),(0,t.jsx)(eg.OK,{children:"Details"})]}),(0,t.jsxs)(eg.nP,{children:[(0,t.jsx)(eg.x4,{children:(0,t.jsxs)(eg.rj,{numItems:1,numItemsSm:2,numItemsLg:3,className:"gap-6",children:[(0,t.jsxs)(eg.Zb,{children:[(0,t.jsx)(eg.xv,{children:"Spend"}),(0,t.jsxs)("div",{className:"mt-2",children:[(0,t.jsxs)(eg.Dx,{children:["$",(0,O.pw)((null===(c=ex.user_info)||void 0===c?void 0:c.spend)||0,4)]}),(0,t.jsxs)(eg.xv,{children:["of"," ",(null===(u=ex.user_info)||void 0===u?void 0:u.max_budget)!==null?"$".concat((0,O.pw)(ex.user_info.max_budget,4)):"Unlimited"]})]})]}),(0,t.jsxs)(eg.Zb,{children:[(0,t.jsx)(eg.xv,{children:"Teams"}),(0,t.jsx)("div",{className:"mt-2",children:(null===(m=ex.teams)||void 0===m?void 0:m.length)&&(null===(x=ex.teams)||void 0===x?void 0:x.length)>0?(0,t.jsxs)("div",{className:"flex flex-wrap gap-2",children:[null===(h=ex.teams)||void 0===h?void 0:h.slice(0,eF?ex.teams.length:20).map((e,s)=>(0,t.jsx)(eg.Ct,{color:"blue",title:e.team_alias,children:e.team_alias},s)),!eF&&(null===(g=ex.teams)||void 0===g?void 0:g.length)>20&&(0,t.jsxs)(eg.Ct,{color:"gray",className:"cursor-pointer hover:bg-gray-200 transition-colors",onClick:()=>eR(!0),children:["+",ex.teams.length-20," more"]}),eF&&(null===(v=ex.teams)||void 0===v?void 0:v.length)>20&&(0,t.jsx)(eg.Ct,{color:"gray",className:"cursor-pointer hover:bg-gray-200 transition-colors",onClick:()=>eR(!1),children:"Show Less"})]}):(0,t.jsx)(eg.xv,{children:"No teams"})})]}),(0,t.jsxs)(eg.Zb,{children:[(0,t.jsx)(eg.xv,{children:"Virtual Keys"}),(0,t.jsx)("div",{className:"mt-2",children:(0,t.jsxs)(eg.xv,{children:[(null===(p=ex.keys)||void 0===p?void 0:p.length)||0," ",(null===(f=ex.keys)||void 0===f?void 0:f.length)===1?"Key":"Keys"]})})]}),(0,t.jsxs)(eg.Zb,{children:[(0,t.jsx)(eg.xv,{children:"Personal Models"}),(0,t.jsx)("div",{className:"mt-2",children:(null===(_=ex.user_info)||void 0===_?void 0:null===(y=_.models)||void 0===y?void 0:y.length)&&(null===(N=ex.user_info)||void 0===N?void 0:null===(b=N.models)||void 0===b?void 0:b.length)>0?null===(w=ex.user_info)||void 0===w?void 0:null===(S=w.models)||void 0===S?void 0:S.map((e,s)=>(0,t.jsx)(eg.xv,{children:e},s)):(0,t.jsx)(eg.xv,{children:"All proxy models"})})]})]})}),(0,t.jsx)(eg.x4,{children:(0,t.jsxs)(eg.Zb,{children:[(0,t.jsxs)("div",{className:"flex justify-between items-center mb-4",children:[(0,t.jsx)(eg.Dx,{children:"User Settings"}),!eZ&&ed&&Z.LQ.includes(ed)&&(0,t.jsx)(eg.zx,{onClick:()=>ew(!0),children:"Edit Settings"})]}),eZ&&ex?(0,t.jsx)(C,{userData:ex,onCancel:()=>ew(!1),onSubmit:eK,teams:ex.teams,accessToken:en,userID:ei,userRole:ed,userModels:ek,possibleUIRoles:ec}):(0,t.jsxs)("div",{className:"space-y-4",children:[(0,t.jsxs)("div",{children:[(0,t.jsx)(eg.xv,{className:"font-medium",children:"User ID"}),(0,t.jsxs)("div",{className:"flex items-center cursor-pointer",children:[(0,t.jsx)(eg.xv,{className:"font-mono",children:ex.user_id}),(0,t.jsx)(B.ZP,{type:"text",size:"small",icon:eO["user-id"]?(0,t.jsx)(ej.Z,{size:12}):(0,t.jsx)(ep.Z,{size:12}),onClick:()=>eV(ex.user_id,"user-id"),className:"left-2 z-10 transition-all duration-200 ".concat(eO["user-id"]?"text-green-600 bg-green-50 border-green-200":"text-gray-500 hover:text-gray-700 hover:bg-gray-100")})]})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(eg.xv,{className:"font-medium",children:"Email"}),(0,t.jsx)(eg.xv,{children:(null===(I=ex.user_info)||void 0===I?void 0:I.user_email)||"Not Set"})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(eg.xv,{className:"font-medium",children:"User Alias"}),(0,t.jsx)(eg.xv,{children:(null===(D=ex.user_info)||void 0===D?void 0:D.user_alias)||"Not Set"})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(eg.xv,{className:"font-medium",children:"Global Proxy Role"}),(0,t.jsx)(eg.xv,{children:(null===(z=ex.user_info)||void 0===z?void 0:z.user_role)||"Not Set"})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(eg.xv,{className:"font-medium",children:"Created"}),(0,t.jsx)(eg.xv,{children:(null===(A=ex.user_info)||void 0===A?void 0:A.created_at)?new Date(ex.user_info.created_at).toLocaleString():"Unknown"})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(eg.xv,{className:"font-medium",children:"Last Updated"}),(0,t.jsx)(eg.xv,{children:(null===(E=ex.user_info)||void 0===E?void 0:E.updated_at)?new Date(ex.user_info.updated_at).toLocaleString():"Unknown"})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(eg.xv,{className:"font-medium",children:"Teams"}),(0,t.jsx)("div",{className:"flex flex-wrap gap-2 mt-1",children:(null===(L=ex.teams)||void 0===L?void 0:L.length)&&(null===(F=ex.teams)||void 0===F?void 0:F.length)>0?(0,t.jsxs)(t.Fragment,{children:[null===(R=ex.teams)||void 0===R?void 0:R.slice(0,eF?ex.teams.length:20).map((e,s)=>(0,t.jsx)("span",{className:"px-2 py-1 bg-blue-100 rounded text-xs",title:e.team_alias||e.team_id,children:e.team_alias||e.team_id},s)),!eF&&(null===(M=ex.teams)||void 0===M?void 0:M.length)>20&&(0,t.jsxs)("span",{className:"px-2 py-1 bg-gray-100 rounded text-xs cursor-pointer hover:bg-gray-200 transition-colors",onClick:()=>eR(!0),children:["+",ex.teams.length-20," more"]}),eF&&(null===(K=ex.teams)||void 0===K?void 0:K.length)>20&&(0,t.jsx)("span",{className:"px-2 py-1 bg-gray-100 rounded text-xs cursor-pointer hover:bg-gray-200 transition-colors",onClick:()=>eR(!1),children:"Show Less"})]}):(0,t.jsx)(eg.xv,{children:"No teams"})})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(eg.xv,{className:"font-medium",children:"Personal Models"}),(0,t.jsx)("div",{className:"flex flex-wrap gap-2 mt-1",children:(null===(q=ex.user_info)||void 0===q?void 0:null===(V=q.models)||void 0===V?void 0:V.length)&&(null===(J=ex.user_info)||void 0===J?void 0:null===(G=J.models)||void 0===G?void 0:G.length)>0?null===(H=ex.user_info)||void 0===H?void 0:null===(Q=H.models)||void 0===Q?void 0:Q.map((e,s)=>(0,t.jsx)("span",{className:"px-2 py-1 bg-blue-100 rounded text-xs",children:e},s)):(0,t.jsx)(eg.xv,{children:"All proxy models"})})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(eg.xv,{className:"font-medium",children:"Virtual Keys"}),(0,t.jsx)("div",{className:"flex flex-wrap gap-2 mt-1",children:(null===($=ex.keys)||void 0===$?void 0:$.length)&&(null===(W=ex.keys)||void 0===W?void 0:W.length)>0?ex.keys.map((e,s)=>(0,t.jsx)("span",{className:"px-2 py-1 bg-green-100 rounded text-xs",children:e.key_alias||e.token},s)):(0,t.jsx)(eg.xv,{children:"No Virtual Keys"})})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(eg.xv,{className:"font-medium",children:"Max Budget"}),(0,t.jsx)(eg.xv,{children:(null===(Y=ex.user_info)||void 0===Y?void 0:Y.max_budget)!==null&&(null===(es=ex.user_info)||void 0===es?void 0:es.max_budget)!==void 0?"$".concat((0,O.pw)(ex.user_info.max_budget,4)):"Unlimited"})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(eg.xv,{className:"font-medium",children:"Budget Reset"}),(0,t.jsx)(eg.xv,{children:(0,k.m)(null!==(ea=null===(el=ex.user_info)||void 0===el?void 0:el.budget_duration)&&void 0!==ea?ea:null)})]}),(0,t.jsxs)("div",{children:[(0,t.jsx)(eg.xv,{className:"font-medium",children:"Metadata"}),(0,t.jsx)("pre",{className:"bg-gray-100 p-2 rounded text-xs overflow-auto mt-1",children:JSON.stringify((null===(et=ex.user_info)||void 0===et?void 0:et.metadata)||{},null,2)})]})]})]})})]})]}),(0,t.jsx)(T.Z,{isInvitationLinkModalVisible:eU,setIsInvitationLinkModalVisible:eI,baseUrl:eA||"",invitationLinkData:eD,modalType:"resetPassword"})]})}var ey=l(56083),e_=l(51205),eb=l(57716),eN=l(73247),eS=l(92369),eZ=l(66344);function ew(e){let{data:s=[],columns:l,isLoading:a=!1,onSortChange:r,currentSort:n,accessToken:d,userRole:o,possibleUIRoles:c,handleEdit:u,handleDelete:m,handleResetPassword:x,selectedUsers:h=[],onSelectionChange:g,enableSelection:v=!1,filters:j,updateFilters:p,initialFilters:f,teams:y,userListResponse:b,currentPage:N,handlePageChange:S}=e,[Z,w]=i.useState([{id:(null==n?void 0:n.sortBy)||"created_at",desc:(null==n?void 0:n.sortOrder)==="desc"}]),[k,C]=i.useState(null),[U,I]=i.useState(!1),[D,z]=i.useState(!1),A=function(e){let s=arguments.length>1&&void 0!==arguments[1]&&arguments[1];C(e),I(s)},B=(e,s)=>{g&&(s?g([...h,e]):g(h.filter(s=>s.user_id!==e.user_id)))},E=e=>{g&&(e?g(s):g([]))},T=e=>h.some(s=>s.user_id===e.user_id),O=s.length>0&&h.length===s.length,L=h.length>0&&h.lengthc?es(c,u,m,x,A,v?{selectedUsers:h,onSelectUser:B,onSelectAll:E,isUserSelected:T,isAllSelected:O,isIndeterminate:L}:void 0):l,[c,u,m,x,A,l,v,h,O,L]),R=(0,el.b7)({data:s,columns:F,state:{sorting:Z},onSortingChange:e=>{let s="function"==typeof e?e(Z):e;if(w(s),s&&Array.isArray(s)&&s.length>0&&s[0]){let e=s[0];if(e.id){let s=e.id,l=e.desc?"desc":"asc";null==r||r(s,l)}}else null==r||r("created_at","desc")},getCoreRowModel:(0,et.sC)(),manualSorting:!0,enableSorting:!0});return(i.useEffect(()=>{n&&w([{id:n.sortBy,desc:"desc"===n.sortOrder}])},[n]),k)?(0,t.jsx)(ef,{userId:k,onClose:()=>{C(null),I(!1)},accessToken:d,userRole:o,possibleUIRoles:c,initialTab:U?1:0,startInEditMode:U}):(0,t.jsxs)("div",{className:"bg-white rounded-lg shadow",children:[(0,t.jsx)("div",{className:"border-b px-6 py-4",children:(0,t.jsxs)("div",{className:"flex flex-col space-y-4",children:[(0,t.jsxs)("div",{className:"flex flex-wrap items-center gap-3",children:[(0,t.jsx)(ey.H,{placeholder:"Search by email...",value:j.email,onChange:e=>p({email:e}),icon:eN.Z}),(0,t.jsx)(e_.c,{onClick:()=>z(!D),active:D,hasActiveFilters:!!(j.user_id||j.user_role||j.team)}),(0,t.jsx)(eb.z,{onClick:()=>{p(f)}})]}),D&&(0,t.jsxs)("div",{className:"flex flex-wrap items-center gap-3 mt-3",children:[(0,t.jsx)(ey.H,{placeholder:"Filter by User ID",value:j.user_id,onChange:e=>p({user_id:e}),icon:eS.Z}),(0,t.jsx)(ey.H,{placeholder:"Filter by SSO ID",value:j.sso_user_id,onChange:e=>p({sso_user_id:e}),icon:eZ.Z}),(0,t.jsx)("div",{className:"w-64",children:(0,t.jsx)(ea.Z,{value:j.user_role,onValueChange:e=>p({user_role:e}),placeholder:"Select Role",children:c&&Object.entries(c).map(e=>{let[s,l]=e;return(0,t.jsx)(_.Z,{value:s,children:l.ui_label},s)})})}),(0,t.jsx)("div",{className:"w-64",children:(0,t.jsx)(ea.Z,{value:j.team,onValueChange:e=>p({team:e}),placeholder:"Select Team",children:null==y?void 0:y.map(e=>(0,t.jsx)(_.Z,{value:e.team_id,children:e.team_alias||e.team_id},e.team_id))})})]}),(0,t.jsxs)("div",{className:"flex justify-between items-center",children:[a?(0,t.jsx)(eh.Z.Input,{active:!0,style:{width:192,height:20}}):(0,t.jsxs)("span",{className:"text-sm text-gray-700",children:["Showing"," ",b&&b.users&&b.users.length>0?(b.page-1)*b.page_size+1:0," ","-"," ",b&&b.users?Math.min(b.page*b.page_size,b.total):0," ","of ",b?b.total:0," results"]}),(0,t.jsx)("div",{className:"flex space-x-2",children:a?(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(eh.Z.Button,{active:!0,size:"small",style:{width:80,height:30}}),(0,t.jsx)(eh.Z.Button,{active:!0,size:"small",style:{width:60,height:30}})]}):(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)("button",{onClick:()=>S(N-1),disabled:1===N,className:"px-3 py-1 text-sm border rounded-md ".concat(1===N?"bg-gray-100 text-gray-400 cursor-not-allowed":"hover:bg-gray-50"),children:"Previous"}),(0,t.jsx)("button",{onClick:()=>S(N+1),disabled:!b||N>=b.total_pages,className:"px-3 py-1 text-sm border rounded-md ".concat(!b||N>=b.total_pages?"bg-gray-100 text-gray-400 cursor-not-allowed":"hover:bg-gray-50"),children:"Next"})]})})]})]})}),(0,t.jsx)("div",{className:"overflow-auto",children:(0,t.jsx)("div",{className:"rounded-lg custom-border relative",children:(0,t.jsx)("div",{className:"overflow-x-auto",children:(0,t.jsxs)(ei.Z,{className:"[&_td]:py-0.5 [&_th]:py-1",children:[(0,t.jsx)(ed.Z,{children:R.getHeaderGroups().map(e=>(0,t.jsx)(ec.Z,{children:e.headers.map(e=>(0,t.jsx)(eo.Z,{className:"py-1 h-8 ".concat("actions"===e.id?"sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]":""," ").concat(e.column.getCanSort()?"cursor-pointer hover:bg-gray-50":""),onClick:e.column.getToggleSortingHandler(),children:(0,t.jsxs)("div",{className:"flex items-center justify-between gap-2",children:[(0,t.jsx)("div",{className:"flex items-center",children:e.isPlaceholder?null:(0,el.ie)(e.column.columnDef.header,e.getContext())}),"actions"!==e.id&&e.column.getCanSort()&&(0,t.jsx)("div",{className:"w-4",children:e.column.getIsSorted()?({asc:(0,t.jsx)(eu.Z,{className:"h-4 w-4 text-blue-500"}),desc:(0,t.jsx)(em.Z,{className:"h-4 w-4 text-blue-500"})})[e.column.getIsSorted()]:(0,t.jsx)(ex.Z,{className:"h-4 w-4 text-gray-400"})})]})},e.id))},e.id))}),(0,t.jsx)(er.Z,{children:a?(0,t.jsx)(ec.Z,{children:(0,t.jsx)(en.Z,{colSpan:F.length,className:"h-8 text-center",children:(0,t.jsx)("div",{className:"text-center text-gray-500",children:(0,t.jsx)("p",{children:"\uD83D\uDE85 Loading users..."})})})}):s.length>0?R.getRowModel().rows.map(e=>(0,t.jsx)(ec.Z,{className:"h-8",children:e.getVisibleCells().map(e=>(0,t.jsx)(en.Z,{className:"py-0.5 max-h-8 overflow-hidden text-ellipsis whitespace-nowrap ".concat("actions"===e.column.id?"sticky right-0 bg-white shadow-[-4px_0_8px_-6px_rgba(0,0,0,0.1)]":""),onClick:()=>{"user_id"===e.column.id&&A(e.getValue(),!1)},style:{cursor:"user_id"===e.column.id?"pointer":"default",color:"user_id"===e.column.id?"#3b82f6":"inherit"},children:(0,el.ie)(e.column.columnDef.cell,e.getContext())},e.id))},e.id)):(0,t.jsx)(ec.Z,{children:(0,t.jsx)(en.Z,{colSpan:F.length,className:"h-8 text-center",children:(0,t.jsx)("div",{className:"text-center text-gray-500",children:(0,t.jsx)("p",{children:"No users found"})})})})})]})})})})]})}let{Text:ek,Title:eC}=n.default,eU={email:"",user_id:"",user_role:"",sso_user_id:"",team:"",model:"",min_spend:null,max_spend:null,sort_by:"created_at",sort_order:"desc"};var eI=e=>{var s,l,n;let{accessToken:d,token:o,userRole:c,userID:u,teams:m}=e,x=(0,F.NL)(),[h,g]=(0,i.useState)(1),[v,p]=(0,i.useState)(!1),[f,y]=(0,i.useState)(null),[_,b]=(0,i.useState)(!1),[N,S]=(0,i.useState)(!1),[w,k]=(0,i.useState)(null),[C,I]=(0,i.useState)("users"),[D,B]=(0,i.useState)(eU),[M,K,V]=(0,L.G)(D,{wait:300}),[q,G]=(0,i.useState)(!1),[Q,H]=(0,i.useState)(null),[$,W]=(0,i.useState)(null),[Y,X]=(0,i.useState)([]),[ee,el]=(0,i.useState)(!1),[et,ea]=(0,i.useState)(!1),[ei,er]=(0,i.useState)([]),en=e=>{k(e),b(!0)};(0,i.useEffect)(()=>()=>{V.cancel()},[V]),(0,i.useEffect)(()=>{W((0,j.getProxyBaseUrl)())},[]),(0,i.useEffect)(()=>{(async()=>{try{if(!u||!c||!d)return;let e=(await (0,j.modelAvailableCall)(d,u,c)).data.map(e=>e.id);console.log("available_model_names:",e),er(e)}catch(e){console.error("Error fetching user models:",e)}})()},[d,u,c]);let ed=e=>{B(s=>{let l={...s,...e};return K(l),l})},eo=async e=>{if(!d){U.Z.fromBackend("Access token not found");return}try{U.Z.success("Generating password reset link...");let s=await (0,j.invitationCreateCall)(d,e);H(s),G(!0)}catch(e){U.Z.fromBackend("Failed to generate password reset link")}},ec=async()=>{if(w&&d)try{S(!0),await (0,j.userDeleteCall)(d,[w.user_id]),x.setQueriesData({queryKey:["userList"]},e=>{if(void 0===e)return e;let s=e.users.filter(e=>e.user_id!==w.user_id);return{...e,users:s}}),U.Z.success("User deleted successfully")}catch(e){console.error("Error deleting user:",e),U.Z.fromBackend("Failed to delete user")}finally{b(!1),k(null),S(!1)}},eu=async()=>{y(null),p(!1)},em=async e=>{if(console.log("inside handleEditSubmit:",e),d&&o&&c&&u){try{let s=await (0,j.userUpdateUserCall)(d,e,null);x.setQueriesData({queryKey:["userList"]},e=>{if(void 0===e)return e;let l=e.users.map(e=>e.user_id===s.data.user_id?(0,O.nl)(e,s.data):e);return{...e,users:l}}),U.Z.success("User ".concat(e.user_id," updated successfully"))}catch(e){console.error("There was an error updating the user",e)}y(null),p(!1)}},ex=async e=>{g(e)},eg=(0,R.a)({queryKey:["userList",{debouncedFilter:M,currentPage:h}],queryFn:async()=>{if(!d)throw Error("Access token required");return await (0,j.userListCall)(d,M.user_id?[M.user_id]:null,h,25,M.email||null,M.user_role||null,M.team||null,M.sso_user_id||null,M.sort_by,M.sort_order)},enabled:!!(d&&o&&c&&u),placeholderData:e=>e}),ev=eg.data,ej=(0,R.a)({queryKey:["userRoles"],initialData:()=>({}),queryFn:async()=>{if(!d)throw Error("Access token required");return await (0,j.getPossibleUserRoles)(d)},enabled:!!(d&&o&&c&&u)}).data,ep=es(ej,e=>{y(e),p(!0)},en,eo,()=>{});return(0,t.jsxs)("div",{className:"w-full p-8 overflow-hidden",children:[(0,t.jsx)("div",{className:"flex items-center justify-between mb-4",children:(0,t.jsx)("div",{className:"flex space-x-3",children:eg.isLoading?(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(eh.Z.Button,{active:!0,size:"default",shape:"default",style:{width:110,height:36}}),(0,t.jsx)(eh.Z.Button,{active:!0,size:"default",shape:"default",style:{width:145,height:36}}),(0,t.jsx)(eh.Z.Button,{active:!0,size:"default",shape:"default",style:{width:110,height:36}})]}):u&&d?(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)(A.Z,{userID:u,accessToken:d,teams:m,possibleUIRoles:ej}),(0,t.jsx)(r.z,{onClick:()=>{ea(!et),X([])},variant:et?"primary":"secondary",className:"flex items-center",children:et?"Cancel Selection":"Select Users"}),et&&(0,t.jsxs)(r.z,{onClick:()=>{if(0===Y.length){U.Z.fromBackend("Please select users to edit");return}el(!0)},disabled:0===Y.length,className:"flex items-center",children:["Bulk Edit (",Y.length," selected)"]})]}):null})}),(0,t.jsxs)(a.v0,{defaultIndex:0,onIndexChange:e=>I(0===e?"users":"settings"),children:[(0,t.jsxs)(a.td,{className:"mb-4",children:[(0,t.jsx)(a.OK,{children:"Users"}),(0,t.jsx)(a.OK,{children:"Default User Settings"})]}),(0,t.jsxs)(a.nP,{children:[(0,t.jsx)(a.x4,{children:(0,t.jsx)(ew,{data:(null===(s=eg.data)||void 0===s?void 0:s.users)||[],columns:ep,isLoading:eg.isLoading,accessToken:d,userRole:c,onSortChange:(e,s)=>{ed({sort_by:e,sort_order:s})},currentSort:{sortBy:D.sort_by,sortOrder:D.sort_order},possibleUIRoles:ej,handleEdit:e=>{y(e),p(!0)},handleDelete:en,handleResetPassword:eo,enableSelection:et,selectedUsers:Y,onSelectionChange:e=>{X(e)},filters:D,updateFilters:ed,initialFilters:eU,teams:m,userListResponse:ev,currentPage:h,handlePageChange:ex})}),(0,t.jsx)(a.x4,{children:u&&c&&d?(0,t.jsx)(J,{accessToken:d,possibleUIRoles:ej,userID:u,userRole:c}):(0,t.jsx)("div",{className:"flex justify-center items-center h-64",children:(0,t.jsx)(eh.Z,{active:!0,paragraph:{rows:4}})})})]})]}),(0,t.jsx)(E,{visible:v,possibleUIRoles:ej,onCancel:eu,user:f,onSubmit:em}),(0,t.jsx)(P.Z,{isOpen:_,title:"Delete User?",message:"Are you sure you want to delete this user? This action cannot be undone.",resourceInformationTitle:"User Information",resourceInformation:[{label:"Email",value:null==w?void 0:w.user_email},{label:"User ID",value:null==w?void 0:w.user_id,code:!0},{label:"Global Proxy Role",value:w&&(null==ej?void 0:null===(l=ej[w.user_role])||void 0===l?void 0:l.ui_label)||(null==w?void 0:w.user_role)||"-"},{label:"Total Spend (USD)",value:null==w?void 0:null===(n=w.spend)||void 0===n?void 0:n.toFixed(2)}],onCancel:()=>{b(!1),k(null)},onOk:ec,confirmLoading:N}),(0,t.jsx)(T.Z,{isInvitationLinkModalVisible:q,setIsInvitationLinkModalVisible:G,baseUrl:$||"",invitationLinkData:Q,modalType:"resetPassword"}),(0,t.jsx)(z,{open:ee,onCancel:()=>el(!1),selectedUsers:Y,possibleUIRoles:ej,accessToken:d,onSuccess:()=>{x.invalidateQueries({queryKey:["userList"]}),X([]),ea(!1)},teams:m,userRole:c,userModels:ei,allowAllUsers:!!c&&(0,Z.tY)(c)})]})}}}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/2353-c94748c0aac514ff.js b/litellm/proxy/_experimental/out/_next/static/chunks/2353-c94748c0aac514ff.js new file mode 100644 index 00000000000..3426c548b25 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/2353-c94748c0aac514ff.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[2353],{57365:function(e,t,n){n.d(t,{Z:function(){return a}});var r=n(5853),o=n(2265),l=n(51975),i=n(13241);let u=(0,n(1153).fn)("SelectItem"),a=o.forwardRef((e,t)=>{let{value:n,icon:a,className:s,children:c}=e,d=(0,r._T)(e,["value","icon","className","children"]);return o.createElement(l.wt,Object.assign({className:(0,i.q)(u("root"),"flex justify-start items-center cursor-default text-tremor-default px-2.5 py-2.5","data-[focus]:bg-tremor-background-muted data-[focus]:text-tremor-content-strong data-[selected]:text-tremor-content-strong data-[selected]:bg-tremor-background-muted text-tremor-content-emphasis","dark:data-[focus]:bg-dark-tremor-background-muted dark:data-[focus]:text-dark-tremor-content-strong dark:data-[selected]:text-dark-tremor-content-strong dark:data-[selected]:bg-dark-tremor-background-muted dark:text-dark-tremor-content-emphasis",s),ref:t,key:n,value:n},d),a&&o.createElement(a,{className:(0,i.q)(u("icon"),"flex-none w-5 h-5 mr-1.5","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle")}),o.createElement("span",{className:"whitespace-nowrap truncate"},null!=c?c:n))});a.displayName="SelectItem"},44140:function(e,t,n){n.d(t,{Z:function(){return o}});var r=n(2265);let o=(e,t)=>{let n=void 0!==t,[o,l]=(0,r.useState)(e);return[n?t:o,e=>{n||l(e)}]}},64803:function(e,t,n){n.d(t,{RR:function(){return v},YF:function(){return d},cv:function(){return f},dp:function(){return m},uY:function(){return p}});var r=n(51050),o=n(2265),l=n(54887),i="undefined"!=typeof document?o.useLayoutEffect:function(){};function u(e,t){let n,r,o;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(!u(e[r],t[r]))return!1;return!0}if((n=(o=Object.keys(e)).length)!==Object.keys(t).length)return!1;for(r=n;0!=r--;)if(!({}).hasOwnProperty.call(t,o[r]))return!1;for(r=n;0!=r--;){let n=o[r];if(("_owner"!==n||!e.$$typeof)&&!u(e[n],t[n]))return!1}return!0}return e!=e&&t!=t}function a(e){return"undefined"==typeof window?1:(e.ownerDocument.defaultView||window).devicePixelRatio||1}function s(e,t){let n=a(e);return Math.round(t*n)/n}function c(e){let t=o.useRef(e);return i(()=>{t.current=e}),t}function d(e){void 0===e&&(e={});let{placement:t="bottom",strategy:n="absolute",middleware:d=[],platform:f,elements:{reference:p,floating:v}={},transform:m=!0,whileElementsMounted:g,open:h}=e,[b,x]=o.useState({x:0,y:0,strategy:n,placement:t,middlewareData:{},isPositioned:!1}),[E,y]=o.useState(d);u(E,d)||y(d);let[S,O]=o.useState(null),[R,w]=o.useState(null),C=o.useCallback(e=>{e!==k.current&&(k.current=e,O(e))},[]),P=o.useCallback(e=>{e!==M.current&&(M.current=e,w(e))},[]),L=p||S,T=v||R,k=o.useRef(null),M=o.useRef(null),I=o.useRef(b),F=null!=g,A=c(g),D=c(f),N=c(h),z=o.useCallback(()=>{if(!k.current||!M.current)return;let e={placement:t,strategy:n,middleware:E};D.current&&(e.platform=D.current),(0,r.oo)(k.current,M.current,e).then(e=>{let t={...e,isPositioned:!1!==N.current};H.current&&!u(I.current,t)&&(I.current=t,l.flushSync(()=>{x(t)}))})},[E,t,n,D,N]);i(()=>{!1===h&&I.current.isPositioned&&(I.current.isPositioned=!1,x(e=>({...e,isPositioned:!1})))},[h]);let H=o.useRef(!1);i(()=>(H.current=!0,()=>{H.current=!1}),[]),i(()=>{if(L&&(k.current=L),T&&(M.current=T),L&&T){if(A.current)return A.current(L,T,z);z()}},[L,T,z,A,F]);let _=o.useMemo(()=>({reference:k,floating:M,setReference:C,setFloating:P}),[C,P]),B=o.useMemo(()=>({reference:L,floating:T}),[L,T]),V=o.useMemo(()=>{let e={position:n,left:0,top:0};if(!B.floating)return e;let t=s(B.floating,b.x),r=s(B.floating,b.y);return m?{...e,transform:"translate("+t+"px, "+r+"px)",...a(B.floating)>=1.5&&{willChange:"transform"}}:{position:n,left:t,top:r}},[n,m,B.floating,b.x,b.y]);return o.useMemo(()=>({...b,update:z,refs:_,elements:B,floatingStyles:V}),[b,z,_,B,V])}let f=(e,t)=>({...(0,r.cv)(e),options:[e,t]}),p=(e,t)=>({...(0,r.uY)(e),options:[e,t]}),v=(e,t)=>({...(0,r.RR)(e),options:[e,t]}),m=(e,t)=>({...(0,r.dp)(e),options:[e,t]})},52307:function(e,t,n){n.d(t,{dk:function(){return f},fw:function(){return d},zH:function(){return c}});var r=n(2265),o=n(93980),l=n(73389),i=n(67561),u=n(87550),a=n(38929);let s=(0,r.createContext)(null);function c(){var e,t;return null!=(t=null==(e=(0,r.useContext)(s))?void 0:e.value)?t:void 0}function d(){let[e,t]=(0,r.useState)([]);return[e.length>0?e.join(" "):void 0,(0,r.useMemo)(()=>function(e){let n=(0,o.z)(e=>(t(t=>[...t,e]),()=>t(t=>{let n=t.slice(),r=n.indexOf(e);return -1!==r&&n.splice(r,1),n}))),l=(0,r.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 r.createElement(s.Provider,{value:l},e.children)},[t])]}s.displayName="DescriptionContext";let f=Object.assign((0,a.yV)(function(e,t){let n=(0,r.useId)(),o=(0,u.B)(),{id:c="headlessui-description-".concat(n),...d}=e,f=function e(){let t=(0,r.useContext)(s);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}(),p=(0,i.T)(t);(0,l.e)(()=>f.register(c),[c,f.register]);let v=o||!1,m=(0,r.useMemo)(()=>({...f.slot,disabled:v}),[f.slot,v]),g={ref:p,...f.props,id:c};return(0,a.L6)()({ourProps:g,theirProps:d,slot:m,defaultTag:"p",name:f.name||"Description"})}),{})},7935:function(e,t,n){n.d(t,{__:function(){return p},bE:function(){return f},wp:function(){return d}});var r=n(2265),o=n(93980),l=n(73389),i=n(67561),u=n(87550),a=n(80281),s=n(38929);let c=(0,r.createContext)(null);function d(e){var t,n,o;let l=null!=(n=null==(t=(0,r.useContext)(c))?void 0:t.value)?n:void 0;return(null!=(o=null==e?void 0:e.length)?o:0)>0?[l,...e].filter(Boolean).join(" "):l}function f(){let{inherit:e=!1}=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=d(),[n,l]=(0,r.useState)([]),i=e?[t,...n].filter(Boolean):n;return[i.length>0?i.join(" "):void 0,(0,r.useMemo)(()=>function(e){let t=(0,o.z)(e=>(l(t=>[...t,e]),()=>l(t=>{let n=t.slice(),r=n.indexOf(e);return -1!==r&&n.splice(r,1),n}))),n=(0,r.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 r.createElement(c.Provider,{value:n},e.children)},[l])]}c.displayName="LabelContext";let p=Object.assign((0,s.yV)(function(e,t){var n;let d=(0,r.useId)(),f=function e(){let t=(0,r.useContext)(c);if(null===t){let t=Error("You used a
    ^jr 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

    3YYKR6{ z;eVZ|Xc9msvJQ0a1f4y;rYN*Z_5;#k5yNt59}4U~2)o?E#zdx9 zPC9b=LLI}SMFA4?dA#P8f6NaE>~Tu@QWvI++oir9CgMp4kI?3l&Tc?W#tuAvXxG2o z`NVw(c1}(WAD$T*oemMiJO;GrH~(E)nul4O$X+P9a_fkAVD z)hZipkv%7T(FL6-59!fX{`4|rAO;L`<9N}%Cs89xXqbMdTHEdj8JHIZz{o}u-W2m& z&OGPICmy}@;!E$o_wIVN##_x`8~?yLH50a{KMsbN9U!j5>|hlU)K;d7G`;mLYz=69 z@FSTG8xx#x2fCdkKlwrJhR-*PU73v=dAD4%Qo-r@7FR?hwV$bPA#{vP6+_^1VGJ|E zLL*-s>`D5G_ekwzlk&_{d-mLT|Ks0&a`%bxBO~Jzz9%w4R{F6Z%D<5;GYH^m<+}P_ zbo!~kzHZ|SPTknKaN$5_sl+x!HLu0%+Ibsna-uwqRp4hNNnt1sJ|KZ)syZd21zDGf!+Igp7&cJY zzz_l(3D`7?1sAD}VWLaSP|c}O4bxI*8?I4Ky+xiq>Fs3Q$KCivfH$!3Aa1V?PnRp> zhxd-`|HigwzH)5;(L?)^<`j`6Ipd_+MFz|NV=$Uvu-4yQ>M1Q)-?!|wtIvAl>1Qrk zy4q@0e#2%usZUlXCZ|~zWP1RKV}GfwsA@S$OEZ>|JQAo*z2dzoxul!rgPF{udvfa* z|m);sUsmyWd7yW+Q0uOJ2I7A|K`GLFLi;5Y+A}Tnc-hBbM7Qq(`KuMwR(j& zY#bk+*tBW=$ndfAF1+}``|hojEBvT0axmKgHICZ+Tl4NDr|U zsJKSDIc6&xTed0)dRLdLkvsMr8XKGH@9$0@B1|PpkQ8^jX!kjXpoJl>X^+jiTMCw+ z(mHV>cm4IXH(Zyz;+Lsw^{ZcAe8JVVkN{2QA;jEksMcJA8qjmHi?v-gQ(BL^Pd zKYcKLZjg!*(pl|Df!LCK* zjm?cG)1%2mBbl1yEl^3bQmxbsi|`K}B{di?<}O8DJATLHwFUqc@eyYaku`8^(B5&vr@jjM74WbTM8z=LJ8o4fRjEQSDd`OcM)FMU+K_FsaG}KJ( z1R_oul!0_iVJMtHa|bZFg{7k=izONcT4)9^>cqF`d@xsGlZAZnBVbqt%#N6RGxbL`0(z%2PURZ>>57!(7~NgA3d~vm{mI8K$+zGl7YSi5oK

    \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/login.txt b/litellm/proxy/_experimental/out/login.txt index 6f7c11e66b4..f8f47b9ef0d 100644 --- a/litellm/proxy/_experimental/out/login.txt +++ b/litellm/proxy/_experimental/out/login.txt @@ -1,7 +1,7 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[15820,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","337","static/chunks/337-bb33d149e9f461b3.js","2409","static/chunks/2409-e94c05c6f11bb939.js","3367","static/chunks/3367-58830187e9e5b9fa.js","5869","static/chunks/5869-426268ba6ad0ce0c.js","2618","static/chunks/2618-062177b80fc4a38e.js","5945","static/chunks/5945-8b3b7713d7f416a2.js","1623","static/chunks/1623-54c56cbe1afc3953.js","3242","static/chunks/3242-6e6ec7e18f5d698d.js","8049","static/chunks/8049-cb52b16664f13e28.js","2626","static/chunks/app/login/page-61bfa80619b62f6b.js"],"default",1] +3:I[15820,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","536","static/chunks/536-8fae454c1d779890.js","2618","static/chunks/2618-062177b80fc4a38e.js","1623","static/chunks/1623-54c56cbe1afc3953.js","8049","static/chunks/8049-98da62d72b2b7dad.js","2626","static/chunks/app/login/page-e40d110cdbc26a70.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["login",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["login",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","login","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["login",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["login",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","login","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]] 6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/login/index.html b/litellm/proxy/_experimental/out/login/index.html deleted file mode 100644 index f42910853e3..00000000000 --- a/litellm/proxy/_experimental/out/login/index.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard
    🚅 LiteLLM
    Loading...
    \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/logs.html b/litellm/proxy/_experimental/out/logs.html new file mode 100644 index 00000000000..0b275a0936a --- /dev/null +++ b/litellm/proxy/_experimental/out/logs.html @@ -0,0 +1 @@ +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/logs.txt b/litellm/proxy/_experimental/out/logs.txt index f0f42af3935..24ee811efae 100644 --- a/litellm/proxy/_experimental/out/logs.txt +++ b/litellm/proxy/_experimental/out/logs.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[19056,["1047","static/chunks/e228588e-635e9029d9d88215.js","6990","static/chunks/13b76428-e1bf383848c17260.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","2409","static/chunks/2409-e94c05c6f11bb939.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","353","static/chunks/353-e55516ea4730f9d4.js","1994","static/chunks/1994-6637a121c9ee1602.js","7138","static/chunks/7138-5b134dc8ad670770.js","7851","static/chunks/7851-c10cbe6fcac2f9d6.js","2068","static/chunks/2068-2c78bfc32dc0de5f.js","8565","static/chunks/8565-5c05f6bbb9d0662f.js","5319","static/chunks/5319-5b2d4bf2dc450f99.js","5333","static/chunks/5333-1540faf81c7d7006.js","8582","static/chunks/8582-3a775364dbf07fa8.js","6609","static/chunks/6609-3e081758ffbe3786.js","4105","static/chunks/4105-9c3c0ee7c494102f.js","2378","static/chunks/2378-252212b7a5e313ce.js","5202","static/chunks/5202-60292daf4bc5c8fb.js","8049","static/chunks/8049-cb52b16664f13e28.js","5144","static/chunks/5144-bbc18c43eade9aef.js","2202","static/chunks/2202-a83ad035a17401aa.js","1098","static/chunks/1098-c3e95c9684ff5e95.js","665","static/chunks/665-d94073042ee5b874.js","1901","static/chunks/1901-4d02d1f2a71cdbf7.js","2100","static/chunks/app/(dashboard)/logs/page-5a10d46ca991b83e.js"],"default",1] +3:I[19056,["1047","static/chunks/e228588e-635e9029d9d88215.js","6990","static/chunks/13b76428-e1bf383848c17260.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","353","static/chunks/353-347e4836f09d94a0.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7971","static/chunks/7971-76912e9c9a840367.js","536","static/chunks/536-8fae454c1d779890.js","6894","static/chunks/6894-8c74216e23aa271e.js","7474","static/chunks/7474-79e3343f32c7e661.js","9258","static/chunks/9258-6907841794d6c1e1.js","3178","static/chunks/3178-47bc3b9e8cf9bf6c.js","5319","static/chunks/5319-7f07d87ef011d5c9.js","9190","static/chunks/9190-e32c76b5b1affa7b.js","6609","static/chunks/6609-a69ca4ee5a2c4a9d.js","2353","static/chunks/2353-c94748c0aac514ff.js","7967","static/chunks/7967-1ac5097c3d83016f.js","5720","static/chunks/5720-a8df9dd74eea4daa.js","4934","static/chunks/4934-d937980b64b5dd57.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5144","static/chunks/5144-ddfa7a8f89c5d465.js","7914","static/chunks/7914-25af99af34bee64b.js","1098","static/chunks/1098-a1702da59647cf14.js","665","static/chunks/665-f361bd1c21e3bf25.js","7794","static/chunks/7794-37e92993b04b6bb9.js","2100","static/chunks/app/(dashboard)/logs/page-a6b6031fb32f8582.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["logs",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["logs",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/0fc668a8750043fe.css","precedence":"next","crossOrigin":"$undefined"}]]],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","logs","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["logs",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["logs",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/0fc668a8750043fe.css","precedence":"next","crossOrigin":"$undefined"}]]],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","logs","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/logs/index.html b/litellm/proxy/_experimental/out/logs/index.html deleted file mode 100644 index 009e96e58cb..00000000000 --- a/litellm/proxy/_experimental/out/logs/index.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/mcp/oauth/callback/index.html b/litellm/proxy/_experimental/out/mcp/oauth/callback.html similarity index 95% rename from litellm/proxy/_experimental/out/mcp/oauth/callback/index.html rename to litellm/proxy/_experimental/out/mcp/oauth/callback.html index 8dfe2d19e03..6748ce86ae9 100644 --- a/litellm/proxy/_experimental/out/mcp/oauth/callback/index.html +++ b/litellm/proxy/_experimental/out/mcp/oauth/callback.html @@ -1 +1 @@ -LiteLLM Dashboard \ No newline at end of file +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/mcp/oauth/callback.txt b/litellm/proxy/_experimental/out/mcp/oauth/callback.txt index 6e6b91856f2..d5d5513069e 100644 --- a/litellm/proxy/_experimental/out/mcp/oauth/callback.txt +++ b/litellm/proxy/_experimental/out/mcp/oauth/callback.txt @@ -2,6 +2,6 @@ 3:I[33422,["480","static/chunks/app/mcp/oauth/callback/page-01be1cae3559363d.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["mcp",{"children":["oauth",{"children":["callback",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["mcp",{"children":["oauth",{"children":["callback",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","mcp","children","oauth","children","callback","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","mcp","children","oauth","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","mcp","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["mcp",{"children":["oauth",{"children":["callback",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["mcp",{"children":["oauth",{"children":["callback",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","mcp","children","oauth","children","callback","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","mcp","children","oauth","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","mcp","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]] 6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/experimental/api-playground/index.html b/litellm/proxy/_experimental/out/model-hub.html similarity index 60% rename from litellm/proxy/_experimental/out/experimental/api-playground/index.html rename to litellm/proxy/_experimental/out/model-hub.html index f0cc1a6ec5c..801ca23166d 100644 --- a/litellm/proxy/_experimental/out/experimental/api-playground/index.html +++ b/litellm/proxy/_experimental/out/model-hub.html @@ -1 +1 @@ -LiteLLM Dashboard \ No newline at end of file +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/model-hub.txt b/litellm/proxy/_experimental/out/model-hub.txt index 4aa2f97413e..78baf271310 100644 --- a/litellm/proxy/_experimental/out/model-hub.txt +++ b/litellm/proxy/_experimental/out/model-hub.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[30615,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","1994","static/chunks/1994-6637a121c9ee1602.js","7138","static/chunks/7138-5b134dc8ad670770.js","2068","static/chunks/2068-2c78bfc32dc0de5f.js","8565","static/chunks/8565-5c05f6bbb9d0662f.js","5869","static/chunks/5869-426268ba6ad0ce0c.js","2618","static/chunks/2618-062177b80fc4a38e.js","7906","static/chunks/7906-59ba450db59c8efa.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4077","static/chunks/4077-50cf2a28a79fdcd4.js","8049","static/chunks/8049-cb52b16664f13e28.js","7526","static/chunks/7526-da6b2857a3ca248d.js","3862","static/chunks/3862-064a3fb795c75b62.js","2678","static/chunks/app/(dashboard)/model-hub/page-1479dcb217587498.js"],"default",1] +3:I[30615,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7971","static/chunks/7971-76912e9c9a840367.js","6894","static/chunks/6894-8c74216e23aa271e.js","7474","static/chunks/7474-79e3343f32c7e661.js","3178","static/chunks/3178-47bc3b9e8cf9bf6c.js","2618","static/chunks/2618-062177b80fc4a38e.js","7906","static/chunks/7906-1b1cdd8da2773bb2.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","4341","static/chunks/4341-3e3f04c866417786.js","8049","static/chunks/8049-98da62d72b2b7dad.js","7526","static/chunks/7526-f6a7e2b51a17dd02.js","6554","static/chunks/6554-265013ca56622e1f.js","2678","static/chunks/app/(dashboard)/model-hub/page-37f3c43872246b40.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["model-hub",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["model-hub",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","model-hub","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["model-hub",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["model-hub",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","model-hub","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/model_hub.html b/litellm/proxy/_experimental/out/model_hub.html new file mode 100644 index 00000000000..8a61fd619ca --- /dev/null +++ b/litellm/proxy/_experimental/out/model_hub.html @@ -0,0 +1 @@ +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/model_hub.txt b/litellm/proxy/_experimental/out/model_hub.txt index 073bad4e99d..6922f12cdc3 100644 --- a/litellm/proxy/_experimental/out/model_hub.txt +++ b/litellm/proxy/_experimental/out/model_hub.txt @@ -1,7 +1,7 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[52829,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","2068","static/chunks/2068-2c78bfc32dc0de5f.js","5869","static/chunks/5869-426268ba6ad0ce0c.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","7926","static/chunks/7926-108623e14caeb770.js","8049","static/chunks/8049-cb52b16664f13e28.js","7526","static/chunks/7526-da6b2857a3ca248d.js","1418","static/chunks/app/model_hub/page-92347d2021ca8580.js"],"default",1] +3:I[52829,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","6894","static/chunks/6894-8c74216e23aa271e.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","8049","static/chunks/8049-98da62d72b2b7dad.js","7526","static/chunks/7526-f6a7e2b51a17dd02.js","1418","static/chunks/app/model_hub/page-649f32c699b27a45.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["model_hub",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["model_hub",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","model_hub","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["model_hub",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["model_hub",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","model_hub","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]] 6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/model_hub_table/index.html b/litellm/proxy/_experimental/out/model_hub_table.html similarity index 78% rename from litellm/proxy/_experimental/out/model_hub_table/index.html rename to litellm/proxy/_experimental/out/model_hub_table.html index 6c17bc92565..96c8f67ec4c 100644 --- a/litellm/proxy/_experimental/out/model_hub_table/index.html +++ b/litellm/proxy/_experimental/out/model_hub_table.html @@ -1 +1 @@ -LiteLLM Dashboard \ No newline at end of file +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/model_hub_table.txt b/litellm/proxy/_experimental/out/model_hub_table.txt index 8ccdde74088..cd68d8db459 100644 --- a/litellm/proxy/_experimental/out/model_hub_table.txt +++ b/litellm/proxy/_experimental/out/model_hub_table.txt @@ -1,7 +1,7 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[22775,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","1994","static/chunks/1994-6637a121c9ee1602.js","7138","static/chunks/7138-5b134dc8ad670770.js","2068","static/chunks/2068-2c78bfc32dc0de5f.js","8565","static/chunks/8565-5c05f6bbb9d0662f.js","5869","static/chunks/5869-426268ba6ad0ce0c.js","2618","static/chunks/2618-062177b80fc4a38e.js","7906","static/chunks/7906-59ba450db59c8efa.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4077","static/chunks/4077-50cf2a28a79fdcd4.js","1623","static/chunks/1623-54c56cbe1afc3953.js","8049","static/chunks/8049-cb52b16664f13e28.js","7526","static/chunks/7526-da6b2857a3ca248d.js","3862","static/chunks/3862-064a3fb795c75b62.js","9025","static/chunks/app/model_hub_table/page-2ad344049541235f.js"],"default",1] +3:I[22775,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7971","static/chunks/7971-76912e9c9a840367.js","6894","static/chunks/6894-8c74216e23aa271e.js","7474","static/chunks/7474-79e3343f32c7e661.js","3178","static/chunks/3178-47bc3b9e8cf9bf6c.js","2618","static/chunks/2618-062177b80fc4a38e.js","7906","static/chunks/7906-1b1cdd8da2773bb2.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","4341","static/chunks/4341-3e3f04c866417786.js","1623","static/chunks/1623-54c56cbe1afc3953.js","8049","static/chunks/8049-98da62d72b2b7dad.js","7526","static/chunks/7526-f6a7e2b51a17dd02.js","6554","static/chunks/6554-265013ca56622e1f.js","9025","static/chunks/app/model_hub_table/page-81008adc04402b54.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["model_hub_table",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["model_hub_table",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","model_hub_table","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["model_hub_table",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["model_hub_table",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","model_hub_table","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]] 6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/models-and-endpoints/index.html b/litellm/proxy/_experimental/out/models-and-endpoints.html similarity index 73% rename from litellm/proxy/_experimental/out/models-and-endpoints/index.html rename to litellm/proxy/_experimental/out/models-and-endpoints.html index d40187b4f42..ae288a7830a 100644 --- a/litellm/proxy/_experimental/out/models-and-endpoints/index.html +++ b/litellm/proxy/_experimental/out/models-and-endpoints.html @@ -1 +1 @@ -LiteLLM Dashboard \ No newline at end of file +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/models-and-endpoints.txt b/litellm/proxy/_experimental/out/models-and-endpoints.txt index 67b6a45ada6..43fd140c7ca 100644 --- a/litellm/proxy/_experimental/out/models-and-endpoints.txt +++ b/litellm/proxy/_experimental/out/models-and-endpoints.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[6121,["1047","static/chunks/e228588e-635e9029d9d88215.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","2409","static/chunks/2409-e94c05c6f11bb939.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","353","static/chunks/353-e55516ea4730f9d4.js","1994","static/chunks/1994-6637a121c9ee1602.js","7138","static/chunks/7138-5b134dc8ad670770.js","7851","static/chunks/7851-c10cbe6fcac2f9d6.js","2068","static/chunks/2068-2c78bfc32dc0de5f.js","8565","static/chunks/8565-5c05f6bbb9d0662f.js","5869","static/chunks/5869-426268ba6ad0ce0c.js","5319","static/chunks/5319-5b2d4bf2dc450f99.js","5333","static/chunks/5333-1540faf81c7d7006.js","8582","static/chunks/8582-3a775364dbf07fa8.js","6609","static/chunks/6609-3e081758ffbe3786.js","2618","static/chunks/2618-062177b80fc4a38e.js","766","static/chunks/766-baf0336e8ba5c686.js","4306","static/chunks/4306-f891b96cf0ee333b.js","2820","static/chunks/2820-592c2b4ff874a913.js","8049","static/chunks/8049-cb52b16664f13e28.js","5144","static/chunks/5144-bbc18c43eade9aef.js","9584","static/chunks/9584-9d4fd7b3d6a7c9e7.js","9841","static/chunks/9841-721a173be76941d1.js","1664","static/chunks/app/(dashboard)/models-and-endpoints/page-b69988590beaa5c8.js"],"default",1] +3:I[6121,["1047","static/chunks/e228588e-635e9029d9d88215.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","353","static/chunks/353-347e4836f09d94a0.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7971","static/chunks/7971-76912e9c9a840367.js","536","static/chunks/536-8fae454c1d779890.js","6894","static/chunks/6894-8c74216e23aa271e.js","7474","static/chunks/7474-79e3343f32c7e661.js","9258","static/chunks/9258-6907841794d6c1e1.js","3178","static/chunks/3178-47bc3b9e8cf9bf6c.js","5319","static/chunks/5319-7f07d87ef011d5c9.js","9190","static/chunks/9190-e32c76b5b1affa7b.js","6609","static/chunks/6609-a69ca4ee5a2c4a9d.js","2353","static/chunks/2353-c94748c0aac514ff.js","2618","static/chunks/2618-062177b80fc4a38e.js","7980","static/chunks/7980-b52a05c1635a1a59.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5144","static/chunks/5144-ddfa7a8f89c5d465.js","9584","static/chunks/9584-4d5bef7e60cfea45.js","1658","static/chunks/1658-c301cddaf7772753.js","1664","static/chunks/app/(dashboard)/models-and-endpoints/page-c3af9027b254a3f0.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["models-and-endpoints",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["models-and-endpoints",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","models-and-endpoints","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["models-and-endpoints",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["models-and-endpoints",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","models-and-endpoints","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/onboarding.html b/litellm/proxy/_experimental/out/onboarding.html new file mode 100644 index 00000000000..5a5639ffc77 --- /dev/null +++ b/litellm/proxy/_experimental/out/onboarding.html @@ -0,0 +1 @@ +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/onboarding.txt b/litellm/proxy/_experimental/out/onboarding.txt index d2b9d0d0299..6eacb94f7dd 100644 --- a/litellm/proxy/_experimental/out/onboarding.txt +++ b/litellm/proxy/_experimental/out/onboarding.txt @@ -1,7 +1,7 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[12011,["3665","static/chunks/3014691f-ba91873bc8fe3fad.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","4865","static/chunks/4865-c1c0885a93c327fa.js","2901","static/chunks/2901-0cdd0656eb7463d6.js","8049","static/chunks/8049-cb52b16664f13e28.js","8461","static/chunks/app/onboarding/page-17ecf5bf068f8157.js"],"default",1] +3:I[12011,["3665","static/chunks/3014691f-ba91873bc8fe3fad.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","4865","static/chunks/4865-c1c0885a93c327fa.js","5510","static/chunks/5510-99fb91d9d17e6ab4.js","8049","static/chunks/8049-98da62d72b2b7dad.js","8461","static/chunks/app/onboarding/page-e8604d757e270b09.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["onboarding",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["onboarding",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","onboarding","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["onboarding",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",true],["",{"children":["onboarding",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","onboarding","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]] 6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/organizations.html b/litellm/proxy/_experimental/out/organizations.html new file mode 100644 index 00000000000..64cb56b06c2 --- /dev/null +++ b/litellm/proxy/_experimental/out/organizations.html @@ -0,0 +1 @@ +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/organizations.txt b/litellm/proxy/_experimental/out/organizations.txt index 1ed3b8d0b9f..7ea33875bd1 100644 --- a/litellm/proxy/_experimental/out/organizations.txt +++ b/litellm/proxy/_experimental/out/organizations.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[57616,["1047","static/chunks/e228588e-635e9029d9d88215.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","2409","static/chunks/2409-e94c05c6f11bb939.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","353","static/chunks/353-e55516ea4730f9d4.js","1994","static/chunks/1994-6637a121c9ee1602.js","7138","static/chunks/7138-5b134dc8ad670770.js","7851","static/chunks/7851-c10cbe6fcac2f9d6.js","8565","static/chunks/8565-5c05f6bbb9d0662f.js","5319","static/chunks/5319-5b2d4bf2dc450f99.js","5333","static/chunks/5333-1540faf81c7d7006.js","8582","static/chunks/8582-3a775364dbf07fa8.js","6609","static/chunks/6609-3e081758ffbe3786.js","2618","static/chunks/2618-062177b80fc4a38e.js","4105","static/chunks/4105-9c3c0ee7c494102f.js","7688","static/chunks/7688-ca173ea41812cf94.js","8049","static/chunks/8049-cb52b16664f13e28.js","5144","static/chunks/5144-bbc18c43eade9aef.js","2202","static/chunks/2202-a83ad035a17401aa.js","1098","static/chunks/1098-c3e95c9684ff5e95.js","5706","static/chunks/5706-1e314cef9ea5c5d6.js","6459","static/chunks/app/(dashboard)/organizations/page-56a03e123f452d60.js"],"default",1] +3:I[57616,["1047","static/chunks/e228588e-635e9029d9d88215.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","353","static/chunks/353-347e4836f09d94a0.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7971","static/chunks/7971-76912e9c9a840367.js","536","static/chunks/536-8fae454c1d779890.js","7474","static/chunks/7474-79e3343f32c7e661.js","9258","static/chunks/9258-6907841794d6c1e1.js","3178","static/chunks/3178-47bc3b9e8cf9bf6c.js","5319","static/chunks/5319-7f07d87ef011d5c9.js","9190","static/chunks/9190-e32c76b5b1affa7b.js","6609","static/chunks/6609-a69ca4ee5a2c4a9d.js","2353","static/chunks/2353-c94748c0aac514ff.js","2618","static/chunks/2618-062177b80fc4a38e.js","7967","static/chunks/7967-1ac5097c3d83016f.js","7799","static/chunks/7799-a8559d23e5deb5b9.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5144","static/chunks/5144-ddfa7a8f89c5d465.js","7914","static/chunks/7914-25af99af34bee64b.js","1098","static/chunks/1098-a1702da59647cf14.js","5706","static/chunks/5706-b92e3cca4b167e71.js","6459","static/chunks/app/(dashboard)/organizations/page-95fa0a5eac5056b4.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["organizations",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["organizations",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","organizations","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["organizations",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["organizations",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","organizations","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/organizations/index.html b/litellm/proxy/_experimental/out/organizations/index.html deleted file mode 100644 index 1f796d6a081..00000000000 --- a/litellm/proxy/_experimental/out/organizations/index.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/playground.html b/litellm/proxy/_experimental/out/playground.html new file mode 100644 index 00000000000..ed83c3da1b9 --- /dev/null +++ b/litellm/proxy/_experimental/out/playground.html @@ -0,0 +1 @@ +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/playground.txt b/litellm/proxy/_experimental/out/playground.txt index 20404ba3cbd..d82aab6e97d 100644 --- a/litellm/proxy/_experimental/out/playground.txt +++ b/litellm/proxy/_experimental/out/playground.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[69039,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","2409","static/chunks/2409-e94c05c6f11bb939.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","353","static/chunks/353-e55516ea4730f9d4.js","1994","static/chunks/1994-6637a121c9ee1602.js","8565","static/chunks/8565-5c05f6bbb9d0662f.js","5319","static/chunks/5319-5b2d4bf2dc450f99.js","7906","static/chunks/7906-59ba450db59c8efa.js","816","static/chunks/816-37c57b39f4e7ece1.js","7271","static/chunks/7271-46e4c11ee6b0a4d6.js","766","static/chunks/766-baf0336e8ba5c686.js","3567","static/chunks/3567-9a29feedd7b63950.js","8529","static/chunks/8529-6b66d5dba2148164.js","8049","static/chunks/8049-cb52b16664f13e28.js","5992","static/chunks/5992-287cec06808c74ae.js","9039","static/chunks/9039-e44ff08ca4f37a12.js","3368","static/chunks/app/(dashboard)/playground/page-fc3dff494dc4db08.js"],"default",1] +3:I[69039,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","353","static/chunks/353-347e4836f09d94a0.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7971","static/chunks/7971-76912e9c9a840367.js","3178","static/chunks/3178-47bc3b9e8cf9bf6c.js","5319","static/chunks/5319-7f07d87ef011d5c9.js","7906","static/chunks/7906-1b1cdd8da2773bb2.js","4851","static/chunks/4851-0dc9f6cfeabb43d0.js","816","static/chunks/816-924f34bbf6b36a05.js","7271","static/chunks/7271-46e4c11ee6b0a4d6.js","9078","static/chunks/9078-e3b627680692b3fd.js","8071","static/chunks/8071-afd8213d652a649a.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5992","static/chunks/5992-ee986583db978ba0.js","9039","static/chunks/9039-2037889778daf211.js","3368","static/chunks/app/(dashboard)/playground/page-e2680b62dbb22cd9.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["playground",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["playground",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","playground","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["playground",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["playground",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","playground","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/playground/index.html b/litellm/proxy/_experimental/out/playground/index.html deleted file mode 100644 index 11af2b88fad..00000000000 --- a/litellm/proxy/_experimental/out/playground/index.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/settings/router-settings/index.html b/litellm/proxy/_experimental/out/policies.html similarity index 61% rename from litellm/proxy/_experimental/out/settings/router-settings/index.html rename to litellm/proxy/_experimental/out/policies.html index 83492c87c91..57c04d66480 100644 --- a/litellm/proxy/_experimental/out/settings/router-settings/index.html +++ b/litellm/proxy/_experimental/out/policies.html @@ -1 +1 @@ -LiteLLM Dashboard \ No newline at end of file +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/policies.txt b/litellm/proxy/_experimental/out/policies.txt new file mode 100644 index 00000000000..c674efd4239 --- /dev/null +++ b/litellm/proxy/_experimental/out/policies.txt @@ -0,0 +1,13 @@ +2:I[19107,[],"ClientPageRoot"] +3:I[56744,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","353","static/chunks/353-347e4836f09d94a0.js","536","static/chunks/536-8fae454c1d779890.js","6894","static/chunks/6894-8c74216e23aa271e.js","9258","static/chunks/9258-6907841794d6c1e1.js","6057","static/chunks/6057-4eacff4874db3ebb.js","8049","static/chunks/8049-98da62d72b2b7dad.js","9120","static/chunks/9120-dc2d8129a3d2175b.js","6649","static/chunks/app/(dashboard)/policies/page-43fedb527f6a4b39.js"],"default",1] +4:I[4707,[],""] +5:I[36423,[],""] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] +7:{} +8:{"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"} +9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} +a:{"display":"inline-block"} +b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["policies",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["policies",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","policies","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] +1:null diff --git a/litellm/proxy/_experimental/out/settings/admin-settings.html b/litellm/proxy/_experimental/out/settings/admin-settings.html new file mode 100644 index 00000000000..555cfe670f2 --- /dev/null +++ b/litellm/proxy/_experimental/out/settings/admin-settings.html @@ -0,0 +1 @@ +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/settings/admin-settings.txt b/litellm/proxy/_experimental/out/settings/admin-settings.txt index 864f93c5ade..983d869d608 100644 --- a/litellm/proxy/_experimental/out/settings/admin-settings.txt +++ b/litellm/proxy/_experimental/out/settings/admin-settings.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[8786,["1047","static/chunks/e228588e-635e9029d9d88215.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","2409","static/chunks/2409-e94c05c6f11bb939.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","353","static/chunks/353-e55516ea4730f9d4.js","1994","static/chunks/1994-6637a121c9ee1602.js","7138","static/chunks/7138-5b134dc8ad670770.js","7851","static/chunks/7851-c10cbe6fcac2f9d6.js","5869","static/chunks/5869-426268ba6ad0ce0c.js","5333","static/chunks/5333-1540faf81c7d7006.js","8582","static/chunks/8582-3a775364dbf07fa8.js","6609","static/chunks/6609-3e081758ffbe3786.js","5945","static/chunks/5945-8b3b7713d7f416a2.js","1128","static/chunks/1128-64fa4a41ccaf67ea.js","8049","static/chunks/8049-cb52b16664f13e28.js","9818","static/chunks/9818-6f03d7efd4fb8533.js","8958","static/chunks/app/(dashboard)/settings/admin-settings/page-746658933a633902.js"],"default",1] +3:I[8786,["1047","static/chunks/e228588e-635e9029d9d88215.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","353","static/chunks/353-347e4836f09d94a0.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7971","static/chunks/7971-76912e9c9a840367.js","536","static/chunks/536-8fae454c1d779890.js","7474","static/chunks/7474-79e3343f32c7e661.js","9258","static/chunks/9258-6907841794d6c1e1.js","9190","static/chunks/9190-e32c76b5b1affa7b.js","6609","static/chunks/6609-a69ca4ee5a2c4a9d.js","2353","static/chunks/2353-c94748c0aac514ff.js","4851","static/chunks/4851-0dc9f6cfeabb43d0.js","3138","static/chunks/3138-faa6fb0b1d7f2d67.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","9271","static/chunks/9271-e8c50ba458178f1c.js","8049","static/chunks/8049-98da62d72b2b7dad.js","1789","static/chunks/1789-c534ff8966aa231a.js","8958","static/chunks/app/(dashboard)/settings/admin-settings/page-d0bae1a3ceef1920.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["settings",{"children":["admin-settings",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["settings",{"children":["admin-settings",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","settings","children","admin-settings","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","settings","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["settings",{"children":["admin-settings",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["settings",{"children":["admin-settings",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","settings","children","admin-settings","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","settings","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/settings/admin-settings/index.html b/litellm/proxy/_experimental/out/settings/admin-settings/index.html deleted file mode 100644 index b65ac01d615..00000000000 --- a/litellm/proxy/_experimental/out/settings/admin-settings/index.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/settings/logging-and-alerts.html b/litellm/proxy/_experimental/out/settings/logging-and-alerts.html new file mode 100644 index 00000000000..8ffc3404874 --- /dev/null +++ b/litellm/proxy/_experimental/out/settings/logging-and-alerts.html @@ -0,0 +1 @@ +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/settings/logging-and-alerts.txt b/litellm/proxy/_experimental/out/settings/logging-and-alerts.txt index 7deb3733c15..39bb4bcc434 100644 --- a/litellm/proxy/_experimental/out/settings/logging-and-alerts.txt +++ b/litellm/proxy/_experimental/out/settings/logging-and-alerts.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[72719,["1047","static/chunks/e228588e-635e9029d9d88215.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","2409","static/chunks/2409-e94c05c6f11bb939.js","3367","static/chunks/3367-58830187e9e5b9fa.js","353","static/chunks/353-e55516ea4730f9d4.js","1994","static/chunks/1994-6637a121c9ee1602.js","7138","static/chunks/7138-5b134dc8ad670770.js","7851","static/chunks/7851-c10cbe6fcac2f9d6.js","5869","static/chunks/5869-426268ba6ad0ce0c.js","5333","static/chunks/5333-1540faf81c7d7006.js","8582","static/chunks/8582-3a775364dbf07fa8.js","6609","static/chunks/6609-3e081758ffbe3786.js","2618","static/chunks/2618-062177b80fc4a38e.js","5945","static/chunks/5945-8b3b7713d7f416a2.js","1414","static/chunks/1414-2770d1155b664522.js","8049","static/chunks/8049-cb52b16664f13e28.js","9264","static/chunks/9264-fd8ab51d702e9535.js","2445","static/chunks/app/(dashboard)/settings/logging-and-alerts/page-e3df74ef5ac0dcd5.js"],"default",1] +3:I[72719,["1047","static/chunks/e228588e-635e9029d9d88215.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","353","static/chunks/353-347e4836f09d94a0.js","7971","static/chunks/7971-76912e9c9a840367.js","536","static/chunks/536-8fae454c1d779890.js","7474","static/chunks/7474-79e3343f32c7e661.js","9258","static/chunks/9258-6907841794d6c1e1.js","9190","static/chunks/9190-e32c76b5b1affa7b.js","6609","static/chunks/6609-a69ca4ee5a2c4a9d.js","2353","static/chunks/2353-c94748c0aac514ff.js","2618","static/chunks/2618-062177b80fc4a38e.js","6988","static/chunks/6988-27c1a5ab5702ba23.js","8049","static/chunks/8049-98da62d72b2b7dad.js","9264","static/chunks/9264-5009b962427411a5.js","2445","static/chunks/app/(dashboard)/settings/logging-and-alerts/page-194a2419931e7649.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["settings",{"children":["logging-and-alerts",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["settings",{"children":["logging-and-alerts",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","settings","children","logging-and-alerts","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","settings","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["settings",{"children":["logging-and-alerts",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["settings",{"children":["logging-and-alerts",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","settings","children","logging-and-alerts","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","settings","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/settings/logging-and-alerts/index.html b/litellm/proxy/_experimental/out/settings/logging-and-alerts/index.html deleted file mode 100644 index 5022910542b..00000000000 --- a/litellm/proxy/_experimental/out/settings/logging-and-alerts/index.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/usage/index.html b/litellm/proxy/_experimental/out/settings/router-settings.html similarity index 57% rename from litellm/proxy/_experimental/out/usage/index.html rename to litellm/proxy/_experimental/out/settings/router-settings.html index 2bda8f96f42..8d86b6076f3 100644 --- a/litellm/proxy/_experimental/out/usage/index.html +++ b/litellm/proxy/_experimental/out/settings/router-settings.html @@ -1 +1 @@ -LiteLLM Dashboard \ No newline at end of file +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/settings/router-settings.txt b/litellm/proxy/_experimental/out/settings/router-settings.txt index 7c6456fa090..cff956b6fa0 100644 --- a/litellm/proxy/_experimental/out/settings/router-settings.txt +++ b/litellm/proxy/_experimental/out/settings/router-settings.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[14809,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","2409","static/chunks/2409-e94c05c6f11bb939.js","7851","static/chunks/7851-c10cbe6fcac2f9d6.js","7271","static/chunks/7271-46e4c11ee6b0a4d6.js","2500","static/chunks/2500-811f2612ec5f6830.js","8049","static/chunks/8049-cb52b16664f13e28.js","5695","static/chunks/5695-dbbcbf2da21d2bab.js","8021","static/chunks/app/(dashboard)/settings/router-settings/page-ffa3245ebcbbc02a.js"],"default",1] +3:I[14809,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","536","static/chunks/536-8fae454c1d779890.js","9258","static/chunks/9258-6907841794d6c1e1.js","7271","static/chunks/7271-46e4c11ee6b0a4d6.js","730","static/chunks/730-6158e287ec72cfda.js","8049","static/chunks/8049-98da62d72b2b7dad.js","8021","static/chunks/app/(dashboard)/settings/router-settings/page-53d06fb7df656af3.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["settings",{"children":["router-settings",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["settings",{"children":["router-settings",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","settings","children","router-settings","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","settings","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["settings",{"children":["router-settings",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["settings",{"children":["router-settings",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","settings","children","router-settings","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","settings","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/settings/ui-theme.html b/litellm/proxy/_experimental/out/settings/ui-theme.html new file mode 100644 index 00000000000..4657395c16d --- /dev/null +++ b/litellm/proxy/_experimental/out/settings/ui-theme.html @@ -0,0 +1 @@ +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/settings/ui-theme.txt b/litellm/proxy/_experimental/out/settings/ui-theme.txt index c3f61d9e304..aa19997aaa0 100644 --- a/litellm/proxy/_experimental/out/settings/ui-theme.txt +++ b/litellm/proxy/_experimental/out/settings/ui-theme.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[8719,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","8049","static/chunks/8049-cb52b16664f13e28.js","3117","static/chunks/app/(dashboard)/settings/ui-theme/page-d833946961b065a5.js"],"default",1] +3:I[8719,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","8049","static/chunks/8049-98da62d72b2b7dad.js","3117","static/chunks/app/(dashboard)/settings/ui-theme/page-612e275485550e83.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["settings",{"children":["ui-theme",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["settings",{"children":["ui-theme",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","settings","children","ui-theme","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","settings","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["settings",{"children":["ui-theme",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["settings",{"children":["ui-theme",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","settings","children","ui-theme","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","settings","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/settings/ui-theme/index.html b/litellm/proxy/_experimental/out/settings/ui-theme/index.html deleted file mode 100644 index fc900017b3a..00000000000 --- a/litellm/proxy/_experimental/out/settings/ui-theme/index.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/teams.html b/litellm/proxy/_experimental/out/teams.html new file mode 100644 index 00000000000..5858f07f0c0 --- /dev/null +++ b/litellm/proxy/_experimental/out/teams.html @@ -0,0 +1 @@ +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/teams.txt b/litellm/proxy/_experimental/out/teams.txt index 297ba5abe36..35bf7f58c67 100644 --- a/litellm/proxy/_experimental/out/teams.txt +++ b/litellm/proxy/_experimental/out/teams.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[67578,["1047","static/chunks/e228588e-635e9029d9d88215.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","2409","static/chunks/2409-e94c05c6f11bb939.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","353","static/chunks/353-e55516ea4730f9d4.js","1994","static/chunks/1994-6637a121c9ee1602.js","7851","static/chunks/7851-c10cbe6fcac2f9d6.js","5333","static/chunks/5333-1540faf81c7d7006.js","2618","static/chunks/2618-062177b80fc4a38e.js","7688","static/chunks/7688-ca173ea41812cf94.js","3507","static/chunks/3507-14fb4e6cd377d7da.js","8049","static/chunks/8049-cb52b16664f13e28.js","5144","static/chunks/5144-bbc18c43eade9aef.js","9584","static/chunks/9584-9d4fd7b3d6a7c9e7.js","5706","static/chunks/5706-1e314cef9ea5c5d6.js","9483","static/chunks/app/(dashboard)/teams/page-8d1a71afa9e9ff16.js"],"default",1] +3:I[67578,["1047","static/chunks/e228588e-635e9029d9d88215.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","353","static/chunks/353-347e4836f09d94a0.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7971","static/chunks/7971-76912e9c9a840367.js","536","static/chunks/536-8fae454c1d779890.js","9258","static/chunks/9258-6907841794d6c1e1.js","2353","static/chunks/2353-c94748c0aac514ff.js","2618","static/chunks/2618-062177b80fc4a38e.js","3138","static/chunks/3138-faa6fb0b1d7f2d67.js","4509","static/chunks/4509-5bbcd014724651a9.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5144","static/chunks/5144-ddfa7a8f89c5d465.js","9584","static/chunks/9584-4d5bef7e60cfea45.js","5706","static/chunks/5706-b92e3cca4b167e71.js","9483","static/chunks/app/(dashboard)/teams/page-35fb23c26a99119e.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["teams",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["teams",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","teams","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["teams",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["teams",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","teams","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/teams/index.html b/litellm/proxy/_experimental/out/teams/index.html deleted file mode 100644 index 61c6f0904bd..00000000000 --- a/litellm/proxy/_experimental/out/teams/index.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/api-reference/index.html b/litellm/proxy/_experimental/out/test-key.html similarity index 62% rename from litellm/proxy/_experimental/out/api-reference/index.html rename to litellm/proxy/_experimental/out/test-key.html index 6c82d29680a..4b9641f7b3f 100644 --- a/litellm/proxy/_experimental/out/api-reference/index.html +++ b/litellm/proxy/_experimental/out/test-key.html @@ -1 +1 @@ -LiteLLM Dashboard \ No newline at end of file +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/test-key.txt b/litellm/proxy/_experimental/out/test-key.txt index a8d523ca50e..d0f3ab4485d 100644 --- a/litellm/proxy/_experimental/out/test-key.txt +++ b/litellm/proxy/_experimental/out/test-key.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[38511,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","2409","static/chunks/2409-e94c05c6f11bb939.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","353","static/chunks/353-e55516ea4730f9d4.js","1994","static/chunks/1994-6637a121c9ee1602.js","8565","static/chunks/8565-5c05f6bbb9d0662f.js","5319","static/chunks/5319-5b2d4bf2dc450f99.js","7906","static/chunks/7906-59ba450db59c8efa.js","816","static/chunks/816-37c57b39f4e7ece1.js","7271","static/chunks/7271-46e4c11ee6b0a4d6.js","766","static/chunks/766-baf0336e8ba5c686.js","3567","static/chunks/3567-9a29feedd7b63950.js","8049","static/chunks/8049-cb52b16664f13e28.js","5992","static/chunks/5992-287cec06808c74ae.js","2322","static/chunks/app/(dashboard)/test-key/page-63cd64b722408984.js"],"default",1] +3:I[38511,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","353","static/chunks/353-347e4836f09d94a0.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7971","static/chunks/7971-76912e9c9a840367.js","3178","static/chunks/3178-47bc3b9e8cf9bf6c.js","5319","static/chunks/5319-7f07d87ef011d5c9.js","7906","static/chunks/7906-1b1cdd8da2773bb2.js","4851","static/chunks/4851-0dc9f6cfeabb43d0.js","816","static/chunks/816-924f34bbf6b36a05.js","7271","static/chunks/7271-46e4c11ee6b0a4d6.js","9078","static/chunks/9078-e3b627680692b3fd.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5992","static/chunks/5992-ee986583db978ba0.js","2322","static/chunks/app/(dashboard)/test-key/page-a02455ca29fab29f.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["test-key",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["test-key",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","test-key","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["test-key",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["test-key",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","test-key","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/test-key/index.html b/litellm/proxy/_experimental/out/test-key/index.html deleted file mode 100644 index ce6673715e2..00000000000 --- a/litellm/proxy/_experimental/out/test-key/index.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/tools/mcp-servers.html b/litellm/proxy/_experimental/out/tools/mcp-servers.html new file mode 100644 index 00000000000..18eeffab050 --- /dev/null +++ b/litellm/proxy/_experimental/out/tools/mcp-servers.html @@ -0,0 +1 @@ +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/tools/mcp-servers.txt b/litellm/proxy/_experimental/out/tools/mcp-servers.txt index f08abc999ba..ae597551a4b 100644 --- a/litellm/proxy/_experimental/out/tools/mcp-servers.txt +++ b/litellm/proxy/_experimental/out/tools/mcp-servers.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[45045,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","2409","static/chunks/2409-e94c05c6f11bb939.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","353","static/chunks/353-e55516ea4730f9d4.js","1994","static/chunks/1994-6637a121c9ee1602.js","7851","static/chunks/7851-c10cbe6fcac2f9d6.js","2068","static/chunks/2068-2c78bfc32dc0de5f.js","5869","static/chunks/5869-426268ba6ad0ce0c.js","5945","static/chunks/5945-8b3b7713d7f416a2.js","7688","static/chunks/7688-ca173ea41812cf94.js","1623","static/chunks/1623-54c56cbe1afc3953.js","4559","static/chunks/4559-52ca85b2d8893149.js","8049","static/chunks/8049-cb52b16664f13e28.js","6537","static/chunks/6537-f70f2c4278e93458.js","6940","static/chunks/app/(dashboard)/tools/mcp-servers/page-7dd2ea6f1433d41f.js"],"default",1] +3:I[45045,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","353","static/chunks/353-347e4836f09d94a0.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7971","static/chunks/7971-76912e9c9a840367.js","536","static/chunks/536-8fae454c1d779890.js","6894","static/chunks/6894-8c74216e23aa271e.js","9258","static/chunks/9258-6907841794d6c1e1.js","4851","static/chunks/4851-0dc9f6cfeabb43d0.js","1623","static/chunks/1623-54c56cbe1afc3953.js","1059","static/chunks/1059-26bdac09bbb12a4b.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5276","static/chunks/5276-22fb90a28ebcab8b.js","6940","static/chunks/app/(dashboard)/tools/mcp-servers/page-eeef4bac80ed234b.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["tools",{"children":["mcp-servers",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["tools",{"children":["mcp-servers",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","tools","children","mcp-servers","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","tools","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["tools",{"children":["mcp-servers",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["tools",{"children":["mcp-servers",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","tools","children","mcp-servers","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","tools","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/tools/mcp-servers/index.html b/litellm/proxy/_experimental/out/tools/mcp-servers/index.html deleted file mode 100644 index cdea14b1384..00000000000 --- a/litellm/proxy/_experimental/out/tools/mcp-servers/index.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/tools/vector-stores.html b/litellm/proxy/_experimental/out/tools/vector-stores.html new file mode 100644 index 00000000000..a263cd676f8 --- /dev/null +++ b/litellm/proxy/_experimental/out/tools/vector-stores.html @@ -0,0 +1 @@ +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/tools/vector-stores.txt b/litellm/proxy/_experimental/out/tools/vector-stores.txt index 3190c71dd88..a937a7be1d6 100644 --- a/litellm/proxy/_experimental/out/tools/vector-stores.txt +++ b/litellm/proxy/_experimental/out/tools/vector-stores.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[77438,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","2409","static/chunks/2409-e94c05c6f11bb939.js","3367","static/chunks/3367-58830187e9e5b9fa.js","353","static/chunks/353-e55516ea4730f9d4.js","7851","static/chunks/7851-c10cbe6fcac2f9d6.js","2068","static/chunks/2068-2c78bfc32dc0de5f.js","5869","static/chunks/5869-426268ba6ad0ce0c.js","2618","static/chunks/2618-062177b80fc4a38e.js","5945","static/chunks/5945-8b3b7713d7f416a2.js","6285","static/chunks/6285-68f82c874b184eba.js","8049","static/chunks/8049-cb52b16664f13e28.js","9140","static/chunks/9140-09af618948244b82.js","6248","static/chunks/app/(dashboard)/tools/vector-stores/page-0f618cc2d6cae794.js"],"default",1] +3:I[77438,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","353","static/chunks/353-347e4836f09d94a0.js","7971","static/chunks/7971-76912e9c9a840367.js","536","static/chunks/536-8fae454c1d779890.js","6894","static/chunks/6894-8c74216e23aa271e.js","7474","static/chunks/7474-79e3343f32c7e661.js","9258","static/chunks/9258-6907841794d6c1e1.js","3178","static/chunks/3178-47bc3b9e8cf9bf6c.js","5319","static/chunks/5319-7f07d87ef011d5c9.js","9190","static/chunks/9190-e32c76b5b1affa7b.js","6609","static/chunks/6609-a69ca4ee5a2c4a9d.js","2618","static/chunks/2618-062177b80fc4a38e.js","1208","static/chunks/1208-5caf6d9856cc3f13.js","8049","static/chunks/8049-98da62d72b2b7dad.js","6213","static/chunks/6213-6c1fab5854e4401f.js","6248","static/chunks/app/(dashboard)/tools/vector-stores/page-a8da9d9d1d928bc0.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["tools",{"children":["vector-stores",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["tools",{"children":["vector-stores",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","tools","children","vector-stores","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","tools","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["tools",{"children":["vector-stores",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["tools",{"children":["vector-stores",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","tools","children","vector-stores","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","tools","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/tools/vector-stores/index.html b/litellm/proxy/_experimental/out/tools/vector-stores/index.html deleted file mode 100644 index b0581c82e24..00000000000 --- a/litellm/proxy/_experimental/out/tools/vector-stores/index.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/usage.html b/litellm/proxy/_experimental/out/usage.html new file mode 100644 index 00000000000..6368b380987 --- /dev/null +++ b/litellm/proxy/_experimental/out/usage.html @@ -0,0 +1 @@ +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/usage.txt b/litellm/proxy/_experimental/out/usage.txt index 0fc0c9e9f3c..8fc61a9419e 100644 --- a/litellm/proxy/_experimental/out/usage.txt +++ b/litellm/proxy/_experimental/out/usage.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[26661,["1047","static/chunks/e228588e-635e9029d9d88215.js","6990","static/chunks/13b76428-e1bf383848c17260.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","2409","static/chunks/2409-e94c05c6f11bb939.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","353","static/chunks/353-e55516ea4730f9d4.js","1994","static/chunks/1994-6637a121c9ee1602.js","7138","static/chunks/7138-5b134dc8ad670770.js","7851","static/chunks/7851-c10cbe6fcac2f9d6.js","2068","static/chunks/2068-2c78bfc32dc0de5f.js","8565","static/chunks/8565-5c05f6bbb9d0662f.js","5319","static/chunks/5319-5b2d4bf2dc450f99.js","5333","static/chunks/5333-1540faf81c7d7006.js","8582","static/chunks/8582-3a775364dbf07fa8.js","6609","static/chunks/6609-3e081758ffbe3786.js","2618","static/chunks/2618-062177b80fc4a38e.js","4105","static/chunks/4105-9c3c0ee7c494102f.js","1108","static/chunks/1108-c2d0c742b6e72436.js","2227","static/chunks/2227-5ae3f36b0a81c5b4.js","5105","static/chunks/5105-2998cbe1c9fc8ee4.js","8049","static/chunks/8049-cb52b16664f13e28.js","5144","static/chunks/5144-bbc18c43eade9aef.js","2202","static/chunks/2202-a83ad035a17401aa.js","1098","static/chunks/1098-c3e95c9684ff5e95.js","665","static/chunks/665-d94073042ee5b874.js","292","static/chunks/292-aaba6c4e7c8d416d.js","4746","static/chunks/app/(dashboard)/usage/page-d7532f354d44803b.js"],"default",1] +3:I[26661,["1047","static/chunks/e228588e-635e9029d9d88215.js","6990","static/chunks/13b76428-e1bf383848c17260.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","353","static/chunks/353-347e4836f09d94a0.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7971","static/chunks/7971-76912e9c9a840367.js","536","static/chunks/536-8fae454c1d779890.js","6894","static/chunks/6894-8c74216e23aa271e.js","7474","static/chunks/7474-79e3343f32c7e661.js","9258","static/chunks/9258-6907841794d6c1e1.js","3178","static/chunks/3178-47bc3b9e8cf9bf6c.js","5319","static/chunks/5319-7f07d87ef011d5c9.js","9190","static/chunks/9190-e32c76b5b1affa7b.js","6609","static/chunks/6609-a69ca4ee5a2c4a9d.js","2353","static/chunks/2353-c94748c0aac514ff.js","2618","static/chunks/2618-062177b80fc4a38e.js","7967","static/chunks/7967-1ac5097c3d83016f.js","1108","static/chunks/1108-8b678b0704cb239b.js","5238","static/chunks/5238-3fa69435be59fb79.js","5188","static/chunks/5188-c6270da3b1debeb8.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5144","static/chunks/5144-ddfa7a8f89c5d465.js","7914","static/chunks/7914-25af99af34bee64b.js","1098","static/chunks/1098-a1702da59647cf14.js","665","static/chunks/665-f361bd1c21e3bf25.js","6728","static/chunks/6728-a6b270885bc8863f.js","4746","static/chunks/app/(dashboard)/usage/page-f5988c9f9087fca8.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["usage",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["usage",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","usage","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["usage",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["usage",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","usage","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/model-hub/index.html b/litellm/proxy/_experimental/out/users.html similarity index 59% rename from litellm/proxy/_experimental/out/model-hub/index.html rename to litellm/proxy/_experimental/out/users.html index 93af4d65b3a..0a83220118c 100644 --- a/litellm/proxy/_experimental/out/model-hub/index.html +++ b/litellm/proxy/_experimental/out/users.html @@ -1 +1 @@ -LiteLLM Dashboard \ No newline at end of file +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/users.txt b/litellm/proxy/_experimental/out/users.txt index dbc69ced42f..1538a17474e 100644 --- a/litellm/proxy/_experimental/out/users.txt +++ b/litellm/proxy/_experimental/out/users.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[87654,["1047","static/chunks/e228588e-635e9029d9d88215.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","2409","static/chunks/2409-e94c05c6f11bb939.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","353","static/chunks/353-e55516ea4730f9d4.js","1994","static/chunks/1994-6637a121c9ee1602.js","7138","static/chunks/7138-5b134dc8ad670770.js","7851","static/chunks/7851-c10cbe6fcac2f9d6.js","2068","static/chunks/2068-2c78bfc32dc0de5f.js","8565","static/chunks/8565-5c05f6bbb9d0662f.js","5869","static/chunks/5869-426268ba6ad0ce0c.js","5319","static/chunks/5319-5b2d4bf2dc450f99.js","5333","static/chunks/5333-1540faf81c7d7006.js","8582","static/chunks/8582-3a775364dbf07fa8.js","6609","static/chunks/6609-3e081758ffbe3786.js","2618","static/chunks/2618-062177b80fc4a38e.js","4105","static/chunks/4105-9c3c0ee7c494102f.js","4306","static/chunks/4306-f891b96cf0ee333b.js","8014","static/chunks/8014-d6138fce46bba1e2.js","8049","static/chunks/8049-cb52b16664f13e28.js","2202","static/chunks/2202-a83ad035a17401aa.js","6653","static/chunks/6653-e61fdc06093fc0a8.js","7297","static/chunks/app/(dashboard)/users/page-6d3643cef6c068ee.js"],"default",1] +3:I[87654,["1047","static/chunks/e228588e-635e9029d9d88215.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","353","static/chunks/353-347e4836f09d94a0.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7971","static/chunks/7971-76912e9c9a840367.js","536","static/chunks/536-8fae454c1d779890.js","6894","static/chunks/6894-8c74216e23aa271e.js","7474","static/chunks/7474-79e3343f32c7e661.js","9258","static/chunks/9258-6907841794d6c1e1.js","3178","static/chunks/3178-47bc3b9e8cf9bf6c.js","5319","static/chunks/5319-7f07d87ef011d5c9.js","9190","static/chunks/9190-e32c76b5b1affa7b.js","6609","static/chunks/6609-a69ca4ee5a2c4a9d.js","2353","static/chunks/2353-c94748c0aac514ff.js","2618","static/chunks/2618-062177b80fc4a38e.js","7967","static/chunks/7967-1ac5097c3d83016f.js","3138","static/chunks/3138-faa6fb0b1d7f2d67.js","8049","static/chunks/8049-98da62d72b2b7dad.js","7914","static/chunks/7914-25af99af34bee64b.js","2318","static/chunks/2318-8bec43289448e95d.js","7297","static/chunks/app/(dashboard)/users/page-993c131fdcb59c92.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["users",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["users",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","users","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["users",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["users",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","users","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/users/index.html b/litellm/proxy/_experimental/out/users/index.html deleted file mode 100644 index a00c0301c08..00000000000 --- a/litellm/proxy/_experimental/out/users/index.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/virtual-keys.html b/litellm/proxy/_experimental/out/virtual-keys.html new file mode 100644 index 00000000000..2820e7c5f1c --- /dev/null +++ b/litellm/proxy/_experimental/out/virtual-keys.html @@ -0,0 +1 @@ +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/virtual-keys.txt b/litellm/proxy/_experimental/out/virtual-keys.txt index 15c9de7b52a..2c10f3b59f1 100644 --- a/litellm/proxy/_experimental/out/virtual-keys.txt +++ b/litellm/proxy/_experimental/out/virtual-keys.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[98441,["1047","static/chunks/e228588e-635e9029d9d88215.js","3665","static/chunks/3014691f-ba91873bc8fe3fad.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","2409","static/chunks/2409-e94c05c6f11bb939.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","353","static/chunks/353-e55516ea4730f9d4.js","1994","static/chunks/1994-6637a121c9ee1602.js","7138","static/chunks/7138-5b134dc8ad670770.js","7851","static/chunks/7851-c10cbe6fcac2f9d6.js","2068","static/chunks/2068-2c78bfc32dc0de5f.js","8565","static/chunks/8565-5c05f6bbb9d0662f.js","5319","static/chunks/5319-5b2d4bf2dc450f99.js","5333","static/chunks/5333-1540faf81c7d7006.js","8582","static/chunks/8582-3a775364dbf07fa8.js","6609","static/chunks/6609-3e081758ffbe3786.js","4105","static/chunks/4105-9c3c0ee7c494102f.js","2136","static/chunks/2136-2c0d6e8c18d2c5c4.js","8049","static/chunks/8049-cb52b16664f13e28.js","5144","static/chunks/5144-bbc18c43eade9aef.js","2202","static/chunks/2202-a83ad035a17401aa.js","1098","static/chunks/1098-c3e95c9684ff5e95.js","665","static/chunks/665-d94073042ee5b874.js","5975","static/chunks/5975-758334d6641b9c63.js","7049","static/chunks/app/(dashboard)/virtual-keys/page-73444bcb0cfe86b7.js"],"default",1] +3:I[98441,["1047","static/chunks/e228588e-635e9029d9d88215.js","3665","static/chunks/3014691f-ba91873bc8fe3fad.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","353","static/chunks/353-347e4836f09d94a0.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7971","static/chunks/7971-76912e9c9a840367.js","536","static/chunks/536-8fae454c1d779890.js","6894","static/chunks/6894-8c74216e23aa271e.js","7474","static/chunks/7474-79e3343f32c7e661.js","9258","static/chunks/9258-6907841794d6c1e1.js","3178","static/chunks/3178-47bc3b9e8cf9bf6c.js","5319","static/chunks/5319-7f07d87ef011d5c9.js","9190","static/chunks/9190-e32c76b5b1affa7b.js","6609","static/chunks/6609-a69ca4ee5a2c4a9d.js","2353","static/chunks/2353-c94748c0aac514ff.js","7967","static/chunks/7967-1ac5097c3d83016f.js","3871","static/chunks/3871-be6e9adb966e0429.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5144","static/chunks/5144-ddfa7a8f89c5d465.js","7914","static/chunks/7914-25af99af34bee64b.js","1098","static/chunks/1098-a1702da59647cf14.js","665","static/chunks/665-f361bd1c21e3bf25.js","5975","static/chunks/5975-60599e8984464729.js","7049","static/chunks/app/(dashboard)/virtual-keys/page-85cb1e2f0392d6e5.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["virtual-keys",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["virtual-keys",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","virtual-keys","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["virtual-keys",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["virtual-keys",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","virtual-keys","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/virtual-keys/index.html b/litellm/proxy/_experimental/out/virtual-keys/index.html deleted file mode 100644 index 702a244b3d9..00000000000 --- a/litellm/proxy/_experimental/out/virtual-keys/index.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index c248082c263..9ae95085f55 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -227,6 +227,8 @@ class KeyManagementRoutes(str, enum.Enum): KEY_REGENERATE_WITH_PATH_PARAM = "/key/{key_id}/regenerate" KEY_BLOCK = "/key/block" KEY_UNBLOCK = "/key/unblock" + KEY_BULK_UPDATE = "/key/bulk_update" + KEY_RESET_SPEND = "/key/{key_id}/reset_spend" # info and health routes KEY_INFO = "/key/info" @@ -293,6 +295,8 @@ class LiteLLMRoutes(enum.Enum): "/batches", "/v1/batches/{batch_id}", "/batches/{batch_id}", + "/v1/batches/{batch_id}/cancel", + "/batches/{batch_id}/cancel", # files "/v1/files", "/files", @@ -353,6 +357,9 @@ class LiteLLMRoutes(enum.Enum): "/v1/vector_stores/{vector_store_id}/files/{file_id}", "/vector_stores/{vector_store_id}/files/{file_id}/content", "/v1/vector_stores/{vector_store_id}/files/{file_id}/content", + "/vector_store/list", + "/v1/vector_store/list", + # search "/search", "/v1/search", @@ -379,6 +386,7 @@ class LiteLLMRoutes(enum.Enum): "/azure", "/azure_ai", "/openai", + "/openai_passthrough", "/assemblyai", "/eu.assemblyai", "/vllm", @@ -425,12 +433,12 @@ class LiteLLMRoutes(enum.Enum): ] google_routes = [ - "/v1beta/models/{model_name}:countTokens", - "/v1beta/models/{model_name}:generateContent", - "/v1beta/models/{model_name}:streamGenerateContent", - "/models/{model_name}:countTokens", - "/models/{model_name}:generateContent", - "/models/{model_name}:streamGenerateContent", + "/v1beta/models/{model_name:path}:countTokens", + "/v1beta/models/{model_name:path}:generateContent", + "/v1beta/models/{model_name:path}:streamGenerateContent", + "/models/{model_name:path}:countTokens", + "/models/{model_name:path}:generateContent", + "/models/{model_name:path}:streamGenerateContent", # Google Interactions API "/interactions", "/v1beta/interactions", @@ -494,6 +502,7 @@ class LiteLLMRoutes(enum.Enum): KeyManagementRoutes.KEY_LIST.value, KeyManagementRoutes.KEY_BLOCK.value, KeyManagementRoutes.KEY_UNBLOCK.value, + KeyManagementRoutes.KEY_BULK_UPDATE.value, ] management_routes = [ @@ -851,6 +860,13 @@ class GenerateRequestBase(LiteLLMPydanticObjectBase): aliases: Optional[dict] = {} object_permission: Optional[LiteLLM_ObjectPermissionBase] = None + @field_validator("max_budget", mode="before") + @classmethod + def check_max_budget(cls, v): + if v == "": + return None + return v + class AllowedVectorStoreIndexItem(LiteLLMPydanticObjectBase): index_name: str @@ -972,6 +988,10 @@ class RegenerateKeyRequest(GenerateKeyRequest): new_master_key: Optional[str] = None +class ResetSpendRequest(LiteLLMPydanticObjectBase): + reset_to: float + + class KeyRequest(LiteLLMPydanticObjectBase): keys: Optional[List[str]] = None key_aliases: Optional[List[str]] = None @@ -2066,6 +2086,14 @@ class ConfigGeneralSettings(LiteLLMPydanticObjectBase): None, description="Controls how non-admin users interact with MCP servers in the dashboard. 'restricted' shows only accessible servers, 'view_all' lists every server in read-only mode.", ) + store_prompts_in_spend_logs: Optional[bool] = Field( + None, + description="If True, stores request messages and responses in spend logs. Default is False.", + ) + maximum_spend_logs_retention_period: Optional[str] = Field( + None, + description="Maximum retention period for spend logs (e.g., '7d' for 7 days). Logs older than this will be deleted.", + ) class ConfigYAML(LiteLLMPydanticObjectBase): @@ -2132,10 +2160,6 @@ class LiteLLM_VerificationToken(LiteLLMPydanticObjectBase): rotation_interval: Optional[str] = None # How often to rotate (e.g., "30d", "90d") last_rotation_at: Optional[datetime] = None # When this key was last rotated key_rotation_at: Optional[datetime] = None # When this key should next be rotated - router_settings: Optional[ - Dict - ] = None # Router settings for this key (Key > Team > Global precedence) - model_config = ConfigDict(protected_namespaces=()) @@ -3902,6 +3926,8 @@ class LiteLLM_ManagedVectorStoresTable(LiteLLMPydanticObjectBase): updated_at: Optional[datetime] litellm_credential_name: Optional[str] litellm_params: Optional[Dict[str, Any]] + team_id: Optional[str] + user_id: Optional[str] class ResponseLiteLLM_ManagedVectorStore(TypedDict, total=False): diff --git a/litellm/proxy/agent_endpoints/a2a_endpoints.py b/litellm/proxy/agent_endpoints/a2a_endpoints.py index a21f3291d31..24727aacd75 100644 --- a/litellm/proxy/agent_endpoints/a2a_endpoints.py +++ b/litellm/proxy/agent_endpoints/a2a_endpoints.py @@ -119,6 +119,11 @@ async def _handle_stream_message( tags=["[beta] A2A Agents"], dependencies=[Depends(user_api_key_auth)], ) +@router.get( + "/a2a/{agent_id}/.well-known/agent.json", + tags=["[beta] A2A Agents"], + dependencies=[Depends(user_api_key_auth)], +) async def get_agent_card( agent_id: str, request: Request, @@ -127,6 +132,10 @@ async def get_agent_card( """ Get the agent card for an agent (A2A discovery endpoint). + Supports both standard paths: + - /.well-known/agent-card.json + - /.well-known/agent.json + The URL in the agent card is rewritten to point to the LiteLLM proxy, so all subsequent A2A calls go through LiteLLM for logging and cost tracking. """ diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index 5e0a211906e..359bb944546 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -21,6 +21,8 @@ from litellm._logging import verbose_proxy_logger from litellm.caching.caching import DualCache from litellm.caching.dual_cache import LimitedSizeOrderedDict from litellm.constants import ( + CLI_JWT_EXPIRATION_HOURS, + CLI_JWT_TOKEN_NAME, DEFAULT_IN_MEMORY_TTL, DEFAULT_MANAGEMENT_OBJECT_IN_MEMORY_CACHE_TTL, DEFAULT_MAX_RECURSE_DEPTH, @@ -74,6 +76,75 @@ db_cache_expiry = DEFAULT_IN_MEMORY_TTL # refresh every 5s all_routes = LiteLLMRoutes.openai_routes.value + LiteLLMRoutes.management_routes.value +def _is_model_cost_zero( + model: Optional[Union[str, List[str]]], llm_router: Optional[Router] +) -> bool: + """ + Check if a model has zero cost (no configured pricing). + + Uses the router's get_model_group_info method to get pricing information. + + Args: + model: The model name or list of model names + llm_router: The LiteLLM router instance + + Returns: + bool: True if all costs for the model are zero, False otherwise + """ + if model is None or llm_router is None: + return False + + # Handle list of models + model_list = [model] if isinstance(model, str) else model + + for model_name in model_list: + try: + # Use router's get_model_group_info method directly for better reliability + model_group_info = llm_router.get_model_group_info(model_group=model_name) + + if model_group_info is None: + # Model not found or no pricing info available + # Conservative approach: assume it has cost + verbose_proxy_logger.debug( + f"No model group info found for {model_name}, assuming it has cost" + ) + return False + + # Check costs for this model + # Only allow bypass if BOTH costs are explicitly set to 0 (not None) + input_cost = model_group_info.input_cost_per_token + output_cost = model_group_info.output_cost_per_token + + # If costs are not explicitly configured (None), assume it has cost + if input_cost is None or output_cost is None: + verbose_proxy_logger.debug( + f"Model {model_name} has undefined cost (input: {input_cost}, output: {output_cost}), assuming it has cost" + ) + return False + + # If either cost is non-zero, return False + if input_cost > 0 or output_cost > 0: + verbose_proxy_logger.debug( + f"Model {model_name} has non-zero cost (input: {input_cost}, output: {output_cost})" + ) + return False + + # This model has zero cost explicitly configured + verbose_proxy_logger.debug( + f"Model {model_name} has zero cost explicitly configured (input: {input_cost}, output: {output_cost})" + ) + + except Exception as e: + # If we can't determine the cost, assume it has cost (conservative approach) + verbose_proxy_logger.debug( + f"Error checking cost for model {model_name}: {str(e)}, assuming it has cost" + ) + return False + + # All models checked have zero cost + return True + + async def common_checks( request_body: dict, team_object: Optional[LiteLLM_TeamTable], @@ -86,6 +157,7 @@ async def common_checks( proxy_logging_obj: ProxyLogging, valid_token: Optional[UserAPIKeyAuth], request: Request, + skip_budget_checks: bool = False, ) -> bool: """ Common checks across jwt + key-based auth. @@ -137,64 +209,66 @@ async def common_checks( user_object=user_object, ) - # 3. If team is in budget - await _team_max_budget_check( - team_object=team_object, - proxy_logging_obj=proxy_logging_obj, - valid_token=valid_token, - ) + # If this is a free model, skip all budget checks + if not skip_budget_checks: + # 3. If team is in budget + await _team_max_budget_check( + team_object=team_object, + proxy_logging_obj=proxy_logging_obj, + valid_token=valid_token, + ) - # 3.1. If organization is in budget - await _organization_max_budget_check( - valid_token=valid_token, - team_object=team_object, - prisma_client=prisma_client, - user_api_key_cache=user_api_key_cache, - proxy_logging_obj=proxy_logging_obj, - ) + # 3.1. If organization is in budget + await _organization_max_budget_check( + valid_token=valid_token, + team_object=team_object, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + proxy_logging_obj=proxy_logging_obj, + ) - await _tag_max_budget_check( - request_body=request_body, - prisma_client=prisma_client, - user_api_key_cache=user_api_key_cache, - proxy_logging_obj=proxy_logging_obj, - valid_token=valid_token, - ) + await _tag_max_budget_check( + request_body=request_body, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + proxy_logging_obj=proxy_logging_obj, + valid_token=valid_token, + ) - # 4. If user is in budget - ## 4.1 check personal budget, if personal key - if ( - (team_object is None or team_object.team_id is None) - and user_object is not None - and user_object.max_budget is not None - ): - user_budget = user_object.max_budget - if user_budget < user_object.spend: - raise litellm.BudgetExceededError( - current_cost=user_object.spend, - max_budget=user_budget, - message=f"ExceededBudget: User={user_object.user_id} over budget. Spend={user_object.spend}, Budget={user_budget}", - ) + # 4. If user is in budget + ## 4.1 check personal budget, if personal key + if ( + (team_object is None or team_object.team_id is None) + and user_object is not None + and user_object.max_budget is not None + ): + user_budget = user_object.max_budget + if user_budget < user_object.spend: + raise litellm.BudgetExceededError( + current_cost=user_object.spend, + max_budget=user_budget, + message=f"ExceededBudget: User={user_object.user_id} over budget. Spend={user_object.spend}, Budget={user_budget}", + ) - ## 4.2 check team member budget, if team key - await _check_team_member_budget( - team_object=team_object, - user_object=user_object, - valid_token=valid_token, - prisma_client=prisma_client, - user_api_key_cache=user_api_key_cache, - proxy_logging_obj=proxy_logging_obj, - ) + ## 4.2 check team member budget, if team key + await _check_team_member_budget( + team_object=team_object, + user_object=user_object, + valid_token=valid_token, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + proxy_logging_obj=proxy_logging_obj, + ) - # 5. If end_user ('user' passed to /chat/completions, /embeddings endpoint) is in budget - if end_user_object is not None and end_user_object.litellm_budget_table is not None: - end_user_budget = end_user_object.litellm_budget_table.max_budget - if end_user_budget is not None and end_user_object.spend > end_user_budget: - raise litellm.BudgetExceededError( - current_cost=end_user_object.spend, - max_budget=end_user_budget, - message=f"ExceededBudget: End User={end_user_object.user_id} over budget. Spend={end_user_object.spend}, Budget={end_user_budget}", - ) + # 5. If end_user ('user' passed to /chat/completions, /embeddings endpoint) is in budget + if end_user_object is not None and end_user_object.litellm_budget_table is not None: + end_user_budget = end_user_object.litellm_budget_table.max_budget + if end_user_budget is not None and end_user_object.spend > end_user_budget: + raise litellm.BudgetExceededError( + current_cost=end_user_object.spend, + max_budget=end_user_budget, + message=f"ExceededBudget: End User={end_user_object.user_id} over budget. Spend={end_user_object.spend}, Budget={end_user_budget}", + ) # 6. [OPTIONAL] If 'enforce_user_param' enabled - did developer pass in 'user' param for openai endpoints if ( @@ -245,6 +319,7 @@ async def common_checks( # 7. [OPTIONAL] If 'litellm.max_budget' is set (>0), is proxy under budget if ( litellm.max_budget > 0 + and not skip_budget_checks and global_proxy_spend is not None # only run global budget checks for OpenAI routes # Reason - the Admin UI should continue working if the proxy crosses it's global budget @@ -1014,8 +1089,10 @@ async def _get_fuzzy_user_object( ) if response is None and user_email is not None: + # Use case-insensitive query to handle emails with different casing + # This matches the pattern used in _check_duplicate_user_email response = await prisma_client.db.litellm_usertable.find_first( - where={"user_email": user_email}, + where={"user_email": {"equals": user_email, "mode": "insensitive"}}, include={"organization_memberships": True}, ) @@ -1602,7 +1679,10 @@ class ExperimentalUIJWTToken: user_info: LiteLLM_UserTable, team_id: Optional[str] = None ) -> str: """ - Generate a JWT token for CLI authentication with 24-hour expiration. + Generate a JWT token for CLI authentication with configurable expiration. + + The expiration time can be controlled via the LITELLM_CLI_JWT_EXPIRATION_HOURS + environment variable (defaults to 24 hours). Args: user_info: User information from the database @@ -1613,7 +1693,6 @@ class ExperimentalUIJWTToken: """ from datetime import timedelta - from litellm.constants import CLI_JWT_TOKEN_NAME from litellm.proxy.common_utils.encrypt_decrypt_utils import ( encrypt_value_helper, ) @@ -1621,8 +1700,8 @@ class ExperimentalUIJWTToken: if user_info.user_role is None: raise Exception("User role is required for CLI JWT login") - # Calculate expiration time (24 hours from now - matching old CLI key behavior) - expiration_time = get_utc_datetime() + timedelta(hours=24) + # Calculate expiration time (configurable via LITELLM_CLI_JWT_EXPIRATION_HOURS env var) + expiration_time = get_utc_datetime() + timedelta(hours=CLI_JWT_EXPIRATION_HOURS) # Format the expiration time as ISO 8601 string expires = expiration_time.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] + "+00:00" diff --git a/litellm/proxy/auth/auth_utils.py b/litellm/proxy/auth/auth_utils.py index 9b9a988c07a..2bd84a1d98d 100644 --- a/litellm/proxy/auth/auth_utils.py +++ b/litellm/proxy/auth/auth_utils.py @@ -758,11 +758,27 @@ def get_model_from_request( if match: model = match.group(1) + # If still not found, extract model from Google generateContent-style routes. + # These routes put the model in the path and allow "/" inside the model id. + # Examples: + # - /v1beta/models/gemini-2.0-flash:generateContent + # - /v1beta/models/bedrock/claude-sonnet-3.7:generateContent + # - /models/custom/ns/model:streamGenerateContent + if model is None and not route.lower().startswith("/vertex"): + google_match = re.search(r"/(?:v1beta|beta)/models/([^:]+):", route) + if google_match: + model = google_match.group(1) + + if model is None and not route.lower().startswith("/vertex"): + google_match = re.search(r"^/models/([^:]+):", route) + if google_match: + model = google_match.group(1) + # If still not found, extract from Vertex AI passthrough route # Pattern: /vertex_ai/.../models/{model_id}:* # Example: /vertex_ai/v1/.../models/gemini-1.5-pro:generateContent - if model is None and "/vertex" in route.lower(): - vertex_match = re.search(r"/models/([^/:]+)", route) + if model is None and route.lower().startswith("/vertex"): + vertex_match = re.search(r"/models/([^:]+)", route) if vertex_match: model = vertex_match.group(1) diff --git a/litellm/proxy/auth/login_utils.py b/litellm/proxy/auth/login_utils.py index 939cfefadcc..4df773dec2b 100644 --- a/litellm/proxy/auth/login_utils.py +++ b/litellm/proxy/auth/login_utils.py @@ -34,59 +34,6 @@ from litellm.secret_managers.main import get_secret_bool from litellm.types.proxy.ui_sso import ReturnedUITokenObject -async def expire_previous_ui_session_tokens( - user_id: str, prisma_client: Optional[PrismaClient] -) -> None: - """ - Expire (block) all other valid UI session tokens for a user. - - This prevents accumulation of multiple valid UI session tokens that - are supposed to be short-lived test keys. Only affects keys with - team_id = "litellm-dashboard" and that haven't expired yet. - - Args: - user_id: The user ID whose previous UI session tokens should be expired - prisma_client: Database client for performing the update - """ - if prisma_client is None: - return - - try: - from datetime import datetime, timezone - - current_time = datetime.now(timezone.utc) - - # Find all unblocked AND non-expired UI session tokens for this user - ui_session_tokens = await prisma_client.db.litellm_verificationtoken.find_many( - where={ - "user_id": user_id, - "team_id": "litellm-dashboard", - "OR": [ - {"blocked": None}, # Tokens that have never been blocked (null) - {"blocked": False}, # Tokens explicitly set to not blocked - ], - "expires": {"gt": current_time}, # Only get tokens that haven't expired - } - ) - - if not ui_session_tokens: - return - - # Block all the found tokens - tokens_to_block = [token.token for token in ui_session_tokens if token.token] - - if tokens_to_block: - await prisma_client.db.litellm_verificationtoken.update_many( - where={"token": {"in": tokens_to_block}}, - data={"blocked": True} - ) - - except Exception: - # Silently fail - don't block login if cleanup fails - # This is a best-effort operation - pass - - def get_ui_credentials(master_key: Optional[str]) -> tuple[str, str]: """ Get UI username and password from environment variables or master key. @@ -227,10 +174,6 @@ async def authenticate_user( # noqa: PLR0915 ) if os.getenv("DATABASE_URL") is not None: - # Expire any previous UI session tokens for this user - await expire_previous_ui_session_tokens( - user_id=key_user_id, prisma_client=prisma_client - ) response = await generate_key_helper_fn( request_type="key", **{ @@ -317,11 +260,6 @@ async def authenticate_user( # noqa: PLR0915 password.encode("utf-8"), _password.encode("utf-8") ) or secrets.compare_digest(hash_password.encode("utf-8"), _password.encode("utf-8")): if os.getenv("DATABASE_URL") is not None: - # Expire any previous UI session tokens for this user - await expire_previous_ui_session_tokens( - user_id=user_id, prisma_client=prisma_client - ) - response = await generate_key_helper_fn( request_type="key", **{ # type: ignore diff --git a/litellm/proxy/auth/route_checks.py b/litellm/proxy/auth/route_checks.py index 96a70b8016f..fa63ff01b3a 100644 --- a/litellm/proxy/auth/route_checks.py +++ b/litellm/proxy/auth/route_checks.py @@ -392,7 +392,15 @@ class RouteChecks: # Ensure route is a string before attempting regex matching if not isinstance(route, str): return False - pattern = re.sub(r"\{[^}]+\}", r"[^/]+", pattern) + + def _placeholder_to_regex(match: re.Match) -> str: + placeholder = match.group(0).strip("{}") + if placeholder.endswith(":path"): + # allow "/" in the placeholder value, but don't eat the route suffix after ":" + return r"[^:]+" + return r"[^/]+" + + pattern = re.sub(r"\{[^}]+\}", _placeholder_to_regex, pattern) # Anchor the pattern to match the entire string pattern = f"^{pattern}$" if re.match(pattern, route): diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index 7e7c7c8c90c..a153c6e51cc 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -53,6 +53,7 @@ from litellm.proxy.auth.handle_jwt import JWTAuthManager, JWTHandler from litellm.proxy.auth.oauth2_check import Oauth2Handler from litellm.proxy.auth.oauth2_proxy_hook import handle_oauth2_proxy_request from litellm.proxy.auth.route_checks import RouteChecks +from litellm.proxy.common_utils.cache_coordinator import EventDrivenCacheCoordinator from litellm.proxy.common_utils.http_parsing_utils import ( _read_request_body, _safe_get_request_headers, @@ -211,6 +212,33 @@ def update_valid_token_with_end_user_params( return valid_token +# Reusable coordinator for global spend to prevent cache stampede +_global_spend_coordinator = EventDrivenCacheCoordinator(log_prefix="[GLOBAL SPEND]") + + +async def _fetch_global_spend_with_event_coordination( + cache_key: str, + user_api_key_cache: DualCache, + prisma_client: PrismaClient, +) -> Optional[float]: + """ + Fetch global spend with event-driven coordination to prevent cache stampede. + Uses EventDrivenCacheCoordinator: first request queries DB and signals others when done. + """ + + async def _load_global_spend() -> Optional[float]: + sql_query = """SELECT SUM(spend) AS total_spend FROM "MonthlyGlobalSpend";""" + response = await prisma_client.db.query_raw(query=sql_query) + val = response[0]["total_spend"] + return float(val) if val is not None else None + + return await _global_spend_coordinator.get_or_load( + cache_key=cache_key, + cache=user_api_key_cache, + load_fn=_load_global_spend, + ) + + async def get_global_proxy_spend( litellm_proxy_admin_name: str, user_api_key_cache: DualCache, @@ -219,25 +247,14 @@ async def get_global_proxy_spend( proxy_logging_obj: ProxyLogging, ) -> Optional[float]: global_proxy_spend = None - if litellm.max_budget > 0: # user set proxy max budget - # check cache - global_proxy_spend = await user_api_key_cache.async_get_cache( - key="{}:spend".format(litellm_proxy_admin_name) + if litellm.max_budget > 0 and prisma_client is not None: # user set proxy max budget + # Use event-driven coordination to prevent cache stampede + cache_key = "{}:spend".format(litellm_proxy_admin_name) + global_proxy_spend = await _fetch_global_spend_with_event_coordination( + cache_key=cache_key, + user_api_key_cache=user_api_key_cache, + prisma_client=prisma_client, ) - if global_proxy_spend is None and prisma_client is not None: - # get from db - sql_query = ( - """SELECT SUM(spend) as total_spend FROM "MonthlyGlobalSpend";""" - ) - - response = await prisma_client.db.query_raw(query=sql_query) - - global_proxy_spend = response[0]["total_spend"] - - await user_api_key_cache.async_set_cache( - key="{}:spend".format(litellm_proxy_admin_name), - value=global_proxy_spend, - ) if global_proxy_spend is not None: user_info = CallInfo( user_id=litellm_proxy_admin_name, @@ -587,6 +604,21 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 if team_object is not None else None, ) + + # Check if model has zero cost - if so, skip all budget checks + model = get_model_from_request(request_data, route) + skip_budget_checks = False + if model is not None and llm_router is not None: + from litellm.proxy.auth.auth_checks import _is_model_cost_zero + + skip_budget_checks = _is_model_cost_zero( + model=model, llm_router=llm_router + ) + if skip_budget_checks: + verbose_proxy_logger.info( + f"Skipping all budget checks for zero-cost model: {model}" + ) + # run through common checks _ = await common_checks( request=request, @@ -600,6 +632,7 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 llm_router=llm_router, proxy_logging_obj=proxy_logging_obj, valid_token=valid_token, + skip_budget_checks=skip_budget_checks, ) # return UserAPIKeyAuth object @@ -991,8 +1024,22 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 ) user_obj = None + # Check 2a. Check if model has zero cost - if so, skip all budget checks + model = get_model_from_request(request_data, route) + skip_budget_checks = False + if model is not None and llm_router is not None: + from litellm.proxy.auth.auth_checks import _is_model_cost_zero + + skip_budget_checks = _is_model_cost_zero( + model=model, llm_router=llm_router + ) + if skip_budget_checks: + verbose_proxy_logger.info( + f"Skipping all budget checks for zero-cost model: {model}" + ) + # Check 3. Check if user is in their team budget - if valid_token.team_member_spend is not None: + if not skip_budget_checks and valid_token.team_member_spend is not None: if prisma_client is not None: _cache_key = f"{valid_token.team_id}_{valid_token.user_id}" @@ -1056,46 +1103,47 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 param=abbreviate_api_key(api_key=api_key), ) - # Check 4. Token Spend is under budget - if RouteChecks.is_llm_api_route(route=route): - await _virtual_key_max_budget_check( + if not skip_budget_checks: + # Check 4. Token Spend is under budget + if RouteChecks.is_llm_api_route(route=route): + await _virtual_key_max_budget_check( + valid_token=valid_token, + proxy_logging_obj=proxy_logging_obj, + user_obj=user_obj, + ) + + # Check 5. Max Budget Alert Check + await _virtual_key_max_budget_alert_check( valid_token=valid_token, proxy_logging_obj=proxy_logging_obj, user_obj=user_obj, ) - # Check 5. Max Budget Alert Check - await _virtual_key_max_budget_alert_check( - valid_token=valid_token, - proxy_logging_obj=proxy_logging_obj, - user_obj=user_obj, - ) - - # Check 6. Soft Budget Check - await _virtual_key_soft_budget_check( - valid_token=valid_token, - proxy_logging_obj=proxy_logging_obj, - user_obj=user_obj, - ) - - # Check 5. Token Model Spend is under Model budget - max_budget_per_model = valid_token.model_max_budget - current_model = request_data.get("model", None) - - if ( - max_budget_per_model is not None - and isinstance(max_budget_per_model, dict) - and len(max_budget_per_model) > 0 - and prisma_client is not None - and current_model is not None - and valid_token.token is not None - ): - ## GET THE SPEND FOR THIS MODEL - await model_max_budget_limiter.is_key_within_model_budget( - user_api_key_dict=valid_token, - model=current_model, + # Check 6. Soft Budget Check + await _virtual_key_soft_budget_check( + valid_token=valid_token, + proxy_logging_obj=proxy_logging_obj, + user_obj=user_obj, ) + # Check 5. Token Model Spend is under Model budget + max_budget_per_model = valid_token.model_max_budget + current_model = request_data.get("model", None) + + if ( + max_budget_per_model is not None + and isinstance(max_budget_per_model, dict) + and len(max_budget_per_model) > 0 + and prisma_client is not None + and current_model is not None + and valid_token.token is not None + ): + ## GET THE SPEND FOR THIS MODEL + await model_max_budget_limiter.is_key_within_model_budget( + user_api_key_dict=valid_token, + model=current_model, + ) + # Check 6: Additional Common Checks across jwt + key auth if valid_token.team_id is not None: _team_obj: Optional[LiteLLM_TeamTable] = LiteLLM_TeamTable( @@ -1120,21 +1168,12 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 if ( litellm.max_budget > 0 and prisma_client is not None ): # user set proxy max budget - # check cache - global_proxy_spend = await user_api_key_cache.async_get_cache( - key="{}:spend".format(litellm_proxy_admin_name) + cache_key = "{}:spend".format(litellm_proxy_admin_name) + global_proxy_spend = await _fetch_global_spend_with_event_coordination( + cache_key=cache_key, + user_api_key_cache=user_api_key_cache, + prisma_client=prisma_client, ) - if global_proxy_spend is None: - # get from db - sql_query = """SELECT SUM(spend) as total_spend FROM "MonthlyGlobalSpend";""" - - response = await prisma_client.db.query_raw(query=sql_query) - - global_proxy_spend = response[0]["total_spend"] - await user_api_key_cache.async_set_cache( - key="{}:spend".format(litellm_proxy_admin_name), - value=global_proxy_spend, - ) if global_proxy_spend is not None: call_info = CallInfo( @@ -1163,6 +1202,7 @@ async def _user_api_key_auth_builder( # noqa: PLR0915 llm_router=llm_router, proxy_logging_obj=proxy_logging_obj, valid_token=valid_token, + skip_budget_checks=skip_budget_checks, ) # Token passed all checks if valid_token is None: @@ -1239,7 +1279,6 @@ async def user_api_key_auth( request_data=request_data, request=request ) route: str = get_request_route(request=request) - ## CHECK IF ROUTE IS ALLOWED user_api_key_auth_obj = await _user_api_key_auth_builder( @@ -1263,7 +1302,6 @@ async def user_api_key_auth( user_api_key_auth_obj.end_user_id = end_user_id user_api_key_auth_obj.request_route = normalize_request_route(route) - return user_api_key_auth_obj diff --git a/litellm/proxy/batches_endpoints/endpoints.py b/litellm/proxy/batches_endpoints/endpoints.py index 086105042e8..f47e2e1667b 100644 --- a/litellm/proxy/batches_endpoints/endpoints.py +++ b/litellm/proxy/batches_endpoints/endpoints.py @@ -24,9 +24,7 @@ from litellm.proxy.openai_files_endpoints.common_utils import ( _is_base64_encoded_unified_file_id, decode_model_from_file_id, encode_file_id_with_model, - get_batch_id_from_unified_batch_id, get_credentials_for_model, - get_model_id_from_unified_batch_id, get_models_from_unified_file_id, get_original_file_id, prepare_data_with_credentials, @@ -382,25 +380,6 @@ async def retrieve_batch( **data # type: ignore ) - # Re-encode all IDs in the response - if response: - if hasattr(response, "id") and response.id: - response.id = batch_id # Keep the encoded batch ID - - if hasattr(response, "input_file_id") and response.input_file_id: - response.input_file_id = encode_file_id_with_model( - file_id=response.input_file_id, model=model_from_id - ) - - if hasattr(response, "output_file_id") and response.output_file_id: - response.output_file_id = encode_file_id_with_model( - file_id=response.output_file_id, model=model_from_id - ) - - if hasattr(response, "error_file_id") and response.error_file_id: - response.error_file_id = encode_file_id_with_model( - file_id=response.error_file_id, model=model_from_id - ) verbose_proxy_logger.debug( f"Retrieved batch using model: {model_from_id}, original_id: {original_batch_id}" @@ -542,14 +521,26 @@ async def list_batches( route_type="alist_batches", ) - model_param = ( + # Try to use managed objects table for listing batches (returns encoded IDs) + managed_files_obj = proxy_logging_obj.get_proxy_hook("managed_files") + if managed_files_obj is not None and hasattr(managed_files_obj, "list_user_batches"): + verbose_proxy_logger.debug( + "Using managed objects table for batch listing" + ) + response = await managed_files_obj.list_user_batches( + user_api_key_dict=user_api_key_dict, + limit=limit, + after=after, + provider=provider, + target_model_names=target_model_names, + llm_router=llm_router, + ) + elif (model_param := ( data.get("model") or request.query_params.get("model") or request.headers.get("x-litellm-model") - ) - - # SCENARIO 2: Use model-based routing from header/query/body - if model_param: + )): + # SCENARIO 2: Use model-based routing from header/query/body credentials = get_credentials_for_model( llm_router=llm_router, model_id=model_param, @@ -683,15 +674,31 @@ async def cancel_batch( data: Dict = {} try: - data = await _read_request_body(request=request) - verbose_proxy_logger.debug( - "Request received by LiteLLM:\n{}".format(json.dumps(data, indent=4)), - ) - # Check for encoded batch ID with model info model_from_id = decode_model_from_file_id(batch_id) + + # Create CancelBatchRequest with batch_id to enable ownership checking + _cancel_batch_request = CancelBatchRequest( + batch_id=batch_id, + ) + data = cast(dict, _cancel_batch_request) + unified_batch_id = _is_base64_encoded_unified_file_id(batch_id) + base_llm_response_processor = ProxyBaseLLMRequestProcessing(data=data) + ( + data, + litellm_logging_obj, + ) = await base_llm_response_processor.common_processing_pre_call_logic( + request=request, + general_settings=general_settings, + user_api_key_dict=user_api_key_dict, + version=version, + proxy_logging_obj=proxy_logging_obj, + proxy_config=proxy_config, + route_type="acancel_batch", + ) + # Include original request and headers in the data data = await add_litellm_data_to_request( data=data, @@ -739,17 +746,13 @@ async def cancel_batch( }, ) - model = ( - get_model_id_from_unified_batch_id(unified_batch_id) - if unified_batch_id - else None - ) - - model_batch_id = get_batch_id_from_unified_batch_id(unified_batch_id) - - data["batch_id"] = model_batch_id - - response = await llm_router.acancel_batch(model=model, **data) # type: ignore + # Hook has already extracted model and unwrapped batch_id into data dict + 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"] # SCENARIO 3: Fallback to custom_llm_provider (uses env variables) else: @@ -757,12 +760,20 @@ async def cancel_batch( custom_llm_provider = ( provider or data.pop("custom_llm_provider", None) or "openai" ) + # Extract batch_id from data to avoid "multiple values for keyword argument" error + # data was cast from CancelBatchRequest which already contains batch_id + data.pop("batch_id", None) _cancel_batch_data = CancelBatchRequest(batch_id=batch_id, **data) response = await litellm.acancel_batch( custom_llm_provider=custom_llm_provider, # type: ignore **_cancel_batch_data, ) + ### 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 ### asyncio.create_task( proxy_logging_obj.update_request_status( diff --git a/litellm/proxy/cached_logo.jpg b/litellm/proxy/cached_logo.jpg index da4faf5b5ca6c812a73e3c1a74d93ea6f6afa682..a10a1d249690c6148a6ba40893e7c0ada44adec0 100644 GIT binary patch literal 24694 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�R2V*J6MXq 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 F{u}R=q(uM# literal 50535 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%0DeNB?(iW!XDl3Is3M{ws&87hHz5Vz*`;sLjzL^ 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 QPXGV_07*qoM6N<$f~<5rQUCw| diff --git a/litellm/proxy/client/cli/commands/auth.py b/litellm/proxy/client/cli/commands/auth.py index bdc8d56d1c3..64b3233536f 100644 --- a/litellm/proxy/client/cli/commands/auth.py +++ b/litellm/proxy/client/cli/commands/auth.py @@ -11,6 +11,8 @@ import requests from rich.console import Console from rich.table import Table +from litellm.constants import CLI_JWT_EXPIRATION_HOURS + # Token storage utilities def get_token_file_path() -> str: @@ -274,6 +276,71 @@ def prompt_team_selection_fallback( # Polling-based authentication - no local server needed +def _poll_for_ready_data( + url: str, + *, + total_timeout: int = 300, + poll_interval: int = 2, + request_timeout: int = 10, + pending_message: Optional[str] = None, + pending_log_every: int = 10, + other_status_message: Optional[str] = None, + other_status_log_every: int = 10, + http_error_log_every: int = 10, + connection_error_log_every: int = 10, +) -> Optional[Dict[str, Any]]: + for attempt in range(total_timeout // poll_interval): + try: + response = requests.get(url, timeout=request_timeout) + if response.status_code == 200: + data = response.json() + status = data.get("status") + if status == "ready": + return data + if status == "pending": + if ( + pending_message + and pending_log_every > 0 + and attempt % pending_log_every == 0 + ): + click.echo(pending_message) + elif ( + other_status_message + and other_status_log_every > 0 + and attempt % other_status_log_every == 0 + ): + click.echo(other_status_message) + elif http_error_log_every > 0 and attempt % http_error_log_every == 0: + click.echo(f"Polling error: HTTP {response.status_code}") + except requests.RequestException as e: + if ( + connection_error_log_every > 0 + and attempt % connection_error_log_every == 0 + ): + click.echo(f"Connection error (will retry): {e}") + time.sleep(poll_interval) + return None + + +def _normalize_teams(teams, team_details): + """If team_details are a + + Args: + teams (_type_): _description_ + team_details (_type_): _description_ + + Returns: + _type_: _description_ + """ + if isinstance(team_details, list) and team_details: + return [ + {"team_id": i.get("team_id") or i.get("id"), "team_alias": i.get("team_alias")} + for i in team_details + if isinstance(i, dict) and (i.get("team_id") or i.get("id")) + ] + if isinstance(teams, list): + return [{"team_id": str(t), "team_alias": None} for t in teams] + return [] def _poll_for_authentication(base_url: str, key_id: str) -> Optional[dict]: @@ -284,106 +351,58 @@ def _poll_for_authentication(base_url: str, key_id: str) -> Optional[dict]: Dictionary with authentication data if successful, None otherwise """ poll_url = f"{base_url}/sso/cli/poll/{key_id}" - timeout = 300 # 5 minute timeout - poll_interval = 2 # Poll every 2 seconds + data = _poll_for_ready_data( + poll_url, + pending_message="Still waiting for authentication...", + ) + if not data: + return None + if data.get("requires_team_selection"): + teams = data.get("teams", []) + team_details = data.get("team_details") + user_id = data.get("user_id") + normalized_teams: List[Dict[str, Any]] = _normalize_teams(teams, team_details) + if not normalized_teams: + click.echo("⚠️ No teams available for selection.") + return None - for attempt in range(timeout // poll_interval): - try: - response = requests.get(poll_url, timeout=10) - if response.status_code == 200: - data = response.json() - if data.get("status") == "ready": - # Check if we need team selection first - if data.get("requires_team_selection"): - # Server returned teams list without JWT - need to select team. - # Newer servers may also return "team_details" containing - # objects with both team_id and team_alias. We prefer those - # for display, but continue to support the legacy list of - # team IDs for backwards compatibility. - teams = data.get("teams", []) - team_details = data.get("team_details") - user_id = data.get("user_id") + # User has multiple teams - let them select + jwt_with_team = _handle_team_selection_during_polling( + base_url=base_url, + key_id=key_id, + teams=normalized_teams, + ) - # Build a normalized list of team objects that always have - # "team_id" and optionally "team_alias". - normalized_teams: List[Dict[str, Any]] = [] - if isinstance(team_details, list) and team_details: - for item in team_details: - if isinstance(item, dict): - team_id = item.get("team_id") or item.get("id") - if team_id is None: - continue - normalized_teams.append( - { - "team_id": team_id, - "team_alias": item.get("team_alias"), - } - ) - elif isinstance(teams, list): - for t in teams: - normalized_teams.append( - { - "team_id": str(t), - "team_alias": None, - } - ) + # Use the team-specific JWT if selection succeeded + if jwt_with_team: + return { + "api_key": jwt_with_team, + "user_id": user_id, + "teams": teams, + "team_id": None, # Set by server in JWT + } - if normalized_teams and len(normalized_teams) > 1: - # User has multiple teams - let them select - jwt_with_team = _handle_team_selection_during_polling( - base_url=base_url, - key_id=key_id, - teams=normalized_teams, - ) + click.echo("❌ Team selection cancelled or JWT generation failed.") + return None - # Use the team-specific JWT if selection succeeded - if jwt_with_team: - return { - "api_key": jwt_with_team, - "user_id": user_id, - "teams": teams, - "team_id": None, # Set by server in JWT - } - else: - # Selection failed or was skipped - poll again without team_id - click.echo("⚠️ Team selection skipped, retrying...") - continue - else: - # Shouldn't happen, but fallback - click.echo("⚠️ No teams available, retrying...") - continue - else: - # JWT is ready (single team or team already selected) - api_key = data.get("key") - user_id = data.get("user_id") - teams = data.get("teams", []) - team_id = data.get("team_id") + # JWT is ready (single team or team already selected) + api_key = data.get("key") + user_id = data.get("user_id") + teams = data.get("teams", []) + team_id = data.get("team_id") - # Show which team was assigned - if team_id and len(teams) == 1: - click.echo(f"\n✅ Automatically assigned to team: {team_id}") + # Show which team was assigned + if team_id and len(teams) == 1: + click.echo(f"\n✅ Automatically assigned to team: {team_id}") - if api_key: - return { - "api_key": api_key, - "user_id": user_id, - "teams": teams, - "team_id": team_id, - } - elif data.get("status") == "pending": - # Still pending - if attempt % 10 == 0: # Show progress every 20 seconds - click.echo("Still waiting for authentication...") - else: - click.echo(f"Polling error: HTTP {response.status_code}") + if api_key: + return { + "api_key": api_key, + "user_id": user_id, + "teams": teams, + "team_id": team_id, + } - except requests.RequestException as e: - if attempt % 10 == 0: - click.echo(f"Connection error (will retry): {e}") - - time.sleep(poll_interval) - - # Timeout reached return None @@ -416,25 +435,21 @@ def _handle_team_selection_during_polling( click.echo(f"\n🔄 Generating JWT for team: {team_id}") - # Re-poll with team_id to get JWT with correct team - try: - poll_url = f"{base_url}/sso/cli/poll/{key_id}?team_id={team_id}" - response = requests.get(poll_url, timeout=10) - - if response.status_code == 200: - data = response.json() - if data.get("status") == "ready": - jwt_token = data.get("key") - if jwt_token: - click.echo(f"✅ Successfully generated JWT for team: {team_id}") - return jwt_token - - click.echo(f"❌ Failed to get JWT with team. Status: {response.status_code}") + poll_url = f"{base_url}/sso/cli/poll/{key_id}?team_id={team_id}" + data = _poll_for_ready_data( + poll_url, + pending_message="Still waiting for team authentication...", + other_status_message="Waiting for team authentication to complete...", + http_error_log_every=10, + ) + if not data: return None + jwt_token = data.get("key") + if jwt_token: + click.echo(f"✅ Successfully generated JWT for team: {team_id}") + return jwt_token - except Exception as e: - click.echo(f"❌ Error getting JWT with team: {e}") - return None + return None def _render_and_prompt_for_team_selection(teams: List[Dict[str, Any]]) -> Optional[str]: @@ -592,8 +607,8 @@ def whoami(): age_hours = (time.time() - timestamp) / 3600 click.echo(f"Token age: {age_hours:.1f} hours") - if age_hours > 24: - click.echo("⚠️ Warning: Token is more than 24 hours old and may have expired.") + if age_hours > CLI_JWT_EXPIRATION_HOURS: + click.echo(f"⚠️ Warning: Token is more than {CLI_JWT_EXPIRATION_HOURS} hours old and may have expired.") # Export functions for use by other CLI commands diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index 0d3e61b75c7..769c250d9fc 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -237,6 +237,88 @@ async def create_response( ) +def _override_openai_response_model( + *, + response_obj: Any, + requested_model: str, + log_context: str, +) -> None: + """ + Force the OpenAI-compatible `model` field in the response to match what the client requested. + + LiteLLM internally prefixes some provider/deployment model identifiers (e.g. `hosted_vllm/...`). + That internal identifier should not be returned to clients in the OpenAI `model` field. + + Note: This is intentionally verbose. A model mismatch is a useful signal that an internal + model identifier is being stamped/preserved somewhere in the request/response pipeline. + We log mismatches as warnings (and then restamp to the client-requested value) so these + paths stay observable for maintainers/operators without breaking client compatibility. + + Errors are reserved for cases where the proxy cannot read/override the response model field. + + Exception: If a fallback occurred (indicated by x-litellm-attempted-fallbacks header), + we should preserve the actual model that was used (the fallback model) rather than + overriding it with the originally requested model. + """ + if not requested_model: + return + + # Check if a fallback occurred - if so, preserve the actual model used + hidden_params = getattr(response_obj, "_hidden_params", {}) or {} + if isinstance(hidden_params, dict): + fallback_headers = hidden_params.get("additional_headers", {}) or {} + attempted_fallbacks = fallback_headers.get("x-litellm-attempted-fallbacks", None) + if attempted_fallbacks is not None and attempted_fallbacks > 0: + # A fallback occurred - preserve the actual model that was used + verbose_proxy_logger.debug( + "%s: fallback detected (attempted_fallbacks=%d), preserving actual model used instead of overriding to requested model.", + log_context, + attempted_fallbacks, + ) + return + + if isinstance(response_obj, dict): + downstream_model = response_obj.get("model") + if downstream_model != requested_model: + verbose_proxy_logger.warning( + "%s: response model mismatch - requested=%r downstream=%r. Overriding response['model'] to requested model.", + log_context, + requested_model, + downstream_model, + ) + response_obj["model"] = requested_model + return + + if not hasattr(response_obj, "model"): + verbose_proxy_logger.error( + "%s: cannot override response model; missing `model` attribute. response_type=%s", + log_context, + type(response_obj), + ) + return + + downstream_model = getattr(response_obj, "model", None) + if downstream_model != requested_model: + verbose_proxy_logger.warning( + "%s: response model mismatch - requested=%r downstream=%r. Overriding response.model to requested model.", + log_context, + requested_model, + downstream_model, + ) + + try: + setattr(response_obj, "model", requested_model) + except Exception as e: + verbose_proxy_logger.error( + "%s: failed to override response.model=%r on response_type=%s. error=%s", + log_context, + requested_model, + type(response_obj), + str(e), + exc_info=True, + ) + + def _get_cost_breakdown_from_logging_obj( litellm_logging_obj: Optional[LiteLLMLoggingObj], ) -> Tuple[Optional[float], Optional[float], Optional[float], Optional[float]]: @@ -392,8 +474,10 @@ class ProxyBaseLLMRequestProcessing: "acreate_batch", "aretrieve_batch", "alist_batches", + "acancel_batch", "afile_content", "afile_retrieve", + "afile_delete", "atext_completion", "acreate_fine_tuning_job", "acancel_fine_tuning_job", @@ -532,31 +616,21 @@ class ProxyBaseLLMRequestProcessing: user_api_key_dict=user_api_key_dict, data=self.data, call_type=route_type # type: ignore ) - # Apply hierarchical router_settings (Key > Team > Global) - if llm_router is not None and proxy_config is not None: - from litellm.proxy.proxy_server import prisma_client - - router_settings = await proxy_config._get_hierarchical_router_settings( - user_api_key_dict=user_api_key_dict, - prisma_client=prisma_client, - ) - - # If router_settings found (from key, team, or global), apply them - # This ensures key/team settings override global settings - if router_settings is not None and router_settings: - # Get model_list from current router - model_list = llm_router.get_model_list() - if model_list is not None: - # Create user_config with model_list and router_settings - # This creates a per-request router with the hierarchical settings - user_config = {"model_list": model_list, **router_settings} - self.data["user_config"] = user_config - if "messages" in self.data and self.data["messages"]: logging_obj.update_messages(self.data["messages"]) return self.data, logging_obj + @staticmethod + def _get_model_id_from_response(hidden_params: dict, data: dict) -> str: + """Extract model_id from hidden_params with fallback to litellm_metadata.""" + model_id = hidden_params.get("model_id", None) or "" + if not model_id: + litellm_metadata = data.get("litellm_metadata", {}) or {} + model_info = litellm_metadata.get("model_info", {}) or {} + model_id = model_info.get("id", "") or "" + return model_id + async def base_process_llm_request( self, request: Request, @@ -606,6 +680,8 @@ class ProxyBaseLLMRequestProcessing: "aget_interaction", "adelete_interaction", "acancel_interaction", + "acancel_batch", + "afile_delete", ], proxy_logging_obj: ProxyLogging, general_settings: dict, @@ -625,6 +701,9 @@ class ProxyBaseLLMRequestProcessing: """ Common request processing logic for both chat completions and responses API endpoints """ + requested_model_from_client: Optional[str] = ( + self.data.get("model") if isinstance(self.data.get("model"), str) else None + ) if verbose_proxy_logger.isEnabledFor(logging.DEBUG): verbose_proxy_logger.debug( "Request received by LiteLLM:\n{}".format( @@ -650,11 +729,15 @@ class ProxyBaseLLMRequestProcessing: ) tasks = [] + # Start the moderation check (during_call_hook) as early as possible + # This gives it a head start to mask/validate input while the proxy handles routing tasks.append( - proxy_logging_obj.during_call_hook( - data=self.data, - user_api_key_dict=user_api_key_dict, - call_type=route_type, # type: ignore + asyncio.create_task( + proxy_logging_obj.during_call_hook( + data=self.data, + user_api_key_dict=user_api_key_dict, + call_type=route_type, # type: ignore + ) ) ) @@ -682,21 +765,17 @@ class ProxyBaseLLMRequestProcessing: response = responses[1] hidden_params = getattr(response, "_hidden_params", {}) or {} - model_id = hidden_params.get("model_id", None) or "" + model_id = self._get_model_id_from_response(hidden_params, self.data) - # Fallback: extract model_id from litellm_metadata if not in hidden_params - if not model_id: - litellm_metadata = self.data.get("litellm_metadata", {}) or {} - model_info = litellm_metadata.get("model_info", {}) or {} - model_id = model_info.get("id", "") or "" - - cache_key = hidden_params.get("cache_key", None) or "" - api_base = hidden_params.get("api_base", None) or "" - response_cost = hidden_params.get("response_cost", None) or "" - fastest_response_batch_completion = hidden_params.get( - "fastest_response_batch_completion", None + cache_key, api_base, response_cost = ( + hidden_params.get("cache_key", None) or "", + hidden_params.get("api_base", None) or "", + hidden_params.get("response_cost", None) or "", + ) + fastest_response_batch_completion, additional_headers = ( + hidden_params.get("fastest_response_batch_completion", None), + hidden_params.get("additional_headers", {}) or {}, ) - additional_headers: dict = hidden_params.get("additional_headers", {}) or {} # Post Call Processing if llm_router is not None: @@ -726,6 +805,22 @@ class ProxyBaseLLMRequestProcessing: litellm_logging_obj=logging_obj, **additional_headers, ) + + # Call response headers hook for streaming success + callback_headers = await proxy_logging_obj.post_call_response_headers_hook( + data=self.data, + user_api_key_dict=user_api_key_dict, + response=response, + ) + if callback_headers: + custom_headers.update(callback_headers) + + # Preserve the original client-requested model (pre-alias mapping) for downstream + # streaming generators. Pre-call processing can rewrite `self.data["model"]` for + # aliasing/routing, but the OpenAI-compatible response `model` field should reflect + # what the client sent. + if requested_model_from_client: + self.data["_litellm_client_requested_model"] = requested_model_from_client if route_type == "allm_passthrough_route": # Check if response is an async generator if self._is_streaming_response(response): @@ -785,6 +880,15 @@ class ProxyBaseLLMRequestProcessing: data=self.data, user_api_key_dict=user_api_key_dict, response=response ) + # Always return the client-requested model name (not provider-prefixed internal identifiers) + # for OpenAI-compatible responses. + if requested_model_from_client: + _override_openai_response_model( + response_obj=response, + requested_model=requested_model_from_client, + log_context=f"litellm_call_id={logging_obj.litellm_call_id}", + ) + hidden_params = ( getattr(response, "_hidden_params", {}) or {} ) # get any updated response headers @@ -807,6 +911,16 @@ class ProxyBaseLLMRequestProcessing: **additional_headers, ) ) + + # Call response headers hook for non-streaming success + callback_headers = await proxy_logging_obj.post_call_response_headers_hook( + data=self.data, + user_api_key_dict=user_api_key_dict, + response=response, + ) + if callback_headers: + fastapi_response.headers.update(callback_headers) + await check_response_size_is_safe(response=response) return response @@ -965,6 +1079,18 @@ class ProxyBaseLLMRequestProcessing: headers = get_response_headers(dict(_response_headers)) headers.update(custom_headers) + # Call response headers hook for failure + try: + callback_headers = await proxy_logging_obj.post_call_response_headers_hook( + data=self.data, + user_api_key_dict=user_api_key_dict, + response=None, + ) + if callback_headers: + headers.update(callback_headers) + except Exception: + pass + if isinstance(e, HTTPException): raise ProxyException( message=getattr(e, "detail", str(e)), diff --git a/litellm/proxy/common_utils/cache_coordinator.py b/litellm/proxy/common_utils/cache_coordinator.py new file mode 100644 index 00000000000..4eceb83af5f --- /dev/null +++ b/litellm/proxy/common_utils/cache_coordinator.py @@ -0,0 +1,192 @@ +""" +Event-driven cache coordinator to prevent cache stampede. + +Use this when many requests can miss the same cache key at once (e.g. after +expiry or restart). Without coordination, they would all run the expensive +load (DB query, API call) in parallel and overload the backend. + +This module ensures only one request performs the load; the rest wait for a +signal and then read the freshly cached value. Reuse it for any cache-aside +pattern: global spend, feature flags, config, or other shared read-through data. +""" + +import asyncio +import time +from typing import Any, Awaitable, Callable, Optional, Protocol, TypeVar + +from litellm._logging import verbose_proxy_logger + +T = TypeVar("T") + + +class AsyncCacheProtocol(Protocol): + """Protocol for cache backends used by EventDrivenCacheCoordinator.""" + + async def async_get_cache(self, key: str, **kwargs: Any) -> Any: + ... + + async def async_set_cache(self, key: str, value: Any, **kwargs: Any) -> Any: + ... + + +class EventDrivenCacheCoordinator: + """ + Coordinates a single in-flight load per logical resource to prevent cache stampede. + + Pattern: + - First request: loads data (e.g. DB query), caches it, then signals waiters. + - Other requests: wait for the signal, then read from cache. + + Create one instance per resource (e.g. one for global spend, one for feature flags). + """ + + def __init__(self, log_prefix: str = "[CACHE]"): + self._lock = asyncio.Lock() + self._event: Optional[asyncio.Event] = None + self._query_in_progress = False + self._log_prefix = log_prefix + + async def _get_cached( + self, cache_key: str, cache: AsyncCacheProtocol + ) -> Optional[Any]: + """Return value from cache if present, else None.""" + return await cache.async_get_cache(key=cache_key) + + def _log_cache_hit(self, value: T) -> None: + if self._log_prefix: + verbose_proxy_logger.debug( + "%s Cache hit, value: %s", self._log_prefix, value + ) + + def _log_cache_miss(self) -> None: + if self._log_prefix: + verbose_proxy_logger.debug("%s Cache miss", self._log_prefix) + + async def _claim_role(self) -> Optional[asyncio.Event]: + """ + Under lock: return event to wait on if load is in progress, else set us as loader and return None. + """ + async with self._lock: + if self._query_in_progress and self._event is not None: + if self._log_prefix: + verbose_proxy_logger.debug( + "%s Load in flight, waiting for signal", self._log_prefix + ) + return self._event + self._query_in_progress = True + self._event = asyncio.Event() + if self._log_prefix: + verbose_proxy_logger.debug( + "%s Starting load (will signal others when done)", + self._log_prefix, + ) + return None + + async def _wait_for_signal_and_get( + self, + event: asyncio.Event, + cache_key: str, + cache: AsyncCacheProtocol, + ) -> Optional[T]: + """Wait for loader to finish, then read from cache.""" + await event.wait() + if self._log_prefix: + verbose_proxy_logger.debug( + "%s Signal received, reading from cache", self._log_prefix + ) + value: Optional[T] = await cache.async_get_cache(key=cache_key) + if value is not None and self._log_prefix: + verbose_proxy_logger.debug( + "%s Cache filled by other request, value: %s", + self._log_prefix, + value, + ) + elif value is None and self._log_prefix: + verbose_proxy_logger.debug( + "%s Signal received but cache still empty", self._log_prefix + ) + return value + + async def _load_and_cache( + self, + cache_key: str, + cache: AsyncCacheProtocol, + load_fn: Callable[[], Awaitable[T]], + ) -> Optional[T]: + """Double-check cache, run load_fn, set cache, return value. Caller must call _signal_done in finally.""" + value = await cache.async_get_cache(key=cache_key) + if value is not None: + if self._log_prefix: + verbose_proxy_logger.debug( + "%s Cache filled while acquiring lock, value: %s", + self._log_prefix, + value, + ) + return value + + if self._log_prefix: + verbose_proxy_logger.debug("%s Running load", self._log_prefix) + start = time.perf_counter() + value = await load_fn() + elapsed_ms = (time.perf_counter() - start) * 1000 + if self._log_prefix: + verbose_proxy_logger.debug( + "%s Load completed in %.2fms, result: %s", + self._log_prefix, + elapsed_ms, + value, + ) + + await cache.async_set_cache(key=cache_key, value=value) + if self._log_prefix: + verbose_proxy_logger.debug("%s Result cached", self._log_prefix) + return value + + async def _signal_done(self) -> None: + """Reset loader state and signal all waiters.""" + async with self._lock: + self._query_in_progress = False + if self._event is not None: + if self._log_prefix: + verbose_proxy_logger.debug( + "%s Signaling all waiting requests", self._log_prefix + ) + self._event.set() + self._event = None + + async def get_or_load( + self, + cache_key: str, + cache: AsyncCacheProtocol, + load_fn: Callable[[], Awaitable[T]], + ) -> Optional[T]: + """ + Return cached value or load it once and signal waiters. + + - cache_key: Key to read/write in the cache. + - cache: Object with async_get_cache(key) and async_set_cache(key, value). + - load_fn: Async callable that performs the load (e.g. DB query). No args. + Return value is cached and returned. If it raises, waiters are + still signaled so they can retry or handle empty cache. + + Returns the value from cache or from load_fn, or None if load failed or + cache was still empty after waiting. + """ + value = await self._get_cached(cache_key, cache) + if value is not None: + self._log_cache_hit(value) + return value + + self._log_cache_miss() + event_to_wait = await self._claim_role() + + if event_to_wait is not None: + return await self._wait_for_signal_and_get( + event_to_wait, cache_key, cache + ) + + try: + result = await self._load_and_cache(cache_key, cache, load_fn) + return result + finally: + await self._signal_done() diff --git a/litellm/proxy/common_utils/callback_utils.py b/litellm/proxy/common_utils/callback_utils.py index c0cff84bacd..faeca9b2aed 100644 --- a/litellm/proxy/common_utils/callback_utils.py +++ b/litellm/proxy/common_utils/callback_utils.py @@ -274,11 +274,20 @@ def initialize_callbacks_on_proxy( # noqa: PLR0915 WebSearchInterceptionLogger, ) - websearch_interception_obj = WebSearchInterceptionLogger.initialize_from_proxy_config( - litellm_settings=litellm_settings, - callback_specific_params=callback_specific_params, + websearch_interception_obj = ( + WebSearchInterceptionLogger.initialize_from_proxy_config( + litellm_settings=litellm_settings, + callback_specific_params=callback_specific_params, + ) ) imported_list.append(websearch_interception_obj) + elif isinstance(callback, str) and callback == "datadog_cost_management": + from litellm.integrations.datadog.datadog_cost_management import ( + DatadogCostManagementLogger, + ) + + datadog_cost_management_obj = DatadogCostManagementLogger() + imported_list.append(datadog_cost_management_obj) elif isinstance(callback, CustomLogger): imported_list.append(callback) else: @@ -353,17 +362,17 @@ def get_remaining_tokens_and_requests_from_request_data(data: Dict) -> Dict[str, remaining_requests_variable_name = f"litellm-key-remaining-requests-{model_group}" remaining_requests = _metadata.get(remaining_requests_variable_name, None) if remaining_requests: - headers[f"x-litellm-key-remaining-requests-{h11_model_group_name}"] = ( - remaining_requests - ) + headers[ + f"x-litellm-key-remaining-requests-{h11_model_group_name}" + ] = remaining_requests # Remaining Tokens remaining_tokens_variable_name = f"litellm-key-remaining-tokens-{model_group}" remaining_tokens = _metadata.get(remaining_tokens_variable_name, None) if remaining_tokens: - headers[f"x-litellm-key-remaining-tokens-{h11_model_group_name}"] = ( - remaining_tokens - ) + headers[ + f"x-litellm-key-remaining-tokens-{h11_model_group_name}" + ] = remaining_tokens return headers @@ -438,9 +447,9 @@ def add_guardrail_response_to_standard_logging_object( ): if litellm_logging_obj is None: return - standard_logging_object: Optional[StandardLoggingPayload] = ( - litellm_logging_obj.model_call_details.get("standard_logging_object") - ) + standard_logging_object: Optional[ + StandardLoggingPayload + ] = litellm_logging_obj.model_call_details.get("standard_logging_object") if standard_logging_object is None: return guardrail_information = standard_logging_object.get("guardrail_information", []) @@ -469,7 +478,9 @@ def get_metadata_variable_name_from_kwargs( return "litellm_metadata" if "litellm_metadata" in kwargs else "metadata" -def process_callback(_callback: str, callback_type: str, environment_variables: dict) -> dict: +def process_callback( + _callback: str, callback_type: str, environment_variables: dict +) -> dict: """Process a single callback and return its data with environment variables""" env_vars = CustomLogger.get_callback_env_vars(_callback) @@ -481,11 +492,9 @@ def process_callback(_callback: str, callback_type: str, environment_variables: else: env_vars_dict[_var] = env_variable - return { - "name": _callback, - "variables": env_vars_dict, - "type": callback_type - } + return {"name": _callback, "variables": env_vars_dict, "type": callback_type} + + def normalize_callback_names(callbacks: Iterable[Any]) -> List[Any]: if callbacks is None: return [] diff --git a/litellm/proxy/common_utils/http_parsing_utils.py b/litellm/proxy/common_utils/http_parsing_utils.py index 1d94b10f6a4..e1bca6e905f 100644 --- a/litellm/proxy/common_utils/http_parsing_utils.py +++ b/litellm/proxy/common_utils/http_parsing_utils.py @@ -298,6 +298,103 @@ async def get_request_body(request: Request) -> Dict[str, Any]: return {} +def extract_nested_form_metadata( + form_data: Dict[str, Any], + prefix: str = "litellm_metadata[" +) -> Dict[str, Any]: + """ + Extract nested metadata from form data with bracket notation. + + Handles form data that uses bracket notation to represent nested dictionaries, + such as litellm_metadata[spend_logs_metadata][owner] = "value". + + This is commonly encountered when SDKs or clients send form data with nested + structures using bracket notation instead of JSON. + + Args: + form_data: Dictionary containing form data (from request.form()) + prefix: The prefix to look for in form keys (default: "litellm_metadata[") + + Returns: + Dictionary with nested structure reconstructed from bracket notation + + Example: + Input form_data: + { + "litellm_metadata[spend_logs_metadata][owner]": "john", + "litellm_metadata[spend_logs_metadata][team]": "engineering", + "litellm_metadata[tags]": "production", + "other_field": "value" + } + + Output: + { + "spend_logs_metadata": { + "owner": "john", + "team": "engineering" + }, + "tags": "production" + } + """ + if not form_data: + return {} + + metadata: Dict[str, Any] = {} + + for key, value in form_data.items(): + # Skip keys that don't start with the prefix + if not isinstance(key, str) or not key.startswith(prefix): + continue + + # Skip UploadFile objects - they should not be in metadata + if isinstance(value, UploadFile): + verbose_proxy_logger.warning( + f"Skipping UploadFile in metadata extraction for key: {key}" + ) + continue + + # Extract the nested path from bracket notation + # Example: "litellm_metadata[spend_logs_metadata][owner]" -> ["spend_logs_metadata", "owner"] + try: + # Remove the prefix and strip trailing ']' + path_string = key.replace(prefix, "").rstrip("]") + + # Split by "][" to get individual path parts + parts = path_string.split("][") + + if not parts or not parts[0]: + verbose_proxy_logger.warning( + f"Invalid metadata key format (empty path): {key}" + ) + continue + + # Navigate/create nested dictionary structure + current = metadata + for part in parts[:-1]: + if not isinstance(current, dict): + verbose_proxy_logger.warning( + f"Cannot create nested path - intermediate value is not a dict at: {part}" + ) + break + current = current.setdefault(part, {}) + else: + # Set the final value (only if we didn't break out of the loop) + if isinstance(current, dict): + current[parts[-1]] = value + else: + verbose_proxy_logger.warning( + f"Cannot set value - parent is not a dict for key: {key}" + ) + + except Exception as e: + verbose_proxy_logger.error( + f"Error parsing metadata key '{key}': {str(e)}" + ) + continue + + return metadata + + def get_tags_from_request_body(request_body: dict) -> List[str]: """ Extract tags from request body metadata. diff --git a/litellm/proxy/example_config_yaml/otel_test_config.yaml b/litellm/proxy/example_config_yaml/otel_test_config.yaml index 714875d56ce..7ddb5d40c0c 100644 --- a/litellm/proxy/example_config_yaml/otel_test_config.yaml +++ b/litellm/proxy/example_config_yaml/otel_test_config.yaml @@ -1,7 +1,7 @@ model_list: - model_name: fake-openai-endpoint litellm_params: - model: openai/fake + model: openai/gpt-3.5-turbo-0301 api_key: fake-key api_base: https://exampleopenaiendpoint-production.up.railway.app/ tags: ["teamA"] @@ -9,7 +9,7 @@ model_list: id: "team-a-model" - model_name: fake-openai-endpoint litellm_params: - model: openai/fake + model: openai/gpt-3.5-turbo-0301 api_key: fake-key api_base: https://exampleopenaiendpoint-production.up.railway.app/ tags: ["teamB"] diff --git a/litellm/proxy/example_config_yaml/spend_tracking_config.yaml b/litellm/proxy/example_config_yaml/spend_tracking_config.yaml index fe8d73d26aa..1fdfbd27e9a 100644 --- a/litellm/proxy/example_config_yaml/spend_tracking_config.yaml +++ b/litellm/proxy/example_config_yaml/spend_tracking_config.yaml @@ -1,7 +1,7 @@ model_list: - model_name: fake-openai-endpoint litellm_params: - model: openai/fake + model: openai/gpt-3.5-turbo-0301 api_key: fake-key api_base: https://exampleopenaiendpoint-production.up.railway.app/ diff --git a/litellm/proxy/google_endpoints/endpoints.py b/litellm/proxy/google_endpoints/endpoints.py index bba5b87024b..5c41b371ca4 100644 --- a/litellm/proxy/google_endpoints/endpoints.py +++ b/litellm/proxy/google_endpoints/endpoints.py @@ -264,7 +264,7 @@ async def create_interaction( general_settings=general_settings, proxy_config=proxy_config, select_data_generator=select_data_generator, - model=data.get("model"), + model=data.get("model") or data.get("agent"), user_model=user_model, user_temperature=user_temperature, user_request_timeout=user_request_timeout, diff --git a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py index c9bd0135a05..083a407e9cf 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py +++ b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/content_filter.py @@ -198,6 +198,15 @@ class ContentFilterGuardrail(CustomGuardrail): for pattern_config in normalized_patterns: self._add_pattern(pattern_config) + # Warn if using during_call with MASK action (unstable) + if self.event_hook == GuardrailEventHooks.during_call and any( + p["action"] == ContentFilterAction.MASK for p in self.compiled_patterns + ): + verbose_proxy_logger.warning( + f"ContentFilterGuardrail '{self.guardrail_name}': 'during_call' mode with 'MASK' action is unstable due to race conditions. " + "Use 'pre_call' mode for reliable request masking." + ) + # Load blocked words - always initialize as dict self.blocked_words: Dict[str, Tuple[ContentFilterAction, Optional[str]]] = {} for word in normalized_blocked_words: @@ -905,11 +914,15 @@ class ContentFilterGuardrail(CustomGuardrail): elif isinstance(e.detail, str): e.detail = e.detail + " (Image description): " + description else: - e.detail = "Content blocked: Image description detected" + description + e.detail = ( + "Content blocked: Image description detected" + description + ) raise e def _count_masked_entities( - self, detections: List[ContentFilterDetection], masked_entity_count: Dict[str, int] + self, + detections: List[ContentFilterDetection], + masked_entity_count: Dict[str, int], ) -> None: """ Count masked entities by type from detections. @@ -964,9 +977,11 @@ class ContentFilterGuardrail(CustomGuardrail): dict(detection) for detection in detections ] if status != "success": - guardrail_json_response = exception_str if exception_str else [ - dict(detection) for detection in detections - ] + guardrail_json_response = ( + exception_str + if exception_str + else [dict(detection) for detection in detections] + ) self.add_standard_logging_guardrail_information_to_request_data( guardrail_provider=self.guardrail_provider, @@ -1066,99 +1081,84 @@ class ContentFilterGuardrail(CustomGuardrail): Process streaming response chunks and check for blocked content. For BLOCK action: Raises HTTPException immediately when blocked content is detected. - For MASK action: Content passes through (masking streaming responses is not supported). + For MASK action: Content is buffered to handle patterns split across chunks. """ + accumulated_full_text = "" + yielded_masked_text_len = 0 + buffer_size = 50 # Increased buffer to catch patterns split across many chunks - # Accumulate content as we iterate through chunks - accumulated_content = "" + verbose_proxy_logger.info( + f"ContentFilterGuardrail: Starting robust streaming masking for model {request_data.get('model')}" + ) async for item in response: - # Accumulate content from this chunk before checking if isinstance(item, ModelResponseStream) and item.choices: + delta_content = "" + is_final = False for choice in item.choices: if hasattr(choice, "delta") and choice.delta: content = getattr(choice.delta, "content", None) if content and isinstance(content, str): - accumulated_content += content + delta_content += content + if getattr(choice, "finish_reason", None): + is_final = True - # Check accumulated content for blocked patterns/keywords after processing all choices - # Only check for BLOCK actions, not MASK (masking streaming is not supported) - if accumulated_content: - try: - # Check patterns - pattern_match = self._check_patterns(accumulated_content) - if pattern_match: - matched_text, pattern_name, action = pattern_match - if action == ContentFilterAction.BLOCK: - error_msg = ( - f"Content blocked: {pattern_name} pattern detected" - ) - verbose_proxy_logger.warning(error_msg) - raise HTTPException( - status_code=403, - detail={ - "error": error_msg, - "pattern": pattern_name, - }, - ) + accumulated_full_text += delta_content - # Check blocked words - blocked_word_match = self._check_blocked_words( - accumulated_content - ) - if blocked_word_match: - keyword, action, description = blocked_word_match - if action == ContentFilterAction.BLOCK: - error_msg = ( - f"Content blocked: keyword '{keyword}' detected" - ) - if description: - error_msg += f" ({description})" - verbose_proxy_logger.warning(error_msg) - raise HTTPException( - status_code=403, - detail={ - "error": error_msg, - "keyword": keyword, - "description": description, - }, - ) + # Check for blocking or apply masking + # Add a space at the end if it's the final chunk to trigger word boundaries (\b) + text_to_check = accumulated_full_text + if is_final: + text_to_check += " " - # Check category keywords - all_exceptions = [] - for category in self.loaded_categories.values(): - all_exceptions.extend(category.exceptions) - category_match = self._check_category_keywords( - accumulated_content, all_exceptions - ) - if category_match: - keyword, category_name, severity, action = category_match - if action == ContentFilterAction.BLOCK: - error_msg = ( - f"Content blocked: {category_name} category keyword '{keyword}' detected " - f"(severity: {severity})" - ) - verbose_proxy_logger.warning(error_msg) - raise HTTPException( - status_code=403, - detail={ - "error": error_msg, - "category": category_name, - "keyword": keyword, - "severity": severity, - }, - ) - except HTTPException: - # Re-raise HTTPException (blocked content detected) - raise - except Exception as e: - # Log other exceptions but don't block the stream - verbose_proxy_logger.warning( - f"Error checking content filter in streaming: {e}" - ) + try: + masked_text = self._filter_single_text(text_to_check) + if is_final and masked_text.endswith(" "): + masked_text = masked_text[:-1] + except HTTPException: + raise + except Exception as e: + verbose_proxy_logger.error( + f"ContentFilterGuardrail: Error in masking: {e}" + ) + masked_text = text_to_check # Fallback to current text - # Yield the chunk (only if no exception was raised above) - yield item + # Determine how much can be safely yielded + if is_final: + safe_to_yield_len = len(masked_text) + else: + safe_to_yield_len = max(0, len(masked_text) - buffer_size) + + if safe_to_yield_len > yielded_masked_text_len: + new_masked_content = masked_text[ + yielded_masked_text_len:safe_to_yield_len + ] + # Modify the chunk to contain only the new masked content + if ( + item.choices + and hasattr(item.choices[0], "delta") + and item.choices[0].delta + ): + item.choices[0].delta.content = new_masked_content + yielded_masked_text_len = safe_to_yield_len + yield item + else: + # Hold content by yielding empty content chunk (keeps metadata/structure) + if ( + item.choices + and hasattr(item.choices[0], "delta") + and item.choices[0].delta + ): + item.choices[0].delta.content = "" + yield item + else: + # Not a ModelResponseStream or no choices - yield as is + yield item + + # Any remaining content (should have been handled by is_final, but just in case) + if yielded_masked_text_len < len(accumulated_full_text): + # We already reached the end of the generator + pass @staticmethod def get_config_model(): diff --git a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/patterns.json b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/patterns.json index f2427b5b920..1eff7804b42 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/patterns.json +++ b/litellm/proxy/guardrails/guardrail_hooks/litellm_content_filter/patterns.json @@ -108,7 +108,7 @@ { "name": "ipv6", "display_name": "IP Address (IPv6)", - "pattern": "\\b(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\b", + "pattern": "(? aiohttp.ClientSession: + @asynccontextmanager + async def _get_session_iterator( + self, + ) -> AsyncGenerator[aiohttp.ClientSession, None]: """ - Get or create the shared HTTP session for Presidio API calls. + Async context manager for yielding an HTTP session. - Fixes memory leak (issue #14540) where every guardrail check created - a new aiohttp.ClientSession that was never properly closed. - - Thread-safe: Uses asyncio.Lock to prevent race conditions when - multiple concurrent requests try to create the session simultaneously. + Logic: + 1. If running in the main thread (where the object was initialized/destined to live normally), + use the shared `self._http_session` (protected by a lock). + 2. If running in a background thread (e.g. logging hook), use a cached session for that loop. """ - async with self._session_lock: - if self._http_session is None or self._http_session.closed: - self._http_session = aiohttp.ClientSession() - return self._http_session + current_loop = asyncio.get_running_loop() + + # Check if we are in the stored main thread + if threading.get_ident() == self._main_thread_id: + # Main thread -> use shared session + async with self._session_lock: + if self._http_session is None or self._http_session.closed: + self._http_session = aiohttp.ClientSession() + yield self._http_session + else: + # Background thread/loop -> use loop-bound session cache + # This avoids "attached to a different loop" or "no running event loop" errors + # when accessing the shared session created in the main loop + if ( + current_loop not in self._loop_sessions + or self._loop_sessions[current_loop].closed + ): + self._loop_sessions[current_loop] = aiohttp.ClientSession() + yield self._loop_sessions[current_loop] async def _close_http_session(self) -> None: - """Close the HTTP session if it exists.""" + """Close all cached HTTP sessions.""" if self._http_session is not None and not self._http_session.closed: await self._http_session.close() self._http_session = None + for session in self._loop_sessions.values(): + if not session.closed: + await session.close() + self._loop_sessions.clear() + def __del__(self): - """Cleanup: close HTTP session on instance destruction.""" - if self._http_session is not None and not self._http_session.closed: - try: - # Try to close the session, but don't fail if event loop is gone - import asyncio - try: - loop = asyncio.get_event_loop() - if loop.is_running(): - # Schedule cleanup, don't block __del__ - asyncio.create_task(self._close_http_session()) - else: - loop.run_until_complete(self._close_http_session()) - except RuntimeError: - # Event loop is closed, can't clean up - not ideal but better than crashing - pass - except Exception: - # Suppress all exceptions in __del__ to avoid issues during shutdown - pass + """Cleanup: we try to close, but doing async cleanup in __del__ is risky.""" + pass def _get_presidio_analyze_request_payload( self, @@ -273,28 +289,27 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail): return self.mock_redacted_text # Use shared session to prevent memory leak (issue #14540) - session = await self._get_http_session() + async with self._get_session_iterator() as session: + # Make the request to /analyze + analyze_url = f"{self.presidio_analyzer_api_base}analyze" - # Make the request to /analyze - analyze_url = f"{self.presidio_analyzer_api_base}analyze" - - analyze_payload: PresidioAnalyzeRequest = ( - self._get_presidio_analyze_request_payload( - text=text, - presidio_config=presidio_config, - request_data=request_data, + analyze_payload: PresidioAnalyzeRequest = ( + self._get_presidio_analyze_request_payload( + text=text, + presidio_config=presidio_config, + request_data=request_data, + ) ) - ) - verbose_proxy_logger.debug( - "Making request to: %s with payload: %s", - analyze_url, - analyze_payload, - ) + verbose_proxy_logger.debug( + "Making request to: %s with payload: %s", + analyze_url, + analyze_payload, + ) - async with session.post(analyze_url, json=analyze_payload) as response: - analyze_results = await response.json() - verbose_proxy_logger.debug("analyze_results: %s", analyze_results) + async with session.post(analyze_url, json=analyze_payload) as response: + analyze_results = await response.json() + verbose_proxy_logger.debug("analyze_results: %s", analyze_results) # Handle error responses from Presidio (e.g., {'error': 'No text provided'}) # Presidio may return a dict instead of a list when errors occur @@ -302,7 +317,7 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail): if "error" in analyze_results: verbose_proxy_logger.warning( "Presidio analyzer returned error: %s, returning empty list", - analyze_results.get("error") + analyze_results.get("error"), ) return [] # If it's a dict but not an error, try to process it as a single item @@ -314,7 +329,7 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail): except Exception as e: verbose_proxy_logger.warning( "Failed to parse Presidio dict response: %s, returning empty list", - e + e, ) return [] @@ -351,20 +366,19 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail): return text # Use shared session to prevent memory leak (issue #14540) - session = await self._get_http_session() + async with self._get_session_iterator() as session: + # Make the request to /anonymize + anonymize_url = f"{self.presidio_anonymizer_api_base}anonymize" + verbose_proxy_logger.debug("Making request to: %s", anonymize_url) + anonymize_payload = { + "text": text, + "analyzer_results": analyze_results, + } - # Make the request to /anonymize - anonymize_url = f"{self.presidio_anonymizer_api_base}anonymize" - verbose_proxy_logger.debug("Making request to: %s", anonymize_url) - anonymize_payload = { - "text": text, - "analyzer_results": analyze_results, - } - - async with session.post( - anonymize_url, json=anonymize_payload - ) as response: - redacted_text = await response.json() + async with session.post( + anonymize_url, json=anonymize_payload + ) as response: + redacted_text = await response.json() new_text = text if redacted_text is not None: @@ -495,12 +509,13 @@ class _OPTIONAL_PresidioPIIMasking(CustomGuardrail): ) # Then anonymize the text using the analysis results - return await self.anonymize_text( + anonymized_text = await self.anonymize_text( text=text, analyze_results=analyze_results, output_parse_pii=output_parse_pii, masked_entity_count=masked_entity_count, ) + return anonymized_text return redacted_text["text"] except Exception as e: status = "guardrail_failed_to_respond" diff --git a/litellm/proxy/health_endpoints/_health_endpoints.py b/litellm/proxy/health_endpoints/_health_endpoints.py index d27e0036235..da90696ec2d 100644 --- a/litellm/proxy/health_endpoints/_health_endpoints.py +++ b/litellm/proxy/health_endpoints/_health_endpoints.py @@ -32,6 +32,7 @@ from litellm.proxy.health_check import ( run_with_timeout, ) from litellm.secret_managers.main import get_secret +from litellm.litellm_core_utils.custom_logger_registry import CustomLoggerRegistry #### Health ENDPOINTS #### @@ -106,6 +107,35 @@ def _resolve_os_environ_variables(params: dict) -> dict: return resolved_root +def get_callback_identifier(callback): + """ + Get the callback identifier string, handling both strings and objects. + + This function extracts a string identifier from a callback, which can be: + - A string (returned as-is) + - An object with a callback_name attribute + - An object registered in CustomLoggerRegistry + - Falls back to callback_name() helper function + + Args: + callback: The callback to identify (can be str or object) + + Returns: + str: The callback identifier string + """ + if isinstance(callback, str): + return callback + if hasattr(callback, 'callback_name') and callback.callback_name: + return callback.callback_name + if hasattr(callback, '__class__'): + callback_strs = CustomLoggerRegistry.get_all_callback_strs_from_class_type(callback.__class__) + if hasattr(callback, 'callback_name') and callback.callback_name in callback_strs: + return callback.callback_name + if callback_strs: + return callback_strs[0] + return callback_name(callback) + + router = APIRouter() services = Union[ Literal[ @@ -118,6 +148,7 @@ services = Union[ "email", "braintrust", "datadog", + "datadog_llm_observability", "generic_api", "arize", "sqs" @@ -190,6 +221,7 @@ async def health_services_endpoint( # noqa: PLR0915 "custom_callback_api", "langsmith", "datadog", + "datadog_llm_observability", "generic_api", "arize", "sqs" @@ -201,11 +233,24 @@ async def health_services_endpoint( # noqa: PLR0915 }, ) + service_in_success_callbacks = False + if service in litellm.success_callback: + service_in_success_callbacks = True + else: + for cb in litellm.success_callback: + if hasattr(cb, 'callback_name') and cb.callback_name == service: + service_in_success_callbacks = True + break + cb_id = get_callback_identifier(cb) + if cb_id == service: + service_in_success_callbacks = True + break + if ( service == "openmeter" or service == "braintrust" or service == "generic_api" - or (service in litellm.success_callback and service != "langfuse") + or (service_in_success_callbacks and service != "langfuse") ): _ = await litellm.acompletion( model="openai/litellm-mock-response-model", diff --git a/litellm/proxy/hooks/mcp_semantic_filter/ARCHITECTURE.md b/litellm/proxy/hooks/mcp_semantic_filter/ARCHITECTURE.md new file mode 100644 index 00000000000..f2f9a1d4856 --- /dev/null +++ b/litellm/proxy/hooks/mcp_semantic_filter/ARCHITECTURE.md @@ -0,0 +1,96 @@ +# MCP Semantic Tool Filter Architecture + +## Why Filter MCP Tools + +When multiple MCP servers are connected, the proxy may expose hundreds of tools. Sending all tools in every request wastes context window tokens and increases cost. The semantic filter keeps only the top-K most relevant tools based on embedding similarity. + +```mermaid +sequenceDiagram + participant Client + participant Hook as SemanticToolFilterHook + participant Filter as SemanticMCPToolFilter + participant Router as semantic-router + participant LLM + + Client->>Hook: POST /chat/completions + Note over Client,Hook: tools: [100+ MCP tools] + Note over Client,Hook: messages: [{"role": "user", "content": "Get my Jira issues"}] + + rect rgb(240, 240, 240) + Note over Hook: 1. Extract User Query + Hook->>Filter: filter_tools("Get my Jira issues", tools) + end + + rect rgb(240, 240, 240) + Note over Filter: 2. Convert Tools → Routes + Note over Filter: Tool name + description → Route + end + + rect rgb(240, 240, 240) + Note over Filter: 3. Semantic Matching + Filter->>Router: router(query) + Router->>Router: Embeddings + similarity + Router-->>Filter: [top 10 matches] + end + + rect rgb(240, 240, 240) + Note over Filter: 4. Return Filtered Tools + Filter-->>Hook: [10 relevant tools] + end + + Hook->>LLM: POST /chat/completions + Note over Hook,LLM: tools: [10 Jira-related tools] ← FILTERED + Note over Hook,LLM: messages: [...] ← UNCHANGED + + LLM-->>Client: Response (unchanged) +``` + +## Filter Operations + +The hook intercepts requests before they reach the LLM: + +| Operation | Description | +|-----------|-------------| +| **Extract query** | Get user message from `messages[-1]` | +| **Convert to Routes** | Transform MCP tools into semantic-router Routes | +| **Semantic match** | Use `semantic-router` to find top-K similar tools | +| **Filter tools** | Replace request `tools` with filtered subset | + +## Trigger Conditions + +The filter only runs when: +- Call type is `completion` or `acompletion` +- Request contains `tools` field +- Request contains `messages` field +- Filter is enabled in config + +## What Does NOT Change + +- Request messages +- Response body +- Non-tool parameters + +## Integration with semantic-router + +Reuses existing LiteLLM infrastructure: +- `semantic-router` - Already an optional dependency +- `LiteLLMRouterEncoder` - Wraps `Router.aembedding()` for embeddings +- `SemanticRouter` - Handles similarity calculation and top-K selection + +## Configuration + +```yaml +litellm_settings: + mcp_semantic_tool_filter: + enabled: true + embedding_model: "openai/text-embedding-3-small" + top_k: 10 + similarity_threshold: 0.3 +``` + +## Error Handling + +The filter fails gracefully: +- If filtering fails → Return all tools (no impact on functionality) +- If query extraction fails → Skip filtering +- If no matches found → Return all tools diff --git a/litellm/proxy/hooks/mcp_semantic_filter/__init__.py b/litellm/proxy/hooks/mcp_semantic_filter/__init__.py new file mode 100644 index 00000000000..36d357d560f --- /dev/null +++ b/litellm/proxy/hooks/mcp_semantic_filter/__init__.py @@ -0,0 +1,9 @@ +""" +MCP Semantic Tool Filter Hook + +Semantic filtering for MCP tools to reduce context window size +and improve tool selection accuracy. +""" +from litellm.proxy.hooks.mcp_semantic_filter.hook import SemanticToolFilterHook + +__all__ = ["SemanticToolFilterHook"] diff --git a/litellm/proxy/hooks/mcp_semantic_filter/hook.py b/litellm/proxy/hooks/mcp_semantic_filter/hook.py new file mode 100644 index 00000000000..fc9349c2a42 --- /dev/null +++ b/litellm/proxy/hooks/mcp_semantic_filter/hook.py @@ -0,0 +1,353 @@ +""" +Semantic Tool Filter Hook + +Pre-call hook that filters MCP tools semantically before LLM inference. +Reduces context window size and improves tool selection accuracy. +""" +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union + +from litellm._logging import verbose_proxy_logger +from litellm.constants import ( + DEFAULT_MCP_SEMANTIC_FILTER_EMBEDDING_MODEL, + DEFAULT_MCP_SEMANTIC_FILTER_SIMILARITY_THRESHOLD, + DEFAULT_MCP_SEMANTIC_FILTER_TOP_K, +) +from litellm.integrations.custom_logger import CustomLogger + +if TYPE_CHECKING: + from litellm.caching.caching import DualCache + from litellm.proxy._experimental.mcp_server.semantic_tool_filter import ( + SemanticMCPToolFilter, + ) + from litellm.proxy._types import UserAPIKeyAuth + from litellm.router import Router + + +class SemanticToolFilterHook(CustomLogger): + """ + Pre-call hook that filters MCP tools semantically. + + This hook: + 1. Extracts the user query from messages + 2. Filters tools based on semantic similarity to the query + 3. Returns only the top-k most relevant tools to the LLM + """ + + def __init__(self, semantic_filter: "SemanticMCPToolFilter"): + """ + Initialize the hook. + + Args: + semantic_filter: SemanticMCPToolFilter instance + """ + super().__init__() + self.filter = semantic_filter + + verbose_proxy_logger.debug( + f"Initialized SemanticToolFilterHook with filter: " + f"enabled={semantic_filter.enabled}, top_k={semantic_filter.top_k}" + ) + + def _should_expand_mcp_tools(self, tools: List[Any]) -> bool: + """ + Check if tools contain MCP references with server_url="litellm_proxy". + + Only expands MCP tools pointing to litellm proxy, not external MCP servers. + """ + from litellm.responses.mcp.litellm_proxy_mcp_handler import ( + LiteLLM_Proxy_MCP_Handler, + ) + + return LiteLLM_Proxy_MCP_Handler._should_use_litellm_mcp_gateway(tools) + + async def _expand_mcp_tools( + self, + tools: List[Any], + user_api_key_dict: "UserAPIKeyAuth", + ) -> List[Dict[str, Any]]: + """ + Expand MCP references to actual tool definitions. + + Reuses LiteLLM_Proxy_MCP_Handler._process_mcp_tools_to_openai_format + which internally does: parse -> fetch -> filter -> deduplicate -> transform + """ + from litellm.responses.mcp.litellm_proxy_mcp_handler import ( + LiteLLM_Proxy_MCP_Handler, + ) + + # Parse to separate MCP tools from other tools + mcp_tools, _ = LiteLLM_Proxy_MCP_Handler._parse_mcp_tools(tools) + + if not mcp_tools: + return [] + + # Use single combined method instead of 3 separate calls + # This already handles: fetch -> filter by allowed_tools -> deduplicate -> transform + openai_tools, _ = await LiteLLM_Proxy_MCP_Handler._process_mcp_tools_to_openai_format( + user_api_key_auth=user_api_key_dict, + mcp_tools_with_litellm_proxy=mcp_tools + ) + + # Convert Pydantic models to dicts for compatibility + openai_tools_as_dicts = [] + for tool in openai_tools: + if hasattr(tool, "model_dump"): + tool_dict = tool.model_dump(exclude_none=True) + verbose_proxy_logger.debug(f"Converted Pydantic tool to dict: {type(tool).__name__} -> dict with keys: {list(tool_dict.keys())}") + openai_tools_as_dicts.append(tool_dict) + elif hasattr(tool, "dict"): + tool_dict = tool.dict(exclude_none=True) + verbose_proxy_logger.debug(f"Converted Pydantic tool (v1) to dict: {type(tool).__name__} -> dict") + openai_tools_as_dicts.append(tool_dict) + elif isinstance(tool, dict): + verbose_proxy_logger.debug(f"Tool is already a dict with keys: {list(tool.keys())}") + openai_tools_as_dicts.append(tool) + else: + verbose_proxy_logger.warning(f"Tool is unknown type: {type(tool)}, passing as-is") + openai_tools_as_dicts.append(tool) + + verbose_proxy_logger.debug( + f"Expanded {len(mcp_tools)} MCP reference(s) to {len(openai_tools_as_dicts)} tools (all as dicts)" + ) + + return openai_tools_as_dicts + + def _get_metadata_variable_name(self, data: dict) -> str: + if "litellm_metadata" in data: + return "litellm_metadata" + return "metadata" + + async def async_pre_call_hook( + self, + user_api_key_dict: "UserAPIKeyAuth", + cache: "DualCache", + data: dict, + call_type: str, + ) -> Optional[Union[Exception, str, dict]]: + """ + Filter tools before LLM call based on user query. + + This hook is called before the LLM request is made. It filters the + tools list to only include semantically relevant tools. + + Args: + user_api_key_dict: User authentication + cache: Cache instance + data: Request data containing messages and tools + call_type: Type of call (completion, acompletion, etc.) + + Returns: + Modified data dict with filtered tools, or None if no changes + """ + # Only filter endpoints that support tools + if call_type not in ("completion", "acompletion", "aresponses"): + verbose_proxy_logger.debug( + f"Skipping semantic filter for call_type={call_type}" + ) + return None + + # Check if tools are present + tools = data.get("tools") + if not tools: + verbose_proxy_logger.debug("No tools in request, skipping semantic filter") + return None + + original_tool_count = len(tools) + + # Check for MCP references (server_url="litellm_proxy") and expand them + if self._should_expand_mcp_tools(tools): + verbose_proxy_logger.debug( + "Detected litellm_proxy MCP references, expanding before semantic filtering" + ) + + try: + expanded_tools = await self._expand_mcp_tools( + tools, user_api_key_dict + ) + + if not expanded_tools: + verbose_proxy_logger.warning( + "No tools expanded from MCP references" + ) + return None + + verbose_proxy_logger.info( + f"Expanded {len(tools)} MCP reference(s) to {len(expanded_tools)} tools" + ) + + # Update tools for filtering + tools = expanded_tools + original_tool_count = len(tools) + + except Exception as e: + verbose_proxy_logger.error( + f"Failed to expand MCP references: {e}", exc_info=True + ) + return None + + # Check if messages are present (try both "messages" and "input" for responses API) + messages = data.get("messages", []) + if not messages: + messages = data.get("input", []) + if not messages: + verbose_proxy_logger.debug("No messages in request, skipping semantic filter") + return None + + # Check if filter is enabled + if not self.filter.enabled: + verbose_proxy_logger.debug("Semantic filter disabled, skipping") + return None + + try: + # Extract user query from messages + user_query = self.filter.extract_user_query(messages) + if not user_query: + verbose_proxy_logger.debug("No user query found, skipping semantic filter") + return None + + verbose_proxy_logger.debug( + f"Applying semantic filter to {len(tools)} tools " + f"with query: '{user_query[:50]}...'" + ) + + # Filter tools semantically + filtered_tools = await self.filter.filter_tools( + query=user_query, + available_tools=tools, # type: ignore + ) + + # Always update tools and emit header (even if count unchanged) + data["tools"] = filtered_tools + + # Store filter stats and tool names for response header + filter_stats = f"{original_tool_count}->{len(filtered_tools)}" + tool_names_csv = self._get_tool_names_csv(filtered_tools) + + _metadata_variable_name = self._get_metadata_variable_name(data) + data[_metadata_variable_name]["litellm_semantic_filter_stats"] = filter_stats + data[_metadata_variable_name]["litellm_semantic_filter_tools"] = tool_names_csv + + verbose_proxy_logger.info( + f"Semantic tool filter: {filter_stats} tools" + ) + + return data + + except Exception as e: + verbose_proxy_logger.warning( + f"Semantic tool filter hook failed: {e}. Proceeding with all tools." + ) + return None + + async def async_post_call_response_headers_hook( + self, + data: dict, + user_api_key_dict: "UserAPIKeyAuth", + response: Any, + request_headers: Optional[Dict[str, str]] = None, + ) -> Optional[Dict[str, str]]: + """Add semantic filter stats and tool names to response headers.""" + from litellm.constants import MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH + + _metadata_variable_name = self._get_metadata_variable_name(data) + metadata = data[_metadata_variable_name] + + filter_stats = metadata.get("litellm_semantic_filter_stats") + if not filter_stats: + return None + + headers = {"x-litellm-semantic-filter": filter_stats} + + # Add CSV of filtered tool names (nginx-safe length) + tool_names_csv = metadata.get("litellm_semantic_filter_tools", "") + if tool_names_csv: + if len(tool_names_csv) > MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH: + tool_names_csv = tool_names_csv[:MAX_MCP_SEMANTIC_FILTER_TOOLS_HEADER_LENGTH - 3] + "..." + + headers["x-litellm-semantic-filter-tools"] = tool_names_csv + + return headers + + def _get_tool_names_csv(self, tools: List[Any]) -> str: + """Extract tool names and return as CSV string.""" + if not tools: + return "" + + tool_names = [] + for tool in tools: + name = tool.get("name", "") if isinstance(tool, dict) else getattr(tool, "name", "") + if name: + tool_names.append(name) + + return ",".join(tool_names) + + @staticmethod + async def initialize_from_config( + config: Optional[Dict[str, Any]], + llm_router: Optional["Router"], + ) -> Optional["SemanticToolFilterHook"]: + """ + Initialize semantic tool filter from proxy config. + + Args: + config: Proxy configuration dict (litellm_settings.mcp_semantic_tool_filter) + llm_router: LiteLLM router instance for embeddings + + Returns: + SemanticToolFilterHook instance if enabled, None otherwise + """ + from litellm.proxy._experimental.mcp_server.semantic_tool_filter import ( + SemanticMCPToolFilter, + ) + if not config or not config.get("enabled", False): + verbose_proxy_logger.debug("Semantic tool filter not enabled in config") + return None + + if llm_router is None: + verbose_proxy_logger.warning( + "Cannot initialize semantic filter: llm_router is None" + ) + return None + + try: + + embedding_model = config.get( + "embedding_model", DEFAULT_MCP_SEMANTIC_FILTER_EMBEDDING_MODEL + ) + top_k = config.get("top_k", DEFAULT_MCP_SEMANTIC_FILTER_TOP_K) + similarity_threshold = config.get( + "similarity_threshold", DEFAULT_MCP_SEMANTIC_FILTER_SIMILARITY_THRESHOLD + ) + + semantic_filter = SemanticMCPToolFilter( + embedding_model=embedding_model, + litellm_router_instance=llm_router, + top_k=top_k, + similarity_threshold=similarity_threshold, + enabled=True, + ) + + # Build router from MCP registry on startup + await semantic_filter.build_router_from_mcp_registry() + + hook = SemanticToolFilterHook(semantic_filter) + + verbose_proxy_logger.info( + f"✅ MCP Semantic Tool Filter enabled: " + f"embedding_model={embedding_model}, top_k={top_k}, " + f"similarity_threshold={similarity_threshold}" + ) + + return hook + + except ImportError as e: + verbose_proxy_logger.warning( + f"semantic-router not installed. Install with: " + f"pip install 'litellm[semantic-router]'. Error: {e}" + ) + return None + except Exception as e: + verbose_proxy_logger.exception( + f"Failed to initialize MCP semantic tool filter: {e}" + ) + return None diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index 60472cd7790..9be78264e85 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -856,7 +856,9 @@ async def add_litellm_data_to_request( # noqa: PLR0915 # Add headers to metadata for guardrails to access (fixes #17477) # Guardrails use metadata["headers"] to access request headers (e.g., User-Agent) - if _metadata_variable_name in data and isinstance(data[_metadata_variable_name], dict): + if _metadata_variable_name in data and isinstance( + data[_metadata_variable_name], dict + ): data[_metadata_variable_name]["headers"] = _headers # check for forwardable headers @@ -1012,7 +1014,9 @@ async def add_litellm_data_to_request( # noqa: PLR0915 # User spend, budget - used by prometheus.py # Follow same pattern as team and API key budgets - data[_metadata_variable_name]["user_api_key_user_spend"] = user_api_key_dict.user_spend + data[_metadata_variable_name][ + "user_api_key_user_spend" + ] = user_api_key_dict.user_spend data[_metadata_variable_name][ "user_api_key_user_max_budget" ] = user_api_key_dict.user_max_budget @@ -1039,8 +1043,8 @@ async def add_litellm_data_to_request( # noqa: PLR0915 ## [Enterprise Only] # Add User-IP Address requester_ip_address = "" - if premium_user is True: - # Only set the IP Address for Enterprise Users + if True: # Always set the IP Address if available + # logic for tracking IP Address # logic for tracking IP Address if ( @@ -1060,6 +1064,16 @@ async def add_litellm_data_to_request( # noqa: PLR0915 requester_ip_address = request.client.host data[_metadata_variable_name]["requester_ip_address"] = requester_ip_address + # Add User-Agent + user_agent = "" + if ( + request is not None + and hasattr(request, "headers") + and "user-agent" in request.headers + ): + user_agent = request.headers["user-agent"] + data[_metadata_variable_name]["user_agent"] = user_agent + # Check if using tag based routing tags = LiteLLMProxyRequestSetup.add_request_tag_to_metadata( llm_router=llm_router, @@ -1510,10 +1524,13 @@ def add_guardrails_from_policy_engine( Add guardrails from the policy engine based on request context. This function: - 1. Gets matching policies based on team_alias, key_alias, and model - 2. Resolves guardrails from matching policies (including inheritance) - 3. Adds guardrails to request metadata - 4. Tracks applied policies in metadata for response headers + 1. Extracts "policies" from request body (if present) for dynamic policy application + 2. Gets matching policies based on team_alias, key_alias, and model (via attachments) + 3. Combines dynamic policies with attachment-based policies + 4. Resolves guardrails from all policies (including inheritance) + 5. Adds guardrails to request metadata + 6. Tracks applied policies in metadata for response headers + 7. Removes "policies" from request body so it's not forwarded to LLM provider Args: data: The request data to update @@ -1529,13 +1546,19 @@ def add_guardrails_from_policy_engine( from litellm.proxy.policy_engine.policy_resolver import PolicyResolver from litellm.types.proxy.policy_engine import PolicyMatchContext + # Extract dynamic policies from request body (if present) + # These will be combined with attachment-based policies + request_body_policies = data.pop("policies", None) + registry = get_policy_registry() verbose_proxy_logger.debug( f"Policy engine: registry initialized={registry.is_initialized()}, " f"policy_count={len(registry.get_all_policies())}" ) if not registry.is_initialized(): - verbose_proxy_logger.debug("Policy engine not initialized, skipping policy matching") + verbose_proxy_logger.debug( + "Policy engine not initialized, skipping policy matching" + ) return # Build context from request @@ -1553,18 +1576,30 @@ def add_guardrails_from_policy_engine( # Get matching policies via attachments matching_policy_names = PolicyMatcher.get_matching_policies(context=context) - verbose_proxy_logger.debug(f"Policy engine: matched policies via attachments: {matching_policy_names}") + verbose_proxy_logger.debug( + f"Policy engine: matched policies via attachments: {matching_policy_names}" + ) - if not matching_policy_names: + # Combine attachment-based policies with dynamic request body policies + all_policy_names = set(matching_policy_names) + if request_body_policies and isinstance(request_body_policies, list): + all_policy_names.update(request_body_policies) + verbose_proxy_logger.debug( + f"Policy engine: added dynamic policies from request body: {request_body_policies}" + ) + + if not all_policy_names: return # Filter to only policies whose conditions match the context applied_policy_names = PolicyMatcher.get_policies_with_matching_conditions( - policy_names=matching_policy_names, + policy_names=list(all_policy_names), context=context, ) - verbose_proxy_logger.debug(f"Policy engine: applied policies (conditions matched): {applied_policy_names}") + verbose_proxy_logger.debug( + f"Policy engine: applied policies (conditions matched): {applied_policy_names}" + ) # Track applied policies in metadata for response headers for policy_name in applied_policy_names: @@ -1575,7 +1610,9 @@ def add_guardrails_from_policy_engine( # Resolve guardrails from matching policies resolved_guardrails = PolicyResolver.resolve_guardrails_for_context(context=context) - verbose_proxy_logger.debug(f"Policy engine: resolved guardrails: {resolved_guardrails}") + verbose_proxy_logger.debug( + f"Policy engine: resolved guardrails: {resolved_guardrails}" + ) if not resolved_guardrails: return diff --git a/litellm/proxy/management_endpoints/internal_user_endpoints.py b/litellm/proxy/management_endpoints/internal_user_endpoints.py index 9a986326c03..38a867d031b 100644 --- a/litellm/proxy/management_endpoints/internal_user_endpoints.py +++ b/litellm/proxy/management_endpoints/internal_user_endpoints.py @@ -814,7 +814,9 @@ def _update_internal_user_params( ) -> dict: non_default_values = {} for k, v in data_json.items(): - if ( + if k == "max_budget": + non_default_values[k] = v + elif ( v is not None and v not in ( diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index ab87e862ea6..d1840363009 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -37,6 +37,13 @@ from litellm.proxy._experimental.mcp_server.db import ( ) from litellm.proxy._types import * from litellm.proxy._types import LiteLLM_VerificationToken +from litellm.types.proxy.management_endpoints.key_management_endpoints import ( + BulkUpdateKeyRequest, + BulkUpdateKeyRequestItem, + BulkUpdateKeyResponse, + FailedKeyUpdate, + SuccessfulKeyUpdate, +) from litellm.proxy.auth.auth_checks import ( _cache_key_object, _delete_cache_key_object, @@ -1438,6 +1445,212 @@ def is_different_team( return data.team_id != existing_key_row.team_id +def _validate_max_budget(max_budget: Optional[float]) -> None: + """ + Validate that max_budget is not negative. + + Args: + max_budget: The max_budget value to validate + + Raises: + HTTPException: If max_budget is negative + """ + if max_budget is not None and max_budget < 0: + raise HTTPException( + status_code=400, + detail={ + "error": f"max_budget cannot be negative. Received: {max_budget}" + }, + ) + + +async def _get_and_validate_existing_key( + token: str, prisma_client: Optional[PrismaClient] +) -> LiteLLM_VerificationToken: + """ + Get existing key from database and validate it exists. + + Args: + token: The key token to look up + prisma_client: Prisma client instance + + Returns: + LiteLLM_VerificationToken: The existing key row + + Raises: + HTTPException: If key is not found + """ + if prisma_client is None: + raise HTTPException( + status_code=500, + detail={"error": "Database not connected"}, + ) + + existing_key_row = await prisma_client.get_data( + token=token, + table_name="key", + query_type="find_unique", + ) + + if existing_key_row is None: + raise HTTPException( + status_code=404, + detail={"error": f"Key not found: {token}"}, + ) + + return existing_key_row + + +async def _process_single_key_update( + key_update_item: BulkUpdateKeyRequestItem, + user_api_key_dict: UserAPIKeyAuth, + litellm_changed_by: Optional[str], + prisma_client: Optional[PrismaClient], + user_api_key_cache: DualCache, + proxy_logging_obj: Any, + llm_router: Optional[Router], +) -> Dict[str, Any]: + """ + Process a single key update with all validations and checks. + + This function encapsulates all the logic for updating a single key, + including validation, permission checks, team checks, and database updates. + + Args: + key_update_item: The key update request item + user_api_key_dict: The authenticated user's API key info + litellm_changed_by: Optional header for tracking who made the change + prisma_client: Prisma client instance + user_api_key_cache: User API key cache + proxy_logging_obj: Proxy logging object + llm_router: LLM router instance + + Returns: + Dict containing the updated key information + + Raises: + HTTPException: For various validation and permission errors + """ + # Validate max_budget + _validate_max_budget(key_update_item.max_budget) + + # Get and validate existing key + existing_key_row = await _get_and_validate_existing_key( + token=key_update_item.key, + prisma_client=prisma_client, + ) + + # Check team member permissions + if prisma_client is not None: + await TeamMemberPermissionChecks.can_team_member_execute_key_management_endpoint( + user_api_key_dict=user_api_key_dict, + route=KeyManagementRoutes.KEY_UPDATE, + prisma_client=prisma_client, + existing_key_row=existing_key_row, + user_api_key_cache=user_api_key_cache, + ) + + # Create UpdateKeyRequest from BulkUpdateKeyRequestItem + update_key_request = UpdateKeyRequest( + key=key_update_item.key, + budget_id=key_update_item.budget_id, + max_budget=key_update_item.max_budget, + team_id=key_update_item.team_id, + tags=key_update_item.tags, + ) + + # Get team object and check team limits if team_id is provided + team_obj: Optional[LiteLLM_TeamTableCachedObj] = None + if update_key_request.team_id is not None: + team_obj = await get_team_object( + team_id=update_key_request.team_id, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + check_db_only=True, + ) + + if team_obj is not None and prisma_client is not None: + await _check_team_key_limits( + team_table=team_obj, + data=update_key_request, + prisma_client=prisma_client, + ) + + # Validate team change if team is being changed + if is_different_team( + data=update_key_request, existing_key_row=existing_key_row + ): + if llm_router is None: + raise HTTPException( + status_code=400, + detail={ + "error": "LLM router not found. Please set it up by passing in a valid config.yaml or adding models via the UI." + }, + ) + if team_obj is None: + raise HTTPException( + status_code=500, + detail={ + "error": "Team object not found for team change validation" + }, + ) + validate_key_team_change( + key=existing_key_row, + team=team_obj, + change_initiated_by=user_api_key_dict, + llm_router=llm_router, + ) + + # Prepare update data + non_default_values = await prepare_key_update_data( + data=update_key_request, existing_key_row=existing_key_row + ) + + # Update key in database + if prisma_client is None: + raise HTTPException( + status_code=500, + detail={"error": "Database not connected"}, + ) + + _data = {**non_default_values, "token": key_update_item.key} + response = await prisma_client.update_data( + token=key_update_item.key, data=_data + ) + + # Delete cache + await _delete_cache_key_object( + hashed_token=hash_token(key_update_item.key), + user_api_key_cache=user_api_key_cache, + proxy_logging_obj=proxy_logging_obj, + ) + + # Trigger async hook + asyncio.create_task( + KeyManagementEventHooks.async_key_updated_hook( + data=update_key_request, + existing_key_row=existing_key_row, + response=response, + user_api_key_dict=user_api_key_dict, + litellm_changed_by=litellm_changed_by, + ) + ) + + if response is None: + raise ValueError("Failed to update key got response = None") + + # Extract and format updated key info + updated_key_info = response.get("data", {}) + if hasattr(updated_key_info, "model_dump"): + updated_key_info = updated_key_info.model_dump() + elif hasattr(updated_key_info, "dict"): + updated_key_info = updated_key_info.dict() + + updated_key_info.pop("token", None) + + return updated_key_info + + @router.post( "/key/update", tags=["key management"], dependencies=[Depends(user_api_key_auth)] ) @@ -1684,6 +1897,167 @@ async def update_key_fn( ) +@router.post( + "/key/bulk_update", + tags=["key management"], + dependencies=[Depends(user_api_key_auth)], + response_model=BulkUpdateKeyResponse, +) +@management_endpoint_wrapper +async def bulk_update_keys( + data: BulkUpdateKeyRequest, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), + litellm_changed_by: Optional[str] = Header( + None, + description="The litellm-changed-by header enables tracking of actions performed by authorized users on behalf of other users, providing an audit trail for accountability", + ), +): + """ + Bulk update multiple keys at once. + + This endpoint allows updating multiple keys in a single request. Each key update + is processed independently - if some updates fail, others will still succeed. + + Parameters: + - keys: List[BulkUpdateKeyRequestItem] - List of key update requests, each containing: + - key: str - The key identifier (token) to update + - budget_id: Optional[str] - Budget ID associated with the key + - max_budget: Optional[float] - Max budget for key + - team_id: Optional[str] - Team ID associated with key + - tags: Optional[List[str]] - Tags for organizing keys + + Returns: + - total_requested: int - Total number of keys requested for update + - successful_updates: List[SuccessfulKeyUpdate] - List of successfully updated keys with their updated info + - failed_updates: List[FailedKeyUpdate] - List of failed updates with key_info and failed_reason + + Example request: + ```bash + curl --location 'http://0.0.0.0:4000/key/bulk_update' \ + --header 'Authorization: Bearer sk-1234' \ + --header 'Content-Type: application/json' \ + --data '{ + "keys": [ + { + "key": "sk-1234", + "max_budget": 100.0, + "team_id": "team-123", + "tags": ["production", "api"] + }, + { + "key": "sk-5678", + "budget_id": "budget-456", + "tags": ["staging"] + } + ] + }' + ``` + """ + from litellm.proxy.proxy_server import ( + llm_router, + prisma_client, + proxy_logging_obj, + user_api_key_cache, + ) + + if user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN.value: + raise HTTPException( + status_code=403, + detail={ + "error": "Only proxy admins can perform bulk key updates" + }, + ) + + if prisma_client is None: + raise HTTPException( + status_code=500, + detail={"error": "Database not connected"}, + ) + + if not data.keys: + raise HTTPException( + status_code=400, + detail={"error": "No keys provided for update"}, + ) + + MAX_BATCH_SIZE = 500 + if len(data.keys) > MAX_BATCH_SIZE: + raise HTTPException( + status_code=400, + detail={ + "error": f"Maximum {MAX_BATCH_SIZE} keys can be updated at once. Found {len(data.keys)} keys." + }, + ) + + successful_updates: List[SuccessfulKeyUpdate] = [] + failed_updates: List[FailedKeyUpdate] = [] + + for key_update_item in data.keys: + try: + # Process single key update using reusable function + updated_key_info = await _process_single_key_update( + key_update_item=key_update_item, + user_api_key_dict=user_api_key_dict, + litellm_changed_by=litellm_changed_by, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + proxy_logging_obj=proxy_logging_obj, + llm_router=llm_router, + ) + + successful_updates.append( + SuccessfulKeyUpdate( + key=key_update_item.key, + key_info=updated_key_info, + ) + ) + + except Exception as e: + verbose_proxy_logger.exception( + f"Failed to update key {key_update_item.key}: {e}" + ) + + if isinstance(e, HTTPException): + error_detail = e.detail + if isinstance(error_detail, dict): + error_message = error_detail.get("error", str(e)) + else: + error_message = str(error_detail) + else: + error_message = str(e) + + key_info = None + try: + existing_key_row = await prisma_client.get_data( + token=key_update_item.key, + table_name="key", + query_type="find_unique", + ) + if existing_key_row is not None: + if hasattr(existing_key_row, "model_dump"): + key_info = existing_key_row.model_dump() + elif hasattr(existing_key_row, "dict"): + key_info = existing_key_row.dict() + if key_info: + key_info.pop("token", None) + except Exception: + pass + + failed_updates.append( + FailedKeyUpdate( + key=key_update_item.key, + key_info=key_info, + failed_reason=error_message, + ) + ) + + return BulkUpdateKeyResponse( + total_requested=len(data.keys), + successful_updates=successful_updates, + failed_updates=failed_updates, + ) + + def validate_key_team_change( key: LiteLLM_VerificationToken, team: LiteLLM_TeamTable, @@ -2999,6 +3373,163 @@ async def regenerate_key_fn( raise handle_exception_on_proxy(e) +async def _check_proxy_or_team_admin_for_key( + key_in_db: LiteLLM_VerificationToken, + user_api_key_dict: UserAPIKeyAuth, + prisma_client: PrismaClient, + user_api_key_cache: DualCache, +) -> None: + if user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value: + return + + if key_in_db.team_id is not None: + team_table = await get_team_object( + team_id=key_in_db.team_id, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + check_db_only=True, + ) + if team_table is not None: + if _is_user_team_admin( + user_api_key_dict=user_api_key_dict, + team_obj=team_table, + ): + return + + raise HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail={"error": "You must be a proxy admin or team admin to reset key spend"}, + ) + + +def _validate_reset_spend_value( + reset_to: Any, key_in_db: LiteLLM_VerificationToken +) -> float: + if not isinstance(reset_to, (int, float)): + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail={"error": "reset_to must be a float"}, + ) + + reset_to = float(reset_to) + + if reset_to < 0: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail={"error": "reset_to must be >= 0"}, + ) + + current_spend = key_in_db.spend or 0.0 + if reset_to > current_spend: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail={"error": f"reset_to ({reset_to}) must be <= current spend ({current_spend})"}, + ) + + max_budget = key_in_db.max_budget + if key_in_db.litellm_budget_table is not None: + budget_max_budget = getattr(key_in_db.litellm_budget_table, "max_budget", None) + if budget_max_budget is not None: + if max_budget is None or budget_max_budget < max_budget: + max_budget = budget_max_budget + + if max_budget is not None and reset_to > max_budget: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail={"error": f"reset_to ({reset_to}) must be <= budget ({max_budget})"}, + ) + + return reset_to + + +@router.post( + "/key/{key:path}/reset_spend", + tags=["key management"], + dependencies=[Depends(user_api_key_auth)], +) +@management_endpoint_wrapper +async def reset_key_spend_fn( + key: str, + data: ResetSpendRequest, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), + litellm_changed_by: Optional[str] = Header( + None, + description="The litellm-changed-by header enables tracking of actions performed by authorized users on behalf of other users, providing an audit trail for accountability", + ), +) -> Dict[str, Any]: + try: + from litellm.proxy.proxy_server import ( + hash_token, + prisma_client, + proxy_logging_obj, + user_api_key_cache, + ) + + if prisma_client is None: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail={"error": "DB not connected. prisma_client is None"}, + ) + + if "sk" not in key: + hashed_api_key = key + else: + hashed_api_key = hash_token(key) + + _key_in_db = await prisma_client.db.litellm_verificationtoken.find_unique( + where={"token": hashed_api_key}, + include={"litellm_budget_table": True}, + ) + if _key_in_db is None: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail={"error": f"Key {key} not found."}, + ) + + current_spend = _key_in_db.spend or 0.0 + reset_to = _validate_reset_spend_value(data.reset_to, _key_in_db) + + await _check_proxy_or_team_admin_for_key( + key_in_db=_key_in_db, + user_api_key_dict=user_api_key_dict, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + ) + + updated_key = await prisma_client.db.litellm_verificationtoken.update( + where={"token": hashed_api_key}, + data={"spend": reset_to}, + ) + + if updated_key is None: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail={"error": "Failed to update key spend"}, + ) + + await _delete_cache_key_object( + hashed_token=hashed_api_key, + user_api_key_cache=user_api_key_cache, + proxy_logging_obj=proxy_logging_obj, + ) + + max_budget = updated_key.max_budget + budget_reset_at = updated_key.budget_reset_at + + return { + "key_hash": hashed_api_key, + "spend": reset_to, + "previous_spend": current_spend, + "max_budget": max_budget, + "budget_reset_at": budget_reset_at, + } + except HTTPException: + raise + except Exception as e: + verbose_proxy_logger.exception("Error resetting key spend: %s", e) + raise handle_exception_on_proxy(e) + + async def validate_key_list_check( user_api_key_dict: UserAPIKeyAuth, user_id: Optional[str], diff --git a/litellm/proxy/management_endpoints/mcp_management_endpoints.py b/litellm/proxy/management_endpoints/mcp_management_endpoints.py index a15f47d13bd..40f4745615e 100644 --- a/litellm/proxy/management_endpoints/mcp_management_endpoints.py +++ b/litellm/proxy/management_endpoints/mcp_management_endpoints.py @@ -37,6 +37,8 @@ from litellm._uuid import uuid from litellm.constants import LITELLM_PROXY_ADMIN_NAME from litellm.proxy._experimental.mcp_server.utils import ( get_server_prefix, +) +from litellm.proxy._experimental.mcp_server.utils import ( validate_and_normalize_mcp_server_payload as _base_validate_and_normalize_mcp_server_payload, ) @@ -56,7 +58,20 @@ except ImportError as e: MCP_AVAILABLE = False if MCP_AVAILABLE: - from mcp.shared.tool_name_validation import validate_tool_name + try: + from mcp.shared.tool_name_validation import ( # type: ignore + validate_tool_name, + ) + except ImportError: + from pydantic import BaseModel + + class ToolNameValidationResult(BaseModel): + is_valid: bool = True + warnings: list = [] + + def validate_tool_name(name: str) -> ToolNameValidationResult: # type: ignore[misc] + return ToolNameValidationResult() + from litellm.proxy._experimental.mcp_server.db import ( create_mcp_server, delete_mcp_server, @@ -65,9 +80,9 @@ if MCP_AVAILABLE: update_mcp_server, ) from litellm.proxy._experimental.mcp_server.discoverable_endpoints import ( - get_request_base_url, authorize_with_server, exchange_token_with_server, + get_request_base_url, register_client_with_server, ) from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( @@ -122,9 +137,7 @@ if MCP_AVAILABLE: ) if validation_result.warnings: error_messages_text = ( - error_messages_text - + "\n" - + "\n".join(validation_result.warnings) + error_messages_text + "\n" + "\n".join(validation_result.warnings) ) raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, diff --git a/litellm/proxy/management_endpoints/scim/scim_v2.py b/litellm/proxy/management_endpoints/scim/scim_v2.py index 0965198bad9..e67e1eae745 100644 --- a/litellm/proxy/management_endpoints/scim/scim_v2.py +++ b/litellm/proxy/management_endpoints/scim/scim_v2.py @@ -410,6 +410,308 @@ async def set_scim_content_type(response: Response): response.headers["Content-Type"] = "application/scim+json" +def _get_resource_types(base_url: str = "/scim/v2") -> list: + """Return the list of SCIM ResourceType definitions per RFC 7643 Section 6.""" + return [ + SCIMResourceType( + id="User", + name="User", + description="User Account", + endpoint="/Users", + schema_="urn:ietf:params:scim:schemas:core:2.0:User", + meta={ + "location": f"{base_url}/ResourceTypes/User", + "resourceType": "ResourceType", + }, + ), + SCIMResourceType( + id="Group", + name="Group", + description="Group", + endpoint="/Groups", + schema_="urn:ietf:params:scim:schemas:core:2.0:Group", + meta={ + "location": f"{base_url}/ResourceTypes/Group", + "resourceType": "ResourceType", + }, + ), + ] + + +def _get_schemas() -> list: + """Return the list of SCIM Schema definitions per RFC 7643 Section 7.""" + return [ + SCIMSchema( + id="urn:ietf:params:scim:schemas:core:2.0:User", + name="User", + description="User Account", + attributes=[ + SCIMSchemaAttribute( + name="userName", + type="string", + multiValued=False, + description="Unique identifier for the User.", + required=True, + mutability="readWrite", + returned="default", + uniqueness="server", + ), + SCIMSchemaAttribute( + name="name", + type="complex", + multiValued=False, + description="The components of the user's real name.", + required=False, + subAttributes=[ + SCIMSchemaAttribute( + name="givenName", + type="string", + description="The given name of the User.", + ), + SCIMSchemaAttribute( + name="familyName", + type="string", + description="The family name of the User.", + ), + SCIMSchemaAttribute( + name="formatted", + type="string", + description="The full name.", + ), + ], + ), + SCIMSchemaAttribute( + name="displayName", + type="string", + multiValued=False, + description="The name of the User, suitable for display.", + ), + SCIMSchemaAttribute( + name="emails", + type="complex", + multiValued=True, + description="Email addresses for the user.", + subAttributes=[ + SCIMSchemaAttribute( + name="value", + type="string", + description="Email address value.", + ), + SCIMSchemaAttribute( + name="type", + type="string", + description="Type of email (work, home, etc.).", + ), + SCIMSchemaAttribute( + name="primary", + type="boolean", + description="Whether this is the primary email.", + ), + ], + ), + SCIMSchemaAttribute( + name="active", + type="boolean", + multiValued=False, + description="Whether the user account is active.", + ), + SCIMSchemaAttribute( + name="groups", + type="complex", + multiValued=True, + description="Groups to which the user belongs.", + mutability="readOnly", + subAttributes=[ + SCIMSchemaAttribute( + name="value", + type="string", + description="Group identifier.", + ), + SCIMSchemaAttribute( + name="display", + type="string", + description="Group display name.", + ), + ], + ), + ], + meta={ + "location": "/scim/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:User", + "resourceType": "Schema", + }, + ), + SCIMSchema( + id="urn:ietf:params:scim:schemas:core:2.0:Group", + name="Group", + description="Group", + attributes=[ + SCIMSchemaAttribute( + name="displayName", + type="string", + multiValued=False, + description="A human-readable name for the Group.", + required=True, + mutability="readWrite", + returned="default", + uniqueness="none", + ), + SCIMSchemaAttribute( + name="members", + type="complex", + multiValued=True, + description="A list of members of the Group.", + subAttributes=[ + SCIMSchemaAttribute( + name="value", + type="string", + description="Member identifier.", + ), + SCIMSchemaAttribute( + name="display", + type="string", + description="Member display name.", + ), + ], + ), + ], + meta={ + "location": "/scim/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:Group", + "resourceType": "Schema", + }, + ), + ] + + +@scim_router.get( + "", + status_code=200, + dependencies=[Depends(user_api_key_auth), Depends(set_scim_content_type)], +) +@scim_router.get( + "/", + status_code=200, + include_in_schema=False, + dependencies=[Depends(user_api_key_auth), Depends(set_scim_content_type)], +) +async def get_scim_base(request: Request): + """ + Base SCIM v2 endpoint for resource discovery per RFC 7644 Section 4. + + Returns a ListResponse of ResourceTypes supported by this SCIM service provider. + Identity providers (Okta, Azure AD, etc.) use this endpoint for resource discovery. + """ + verbose_proxy_logger.debug( + "SCIM base resource discovery request: method=%s url=%s", + request.method, + request.url, + ) + base_url = str(request.base_url).rstrip("/") + "/scim/v2" + resource_types = _get_resource_types(base_url) + return { + "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"], + "totalResults": len(resource_types), + "Resources": [rt.model_dump() for rt in resource_types], + } + + +@scim_router.get( + "/ResourceTypes", + status_code=200, + dependencies=[Depends(user_api_key_auth), Depends(set_scim_content_type)], +) +async def get_resource_types(request: Request): + """ + SCIM ResourceTypes endpoint per RFC 7644 Section 4. + + Returns a ListResponse of all resource types supported by this service provider. + """ + verbose_proxy_logger.debug( + "SCIM ResourceTypes request: method=%s url=%s", + request.method, + request.url, + ) + base_url = str(request.base_url).rstrip("/") + "/scim/v2" + resource_types = _get_resource_types(base_url) + return { + "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"], + "totalResults": len(resource_types), + "Resources": [rt.model_dump() for rt in resource_types], + } + + +@scim_router.get( + "/ResourceTypes/{resource_type_id}", + status_code=200, + dependencies=[Depends(user_api_key_auth), Depends(set_scim_content_type)], +) +async def get_resource_type( + request: Request, + resource_type_id: str = Path(..., title="ResourceType ID"), +): + """ + Get a single ResourceType by ID per RFC 7644. + """ + verbose_proxy_logger.debug( + "SCIM ResourceType request for id=%s", resource_type_id + ) + base_url = str(request.base_url).rstrip("/") + "/scim/v2" + resource_types = _get_resource_types(base_url) + for rt in resource_types: + if rt.id == resource_type_id: + return rt.model_dump() + raise HTTPException( + status_code=404, + detail={"error": f"ResourceType not found: {resource_type_id}"}, + ) + + +@scim_router.get( + "/Schemas", + status_code=200, + dependencies=[Depends(user_api_key_auth), Depends(set_scim_content_type)], +) +async def get_schemas(request: Request): + """ + SCIM Schemas endpoint per RFC 7643 Section 7. + + Returns a ListResponse of all schemas supported by this service provider. + """ + verbose_proxy_logger.debug( + "SCIM Schemas request: method=%s url=%s", + request.method, + request.url, + ) + schemas = _get_schemas() + return { + "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"], + "totalResults": len(schemas), + "Resources": [s.model_dump() for s in schemas], + } + + +@scim_router.get( + "/Schemas/{schema_id:path}", + status_code=200, + dependencies=[Depends(user_api_key_auth), Depends(set_scim_content_type)], +) +async def get_schema( + request: Request, + schema_id: str = Path(..., title="Schema URI"), +): + """ + Get a single Schema by its URI per RFC 7643 Section 7. + """ + verbose_proxy_logger.debug("SCIM Schema request for id=%s", schema_id) + schemas = _get_schemas() + for s in schemas: + if s.id == schema_id: + return s.model_dump() + raise HTTPException( + status_code=404, + detail={"error": f"Schema not found: {schema_id}"}, + ) + + @scim_router.get( "/ServiceProviderConfig", response_model=SCIMServiceProviderConfig, diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py index c77b60649ab..63db2d72fe4 100644 --- a/litellm/proxy/management_endpoints/team_endpoints.py +++ b/litellm/proxy/management_endpoints/team_endpoints.py @@ -497,14 +497,18 @@ async def _check_org_team_limits( # Validate team models against organization's allowed models if data.models is not None and len(org_table.models) > 0: - for m in data.models: - if m not in org_table.models: - raise HTTPException( - status_code=400, - detail={ - "error": f"Model '{m}' not in organization's allowed models. Organization allowed models={org_table.models}. Organization: {org_table.organization_id}" - }, - ) + # If organization has 'all-proxy-models', skip validation as it allows all models + if SpecialModelNames.all_proxy_models.value in org_table.models: + pass + else: + for m in data.models: + if m not in org_table.models: + raise HTTPException( + status_code=400, + detail={ + "error": f"Model '{m}' not in organization's allowed models. Organization allowed models={org_table.models}. Organization: {org_table.organization_id}" + }, + ) # Validate team TPM/RPM against organization's TPM/RPM limits (direct comparison) if ( @@ -1767,6 +1771,113 @@ async def _add_team_members_to_team( return updated_team, updated_users, updated_team_memberships +async def _validate_and_populate_member_user_info( + member: Member, + prisma_client: PrismaClient, +) -> Member: + """ + Validate and populate user_email/user_id for a member. + + Logic: + 1. If both user_email and user_id are provided, verify they belong to the same user (use user_email as source of truth) + 2. If only user_email is provided, populate user_id from DB + 3. If only user_id is provided, populate user_email from DB (if user exists) + 4. If only user_id is provided and doesn't exist, allow it to pass with user_email as None (will be upserted later) + 5. If user_email and user_id mismatch, throw error + + Returns a Member with user_email and user_id populated (user_email may be None if only user_id provided and user doesn't exist). + """ + if member.user_email is None and member.user_id is None: + raise HTTPException( + status_code=400, + detail={"error": "Either user_id or user_email must be provided"}, + ) + + # Case 1: Both user_email and user_id provided - verify they match + if member.user_email is not None and member.user_id is not None: + # Use user_email as source of truth + # Check for multiple users with same email first + users_by_email = await prisma_client.get_data( + key_val={"user_email": member.user_email}, + table_name="user", + query_type="find_all", + ) + + if users_by_email is None or ( + isinstance(users_by_email, list) and len(users_by_email) == 0 + ): + # User doesn't exist yet - this is fine, will be created later + return member + + if isinstance(users_by_email, list) and len(users_by_email) > 1: + raise HTTPException( + status_code=400, + detail={ + "error": f"Multiple users found with email '{member.user_email}'. Please use 'user_id' instead." + }, + ) + + # Get the single user + user_by_email = users_by_email[0] + + # Verify the user_id matches + if user_by_email.user_id != member.user_id: + raise HTTPException( + status_code=400, + detail={ + "error": f"user_email '{member.user_email}' and user_id '{member.user_id}' do not belong to the same user." + }, + ) + + # Both match, return as is + return member + + # Case 2: Only user_email provided - populate user_id from DB + if member.user_email is not None and member.user_id is None: + user_by_email = await prisma_client.db.litellm_usertable.find_first( + where={"user_email": {"equals": member.user_email, "mode": "insensitive"}} + ) + + if user_by_email is None: + # User doesn't exist yet - this is fine, will be created later + return member + + # Check for multiple users with same email + users_by_email = await prisma_client.get_data( + key_val={"user_email": member.user_email}, + table_name="user", + query_type="find_all", + ) + + if users_by_email and isinstance(users_by_email, list) and len(users_by_email) > 1: + raise HTTPException( + status_code=400, + detail={ + "error": f"Multiple users found with email '{member.user_email}'. Please use 'user_id' instead." + }, + ) + + # Populate user_id + member.user_id = user_by_email.user_id + return member + + # Case 3: Only user_id provided - populate user_email from DB if user exists + if member.user_id is not None and member.user_email is None: + user_by_id = await prisma_client.db.litellm_usertable.find_unique( + where={"user_id": member.user_id} + ) + + if user_by_id is None: + # User doesn't exist yet - allow it to pass with user_email as None + # Will be upserted later with just user_id and null email + return member + + # Populate user_email + member.user_email = user_by_id.user_email + return member + + return member + @router.post( "/team/member_add", tags=["team management"], @@ -1842,6 +1953,19 @@ async def team_member_add( complete_team_data=complete_team_data, ) + # Validate and populate user_email/user_id for members before processing + if isinstance(data.member, Member): + await _validate_and_populate_member_user_info( + member=data.member, + prisma_client=prisma_client, + ) + elif isinstance(data.member, List): + for m in data.member: + await _validate_and_populate_member_user_info( + member=m, + prisma_client=prisma_client, + ) + updated_team, updated_users, updated_team_memberships = ( await _add_team_members_to_team( data=data, diff --git a/litellm/proxy/management_endpoints/ui_sso.py b/litellm/proxy/management_endpoints/ui_sso.py index 1756fe9c6f6..2d248dc81f3 100644 --- a/litellm/proxy/management_endpoints/ui_sso.py +++ b/litellm/proxy/management_endpoints/ui_sso.py @@ -93,6 +93,26 @@ else: router = APIRouter() +def normalize_email(email: Optional[str]) -> Optional[str]: + """ + Normalize email address to lowercase for consistent storage and comparison. + + Email addresses should be treated as case-insensitive for SSO purposes, + even though RFC 5321 technically allows case-sensitive local parts. + This prevents issues where SSO providers return emails with different casing + than what's stored in the database. + + Args: + email: Email address to normalize, can be None + + Returns: + Lowercased email address, or None if input is None + """ + if email is None: + return None + return email.lower() if isinstance(email, str) else email + + def determine_role_from_groups( user_groups: List[str], role_mappings: "RoleMappings", @@ -306,6 +326,7 @@ def generic_response_convertor( jwt_handler: JWTHandler, sso_jwt_handler: Optional[JWTHandler] = None, role_mappings: Optional["RoleMappings"] = None, + team_mappings: Optional["TeamMappings"] = None, ) -> CustomOpenID: generic_user_id_attribute_name = os.getenv( "GENERIC_USER_ID_ATTRIBUTE", "preferred_username" @@ -339,8 +360,20 @@ def generic_response_convertor( team_ids = sso_jwt_handler.get_team_ids_from_jwt(cast(dict, response)) all_teams.extend(team_ids) - team_ids = jwt_handler.get_team_ids_from_jwt(cast(dict, response)) - all_teams.extend(team_ids) + if team_mappings is not None and team_mappings.team_ids_jwt_field is not None: + team_ids_from_db_mapping: Optional[List[str]] = get_nested_value( + data=cast(dict, response), + key_path=team_mappings.team_ids_jwt_field, + default=[], + ) + if team_ids_from_db_mapping: + all_teams.extend(team_ids_from_db_mapping) + verbose_proxy_logger.debug( + f"Loaded team_ids from DB team_mappings.team_ids_jwt_field='{team_mappings.team_ids_jwt_field}': {team_ids_from_db_mapping}" + ) + else: + team_ids = jwt_handler.get_team_ids_from_jwt(cast(dict, response)) + all_teams.extend(team_ids) # Determine user role based on role_mappings if available # Only apply role_mappings for GENERIC SSO provider @@ -395,7 +428,7 @@ def generic_response_convertor( display_name=get_nested_value( response, generic_user_display_name_attribute_name ), - email=get_nested_value(response, generic_user_email_attribute_name), + email=normalize_email(get_nested_value(response, generic_user_email_attribute_name)), first_name=get_nested_value(response, generic_user_first_name_attribute_name), last_name=get_nested_value(response, generic_user_last_name_attribute_name), provider=get_nested_value(response, generic_provider_attribute_name), @@ -464,6 +497,43 @@ def _setup_generic_sso_env_vars( ) +async def _setup_team_mappings() -> Optional["TeamMappings"]: + """Setup team mappings from SSO database settings.""" + team_mappings: Optional["TeamMappings"] = None + try: + from litellm.proxy.utils import get_prisma_client_or_throw + + prisma_client = get_prisma_client_or_throw( + "Prisma client is None, connect a database to your proxy" + ) + + sso_db_record = await prisma_client.db.litellm_ssoconfig.find_unique( + where={"id": "sso_config"} + ) + + if sso_db_record and sso_db_record.sso_settings: + sso_settings_dict = dict(sso_db_record.sso_settings) + team_mappings_data = sso_settings_dict.get("team_mappings") + + if team_mappings_data: + from litellm.types.proxy.management_endpoints.ui_sso import TeamMappings + if isinstance(team_mappings_data, dict): + team_mappings = TeamMappings(**team_mappings_data) + elif isinstance(team_mappings_data, TeamMappings): + team_mappings = team_mappings_data + + if team_mappings and team_mappings.team_ids_jwt_field: + verbose_proxy_logger.debug( + f"Loaded team_mappings with team_ids_jwt_field: '{team_mappings.team_ids_jwt_field}'" + ) + except Exception as e: + verbose_proxy_logger.debug( + f"Could not load team_mappings from database: {e}. Continuing with config-based team mapping." + ) + + return team_mappings + + async def _setup_role_mappings() -> Optional["RoleMappings"]: """Setup role mappings from SSO database settings.""" role_mappings: Optional["RoleMappings"] = None @@ -474,7 +544,6 @@ async def _setup_role_mappings() -> Optional["RoleMappings"]: "Prisma client is None, connect a database to your proxy" ) - # Get SSO config from dedicated table sso_db_record = await prisma_client.db.litellm_ssoconfig.find_unique( where={"id": "sso_config"} ) @@ -495,7 +564,6 @@ async def _setup_role_mappings() -> Optional["RoleMappings"]: f"Loaded role_mappings for provider '{role_mappings.provider}'" ) except Exception as e: - # If we can't load role_mappings, continue with existing logic verbose_proxy_logger.debug( f"Could not load role_mappings from database: {e}. Continuing with existing role logic." ) @@ -570,8 +638,8 @@ async def get_generic_sso_response( userinfo_endpoint=generic_userinfo_endpoint, ) - # Get role_mappings from SSO settings if available role_mappings = await _setup_role_mappings() + team_mappings = await _setup_team_mappings() def response_convertor(response, client): nonlocal received_response # return for user debugging @@ -581,6 +649,7 @@ async def get_generic_sso_response( jwt_handler=jwt_handler, sso_jwt_handler=sso_jwt_handler, role_mappings=role_mappings, + team_mappings=team_mappings, ) SSOProvider = create_provider( @@ -731,7 +800,7 @@ async def get_user_info_from_db( if _id is not None and isinstance(_id, str): potential_user_ids.append(_id) - user_email = ( + user_email = normalize_email( getattr(result, "email", None) if not isinstance(result, dict) else result.get("email", None) @@ -806,8 +875,8 @@ def _build_sso_user_update_data( Returns: dict: Update data containing user_email and optionally user_role if valid - """ - update_data: dict = {"user_email": user_email} + """ + update_data: dict = {"user_email": normalize_email(user_email)} # Get SSO role from result and include if valid sso_role = getattr(result, "user_role", None) @@ -1199,7 +1268,7 @@ async def cli_poll_key(key_id: str, team_id: Optional[str] = None): max_budget=litellm.max_ui_session_budget, ) - # Generate CLI JWT on-demand (24hr expiration) + # Generate CLI JWT on-demand (expiration configurable via LITELLM_CLI_JWT_EXPIRATION_HOURS) # Pass selected team_id to ensure JWT has correct team jwt_token = ExperimentalUIJWTToken.get_cli_jwt_auth_token( user_info=user_info, team_id=team_id @@ -1316,7 +1385,7 @@ async def insert_sso_user( new_user_request = NewUserRequest( user_id=user_defined_values["user_id"], - user_email=user_defined_values["user_email"], + user_email=normalize_email(user_defined_values["user_email"]), user_role=user_defined_values["user_role"], # type: ignore max_budget=user_defined_values["max_budget"], budget_duration=user_defined_values["budget_duration"], @@ -1981,7 +2050,7 @@ class SSOAuthenticationHandler: """ Gets the user email and id from the OpenID result after validating the email domain """ - user_email: Optional[str] = getattr(result, "email", None) + user_email: Optional[str] = normalize_email(getattr(result, "email", None)) user_id: Optional[str] = ( getattr(result, "id", None) if result is not None else None ) @@ -2020,7 +2089,7 @@ class SSOAuthenticationHandler: "GENERIC_USER_ROLE_ATTRIBUTE", "role" ) user_id = getattr(result, "id", None) - user_email = getattr(result, "email", None) + user_email = normalize_email(getattr(result, "email", None)) if user_role is None: _role_from_attr = getattr(result, generic_user_role_attribute_name, None) # type: ignore if _role_from_attr is not None: @@ -2413,7 +2482,7 @@ class MicrosoftSSOHandler: response = response or {} verbose_proxy_logger.debug(f"Microsoft SSO Callback Response: {response}") openid_response = CustomOpenID( - email=response.get(MICROSOFT_USER_EMAIL_ATTRIBUTE) or response.get("mail"), + email=normalize_email(response.get(MICROSOFT_USER_EMAIL_ATTRIBUTE) or response.get("mail")), display_name=response.get(MICROSOFT_USER_DISPLAY_NAME_ATTRIBUTE), provider="microsoft", id=response.get(MICROSOFT_USER_ID_ATTRIBUTE), diff --git a/litellm/proxy/openai_files_endpoints/files_endpoints.py b/litellm/proxy/openai_files_endpoints/files_endpoints.py index 2c6b378ae38..ec6e9733344 100644 --- a/litellm/proxy/openai_files_endpoints/files_endpoints.py +++ b/litellm/proxy/openai_files_endpoints/files_endpoints.py @@ -29,7 +29,10 @@ from litellm.llms.base_llm.files.transformation import BaseFileEndpoints from litellm.proxy._types import * 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.http_parsing_utils import _read_request_body +from litellm.proxy.common_utils.http_parsing_utils import ( + _read_request_body, + extract_nested_form_metadata, +) from litellm.proxy.common_utils.openai_endpoint_utils import ( get_custom_llm_provider_from_request_body, get_custom_llm_provider_from_request_headers, @@ -153,8 +156,10 @@ async def route_create_file( # Handle custom storage backend if target_storage and target_storage != "default": - from litellm.litellm_core_utils.prompt_templates.common_utils import extract_file_data - + from litellm.litellm_core_utils.prompt_templates.common_utils import ( + extract_file_data, + ) + # Extract file data file_data = extract_file_data(cast(Any, _create_file_request.get("file"))) @@ -354,16 +359,21 @@ async def create_file( # noqa: PLR0915 data = {} - # Add litellm_metadata to data if provided (from form field) - if litellm_metadata is not None: - data["litellm_metadata"] = litellm_metadata - # Parse expires_after if provided - expires_after = None - form_data = await request.form() - expires_after_anchor = form_data.get("expires_after[anchor]") - expires_after_seconds_str = form_data.get("expires_after[seconds]") + expires_after: Optional[FileExpiresAfter] = None + form_data_raw = await request.form() + form_data_dict: Dict[str, Any] = dict(form_data_raw) + extracted_litellm_metadata: Optional[Dict[str, Any]] = extract_nested_form_metadata( + form_data=form_data_dict, + prefix="litellm_metadata[" + ) + expires_after_anchor = form_data_raw.get("expires_after[anchor]") + expires_after_seconds_str = form_data_raw.get("expires_after[seconds]") + # Add litellm_metadata to data if provided (from form field) + if extracted_litellm_metadata is not None: + data["litellm_metadata"] = extracted_litellm_metadata + if expires_after_anchor is not None or expires_after_seconds_str is not None: if expires_after_anchor is None or expires_after_seconds_str is None: raise HTTPException( @@ -622,13 +632,16 @@ async def get_file_content( # noqa: PLR0915 ) # Check if file is stored in a storage backend (check DB) - if hasattr(managed_files_obj, "prisma_client") and managed_files_obj.prisma_client: - db_file = await managed_files_obj.prisma_client.db.litellm_managedfiletable.find_first( + if hasattr(managed_files_obj, "prisma_client") and getattr(managed_files_obj, "prisma_client", None): + prisma_client = getattr(managed_files_obj, "prisma_client") + db_file = await prisma_client.db.litellm_managedfiletable.find_first( where={"unified_file_id": file_id} ) if db_file and db_file.storage_backend and db_file.storage_url: # File is stored in a storage backend, download it - from litellm.llms.base_llm.files.storage_backend_factory import get_storage_backend + from litellm.llms.base_llm.files.storage_backend_factory import ( + get_storage_backend, + ) storage_backend_name = db_file.storage_backend storage_url = db_file.storage_url @@ -812,7 +825,7 @@ async def get_file( version, ) - data: Dict = {} + data: Dict = {"file_id": file_id} try: custom_llm_provider = ( @@ -891,6 +904,9 @@ async def get_file( llm_router=llm_router, ) else: + # Remove file_id from data to avoid "multiple values for keyword argument" error + # data was initialized with {"file_id": file_id} + data.pop("file_id", None) response = await litellm.afile_retrieve( custom_llm_provider=custom_llm_provider, file_id=file_id, **data # type: ignore ) @@ -992,7 +1008,7 @@ async def delete_file( version, ) - data: Dict = {} + data: Dict = {"file_id": file_id} try: custom_llm_provider = ( provider @@ -1001,6 +1017,22 @@ async def delete_file( or await get_custom_llm_provider_from_request_body(request=request) or "openai" ) + + # Call common_processing_pre_call_logic to trigger permission checks + base_llm_response_processor = ProxyBaseLLMRequestProcessing(data=data) + ( + data, + litellm_logging_obj, + ) = await base_llm_response_processor.common_processing_pre_call_logic( + request=request, + general_settings=general_settings, + user_api_key_dict=user_api_key_dict, + version=version, + proxy_logging_obj=proxy_logging_obj, + proxy_config=proxy_config, + route_type="afile_delete", + ) + # Include original request and headers in the data data = await add_litellm_data_to_request( data=data, @@ -1060,13 +1092,16 @@ async def delete_file( code=500, ) + # Remove file_id from data to avoid duplicate keyword argument + data_without_file_id = {k: v for k, v in data.items() if k != "file_id"} response = await managed_files_obj.afile_delete( file_id=file_id, litellm_parent_otel_span=user_api_key_dict.parent_otel_span, llm_router=llm_router, - **data, + **data_without_file_id, ) else: + data.pop("file_id", None) response = await litellm.afile_delete( custom_llm_provider=custom_llm_provider, file_id=file_id, **data # type: ignore ) diff --git a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py index b079e161519..3dab6ea14f8 100644 --- a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py @@ -16,6 +16,7 @@ from fastapi.responses import StreamingResponse from starlette.websockets import WebSocketState import litellm +from litellm import get_llm_provider from litellm._logging import verbose_proxy_logger from litellm.constants import ( ALLOWED_VERTEX_AI_PASSTHROUGH_HEADERS, @@ -1052,6 +1053,89 @@ async def bedrock_proxy_route( return received_value +def _resolve_vertex_model_from_router( + model_id: str, + llm_router: Optional[litellm.Router], + encoded_endpoint: str, + endpoint: str, + vertex_project: Optional[str], + vertex_location: Optional[str], +) -> Tuple[str, str, Optional[str], Optional[str]]: + """ + Resolve Vertex AI model configuration from router. + + Args: + model_id: The model ID extracted from the URL (e.g., "gcp/google/gemini-2.5-flash") + llm_router: The LiteLLM router instance + encoded_endpoint: The encoded endpoint path + endpoint: The original endpoint path + vertex_project: Current vertex project (may be from URL) + vertex_location: Current vertex location (may be from URL) + + Returns: + Tuple of (encoded_endpoint, endpoint, vertex_project, vertex_location) + with resolved values from router config + """ + if not llm_router: + return encoded_endpoint, endpoint, vertex_project, vertex_location + + try: + deployment = llm_router.get_available_deployment_for_pass_through(model=model_id) + if not deployment: + return encoded_endpoint, endpoint, vertex_project, vertex_location + + litellm_params = deployment.get("litellm_params", {}) + + # Always override with router config values (they take precedence over URL values) + config_vertex_project = litellm_params.get("vertex_project") + config_vertex_location = litellm_params.get("vertex_location") + if config_vertex_project: + vertex_project = config_vertex_project + if config_vertex_location: + vertex_location = config_vertex_location + + # Get the actual Vertex AI model name by stripping the provider prefix + # e.g., "vertex_ai/gemini-2.0-flash-exp" -> "gemini-2.0-flash-exp" + model_from_config = litellm_params.get("model", "") + if model_from_config: + + # get_llm_provider returns (model, custom_llm_provider, dynamic_api_key, api_base) + # For "vertex_ai/gemini-2.0-flash-exp" it returns: + # model="gemini-2.0-flash-exp", custom_llm_provider="vertex_ai" + actual_model, custom_llm_provider, _, _ = get_llm_provider( + model=model_from_config + ) + + # Log only non-sensitive information (model names and provider), never API keys or secrets. + safe_actual_model = actual_model + safe_custom_llm_provider = custom_llm_provider + verbose_proxy_logger.debug( + "get_llm_provider returned: actual_model=%s, custom_llm_provider=%s, model_id=%s", + safe_actual_model, + safe_custom_llm_provider, + model_id, + ) + + if actual_model and model_id != actual_model: + verbose_proxy_logger.debug( + "Resolved router model '%s' to '%s' (provider=%s) with project=%s, location=%s", + model_id, + actual_model, + custom_llm_provider, + vertex_project, + vertex_location, + ) + encoded_endpoint = encoded_endpoint.replace(model_id, actual_model) + endpoint = endpoint.replace(model_id, actual_model) + + except Exception as e: + verbose_proxy_logger.debug( + f"Error resolving vertex model from router for model {model_id}: {e}" + ) + + return encoded_endpoint, endpoint, vertex_project, vertex_location + + def _is_bedrock_agent_runtime_route(endpoint: str) -> bool: """ Return True, if the endpoint should be routed to the `bedrock-agent-runtime` endpoint. @@ -1512,8 +1596,13 @@ async def _prepare_vertex_auth_headers( if router_credentials is not None: vertex_credentials_str = None elif vertex_credentials is not None: - vertex_project = vertex_credentials.vertex_project - vertex_location = vertex_credentials.vertex_location + # Use credentials from vertex_credentials + # When vertex_credentials are provided (including default credentials), + # use their project/location values if available + if vertex_credentials.vertex_project is not None: + vertex_project = vertex_credentials.vertex_project + if vertex_credentials.vertex_location is not None: + vertex_location = vertex_credentials.vertex_location vertex_credentials_str = vertex_credentials.vertex_credentials else: raise ValueError("No vertex credentials found") @@ -1583,6 +1672,7 @@ async def _base_vertex_proxy_route( get_vertex_model_id_from_url, get_vertex_project_id_from_url, ) + from litellm.proxy.proxy_server import llm_router encoded_endpoint = httpx.URL(endpoint).path verbose_proxy_logger.debug("requested endpoint %s", endpoint) @@ -1610,24 +1700,20 @@ async def _base_vertex_proxy_route( vertex_location=vertex_location, ) - if vertex_project is None or vertex_location is None: - # Check if model is in router config - model_id = get_vertex_model_id_from_url(endpoint) - if model_id: - from litellm.proxy.proxy_server import llm_router + # Check if model is in router config - always do this to resolve custom model names + model_id = get_vertex_model_id_from_url(endpoint) + if model_id: - if llm_router: - try: - # Use the dedicated pass-through deployment selection method to automatically filter use_in_pass_through=True - deployment = llm_router.get_available_deployment_for_pass_through(model=model_id) - if deployment: - litellm_params = deployment.get("litellm_params", {}) - vertex_project = litellm_params.get("vertex_project") - vertex_location = litellm_params.get("vertex_location") - except Exception as e: - verbose_proxy_logger.debug( - f"Error getting available deployment for model {model_id}: {e}" - ) + if llm_router: + # Resolve model configuration from router + encoded_endpoint, endpoint, vertex_project, vertex_location = _resolve_vertex_model_from_router( + model_id=model_id, + llm_router=llm_router, + encoded_endpoint=encoded_endpoint, + endpoint=endpoint, + vertex_project=vertex_project, + vertex_location=vertex_location, + ) vertex_credentials = passthrough_endpoint_router.get_vertex_credentials( project_id=vertex_project, @@ -1795,6 +1881,11 @@ async def vertex_proxy_route( ) +@router.api_route( + "/openai_passthrough/{endpoint:path}", + methods=["GET", "POST", "PUT", "DELETE", "PATCH"], + tags=["OpenAI Pass-through", "pass-through"], +) @router.api_route( "/openai/{endpoint:path}", methods=["GET", "POST", "PUT", "DELETE", "PATCH"], @@ -1807,9 +1898,27 @@ async def openai_proxy_route( user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), ): """ - Simple pass-through for OpenAI. Use this if you want to directly send a request to OpenAI. - - + Pass-through endpoint for OpenAI API calls. + + Available on both routes: + - /openai/{endpoint:path} - Standard OpenAI passthrough route + - /openai_passthrough/{endpoint:path} - Dedicated passthrough route (recommended for Responses API) + + Use /openai_passthrough/* when you need guaranteed passthrough to OpenAI without conflicts + with LiteLLM's native implementations (e.g., for the Responses API at /v1/responses). + + Examples: + Standard route: + - /openai/v1/chat/completions + - /openai/v1/assistants + - /openai/v1/threads + + Dedicated passthrough (for Responses API): + - /openai_passthrough/v1/responses + - /openai_passthrough/v1/responses/{response_id} + - /openai_passthrough/v1/responses/{response_id}/input_items + + [Docs](https://docs.litellm.ai/docs/pass_through/openai_passthrough) """ base_target_url = os.getenv("OPENAI_API_BASE") or "https://api.openai.com/" # Add or update query parameters diff --git a/litellm/proxy/proxy_cli.py b/litellm/proxy/proxy_cli.py index 2059246674b..2bc1c8f8e98 100644 --- a/litellm/proxy/proxy_cli.py +++ b/litellm/proxy/proxy_cli.py @@ -127,6 +127,7 @@ class ProxyInitializationHelpers: Get the arguments for `uvicorn` worker """ import litellm + from litellm._logging import _get_uvicorn_json_log_config uvicorn_args = { "app": "litellm.proxy.proxy_server:app", @@ -137,8 +138,8 @@ class ProxyInitializationHelpers: print(f"Using log_config: {log_config}") # noqa uvicorn_args["log_config"] = log_config elif litellm.json_logs: - print("Using json logs. Setting log_config to None.") # noqa - uvicorn_args["log_config"] = None + # Use JSON log config for uvicorn to ensure all logs (including exceptions) are JSON + uvicorn_args["log_config"] = _get_uvicorn_json_log_config() if keepalive_timeout is not None: uvicorn_args["timeout_keep_alive"] = keepalive_timeout return uvicorn_args diff --git a/litellm/proxy/proxy_config.yaml b/litellm/proxy/proxy_config.yaml index ea405c1dea4..d87ae8b14ca 100644 --- a/litellm/proxy/proxy_config.yaml +++ b/litellm/proxy/proxy_config.yaml @@ -1,101 +1,62 @@ model_list: - - model_name: "*" + - model_name: gpt-4o litellm_params: - model: "*" - - model_name: "gpt-4" - litellm_params: - model: "gpt-4" - api_key: os.environ/OPENAI_API_KEY - - model_name: "gpt-3.5-turbo" - litellm_params: - model: "gpt-3.5-turbo" + model: openai/gpt-4o api_key: os.environ/OPENAI_API_KEY + - model_name: text-embedding-3-small + litellm_params: + model: openai/text-embedding-3-small + api_key: os.environ/OPENAI_API_KEY + + - model_name: bedrock-claude-sonnet-3.5 + litellm_params: + model: "bedrock/us.anthropic.claude-3-5-sonnet-20240620-v1:0" + aws_region_name: "us-east-1" + + - model_name: bedrock-claude-sonnet-4 + litellm_params: + model: "bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0" + aws_region_name: "us-east-1" + + - model_name: bedrock-claude-sonnet-4.5 + litellm_params: + model: "bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0" + aws_region_name: "us-east-1" + + - model_name: bedrock-claude-opus-4.5 + litellm_params: + model: "bedrock/converse/us.anthropic.claude-opus-4-5-20251101-v1:0" + aws_region_name: "us-east-1" + + - model_name: bedrock-nova-premier + litellm_params: + model: "bedrock/us.amazon.nova-premier-v1:0" + aws_region_name: "us-east-1" + +# MCP Server Configuration +mcp_servers: + # Wikipedia MCP - reliable and works without external deps + wikipedia: + transport: "stdio" + command: "uvx" + args: ["mcp-server-fetch"] + description: "Fetch web pages and Wikipedia content" + deepwiki: + transport: "http" + url: "https://mcp.deepwiki.com/mcp" + +# General Settings general_settings: master_key: sk-1234 + store_model_in_db: false -# ─────────────────────────────────────────────── -# POLICIES - Define WHAT guardrails to apply -# ─────────────────────────────────────────────── -# -# Policies define guardrails with: -# - inherit: Inherit guardrails from another policy -# - description: Human-readable description -# - guardrails.add: Add guardrails (on top of inherited) -# - guardrails.remove: Remove guardrails (from inherited) -# - condition.model: Model pattern (exact or regex) for when guardrails apply -# -policies: - # Global baseline policy - global-baseline: - description: "Base guardrails for all requests" - guardrails: - add: - - pii_blocker - - # Healthcare policy - inherits from global-baseline - healthcare-compliance: - inherit: global-baseline - description: "HIPAA compliance for healthcare teams" - guardrails: - add: - - hipaa_audit - - # Dev policy - inherits but removes PII blocker for testing - internal-dev: - inherit: global-baseline - description: "Relaxed policy for internal development" - guardrails: - add: - - toxicity_filter - remove: - - pii_blocker - - # Policy with model condition (regex pattern) - gpt4-safety: - description: "Extra safety for GPT-4 models" - guardrails: - add: - - toxicity_filter - condition: - model: "gpt-4.*" # regex: matches gpt-4, gpt-4-turbo, gpt-4o, etc. - - # Policy with model condition (exact match list) - bedrock-compliance: - description: "Compliance for Bedrock models" - guardrails: - add: - - strict_pii_blocker - condition: - model: ["bedrock/claude-3", "bedrock/claude-2"] # exact matches - -# ─────────────────────────────────────────────── -# POLICY ATTACHMENTS - Define WHERE policies apply -# ─────────────────────────────────────────────── -# -# Attachments are REQUIRED to make policies active. -# A policy without an attachment will not be applied. -# -policy_attachments: - # Global attachment - applies to all requests - - policy: global-baseline - scope: "*" - - # Team-specific attachment - - policy: healthcare-compliance - teams: - - healthcare-team - - medical-research - - # Key pattern attachment - - policy: internal-dev - keys: - - "dev-key-*" - - "test-key-*" - - # Model-specific policies (attached globally, condition filters by model) - - policy: gpt4-safety - scope: "*" - - - policy: bedrock-compliance - scope: "*" +# LiteLLM Settings +litellm_settings: + # Enable MCP Semantic Tool Filter + mcp_semantic_tool_filter: + enabled: true + embedding_model: "text-embedding-3-small" + top_k: 5 + similarity_threshold: 0.3 + diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 8268e612bfb..8f433bfa486 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -1,5 +1,6 @@ import asyncio import copy +import enum import inspect import io import os @@ -11,7 +12,7 @@ import sys import time import traceback import warnings -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import ( TYPE_CHECKING, Any, @@ -29,6 +30,8 @@ from typing import ( get_type_hints, ) +from pydantic import BaseModel, Json + from litellm._uuid import uuid from litellm.constants import ( AIOHTTP_CONNECTOR_LIMIT, @@ -45,7 +48,43 @@ from litellm.constants import ( LITELLM_EMBEDDING_PROVIDERS_SUPPORTING_INPUT_ARRAY_OF_TOKENS, LITELLM_SETTINGS_SAFE_DB_OVERRIDES, ) +from litellm.litellm_core_utils.litellm_logging import ( + _init_custom_logger_compatible_class, +) from litellm.litellm_core_utils.safe_json_dumps import safe_dumps +from litellm.proxy._types import ( + CallbackDelete, + CallInfo, + CommonProxyErrors, + ConfigFieldDelete, + ConfigFieldInfo, + ConfigFieldUpdate, + ConfigGeneralSettings, + ConfigList, + ConfigYAML, + EnterpriseLicenseData, + FieldDetail, + InvitationClaim, + InvitationDelete, + InvitationModel, + InvitationNew, + InvitationUpdate, + Litellm_EntityType, + LiteLLM_JWTAuth, + LiteLLM_TeamTable, + LiteLLM_UserTable, + LitellmUserRoles, + PassThroughGenericEndpoint, + ProxyErrorTypes, + ProxyException, + RoleBasedPermissions, + SpecialModelNames, + SupportedDBObjectType, + TeamDefaultSettings, + TokenCountRequest, + TransformRequestBody, + UserAPIKeyAuth, +) from litellm.proxy.common_utils.callback_utils import ( normalize_callback_names, process_callback, @@ -226,6 +265,7 @@ from litellm.proxy.auth.model_checks import ( get_team_models, ) from litellm.proxy.auth.user_api_key_auth import ( + _fetch_global_spend_with_event_coordination, user_api_key_auth, user_api_key_auth_websocket, ) @@ -550,9 +590,9 @@ except ImportError: server_root_path = get_server_root_path() _license_check = LicenseCheck() premium_user: bool = _license_check.is_premium() -premium_user_data: Optional["EnterpriseLicenseData"] = ( - _license_check.airgapped_license_data -) +premium_user_data: Optional[ + "EnterpriseLicenseData" +] = _license_check.airgapped_license_data global_max_parallel_request_retries_env: Optional[str] = os.getenv( "LITELLM_GLOBAL_MAX_PARALLEL_REQUEST_RETRIES" ) @@ -753,6 +793,21 @@ async def proxy_startup_event(app: FastAPI): # noqa: PLR0915 redis_usage_cache=redis_usage_cache, ) + ## SEMANTIC TOOL FILTER ## + # Read litellm_settings from config for semantic filter initialization + try: + verbose_proxy_logger.debug("About to initialize semantic tool filter") + _config = proxy_config.get_config_state() + _litellm_settings = _config.get("litellm_settings", {}) + verbose_proxy_logger.debug(f"litellm_settings keys = {list(_litellm_settings.keys())}") + await ProxyStartupEvent._initialize_semantic_tool_filter( + llm_router=llm_router, + litellm_settings=_litellm_settings, + ) + verbose_proxy_logger.debug("After semantic tool filter initialization") + except Exception as e: + verbose_proxy_logger.error(f"Semantic filter init failed: {e}", exc_info=True) + ## JWT AUTH ## ProxyStartupEvent._initialize_jwt_auth( general_settings=general_settings, @@ -768,6 +823,13 @@ async def proxy_startup_event(app: FastAPI): # noqa: PLR0915 ProxyStartupEvent._add_proxy_budget_to_db( litellm_proxy_budget_name=litellm_proxy_admin_name ) + asyncio.create_task( + ProxyStartupEvent._warm_global_spend_cache( + litellm_proxy_admin_name=litellm_proxy_admin_name, + user_api_key_cache=user_api_key_cache, + prisma_client=prisma_client, + ) + ) ### START BATCH WRITING DB + CHECKING NEW MODELS### if prisma_client is not None: @@ -825,6 +887,7 @@ app = FastAPI( title=_title, description=_description, version=version, + root_path=server_root_path, lifespan=proxy_startup_event, # type: ignore[reportGeneralTypeIssues] ) @@ -1209,9 +1272,9 @@ master_key: Optional[str] = None config_agents: Optional[List[AgentConfig]] = None otel_logging = False prisma_client: Optional[PrismaClient] = None -shared_aiohttp_session: Optional["ClientSession"] = ( - None # Global shared session for connection reuse -) +shared_aiohttp_session: Optional[ + "ClientSession" +] = None # Global shared session for connection reuse user_api_key_cache = DualCache( default_in_memory_ttl=UserAPIKeyCacheTTLEnum.in_memory_cache_ttl.value ) @@ -1219,10 +1282,11 @@ model_max_budget_limiter = _PROXY_VirtualKeyModelMaxBudgetLimiter( dual_cache=user_api_key_cache ) litellm.logging_callback_manager.add_litellm_callback(model_max_budget_limiter) -redis_usage_cache: Optional[RedisCache] = ( - None # redis cache used for tracking spend, tpm/rpm limits -) +redis_usage_cache: Optional[ + RedisCache +] = None # redis cache used for tracking spend, tpm/rpm limits polling_via_cache_enabled: Union[Literal["all"], List[str], bool] = False +native_background_mode: List[str] = [] # Models that should use native provider background mode instead of polling polling_cache_ttl: int = 3600 # Default 1 hour TTL for polling cache user_custom_auth = None user_custom_key_generate = None @@ -1560,9 +1624,9 @@ async def update_cache( # noqa: PLR0915 _id = "team_id:{}".format(team_id) try: # Fetch the existing cost for the given user - existing_spend_obj: Optional[LiteLLM_TeamTable] = ( - await user_api_key_cache.async_get_cache(key=_id) - ) + existing_spend_obj: Optional[ + LiteLLM_TeamTable + ] = await user_api_key_cache.async_get_cache(key=_id) if existing_spend_obj is None: # do nothing if team not in api key cache return @@ -2144,6 +2208,12 @@ class ProxyConfig: List of validated SearchToolTypedDict or None if not configured """ search_tools_raw = config.get("search_tools", None) + if not search_tools_raw: + # Check in general_settings + general_settings = config.get("general_settings", {}) + if general_settings: + search_tools_raw = general_settings.get("search_tools", None) + if not search_tools_raw: return None @@ -2455,14 +2525,17 @@ class ProxyConfig: pass elif key == "responses": # Initialize global polling via cache settings - global polling_via_cache_enabled, polling_cache_ttl + global polling_via_cache_enabled, native_background_mode, polling_cache_ttl background_mode = value.get("background_mode", {}) polling_via_cache_enabled = background_mode.get( "polling_via_cache", False ) + native_background_mode = background_mode.get( + "native_background_mode", [] + ) polling_cache_ttl = background_mode.get("ttl", 3600) verbose_proxy_logger.debug( - f"{blue_color_code} Initialized polling via cache: enabled={polling_via_cache_enabled}, ttl={polling_cache_ttl}{reset_color_code}" + f"{blue_color_code} Initialized polling via cache: enabled={polling_via_cache_enabled}, native_background_mode={native_background_mode}, ttl={polling_cache_ttl}{reset_color_code}" ) elif key == "default_team_settings": for idx, team_setting in enumerate( @@ -2726,15 +2799,15 @@ class ProxyConfig: router_settings = config.get("router_settings", None) if router_settings and isinstance(router_settings, dict): - arg_spec = inspect.getfullargspec(litellm.Router) # model list and search_tools already set exclude_args = { - "self", "model_list", "search_tools", } - available_args = [x for x in arg_spec.args if x not in exclude_args] + available_args = [ + x for x in litellm.Router.get_valid_args() if x not in exclude_args + ] for k, v in router_settings.items(): if k in available_args: @@ -2856,6 +2929,7 @@ class ProxyConfig: from litellm.proxy.policy_engine.init_policies import init_policies from litellm.proxy.policy_engine.policy_validator import PolicyValidator + if config is None: verbose_proxy_logger.debug("Policy engine: config is None, skipping") return @@ -2867,7 +2941,9 @@ class ProxyConfig: policy_attachments_config = config.get("policy_attachments", None) - verbose_proxy_logger.info(f"Policy engine: found {len(policies_config)} policies in config") + verbose_proxy_logger.info( + f"Policy engine: found {len(policies_config)} policies in config" + ) # Initialize policies await init_policies( @@ -2882,9 +2958,6 @@ class ProxyConfig: """ Initialize alerting settings """ - from litellm.litellm_core_utils.litellm_logging import ( - _init_custom_logger_compatible_class, - ) _alerting_callbacks = general_settings.get("alerting", None) verbose_proxy_logger.debug(f"_alerting_callbacks: {general_settings}") @@ -3182,6 +3255,8 @@ class ProxyConfig: verbose_proxy_logger.debug(f"updated llm_router: {llm_router}") else: verbose_proxy_logger.debug(f"len new_models: {len(models_list)}") + if search_tools is not None and llm_router is not None: + llm_router.search_tools = search_tools ## DELETE MODEL LOGIC await self._delete_deployment(db_models=models_list) @@ -3320,105 +3395,6 @@ class ProxyConfig: decrypted_variables[k] = decrypted_value return decrypted_variables - async def _get_hierarchical_router_settings( - self, - user_api_key_dict: Optional["UserAPIKeyAuth"], - prisma_client: Optional[PrismaClient], - ) -> Optional[dict]: - """ - Get router_settings in priority order: Key > Team > Global - - Returns: - dict: Combined router_settings, or None if no settings found - """ - if prisma_client is None: - return None - - import json - - import yaml - - # 1. Try key-level router_settings - if user_api_key_dict is not None: - # Check if router_settings is available on the key object - key_router_settings_value = getattr( - user_api_key_dict, "router_settings", None - ) - if key_router_settings_value is not None: - key_router_settings = None - if isinstance(key_router_settings_value, str): - try: - key_router_settings = yaml.safe_load(key_router_settings_value) - except (yaml.YAMLError, json.JSONDecodeError): - try: - key_router_settings = json.loads(key_router_settings_value) - except json.JSONDecodeError: - pass - elif isinstance(key_router_settings_value, dict): - key_router_settings = key_router_settings_value - - # If key has router_settings (non-empty dict), use it - if ( - key_router_settings is not None - and isinstance(key_router_settings, dict) - and key_router_settings - ): - return key_router_settings - - # 2. Try team-level router_settings - if user_api_key_dict is not None and user_api_key_dict.team_id is not None: - try: - team_obj = await prisma_client.db.litellm_teamtable.find_unique( - where={"team_id": user_api_key_dict.team_id} - ) - if team_obj is not None: - team_router_settings_value = getattr( - team_obj, "router_settings", None - ) - if team_router_settings_value is not None: - team_router_settings = None - if isinstance(team_router_settings_value, str): - try: - team_router_settings = yaml.safe_load( - team_router_settings_value - ) - except (yaml.YAMLError, json.JSONDecodeError): - try: - team_router_settings = json.loads( - team_router_settings_value - ) - except json.JSONDecodeError: - pass - elif isinstance(team_router_settings_value, dict): - team_router_settings = team_router_settings_value - - # If team has router_settings (non-empty dict), use it - if ( - team_router_settings is not None - and isinstance(team_router_settings, dict) - and team_router_settings - ): - return team_router_settings - except Exception: - # If team lookup fails, continue to global settings - pass - - # 3. Try global router_settings - try: - db_router_settings = await prisma_client.db.litellm_config.find_first( - where={"param_name": "router_settings"} - ) - if ( - db_router_settings is not None - and isinstance(db_router_settings.param_value, dict) - and db_router_settings.param_value - ): - return db_router_settings.param_value - except Exception: - pass - - return None - async def _add_router_settings_from_db_config( self, config_data: dict, @@ -3536,6 +3512,81 @@ class ProxyConfig: llm_router=llm_router, ) + async def _reschedule_spend_log_cleanup_job(self): + """ + Reschedule the spend log cleanup job based on current general_settings. + This is called when maximum_spend_logs_retention_period is updated dynamically. + If the retention period is None, the job will be removed. + """ + global scheduler, general_settings, prisma_client + if scheduler is None: + return + + # Remove existing job if it exists + try: + scheduler.remove_job("spend_log_cleanup_job") + verbose_proxy_logger.info("Removed existing spend log cleanup job") + except Exception: + pass # Job might not exist, which is fine + + # Schedule new job if retention period is set (not None) + retention_period = general_settings.get("maximum_spend_logs_retention_period") + if retention_period is not None: + from litellm.proxy.db.db_transaction_queue.spend_log_cleanup import ( + SpendLogCleanup, + ) + + spend_log_cleanup = SpendLogCleanup() + cleanup_cron = general_settings.get("maximum_spend_logs_cleanup_cron") + + if cleanup_cron: + from apscheduler.triggers.cron import CronTrigger + + try: + cron_trigger = CronTrigger.from_crontab(cleanup_cron) + scheduler.add_job( + spend_log_cleanup.cleanup_old_spend_logs, + cron_trigger, + args=[prisma_client], + id="spend_log_cleanup_job", + replace_existing=True, + misfire_grace_time=APSCHEDULER_MISFIRE_GRACE_TIME, + ) + verbose_proxy_logger.info( + f"Spend log cleanup rescheduled with cron: {cleanup_cron}" + ) + except ValueError: + verbose_proxy_logger.error( + f"Invalid maximum_spend_logs_cleanup_cron value: {cleanup_cron}" + ) + else: + # Interval-based scheduling (existing behavior) + from litellm.litellm_core_utils.duration_parser import ( + duration_in_seconds, + ) + + retention_interval = general_settings.get( + "maximum_spend_logs_retention_interval", "1d" + ) + try: + interval_seconds = duration_in_seconds(retention_interval) + scheduler.add_job( + spend_log_cleanup.cleanup_old_spend_logs, + "interval", + seconds=interval_seconds + random.randint(0, 60), + args=[prisma_client], + id="spend_log_cleanup_job", + replace_existing=True, + misfire_grace_time=APSCHEDULER_MISFIRE_GRACE_TIME, + ) + verbose_proxy_logger.info( + f"Spend log cleanup rescheduled with interval: {retention_interval}" + ) + except ValueError: + verbose_proxy_logger.error( + "Invalid maximum_spend_logs_retention_interval value" + ) + async def _update_general_settings(self, db_general_settings: Optional[Json]): """ Pull from DB, read general settings value @@ -3575,6 +3626,32 @@ class ProxyConfig: if "ui_access_mode" in _general_settings: general_settings["ui_access_mode"] = _general_settings["ui_access_mode"] + ## STORE PROMPTS IN SPEND LOGS ## + if "store_prompts_in_spend_logs" in _general_settings: + value = _general_settings["store_prompts_in_spend_logs"] + # Normalize case: handle True/true/TRUE, False/false/FALSE, None/null + if value is None: + general_settings["store_prompts_in_spend_logs"] = None + elif isinstance(value, bool): + general_settings["store_prompts_in_spend_logs"] = value + elif isinstance(value, str): + # Case-insensitive string comparison + general_settings["store_prompts_in_spend_logs"] = ( + value.lower() == "true" + ) + else: + # For other types, convert to bool + general_settings["store_prompts_in_spend_logs"] = bool(value) + + ## MAXIMUM SPEND LOGS RETENTION PERIOD ## + if "maximum_spend_logs_retention_period" in _general_settings: + old_value = general_settings.get("maximum_spend_logs_retention_period") + new_value = _general_settings["maximum_spend_logs_retention_period"] + general_settings["maximum_spend_logs_retention_period"] = new_value + # Reschedule cleanup job if value changed (including when set to None) + if old_value != new_value: + await self._reschedule_spend_log_cleanup_job() + def _update_config_fields( self, current_config: dict, @@ -3849,8 +3926,9 @@ class ProxyConfig: where={"id": "sso_config"} ) if sso_settings is not None: - # Capitalize all keys in sso_settings dictionary sso_settings.sso_settings.pop("role_mappings", None) + sso_settings.sso_settings.pop("team_mappings", None) + sso_settings.sso_settings.pop("ui_access_mode", None) uppercase_sso_settings = { key.upper(): value for key, value in sso_settings.sso_settings.items() @@ -4009,10 +4087,10 @@ class ProxyConfig: ) try: - guardrails_in_db: List[Guardrail] = ( - await GuardrailRegistry.get_all_guardrails_from_db( - prisma_client=prisma_client - ) + guardrails_in_db: List[ + Guardrail + ] = await GuardrailRegistry.get_all_guardrails_from_db( + prisma_client=prisma_client ) verbose_proxy_logger.debug( "guardrails from the DB %s", str(guardrails_in_db) @@ -4046,7 +4124,9 @@ class ProxyConfig: await policy_registry.sync_policies_from_db(prisma_client=prisma_client) # Sync attachments from DB to in-memory registry - await attachment_registry.sync_attachments_from_db(prisma_client=prisma_client) + await attachment_registry.sync_attachments_from_db( + prisma_client=prisma_client + ) verbose_proxy_logger.debug( "Successfully synced policies and attachments from DB" @@ -4369,9 +4449,9 @@ async def initialize( # noqa: PLR0915 user_api_base = api_base dynamic_config[user_model]["api_base"] = api_base if api_version: - os.environ["AZURE_API_VERSION"] = ( - api_version # set this for azure - litellm can read this from the env - ) + os.environ[ + "AZURE_API_VERSION" + ] = api_version # set this for azure - litellm can read this from the env if max_tokens: # model-specific param dynamic_config[user_model]["max_tokens"] = max_tokens if temperature: # model-specific param @@ -4462,6 +4542,68 @@ async def async_assistants_data_generator( yield f"data: {error_returned}\n\n" +def _get_client_requested_model_for_streaming(request_data: dict) -> str: + """ + Prefer the original client-requested model (pre-alias mapping) when available. + + Pre-call processing can rewrite `request_data["model"]` for aliasing/routing purposes. + The OpenAI-compatible public `model` field should reflect what the client sent. + """ + requested_model = request_data.get("_litellm_client_requested_model") + if isinstance(requested_model, str): + return requested_model + + requested_model = request_data.get("model") + return requested_model if isinstance(requested_model, str) else "" + + +def _restamp_streaming_chunk_model( + *, + chunk: Any, + requested_model_from_client: str, + request_data: dict, + model_mismatch_logged: bool, +) -> Tuple[Any, bool]: + # Always return the client-requested model name (not provider-prefixed internal identifiers) + # on streaming chunks. + # + # Note: This warning is intentionally verbose. A mismatch is a useful signal that an + # internal provider/deployment identifier is leaking into the public API, and helps + # maintainers/operators catch regressions while preserving OpenAI-compatible output. + if not requested_model_from_client or not isinstance(chunk, (BaseModel, dict)): + return chunk, model_mismatch_logged + + downstream_model = ( + chunk.get("model") if isinstance(chunk, dict) else getattr(chunk, "model", None) + ) + if not model_mismatch_logged and downstream_model != requested_model_from_client: + verbose_proxy_logger.warning( + "litellm_call_id=%s: streaming chunk model mismatch - requested=%r downstream=%r. Overriding model to requested.", + request_data.get("litellm_call_id"), + requested_model_from_client, + downstream_model, + ) + model_mismatch_logged = True + + if isinstance(chunk, dict): + chunk["model"] = requested_model_from_client + return chunk, model_mismatch_logged + + try: + setattr(chunk, "model", requested_model_from_client) + except Exception as e: + verbose_proxy_logger.error( + "litellm_call_id=%s: failed to override chunk.model=%r on chunk_type=%s. error=%s", + request_data.get("litellm_call_id"), + requested_model_from_client, + type(chunk), + str(e), + exc_info=True, + ) + + return chunk, model_mismatch_logged + + async def async_data_generator( response, user_api_key_dict: UserAPIKeyAuth, request_data: dict ): @@ -4470,6 +4612,10 @@ async def async_data_generator( # Use a list to accumulate response segments to avoid O(n^2) string concatenation str_so_far_parts: list[str] = [] error_message: Optional[str] = None + requested_model_from_client = _get_client_requested_model_for_streaming( + request_data=request_data + ) + model_mismatch_logged = False async for chunk in proxy_logging_obj.async_post_call_streaming_iterator_hook( user_api_key_dict=user_api_key_dict, response=response, @@ -4491,6 +4637,13 @@ async def async_data_generator( response_str = litellm.get_response_string(response_obj=chunk) str_so_far_parts.append(response_str) + chunk, model_mismatch_logged = _restamp_streaming_chunk_model( + chunk=chunk, + requested_model_from_client=requested_model_from_client, + request_data=request_data, + model_mismatch_logged=model_mismatch_logged, + ) + if isinstance(chunk, BaseModel): chunk = chunk.model_dump_json(exclude_none=True, exclude_unset=True) elif isinstance(chunk, str) and chunk.startswith("data: "): @@ -4604,6 +4757,34 @@ class ProxyStartupEvent: llm_router=llm_router, redis_usage_cache=redis_usage_cache ) + @classmethod + async def _initialize_semantic_tool_filter( + cls, + llm_router: Optional[Router], + litellm_settings: Dict[str, Any], + ): + """Initialize MCP semantic tool filter if configured""" + from litellm.proxy.hooks.mcp_semantic_filter import SemanticToolFilterHook + + verbose_proxy_logger.info( + f"Initializing semantic tool filter: llm_router={llm_router is not None}, " + f"litellm_settings keys={list(litellm_settings.keys())}" + ) + + mcp_semantic_filter_config = litellm_settings.get("mcp_semantic_tool_filter", None) + verbose_proxy_logger.debug(f"Semantic filter config: {mcp_semantic_filter_config}") + + hook = await SemanticToolFilterHook.initialize_from_config( + config=mcp_semantic_filter_config, + llm_router=llm_router, + ) + + if hook: + verbose_proxy_logger.debug("✅ Semantic tool filter hook registered") + litellm.logging_callback_manager.add_litellm_callback(hook) + else: + verbose_proxy_logger.warning("❌ Semantic tool filter hook not initialized") + @classmethod def _initialize_jwt_auth( cls, @@ -4654,6 +4835,26 @@ class ProxyStartupEvent: ) ) + @classmethod + async def _warm_global_spend_cache( + cls, + litellm_proxy_admin_name: str, + user_api_key_cache: DualCache, + prisma_client: PrismaClient, + ) -> None: + """Warm global spend cache once at startup to reduce impact of first wave of requests.""" + try: + cache_key = "{}:spend".format(litellm_proxy_admin_name) + await _fetch_global_spend_with_event_coordination( + cache_key=cache_key, + user_api_key_cache=user_api_key_cache, + prisma_client=prisma_client, + ) + except Exception as e: + verbose_proxy_logger.debug( + "Global spend cache warm-up at startup skipped or failed: %s", e + ) + @classmethod async def _update_default_team_member_budget(cls): """Update the default team member budget""" @@ -4682,7 +4883,7 @@ class ProxyStartupEvent: proxy_logging_obj: ProxyLogging, ): """Initializes scheduled background jobs""" - global store_model_in_db + global store_model_in_db, scheduler # MEMORY LEAK FIX: Configure scheduler with optimized settings # Memray analysis showed APScheduler's normalize() and _apply_jitter() causing @@ -5092,7 +5293,20 @@ class ProxyStartupEvent: except Exception as e: raise e - await prisma_client.connect() + try: + await prisma_client.connect() + except Exception as e: + if "P3018" in str(e) or "P3009" in str(e): + verbose_proxy_logger.debug( + "CRITICAL: DATABASE MIGRATION FAILED" + ) + verbose_proxy_logger.debug( + "Your database is in a 'dirty' state." + ) + verbose_proxy_logger.debug( + "FIX: Run 'prisma migrate resolve --applied '" + ) + raise e ## Start RDS IAM token refresh background task if enabled ## # This proactively refreshes IAM tokens before they expire, @@ -5217,7 +5431,9 @@ async def model_list( # Include model access groups if requested if include_model_access_groups: - proxy_model_list = list(set(proxy_model_list + list(model_access_groups.keys()))) + proxy_model_list = list( + set(proxy_model_list + list(model_access_groups.keys())) + ) # Get complete model list including wildcard routes if requested from litellm.proxy.auth.model_checks import get_complete_model_list @@ -7729,12 +7945,12 @@ def _enrich_model_info_with_litellm_data( """ Enrich a model dictionary with litellm model info (pricing, context window, etc.) and remove sensitive information. - + Args: model: Model dictionary to enrich debug: Whether to include debug information like openai_client llm_router: Optional router instance for debug info - + Returns: Enriched model dictionary with sensitive info removed """ @@ -7744,9 +7960,7 @@ def _enrich_model_info_with_litellm_data( _openai_client = "None" if llm_router is not None: _openai_client = ( - llm_router._get_client( - deployment=model, kwargs={}, client_type="async" - ) + llm_router._get_client(deployment=model, kwargs={}, client_type="async") or "None" ) else: @@ -7794,6 +8008,463 @@ def _enrich_model_info_with_litellm_data( return model +async def _apply_search_filter_to_models( + all_models: List[Dict[str, Any]], + search: str, + page: int, + size: int, + prisma_client: Optional[Any], + proxy_config: Any, + sort_by: Optional[str] = None, +) -> Tuple[List[Dict[str, Any]], Optional[int]]: + """ + Apply search filter to models, querying database for additional matching models. + + Args: + all_models: List of models to filter + search: Search term (case-insensitive) + page: Current page number + size: Page size + prisma_client: Prisma client for database queries + proxy_config: Proxy config for decrypting models + sort_by: Optional sort field - if provided, fetch all matching models instead of paginating at DB level + + Returns: + Tuple of (filtered_models, total_count). total_count is None if not searching. + """ + if not search or not search.strip(): + return all_models, None + + search_lower = search.lower().strip() + + # Filter models in router by search term + filtered_router_models = [ + m for m in all_models if search_lower in m.get("model_name", "").lower() + ] + + # Separate filtered models into config vs db models, and track db model IDs + filtered_config_models = [] + db_model_ids_in_router = set() + + for m in filtered_router_models: + model_info = m.get("model_info", {}) + is_db_model = model_info.get("db_model", False) + model_id = model_info.get("id") + + if is_db_model and model_id: + db_model_ids_in_router.add(model_id) + else: + filtered_config_models.append(m) + + config_models_count = len(filtered_config_models) + db_models_in_router_count = len(db_model_ids_in_router) + router_models_count = config_models_count + db_models_in_router_count + + # Query database for additional models with search term + db_models = [] + db_models_total_count = 0 + models_needed_for_page = size * page + + # Only query database if prisma_client is available + if prisma_client is not None: + try: + # Build where condition for database query + db_where_condition: Dict[str, Any] = { + "model_name": { + "contains": search_lower, + "mode": "insensitive", + } + } + # Exclude models already in router if we have any + if db_model_ids_in_router: + db_where_condition["model_id"] = { + "not": {"in": list(db_model_ids_in_router)} + } + + # Get total count of matching database models + db_models_total_count = ( + await prisma_client.db.litellm_proxymodeltable.count( + where=db_where_condition + ) + ) + + # Calculate total count for search results + search_total_count = router_models_count + db_models_total_count + + # If sorting is requested, we need to fetch ALL matching models to sort correctly + # Otherwise, we can optimize by only fetching what's needed for the current page + if sort_by: + # Fetch all matching database models for sorting + if db_models_total_count > 0: + db_models_raw = ( + await prisma_client.db.litellm_proxymodeltable.find_many( + where=db_where_condition, + take=db_models_total_count, # Fetch all matching models + ) + ) + + # Convert database models to router format + for db_model in db_models_raw: + decrypted_models = proxy_config.decrypt_model_list_from_db( + [db_model] + ) + if decrypted_models: + db_models.extend(decrypted_models) + else: + # Fetch database models if we need more for the current page + if router_models_count < models_needed_for_page: + models_to_fetch = min( + models_needed_for_page - router_models_count, db_models_total_count + ) + + if models_to_fetch > 0: + db_models_raw = ( + await prisma_client.db.litellm_proxymodeltable.find_many( + where=db_where_condition, + take=models_to_fetch, + ) + ) + + # Convert database models to router format + for db_model in db_models_raw: + decrypted_models = proxy_config.decrypt_model_list_from_db( + [db_model] + ) + if decrypted_models: + db_models.extend(decrypted_models) + except Exception as e: + verbose_proxy_logger.exception( + f"Error querying database models with search: {str(e)}" + ) + # If error, use router models count as fallback + search_total_count = router_models_count + else: + # If no prisma_client, only use router models + search_total_count = router_models_count + + # Combine all models + filtered_models = filtered_router_models + db_models + return filtered_models, search_total_count + + +def _normalize_datetime_for_sorting(dt: Any) -> Optional[datetime]: + """ + Normalize a datetime value to a timezone-aware UTC datetime for sorting. + + This function handles: + - None values: returns None + - String values: parses ISO format strings and converts to UTC-aware datetime + - Datetime objects: converts naive datetimes to UTC-aware, and aware datetimes to UTC + + Args: + dt: Datetime value (None, str, or datetime object) + + Returns: + UTC-aware datetime object, or None if input is None or cannot be parsed + """ + if dt is None: + return None + + if isinstance(dt, str): + try: + # Handle ISO format strings, including 'Z' suffix + dt_str = dt.replace("Z", "+00:00") if dt.endswith("Z") else dt + parsed_dt = datetime.fromisoformat(dt_str) + # Ensure it's UTC-aware + if parsed_dt.tzinfo is None: + parsed_dt = parsed_dt.replace(tzinfo=timezone.utc) + else: + parsed_dt = parsed_dt.astimezone(timezone.utc) + return parsed_dt + except (ValueError, AttributeError): + return None + + if isinstance(dt, datetime): + # If naive, assume UTC and make it aware + if dt.tzinfo is None: + return dt.replace(tzinfo=timezone.utc) + # If aware, convert to UTC + return dt.astimezone(timezone.utc) + + return None + + +def _sort_models( + all_models: List[Dict[str, Any]], + sort_by: Optional[str], + sort_order: str = "asc", +) -> List[Dict[str, Any]]: + """ + Sort models by the specified field and order. + + Args: + all_models: List of models to sort + sort_by: Field to sort by (model_name, created_at, updated_at, costs, status) + sort_order: Sort order (asc or desc) + + Returns: + Sorted list of models + """ + if not sort_by or sort_by not in ["model_name", "created_at", "updated_at", "costs", "status"]: + return all_models + + reverse = sort_order.lower() == "desc" + + def get_sort_key(model: Dict[str, Any]) -> Any: + model_info = model.get("model_info", {}) + + if sort_by == "model_name": + return model.get("model_name", "").lower() + + elif sort_by == "created_at": + created_at = model_info.get("created_at") + normalized_dt = _normalize_datetime_for_sorting(created_at) + if normalized_dt is None: + # Put None values at the end for asc, at the start for desc + return (datetime.max.replace(tzinfo=timezone.utc) if not reverse else datetime.min.replace(tzinfo=timezone.utc)) + return normalized_dt + + elif sort_by == "updated_at": + updated_at = model_info.get("updated_at") + normalized_dt = _normalize_datetime_for_sorting(updated_at) + if normalized_dt is None: + return (datetime.max.replace(tzinfo=timezone.utc) if not reverse else datetime.min.replace(tzinfo=timezone.utc)) + return normalized_dt + + elif sort_by == "costs": + input_cost = model_info.get("input_cost_per_token", 0) or 0 + output_cost = model_info.get("output_cost_per_token", 0) or 0 + total_cost = input_cost + output_cost + # Put 0 or None costs at the end for asc, at the start for desc + if total_cost == 0: + return (float("inf") if not reverse else float("-inf")) + return total_cost + + elif sort_by == "status": + # False (config) comes before True (db) for asc + db_model = model_info.get("db_model", False) + return db_model + + return None + + try: + sorted_models = sorted(all_models, key=get_sort_key, reverse=reverse) + return sorted_models + except Exception as e: + verbose_proxy_logger.exception(f"Error sorting models by {sort_by}: {str(e)}") + return all_models + + +def _paginate_models_response( + all_models: List[Dict[str, Any]], + page: int, + size: int, + total_count: Optional[int], + search: Optional[str], +) -> Dict[str, Any]: + """ + Paginate models and return response dictionary. + + Args: + all_models: List of all models + page: Current page number + size: Page size + total_count: Total count (if None, uses len(all_models)) + search: Search term (for logging) + + Returns: + Paginated response dictionary + """ + if total_count is None: + total_count = len(all_models) + + skip = (page - 1) * size + total_pages = -(-total_count // size) if total_count > 0 else 0 + paginated_models = all_models[skip : skip + size] + + verbose_proxy_logger.debug( + f"Pagination: skip={skip}, take={size}, total_count={total_count}, total_pages={total_pages}, search={search}" + ) + + return { + "data": paginated_models, + "total_count": total_count, + "current_page": page, + "total_pages": total_pages, + "size": size, + } + + +async def _filter_models_by_team_id( + all_models: List[Dict[str, Any]], + team_id: str, + prisma_client: PrismaClient, + llm_router: Router, +) -> List[Dict[str, Any]]: + """ + Filter models by team ID. Returns models where: + - direct_access is True, OR + - team_id is in access_via_team_ids + + Also searches config and database for models accessible to the team. + + Args: + all_models: List of models to filter + team_id: Team ID to filter by + prisma_client: Prisma client for database queries + llm_router: Router instance for config queries + + Returns: + Filtered list of models + """ + # Get team from database + try: + team_db_object = await prisma_client.db.litellm_teamtable.find_unique( + where={"team_id": team_id} + ) + if team_db_object is None: + verbose_proxy_logger.warning(f"Team {team_id} not found in database") + # If team doesn't exist, return empty list + return [] + + team_object = LiteLLM_TeamTable(**team_db_object.model_dump()) + except Exception as e: + verbose_proxy_logger.exception(f"Error fetching team {team_id}: {str(e)}") + return [] + + # Get models accessible to this team (similar to _add_team_models_to_all_models) + team_accessible_model_ids: Set[str] = set() + + if ( + len(team_object.models) == 0 # empty list = all model access + or SpecialModelNames.all_proxy_models.value in team_object.models + ): + # Team has access to all models + model_list = llm_router.get_model_list() if llm_router else [] + if model_list is not None: + for model in model_list: + model_id = model.get("model_info", {}).get("id", None) + if model_id is None: + continue + # if team model id set, check if team id matches + team_model_id = model.get("model_info", {}).get("team_id", None) + can_add_model = False + if team_model_id is None: + can_add_model = True + elif team_model_id == team_id: + can_add_model = True + + if can_add_model: + team_accessible_model_ids.add(model_id) + else: + # Team has access to specific models + for model_name in team_object.models: + _models = ( + llm_router.get_model_list(model_name=model_name, team_id=team_id) + if llm_router + else [] + ) + if _models is not None: + for model in _models: + model_id = model.get("model_info", {}).get("id", None) + if model_id is not None: + team_accessible_model_ids.add(model_id) + + # Also search database for models accessible to this team + # This complements the config search done above + try: + if ( + team_object.models + and SpecialModelNames.all_proxy_models.value not in team_object.models + ): + # Team has specific models - check database for those model names + db_models = await prisma_client.db.litellm_proxymodeltable.find_many( + where={"model_name": {"in": team_object.models}} + ) + for db_model in db_models: + model_id = db_model.model_id + if model_id: + team_accessible_model_ids.add(model_id) + except Exception as e: + verbose_proxy_logger.debug( + f"Error querying database models for team {team_id}: {str(e)}" + ) + + # Filter models based on direct_access or access_via_team_ids + # Models are already enriched with these fields before this function is called + filtered_models = [] + for _model in all_models: + model_info = _model.get("model_info", {}) + model_id = model_info.get("id", None) + + # Include if direct_access is True + if model_info.get("direct_access", False): + filtered_models.append(_model) + continue + + # Include if team_id is in access_via_team_ids + access_via_team_ids = model_info.get("access_via_team_ids", []) + if isinstance(access_via_team_ids, list) and team_id in access_via_team_ids: + filtered_models.append(_model) + continue + + # Also include if model_id is in team_accessible_model_ids (from config/db search) + # This catches models that might not have been enriched with access_via_team_ids yet + if model_id and model_id in team_accessible_model_ids: + filtered_models.append(_model) + + return filtered_models + + +async def _find_model_by_id( + model_id: str, + search: Optional[str], + llm_router, + prisma_client, + proxy_config, +) -> tuple[list, Optional[int]]: + """Find a model by its ID and optionally filter by search term.""" + found_model = None + + # First, search in config + if llm_router is not None: + found_model = llm_router.get_model_info(id=model_id) + if found_model: + found_model = copy.deepcopy(found_model) + + # If not found in config, search in database + if found_model is None: + try: + db_model = await prisma_client.db.litellm_proxymodeltable.find_unique( + where={"model_id": model_id} + ) + if db_model: + # Convert database model to router format + decrypted_models = proxy_config.decrypt_model_list_from_db( + [db_model] + ) + if decrypted_models: + found_model = decrypted_models[0] + except Exception as e: + verbose_proxy_logger.exception( + f"Error querying database for modelId {model_id}: {str(e)}" + ) + + # If model found, verify search filter if provided + if found_model is not None: + if search is not None and search.strip(): + search_lower = search.lower().strip() + model_name = found_model.get("model_name", "") + if search_lower not in model_name.lower(): + # Model found but doesn't match search filter + found_model = None + + # Set all_models to the found model or empty list + all_models = [found_model] if found_model is not None else [] + search_total_count: Optional[int] = len(all_models) + return all_models, search_total_count + + @router.get( "/v2/model/info", description="v2 - returns models available to the user based on their API key permissions. Shows model info from config.yaml (except api key and api base). Filter to just user-added models with ?user_models_only=true", @@ -7815,6 +8486,24 @@ async def model_info_v2( debug: Optional[bool] = False, page: int = Query(1, description="Page number", ge=1), size: int = Query(50, description="Page size", ge=1), + search: Optional[str] = fastapi.Query( + None, description="Search model names (case-insensitive partial match)" + ), + modelId: Optional[str] = fastapi.Query( + None, description="Search for a specific model by its unique ID" + ), + teamId: Optional[str] = fastapi.Query( + None, + description="Filter models by team ID. Returns models with direct_access=True or teamId in access_via_team_ids", + ), + sortBy: Optional[str] = fastapi.Query( + None, + description="Field to sort by. Options: model_name, created_at, updated_at, costs, status", + ), + sortOrder: Optional[str] = fastapi.Query( + "asc", + description="Sort order. Options: asc, desc", + ), ): """ BETA ENDPOINT. Might change unexpectedly. Use `/v1/model/info` for now. @@ -7839,14 +8528,37 @@ async def model_info_v2( # Load existing config await proxy_config.get_config() - all_models = copy.deepcopy(llm_router.model_list) - if user_model is not None: - # if user does not use a config.yaml, https://github.com/BerriAI/litellm/issues/2061 - all_models += [user_model] + # If modelId is provided, search for the specific model + if modelId is not None: + all_models, search_total_count = await _find_model_by_id( + model_id=modelId, + search=search, + llm_router=llm_router, + prisma_client=prisma_client, + proxy_config=proxy_config, + ) + else: + # Normal flow when modelId is not provided + all_models = copy.deepcopy(llm_router.model_list) - if model is not None: - all_models = [m for m in all_models if m["model_name"] == model] + if user_model is not None: + # if user does not use a config.yaml, https://github.com/BerriAI/litellm/issues/2061 + all_models += [user_model] + + if model is not None: + all_models = [m for m in all_models if m["model_name"] == model] + + # Apply search filter if provided + all_models, search_total_count = await _apply_search_filter_to_models( + all_models=all_models, + search=search or "", + page=page, + size=size, + prisma_client=prisma_client, + proxy_config=proxy_config, + sort_by=sortBy, + ) if user_models_only: all_models = await non_admin_all_models( @@ -7863,33 +8575,55 @@ async def model_info_v2( llm_router=llm_router, all_models=all_models, ) - # fill in model info based on config.yaml and litellm model_prices_and_context_window.json + + # Fill in model info based on config.yaml and litellm model_prices_and_context_window.json + # This must happen before teamId filtering so that direct_access and access_via_team_ids are populated for i, _model in enumerate(all_models): all_models[i] = _enrich_model_info_with_litellm_data( - model=_model, debug=debug if debug is not None else False, llm_router=llm_router + model=_model, + debug=debug if debug is not None else False, + llm_router=llm_router, + ) + + # Apply teamId filter if provided + if teamId is not None and teamId.strip(): + all_models = await _filter_models_by_team_id( + all_models=all_models, + team_id=teamId.strip(), + prisma_client=prisma_client, + llm_router=llm_router, + ) + # Update search_total_count after teamId filter is applied + search_total_count = len(all_models) + + # If modelId was provided, update search_total_count after filters are applied + # to ensure pagination reflects the final filtered result (0 or 1) + if modelId is not None: + search_total_count = len(all_models) + + # Apply sorting before pagination + if sortBy: + # Validate sortOrder + if sortOrder and sortOrder.lower() not in ["asc", "desc"]: + raise HTTPException( + status_code=400, + detail=f"Invalid sortOrder: {sortOrder}. Must be 'asc' or 'desc'", + ) + all_models = _sort_models( + all_models=all_models, + sort_by=sortBy, + sort_order=sortOrder or "asc", ) verbose_proxy_logger.debug("all_models: %s", all_models) - - total_count = len(all_models) - - skip = (page - 1) * size - - total_pages = -(-total_count // size) if total_count > 0 else 0 - - paginated_models = all_models[skip : skip + size] - - verbose_proxy_logger.debug( - f"Pagination: skip={skip}, take={size}, total_count={total_count}, total_pages={total_pages}" + + return _paginate_models_response( + all_models=all_models, + page=page, + size=size, + total_count=search_total_count, + search=search, ) - - return { - "data": paginated_models, - "total_count": total_count, - "current_page": page, - "total_pages": total_pages, - "size": size, - } @router.get( @@ -9345,7 +10079,7 @@ def get_logo_url(): @app.get("/get_image", include_in_schema=False) -def get_image(): +async def get_image(): """Get logo to show on admin UI""" # get current_dir @@ -9364,25 +10098,41 @@ def get_image(): if is_non_root and not os.path.exists(default_logo): default_logo = default_site_logo + cache_dir = assets_dir if is_non_root else current_dir + cache_path = os.path.join(cache_dir, "cached_logo.jpg") + + # [OPTIMIZATION] Check if the cached image exists first + if os.path.exists(cache_path): + return FileResponse(cache_path, media_type="image/jpeg") + logo_path = os.getenv("UI_LOGO_PATH", default_logo) verbose_proxy_logger.debug("Reading logo from path: %s", logo_path) # Check if the logo path is an HTTP/HTTPS URL if logo_path.startswith(("http://", "https://")): - # Download the image and cache it - client = HTTPHandler() - response = client.get(logo_path) - if response.status_code == 200: - # Save the image to a local file - cache_dir = assets_dir if is_non_root else current_dir - cache_path = os.path.join(cache_dir, "cached_logo.jpg") - with open(cache_path, "wb") as f: - f.write(response.content) + try: + # Download the image and cache it + from litellm.llms.custom_httpx.http_handler import get_async_httpx_client + from litellm.types.llms.custom_http import httpxSpecialProvider - # Return the cached image as a FileResponse - return FileResponse(cache_path, media_type="image/jpeg") - else: - # Handle the case when the image cannot be downloaded + async_client = get_async_httpx_client( + llm_provider=httpxSpecialProvider.UI, + params={"timeout": 5.0}, + ) + response = await async_client.get(logo_path) + if response.status_code == 200: + # Save the image to a local file + with open(cache_path, "wb") as f: + f.write(response.content) + + # Return the cached image as a FileResponse + return FileResponse(cache_path, media_type="image/jpeg") + else: + # Handle the case when the image cannot be downloaded + return FileResponse(default_logo, media_type="image/jpeg") + except Exception as e: + # Handle any exceptions during the download (e.g., timeout, connection error) + verbose_proxy_logger.debug(f"Error downloading logo from {logo_path}: {e}") return FileResponse(default_logo, media_type="image/jpeg") else: # Return the local image file if the logo path is not an HTTP/HTTPS URL @@ -9962,6 +10712,8 @@ async def get_config_list( "max_request_size_mb": {"type": "Integer"}, "max_response_size_mb": {"type": "Integer"}, "pass_through_endpoints": {"type": "PydanticModel"}, + "store_prompts_in_spend_logs": {"type": "Boolean"}, + "maximum_spend_logs_retention_period": {"type": "String"}, } return_val = [] @@ -10000,9 +10752,9 @@ async def get_config_list( hasattr(sub_field_info, "description") and sub_field_info.description is not None ): - nested_fields[idx].field_description = ( - sub_field_info.description - ) + nested_fields[ + idx + ].field_description = sub_field_info.description idx += 1 _stored_in_db = None diff --git a/litellm/proxy/rag_endpoints/endpoints.py b/litellm/proxy/rag_endpoints/endpoints.py index 79b4fd6873d..39909df8e9f 100644 --- a/litellm/proxy/rag_endpoints/endpoints.py +++ b/litellm/proxy/rag_endpoints/endpoints.py @@ -26,6 +26,196 @@ from litellm.proxy.common_utils.http_parsing_utils import ( router = APIRouter() +def _build_file_metadata_entry( + response: Any, + file_data: Optional[Tuple[str, bytes, str]] = None, + file_url: Optional[str] = None, +) -> Dict[str, Any]: + """ + Build a file metadata entry for storing in vector_store_metadata. + + Args: + response: The response from litellm.aingest containing file_id + file_data: Optional tuple of (filename, content, content_type) + file_url: Optional URL if file was ingested from URL + + Returns: + Dictionary with file metadata (file_id, filename, file_url, ingested_at, etc.) + """ + from datetime import datetime, timezone + + # Extract file_id from response + file_id = None + if hasattr(response, "get"): + file_id = response.get("file_id") + elif hasattr(response, "file_id"): + file_id = response.file_id + + # Extract file information from file_data tuple + filename = None + file_size = None + content_type = None + + if file_data: + filename = file_data[0] + file_size = len(file_data[1]) if len(file_data) > 1 else None + content_type = file_data[2] if len(file_data) > 2 else None + + # Build file metadata entry + file_entry = { + "file_id": file_id, + "filename": filename, + "file_url": file_url, + "ingested_at": datetime.now(timezone.utc).isoformat(), + } + + # Add optional fields if available + if file_size is not None: + file_entry["file_size"] = file_size + if content_type is not None: + file_entry["content_type"] = content_type + + return file_entry + + +async def _save_vector_store_to_db_from_rag_ingest( + response: Any, + ingest_options: Dict[str, Any], + prisma_client, + user_api_key_dict: UserAPIKeyAuth, + file_data: Optional[Tuple[str, bytes, str]] = None, + file_url: Optional[str] = None, +) -> None: + """ + Helper function to save a newly created vector store from RAG ingest to the database. + + This function: + - Extracts vector store ID and config from the ingest response + - Checks if the vector store already exists in the database + - Creates a new database entry if it doesn't exist + - Adds the vector store to the registry + - Tracks team_id and user_id for access control + + Args: + response: The response from litellm.aingest() + ingest_options: The ingest options containing vector store config + prisma_client: The Prisma database client + user_api_key_dict: User API key authentication info + """ + from litellm.proxy.vector_store_endpoints.management_endpoints import ( + create_vector_store_in_db, + ) + + # Handle both dict and object responses + if hasattr(response, "get"): + vector_store_id = response.get("vector_store_id") + elif hasattr(response, "vector_store_id"): + vector_store_id = response.vector_store_id + else: + verbose_proxy_logger.warning( + f"Unable to extract vector_store_id from response type: {type(response)}" + ) + return + + if vector_store_id is None or not isinstance(vector_store_id, str): + verbose_proxy_logger.warning( + "Vector store ID is None or not a string, skipping database save" + ) + return + + vector_store_config = ingest_options.get("vector_store", {}) + custom_llm_provider = vector_store_config.get("custom_llm_provider") + + # Extract litellm_vector_store_params for custom name and description + litellm_vector_store_params = ingest_options.get("litellm_vector_store_params", {}) + custom_vector_store_name = litellm_vector_store_params.get("vector_store_name") + custom_vector_store_description = litellm_vector_store_params.get("vector_store_description") + + # Extract provider-specific params from vector_store_config to save as litellm_params + # This ensures params like aws_region_name, embedding_model, etc. are available for search + provider_specific_params = {} + excluded_keys = {"custom_llm_provider", "vector_store_id"} + for key, value in vector_store_config.items(): + if key not in excluded_keys and value is not None: + provider_specific_params[key] = value + + # Build file metadata entry using helper + file_entry = _build_file_metadata_entry( + response=response, + file_data=file_data, + file_url=file_url, + ) + + try: + # Check if vector store already exists in database + existing_vector_store = ( + await prisma_client.db.litellm_managedvectorstorestable.find_unique( + where={"vector_store_id": vector_store_id} + ) + ) + + # Only create if it doesn't exist + if existing_vector_store is None: + verbose_proxy_logger.info( + f"Saving newly created vector store {vector_store_id} to database" + ) + + # Initialize metadata with first file + initial_metadata = { + "ingested_files": [file_entry] + } + + # Use custom name if provided, otherwise default + vector_store_name = custom_vector_store_name or f"RAG Vector Store - {vector_store_id[:8]}" + vector_store_description = custom_vector_store_description or "Created via RAG ingest endpoint" + + await create_vector_store_in_db( + vector_store_id=vector_store_id, + custom_llm_provider=custom_llm_provider or "openai", + prisma_client=prisma_client, + vector_store_name=vector_store_name, + vector_store_description=vector_store_description, + vector_store_metadata=initial_metadata, + litellm_params=provider_specific_params if provider_specific_params else None, + team_id=user_api_key_dict.team_id, + user_id=user_api_key_dict.user_id, + ) + + verbose_proxy_logger.info( + f"Vector store {vector_store_id} saved to database successfully" + ) + else: + verbose_proxy_logger.info( + f"Vector store {vector_store_id} already exists, appending file to metadata" + ) + + # Update existing vector store with new file + existing_metadata = existing_vector_store.vector_store_metadata or {} + if isinstance(existing_metadata, str): + import json + existing_metadata = json.loads(existing_metadata) + + ingested_files = existing_metadata.get("ingested_files", []) + ingested_files.append(file_entry) + existing_metadata["ingested_files"] = ingested_files + + # Update the vector store + from litellm.proxy.utils import safe_dumps + await prisma_client.db.litellm_managedvectorstorestable.update( + where={"vector_store_id": vector_store_id}, + data={"vector_store_metadata": safe_dumps(existing_metadata)} + ) + + verbose_proxy_logger.info( + f"Added file {file_entry.get('filename') or file_entry.get('file_url', 'Unknown')} to vector store {vector_store_id} metadata" + ) + except Exception as db_error: + # Log the error but don't fail the request since ingestion succeeded + verbose_proxy_logger.exception( + f"Failed to save vector store {vector_store_id} to database: {db_error}" + ) + + async def parse_rag_ingest_request( request: Request, ) -> Tuple[Dict[str, Any], Optional[Tuple[str, bytes, str]], Optional[str], Optional[str]]: @@ -158,6 +348,7 @@ async def rag_ingest( add_litellm_data_to_request, general_settings, llm_router, + prisma_client, proxy_config, version, ) @@ -189,6 +380,25 @@ async def rag_ingest( **request_data, ) + # Save vector store to database if it was newly created and prisma_client is available + verbose_proxy_logger.debug( + f"RAG Ingest - Checking database save conditions: prisma_client={prisma_client is not None}, response={response is not None}, response_type={type(response)}" + ) + + if prisma_client is not None and response is not None: + await _save_vector_store_to_db_from_rag_ingest( + response=response, + ingest_options=ingest_options, + prisma_client=prisma_client, + user_api_key_dict=user_api_key_dict, + file_data=file_data, + file_url=file_url, + ) + else: + verbose_proxy_logger.warning( + f"Skipping database save: prisma_client={prisma_client is not None}, response={response is not None}" + ) + return response except HTTPException: diff --git a/litellm/proxy/response_api_endpoints/endpoints.py b/litellm/proxy/response_api_endpoints/endpoints.py index ec1bc5497bd..44e8c42b2c1 100644 --- a/litellm/proxy/response_api_endpoints/endpoints.py +++ b/litellm/proxy/response_api_endpoints/endpoints.py @@ -68,6 +68,7 @@ async def responses_api( _read_request_body, general_settings, llm_router, + native_background_mode, polling_cache_ttl, polling_via_cache_enabled, proxy_config, @@ -95,6 +96,7 @@ async def responses_api( redis_cache=redis_usage_cache, model=data.get("model", ""), llm_router=llm_router, + native_background_mode=native_background_mode, ) # If polling is enabled, use polling mode diff --git a/litellm/proxy/response_polling/polling_handler.py b/litellm/proxy/response_polling/polling_handler.py index c47578c8d7b..f0b850049bf 100644 --- a/litellm/proxy/response_polling/polling_handler.py +++ b/litellm/proxy/response_polling/polling_handler.py @@ -3,7 +3,7 @@ Response Polling Handler for Background Responses with Cache """ import json from datetime import datetime, timezone -from typing import Any, Dict, Optional +from typing import Any, Dict, List, Optional from litellm._logging import verbose_proxy_logger from litellm._uuid import uuid4 @@ -257,6 +257,7 @@ def should_use_polling_for_request( redis_cache, # RedisCache or None model: str, llm_router, # Router instance or None + native_background_mode: Optional[List[str]] = None, # List of models that should use native background mode ) -> bool: """ Determine if polling via cache should be used for a request. @@ -267,6 +268,8 @@ def should_use_polling_for_request( redis_cache: Redis cache instance (required for polling) model: Model name from the request (e.g., "gpt-5" or "openai/gpt-4o") llm_router: LiteLLM router instance for looking up model deployments + native_background_mode: List of model names that should use native provider + background mode instead of polling via cache Returns: True if polling should be used, False otherwise @@ -275,6 +278,13 @@ def should_use_polling_for_request( if not (background_mode and polling_via_cache_enabled and redis_cache): return False + # Check if model is in native_background_mode list - these use native provider background mode + if native_background_mode and model in native_background_mode: + verbose_proxy_logger.debug( + f"Model {model} is in native_background_mode list, skipping polling via cache" + ) + return False + # "all" enables polling for all providers if polling_via_cache_enabled == "all": return True diff --git a/litellm/proxy/route_llm_request.py b/litellm/proxy/route_llm_request.py index 0e061a839d9..c6a93164d49 100644 --- a/litellm/proxy/route_llm_request.py +++ b/litellm/proxy/route_llm_request.py @@ -160,6 +160,8 @@ async def route_request( "aget_interaction", "adelete_interaction", "acancel_interaction", + "acancel_batch", + "afile_delete", ], ): """ @@ -197,7 +199,13 @@ async def route_request( elif "user_config" in data: router_config = data.pop("user_config") - user_router = litellm.Router(**router_config) + + # Filter router_config to only include valid Router.__init__ arguments + # This prevents TypeError when invalid parameters are stored in the database + valid_args = litellm.Router.get_valid_args() + filtered_config = {k: v for k, v in router_config.items() if k in valid_args} + + user_router = litellm.Router(**filtered_config) ret_val = getattr(user_router, f"{route_type}")(**data) user_router.discard() return ret_val @@ -215,8 +223,9 @@ async def route_request( "aretrieve_container_file_content", ]: return getattr(llm_router, f"{route_type}")(**data) - # Interactions API: get/delete/cancel don't need model routing + # Interactions API: create with agent, get/delete/cancel don't need model routing if route_type in [ + "acreate_interaction", "aget_interaction", "adelete_interaction", "acancel_interaction", diff --git a/litellm/proxy/schema.prisma b/litellm/proxy/schema.prisma index d7aa6e9f0d0..b118400b620 100644 --- a/litellm/proxy/schema.prisma +++ b/litellm/proxy/schema.prisma @@ -5,6 +5,7 @@ datasource client { generator client { provider = "prisma-client-py" + binaryTargets = ["native", "debian-openssl-1.1.x", "debian-openssl-3.0.x", "linux-musl", "linux-musl-openssl-3.0.x"] } // Budget / Rate Limits for an org @@ -760,6 +761,11 @@ model LiteLLM_ManagedVectorStoresTable { updated_at DateTime @updatedAt litellm_credential_name String? litellm_params Json? + team_id String? + user_id String? + + @@index([team_id]) + @@index([user_id]) } // Guardrails table for storing guardrail configurations diff --git a/litellm/proxy/search_endpoints/endpoints.py b/litellm/proxy/search_endpoints/endpoints.py index 8cfc8bb5106..c7a3b88c490 100644 --- a/litellm/proxy/search_endpoints/endpoints.py +++ b/litellm/proxy/search_endpoints/endpoints.py @@ -1,9 +1,10 @@ #### Search Endpoints ##### import orjson -from fastapi import APIRouter, Depends, Request, Response +from fastapi import APIRouter, Depends, HTTPException, Request, Response from fastapi.responses import ORJSONResponse +from litellm._logging import verbose_proxy_logger from litellm.proxy._types import * from litellm.proxy.auth.user_api_key_auth import UserAPIKeyAuth, user_api_key_auth from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing @@ -136,6 +137,13 @@ async def search( if llm_router is not None and hasattr(llm_router, "search_tools"): search_tool_name_value = data["search_tool_name"] + + verbose_proxy_logger.debug( + f"Search endpoint - Looking for search_tool_name: {search_tool_name_value}. " + f"Available search tools in router: {[tool.get('search_tool_name') for tool in llm_router.search_tools]}. " + f"Total search tools: {len(llm_router.search_tools)}" + ) + matching_tools = [ tool for tool in llm_router.search_tools if tool.get("search_tool_name") == search_tool_name_value @@ -181,3 +189,75 @@ async def search( version=version, ) +@router.get( + "/v1/search/tools", + dependencies=[Depends(user_api_key_auth)], + response_class=ORJSONResponse, + tags=["search"], +) +@router.get( + "/search/tools", + dependencies=[Depends(user_api_key_auth)], + response_class=ORJSONResponse, + tags=["search"], +) +async def list_search_tools( + request: Request, + fastapi_response: Response, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """ + List all available search tools configured in the router. + + This endpoint returns the search tools that are currently loaded and available + for use with the /v1/search endpoint. + + Example: + ```bash + curl -X GET "http://localhost:4000/v1/search/tools" \ + -H "Authorization: Bearer sk-1234" + ``` + + Response: + ```json + { + "object": "list", + "data": [ + { + "search_tool_name": "litellm-search", + "search_provider": "perplexity", + "description": "Perplexity search tool" + } + ] + } + ``` + """ + from litellm.proxy.proxy_server import llm_router + + try: + search_tools_list = [] + + if llm_router is not None and hasattr(llm_router, "search_tools"): + for tool in llm_router.search_tools: + tool_info = { + "search_tool_name": tool.get("search_tool_name"), + "search_provider": tool.get("litellm_params", {}).get("search_provider"), + } + + # Add description if available + if "search_tool_info" in tool and tool["search_tool_info"]: + description = tool["search_tool_info"].get("description") + if description: + tool_info["description"] = description + + search_tools_list.append(tool_info) + + return { + "object": "list", + "data": search_tools_list + } + except Exception as e: + from litellm._logging import verbose_proxy_logger + verbose_proxy_logger.exception(f"Error listing search tools: {e}") + raise HTTPException(status_code=500, detail=str(e)) + diff --git a/litellm/proxy/spend_tracking/spend_management_endpoints.py b/litellm/proxy/spend_tracking/spend_management_endpoints.py index dcdc17ef318..6e49e4244e7 100644 --- a/litellm/proxy/spend_tracking/spend_management_endpoints.py +++ b/litellm/proxy/spend_tracking/spend_management_endpoints.py @@ -1681,6 +1681,9 @@ async def ui_view_spend_logs( # noqa: PLR0915 error_code: Optional[str] = fastapi.Query( default=None, description="Filter logs by error code (e.g., '404', '500')" ), + error_message: Optional[str] = fastapi.Query( + default=None, description="Filter logs by error message (partial string match)" + ), ): """ View spend logs with pagination support. @@ -1774,6 +1777,12 @@ async def ui_view_spend_logs( # noqa: PLR0915 "equals": f'"{error_code}"', }) + if error_message is not None: + metadata_filters.append({ + "path": ["error_information", "error_message"], + "string_contains": error_message, + }) + if metadata_filters: if len(metadata_filters) == 1: where_conditions["metadata"] = metadata_filters[0] diff --git a/litellm/proxy/spend_tracking/spend_tracking_utils.py b/litellm/proxy/spend_tracking/spend_tracking_utils.py index db4e4beec21..bd148ecb481 100644 --- a/litellm/proxy/spend_tracking/spend_tracking_utils.py +++ b/litellm/proxy/spend_tracking/spend_tracking_utils.py @@ -759,10 +759,19 @@ def _should_store_prompts_and_responses_in_spend_logs() -> bool: from litellm.proxy.proxy_server import general_settings from litellm.secret_managers.main import get_secret_bool - return ( - general_settings.get("store_prompts_in_spend_logs") is True - or get_secret_bool("STORE_PROMPTS_IN_SPEND_LOGS") is True - ) + # Check general_settings (from DB or proxy_config.yaml) + store_prompts_value = general_settings.get("store_prompts_in_spend_logs") + + # Normalize case: handle True/true/TRUE, False/false/FALSE, None/null + if store_prompts_value is True: + return True + elif isinstance(store_prompts_value, str): + # Case-insensitive string comparison + if store_prompts_value.lower() == "true": + return True + + # Also check environment variable + return get_secret_bool("STORE_PROMPTS_IN_SPEND_LOGS") is True def _get_status_for_spend_log( diff --git a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py index 09c14bc42c1..30ec0766dbf 100644 --- a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py +++ b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py @@ -78,6 +78,11 @@ class UISettings(BaseModel): description="Prevents Team Admins from deleting users from the teams they manage. Useful for SCIM provisioning where team membership is defined externally.", ) + enabled_ui_pages_internal_users: Optional[List[str]] = Field( + default=None, + description="List of page keys that internal users (non-admins) can see in the UI sidebar. If not set, all pages are visible based on role permissions.", + ) + class UISettingsResponse(SettingsResponse): """Response model for UI settings""" @@ -89,6 +94,7 @@ class UISettingsResponse(SettingsResponse): ALLOWED_UI_SETTINGS_FIELDS = { "disable_model_add_for_internal_users", "disable_team_admin_delete_team_user", + "enabled_ui_pages_internal_users", } @@ -443,7 +449,6 @@ async def get_sso_settings(): # Load settings from database sso_settings_dict = dict(sso_db_record.sso_settings) - # Extract role_mappings before removing it (it's a dict, not an env variable) role_mappings_data = sso_settings_dict.pop("role_mappings", None) role_mappings = None if role_mappings_data: @@ -454,6 +459,16 @@ async def get_sso_settings(): elif isinstance(role_mappings_data, RoleMappings): role_mappings = role_mappings_data + team_mappings_data = sso_settings_dict.pop("team_mappings", None) + team_mappings = None + if team_mappings_data: + from litellm.types.proxy.management_endpoints.ui_sso import TeamMappings + + if isinstance(team_mappings_data, dict): + team_mappings = TeamMappings(**team_mappings_data) + elif isinstance(team_mappings_data, TeamMappings): + team_mappings = team_mappings_data + decrypted_sso_settings_dict = proxy_config._decrypt_and_set_db_env_variables( environment_variables=sso_settings_dict ) @@ -489,6 +504,7 @@ async def get_sso_settings(): user_email=decrypted_sso_settings_dict.get("user_email"), ui_access_mode=decrypted_sso_settings_dict.get("ui_access_mode"), role_mappings=role_mappings, + team_mappings=team_mappings, ) # Get the schema for UI display diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 5dcb0339739..6bbf0df74de 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -982,7 +982,9 @@ class ProxyLogging: try: # Check if load balancing should be used - if guardrail_name and self._should_use_guardrail_load_balancing(guardrail_name): + if guardrail_name and self._should_use_guardrail_load_balancing( + guardrail_name + ): response = await self._execute_guardrail_with_load_balancing( guardrail_name=guardrail_name, hook_type="pre_call", @@ -1017,7 +1019,11 @@ class ProxyLogging: latency_seconds = guardrail_end_time - guardrail_start_time # Get guardrail name for metrics (fallback if not set) - metrics_guardrail_name = guardrail_name or getattr(callback, "guardrail_name", callback.__class__.__name__) or "unknown" + metrics_guardrail_name = ( + guardrail_name + or getattr(callback, "guardrail_name", callback.__class__.__name__) + or "unknown" + ) # Find PrometheusLogger in callbacks and record metrics for prom_callback in litellm.callbacks: @@ -1793,13 +1799,55 @@ class ProxyLogging: ################################################################# for callback in other_callbacks: - await callback.async_post_call_success_hook( + callback_response = await callback.async_post_call_success_hook( user_api_key_dict=user_api_key_dict, data=data, response=response ) + if callback_response is not None: + response = callback_response except Exception as e: raise e return response + async def post_call_response_headers_hook( + self, + data: dict, + user_api_key_dict: UserAPIKeyAuth, + response: Any, + request_headers: Optional[Dict[str, str]] = None, + ) -> Dict[str, str]: + """ + Calls async_post_call_response_headers_hook on all CustomLogger callbacks. + Merges all returned header dicts (later callbacks override earlier ones). + + Returns: + Dict[str, str]: Merged headers from all callbacks. + """ + merged_headers: Dict[str, str] = {} + try: + for callback in litellm.callbacks: + _callback: Optional[CustomLogger] = None + if isinstance(callback, str): + _callback = litellm.litellm_core_utils.litellm_logging.get_custom_logger_compatible_class( + cast(_custom_logger_compatible_callbacks_literal, callback) + ) + else: + _callback = callback # type: ignore + + if _callback is not None and isinstance(_callback, CustomLogger): + result = await _callback.async_post_call_response_headers_hook( + data=data, + user_api_key_dict=user_api_key_dict, + response=response, + request_headers=request_headers, + ) + if result is not None: + merged_headers.update(result) + except Exception as e: + verbose_proxy_logger.exception( + "Error in post_call_response_headers_hook: %s", str(e) + ) + return merged_headers + async def async_post_call_streaming_hook( self, data: dict, @@ -1852,16 +1900,14 @@ class ProxyLogging: complete_response = str_so_far + response_str else: complete_response = response_str - potential_error_response = ( + callback_response = ( await _callback.async_post_call_streaming_hook( user_api_key_dict=user_api_key_dict, response=complete_response, ) ) - if isinstance( - potential_error_response, str - ) and potential_error_response.startswith("data: "): - return potential_error_response + if callback_response is not None: + response = callback_response except Exception as e: raise e return response @@ -1895,7 +1941,18 @@ class ProxyLogging: ) or _callback.should_run_guardrail( data=request_data, event_type=GuardrailEventHooks.post_call ): - if "apply_guardrail" in type(callback).__dict__: + if ( + "async_post_call_streaming_iterator_hook" + in type(callback).__dict__ + ): + current_response = ( + _callback.async_post_call_streaming_iterator_hook( + user_api_key_dict=user_api_key_dict, + response=current_response, + request_data=request_data, + ) + ) + elif "apply_guardrail" in type(callback).__dict__: request_data["guardrail_to_apply"] = callback current_response = ( unified_guardrail.async_post_call_streaming_iterator_hook( @@ -2218,17 +2275,17 @@ class PrismaClient: ) -> Optional[dict]: """ Execute a query with automatic fallback for PostgreSQL cached plan errors. - + This handles the "cached plan must not change result type" error that occurs during rolling deployments when schema changes are applied while old pods still have cached query plans expecting the old schema. - + Args: sql_query: SQL query string to execute - + Returns: Query result or None - + Raises: Original exception if not a cached plan error """ @@ -2241,7 +2298,7 @@ class PrismaClient: # Add a unique comment to make the query different sql_query_retry = sql_query.replace( "SELECT", - f"SELECT /* cache_invalidated_{int(time.time() * 1000)} */" + f"SELECT /* cache_invalidated_{int(time.time() * 1000)} */", ) verbose_proxy_logger.warning( "PostgreSQL cached plan error detected for token lookup, " @@ -2583,7 +2640,9 @@ class PrismaClient: WHERE v.token = '{token}' """ - response = await self._query_first_with_cached_plan_fallback(sql_query) + response = await self._query_first_with_cached_plan_fallback( + sql_query + ) if response is not None: if response["team_models"] is None: @@ -4227,7 +4286,7 @@ def get_server_root_path() -> str: - If SERVER_ROOT_PATH is set, return it. - Otherwise, default to "/". """ - return os.getenv("SERVER_ROOT_PATH", "/") + return os.getenv("SERVER_ROOT_PATH", "") def get_prisma_client_or_throw(message: str): diff --git a/litellm/proxy/vector_store_endpoints/endpoints.py b/litellm/proxy/vector_store_endpoints/endpoints.py index 6e22d66c35d..9ba12537bc8 100644 --- a/litellm/proxy/vector_store_endpoints/endpoints.py +++ b/litellm/proxy/vector_store_endpoints/endpoints.py @@ -18,13 +18,54 @@ router = APIRouter() ######################################################## +def _check_vector_store_access( + vector_store: LiteLLM_ManagedVectorStore, + user_api_key_dict: UserAPIKeyAuth, +) -> bool: + """ + Check if the user has access to the vector store based on team membership. + + Args: + vector_store: The vector store to check access for + user_api_key_dict: User API key authentication info + + Returns: + True if user has access, False otherwise + + Access rules: + - If vector store has no team_id, it's accessible to all (legacy behavior) + - If user's team_id matches the vector store's team_id, access is granted + - Otherwise, access is denied + """ + vector_store_team_id = vector_store.get("team_id") + + # If vector store has no team_id, it's accessible to all (legacy behavior) + if vector_store_team_id is None: + return True + + # Check if user's team matches the vector store's team + user_team_id = user_api_key_dict.team_id + if user_team_id == vector_store_team_id: + return True + + return False + + def _update_request_data_with_litellm_managed_vector_store_registry( data: Dict, vector_store_id: str, + user_api_key_dict: Optional[UserAPIKeyAuth] = None, ) -> Dict: """ Update the request data with the litellm managed vector store registry. - + + Args: + data: Request data to update + vector_store_id: ID of the vector store + user_api_key_dict: User API key authentication info for access control + + Raises: + HTTPException: If user doesn't have access to the vector store """ if litellm.vector_store_registry is not None: vector_store_to_run: Optional[LiteLLM_ManagedVectorStore] = ( @@ -33,6 +74,14 @@ def _update_request_data_with_litellm_managed_vector_store_registry( ) ) if vector_store_to_run is not None: + # Check access control if user_api_key_dict is provided + if user_api_key_dict is not None: + if not _check_vector_store_access(vector_store_to_run, user_api_key_dict): + raise HTTPException( + status_code=403, + detail="Access denied: You do not have permission to access this vector store", + ) + if "custom_llm_provider" in vector_store_to_run: data["custom_llm_provider"] = vector_store_to_run.get( "custom_llm_provider" @@ -88,7 +137,7 @@ async def vector_store_search( data["vector_store_id"] = vector_store_id data = _update_request_data_with_litellm_managed_vector_store_registry( - data=data, vector_store_id=vector_store_id + data=data, vector_store_id=vector_store_id, user_api_key_dict=user_api_key_dict ) processor = ProxyBaseLLMRequestProcessing(data=data) diff --git a/litellm/proxy/vector_store_endpoints/management_endpoints.py b/litellm/proxy/vector_store_endpoints/management_endpoints.py index bc61a60fe5a..cccbb51f47b 100644 --- a/litellm/proxy/vector_store_endpoints/management_endpoints.py +++ b/litellm/proxy/vector_store_endpoints/management_endpoints.py @@ -37,6 +37,88 @@ from litellm.vector_stores.vector_store_registry import VectorStoreRegistry router = APIRouter() +def _resolve_embedding_config_from_router( + embedding_model: str, llm_router +) -> Optional[Dict[str, Any]]: + """ + Resolve embedding config from router's config-defined models. + + Config-defined models (from proxy_config.yaml) are stored in the router's model_list, + not in the database. This function looks up the model in the router and extracts + api_key, api_base, and api_version from the deployment's litellm_params. + + Args: + embedding_model: The embedding model string (e.g., "text-embedding-ada-002" or "azure/text-embedding-3-large") + llm_router: The LiteLLM router instance + + Returns: + Dictionary with api_key, api_base, and api_version if model found, None otherwise + """ + if not embedding_model or llm_router is None: + return None + + # Extract model name candidates - could be "text-embedding-ada-002" or "azure/text-embedding-3-large" + # Try exact match first, then try without provider prefix + model_name_candidates = [embedding_model] + if "/" in embedding_model: + # If it has a provider prefix, also try without it + _, model_name = embedding_model.split("/", 1) + model_name_candidates.append(model_name) + + # Try to find model in router + for model_name in model_name_candidates: + try: + # Try to get deployment by model group name (model_name in config) + deployment = llm_router.get_deployment_by_model_group_name( + model_group_name=model_name + ) + + if deployment is not None and deployment.litellm_params is not None: + litellm_params = deployment.litellm_params + + # Build embedding config from model params + embedding_config: Dict[str, Any] = {} + + # Extract api_key + api_key = getattr(litellm_params, "api_key", None) + if api_key: + # Handle os.environ/ prefix + if isinstance(api_key, str) and api_key.startswith("os.environ/"): + api_key = get_secret(api_key) + embedding_config["api_key"] = api_key + + # Extract api_base + api_base = getattr(litellm_params, "api_base", None) + if api_base: + # Handle os.environ/ prefix + if isinstance(api_base, str) and api_base.startswith("os.environ/"): + api_base = get_secret(api_base) + embedding_config["api_base"] = api_base + + # Extract api_version + api_version = getattr(litellm_params, "api_version", None) + if api_version: + embedding_config["api_version"] = api_version + + project_id = getattr(litellm_params, "project_id", None) + if project_id: + embedding_config["project_id"] = project_id + + # Only return config if we have at least api_key or api_base + if embedding_config: + verbose_proxy_logger.debug( + f"Resolved embedding config from router model {model_name}: {list(embedding_config.keys())}" + ) + return embedding_config + except Exception as e: + verbose_proxy_logger.debug( + f"Error resolving embedding config from router for model {model_name}: {str(e)}" + ) + continue + + return None + + async def _resolve_embedding_config_from_db( embedding_model: str, prisma_client ) -> Optional[Dict[str, Any]]: @@ -133,6 +215,208 @@ async def _resolve_embedding_config_from_db( return None +async def _resolve_embedding_config( + embedding_model: str, prisma_client, llm_router=None +) -> Optional[Dict[str, Any]]: + """ + Resolve embedding config from either router (config-defined) or database models. + + This function first checks the router for config-defined models, then falls back + to the database. This allows users to use models defined in either location. + + Args: + embedding_model: The embedding model string (e.g., "text-embedding-ada-002" or "azure/text-embedding-3-large") + prisma_client: The Prisma client instance + llm_router: The LiteLLM router instance (optional, will be imported if not provided) + + Returns: + Dictionary with api_key, api_base, and api_version if model found, None otherwise + """ + if not embedding_model: + return None + + # Import llm_router if not provided + if llm_router is None: + try: + from litellm.proxy.proxy_server import llm_router + except ImportError: + llm_router = None + + # First try to resolve from router (config-defined models) + if llm_router is not None: + router_config = _resolve_embedding_config_from_router( + embedding_model=embedding_model, + llm_router=llm_router + ) + if router_config: + verbose_proxy_logger.debug( + f"Resolved embedding config from router for model {embedding_model}" + ) + return router_config + + # Fall back to database + if prisma_client is not None: + db_config = await _resolve_embedding_config_from_db( + embedding_model=embedding_model, + prisma_client=prisma_client + ) + if db_config: + verbose_proxy_logger.debug( + f"Resolved embedding config from database for model {embedding_model}" + ) + return db_config + + verbose_proxy_logger.debug( + f"Could not resolve embedding config for model {embedding_model} from router or database" + ) + return None + + +######################################################## +# Helper Functions +######################################################## +def _check_vector_store_access( + vector_store: LiteLLM_ManagedVectorStore, + user_api_key_dict: UserAPIKeyAuth, +) -> bool: + """ + Check if the user has access to the vector store based on team membership. + + Args: + vector_store: The vector store to check access for + user_api_key_dict: User API key authentication info + + Returns: + True if user has access, False otherwise + + Access rules: + - If vector store has no team_id, it's accessible to all (legacy behavior) + - If user's team_id matches the vector store's team_id, access is granted + - Otherwise, access is denied + """ + vector_store_team_id = vector_store.get("team_id") + + # If vector store has no team_id, it's accessible to all (legacy behavior) + if vector_store_team_id is None: + return True + + # Check if user's team matches the vector store's team + user_team_id = user_api_key_dict.team_id + if user_team_id == vector_store_team_id: + return True + + return False + + +async def create_vector_store_in_db( + vector_store_id: str, + custom_llm_provider: str, + prisma_client, + vector_store_name: Optional[str] = None, + vector_store_description: Optional[str] = None, + vector_store_metadata: Optional[Dict] = None, + litellm_params: Optional[Dict] = None, + litellm_credential_name: Optional[str] = None, + team_id: Optional[str] = None, + user_id: Optional[str] = None, +) -> LiteLLM_ManagedVectorStore: + """ + Helper function to create a vector store in the database. + + This function handles: + - Checking if vector store already exists + - Creating the vector store in the database + - Adding it to the vector store registry + + Returns: + LiteLLM_ManagedVectorStore: The created vector store object + + Raises: + HTTPException: If vector store already exists or database error occurs + """ + from litellm.types.router import GenericLiteLLMParams + + if prisma_client is None: + raise HTTPException(status_code=500, detail="Database not connected") + + # Check if vector store already exists + existing_vector_store = ( + await prisma_client.db.litellm_managedvectorstorestable.find_unique( + where={"vector_store_id": vector_store_id} + ) + ) + if existing_vector_store is not None: + raise HTTPException( + status_code=400, + detail=f"Vector store with ID {vector_store_id} already exists", + ) + + # Prepare data for database + data_to_create: Dict[str, Any] = { + "vector_store_id": vector_store_id, + "custom_llm_provider": custom_llm_provider, + } + + if vector_store_name is not None: + data_to_create["vector_store_name"] = vector_store_name + if vector_store_description is not None: + data_to_create["vector_store_description"] = vector_store_description + if vector_store_metadata is not None: + data_to_create["vector_store_metadata"] = safe_dumps(vector_store_metadata) + if litellm_credential_name is not None: + data_to_create["litellm_credential_name"] = litellm_credential_name + if team_id is not None: + data_to_create["team_id"] = team_id + if user_id is not None: + data_to_create["user_id"] = user_id + + # Handle litellm_params - always provide at least an empty dict + if litellm_params: + # Auto-resolve embedding config if embedding model is provided but config is not + embedding_model = litellm_params.get("litellm_embedding_model") + if embedding_model and not litellm_params.get("litellm_embedding_config"): + resolved_config = await _resolve_embedding_config( + embedding_model=embedding_model, + prisma_client=prisma_client + ) + if resolved_config: + litellm_params["litellm_embedding_config"] = resolved_config + verbose_proxy_logger.info( + f"Auto-resolved embedding config for model {embedding_model}" + ) + + litellm_params_dict = GenericLiteLLMParams( + **litellm_params + ).model_dump(exclude_none=True) + data_to_create["litellm_params"] = safe_dumps(litellm_params_dict) + else: + # Provide empty dict if no litellm_params provided + data_to_create["litellm_params"] = safe_dumps({}) + + # Create in database + _new_vector_store = ( + await prisma_client.db.litellm_managedvectorstorestable.create( + data=data_to_create + ) + ) + + new_vector_store: LiteLLM_ManagedVectorStore = LiteLLM_ManagedVectorStore( + **_new_vector_store.model_dump() + ) + + # Add vector store to registry + if litellm.vector_store_registry is not None: + litellm.vector_store_registry.add_vector_store_to_registry( + vector_store=new_vector_store + ) + + verbose_proxy_logger.info( + f"Vector store {vector_store_id} created in database successfully" + ) + + return new_vector_store + + ######################################################## # Management Endpoints ######################################################## @@ -156,71 +440,36 @@ async def new_vector_store( - vector_store_metadata: Optional[Dict] - Additional metadata for the vector store """ from litellm.proxy.proxy_server import prisma_client - from litellm.types.router import GenericLiteLLMParams - - if prisma_client is None: - raise HTTPException(status_code=500, detail="Database not connected") try: - # Check if vector store already exists - existing_vector_store = ( - await prisma_client.db.litellm_managedvectorstorestable.find_unique( - where={"vector_store_id": vector_store.get("vector_store_id")} - ) - ) - if existing_vector_store is not None: + vector_store_id = vector_store.get("vector_store_id") + custom_llm_provider = vector_store.get("custom_llm_provider") + + if not vector_store_id or not custom_llm_provider: raise HTTPException( status_code=400, - detail=f"Vector store with ID {vector_store.get('vector_store_id')} already exists", - ) - - if vector_store.get("vector_store_metadata") is not None: - vector_store["vector_store_metadata"] = safe_dumps( - vector_store.get("vector_store_metadata") - ) - - # Safely handle JSON serialization of litellm_params - litellm_params_json: Optional[str] = None - _input_litellm_params: dict = vector_store.get("litellm_params", {}) or {} - if _input_litellm_params is not None: - # Auto-resolve embedding config if embedding model is provided but config is not - embedding_model = _input_litellm_params.get("litellm_embedding_model") - if embedding_model and not _input_litellm_params.get("litellm_embedding_config"): - resolved_config = await _resolve_embedding_config_from_db( - embedding_model=embedding_model, - prisma_client=prisma_client - ) - if resolved_config: - _input_litellm_params["litellm_embedding_config"] = resolved_config - verbose_proxy_logger.info( - f"Auto-resolved embedding config for model {embedding_model}" - ) - - litellm_params_dict = GenericLiteLLMParams( - **_input_litellm_params - ).model_dump(exclude_none=True) - litellm_params_json = safe_dumps(litellm_params_dict) - del vector_store["litellm_params"] - - _new_vector_store = ( - await prisma_client.db.litellm_managedvectorstorestable.create( - data={ - **vector_store, - "litellm_params": litellm_params_json, - } + detail="vector_store_id and custom_llm_provider are required" ) + + # Extract and validate metadata + metadata = vector_store.get("vector_store_metadata") + validated_metadata: Optional[Dict] = None + if metadata is not None and isinstance(metadata, dict): + validated_metadata = metadata + + new_vector_store = await create_vector_store_in_db( + vector_store_id=vector_store_id, + custom_llm_provider=custom_llm_provider, + prisma_client=prisma_client, + vector_store_name=vector_store.get("vector_store_name"), + vector_store_description=vector_store.get("vector_store_description"), + vector_store_metadata=validated_metadata, + litellm_params=vector_store.get("litellm_params"), + litellm_credential_name=vector_store.get("litellm_credential_name"), + team_id=user_api_key_dict.team_id, + user_id=user_api_key_dict.user_id, ) - new_vector_store: LiteLLM_ManagedVectorStore = LiteLLM_ManagedVectorStore( - **_new_vector_store.model_dump() - ) - - # Add vector store to registry - if litellm.vector_store_registry is not None: - litellm.vector_store_registry.add_vector_store_to_registry( - vector_store=new_vector_store - ) - return { "status": "success", "message": f"Vector store {vector_store.get('vector_store_id')} created successfully", @@ -237,6 +486,12 @@ async def new_vector_store( dependencies=[Depends(user_api_key_auth)], response_model=LiteLLM_ManagedVectorStoreListResponse, ) +@router.get( + "/v1/vector_store/list", + tags=["vector store management"], + dependencies=[Depends(user_api_key_auth)], + response_model=LiteLLM_ManagedVectorStoreListResponse, +) async def list_vector_stores( user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), page: int = 1, @@ -311,14 +566,19 @@ async def list_vector_stores( updated_data=vector_store ) - combined_vector_stores = list(vector_store_map.values()) - total_count = len(combined_vector_stores) + # Filter vector stores based on team access + accessible_vector_stores = [ + vs for vs in vector_store_map.values() + if _check_vector_store_access(vs, user_api_key_dict) + ] + + total_count = len(accessible_vector_stores) total_pages = (total_count + page_size - 1) // page_size # Format response using LiteLLM_ManagedVectorStoreListResponse response = LiteLLM_ManagedVectorStoreListResponse( object="list", - data=combined_vector_stores, + data=accessible_vector_stores, total_count=total_count, current_page=page, total_pages=total_pages, @@ -354,6 +614,7 @@ async def delete_vector_store( # Check if vector store exists in database or in-memory registry db_vector_store_exists = False memory_vector_store_exists = False + vector_store_to_check = None existing_vector_store = ( await prisma_client.db.litellm_managedvectorstorestable.find_unique( @@ -362,6 +623,9 @@ async def delete_vector_store( ) if existing_vector_store is not None: db_vector_store_exists = True + vector_store_to_check = LiteLLM_ManagedVectorStore( + **existing_vector_store.model_dump() + ) # Check in-memory registry if litellm.vector_store_registry is not None: @@ -370,6 +634,8 @@ async def delete_vector_store( ) if memory_vector_store is not None: memory_vector_store_exists = True + if vector_store_to_check is None: + vector_store_to_check = memory_vector_store # If not found in either location, raise 404 if not db_vector_store_exists and not memory_vector_store_exists: @@ -377,6 +643,15 @@ async def delete_vector_store( status_code=404, detail=f"Vector store with ID {data.vector_store_id} not found", ) + + # Check access control + if vector_store_to_check and not _check_vector_store_access( + vector_store_to_check, user_api_key_dict + ): + raise HTTPException( + status_code=403, + detail="Access denied: You do not have permission to delete this vector store", + ) # Delete from database if exists if db_vector_store_exists: @@ -423,6 +698,13 @@ async def get_vector_store_info( vector_store_id=data.vector_store_id ) if vector_store is not None: + # Check access control + if not _check_vector_store_access(vector_store, user_api_key_dict): + raise HTTPException( + status_code=403, + detail="Access denied: You do not have permission to access this vector store", + ) + vector_store_metadata = vector_store.get("vector_store_metadata") # Parse metadata if it's a JSON string parsed_metadata: Optional[dict] = None @@ -444,6 +726,8 @@ async def get_vector_store_info( updated_at=vector_store.get("updated_at") or None, litellm_credential_name=vector_store.get("litellm_credential_name"), litellm_params=vector_store.get("litellm_params") or None, + team_id=vector_store.get("team_id") or None, + user_id=vector_store.get("user_id") or None, ) return {"vector_store": vector_store_pydantic_obj} @@ -457,8 +741,16 @@ async def get_vector_store_info( status_code=404, detail=f"Vector store with ID {data.vector_store_id} not found", ) - + + # Check access control for DB vector store vector_store_dict = vector_store.model_dump() # type: ignore[attr-defined] + vector_store_typed = LiteLLM_ManagedVectorStore(**vector_store_dict) + if not _check_vector_store_access(vector_store_typed, user_api_key_dict): + raise HTTPException( + status_code=403, + detail="Access denied: You do not have permission to access this vector store", + ) + return {"vector_store": vector_store_dict} except Exception as e: verbose_proxy_logger.exception(f"Error getting vector store info: {str(e)}") @@ -501,7 +793,7 @@ async def update_vector_store( # Auto-resolve embedding config if embedding model is provided but config is not embedding_model = _input_litellm_params.get("litellm_embedding_model") if embedding_model and not _input_litellm_params.get("litellm_embedding_config"): - resolved_config = await _resolve_embedding_config_from_db( + resolved_config = await _resolve_embedding_config( embedding_model=embedding_model, prisma_client=prisma_client ) diff --git a/litellm/rag/ingestion/base_ingestion.py b/litellm/rag/ingestion/base_ingestion.py index 20059487b43..3daa767188d 100644 --- a/litellm/rag/ingestion/base_ingestion.py +++ b/litellm/rag/ingestion/base_ingestion.py @@ -24,6 +24,7 @@ from litellm.llms.custom_httpx.http_handler import ( get_async_httpx_client, httpxSpecialProvider, ) +from litellm.rag.ingestion.file_parsers import extract_text_from_pdf from litellm.rag.text_splitters import RecursiveCharacterTextSplitter from litellm.types.rag import RAGIngestOptions, RAGIngestResponse @@ -193,11 +194,23 @@ class BaseRAGIngestion(ABC): if text: text_to_chunk = text elif file_content and not ocr_was_used: + # Try UTF-8 decode first try: text_to_chunk = file_content.decode("utf-8") except UnicodeDecodeError: - verbose_logger.debug("Binary file detected, skipping text chunking") - return [] + # Check if it's a PDF and try to extract text + if file_content.startswith(b"%PDF"): + verbose_logger.debug("PDF detected, attempting text extraction") + text_to_chunk = extract_text_from_pdf(file_content) + if not text_to_chunk: + verbose_logger.debug( + "PDF text extraction failed. Install 'pypdf' or 'PyPDF2' for PDF support, " + "or enable OCR with a vision model." + ) + return [] + else: + verbose_logger.debug("Binary file detected, skipping text chunking") + return [] if not text_to_chunk: return [] diff --git a/litellm/rag/ingestion/file_parsers/__init__.py b/litellm/rag/ingestion/file_parsers/__init__.py new file mode 100644 index 00000000000..5be68cdbb74 --- /dev/null +++ b/litellm/rag/ingestion/file_parsers/__init__.py @@ -0,0 +1,9 @@ +""" +File parsers for RAG ingestion. + +Provides text extraction utilities for various file formats. +""" + +from .pdf_parser import extract_text_from_pdf + +__all__ = ["extract_text_from_pdf"] diff --git a/litellm/rag/ingestion/file_parsers/pdf_parser.py b/litellm/rag/ingestion/file_parsers/pdf_parser.py new file mode 100644 index 00000000000..9a533ccf138 --- /dev/null +++ b/litellm/rag/ingestion/file_parsers/pdf_parser.py @@ -0,0 +1,70 @@ +""" +PDF text extraction utilities. + +Provides text extraction from PDF files using pypdf or PyPDF2. +""" + +from typing import Optional + +from litellm._logging import verbose_logger + + +def extract_text_from_pdf(file_content: bytes) -> Optional[str]: + """ + Extract text from PDF using pypdf if available. + + Args: + file_content: Raw PDF bytes + + Returns: + Extracted text or None if extraction fails + """ + try: + from io import BytesIO + + # Try pypdf first (most common) + try: + from pypdf import PdfReader as PypdfReader + + pdf_file = BytesIO(file_content) + reader = PypdfReader(pdf_file) + + text_parts = [] + for page in reader.pages: + text = page.extract_text() + if text: + text_parts.append(text) + + if text_parts: + extracted_text = "\n\n".join(text_parts) + verbose_logger.debug(f"Extracted {len(extracted_text)} characters from PDF using pypdf") + return extracted_text + + except ImportError: + verbose_logger.debug("pypdf not available, trying PyPDF2") + + # Fallback to PyPDF2 + try: + from PyPDF2 import PdfReader as PyPDF2Reader + + pdf_file = BytesIO(file_content) + reader = PyPDF2Reader(pdf_file) + + text_parts = [] + for page in reader.pages: + text = page.extract_text() + if text: + text_parts.append(text) + + if text_parts: + extracted_text = "\n\n".join(text_parts) + verbose_logger.debug(f"Extracted {len(extracted_text)} characters from PDF using PyPDF2") + return extracted_text + + except ImportError: + verbose_logger.debug("PyPDF2 not available, PDF extraction requires OCR or pypdf/PyPDF2 library") + + except Exception as e: + verbose_logger.debug(f"PDF text extraction failed: {e}") + + return None diff --git a/litellm/rag/ingestion/s3_vectors_ingestion.py b/litellm/rag/ingestion/s3_vectors_ingestion.py new file mode 100644 index 00000000000..e6c166a1015 --- /dev/null +++ b/litellm/rag/ingestion/s3_vectors_ingestion.py @@ -0,0 +1,573 @@ +""" +S3 Vectors-specific RAG Ingestion implementation. + +S3 Vectors is AWS's native vector storage service that provides: +- Purpose-built vector buckets for storing and querying vectors +- Vector indexes with configurable dimensions and distance metrics +- Metadata filtering for semantic search + +This implementation: +1. Auto-creates vector buckets and indexes if not provided +2. Uses LiteLLM's embedding API (supports any provider) +3. Uses httpx + AWS SigV4 signing (no boto3 dependency for S3 Vectors APIs) +4. Stores vectors with metadata using PutVectors API +""" + +from __future__ import annotations + +import hashlib +import uuid +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple + +import litellm +from litellm._logging import verbose_logger +from litellm.constants import ( + S3_VECTORS_DEFAULT_DIMENSION, + S3_VECTORS_DEFAULT_DISTANCE_METRIC, + S3_VECTORS_DEFAULT_NON_FILTERABLE_METADATA_KEYS, +) +from litellm.litellm_core_utils.safe_json_dumps import safe_dumps +from litellm.llms.bedrock.base_aws_llm import BaseAWSLLM +from litellm.llms.custom_httpx.http_handler import ( + get_async_httpx_client, + httpxSpecialProvider, +) +from litellm.rag.ingestion.base_ingestion import BaseRAGIngestion + +if TYPE_CHECKING: + from litellm import Router + from litellm.types.rag import RAGIngestOptions + + +class S3VectorsRAGIngestion(BaseRAGIngestion, BaseAWSLLM): + """ + S3 Vectors RAG ingestion using httpx + AWS SigV4 signing. + + Workflow: + 1. Auto-create vector bucket if needed (CreateVectorBucket API) + 2. Auto-create vector index if needed (CreateVectorIndex API) + 3. Generate embeddings using LiteLLM (supports any provider) + 4. Store vectors with PutVectors API + + Configuration: + - vector_bucket_name: S3 vector bucket name (required) + - index_name: Vector index name (auto-creates if not provided) + - dimension: Vector dimension (default: S3_VECTORS_DEFAULT_DIMENSION) + - distance_metric: "cosine" or "euclidean" (default: S3_VECTORS_DEFAULT_DISTANCE_METRIC) + - non_filterable_metadata_keys: List of metadata keys to exclude from filtering + """ + + def __init__( + self, + ingest_options: "RAGIngestOptions", + router: Optional["Router"] = None, + ): + BaseRAGIngestion.__init__(self, ingest_options=ingest_options, router=router) + BaseAWSLLM.__init__(self) + + # Extract config + self.vector_bucket_name = self.vector_store_config["vector_bucket_name"] + self.index_name = self.vector_store_config.get("index_name") + self.distance_metric = self.vector_store_config.get( + "distance_metric", S3_VECTORS_DEFAULT_DISTANCE_METRIC + ) + self.non_filterable_metadata_keys = self.vector_store_config.get( + "non_filterable_metadata_keys", + S3_VECTORS_DEFAULT_NON_FILTERABLE_METADATA_KEYS, + ) + + # Get dimension from config (will be auto-detected on first use if not provided) + self.dimension = self._get_dimension_from_config() + + # Get AWS region using BaseAWSLLM method + _aws_region = self.vector_store_config.get("aws_region_name") + self.aws_region_name = self.get_aws_region_name_for_non_llm_api_calls( + aws_region_name=str(_aws_region) if _aws_region else None + ) + + # Create httpx client (similar to s3_v2.py) + ssl_verify = self._get_ssl_verify( + ssl_verify=self.vector_store_config.get("ssl_verify") + ) + self.async_httpx_client = get_async_httpx_client( + llm_provider=httpxSpecialProvider.RAG, + params={"ssl_verify": ssl_verify} if ssl_verify is not None else None, + ) + + # Track if infrastructure is initialized + self._config_initialized = False + + async def _get_dimension_from_embedding_request(self) -> int: + """ + Auto-detect dimension by making a test embedding request. + + Makes a single embedding request with a test string to determine + the output dimension of the embedding model. + """ + if not self.embedding_config or "model" not in self.embedding_config: + return S3_VECTORS_DEFAULT_DIMENSION + + try: + model_name = self.embedding_config["model"] + verbose_logger.debug( + f"Auto-detecting dimension by making test embedding request to {model_name}" + ) + + # Make a test embedding request + test_input = "test" + if self.router: + response = await self.router.aembedding(model=model_name, input=[test_input]) + else: + response = await litellm.aembedding(model=model_name, input=[test_input]) + + # Get dimension from the response + if response.data and len(response.data) > 0: + dimension = len(response.data[0]["embedding"]) + verbose_logger.debug( + f"Auto-detected dimension {dimension} for embedding model {model_name}" + ) + return dimension + except Exception as e: + verbose_logger.warning( + f"Could not auto-detect dimension from embedding model: {e}. " + f"Using default dimension of {S3_VECTORS_DEFAULT_DIMENSION}." + ) + + return S3_VECTORS_DEFAULT_DIMENSION + + def _get_dimension_from_config(self) -> Optional[int]: + """ + Get vector dimension from config if explicitly provided. + + Returns None if dimension should be auto-detected. + """ + if "dimension" in self.vector_store_config: + return int(self.vector_store_config["dimension"]) + return None + + async def _ensure_config_initialized(self): + """Lazily initialize S3 Vectors infrastructure.""" + if self._config_initialized: + return + + # Auto-detect dimension if not provided + if self.dimension is None: + self.dimension = await self._get_dimension_from_embedding_request() + + # Ensure vector bucket exists + await self._ensure_vector_bucket_exists() + + # Ensure vector index exists + if not self.index_name: + # Auto-generate index name + unique_id = uuid.uuid4().hex[:8] + self.index_name = f"litellm-index-{unique_id}" + + await self._ensure_vector_index_exists() + + self._config_initialized = True + + async def _sign_and_execute_request( + self, + method: str, + url: str, + data: Optional[str] = None, + headers: Optional[Dict[str, str]] = None, + ) -> Any: + """ + Helper to sign and execute AWS API requests using httpx + SigV4. + + Pattern from litellm/integrations/s3_v2.py + """ + try: + import requests + from botocore.auth import SigV4Auth + from botocore.awsrequest import AWSRequest + except ImportError: + raise ImportError( + "Missing botocore to call S3 Vectors. Run 'pip install boto3'." + ) + + # Get AWS credentials using BaseAWSLLM's get_credentials method + credentials = self.get_credentials( + aws_access_key_id=self.vector_store_config.get("aws_access_key_id"), + aws_secret_access_key=self.vector_store_config.get("aws_secret_access_key"), + aws_session_token=self.vector_store_config.get("aws_session_token"), + aws_region_name=self.aws_region_name, + aws_session_name=self.vector_store_config.get("aws_session_name"), + aws_profile_name=self.vector_store_config.get("aws_profile_name"), + aws_role_name=self.vector_store_config.get("aws_role_name"), + aws_web_identity_token=self.vector_store_config.get("aws_web_identity_token"), + aws_sts_endpoint=self.vector_store_config.get("aws_sts_endpoint"), + aws_external_id=self.vector_store_config.get("aws_external_id"), + ) + + # Prepare headers + if headers is None: + headers = {} + + if data: + headers["Content-Type"] = "application/json" + # Calculate SHA256 hash of the content + content_hash = hashlib.sha256(data.encode("utf-8")).hexdigest() + headers["x-amz-content-sha256"] = content_hash + else: + # For requests without body, use hash of empty string + headers["x-amz-content-sha256"] = hashlib.sha256(b"").hexdigest() + + # Prepare the request + req = requests.Request(method, url, data=data, headers=headers) + prepped = req.prepare() + + # Sign the request + aws_request = AWSRequest( + method=prepped.method, + url=prepped.url, + data=prepped.body, + headers=prepped.headers, + ) + SigV4Auth(credentials, "s3vectors", self.aws_region_name).add_auth(aws_request) + + # Prepare the signed headers + signed_headers = dict(aws_request.headers.items()) + + # Make the request using specific method (pattern from s3_v2.py) + method_upper = method.upper() + if method_upper == "PUT": + response = await self.async_httpx_client.put( + url, data=data, headers=signed_headers + ) + elif method_upper == "POST": + response = await self.async_httpx_client.post( + url, data=data, headers=signed_headers + ) + elif method_upper == "GET": + response = await self.async_httpx_client.get(url, headers=signed_headers) + else: + raise ValueError(f"Unsupported HTTP method: {method}") + + return response + + async def _ensure_vector_bucket_exists(self): + """Create vector bucket if it doesn't exist using GetVectorBucket and CreateVectorBucket APIs.""" + verbose_logger.debug( + f"Ensuring S3 vector bucket exists: {self.vector_bucket_name}" + ) + + # Validate bucket name (AWS S3 naming rules) + if len(self.vector_bucket_name) < 3: + raise ValueError( + f"Invalid vector_bucket_name '{self.vector_bucket_name}': " + f"AWS S3 bucket names must be at least 3 characters long. " + f"Please provide a valid bucket name (e.g., 'my-vector-bucket')." + ) + if not self.vector_bucket_name.replace("-", "").replace(".", "").isalnum(): + raise ValueError( + f"Invalid vector_bucket_name '{self.vector_bucket_name}': " + f"AWS S3 bucket names can only contain lowercase letters, numbers, hyphens, and periods. " + f"Please provide a valid bucket name (e.g., 'my-vector-bucket')." + ) + + # Try to get bucket info using GetVectorBucket API + get_url = f"https://s3vectors.{self.aws_region_name}.api.aws/GetVectorBucket" + get_body = safe_dumps({"vectorBucketName": self.vector_bucket_name}) + + try: + response = await self._sign_and_execute_request("POST", get_url, data=get_body) + if response.status_code == 200: + verbose_logger.debug(f"Vector bucket {self.vector_bucket_name} exists") + return + except Exception as e: + verbose_logger.debug( + f"Bucket check failed (may not exist): {e}, attempting to create" + ) + + # Create vector bucket using CreateVectorBucket API + try: + verbose_logger.debug(f"Creating vector bucket: {self.vector_bucket_name}") + create_url = f"https://s3vectors.{self.aws_region_name}.api.aws/CreateVectorBucket" + create_body = safe_dumps({ + "vectorBucketName": self.vector_bucket_name + }) + + response = await self._sign_and_execute_request("POST", create_url, data=create_body) + + if response.status_code in (200, 201): + verbose_logger.info(f"Created vector bucket: {self.vector_bucket_name}") + elif response.status_code == 409: + # Bucket already exists (ConflictException) + verbose_logger.debug( + f"Vector bucket {self.vector_bucket_name} already exists" + ) + else: + verbose_logger.error(f"CreateVectorBucket failed: {response.status_code} - {response.text}") + response.raise_for_status() + except Exception as e: + verbose_logger.exception(f"Error creating vector bucket: {e}") + raise + + async def _ensure_vector_index_exists(self): + """Create vector index if it doesn't exist using GetIndex and CreateIndex APIs.""" + verbose_logger.debug( + f"Ensuring vector index exists: {self.vector_bucket_name}/{self.index_name}" + ) + + # Try to get index info using GetIndex API + get_url = f"https://s3vectors.{self.aws_region_name}.api.aws/GetIndex" + get_body = safe_dumps({ + "vectorBucketName": self.vector_bucket_name, + "indexName": self.index_name + }) + + try: + response = await self._sign_and_execute_request("POST", get_url, data=get_body) + if response.status_code == 200: + verbose_logger.debug(f"Vector index {self.index_name} exists") + return + except Exception as e: + verbose_logger.debug( + f"Index check failed (may not exist): {e}, attempting to create" + ) + + # Create vector index using CreateIndex API + try: + verbose_logger.debug( + f"Creating vector index: {self.index_name} with dimension={self.dimension}, metric={self.distance_metric}" + ) + + # Prepare index configuration per AWS API docs + index_config = { + "vectorBucketName": self.vector_bucket_name, + "indexName": self.index_name, + "dataType": "float32", + "dimension": self.dimension, + "distanceMetric": self.distance_metric, + } + + if self.non_filterable_metadata_keys: + index_config["metadataConfiguration"] = { + "nonFilterableMetadataKeys": self.non_filterable_metadata_keys + } + + create_url = f"https://s3vectors.{self.aws_region_name}.api.aws/CreateIndex" + response = await self._sign_and_execute_request( + "POST", create_url, data=safe_dumps(index_config) + ) + + if response.status_code in (200, 201): + verbose_logger.info(f"Created vector index: {self.index_name}") + elif response.status_code == 409: + verbose_logger.debug(f"Vector index {self.index_name} already exists") + else: + verbose_logger.error(f"CreateIndex failed: {response.status_code} - {response.text}") + response.raise_for_status() + except Exception as e: + verbose_logger.exception(f"Error creating vector index: {e}") + raise + + async def _put_vectors(self, vectors: List[Dict[str, Any]]): + """ + Call PutVectors API to store vectors in S3 Vectors. + + Args: + vectors: List of vector objects with keys: "key", "data", "metadata" + """ + verbose_logger.debug( + f"Storing {len(vectors)} vectors in {self.vector_bucket_name}/{self.index_name}" + ) + + url = f"https://s3vectors.{self.aws_region_name}.api.aws/PutVectors" + + # Prepare request body per AWS API docs + request_body = { + "vectorBucketName": self.vector_bucket_name, + "indexName": self.index_name, + "vectors": vectors + } + + try: + response = await self._sign_and_execute_request( + "POST", url, data=safe_dumps(request_body) + ) + + if response.status_code in (200, 201): + verbose_logger.info( + f"Successfully stored {len(vectors)} vectors in index {self.index_name}" + ) + else: + verbose_logger.error( + f"PutVectors failed with status {response.status_code}: {response.text}" + ) + response.raise_for_status() + except Exception as e: + verbose_logger.exception(f"Error storing vectors: {e}") + raise + + async def embed( + self, + chunks: List[str], + ) -> Optional[List[List[float]]]: + """ + Generate embeddings using LiteLLM's embedding API. + + Supports any embedding provider (OpenAI, Bedrock, Cohere, etc.) + """ + if not chunks: + return None + + # Use embedding config from ingest_options or default + if not self.embedding_config: + verbose_logger.warning( + "No embedding config provided, using default text-embedding-3-small" + ) + self.embedding_config = {"model": "text-embedding-3-small"} + + embedding_model = self.embedding_config.get("model", "text-embedding-3-small") + + verbose_logger.debug( + f"Generating embeddings for {len(chunks)} chunks using {embedding_model}" + ) + + # Convert to list to ensure type compatibility + input_chunks: List[str] = list(chunks) + + if self.router: + response = await self.router.aembedding(model=embedding_model, input=input_chunks) + else: + response = await litellm.aembedding(model=embedding_model, input=input_chunks) + + return [item["embedding"] for item in response.data] + + async def store( + self, + file_content: Optional[bytes], + filename: Optional[str], + content_type: Optional[str], + chunks: List[str], + embeddings: Optional[List[List[float]]], + ) -> Tuple[Optional[str], Optional[str]]: + """ + Store vectors in S3 Vectors using PutVectors API. + + Steps: + 1. Ensure vector bucket exists (auto-create if needed) + 2. Ensure vector index exists (auto-create if needed) + 3. Prepare vector data with metadata + 4. Call PutVectors API with httpx + SigV4 signing + + Args: + file_content: Raw file bytes (not used for S3 Vectors) + filename: Name of the file + content_type: MIME type (not used for S3 Vectors) + chunks: Text chunks + embeddings: Vector embeddings + + Returns: + Tuple of (index_name, filename) + """ + # Ensure infrastructure exists + await self._ensure_config_initialized() + + if not embeddings or not chunks: + error_msg = ( + "No text content could be extracted from the file for embedding. " + "Possible causes:\n" + " 1. PDF files require OCR - add 'ocr' config with a vision model (e.g., 'anthropic/claude-3-5-sonnet-20241022')\n" + " 2. Binary files cannot be processed - convert to text first\n" + " 3. File is empty or contains no extractable text\n" + "For PDFs, either enable OCR or use a PDF extraction library to convert to text before ingestion." + ) + verbose_logger.error(error_msg) + raise ValueError(error_msg) + + # Prepare vectors for PutVectors API + vectors = [] + for i, (chunk, embedding) in enumerate(zip(chunks, embeddings)): + # Build metadata dict + metadata: Dict[str, str] = { + "source_text": chunk, # Non-filterable (for reference) + "chunk_index": str(i), # Filterable + } + + if filename: + metadata["filename"] = filename # Filterable + + vector_obj = { + "key": f"{filename}_{i}" if filename else f"chunk_{i}", + "data": {"float32": embedding}, + "metadata": metadata, + } + + vectors.append(vector_obj) + + # Call PutVectors API + await self._put_vectors(vectors) + + # Return vector_store_id in format bucket_name:index_name for S3 Vectors search compatibility + vector_store_id = f"{self.vector_bucket_name}:{self.index_name}" + return vector_store_id, filename + + async def query_vector_store( + self, vector_store_id: str, query: str, top_k: int = 5 + ) -> Optional[Dict[str, Any]]: + """ + Query S3 Vectors using QueryVectors API. + + Args: + vector_store_id: Index name + query: Query text + top_k: Number of results to return + + Returns: + Query results with vectors and metadata + """ + verbose_logger.debug(f"Querying index {vector_store_id} with query: {query}") + + # Generate query embedding + if not self.embedding_config: + self.embedding_config = {"model": "text-embedding-3-small"} + + embedding_model = self.embedding_config.get("model", "text-embedding-3-small") + + response = await litellm.aembedding(model=embedding_model, input=[query]) + query_embedding = response.data[0]["embedding"] + + # Call QueryVectors API + url = f"https://s3vectors.{self.aws_region_name}.api.aws/QueryVectors" + + request_body = { + "vectorBucketName": self.vector_bucket_name, + "indexName": vector_store_id, + "queryVector": {"float32": query_embedding}, + "topK": top_k, + "returnDistance": True, + "returnMetadata": True, + } + + try: + response = await self._sign_and_execute_request( + "POST", url, data=safe_dumps(request_body) + ) + + if response.status_code == 200: + results = response.json() + verbose_logger.debug(f"Query returned {len(results.get('vectors', []))} results") + + # Check if query terms appear in results + if results.get("vectors"): + for result in results["vectors"]: + metadata = result.get("metadata", {}) + source_text = metadata.get("source_text", "") + if query.lower() in source_text.lower(): + return results + + # Return results even if exact match not found + return results + else: + verbose_logger.error( + f"QueryVectors failed with status {response.status_code}: {response.text}" + ) + return None + except Exception as e: + verbose_logger.exception(f"Error querying vectors: {e}") + return None diff --git a/litellm/rag/main.py b/litellm/rag/main.py index 620097f83dd..571f78f2f70 100644 --- a/litellm/rag/main.py +++ b/litellm/rag/main.py @@ -31,6 +31,7 @@ from litellm.rag.ingestion.base_ingestion import BaseRAGIngestion from litellm.rag.ingestion.bedrock_ingestion import BedrockRAGIngestion from litellm.rag.ingestion.gemini_ingestion import GeminiRAGIngestion from litellm.rag.ingestion.openai_ingestion import OpenAIRAGIngestion +from litellm.rag.ingestion.s3_vectors_ingestion import S3VectorsRAGIngestion from litellm.rag.rag_query import RAGQuery from litellm.types.rag import ( RAGIngestOptions, @@ -48,6 +49,7 @@ INGESTION_REGISTRY: Dict[str, Type[BaseRAGIngestion]] = { "openai": OpenAIRAGIngestion, "bedrock": BedrockRAGIngestion, "gemini": GeminiRAGIngestion, + "s3_vectors": S3VectorsRAGIngestion, } diff --git a/litellm/realtime_api/main.py b/litellm/realtime_api/main.py index 0a78fb7b72a..40983fa55fa 100644 --- a/litellm/realtime_api/main.py +++ b/litellm/realtime_api/main.py @@ -3,8 +3,8 @@ from typing import Any, Optional, cast import litellm -from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider from litellm.constants import REALTIME_WEBSOCKET_MAX_MESSAGE_SIZE_BYTES +from litellm.litellm_core_utils.get_llm_provider_logic import get_llm_provider from litellm.llms.base_llm.realtime.transformation import BaseRealtimeConfig from litellm.llms.custom_httpx.llm_http_handler import BaseLLMHTTPHandler from litellm.secret_managers.main import get_secret_str @@ -16,12 +16,14 @@ from litellm.utils import ProviderConfigManager from ..litellm_core_utils.get_litellm_params import get_litellm_params from ..litellm_core_utils.litellm_logging import Logging as LiteLLMLogging from ..llms.azure.realtime.handler import AzureOpenAIRealtime +from ..llms.bedrock.realtime.handler import BedrockRealtime +from ..llms.custom_httpx.http_handler import get_shared_realtime_ssl_context from ..llms.openai.realtime.handler import OpenAIRealtime from ..utils import client as wrapper_client -from ..llms.custom_httpx.http_handler import get_shared_realtime_ssl_context azure_realtime = AzureOpenAIRealtime() openai_realtime = OpenAIRealtime() +bedrock_realtime = BedrockRealtime() base_llm_http_handler = BaseLLMHTTPHandler() @@ -153,6 +155,39 @@ async def _arealtime( timeout=timeout, query_params=query_params, ) + elif _custom_llm_provider == "bedrock": + # Extract AWS parameters from kwargs + aws_region_name = kwargs.get("aws_region_name") + aws_access_key_id = kwargs.get("aws_access_key_id") + aws_secret_access_key = kwargs.get("aws_secret_access_key") + aws_session_token = kwargs.get("aws_session_token") + aws_role_name = kwargs.get("aws_role_name") + aws_session_name = kwargs.get("aws_session_name") + aws_profile_name = kwargs.get("aws_profile_name") + aws_web_identity_token = kwargs.get("aws_web_identity_token") + aws_sts_endpoint = kwargs.get("aws_sts_endpoint") + aws_bedrock_runtime_endpoint = kwargs.get("aws_bedrock_runtime_endpoint") + aws_external_id = kwargs.get("aws_external_id") + + await bedrock_realtime.async_realtime( + model=model, + websocket=websocket, + logging_obj=litellm_logging_obj, + api_base=dynamic_api_base or api_base, + api_key=dynamic_api_key or api_key, + timeout=timeout, + aws_region_name=aws_region_name, + aws_access_key_id=aws_access_key_id, + aws_secret_access_key=aws_secret_access_key, + aws_session_token=aws_session_token, + aws_role_name=aws_role_name, + aws_session_name=aws_session_name, + aws_profile_name=aws_profile_name, + aws_web_identity_token=aws_web_identity_token, + aws_sts_endpoint=aws_sts_endpoint, + aws_bedrock_runtime_endpoint=aws_bedrock_runtime_endpoint, + aws_external_id=aws_external_id, + ) else: raise ValueError(f"Unsupported model: {model}") diff --git a/litellm/responses/litellm_completion_transformation/transformation.py b/litellm/responses/litellm_completion_transformation/transformation.py index 3badbc50578..df298f7c448 100644 --- a/litellm/responses/litellm_completion_transformation/transformation.py +++ b/litellm/responses/litellm_completion_transformation/transformation.py @@ -1413,6 +1413,7 @@ class LiteLLMCompletionResponsesConfig: ), user=getattr(chat_completion_response, "user", None), ) + responses_api_response._hidden_params = getattr(chat_completion_response, "_hidden_params", {}) return responses_api_response @staticmethod @@ -1743,13 +1744,15 @@ class LiteLLMCompletionResponsesConfig: and usage.prompt_tokens_details is not None ): prompt_details = usage.prompt_tokens_details - input_details_dict: Dict[str, Optional[int]] = {} + input_details_dict: Dict[str, int] = {} if ( hasattr(prompt_details, "cached_tokens") and prompt_details.cached_tokens is not None ): input_details_dict["cached_tokens"] = prompt_details.cached_tokens + else: + input_details_dict["cached_tokens"] = 0 if ( hasattr(prompt_details, "text_tokens") @@ -1774,7 +1777,7 @@ class LiteLLMCompletionResponsesConfig: and usage.completion_tokens_details is not None ): completion_details = usage.completion_tokens_details - output_details_dict: Dict[str, Optional[int]] = {} + output_details_dict: Dict[str, int] = {} if ( hasattr(completion_details, "reasoning_tokens") and completion_details.reasoning_tokens is not None @@ -1782,6 +1785,8 @@ class LiteLLMCompletionResponsesConfig: output_details_dict["reasoning_tokens"] = ( completion_details.reasoning_tokens ) + else: + output_details_dict["reasoning_tokens"] = 0 if ( hasattr(completion_details, "text_tokens") diff --git a/litellm/responses/main.py b/litellm/responses/main.py index 83c23a58500..b2c2493c812 100644 --- a/litellm/responses/main.py +++ b/litellm/responses/main.py @@ -434,6 +434,8 @@ async def aresponses( _, custom_llm_provider, _, _ = litellm.get_llm_provider( model=model, api_base=local_vars.get("base_url", None) ) + # Update local_vars with detected provider (fixes #19782) + local_vars["custom_llm_provider"] = custom_llm_provider func = partial( responses, @@ -583,6 +585,9 @@ def responses( api_key=litellm_params.api_key, ) + # Update local_vars with detected provider (fixes #19782) + local_vars["custom_llm_provider"] = custom_llm_provider + # Use dynamic credentials from get_llm_provider (e.g., when use_litellm_proxy=True) if dynamic_api_key is not None: litellm_params.api_key = dynamic_api_key @@ -1411,6 +1416,8 @@ async def acompact_responses( _, custom_llm_provider, _, _ = litellm.get_llm_provider( model=model, api_base=local_vars.get("base_url", None) ) + # Update local_vars with detected provider (fixes #19782) + local_vars["custom_llm_provider"] = custom_llm_provider func = partial( compact_responses, @@ -1498,6 +1505,9 @@ def compact_responses( api_key=litellm_params.api_key, ) + # Update local_vars with detected provider (fixes #19782) + local_vars["custom_llm_provider"] = custom_llm_provider + # Use dynamic credentials from get_llm_provider (e.g., when use_litellm_proxy=True) if dynamic_api_key is not None: litellm_params.api_key = dynamic_api_key diff --git a/litellm/responses/utils.py b/litellm/responses/utils.py index 469eeb3da06..39aebb262fe 100644 --- a/litellm/responses/utils.py +++ b/litellm/responses/utils.py @@ -462,12 +462,17 @@ class ResponseAPILoggingUtils: completion_tokens: int = response_api_usage.output_tokens or 0 prompt_tokens_details: Optional[PromptTokensDetailsWrapper] = None if response_api_usage.input_tokens_details: - prompt_tokens_details = PromptTokensDetailsWrapper( - cached_tokens=getattr(response_api_usage.input_tokens_details, "cached_tokens", None), - audio_tokens=getattr(response_api_usage.input_tokens_details, "audio_tokens", None), - text_tokens=getattr(response_api_usage.input_tokens_details, "text_tokens", None), - image_tokens=getattr(response_api_usage.input_tokens_details, "image_tokens", None), - ) + if isinstance(response_api_usage.input_tokens_details, dict): + prompt_tokens_details = PromptTokensDetailsWrapper( + **response_api_usage.input_tokens_details + ) + else: + prompt_tokens_details = PromptTokensDetailsWrapper( + cached_tokens=getattr(response_api_usage.input_tokens_details, "cached_tokens", None), + audio_tokens=getattr(response_api_usage.input_tokens_details, "audio_tokens", None), + text_tokens=getattr(response_api_usage.input_tokens_details, "text_tokens", None), + image_tokens=getattr(response_api_usage.input_tokens_details, "image_tokens", None), + ) completion_tokens_details: Optional[CompletionTokensDetailsWrapper] = None output_tokens_details = getattr(response_api_usage, "output_tokens_details", None) if output_tokens_details: diff --git a/litellm/router.py b/litellm/router.py index 62070d23755..d01c8443dab 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -58,6 +58,7 @@ from litellm.litellm_core_utils.core_helpers import ( _get_parent_otel_span_from_kwargs, get_metadata_variable_name_from_kwargs, ) +from litellm.litellm_core_utils.thread_pool_executor import executor from litellm.litellm_core_utils.coroutine_checker import coroutine_checker from litellm.litellm_core_utils.credential_accessor import CredentialAccessor from litellm.litellm_core_utils.dd_tracing import tracer @@ -116,6 +117,9 @@ from litellm.router_utils.pre_call_checks.prompt_caching_deployment_check import from litellm.router_utils.pre_call_checks.responses_api_deployment_check import ( ResponsesApiDeploymentCheck, ) +from litellm.router_utils.pre_call_checks.model_rate_limit_check import ( + ModelRateLimitingCheck, +) from litellm.router_utils.router_callbacks.track_deployment_metrics import ( increment_deployment_failures_for_current_minute, increment_deployment_successes_for_current_minute, @@ -223,6 +227,7 @@ class Router: redis_host: Optional[str] = None, redis_port: Optional[int] = None, redis_password: Optional[str] = None, + redis_db: Optional[int] = None, cache_responses: Optional[bool] = False, cache_kwargs: dict = {}, # additional kwargs to pass to RedisCache (see caching.py) caching_groups: Optional[ @@ -409,6 +414,12 @@ class Router: if redis_password is not None: cache_config["password"] = redis_password + if redis_db is not None: + verbose_router_logger.warning( + "Deprecated 'redis_db' argument used. Please remove 'redis_db' from your config/database and use 'cache_kwargs' instead." + ) + cache_config["db"] = str(redis_db) + # Add additional key-value pairs from cache_kwargs cache_config.update(cache_kwargs) redis_cache = self._create_redis_cache(cache_config) @@ -643,6 +654,17 @@ class Router: self.initialize_router_endpoints() self.apply_default_settings() + @staticmethod + def get_valid_args() -> List[str]: + """ + Returns a list of valid arguments for the Router.__init__ method. + """ + arg_spec = inspect.getfullargspec(Router.__init__) + valid_args = arg_spec.args + arg_spec.kwonlyargs + if "self" in valid_args: + valid_args.remove("self") + return valid_args + def apply_default_settings(self): """ Apply the default settings to the router. @@ -868,9 +890,8 @@ class Router: self.allm_passthrough_route = self.factory_function( litellm.allm_passthrough_route, call_type="allm_passthrough_route" ) - self.acancel_batch = self.factory_function( - litellm.acancel_batch, call_type="acancel_batch" - ) + # Note: acancel_batch is defined as a method on the Router class (not using factory_function) + # to properly handle model-to-provider mapping like acreate_batch and aretrieve_batch def _initialize_vector_store_endpoints(self): """Initialize vector store endpoints.""" @@ -1176,6 +1197,8 @@ class Router: ) elif pre_call_check == "responses_api_deployment_check": _callback = ResponsesApiDeploymentCheck() + elif pre_call_check == "enforce_model_rate_limits": + _callback = ModelRateLimitingCheck(dual_cache=self.cache) if _callback is not None: if self.optional_callbacks is None: self.optional_callbacks = [] @@ -1239,10 +1262,25 @@ class Router: specific_deployment=kwargs.pop("specific_deployment", None), request_kwargs=kwargs, ) - self._update_kwargs_with_deployment(deployment=deployment, kwargs=kwargs) + # Check for silent model experiment + # Make a local copy of litellm_params to avoid mutating the Router's state + litellm_params = deployment["litellm_params"].copy() + silent_model = litellm_params.pop("silent_model", None) + if silent_model is not None: + # Mirroring traffic to a secondary model + # Use shared thread pool for background calls + executor.submit( + self._silent_experiment_completion, + silent_model, + messages, + **kwargs, + ) + + self._update_kwargs_with_deployment(deployment=deployment, kwargs=kwargs) + kwargs.pop("silent_model", None) # Ensure it's not in kwargs either # No copy needed - data is only read and spread into new dict below - data = deployment["litellm_params"] + data = litellm_params.copy() # Use the local copy of litellm_params model_name = data["model"] potential_model_client = self._get_client( deployment=deployment, kwargs=kwargs @@ -1263,15 +1301,14 @@ class Router: if not self.has_model_id(model): self.routing_strategy_pre_call_checks(deployment=deployment) - response = litellm.completion( - **{ - **data, - "messages": messages, - "caching": self.cache_responses, - "client": model_client, - **kwargs, - } - ) + input_kwargs = { + **data, + "messages": messages, + "caching": self.cache_responses, + "client": model_client, + **kwargs, + } + response = litellm.completion(**input_kwargs) verbose_router_logger.info( f"litellm.completion(model={model_name})\033[32m 200 OK\033[0m" ) @@ -1298,6 +1335,58 @@ class Router: self._set_deployment_num_retries_on_exception(e, deployment) raise e + def _get_silent_experiment_kwargs(self, **kwargs) -> dict: + """ + Prepare kwargs for a silent experiment by ensuring isolation from the primary call. + """ + # Copy kwargs to ensure isolation (use safe_deep_copy to handle non-serializable objects like OTEL spans) + from litellm.litellm_core_utils.core_helpers import safe_deep_copy + + silent_kwargs = safe_deep_copy(kwargs) + if "metadata" not in silent_kwargs: + silent_kwargs["metadata"] = {} + + silent_kwargs["metadata"]["is_silent_experiment"] = True + + # Pop logging objects and call IDs to ensure a fresh logging context + # This prevents collisions in the Proxy's database (spend_logs) + silent_kwargs.pop("litellm_call_id", None) + silent_kwargs.pop("litellm_logging_obj", None) + silent_kwargs.pop("standard_logging_object", None) + silent_kwargs.pop("proxy_server_request", None) + + return silent_kwargs + + def _silent_experiment_completion( + self, silent_model: str, messages: List[Any], **kwargs + ): + """ + Run a silent experiment in the background (thread). + """ + try: + # Prevent infinite recursion if silent model also has a silent model + if kwargs.get("metadata", {}).get("is_silent_experiment", False): + return + + messages = copy.deepcopy(messages) + + verbose_router_logger.info( + f"Starting silent experiment for model {silent_model}" + ) + + silent_kwargs = self._get_silent_experiment_kwargs(**kwargs) + + # Trigger the silent request + self.completion( + model=silent_model, + messages=cast(List[Dict[str, str]], messages), + **silent_kwargs, + ) + except Exception as e: + verbose_router_logger.error( + f"Silent experiment failed for model {silent_model}: {str(e)}" + ) + # fmt: off @overload @@ -1506,6 +1595,36 @@ class Router: return FallbackStreamWrapper(stream_with_fallbacks()) + async def _silent_experiment_acompletion( + self, silent_model: str, messages: List[Any], **kwargs + ): + """ + Run a silent experiment in the background. + """ + try: + # Prevent infinite recursion if silent model also has a silent model + if kwargs.get("metadata", {}).get("is_silent_experiment", False): + return + + messages = copy.deepcopy(messages) + + verbose_router_logger.info( + f"Starting silent experiment for model {silent_model}" + ) + + silent_kwargs = self._get_silent_experiment_kwargs(**kwargs) + + # Trigger the silent request + await self.acompletion( + model=silent_model, + messages=cast(List[AllMessageValues], messages), + **silent_kwargs, + ) + except Exception as e: + verbose_router_logger.error( + f"Silent experiment failed for model {silent_model}: {str(e)}" + ) + async def _acompletion( # noqa: PLR0915 self, model: str, messages: List[Dict[str, str]], **kwargs ) -> Union[ModelResponse, CustomStreamWrapper,]: @@ -1552,9 +1671,27 @@ class Router: self._track_deployment_metrics( deployment=deployment, parent_otel_span=parent_otel_span ) + + # Check for silent model experiment + # Make a local copy of litellm_params to avoid mutating the Router's state + litellm_params = deployment["litellm_params"].copy() + silent_model = litellm_params.pop("silent_model", None) + + if silent_model is not None: + # Mirroring traffic to a secondary model + # This is a silent experiment, so we don't want to block the primary request + asyncio.create_task( + self._silent_experiment_acompletion( + silent_model=silent_model, + messages=messages, # Use messages instead of *args + **kwargs, + ) + ) + self._update_kwargs_with_deployment(deployment=deployment, kwargs=kwargs) + kwargs.pop("silent_model", None) # Ensure it's not in kwargs either # No copy needed - data is only read and spread into new dict below - data = deployment["litellm_params"] + data = litellm_params.copy() # Use the local copy of litellm_params model_name = data["model"] @@ -1571,6 +1708,7 @@ class Router: "client": model_client, **kwargs, } + input_kwargs.pop("silent_model", None) _response = litellm.acompletion(**input_kwargs) @@ -1696,8 +1834,11 @@ class Router: litellm_params = deployment.get("litellm_params", {}) dep_num_retries = litellm_params.get("num_retries") - if dep_num_retries is not None and isinstance(dep_num_retries, int): - exception.num_retries = dep_num_retries # type: ignore + if dep_num_retries is not None: + try: + exception.num_retries = int(dep_num_retries) # type: ignore # Handle both int and str + except (ValueError, TypeError): + pass # Skip if value can't be converted to int def _update_kwargs_with_default_litellm_params( self, kwargs: dict, metadata_variable_name: Optional[str] = "metadata" @@ -3891,6 +4032,120 @@ class Router: ) raise e + async def acancel_batch( + self, + model: str, + **kwargs, + ) -> LiteLLMBatch: + """ + Cancel a batch through the router with proper model-to-provider mapping. + """ + try: + kwargs["model"] = model + kwargs["original_function"] = self._acancel_batch + kwargs["num_retries"] = kwargs.get("num_retries", self.num_retries) + metadata_variable_name = _get_router_metadata_variable_name( + function_name="_acancel_batch" + ) + self._update_kwargs_before_fallbacks( + model=model, + kwargs=kwargs, + metadata_variable_name=metadata_variable_name, + ) + response = await self.async_function_with_fallbacks(**kwargs) + + return response + except Exception as e: + asyncio.create_task( + send_llm_exception_alert( + litellm_router_instance=self, + request_kwargs=kwargs, + error_traceback_str=traceback.format_exc(), + original_exception=e, + ) + ) + raise e + + async def _acancel_batch( + self, + model: str, + **kwargs, + ) -> LiteLLMBatch: + try: + verbose_router_logger.debug( + f"Inside _acancel_batch()- model: {model}; kwargs: {kwargs}" + ) + parent_otel_span = _get_parent_otel_span_from_kwargs(kwargs) + deployment = await self.async_get_available_deployment( + model=model, + messages=[{"role": "user", "content": "batch-api-fake-text"}], + specific_deployment=kwargs.pop("specific_deployment", None), + request_kwargs=kwargs, + ) + + data = deployment["litellm_params"].copy() + model_name = data["model"] + self._update_kwargs_with_deployment( + deployment=deployment, kwargs=kwargs, function_name="_acancel_batch" + ) + + model_client = self._get_async_openai_model_client( + deployment=deployment, + kwargs=kwargs, + ) + self.total_calls[model_name] += 1 + + ## SET CUSTOM PROVIDER TO SELECTED DEPLOYMENT ## + _, custom_llm_provider, _, _ = get_llm_provider(model=data["model"]) + + response = litellm.acancel_batch( + **{ + **data, + "custom_llm_provider": custom_llm_provider, + "caching": self.cache_responses, + "client": model_client, + **kwargs, + } + ) + + rpm_semaphore = self._get_client( + deployment=deployment, + kwargs=kwargs, + client_type="max_parallel_requests", + ) + + if rpm_semaphore is not None and isinstance( + rpm_semaphore, asyncio.Semaphore + ): + async with rpm_semaphore: + """ + - Check rpm limits before making the call + - If allowed, increment the rpm limit (allows global value to be updated, concurrency-safe) + """ + await self.async_routing_strategy_pre_call_checks( + deployment=deployment, parent_otel_span=parent_otel_span + ) + response = await response # type: ignore + else: + await self.async_routing_strategy_pre_call_checks( + deployment=deployment, parent_otel_span=parent_otel_span + ) + response = await response # type: ignore + + self.success_calls[model_name] += 1 + verbose_router_logger.info( + f"litellm.acancel_batch(model={model_name})\033[32m 200 OK\033[0m" + ) + + return response # type: ignore + except Exception as e: + verbose_router_logger.exception( + f"litellm._acancel_batch(model={model}, {kwargs})\033[31m Exception {str(e)}\033[0m" + ) + if model is not None: + self.fail_calls[model] += 1 + raise e + async def alist_batches( self, model: str, @@ -3984,7 +4239,6 @@ class Router: "afile_delete", "afile_content", "_arealtime", - "acancel_batch", "acreate_fine_tuning_job", "acancel_fine_tuning_job", "alist_fine_tuning_jobs", @@ -4172,7 +4426,6 @@ class Router: "avideo_status", "avideo_content", "avideo_remix", - "acancel_batch", "acreate_skill", "alist_skills", "aget_skill", @@ -4692,9 +4945,12 @@ class Router: # get num_retries from retry policy # Use the model_group captured at the start of the function, or get it from metadata # kwargs.get("model") at this point is the deployment model, not the model_group - _model_group_for_retry_policy = model_group or _metadata.get("model_group") or kwargs.get("model") + _model_group_for_retry_policy = ( + model_group or _metadata.get("model_group") or kwargs.get("model") + ) _retry_policy_retries = self.get_num_retries_from_retry_policy( - exception=original_exception, model_group=_model_group_for_retry_policy + exception=original_exception, + model_group=_model_group_for_retry_policy, ) if _retry_policy_retries is not None: num_retries = _retry_policy_retries @@ -5879,7 +6135,10 @@ class Router: ) # done reading model["litellm_params"] # Check if provider is supported: either in enum or JSON-configured - if custom_llm_provider not in litellm.provider_list and not JSONProviderRegistry.exists(custom_llm_provider): + if ( + custom_llm_provider not in litellm.provider_list + and not JSONProviderRegistry.exists(custom_llm_provider) + ): raise Exception(f"Unsupported provider - {custom_llm_provider}") #### DEPLOYMENT NAMES INIT ######## @@ -6355,19 +6614,20 @@ class Router: elif custom_llm_provider != "azure": model = _model - potential_models = self.pattern_router.route(received_model_name) - if "*" in model and potential_models is not None: # if wildcard route - for potential_model in potential_models: - try: - if potential_model.get("model_info", {}).get( - "id" - ) == deployment.get("model_info", {}).get("id"): - model = potential_model.get("litellm_params", {}).get( - "model" - ) - break - except Exception: - pass + if "*" in model: # only call pattern_router for wildcard models + potential_models = self.pattern_router.route(received_model_name) + if potential_models is not None: + for potential_model in potential_models: + try: + if potential_model.get("model_info", {}).get( + "id" + ) == deployment.get("model_info", {}).get("id"): + model = potential_model.get("litellm_params", {}).get( + "model" + ) + break + except Exception: + pass ## GET LITELLM MODEL INFO - raises exception, if model is not mapped if model is None: @@ -8594,11 +8854,6 @@ class Router: if allowed_fails_policy is None: return None - if ( - isinstance(exception, litellm.BadRequestError) - and allowed_fails_policy.BadRequestErrorAllowedFails is not None - ): - return allowed_fails_policy.BadRequestErrorAllowedFails if ( isinstance(exception, litellm.AuthenticationError) and allowed_fails_policy.AuthenticationErrorAllowedFails is not None @@ -8619,6 +8874,11 @@ class Router: and allowed_fails_policy.ContentPolicyViolationErrorAllowedFails is not None ): return allowed_fails_policy.ContentPolicyViolationErrorAllowedFails + if ( + isinstance(exception, litellm.BadRequestError) + and allowed_fails_policy.BadRequestErrorAllowedFails is not None + ): + return allowed_fails_policy.BadRequestErrorAllowedFails def _initialize_alerting(self): from litellm.integrations.SlackAlerting.slack_alerting import SlackAlerting diff --git a/litellm/router_utils/get_retry_from_policy.py b/litellm/router_utils/get_retry_from_policy.py index 48df43ef818..ec326ebb50d 100644 --- a/litellm/router_utils/get_retry_from_policy.py +++ b/litellm/router_utils/get_retry_from_policy.py @@ -43,11 +43,6 @@ def get_num_retries_from_retry_policy( if isinstance(retry_policy, dict): retry_policy = RetryPolicy(**retry_policy) - if ( - isinstance(exception, BadRequestError) - and retry_policy.BadRequestErrorRetries is not None - ): - return retry_policy.BadRequestErrorRetries if ( isinstance(exception, AuthenticationError) and retry_policy.AuthenticationErrorRetries is not None @@ -65,6 +60,11 @@ def get_num_retries_from_retry_policy( and retry_policy.ContentPolicyViolationErrorRetries is not None ): return retry_policy.ContentPolicyViolationErrorRetries + if ( + isinstance(exception, BadRequestError) + and retry_policy.BadRequestErrorRetries is not None + ): + return retry_policy.BadRequestErrorRetries def reset_retry_policy() -> RetryPolicy: diff --git a/litellm/router_utils/pre_call_checks/model_rate_limit_check.py b/litellm/router_utils/pre_call_checks/model_rate_limit_check.py new file mode 100644 index 00000000000..e5be61690ba --- /dev/null +++ b/litellm/router_utils/pre_call_checks/model_rate_limit_check.py @@ -0,0 +1,373 @@ +""" +Enforce TPM/RPM rate limits set on model deployments. + +This pre-call check ensures that model-level TPM/RPM limits are enforced +across all requests, regardless of routing strategy. + +When enabled via `enforce_model_rate_limits: true` in litellm_settings, +requests that exceed the configured TPM/RPM limits will receive a 429 error. +""" + +from typing import TYPE_CHECKING, Any, Dict, Optional, Union + +import httpx + +import litellm +from litellm._logging import verbose_router_logger +from litellm.caching.dual_cache import DualCache +from litellm.integrations.custom_logger import CustomLogger +from litellm.types.router import RouterErrors +from litellm.types.utils import StandardLoggingPayload +from litellm.utils import get_utc_datetime + +if TYPE_CHECKING: + from opentelemetry.trace import Span as _Span + + Span = Union[_Span, Any] +else: + Span = Any + + +class RoutingArgs: + ttl: int = 60 # 1min (RPM/TPM expire key) + + +class ModelRateLimitingCheck(CustomLogger): + """ + Pre-call check that enforces TPM/RPM limits on model deployments. + + This check runs before each request and raises a RateLimitError + if the deployment has exceeded its configured TPM or RPM limits. + + Unlike the usage-based-routing strategy which uses limits for routing decisions, + this check actively enforces those limits across ALL routing strategies. + """ + + def __init__(self, dual_cache: DualCache): + self.dual_cache = dual_cache + + def _get_deployment_limits( + self, deployment: Dict + ) -> tuple[Optional[int], Optional[int]]: + """ + Extract TPM and RPM limits from a deployment configuration. + + Checks in order: + 1. Top-level 'tpm'/'rpm' fields + 2. litellm_params.tpm/rpm + 3. model_info.tpm/rpm + + Returns: + Tuple of (tpm_limit, rpm_limit) + """ + # Check top-level + tpm = deployment.get("tpm") + rpm = deployment.get("rpm") + + # Check litellm_params + if tpm is None: + tpm = deployment.get("litellm_params", {}).get("tpm") + if rpm is None: + rpm = deployment.get("litellm_params", {}).get("rpm") + + # Check model_info + if tpm is None: + tpm = deployment.get("model_info", {}).get("tpm") + if rpm is None: + rpm = deployment.get("model_info", {}).get("rpm") + + return tpm, rpm + + def _get_cache_keys(self, deployment: Dict, current_minute: str) -> tuple[str, str]: + """Get the cache keys for TPM and RPM tracking.""" + model_id = deployment.get("model_info", {}).get("id") + deployment_name = deployment.get("litellm_params", {}).get("model") + + tpm_key = f"{model_id}:{deployment_name}:tpm:{current_minute}" + rpm_key = f"{model_id}:{deployment_name}:rpm:{current_minute}" + + return tpm_key, rpm_key + + def pre_call_check(self, deployment: Dict) -> Optional[Dict]: + """ + Synchronous pre-call check for model rate limits. + + Raises RateLimitError if deployment exceeds TPM/RPM limits. + """ + try: + tpm_limit, rpm_limit = self._get_deployment_limits(deployment) + + # If no limits are set, allow the request + if tpm_limit is None and rpm_limit is None: + return deployment + + dt = get_utc_datetime() + current_minute = dt.strftime("%H-%M") + tpm_key, rpm_key = self._get_cache_keys(deployment, current_minute) + + model_id = deployment.get("model_info", {}).get("id") + model_name = deployment.get("litellm_params", {}).get("model") + model_group = deployment.get("model_name", "") + + # Check TPM limit + if tpm_limit is not None: + # First check local cache + current_tpm = self.dual_cache.get_cache(key=tpm_key, local_only=True) + if current_tpm is not None and current_tpm >= tpm_limit: + raise litellm.RateLimitError( + message=f"Model rate limit exceeded. TPM limit={tpm_limit}, current usage={current_tpm}", + llm_provider="", + model=model_name, + response=httpx.Response( + status_code=429, + content=f"{RouterErrors.user_defined_ratelimit_error.value} tpm limit={tpm_limit}. current usage={current_tpm}. id={model_id}, model_group={model_group}", + headers={"retry-after": str(60)}, + request=httpx.Request( + method="model_rate_limit_check", + url="https://github.com/BerriAI/litellm", + ), + ), + ) + + # Check RPM limit + if rpm_limit is not None: + # First check local cache + current_rpm = self.dual_cache.get_cache(key=rpm_key, local_only=True) + if current_rpm >= rpm_limit: + raise litellm.RateLimitError( + message=f"Model rate limit exceeded. RPM limit={rpm_limit}, current usage={current_rpm}", + llm_provider="", + model=model_name, + response=httpx.Response( + status_code=429, + content=f"{RouterErrors.user_defined_ratelimit_error.value} rpm limit={rpm_limit}. current usage={current_rpm}. id={model_id}, model_group={model_group}", + headers={"retry-after": str(60)}, + request=httpx.Request( + method="model_rate_limit_check", + url="https://github.com/BerriAI/litellm", + ), + ), + ) + + # Check redis cache and increment + current_rpm = self.dual_cache.increment_cache( + key=rpm_key, value=1, ttl=RoutingArgs.ttl + ) + if current_rpm is not None and current_rpm > rpm_limit: + raise litellm.RateLimitError( + message=f"Model rate limit exceeded. RPM limit={rpm_limit}, current usage={current_rpm}", + llm_provider="", + model=model_name, + response=httpx.Response( + status_code=429, + content=f"{RouterErrors.user_defined_ratelimit_error.value} rpm limit={rpm_limit}. current usage={current_rpm}. id={model_id}, model_group={model_group}", + headers={"retry-after": str(60)}, + request=httpx.Request( + method="model_rate_limit_check", + url="https://github.com/BerriAI/litellm", + ), + ), + ) + + return deployment + + except litellm.RateLimitError: + raise + except Exception as e: + verbose_router_logger.debug( + f"Error in ModelRateLimitingCheck.pre_call_check: {str(e)}" + ) + # Don't fail the request if rate limit check fails + return deployment + + async def async_pre_call_check( + self, deployment: Dict, parent_otel_span: Optional[Span] = None + ) -> Optional[Dict]: + """ + Async pre-call check for model rate limits. + + Raises RateLimitError if deployment exceeds TPM/RPM limits. + """ + try: + tpm_limit, rpm_limit = self._get_deployment_limits(deployment) + + # If no limits are set, allow the request + if tpm_limit is None and rpm_limit is None: + return deployment + + dt = get_utc_datetime() + current_minute = dt.strftime("%H-%M") + tpm_key, rpm_key = self._get_cache_keys(deployment, current_minute) + + model_id = deployment.get("model_info", {}).get("id") + model_name = deployment.get("litellm_params", {}).get("model") + model_group = deployment.get("model_name", "") + + # Check TPM limit + if tpm_limit is not None: + # First check local cache + current_tpm = await self.dual_cache.async_get_cache( + key=tpm_key, local_only=True + ) + if current_tpm is not None and current_tpm >= tpm_limit: + raise litellm.RateLimitError( + message=f"Model rate limit exceeded. TPM limit={tpm_limit}, current usage={current_tpm}", + llm_provider="", + model=model_name, + response=httpx.Response( + status_code=429, + content=f"{RouterErrors.user_defined_ratelimit_error.value} tpm limit={tpm_limit}. current usage={current_tpm}. id={model_id}, model_group={model_group}", + headers={"retry-after": str(60)}, + request=httpx.Request( + method="model_rate_limit_check", + url="https://github.com/BerriAI/litellm", + ), + ), + num_retries=0, # Don't retry - return 429 immediately + ) + + # Check RPM limit + if rpm_limit is not None: + # First check local cache + current_rpm = await self.dual_cache.async_get_cache( + key=rpm_key, local_only=True + ) + if current_rpm is not None and current_rpm >= rpm_limit: + raise litellm.RateLimitError( + message=f"Model rate limit exceeded. RPM limit={rpm_limit}, current usage={current_rpm}", + llm_provider="", + model=model_name, + response=httpx.Response( + status_code=429, + content=f"{RouterErrors.user_defined_ratelimit_error.value} rpm limit={rpm_limit}. current usage={current_rpm}. id={model_id}, model_group={model_group}", + headers={"retry-after": str(60)}, + request=httpx.Request( + method="model_rate_limit_check", + url="https://github.com/BerriAI/litellm", + ), + ), + num_retries=0, # Don't retry - return 429 immediately + ) + + # Check redis cache and increment + current_rpm = await self.dual_cache.async_increment_cache( + key=rpm_key, + value=1, + ttl=RoutingArgs.ttl, + parent_otel_span=parent_otel_span, + ) + if current_rpm is not None and current_rpm > rpm_limit: + raise litellm.RateLimitError( + message=f"Model rate limit exceeded. RPM limit={rpm_limit}, current usage={current_rpm}", + llm_provider="", + model=model_name, + response=httpx.Response( + status_code=429, + content=f"{RouterErrors.user_defined_ratelimit_error.value} rpm limit={rpm_limit}. current usage={current_rpm}. id={model_id}, model_group={model_group}", + headers={"retry-after": str(60)}, + request=httpx.Request( + method="model_rate_limit_check", + url="https://github.com/BerriAI/litellm", + ), + ), + num_retries=0, # Don't retry - return 429 immediately + ) + + return deployment + + except litellm.RateLimitError: + raise + except Exception as e: + verbose_router_logger.debug( + f"Error in ModelRateLimitingCheck.async_pre_call_check: {str(e)}" + ) + # Don't fail the request if rate limit check fails + return deployment + + async def async_log_success_event(self, kwargs, response_obj, start_time, end_time): + """ + Track TPM usage after successful request. + + This updates the TPM counter with the actual tokens used. + Always tracks tokens - the pre-call check handles enforcement. + """ + try: + standard_logging_object: Optional[StandardLoggingPayload] = kwargs.get( + "standard_logging_object" + ) + if standard_logging_object is None: + return + + model_id = standard_logging_object.get("model_id") + if model_id is None: + return + + total_tokens = standard_logging_object.get("total_tokens", 0) + model = standard_logging_object.get("hidden_params", {}).get( + "litellm_model_name" + ) + + verbose_router_logger.debug( + f"[TPM TRACKING] model_id={model_id}, total_tokens={total_tokens}, model={model}" + ) + + if not model or not total_tokens: + return + + dt = get_utc_datetime() + current_minute = dt.strftime("%H-%M") + tpm_key = f"{model_id}:{model}:tpm:{current_minute}" + + verbose_router_logger.debug( + f"[TPM TRACKING] Incrementing {tpm_key} by {total_tokens}" + ) + + await self.dual_cache.async_increment_cache( + key=tpm_key, + value=total_tokens, + ttl=RoutingArgs.ttl, + ) + + except Exception as e: + verbose_router_logger.debug( + f"Error in ModelRateLimitingCheck.async_log_success_event: {str(e)}" + ) + + def log_success_event(self, kwargs, response_obj, start_time, end_time): + """ + Sync version of tracking TPM usage after successful request. + Always tracks tokens - the pre-call check handles enforcement. + """ + try: + standard_logging_object: Optional[StandardLoggingPayload] = kwargs.get( + "standard_logging_object" + ) + if standard_logging_object is None: + return + + model_id = standard_logging_object.get("model_id") + if model_id is None: + return + + total_tokens = standard_logging_object.get("total_tokens", 0) + model = standard_logging_object.get("hidden_params", {}).get( + "litellm_model_name" + ) + + if not model or not total_tokens: + return + + dt = get_utc_datetime() + current_minute = dt.strftime("%H-%M") + tpm_key = f"{model_id}:{model}:tpm:{current_minute}" + + self.dual_cache.increment_cache( + key=tpm_key, + value=total_tokens, + ttl=RoutingArgs.ttl, + ) + + except Exception as e: + verbose_router_logger.debug( + f"Error in ModelRateLimitingCheck.log_success_event: {str(e)}" + ) diff --git a/litellm/router_utils/search_api_router.py b/litellm/router_utils/search_api_router.py index 247df099462..9a1907fa559 100644 --- a/litellm/router_utils/search_api_router.py +++ b/litellm/router_utils/search_api_router.py @@ -127,8 +127,9 @@ class SearchAPIRouter: model=search_tool_name, kwargs=kwargs, metadata_variable_name="litellm_metadata" ) + available_search_tool_names = [tool.get("search_tool_name") for tool in router_instance.search_tools] verbose_router_logger.debug( - f"Inside SearchAPIRouter.async_search_with_fallbacks() - search_tool_name: {search_tool_name}; kwargs: {kwargs}" + f"Inside SearchAPIRouter.async_search_with_fallbacks() - search_tool_name: {search_tool_name}, Available Search Tools: {available_search_tool_names}, kwargs: {kwargs}" ) # Use the existing retry/fallback infrastructure diff --git a/litellm/secret_managers/main.py b/litellm/secret_managers/main.py index a093fe2d2fd..38405f058c3 100644 --- a/litellm/secret_managers/main.py +++ b/litellm/secret_managers/main.py @@ -17,6 +17,22 @@ from litellm.secret_managers.secret_manager_handler import get_secret_from_manag oidc_cache = DualCache() +def _get_oidc_http_handler(timeout: Optional[httpx.Timeout] = None) -> HTTPHandler: + """ + Factory function to create HTTPHandler for OIDC requests. + This function can be mocked in tests. + + Args: + timeout: Optional timeout for HTTP requests. Defaults to 600.0 seconds with 5.0 connect timeout. + + Returns: + HTTPHandler instance configured for OIDC requests. + """ + if timeout is None: + timeout = httpx.Timeout(timeout=600.0, connect=5.0) + return HTTPHandler(timeout=timeout) + + ######### Secret Manager ############################ # checks if user has passed in a secret manager client # if passed in then checks the secret there @@ -103,7 +119,7 @@ def get_secret( # noqa: PLR0915 if oidc_token is not None: return oidc_token - oidc_client = HTTPHandler(timeout=httpx.Timeout(timeout=600.0, connect=5.0)) + oidc_client = _get_oidc_http_handler() # https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature response = oidc_client.get( "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity", @@ -141,7 +157,7 @@ def get_secret( # noqa: PLR0915 if oidc_token is not None: return oidc_token - oidc_client = HTTPHandler(timeout=httpx.Timeout(timeout=600.0, connect=5.0)) + oidc_client = _get_oidc_http_handler() response = oidc_client.get( actions_id_token_request_url, params={"audience": oidc_aud}, diff --git a/litellm/types/guardrails.py b/litellm/types/guardrails.py index eea0a26b332..ca22049720e 100644 --- a/litellm/types/guardrails.py +++ b/litellm/types/guardrails.py @@ -20,6 +20,9 @@ from litellm.types.proxy.guardrails.guardrail_hooks.tool_permission import ( from litellm.types.proxy.guardrails.guardrail_hooks.qualifire import ( QualifireGuardrailConfigModel, ) +from litellm.types.proxy.guardrails.guardrail_hooks.litellm_content_filter import ( + ContentFilterCategoryConfig, +) """ Pydantic object defining how to set guardrails on litellm proxy @@ -547,9 +550,27 @@ class ContentFilterConfigModel(BaseModel): blocked_words_file: Optional[str] = Field( default=None, description="Path to YAML file containing blocked_words list" ) + categories: Optional[List[ContentFilterCategoryConfig]] = Field( + default=None, + description="List of prebuilt categories to enable (harmful_*, bias_*)", + ) + severity_threshold: Optional[str] = Field( + default=None, + description="Minimum severity to block (high, medium, low)", + ) + pattern_redaction_format: Optional[str] = Field( + default=None, + description="Format string for pattern redaction (use {pattern_name} placeholder)", + ) + keyword_redaction_tag: Optional[str] = Field( + default=None, + description="Tag to use for keyword redaction", + ) -class BaseLitellmParams(BaseModel): # works for new and patch update guardrails +class BaseLitellmParams( + ContentFilterConfigModel +): # works for new and patch update guardrails api_key: Optional[str] = Field( default=None, description="API key for the guardrail service" ) @@ -630,7 +651,6 @@ class BaseLitellmParams(BaseModel): # works for new and patch update guardrails description="Whether to fail the request if Model Armor encounters an error", ) - # Generic Guardrail API params additional_provider_specific_params: Optional[Dict[str, Any]] = Field( default=None, description="Additional provider-specific parameters for generic guardrail APIs", @@ -657,7 +677,6 @@ class LitellmParams( ToolPermissionGuardrailConfigModel, ZscalerAIGuardConfigModel, JavelinGuardrailConfigModel, - ContentFilterConfigModel, BaseLitellmParams, EnkryptAIGuardrailConfigs, IBMGuardrailsBaseConfigModel, diff --git a/litellm/types/integrations/datadog_cost_management.py b/litellm/types/integrations/datadog_cost_management.py new file mode 100644 index 00000000000..fe04f43ea03 --- /dev/null +++ b/litellm/types/integrations/datadog_cost_management.py @@ -0,0 +1,27 @@ +from typing import Dict, Optional, TypedDict + + +from litellm.types.integrations.custom_logger import StandardCustomLoggerInitParams + + +class DatadogCostManagementInitParams(StandardCustomLoggerInitParams): + """ + Init params for Datadog Cost Management + """ + + datadog_cost_management_params: Optional[Dict] = None + + +class DatadogFOCUSCostEntry(TypedDict): + """ + Represents a single cost line item in the FOCUS format. + Ref: https://focus.finops.org/#specification + """ + + ProviderName: str + ChargeDescription: str + ChargePeriodStart: str + ChargePeriodEnd: str + BilledCost: float + BillingCurrency: str + Tags: Optional[Dict[str, str]] diff --git a/litellm/types/integrations/prometheus.py b/litellm/types/integrations/prometheus.py index fd9b722287d..146c390363b 100644 --- a/litellm/types/integrations/prometheus.py +++ b/litellm/types/integrations/prometheus.py @@ -150,6 +150,9 @@ class UserAPIKeyLabelNames(Enum): FALLBACK_MODEL = "fallback_model" ROUTE = "route" MODEL_GROUP = "model_group" + CLIENT_IP = "client_ip" + USER_AGENT = "user_agent" + CALLBACK_NAME = "callback_name" DEFINED_PROMETHEUS_METRICS = Literal[ @@ -196,6 +199,12 @@ DEFINED_PROMETHEUS_METRICS = Literal[ "litellm_cache_hits_metric", "litellm_cache_misses_metric", "litellm_cached_tokens_metric", + "litellm_deployment_tpm_limit", + "litellm_deployment_rpm_limit", + "litellm_remaining_api_key_requests_for_model", + "litellm_remaining_api_key_tokens_for_model", + "litellm_llm_api_failed_requests_metric", + "litellm_callback_logging_failures_metric", ] @@ -209,6 +218,7 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.REQUESTED_MODEL.value, UserAPIKeyLabelNames.END_USER.value, UserAPIKeyLabelNames.USER.value, + UserAPIKeyLabelNames.MODEL_ID.value, ] litellm_llm_api_time_to_first_token_metric = [ @@ -217,6 +227,10 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.API_KEY_ALIAS.value, UserAPIKeyLabelNames.TEAM.value, UserAPIKeyLabelNames.TEAM_ALIAS.value, + UserAPIKeyLabelNames.REQUESTED_MODEL.value, + UserAPIKeyLabelNames.END_USER.value, + UserAPIKeyLabelNames.USER.value, + UserAPIKeyLabelNames.MODEL_ID.value, ] litellm_request_total_latency_metric = [ @@ -228,6 +242,7 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.TEAM_ALIAS.value, UserAPIKeyLabelNames.USER.value, UserAPIKeyLabelNames.v1_LITELLM_MODEL_NAME.value, + UserAPIKeyLabelNames.MODEL_ID.value, ] litellm_request_queue_time_seconds = [ @@ -239,6 +254,7 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.TEAM_ALIAS.value, UserAPIKeyLabelNames.USER.value, UserAPIKeyLabelNames.v1_LITELLM_MODEL_NAME.value, + UserAPIKeyLabelNames.MODEL_ID.value, ] # Guardrail metrics - these use custom labels (guardrail_name, status, error_type, hook_type) @@ -258,6 +274,9 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.STATUS_CODE.value, UserAPIKeyLabelNames.USER_EMAIL.value, UserAPIKeyLabelNames.ROUTE.value, + UserAPIKeyLabelNames.CLIENT_IP.value, + UserAPIKeyLabelNames.USER_AGENT.value, + UserAPIKeyLabelNames.MODEL_ID.value, ] litellm_proxy_failed_requests_metric = [ @@ -272,6 +291,9 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.EXCEPTION_STATUS.value, UserAPIKeyLabelNames.EXCEPTION_CLASS.value, UserAPIKeyLabelNames.ROUTE.value, + UserAPIKeyLabelNames.CLIENT_IP.value, + UserAPIKeyLabelNames.USER_AGENT.value, + UserAPIKeyLabelNames.MODEL_ID.value, ] litellm_deployment_latency_per_output_token = [ @@ -292,6 +314,7 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.v2_LITELLM_MODEL_NAME.value, UserAPIKeyLabelNames.API_KEY_HASH.value, UserAPIKeyLabelNames.API_KEY_ALIAS.value, + UserAPIKeyLabelNames.MODEL_ID.value, ] litellm_remaining_requests_metric = [ @@ -301,6 +324,7 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.v2_LITELLM_MODEL_NAME.value, UserAPIKeyLabelNames.API_KEY_HASH.value, UserAPIKeyLabelNames.API_KEY_ALIAS.value, + UserAPIKeyLabelNames.MODEL_ID.value, ] litellm_remaining_tokens_metric = [ @@ -310,6 +334,7 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.v2_LITELLM_MODEL_NAME.value, UserAPIKeyLabelNames.API_KEY_HASH.value, UserAPIKeyLabelNames.API_KEY_ALIAS.value, + UserAPIKeyLabelNames.MODEL_ID.value, ] litellm_requests_metric = [ @@ -321,6 +346,9 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.TEAM_ALIAS.value, UserAPIKeyLabelNames.USER.value, UserAPIKeyLabelNames.USER_EMAIL.value, + UserAPIKeyLabelNames.CLIENT_IP.value, + UserAPIKeyLabelNames.USER_AGENT.value, + UserAPIKeyLabelNames.MODEL_ID.value, ] litellm_spend_metric = [ @@ -332,6 +360,9 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.TEAM_ALIAS.value, UserAPIKeyLabelNames.USER.value, UserAPIKeyLabelNames.USER_EMAIL.value, + UserAPIKeyLabelNames.CLIENT_IP.value, + UserAPIKeyLabelNames.USER_AGENT.value, + UserAPIKeyLabelNames.MODEL_ID.value, ] litellm_input_tokens_metric = [ @@ -344,6 +375,7 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.USER.value, UserAPIKeyLabelNames.USER_EMAIL.value, UserAPIKeyLabelNames.REQUESTED_MODEL.value, + UserAPIKeyLabelNames.MODEL_ID.value, ] litellm_total_tokens_metric = [ @@ -356,6 +388,7 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.USER.value, UserAPIKeyLabelNames.USER_EMAIL.value, UserAPIKeyLabelNames.REQUESTED_MODEL.value, + UserAPIKeyLabelNames.MODEL_ID.value, ] litellm_output_tokens_metric = [ @@ -368,6 +401,7 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.USER.value, UserAPIKeyLabelNames.USER_EMAIL.value, UserAPIKeyLabelNames.REQUESTED_MODEL.value, + UserAPIKeyLabelNames.MODEL_ID.value, ] litellm_deployment_state = [ @@ -377,6 +411,15 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.API_PROVIDER.value, ] + litellm_deployment_tpm_limit = [ + UserAPIKeyLabelNames.v2_LITELLM_MODEL_NAME.value, + UserAPIKeyLabelNames.MODEL_ID.value, + UserAPIKeyLabelNames.API_BASE.value, + UserAPIKeyLabelNames.API_PROVIDER.value, + ] + + litellm_deployment_rpm_limit = litellm_deployment_tpm_limit + litellm_deployment_cooled_down = [ UserAPIKeyLabelNames.v2_LITELLM_MODEL_NAME.value, UserAPIKeyLabelNames.MODEL_ID.value, @@ -394,6 +437,7 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.TEAM_ALIAS.value, UserAPIKeyLabelNames.EXCEPTION_STATUS.value, UserAPIKeyLabelNames.EXCEPTION_CLASS.value, + UserAPIKeyLabelNames.MODEL_ID.value, ] litellm_deployment_failed_fallbacks = litellm_deployment_successful_fallbacks @@ -436,6 +480,26 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.USER.value, ] + litellm_user_budget_remaining_hours_metric = [ + UserAPIKeyLabelNames.USER.value, + ] + + litellm_remaining_api_key_requests_for_model = [ + UserAPIKeyLabelNames.API_KEY_HASH.value, + UserAPIKeyLabelNames.API_KEY_ALIAS.value, + UserAPIKeyLabelNames.v1_LITELLM_MODEL_NAME.value, + ] + + litellm_remaining_api_key_tokens_for_model = [ + UserAPIKeyLabelNames.API_KEY_HASH.value, + UserAPIKeyLabelNames.API_KEY_ALIAS.value, + UserAPIKeyLabelNames.v1_LITELLM_MODEL_NAME.value, + ] + + litellm_callback_logging_failures_metric = [ + UserAPIKeyLabelNames.CALLBACK_NAME.value, + ] + # Add deployment metrics litellm_deployment_failure_responses = [ UserAPIKeyLabelNames.REQUESTED_MODEL.value, @@ -449,6 +513,8 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.API_KEY_ALIAS.value, UserAPIKeyLabelNames.TEAM.value, UserAPIKeyLabelNames.TEAM_ALIAS.value, + UserAPIKeyLabelNames.CLIENT_IP.value, + UserAPIKeyLabelNames.USER_AGENT.value, ] litellm_deployment_total_requests = [ @@ -461,10 +527,37 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.API_KEY_ALIAS.value, UserAPIKeyLabelNames.TEAM.value, UserAPIKeyLabelNames.TEAM_ALIAS.value, + UserAPIKeyLabelNames.CLIENT_IP.value, + UserAPIKeyLabelNames.USER_AGENT.value, ] litellm_deployment_success_responses = litellm_deployment_total_requests + litellm_remaining_api_key_requests_for_model = [ + UserAPIKeyLabelNames.API_KEY_HASH.value, + UserAPIKeyLabelNames.API_KEY_ALIAS.value, + UserAPIKeyLabelNames.v1_LITELLM_MODEL_NAME.value, + UserAPIKeyLabelNames.MODEL_ID.value, + ] + + litellm_remaining_api_key_tokens_for_model = [ + UserAPIKeyLabelNames.API_KEY_HASH.value, + UserAPIKeyLabelNames.API_KEY_ALIAS.value, + UserAPIKeyLabelNames.v1_LITELLM_MODEL_NAME.value, + UserAPIKeyLabelNames.MODEL_ID.value, + ] + + litellm_llm_api_failed_requests_metric = [ + UserAPIKeyLabelNames.END_USER.value, + UserAPIKeyLabelNames.API_KEY_HASH.value, + UserAPIKeyLabelNames.API_KEY_ALIAS.value, + UserAPIKeyLabelNames.v1_LITELLM_MODEL_NAME.value, + UserAPIKeyLabelNames.TEAM.value, + UserAPIKeyLabelNames.TEAM_ALIAS.value, + UserAPIKeyLabelNames.USER.value, + UserAPIKeyLabelNames.MODEL_ID.value, + ] + # Buffer monitoring metrics - these typically don't need additional labels litellm_pod_lock_manager_size: List[str] = [] @@ -485,6 +578,7 @@ class PrometheusMetricLabels: UserAPIKeyLabelNames.TEAM_ALIAS.value, UserAPIKeyLabelNames.END_USER.value, UserAPIKeyLabelNames.USER.value, + UserAPIKeyLabelNames.MODEL_ID.value, ] litellm_cache_hits_metric = _cache_metric_labels @@ -577,6 +671,12 @@ class UserAPIKeyLabelValues(BaseModel): route: Annotated[ Optional[str], Field(..., alias=UserAPIKeyLabelNames.ROUTE.value) ] = None + client_ip: Annotated[ + Optional[str], Field(..., alias=UserAPIKeyLabelNames.CLIENT_IP.value) + ] = None + user_agent: Annotated[ + Optional[str], Field(..., alias=UserAPIKeyLabelNames.USER_AGENT.value) + ] = None class PrometheusMetricsConfig(BaseModel): diff --git a/litellm/types/llms/anthropic.py b/litellm/types/llms/anthropic.py index 8d18322d374..62e775d4faa 100644 --- a/litellm/types/llms/anthropic.py +++ b/litellm/types/llms/anthropic.py @@ -296,9 +296,9 @@ class AnthropicMessagesDocumentParam(TypedDict, total=False): citations: Optional[CitationsObject] -class AnthropicMessagesToolResultContent(TypedDict): - type: Literal["text"] - text: str +class AnthropicMessagesToolResultContent(TypedDict, total=False): + type: Required[Literal["text"]] + text: Required[str] cache_control: Optional[Union[dict, ChatCompletionCachedContent]] @@ -647,4 +647,4 @@ ANTHROPIC_EFFORT_BETA_HEADER = "effort-2025-11-24" ANTHROPIC_OAUTH_TOKEN_PREFIX = "sk-ant-oat" ANTHROPIC_OAUTH_BETA_HEADER = "oauth-2025-04-20" - +ANTHROPIC_PROMPT_CACHING_SCOPE_BETA_HEADER = "prompt-caching-scope-2026-01-05" \ No newline at end of file diff --git a/litellm/types/llms/bedrock.py b/litellm/types/llms/bedrock.py index ef2f1ba4d5e..6293efe9e09 100644 --- a/litellm/types/llms/bedrock.py +++ b/litellm/types/llms/bedrock.py @@ -93,6 +93,67 @@ class GuardrailConverseContentBlock(TypedDict, total=False): text: GuardrailConverseTextBlock +class CitationWebLocationBlock(TypedDict, total=False): + """ + Web location block for Nova grounding citations. + Contains the URL and domain from web search results. + + Reference: https://docs.aws.amazon.com/nova/latest/userguide/grounding.html + """ + + url: str + domain: str + + +class CitationLocationBlock(TypedDict, total=False): + """ + Location block containing the web location for a citation. + """ + + web: CitationWebLocationBlock + + +class CitationReferenceBlock(TypedDict, total=False): + """ + Citation reference block containing a single citation with its location. + + Each citation contains: + - location.web.url: The URL of the source + - location.web.domain: The domain of the source + """ + + location: CitationLocationBlock + + +class CitationsContentBlock(TypedDict, total=False): + """ + Citations content block returned by Nova grounding (web search) tool. + + When Nova grounding is enabled via systemTool, the model may return + citationsContent blocks containing web search citation references. + + Reference: https://docs.aws.amazon.com/nova/latest/userguide/grounding.html + + Example response structure: + { + "citationsContent": { + "citations": [ + { + "location": { + "web": { + "url": "https://example.com/article", + "domain": "example.com" + } + } + } + ] + } + } + """ + + citations: List[CitationReferenceBlock] + + class ContentBlock(TypedDict, total=False): text: str image: ImageBlock @@ -103,6 +164,7 @@ class ContentBlock(TypedDict, total=False): cachePoint: CachePointBlock reasoningContent: BedrockConverseReasoningContentBlock guardContent: GuardrailConverseContentBlock + citationsContent: CitationsContentBlock class MessageBlock(TypedDict): @@ -159,8 +221,24 @@ class ToolSpecBlock(TypedDict, total=False): description: str +class SystemToolBlock(TypedDict, total=False): + """ + System tool block for Nova grounding and other built-in tools. + + Example: + { + "systemTool": { + "name": "nova_grounding" + } + } + """ + + name: Required[str] + + class ToolBlock(TypedDict, total=False): toolSpec: Optional[ToolSpecBlock] + systemTool: Optional[SystemToolBlock] cachePoint: Optional[CachePointBlock] @@ -210,11 +288,13 @@ class ContentBlockStartEvent(TypedDict, total=False): class ContentBlockDeltaEvent(TypedDict, total=False): """ Either 'text' or 'toolUse' will be specified for Converse API streaming response. + May also include 'citationsContent' when Nova grounding is enabled. """ text: str toolUse: ToolBlockDeltaEvent reasoningContent: BedrockConverseReasoningContentBlockDelta + citationsContent: CitationsContentBlock class PerformanceConfigBlock(TypedDict): @@ -317,6 +397,7 @@ class CohereEmbeddingRequest(TypedDict, total=False): input_type: Required[COHERE_EMBEDDING_INPUT_TYPES] truncate: Literal["NONE", "START", "END"] embedding_types: Literal["float", "int8", "uint8", "binary", "ubinary"] + output_dimension: int class CohereEmbeddingRequestWithModel(CohereEmbeddingRequest): @@ -879,3 +960,8 @@ class BedrockGetBatchResponse(TypedDict, total=False): outputDataConfig: BedrockOutputDataConfig timeoutDurationInHours: Optional[int] clientRequestToken: Optional[str] + +class BedrockToolBlock(TypedDict, total=False): + toolSpec: Optional[ToolSpecBlock] + systemTool: Optional[SystemToolBlock] # For Nova grounding + cachePoint: Optional[CachePointBlock] diff --git a/litellm/types/llms/custom_http.py b/litellm/types/llms/custom_http.py index ca348bad97c..32f3dc2efaf 100644 --- a/litellm/types/llms/custom_http.py +++ b/litellm/types/llms/custom_http.py @@ -26,6 +26,7 @@ class httpxSpecialProvider(str, Enum): RAG = "rag" A2A = "a2a" PromptManagement = "prompt_management" + UI = "ui" VerifyTypes = Union[str, bool, ssl.SSLContext] diff --git a/litellm/types/llms/oci.py b/litellm/types/llms/oci.py index b9a82cc8b73..9a654bc0f6c 100644 --- a/litellm/types/llms/oci.py +++ b/litellm/types/llms/oci.py @@ -35,11 +35,18 @@ class OCITextContentPart(OCIContentPart): text: str +class OCIImageUrl(BaseModel): + """ImageUrl object for OCI API. See: https://docs.oracle.com/en-us/iaas/tools/python/latest/api/generative_ai_inference/models/oci.generative_ai_inference.models.ImageUrl.html""" + + url: str + detail: Optional[Literal["AUTO", "HIGH", "LOW"]] = None + + class OCIImageContentPart(OCIContentPart): """Image content part for the OCI API.""" type: Literal["IMAGE"] = "IMAGE" - imageUrl: str + imageUrl: OCIImageUrl OCIContentPartUnion = Union[OCITextContentPart, OCIImageContentPart] diff --git a/litellm/types/llms/openai.py b/litellm/types/llms/openai.py index 9fea1a8b937..299b47199ed 100644 --- a/litellm/types/llms/openai.py +++ b/litellm/types/llms/openai.py @@ -1114,7 +1114,7 @@ class ResponsesAPIRequestParams(ResponsesAPIOptionalRequestParams, total=False): class OutputTokensDetails(BaseLiteLLMOpenAIResponseObject): - reasoning_tokens: Optional[int] = None + reasoning_tokens: int = 0 text_tokens: Optional[int] = None @@ -1123,7 +1123,7 @@ class OutputTokensDetails(BaseLiteLLMOpenAIResponseObject): class InputTokensDetails(BaseLiteLLMOpenAIResponseObject): audio_tokens: Optional[int] = None - cached_tokens: Optional[int] = None + cached_tokens: int = 0 text_tokens: Optional[int] = None model_config = {"extra": "allow"} @@ -1193,7 +1193,7 @@ class ResponsesAPIResponse(BaseLiteLLMOpenAIResponseObject): status: Optional[str] = None text: Optional[Union["ResponseText", Dict[str, Any]]] = None truncation: Optional[Literal["auto", "disabled"]] = None - usage: Optional[Any] = None + usage: Optional[ResponseAPIUsage] = None user: Optional[str] = None store: Optional[bool] = None # Define private attributes using PrivateAttr diff --git a/litellm/types/llms/xai.py b/litellm/types/llms/xai.py new file mode 100644 index 00000000000..9de8dfc7add --- /dev/null +++ b/litellm/types/llms/xai.py @@ -0,0 +1,23 @@ +from typing import List, Literal, Optional, TypedDict + + +class XAIWebSearchFilters(TypedDict, total=False): + """Filters for XAI web search tool""" + allowed_domains: Optional[List[str]] # Max 5 domains + excluded_domains: Optional[List[str]] # Max 5 domains + +class XAIWebSearchTool(TypedDict, total=False): + """XAI web search tool configuration""" + type: Literal["web_search"] + filters: Optional[XAIWebSearchFilters] + enable_image_understanding: Optional[bool] + +class XAIXSearchTool(TypedDict, total=False): + """XAI X (Twitter) search tool configuration""" + type: Literal["x_search"] + allowed_x_handles: Optional[List[str]] # Max 10 handles + excluded_x_handles: Optional[List[str]] # Max 10 handles + from_date: Optional[str] # ISO8601 format: YYYY-MM-DD + to_date: Optional[str] # ISO8601 format: YYYY-MM-DD + enable_image_understanding: Optional[bool] + enable_video_understanding: Optional[bool] \ No newline at end of file diff --git a/litellm/types/proxy/guardrails/guardrail_hooks/onyx.py b/litellm/types/proxy/guardrails/guardrail_hooks/onyx.py index aa5b9d7a3fc..42d7e94829f 100644 --- a/litellm/types/proxy/guardrails/guardrail_hooks/onyx.py +++ b/litellm/types/proxy/guardrails/guardrail_hooks/onyx.py @@ -16,6 +16,11 @@ class OnyxGuardrailConfigModel(GuardrailConfigModel): description="The API key for the Onyx Guard server. If not provided, the `ONYX_API_KEY` environment variable is checked.", ) + timeout: Optional[float] = Field( + default=None, + description="The timeout for the Onyx Guard server in seconds. If not provided, the `ONYX_TIMEOUT` environment variable is checked.", + ) + @staticmethod def ui_friendly_name() -> str: return "Onyx Guardrail" diff --git a/litellm/types/proxy/management_endpoints/key_management_endpoints.py b/litellm/types/proxy/management_endpoints/key_management_endpoints.py new file mode 100644 index 00000000000..b1d25455d18 --- /dev/null +++ b/litellm/types/proxy/management_endpoints/key_management_endpoints.py @@ -0,0 +1,42 @@ +from typing import Any, Dict, List, Optional + +from pydantic import BaseModel + + +class BulkUpdateKeyRequestItem(BaseModel): + """Individual key update request item""" + + key: str # Key identifier (token) + budget_id: Optional[str] = None # Budget ID associated with the key + max_budget: Optional[float] = None # Max budget for key + team_id: Optional[str] = None # Team ID associated with key + tags: Optional[List[str]] = None # Tags for organizing keys + + +class BulkUpdateKeyRequest(BaseModel): + """Request for bulk key updates""" + + keys: List[BulkUpdateKeyRequestItem] + + +class SuccessfulKeyUpdate(BaseModel): + """Successfully updated key with its updated information""" + + key: str + key_info: Dict[str, Any] + + +class FailedKeyUpdate(BaseModel): + """Failed key update with reason""" + + key: str + key_info: Optional[Dict[str, Any]] = None + failed_reason: str + + +class BulkUpdateKeyResponse(BaseModel): + """Response for bulk key update operations""" + + total_requested: int + successful_updates: List[SuccessfulKeyUpdate] + failed_updates: List[FailedKeyUpdate] diff --git a/litellm/types/proxy/management_endpoints/scim_v2.py b/litellm/types/proxy/management_endpoints/scim_v2.py index bff9f0b876c..c4d95d99ed4 100644 --- a/litellm/types/proxy/management_endpoints/scim_v2.py +++ b/litellm/types/proxy/management_endpoints/scim_v2.py @@ -1,7 +1,7 @@ from typing import Any, Dict, List, Literal, Optional, Union from fastapi import HTTPException -from pydantic import BaseModel, EmailStr, field_validator +from pydantic import BaseModel, ConfigDict, EmailStr, field_validator class LiteLLM_UserScimMetadata(BaseModel): @@ -112,3 +112,67 @@ class SCIMServiceProviderConfig(BaseModel): etag: SCIMFeature = SCIMFeature(supported=False) authenticationSchemes: Optional[List[Dict[str, Any]]] = None meta: Optional[Dict[str, Any]] = None + + +# SCIM ResourceType Models (RFC 7643 Section 6) +class SCIMSchemaExtension(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + schema_: str # aliased to "schema" in serialization + required: bool + + def model_dump(self, **kwargs): + d = super().model_dump(**kwargs) + d["schema"] = d.pop("schema_") + return d + + +class SCIMResourceType(BaseModel): + model_config = ConfigDict(populate_by_name=True) + + schemas: List[str] = [ + "urn:ietf:params:scim:schemas:core:2.0:ResourceType" + ] + id: str + name: str + description: Optional[str] = None + endpoint: str + schema_: str # "schema" is a reserved name in Pydantic context + + schemaExtensions: Optional[List[SCIMSchemaExtension]] = None + meta: Optional[Dict[str, Any]] = None + + def model_dump(self, **kwargs): + d = super().model_dump(**kwargs) + d["schema"] = d.pop("schema_") + if d.get("schemaExtensions") is None: + d.pop("schemaExtensions", None) + return d + + +# SCIM Schema Models (RFC 7643 Section 7) +class SCIMSchemaAttribute(BaseModel): + name: str + type: str + multiValued: bool = False + description: Optional[str] = None + required: bool = False + mutability: str = "readWrite" + returned: str = "default" + uniqueness: str = "none" + subAttributes: Optional[List["SCIMSchemaAttribute"]] = None + + def model_dump(self, **kwargs): + d = super().model_dump(**kwargs) + if d.get("subAttributes") is None: + d.pop("subAttributes", None) + return d + + +class SCIMSchema(BaseModel): + schemas: List[str] = ["urn:ietf:params:scim:schemas:core:2.0:Schema"] + id: str + name: str + description: Optional[str] = None + attributes: List[SCIMSchemaAttribute] = [] + meta: Optional[Dict[str, Any]] = None diff --git a/litellm/types/proxy/management_endpoints/ui_sso.py b/litellm/types/proxy/management_endpoints/ui_sso.py index c9d998f6a92..6743c4a5b9b 100644 --- a/litellm/types/proxy/management_endpoints/ui_sso.py +++ b/litellm/types/proxy/management_endpoints/ui_sso.py @@ -86,6 +86,20 @@ class RoleMappings(LiteLLMPydanticObjectBase): ) +class TeamMappings(LiteLLMPydanticObjectBase): + """ + Configuration for mapping SSO JWT fields to team IDs. + + This allows configuring team_ids_jwt_field via the database instead of + requiring config file changes and restarts. + """ + + team_ids_jwt_field: Optional[str] = Field( + default=None, + description="The field name in the SSO/JWT token that contains the team IDs array (e.g., 'groups', 'teams'). Supports dot notation for nested fields.", + ) + + class SSOConfig(LiteLLMPydanticObjectBase): """ Configuration for SSO environment variables and settings @@ -159,6 +173,12 @@ class SSOConfig(LiteLLMPydanticObjectBase): description="Configuration for mapping SSO groups to LiteLLM roles based on group claims in the SSO token", ) + # Team Mappings + team_mappings: Optional[TeamMappings] = Field( + default=None, + description="Configuration for mapping SSO JWT fields to team IDs. Takes precedence over config file settings.", + ) + class DefaultTeamSSOParams(LiteLLMPydanticObjectBase): """ diff --git a/litellm/types/rag.py b/litellm/types/rag.py index fe237a13431..cae07708686 100644 --- a/litellm/types/rag.py +++ b/litellm/types/rag.py @@ -129,9 +129,53 @@ class VertexAIVectorStoreOptions(TypedDict, total=False): import_timeout: Optional[int] # Timeout in seconds (default: 600) +class S3VectorsVectorStoreOptions(TypedDict, total=False): + """ + AWS S3 Vectors configuration. + + Example (auto-create): + {"custom_llm_provider": "s3_vectors", "vector_bucket_name": "my-embeddings"} + + Example (use existing): + {"custom_llm_provider": "s3_vectors", "vector_bucket_name": "my-embeddings", + "index_name": "my-index"} + + Example (with credentials): + {"custom_llm_provider": "s3_vectors", "vector_bucket_name": "my-embeddings", + "litellm_credential_name": "my-aws-creds"} + + Auto-creation creates: S3 vector bucket and vector index (if not provided). + Embeddings are generated using LiteLLM's embedding API (supports any provider). + """ + + custom_llm_provider: Literal["s3_vectors"] + vector_bucket_name: str # Required - S3 vector bucket name + index_name: Optional[str] # Vector index name (auto-creates if not provided) + + # Index configuration (for auto-creation) + dimension: Optional[int] # Vector dimension (auto-detected from embedding model, or default: 1024) + distance_metric: Optional[Literal["cosine", "euclidean"]] # Default: cosine + non_filterable_metadata_keys: Optional[List[str]] # Keys excluded from filtering (e.g., ["source_text"]) + + # Credentials (loaded from litellm.credential_list if litellm_credential_name is provided) + litellm_credential_name: Optional[str] # Credential name to load from litellm.credential_list + + # AWS auth (uses BaseAWSLLM) + aws_access_key_id: Optional[str] + aws_secret_access_key: Optional[str] + aws_session_token: Optional[str] + aws_region_name: Optional[str] # default: us-west-2 + aws_role_name: Optional[str] + aws_session_name: Optional[str] + aws_profile_name: Optional[str] + aws_web_identity_token: Optional[str] + aws_sts_endpoint: Optional[str] + aws_external_id: Optional[str] + + # Union type for vector store options RAGIngestVectorStoreOptions = Union[ - OpenAIVectorStoreOptions, BedrockVectorStoreOptions, VertexAIVectorStoreOptions + OpenAIVectorStoreOptions, BedrockVectorStoreOptions, VertexAIVectorStoreOptions, S3VectorsVectorStoreOptions ] diff --git a/litellm/types/router.py b/litellm/types/router.py index 8ea7a207535..f78789c9772 100644 --- a/litellm/types/router.py +++ b/litellm/types/router.py @@ -95,18 +95,16 @@ class ModelInfo(BaseModel): id: Optional[ str ] # Allow id to be optional on input, but it will always be present as a str in the model instance - db_model: bool = ( - False # used for proxy - to separate models which are stored in the db vs. config. - ) + db_model: bool = False # used for proxy - to separate models which are stored in the db vs. config. updated_at: Optional[datetime.datetime] = None updated_by: Optional[str] = None created_at: Optional[datetime.datetime] = None created_by: Optional[str] = None - base_model: Optional[str] = ( - None # specify if the base model is azure/gpt-3.5-turbo etc for accurate cost tracking - ) + base_model: Optional[ + str + ] = None # specify if the base model is azure/gpt-3.5-turbo etc for accurate cost tracking tier: Optional[Literal["free", "paid"]] = None """ @@ -172,12 +170,12 @@ class GenericLiteLLMParams(CredentialLiteLLMParams, CustomPricingLiteLLMParams): custom_llm_provider: Optional[str] = None tpm: Optional[int] = None rpm: Optional[int] = None - timeout: Optional[Union[float, str, httpx.Timeout]] = ( - None # if str, pass in as os.environ/ - ) - stream_timeout: Optional[Union[float, str]] = ( - None # timeout when making stream=True calls, if str, pass in as os.environ/ - ) + timeout: Optional[ + Union[float, str, httpx.Timeout] + ] = None # if str, pass in as os.environ/ + stream_timeout: Optional[ + Union[float, str] + ] = None # timeout when making stream=True calls, if str, pass in as os.environ/ max_retries: Optional[int] = None organization: Optional[str] = None # for openai orgs configurable_clientside_auth_params: CONFIGURABLE_CLIENTSIDE_AUTH_PARAMS = None @@ -276,9 +274,9 @@ class GenericLiteLLMParams(CredentialLiteLLMParams, CustomPricingLiteLLMParams): if max_retries is not None and isinstance(max_retries, str): max_retries = int(max_retries) # cast to int # We need to keep max_retries in args since it's a parameter of GenericLiteLLMParams - args["max_retries"] = ( - max_retries # Put max_retries back in args after popping it - ) + args[ + "max_retries" + ] = max_retries # Put max_retries back in args after popping it super().__init__(**args, **params) def __contains__(self, key): @@ -404,6 +402,10 @@ class LiteLLMParamsTypedDict(TypedDict, total=False): aws_access_key_id: Optional[str] aws_secret_access_key: Optional[str] aws_region_name: Optional[str] + ## AWS S3 VECTORS ## + vector_bucket_name: Optional[str] + index_name: Optional[str] + embedding_model: Optional[str] ## IBM WATSONX ## watsonx_region_name: Optional[str] ## CUSTOM PRICING ## @@ -619,7 +621,13 @@ class SearchToolLiteLLMParams(TypedDict, total=False): max_retries: Optional[int] -class SearchToolTypedDict(TypedDict): +class SearchToolInfoTypedDict(TypedDict, total=False): + """Optional metadata about a search tool.""" + + description: str + + +class SearchToolTypedDict(TypedDict, total=False): """ Configuration for a search tool in the router. @@ -635,6 +643,7 @@ class SearchToolTypedDict(TypedDict): search_tool_name: Required[str] litellm_params: Required[SearchToolLiteLLMParams] + search_tool_info: SearchToolInfoTypedDict class GuardrailLiteLLMParams(TypedDict, total=False): @@ -794,6 +803,7 @@ OptionalPreCallChecks = List[ "router_budget_limiting", "responses_api_deployment_check", "forward_client_headers_by_model_group", + "enforce_model_rate_limits", ] ] diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 0ec79e90a1f..09c944e1fe8 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -3,25 +3,28 @@ import time from enum import Enum from typing import TYPE_CHECKING, Any, Dict, List, Literal, Mapping, Optional, Union -from aiohttp import FormData from openai._models import BaseModel as OpenAIObject -from openai.types.audio.transcription_create_params import FileTypes # type: ignore -from openai.types.chat.chat_completion import ChatCompletion +from openai.types.audio.transcription_create_params import ( + FileTypes as FileTypes, # type: ignore +) +from openai.types.chat.chat_completion import ChatCompletion as ChatCompletion from openai.types.completion_usage import ( CompletionTokensDetails, CompletionUsage, PromptTokensDetails, ) +from openai.types.moderation import Categories as Categories from openai.types.moderation import ( - Categories, - CategoryAppliedInputTypes, - CategoryScores, + CategoryAppliedInputTypes as CategoryAppliedInputTypes, +) +from openai.types.moderation import CategoryScores as CategoryScores +from openai.types.moderation_create_response import Moderation as Moderation +from openai.types.moderation_create_response import ( + ModerationCreateResponse as ModerationCreateResponse, ) -from openai.types.moderation_create_response import Moderation, ModerationCreateResponse from pydantic import BaseModel, ConfigDict, Field, PrivateAttr, model_validator -from typing_extensions import Callable, Dict, Required, TypedDict, override +from typing_extensions import Required, TypedDict -import litellm from litellm._uuid import uuid from litellm.types.llms.base import ( BaseLiteLLMOpenAIResponseObject, @@ -52,7 +55,7 @@ from .llms.openai import ( ResponsesAPIResponse, WebSearchOptions, ) -from .rerank import RerankResponse +from .rerank import RerankResponse as RerankResponse if TYPE_CHECKING: from .vector_stores import VectorStoreSearchResponse @@ -273,6 +276,8 @@ class CallTypes(str, Enum): acreate_batch = "acreate_batch" aretrieve_batch = "aretrieve_batch" retrieve_batch = "retrieve_batch" + acancel_batch = "acancel_batch" + cancel_batch = "cancel_batch" pass_through = "pass_through_endpoint" anthropic_messages = "anthropic_messages" get_assistants = "get_assistants" @@ -1411,7 +1416,7 @@ class Usage(SafeAttributeModel, CompletionUsage): prompt_tokens_details: Optional[PromptTokensDetailsWrapper] = None """Breakdown of tokens used in the prompt.""" - def __init__( + def __init__( # noqa: PLR0915 self, prompt_tokens: Optional[int] = None, completion_tokens: Optional[int] = None, @@ -2124,6 +2129,7 @@ class ImageObject(OpenAIImage): b64_json: The base64-encoded JSON of the generated image, if response_format is b64_json. url: The URL of the generated image, if response_format is url (default). revised_prompt: The prompt that was used to generate the image, if there was any revision to the prompt. + provider_specific_fields: Provider-specific fields not part of OpenAI spec. https://platform.openai.com/docs/api-reference/images/object """ @@ -2131,9 +2137,12 @@ class ImageObject(OpenAIImage): b64_json: Optional[str] = None url: Optional[str] = None revised_prompt: Optional[str] = None + provider_specific_fields: Optional[Dict[str, Any]] = None - def __init__(self, b64_json=None, url=None, revised_prompt=None, **kwargs): + def __init__(self, b64_json=None, url=None, revised_prompt=None, provider_specific_fields=None, **kwargs): super().__init__(b64_json=b64_json, url=url, revised_prompt=revised_prompt) # type: ignore + if provider_specific_fields: + self.provider_specific_fields = provider_specific_fields def __contains__(self, key): # Define custom behavior for the 'in' operator @@ -2501,6 +2510,7 @@ class StandardLoggingMetadata(StandardLoggingUserAPIKeyMetadata): dict ] # special param to log k,v pairs to spendlogs for a call requester_ip_address: Optional[str] + user_agent: Optional[str] requester_metadata: Optional[dict] requester_custom_headers: Optional[ Dict[str, str] @@ -2631,6 +2641,7 @@ class CostBreakdown(TypedDict, total=False): ) total_cost: float # Total cost (input + output + tool usage) tool_usage_cost: float # Cost of usage of built-in tools + additional_costs: Dict[str, float] # Free-form additional costs (e.g., {"azure_model_router_flat_cost": 0.00014}) original_cost: float # Cost before discount (optional) discount_percent: float # Discount percentage applied (e.g., 0.05 = 5%) (optional) discount_amount: float # Discount amount in USD (optional) @@ -2686,6 +2697,7 @@ class StandardLoggingPayload(TypedDict): request_tags: list end_user: Optional[str] requester_ip_address: Optional[str] + user_agent: Optional[str] messages: Optional[Union[str, list, dict]] response: Optional[Union[str, list, dict]] error_str: Optional[str] @@ -3072,6 +3084,7 @@ class LlmProviders(str, Enum): LLAMA = "meta_llama" NSCALE = "nscale" PG_VECTOR = "pg_vector" + S3_VECTORS = "s3_vectors" HELICONE = "helicone" HYPERBOLIC = "hyperbolic" RECRAFT = "recraft" diff --git a/litellm/types/vector_stores.py b/litellm/types/vector_stores.py index a4ceb2c9ac7..58a6a3272cd 100644 --- a/litellm/types/vector_stores.py +++ b/litellm/types/vector_stores.py @@ -38,6 +38,10 @@ class LiteLLM_ManagedVectorStore(TypedDict, total=False): # litellm_params litellm_params: Optional[Dict[str, Any]] + + # access control fields + team_id: Optional[str] + user_id: Optional[str] class LiteLLM_ManagedVectorStoreListResponse(TypedDict, total=False): diff --git a/litellm/utils.py b/litellm/utils.py index 1865d364586..f3d14b455cd 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -199,6 +199,8 @@ from litellm.types.utils import ( all_litellm_params, ) +_CALL_TYPE_ENUM_MAP: dict = {ct.value: ct for ct in CallTypes} + # +-----------------------------------------------+ # | | # | Give Feedback / Get Help | @@ -771,13 +773,15 @@ def function_setup( # noqa: PLR0915 function_id: Optional[str] = kwargs["id"] if "id" in kwargs else None ## LAZY LOAD COROUTINE CHECKER ## - get_coroutine_checker_fn = getattr(sys.modules[__name__], "get_coroutine_checker") + get_coroutine_checker_fn = getattr( + sys.modules[__name__], "get_coroutine_checker" + ) coroutine_checker = get_coroutine_checker_fn() ## DYNAMIC CALLBACKS ## - dynamic_callbacks: Optional[ - List[Union[str, Callable, "CustomLogger"]] - ] = kwargs.pop("callbacks", None) + dynamic_callbacks: Optional[List[Union[str, Callable, "CustomLogger"]]] = ( + kwargs.pop("callbacks", None) + ) all_callbacks = get_dynamic_callbacks(dynamic_callbacks=dynamic_callbacks) if len(all_callbacks) > 0: @@ -1660,9 +1664,9 @@ def client(original_function): # noqa: PLR0915 exception=e, retry_policy=kwargs.get("retry_policy"), ) - kwargs[ - "retry_policy" - ] = reset_retry_policy() # prevent infinite loops + kwargs["retry_policy"] = ( + reset_retry_policy() + ) # prevent infinite loops litellm.num_retries = ( None # set retries to None to prevent infinite loops ) @@ -1709,9 +1713,9 @@ def client(original_function): # noqa: PLR0915 exception=e, retry_policy=kwargs.get("retry_policy"), ) - kwargs[ - "retry_policy" - ] = reset_retry_policy() # prevent infinite loops + kwargs["retry_policy"] = ( + reset_retry_policy() + ) # prevent infinite loops litellm.num_retries = ( None # set retries to None to prevent infinite loops ) @@ -1744,6 +1748,7 @@ def client(original_function): # noqa: PLR0915 print_args_passed_to_litellm(original_function, args, kwargs) start_time = datetime.datetime.now() result = None + _update_response_metadata = getattr(sys.modules[__name__], "update_response_metadata") logging_obj: Optional[LiteLLMLoggingObject] = kwargs.get( "litellm_logging_obj", None ) @@ -1784,9 +1789,10 @@ def client(original_function): # noqa: PLR0915 ) # [OPTIONAL] CHECK CACHE - print_verbose( - f"ASYNC kwargs[caching]: {kwargs.get('caching', False)}; litellm.cache: {litellm.cache}; kwargs.get('cache'): {kwargs.get('cache', None)}" - ) + if _is_debugging_on(): + print_verbose( + f"ASYNC kwargs[caching]: {kwargs.get('caching', False)}; litellm.cache: {litellm.cache}; kwargs.get('cache'): {kwargs.get('cache', None)}" + ) _caching_handler_response: "Optional[CachingHandlerResponse]" = ( await _llm_caching_handler._async_get_cache( model=model or "", @@ -1862,10 +1868,7 @@ def client(original_function): # noqa: PLR0915 chunks, messages=kwargs.get("messages", None) ) else: - update_response_metadata = getattr( - sys.modules[__name__], "update_response_metadata" - ) - update_response_metadata( + _update_response_metadata( result=result, logging_obj=logging_obj, model=model, @@ -1885,11 +1888,12 @@ def client(original_function): # noqa: PLR0915 rules_obj=rules_obj, ) # Only run if call_type is a valid value in CallTypes - if call_type in [ct.value for ct in CallTypes]: + _call_type_enum = _CALL_TYPE_ENUM_MAP.get(call_type) + if _call_type_enum is not None: result = await async_post_call_success_deployment_hook( request_data=kwargs, response=result, - call_type=CallTypes(call_type), + call_type=_call_type_enum, ) ## Add response to cache @@ -1929,10 +1933,7 @@ def client(original_function): # noqa: PLR0915 end_time=end_time, ) - update_response_metadata = getattr( - sys.modules[__name__], "update_response_metadata" - ) - update_response_metadata( + _update_response_metadata( result=result, logging_obj=logging_obj, model=model, @@ -2642,7 +2643,14 @@ def get_supported_regions( model=model, custom_llm_provider=custom_llm_provider ) - supported_regions = model_info.get("supported_regions", None) + # Get the key used in model_cost to look up supported_regions + # since ModelInfoBase doesn't include this field + model_key = model_info.get("key") + if model_key is None: + return None + + model_cost_data = litellm.model_cost.get(model_key, {}) + supported_regions = model_cost_data.get("supported_regions", None) if supported_regions is None: return None @@ -3240,7 +3248,7 @@ def get_optional_params_embeddings( # noqa: PLR0915 object = litellm.AmazonTitanMultimodalEmbeddingG1Config() elif "amazon.titan-embed-text-v2:0" in model: object = litellm.AmazonTitanV2Config() - elif "cohere.embed-multilingual-v3" in model: + elif "cohere.embed-multilingual-v3" in model or "cohere.embed-v4" in model: object = litellm.BedrockCohereEmbeddingConfig() elif "twelvelabs" in model or "marengo" in model: object = litellm.TwelveLabsMarengoEmbeddingConfig() @@ -3640,10 +3648,10 @@ def pre_process_non_default_params( if "response_format" in non_default_params: if provider_config is not None: - non_default_params[ - "response_format" - ] = provider_config.get_json_schema_from_pydantic_object( - response_format=non_default_params["response_format"] + non_default_params["response_format"] = ( + provider_config.get_json_schema_from_pydantic_object( + response_format=non_default_params["response_format"] + ) ) else: non_default_params["response_format"] = type_to_response_format_param( @@ -3772,16 +3780,16 @@ def pre_process_optional_params( True # so that main.py adds the function call to the prompt ) if "tools" in non_default_params: - optional_params[ - "functions_unsupported_model" - ] = non_default_params.pop("tools") + optional_params["functions_unsupported_model"] = ( + non_default_params.pop("tools") + ) non_default_params.pop( "tool_choice", None ) # causes ollama requests to hang elif "functions" in non_default_params: - optional_params[ - "functions_unsupported_model" - ] = non_default_params.pop("functions") + optional_params["functions_unsupported_model"] = ( + non_default_params.pop("functions") + ) elif ( litellm.add_function_to_prompt ): # if user opts to add it to prompt instead @@ -4937,9 +4945,9 @@ def get_response_string(response_obj: Union[ModelResponse, ModelResponseStream]) return delta if isinstance(delta, str) else "" # Handle standard ModelResponse and ModelResponseStream - _choices: Union[ - List[Union[Choices, StreamingChoices]], List[StreamingChoices] - ] = response_obj.choices + _choices: Union[List[Union[Choices, StreamingChoices]], List[StreamingChoices]] = ( + response_obj.choices + ) # Use list accumulation to avoid O(n^2) string concatenation across choices response_parts: List[str] = [] @@ -5135,10 +5143,15 @@ def _invalidate_model_cost_lowercase_map() -> None: """Invalidate the case-insensitive lookup map for model_cost. Call this whenever litellm.model_cost is modified to ensure the map is rebuilt. + Also clears related LRU caches that depend on model_cost data. """ global _model_cost_lowercase_map _model_cost_lowercase_map = None + # Clear LRU caches that depend on model_cost data + get_model_info.cache_clear() + _cached_get_model_info_helper.cache_clear() + def _rebuild_model_cost_lowercase_map() -> Dict[str, str]: """Rebuild the case-insensitive lookup map from the current model_cost. @@ -5352,6 +5365,7 @@ def _get_max_position_embeddings(model_name: str) -> Optional[int]: return None +@lru_cache(maxsize=DEFAULT_MAX_LRU_CACHE_SIZE) def _cached_get_model_info_helper( model: str, custom_llm_provider: Optional[str] ) -> ModelInfoBase: @@ -5699,6 +5713,7 @@ def _get_model_info_helper( # noqa: PLR0915 ) +@lru_cache(maxsize=DEFAULT_MAX_LRU_CACHE_SIZE) def get_model_info(model: str, custom_llm_provider: Optional[str] = None) -> ModelInfo: """ Get a dict for the maximum tokens (context window), input_cost_per_token, output_cost_per_token for a given model. @@ -7401,7 +7416,13 @@ def is_cached_message(message: AllMessageValues) -> bool: Used for anthropic/gemini context caching. Follows the anthropic format {"cache_control": {"type": "ephemeral"}} + + Can be disabled globally by setting litellm.disable_anthropic_gemini_context_caching_transform = True """ + # Check if context caching is disabled globally + if litellm.disable_anthropic_gemini_context_caching_transform is True: + return False + if "content" not in message: return False @@ -7707,25 +7728,29 @@ def validate_chat_completion_tool_choice( f"Invalid tool choice, tool_choice={tool_choice}. Got={type(tool_choice)}. Expecting str, or dict. Please ensure tool_choice follows the OpenAI tool_choice spec" ) -def validate_openai_optional_params( - stop: Optional[Union[str, List[str]]] = None, - **kwargs -) -> Optional[Union[str, List[str]]]: - """ - Validates and fixes OpenAI optional parameters. - - Args: - stop: Stop sequences (string or list of strings) - **kwargs: Additional optional parameters - - Returns: - Validated stop parameter (truncated to 4 elements if needed) - """ - if stop is not None and isinstance(stop, list) and not litellm.disable_stop_sequence_limit: + +def validate_openai_optional_params( + stop: Optional[Union[str, List[str]]] = None, **kwargs +) -> Optional[Union[str, List[str]]]: + """ + Validates and fixes OpenAI optional parameters. + + Args: + stop: Stop sequences (string or list of strings) + **kwargs: Additional optional parameters + + Returns: + Validated stop parameter (truncated to 4 elements if needed) + """ + if ( + stop is not None + and isinstance(stop, list) + and not litellm.disable_stop_sequence_limit + ): # Truncate to 4 elements if more are provided as openai only supports up to 4 stop sequences - if len(stop) > 4: - stop = stop[:4] - + if len(stop) > 4: + stop = stop[:4] + return stop @@ -7895,9 +7920,8 @@ class ProviderConfigManager: @staticmethod def _get_azure_ai_config(model: str) -> BaseConfig: """Get Azure AI config based on model type.""" - if "claude" in model.lower(): - return litellm.AzureAnthropicConfig() - return litellm.AzureAIStudioConfig() + from litellm.llms.azure_ai.common_utils import AzureFoundryModelInfo + return AzureFoundryModelInfo.get_azure_ai_config_for_model(model) @staticmethod def _get_vertex_ai_config(model: str) -> BaseConfig: @@ -8046,8 +8070,16 @@ class ProviderConfigManager: ) return OpenrouterEmbeddingConfig() + elif litellm.LlmProviders.VERCEL_AI_GATEWAY == provider: + from litellm.llms.vercel_ai_gateway.embedding.transformation import ( + VercelAIGatewayEmbeddingConfig, + ) + + return VercelAIGatewayEmbeddingConfig() elif litellm.LlmProviders.GIGACHAT == provider: return litellm.GigaChatEmbeddingConfig() + elif litellm.LlmProviders.HOSTED_VLLM == provider: + return litellm.HostedVLLMEmbeddingConfig() elif litellm.LlmProviders.SAGEMAKER == provider: from litellm.llms.sagemaker.embedding.transformation import ( SagemakerEmbeddingConfig, @@ -8439,6 +8471,12 @@ class ProviderConfigManager: ) return RAGFlowVectorStoreConfig() + elif litellm.LlmProviders.S3_VECTORS == provider: + from litellm.llms.s3_vectors.vector_stores.transformation import ( + S3VectorsVectorStoreConfig, + ) + + return S3VectorsVectorStoreConfig() return None @staticmethod @@ -8686,9 +8724,9 @@ class ProviderConfigManager: """ Get Search configuration for a given provider. """ + from litellm.llms.brave.search.transformation import BraveSearchConfig from litellm.llms.dataforseo.search.transformation import DataForSEOSearchConfig from litellm.llms.exa_ai.search.transformation import ExaAISearchConfig - from litellm.llms.brave.search.transformation import BraveSearchConfig from litellm.llms.firecrawl.search.transformation import FirecrawlSearchConfig from litellm.llms.google_pse.search.transformation import GooglePSESearchConfig from litellm.llms.linkup.search.transformation import LinkupSearchConfig diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index 209c0794e50..485bee4f191 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -354,6 +354,25 @@ "supports_video_input": true, "supports_vision": true }, + "amazon.nova-2-pro-preview-20251202-v1:0": { + "cache_read_input_token_cost": 5.46875e-07, + "input_cost_per_token": 2.1875e-06, + "input_cost_per_image_token": 2.1875e-06, + "input_cost_per_audio_token": 2.1875e-06, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 1.75e-05, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_video_input": true, + "supports_vision": true + }, "apac.amazon.nova-2-lite-v1:0": { "cache_read_input_token_cost": 8.25e-08, "input_cost_per_token": 3.3e-07, @@ -371,6 +390,25 @@ "supports_video_input": true, "supports_vision": true }, + "apac.amazon.nova-2-pro-preview-20251202-v1:0": { + "cache_read_input_token_cost": 5.46875e-07, + "input_cost_per_token": 2.1875e-06, + "input_cost_per_image_token": 2.1875e-06, + "input_cost_per_audio_token": 2.1875e-06, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 1.75e-05, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_video_input": true, + "supports_vision": true + }, "eu.amazon.nova-2-lite-v1:0": { "cache_read_input_token_cost": 8.25e-08, "input_cost_per_token": 3.3e-07, @@ -388,6 +426,25 @@ "supports_video_input": true, "supports_vision": true }, + "eu.amazon.nova-2-pro-preview-20251202-v1:0": { + "cache_read_input_token_cost": 5.46875e-07, + "input_cost_per_token": 2.1875e-06, + "input_cost_per_image_token": 2.1875e-06, + "input_cost_per_audio_token": 2.1875e-06, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 1.75e-05, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_video_input": true, + "supports_vision": true + }, "us.amazon.nova-2-lite-v1:0": { "cache_read_input_token_cost": 8.25e-08, "input_cost_per_token": 3.3e-07, @@ -405,6 +462,25 @@ "supports_video_input": true, "supports_vision": true }, + "us.amazon.nova-2-pro-preview-20251202-v1:0": { + "cache_read_input_token_cost": 5.46875e-07, + "input_cost_per_token": 2.1875e-06, + "input_cost_per_image_token": 2.1875e-06, + "input_cost_per_audio_token": 2.1875e-06, + "litellm_provider": "bedrock_converse", + "max_input_tokens": 1000000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "output_cost_per_token": 1.75e-05, + "supports_function_calling": true, + "supports_pdf_input": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_video_input": true, + "supports_vision": true + }, "amazon.nova-2-multimodal-embeddings-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 8172, @@ -673,7 +749,7 @@ "anthropic.claude-3-5-sonnet-20240620-v1:0": { "input_cost_per_token": 3e-06, "litellm_provider": "bedrock", - "max_input_tokens": 200000, + "max_input_tokens": 1000000, "max_output_tokens": 4096, "max_tokens": 4096, "mode": "chat", @@ -682,14 +758,22 @@ "supports_pdf_input": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "input_cost_per_token_above_200k_tokens": 6e-06, + "output_cost_per_token_above_200k_tokens": 3e-05, + "cache_creation_input_token_cost_above_200k_tokens": 7.5e-06, + "cache_read_input_token_cost_above_200k_tokens": 6e-07, + "cache_creation_input_token_cost_above_1hr": 7.5e-06, + "cache_creation_input_token_cost_above_1hr_above_200k_tokens": 1.5e-05, + "cache_creation_input_token_cost": 3.75e-06, + "cache_read_input_token_cost": 3e-07 }, "anthropic.claude-3-5-sonnet-20241022-v2:0": { "cache_creation_input_token_cost": 3.75e-06, "cache_read_input_token_cost": 3e-07, "input_cost_per_token": 3e-06, "litellm_provider": "bedrock", - "max_input_tokens": 200000, + "max_input_tokens": 1000000, "max_output_tokens": 8192, "max_tokens": 8192, "mode": "chat", @@ -701,7 +785,13 @@ "supports_prompt_caching": true, "supports_response_schema": true, "supports_tool_choice": true, - "supports_vision": true + "supports_vision": true, + "input_cost_per_token_above_200k_tokens": 6e-06, + "output_cost_per_token_above_200k_tokens": 3e-05, + "cache_creation_input_token_cost_above_200k_tokens": 7.5e-06, + "cache_read_input_token_cost_above_200k_tokens": 6e-07, + "cache_creation_input_token_cost_above_1hr": 7.5e-06, + "cache_creation_input_token_cost_above_1hr_above_200k_tokens": 1.5e-05 }, "anthropic.claude-3-7-sonnet-20240620-v1:0": { "cache_creation_input_token_cost": 4.5e-06, @@ -1441,6 +1531,14 @@ "supports_response_schema": true, "supports_tool_choice": true }, + "azure_ai/model_router": { + "input_cost_per_token": 1.4e-07, + "output_cost_per_token": 0, + "litellm_provider": "azure_ai", + "mode": "chat", + "source": "https://azure.microsoft.com/en-us/pricing/details/ai-services/", + "comment": "Flat cost of $0.14 per M input tokens for Azure AI Foundry Model Router infrastructure. Use pattern: azure_ai/model_router/ where deployment-name is your Azure deployment (e.g., azure-model-router)" + }, "azure/eu/gpt-4o-2024-08-06": { "deprecation_date": "2026-02-27", "cache_read_input_token_cost": 1.375e-06, @@ -3130,7 +3228,7 @@ "supports_reasoning": true, "supports_response_schema": true, "supports_system_messages": true, - "supports_tool_choice": false, + "supports_tool_choice": true, "supports_vision": true }, "azure/gpt-5-chat-latest": { @@ -3162,7 +3260,7 @@ "supports_reasoning": true, "supports_response_schema": true, "supports_system_messages": true, - "supports_tool_choice": false, + "supports_tool_choice": true, "supports_vision": true }, "azure/gpt-5-codex": { @@ -3650,13 +3748,12 @@ "cache_read_input_token_cost": 1.75e-07, "input_cost_per_token": 1.75e-06, "litellm_provider": "azure", - "max_input_tokens": 128000, - "max_output_tokens": 16384, - "max_tokens": 16384, - "mode": "chat", + "max_input_tokens": 272000, + "max_output_tokens": 128000, + "max_tokens": 128000, + "mode": "responses", "output_cost_per_token": 1.4e-05, "supported_endpoints": [ - "/v1/chat/completions", "/v1/responses" ], "supported_modalities": [ @@ -6632,13 +6729,13 @@ "supports_tool_choice": true }, "cerebras/gpt-oss-120b": { - "input_cost_per_token": 2.5e-07, + "input_cost_per_token": 3.5e-07, "litellm_provider": "cerebras", "max_input_tokens": 131072, "max_output_tokens": 32768, "max_tokens": 32768, "mode": "chat", - "output_cost_per_token": 6.9e-07, + "output_cost_per_token": 7.5e-07, "source": "https://www.cerebras.ai/blog/openai-gpt-oss-120b-runs-fastest-on-cerebras", "supports_function_calling": true, "supports_parallel_function_calling": true, @@ -6656,6 +6753,7 @@ "output_cost_per_token": 8e-07, "source": "https://inference-docs.cerebras.ai/support/pricing", "supports_function_calling": true, + "supports_reasoning": true, "supports_tool_choice": true }, "cerebras/zai-glm-4.6": { @@ -9788,6 +9886,7 @@ "supports_tool_choice": true }, "deepinfra/google/gemini-2.0-flash-001": { + "deprecation_date": "2026-03-31", "max_tokens": 1000000, "max_input_tokens": 1000000, "max_output_tokens": 1000000, @@ -10231,6 +10330,48 @@ "mode": "completion", "output_cost_per_token": 5e-07 }, + "deepseek-v3-2-251201": { + "input_cost_per_token": 0.0, + "litellm_provider": "volcengine", + "max_input_tokens": 98304, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "output_cost_per_token": 0.0, + "supports_assistant_prefill": true, + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_tool_choice": true + }, + "glm-4-7-251222": { + "input_cost_per_token": 0.0, + "litellm_provider": "volcengine", + "max_input_tokens": 204800, + "max_output_tokens": 131072, + "max_tokens": 131072, + "mode": "chat", + "output_cost_per_token": 0.0, + "supports_assistant_prefill": true, + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_tool_choice": true + }, + "kimi-k2-thinking-251104": { + "input_cost_per_token": 0.0, + "litellm_provider": "volcengine", + "max_input_tokens": 229376, + "max_output_tokens": 32768, + "max_tokens": 32768, + "mode": "chat", + "output_cost_per_token": 0.0, + "supports_assistant_prefill": true, + "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, + "supports_tool_choice": true + }, "doubao-embedding": { "input_cost_per_token": 0.0, "litellm_provider": "volcengine", @@ -12105,6 +12246,7 @@ }, "gemini-2.0-flash": { "cache_read_input_token_cost": 2.5e-08, + "deprecation_date": "2026-03-31", "input_cost_per_audio_token": 7e-07, "input_cost_per_token": 1e-07, "litellm_provider": "vertex_ai-language-models", @@ -12144,7 +12286,7 @@ }, "gemini-2.0-flash-001": { "cache_read_input_token_cost": 3.75e-08, - "deprecation_date": "2026-02-05", + "deprecation_date": "2026-03-31", "input_cost_per_audio_token": 1e-06, "input_cost_per_token": 1.5e-07, "litellm_provider": "vertex_ai-language-models", @@ -12230,6 +12372,7 @@ }, "gemini-2.0-flash-lite": { "cache_read_input_token_cost": 1.875e-08, + "deprecation_date": "2026-03-31", "input_cost_per_audio_token": 7.5e-08, "input_cost_per_token": 7.5e-08, "litellm_provider": "vertex_ai-language-models", @@ -12265,7 +12408,7 @@ }, "gemini-2.0-flash-lite-001": { "cache_read_input_token_cost": 1.875e-08, - "deprecation_date": "2026-02-25", + "deprecation_date": "2026-03-31", "input_cost_per_audio_token": 7.5e-08, "input_cost_per_token": 7.5e-08, "litellm_provider": "vertex_ai-language-models", @@ -13477,6 +13620,79 @@ "supports_vision": true, "supports_web_search": true }, + "gemini-robotics-er-1.5-preview": { + "cache_read_input_token_cost": 0, + "input_cost_per_token": 3e-07, + "input_cost_per_audio_token": 1e-06, + "litellm_provider": "vertex_ai-language-models", + "max_input_tokens": 1048576, + "max_output_tokens": 65535, + "max_tokens": 65535, + "mode": "chat", + "output_cost_per_token": 2.5e-06, + "output_cost_per_reasoning_token": 2.5e-06, + "source": "https://ai.google.dev/gemini-api/docs/models#gemini-robotics-er-1-5-preview", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/completions" + ], + "supported_modalities": [ + "text", + "image", + "video", + "audio" + ], + "supported_output_modalities": [ + "text" + ], + "supports_audio_output": false, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_prompt_caching": false, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_url_context": true, + "supports_vision": true + }, + "gemini/gemini-robotics-er-1.5-preview": { + "cache_read_input_token_cost": 0, + "input_cost_per_token": 3e-07, + "input_cost_per_audio_token": 1e-06, + "litellm_provider": "gemini", + "max_input_tokens": 1048576, + "max_output_tokens": 65535, + "max_tokens": 65535, + "mode": "chat", + "output_cost_per_token": 2.5e-06, + "output_cost_per_reasoning_token": 2.5e-06, + "source": "https://ai.google.dev/gemini-api/docs/models#gemini-robotics-er-1-5-preview", + "supported_endpoints": [ + "/v1/chat/completions", + "/v1/completions" + ], + "supported_modalities": [ + "text", + "image", + "video", + "audio" + ], + "supported_output_modalities": [ + "text" + ], + "supports_audio_output": false, + "supports_function_calling": true, + "supports_parallel_function_calling": true, + "supports_prompt_caching": false, + "supports_reasoning": true, + "supports_response_schema": true, + "supports_system_messages": true, + "supports_tool_choice": true, + "supports_url_context": true, + "supports_vision": true, + "supports_web_search": true + }, "gemini-2.5-computer-use-preview-10-2025": { "input_cost_per_token": 1.25e-06, "input_cost_per_token_above_200k_tokens": 2.5e-06, @@ -13930,6 +14146,7 @@ }, "gemini/gemini-2.0-flash": { "cache_read_input_token_cost": 2.5e-08, + "deprecation_date": "2026-03-31", "input_cost_per_audio_token": 7e-07, "input_cost_per_token": 1e-07, "litellm_provider": "gemini", @@ -13970,6 +14187,7 @@ }, "gemini/gemini-2.0-flash-001": { "cache_read_input_token_cost": 2.5e-08, + "deprecation_date": "2026-03-31", "input_cost_per_audio_token": 7e-07, "input_cost_per_token": 1e-07, "litellm_provider": "gemini", @@ -14057,6 +14275,7 @@ }, "gemini/gemini-2.0-flash-lite": { "cache_read_input_token_cost": 1.875e-08, + "deprecation_date": "2026-03-31", "input_cost_per_audio_token": 7.5e-08, "input_cost_per_token": 7.5e-08, "litellm_provider": "gemini", @@ -18603,7 +18822,7 @@ "input_cost_per_token": 1.75e-06, "input_cost_per_token_priority": 3.5e-06, "litellm_provider": "openai", - "max_input_tokens": 400000, + "max_input_tokens": 272000, "max_output_tokens": 128000, "max_tokens": 128000, "mode": "responses", @@ -20543,6 +20762,7 @@ "supports_function_calling": true, "supports_tool_choice": true, "supports_prompt_caching": true, + "supports_reasoning": true, "supports_system_messages": true, "max_input_tokens": 1000000, "max_output_tokens": 8192 @@ -20557,6 +20777,7 @@ "supports_function_calling": true, "supports_tool_choice": true, "supports_prompt_caching": true, + "supports_reasoning": true, "supports_system_messages": true, "max_input_tokens": 1000000, "max_output_tokens": 8192 @@ -20571,6 +20792,7 @@ "supports_function_calling": true, "supports_tool_choice": true, "supports_prompt_caching": true, + "supports_reasoning": true, "supports_system_messages": true, "max_input_tokens": 200000, "max_output_tokens": 8192 @@ -21266,6 +21488,20 @@ "supports_tool_choice": true, "supports_web_search": true }, + "moonshot/kimi-k2.5": { + "cache_read_input_token_cost": 1e-07, + "input_cost_per_token": 6e-07, + "litellm_provider": "moonshot", + "max_input_tokens": 262144, + "max_output_tokens": 262144, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 3e-06, + "source": "https://platform.moonshot.ai/docs/pricing/chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_vision": true + }, "moonshot/kimi-latest": { "cache_read_input_token_cost": 1.5e-07, "input_cost_per_token": 2e-06, @@ -23158,6 +23394,7 @@ "supports_tool_choice": true }, "openrouter/google/gemini-2.0-flash-001": { + "deprecation_date": "2026-03-31", "input_cost_per_audio_token": 7e-07, "input_cost_per_token": 1e-07, "litellm_provider": "openrouter", @@ -23454,7 +23691,7 @@ "mode": "chat", "output_cost_per_token": 1.02e-06, "supports_function_calling": true, - "supports_prompt_caching": false, + "supports_prompt_caching": true, "supports_reasoning": true, "supports_tool_choice": true }, @@ -23590,6 +23827,20 @@ "output_cost_per_token": 6.5e-07, "supports_tool_choice": true }, + "openrouter/moonshotai/kimi-k2.5": { + "cache_read_input_token_cost": 1e-07, + "input_cost_per_token": 6e-07, + "litellm_provider": "openrouter", + "max_input_tokens": 262144, + "max_output_tokens": 262144, + "max_tokens": 262144, + "mode": "chat", + "output_cost_per_token": 3e-06, + "source": "https://openrouter.ai/moonshotai/kimi-k2.5", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_vision": true + }, "openrouter/nousresearch/nous-hermes-llama2-13b": { "input_cost_per_token": 2e-07, "litellm_provider": "openrouter", @@ -23803,11 +24054,14 @@ "cache_read_input_token_cost": 1.75e-07, "input_cost_per_token": 1.75e-06, "litellm_provider": "openrouter", - "max_input_tokens": 400000, + "max_input_tokens": 272000, "max_output_tokens": 128000, "max_tokens": 128000, - "mode": "chat", + "mode": "responses", "output_cost_per_token": 1.4e-05, + "supported_endpoints": [ + "/v1/responses" + ], "supported_modalities": [ "text", "image" @@ -24146,6 +24400,7 @@ "output_cost_per_token": 1.75e-06, "source": "https://openrouter.ai/z-ai/glm-4.6", "supports_function_calling": true, + "supports_prompt_caching": true, "supports_reasoning": true, "supports_tool_choice": true }, @@ -24159,9 +24414,76 @@ "output_cost_per_token": 1.9e-06, "source": "https://openrouter.ai/z-ai/glm-4.6:exacto", "supports_function_calling": true, + "supports_prompt_caching": true, "supports_reasoning": true, "supports_tool_choice": true }, + "openrouter/xiaomi/mimo-v2-flash": { + "input_cost_per_token": 9e-08, + "output_cost_per_token": 2.9e-07, + "cache_creation_input_token_cost": 0.0, + "cache_read_input_token_cost": 0.0, + "litellm_provider": "openrouter", + "max_input_tokens": 262144, + "max_output_tokens": 16384, + "max_tokens": 16384, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "supports_vision": false, + "supports_prompt_caching": false + }, + "openrouter/z-ai/glm-4.7": { + "input_cost_per_token": 4e-07, + "output_cost_per_token": 1.5e-06, + "cache_creation_input_token_cost": 0.0, + "cache_read_input_token_cost": 0.0, + "litellm_provider": "openrouter", + "max_input_tokens": 202752, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "supports_vision": true, + "supports_prompt_caching": false, + "supports_assistant_prefill": true + }, + "openrouter/z-ai/glm-4.7-flash": { + "input_cost_per_token": 7e-08, + "output_cost_per_token": 4e-07, + "cache_creation_input_token_cost": 0.0, + "cache_read_input_token_cost": 0.0, + "litellm_provider": "openrouter", + "max_input_tokens": 200000, + "max_output_tokens": 32000, + "max_tokens": 32000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "supports_vision": true, + "supports_prompt_caching": false + }, + "openrouter/minimax/minimax-m2.1": { + "input_cost_per_token": 2.7e-07, + "output_cost_per_token": 1.2e-06, + "cache_creation_input_token_cost": 0.0, + "cache_read_input_token_cost": 0.0, + "litellm_provider": "openrouter", + "max_input_tokens": 204000, + "max_output_tokens": 64000, + "max_tokens": 64000, + "mode": "chat", + "supports_function_calling": true, + "supports_tool_choice": true, + "supports_reasoning": true, + "supports_vision": true, + "supports_prompt_caching": false, + "supports_computer_use": false + }, "ovhcloud/DeepSeek-R1-Distill-Llama-70B": { "input_cost_per_token": 6.7e-07, "litellm_provider": "ovhcloud", @@ -26026,13 +26348,13 @@ "litellm_provider": "bedrock", "max_input_tokens": 77, "mode": "image_edit", - "output_cost_per_image": 0.40 + "output_cost_per_image": 0.4 }, "stability.stable-creative-upscale-v1:0": { "litellm_provider": "bedrock", "max_input_tokens": 77, "mode": "image_edit", - "output_cost_per_image": 0.60 + "output_cost_per_image": 0.6 }, "stability.stable-fast-upscale-v1:0": { "litellm_provider": "bedrock", @@ -27695,6 +28017,7 @@ "output_cost_per_token": 9e-07 }, "vercel_ai_gateway/google/gemini-2.0-flash": { + "deprecation_date": "2026-03-31", "input_cost_per_token": 1.5e-07, "litellm_provider": "vercel_ai_gateway", "max_input_tokens": 1048576, @@ -27704,6 +28027,7 @@ "output_cost_per_token": 6e-07 }, "vercel_ai_gateway/google/gemini-2.0-flash-lite": { + "deprecation_date": "2026-03-31", "input_cost_per_token": 7.5e-08, "litellm_provider": "vercel_ai_gateway", "max_input_tokens": 1048576, @@ -29475,6 +29799,7 @@ "mode": "chat", "output_cost_per_token": 1e-06, "source": "https://cloud.google.com/vertex-ai/generative-ai/pricing", + "supported_regions": ["global"], "supports_function_calling": true, "supports_tool_choice": true }, @@ -29487,6 +29812,7 @@ "mode": "chat", "output_cost_per_token": 4e-06, "source": "https://cloud.google.com/vertex-ai/generative-ai/pricing", + "supported_regions": ["global"], "supports_function_calling": true, "supports_tool_choice": true }, @@ -29499,6 +29825,7 @@ "mode": "chat", "output_cost_per_token": 1.2e-06, "source": "https://cloud.google.com/vertex-ai/generative-ai/pricing", + "supported_regions": ["global"], "supports_function_calling": true, "supports_tool_choice": true }, @@ -29511,6 +29838,7 @@ "mode": "chat", "output_cost_per_token": 1.2e-06, "source": "https://cloud.google.com/vertex-ai/generative-ai/pricing", + "supported_regions": ["global"], "supports_function_calling": true, "supports_tool_choice": true }, @@ -30373,6 +30701,7 @@ "supports_web_search": true }, "xai/grok-3": { + "cache_read_input_token_cost": 7.5e-07, "input_cost_per_token": 3e-06, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30387,6 +30716,7 @@ "supports_web_search": true }, "xai/grok-3-beta": { + "cache_read_input_token_cost": 7.5e-07, "input_cost_per_token": 3e-06, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30401,6 +30731,7 @@ "supports_web_search": true }, "xai/grok-3-fast-beta": { + "cache_read_input_token_cost": 1.25e-06, "input_cost_per_token": 5e-06, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30415,6 +30746,7 @@ "supports_web_search": true }, "xai/grok-3-fast-latest": { + "cache_read_input_token_cost": 1.25e-06, "input_cost_per_token": 5e-06, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30429,6 +30761,7 @@ "supports_web_search": true }, "xai/grok-3-latest": { + "cache_read_input_token_cost": 7.5e-07, "input_cost_per_token": 3e-06, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30443,6 +30776,7 @@ "supports_web_search": true }, "xai/grok-3-mini": { + "cache_read_input_token_cost": 7.5e-08, "input_cost_per_token": 3e-07, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30458,6 +30792,7 @@ "supports_web_search": true }, "xai/grok-3-mini-beta": { + "cache_read_input_token_cost": 7.5e-08, "input_cost_per_token": 3e-07, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30473,6 +30808,7 @@ "supports_web_search": true }, "xai/grok-3-mini-fast": { + "cache_read_input_token_cost": 1.5e-07, "input_cost_per_token": 6e-07, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30488,6 +30824,7 @@ "supports_web_search": true }, "xai/grok-3-mini-fast-beta": { + "cache_read_input_token_cost": 1.5e-07, "input_cost_per_token": 6e-07, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30503,6 +30840,7 @@ "supports_web_search": true }, "xai/grok-3-mini-fast-latest": { + "cache_read_input_token_cost": 1.5e-07, "input_cost_per_token": 6e-07, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30518,6 +30856,7 @@ "supports_web_search": true }, "xai/grok-3-mini-latest": { + "cache_read_input_token_cost": 7.5e-08, "input_cost_per_token": 3e-07, "litellm_provider": "xai", "max_input_tokens": 131072, @@ -30784,11 +31123,14 @@ "max_output_tokens": 128000, "mode": "chat", "supports_function_calling": true, + "supports_prompt_caching": true, "supports_reasoning": true, "supports_tool_choice": true, "source": "https://docs.z.ai/guides/overview/pricing" }, "zai/glm-4.6": { + "cache_creation_input_token_cost": 0, + "cache_read_input_token_cost": 1.1e-07, "input_cost_per_token": 6e-07, "output_cost_per_token": 2.2e-06, "litellm_provider": "zai", @@ -30796,6 +31138,8 @@ "max_output_tokens": 128000, "mode": "chat", "supports_function_calling": true, + "supports_prompt_caching": true, + "supports_reasoning": true, "supports_tool_choice": true, "source": "https://docs.z.ai/guides/overview/pricing" }, @@ -34443,4 +34787,4 @@ "output_cost_per_token": 0, "supports_reasoning": true } -} +} \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index e5c304ea776..3b2a8d20f20 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.2.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.4 and should not be changed by hand. [[package]] name = "a2a-sdk" @@ -343,7 +343,7 @@ zookeeper = ["kazoo"] name = "async-timeout" version = "5.0.1" description = "Timeout context manager for asyncio programs" -optional = false +optional = true python-versions = ">=3.8" groups = ["main"] markers = "python_full_version < \"3.11.3\" and (extra == \"extra-proxy\" or extra == \"proxy\" or python_version < \"3.11\")" @@ -593,8 +593,8 @@ files = [ jmespath = ">=0.7.1,<2.0.0" python-dateutil = ">=2.1,<3.0.0" urllib3 = [ - {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""}, + {version = ">=1.25.4,<1.27", markers = "python_version < \"3.10\""}, ] [package.extras] @@ -902,7 +902,7 @@ files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] -markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\" and python_version < \"3.14\" and (extra == \"utils\" or extra == \"semantic-router\") or sys_platform == \"win32\" and extra == \"utils\" or python_version < \"3.14\" and extra == \"semantic-router\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\"", proxy-dev = "platform_system == \"Windows\""} +markers = {main = "platform_system == \"Windows\" or sys_platform == \"win32\" and python_version < \"3.14\" and (extra == \"utils\" or extra == \"semantic-router\") or sys_platform == \"win32\" and extra == \"utils\" or python_version <= \"3.13\" and extra == \"semantic-router\"", dev = "platform_system == \"Windows\" or sys_platform == \"win32\"", proxy-dev = "platform_system == \"Windows\""} [[package]] name = "coloredlogs" @@ -2028,7 +2028,7 @@ description = "Google API client core library" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "python_version == \"3.9\" and (extra == \"google\" or extra == \"extra-proxy\") or python_version < \"3.14\" and (extra == \"extra-proxy\" or extra == \"google\")" +markers = "(extra == \"extra-proxy\" or extra == \"google\") and python_version < \"3.14\"" files = [ {file = "google_api_core-2.28.1-py3-none-any.whl", hash = "sha256:4021b0f8ceb77a6fb4de6fde4502cecab45062e66ff4f2895169e0b35bc9466c"}, {file = "google_api_core-2.28.1.tar.gz", hash = "sha256:2b405df02d68e68ce0fbc138559e6036559e685159d148ae5861013dc201baf8"}, @@ -2038,12 +2038,12 @@ files = [ google-auth = ">=2.14.1,<3.0.0" googleapis-common-protos = ">=1.56.2,<2.0.0" grpcio = [ - {version = ">=1.33.2,<2.0.0", optional = true, markers = "extra == \"grpc\""}, {version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, + {version = ">=1.33.2,<2.0.0", optional = true, markers = "python_version < \"3.11\" and extra == \"grpc\""}, ] grpcio-status = [ - {version = ">=1.33.2,<2.0.0", optional = true, markers = "extra == \"grpc\""}, {version = ">=1.49.1,<2.0.0", optional = true, markers = "python_version >= \"3.11\" and extra == \"grpc\""}, + {version = ">=1.33.2,<2.0.0", optional = true, markers = "extra == \"grpc\""}, ] proto-plus = [ {version = ">=1.22.3,<2.0.0"}, @@ -2525,7 +2525,7 @@ description = "Lightweight in-process concurrent programming" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "python_version >= \"3.10\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\") and extra == \"mlflow\"" +markers = "python_version >= \"3.10\" and extra == \"mlflow\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")" files = [ {file = "greenlet-3.2.4-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:8c68325b0d0acf8d91dde4e6f930967dd52a5302cd4062932a6b2e7c2969f47c"}, {file = "greenlet-3.2.4-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:94385f101946790ae13da500603491f04a76b6e4c059dab271b3ce2e283b2590"}, @@ -2720,7 +2720,7 @@ description = "WSGI HTTP Server for UNIX" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "(python_version < \"3.14\" or extra == \"mlflow\" or extra == \"proxy\") and (platform_system != \"Windows\" or extra == \"proxy\") and (python_version >= \"3.10\" or extra == \"proxy\") and (extra == \"proxy\" or extra == \"mlflow\")" +markers = "extra == \"proxy\" or python_version >= \"3.10\" and (extra == \"proxy\" or extra == \"mlflow\") and platform_system != \"Windows\"" files = [ {file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"}, {file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"}, @@ -3426,15 +3426,15 @@ files = [ [[package]] name = "litellm-proxy-extras" -version = "0.4.26" +version = "0.4.29" description = "Additional files for the LiteLLM Proxy. Reduces the size of the main litellm package." optional = true python-versions = "!=2.7.*,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,!=3.7.*,>=3.8" groups = ["main"] markers = "extra == \"proxy\"" files = [ - {file = "litellm_proxy_extras-0.4.26-py3-none-any.whl", hash = "sha256:6b8720c7ae0b51637a3155b6cb02783c316443fe002641061ed9162d2667aee4"}, - {file = "litellm_proxy_extras-0.4.26.tar.gz", hash = "sha256:cb9544703580172fc6436670e1a693a46ccb34a9057548c94f43e97b7b032754"}, + {file = "litellm_proxy_extras-0.4.29-py3-none-any.whl", hash = "sha256:c36c1b69675c61acccc6b61dd610eb37daeb72c6fd819461cefb5b0cc7e0550f"}, + {file = "litellm_proxy_extras-0.4.29.tar.gz", hash = "sha256:1a8266911e0546f1e17e6714ca20b72e9fef47c1683f9c16399cf2d1786437a0"}, ] [[package]] @@ -3777,10 +3777,10 @@ files = [ [package.dependencies] numpy = [ - {version = ">1.20"}, {version = ">=1.23.3", markers = "python_version >= \"3.11\""}, - {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, + {version = ">1.20"}, + {version = ">=1.21.2", markers = "python_version >= \"3.10\""}, ] [package.extras] @@ -4191,7 +4191,7 @@ description = "Fundamental package for array computing in Python" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "python_version < \"3.12\" and (python_version >= \"3.10\" or extra == \"extra-proxy\" or extra == \"semantic-router\" or extra == \"google\") and (extra == \"extra-proxy\" or extra == \"semantic-router\" or extra == \"google\" or extra == \"mlflow\")" +markers = "(python_version >= \"3.10\" or extra == \"extra-proxy\" or extra == \"semantic-router\" or extra == \"google\") and python_version < \"3.12\" and (extra == \"extra-proxy\" or extra == \"semantic-router\" or extra == \"google\" or extra == \"mlflow\")" files = [ {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, @@ -4708,8 +4708,8 @@ files = [ [package.dependencies] numpy = [ {version = ">=1.23.2", markers = "python_version == \"3.11\""}, - {version = ">=1.22.4", markers = "python_version < \"3.11\""}, {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, + {version = ">=1.22.4", markers = "python_version < \"3.11\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -5169,7 +5169,7 @@ description = "Beautiful, Pythonic protocol buffers" optional = true python-versions = ">=3.7" groups = ["main"] -markers = "extra == \"google\" or extra == \"extra-proxy\"" +markers = "extra == \"extra-proxy\" or extra == \"google\"" files = [ {file = "proto_plus-1.26.1-py3-none-any.whl", hash = "sha256:13285478c2dcf2abb829db158e1047e2f1e8d63a077d94263c2b88b043c75a66"}, {file = "proto_plus-1.26.1.tar.gz", hash = "sha256:21a515a4c4c0088a773899e23c7bbade3d18f9c66c73edd4c7ee3816bc96a012"}, @@ -5316,7 +5316,7 @@ files = [ {file = "pycparser-2.23-py3-none-any.whl", hash = "sha256:e5c6e8d3fbad53479cab09ac03729e0a9faf2bee3db8208a550daf5af81a5934"}, {file = "pycparser-2.23.tar.gz", hash = "sha256:78816d4f24add8f10a06d6f05b4d424ad9e96cfebf68a4ddc99c65c0720d00c2"}, ] -markers = {main = "implementation_name != \"PyPy\" and (platform_python_implementation != \"PyPy\" or extra == \"proxy\")", dev = "platform_python_implementation != \"PyPy\" and implementation_name != \"PyPy\"", proxy-dev = "implementation_name != \"PyPy\" and platform_python_implementation != \"PyPy\""} +markers = {main = "implementation_name != \"PyPy\" and (platform_python_implementation != \"PyPy\" or extra == \"proxy\")", dev = "implementation_name != \"PyPy\" and platform_python_implementation != \"PyPy\"", proxy-dev = "implementation_name != \"PyPy\" and platform_python_implementation != \"PyPy\""} [[package]] name = "pydantic" @@ -5617,7 +5617,7 @@ description = "A python implementation of GNU readline." optional = true python-versions = ">=3.8" groups = ["main"] -markers = "extra == \"extra-proxy\" and sys_platform == \"win32\" and python_version < \"3.14\"" +markers = "sys_platform == \"win32\" and extra == \"extra-proxy\" and python_version < \"3.14\"" files = [ {file = "pyreadline3-3.5.4-py3-none-any.whl", hash = "sha256:eaf8e6cc3c49bcccf145fc6067ba8643d1df34d604a1ec0eccbf7a18e6d3fae6"}, {file = "pyreadline3-3.5.4.tar.gz", hash = "sha256:8d57d53039a1c75adba8e50dd3d992b28143480816187ea5efbd5c78e6c885b7"}, @@ -5686,6 +5686,42 @@ pytest = ">=6.2.5" [package.extras] dev = ["pre-commit", "pytest-asyncio", "tox"] +[[package]] +name = "pytest-retry" +version = "1.7.0" +description = "Adds the ability to retry flaky tests in CI environments" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest_retry-1.7.0-py3-none-any.whl", hash = "sha256:a2dac85b79a4e2375943f1429479c65beb6c69553e7dae6b8332be47a60954f4"}, + {file = "pytest_retry-1.7.0.tar.gz", hash = "sha256:f8d52339f01e949df47c11ba9ee8d5b362f5824dff580d3870ec9ae0057df80f"}, +] + +[package.dependencies] +pytest = ">=7.0.0" + +[package.extras] +dev = ["black", "flake8", "isort", "mypy"] + +[[package]] +name = "pytest-retry" +version = "1.7.0" +description = "Adds the ability to retry flaky tests in CI environments" +optional = false +python-versions = ">=3.9" +groups = ["dev"] +files = [ + {file = "pytest_retry-1.7.0-py3-none-any.whl", hash = "sha256:a2dac85b79a4e2375943f1429479c65beb6c69553e7dae6b8332be47a60954f4"}, + {file = "pytest_retry-1.7.0.tar.gz", hash = "sha256:f8d52339f01e949df47c11ba9ee8d5b362f5824dff580d3870ec9ae0057df80f"}, +] + +[package.dependencies] +pytest = ">=7.0.0" + +[package.extras] +dev = ["black", "flake8", "isort", "mypy"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -5719,15 +5755,15 @@ cli = ["click (>=5.0)"] [[package]] name = "python-multipart" -version = "0.0.18" +version = "0.0.22" description = "A streaming multipart parser for Python" optional = true -python-versions = ">=3.8" +python-versions = ">=3.10" groups = ["main"] -markers = "extra == \"proxy\"" +markers = "python_version >= \"3.10\" and extra == \"proxy\"" files = [ - {file = "python_multipart-0.0.18-py3-none-any.whl", hash = "sha256:efe91480f485f6a361427a541db4796f9e1591afc0fb8e7a4ba06bfbc6708996"}, - {file = "python_multipart-0.0.18.tar.gz", hash = "sha256:7a68db60c8bfb82e460637fa4750727b45af1d5e2ed215593f917f64694d34fe"}, + {file = "python_multipart-0.0.22-py3-none-any.whl", hash = "sha256:2b2cd894c83d21bf49d702499531c7bafd057d730c201782048f7945d82de155"}, + {file = "python_multipart-0.0.22.tar.gz", hash = "sha256:7340bef99a7e0032613f56dc36027b959fd3b30a787ed62d310e951f7c3a3a58"}, ] [[package]] @@ -5766,7 +5802,7 @@ description = "Python for Window Extensions" optional = true python-versions = "*" groups = ["main"] -markers = "python_version >= \"3.10\" and sys_platform == \"win32\" and (extra == \"proxy\" or extra == \"mlflow\")" +markers = "python_version >= \"3.10\" and (extra == \"proxy\" or extra == \"mlflow\") and sys_platform == \"win32\"" files = [ {file = "pywin32-311-cp310-cp310-win32.whl", hash = "sha256:d03ff496d2a0cd4a5893504789d4a15399133fe82517455e78bad62efbb7f0a3"}, {file = "pywin32-311-cp310-cp310-win_amd64.whl", hash = "sha256:797c2772017851984b97180b0bebe4b620bb86328e8a884bb626156295a63b3b"}, @@ -7450,26 +7486,6 @@ examples = ["aiosqlite (>=0.21.0)", "fastapi (>=0.115.12)", "sqlalchemy[asyncio] granian = ["granian (>=2.3.1)"] uvicorn = ["uvicorn (>=0.34.0)"] -[[package]] -name = "starlette" -version = "0.49.3" -description = "The little ASGI library that shines." -optional = false -python-versions = ">=3.9" -groups = ["main", "dev"] -files = [ - {file = "starlette-0.49.3-py3-none-any.whl", hash = "sha256:b579b99715fdc2980cf88c8ec96d3bf1ce16f5a8051a7c2b84ef9b1cdecaea2f"}, - {file = "starlette-0.49.3.tar.gz", hash = "sha256:1c14546f299b5901a1ea0e34410575bc33bbd741377a10484a54445588d00284"}, -] -markers = {main = "python_version == \"3.9\" and extra == \"proxy\"", dev = "python_version == \"3.9\""} - -[package.dependencies] -anyio = ">=3.6.2,<5" -typing-extensions = {version = ">=4.10.0", markers = "python_version < \"3.13\""} - -[package.extras] -full = ["httpx (>=0.27.0,<0.29.0)", "itsdangerous", "jinja2", "python-multipart (>=0.0.18)", "pyyaml"] - [[package]] name = "starlette" version = "0.50.0" @@ -7481,7 +7497,7 @@ files = [ {file = "starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca"}, {file = "starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca"}, ] -markers = {main = "python_version >= \"3.10\" and (extra == \"mlflow\" or extra == \"proxy\") or extra == \"proxy\""} +markers = {main = "(extra == \"mlflow\" or extra == \"proxy\") and python_version >= \"3.10\" or extra == \"proxy\""} [package.dependencies] anyio = ">=3.6.2,<5" @@ -7530,7 +7546,7 @@ description = "Retry code until it succeeds" optional = true python-versions = ">=3.9" groups = ["main"] -markers = "(extra == \"extra-proxy\" or extra == \"google\") and (python_version < \"3.14\" or extra == \"google\")" +markers = "(extra == \"extra-proxy\" or extra == \"google\") and python_version < \"3.14\" or extra == \"google\"" files = [ {file = "tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138"}, {file = "tenacity-9.1.2.tar.gz", hash = "sha256:1169d376c297e7de388d18b4481760d478b0e99a777cad3a9c86e556f4b697cb"}, @@ -8082,7 +8098,7 @@ description = "Waitress WSGI server" optional = true python-versions = ">=3.9.0" groups = ["main"] -markers = "python_version >= \"3.10\" and platform_system == \"Windows\" and extra == \"mlflow\"" +markers = "python_version >= \"3.10\" and extra == \"mlflow\" and platform_system == \"Windows\"" files = [ {file = "waitress-3.0.2-py3-none-any.whl", hash = "sha256:c56d67fd6e87c2ee598b76abdd4e96cfad1f24cacdea5078d382b1f9d7b5ed2e"}, {file = "waitress-3.0.2.tar.gz", hash = "sha256:682aaaf2af0c44ada4abfb70ded36393f0e307f4ab9456a215ce0020baefc31f"}, @@ -8492,4 +8508,4 @@ utils = ["numpydoc"] [metadata] lock-version = "2.1" python-versions = ">=3.9,<4.0" -content-hash = "ee8411854731cb6cc038a6135d7589a81789a269e718a226101953ba24fb2033" +content-hash = "95fd27dc139d0e52e70093220c50582f16c78e5977ec77f4297f50a30df964c6" diff --git a/provider_endpoints_support.json b/provider_endpoints_support.json index a901739c46e..0738c6e4e09 100644 --- a/provider_endpoints_support.json +++ b/provider_endpoints_support.json @@ -236,6 +236,26 @@ "rag_query": true } }, + "s3_vectors": { + "display_name": "AWS S3 Vectors (`s3_vectors`)", + "url": "https://docs.litellm.ai/docs/providers/s3_vectors", + "endpoints": { + "chat_completions": false, + "messages": false, + "responses": false, + "embeddings": false, + "image_generations": false, + "audio_transcriptions": false, + "audio_speech": false, + "moderations": false, + "batches": false, + "rerank": false, + "a2a": false, + "interactions": false, + "vector_stores_create": true, + "vector_stores_search": true + } + }, "sagemaker": { "display_name": "AWS - Sagemaker (`sagemaker`)", "url": "https://docs.litellm.ai/docs/providers/aws_sagemaker", diff --git a/proxy_config.yaml b/proxy_config.yaml deleted file mode 100644 index ea405c1dea4..00000000000 --- a/proxy_config.yaml +++ /dev/null @@ -1,101 +0,0 @@ -model_list: - - model_name: "*" - litellm_params: - model: "*" - - model_name: "gpt-4" - litellm_params: - model: "gpt-4" - api_key: os.environ/OPENAI_API_KEY - - model_name: "gpt-3.5-turbo" - litellm_params: - model: "gpt-3.5-turbo" - api_key: os.environ/OPENAI_API_KEY - -general_settings: - master_key: sk-1234 - -# ─────────────────────────────────────────────── -# POLICIES - Define WHAT guardrails to apply -# ─────────────────────────────────────────────── -# -# Policies define guardrails with: -# - inherit: Inherit guardrails from another policy -# - description: Human-readable description -# - guardrails.add: Add guardrails (on top of inherited) -# - guardrails.remove: Remove guardrails (from inherited) -# - condition.model: Model pattern (exact or regex) for when guardrails apply -# -policies: - # Global baseline policy - global-baseline: - description: "Base guardrails for all requests" - guardrails: - add: - - pii_blocker - - # Healthcare policy - inherits from global-baseline - healthcare-compliance: - inherit: global-baseline - description: "HIPAA compliance for healthcare teams" - guardrails: - add: - - hipaa_audit - - # Dev policy - inherits but removes PII blocker for testing - internal-dev: - inherit: global-baseline - description: "Relaxed policy for internal development" - guardrails: - add: - - toxicity_filter - remove: - - pii_blocker - - # Policy with model condition (regex pattern) - gpt4-safety: - description: "Extra safety for GPT-4 models" - guardrails: - add: - - toxicity_filter - condition: - model: "gpt-4.*" # regex: matches gpt-4, gpt-4-turbo, gpt-4o, etc. - - # Policy with model condition (exact match list) - bedrock-compliance: - description: "Compliance for Bedrock models" - guardrails: - add: - - strict_pii_blocker - condition: - model: ["bedrock/claude-3", "bedrock/claude-2"] # exact matches - -# ─────────────────────────────────────────────── -# POLICY ATTACHMENTS - Define WHERE policies apply -# ─────────────────────────────────────────────── -# -# Attachments are REQUIRED to make policies active. -# A policy without an attachment will not be applied. -# -policy_attachments: - # Global attachment - applies to all requests - - policy: global-baseline - scope: "*" - - # Team-specific attachment - - policy: healthcare-compliance - teams: - - healthcare-team - - medical-research - - # Key pattern attachment - - policy: internal-dev - keys: - - "dev-key-*" - - "test-key-*" - - # Model-specific policies (attached globally, condition filters by model) - - policy: gpt4-safety - scope: "*" - - - policy: bedrock-compliance - scope: "*" diff --git a/proxy_server_config.yaml b/proxy_server_config.yaml index 85c26ed37e7..8ed728c5b28 100644 --- a/proxy_server_config.yaml +++ b/proxy_server_config.yaml @@ -46,7 +46,7 @@ model_list: model: dall-e-3 - model_name: fake-openai-endpoint litellm_params: - model: openai/fake + model: openai/gpt-3.5-turbo-0301 api_key: fake-key api_base: https://exampleopenaiendpoint-production.up.railway.app/ - model_name: fake-openai-endpoint-2 diff --git a/pyproject.toml b/pyproject.toml index 862611879a5..9832ca483dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "litellm" -version = "1.81.2" +version = "1.81.7" description = "Library to easily interface with LLM API providers" authors = ["BerriAI"] license = "MIT" @@ -45,7 +45,7 @@ orjson = {version = "^3.9.7", optional = true} apscheduler = {version = "^3.10.4", optional = true} fastapi-sso = { version = "^0.16.0", optional = true } PyJWT = { version = "^2.10.1", optional = true, python = ">=3.9" } -python-multipart = { version = "^0.0.18", optional = true} +python-multipart = { version = "^0.0.22", optional = true, python = ">=3.10"} cryptography = {version = "*", optional = true} prisma = {version = "0.11.0", optional = true} azure-identity = {version = "^1.15.0", optional = true, python = ">=3.9"} @@ -61,7 +61,7 @@ boto3 = { version = "1.40.76", optional = true } redisvl = {version = "^0.4.1", optional = true, markers = "python_version >= '3.9' and python_version < '3.14'"} mcp = {version = ">=1.25.0,<2.0.0", optional = true, python = ">=3.10"} a2a-sdk = {version = "^0.3.22", optional = true, python = ">=3.10"} -litellm-proxy-extras = {version = "0.4.26", optional = true} +litellm-proxy-extras = {version = "0.4.29", optional = true} rich = {version = "13.7.1", optional = true} litellm-enterprise = {version = "0.1.27", optional = true} diskcache = {version = "^5.6.1", optional = true} @@ -145,6 +145,7 @@ mypy = "^1.0" pytest = "^7.4.3" pytest-mock = "^3.12.0" pytest-asyncio = "^0.21.1" +pytest-retry = "^1.6.3" requests-mock = "^1.12.1" responses = "^0.25.7" respx = "^0.22.0" @@ -173,7 +174,7 @@ requires = ["poetry-core", "wheel"] build-backend = "poetry.core.masonry.api" [tool.commitizen] -version = "1.81.2" +version = "1.81.7" version_files = [ "pyproject.toml:^version" ] @@ -183,6 +184,8 @@ plugins = "pydantic.mypy" [tool.pytest.ini_options] asyncio_mode = "auto" +retries = 20 +retry_delay = 5 markers = [ "asyncio: mark test as an asyncio test", "limit_leaks: mark test with memory limit for leak detection (e.g., '40 MB')", diff --git a/requirements.txt b/requirements.txt index 9e32cd79507..0b7cf4992e8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,7 +32,7 @@ polars==1.31.0 # for data processing apscheduler==3.10.4 # for resetting budget in background fastapi-sso==0.19.0 # admin UI, SSO pyjwt[crypto]==2.10.1 ; python_version >= "3.9" -python-multipart==0.0.18 # admin UI +python-multipart==0.0.22 # admin UI Pillow==11.0.0 jaraco.context>=6.1.0 azure-ai-contentsafety==1.0.0 # for azure content safety @@ -50,7 +50,7 @@ sentry_sdk==2.21.0 # for sentry error handling detect-secrets==1.5.0 # Enterprise - secret detection / masking in LLM requests cryptography==44.0.1 tzdata==2025.1 # IANA time zone database -litellm-proxy-extras==0.4.26 # for proxy extras - e.g. prisma migrations +litellm-proxy-extras==0.4.29 # for proxy extras - e.g. prisma migrations llm-sandbox==0.3.31 # for skill execution in sandbox ### LITELLM PACKAGE DEPENDENCIES python-dotenv==1.0.1 # for env @@ -68,6 +68,7 @@ jsonschema>=4.23.0,<5.0.0 # validating json schema - aligned with openapi-core + websockets==15.0.1 # for realtime API soundfile==0.12.1 # for audio file processing openapi-core==0.21.0 # for OpenAPI compliance tests +pypdf>=6.6.2 # for PDF text extraction in RAG ingestion ######################## # LITELLM ENTERPRISE DEPENDENCIES diff --git a/schema.prisma b/schema.prisma index d7aa6e9f0d0..b118400b620 100644 --- a/schema.prisma +++ b/schema.prisma @@ -5,6 +5,7 @@ datasource client { generator client { provider = "prisma-client-py" + binaryTargets = ["native", "debian-openssl-1.1.x", "debian-openssl-3.0.x", "linux-musl", "linux-musl-openssl-3.0.x"] } // Budget / Rate Limits for an org @@ -760,6 +761,11 @@ model LiteLLM_ManagedVectorStoresTable { updated_at DateTime @updatedAt litellm_credential_name String? litellm_params Json? + team_id String? + user_id String? + + @@index([team_id]) + @@index([user_id]) } // Guardrails table for storing guardrail configurations diff --git a/tests/batches_tests/test_batches_logging_unit_tests.py b/tests/batches_tests/test_batches_logging_unit_tests.py index 281624cd594..4f175d438cd 100644 --- a/tests/batches_tests/test_batches_logging_unit_tests.py +++ b/tests/batches_tests/test_batches_logging_unit_tests.py @@ -169,3 +169,342 @@ def test_get_response_from_batch_job_output_file(sample_file_content_dict): assert result["id"] == "chatcmpl-AhjSMl7oZ79yIPHLRYgmgXSixTJr7" assert result["object"] == "chat.completion" assert result["usage"]["total_tokens"] == 30 + + +@pytest.mark.asyncio +async def test_batch_retrieve_cost_tracking_with_completed_batch_no_explicit_cost(): + """ + Test that cost is calculated for completed batches when no explicit cost data is provided. + + Regression test for: When batch status is "completed" and explicit batch_cost/batch_usage/batch_models + are not provided, the system should compute batch data by calling _handle_completed_batch. + """ + from litellm.litellm_core_utils.litellm_logging import Logging + from litellm.types.utils import CallTypes + from litellm.types.utils import LiteLLMBatch + from unittest.mock import AsyncMock, patch + + # Mock batch result with completed status + mock_batch = LiteLLMBatch( + id="batch-test-123", + object="batch", + endpoint="/v1/chat/completions", + errors=None, + input_file_id="file-input-123", + completion_window="24h", + status="completed", + output_file_id="file-output-123", + error_file_id=None, + created_at=1234567890, + in_progress_at=1234567900, + expires_at=1234654290, + finalizing_at=1234568000, + completed_at=1234568100, + failed_at=None, + expired_at=None, + cancelling_at=None, + cancelled_at=None, + request_counts={ + "total": 10, + "completed": 10, + "failed": 0, + }, + metadata=None, + ) + mock_batch._hidden_params = {} + + # Create logging object + logging_obj = Logging( + model="gpt-4o-mini", + messages=[{"role": "user", "content": "test"}], + stream=False, + call_type=CallTypes.aretrieve_batch.value, + litellm_call_id="test-call-123", + function_id="test-function", + start_time=time.time(), + dynamic_success_callbacks=[], + ) + logging_obj.custom_llm_provider = "openai" + + # Mock _handle_completed_batch to return cost data + expected_cost = 0.05 + expected_usage = litellm.Usage( + prompt_tokens=100, + completion_tokens=50, + total_tokens=150, + ) + expected_models = ["gpt-4o-mini"] + + with patch( + "litellm.litellm_core_utils.litellm_logging._handle_completed_batch", + new=AsyncMock(return_value=(expected_cost, expected_usage, expected_models)) + ) as mock_handle_batch: + # Call async_success_handler + await logging_obj.async_success_handler( + result=mock_batch, + start_time=time.time(), + end_time=time.time() + 1, + ) + + # Verify _handle_completed_batch was called + mock_handle_batch.assert_called_once() + + # Verify cost and usage were set on the batch result + assert mock_batch._hidden_params["response_cost"] == expected_cost + assert mock_batch._hidden_params["batch_models"] == expected_models + assert mock_batch.usage == expected_usage + + +@pytest.mark.asyncio +async def test_batch_retrieve_cost_tracking_with_explicit_cost_data(): + """ + Test that explicit cost data is used when provided, skipping computation. + + Regression test for: When batch_cost, batch_usage, and batch_models are explicitly + provided in kwargs, they should be used directly without calling _handle_completed_batch. + """ + from litellm.litellm_core_utils.litellm_logging import Logging + from litellm.types.utils import CallTypes + from litellm.types.utils import LiteLLMBatch + from unittest.mock import AsyncMock, patch + + # Mock batch result with completed status + mock_batch = LiteLLMBatch( + id="batch-test-456", + object="batch", + endpoint="/v1/chat/completions", + errors=None, + input_file_id="file-input-456", + completion_window="24h", + status="completed", + output_file_id="file-output-456", + error_file_id=None, + created_at=1234567890, + in_progress_at=1234567900, + expires_at=1234654290, + finalizing_at=1234568000, + completed_at=1234568100, + failed_at=None, + expired_at=None, + cancelling_at=None, + cancelled_at=None, + request_counts={ + "total": 5, + "completed": 5, + "failed": 0, + }, + metadata=None, + ) + mock_batch._hidden_params = {} + + # Create logging object + logging_obj = Logging( + model="gpt-4o-mini", + messages=[{"role": "user", "content": "test"}], + stream=False, + call_type=CallTypes.aretrieve_batch.value, + litellm_call_id="test-call-456", + function_id="test-function", + start_time=time.time(), + dynamic_success_callbacks=[], + ) + logging_obj.custom_llm_provider = "openai" + + # Explicit cost data to pass in kwargs + explicit_cost = 0.10 + explicit_usage = litellm.Usage( + prompt_tokens=200, + completion_tokens=100, + total_tokens=300, + ) + explicit_models = ["gpt-4o-mini", "gpt-3.5-turbo"] + + with patch( + "litellm.litellm_core_utils.litellm_logging._handle_completed_batch", + new=AsyncMock() + ) as mock_handle_batch: + # Call async_success_handler with explicit cost data + await logging_obj.async_success_handler( + result=mock_batch, + start_time=time.time(), + end_time=time.time() + 1, + batch_cost=explicit_cost, + batch_usage=explicit_usage, + batch_models=explicit_models, + ) + + # Verify _handle_completed_batch was NOT called (since explicit data provided) + mock_handle_batch.assert_not_called() + + # Verify explicit cost data was used + assert mock_batch._hidden_params["response_cost"] == explicit_cost + assert mock_batch._hidden_params["batch_models"] == explicit_models + assert mock_batch.usage == explicit_usage + + +@pytest.mark.asyncio +async def test_batch_retrieve_cost_tracking_with_unified_file_id_incomplete_batch(): + """ + Test that cost computation is skipped for unified file IDs with non-completed batches. + + Regression test for: For unified file IDs (base64 encoded), cost should only be computed + when batch status is "completed" and explicit data is not provided. + """ + import base64 + from litellm.litellm_core_utils.litellm_logging import Logging + from litellm.types.utils import CallTypes, SpecialEnums + from litellm.types.utils import LiteLLMBatch + from unittest.mock import AsyncMock, patch + + # Create a proper unified file ID by encoding the correct prefix + unified_id_str = f"{SpecialEnums.LITELM_MANAGED_FILE_ID_PREFIX.value}:test_file_789;unified_id:batch-789" + encoded_unified_id = base64.urlsafe_b64encode(unified_id_str.encode()).decode().rstrip("=") + + # Mock batch result with in_progress status and unified file ID + mock_batch = LiteLLMBatch( + id=encoded_unified_id, # Properly encoded unified ID + object="batch", + endpoint="/v1/chat/completions", + errors=None, + input_file_id="file-input-789", + completion_window="24h", + status="in_progress", # Not completed + output_file_id=None, + error_file_id=None, + created_at=1234567890, + in_progress_at=1234567900, + expires_at=1234654290, + finalizing_at=None, + completed_at=None, + failed_at=None, + expired_at=None, + cancelling_at=None, + cancelled_at=None, + request_counts={ + "total": 10, + "completed": 3, + "failed": 0, + }, + metadata=None, + ) + mock_batch._hidden_params = {} + + # Create logging object + logging_obj = Logging( + model="gpt-4o-mini", + messages=[{"role": "user", "content": "test"}], + stream=False, + call_type=CallTypes.aretrieve_batch.value, + litellm_call_id="test-call-789", + function_id="test-function", + start_time=time.time(), + dynamic_success_callbacks=[], + ) + logging_obj.custom_llm_provider = "openai" + + with patch( + "litellm.litellm_core_utils.litellm_logging._handle_completed_batch", + new=AsyncMock() + ) as mock_handle_batch: + # Call async_success_handler with in_progress batch (unified file ID) + await logging_obj.async_success_handler( + result=mock_batch, + start_time=time.time(), + end_time=time.time() + 1, + ) + + # Verify _handle_completed_batch was NOT called (batch not completed and is unified file ID) + mock_handle_batch.assert_not_called() + + # Verify cost data was not set + assert "response_cost" not in mock_batch._hidden_params + assert "batch_models" not in mock_batch._hidden_params + assert not hasattr(mock_batch, "usage") or mock_batch.usage is None + + +@pytest.mark.asyncio +async def test_batch_retrieve_cost_tracking_with_partial_explicit_data(): + """ + Test that cost is computed when only partial explicit data is provided. + + Regression test for: If batch_cost, batch_usage, or batch_models is missing + (not all three provided), and batch is completed, system should compute the data. + """ + from litellm.litellm_core_utils.litellm_logging import Logging + from litellm.types.utils import CallTypes + from litellm.types.utils import LiteLLMBatch + from unittest.mock import AsyncMock, patch + + # Mock batch result with completed status + mock_batch = LiteLLMBatch( + id="batch-test-partial", + object="batch", + endpoint="/v1/chat/completions", + errors=None, + input_file_id="file-input-partial", + completion_window="24h", + status="completed", + output_file_id="file-output-partial", + error_file_id=None, + created_at=1234567890, + in_progress_at=1234567900, + expires_at=1234654290, + finalizing_at=1234568000, + completed_at=1234568100, + failed_at=None, + expired_at=None, + cancelling_at=None, + cancelled_at=None, + request_counts={ + "total": 8, + "completed": 8, + "failed": 0, + }, + metadata=None, + ) + mock_batch._hidden_params = {} + + # Create logging object + logging_obj = Logging( + model="gpt-4o-mini", + messages=[{"role": "user", "content": "test"}], + stream=False, + call_type=CallTypes.aretrieve_batch.value, + litellm_call_id="test-call-partial", + function_id="test-function", + start_time=time.time(), + dynamic_success_callbacks=[], + ) + + logging_obj.custom_llm_provider = "openai" + + # Only provide batch_cost, missing batch_usage and batch_models + partial_cost = 0.08 + + expected_cost = 0.06 + expected_usage = litellm.Usage( + prompt_tokens=150, + completion_tokens=75, + total_tokens=225, + ) + expected_models = ["gpt-4o-mini"] + + with patch( + "litellm.litellm_core_utils.litellm_logging._handle_completed_batch", + new=AsyncMock(return_value=(expected_cost, expected_usage, expected_models)) + ) as mock_handle_batch: + # Call async_success_handler with partial explicit data + await logging_obj.async_success_handler( + result=mock_batch, + start_time=time.time(), + end_time=time.time() + 1, + batch_cost=partial_cost, # Only cost provided, not usage or models + ) + + # Verify _handle_completed_batch WAS called (since not all data provided) + mock_handle_batch.assert_called_once() + + # Verify computed cost data was used (not partial explicit data) + assert mock_batch._hidden_params["response_cost"] == expected_cost + assert mock_batch._hidden_params["batch_models"] == expected_models + assert mock_batch.usage == expected_usage diff --git a/tests/code_coverage_tests/liccheck.ini b/tests/code_coverage_tests/liccheck.ini index ea87b56ff30..dc46f83366a 100644 --- a/tests/code_coverage_tests/liccheck.ini +++ b/tests/code_coverage_tests/liccheck.ini @@ -141,3 +141,4 @@ llm-sandbox: >=0.3.31 # MIT License - https://github.com/vndee/llm-sandbox nodejs-wheel-binaries: >=24.12.0 # MIT license manually verified grpcio: >=1.69.0 # Apache License 2.0 jaraco.context: >=6.1.0 # Unknown license +pypdf: >=6.6.2 # BSD-3-Clause license - https://github.com/py-pdf/pypdf/blob/main/LICENSE diff --git a/tests/code_coverage_tests/license_cache.json b/tests/code_coverage_tests/license_cache.json index 910ec931c86..a9c7fad2b14 100644 --- a/tests/code_coverage_tests/license_cache.json +++ b/tests/code_coverage_tests/license_cache.json @@ -20,7 +20,7 @@ "apscheduler:3.10.4": "MIT", "fastapi-sso:0.16.0": "MIT", "pyjwt:2.9.0": "MIT", - "python-multipart:0.0.18": "Apache-2.0", + "python-multipart:0.0.22": "Apache-2.0", "Pillow:11.0.0": "MIT-CMU", "azure-ai-contentsafety:1.0.0": "MIT License", "azure-identity:1.16.1": "MIT License", diff --git a/tests/code_coverage_tests/router_code_coverage.py b/tests/code_coverage_tests/router_code_coverage.py index d700e56a06f..581f9340876 100644 --- a/tests/code_coverage_tests/router_code_coverage.py +++ b/tests/code_coverage_tests/router_code_coverage.py @@ -6,7 +6,7 @@ def get_function_names_from_file(file_path): """ Extracts all function names from a given Python file. """ - with open(file_path, "r") as file: + with open(file_path, "r", encoding="utf-8") as file: tree = ast.parse(file.read()) function_names = [] @@ -45,7 +45,7 @@ def get_all_functions_called_in_tests(base_dir): if file.endswith(".py") and "router" in file.lower(): print("file: ", file) file_path = os.path.join(root, file) - with open(file_path, "r") as f: + with open(file_path, "r", encoding="utf-8") as f: try: tree = ast.parse(f.read()) except SyntaxError: @@ -74,6 +74,7 @@ def get_functions_from_router(file_path): ignored_function_names = [ + "_acancel_batch", "__init__", ] diff --git a/tests/enterprise/litellm_enterprise/enterprise_callbacks/test_prometheus_logging_callbacks.py b/tests/enterprise/litellm_enterprise/enterprise_callbacks/test_prometheus_logging_callbacks.py index 2419c61c25c..0a57d046c72 100644 --- a/tests/enterprise/litellm_enterprise/enterprise_callbacks/test_prometheus_logging_callbacks.py +++ b/tests/enterprise/litellm_enterprise/enterprise_callbacks/test_prometheus_logging_callbacks.py @@ -230,6 +230,7 @@ def test_increment_token_metrics(prometheus_logger): team_alias="test_team_alias", requested_model=None, model="gpt-3.5-turbo", + model_id="model-123", ) prometheus_logger.litellm_tokens_metric.labels().inc.assert_called_once_with(100) @@ -243,6 +244,7 @@ def test_increment_token_metrics(prometheus_logger): team_alias="test_team_alias", requested_model=None, model="gpt-3.5-turbo", + model_id="model-123", ) prometheus_logger.litellm_input_tokens_metric.labels().inc.assert_called_once_with( 50 @@ -258,6 +260,7 @@ def test_increment_token_metrics(prometheus_logger): team_alias="test_team_alias", requested_model=None, model="gpt-3.5-turbo", + model_id="model-123", ) prometheus_logger.litellm_output_tokens_metric.labels().inc.assert_called_once_with( 50 @@ -411,7 +414,15 @@ def test_set_latency_metrics(prometheus_logger): # completion_start_time - api_call_start_time prometheus_logger.litellm_llm_api_time_to_first_token_metric.labels.assert_called_once_with( - "gpt-3.5-turbo", "key1", "alias1", "team1", "team_alias1" + end_user=None, + user="test_user", + hashed_api_key="test_hash", + api_key_alias="test_alias", + team="test_team", + team_alias="test_team_alias", + requested_model="openai-gpt", + model="gpt-3.5-turbo", + model_id="model-123", ) prometheus_logger.litellm_llm_api_time_to_first_token_metric.labels().observe.assert_called_once_with( 0.5 @@ -427,6 +438,7 @@ def test_set_latency_metrics(prometheus_logger): team_alias="test_team_alias", requested_model="openai-gpt", model="gpt-3.5-turbo", + model_id="model-123", ) prometheus_logger.litellm_llm_api_latency_metric.labels().observe.assert_called_once_with( 1.5 @@ -442,6 +454,7 @@ def test_set_latency_metrics(prometheus_logger): team_alias="test_team_alias", requested_model="openai-gpt", model="gpt-3.5-turbo", + model_id="model-123", ) prometheus_logger.litellm_request_total_latency_metric.labels().observe.assert_called_once_with( 2.0 @@ -577,12 +590,15 @@ def test_increment_top_level_request_and_spend_metrics(prometheus_logger): prometheus_logger.litellm_requests_metric.labels.assert_called_once_with( end_user=None, user=None, + user_email=None, hashed_api_key="test_hash", api_key_alias="test_alias", team="test_team", team_alias="test_team_alias", model="gpt-3.5-turbo", - user_email=None, + model_id="model-123", + client_ip=None, + user_agent=None, ) prometheus_logger.litellm_requests_metric.labels().inc.assert_called_once() @@ -590,12 +606,15 @@ def test_increment_top_level_request_and_spend_metrics(prometheus_logger): prometheus_logger.litellm_spend_metric.labels.assert_called_once_with( end_user=None, user=None, + user_email=None, hashed_api_key="test_hash", api_key_alias="test_alias", team="test_team", team_alias="test_team_alias", model="gpt-3.5-turbo", - user_email=None, + model_id="model-123", + client_ip=None, + user_agent=None, ) prometheus_logger.litellm_spend_metric.labels().inc.assert_called_once_with(0.1) @@ -647,6 +666,7 @@ async def test_async_log_failure_event(prometheus_logger): "test_team", "test_team_alias", "test_user", + "model-123", ) prometheus_logger.litellm_llm_api_failed_requests_metric.labels().inc.assert_called_once() @@ -671,6 +691,8 @@ async def test_async_log_failure_event(prometheus_logger): api_key_alias="test_alias", team="test_team", team_alias="test_team_alias", + client_ip="127.0.0.1", # from standard logging payload + user_agent=None, ) prometheus_logger.litellm_deployment_failure_responses.labels().inc.assert_called_once() @@ -685,6 +707,8 @@ async def test_async_log_failure_event(prometheus_logger): api_key_alias="test_alias", team="test_team", team_alias="test_team_alias", + client_ip="127.0.0.1", # from standard logging payload + user_agent=None, ) prometheus_logger.litellm_deployment_total_requests.labels().inc.assert_called_once() @@ -737,6 +761,9 @@ async def test_async_post_call_failure_hook(prometheus_logger): exception_status="429", exception_class="Openai.RateLimitError", route=user_api_key_dict.request_route, + model_id=None, + client_ip=None, + user_agent=None, ) prometheus_logger.litellm_proxy_failed_requests_metric.labels().inc.assert_called_once() @@ -752,6 +779,9 @@ async def test_async_post_call_failure_hook(prometheus_logger): status_code="429", user_email=None, route=user_api_key_dict.request_route, + model_id=None, + client_ip=None, + user_agent=None, ) prometheus_logger.litellm_proxy_total_requests_metric.labels().inc.assert_called_once() @@ -798,6 +828,9 @@ async def test_async_post_call_success_hook(prometheus_logger): status_code="200", user_email=None, route=user_api_key_dict.request_route, + model_id=None, + client_ip=None, + user_agent=None, ) prometheus_logger.litellm_proxy_total_requests_metric.labels().inc.assert_called_once() @@ -863,6 +896,7 @@ def test_set_llm_deployment_success_metrics(prometheus_logger): litellm_model_name="gpt-3.5-turbo", # actual model used - litellm model name hashed_api_key=standard_logging_payload["metadata"]["user_api_key_hash"], api_key_alias=standard_logging_payload["metadata"]["user_api_key_alias"], + model_id="model-123", ) prometheus_logger.litellm_remaining_requests_metric.labels().set.assert_called_once_with( @@ -877,6 +911,7 @@ def test_set_llm_deployment_success_metrics(prometheus_logger): hashed_api_key=standard_logging_payload["metadata"]["user_api_key_hash"], litellm_model_name="gpt-3.5-turbo", model_group="my_custom_model_group", + model_id="model-123", ) prometheus_logger.litellm_remaining_tokens_metric.labels().set.assert_called_once_with( @@ -902,6 +937,8 @@ def test_set_llm_deployment_success_metrics(prometheus_logger): api_key_alias=standard_logging_payload["metadata"]["user_api_key_alias"], team=standard_logging_payload["metadata"]["user_api_key_team_id"], team_alias=standard_logging_payload["metadata"]["user_api_key_team_alias"], + client_ip=None, + user_agent=None, ) prometheus_logger.litellm_deployment_success_responses.labels().inc.assert_called_once() @@ -916,6 +953,8 @@ def test_set_llm_deployment_success_metrics(prometheus_logger): api_key_alias=standard_logging_payload["metadata"]["user_api_key_alias"], team=standard_logging_payload["metadata"]["user_api_key_team_id"], team_alias=standard_logging_payload["metadata"]["user_api_key_team_alias"], + client_ip=None, + user_agent=None, ) prometheus_logger.litellm_deployment_total_requests.labels().inc.assert_called_once() @@ -937,6 +976,7 @@ def test_set_llm_deployment_success_metrics(prometheus_logger): hashed_api_key=standard_logging_payload["metadata"]["user_api_key_hash"], litellm_model_name="gpt-3.5-turbo", model_group="my_custom_model_group", + model_id="model-123", ) # Calculate expected latency per token (1 second / 10 tokens = 0.1 seconds per token) @@ -979,6 +1019,7 @@ async def test_log_success_fallback_event(prometheus_logger): team_alias="test_team_alias", exception_status="429", exception_class="Openai.RateLimitError", + model_id=None, ) prometheus_logger.litellm_deployment_successful_fallbacks.labels().inc.assert_called_once() @@ -1016,6 +1057,7 @@ async def test_log_failure_fallback_event(prometheus_logger): team_alias="test_team_alias", exception_status="429", exception_class="Openai.RateLimitError", + model_id=None, ) prometheus_logger.litellm_deployment_failed_fallbacks.labels().inc.assert_called_once() diff --git a/tests/enterprise/litellm_enterprise/integrations/test_prometheus.py b/tests/enterprise/litellm_enterprise/integrations/test_prometheus.py index 53757390a44..4a08c208fda 100644 --- a/tests/enterprise/litellm_enterprise/integrations/test_prometheus.py +++ b/tests/enterprise/litellm_enterprise/integrations/test_prometheus.py @@ -937,6 +937,124 @@ def test_callback_failure_metric_different_callbacks(prometheus_logger): ) +@pytest.mark.asyncio +async def test_langfuse_callback_failure_metric(prometheus_logger): + """ + Test that Langfuse callback failures are properly tracked in Prometheus metrics. + + This test verifies that when Langfuse logging fails, the + litellm_callback_logging_failures_metric is incremented with callback_name="langfuse". + """ + from unittest.mock import MagicMock, patch + + from litellm.integrations.langfuse.langfuse_prompt_management import ( + LangfusePromptManagement, + ) + + # Get initial value + initial_value = 0 + try: + initial_value = prometheus_logger.litellm_callback_logging_failures_metric.labels( + callback_name="langfuse" + )._value.get() + except Exception: + initial_value = 0 + + # Create Langfuse logger with mocked initialization + with patch("litellm.integrations.langfuse.langfuse_prompt_management.langfuse_client_init"): + langfuse_logger = LangfusePromptManagement() + + # Mock the log_event_on_langfuse to raise an exception + with patch( + "litellm.integrations.langfuse.langfuse_prompt_management.LangFuseHandler.get_langfuse_logger_for_request" + ) as mock_get_logger: + mock_logger = MagicMock() + mock_logger.log_event_on_langfuse.side_effect = Exception("Langfuse API error") + mock_get_logger.return_value = mock_logger + + # Mock handle_callback_failure to track calls + with patch.object(prometheus_logger, "increment_callback_logging_failure") as mock_increment: + # Inject prometheus logger into the langfuse logger + langfuse_logger.handle_callback_failure = lambda callback_name: mock_increment( + callback_name=callback_name + ) + + # Call async_log_success_event - should catch exception and increment metric + await langfuse_logger.async_log_success_event( + kwargs={}, + response_obj={}, + start_time=None, + end_time=None, + ) + + # Verify that increment was called with correct callback name + mock_increment.assert_called_once_with(callback_name="langfuse") + + print("✓ Langfuse callback failure metric test passed") + + +@pytest.mark.asyncio +async def test_langfuse_otel_callback_failure_metric(prometheus_logger): + """ + Test that Langfuse OTEL callback failures are properly tracked in Prometheus metrics. + + This test verifies that when Langfuse OTEL logging fails, the + litellm_callback_logging_failures_metric is incremented with callback_name="langfuse_otel". + """ + from unittest.mock import MagicMock, patch + + from litellm.integrations.langfuse.langfuse_otel import LangfuseOtelLogger + + # Get initial value + initial_value = 0 + try: + initial_value = prometheus_logger.litellm_callback_logging_failures_metric.labels( + callback_name="langfuse_otel" + )._value.get() + except Exception: + initial_value = 0 + + # Create Langfuse OTEL logger with mocked initialization + with patch("litellm.integrations.opentelemetry.OpenTelemetry.__init__", return_value=None): + langfuse_otel_logger = LangfuseOtelLogger(callback_name="langfuse_otel") + langfuse_otel_logger.callback_name = "langfuse_otel" + + # Mock handle_callback_failure to track calls + with patch.object(prometheus_logger, "increment_callback_logging_failure") as mock_increment: + # Inject prometheus logger into the langfuse otel logger + langfuse_otel_logger.handle_callback_failure = lambda callback_name: mock_increment( + callback_name=callback_name + ) + + # Test that the OpenTelemetry base class set_attributes exception handler works + # This is where langfuse_otel failures are caught and tracked + with patch.object(langfuse_otel_logger, "set_attributes") as mock_set_attributes: + # Simulate the exception handling in set_attributes + def set_attributes_with_error(*args, **kwargs): + # This simulates what happens in the real set_attributes method + try: + raise Exception("Attribute error") + except Exception as e: + langfuse_otel_logger.handle_callback_failure(callback_name=langfuse_otel_logger.callback_name) + + mock_set_attributes.side_effect = set_attributes_with_error + + # Call set_attributes + try: + langfuse_otel_logger.set_attributes( + span=MagicMock(), + kwargs={}, + response_obj={} + ) + except Exception: + pass + + # Verify that increment was called with correct callback name + mock_increment.assert_called_with(callback_name="langfuse_otel") + + print("✓ Langfuse OTEL callback failure metric test passed") + + # ============================================================================== # END CALLBACK FAILURE METRICS TESTS # ============================================================================== diff --git a/tests/enterprise/litellm_enterprise/proxy/auth/test_route_checks.py b/tests/enterprise/litellm_enterprise/proxy/auth/test_route_checks.py index ce77ff69e05..706e3b71870 100644 --- a/tests/enterprise/litellm_enterprise/proxy/auth/test_route_checks.py +++ b/tests/enterprise/litellm_enterprise/proxy/auth/test_route_checks.py @@ -180,3 +180,68 @@ class TestEnterpriseRouteChecks: # Should not raise exception since management routes are enabled EnterpriseRouteChecks.should_call_route("/config/update") + + +class TestEnterpriseRouteChecksErrorMessages: + """Test that error messages correctly identify which feature requires Enterprise license""" + + @patch("litellm.secret_managers.main.get_secret_bool") + @patch("litellm.proxy.proxy_server.premium_user", False) + def test_disable_llm_api_endpoints_error_message(self, mock_get_secret_bool): + """ + Test that when DISABLE_LLM_API_ENDPOINTS is set without Enterprise license, + the error message correctly mentions 'LLM API ENDPOINTS' + """ + with patch.dict(os.environ, {"DISABLE_LLM_API_ENDPOINTS": "true"}): + with pytest.raises(HTTPException) as exc_info: + EnterpriseRouteChecks.is_llm_api_route_disabled() + + assert exc_info.value.status_code == 500 + assert "DISABLING LLM API ENDPOINTS is an Enterprise feature" in str( + exc_info.value.detail + ) + + @patch("litellm.secret_managers.main.get_secret_bool") + @patch("litellm.proxy.proxy_server.premium_user", False) + def test_disable_admin_endpoints_error_message(self, mock_get_secret_bool): + """ + Test that when DISABLE_ADMIN_ENDPOINTS is set without Enterprise license, + the error message correctly mentions 'ADMIN ENDPOINTS' (not 'LLM API ENDPOINTS') + + This is a regression test for a bug where the error message incorrectly said + 'DISABLING LLM API ENDPOINTS' when the actual issue was DISABLE_ADMIN_ENDPOINTS. + """ + with patch.dict(os.environ, {"DISABLE_ADMIN_ENDPOINTS": "true"}): + with pytest.raises(HTTPException) as exc_info: + EnterpriseRouteChecks.is_management_routes_disabled() + + assert exc_info.value.status_code == 500 + assert "DISABLING ADMIN ENDPOINTS is an Enterprise feature" in str( + exc_info.value.detail + ) + # Ensure it does NOT mention LLM API ENDPOINTS (the old buggy message) + assert "LLM API ENDPOINTS" not in str(exc_info.value.detail) + + @patch("litellm.secret_managers.main.get_secret_bool") + @patch("litellm.proxy.proxy_server.premium_user", True) + def test_disable_llm_api_endpoints_with_premium_user(self, mock_get_secret_bool): + """ + Test that premium users can use DISABLE_LLM_API_ENDPOINTS without error + """ + mock_get_secret_bool.return_value = True + with patch.dict(os.environ, {"DISABLE_LLM_API_ENDPOINTS": "true"}): + # Should not raise exception for premium users + result = EnterpriseRouteChecks.is_llm_api_route_disabled() + assert result is True + + @patch("litellm.secret_managers.main.get_secret_bool") + @patch("litellm.proxy.proxy_server.premium_user", True) + def test_disable_admin_endpoints_with_premium_user(self, mock_get_secret_bool): + """ + Test that premium users can use DISABLE_ADMIN_ENDPOINTS without error + """ + mock_get_secret_bool.return_value = True + with patch.dict(os.environ, {"DISABLE_ADMIN_ENDPOINTS": "true"}): + # Should not raise exception for premium users + result = EnterpriseRouteChecks.is_management_routes_disabled() + assert result is True diff --git a/tests/enterprise/litellm_enterprise/proxy/hooks/test_managed_files.py b/tests/enterprise/litellm_enterprise/proxy/hooks/test_managed_files.py index 9a6e153a22b..3fd19cfa18f 100644 --- a/tests/enterprise/litellm_enterprise/proxy/hooks/test_managed_files.py +++ b/tests/enterprise/litellm_enterprise/proxy/hooks/test_managed_files.py @@ -235,10 +235,10 @@ async def test_async_pre_call_hook_for_unified_finetuning_job(): @pytest.mark.asyncio -@pytest.mark.parametrize("call_type", ["afile_content", "afile_delete"]) +@pytest.mark.parametrize("call_type", ["afile_content", "afile_delete", "afile_retrieve"]) async def test_can_user_call_unified_file_id(call_type): """ - Test that on file retrieve, delete we check if the user has access to the file + Test that on file retrieve, delete, and content we check if the user has access to the file """ from litellm.proxy._types import UserAPIKeyAuth @@ -376,10 +376,12 @@ async def test_output_file_id_for_batch_retrieve(): @pytest.mark.asyncio async def test_async_post_call_success_hook_twice_assert_no_unique_violation(): import asyncio - from litellm.types.utils import LiteLLMBatch - from litellm.proxy._types import UserAPIKeyAuth + from openai.types.batch import BatchRequestCounts + from litellm.proxy._types import UserAPIKeyAuth + from litellm.types.utils import LiteLLMBatch + # Use AsyncMock instead of real database connection prisma_client = AsyncMock() @@ -456,7 +458,7 @@ def test_update_responses_input_with_unified_file_id(): from litellm.litellm_core_utils.prompt_templates.common_utils import ( update_responses_input_with_model_file_ids, ) - + # Create a base64-encoded unified file ID # This decodes to: litellm_proxy:application/pdf;unified_id,6c0b5890-8914-48e0-b8f4-0ae5ed3c14a5;target_model_names,gpt-4o;llm_output_file_id,file-ECBPW7ML9g7XHdwGgUPZaM;llm_output_file_model_id,e26453f9e76e7993680d0068d98c1f4cc205bbad0967a33c664893568ca743c2 unified_file_id = "bGl0ZWxsbV9wcm94eTphcHBsaWNhdGlvbi9wZGY7dW5pZmllZF9pZCw2YzBiNTg5MC04OTE0LTQ4ZTAtYjhmNC0wYWU1ZWQzYzE0YTU7dGFyZ2V0X21vZGVsX25hbWVzLGdwdC00bztsbG1fb3V0cHV0X2ZpbGVfaWQsZmlsZS1FQ0JQVzdNTDlnN1hIZHdHZ1VQWmFNO2xsbV9vdXRwdXRfZmlsZV9tb2RlbF9pZCxlMjY0NTNmOWU3NmU3OTkzNjgwZDAwNjhkOThjMWY0Y2MyMDViYmFkMDk2N2EzM2M2NjQ4OTM1NjhjYTc0M2My" @@ -496,7 +498,7 @@ def test_update_responses_input_with_regular_file_id(): from litellm.litellm_core_utils.prompt_templates.common_utils import ( update_responses_input_with_model_file_ids, ) - + # Regular OpenAI file ID (not a unified file ID) regular_file_id = "file-abc123xyz" @@ -549,7 +551,7 @@ def test_update_responses_input_with_multiple_file_ids(): from litellm.litellm_core_utils.prompt_templates.common_utils import ( update_responses_input_with_model_file_ids, ) - + # Unified file ID unified_file_id = "bGl0ZWxsbV9wcm94eTphcHBsaWNhdGlvbi9wZGY7dW5pZmllZF9pZCw2YzBiNTg5MC04OTE0LTQ4ZTAtYjhmNC0wYWU1ZWQzYzE0YTU7dGFyZ2V0X21vZGVsX25hbWVzLGdwdC00bztsbG1fb3V0cHV0X2ZpbGVfaWQsZmlsZS1FQ0JQVzdNTDlnN1hIZHdHZ1VQWmFNO2xsbV9vdXRwdXRfZmlsZV9tb2RlbF9pZCxlMjY0NTNmOWU3NmU3OTkzNjgwZDAwNjhkOThjMWY0Y2MyMDViYmFkMDk2N2EzM2M2NjQ4OTM1NjhjYTc0M2My" # Regular OpenAI file ID @@ -827,3 +829,637 @@ async def test_afile_retrieve_raises_error_for_non_managed_file(): ) assert "not found" in str(exc_info.value) + + +@pytest.mark.asyncio +async def test_list_batches_from_managed_objects_table(): + from openai.types.batch import BatchRequestCounts + + from litellm.proxy._types import UserAPIKeyAuth + + prisma_client = AsyncMock() + + batch_record_1 = MagicMock() + batch_record_1.unified_object_id = "unified-batch-id-1" + batch_record_1.file_object = json.dumps({ + "id": "batch_abc123", + "object": "batch", + "endpoint": "/v1/chat/completions", + "completion_window": "24h", + "status": "completed", + "created_at": 1234567890, + "input_file_id": "file-input-1", + "request_counts": {"total": 1, "completed": 1, "failed": 0}, + }) + + batch_record_2 = MagicMock() + batch_record_2.unified_object_id = "unified-batch-id-2" + batch_record_2.file_object = json.dumps({ + "id": "batch_xyz789", + "object": "batch", + "endpoint": "/v1/chat/completions", + "completion_window": "24h", + "status": "in_progress", + "created_at": 1234567891, + "input_file_id": "file-input-2", + "request_counts": {"total": 5, "completed": 2, "failed": 0}, + }) + + prisma_client.db.litellm_managedobjecttable.find_many.return_value = [ + batch_record_1, + batch_record_2, + ] + + proxy_managed_files = _PROXY_LiteLLMManagedFiles( + DualCache(), prisma_client=prisma_client + ) + + result = await proxy_managed_files.list_user_batches( + user_api_key_dict=UserAPIKeyAuth(user_id="test-user"), + limit=10, + ) + + assert result["object"] == "list" + assert len(result["data"]) == 2 + assert result["data"][0].id == "unified-batch-id-1" + assert result["data"][1].id == "unified-batch-id-2" + assert result["first_id"] == "unified-batch-id-1" + assert result["last_id"] == "unified-batch-id-2" + + # Should filter by user_id (created_by) + prisma_client.db.litellm_managedobjecttable.find_many.assert_called_once_with( + where={"file_purpose": "batch", "created_by": "test-user"}, + take=10, + order={"created_at": "desc"}, + ) + + +@pytest.mark.asyncio +async def test_list_batches_from_managed_objects_table_empty_list(): + from litellm.proxy._types import UserAPIKeyAuth + + prisma_client = AsyncMock() + prisma_client.db.litellm_managedobjecttable.find_many.return_value = [] + + proxy_managed_files = _PROXY_LiteLLMManagedFiles( + DualCache(), prisma_client=prisma_client + ) + + result = await proxy_managed_files.list_user_batches( + user_api_key_dict=UserAPIKeyAuth(user_id="test-user"), + ) + + assert result["object"] == "list" + assert len(result["data"]) == 0 + assert result["first_id"] is None + assert result["last_id"] is None + assert result["has_more"] is False + + # Verify where clause includes created_by filter + # Default take is 20 when no limit is provided + prisma_client.db.litellm_managedobjecttable.find_many.assert_called_once_with( + where={"file_purpose": "batch", "created_by": "test-user"}, + take=20, + order={"created_at": "desc"}, + ) + + +def _create_unified_batch_id(model_id: str, batch_id: str) -> str: + import base64 + unified_str = f"litellm_proxy;model_id:{model_id};llm_batch_id:{batch_id}" + return base64.urlsafe_b64encode(unified_str.encode()).decode().rstrip("=") + + +@pytest.mark.asyncio +async def test_list_batches_from_managed_objects_table_provider_filter_raises_exception(): + from litellm.proxy._types import UserAPIKeyAuth + + prisma_client = AsyncMock() + + proxy_managed_files = _PROXY_LiteLLMManagedFiles( + DualCache(), prisma_client=prisma_client + ) + + # Filtering by provider should raise Exception + with pytest.raises(Exception) as exc_info: + await proxy_managed_files.list_user_batches( + user_api_key_dict=UserAPIKeyAuth(user_id="test-user"), + limit=10, + provider="openai", + ) + + assert str(exc_info.value) == ( + "Filtering by 'provider' is not supported when using managed batches." + ) + + # Verify find_many was NOT called since exception is raised before database query + prisma_client.db.litellm_managedobjecttable.find_many.assert_not_called() + + +@pytest.mark.asyncio +async def test_list_batches_from_managed_objects_table_target_model_name_filter_raises_exception(): + from litellm.proxy._types import UserAPIKeyAuth + + prisma_client = AsyncMock() + + proxy_managed_files = _PROXY_LiteLLMManagedFiles( + DualCache(), prisma_client=prisma_client + ) + + # Filtering by provider should raise Exception + with pytest.raises(Exception) as exc_info: + await proxy_managed_files.list_user_batches( + user_api_key_dict=UserAPIKeyAuth(user_id="test-user"), + limit=10, + target_model_names="gpt-4o,gpt-3.5", + ) + + assert str(exc_info.value) == ( + "Filtering by 'target_model_names' is not supported when using managed batches." + ) + + # Verify find_many was NOT called since exception is raised before database query + prisma_client.db.litellm_managedobjecttable.find_many.assert_not_called() + +@pytest.mark.asyncio +async def test_list_batches_from_managed_objects_table_filters_by_created_by(): + from litellm.proxy._types import UserAPIKeyAuth + + prisma_client = AsyncMock() + + # Create batch for user1 + batch_user1 = MagicMock() + batch_user1.unified_object_id = "unified-batch-user1" + batch_user1.file_object = json.dumps({ + "id": "batch_user1_abc", + "object": "batch", + "endpoint": "/v1/chat/completions", + "completion_window": "24h", + "status": "completed", + "created_at": 1234567890, + "input_file_id": "file-input-user1", + "request_counts": {"total": 1, "completed": 1, "failed": 0}, + }) + + # Create batch for user2 + batch_user2 = MagicMock() + batch_user2.unified_object_id = "unified-batch-user2" + batch_user2.file_object = json.dumps({ + "id": "batch_user2_xyz", + "object": "batch", + "endpoint": "/v1/chat/completions", + "completion_window": "24h", + "status": "completed", + "created_at": 1234567891, + "input_file_id": "file-input-user2", + "request_counts": {"total": 2, "completed": 2, "failed": 0}, + }) + + proxy_managed_files = _PROXY_LiteLLMManagedFiles( + DualCache(), prisma_client=prisma_client + ) + + # Query with user1's API key - should only return user1's batch + prisma_client.db.litellm_managedobjecttable.find_many.return_value = [batch_user1] + result_user1 = await proxy_managed_files.list_user_batches( + user_api_key_dict=UserAPIKeyAuth(user_id="user1"), + limit=10, + ) + + assert len(result_user1["data"]) == 1 + assert result_user1["data"][0].id == "unified-batch-user1" + prisma_client.db.litellm_managedobjecttable.find_many.assert_called_with( + where={"file_purpose": "batch", "created_by": "user1"}, + take=10, + order={"created_at": "desc"}, + ) + + # Query with user2's API key - should only return user2's batch + prisma_client.db.litellm_managedobjecttable.find_many.return_value = [batch_user2] + result_user2 = await proxy_managed_files.list_user_batches( + user_api_key_dict=UserAPIKeyAuth(user_id="user2"), + limit=10, + ) + + assert len(result_user2["data"]) == 1 + assert result_user2["data"][0].id == "unified-batch-user2" + prisma_client.db.litellm_managedobjecttable.find_many.assert_called_with( + where={"file_purpose": "batch", "created_by": "user2"}, + take=10, + order={"created_at": "desc"}, + ) + + +@pytest.mark.asyncio +async def test_return_unified_file_id_includes_expires_at(): + from litellm.types.llms.openai import OpenAIFileObject + + # Create a mock file object with expires_at set + file_object = OpenAIFileObject( + id="file-abc123", + object="file", + bytes=1234, + created_at=1234567890, + filename="test.jsonl", + purpose="batch", + status="uploaded", + expires_at=1234657890, + ) + file_object._hidden_params = {"model_id": "test-model-id"} + + create_file_request = { + "file": ("test.jsonl", b"test content", "application/jsonl"), + "purpose": "batch", + } + + internal_usage_cache = MagicMock() + + result = await _PROXY_LiteLLMManagedFiles.return_unified_file_id( + file_objects=[file_object], + create_file_request=create_file_request, + internal_usage_cache=internal_usage_cache, + litellm_parent_otel_span=None, + target_model_names_list=["gpt-4o"], + ) + + # Verify expires_at is passed through + assert result.expires_at == 1234657890 + assert result.purpose == "batch" + assert result.filename == "test.jsonl" + assert result.bytes == 1234 + assert result.created_at == 1234567890 + assert _is_base64_encoded_unified_file_id(result.id) + + +# ============================================================================ +# Permission Tests - Cross-User Batch Access +# ============================================================================ +# These tests verify that batches and files created by one user +# cannot be accessed, modified, or cancelled by a different user. +# Reference: https://github.com/BerriAI/litellm/pull/17401/files + + +@pytest.mark.asyncio +async def test_user_b_cannot_retrieve_user_a_batch(): + """ + Test that User B cannot retrieve a batch created by User A. + + This verifies batch isolation between users at the database/hook level. + """ + from litellm.proxy._types import UserAPIKeyAuth + + prisma_client = AsyncMock() + + # Mock database to return User A as the creator + batch_record = MagicMock() + batch_record.created_by = "user_a_id" + prisma_client.db.litellm_managedobjecttable.find_first.return_value = batch_record + + proxy_managed_files = _PROXY_LiteLLMManagedFiles( + DualCache(), prisma_client=prisma_client + ) + + # User B tries to retrieve User A's batch + unified_batch_id = "bGl0ZWxsbV9wcm94eTttb2RlbF9pZDpteS1tb2RlbDtsbG1fYmF0Y2hfaWQ6YmF0Y2hfYWJjMTIz" + + with pytest.raises(HTTPException) as exc_info: + await proxy_managed_files.async_pre_call_hook( + user_api_key_dict=UserAPIKeyAuth( + user_id="user_b_id", parent_otel_span=MagicMock() + ), + cache=MagicMock(), + data={"batch_id": unified_batch_id}, + call_type="aretrieve_batch", + ) + + # Should raise 403 Permission Denied + assert exc_info.value.status_code == 403 + + +@pytest.mark.asyncio +async def test_user_b_cannot_cancel_user_a_batch(): + """ + Test that User B cannot cancel a batch created by User A. + """ + from litellm.proxy._types import UserAPIKeyAuth + + prisma_client = AsyncMock() + + # Mock database to return User A as the creator + batch_record = MagicMock() + batch_record.created_by = "user_a_id" + prisma_client.db.litellm_managedobjecttable.find_first.return_value = batch_record + + proxy_managed_files = _PROXY_LiteLLMManagedFiles( + DualCache(), prisma_client=prisma_client + ) + + # User B tries to cancel User A's batch + unified_batch_id = "bGl0ZWxsbV9wcm94eTttb2RlbF9pZDpteS1tb2RlbDtsbG1fYmF0Y2hfaWQ6YmF0Y2hfYWJjMTIz" + + with pytest.raises(HTTPException) as exc_info: + await proxy_managed_files.async_pre_call_hook( + user_api_key_dict=UserAPIKeyAuth( + user_id="user_b_id", parent_otel_span=MagicMock() + ), + cache=MagicMock(), + data={"batch_id": unified_batch_id}, + call_type="acancel_batch", + ) + + # Should raise 403 Permission Denied + assert exc_info.value.status_code == 403 + + +@pytest.mark.asyncio +async def test_user_a_can_retrieve_own_batch(): + """ + Test that User A can successfully retrieve their own batch. + + This is a positive test case to ensure permission checks don't block + legitimate access. + """ + from litellm.proxy._types import UserAPIKeyAuth + + prisma_client = AsyncMock() + + # Mock database to return User A as the creator + batch_record = MagicMock() + batch_record.created_by = "user_a_id" + prisma_client.db.litellm_managedobjecttable.find_first.return_value = batch_record + + proxy_managed_files = _PROXY_LiteLLMManagedFiles( + DualCache(), prisma_client=prisma_client + ) + + # User A retrieves their own batch + unified_batch_id = "bGl0ZWxsbV9wcm94eTttb2RlbF9pZDpteS1tb2RlbDtsbG1fYmF0Y2hfaWQ6YmF0Y2hfYWJjMTIz" + + # Should not raise an exception + result = await proxy_managed_files.async_pre_call_hook( + user_api_key_dict=UserAPIKeyAuth( + user_id="user_a_id", parent_otel_span=MagicMock() + ), + cache=MagicMock(), + data={"batch_id": unified_batch_id}, + call_type="aretrieve_batch", + ) + + # Should successfully return the decoded batch_id + assert "batch_id" in result + assert result["model"] == "my-model" + + +@pytest.mark.asyncio +async def test_user_b_cannot_retrieve_user_a_file(): + """ + Test that User B cannot retrieve a file created by User A. + """ + from litellm.proxy._types import UserAPIKeyAuth + + prisma_client = AsyncMock() + + # Mock database to return User A as the creator + file_record = MagicMock() + file_record.created_by = "user_a_id" + prisma_client.db.litellm_managedfiletable.find_first.return_value = file_record + + proxy_managed_files = _PROXY_LiteLLMManagedFiles( + MagicMock(), prisma_client=prisma_client + ) + + # User B tries to retrieve User A's file + unified_file_id = "bGl0ZWxsbV9wcm94eTphcHBsaWNhdGlvbi9qc29uO3VuaWZpZWRfaWQsZmlsZS1hYmMxMjM" + + with pytest.raises(HTTPException) as exc_info: + await proxy_managed_files.async_pre_call_hook( + user_api_key_dict=UserAPIKeyAuth( + user_id="user_b_id", parent_otel_span=MagicMock() + ), + cache=MagicMock(), + data={"file_id": unified_file_id}, + call_type="afile_retrieve", + ) + + # Should raise 403 Permission Denied + assert exc_info.value.status_code == 403 + + +@pytest.mark.asyncio +async def test_user_b_cannot_download_user_a_file_content(): + """ + Test that User B cannot download file content for User A's file. + """ + from litellm.proxy._types import UserAPIKeyAuth + + prisma_client = AsyncMock() + + # Mock database to return User A as the creator + file_record = MagicMock() + file_record.created_by = "user_a_id" + prisma_client.db.litellm_managedfiletable.find_first.return_value = file_record + + proxy_managed_files = _PROXY_LiteLLMManagedFiles( + MagicMock(), prisma_client=prisma_client + ) + + # User B tries to download User A's file content + unified_file_id = "bGl0ZWxsbV9wcm94eTphcHBsaWNhdGlvbi9qc29uO3VuaWZpZWRfaWQsZmlsZS1hYmMxMjM" + + with pytest.raises(HTTPException) as exc_info: + await proxy_managed_files.async_pre_call_hook( + user_api_key_dict=UserAPIKeyAuth( + user_id="user_b_id", parent_otel_span=MagicMock() + ), + cache=MagicMock(), + data={"file_id": unified_file_id}, + call_type="afile_content", + ) + + # Should raise 403 Permission Denied + assert exc_info.value.status_code == 403 + + +@pytest.mark.asyncio +async def test_user_b_cannot_delete_user_a_file(): + """ + Test that User B cannot delete a file created by User A. + """ + from litellm.proxy._types import UserAPIKeyAuth + + prisma_client = AsyncMock() + + # Mock database to return User A as the creator + file_record = MagicMock() + file_record.created_by = "user_a_id" + prisma_client.db.litellm_managedfiletable.find_first.return_value = file_record + + proxy_managed_files = _PROXY_LiteLLMManagedFiles( + MagicMock(), prisma_client=prisma_client + ) + + # User B tries to delete User A's file + unified_file_id = "bGl0ZWxsbV9wcm94eTphcHBsaWNhdGlvbi9qc29uO3VuaWZpZWRfaWQsZmlsZS1hYmMxMjM" + + with pytest.raises(HTTPException) as exc_info: + await proxy_managed_files.async_pre_call_hook( + user_api_key_dict=UserAPIKeyAuth( + user_id="user_b_id", parent_otel_span=MagicMock() + ), + cache=MagicMock(), + data={"file_id": unified_file_id}, + call_type="afile_delete", + ) + + # Should raise 403 Permission Denied + assert exc_info.value.status_code == 403 + + +@pytest.mark.asyncio +async def test_user_a_can_retrieve_own_file(): + """ + Test that User A can successfully retrieve their own file. + + Positive test case to ensure permission checks work correctly for the owner. + """ + from litellm.proxy._types import UserAPIKeyAuth + + prisma_client = AsyncMock() + + # Mock database to return User A as the creator + file_record = MagicMock() + file_record.created_by = "user_a_id" + file_record.model_mappings = '{"model-123": "file-abc123"}' + file_record.file_object = json.dumps({ + "id": "file-abc123", + "object": "file", + "bytes": 1234, + "created_at": 1234567890, + "filename": "test.jsonl", + "purpose": "batch", + }) + prisma_client.db.litellm_managedfiletable.find_first.return_value = file_record + + proxy_managed_files = _PROXY_LiteLLMManagedFiles( + MagicMock(), prisma_client=prisma_client + ) + + # User A retrieves their own file + unified_file_id = "bGl0ZWxsbV9wcm94eTphcHBsaWNhdGlvbi9qc29uO3VuaWZpZWRfaWQsZmlsZS1hYmMxMjM" + + # Should not raise an exception + result = await proxy_managed_files.async_pre_call_hook( + user_api_key_dict=UserAPIKeyAuth( + user_id="user_a_id", parent_otel_span=MagicMock() + ), + cache=MagicMock(), + data={"file_id": unified_file_id}, + call_type="afile_retrieve", + ) + + # Should successfully return the decoded file_id + assert "file_id" in result + + +@pytest.mark.asyncio +async def test_list_batches_only_returns_user_own_batches(): + """ + Test that list_user_batches only returns batches created by the requesting user. + + This ensures users cannot see other users' batches in list operations. + """ + from litellm.proxy._types import UserAPIKeyAuth + + prisma_client = AsyncMock() + + # Create batches for User A + batch_user_a = MagicMock() + batch_user_a.unified_object_id = "batch-user-a" + batch_user_a.file_object = json.dumps({ + "id": "batch_a", + "object": "batch", + "endpoint": "/v1/chat/completions", + "completion_window": "24h", + "status": "completed", + "created_at": 1234567890, + "input_file_id": "file-a", + "request_counts": {"total": 1, "completed": 1, "failed": 0}, + }) + + # Mock database to only return User A's batches + prisma_client.db.litellm_managedobjecttable.find_many.return_value = [batch_user_a] + + proxy_managed_files = _PROXY_LiteLLMManagedFiles( + DualCache(), prisma_client=prisma_client + ) + + # User A requests their batches + result = await proxy_managed_files.list_user_batches( + user_api_key_dict=UserAPIKeyAuth(user_id="user_a_id"), + limit=10, + ) + + # Should only return User A's batches + assert len(result["data"]) == 1 + assert result["data"][0].id == "batch-user-a" + + # Verify the database query filtered by user_id + prisma_client.db.litellm_managedobjecttable.find_many.assert_called_once_with( + where={"file_purpose": "batch", "created_by": "user_a_id"}, + take=10, + order={"created_at": "desc"}, + ) + + +@pytest.mark.asyncio +async def test_same_user_different_keys_can_access_batch(): + """ + Test that different API keys for the same user can access the same batch. + + This verifies that permission checks are based on user_id, not API key, + allowing users to have multiple keys that can all access their resources. + """ + from litellm.proxy._types import UserAPIKeyAuth + + prisma_client = AsyncMock() + + # Mock database to return the user_id as creator + batch_record = MagicMock() + batch_record.created_by = "user_a_id" + prisma_client.db.litellm_managedobjecttable.find_first.return_value = batch_record + + proxy_managed_files = _PROXY_LiteLLMManagedFiles( + DualCache(), prisma_client=prisma_client + ) + + unified_batch_id = "bGl0ZWxsbV9wcm94eTttb2RlbF9pZDpteS1tb2RlbDtsbG1fYmF0Y2hfaWQ6YmF0Y2hfYWJjMTIz" + + # First API key for User A retrieves the batch + result1 = await proxy_managed_files.async_pre_call_hook( + user_api_key_dict=UserAPIKeyAuth( + user_id="user_a_id", + api_key="key-1", + parent_otel_span=MagicMock() + ), + cache=MagicMock(), + data={"batch_id": unified_batch_id}, + call_type="aretrieve_batch", + ) + + assert "batch_id" in result1 + + # Second API key for the same User A retrieves the batch + result2 = await proxy_managed_files.async_pre_call_hook( + user_api_key_dict=UserAPIKeyAuth( + user_id="user_a_id", + api_key="key-2", + parent_otel_span=MagicMock() + ), + cache=MagicMock(), + data={"batch_id": unified_batch_id}, + call_type="aretrieve_batch", + ) + + assert "batch_id" in result2 + # Both keys should get the same result + assert result1["batch_id"] == result2["batch_id"] diff --git a/tests/guardrails_tests/test_lakera_v2.py b/tests/guardrails_tests/test_lakera_v2.py index 9e0244a4819..2a8731d5ecd 100644 --- a/tests/guardrails_tests/test_lakera_v2.py +++ b/tests/guardrails_tests/test_lakera_v2.py @@ -22,38 +22,58 @@ async def test_lakera_pre_call_hook_for_pii_masking(): # Setup the guardrail with specific entities config litellm._turn_on_debug() lakera_guardrail = LakeraAIGuardrail( - api_key=os.environ.get("LAKERA_API_KEY"), + api_key="test_key", ) - # Create a sample request with PII data - data = { - "messages": [ - {"role": "system", "content": "You are a helpful assistant."}, - {"role": "user", "content": "My credit card is 4111-1111-1111-1111 and my email is test@example.com. My phone number is 555-123-4567"} + # Mock response with PII detections in payload (with start/end positions for masking) + mock_response = { + 'payload': [ + {'detector_type': 'pii/credit_card', 'start': 18, 'end': 37, 'message_id': 1}, # "4111-1111-1111-1111" + {'detector_type': 'pii/email', 'start': 54, 'end': 70, 'message_id': 1}, # "test@example.com" ], - "model": "gpt-3.5-turbo", - "metadata": {} + 'flagged': True, + 'breakdown': [ + {'detector_type': 'pii/credit_card', 'detected': True, 'message_id': 1}, + {'detector_type': 'pii/email', 'detected': True, 'message_id': 1}, + ] } - # Mock objects needed for the pre-call hook - user_api_key_dict = UserAPIKeyAuth(api_key="test_key") - cache = DualCache() - - # Call the pre-call hook with the specified call type - modified_data = await lakera_guardrail.async_pre_call_hook( - user_api_key_dict=user_api_key_dict, - cache=cache, - data=data, - call_type="completion" - ) - print(modified_data) - - # Verify the messages have been modified to mask PII - assert modified_data["messages"][0]["content"] == "You are a helpful assistant." # System prompt should be unchanged - - user_message = modified_data["messages"][1]["content"] - assert "4111-1111-1111-1111" not in user_message - assert "test@example.com" not in user_message + with patch.object(lakera_guardrail, 'call_v2_guard', new_callable=AsyncMock) as mock_call: + mock_call.return_value = (mock_response, {}) + + # Create a sample request with PII data + data = { + "messages": [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "My credit card is 4111-1111-1111-1111 and my email is test@example.com. My phone number is 555-123-4567"} + ], + "model": "gpt-3.5-turbo", + "metadata": {} + } + + # Mock objects needed for the pre-call hook + user_api_key_dict = UserAPIKeyAuth(api_key="test_key") + cache = DualCache() + + # Call the pre-call hook with the specified call type + modified_data = await lakera_guardrail.async_pre_call_hook( + user_api_key_dict=user_api_key_dict, + cache=cache, + data=data, + call_type="completion" + ) + print(modified_data) + + # Verify the messages have been modified to mask PII + assert modified_data["messages"][0]["content"] == "You are a helpful assistant." # System prompt should be unchanged + + user_message = modified_data["messages"][1]["content"] + # Verify both credit card and email are masked + assert "4111-1111-1111-1111" not in user_message + assert "test@example.com" not in user_message + # Verify masking placeholders are present + assert "[MASKED CREDIT_CARD]" in user_message + assert "[MASKED EMAIL]" in user_message @pytest.mark.asyncio diff --git a/tests/litellm-proxy-extras/test_litellm_proxy_extras_utils.py b/tests/litellm-proxy-extras/test_litellm_proxy_extras_utils.py index 7c151c80ae3..597c0845d43 100644 --- a/tests/litellm-proxy-extras/test_litellm_proxy_extras_utils.py +++ b/tests/litellm-proxy-extras/test_litellm_proxy_extras_utils.py @@ -97,6 +97,11 @@ class TestIdempotentErrorDetection: error_message = "constraint 'fk_user_id' already exists" assert ProxyExtrasDBManager._is_idempotent_error(error_message) is True + def test_is_idempotent_error_does_not_exist(self): + """Test detection of 'does not exist' error""" + error_message = "ERROR: index 'idx' does not exist" + assert ProxyExtrasDBManager._is_idempotent_error(error_message) is True + def test_is_idempotent_error_case_insensitive(self): """Test that idempotent error detection is case insensitive""" error_message = "COLUMN 'ID' ALREADY EXISTS" diff --git a/tests/litellm/llms/oci/chat/test_oci_chat_transformation.py b/tests/litellm/llms/oci/chat/test_oci_chat_transformation.py index f706a025a09..0a6c59d1b44 100644 --- a/tests/litellm/llms/oci/chat/test_oci_chat_transformation.py +++ b/tests/litellm/llms/oci/chat/test_oci_chat_transformation.py @@ -203,6 +203,7 @@ class TestOCIImageUrlTransformation: """Tests for OCI image_url format handling in multimodal messages. Fixes: https://github.com/BerriAI/litellm/issues/18270 + Fixes: https://github.com/BerriAI/litellm/issues/19589 """ def test_image_url_as_string(self): @@ -224,7 +225,8 @@ class TestOCIImageUrlTransformation: assert len(result) == 1 assert result[0].role == "USER" assert len(result[0].content) == 2 - assert result[0].content[1].imageUrl == "https://example.com/image.png" + # imageUrl is now an OCIImageUrl object with a 'url' property + assert result[0].content[1].imageUrl.url == "https://example.com/image.png" def test_image_url_as_openai_object(self): """Test that image_url as OpenAI-style object {"url": "..."} works.""" @@ -245,7 +247,38 @@ class TestOCIImageUrlTransformation: assert len(result) == 1 assert result[0].role == "USER" assert len(result[0].content) == 2 - assert result[0].content[1].imageUrl == "https://example.com/image.png" + # imageUrl is now an OCIImageUrl object with a 'url' property + assert result[0].content[1].imageUrl.url == "https://example.com/image.png" + + def test_image_url_serializes_as_object(self): + """Test that imageUrl serializes as {"url": "..."} for OCI API. + + Fixes: https://github.com/BerriAI/litellm/issues/19589 + OCI expects imageUrl to be an object with a 'url' property, not a plain string. + """ + from litellm.llms.oci.chat.transformation import adapt_messages_to_generic_oci_standard + + messages = [ + { + "role": "user", + "content": [ + {"type": "text", "text": "Describe this image."}, + {"type": "image_url", "image_url": {"url": "data:image/png;base64,ABC123"}}, + ], + } + ] + + result = adapt_messages_to_generic_oci_standard(messages) + image_part = result[0].content[1] + + # Serialize as OCI would receive it (with exclude_none=True) + serialized = image_part.model_dump(exclude_none=True) + + # Verify the structure matches OCI's expected format + assert serialized == { + "type": "IMAGE", + "imageUrl": {"url": "data:image/png;base64,ABC123"} + } def test_image_url_invalid_type_raises_error(self): """Test that invalid image_url type raises an error.""" diff --git a/tests/litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py b/tests/litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py new file mode 100644 index 00000000000..ac2c945baa6 --- /dev/null +++ b/tests/litellm/proxy/_experimental/mcp_server/test_discoverable_endpoints.py @@ -0,0 +1,1108 @@ +"""Tests for MCP OAuth discoverable endpoints""" +import pytest +from unittest.mock import AsyncMock, MagicMock, patch + + +@pytest.mark.asyncio +async def test_authorize_endpoint_includes_response_type(): + """Test that authorize endpoint includes response_type=code parameter (fixes #15684)""" + try: + from litellm.proxy._experimental.mcp_server.discoverable_endpoints import ( + authorize, + ) + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + global_mcp_server_manager, + ) + from litellm.types.mcp import MCPAuth + from litellm.types.mcp_server.mcp_server_manager import MCPServer + from litellm.proxy._types import MCPTransport + from fastapi import Request + except ImportError: + pytest.skip("MCP discoverable endpoints not available") + + # Clear registry + global_mcp_server_manager.registry.clear() + + # Create mock OAuth2 server + oauth2_server = MCPServer( + server_id="test_oauth_server", + name="test_oauth", + server_name="test_oauth", + alias="test_oauth", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + client_id="test_client_id", + client_secret="test_client_secret", + authorization_url="https://provider.com/oauth/authorize", + token_url="https://provider.com/oauth/token", + scopes=["read", "write"], + ) + global_mcp_server_manager.registry[oauth2_server.server_id] = oauth2_server + + # Mock request + mock_request = MagicMock(spec=Request) + mock_request.base_url = "https://litellm.example.com/" + mock_request.headers = {} + + # Mock the encryption functions to avoid needing a signing key + with patch( + "litellm.proxy._experimental.mcp_server.discoverable_endpoints.encrypt_value_helper" + ) as mock_encrypt: + mock_encrypt.return_value = "mocked_encrypted_state" + + # Call authorize endpoint + response = await authorize( + request=mock_request, + client_id="test_client_id", + mcp_server_name="test_oauth", + redirect_uri="https://client.example.com/callback", + state="test_state", + ) + + # Verify response is a redirect + assert response.status_code == 307 # FastAPI RedirectResponse default + + # Verify response_type is in the redirect URL + assert "response_type=code" in response.headers["location"] + assert "https://provider.com/oauth/authorize" in response.headers["location"] + assert "client_id=test_client_id" in response.headers["location"] + assert "scope=read+write" in response.headers["location"] + + +@pytest.mark.asyncio +async def test_authorize_endpoint_forwards_pkce_parameters(): + """Test that authorize endpoint forwards PKCE parameters (code_challenge and code_challenge_method)""" + try: + from litellm.proxy._experimental.mcp_server.discoverable_endpoints import ( + authorize, + ) + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + global_mcp_server_manager, + ) + from litellm.types.mcp import MCPAuth + from litellm.types.mcp_server.mcp_server_manager import MCPServer + from litellm.proxy._types import MCPTransport + from fastapi import Request + except ImportError: + pytest.skip("MCP discoverable endpoints not available") + + # Clear registry + global_mcp_server_manager.registry.clear() + + # Create mock OAuth2 server (simulating Google OAuth) + oauth2_server = MCPServer( + server_id="google_mcp", + name="google_mcp", + server_name="google_mcp", + alias="google_mcp", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + client_id="669428968603-test.apps.googleusercontent.com", + client_secret="GOCSPX-test_secret", + authorization_url="https://accounts.google.com/o/oauth2/v2/auth", + token_url="https://oauth2.googleapis.com/token", + scopes=["https://www.googleapis.com/auth/drive", "openid", "email"], + ) + global_mcp_server_manager.registry[oauth2_server.server_id] = oauth2_server + + # Mock request + mock_request = MagicMock(spec=Request) + mock_request.base_url = "https://litellm-proxy.example.com/" + mock_request.headers = {} + + # Mock the encryption function + with patch( + "litellm.proxy._experimental.mcp_server.discoverable_endpoints.encrypt_value_helper" + ) as mock_encrypt: + mock_encrypt.return_value = "mocked_encrypted_state_with_pkce" + + # Call authorize endpoint with PKCE parameters + response = await authorize( + request=mock_request, + client_id="669428968603-test.apps.googleusercontent.com", + mcp_server_name="google_mcp", + redirect_uri="http://localhost:60108/callback", + state="test_client_state", + code_challenge="x6YH_qgwbvOzbsHDuL1sW9gYkR9-gObUiIB5RkPwxDk", + code_challenge_method="S256", + ) + + # Verify response is a redirect + assert response.status_code == 307 + + # Verify PKCE parameters are included in the redirect URL + location = response.headers["location"] + assert "https://accounts.google.com/o/oauth2/v2/auth" in location + assert "code_challenge=x6YH_qgwbvOzbsHDuL1sW9gYkR9-gObUiIB5RkPwxDk" in location + assert "code_challenge_method=S256" in location + assert "client_id=669428968603-test.apps.googleusercontent.com" in location + assert "response_type=code" in location + + +@pytest.mark.asyncio +async def test_token_endpoint_forwards_code_verifier(): + """Test that token endpoint forwards code_verifier for PKCE flow""" + try: + from litellm.proxy._experimental.mcp_server.discoverable_endpoints import ( + token_endpoint, + ) + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + global_mcp_server_manager, + ) + from litellm.types.mcp import MCPAuth + from litellm.types.mcp_server.mcp_server_manager import MCPServer + from litellm.proxy._types import MCPTransport + from fastapi import Request + import httpx + except ImportError: + pytest.skip("MCP discoverable endpoints not available") + + # Clear registry + global_mcp_server_manager.registry.clear() + + # Create mock OAuth2 server + oauth2_server = MCPServer( + server_id="google_mcp", + name="google_mcp", + server_name="google_mcp", + alias="google_mcp", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + client_id="669428968603-test.apps.googleusercontent.com", + client_secret="GOCSPX-test_secret", + authorization_url="https://accounts.google.com/o/oauth2/v2/auth", + token_url="https://oauth2.googleapis.com/token", + scopes=["https://www.googleapis.com/auth/drive", "openid", "email"], + ) + global_mcp_server_manager.registry[oauth2_server.server_id] = oauth2_server + + # Mock request + mock_request = MagicMock(spec=Request) + mock_request.base_url = "https://litellm-proxy.example.com/" + mock_request.headers = {} + + # Mock httpx client response + mock_response = MagicMock() + mock_response.json.return_value = { + "access_token": "ya29.test_access_token", + "token_type": "Bearer", + "expires_in": 3599, + "scope": "openid email https://www.googleapis.com/auth/drive", + } + mock_response.raise_for_status = MagicMock() + + # Mock the async httpx client with AsyncMock for async methods + from unittest.mock import AsyncMock + + with patch( + "litellm.proxy._experimental.mcp_server.discoverable_endpoints.get_async_httpx_client" + ) as mock_get_client: + mock_async_client = MagicMock() + # Use AsyncMock for the async post method + mock_async_client.post = AsyncMock(return_value=mock_response) + mock_get_client.return_value = mock_async_client + + # Call token endpoint with code_verifier + response = await token_endpoint( + request=mock_request, + grant_type="authorization_code", + code="4/test_authorization_code", + redirect_uri="http://localhost:60108/callback", + client_id="669428968603-test.apps.googleusercontent.com", + mcp_server_name="google_mcp", + client_secret="GOCSPX-test_secret", + code_verifier="test_code_verifier_from_client", + ) + + # Verify that the token endpoint was called with code_verifier + mock_async_client.post.assert_called_once() + call_args = mock_async_client.post.call_args + + # Check the data parameter includes code_verifier + assert call_args[1]["data"]["code_verifier"] == "test_code_verifier_from_client" + assert call_args[1]["data"]["code"] == "4/test_authorization_code" + assert ( + call_args[1]["data"]["client_id"] + == "669428968603-test.apps.googleusercontent.com" + ) + assert call_args[1]["data"]["client_secret"] == "GOCSPX-test_secret" + assert call_args[1]["data"]["grant_type"] == "authorization_code" + + # Verify response + response_data = response.body + import json + + token_data = json.loads(response_data) + assert token_data["access_token"] == "ya29.test_access_token" + assert token_data["token_type"] == "Bearer" + + +@pytest.mark.asyncio +async def test_register_client_without_mcp_server_name_returns_dummy(): + try: + from litellm.proxy._experimental.mcp_server.discoverable_endpoints import ( + register_client, + ) + from fastapi import Request + except ImportError: + pytest.skip("MCP discoverable endpoints not available") + + mock_request = MagicMock(spec=Request) + mock_request.base_url = "https://proxy.litellm.example/" + mock_request.headers = {} + with patch( + "litellm.proxy._experimental.mcp_server.discoverable_endpoints._read_request_body", + new=AsyncMock(return_value={}), + ): + result = await register_client(request=mock_request) + + assert result == { + "client_id": "dummy_client", + "client_secret": "dummy", + "redirect_uris": ["https://proxy.litellm.example/callback"], + } + + +@pytest.mark.asyncio +async def test_register_client_returns_existing_server_credentials(): + try: + from litellm.proxy._experimental.mcp_server.discoverable_endpoints import ( + register_client, + ) + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + global_mcp_server_manager, + ) + from litellm.types.mcp import MCPAuth + from litellm.types.mcp_server.mcp_server_manager import MCPServer + from litellm.proxy._types import MCPTransport + from fastapi import Request + except ImportError: + pytest.skip("MCP discoverable endpoints not available") + + global_mcp_server_manager.registry.clear() + oauth2_server = MCPServer( + server_id="stored_server", + name="stored_server", + server_name="stored_server", + alias="stored_server", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + client_id="existing-client", + client_secret="existing-secret", + authorization_url="https://provider.example/oauth/authorize", + token_url="https://provider.example/oauth/token", + ) + global_mcp_server_manager.registry[oauth2_server.server_id] = oauth2_server + + mock_request = MagicMock(spec=Request) + mock_request.base_url = "https://proxy.litellm.example/" + mock_request.headers = {} + + try: + with patch( + "litellm.proxy._experimental.mcp_server.discoverable_endpoints._read_request_body", + new=AsyncMock(return_value={}), + ): + result = await register_client( + request=mock_request, mcp_server_name=oauth2_server.server_name + ) + finally: + global_mcp_server_manager.registry.clear() + + assert result == { + "client_id": "stored_server", + "client_secret": "dummy", + "redirect_uris": ["https://proxy.litellm.example/callback"], + } + + +@pytest.mark.asyncio +async def test_register_client_remote_registration_success(): + try: + from litellm.proxy._experimental.mcp_server.discoverable_endpoints import ( + register_client, + ) + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + global_mcp_server_manager, + ) + from litellm.types.mcp import MCPAuth + from litellm.types.mcp_server.mcp_server_manager import MCPServer + from litellm.proxy._types import MCPTransport + from fastapi import Request + except ImportError: + pytest.skip("MCP discoverable endpoints not available") + + global_mcp_server_manager.registry.clear() + oauth2_server = MCPServer( + server_id="remote_server", + name="remote_server", + server_name="remote_server", + alias="remote_server", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + client_id=None, + client_secret=None, + authorization_url="https://provider.example/oauth/authorize", + token_url="https://provider.example/oauth/token", + registration_url="https://provider.example/oauth/register", + ) + global_mcp_server_manager.registry[oauth2_server.server_id] = oauth2_server + + mock_request = MagicMock(spec=Request) + mock_request.base_url = "https://proxy.litellm.example/" + mock_request.headers = {} + + request_payload = { + "client_name": "Litellm Proxy", + "grant_types": ["authorization_code", "refresh_token"], + "response_types": ["code"], + "token_endpoint_auth_method": "client_secret_post", + } + + mock_response = MagicMock() + mock_response.json.return_value = { + "client_id": "generated-client", + "client_secret": "generated-secret", + } + mock_response.raise_for_status = MagicMock() + mock_async_client = MagicMock() + mock_async_client.post = AsyncMock(return_value=mock_response) + + try: + with patch( + "litellm.proxy._experimental.mcp_server.discoverable_endpoints._read_request_body", + new=AsyncMock(return_value=request_payload), + ), patch( + "litellm.proxy._experimental.mcp_server.discoverable_endpoints.get_async_httpx_client", + return_value=mock_async_client, + ): + response = await register_client( + request=mock_request, mcp_server_name=oauth2_server.server_name + ) + finally: + global_mcp_server_manager.registry.clear() + + import json + + assert response.status_code == 200 + payload = json.loads(response.body.decode("utf-8")) + assert payload == mock_response.json.return_value + + mock_async_client.post.assert_called_once() + call_args = mock_async_client.post.call_args + assert call_args.args[0] == oauth2_server.registration_url + assert call_args.kwargs["headers"] == { + "Content-Type": "application/json", + "Accept": "application/json", + } + assert call_args.kwargs["json"]["redirect_uris"] == [ + "https://proxy.litellm.example/callback" + ] + assert call_args.kwargs["json"]["grant_types"] == request_payload["grant_types"] + assert ( + call_args.kwargs["json"]["token_endpoint_auth_method"] + == request_payload["token_endpoint_auth_method"] + ) + + +@pytest.mark.asyncio +async def test_authorize_endpoint_respects_x_forwarded_proto(): + """Test that authorize endpoint uses X-Forwarded-Proto header to construct correct redirect_uri""" + try: + from litellm.proxy._experimental.mcp_server.discoverable_endpoints import ( + authorize, + ) + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + global_mcp_server_manager, + ) + from litellm.types.mcp import MCPAuth + from litellm.types.mcp_server.mcp_server_manager import MCPServer + from litellm.proxy._types import MCPTransport + from fastapi import Request + except ImportError: + pytest.skip("MCP discoverable endpoints not available") + + # Clear registry + global_mcp_server_manager.registry.clear() + + # Create mock OAuth2 server + oauth2_server = MCPServer( + server_id="test_oauth_server", + name="test_oauth", + server_name="test_oauth", + alias="test_oauth", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + client_id="test_client_id", + client_secret="test_client_secret", + authorization_url="https://provider.com/oauth/authorize", + token_url="https://provider.com/oauth/token", + scopes=["read", "write"], + ) + global_mcp_server_manager.registry[oauth2_server.server_id] = oauth2_server + + # Mock request with http base_url but X-Forwarded-Proto: https + mock_request = MagicMock(spec=Request) + mock_request.base_url = "http://litellm.example.com/" # HTTP + mock_request.headers = {"X-Forwarded-Proto": "https"} # Behind HTTPS proxy + + # Mock the encryption functions + with patch( + "litellm.proxy._experimental.mcp_server.discoverable_endpoints.encrypt_value_helper" + ) as mock_encrypt: + mock_encrypt.return_value = "mocked_encrypted_state" + + # Call authorize endpoint + response = await authorize( + request=mock_request, + client_id="test_client_id", + mcp_server_name="test_oauth", + redirect_uri="https://client.example.com/callback", + state="test_state", + ) + + # Verify redirect URL uses HTTPS in the redirect_uri parameter + location = response.headers["location"] + + # The redirect_uri parameter sent to the OAuth provider should use HTTPS + assert ( + "redirect_uri=https%3A%2F%2Flitellm.example.com%2Fcallback" in location + or "redirect_uri=https://litellm.example.com/callback" in location + ) + + +@pytest.mark.asyncio +async def test_token_endpoint_respects_x_forwarded_proto(): + """Test that token endpoint uses X-Forwarded-Proto header for redirect_uri""" + try: + from litellm.proxy._experimental.mcp_server.discoverable_endpoints import ( + token_endpoint, + ) + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + global_mcp_server_manager, + ) + from litellm.types.mcp import MCPAuth + from litellm.types.mcp_server.mcp_server_manager import MCPServer + from litellm.proxy._types import MCPTransport + from fastapi import Request + except ImportError: + pytest.skip("MCP discoverable endpoints not available") + + # Clear registry + global_mcp_server_manager.registry.clear() + + # Create mock OAuth2 server + oauth2_server = MCPServer( + server_id="google_mcp", + name="google_mcp", + server_name="google_mcp", + alias="google_mcp", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + client_id="test_client_id", + client_secret="test_secret", + authorization_url="https://accounts.google.com/o/oauth2/v2/auth", + token_url="https://oauth2.googleapis.com/token", + scopes=["openid", "email"], + ) + global_mcp_server_manager.registry[oauth2_server.server_id] = oauth2_server + + # Mock request with http base_url but X-Forwarded-Proto: https + mock_request = MagicMock(spec=Request) + mock_request.base_url = "http://litellm-proxy.example.com/" # HTTP + mock_request.headers = {"X-Forwarded-Proto": "https"} # Behind HTTPS proxy + + # Mock httpx client response + mock_response = MagicMock() + mock_response.json.return_value = { + "access_token": "test_token", + "token_type": "Bearer", + "expires_in": 3599, + } + mock_response.raise_for_status = MagicMock() + + # Mock the async httpx client + mock_async_client = MagicMock() + mock_async_client.post = AsyncMock(return_value=mock_response) + + with patch( + "litellm.proxy._experimental.mcp_server.discoverable_endpoints.get_async_httpx_client" + ) as mock_get_client: + mock_get_client.return_value = mock_async_client + + # Call token endpoint + response = await token_endpoint( + request=mock_request, + grant_type="authorization_code", + code="test_code", + redirect_uri="http://localhost:60108/callback", + client_id="test_client_id", + mcp_server_name="google_mcp", + client_secret="test_secret", + ) + + # Verify that the redirect_uri sent to the provider uses HTTPS + call_args = mock_async_client.post.call_args + assert ( + call_args[1]["data"]["redirect_uri"] + == "https://litellm-proxy.example.com/callback" + ) + + +@pytest.mark.asyncio +async def test_oauth_protected_resource_standard_pattern(): + """Test that oauth_protected_resource_mcp_standard returns standard MCP URL pattern (/mcp/{server_name})""" + try: + from litellm.proxy._experimental.mcp_server.discoverable_endpoints import ( + oauth_protected_resource_mcp_standard, + ) + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + global_mcp_server_manager, + ) + from litellm.types.mcp import MCPAuth + from litellm.types.mcp_server.mcp_server_manager import MCPServer + from litellm.proxy._types import MCPTransport + from fastapi import Request + except ImportError: + pytest.skip("MCP discoverable endpoints not available") + + # Clear registry + global_mcp_server_manager.registry.clear() + + # Create mock OAuth2 server + oauth2_server = MCPServer( + server_id="test_server", + name="test_server", + server_name="test_server", + alias="test_server", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + client_id="test_client_id", + client_secret="test_client_secret", + authorization_url="https://provider.com/oauth/authorize", + token_url="https://provider.com/oauth/token", + scopes=["read", "write"], + ) + global_mcp_server_manager.registry[oauth2_server.server_id] = oauth2_server + + # Mock request + mock_request = MagicMock(spec=Request) + mock_request.base_url = "https://litellm.example.com/" + mock_request.headers = {} + + # Call the standard pattern endpoint + response = await oauth_protected_resource_mcp_standard( + request=mock_request, + mcp_server_name="test_server", + ) + + # Verify response uses standard MCP pattern: /mcp/{server_name} + assert response["resource"] == "https://litellm.example.com/mcp/test_server" + assert response["authorization_servers"][0] == "https://litellm.example.com/test_server" + assert response["scopes_supported"] == oauth2_server.scopes + + +@pytest.mark.asyncio +async def test_oauth_protected_resource_legacy_pattern(): + """Test that oauth_protected_resource_mcp returns legacy URL pattern (/{server_name}/mcp)""" + try: + from litellm.proxy._experimental.mcp_server.discoverable_endpoints import ( + oauth_protected_resource_mcp, + ) + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + global_mcp_server_manager, + ) + from litellm.types.mcp import MCPAuth + from litellm.types.mcp_server.mcp_server_manager import MCPServer + from litellm.proxy._types import MCPTransport + from fastapi import Request + except ImportError: + pytest.skip("MCP discoverable endpoints not available") + + # Clear registry + global_mcp_server_manager.registry.clear() + + # Create mock OAuth2 server + oauth2_server = MCPServer( + server_id="test_server", + name="test_server", + server_name="test_server", + alias="test_server", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + client_id="test_client_id", + client_secret="test_client_secret", + authorization_url="https://provider.com/oauth/authorize", + token_url="https://provider.com/oauth/token", + scopes=["read", "write"], + ) + global_mcp_server_manager.registry[oauth2_server.server_id] = oauth2_server + + # Mock request + mock_request = MagicMock(spec=Request) + mock_request.base_url = "https://litellm.example.com/" + mock_request.headers = {} + + # Call the legacy pattern endpoint + response = await oauth_protected_resource_mcp( + request=mock_request, + mcp_server_name="test_server", + ) + + # Verify response uses legacy pattern: /{server_name}/mcp + assert response["resource"] == "https://litellm.example.com/test_server/mcp" + assert response["authorization_servers"][0] == "https://litellm.example.com/test_server" + assert response["scopes_supported"] == oauth2_server.scopes + + +@pytest.mark.asyncio +async def test_oauth_protected_resource_respects_x_forwarded_proto(): + """Test that oauth_protected_resource_mcp uses X-Forwarded-Proto for URLs""" + try: + from litellm.proxy._experimental.mcp_server.discoverable_endpoints import ( + oauth_protected_resource_mcp, + ) + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + global_mcp_server_manager, + ) + from litellm.types.mcp import MCPAuth + from litellm.types.mcp_server.mcp_server_manager import MCPServer + from litellm.proxy._types import MCPTransport + from fastapi import Request + except ImportError: + pytest.skip("MCP discoverable endpoints not available") + # Clear registry + global_mcp_server_manager.registry.clear() + + # Create mock OAuth2 server + oauth2_server = MCPServer( + server_id="test_oauth_server", + name="test_oauth", + server_name="test_oauth", + alias="test_oauth", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + client_id="test_client_id", + client_secret="test_client_secret", + authorization_url="https://provider.com/oauth/authorize", + token_url="https://provider.com/oauth/token", + scopes=["read", "write"], + ) + global_mcp_server_manager.registry[oauth2_server.server_id] = oauth2_server + + # Mock request with http base_url but X-Forwarded-Proto: https + mock_request = MagicMock(spec=Request) + mock_request.base_url = "http://litellm.example.com/" # HTTP + mock_request.headers = {"X-Forwarded-Proto": "https"} # Behind HTTPS proxy + + # Call the endpoint + response = await oauth_protected_resource_mcp( + request=mock_request, + mcp_server_name="test_oauth", + ) + + # Verify response uses HTTPS URLs + assert response["authorization_servers"][0].startswith( + "https://litellm.example.com/" + ) + assert response["scopes_supported"] == oauth2_server.scopes + + +@pytest.mark.asyncio +async def test_oauth_authorization_server_respects_x_forwarded_proto(): + """Test that oauth_authorization_server_mcp uses X-Forwarded-Proto for URLs""" + try: + from litellm.proxy._experimental.mcp_server.discoverable_endpoints import ( + oauth_authorization_server_mcp, + ) + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + global_mcp_server_manager, + ) + from litellm.types.mcp import MCPAuth + from litellm.types.mcp_server.mcp_server_manager import MCPServer + from litellm.proxy._types import MCPTransport + from fastapi import Request + except ImportError: + pytest.skip("MCP discoverable endpoints not available") + # Clear registry + global_mcp_server_manager.registry.clear() + + # Create mock OAuth2 server + oauth2_server = MCPServer( + server_id="test_oauth_server", + name="test_oauth", + server_name="test_oauth", + alias="test_oauth", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + client_id="test_client_id", + client_secret="test_client_secret", + authorization_url="https://provider.com/oauth/authorize", + token_url="https://provider.com/oauth/token", + scopes=["read", "write"], + ) + global_mcp_server_manager.registry[oauth2_server.server_id] = oauth2_server + + # Mock request with http base_url but X-Forwarded-Proto: https + mock_request = MagicMock(spec=Request) + mock_request.base_url = "http://litellm.example.com/" # HTTP + mock_request.headers = {"X-Forwarded-Proto": "https"} # Behind HTTPS proxy + + # Call the endpoint + response = await oauth_authorization_server_mcp( + request=mock_request, + mcp_server_name="test_oauth", + ) + + # Verify response uses HTTPS URLs + assert response["authorization_endpoint"].startswith("https://litellm.example.com/") + assert response["token_endpoint"].startswith("https://litellm.example.com/") + assert response["registration_endpoint"].startswith("https://litellm.example.com/") + assert response["grant_types_supported"] == ["authorization_code", "refresh_token"] + assert response["scopes_supported"] == oauth2_server.scopes + + +@pytest.mark.asyncio +async def test_register_client_respects_x_forwarded_proto(): + """Test that register_client uses X-Forwarded-Proto for redirect_uris""" + try: + from litellm.proxy._experimental.mcp_server.discoverable_endpoints import ( + register_client, + ) + from fastapi import Request + except ImportError: + pytest.skip("MCP discoverable endpoints not available") + + # Mock request with http base_url but X-Forwarded-Proto: https + mock_request = MagicMock(spec=Request) + mock_request.base_url = "http://proxy.litellm.example/" # HTTP + mock_request.headers = {"X-Forwarded-Proto": "https"} # Behind HTTPS proxy + + with patch( + "litellm.proxy._experimental.mcp_server.discoverable_endpoints._read_request_body", + new=AsyncMock(return_value={}), + ): + result = await register_client(request=mock_request) + + # Verify the redirect_uris use HTTPS + assert result == { + "client_id": "dummy_client", + "client_secret": "dummy", + "redirect_uris": ["https://proxy.litellm.example/callback"], + } + + +@pytest.mark.asyncio +async def test_authorize_endpoint_respects_x_forwarded_host(): + """Test that authorize endpoint uses X-Forwarded-Host and X-Forwarded-Proto to construct correct redirect_uri""" + try: + from litellm.proxy._experimental.mcp_server.discoverable_endpoints import ( + authorize, + ) + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + global_mcp_server_manager, + ) + from litellm.types.mcp import MCPAuth + from litellm.types.mcp_server.mcp_server_manager import MCPServer + from litellm.proxy._types import MCPTransport + from fastapi import Request + except ImportError: + pytest.skip("MCP discoverable endpoints not available") + + # Clear registry + global_mcp_server_manager.registry.clear() + + # Create mock OAuth2 server + oauth2_server = MCPServer( + server_id="test_oauth_server", + name="test_oauth", + server_name="test_oauth", + alias="test_oauth", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + client_id="test_client_id", + client_secret="test_client_secret", + authorization_url="https://provider.com/oauth/authorize", + token_url="https://provider.com/oauth/token", + scopes=["read", "write"], + ) + global_mcp_server_manager.registry[oauth2_server.server_id] = oauth2_server + + # Mock request simulating nginx proxy: + # Internal: http://localhost:8888/github/mcp + # External: https://proxy.example.com/github/mcp + mock_request = MagicMock(spec=Request) + mock_request.base_url = "http://localhost:8888/github/mcp" + mock_request.headers = { + "X-Forwarded-Proto": "https", + "X-Forwarded-Host": "proxy.example.com", + } + + # Mock the encryption functions + with patch( + "litellm.proxy._experimental.mcp_server.discoverable_endpoints.encrypt_value_helper" + ) as mock_encrypt: + mock_encrypt.return_value = "mocked_encrypted_state" + + # Call authorize endpoint + response = await authorize( + request=mock_request, + client_id="test_client_id", + mcp_server_name="test_oauth", + redirect_uri="https://client.example.com/callback", + state="test_state", + ) + + # Verify redirect URL uses the forwarded host and scheme + location = response.headers["location"] + + # The redirect_uri parameter should use the external URL + assert ( + "redirect_uri=https%3A%2F%2Fproxy.example.com%2Fgithub%2Fmcp%2Fcallback" + in location + or "redirect_uri=https://proxy.example.com/github/mcp/callback" in location + ) + + +@pytest.mark.asyncio +async def test_token_endpoint_respects_x_forwarded_host(): + """Test that token endpoint uses X-Forwarded-Host and X-Forwarded-Proto for redirect_uri""" + try: + from litellm.proxy._experimental.mcp_server.discoverable_endpoints import ( + token_endpoint, + ) + from litellm.proxy._experimental.mcp_server.mcp_server_manager import ( + global_mcp_server_manager, + ) + from litellm.types.mcp import MCPAuth + from litellm.types.mcp_server.mcp_server_manager import MCPServer + from litellm.proxy._types import MCPTransport + from fastapi import Request + except ImportError: + pytest.skip("MCP discoverable endpoints not available") + + # Clear registry + global_mcp_server_manager.registry.clear() + + # Create mock OAuth2 server + oauth2_server = MCPServer( + server_id="google_mcp", + name="google_mcp", + server_name="google_mcp", + alias="google_mcp", + transport=MCPTransport.http, + auth_type=MCPAuth.oauth2, + client_id="test_client_id", + client_secret="test_secret", + authorization_url="https://accounts.google.com/o/oauth2/v2/auth", + token_url="https://oauth2.googleapis.com/token", + scopes=["openid", "email"], + ) + global_mcp_server_manager.registry[oauth2_server.server_id] = oauth2_server + + # Mock request simulating nginx proxy without port in host + mock_request = MagicMock(spec=Request) + mock_request.base_url = "http://localhost:8888/github/mcp" + mock_request.headers = { + "X-Forwarded-Proto": "https", + "X-Forwarded-Host": "proxy.example.com", + } + + # Mock httpx client response + mock_response = MagicMock() + mock_response.json.return_value = { + "access_token": "test_token", + "token_type": "Bearer", + "expires_in": 3599, + } + mock_response.raise_for_status = MagicMock() + + # Mock the async httpx client + mock_async_client = MagicMock() + mock_async_client.post = AsyncMock(return_value=mock_response) + + with patch( + "litellm.proxy._experimental.mcp_server.discoverable_endpoints.get_async_httpx_client" + ) as mock_get_client: + mock_get_client.return_value = mock_async_client + + # Call token endpoint + response = await token_endpoint( + request=mock_request, + grant_type="authorization_code", + code="test_code", + redirect_uri="http://localhost:60108/callback", + client_id="test_client_id", + mcp_server_name="google_mcp", + client_secret="test_secret", + ) + + # Verify that the redirect_uri sent to the provider uses the external URL + call_args = mock_async_client.post.call_args + assert ( + call_args[1]["data"]["redirect_uri"] + == "https://proxy.example.com/github/mcp/callback" + ) + + +@pytest.mark.parametrize( + "base_url,x_forwarded_proto,x_forwarded_host,x_forwarded_port,expected_url", + [ + # Case 1: No forwarded headers - use original URL as-is (no trailing slash) + ( + "http://localhost:4000/", + None, + None, + None, + "http://localhost:4000", + ), + # Case 2: Only X-Forwarded-Proto - change scheme only + ( + "http://localhost:4000/", + "https", + None, + None, + "https://localhost:4000", + ), + # Case 3: X-Forwarded-Proto + X-Forwarded-Host - change scheme and host + ( + "http://localhost:4000/", + "https", + "proxy.example.com", + None, + "https://proxy.example.com", + ), + # Case 4: X-Forwarded-Host with port included in host header + ( + "http://localhost:4000/", + "https", + "proxy.example.com:8080", + None, + "https://proxy.example.com:8080", + ), + # Case 5: X-Forwarded-Host + X-Forwarded-Port as separate headers + ( + "http://localhost:4000/", + "https", + "proxy.example.com", + "8443", + "https://proxy.example.com:8443", + ), + # Case 6: Only X-Forwarded-Host without proto - use original scheme + ( + "http://localhost:4000/", + None, + "proxy.example.com", + None, + "http://proxy.example.com", + ), + # Case 7: Only X-Forwarded-Port without host - preserves original port if present + # (This is safer behavior - X-Forwarded-Port alone is unusual) + ( + "http://localhost:4000/", + None, + None, + "8443", + "http://localhost:4000", # Original port preserved when already present + ), + # Case 8: Complex internal URL with path (path is preserved) + ( + "http://localhost:8888/github/mcp", + "https", + "proxy.example.com", + None, + "https://proxy.example.com/github/mcp", + ), + # Case 9: IPv6 address in X-Forwarded-Host (should not treat :: as port separator) + ( + "http://localhost:4000/", + "https", + "[2001:db8::1]", + None, + "https://[2001:db8::1]", + ), + # Case 10: IPv6 address with port + ( + "http://localhost:4000/", + "https", + "[2001:db8::1]:8080", + None, + "https://[2001:db8::1]:8080", + ), + # Case 11: X-Forwarded-Host already has port, X-Forwarded-Port also provided (host wins) + ( + "http://localhost:4000/", + "https", + "proxy.example.com:9000", + "8443", + "https://proxy.example.com:9000", + ), + # Case 12: Standard proxy setup (most common case) + ( + "http://127.0.0.1:8888/", + "https", + "chatproxy.company.com", + None, + "https://chatproxy.company.com", + ), + # Case 13: Internal URL already has port, X-Forwarded-Port does NOT override + # (safer behavior - preserves original port when X-Forwarded-Host not provided) + ( + "http://localhost:4000/", + None, + None, + "443", + "http://localhost:4000", # Original port preserved + ), + # Case 14: Original URL with existing port in netloc, X-Forwarded-Host replaces it + ( + "http://internal.local:8888/", + "https", + "external.com", + None, + "https://external.com", + ), + ], +) +def test_get_request_base_url_comprehensive( + base_url, x_forwarded_proto, x_forwarded_host, x_forwarded_port, expected_url +): + """Comprehensive test for get_request_base_url with various header combinations""" + try: + from litellm.proxy._experimental.mcp_server.discoverable_endpoints import ( + get_request_base_url, + ) + from fastapi import Request + except ImportError: + pytest.skip("MCP discoverable endpoints not available") + + # Create mock request + mock_request = MagicMock(spec=Request) + mock_request.base_url = base_url + + # Build headers dict + headers = {} + if x_forwarded_proto: + headers["X-Forwarded-Proto"] = x_forwarded_proto + if x_forwarded_host: + headers["X-Forwarded-Host"] = x_forwarded_host + if x_forwarded_port: + headers["X-Forwarded-Port"] = x_forwarded_port + + # Mock headers.get() to return our test values + def mock_get(header_name, default=None): + return headers.get(header_name, default) + + mock_request.headers.get = mock_get + + # Test the function + result = get_request_base_url(mock_request) + + # Verify result + assert result == expected_url, ( + f"Expected '{expected_url}' but got '{result}'\n" + f"Input: base_url={base_url}, " + f"X-Forwarded-Proto={x_forwarded_proto}, " + f"X-Forwarded-Host={x_forwarded_host}, " + f"X-Forwarded-Port={x_forwarded_port}" + ) diff --git a/tests/litellm/test_stream_chunk_builder_images.py b/tests/litellm/test_stream_chunk_builder_images.py new file mode 100644 index 00000000000..c51a14ede67 --- /dev/null +++ b/tests/litellm/test_stream_chunk_builder_images.py @@ -0,0 +1,242 @@ +""" +Test that stream_chunk_builder correctly preserves images from streaming chunks. + +This tests the fix for https://github.com/BerriAI/litellm/issues/19478 +where images from models like gemini-2.5-flash-image were lost when +rebuilding the response from streaming chunks. +""" +import pytest +import litellm +from litellm import stream_chunk_builder + + +def test_stream_chunk_builder_preserves_images(): + """ + Test that stream_chunk_builder correctly preserves images from streaming chunks. + """ + # Simulate streaming chunks from an image generation model + init_chunks = [ + { + "id": "chatcmpl-image-test", + "choices": [ + { + "index": 0, + "delta": { + "role": "assistant", + }, + "finish_reason": None, + } + ], + "created": 1737654321, + "model": "gemini/gemini-2.5-flash-image", + "object": "chat.completion.chunk", + }, + { + "id": "chatcmpl-image-test", + "choices": [ + { + "index": 0, + "delta": { + "images": [ + { + "image_url": { + "url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", + "detail": "auto" + }, + "index": 0, + "type": "image_url" + } + ], + }, + "finish_reason": None, + } + ], + "created": 1737654321, + "model": "gemini/gemini-2.5-flash-image", + "object": "chat.completion.chunk", + }, + { + "id": "chatcmpl-image-test", + "choices": [ + { + "index": 0, + "delta": {}, + "finish_reason": "stop", + } + ], + "created": 1737654321, + "model": "gemini/gemini-2.5-flash-image", + "object": "chat.completion.chunk", + }, + ] + + chunks = [] + for chunk in init_chunks: + chunks.append(litellm.ModelResponse(**chunk, stream=True)) + + response = stream_chunk_builder(chunks=chunks) + + # Verify that images are preserved in the rebuilt response + assert response.choices[0].message.images is not None, "Images should be preserved in stream_chunk_builder" + assert len(response.choices[0].message.images) == 1, "Should have exactly 1 image" + assert response.choices[0].message.images[0]["type"] == "image_url" + assert "base64" in response.choices[0].message.images[0]["image_url"]["url"] + + +def test_stream_chunk_builder_preserves_multiple_images(): + """ + Test that stream_chunk_builder correctly preserves multiple images from different chunks. + """ + init_chunks = [ + { + "id": "chatcmpl-multi-image-test", + "choices": [ + { + "index": 0, + "delta": { + "role": "assistant", + "content": "Here are your images:", + }, + "finish_reason": None, + } + ], + "created": 1737654321, + "model": "gemini/gemini-2.5-flash-image", + "object": "chat.completion.chunk", + }, + { + "id": "chatcmpl-multi-image-test", + "choices": [ + { + "index": 0, + "delta": { + "images": [ + { + "image_url": {"url": "data:image/png;base64,image1data", "detail": "auto"}, + "index": 0, + "type": "image_url" + } + ], + }, + "finish_reason": None, + } + ], + "created": 1737654321, + "model": "gemini/gemini-2.5-flash-image", + "object": "chat.completion.chunk", + }, + { + "id": "chatcmpl-multi-image-test", + "choices": [ + { + "index": 0, + "delta": { + "images": [ + { + "image_url": {"url": "data:image/png;base64,image2data", "detail": "auto"}, + "index": 1, + "type": "image_url" + } + ], + }, + "finish_reason": None, + } + ], + "created": 1737654321, + "model": "gemini/gemini-2.5-flash-image", + "object": "chat.completion.chunk", + }, + { + "id": "chatcmpl-multi-image-test", + "choices": [ + { + "index": 0, + "delta": {}, + "finish_reason": "stop", + } + ], + "created": 1737654321, + "model": "gemini/gemini-2.5-flash-image", + "object": "chat.completion.chunk", + }, + ] + + chunks = [] + for chunk in init_chunks: + chunks.append(litellm.ModelResponse(**chunk, stream=True)) + + response = stream_chunk_builder(chunks=chunks) + + # Verify content is preserved + assert response.choices[0].message.content == "Here are your images:" + + # Verify all images are preserved + assert response.choices[0].message.images is not None, "Images should be preserved" + assert len(response.choices[0].message.images) == 2, "Should have exactly 2 images" + assert "image1data" in response.choices[0].message.images[0]["image_url"]["url"] + assert "image2data" in response.choices[0].message.images[1]["image_url"]["url"] + + +def test_stream_chunk_builder_no_images(): + """ + Test that stream_chunk_builder works correctly when there are no images (regression test). + """ + init_chunks = [ + { + "id": "chatcmpl-no-image-test", + "choices": [ + { + "index": 0, + "delta": { + "role": "assistant", + "content": "Hello, ", + }, + "finish_reason": None, + } + ], + "created": 1737654321, + "model": "gpt-4", + "object": "chat.completion.chunk", + }, + { + "id": "chatcmpl-no-image-test", + "choices": [ + { + "index": 0, + "delta": { + "content": "world!", + }, + "finish_reason": None, + } + ], + "created": 1737654321, + "model": "gpt-4", + "object": "chat.completion.chunk", + }, + { + "id": "chatcmpl-no-image-test", + "choices": [ + { + "index": 0, + "delta": {}, + "finish_reason": "stop", + } + ], + "created": 1737654321, + "model": "gpt-4", + "object": "chat.completion.chunk", + }, + ] + + chunks = [] + for chunk in init_chunks: + chunks.append(litellm.ModelResponse(**chunk, stream=True)) + + response = stream_chunk_builder(chunks=chunks) + + # Verify content is preserved + assert response.choices[0].message.content == "Hello, world!" + + # Verify images attribute doesn't exist or is None (no images in this stream) + images = getattr(response.choices[0].message, 'images', None) + assert images is None, "Should not have images when none were in the stream" diff --git a/tests/litellm_core_utils/test_bedrock_converse_dedup_factory.py b/tests/litellm_core_utils/test_bedrock_converse_dedup_factory.py new file mode 100644 index 00000000000..0969c77299c --- /dev/null +++ b/tests/litellm_core_utils/test_bedrock_converse_dedup_factory.py @@ -0,0 +1,332 @@ + +import sys +import os +import pytest + +sys.path.insert(0, os.path.abspath(".")) + +from litellm.litellm_core_utils.prompt_templates.factory import ( + _bedrock_converse_messages_pt, + _deduplicate_bedrock_content_blocks, + _deduplicate_bedrock_tool_content, + BedrockConverseMessagesProcessor, +) + + +MODEL = "anthropic.claude-v2" +PROVIDER = "bedrock_converse" + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _make_duplicate_tool_result_messages(): + """Return messages where two consecutive tool-role messages reference the + same tool_call_id, simulating the duplication scenario.""" + return [ + {"role": "user", "content": "What's the weather?"}, + { + "role": "assistant", + "content": None, + "tool_calls": [ + { + "id": "tooluse_abc123", + "type": "function", + "function": { + "name": "get_weather", + "arguments": '{"location": "Paris"}', + }, + } + ], + }, + { + "role": "tool", + "tool_call_id": "tooluse_abc123", + "content": '{"temp": 22}', + }, + { + "role": "tool", + "tool_call_id": "tooluse_abc123", # DUPLICATE + "content": '{"temp": 22}', + }, + ] + + +def _make_duplicate_tool_use_messages(): + """Return messages where two consecutive assistant messages carry tool_calls + with the same id, simulating assistant-side duplication.""" + return [ + {"role": "user", "content": "Do something"}, + { + "role": "assistant", + "content": None, + "tool_calls": [ + { + "id": "tool_1", + "type": "function", + "function": {"name": "fn_a", "arguments": "{}"}, + }, + ], + }, + { + "role": "assistant", + "content": None, + "tool_calls": [ + { + "id": "tool_1", # DUPLICATE + "type": "function", + "function": {"name": "fn_a", "arguments": "{}"}, + }, + ], + }, + # Need a tool result so the conversation is valid + { + "role": "tool", + "tool_call_id": "tool_1", + "content": '{"ok": true}', + }, + ] + + +def _extract_blocks(result, role, key): + """Extract all content blocks containing ``key`` from messages with ``role``.""" + return [ + block + for msg in result + if msg["role"] == role + for block in msg["content"] + if key in block + ] + + +# --------------------------------------------------------------------------- +# toolResult dedup tests +# --------------------------------------------------------------------------- + + +def test_bedrock_converse_deduplicates_tool_results(): + """Verify _bedrock_converse_messages_pt deduplicates toolResult blocks + with the same toolUseId when merging consecutive tool messages.""" + messages = _make_duplicate_tool_result_messages() + result = _bedrock_converse_messages_pt(messages, MODEL, PROVIDER) + + tool_results = _extract_blocks(result, "user", "toolResult") + ids = [tr["toolResult"]["toolUseId"] for tr in tool_results] + assert ids.count("tooluse_abc123") == 1 + + +@pytest.mark.asyncio +async def test_bedrock_converse_deduplicates_tool_results_async(): + """Verify the async path also deduplicates toolResult blocks with the + same toolUseId when merging consecutive tool messages.""" + messages = _make_duplicate_tool_result_messages() + result = await BedrockConverseMessagesProcessor._bedrock_converse_messages_pt_async( + messages, MODEL, PROVIDER + ) + + tool_results = _extract_blocks(result, "user", "toolResult") + ids = [tr["toolResult"]["toolUseId"] for tr in tool_results] + assert ids.count("tooluse_abc123") == 1 + + +def test_bedrock_converse_preserves_unique_tool_results(): + """Different toolUseIds should all be preserved.""" + messages = [ + {"role": "user", "content": "Weather and time?"}, + { + "role": "assistant", + "content": None, + "tool_calls": [ + { + "id": "tool_1", + "type": "function", + "function": {"name": "get_weather", "arguments": "{}"}, + }, + { + "id": "tool_2", + "type": "function", + "function": {"name": "get_time", "arguments": "{}"}, + }, + ], + }, + {"role": "tool", "tool_call_id": "tool_1", "content": '{"temp": 22}'}, + {"role": "tool", "tool_call_id": "tool_2", "content": '{"time": "14:00"}'}, + ] + + result = _bedrock_converse_messages_pt(messages, MODEL, PROVIDER) + + tool_results = _extract_blocks(result, "user", "toolResult") + assert len(tool_results) == 2 + ids = {tr["toolResult"]["toolUseId"] for tr in tool_results} + assert ids == {"tool_1", "tool_2"} + + +def test_bedrock_converse_dedup_preserves_cache_points(): + """cachePoint blocks should not be removed during dedup.""" + messages = [ + {"role": "user", "content": "Weather?"}, + { + "role": "assistant", + "content": None, + "tool_calls": [ + { + "id": "tool_1", + "type": "function", + "function": {"name": "get_weather", "arguments": "{}"}, + } + ], + }, + { + "role": "tool", + "tool_call_id": "tool_1", + "content": [ + { + "type": "text", + "text": "sunny", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "tool", + "tool_call_id": "tool_1", # DUPLICATE + "content": '{"temp": 22}', + }, + ] + + result = _bedrock_converse_messages_pt(messages, MODEL, PROVIDER) + + tool_results = _extract_blocks(result, "user", "toolResult") + cache_points = _extract_blocks(result, "user", "cachePoint") + + assert len(tool_results) == 1 + assert len(cache_points) == 1 + + +# --------------------------------------------------------------------------- +# toolUse dedup tests +# --------------------------------------------------------------------------- + + +def test_bedrock_converse_deduplicates_tool_use_sync(): + """Verify the sync path deduplicates toolUse blocks with the same + toolUseId when merging consecutive assistant messages.""" + messages = _make_duplicate_tool_use_messages() + result = _bedrock_converse_messages_pt(messages, MODEL, PROVIDER) + + tool_uses = _extract_blocks(result, "assistant", "toolUse") + ids = [tu["toolUse"]["toolUseId"] for tu in tool_uses] + assert ids.count("tool_1") == 1 + + +@pytest.mark.asyncio +async def test_bedrock_converse_deduplicates_tool_use_async(): + """Verify the async path deduplicates toolUse blocks with the same + toolUseId when merging consecutive assistant messages.""" + messages = _make_duplicate_tool_use_messages() + result = await BedrockConverseMessagesProcessor._bedrock_converse_messages_pt_async( + messages, MODEL, PROVIDER + ) + + tool_uses = _extract_blocks(result, "assistant", "toolUse") + ids = [tu["toolUse"]["toolUseId"] for tu in tool_uses] + assert ids.count("tool_1") == 1 + + +@pytest.mark.asyncio +async def test_bedrock_converse_tool_use_sync_async_parity(): + """Sync and async paths should produce identical results for duplicate + toolUse blocks.""" + messages = _make_duplicate_tool_use_messages() + sync_result = _bedrock_converse_messages_pt(messages, MODEL, PROVIDER) + async_result = await BedrockConverseMessagesProcessor._bedrock_converse_messages_pt_async( + messages, MODEL, PROVIDER + ) + assert sync_result == async_result + + +# --------------------------------------------------------------------------- +# Generalized helper unit tests +# --------------------------------------------------------------------------- + + +def test_deduplicate_bedrock_content_blocks_tool_result(): + """Direct unit test: first occurrence wins, duplicates dropped, non-tool + blocks preserved.""" + blocks = [ + {"toolResult": {"toolUseId": "id_1", "content": [{"text": "a"}]}}, + {"cachePoint": {"type": "default"}}, + {"toolResult": {"toolUseId": "id_1", "content": [{"text": "b"}]}}, # duplicate + {"toolResult": {"toolUseId": "id_2", "content": [{"text": "c"}]}}, + ] + + result = _deduplicate_bedrock_content_blocks(blocks, "toolResult") + + assert len(result) == 3 # id_1, cachePoint, id_2 + tool_ids = [b["toolResult"]["toolUseId"] for b in result if "toolResult" in b] + assert tool_ids == ["id_1", "id_2"] + # First-wins: content "a" is kept, "b" is dropped + assert result[0]["toolResult"]["content"] == [{"text": "a"}] + + +def test_deduplicate_bedrock_content_blocks_tool_use(): + """Direct unit test of toolUse dedup via the generalized helper.""" + blocks = [ + {"toolUse": {"toolUseId": "id_1", "name": "fn_a", "input": {}}}, + {"text": "thinking..."}, + {"toolUse": {"toolUseId": "id_1", "name": "fn_a", "input": {}}}, # duplicate + {"toolUse": {"toolUseId": "id_2", "name": "fn_b", "input": {}}}, + ] + + result = _deduplicate_bedrock_content_blocks(blocks, "toolUse") + + assert len(result) == 3 # id_1, text, id_2 + tool_ids = [b["toolUse"]["toolUseId"] for b in result if "toolUse" in b] + assert tool_ids == ["id_1", "id_2"] + + +def test_deduplicate_preserves_blocks_with_missing_id(): + """Blocks where toolUseId is None or empty should pass through without + dedup tracking (they cannot be compared).""" + blocks = [ + {"toolResult": {"toolUseId": None, "content": [{"text": "a"}]}}, + {"toolResult": {"toolUseId": "", "content": [{"text": "b"}]}}, + {"toolResult": {"toolUseId": "id_1", "content": [{"text": "c"}]}}, + ] + + result = _deduplicate_bedrock_content_blocks(blocks, "toolResult") + + # All three should be preserved — None and "" are not tracked + assert len(result) == 3 + + +def test_deduplicate_bedrock_tool_content_convenience_wrapper(): + """The convenience wrapper should behave identically to calling the + generalized helper with block_key='toolResult'.""" + blocks = [ + {"toolResult": {"toolUseId": "id_1", "content": [{"text": "a"}]}}, + {"toolResult": {"toolUseId": "id_1", "content": [{"text": "b"}]}}, + ] + + assert _deduplicate_bedrock_tool_content(blocks) == _deduplicate_bedrock_content_blocks(blocks, "toolResult") + + +# --------------------------------------------------------------------------- +# Sync/async parity for toolResult +# --------------------------------------------------------------------------- + + +@pytest.mark.asyncio +async def test_bedrock_converse_sync_async_parity_with_duplicates(): + """Sync and async paths should produce identical results with duplicate + tool results.""" + messages = _make_duplicate_tool_result_messages() + + sync_result = _bedrock_converse_messages_pt(messages, MODEL, PROVIDER) + async_result = await BedrockConverseMessagesProcessor._bedrock_converse_messages_pt_async( + messages, MODEL, PROVIDER + ) + + assert sync_result == async_result diff --git a/tests/llm_translation/test_azure_ai.py b/tests/llm_translation/test_azure_ai.py index 1633fb4cc82..972ba34a179 100644 --- a/tests/llm_translation/test_azure_ai.py +++ b/tests/llm_translation/test_azure_ai.py @@ -373,10 +373,17 @@ def test_completion_azure_ai_gpt_4o_with_flexible_api_base(api_base): async def test_azure_ai_model_router(): """ Test Azure AI model router non-streaming response cost tracking. + Verifies that the flat cost of $0.14 per M input tokens is applied. + + Tests the pattern: azure_ai/model_router/ + Where deployment-name is the Azure deployment (e.g., "azure-model-router"). + The model_router prefix is stripped before sending to Azure API. """ + from litellm.llms.azure_ai.cost_calculator import calculate_azure_model_router_flat_cost + litellm._turn_on_debug() response = await litellm.acompletion( - model="azure_ai/azure-model-router", + model="azure_ai/model_router/azure-model-router", messages=[{"role": "user", "content": "hi who is this"}], api_base="https://ishaa-mh6uutut-swedencentral.cognitiveservices.azure.com/openai/v1/", api_key=os.getenv("AZURE_MODEL_ROUTER_API_KEY"), @@ -387,6 +394,25 @@ async def test_azure_ai_model_router(): tracked_cost = response._hidden_params["response_cost"] assert tracked_cost > 0 print("Tracked cost: ", tracked_cost) + + # Verify flat cost is included using the helper function + usage = response.usage + if usage and usage.prompt_tokens: + expected_flat_cost = calculate_azure_model_router_flat_cost( + model="model_router/azure-model-router", + prompt_tokens=usage.prompt_tokens + ) + print(f"Prompt tokens: {usage.prompt_tokens}") + print(f"Expected flat cost: ${expected_flat_cost:.9f}") + print(f"Total tracked cost: ${tracked_cost:.9f}") + + # Total cost should be at least the flat cost + assert tracked_cost >= expected_flat_cost, ( + f"Cost ${tracked_cost:.9f} should be >= flat cost ${expected_flat_cost:.9f}" + ) + + # Verify the flat cost is non-zero + assert expected_flat_cost > 0, "Flat cost should be greater than 0" @pytest.mark.asyncio diff --git a/tests/llm_translation/test_bedrock_anthropic_regression.py b/tests/llm_translation/test_bedrock_anthropic_regression.py new file mode 100644 index 00000000000..df8755ba1ad --- /dev/null +++ b/tests/llm_translation/test_bedrock_anthropic_regression.py @@ -0,0 +1,526 @@ +""" +Regression tests for Bedrock Anthropic models. + +Tests critical functionality that has broken in the past between bedrock/invoke +and bedrock/converse routing: +1. Prompt caching support (cache_control) +2. 1M context window support (anthropic-beta header) + +These tests ensure that both routing methods (invoke vs converse) maintain +feature parity and prevent regression of previously fixed issues. +""" + +import json +import os +import sys +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +sys.path.insert(0, os.path.abspath("../..")) + +import litellm +from litellm import completion + + +# Large document for caching tests (needs 1024+ tokens for Claude models) +LARGE_DOCUMENT_FOR_CACHING = """ +This is a comprehensive legal agreement between Party A and Party B. + +ARTICLE 1: DEFINITIONS +1.1 "Agreement" means this document and all attachments. +1.2 "Confidential Information" means any non-public information. +1.3 "Effective Date" means the date of last signature. +1.4 "Term" means the period during which this Agreement is in effect. + +ARTICLE 2: SCOPE OF SERVICES +2.1 Party A agrees to provide the following services... +2.2 Party B agrees to compensate Party A for services rendered... +2.3 All services shall be performed in a professional manner... + +ARTICLE 3: PAYMENT TERMS +3.1 Payment shall be made within 30 days of invoice receipt. +3.2 Late payments shall accrue interest at 1.5% per month. +3.3 All fees are non-refundable unless otherwise specified. + +ARTICLE 4: INTELLECTUAL PROPERTY +4.1 All pre-existing IP remains with the original owner. +4.2 Work product created under this Agreement shall be owned by Party B. +4.3 Party A grants a license to use any tools or methodologies. + +ARTICLE 5: CONFIDENTIALITY +5.1 Both parties agree to maintain confidentiality of all shared information. +5.2 Confidential information shall not be disclosed to third parties. +5.3 This obligation survives termination of the Agreement. + +ARTICLE 6: TERMINATION +6.1 Either party may terminate with 30 days written notice. +6.2 Immediate termination is permitted for material breach. +6.3 Upon termination, all confidential information must be returned. + +ARTICLE 7: LIMITATION OF LIABILITY +7.1 Neither party shall be liable for consequential damages. +7.2 Total liability shall not exceed fees paid in the prior 12 months. +7.3 This limitation does not apply to willful misconduct. + +ARTICLE 8: DISPUTE RESOLUTION +8.1 Disputes shall first be addressed through good faith negotiation. +8.2 If negotiation fails, disputes shall be submitted to arbitration. +8.3 Arbitration shall be conducted under AAA rules. + +ARTICLE 9: GENERAL PROVISIONS +9.1 This Agreement constitutes the entire understanding between parties. +9.2 Amendments must be in writing and signed by both parties. +9.3 This Agreement shall be governed by the laws of Delaware. +9.4 Neither party may assign this Agreement without consent. +9.5 Waiver of any provision shall not constitute ongoing waiver. + +IN WITNESS WHEREOF, the parties have executed this Agreement. +""" * 8 # Repeat to ensure we have enough tokens (need 1024+ for Claude models) + + +class TestBedrockAnthropicPromptCachingRegression: + """ + Regression tests for prompt caching support across bedrock/invoke and bedrock/converse. + + Issue: Prompt caching broke between invoke and converse routing due to: + - Different cache_control syntax expectations + - Incorrect beta header handling + - Missing transformation for cachePoint vs cache_control + """ + + @pytest.mark.parametrize( + "model_prefix", + [ + "bedrock/invoke/", + "bedrock/converse/", + ], + ) + def test_prompt_caching_cache_control_transforms_correctly( + self, model_prefix + ): + """ + Test that cache_control in messages is correctly transformed for both invoke and converse APIs. + + Regression test: Ensure cache_control works the same way for both routing methods. + - bedrock/invoke uses cache_control directly in the Anthropic Messages API format + - bedrock/converse should transform to cachePoint format + """ + from litellm.llms.bedrock.chat.converse_transformation import ( + AmazonConverseConfig, + ) + from litellm.llms.bedrock.chat.invoke_transformations.anthropic_claude3_transformation import ( + AmazonAnthropicClaudeConfig, + ) + + messages = [ + { + "role": "user", + "content": [ + { + "type": "text", + "text": LARGE_DOCUMENT_FOR_CACHING, + "cache_control": {"type": "ephemeral"}, + }, + { + "type": "text", + "text": "What are the payment terms?", + }, + ], + }, + ] + + if "converse" in model_prefix: + config = AmazonConverseConfig() + result = config.transform_request( + model="us.anthropic.claude-3-7-sonnet-20250219-v1:0", + messages=messages, + optional_params={}, + litellm_params={}, + headers={}, + ) + + print(f"\n{model_prefix} Request body: {json.dumps(result, indent=2, default=str)}") + + # For converse, cache_control should be transformed to cachePoint + assert "messages" in result + user_msg = result["messages"][0] + assert "content" in user_msg + + # Check that cachePoint is present (Bedrock Converse format) + has_cache_point = any( + isinstance(c, dict) and "cachePoint" in c + for c in user_msg["content"] + ) + # The transformation should preserve the cache marking in some form + assert "messages" in result, "messages should be present in converse request" + + else: + config = AmazonAnthropicClaudeConfig() + result = config.transform_request( + model="us.anthropic.claude-3-7-sonnet-20250219-v1:0", + messages=messages, + optional_params={}, + litellm_params={}, + headers={}, + ) + + print(f"\n{model_prefix} Request body: {json.dumps(result, indent=2, default=str)}") + + # For invoke, cache_control should be preserved in messages content + assert "messages" in result + user_msg = result["messages"][0] + assert "content" in user_msg + + # Check that cache_control is preserved + has_cache_control = any( + isinstance(c, dict) and "cache_control" in c + for c in user_msg["content"] + ) + assert has_cache_control, "cache_control should be present in invoke messages" + + @pytest.mark.parametrize( + "model_prefix", + [ + "bedrock/invoke/", + "bedrock/converse/", + ], + ) + def test_prompt_caching_no_beta_header_added(self, model_prefix): + """ + Test that prompt-caching-2024-07-31 beta header is NOT added for Bedrock. + + Regression test: Bedrock recognizes prompt caching via cache_control in the + request body, NOT through beta headers. Adding the beta header breaks requests. + + This was a critical bug where litellm was incorrectly adding the Anthropic API + beta header to Bedrock requests. + """ + from litellm.llms.bedrock.chat.converse_transformation import ( + AmazonConverseConfig, + ) + from litellm.llms.bedrock.chat.invoke_transformations.anthropic_claude3_transformation import ( + AmazonAnthropicClaudeConfig, + ) + + messages = [ + { + "role": "user", + "content": [ + { + "type": "text", + "text": "Hello", + "cache_control": {"type": "ephemeral"}, + } + ], + } + ] + + if "converse" in model_prefix: + config = AmazonConverseConfig() + result = config._transform_request_helper( + model="us.anthropic.claude-3-7-sonnet-20250219-v1:0", + system_content_blocks=[], + optional_params={}, + messages=messages, + headers={}, + ) + else: + config = AmazonAnthropicClaudeConfig() + result = config.transform_request( + model="us.anthropic.claude-3-7-sonnet-20250219-v1:0", + messages=messages, + optional_params={}, + litellm_params={}, + headers={}, + ) + + # Verify prompt-caching beta header is NOT present + if "anthropic_beta" in result: + assert "prompt-caching-2024-07-31" not in result["anthropic_beta"], ( + f"{model_prefix}: prompt-caching-2024-07-31 should NOT be added as a beta header for Bedrock. " + "Bedrock recognizes prompt caching via cache_control in the request body, not beta headers." + ) + + # For converse, also check additionalModelRequestFields + if "converse" in model_prefix and "additionalModelRequestFields" in result: + additional_fields = result["additionalModelRequestFields"] + if "anthropic_beta" in additional_fields: + assert "prompt-caching-2024-07-31" not in additional_fields["anthropic_beta"] + + +class TestBedrockAnthropic1MContextRegression: + """ + Regression tests for 1M context window support across bedrock/invoke and bedrock/converse. + + Issue: 1M context support broke between invoke and converse routing due to: + - Missing anthropic-beta header passthrough in converse + - Incorrect handling of context-1m-2025-08-07 beta header + """ + + @pytest.mark.parametrize( + "model_prefix", + [ + "bedrock/invoke/", + "bedrock/converse/", + ], + ) + def test_1m_context_beta_header_is_passed_via_transformation(self, model_prefix): + """ + Test that the 1M context beta header is correctly passed to Bedrock API. + + Regression test: Ensure anthropic-beta: context-1m-2025-08-07 header + is correctly included in the request for both invoke and converse. + + This test verifies the transformation layer directly to avoid async complexity. + """ + from litellm.llms.bedrock.chat.converse_transformation import ( + AmazonConverseConfig, + ) + from litellm.llms.bedrock.chat.invoke_transformations.anthropic_claude3_transformation import ( + AmazonAnthropicClaudeConfig, + ) + + headers = {"anthropic-beta": "context-1m-2025-08-07"} + messages = [{"role": "user", "content": "Test message"}] + + if "converse" in model_prefix: + config = AmazonConverseConfig() + result = config._transform_request_helper( + model="us.anthropic.claude-3-5-sonnet-20241022-v2:0", + system_content_blocks=[], + optional_params={}, + messages=messages, + headers=headers, + ) + + print(f"\n{model_prefix} Request body: {json.dumps(result, indent=2, default=str)}") + + # For converse, beta header should be in additionalModelRequestFields + assert "additionalModelRequestFields" in result, ( + f"{model_prefix}: additionalModelRequestFields should be present for anthropic-beta headers" + ) + additional_fields = result["additionalModelRequestFields"] + assert "anthropic_beta" in additional_fields, ( + f"{model_prefix}: anthropic_beta should be in additionalModelRequestFields" + ) + assert "context-1m-2025-08-07" in additional_fields["anthropic_beta"], ( + f"{model_prefix}: context-1m-2025-08-07 should be in anthropic_beta array" + ) + else: + config = AmazonAnthropicClaudeConfig() + result = config.transform_request( + model="us.anthropic.claude-3-5-sonnet-20241022-v2:0", + messages=messages, + optional_params={}, + litellm_params={}, + headers=headers, + ) + + print(f"\n{model_prefix} Request body: {json.dumps(result, indent=2, default=str)}") + + # For invoke, beta header should be in top-level request + assert "anthropic_beta" in result, ( + f"{model_prefix}: anthropic_beta should be in request body" + ) + assert "context-1m-2025-08-07" in result["anthropic_beta"], ( + f"{model_prefix}: context-1m-2025-08-07 should be in anthropic_beta array" + ) + + @pytest.mark.parametrize( + "model_prefix", + [ + "bedrock/invoke/", + "bedrock/converse/", + ], + ) + def test_1m_context_beta_header_transformation(self, model_prefix): + """ + Test that the 1M context beta header is correctly transformed at the config level. + + This is a unit test that verifies the transformation logic directly without + making actual API calls. + """ + from litellm.llms.bedrock.chat.converse_transformation import ( + AmazonConverseConfig, + ) + from litellm.llms.bedrock.chat.invoke_transformations.anthropic_claude3_transformation import ( + AmazonAnthropicClaudeConfig, + ) + + headers = {"anthropic-beta": "context-1m-2025-08-07"} + messages = [{"role": "user", "content": "Test"}] + + if "converse" in model_prefix: + config = AmazonConverseConfig() + result = config._transform_request_helper( + model="us.anthropic.claude-3-5-sonnet-20241022-v2:0", + system_content_blocks=[], + optional_params={}, + messages=messages, + headers=headers, + ) + + # Verify beta header is in additionalModelRequestFields + assert "additionalModelRequestFields" in result + additional_fields = result["additionalModelRequestFields"] + assert "anthropic_beta" in additional_fields + assert "context-1m-2025-08-07" in additional_fields["anthropic_beta"] + + else: + config = AmazonAnthropicClaudeConfig() + result = config.transform_request( + model="us.anthropic.claude-3-5-sonnet-20241022-v2:0", + messages=messages, + optional_params={}, + litellm_params={}, + headers=headers, + ) + + # Verify beta header is in top-level request + assert "anthropic_beta" in result + assert "context-1m-2025-08-07" in result["anthropic_beta"] + + @pytest.mark.parametrize( + "model_prefix", + [ + "bedrock/invoke/", + "bedrock/converse/", + ], + ) + def test_1m_context_with_multiple_beta_headers(self, model_prefix): + """ + Test that 1M context header works alongside other beta headers. + + Ensures that multiple anthropic-beta values (comma-separated) are all + correctly passed through. + """ + from litellm.llms.bedrock.chat.converse_transformation import ( + AmazonConverseConfig, + ) + from litellm.llms.bedrock.chat.invoke_transformations.anthropic_claude3_transformation import ( + AmazonAnthropicClaudeConfig, + ) + + # Multiple beta headers including 1M context + headers = { + "anthropic-beta": "context-1m-2025-08-07,computer-use-2024-10-22" + } + messages = [{"role": "user", "content": "Test"}] + + if "converse" in model_prefix: + config = AmazonConverseConfig() + result = config._transform_request_helper( + model="us.anthropic.claude-3-5-sonnet-20241022-v2:0", + system_content_blocks=[], + optional_params={}, + messages=messages, + headers=headers, + ) + + additional_fields = result["additionalModelRequestFields"] + beta_headers = additional_fields["anthropic_beta"] + + else: + config = AmazonAnthropicClaudeConfig() + result = config.transform_request( + model="us.anthropic.claude-3-5-sonnet-20241022-v2:0", + messages=messages, + optional_params={}, + litellm_params={}, + headers=headers, + ) + + beta_headers = result["anthropic_beta"] + + # Verify both headers are present + assert "context-1m-2025-08-07" in beta_headers + assert "computer-use-2024-10-22" in beta_headers + + +class TestBedrockAnthropicCombinedRegressions: + """ + Tests that combine multiple features to ensure they work together. + """ + + @pytest.mark.parametrize( + "model_prefix", + [ + "bedrock/invoke/", + "bedrock/converse/", + ], + ) + def test_1m_context_with_prompt_caching(self, model_prefix): + """ + Test that 1M context and prompt caching work together. + + This is a real-world scenario where a user might want to use both features + simultaneously. + """ + from litellm.llms.bedrock.chat.converse_transformation import ( + AmazonConverseConfig, + ) + from litellm.llms.bedrock.chat.invoke_transformations.anthropic_claude3_transformation import ( + AmazonAnthropicClaudeConfig, + ) + + headers = {"anthropic-beta": "context-1m-2025-08-07"} + messages = [ + { + "role": "user", + "content": [ + { + "type": "text", + "text": LARGE_DOCUMENT_FOR_CACHING, + "cache_control": {"type": "ephemeral"}, + }, + { + "type": "text", + "text": "Summarize this document.", + }, + ], + } + ] + + if "converse" in model_prefix: + config = AmazonConverseConfig() + result = config._transform_request_helper( + model="us.anthropic.claude-3-7-sonnet-20250219-v1:0", + system_content_blocks=[], + optional_params={}, + messages=messages, + headers=headers, + ) + + # Should have 1M context header + additional_fields = result["additionalModelRequestFields"] + assert "anthropic_beta" in additional_fields + assert "context-1m-2025-08-07" in additional_fields["anthropic_beta"] + + # Should NOT have prompt-caching header + assert "prompt-caching-2024-07-31" not in additional_fields["anthropic_beta"] + + else: + config = AmazonAnthropicClaudeConfig() + result = config.transform_request( + model="us.anthropic.claude-3-7-sonnet-20250219-v1:0", + messages=messages, + optional_params={}, + litellm_params={}, + headers=headers, + ) + + # Should have 1M context header + assert "anthropic_beta" in result + assert "context-1m-2025-08-07" in result["anthropic_beta"] + + # Should NOT have prompt-caching header + assert "prompt-caching-2024-07-31" not in result["anthropic_beta"] + + # Should have cache_control in messages + user_msg = result["messages"][0] + has_cache_control = any( + isinstance(c, dict) and "cache_control" in c + for c in user_msg["content"] + ) + assert has_cache_control diff --git a/tests/llm_translation/test_bedrock_completion.py b/tests/llm_translation/test_bedrock_completion.py index 7c0db41d13a..9b0b69caeb3 100644 --- a/tests/llm_translation/test_bedrock_completion.py +++ b/tests/llm_translation/test_bedrock_completion.py @@ -3954,3 +3954,285 @@ def test_bedrock_openai_error_handling(): assert exc_info.value.status_code == 422 print("✓ Error handling works correctly") + +# ============================================================================ +# Nova Grounding (web_search_options) Unit Tests (Mocked) +# ============================================================================ + +def test_bedrock_nova_grounding_web_search_options_non_streaming(): + """ + Unit test for Nova grounding using web_search_options parameter (non-streaming). + + This test mocks the HTTP call to verify: + 1. web_search_options is correctly mapped to systemTool for Nova models + 2. The request structure is correct + + Related: https://docs.aws.amazon.com/nova/latest/userguide/grounding.html + """ + from unittest.mock import patch, MagicMock + from litellm.llms.custom_httpx.http_handler import HTTPHandler + + client = HTTPHandler() + + messages = [ + { + "role": "user", + "content": "What is the current population of Tokyo, Japan?", + } + ] + + with patch.object(client, "post") as mock_post: + try: + completion( + model="bedrock/us.amazon.nova-pro-v1:0", + messages=messages, + web_search_options={}, # Enables Nova grounding + max_tokens=500, + client=client, + ) + except Exception: + pass # Expected - we're just checking the request structure + + # Verify the request was made correctly + if mock_post.called: + request_body = json.loads(mock_post.call_args.kwargs.get("data", "{}")) + print(f"Request body: {json.dumps(request_body, indent=2)}") + + # Verify toolConfig is present with systemTool + assert "toolConfig" in request_body, "toolConfig should be in request" + tool_config = request_body["toolConfig"] + assert "tools" in tool_config, "tools should be in toolConfig" + + # Find the systemTool for nova_grounding + system_tool_found = False + for tool in tool_config["tools"]: + if "systemTool" in tool: + assert tool["systemTool"]["name"] == "nova_grounding" + system_tool_found = True + break + + assert system_tool_found, "systemTool with nova_grounding should be present" + print(f"✓ web_search_options correctly transformed to systemTool (non-streaming)") + + +def test_bedrock_nova_grounding_with_function_tools(): + """ + Unit test for Nova grounding combined with regular function tools. + + This tests the scenario where users want both web grounding AND + custom function calling capabilities. + """ + from unittest.mock import patch + from litellm.llms.custom_httpx.http_handler import HTTPHandler + + client = HTTPHandler() + + # Regular function tool + tools = [ + { + "type": "function", + "function": { + "name": "get_stock_price", + "description": "Get the current stock price for a given ticker symbol", + "parameters": { + "type": "object", + "properties": { + "ticker": { + "type": "string", + "description": "The stock ticker symbol, e.g. AAPL, GOOGL", + } + }, + "required": ["ticker"], + }, + }, + } + ] + + messages = [ + { + "role": "user", + "content": "What is the current market cap of Apple Inc?", + } + ] + + with patch.object(client, "post") as mock_post: + try: + completion( + model="bedrock/us.amazon.nova-pro-v1:0", + messages=messages, + tools=tools, + web_search_options={}, # Also enable web grounding + max_tokens=500, + client=client, + ) + except Exception: + pass # Expected - we're just checking the request structure + + # Verify the request was made correctly + if mock_post.called: + request_body = json.loads(mock_post.call_args.kwargs.get("data", "{}")) + print(f"Request body: {json.dumps(request_body, indent=2)}") + + # Verify toolConfig has both function tool and systemTool + assert "toolConfig" in request_body, "toolConfig should be in request" + tool_config = request_body["toolConfig"] + assert "tools" in tool_config, "tools should be in toolConfig" + + tools_in_request = tool_config["tools"] + + # Should have both the function tool and the systemTool + function_tool_found = False + system_tool_found = False + + for tool in tools_in_request: + if "toolSpec" in tool: + assert tool["toolSpec"]["name"] == "get_stock_price" + function_tool_found = True + if "systemTool" in tool: + assert tool["systemTool"]["name"] == "nova_grounding" + system_tool_found = True + + assert function_tool_found, "Function tool (get_stock_price) should be present" + assert system_tool_found, "systemTool (nova_grounding) should be present" + print(f"✓ Both function tools and web_search_options correctly combined") + + +@pytest.mark.asyncio +async def test_bedrock_nova_grounding_async(): + """ + Async unit test for Nova grounding via web_search_options. + + This test verifies the request transformation for async calls. + """ + from unittest.mock import patch, AsyncMock + from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler + + client = AsyncHTTPHandler() + + messages = [ + { + "role": "user", + "content": "What is the weather forecast for New York City today?", + } + ] + + with patch.object(client, "post", new=AsyncMock()) as mock_post: + try: + await litellm.acompletion( + model="bedrock/us.amazon.nova-pro-v1:0", + messages=messages, + web_search_options={}, + max_tokens=500, + client=client, + ) + except Exception: + pass # Expected - we're just checking the request structure + + # Verify the request was made correctly + if mock_post.called: + request_body = json.loads(mock_post.call_args.kwargs.get("data", "{}")) + print(f"Request body: {json.dumps(request_body, indent=2)}") + + # Verify toolConfig is present with systemTool + assert "toolConfig" in request_body, "toolConfig should be in request" + tool_config = request_body["toolConfig"] + assert "tools" in tool_config, "tools should be in toolConfig" + + # Find the systemTool for nova_grounding + system_tool_found = False + for tool in tool_config["tools"]: + if "systemTool" in tool: + assert tool["systemTool"]["name"] == "nova_grounding" + system_tool_found = True + break + + assert system_tool_found, "systemTool with nova_grounding should be present" + print(f"✓ Async web_search_options correctly transformed to systemTool") + + +def test_bedrock_nova_web_search_options_ignored_for_non_nova(): + """ + Test that web_search_options is ignored for non-Nova Bedrock models. + + Nova grounding is only supported on Nova models. For other models, + the parameter should be silently ignored. + """ + from litellm.llms.bedrock.chat.converse_transformation import AmazonConverseConfig + + config = AmazonConverseConfig() + + # Should return None for non-Nova models + result = config._map_web_search_options({}, "anthropic.claude-3-sonnet-v1") + assert result is None + + result = config._map_web_search_options({}, "amazon.titan-text-express-v1") + assert result is None + + # Should return systemTool for Nova models + result = config._map_web_search_options({}, "amazon.nova-pro-v1:0") + assert result is not None + system_tool = result.get("systemTool") + assert system_tool is not None + assert system_tool["name"] == "nova_grounding" + + result2 = config._map_web_search_options({}, "us.amazon.nova-premier-v1:0") + assert result2 is not None + system_tool2 = result2.get("systemTool") + assert system_tool2 is not None + assert system_tool2["name"] == "nova_grounding" + + +def test_bedrock_nova_grounding_request_transformation(): + """ + Unit test to verify that web_search_options transforms to systemTool in the request. + """ + from unittest.mock import patch, MagicMock + from litellm.llms.custom_httpx.http_handler import HTTPHandler + + client = HTTPHandler() + + messages = [{"role": "user", "content": "What is the population of Tokyo?"}] + + with patch.object(client, "post") as mock_post: + mock_post.return_value = MagicMock( + status_code=200, + json=lambda: { + "output": {"message": {"role": "assistant", "content": [{"text": "Test"}]}}, + "stopReason": "end_turn", + "usage": {"inputTokens": 10, "outputTokens": 5} + } + ) + + try: + response = completion( + model="bedrock/us.amazon.nova-pro-v1:0", + messages=messages, + web_search_options={}, + max_tokens=100, + client=client, + ) + except Exception: + pass # Expected - we're just checking the request + + if mock_post.called: + request_body = json.loads(mock_post.call_args.kwargs.get("data", "{}")) + print(f"Request body: {json.dumps(request_body, indent=2)}") + + # Verify toolConfig is present with systemTool + assert "toolConfig" in request_body, "toolConfig should be in request" + + tool_config = request_body["toolConfig"] + assert "tools" in tool_config, "tools should be in toolConfig" + + tools_in_request = tool_config["tools"] + + # Find the systemTool + system_tool_found = False + for tool in tools_in_request: + if "systemTool" in tool: + assert tool["systemTool"]["name"] == "nova_grounding" + system_tool_found = True + break + + assert system_tool_found, "systemTool with nova_grounding should be present" + print("✓ web_search_options correctly transformed to systemTool") diff --git a/tests/llm_translation/test_gemini.py b/tests/llm_translation/test_gemini.py index e3e05786449..c1c52757cf0 100644 --- a/tests/llm_translation/test_gemini.py +++ b/tests/llm_translation/test_gemini.py @@ -1435,3 +1435,20 @@ def test_gemini_image_size_limit_exceeded(): error_message = str(excinfo.value) assert "Image size" in error_message assert "exceeds maximum allowed size" in error_message + +@pytest.mark.asyncio +async def test_gemini_openai_web_search_tool_to_google_search(): + """ + Test that OpenAI-style web_search tools are transformed to Gemini's googleSearch. + + When passing {"type": "web_search"} or {"type": "web_search_preview"} to Gemini, + these should be transformed to googleSearch, not silently ignored. + """ + response = await litellm.acompletion( + model="gemini/gemini-2.5-flash", + messages=[{"role": "user", "content": "What is the capital of France?"}], + tools=[{"type": "web_search"}], + ) + print("response: ", response.model_dump_json(indent=4)) + assert hasattr(response, "vertex_ai_grounding_metadata") + assert getattr(response, "vertex_ai_grounding_metadata") is not None diff --git a/tests/llm_translation/test_gigachat.py b/tests/llm_translation/test_gigachat.py index dd8d56ff54a..b69a5428e42 100644 --- a/tests/llm_translation/test_gigachat.py +++ b/tests/llm_translation/test_gigachat.py @@ -5,9 +5,7 @@ Tests message transformation, parameter handling, and response transformation. Run with: pytest tests/llm_translation/test_gigachat.py -v """ -import json import pytest -from unittest.mock import Mock, MagicMock class TestGigaChatMessageTransformation: @@ -16,6 +14,7 @@ class TestGigaChatMessageTransformation: @pytest.fixture def config(self): from litellm.llms.gigachat.chat.transformation import GigaChatConfig + return GigaChatConfig() def test_simple_user_message(self, config): @@ -52,20 +51,46 @@ class TestGigaChatMessageTransformation: assert result[0]["role"] == "function" + def test_tool_content_convertation_non_string_value(self, config): + """Non string tool content should be serialized""" + messages = [{"role": "tool", "content": {"output": 42}}] + result = config._transform_messages(messages) + + assert result[0]["content"] == '{"output": 42}' + + def test_tool_content_convertation_json_string_value(self, config): + """JSON string tool content left unchanged""" + valid_json = '{"output": "red car"}' + messages = [{"role": "tool", "content": valid_json}] + result = config._transform_messages(messages) + + assert result[0]["content"] == valid_json + + def test_tool_content_convertation_random_string_value(self, config): + """Non JSON tool content should be serialized""" + messages = [{"role": "tool", "content": "random string"}] + result = config._transform_messages(messages) + + assert result[0]["content"] == '"random string"' + def test_tool_calls_to_function_call(self, config): """tool_calls should be converted to function_call""" - messages = [{ - "role": "assistant", - "content": "", - "tool_calls": [{ - "id": "call_123", - "type": "function", - "function": { - "name": "get_weather", - "arguments": '{"city": "Moscow"}' - } - }] - }] + messages = [ + { + "role": "assistant", + "content": "", + "tool_calls": [ + { + "id": "call_123", + "type": "function", + "function": { + "name": "get_weather", + "arguments": '{"city": "Moscow"}', + }, + } + ], + } + ] result = config._transform_messages(messages) assert "function_call" in result[0] @@ -94,6 +119,7 @@ class TestGigaChatCollapseUserMessages: @pytest.fixture def config(self): from litellm.llms.gigachat.chat.transformation import GigaChatConfig + return GigaChatConfig() def test_no_collapse_single_message(self, config): @@ -136,23 +162,24 @@ class TestGigaChatToolsTransformation: @pytest.fixture def config(self): from litellm.llms.gigachat.chat.transformation import GigaChatConfig + return GigaChatConfig() def test_single_tool_conversion(self, config): """Single tool should be converted correctly""" - tools = [{ - "type": "function", - "function": { - "name": "get_weather", - "description": "Get weather for a city", - "parameters": { - "type": "object", - "properties": { - "city": {"type": "string"} - } - } + tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get weather for a city", + "parameters": { + "type": "object", + "properties": {"city": {"type": "string"}}, + }, + }, } - }] + ] result = config._convert_tools_to_functions(tools) assert len(result) == 1 @@ -162,8 +189,22 @@ class TestGigaChatToolsTransformation: def test_multiple_tools_conversion(self, config): """Multiple tools should all be converted""" tools = [ - {"type": "function", "function": {"name": "func1", "description": "First", "parameters": {"type": "object", "properties": {}}}}, - {"type": "function", "function": {"name": "func2", "description": "Second", "parameters": {"type": "object", "properties": {}}}}, + { + "type": "function", + "function": { + "name": "func1", + "description": "First", + "parameters": {"type": "object", "properties": {}}, + }, + }, + { + "type": "function", + "function": { + "name": "func2", + "description": "Second", + "parameters": {"type": "object", "properties": {}}, + }, + }, ] result = config._convert_tools_to_functions(tools) @@ -178,6 +219,7 @@ class TestGigaChatParamsTransformation: @pytest.fixture def config(self): from litellm.llms.gigachat.chat.transformation import GigaChatConfig + return GigaChatConfig() def test_temperature_zero_becomes_top_p_zero(self, config): @@ -229,10 +271,10 @@ class TestGigaChatParamsTransformation: "type": "object", "properties": { "name": {"type": "string"}, - "age": {"type": "integer"} - } - } - } + "age": {"type": "integer"}, + }, + }, + }, } } result = config.map_openai_params( @@ -283,6 +325,7 @@ class TestGigaChatTransformRequest: @pytest.fixture def config(self): from litellm.llms.gigachat.chat.transformation import GigaChatConfig + return GigaChatConfig() def test_basic_request(self, config): @@ -335,6 +378,7 @@ class TestGigaChatSupportedParams: @pytest.fixture def config(self): from litellm.llms.gigachat.chat.transformation import GigaChatConfig + return GigaChatConfig() def test_supported_params(self, config): diff --git a/tests/llm_translation/test_groq.py b/tests/llm_translation/test_groq.py index 33c1a425870..b82cffd4189 100644 --- a/tests/llm_translation/test_groq.py +++ b/tests/llm_translation/test_groq.py @@ -11,7 +11,10 @@ import pytest import litellm from base_llm_unit_tests import BaseLLMChatTest -from litellm.llms.groq.chat.transformation import GroqChatConfig +from litellm.llms.groq.chat.transformation import ( + GroqChatConfig, + GroqChatCompletionStreamingHandler, +) class TestGroq(BaseLLMChatTest): def get_base_completion_call_args(self) -> dict: @@ -164,3 +167,124 @@ class TestGroqStructuredOutputs: if "tools" in result: tool_names = [t.get("function", {}).get("name") for t in result["tools"]] assert "json_tool_call" not in tool_names + + +class TestGroqReasoning: + """ + Tests for Groq reasoning field mapping. + + Groq returns 'reasoning' field in delta, but LiteLLM expects 'reasoning_content'. + """ + + def test_reasoning_field_mapping_in_streaming_chunks(self): + """ + Test that Groq's 'reasoning' field in streaming chunks is properly mapped + to LiteLLM's 'reasoning_content' field. + """ + handler = GroqChatCompletionStreamingHandler( + streaming_response=None, sync_stream=True + ) + + # Simulate a chunk with reasoning field as returned by Groq + groq_chunk = { + "id": "chatcmpl-test", + "object": "chat.completion.chunk", + "created": 1769511767, + "model": "qwen/qwen3-32b", + "choices": [ + { + "delta": { + "reasoning": "This is reasoning content", + "role": None, + }, + "finish_reason": None, + "index": 0, + } + ], + } + + # Parse the chunk + parsed_chunk = handler.chunk_parser(groq_chunk) + + # Verify that reasoning was mapped to reasoning_content + assert parsed_chunk.choices[0].delta.reasoning_content == "This is reasoning content" + # Verify that the original 'reasoning' field was removed + assert not hasattr(parsed_chunk.choices[0].delta, "reasoning") + + def test_reasoning_field_not_present(self): + """ + Test that chunks without reasoning field still work correctly. + """ + handler = GroqChatCompletionStreamingHandler( + streaming_response=None, sync_stream=True + ) + + # Simulate a chunk without reasoning field + groq_chunk = { + "id": "chatcmpl-test", + "object": "chat.completion.chunk", + "created": 1769511767, + "model": "qwen/qwen3-32b", + "choices": [ + { + "delta": { + "content": "Regular content", + "role": "assistant", + }, + "finish_reason": None, + "index": 0, + } + ], + } + + # Parse the chunk + parsed_chunk = handler.chunk_parser(groq_chunk) + + # Verify that content is present + assert parsed_chunk.choices[0].delta.content == "Regular content" + assert parsed_chunk.choices[0].delta.role == "assistant" + # Verify that reasoning_content is not set (it should be deleted by Delta.__init__) + assert not hasattr(parsed_chunk.choices[0].delta, "reasoning_content") + + def test_reasoning_with_tool_calls(self): + """ + Test that reasoning field is properly mapped even when tool_calls are present. + """ + handler = GroqChatCompletionStreamingHandler( + streaming_response=None, sync_stream=True + ) + + # Simulate a chunk with both reasoning and tool_calls + groq_chunk = { + "id": "chatcmpl-test", + "object": "chat.completion.chunk", + "created": 1769511767, + "model": "qwen/qwen3-32b", + "choices": [ + { + "delta": { + "reasoning": "Reasoning before tool call", + "tool_calls": [ + { + "index": 0, + "id": "call_123", + "function": {"name": "test_function", "arguments": "{}"}, + "type": "function", + } + ], + }, + "finish_reason": None, + "index": 0, + } + ], + } + + # Parse the chunk + parsed_chunk = handler.chunk_parser(groq_chunk) + + # Verify that reasoning was mapped to reasoning_content + assert parsed_chunk.choices[0].delta.reasoning_content == "Reasoning before tool call" + # Verify tool_calls are still present + assert parsed_chunk.choices[0].delta.tool_calls is not None + assert len(parsed_chunk.choices[0].delta.tool_calls) == 1 + assert parsed_chunk.choices[0].delta.tool_calls[0]["function"]["name"] == "test_function" diff --git a/tests/llm_translation/test_optional_params.py b/tests/llm_translation/test_optional_params.py index 6386dce54af..4699c31c378 100644 --- a/tests/llm_translation/test_optional_params.py +++ b/tests/llm_translation/test_optional_params.py @@ -224,7 +224,7 @@ def test_bedrock_optional_params_simple(model): ("bedrock/amazon.titan-embed-text-v1", False, None), ("bedrock/amazon.titan-embed-image-v1", True, "embeddingConfig"), ("bedrock/amazon.titan-embed-text-v2:0", True, "dimensions"), - ("bedrock/cohere.embed-multilingual-v3", False, None), + ("bedrock/cohere.embed-multilingual-v3", True, None), ], ) def test_bedrock_optional_params_embeddings_dimension( diff --git a/tests/llm_translation/test_xai.py b/tests/llm_translation/test_xai.py index 64ee95b52f1..c71b9e3fe51 100644 --- a/tests/llm_translation/test_xai.py +++ b/tests/llm_translation/test_xai.py @@ -175,3 +175,29 @@ class TestXAIChat(BaseLLMChatTest): def test_tool_call_no_arguments(self, tool_call_no_arguments): """Test that tool calls with no arguments is translated correctly. Relevant issue: https://github.com/BerriAI/litellm/issues/6833""" pass + + def test_web_search(self): + """Web search is only supported for Grok 4 family models""" + from litellm.utils import supports_web_search + + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + + litellm._turn_on_debug() + + # Use grok-4-1-fast which supports web search + model = "xai/grok-4-1-fast" + + if not supports_web_search(model, None): + pytest.skip("Model does not support web search") + + response = completion( + model=model, + messages=[ + {"role": "user", "content": "What's the weather like in Boston today?"} + ], + web_search_options={}, + max_tokens=100, + ) + + assert response is not None diff --git a/tests/local_testing/test_add_update_models.py b/tests/local_testing/test_add_update_models.py index 6d7ae597b54..4b5ec95d3fc 100644 --- a/tests/local_testing/test_add_update_models.py +++ b/tests/local_testing/test_add_update_models.py @@ -16,6 +16,8 @@ sys.path.insert( ) # Adds the parent directory to the system path import pytest, logging, asyncio import litellm +import litellm.proxy +import litellm.proxy.proxy_server from litellm.proxy.management_endpoints.model_management_endpoints import ( add_new_model, update_model, diff --git a/tests/local_testing/test_amazing_vertex_completion.py b/tests/local_testing/test_amazing_vertex_completion.py index 1987a7a545f..e1c24ea46fe 100644 --- a/tests/local_testing/test_amazing_vertex_completion.py +++ b/tests/local_testing/test_amazing_vertex_completion.py @@ -742,7 +742,7 @@ def test_gemini_pro_grounding(value_in_dict): @pytest.mark.parametrize("model", ["vertex_ai_beta/gemini-2.5-flash-lite"]) # "vertex_ai", @pytest.mark.parametrize("sync_mode", [True]) # "vertex_ai", @pytest.mark.asyncio -@pytest.mark.flaky(retries=3, delay=1) +@pytest.mark.flaky(retries=6, delay=2) async def test_gemini_pro_function_calling_httpx(model, sync_mode): try: load_vertex_ai_credentials() @@ -785,6 +785,7 @@ async def test_gemini_pro_function_calling_httpx(model, sync_mode): "messages": messages, "tools": tools, "tool_choice": "required", + "timeout": 60, # Add explicit timeout } print(f"Model for call - {model}") if sync_mode: @@ -799,12 +800,18 @@ async def test_gemini_pro_function_calling_httpx(model, sync_mode): response.choices[0].message.tool_calls[0].function.arguments, str ) except litellm.RateLimitError as e: - pass + pytest.skip(f"Rate limit exceeded: {str(e)}") + except litellm.ServiceUnavailableError as e: + pytest.skip(f"Service unavailable: {str(e)}") + except litellm.Timeout as e: + pytest.skip(f"Request timeout: {str(e)}") except Exception as e: - if "429 Quota exceeded" in str(e): - pass + error_msg = str(e) + # Skip test for known transient API issues + if any(x in error_msg for x in ["429 Quota exceeded", "503", "Service unavailable", "timeout", "Timeout", "UNAVAILABLE"]): + pytest.skip(f"Transient API error: {error_msg}") else: - pytest.fail("An unexpected exception occurred - {}".format(str(e))) + pytest.fail(f"An unexpected exception occurred - {error_msg}") from test_completion import response_format_tests @@ -813,7 +820,7 @@ from test_completion import response_format_tests @pytest.mark.parametrize( "model,region", [ - ("vertex_ai/mistral-large-2411", "us-central1"), + ("vertex_ai/mistral-small-2503", "us-central1"), ("vertex_ai/qwen/qwen3-coder-480b-a35b-instruct-maas", "us-south1"), ("vertex_ai/openai/gpt-oss-20b-maas", "us-central1"), ], @@ -886,7 +893,7 @@ async def test_partner_models_httpx(model, region, sync_mode): ("vertex_ai/meta/llama-4-scout-17b-16e-instruct-maas", "us-east5"), ("vertex_ai/qwen/qwen3-coder-480b-a35b-instruct-maas", "us-south1"), ( - "vertex_ai/mistral-large-2411", + "vertex_ai/mistral-small-2503", "us-central1", ), # critical - we had this issue: https://github.com/BerriAI/litellm/issues/13888 ("vertex_ai/openai/gpt-oss-20b-maas", "us-central1"), @@ -2564,6 +2571,115 @@ async def test_gemini_context_caching_anthropic_format(sync_mode): # ) +@pytest.mark.parametrize( + "sync_mode", + [True, False], +) +@pytest.mark.asyncio +async def test_gemini_context_caching_disabled_flag(sync_mode): + """ + Test that disable_anthropic_gemini_context_caching_transform flag properly disables context caching. + + When the flag is set to True, messages with cache_control should not trigger caching API calls. + """ + from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler + + litellm.set_verbose = True + + # Store original value to restore later + original_flag_value = litellm.disable_anthropic_gemini_context_caching_transform + + try: + # Enable the disable flag + litellm.disable_anthropic_gemini_context_caching_transform = True + + gemini_context_caching_messages = [ + # System Message with cache_control + { + "role": "system", + "content": [ + { + "type": "text", + "text": "Here is the full text of a complex legal agreement {}".format( + uuid.uuid4() + ) + * 4000, + "cache_control": {"type": "ephemeral"}, + } + ], + }, + # User message with cache_control + { + "role": "user", + "content": [ + { + "type": "text", + "text": "What are the key terms and conditions in this agreement?", + "cache_control": {"type": "ephemeral"}, + } + ], + }, + { + "role": "assistant", + "content": "Certainly! the key terms and conditions are the following: the contract is 1 year long for $10/mo", + }, + { + "role": "user", + "content": [ + { + "type": "text", + "text": "What are the key terms and conditions in this agreement?", + } + ], + }, + ] + + if sync_mode: + client = HTTPHandler(concurrent_limit=1) + else: + client = AsyncHTTPHandler(concurrent_limit=1) + + with patch.object(client, "post", side_effect=mock_gemini_request) as mock_client: + try: + if sync_mode: + response = litellm.completion( + model="gemini/gemini-2.5-flash-lite-001", + messages=gemini_context_caching_messages, + temperature=0.2, + max_tokens=10, + client=client, + ) + else: + response = await litellm.acompletion( + model="gemini/gemini-2.5-flash-lite-001", + messages=gemini_context_caching_messages, + temperature=0.2, + max_tokens=10, + client=client, + ) + + except Exception as e: + print(e) + + # When caching is disabled, should only make 1 call (no separate cache creation call) + assert mock_client.call_count == 1, f"Expected 1 call when caching is disabled, got {mock_client.call_count}" + + first_call_args = mock_client.call_args_list[0].kwargs + first_call_positional_args = mock_client.call_args_list[0].args + + print(f"first_call_args with caching disabled: {first_call_args}") + print(f"first_call_positional_args with caching disabled: {first_call_positional_args}") + + # Assert that cachedContents is NOT in the URL when caching is disabled + url = first_call_args.get("url", first_call_positional_args[0] if first_call_positional_args else "") + assert "cachedContents" not in url, "cachedContents should not be in URL when caching is disabled" + + finally: + # Restore original flag value + litellm.disable_anthropic_gemini_context_caching_transform = original_flag_value + + + @pytest.mark.asyncio async def test_partner_models_httpx_ai21(): litellm.set_verbose = True diff --git a/tests/local_testing/test_auth_utils.py b/tests/local_testing/test_auth_utils.py index 72f799a6cf0..d36f96b1a39 100644 --- a/tests/local_testing/test_auth_utils.py +++ b/tests/local_testing/test_auth_utils.py @@ -356,6 +356,25 @@ def test_get_internal_user_header_from_mapping_no_internal_returns_none(): "/openai/deployments/my-deployment/chat/completions", "my-deployment" ), + # Custom model_name with slashes (e.g., gcp/google/gemini-2.5-flash) + # This is the NVIDIA P0 bug fix - regex should capture full model name including slashes + ( + {}, + "/vertex_ai/v1/projects/my-project/locations/us-central1/publishers/google/models/gcp/google/gemini-2.5-flash:generateContent", + "gcp/google/gemini-2.5-flash" + ), + # Another custom model_name with slashes + ( + {}, + "/vertex_ai/v1/projects/my-project/locations/global/publishers/google/models/gcp/google/gemini-3-flash-preview:generateContent", + "gcp/google/gemini-3-flash-preview" + ), + # Model name with single slash + ( + {}, + "/vertex_ai/v1/projects/my-project/locations/us-central1/publishers/google/models/custom/model:generateContent", + "custom/model" + ), ], ) def test_get_model_from_request_vertex_ai_passthrough(request_data, route, expected_model): diff --git a/tests/local_testing/test_batch_completion_return_exceptions.py b/tests/local_testing/test_batch_completion_return_exceptions.py index 2d2ea8675ec..24540edf318 100644 --- a/tests/local_testing/test_batch_completion_return_exceptions.py +++ b/tests/local_testing/test_batch_completion_return_exceptions.py @@ -8,11 +8,19 @@ msg2 = [{"role": "user", "content": "hi 2"}] def test_batch_completion_return_exceptions_true(): - """Test batch_completion's return_exceptions.""" + """Test batch_completion's return_exceptions. + + With an invalid API key, we expect an error to be returned rather than raised. + The error type may be AuthenticationError (from API) or InternalServerError + (from connection issues), depending on network conditions. + """ res = litellm.batch_completion( model="gpt-3.5-turbo", messages=[msg1, msg2], api_key="sk_xxx", # deliberately set invalid key ) - assert isinstance(res[0], litellm.exceptions.AuthenticationError) + # batch_completion should return exceptions rather than raise them + # Accept either AuthenticationError (API rejected key) or InternalServerError (network issues) + assert isinstance(res[0], (litellm.exceptions.AuthenticationError, litellm.exceptions.InternalServerError)), \ + f"Expected AuthenticationError or InternalServerError, got {type(res[0])}" diff --git a/tests/local_testing/test_completion.py b/tests/local_testing/test_completion.py index de10034ca91..c322db157e9 100644 --- a/tests/local_testing/test_completion.py +++ b/tests/local_testing/test_completion.py @@ -1373,8 +1373,8 @@ HF Tests we should pass @pytest.mark.parametrize( - "provider", ["openai", "hosted_vllm", "lm_studio", "llamafile"] -) # "vertex_ai", + "provider", ["openai", "lm_studio", "llamafile"] +) # "vertex_ai", hosted_vllm removed - no longer uses OpenAI client @pytest.mark.asyncio async def test_openai_compatible_custom_api_base(provider): litellm.set_verbose = True @@ -1414,10 +1414,9 @@ async def test_openai_compatible_custom_api_base(provider): "provider", [ "openai", - "hosted_vllm", "llamafile", ], -) # "vertex_ai", +) # "vertex_ai", hosted_vllm removed - no longer uses OpenAI client @pytest.mark.asyncio async def test_openai_compatible_custom_api_video(provider): litellm.set_verbose = True diff --git a/tests/local_testing/test_completion_with_retries.py b/tests/local_testing/test_completion_with_retries.py index 6eb3ad460e6..585e1ee2618 100644 --- a/tests/local_testing/test_completion_with_retries.py +++ b/tests/local_testing/test_completion_with_retries.py @@ -60,6 +60,7 @@ async def test_completion_with_retry_policy(sync_mode): retry_number = 1 retry_policy = RetryPolicy( + BadRequestErrorRetries=10, ContentPolicyViolationErrorRetries=retry_number, # run 3 retries for ContentPolicyViolationErrors AuthenticationErrorRetries=0, # run 0 retries for AuthenticationErrorRetries ) diff --git a/tests/local_testing/test_literalai.py b/tests/local_testing/test_literalai.py deleted file mode 100644 index 35e583549dd..00000000000 --- a/tests/local_testing/test_literalai.py +++ /dev/null @@ -1,72 +0,0 @@ -import os -import sys - -sys.path.insert(0, os.path.abspath("../..")) - -import asyncio -import logging - -import pytest - -import litellm -from litellm._logging import verbose_logger -from litellm.integrations.literal_ai import LiteralAILogger - -verbose_logger.setLevel(logging.DEBUG) - -litellm.set_verbose = True - - -@pytest.mark.asyncio -async def test_literalai_queue_logging(): - try: - # Initialize LiteralAILogger - test_literalai_logger = LiteralAILogger() - - litellm.callbacks = [test_literalai_logger] - test_literalai_logger.batch_size = 6 - litellm.set_verbose = True - - # Make multiple calls to ensure we don't hit the batch size - for _ in range(5): - response = await litellm.acompletion( - model="gpt-3.5-turbo", - messages=[{"role": "user", "content": "Test message"}], - max_tokens=10, - temperature=0.2, - mock_response="This is a mock response", - ) - - await asyncio.sleep(3) - - # Check that logs are in the queue - assert len(test_literalai_logger.log_queue) == 5 - - # Now make calls to exceed the batch size - for _ in range(3): - await litellm.acompletion( - model="gpt-3.5-turbo", - messages=[{"role": "user", "content": "Test message"}], - max_tokens=10, - temperature=0.2, - mock_response="This is a mock response", - ) - - # Wait a short time for any asynchronous operations to complete - await asyncio.sleep(1) - - print( - "Length of literalai log queue: {}".format( - len(test_literalai_logger.log_queue) - ) - ) - # Check that the queue was flushed after exceeding batch size - assert len(test_literalai_logger.log_queue) < 5 - - # Clean up - for cb in litellm.callbacks: - if isinstance(cb, LiteralAILogger): - await cb.async_httpx_client.client.aclose() - - except Exception as e: - pytest.fail(f"Error occurred: {e}") diff --git a/tests/local_testing/test_timeout.py b/tests/local_testing/test_timeout.py index bb6dbc938ba..7f9837e81dd 100644 --- a/tests/local_testing/test_timeout.py +++ b/tests/local_testing/test_timeout.py @@ -285,94 +285,3 @@ async def test_anthropic_timeout(streaming, sync_mode): ) print(type(e)) pass - - -@pytest.mark.asyncio -async def test_timeout_respects_total_time_not_per_retry(): - """ - Test that timeout applies to the TOTAL operation time, not per-retry. - - This test ensures that when a user sets timeout=2, the entire operation - (including all retries) times out at ~2 seconds, not at 2s * num_retries. - - This is a regression test for the issue where timeout was being applied - per-retry attempt, causing the total time to be much longer than expected. - """ - litellm.set_verbose = False - - timeout_value = 2.0 - # Allow for some overhead (network, processing, etc.) - # but ensure we don't wait for multiple retries - max_allowed_time = timeout_value + 1.0 # 3 seconds max - - start_time = time.time() - - try: - # This should timeout because we're asking for a long response - # with a very short timeout - response = await litellm.acompletion( - model="gpt-3.5-turbo", - timeout=timeout_value, - messages=[{"role": "user", "content": "Write a very long detailed essay about the history of computing, at least 5000 words."}], - ) - pytest.fail("Expected timeout error but got a response") - except (openai.APITimeoutError, litellm.exceptions.Timeout) as e: - elapsed_time = time.time() - start_time - - print(f"Timeout occurred after {elapsed_time:.2f} seconds") - print(f"Expected timeout: {timeout_value} seconds") - print(f"Max allowed time: {max_allowed_time} seconds") - - # Verify that the timeout happened within the expected time window - # It should be close to timeout_value, not timeout_value * num_retries - assert elapsed_time < max_allowed_time, ( - f"Timeout took too long! Expected ~{timeout_value}s, " - f"got {elapsed_time:.2f}s. This suggests timeout is being " - f"applied per-retry instead of to the total operation." - ) - - # Also verify it's not TOO fast (sanity check) - assert elapsed_time >= timeout_value * 0.5, ( - f"Timeout happened too quickly: {elapsed_time:.2f}s. " - f"Expected at least {timeout_value * 0.5}s" - ) - - print("✓ Timeout correctly applied to total operation time, not per-retry") - except Exception as e: - pytest.fail( - f"Expected timeout error but got different error: {type(e).__name__}: {e}" - ) - - -@pytest.mark.asyncio -async def test_timeout_with_retries_disabled(): - """ - Test that timeout works correctly when retries are explicitly disabled. - This should timeout even faster since there are no retry attempts. - """ - litellm.set_verbose = False - - timeout_value = 2.0 - max_allowed_time = timeout_value + 0.5 # Even tighter bound with no retries - - start_time = time.time() - - try: - response = await litellm.acompletion( - model="gpt-3.5-turbo", - timeout=timeout_value, - max_retries=0, # Disable retries - messages=[{"role": "user", "content": "Write a very long detailed essay about the history of computing, at least 5000 words."}], - ) - pytest.fail("Expected timeout error but got a response") - except (openai.APITimeoutError, litellm.exceptions.Timeout) as e: - elapsed_time = time.time() - start_time - - print(f"Timeout with no retries occurred after {elapsed_time:.2f} seconds") - - assert elapsed_time < max_allowed_time, ( - f"Timeout took too long even with retries disabled! " - f"Expected ~{timeout_value}s, got {elapsed_time:.2f}s" - ) - - print("✓ Timeout works correctly with retries disabled") diff --git a/tests/mcp_tests/test_aresponses_api_with_mcp.py b/tests/mcp_tests/test_aresponses_api_with_mcp.py index 6c8f51201d8..a7bbfef14af 100644 --- a/tests/mcp_tests/test_aresponses_api_with_mcp.py +++ b/tests/mcp_tests/test_aresponses_api_with_mcp.py @@ -683,9 +683,11 @@ async def test_streaming_responses_api_with_mcp_tools( Return the user the result of request 2 """ - # Skip test if ANTHROPIC_API_KEY is not set for anthropic models - if "anthropic" in model.lower() and not os.getenv("ANTHROPIC_API_KEY"): + # Skip test if required API keys are not set + if ("anthropic" in model.lower() or "claude" in model.lower()) and not os.getenv("ANTHROPIC_API_KEY"): pytest.skip("ANTHROPIC_API_KEY not set, skipping anthropic model test") + if ("gpt" in model.lower() or "openai" in model.lower()) and not os.getenv("OPENAI_API_KEY"): + pytest.skip("OPENAI_API_KEY not set, skipping openai model test") from unittest.mock import AsyncMock, patch diff --git a/tests/mcp_tests/test_mcp_client_unit.py b/tests/mcp_tests/test_mcp_client_unit.py index fcee3208be1..9533e56bcc8 100644 --- a/tests/mcp_tests/test_mcp_client_unit.py +++ b/tests/mcp_tests/test_mcp_client_unit.py @@ -5,7 +5,7 @@ import base64 import os import sys import pytest -from unittest.mock import AsyncMock, MagicMock, patch +from unittest.mock import AsyncMock, MagicMock, patch, ANY # Add the project root to the path sys.path.insert(0, os.path.abspath("../../..")) @@ -183,7 +183,7 @@ class TestMCPClientUnitTests: assert result == mock_result mock_session_instance.initialize.assert_called_once() mock_session_instance.call_tool.assert_called_once_with( - name="test_tool", arguments={"arg1": "value1"} + name="test_tool", arguments={"arg1": "value1"},progress_callback=ANY ) diff --git a/tests/mcp_tests/test_mcp_server.py b/tests/mcp_tests/test_mcp_server.py index e8a1231c6fb..5785ff750fd 100644 --- a/tests/mcp_tests/test_mcp_server.py +++ b/tests/mcp_tests/test_mcp_server.py @@ -1105,9 +1105,26 @@ async def test_mcp_server_manager_config_integration_with_database(): test_manager.get_allowed_mcp_servers = mock_get_allowed_servers - # Test the method (this tests our second fix) - import asyncio + # Mock health_check_server to avoid real network calls that timeout + async def mock_health_check(server_id: str, mcp_auth_header=None): + server = test_manager.get_mcp_server_by_id(server_id) + if not server: + return None + return LiteLLM_MCPServerTable( + server_id=server_id, + server_name=server.name, + url=server.url, + transport=server.transport, + description=server.mcp_info.get("description") if server.mcp_info else None, + mcp_access_groups=server.access_groups, + status="healthy", + last_health_check=datetime.datetime.now(), + mcp_info=server.mcp_info, + ) + test_manager.health_check_server = mock_health_check + + # Test the method (this tests our second fix) servers_list = await test_manager.get_all_mcp_servers_with_health_and_teams( user_api_key_auth=mock_user_auth ) diff --git a/tests/mcp_tests/test_semantic_tool_filter_e2e.py b/tests/mcp_tests/test_semantic_tool_filter_e2e.py new file mode 100644 index 00000000000..91c072ae8a3 --- /dev/null +++ b/tests/mcp_tests/test_semantic_tool_filter_e2e.py @@ -0,0 +1,89 @@ +""" +End-to-end test for MCP Semantic Tool Filtering +""" +import asyncio +import os +import sys +from unittest.mock import Mock + +import pytest + +sys.path.insert(0, os.path.abspath("../..")) + +from mcp.types import Tool as MCPTool + +# Check if semantic-router is available +try: + import semantic_router + SEMANTIC_ROUTER_AVAILABLE = True +except ImportError: + SEMANTIC_ROUTER_AVAILABLE = False + + +@pytest.mark.asyncio +@pytest.mark.skipif( + not SEMANTIC_ROUTER_AVAILABLE, + reason="semantic-router not installed. Install with: pip install 'litellm[semantic-router]'" +) +async def test_e2e_semantic_filter(): + """E2E: Load router/filter and verify hook filters tools.""" + from litellm import Router + from litellm.proxy.hooks.mcp_semantic_filter import SemanticToolFilterHook + from litellm.proxy._experimental.mcp_server.semantic_tool_filter import ( + SemanticMCPToolFilter, + ) + + # Create router and filter + router = Router( + model_list=[{ + "model_name": "text-embedding-3-small", + "litellm_params": {"model": "openai/text-embedding-3-small"}, + }] + ) + + filter_instance = SemanticMCPToolFilter( + embedding_model="text-embedding-3-small", + litellm_router_instance=router, + top_k=3, + enabled=True, + ) + + # Create 10 tools + tools = [ + MCPTool(name="gmail_send", description="Send an email via Gmail", inputSchema={"type": "object"}), + MCPTool(name="calendar_create", description="Create a calendar event", inputSchema={"type": "object"}), + MCPTool(name="file_upload", description="Upload a file", inputSchema={"type": "object"}), + MCPTool(name="web_search", description="Search the web", inputSchema={"type": "object"}), + MCPTool(name="slack_send", description="Send Slack message", inputSchema={"type": "object"}), + MCPTool(name="doc_read", description="Read document", inputSchema={"type": "object"}), + MCPTool(name="db_query", description="Query database", inputSchema={"type": "object"}), + MCPTool(name="api_call", description="Make API call", inputSchema={"type": "object"}), + MCPTool(name="task_create", description="Create task", inputSchema={"type": "object"}), + MCPTool(name="note_add", description="Add note", inputSchema={"type": "object"}), + ] + + # Build router with test tools + filter_instance._build_router(tools) + + hook = SemanticToolFilterHook(filter_instance) + + data = { + "model": "gpt-4", + "messages": [{"role": "user", "content": "Send an email and create a calendar event"}], + "tools": tools, + "metadata": {}, # Initialize metadata dict for hook to store filter stats + } + + # Call hook + result = await hook.async_pre_call_hook( + user_api_key_dict=Mock(), + cache=Mock(), + data=data, + call_type="completion", + ) + + # Single assertion: hook filtered tools + assert result and len(result["tools"]) < len(tools), f"Expected filtered tools, got {len(result['tools'])} tools (original: {len(tools)})" + + print(f"✅ E2E test passed: Filtering reduced tools from {len(tools)} to {len(result['tools'])}") + print(f" Filtered tools: {[t.name for t in result['tools']]}") diff --git a/tests/otel_tests/test_prometheus.py b/tests/otel_tests/test_prometheus.py index ce3031b5141..1fce9e82045 100644 --- a/tests/otel_tests/test_prometheus.py +++ b/tests/otel_tests/test_prometheus.py @@ -106,19 +106,54 @@ async def test_proxy_failure_metrics(): print("/metrics", metrics) # Check if the failure metric is present and correct - use pattern matching for robustness - expected_metric_pattern = 'litellm_proxy_failed_requests_metric_total{api_key_alias="None",end_user="None",exception_class="Openai.RateLimitError",exception_status="429",hashed_api_key="88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b",requested_model="fake-azure-endpoint",route="/chat/completions",team="None",team_alias="None",user="default_user_id",user_email="None"}' + # Labels are ordered alphabetically by Prometheus: api_key_alias, end_user, exception_class, + # exception_status, hashed_api_key, requested_model, route, team, team_alias, user, user_email + # Note: client_ip, user_agent, model_id are present but we use substring matching to be flexible + # Check for both the new metric and deprecated metric for backwards compatibility + expected_patterns = [ + 'litellm_proxy_failed_requests_metric_total{', # New metric + 'litellm_llm_api_failed_requests_metric_total{' # Deprecated but may still be used + ] + + # Check if either pattern is in metrics and contains required fields + found_metric = False + for pattern in expected_patterns: + for line in metrics.split("\n"): + # For proxy metric, check proxy-specific fields + if 'litellm_proxy_failed_requests_metric_total{' in line: + if 'api_key_alias="None"' in line and \ + 'exception_class="Openai.RateLimitError"' in line and \ + 'exception_status="429"' in line and \ + 'hashed_api_key="88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b"' in line and \ + 'requested_model="fake-azure-endpoint"' in line and \ + 'route="/chat/completions"' in line: + found_metric = True + break + # For deprecated llm_api metric, check llm-specific fields + elif 'litellm_llm_api_failed_requests_metric_total{' in line: + if 'hashed_api_key="88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b"' in line and \ + 'model="429"' in line: # The deprecated metric uses the actual model from the request + found_metric = True + break + if found_metric: + break + + assert found_metric, f"Expected failure metric not found in /metrics. Looking for either litellm_proxy_failed_requests_metric_total or litellm_llm_api_failed_requests_metric_total with required fields" - # Check if the pattern is in metrics (this metric doesn't include user_email field) - assert any( - expected_metric_pattern in line for line in metrics.split("\n") - ), f"Expected failure metric pattern not found in /metrics. Pattern: {expected_metric_pattern}" - - # Check total requests metric which includes user_email - total_requests_pattern = 'litellm_proxy_total_requests_metric_total{api_key_alias="None",end_user="None",hashed_api_key="88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b",requested_model="fake-azure-endpoint",route="/chat/completions",status_code="429",team="None",team_alias="None",user="default_user_id",user_email="None"}' - - assert any( - total_requests_pattern in line for line in metrics.split("\n") - ), f"Expected total requests metric pattern not found in /metrics. Pattern: {total_requests_pattern}" + # Check total requests metric similarly + # The litellm_proxy_total_requests_metric_total should be present + total_requests_pattern = 'litellm_proxy_total_requests_metric_total{' + + found_total_metric = False + for line in metrics.split("\n"): + if total_requests_pattern in line and \ + 'hashed_api_key="88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b"' in line and \ + 'requested_model="fake-azure-endpoint"' in line and \ + 'status_code="429"' in line: + found_total_metric = True + break + + assert found_total_metric, f"Expected total requests metric not found in /metrics. Looking for: {total_requests_pattern} with hashed_api_key and status_code=429" @pytest.mark.asyncio @@ -147,16 +182,33 @@ async def test_proxy_success_metrics(): assert END_USER_ID not in metrics - # Check if the success metric is present and correct - assert ( - 'litellm_request_total_latency_metric_bucket{api_key_alias="None",end_user="None",hashed_api_key="88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b",le="0.005",model="fake",requested_model="fake-openai-endpoint",team="None",team_alias="None",user="default_user_id"}' - in metrics - ) + # Check if the success metric is present and correct - use flexible matching + # Check for request_total_latency_metric with required fields + # Note: The model can be "gpt-3.5-turbo-0301" or similar depending on what's returned + found_request_latency = False + for line in metrics.split("\n"): + if 'litellm_request_total_latency_metric_bucket{' in line and \ + 'api_key_alias="None"' in line and \ + 'hashed_api_key="88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b"' in line and \ + 'requested_model="fake-openai-endpoint"' in line and \ + 'le="0.005"' in line: + found_request_latency = True + break + + assert found_request_latency, "Expected litellm_request_total_latency_metric_bucket not found in /metrics" - assert ( - 'litellm_llm_api_latency_metric_bucket{api_key_alias="None",end_user="None",hashed_api_key="88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b",le="0.005",model="fake",requested_model="fake-openai-endpoint",team="None",team_alias="None",user="default_user_id"}' - in metrics - ) + # Check for llm_api_latency_metric with required fields + found_api_latency = False + for line in metrics.split("\n"): + if 'litellm_llm_api_latency_metric_bucket{' in line and \ + 'api_key_alias="None"' in line and \ + 'hashed_api_key="88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b"' in line and \ + 'requested_model="fake-openai-endpoint"' in line and \ + 'le="0.005"' in line: + found_api_latency = True + break + + assert found_api_latency, "Expected litellm_llm_api_latency_metric_bucket not found in /metrics" verify_latency_metrics(metrics) @@ -223,17 +275,37 @@ async def test_proxy_fallback_metrics(): print("/metrics", metrics) - # Check if successful fallback metric is incremented - assert ( - 'litellm_deployment_successful_fallbacks_total{api_key_alias="None",exception_class="Openai.RateLimitError",exception_status="429",fallback_model="fake-openai-endpoint",hashed_api_key="88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b",requested_model="fake-azure-endpoint",team="None",team_alias="None"} 1.0' - in metrics - ) + # Check if successful fallback metric is incremented - use flexible matching + found_successful_fallback = False + for line in metrics.split("\n"): + if 'litellm_deployment_successful_fallbacks_total{' in line and \ + 'api_key_alias="None"' in line and \ + 'exception_class="Openai.RateLimitError"' in line and \ + 'exception_status="429"' in line and \ + 'fallback_model="fake-openai-endpoint"' in line and \ + 'hashed_api_key="88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b"' in line and \ + 'requested_model="fake-azure-endpoint"' in line and \ + '1.0' in line: + found_successful_fallback = True + break + + assert found_successful_fallback, "Expected litellm_deployment_successful_fallbacks_total metric not found in /metrics" - # Check if failed fallback metric is incremented - assert ( - 'litellm_deployment_failed_fallbacks_total{api_key_alias="None",exception_class="Openai.RateLimitError",exception_status="429",fallback_model="unknown-model",hashed_api_key="88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b",requested_model="fake-azure-endpoint",team="None",team_alias="None"} 1.0' - in metrics - ) + # Check if failed fallback metric is incremented - use flexible matching + found_failed_fallback = False + for line in metrics.split("\n"): + if 'litellm_deployment_failed_fallbacks_total{' in line and \ + 'api_key_alias="None"' in line and \ + 'exception_class="Openai.RateLimitError"' in line and \ + 'exception_status="429"' in line and \ + 'fallback_model="unknown-model"' in line and \ + 'hashed_api_key="88dc28d0f030c55ed4ab77ed8faf098196cb1c05df778539800c9f1243fe6b4b"' in line and \ + 'requested_model="fake-azure-endpoint"' in line and \ + '1.0' in line: + found_failed_fallback = True + break + + assert found_failed_fallback, "Expected litellm_deployment_failed_fallbacks_total metric not found in /metrics" async def create_test_team( diff --git a/tests/pass_through_unit_tests/test_anthropic_messages_tool_search.py b/tests/pass_through_unit_tests/test_anthropic_messages_tool_search.py index 4914a3df77a..1bd1b0d906c 100644 --- a/tests/pass_through_unit_tests/test_anthropic_messages_tool_search.py +++ b/tests/pass_through_unit_tests/test_anthropic_messages_tool_search.py @@ -67,17 +67,17 @@ class TestAnthropicAPIToolSearch(BaseAnthropicMessagesToolSearchTest): # return "vertex_ai/claude-sonnet-4@20250514" -class TestBedrockInvokeToolSearch(BaseAnthropicMessagesToolSearchTest): - """ - E2E tests for tool search with Bedrock Invoke API. +# class TestBedrockInvokeToolSearch(BaseAnthropicMessagesToolSearchTest): +# """ +# E2E tests for tool search with Bedrock Invoke API. - Uses the bedrock/invoke/ prefix which routes through the native - Anthropic Messages API format on Bedrock. +# Uses the bedrock/invoke/ prefix which routes through the native +# Anthropic Messages API format on Bedrock. - Beta header: advanced-tool-use-2025-11-20 (passed via extra_headers) +# Beta header: advanced-tool-use-2025-11-20 (passed via extra_headers) - Note: Tool search on Bedrock is only supported on Claude Opus 4.5. - """ +# Note: Tool search on Bedrock is only supported on Claude Opus 4.5. +# """ - def get_model(self) -> str: - return "bedrock/invoke/us.anthropic.claude-opus-4-5-20251101-v1:0" +# def get_model(self) -> str: +# return "bedrock/invoke/us.anthropic.claude-opus-4-5-20251101-v1:0" diff --git a/tests/proxy_admin_ui_tests/test_key_management.py b/tests/proxy_admin_ui_tests/test_key_management.py index a196080eada..fa39a05a27d 100644 --- a/tests/proxy_admin_ui_tests/test_key_management.py +++ b/tests/proxy_admin_ui_tests/test_key_management.py @@ -473,18 +473,41 @@ async def test_get_users_key_count(prisma_client): setattr(litellm.proxy.proxy_server, "master_key", "sk-1234") await litellm.proxy.proxy_server.prisma_client.connect() - # Get initial user list and select the first user - initial_users = await get_users(role=None, page=1, page_size=20) + # Create a test user with no initial keys to ensure deterministic behavior + test_user_id = f"test_user_key_count-{uuid.uuid4()}" + test_user_request = NewUserRequest( + user_id=test_user_id, + user_role=LitellmUserRoles.INTERNAL_USER.value, + auto_create_key=False, # Ensure we start with 0 keys + ) + + await new_user( + test_user_request, + UserAPIKeyAuth( + user_role=LitellmUserRoles.PROXY_ADMIN, + api_key="sk-1234", + user_id="admin", + ), + ) + + # Get initial key count for the test user + initial_users = await get_users( + user_ids=test_user_id, + role=None, + page=1, + page_size=20, + ) print("initial_users", initial_users) - assert len(initial_users["users"]) > 0, "No users found to test with" - + assert len(initial_users["users"]) == 1, "Test user should be found" test_user = initial_users["users"][0] + assert test_user.user_id == test_user_id initial_key_count = test_user.key_count + assert initial_key_count == 0, f"Expected initial key count to be 0, but got {initial_key_count}" - # Create a new key for the selected user + # Create a new key for the test user new_key = await generate_key_fn( data=GenerateKeyRequest( - user_id=test_user.user_id, + user_id=test_user_id, key_alias=f"test_key_{uuid.uuid4()}", models=["fake-model"], ), @@ -496,19 +519,26 @@ async def test_get_users_key_count(prisma_client): ) # Get updated user list and check key count - updated_users = await get_users(role=None, page=1, page_size=20) + updated_users = await get_users( + user_ids=test_user_id, + role=None, + page=1, + page_size=20, + ) print("updated_users", updated_users) - updated_key_count = None - for user in updated_users["users"]: - if user.user_id == test_user.user_id: - updated_key_count = user.key_count - break + assert len(updated_users["users"]) == 1, "Test user should still be found" + updated_user = updated_users["users"][0] + updated_key_count = updated_user.key_count - assert updated_key_count is not None, "Test user not found in updated users list" assert ( updated_key_count == initial_key_count + 1 ), f"Expected key count to increase by 1, but got {updated_key_count} (was {initial_key_count})" + # Clean up test user and keys + await prisma_client.db.litellm_usertable.delete( + where={"user_id": test_user_id} + ) + async def cleanup_existing_teams(prisma_client): all_teams = await prisma_client.db.litellm_teamtable.find_many() diff --git a/tests/proxy_e2e_anthropic_messages_tests/test_claude_agent_sdk.py b/tests/proxy_e2e_anthropic_messages_tests/test_claude_agent_sdk.py new file mode 100644 index 00000000000..f1f6eb921bb --- /dev/null +++ b/tests/proxy_e2e_anthropic_messages_tests/test_claude_agent_sdk.py @@ -0,0 +1,124 @@ +""" +E2E tests for Claude Agent SDK with LiteLLM Proxy using Bedrock models. + +Tests streaming messages across different Bedrock models: +- Regular Bedrock Claude Sonnet 4.5 +- Bedrock Converse Claude Sonnet 4.5 +- AWS Nova Premier +""" + +import os +import pytest +import asyncio +from claude_agent_sdk import ClaudeSDKClient, ClaudeAgentOptions + + +# Test models from test_config.yaml +# Note: bedrock-converse-claude-sonnet-4.5 removed temporarily as the Bedrock Converse API +# for Claude Sonnet 4.5 may not be available in all regions/accounts +# Note: bedrock-nova-premier requires an inference profile for on-demand throughput +# https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles.html +TEST_MODELS = [ + ("bedrock-claude-sonnet-4.5", "Bedrock Invoke API"), + ("bedrock-converse-claude-sonnet-4.5", "Bedrock Converse API"), + ("bedrock-nova-premier", "AWS Nova Premier"), +] + + +@pytest.fixture(scope="module") +def litellm_proxy_config(): + """Configure connection to LiteLLM proxy""" + proxy_url = os.getenv("LITELLM_PROXY_URL", "http://localhost:4000") + api_key = os.getenv("LITELLM_API_KEY", "sk-1234") + + # Set environment variables for Claude Agent SDK + os.environ["ANTHROPIC_BASE_URL"] = proxy_url.rstrip('/') + os.environ["ANTHROPIC_API_KEY"] = api_key + + return { + "proxy_url": proxy_url, + "api_key": api_key, + } + + +@pytest.mark.asyncio +@pytest.mark.parametrize("model_name,model_description", TEST_MODELS) +async def test_claude_agent_sdk_streaming(litellm_proxy_config, model_name, model_description): + """ + Test streaming messages with Claude Agent SDK through LiteLLM proxy. + + This validates: + 1. Claude Agent SDK can connect to LiteLLM proxy + 2. Streaming works correctly + 3. Different Bedrock models (Invoke, Converse, Nova) work end-to-end + """ + print(f"\n{'='*60}") + print(f"Testing: {model_name} ({model_description})") + print(f"{'='*60}") + + # Configure agent options + options = ClaudeAgentOptions( + system_prompt="You are a helpful AI assistant. Be concise.", + model=model_name, + max_turns=5, + ) + + # Test query + test_query = "Say 'Hello from LiteLLM!' and nothing else." + + # Track streaming + received_chunks = [] + full_response = "" + + try: + async with ClaudeSDKClient(options=options) as client: + await client.query(test_query) + + # Collect streaming response + async for msg in client.receive_response(): + # Handle different message types + if hasattr(msg, 'type'): + if msg.type == 'content_block_delta': + # Streaming text delta + if hasattr(msg, 'delta') and hasattr(msg.delta, 'text'): + chunk_text = msg.delta.text + received_chunks.append(chunk_text) + full_response += chunk_text + elif msg.type == 'content_block_start': + # Start of content block + if hasattr(msg, 'content_block') and hasattr(msg.content_block, 'text'): + chunk_text = msg.content_block.text + received_chunks.append(chunk_text) + full_response += chunk_text + + # Fallback to content handling + if hasattr(msg, 'content'): + for content_block in msg.content: + if hasattr(content_block, 'text'): + chunk_text = content_block.text + received_chunks.append(chunk_text) + full_response += chunk_text + + # Assertions + print(f"\n✅ Received {len(received_chunks)} chunks") + print(f"📝 Full response: {full_response[:100]}...") + + # Verify we got a response + assert len(full_response) > 0, f"No response received from {model_name}" + + # Verify streaming (should have multiple chunks for most responses) + # Note: Very short responses might come in 1 chunk, so we just verify we got content + assert len(received_chunks) > 0, f"No chunks received from {model_name}" + + # Verify response contains expected content (case insensitive) + assert "hello" in full_response.lower(), f"Response doesn't contain expected greeting: {full_response}" + + print(f"✅ Test passed for {model_name}") + + except Exception as e: + pytest.fail(f"Test failed for {model_name} ({model_description}): {str(e)}") + + +if __name__ == "__main__": + # Run tests + pytest.main([__file__, "-v", "-s"]) diff --git a/tests/proxy_e2e_anthropic_messages_tests/test_config.yaml b/tests/proxy_e2e_anthropic_messages_tests/test_config.yaml new file mode 100644 index 00000000000..931c222ef51 --- /dev/null +++ b/tests/proxy_e2e_anthropic_messages_tests/test_config.yaml @@ -0,0 +1,31 @@ +model_list: + - model_name: bedrock-claude-sonnet-3.5 + litellm_params: + model: "bedrock/us.anthropic.claude-3-5-sonnet-20240620-v1:0" + aws_region_name: "us-east-1" + + - model_name: bedrock-claude-sonnet-4 + litellm_params: + model: "bedrock/us.anthropic.claude-sonnet-4-20250514-v1:0" + aws_region_name: "us-east-1" + + - model_name: bedrock-claude-sonnet-4.5 + litellm_params: + model: "bedrock/us.anthropic.claude-sonnet-4-5-20250929-v1:0" + aws_region_name: "us-east-1" + + - model_name: bedrock-claude-opus-4.5 + litellm_params: + model: "bedrock/us.anthropic.claude-opus-4-5-20251101-v1:0" + aws_region_name: "us-east-1" + + - model_name: bedrock-nova-premier + litellm_params: + model: "bedrock/us.amazon.nova-premier-v1:0" + aws_region_name: "us-east-1" + + # Converse API models + - model_name: bedrock-converse-claude-sonnet-4.5 + litellm_params: + model: "bedrock/converse/us.anthropic.claude-sonnet-4-5-20250929-v1:0" + aws_region_name: "us-east-1" diff --git a/tests/proxy_unit_tests/test_auth_checks.py b/tests/proxy_unit_tests/test_auth_checks.py index 24c005443af..05c6e4984af 100644 --- a/tests/proxy_unit_tests/test_auth_checks.py +++ b/tests/proxy_unit_tests/test_auth_checks.py @@ -584,7 +584,7 @@ async def test_get_fuzzy_user_object(): ) assert result == test_user mock_prisma.db.litellm_usertable.find_first.assert_called_with( - where={"user_email": "test@example.com"}, + where={"user_email": {"equals": "test@example.com", "mode": "insensitive"}}, include={"organization_memberships": True}, ) @@ -612,7 +612,7 @@ async def test_get_fuzzy_user_object(): ) assert result == test_user mock_prisma.db.litellm_usertable.find_first.assert_called_with( - where={"user_email": "test@example.com"}, + where={"user_email": {"equals": "test@example.com", "mode": "insensitive"}}, include={"organization_memberships": True}, ) diff --git a/tests/proxy_unit_tests/test_get_image.py b/tests/proxy_unit_tests/test_get_image.py new file mode 100644 index 00000000000..ad8c2672754 --- /dev/null +++ b/tests/proxy_unit_tests/test_get_image.py @@ -0,0 +1,89 @@ +import os +import sys +from unittest import mock + +# Standard path insertion +sys.path.insert(0, os.path.abspath("../..")) + +import pytest +import httpx +from litellm.proxy.proxy_server import app + + +@pytest.mark.asyncio +async def test_get_image_error_handling(): + """ + Test that get_image handles network errors gracefully and doesn't hang. + """ + # Set an unreachable URL + os.environ["UI_LOGO_PATH"] = "http://invalid-url-12345.com/logo.jpg" + + # Clear cache + parent_dir = os.path.dirname( + os.path.dirname( + app.__file__ + if hasattr(app, "__file__") + else "litellm/proxy/proxy_server.py" + ) + ) + cache_path = os.path.join(parent_dir, "proxy", "cached_logo.jpg") + if os.path.exists(cache_path): + os.remove(cache_path) + + # Mock AsyncHTTPHandler to simulate a timeout or connection error + with mock.patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.get" + ) as mock_get: + mock_get.side_effect = httpx.ConnectError("Network is unreachable") + + async with httpx.AsyncClient( + transport=httpx.ASGITransport(app=app), base_url="http://testserver" + ) as ac: + response = await ac.get("/get_image") + + assert response.status_code == 200 + assert response.headers["content-type"] == "image/jpeg" + + +@pytest.mark.asyncio +async def test_get_image_cache_logic(): + """ + Test that once cached, get_image doesn't hit the network. + """ + os.environ["UI_LOGO_PATH"] = "http://example.com/logo.jpg" + + # Clear cache + parent_dir = os.path.dirname( + os.path.dirname( + app.__file__ + if hasattr(app, "__file__") + else "litellm/proxy/proxy_server.py" + ) + ) + cache_path = os.path.join(parent_dir, "proxy", "cached_logo.jpg") + if os.path.exists(cache_path): + os.remove(cache_path) + + # Mock response + mock_response = mock.Mock() + mock_response.status_code = 200 + mock_response.content = b"fake image data" + + with mock.patch( + "litellm.llms.custom_httpx.http_handler.AsyncHTTPHandler.get" + ) as mock_get: + mock_get.return_value = mock_response + + async with httpx.AsyncClient( + transport=httpx.ASGITransport(app=app), base_url="http://testserver" + ) as ac: + # First call - should hit download logic + response1 = await ac.get("/get_image") + assert response1.status_code == 200 + assert mock_get.call_count == 1 + + # Second call - should hit cache + response2 = await ac.get("/get_image") + assert response2.status_code == 200 + # If cache works, mock_get shouldn't be called again + assert mock_get.call_count == 1 diff --git a/tests/proxy_unit_tests/test_proxy_custom_logger.py b/tests/proxy_unit_tests/test_proxy_custom_logger.py index b7e8f6cf499..909799a05ca 100644 --- a/tests/proxy_unit_tests/test_proxy_custom_logger.py +++ b/tests/proxy_unit_tests/test_proxy_custom_logger.py @@ -84,35 +84,33 @@ def test_embedding(client): ) # checks if kwargs passed to async_log_success_event are correct kwargs = my_custom_logger.async_embedding_kwargs litellm_params = kwargs.get("litellm_params") + + # Test 1: Verify metadata is populated correctly metadata = litellm_params.get("metadata", None) print("\n\n Metadata in custom logger kwargs", litellm_params.get("metadata")) - assert metadata is not None - assert "user_api_key" in metadata - assert "headers" in metadata + assert metadata is not None, "metadata should be present in litellm_params" + assert "user_api_key" in metadata, "user_api_key should be in metadata" + assert "headers" in metadata, "headers should be in metadata" + + # Test 2: Verify proxy_server_request contains the original request details proxy_server_request = litellm_params.get("proxy_server_request") + assert proxy_server_request is not None, "proxy_server_request should exist" + assert proxy_server_request.get("url") == "http://testserver/embeddings", "url should match" + assert proxy_server_request.get("method") == "POST", "method should be POST" + assert "headers" in proxy_server_request, "headers should be present" + assert "body" in proxy_server_request, "body should be present" + + # Test 3: Verify request body contains the original input data + body = proxy_server_request["body"] + assert body.get("model") == "azure-embedding-model", "model should match original request" + assert body.get("input") == ["hello"], "input should match original request" + + # Test 4: Verify model_info is populated model_info = litellm_params.get("model_info") - # Remove arrival_time from comparison as it's dynamic - proxy_server_request_copy = {k: v for k, v in proxy_server_request.items() if k != "arrival_time"} - assert proxy_server_request_copy == { - "url": "http://testserver/embeddings", - "method": "POST", - "headers": { - "host": "testserver", - "accept": "*/*", - "accept-encoding": "gzip, deflate, zstd", - "connection": "keep-alive", - "user-agent": "testclient", - "content-length": "51", - "content-type": "application/json", - }, - "body": {"model": "azure-embedding-model", "input": ["hello"]}, - } - assert model_info == { - "input_cost_per_token": 0.002, - "mode": "embedding", - "id": "hello", - "db_model": False, - } + assert model_info is not None, "model_info should exist" + assert model_info.get("mode") == "embedding", "mode should be embedding" + assert model_info.get("id") == "hello", "id should match" + assert model_info.get("input_cost_per_token") == 0.002, "input cost should match" result = response.json() print(f"Received response: {result}") print("Passed Embedding custom logger on proxy!") @@ -173,43 +171,36 @@ def test_chat_completion(client): my_custom_logger.async_completion_kwargs, ) litellm_params = my_custom_logger.async_completion_kwargs.get("litellm_params") + + # Test 1: Verify metadata is populated correctly metadata = litellm_params.get("metadata", None) print("\n\n Metadata in custom logger kwargs", litellm_params.get("metadata")) - assert metadata is not None - assert "user_api_key" in metadata - assert "user_api_key_metadata" in metadata - assert "headers" in metadata + assert metadata is not None, "metadata should be present" + assert "user_api_key" in metadata, "user_api_key should be in metadata" + assert "user_api_key_metadata" in metadata, "user_api_key_metadata should be in metadata" + assert "headers" in metadata, "headers should be in metadata" + + # Test 2: Verify model_info is populated config_model_info = litellm_params.get("model_info") + assert config_model_info is not None, "model_info should exist" + assert config_model_info.get("id") == "gm", "model id should match" + assert config_model_info.get("mode") == "chat", "mode should be chat" + assert config_model_info.get("input_cost_per_token") == 0.0002, "input cost should match" + + # Test 3: Verify proxy_server_request contains request details proxy_server_request_object = litellm_params.get("proxy_server_request") - - assert config_model_info == { - "id": "gm", - "input_cost_per_token": 0.0002, - "mode": "chat", - "db_model": False, - } - - assert "authorization" not in proxy_server_request_object["headers"] - # Remove arrival_time from comparison as it's dynamic - proxy_server_request_copy = {k: v for k, v in proxy_server_request_object.items() if k != "arrival_time"} - assert proxy_server_request_copy == { - "url": "http://testserver/chat/completions", - "method": "POST", - "headers": { - "host": "testserver", - "accept": "*/*", - "accept-encoding": "gzip, deflate, zstd", - "connection": "keep-alive", - "user-agent": "testclient", - "content-length": "115", - "content-type": "application/json", - }, - "body": { - "model": "Azure OpenAI GPT-4 Canada", - "messages": [{"role": "user", "content": "write a litellm poem"}], - "max_tokens": 10, - }, - } + assert proxy_server_request_object is not None, "proxy_server_request should exist" + assert proxy_server_request_object.get("url") == "http://testserver/chat/completions", "url should match" + assert proxy_server_request_object.get("method") == "POST", "method should be POST" + + # Test 4: Verify authorization is not leaked in logged headers + assert "authorization" not in proxy_server_request_object["headers"], "authorization should not be in headers" + + # Test 5: Verify request body contains original input data + body = proxy_server_request_object.get("body", {}) + assert body.get("model") == "Azure OpenAI GPT-4 Canada", "model should match original request" + assert body.get("messages") == [{"role": "user", "content": "write a litellm poem"}], "messages should match" + assert body.get("max_tokens") == 10, "max_tokens should match" result = response.json() print(f"Received response: {result}") print("\nPassed /chat/completions with Custom Logger!") diff --git a/tests/proxy_unit_tests/test_proxy_server.py b/tests/proxy_unit_tests/test_proxy_server.py index 839d09b52d7..d864a0d986a 100644 --- a/tests/proxy_unit_tests/test_proxy_server.py +++ b/tests/proxy_unit_tests/test_proxy_server.py @@ -1117,7 +1117,10 @@ async def test_create_team_member_add(prisma_client, new_member_method): ) as mock_litellm_usertable, patch( "litellm.proxy.auth.auth_checks._get_team_object_from_user_api_key_cache", new=AsyncMock(return_value=team_obj), - ) as mock_team_obj: + ) as mock_team_obj, patch( + "litellm.proxy.proxy_server.prisma_client.get_data", + new=AsyncMock(return_value=[]), + ) as mock_get_data: mock_client = AsyncMock( return_value=LiteLLM_UserTable( @@ -1126,6 +1129,10 @@ async def test_create_team_member_add(prisma_client, new_member_method): ) mock_litellm_usertable.upsert = mock_client mock_litellm_usertable.find_many = AsyncMock(return_value=None) + # Mock find_first for user_email validation (returns None for new users) + mock_litellm_usertable.find_first = AsyncMock(return_value=None) + # Mock find_unique for user_id validation (returns None for new users) + mock_litellm_usertable.find_unique = AsyncMock(return_value=None) team_mock_client = AsyncMock() original_val = getattr( litellm.proxy.proxy_server.prisma_client.db, "litellm_teamtable" @@ -1299,7 +1306,10 @@ async def test_create_team_member_add_team_admin( ) as mock_litellm_usertable, patch( "litellm.proxy.auth.auth_checks._get_team_object_from_user_api_key_cache", new=AsyncMock(return_value=team_obj), - ) as mock_team_obj: + ) as mock_team_obj, patch( + "litellm.proxy.proxy_server.prisma_client.get_data", + new=AsyncMock(return_value=[]), + ) as mock_get_data: mock_client = AsyncMock( return_value=LiteLLM_UserTable( user_id="1234", max_budget=100, user_email="1234" @@ -1307,6 +1317,10 @@ async def test_create_team_member_add_team_admin( ) mock_litellm_usertable.upsert = mock_client mock_litellm_usertable.find_many = AsyncMock(return_value=None) + # Mock find_first for user_email validation (returns None for new users) + mock_litellm_usertable.find_first = AsyncMock(return_value=None) + # Mock find_unique for user_id validation (returns None for new users) + mock_litellm_usertable.find_unique = AsyncMock(return_value=None) team_mock_client = AsyncMock() original_val = getattr( diff --git a/tests/proxy_unit_tests/test_response_polling_handler.py b/tests/proxy_unit_tests/test_response_polling_handler.py index cb4cd0efe57..26f8ac24adc 100644 --- a/tests/proxy_unit_tests/test_response_polling_handler.py +++ b/tests/proxy_unit_tests/test_response_polling_handler.py @@ -1005,6 +1005,142 @@ class TestPollingConditionChecks: assert result is False + # ==================== Native Background Mode Tests ==================== + + def test_polling_disabled_when_model_in_native_background_mode(self): + """Test that polling is disabled when model is in native_background_mode list""" + from litellm.proxy.response_polling.polling_handler import should_use_polling_for_request + + result = should_use_polling_for_request( + background_mode=True, + polling_via_cache_enabled="all", + redis_cache=Mock(), + model="o4-mini-deep-research", + llm_router=None, + native_background_mode=["o4-mini-deep-research", "o3-deep-research"], + ) + + assert result is False + + def test_polling_disabled_for_native_background_mode_with_provider_list(self): + """Test that native_background_mode takes precedence even when provider matches""" + from litellm.proxy.response_polling.polling_handler import should_use_polling_for_request + + result = should_use_polling_for_request( + background_mode=True, + polling_via_cache_enabled=["openai"], + redis_cache=Mock(), + model="openai/o4-mini-deep-research", + llm_router=None, + native_background_mode=["openai/o4-mini-deep-research"], + ) + + assert result is False + + def test_polling_enabled_when_model_not_in_native_background_mode(self): + """Test that polling is enabled when model is not in native_background_mode list""" + from litellm.proxy.response_polling.polling_handler import should_use_polling_for_request + + result = should_use_polling_for_request( + background_mode=True, + polling_via_cache_enabled="all", + redis_cache=Mock(), + model="gpt-4o", + llm_router=None, + native_background_mode=["o4-mini-deep-research"], + ) + + assert result is True + + def test_polling_enabled_when_native_background_mode_is_none(self): + """Test that polling works normally when native_background_mode is None""" + from litellm.proxy.response_polling.polling_handler import should_use_polling_for_request + + result = should_use_polling_for_request( + background_mode=True, + polling_via_cache_enabled="all", + redis_cache=Mock(), + model="gpt-4o", + llm_router=None, + native_background_mode=None, + ) + + assert result is True + + def test_polling_enabled_when_native_background_mode_is_empty_list(self): + """Test that polling works normally when native_background_mode is empty list""" + from litellm.proxy.response_polling.polling_handler import should_use_polling_for_request + + result = should_use_polling_for_request( + background_mode=True, + polling_via_cache_enabled="all", + redis_cache=Mock(), + model="gpt-4o", + llm_router=None, + native_background_mode=[], + ) + + assert result is True + + def test_native_background_mode_exact_match_required(self): + """Test that native_background_mode uses exact model name matching""" + from litellm.proxy.response_polling.polling_handler import should_use_polling_for_request + + # "o4-mini" should not match "o4-mini-deep-research" + result = should_use_polling_for_request( + background_mode=True, + polling_via_cache_enabled="all", + redis_cache=Mock(), + model="o4-mini", + llm_router=None, + native_background_mode=["o4-mini-deep-research"], + ) + + assert result is True + + def test_native_background_mode_with_provider_prefix_in_request(self): + """Test native_background_mode matching when request model has provider prefix""" + from litellm.proxy.response_polling.polling_handler import should_use_polling_for_request + + # Model in native_background_mode without provider prefix + # Request comes in with provider prefix - should not match + result = should_use_polling_for_request( + background_mode=True, + polling_via_cache_enabled=["openai"], + redis_cache=Mock(), + model="openai/o4-mini-deep-research", + llm_router=None, + native_background_mode=["o4-mini-deep-research"], # Without prefix + ) + + # Should return True because "openai/o4-mini-deep-research" != "o4-mini-deep-research" + assert result is True + + def test_native_background_mode_with_router_lookup(self): + """Test that native_background_mode works with router-resolved models""" + from litellm.proxy.response_polling.polling_handler import should_use_polling_for_request + + mock_router = Mock() + mock_router.model_name_to_deployment_indices = {"deep-research": [0]} + mock_router.model_list = [ + { + "model_name": "deep-research", + "litellm_params": {"model": "openai/o4-mini-deep-research"} + } + ] + + # Model alias "deep-research" is in native_background_mode + result = should_use_polling_for_request( + background_mode=True, + polling_via_cache_enabled=["openai"], + redis_cache=Mock(), + model="deep-research", + llm_router=mock_router, + native_background_mode=["deep-research"], + ) + + assert result is False + class TestStreamingEventParsing: """ diff --git a/tests/proxy_unit_tests/test_server_root_path.py b/tests/proxy_unit_tests/test_server_root_path.py new file mode 100644 index 00000000000..4b39558e15a --- /dev/null +++ b/tests/proxy_unit_tests/test_server_root_path.py @@ -0,0 +1,64 @@ +import os +from unittest import mock +from litellm.proxy import utils + + +# Test the utility function logic +def test_get_server_root_path_unset(): + """ + Test that get_server_root_path returns empty string when SERVER_ROOT_PATH is unset + """ + with mock.patch.dict(os.environ, {}, clear=True): + # We need to make sure SERVER_ROOT_PATH is not in env + if "SERVER_ROOT_PATH" in os.environ: + del os.environ["SERVER_ROOT_PATH"] + + root_path = utils.get_server_root_path() + assert ( + root_path == "" + ), "Should return empty string when unset to allow X-Forwarded-Prefix" + + +def test_get_server_root_path_set(): + """ + Test that get_server_root_path returns the value when SERVER_ROOT_PATH is set + """ + with mock.patch.dict(os.environ, {"SERVER_ROOT_PATH": "/my-path"}, clear=True): + root_path = utils.get_server_root_path() + assert root_path == "/my-path", "Should return the set value" + + +def test_get_server_root_path_empty_string(): + """ + Test that get_server_root_path returns empty string when SERVER_ROOT_PATH is explicitly empty + """ + with mock.patch.dict(os.environ, {"SERVER_ROOT_PATH": ""}, clear=True): + root_path = utils.get_server_root_path() + assert ( + root_path == "" + ), "Should return empty string when explicitly set to empty" + + +# Integration test simulation for FastAPI app initialization +def test_fastapi_app_initialization_mock(): + """ + Simulate how proxy_server.py initializes FastAPI app with the root_path. + We don't import proxy_server because it has global side effects/singletons. + Instead we verify the logic flow. + """ + from fastapi import FastAPI + + # CASE 1: Proxy Mode (Unset) + with mock.patch.dict(os.environ, {}, clear=True): + if "SERVER_ROOT_PATH" in os.environ: + del os.environ["SERVER_ROOT_PATH"] + + server_root_path = utils.get_server_root_path() + app = FastAPI(root_path=server_root_path) + assert app.root_path == "" + + # CASE 2: Direct Mode (Set) + with mock.patch.dict(os.environ, {"SERVER_ROOT_PATH": "/custom-root"}, clear=True): + server_root_path = utils.get_server_root_path() + app = FastAPI(root_path=server_root_path) + assert app.root_path == "/custom-root" diff --git a/tests/proxy_unit_tests/test_zero_cost_model_budget_bypass.py b/tests/proxy_unit_tests/test_zero_cost_model_budget_bypass.py new file mode 100644 index 00000000000..bc818fc0dca --- /dev/null +++ b/tests/proxy_unit_tests/test_zero_cost_model_budget_bypass.py @@ -0,0 +1,590 @@ +""" +Tests for zero-cost model budget bypass functionality. + +When a user exceeds their budget, the system should still allow requests +to models with zero cost (e.g., on-premises models). +""" + +import asyncio +from typing import Optional +from unittest.mock import MagicMock, patch + +import pytest + +import litellm +from litellm.caching.caching import DualCache +from litellm.proxy._types import ( + LiteLLM_BudgetTable, + LiteLLM_EndUserTable, + LiteLLM_TeamMembership, + LiteLLM_TeamTable, + LiteLLM_UserTable, + UserAPIKeyAuth, +) +from litellm.proxy.auth.auth_checks import ( + _check_team_member_budget, + _is_model_cost_zero, + _team_max_budget_check, + common_checks, +) +from litellm.proxy.utils import ProxyLogging +from litellm.router import Router +from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo + + +@pytest.fixture +def mock_router_with_zero_cost_model(): + """Create a mock router with a zero-cost model.""" + router = Router( + model_list=[ + { + "model_name": "on-prem-model", + "litellm_params": { + "model": "ollama/llama2", + "api_base": "http://localhost:11434", + "input_cost_per_token": 0.0, + "output_cost_per_token": 0.0, + }, + "model_info": { + "id": "on-prem-model-id", + "input_cost_per_token": 0.0, + "output_cost_per_token": 0.0, + }, + }, + { + "model_name": "cloud-model", + "litellm_params": { + "model": "gpt-3.5-turbo", + "api_key": "sk-test", + }, + "model_info": { + "id": "cloud-model-id", + }, + }, + ] + ) + return router + + +@pytest.fixture +def mock_router_with_paid_model(): + """Create a mock router with only paid models.""" + router = Router( + model_list=[ + { + "model_name": "cloud-model", + "litellm_params": { + "model": "gpt-3.5-turbo", + "api_key": "sk-test", + }, + "model_info": { + "id": "cloud-model-id", + }, + } + ] + ) + return router + + +@pytest.fixture +def mock_proxy_logging(): + """Create a mock ProxyLogging instance.""" + proxy_logging = ProxyLogging(user_api_key_cache=None) + + async def mock_budget_alerts(*args, **kwargs): + pass + + proxy_logging.budget_alerts = mock_budget_alerts + return proxy_logging + + +class TestIsModelCostZero: + """Tests for _is_model_cost_zero helper function.""" + + def test_zero_cost_model_in_router(self, mock_router_with_zero_cost_model): + """Test that a zero-cost model in router is correctly identified.""" + result = _is_model_cost_zero( + model="on-prem-model", llm_router=mock_router_with_zero_cost_model + ) + assert result is True + + def test_paid_model_in_router(self, mock_router_with_zero_cost_model): + """Test that a paid model is correctly identified as non-zero cost.""" + with patch("litellm.get_model_info") as mock_get_model_info: + # Mock the return value for gpt-3.5-turbo + mock_get_model_info.return_value = { + "input_cost_per_token": 0.0000015, + "output_cost_per_token": 0.000002, + } + result = _is_model_cost_zero( + model="cloud-model", llm_router=mock_router_with_zero_cost_model + ) + assert result is False + + def test_none_model(self, mock_router_with_zero_cost_model): + """Test that None model returns False.""" + result = _is_model_cost_zero( + model=None, llm_router=mock_router_with_zero_cost_model + ) + assert result is False + + def test_none_router(self): + """Test that None router returns False.""" + result = _is_model_cost_zero(model="some-model", llm_router=None) + assert result is False + + def test_list_of_zero_cost_models(self, mock_router_with_zero_cost_model): + """Test that a list of zero-cost models returns True.""" + result = _is_model_cost_zero( + model=["on-prem-model"], llm_router=mock_router_with_zero_cost_model + ) + assert result is True + + def test_mixed_cost_models(self, mock_router_with_zero_cost_model): + """Test that a list with mixed cost models returns False.""" + with patch("litellm.get_model_info") as mock_get_model_info: + mock_get_model_info.return_value = { + "input_cost_per_token": 0.0000015, + "output_cost_per_token": 0.000002, + } + result = _is_model_cost_zero( + model=["on-prem-model", "cloud-model"], + llm_router=mock_router_with_zero_cost_model, + ) + assert result is False + + +class TestUserBudgetBypass: + """Tests for user budget bypass with zero-cost models.""" + + @pytest.mark.asyncio + async def test_user_over_budget_with_zero_cost_model_allowed( + self, mock_router_with_zero_cost_model, mock_proxy_logging + ): + """Test that user over budget can still use zero-cost models.""" + user_object = LiteLLM_UserTable( + user_id="test-user", + spend=100.0, + max_budget=50.0, + ) + + request_body = {"model": "on-prem-model"} + + # Should not raise BudgetExceededError + result = await common_checks( + request_body=request_body, + team_object=None, + user_object=user_object, + end_user_object=None, + global_proxy_spend=None, + general_settings={}, + route="/v1/chat/completions", + llm_router=mock_router_with_zero_cost_model, + proxy_logging_obj=mock_proxy_logging, + valid_token=UserAPIKeyAuth( + token="test-token", + user_id="test-user", + ), + request=MagicMock(), + skip_budget_checks=True, # This is set by user_api_key_auth for zero-cost models + ) + assert result is True + + @pytest.mark.asyncio + async def test_user_over_budget_with_paid_model_blocked( + self, mock_router_with_zero_cost_model, mock_proxy_logging + ): + """Test that user over budget cannot use paid models.""" + user_object = LiteLLM_UserTable( + user_id="test-user", + spend=100.0, + max_budget=50.0, + ) + + request_body = {"model": "cloud-model"} + + with patch("litellm.get_model_info") as mock_get_model_info: + mock_get_model_info.return_value = { + "input_cost_per_token": 0.0000015, + "output_cost_per_token": 0.000002, + } + with pytest.raises(litellm.BudgetExceededError) as exc_info: + await common_checks( + request_body=request_body, + team_object=None, + user_object=user_object, + end_user_object=None, + global_proxy_spend=None, + general_settings={}, + route="/v1/chat/completions", + llm_router=mock_router_with_zero_cost_model, + proxy_logging_obj=mock_proxy_logging, + valid_token=UserAPIKeyAuth( + token="test-token", + user_id="test-user", + ), + request=MagicMock(), + ) + + assert exc_info.value.current_cost == 100.0 + assert exc_info.value.max_budget == 50.0 + assert "test-user" in str(exc_info.value) + + +class TestEndUserBudgetBypass: + """Tests for end user budget bypass with zero-cost models.""" + + @pytest.mark.asyncio + async def test_end_user_over_budget_with_zero_cost_model_allowed( + self, mock_router_with_zero_cost_model, mock_proxy_logging + ): + """Test that end user over budget can still use zero-cost models.""" + end_user_budget = LiteLLM_BudgetTable(max_budget=20.0) + end_user_object = LiteLLM_EndUserTable( + user_id="end-user-123", + spend=50.0, + litellm_budget_table=end_user_budget, + blocked=False, + ) + + request_body = {"model": "on-prem-model", "user": "end-user-123"} + + # In the real flow, skip_budget_checks would be set to True for zero-cost models + result = await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=end_user_object, + global_proxy_spend=None, + general_settings={}, + route="/v1/chat/completions", + llm_router=mock_router_with_zero_cost_model, + proxy_logging_obj=mock_proxy_logging, + valid_token=UserAPIKeyAuth( + token="test-token", + ), + request=MagicMock(), + skip_budget_checks=True, # This is set by user_api_key_auth for zero-cost models + ) + assert result is True + + @pytest.mark.asyncio + async def test_end_user_over_budget_with_paid_model_blocked( + self, mock_router_with_zero_cost_model, mock_proxy_logging + ): + """Test that end user over budget cannot use paid models.""" + end_user_budget = LiteLLM_BudgetTable(max_budget=20.0) + end_user_object = LiteLLM_EndUserTable( + user_id="end-user-123", + spend=50.0, + litellm_budget_table=end_user_budget, + blocked=False, + ) + + request_body = {"model": "cloud-model", "user": "end-user-123"} + + with patch("litellm.get_model_info") as mock_get_model_info: + mock_get_model_info.return_value = { + "input_cost_per_token": 0.0000015, + "output_cost_per_token": 0.000002, + } + with pytest.raises(litellm.BudgetExceededError) as exc_info: + await common_checks( + request_body=request_body, + team_object=None, + user_object=None, + end_user_object=end_user_object, + global_proxy_spend=None, + general_settings={}, + route="/v1/chat/completions", + llm_router=mock_router_with_zero_cost_model, + proxy_logging_obj=mock_proxy_logging, + valid_token=UserAPIKeyAuth( + token="test-token", + ), + request=MagicMock(), + ) + + assert exc_info.value.current_cost == 50.0 + assert exc_info.value.max_budget == 20.0 + assert "end-user-123" in str(exc_info.value) + + +class TestTeamBudgetBypass: + """Tests for team budget bypass with zero-cost models.""" + + @pytest.mark.asyncio + async def test_team_over_budget_with_zero_cost_model_allowed( + self, mock_router_with_zero_cost_model, mock_proxy_logging + ): + """Test that team over budget can still use zero-cost models.""" + team_object = LiteLLM_TeamTable( + team_id="test-team", + spend=150.0, + max_budget=100.0, + ) + + valid_token = UserAPIKeyAuth( + token="test-token", + team_id="test-team", + ) + + request_body = {"model": "on-prem-model"} + + # In the real flow, skip_budget_checks would be set to True for zero-cost models + result = await common_checks( + request_body=request_body, + team_object=team_object, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings={}, + route="/v1/chat/completions", + llm_router=mock_router_with_zero_cost_model, + proxy_logging_obj=mock_proxy_logging, + valid_token=valid_token, + request=MagicMock(), + skip_budget_checks=True, # This is set by user_api_key_auth for zero-cost models + ) + assert result is True + + @pytest.mark.asyncio + async def test_team_over_budget_with_paid_model_blocked( + self, mock_router_with_zero_cost_model, mock_proxy_logging + ): + """Test that team over budget cannot use paid models.""" + team_object = LiteLLM_TeamTable( + team_id="test-team", + spend=150.0, + max_budget=100.0, + ) + + valid_token = UserAPIKeyAuth( + token="test-token", + team_id="test-team", + ) + + request_body = {"model": "cloud-model"} + + with patch("litellm.get_model_info") as mock_get_model_info: + mock_get_model_info.return_value = { + "input_cost_per_token": 0.0000015, + "output_cost_per_token": 0.000002, + } + with pytest.raises(litellm.BudgetExceededError) as exc_info: + await common_checks( + request_body=request_body, + team_object=team_object, + user_object=None, + end_user_object=None, + global_proxy_spend=None, + general_settings={}, + route="/v1/chat/completions", + llm_router=mock_router_with_zero_cost_model, + proxy_logging_obj=mock_proxy_logging, + valid_token=valid_token, + request=MagicMock(), + ) + + assert exc_info.value.current_cost == 150.0 + assert exc_info.value.max_budget == 100.0 + assert "test-team" in str(exc_info.value) + + +class TestTeamMemberBudgetBypass: + """Tests for team member budget bypass with zero-cost models.""" + + @pytest.mark.asyncio + async def test_team_member_over_budget_with_zero_cost_model_allowed( + self, mock_router_with_zero_cost_model, mock_proxy_logging + ): + """Test that team member over budget can still use zero-cost models.""" + team_object = LiteLLM_TeamTable( + team_id="test-team", + ) + + user_object = LiteLLM_UserTable( + user_id="test-user", + ) + + valid_token = UserAPIKeyAuth( + token="test-token", + user_id="test-user", + team_id="test-team", + ) + + member_budget = LiteLLM_BudgetTable(max_budget=30.0) + team_membership = LiteLLM_TeamMembership( + user_id="test-user", + team_id="test-team", + spend=60.0, + litellm_budget_table=member_budget, + ) + + request_body = {"model": "on-prem-model"} + + # Mock get_team_membership + with patch( + "litellm.proxy.auth.auth_checks.get_team_membership" + ) as mock_get_membership: + mock_get_membership.return_value = team_membership + + # In the real flow, skip_budget_checks would be set to True for zero-cost models + result = await common_checks( + request_body=request_body, + team_object=team_object, + user_object=user_object, + end_user_object=None, + global_proxy_spend=None, + general_settings={}, + route="/v1/chat/completions", + llm_router=mock_router_with_zero_cost_model, + proxy_logging_obj=mock_proxy_logging, + valid_token=valid_token, + request=MagicMock(), + skip_budget_checks=True, # This is set by user_api_key_auth for zero-cost models + ) + assert result is True + + @pytest.mark.asyncio + async def test_team_member_over_budget_with_paid_model_blocked( + self, mock_router_with_zero_cost_model, mock_proxy_logging + ): + """Test that team member over budget cannot use paid models.""" + team_object = LiteLLM_TeamTable( + team_id="test-team", + ) + + user_object = LiteLLM_UserTable( + user_id="test-user", + ) + + valid_token = UserAPIKeyAuth( + token="test-token", + user_id="test-user", + team_id="test-team", + ) + + member_budget = LiteLLM_BudgetTable(max_budget=30.0) + team_membership = LiteLLM_TeamMembership( + user_id="test-user", + team_id="test-team", + spend=60.0, + litellm_budget_table=member_budget, + ) + + request_body = {"model": "cloud-model"} + + with patch( + "litellm.proxy.auth.auth_checks.get_team_membership" + ) as mock_get_membership: + mock_get_membership.return_value = team_membership + + with patch("litellm.get_model_info") as mock_get_model_info: + mock_get_model_info.return_value = { + "input_cost_per_token": 0.0000015, + "output_cost_per_token": 0.000002, + } + with pytest.raises(litellm.BudgetExceededError) as exc_info: + await common_checks( + request_body=request_body, + team_object=team_object, + user_object=user_object, + end_user_object=None, + global_proxy_spend=None, + general_settings={}, + route="/v1/chat/completions", + llm_router=mock_router_with_zero_cost_model, + proxy_logging_obj=mock_proxy_logging, + valid_token=valid_token, + request=MagicMock(), + ) + + assert exc_info.value.current_cost == 60.0 + assert exc_info.value.max_budget == 30.0 + assert "test-user" in str(exc_info.value) + assert "test-team" in str(exc_info.value) + + +class TestEdgeCases: + """Tests for edge cases and error handling.""" + + def test_model_not_in_router(self, mock_router_with_zero_cost_model): + """Test behavior when model is not found in router.""" + with patch("litellm.get_model_info") as mock_get_model_info: + # Simulate model not found + mock_get_model_info.side_effect = Exception("Model not found") + result = _is_model_cost_zero( + model="nonexistent-model", llm_router=mock_router_with_zero_cost_model + ) + # Should return False (conservative approach) + assert result is False + + @pytest.mark.asyncio + async def test_user_under_budget_with_paid_model_allowed( + self, mock_router_with_zero_cost_model, mock_proxy_logging + ): + """Test that user under budget can use paid models normally.""" + user_object = LiteLLM_UserTable( + user_id="test-user", + spend=30.0, + max_budget=100.0, + ) + + request_body = {"model": "cloud-model"} + + with patch("litellm.get_model_info") as mock_get_model_info: + mock_get_model_info.return_value = { + "input_cost_per_token": 0.0000015, + "output_cost_per_token": 0.000002, + } + # Should not raise BudgetExceededError + result = await common_checks( + request_body=request_body, + team_object=None, + user_object=user_object, + end_user_object=None, + global_proxy_spend=None, + general_settings={}, + route="/v1/chat/completions", + llm_router=mock_router_with_zero_cost_model, + proxy_logging_obj=mock_proxy_logging, + valid_token=UserAPIKeyAuth( + token="test-token", + user_id="test-user", + ), + request=MagicMock(), + ) + assert result is True + + @pytest.mark.asyncio + async def test_user_under_budget_with_zero_cost_model_allowed( + self, mock_router_with_zero_cost_model, mock_proxy_logging + ): + """Test that user under budget can use zero-cost models normally.""" + user_object = LiteLLM_UserTable( + user_id="test-user", + spend=30.0, + max_budget=100.0, + ) + + request_body = {"model": "on-prem-model"} + + # Should not raise BudgetExceededError + result = await common_checks( + request_body=request_body, + team_object=None, + user_object=user_object, + end_user_object=None, + global_proxy_spend=None, + general_settings={}, + route="/v1/chat/completions", + llm_router=mock_router_with_zero_cost_model, + proxy_logging_obj=mock_proxy_logging, + valid_token=UserAPIKeyAuth( + token="test-token", + user_id="test-user", + ), + request=MagicMock(), + ) + assert result is True diff --git a/tests/router_unit_tests/test_router_acancel_batch.py b/tests/router_unit_tests/test_router_acancel_batch.py new file mode 100644 index 00000000000..6e8f489bca8 --- /dev/null +++ b/tests/router_unit_tests/test_router_acancel_batch.py @@ -0,0 +1,53 @@ +""" +Test router.acancel_batch() functionality + +This ensures the router's batch cancellation method has test coverage. +""" +import sys +import os + +sys.path.insert(0, os.path.abspath("../..")) + +import pytest +from unittest.mock import patch, AsyncMock, MagicMock +from litellm import Router +import litellm + + +@pytest.fixture +def router(): + """Create a router with a mock deployment""" + return Router( + model_list=[ + { + "model_name": "gpt-4", + "litellm_params": { + "model": "gpt-4", + "api_key": "fake-key", + }, + } + ] + ) + + +@pytest.mark.asyncio +async def test_router_acancel_batch(router): + """Test that router.acancel_batch() calls litellm.acancel_batch with correct params""" + mock_response = MagicMock() + mock_response.id = "batch_123" + mock_response.status = "cancelled" + + with patch.object(litellm, "acancel_batch", new_callable=AsyncMock) as mock_cancel: + mock_cancel.return_value = mock_response + + # This tests that the router method exists and can be called + # The actual API call is mocked + response = await router.acancel_batch( + model="gpt-4", + batch_id="batch_123", + ) + + # Verify the mock was called + assert mock_cancel.called + assert response.id == "batch_123" + assert response.status == "cancelled" diff --git a/tests/router_unit_tests/test_router_helper_utils.py b/tests/router_unit_tests/test_router_helper_utils.py index 073433cb9e5..673c606a7d1 100644 --- a/tests/router_unit_tests/test_router_helper_utils.py +++ b/tests/router_unit_tests/test_router_helper_utils.py @@ -2169,3 +2169,32 @@ def test_resolve_model_name_from_model_id(): result = router.resolve_model_name_from_model_id("gpt-3.5-turbo") assert result == "gpt-3.5-turbo" + + +def test_get_valid_args(): + """Test get_valid_args static method returns valid Router.__init__ arguments""" + # Call the static method + valid_args = Router.get_valid_args() + + # Verify it returns a list + assert isinstance(valid_args, list) + assert len(valid_args) > 0 + + # Verify it contains expected Router.__init__ arguments + expected_args = [ + "model_list", + "routing_strategy", + "cache_responses", + "num_retries", + "timeout", + "fallbacks", + ] + for arg in expected_args: + assert arg in valid_args, f"Expected argument '{arg}' not found in valid_args" + + # Verify "self" is not in the list (since it's removed) + assert "self" not in valid_args + + # Verify it contains keyword-only arguments too + # These are common Router.__init__ parameters + assert "assistants_config" in valid_args or "search_tools" in valid_args diff --git a/tests/test_litellm/a2a_protocol/test_card_resolver.py b/tests/test_litellm/a2a_protocol/test_card_resolver.py new file mode 100644 index 00000000000..a1bd33107b6 --- /dev/null +++ b/tests/test_litellm/a2a_protocol/test_card_resolver.py @@ -0,0 +1,69 @@ +""" +Mock tests for LiteLLMA2ACardResolver. + +Tests that the card resolver tries both old and new well-known paths. +""" + +import sys +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + + +@pytest.mark.asyncio +async def test_card_resolver_fallback_from_new_to_old_path(): + """ + Test that the card resolver tries the new path (/.well-known/agent-card.json) first, + and falls back to the old path (/.well-known/agent.json) if the new path fails. + """ + # Mock the AgentCard + mock_agent_card = MagicMock() + mock_agent_card.name = "Test Agent" + mock_agent_card.description = "A test agent" + + # Track which paths were called + paths_called = [] + + # Create a mock base class + class MockA2ACardResolver: + def __init__(self, base_url): + self.base_url = base_url + + async def get_agent_card(self, relative_card_path=None, http_kwargs=None): + paths_called.append(relative_card_path) + if relative_card_path == "/.well-known/agent-card.json": + # First call (new path) fails + raise Exception("404 Not Found") + else: + # Second call (old path) succeeds + return mock_agent_card + + # Create mock A2A module + mock_a2a_module = MagicMock() + mock_a2a_client = MagicMock() + mock_a2a_constants = MagicMock() + mock_a2a_constants.AGENT_CARD_WELL_KNOWN_PATH = "/.well-known/agent-card.json" + mock_a2a_constants.PREV_AGENT_CARD_WELL_KNOWN_PATH = "/.well-known/agent.json" + + with patch.dict( + sys.modules, + { + "a2a": mock_a2a_module, + "a2a.client": MagicMock(A2ACardResolver=MockA2ACardResolver), + "a2a.utils.constants": mock_a2a_constants, + }, + ): + # Import after patching + from litellm.a2a_protocol.card_resolver import LiteLLMA2ACardResolver + + resolver = LiteLLMA2ACardResolver(base_url="http://test-agent:8000") + result = await resolver.get_agent_card() + + # Verify both paths were tried in correct order + assert len(paths_called) == 2 + assert paths_called[0] == "/.well-known/agent-card.json" # New path tried first + assert paths_called[1] == "/.well-known/agent.json" # Old path tried second + + # Verify the result + assert result == mock_agent_card + assert result.name == "Test Agent" diff --git a/tests/test_litellm/containers/test_container_api.py b/tests/test_litellm/containers/test_container_api.py index c7bb68e79cb..9dcd9312ef3 100644 --- a/tests/test_litellm/containers/test_container_api.py +++ b/tests/test_litellm/containers/test_container_api.py @@ -22,6 +22,7 @@ from litellm.containers.main import ( list_containers, retrieve_container, ) +from litellm.main import base_llm_http_handler from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLogging from litellm.llms.openai.containers.transformation import OpenAIContainerConfig from litellm.router import Router @@ -32,6 +33,21 @@ from litellm.types.containers.main import ( ) +@pytest.fixture(autouse=True) +def clear_client_cache(): + """ + Clear the HTTP client cache before each test to ensure mocks are used. + This prevents cached real clients from being reused across tests. + """ + cache = getattr(litellm, "in_memory_llm_clients_cache", None) + if cache is not None: + cache.flush_cache() + yield + # Clear again after test to avoid polluting other tests + if cache is not None: + cache.flush_cache() + + class TestContainerAPI: """Test suite for container API functionality.""" @@ -48,9 +64,7 @@ class TestContainerAPI: name="Test Container" ) - with patch('litellm.containers.main.base_llm_http_handler') as mock_handler: - mock_handler.container_create_handler.return_value = mock_response - + with patch.object(base_llm_http_handler, 'container_create_handler', return_value=mock_response): response = create_container( name="Test Container", custom_llm_provider="openai" @@ -74,9 +88,7 @@ class TestContainerAPI: name="Expiring Container" ) - with patch('litellm.containers.main.base_llm_http_handler') as mock_handler: - mock_handler.container_create_handler.return_value = mock_response - + with patch.object(base_llm_http_handler, 'container_create_handler', return_value=mock_response): response = create_container( name="Expiring Container", expires_after={"anchor": "last_active_at", "minutes": 30}, @@ -98,9 +110,7 @@ class TestContainerAPI: name="Container with Files" ) - with patch('litellm.containers.main.base_llm_http_handler') as mock_handler: - mock_handler.container_create_handler.return_value = mock_response - + with patch.object(base_llm_http_handler, 'container_create_handler', return_value=mock_response): response = create_container( name="Container with Files", file_ids=["file_123", "file_456"], @@ -122,9 +132,7 @@ class TestContainerAPI: name="Async Test Container" ) - with patch('litellm.containers.main.base_llm_http_handler') as mock_handler: - mock_handler.container_create_handler.return_value = mock_response - + with patch.object(base_llm_http_handler, 'container_create_handler', return_value=mock_response): response = await acreate_container( name="Async Test Container", custom_llm_provider="openai" @@ -156,9 +164,7 @@ class TestContainerAPI: has_more=False ) - with patch('litellm.containers.main.base_llm_http_handler') as mock_handler: - mock_handler.container_list_handler.return_value = mock_response - + with patch.object(base_llm_http_handler, 'container_list_handler', return_value=mock_response): response = await alist_containers( custom_llm_provider="openai" ) @@ -166,30 +172,53 @@ class TestContainerAPI: assert isinstance(response, ContainerListResponse) assert len(response.data) == 1 - def test_retrieve_container_basic(self): - """Test basic container retrieval functionality.""" - container_id = "cntr_retrieve_test" + @pytest.mark.parametrize( + "container_id,container_name,status,provider", + [ + ("cntr_retrieve_test", "Retrieved Container", "running", "openai"), + ("cntr_different_id", "Another Container", "stopped", "openai"), + ], + ) + def test_retrieve_container_basic(self, container_id, container_name, status, provider): + """Test basic container retrieval functionality. + + This test verifies that: + 1. retrieve_container correctly calls the handler with the container_id + 2. The response is properly deserialized into a ContainerObject + 3. All fields are correctly mapped from the handler response + 4. The function works with different container states and IDs + """ + # Arrange: Create mock response with test parameters mock_response = ContainerObject( id=container_id, object="container", created_at=1747857508, - status="running", + status=status, expires_after={"anchor": "last_active_at", "minutes": 20}, last_active_at=1747857508, - name="Retrieved Container" + name=container_name ) - with patch('litellm.containers.main.base_llm_http_handler') as mock_handler: - mock_handler.container_retrieve_handler.return_value = mock_response - + with patch.object(base_llm_http_handler, 'container_retrieve_handler', return_value=mock_response) as mock_method: + # Act: Call retrieve_container response = retrieve_container( container_id=container_id, - custom_llm_provider="openai" + custom_llm_provider=provider ) + # Assert: Verify the handler was called correctly + mock_method.assert_called_once() + call_kwargs = mock_method.call_args.kwargs + assert call_kwargs["container_id"] == container_id + + # Assert: Verify response structure and content assert isinstance(response, ContainerObject) assert response.id == container_id - assert response.name == "Retrieved Container" + assert response.name == container_name + assert response.status == status + assert response.object == "container" + assert response.expires_after.minutes == 20 + assert response.expires_after.anchor == "last_active_at" @pytest.mark.asyncio async def test_aretrieve_container_basic(self): @@ -205,9 +234,7 @@ class TestContainerAPI: name="Async Retrieved Container" ) - with patch('litellm.containers.main.base_llm_http_handler') as mock_handler: - mock_handler.container_retrieve_handler.return_value = mock_response - + with patch.object(base_llm_http_handler, 'container_retrieve_handler', return_value=mock_response): response = await aretrieve_container( container_id=container_id, custom_llm_provider="openai" @@ -225,9 +252,7 @@ class TestContainerAPI: deleted=True ) - with patch('litellm.containers.main.base_llm_http_handler') as mock_handler: - mock_handler.container_delete_handler.return_value = mock_response - + with patch.object(base_llm_http_handler, 'container_delete_handler', return_value=mock_response): response = delete_container( container_id=container_id, custom_llm_provider="openai" @@ -248,9 +273,7 @@ class TestContainerAPI: deleted=True ) - with patch('litellm.containers.main.base_llm_http_handler') as mock_handler: - mock_handler.container_delete_handler.return_value = mock_response - + with patch.object(base_llm_http_handler, 'container_delete_handler', return_value=mock_response): response = await adelete_container( container_id=container_id, custom_llm_provider="openai" @@ -262,9 +285,7 @@ class TestContainerAPI: def test_create_container_error_handling(self): """Test error handling in container creation.""" - with patch('litellm.containers.main.base_llm_http_handler') as mock_handler: - mock_handler.container_create_handler.side_effect = Exception("API Error") - + with patch.object(base_llm_http_handler, 'container_create_handler', side_effect=Exception("API Error")): with pytest.raises(Exception): create_container( name="Error Test Container", @@ -273,21 +294,20 @@ class TestContainerAPI: def test_container_provider_config_retrieval(self): """Test that provider config is retrieved correctly.""" + mock_response = ContainerObject( + id="cntr_config_test", + object="container", + created_at=1747857508, + status="running", + expires_after={"anchor": "last_active_at", "minutes": 20}, + last_active_at=1747857508, + name="Config Test" + ) + with patch('litellm.containers.main.ProviderConfigManager') as mock_config_manager: mock_config_manager.get_provider_container_config.return_value = OpenAIContainerConfig() - with patch('litellm.containers.main.base_llm_http_handler') as mock_handler: - mock_response = ContainerObject( - id="cntr_config_test", - object="container", - created_at=1747857508, - status="running", - expires_after={"anchor": "last_active_at", "minutes": 20}, - last_active_at=1747857508, - name="Config Test" - ) - mock_handler.container_create_handler.return_value = mock_response - + with patch.object(base_llm_http_handler, 'container_create_handler', return_value=mock_response): response = create_container( name="Config Test", custom_llm_provider="openai" @@ -315,9 +335,7 @@ class TestContainerAPI: name="Test Container" ) - with patch('litellm.containers.main.base_llm_http_handler') as mock_handler: - mock_handler.container_create_handler.return_value = mock_response - + with patch.object(base_llm_http_handler, 'container_create_handler', return_value=mock_response): result = await router.acreate_container( name="Test Container", custom_llm_provider="openai" diff --git a/tests/test_litellm/containers/test_container_integration.py b/tests/test_litellm/containers/test_container_integration.py index e83ae921c19..d36918c63b9 100644 --- a/tests/test_litellm/containers/test_container_integration.py +++ b/tests/test_litellm/containers/test_container_integration.py @@ -357,15 +357,15 @@ class TestContainerIntegration: def test_error_handling_integration(self): """Test error handling in the integration flow.""" - with patch('litellm.containers.main.base_llm_http_handler') as mock_handler: - # Simulate an API error - mock_handler.container_create_handler.side_effect = litellm.APIError( - status_code=400, - message="API Error occurred", - llm_provider="openai", - model="" - ) - + # Simulate an API error + api_error = litellm.APIError( + status_code=400, + message="API Error occurred", + llm_provider="openai", + model="" + ) + + with patch.object(litellm.main.base_llm_http_handler, 'container_create_handler', side_effect=api_error): with pytest.raises(litellm.APIError): create_container( name="Error Test Container", @@ -385,12 +385,12 @@ class TestContainerIntegration: name="Provider Test Container" ) - with patch('litellm.containers.main.base_llm_http_handler') as mock_handler: - mock_handler.container_create_handler.return_value = mock_response - + with patch.object(litellm.main.base_llm_http_handler, 'container_create_handler', return_value=mock_response) as mock_handler: response = create_container( name="Provider Test Container", custom_llm_provider=provider ) assert response.name == "Provider Test Container" + # Verify the mock was actually called (not making real API calls) + mock_handler.assert_called_once() diff --git a/tests/test_litellm/enterprise/enterprise_callbacks/send_emails/test_resend_email.py b/tests/test_litellm/enterprise/enterprise_callbacks/send_emails/test_resend_email.py index dffa343d99f..b07216921eb 100644 --- a/tests/test_litellm/enterprise/enterprise_callbacks/send_emails/test_resend_email.py +++ b/tests/test_litellm/enterprise/enterprise_callbacks/send_emails/test_resend_email.py @@ -7,10 +7,28 @@ from httpx import Response sys.path.insert(0, os.path.abspath("../../..")) +import litellm from litellm_enterprise.enterprise_callbacks.send_emails.resend_email import ( ResendEmailLogger, ) +# Test file for Resend email integration + + +@pytest.fixture(autouse=True) +def clear_client_cache(): + """ + Clear the HTTP client cache before each test to ensure mocks are used. + This prevents cached real clients from being reused across tests. + """ + cache = getattr(litellm, "in_memory_llm_clients_cache", None) + if cache is not None: + cache.flush_cache() + yield + # Clear again after test to avoid polluting other tests + if cache is not None: + cache.flush_cache() + @pytest.fixture def mock_env_vars(): @@ -18,26 +36,8 @@ def mock_env_vars(): yield -@pytest.fixture -def mock_httpx_client(): - with mock.patch( - "litellm_enterprise.enterprise_callbacks.send_emails.resend_email.get_async_httpx_client" - ) as mock_client: - # Create a mock response - mock_response = mock.AsyncMock(spec=Response) - mock_response.status_code = 200 - mock_response.json.return_value = {"id": "test_email_id"} - - # Create a mock client - mock_async_client = mock.AsyncMock() - mock_async_client.post.return_value = mock_response - mock_client.return_value = mock_async_client - - yield mock_async_client - - @pytest.mark.asyncio -async def test_send_email_success(mock_env_vars, mock_httpx_client): +async def test_send_email_success(mock_env_vars): # Initialize the logger logger = ResendEmailLogger() @@ -47,14 +47,27 @@ async def test_send_email_success(mock_env_vars, mock_httpx_client): subject = "Test Subject" html_body = "

    Test email body

    " + # Create mock HTTP client and inject it directly into the logger + # This ensures the mock is used regardless of any caching/import issues + mock_response = mock.Mock(spec=Response) + mock_response.status_code = 200 + mock_response.json.return_value = {"id": "test_email_id"} + mock_response.raise_for_status.return_value = None + + mock_async_client = mock.AsyncMock() + mock_async_client.post.return_value = mock_response + + # Directly inject the mock client to bypass any caching + logger.async_httpx_client = mock_async_client + # Send email await logger.send_email( from_email=from_email, to_email=to_email, subject=subject, html_body=html_body ) # Verify the HTTP client was called correctly - mock_httpx_client.post.assert_called_once() - call_args = mock_httpx_client.post.call_args + mock_async_client.post.assert_called_once() + call_args = mock_async_client.post.call_args # Verify the URL assert call_args[1]["url"] == "https://api.resend.com/emails" @@ -71,33 +84,50 @@ async def test_send_email_success(mock_env_vars, mock_httpx_client): @pytest.mark.asyncio -async def test_send_email_missing_api_key(mock_httpx_client): - # Remove the API key from environment - if "RESEND_API_KEY" in os.environ: - del os.environ["RESEND_API_KEY"] +async def test_send_email_missing_api_key(): + # Remove the API key from environment before initializing logger + original_key = os.environ.pop("RESEND_API_KEY", None) + + try: + # Initialize the logger after removing the API key + logger = ResendEmailLogger() - # Initialize the logger - logger = ResendEmailLogger() + # Test data + from_email = "test@example.com" + to_email = ["recipient@example.com"] + subject = "Test Subject" + html_body = "

    Test email body

    " - # Test data - from_email = "test@example.com" - to_email = ["recipient@example.com"] - subject = "Test Subject" - html_body = "

    Test email body

    " + # Create mock HTTP client and inject it directly into the logger + # This ensures the mock is used regardless of any caching issues + mock_response = mock.Mock(spec=Response) + mock_response.raise_for_status.return_value = None + mock_response.status_code = 200 + mock_response.json.return_value = {"id": "test_email_id"} + + mock_async_client = mock.AsyncMock() + mock_async_client.post.return_value = mock_response + + # Directly inject the mock client to bypass any caching + logger.async_httpx_client = mock_async_client - # Send email - await logger.send_email( - from_email=from_email, to_email=to_email, subject=subject, html_body=html_body - ) + # Send email + await logger.send_email( + from_email=from_email, to_email=to_email, subject=subject, html_body=html_body + ) - # Verify the HTTP client was called with None as the API key - mock_httpx_client.post.assert_called_once() - call_args = mock_httpx_client.post.call_args - assert call_args[1]["headers"] == {"Authorization": "Bearer None"} + # Verify the HTTP client was called with None as the API key + mock_async_client.post.assert_called_once() + call_args = mock_async_client.post.call_args + assert call_args[1]["headers"] == {"Authorization": "Bearer None"} + finally: + # Restore the original key if it existed + if original_key is not None: + os.environ["RESEND_API_KEY"] = original_key @pytest.mark.asyncio -async def test_send_email_multiple_recipients(mock_env_vars, mock_httpx_client): +async def test_send_email_multiple_recipients(mock_env_vars): # Initialize the logger logger = ResendEmailLogger() @@ -107,13 +137,25 @@ async def test_send_email_multiple_recipients(mock_env_vars, mock_httpx_client): subject = "Test Subject" html_body = "

    Test email body

    " + # Create mock HTTP client and inject it directly into the logger + mock_response = mock.Mock(spec=Response) + mock_response.status_code = 200 + mock_response.json.return_value = {"id": "test_email_id"} + mock_response.raise_for_status.return_value = None + + mock_async_client = mock.AsyncMock() + mock_async_client.post.return_value = mock_response + + # Directly inject the mock client to bypass any caching + logger.async_httpx_client = mock_async_client + # Send email await logger.send_email( from_email=from_email, to_email=to_email, subject=subject, html_body=html_body ) # Verify the HTTP client was called with multiple recipients - mock_httpx_client.post.assert_called_once() - call_args = mock_httpx_client.post.call_args + mock_async_client.post.assert_called_once() + call_args = mock_async_client.post.call_args request_body = call_args[1]["json"] assert request_body["to"] == to_email diff --git a/tests/test_litellm/enterprise/enterprise_callbacks/send_emails/test_sendgrid_email.py b/tests/test_litellm/enterprise/enterprise_callbacks/send_emails/test_sendgrid_email.py index 4ecb4872aaa..fb070cf19b8 100644 --- a/tests/test_litellm/enterprise/enterprise_callbacks/send_emails/test_sendgrid_email.py +++ b/tests/test_litellm/enterprise/enterprise_callbacks/send_emails/test_sendgrid_email.py @@ -2,20 +2,55 @@ import os import sys import unittest.mock as mock +import httpx import pytest +import respx from httpx import Response sys.path.insert(0, os.path.abspath("../../..")) +import litellm from litellm_enterprise.enterprise_callbacks.send_emails.sendgrid_email import ( SendGridEmailLogger, ) +@pytest.fixture(autouse=True) +def clear_client_cache(): + """ + Clear the HTTP client cache before each test to ensure mocks are used. + This prevents cached real clients from being reused across tests. + """ + cache = getattr(litellm, "in_memory_llm_clients_cache", None) + if cache is not None: + cache.flush_cache() + yield + if cache is not None: + cache.flush_cache() + + @pytest.fixture def mock_env_vars(): - with mock.patch.dict(os.environ, {"SENDGRID_API_KEY": "test_api_key"}): + # Store original values + original_api_key = os.environ.get("SENDGRID_API_KEY") + original_sender_email = os.environ.get("SENDGRID_SENDER_EMAIL") + + # Set test API key and remove SENDGRID_SENDER_EMAIL to ensure isolation + os.environ["SENDGRID_API_KEY"] = "test_api_key" + if "SENDGRID_SENDER_EMAIL" in os.environ: + del os.environ["SENDGRID_SENDER_EMAIL"] + + try: yield + finally: + # Restore original values + if original_api_key is not None: + os.environ["SENDGRID_API_KEY"] = original_api_key + elif "SENDGRID_API_KEY" in os.environ: + del os.environ["SENDGRID_API_KEY"] + + if original_sender_email is not None: + os.environ["SENDGRID_SENDER_EMAIL"] = original_sender_email @pytest.fixture @@ -23,14 +58,16 @@ def mock_httpx_client(): with mock.patch( "litellm_enterprise.enterprise_callbacks.send_emails.sendgrid_email.get_async_httpx_client" ) as mock_client: - mock_response = mock.AsyncMock(spec=Response) + + mock_response = mock.Mock(spec=Response) mock_response.status_code = 202 mock_response.text = "accepted" + mock_response.raise_for_status.return_value = None mock_async_client = mock.AsyncMock() mock_async_client.post.return_value = mock_response - mock_client.return_value = mock_async_client + mock_client.return_value = mock_async_client yield mock_async_client @@ -62,8 +99,10 @@ async def test_send_email_success(mock_env_vars, mock_httpx_client): @pytest.mark.asyncio -async def test_send_email_missing_api_key(mock_httpx_client): - with mock.patch.dict(os.environ, {}, clear=True): +async def test_send_email_missing_api_key(): + original_key = os.environ.pop("SENDGRID_API_KEY", None) + + try: logger = SendGridEmailLogger() with pytest.raises(ValueError): @@ -73,12 +112,19 @@ async def test_send_email_missing_api_key(mock_httpx_client): subject="Test Subject", html_body="

    Test email body

    ", ) - - mock_httpx_client.post.assert_not_called() + finally: + if original_key is not None: + os.environ["SENDGRID_API_KEY"] = original_key @pytest.mark.asyncio +@respx.mock async def test_send_email_multiple_recipients(mock_env_vars, mock_httpx_client): + # Block all HTTP requests at network level to prevent real API calls + respx.post("https://api.sendgrid.com/v3/mail/send").mock( + return_value=httpx.Response(202, text="accepted") + ) + logger = SendGridEmailLogger() from_email = "test@example.com" @@ -86,6 +132,12 @@ async def test_send_email_multiple_recipients(mock_env_vars, mock_httpx_client): subject = "Test Subject" html_body = "

    Test email body

    " + mock_response = mock.Mock(spec=Response) + mock_response.status_code = 202 + mock_response.text = "accepted" + mock_response.raise_for_status.return_value = None + mock_httpx_client.post.return_value = mock_response + await logger.send_email( from_email=from_email, to_email=to_email, subject=subject, html_body=html_body ) diff --git a/tests/test_litellm/experimental_mcp_client/test_mcp_client.py b/tests/test_litellm/experimental_mcp_client/test_mcp_client.py index 998f4156e93..febc7c454bd 100644 --- a/tests/test_litellm/experimental_mcp_client/test_mcp_client.py +++ b/tests/test_litellm/experimental_mcp_client/test_mcp_client.py @@ -52,16 +52,19 @@ class TestMCPClient: self, mock_session, mock_stdio_client ): """Test successful stdio connection""" - # Setup mocks + # Setup mocks - create proper async context manager mock_transport = (MagicMock(), MagicMock()) - mock_stdio_client.return_value.__aenter__ = AsyncMock( - return_value=mock_transport - ) + mock_stdio_ctx = AsyncMock() + mock_stdio_ctx.__aenter__.return_value = mock_transport + mock_stdio_ctx.__aexit__.return_value = None + mock_stdio_client.return_value = mock_stdio_ctx - mock_session_instance = MagicMock() - mock_session_instance.__aenter__ = AsyncMock(return_value=mock_session_instance) + mock_session_instance = AsyncMock() mock_session_instance.initialize = AsyncMock() - mock_session.return_value = mock_session_instance + mock_session_ctx = AsyncMock() + mock_session_ctx.__aenter__.return_value = mock_session_instance + mock_session_ctx.__aexit__.return_value = None + mock_session.return_value = mock_session_ctx stdio_config = MCPStdioConfig( command="python", args=["-m", "my_mcp_server"], env={"DEBUG": "1"} @@ -94,22 +97,23 @@ class TestMCPClient: self, mock_streamable_http_client ): """Test that MCP client uses SSL configuration from environment variables""" - # Setup mocks + # Setup mocks - create proper async context manager mock_transport = (MagicMock(), MagicMock()) - mock_streamable_http_client.return_value.__aenter__ = AsyncMock( - return_value=mock_transport - ) + mock_http_ctx = AsyncMock() + mock_http_ctx.__aenter__.return_value = mock_transport + mock_http_ctx.__aexit__.return_value = None + mock_streamable_http_client.return_value = mock_http_ctx # Mock the session with patch( "litellm.experimental_mcp_client.client.ClientSession" ) as mock_session: - mock_session_instance = MagicMock() - mock_session_instance.__aenter__ = AsyncMock( - return_value=mock_session_instance - ) + mock_session_instance = AsyncMock() mock_session_instance.initialize = AsyncMock() - mock_session.return_value = mock_session_instance + mock_session_ctx = AsyncMock() + mock_session_ctx.__aenter__.return_value = mock_session_instance + mock_session_ctx.__aexit__.return_value = None + mock_session.return_value = mock_session_ctx client = MCPClient( server_url="https://mcp-server.example.com", @@ -141,20 +145,23 @@ class TestMCPClient: @patch.object(mcp_client_module, "sse_client") async def test_mcp_client_ssl_verify_parameter(self, mock_sse_client): """Test that MCP client uses ssl_verify parameter when provided""" - # Setup mocks + # Setup mocks - create proper async context manager mock_transport = (MagicMock(), MagicMock()) - mock_sse_client.return_value.__aenter__ = AsyncMock(return_value=mock_transport) + mock_sse_ctx = AsyncMock() + mock_sse_ctx.__aenter__.return_value = mock_transport + mock_sse_ctx.__aexit__.return_value = None + mock_sse_client.return_value = mock_sse_ctx # Mock the session with patch( "litellm.experimental_mcp_client.client.ClientSession" ) as mock_session: - mock_session_instance = MagicMock() - mock_session_instance.__aenter__ = AsyncMock( - return_value=mock_session_instance - ) + mock_session_instance = AsyncMock() mock_session_instance.initialize = AsyncMock() - mock_session.return_value = mock_session_instance + mock_session_ctx = AsyncMock() + mock_session_ctx.__aenter__.return_value = mock_session_instance + mock_session_ctx.__aexit__.return_value = None + mock_session.return_value = mock_session_ctx # Test with ssl_verify=False client = MCPClient( @@ -192,22 +199,23 @@ class TestMCPClient: @patch.object(mcp_client_module, "streamable_http_client") async def test_mcp_client_ssl_verify_custom_path(self, mock_streamable_http_client): """Test that MCP client uses custom CA bundle path from ssl_verify parameter""" - # Setup mocks + # Setup mocks - create proper async context manager mock_transport = (MagicMock(), MagicMock()) - mock_streamable_http_client.return_value.__aenter__ = AsyncMock( - return_value=mock_transport - ) + mock_http_ctx = AsyncMock() + mock_http_ctx.__aenter__.return_value = mock_transport + mock_http_ctx.__aexit__.return_value = None + mock_streamable_http_client.return_value = mock_http_ctx # Mock the session with patch( "litellm.experimental_mcp_client.client.ClientSession" ) as mock_session: - mock_session_instance = MagicMock() - mock_session_instance.__aenter__ = AsyncMock( - return_value=mock_session_instance - ) + mock_session_instance = AsyncMock() mock_session_instance.initialize = AsyncMock() - mock_session.return_value = mock_session_instance + mock_session_ctx = AsyncMock() + mock_session_ctx.__aenter__.return_value = mock_session_instance + mock_session_ctx.__aexit__.return_value = None + mock_session.return_value = mock_session_ctx # Test with custom CA bundle path custom_ca_path = "/custom/path/to/ca-bundle.pem" diff --git a/tests/test_litellm/google_genai/test_google_genai_adapter.py b/tests/test_litellm/google_genai/test_google_genai_adapter.py index a5333550992..05b22098371 100644 --- a/tests/test_litellm/google_genai/test_google_genai_adapter.py +++ b/tests/test_litellm/google_genai/test_google_genai_adapter.py @@ -1090,8 +1090,8 @@ async def test_google_generate_content_with_openai(): usage=mock_usage ) - # Use AsyncMock for proper async function mocking - with unittest.mock.patch("litellm.acompletion", new_callable=unittest.mock.AsyncMock) as mock_completion: + # Use AsyncMock for proper async function mocking - patch at the module level where it's imported + with unittest.mock.patch("litellm.google_genai.main.litellm.acompletion", new_callable=unittest.mock.AsyncMock) as mock_completion: # Set the return value directly on the MagicMock mock_completion.return_value = mock_response diff --git a/tests/test_litellm/google_genai/test_google_genai_handler.py b/tests/test_litellm/google_genai/test_google_genai_handler.py index 89b53ca06d5..17a6cba2d63 100644 --- a/tests/test_litellm/google_genai/test_google_genai_handler.py +++ b/tests/test_litellm/google_genai/test_google_genai_handler.py @@ -25,7 +25,7 @@ def test_non_stream_response_when_stream_requested_sync(): the sync handler correctly transforms it to generate_content format. """ from litellm.types.utils import Choices - + # Mock a non-stream response (ModelResponse with valid choices) mock_response = ModelResponse( id="test-123", @@ -70,7 +70,7 @@ async def test_non_stream_response_when_stream_requested_async(): the async handler correctly transforms it to generate_content format. """ from litellm.types.utils import Choices - + # Mock a non-stream response (ModelResponse with valid choices) mock_response = ModelResponse( id="test-123", @@ -183,6 +183,7 @@ def test_stream_transformation_error_sync(): "translate_completion_output_params_streaming", return_value=None ): + # Patch litellm.completion directly to prevent real API calls with patch("litellm.completion", return_value=mock_stream): # Call the handler with stream=True and expect a ValueError with pytest.raises(ValueError, match="Failed to transform streaming response"): @@ -209,7 +210,11 @@ async def test_stream_transformation_error_async(): "translate_completion_output_params_streaming", return_value=None ): - with patch("litellm.acompletion", return_value=mock_stream): + # Mock litellm.acompletion at the module level where it's imported + # We need to patch it in the handler module, not in litellm itself + with patch("litellm.google_genai.adapters.handler.litellm") as mock_litellm: + # Use AsyncMock for async function + mock_litellm.acompletion = AsyncMock(return_value=mock_stream) # Call the handler with stream=True and expect a ValueError with pytest.raises(ValueError, match="Failed to transform streaming response"): await GenerateContentToCompletionHandler.async_generate_content_handler( @@ -225,11 +230,13 @@ def test_citation_metadata_transformation(): Test that citationMetadata.citationSources is properly transformed to citationMetadata.citations to avoid Pydantic validation errors. """ - from litellm.llms.gemini.google_genai.transformation import GoogleGenAIConfig - from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj from unittest.mock import MagicMock + import httpx - + + from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj + from litellm.llms.gemini.google_genai.transformation import GoogleGenAIConfig + # Create a mock response with citationMetadata.citationSources (the problematic format) mock_response_data = { "candidates": [ diff --git a/tests/test_litellm/integrations/arize/test_arize_utils.py b/tests/test_litellm/integrations/arize/test_arize_utils.py index 6eb6e67a088..9a9f3d5afc7 100644 --- a/tests/test_litellm/integrations/arize/test_arize_utils.py +++ b/tests/test_litellm/integrations/arize/test_arize_utils.py @@ -84,7 +84,7 @@ def test_arize_set_attributes(): ArizeLogger.set_arize_attributes(span, kwargs, response_obj) # Validate that the expected number of attributes were set - assert span.set_attribute.call_count == 28 + assert span.set_attribute.call_count == 26 # Metadata attached to the span span.set_attribute.assert_any_call( @@ -108,7 +108,8 @@ def test_arize_set_attributes(): # Response metadata span.set_attribute.assert_any_call("llm.response.id", "chatcmpl-ID") span.set_attribute.assert_any_call("llm.response.model", "gpt-4o") - span.set_attribute.assert_any_call(SpanAttributes.OPENINFERENCE_SPAN_KIND, "LLM") + # Span kind is set to TOOL when tools are present + span.set_attribute.assert_any_call(SpanAttributes.OPENINFERENCE_SPAN_KIND, "TOOL") # Request message content and metadata span.set_attribute.assert_any_call( @@ -125,14 +126,14 @@ def test_arize_set_attributes(): # Tool call definitions and function names span.set_attribute.assert_any_call( - f"{SpanAttributes.LLM_TOOLS}.0.{SpanAttributes.TOOL_NAME}", "get_weather" + f"{SpanAttributes.LLM_TOOLS}.0.name", "get_weather" ) span.set_attribute.assert_any_call( - f"{SpanAttributes.LLM_TOOLS}.0.{SpanAttributes.TOOL_DESCRIPTION}", + f"{SpanAttributes.LLM_TOOLS}.0.description", "Fetches weather details.", ) span.set_attribute.assert_any_call( - f"{SpanAttributes.LLM_TOOLS}.0.{SpanAttributes.TOOL_PARAMETERS}", + f"{SpanAttributes.LLM_TOOLS}.0.parameters", json.dumps( { "type": "object", @@ -144,16 +145,6 @@ def test_arize_set_attributes(): ), ) - # Tool calls captured from optional_params - span.set_attribute.assert_any_call( - f"{MessageAttributes.MESSAGE_TOOL_CALLS}.0.{ToolCallAttributes.TOOL_CALL_FUNCTION_NAME}", - "get_weather", - ) - span.set_attribute.assert_any_call( - f"{MessageAttributes.MESSAGE_TOOL_CALLS}.1.{ToolCallAttributes.TOOL_CALL_FUNCTION_NAME}", - "get_stock_price", - ) - # Invocation parameters span.set_attribute.assert_any_call( SpanAttributes.LLM_INVOCATION_PARAMETERS, '{"user": "test_user"}' diff --git a/tests/test_litellm/integrations/datadog/test_datadog_cost_management.py b/tests/test_litellm/integrations/datadog/test_datadog_cost_management.py new file mode 100644 index 00000000000..be2084969a5 --- /dev/null +++ b/tests/test_litellm/integrations/datadog/test_datadog_cost_management.py @@ -0,0 +1,169 @@ +import os +import time +from unittest.mock import AsyncMock + +import pytest +from httpx import Response + +from litellm.integrations.datadog.datadog_cost_management import ( + DatadogCostManagementLogger, +) +from litellm.types.utils import StandardLoggingPayload + + +@pytest.fixture +def clean_env(): + # Save original env + original_api_key = os.environ.get("DD_API_KEY") + original_app_key = os.environ.get("DD_APP_KEY") + original_site = os.environ.get("DD_SITE") + + # Set test env + os.environ["DD_API_KEY"] = "test_api_key" + os.environ["DD_APP_KEY"] = "test_app_key" + os.environ["DD_SITE"] = "test.datadoghq.com" + + yield + + # Restore original env + if original_api_key: + os.environ["DD_API_KEY"] = original_api_key + else: + del os.environ["DD_API_KEY"] + + if original_app_key: + os.environ["DD_APP_KEY"] = original_app_key + else: + del os.environ["DD_APP_KEY"] + + if original_site: + os.environ["DD_SITE"] = original_site + else: + del os.environ["DD_SITE"] + + +@pytest.mark.asyncio +async def test_init(clean_env): + """ + Test initialization sets up clients and url correctly + """ + logger = DatadogCostManagementLogger() + assert logger.dd_api_key == "test_api_key" + assert logger.dd_app_key == "test_app_key" + assert ( + logger.upload_url == "https://api.test.datadoghq.com/api/v2/cost/custom_costs" + ) + + +@pytest.mark.asyncio +async def test_aggregate_costs(clean_env): + """ + Test that costs are correctly aggregated by provider, model, and date + """ + logger = DatadogCostManagementLogger() + + # Mock some log payloads + now = time.time() + day_str = time.strftime("%Y-%m-%d", time.localtime(now)) + + logs = [ + StandardLoggingPayload( + custom_llm_provider="openai", + model="gpt-4", + response_cost=0.01, + startTime=now, + metadata={"user_api_key_team_alias": "team-a"}, + ), + StandardLoggingPayload( + custom_llm_provider="openai", + model="gpt-4", + response_cost=0.02, + startTime=now, + metadata={"user_api_key_team_alias": "team-a"}, + ), + StandardLoggingPayload( + custom_llm_provider="anthropic", + model="claude-3", + response_cost=0.05, + startTime=now, + ), + ] + + aggregated = logger._aggregate_costs(logs) + + assert len(aggregated) == 2 + + # Check OpenAI entry + openai_entry = next(e for e in aggregated if e["ProviderName"] == "openai") + assert openai_entry["BilledCost"] == 0.03 + assert openai_entry["ChargeDescription"] == "LLM Usage for gpt-4" + assert openai_entry["ChargePeriodStart"] == day_str + assert openai_entry["Tags"]["team"] == "team-a" + assert "env" in openai_entry["Tags"] + assert "service" in openai_entry["Tags"] + + # Check Anthropic entry + anthropic_entry = next(e for e in aggregated if e["ProviderName"] == "anthropic") + assert anthropic_entry["BilledCost"] == 0.05 + + +@pytest.mark.asyncio +async def test_async_log_success_event(clean_env): + """ + Test that logs are added to queue + """ + logger = DatadogCostManagementLogger(batch_size=10) + + await logger.async_log_success_event( + kwargs={"standard_logging_object": {"response_cost": 0.01}}, + response_obj={}, + start_time=time.time(), + end_time=time.time(), + ) + + assert len(logger.log_queue) == 1 + assert logger.log_queue[0]["response_cost"] == 0.01 + + # Test zero cost ignored + await logger.async_log_success_event( + kwargs={"standard_logging_object": {"response_cost": 0.0}}, + response_obj={}, + start_time=time.time(), + end_time=time.time(), + ) + + assert len(logger.log_queue) == 1 + + +@pytest.mark.asyncio +async def test_async_send_batch(clean_env): + """ + Test that batch is aggregated and uploaded + """ + logger = DatadogCostManagementLogger() + logger.async_client = AsyncMock() + logger.async_client.put.return_value = Response(202, json={"status": "ok"}) + + # Add logs directly to queue + logger.log_queue = [ + StandardLoggingPayload( + custom_llm_provider="openai", + model="gpt-4", + response_cost=0.01, + startTime=time.time(), + ) + ] + + await logger.async_send_batch() + + # Verify API called + assert logger.async_client.put.called + call_args = logger.async_client.put.call_args + assert call_args[0][0] == "https://api.test.datadoghq.com/api/v2/cost/custom_costs" + + import json + + # Use call_args.kwargs['content'] + content = json.loads(call_args[1]["content"]) + assert content[0]["ProviderName"] == "openai" + assert content[0]["BilledCost"] == 0.01 diff --git a/tests/test_litellm/integrations/datadog/test_datadog_llm_obs_agent.py b/tests/test_litellm/integrations/datadog/test_datadog_llm_obs_agent.py new file mode 100644 index 00000000000..2bb51e1e1b7 --- /dev/null +++ b/tests/test_litellm/integrations/datadog/test_datadog_llm_obs_agent.py @@ -0,0 +1,62 @@ +import os +from unittest.mock import patch +from litellm.integrations.datadog.datadog_llm_obs import DataDogLLMObsLogger + + +def test_datadog_llm_obs_agent_configuration(): + """ + Test that DataDog LLM Obs logger correctly configures agent endpoint. + """ + test_env = { + "LITELLM_DD_AGENT_HOST": "localhost", + "LITELLM_DD_LLM_OBS_PORT": "10518", + "DD_API_KEY": "test-api-key", # Optional, but checking if it's preserved + } + + # Ensure DD_SITE is NOT set to verify we don't need it in agent mode + + with patch.dict(os.environ, test_env, clear=True): + with patch("asyncio.create_task"): # Prevent periodic flush task from running + dd_logger = DataDogLLMObsLogger() + + expected_url = "http://localhost:10518/api/intake/llm-obs/v1/trace/spans" + assert dd_logger.intake_url == expected_url + assert dd_logger.DD_API_KEY == "test-api-key" + + +def test_datadog_llm_obs_agent_no_api_key_ok(): + """ + Test that agent mode works WITHOUT DD_API_KEY (agent handles auth). + """ + test_env = { + "LITELLM_DD_AGENT_HOST": "localhost", + # No DD_API_KEY + } + + with patch.dict(os.environ, test_env, clear=True): + with patch("asyncio.create_task"): + # Should NOT raise exception anymore + dd_logger = DataDogLLMObsLogger() + + assert dd_logger.DD_API_KEY is None + # Default port is 8126 if not set + expected_url = "http://localhost:8126/api/intake/llm-obs/v1/trace/spans" + assert dd_logger.intake_url == expected_url + + +def test_datadog_llm_obs_direct_api_configuration(): + """ + Test that direct API configuration still works as expected. + """ + test_env = { + "DD_API_KEY": "direct-api-key", + "DD_SITE": "us5.datadoghq.com", + } + + with patch.dict(os.environ, test_env, clear=True): + with patch("asyncio.create_task"): + dd_logger = DataDogLLMObsLogger() + + expected_url = "https://api.us5.datadoghq.com/api/intake/llm-obs/v1/trace/spans" + assert dd_logger.intake_url == expected_url + assert dd_logger.DD_API_KEY == "direct-api-key" diff --git a/tests/test_litellm/integrations/test_custom_guardrail_recursion.py b/tests/test_litellm/integrations/test_custom_guardrail_recursion.py new file mode 100644 index 00000000000..f05b5848bdf --- /dev/null +++ b/tests/test_litellm/integrations/test_custom_guardrail_recursion.py @@ -0,0 +1,73 @@ +import pytest +from litellm.integrations.custom_guardrail import CustomGuardrail +from litellm.types.guardrails import GuardrailEventHooks +import json + + +class TestCustomGuardrailRecursion: + """ + Specific tests for the circular reference / RecursionError fix in logging. + """ + + def test_log_guardrail_information_handles_circular_references(self): + """ + Test that add_standard_logging method sanitizes input data containing circular references + instead of crashing. + + This reproduces the Langfuse crash scenario: + Request -> Metadata -> GuardrailResponse -> DebugContext -> Request + """ + guardrail = CustomGuardrail( + guardrail_name="recursion_test_guardrail", + event_hook=GuardrailEventHooks.pre_call, + ) + + # 1. Setup Circular Data + request_data = {"user_id": "test_recursive_user"} + metadata = {"session_id": "123"} + request_data["metadata"] = metadata + + # Create the danger: Guardrail Response holding a reference back to request_data + dirty_response = { + "flagged": False, + "debug_context": request_data, # <--- ACCESS TO ROOT (Circular Ref) + } + + # 2. Invoke the logging method + # If the fix is working, this will NOT raise RecursionError + try: + guardrail.add_standard_logging_guardrail_information_to_request_data( + guardrail_json_response=dirty_response, + request_data=request_data, + guardrail_status="success", + start_time=1.0, + end_time=2.0, + duration=1.0, + masked_entity_count={}, + event_type=GuardrailEventHooks.pre_call, + ) + except RecursionError: + pytest.fail( + "RecursionError raised! The cyclic reference sanitization failed." + ) + + # 3. Verify the data stored is safe + stored_info = request_data["metadata"][ + "standard_logging_guardrail_information" + ][0] + stored_response = stored_info["guardrail_response"] + + # Check that we can dump it to JSON without crashing (Ultimate proof) + try: + json.dumps(stored_response) + except Exception as e: + pytest.fail(f"Stored data is not JSON serializable: {e}") + + # Check content - keys should be preserved but recursion broken + assert "debug_context" in stored_response + debug_context = stored_response["debug_context"] + + # In a sanitized copy, the nested metadata should be a copy, not the original live dict + assert debug_context["user_id"] == "test_recursive_user" + # The 'metadata' inside 'debug_context' would be where recursion stops or is filtered + assert "metadata" in debug_context diff --git a/tests/test_litellm/integrations/test_prometheus_client_ip_user_agent.py b/tests/test_litellm/integrations/test_prometheus_client_ip_user_agent.py new file mode 100644 index 00000000000..4a9fa3de5fd --- /dev/null +++ b/tests/test_litellm/integrations/test_prometheus_client_ip_user_agent.py @@ -0,0 +1,203 @@ +import pytest +from unittest.mock import MagicMock, patch +from litellm.integrations.prometheus import PrometheusLogger +from litellm.types.integrations.prometheus import ( + UserAPIKeyLabelValues, +) +from litellm.proxy._types import UserAPIKeyAuth + + +@pytest.mark.asyncio +async def test_async_post_call_failure_hook_includes_client_ip_user_agent(): + """ + Test that async_post_call_failure_hook includes client_ip and user_agent in UserAPIKeyLabelValues + """ + # Mocking + # Mocking + with patch( + "litellm.integrations.prometheus.PrometheusLogger.__init__", return_value=None + ): + logger = PrometheusLogger() + # Initialize attributes manually as __init__ is mocked + logger.litellm_proxy_failed_requests_metric = MagicMock() + logger.litellm_proxy_total_requests_metric = MagicMock() + logger.get_labels_for_metric = MagicMock( + return_value=["client_ip", "user_agent"] + ) + + request_data = { + "model": "gpt-4", + "metadata": { + "requester_ip_address": "127.0.0.1", + "user_agent": "test-agent", + }, + } + user_api_key_dict = UserAPIKeyAuth(token="test_token") + original_exception = Exception("Test exception") + + # Mock prometheus_label_factory to inspect arguments + with patch( + "litellm.integrations.prometheus.prometheus_label_factory" + ) as mock_label_factory: + mock_label_factory.return_value = {} + + await logger.async_post_call_failure_hook( + request_data=request_data, + original_exception=original_exception, + user_api_key_dict=user_api_key_dict, + ) + + # Verification + assert mock_label_factory.call_count >= 1 + + # Check calls + calls = mock_label_factory.call_args_list + found = False + for call in calls: + kwargs = call.kwargs + enum_values = kwargs.get("enum_values") + if isinstance(enum_values, UserAPIKeyLabelValues): + if ( + enum_values.client_ip == "127.0.0.1" + and enum_values.user_agent == "test-agent" + ): + found = True + break + + assert ( + found + ), "UserAPIKeyLabelValues should contain client_ip='127.0.0.1' and user_agent='test-agent'" + + +@pytest.mark.asyncio +async def test_async_post_call_success_hook_includes_client_ip_user_agent(): + """ + Test that async_post_call_success_hook includes client_ip and user_agent in UserAPIKeyLabelValues + """ + # Mocking + # Mocking + with patch( + "litellm.integrations.prometheus.PrometheusLogger.__init__", return_value=None + ): + logger = PrometheusLogger() + logger.litellm_proxy_total_requests_metric = MagicMock() + logger.get_labels_for_metric = MagicMock( + return_value=["client_ip", "user_agent"] + ) + + data = { + "model": "gpt-4", + "metadata": { + "requester_ip_address": "192.168.1.1", + "user_agent": "success-agent", + }, + } + user_api_key_dict = UserAPIKeyAuth(token="test_token") + response = MagicMock() + + # Mock prometheus_label_factory to inspect arguments + with patch( + "litellm.integrations.prometheus.prometheus_label_factory" + ) as mock_label_factory: + mock_label_factory.return_value = {} + + await logger.async_post_call_success_hook( + data=data, + user_api_key_dict=user_api_key_dict, + response=response, + ) + + # Verification + assert mock_label_factory.call_count >= 1 + + # Check calls + calls = mock_label_factory.call_args_list + found = False + for call in calls: + kwargs = call.kwargs + enum_values = kwargs.get("enum_values") + if isinstance(enum_values, UserAPIKeyLabelValues): + if ( + enum_values.client_ip == "192.168.1.1" + and enum_values.user_agent == "success-agent" + ): + found = True + break + + assert ( + found + ), "UserAPIKeyLabelValues should contain client_ip='192.168.1.1' and user_agent='success-agent'" + + +def test_set_llm_deployment_failure_metrics_includes_client_ip_user_agent(): + """ + Test that set_llm_deployment_failure_metrics includes client_ip and user_agent in UserAPIKeyLabelValues + """ + # Mocking + # Mocking + with patch( + "litellm.integrations.prometheus.PrometheusLogger.__init__", return_value=None + ): + logger = PrometheusLogger() + logger.litellm_deployment_failure_responses = MagicMock() + logger.litellm_deployment_total_requests = MagicMock() + logger.get_labels_for_metric = MagicMock( + return_value=["client_ip", "user_agent"] + ) + logger.set_deployment_partial_outage = MagicMock() + + request_kwargs = { + "model": "gpt-4", + "standard_logging_object": { + "metadata": { + "requester_ip_address": "10.0.0.1", + "user_agent": "failure-deployment", + "user_api_key_team_id": "team_1", + "user_api_key_team_alias": "team_alias_1", + "user_api_key_alias": "key_alias_1", + }, + "model_group": "group_1", + "api_base": "http://api.base", + "model_id": "model_1", + }, + "litellm_params": {}, + "exception": Exception("Deployment failure"), + } + + # Mock prometheus_label_factory to inspect arguments + with patch( + "litellm.integrations.prometheus.prometheus_label_factory" + ) as mock_label_factory: + mock_label_factory.return_value = {} + + logger.set_llm_deployment_failure_metrics(request_kwargs=request_kwargs) + + # Verification + assert mock_label_factory.call_count >= 1 + + # Check calls + calls = mock_label_factory.call_args_list + found = False + for call in calls: + kwargs = call.kwargs + enum_values = kwargs.get("enum_values") + if isinstance(enum_values, UserAPIKeyLabelValues): + if ( + enum_values.client_ip == "10.0.0.1" + and enum_values.user_agent == "failure-deployment" + ): + found = True + break + + assert ( + found + ), "UserAPIKeyLabelValues should contain client_ip='10.0.0.1' and user_agent='failure-deployment'" + + +if __name__ == "__main__": + import asyncio + + asyncio.run(test_async_post_call_failure_hook_includes_client_ip_user_agent()) + asyncio.run(test_async_post_call_success_hook_includes_client_ip_user_agent()) + test_set_llm_deployment_failure_metrics_includes_client_ip_user_agent() + print("✅ All client_ip and user_agent tests passed!") diff --git a/tests/test_litellm/integrations/test_prometheus_labels.py b/tests/test_litellm/integrations/test_prometheus_labels.py index c0b863ef6ee..9d4626b88a5 100644 --- a/tests/test_litellm/integrations/test_prometheus_labels.py +++ b/tests/test_litellm/integrations/test_prometheus_labels.py @@ -26,15 +26,49 @@ def test_user_email_in_required_metrics(): "litellm_input_tokens_metric", "litellm_output_tokens_metric", "litellm_requests_metric", - "litellm_spend_metric" + "litellm_spend_metric", ] for metric_name in metrics_with_user_email: labels = PrometheusMetricLabels.get_labels(metric_name) - assert user_email_label in labels, f"Metric {metric_name} should contain user_email label" + assert ( + user_email_label in labels + ), f"Metric {metric_name} should contain user_email label" print(f"✅ {metric_name} contains user_email label") +def test_model_id_in_required_metrics(): + """ + Test that model_id label is present in all the metrics that should have it + """ + model_id_label = UserAPIKeyLabelNames.MODEL_ID.value + + # Metrics that should have model_id + metrics_with_model_id = [ + "litellm_proxy_total_requests_metric", + "litellm_proxy_failed_requests_metric", + "litellm_input_tokens_metric", + "litellm_output_tokens_metric", + "litellm_requests_metric", + "litellm_spend_metric", + "litellm_llm_api_latency_metric", + "litellm_remaining_requests_metric", + "litellm_deployment_successful_fallbacks", + "litellm_cache_hits_metric", + "litellm_cache_misses_metric", + "litellm_remaining_api_key_requests_for_model", + "litellm_remaining_api_key_tokens_for_model", + "litellm_llm_api_failed_requests_metric", + ] + + for metric_name in metrics_with_model_id: + labels = PrometheusMetricLabels.get_labels(metric_name) + assert ( + model_id_label in labels + ), f"Metric {metric_name} should contain model_id label" + print(f"✅ {metric_name} contains model_id label") + + def test_user_email_label_exists(): """Test that the USER_EMAIL label is properly defined""" assert UserAPIKeyLabelNames.USER_EMAIL.value == "user_email" @@ -52,12 +86,14 @@ def test_prometheus_metric_labels_structure(): "litellm_proxy_failed_requests_metric", "litellm_input_tokens_metric", "litellm_output_tokens_metric", - "litellm_spend_metric" + "litellm_spend_metric", ] for metric_name in test_metrics: # Check metric is in DEFINED_PROMETHEUS_METRICS - assert metric_name in get_args(DEFINED_PROMETHEUS_METRICS), f"{metric_name} should be in DEFINED_PROMETHEUS_METRICS" + assert metric_name in get_args( + DEFINED_PROMETHEUS_METRICS + ), f"{metric_name} should be in DEFINED_PROMETHEUS_METRICS" # Check labels can be retrieved labels = PrometheusMetricLabels.get_labels(metric_name) @@ -70,15 +106,39 @@ def test_prometheus_metric_labels_structure(): print(f"✅ {metric_name} has proper label structure with user_email") +def test_model_id_in_required_metrics(): + """ + Test that model_id label is present in all the metrics that should have it: + - litellm_proxy_total_requests_metric + - litellm_proxy_failed_requests_metric + - litellm_request_total_latency_metric + - litellm_llm_api_time_to_first_token_metric + """ + model_id_label = UserAPIKeyLabelNames.MODEL_ID.value + + # Metrics that should have model_id + metrics_with_model_id = [ + "litellm_proxy_total_requests_metric", + "litellm_proxy_failed_requests_metric", + "litellm_request_total_latency_metric", + "litellm_llm_api_time_to_first_token_metric" + ] + + for metric_name in metrics_with_model_id: + labels = PrometheusMetricLabels.get_labels(metric_name) + assert model_id_label in labels, f"Metric {metric_name} should contain model_id label" + print(f"✅ {metric_name} contains model_id label") + + def test_route_normalization_for_responses_api(): """ Test that route normalization prevents high cardinality in Prometheus metrics for the /v1/responses/{response_id} endpoint. - + Issue: https://github.com/BerriAI/litellm/issues/XXXX Each unique response ID was creating a separate metric line, causing the /metrics endpoint to grow to ~30MB and take ~40 seconds to respond. - + Fix: Routes are normalized to collapse dynamic IDs into placeholders. """ from litellm.proxy.auth.auth_utils import normalize_request_route @@ -91,43 +151,53 @@ def test_route_normalization_for_responses_api(): ("/v1/responses/resp_abc123", "/v1/responses/{response_id}"), ("/v1/responses/litellm_poll_xyz", "/v1/responses/{response_id}"), ] - + for original, expected in responses_routes: normalized = normalize_request_route(original) - assert normalized == expected, \ - f"Failed: {original} -> {normalized} (expected {expected})" - + assert ( + normalized == expected + ), f"Failed: {original} -> {normalized} (expected {expected})" + # Verify cardinality reduction - unique_normalized = set(normalize_request_route(route) for route, _ in responses_routes) - assert len(unique_normalized) == 1, \ - f"Expected 1 unique normalized route, got {len(unique_normalized)}: {unique_normalized}" - - print(f"✅ Responses API routes: {len(responses_routes)} different IDs normalized to 1 metric label") - + unique_normalized = set( + normalize_request_route(route) for route, _ in responses_routes + ) + assert ( + len(unique_normalized) == 1 + ), f"Expected 1 unique normalized route, got {len(unique_normalized)}: {unique_normalized}" + + print( + f"✅ Responses API routes: {len(responses_routes)} different IDs normalized to 1 metric label" + ) + def test_route_normalization_for_sub_routes(): """Test that sub-routes like /cancel and /input_items are normalized correctly""" from litellm.proxy.auth.auth_utils import normalize_request_route - + sub_routes = [ ("/v1/responses/id1/cancel", "/v1/responses/{response_id}/cancel"), ("/v1/responses/id2/cancel", "/v1/responses/{response_id}/cancel"), ("/v1/responses/id3/input_items", "/v1/responses/{response_id}/input_items"), - ("/openai/v1/responses/id4/input_items", "/openai/v1/responses/{response_id}/input_items"), + ( + "/openai/v1/responses/id4/input_items", + "/openai/v1/responses/{response_id}/input_items", + ), ] - + for original, expected in sub_routes: normalized = normalize_request_route(original) - assert normalized == expected, \ - f"Failed: {original} -> {normalized} (expected {expected})" - + assert ( + normalized == expected + ), f"Failed: {original} -> {normalized} (expected {expected})" + print("✅ Sub-routes normalized correctly") def test_route_normalization_preserves_static_routes(): """Test that static routes are not affected by normalization""" from litellm.proxy.auth.auth_utils import normalize_request_route - + static_routes = [ "/chat/completions", "/v1/chat/completions", @@ -137,46 +207,47 @@ def test_route_normalization_preserves_static_routes(): "/v1/models", "/v1/responses", # List endpoint without ID ] - + for route in static_routes: normalized = normalize_request_route(route) - assert normalized == route, \ - f"Static route should not be modified: {route} -> {normalized}" - + assert ( + normalized == route + ), f"Static route should not be modified: {route} -> {normalized}" + print(f"✅ {len(static_routes)} static routes preserved") def test_route_normalization_other_dynamic_apis(): """Test normalization for other OpenAI-compatible APIs with dynamic IDs""" from litellm.proxy.auth.auth_utils import normalize_request_route - + test_cases = [ # Threads API ("/v1/threads/thread_123", "/v1/threads/{thread_id}"), ("/v1/threads/thread_abc/messages", "/v1/threads/{thread_id}/messages"), - ("/v1/threads/thread_abc/runs/run_123", "/v1/threads/{thread_id}/runs/{run_id}"), - + ( + "/v1/threads/thread_abc/runs/run_123", + "/v1/threads/{thread_id}/runs/{run_id}", + ), # Vector Stores API ("/v1/vector_stores/vs_123", "/v1/vector_stores/{vector_store_id}"), ("/v1/vector_stores/vs_123/files", "/v1/vector_stores/{vector_store_id}/files"), - # Assistants API ("/v1/assistants/asst_123", "/v1/assistants/{assistant_id}"), - # Files API ("/v1/files/file_123", "/v1/files/{file_id}"), ("/v1/files/file_123/content", "/v1/files/{file_id}/content"), - # Batches API ("/v1/batches/batch_123", "/v1/batches/{batch_id}"), ("/v1/batches/batch_123/cancel", "/v1/batches/{batch_id}/cancel"), ] - + for original, expected in test_cases: normalized = normalize_request_route(original) - assert normalized == expected, \ - f"Failed: {original} -> {normalized} (expected {expected})" - + assert ( + normalized == expected + ), f"Failed: {original} -> {normalized} (expected {expected})" + print(f"✅ {len(test_cases)} other API routes normalized correctly") @@ -195,26 +266,29 @@ def test_prometheus_metrics_use_normalized_routes(): # Create a mock PrometheusLogger prometheus_logger = MagicMock() - prometheus_logger.get_labels_for_metric = PrometheusLogger.get_labels_for_metric.__get__(prometheus_logger) - + prometheus_logger.get_labels_for_metric = ( + PrometheusLogger.get_labels_for_metric.__get__(prometheus_logger) + ) + # Test with a normalized route enum_values = UserAPIKeyLabelValues( route="/v1/responses/{response_id}", # Normalized route status_code="200", requested_model="gpt-4", ) - + labels = prometheus_label_factory( supported_enum_labels=prometheus_logger.get_labels_for_metric( metric_name="litellm_proxy_total_requests_metric" ), enum_values=enum_values, ) - + # Verify the route is normalized in labels - assert labels["route"] == "/v1/responses/{response_id}", \ - f"Expected normalized route in labels, got: {labels.get('route')}" - + assert ( + labels["route"] == "/v1/responses/{response_id}" + ), f"Expected normalized route in labels, got: {labels.get('route')}" + print("✅ Prometheus metrics use normalized routes in labels") @@ -227,4 +301,4 @@ if __name__ == "__main__": test_route_normalization_preserves_static_routes() test_route_normalization_other_dynamic_apis() test_prometheus_metrics_use_normalized_routes() - print("\n✅ All prometheus label tests passed!") \ No newline at end of file + print("\n✅ All prometheus label tests passed!") diff --git a/tests/test_litellm/integrations/test_prometheus_missing_metrics.py b/tests/test_litellm/integrations/test_prometheus_missing_metrics.py new file mode 100644 index 00000000000..7fcfb21ed4c --- /dev/null +++ b/tests/test_litellm/integrations/test_prometheus_missing_metrics.py @@ -0,0 +1,77 @@ +""" +Unit tests for the new Prometheus metrics that were previously missing from validation. + +Tests for: +- litellm_remaining_api_key_requests_for_model +- litellm_remaining_api_key_tokens_for_model +- litellm_callback_logging_failures_metric +""" +from typing import get_args +from litellm.types.integrations.prometheus import ( + DEFINED_PROMETHEUS_METRICS, + PrometheusMetricLabels, + UserAPIKeyLabelNames, +) + + +def test_new_metrics_in_defined_metrics(): + """ + Test that the new metrics are present in DEFINED_PROMETHEUS_METRICS. + """ + defined_metrics = get_args(DEFINED_PROMETHEUS_METRICS) + + new_metrics = [ + "litellm_remaining_api_key_requests_for_model", + "litellm_remaining_api_key_tokens_for_model", + "litellm_callback_logging_failures_metric", + ] + + for metric in new_metrics: + assert ( + metric in defined_metrics + ), f"{metric} should be in DEFINED_PROMETHEUS_METRICS" + + +def test_new_metrics_have_correct_labels(): + """ + Test that the new metrics have the correct labels defined. + """ + # Test API Key limits metrics labels + api_key_metrics = [ + "litellm_remaining_api_key_requests_for_model", + "litellm_remaining_api_key_tokens_for_model", + ] + + expected_api_key_labels = [ + UserAPIKeyLabelNames.API_KEY_HASH.value, + UserAPIKeyLabelNames.API_KEY_ALIAS.value, + UserAPIKeyLabelNames.v1_LITELLM_MODEL_NAME.value, + ] + + for metric in api_key_metrics: + labels = PrometheusMetricLabels.get_labels(metric) + for expected_label in expected_api_key_labels: + assert ( + expected_label in labels + ), f"{metric} should have label {expected_label}" + + # Test Callback failure metric labels + callback_metric = "litellm_callback_logging_failures_metric" + callback_labels = PrometheusMetricLabels.get_labels(callback_metric) + + assert ( + UserAPIKeyLabelNames.CALLBACK_NAME.value in callback_labels + ), f"{callback_metric} should have label {UserAPIKeyLabelNames.CALLBACK_NAME.value}" + + +def test_callback_name_label_definition(): + """ + Test that CALLBACK_NAME is defined correctly in UserAPIKeyLabelNames. + """ + assert UserAPIKeyLabelNames.CALLBACK_NAME.value == "callback_name" + + +if __name__ == "__main__": + test_new_metrics_in_defined_metrics() + test_new_metrics_have_correct_labels() + test_callback_name_label_definition() diff --git a/tests/test_litellm/integrations/test_prometheus_services.py b/tests/test_litellm/integrations/test_prometheus_services.py index b627d31fda0..ff80d7d9f8b 100644 --- a/tests/test_litellm/integrations/test_prometheus_services.py +++ b/tests/test_litellm/integrations/test_prometheus_services.py @@ -1,6 +1,7 @@ import json import os import sys +import time from unittest.mock import AsyncMock, patch import pytest @@ -17,6 +18,63 @@ sys.path.insert( ) # Adds the parent directory to the system path +def test_is_metric_registered_does_not_use_registry_collect(): + """is_metric_registered() must use _names_to_collectors, not REGISTRY.collect() (perf; #19921).""" + from prometheus_client import CollectorRegistry, Counter, Histogram + + registry = CollectorRegistry() + for i in range(80): + Counter( + f"litellm_service_{i}_total_requests", + "Total requests", + labelnames=["service"], + registry=registry, + ) + Histogram( + f"litellm_service_{i}_latency", + "Latency", + labelnames=["service"], + registry=registry, + ) + + pl = PrometheusServicesLogger() + pl.REGISTRY = registry + + original_collect = registry.collect + collect_called = [] + + def track_collect(*args, **kwargs): + collect_called.append(1) + return original_collect(*args, **kwargs) + + registry.collect = track_collect + + n_calls = 30 * 2 + start = time.perf_counter() + for _ in range(30): + pl.is_metric_registered("litellm_service_0_latency") + pl.is_metric_registered("litellm_service_79_total_requests") + elapsed_s = time.perf_counter() - start + elapsed_ms = elapsed_s * 1000 + per_call_us = (elapsed_s / n_calls) * 1_000_000 if n_calls else 0 + n_collect = len(collect_called) + + path = "slow (REGISTRY.collect)" if n_collect else "fast (_names_to_collectors)" + print( + f"\n is_metric_registered: {elapsed_ms:.2f} ms total | " + f"{per_call_us:.1f} µs/call | {n_calls} calls | {n_collect} collect() | {path}\n" + ) + + assert n_collect == 0, ( + f"is_metric_registered() must not use REGISTRY.collect() when _names_to_collectors " + f"is available. Latency: {elapsed_ms:.2f} ms, {per_call_us:.1f} µs/call, {n_calls} calls, " + f"collect() called {n_collect} times." + ) + assert elapsed_s < 0.05, ( + f"is_metric_registered() took {elapsed_ms:.2f} ms for {n_calls} calls; expected <50 ms." + ) + + def test_create_gauge_new(): """Test creating a new gauge""" pl = PrometheusServicesLogger() diff --git a/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py b/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py index 5ba78d9eed1..9e70f3e08d5 100644 --- a/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py +++ b/tests/test_litellm/litellm_core_utils/llm_cost_calc/test_llm_cost_calc_utils.py @@ -313,10 +313,12 @@ def test_string_cost_values(): } # Test usage with various token types + # Note: prompt_tokens must equal sum of details to avoid double-counting adjustment + # text_tokens(700) + audio_tokens(100) + cached_tokens(200) + cache_creation_tokens(150) = 1150 usage = Usage( - prompt_tokens=1000, + prompt_tokens=1150, completion_tokens=500, - total_tokens=1500, + total_tokens=1650, prompt_tokens_details=PromptTokensDetailsWrapper( audio_tokens=100, cached_tokens=200, text_tokens=700, image_tokens=None, cache_creation_tokens=150 ), diff --git a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py index a22fe13798f..e87233a52a3 100644 --- a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py +++ b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py @@ -1138,6 +1138,73 @@ def test_bedrock_create_bedrock_block_different_document_formats(): assert block["document"]["name"].endswith(f"_{format_type}") assert block["document"]["format"] == format_type +def test_bedrock_nova_web_search_options_mapping(): + """ + Test that web_search_options is correctly mapped to Nova grounding. + + This follows the LiteLLM pattern for web search where: + - Vertex AI maps web_search_options to {"googleSearch": {}} + - Anthropic maps web_search_options to {"type": "web_search_20250305", ...} + - Nova should map web_search_options to {"systemTool": {"name": "nova_grounding"}} + """ + from litellm.llms.bedrock.chat.converse_transformation import AmazonConverseConfig + + config = AmazonConverseConfig() + + # Test basic mapping for Nova model + result = config._map_web_search_options({}, "amazon.nova-pro-v1:0") + + assert result is not None + system_tool = result.get("systemTool") + assert system_tool is not None + assert system_tool["name"] == "nova_grounding" + + # Test with search_context_size (should be ignored for Nova) + result2 = config._map_web_search_options( + {"search_context_size": "high"}, + "us.amazon.nova-premier-v1:0" + ) + + assert result2 is not None + system_tool2 = result2.get("systemTool") + assert system_tool2 is not None + assert system_tool2["name"] == "nova_grounding" + # Nova doesn't support search_context_size, so it's just ignored + +def test_bedrock_tools_pt_does_not_handle_system_tool(): + """ + Verify that _bedrock_tools_pt does NOT handle system_tool format. + + System tools (nova_grounding) should be added via web_search_options, + not via the tools parameter directly. + """ + + from litellm.litellm_core_utils.prompt_templates.factory import _bedrock_tools_pt + + # Regular function tools should still work + tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the current weather", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string"} + }, + "required": ["location"] + } + } + } + ] + + result = _bedrock_tools_pt(tools=tools) + + assert len(result) == 1 + tool_spec = result[0].get("toolSpec") + assert tool_spec is not None + assert tool_spec["name"] == "get_weather" def test_convert_to_anthropic_tool_result_image_with_cache_control(): """ @@ -1305,12 +1372,12 @@ def test_convert_to_anthropic_tool_result_image_url_as_http(): assert result["content"][0]["cache_control"]["type"] == "ephemeral" def test_anthropic_messages_pt_server_tool_use_passthrough(): """ - Test that anthropic_messages_pt passes through server_tool_use and + Test that anthropic_messages_pt passes through server_tool_use and tool_search_tool_result blocks in assistant message content. - + These are Anthropic-native content types used for tool search functionality that need to be preserved when reconstructing multi-turn conversations. - + Fixes: https://github.com/BerriAI/litellm/issues/XXXXX """ from litellm.litellm_core_utils.prompt_templates.factory import anthropic_messages_pt @@ -1359,15 +1426,15 @@ def test_anthropic_messages_pt_server_tool_use_passthrough(): # Verify we have 3 messages (user, assistant, user) assert len(result) == 3 - + # Verify the assistant message content assistant_msg = result[1] assert assistant_msg["role"] == "assistant" assert isinstance(assistant_msg["content"], list) - + # Find the different content block types content_types = [block.get("type") for block in assistant_msg["content"]] - + # Verify server_tool_use block is preserved assert "server_tool_use" in content_types server_tool_use_block = next( @@ -1376,7 +1443,7 @@ def test_anthropic_messages_pt_server_tool_use_passthrough(): assert server_tool_use_block["id"] == "srvtoolu_01ABC123" assert server_tool_use_block["name"] == "tool_search_tool_regex" assert server_tool_use_block["input"] == {"query": ".*time.*"} - + # Verify tool_search_tool_result block is preserved assert "tool_search_tool_result" in content_types tool_result_block = next( @@ -1385,7 +1452,7 @@ def test_anthropic_messages_pt_server_tool_use_passthrough(): assert tool_result_block["tool_use_id"] == "srvtoolu_01ABC123" assert tool_result_block["content"]["type"] == "tool_search_tool_search_result" assert tool_result_block["content"]["tool_references"][0]["tool_name"] == "get_time" - + # Verify text block is also preserved assert "text" in content_types text_block = next( diff --git a/tests/test_litellm/litellm_core_utils/test_image_handling.py b/tests/test_litellm/litellm_core_utils/test_image_handling.py index b15d75a4145..9c2939b2da5 100644 --- a/tests/test_litellm/litellm_core_utils/test_image_handling.py +++ b/tests/test_litellm/litellm_core_utils/test_image_handling.py @@ -66,6 +66,41 @@ class LargeImageClient: ) +class StreamingLargeImageClient: + """ + Client that streams a large image to test streaming download protection. + This simulates a huge file without actually creating it all in memory. + """ + + def __init__(self, size_mb=100, include_content_length=False): + self.size_mb = size_mb + self.include_content_length = include_content_length + + def get(self, url, follow_redirects=True): + size_bytes = int(self.size_mb * 1024 * 1024) + headers = {"Content-Type": "image/jpeg"} + if self.include_content_length: + headers["Content-Length"] = str(size_bytes) + + # Create a generator that yields chunks without creating the whole file in memory + def generate_chunks(total_size, chunk_size=8192): + bytes_sent = 0 + while bytes_sent < total_size: + chunk = b"x" * min(chunk_size, total_size - bytes_sent) + bytes_sent += len(chunk) + yield chunk + + # Create response with streaming content + response = Response( + status_code=200, + headers=headers, + request=Request("GET", url), + ) + # Mock the iter_bytes method to return our generator + response.iter_bytes = lambda chunk_size=8192: generate_chunks(size_bytes, chunk_size) + return response + + def test_image_exceeds_size_limit_with_content_length(monkeypatch): """ Test that images exceeding MAX_IMAGE_URL_DOWNLOAD_SIZE_MB are rejected when Content-Length header is present. @@ -83,6 +118,7 @@ def test_image_exceeds_size_limit_with_content_length(monkeypatch): def test_image_exceeds_size_limit_without_content_length(monkeypatch): """ Test that images exceeding MAX_IMAGE_URL_DOWNLOAD_SIZE_MB are rejected even without Content-Length header. + This uses the old non-streaming mock for backward compatibility. """ monkeypatch.setattr( litellm, "module_level_client", LargeImageClient(size_mb=100, include_content_length=False) @@ -94,6 +130,29 @@ def test_image_exceeds_size_limit_without_content_length(monkeypatch): assert "exceeds maximum allowed size" in str(excinfo.value) +def test_streaming_download_protects_against_huge_files(monkeypatch): + """ + Test that streaming download aborts early when file exceeds size limit, + preventing memory exhaustion from huge files (e.g., petabyte-sized files). + + This test verifies that the streaming implementation doesn't download the entire + file into memory before checking size. Instead, it should abort as soon as the + limit is exceeded during streaming. + """ + # Simulate a 1GB file - far larger than the 50MB default limit + client = StreamingLargeImageClient(size_mb=1024, include_content_length=False) + monkeypatch.setattr(litellm, "module_level_client", client) + + with pytest.raises(litellm.ImageFetchError) as excinfo: + convert_url_to_base64("https://example.com/huge-image.jpg") + + # Verify the error message shows it was caught during streaming + assert "exceeds maximum allowed size" in str(excinfo.value) + + # The error should be raised after downloading just slightly more than the limit + # not after downloading the full 1GB + + class SmallImageClient: """ Client that returns a small valid image. @@ -124,6 +183,26 @@ def test_image_within_size_limit(monkeypatch): assert result.startswith("data:image/jpeg;base64,") +def test_streaming_download_handles_petabyte_file(monkeypatch): + """ + Test that streaming download can handle extremely large file URLs (e.g., petabyte-sized) + without attempting to download the entire file or causing memory exhaustion. + + This simulates what happens if a malicious actor or misconfiguration provides + a URL to an extremely large file. + """ + # Simulate a 1 petabyte file (1,000,000 GB) + # Without streaming protection, this would cause OOM or hang indefinitely + client = StreamingLargeImageClient(size_mb=1_000_000_000, include_content_length=False) + monkeypatch.setattr(litellm, "module_level_client", client) + + with pytest.raises(litellm.ImageFetchError) as excinfo: + convert_url_to_base64("https://example.com/petabyte-file.jpg") + + # Should fail fast without downloading anywhere near 1 petabyte + assert "exceeds maximum allowed size" in str(excinfo.value) + + def test_image_size_limit_disabled(monkeypatch): """ Test that setting MAX_IMAGE_URL_DOWNLOAD_SIZE_MB to 0 disables all image URL downloads. diff --git a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py index e035e193fe1..734d52918ba 100644 --- a/tests/test_litellm/litellm_core_utils/test_litellm_logging.py +++ b/tests/test_litellm/litellm_core_utils/test_litellm_logging.py @@ -787,11 +787,13 @@ def test_get_masked_values(): "presidio_ad_hoc_recognizers": None, "aws_bedrock_runtime_endpoint": None, "presidio_anonymizer_api_base": None, + "vertex_credentials": "{sensitive_api_key}", } masked_values = _get_masked_values( sensitive_object, unmasked_length=4, number_of_asterisks=4 ) assert masked_values["presidio_anonymizer_api_base"] is None + assert masked_values["vertex_credentials"] == "{s****y}" @pytest.mark.asyncio @@ -1058,3 +1060,298 @@ def test_append_system_prompt_messages(): kwargs=None, messages=messages ) assert result == messages + + +@pytest.mark.asyncio +async def test_async_success_handler_sets_standard_logging_object_for_pass_through_endpoints(): + """ + Test that async_success_handler sets standard_logging_object for pass-through endpoints + even when complete_streaming_response is None. + + This is a regression test for the bug where pass-through endpoints (like vLLM classify) + would not set standard_logging_object, causing model_max_budget_limiter to raise + ValueError("standard_logging_payload is required"). + + The fix adds an elif branch in async_success_handler to set standard_logging_object + for pass-through endpoints when complete_streaming_response is None. + """ + from datetime import datetime + from unittest.mock import patch + + from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj + from litellm.types.utils import StandardPassThroughResponseObject + + # Create a logging object for a pass-through endpoint + logging_obj = LiteLLMLoggingObj( + model="unknown", + messages=[{"role": "user", "content": "test"}], + stream=False, + call_type="pass_through_endpoint", + start_time=datetime.now(), + litellm_call_id="test-call-id", + function_id="test-function-id", + ) + + # Set up model_call_details with required fields + logging_obj.model_call_details = { + "litellm_params": { + "metadata": {}, + "proxy_server_request": {}, + }, + "litellm_call_id": "test-call-id", + } + + # Create a pass-through response object (not a ModelResponse) + result = StandardPassThroughResponseObject(response='{"status": "success"}') + + start_time = datetime.now() + end_time = datetime.now() + + # Mock the callbacks to avoid actual logging + with patch.object(logging_obj, "get_combined_callback_list", return_value=[]): + # Call async_success_handler + await logging_obj.async_success_handler( + result=result, + start_time=start_time, + end_time=end_time, + cache_hit=False, + ) + + # Verify that standard_logging_object was set + assert "standard_logging_object" in logging_obj.model_call_details, ( + "standard_logging_object should be set for pass-through endpoints " + "even when complete_streaming_response is None" + ) + assert logging_obj.model_call_details["standard_logging_object"] is not None, ( + "standard_logging_object should not be None for pass-through endpoints" + ) + + # Verify that async_complete_streaming_response was set to prevent re-processing + # This is consistent with the existing code pattern for regular streaming + assert "async_complete_streaming_response" in logging_obj.model_call_details, ( + "async_complete_streaming_response should be set to prevent re-processing, " + "consistent with the existing code pattern" + ) + assert logging_obj.model_call_details["async_complete_streaming_response"] is result, ( + "async_complete_streaming_response should be set to the result" + ) + + # Verify that response_cost is set to None (cost calculation not possible for pass-through) + # This is consistent with the error handling in the non-pass-through code path + assert "response_cost" in logging_obj.model_call_details, ( + "response_cost should be set for pass-through endpoints" + ) + assert logging_obj.model_call_details["response_cost"] is None, ( + "response_cost should be None for pass-through endpoints since " + "StandardPassThroughResponseObject doesn't have standard usage info" + ) + + +@pytest.mark.asyncio +async def test_async_success_handler_prevents_reprocessing_for_pass_through_endpoints(): + """ + Test that async_success_handler prevents re-processing for pass-through endpoints + by setting async_complete_streaming_response, consistent with the existing code pattern. + + This ensures that if async_success_handler is called multiple times (e.g., during + streaming), it won't re-process the response after the first complete call. + """ + from datetime import datetime + from unittest.mock import patch + + from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj + from litellm.types.utils import StandardPassThroughResponseObject + + # Create a logging object for a pass-through endpoint + logging_obj = LiteLLMLoggingObj( + model="unknown", + messages=[{"role": "user", "content": "test"}], + stream=False, + call_type="pass_through_endpoint", + start_time=datetime.now(), + litellm_call_id="test-call-id-reprocess", + function_id="test-function-id-reprocess", + ) + + # Set up model_call_details with required fields + logging_obj.model_call_details = { + "litellm_params": { + "metadata": {}, + "proxy_server_request": {}, + }, + "litellm_call_id": "test-call-id-reprocess", + } + + result = StandardPassThroughResponseObject(response='{"status": "success"}') + start_time = datetime.now() + end_time = datetime.now() + + # Mock the callbacks to avoid actual logging + with patch.object(logging_obj, "get_combined_callback_list", return_value=[]): + # First call - should process and set standard_logging_object + await logging_obj.async_success_handler( + result=result, + start_time=start_time, + end_time=end_time, + cache_hit=False, + ) + + # Verify first call set the values + assert "standard_logging_object" in logging_obj.model_call_details + assert "async_complete_streaming_response" in logging_obj.model_call_details + first_standard_logging_object = logging_obj.model_call_details["standard_logging_object"] + + # Second call - should return early due to async_complete_streaming_response guard + with patch.object(logging_obj, "get_combined_callback_list", return_value=[]) as mock_callbacks: + await logging_obj.async_success_handler( + result=result, + start_time=start_time, + end_time=end_time, + cache_hit=False, + ) + # The guard should cause early return, so get_combined_callback_list should not be called + mock_callbacks.assert_not_called() + + # Verify standard_logging_object wasn't modified by second call + assert logging_obj.model_call_details["standard_logging_object"] is first_standard_logging_object, ( + "standard_logging_object should not be modified on re-processing" + ) + + +@pytest.mark.asyncio +async def test_async_success_handler_sets_standard_logging_object_for_streaming_pass_through(): + """ + Test that async_success_handler sets standard_logging_object for streaming + pass-through endpoints when the response cannot be parsed into a ModelResponse. + + This covers the case where streaming pass-through endpoints for unknown providers + return a StandardPassThroughResponseObject instead of a ModelResponse. + """ + from datetime import datetime + from unittest.mock import patch + + from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj + from litellm.types.utils import StandardPassThroughResponseObject + + # Create a logging object for a streaming pass-through endpoint + logging_obj = LiteLLMLoggingObj( + model="unknown", + messages=[{"role": "user", "content": "test"}], + stream=True, # Streaming request + call_type="pass_through_endpoint", + start_time=datetime.now(), + litellm_call_id="test-call-id-streaming", + function_id="test-function-id-streaming", + ) + + # Set up model_call_details with required fields + logging_obj.model_call_details = { + "litellm_params": { + "metadata": {}, + "proxy_server_request": {}, + }, + "litellm_call_id": "test-call-id-streaming", + } + + # Create a pass-through response object (simulating unparseable streaming response) + result = StandardPassThroughResponseObject( + response='data: {"chunk": 1}\ndata: {"chunk": 2}\ndata: [DONE]' + ) + + start_time = datetime.now() + end_time = datetime.now() + + # Mock the callbacks to avoid actual logging + with patch.object(logging_obj, "get_combined_callback_list", return_value=[]): + # Call async_success_handler + await logging_obj.async_success_handler( + result=result, + start_time=start_time, + end_time=end_time, + cache_hit=False, + ) + + # Verify that standard_logging_object was set + assert "standard_logging_object" in logging_obj.model_call_details, ( + "standard_logging_object should be set for streaming pass-through endpoints " + "even when the response cannot be parsed into a ModelResponse" + ) + assert logging_obj.model_call_details["standard_logging_object"] is not None, ( + "standard_logging_object should not be None for streaming pass-through endpoints" + ) +def test_get_error_information_error_code_priority(): + """ + Test get_error_information prioritizes 'code' attribute over 'status_code' attribute + and handles edge cases like empty strings and "None" string values. + """ + from litellm.litellm_core_utils.litellm_logging import StandardLoggingPayloadSetup + + # Test case 1: Exception with 'code' attribute (ProxyException style) + class ProxyException(Exception): + def __init__(self, code, message): + self.code = code + self.message = message + super().__init__(message) + + proxy_exception = ProxyException(code="500", message="Internal Server Error") + result = StandardLoggingPayloadSetup.get_error_information(proxy_exception) + assert result["error_code"] == "500" + assert result["error_class"] == "ProxyException" + + # Test case 2: Exception with 'status_code' attribute (LiteLLM style) + class LiteLLMException(Exception): + def __init__(self, status_code, message): + self.status_code = status_code + self.message = message + super().__init__(message) + + litellm_exception = LiteLLMException(status_code=429, message="Rate limit exceeded") + result = StandardLoggingPayloadSetup.get_error_information(litellm_exception) + assert result["error_code"] == "429" + assert result["error_class"] == "LiteLLMException" + + # Test case 3: Exception with both 'code' and 'status_code' - should prefer 'code' + class BothAttributesException(Exception): + def __init__(self, code, status_code, message): + self.code = code + self.status_code = status_code + self.message = message + super().__init__(message) + + both_exception = BothAttributesException( + code="400", status_code=500, message="Bad Request" + ) + result = StandardLoggingPayloadSetup.get_error_information(both_exception) + assert result["error_code"] == "400" # Should prefer 'code' over 'status_code' + + # Test case 4: Exception with 'code' as empty string - should fall back to 'status_code' + empty_code_exception = BothAttributesException( + code="", status_code=404, message="Not Found" + ) + result = StandardLoggingPayloadSetup.get_error_information(empty_code_exception) + assert result["error_code"] == "404" # Should fall back to status_code + + # Test case 5: Exception with 'code' as "None" string - should fall back to 'status_code' + none_string_exception = BothAttributesException( + code="None", status_code=503, message="Service Unavailable" + ) + result = StandardLoggingPayloadSetup.get_error_information(none_string_exception) + assert result["error_code"] == "503" # Should fall back to status_code + + # Test case 6: Exception with 'code' as None - should fall back to 'status_code' + none_code_exception = BothAttributesException( + code=None, status_code=401, message="Unauthorized" + ) + result = StandardLoggingPayloadSetup.get_error_information(none_code_exception) + assert result["error_code"] == "401" # Should fall back to status_code + + # Test case 7: Exception with neither 'code' nor 'status_code' - should return empty string + class NoCodeException(Exception): + def __init__(self, message): + self.message = message + super().__init__(message) + + no_code_exception = NoCodeException(message="Generic error") + result = StandardLoggingPayloadSetup.get_error_information(no_code_exception) + assert result["error_code"] == "" + assert result["error_class"] == "NoCodeException" diff --git a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py index 7e96c4634fd..eee0b267fad 100644 --- a/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py +++ b/tests/test_litellm/llms/anthropic/chat/test_anthropic_chat_transformation.py @@ -548,6 +548,59 @@ def test_map_tool_choice_dict_type_function_with_name(): assert result["name"] == "my_tool" +def test_map_tool_choice_dict_type_auto(): + """ + Test that dict {"type": "auto"} maps to Anthropic type='auto'. + This handles Cursor's format for tool_choice. + """ + config = AnthropicConfig() + result = config._map_tool_choice( + tool_choice={"type": "auto"}, + parallel_tool_use=None, + ) + assert result is not None + assert result["type"] == "auto" + + +def test_map_tool_choice_dict_type_required(): + """ + Test that dict {"type": "required"} maps to Anthropic type='any'. + """ + config = AnthropicConfig() + result = config._map_tool_choice( + tool_choice={"type": "required"}, + parallel_tool_use=None, + ) + assert result is not None + assert result["type"] == "any" + + +def test_map_tool_choice_dict_type_none(): + """ + Test that dict {"type": "none"} maps to Anthropic type='none'. + """ + config = AnthropicConfig() + result = config._map_tool_choice( + tool_choice={"type": "none"}, + parallel_tool_use=None, + ) + assert result is not None + assert result["type"] == "none" + + +def test_map_tool_choice_dict_type_function_without_name(): + """ + Test that dict {"type": "function"} without name is handled gracefully. + Should return None since there's no valid tool name. + """ + config = AnthropicConfig() + result = config._map_tool_choice( + tool_choice={"type": "function"}, + parallel_tool_use=None, + ) + assert result is None + + def test_transform_response_with_prefix_prompt(): import httpx @@ -1842,7 +1895,7 @@ def test_calculate_usage_completion_tokens_details_always_populated(): # completion_tokens_details should NOT be None assert usage.completion_tokens_details is not None - assert usage.completion_tokens_details.reasoning_tokens is None + assert usage.completion_tokens_details.reasoning_tokens is 0 assert usage.completion_tokens_details.text_tokens == 248 assert usage.completion_tokens == 248 assert usage.prompt_tokens == 37 diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py index 6aadbc058d1..6a5c022ac7a 100644 --- a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py +++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py @@ -8,7 +8,10 @@ sys.path.insert(0, os.path.abspath("../../../../..")) from litellm.llms.anthropic.experimental_pass_through.adapters.transformation import ( + OPENAI_MAX_TOOL_NAME_LENGTH, LiteLLMAnthropicMessagesAdapter, + create_tool_name_mapping, + truncate_tool_name, ) from litellm.types.llms.anthropic import ( AnthopicMessagesAssistantMessageParam, @@ -1108,3 +1111,598 @@ def test_streaming_chunk_with_both_text_and_tool_calls_issue_18238(): assert block_type == "tool_use" assert content_block_start["name"] == "Bash" assert content_block_start["id"] == "toolu_bdrk_013xRVejhv3ybmLEGCoZib2b" + + +# ============================================================================ +# Cache Control Transformation Tests +# ============================================================================ + +# Model constant for cache control tests +CACHE_CONTROL_BEDROCK_CONVERSE_MODEL = "bedrock/converse/global.anthropic.claude-opus-4-5-20251101-v1:0" +CACHE_CONTROL_NON_ANTHROPIC_MODEL = "gpt-4" + + +def test_should_add_cache_control_for_anthropic_model(): + """Should add cache_control to target for Anthropic Claude models.""" + adapter = LiteLLMAnthropicMessagesAdapter() + cache_control = {"type": "ephemeral"} + + for model in [ + CACHE_CONTROL_BEDROCK_CONVERSE_MODEL, + "anthropic/claude-sonnet-4-5", + "claude-opus-4-5-20251101", + "vertex_ai/claude-3-sonnet@20240229", + ]: + target = {} + adapter._add_cache_control_if_applicable({"cache_control": cache_control}, target, model) + assert "cache_control" in target + assert target["cache_control"] == cache_control + + +def test_should_not_add_cache_control_for_non_anthropic_model(): + """Should not add cache_control for non-Anthropic models.""" + adapter = LiteLLMAnthropicMessagesAdapter() + cache_control = {"type": "ephemeral"} + + for model in [CACHE_CONTROL_NON_ANTHROPIC_MODEL, "openai/gpt-4-turbo", "gemini-pro"]: + target = {} + adapter._add_cache_control_if_applicable({"cache_control": cache_control}, target, model) + assert "cache_control" not in target + + +def test_should_not_add_cache_control_when_none(): + """Should not add cache_control when source has None or empty cache_control.""" + adapter = LiteLLMAnthropicMessagesAdapter() + + for source in [{"cache_control": None}, {"cache_control": {}}, {"cache_control": ""}, {}]: + target = {} + adapter._add_cache_control_if_applicable(source, target, CACHE_CONTROL_BEDROCK_CONVERSE_MODEL) + assert "cache_control" not in target + + +def test_should_not_add_cache_control_when_model_none(): + """Should not add cache_control when model is None or empty.""" + adapter = LiteLLMAnthropicMessagesAdapter() + cache_control = {"type": "ephemeral"} + + for model in [None, ""]: + target = {} + adapter._add_cache_control_if_applicable({"cache_control": cache_control}, target, model) + assert "cache_control" not in target + + +def test_cache_control_preserved_in_text_content_for_claude(): + """Cache control should be preserved in text content for Claude models.""" + anthropic_messages = [ + AnthropicMessagesUserMessageParam( + role="user", + content=[ + { + "type": "text", + "text": "This is cached content", + "cache_control": {"type": "ephemeral"}, + } + ], + ) + ] + + adapter = LiteLLMAnthropicMessagesAdapter() + result = adapter.translate_anthropic_messages_to_openai( + messages=anthropic_messages, model=CACHE_CONTROL_BEDROCK_CONVERSE_MODEL + ) + + assert len(result) == 1 + assert result[0]["content"][0]["cache_control"] == {"type": "ephemeral"} + + +def test_cache_control_not_preserved_for_non_claude_model(): + """Cache control should NOT be preserved for non-Claude models.""" + anthropic_messages = [ + AnthropicMessagesUserMessageParam( + role="user", + content=[ + { + "type": "text", + "text": "This is cached content", + "cache_control": {"type": "ephemeral"}, + } + ], + ) + ] + + adapter = LiteLLMAnthropicMessagesAdapter() + result = adapter.translate_anthropic_messages_to_openai( + messages=anthropic_messages, model=CACHE_CONTROL_NON_ANTHROPIC_MODEL + ) + + assert len(result) == 1 + assert "cache_control" not in result[0]["content"][0] + + +def test_cache_control_preserved_in_image_content_for_claude(): + """Cache control should be preserved in image content for Claude models.""" + anthropic_messages = [ + AnthropicMessagesUserMessageParam( + role="user", + content=[ + { + "type": "image", + "source": { + "type": "base64", + "media_type": "image/png", + "data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==", + }, + "cache_control": {"type": "ephemeral"}, + } + ], + ) + ] + + adapter = LiteLLMAnthropicMessagesAdapter() + result = adapter.translate_anthropic_messages_to_openai( + messages=anthropic_messages, model=CACHE_CONTROL_BEDROCK_CONVERSE_MODEL + ) + + assert len(result) == 1 + assert result[0]["content"][0]["cache_control"] == {"type": "ephemeral"} + + +def test_cache_control_preserved_in_document_content_for_claude(): + """Cache control should be preserved in document content for Claude models.""" + anthropic_messages = [ + AnthropicMessagesUserMessageParam( + role="user", + content=[ + { + "type": "document", + "source": { + "type": "base64", + "media_type": "application/pdf", + "data": "JVBERi0xLjQKJeLjz9MK", + }, + "cache_control": {"type": "ephemeral"}, + } + ], + ) + ] + + adapter = LiteLLMAnthropicMessagesAdapter() + result = adapter.translate_anthropic_messages_to_openai( + messages=anthropic_messages, model=CACHE_CONTROL_BEDROCK_CONVERSE_MODEL + ) + + assert len(result) == 1 + assert result[0]["content"][0]["cache_control"] == {"type": "ephemeral"} + + +def test_cache_control_preserved_in_tool_result_for_claude(): + """Cache control should be preserved in tool_result for Claude models.""" + anthropic_messages = [ + AnthropicMessagesUserMessageParam( + role="user", + content=[ + { + "type": "tool_result", + "tool_use_id": "toolu_01234", + "content": "Tool result content", + "cache_control": {"type": "ephemeral"}, + } + ], + ) + ] + + adapter = LiteLLMAnthropicMessagesAdapter() + result = adapter.translate_anthropic_messages_to_openai( + messages=anthropic_messages, model=CACHE_CONTROL_BEDROCK_CONVERSE_MODEL + ) + + tool_message = next(msg for msg in result if msg.get("role") == "tool") + assert tool_message["cache_control"] == {"type": "ephemeral"} + + +def test_cache_control_not_preserved_in_tool_result_for_non_claude(): + """Cache control should NOT be preserved in tool_result for non-Claude models.""" + anthropic_messages = [ + AnthropicMessagesUserMessageParam( + role="user", + content=[ + { + "type": "tool_result", + "tool_use_id": "toolu_01234", + "content": "Tool result content", + "cache_control": {"type": "ephemeral"}, + } + ], + ) + ] + + adapter = LiteLLMAnthropicMessagesAdapter() + result = adapter.translate_anthropic_messages_to_openai( + messages=anthropic_messages, model=CACHE_CONTROL_NON_ANTHROPIC_MODEL + ) + + tool_message = next(msg for msg in result if msg.get("role") == "tool") + assert "cache_control" not in tool_message + + +def test_cache_control_preserved_in_assistant_text_for_claude(): + """Cache control should be preserved in assistant text blocks for Claude models.""" + anthropic_messages = [ + AnthopicMessagesAssistantMessageParam( + role="assistant", + content=[ + { + "type": "text", + "text": "Assistant response", + "cache_control": {"type": "ephemeral"}, + } + ], + ) + ] + + adapter = LiteLLMAnthropicMessagesAdapter() + result = adapter.translate_anthropic_messages_to_openai( + messages=anthropic_messages, model=CACHE_CONTROL_BEDROCK_CONVERSE_MODEL + ) + + assert len(result) == 1 + assert result[0]["role"] == "assistant" + # When cache_control is present, content should be a list + assert isinstance(result[0]["content"], list) + assert result[0]["content"][0]["cache_control"] == {"type": "ephemeral"} + + +def test_cache_control_preserved_in_tool_use_for_claude(): + """Cache control should be preserved in tool_use blocks for Claude models.""" + anthropic_messages = [ + AnthopicMessagesAssistantMessageParam( + role="assistant", + content=[ + { + "type": "tool_use", + "id": "toolu_01234", + "name": "get_weather", + "input": {"location": "Boston"}, + "cache_control": {"type": "ephemeral"}, + } + ], + ) + ] + + adapter = LiteLLMAnthropicMessagesAdapter() + result = adapter.translate_anthropic_messages_to_openai( + messages=anthropic_messages, model=CACHE_CONTROL_BEDROCK_CONVERSE_MODEL + ) + + assert len(result) == 1 + assert "tool_calls" in result[0] + assert result[0]["tool_calls"][0]["cache_control"] == {"type": "ephemeral"} + + +def test_cache_control_preserved_in_tools_for_claude(): + """Cache control should be preserved in tools for Claude models.""" + tools = [ + { + "name": "get_weather", + "description": "Get weather for a location", + "input_schema": {"type": "object", "properties": {"location": {"type": "string"}}}, + "cache_control": {"type": "ephemeral"}, + } + ] + + adapter = LiteLLMAnthropicMessagesAdapter() + result, tool_name_mapping = adapter.translate_anthropic_tools_to_openai( + tools=tools, model=CACHE_CONTROL_BEDROCK_CONVERSE_MODEL + ) + + assert len(result) == 1 + assert result[0]["cache_control"] == {"type": "ephemeral"} + assert tool_name_mapping == {} # No truncation needed for short names + + +def test_cache_control_not_preserved_in_tools_for_non_claude(): + """Cache control should NOT be preserved in tools for non-Claude models.""" + tools = [ + { + "name": "get_weather", + "description": "Get weather for a location", + "input_schema": {"type": "object", "properties": {"location": {"type": "string"}}}, + "cache_control": {"type": "ephemeral"}, + } + ] + + adapter = LiteLLMAnthropicMessagesAdapter() + result, tool_name_mapping = adapter.translate_anthropic_tools_to_openai( + tools=tools, model=CACHE_CONTROL_NON_ANTHROPIC_MODEL + ) + + assert len(result) == 1 + assert "cache_control" not in result[0] + + +def test_translate_openai_content_to_anthropic_reasoning_content_without_thinking_blocks(): + """ + Test that reasoning_content is converted to thinking block when thinking_blocks is not present. + This handles providers like OpenRouter that return reasoning_content instead of thinking_blocks. + + Regression test for: OpenRouter models returning reasoning_content in /v1/messages endpoint + should be converted to Anthropic's thinking block format. + """ + openai_choices = [ + Choices( + message=Message( + role="assistant", + content="There are **3** \"r\"s in the word strawberry.", + reasoning_content="**Considering Letter Frequency**\n\nI've homed in on the specifics: The task focuses on counting the letter 'r'. I've identified the target word, \"strawberry,\" and confirmed my understanding of the letter's location. The first 'r' follows 't', the second after 'e', and the third… well, I'm almost there.\n\n\n**Calculating the Count**\n\nMy analysis is complete! I've confirmed that the letter \"r\" appears three times in \"strawberry.\" The first follows \"t,\" the second \"e,\" and the third immediately follows the second. The count is definitively three.", + ) + ) + ] + + adapter = LiteLLMAnthropicMessagesAdapter() + result = adapter._translate_openai_content_to_anthropic(choices=openai_choices) + + assert len(result) == 2 + # First block should be thinking block with reasoning_content + assert result[0].type == "thinking" + assert "Considering Letter Frequency" in result[0].thinking + assert "Calculating the Count" in result[0].thinking + assert result[0].signature is None + # Second block should be text block with content + assert result[1].type == "text" + assert result[1].text == "There are **3** \"r\"s in the word strawberry." + + +def test_translate_streaming_openai_chunk_to_anthropic_reasoning_content_without_thinking_blocks(): + """ + Test that reasoning_content in streaming chunks is converted to thinking_delta + when thinking_blocks is not present. + + This handles providers like OpenRouter that return reasoning_content in streaming + responses without thinking_blocks. + """ + choices = [ + StreamingChoices( + finish_reason=None, + index=0, + delta=Delta( + reasoning_content="I need to analyze this carefully...", + content="", + role="assistant", + function_call=None, + tool_calls=None, + audio=None, + ), + logprobs=None, + ) + ] + + ( + type_of_content, + content_block_delta, + ) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic( + choices=choices + ) + + assert type_of_content == "thinking_delta" + assert content_block_delta["type"] == "thinking_delta" + assert content_block_delta["thinking"] == "I need to analyze this carefully..." + + +def test_translate_openai_response_to_anthropic_with_reasoning_content_only(): + """ + Test the full response translation when only reasoning_content is present + (no thinking_blocks). + + This simulates OpenRouter's response format being translated to Anthropic format + through /v1/messages endpoint. + """ + openai_response = ModelResponse( + id="gen-1770027855-HyrqYvLcX8oTLNgfyDob", + model="gemini-3-flash", + choices=[ + Choices( + finish_reason="stop", + message=Message( + role="assistant", + content="There are **3** \"r\"s in the word strawberry.", + reasoning_content="**Considering Letter Frequency**\n\nI've homed in on the specifics: The task focuses on counting the letter 'r'.", + ), + ) + ], + usage=Usage(prompt_tokens=13, completion_tokens=138), + ) + + adapter = LiteLLMAnthropicMessagesAdapter() + anthropic_response = adapter.translate_openai_response_to_anthropic( + response=openai_response + ) + + anthropic_content = anthropic_response.get("content") + assert anthropic_content is not None + assert len(anthropic_content) == 2 + + # First block should be thinking + assert cast(Any, anthropic_content[0]).type == "thinking" + assert "Considering Letter Frequency" in cast(Any, anthropic_content[0]).thinking + assert cast(Any, anthropic_content[0]).signature is None + + # Second block should be text + assert cast(Any, anthropic_content[1]).type == "text" + assert cast(Any, anthropic_content[1]).text == "There are **3** \"r\"s in the word strawberry." + + assert anthropic_response.get("stop_reason") == "end_turn" + + +# ===================================================================== +# Tool Name Truncation Tests (Issue #17904) +# OpenAI has a 64-character limit for function/tool names +# ===================================================================== + + +def test_truncate_tool_name_short_name(): + """Short tool names should not be truncated.""" + short_name = "get_weather" + result = truncate_tool_name(short_name) + assert result == short_name + assert len(result) <= OPENAI_MAX_TOOL_NAME_LENGTH + + +def test_truncate_tool_name_exactly_64_chars(): + """Tool names exactly 64 chars should not be truncated.""" + name_64_chars = "a" * 64 + result = truncate_tool_name(name_64_chars) + assert result == name_64_chars + assert len(result) == 64 + + +def test_truncate_tool_name_long_name(): + """Long tool names should be truncated with hash suffix.""" + long_name = "computer_tool_with_very_long_name_that_exceeds_openai_64_character_limit_and_keeps_going" + result = truncate_tool_name(long_name) + + assert len(result) == OPENAI_MAX_TOOL_NAME_LENGTH + assert result != long_name + # Should have format: {55-char-prefix}_{8-char-hash} + assert "_" in result + parts = result.rsplit("_", 1) + assert len(parts[0]) == 55 + assert len(parts[1]) == 8 + + +def test_truncate_tool_name_deterministic(): + """Truncation should be deterministic (same input = same output).""" + long_name = "a_very_long_tool_name_that_needs_to_be_truncated_for_openai_compatibility_reasons" + result1 = truncate_tool_name(long_name) + result2 = truncate_tool_name(long_name) + assert result1 == result2 + + +def test_truncate_tool_name_avoids_collisions(): + """Similar long names should produce different truncated names.""" + name1 = "process_user_data_with_validation_and_error_handling_for_production_environment" + name2 = "process_user_data_with_validation_and_error_handling_for_staging_environment" + + result1 = truncate_tool_name(name1) + result2 = truncate_tool_name(name2) + + assert result1 != result2 # Different hashes prevent collision + + +def test_create_tool_name_mapping_no_long_names(): + """Mapping should be empty when no names need truncation.""" + tools = [ + {"name": "get_weather"}, + {"name": "search_web"}, + ] + mapping = create_tool_name_mapping(tools) + assert mapping == {} + + +def test_create_tool_name_mapping_with_long_names(): + """Mapping should contain entries for truncated names.""" + long_name = "a_very_long_tool_name_that_exceeds_the_64_character_limit_imposed_by_openai" + tools = [ + {"name": "short_name"}, + {"name": long_name}, + ] + mapping = create_tool_name_mapping(tools) + + assert len(mapping) == 1 + truncated = truncate_tool_name(long_name) + assert truncated in mapping + assert mapping[truncated] == long_name + + +def test_translate_anthropic_tools_with_long_names(): + """Tools with long names should be truncated and mapped.""" + long_name = "computer_tool_with_very_long_descriptive_name_that_exceeds_openai_limit_completely" + tools = [ + { + "name": long_name, + "description": "A tool with a very long name", + "input_schema": {"type": "object", "properties": {}}, + } + ] + + adapter = LiteLLMAnthropicMessagesAdapter() + result, tool_name_mapping = adapter.translate_anthropic_tools_to_openai( + tools=tools, model="gpt-4" + ) + + assert len(result) == 1 + # The tool name should be truncated + truncated_name = result[0]["function"]["name"] + assert len(truncated_name) <= 64 + assert truncated_name != long_name + # Mapping should have the reverse lookup + assert truncated_name in tool_name_mapping + assert tool_name_mapping[truncated_name] == long_name + + +def test_translate_anthropic_tools_mixed_names(): + """Mix of short and long names should work correctly.""" + short_name = "get_weather" + long_name = "process_complex_data_transformation_with_validation_and_error_handling_pipeline" + tools = [ + {"name": short_name, "input_schema": {"type": "object"}}, + {"name": long_name, "input_schema": {"type": "object"}}, + ] + + adapter = LiteLLMAnthropicMessagesAdapter() + result, tool_name_mapping = adapter.translate_anthropic_tools_to_openai( + tools=tools, model="gpt-4" + ) + + assert len(result) == 2 + # Short name unchanged + assert result[0]["function"]["name"] == short_name + # Long name truncated + assert result[1]["function"]["name"] != long_name + assert len(result[1]["function"]["name"]) <= 64 + # Only long name in mapping + assert len(tool_name_mapping) == 1 + + +def test_translate_openai_response_restores_tool_names(): + """Tool names in responses should be restored to original.""" + original_name = "a_very_long_tool_name_that_needs_truncation_for_openai_api_compatibility" + truncated_name = truncate_tool_name(original_name) + tool_name_mapping = {truncated_name: original_name} + + # Create a mock OpenAI response with the truncated name + response = ModelResponse( + id="test-id", + choices=[ + Choices( + index=0, + finish_reason="tool_calls", + message=Message( + role="assistant", + content=None, + tool_calls=[ + ChatCompletionAssistantToolCall( + id="call_123", + type="function", + function=Function( + name=truncated_name, + arguments='{"arg": "value"}', + ), + ) + ], + ), + ) + ], + model="gpt-4", + usage=Usage(prompt_tokens=10, completion_tokens=5, total_tokens=15), + ) + + adapter = LiteLLMAnthropicMessagesAdapter() + result = adapter.translate_openai_response_to_anthropic( + response=response, tool_name_mapping=tool_name_mapping + ) + + # Find the tool_use block in the response + tool_use_blocks = [c for c in result["content"] if getattr(c, "type", None) == "tool_use"] + assert len(tool_use_blocks) == 1 + # Name should be restored to original + assert getattr(tool_use_blocks[0], "name", None) == original_name diff --git a/tests/test_litellm/llms/azure/chat/test_azure_gpt5_transformation.py b/tests/test_litellm/llms/azure/chat/test_azure_gpt5_transformation.py index 199a16d8590..25f3d1364f6 100644 --- a/tests/test_litellm/llms/azure/chat/test_azure_gpt5_transformation.py +++ b/tests/test_litellm/llms/azure/chat/test_azure_gpt5_transformation.py @@ -16,6 +16,17 @@ def test_azure_gpt5_supports_reasoning_effort(config: AzureOpenAIGPT5Config): ) +def test_azure_gpt5_allows_tool_choice_for_deployment_names(): + supported_params = litellm.get_supported_openai_params( + model="gpt-5-chat-2025-08-07", custom_llm_provider="azure" + ) + assert supported_params is not None + assert "tool_choice" in supported_params + # gpt-5-chat* should not be treated as a GPT-5 reasoning model + assert "reasoning_effort" not in supported_params + assert "temperature" in supported_params + + def test_azure_gpt5_maps_max_tokens(config: AzureOpenAIGPT5Config): params = config.map_openai_params( non_default_params={"max_tokens": 5}, diff --git a/tests/test_litellm/llms/azure/test_azure_common_utils.py b/tests/test_litellm/llms/azure/test_azure_common_utils.py index 5a0680d66bf..b8c38fb9099 100644 --- a/tests/test_litellm/llms/azure/test_azure_common_utils.py +++ b/tests/test_litellm/llms/azure/test_azure_common_utils.py @@ -483,6 +483,7 @@ async def test_ensure_initialize_azure_sdk_client_always_used(call_type): "input_file_id": "123", }, "aretrieve_batch": {"batch_id": "123"}, + "acancel_batch": {"batch_id": "123"}, "aget_assistants": {"custom_llm_provider": "azure"}, "acreate_assistants": {"custom_llm_provider": "azure"}, "adelete_assistant": {"custom_llm_provider": "azure", "assistant_id": "123"}, @@ -537,7 +538,7 @@ async def test_ensure_initialize_azure_sdk_client_always_used(call_type): patch_target = ( "litellm.rerank_api.main.azure_rerank.initialize_azure_sdk_client" ) - elif call_type == CallTypes.acreate_batch or call_type == CallTypes.aretrieve_batch: + elif call_type == CallTypes.acreate_batch or call_type == CallTypes.aretrieve_batch or call_type == CallTypes.acancel_batch: patch_target = ( "litellm.batches.main.azure_batches_instance.initialize_azure_sdk_client" ) diff --git a/tests/test_litellm/llms/azure_ai/claude/test_azure_anthropic_count_tokens_transformation.py b/tests/test_litellm/llms/azure_ai/claude/test_azure_anthropic_count_tokens_transformation.py new file mode 100644 index 00000000000..78806831685 --- /dev/null +++ b/tests/test_litellm/llms/azure_ai/claude/test_azure_anthropic_count_tokens_transformation.py @@ -0,0 +1,111 @@ +""" +Tests for Azure AI Anthropic CountTokens transformation. + +Verifies that the CountTokens API uses the correct authentication headers. +""" +import os +import sys + +sys.path.insert( + 0, os.path.abspath("../../../../..") +) # Adds the parent directory to the system path + + +from litellm.llms.azure_ai.anthropic.count_tokens.transformation import ( + AzureAIAnthropicCountTokensConfig, +) + + +class TestAzureAIAnthropicCountTokensConfig: + """Test Azure AI Anthropic CountTokens configuration and headers.""" + + def test_get_required_headers_includes_x_api_key(self): + """ + Test that get_required_headers includes x-api-key header. + + Azure AI Anthropic uses Anthropic's native API format which requires + the x-api-key header for authentication (not just Azure's api-key). + """ + config = AzureAIAnthropicCountTokensConfig() + api_key = "test-api-key-12345" + + headers = config.get_required_headers(api_key=api_key) + + # Verify x-api-key header is set + assert "x-api-key" in headers + assert headers["x-api-key"] == api_key + + # Verify base headers are present + assert headers["Content-Type"] == "application/json" + assert headers["anthropic-version"] == "2023-06-01" + assert "anthropic-beta" in headers + + def test_get_required_headers_includes_azure_api_key(self): + """ + Test that get_required_headers includes Azure api-key header. + + Both x-api-key and api-key headers should be present. + """ + config = AzureAIAnthropicCountTokensConfig() + api_key = "test-azure-key-67890" + + headers = config.get_required_headers(api_key=api_key) + + # Verify both authentication headers are set + assert "x-api-key" in headers + assert "api-key" in headers + assert headers["x-api-key"] == api_key + assert headers["api-key"] == api_key + + def test_get_required_headers_with_litellm_params(self): + """ + Test that get_required_headers works with litellm_params. + """ + config = AzureAIAnthropicCountTokensConfig() + api_key = "test-key" + litellm_params = {"api_key": "param-key", "custom_field": "value"} + + headers = config.get_required_headers( + api_key=api_key, litellm_params=litellm_params + ) + + # x-api-key should use the direct api_key parameter + assert headers["x-api-key"] == api_key + # Azure api-key should come from litellm_params + assert headers["api-key"] == "param-key" + + def test_get_count_tokens_endpoint_with_base_url(self): + """Test endpoint generation from base URL.""" + config = AzureAIAnthropicCountTokensConfig() + + api_base = "https://my-resource.services.ai.azure.com" + endpoint = config.get_count_tokens_endpoint(api_base) + + assert ( + endpoint + == "https://my-resource.services.ai.azure.com/anthropic/v1/messages/count_tokens" + ) + + def test_get_count_tokens_endpoint_with_anthropic_path(self): + """Test endpoint generation when base URL already includes /anthropic.""" + config = AzureAIAnthropicCountTokensConfig() + + api_base = "https://my-resource.services.ai.azure.com/anthropic" + endpoint = config.get_count_tokens_endpoint(api_base) + + assert ( + endpoint + == "https://my-resource.services.ai.azure.com/anthropic/v1/messages/count_tokens" + ) + + def test_get_count_tokens_endpoint_with_trailing_slash(self): + """Test endpoint generation with trailing slash in base URL.""" + config = AzureAIAnthropicCountTokensConfig() + + api_base = "https://my-resource.services.ai.azure.com/" + endpoint = config.get_count_tokens_endpoint(api_base) + + assert ( + endpoint + == "https://my-resource.services.ai.azure.com/anthropic/v1/messages/count_tokens" + ) diff --git a/tests/test_litellm/llms/azure_ai/rerank/test_azure_ai_rerank_transformation.py b/tests/test_litellm/llms/azure_ai/rerank/test_azure_ai_rerank_transformation.py new file mode 100644 index 00000000000..1f425113439 --- /dev/null +++ b/tests/test_litellm/llms/azure_ai/rerank/test_azure_ai_rerank_transformation.py @@ -0,0 +1,100 @@ +import os +import sys + +import pytest + +sys.path.insert( + 0, os.path.abspath("../../../../..") +) # Adds the parent directory to the system path + +from litellm.llms.azure_ai.rerank.transformation import AzureAIRerankConfig + + +class TestAzureAIRerankConfigGetCompleteUrl: + def setup_method(self): + self.config = AzureAIRerankConfig() + self.model = "azure_ai/cohere-rerank-v3-english" + + def test_api_base_required(self): + with pytest.raises(ValueError) as exc_info: + self.config.get_complete_url(api_base=None, model=self.model) + + assert "api_base=None" in str(exc_info.value) + + @pytest.mark.parametrize( + "api_base", + [ + "example.com", + "example.com/v1", + "//example.com/v1", + "/v1/rerank", + ], + ) + def test_api_base_requires_scheme(self, api_base): + with pytest.raises(ValueError) as exc_info: + self.config.get_complete_url(api_base=api_base, model=self.model) + + error_message = str(exc_info.value).lower() + assert "absolute url" in error_message + assert "scheme" in error_message + + @pytest.mark.parametrize( + "api_base, expected_url", + [ + ( + "https://my-resource.services.ai.azure.com/v1/rerank/", + "https://my-resource.services.ai.azure.com/v1/rerank", + ), + ( + "https://my-resource.services.ai.azure.com/providers/cohere/v2/rerank/", + "https://my-resource.services.ai.azure.com/providers/cohere/v2/rerank", + ), + ], + ) + def test_preserves_full_rerank_endpoint(self, api_base, expected_url): + url = self.config.get_complete_url(api_base=api_base, model=self.model) + assert url == expected_url + + @pytest.mark.parametrize( + "api_base, expected_url", + [ + ( + "https://my-resource.services.ai.azure.com/v1", + "https://my-resource.services.ai.azure.com/v1/rerank", + ), + ( + "https://my-resource.services.ai.azure.com/v2/", + "https://my-resource.services.ai.azure.com/v2/rerank", + ), + ( + "https://my-resource.services.ai.azure.com/providers/cohere/v2", + "https://my-resource.services.ai.azure.com/providers/cohere/v2/rerank", + ), + ( + "https://my-resource.services.ai.azure.com/providers/cohere/v2/", + "https://my-resource.services.ai.azure.com/providers/cohere/v2/rerank", + ), + ], + ) + def test_appends_rerank_for_version_paths(self, api_base, expected_url): + url = self.config.get_complete_url(api_base=api_base, model=self.model) + assert url == expected_url + + @pytest.mark.parametrize( + "api_base", + [ + "https://my-resource.services.ai.azure.com", + "https://my-resource.services.ai.azure.com/", + ], + ) + def test_defaults_to_v1_rerank_when_base_has_no_path(self, api_base): + url = self.config.get_complete_url(api_base=api_base, model=self.model) + assert url == "https://my-resource.services.ai.azure.com/v1/rerank" + + def test_preserves_query_params(self): + url = self.config.get_complete_url( + api_base="https://my-resource.services.ai.azure.com/v1?r=1", + model=self.model, + ) + assert url == "https://my-resource.services.ai.azure.com/v1/rerank?r=1" + diff --git a/tests/test_litellm/llms/azure_ai/test_cost_calculator.py b/tests/test_litellm/llms/azure_ai/test_cost_calculator.py new file mode 100644 index 00000000000..30bbd753204 --- /dev/null +++ b/tests/test_litellm/llms/azure_ai/test_cost_calculator.py @@ -0,0 +1,346 @@ +""" +Test Azure AI cost calculator, especially Model Router flat cost. +""" + +import pytest + +from litellm.llms.azure_ai.cost_calculator import ( + _is_azure_model_router, + cost_per_token, +) +from litellm.types.utils import Usage +from litellm.utils import get_model_info + +# Get the flat cost from model_prices_and_context_window.json +_model_info = get_model_info(model="model_router", custom_llm_provider="azure_ai") +AZURE_MODEL_ROUTER_FLAT_COST_PER_M_INPUT_TOKENS = _model_info.get("input_cost_per_token", 0) * 1_000_000 + + +class TestAzureModelRouterDetection: + """Test that we correctly identify Azure Model Router models. + + Model Router deployments follow the pattern: model_router/ + where deployment-name is the Azure deployment (e.g., 'azure-model-router', 'prod-router') + """ + + @pytest.mark.parametrize( + "model,expected", + [ + # Deployment names containing 'model-router' or 'model_router' + ("azure-model-router", True), + ("AZURE-MODEL-ROUTER", True), + ("model-router", True), + ("MODEL-ROUTER", True), + ("my-model-router-deployment", True), + ("prod-model_router", True), + # New pattern: model_router/ + ("model_router/azure-model-router", True), + ("model-router/prod-router", True), + ("model_router/my-deployment", True), + ("MODEL_ROUTER/AZURE-MODEL-ROUTER", True), + # Non-router models + ("gpt-4o", False), + ("gpt-4o-mini", False), + ("claude-sonnet-4-5", False), + ("my-regular-deployment", False), + ], + ) + def test_is_azure_model_router(self, model: str, expected: bool): + """Test Azure Model Router detection.""" + assert _is_azure_model_router(model) == expected + + +class TestAzureModelRouterPrefix: + """Test Azure Model Router prefix stripping.""" + + @pytest.mark.parametrize( + "model,expected", + [ + # Model router deployments - the deployment name comes after model_router/ + ("model_router/azure-model-router", "azure-model-router"), + ("model-router/my-router-deployment", "my-router-deployment"), + ("model_router/prod-router", "prod-router"), + # Non-router models - should pass through unchanged + ("gpt-4o", "gpt-4o"), + ("azure-model-router", "azure-model-router"), + ("claude-sonnet-4", "claude-sonnet-4"), + ], + ) + def test_strip_model_router_prefix(self, model: str, expected: str): + """Test that model_router prefix is stripped correctly. + + The pattern is: model_router/ + where deployment-name is the Azure deployment (e.g., 'azure-model-router', 'prod-router') + """ + from litellm.llms.azure_ai.common_utils import AzureFoundryModelInfo + + result = AzureFoundryModelInfo.strip_model_router_prefix(model) + assert result == expected + + +class TestAzureModelRouterFlatCost: + """Test Azure AI Foundry Model Router flat cost calculation.""" + + def test_model_router_flat_cost_basic(self): + """Test that flat cost is added for Model Router requests.""" + model = "azure-model-router" + usage = Usage( + prompt_tokens=1000, + completion_tokens=500, + total_tokens=1500, + ) + + prompt_cost, completion_cost = cost_per_token(model=model, usage=usage) + + # Calculate expected flat cost + expected_flat_cost = ( + usage.prompt_tokens * AZURE_MODEL_ROUTER_FLAT_COST_PER_M_INPUT_TOKENS / 1_000_000 + ) + + # Flat cost should be $0.00014 (1000 tokens × $0.14 / 1M tokens) + assert expected_flat_cost == pytest.approx(0.00014, rel=1e-9) + + # Prompt cost should include the flat cost + # (plus any base cost from the actual model used, which might be 0 if not in model_cost) + assert prompt_cost >= expected_flat_cost + print( + f"Model Router flat cost for {usage.prompt_tokens} tokens: ${expected_flat_cost:.6f}" + ) + print(f"Total prompt cost: ${prompt_cost:.6f}") + + def test_model_router_flat_cost_large_request(self): + """Test flat cost calculation for larger requests.""" + model = "model-router" + usage = Usage( + prompt_tokens=100_000, + completion_tokens=50_000, + total_tokens=150_000, + ) + + prompt_cost, completion_cost = cost_per_token(model=model, usage=usage) + + # Calculate expected flat cost + expected_flat_cost = ( + usage.prompt_tokens * AZURE_MODEL_ROUTER_FLAT_COST_PER_M_INPUT_TOKENS / 1_000_000 + ) + + # Flat cost should be $0.014 (100k tokens × $0.14 / 1M tokens) + assert expected_flat_cost == pytest.approx(0.014, rel=1e-9) + # Use approx for floating-point comparison + assert prompt_cost >= expected_flat_cost or prompt_cost == pytest.approx(expected_flat_cost, rel=1e-9) + print( + f"Model Router flat cost for {usage.prompt_tokens} tokens: ${expected_flat_cost:.6f}" + ) + print(f"Total prompt cost: ${prompt_cost:.6f}") + + def test_model_router_flat_cost_1m_tokens(self): + """Test flat cost for exactly 1 million input tokens.""" + model = "azure-model-router" + usage = Usage( + prompt_tokens=1_000_000, + completion_tokens=100_000, + total_tokens=1_100_000, + ) + + prompt_cost, completion_cost = cost_per_token(model=model, usage=usage) + + # Calculate expected flat cost + expected_flat_cost = AZURE_MODEL_ROUTER_FLAT_COST_PER_M_INPUT_TOKENS + + # Flat cost should be exactly $0.14 for 1M tokens + assert expected_flat_cost == pytest.approx(0.14, rel=1e-9) + assert prompt_cost >= expected_flat_cost + print(f"Model Router flat cost for 1M tokens: ${expected_flat_cost:.6f}") + print(f"Total prompt cost: ${prompt_cost:.6f}") + + def test_non_model_router_no_flat_cost(self): + """Test that non-Model Router models don't get the flat cost.""" + model = "gpt-4o" + usage = Usage( + prompt_tokens=1000, + completion_tokens=500, + total_tokens=1500, + ) + + prompt_cost, completion_cost = cost_per_token(model=model, usage=usage) + + # No flat cost should be added for non-Model Router models + # The cost might be 0 or based on the model's pricing + print(f"Non-Model Router prompt cost: ${prompt_cost:.6f}") + # We just ensure it doesn't crash and returns valid values + assert prompt_cost >= 0 + assert completion_cost >= 0 + + def test_model_router_with_cached_tokens(self): + """Test Model Router flat cost with cached tokens.""" + model = "azure-model-router" + usage = Usage( + prompt_tokens=2000, + completion_tokens=800, + total_tokens=2800, + cache_read_input_tokens=500, + cache_creation_input_tokens=200, + ) + + prompt_cost, completion_cost = cost_per_token(model=model, usage=usage) + + # Flat cost is based on ALL prompt tokens (including cached) + expected_flat_cost = ( + usage.prompt_tokens * AZURE_MODEL_ROUTER_FLAT_COST_PER_M_INPUT_TOKENS / 1_000_000 + ) + + assert expected_flat_cost == pytest.approx(0.00028, rel=1e-9) + assert prompt_cost >= expected_flat_cost + print( + f"Model Router flat cost with caching for {usage.prompt_tokens} tokens: ${expected_flat_cost:.6f}" + ) + print(f"Total prompt cost: ${prompt_cost:.6f}") + + +class TestAzureModelRouterCostBreakdown: + """Test that Azure Model Router flat cost is tracked in cost breakdown.""" + + def test_flat_cost_calculation_helper(self): + """Test that flat cost can be calculated using the helper function.""" + from litellm.llms.azure_ai.cost_calculator import ( + calculate_azure_model_router_flat_cost, + ) + + model = "azure-model-router" + prompt_tokens = 10000 + + # Calculate flat cost using helper function + flat_cost = calculate_azure_model_router_flat_cost( + model=model, prompt_tokens=prompt_tokens + ) + + # Expected flat cost + expected_flat_cost = ( + prompt_tokens * AZURE_MODEL_ROUTER_FLAT_COST_PER_M_INPUT_TOKENS / 1_000_000 + ) + + assert flat_cost > 0 + assert flat_cost == pytest.approx(expected_flat_cost, rel=1e-9) + print(f"Flat cost calculated: ${flat_cost:.6f}") + + def test_flat_cost_integration_with_completion_cost(self): + """Test that flat cost is properly integrated into completion_cost calculation.""" + import litellm + from litellm.cost_calculator import completion_cost + from litellm.types.utils import Choices, Message, ModelResponse, Usage + + # Create a mock response for azure_ai model router + response = ModelResponse( + id="test-123", + choices=[ + Choices( + finish_reason="stop", + index=0, + message=Message( + role="assistant", + content="Test response", + ), + ) + ], + created=1234567890, + model="azure-model-router", + object="chat.completion", + usage=Usage( + prompt_tokens=5000, + completion_tokens=2000, + total_tokens=7000, + ), + ) + + # Set hidden params for provider + response._hidden_params = {"custom_llm_provider": "azure_ai"} + + # Calculate cost + cost = completion_cost( + completion_response=response, + model="azure-model-router", + custom_llm_provider="azure_ai", + ) + + # Expected flat cost + expected_flat_cost = ( + 5000 * AZURE_MODEL_ROUTER_FLAT_COST_PER_M_INPUT_TOKENS / 1_000_000 + ) + + # Cost should include the flat cost (use approx for floating-point comparison) + assert cost >= expected_flat_cost or cost == pytest.approx(expected_flat_cost, rel=1e-9) + print(f"Total cost with flat fee: ${cost:.6f}") + print(f"Expected minimum flat cost: ${expected_flat_cost:.6f}") + + def test_additional_costs_in_cost_breakdown(self): + """Test that Azure Model Router flat cost appears in additional_costs dict.""" + from datetime import datetime + + from litellm.cost_calculator import completion_cost + from litellm.litellm_core_utils.litellm_logging import Logging + from litellm.types.utils import Choices, Message, ModelResponse, Usage + + # Create logging object with required parameters + logging_obj = Logging( + model="azure-model-router", + messages=[{"role": "user", "content": "Hello"}], + stream=False, + call_type="completion", + start_time=datetime.now(), + litellm_call_id="test-123", + function_id="test-function", + ) + + # Create a mock response for azure_ai model router + response = ModelResponse( + id="test-123", + choices=[ + Choices( + finish_reason="stop", + index=0, + message=Message( + role="assistant", + content="Test response", + ), + ) + ], + created=1234567890, + model="azure-model-router", + object="chat.completion", + usage=Usage( + prompt_tokens=5000, + completion_tokens=2000, + total_tokens=7000, + ), + ) + + # Set hidden params for provider + response._hidden_params = {"custom_llm_provider": "azure_ai"} + + # Calculate cost with logging object + cost = completion_cost( + completion_response=response, + model="azure-model-router", + custom_llm_provider="azure_ai", + litellm_logging_obj=logging_obj, + ) + + # Check that cost breakdown contains additional_costs + assert hasattr(logging_obj, "cost_breakdown") + assert logging_obj.cost_breakdown is not None + assert "additional_costs" in logging_obj.cost_breakdown + assert isinstance(logging_obj.cost_breakdown["additional_costs"], dict) + + # Check that the Azure Model Router flat cost is in additional_costs + additional_costs = logging_obj.cost_breakdown["additional_costs"] + assert "Azure Model Router Flat Cost" in additional_costs + + # Verify the flat cost value + expected_flat_cost = ( + 5000 * AZURE_MODEL_ROUTER_FLAT_COST_PER_M_INPUT_TOKENS / 1_000_000 + ) + actual_flat_cost = additional_costs["Azure Model Router Flat Cost"] + assert actual_flat_cost == pytest.approx(expected_flat_cost, rel=1e-9) + + print(f"Additional costs in breakdown: {additional_costs}") + print(f"Azure Model Router Flat Cost: ${actual_flat_cost:.6f}") diff --git a/tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_bedrock_chat_invoke_transformations_anthropic_claude3_transformation.py b/tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_bedrock_chat_invoke_transformations_anthropic_claude3_transformation.py index 1cb84d32c1d..edfdeb08d82 100644 --- a/tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_bedrock_chat_invoke_transformations_anthropic_claude3_transformation.py +++ b/tests/test_litellm/llms/bedrock/chat/invoke_transformations/test_bedrock_chat_invoke_transformations_anthropic_claude3_transformation.py @@ -279,3 +279,293 @@ def test_output_format_with_no_schema(): # Content should remain as string (not converted to list) assert isinstance(last_user_message["content"], str) assert last_user_message["content"] == "Hello" + + +def test_advanced_tool_use_header_translation_for_opus_4_5(): + """ + Test that advanced-tool-use-2025-11-20 header is translated to Bedrock-specific headers + for Claude Opus 4.5. + + Regression test for: Claude Code sends advanced-tool-use header which needs to be + translated to tool-search-tool-2025-10-19 and tool-examples-2025-10-29 for Bedrock + Invoke API on Claude Opus 4.5. + + Ref: https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages-request-response.html + """ + from litellm.llms.bedrock.messages.invoke_transformations.anthropic_claude3_transformation import ( + AmazonAnthropicClaudeMessagesConfig, + ) + + config = AmazonAnthropicClaudeMessagesConfig() + + messages = [ + {"role": "user", "content": "What's the weather like?"} + ] + + anthropic_messages_optional_request_params = { + "max_tokens": 100, + } + + # Simulate advanced-tool-use header from Claude Code + headers = { + "anthropic-beta": "advanced-tool-use-2025-11-20" + } + + # Test with Claude Opus 4.5 + result = config.transform_anthropic_messages_request( + model="anthropic.claude-opus-4-5-20250514-v1:0", + messages=messages, + anthropic_messages_optional_request_params=anthropic_messages_optional_request_params, + litellm_params={}, + headers=headers, + ) + + # Verify advanced-tool-use header was removed + assert "anthropic_beta" in result + beta_headers = result["anthropic_beta"] + assert "advanced-tool-use-2025-11-20" not in beta_headers, \ + "advanced-tool-use header should be removed for Bedrock" + + # Verify Bedrock-specific headers were added + assert "tool-search-tool-2025-10-19" in beta_headers, \ + "tool-search-tool-2025-10-19 should be added for Opus 4.5" + assert "tool-examples-2025-10-29" in beta_headers, \ + "tool-examples-2025-10-29 should be added for Opus 4.5" + + +def test_advanced_tool_use_header_filtered_for_non_opus_4_5(): + """ + Test that advanced-tool-use-2025-11-20 header is filtered out for models + that don't support tool search on Bedrock. + + Tool search is supported on: Claude Opus 4.5, Claude Sonnet 4.5 + Tool search is NOT supported on: Claude 3.5 Sonnet and earlier + """ + from litellm.llms.bedrock.messages.invoke_transformations.anthropic_claude3_transformation import ( + AmazonAnthropicClaudeMessagesConfig, + ) + + config = AmazonAnthropicClaudeMessagesConfig() + + messages = [ + {"role": "user", "content": "What's the weather like?"} + ] + + anthropic_messages_optional_request_params = { + "max_tokens": 100, + } + + # Simulate advanced-tool-use header from Claude Code + headers = { + "anthropic-beta": "advanced-tool-use-2025-11-20" + } + + # Test with Claude 3.5 Sonnet (does NOT support tool search on Bedrock) + result = config.transform_anthropic_messages_request( + model="anthropic.claude-3-5-sonnet-20241022-v2:0", + messages=messages, + anthropic_messages_optional_request_params=anthropic_messages_optional_request_params, + litellm_params={}, + headers=headers, + ) + + # Verify advanced-tool-use header was removed + beta_headers = result.get("anthropic_beta", []) + assert "advanced-tool-use-2025-11-20" not in beta_headers, \ + "advanced-tool-use header should be removed for Bedrock" + + # Verify Bedrock-specific headers were NOT added (only for Opus 4.5 and Sonnet 4.5) + assert "tool-search-tool-2025-10-19" not in beta_headers, \ + "tool-search-tool should not be added for models without tool search support" + assert "tool-examples-2025-10-29" not in beta_headers, \ + "tool-examples should not be added for models without tool search support" + + +def test_advanced_tool_use_header_translation_with_multiple_beta_headers(): + """ + Test that advanced-tool-use header translation works correctly when multiple + beta headers are present. + """ + from litellm.llms.bedrock.messages.invoke_transformations.anthropic_claude3_transformation import ( + AmazonAnthropicClaudeMessagesConfig, + ) + + config = AmazonAnthropicClaudeMessagesConfig() + + messages = [ + {"role": "user", "content": "What's the weather like?"} + ] + + anthropic_messages_optional_request_params = { + "max_tokens": 100, + } + + # Multiple beta headers including advanced-tool-use + headers = { + "anthropic-beta": "claude-code-20250219,advanced-tool-use-2025-11-20,interleaved-thinking-2025-05-14" + } + + # Test with Claude Opus 4.5 + result = config.transform_anthropic_messages_request( + model="anthropic.claude-opus-4-5-20250514-v1:0", + messages=messages, + anthropic_messages_optional_request_params=anthropic_messages_optional_request_params, + litellm_params={}, + headers=headers, + ) + + beta_headers = result.get("anthropic_beta", []) + + # Verify advanced-tool-use was removed + assert "advanced-tool-use-2025-11-20" not in beta_headers + + # Verify Bedrock-specific headers were added + assert "tool-search-tool-2025-10-19" in beta_headers + assert "tool-examples-2025-10-29" in beta_headers + + # Verify other beta headers are preserved + assert "claude-code-20250219" in beta_headers + assert "interleaved-thinking-2025-05-14" in beta_headers + + +def test_opus_4_5_model_detection(): + """ + Test that the _is_claude_opus_4_5 method correctly identifies Opus 4.5 models + with various naming conventions. + """ + from litellm.llms.bedrock.messages.invoke_transformations.anthropic_claude3_transformation import ( + AmazonAnthropicClaudeMessagesConfig, + ) + + config = AmazonAnthropicClaudeMessagesConfig() + + # Test various Opus 4.5 naming patterns + opus_4_5_models = [ + "anthropic.claude-opus-4-5-20250514-v1:0", + "anthropic.claude-opus-4.5-20250514-v1:0", + "anthropic.claude-opus_4_5-20250514-v1:0", + "anthropic.claude-opus_4.5-20250514-v1:0", + "us.anthropic.claude-opus-4-5-20250514-v1:0", + "ANTHROPIC.CLAUDE-OPUS-4-5-20250514-V1:0", # Case insensitive + ] + + for model in opus_4_5_models: + assert config._is_claude_opus_4_5(model), \ + f"Should detect {model} as Opus 4.5" + + # Test non-Opus 4.5 models + non_opus_4_5_models = [ + "anthropic.claude-sonnet-4-5-20250929-v1:0", + "anthropic.claude-opus-4-20250514-v1:0", # Opus 4, not 4.5 + "anthropic.claude-opus-4-1-20250514-v1:0", # Opus 4.1, not 4.5 + "anthropic.claude-haiku-4-5-20251001-v1:0", + ] + + for model in non_opus_4_5_models: + assert not config._is_claude_opus_4_5(model), \ + f"Should not detect {model} as Opus 4.5" + + +def test_structured_outputs_beta_header_filtered_for_bedrock_invoke(): + """ + Test that unsupported beta headers are filtered out for Bedrock Invoke API. + + Bedrock Invoke API only supports a specific whitelist of beta flags and returns + "invalid beta flag" error for others (e.g., structured-outputs, mcp-servers). + This test ensures unsupported headers are filtered while keeping supported ones. + + Fixes: https://github.com/BerriAI/litellm/issues/16726 + """ + config = AmazonAnthropicClaudeConfig() + + messages = [{"role": "user", "content": "test"}] + + # Test 1: structured-outputs beta header (unsupported) + headers = {"anthropic-beta": "structured-outputs-2025-11-13"} + + result = config.transform_request( + model="anthropic.claude-4-0-sonnet-20250514-v1:0", + messages=messages, + optional_params={}, + litellm_params={}, + headers=headers, + ) + + # Verify structured-outputs beta is filtered out + anthropic_beta = result.get("anthropic_beta", []) + assert not any("structured-outputs" in beta for beta in anthropic_beta), \ + f"structured-outputs beta should be filtered, got: {anthropic_beta}" + + # Test 2: mcp-servers beta header (unsupported - the main issue from #16726) + headers = {"anthropic-beta": "mcp-servers-2025-12-04"} + + result = config.transform_request( + model="anthropic.claude-4-0-sonnet-20250514-v1:0", + messages=messages, + optional_params={}, + litellm_params={}, + headers=headers, + ) + + # Verify mcp-servers beta is filtered out + anthropic_beta = result.get("anthropic_beta", []) + assert not any("mcp-servers" in beta for beta in anthropic_beta), \ + f"mcp-servers beta should be filtered, got: {anthropic_beta}" + + # Test 3: Mix of supported and unsupported beta headers + headers = {"anthropic-beta": "computer-use-2024-10-22,mcp-servers-2025-12-04,structured-outputs-2025-11-13"} + + result = config.transform_request( + model="anthropic.claude-4-0-sonnet-20250514-v1:0", + messages=messages, + optional_params={}, + litellm_params={}, + headers=headers, + ) + + # Verify only supported betas are kept + anthropic_beta = result.get("anthropic_beta", []) + assert not any("structured-outputs" in beta for beta in anthropic_beta), \ + f"structured-outputs beta should be filtered, got: {anthropic_beta}" + assert not any("mcp-servers" in beta for beta in anthropic_beta), \ + f"mcp-servers beta should be filtered, got: {anthropic_beta}" + assert any("computer-use" in beta for beta in anthropic_beta), \ + f"computer-use beta should be kept, got: {anthropic_beta}" + + +def test_output_format_removed_from_bedrock_invoke_request(): + """ + Test that output_format parameter is removed from Bedrock Invoke requests. + + Bedrock Invoke API doesn't support the output_format parameter (only supported + in Anthropic Messages API). This test ensures it's removed to prevent errors. + """ + config = AmazonAnthropicClaudeConfig() + + messages = [{"role": "user", "content": "test"}] + + # Create a request with output_format via map_openai_params + non_default_params = { + "response_format": {"type": "json_object"} + } + optional_params = {} + + # This should trigger tool-based structured outputs + optional_params = config.map_openai_params( + non_default_params=non_default_params, + optional_params=optional_params, + model="anthropic.claude-4-0-sonnet-20250514-v1:0", + drop_params=False, + ) + + result = config.transform_request( + model="anthropic.claude-4-0-sonnet-20250514-v1:0", + messages=messages, + optional_params=optional_params, + litellm_params={}, + headers={}, + ) + + # Verify output_format is not in the request + assert "output_format" not in result, \ + f"output_format should be removed for Bedrock Invoke, got keys: {result.keys()}" diff --git a/tests/test_litellm/llms/bedrock/realtime/test_bedrock_realtime_transformation.py b/tests/test_litellm/llms/bedrock/realtime/test_bedrock_realtime_transformation.py new file mode 100644 index 00000000000..ee61825936f --- /dev/null +++ b/tests/test_litellm/llms/bedrock/realtime/test_bedrock_realtime_transformation.py @@ -0,0 +1,646 @@ +import json +import os +import sys +from unittest.mock import MagicMock + +import pytest + +sys.path.insert( + 0, os.path.abspath("../../../../..") +) # Adds the parent directory to the system path + +from litellm.llms.bedrock.realtime.transformation import BedrockRealtimeConfig +from litellm.types.llms.openai import OpenAIRealtimeEventTypes + + +class TestBedrockRealtimeConfig: + """Test suite for BedrockRealtimeConfig class""" + + def test_initialization(self): + """Test that BedrockRealtimeConfig initializes with correct defaults""" + config = BedrockRealtimeConfig() + + assert config is not None + assert config.max_tokens == 1024 + assert config.temperature == 0.7 + assert config.top_p == 0.9 + assert config.voice_id == "matthew" + assert config.output_sample_rate_hertz == 24000 + assert config.input_sample_rate_hertz == 16000 + assert config.text_media_type == "text/plain" + + def test_session_configuration_request(self): + """Test session configuration request generation""" + config = BedrockRealtimeConfig() + + session_config = config.session_configuration_request("amazon.nova-sonic-v1:0") + session_dict = json.loads(session_config) + + assert "session_start" in session_dict + assert "prompt_start" in session_dict + + # Check session start + session_start = session_dict["session_start"]["event"]["sessionStart"] + assert session_start["inferenceConfiguration"]["maxTokens"] == 1024 + assert session_start["inferenceConfiguration"]["temperature"] == 0.7 + + # Check prompt start + prompt_start = session_dict["prompt_start"]["event"]["promptStart"] + assert prompt_start["audioOutputConfiguration"]["voiceId"] == "matthew" + assert prompt_start["audioOutputConfiguration"]["sampleRateHertz"] == 24000 + + def test_session_configuration_with_tools(self): + """Test session configuration with tools""" + config = BedrockRealtimeConfig() + + tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get weather", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string"} + } + } + } + } + ] + + session_config = config.session_configuration_request( + "amazon.nova-sonic-v1:0", + tools=tools + ) + session_dict = json.loads(session_config) + + prompt_start = session_dict["prompt_start"]["event"]["promptStart"] + assert "toolConfiguration" in prompt_start + assert "tools" in prompt_start["toolConfiguration"] + assert len(prompt_start["toolConfiguration"]["tools"]) == 1 + assert prompt_start["toolConfiguration"]["tools"][0]["toolSpec"]["name"] == "get_weather" + + def test_transform_tools_to_bedrock_format(self): + """Test OpenAI tool format to Bedrock format transformation""" + config = BedrockRealtimeConfig() + + openai_tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get current weather", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string", "description": "City name"} + }, + "required": ["location"] + } + } + } + ] + + bedrock_tools = config._transform_tools_to_bedrock_format(openai_tools) + + assert len(bedrock_tools) == 1 + assert bedrock_tools[0]["toolSpec"]["name"] == "get_weather" + assert bedrock_tools[0]["toolSpec"]["description"] == "Get current weather" + assert "inputSchema" in bedrock_tools[0]["toolSpec"] + + # Verify the schema is properly JSON stringified + schema = json.loads(bedrock_tools[0]["toolSpec"]["inputSchema"]["json"]) + assert schema["type"] == "object" + assert "location" in schema["properties"] + + def test_audio_format_mapping(self): + """Test audio format to sample rate mapping""" + config = BedrockRealtimeConfig() + + # Test PCM16 format + assert config._map_audio_format_to_sample_rate("pcm16", is_output=True) == 24000 + assert config._map_audio_format_to_sample_rate("pcm16", is_output=False) == 16000 + + # Test G.711 formats + assert config._map_audio_format_to_sample_rate("g711_ulaw", is_output=True) == 8000 + assert config._map_audio_format_to_sample_rate("g711_alaw", is_output=False) == 8000 + + def test_transform_session_update_event(self): + """Test session.update event transformation""" + config = BedrockRealtimeConfig() + + session_update = { + "type": "session.update", + "session": { + "temperature": 0.9, + "voice": "joanna", + "max_response_output_tokens": 2048, + "output_audio_format": "pcm16" + } + } + + messages = config.transform_session_update_event(session_update) + + assert len(messages) >= 2 # At least session start and prompt start + + # Verify attributes were updated + assert config.temperature == 0.9 + assert config.voice_id == "joanna" + assert config.max_tokens == 2048 + + # Verify session start message + session_start = json.loads(messages[0]) + assert session_start["event"]["sessionStart"]["inferenceConfiguration"]["temperature"] == 0.9 + + def test_transform_session_update_with_tools(self): + """Test session.update with tools""" + config = BedrockRealtimeConfig() + + session_update = { + "type": "session.update", + "session": { + "tools": [ + { + "type": "function", + "function": { + "name": "get_time", + "description": "Get current time", + "parameters": {"type": "object", "properties": {}} + } + } + ] + } + } + + messages = config.transform_session_update_event(session_update) + + # Find prompt start message + prompt_start = json.loads(messages[1]) + assert "toolConfiguration" in prompt_start["event"]["promptStart"] + + def test_transform_conversation_item_create_text(self): + """Test conversation.item.create with text""" + config = BedrockRealtimeConfig() + + item_create = { + "type": "conversation.item.create", + "item": { + "type": "message", + "role": "user", + "content": [ + { + "type": "input_text", + "text": "Hello, how are you?" + } + ] + } + } + + messages = config.transform_conversation_item_create_event(item_create) + + # Should have content start, text input, and content end + assert len(messages) == 3 + + content_start = json.loads(messages[0]) + assert content_start["event"]["contentStart"]["type"] == "TEXT" + assert content_start["event"]["contentStart"]["role"] == "USER" + + text_input = json.loads(messages[1]) + assert text_input["event"]["textInput"]["content"] == "Hello, how are you?" + + def test_transform_conversation_item_create_tool_result(self): + """Test conversation.item.create with tool result""" + config = BedrockRealtimeConfig() + + tool_result = { + "type": "conversation.item.create", + "item": { + "type": "function_call_output", + "call_id": "call_123", + "output": json.dumps({"temperature": 72, "conditions": "sunny"}) + } + } + + messages = config.transform_conversation_item_create_event(tool_result) + + # Should have content start, tool result, and content end + assert len(messages) == 3 + + content_start = json.loads(messages[0]) + assert content_start["event"]["contentStart"]["type"] == "TOOL" + assert content_start["event"]["contentStart"]["role"] == "TOOL" + assert content_start["event"]["contentStart"]["toolResultInputConfiguration"]["toolUseId"] == "call_123" + + def test_transform_input_audio_buffer_append(self): + """Test input_audio_buffer.append transformation""" + config = BedrockRealtimeConfig() + + audio_append = { + "type": "input_audio_buffer.append", + "audio": "base64_audio_data_here" + } + + messages = config.transform_input_audio_buffer_append_event(audio_append) + + # First call should include content start + assert len(messages) == 2 + + content_start = json.loads(messages[0]) + assert content_start["event"]["contentStart"]["type"] == "AUDIO" + assert content_start["event"]["contentStart"]["audioInputConfiguration"]["sampleRateHertz"] == 16000 + + audio_input = json.loads(messages[1]) + assert audio_input["event"]["audioInput"]["content"] == "base64_audio_data_here" + + def test_transform_input_audio_buffer_commit(self): + """Test input_audio_buffer.commit transformation""" + config = BedrockRealtimeConfig() + + # First append to set the flag + config._audio_content_started = True + + commit = { + "type": "input_audio_buffer.commit" + } + + messages = config.transform_input_audio_buffer_commit_event(commit) + + assert len(messages) == 1 + content_end = json.loads(messages[0]) + assert "contentEnd" in content_end["event"] + + +class TestBedrockRealtimeResponseTransformation: + """Test suite for response transformation""" + + def test_transform_session_start_response(self): + """Test sessionStart response transformation""" + config = BedrockRealtimeConfig() + logging_obj = MagicMock() + logging_obj.litellm_trace_id = "trace_123" + + bedrock_message = { + "event": { + "sessionStart": { + "inferenceConfiguration": { + "maxTokens": 1024, + "temperature": 0.7 + } + } + } + } + + result = config.transform_realtime_response( + json.dumps(bedrock_message), + "amazon.nova-sonic-v1:0", + logging_obj, + realtime_response_transform_input={ + "session_configuration_request": None, + "current_output_item_id": None, + "current_response_id": None, + "current_conversation_id": None, + "current_delta_chunks": [], + "current_item_chunks": [], + "current_delta_type": None, + } + ) + + assert len(result["response"]) == 1 + assert result["response"][0]["type"] == "session.created" + assert result["response"][0]["session"]["id"] == "trace_123" + assert "model" in result["response"][0]["session"] + + def test_transform_text_output_response(self): + """Test textOutput response transformation""" + config = BedrockRealtimeConfig() + logging_obj = MagicMock() + logging_obj.litellm_trace_id = "trace_123" + + # First create a content start to initialize IDs + content_start_message = { + "event": { + "contentStart": { + "role": "ASSISTANT", + "type": "TEXT" + } + } + } + + result1 = config.transform_realtime_response( + json.dumps(content_start_message), + "amazon.nova-sonic-v1:0", + logging_obj, + realtime_response_transform_input={ + "session_configuration_request": json.dumps({"configured": True}), + "current_output_item_id": None, + "current_response_id": None, + "current_conversation_id": None, + "current_delta_chunks": [], + "current_item_chunks": [], + "current_delta_type": None, + } + ) + + # Now send text output + text_output_message = { + "event": { + "textOutput": { + "content": "Hello, world!" + } + } + } + + result2 = config.transform_realtime_response( + json.dumps(text_output_message), + "amazon.nova-sonic-v1:0", + logging_obj, + realtime_response_transform_input={ + "session_configuration_request": json.dumps({"configured": True}), + "current_output_item_id": result1["current_output_item_id"], + "current_response_id": result1["current_response_id"], + "current_conversation_id": result1["current_conversation_id"], + "current_delta_chunks": result1["current_delta_chunks"], + "current_item_chunks": [], + "current_delta_type": result1["current_delta_type"], + } + ) + + # Check for text delta + text_deltas = [msg for msg in result2["response"] if msg["type"] == "response.text.delta"] + assert len(text_deltas) == 1 + assert text_deltas[0]["delta"] == "Hello, world!" + + # Check that delta chunks are accumulated + assert len(result2["current_delta_chunks"]) == 1 + + def test_transform_audio_output_response(self): + """Test audioOutput response transformation""" + config = BedrockRealtimeConfig() + logging_obj = MagicMock() + logging_obj.litellm_trace_id = "trace_123" + + # First create a content start for audio + content_start_message = { + "event": { + "contentStart": { + "role": "ASSISTANT", + "type": "AUDIO" + } + } + } + + result1 = config.transform_realtime_response( + json.dumps(content_start_message), + "amazon.nova-sonic-v1:0", + logging_obj, + realtime_response_transform_input={ + "session_configuration_request": json.dumps({"configured": True}), + "current_output_item_id": None, + "current_response_id": None, + "current_conversation_id": None, + "current_delta_chunks": [], + "current_item_chunks": [], + "current_delta_type": None, + } + ) + + # Now send audio output + audio_output_message = { + "event": { + "audioOutput": { + "content": "base64_audio_content" + } + } + } + + result2 = config.transform_realtime_response( + json.dumps(audio_output_message), + "amazon.nova-sonic-v1:0", + logging_obj, + realtime_response_transform_input={ + "session_configuration_request": json.dumps({"configured": True}), + "current_output_item_id": result1["current_output_item_id"], + "current_response_id": result1["current_response_id"], + "current_conversation_id": result1["current_conversation_id"], + "current_delta_chunks": [], + "current_item_chunks": [], + "current_delta_type": result1["current_delta_type"], + } + ) + + # Check for audio delta + audio_deltas = [msg for msg in result2["response"] if msg["type"] == "response.audio.delta"] + assert len(audio_deltas) == 1 + assert audio_deltas[0]["delta"] == "base64_audio_content" + + def test_transform_tool_use_response(self): + """Test toolUse response transformation""" + config = BedrockRealtimeConfig() + logging_obj = MagicMock() + logging_obj.litellm_trace_id = "trace_123" + + tool_use_message = { + "event": { + "toolUse": { + "toolUseId": "tool_call_123", + "toolName": "get_weather", + "input": json.dumps({"location": "San Francisco"}) + } + } + } + + result = config.transform_realtime_response( + json.dumps(tool_use_message), + "amazon.nova-sonic-v1:0", + logging_obj, + realtime_response_transform_input={ + "session_configuration_request": json.dumps({"configured": True}), + "current_output_item_id": "item_123", + "current_response_id": "resp_123", + "current_conversation_id": "conv_123", + "current_delta_chunks": [], + "current_item_chunks": [], + "current_delta_type": "text", + } + ) + + # Check for function call event + assert len(result["response"]) == 1 + function_call = result["response"][0] + assert function_call["type"] == "response.function_call_arguments.done" + assert function_call["call_id"] == "tool_call_123" + assert function_call["name"] == "get_weather" + + # Verify arguments are properly formatted + args = json.loads(function_call["arguments"]) + assert args["location"] == "San Francisco" + + def test_transform_content_end_text(self): + """Test contentEnd for text response""" + config = BedrockRealtimeConfig() + logging_obj = MagicMock() + logging_obj.litellm_trace_id = "trace_123" + + # Create some delta chunks first + delta_chunks = [ + {"delta": "Hello, ", "type": "response.text.delta"}, + {"delta": "world!", "type": "response.text.delta"} + ] + + content_end_message = { + "event": { + "contentEnd": {} + } + } + + result = config.transform_realtime_response( + json.dumps(content_end_message), + "amazon.nova-sonic-v1:0", + logging_obj, + realtime_response_transform_input={ + "session_configuration_request": json.dumps({"configured": True}), + "current_output_item_id": "item_123", + "current_response_id": "resp_123", + "current_conversation_id": "conv_123", + "current_delta_chunks": delta_chunks, + "current_item_chunks": [], + "current_delta_type": "text", + } + ) + + # Should have text.done, content_part.done, and output_item.done + assert len(result["response"]) == 3 + + text_done = [msg for msg in result["response"] if msg["type"] == "response.text.done"][0] + assert text_done["text"] == "Hello, world!" + + # Delta chunks should be reset + assert result["current_delta_chunks"] is None + + def test_transform_prompt_end_response(self): + """Test promptEnd response transformation""" + config = BedrockRealtimeConfig() + logging_obj = MagicMock() + logging_obj.litellm_trace_id = "trace_123" + + prompt_end_message = { + "event": { + "promptEnd": {} + } + } + + result = config.transform_realtime_response( + json.dumps(prompt_end_message), + "amazon.nova-sonic-v1:0", + logging_obj, + realtime_response_transform_input={ + "session_configuration_request": json.dumps({"configured": True}), + "current_output_item_id": "item_123", + "current_response_id": "resp_123", + "current_conversation_id": "conv_123", + "current_delta_chunks": [], + "current_item_chunks": [], + "current_delta_type": "text", + } + ) + + # Should have response.done + assert len(result["response"]) == 1 + assert result["response"][0]["type"] == "response.done" + assert result["response"][0]["response"]["status"] == "completed" + + # State should be reset + assert result["current_output_item_id"] is None + assert result["current_response_id"] is None + assert result["current_delta_type"] is None + + def test_event_id_uniqueness(self): + """Test that all event_ids are unique""" + config = BedrockRealtimeConfig() + logging_obj = MagicMock() + logging_obj.litellm_trace_id = "trace_123" + + # Create a sequence of messages + content_start = {"event": {"contentStart": {"role": "ASSISTANT", "type": "TEXT"}}} + text_output1 = {"event": {"textOutput": {"content": "Hello"}}} + text_output2 = {"event": {"textOutput": {"content": " world"}}} + + all_events = [] + state = { + "session_configuration_request": json.dumps({"configured": True}), + "current_output_item_id": None, + "current_response_id": None, + "current_conversation_id": None, + "current_delta_chunks": [], + "current_item_chunks": [], + "current_delta_type": None, + } + + # Process all messages + for msg in [content_start, text_output1, text_output2]: + result = config.transform_realtime_response( + json.dumps(msg), + "amazon.nova-sonic-v1:0", + logging_obj, + realtime_response_transform_input=state + ) + all_events.extend(result["response"]) + # Update state for next iteration + state.update({ + "current_output_item_id": result["current_output_item_id"], + "current_response_id": result["current_response_id"], + "current_conversation_id": result["current_conversation_id"], + "current_delta_chunks": result["current_delta_chunks"], + "current_delta_type": result["current_delta_type"], + }) + + # Check all event_ids are unique + event_ids = [event["event_id"] for event in all_events if "event_id" in event] + assert len(event_ids) == len(set(event_ids)), "Event IDs should be unique" + + def test_response_id_consistency(self): + """Test that response_id remains consistent across related events""" + config = BedrockRealtimeConfig() + logging_obj = MagicMock() + logging_obj.litellm_trace_id = "trace_123" + + # Create a sequence of messages + content_start = {"event": {"contentStart": {"role": "ASSISTANT", "type": "TEXT"}}} + text_output = {"event": {"textOutput": {"content": "Hello"}}} + + all_events = [] + state = { + "session_configuration_request": json.dumps({"configured": True}), + "current_output_item_id": None, + "current_response_id": None, + "current_conversation_id": None, + "current_delta_chunks": [], + "current_item_chunks": [], + "current_delta_type": None, + } + + # Process messages + for msg in [content_start, text_output]: + result = config.transform_realtime_response( + json.dumps(msg), + "amazon.nova-sonic-v1:0", + logging_obj, + realtime_response_transform_input=state + ) + all_events.extend(result["response"]) + state.update({ + "current_output_item_id": result["current_output_item_id"], + "current_response_id": result["current_response_id"], + "current_conversation_id": result["current_conversation_id"], + "current_delta_chunks": result["current_delta_chunks"], + "current_delta_type": result["current_delta_type"], + }) + + # Check all response_ids are the same + response_ids = [event["response_id"] for event in all_events if "response_id" in event] + assert len(set(response_ids)) == 1, "Response IDs should be consistent" + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/test_litellm/llms/bedrock/test_anthropic_beta_support.py b/tests/test_litellm/llms/bedrock/test_anthropic_beta_support.py index b9324e4966f..e7b6de29b6b 100644 --- a/tests/test_litellm/llms/bedrock/test_anthropic_beta_support.py +++ b/tests/test_litellm/llms/bedrock/test_anthropic_beta_support.py @@ -390,3 +390,103 @@ class TestAnthropicBetaHeaderSupport: "anthropic_beta SHOULD be added for Anthropic models with cross-region prefix." ) assert "context-1m-2025-08-07" in additional_fields["anthropic_beta"] + + def test_messages_advanced_tool_use_translation_opus_4_5(self): + """Test that advanced-tool-use header is translated to Bedrock-specific headers for Opus 4.5. + + Regression test for: Claude Code sends advanced-tool-use-2025-11-20 header which needs + to be translated to tool-search-tool-2025-10-19 and tool-examples-2025-10-29 for + Bedrock Invoke API on Claude Opus 4.5. + + Ref: https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages-request-response.html + """ + config = AmazonAnthropicClaudeMessagesConfig() + headers = {"anthropic-beta": "advanced-tool-use-2025-11-20"} + + result = config.transform_anthropic_messages_request( + model="us.anthropic.claude-opus-4-5-20250514-v1:0", + messages=[{"role": "user", "content": "Test"}], + anthropic_messages_optional_request_params={"max_tokens": 100}, + litellm_params={}, + headers=headers + ) + + assert "anthropic_beta" in result + beta_headers = result["anthropic_beta"] + + # advanced-tool-use should be removed + assert "advanced-tool-use-2025-11-20" not in beta_headers, ( + "advanced-tool-use-2025-11-20 should be removed for Bedrock Invoke API" + ) + + # Bedrock-specific headers should be added for Opus 4.5 + assert "tool-search-tool-2025-10-19" in beta_headers, ( + "tool-search-tool-2025-10-19 should be added for Opus 4.5" + ) + assert "tool-examples-2025-10-29" in beta_headers, ( + "tool-examples-2025-10-29 should be added for Opus 4.5" + ) + + def test_messages_advanced_tool_use_translation_sonnet_4_5(self): + """Test that advanced-tool-use header is translated to Bedrock-specific headers for Sonnet 4.5. + + Regression test for: Claude Code sends advanced-tool-use-2025-11-20 header which needs + to be translated to tool-search-tool-2025-10-19 and tool-examples-2025-10-29 for + Bedrock Invoke API on Claude Sonnet 4.5. + + Ref: https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool + """ + config = AmazonAnthropicClaudeMessagesConfig() + headers = {"anthropic-beta": "advanced-tool-use-2025-11-20"} + + result = config.transform_anthropic_messages_request( + model="us.anthropic.claude-sonnet-4-5-20250929-v1:0", + messages=[{"role": "user", "content": "Test"}], + anthropic_messages_optional_request_params={"max_tokens": 100}, + litellm_params={}, + headers=headers + ) + + assert "anthropic_beta" in result + beta_headers = result["anthropic_beta"] + + # advanced-tool-use should be removed + assert "advanced-tool-use-2025-11-20" not in beta_headers, ( + "advanced-tool-use-2025-11-20 should be removed for Bedrock Invoke API" + ) + + # Bedrock-specific headers should be added for Sonnet 4.5 + assert "tool-search-tool-2025-10-19" in beta_headers, ( + "tool-search-tool-2025-10-19 should be added for Sonnet 4.5" + ) + assert "tool-examples-2025-10-29" in beta_headers, ( + "tool-examples-2025-10-29 should be added for Sonnet 4.5" + ) + + def test_messages_advanced_tool_use_filtered_unsupported_model(self): + """Test that advanced-tool-use header is filtered out for models that don't support tool search. + + The translation to Bedrock-specific headers should only happen for models that + support tool search on Bedrock (Opus 4.5, Sonnet 4.5). + For other models, the advanced-tool-use header should just be removed. + """ + config = AmazonAnthropicClaudeMessagesConfig() + headers = {"anthropic-beta": "advanced-tool-use-2025-11-20"} + + # Test with Claude 3.5 Sonnet (does NOT support tool search on Bedrock) + result = config.transform_anthropic_messages_request( + model="us.anthropic.claude-3-5-sonnet-20241022-v2:0", + messages=[{"role": "user", "content": "Test"}], + anthropic_messages_optional_request_params={"max_tokens": 100}, + litellm_params={}, + headers=headers + ) + + beta_headers = result.get("anthropic_beta", []) + + # advanced-tool-use should be removed + assert "advanced-tool-use-2025-11-20" not in beta_headers + + # Bedrock-specific headers should NOT be added for unsupported models + assert "tool-search-tool-2025-10-19" not in beta_headers + assert "tool-examples-2025-10-29" not in beta_headers diff --git a/tests/test_litellm/llms/custom_httpx/test_http_handler.py b/tests/test_litellm/llms/custom_httpx/test_http_handler.py index 0b154474d48..65f08ef5021 100644 --- a/tests/test_litellm/llms/custom_httpx/test_http_handler.py +++ b/tests/test_litellm/llms/custom_httpx/test_http_handler.py @@ -471,3 +471,87 @@ def test_ssl_ecdh_curve(env_curve, litellm_curve, expected_curve, should_call, m assert isinstance(ssl_context, ssl.SSLContext) finally: litellm.ssl_ecdh_curve = original_value + + +def test_default_user_agent_is_litellm_version(monkeypatch): + from litellm._version import version + from litellm.llms.custom_httpx.http_handler import get_default_headers + + monkeypatch.delenv("LITELLM_USER_AGENT", raising=False) + + assert get_default_headers()["User-Agent"] == f"litellm/{version}" + + +def test_user_agent_can_be_overridden_via_env_var(monkeypatch): + from litellm.llms.custom_httpx.http_handler import get_default_headers + + monkeypatch.setenv("LITELLM_USER_AGENT", "Claude Code") + + assert get_default_headers()["User-Agent"] == "Claude Code" + + +def test_user_agent_env_var_can_be_empty_string(monkeypatch): + from litellm.llms.custom_httpx.http_handler import get_default_headers + + monkeypatch.setenv("LITELLM_USER_AGENT", "") + + assert get_default_headers()["User-Agent"] == "" + + +def test_user_agent_override_is_not_appended_to_default(monkeypatch): + from litellm.llms.custom_httpx.http_handler import HTTPHandler + + monkeypatch.delenv("LITELLM_USER_AGENT", raising=False) + + handler = HTTPHandler() + try: + req = handler.client.build_request( + "GET", + "https://example.com", + headers={"user-agent": "Claude Code"}, + ) + + assert req.headers.get_list("User-Agent") == ["Claude Code"] + finally: + handler.close() + + +def test_sync_http_handler_uses_env_user_agent(monkeypatch): + from litellm.llms.custom_httpx.http_handler import HTTPHandler + + monkeypatch.setenv("LITELLM_USER_AGENT", "Claude Code") + + handler = HTTPHandler() + try: + req = handler.client.build_request("GET", "https://example.com") + assert req.headers.get("User-Agent") == "Claude Code" + finally: + handler.close() + + +@pytest.mark.asyncio +async def test_async_http_handler_uses_env_user_agent(monkeypatch): + from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler + + monkeypatch.setenv("LITELLM_USER_AGENT", "Claude Code") + + handler = AsyncHTTPHandler() + try: + req = handler.client.build_request("GET", "https://example.com") + assert req.headers.get("User-Agent") == "Claude Code" + finally: + await handler.close() + + +@pytest.mark.asyncio +async def test_httpx_handler_uses_env_user_agent(monkeypatch): + from litellm.llms.custom_httpx.httpx_handler import HTTPHandler + + monkeypatch.setenv("LITELLM_USER_AGENT", "Claude Code") + + handler = HTTPHandler() + try: + req = handler.client.build_request("GET", "https://example.com") + assert req.headers.get("User-Agent") == "Claude Code" + finally: + await handler.close() diff --git a/tests/test_litellm/llms/gemini/image_edit/test_gemini_image_edit_transformation.py b/tests/test_litellm/llms/gemini/image_edit/test_gemini_image_edit_transformation.py index 021cfaeff5e..9cd746cfde4 100644 --- a/tests/test_litellm/llms/gemini/image_edit/test_gemini_image_edit_transformation.py +++ b/tests/test_litellm/llms/gemini/image_edit/test_gemini_image_edit_transformation.py @@ -61,7 +61,7 @@ class TestGeminiImageEditTransformation: assert base64.b64decode(inline_data["data"]) == image_bytes generation_config = request_body["generationConfig"] - assert generation_config["aspectRatio"] == "16:9" + assert generation_config["imageConfig"]["aspectRatio"] == "16:9" def test_transform_image_edit_request_multiple_images(self) -> None: image_one = BytesIO(b"image_one") diff --git a/tests/test_litellm/llms/hosted_vllm/chat/test_hosted_vllm_chat_transformation.py b/tests/test_litellm/llms/hosted_vllm/chat/test_hosted_vllm_chat_transformation.py index 3749a5a8ca4..ddc01db0ec7 100644 --- a/tests/test_litellm/llms/hosted_vllm/chat/test_hosted_vllm_chat_transformation.py +++ b/tests/test_litellm/llms/hosted_vllm/chat/test_hosted_vllm_chat_transformation.py @@ -1,10 +1,7 @@ import json import os import sys -from unittest.mock import AsyncMock, MagicMock, patch - -import httpx -import pytest +from unittest.mock import MagicMock, patch sys.path.insert( 0, os.path.abspath("../../../../..") @@ -47,15 +44,34 @@ def test_hosted_vllm_chat_transformation_file_url(): def test_hosted_vllm_chat_transformation_with_audio_url(): from litellm import completion - from litellm.llms.custom_httpx.http_handler import HTTPHandler - client = MagicMock() + mock_client = MagicMock() + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.headers = {"content-type": "application/json"} + mock_response.json.return_value = { + "id": "chatcmpl-test", + "object": "chat.completion", + "created": 1234567890, + "model": "llama-3.1-70b-instruct", + "choices": [ + { + "index": 0, + "message": {"role": "assistant", "content": "Test response"}, + "finish_reason": "stop", + } + ], + "usage": {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15}, + } + mock_response.text = json.dumps(mock_response.json.return_value) + mock_client.post.return_value = mock_response - with patch.object( - client.chat.completions.with_raw_response, "create", return_value=MagicMock() - ) as mock_post: + with patch( + "litellm.llms.custom_httpx.llm_http_handler._get_httpx_client", + return_value=mock_client, + ): try: - response = completion( + completion( model="hosted_vllm/llama-3.1-70b-instruct", messages=[ { @@ -68,14 +84,15 @@ def test_hosted_vllm_chat_transformation_with_audio_url(): ], }, ], - client=client, + api_base="https://test-vllm.example.com/v1", ) - except Exception as e: - print(f"Error: {e}") + except Exception: + pass - mock_post.assert_called_once() - print(f"mock_post.call_args.kwargs: {mock_post.call_args.kwargs}") - assert mock_post.call_args.kwargs["messages"] == [ + mock_client.post.assert_called_once() + call_kwargs = mock_client.post.call_args[1] + request_data = json.loads(call_kwargs["data"]) + assert request_data["messages"] == [ { "role": "user", "content": [ @@ -101,3 +118,50 @@ def test_hosted_vllm_supports_reasoning_effort(): drop_params=False, ) assert optional_params["reasoning_effort"] == "high" + + +def test_hosted_vllm_supports_thinking(): + """ + Test that hosted_vllm supports the 'thinking' parameter. + + Anthropic-style thinking is converted to OpenAI-style reasoning_effort + since vLLM is OpenAI-compatible. + + Related issue: https://github.com/BerriAI/litellm/issues/19761 + """ + config = HostedVLLMChatConfig() + supported_params = config.get_supported_openai_params( + model="hosted_vllm/GLM-4.6-FP8" + ) + assert "thinking" in supported_params + + # Test thinking with low budget_tokens -> "minimal" (for < 2000) + optional_params = config.map_openai_params( + non_default_params={"thinking": {"type": "enabled", "budget_tokens": 1024}}, + optional_params={}, + model="hosted_vllm/GLM-4.6-FP8", + drop_params=False, + ) + assert "thinking" not in optional_params # thinking should NOT be passed + assert optional_params["reasoning_effort"] == "minimal" + + # Test thinking with high budget_tokens -> "high" + optional_params = config.map_openai_params( + non_default_params={"thinking": {"type": "enabled", "budget_tokens": 15000}}, + optional_params={}, + model="hosted_vllm/GLM-4.6-FP8", + drop_params=False, + ) + assert optional_params["reasoning_effort"] == "high" + + # Test that existing reasoning_effort is not overwritten + optional_params = config.map_openai_params( + non_default_params={ + "thinking": {"type": "enabled", "budget_tokens": 15000}, + "reasoning_effort": "low", + }, + optional_params={}, + model="hosted_vllm/GLM-4.6-FP8", + drop_params=False, + ) + assert optional_params["reasoning_effort"] == "low" diff --git a/tests/test_litellm/llms/hosted_vllm/chat/test_hosted_vllm_ssl_verify.py b/tests/test_litellm/llms/hosted_vllm/chat/test_hosted_vllm_ssl_verify.py new file mode 100644 index 00000000000..8f98b3ca8f1 --- /dev/null +++ b/tests/test_litellm/llms/hosted_vllm/chat/test_hosted_vllm_ssl_verify.py @@ -0,0 +1,152 @@ +""" +Test SSL verification for hosted_vllm provider. + +This test ensures that the ssl_verify parameter is properly passed through +to the HTTP client when using the hosted_vllm provider. + +Issue: ssl_verify parameter was being ignored because hosted_vllm fell through +to the OpenAI catch-all path in main.py, which doesn't pass ssl_verify to the HTTP client. +""" + +import os +import sys +from unittest.mock import MagicMock, patch + +import pytest + +sys.path.insert( + 0, os.path.abspath("../../../../..") +) # Adds the parent directory to the system path + +import litellm + + +class TestHostedVLLMSSLVerify: + """Test suite for SSL verification in hosted_vllm provider.""" + + @patch("litellm.llms.custom_httpx.llm_http_handler._get_httpx_client") + def test_hosted_vllm_ssl_verify_false_sync(self, mock_get_httpx_client): + """Test that ssl_verify=False is passed to the HTTP client for sync calls.""" + # Setup mock client + mock_client = MagicMock() + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.headers = {"content-type": "application/json"} + mock_response.json.return_value = { + "id": "chatcmpl-test", + "object": "chat.completion", + "created": 1234567890, + "model": "test-model", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Test response", + }, + "finish_reason": "stop", + } + ], + "usage": { + "prompt_tokens": 10, + "completion_tokens": 5, + "total_tokens": 15, + }, + } + mock_response.text = '{"id": "chatcmpl-test", "object": "chat.completion", "created": 1234567890, "model": "test-model", "choices": [{"index": 0, "message": {"role": "assistant", "content": "Test response"}, "finish_reason": "stop"}], "usage": {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15}}' + mock_client.post.return_value = mock_response + mock_get_httpx_client.return_value = mock_client + + try: + litellm.completion( + model="hosted_vllm/test-model", + messages=[{"role": "user", "content": "Hello"}], + api_base="https://test-vllm.example.com/v1", + ssl_verify=False, + ) + except Exception: + # Even if the response parsing fails, we just need to verify + # that the mock was called with the correct ssl_verify parameter + pass + + # Verify _get_httpx_client was called with ssl_verify=False + mock_get_httpx_client.assert_called() + call_args = mock_get_httpx_client.call_args + + # Check that params contains ssl_verify=False + if call_args[0]: + # Positional argument + params = call_args[0][0] + else: + # Keyword argument + params = call_args[1].get("params", {}) + + assert ( + params.get("ssl_verify") is False + ), f"Expected ssl_verify=False in params, got {params}" + + @patch("litellm.llms.custom_httpx.llm_http_handler.get_async_httpx_client") + @pytest.mark.asyncio + async def test_hosted_vllm_ssl_verify_false_async( + self, mock_get_async_httpx_client + ): + """Test that ssl_verify=False is passed to the HTTP client for async calls.""" + # Setup mock async client + mock_client = MagicMock() + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.headers = {"content-type": "application/json"} + mock_response.json.return_value = { + "id": "chatcmpl-test", + "object": "chat.completion", + "created": 1234567890, + "model": "test-model", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Test response", + }, + "finish_reason": "stop", + } + ], + "usage": { + "prompt_tokens": 10, + "completion_tokens": 5, + "total_tokens": 15, + }, + } + mock_response.text = '{"id": "chatcmpl-test", "object": "chat.completion", "created": 1234567890, "model": "test-model", "choices": [{"index": 0, "message": {"role": "assistant", "content": "Test response"}, "finish_reason": "stop"}], "usage": {"prompt_tokens": 10, "completion_tokens": 5, "total_tokens": 15}}' + + async def mock_post(*args, **kwargs): + return mock_response + + mock_client.post = mock_post + mock_get_async_httpx_client.return_value = mock_client + + try: + await litellm.acompletion( + model="hosted_vllm/test-model", + messages=[{"role": "user", "content": "Hello"}], + api_base="https://test-vllm.example.com/v1", + ssl_verify=False, + ) + except Exception: + # Even if the response parsing fails, we just need to verify + # that the mock was called with the correct ssl_verify parameter + pass + + # Verify get_async_httpx_client was called with ssl_verify=False + mock_get_async_httpx_client.assert_called() + call_kwargs = mock_get_async_httpx_client.call_args[1] + + # Check that params contains ssl_verify=False + params = call_kwargs.get("params", {}) + assert ( + params.get("ssl_verify") is False + ), f"Expected ssl_verify=False in params, got {params}" + + +if __name__ == "__main__": + pytest.main([__file__, "-v", "-s"]) diff --git a/tests/test_litellm/llms/hosted_vllm/embedding/test_hosted_vllm_embedding_ssl_verify.py b/tests/test_litellm/llms/hosted_vllm/embedding/test_hosted_vllm_embedding_ssl_verify.py new file mode 100644 index 00000000000..bb911814c23 --- /dev/null +++ b/tests/test_litellm/llms/hosted_vllm/embedding/test_hosted_vllm_embedding_ssl_verify.py @@ -0,0 +1,140 @@ +""" +Test SSL verification for hosted_vllm provider embeddings. + +This test ensures that the ssl_verify parameter is properly passed through +to the HTTP client when using the hosted_vllm provider for embeddings. + +Issue: ssl_verify parameter was being ignored because hosted_vllm fell through +to the openai_like catch-all path in main.py, which doesn't pass ssl_verify to the HTTP client. +""" + +import os +import sys +from unittest.mock import MagicMock, patch + +import pytest + +sys.path.insert( + 0, os.path.abspath("../../../../..") +) # Adds the parent directory to the system path + +import litellm + + +class TestHostedVLLMEmbeddingSSLVerify: + """Test suite for SSL verification in hosted_vllm provider embeddings.""" + + @patch("litellm.llms.custom_httpx.llm_http_handler._get_httpx_client") + def test_hosted_vllm_embedding_ssl_verify_false_sync(self, mock_get_httpx_client): + """Test that ssl_verify=False is passed to the HTTP client for sync embedding calls.""" + # Setup mock client + mock_client = MagicMock() + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.headers = {"content-type": "application/json"} + mock_response.json.return_value = { + "object": "list", + "data": [ + { + "object": "embedding", + "index": 0, + "embedding": [0.1, 0.2, 0.3, 0.4, 0.5], + } + ], + "model": "text-embedding-model", + "usage": { + "prompt_tokens": 5, + "total_tokens": 5, + }, + } + mock_response.text = '{"object": "list", "data": [{"object": "embedding", "index": 0, "embedding": [0.1, 0.2, 0.3, 0.4, 0.5]}], "model": "text-embedding-model", "usage": {"prompt_tokens": 5, "total_tokens": 5}}' + mock_client.post.return_value = mock_response + mock_get_httpx_client.return_value = mock_client + + try: + litellm.embedding( + model="hosted_vllm/text-embedding-model", + input=["hello world"], + api_base="https://test-vllm.example.com/v1", + ssl_verify=False, + ) + except Exception: + # Even if the response parsing fails, we just need to verify + # that the mock was called with the correct ssl_verify parameter + pass + + # Verify _get_httpx_client was called with ssl_verify=False + mock_get_httpx_client.assert_called() + call_args = mock_get_httpx_client.call_args + + # Check that params contains ssl_verify=False + if call_args[0]: + # Positional argument + params = call_args[0][0] + else: + # Keyword argument + params = call_args[1].get("params", {}) + + assert ( + params.get("ssl_verify") is False + ), f"Expected ssl_verify=False in params, got {params}" + + @patch("litellm.llms.custom_httpx.llm_http_handler.get_async_httpx_client") + @pytest.mark.asyncio + async def test_hosted_vllm_embedding_ssl_verify_false_async( + self, mock_get_async_httpx_client + ): + """Test that ssl_verify=False is passed to the HTTP client for async embedding calls.""" + # Setup mock async client + mock_client = MagicMock() + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.headers = {"content-type": "application/json"} + mock_response.json.return_value = { + "object": "list", + "data": [ + { + "object": "embedding", + "index": 0, + "embedding": [0.1, 0.2, 0.3, 0.4, 0.5], + } + ], + "model": "text-embedding-model", + "usage": { + "prompt_tokens": 5, + "total_tokens": 5, + }, + } + mock_response.text = '{"object": "list", "data": [{"object": "embedding", "index": 0, "embedding": [0.1, 0.2, 0.3, 0.4, 0.5]}], "model": "text-embedding-model", "usage": {"prompt_tokens": 5, "total_tokens": 5}}' + + async def mock_post(*args, **kwargs): + return mock_response + + mock_client.post = mock_post + mock_get_async_httpx_client.return_value = mock_client + + try: + await litellm.aembedding( + model="hosted_vllm/text-embedding-model", + input=["hello world"], + api_base="https://test-vllm.example.com/v1", + ssl_verify=False, + ) + except Exception: + # Even if the response parsing fails, we just need to verify + # that the mock was called with the correct ssl_verify parameter + pass + + # Verify get_async_httpx_client was called with ssl_verify=False + mock_get_async_httpx_client.assert_called() + call_kwargs = mock_get_async_httpx_client.call_args[1] + + # Check that params contains ssl_verify=False + params = call_kwargs.get("params", {}) + assert ( + params.get("ssl_verify") is False + ), f"Expected ssl_verify=False in params, got {params}" + + +if __name__ == "__main__": + pytest.main([__file__, "-v", "-s"]) diff --git a/tests/test_litellm/llms/openai/test_gpt5_transformation.py b/tests/test_litellm/llms/openai/test_gpt5_transformation.py index fd25d302d07..386f264a4dd 100644 --- a/tests/test_litellm/llms/openai/test_gpt5_transformation.py +++ b/tests/test_litellm/llms/openai/test_gpt5_transformation.py @@ -20,6 +20,23 @@ def test_gpt5_supports_reasoning_effort(config: OpenAIConfig): assert "reasoning_effort" in config.get_supported_openai_params(model="gpt-5-mini") +def test_gpt5_chat_does_not_support_reasoning_effort(config: OpenAIConfig): + assert ( + "reasoning_effort" + not in config.get_supported_openai_params(model="gpt-5-chat-latest") + ) + + +def test_gpt5_chat_supports_temperature(config: OpenAIConfig): + params = config.map_openai_params( + non_default_params={"temperature": 0.3}, + optional_params={}, + model="gpt-5-chat-latest", + drop_params=False, + ) + assert params["temperature"] == 0.3 + + def test_gpt5_maps_max_tokens(config: OpenAIConfig): params = config.map_openai_params( non_default_params={"max_tokens": 10}, diff --git a/tests/test_litellm/llms/openai_like/embedding/__init__.py b/tests/test_litellm/llms/openai_like/embedding/__init__.py new file mode 100644 index 00000000000..2cb77227ed0 --- /dev/null +++ b/tests/test_litellm/llms/openai_like/embedding/__init__.py @@ -0,0 +1 @@ +# Test module for OpenAI-like embedding handler diff --git a/tests/test_litellm/llms/openai_like/embedding/test_openai_like_embedding.py b/tests/test_litellm/llms/openai_like/embedding/test_openai_like_embedding.py new file mode 100644 index 00000000000..a82fe776e1a --- /dev/null +++ b/tests/test_litellm/llms/openai_like/embedding/test_openai_like_embedding.py @@ -0,0 +1,378 @@ +""" +Test cases for OpenAI-like embedding handler +""" + +import json +from unittest.mock import MagicMock, Mock, patch + +import pytest + +from litellm.llms.openai_like.embedding.handler import OpenAILikeEmbeddingHandler +from litellm.types.utils import EmbeddingResponse + + +class TestOpenAILikeEmbeddingHandler: + """Test OpenAI-like embedding handler functionality""" + + def test_encoding_format_none_filtered_out(self): + """ + Test that encoding_format=None is filtered out from the request payload. + + According to OpenAI API spec, encoding_format should be omitted if not specified, + not sent as None or empty string. This prevents errors with providers like VLLM + that reject empty encoding_format values. + """ + handler = OpenAILikeEmbeddingHandler() + + # Mock the HTTP client + mock_client = MagicMock() + mock_response = Mock() + mock_response.json.return_value = { + "object": "list", + "data": [ + { + "object": "embedding", + "embedding": [0.1, 0.2, 0.3], + "index": 0 + } + ], + "model": "test-model", + "usage": { + "prompt_tokens": 5, + "total_tokens": 5 + } + } + mock_response.raise_for_status = Mock() + mock_client.post.return_value = mock_response + + # Mock logging object + mock_logging = MagicMock() + + # Call embedding with encoding_format=None + optional_params = {"encoding_format": None} + + with patch.object(handler, '_validate_environment', return_value=("http://test.com/v1/embeddings", {})): + response = handler.embedding( + model="test-model", + input=["test input"], + timeout=60.0, + logging_obj=mock_logging, + api_key="test-key", + api_base="http://test.com", + optional_params=optional_params, + client=mock_client + ) + + # Verify the request was made + assert mock_client.post.called + + # Get the data that was sent in the request + call_args = mock_client.post.call_args + sent_data = json.loads(call_args[1]['data']) + + # Assert that encoding_format is NOT in the sent data + assert "encoding_format" not in sent_data, ( + "encoding_format=None should be filtered out from the request payload" + ) + + # Assert that model and input are still present + assert sent_data["model"] == "test-model" + assert sent_data["input"] == ["test input"] + + def test_encoding_format_empty_string_filtered_out(self): + """ + Test that encoding_format="" (empty string) is filtered out from the request payload. + + This is the specific case mentioned in the issue where VLLM rejects empty string + encoding_format values with error: "unknown variant ``, expected float or base64" + """ + handler = OpenAILikeEmbeddingHandler() + + # Mock the HTTP client + mock_client = MagicMock() + mock_response = Mock() + mock_response.json.return_value = { + "object": "list", + "data": [ + { + "object": "embedding", + "embedding": [0.1, 0.2, 0.3], + "index": 0 + } + ], + "model": "test-model", + "usage": { + "prompt_tokens": 5, + "total_tokens": 5 + } + } + mock_response.raise_for_status = Mock() + mock_client.post.return_value = mock_response + + # Mock logging object + mock_logging = MagicMock() + + # Call embedding with encoding_format="" (empty string) + optional_params = {"encoding_format": ""} + + with patch.object(handler, '_validate_environment', return_value=("http://test.com/v1/embeddings", {})): + response = handler.embedding( + model="test-model", + input=["test input"], + timeout=60.0, + logging_obj=mock_logging, + api_key="test-key", + api_base="http://test.com", + optional_params=optional_params, + client=mock_client + ) + + # Verify the request was made + assert mock_client.post.called + + # Get the data that was sent in the request + call_args = mock_client.post.call_args + sent_data = json.loads(call_args[1]['data']) + + # Assert that encoding_format is NOT in the sent data + assert "encoding_format" not in sent_data, ( + "encoding_format='' (empty string) should be filtered out from the request payload" + ) + + def test_encoding_format_float_preserved(self): + """ + Test that encoding_format="float" is preserved in the request payload. + """ + handler = OpenAILikeEmbeddingHandler() + + # Mock the HTTP client + mock_client = MagicMock() + mock_response = Mock() + mock_response.json.return_value = { + "object": "list", + "data": [ + { + "object": "embedding", + "embedding": [0.1, 0.2, 0.3], + "index": 0 + } + ], + "model": "test-model", + "usage": { + "prompt_tokens": 5, + "total_tokens": 5 + } + } + mock_response.raise_for_status = Mock() + mock_client.post.return_value = mock_response + + # Mock logging object + mock_logging = MagicMock() + + # Call embedding with encoding_format="float" + optional_params = {"encoding_format": "float"} + + with patch.object(handler, '_validate_environment', return_value=("http://test.com/v1/embeddings", {})): + response = handler.embedding( + model="test-model", + input=["test input"], + timeout=60.0, + logging_obj=mock_logging, + api_key="test-key", + api_base="http://test.com", + optional_params=optional_params, + client=mock_client + ) + + # Verify the request was made + assert mock_client.post.called + + # Get the data that was sent in the request + call_args = mock_client.post.call_args + sent_data = json.loads(call_args[1]['data']) + + # Assert that encoding_format IS in the sent data with correct value + assert "encoding_format" in sent_data, ( + "encoding_format='float' should be preserved in the request payload" + ) + assert sent_data["encoding_format"] == "float" + + def test_encoding_format_base64_preserved(self): + """ + Test that encoding_format="base64" is preserved in the request payload. + """ + handler = OpenAILikeEmbeddingHandler() + + # Mock the HTTP client + mock_client = MagicMock() + mock_response = Mock() + mock_response.json.return_value = { + "object": "list", + "data": [ + { + "object": "embedding", + "embedding": [0.1, 0.2, 0.3], + "index": 0 + } + ], + "model": "test-model", + "usage": { + "prompt_tokens": 5, + "total_tokens": 5 + } + } + mock_response.raise_for_status = Mock() + mock_client.post.return_value = mock_response + + # Mock logging object + mock_logging = MagicMock() + + # Call embedding with encoding_format="base64" + optional_params = {"encoding_format": "base64"} + + with patch.object(handler, '_validate_environment', return_value=("http://test.com/v1/embeddings", {})): + response = handler.embedding( + model="test-model", + input=["test input"], + timeout=60.0, + logging_obj=mock_logging, + api_key="test-key", + api_base="http://test.com", + optional_params=optional_params, + client=mock_client + ) + + # Verify the request was made + assert mock_client.post.called + + # Get the data that was sent in the request + call_args = mock_client.post.call_args + sent_data = json.loads(call_args[1]['data']) + + # Assert that encoding_format IS in the sent data with correct value + assert "encoding_format" in sent_data, ( + "encoding_format='base64' should be preserved in the request payload" + ) + assert sent_data["encoding_format"] == "base64" + + def test_other_optional_params_preserved(self): + """ + Test that other optional parameters are preserved when encoding_format is filtered. + """ + handler = OpenAILikeEmbeddingHandler() + + # Mock the HTTP client + mock_client = MagicMock() + mock_response = Mock() + mock_response.json.return_value = { + "object": "list", + "data": [ + { + "object": "embedding", + "embedding": [0.1, 0.2, 0.3], + "index": 0 + } + ], + "model": "test-model", + "usage": { + "prompt_tokens": 5, + "total_tokens": 5 + } + } + mock_response.raise_for_status = Mock() + mock_client.post.return_value = mock_response + + # Mock logging object + mock_logging = MagicMock() + + # Call embedding with encoding_format=None and other params + optional_params = { + "encoding_format": None, + "dimensions": 512, + "user": "test-user" + } + + with patch.object(handler, '_validate_environment', return_value=("http://test.com/v1/embeddings", {})): + response = handler.embedding( + model="test-model", + input=["test input"], + timeout=60.0, + logging_obj=mock_logging, + api_key="test-key", + api_base="http://test.com", + optional_params=optional_params, + client=mock_client + ) + + # Verify the request was made + assert mock_client.post.called + + # Get the data that was sent in the request + call_args = mock_client.post.call_args + sent_data = json.loads(call_args[1]['data']) + + # Assert that encoding_format is NOT in the sent data + assert "encoding_format" not in sent_data + + # Assert that other parameters ARE preserved + assert sent_data["dimensions"] == 512 + assert sent_data["user"] == "test-user" + assert sent_data["model"] == "test-model" + assert sent_data["input"] == ["test input"] + + def test_no_optional_params(self): + """ + Test that the handler works correctly when no optional params are provided. + """ + handler = OpenAILikeEmbeddingHandler() + + # Mock the HTTP client + mock_client = MagicMock() + mock_response = Mock() + mock_response.json.return_value = { + "object": "list", + "data": [ + { + "object": "embedding", + "embedding": [0.1, 0.2, 0.3], + "index": 0 + } + ], + "model": "test-model", + "usage": { + "prompt_tokens": 5, + "total_tokens": 5 + } + } + mock_response.raise_for_status = Mock() + mock_client.post.return_value = mock_response + + # Mock logging object + mock_logging = MagicMock() + + # Call embedding with empty optional_params + optional_params = {} + + with patch.object(handler, '_validate_environment', return_value=("http://test.com/v1/embeddings", {})): + response = handler.embedding( + model="test-model", + input=["test input"], + timeout=60.0, + logging_obj=mock_logging, + api_key="test-key", + api_base="http://test.com", + optional_params=optional_params, + client=mock_client + ) + + # Verify the request was made + assert mock_client.post.called + + # Get the data that was sent in the request + call_args = mock_client.post.call_args + sent_data = json.loads(call_args[1]['data']) + + # Assert that only model and input are in the sent data + assert sent_data["model"] == "test-model" + assert sent_data["input"] == ["test input"] + assert "encoding_format" not in sent_data diff --git a/tests/test_litellm/llms/s3_vectors/__init__.py b/tests/test_litellm/llms/s3_vectors/__init__.py new file mode 100644 index 00000000000..d4b0c4d8550 --- /dev/null +++ b/tests/test_litellm/llms/s3_vectors/__init__.py @@ -0,0 +1 @@ +# S3 Vectors tests diff --git a/tests/test_litellm/llms/s3_vectors/vector_stores/__init__.py b/tests/test_litellm/llms/s3_vectors/vector_stores/__init__.py new file mode 100644 index 00000000000..231735c1de7 --- /dev/null +++ b/tests/test_litellm/llms/s3_vectors/vector_stores/__init__.py @@ -0,0 +1 @@ +# S3 Vectors vector store tests diff --git a/tests/test_litellm/llms/s3_vectors/vector_stores/test_s3_vectors_transformation.py b/tests/test_litellm/llms/s3_vectors/vector_stores/test_s3_vectors_transformation.py new file mode 100644 index 00000000000..3a84da31542 --- /dev/null +++ b/tests/test_litellm/llms/s3_vectors/vector_stores/test_s3_vectors_transformation.py @@ -0,0 +1,115 @@ +from unittest.mock import MagicMock, Mock + +import httpx +import pytest + +from litellm.llms.s3_vectors.vector_stores.transformation import ( + S3VectorsVectorStoreConfig, +) +from litellm.types.vector_stores import VectorStoreSearchResponse + + +class TestS3VectorsVectorStoreConfig: + def test_init(self): + """Test that S3VectorsVectorStoreConfig initializes correctly""" + config = S3VectorsVectorStoreConfig() + assert config is not None + + def test_get_supported_openai_params(self): + """Test that supported OpenAI params are returned""" + config = S3VectorsVectorStoreConfig() + params = config.get_supported_openai_params("test-model") + assert "max_num_results" in params + + def test_get_complete_url(self): + """Test URL generation for S3 Vectors""" + config = S3VectorsVectorStoreConfig() + litellm_params = {"aws_region_name": "us-west-2"} + url = config.get_complete_url(None, litellm_params) + assert url == "https://s3vectors.us-west-2.api.aws" + + def test_get_complete_url_missing_region(self): + """Test that missing region raises error""" + config = S3VectorsVectorStoreConfig() + litellm_params = {} + with pytest.raises(ValueError, match="aws_region_name is required"): + config.get_complete_url(None, litellm_params) + + @pytest.mark.skip(reason="Requires embedding API call, tested in integration tests") + def test_transform_search_request(self): + """Test search request transformation""" + # This test requires making an actual embedding API call + # It's better tested in integration tests + pass + + def test_transform_search_request_invalid_vector_store_id(self): + """Test that invalid vector_store_id format raises error""" + config = S3VectorsVectorStoreConfig() + mock_logging_obj = Mock() + mock_logging_obj.model_call_details = {} + + with pytest.raises( + ValueError, match="vector_store_id must be in format 'bucket_name:index_name'" + ): + config.transform_search_vector_store_request( + vector_store_id="invalid-format", + query="test query", + vector_store_search_optional_params={}, + api_base="https://s3vectors.us-west-2.api.aws", + litellm_logging_obj=mock_logging_obj, + litellm_params={}, + ) + + def test_transform_search_response(self): + """Test search response transformation""" + config = S3VectorsVectorStoreConfig() + mock_logging_obj = Mock() + mock_logging_obj.model_call_details = {"query": "test query"} + + mock_response = Mock(spec=httpx.Response) + mock_response.json.return_value = { + "vectors": [ + { + "distance": 0.05, # S3 Vectors returns distance, not score + "metadata": { + "source_text": "This is test content", + "chunk_index": "0", + "filename": "test.pdf", + }, + }, + { + "distance": 0.15, + "metadata": { + "source_text": "More test content", + "chunk_index": "1", + }, + }, + ] + } + mock_response.status_code = 200 + mock_response.headers = {} + + result = config.transform_search_vector_store_response( + mock_response, mock_logging_obj + ) + + # VectorStoreSearchResponse is a TypedDict, so check structure instead of isinstance + assert result["object"] == "vector_store.search_results.page" + assert result["search_query"] == "test query" + assert len(result["data"]) == 2 + # Score should be 1 - distance (cosine similarity) + assert result["data"][0]["score"] == 0.95 # 1 - 0.05 + assert result["data"][0]["content"][0]["text"] == "This is test content" + assert result["data"][0]["filename"] == "test.pdf" + assert result["data"][1]["score"] == 0.85 # 1 - 0.15 + assert result["data"][1]["content"][0]["text"] == "More test content" + + def test_map_openai_params(self): + """Test OpenAI parameter mapping""" + config = S3VectorsVectorStoreConfig() + non_default_params = {"max_num_results": 5} + optional_params = {} + + result = config.map_openai_params(non_default_params, optional_params, False) + + assert result["maxResults"] == 5 diff --git a/tests/test_litellm/llms/test_cache_control_and_reasoning.py b/tests/test_litellm/llms/test_cache_control_and_reasoning.py new file mode 100644 index 00000000000..468927cdd49 --- /dev/null +++ b/tests/test_litellm/llms/test_cache_control_and_reasoning.py @@ -0,0 +1,281 @@ +""" +Test cache_control and reasoning parameter support for MiniMax, GLM/ZAI, and OpenRouter. + +This test file verifies the fixes for Issue #19923: +- cache_control is preserved (not stripped) for MiniMax, GLM, and OpenRouter variants +- thinking parameter is supported for reasoning-capable models +- Model metadata correctly reflects capabilities +""" +import os +import sys + +import pytest + +sys.path.insert( + 0, os.path.abspath("../../..") +) # Adds the parent directory to the system path + +from litellm.llms.minimax.chat.transformation import MinimaxChatConfig +from litellm.llms.openrouter.chat.transformation import OpenrouterConfig +from litellm.llms.zai.chat.transformation import ZAIChatConfig + + +def test_minimax_preserves_cache_control_in_messages(): + """MiniMax should NOT strip cache_control from messages.""" + config = MinimaxChatConfig() + + messages = [ + { + "role": "system", + "content": "You are a helpful assistant.", + "cache_control": {"type": "ephemeral"}, + }, + { + "role": "user", + "content": "Hello, world!", + }, + ] + + transformed_messages, _ = config.remove_cache_control_flag_from_messages_and_tools( + model="minimax/MiniMax-M2.1", messages=messages + ) + + # cache_control should be preserved + assert transformed_messages[0].get("cache_control") == {"type": "ephemeral"} + + +def test_minimax_preserves_cache_control_in_tools(): + """MiniMax should NOT strip cache_control from tools.""" + config = MinimaxChatConfig() + + tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get weather information", + "parameters": {"type": "object", "properties": {}}, + }, + "cache_control": {"type": "ephemeral"}, + } + ] + + _, transformed_tools = config.remove_cache_control_flag_from_messages_and_tools( + model="minimax/MiniMax-M2.1", messages=[], tools=tools + ) + + # cache_control should be preserved + assert transformed_tools[0].get("cache_control") == {"type": "ephemeral"} + + +def test_minimax_supports_thinking_param(): + """MiniMax reasoning models should support thinking parameter.""" + config = MinimaxChatConfig() + + supported_params = config.get_supported_openai_params( + model="minimax/MiniMax-M2.1" + ) + + # thinking should be in supported params for reasoning models + assert "thinking" in supported_params + # reasoning_split should also be supported + assert "reasoning_split" in supported_params + + +def test_zai_preserves_cache_control_in_messages(): + """ZAI should NOT strip cache_control from messages.""" + config = ZAIChatConfig() + + messages = [ + { + "role": "system", + "content": "You are a helpful assistant.", + "cache_control": {"type": "ephemeral"}, + }, + { + "role": "user", + "content": "Hello, world!", + }, + ] + + transformed_messages, _ = config.remove_cache_control_flag_from_messages_and_tools( + model="zai/glm-4.7", messages=messages + ) + + # cache_control should be preserved + assert transformed_messages[0].get("cache_control") == {"type": "ephemeral"} + + +def test_zai_preserves_cache_control_in_tools(): + """ZAI should NOT strip cache_control from tools.""" + config = ZAIChatConfig() + + tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get weather information", + "parameters": {"type": "object", "properties": {}}, + }, + "cache_control": {"type": "ephemeral"}, + } + ] + + _, transformed_tools = config.remove_cache_control_flag_from_messages_and_tools( + model="zai/glm-4.7", messages=[], tools=tools + ) + + # cache_control should be preserved + assert transformed_tools[0].get("cache_control") == {"type": "ephemeral"} + + +def test_zai_supports_thinking_param_for_reasoning_models(): + """ZAI reasoning models (glm-4.7, glm-4.6) should support thinking parameter.""" + config = ZAIChatConfig() + + # glm-4.7 supports reasoning + supported_params_47 = config.get_supported_openai_params(model="zai/glm-4.7") + assert "thinking" in supported_params_47 + + # glm-4.6 supports reasoning + supported_params_46 = config.get_supported_openai_params(model="zai/glm-4.6") + assert "thinking" in supported_params_46 + + +def test_openrouter_minimax_supports_cache_control(): + """OpenRouter should preserve cache_control for MiniMax models.""" + config = OpenrouterConfig() + + messages = [ + { + "role": "user", + "content": "Hello, world!", + "cache_control": {"type": "ephemeral"}, + } + ] + + # Test that cache_control is not removed + transformed_messages, _ = config.remove_cache_control_flag_from_messages_and_tools( + model="openrouter/minimax/minimax-m2", messages=messages + ) + + # The method should preserve cache_control for minimax models + assert transformed_messages[0].get("cache_control") == {"type": "ephemeral"} + + +def test_openrouter_glm_supports_cache_control(): + """OpenRouter should preserve cache_control for GLM models.""" + config = OpenrouterConfig() + + messages = [ + { + "role": "user", + "content": "Hello, world!", + "cache_control": {"type": "ephemeral"}, + } + ] + + # Test that cache_control is not removed for GLM models + transformed_messages, _ = config.remove_cache_control_flag_from_messages_and_tools( + model="openrouter/z-ai/glm-4.6", messages=messages + ) + + # The method should preserve cache_control for GLM models + assert transformed_messages[0].get("cache_control") == {"type": "ephemeral"} + + +def test_openrouter_deepseek_strips_cache_control(): + """OpenRouter should still strip cache_control for non-supported models.""" + config = OpenrouterConfig() + + messages = [ + { + "role": "user", + "content": "Hello, world!", + "cache_control": {"type": "ephemeral"}, + } + ] + + # DeepSeek doesn't support cache_control, so it should be stripped + transformed_messages, _ = config.remove_cache_control_flag_from_messages_and_tools( + model="openrouter/deepseek/deepseek-chat", messages=messages + ) + + # cache_control should be removed for non-supported models + assert transformed_messages[0].get("cache_control") is None + + +def test_openrouter_minimax_transform_moves_cache_control_to_content(): + """OpenRouter should move cache_control to content blocks for MiniMax.""" + config = OpenrouterConfig() + + messages = [ + { + "role": "user", + "content": "Analyze this data", + "cache_control": {"type": "ephemeral"}, + } + ] + + transformed_request = config.transform_request( + model="openrouter/minimax/minimax-m2", + messages=messages, + optional_params={}, + litellm_params={}, + headers={}, + ) + + # cache_control should be moved to content blocks + assert "messages" in transformed_request + user_message = transformed_request["messages"][0] + assert isinstance(user_message["content"], list) + assert user_message["content"][0]["cache_control"] == {"type": "ephemeral"} + # Message-level cache_control should be removed + assert "cache_control" not in user_message + + +def test_openrouter_glm_transform_moves_cache_control_to_content(): + """OpenRouter should move cache_control to content blocks for GLM.""" + config = OpenrouterConfig() + + messages = [ + { + "role": "user", + "content": "Analyze this data", + "cache_control": {"type": "ephemeral"}, + } + ] + + transformed_request = config.transform_request( + model="openrouter/z-ai/glm-4.6", + messages=messages, + optional_params={}, + litellm_params={}, + headers={}, + ) + + # cache_control should be moved to content blocks + assert "messages" in transformed_request + user_message = transformed_request["messages"][0] + assert isinstance(user_message["content"], list) + assert user_message["content"][0]["cache_control"] == {"type": "ephemeral"} + + +def test_openrouter_supports_thinking_param_for_reasoning_models(): + """OpenRouter should support thinking parameter for reasoning-capable models.""" + config = OpenrouterConfig() + + # Test MiniMax (supports reasoning) + supported_params_minimax = config.get_supported_openai_params( + model="openrouter/minimax/minimax-m2" + ) + assert "thinking" in supported_params_minimax + assert "reasoning_effort" in supported_params_minimax + + # Test GLM (supports reasoning) + supported_params_glm = config.get_supported_openai_params( + model="openrouter/z-ai/glm-4.6" + ) + assert "thinking" in supported_params_glm + assert "reasoning_effort" in supported_params_glm diff --git a/tests/test_litellm/llms/vercel_ai_gateway/embedding/__init__.py b/tests/test_litellm/llms/vercel_ai_gateway/embedding/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/test_litellm/llms/vercel_ai_gateway/embedding/test_vercel_ai_gateway_embedding.py b/tests/test_litellm/llms/vercel_ai_gateway/embedding/test_vercel_ai_gateway_embedding.py new file mode 100644 index 00000000000..af1e1df92fd --- /dev/null +++ b/tests/test_litellm/llms/vercel_ai_gateway/embedding/test_vercel_ai_gateway_embedding.py @@ -0,0 +1,218 @@ +import os +import sys +from unittest.mock import MagicMock, patch + +import httpx +import pytest + +sys.path.insert( + 0, os.path.abspath("../../../../..") +) # Adds the parent directory to the system path + +from litellm.llms.vercel_ai_gateway.embedding.transformation import ( + VercelAIGatewayEmbeddingConfig, +) +from litellm.llms.vercel_ai_gateway.common_utils import VercelAIGatewayException +from litellm.types.utils import EmbeddingResponse + + +def test_vercel_ai_gateway_embedding_get_complete_url(): + """Test URL generation for embeddings endpoint""" + config = VercelAIGatewayEmbeddingConfig() + + # Test with default API base + url = config.get_complete_url( + api_base=None, + api_key=None, + model="openai/text-embedding-3-small", + optional_params={}, + litellm_params={}, + ) + assert url == "https://ai-gateway.vercel.sh/v1/embeddings" + + # Test with custom API base + url = config.get_complete_url( + api_base="https://custom.vercel.sh/v1", + api_key=None, + model="openai/text-embedding-3-small", + optional_params={}, + litellm_params={}, + ) + assert url == "https://custom.vercel.sh/v1/embeddings" + + # Test with trailing slash + url = config.get_complete_url( + api_base="https://custom.vercel.sh/v1/", + api_key=None, + model="openai/text-embedding-3-small", + optional_params={}, + litellm_params={}, + ) + assert url == "https://custom.vercel.sh/v1/embeddings" + + +def test_vercel_ai_gateway_embedding_transform_request(): + """Test request transformation for embeddings""" + config = VercelAIGatewayEmbeddingConfig() + + # Test with string input + request = config.transform_embedding_request( + model="openai/text-embedding-3-small", + input="Hello world", + optional_params={}, + headers={}, + ) + assert request["model"] == "openai/text-embedding-3-small" + assert request["input"] == ["Hello world"] + + # Test with list input + request = config.transform_embedding_request( + model="openai/text-embedding-3-small", + input=["Hello", "World"], + optional_params={}, + headers={}, + ) + assert request["model"] == "openai/text-embedding-3-small" + assert request["input"] == ["Hello", "World"] + + # Test stripping vercel_ai_gateway/ prefix + request = config.transform_embedding_request( + model="vercel_ai_gateway/openai/text-embedding-3-small", + input="Hello", + optional_params={}, + headers={}, + ) + assert request["model"] == "openai/text-embedding-3-small" + + +def test_vercel_ai_gateway_embedding_transform_request_with_dimensions(): + """Test request transformation with dimensions parameter""" + config = VercelAIGatewayEmbeddingConfig() + + request = config.transform_embedding_request( + model="openai/text-embedding-3-small", + input="Hello world", + optional_params={"dimensions": 768}, + headers={}, + ) + assert request["model"] == "openai/text-embedding-3-small" + assert request["input"] == ["Hello world"] + assert request["dimensions"] == 768 + + +def test_vercel_ai_gateway_embedding_validate_environment(): + """Test header validation and setup""" + config = VercelAIGatewayEmbeddingConfig() + + headers = config.validate_environment( + headers={}, + model="openai/text-embedding-3-small", + messages=[], + optional_params={}, + litellm_params={}, + api_key="test_key", + ) + assert headers["Content-Type"] == "application/json" + assert headers["Authorization"] == "Bearer test_key" + + # Test with existing headers (should merge) + headers = config.validate_environment( + headers={"X-Custom": "value"}, + model="openai/text-embedding-3-small", + messages=[], + optional_params={}, + litellm_params={}, + api_key="test_key", + ) + assert headers["X-Custom"] == "value" + assert headers["Authorization"] == "Bearer test_key" + + +def test_vercel_ai_gateway_embedding_get_supported_params(): + """Test supported OpenAI parameters""" + config = VercelAIGatewayEmbeddingConfig() + supported = config.get_supported_openai_params("openai/text-embedding-3-small") + + assert "dimensions" in supported + assert "encoding_format" in supported + assert "timeout" in supported + assert "user" in supported + + +def test_vercel_ai_gateway_embedding_map_openai_params(): + """Test OpenAI parameter mapping""" + config = VercelAIGatewayEmbeddingConfig() + + optional_params = config.map_openai_params( + non_default_params={"dimensions": 768, "encoding_format": "float"}, + optional_params={}, + model="openai/text-embedding-3-small", + drop_params=False, + ) + assert optional_params["dimensions"] == 768 + assert optional_params["encoding_format"] == "float" + + +def test_vercel_ai_gateway_embedding_error_class(): + """Test error class creation""" + config = VercelAIGatewayEmbeddingConfig() + + error = config.get_error_class( + error_message="Test error", + status_code=400, + headers={"Content-Type": "application/json"}, + ) + + assert isinstance(error, VercelAIGatewayException) + assert error.message == "Test error" + assert error.status_code == 400 + + +def test_vercel_ai_gateway_embedding_transform_response(): + """Test response transformation""" + config = VercelAIGatewayEmbeddingConfig() + + mock_response = MagicMock(spec=httpx.Response) + mock_response.text = '{"object":"list","data":[{"object":"embedding","index":0,"embedding":[0.1,0.2,0.3]}],"model":"openai/text-embedding-3-small","usage":{"prompt_tokens":2,"total_tokens":2}}' + mock_response.json.return_value = { + "object": "list", + "data": [{"object": "embedding", "index": 0, "embedding": [0.1, 0.2, 0.3]}], + "model": "openai/text-embedding-3-small", + "usage": {"prompt_tokens": 2, "total_tokens": 2}, + } + + mock_logging = MagicMock() + + response = config.transform_embedding_response( + model="openai/text-embedding-3-small", + raw_response=mock_response, + model_response=EmbeddingResponse(), + logging_obj=mock_logging, + api_key="test_key", + request_data={}, + optional_params={}, + litellm_params={}, + ) + + assert response is not None + mock_logging.post_call.assert_called_once() + + +def test_vercel_ai_gateway_embedding_env_vars(): + """Test environment variable handling""" + config = VercelAIGatewayEmbeddingConfig() + + with patch.dict( + os.environ, + { + "VERCEL_AI_GATEWAY_API_BASE": "https://env.vercel.sh/v1", + }, + ): + url = config.get_complete_url( + api_base=None, + api_key=None, + model="openai/text-embedding-3-small", + optional_params={}, + litellm_params={}, + ) + assert url == "https://env.vercel.sh/v1/embeddings" diff --git a/tests/test_litellm/llms/vertex_ai/context_caching/test_vertex_ai_context_caching.py b/tests/test_litellm/llms/vertex_ai/context_caching/test_vertex_ai_context_caching.py index e9d14d4e18f..a47d026c169 100644 --- a/tests/test_litellm/llms/vertex_ai/context_caching/test_vertex_ai_context_caching.py +++ b/tests/test_litellm/llms/vertex_ai/context_caching/test_vertex_ai_context_caching.py @@ -14,6 +14,7 @@ from litellm.litellm_core_utils.litellm_logging import Logging from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler, HTTPHandler from litellm.llms.vertex_ai.common_utils import VertexAIError from litellm.llms.vertex_ai.context_caching.vertex_ai_context_caching import ( + MAX_PAGINATION_PAGES, ContextCachingEndpoints, ) @@ -787,6 +788,358 @@ class TestContextCachingEndpoints: assert original_tools == self.sample_tools +class TestCheckCachePagination: + """Test pagination logic in check_cache and async_check_cache methods.""" + + def setup_method(self): + """Setup for each test method""" + self.context_caching = ContextCachingEndpoints() + self.mock_logging = MagicMock(spec=Logging) + self.mock_client = MagicMock(spec=HTTPHandler) + self.mock_async_client = MagicMock(spec=AsyncHTTPHandler) + + @pytest.mark.parametrize( + "custom_llm_provider", ["gemini", "vertex_ai", "vertex_ai_beta"] + ) + @patch.object(ContextCachingEndpoints, "_get_token_and_url_context_caching") + def test_check_cache_pagination_finds_cache_on_second_page( + self, mock_get_token_url, custom_llm_provider + ): + """Test that check_cache correctly handles pagination and finds cache on second page""" + # Setup + mock_get_token_url.return_value = ("token", "https://test-url.com") + cache_key_to_find = "target_cache_key" + + # Mock first page response (no match, has nextPageToken) + first_page_response = MagicMock() + first_page_response.json.return_value = { + "cachedContents": [ + {"name": "cache_1", "displayName": "cache_key_1"}, + {"name": "cache_2", "displayName": "cache_key_2"}, + ], + "nextPageToken": "token_page_2", + } + + # Mock second page response (has match, no nextPageToken) + second_page_response = MagicMock() + second_page_response.json.return_value = { + "cachedContents": [ + {"name": "cache_3", "displayName": cache_key_to_find}, + {"name": "cache_4", "displayName": "cache_key_4"}, + ] + } + + # Setup mock client to return different responses + self.mock_client.get.side_effect = [first_page_response, second_page_response] + + # Execute + result = self.context_caching.check_cache( + cache_key=cache_key_to_find, + client=self.mock_client, + headers={"Authorization": "Bearer token"}, + api_key="test_key", + api_base=None, + logging_obj=self.mock_logging, + custom_llm_provider=custom_llm_provider, + vertex_project="test_project", + vertex_location="us-central1", + vertex_auth_header="Bearer test-token", + ) + + # Assert + assert result == "cache_3" + assert self.mock_client.get.call_count == 2 + # Check that second call includes pageToken + second_call_url = self.mock_client.get.call_args_list[1].kwargs["url"] + assert "pageToken=token_page_2" in second_call_url + + @pytest.mark.parametrize( + "custom_llm_provider", ["gemini", "vertex_ai", "vertex_ai_beta"] + ) + @patch.object(ContextCachingEndpoints, "_get_token_and_url_context_caching") + def test_check_cache_pagination_stops_when_no_next_token( + self, mock_get_token_url, custom_llm_provider + ): + """Test that check_cache stops pagination when no nextPageToken is present""" + # Setup + mock_get_token_url.return_value = ("token", "https://test-url.com") + cache_key_to_find = "nonexistent_cache_key" + + # Mock response without nextPageToken + response = MagicMock() + response.json.return_value = { + "cachedContents": [ + {"name": "cache_1", "displayName": "cache_key_1"}, + {"name": "cache_2", "displayName": "cache_key_2"}, + ] + } + + self.mock_client.get.return_value = response + + # Execute + result = self.context_caching.check_cache( + cache_key=cache_key_to_find, + client=self.mock_client, + headers={"Authorization": "Bearer token"}, + api_key="test_key", + api_base=None, + logging_obj=self.mock_logging, + custom_llm_provider=custom_llm_provider, + vertex_project="test_project", + vertex_location="us-central1", + vertex_auth_header="Bearer test-token", + ) + + # Assert + assert result is None + assert self.mock_client.get.call_count == 1 + + @pytest.mark.parametrize( + "custom_llm_provider", ["gemini", "vertex_ai", "vertex_ai_beta"] + ) + @patch.object(ContextCachingEndpoints, "_get_token_and_url_context_caching") + def test_check_cache_pagination_multiple_pages( + self, mock_get_token_url, custom_llm_provider + ): + """Test that check_cache correctly iterates through multiple pages""" + # Setup + mock_get_token_url.return_value = ("token", "https://test-url.com") + cache_key_to_find = "target_cache_key" + + # Mock three pages + page1 = MagicMock() + page1.json.return_value = { + "cachedContents": [{"name": "cache_1", "displayName": "cache_key_1"}], + "nextPageToken": "token_page_2", + } + + page2 = MagicMock() + page2.json.return_value = { + "cachedContents": [{"name": "cache_2", "displayName": "cache_key_2"}], + "nextPageToken": "token_page_3", + } + + page3 = MagicMock() + page3.json.return_value = { + "cachedContents": [{"name": "cache_3", "displayName": cache_key_to_find}], + } + + self.mock_client.get.side_effect = [page1, page2, page3] + + # Execute + result = self.context_caching.check_cache( + cache_key=cache_key_to_find, + client=self.mock_client, + headers={"Authorization": "Bearer token"}, + api_key="test_key", + api_base=None, + logging_obj=self.mock_logging, + custom_llm_provider=custom_llm_provider, + vertex_project="test_project", + vertex_location="us-central1", + vertex_auth_header="Bearer test-token", + ) + + # Assert + assert result == "cache_3" + assert self.mock_client.get.call_count == 3 + + @pytest.mark.asyncio + @pytest.mark.parametrize( + "custom_llm_provider", ["gemini", "vertex_ai", "vertex_ai_beta"] + ) + @patch.object(ContextCachingEndpoints, "_get_token_and_url_context_caching") + async def test_async_check_cache_pagination_finds_cache_on_second_page( + self, mock_get_token_url, custom_llm_provider + ): + """Test that async_check_cache correctly handles pagination and finds cache on second page""" + # Setup + mock_get_token_url.return_value = ("token", "https://test-url.com") + cache_key_to_find = "target_cache_key" + + # Mock first page response (no match, has nextPageToken) + first_page_response = MagicMock() + first_page_response.json.return_value = { + "cachedContents": [ + {"name": "cache_1", "displayName": "cache_key_1"}, + {"name": "cache_2", "displayName": "cache_key_2"}, + ], + "nextPageToken": "token_page_2", + } + + # Mock second page response (has match, no nextPageToken) + second_page_response = MagicMock() + second_page_response.json.return_value = { + "cachedContents": [ + {"name": "cache_3", "displayName": cache_key_to_find}, + {"name": "cache_4", "displayName": "cache_key_4"}, + ] + } + + # Setup mock async client to return different responses + self.mock_async_client.get = AsyncMock( + side_effect=[first_page_response, second_page_response] + ) + + # Execute + result = await self.context_caching.async_check_cache( + cache_key=cache_key_to_find, + client=self.mock_async_client, + headers={"Authorization": "Bearer token"}, + api_key="test_key", + api_base=None, + logging_obj=self.mock_logging, + custom_llm_provider=custom_llm_provider, + vertex_project="test_project", + vertex_location="us-central1", + vertex_auth_header="Bearer test-token", + ) + + # Assert + assert result == "cache_3" + assert self.mock_async_client.get.call_count == 2 + # Check that second call includes pageToken + second_call_url = self.mock_async_client.get.call_args_list[1].kwargs["url"] + assert "pageToken=token_page_2" in second_call_url + + @pytest.mark.asyncio + @pytest.mark.parametrize( + "custom_llm_provider", ["gemini", "vertex_ai", "vertex_ai_beta"] + ) + @patch.object(ContextCachingEndpoints, "_get_token_and_url_context_caching") + async def test_async_check_cache_pagination_stops_when_no_next_token( + self, mock_get_token_url, custom_llm_provider + ): + """Test that async_check_cache stops pagination when no nextPageToken is present""" + # Setup + mock_get_token_url.return_value = ("token", "https://test-url.com") + cache_key_to_find = "nonexistent_cache_key" + + # Mock response without nextPageToken + response = MagicMock() + response.json.return_value = { + "cachedContents": [ + {"name": "cache_1", "displayName": "cache_key_1"}, + {"name": "cache_2", "displayName": "cache_key_2"}, + ] + } + + self.mock_async_client.get = AsyncMock(return_value=response) + + # Execute + result = await self.context_caching.async_check_cache( + cache_key=cache_key_to_find, + client=self.mock_async_client, + headers={"Authorization": "Bearer token"}, + api_key="test_key", + api_base=None, + logging_obj=self.mock_logging, + custom_llm_provider=custom_llm_provider, + vertex_project="test_project", + vertex_location="us-central1", + vertex_auth_header="Bearer test-token", + ) + + # Assert + assert result is None + assert self.mock_async_client.get.call_count == 1 + + @pytest.mark.parametrize( + "custom_llm_provider", ["gemini", "vertex_ai", "vertex_ai_beta"] + ) + @patch.object(ContextCachingEndpoints, "_get_token_and_url_context_caching") + def test_check_cache_pagination_max_pages_limit( + self, mock_get_token_url, custom_llm_provider + ): + """Test that pagination stops after MAX_PAGINATION_PAGES iterations""" + # Setup + mock_get_token_url.return_value = ("token", "https://test-url.com") + cache_key_to_find = "nonexistent_cache_key" + + # Create mock response that always has nextPageToken (infinite pagination scenario) + def create_page_response(page_num): + response = MagicMock() + response.json.return_value = { + "cachedContents": [ + {"name": f"cache_{page_num}", "displayName": f"key_{page_num}"} + ], + "nextPageToken": f"token_page_{page_num + 1}", + } + return response + + # Create MAX_PAGINATION_PAGES responses, each with a nextPageToken + self.mock_client.get.side_effect = [ + create_page_response(i) for i in range(MAX_PAGINATION_PAGES) + ] + + # Execute + result = self.context_caching.check_cache( + cache_key=cache_key_to_find, + client=self.mock_client, + headers={"Authorization": "Bearer token"}, + api_key="test_key", + api_base=None, + logging_obj=self.mock_logging, + custom_llm_provider=custom_llm_provider, + vertex_project="test_project", + vertex_location="us-central1", + vertex_auth_header="Bearer test-token", + ) + + # Assert - should return None after exhausting all pages without finding match + assert result is None + # Verify exactly MAX_PAGINATION_PAGES API calls were made (not more) + assert self.mock_client.get.call_count == MAX_PAGINATION_PAGES + + @pytest.mark.asyncio + @pytest.mark.parametrize( + "custom_llm_provider", ["gemini", "vertex_ai", "vertex_ai_beta"] + ) + @patch.object(ContextCachingEndpoints, "_get_token_and_url_context_caching") + async def test_async_check_cache_pagination_max_pages_limit( + self, mock_get_token_url, custom_llm_provider + ): + """Test that async pagination stops after MAX_PAGINATION_PAGES iterations""" + # Setup + mock_get_token_url.return_value = ("token", "https://test-url.com") + cache_key_to_find = "nonexistent_cache_key" + + # Create mock response that always has nextPageToken (infinite pagination scenario) + def create_page_response(page_num): + response = MagicMock() + response.json.return_value = { + "cachedContents": [ + {"name": f"cache_{page_num}", "displayName": f"key_{page_num}"} + ], + "nextPageToken": f"token_page_{page_num + 1}", + } + return response + + # Create MAX_PAGINATION_PAGES responses, each with a nextPageToken + self.mock_async_client.get = AsyncMock( + side_effect=[create_page_response(i) for i in range(MAX_PAGINATION_PAGES)] + ) + + # Execute + result = await self.context_caching.async_check_cache( + cache_key=cache_key_to_find, + client=self.mock_async_client, + headers={"Authorization": "Bearer token"}, + api_key="test_key", + api_base=None, + logging_obj=self.mock_logging, + custom_llm_provider=custom_llm_provider, + vertex_project="test_project", + vertex_location="us-central1", + vertex_auth_header="Bearer test-token", + ) + + # Assert - should return None after exhausting all pages without finding match + assert result is None + # Verify exactly MAX_PAGINATION_PAGES async API calls were made (not more) + assert self.mock_async_client.get.call_count == MAX_PAGINATION_PAGES + + class TestVertexAIGlobalLocation: """Test global location handling in context caching.""" diff --git a/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py b/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py index ac099a0168c..cb3b51acd69 100644 --- a/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py +++ b/tests/test_litellm/llms/vertex_ai/gemini/test_vertex_and_google_ai_studio_gemini.py @@ -2663,6 +2663,111 @@ def test_vertex_ai_single_tool_type_still_works(): assert tools[0]["code_execution"] == {} +def test_vertex_ai_openai_web_search_tool_transformation(): + """ + Test that OpenAI-style web_search and web_search_preview tools are transformed to googleSearch. + + This fixes the issue where passing OpenAI-style web search tools like: + {"type": "web_search"} or {"type": "web_search_preview"} + would be silently ignored (the request succeeds but grounding is not applied). + + The fix transforms these to Gemini's googleSearch tool. + + Input: + value=[{"type": "web_search"}] + + Expected Output: + tools=[{"googleSearch": {}}] + """ + v = VertexGeminiConfig() + optional_params = {} + + # Test web_search transformation + tools = v._map_function( + value=[{"type": "web_search"}], + optional_params=optional_params + ) + + assert len(tools) == 1, f"Expected 1 Tool object, got {len(tools)}" + assert "googleSearch" in tools[0], f"Expected googleSearch in tool, got {tools[0].keys()}" + assert tools[0]["googleSearch"] == {}, f"Expected empty googleSearch config, got {tools[0]['googleSearch']}" + + +def test_vertex_ai_openai_web_search_preview_tool_transformation(): + """ + Test that OpenAI-style web_search_preview tool is transformed to googleSearch. + + Input: + value=[{"type": "web_search_preview"}] + + Expected Output: + tools=[{"googleSearch": {}}] + """ + v = VertexGeminiConfig() + optional_params = {} + + # Test web_search_preview transformation + tools = v._map_function( + value=[{"type": "web_search_preview"}], + optional_params=optional_params + ) + + assert len(tools) == 1, f"Expected 1 Tool object, got {len(tools)}" + assert "googleSearch" in tools[0], f"Expected googleSearch in tool, got {tools[0].keys()}" + assert tools[0]["googleSearch"] == {}, f"Expected empty googleSearch config, got {tools[0]['googleSearch']}" + + +def test_vertex_ai_openai_web_search_with_function_tools(): + """ + Test that OpenAI-style web_search tool works alongside function tools. + + Input: + value=[ + {"type": "web_search"}, + {"type": "function", "function": {"name": "get_weather", "description": "Get weather"}}, + ] + + Expected Output: + tools=[ + {"googleSearch": {}}, + {"function_declarations": [{"name": "get_weather", "description": "Get weather"}]}, + ] + """ + v = VertexGeminiConfig() + optional_params = {} + + tools = v._map_function( + value=[ + {"type": "web_search"}, + {"type": "function", "function": {"name": "get_weather", "description": "Get weather"}}, + ], + optional_params=optional_params + ) + + # Should have 2 separate Tool objects + assert len(tools) == 2, f"Expected 2 Tool objects, got {len(tools)}" + + # Find each tool type + search_tool = None + func_tool = None + + for tool in tools: + if "googleSearch" in tool: + search_tool = tool + elif "function_declarations" in tool: + func_tool = tool + + # Verify both tools are present + assert search_tool is not None, "googleSearch Tool should be present" + assert func_tool is not None, "function_declarations Tool should be present" + + # Verify googleSearch is empty config + assert search_tool["googleSearch"] == {} + + # Verify function declaration content + assert func_tool["function_declarations"][0]["name"] == "get_weather" + + def test_vertex_ai_multiple_function_declarations_grouped(): """ Test that multiple function declarations are grouped in ONE Tool object. diff --git a/tests/test_litellm/llms/vertex_ai/image_generation/test_vertex_ai_image_generation_transformation.py b/tests/test_litellm/llms/vertex_ai/image_generation/test_vertex_ai_image_generation_transformation.py index b91438b3cac..6736eaffebd 100644 --- a/tests/test_litellm/llms/vertex_ai/image_generation/test_vertex_ai_image_generation_transformation.py +++ b/tests/test_litellm/llms/vertex_ai/image_generation/test_vertex_ai_image_generation_transformation.py @@ -230,6 +230,47 @@ class TestVertexAIGeminiImageGenerationConfig: assert result.data[0].b64_json == "image1" assert result.data[1].b64_json == "image2" + def test_transform_image_generation_response_signature(self): + """Test response transformation includes thoughtSignature for Gemini 3 Pro""" + mock_response = MagicMock(spec=httpx.Response) + mock_response.status_code = 200 + mock_response.json.return_value = { + "candidates": [ + { + "content": { + "parts": [ + { + "inlineData": { + "mimeType": "image/png", + "data": "base64_encoded_image_data", + }, + "thoughtSignature": "test_signature_abc123", + } + ] + } + } + ] + } + mock_response.headers = {} + + from litellm.types.utils import ImageResponse + + model_response = ImageResponse() + result = self.config.transform_image_generation_response( + model="gemini-3-pro-image-preview", + raw_response=mock_response, + model_response=model_response, + logging_obj=MagicMock(), + request_data={}, + optional_params={}, + litellm_params={}, + encoding=None, + ) + + assert len(result.data) == 1 + assert result.data[0].b64_json == "base64_encoded_image_data" + assert result.data[0].provider_specific_fields["thought_signature"] == "test_signature_abc123" + class TestVertexAIImagenImageGenerationConfig: def setup_method(self): diff --git a/tests/test_litellm/llms/vertex_ai/test_vertex_ai_common_utils.py b/tests/test_litellm/llms/vertex_ai/test_vertex_ai_common_utils.py index 953f54e4a54..94323e06901 100644 --- a/tests/test_litellm/llms/vertex_ai/test_vertex_ai_common_utils.py +++ b/tests/test_litellm/llms/vertex_ai/test_vertex_ai_common_utils.py @@ -812,6 +812,36 @@ def test_get_vertex_model_id_from_url(): assert model_id is None +def test_get_vertex_model_id_from_url_with_slashes(): + """Test get_vertex_model_id_from_url with model names containing slashes (e.g., gcp/google/gemini-2.5-flash) + + Regression test for NVIDIA issue: custom model names with slashes in passthrough URLs + were being truncated (e.g., 'gcp/google/gemini-2.5-flash' -> 'gcp'), causing access_groups + checks to fail. + """ + from litellm.llms.vertex_ai.common_utils import get_vertex_model_id_from_url + + # Test with model name containing slashes: gcp/google/gemini-2.5-flash + url = "https://us-central1-aiplatform.googleapis.com/v1/projects/test-project/locations/us-central1/publishers/google/models/gcp/google/gemini-2.5-flash:generateContent" + model_id = get_vertex_model_id_from_url(url) + assert model_id == "gcp/google/gemini-2.5-flash" + + # Test with model name containing slashes: gcp/google/gemini-3-flash-preview + url = "https://us-central1-aiplatform.googleapis.com/v1/projects/test-project/locations/global/publishers/google/models/gcp/google/gemini-3-flash-preview:streamGenerateContent" + model_id = get_vertex_model_id_from_url(url) + assert model_id == "gcp/google/gemini-3-flash-preview" + + # Test with custom model path: custom/model + url = "https://us-central1-aiplatform.googleapis.com/v1/projects/test-project/locations/us-central1/publishers/google/models/custom/model:generateContent" + model_id = get_vertex_model_id_from_url(url) + assert model_id == "custom/model" + + # Test passthrough URL format (without host) + url = "v1/projects/my-project/locations/us-central1/publishers/google/models/gcp/google/gemini-2.5-flash:generateContent" + model_id = get_vertex_model_id_from_url(url) + assert model_id == "gcp/google/gemini-2.5-flash" + + def test_construct_target_url_with_version_prefix(): """Test construct_target_url with version prefixes""" from litellm.llms.vertex_ai.common_utils import construct_target_url diff --git a/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_anthropic_image_url_handling.py b/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_anthropic_image_url_handling.py index fca784342d7..3f014d65d4d 100644 --- a/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_anthropic_image_url_handling.py +++ b/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_anthropic_image_url_handling.py @@ -17,6 +17,7 @@ sys.path.insert( from litellm.litellm_core_utils.prompt_templates.factory import ( anthropic_messages_pt, + convert_to_anthropic_tool_result, create_anthropic_image_param, ) @@ -177,3 +178,194 @@ class TestCreateAnthropicImageParam: mock_convert_url.assert_not_called() assert result["source"]["type"] == "url" assert result["source"]["url"] == "https://example.com/image.jpg" + + +class TestToolMessageImageURLHandling: + """ + Test that tool messages with image_url are converted to base64 for Vertex AI. + + Issue: https://github.com/BerriAI/litellm/issues/19891 + """ + + @patch("litellm.litellm_core_utils.prompt_templates.factory.convert_url_to_base64") + def test_convert_to_anthropic_tool_result_with_force_base64( + self, mock_convert_url: MagicMock + ): + """ + Test that convert_to_anthropic_tool_result converts image URLs to base64 + when force_base64=True. + """ + mock_convert_url.return_value = "data:image/jpeg;base64,/9j/4AAQSkZJRg==" + + tool_message = { + "role": "tool", + "tool_call_id": "call_123", + "content": [ + { + "type": "image_url", + "image_url": {"url": "https://example.com/tool_result.jpg"}, + } + ], + } + + result = convert_to_anthropic_tool_result(tool_message, force_base64=True) + + mock_convert_url.assert_called_once_with(url="https://example.com/tool_result.jpg") + assert result["type"] == "tool_result" + assert result["tool_use_id"] == "call_123" + + # Check the image content is base64 + content = result["content"] + assert len(content) == 1 + assert content[0]["type"] == "image" + assert content[0]["source"]["type"] == "base64" + + @patch("litellm.litellm_core_utils.prompt_templates.factory.convert_url_to_base64") + def test_convert_to_anthropic_tool_result_without_force_base64( + self, mock_convert_url: MagicMock + ): + """ + Test that convert_to_anthropic_tool_result uses URL type when force_base64=False. + """ + tool_message = { + "role": "tool", + "tool_call_id": "call_456", + "content": [ + { + "type": "image_url", + "image_url": {"url": "https://example.com/image.jpg"}, + } + ], + } + + result = convert_to_anthropic_tool_result(tool_message, force_base64=False) + + mock_convert_url.assert_not_called() + assert result["type"] == "tool_result" + + # Check the image content uses URL type + content = result["content"] + assert len(content) == 1 + assert content[0]["type"] == "image" + assert content[0]["source"]["type"] == "url" + + @patch("litellm.litellm_core_utils.prompt_templates.factory.convert_url_to_base64") + def test_vertex_ai_tool_message_converts_image_to_base64( + self, mock_convert_url: MagicMock + ): + """ + Test full conversation with tool result containing image for Vertex AI. + The image URL should be converted to base64. + """ + mock_convert_url.return_value = "data:image/jpeg;base64,/9j/4AAQSkZJRg==" + + messages = [ + { + "role": "user", + "content": "Get me an image and describe it", + }, + { + "role": "assistant", + "content": None, + "tool_calls": [ + { + "id": "call_789", + "type": "function", + "function": { + "name": "get_image", + "arguments": "{}", + }, + } + ], + }, + { + "role": "tool", + "tool_call_id": "call_789", + "content": [ + { + "type": "image_url", + "image_url": {"url": "https://example.com/result.jpg"}, + } + ], + }, + ] + + result = anthropic_messages_pt( + messages=messages, + model="claude-sonnet-4", + llm_provider="vertex_ai", + ) + + # Verify convert_url_to_base64 was called for the tool result image + mock_convert_url.assert_called_once_with(url="https://example.com/result.jpg") + + # Find the tool_result in the converted messages + for msg in result: + if msg.get("role") == "user": + for content_item in msg.get("content", []): + if isinstance(content_item, dict) and content_item.get("type") == "tool_result": + tool_content = content_item.get("content", []) + for item in tool_content: + if isinstance(item, dict) and item.get("type") == "image": + assert item["source"]["type"] == "base64" + return + pytest.fail("Could not find image in tool result") + + @patch("litellm.litellm_core_utils.prompt_templates.factory.convert_url_to_base64") + def test_regular_anthropic_tool_message_uses_url( + self, mock_convert_url: MagicMock + ): + """ + Test that regular Anthropic API uses URL type for tool result images. + """ + messages = [ + { + "role": "user", + "content": "Get me an image", + }, + { + "role": "assistant", + "content": None, + "tool_calls": [ + { + "id": "call_abc", + "type": "function", + "function": { + "name": "get_image", + "arguments": "{}", + }, + } + ], + }, + { + "role": "tool", + "tool_call_id": "call_abc", + "content": [ + { + "type": "image_url", + "image_url": {"url": "https://example.com/image.jpg"}, + } + ], + }, + ] + + result = anthropic_messages_pt( + messages=messages, + model="claude-sonnet-4", + llm_provider="anthropic", + ) + + # convert_url_to_base64 should NOT be called for regular Anthropic + mock_convert_url.assert_not_called() + + # Find the tool_result and verify URL type + for msg in result: + if msg.get("role") == "user": + for content_item in msg.get("content", []): + if isinstance(content_item, dict) and content_item.get("type") == "tool_result": + tool_content = content_item.get("content", []) + for item in tool_content: + if isinstance(item, dict) and item.get("type") == "image": + assert item["source"]["type"] == "url" + return + pytest.fail("Could not find image in tool result") diff --git a/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_transformation.py b/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_transformation.py index 7b60a0a3369..3e6c6f6740c 100644 --- a/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_transformation.py +++ b/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/anthropic/test_vertex_ai_partner_models_anthropic_transformation.py @@ -37,8 +37,9 @@ def test_get_supported_params_thinking(): def test_vertex_ai_anthropic_web_search_header_in_completion(): """Test that web search tool adds the required beta header for Vertex AI completion requests""" from unittest.mock import MagicMock, patch + from litellm.llms.anthropic.common_utils import AnthropicModelInfo - + # Create the config instance model_info = AnthropicModelInfo() @@ -259,3 +260,33 @@ def test_vertex_ai_anthropic_other_models_still_use_tools(): assert "tools" in result_params, "Claude 3 Sonnet should also use tool-based structured output" assert "tool_choice" in result_params, "Tool choice should be present" assert "json_mode" in result_params, "JSON mode should be enabled" + +def test_vertex_ai_partner_models_anthropic_remove_prompt_caching_scope_beta_header(): + """ + Test that remove_unsupported_beta correctly filters out prompt-caching-scope-2026-01-05 + from the anthropic-beta headers. + """ + from litellm.llms.vertex_ai.vertex_ai_partner_models.anthropic.experimental_pass_through.transformation import ( + VertexAIPartnerModelsAnthropicMessagesConfig, + ) + + # This beta header should be removed + PROMPT_CACHING_BETA_HEADER = "prompt-caching-scope-2026-01-05" + headers = { + "anthropic-beta": f"other-feature,{PROMPT_CACHING_BETA_HEADER},web-search-2025-03-05" + } + + config = VertexAIPartnerModelsAnthropicMessagesConfig() + config.remove_unsupported_beta(headers) + + beta_header = headers.get("anthropic-beta") + assert PROMPT_CACHING_BETA_HEADER not in (beta_header or ""), \ + f"{PROMPT_CACHING_BETA_HEADER} should be filtered out" + assert "other-feature" in (beta_header or ""), \ + "Other non-excluded beta headers should remain" + assert "web-search-2025-03-05" in (beta_header or ""), \ + "Other non-excluded beta headers should remain" + # If prompt-caching was the only value, header should be removed completely + headers2 = {"anthropic-beta": PROMPT_CACHING_BETA_HEADER} + config.remove_unsupported_beta(headers2) + assert "anthropic-beta" not in headers2, "Header should be removed if no supported values remain" \ No newline at end of file diff --git a/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/qwen/__init__.py b/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/qwen/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/qwen/test_vertex_ai_qwen_global_endpoint.py b/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/qwen/test_vertex_ai_qwen_global_endpoint.py new file mode 100644 index 00000000000..6310431813b --- /dev/null +++ b/tests/test_litellm/llms/vertex_ai/vertex_ai_partner_models/qwen/test_vertex_ai_qwen_global_endpoint.py @@ -0,0 +1,263 @@ +""" +Tests for Vertex AI Qwen MaaS models that require the global endpoint. + +These tests verify that: +1. Qwen models are correctly identified as global-only models +2. The correct global URL is constructed (https://aiplatform.googleapis.com) +3. The completion() and responses() API work with Qwen models +""" + +import json +import os +import sys +from unittest.mock import MagicMock, patch + +import pytest + +sys.path.insert( + 0, os.path.abspath("../../../../../..") +) # Adds the parent directory to the system path + +import litellm +from litellm.llms.vertex_ai.common_utils import is_global_only_vertex_model +from litellm.llms.vertex_ai.vertex_llm_base import VertexBase +from litellm.types.llms.vertex_ai import VertexPartnerProvider + + +class TestQwenGlobalOnlyDetection: + """Test that Qwen models are correctly identified as global-only.""" + + @pytest.mark.parametrize( + "model", + [ + "vertex_ai/qwen/qwen3-next-80b-a3b-instruct-maas", + "vertex_ai/qwen/qwen3-next-80b-a3b-thinking-maas", + "vertex_ai/qwen/qwen3-235b-a22b-instruct-2507-maas", + "vertex_ai/qwen/qwen3-coder-480b-a35b-instruct-maas", + ], + ) + def test_qwen_models_are_global_only(self, model): + """Test that Qwen MaaS models are identified as global-only.""" + # This test requires the model_cost to have supported_regions: ["global"] + # If the model is not in model_cost, it should return False (fallback behavior) + result = is_global_only_vertex_model(model) + # Note: This will return True only if the model is in model_cost with supported_regions: ["global"] + # If running without the updated model_cost, this may return False + assert isinstance(result, bool) + + def test_non_global_model_returns_false(self): + """Test that non-global models return False.""" + result = is_global_only_vertex_model("vertex_ai/gemini-1.5-pro") + assert result is False + + def test_unknown_model_returns_false(self): + """Test that unknown models return False (fallback behavior).""" + result = is_global_only_vertex_model("vertex_ai/unknown-model-xyz") + assert result is False + + +class TestVertexBaseGetVertexRegion: + """Test the get_vertex_region method.""" + + def test_global_only_model_returns_global(self): + """Test that global-only models return 'global' regardless of input.""" + vertex_base = VertexBase() + + with patch( + "litellm.llms.vertex_ai.vertex_llm_base.is_global_only_vertex_model", + return_value=True, + ): + result = vertex_base.get_vertex_region( + vertex_region="us-central1", + model="vertex_ai/qwen/qwen3-next-80b-a3b-instruct-maas", + ) + assert result == "global" + + def test_global_only_model_with_none_returns_global(self): + """Test that global-only models return 'global' even with None input.""" + vertex_base = VertexBase() + + with patch( + "litellm.llms.vertex_ai.vertex_llm_base.is_global_only_vertex_model", + return_value=True, + ): + result = vertex_base.get_vertex_region( + vertex_region=None, + model="vertex_ai/qwen/qwen3-next-80b-a3b-instruct-maas", + ) + assert result == "global" + + def test_non_global_model_uses_provided_region(self): + """Test that non-global models use the provided region.""" + vertex_base = VertexBase() + + with patch( + "litellm.llms.vertex_ai.vertex_llm_base.is_global_only_vertex_model", + return_value=False, + ): + result = vertex_base.get_vertex_region( + vertex_region="europe-west1", + model="vertex_ai/gemini-1.5-pro", + ) + assert result == "europe-west1" + + def test_non_global_model_fallback_to_us_central1(self): + """Test that non-global models with None region fallback to us-central1.""" + vertex_base = VertexBase() + + with patch( + "litellm.llms.vertex_ai.vertex_llm_base.is_global_only_vertex_model", + return_value=False, + ): + result = vertex_base.get_vertex_region( + vertex_region=None, + model="vertex_ai/gemini-1.5-pro", + ) + assert result == "us-central1" + + +class TestCreateVertexURLGlobal: + """Test that create_vertex_url handles global location correctly.""" + + def test_global_location_url_format(self): + """Test that global location produces correct URL without region prefix.""" + url = VertexBase.create_vertex_url( + vertex_location="global", + vertex_project="test-project", + partner=VertexPartnerProvider.llama, + stream=False, + model="qwen/qwen3-next-80b-a3b-instruct-maas", + ) + + # Global URL should NOT have region prefix + assert url.startswith("https://aiplatform.googleapis.com") + assert "global-aiplatform.googleapis.com" not in url + assert "/locations/global/" in url + + def test_regional_location_url_format(self): + """Test that regional location produces correct URL with region prefix.""" + url = VertexBase.create_vertex_url( + vertex_location="us-central1", + vertex_project="test-project", + partner=VertexPartnerProvider.llama, + stream=False, + model="openai/gpt-oss-20b-maas", + ) + + # Regional URL should have region prefix + assert url.startswith("https://us-central1-aiplatform.googleapis.com") + assert "/locations/us-central1/" in url + + +@pytest.mark.asyncio +async def test_vertex_ai_qwen_global_endpoint_url(): + """ + Test that Qwen models use the global endpoint URL. + """ + from litellm.llms.custom_httpx.http_handler import AsyncHTTPHandler + from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import ( + VertexLLM, + ) + + # Mock response + mock_response = MagicMock() + mock_response.status_code = 200 + mock_response.headers = {} + mock_response.json.return_value = { + "id": "chatcmpl-qwen-test", + "object": "chat.completion", + "created": 1234567890, + "model": "qwen/qwen3-next-80b-a3b-instruct-maas", + "choices": [ + { + "index": 0, + "message": { + "role": "assistant", + "content": "Hello! How can I help you today?", + }, + "finish_reason": "stop", + } + ], + "usage": {"prompt_tokens": 10, "completion_tokens": 8, "total_tokens": 18}, + } + + client = AsyncHTTPHandler() + + async def mock_post_func(*args, **kwargs): + return mock_response + + with patch.object(client, "post", side_effect=mock_post_func) as mock_post, patch.object( + VertexLLM, "_ensure_access_token", return_value=("fake-token", "test-project") + ), patch( + "litellm.llms.vertex_ai.vertex_llm_base.is_global_only_vertex_model", + return_value=True, + ): + response = await litellm.acompletion( + model="vertex_ai/qwen/qwen3-next-80b-a3b-instruct-maas", + messages=[{"role": "user", "content": "Hello"}], + vertex_ai_project="test-project", + client=client, + ) + + # Verify the mock was called + mock_post.assert_called_once() + + # Get the call arguments + call_args = mock_post.call_args + called_url = call_args.kwargs["url"] + + # Verify the URL uses global endpoint (no region prefix) + assert called_url.startswith("https://aiplatform.googleapis.com") + assert "global-aiplatform.googleapis.com" not in called_url + assert "/locations/global/" in called_url + assert "/endpoints/openapi/chat/completions" in called_url + + # Verify response + assert response.model == "qwen/qwen3-next-80b-a3b-instruct-maas" + + +class TestGetSupportedRegions: + """Test that get_supported_regions correctly reads from model_cost.""" + + def test_get_supported_regions_returns_list(self): + """Test that get_supported_regions returns a list when model has supported_regions.""" + # Mock the model_cost to have supported_regions + with patch.dict( + litellm.model_cost, + { + "vertex_ai/qwen/qwen3-next-80b-a3b-instruct-maas": { + "supported_regions": ["global"], + "litellm_provider": "vertex_ai-qwen_models", + } + }, + ): + regions = litellm.utils.get_supported_regions( + model="vertex_ai/qwen/qwen3-next-80b-a3b-instruct-maas", + custom_llm_provider="vertex_ai", + ) + assert regions == ["global"] + + def test_get_supported_regions_returns_none_when_not_set(self): + """Test that get_supported_regions returns None when model doesn't have supported_regions.""" + # Mock the model_cost without supported_regions + with patch.dict( + litellm.model_cost, + { + "vertex_ai/gemini-1.5-pro": { + "litellm_provider": "vertex_ai", + } + }, + ): + regions = litellm.utils.get_supported_regions( + model="vertex_ai/gemini-1.5-pro", + custom_llm_provider="vertex_ai", + ) + assert regions is None + + def test_get_supported_regions_returns_none_for_unknown_model(self): + """Test that get_supported_regions returns None for unknown models.""" + regions = litellm.utils.get_supported_regions( + model="vertex_ai/unknown-model-xyz", + custom_llm_provider="vertex_ai", + ) + assert regions is None diff --git a/tests/test_litellm/llms/watsonx/test_watsonx_common_utils.py b/tests/test_litellm/llms/watsonx/test_watsonx_common_utils.py new file mode 100644 index 00000000000..8afa24d34a6 --- /dev/null +++ b/tests/test_litellm/llms/watsonx/test_watsonx_common_utils.py @@ -0,0 +1,242 @@ +import os +import sys +from unittest.mock import MagicMock, call, patch + +import pytest + +sys.path.insert( + 0, os.path.abspath("../../..") +) # Adds the parent directory to the system path + +from litellm.llms.watsonx.common_utils import generate_iam_token + + +class TestGenerateIAMToken: + """Tests for the generate_iam_token function, specifically testing API key fallback logic.""" + + @patch("litellm.llms.watsonx.common_utils.iam_token_cache") + @patch("litellm.llms.watsonx.common_utils.litellm.module_level_client") + @patch("litellm.llms.watsonx.common_utils.get_secret_str") + def test_generate_iam_token_with_watsonx_zenapikey( + self, mock_get_secret_str, mock_client, mock_cache + ): + """Test that WATSONX_ZENAPIKEY is used when it's the only key available.""" + # Setup mocks + mock_cache.get_cache.return_value = None # Cache miss + mock_get_secret_str.side_effect = lambda key: ( + "zen-api-key-12345" if key == "WATSONX_ZENAPIKEY" else None + ) + + mock_response = MagicMock() + mock_response.json.return_value = { + "access_token": "test-token-12345", + "expires_in": 3600, + } + mock_response.raise_for_status = MagicMock() + mock_client.post.return_value = mock_response + + # Call function without api_key parameter + result = generate_iam_token() + + # Verify get_secret_str was called with correct keys in order + # Note: get_watsonx_iam_url() also calls get_secret_str("WATSONX_IAM_URL") + calls = [ + call[0][0] + for call in mock_get_secret_str.call_args_list + if call[0][0] != "WATSONX_IAM_URL" + ] + assert "WX_API_KEY" in calls + assert "WATSONX_API_KEY" in calls + assert "WATSONX_APIKEY" in calls + assert "WATSONX_ZENAPIKEY" in calls + + # Verify the token was generated using WATSONX_ZENAPIKEY + assert result == "test-token-12345" + mock_client.post.assert_called_once() + call_kwargs = mock_client.post.call_args + assert call_kwargs.kwargs["data"]["apikey"] == "zen-api-key-12345" + + @patch("litellm.llms.watsonx.common_utils.iam_token_cache") + @patch("litellm.llms.watsonx.common_utils.litellm.module_level_client") + @patch("litellm.llms.watsonx.common_utils.get_secret_str") + def test_generate_iam_token_api_key_priority_order( + self, mock_get_secret_str, mock_client, mock_cache + ): + """Test that API keys are checked in the correct priority order.""" + # Setup mocks + mock_cache.get_cache.return_value = None # Cache miss + + # Test priority: WX_API_KEY > WATSONX_API_KEY > WATSONX_APIKEY > WATSONX_ZENAPIKEY + test_cases = [ + # (env_keys_set, expected_key_used, expected_calls) + ( + {"WX_API_KEY": "wx-key"}, + "wx-key", + ["WX_API_KEY"], # Should stop after first call + ), + ( + {"WATSONX_API_KEY": "watsonx-api-key"}, + "watsonx-api-key", + ["WX_API_KEY", "WATSONX_API_KEY"], # Should check WX_API_KEY first, then WATSONX_API_KEY + ), + ( + {"WATSONX_APIKEY": "watsonx-apikey"}, + "watsonx-apikey", + ["WX_API_KEY", "WATSONX_API_KEY", "WATSONX_APIKEY"], + ), + ( + {"WATSONX_ZENAPIKEY": "watsonx-zenapikey"}, + "watsonx-zenapikey", + ["WX_API_KEY", "WATSONX_API_KEY", "WATSONX_APIKEY", "WATSONX_ZENAPIKEY"], + ), + # Test that higher priority keys take precedence + ( + { + "WX_API_KEY": "wx-key", + "WATSONX_ZENAPIKEY": "zen-key", + }, + "wx-key", + ["WX_API_KEY"], # Should stop after first call + ), + ( + { + "WATSONX_API_KEY": "watsonx-api-key", + "WATSONX_ZENAPIKEY": "zen-key", + }, + "watsonx-api-key", + ["WX_API_KEY", "WATSONX_API_KEY"], # Should stop after WATSONX_API_KEY + ), + ( + { + "WATSONX_APIKEY": "watsonx-apikey", + "WATSONX_ZENAPIKEY": "zen-key", + }, + "watsonx-apikey", + ["WX_API_KEY", "WATSONX_API_KEY", "WATSONX_APIKEY"], + ), + ] + + for env_keys, expected_key, expected_calls in test_cases: + mock_get_secret_str.reset_mock() + mock_client.reset_mock() + mock_cache.reset_mock() + + # Configure mock to return values based on env_keys + def get_secret_side_effect(key): + return env_keys.get(key) + + mock_get_secret_str.side_effect = get_secret_side_effect + + mock_response = MagicMock() + mock_response.json.return_value = { + "access_token": "test-token", + "expires_in": 3600, + } + mock_response.raise_for_status = MagicMock() + mock_client.post.return_value = mock_response + + # Call function + result = generate_iam_token() + + # Verify the correct key was used + call_kwargs = mock_client.post.call_args + assert ( + call_kwargs.kwargs["data"]["apikey"] == expected_key + ), f"Expected {expected_key} but got {call_kwargs.kwargs['data']['apikey']} for env_keys: {env_keys}" + + # Verify get_secret_str was called with expected keys (checking short-circuit behavior) + # Note: get_watsonx_iam_url() also calls get_secret_str("WATSONX_IAM_URL"), so we filter that out + actual_calls = [ + call[0][0] + for call in mock_get_secret_str.call_args_list + if call[0][0] != "WATSONX_IAM_URL" + ] + assert ( + actual_calls == expected_calls + ), f"Expected calls {expected_calls} but got {actual_calls} for env_keys: {env_keys}" + + @patch("litellm.llms.watsonx.common_utils.iam_token_cache") + @patch("litellm.llms.watsonx.common_utils.litellm.module_level_client") + @patch("litellm.llms.watsonx.common_utils.get_secret_str") + def test_generate_iam_token_with_direct_api_key( + self, mock_get_secret_str, mock_client, mock_cache + ): + """Test that when api_key is passed directly, it's used instead of environment variables.""" + # Setup mocks + mock_cache.get_cache.return_value = None # Cache miss + mock_get_secret_str.return_value = "env-key-should-not-be-used" + + mock_response = MagicMock() + mock_response.json.return_value = { + "access_token": "test-token-12345", + "expires_in": 3600, + } + mock_response.raise_for_status = MagicMock() + mock_client.post.return_value = mock_response + + # Call function with direct api_key + direct_key = "direct-api-key-12345" + result = generate_iam_token(api_key=direct_key) + + # Verify get_secret_str was NOT called for API keys (since api_key was provided) + # Note: get_watsonx_iam_url() calls get_secret_str("WATSONX_IAM_URL"), which is expected + api_key_calls = [ + call[0][0] + for call in mock_get_secret_str.call_args_list + if call[0][0] not in ["WATSONX_IAM_URL"] + ] + assert ( + len(api_key_calls) == 0 + ), f"Expected no API key calls but got {api_key_calls}" + + # Verify the direct key was used + assert result == "test-token-12345" + call_kwargs = mock_client.post.call_args + assert call_kwargs.kwargs["data"]["apikey"] == direct_key + + @patch("litellm.llms.watsonx.common_utils.iam_token_cache") + @patch("litellm.llms.watsonx.common_utils.get_secret_str") + def test_generate_iam_token_no_api_key_raises_error( + self, mock_get_secret_str, mock_cache + ): + """Test that ValueError is raised when no API key is available.""" + # Setup mocks + mock_cache.get_cache.return_value = None # Cache miss + mock_get_secret_str.return_value = None # No keys available + + # Call function without api_key and expect ValueError + with pytest.raises(ValueError, match="API key is required"): + generate_iam_token() + + # Verify get_secret_str was called for all possible API keys + # Note: get_watsonx_iam_url() also calls get_secret_str("WATSONX_IAM_URL") + calls = [ + call[0][0] + for call in mock_get_secret_str.call_args_list + if call[0][0] != "WATSONX_IAM_URL" + ] + assert "WX_API_KEY" in calls + assert "WATSONX_API_KEY" in calls + assert "WATSONX_APIKEY" in calls + assert "WATSONX_ZENAPIKEY" in calls + + @patch("litellm.llms.watsonx.common_utils.iam_token_cache") + @patch("litellm.llms.watsonx.common_utils.litellm.module_level_client") + @patch("litellm.llms.watsonx.common_utils.get_secret_str") + def test_generate_iam_token_uses_cache( + self, mock_get_secret_str, mock_client, mock_cache + ): + """Test that cached token is returned when available.""" + # Setup mocks + cached_token = "cached-token-12345" + mock_cache.get_cache.return_value = cached_token + + # Call function + result = generate_iam_token() + + # Verify cached token was returned + assert result == cached_token + + # Verify get_secret_str and client.post were NOT called (cache hit) + mock_get_secret_str.assert_not_called() + mock_client.post.assert_not_called() diff --git a/tests/test_litellm/llms/xai/responses/test_xai_responses_transformation.py b/tests/test_litellm/llms/xai/responses/test_xai_responses_transformation.py index c0871d3b9b7..dc06d6a1b0d 100644 --- a/tests/test_litellm/llms/xai/responses/test_xai_responses_transformation.py +++ b/tests/test_litellm/llms/xai/responses/test_xai_responses_transformation.py @@ -6,16 +6,17 @@ transformations for the Responses API. Source: litellm/llms/xai/responses/transformation.py """ -import sys import os +import sys sys.path.insert(0, os.path.abspath("../../../../..")) import pytest -from litellm.types.utils import LlmProviders -from litellm.utils import ProviderConfigManager + from litellm.llms.xai.responses.transformation import XAIResponsesAPIConfig from litellm.types.llms.openai import ResponsesAPIOptionalRequestParams +from litellm.types.utils import LlmProviders +from litellm.utils import ProviderConfigManager class TestXAIResponsesAPITransformation: @@ -110,3 +111,209 @@ class TestXAIResponsesAPITransformation: ) assert url_with_slash == "https://api.x.ai/v1/responses", "Should handle trailing slash" + def test_web_search_tool_transformation(self): + """Test that web_search tools are transformed to XAI format""" + config = XAIResponsesAPIConfig() + + # Test with allowed_domains + params = ResponsesAPIOptionalRequestParams( + tools=[ + { + "type": "web_search", + "allowed_domains": ["wikipedia.org", "x.ai"], + "enable_image_understanding": True + } + ] + ) + + result = config.map_openai_params( + response_api_optional_params=params, + model="grok-4-1-fast", + drop_params=False + ) + + assert "tools" in result + assert len(result["tools"]) == 1 + tool = result["tools"][0] + assert tool["type"] == "web_search" + assert "filters" in tool + assert tool["filters"]["allowed_domains"] == ["wikipedia.org", "x.ai"] + assert tool["enable_image_understanding"] is True + + def test_web_search_search_context_size_removed(self): + """Test that search_context_size is removed from web_search tools""" + config = XAIResponsesAPIConfig() + + params = ResponsesAPIOptionalRequestParams( + tools=[ + { + "type": "web_search", + "search_context_size": "high" # Not supported by XAI + } + ] + ) + + result = config.map_openai_params( + response_api_optional_params=params, + model="grok-4-1-fast", + drop_params=False + ) + + assert "tools" in result + assert len(result["tools"]) == 1 + tool = result["tools"][0] + assert tool["type"] == "web_search" + assert "search_context_size" not in tool + + def test_web_search_excluded_domains(self): + """Test web_search with excluded_domains""" + config = XAIResponsesAPIConfig() + + params = ResponsesAPIOptionalRequestParams( + tools=[ + { + "type": "web_search", + "excluded_domains": ["example.com", "test.com"] + } + ] + ) + + result = config.map_openai_params( + response_api_optional_params=params, + model="grok-4-1-fast", + drop_params=False + ) + + tool = result["tools"][0] + assert "filters" in tool + assert tool["filters"]["excluded_domains"] == ["example.com", "test.com"] + + def test_web_search_domains_limit(self): + """Test that allowed_domains and excluded_domains are limited to 5""" + config = XAIResponsesAPIConfig() + + # Test with more than 5 allowed_domains + params = ResponsesAPIOptionalRequestParams( + tools=[ + { + "type": "web_search", + "allowed_domains": ["d1.com", "d2.com", "d3.com", "d4.com", "d5.com", "d6.com", "d7.com"] + } + ] + ) + + result = config.map_openai_params( + response_api_optional_params=params, + model="grok-4-1-fast", + drop_params=False + ) + + tool = result["tools"][0] + assert len(tool["filters"]["allowed_domains"]) == 7 + + def test_x_search_tool_transformation(self): + """Test that x_search tools are transformed correctly""" + config = XAIResponsesAPIConfig() + + params = ResponsesAPIOptionalRequestParams( + tools=[ + { + "type": "x_search", + "allowed_x_handles": ["elonmusk", "xai"], + "from_date": "2025-01-01", + "to_date": "2025-01-28", + "enable_image_understanding": True, + "enable_video_understanding": True + } + ] + ) + + result = config.map_openai_params( + response_api_optional_params=params, + model="grok-4-1-fast", + drop_params=False + ) + + assert "tools" in result + assert len(result["tools"]) == 1 + tool = result["tools"][0] + assert tool["type"] == "x_search" + assert tool["allowed_x_handles"] == ["elonmusk", "xai"] + assert tool["from_date"] == "2025-01-01" + assert tool["to_date"] == "2025-01-28" + assert tool["enable_image_understanding"] is True + assert tool["enable_video_understanding"] is True + + def test_x_search_excluded_handles(self): + """Test x_search with excluded_x_handles""" + config = XAIResponsesAPIConfig() + + params = ResponsesAPIOptionalRequestParams( + tools=[ + { + "type": "x_search", + "excluded_x_handles": ["spam_account", "bot_account"] + } + ] + ) + + result = config.map_openai_params( + response_api_optional_params=params, + model="grok-4-1-fast", + drop_params=False + ) + + tool = result["tools"][0] + assert tool["excluded_x_handles"] == ["spam_account", "bot_account"] + + def test_mixed_tools(self): + """Test transformation with multiple tool types""" + config = XAIResponsesAPIConfig() + + params = ResponsesAPIOptionalRequestParams( + tools=[ + { + "type": "code_interpreter", + "container": {"type": "auto"} + }, + { + "type": "web_search", + "allowed_domains": ["wikipedia.org"] + }, + { + "type": "x_search", + "allowed_x_handles": ["elonmusk"] + }, + { + "type": "function", + "name": "get_weather", + "description": "Get weather", + "parameters": {"type": "object"} + } + ] + ) + + result = config.map_openai_params( + response_api_optional_params=params, + model="grok-4-1-fast", + drop_params=False + ) + + assert len(result["tools"]) == 4 + + # Verify code_interpreter + assert result["tools"][0]["type"] == "code_interpreter" + assert "container" not in result["tools"][0] + + # Verify web_search + assert result["tools"][1]["type"] == "web_search" + assert "filters" in result["tools"][1] + + # Verify x_search + assert result["tools"][2]["type"] == "x_search" + assert result["tools"][2]["allowed_x_handles"] == ["elonmusk"] + + # Verify function tool is unchanged + assert result["tools"][3]["type"] == "function" + assert result["tools"][3]["name"] == "get_weather" + diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py index ecdc75ede52..f241b2aa0d5 100644 --- a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_server_manager.py @@ -1885,7 +1885,7 @@ class TestMCPServerManager: # Create mock client that tracks call_tool usage mock_client = AsyncMock() - async def mock_call_tool(params): + async def mock_call_tool(params, host_progress_callback=None): # Return a mock CallToolResult result = MagicMock(spec=CallToolResult) result.content = [{"type": "text", "text": "Tool executed successfully"}] diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_stale_session.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_stale_session.py new file mode 100644 index 00000000000..a447ee6af01 --- /dev/null +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_mcp_stale_session.py @@ -0,0 +1,289 @@ +""" +Tests for MCP stale session ID handling (Fixes #20292). + +When VSCode reconnects to LiteLLM's MCP endpoint after a reload, it sends a stale +`mcp-session-id` header. The session manager returns a 404 because the old session +was cleaned up. This test verifies that stale session IDs are detected and stripped +so a new session is created automatically. +""" + +import pytest +from unittest.mock import AsyncMock, MagicMock, patch + + +class TestStripStaleMcpSessionHeader: + """Unit tests for the _strip_stale_mcp_session_header helper.""" + + def test_strips_stale_session_id(self): + try: + from litellm.proxy._experimental.mcp_server.server import ( + _strip_stale_mcp_session_header, + ) + except ImportError: + pytest.skip("MCP server not available") + + scope = { + "headers": [ + (b"content-type", b"application/json"), + (b"mcp-session-id", b"stale-id"), + ], + } + mgr = MagicMock() + mgr._server_instances = {} # no active sessions + + _strip_stale_mcp_session_header(scope, mgr) + + header_names = [k for k, _ in scope["headers"]] + assert b"mcp-session-id" not in header_names + + def test_preserves_valid_session_id(self): + try: + from litellm.proxy._experimental.mcp_server.server import ( + _strip_stale_mcp_session_header, + ) + except ImportError: + pytest.skip("MCP server not available") + + scope = { + "headers": [ + (b"content-type", b"application/json"), + (b"mcp-session-id", b"valid-id"), + ], + } + mgr = MagicMock() + mgr._server_instances = {"valid-id": MagicMock()} + + _strip_stale_mcp_session_header(scope, mgr) + + header_names = [k for k, _ in scope["headers"]] + assert b"mcp-session-id" in header_names + + def test_no_op_when_no_session_header(self): + try: + from litellm.proxy._experimental.mcp_server.server import ( + _strip_stale_mcp_session_header, + ) + except ImportError: + pytest.skip("MCP server not available") + + scope = { + "headers": [ + (b"content-type", b"application/json"), + ], + } + mgr = MagicMock() + mgr._server_instances = {} + + _strip_stale_mcp_session_header(scope, mgr) + + assert len(scope["headers"]) == 1 + + def test_no_op_when_server_instances_missing(self): + """If _server_instances attr doesn't exist, don't crash.""" + try: + from litellm.proxy._experimental.mcp_server.server import ( + _strip_stale_mcp_session_header, + ) + except ImportError: + pytest.skip("MCP server not available") + + scope = { + "headers": [ + (b"mcp-session-id", b"some-id"), + ], + } + mgr = MagicMock(spec=[]) # no attributes + + _strip_stale_mcp_session_header(scope, mgr) + + # Should keep the header since we can't verify + header_names = [k for k, _ in scope["headers"]] + assert b"mcp-session-id" in header_names + + +@pytest.mark.asyncio +async def test_stale_mcp_session_id_is_stripped(): + """ + When the mcp-session-id header references a session that no longer exists, + handle_streamable_http_mcp should strip the header before forwarding the + request to the session manager so a fresh session is created. + """ + try: + from litellm.proxy._experimental.mcp_server.server import ( + handle_streamable_http_mcp, + session_manager, + ) + except ImportError: + pytest.skip("MCP server not available") + + stale_session_id = "stale-session-id-12345" + + scope = { + "type": "http", + "method": "POST", + "path": "/mcp", + "headers": [ + (b"content-type", b"application/json"), + (b"mcp-session-id", stale_session_id.encode()), + (b"authorization", b"Bearer test-key"), + ], + } + + receive = AsyncMock() + send = AsyncMock() + + # Simulate: session manager has NO sessions (the stale one was cleaned up) + captured_scope = {} + + async def mock_handle_request(s, r, se): + # Capture the scope that was actually passed + captured_scope.update(s) + + with patch( + "litellm.proxy._experimental.mcp_server.server.extract_mcp_auth_context", + new_callable=AsyncMock, + return_value=(MagicMock(), None, None, None, None, None), + ), patch( + "litellm.proxy._experimental.mcp_server.server.set_auth_context", + ), patch( + "litellm.proxy._experimental.mcp_server.server._SESSION_MANAGERS_INITIALIZED", + True, + ), patch.object( + session_manager, + "handle_request", + side_effect=mock_handle_request, + ), patch.object( + session_manager, + "_server_instances", + {}, # Empty dict = no active sessions + ): + await handle_streamable_http_mcp(scope, receive, send) + + # Verify the mcp-session-id header was stripped + header_names = [k for k, v in captured_scope.get("headers", [])] + assert b"mcp-session-id" not in header_names, ( + "Stale mcp-session-id header should have been stripped from the scope" + ) + + +@pytest.mark.asyncio +async def test_valid_mcp_session_id_is_preserved(): + """ + When the mcp-session-id header references a session that still exists, + handle_streamable_http_mcp should NOT strip the header. + """ + try: + from litellm.proxy._experimental.mcp_server.server import ( + handle_streamable_http_mcp, + session_manager, + ) + except ImportError: + pytest.skip("MCP server not available") + + valid_session_id = "valid-session-id-67890" + + scope = { + "type": "http", + "method": "POST", + "path": "/mcp", + "headers": [ + (b"content-type", b"application/json"), + (b"mcp-session-id", valid_session_id.encode()), + (b"authorization", b"Bearer test-key"), + ], + } + + receive = AsyncMock() + send = AsyncMock() + + captured_scope = {} + + async def mock_handle_request(s, r, se): + captured_scope.update(s) + + # Session manager HAS this session + mock_instances = {valid_session_id: MagicMock()} + + with patch( + "litellm.proxy._experimental.mcp_server.server.extract_mcp_auth_context", + new_callable=AsyncMock, + return_value=(MagicMock(), None, None, None, None, None), + ), patch( + "litellm.proxy._experimental.mcp_server.server.set_auth_context", + ), patch( + "litellm.proxy._experimental.mcp_server.server._SESSION_MANAGERS_INITIALIZED", + True, + ), patch.object( + session_manager, + "handle_request", + side_effect=mock_handle_request, + ), patch.object( + session_manager, + "_server_instances", + mock_instances, + ): + await handle_streamable_http_mcp(scope, receive, send) + + # Verify the mcp-session-id header was preserved + header_names = [k for k, v in captured_scope.get("headers", [])] + assert b"mcp-session-id" in header_names, ( + "Valid mcp-session-id header should have been preserved" + ) + + +@pytest.mark.asyncio +async def test_no_mcp_session_id_header_works_normally(): + """ + When no mcp-session-id header is present (initial connection), + handle_streamable_http_mcp should work without any issues. + """ + try: + from litellm.proxy._experimental.mcp_server.server import ( + handle_streamable_http_mcp, + session_manager, + ) + except ImportError: + pytest.skip("MCP server not available") + + scope = { + "type": "http", + "method": "POST", + "path": "/mcp", + "headers": [ + (b"content-type", b"application/json"), + (b"authorization", b"Bearer test-key"), + ], + } + + receive = AsyncMock() + send = AsyncMock() + + captured_scope = {} + + async def mock_handle_request(s, r, se): + captured_scope.update(s) + + with patch( + "litellm.proxy._experimental.mcp_server.server.extract_mcp_auth_context", + new_callable=AsyncMock, + return_value=(MagicMock(), None, None, None, None, None), + ), patch( + "litellm.proxy._experimental.mcp_server.server.set_auth_context", + ), patch( + "litellm.proxy._experimental.mcp_server.server._SESSION_MANAGERS_INITIALIZED", + True, + ), patch.object( + session_manager, + "handle_request", + side_effect=mock_handle_request, + ), patch.object( + session_manager, + "_server_instances", + {}, + ): + await handle_streamable_http_mcp(scope, receive, send) + + # Verify headers are unchanged (no mcp-session-id was added or anything weird) + header_names = [k for k, v in captured_scope.get("headers", [])] + assert b"mcp-session-id" not in header_names + assert b"content-type" in header_names diff --git a/tests/test_litellm/proxy/_experimental/mcp_server/test_semantic_tool_filter.py b/tests/test_litellm/proxy/_experimental/mcp_server/test_semantic_tool_filter.py new file mode 100644 index 00000000000..87c597c659b --- /dev/null +++ b/tests/test_litellm/proxy/_experimental/mcp_server/test_semantic_tool_filter.py @@ -0,0 +1,394 @@ +""" +Unit tests for MCP Semantic Tool Filtering + +Tests the core filtering logic that takes a long list of tools and returns +an ordered set of top K tools based on semantic similarity. +""" +import asyncio +import os +import sys +from unittest.mock import AsyncMock, Mock, patch + +import pytest + +sys.path.insert(0, os.path.abspath("../..")) + +from mcp.types import Tool as MCPTool + + +@pytest.mark.asyncio +async def test_semantic_filter_basic_filtering(): + """ + Test that the semantic filter correctly filters tools based on query. + + Given: 10 email/calendar tools + When: Query is "send an email" + Then: Email tools should rank higher than calendar tools + """ + from litellm.proxy._experimental.mcp_server.semantic_tool_filter import ( + SemanticMCPToolFilter, + ) + + # Create mock tools - mix of email and calendar tools + tools = [ + MCPTool(name="gmail_send", description="Send an email via Gmail", inputSchema={"type": "object"}), + MCPTool(name="outlook_send", description="Send an email via Outlook", inputSchema={"type": "object"}), + MCPTool(name="calendar_create", description="Create a calendar event", inputSchema={"type": "object"}), + MCPTool(name="calendar_update", description="Update a calendar event", inputSchema={"type": "object"}), + MCPTool(name="email_read", description="Read emails from inbox", inputSchema={"type": "object"}), + MCPTool(name="email_delete", description="Delete an email", inputSchema={"type": "object"}), + MCPTool(name="calendar_delete", description="Delete a calendar event", inputSchema={"type": "object"}), + MCPTool(name="email_search", description="Search for emails", inputSchema={"type": "object"}), + MCPTool(name="calendar_list", description="List calendar events", inputSchema={"type": "object"}), + MCPTool(name="email_forward", description="Forward an email to someone", inputSchema={"type": "object"}), + ] + + # Mock router that returns mock embeddings + from litellm.types.utils import Embedding, EmbeddingResponse + + mock_router = Mock() + + def mock_embedding_sync(*args, **kwargs): + return EmbeddingResponse( + data=[Embedding(embedding=[0.1] * 1536, index=0, object="embedding")], + model="text-embedding-3-small", + object="list", + usage={"prompt_tokens": 10, "total_tokens": 10} + ) + + async def mock_embedding_async(*args, **kwargs): + return mock_embedding_sync() + + mock_router.embedding = mock_embedding_sync + mock_router.aembedding = mock_embedding_async + + # Create filter + filter_instance = SemanticMCPToolFilter( + embedding_model="text-embedding-3-small", + litellm_router_instance=mock_router, + top_k=3, + similarity_threshold=0.3, + enabled=True, + ) + + # Build router with the tools before filtering + filter_instance._build_router(tools) + + # Filter tools with email-related query + filtered = await filter_instance.filter_tools( + query="send an email to john@example.com", + available_tools=tools, + ) + + # Assertions - validate filtering mechanics work + assert len(filtered) <= 3, f"Should return at most 3 tools (top_k), got {len(filtered)}" + assert len(filtered) > 0, "Should return at least some tools" + assert len(filtered) < len(tools), f"Should filter down from {len(tools)} tools, got {len(filtered)}" + + # Validate tools are actual MCPTool objects + for tool in filtered: + assert hasattr(tool, 'name'), "Filtered result should be MCPTool with name" + assert hasattr(tool, 'description'), "Filtered result should be MCPTool with description" + + filtered_names = [t.name for t in filtered] + print(f"✅ Successfully filtered {len(tools)} tools down to top {len(filtered)}: {filtered_names}") + print(f" Filter respects top_k parameter correctly") + + +@pytest.mark.asyncio +async def test_semantic_filter_top_k_limiting(): + """ + Test that the filter respects top_k parameter. + + Given: 20 tools + When: top_k=5 + Then: Should return at most 5 tools + """ + from litellm.proxy._experimental.mcp_server.semantic_tool_filter import ( + SemanticMCPToolFilter, + ) + + # Create 20 tools + tools = [ + MCPTool(name=f"tool_{i}", description=f"Tool number {i} for testing", inputSchema={"type": "object"}) + for i in range(20) + ] + + # Mock router + from litellm.types.utils import Embedding, EmbeddingResponse + + mock_router = Mock() + + def mock_embedding_sync(*args, **kwargs): + return EmbeddingResponse( + data=[Embedding(embedding=[0.1] * 1536, index=0, object="embedding")], + model="text-embedding-3-small", + object="list", + usage={"prompt_tokens": 10, "total_tokens": 10} + ) + + async def mock_embedding_async(*args, **kwargs): + return mock_embedding_sync() + + mock_router.embedding = mock_embedding_sync + mock_router.aembedding = mock_embedding_async + + # Create filter with top_k=5 + filter_instance = SemanticMCPToolFilter( + embedding_model="text-embedding-3-small", + litellm_router_instance=mock_router, + top_k=5, + similarity_threshold=0.3, + enabled=True, + ) + + # Build router with the tools before filtering + filter_instance._build_router(tools) + + # Filter tools + filtered = await filter_instance.filter_tools( + query="test query", + available_tools=tools, + ) + + # Should return at most 5 tools + assert len(filtered) <= 5, f"Expected at most 5 tools, got {len(filtered)}" + print(f"Returned {len(filtered)} tools out of {len(tools)} (top_k=5)") + + +@pytest.mark.asyncio +async def test_semantic_filter_disabled(): + """ + Test that when filter is disabled, all tools are returned. + """ + from litellm.proxy._experimental.mcp_server.semantic_tool_filter import ( + SemanticMCPToolFilter, + ) + + tools = [ + MCPTool(name=f"tool_{i}", description=f"Tool {i}", inputSchema={"type": "object"}) + for i in range(10) + ] + + mock_router = Mock() + + # Create disabled filter + filter_instance = SemanticMCPToolFilter( + embedding_model="text-embedding-3-small", + litellm_router_instance=mock_router, + top_k=3, + similarity_threshold=0.3, + enabled=False, # Disabled + ) + + # Filter tools + filtered = await filter_instance.filter_tools( + query="test query", + available_tools=tools, + ) + + # Should return all tools when disabled + assert len(filtered) == len(tools), f"Expected all {len(tools)} tools, got {len(filtered)}" + + +@pytest.mark.asyncio +async def test_semantic_filter_empty_tools(): + """ + Test that filter handles empty tool list gracefully. + """ + from litellm.proxy._experimental.mcp_server.semantic_tool_filter import ( + SemanticMCPToolFilter, + ) + + mock_router = Mock() + + filter_instance = SemanticMCPToolFilter( + embedding_model="text-embedding-3-small", + litellm_router_instance=mock_router, + top_k=3, + similarity_threshold=0.3, + enabled=True, + ) + + # Filter empty list + filtered = await filter_instance.filter_tools( + query="test query", + available_tools=[], + ) + + assert len(filtered) == 0, "Should return empty list for empty input" + + +@pytest.mark.asyncio +async def test_semantic_filter_extract_user_query(): + """ + Test that user query extraction works correctly from messages. + """ + from litellm.proxy._experimental.mcp_server.semantic_tool_filter import ( + SemanticMCPToolFilter, + ) + + mock_router = Mock() + + filter_instance = SemanticMCPToolFilter( + embedding_model="text-embedding-3-small", + litellm_router_instance=mock_router, + top_k=3, + similarity_threshold=0.3, + enabled=True, + ) + + # Test string content + messages = [ + {"role": "system", "content": "You are a helpful assistant"}, + {"role": "user", "content": "Send an email to john@example.com"}, + ] + + query = filter_instance.extract_user_query(messages) + assert query == "Send an email to john@example.com" + + # Test list content blocks + messages_with_blocks = [ + {"role": "user", "content": [ + {"type": "text", "text": "Hello, "}, + {"type": "text", "text": "send email please"}, + ]}, + ] + + query2 = filter_instance.extract_user_query(messages_with_blocks) + assert "Hello" in query2 and "send email" in query2 + + # Test no user messages + messages_no_user = [ + {"role": "system", "content": "System message only"}, + ] + + query3 = filter_instance.extract_user_query(messages_no_user) + assert query3 == "" + + +@pytest.mark.asyncio +async def test_semantic_filter_hook_triggers_on_completion(): + """ + Test that the hook triggers for completion requests with tools. + """ + from litellm.proxy._experimental.mcp_server.semantic_tool_filter import ( + SemanticMCPToolFilter, + ) + from litellm.proxy.hooks.mcp_semantic_filter import SemanticToolFilterHook + from litellm.types.utils import Embedding, EmbeddingResponse + + # Create mock filter + mock_router = Mock() + + def mock_embedding_sync(*args, **kwargs): + return EmbeddingResponse( + data=[Embedding(embedding=[0.1] * 1536, index=0, object="embedding")], + model="text-embedding-3-small", + object="list", + usage={"prompt_tokens": 10, "total_tokens": 10} + ) + + async def mock_embedding_async(*args, **kwargs): + return mock_embedding_sync() + + mock_router.embedding = mock_embedding_sync + mock_router.aembedding = mock_embedding_async + + filter_instance = SemanticMCPToolFilter( + embedding_model="text-embedding-3-small", + litellm_router_instance=mock_router, + top_k=3, + similarity_threshold=0.3, + enabled=True, + ) + + # Prepare data - completion request with tools + tools = [ + MCPTool(name=f"tool_{i}", description=f"Tool {i}", inputSchema={"type": "object"}) + for i in range(10) + ] + + # Build router with the tools before filtering + filter_instance._build_router(tools) + + # Create hook + hook = SemanticToolFilterHook(filter_instance) + + data = { + "model": "gpt-4", + "messages": [ + {"role": "user", "content": "Send an email"} + ], + "tools": tools, + "metadata": {}, # Hook needs metadata field to store filter stats + } + + # Mock user API key dict and cache + mock_user_api_key_dict = Mock() + mock_cache = Mock() + + # Call hook + result = await hook.async_pre_call_hook( + user_api_key_dict=mock_user_api_key_dict, + cache=mock_cache, + data=data, + call_type="completion", + ) + + # Assertions + assert result is not None, "Hook should return modified data" + assert "tools" in result, "Result should contain tools" + assert len(result["tools"]) < len(tools), f"Hook should filter tools, got {len(result['tools'])}/{len(tools)}" + + print(f"✅ Hook triggered correctly: {len(tools)} -> {len(result['tools'])} tools") + + + +@pytest.mark.asyncio +async def test_semantic_filter_hook_skips_no_tools(): + """ + Test that the hook does NOT trigger when there are no tools. + """ + from litellm.proxy._experimental.mcp_server.semantic_tool_filter import ( + SemanticMCPToolFilter, + ) + from litellm.proxy.hooks.mcp_semantic_filter import SemanticToolFilterHook + + # Create mock filter + mock_router = Mock() + filter_instance = SemanticMCPToolFilter( + embedding_model="text-embedding-3-small", + litellm_router_instance=mock_router, + top_k=3, + similarity_threshold=0.3, + enabled=True, + ) + + # Create hook + hook = SemanticToolFilterHook(filter_instance) + + # Prepare data - completion without tools + data = { + "model": "gpt-4", + "messages": [ + {"role": "user", "content": "Hello"} + ], + } + + # Mock user API key dict and cache + mock_user_api_key_dict = Mock() + mock_cache = Mock() + + # Call hook + result = await hook.async_pre_call_hook( + user_api_key_dict=mock_user_api_key_dict, + cache=mock_cache, + data=data, + call_type="completion", + ) + + # Should return None (no modification) + assert result is None, "Hook should skip requests without tools" + print("✅ Hook correctly skips requests without tools") + diff --git a/tests/test_litellm/proxy/auth/test_auth_checks.py b/tests/test_litellm/proxy/auth/test_auth_checks.py index 807559207e6..ebcd9676129 100644 --- a/tests/test_litellm/proxy/auth/test_auth_checks.py +++ b/tests/test_litellm/proxy/auth/test_auth_checks.py @@ -15,10 +15,10 @@ import pytest import litellm from litellm.proxy._types import ( CallInfo, + Litellm_EntityType, LiteLLM_ObjectPermissionTable, LiteLLM_TeamTable, LiteLLM_UserTable, - Litellm_EntityType, LitellmUserRoles, ProxyErrorTypes, ProxyException, @@ -28,6 +28,7 @@ from litellm.proxy._types import ( from litellm.proxy.auth.auth_checks import ( ExperimentalUIJWTToken, _can_object_call_vector_stores, + _get_fuzzy_user_object, _get_team_db_check, _virtual_key_max_budget_alert_check, _virtual_key_soft_budget_check, @@ -44,6 +45,24 @@ def set_salt_key(monkeypatch): monkeypatch.setenv("LITELLM_SALT_KEY", "sk-1234") +@pytest.fixture(autouse=True) +def reset_constants_module(): + """Reset constants module to ensure clean state before each test""" + import importlib + from litellm import constants + from litellm.proxy.auth import auth_checks + + # Reload modules before test + importlib.reload(constants) + importlib.reload(auth_checks) + + yield + + # Reload modules after test to clean up + importlib.reload(constants) + importlib.reload(auth_checks) + + @pytest.fixture def valid_sso_user_defined_values(): return LiteLLM_UserTable( @@ -131,6 +150,63 @@ def test_get_key_object_from_ui_hash_key_invalid(): assert key_object is None +def test_get_cli_jwt_auth_token_default_expiration(valid_sso_user_defined_values): + """Test generating CLI JWT token with default 24-hour expiration""" + token = ExperimentalUIJWTToken.get_cli_jwt_auth_token(valid_sso_user_defined_values) + + # Decrypt and verify token contents + decrypted_token = decrypt_value_helper( + token, key="ui_hash_key", exception_type="debug" + ) + assert decrypted_token is not None + token_data = json.loads(decrypted_token) + + assert token_data["user_id"] == "test_user" + assert token_data["user_role"] == LitellmUserRoles.PROXY_ADMIN.value + assert token_data["models"] == ["gpt-3.5-turbo"] + assert token_data["max_budget"] == litellm.max_ui_session_budget + + # Verify expiration time is set to 24 hours (default) + assert "expires" in token_data + expires = datetime.fromisoformat(token_data["expires"].replace("Z", "+00:00")) + assert expires > get_utc_datetime() + assert expires <= get_utc_datetime() + timedelta(hours=24, minutes=1) + assert expires >= get_utc_datetime() + timedelta(hours=23, minutes=59) + + +def test_get_cli_jwt_auth_token_custom_expiration( + valid_sso_user_defined_values, monkeypatch +): + """Test generating CLI JWT token with custom expiration via environment variable""" + import importlib + from litellm import constants + from litellm.proxy.auth import auth_checks + + # Set custom expiration to 48 hours + monkeypatch.setenv("LITELLM_CLI_JWT_EXPIRATION_HOURS", "48") + + # Reload the constants module to pick up the new env var + importlib.reload(constants) + # Also reload auth_checks to pick up the new constant value + importlib.reload(auth_checks) + + token = auth_checks.ExperimentalUIJWTToken.get_cli_jwt_auth_token(valid_sso_user_defined_values) + + # Decrypt and verify token contents + decrypted_token = decrypt_value_helper( + token, key="ui_hash_key", exception_type="debug" + ) + assert decrypted_token is not None + token_data = json.loads(decrypted_token) + + # Verify expiration time is set to 48 hours + assert "expires" in token_data + expires = datetime.fromisoformat(token_data["expires"].replace("Z", "+00:00")) + assert expires > get_utc_datetime() + timedelta(hours=47, minutes=59) + assert expires <= get_utc_datetime() + timedelta(hours=48, minutes=1) + + + @pytest.mark.asyncio async def test_default_internal_user_params_with_get_user_object(monkeypatch): """Test that default_internal_user_params is used when creating a new user via get_user_object""" @@ -1277,3 +1353,42 @@ async def test_virtual_key_max_budget_alert_check_scenarios( assert ( alert_triggered == expect_alert ), f"Expected alert_triggered to be {expect_alert} for spend={spend}, max_budget={max_budget}" + + +@pytest.mark.asyncio +async def test_get_fuzzy_user_object_case_insensitive_email(): + """Test that _get_fuzzy_user_object uses case-insensitive email lookup""" + # Setup mock Prisma client + mock_prisma = MagicMock() + mock_prisma.db = MagicMock() + mock_prisma.db.litellm_usertable = MagicMock() + + # Mock user data with mixed case email + test_user = LiteLLM_UserTable( + user_id="test_123", + sso_user_id=None, + user_email="Test@Example.com", # Mixed case in DB + organization_memberships=[], + max_budget=None, + ) + + # Test: SSO ID not found, find by email with different casing + mock_prisma.db.litellm_usertable.find_unique = AsyncMock(return_value=None) + mock_prisma.db.litellm_usertable.find_first = AsyncMock(return_value=test_user) + + # Search with lowercase email (different from DB) + result = await _get_fuzzy_user_object( + prisma_client=mock_prisma, + sso_user_id=None, + user_email="test@example.com", # Lowercase search + ) + + # Verify user was found despite case difference + assert result == test_user + + # Verify the query used case-insensitive mode + mock_prisma.db.litellm_usertable.find_first.assert_called_once() + call_args = mock_prisma.db.litellm_usertable.find_first.call_args + assert call_args.kwargs["where"]["user_email"]["equals"] == "test@example.com" + assert call_args.kwargs["where"]["user_email"]["mode"] == "insensitive" + assert call_args.kwargs["include"] == {"organization_memberships": True} diff --git a/tests/test_litellm/proxy/auth/test_auth_utils.py b/tests/test_litellm/proxy/auth/test_auth_utils.py index 62f9cc33b64..82920ce1d80 100644 --- a/tests/test_litellm/proxy/auth/test_auth_utils.py +++ b/tests/test_litellm/proxy/auth/test_auth_utils.py @@ -8,6 +8,7 @@ from litellm.proxy._types import UserAPIKeyAuth from litellm.proxy.auth.auth_utils import ( _get_customer_id_from_standard_headers, get_end_user_id_from_request_body, + get_model_from_request, get_key_model_rpm_limit, get_key_model_tpm_limit, ) @@ -186,3 +187,25 @@ class TestGetEndUserIdFromRequestBodyWithStandardHeaders: request_body=request_body, request_headers=headers ) assert result == "body-user" + + +def test_get_model_from_request_supports_google_model_names_with_slashes(): + assert ( + get_model_from_request( + request_data={}, + route="/v1beta/models/bedrock/claude-sonnet-3.7:generateContent", + ) + == "bedrock/claude-sonnet-3.7" + ) + assert ( + get_model_from_request( + request_data={}, + route="/models/hosted_vllm/gpt-oss-20b:generateContent", + ) + == "hosted_vllm/gpt-oss-20b" + ) + + +def test_get_model_from_request_vertex_passthrough_still_works(): + route = "/vertex_ai/v1/projects/p/locations/l/publishers/google/models/gemini-1.5-pro:generateContent" + assert get_model_from_request(request_data={}, route=route) == "gemini-1.5-pro" diff --git a/tests/test_litellm/proxy/auth/test_cli_auth.py b/tests/test_litellm/proxy/auth/test_cli_auth.py new file mode 100644 index 00000000000..2faf6436523 --- /dev/null +++ b/tests/test_litellm/proxy/auth/test_cli_auth.py @@ -0,0 +1,203 @@ +""" +Tests for litellm/proxy/client/cli/commands/auth.py + +This module tests the auth commands and their associated functionality. +""" + +import pytest +import requests +from unittest.mock import AsyncMock, patch, Mock, call +from litellm.proxy.client.cli.commands.auth import _normalize_teams, _poll_for_ready_data, _poll_for_authentication + +@pytest.mark.asyncio +async def test_normalize_teams_teams_only(): + """Test normalize teams helper function""" + teams = ["1", "2", "3"] + team_details = [] + result = _normalize_teams(teams, team_details) + assert result == [{"team_id": "1", "team_alias": None}, {"team_id": "2", "team_alias": None}, {"team_id": "3", "team_alias": None}] + +@pytest.mark.asyncio +async def test_normalize_teams_with_details_no_aliases(): + """Test normalize teams helper function""" + teams = ["4", "5", "6"] + team_details = [{"team_id": "1"}, {"team_id": "2"}, {"team_id": "3"}] + result = _normalize_teams(teams, team_details) + assert result == [{"team_id": "1", "team_alias": None}, {"team_id": "2", "team_alias": None}, {"team_id": "3", "team_alias": None}] + +@pytest.mark.asyncio +async def test_normalize_teams_with_details_with_aliases(): + """Test normalize teams helper function""" + teams = ["4", "5", "6"] + team_details = [{"team_id": "1", "team_alias": "A"}, {"team_id": "2", "team_alias": "B"}, {"team_id": "3", "team_alias": "C"}] + result = _normalize_teams(teams, team_details) + assert result == [{"team_id": "1", "team_alias": "A"}, {"team_id": "2", "team_alias": "B"}, {"team_id": "3", "team_alias": "C"}] + +@pytest.mark.asyncio +@patch("litellm.proxy.client.cli.commands.auth.requests.get", side_effect=[Mock(status_code=404)]) +@patch("litellm.proxy.client.cli.commands.auth.click.echo") +@patch("litellm.proxy.client.cli.commands.auth.time.sleep") +async def test_poll_for_ready_404(sleep_mock, click_mock, request_mock): + """Test poll_for_ready function""" + actual = _poll_for_ready_data("https://litellm.com", poll_interval=1, total_timeout=1, request_timeout=42) + assert actual is None + click_mock.assert_called_once_with("Polling error: HTTP 404") + request_mock.assert_called_once_with("https://litellm.com", timeout=42) + +@pytest.mark.asyncio +@patch("litellm.proxy.client.cli.commands.auth.requests.get", side_effect=[Mock(status_code=200, json=Mock(return_value={"status": "ready","json": "data"}))]) +@patch("litellm.proxy.client.cli.commands.auth.click.echo") +@patch("litellm.proxy.client.cli.commands.auth.time.sleep") +async def test_poll_for_ready_200_ready(sleep_mock, click_mock, request_mock): + """Test poll_for_ready function""" + actual = _poll_for_ready_data("https://litellm.com", poll_interval=1, total_timeout=1, request_timeout=42) + assert actual == {"status": "ready", "json": "data"} + click_mock.assert_not_called() + request_mock.assert_called_once_with("https://litellm.com", timeout=42) + sleep_mock.assert_not_called() + +@pytest.mark.asyncio +@patch("litellm.proxy.client.cli.commands.auth.requests.get", side_effect=[Mock(status_code=200, json=Mock(return_value={"status": "pending","json": "data"})), Mock(status_code=200, json=Mock(return_value={"status": "ready","json": "data"}))]) +@patch("litellm.proxy.client.cli.commands.auth.click.echo") +@patch("litellm.proxy.client.cli.commands.auth.time.sleep") +async def test_poll_for_ready_single_pending(sleep_mock, click_mock, request_mock): + """Test poll_for_ready function""" + actual = _poll_for_ready_data("https://litellm.com", poll_interval=1, total_timeout=2, request_timeout=42) + assert actual == {"status": "ready", "json": "data"} + click_mock.assert_not_called() + request_mock.assert_has_calls([ + call("https://litellm.com", timeout=42), + call("https://litellm.com", timeout=42) + ]) + sleep_mock.assert_called_once_with(1) + +@pytest.mark.asyncio +@patch("litellm.proxy.client.cli.commands.auth.requests.get", side_effect=[Mock(status_code=200, json=Mock(return_value={"status": "pending","json": "data"})), Mock(status_code=200, json=Mock(return_value={"status": "pending","json": "data"}))]) +@patch("litellm.proxy.client.cli.commands.auth.click.echo") +@patch("litellm.proxy.client.cli.commands.auth.time.sleep") +async def test_poll_for_ready_pending(sleep_mock, click_mock, request_mock): + """Test poll_for_ready function""" + actual = _poll_for_ready_data("https://litellm.com", poll_interval=1, total_timeout=2, request_timeout=42, pending_message="Pending message", pending_log_every=1) + assert actual is None + click_mock.assert_has_calls([ + call("Pending message"), + call("Pending message") + ]) + request_mock.assert_has_calls([ + call("https://litellm.com", timeout=42), + call("https://litellm.com", timeout=42) + ]) + sleep_mock.assert_has_calls([ + call(1), + call(1) + ]) + + +@pytest.mark.asyncio +@patch("litellm.proxy.client.cli.commands.auth.requests.get", side_effect=[requests.RequestException("ERROR"), + requests.RequestException("ERROR")]) +@patch("litellm.proxy.client.cli.commands.auth.click.echo") +@patch("litellm.proxy.client.cli.commands.auth.time.sleep") +async def test_poll_for_ready_connection_failure(sleep_mock, click_mock, request_mock): + """Test poll_for_ready function""" + actual = _poll_for_ready_data("https://litellm.com", poll_interval=1, total_timeout=2, request_timeout=42) + assert actual is None + click_mock.assert_called_once_with("Connection error (will retry): ERROR") + request_mock.assert_has_calls([ + call("https://litellm.com", timeout=42), + ]) + sleep_mock.assert_has_calls([ + call(1), + call(1) + ]) + + +@pytest.mark.asyncio +@patch("litellm.proxy.client.cli.commands.auth._handle_team_selection_during_polling") +@patch("litellm.proxy.client.cli.commands.auth._poll_for_ready_data", return_value=None) +@patch("litellm.proxy.client.cli.commands.auth.click.echo") +async def test_poll_for_authentication_no_data(click_mock, poll_mock, handle_mock): + """Test poll_for_authentication function""" + actual = _poll_for_authentication("https://litellm.com", "key-123") + assert actual is None + poll_mock.assert_called_once_with( + "https://litellm.com/sso/cli/poll/key-123", + pending_message="Still waiting for authentication...", + ) + handle_mock.assert_not_called() + click_mock.assert_not_called() + + +@pytest.mark.asyncio +@patch("litellm.proxy.client.cli.commands.auth._handle_team_selection_during_polling") +@patch("litellm.proxy.client.cli.commands.auth._poll_for_ready_data", return_value={"requires_team_selection": True, "teams": [], "team_details": []}) +@patch("litellm.proxy.client.cli.commands.auth.click.echo") +async def test_poll_for_authentication_no_teams(click_mock, poll_mock, handle_mock): + """Test poll_for_authentication function""" + actual = _poll_for_authentication("https://litellm.com", "key-123") + assert actual is None + poll_mock.assert_called_once_with( + "https://litellm.com/sso/cli/poll/key-123", + pending_message="Still waiting for authentication...", + ) + handle_mock.assert_not_called() + click_mock.assert_called_once() + assert "No teams available for selection." in click_mock.call_args[0][0] + + +@pytest.mark.asyncio +@patch("litellm.proxy.client.cli.commands.auth._handle_team_selection_during_polling", return_value="jwt-123") +@patch("litellm.proxy.client.cli.commands.auth._poll_for_ready_data", return_value={"requires_team_selection": True, "teams": [1, 2], "user_id": "user-123"}) +@patch("litellm.proxy.client.cli.commands.auth.click.echo") +async def test_poll_for_authentication_team_selection_success(click_mock, poll_mock, handle_mock): + """Test poll_for_authentication function""" + actual = _poll_for_authentication("https://litellm.com", "key-123") + assert actual == {"api_key": "jwt-123", "user_id": "user-123", "teams": [1, 2], "team_id": None} + poll_mock.assert_called_once_with( + "https://litellm.com/sso/cli/poll/key-123", + pending_message="Still waiting for authentication...", + ) + handle_mock.assert_called_once_with( + base_url="https://litellm.com", + key_id="key-123", + teams=[{"team_id": "1", "team_alias": None}, {"team_id": "2", "team_alias": None}], + ) + click_mock.assert_not_called() + + +@pytest.mark.asyncio +@patch("litellm.proxy.client.cli.commands.auth._handle_team_selection_during_polling", return_value=None) +@patch("litellm.proxy.client.cli.commands.auth._poll_for_ready_data", return_value={"requires_team_selection": True, "teams": ["team-1"], "user_id": "user-123"}) +@patch("litellm.proxy.client.cli.commands.auth.click.echo") +async def test_poll_for_authentication_team_selection_cancelled(click_mock, poll_mock, handle_mock): + """Test poll_for_authentication function""" + actual = _poll_for_authentication("https://litellm.com", "key-123") + assert actual is None + poll_mock.assert_called_once_with( + "https://litellm.com/sso/cli/poll/key-123", + pending_message="Still waiting for authentication...", + ) + handle_mock.assert_called_once_with( + base_url="https://litellm.com", + key_id="key-123", + teams=[{"team_id": "team-1", "team_alias": None}], + ) + click_mock.assert_called_once() + assert "Team selection cancelled" in click_mock.call_args[0][0] + + +@pytest.mark.asyncio +@patch("litellm.proxy.client.cli.commands.auth._handle_team_selection_during_polling") +@patch("litellm.proxy.client.cli.commands.auth._poll_for_ready_data", return_value={"key": "jwt-456", "user_id": "user-456", "teams": ["team-1"], "team_id": "team-1"}) +@patch("litellm.proxy.client.cli.commands.auth.click.echo") +async def test_poll_for_authentication_auto_assigned_team(click_mock, poll_mock, handle_mock): + """Test poll_for_authentication function""" + actual = _poll_for_authentication("https://litellm.com", "key-123") + assert actual == {"api_key": "jwt-456", "user_id": "user-456", "teams": ["team-1"], "team_id": "team-1"} + poll_mock.assert_called_once_with( + "https://litellm.com/sso/cli/poll/key-123", + pending_message="Still waiting for authentication...", + ) + handle_mock.assert_not_called() + click_mock.assert_called_once() + assert "Automatically assigned to team: team-1" in click_mock.call_args[0][0] diff --git a/tests/test_litellm/proxy/auth/test_login_utils.py b/tests/test_litellm/proxy/auth/test_login_utils.py index a0e29e06100..6d2a85522fa 100644 --- a/tests/test_litellm/proxy/auth/test_login_utils.py +++ b/tests/test_litellm/proxy/auth/test_login_utils.py @@ -6,7 +6,6 @@ to login_utils.py for better reusability. """ import os -from datetime import datetime, timezone, timedelta from unittest.mock import AsyncMock, MagicMock, patch import pytest @@ -22,7 +21,6 @@ from litellm.proxy._types import ( from litellm.proxy.auth.login_utils import ( LoginResult, authenticate_user, - expire_previous_ui_session_tokens, get_ui_credentials, ) @@ -288,31 +286,26 @@ async def test_authenticate_user_email_case_insensitive_login(): }, ): with patch( - "litellm.proxy.auth.login_utils.expire_previous_ui_session_tokens", + "litellm.proxy.auth.login_utils.generate_key_helper_fn", new_callable=AsyncMock, - return_value=None, - ): - with patch( - "litellm.proxy.auth.login_utils.generate_key_helper_fn", - new_callable=AsyncMock, - ) as mock_generate_key: - mock_generate_key.side_effect = [ - {"token": "token-1"}, - {"token": "token-2"}, - ] + ) as mock_generate_key: + mock_generate_key.side_effect = [ + {"token": "token-1"}, + {"token": "token-2"}, + ] - result_mixed = await authenticate_user( - username=login_email_mixed_case, - password=correct_password, - master_key=master_key, - prisma_client=mock_prisma_client, - ) - result_lower = await authenticate_user( - username=stored_email, - password=correct_password, - master_key=master_key, - prisma_client=mock_prisma_client, - ) + result_mixed = await authenticate_user( + username=login_email_mixed_case, + password=correct_password, + master_key=master_key, + prisma_client=mock_prisma_client, + ) + result_lower = await authenticate_user( + username=stored_email, + password=correct_password, + master_key=master_key, + prisma_client=mock_prisma_client, + ) assert result_mixed.user_id == result_lower.user_id == "test-user-123" assert result_mixed.user_email == result_lower.user_email == stored_email @@ -363,271 +356,6 @@ async def test_authenticate_user_database_required_for_admin(): os.environ["DATABASE_URL"] = original_db_url -@pytest.mark.asyncio -async def test_expire_previous_ui_session_tokens_none_prisma_client(): - """Test that function returns early when prisma_client is None""" - await expire_previous_ui_session_tokens("test-user", None) - # Should not raise any exception - - -@pytest.mark.asyncio -async def test_expire_previous_ui_session_tokens_only_litellm_dashboard_team(): - """Test that only tokens with team_id='litellm-dashboard' are expired""" - user_id = "test-user" - current_time = datetime.now(timezone.utc) - - # Create mock tokens with proper attributes - token1 = MagicMock() - token1.token = "token1" - token1.user_id = user_id - token1.team_id = "litellm-dashboard" - token1.blocked = None - token1.expires = current_time + timedelta(hours=1) - - token2 = MagicMock() - token2.token = "token2" - token2.user_id = user_id - token2.team_id = "other-team" - token2.blocked = None - token2.expires = current_time + timedelta(hours=1) - - def mock_find_many(**kwargs): - """Mock find_many that filters tokens based on query criteria""" - where_clause = kwargs.get("where", {}) - filtered_tokens = [] - - for token in [token1, token2]: - # Check user_id match - if token.user_id != where_clause.get("user_id"): - continue - # Check team_id match - if token.team_id != where_clause.get("team_id"): - continue - # Check blocked condition (None or False) - if token.blocked is not None and token.blocked is not False: - continue - # Check expires > current_time - if token.expires <= where_clause.get("expires", {}).get("gt"): - continue - filtered_tokens.append(token) - - return filtered_tokens - - mock_prisma_client = MagicMock() - mock_prisma_client.db.litellm_verificationtoken.find_many = AsyncMock(side_effect=mock_find_many) - mock_prisma_client.db.litellm_verificationtoken.update_many = AsyncMock() - - await expire_previous_ui_session_tokens(user_id, mock_prisma_client) - - # Should only call update_many with the litellm-dashboard token - mock_prisma_client.db.litellm_verificationtoken.update_many.assert_called_once_with( - where={"token": {"in": ["token1"]}}, - data={"blocked": True} - ) - - -@pytest.mark.asyncio -async def test_expire_previous_ui_session_tokens_blocks_null_and_false(): - """Test that tokens with blocked=None and blocked=False are both processed""" - user_id = "test-user" - current_time = datetime.now(timezone.utc) - - # Create mock tokens with proper attributes - token1 = MagicMock() - token1.token = "token1" - token1.user_id = user_id - token1.team_id = "litellm-dashboard" - token1.blocked = None - token1.expires = current_time + timedelta(hours=1) - - token2 = MagicMock() - token2.token = "token2" - token2.user_id = user_id - token2.team_id = "litellm-dashboard" - token2.blocked = False - token2.expires = current_time + timedelta(hours=1) - - token3 = MagicMock() - token3.token = "token3" - token3.user_id = user_id - token3.team_id = "litellm-dashboard" - token3.blocked = True # This should be ignored - token3.expires = current_time + timedelta(hours=1) - - def mock_find_many(**kwargs): - """Mock find_many that filters tokens based on query criteria""" - where_clause = kwargs.get("where", {}) - filtered_tokens = [] - - for token in [token1, token2, token3]: - # Check user_id match - if token.user_id != where_clause.get("user_id"): - continue - # Check team_id match - if token.team_id != where_clause.get("team_id"): - continue - # Check blocked condition (None or False) - if token.blocked is not None and token.blocked is not False: - continue - # Check expires > current_time - if token.expires <= where_clause.get("expires", {}).get("gt"): - continue - filtered_tokens.append(token) - - return filtered_tokens - - mock_prisma_client = MagicMock() - mock_prisma_client.db.litellm_verificationtoken.find_many = AsyncMock(side_effect=mock_find_many) - mock_prisma_client.db.litellm_verificationtoken.update_many = AsyncMock() - - await expire_previous_ui_session_tokens(user_id, mock_prisma_client) - - # Should only block token1 and token2 (not token3 which is already blocked) - mock_prisma_client.db.litellm_verificationtoken.update_many.assert_called_once_with( - where={"token": {"in": ["token1", "token2"]}}, - data={"blocked": True} - ) - - -@pytest.mark.asyncio -async def test_expire_previous_ui_session_tokens_only_non_expired(): - """Test that only non-expired tokens are processed""" - user_id = "test-user" - current_time = datetime.now(timezone.utc) - - # Create mock tokens with proper attributes - token1 = MagicMock() - token1.token = "token1" - token1.user_id = user_id - token1.team_id = "litellm-dashboard" - token1.blocked = None - token1.expires = current_time + timedelta(hours=1) # Not expired - - token2 = MagicMock() - token2.token = "token2" - token2.user_id = user_id - token2.team_id = "litellm-dashboard" - token2.blocked = None - token2.expires = current_time - timedelta(hours=1) # Already expired - - def mock_find_many(**kwargs): - """Mock find_many that filters tokens based on query criteria""" - where_clause = kwargs.get("where", {}) - filtered_tokens = [] - - for token in [token1, token2]: - # Check user_id match - if token.user_id != where_clause.get("user_id"): - continue - # Check team_id match - if token.team_id != where_clause.get("team_id"): - continue - # Check blocked condition (None or False) - if token.blocked is not None and token.blocked is not False: - continue - # Check expires > current_time - if token.expires <= where_clause.get("expires", {}).get("gt"): - continue - filtered_tokens.append(token) - - return filtered_tokens - - mock_prisma_client = MagicMock() - mock_prisma_client.db.litellm_verificationtoken.find_many = AsyncMock(side_effect=mock_find_many) - mock_prisma_client.db.litellm_verificationtoken.update_many = AsyncMock() - - await expire_previous_ui_session_tokens(user_id, mock_prisma_client) - - # Should only block the non-expired token - mock_prisma_client.db.litellm_verificationtoken.update_many.assert_called_once_with( - where={"token": {"in": ["token1"]}}, - data={"blocked": True} - ) - - -@pytest.mark.asyncio -async def test_expire_previous_ui_session_tokens_no_tokens_found(): - """Test behavior when no valid tokens are found""" - user_id = "test-user" - - mock_prisma_client = MagicMock() - mock_prisma_client.db.litellm_verificationtoken.find_many = AsyncMock(return_value=[]) - mock_prisma_client.db.litellm_verificationtoken.update_many = AsyncMock() - - await expire_previous_ui_session_tokens(user_id, mock_prisma_client) - - # Should not call update_many when no tokens found - mock_prisma_client.db.litellm_verificationtoken.update_many.assert_not_called() - - -@pytest.mark.asyncio -async def test_expire_previous_ui_session_tokens_filters_none_token(): - """Test that tokens with None token value are filtered out""" - user_id = "test-user" - current_time = datetime.now(timezone.utc) - - # Create mock tokens with proper attributes - token1 = MagicMock() - token1.token = "token1" - token1.user_id = user_id - token1.team_id = "litellm-dashboard" - token1.blocked = None - token1.expires = current_time + timedelta(hours=1) - - token2 = MagicMock() - token2.token = None # This should be filtered out in the token collection step - token2.user_id = user_id - token2.team_id = "litellm-dashboard" - token2.blocked = None - token2.expires = current_time + timedelta(hours=1) - - def mock_find_many(**kwargs): - """Mock find_many that filters tokens based on query criteria""" - where_clause = kwargs.get("where", {}) - filtered_tokens = [] - - for token in [token1, token2]: - # Check user_id match - if token.user_id != where_clause.get("user_id"): - continue - # Check team_id match - if token.team_id != where_clause.get("team_id"): - continue - # Check blocked condition (None or False) - if token.blocked is not None and token.blocked is not False: - continue - # Check expires > current_time - if token.expires <= where_clause.get("expires", {}).get("gt"): - continue - filtered_tokens.append(token) - - return filtered_tokens - - mock_prisma_client = MagicMock() - mock_prisma_client.db.litellm_verificationtoken.find_many = AsyncMock(side_effect=mock_find_many) - mock_prisma_client.db.litellm_verificationtoken.update_many = AsyncMock() - - await expire_previous_ui_session_tokens(user_id, mock_prisma_client) - - # Should only block token1 (token with None value should be filtered out) - mock_prisma_client.db.litellm_verificationtoken.update_many.assert_called_once_with( - where={"token": {"in": ["token1"]}}, - data={"blocked": True} - ) - - -@pytest.mark.asyncio -async def test_expire_previous_ui_session_tokens_exception_handling(): - """Test that exceptions during token expiry are silently handled""" - user_id = "test-user" - - mock_prisma_client = MagicMock() - mock_prisma_client.db.litellm_verificationtoken.find_many = AsyncMock(side_effect=Exception("Database error")) - - # Should not raise exception despite database error - await expire_previous_ui_session_tokens(user_id, mock_prisma_client) - - @pytest.mark.asyncio async def test_authenticate_user_admin_login_with_non_ascii_characters(): """Test admin login with non-ASCII characters in password (issue #19559)""" @@ -701,6 +429,80 @@ def test_authenticate_user_non_ascii_direct_comparison(): assert result is False +@pytest.mark.asyncio +async def test_authenticate_user_multiple_logins_generate_unique_tokens(): + """Test that multiple logins for the same user each generate unique tokens. + + This test verifies that users can have multiple concurrent UI sessions. + Previous UI session tokens should NOT be expired/blocked when a new session is created. + """ + master_key = "sk-1234" + ui_username = "admin" + ui_password = "sk-1234" + + mock_prisma_client = MagicMock() + mock_prisma_client.db.litellm_usertable.find_first = AsyncMock(return_value=None) + + with patch.dict( + os.environ, + { + "UI_USERNAME": ui_username, + "UI_PASSWORD": ui_password, + "DATABASE_URL": "postgresql://test:test@localhost/test", + }, + ): + with patch( + "litellm.proxy.auth.login_utils.generate_key_helper_fn", + new_callable=AsyncMock, + ) as mock_generate_key: + # Each login should generate a unique token + mock_generate_key.side_effect = [ + {"token": "session-token-1", "user_id": LITELLM_PROXY_ADMIN_NAME}, + {"token": "session-token-2", "user_id": LITELLM_PROXY_ADMIN_NAME}, + {"token": "session-token-3", "user_id": LITELLM_PROXY_ADMIN_NAME}, + ] + + with patch( + "litellm.proxy.auth.login_utils.user_update", + new_callable=AsyncMock, + return_value=None, + ): + with patch( + "litellm.proxy.auth.login_utils.get_secret_bool", + return_value=False, + ): + # Simulate multiple logins from the same user + result1 = await authenticate_user( + username=ui_username, + password=ui_password, + master_key=master_key, + prisma_client=mock_prisma_client, + ) + result2 = await authenticate_user( + username=ui_username, + password=ui_password, + master_key=master_key, + prisma_client=mock_prisma_client, + ) + result3 = await authenticate_user( + username=ui_username, + password=ui_password, + master_key=master_key, + prisma_client=mock_prisma_client, + ) + + # Each login should return a unique token + assert result1.key == "session-token-1" + assert result2.key == "session-token-2" + assert result3.key == "session-token-3" + + # All tokens should be different (concurrent sessions allowed) + assert len({result1.key, result2.key, result3.key}) == 3 + + # generate_key_helper_fn should be called 3 times (once per login) + assert mock_generate_key.call_count == 3 + + @pytest.mark.asyncio async def test_authenticate_user_database_login_with_non_ascii_password(): """Test database user login with non-ASCII characters in password (issue #19559)""" @@ -736,23 +538,18 @@ async def test_authenticate_user_database_login_with_non_ascii_password(): }, ): with patch( - "litellm.proxy.auth.login_utils.expire_previous_ui_session_tokens", + "litellm.proxy.auth.login_utils.generate_key_helper_fn", new_callable=AsyncMock, - return_value=None, - ): - with patch( - "litellm.proxy.auth.login_utils.generate_key_helper_fn", - new_callable=AsyncMock, - ) as mock_generate_key: - mock_generate_key.return_value = {"token": "token-123"} + ) as mock_generate_key: + mock_generate_key.return_value = {"token": "token-123"} - result = await authenticate_user( - username=user_email, - password=password_with_special_char, - master_key=master_key, - prisma_client=mock_prisma_client, - ) + result = await authenticate_user( + username=user_email, + password=password_with_special_char, + master_key=master_key, + prisma_client=mock_prisma_client, + ) - assert isinstance(result, LoginResult) - assert result.user_id == "test-user-123" - assert result.user_email == user_email + assert isinstance(result, LoginResult) + assert result.user_id == "test-user-123" + assert result.user_email == user_email diff --git a/tests/test_litellm/proxy/auth/test_route_checks.py b/tests/test_litellm/proxy/auth/test_route_checks.py index b8084906fa5..a745ac3de13 100644 --- a/tests/test_litellm/proxy/auth/test_route_checks.py +++ b/tests/test_litellm/proxy/auth/test_route_checks.py @@ -161,9 +161,11 @@ def test_virtual_key_llm_api_route_includes_passthrough_prefix(route): [ "/v1beta/models/gemini-2.5-flash:countTokens", "/v1beta/models/gemini-2.0-flash:generateContent", + "/v1beta/models/bedrock/claude-sonnet-3.7:generateContent", "/v1beta/models/gemini-1.5-pro:streamGenerateContent", "/models/gemini-2.5-flash:countTokens", "/models/gemini-2.0-flash:generateContent", + "/models/bedrock/claude-sonnet-3.7:generateContent", "/models/gemini-1.5-pro:streamGenerateContent", ], ) @@ -187,9 +189,11 @@ def test_virtual_key_llm_api_routes_allows_google_routes(route): "/v1beta/models/google-gemini-2-5-pro-code-reviewer-k8s:generateContent", "/v1beta/models/gemini-2.5-flash-exp:countTokens", "/v1beta/models/custom-model-name-123:streamGenerateContent", + "/v1beta/models/bedrock/claude-sonnet-3.7:generateContent", "/models/google-gemini-2-5-pro-code-reviewer-k8s:generateContent", "/models/gemini-2.5-flash-exp:countTokens", "/models/custom-model-name-123:streamGenerateContent", + "/models/bedrock/claude-sonnet-3.7:generateContent", ], ) def test_google_routes_with_dynamic_model_names_recognized_as_llm_api_route(route): diff --git a/tests/test_litellm/proxy/google_endpoints/test_interactions_agent_param.py b/tests/test_litellm/proxy/google_endpoints/test_interactions_agent_param.py new file mode 100644 index 00000000000..1063f59afb6 --- /dev/null +++ b/tests/test_litellm/proxy/google_endpoints/test_interactions_agent_param.py @@ -0,0 +1,75 @@ +""" +Test for interactions endpoint agent parameter handling. + +Tests that the /v1beta/interactions endpoint correctly extracts +the `agent` parameter as a fallback when `model` is not provided. +""" + +import pytest + + +class TestInteractionsAgentParameter: + """Test agent parameter handling in interactions endpoint.""" + + def test_agent_parameter_fallback_logic(self): + """ + Test the core logic: model or agent extraction. + + This tests the fix in endpoints.py line ~267: + model=data.get("model") or data.get("agent") + """ + # Case 1: Only agent provided (Deep Research use case) + data = { + "agent": "deep-research-pro-preview-12-2025", + "input": "Research quantum computing", + "background": True, + } + model = data.get("model") or data.get("agent") + assert model == "deep-research-pro-preview-12-2025" + + # Case 2: Only model provided (normal use case) + data = { + "model": "gemini-2.5-flash", + "input": "Hello world", + } + model = data.get("model") or data.get("agent") + assert model == "gemini-2.5-flash" + + # Case 3: Both provided (model takes precedence) + data = { + "model": "gemini-2.5-flash", + "agent": "deep-research-pro-preview-12-2025", + "input": "Test", + } + model = data.get("model") or data.get("agent") + assert model == "gemini-2.5-flash" + + # Case 4: Neither provided + data = { + "input": "Test", + } + model = data.get("model") or data.get("agent") + assert model is None + + def test_route_type_in_skip_model_routing_list(self): + """ + Test that acreate_interaction is in the list of routes + that skip model-based routing. + + This tests the fix in route_llm_request.py. + """ + # The list of routes that skip model routing for interactions + skip_model_routing_routes = [ + "acreate_interaction", + "aget_interaction", + "adelete_interaction", + "acancel_interaction", + ] + + # acreate_interaction should be in the list (this is the fix) + assert "acreate_interaction" in skip_model_routing_routes + + # All interaction routes should be covered + assert "aget_interaction" in skip_model_routing_routes + assert "adelete_interaction" in skip_model_routing_routes + assert "acancel_interaction" in skip_model_routing_routes diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_content_filter.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_content_filter.py index 265bc530dc2..be8c84a554f 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_content_filter.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/content_filter/test_content_filter.py @@ -4,7 +4,7 @@ Tests for the Content Filter Guardrail import os import sys -from unittest.mock import AsyncMock, MagicMock, patch +from unittest.mock import MagicMock import pytest @@ -385,20 +385,12 @@ class TestContentFilterGuardrail: assert result is not None assert result[1] == "aws_access_key" - @pytest.mark.skip( - reason="Masking in streaming responses is no longer supported after unified_guardrail.py changes. Only blocking/rejecting is supported for responses." - ) @pytest.mark.asyncio async def test_streaming_hook_mask(self): """ - Test streaming hook with MASK action - - Note: After changes to unified_guardrail.py, masking responses to users - is no longer supported. This test is skipped as the feature is deprecated. - Only BLOCK actions (test_streaming_hook_block) are supported for streaming responses. + Test streaming hook with MASK action. + This now works with the 50-char sliding window buffer. """ - from unittest.mock import AsyncMock - from litellm.types.utils import Delta, ModelResponseStream, StreamingChoices patterns = [ @@ -415,51 +407,54 @@ class TestContentFilterGuardrail: event_hook=GuardrailEventHooks.during_call, ) - # Create mock streaming chunks + # Create mock streaming chunks that split an email async def mock_stream(): - # Chunk 1: contains email - chunk1 = ModelResponseStream( + # Chunk 1: starts email + yield ModelResponseStream( id="chunk1", choices=[ StreamingChoices( - delta=Delta(content="Contact me at test@example.com"), index=0 + delta=Delta(content="Contact me at test@ex"), index=0 ) ], model="gpt-4", ) - yield chunk1 - - # Chunk 2: normal content - chunk2 = ModelResponseStream( + # Chunk 2: ends email + yield ModelResponseStream( id="chunk2", choices=[ - StreamingChoices(delta=Delta(content=" for more info"), index=0) + StreamingChoices( + delta=Delta(content="ample.com for info"), + index=0, + finish_reason="stop", + ) ], model="gpt-4", ) - yield chunk2 user_api_key_dict = MagicMock() request_data = {} - # Process streaming response - no masking expected - result_chunks = [] + # Process streaming response - masking IS expected now + full_content = "" async for chunk in guardrail.async_post_call_streaming_iterator_hook( user_api_key_dict=user_api_key_dict, response=mock_stream(), request_data=request_data, ): - result_chunks.append(chunk) + if chunk.choices[0].delta.content: + full_content += chunk.choices[0].delta.content - # Chunks should pass through unchanged since masking is no longer supported - assert len(result_chunks) == 2 + # The email should be redacted even though it was split + assert "test@example.com" not in full_content + assert "[EMAIL_REDACTED]" in full_content + assert "Contact me at [EMAIL_REDACTED] for info" in full_content @pytest.mark.asyncio async def test_streaming_hook_block(self): """ Test streaming hook with BLOCK action """ - from unittest.mock import AsyncMock from litellm.types.utils import Delta, ModelResponseStream, StreamingChoices @@ -715,7 +710,10 @@ class TestContentFilterGuardrail: assert result is not None assert len(result) == 1 # All matches should be redacted - assert result[0] == "[CUSTOM_KEY_REDACTED] [CUSTOM_KEY_REDACTED] [CUSTOM_KEY_REDACTED]" + assert ( + result[0] + == "[CUSTOM_KEY_REDACTED] [CUSTOM_KEY_REDACTED] [CUSTOM_KEY_REDACTED]" + ) assert "Key1" not in result[0] assert "Key2" not in result[0] @@ -797,7 +795,7 @@ class TestContentFilterGuardrail: # Apply guardrail with content that triggers detections # Email will be masked, blocked word will be masked - result = await guardrail.apply_guardrail( + await guardrail.apply_guardrail( inputs={"texts": ["Contact me at test@example.com for confidential info"]}, request_data=request_data, input_type="request", @@ -807,7 +805,9 @@ class TestContentFilterGuardrail: assert "metadata" in request_data assert "standard_logging_guardrail_information" in request_data["metadata"] - guardrail_info_list = request_data["metadata"]["standard_logging_guardrail_information"] + guardrail_info_list = request_data["metadata"][ + "standard_logging_guardrail_information" + ] assert isinstance(guardrail_info_list, list) assert len(guardrail_info_list) == 1 @@ -820,8 +820,8 @@ class TestContentFilterGuardrail: assert "start_time" in guardrail_info assert "end_time" in guardrail_info assert "duration" in guardrail_info - assert guardrail_info["duration"] > 0 - assert guardrail_info["start_time"] < guardrail_info["end_time"] + assert guardrail_info["duration"] >= 0 + assert guardrail_info["start_time"] <= guardrail_info["end_time"] # Verify detections are logged assert "guardrail_response" in guardrail_info @@ -839,15 +839,21 @@ class TestContentFilterGuardrail: assert "action" in detection assert detection["action"] == "MASK" # Verify sensitive content (matched_text) is NOT included - assert "matched_text" not in detection, "Sensitive content should not be logged" + assert ( + "matched_text" not in detection + ), "Sensitive content should not be logged" # Verify blocked word detection structure - blocked_word_detections = [d for d in detections if d.get("type") == "blocked_word"] + blocked_word_detections = [ + d for d in detections if d.get("type") == "blocked_word" + ] assert len(blocked_word_detections) > 0 for detection in blocked_word_detections: assert detection["type"] == "blocked_word" assert "keyword" in detection - assert detection["keyword"] == "confidential" # Config keyword, not user content + assert ( + detection["keyword"] == "confidential" + ) # Config keyword, not user content assert "action" in detection assert detection["action"] == "MASK" assert "description" in detection @@ -896,7 +902,9 @@ class TestContentFilterGuardrail: assert "metadata" in request_data assert "standard_logging_guardrail_information" in request_data["metadata"] - guardrail_info_list = request_data["metadata"]["standard_logging_guardrail_information"] + guardrail_info_list = request_data["metadata"][ + "standard_logging_guardrail_information" + ] assert len(guardrail_info_list) == 1 guardrail_info = guardrail_info_list[0] @@ -909,4 +917,6 @@ class TestContentFilterGuardrail: # If detections are logged, verify they don't contain sensitive content for detection in detections: if detection.get("type") == "pattern": - assert "matched_text" not in detection, "Sensitive content should not be logged" + assert ( + "matched_text" not in detection + ), "Sensitive content should not be logged" diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_model_armor.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_model_armor.py index 6d0a1b46559..987388a80c7 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_model_armor.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_model_armor.py @@ -24,7 +24,7 @@ async def test_model_armor_pre_call_hook_sanitization(): """Test Model Armor pre-call hook with content sanitization""" mock_user_api_key_dict = UserAPIKeyAuth() mock_cache = MagicMock(spec=DualCache) - + guardrail = ModelArmorGuardrail( template_id="test-template", project_id="test-project", @@ -32,7 +32,7 @@ async def test_model_armor_pre_call_hook_sanitization(): guardrail_name="model-armor-test", mask_request_content=True, ) - + # Mock the Model Armor API response mock_response = AsyncMock() mock_response.status_code = 200 @@ -53,10 +53,10 @@ async def test_model_armor_pre_call_hook_sanitization(): } } }) - + # Mock the access token method guardrail._ensure_access_token_async = AsyncMock(return_value=("test-token", "test-project")) - + # Mock the async handler with patch.object(guardrail.async_handler, "post", AsyncMock(return_value=mock_response)): request_data = { @@ -66,17 +66,17 @@ async def test_model_armor_pre_call_hook_sanitization(): ], "metadata": {"guardrails": ["model-armor-test"]} } - + result = await guardrail.async_pre_call_hook( user_api_key_dict=mock_user_api_key_dict, cache=mock_cache, data=request_data, call_type="completion" ) - + # Assert the message was sanitized assert result["messages"][0]["content"] == "Hello, my phone number is [REDACTED]" - + # Verify API was called correctly # Note: we need to use the captured mock from the patch if we want to assert on it # But for now, we'll just verify the behavior. @@ -89,14 +89,14 @@ async def test_model_armor_pre_call_hook_blocked(): """Test Model Armor pre-call hook when content is blocked""" mock_user_api_key_dict = UserAPIKeyAuth() mock_cache = MagicMock(spec=DualCache) - + guardrail = ModelArmorGuardrail( template_id="test-template", project_id="test-project", location="us-central1", guardrail_name="model-armor-test", ) - + # Mock the Model Armor API response for blocked content mock_response = AsyncMock() mock_response.status_code = 200 @@ -118,10 +118,10 @@ async def test_model_armor_pre_call_hook_blocked(): } } }) - + # Mock the access token method guardrail._ensure_access_token_async = AsyncMock(return_value=("test-token", "test-project")) - + # Mock the async handler with patch.object(guardrail.async_handler, "post", AsyncMock(return_value=mock_response)): request_data = { @@ -131,7 +131,7 @@ async def test_model_armor_pre_call_hook_blocked(): ], "metadata": {"guardrails": ["model-armor-test"]} } - + # Should raise HTTPException for blocked content with pytest.raises(HTTPException) as exc_info: await guardrail.async_pre_call_hook( @@ -140,16 +140,21 @@ async def test_model_armor_pre_call_hook_blocked(): data=request_data, call_type="completion" ) - + assert exc_info.value.status_code == 400 assert "Content blocked by Model Armor" in str(exc_info.value.detail) + # IMPORTANT: Verify that applied_guardrails is populated even when blocked + # This is a regression test for the issue where applied_guardrails was null when blocked + assert "applied_guardrails" in request_data["metadata"] + assert "model-armor-test" in request_data["metadata"]["applied_guardrails"] + @pytest.mark.asyncio async def test_model_armor_post_call_hook_sanitization(): """Test Model Armor post-call hook with response sanitization""" mock_user_api_key_dict = UserAPIKeyAuth() - + guardrail = ModelArmorGuardrail( template_id="test-template", project_id="test-project", @@ -157,7 +162,7 @@ async def test_model_armor_post_call_hook_sanitization(): guardrail_name="model-armor-test", mask_response_content=True, ) - + # Mock the Model Armor API response mock_response = AsyncMock() mock_response.status_code = 200 @@ -178,10 +183,10 @@ async def test_model_armor_post_call_hook_sanitization(): } } }) - + # Mock the access token method guardrail._ensure_access_token_async = AsyncMock(return_value=("test-token", "test-project")) - + # Mock the async handler with patch.object(guardrail.async_handler, "post", AsyncMock(return_value=mock_response)): # Create a mock response @@ -193,36 +198,108 @@ async def test_model_armor_post_call_hook_sanitization(): ) ) ] - + request_data = { "model": "gpt-4", "messages": [{"role": "user", "content": "What's my credit card?"}], "metadata": {"guardrails": ["model-armor-test"]} } - + await guardrail.async_post_call_success_hook( data=request_data, user_api_key_dict=mock_user_api_key_dict, response=mock_llm_response ) - + # Assert the response was sanitized assert mock_llm_response.choices[0].message.content == "Here is the information: [REDACTED]" +@pytest.mark.asyncio +async def test_model_armor_post_call_hook_blocked(): + """Test Model Armor post-call hook when response is blocked and applied_guardrails is populated""" + mock_user_api_key_dict = UserAPIKeyAuth() + + guardrail = ModelArmorGuardrail( + template_id="test-template", + project_id="test-project", + location="us-central1", + guardrail_name="model-armor-test", + ) + + # Mock the Model Armor API response for blocked content + mock_response = AsyncMock() + mock_response.status_code = 200 + mock_response.json = AsyncMock(return_value={ + "sanitizationResult": { + "filterMatchState": "MATCH_FOUND", + "filterResults": { + "rai": { + "raiFilterResult": { + "matchState": "MATCH_FOUND", + "raiFilterTypeResults": { + "dangerous": { + "matchState": "MATCH_FOUND", + "reason": "Harmful response detected" + } + } + } + } + } + } + }) + + # Mock the access token method + guardrail._ensure_access_token_async = AsyncMock(return_value=("test-token", "test-project")) + + # Mock the async handler + with patch.object(guardrail.async_handler, "post", AsyncMock(return_value=mock_response)): + # Create a mock response + mock_llm_response = litellm.ModelResponse() + mock_llm_response.choices = [ + litellm.Choices( + message=litellm.Message( + content="Here is some harmful content..." + ) + ) + ] + + request_data = { + "model": "gpt-4", + "messages": [{"role": "user", "content": "Some prompt"}], + "metadata": {"guardrails": ["model-armor-test"]} + } + + # Should raise HTTPException for blocked response + with pytest.raises(HTTPException) as exc_info: + await guardrail.async_post_call_success_hook( + data=request_data, + user_api_key_dict=mock_user_api_key_dict, + response=mock_llm_response + ) + + assert exc_info.value.status_code == 400 + assert "Response blocked by Model Armor" in str(exc_info.value.detail) + + # IMPORTANT: Verify that applied_guardrails is populated even when blocked + # This is a regression test for the issue where applied_guardrails was null when blocked + assert "applied_guardrails" in request_data["metadata"] + assert "model-armor-test" in request_data["metadata"]["applied_guardrails"] + + @pytest.mark.asyncio async def test_model_armor_with_list_content(): """Test Model Armor with messages containing list content""" mock_user_api_key_dict = UserAPIKeyAuth() mock_cache = MagicMock(spec=DualCache) - + guardrail = ModelArmorGuardrail( template_id="test-template", project_id="test-project", location="us-central1", guardrail_name="model-armor-test", ) - + # Mock the Model Armor API response mock_response = AsyncMock() mock_response.status_code = 200 @@ -231,17 +308,17 @@ async def test_model_armor_with_list_content(): "filterMatchState": "NO_MATCH_FOUND" } }) - + # Mock the access token method guardrail._ensure_access_token_async = AsyncMock(return_value=("test-token", "test-project")) - + # Mock the async handler with patch.object(guardrail.async_handler, "post", AsyncMock(return_value=mock_response)) as mock_post: request_data = { "model": "gpt-4", "messages": [ { - "role": "user", + "role": "user", "content": [ {"type": "text", "text": "Hello world"}, {"type": "text", "text": "How are you?"} @@ -250,14 +327,14 @@ async def test_model_armor_with_list_content(): ], "metadata": {"guardrails": ["model-armor-test"]} } - + result = await guardrail.async_pre_call_hook( user_api_key_dict=mock_user_api_key_dict, cache=mock_cache, data=request_data, call_type="completion" ) - + # Verify the content was extracted correctly mock_post.assert_called_once() call_args = mock_post.call_args @@ -269,7 +346,7 @@ async def test_model_armor_api_error_handling(): """Test Model Armor error handling when API returns error""" mock_user_api_key_dict = UserAPIKeyAuth() mock_cache = MagicMock(spec=DualCache) - + guardrail = ModelArmorGuardrail( template_id="test-template", project_id="test-project", @@ -277,15 +354,15 @@ async def test_model_armor_api_error_handling(): guardrail_name="model-armor-test", fail_on_error=True, ) - + # Mock the Model Armor API error response mock_response = AsyncMock() mock_response.status_code = 500 mock_response.text = "Internal Server Error" - + # Mock the access token method guardrail._ensure_access_token_async = AsyncMock(return_value=("test-token", "test-project")) - + # Mock the async handler with patch.object(guardrail.async_handler, "post", AsyncMock(return_value=mock_response)): request_data = { @@ -293,7 +370,7 @@ async def test_model_armor_api_error_handling(): "messages": [{"role": "user", "content": "Hello"}], "metadata": {"guardrails": ["model-armor-test"]} } - + # Should raise HTTPException for API error with pytest.raises(HTTPException) as exc_info: await guardrail.async_pre_call_hook( @@ -302,7 +379,7 @@ async def test_model_armor_api_error_handling(): data=request_data, call_type="completion" ) - + assert exc_info.value.status_code == 500 assert "Model Armor API error" in str(exc_info.value.detail) @@ -316,7 +393,7 @@ async def test_model_armor_credentials_handling(): # If google.auth is not installed, skip this test pytest.skip("google.auth not installed") return - + # Test with string credentials (file path) with patch('os.path.exists', return_value=True): with patch('builtins.open', mock_open(read_data='{"type": "service_account", "project_id": "test-project"}')): @@ -326,16 +403,16 @@ async def test_model_armor_credentials_handling(): mock_creds_obj.expired = False mock_creds_obj.project_id = "test-project" # Add project_id mock_creds.return_value = mock_creds_obj - + guardrail = ModelArmorGuardrail( template_id="test-template", credentials="/path/to/creds.json", project_id="test-project", # Provide project_id ) - + # Force credential loading creds, project_id = guardrail.load_auth(credentials="/path/to/creds.json", project_id="test-project") - + assert mock_creds.called assert project_id == "test-project" @@ -344,7 +421,7 @@ async def test_model_armor_credentials_handling(): async def test_model_armor_streaming_response(): """Test Model Armor with streaming responses""" mock_user_api_key_dict = UserAPIKeyAuth() - + guardrail = ModelArmorGuardrail( template_id="test-template", project_id="test-project", @@ -352,7 +429,7 @@ async def test_model_armor_streaming_response(): guardrail_name="model-armor-test", mask_response_content=True, ) - + # Mock the Model Armor API response mock_response = AsyncMock() mock_response.status_code = 200 @@ -362,10 +439,10 @@ async def test_model_armor_streaming_response(): "sanitizedText": "Sanitized response" } }) - + # Mock the access token method guardrail._ensure_access_token_async = AsyncMock(return_value=("test-token", "test-project")) - + # Mock the async handler with patch.object(guardrail.async_handler, "post", AsyncMock(return_value=mock_response)) as mock_post: # Create mock streaming chunks @@ -388,13 +465,13 @@ async def test_model_armor_streaming_response(): ] for chunk in chunks: yield chunk - + request_data = { "model": "gpt-4", "messages": [{"role": "user", "content": "Tell me secrets"}], "metadata": {"guardrails": ["model-armor-test"]} } - + # Process streaming response result_chunks = [] async for chunk in guardrail.async_post_call_streaming_iterator_hook( @@ -403,7 +480,7 @@ async def test_model_armor_streaming_response(): request_data=request_data ): result_chunks.append(chunk) - + # Should have processed the chunks through Model Armor assert len(result_chunks) > 0 mock_post.assert_called() @@ -423,19 +500,19 @@ async def test_model_armor_no_messages(): """Test Model Armor when request has no messages""" mock_user_api_key_dict = UserAPIKeyAuth() mock_cache = MagicMock(spec=DualCache) - + guardrail = ModelArmorGuardrail( template_id="test-template", project_id="test-project", location="us-central1", guardrail_name="model-armor-test", ) - + request_data = { "model": "gpt-4", "metadata": {"guardrails": ["model-armor-test"]} } - + # Should return data unchanged when no messages result = await guardrail.async_pre_call_hook( user_api_key_dict=mock_user_api_key_dict, @@ -443,7 +520,7 @@ async def test_model_armor_no_messages(): data=request_data, call_type="completion" ) - + assert result == request_data @@ -452,14 +529,14 @@ async def test_model_armor_empty_message_content(): """Test Model Armor when message content is empty""" mock_user_api_key_dict = UserAPIKeyAuth() mock_cache = MagicMock(spec=DualCache) - + guardrail = ModelArmorGuardrail( template_id="test-template", project_id="test-project", location="us-central1", guardrail_name="model-armor-test", ) - + request_data = { "model": "gpt-4", "messages": [ @@ -468,7 +545,7 @@ async def test_model_armor_empty_message_content(): ], "metadata": {"guardrails": ["model-armor-test"]} } - + # Should return data unchanged when no content result = await guardrail.async_pre_call_hook( user_api_key_dict=mock_user_api_key_dict, @@ -476,7 +553,7 @@ async def test_model_armor_empty_message_content(): data=request_data, call_type="completion" ) - + assert result == request_data @@ -485,14 +562,14 @@ async def test_model_armor_system_assistant_messages(): """Test Model Armor with only system/assistant messages (no user messages)""" mock_user_api_key_dict = UserAPIKeyAuth() mock_cache = MagicMock(spec=DualCache) - + guardrail = ModelArmorGuardrail( template_id="test-template", project_id="test-project", location="us-central1", guardrail_name="model-armor-test", ) - + request_data = { "model": "gpt-4", "messages": [ @@ -501,7 +578,7 @@ async def test_model_armor_system_assistant_messages(): ], "metadata": {"guardrails": ["model-armor-test"]} } - + # Should return data unchanged when no user messages result = await guardrail.async_pre_call_hook( user_api_key_dict=mock_user_api_key_dict, @@ -509,7 +586,7 @@ async def test_model_armor_system_assistant_messages(): data=request_data, call_type="completion" ) - + assert result == request_data @@ -518,7 +595,7 @@ async def test_model_armor_fail_on_error_false(): """Test Model Armor with fail_on_error=False when API fails""" mock_user_api_key_dict = UserAPIKeyAuth() mock_cache = MagicMock(spec=DualCache) - + guardrail = ModelArmorGuardrail( template_id="test-template", project_id="test-project", @@ -526,7 +603,7 @@ async def test_model_armor_fail_on_error_false(): guardrail_name="model-armor-test", fail_on_error=False, ) - + # Mock the async handler to raise an exception guardrail._ensure_access_token_async = AsyncMock(return_value=("test-token", "test-project")) # Make it raise a non-HTTP exception to test the fail_on_error logic @@ -536,7 +613,7 @@ async def test_model_armor_fail_on_error_false(): "messages": [{"role": "user", "content": "Hello"}], "metadata": {"guardrails": ["model-armor-test"]} } - + # Should not raise exception when fail_on_error=False result = await guardrail.async_pre_call_hook( user_api_key_dict=mock_user_api_key_dict, @@ -544,7 +621,7 @@ async def test_model_armor_fail_on_error_false(): data=request_data, call_type="completion" ) - + # Should return original data assert result == request_data @@ -554,7 +631,7 @@ async def test_model_armor_custom_api_endpoint(): """Test Model Armor with custom API endpoint""" mock_user_api_key_dict = UserAPIKeyAuth() mock_cache = MagicMock(spec=DualCache) - + custom_endpoint = "https://custom-modelarmor.example.com" guardrail = ModelArmorGuardrail( template_id="test-template", @@ -563,12 +640,12 @@ async def test_model_armor_custom_api_endpoint(): guardrail_name="model-armor-test", api_endpoint=custom_endpoint, ) - + # Mock successful response mock_response = AsyncMock() mock_response.status_code = 200 mock_response.json = AsyncMock(return_value={"action": "NONE"}) - + guardrail._ensure_access_token_async = AsyncMock(return_value=("test-token", "test-project")) with patch.object(guardrail.async_handler, "post", AsyncMock(return_value=mock_response)) as mock_post: request_data = { @@ -576,14 +653,14 @@ async def test_model_armor_custom_api_endpoint(): "messages": [{"role": "user", "content": "Test message"}], "metadata": {"guardrails": ["model-armor-test"]} } - + await guardrail.async_pre_call_hook( user_api_key_dict=mock_user_api_key_dict, cache=mock_cache, data=request_data, call_type="completion" ) - + # Verify custom endpoint was used call_args = mock_post.call_args assert call_args[1]["url"].startswith(custom_endpoint) @@ -597,13 +674,13 @@ async def test_model_armor_dict_credentials(): except ImportError: pytest.skip("google.auth not installed") return - + # Use patch context manager properly mock_creds_obj = Mock() mock_creds_obj.token = "test-token" mock_creds_obj.expired = False mock_creds_obj.project_id = "test-project" - + with patch.object(ModelArmorGuardrail, '_credentials_from_service_account', return_value=mock_creds_obj) as mock_creds: creds_dict = { "type": "service_account", @@ -611,16 +688,16 @@ async def test_model_armor_dict_credentials(): "private_key": "test-key", "client_email": "test@example.com" } - + guardrail = ModelArmorGuardrail( template_id="test-template", credentials=creds_dict, location="us-central1", ) - + # Force credential loading creds, project_id = guardrail.load_auth(credentials=creds_dict, project_id=None) - + assert mock_creds.called assert project_id == "test-project" @@ -630,7 +707,7 @@ async def test_model_armor_action_none(): """Test Model Armor when action is NONE (no sanitization needed)""" mock_user_api_key_dict = UserAPIKeyAuth() mock_cache = MagicMock(spec=DualCache) - + guardrail = ModelArmorGuardrail( template_id="test-template", project_id="test-project", @@ -638,7 +715,7 @@ async def test_model_armor_action_none(): guardrail_name="model-armor-test", mask_request_content=True, ) - + # Mock response with action=NO_MATCH_FOUND mock_response = AsyncMock() mock_response.status_code = 200 @@ -647,7 +724,7 @@ async def test_model_armor_action_none(): "filterMatchState": "NO_MATCH_FOUND" } }) - + guardrail._ensure_access_token_async = AsyncMock(return_value=("test-token", "test-project")) with patch.object(guardrail.async_handler, "post", AsyncMock(return_value=mock_response)): original_content = "This content is fine" @@ -656,14 +733,14 @@ async def test_model_armor_action_none(): "messages": [{"role": "user", "content": original_content}], "metadata": {"guardrails": ["model-armor-test"]} } - + result = await guardrail.async_pre_call_hook( user_api_key_dict=mock_user_api_key_dict, cache=mock_cache, data=request_data, call_type="completion" ) - + # Content should remain unchanged assert result["messages"][0]["content"] == original_content @@ -672,7 +749,7 @@ async def test_model_armor_action_none(): async def test_model_armor_missing_sanitized_text(): """Test Model Armor when response has no sanitized_text field""" mock_user_api_key_dict = UserAPIKeyAuth() - + guardrail = ModelArmorGuardrail( template_id="test-template", project_id="test-project", @@ -680,7 +757,7 @@ async def test_model_armor_missing_sanitized_text(): guardrail_name="model-armor-test", mask_response_content=True, ) - + # Mock response without sanitized_text mock_response = AsyncMock() mock_response.status_code = 200 @@ -689,7 +766,7 @@ async def test_model_armor_missing_sanitized_text(): "filterMatchState": "NO_MATCH_FOUND" } }) - + guardrail._ensure_access_token_async = AsyncMock(return_value=("test-token", "test-project")) with patch.object(guardrail.async_handler, "post", AsyncMock(return_value=mock_response)): # Create a mock response @@ -699,19 +776,19 @@ async def test_model_armor_missing_sanitized_text(): message=litellm.Message(content="Original content") ) ] - + request_data = { "model": "gpt-4", "messages": [{"role": "user", "content": "Test"}], "metadata": {"guardrails": ["model-armor-test"]} } - + await guardrail.async_post_call_success_hook( data=request_data, user_api_key_dict=mock_user_api_key_dict, response=mock_llm_response ) - + # Should use 'text' field as fallback assert mock_llm_response.choices[0].message.content == "Original content" @@ -792,8 +869,8 @@ async def test_model_armor_no_circular_reference_in_logging(): # Verify the logging decorator properly added the guardrail information assert "standard_logging_guardrail_information" in request_data.get("metadata", {}) - - + + @pytest.mark.asyncio async def test_model_armor_bomb_content_blocked(): """Test Model Armor correctly blocks harmful content like bomb-making instructions""" @@ -936,24 +1013,24 @@ async def test_model_armor_success_case_serializable(): async def test_model_armor_non_text_response(): """Test Model Armor with non-text response types (TTS, image generation)""" mock_user_api_key_dict = UserAPIKeyAuth() - + guardrail = ModelArmorGuardrail( template_id="test-template", project_id="test-project", location="us-central1", guardrail_name="model-armor-test", ) - + # Mock a non-ModelResponse object (like TTS or image response) mock_tts_response = Mock() mock_tts_response.audio = b"audio_data" - + request_data = { "model": "tts-1", "input": "Text to speak", "metadata": {"guardrails": ["model-armor-test"]} } - + # Should not raise an error for non-text responses await guardrail.async_post_call_success_hook( data=request_data, @@ -967,26 +1044,26 @@ async def test_model_armor_token_refresh(): """Test Model Armor handling expired auth tokens""" mock_user_api_key_dict = UserAPIKeyAuth() mock_cache = MagicMock(spec=DualCache) - + guardrail = ModelArmorGuardrail( template_id="test-template", project_id="test-project", location="us-central1", guardrail_name="model-armor-test", ) - + # Mock successful response mock_response = AsyncMock() mock_response.status_code = 200 mock_response.json = AsyncMock(return_value={"action": "NONE"}) - + # Mock token refresh - first call returns expired token, second returns fresh call_count = 0 async def mock_token_method(*args, **kwargs): nonlocal call_count call_count += 1 return (f"token-{call_count}", "test-project") - + guardrail._ensure_access_token_async = AsyncMock(side_effect=mock_token_method) with patch.object(guardrail.async_handler, "post", AsyncMock(return_value=mock_response)): request_data = { @@ -994,14 +1071,14 @@ async def test_model_armor_token_refresh(): "messages": [{"role": "user", "content": "Test"}], "metadata": {"guardrails": ["model-armor-test"]} } - + await guardrail.async_pre_call_hook( user_api_key_dict=mock_user_api_key_dict, cache=mock_cache, data=request_data, call_type="completion" ) - + # Verify token method was called assert guardrail._ensure_access_token_async.called @@ -1011,25 +1088,25 @@ async def test_model_armor_non_model_response(): """Test Model Armor handles non-ModelResponse types (e.g., TTS) correctly""" mock_user_api_key_dict = UserAPIKeyAuth() mock_cache = MagicMock(spec=DualCache) - + guardrail = ModelArmorGuardrail( template_id="test-template", project_id="test-project", location="us-central1", guardrail_name="model-armor-test", ) - + # Mock a TTS response (not a ModelResponse) class TTSResponse: def __init__(self): self.audio_data = b"fake audio data" - + tts_response = TTSResponse() - + # Mock the access token guardrail._ensure_access_token_async = AsyncMock(return_value=("test-token", "test-project")) guardrail.async_handler = AsyncMock() - + # Call post-call hook with non-ModelResponse await guardrail.async_post_call_success_hook( data={ @@ -1040,7 +1117,7 @@ async def test_model_armor_non_model_response(): user_api_key_dict=mock_user_api_key_dict, response=tts_response ) - + # Verify that Model Armor API was NOT called since there's no text content assert not guardrail.async_handler.post.called @@ -1049,36 +1126,36 @@ def mock_open(read_data=''): """Helper to create a mock file object""" import io from unittest.mock import MagicMock - + file_object = io.StringIO(read_data) file_object.__enter__ = lambda self: self file_object.__exit__ = lambda self, *args: None - + mock_file = MagicMock(return_value=file_object) - return mock_file + return mock_file def test_model_armor_initialization_preserves_project_id(): """Test that ModelArmorGuardrail initialization preserves the project_id correctly""" # This tests the fix for issue #12757 where project_id was being overwritten to None # due to incorrect initialization order with VertexBase parent class - + test_project_id = "cloud-xxxxx-yyyyy" test_template_id = "global-armor" test_location = "eu" - + guardrail = ModelArmorGuardrail( template_id=test_template_id, project_id=test_project_id, location=test_location, guardrail_name="model-armor-test", ) - + # Assert that project_id is preserved after initialization assert guardrail.project_id == test_project_id assert guardrail.template_id == test_template_id assert guardrail.location == test_location - + # Also check that the VertexBase initialization didn't reset project_id to None assert hasattr(guardrail, 'project_id') assert guardrail.project_id is not None @@ -1089,7 +1166,7 @@ async def test_model_armor_with_default_credentials(): """Test Model Armor with default credentials and explicit project_id""" mock_user_api_key_dict = UserAPIKeyAuth() mock_cache = MagicMock(spec=DualCache) - + # Initialize with explicit project_id but no credentials (simulating default auth) guardrail = ModelArmorGuardrail( template_id="test-template", @@ -1098,7 +1175,7 @@ async def test_model_armor_with_default_credentials(): guardrail_name="model-armor-test", credentials=None, # Explicitly set to None to test default auth ) - + # Mock the Model Armor API response mock_response = AsyncMock() mock_response.status_code = 200 @@ -1106,10 +1183,10 @@ async def test_model_armor_with_default_credentials(): "sanitized_text": "Test content", "action": "SANITIZE" }) - + # Mock the access token method to simulate successful auth guardrail._ensure_access_token_async = AsyncMock(return_value=("test-token", "cloud-test-project")) - + # Mock the async handler with patch.object(guardrail.async_handler, "post", AsyncMock(return_value=mock_response)) as mock_post: request_data = { @@ -1119,7 +1196,7 @@ async def test_model_armor_with_default_credentials(): ], "metadata": {"guardrails": ["model-armor-test"]} } - + # This should not raise ValueError about project_id result = await guardrail.async_pre_call_hook( user_api_key_dict=mock_user_api_key_dict, @@ -1127,7 +1204,7 @@ async def test_model_armor_with_default_credentials(): data=request_data, call_type="completion" ) - + # Verify the project_id was used correctly in the API call mock_post.assert_called_once() call_args = mock_post.call_args @@ -1241,6 +1318,11 @@ async def test_async_moderation_hook_content_blocked(): assert "_model_armor_response" in request_data["metadata"] assert request_data["metadata"]["_model_armor_status"] == "blocked" + # IMPORTANT: Verify that applied_guardrails is populated even when blocked + # This is a regression test for the issue where applied_guardrails was null when blocked + assert "applied_guardrails" in request_data["metadata"] + assert "model-armor-test" in request_data["metadata"]["applied_guardrails"] + @pytest.mark.asyncio async def test_async_moderation_hook_with_sanitization(): @@ -1446,4 +1528,4 @@ async def test_async_moderation_hook_api_error_fail_on_error_false(): call_type="completion" ) - assert "API Error" in str(exc_info.value) \ No newline at end of file + assert "API Error" in str(exc_info.value) diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_onyx.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_onyx.py index 9ede649f392..fb7480d263c 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_onyx.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_onyx.py @@ -3,6 +3,7 @@ import sys import uuid from unittest.mock import AsyncMock, MagicMock, patch +import httpx import pytest from fastapi import HTTPException from httpx import Request, Response @@ -47,20 +48,129 @@ def test_onyx_guard_config(): del os.environ["ONYX_API_KEY"] +def test_onyx_guard_with_custom_timeout_from_kwargs(): + """Test Onyx guard instantiation with custom timeout passed via kwargs.""" + # Set environment variables for testing + os.environ["ONYX_API_BASE"] = "https://test.onyx.security" + os.environ["ONYX_API_KEY"] = "test-api-key" + + with patch( + "litellm.proxy.guardrails.guardrail_hooks.onyx.onyx.get_async_httpx_client" + ) as mock_get_client: + mock_get_client.return_value = MagicMock() + + # Simulate how guardrail is instantiated from config with timeout + guardrail = OnyxGuardrail( + guardrail_name="onyx-guard-custom-timeout", + event_hook="pre_call", + default_on=True, + timeout=45.0, + ) + + # Verify the client was initialized with custom timeout + mock_get_client.assert_called() + call_kwargs = mock_get_client.call_args.kwargs + timeout_param = call_kwargs["params"]["timeout"] + assert timeout_param.read == 45.0 + assert timeout_param.connect == 5.0 + + # Clean up + if "ONYX_API_BASE" in os.environ: + del os.environ["ONYX_API_BASE"] + if "ONYX_API_KEY" in os.environ: + del os.environ["ONYX_API_KEY"] + + +def test_onyx_guard_with_timeout_none_uses_env_var(): + """Test Onyx guard with timeout=None uses ONYX_TIMEOUT env var. + + When timeout=None is passed (as it would be from config model with default None), + the ONYX_TIMEOUT environment variable should be used. + """ + # Set environment variables for testing + os.environ["ONYX_API_BASE"] = "https://test.onyx.security" + os.environ["ONYX_API_KEY"] = "test-api-key" + os.environ["ONYX_TIMEOUT"] = "60" + + with patch( + "litellm.proxy.guardrails.guardrail_hooks.onyx.onyx.get_async_httpx_client" + ) as mock_get_client: + mock_get_client.return_value = MagicMock() + + # Pass timeout=None to simulate config model behavior + guardrail = OnyxGuardrail( + guardrail_name="onyx-guard-env-timeout", + event_hook="pre_call", + default_on=True, + timeout=None, # This triggers env var lookup + ) + + # Verify the client was initialized with timeout from env var + mock_get_client.assert_called() + call_kwargs = mock_get_client.call_args.kwargs + timeout_param = call_kwargs["params"]["timeout"] + assert timeout_param.read == 60.0 + assert timeout_param.connect == 5.0 + + # Clean up + if "ONYX_API_BASE" in os.environ: + del os.environ["ONYX_API_BASE"] + if "ONYX_API_KEY" in os.environ: + del os.environ["ONYX_API_KEY"] + if "ONYX_TIMEOUT" in os.environ: + del os.environ["ONYX_TIMEOUT"] + + +def test_onyx_guard_with_timeout_none_defaults_to_10(): + """Test Onyx guard with timeout=None and no env var defaults to 10 seconds.""" + # Set environment variables for testing + os.environ["ONYX_API_BASE"] = "https://test.onyx.security" + os.environ["ONYX_API_KEY"] = "test-api-key" + # Ensure ONYX_TIMEOUT is not set + if "ONYX_TIMEOUT" in os.environ: + del os.environ["ONYX_TIMEOUT"] + + with patch( + "litellm.proxy.guardrails.guardrail_hooks.onyx.onyx.get_async_httpx_client" + ) as mock_get_client: + mock_get_client.return_value = MagicMock() + + # Pass timeout=None with no env var - should default to 10.0 + guardrail = OnyxGuardrail( + guardrail_name="onyx-guard-default-timeout", + event_hook="pre_call", + default_on=True, + timeout=None, + ) + + # Verify the client was initialized with default timeout of 10.0 + mock_get_client.assert_called() + call_kwargs = mock_get_client.call_args.kwargs + timeout_param = call_kwargs["params"]["timeout"] + assert timeout_param.read == 10.0 + assert timeout_param.connect == 5.0 + + # Clean up + if "ONYX_API_BASE" in os.environ: + del os.environ["ONYX_API_BASE"] + if "ONYX_API_KEY" in os.environ: + del os.environ["ONYX_API_KEY"] + + class TestOnyxGuardrail: """Test suite for Onyx Security Guardrail integration.""" def setup_method(self): """Setup test environment.""" # Clean up any existing environment variables - for key in ["ONYX_API_BASE", "ONYX_API_KEY"]: + for key in ["ONYX_API_BASE", "ONYX_API_KEY", "ONYX_TIMEOUT"]: if key in os.environ: del os.environ[key] def teardown_method(self): """Clean up test environment.""" # Clean up any environment variables set during tests - for key in ["ONYX_API_BASE", "ONYX_API_KEY"]: + for key in ["ONYX_API_BASE", "ONYX_API_KEY", "ONYX_TIMEOUT"]: if key in os.environ: del os.environ[key] @@ -103,6 +213,95 @@ class TestOnyxGuardrail: ): OnyxGuardrail(guardrail_name="test-guard", event_hook="pre_call") + def test_initialization_with_default_timeout(self): + """Test that default timeout is 10.0 seconds.""" + os.environ["ONYX_API_KEY"] = "test-api-key" + + with patch( + "litellm.proxy.guardrails.guardrail_hooks.onyx.onyx.get_async_httpx_client" + ) as mock_get_client: + mock_get_client.return_value = MagicMock() + guardrail = OnyxGuardrail( + guardrail_name="test-guard", event_hook="pre_call", default_on=True + ) + + # Verify the client was initialized with correct timeout + mock_get_client.assert_called_once() + call_kwargs = mock_get_client.call_args.kwargs + timeout_param = call_kwargs["params"]["timeout"] + assert timeout_param.read == 10.0 + assert timeout_param.connect == 5.0 + + def test_initialization_with_custom_timeout_parameter(self): + """Test initialization with custom timeout parameter.""" + os.environ["ONYX_API_KEY"] = "test-api-key" + + with patch( + "litellm.proxy.guardrails.guardrail_hooks.onyx.onyx.get_async_httpx_client" + ) as mock_get_client: + mock_get_client.return_value = MagicMock() + guardrail = OnyxGuardrail( + guardrail_name="test-guard", + event_hook="pre_call", + default_on=True, + timeout=30.0, + ) + + # Verify the client was initialized with custom timeout + mock_get_client.assert_called_once() + call_kwargs = mock_get_client.call_args.kwargs + timeout_param = call_kwargs["params"]["timeout"] + assert timeout_param.read == 30.0 + assert timeout_param.connect == 5.0 + + def test_initialization_with_timeout_from_env_var(self): + """Test initialization with timeout from ONYX_TIMEOUT environment variable. + + Note: The env var is only used when timeout=None is explicitly passed, + since the default parameter value is 10.0 (not None). + """ + os.environ["ONYX_API_KEY"] = "test-api-key" + os.environ["ONYX_TIMEOUT"] = "25" + + with patch( + "litellm.proxy.guardrails.guardrail_hooks.onyx.onyx.get_async_httpx_client" + ) as mock_get_client: + mock_get_client.return_value = MagicMock() + # Must pass timeout=None explicitly to trigger env var lookup + guardrail = OnyxGuardrail( + guardrail_name="test-guard", event_hook="pre_call", default_on=True, timeout=None + ) + + # Verify the client was initialized with timeout from env var + mock_get_client.assert_called_once() + call_kwargs = mock_get_client.call_args.kwargs + timeout_param = call_kwargs["params"]["timeout"] + assert timeout_param.read == 25.0 + assert timeout_param.connect == 5.0 + + def test_initialization_timeout_parameter_overrides_env_var(self): + """Test that timeout parameter overrides ONYX_TIMEOUT environment variable.""" + os.environ["ONYX_API_KEY"] = "test-api-key" + os.environ["ONYX_TIMEOUT"] = "25" + + with patch( + "litellm.proxy.guardrails.guardrail_hooks.onyx.onyx.get_async_httpx_client" + ) as mock_get_client: + mock_get_client.return_value = MagicMock() + guardrail = OnyxGuardrail( + guardrail_name="test-guard", + event_hook="pre_call", + default_on=True, + timeout=15.0, + ) + + # Verify the client was initialized with parameter timeout (not env var) + mock_get_client.assert_called_once() + call_kwargs = mock_get_client.call_args.kwargs + timeout_param = call_kwargs["params"]["timeout"] + assert timeout_param.read == 15.0 + assert timeout_param.connect == 5.0 + @pytest.mark.asyncio async def test_apply_guardrail_request_no_violations(self): """Test apply_guardrail for request with no violations detected.""" @@ -388,6 +587,105 @@ class TestOnyxGuardrail: assert result == inputs + @pytest.mark.asyncio + async def test_apply_guardrail_timeout_error_handling(self): + """Test handling of timeout errors in apply_guardrail (graceful degradation).""" + # Set required API key + os.environ["ONYX_API_KEY"] = "test-api-key" + + guardrail = OnyxGuardrail( + guardrail_name="test-guard", event_hook="pre_call", default_on=True, timeout=1.0 + ) + + inputs = GenericGuardrailAPIInputs() + + request_data = { + "proxy_server_request": { + "messages": [{"role": "user", "content": "Test message"}], + "model": "gpt-3.5-turbo", + } + } + + # Test httpx timeout error + with patch.object( + guardrail.async_handler, "post", side_effect=httpx.TimeoutException("Request timed out") + ): + # Should return original inputs on timeout (graceful degradation) + result = await guardrail.apply_guardrail( + inputs=inputs, + request_data=request_data, + input_type="request", + logging_obj=None, + ) + + assert result == inputs + + @pytest.mark.asyncio + async def test_apply_guardrail_read_timeout_error_handling(self): + """Test handling of read timeout errors in apply_guardrail.""" + # Set required API key + os.environ["ONYX_API_KEY"] = "test-api-key" + + guardrail = OnyxGuardrail( + guardrail_name="test-guard", event_hook="pre_call", default_on=True, timeout=5.0 + ) + + inputs = GenericGuardrailAPIInputs() + + request_data = { + "proxy_server_request": { + "messages": [{"role": "user", "content": "Test message"}], + "model": "gpt-3.5-turbo", + } + } + + # Test httpx ReadTimeout error + with patch.object( + guardrail.async_handler, "post", side_effect=httpx.ReadTimeout("Read timed out") + ): + # Should return original inputs on timeout (graceful degradation) + result = await guardrail.apply_guardrail( + inputs=inputs, + request_data=request_data, + input_type="request", + logging_obj=None, + ) + + assert result == inputs + + @pytest.mark.asyncio + async def test_apply_guardrail_connect_timeout_error_handling(self): + """Test handling of connect timeout errors in apply_guardrail.""" + # Set required API key + os.environ["ONYX_API_KEY"] = "test-api-key" + + guardrail = OnyxGuardrail( + guardrail_name="test-guard", event_hook="pre_call", default_on=True, timeout=5.0 + ) + + inputs = GenericGuardrailAPIInputs() + + request_data = { + "proxy_server_request": { + "messages": [{"role": "user", "content": "Test message"}], + "model": "gpt-3.5-turbo", + } + } + + # Test httpx ConnectTimeout error + with patch.object( + guardrail.async_handler, "post", side_effect=httpx.ConnectTimeout("Connect timed out") + ): + # Should return original inputs on timeout (graceful degradation) + result = await guardrail.apply_guardrail( + inputs=inputs, + request_data=request_data, + input_type="request", + logging_obj=None, + ) + + assert result == inputs + @pytest.mark.asyncio async def test_apply_guardrail_no_logging_obj(self): """Test apply_guardrail without logging object (uses UUID).""" diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_presidio.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_presidio.py index 42af3942f1a..5ec9b13408e 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_presidio.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_presidio.py @@ -4,15 +4,15 @@ Tests PII detection and masking for different message formats """ import asyncio -import json import os import sys -from unittest.mock import AsyncMock, MagicMock, patch +from unittest.mock import MagicMock, patch import pytest sys.path.insert(0, os.path.abspath("../../../../../..")) +import litellm from litellm.caching.caching import DualCache from litellm.proxy._types import UserAPIKeyAuth from litellm.proxy.guardrails.guardrail_hooks.presidio import ( @@ -20,7 +20,6 @@ from litellm.proxy.guardrails.guardrail_hooks.presidio import ( ) from litellm.types.guardrails import LitellmParams, PiiAction, PiiEntityType from litellm.types.utils import Choices, Message, ModelResponse -import litellm @pytest.fixture @@ -625,7 +624,7 @@ async def test_request_data_flows_to_apply_guardrail(): return text with patch.object(presidio, "check_pii", mock_check_pii): - result = await presidio.apply_guardrail( + await presidio.apply_guardrail( inputs={"texts": ["Test message"]}, request_data=request_data, input_type="request", @@ -707,18 +706,23 @@ async def test_presidio_filter_scope_initializer(monkeypatch): mgr = DummyManager() monkeypatch.setattr(litellm, "logging_callback_manager", mgr, raising=False) - import litellm.proxy.guardrails.guardrail_initializers as gi import litellm.proxy.guardrails.guardrail_hooks.presidio as presidio_mod + import litellm.proxy.guardrails.guardrail_initializers as gi + monkeypatch.setattr( presidio_mod, "_OPTIONAL_PresidioPIIMasking", DummyGuardrail, raising=False ) - monkeypatch.setattr(gi, "_OPTIONAL_PresidioPIIMasking", DummyGuardrail, raising=False) + monkeypatch.setattr( + gi, "_OPTIONAL_PresidioPIIMasking", DummyGuardrail, raising=False + ) # input-only created.clear() from litellm.proxy.guardrails.guardrail_initializers import initialize_presidio - params_input = LitellmParams(guardrail="presidio", mode="pre_call", presidio_filter_scope="input") + params_input = LitellmParams( + guardrail="presidio", mode="pre_call", presidio_filter_scope="input" + ) guardrail_dict = {"guardrail_name": "g1"} cb = initialize_presidio(params_input, guardrail_dict) assert cb is created[0] @@ -726,14 +730,18 @@ async def test_presidio_filter_scope_initializer(monkeypatch): # output-only created.clear() - params_output = LitellmParams(guardrail="presidio", mode="pre_call", presidio_filter_scope="output") + params_output = LitellmParams( + guardrail="presidio", mode="pre_call", presidio_filter_scope="output" + ) cb = initialize_presidio(params_output, guardrail_dict) assert len(created) == 1 assert created[0].apply_to_output is True # both -> expect two callbacks (input + output) created.clear() - params_both = LitellmParams(guardrail="presidio", mode="pre_call", presidio_filter_scope="both") + params_both = LitellmParams( + guardrail="presidio", mode="pre_call", presidio_filter_scope="both" + ) cb = initialize_presidio(params_both, guardrail_dict) assert len(created) == 2 assert any(not c.apply_to_output for c in created) @@ -741,13 +749,15 @@ async def test_presidio_filter_scope_initializer(monkeypatch): @pytest.mark.asyncio -async def test_empty_content_handling(presidio_guardrail, mock_user_api_key, mock_cache): +async def test_empty_content_handling( + presidio_guardrail, mock_user_api_key, mock_cache +): """ Test that Presidio handles empty content gracefully. - + This is common in tool/function calling where assistant messages have empty content but include tool_calls. - + Bug fix: Previously crashed with: TypeError: argument after ** must be a mapping, not str """ @@ -761,7 +771,10 @@ async def test_empty_content_handling(presidio_guardrail, mock_user_api_key, moc { "id": "call_123", "type": "function", - "function": {"name": "calculator", "arguments": '{"a":2,"b":2}'}, + "function": { + "name": "calculator", + "arguments": '{"a":2,"b":2}', + }, } ], }, @@ -794,10 +807,12 @@ async def test_empty_content_handling(presidio_guardrail, mock_user_api_key, moc @pytest.mark.asyncio -async def test_whitespace_only_content(presidio_guardrail, mock_user_api_key, mock_cache): +async def test_whitespace_only_content( + presidio_guardrail, mock_user_api_key, mock_cache +): """ Test that Presidio handles whitespace-only content gracefully. - + Whitespace-only content should be treated the same as empty content. """ test_data = { @@ -832,7 +847,7 @@ async def test_whitespace_only_content(presidio_guardrail, mock_user_api_key, mo async def test_analyze_text_with_empty_string(): """ Test analyze_text method directly with empty string. - + Should return empty list without making API call to Presidio. """ presidio = _OPTIONAL_PresidioPIIMasking( @@ -864,7 +879,7 @@ async def test_analyze_text_with_empty_string(): async def test_analyze_text_error_dict_handling(): """ Test that analyze_text handles error dict responses from Presidio API. - + When Presidio returns {'error': 'No text provided'}, should handle gracefully instead of crashing with TypeError. """ @@ -878,16 +893,20 @@ async def test_analyze_text_error_dict_handling(): class MockResponse: async def json(self): return {"error": "No text provided"} + async def __aenter__(self): return self + async def __aexit__(self, *args): pass - + class MockSession: def post(self, *args, **kwargs): return MockResponse() + async def __aenter__(self): return self + async def __aexit__(self, *args): pass @@ -904,10 +923,12 @@ async def test_analyze_text_error_dict_handling(): @pytest.mark.asyncio -async def test_tool_calling_complete_scenario(presidio_guardrail, mock_user_api_key, mock_cache): +async def test_tool_calling_complete_scenario( + presidio_guardrail, mock_user_api_key, mock_cache +): """ Test complete tool calling scenario with PII in user message. - + This tests the real-world scenario where: 1. User provides a query with PII 2. Assistant responds with empty content + tool_calls @@ -1002,7 +1023,12 @@ def test_no_thresholds_returns_all(): guardrail = _OPTIONAL_PresidioPIIMasking(mock_testing=True) analyze_results = [ {"entity_type": PiiEntityType.CREDIT_CARD, "score": 0.1, "start": 0, "end": 4}, - {"entity_type": PiiEntityType.EMAIL_ADDRESS, "score": 0.2, "start": 5, "end": 9}, + { + "entity_type": PiiEntityType.EMAIL_ADDRESS, + "score": 0.2, + "start": 5, + "end": 9, + }, ] filtered = guardrail.filter_analyze_results_by_score(analyze_results) @@ -1019,7 +1045,12 @@ def test_entity_specific_threshold_only_applies_to_that_entity(): ) analyze_results = [ {"entity_type": PiiEntityType.CREDIT_CARD, "score": 0.7, "start": 0, "end": 4}, - {"entity_type": PiiEntityType.EMAIL_ADDRESS, "score": 0.1, "start": 5, "end": 9}, + { + "entity_type": PiiEntityType.EMAIL_ADDRESS, + "score": 0.1, + "start": 5, + "end": 9, + }, ] filtered = guardrail.filter_analyze_results_by_score(analyze_results) @@ -1038,7 +1069,12 @@ def test_filter_uses_default_all_threshold(): ) analyze_results = [ {"entity_type": PiiEntityType.CREDIT_CARD, "score": 0.7, "start": 0, "end": 4}, - {"entity_type": PiiEntityType.EMAIL_ADDRESS, "score": 0.8, "start": 5, "end": 9}, + { + "entity_type": PiiEntityType.EMAIL_ADDRESS, + "score": 0.8, + "start": 5, + "end": 9, + }, ] filtered = guardrail.filter_analyze_results_by_score(analyze_results) @@ -1059,7 +1095,12 @@ def test_entity_specific_overrides_default_threshold(): ) analyze_results = [ {"entity_type": PiiEntityType.CREDIT_CARD, "score": 0.65, "start": 0, "end": 4}, - {"entity_type": PiiEntityType.EMAIL_ADDRESS, "score": 0.75, "start": 5, "end": 9}, + { + "entity_type": PiiEntityType.EMAIL_ADDRESS, + "score": 0.75, + "start": 5, + "end": 9, + }, ] filtered = guardrail.filter_analyze_results_by_score(analyze_results) @@ -1134,3 +1175,61 @@ def test_update_in_memory_applies_score_thresholds(): guardrail.update_in_memory_litellm_params(params) assert guardrail.presidio_score_thresholds == {PiiEntityType.CREDIT_CARD: 0.85} + + +@pytest.mark.asyncio +async def test_get_session_iterator_thread_safety(presidio_guardrail): + """ + Test that _get_session_iterator yields: + 1. The shared session when in the main thread. + 2. A loop-bound cached session when in a background thread (reused per loop for efficiency). + """ + import threading + + import aiohttp + + # 1. Main Thread Case + # We are in the "main thread" relative to the guardrail initialization + async with presidio_guardrail._get_session_iterator() as session: + assert isinstance(session, aiohttp.ClientSession) + assert session is presidio_guardrail._http_session + shared_session_id = id(session) + + # 2. Background Thread Case + # Define a helper function to run in a thread + def thread_target(loop, result_future): + async def run_in_loop(): + # This runs in the thread's loop + async with presidio_guardrail._get_session_iterator() as session: + return session, id(session) + + try: + # Create a new loop for this thread to run async code + new_loop = asyncio.new_event_loop() + asyncio.set_event_loop(new_loop) + session_obj, session_id = new_loop.run_until_complete(run_in_loop()) + result_future.set_result((session_obj, session_id)) + new_loop.close() + except Exception as e: + result_future.set_exception(e) + + # Run the background thread test + bg_future = asyncio.Future() + t = threading.Thread( + target=thread_target, args=(asyncio.get_running_loop(), bg_future) + ) + t.start() + t.join() + + bg_session, bg_session_id = await bg_future + + # Assertions + # The background session should be DIFFERENT from the shared session + assert bg_session_id != shared_session_id + # The shared session should still be open (not closed by the background thread) + assert not presidio_guardrail._http_session.closed + # The background session should be cached in _loop_sessions and remain open for reuse + # (Changed behavior: no longer closes immediately, cached per loop for efficiency) + assert not bg_session.closed, "Background session should remain open for reuse" + + print("✓ Session iterator thread safety test passed") diff --git a/tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py b/tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py index edfdd9e4065..97ab8355343 100644 --- a/tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py +++ b/tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py @@ -15,6 +15,7 @@ from prisma.errors import ClientNotConnectedError, HTTPClientClosedError, Prisma from litellm.proxy.health_endpoints._health_endpoints import ( _db_health_readiness_check, db_health_cache, + get_callback_identifier, health_license_endpoint, health_services_endpoint, ) @@ -312,6 +313,43 @@ async def test_test_model_connection_loads_config_from_router(): assert "result" in result +@pytest.mark.asyncio +async def test_health_services_endpoint_datadog_llm_observability(): + """ + Verify that 'datadog_llm_observability' is accepted as a valid service + by the /health/services endpoint and does not raise a 400 error. + + Regression test for: https://github.com/BerriAI/litellm/issues/XXXX + The service was missing from the allowed services validation list. + """ + from litellm.proxy.health_endpoints._health_endpoints import ( + health_services_endpoint, + ) + + # Mock datadog_llm_observability to be in success_callback so the generic branch handles it + with patch("litellm.success_callback", ["datadog_llm_observability"]): + result = await health_services_endpoint( + service="datadog_llm_observability" + ) + + # Should not raise HTTPException(400) and should return success + assert result["status"] == "success" + assert "datadog_llm_observability" in result["message"] + + +@pytest.mark.asyncio +async def test_health_services_endpoint_rejects_unknown_service(): + """ + Verify that an unknown service name is rejected with a 400 error. + """ + from litellm.proxy._types import ProxyException + + with pytest.raises(ProxyException): + await health_services_endpoint( + service="totally_unknown_service_xyz" + ) + + @pytest.fixture(scope="function") def proxy_client(monkeypatch): """ @@ -441,3 +479,135 @@ def test_health_readiness(proxy_client): f"Unexpected db status: {db_status}" print("="*60 + "\n") + + +def test_get_callback_identifier_string_and_object_with_callback_name(): + """ + Test get_callback_identifier with string callbacks and objects with callback_name attribute. + + Covers: + - String callback (returned as-is) + - Object with callback_name attribute + - Object with empty/None callback_name (should fall through to other checks) + """ + from litellm.proxy.health_endpoints._health_endpoints import get_callback_identifier + + # Test 1: String callback should be returned as-is + assert get_callback_identifier("datadog") == "datadog" + assert get_callback_identifier("langfuse") == "langfuse" + + # Test 2: Object with callback_name attribute + class MockCallbackWithName: + def __init__(self, name): + self.callback_name = name + + callback_obj = MockCallbackWithName("custom_callback") + assert get_callback_identifier(callback_obj) == "custom_callback" + + # Test 3: Object with empty callback_name should fall through + callback_obj_empty = MockCallbackWithName("") + # This should fall through to CustomLoggerRegistry or callback_name() fallback + # We'll verify it doesn't return empty string + result = get_callback_identifier(callback_obj_empty) + assert result != "" # Should not return empty string + assert isinstance(result, str) # Should still return a string + + +def test_get_callback_identifier_custom_logger_registry_and_fallback(): + """ + Test get_callback_identifier with CustomLoggerRegistry lookup and fallback scenarios. + + Covers: + - Object registered in CustomLoggerRegistry + - Object with callback_name that matches registry entry + - Fallback to callback_name() helper function + """ + from litellm.proxy.health_endpoints._health_endpoints import get_callback_identifier + from litellm.litellm_core_utils.custom_logger_registry import CustomLoggerRegistry + + # Test 1: Object registered in CustomLoggerRegistry (without callback_name attribute) + # Mock a class that's registered in the registry + class MockRegisteredLogger: + pass + + # Mock the registry to return callback strings for our mock class + with patch.object( + CustomLoggerRegistry, + 'get_all_callback_strs_from_class_type', + return_value=['mock_logger'] + ): + mock_instance = MockRegisteredLogger() + result = get_callback_identifier(mock_instance) + assert result == "mock_logger" + + # Test 2: Object with callback_name that matches registry entry + class MockCallbackWithMatchingName: + def __init__(self): + self.callback_name = "matched_name" + + callback_with_matching = MockCallbackWithMatchingName() + # Mock registry to return list containing the matching name + with patch.object( + CustomLoggerRegistry, + 'get_all_callback_strs_from_class_type', + return_value=['matched_name', 'other_name'] + ): + result = get_callback_identifier(callback_with_matching) + assert result == "matched_name" + + # Test 3: Object with falsy callback_name (empty string), should use registry + class MockCallbackWithEmptyName: + def __init__(self): + self.callback_name = "" # Empty string is falsy + + callback_empty = MockCallbackWithEmptyName() + # Mock registry to return list - should use first registry entry since callback_name is falsy + with patch.object( + CustomLoggerRegistry, + 'get_all_callback_strs_from_class_type', + return_value=['registry_name'] + ): + result = get_callback_identifier(callback_empty) + assert result == "registry_name" + + # Test 3b: Object with truthy callback_name not in registry - returns callback_name immediately + # (This tests that truthy callback_name takes precedence over registry) + class MockCallbackWithNonMatchingName: + def __init__(self): + self.callback_name = "non_matching" + + callback_non_matching = MockCallbackWithNonMatchingName() + # Even if registry has different values, truthy callback_name is returned first + with patch.object( + CustomLoggerRegistry, + 'get_all_callback_strs_from_class_type', + return_value=['registry_name'] + ): + result = get_callback_identifier(callback_non_matching) + # Should return callback_name because it's truthy (checked before registry) + assert result == "non_matching" + + # Test 4: Object not in registry, falls back to callback_name() helper + class UnregisteredCallback: + def __init__(self): + pass + + unregistered = UnregisteredCallback() + # Mock registry to return empty list (not registered) + with patch.object( + CustomLoggerRegistry, + 'get_all_callback_strs_from_class_type', + return_value=[] + ): + result = get_callback_identifier(unregistered) + # Should fall back to callback_name() which returns __class__.__name__ + assert result == "UnregisteredCallback" + + # Test 5: Function callback (not a class instance) + def my_callback_function(): + pass + + # Function won't have __class__, so it will skip registry check and go to callback_name() + result = get_callback_identifier(my_callback_function) + # Should fall back to callback_name() which returns __name__ + assert result == "my_callback_function" diff --git a/tests/test_litellm/proxy/hooks/test_post_call_response_headers_hook.py b/tests/test_litellm/proxy/hooks/test_post_call_response_headers_hook.py new file mode 100644 index 00000000000..6a12366fdd3 --- /dev/null +++ b/tests/test_litellm/proxy/hooks/test_post_call_response_headers_hook.py @@ -0,0 +1,197 @@ +""" +Integration tests for async_post_call_response_headers_hook. + +Tests verify that CustomLogger callbacks can inject custom HTTP response headers +into success (streaming and non-streaming) and failure responses. +""" + +import os +import sys +import pytest +from typing import Any, Dict, Optional +from unittest.mock import patch + +sys.path.insert(0, os.path.abspath("../../../..")) + +import litellm +from litellm.integrations.custom_logger import CustomLogger +from litellm.proxy._types import UserAPIKeyAuth + + +class HeaderInjectorLogger(CustomLogger): + """Logger that injects custom headers into responses.""" + + def __init__(self, headers: Optional[Dict[str, str]] = None): + self.headers = headers + self.called = False + self.received_response = None + self.received_data = None + + async def async_post_call_response_headers_hook( + self, + data: dict, + user_api_key_dict: UserAPIKeyAuth, + response: Any, + request_headers: Optional[Dict[str, str]] = None, + ) -> Optional[Dict[str, str]]: + self.called = True + self.received_response = response + self.received_data = data + return self.headers + + +@pytest.mark.asyncio +async def test_response_headers_hook_returns_headers(): + """Test that the hook returns headers from a single callback.""" + injector = HeaderInjectorLogger(headers={"x-custom-id": "abc123"}) + + with patch("litellm.callbacks", [injector]): + from litellm.proxy.utils import ProxyLogging + from litellm.caching.caching import DualCache + + proxy_logging = ProxyLogging(user_api_key_cache=DualCache()) + + result = await proxy_logging.post_call_response_headers_hook( + data={"model": "test-model"}, + user_api_key_dict=UserAPIKeyAuth(api_key="test-key"), + response={"id": "resp-1"}, + ) + + assert injector.called is True + assert result == {"x-custom-id": "abc123"} + + +@pytest.mark.asyncio +async def test_response_headers_hook_returns_none(): + """Test that returning None results in empty headers dict.""" + injector = HeaderInjectorLogger(headers=None) + + with patch("litellm.callbacks", [injector]): + from litellm.proxy.utils import ProxyLogging + from litellm.caching.caching import DualCache + + proxy_logging = ProxyLogging(user_api_key_cache=DualCache()) + + result = await proxy_logging.post_call_response_headers_hook( + data={"model": "test-model"}, + user_api_key_dict=UserAPIKeyAuth(api_key="test-key"), + response={"id": "resp-1"}, + ) + + assert injector.called is True + assert result == {} + + +@pytest.mark.asyncio +async def test_response_headers_hook_multiple_callbacks_merge(): + """Test that headers from multiple callbacks are merged.""" + injector1 = HeaderInjectorLogger(headers={"x-header-a": "value-a"}) + injector2 = HeaderInjectorLogger(headers={"x-header-b": "value-b"}) + + with patch("litellm.callbacks", [injector1, injector2]): + from litellm.proxy.utils import ProxyLogging + from litellm.caching.caching import DualCache + + proxy_logging = ProxyLogging(user_api_key_cache=DualCache()) + + result = await proxy_logging.post_call_response_headers_hook( + data={"model": "test-model"}, + user_api_key_dict=UserAPIKeyAuth(api_key="test-key"), + response=None, + ) + + assert injector1.called is True + assert injector2.called is True + assert result == {"x-header-a": "value-a", "x-header-b": "value-b"} + + +@pytest.mark.asyncio +async def test_response_headers_hook_later_callback_overrides(): + """Test that later callbacks override earlier ones for the same header key.""" + injector1 = HeaderInjectorLogger(headers={"x-request-id": "first"}) + injector2 = HeaderInjectorLogger(headers={"x-request-id": "second"}) + + with patch("litellm.callbacks", [injector1, injector2]): + from litellm.proxy.utils import ProxyLogging + from litellm.caching.caching import DualCache + + proxy_logging = ProxyLogging(user_api_key_cache=DualCache()) + + result = await proxy_logging.post_call_response_headers_hook( + data={"model": "test-model"}, + user_api_key_dict=UserAPIKeyAuth(api_key="test-key"), + response=None, + ) + + assert result == {"x-request-id": "second"} + + +@pytest.mark.asyncio +async def test_response_headers_hook_receives_response_on_success(): + """Test that the hook receives the response object on success.""" + injector = HeaderInjectorLogger(headers={"x-ok": "1"}) + mock_response = {"id": "resp-success", "choices": []} + + with patch("litellm.callbacks", [injector]): + from litellm.proxy.utils import ProxyLogging + from litellm.caching.caching import DualCache + + proxy_logging = ProxyLogging(user_api_key_cache=DualCache()) + + await proxy_logging.post_call_response_headers_hook( + data={"model": "test-model"}, + user_api_key_dict=UserAPIKeyAuth(api_key="test-key"), + response=mock_response, + ) + + assert injector.received_response is mock_response + + +@pytest.mark.asyncio +async def test_response_headers_hook_receives_none_response_on_failure(): + """Test that the hook receives None response for failure cases.""" + injector = HeaderInjectorLogger(headers={"x-error-id": "err-1"}) + + with patch("litellm.callbacks", [injector]): + from litellm.proxy.utils import ProxyLogging + from litellm.caching.caching import DualCache + + proxy_logging = ProxyLogging(user_api_key_cache=DualCache()) + + await proxy_logging.post_call_response_headers_hook( + data={"model": "test-model"}, + user_api_key_dict=UserAPIKeyAuth(api_key="test-key"), + response=None, + ) + + assert injector.received_response is None + + +@pytest.mark.asyncio +async def test_response_headers_hook_no_callbacks(): + """Test that no callbacks results in empty headers.""" + with patch("litellm.callbacks", []): + from litellm.proxy.utils import ProxyLogging + from litellm.caching.caching import DualCache + + proxy_logging = ProxyLogging(user_api_key_cache=DualCache()) + + result = await proxy_logging.post_call_response_headers_hook( + data={"model": "test-model"}, + user_api_key_dict=UserAPIKeyAuth(api_key="test-key"), + response=None, + ) + + assert result == {} + + +@pytest.mark.asyncio +async def test_default_hook_returns_none(): + """Test that the base CustomLogger hook returns None by default.""" + logger = CustomLogger() + result = await logger.async_post_call_response_headers_hook( + data={}, + user_api_key_dict=UserAPIKeyAuth(api_key="test-key"), + response=None, + ) + assert result is None diff --git a/tests/test_litellm/proxy/hooks/test_post_call_streaming_hook_integration.py b/tests/test_litellm/proxy/hooks/test_post_call_streaming_hook_integration.py new file mode 100644 index 00000000000..3bc111ef142 --- /dev/null +++ b/tests/test_litellm/proxy/hooks/test_post_call_streaming_hook_integration.py @@ -0,0 +1,273 @@ +""" +Integration tests for async_post_call_streaming_hook. + +Tests verify that the streaming hook can transform streaming responses sent to clients. +""" + +import os +import sys +import pytest +from typing import Any +from unittest.mock import patch, MagicMock + +sys.path.insert(0, os.path.abspath("../../../..")) + +import litellm +from litellm.integrations.custom_logger import CustomLogger +from litellm.proxy._types import UserAPIKeyAuth +from litellm.types.utils import ModelResponseStream, StreamingChoices, Delta + + +class StreamingResponseTransformerLogger(CustomLogger): + """Logger that transforms streaming responses""" + + def __init__(self, transform_content: str = None): + self.called = False + self.transform_content = transform_content + self.received_response = None + + async def async_post_call_streaming_hook( + self, + user_api_key_dict: UserAPIKeyAuth, + response: str, + ) -> Any: + self.called = True + self.received_response = response + if self.transform_content is not None: + return self.transform_content + return None + + +@pytest.mark.asyncio +async def test_streaming_hook_transforms_response(): + """ + Test that async_post_call_streaming_hook can transform streaming responses. + """ + transformer = StreamingResponseTransformerLogger(transform_content="Modified streaming response") + + with patch("litellm.callbacks", [transformer]): + from litellm.proxy.utils import ProxyLogging + from litellm.caching.caching import DualCache + + proxy_logging = ProxyLogging(user_api_key_cache=DualCache()) + + # Create a mock streaming response + original_response = ModelResponseStream( + id="original-stream", + choices=[ + StreamingChoices( + delta=Delta(content="Original content", role="assistant"), + index=0, + ) + ], + model="test-model", + ) + + data = {"model": "test-model"} + user_api_key_dict = UserAPIKeyAuth(api_key="test-key") + + # Call the hook + result = await proxy_logging.async_post_call_streaming_hook( + data=data, + response=original_response, + user_api_key_dict=user_api_key_dict, + ) + + # Verify hook was called + assert transformer.called is True + + # Verify transformed response is returned + assert result == "Modified streaming response" + + +@pytest.mark.asyncio +async def test_streaming_hook_returns_none_keeps_original(): + """ + Test that hook returning None keeps the original response. + """ + + class NoOpLogger(CustomLogger): + def __init__(self): + self.called = False + + async def async_post_call_streaming_hook( + self, + user_api_key_dict: UserAPIKeyAuth, + response: str, + ): + self.called = True + return None + + logger = NoOpLogger() + + with patch("litellm.callbacks", [logger]): + from litellm.proxy.utils import ProxyLogging + from litellm.caching.caching import DualCache + + proxy_logging = ProxyLogging(user_api_key_cache=DualCache()) + + original_response = ModelResponseStream( + id="original-stream", + choices=[ + StreamingChoices( + delta=Delta(content="Original content", role="assistant"), + index=0, + ) + ], + model="test-model", + ) + + data = {"model": "test-model"} + user_api_key_dict = UserAPIKeyAuth(api_key="test-key") + + result = await proxy_logging.async_post_call_streaming_hook( + data=data, + response=original_response, + user_api_key_dict=user_api_key_dict, + ) + + # Should return original response object + assert result.id == "original-stream" + assert logger.called is True + + +@pytest.mark.asyncio +async def test_streaming_hook_works_with_sse_format(): + """ + Test that hook works with SSE-formatted strings (data: prefix). + This was the only supported format before the fix. + """ + transformer = StreamingResponseTransformerLogger( + transform_content="data: {\"error\": \"custom error\"}\n\n" + ) + + with patch("litellm.callbacks", [transformer]): + from litellm.proxy.utils import ProxyLogging + from litellm.caching.caching import DualCache + + proxy_logging = ProxyLogging(user_api_key_cache=DualCache()) + + original_response = ModelResponseStream( + id="original-stream", + choices=[ + StreamingChoices( + delta=Delta(content="Original content", role="assistant"), + index=0, + ) + ], + model="test-model", + ) + + data = {"model": "test-model"} + user_api_key_dict = UserAPIKeyAuth(api_key="test-key") + + result = await proxy_logging.async_post_call_streaming_hook( + data=data, + response=original_response, + user_api_key_dict=user_api_key_dict, + ) + + # Verify SSE-formatted response is returned + assert result == "data: {\"error\": \"custom error\"}\n\n" + + +@pytest.mark.asyncio +async def test_streaming_hook_chains_multiple_callbacks(): + """ + Test that multiple callbacks can chain modifications. + """ + + class AppendLogger(CustomLogger): + def __init__(self, suffix: str): + self.suffix = suffix + self.called = False + + async def async_post_call_streaming_hook( + self, + user_api_key_dict: UserAPIKeyAuth, + response: str, + ) -> str: + self.called = True + # Note: response here is the complete_response string, not the chunk + return f"[{self.suffix}]" + + callback1 = AppendLogger("CB1") + callback2 = AppendLogger("CB2") + + with patch("litellm.callbacks", [callback1, callback2]): + from litellm.proxy.utils import ProxyLogging + from litellm.caching.caching import DualCache + + proxy_logging = ProxyLogging(user_api_key_cache=DualCache()) + + original_response = ModelResponseStream( + id="original-stream", + choices=[ + StreamingChoices( + delta=Delta(content="Hello", role="assistant"), + index=0, + ) + ], + model="test-model", + ) + + data = {"model": "test-model"} + user_api_key_dict = UserAPIKeyAuth(api_key="test-key") + + result = await proxy_logging.async_post_call_streaming_hook( + data=data, + response=original_response, + user_api_key_dict=user_api_key_dict, + ) + + # Both callbacks should have been called + assert callback1.called is True + assert callback2.called is True + + # Last callback's result should be used + assert result == "[CB2]" + + +@pytest.mark.asyncio +async def test_streaming_hook_handles_exceptions(): + """ + Test that hook exceptions are propagated. + """ + + class FailingLogger(CustomLogger): + async def async_post_call_streaming_hook( + self, + user_api_key_dict: UserAPIKeyAuth, + response: str, + ): + raise RuntimeError("Streaming hook crashed!") + + logger = FailingLogger() + + with patch("litellm.callbacks", [logger]): + from litellm.proxy.utils import ProxyLogging + from litellm.caching.caching import DualCache + + proxy_logging = ProxyLogging(user_api_key_cache=DualCache()) + + original_response = ModelResponseStream( + id="original-stream", + choices=[ + StreamingChoices( + delta=Delta(content="Hello", role="assistant"), + index=0, + ) + ], + model="test-model", + ) + + data = {"model": "test-model"} + user_api_key_dict = UserAPIKeyAuth(api_key="test-key") + + # Exception should be propagated + with pytest.raises(RuntimeError, match="Streaming hook crashed!"): + await proxy_logging.async_post_call_streaming_hook( + data=data, + response=original_response, + user_api_key_dict=user_api_key_dict, + ) diff --git a/tests/test_litellm/proxy/hooks/test_post_call_success_hook_integration.py b/tests/test_litellm/proxy/hooks/test_post_call_success_hook_integration.py new file mode 100644 index 00000000000..870286f5382 --- /dev/null +++ b/tests/test_litellm/proxy/hooks/test_post_call_success_hook_integration.py @@ -0,0 +1,260 @@ +""" +Integration tests for async_post_call_success_hook. + +Tests verify that the success hook can transform responses sent to clients. +This mirrors the behavior of CustomGuardrail hooks and streaming iterator hooks. +""" + +import os +import sys +import pytest +from typing import Any +from unittest.mock import patch, MagicMock + +sys.path.insert(0, os.path.abspath("../../../..")) + +import litellm +from litellm.integrations.custom_logger import CustomLogger +from litellm.proxy._types import UserAPIKeyAuth +from litellm.types.utils import ModelResponse, Choices, Message, Usage + + +class ResponseTransformerLogger(CustomLogger): + """Logger that transforms successful responses""" + + def __init__(self, transform_content: str = None): + self.called = False + self.transform_content = transform_content + + async def async_post_call_success_hook( + self, + data: dict, + user_api_key_dict: UserAPIKeyAuth, + response: Any, + ) -> Any: + self.called = True + if self.transform_content is not None: + # Create a modified response with custom content + return { + "id": "transformed-response", + "choices": [ + { + "message": {"content": self.transform_content, "role": "assistant"}, + "index": 0, + } + ], + "model": "test-model", + "custom_field": "added_by_hook", + } + return response + + +@pytest.mark.asyncio +async def test_success_hook_transforms_response(): + """ + Test that async_post_call_success_hook can transform successful responses. + """ + transformer = ResponseTransformerLogger(transform_content="Modified response") + + with patch("litellm.callbacks", [transformer]): + from litellm.proxy.utils import ProxyLogging + from litellm.caching.caching import DualCache + + proxy_logging = ProxyLogging(user_api_key_cache=DualCache()) + + # Create a mock response + original_response = ModelResponse( + id="original-response", + choices=[ + Choices( + message=Message(content="Original content", role="assistant"), + index=0, + finish_reason="stop", + ) + ], + model="test-model", + usage=Usage(prompt_tokens=10, completion_tokens=20, total_tokens=30), + ) + + data = {"model": "test-model"} + user_api_key_dict = UserAPIKeyAuth(api_key="test-key") + + # Call the hook + result = await proxy_logging.post_call_success_hook( + data=data, + response=original_response, + user_api_key_dict=user_api_key_dict, + ) + + # Verify hook was called + assert transformer.called is True + + # Verify transformed response is returned + assert result is not None + assert result["id"] == "transformed-response" + assert result["choices"][0]["message"]["content"] == "Modified response" + assert result["custom_field"] == "added_by_hook" + + +@pytest.mark.asyncio +async def test_success_hook_returns_none_keeps_original(): + """ + Test that hook returning None keeps the original response. + """ + + class NoOpLogger(CustomLogger): + def __init__(self): + self.called = False + + async def async_post_call_success_hook(self, *args, **kwargs): + self.called = True + return None + + logger = NoOpLogger() + + with patch("litellm.callbacks", [logger]): + from litellm.proxy.utils import ProxyLogging + from litellm.caching.caching import DualCache + + proxy_logging = ProxyLogging(user_api_key_cache=DualCache()) + + original_response = ModelResponse( + id="original-response", + choices=[ + Choices( + message=Message(content="Original content", role="assistant"), + index=0, + finish_reason="stop", + ) + ], + model="test-model", + usage=Usage(prompt_tokens=10, completion_tokens=20, total_tokens=30), + ) + + data = {"model": "test-model"} + user_api_key_dict = UserAPIKeyAuth(api_key="test-key") + + result = await proxy_logging.post_call_success_hook( + data=data, + response=original_response, + user_api_key_dict=user_api_key_dict, + ) + + # Should return original response + assert result.id == "original-response" + assert logger.called is True + + +@pytest.mark.asyncio +async def test_success_hook_chains_multiple_callbacks(): + """ + Test that multiple callbacks can chain modifications. + """ + + class AddFieldLogger(CustomLogger): + def __init__(self, field_name: str, field_value: Any): + self.field_name = field_name + self.field_value = field_value + self.called = False + + async def async_post_call_success_hook( + self, + data: dict, + user_api_key_dict: UserAPIKeyAuth, + response: Any, + ) -> Any: + self.called = True + # Convert response to dict if needed + if hasattr(response, "model_dump"): + resp_dict = response.model_dump() + elif hasattr(response, "dict"): + resp_dict = response.dict() + elif isinstance(response, dict): + resp_dict = response.copy() + else: + resp_dict = {} + + resp_dict[self.field_name] = self.field_value + return resp_dict + + callback1 = AddFieldLogger("field1", "value1") + callback2 = AddFieldLogger("field2", "value2") + + with patch("litellm.callbacks", [callback1, callback2]): + from litellm.proxy.utils import ProxyLogging + from litellm.caching.caching import DualCache + + proxy_logging = ProxyLogging(user_api_key_cache=DualCache()) + + original_response = ModelResponse( + id="original-response", + choices=[ + Choices( + message=Message(content="Original content", role="assistant"), + index=0, + finish_reason="stop", + ) + ], + model="test-model", + usage=Usage(prompt_tokens=10, completion_tokens=20, total_tokens=30), + ) + + data = {"model": "test-model"} + user_api_key_dict = UserAPIKeyAuth(api_key="test-key") + + result = await proxy_logging.post_call_success_hook( + data=data, + response=original_response, + user_api_key_dict=user_api_key_dict, + ) + + # Both callbacks should have been called + assert callback1.called is True + assert callback2.called is True + + # Both fields should be present (chained modifications) + assert result["field1"] == "value1" + assert result["field2"] == "value2" + + +@pytest.mark.asyncio +async def test_success_hook_handles_exceptions(): + """ + Test that hook exceptions are propagated (not silently swallowed). + """ + + class FailingLogger(CustomLogger): + async def async_post_call_success_hook(self, *args, **kwargs): + raise RuntimeError("Hook crashed!") + + logger = FailingLogger() + + with patch("litellm.callbacks", [logger]): + from litellm.proxy.utils import ProxyLogging + from litellm.caching.caching import DualCache + + proxy_logging = ProxyLogging(user_api_key_cache=DualCache()) + + original_response = ModelResponse( + id="original-response", + choices=[ + Choices( + message=Message(content="Original content", role="assistant"), + index=0, + finish_reason="stop", + ) + ], + model="test-model", + usage=Usage(prompt_tokens=10, completion_tokens=20, total_tokens=30), + ) + + data = {"model": "test-model"} + user_api_key_dict = UserAPIKeyAuth(api_key="test-key") + + # Exception should be propagated + with pytest.raises(RuntimeError, match="Hook crashed!"): + await proxy_logging.post_call_success_hook( + data=data, + response=original_response, + user_api_key_dict=user_api_key_dict, + ) diff --git a/tests/test_litellm/proxy/management_endpoints/scim/test_scim_v2_discovery.py b/tests/test_litellm/proxy/management_endpoints/scim/test_scim_v2_discovery.py new file mode 100644 index 00000000000..2162d6e188d --- /dev/null +++ b/tests/test_litellm/proxy/management_endpoints/scim/test_scim_v2_discovery.py @@ -0,0 +1,300 @@ +""" +Tests for SCIM v2 resource discovery endpoints: +- GET /scim/v2 (base endpoint) +- GET /scim/v2/ResourceTypes +- GET /scim/v2/ResourceTypes/{id} +- GET /scim/v2/Schemas +- GET /scim/v2/Schemas/{uri} +""" + +from unittest.mock import AsyncMock, MagicMock + +import pytest +from fastapi import HTTPException + +from litellm.proxy.management_endpoints.scim.scim_v2 import ( + _get_resource_types, + _get_schemas, + get_resource_type, + get_resource_types, + get_schema, + get_schemas, + get_scim_base, +) +from litellm.types.proxy.management_endpoints.scim_v2 import ( + SCIMResourceType, + SCIMSchema, +) + + +def _make_mock_request(base_url="http://localhost:4000/", url="http://localhost:4000/scim/v2"): + """Create a mock FastAPI Request object.""" + request = MagicMock() + request.method = "GET" + request.url = url + request.base_url = base_url + return request + + +# ---- Helper function tests ---- + + +class TestGetResourceTypes: + def test_returns_user_and_group(self): + resource_types = _get_resource_types() + assert len(resource_types) == 2 + ids = [rt.id for rt in resource_types] + assert "User" in ids + assert "Group" in ids + + def test_user_resource_type_fields(self): + resource_types = _get_resource_types() + user_rt = next(rt for rt in resource_types if rt.id == "User") + assert user_rt.name == "User" + assert user_rt.endpoint == "/Users" + assert user_rt.schema_ == "urn:ietf:params:scim:schemas:core:2.0:User" + assert user_rt.schemas == ["urn:ietf:params:scim:schemas:core:2.0:ResourceType"] + + def test_group_resource_type_fields(self): + resource_types = _get_resource_types() + group_rt = next(rt for rt in resource_types if rt.id == "Group") + assert group_rt.name == "Group" + assert group_rt.endpoint == "/Groups" + assert group_rt.schema_ == "urn:ietf:params:scim:schemas:core:2.0:Group" + + def test_custom_base_url(self): + resource_types = _get_resource_types("https://example.com/scim/v2") + user_rt = next(rt for rt in resource_types if rt.id == "User") + assert user_rt.meta["location"] == "https://example.com/scim/v2/ResourceTypes/User" + + def test_model_dump_uses_schema_key(self): + """Ensure model_dump() outputs 'schema' not 'schema_'.""" + resource_types = _get_resource_types() + dumped = resource_types[0].model_dump() + assert "schema" in dumped + assert "schema_" not in dumped + + +class TestGetSchemas: + def test_returns_user_and_group_schemas(self): + schemas = _get_schemas() + assert len(schemas) == 2 + ids = [s.id for s in schemas] + assert "urn:ietf:params:scim:schemas:core:2.0:User" in ids + assert "urn:ietf:params:scim:schemas:core:2.0:Group" in ids + + def test_user_schema_has_required_attributes(self): + schemas = _get_schemas() + user_schema = next( + s for s in schemas if s.id == "urn:ietf:params:scim:schemas:core:2.0:User" + ) + attr_names = [a.name for a in user_schema.attributes] + assert "userName" in attr_names + assert "name" in attr_names + assert "emails" in attr_names + assert "active" in attr_names + assert "groups" in attr_names + + def test_group_schema_has_required_attributes(self): + schemas = _get_schemas() + group_schema = next( + s for s in schemas if s.id == "urn:ietf:params:scim:schemas:core:2.0:Group" + ) + attr_names = [a.name for a in group_schema.attributes] + assert "displayName" in attr_names + assert "members" in attr_names + + def test_schema_meta_fields(self): + schemas = _get_schemas() + user_schema = next( + s for s in schemas if s.id == "urn:ietf:params:scim:schemas:core:2.0:User" + ) + assert user_schema.meta is not None + assert user_schema.meta["resourceType"] == "Schema" + + +# ---- Endpoint tests ---- + + +class TestGetScimBase: + @pytest.mark.asyncio + async def test_returns_list_response(self): + request = _make_mock_request() + result = await get_scim_base(request) + + assert result["schemas"] == ["urn:ietf:params:scim:api:messages:2.0:ListResponse"] + assert result["totalResults"] == 2 + assert len(result["Resources"]) == 2 + + @pytest.mark.asyncio + async def test_resources_contain_user_and_group(self): + request = _make_mock_request() + result = await get_scim_base(request) + + resource_ids = [r["id"] for r in result["Resources"]] + assert "User" in resource_ids + assert "Group" in resource_ids + + @pytest.mark.asyncio + async def test_resources_have_schema_field(self): + """Each resource should have 'schema' (not 'schema_') per SCIM spec.""" + request = _make_mock_request() + result = await get_scim_base(request) + + for resource in result["Resources"]: + assert "schema" in resource + assert "schema_" not in resource + + @pytest.mark.asyncio + async def test_location_uses_base_url(self): + request = _make_mock_request(base_url="https://proxy.example.com/") + result = await get_scim_base(request) + + user_resource = next(r for r in result["Resources"] if r["id"] == "User") + assert user_resource["meta"]["location"] == "https://proxy.example.com/scim/v2/ResourceTypes/User" + + +class TestGetResourceTypesEndpoint: + @pytest.mark.asyncio + async def test_returns_list_response(self): + request = _make_mock_request() + result = await get_resource_types(request) + + assert result["schemas"] == ["urn:ietf:params:scim:api:messages:2.0:ListResponse"] + assert result["totalResults"] == 2 + + @pytest.mark.asyncio + async def test_resources_match_base_endpoint(self): + """ResourceTypes endpoint should return same data as base endpoint.""" + request = _make_mock_request() + base_result = await get_scim_base(request) + rt_result = await get_resource_types(request) + + assert base_result["totalResults"] == rt_result["totalResults"] + assert len(base_result["Resources"]) == len(rt_result["Resources"]) + + +class TestGetResourceTypeById: + @pytest.mark.asyncio + async def test_get_user_resource_type(self): + request = _make_mock_request() + result = await get_resource_type(request, resource_type_id="User") + + assert result["id"] == "User" + assert result["name"] == "User" + assert result["endpoint"] == "/Users" + assert result["schema"] == "urn:ietf:params:scim:schemas:core:2.0:User" + + @pytest.mark.asyncio + async def test_get_group_resource_type(self): + request = _make_mock_request() + result = await get_resource_type(request, resource_type_id="Group") + + assert result["id"] == "Group" + assert result["name"] == "Group" + assert result["endpoint"] == "/Groups" + + @pytest.mark.asyncio + async def test_not_found(self): + request = _make_mock_request() + with pytest.raises(HTTPException) as exc_info: + await get_resource_type(request, resource_type_id="NonExistent") + assert exc_info.value.status_code == 404 + + +class TestGetSchemasEndpoint: + @pytest.mark.asyncio + async def test_returns_list_response(self): + request = _make_mock_request() + result = await get_schemas(request) + + assert result["schemas"] == ["urn:ietf:params:scim:api:messages:2.0:ListResponse"] + assert result["totalResults"] == 2 + + @pytest.mark.asyncio + async def test_resources_have_correct_ids(self): + request = _make_mock_request() + result = await get_schemas(request) + + schema_ids = [r["id"] for r in result["Resources"]] + assert "urn:ietf:params:scim:schemas:core:2.0:User" in schema_ids + assert "urn:ietf:params:scim:schemas:core:2.0:Group" in schema_ids + + +class TestGetSchemaById: + @pytest.mark.asyncio + async def test_get_user_schema(self): + request = _make_mock_request() + result = await get_schema( + request, schema_id="urn:ietf:params:scim:schemas:core:2.0:User" + ) + + assert result["id"] == "urn:ietf:params:scim:schemas:core:2.0:User" + assert result["name"] == "User" + assert len(result["attributes"]) > 0 + + @pytest.mark.asyncio + async def test_get_group_schema(self): + request = _make_mock_request() + result = await get_schema( + request, schema_id="urn:ietf:params:scim:schemas:core:2.0:Group" + ) + + assert result["id"] == "urn:ietf:params:scim:schemas:core:2.0:Group" + assert result["name"] == "Group" + + @pytest.mark.asyncio + async def test_not_found(self): + request = _make_mock_request() + with pytest.raises(HTTPException) as exc_info: + await get_schema(request, schema_id="urn:nonexistent:schema") + assert exc_info.value.status_code == 404 + + +class TestSCIMResourceTypeModel: + """Test the SCIMResourceType Pydantic model itself.""" + + def test_model_dump_schema_key(self): + rt = SCIMResourceType( + id="Test", + name="Test", + endpoint="/Test", + schema_="urn:test", + ) + dumped = rt.model_dump() + assert "schema" in dumped + assert "schema_" not in dumped + assert dumped["schema"] == "urn:test" + + def test_no_schema_extensions_omitted(self): + rt = SCIMResourceType( + id="Test", + name="Test", + endpoint="/Test", + schema_="urn:test", + ) + dumped = rt.model_dump() + assert "schemaExtensions" not in dumped + + +class TestSCIMSchemaModel: + """Test the SCIMSchema Pydantic model.""" + + def test_basic_schema(self): + schema = SCIMSchema( + id="urn:test", + name="Test", + description="A test schema", + ) + assert schema.id == "urn:test" + assert schema.attributes == [] + + def test_sub_attributes_omitted_when_none(self): + from litellm.types.proxy.management_endpoints.scim_v2 import SCIMSchemaAttribute + + attr = SCIMSchemaAttribute( + name="test", + type="string", + ) + dumped = attr.model_dump() + assert "subAttributes" not in dumped diff --git a/tests/test_litellm/proxy/management_endpoints/test_internal_user_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_internal_user_endpoints.py index 397a6af556f..dc436bac087 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_internal_user_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_internal_user_endpoints.py @@ -1096,3 +1096,57 @@ async def test_get_users_user_id_partial_match(mocker): assert "user_id" in captured_where_conditions assert "in" in captured_where_conditions["user_id"] assert captured_where_conditions["user_id"]["in"] == ["user1", "user2", "user3"] + + +def test_update_internal_user_params_reset_max_budget_with_none(): + """ + Test that _update_internal_user_params allows setting max_budget to None. + This verifies the fix for unsetting/resetting the budget to unlimited. + """ + + # Case 1: max_budget is explicitly None in the input dictionary + data_json = {"max_budget": None, "user_id": "test_user"} + data = UpdateUserRequest(max_budget=None, user_id="test_user") + + # Call the function + non_default_values = _update_internal_user_params(data_json=data_json, data=data) + + # Assertions + assert "max_budget" in non_default_values + assert non_default_values["max_budget"] is None + assert non_default_values["user_id"] == "test_user" + + +def test_update_internal_user_params_ignores_other_nones(): + """ + Test that other fields are still filtered out if None + """ + # Create test data with other None fields + data_json = {"user_alias": None, "user_id": "test_user", "max_budget": 100.0} + data = UpdateUserRequest(user_alias=None, user_id="test_user", max_budget=100.0) + + # Call the function + non_default_values = _update_internal_user_params(data_json=data_json, data=data) + + # Assertions + assert "user_alias" not in non_default_values + assert non_default_values["max_budget"] == 100.0 + + +def test_generate_request_base_validator(): + """ + Test that GenerateRequestBase validator converts empty string to None for max_budget + """ + from litellm.proxy._types import GenerateRequestBase + + # Test with empty string + req = GenerateRequestBase(max_budget="") + assert req.max_budget is None + + # Test with actual float + req = GenerateRequestBase(max_budget=100.0) + assert req.max_budget == 100.0 + + # Test with None + req = GenerateRequestBase(max_budget=None) + assert req.max_budget is None \ No newline at end of file diff --git a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py index 7d31f762096..e90fb277eed 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py @@ -19,10 +19,12 @@ from litellm.proxy._types import ( LiteLLM_BudgetTable, LiteLLM_OrganizationTable, LiteLLM_TeamTableCachedObj, + LiteLLM_UserTable, LiteLLM_VerificationToken, LitellmUserRoles, Member, ProxyException, + ResetSpendRequest, UpdateKeyRequest, ) from litellm.proxy.auth.user_api_key_auth import UserAPIKeyAuth @@ -30,10 +32,14 @@ from litellm.proxy.management_endpoints.key_management_endpoints import ( _check_org_key_limits, _check_team_key_limits, _common_key_generation_helper, + _get_and_validate_existing_key, _list_key_helper, _persist_deleted_verification_tokens, + _process_single_key_update, _save_deleted_verification_token_records, _transform_verification_tokens_to_deleted_records, + _validate_max_budget, + _validate_reset_spend_value, can_modify_verification_token, check_org_key_model_specific_limits, check_team_key_model_specific_limits, @@ -41,6 +47,8 @@ from litellm.proxy.management_endpoints.key_management_endpoints import ( generate_key_helper_fn, list_keys, prepare_key_update_data, + reset_key_spend_fn, + validate_key_list_check, validate_key_team_change, ) from litellm.proxy.proxy_server import app @@ -357,8 +365,8 @@ async def test_budget_reset_and_expires_at_first_of_month(monkeypatch): expires = response.get("expires") assert expires is not None, "expires not found in response" # expires should be approximately 1 month from now (same day next month, same time) - # Allow for some variance due to test execution time - expected_expires_min = now + timedelta(days=28) + # Allow for some variance due to test execution time (subtract 1 second buffer for timing) + expected_expires_min = now + timedelta(days=28, seconds=-1) expected_expires_max = now + timedelta(days=32) assert ( expected_expires_min <= expires <= expected_expires_max @@ -4007,8 +4015,8 @@ async def test_list_keys_with_invalid_status(): mock_prisma_client = AsyncMock() # Mock the endpoint function directly to test validation - from litellm.proxy.management_endpoints.key_management_endpoints import list_keys from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth + from litellm.proxy.management_endpoints.key_management_endpoints import list_keys from litellm.proxy.utils import ProxyException mock_request = Mock() @@ -4223,3 +4231,1163 @@ async def test_update_key_with_router_settings(monkeypatch): # Verify router_settings can be deserialized and matches input deserialized_settings = json.loads(result["router_settings"]) assert deserialized_settings == router_settings_data + + +@pytest.mark.asyncio +async def test_validate_max_budget(): + """ + Test _validate_max_budget helper function. + + Tests: + 1. Positive max_budget should pass + 2. Zero max_budget should pass + 3. Negative max_budget should raise HTTPException + 4. None max_budget should pass + """ + from fastapi import HTTPException + + # Test Case 1: Positive max_budget should pass + try: + _validate_max_budget(100.0) + _validate_max_budget(0.0) + except HTTPException: + pytest.fail("_validate_max_budget raised HTTPException for valid values") + + # Test Case 2: None max_budget should pass + try: + _validate_max_budget(None) + except HTTPException: + pytest.fail("_validate_max_budget raised HTTPException for None") + + # Test Case 3: Negative max_budget should raise HTTPException + with pytest.raises(HTTPException) as exc_info: + _validate_max_budget(-10.0) + + assert exc_info.value.status_code == 400 + assert "max_budget cannot be negative" in str(exc_info.value.detail) + + +@pytest.mark.asyncio +async def test_get_and_validate_existing_key(): + """ + Test _get_and_validate_existing_key helper function. + + Tests: + 1. Successfully retrieve existing key + 2. Key not found raises HTTPException + 3. Database not connected raises HTTPException + """ + from fastapi import HTTPException + + # Test Case 1: Successfully retrieve existing key + mock_prisma_client = AsyncMock() + mock_key = LiteLLM_VerificationToken( + token="test-key-123", + user_id="user-123", + models=["gpt-4"], + team_id=None, + ) + mock_prisma_client.get_data = AsyncMock(return_value=mock_key) + + result = await _get_and_validate_existing_key( + token="test-key-123", + prisma_client=mock_prisma_client, + ) + + assert result == mock_key + mock_prisma_client.get_data.assert_called_once_with( + token="test-key-123", + table_name="key", + query_type="find_unique", + ) + + # Test Case 2: Key not found raises HTTPException + mock_prisma_client.get_data = AsyncMock(return_value=None) + + with pytest.raises(HTTPException) as exc_info: + await _get_and_validate_existing_key( + token="non-existent-key", + prisma_client=mock_prisma_client, + ) + + assert exc_info.value.status_code == 404 + assert "Key not found" in str(exc_info.value.detail) + + # Test Case 3: Database not connected raises HTTPException + with pytest.raises(HTTPException) as exc_info: + await _get_and_validate_existing_key( + token="test-key-123", + prisma_client=None, + ) + + assert exc_info.value.status_code == 500 + assert "Database not connected" in str(exc_info.value.detail) + + +@pytest.mark.asyncio +async def test_process_single_key_update(): + """ + Test _process_single_key_update helper function. + + Tests successful key update with all validations passing. + """ + from litellm.types.proxy.management_endpoints.key_management_endpoints import ( + BulkUpdateKeyRequestItem, + ) + + # Setup mocks + mock_prisma_client = AsyncMock() + mock_user_api_key_cache = MagicMock() + mock_proxy_logging_obj = MagicMock() + mock_llm_router = MagicMock() + + # Mock existing key + existing_key = LiteLLM_VerificationToken( + token="test-key-123", + user_id="user-123", + models=["gpt-4"], + team_id=None, + max_budget=None, + tags=None, + ) + + # Mock updated key response + updated_key_data = { + "user_id": "user-123", + "models": ["gpt-4"], + "team_id": None, + "max_budget": 100.0, + "tags": ["production"], + } + + mock_prisma_client.get_data = AsyncMock(return_value=existing_key) + mock_updated_key_obj = MagicMock() + mock_updated_key_obj.model_dump.return_value = updated_key_data + mock_prisma_client.update_data = AsyncMock( + return_value={"data": mock_updated_key_obj} + ) + + # Mock prepare_key_update_data + with patch( + "litellm.proxy.management_endpoints.key_management_endpoints.prepare_key_update_data" + ) as mock_prepare: + mock_prepare.return_value = {"max_budget": 100.0, "tags": ["production"]} + + # Mock TeamMemberPermissionChecks + with patch( + "litellm.proxy.management_endpoints.key_management_endpoints.TeamMemberPermissionChecks.can_team_member_execute_key_management_endpoint" + ) as mock_permission_check: + mock_permission_check.return_value = None + + # Mock _delete_cache_key_object + with patch( + "litellm.proxy.management_endpoints.key_management_endpoints._delete_cache_key_object" + ) as mock_delete_cache: + mock_delete_cache.return_value = None + + # Mock hash_token (imported from litellm.proxy._types) + with patch( + "litellm.proxy._types.hash_token" + ) as mock_hash: + mock_hash.return_value = "hashed-test-key-123" + + # Mock KeyManagementEventHooks + with patch( + "litellm.proxy.management_endpoints.key_management_endpoints.KeyManagementEventHooks.async_key_updated_hook" + ): + # Create update request + key_update_item = BulkUpdateKeyRequestItem( + key="test-key-123", + max_budget=100.0, + tags=["production"], + ) + + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.PROXY_ADMIN, + api_key="sk-admin", + user_id="admin-user", + ) + + # Call the function + result = await _process_single_key_update( + key_update_item=key_update_item, + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + prisma_client=mock_prisma_client, + user_api_key_cache=mock_user_api_key_cache, + proxy_logging_obj=mock_proxy_logging_obj, + llm_router=mock_llm_router, + ) + + # Verify results + assert result is not None + assert "token" not in result # Token should be removed + assert result.get("max_budget") == 100.0 + assert result.get("tags") == ["production"] + + # Verify mocks were called + mock_prisma_client.get_data.assert_called_once() + mock_prisma_client.update_data.assert_called_once() + mock_delete_cache.assert_called_once() + + +@pytest.mark.asyncio +async def test_bulk_update_keys_success(monkeypatch): + """ + Test /key/bulk_update endpoint with successful updates. + + Tests: + 1. Multiple keys updated successfully + 2. Response contains correct counts and data + """ + from litellm.proxy.management_endpoints.key_management_endpoints import ( + bulk_update_keys, + ) + from litellm.proxy.proxy_server import ( + llm_router, + prisma_client, + proxy_logging_obj, + user_api_key_cache, + ) + from litellm.types.proxy.management_endpoints.key_management_endpoints import ( + BulkUpdateKeyRequest, + BulkUpdateKeyRequestItem, + ) + + # Setup mocks + mock_prisma_client = AsyncMock() + mock_user_api_key_cache = MagicMock() + mock_proxy_logging_obj = MagicMock() + mock_llm_router = MagicMock() + + # Mock existing keys + existing_key_1 = LiteLLM_VerificationToken( + token="test-key-1", + user_id="user-123", + models=["gpt-4"], + team_id=None, + max_budget=None, + ) + existing_key_2 = LiteLLM_VerificationToken( + token="test-key-2", + user_id="user-123", + models=["gpt-3.5-turbo"], + team_id=None, + max_budget=50.0, + ) + + # Mock updated key responses + updated_key_1_data = { + "user_id": "user-123", + "models": ["gpt-4"], + "max_budget": 100.0, + "tags": ["production"], + } + updated_key_2_data = { + "user_id": "user-123", + "models": ["gpt-3.5-turbo"], + "max_budget": 200.0, + "tags": ["staging"], + } + + mock_prisma_client.get_data = AsyncMock( + side_effect=[existing_key_1, existing_key_2] + ) + mock_updated_key_1_obj = MagicMock() + mock_updated_key_1_obj.model_dump.return_value = updated_key_1_data + mock_updated_key_2_obj = MagicMock() + mock_updated_key_2_obj.model_dump.return_value = updated_key_2_data + mock_prisma_client.update_data = AsyncMock( + side_effect=[ + {"data": mock_updated_key_1_obj}, + {"data": mock_updated_key_2_obj}, + ] + ) + + # Patch dependencies + monkeypatch.setattr( + "litellm.proxy.proxy_server.prisma_client", mock_prisma_client + ) + monkeypatch.setattr( + "litellm.proxy.proxy_server.user_api_key_cache", mock_user_api_key_cache + ) + monkeypatch.setattr( + "litellm.proxy.proxy_server.proxy_logging_obj", mock_proxy_logging_obj + ) + monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", mock_llm_router) + + # Mock helper functions + with patch( + "litellm.proxy.management_endpoints.key_management_endpoints.prepare_key_update_data" + ) as mock_prepare: + mock_prepare.side_effect = [ + {"max_budget": 100.0, "tags": ["production"]}, + {"max_budget": 200.0, "tags": ["staging"]}, + ] + + with patch( + "litellm.proxy.management_endpoints.key_management_endpoints.TeamMemberPermissionChecks.can_team_member_execute_key_management_endpoint" + ): + with patch( + "litellm.proxy.management_endpoints.key_management_endpoints._delete_cache_key_object" + ): + with patch( + "litellm.proxy._types.hash_token" + ) as mock_hash: + mock_hash.side_effect = ["hashed-key-1", "hashed-key-2"] + + with patch( + "litellm.proxy.management_endpoints.key_management_endpoints.KeyManagementEventHooks.async_key_updated_hook" + ): + # Create request + request_data = BulkUpdateKeyRequest( + keys=[ + BulkUpdateKeyRequestItem( + key="test-key-1", + max_budget=100.0, + tags=["production"], + ), + BulkUpdateKeyRequestItem( + key="test-key-2", + max_budget=200.0, + tags=["staging"], + ), + ] + ) + + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.PROXY_ADMIN, + api_key="sk-admin", + user_id="admin-user", + ) + + # Call endpoint + response = await bulk_update_keys( + data=request_data, + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + + # Verify response + assert response.total_requested == 2 + assert len(response.successful_updates) == 2 + assert len(response.failed_updates) == 0 + assert response.successful_updates[0].key == "test-key-1" + assert response.successful_updates[1].key == "test-key-2" + + +@pytest.mark.asyncio +async def test_bulk_update_keys_partial_failures(monkeypatch): + """ + Test /key/bulk_update endpoint with partial failures. + + Tests: + 1. Some keys update successfully, others fail + 2. Response contains both successful and failed updates + 3. Failed updates include error messages + """ + from litellm.proxy.management_endpoints.key_management_endpoints import ( + bulk_update_keys, + ) + from litellm.types.proxy.management_endpoints.key_management_endpoints import ( + BulkUpdateKeyRequest, + BulkUpdateKeyRequestItem, + ) + + # Setup mocks + mock_prisma_client = AsyncMock() + mock_user_api_key_cache = MagicMock() + mock_proxy_logging_obj = MagicMock() + mock_llm_router = MagicMock() + + # Mock existing keys + existing_key_1 = LiteLLM_VerificationToken( + token="test-key-1", + user_id="user-123", + models=["gpt-4"], + team_id=None, + max_budget=None, + ) + + # Mock updated key response for successful update + updated_key_1_data = { + "user_id": "user-123", + "models": ["gpt-4"], + "max_budget": 100.0, + "tags": ["production"], + } + + # First key exists, second key doesn't exist + mock_prisma_client.get_data = AsyncMock( + side_effect=[existing_key_1, None] # Second key not found + ) + mock_updated_key_1_obj = MagicMock() + mock_updated_key_1_obj.model_dump.return_value = updated_key_1_data + mock_prisma_client.update_data = AsyncMock( + return_value={"data": mock_updated_key_1_obj} + ) + + # Patch dependencies + monkeypatch.setattr( + "litellm.proxy.proxy_server.prisma_client", mock_prisma_client + ) + monkeypatch.setattr( + "litellm.proxy.proxy_server.user_api_key_cache", mock_user_api_key_cache + ) + monkeypatch.setattr( + "litellm.proxy.proxy_server.proxy_logging_obj", mock_proxy_logging_obj + ) + monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", mock_llm_router) + + # Mock helper functions + with patch( + "litellm.proxy.management_endpoints.key_management_endpoints.prepare_key_update_data" + ) as mock_prepare: + mock_prepare.return_value = {"max_budget": 100.0, "tags": ["production"]} + + with patch( + "litellm.proxy.management_endpoints.key_management_endpoints.TeamMemberPermissionChecks.can_team_member_execute_key_management_endpoint" + ): + with patch( + "litellm.proxy.management_endpoints.key_management_endpoints._delete_cache_key_object" + ): + with patch( + "litellm.proxy._types.hash_token" + ) as mock_hash: + mock_hash.return_value = "hashed-key-1" + + with patch( + "litellm.proxy.management_endpoints.key_management_endpoints.KeyManagementEventHooks.async_key_updated_hook" + ): + # Create request with one valid and one invalid key + request_data = BulkUpdateKeyRequest( + keys=[ + BulkUpdateKeyRequestItem( + key="test-key-1", + max_budget=100.0, + tags=["production"], + ), + BulkUpdateKeyRequestItem( + key="non-existent-key", + max_budget=200.0, + tags=["staging"], + ), + ] + ) + + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.PROXY_ADMIN, + api_key="sk-admin", + user_id="admin-user", + ) + + # Call endpoint + response = await bulk_update_keys( + data=request_data, + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + + # Verify response + assert response.total_requested == 2 + assert len(response.successful_updates) == 1 + assert len(response.failed_updates) == 1 + assert response.successful_updates[0].key == "test-key-1" + assert response.failed_updates[0].key == "non-existent-key" + assert "Key not found" in response.failed_updates[0].failed_reason + + +@pytest.mark.parametrize( + "reset_to,key_spend,key_max_budget,budget_max_budget,expected_error", + [ + ("not_a_number", 100.0, None, None, "reset_to must be a float"), + (None, 100.0, None, None, "reset_to must be a float"), + ([], 100.0, None, None, "reset_to must be a float"), + ({}, 100.0, None, None, "reset_to must be a float"), + (-1.0, 100.0, None, None, "reset_to must be >= 0"), + (-0.1, 100.0, None, None, "reset_to must be >= 0"), + (101.0, 100.0, None, None, "reset_to (101.0) must be <= current spend (100.0)"), + (150.0, 100.0, None, None, "reset_to (150.0) must be <= current spend (100.0)"), + (50.0, 100.0, 30.0, None, "reset_to (50.0) must be <= budget (30.0)"), + ], +) +def test_validate_reset_spend_value_invalid( + reset_to, key_spend, key_max_budget, budget_max_budget, expected_error +): + key_in_db = LiteLLM_VerificationToken( + token="test-token", + user_id="test-user", + spend=key_spend, + max_budget=key_max_budget, + litellm_budget_table=LiteLLM_BudgetTable( + budget_id="test-budget", max_budget=budget_max_budget + ).dict() + if budget_max_budget is not None + else None, + ) + + with pytest.raises(HTTPException) as exc_info: + _validate_reset_spend_value(reset_to, key_in_db) + + assert exc_info.value.status_code == 400 + assert expected_error in str(exc_info.value.detail) + + +@pytest.mark.parametrize( + "reset_to,key_spend,key_max_budget,budget_max_budget", + [ + (0.0, 100.0, None, None), + (0, 100.0, None, None), + (50.0, 100.0, None, None), + (100.0, 100.0, None, None), + (25.0, 100.0, 50.0, None), + (0.0, 0.0, None, None), + (10.5, 50.0, 20.0, None), + ], +) +def test_validate_reset_spend_value_valid( + reset_to, key_spend, key_max_budget, budget_max_budget +): + key_in_db = LiteLLM_VerificationToken( + token="test-token", + user_id="test-user", + spend=key_spend, + max_budget=key_max_budget, + litellm_budget_table=LiteLLM_BudgetTable( + budget_id="test-budget", max_budget=budget_max_budget + ).dict() + if budget_max_budget is not None + else None, + ) + + result = _validate_reset_spend_value(reset_to, key_in_db) + assert result == float(reset_to) + + +def test_validate_reset_spend_value_no_budget_table(): + key_in_db = LiteLLM_VerificationToken( + token="test-token", + user_id="test-user", + spend=100.0, + max_budget=50.0, + litellm_budget_table=None, + ) + + result = _validate_reset_spend_value(25.0, key_in_db) + assert result == 25.0 + + +def test_validate_reset_spend_value_none_spend(): + key_in_db = LiteLLM_VerificationToken( + token="test-token", + user_id="test-user", + spend=0.0, + max_budget=None, + litellm_budget_table=None, + ) + + result = _validate_reset_spend_value(0.0, key_in_db) + assert result == 0.0 + + with pytest.raises(HTTPException) as exc_info: + _validate_reset_spend_value(1.0, key_in_db) + assert exc_info.value.status_code == 400 + assert "must be <= current spend" in str(exc_info.value.detail) + + +@pytest.mark.asyncio +async def test_reset_key_spend_success(monkeypatch): + mock_prisma_client = MagicMock() + mock_user_api_key_cache = MagicMock() + mock_proxy_logging_obj = MagicMock() + + hashed_key = "hashed-test-key" + key_in_db = LiteLLM_VerificationToken( + token=hashed_key, + user_id="test-user", + spend=100.0, + max_budget=200.0, + litellm_budget_table=None, + ) + + updated_key = LiteLLM_VerificationToken( + token=hashed_key, + user_id="test-user", + spend=50.0, + max_budget=200.0, + budget_reset_at=None, + ) + + mock_prisma_client.db.litellm_verificationtoken.find_unique = AsyncMock( + return_value=key_in_db + ) + mock_prisma_client.db.litellm_verificationtoken.update = AsyncMock( + return_value=updated_key + ) + + monkeypatch.setattr( + "litellm.proxy.proxy_server.prisma_client", mock_prisma_client + ) + monkeypatch.setattr( + "litellm.proxy.proxy_server.user_api_key_cache", mock_user_api_key_cache + ) + monkeypatch.setattr( + "litellm.proxy.proxy_server.proxy_logging_obj", mock_proxy_logging_obj + ) + + with patch( + "litellm.proxy.proxy_server.hash_token" + ) as mock_hash_token, patch( + "litellm.proxy.management_endpoints.key_management_endpoints._check_proxy_or_team_admin_for_key" + ) as mock_check_admin, patch( + "litellm.proxy.management_endpoints.key_management_endpoints._delete_cache_key_object" + ) as mock_delete_cache: + mock_hash_token.return_value = hashed_key + mock_check_admin.return_value = None + mock_delete_cache.return_value = None + + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.PROXY_ADMIN, + api_key="sk-admin", + user_id="admin-user", + ) + + response = await reset_key_spend_fn( + key="sk-test-key", + data=ResetSpendRequest(reset_to=50.0), + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + + assert response["spend"] == 50.0 + assert response["previous_spend"] == 100.0 + assert response["key_hash"] == hashed_key + assert response["max_budget"] == 200.0 + mock_prisma_client.db.litellm_verificationtoken.update.assert_called_once() + mock_delete_cache.assert_awaited_once() + + +@pytest.mark.asyncio +async def test_reset_key_spend_success_team_admin(monkeypatch): + """Test that team admin can reset key spend for keys in their team.""" + mock_prisma_client = MagicMock() + mock_user_api_key_cache = MagicMock() + mock_proxy_logging_obj = MagicMock() + + hashed_key = "hashed-test-key" + team_id = "test-team-123" + key_in_db = LiteLLM_VerificationToken( + token=hashed_key, + user_id="test-user", + team_id=team_id, + spend=100.0, + max_budget=200.0, + litellm_budget_table=None, + ) + + updated_key = LiteLLM_VerificationToken( + token=hashed_key, + user_id="test-user", + team_id=team_id, + spend=50.0, + max_budget=200.0, + budget_reset_at=None, + ) + + mock_prisma_client.db.litellm_verificationtoken.find_unique = AsyncMock( + return_value=key_in_db + ) + mock_prisma_client.db.litellm_verificationtoken.update = AsyncMock( + return_value=updated_key + ) + + # Set up team table with user as admin + team_table = LiteLLM_TeamTableCachedObj( + team_id=team_id, + team_alias="test-team", + tpm_limit=None, + rpm_limit=None, + max_budget=None, + spend=0.0, + models=[], + blocked=False, + members_with_roles=[ + Member(user_id="team-admin-user", role="admin"), + Member(user_id="test-user", role="user"), + ], + ) + + async def mock_get_team_object(*args, **kwargs): + return team_table + + monkeypatch.setattr( + "litellm.proxy.proxy_server.prisma_client", mock_prisma_client + ) + monkeypatch.setattr( + "litellm.proxy.proxy_server.user_api_key_cache", mock_user_api_key_cache + ) + monkeypatch.setattr( + "litellm.proxy.proxy_server.proxy_logging_obj", mock_proxy_logging_obj + ) + monkeypatch.setattr( + "litellm.proxy.management_endpoints.key_management_endpoints.get_team_object", + mock_get_team_object, + ) + + with patch( + "litellm.proxy.proxy_server.hash_token" + ) as mock_hash_token, patch( + "litellm.proxy.management_endpoints.key_management_endpoints._delete_cache_key_object" + ) as mock_delete_cache: + mock_hash_token.return_value = hashed_key + mock_delete_cache.return_value = None + + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + api_key="sk-team-admin", + user_id="team-admin-user", + ) + + response = await reset_key_spend_fn( + key="sk-test-key", + data=ResetSpendRequest(reset_to=50.0), + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + + assert response["spend"] == 50.0 + assert response["previous_spend"] == 100.0 + assert response["key_hash"] == hashed_key + assert response["max_budget"] == 200.0 + mock_prisma_client.db.litellm_verificationtoken.update.assert_called_once() + mock_delete_cache.assert_awaited_once() + + +@pytest.mark.asyncio +async def test_reset_key_spend_key_not_found(monkeypatch): + mock_prisma_client = MagicMock() + mock_prisma_client.db.litellm_verificationtoken.find_unique = AsyncMock( + return_value=None + ) + + monkeypatch.setattr( + "litellm.proxy.proxy_server.prisma_client", mock_prisma_client + ) + + with patch("litellm.proxy.proxy_server.hash_token") as mock_hash_token: + mock_hash_token.return_value = "hashed-key" + + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.PROXY_ADMIN, + api_key="sk-admin", + user_id="admin-user", + ) + + with pytest.raises(HTTPException) as exc_info: + await reset_key_spend_fn( + key="sk-test-key", + data=ResetSpendRequest(reset_to=50.0), + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + + assert exc_info.value.status_code == 404 + assert "Key not found" in str(exc_info.value.detail) or "Key sk-test-key not found" in str(exc_info.value.detail) + + +@pytest.mark.asyncio +async def test_reset_key_spend_db_not_connected(monkeypatch): + monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", None) + + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.PROXY_ADMIN, + api_key="sk-admin", + user_id="admin-user", + ) + + with pytest.raises(HTTPException) as exc_info: + await reset_key_spend_fn( + key="sk-test-key", + data=ResetSpendRequest(reset_to=50.0), + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + + assert exc_info.value.status_code == 500 + assert "DB not connected" in str(exc_info.value.detail) + + +@pytest.mark.asyncio +async def test_reset_key_spend_validation_error(monkeypatch): + mock_prisma_client = MagicMock() + key_in_db = LiteLLM_VerificationToken( + token="hashed-key", + user_id="test-user", + spend=100.0, + max_budget=None, + litellm_budget_table=None, + ) + + mock_prisma_client.db.litellm_verificationtoken.find_unique = AsyncMock( + return_value=key_in_db + ) + + monkeypatch.setattr( + "litellm.proxy.proxy_server.prisma_client", mock_prisma_client + ) + + with patch("litellm.proxy.proxy_server.hash_token") as mock_hash_token: + mock_hash_token.return_value = "hashed-key" + + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.PROXY_ADMIN, + api_key="sk-admin", + user_id="admin-user", + ) + + with pytest.raises(HTTPException) as exc_info: + await reset_key_spend_fn( + key="sk-test-key", + data=ResetSpendRequest(reset_to=150.0), + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + + assert exc_info.value.status_code == 400 + assert "must be <= current spend" in str(exc_info.value.detail) + + +@pytest.mark.asyncio +async def test_reset_key_spend_authorization_failure(monkeypatch): + mock_prisma_client = MagicMock() + mock_user_api_key_cache = MagicMock() + + hashed_key = "hashed-test-key" + key_in_db = LiteLLM_VerificationToken( + token=hashed_key, + user_id="test-user", + team_id="team-1", + spend=100.0, + max_budget=None, + litellm_budget_table=None, + ) + + mock_prisma_client.db.litellm_verificationtoken.find_unique = AsyncMock( + return_value=key_in_db + ) + + monkeypatch.setattr( + "litellm.proxy.proxy_server.prisma_client", mock_prisma_client + ) + monkeypatch.setattr( + "litellm.proxy.proxy_server.user_api_key_cache", mock_user_api_key_cache + ) + + with patch("litellm.proxy.proxy_server.hash_token") as mock_hash_token, patch( + "litellm.proxy.management_endpoints.key_management_endpoints._check_proxy_or_team_admin_for_key" + ) as mock_check_admin: + mock_hash_token.return_value = hashed_key + mock_check_admin.side_effect = HTTPException( + status_code=403, detail={"error": "Not authorized"} + ) + + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + api_key="sk-user", + user_id="user-1", + ) + + with pytest.raises(HTTPException) as exc_info: + await reset_key_spend_fn( + key="sk-test-key", + data=ResetSpendRequest(reset_to=50.0), + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + + assert exc_info.value.status_code == 403 + + +@pytest.mark.asyncio +async def test_reset_key_spend_hashed_key(monkeypatch): + mock_prisma_client = MagicMock() + mock_user_api_key_cache = MagicMock() + mock_proxy_logging_obj = MagicMock() + + hashed_key = "already-hashed-key" + key_in_db = LiteLLM_VerificationToken( + token=hashed_key, + user_id="test-user", + spend=100.0, + max_budget=None, + litellm_budget_table=None, + ) + + updated_key = LiteLLM_VerificationToken( + token=hashed_key, + user_id="test-user", + spend=50.0, + max_budget=None, + budget_reset_at=None, + ) + + mock_prisma_client.db.litellm_verificationtoken.find_unique = AsyncMock( + return_value=key_in_db + ) + mock_prisma_client.db.litellm_verificationtoken.update = AsyncMock( + return_value=updated_key + ) + + monkeypatch.setattr( + "litellm.proxy.proxy_server.prisma_client", mock_prisma_client + ) + monkeypatch.setattr( + "litellm.proxy.proxy_server.user_api_key_cache", mock_user_api_key_cache + ) + monkeypatch.setattr( + "litellm.proxy.proxy_server.proxy_logging_obj", mock_proxy_logging_obj + ) + + with patch( + "litellm.proxy.management_endpoints.key_management_endpoints._check_proxy_or_team_admin_for_key" + ) as mock_check_admin, patch( + "litellm.proxy.management_endpoints.key_management_endpoints._delete_cache_key_object" + ) as mock_delete_cache: + mock_check_admin.return_value = None + mock_delete_cache.return_value = None + + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.PROXY_ADMIN, + api_key="sk-admin", + user_id="admin-user", + ) + + response = await reset_key_spend_fn( + key=hashed_key, + data=ResetSpendRequest(reset_to=50.0), + user_api_key_dict=user_api_key_dict, + litellm_changed_by=None, + ) + + assert response["spend"] == 50.0 + mock_prisma_client.db.litellm_verificationtoken.find_unique.assert_called_once_with( + where={"token": hashed_key}, include={"litellm_budget_table": True} + ) + + +@pytest.mark.asyncio +async def test_validate_key_list_check_proxy_admin(): + mock_prisma_client = AsyncMock() + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.PROXY_ADMIN, + user_id="admin-user", + ) + + result = await validate_key_list_check( + user_api_key_dict=user_api_key_dict, + user_id=None, + team_id=None, + organization_id=None, + key_alias=None, + key_hash=None, + prisma_client=mock_prisma_client, + ) + + assert result is None + + +@pytest.mark.asyncio +async def test_validate_key_list_check_team_admin_success(): + mock_prisma_client = AsyncMock() + user_info = LiteLLM_UserTable( + user_id="test-user", + user_email="test@example.com", + teams=["team-1"], + organization_memberships=[], + ) + + mock_prisma_client.db.litellm_usertable.find_unique = AsyncMock( + return_value=user_info + ) + + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="test-user", + ) + + result = await validate_key_list_check( + user_api_key_dict=user_api_key_dict, + user_id=None, + team_id="team-1", + organization_id=None, + key_alias=None, + key_hash=None, + prisma_client=mock_prisma_client, + ) + + assert result is not None + assert result.user_id == "test-user" + + +@pytest.mark.asyncio +async def test_validate_key_list_check_team_admin_fail(): + mock_prisma_client = AsyncMock() + user_info = LiteLLM_UserTable( + user_id="test-user", + user_email="test@example.com", + teams=["team-1"], + organization_memberships=[], + ) + + mock_prisma_client.db.litellm_usertable.find_unique = AsyncMock( + return_value=user_info + ) + + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="test-user", + ) + + with pytest.raises(ProxyException) as exc_info: + await validate_key_list_check( + user_api_key_dict=user_api_key_dict, + user_id=None, + team_id="team-2", + organization_id=None, + key_alias=None, + key_hash=None, + prisma_client=mock_prisma_client, + ) + + assert exc_info.value.code == "403" or exc_info.value.code == 403 + assert "not authorized to check this team's keys" in exc_info.value.message + + +@pytest.mark.asyncio +async def test_validate_key_list_check_key_hash_authorized(): + mock_prisma_client = AsyncMock() + user_info = LiteLLM_UserTable( + user_id="test-user", + user_email="test@example.com", + teams=[], + organization_memberships=[], + ) + + key_info = LiteLLM_VerificationToken( + token="hashed-key", + user_id="test-user", + ) + + mock_prisma_client.db.litellm_usertable.find_unique = AsyncMock( + return_value=user_info + ) + mock_prisma_client.db.litellm_verificationtoken.find_unique = AsyncMock( + return_value=key_info + ) + + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="test-user", + ) + + with patch( + "litellm.proxy.management_endpoints.key_management_endpoints._can_user_query_key_info" + ) as mock_can_query: + mock_can_query.return_value = True + + result = await validate_key_list_check( + user_api_key_dict=user_api_key_dict, + user_id=None, + team_id=None, + organization_id=None, + key_alias=None, + key_hash="hashed-key", + prisma_client=mock_prisma_client, + ) + + assert result is not None + assert result.user_id == "test-user" + + +@pytest.mark.asyncio +async def test_validate_key_list_check_key_hash_unauthorized(): + mock_prisma_client = AsyncMock() + user_info = LiteLLM_UserTable( + user_id="test-user", + user_email="test@example.com", + teams=[], + organization_memberships=[], + ) + + key_info = LiteLLM_VerificationToken( + token="hashed-key", + user_id="other-user", + ) + + mock_prisma_client.db.litellm_usertable.find_unique = AsyncMock( + return_value=user_info + ) + mock_prisma_client.db.litellm_verificationtoken.find_unique = AsyncMock( + return_value=key_info + ) + + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="test-user", + ) + + with patch( + "litellm.proxy.management_endpoints.key_management_endpoints._can_user_query_key_info" + ) as mock_can_query: + mock_can_query.return_value = False + + with pytest.raises(HTTPException) as exc_info: + await validate_key_list_check( + user_api_key_dict=user_api_key_dict, + user_id=None, + team_id=None, + organization_id=None, + key_alias=None, + key_hash="hashed-key", + prisma_client=mock_prisma_client, + ) + + assert exc_info.value.status_code == 403 + assert "not allowed to access this key's info" in str(exc_info.value.detail) + + +@pytest.mark.asyncio +async def test_validate_key_list_check_key_hash_not_found(): + mock_prisma_client = AsyncMock() + user_info = LiteLLM_UserTable( + user_id="test-user", + user_email="test@example.com", + teams=[], + organization_memberships=[], + ) + + mock_prisma_client.db.litellm_usertable.find_unique = AsyncMock( + return_value=user_info + ) + mock_prisma_client.db.litellm_verificationtoken.find_unique = AsyncMock( + side_effect=Exception("Key not found") + ) + + user_api_key_dict = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="test-user", + ) + + with pytest.raises(ProxyException) as exc_info: + await validate_key_list_check( + user_api_key_dict=user_api_key_dict, + user_id=None, + team_id=None, + organization_id=None, + key_alias=None, + key_hash="non-existent-key", + prisma_client=mock_prisma_client, + ) + + assert exc_info.value.code == "403" or exc_info.value.code == 403 + assert "Key Hash not found" in exc_info.value.message diff --git a/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py index 0d78545823f..467ee3661d1 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py @@ -36,6 +36,7 @@ from litellm.proxy.management_endpoints.team_endpoints import ( _persist_deleted_team_records, _save_deleted_team_records, _transform_teams_to_deleted_records, + _validate_and_populate_member_user_info, delete_team, router, team_member_add_duplication_check, @@ -3654,6 +3655,100 @@ async def test_update_team_org_scoped_models_not_in_org_models(): assert "claude-3-opus" in str(exc_info.value.message) or "organization" in str(exc_info.value.message).lower() +@pytest.mark.asyncio +async def test_update_team_org_scoped_models_with_all_proxy_models(): + """ + Test that /team/update for an org-scoped team succeeds when organization has 'all-proxy-models'. + + Scenario: + - Organization has models=['all-proxy-models'] (catch-all for all models) + - Org-scoped team exists + - User tries to update team models to ['rerank-english-v3.0', 'text-embedding-3-small', 'gpt-4o-mini-test'] + - Expected: Should succeed because 'all-proxy-models' allows all models + """ + from fastapi import Request + + from litellm.proxy._types import ( + LiteLLM_OrganizationTable, + SpecialModelNames, + UpdateTeamRequest, + UserAPIKeyAuth, + ) + from litellm.proxy.management_endpoints.team_endpoints import update_team + + # Create user (org admin) + org_admin_user = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="org-admin-all-proxy-models-test", + models=[], + ) + + # Create update request with models that aren't explicitly in org's models list + # but should be allowed because org has 'all-proxy-models' + update_request = UpdateTeamRequest( + team_id="org-team-all-proxy-models-123", + models=["rerank-english-v3.0", "text-embedding-3-small", "gpt-4o-mini-test"], + ) + + dummy_request = MagicMock(spec=Request) + + # Mock organization with 'all-proxy-models' (catch-all) + mock_org = MagicMock(spec=LiteLLM_OrganizationTable) + mock_org.organization_id = "test-org-all-proxy-models" + mock_org.models = [SpecialModelNames.all_proxy_models.value] # Allows all models + mock_org.litellm_budget_table = None + + with patch("litellm.proxy.proxy_server.prisma_client") as mock_prisma, patch( + "litellm.proxy.proxy_server.user_api_key_cache" + ) as mock_cache, patch( + "litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin" + ), patch( + "litellm.proxy.proxy_server.create_audit_log_for_update", new=AsyncMock() + ) as mock_audit, patch( + "litellm.proxy.management_endpoints.team_endpoints.get_org_object", + new=AsyncMock(return_value=mock_org) + ) as mock_get_org: + + # Mock existing org-scoped team + mock_existing_team = MagicMock() + mock_existing_team.team_id = "org-team-all-proxy-models-123" + mock_existing_team.organization_id = "test-org-all-proxy-models" + mock_existing_team.models = ["gpt-4"] + mock_existing_team.model_id = None + mock_existing_team.model_dump.return_value = { + "team_id": "org-team-all-proxy-models-123", + "organization_id": "test-org-all-proxy-models", + "models": ["gpt-4"], + } + mock_prisma.db.litellm_teamtable.find_unique = AsyncMock(return_value=mock_existing_team) + mock_prisma.jsonify_team_object = lambda db_data: db_data + mock_cache.async_set_cache = AsyncMock() # Mock cache set for _cache_team_object + + # Mock team update + mock_updated_team = MagicMock() + mock_updated_team.team_id = "org-team-all-proxy-models-123" + mock_updated_team.organization_id = "test-org-all-proxy-models" + mock_updated_team.models = ["rerank-english-v3.0", "text-embedding-3-small", "gpt-4o-mini-test"] + mock_updated_team.litellm_model_table = None + mock_updated_team.model_dump.return_value = { + "team_id": "org-team-all-proxy-models-123", + "organization_id": "test-org-all-proxy-models", + "models": ["rerank-english-v3.0", "text-embedding-3-small", "gpt-4o-mini-test"], + } + mock_prisma.db.litellm_teamtable.update = AsyncMock(return_value=mock_updated_team) + + # Should NOT raise an exception - 'all-proxy-models' allows all models + result = await update_team( + data=update_request, + http_request=dummy_request, + user_api_key_dict=org_admin_user, + ) + + # Verify the team was updated successfully with the new models + assert result is not None + assert result["data"].models == ["rerank-english-v3.0", "text-embedding-3-small", "gpt-4o-mini-test"] + + @pytest.mark.asyncio async def test_update_team_tpm_limit_exceeds_user_limit(): """ @@ -5372,3 +5467,122 @@ async def test_get_team_daily_activity_team_admin_sees_all_spend(mock_db_client) ) and mock_db_client.db.litellm_verificationtoken.find_many.called: # If it was called, that's unexpected for admin users assert False, "API keys should not be fetched for team admin users" + + +@pytest.mark.asyncio +async def test_validate_and_populate_member_user_info_both_provided_match(): + """ + Test _validate_and_populate_member_user_info when both user_email and user_id + are provided and they match the same user in the database. + """ + # Create member with both user_email and user_id + member = Member(user_email="test@example.com", user_id="user-123", role="user") + + # Mock prisma client + mock_prisma_client = MagicMock() + + # Mock user object that matches both email and user_id + mock_user = MagicMock() + mock_user.user_id = "user-123" + mock_user.user_email = "test@example.com" + + # Mock get_data to return single user matching email + mock_prisma_client.get_data = AsyncMock(return_value=[mock_user]) + + # Call the function + result = await _validate_and_populate_member_user_info( + member=member, + prisma_client=mock_prisma_client, + ) + + # Verify result matches input (both already provided and match) + assert result.user_email == "test@example.com" + assert result.user_id == "user-123" + + # Verify get_data was called with correct parameters + mock_prisma_client.get_data.assert_called_once_with( + key_val={"user_email": "test@example.com"}, + table_name="user", + query_type="find_all", + ) + + +@pytest.mark.asyncio +async def test_validate_and_populate_member_user_info_only_email_provided(): + """ + Test _validate_and_populate_member_user_info when only user_email is provided. + Should populate user_id from database. + """ + # Create member with only user_email + member = Member(user_email="test@example.com", user_id=None, role="user") + + # Mock prisma client + mock_prisma_client = MagicMock() + + # Mock user object from find_first + mock_user_find_first = MagicMock() + mock_user_find_first.user_id = "user-456" + mock_user_find_first.user_email = "test@example.com" + + # Mock find_first to return the user + mock_prisma_client.db.litellm_usertable.find_first = AsyncMock( + return_value=mock_user_find_first + ) + + # Mock get_data to return single user (no duplicates) + mock_prisma_client.get_data = AsyncMock(return_value=[mock_user_find_first]) + + # Call the function + result = await _validate_and_populate_member_user_info( + member=member, + prisma_client=mock_prisma_client, + ) + + # Verify user_id was populated + assert result.user_email == "test@example.com" + assert result.user_id == "user-456" + + # Verify find_first was called with correct parameters + mock_prisma_client.db.litellm_usertable.find_first.assert_called_once_with( + where={"user_email": {"equals": "test@example.com", "mode": "insensitive"}} + ) + + # Verify get_data was called to check for duplicates + mock_prisma_client.get_data.assert_called_once_with( + key_val={"user_email": "test@example.com"}, + table_name="user", + query_type="find_all", + ) + + +@pytest.mark.asyncio +async def test_validate_and_populate_member_user_info_only_user_id_not_found(): + """ + Test _validate_and_populate_member_user_info when only user_id is provided + but the user doesn't exist in the database. Should allow it to pass with + user_email as None (will be upserted later). + """ + # Create member with only user_id + member = Member(user_email=None, user_id="nonexistent-user", role="user") + + # Mock prisma client + mock_prisma_client = MagicMock() + + # Mock find_unique to return None (user not found) + mock_prisma_client.db.litellm_usertable.find_unique = AsyncMock(return_value=None) + + # Call the function - should NOT raise an exception + result = await _validate_and_populate_member_user_info( + member=member, + prisma_client=mock_prisma_client, + ) + + # Verify the result - should return member with user_id set and user_email as None + assert result.user_id == "nonexistent-user" + assert result.user_email is None + assert result.role == "user" + + # Verify find_unique was called with correct parameters + mock_prisma_client.db.litellm_usertable.find_unique.assert_called_once_with( + where={"user_id": "nonexistent-user"} + ) diff --git a/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py b/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py index f983af2d0b2..41096503a2e 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py +++ b/tests/test_litellm/proxy/management_endpoints/test_ui_sso.py @@ -24,12 +24,15 @@ from litellm.proxy.management_endpoints.ui_sso import ( GoogleSSOHandler, MicrosoftSSOHandler, SSOAuthenticationHandler, + normalize_email, + _setup_team_mappings, ) from litellm.types.proxy.management_endpoints.ui_sso import ( DefaultTeamSSOParams, MicrosoftGraphAPIUserGroupDirectoryObject, MicrosoftGraphAPIUserGroupResponse, MicrosoftServicePrincipalTeam, + TeamMappings, ) @@ -667,6 +670,85 @@ def test_build_sso_user_update_data_without_role(): assert "user_role" not in update_data +def test_normalize_email(): + """ + Test that normalize_email correctly lowercases email addresses and handles edge cases. + """ + # Test with lowercase email + assert normalize_email("test@example.com") == "test@example.com" + + # Test with uppercase email + assert normalize_email("TEST@EXAMPLE.COM") == "test@example.com" + + # Test with mixed case email + assert normalize_email("Test.User@Example.COM") == "test.user@example.com" + + # Test with None + assert normalize_email(None) is None + + # Test with empty string + assert normalize_email("") == "" + + +def test_build_sso_user_update_data_normalizes_email(): + """ + Test that _build_sso_user_update_data normalizes email addresses to lowercase. + """ + from litellm.proxy.management_endpoints.types import CustomOpenID + from litellm.proxy.management_endpoints.ui_sso import _build_sso_user_update_data + + sso_result = CustomOpenID( + id="test-user-789", + email="Test.User@Example.COM", + display_name="Test User", + provider="microsoft", + team_ids=[], + user_role=None, + ) + + update_data = _build_sso_user_update_data( + result=sso_result, + user_email="Test.User@Example.COM", + user_id="test-user-789", + ) + + # Email should be normalized to lowercase + assert update_data["user_email"] == "test.user@example.com" + assert "user_role" not in update_data + + +def test_generic_response_convertor_normalizes_email(): + """ + Test that generic_response_convertor normalizes email addresses. + """ + from litellm.proxy.management_endpoints.ui_sso import generic_response_convertor + + mock_response = { + "preferred_username": "user123", + "email": "Test.User@Example.COM", + "sub": "Test User", + "first_name": "Test", + "last_name": "User", + "provider": "generic", + } + + # Mock JWT handler + mock_jwt_handler = MagicMock(spec=JWTHandler) + mock_jwt_handler.get_team_ids_from_jwt.return_value = [] + + result = generic_response_convertor( + response=mock_response, + jwt_handler=mock_jwt_handler, + sso_jwt_handler=None, + role_mappings=None, + ) + + # Email should be normalized to lowercase + assert result.email == "test.user@example.com" + assert result.id == "user123" + assert result.display_name == "Test User" + + @pytest.mark.asyncio async def test_upsert_sso_user_updates_role_for_existing_user(): """ @@ -3735,3 +3817,34 @@ class TestCustomMicrosoftSSO: ) assert isinstance(sso, MicrosoftSSO) + + +@pytest.mark.asyncio +async def test_setup_team_mappings(): + """Test _setup_team_mappings function loads team mappings from database.""" + # Arrange + mock_prisma = MagicMock() + mock_sso_config = MagicMock() + mock_sso_config.sso_settings = { + "team_mappings": { + "team_ids_jwt_field": "groups" + } + } + mock_prisma.db.litellm_ssoconfig.find_unique = AsyncMock( + return_value=mock_sso_config + ) + + with patch( + "litellm.proxy.utils.get_prisma_client_or_throw", + return_value=mock_prisma, + ): + # Act + result = await _setup_team_mappings() + + # Assert + assert result is not None + assert isinstance(result, TeamMappings) + assert result.team_ids_jwt_field == "groups" + mock_prisma.db.litellm_ssoconfig.find_unique.assert_called_once_with( + where={"id": "sso_config"} + ) diff --git a/tests/test_litellm/proxy/openai_files_endpoint/test_files_endpoint.py b/tests/test_litellm/proxy/openai_files_endpoint/test_files_endpoint.py index b86f927ea00..837ae79bffc 100644 --- a/tests/test_litellm/proxy/openai_files_endpoint/test_files_endpoint.py +++ b/tests/test_litellm/proxy/openai_files_endpoint/test_files_endpoint.py @@ -950,3 +950,181 @@ def test_managed_files_with_loadbalancing(mocker: MockerFixture, monkeypatch, ll assert router_acreate_file_calls[0]["model"] == "azure-gpt-3-5-turbo" assert router_acreate_file_calls[1]["model"] == "gpt-3.5-turbo" assert all(call["via_router"] for call in router_acreate_file_calls), "All calls should go through router" + + +def test_create_file_with_nested_litellm_metadata( + mocker: MockerFixture, monkeypatch, llm_router: Router +): + """ + Test that nested litellm_metadata is correctly parsed from form data in bracket notation. + + Regression test for: litellm_metadata[spend_logs_metadata][owner] format should be + correctly parsed into nested dictionary structure. + """ + from litellm.llms.base_llm.files.transformation import BaseFileEndpoints + from litellm.types.llms.openai import OpenAIFileObject + + proxy_logging_obj = ProxyLogging( + user_api_key_cache=DualCache(default_in_memory_ttl=1) + ) + proxy_logging_obj._add_proxy_hooks(llm_router) + + captured_litellm_metadata = {} + + class DummyManagedFiles(BaseFileEndpoints): + async def acreate_file(self, llm_router, create_file_request, target_model_names_list, litellm_parent_otel_span, user_api_key_dict): + # Capture litellm_metadata for verification + if isinstance(create_file_request, dict): + captured_litellm_metadata.update( + create_file_request.get("litellm_metadata", {}) + ) + else: + captured_litellm_metadata.update( + getattr(create_file_request, "litellm_metadata", {}) + ) + + return OpenAIFileObject( + id="file-test-123", + object="file", + bytes=100, + created_at=1234567890, + filename="test.jsonl", + purpose="fine-tune", + status="uploaded", + ) + + async def afile_retrieve(self, file_id, litellm_parent_otel_span, llm_router): + raise NotImplementedError("Not implemented for test") + + async def afile_list(self, purpose, litellm_parent_otel_span): + raise NotImplementedError("Not implemented for test") + + async def afile_delete(self, file_id, litellm_parent_otel_span, llm_router, **data): + raise NotImplementedError("Not implemented for test") + + async def afile_content(self, file_id, litellm_parent_otel_span, llm_router, **data): + raise NotImplementedError("Not implemented for test") + + proxy_logging_obj.proxy_hook_mapping["managed_files"] = DummyManagedFiles() + monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", llm_router) + monkeypatch.setattr( + "litellm.proxy.proxy_server.proxy_logging_obj", proxy_logging_obj + ) + + test_file_content = b'{"prompt": "Hello", "completion": "Hi"}' + test_file = ("test.jsonl", test_file_content, "application/jsonl") + + # Test with nested litellm_metadata in bracket notation + response = client.post( + "/v1/files", + files={"file": test_file}, + data={ + "purpose": "fine-tune", + "target_model_names": "gpt-3.5-turbo", + "litellm_metadata[spend_logs_metadata][owner]": "john_doe", + "litellm_metadata[spend_logs_metadata][team]": "engineering", + "litellm_metadata[tags]": "production", + "litellm_metadata[environment]": "prod", + }, + headers={"Authorization": "Bearer test-key"}, + ) + + # Verify success + assert response.status_code == 200 + result = response.json() + assert result["id"] == "file-test-123" + + # Verify nested metadata was correctly parsed + assert "spend_logs_metadata" in captured_litellm_metadata + assert captured_litellm_metadata["spend_logs_metadata"]["owner"] == "john_doe" + assert captured_litellm_metadata["spend_logs_metadata"]["team"] == "engineering" + assert captured_litellm_metadata["tags"] == "production" + assert captured_litellm_metadata["environment"] == "prod" + + +def test_create_file_with_deep_nested_litellm_metadata( + mocker: MockerFixture, monkeypatch, llm_router: Router +): + """ + Test that deeply nested litellm_metadata is correctly parsed from form data. + + Regression test for: litellm_metadata[a][b][c] format should be correctly parsed. + """ + from litellm.llms.base_llm.files.transformation import BaseFileEndpoints + from litellm.types.llms.openai import OpenAIFileObject + + proxy_logging_obj = ProxyLogging( + user_api_key_cache=DualCache(default_in_memory_ttl=1) + ) + proxy_logging_obj._add_proxy_hooks(llm_router) + + captured_litellm_metadata = {} + + class DummyManagedFiles(BaseFileEndpoints): + async def acreate_file(self, llm_router, create_file_request, target_model_names_list, litellm_parent_otel_span, user_api_key_dict): + if isinstance(create_file_request, dict): + captured_litellm_metadata.update( + create_file_request.get("litellm_metadata", {}) + ) + else: + captured_litellm_metadata.update( + getattr(create_file_request, "litellm_metadata", {}) + ) + + return OpenAIFileObject( + id="file-test-456", + object="file", + bytes=50, + created_at=1234567890, + filename="nested.jsonl", + purpose="batch", + status="uploaded", + ) + + async def afile_retrieve(self, file_id, litellm_parent_otel_span, llm_router): + raise NotImplementedError("Not implemented for test") + + async def afile_list(self, purpose, litellm_parent_otel_span): + raise NotImplementedError("Not implemented for test") + + async def afile_delete(self, file_id, litellm_parent_otel_span, llm_router, **data): + raise NotImplementedError("Not implemented for test") + + async def afile_content(self, file_id, litellm_parent_otel_span, llm_router, **data): + raise NotImplementedError("Not implemented for test") + + proxy_logging_obj.proxy_hook_mapping["managed_files"] = DummyManagedFiles() + monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", llm_router) + monkeypatch.setattr( + "litellm.proxy.proxy_server.proxy_logging_obj", proxy_logging_obj + ) + + test_file_content = b'{"custom_id": "req-1", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-3.5-turbo"}}' + test_file = ("nested.jsonl", test_file_content, "application/jsonl") + + # Test with deeply nested metadata + response = client.post( + "/v1/files", + files={"file": test_file}, + data={ + "purpose": "batch", + "target_model_names": "gpt-3.5-turbo", + "litellm_metadata[config][database][host]": "localhost", + "litellm_metadata[config][database][port]": "5432", + "litellm_metadata[config][cache][enabled]": "true", + }, + headers={"Authorization": "Bearer test-key"}, + ) + + # Verify success + assert response.status_code == 200 + result = response.json() + assert result["id"] == "file-test-456" + + # Verify deeply nested metadata was correctly parsed + assert "config" in captured_litellm_metadata + assert "database" in captured_litellm_metadata["config"] + assert captured_litellm_metadata["config"]["database"]["host"] == "localhost" + assert captured_litellm_metadata["config"]["database"]["port"] == "5432" + assert "cache" in captured_litellm_metadata["config"] + assert captured_litellm_metadata["config"]["cache"]["enabled"] == "true" diff --git a/tests/test_litellm/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py b/tests/test_litellm/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py index 368e2103590..a0953bf88c7 100644 --- a/tests/test_litellm/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py +++ b/tests/test_litellm/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py @@ -22,6 +22,7 @@ from litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints import ( create_pass_through_route, llm_passthrough_factory_proxy_route, milvus_proxy_route, + openai_proxy_route, vertex_discovery_proxy_route, vertex_proxy_route, vllm_proxy_route, @@ -2189,3 +2190,178 @@ class TestMilvusProxyRoute: # Verify that the target URL has correct path create_route_args = mock_create_route.call_args[1] assert "/vectors/search" in create_route_args["target"] + + +class TestOpenAIPassthroughRoute: + """ + Test cases for OpenAI passthrough endpoint (/openai_passthrough) + """ + + @pytest.mark.asyncio + async def test_openai_passthrough_responses_api(self): + """ + Test that /openai_passthrough endpoint correctly handles Responses API calls + This verifies the fix for issue #18865 where /openai/v1/responses was being + routed to LiteLLM's native implementation instead of passthrough + """ + from litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints import ( + openai_proxy_route, + ) + + # Mock request for Responses API + mock_request = MagicMock(spec=Request) + mock_request.method = "POST" + mock_request.headers = {"content-type": "application/json"} + mock_request.query_params = {} + mock_response = MagicMock(spec=Response) + mock_user_api_key_dict = MagicMock() + + with patch( + "litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.passthrough_endpoint_router.get_credentials", + return_value="sk-test-key", + ), patch( + "litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.create_pass_through_route" + ) as mock_create_route: + mock_endpoint_func = AsyncMock( + return_value={"id": "resp_123", "status": "completed"} + ) + mock_create_route.return_value = mock_endpoint_func + + # Call the route with /v1/responses endpoint + result = await openai_proxy_route( + endpoint="v1/responses", + request=mock_request, + fastapi_response=mock_response, + user_api_key_dict=mock_user_api_key_dict, + ) + + # Verify create_pass_through_route was called with correct target + mock_create_route.assert_called_once() + call_args = mock_create_route.call_args[1] + + # Should route to OpenAI's responses API + assert call_args["target"] == "https://api.openai.com/v1/responses" + assert call_args["endpoint"] == "v1/responses" + + # Verify headers contain API key + assert "authorization" in call_args["custom_headers"] + assert "Bearer sk-test-key" in call_args["custom_headers"]["authorization"] + + # Verify result + assert result == {"id": "resp_123", "status": "completed"} + + @pytest.mark.asyncio + async def test_openai_passthrough_chat_completions(self): + """ + Test that /openai_passthrough works for chat completions + """ + from litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints import ( + openai_proxy_route, + ) + + mock_request = MagicMock(spec=Request) + mock_request.method = "POST" + mock_request.headers = {"content-type": "application/json"} + mock_request.query_params = {} + mock_response = MagicMock(spec=Response) + mock_user_api_key_dict = MagicMock() + + with patch( + "litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.passthrough_endpoint_router.get_credentials", + return_value="sk-test-key", + ), patch( + "litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.create_pass_through_route" + ) as mock_create_route: + mock_endpoint_func = AsyncMock( + return_value={"id": "chatcmpl-123", "choices": []} + ) + mock_create_route.return_value = mock_endpoint_func + + result = await openai_proxy_route( + endpoint="v1/chat/completions", + request=mock_request, + fastapi_response=mock_response, + user_api_key_dict=mock_user_api_key_dict, + ) + + # Verify routing + mock_create_route.assert_called_once() + call_args = mock_create_route.call_args[1] + assert call_args["target"] == "https://api.openai.com/v1/chat/completions" + + # Verify result + assert result == {"id": "chatcmpl-123", "choices": []} + + @pytest.mark.asyncio + async def test_openai_passthrough_missing_api_key(self): + """ + Test that missing OPENAI_API_KEY raises an exception + """ + from litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints import ( + openai_proxy_route, + ) + + mock_request = MagicMock(spec=Request) + mock_response = MagicMock(spec=Response) + mock_user_api_key_dict = MagicMock() + + with patch( + "litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.passthrough_endpoint_router.get_credentials", + return_value=None, + ): + with pytest.raises(Exception) as exc_info: + await openai_proxy_route( + endpoint="v1/chat/completions", + request=mock_request, + fastapi_response=mock_response, + user_api_key_dict=mock_user_api_key_dict, + ) + + assert "Required 'OPENAI_API_KEY'" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_openai_passthrough_assistants_api(self): + """ + Test that /openai_passthrough works for Assistants API endpoints + """ + from litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints import ( + openai_proxy_route, + ) + + mock_request = MagicMock(spec=Request) + mock_request.method = "POST" + mock_request.headers = {"content-type": "application/json"} + mock_request.query_params = {} + mock_request.url = MagicMock() + mock_request.url.path = "/v1/assistants" + mock_response = MagicMock(spec=Response) + mock_user_api_key_dict = MagicMock() + + with patch( + "litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.passthrough_endpoint_router.get_credentials", + return_value="sk-test-key", + ), patch( + "litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.create_pass_through_route" + ) as mock_create_route: + mock_endpoint_func = AsyncMock( + return_value={"id": "asst_123", "object": "assistant"} + ) + mock_create_route.return_value = mock_endpoint_func + + result = await openai_proxy_route( + endpoint="v1/assistants", + request=mock_request, + fastapi_response=mock_response, + user_api_key_dict=mock_user_api_key_dict, + ) + + # Verify routing + mock_create_route.assert_called_once() + call_args = mock_create_route.call_args[1] + assert call_args["target"] == "https://api.openai.com/v1/assistants" + + # Verify headers contain API key and OpenAI-Beta header + assert "authorization" in call_args["custom_headers"] + + # Verify result + assert result == {"id": "asst_123", "object": "assistant"} diff --git a/tests/test_litellm/proxy/pass_through_endpoints/test_vertex_passthrough_load_balancing.py b/tests/test_litellm/proxy/pass_through_endpoints/test_vertex_passthrough_load_balancing.py index 28b3ba0a179..d2fdb157c8d 100644 --- a/tests/test_litellm/proxy/pass_through_endpoints/test_vertex_passthrough_load_balancing.py +++ b/tests/test_litellm/proxy/pass_through_endpoints/test_vertex_passthrough_load_balancing.py @@ -447,3 +447,75 @@ def test_forward_headers_from_request_x_pass_prefix(): assert "x-pass-anthropic-beta" not in result assert "x-pass-custom-header" not in result + +@pytest.mark.asyncio +async def test_vertex_passthrough_custom_model_name_replaced_in_url(): + """ + Test that when a passthrough URL contains a custom model_name (e.g., gcp/google/gemini-3-pro), + the URL is rewritten to use the actual Vertex AI model name (e.g., gemini-3-pro) + before being forwarded to Vertex AI. + + This prevents 404 errors from Vertex AI when custom model names are used in the config. + + Config example: + model_name: gcp/google/gemini-3-pro + litellm_params: + model: vertex_ai/gemini-3-pro + vertex_project: "my-project" + vertex_location: "global" + use_in_pass_through: true + """ + mock_request = MagicMock() + mock_response = MagicMock() + mock_handler = MagicMock() + + # Deployment with custom model_name but real vertex model + mock_deployment = { + "litellm_params": { + "model": "vertex_ai/gemini-3-pro", + "vertex_project": "nv-gcpllmgwit-20250411173346", + "vertex_location": "global", + "use_in_pass_through": True, + } + } + mock_router = MagicMock() + mock_router.get_available_deployment_for_pass_through.return_value = mock_deployment + + # The URL contains project/location AND a custom model name with slashes + test_endpoint = "v1/projects/nv-gcpllmgwit-20250411173346/locations/global/publishers/google/models/gcp/google/gemini-3-pro:generateContent" + + with patch("litellm.proxy.proxy_server.llm_router", mock_router), \ + patch("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.passthrough_endpoint_router") as mock_pt_router, \ + patch("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints._prepare_vertex_auth_headers", new_callable=AsyncMock) as mock_prep_headers, \ + patch("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.create_pass_through_route") as mock_create_route, \ + patch("litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints.user_api_key_auth", new_callable=AsyncMock) as mock_auth: + + mock_pt_router.get_vertex_credentials.return_value = MagicMock() + mock_prep_headers.return_value = ({}, "https://global-aiplatform.googleapis.com", False, "nv-gcpllmgwit-20250411173346", "global") + mock_endpoint_func = AsyncMock() + mock_create_route.return_value = mock_endpoint_func + mock_auth.return_value = {} + + mock_handler.get_default_base_target_url.return_value = "https://global-aiplatform.googleapis.com" + + await _base_vertex_proxy_route( + endpoint=test_endpoint, + request=mock_request, + fastapi_response=mock_response, + get_vertex_pass_through_handler=mock_handler, + ) + + # Verify the router was called with the custom model name (extracted from URL) + mock_router.get_available_deployment_for_pass_through.assert_called_once_with( + model="gcp/google/gemini-3-pro" + ) + + # Verify the target URL passed to create_pass_through_route contains + # the REAL Vertex AI model name, not the custom one + create_route_call = mock_create_route.call_args + target_url = create_route_call.kwargs.get("target", "") + assert "gcp/google/gemini-3-pro" not in target_url, \ + f"Custom model name should have been replaced in target URL. Got: {target_url}" + assert "gemini-3-pro" in target_url, \ + f"Actual Vertex AI model name should be in target URL. Got: {target_url}" + diff --git a/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py b/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py index 33e000143c8..13368d0a142 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py +++ b/tests/test_litellm/proxy/spend_tracking/test_spend_management_endpoints.py @@ -1952,6 +1952,89 @@ async def test_ui_view_spend_logs_with_error_code(client): assert metadata["error_information"]["error_code"] == "404" +@pytest.mark.asyncio +async def test_ui_view_spend_logs_with_error_message(client): + """Test filtering spend logs by error message""" + mock_spend_logs = [ + { + "id": "log1", + "request_id": "req1", + "api_key": "sk-test-key", + "user": "test_user_1", + "team_id": "team1", + "spend": 0.05, + "startTime": datetime.datetime.now(timezone.utc).isoformat(), + "model": "gpt-3.5-turbo", + "metadata": '{"error_information": {"error_message": "Rate limit exceeded"}}', + }, + { + "id": "log2", + "request_id": "req2", + "api_key": "sk-test-key", + "user": "test_user_2", + "team_id": "team1", + "spend": 0.10, + "startTime": datetime.datetime.now(timezone.utc).isoformat(), + "model": "gpt-4", + "metadata": '{"error_information": {"error_message": "Invalid API key"}}', + }, + ] + + with patch.object(ps, "prisma_client") as mock_prisma: + # Mock the find_many method to return filtered results + async def mock_find_many(*args, **kwargs): + where_conditions = kwargs.get("where", {}) + if "metadata" in where_conditions: + metadata_filter = where_conditions["metadata"] + if metadata_filter.get("path") == ["error_information", "error_message"]: + error_message_filter = metadata_filter.get("string_contains") + # Check if the error message contains the filter string + if error_message_filter == "Rate limit": + return [mock_spend_logs[0]] + elif error_message_filter == "Invalid API": + return [mock_spend_logs[1]] + return mock_spend_logs + + async def mock_count(*args, **kwargs): + where_conditions = kwargs.get("where", {}) + if "metadata" in where_conditions: + metadata_filter = where_conditions["metadata"] + if metadata_filter.get("path") == ["error_information", "error_message"]: + error_message_filter = metadata_filter.get("string_contains") + if error_message_filter == "Rate limit": + return 1 + elif error_message_filter == "Invalid API": + return 1 + return len(mock_spend_logs) + + mock_prisma.db.litellm_spendlogs.find_many = mock_find_many + mock_prisma.db.litellm_spendlogs.count = mock_count + + start_date = ( + datetime.datetime.now(timezone.utc) - datetime.timedelta(days=7) + ).strftime("%Y-%m-%d %H:%M:%S") + end_date = datetime.datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S") + + response = client.get( + "/spend/logs/ui", + params={ + "error_message": "Rate limit", + "start_date": start_date, + "end_date": end_date, + }, + headers={"Authorization": "Bearer sk-test"}, + ) + + assert response.status_code == 200 + data = response.json() + assert data["total"] == 1 + assert len(data["data"]) == 1 + assert data["data"][0]["id"] == "log1" + metadata = json.loads(data["data"][0]["metadata"]) + assert "error_information" in metadata + assert "Rate limit exceeded" in metadata["error_information"]["error_message"] + + @pytest.mark.asyncio async def test_ui_view_spend_logs_with_error_code_and_key_alias(client): """Test merging error_code and key_alias filters with AND logic""" diff --git a/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py b/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py index dd4cbfa2e7f..1972103c3d2 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py +++ b/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py @@ -23,6 +23,7 @@ from litellm.proxy.spend_tracking.spend_tracking_utils import ( _get_response_for_spend_logs_payload, _get_vector_store_request_for_spend_logs_payload, _sanitize_request_body_for_spend_logs_payload, + _should_store_prompts_and_responses_in_spend_logs, get_logging_payload, ) from litellm.types.utils import ( @@ -911,3 +912,48 @@ def test_spend_logs_redacts_request_and_response_when_turn_off_message_logging_e parsed_response = json.loads(response_result) assert parsed_response == {"text": "redacted-by-litellm"} + +@patch("litellm.secret_managers.main.get_secret_bool") +def test_should_store_prompts_and_responses_in_spend_logs_case_insensitive_string( + mock_get_secret_bool, +): + """ + Test that _should_store_prompts_and_responses_in_spend_logs handles + case-insensitive string values for store_prompts_in_spend_logs in general_settings. + """ + # Test case-insensitive string "true" variations + for true_value in ["true", "TRUE", "True", "TrUe"]: + with patch("litellm.proxy.proxy_server.general_settings", {"store_prompts_in_spend_logs": true_value}): + mock_get_secret_bool.return_value = False # Ensure env var is False + result = _should_store_prompts_and_responses_in_spend_logs() + assert result is True, f"Expected True for '{true_value}', got {result}" + + # Test boolean True + with patch("litellm.proxy.proxy_server.general_settings", {"store_prompts_in_spend_logs": True}): + mock_get_secret_bool.return_value = False + result = _should_store_prompts_and_responses_in_spend_logs() + assert result is True, f"Expected True for boolean True, got {result}" + + # Test that non-true values fall back to environment variable + for false_value in [False, None, "false", "FALSE", "False", "anything"]: + with patch("litellm.proxy.proxy_server.general_settings", {"store_prompts_in_spend_logs": false_value}): + # When env var is True, should return True + mock_get_secret_bool.return_value = True + result = _should_store_prompts_and_responses_in_spend_logs() + assert result is True, f"Expected True (from env var) for '{false_value}', got {result}" + + # When env var is False, should return False + mock_get_secret_bool.return_value = False + result = _should_store_prompts_and_responses_in_spend_logs() + assert result is False, f"Expected False (from env var) for '{false_value}', got {result}" + + # Test when general_settings doesn't have the key at all + with patch("litellm.proxy.proxy_server.general_settings", {}): + mock_get_secret_bool.return_value = True + result = _should_store_prompts_and_responses_in_spend_logs() + assert result is True, "Expected True (from env var) when key missing, got False" + + mock_get_secret_bool.return_value = False + result = _should_store_prompts_and_responses_in_spend_logs() + assert result is False, "Expected False (from env var) when key missing, got True" + diff --git a/tests/test_litellm/proxy/test_common_request_processing.py b/tests/test_litellm/proxy/test_common_request_processing.py index 3d1e9aece41..69cf8240c63 100644 --- a/tests/test_litellm/proxy/test_common_request_processing.py +++ b/tests/test_litellm/proxy/test_common_request_processing.py @@ -13,6 +13,7 @@ from litellm.proxy.common_request_processing import ( ProxyConfig, _extract_error_from_sse_chunk, _get_cost_breakdown_from_logging_obj, + _override_openai_response_model, _parse_event_data_for_error, create_response, ) @@ -76,84 +77,6 @@ class TestProxyBaseLLMRequestProcessing: pytest.fail("litellm_call_id is not a valid UUID") assert data_passed["litellm_call_id"] == returned_data["litellm_call_id"] - @pytest.mark.asyncio - async def test_should_apply_hierarchical_router_settings_to_user_config( - self, monkeypatch - ): - processing_obj = ProxyBaseLLMRequestProcessing(data={}) - mock_request = MagicMock(spec=Request) - mock_request.headers = {} - - async def mock_add_litellm_data_to_request(*args, **kwargs): - return {} - - async def mock_common_processing_pre_call_logic( - user_api_key_dict, data, call_type - ): - data_copy = copy.deepcopy(data) - return data_copy - - mock_proxy_logging_obj = MagicMock(spec=ProxyLogging) - mock_proxy_logging_obj.pre_call_hook = AsyncMock( - side_effect=mock_common_processing_pre_call_logic - ) - monkeypatch.setattr( - litellm.proxy.common_request_processing, - "add_litellm_data_to_request", - mock_add_litellm_data_to_request, - ) - - mock_general_settings = {} - mock_user_api_key_dict = MagicMock(spec=UserAPIKeyAuth) - mock_proxy_config = MagicMock(spec=ProxyConfig) - - mock_router_settings = { - "routing_strategy": "least-busy", - "timeout": 30.0, - "num_retries": 3, - } - mock_proxy_config._get_hierarchical_router_settings = AsyncMock( - return_value=mock_router_settings - ) - - mock_model_list = [ - {"model_name": "gpt-3.5-turbo", "litellm_params": {"model": "gpt-3.5-turbo"}}, - {"model_name": "gpt-4", "litellm_params": {"model": "gpt-4"}}, - ] - mock_llm_router = MagicMock() - mock_llm_router.get_model_list = MagicMock(return_value=mock_model_list) - - mock_prisma_client = MagicMock() - monkeypatch.setattr( - "litellm.proxy.proxy_server.prisma_client", - mock_prisma_client, - ) - - route_type = "acompletion" - - returned_data, logging_obj = await processing_obj.common_processing_pre_call_logic( - request=mock_request, - general_settings=mock_general_settings, - user_api_key_dict=mock_user_api_key_dict, - proxy_logging_obj=mock_proxy_logging_obj, - proxy_config=mock_proxy_config, - route_type=route_type, - llm_router=mock_llm_router, - ) - - mock_proxy_config._get_hierarchical_router_settings.assert_called_once_with( - user_api_key_dict=mock_user_api_key_dict, - prisma_client=mock_prisma_client, - ) - mock_llm_router.get_model_list.assert_called_once() - - assert "user_config" in returned_data - user_config = returned_data["user_config"] - assert user_config["model_list"] == mock_model_list - assert user_config["routing_strategy"] == "least-busy" - assert user_config["timeout"] == 30.0 - assert user_config["num_retries"] == 3 - @pytest.mark.asyncio async def test_stream_timeout_header_processing(self): """ @@ -1024,3 +947,229 @@ class TestExtractErrorFromSSEChunk: # Other fields should be obtained from the original error object (if exists) +class TestOverrideOpenAIResponseModel: + """Tests for _override_openai_response_model function""" + + def test_override_model_preserves_fallback_model_when_fallback_occurred_object(self): + """ + Test that when a fallback occurred (x-litellm-attempted-fallbacks > 0), + the actual model used (fallback model) is preserved instead of being + overridden with the requested model. + + This is the regression test to ensure the model being called is properly + displayed when a fallback happens. + """ + requested_model = "gpt-4" + fallback_model = "gpt-3.5-turbo" + + # Create a mock object response with fallback model + # _hidden_params is an attribute (not a dict key) accessed via getattr + response_obj = MagicMock() + response_obj.model = fallback_model + response_obj._hidden_params = { + "additional_headers": { + "x-litellm-attempted-fallbacks": 1 + } + } + + # Call the function - should preserve fallback model + _override_openai_response_model( + response_obj=response_obj, + requested_model=requested_model, + log_context="test_context", + ) + + # Verify the model was NOT overridden - should still be the fallback model + assert response_obj.model == fallback_model + assert response_obj.model != requested_model + + def test_override_model_preserves_fallback_model_multiple_fallbacks(self): + """ + Test that when multiple fallbacks occurred, the actual model used + (fallback model) is preserved. + """ + requested_model = "gpt-4" + fallback_model = "claude-haiku-4-5-20251001" + + # Create a mock object response with fallback model + response_obj = MagicMock() + response_obj.model = fallback_model + response_obj._hidden_params = { + "additional_headers": { + "x-litellm-attempted-fallbacks": 2 # Multiple fallbacks + } + } + + # Call the function - should preserve fallback model + _override_openai_response_model( + response_obj=response_obj, + requested_model=requested_model, + log_context="test_context", + ) + + # Verify the model was NOT overridden - should still be the fallback model + assert response_obj.model == fallback_model + assert response_obj.model != requested_model + + def test_override_model_overrides_when_no_fallback_dict(self): + """ + Test that when no fallback occurred, the model is overridden + to match the requested model (dict response). + """ + requested_model = "gpt-4" + downstream_model = "gpt-3.5-turbo" + + # Create a dict response without fallback + # For dict responses, _hidden_params won't be found via getattr, + # so the fallback check won't trigger and model will be overridden + response_obj = {"model": downstream_model} + + # Call the function - should override to requested model + _override_openai_response_model( + response_obj=response_obj, + requested_model=requested_model, + log_context="test_context", + ) + + # Verify the model WAS overridden to requested model + assert response_obj["model"] == requested_model + + def test_override_model_overrides_when_no_fallback_object(self): + """ + Test that when no fallback occurred (object response), the model is overridden + to match the requested model. + """ + requested_model = "gpt-4" + downstream_model = "gpt-3.5-turbo" + + # Create a mock object response without fallback + response_obj = MagicMock() + response_obj.model = downstream_model + response_obj._hidden_params = { + "additional_headers": {} # No attempted_fallbacks header + } + + # Call the function - should override to requested model + _override_openai_response_model( + response_obj=response_obj, + requested_model=requested_model, + log_context="test_context", + ) + + # Verify the model WAS overridden to requested model + assert response_obj.model == requested_model + + def test_override_model_overrides_when_attempted_fallbacks_is_zero(self): + """ + Test that when attempted_fallbacks is 0 (no fallback occurred), + the model is overridden to match the requested model. + """ + requested_model = "gpt-4" + downstream_model = "gpt-3.5-turbo" + + # Create a mock object response + response_obj = MagicMock() + response_obj.model = downstream_model + response_obj._hidden_params = { + "additional_headers": { + "x-litellm-attempted-fallbacks": 0 # Zero means no fallback occurred + } + } + + # Call the function - should override to requested model + _override_openai_response_model( + response_obj=response_obj, + requested_model=requested_model, + log_context="test_context", + ) + + # Verify the model WAS overridden to requested model + assert response_obj.model == requested_model + + def test_override_model_overrides_when_attempted_fallbacks_is_none(self): + """ + Test that when attempted_fallbacks is None (not set), + the model is overridden to match the requested model. + """ + requested_model = "gpt-4" + downstream_model = "gpt-3.5-turbo" + + # Create a mock object response + response_obj = MagicMock() + response_obj.model = downstream_model + response_obj._hidden_params = { + "additional_headers": { + "x-litellm-attempted-fallbacks": None + } + } + + # Call the function - should override to requested model + _override_openai_response_model( + response_obj=response_obj, + requested_model=requested_model, + log_context="test_context", + ) + + # Verify the model WAS overridden to requested model + assert response_obj.model == requested_model + + def test_override_model_no_hidden_params(self): + """ + Test that when _hidden_params is not present, the model is overridden + to match the requested model. + """ + requested_model = "gpt-4" + downstream_model = "gpt-3.5-turbo" + + # Create a mock object response without _hidden_params + response_obj = MagicMock() + response_obj.model = downstream_model + # Don't set _hidden_params - getattr will return {} + + # Call the function - should override to requested model + _override_openai_response_model( + response_obj=response_obj, + requested_model=requested_model, + log_context="test_context", + ) + + # Verify the model WAS overridden to requested model + assert response_obj.model == requested_model + + def test_override_model_no_requested_model(self): + """ + Test that when requested_model is None or empty, the function returns early + without modifying the response. + """ + fallback_model = "gpt-3.5-turbo" + + # Create a mock object response + response_obj = MagicMock() + response_obj.model = fallback_model + response_obj._hidden_params = { + "additional_headers": { + "x-litellm-attempted-fallbacks": 1 + } + } + + # Call the function with None requested_model + _override_openai_response_model( + response_obj=response_obj, + requested_model=None, + log_context="test_context", + ) + + # Verify the model was not changed + assert response_obj.model == fallback_model + + # Call with empty string + _override_openai_response_model( + response_obj=response_obj, + requested_model="", + log_context="test_context", + ) + + # Verify the model was not changed + assert response_obj.model == fallback_model + + diff --git a/tests/test_litellm/proxy/test_litellm_pre_call_utils.py b/tests/test_litellm/proxy/test_litellm_pre_call_utils.py index b9485a2e4cb..da6a5aeab09 100644 --- a/tests/test_litellm/proxy/test_litellm_pre_call_utils.py +++ b/tests/test_litellm/proxy/test_litellm_pre_call_utils.py @@ -1548,3 +1548,50 @@ def test_add_guardrails_from_policy_engine(): policy_registry._initialized = False attachment_registry._attachments = [] attachment_registry._initialized = False + + +def test_add_guardrails_from_policy_engine_accepts_dynamic_policies_and_pops_from_data(): + """ + Test that add_guardrails_from_policy_engine accepts dynamic 'policies' from the request body + and removes them to prevent forwarding to the LLM provider. + + This is critical because 'policies' is a LiteLLM proxy-specific parameter that should + not be sent to the actual LLM API (e.g., OpenAI, Anthropic, etc.). + """ + from litellm.proxy.policy_engine.policy_registry import get_policy_registry + + # Setup test data with 'policies' in the request body + data = { + "model": "gpt-4", + "messages": [{"role": "user", "content": "Hello"}], + "policies": ["PII-POLICY-GLOBAL", "HIPAA-POLICY"], # Dynamic policies - should be accepted and removed + "metadata": {}, + } + + user_api_key_dict = UserAPIKeyAuth( + api_key="test-key", + team_alias="test-team", + key_alias="test-key", + ) + + # Initialize empty policy registry (we're just testing the accept and pop behavior) + policy_registry = get_policy_registry() + policy_registry._policies = {} + policy_registry._initialized = False + + # Call the function - should accept dynamic policies and not raise an error + add_guardrails_from_policy_engine( + data=data, + metadata_variable_name="metadata", + user_api_key_dict=user_api_key_dict, + ) + + # Verify that 'policies' was removed from the request body + assert "policies" not in data, "'policies' should be removed from request body to prevent forwarding to LLM provider" + + # Verify that other fields are preserved + assert "model" in data + assert data["model"] == "gpt-4" + assert "messages" in data + assert data["messages"] == [{"role": "user", "content": "Hello"}] + assert "metadata" in data diff --git a/tests/test_litellm/proxy/test_proxy_cli.py b/tests/test_litellm/proxy/test_proxy_cli.py index f52f14b860e..12065ad5b4d 100644 --- a/tests/test_litellm/proxy/test_proxy_cli.py +++ b/tests/test_litellm/proxy/test_proxy_cli.py @@ -103,7 +103,11 @@ class TestProxyInitializationHelpers: args = ProxyInitializationHelpers._get_default_unvicorn_init_args( "localhost", 8000 ) - assert args["log_config"] is None + # When json_logs is True, log_config should be set to the JSON log config dict + assert args["log_config"] is not None + assert isinstance(args["log_config"], dict) + assert "version" in args["log_config"] + assert "formatters" in args["log_config"] # Test with keepalive_timeout args = ProxyInitializationHelpers._get_default_unvicorn_init_args( @@ -218,6 +222,7 @@ class TestProxyInitializationHelpers: @patch("atexit.register") # 🔥 critical def test_skip_server_startup(self, mock_atexit_register, mock_uvicorn_run): from click.testing import CliRunner + from litellm.proxy.proxy_cli import run_server runner = CliRunner() diff --git a/tests/test_litellm/proxy/test_proxy_server.py b/tests/test_litellm/proxy/test_proxy_server.py index cb519e9f509..acd99090397 100644 --- a/tests/test_litellm/proxy/test_proxy_server.py +++ b/tests/test_litellm/proxy/test_proxy_server.py @@ -5,7 +5,7 @@ import os import socket import subprocess import sys -from datetime import datetime +from datetime import datetime, timezone from pathlib import Path from unittest import mock from unittest.mock import AsyncMock, MagicMock, mock_open, patch @@ -1049,6 +1049,82 @@ async def test_get_config_from_file(tmp_path, monkeypatch): assert result == test_config +def test_normalize_datetime_for_sorting(): + """ + Test the _normalize_datetime_for_sorting function. + Tests various scenarios: None values, ISO format strings, datetime objects (naive and aware). + """ + from litellm.proxy.proxy_server import _normalize_datetime_for_sorting + + # Test Case 1: None value + assert _normalize_datetime_for_sorting(None) is None + + # Test Case 2: ISO format string with 'Z' suffix + dt_str_z = "2024-01-15T10:30:00Z" + result = _normalize_datetime_for_sorting(dt_str_z) + assert result is not None + assert isinstance(result, datetime) + assert result.tzinfo == timezone.utc + assert result.year == 2024 + assert result.month == 1 + assert result.day == 15 + assert result.hour == 10 + assert result.minute == 30 + + # Test Case 3: ISO format string without 'Z' suffix (naive) + dt_str_naive = "2024-01-15T10:30:00" + result = _normalize_datetime_for_sorting(dt_str_naive) + assert result is not None + assert isinstance(result, datetime) + assert result.tzinfo == timezone.utc + + # Test Case 4: ISO format string with timezone offset + dt_str_tz = "2024-01-15T10:30:00+05:00" + result = _normalize_datetime_for_sorting(dt_str_tz) + assert result is not None + assert isinstance(result, datetime) + assert result.tzinfo == timezone.utc + # Should convert from +05:00 to UTC (subtract 5 hours) + assert result.hour == 5 # 10:30 - 5 hours = 5:30 UTC + + # Test Case 5: Naive datetime object + naive_dt = datetime(2024, 1, 15, 10, 30, 0) + result = _normalize_datetime_for_sorting(naive_dt) + assert result is not None + assert isinstance(result, datetime) + assert result.tzinfo == timezone.utc + assert result.year == 2024 + assert result.month == 1 + assert result.day == 15 + + # Test Case 6: Timezone-aware datetime object (non-UTC) + from datetime import timedelta + aware_dt = datetime(2024, 1, 15, 10, 30, 0, tzinfo=timezone(timedelta(hours=5))) + result = _normalize_datetime_for_sorting(aware_dt) + assert result is not None + assert isinstance(result, datetime) + assert result.tzinfo == timezone.utc + # Should convert from +05:00 to UTC + assert result.hour == 5 + + # Test Case 7: UTC-aware datetime object + utc_dt = datetime(2024, 1, 15, 10, 30, 0, tzinfo=timezone.utc) + result = _normalize_datetime_for_sorting(utc_dt) + assert result is not None + assert isinstance(result, datetime) + assert result.tzinfo == timezone.utc + assert result == utc_dt + + # Test Case 8: Invalid string format + invalid_str = "not-a-date" + result = _normalize_datetime_for_sorting(invalid_str) + assert result is None + + # Test Case 9: Invalid type (should return None) + result = _normalize_datetime_for_sorting(12345) + assert result is None + + @pytest.mark.asyncio async def test_add_proxy_budget_to_db_only_creates_user_no_keys(): """ @@ -2907,7 +2983,8 @@ def test_root_redirect_when_docs_url_not_root_and_redirect_url_set(monkeypatch): assert response.headers["location"] == test_redirect_url -def test_get_image_non_root_uses_var_lib_assets_dir(monkeypatch): +@pytest.mark.asyncio +async def test_get_image_non_root_uses_var_lib_assets_dir(monkeypatch): """ Test that get_image uses /var/lib/litellm/assets when LITELLM_NON_ROOT is true. """ @@ -2936,13 +3013,14 @@ def test_get_image_non_root_uses_var_lib_assets_dir(monkeypatch): mock_getenv.side_effect = getenv_side_effect # Call the function - get_image() + await get_image() # Verify makedirs was called with /var/lib/litellm/assets mock_makedirs.assert_called_once_with("/var/lib/litellm/assets", exist_ok=True) -def test_get_image_non_root_fallback_to_default_logo(monkeypatch): +@pytest.mark.asyncio +async def test_get_image_non_root_fallback_to_default_logo(monkeypatch): """ Test that get_image falls back to default_site_logo when logo doesn't exist in /var/lib/litellm/assets for non-root case. @@ -2982,7 +3060,7 @@ def test_get_image_non_root_fallback_to_default_logo(monkeypatch): mock_getenv.side_effect = getenv_side_effect # Call the function - get_image() + await get_image() # Verify makedirs was called with /var/lib/litellm/assets mock_makedirs.assert_called_once_with("/var/lib/litellm/assets", exist_ok=True) @@ -2996,7 +3074,8 @@ def test_get_image_non_root_fallback_to_default_logo(monkeypatch): assert mock_file_response.called, "FileResponse should be called" -def test_get_image_root_case_uses_current_dir(monkeypatch): +@pytest.mark.asyncio +async def test_get_image_root_case_uses_current_dir(monkeypatch): """ Test that get_image uses current_dir when LITELLM_NON_ROOT is not true. """ @@ -3025,7 +3104,7 @@ def test_get_image_root_case_uses_current_dir(monkeypatch): mock_getenv.side_effect = getenv_side_effect # Call the function - get_image() + await get_image() # Verify makedirs was NOT called with /var/lib/litellm/assets (should not create it for root case) var_lib_assets_calls = [ @@ -3124,943 +3203,3 @@ def test_deep_merge_dicts_skips_none_and_empty_lists(monkeypatch): assert result["general_settings"]["nested"]["key1"] == "updated_value1" assert result["general_settings"]["nested"]["key2"] == "value2" assert result["general_settings"]["nested"]["key3"] == "value3" - - -@pytest.mark.asyncio -async def test_get_hierarchical_router_settings(): - """ - Test _get_hierarchical_router_settings method's priority order: Key > Team > Global - """ - from unittest.mock import AsyncMock, MagicMock - - from litellm.proxy._types import UserAPIKeyAuth - from litellm.proxy.proxy_server import ProxyConfig - - proxy_config = ProxyConfig() - - # Test Case 1: Returns None when prisma_client is None - result = await proxy_config._get_hierarchical_router_settings( - user_api_key_dict=None, - prisma_client=None, - ) - assert result is None - - # Test Case 2: Returns key-level router_settings when available (as dict) - mock_user_api_key_dict = MagicMock(spec=UserAPIKeyAuth) - mock_user_api_key_dict.router_settings = {"routing_strategy": "key-level", "timeout": 10} - mock_user_api_key_dict.team_id = None - - mock_prisma_client = MagicMock() - - result = await proxy_config._get_hierarchical_router_settings( - user_api_key_dict=mock_user_api_key_dict, - prisma_client=mock_prisma_client, - ) - assert result == {"routing_strategy": "key-level", "timeout": 10} - - # Test Case 3: Returns key-level router_settings when available (as YAML string) - mock_user_api_key_dict.router_settings = "routing_strategy: key-yaml\ntimeout: 20" - result = await proxy_config._get_hierarchical_router_settings( - user_api_key_dict=mock_user_api_key_dict, - prisma_client=mock_prisma_client, - ) - assert result == {"routing_strategy": "key-yaml", "timeout": 20} - - # Test Case 4: Falls back to team-level router_settings when key-level is not available - mock_user_api_key_dict.router_settings = None - mock_user_api_key_dict.team_id = "team-123" - - mock_team_obj = MagicMock() - mock_team_obj.router_settings = {"routing_strategy": "team-level", "timeout": 30} - - mock_prisma_client.db.litellm_teamtable.find_unique = AsyncMock( - return_value=mock_team_obj - ) - - result = await proxy_config._get_hierarchical_router_settings( - user_api_key_dict=mock_user_api_key_dict, - prisma_client=mock_prisma_client, - ) - assert result == {"routing_strategy": "team-level", "timeout": 30} - mock_prisma_client.db.litellm_teamtable.find_unique.assert_called_once_with( - where={"team_id": "team-123"} - ) - - # Test Case 5: Falls back to global router_settings when neither key nor team settings are available - mock_user_api_key_dict.router_settings = None - mock_prisma_client.db.litellm_teamtable.find_unique = AsyncMock(return_value=None) - - mock_db_config = MagicMock() - mock_db_config.param_value = {"routing_strategy": "global-level", "timeout": 40} - - mock_prisma_client.db.litellm_config.find_first = AsyncMock( - return_value=mock_db_config - ) - - result = await proxy_config._get_hierarchical_router_settings( - user_api_key_dict=mock_user_api_key_dict, - prisma_client=mock_prisma_client, - ) - assert result == {"routing_strategy": "global-level", "timeout": 40} - mock_prisma_client.db.litellm_config.find_first.assert_called_once_with( - where={"param_name": "router_settings"} - ) - - # Test Case 6: Returns None when no settings are found - mock_user_api_key_dict.router_settings = None - mock_prisma_client.db.litellm_teamtable.find_unique = AsyncMock(return_value=None) - mock_prisma_client.db.litellm_config.find_first = AsyncMock(return_value=None) - - result = await proxy_config._get_hierarchical_router_settings( - user_api_key_dict=mock_user_api_key_dict, - prisma_client=mock_prisma_client, - ) - assert result is None - - -@pytest.mark.asyncio -async def test_model_info_v2_pagination_basic(monkeypatch): - """ - Test basic pagination functionality for /v2/model/info endpoint. - Tests multiple pages with different page sizes. - """ - from unittest.mock import AsyncMock, MagicMock - - from litellm.proxy._types import UserAPIKeyAuth - from litellm.proxy.proxy_server import app, proxy_config, user_api_key_auth - - # Create 75 mock models for testing pagination - mock_models = [ - { - "model_name": f"model-{i}", - "litellm_params": {"model": f"gpt-{i}"}, - "model_info": {"id": f"model-{i}"}, - } - for i in range(1, 76) # 75 models total - ] - - # Mock llm_router - mock_router = MagicMock() - mock_router.model_list = mock_models - - # Mock prisma_client - mock_prisma_client = MagicMock() - - # Mock proxy_config.get_config - mock_get_config = AsyncMock(return_value={}) - - # Mock user authentication - mock_user_api_key_dict = MagicMock(spec=UserAPIKeyAuth) - mock_user_api_key_dict.user_id = "test-user" - mock_user_api_key_dict.api_key = "test-key" - mock_user_api_key_dict.team_models = [] - mock_user_api_key_dict.models = [] - - # Apply monkeypatches - monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", mock_router) - monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) - monkeypatch.setattr("litellm.proxy.proxy_server.user_model", None) - monkeypatch.setattr(proxy_config, "get_config", mock_get_config) - - # Override auth dependency - original_overrides = app.dependency_overrides.copy() - app.dependency_overrides[user_api_key_auth] = lambda: mock_user_api_key_dict - - client = TestClient(app) - try: - # Test page 1 with size 25 (should return models 1-25) - response = client.get("/v2/model/info", params={"page": 1, "size": 25}) - assert response.status_code == 200 - data = response.json() - assert data["total_count"] == 75 - assert data["current_page"] == 1 - assert data["size"] == 25 - assert data["total_pages"] == 3 # ceil(75/25) = 3 - assert len(data["data"]) == 25 - assert data["data"][0]["model_name"] == "model-1" - assert data["data"][24]["model_name"] == "model-25" - - # Test page 2 with size 25 (should return models 26-50) - response = client.get("/v2/model/info", params={"page": 2, "size": 25}) - assert response.status_code == 200 - data = response.json() - assert data["total_count"] == 75 - assert data["current_page"] == 2 - assert data["size"] == 25 - assert data["total_pages"] == 3 - assert len(data["data"]) == 25 - assert data["data"][0]["model_name"] == "model-26" - assert data["data"][24]["model_name"] == "model-50" - - # Test page 3 with size 25 (should return models 51-75) - response = client.get("/v2/model/info", params={"page": 3, "size": 25}) - assert response.status_code == 200 - data = response.json() - assert data["total_count"] == 75 - assert data["current_page"] == 3 - assert data["size"] == 25 - assert data["total_pages"] == 3 - assert len(data["data"]) == 25 - assert data["data"][0]["model_name"] == "model-51" - assert data["data"][24]["model_name"] == "model-75" - - # Test different page size (size 10) - response = client.get("/v2/model/info", params={"page": 1, "size": 10}) - assert response.status_code == 200 - data = response.json() - assert data["total_count"] == 75 - assert data["current_page"] == 1 - assert data["size"] == 10 - assert data["total_pages"] == 8 # ceil(75/10) = 8 - assert len(data["data"]) == 10 - - finally: - app.dependency_overrides = original_overrides - - -@pytest.mark.asyncio -async def test_model_info_v2_pagination_edge_cases(monkeypatch): - """ - Test edge cases for pagination in /v2/model/info endpoint. - Tests empty results, last page with partial results, and boundary conditions. - """ - from unittest.mock import AsyncMock, MagicMock - - from litellm.proxy._types import UserAPIKeyAuth - from litellm.proxy.proxy_server import app, proxy_config, user_api_key_auth - - # Mock prisma_client - mock_prisma_client = MagicMock() - - # Mock user authentication - mock_user_api_key_dict = MagicMock(spec=UserAPIKeyAuth) - mock_user_api_key_dict.user_id = "test-user" - mock_user_api_key_dict.api_key = "test-key" - mock_user_api_key_dict.team_models = [] - mock_user_api_key_dict.models = [] - - # Mock proxy_config.get_config - mock_get_config = AsyncMock(return_value={}) - - # Apply monkeypatches - monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) - monkeypatch.setattr("litellm.proxy.proxy_server.user_model", None) - monkeypatch.setattr(proxy_config, "get_config", mock_get_config) - - # Override auth dependency - original_overrides = app.dependency_overrides.copy() - app.dependency_overrides[user_api_key_auth] = lambda: mock_user_api_key_dict - - client = TestClient(app) - try: - # Test Case 1: Empty model list (no models configured) - mock_router_empty = MagicMock() - mock_router_empty.model_list = [] - monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", mock_router_empty) - - response = client.get("/v2/model/info", params={"page": 1, "size": 25}) - assert response.status_code == 200 - data = response.json() - assert data["total_count"] == 0 - assert data["current_page"] == 1 - assert data["size"] == 25 - assert data["total_pages"] == 0 - assert len(data["data"]) == 0 - - # Test Case 2: Last page with partial results (23 models, page size 10) - mock_models_partial = [ - { - "model_name": f"model-{i}", - "litellm_params": {"model": f"gpt-{i}"}, - "model_info": {"id": f"model-{i}"}, - } - for i in range(1, 24) # 23 models total - ] - mock_router_partial = MagicMock() - mock_router_partial.model_list = mock_models_partial - monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", mock_router_partial) - - # Page 1 should have 10 models - response = client.get("/v2/model/info", params={"page": 1, "size": 10}) - assert response.status_code == 200 - data = response.json() - assert data["total_count"] == 23 - assert data["current_page"] == 1 - assert data["total_pages"] == 3 # ceil(23/10) = 3 - assert len(data["data"]) == 10 - - # Page 2 should have 10 models - response = client.get("/v2/model/info", params={"page": 2, "size": 10}) - assert response.status_code == 200 - data = response.json() - assert data["total_count"] == 23 - assert data["current_page"] == 2 - assert data["total_pages"] == 3 - assert len(data["data"]) == 10 - - # Page 3 (last page) should have only 3 models - response = client.get("/v2/model/info", params={"page": 3, "size": 10}) - assert response.status_code == 200 - data = response.json() - assert data["total_count"] == 23 - assert data["current_page"] == 3 - assert data["total_pages"] == 3 - assert len(data["data"]) == 3 - assert data["data"][0]["model_name"] == "model-21" - assert data["data"][2]["model_name"] == "model-23" - - # Test Case 3: Page beyond available pages (should return empty data) - response = client.get("/v2/model/info", params={"page": 4, "size": 10}) - assert response.status_code == 200 - data = response.json() - assert data["total_count"] == 23 - assert data["current_page"] == 4 - assert data["total_pages"] == 3 - assert len(data["data"]) == 0 # No data for page beyond total_pages - - # Test Case 4: Single model with page size 1 - mock_models_single = [ - { - "model_name": "single-model", - "litellm_params": {"model": "gpt-4"}, - "model_info": {"id": "single-model"}, - } - ] - mock_router_single = MagicMock() - mock_router_single.model_list = mock_models_single - monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", mock_router_single) - - response = client.get("/v2/model/info", params={"page": 1, "size": 1}) - assert response.status_code == 200 - data = response.json() - assert data["total_count"] == 1 - assert data["current_page"] == 1 - assert data["total_pages"] == 1 - assert len(data["data"]) == 1 - assert data["data"][0]["model_name"] == "single-model" - - finally: - app.dependency_overrides = original_overrides - - -def test_enrich_model_info_with_litellm_data(): - """ - Test the _enrich_model_info_with_litellm_data helper function. - Tests model info enrichment, debug mode, and sensitive info removal. - """ - from unittest.mock import MagicMock, patch - - from litellm.proxy.proxy_server import _enrich_model_info_with_litellm_data - - # Test Case 1: Basic model enrichment without debug - model = { - "model_name": "test-model", - "litellm_params": {"model": "gpt-3.5-turbo"}, - "model_info": {"id": "test-model"}, - "api_key": "sk-secret-key", # Should be removed - } - - with patch("litellm.proxy.proxy_server.get_litellm_model_info") as mock_get_info, patch( - "litellm.proxy.proxy_server.remove_sensitive_info_from_deployment" - ) as mock_remove_sensitive: - mock_get_info.return_value = { - "input_cost_per_token": 0.001, - "output_cost_per_token": 0.002, - "max_tokens": 4096, - } - mock_remove_sensitive.return_value = { - "model_name": "test-model", - "litellm_params": {"model": "gpt-3.5-turbo"}, - "model_info": { - "id": "test-model", - "input_cost_per_token": 0.001, - "output_cost_per_token": 0.002, - "max_tokens": 4096, - }, - } - - result = _enrich_model_info_with_litellm_data(model=model, debug=False) - - # Verify get_litellm_model_info was called - mock_get_info.assert_called_once_with(model=model) - # Verify remove_sensitive_info_from_deployment was called - mock_remove_sensitive.assert_called_once() - # Verify result doesn't have api_key - assert "api_key" not in result - # Verify model_info was enriched - assert "input_cost_per_token" in result["model_info"] - - # Test Case 2: Model enrichment with debug mode - model_with_debug = { - "model_name": "test-model-debug", - "litellm_params": {"model": "gpt-4"}, - "model_info": {}, - } - - mock_router = MagicMock() - mock_client = MagicMock() - mock_router._get_client.return_value = mock_client - - with patch("litellm.proxy.proxy_server.get_litellm_model_info") as mock_get_info, patch( - "litellm.proxy.proxy_server.remove_sensitive_info_from_deployment" - ) as mock_remove_sensitive: - mock_get_info.return_value = {} - mock_remove_sensitive.return_value = { - "model_name": "test-model-debug", - "litellm_params": {"model": "gpt-4"}, - "model_info": {}, - "openai_client": str(mock_client), - } - - result = _enrich_model_info_with_litellm_data( - model=model_with_debug, debug=True, llm_router=mock_router - ) - - # Verify debug info was added - mock_remove_sensitive.assert_called_once() - call_args = mock_remove_sensitive.call_args[0][0] - assert "openai_client" in call_args - # Verify router._get_client was called for debug - mock_router._get_client.assert_called_once() - - # Test Case 3: Model with fallback to litellm.get_model_info - model_fallback = { - "model_name": "test-model-fallback", - "litellm_params": {"model": "claude-3-opus"}, - "model_info": {}, - } - - with patch("litellm.proxy.proxy_server.get_litellm_model_info") as mock_get_info, patch( - "litellm.get_model_info" - ) as mock_litellm_info, patch( - "litellm.proxy.proxy_server.remove_sensitive_info_from_deployment" - ) as mock_remove_sensitive: - # First call returns empty, triggering fallback - mock_get_info.return_value = {} - mock_litellm_info.return_value = { - "input_cost_per_token": 0.015, - "output_cost_per_token": 0.075, - "max_tokens": 200000, - } - mock_remove_sensitive.return_value = { - "model_name": "test-model-fallback", - "litellm_params": {"model": "claude-3-opus"}, - "model_info": { - "input_cost_per_token": 0.015, - "output_cost_per_token": 0.075, - "max_tokens": 200000, - }, - } - - result = _enrich_model_info_with_litellm_data(model=model_fallback, debug=False) - - # Verify fallback was attempted - mock_litellm_info.assert_called_once_with(model="claude-3-opus") - # Verify model_info was enriched with fallback data - call_args = mock_remove_sensitive.call_args[0][0] - assert call_args["model_info"]["input_cost_per_token"] == 0.015 - - # Test Case 4: Model with split model name fallback - model_split = { - "model_name": "test-model-split", - "litellm_params": {"model": "azure/gpt-4"}, - "model_info": {}, - } - - with patch("litellm.proxy.proxy_server.get_litellm_model_info") as mock_get_info, patch( - "litellm.get_model_info" - ) as mock_litellm_info, patch( - "litellm.proxy.proxy_server.remove_sensitive_info_from_deployment" - ) as mock_remove_sensitive: - # Both first and second pass return empty, triggering third pass - mock_get_info.return_value = {} - # Second pass (no split) - mock_litellm_info.side_effect = [ - {}, # First call returns empty - {"max_tokens": 8192}, # Third pass with split succeeds - ] - mock_remove_sensitive.return_value = { - "model_name": "test-model-split", - "litellm_params": {"model": "azure/gpt-4"}, - "model_info": {"max_tokens": 8192}, - } - - result = _enrich_model_info_with_litellm_data(model=model_split, debug=False) - - # Verify third pass was attempted with split model name - assert mock_litellm_info.call_count == 2 - # Check that second call used split model name - second_call = mock_litellm_info.call_args_list[1] - assert second_call[1]["model"] == "gpt-4" - assert second_call[1]["custom_llm_provider"] == "azure" - - # Test Case 5: Model with existing model_info (should preserve existing keys) - model_existing = { - "model_name": "test-model-existing", - "litellm_params": {"model": "gpt-3.5-turbo"}, - "model_info": {"id": "existing-id", "custom_key": "custom_value"}, - } - - with patch("litellm.proxy.proxy_server.get_litellm_model_info") as mock_get_info, patch( - "litellm.proxy.proxy_server.remove_sensitive_info_from_deployment" - ) as mock_remove_sensitive: - mock_get_info.return_value = { - "input_cost_per_token": 0.001, - "id": "new-id", # Should not override existing "id" - } - mock_remove_sensitive.return_value = { - "model_name": "test-model-existing", - "litellm_params": {"model": "gpt-3.5-turbo"}, - "model_info": { - "id": "existing-id", # Existing key preserved - "custom_key": "custom_value", # Existing key preserved - "input_cost_per_token": 0.001, # New key added - }, - } - - result = _enrich_model_info_with_litellm_data(model=model_existing, debug=False) - - # Verify existing keys are preserved - call_args = mock_remove_sensitive.call_args[0][0] - assert call_args["model_info"]["id"] == "existing-id" - assert call_args["model_info"]["custom_key"] == "custom_value" - assert call_args["model_info"]["input_cost_per_token"] == 0.001 - - -@pytest.mark.asyncio -async def test_model_list_scope_parameter_validation(monkeypatch): - """Test that invalid scope parameter raises HTTPException""" - from fastapi import HTTPException - from litellm.proxy._types import UserAPIKeyAuth, LitellmUserRoles - from litellm.proxy.proxy_server import model_list - - mock_user_api_key_dict = UserAPIKeyAuth( - user_id="test-user", - user_role=LitellmUserRoles.INTERNAL_USER, - api_key="test-key", - ) - - # Test invalid scope parameter - with pytest.raises(HTTPException) as exc_info: - await model_list( - user_api_key_dict=mock_user_api_key_dict, - scope="invalid_scope", - ) - - assert exc_info.value.status_code == 400 - assert "Invalid scope parameter" in exc_info.value.detail - assert "Only 'expand' is currently supported" in exc_info.value.detail - - -@pytest.mark.asyncio -async def test_model_list_scope_expand_proxy_admin(monkeypatch): - """Test that proxy admin with scope=expand returns all proxy models""" - from litellm.proxy._types import UserAPIKeyAuth, LitellmUserRoles, LiteLLM_UserTable - from litellm.proxy.proxy_server import model_list - - # Mock user API key dict for proxy admin - mock_user_api_key_dict = UserAPIKeyAuth( - user_id="proxy-admin-user", - user_role=LitellmUserRoles.PROXY_ADMIN, - api_key="test-key", - ) - - # Mock llm_router with proxy models - mock_router = MagicMock() - mock_router.get_model_names.return_value = ["gpt-4", "gpt-3.5-turbo", "claude-3-opus"] - mock_router.get_model_access_groups.return_value = {} - - # Mock prisma_client - mock_prisma_client = MagicMock() - - # Mock user_api_key_cache - mock_user_api_key_cache = MagicMock() - - # Mock proxy_logging_obj - mock_proxy_logging_obj = MagicMock() - - # Mock get_complete_model_list - mock_all_models = ["gpt-4", "gpt-3.5-turbo", "claude-3-opus"] - - # Mock create_model_info_response - def mock_create_model_info_response(model_id, provider, include_metadata=False, fallback_type=None, llm_router=None): - return {"id": model_id, "object": "model"} - - # Apply monkeypatches - monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", mock_router) - monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) - monkeypatch.setattr("litellm.proxy.proxy_server.user_api_key_cache", mock_user_api_key_cache) - monkeypatch.setattr("litellm.proxy.proxy_server.proxy_logging_obj", mock_proxy_logging_obj) - monkeypatch.setattr("litellm.proxy.proxy_server.general_settings", {}) - monkeypatch.setattr("litellm.proxy.proxy_server.user_model", None) - monkeypatch.setattr( - "litellm.proxy.auth.model_checks.get_complete_model_list", - lambda **kwargs: mock_all_models, - ) - monkeypatch.setattr( - "litellm.proxy.utils.create_model_info_response", - mock_create_model_info_response, - ) - - # Call model_list with scope=expand - result = await model_list( - user_api_key_dict=mock_user_api_key_dict, - scope="expand", - ) - - # Verify result contains all proxy models - assert result["object"] == "list" - assert len(result["data"]) == 3 - assert all(model["id"] in mock_all_models for model in result["data"]) - - # Verify router methods were called - mock_router.get_model_names.assert_called_once() - mock_router.get_model_access_groups.assert_called_once() - - -@pytest.mark.asyncio -async def test_model_list_scope_expand_org_admin(monkeypatch): - """Test that org admin with scope=expand returns all proxy models""" - from litellm.proxy._types import ( - UserAPIKeyAuth, - LitellmUserRoles, - LiteLLM_UserTable, - ) - from litellm.proxy.proxy_server import model_list - - # Mock user API key dict for org admin - mock_user_api_key_dict = UserAPIKeyAuth( - user_id="org-admin-user", - user_role=LitellmUserRoles.INTERNAL_USER, # Not proxy admin, but org admin - api_key="test-key", - ) - - # Mock user object with org admin membership - from litellm.proxy._types import LiteLLM_OrganizationMembershipTable - from datetime import datetime - - mock_user_obj = LiteLLM_UserTable( - user_id="org-admin-user", - user_email="org-admin@example.com", - organization_memberships=[ - LiteLLM_OrganizationMembershipTable( - user_id="org-admin-user", - organization_id="org-123", - user_role=LitellmUserRoles.ORG_ADMIN.value, - spend=0.0, - created_at=datetime.now(), - updated_at=datetime.now(), - ) - ], - teams=[], - ) - - # Mock llm_router with proxy models - mock_router = MagicMock() - mock_router.get_model_names.return_value = ["gpt-4", "gpt-3.5-turbo", "claude-3-opus"] - mock_router.get_model_access_groups.return_value = {} - - # Mock prisma_client - mock_prisma_client = MagicMock() - - # Mock user_api_key_cache - mock_user_api_key_cache = MagicMock() - - # Mock proxy_logging_obj - mock_proxy_logging_obj = MagicMock() - - # Mock get_user_object to return user with org admin role - async def mock_get_user_object(*args, **kwargs): - return mock_user_obj - - # Mock get_complete_model_list - mock_all_models = ["gpt-4", "gpt-3.5-turbo", "claude-3-opus"] - - # Mock create_model_info_response - def mock_create_model_info_response(model_id, provider, include_metadata=False, fallback_type=None, llm_router=None): - return {"id": model_id, "object": "model"} - - # Apply monkeypatches - monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", mock_router) - monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) - monkeypatch.setattr("litellm.proxy.proxy_server.user_api_key_cache", mock_user_api_key_cache) - monkeypatch.setattr("litellm.proxy.proxy_server.proxy_logging_obj", mock_proxy_logging_obj) - monkeypatch.setattr("litellm.proxy.proxy_server.general_settings", {}) - monkeypatch.setattr("litellm.proxy.proxy_server.user_model", None) - monkeypatch.setattr( - "litellm.proxy.auth.auth_checks.get_user_object", - mock_get_user_object, - ) - monkeypatch.setattr( - "litellm.proxy.auth.model_checks.get_complete_model_list", - lambda **kwargs: mock_all_models, - ) - monkeypatch.setattr( - "litellm.proxy.utils.create_model_info_response", - mock_create_model_info_response, - ) - - # Call model_list with scope=expand - result = await model_list( - user_api_key_dict=mock_user_api_key_dict, - scope="expand", - ) - - # Verify result contains all proxy models - assert result["object"] == "list" - assert len(result["data"]) == 3 - assert all(model["id"] in mock_all_models for model in result["data"]) - - # Verify router methods were called - mock_router.get_model_names.assert_called_once() - mock_router.get_model_access_groups.assert_called_once() - - -@pytest.mark.asyncio -async def test_model_list_scope_expand_team_admin(monkeypatch): - """Test that team admin with scope=expand returns all proxy models""" - from litellm.proxy._types import ( - UserAPIKeyAuth, - LitellmUserRoles, - LiteLLM_UserTable, - LiteLLM_TeamTable, - ) - from litellm.proxy.proxy_server import model_list - - # Mock user API key dict for team admin - mock_user_api_key_dict = UserAPIKeyAuth( - user_id="team-admin-user", - user_role=LitellmUserRoles.INTERNAL_USER, # Not proxy admin, but team admin - api_key="test-key", - ) - - # Mock team with user as admin - use dict structure that matches Prisma return - mock_team = MagicMock() - mock_team.model_dump.return_value = { - "team_id": "team-123", - "members_with_roles": [ - {"user_id": "team-admin-user", "role": "admin"} - ], - } - # Create team object from the dict (validator will convert members_with_roles to Member objects) - mock_team_obj = LiteLLM_TeamTable(**mock_team.model_dump()) - - # Mock user object with team membership - mock_user_obj = LiteLLM_UserTable( - user_id="team-admin-user", - user_email="team-admin@example.com", - organization_memberships=[], - teams=["team-123"], - ) - - # Mock llm_router with proxy models - mock_router = MagicMock() - mock_router.get_model_names.return_value = ["gpt-4", "gpt-3.5-turbo", "claude-3-opus"] - mock_router.get_model_access_groups.return_value = {} - - # Mock prisma_client - mock_prisma_client = MagicMock() - mock_prisma_client.db.litellm_teamtable.find_many = AsyncMock( - return_value=[mock_team] - ) - - # Mock user_api_key_cache - mock_user_api_key_cache = MagicMock() - - # Mock proxy_logging_obj - mock_proxy_logging_obj = MagicMock() - - # Mock get_user_object to return user with team membership - async def mock_get_user_object(*args, **kwargs): - return mock_user_obj - - # Mock get_complete_model_list - mock_all_models = ["gpt-4", "gpt-3.5-turbo", "claude-3-opus"] - - # Mock create_model_info_response - def mock_create_model_info_response(model_id, provider, include_metadata=False, fallback_type=None, llm_router=None): - return {"id": model_id, "object": "model"} - - # Apply monkeypatches - monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", mock_router) - monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) - monkeypatch.setattr("litellm.proxy.proxy_server.user_api_key_cache", mock_user_api_key_cache) - monkeypatch.setattr("litellm.proxy.proxy_server.proxy_logging_obj", mock_proxy_logging_obj) - monkeypatch.setattr("litellm.proxy.proxy_server.general_settings", {}) - monkeypatch.setattr("litellm.proxy.proxy_server.user_model", None) - monkeypatch.setattr( - "litellm.proxy.auth.auth_checks.get_user_object", - mock_get_user_object, - ) - monkeypatch.setattr( - "litellm.proxy.auth.model_checks.get_complete_model_list", - lambda **kwargs: mock_all_models, - ) - monkeypatch.setattr( - "litellm.proxy.utils.create_model_info_response", - mock_create_model_info_response, - ) - - # Call model_list with scope=expand - result = await model_list( - user_api_key_dict=mock_user_api_key_dict, - scope="expand", - ) - - # Verify result contains all proxy models - assert result["object"] == "list" - assert len(result["data"]) == 3 - assert all(model["id"] in mock_all_models for model in result["data"]) - - # Verify router methods were called - mock_router.get_model_names.assert_called_once() - mock_router.get_model_access_groups.assert_called_once() - - -@pytest.mark.asyncio -async def test_model_list_scope_expand_normal_user(monkeypatch): - """Test that normal internal user with scope=expand returns only their models (not expanded)""" - from litellm.proxy._types import UserAPIKeyAuth, LitellmUserRoles, LiteLLM_UserTable - from litellm.proxy.proxy_server import model_list - - # Mock user API key dict for normal internal user - mock_user_api_key_dict = UserAPIKeyAuth( - user_id="normal-user", - user_role=LitellmUserRoles.INTERNAL_USER, - api_key="test-key", - models=["gpt-3.5-turbo"], # User only has access to this model - ) - - # Mock user object without admin privileges - mock_user_obj = LiteLLM_UserTable( - user_id="normal-user", - user_email="normal@example.com", - organization_memberships=[], # No org admin - teams=[], # No teams - ) - - # Mock llm_router - mock_router = MagicMock() - mock_router.get_model_names.return_value = ["gpt-4", "gpt-3.5-turbo", "claude-3-opus"] - - # Mock prisma_client - mock_prisma_client = MagicMock() - - # Mock user_api_key_cache - mock_user_api_key_cache = MagicMock() - - # Mock proxy_logging_obj - mock_proxy_logging_obj = MagicMock() - - # Mock get_user_object to return user without admin privileges - async def mock_get_user_object(*args, **kwargs): - return mock_user_obj - - # Mock get_available_models_for_user to return only user's models - async def mock_get_available_models_for_user(*args, **kwargs): - return ["gpt-3.5-turbo"] # Only user's accessible models - - # Mock create_model_info_response - def mock_create_model_info_response(model_id, provider, include_metadata=False, fallback_type=None, llm_router=None): - return {"id": model_id, "object": "model"} - - # Apply monkeypatches - monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", mock_router) - monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) - monkeypatch.setattr("litellm.proxy.proxy_server.user_api_key_cache", mock_user_api_key_cache) - monkeypatch.setattr("litellm.proxy.proxy_server.proxy_logging_obj", mock_proxy_logging_obj) - monkeypatch.setattr("litellm.proxy.proxy_server.general_settings", {}) - monkeypatch.setattr("litellm.proxy.proxy_server.user_model", None) - monkeypatch.setattr( - "litellm.proxy.auth.auth_checks.get_user_object", - mock_get_user_object, - ) - monkeypatch.setattr( - "litellm.proxy.utils.get_available_models_for_user", - mock_get_available_models_for_user, - ) - monkeypatch.setattr( - "litellm.proxy.utils.create_model_info_response", - mock_create_model_info_response, - ) - - # Call model_list with scope=expand - result = await model_list( - user_api_key_dict=mock_user_api_key_dict, - scope="expand", - ) - - # Verify result contains only user's models (not all proxy models) - assert result["object"] == "list" - assert len(result["data"]) == 1 - assert result["data"][0]["id"] == "gpt-3.5-turbo" - - # Verify router methods were NOT called (normal path, not expanded) - mock_router.get_model_names.assert_not_called() - mock_router.get_model_access_groups.assert_not_called() - - -@pytest.mark.asyncio -async def test_model_list_no_scope_parameter(monkeypatch): - """Test that model_list without scope parameter uses normal behavior""" - from litellm.proxy._types import UserAPIKeyAuth, LitellmUserRoles - from litellm.proxy.proxy_server import model_list - - # Mock user API key dict - mock_user_api_key_dict = UserAPIKeyAuth( - user_id="test-user", - user_role=LitellmUserRoles.INTERNAL_USER, - api_key="test-key", - models=["gpt-3.5-turbo"], - ) - - # Mock llm_router - mock_router = MagicMock() - - # Mock prisma_client - mock_prisma_client = MagicMock() - - # Mock user_api_key_cache - mock_user_api_key_cache = MagicMock() - - # Mock proxy_logging_obj - mock_proxy_logging_obj = MagicMock() - - # Mock get_available_models_for_user - async def mock_get_available_models_for_user(*args, **kwargs): - return ["gpt-3.5-turbo"] - - # Mock create_model_info_response - def mock_create_model_info_response(model_id, provider, include_metadata=False, fallback_type=None, llm_router=None): - return {"id": model_id, "object": "model"} - - # Apply monkeypatches - monkeypatch.setattr("litellm.proxy.proxy_server.llm_router", mock_router) - monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) - monkeypatch.setattr("litellm.proxy.proxy_server.user_api_key_cache", mock_user_api_key_cache) - monkeypatch.setattr("litellm.proxy.proxy_server.proxy_logging_obj", mock_proxy_logging_obj) - monkeypatch.setattr("litellm.proxy.proxy_server.general_settings", {}) - monkeypatch.setattr("litellm.proxy.proxy_server.user_model", None) - monkeypatch.setattr( - "litellm.proxy.utils.get_available_models_for_user", - mock_get_available_models_for_user, - ) - monkeypatch.setattr( - "litellm.proxy.utils.create_model_info_response", - mock_create_model_info_response, - ) - - # Call model_list without scope parameter - result = await model_list( - user_api_key_dict=mock_user_api_key_dict, - scope=None, - ) - - # Verify result uses normal behavior - assert result["object"] == "list" - assert len(result["data"]) == 1 - assert result["data"][0]["id"] == "gpt-3.5-turbo" - - # Verify router methods were NOT called (normal path) - mock_router.get_model_names.assert_not_called() - mock_router.get_model_access_groups.assert_not_called() diff --git a/tests/test_litellm/proxy/test_response_model_sanitization.py b/tests/test_litellm/proxy/test_response_model_sanitization.py new file mode 100644 index 00000000000..b1bb8d0ed39 --- /dev/null +++ b/tests/test_litellm/proxy/test_response_model_sanitization.py @@ -0,0 +1,217 @@ +import asyncio +import json +import os +import sys +from typing import AsyncGenerator +from unittest.mock import AsyncMock, MagicMock + +import pytest +import yaml +from fastapi.testclient import TestClient + +sys.path.insert(0, os.path.abspath("../../..")) + +import litellm + +pytestmark = pytest.mark.flaky(condition=False) + + +def _initialize_proxy_with_config(config: dict, tmp_path) -> TestClient: + """ + Initialize the proxy server with a temporary config file and return a TestClient. + + IMPORTANT: proxy_server.initialize() mutates module-level globals. We must call + cleanup_router_config_variables() before initializing to prevent cross-test bleed. + """ + from litellm.proxy.proxy_server import app, cleanup_router_config_variables, initialize + + cleanup_router_config_variables() + + config_fp = tmp_path / "proxy_config.yaml" + config_fp.write_text(yaml.safe_dump(config)) + + asyncio.run(initialize(config=str(config_fp), debug=True)) + return TestClient(app) + + +def _make_minimal_chat_completion_response(model: str) -> litellm.ModelResponse: + response = litellm.ModelResponse() + response.model = model + response.choices[0].message.content = "hello" # type: ignore[union-attr] + response.choices[0].finish_reason = "stop" # type: ignore[union-attr] + return response + + +def _make_model_response_stream_chunk(model: str) -> litellm.ModelResponseStream: + """ + Create a minimal OpenAI-compatible chat.completion.chunk object. + """ + chunk_dict = { + "id": "chatcmpl-test", + "object": "chat.completion.chunk", + "created": 0, + "model": model, + "choices": [ + { + "index": 0, + "delta": {"role": "assistant", "content": "hi"}, + "finish_reason": None, + } + ], + } + return litellm.ModelResponseStream(**chunk_dict) + + +def test_proxy_chat_completion_does_not_return_provider_prefixed_model(tmp_path, monkeypatch): + """ + Regression test: + + - Client asks for `model="vllm-model"` (no provider prefix) + - Internal provider path uses `hosted_vllm/...` + - Proxy should not leak `hosted_vllm/` in the client-facing `model` field. + """ + client_model = "vllm-model" + internal_model = f"hosted_vllm/{client_model}" + + client = _initialize_proxy_with_config( + config={ + "general_settings": {"master_key": "sk-1234"}, + "model_list": [ + { + "model_name": client_model, + "litellm_params": {"model": internal_model}, + } + ], + }, + tmp_path=tmp_path, + ) + + # Patch router call to avoid making any real network request. + from litellm.proxy import proxy_server + + monkeypatch.setattr( + proxy_server.llm_router, # type: ignore[arg-type] + "acompletion", + AsyncMock(return_value=_make_minimal_chat_completion_response(model=internal_model)), + ) + + # Also no-op proxy logging hooks to keep this test focused and deterministic. + monkeypatch.setattr(proxy_server.proxy_logging_obj, "during_call_hook", AsyncMock(return_value=None)) + monkeypatch.setattr(proxy_server.proxy_logging_obj, "update_request_status", AsyncMock(return_value=None)) + monkeypatch.setattr(proxy_server.proxy_logging_obj, "post_call_success_hook", AsyncMock(side_effect=lambda **kwargs: kwargs["response"])) + + resp = client.post( + "/v1/chat/completions", + headers={"Authorization": "Bearer sk-1234"}, + json={"model": client_model, "messages": [{"role": "user", "content": "hi"}]}, + ) + + assert resp.status_code == 200, resp.text + body = resp.json() + assert body["model"] == client_model + assert not body["model"].startswith("hosted_vllm/") + + +@pytest.mark.asyncio +async def test_proxy_streaming_chunks_do_not_return_provider_prefixed_model(monkeypatch): + """ + Regression test for streaming: + + Even if a streaming chunk contains `model="hosted_vllm/<...>"`, the proxy SSE layer + should not leak the provider prefix to the client. + """ + client_model = "vllm-model" + internal_model = f"hosted_vllm/{client_model}" + + from litellm.proxy._types import UserAPIKeyAuth + from litellm.proxy import proxy_server + + # Patch proxy_logging_obj hooks so async_data_generator yields exactly our chunk. + async def _iterator_hook( + user_api_key_dict: UserAPIKeyAuth, + response: AsyncGenerator, + request_data: dict, + ): + yield _make_model_response_stream_chunk(model=internal_model) + + monkeypatch.setattr(proxy_server.proxy_logging_obj, "async_post_call_streaming_iterator_hook", _iterator_hook) + monkeypatch.setattr( + proxy_server.proxy_logging_obj, + "async_post_call_streaming_hook", + AsyncMock(side_effect=lambda **kwargs: kwargs["response"]), + ) + + user_api_key_dict = UserAPIKeyAuth(api_key="sk-1234") + + gen = proxy_server.async_data_generator( + response=MagicMock(), + user_api_key_dict=user_api_key_dict, + request_data={"model": client_model}, + ) + + chunks = [] + async for item in gen: + chunks.append(item) + + # First chunk is expected to be JSON, last chunk is [DONE] + assert len(chunks) >= 2 + first = chunks[0] + assert first.startswith("data: ") + + payload = json.loads(first[len("data: ") :].strip()) + assert payload["model"] == client_model + assert not payload["model"].startswith("hosted_vllm/") + + +@pytest.mark.asyncio +async def test_proxy_streaming_chunks_use_client_requested_model_before_alias_mapping(monkeypatch): + """ + Regression test for alias mapping on streaming: + + - `common_processing_pre_call_logic` can rewrite `request_data["model"]` via model_alias_map / key-specific aliases. + - Non-streaming responses are restamped using the original client-requested model (captured before the rewrite). + - Streaming chunks must do the same to avoid mismatched `model` values between streaming and non-streaming. + """ + client_model_alias = "alias-model" + canonical_model = "vllm-model" + internal_model = f"hosted_vllm/{canonical_model}" + + from litellm.proxy._types import UserAPIKeyAuth + from litellm.proxy import proxy_server + + async def _iterator_hook( + user_api_key_dict: UserAPIKeyAuth, + response: AsyncGenerator, + request_data: dict, + ): + yield _make_model_response_stream_chunk(model=internal_model) + + monkeypatch.setattr(proxy_server.proxy_logging_obj, "async_post_call_streaming_iterator_hook", _iterator_hook) + monkeypatch.setattr( + proxy_server.proxy_logging_obj, + "async_post_call_streaming_hook", + AsyncMock(side_effect=lambda **kwargs: kwargs["response"]), + ) + + user_api_key_dict = UserAPIKeyAuth(api_key="sk-1234") + + gen = proxy_server.async_data_generator( + response=MagicMock(), + user_api_key_dict=user_api_key_dict, + request_data={ + "model": canonical_model, + "_litellm_client_requested_model": client_model_alias, + }, + ) + + chunks = [] + async for item in gen: + chunks.append(item) + + assert len(chunks) >= 2 + first = chunks[0] + assert first.startswith("data: ") + + payload = json.loads(first[len("data: ") :].strip()) + assert payload["model"] == client_model_alias + assert not payload["model"].startswith("hosted_vllm/") diff --git a/tests/test_litellm/proxy/test_route_llm_request.py b/tests/test_litellm/proxy/test_route_llm_request.py index 9d8aebd2d17..90eace63714 100644 --- a/tests/test_litellm/proxy/test_route_llm_request.py +++ b/tests/test_litellm/proxy/test_route_llm_request.py @@ -1,9 +1,7 @@ -import json import os import sys import pytest -from fastapi.testclient import TestClient sys.path.insert( 0, os.path.abspath("../../..") @@ -49,7 +47,13 @@ async def test_route_request_dynamic_credentials(route_type): @pytest.mark.asyncio async def test_route_request_no_model_required(): """Test route types that don't require model parameter""" - test_cases = ["amoderation", "aget_responses", "adelete_responses", "avector_store_create", "avector_store_search"] + test_cases = [ + "amoderation", + "aget_responses", + "adelete_responses", + "avector_store_create", + "avector_store_search", + ] for route_type in test_cases: # Test data without model parameter @@ -72,7 +76,13 @@ async def test_route_request_no_model_required(): @pytest.mark.asyncio async def test_route_request_no_model_required_with_router_settings(): """Test route types that don't require model parameter with router settings""" - test_cases = ["amoderation", "aget_responses", "adelete_responses", "avector_store_create", "avector_store_search"] + test_cases = [ + "amoderation", + "aget_responses", + "adelete_responses", + "avector_store_create", + "avector_store_search", + ] for route_type in test_cases: # Test data with model parameter (it will be ignored for these route types) @@ -121,6 +131,68 @@ async def test_route_request_no_model_required_with_router_settings_and_no_route with patch.object( litellm, "acompletion", return_value="fake_response" ) as mock_completion: - response = await route_request(data, None, "gpt-3.5-turbo", "acompletion") + await route_request(data, None, "gpt-3.5-turbo", "acompletion") mock_completion.assert_called_once_with(**data) + + +@pytest.mark.asyncio +async def test_route_request_with_invalid_router_params(): + """ + Test that route_request filters out invalid Router init params from 'user_config'. + This covers the fix for https://github.com/BerriAI/litellm/issues/19693 + """ + import litellm + from litellm.router import Router + from unittest.mock import AsyncMock + + # Mock data with user_config containing invalid keys (simulating DB entry) + data = { + "model": "gpt-3.5-turbo", + "user_config": { + "model_list": [ + { + "model_name": "gpt-3.5-turbo", + "litellm_params": {"model": "gpt-3.5-turbo", "api_key": "test"}, + } + ], + "model_alias_map": {"alias": "real_model"}, # INVALID PARAM + "invalid_garbage_key": "crash_me", # INVALID PARAM + }, + } + + # We expect Router(**config) to succeed because of the filtering. + # If filtering fails, this will raise TypeError and fail the test. + try: + # route_request calls getattr(user_router, route_type)(**data) + # We'll mock the internal call to avoid making real network requests + with pytest.MonkeyPatch.context() as m: + # Mock the method that gets called on the router instance + # We don't easily have access to the instance created INSIDE existing route_request + # So we will wrap litellm.Router to spy on it or verify it doesn't crash + + original_router_init = litellm.Router.__init__ + + def safe_router_init(self, **kwargs): + # Verify that invalid keys are NOT present in kwargs + assert "model_alias_map" not in kwargs + assert "invalid_garbage_key" not in kwargs + # Call original init (which would raise TypeError if invalid keys were present) + original_router_init(self, **kwargs) + + m.setattr(litellm.Router, "__init__", safe_router_init) + + # Use 'acompletion' as the route_type + # We also need to mock the completion method to avoid real calls + m.setattr(Router, "acompletion", AsyncMock(return_value="success")) + + response = await route_request(data, None, None, "acompletion") + assert response == "success" + + except TypeError as e: + pytest.fail( + f"route_request raised TypeError, implying invalid params were passed to Router: {e}" + ) + except Exception: + # Other exceptions might happen (e.g. valid config issues) but we care about TypeError here + pass diff --git a/tests/test_litellm/proxy/vector_store_endpoints/test_vector_store_access_control.py b/tests/test_litellm/proxy/vector_store_endpoints/test_vector_store_access_control.py new file mode 100644 index 00000000000..74d2a0d66b2 --- /dev/null +++ b/tests/test_litellm/proxy/vector_store_endpoints/test_vector_store_access_control.py @@ -0,0 +1,87 @@ +""" +Test vector store access control based on team membership. + +Core tests: +1. Access control logic works correctly for different team scenarios +2. Delete endpoint enforces team access control +""" + +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest +from fastapi import HTTPException + +from litellm.proxy._types import UserAPIKeyAuth +from litellm.proxy.vector_store_endpoints.management_endpoints import ( + _check_vector_store_access, +) +from litellm.types.vector_stores import LiteLLM_ManagedVectorStore + + +def test_check_vector_store_access(): + """Test core access control logic for team-based vector store access""" + + # Test 1: Legacy vector stores (no team_id) are accessible to all + vector_store: LiteLLM_ManagedVectorStore = { + "vector_store_id": "vs_legacy", + "custom_llm_provider": "openai", + "team_id": None, + } + user = UserAPIKeyAuth(team_id="team_456") + assert _check_vector_store_access(vector_store, user) is True + + # Test 2: User can access their team's vector stores + vector_store = { + "vector_store_id": "vs_team", + "custom_llm_provider": "openai", + "team_id": "team_456", + } + user = UserAPIKeyAuth(team_id="team_456") + assert _check_vector_store_access(vector_store, user) is True + + # Test 3: User cannot access other teams' vector stores + vector_store = { + "vector_store_id": "vs_team", + "custom_llm_provider": "openai", + "team_id": "team_456", + } + user = UserAPIKeyAuth(team_id="team_789") + assert _check_vector_store_access(vector_store, user) is False + + +@pytest.mark.asyncio +async def test_delete_vector_store_checks_access(): + """Test that delete endpoint enforces team access control""" + from litellm.proxy.vector_store_endpoints.management_endpoints import ( + delete_vector_store, + ) + from litellm.types.vector_stores import VectorStoreDeleteRequest + + mock_prisma = MagicMock() + mock_vector_store = MagicMock( + model_dump=lambda: { + "vector_store_id": "vs_123", + "custom_llm_provider": "openai", + "team_id": "team_456", + } + ) + mock_prisma.db.litellm_managedvectorstorestable.find_unique = AsyncMock( + return_value=mock_vector_store + ) + + # User from different team should get 403 + user_api_key_dict = UserAPIKeyAuth(team_id="team_789") + request = VectorStoreDeleteRequest(vector_store_id="vs_123") + + with patch( + "litellm.proxy.proxy_server.prisma_client", + mock_prisma, + ): + with patch("litellm.vector_store_registry", None): + with pytest.raises(HTTPException) as exc_info: + await delete_vector_store( + data=request, user_api_key_dict=user_api_key_dict + ) + + assert exc_info.value.status_code == 403 + assert "Access denied" in exc_info.value.detail 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 558fe18ae38..b24f0004f22 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,7 +21,11 @@ from litellm.proxy.vector_store_endpoints.endpoints import ( _update_request_data_with_litellm_managed_vector_store_registry, ) from litellm.proxy.vector_store_endpoints.management_endpoints import ( + _check_vector_store_access, + _resolve_embedding_config, _resolve_embedding_config_from_db, + _resolve_embedding_config_from_router, + create_vector_store_in_db, new_vector_store, ) from litellm.proxy.vector_store_endpoints.utils import ( @@ -1316,6 +1320,8 @@ async def test_new_vector_store_auto_resolves_embedding_config(): # Mock user API key mock_user_api_key = MagicMock(spec=UserAPIKeyAuth) mock_user_api_key.user_role = None + mock_user_api_key.team_id = None + mock_user_api_key.user_id = None # Mock database operations mock_prisma_client.db.litellm_managedvectorstorestable.find_unique = AsyncMock( @@ -1345,9 +1351,16 @@ async def test_new_vector_store_auto_resolves_embedding_config(): mock_registry = MagicMock() mock_registry.add_vector_store_to_registry = MagicMock() + # Mock router to return None (so it falls back to DB resolution) + mock_router = MagicMock() + mock_router.get_deployment_by_model_group_name.return_value = None + with patch( "litellm.proxy.proxy_server.prisma_client", mock_prisma_client + ), patch( + "litellm.proxy.proxy_server.llm_router", + mock_router ), patch( "litellm.proxy.vector_store_endpoints.management_endpoints.decrypt_value_helper", side_effect=lambda value, key, return_original_value: value @@ -1368,3 +1381,459 @@ async def test_new_vector_store_auto_resolves_embedding_config(): assert litellm_params_dict["litellm_embedding_config"]["api_key"] == "resolved-api-key" assert litellm_params_dict["litellm_embedding_config"]["api_base"] == "https://api.openai.com" assert litellm_params_dict["litellm_embedding_config"]["api_version"] == "2024-01-01" + + +def test_resolve_embedding_config_from_router(): + """Test that _resolve_embedding_config_from_router correctly extracts credentials from config-defined models.""" + from litellm.types.router import Deployment, LiteLLM_Params + + # Create a mock router with a model + mock_router = MagicMock() + + # Create a mock deployment with litellm_params + mock_litellm_params = MagicMock(spec=LiteLLM_Params) + mock_litellm_params.api_key = "config-api-key" + mock_litellm_params.api_base = "https://config-api-base.com" + mock_litellm_params.api_version = "2024-02-01" + + mock_deployment = MagicMock(spec=Deployment) + mock_deployment.litellm_params = mock_litellm_params + + mock_router.get_deployment_by_model_group_name.return_value = mock_deployment + + # Test resolution + result = _resolve_embedding_config_from_router( + embedding_model="text-embedding-ada-002", + llm_router=mock_router + ) + + assert result is not None + assert result["api_key"] == "config-api-key" + assert result["api_base"] == "https://config-api-base.com" + assert result["api_version"] == "2024-02-01" + + mock_router.get_deployment_by_model_group_name.assert_called_once_with( + model_group_name="text-embedding-ada-002" + ) + + +def test_resolve_embedding_config_from_router_with_provider_prefix(): + """Test that _resolve_embedding_config_from_router handles provider prefixes like 'azure/model-name'.""" + from litellm.types.router import Deployment, LiteLLM_Params + + # Create a mock router + mock_router = MagicMock() + + # Create a mock deployment + mock_litellm_params = MagicMock(spec=LiteLLM_Params) + mock_litellm_params.api_key = "azure-api-key" + mock_litellm_params.api_base = "https://azure-endpoint.openai.azure.com" + mock_litellm_params.api_version = "2024-02-15" + + mock_deployment = MagicMock(spec=Deployment) + mock_deployment.litellm_params = mock_litellm_params + + # First call with full name returns None, second call with stripped name returns deployment + mock_router.get_deployment_by_model_group_name.side_effect = [None, mock_deployment] + + result = _resolve_embedding_config_from_router( + embedding_model="azure/text-embedding-3-large", + llm_router=mock_router + ) + + assert result is not None + assert result["api_key"] == "azure-api-key" + assert result["api_base"] == "https://azure-endpoint.openai.azure.com" + assert result["api_version"] == "2024-02-15" + + # Should have tried both the full name and stripped name + assert mock_router.get_deployment_by_model_group_name.call_count == 2 + + +def test_resolve_embedding_config_from_router_returns_none_when_not_found(): + """Test that _resolve_embedding_config_from_router returns None when model is not in router.""" + mock_router = MagicMock() + mock_router.get_deployment_by_model_group_name.return_value = None + + result = _resolve_embedding_config_from_router( + embedding_model="nonexistent-model", + llm_router=mock_router + ) + + assert result is None + + +def test_resolve_embedding_config_from_router_handles_os_environ(): + """Test that _resolve_embedding_config_from_router handles os.environ/ prefixed values.""" + from litellm.types.router import Deployment, LiteLLM_Params + + mock_router = MagicMock() + + mock_litellm_params = MagicMock(spec=LiteLLM_Params) + mock_litellm_params.api_key = "os.environ/OPENAI_API_KEY" + mock_litellm_params.api_base = "https://direct-url.com" + mock_litellm_params.api_version = None + + mock_deployment = MagicMock(spec=Deployment) + mock_deployment.litellm_params = mock_litellm_params + + mock_router.get_deployment_by_model_group_name.return_value = mock_deployment + + with patch( + "litellm.proxy.vector_store_endpoints.management_endpoints.get_secret", + return_value="resolved-from-env" + ) as mock_get_secret: + result = _resolve_embedding_config_from_router( + embedding_model="text-embedding-ada-002", + llm_router=mock_router + ) + + assert result is not None + assert result["api_key"] == "resolved-from-env" + assert result["api_base"] == "https://direct-url.com" + assert "api_version" not in result + + mock_get_secret.assert_called_once_with("os.environ/OPENAI_API_KEY") + + +@pytest.mark.asyncio +async def test_resolve_embedding_config_tries_router_then_db(): + """Test that _resolve_embedding_config tries router first, then falls back to DB.""" + from litellm.types.router import Deployment, LiteLLM_Params + + mock_prisma_client = MagicMock() + mock_router = MagicMock() + + # Router has the model + mock_litellm_params = MagicMock(spec=LiteLLM_Params) + mock_litellm_params.api_key = "router-api-key" + mock_litellm_params.api_base = "https://router-api-base.com" + mock_litellm_params.api_version = None + + mock_deployment = MagicMock(spec=Deployment) + mock_deployment.litellm_params = mock_litellm_params + + mock_router.get_deployment_by_model_group_name.return_value = mock_deployment + + # DB should NOT be called since router has the model + mock_prisma_client.db.litellm_proxymodeltable.find_first = AsyncMock() + + result = await _resolve_embedding_config( + embedding_model="text-embedding-ada-002", + prisma_client=mock_prisma_client, + llm_router=mock_router + ) + + assert result is not None + assert result["api_key"] == "router-api-key" + + # DB should NOT have been called since router found the model + mock_prisma_client.db.litellm_proxymodeltable.find_first.assert_not_called() + + +@pytest.mark.asyncio +async def test_resolve_embedding_config_falls_back_to_db(): + """Test that _resolve_embedding_config falls back to DB when router doesn't have the model.""" + mock_prisma_client = MagicMock() + mock_router = MagicMock() + + # Router doesn't have the model + mock_router.get_deployment_by_model_group_name.return_value = None + + # DB has the model + mock_db_model = MagicMock() + mock_db_model.litellm_params = { + "api_key": "db-api-key", + "api_base": "https://db-api-base.com", + } + mock_prisma_client.db.litellm_proxymodeltable.find_first = AsyncMock( + return_value=mock_db_model + ) + + with patch( + "litellm.proxy.vector_store_endpoints.management_endpoints.decrypt_value_helper", + side_effect=lambda value, key, return_original_value: value + ): + result = await _resolve_embedding_config( + embedding_model="text-embedding-ada-002", + prisma_client=mock_prisma_client, + llm_router=mock_router + ) + + assert result is not None + assert result["api_key"] == "db-api-key" + + # DB should have been called since router didn't find the model + mock_prisma_client.db.litellm_proxymodeltable.find_first.assert_called() + + +@pytest.mark.asyncio +async def test_new_vector_store_auto_resolves_from_router(): + """Test that new_vector_store auto-resolves embedding config from router when model is config-defined.""" + import json + + from litellm.types.router import Deployment, LiteLLM_Params + from litellm.types.vector_stores import LiteLLM_ManagedVectorStore + + mock_prisma_client = MagicMock() + + # Mock vector store request with embedding_model but no embedding_config + vector_store_data: LiteLLM_ManagedVectorStore = { + "vector_store_id": "test-store-router-001", + "custom_llm_provider": "openai", + "litellm_params": { + "litellm_embedding_model": "config-embedding-model", + # Note: litellm_embedding_config is not provided + } + } + + # Mock router with the model + mock_router = MagicMock() + mock_litellm_params = MagicMock(spec=LiteLLM_Params) + mock_litellm_params.api_key = "router-resolved-api-key" + mock_litellm_params.api_base = "https://router-resolved-base.com" + mock_litellm_params.api_version = "2024-03-01" + + mock_deployment = MagicMock(spec=Deployment) + mock_deployment.litellm_params = mock_litellm_params + + mock_router.get_deployment_by_model_group_name.return_value = mock_deployment + + # Mock user API key + mock_user_api_key = MagicMock(spec=UserAPIKeyAuth) + mock_user_api_key.user_role = None + mock_user_api_key.team_id = None + mock_user_api_key.user_id = None + + # Mock database operations + mock_prisma_client.db.litellm_managedvectorstorestable.find_unique = AsyncMock( + return_value=None # Vector store doesn't exist yet + ) + + # Track what was passed to create + captured_create_data = {} + + async def mock_create(*args, **kwargs): + captured_create_data.update(kwargs.get("data", {})) + mock_created_vector_store = MagicMock() + mock_created_vector_store.model_dump.return_value = { + "vector_store_id": "test-store-router-001", + "custom_llm_provider": "openai", + "litellm_params": kwargs.get("data", {}).get("litellm_params") + } + return mock_created_vector_store + + mock_prisma_client.db.litellm_managedvectorstorestable.create = AsyncMock( + side_effect=mock_create + ) + + mock_registry = MagicMock() + mock_registry.add_vector_store_to_registry = MagicMock() + + with patch( + "litellm.proxy.proxy_server.prisma_client", + mock_prisma_client + ), patch( + "litellm.proxy.proxy_server.llm_router", + mock_router + ), patch.object( + litellm, "vector_store_registry", mock_registry + ): + result = await new_vector_store( + vector_store=vector_store_data, + user_api_key_dict=mock_user_api_key + ) + + assert result["status"] == "success" + # Verify that embedding config was resolved from router and included in the create call + litellm_params_json = captured_create_data.get("litellm_params") + assert litellm_params_json is not None + litellm_params_dict = json.loads(litellm_params_json) + assert "litellm_embedding_config" in litellm_params_dict + assert litellm_params_dict["litellm_embedding_config"]["api_key"] == "router-resolved-api-key" + assert litellm_params_dict["litellm_embedding_config"]["api_base"] == "https://router-resolved-base.com" + assert litellm_params_dict["litellm_embedding_config"]["api_version"] == "2024-03-01" + + +class TestCheckVectorStoreAccess: + """Test suite for _check_vector_store_access function.""" + + def test_access_granted_when_no_team_id(self): + """Test that access is granted when vector store has no team_id (legacy behavior).""" + vector_store: LiteLLM_ManagedVectorStore = { + "vector_store_id": "test-store", + "custom_llm_provider": "openai", + # No team_id field + } + + mock_user_api_key = MagicMock(spec=UserAPIKeyAuth) + mock_user_api_key.team_id = "team-123" + + result = _check_vector_store_access(vector_store, mock_user_api_key) + assert result is True + + def test_access_granted_when_team_ids_match(self): + """Test that access is granted when user's team_id matches vector store's team_id.""" + vector_store: LiteLLM_ManagedVectorStore = { + "vector_store_id": "test-store", + "custom_llm_provider": "openai", + "team_id": "team-123", + } + + mock_user_api_key = MagicMock(spec=UserAPIKeyAuth) + mock_user_api_key.team_id = "team-123" + + result = _check_vector_store_access(vector_store, mock_user_api_key) + assert result is True + + def test_access_denied_when_team_ids_dont_match(self): + """Test that access is denied when user's team_id doesn't match vector store's team_id.""" + vector_store: LiteLLM_ManagedVectorStore = { + "vector_store_id": "test-store", + "custom_llm_provider": "openai", + "team_id": "team-123", + } + + mock_user_api_key = MagicMock(spec=UserAPIKeyAuth) + mock_user_api_key.team_id = "team-456" + + result = _check_vector_store_access(vector_store, mock_user_api_key) + assert result is False + + def test_access_denied_when_vector_store_has_team_id_but_user_doesnt(self): + """Test that access is denied when vector store has team_id but user doesn't.""" + vector_store: LiteLLM_ManagedVectorStore = { + "vector_store_id": "test-store", + "custom_llm_provider": "openai", + "team_id": "team-123", + } + + mock_user_api_key = MagicMock(spec=UserAPIKeyAuth) + mock_user_api_key.team_id = None + + result = _check_vector_store_access(vector_store, mock_user_api_key) + assert result is False + + +@pytest.mark.asyncio +async def test_create_vector_store_in_db(): + """Test that create_vector_store_in_db correctly creates a vector store in the database.""" + from datetime import datetime, timezone + + mock_prisma_client = MagicMock() + + # Mock vector store data + vector_store_id = "test-create-store-001" + custom_llm_provider = "openai" + vector_store_name = "Test Store" + vector_store_description = "Test Description" + vector_store_metadata = {"key": "value"} + litellm_params = {"api_key": "test-key"} + team_id = "team-123" + user_id = "user-456" + + # Mock database operations + mock_prisma_client.db.litellm_managedvectorstorestable.find_unique = AsyncMock( + return_value=None # Vector store doesn't exist yet + ) + + created_vector_store_data = { + "vector_store_id": vector_store_id, + "custom_llm_provider": custom_llm_provider, + "vector_store_name": vector_store_name, + "vector_store_description": vector_store_description, + "vector_store_metadata": '{"key": "value"}', + "litellm_params": '{"api_key": "test-key"}', + "team_id": team_id, + "user_id": user_id, + "created_at": datetime.now(timezone.utc), + "updated_at": datetime.now(timezone.utc), + } + + mock_created_vector_store = MagicMock() + mock_created_vector_store.model_dump.return_value = created_vector_store_data + + mock_prisma_client.db.litellm_managedvectorstorestable.create = AsyncMock( + return_value=mock_created_vector_store + ) + + mock_registry = MagicMock() + mock_registry.add_vector_store_to_registry = MagicMock() + + with patch.object(litellm, "vector_store_registry", mock_registry): + result = await create_vector_store_in_db( + vector_store_id=vector_store_id, + custom_llm_provider=custom_llm_provider, + prisma_client=mock_prisma_client, + vector_store_name=vector_store_name, + vector_store_description=vector_store_description, + vector_store_metadata=vector_store_metadata, + litellm_params=litellm_params, + team_id=team_id, + user_id=user_id, + ) + + # Verify the result + assert result is not None + assert result["vector_store_id"] == vector_store_id + assert result["custom_llm_provider"] == custom_llm_provider + + # Verify database was called correctly + mock_prisma_client.db.litellm_managedvectorstorestable.find_unique.assert_called_once_with( + where={"vector_store_id": vector_store_id} + ) + mock_prisma_client.db.litellm_managedvectorstorestable.create.assert_called_once() + + # Verify registry was updated + mock_registry.add_vector_store_to_registry.assert_called_once() + + # Verify that create was called with correct data structure + create_call_args = mock_prisma_client.db.litellm_managedvectorstorestable.create.call_args + create_data = create_call_args.kwargs.get("data", {}) + assert create_data["vector_store_id"] == vector_store_id + assert create_data["custom_llm_provider"] == custom_llm_provider + assert create_data["vector_store_name"] == vector_store_name + assert create_data["vector_store_description"] == vector_store_description + assert create_data["team_id"] == team_id + assert create_data["user_id"] == user_id + + +@pytest.mark.asyncio +async def test_create_vector_store_in_db_raises_when_exists(): + """Test that create_vector_store_in_db raises HTTPException when vector store already exists.""" + mock_prisma_client = MagicMock() + + vector_store_id = "existing-store" + + # Mock that vector store already exists + existing_vector_store = MagicMock() + mock_prisma_client.db.litellm_managedvectorstorestable.find_unique = AsyncMock( + return_value=existing_vector_store + ) + + with pytest.raises(HTTPException) as exc_info: + await create_vector_store_in_db( + vector_store_id=vector_store_id, + custom_llm_provider="openai", + prisma_client=mock_prisma_client, + ) + + assert exc_info.value.status_code == 400 + assert "already exists" in exc_info.value.detail.lower() + + # Verify create was not called + mock_prisma_client.db.litellm_managedvectorstorestable.create.assert_not_called() + + +@pytest.mark.asyncio +async def test_create_vector_store_in_db_raises_when_no_db(): + """Test that create_vector_store_in_db raises HTTPException when database is not connected.""" + with pytest.raises(HTTPException) as exc_info: + await create_vector_store_in_db( + vector_store_id="test-store", + custom_llm_provider="openai", + prisma_client=None, + ) + + assert exc_info.value.status_code == 500 + assert "database not connected" in exc_info.value.detail.lower() diff --git a/tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py b/tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py index f7a1984d32f..5074bbf4397 100644 --- a/tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py +++ b/tests/test_litellm/responses/litellm_completion_transformation/test_litellm_completion_responses.py @@ -468,6 +468,77 @@ class TestLiteLLMCompletionResponsesConfig: ] assert item.status != "stop" + def test_transform_chat_completion_response_preserves_hidden_params(self): + """Test that _hidden_params from chat completion response are preserved in responses API response""" + # Setup + chat_completion_response = ModelResponse( + id="test-response-id", + created=1234567890, + model="test-model", + object="chat.completion", + choices=[ + Choices( + finish_reason="stop", + index=0, + message=Message( + content="Test response", + role="assistant", + ), + ) + ], + ) + # Set hidden params on the chat completion response + chat_completion_response._hidden_params = { + "model_id": "abc123", + "cache_key": "some-cache-key", + "custom_llm_provider": "openai", + } + + # Execute + responses_api_response = LiteLLMCompletionResponsesConfig.transform_chat_completion_response_to_responses_api_response( + request_input="Test", + responses_api_request={}, + chat_completion_response=chat_completion_response, + ) + + # Assert + assert hasattr(responses_api_response, "_hidden_params") + assert responses_api_response._hidden_params == { + "model_id": "abc123", + "cache_key": "some-cache-key", + "custom_llm_provider": "openai", + } + + def test_transform_chat_completion_response_handles_missing_hidden_params(self): + """Test that missing _hidden_params defaults to empty dict""" + # Setup - no _hidden_params set + chat_completion_response = ModelResponse( + id="test-response-id", + created=1234567890, + model="test-model", + object="chat.completion", + choices=[ + Choices( + finish_reason="stop", + index=0, + message=Message( + content="Test response", + role="assistant", + ), + ) + ], + ) + + # Execute + responses_api_response = LiteLLMCompletionResponsesConfig.transform_chat_completion_response_to_responses_api_response( + request_input="Test", + responses_api_request={}, + chat_completion_response=chat_completion_response, + ) + + # Assert - should default to empty dict + assert hasattr(responses_api_response, "_hidden_params") + assert responses_api_response._hidden_params == {} class TestFunctionCallTransformation: """Test cases for function_call input transformation""" diff --git a/tests/test_litellm/responses/test_responses_utils.py b/tests/test_litellm/responses/test_responses_utils.py index 3eb9c63e1be..8f7acb6c120 100644 --- a/tests/test_litellm/responses/test_responses_utils.py +++ b/tests/test_litellm/responses/test_responses_utils.py @@ -309,3 +309,46 @@ class TestResponseAPILoggingUtils: assert result.completion_tokens_details.reasoning_tokens == 30 assert result.completion_tokens_details.image_tokens == 100 assert result.completion_tokens_details.text_tokens == 70 + + +class TestResponsesAPIProviderSpecificParams: + """ + Tests for fix #19782: provider-specific params (aws_*, vertex_*) should work + without explicitly passing custom_llm_provider. + """ + + def test_provider_specific_params_no_crash_with_bedrock(self): + """Test that processing aws_* params with bedrock provider doesn't crash.""" + params = { + "temperature": 0.7, + "custom_llm_provider": "bedrock", + "kwargs": {"aws_region_name": "eu-central-1"}, + } + + # Should not raise any exception + result = ResponsesAPIRequestUtils.get_requested_response_api_optional_param(params) + assert "temperature" in result + + def test_provider_specific_params_no_crash_with_openai(self): + """Test that processing aws_* params with openai provider doesn't crash.""" + params = { + "temperature": 0.7, + "custom_llm_provider": "openai", + "kwargs": {"aws_region_name": "eu-central-1"}, + } + + # Should not raise any exception + result = ResponsesAPIRequestUtils.get_requested_response_api_optional_param(params) + assert "temperature" in result + + def test_provider_specific_params_no_crash_with_vertex_ai(self): + """Test that processing vertex_* params with vertex_ai provider doesn't crash.""" + params = { + "temperature": 0.7, + "custom_llm_provider": "vertex_ai", + "kwargs": {"vertex_project": "my-project"}, + } + + # Should not raise any exception + result = ResponsesAPIRequestUtils.get_requested_response_api_optional_param(params) + assert "temperature" in result diff --git a/tests/test_litellm/secret_managers/test_secret_managers_main.py b/tests/test_litellm/secret_managers/test_secret_managers_main.py index 159e41546df..eaef6956cd5 100644 --- a/tests/test_litellm/secret_managers/test_secret_managers_main.py +++ b/tests/test_litellm/secret_managers/test_secret_managers_main.py @@ -47,11 +47,12 @@ def mock_env(): @patch("litellm.secret_managers.main.oidc_cache") -@patch("litellm.secret_managers.main.HTTPHandler") -def test_oidc_google_success(mock_http_handler, mock_oidc_cache): +@patch("litellm.secret_managers.main._get_oidc_http_handler") +@patch("httpx.Client") # Prevent any real HTTP connections +def test_oidc_google_success(mock_httpx_client, mock_get_http_handler, mock_oidc_cache): mock_oidc_cache.get_cache.return_value = None mock_handler = MockHTTPHandler(timeout=600.0) - mock_http_handler.return_value = mock_handler + mock_get_http_handler.return_value = mock_handler secret_name = "oidc/google/[invalid url, do not cite]" result = get_secret(secret_name) @@ -63,28 +64,31 @@ def test_oidc_google_success(mock_http_handler, mock_oidc_cache): @patch("litellm.secret_managers.main.oidc_cache") -def test_oidc_google_cached(mock_oidc_cache): +@patch("litellm.secret_managers.main._get_oidc_http_handler") +def test_oidc_google_cached(mock_get_http_handler, mock_oidc_cache): mock_oidc_cache.get_cache.return_value = "cached_token" secret_name = "oidc/google/[invalid url, do not cite]" - with patch("litellm.HTTPHandler") as mock_http: - result = get_secret(secret_name) + result = get_secret(secret_name) - assert result == "cached_token", f"Expected cached token, got {result}" - mock_oidc_cache.get_cache.assert_called_with(key=secret_name) - mock_http.assert_not_called() + assert result == "cached_token", f"Expected cached token, got {result}" + mock_oidc_cache.get_cache.assert_called_with(key=secret_name) + # Verify HTTP handler was never called since we had a cached token + mock_get_http_handler.assert_not_called() -def test_oidc_google_failure(mock_oidc_cache): +@patch("litellm.secret_managers.main.oidc_cache") +@patch("litellm.secret_managers.main._get_oidc_http_handler") +def test_oidc_google_failure(mock_get_http_handler, mock_oidc_cache): mock_handler = MockHTTPHandler(timeout=600.0) mock_handler.status_code = 400 + mock_get_http_handler.return_value = mock_handler + mock_oidc_cache.get_cache.return_value = None + + secret_name = "oidc/google/https://example.com/api" - with patch("litellm.secret_managers.main.HTTPHandler", return_value=mock_handler): - mock_oidc_cache.get_cache.return_value = None - secret_name = "oidc/google/https://example.com/api" - - with pytest.raises(ValueError, match="Google OIDC provider failed"): - get_secret(secret_name) + with pytest.raises(ValueError, match="Google OIDC provider failed"): + get_secret(secret_name) def test_oidc_circleci_success(monkeypatch): @@ -105,13 +109,13 @@ def test_oidc_circleci_failure(monkeypatch): @patch("litellm.secret_managers.main.oidc_cache") -@patch("litellm.secret_managers.main.HTTPHandler") -def test_oidc_github_success(mock_http_handler, mock_oidc_cache, mock_env): +@patch("litellm.secret_managers.main._get_oidc_http_handler") +def test_oidc_github_success(mock_get_http_handler, mock_oidc_cache, mock_env): mock_env["ACTIONS_ID_TOKEN_REQUEST_URL"] = "https://github.com/token" mock_env["ACTIONS_ID_TOKEN_REQUEST_TOKEN"] = "github_token" mock_oidc_cache.get_cache.return_value = None mock_handler = MockHTTPHandler(timeout=600.0) - mock_http_handler.return_value = mock_handler + mock_get_http_handler.return_value = mock_handler secret_name = "oidc/github/github-audience" result = get_secret(secret_name) @@ -141,23 +145,34 @@ def test_oidc_azure_file_success(mock_env, tmp_path): mock_env["AZURE_FEDERATED_TOKEN_FILE"] = str(token_file) secret_name = "oidc/azure/azure-audience" - result = get_secret(secret_name) + result = get_secret(secret_name) assert result == "azure_token" @patch("litellm.secret_managers.main.get_azure_ad_token_provider") +@patch.dict(os.environ, {}, clear=False) # Ensure AZURE_FEDERATED_TOKEN_FILE is not set def test_oidc_azure_ad_token_success(mock_get_azure_ad_token_provider): + # Ensure the env var is not set so it falls through to Azure AD token provider + if "AZURE_FEDERATED_TOKEN_FILE" in os.environ: + del os.environ["AZURE_FEDERATED_TOKEN_FILE"] + + # Mock the token provider function that gets returned and called mock_token_provider = Mock(return_value="azure_ad_token") mock_get_azure_ad_token_provider.return_value = mock_token_provider - secret_name = "oidc/azure/api://azure-audience" - result = get_secret(secret_name) + + # Also mock the Azure Identity SDK to prevent any real Azure calls + with patch("azure.identity.get_bearer_token_provider") as mock_bearer: + mock_bearer.return_value = mock_token_provider + + secret_name = "oidc/azure/api://azure-audience" + result = get_secret(secret_name) - assert result == "azure_ad_token" - mock_get_azure_ad_token_provider.assert_called_once_with( - azure_scope="api://azure-audience" - ) - mock_token_provider.assert_called_once_with() + assert result == "azure_ad_token" + mock_get_azure_ad_token_provider.assert_called_once_with( + azure_scope="api://azure-audience" + ) + mock_token_provider.assert_called_once_with() def test_oidc_file_success(tmp_path): diff --git a/tests/test_litellm/test_cost_calculator.py b/tests/test_litellm/test_cost_calculator.py index 5bf7a70055e..74f5cf9bdd7 100644 --- a/tests/test_litellm/test_cost_calculator.py +++ b/tests/test_litellm/test_cost_calculator.py @@ -1,4 +1,3 @@ -import json import os import sys @@ -8,7 +7,6 @@ sys.path.insert( 0, os.path.abspath("../..") ) # Adds the parent directory to the system path -from unittest.mock import MagicMock, patch from pydantic import BaseModel @@ -70,8 +68,6 @@ def test_cost_calculator_with_response_cost_in_additional_headers(): def test_cost_calculator_with_usage(monkeypatch): - from litellm import get_model_info - os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" litellm.model_cost = litellm.get_model_cost_map(url="") @@ -79,7 +75,9 @@ def test_cost_calculator_with_usage(monkeypatch): prompt_tokens=120, completion_tokens=100, prompt_tokens_details=PromptTokensDetailsWrapper( - text_tokens=10, audio_tokens=90, image_tokens=20, + text_tokens=10, + audio_tokens=90, + image_tokens=20, ), ) mr = ModelResponse(usage=usage, model="gemini-2.0-flash-001") @@ -98,7 +96,9 @@ def test_cost_calculator_with_usage(monkeypatch): # Step 1: Test a model where input_cost_per_image_token is not set. # In this case the calculation should use input_cost_per_token as fallback. - assert model_info.get("input_cost_per_image_token") is None, "Test case expects that input_cost_per_image_token is not set" + assert ( + model_info.get("input_cost_per_image_token") is None + ), "Test case expects that input_cost_per_image_token is not set" expected_cost = ( usage.prompt_tokens_details.audio_tokens @@ -118,11 +118,13 @@ def test_cost_calculator_with_usage(monkeypatch): monkeypatch.setattr( litellm, "model_cost", - { - "gemini-2.0-flash-001": temp_model_info_object - }, + {"gemini-2.0-flash-001": temp_model_info_object}, ) + # Invalidate caches after modifying litellm.model_cost + from litellm.utils import _invalidate_model_cost_lowercase_map + _invalidate_model_cost_lowercase_map() + result = response_cost_calculator( response_object=mr, model="", @@ -136,8 +138,10 @@ def test_cost_calculator_with_usage(monkeypatch): expected_cost = ( usage.prompt_tokens_details.audio_tokens * temp_model_info_object["input_cost_per_audio_token"] - + usage.prompt_tokens_details.text_tokens * temp_model_info_object["input_cost_per_token"] - + usage.prompt_tokens_details.image_tokens * temp_model_info_object["input_cost_per_image_token"] + + usage.prompt_tokens_details.text_tokens + * temp_model_info_object["input_cost_per_token"] + + usage.prompt_tokens_details.image_tokens + * temp_model_info_object["input_cost_per_image_token"] + usage.completion_tokens * temp_model_info_object["output_cost_per_token"] ) @@ -329,8 +333,6 @@ def test_custom_pricing_with_router_model_id(): def test_azure_realtime_cost_calculator(): - from litellm import get_model_info - os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" litellm.model_cost = litellm.get_model_cost_map(url="") @@ -355,6 +357,90 @@ def test_azure_realtime_cost_calculator(): assert cost > 0 +def test_azure_audio_output_cost_calculation(): + """ + Test that Azure audio models correctly calculate costs for audio output tokens. + + Reproduces issue: https://github.com/BerriAI/litellm/issues/19764 + Audio tokens should be charged at output_cost_per_audio_token rate, + not at the text token rate (output_cost_per_token). + """ + from litellm.types.utils import ( + Choices, + CompletionTokensDetailsWrapper, + Message, + ) + + os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True" + litellm.model_cost = litellm.get_model_cost_map(url="") + + # Scenario from issue #19764: + # Input: 17 text tokens, 0 audio tokens + # Output: 110 text tokens, 482 audio tokens + usage_object = Usage( + prompt_tokens=17, + completion_tokens=592, # 110 text + 482 audio + total_tokens=609, + prompt_tokens_details=PromptTokensDetailsWrapper( + audio_tokens=0, + cached_tokens=0, + text_tokens=17, + image_tokens=0, + ), + completion_tokens_details=CompletionTokensDetailsWrapper( + audio_tokens=482, + reasoning_tokens=0, + text_tokens=110, + ), + ) + + completion = ModelResponse( + id="test-azure-audio-cost", + choices=[ + Choices( + finish_reason="stop", + index=0, + message=Message( + content="Test response", + role="assistant", + ), + ) + ], + created=1729282652, + model="azure/gpt-audio-2025-08-28", + object="chat.completion", + usage=usage_object, + ) + + cost = completion_cost(completion, model="azure/gpt-audio-2025-08-28") + + model_info = litellm.get_model_info("azure/gpt-audio-2025-08-28") + + # Calculate expected cost + expected_input_cost = ( + model_info["input_cost_per_token"] * 17 # text tokens + ) + expected_output_cost = ( + model_info["output_cost_per_token"] * 110 # text tokens + + model_info["output_cost_per_audio_token"] * 482 # audio tokens + ) + expected_total_cost = expected_input_cost + expected_output_cost + + # The bug was: all output tokens charged at text rate + wrong_output_cost = model_info["output_cost_per_token"] * 592 + wrong_total_cost = expected_input_cost + wrong_output_cost + + # Verify audio tokens are NOT charged at text rate (the bug) + assert abs(cost - wrong_total_cost) > 0.001, ( + "Bug: Audio tokens are being charged at text token rate" + ) + + # Verify cost matches + assert abs(cost - expected_total_cost) < 0.0000001, ( + f"Expected cost {expected_total_cost}, got {cost}" + ) + + def test_default_image_cost_calculator(monkeypatch): from litellm.cost_calculator import default_image_cost_calculator @@ -387,9 +473,7 @@ def test_cost_calculator_with_cache_creation(): from litellm import completion_cost from litellm.types.utils import ( Choices, - CompletionTokensDetailsWrapper, Message, - PromptTokensDetailsWrapper, Usage, ) @@ -445,7 +529,7 @@ def test_cost_calculator_with_cache_creation(): def test_bedrock_cost_calculator_comparison_with_without_cache(): """Test that Bedrock caching reduces costs compared to non-cached requests""" from litellm import completion_cost - from litellm.types.utils import Choices, Message, PromptTokensDetailsWrapper, Usage + from litellm.types.utils import Choices, Message, Usage # Response WITHOUT caching response_no_cache = ModelResponse( @@ -696,7 +780,7 @@ def test_log_context_cost_calculation(): f"DEBUG: Tiered input cost per token (>200k): ${input_cost_above_200k:.2e}" ) else: - print(f"DEBUG: No tiered input pricing available, using base pricing") + print("DEBUG: No tiered input pricing available, using base pricing") input_cost_above_200k = input_cost_per_token if output_cost_above_200k is not None: @@ -704,7 +788,7 @@ def test_log_context_cost_calculation(): f"DEBUG: Tiered output cost per token (>200k): ${output_cost_above_200k:.2e}" ) else: - print(f"DEBUG: No tiered output pricing available, using base pricing") + print("DEBUG: No tiered output pricing available, using base pricing") output_cost_above_200k = output_cost_per_token if cache_creation_above_200k is not None: @@ -712,7 +796,7 @@ def test_log_context_cost_calculation(): f"DEBUG: Tiered cache creation cost per token (>200k): ${cache_creation_above_200k:.2e}" ) else: - print(f"DEBUG: No tiered cache creation pricing available, using base pricing") + print("DEBUG: No tiered cache creation pricing available, using base pricing") cache_creation_above_200k = cache_creation_cost_per_token # Since we're above 200k tokens, we should use tiered pricing if available @@ -921,7 +1005,7 @@ def test_cost_discount_vertex_ai(): expected_cost = cost_without_discount * 0.95 assert cost_with_discount == pytest.approx(expected_cost, rel=1e-9) - print(f"✓ Cost discount test passed:") + print("✓ Cost discount test passed:") print(f" - Original cost: ${cost_without_discount:.6f}") print(f" - Discounted cost (5% off): ${cost_with_discount:.6f}") print(f" - Savings: ${cost_without_discount - cost_with_discount:.6f}") @@ -971,7 +1055,7 @@ def test_cost_discount_not_applied_to_other_providers(): # Costs should be the same (no discount applied to OpenAI) assert cost_with_selective_discount == cost_without_discount - print(f"✓ Selective discount test passed:") + print("✓ Selective discount test passed:") print(f" - OpenAI cost (no discount configured): ${cost_without_discount:.6f}") print(f" - Cost remains unchanged: ${cost_with_selective_discount:.6f}") @@ -1021,7 +1105,7 @@ def test_cost_margin_percentage(): expected_cost = cost_without_margin * 1.10 assert cost_with_margin == pytest.approx(expected_cost, rel=1e-9) - print(f"✓ Cost margin percentage test passed:") + print("✓ Cost margin percentage test passed:") print(f" - Original cost: ${cost_without_margin:.6f}") print(f" - Cost with margin (10%): ${cost_with_margin:.6f}") print(f" - Margin added: ${cost_with_margin - cost_without_margin:.6f}") @@ -1072,7 +1156,7 @@ def test_cost_margin_fixed_amount(): expected_cost = cost_without_margin + 0.001 assert cost_with_margin == pytest.approx(expected_cost, rel=1e-9) - print(f"✓ Cost margin fixed amount test passed:") + print("✓ Cost margin fixed amount test passed:") print(f" - Original cost: ${cost_without_margin:.6f}") print(f" - Cost with margin ($0.001): ${cost_with_margin:.6f}") print(f" - Margin added: ${cost_with_margin - cost_without_margin:.6f}") @@ -1107,7 +1191,9 @@ def test_cost_margin_combined(): ) # Set 8% margin + $0.0005 fixed for openai - litellm.cost_margin_config = {"openai": {"percentage": 0.08, "fixed_amount": 0.0005}} + litellm.cost_margin_config = { + "openai": {"percentage": 0.08, "fixed_amount": 0.0005} + } # Calculate cost with margin cost_with_margin = completion_cost( @@ -1123,7 +1209,7 @@ def test_cost_margin_combined(): expected_cost = cost_without_margin * 1.08 + 0.0005 assert cost_with_margin == pytest.approx(expected_cost, rel=1e-9) - print(f"✓ Cost margin combined test passed:") + print("✓ Cost margin combined test passed:") print(f" - Original cost: ${cost_without_margin:.6f}") print(f" - Cost with margin (8% + $0.0005): ${cost_with_margin:.6f}") print(f" - Margin added: ${cost_with_margin - cost_without_margin:.6f}") @@ -1174,7 +1260,7 @@ def test_cost_margin_global(): expected_cost = cost_without_margin * 1.05 assert cost_with_global_margin == pytest.approx(expected_cost, rel=1e-9) - print(f"✓ Cost margin global test passed:") + print("✓ Cost margin global test passed:") print(f" - Original cost: ${cost_without_margin:.6f}") print(f" - Cost with global margin (5%): ${cost_with_global_margin:.6f}") print(f" - Margin added: ${cost_with_global_margin - cost_without_margin:.6f}") @@ -1225,9 +1311,11 @@ def test_cost_margin_provider_overrides_global(): expected_cost = cost_without_margin * 1.10 # 10% from provider, not 5% from global assert cost_with_provider_margin == pytest.approx(expected_cost, rel=1e-9) - print(f"✓ Cost margin provider override test passed:") + print("✓ Cost margin provider override test passed:") print(f" - Original cost: ${cost_without_margin:.6f}") - print(f" - Cost with provider margin (10%, overrides 5% global): ${cost_with_provider_margin:.6f}") + print( + f" - Cost with provider margin (10%, overrides 5% global): ${cost_with_provider_margin:.6f}" + ) print(f" - Margin added: ${cost_with_provider_margin - cost_without_margin:.6f}") @@ -1281,7 +1369,7 @@ def test_cost_margin_with_discount(): expected_cost = base_cost * 0.95 * 1.10 assert cost_with_both == pytest.approx(expected_cost, rel=1e-9) - print(f"✓ Cost margin with discount test passed:") + print("✓ Cost margin with discount test passed:") print(f" - Base cost: ${base_cost:.6f}") print(f" - Cost with 5% discount + 10% margin: ${cost_with_both:.6f}") print(f" - Expected: ${expected_cost:.6f}") @@ -1350,14 +1438,10 @@ def test_completion_cost_extracts_service_tier_from_response(): # Test with gpt-5-nano which has flex pricing model = "gpt-5-nano" - + # Create usage object - usage = Usage( - prompt_tokens=1000, - completion_tokens=500, - total_tokens=1500 - ) - + usage = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500) + # Create ModelResponse with service_tier in the response object response_with_service_tier = ModelResponse( usage=usage, @@ -1365,34 +1449,36 @@ def test_completion_cost_extracts_service_tier_from_response(): ) # Set service_tier as an attribute on the response setattr(response_with_service_tier, "service_tier", "flex") - + # Test that flex pricing is used when service_tier is in response flex_cost = completion_cost( completion_response=response_with_service_tier, model=model, custom_llm_provider="openai", ) - + # Create ModelResponse without service_tier (should use standard pricing) response_without_service_tier = ModelResponse( usage=usage, model=model, ) - + # Test that standard pricing is used when service_tier is not in response standard_cost = completion_cost( completion_response=response_without_service_tier, model=model, custom_llm_provider="openai", ) - + # Flex should be approximately 50% of standard assert flex_cost > 0, "Flex cost should be greater than 0" assert standard_cost > 0, "Standard cost should be greater than 0" assert flex_cost < standard_cost, "Flex cost should be less than standard cost" - + flex_ratio = flex_cost / standard_cost - assert 0.45 <= flex_ratio <= 0.55, f"Flex pricing should be ~50% of standard, got {flex_ratio:.2f}" + assert ( + 0.45 <= flex_ratio <= 0.55 + ), f"Flex pricing should be ~50% of standard, got {flex_ratio:.2f}" def test_completion_cost_extracts_service_tier_from_usage(): @@ -1404,56 +1490,54 @@ def test_completion_cost_extracts_service_tier_from_usage(): # Test with gpt-5-nano which has flex pricing model = "gpt-5-nano" - + # Create usage object with service_tier usage_with_service_tier = Usage( - prompt_tokens=1000, - completion_tokens=500, - total_tokens=1500 + prompt_tokens=1000, completion_tokens=500, total_tokens=1500 ) # Set service_tier as an attribute on the usage object setattr(usage_with_service_tier, "service_tier", "flex") - + # Create ModelResponse with usage containing service_tier response = ModelResponse( usage=usage_with_service_tier, model=model, ) - + # Test that flex pricing is used when service_tier is in usage flex_cost = completion_cost( completion_response=response, model=model, custom_llm_provider="openai", ) - + # Create usage object without service_tier usage_without_service_tier = Usage( - prompt_tokens=1000, - completion_tokens=500, - total_tokens=1500 + prompt_tokens=1000, completion_tokens=500, total_tokens=1500 ) - + # Create ModelResponse with usage without service_tier response_standard = ModelResponse( usage=usage_without_service_tier, model=model, ) - + # Test that standard pricing is used when service_tier is not in usage standard_cost = completion_cost( completion_response=response_standard, model=model, custom_llm_provider="openai", ) - + # Flex should be approximately 50% of standard assert flex_cost > 0, "Flex cost should be greater than 0" assert standard_cost > 0, "Standard cost should be greater than 0" assert flex_cost < standard_cost, "Flex cost should be less than standard cost" - + flex_ratio = flex_cost / standard_cost - assert 0.45 <= flex_ratio <= 0.55, f"Flex pricing should be ~50% of standard, got {flex_ratio:.2f}" + assert ( + 0.45 <= flex_ratio <= 0.55 + ), f"Flex pricing should be ~50% of standard, got {flex_ratio:.2f}" def test_completion_cost_service_tier_priority(): @@ -1465,22 +1549,18 @@ def test_completion_cost_service_tier_priority(): # Test with gpt-5-nano which has flex pricing model = "gpt-5-nano" - + # Create usage object with service_tier="flex" - usage = Usage( - prompt_tokens=1000, - completion_tokens=500, - total_tokens=1500 - ) + usage = Usage(prompt_tokens=1000, completion_tokens=500, total_tokens=1500) setattr(usage, "service_tier", "flex") - + # Create response with service_tier="priority" response = ModelResponse( usage=usage, model=model, ) setattr(response, "service_tier", "priority") - + # Test that optional_params takes priority over response and usage cost_from_params = completion_cost( completion_response=response, @@ -1488,14 +1568,14 @@ def test_completion_cost_service_tier_priority(): custom_llm_provider="openai", optional_params={"service_tier": "flex"}, ) - + # Test that response takes priority over usage when optional_params is not provided - cost_from_response = completion_cost( + completion_cost( completion_response=response, model=model, custom_llm_provider="openai", ) - + # Test that usage is used when neither optional_params nor response have service_tier # Create a new response without service_tier attribute response_no_tier = ModelResponse( @@ -1503,25 +1583,27 @@ def test_completion_cost_service_tier_priority(): model=model, ) # Don't set service_tier on response, so it will fall back to usage - + cost_from_usage = completion_cost( completion_response=response_no_tier, model=model, custom_llm_provider="openai", ) - + # All should use flex pricing (from different sources) assert cost_from_params > 0, "Cost from params should be greater than 0" assert cost_from_usage > 0, "Cost from usage should be greater than 0" - + # Costs should be similar (all using flex) - assert abs(cost_from_params - cost_from_usage) < 1e-6, "Costs from params and usage should be similar (both flex)" + assert ( + abs(cost_from_params - cost_from_usage) < 1e-6 + ), "Costs from params and usage should be similar (both flex)" def test_gemini_cache_tokens_details_no_negative_values(): """ Test for Issue #18750: Negative text_tokens with Gemini caching - + When using Gemini with explicit caching, the response includes cacheTokensDetails which breaks down cached tokens by modality. This test ensures that: 1. text_tokens is never negative @@ -1542,41 +1624,47 @@ def test_gemini_cache_tokens_details_no_negative_values(): # Total tokens by modality (includes cached + non-cached) "promptTokensDetails": [ {"modality": "TEXT", "tokenCount": 9402}, - {"modality": "IMAGE", "tokenCount": 258} + {"modality": "IMAGE", "tokenCount": 258}, ], # Breakdown of cached tokens by modality "cacheTokensDetails": [ {"modality": "TEXT", "tokenCount": 9393}, - {"modality": "IMAGE", "tokenCount": 258} - ] + {"modality": "IMAGE", "tokenCount": 258}, + ], } } usage = VertexGeminiConfig._calculate_usage(completion_response) # Text tokens should be non-cached text only: 9402 - 9393 = 9 - assert usage.prompt_tokens_details.text_tokens == 9, \ - f"Expected text_tokens=9, got {usage.prompt_tokens_details.text_tokens}" + assert ( + usage.prompt_tokens_details.text_tokens == 9 + ), f"Expected text_tokens=9, got {usage.prompt_tokens_details.text_tokens}" # Image tokens should be non-cached image only: 258 - 258 = 0 - assert usage.prompt_tokens_details.image_tokens == 0, \ - f"Expected image_tokens=0, got {usage.prompt_tokens_details.image_tokens}" + assert ( + usage.prompt_tokens_details.image_tokens == 0 + ), f"Expected image_tokens=0, got {usage.prompt_tokens_details.image_tokens}" # Total cached should match - assert usage.prompt_tokens_details.cached_tokens == 9651, \ - f"Expected cached_tokens=9651, got {usage.prompt_tokens_details.cached_tokens}" + assert ( + usage.prompt_tokens_details.cached_tokens == 9651 + ), f"Expected cached_tokens=9651, got {usage.prompt_tokens_details.cached_tokens}" # MOST IMPORTANT: text_tokens should NEVER be negative - assert usage.prompt_tokens_details.text_tokens >= 0, \ - f"BUG: text_tokens is negative ({usage.prompt_tokens_details.text_tokens})! This was the issue in #18750" + assert ( + usage.prompt_tokens_details.text_tokens >= 0 + ), f"BUG: text_tokens is negative ({usage.prompt_tokens_details.text_tokens})! This was the issue in #18750" - print("✅ Issue #18750 fix verified: text_tokens is correctly calculated and non-negative") + print( + "✅ Issue #18750 fix verified: text_tokens is correctly calculated and non-negative" + ) def test_gemini_without_cache_tokens_details(): """ Test Gemini response without cacheTokensDetails (implicit caching or no cache) - + When cacheTokensDetails is not present, we should use promptTokensDetails as-is without subtracting anything. """ @@ -1591,7 +1679,7 @@ def test_gemini_without_cache_tokens_details(): "totalTokenCount": 279, "promptTokensDetails": [ {"modality": "TEXT", "tokenCount": 6}, - {"modality": "IMAGE", "tokenCount": 258} + {"modality": "IMAGE", "tokenCount": 258}, ] # No cacheTokensDetails } @@ -1605,3 +1693,86 @@ def test_gemini_without_cache_tokens_details(): assert usage.prompt_tokens_details.text_tokens >= 0 print("✅ Gemini without cacheTokensDetails works correctly") + + +def test_gemini_implicit_caching_cost_calculation(): + """ + Test for Issue #16341: Gemini implicit cached tokens not counted in spend log + + When Gemini uses implicit caching, it returns cachedContentTokenCount but NOT + cacheTokensDetails. In this case, we should subtract cachedContentTokenCount + from text_tokens to correctly calculate costs. + + See: https://github.com/BerriAI/litellm/issues/16341 + """ + from litellm import completion_cost + from litellm.llms.vertex_ai.gemini.vertex_and_google_ai_studio_gemini import ( + VertexGeminiConfig, + ) + from litellm.types.utils import Choices, Message, ModelResponse + + # Simulate Gemini response with implicit caching (cachedContentTokenCount only) + completion_response = { + "usageMetadata": { + "promptTokenCount": 10000, + "candidatesTokenCount": 5, + "totalTokenCount": 10005, + "cachedContentTokenCount": 8000, # Implicit caching - no cacheTokensDetails + "promptTokensDetails": [{"modality": "TEXT", "tokenCount": 10000}], + "candidatesTokensDetails": [{"modality": "TEXT", "tokenCount": 5}], + } + } + + usage = VertexGeminiConfig._calculate_usage(completion_response) + + # Verify parsing + assert ( + usage.cache_read_input_tokens == 8000 + ), f"cache_read_input_tokens should be 8000, got {usage.cache_read_input_tokens}" + assert ( + usage.prompt_tokens_details.cached_tokens == 8000 + ), f"cached_tokens should be 8000, got {usage.prompt_tokens_details.cached_tokens}" + + # CRITICAL: text_tokens should be (10000 - 8000) = 2000, NOT 10000 + # This is the fix for issue #16341 + assert ( + usage.prompt_tokens_details.text_tokens == 2000 + ), f"text_tokens should be 2000 (10000 - 8000), got {usage.prompt_tokens_details.text_tokens}" + + # Verify cost calculation uses cached token pricing + response = ModelResponse( + id="mock-id", + model="gemini-2.0-flash", + choices=[ + Choices( + index=0, + message=Message(role="assistant", content="Hello!"), + finish_reason="stop", + ) + ], + usage=usage, + ) + + cost = completion_cost( + completion_response=response, + model="gemini-2.0-flash", + custom_llm_provider="gemini", + ) + + # Get model pricing for verification + import litellm + + model_info = litellm.get_model_info("gemini/gemini-2.0-flash") + input_cost = model_info.get("input_cost_per_token", 0) + cache_read_cost = model_info.get("cache_read_input_token_cost", input_cost) + output_cost = model_info.get("output_cost_per_token", 0) + + # Expected cost: (2000 * input) + (8000 * cache_read) + (5 * output) + expected_cost = (2000 * input_cost) + (8000 * cache_read_cost) + (5 * output_cost) + + assert abs(cost - expected_cost) < 1e-9, ( + f"Cost calculation is wrong. Got ${cost:.6f}, expected ${expected_cost:.6f}. " + f"Cached tokens may not be using reduced pricing." + ) + + print("✅ Issue #16341 fix verified: Gemini implicit caching cost calculated correctly") diff --git a/tests/test_litellm/test_eager_tiktoken_load.py b/tests/test_litellm/test_eager_tiktoken_load.py index 1264c68b99e..33dd57fad8d 100644 --- a/tests/test_litellm/test_eager_tiktoken_load.py +++ b/tests/test_litellm/test_eager_tiktoken_load.py @@ -78,6 +78,35 @@ def test_lazy_loading_default(): assert len(tokens) > 0, "Encoding should work" +def test_tiktoken_cache_dir_set_on_lazy_load(): + """Test that TIKTOKEN_CACHE_DIR is set when encoding is lazy loaded. + + This ensures the local tiktoken cache is used instead of downloading + from the internet. Regression test for issue #19768. + """ + # Remove environment variables to ensure clean state + if "LITELLM_DISABLE_LAZY_LOADING" in os.environ: + del os.environ["LITELLM_DISABLE_LAZY_LOADING"] + if "TIKTOKEN_CACHE_DIR" in os.environ: + del os.environ["TIKTOKEN_CACHE_DIR"] + + # Clear any cached modules + modules_to_clear = [k for k in sys.modules.keys() if k.startswith("litellm")] + for module in modules_to_clear: + del sys.modules[module] + + # Import litellm fresh + import litellm + + # Access encoding (triggers lazy load) + _ = litellm.encoding + + # Verify TIKTOKEN_CACHE_DIR is now set and points to local tokenizers + assert "TIKTOKEN_CACHE_DIR" in os.environ, "TIKTOKEN_CACHE_DIR should be set after lazy loading encoding" + cache_dir = os.environ["TIKTOKEN_CACHE_DIR"] + assert "tokenizers" in cache_dir, f"TIKTOKEN_CACHE_DIR should point to tokenizers directory, got: {cache_dir}" + + @pytest.fixture(autouse=True) def cleanup_env(): """Clean up environment variable after each test""" diff --git a/tests/test_litellm/test_gpt_image_cost_calculator.py b/tests/test_litellm/test_gpt_image_cost_calculator.py index 0a2a62b6c97..620c0734980 100644 --- a/tests/test_litellm/test_gpt_image_cost_calculator.py +++ b/tests/test_litellm/test_gpt_image_cost_calculator.py @@ -19,10 +19,13 @@ import pytest import litellm from litellm.types.utils import ( + CompletionTokensDetailsWrapper, ImageResponse, ImageObject, ImageUsage, ImageUsageInputTokensDetails, + PromptTokensDetailsWrapper, + Usage, ) @@ -202,6 +205,71 @@ class TestGPTImageCostRouting: assert cost >= 0 +class TestGPTImage15OutputImageTokens: + """ + Test for GitHub issue #19508: + Image usage calculation does not include image tokens in gpt-image-1.5 + + gpt-image-1.5 returns output_tokens_details with separate image_tokens and text_tokens, + and these must be correctly included in cost calculation. + """ + + def test_gpt_image_15_output_image_tokens_cost(self): + """ + Test that output image tokens are correctly included in cost calculation. + + This tests the fix for issue #19508 where output_tokens_details.image_tokens + were not being included in the cost calculation, causing costs to be + underreported (e.g., $0.046 instead of $0.14). + """ + # Simulate gpt-image-1.5 response with output_tokens_details + # This is what the API returns and what convert_to_image_response transforms + usage = Usage( + prompt_tokens=169, + completion_tokens=4599, + total_tokens=4768, + prompt_tokens_details=PromptTokensDetailsWrapper( + text_tokens=169, + image_tokens=0, + ), + completion_tokens_details=CompletionTokensDetailsWrapper( + text_tokens=439, + image_tokens=4160, + ), + ) + + image_response = ImageResponse( + created=1234567890, + data=[ImageObject(b64_json="test")], + ) + image_response.usage = usage + image_response._hidden_params = {"custom_llm_provider": "openai"} + + cost = litellm.completion_cost( + completion_response=image_response, + model="gpt-image-1.5", + call_type="image_generation", + custom_llm_provider="openai", + ) + + # gpt-image-1.5 pricing: + # - input_cost_per_token: 5e-06 ($5/1M for text input) + # - output_cost_per_token: 1e-05 ($10/1M for text output) + # - output_cost_per_image_token: 3.2e-05 ($32/1M for image output) + # + # Expected cost: + # Input text: 169 * $5/1M = $0.000845 + # Output text: 439 * $10/1M = $0.00439 + # Output image: 4160 * $32/1M = $0.13312 + # Total: $0.138355 + expected_cost = 169 * 5e-06 + 439 * 1e-05 + 4160 * 3.2e-05 + + assert abs(cost - expected_cost) < 1e-6, ( + f"Expected {expected_cost}, got {cost}. " + f"Image tokens may not be included in cost calculation." + ) + + class TestCompletionCostIntegration: """Test the full completion_cost integration for gpt-image-1""" diff --git a/tests/test_litellm/test_main.py b/tests/test_litellm/test_main.py index d2416f8db8c..70664827253 100644 --- a/tests/test_litellm/test_main.py +++ b/tests/test_litellm/test_main.py @@ -15,10 +15,23 @@ import urllib.parse from unittest.mock import MagicMock, patch import litellm - from litellm import main as litellm_main +@pytest.fixture(autouse=True) +def clear_client_cache(): + """ + Clear the HTTP client cache before each test to ensure mocks are used. + This prevents cached real clients from being reused across tests. + """ + cache = getattr(litellm, "in_memory_llm_clients_cache", None) + if cache is not None: + cache.flush_cache() + yield + if cache is not None: + cache.flush_cache() + + @pytest.fixture(autouse=True) def add_api_keys_to_env(monkeypatch): monkeypatch.setenv("ANTHROPIC_API_KEY", "sk-ant-api03-1234567890") @@ -473,10 +486,18 @@ async def test_extra_body_with_fallback( @pytest.mark.parametrize("env_base", ["OPENAI_BASE_URL", "OPENAI_API_BASE"]) @pytest.mark.asyncio +@pytest.mark.flaky(retries=3, delay=1) async def test_openai_env_base( respx_mock: respx.MockRouter, env_base, openai_api_response, monkeypatch ): "This tests OpenAI env variables are honored, including legacy OPENAI_API_BASE" + # Clear cache to ensure no cached clients from previous tests interfere + # This prevents cache pollution where a previous test cached a client with + # aiohttp transport, which would bypass respx mocks + if hasattr(litellm, "in_memory_llm_clients_cache"): + litellm.in_memory_llm_clients_cache.flush_cache() + + # Ensure aiohttp transport is disabled to use httpx which respx can mock litellm.disable_aiohttp_transport = True expected_base_url = "http://localhost:12345/v1" @@ -488,7 +509,11 @@ async def test_openai_env_base( model = "gpt-4o" messages = [{"role": "user", "content": "Hello, how are you?"}] - respx_mock.post(f"{expected_base_url}/chat/completions").respond( + # Configure respx mock to intercept the request + mock_route = respx_mock.post( + url__regex=r"http://localhost:12345/v1/chat/completions.*" + ).mock(return_value=httpx.Response( + status_code=200, json={ "id": "chatcmpl-123", "object": "chat.completion", @@ -506,12 +531,19 @@ async def test_openai_env_base( ], "usage": {"prompt_tokens": 9, "completion_tokens": 12, "total_tokens": 21}, } - ) + )) - response = await litellm.acompletion(model=model, messages=messages) - - # verify we had a response - assert response.choices[0].message.content == "Hello from mocked response!" + try: + response = await litellm.acompletion(model=model, messages=messages) + + # verify we had a response + assert response.choices[0].message.content == "Hello from mocked response!" + + # Verify the mock was called + assert mock_route.called, "Mock route was not called - request may have bypassed respx" + finally: + # Clean up to avoid affecting other tests + litellm.disable_aiohttp_transport = False def build_database_url(username, password, host, dbname): @@ -1303,6 +1335,8 @@ def test_anthropic_text_disable_url_suffix_env_var(): def test_image_edit_merges_headers_and_extra_headers(): + from litellm.images.main import base_llm_http_handler + combined_headers = { "x-test-header-one": "value-1", "x-test-header-two": "value-2", @@ -1319,8 +1353,9 @@ def test_image_edit_merges_headers_and_extra_headers(): "litellm.images.main.ProviderConfigManager.get_provider_image_edit_config", return_value=mock_image_edit_config, ) as mock_config, - patch( - "litellm.images.main.base_llm_http_handler.image_edit_handler", + patch.object( + base_llm_http_handler, + "image_edit_handler", return_value="ok", ) as mock_handler, ): diff --git a/tests/test_litellm/test_model_param_helper.py b/tests/test_litellm/test_model_param_helper.py new file mode 100644 index 00000000000..c6e4b864a22 --- /dev/null +++ b/tests/test_litellm/test_model_param_helper.py @@ -0,0 +1,33 @@ +from litellm.litellm_core_utils.model_param_helper import ModelParamHelper + + +def test_cached_relevant_logging_args_matches_dynamic(): + """Verify the cached frozenset matches the dynamically computed set.""" + cached = ModelParamHelper._relevant_logging_args + dynamic = ModelParamHelper._get_relevant_args_to_use_for_logging() + assert cached == dynamic + assert isinstance(cached, frozenset) + + +def test_get_standard_logging_model_parameters_filters(): + """Verify model parameters are filtered to only supported keys.""" + params = {"temperature": 0.7, "messages": [{"role": "user"}], "max_tokens": 100} + result = ModelParamHelper.get_standard_logging_model_parameters(params) + assert "temperature" in result + assert "max_tokens" in result + assert "messages" not in result # excluded prompt content + + +def test_get_standard_logging_model_parameters_excludes_prompt_content(): + """Verify all prompt content keys are excluded.""" + params = { + "messages": [{"role": "user", "content": "hi"}], + "prompt": "hello", + "input": "test", + "temperature": 0.5, + } + result = ModelParamHelper.get_standard_logging_model_parameters(params) + assert "messages" not in result + assert "prompt" not in result + assert "input" not in result + assert result == {"temperature": 0.5} diff --git a/tests/test_litellm/test_responses_api_bridge_non_stream.py b/tests/test_litellm/test_responses_api_bridge_non_stream.py index 8f72debfc5d..c35ca1046fd 100644 --- a/tests/test_litellm/test_responses_api_bridge_non_stream.py +++ b/tests/test_litellm/test_responses_api_bridge_non_stream.py @@ -1,9 +1,32 @@ +import os +import sys +from typing import Optional +from unittest.mock import Mock + +import pytest + +sys.path.insert(0, os.path.abspath("../..")) + from litellm.completion_extras.litellm_responses_transformation.handler import ( ResponsesToCompletionBridgeHandler, ) -from litellm.types.llms.openai import ResponsesAPIResponse +from litellm.responses.litellm_completion_transformation.transformation import ( + LiteLLMCompletionResponsesConfig, +) +from litellm.types.llms.openai import ( + InputTokensDetails, + OutputTokensDetails, + ResponsesAPIResponse, +) +from litellm.types.utils import Choices, Message, ModelResponse, Usage +""" +Test that all providers can transform completion responses to Responses API format +without breaking due to required fields in InputTokensDetails and OutputTokensDetails. +This is a regression test for the change where reasoning_tokens and cached_tokens +were made non-optional (must be int, not Optional[int]). +""" class _CompletedEvent: def __init__(self, response): self.response = response @@ -42,3 +65,358 @@ def test_should_collect_response_from_stream(): assert collected.id == "resp-1" assert collected._hidden_params.get("headers") == {"x-test": "1"} + + +def create_mock_completion_response( + model: str = "gpt-4", + prompt_tokens: int = 10, + completion_tokens: int = 20, + total_tokens: int = 30, + reasoning_tokens: Optional[int] = None, + cached_tokens: Optional[int] = None, + text_tokens: Optional[int] = None, +) -> ModelResponse: + """ + Create a mock ModelResponse (chat completion) with various token details. + + This simulates responses from different providers that may or may not include + reasoning_tokens, cached_tokens, etc. + """ + usage = Usage( + prompt_tokens=prompt_tokens, + completion_tokens=completion_tokens, + total_tokens=total_tokens, + ) + + # Add prompt_tokens_details if we have cached_tokens or text_tokens + if cached_tokens is not None or text_tokens is not None: + from litellm.types.utils import PromptTokensDetails + usage.prompt_tokens_details = PromptTokensDetails( + cached_tokens=cached_tokens, + text_tokens=text_tokens, + ) + + # Add completion_tokens_details if we have reasoning_tokens or text_tokens + if reasoning_tokens is not None or text_tokens is not None: + from litellm.types.utils import CompletionTokensDetails + usage.completion_tokens_details = CompletionTokensDetails( + reasoning_tokens=reasoning_tokens, + text_tokens=text_tokens, + ) + + return ModelResponse( + id="chatcmpl-test", + created=1234567890, + model=model, + object="chat.completion", + choices=[ + Choices( + finish_reason="stop", + index=0, + message=Message( + content="Test response", + role="assistant", + ), + ) + ], + usage=usage, + ) + + +def test_transform_usage_no_token_details(): + """ + Test that transformation works when completion response has NO token details. + + This simulates providers that don't return detailed token breakdowns. + """ + completion_response = create_mock_completion_response( + model="gpt-4", + prompt_tokens=10, + completion_tokens=20, + total_tokens=30, + ) + + # Transform to Responses API usage format + responses_usage = LiteLLMCompletionResponsesConfig._transform_chat_completion_usage_to_responses_usage( + completion_response + ) + + # Should succeed without errors + assert responses_usage.input_tokens == 10 + assert responses_usage.output_tokens == 20 + assert responses_usage.total_tokens == 30 + + # Token details should not be present when not provided + assert responses_usage.input_tokens_details is None + assert responses_usage.output_tokens_details is None + + print("✓ Transformation works with no token details") + + +def test_transform_usage_with_cached_tokens_only(): + """ + Test transformation when only cached_tokens is provided (no reasoning_tokens). + + This simulates providers like Anthropic that support prompt caching but not reasoning. + """ + completion_response = create_mock_completion_response( + model="claude-3-opus", + prompt_tokens=100, + completion_tokens=50, + total_tokens=150, + cached_tokens=80, # Has cached tokens + reasoning_tokens=None, # No reasoning tokens + ) + + responses_usage = LiteLLMCompletionResponsesConfig._transform_chat_completion_usage_to_responses_usage( + completion_response + ) + + # Should succeed and default reasoning_tokens to 0 + assert responses_usage.input_tokens == 100 + assert responses_usage.output_tokens == 50 + assert responses_usage.total_tokens == 150 + + # Input details should be present with cached_tokens + assert responses_usage.input_tokens_details is not None + assert isinstance(responses_usage.input_tokens_details, InputTokensDetails) + assert responses_usage.input_tokens_details.cached_tokens == 80 + + # Output details should not be present (no reasoning_tokens provided) + assert responses_usage.output_tokens_details is None + + print("✓ Transformation works with cached_tokens only") + + +def test_transform_usage_with_reasoning_tokens_only(): + """ + Test transformation when only reasoning_tokens is provided (no cached_tokens). + + This simulates providers like OpenAI o1 that support reasoning but not caching. + """ + completion_response = create_mock_completion_response( + model="o1-preview", + prompt_tokens=50, + completion_tokens=100, + total_tokens=150, + cached_tokens=None, # No cached tokens + reasoning_tokens=60, # Has reasoning tokens + ) + + responses_usage = LiteLLMCompletionResponsesConfig._transform_chat_completion_usage_to_responses_usage( + completion_response + ) + + # Should succeed and default cached_tokens to 0 + assert responses_usage.input_tokens == 50 + assert responses_usage.output_tokens == 100 + assert responses_usage.total_tokens == 150 + + # Input details should not be present (no cached_tokens provided) + assert responses_usage.input_tokens_details is None + + # Output details should be present with reasoning_tokens + assert responses_usage.output_tokens_details is not None + assert isinstance(responses_usage.output_tokens_details, OutputTokensDetails) + assert responses_usage.output_tokens_details.reasoning_tokens == 60 + + print("✓ Transformation works with reasoning_tokens only") + + +def test_transform_usage_with_both_token_details(): + """ + Test transformation when both cached_tokens and reasoning_tokens are provided. + + This simulates advanced providers that support both features. + """ + completion_response = create_mock_completion_response( + model="gpt-4o", + prompt_tokens=100, + completion_tokens=80, + total_tokens=180, + cached_tokens=50, + reasoning_tokens=30, + text_tokens=50, # Also include text_tokens + ) + + responses_usage = LiteLLMCompletionResponsesConfig._transform_chat_completion_usage_to_responses_usage( + completion_response + ) + + # Should succeed with all details + assert responses_usage.input_tokens == 100 + assert responses_usage.output_tokens == 80 + assert responses_usage.total_tokens == 180 + + # Input details should have cached_tokens + assert responses_usage.input_tokens_details is not None + assert responses_usage.input_tokens_details.cached_tokens == 50 + assert responses_usage.input_tokens_details.text_tokens == 50 + + # Output details should have reasoning_tokens + assert responses_usage.output_tokens_details is not None + assert responses_usage.output_tokens_details.reasoning_tokens == 30 + assert responses_usage.output_tokens_details.text_tokens == 50 + + print("✓ Transformation works with both cached_tokens and reasoning_tokens") + + +def test_transform_usage_with_zero_values(): + """ + Test transformation when token details are explicitly set to 0. + + This ensures 0 values are preserved and not treated as None. + """ + completion_response = create_mock_completion_response( + model="gpt-4", + prompt_tokens=100, + completion_tokens=50, + total_tokens=150, + cached_tokens=0, # Explicitly 0 + reasoning_tokens=0, # Explicitly 0 + ) + + responses_usage = LiteLLMCompletionResponsesConfig._transform_chat_completion_usage_to_responses_usage( + completion_response + ) + + # Should preserve 0 values + assert responses_usage.input_tokens_details is not None + assert responses_usage.input_tokens_details.cached_tokens == 0 + + assert responses_usage.output_tokens_details is not None + assert responses_usage.output_tokens_details.reasoning_tokens == 0 + + print("✓ Transformation preserves explicit 0 values") + + +def test_input_tokens_details_requires_cached_tokens(): + """ + Test that InputTokensDetails has cached_tokens as an int with default value 0. + + This ensures backward compatibility while making the field non-optional. + """ + # Should work with cached_tokens=0 + details1 = InputTokensDetails(cached_tokens=0) + assert details1.cached_tokens == 0 + + # Should work with cached_tokens=100 + details2 = InputTokensDetails(cached_tokens=100) + assert details2.cached_tokens == 100 + + # Should work without cached_tokens (defaults to 0) + details3 = InputTokensDetails() + assert details3.cached_tokens == 0 + + print("✓ InputTokensDetails correctly defaults cached_tokens to 0") + + +def test_output_tokens_details_requires_reasoning_tokens(): + """ + Test that OutputTokensDetails has reasoning_tokens as an int with default value 0. + + This ensures backward compatibility while making the field non-optional. + """ + # Should work with reasoning_tokens=0 + details1 = OutputTokensDetails(reasoning_tokens=0) + assert details1.reasoning_tokens == 0 + + # Should work with reasoning_tokens=100 + details2 = OutputTokensDetails(reasoning_tokens=100) + assert details2.reasoning_tokens == 100 + + # Should work without reasoning_tokens (defaults to 0) + details3 = OutputTokensDetails() + assert details3.reasoning_tokens == 0 + + print("✓ OutputTokensDetails correctly defaults reasoning_tokens to 0") + + +def test_all_providers_transformation_scenarios(): + """ + Test various provider scenarios to ensure none break after the field requirement change. + + This tests the most common scenarios across different providers: + - OpenAI: may have reasoning_tokens + - Anthropic: may have cached_tokens + - Azure: similar to OpenAI + - Other providers: basic usage only + """ + test_scenarios = [ + { + "name": "Basic provider (no details)", + "model": "gpt-3.5-turbo", + "kwargs": {}, + }, + { + "name": "OpenAI with reasoning", + "model": "o1-preview", + "kwargs": {"reasoning_tokens": 100}, + }, + { + "name": "Anthropic with caching", + "model": "claude-3-opus", + "kwargs": {"cached_tokens": 50}, + }, + { + "name": "OpenAI with caching", + "model": "gpt-4o", + "kwargs": {"cached_tokens": 30}, + }, + { + "name": "Full details (both)", + "model": "gpt-4o", + "kwargs": {"cached_tokens": 40, "reasoning_tokens": 60, "text_tokens": 100}, + }, + { + "name": "Zero values", + "model": "gpt-4", + "kwargs": {"cached_tokens": 0, "reasoning_tokens": 0}, + }, + ] + + for scenario in test_scenarios: + print(f"\nTesting: {scenario['name']}") + + completion_response = create_mock_completion_response( + model=scenario["model"], + **scenario["kwargs"] + ) + + # This should not raise any errors + responses_usage = LiteLLMCompletionResponsesConfig._transform_chat_completion_usage_to_responses_usage( + completion_response + ) + + # Basic assertions + assert responses_usage.input_tokens >= 0 + assert responses_usage.output_tokens >= 0 + assert responses_usage.total_tokens >= 0 + + # If input_tokens_details exists, cached_tokens must be an int + if responses_usage.input_tokens_details is not None: + assert isinstance(responses_usage.input_tokens_details.cached_tokens, int) + + # If output_tokens_details exists, reasoning_tokens must be an int + if responses_usage.output_tokens_details is not None: + assert isinstance(responses_usage.output_tokens_details.reasoning_tokens, int) + + print(f" ✓ {scenario['name']} transformation successful") + + print("\n✓ All provider scenarios work correctly") + + +if __name__ == "__main__": + # Run all tests + test_transform_usage_no_token_details() + test_transform_usage_with_cached_tokens_only() + test_transform_usage_with_reasoning_tokens_only() + test_transform_usage_with_both_token_details() + test_transform_usage_with_zero_values() + test_input_tokens_details_requires_cached_tokens() + test_output_tokens_details_requires_reasoning_tokens() + test_all_providers_transformation_scenarios() + + print("\n" + "="*60) + print("ALL TESTS PASSED!") + print("="*60) diff --git a/tests/test_litellm/test_router/test_enforce_model_rate_limits.py b/tests/test_litellm/test_router/test_enforce_model_rate_limits.py new file mode 100644 index 00000000000..3bca3df4e1d --- /dev/null +++ b/tests/test_litellm/test_router/test_enforce_model_rate_limits.py @@ -0,0 +1,315 @@ +""" +Tests for enforce_model_rate_limits feature. + +This feature allows users to enforce TPM/RPM limits set on model deployments +regardless of the routing strategy being used. +""" + +from unittest.mock import AsyncMock, MagicMock + +import pytest + +import litellm +from litellm import Router +from litellm.router_utils.pre_call_checks.model_rate_limit_check import ( + ModelRateLimitingCheck, +) + + +class TestModelRateLimitingCheck: + """Test the ModelRateLimitingCheck class directly.""" + + def test_get_deployment_limits_from_top_level(self): + """Test extracting limits from top-level deployment config.""" + check = ModelRateLimitingCheck(dual_cache=MagicMock()) + + deployment = { + "tpm": 1000, + "rpm": 10, + "litellm_params": {"model": "gpt-4"}, + "model_info": {"id": "test-id"}, + } + + tpm, rpm = check._get_deployment_limits(deployment) + assert tpm == 1000 + assert rpm == 10 + + def test_get_deployment_limits_from_litellm_params(self): + """Test extracting limits from litellm_params.""" + check = ModelRateLimitingCheck(dual_cache=MagicMock()) + + deployment = { + "litellm_params": {"model": "gpt-4", "tpm": 2000, "rpm": 20}, + "model_info": {"id": "test-id"}, + } + + tpm, rpm = check._get_deployment_limits(deployment) + assert tpm == 2000 + assert rpm == 20 + + def test_get_deployment_limits_from_model_info(self): + """Test extracting limits from model_info.""" + check = ModelRateLimitingCheck(dual_cache=MagicMock()) + + deployment = { + "litellm_params": {"model": "gpt-4"}, + "model_info": {"id": "test-id", "tpm": 3000, "rpm": 30}, + } + + tpm, rpm = check._get_deployment_limits(deployment) + assert tpm == 3000 + assert rpm == 30 + + def test_get_deployment_limits_none_when_not_set(self): + """Test that None is returned when limits are not set.""" + check = ModelRateLimitingCheck(dual_cache=MagicMock()) + + deployment = { + "litellm_params": {"model": "gpt-4"}, + "model_info": {"id": "test-id"}, + } + + tpm, rpm = check._get_deployment_limits(deployment) + assert tpm is None + assert rpm is None + + def test_pre_call_check_allows_request_when_no_limits(self): + """Test that requests are allowed when no limits are set.""" + check = ModelRateLimitingCheck(dual_cache=MagicMock()) + + deployment = { + "litellm_params": {"model": "gpt-4"}, + "model_info": {"id": "test-id"}, + } + + result = check.pre_call_check(deployment) + assert result == deployment + + def test_pre_call_check_raises_rate_limit_error_when_over_rpm(self): + """Test that RateLimitError is raised when RPM limit is exceeded.""" + mock_cache = MagicMock() + mock_cache.get_cache.return_value = 10 # Already at limit + + check = ModelRateLimitingCheck(dual_cache=mock_cache) + + deployment = { + "rpm": 10, + "litellm_params": {"model": "gpt-4"}, + "model_info": {"id": "test-id"}, + "model_name": "test-model", + } + + with pytest.raises(litellm.RateLimitError) as exc_info: + check.pre_call_check(deployment) + + assert "RPM limit=10" in str(exc_info.value) + assert "current usage=10" in str(exc_info.value) + + def test_pre_call_check_allows_request_under_limit(self): + """Test that requests are allowed when under the limit.""" + mock_cache = MagicMock() + mock_cache.get_cache.return_value = 5 + mock_cache.increment_cache.return_value = 6 + + check = ModelRateLimitingCheck(dual_cache=mock_cache) + + deployment = { + "rpm": 10, + "litellm_params": {"model": "gpt-4"}, + "model_info": {"id": "test-id"}, + "model_name": "test-model", + } + + result = check.pre_call_check(deployment) + assert result == deployment + + def test_pre_call_check_raises_rate_limit_error_when_over_tpm(self): + """Test that RateLimitError is raised when TPM limit is exceeded.""" + mock_cache = MagicMock() + mock_cache.get_cache.return_value = 1000 # Already at limit + + check = ModelRateLimitingCheck(dual_cache=mock_cache) + + deployment = { + "tpm": 1000, + "litellm_params": {"model": "gpt-4"}, + "model_info": {"id": "test-id"}, + "model_name": "test-model", + } + + with pytest.raises(litellm.RateLimitError) as exc_info: + check.pre_call_check(deployment) + + assert "TPM limit=1000" in str(exc_info.value) + assert "current usage=1000" in str(exc_info.value) + + def test_log_success_event_increments_cache(self): + """Test that log_success_event correctly increments the cache.""" + mock_cache = MagicMock() + check = ModelRateLimitingCheck(dual_cache=mock_cache) + + kwargs = { + "standard_logging_object": { + "model_id": "test-id", + "total_tokens": 50, + "hidden_params": {"litellm_model_name": "gpt-4"}, + } + } + + check.log_success_event(kwargs, None, None, None) + + # Verify increment_cache was called + mock_cache.increment_cache.assert_called_once() + _, kwarg_params = mock_cache.increment_cache.call_args + assert "test-id:gpt-4:tpm:" in kwarg_params["key"] + assert kwarg_params["value"] == 50 + + +class TestModelRateLimitingCheckAsync: + """Test async methods of ModelRateLimitingCheck.""" + + @pytest.mark.asyncio + async def test_async_pre_call_check_allows_request_when_no_limits(self): + """Test that requests are allowed when no limits are set (async).""" + mock_cache = MagicMock() + mock_cache.async_get_cache = AsyncMock(return_value=None) + + check = ModelRateLimitingCheck(dual_cache=mock_cache) + + deployment = { + "litellm_params": {"model": "gpt-4"}, + "model_info": {"id": "test-id"}, + } + + result = await check.async_pre_call_check(deployment) + assert result == deployment + + @pytest.mark.asyncio + async def test_async_pre_call_check_raises_rate_limit_error_when_over_rpm(self): + """Test that RateLimitError is raised when RPM limit is exceeded (async).""" + mock_cache = MagicMock() + mock_cache.async_get_cache = AsyncMock(return_value=10) # Already at limit + + check = ModelRateLimitingCheck(dual_cache=mock_cache) + + deployment = { + "rpm": 10, + "litellm_params": {"model": "gpt-4"}, + "model_info": {"id": "test-id"}, + "model_name": "test-model", + } + + with pytest.raises(litellm.RateLimitError) as exc_info: + await check.async_pre_call_check(deployment) + + assert "RPM limit=10" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_async_pre_call_check_allows_request_under_limit(self): + """Test that requests are allowed when under the limit (async).""" + mock_cache = MagicMock() + mock_cache.async_get_cache = AsyncMock(return_value=5) + mock_cache.async_increment_cache = AsyncMock(return_value=6) + + check = ModelRateLimitingCheck(dual_cache=mock_cache) + + deployment = { + "rpm": 10, + "litellm_params": {"model": "gpt-4"}, + "model_info": {"id": "test-id"}, + "model_name": "test-model", + } + + result = await check.async_pre_call_check(deployment) + assert result == deployment + + @pytest.mark.asyncio + async def test_async_pre_call_check_raises_rate_limit_error_when_over_tpm(self): + """Test that RateLimitError is raised when TPM limit is exceeded (async).""" + mock_cache = MagicMock() + mock_cache.async_get_cache = AsyncMock(return_value=1000) # Already at limit + + check = ModelRateLimitingCheck(dual_cache=mock_cache) + + deployment = { + "tpm": 1000, + "litellm_params": {"model": "gpt-4"}, + "model_info": {"id": "test-id"}, + "model_name": "test-model", + } + + with pytest.raises(litellm.RateLimitError) as exc_info: + await check.async_pre_call_check(deployment) + + assert "TPM limit=1000" in str(exc_info.value) + + @pytest.mark.asyncio + async def test_async_log_success_event_increments_cache(self): + """Test that async_log_success_event correctly increments the cache.""" + mock_cache = MagicMock() + mock_cache.async_increment_cache = AsyncMock() + check = ModelRateLimitingCheck(dual_cache=mock_cache) + + kwargs = { + "standard_logging_object": { + "model_id": "test-id", + "total_tokens": 50, + "hidden_params": {"litellm_model_name": "gpt-4"}, + } + } + + await check.async_log_success_event(kwargs, None, None, None) + + # Verify async_increment_cache was called + mock_cache.async_increment_cache.assert_called_once() + _, kwarg_params = mock_cache.async_increment_cache.call_args + assert "test-id:gpt-4:tpm:" in kwarg_params["key"] + assert kwarg_params["value"] == 50 + + +class TestRouterWithEnforceModelRateLimits: + """Test Router integration with enforce_model_rate_limits.""" + + def test_router_initializes_with_enforce_model_rate_limits(self): + """Test that Router properly initializes the ModelRateLimitingCheck.""" + model_list = [ + { + "model_name": "gpt-4", + "litellm_params": {"model": "gpt-4", "api_key": "test"}, + "rpm": 10, + } + ] + + router = Router( + model_list=model_list, + optional_pre_call_checks=["enforce_model_rate_limits"], + ) + + # Check that the callback was added + assert router.optional_callbacks is not None + assert len(router.optional_callbacks) == 1 + assert isinstance(router.optional_callbacks[0], ModelRateLimitingCheck) + + def test_router_optional_callbacks_contains_model_rate_limiting(self): + """Test that ModelRateLimitingCheck is in the callbacks list.""" + model_list = [ + { + "model_name": "gpt-4", + "litellm_params": {"model": "gpt-4", "api_key": "test"}, + "rpm": 10, + } + ] + + Router( + model_list=model_list, + optional_pre_call_checks=["enforce_model_rate_limits"], + ) + + # Find the ModelRateLimitingCheck in litellm.callbacks + found = False + for callback in litellm.callbacks: + if isinstance(callback, ModelRateLimitingCheck): + found = True + break + + assert found, "ModelRateLimitingCheck should be in litellm.callbacks" diff --git a/tests/test_litellm/test_router_per_deployment_num_retries.py b/tests/test_litellm/test_router_per_deployment_num_retries.py index 4021ca28073..154ba579e4e 100644 --- a/tests/test_litellm/test_router_per_deployment_num_retries.py +++ b/tests/test_litellm/test_router_per_deployment_num_retries.py @@ -32,17 +32,17 @@ class TestPerDeploymentNumRetries: ) deployment = router.model_list[0] - + # Create a mock exception without num_retries class MockException(Exception): pass - + exc = MockException("test error") assert not hasattr(exc, "num_retries") or exc.num_retries is None - + # Call the helper router._set_deployment_num_retries_on_exception(exc, deployment) - + # Verify num_retries was set from deployment assert exc.num_retries == 5 @@ -66,16 +66,16 @@ class TestPerDeploymentNumRetries: ) deployment = router.model_list[0] - + # Create an exception that already has num_retries class MockException(Exception): num_retries = 10 # Already set - + exc = MockException("test error") - + # Call the helper router._set_deployment_num_retries_on_exception(exc, deployment) - + # Verify num_retries was NOT overridden assert exc.num_retries == 10 @@ -99,15 +99,15 @@ class TestPerDeploymentNumRetries: ) deployment = router.model_list[0] - + class MockException(Exception): pass - + exc = MockException("test error") - + # Call the helper router._set_deployment_num_retries_on_exception(exc, deployment) - + # Verify num_retries was not set (deployment has no num_retries) assert not hasattr(exc, "num_retries") or exc.num_retries is None @@ -155,3 +155,36 @@ class TestPerDeploymentNumRetries: kwargs = {} router._update_kwargs_before_fallbacks(model="test-model", kwargs=kwargs) assert kwargs["num_retries"] == 7 # Uses global + + def test_set_deployment_num_retries_with_string_value(self): + """ + Test that _set_deployment_num_retries_on_exception handles string values + from environment variables correctly. + GitHub Issue: #19481 + """ + router = Router( + model_list=[ + { + "model_name": "test-model", + "litellm_params": { + "model": "openai/gpt-4", + "api_key": "test-key", + "num_retries": "6", # String value (as from env var) + }, + }, + ], + num_retries=0, # Global setting + ) + + deployment = router.model_list[0] + + class MockException(Exception): + pass + + exc = MockException("test error") + + # Call the helper + router._set_deployment_num_retries_on_exception(exc, deployment) + + # Verify num_retries was converted from string to int + assert exc.num_retries == 6 diff --git a/tests/test_litellm/test_router_redis_init.py b/tests/test_litellm/test_router_redis_init.py new file mode 100644 index 00000000000..4a8a5b57622 --- /dev/null +++ b/tests/test_litellm/test_router_redis_init.py @@ -0,0 +1,56 @@ +import pytest +import asyncio +import os +from litellm import Router + + +# Mark as async test +@pytest.mark.asyncio +async def test_router_uses_correct_redis_db(): + """ + Verifies that when redis_db is passed to Router, + items are actually stored in that specific Redis DB index. + """ + # 1. Setup - Use a non-standard DB index (e.g., 5) to prove it's not using default 0 + test_db_index = 5 + + # Ensure we have a Redis URL available (fallback to localhost if env var not set) + redis_host = os.getenv("REDIS_HOST", "localhost") + redis_port = os.getenv("REDIS_PORT", "6379") + + # Initialize Router with specific redis_db + router = Router( + model_list=[ + { + "model_name": "gpt-3.5-turbo", + "litellm_params": {"model": "gpt-3.5-turbo"}, + } + ], + redis_host=redis_host, + redis_port=int(redis_port), + redis_db=test_db_index, + cache_responses=True, # Important: Enable caching to trigger Redis usage + ) + + # 2. Verify Internal State + # Check if the underlying cache client is configured with the correct DB + # Accessing internal attributes for verification purposes + try: + if router.cache.redis_cache: + # Check connection kwargs or internal client db + cache_client = router.cache.redis_cache.redis_client + # Redis client stores connection args in connection_pool.connection_kwargs + conn_kwargs = cache_client.connection_pool.connection_kwargs + + assert str(conn_kwargs.get("db")) == str( + test_db_index + ), f"Router Internal Check Failed: Expected DB {test_db_index}, got {conn_kwargs.get('db')}" + else: + pytest.fail("Redis cache was not initialized in Router") + + except Exception as e: + pytest.fail(f"Failed to inspect Router internals: {e}") + + +if __name__ == "__main__": + asyncio.run(test_router_uses_correct_redis_db()) diff --git a/tests/test_litellm/test_router_silent_experiment.py b/tests/test_litellm/test_router_silent_experiment.py new file mode 100644 index 00000000000..a23ea80f7ce --- /dev/null +++ b/tests/test_litellm/test_router_silent_experiment.py @@ -0,0 +1,214 @@ +import asyncio +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +import litellm +from litellm.router import Router + + +def test_get_silent_experiment_kwargs(): + """ + Test _get_silent_experiment_kwargs returns isolated kwargs with silent experiment metadata. + Direct call for router code coverage. + """ + model_list = [ + { + "model_name": "gpt-3.5-turbo", + "litellm_params": {"model": "gpt-3.5-turbo", "api_key": "fake-key"}, + }, + ] + router = Router(model_list=model_list) + kwargs = {"metadata": {"foo": "bar"}, "litellm_call_id": "call-123"} + result = router._get_silent_experiment_kwargs(**kwargs) + assert result["metadata"]["is_silent_experiment"] is True + assert result["metadata"]["foo"] == "bar" + assert "litellm_call_id" not in result + + +def test_silent_experiment_completion_direct(): + """ + Test _silent_experiment_completion directly (for router code coverage). + Mocks router.completion to avoid real API call. + """ + model_list = [ + { + "model_name": "gpt-3.5-turbo", + "litellm_params": {"model": "gpt-3.5-turbo", "api_key": "fake-key"}, + }, + ] + router = Router(model_list=model_list) + messages = [{"role": "user", "content": "hi"}] + with patch.object(router, "completion", return_value=None): + router._silent_experiment_completion( + silent_model="gpt-3.5-turbo", + messages=messages, + ) + + +@pytest.mark.asyncio +async def test_silent_experiment_acompletion_direct(): + """ + Test _silent_experiment_acompletion directly (for router code coverage). + Mocks router.acompletion to avoid real API call. + """ + model_list = [ + { + "model_name": "gpt-3.5-turbo", + "litellm_params": {"model": "gpt-3.5-turbo", "api_key": "fake-key"}, + }, + ] + router = Router(model_list=model_list) + messages = [{"role": "user", "content": "hi"}] + with patch.object(router, "acompletion", new_callable=AsyncMock, return_value=None): + await router._silent_experiment_acompletion( + silent_model="gpt-3.5-turbo", + messages=messages, + ) + + +@pytest.mark.asyncio +async def test_router_silent_experiment_acompletion(): + """ + Test that silent_model triggers a background acompletion call + and that the silent_model parameter is stripped from both calls. + """ + model_list = [ + { + "model_name": "primary-model", + "litellm_params": { + "model": "openai/gpt-3.5-turbo", + "api_key": "fake-key", + "silent_model": "silent-model", + }, + }, + { + "model_name": "silent-model", + "litellm_params": { + "model": "openai/gpt-4", + "api_key": "fake-key", + }, + }, + ] + + router = Router(model_list=model_list) + + # Use AsyncMock for async function mocking + mock_response = litellm.ModelResponse(choices=[{"message": {"content": "hello"}}]) + mock_acompletion = AsyncMock(return_value=mock_response) + + # Patch at the litellm.router module level where it's imported and used + with patch.object(litellm, "acompletion", mock_acompletion): + response = await router.acompletion( + model="primary-model", + messages=[{"role": "user", "content": "hi"}], + ) + + assert response.choices[0].message.content == "hello" + + # Give the background task a moment to trigger (it's an asyncio task) + await asyncio.sleep(0.1) + + # Should have 2 calls: one for primary, one for silent + assert mock_acompletion.call_count == 2 + + # Check call arguments + call_args_list = mock_acompletion.call_args_list + + # Verify no silent_model in any call to litellm.acompletion + for call in call_args_list: + args, kwargs = call + assert "silent_model" not in kwargs + if "metadata" in kwargs: + # One call should have is_silent_experiment=True + pass + + # Find the silent call + silent_call = next( + ( + c + for c in call_args_list + if c[1].get("metadata", {}).get("is_silent_experiment") is True + ), + None, + ) + assert silent_call is not None + assert silent_call[1]["model"] == "openai/gpt-4" + + # Find the primary call + primary_call = next( + ( + c + for c in call_args_list + if not c[1].get("metadata", {}).get("is_silent_experiment") + ), + None, + ) + assert primary_call is not None + assert primary_call[1]["model"] == "openai/gpt-3.5-turbo" + + +def test_router_silent_experiment_completion(): + """ + Test that silent_model triggers a background completion call (sync) + and that the silent_model parameter is stripped. + """ + model_list = [ + { + "model_name": "primary-model", + "litellm_params": { + "model": "openai/gpt-3.5-turbo", + "api_key": "fake-key", + "silent_model": "silent-model", + }, + }, + { + "model_name": "silent-model", + "litellm_params": { + "model": "openai/gpt-4", + "api_key": "fake-key", + }, + }, + ] + + router = Router(model_list=model_list) + + # Mock litellm.completion + mock_response = litellm.ModelResponse(choices=[{"message": {"content": "hello"}}]) + mock_completion = MagicMock(return_value=mock_response) + + # Patch at the litellm module level + with patch.object(litellm, "completion", mock_completion): + response = router.completion( + model="primary-model", + messages=[{"role": "user", "content": "hi"}], + ) + + assert response.choices[0].message.content == "hello" + + # The sync background call uses a thread pool. We might need to wait a bit. + import time + + time.sleep(0.5) + + # Should have 2 calls + assert mock_completion.call_count == 2 + + call_args_list = mock_completion.call_args_list + + # Verify no silent_model in any call + for call in call_args_list: + args, kwargs = call + assert "silent_model" not in kwargs + + # Find the silent call + silent_call = next( + ( + c + for c in call_args_list + if c[1].get("metadata", {}).get("is_silent_experiment") is True + ), + None, + ) + assert silent_call is not None + assert silent_call[1]["model"] == "openai/gpt-4" diff --git a/tests/test_litellm/test_utils.py b/tests/test_litellm/test_utils.py index f6c24d19df5..c7803445eb4 100644 --- a/tests/test_litellm/test_utils.py +++ b/tests/test_litellm/test_utils.py @@ -539,6 +539,7 @@ def test_aaamodel_prices_and_context_window_json_is_valid(): "cache_creation_input_token_cost_above_200k_tokens": {"type": "number"}, "cache_read_input_token_cost": {"type": "number"}, "cache_read_input_token_cost_above_200k_tokens": {"type": "number"}, + "cache_creation_input_token_cost_above_1hr_above_200k_tokens": {"type": "number"}, "cache_read_input_audio_token_cost": {"type": "number"}, "cache_read_input_image_token_cost": {"type": "number"}, "deprecation_date": {"type": "string"}, @@ -639,6 +640,7 @@ def test_aaamodel_prices_and_context_window_json_is_valid(): "rpd": {"type": "number"}, "rpm": {"type": "number"}, "source": {"type": "string"}, + "comment": {"type": "string"}, "supports_assistant_prefill": {"type": "boolean"}, "supports_audio_input": {"type": "boolean"}, "supports_audio_output": {"type": "boolean"}, @@ -841,6 +843,7 @@ def test_get_model_info_gemini(): and not "learnlm" in model and not "imagen" in model and not "veo" in model + and not "robotics" in model ): assert info.get("tpm") is not None, f"{model} does not have tpm" assert info.get("rpm") is not None, f"{model} does not have rpm" @@ -2280,8 +2283,23 @@ def test_register_model_with_scientific_notation(): """ Test that the register_model function can handle scientific notation in the model name. """ + # Use a unique model name to avoid conflicts with other tests + test_model_name = "test-scientific-notation-model-unique-12345" + + # Clean up any pre-existing entry and clear caches + if test_model_name in litellm.model_cost: + del litellm.model_cost[test_model_name] + + # Clear LRU caches that might have stale data + from litellm.utils import ( + _cached_get_model_info_helper, + _invalidate_model_cost_lowercase_map, + get_model_info, + ) + _invalidate_model_cost_lowercase_map() + model_cost_dict = { - "my-custom-model": { + test_model_name: { "max_tokens": 8192, "input_cost_per_token": "3e-07", "output_cost_per_token": "6e-07", @@ -2292,12 +2310,17 @@ def test_register_model_with_scientific_notation(): litellm.register_model(model_cost_dict) - registered_model = litellm.model_cost["my-custom-model"] + registered_model = litellm.model_cost[test_model_name] print(registered_model) assert registered_model["input_cost_per_token"] == 3e-07 assert registered_model["output_cost_per_token"] == 6e-07 assert registered_model["litellm_provider"] == "openai" assert registered_model["mode"] == "chat" + + # Clean up after test + if test_model_name in litellm.model_cost: + del litellm.model_cost[test_model_name] + _invalidate_model_cost_lowercase_map() def test_reasoning_content_preserved_in_text_completion_wrapper(): @@ -2543,6 +2566,48 @@ def test_model_info_for_vertex_ai_deepseek_model(): print("vertex deepseek model info", model_info) +def test_model_info_for_openrouter_kimi_k2_5(): + """ + Test that openrouter/moonshotai/kimi-k2.5 model info is correctly configured + in model_prices_and_context_window.json. + + Model properties from OpenRouter API: + - context_length: 262144 + - pricing: prompt=$0.0000006, completion=$0.000003, input_cache_read=$0.0000001 + - modality: text+image->text (supports vision) + - supports: tool_choice, tools (function calling) + """ + import json + from pathlib import Path + + # Load directly from the local JSON file + json_path = Path(__file__).parents[2] / "model_prices_and_context_window.json" + with open(json_path) as f: + model_cost = json.load(f) + + model_info = model_cost.get("openrouter/moonshotai/kimi-k2.5") + assert model_info is not None, "Model not found in model_prices_and_context_window.json" + assert model_info["litellm_provider"] == "openrouter" + assert model_info["mode"] == "chat" + + # Verify context window + assert model_info["max_input_tokens"] == 262144 + assert model_info["max_output_tokens"] == 262144 + assert model_info["max_tokens"] == 262144 + + # Verify pricing + assert model_info["input_cost_per_token"] == 6e-07 + assert model_info["output_cost_per_token"] == 3e-06 + assert model_info["cache_read_input_token_cost"] == 1e-07 + + # Verify capabilities + assert model_info["supports_vision"] is True + assert model_info["supports_function_calling"] is True + assert model_info["supports_tool_choice"] is True + + print("openrouter kimi-k2.5 model info", model_info) + + class TestGetValidModelsWithCLI: """Test get_valid_models function as used in CLI token usage""" diff --git a/tests/test_litellm/test_video_generation.py b/tests/test_litellm/test_video_generation.py index 73bfa71d20b..cfc1535052c 100644 --- a/tests/test_litellm/test_video_generation.py +++ b/tests/test_litellm/test_video_generation.py @@ -204,7 +204,7 @@ class TestVideoGeneration: """Test video generation cost calculation.""" import json import os - + # Try to load the local model cost map, skip if not found cost_map_path = "model_prices_and_context_window.json" if not os.path.exists(cost_map_path): @@ -294,39 +294,47 @@ class TestVideoGeneration: with patch.object(config, 'transform_video_create_request') as mock_transform: mock_transform.return_value = ({"model": "sora-2", "prompt": "test"}, [], "https://api.openai.com/v1/videos") - mock_response = MagicMock() - mock_response.json.return_value = { - "id": "video_123", - "object": "video", - "status": "queued", - "created_at": 1712697600, - "model": "sora-2" - } - mock_response.status_code = 200 - - mock_client = MagicMock() - mock_client.post.return_value = mock_response - - with patch( - "litellm.llms.custom_httpx.llm_http_handler._get_httpx_client", - return_value=mock_client, - ): - handler.video_generation_handler( - model="sora-2", - prompt="test prompt", - video_generation_provider_config=config, - video_generation_optional_request_params={}, - custom_llm_provider="openai", - litellm_params={"api_key": "deployment-api-key", "api_base": "https://api.openai.com/v1"}, - logging_obj=MagicMock(), - timeout=5.0, - api_key=None, # Function parameter is None - _is_async=False, - ) - - # Verify validate_environment was called with api_key from litellm_params - mock_validate.assert_called_once() - call_args = mock_validate.call_args + # Mock the transform_video_create_response to avoid needing a real response + with patch.object(config, 'transform_video_create_response') as mock_transform_response: + mock_video_object = MagicMock() + mock_video_object.id = "video_123" + mock_video_object.object = "video" + mock_video_object.status = "queued" + mock_transform_response.return_value = mock_video_object + + mock_response = MagicMock() + mock_response.json.return_value = { + "id": "video_123", + "object": "video", + "status": "queued", + "created_at": 1712697600, + "model": "sora-2" + } + mock_response.status_code = 200 + + mock_client = MagicMock() + mock_client.post.return_value = mock_response + + with patch( + "litellm.llms.custom_httpx.llm_http_handler._get_httpx_client", + return_value=mock_client, + ): + result = handler.video_generation_handler( + model="sora-2", + prompt="test prompt", + video_generation_provider_config=config, + video_generation_optional_request_params={}, + custom_llm_provider="openai", + litellm_params={"api_key": "deployment-api-key", "api_base": "https://api.openai.com/v1"}, + logging_obj=MagicMock(), + timeout=5.0, + api_key=None, # Function parameter is None + _is_async=False, + ) + + # Verify validate_environment was called with api_key from litellm_params + mock_validate.assert_called_once() + call_args = mock_validate.call_args assert call_args.kwargs["api_key"] == "deployment-api-key" def test_video_generation_url_generation(self): @@ -824,7 +832,7 @@ def test_video_content_handler_uses_get_for_openai(): def test_video_content_respects_api_base_and_api_key_from_kwargs(): """Test that video_content respects api_base and api_key from kwargs (simulating database entry).""" from litellm.videos.main import video_content - + # Mock the handler to capture litellm_params captured_litellm_params = None @@ -916,10 +924,16 @@ class TestVideoEndpointsProxyLitellmParams: """Create a test client with a proxy config that includes Vertex AI model with litellm_params.""" import asyncio import tempfile + import yaml from fastapi import FastAPI from fastapi.testclient import TestClient - from litellm.proxy.proxy_server import cleanup_router_config_variables, router, initialize + + from litellm.proxy.proxy_server import ( + cleanup_router_config_variables, + initialize, + router, + ) from litellm.proxy.video_endpoints.endpoints import router as video_router # Clean up any existing router config diff --git a/tests/test_litellm/test_xai_responses_auto_routing.py b/tests/test_litellm/test_xai_responses_auto_routing.py new file mode 100644 index 00000000000..68c22d75f46 --- /dev/null +++ b/tests/test_litellm/test_xai_responses_auto_routing.py @@ -0,0 +1,264 @@ +""" +Test automatic routing to xAI Responses API when tools are present +""" +import os +import sys +from unittest.mock import MagicMock, patch + +sys.path.insert(0, os.path.abspath("../..")) + +import pytest +import litellm +from litellm.main import responses_api_bridge_check + + +class TestXAIResponsesAutoRouting: + """Test that xAI requests with tools automatically route to Responses API""" + + def test_responses_api_bridge_check_without_tools(self): + """Test that without tools, xAI uses chat mode""" + model = "grok-3" + custom_llm_provider = "xai" + tools = None + web_search_options = None + + model_info, updated_model = responses_api_bridge_check( + model=model, + custom_llm_provider=custom_llm_provider, + web_search_options=web_search_options, + ) + + # Should not auto-route to responses mode without tools + assert model_info.get("mode") != "responses" + assert updated_model == model + + def test_responses_api_bridge_check_with_tools(self): + """Test that with tools, xAI automatically routes to Responses API""" + model = "grok-3" + custom_llm_provider = "xai" + tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the weather", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string"} + } + } + } + } + ] + web_search_options = None + + model_info, updated_model = responses_api_bridge_check( + model=model, + custom_llm_provider=custom_llm_provider, + web_search_options=web_search_options, + ) + + # Should auto-route to responses mode when tools are present + assert model_info.get("mode") == "chat" + assert updated_model == model + + def test_responses_api_bridge_check_with_empty_tools(self): + """Test that with empty tools list, xAI does not route to Responses API""" + model = "grok-3" + custom_llm_provider = "xai" + tools = [] + web_search_options = None + + model_info, updated_model = responses_api_bridge_check( + model=model, + custom_llm_provider=custom_llm_provider, + web_search_options=web_search_options, + ) + + # Should not auto-route with empty tools list + assert model_info.get("mode") != "responses" + assert updated_model == model + + def test_responses_api_bridge_check_non_xai_provider_with_tools(self): + """Test that non-xAI providers don't get auto-routed""" + model = "gpt-4" + custom_llm_provider = "openai" + tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the weather", + } + } + ] + web_search_options = None + + model_info, updated_model = responses_api_bridge_check( + model=model, + custom_llm_provider=custom_llm_provider, + web_search_options=web_search_options, + ) + + # Should not auto-route non-xAI providers + assert model_info.get("mode") != "responses" + assert updated_model == model + + def test_responses_api_bridge_check_with_responses_prefix(self): + """Test that responses/ prefix still works""" + model = "responses/grok-3" + custom_llm_provider = "xai" + tools = None + web_search_options = None + + model_info, updated_model = responses_api_bridge_check( + model=model, + custom_llm_provider=custom_llm_provider, + web_search_options=web_search_options, + ) + + # Should route to responses mode with prefix, even without tools + assert model_info.get("mode") == "responses" + assert updated_model == "grok-3" # prefix removed + + def test_responses_api_bridge_check_with_code_interpreter_tool(self): + """Test auto-routing with code_interpreter tool""" + model = "grok-3" + custom_llm_provider = "xai" + tools = [{"type": "code_interpreter"}] + web_search_options = None + + model_info, updated_model = responses_api_bridge_check( + model=model, + custom_llm_provider=custom_llm_provider, + web_search_options=web_search_options, + ) + # Should auto-route with code_interpreter tool + assert model_info.get("mode") == "chat" + assert updated_model == model + + def test_responses_api_bridge_check_with_web_search_tool(self): + """Test auto-routing with web_search tool""" + model = "grok-4" + custom_llm_provider = "xai" + tools = [ + { + "type": "web_search", + "filters": { + "allowed_domains": ["wikipedia.org"] + } + } + ] + web_search_options = None + + model_info, updated_model = responses_api_bridge_check( + model=model, + custom_llm_provider=custom_llm_provider, + web_search_options=web_search_options, + ) + + # Should auto-route with web_search tool + assert model_info.get("mode") == "chat" + assert updated_model == model + + def test_responses_api_bridge_check_with_x_search_tool(self): + """Test auto-routing with x_search tool""" + model = "grok-4" + custom_llm_provider = "xai" + tools = [ + { + "type": "x_search", + "allowed_x_handles": ["@elonmusk"] + } + ] + web_search_options = None + + model_info, updated_model = responses_api_bridge_check( + model=model, + custom_llm_provider=custom_llm_provider, + web_search_options=web_search_options, + ) + + # Should auto-route with x_search tool + assert model_info.get("mode") == "chat" + assert updated_model == model + + def test_responses_api_bridge_check_with_web_search_options(self): + """Test auto-routing with web_search_options""" + model = "grok-4-1-fast" + custom_llm_provider = "xai" + tools = None + web_search_options = {} # Empty dict should trigger routing + + model_info, updated_model = responses_api_bridge_check( + model=model, + custom_llm_provider=custom_llm_provider, + web_search_options=web_search_options, + ) + + # Should auto-route with web_search_options + assert model_info.get("mode") == "responses" + assert updated_model == model + + def test_responses_api_bridge_check_with_web_search_options_and_tools(self): + """Test auto-routing with both web_search_options and tools""" + model = "grok-4" + custom_llm_provider = "xai" + tools = [{"type": "code_interpreter"}] + web_search_options = {"enabled": True} + + model_info, updated_model = responses_api_bridge_check( + model=model, + custom_llm_provider=custom_llm_provider, + web_search_options=web_search_options, + ) + + # Should auto-route with both present + assert model_info.get("mode") == "responses" + assert updated_model == model + + @patch("litellm.completion_extras.responses_api_bridge.completion") + def test_completion_with_tools_routes_to_responses_api( + self, mock_responses_completion + ): + """Test that completion() with tools routes to Responses API""" + # Mock the responses_api_bridge.completion to avoid actual API calls + mock_responses_completion.return_value = MagicMock() + + model = "xai/grok-3" + messages = [{"role": "user", "content": "What's the weather?"}] + tools = [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get weather info", + "parameters": { + "type": "object", + "properties": { + "location": {"type": "string"} + } + } + } + } + ] + + try: + litellm.completion( + model=model, + messages=messages, + tools=tools, + mock_response="This is a test" # Use mock mode to avoid API calls + ) + except Exception: + # It's ok if this fails, we just want to verify the routing logic + pass + + # The mock should have been called, indicating responses API was used + # Note: This test may need adjustment based on actual mock_response behavior + # The key is that the responses_api_bridge_check logic routes correctly + + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) diff --git a/tests/test_litellm/vector_stores/test_vector_store_create_provider_logic.py b/tests/test_litellm/vector_stores/test_vector_store_create_provider_logic.py index cca20847f12..08da9b9807f 100644 --- a/tests/test_litellm/vector_stores/test_vector_store_create_provider_logic.py +++ b/tests/test_litellm/vector_stores/test_vector_store_create_provider_logic.py @@ -51,9 +51,10 @@ def test_vector_store_create_with_simple_provider_name(): ) assert vector_store_provider_config is not None, "Should return a config for OpenAI" - assert isinstance( - vector_store_provider_config, OpenAIVectorStoreConfig - ), "Should return OpenAIVectorStoreConfig for OpenAI provider" + # Use type name check instead of isinstance to avoid module identity issues + # caused by sys.path manipulation in test setup + assert type(vector_store_provider_config).__name__ == "OpenAIVectorStoreConfig", \ + f"Should return OpenAIVectorStoreConfig for OpenAI provider, got {type(vector_store_provider_config).__name__}" print("✅ Test passed: Simple provider name 'openai' handled correctly") @@ -97,9 +98,9 @@ def test_vector_store_create_with_provider_api_type(): ) assert vector_store_provider_config is not None, "Should return a config for Vertex AI" - assert isinstance( - vector_store_provider_config, VertexVectorStoreConfig - ), "Should return VertexVectorStoreConfig for vertex_ai provider with rag_api" + # Use type name check instead of isinstance to avoid module identity issues + assert type(vector_store_provider_config).__name__ == "VertexVectorStoreConfig", \ + f"Should return VertexVectorStoreConfig for vertex_ai provider with rag_api, got {type(vector_store_provider_config).__name__}" print("✅ Test passed: Provider with api_type 'vertex_ai/rag_api' handled correctly") @@ -134,9 +135,9 @@ def test_vector_store_create_with_ragflow_provider(): ) assert vector_store_provider_config is not None, "Should return a config for RAGFlow" - assert isinstance( - vector_store_provider_config, RAGFlowVectorStoreConfig - ), "Should return RAGFlowVectorStoreConfig for RAGFlow provider" + # Use type name check instead of isinstance to avoid module identity issues + assert type(vector_store_provider_config).__name__ == "RAGFlowVectorStoreConfig", \ + f"Should return RAGFlowVectorStoreConfig for RAGFlow provider, got {type(vector_store_provider_config).__name__}" print("✅ Test passed: RAGFlow provider handled correctly") diff --git a/tests/test_litellm/vector_stores/test_vector_store_registry.py b/tests/test_litellm/vector_stores/test_vector_store_registry.py index fb585e11220..ef8afe31c65 100644 --- a/tests/test_litellm/vector_stores/test_vector_store_registry.py +++ b/tests/test_litellm/vector_stores/test_vector_store_registry.py @@ -21,6 +21,20 @@ from litellm.vector_stores.main import search from litellm.vector_stores.vector_store_registry import VectorStoreRegistry +@pytest.fixture(autouse=True) +def clear_client_cache(): + """ + Clear the HTTP client cache before each test to ensure mocks are used. + This prevents cached real clients from being reused across tests. + """ + cache = getattr(litellm, "in_memory_llm_clients_cache", None) + if cache is not None: + cache.flush_cache() + yield + if cache is not None: + cache.flush_cache() + + def test_get_credentials_for_vector_store(): """Test that get_credentials_for_vector_store returns correct credentials""" # Create test vector stores @@ -121,6 +135,9 @@ def test_add_vector_store_to_registry(): def test_search_uses_registry_credentials(): """search() should pull credentials from vector_store_registry when available""" + # Import the module to get the actual handler instance + import litellm.vector_stores.main as vector_stores_main + vector_store = LiteLLM_ManagedVectorStore( vector_store_id="vs1", custom_llm_provider="bedrock", @@ -133,6 +150,16 @@ def test_search_uses_registry_credentials(): try: logger = MagicMock() logger._response_cost_calculator.return_value = 0 + + # Mock the search response + mock_search_response = { + "object": "list", + "data": [], + "first_id": None, + "last_id": None, + "has_more": False + } + with patch.object( registry, "get_credentials_for_vector_store", @@ -140,9 +167,10 @@ def test_search_uses_registry_credentials(): ) as mock_get_creds, patch( "litellm.vector_stores.main.ProviderConfigManager.get_provider_vector_stores_config", return_value=MagicMock(), - ), patch( - "litellm.vector_stores.main.base_llm_http_handler.vector_store_search_handler", - return_value={}, + ), patch.object( + vector_stores_main.base_llm_http_handler, + "vector_store_search_handler", + return_value=mock_search_response, ) as mock_handler: search(vector_store_id="vs1", query="test", litellm_logging_obj=logger) mock_get_creds.assert_called_once_with("vs1") diff --git a/tests/test_otel_thread_leak.py b/tests/test_otel_thread_leak.py new file mode 100644 index 00000000000..34f6b299caa --- /dev/null +++ b/tests/test_otel_thread_leak.py @@ -0,0 +1,90 @@ +import sys +import os +import threading +import time +import pytest + +# Add the project root to the path +sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) + +from litellm.integrations.opentelemetry import OpenTelemetry, OpenTelemetryConfig +from litellm.types.utils import StandardCallbackDynamicParams + +def get_thread_count() -> int: + """Helper to get active thread count""" + return threading.active_count() + +@pytest.fixture +def otel_logger(): + """Fixture to provide a clean OTEL logger for each test""" + config = OpenTelemetryConfig( + exporter="console", + enable_metrics=False, + service_name="litellm-unit-test" + ) + return OpenTelemetry(config=config) + +def test_otel_thread_leak_dynamic_headers(otel_logger): + """ + Unit test to verify that calling get_tracer_to_use_for_request with + dynamic headers doesn't cause a linear thread leak. + + This test reproduces the issue where each unique team/key credential + set causes a new TracerProvider (and its background threads) to be + spawned but never closed. + """ + + # 1. Setup dynamic header simulation (monkey-patch) + # This simulates what LangfuseOtelLogger does for per-team keys + def mock_construct_dynamic_headers(standard_callback_dynamic_params): + if standard_callback_dynamic_params: + return {"Authorization": "Bearer fake_token"} + return None + + otel_logger.construct_dynamic_otel_headers = mock_construct_dynamic_headers + + # 2. Establish Baseline + initial_threads = get_thread_count() + + # 3. Simulate requests + num_requests = 10 + latencies = [] + + print("\n🚀 Simulating requests with dynamic headers:") + for i in range(num_requests): + kwargs = { + "standard_callback_dynamic_params": StandardCallbackDynamicParams( + langfuse_public_key=f"key_{i}", + langfuse_secret_key=f"secret_{i}", + ) + } + + # Measure latency + start_time = time.perf_counter() + tracer = otel_logger.get_tracer_to_use_for_request(kwargs) + end_time = time.perf_counter() + + latency_ms = (end_time - start_time) * 1000 + latencies.append(latency_ms) + print(f" Request {i+1:2d}: Latency = {latency_ms:6.2f} ms") + + # Verify a tracer was actually returned + assert tracer is not None + + avg_latency = sum(latencies) / len(latencies) + print(f"\n📊 Average Latency: {avg_latency:.2f} ms") + + # 4. Check for leaks + # Allow for a small constant increase (OTEL might start a few shared threads) + # but a linear leak would result in +10 or more threads here. + final_threads = get_thread_count() + thread_delta = final_threads - initial_threads + + print(f"\nThread growth: {thread_delta} threads across {num_requests} requests") + + # ASSERTION: The growth should be significantly less than 1 thread per request. + # If the bug exists, thread_delta will be >= num_requests. + assert thread_delta < (num_requests / 2), ( + f"Thread leak detected! Threads grew by {thread_delta} over {num_requests} requests. " + "Each request with dynamic headers appears to be leaking background threads." + ) diff --git a/tests/test_presidio_latency.py b/tests/test_presidio_latency.py new file mode 100644 index 00000000000..d434e6222eb --- /dev/null +++ b/tests/test_presidio_latency.py @@ -0,0 +1,73 @@ + +import asyncio +import aiohttp +import pytest +from unittest.mock import MagicMock, patch +from litellm.proxy.guardrails.guardrail_hooks.presidio import _OPTIONAL_PresidioPIIMasking + +@pytest.mark.asyncio +async def test_sanity_presidio_session_reuse_main_thread(): + """ + SANITY CHECK: + Verify that Presidio guardrail reuses sessions in the main thread. + This ensures we don't break existing session pooling functionality. + """ + presidio = _OPTIONAL_PresidioPIIMasking( + mock_testing=True, + presidio_analyzer_api_base="http://mock-analyzer", + presidio_anonymizer_api_base="http://mock-anonymizer" + ) + + session_creations = 0 + original_init = aiohttp.ClientSession.__init__ + + def mocked_init(self, *args, **kwargs): + nonlocal session_creations + session_creations += 1 + original_init(self, *args, **kwargs) + + with patch.object(aiohttp.ClientSession, "__init__", side_effect=mocked_init, autospec=True): + for _ in range(10): + async with presidio._get_session_iterator() as session: + pass + + # Expected: Only 1 session created for all 10 calls. + assert session_creations == 1 + + await presidio._close_http_session() + +@pytest.mark.asyncio +async def test_bug_presidio_session_explosion_background_thread_causes_latency(): + """ + BUG REPRODUCTION: + Verify that background threads (like logging hooks) REUSE sessions. + Previously, each call in a background loop created a NEW ephemeral session, + leading to socket exhaustion and the reported 97s latency spike. + """ + import threading + presidio = _OPTIONAL_PresidioPIIMasking( + mock_testing=True, + presidio_analyzer_api_base="http://mock-analyzer", + presidio_anonymizer_api_base="http://mock-anonymizer" + ) + + # Force the code to think it's in a background thread + presidio._main_thread_id = threading.get_ident() + 1 + + session_creations = 0 + original_init = aiohttp.ClientSession.__init__ + + def mocked_init(self, *args, **kwargs): + nonlocal session_creations + session_creations += 1 + original_init(self, *args, **kwargs) + + with patch.object(aiohttp.ClientSession, "__init__", side_effect=mocked_init, autospec=True): + for _ in range(10): + async with presidio._get_session_iterator() as session: + pass + + # FIX VERIFICATION: Should now be 1 session (reused) instead of 10. + assert session_creations == 1 + + await presidio._close_http_session() \ No newline at end of file diff --git a/tests/test_team.py b/tests/test_team.py index f7752f9c89b..d67c5e670f4 100644 --- a/tests/test_team.py +++ b/tests/test_team.py @@ -531,6 +531,7 @@ async def test_team_update_sc_2(): or k == "object_permission_id" or k == "object_permission" or k == "litellm_model_table" + or k == "policies" ): pass else: diff --git a/tests/vector_store_tests/rag/test_rag_s3_vectors.py b/tests/vector_store_tests/rag/test_rag_s3_vectors.py new file mode 100644 index 00000000000..cd8a362a7bf --- /dev/null +++ b/tests/vector_store_tests/rag/test_rag_s3_vectors.py @@ -0,0 +1,107 @@ +""" +S3 Vectors RAG ingestion tests. + +Requires environment variables: +- AWS_ACCESS_KEY_ID +- AWS_SECRET_ACCESS_KEY +- AWS_REGION_NAME (optional, defaults to us-west-2) + +Optional: +- S3_VECTOR_BUCKET_NAME (optional, auto-generates if not set) +""" + +import os +import sys +from typing import Any, Dict, Optional + +import pytest + +sys.path.insert(0, os.path.abspath("../../..")) + +import litellm +from litellm.types.rag import RAGIngestOptions +from tests.vector_store_tests.rag.base_rag_tests import BaseRAGTest + + +class TestRAGS3Vectors(BaseRAGTest): + """Test RAG Ingest with AWS S3 Vectors.""" + + @pytest.fixture(autouse=True) + def check_env_vars(self): + """Check required environment variables before each test.""" + aws_key = os.environ.get("AWS_ACCESS_KEY_ID") + aws_secret = os.environ.get("AWS_SECRET_ACCESS_KEY") + + if not aws_key or not aws_secret: + pytest.skip("Skipping S3 Vectors test: AWS credentials required") + + def get_base_ingest_options(self) -> RAGIngestOptions: + """ + Return S3 Vectors-specific ingest options. + + Chunking is configured via chunking_strategy (unified interface). + Embeddings are generated using LiteLLM's embedding API. + """ + vector_bucket_name = os.environ.get( + "S3_VECTOR_BUCKET_NAME", "test-litellm-vectors" + ) + aws_region = os.environ.get("AWS_REGION_NAME", "us-west-2") + + return { + "chunking_strategy": { + "chunk_size": 512, + "chunk_overlap": 100, + }, + "embedding": { + "model": "text-embedding-3-small" # Can use any LiteLLM-supported model + }, + "vector_store": { + "custom_llm_provider": "s3_vectors", + "vector_bucket_name": vector_bucket_name, + "index_name": "test-index", + # dimension is auto-detected from embedding model (text-embedding-3-small = 1536) + "distance_metric": "cosine", + "non_filterable_metadata_keys": ["source_text"], + "aws_region_name": aws_region, + }, + } + + async def query_vector_store( + self, + vector_store_id: str, + query: str, + ) -> Optional[Dict[str, Any]]: + """Query S3 Vectors index.""" + try: + # Import the ingestion class to use its query method + from litellm.rag.ingestion.s3_vectors_ingestion import ( + S3VectorsRAGIngestion, + ) + except ImportError: + pytest.skip("S3 Vectors ingestion not available") + + vector_bucket_name = os.environ.get( + "S3_VECTOR_BUCKET_NAME", "test-litellm-vectors" + ) + aws_region = os.environ.get("AWS_REGION_NAME", "us-west-2") + + # Create ingestion instance to use query method + ingest_options = { + "embedding": {"model": "text-embedding-3-small"}, + "vector_store": { + "custom_llm_provider": "s3_vectors", + "vector_bucket_name": vector_bucket_name, + "aws_region_name": aws_region, + }, + } + + ingestion = S3VectorsRAGIngestion(ingest_options=ingest_options) + + # Query the index + results = await ingestion.query_vector_store( + vector_store_id=vector_store_id, + query=query, + top_k=5, + ) + + return results diff --git a/tests/vector_store_tests/test_s3_vectors_vector_store.py b/tests/vector_store_tests/test_s3_vectors_vector_store.py new file mode 100644 index 00000000000..a7a1568c1cc --- /dev/null +++ b/tests/vector_store_tests/test_s3_vectors_vector_store.py @@ -0,0 +1,42 @@ +from base_vector_store_test import BaseVectorStoreTest +import os +import pytest + + +class TestS3VectorsVectorStore(BaseVectorStoreTest): + @pytest.fixture(autouse=True) + def check_env_vars(self): + """Check if required environment variables are set""" + required_vars = ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"] + missing_vars = [var for var in required_vars if not os.getenv(var)] + if missing_vars: + pytest.skip(f"Missing required environment variables: {', '.join(missing_vars)}") + + def get_base_request_args(self) -> dict: + """ + Must return the base request args for searching. + For S3 Vectors, vector_store_id should be in format: bucket_name:index_name + """ + return { + "custom_llm_provider": "s3_vectors", + "vector_store_id": os.getenv( + "S3_VECTORS_VECTOR_STORE_ID", "test-litellm-vectors:test-index" + ), + "query": "What is machine learning?", + "aws_region_name": os.getenv("AWS_REGION_NAME", "us-west-2"), + "aws_access_key_id": os.getenv("AWS_ACCESS_KEY_ID"), + "aws_secret_access_key": os.getenv("AWS_SECRET_ACCESS_KEY"), + } + + def get_base_create_vector_store_args(self) -> dict: + """ + Vector store creation is not yet implemented for S3 Vectors. + This test will be skipped. + """ + return {} + + @pytest.mark.parametrize("sync_mode", [True, False]) + @pytest.mark.asyncio + async def test_basic_create_vector_store(self, sync_mode): + """S3 Vectors doesn't support vector store creation via this API yet""" + pytest.skip("Vector store creation not yet implemented for S3 Vectors") diff --git a/ui/litellm-dashboard/next.config.mjs b/ui/litellm-dashboard/next.config.mjs index f3083c5e802..bdf492de332 100644 --- a/ui/litellm-dashboard/next.config.mjs +++ b/ui/litellm-dashboard/next.config.mjs @@ -1,12 +1,18 @@ +import path from "path"; +import { fileURLToPath } from "url"; + /** @type {import('next').NextConfig} */ +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + const nextConfig = { output: "export", basePath: "", - assetPrefix: "/litellm-asset-prefix", // If a server_root_path is set, this will be overridden by runtime injection -}; - -nextConfig.experimental = { - missingSuspenseWithCSRBailout: false, + assetPrefix: "/litellm-asset-prefix", + turbopack: { + // Must be absolute; "." is no longer allowed + root: __dirname, + }, }; export default nextConfig; diff --git a/ui/litellm-dashboard/package-lock.json b/ui/litellm-dashboard/package-lock.json index ee657ebe18f..33d8ea54b30 100644 --- a/ui/litellm-dashboard/package-lock.json +++ b/ui/litellm-dashboard/package-lock.json @@ -9,8 +9,6 @@ "version": "0.1.0", "dependencies": { "@anthropic-ai/sdk": "^0.54.0", - "@docusaurus/theme-mermaid": "^3.9.0", - "@headlessui/react": "^1.7.18", "@headlessui/tailwindcss": "^0.2.0", "@heroicons/react": "^1.0.6", "@remixicon/react": "^4.1.1", @@ -21,17 +19,15 @@ "@types/papaparse": "^5.3.15", "antd": "^5.13.2", "cva": "^1.0.0-beta.3", - "fs": "^0.0.1-security", - "jsonwebtoken": "^9.0.2", "jwt-decode": "^4.0.0", "lucide-react": "^0.513.0", "moment": "^2.30.1", - "next": "^14.2.32", + "next": "^16.1.6", "openai": "^4.93.0", "papaparse": "^5.5.2", - "react": "^18", + "react": "^18.3.1", "react-copy-to-clipboard": "^5.1.0", - "react-dom": "^18", + "react-dom": "^18.3.1", "react-json-view-lite": "^2.5.0", "react-markdown": "^9.0.1", "react-syntax-highlighter": "^15.6.6", @@ -54,13 +50,12 @@ "@types/react-dom": "^18", "@types/react-syntax-highlighter": "^15.5.11", "@types/uuid": "^10.0.0", - "@vitejs/plugin-react": "^5.0.4", "@vitest/coverage-v8": "^3.2.4", "@vitest/ui": "^3.2.4", "autoprefixer": "^10.4.17", "dotenv": "^17.2.3", - "eslint": "^8", - "eslint-config-next": "14.2.32", + "eslint": "^9.39.2", + "eslint-config-next": "15.5.10", "eslint-config-prettier": "^10.1.8", "eslint-plugin-unused-imports": "^4.2.0", "jsdom": "^27.0.0", @@ -77,9 +72,9 @@ } }, "node_modules/@acemir/cssom": { - "version": "0.9.24", - "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.24.tgz", - "integrity": "sha512-5YjgMmAiT2rjJZU7XK1SNI7iqTy92DpaYVgG6x63FxkJ11UpYfLndHJATtinWJClAXiOlW9XWaUyAQf8pMrQPg==", + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", "dev": true, "license": "MIT" }, @@ -214,28 +209,6 @@ "react": ">=16.9.0" } }, - "node_modules/@antfu/install-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", - "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", - "license": "MIT", - "dependencies": { - "package-manager-detector": "^1.3.0", - "tinyexec": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@antfu/utils": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-9.3.0.tgz", - "integrity": "sha512-9hFT4RauhcUzqOE4f1+frMKLZrgNog5b06I7VmZQV1BkvwvqrbC8EBZf3L1eEL2AKb6rNKjER0sEvJiSP1FXEA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/@anthropic-ai/sdk": { "version": "0.54.0", "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.54.0.tgz", @@ -246,9 +219,9 @@ } }, "node_modules/@asamuzakjp/css-color": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.0.tgz", - "integrity": "sha512-9xiBAtLn4aNsa4mDnpovJvBn72tNEIACyvlqaNJ+ADemR+yeMJWnBudOi2qGDviJa7SwcDOU/TRh5dnET7qk0w==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.1.tgz", + "integrity": "sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==", "dev": true, "license": "MIT", "dependencies": { @@ -256,23 +229,13 @@ "@csstools/css-color-parser": "^3.1.0", "@csstools/css-parser-algorithms": "^3.0.5", "@csstools/css-tokenizer": "^3.0.4", - "lru-cache": "^11.2.2" - } - }, - "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" + "lru-cache": "^11.2.4" } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "6.7.4", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.4.tgz", - "integrity": "sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==", + "version": "6.7.7", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.7.tgz", + "integrity": "sha512-8CO/UQ4tzDd7ula+/CVimJIVWez99UJlbMyIgk8xOnhAVPKLnBZmUFYVgugS441v2ZqUq5EnSh6B0Ua0liSFAA==", "dev": true, "license": "MIT", "dependencies": { @@ -280,17 +243,7 @@ "bidi-js": "^1.0.3", "css-tree": "^3.1.0", "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.2" - } - }, - "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" + "lru-cache": "^11.2.5" } }, "node_modules/@asamuzakjp/nwsapi": { @@ -301,12 +254,13 @@ "license": "MIT" }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -314,303 +268,11 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", - "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.28.5", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.5", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", - "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "regexpu-core": "^6.3.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", - "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "debug": "^4.4.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.22.10" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", - "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", - "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-wrap-function": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -620,54 +282,20 @@ "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", - "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.3", - "@babel/types": "^7.28.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.5" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -676,1338 +304,20 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", - "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", - "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", - "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", - "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", - "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", - "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", - "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", - "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", - "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", - "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.28.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", - "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-remap-async-to-generator": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", - "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz", - "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", - "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", - "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", - "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-globals": "^7.28.0", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", - "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/template": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", - "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", - "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", - "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", - "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", - "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz", - "integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", - "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", - "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", - "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", - "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", - "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz", - "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", - "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", - "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", - "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", - "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", - "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", - "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", - "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", - "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", - "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/traverse": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", - "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", - "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz", - "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", - "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", - "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", - "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", - "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz", - "integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", - "integrity": "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-development": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz", - "integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==", - "license": "MIT", - "dependencies": { - "@babel/plugin-transform-react-jsx": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-pure-annotations": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz", - "integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", - "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", - "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", - "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.5.tgz", - "integrity": "sha512-20NUVgOrinudkIBzQ2bNxP08YpKprUkRTiRSd2/Z5GOdPImJGkoN4Z7IQe1T5AdyKI1i5L6RBmluqdSzvaq9/w==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", - "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", - "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", - "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", - "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", - "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.5.tgz", - "integrity": "sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==", - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-create-class-features-plugin": "^7.28.5", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", - "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", - "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", - "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", - "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", - "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz", - "integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.27.1", - "@babel/plugin-syntax-import-attributes": "^7.27.1", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.28.0", - "@babel/plugin-transform-async-to-generator": "^7.27.1", - "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.5", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.28.3", - "@babel/plugin-transform-classes": "^7.28.4", - "@babel/plugin-transform-computed-properties": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.5", - "@babel/plugin-transform-dotall-regex": "^7.27.1", - "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-explicit-resource-management": "^7.28.0", - "@babel/plugin-transform-exponentiation-operator": "^7.28.5", - "@babel/plugin-transform-export-namespace-from": "^7.27.1", - "@babel/plugin-transform-for-of": "^7.27.1", - "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.27.1", - "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.28.5", - "@babel/plugin-transform-member-expression-literals": "^7.27.1", - "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-modules-systemjs": "^7.28.5", - "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", - "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.28.4", - "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.28.5", - "@babel/plugin-transform-parameters": "^7.27.7", - "@babel/plugin-transform-private-methods": "^7.27.1", - "@babel/plugin-transform-private-property-in-object": "^7.27.1", - "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.4", - "@babel/plugin-transform-regexp-modifiers": "^7.27.1", - "@babel/plugin-transform-reserved-words": "^7.27.1", - "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.27.1", - "@babel/plugin-transform-sticky-regex": "^7.27.1", - "@babel/plugin-transform-template-literals": "^7.27.1", - "@babel/plugin-transform-typeof-symbol": "^7.27.1", - "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.27.1", - "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "core-js-compat": "^3.43.0", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", - "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/preset-react": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.28.5.tgz", - "integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-transform-react-display-name": "^7.28.0", - "@babel/plugin-transform-react-jsx": "^7.27.1", - "@babel/plugin-transform-react-jsx-development": "^7.27.1", - "@babel/plugin-transform-react-pure-annotations": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", - "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/runtime": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", - "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/runtime-corejs3": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.28.4.tgz", - "integrity": "sha512-h7iEYiW4HebClDEhtvFObtPmIvrd1SSfpI9EhOeKk4CtIK/ngBWFpuhCzhdmRKtg71ylcue+9I6dv54XYO1epQ==", - "license": "MIT", - "dependencies": { - "core-js-pure": "^3.43.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -2027,88 +337,11 @@ "node": ">=18" } }, - "node_modules/@braintree/sanitize-url": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.1.tgz", - "integrity": "sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==", - "license": "MIT" - }, - "node_modules/@chevrotain/cst-dts-gen": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", - "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/gast": "11.0.3", - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/@chevrotain/gast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", - "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/types": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/@chevrotain/regexp-to-ast": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", - "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/types": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", - "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", - "license": "Apache-2.0" - }, - "node_modules/@chevrotain/utils": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", - "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", - "license": "Apache-2.0" - }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/@csstools/cascade-layer-name-parser": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.5.tgz", - "integrity": "sha512-p1ko5eHgV+MgXFVa4STPKpvPxr6ReS8oS2jzTukjR74i5zJNyWO1ZM1m8YKBXnzDKWfBN1ztLYlHxbVemDD88A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, "funding": [ { "type": "github", @@ -2128,6 +361,7 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, "funding": [ { "type": "github", @@ -2151,6 +385,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, "funding": [ { "type": "github", @@ -2178,6 +413,7 @@ "version": "3.0.5", "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, "funding": [ { "type": "github", @@ -2197,9 +433,9 @@ } }, "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.17.tgz", - "integrity": "sha512-LCC++2h8pLUSPY+EsZmrrJ1EOUu+5iClpEiDhhdw3zRJpPbABML/N5lmRuBHjxtKm9VnRcsUzioyD0sekFMF0A==", + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.26.tgz", + "integrity": "sha512-6boXK0KkzT5u5xOgF6TKB+CLq9SOpEGmkZw0g5n9/7yg85wab3UzSxB8TxhLJ31L4SGJ6BCFRw/iftTha1CJXA==", "dev": true, "funding": [ { @@ -2211,15 +447,13 @@ "url": "https://opencollective.com/csstools" } ], - "license": "MIT-0", - "engines": { - "node": ">=18" - } + "license": "MIT-0" }, "node_modules/@csstools/css-tokenizer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, "funding": [ { "type": "github", @@ -2235,1475 +469,10 @@ "node": ">=18" } }, - "node_modules/@csstools/media-query-list-parser": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", - "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - } - }, - "node_modules/@csstools/postcss-alpha-function": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-alpha-function/-/postcss-alpha-function-1.0.1.tgz", - "integrity": "sha512-isfLLwksH3yHkFXfCI2Gcaqg7wGGHZZwunoJzEZk0yKYIokgre6hYVFibKL3SYAoR1kBXova8LB+JoO5vZzi9w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-cascade-layers": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-cascade-layers/-/postcss-cascade-layers-5.0.2.tgz", - "integrity": "sha512-nWBE08nhO8uWl6kSAeCx4im7QfVko3zLrtgWZY4/bP87zrSPpSyN/3W3TDqz1jJuH+kbKOHXg5rJnK+ZVYcFFg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-cascade-layers/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/@csstools/postcss-cascade-layers/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@csstools/postcss-color-function": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.12.tgz", - "integrity": "sha512-yx3cljQKRaSBc2hfh8rMZFZzChaFgwmO2JfFgFr1vMcF3C/uyy5I4RFIBOIWGq1D+XbKCG789CGkG6zzkLpagA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-color-function-display-p3-linear": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function-display-p3-linear/-/postcss-color-function-display-p3-linear-1.0.1.tgz", - "integrity": "sha512-E5qusdzhlmO1TztYzDIi8XPdPoYOjoTY6HBYBCYSj+Gn4gQRBlvjgPQXzfzuPQqt8EhkC/SzPKObg4Mbn8/xMg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-color-mix-function": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.12.tgz", - "integrity": "sha512-4STERZfCP5Jcs13P1U5pTvI9SkgLgfMUMhdXW8IlJWkzOOOqhZIjcNhWtNJZes2nkBDsIKJ0CJtFtuaZ00moag==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-color-mix-variadic-function-arguments": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-variadic-function-arguments/-/postcss-color-mix-variadic-function-arguments-1.0.2.tgz", - "integrity": "sha512-rM67Gp9lRAkTo+X31DUqMEq+iK+EFqsidfecmhrteErxJZb6tUoJBVQca1Vn1GpDql1s1rD1pKcuYzMsg7Z1KQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-content-alt-text": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.8.tgz", - "integrity": "sha512-9SfEW9QCxEpTlNMnpSqFaHyzsiRpZ5J5+KqCu1u5/eEJAWsMhzT40qf0FIbeeglEvrGRMdDzAxMIz3wqoGSb+Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-contrast-color-function": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-contrast-color-function/-/postcss-contrast-color-function-2.0.12.tgz", - "integrity": "sha512-YbwWckjK3qwKjeYz/CijgcS7WDUCtKTd8ShLztm3/i5dhh4NaqzsbYnhm4bjrpFpnLZ31jVcbK8YL77z3GBPzA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-exponential-functions": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.9.tgz", - "integrity": "sha512-abg2W/PI3HXwS/CZshSa79kNWNZHdJPMBXeZNyPQFbbj8sKO3jXxOt/wF7juJVjyDTc6JrvaUZYFcSBZBhaxjw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-font-format-keywords": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-font-format-keywords/-/postcss-font-format-keywords-4.0.0.tgz", - "integrity": "sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-gamut-mapping": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.11.tgz", - "integrity": "sha512-fCpCUgZNE2piVJKC76zFsgVW1apF6dpYsqGyH8SIeCcM4pTEsRTWTLCaJIMKFEundsCKwY1rwfhtrio04RJ4Dw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-gradients-interpolation-method": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.12.tgz", - "integrity": "sha512-jugzjwkUY0wtNrZlFeyXzimUL3hN4xMvoPnIXxoZqxDvjZRiSh+itgHcVUWzJ2VwD/VAMEgCLvtaJHX+4Vj3Ow==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-hwb-function": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.12.tgz", - "integrity": "sha512-mL/+88Z53KrE4JdePYFJAQWFrcADEqsLprExCM04GDNgHIztwFzj0Mbhd/yxMBngq0NIlz58VVxjt5abNs1VhA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-ic-unit": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-ic-unit/-/postcss-ic-unit-4.0.4.tgz", - "integrity": "sha512-yQ4VmossuOAql65sCPppVO1yfb7hDscf4GseF0VCA/DTDaBc0Wtf8MTqVPfjGYlT5+2buokG0Gp7y0atYZpwjg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-initial": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-initial/-/postcss-initial-2.0.1.tgz", - "integrity": "sha512-L1wLVMSAZ4wovznquK0xmC7QSctzO4D0Is590bxpGqhqjboLXYA16dWZpfwImkdOgACdQ9PqXsuRroW6qPlEsg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-is-pseudo-class": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-is-pseudo-class/-/postcss-is-pseudo-class-5.0.3.tgz", - "integrity": "sha512-jS/TY4SpG4gszAtIg7Qnf3AS2pjcUM5SzxpApOrlndMeGhIbaTzWBzzP/IApXoNWEW7OhcjkRT48jnAUIFXhAQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-is-pseudo-class/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/@csstools/postcss-is-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@csstools/postcss-light-dark-function": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.11.tgz", - "integrity": "sha512-fNJcKXJdPM3Lyrbmgw2OBbaioU7yuKZtiXClf4sGdQttitijYlZMD5K7HrC/eF83VRWRrYq6OZ0Lx92leV2LFA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-float-and-clear": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-float-and-clear/-/postcss-logical-float-and-clear-3.0.0.tgz", - "integrity": "sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-overflow": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overflow/-/postcss-logical-overflow-2.0.0.tgz", - "integrity": "sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-overscroll-behavior": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-overscroll-behavior/-/postcss-logical-overscroll-behavior-2.0.0.tgz", - "integrity": "sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-resize": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-resize/-/postcss-logical-resize-3.0.0.tgz", - "integrity": "sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-logical-viewport-units": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.4.tgz", - "integrity": "sha512-q+eHV1haXA4w9xBwZLKjVKAWn3W2CMqmpNpZUk5kRprvSiBEGMgrNH3/sJZ8UA3JgyHaOt3jwT9uFa4wLX4EqQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-media-minmax": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.9.tgz", - "integrity": "sha512-af9Qw3uS3JhYLnCbqtZ9crTvvkR+0Se+bBqSr7ykAnl9yKhk6895z9rf+2F4dClIDJWxgn0iZZ1PSdkhrbs2ig==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/media-query-list-parser": "^4.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.5.tgz", - "integrity": "sha512-zhAe31xaaXOY2Px8IYfoVTB3wglbJUVigGphFLj6exb7cjZRH9A6adyE22XfFK3P2PzwRk0VDeTJmaxpluyrDg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/media-query-list-parser": "^4.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-nested-calc": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-nested-calc/-/postcss-nested-calc-4.0.0.tgz", - "integrity": "sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-normalize-display-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.0.tgz", - "integrity": "sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-oklab-function": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.12.tgz", - "integrity": "sha512-HhlSmnE1NKBhXsTnNGjxvhryKtO7tJd1w42DKOGFD6jSHtYOrsJTQDKPMwvOfrzUAk8t7GcpIfRyM7ssqHpFjg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-progressive-custom-properties": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-progressive-custom-properties/-/postcss-progressive-custom-properties-4.2.1.tgz", - "integrity": "sha512-uPiiXf7IEKtUQXsxu6uWtOlRMXd2QWWy5fhxHDnPdXKCQckPP3E34ZgDoZ62r2iT+UOgWsSbM4NvHE5m3mAEdw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-random-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-random-function/-/postcss-random-function-2.0.1.tgz", - "integrity": "sha512-q+FQaNiRBhnoSNo+GzqGOIBKoHQ43lYz0ICrV+UudfWnEF6ksS6DsBIJSISKQT2Bvu3g4k6r7t0zYrk5pDlo8w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-relative-color-syntax": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.12.tgz", - "integrity": "sha512-0RLIeONxu/mtxRtf3o41Lq2ghLimw0w9ByLWnnEVuy89exmEEq8bynveBxNW3nyHqLAFEeNtVEmC1QK9MZ8Huw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-scope-pseudo-class": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-scope-pseudo-class/-/postcss-scope-pseudo-class-4.0.1.tgz", - "integrity": "sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-scope-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@csstools/postcss-sign-functions": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@csstools/postcss-sign-functions/-/postcss-sign-functions-1.1.4.tgz", - "integrity": "sha512-P97h1XqRPcfcJndFdG95Gv/6ZzxUBBISem0IDqPZ7WMvc/wlO+yU0c5D/OCpZ5TJoTt63Ok3knGk64N+o6L2Pg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-stepped-value-functions": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.9.tgz", - "integrity": "sha512-h9btycWrsex4dNLeQfyU3y3w40LMQooJWFMm/SK9lrKguHDcFl4VMkncKKoXi2z5rM9YGWbUQABI8BT2UydIcA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-text-decoration-shorthand": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@csstools/postcss-text-decoration-shorthand/-/postcss-text-decoration-shorthand-4.0.3.tgz", - "integrity": "sha512-KSkGgZfx0kQjRIYnpsD7X2Om9BUXX/Kii77VBifQW9Ih929hK0KNjVngHDH0bFB9GmfWcR9vJYJJRvw/NQjkrA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-trigonometric-functions": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.9.tgz", - "integrity": "sha512-Hnh5zJUdpNrJqK9v1/E3BbrQhaDTj5YiX7P61TOvUhoDHnUmsNNxcDAgkQ32RrcWx9GVUvfUNPcUkn8R3vIX6A==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-calc": "^2.1.4", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/postcss-unset-value": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-unset-value/-/postcss-unset-value-4.0.0.tgz", - "integrity": "sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@csstools/utilities": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/utilities/-/utilities-2.0.0.tgz", - "integrity": "sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", - "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/babel": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/babel/-/babel-3.9.2.tgz", - "integrity": "sha512-GEANdi/SgER+L7Japs25YiGil/AUDnFFHaCGPBbundxoWtCkA2lmy7/tFmgED4y1htAy6Oi4wkJEQdGssnw9MA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-transform-runtime": "^7.25.9", - "@babel/preset-env": "^7.25.9", - "@babel/preset-react": "^7.25.9", - "@babel/preset-typescript": "^7.25.9", - "@babel/runtime": "^7.25.9", - "@babel/runtime-corejs3": "^7.25.9", - "@babel/traverse": "^7.25.9", - "@docusaurus/logger": "3.9.2", - "@docusaurus/utils": "3.9.2", - "babel-plugin-dynamic-import-node": "^2.3.3", - "fs-extra": "^11.1.1", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - } - }, - "node_modules/@docusaurus/bundler": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/bundler/-/bundler-3.9.2.tgz", - "integrity": "sha512-ZOVi6GYgTcsZcUzjblpzk3wH1Fya2VNpd5jtHoCCFcJlMQ1EYXZetfAnRHLcyiFeBABaI1ltTYbOBtH/gahGVA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.25.9", - "@docusaurus/babel": "3.9.2", - "@docusaurus/cssnano-preset": "3.9.2", - "@docusaurus/logger": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils": "3.9.2", - "babel-loader": "^9.2.1", - "clean-css": "^5.3.3", - "copy-webpack-plugin": "^11.0.0", - "css-loader": "^6.11.0", - "css-minimizer-webpack-plugin": "^5.0.1", - "cssnano": "^6.1.2", - "file-loader": "^6.2.0", - "html-minifier-terser": "^7.2.0", - "mini-css-extract-plugin": "^2.9.2", - "null-loader": "^4.0.1", - "postcss": "^8.5.4", - "postcss-loader": "^7.3.4", - "postcss-preset-env": "^10.2.1", - "terser-webpack-plugin": "^5.3.9", - "tslib": "^2.6.0", - "url-loader": "^4.1.1", - "webpack": "^5.95.0", - "webpackbar": "^6.0.1" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "@docusaurus/faster": "*" - }, - "peerDependenciesMeta": { - "@docusaurus/faster": { - "optional": true - } - } - }, - "node_modules/@docusaurus/core": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.9.2.tgz", - "integrity": "sha512-HbjwKeC+pHUFBfLMNzuSjqFE/58+rLVKmOU3lxQrpsxLBOGosYco/Q0GduBb0/jEMRiyEqjNT/01rRdOMWq5pw==", - "license": "MIT", - "dependencies": { - "@docusaurus/babel": "3.9.2", - "@docusaurus/bundler": "3.9.2", - "@docusaurus/logger": "3.9.2", - "@docusaurus/mdx-loader": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-common": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", - "boxen": "^6.2.1", - "chalk": "^4.1.2", - "chokidar": "^3.5.3", - "cli-table3": "^0.6.3", - "combine-promises": "^1.1.0", - "commander": "^5.1.0", - "core-js": "^3.31.1", - "detect-port": "^1.5.1", - "escape-html": "^1.0.3", - "eta": "^2.2.0", - "eval": "^0.1.8", - "execa": "5.1.1", - "fs-extra": "^11.1.1", - "html-tags": "^3.3.1", - "html-webpack-plugin": "^5.6.0", - "leven": "^3.1.0", - "lodash": "^4.17.21", - "open": "^8.4.0", - "p-map": "^4.0.0", - "prompts": "^2.4.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "react-loadable": "npm:@docusaurus/react-loadable@6.0.0", - "react-loadable-ssr-addon-v5-slorber": "^1.0.1", - "react-router": "^5.3.4", - "react-router-config": "^5.1.1", - "react-router-dom": "^5.3.4", - "semver": "^7.5.4", - "serve-handler": "^6.1.6", - "tinypool": "^1.0.2", - "tslib": "^2.6.0", - "update-notifier": "^6.0.2", - "webpack": "^5.95.0", - "webpack-bundle-analyzer": "^4.10.2", - "webpack-dev-server": "^5.2.2", - "webpack-merge": "^6.0.1" - }, - "bin": { - "docusaurus": "bin/docusaurus.mjs" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "@mdx-js/react": "^3.0.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/cssnano-preset": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.9.2.tgz", - "integrity": "sha512-8gBKup94aGttRduABsj7bpPFTX7kbwu+xh3K9NMCF5K4bWBqTFYW+REKHF6iBVDHRJ4grZdIPbvkiHd/XNKRMQ==", - "license": "MIT", - "dependencies": { - "cssnano-preset-advanced": "^6.1.2", - "postcss": "^8.5.4", - "postcss-sort-media-queries": "^5.2.0", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - } - }, - "node_modules/@docusaurus/logger": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.9.2.tgz", - "integrity": "sha512-/SVCc57ByARzGSU60c50rMyQlBuMIJCjcsJlkphxY6B0GV4UH3tcA1994N8fFfbJ9kX3jIBe/xg3XP5qBtGDbA==", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.2", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - } - }, - "node_modules/@docusaurus/mdx-loader": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.9.2.tgz", - "integrity": "sha512-wiYoGwF9gdd6rev62xDU8AAM8JuLI/hlwOtCzMmYcspEkzecKrP8J8X+KpYnTlACBUUtXNJpSoCwFWJhLRevzQ==", - "license": "MIT", - "dependencies": { - "@docusaurus/logger": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", - "@mdx-js/mdx": "^3.0.0", - "@slorber/remark-comment": "^1.0.0", - "escape-html": "^1.0.3", - "estree-util-value-to-estree": "^3.0.1", - "file-loader": "^6.2.0", - "fs-extra": "^11.1.1", - "image-size": "^2.0.2", - "mdast-util-mdx": "^3.0.0", - "mdast-util-to-string": "^4.0.0", - "rehype-raw": "^7.0.0", - "remark-directive": "^3.0.0", - "remark-emoji": "^4.0.0", - "remark-frontmatter": "^5.0.0", - "remark-gfm": "^4.0.0", - "stringify-object": "^3.3.0", - "tslib": "^2.6.0", - "unified": "^11.0.3", - "unist-util-visit": "^5.0.0", - "url-loader": "^4.1.1", - "vfile": "^6.0.1", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/module-type-aliases": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.9.2.tgz", - "integrity": "sha512-8qVe2QA9hVLzvnxP46ysuofJUIc/yYQ82tvA/rBTrnpXtCjNSFLxEZfd5U8cYZuJIVlkPxamsIgwd5tGZXfvew==", - "license": "MIT", - "dependencies": { - "@docusaurus/types": "3.9.2", - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router-config": "*", - "@types/react-router-dom": "*", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "react-loadable": "npm:@docusaurus/react-loadable@6.0.0" - }, - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/@docusaurus/theme-common": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.9.2.tgz", - "integrity": "sha512-6c4DAbR6n6nPbnZhY2V3tzpnKnGL+6aOsLvFL26VRqhlczli9eWG0VDUNoCQEPnGwDMhPS42UhSAnz5pThm5Ag==", - "license": "MIT", - "dependencies": { - "@docusaurus/mdx-loader": "3.9.2", - "@docusaurus/module-type-aliases": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-common": "3.9.2", - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router-config": "*", - "clsx": "^2.0.0", - "parse-numeric-range": "^1.3.0", - "prism-react-renderer": "^2.3.0", - "tslib": "^2.6.0", - "utility-types": "^3.10.0" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "@docusaurus/plugin-content-docs": "*", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/theme-mermaid": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/theme-mermaid/-/theme-mermaid-3.9.2.tgz", - "integrity": "sha512-5vhShRDq/ntLzdInsQkTdoKWSzw8d1jB17sNPYhA/KvYYFXfuVEGHLM6nrf8MFbV8TruAHDG21Fn3W4lO8GaDw==", - "license": "MIT", - "dependencies": { - "@docusaurus/core": "3.9.2", - "@docusaurus/module-type-aliases": "3.9.2", - "@docusaurus/theme-common": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils-validation": "3.9.2", - "mermaid": ">=11.6.0", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - }, - "peerDependencies": { - "@mermaid-js/layout-elk": "^0.1.9", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@mermaid-js/layout-elk": { - "optional": true - } - } - }, - "node_modules/@docusaurus/types": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.9.2.tgz", - "integrity": "sha512-Ux1JUNswg+EfUEmajJjyhIohKceitY/yzjRUpu04WXgvVz+fbhVC0p+R0JhvEu4ytw8zIAys2hrdpQPBHRIa8Q==", - "license": "MIT", - "dependencies": { - "@mdx-js/mdx": "^3.0.0", - "@types/history": "^4.7.11", - "@types/mdast": "^4.0.2", - "@types/react": "*", - "commander": "^5.1.0", - "joi": "^17.9.2", - "react-helmet-async": "npm:@slorber/react-helmet-async@1.3.0", - "utility-types": "^3.10.0", - "webpack": "^5.95.0", - "webpack-merge": "^5.9.0" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@docusaurus/types/node_modules/webpack-merge": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz", - "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@docusaurus/utils": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.9.2.tgz", - "integrity": "sha512-lBSBiRruFurFKXr5Hbsl2thmGweAPmddhF3jb99U4EMDA5L+e5Y1rAkOS07Nvrup7HUMBDrCV45meaxZnt28nQ==", - "license": "MIT", - "dependencies": { - "@docusaurus/logger": "3.9.2", - "@docusaurus/types": "3.9.2", - "@docusaurus/utils-common": "3.9.2", - "escape-string-regexp": "^4.0.0", - "execa": "5.1.1", - "file-loader": "^6.2.0", - "fs-extra": "^11.1.1", - "github-slugger": "^1.5.0", - "globby": "^11.1.0", - "gray-matter": "^4.0.3", - "jiti": "^1.20.0", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "micromatch": "^4.0.5", - "p-queue": "^6.6.2", - "prompts": "^2.4.2", - "resolve-pathname": "^3.0.0", - "tslib": "^2.6.0", - "url-loader": "^4.1.1", - "utility-types": "^3.10.0", - "webpack": "^5.88.1" - }, - "engines": { - "node": ">=20.0" - } - }, - "node_modules/@docusaurus/utils-common": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.9.2.tgz", - "integrity": "sha512-I53UC1QctruA6SWLvbjbhCpAw7+X7PePoe5pYcwTOEXD/PxeP8LnECAhTHHwWCblyUX5bMi4QLRkxvyZ+IT8Aw==", - "license": "MIT", - "dependencies": { - "@docusaurus/types": "3.9.2", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - } - }, - "node_modules/@docusaurus/utils-validation": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.9.2.tgz", - "integrity": "sha512-l7yk3X5VnNmATbwijJkexdhulNsQaNDwoagiwujXoxFbWLcxHQqNQ+c/IAlzrfMMOfa/8xSBZ7KEKDesE/2J7A==", - "license": "MIT", - "dependencies": { - "@docusaurus/logger": "3.9.2", - "@docusaurus/utils": "3.9.2", - "@docusaurus/utils-common": "3.9.2", - "fs-extra": "^11.2.0", - "joi": "^17.9.2", - "js-yaml": "^4.1.0", - "lodash": "^4.17.21", - "tslib": "^2.6.0" - }, - "engines": { - "node": ">=20.0" - } - }, "node_modules/@emnapi/core": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", - "integrity": "sha512-o1uhUASyo921r2XtHYOHy7gdkGLge8ghBEQHMWmyJFoXlpU58kIrhhN3w26lpQb6dspetweapMn2CSNwQ8I4wg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", "dev": true, "license": "MIT", "optional": true, @@ -3713,10 +482,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", - "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", - "dev": true, + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", "license": "MIT", "optional": true, "dependencies": { @@ -3747,9 +515,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", "cpu": [ "ppc64" ], @@ -3764,9 +532,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", "cpu": [ "arm" ], @@ -3781,9 +549,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", "cpu": [ "arm64" ], @@ -3798,9 +566,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", "cpu": [ "x64" ], @@ -3815,9 +583,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", "cpu": [ "arm64" ], @@ -3832,9 +600,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", "cpu": [ "x64" ], @@ -3849,9 +617,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", "cpu": [ "arm64" ], @@ -3866,9 +634,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", "cpu": [ "x64" ], @@ -3883,9 +651,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", "cpu": [ "arm" ], @@ -3900,9 +668,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", "cpu": [ "arm64" ], @@ -3917,9 +685,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", "cpu": [ "ia32" ], @@ -3934,9 +702,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", "cpu": [ "loong64" ], @@ -3951,9 +719,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", "cpu": [ "mips64el" ], @@ -3968,9 +736,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", "cpu": [ "ppc64" ], @@ -3985,9 +753,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", "cpu": [ "riscv64" ], @@ -4002,9 +770,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", "cpu": [ "s390x" ], @@ -4019,9 +787,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", "cpu": [ "x64" ], @@ -4036,9 +804,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", - "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", "cpu": [ "arm64" ], @@ -4053,9 +821,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", "cpu": [ "x64" ], @@ -4070,9 +838,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", - "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", "cpu": [ "arm64" ], @@ -4087,9 +855,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", "cpu": [ "x64" ], @@ -4104,9 +872,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", - "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", "cpu": [ "arm64" ], @@ -4121,9 +889,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", "cpu": [ "x64" ], @@ -4138,9 +906,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", "cpu": [ "arm64" ], @@ -4155,9 +923,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", "cpu": [ "ia32" ], @@ -4172,9 +940,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", "cpu": [ "x64" ], @@ -4189,9 +957,9 @@ } }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4207,6 +975,19 @@ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@eslint-community/regexpp": { "version": "4.12.2", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", @@ -4217,56 +998,142 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", "dev": true, "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", + "js-yaml": "^4.1.1", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "dev": true, "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.10.0.tgz", + "integrity": "sha512-tf8YdcbirXdPnJ+Nd4UN1EXnz+IP2DI45YVEr3vvzcVTOyrApkmIB4zvOQVd3XPr7RXnfBtAx+PXImXOIU0Ajg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } } }, "node_modules/@floating-ui/core": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", - "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", "license": "MIT", "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", - "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", + "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.3", + "@floating-ui/core": "^1.7.4", "@floating-ui/utils": "^0.2.10" } }, @@ -4304,36 +1171,51 @@ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", "license": "MIT" }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, "node_modules/@headlessui/react": { - "version": "1.7.19", - "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-1.7.19.tgz", - "integrity": "sha512-Ll+8q3OlMJfJbAKM/+/Y2q6PPYbryqNTXDbryx7SXLIDamkF6iQFbriYHga0dY44PvDhvvBWCx1Xj4U5+G4hOw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.0.tgz", + "integrity": "sha512-RzCEg+LXsuI7mHiSomsu/gBJSjpupm6A1qIZ5sWjd7JhARNlMiSA4kKfJpCKwU9tE+zMRterhhrP74PvfJrpXQ==", "license": "MIT", "dependencies": { - "@tanstack/react-virtual": "^3.0.0-beta.60", - "client-only": "^0.0.1" + "@floating-ui/react": "^0.26.16", + "@react-aria/focus": "^3.17.1", + "@react-aria/interactions": "^3.21.3", + "@tanstack/react-virtual": "^3.8.1" }, "engines": { "node": ">=10" }, "peerDependencies": { - "react": "^16 || ^17 || ^18", - "react-dom": "^16 || ^17 || ^18" + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/@headlessui/react/node_modules/@floating-ui/react": { + "version": "0.26.28", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", + "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@headlessui/react/node_modules/@floating-ui/react-dom": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz", + "integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.5" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" } }, "node_modules/@headlessui/tailwindcss": { @@ -4357,20 +1239,28 @@ "react": ">= 16" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -4387,46 +1277,484 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@iconify/types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", - "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", - "license": "MIT" - }, - "node_modules/@iconify/utils": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.0.2.tgz", - "integrity": "sha512-EfJS0rLfVuRuJRn4psJHtK2A9TqVnkxPpHY6lYHiB9+8eSuudsxbwMiavocG45ujOo6FJ+CIRlRnlOGinzkaGQ==", - "license": "MIT", - "dependencies": { - "@antfu/install-pkg": "^1.1.0", - "@antfu/utils": "^9.2.0", - "@iconify/types": "^2.0.0", - "debug": "^4.4.1", - "globals": "^15.15.0", - "kolorist": "^1.8.0", - "local-pkg": "^1.1.1", - "mlly": "^1.7.4" - } - }, - "node_modules/@iconify/utils/node_modules/globals": { - "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=18" + "node": ">=18.18" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, "node_modules/@isaacs/balanced-match": { @@ -4462,256 +1790,45 @@ "node": ">=8" } }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.11", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", - "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jsonjoy.com/base64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", - "integrity": "sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@jsonjoy.com/buffers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/buffers/-/buffers-1.2.1.tgz", - "integrity": "sha512-12cdlDwX4RUM3QxmUbVJWqZ/mrK6dFQH4Zxq6+r1YXKXYBNgZXndx2qbCJwh3+WWkCSn67IjnlG3XYTvmvYtgA==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@jsonjoy.com/codegen": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/codegen/-/codegen-1.0.0.tgz", - "integrity": "sha512-E8Oy+08cmCf0EK/NMxpaJZmOxPqM+6iSe2S4nlSBrPZOORoDJILxtbSUEDKQyTamm/BVAhIGllOBNU79/dwf0g==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@jsonjoy.com/json-pack": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pack/-/json-pack-1.21.0.tgz", - "integrity": "sha512-+AKG+R2cfZMShzrF2uQw34v3zbeDYUqnQ+jg7ORic3BGtfw9p/+N6RJbq/kkV8JmYZaINknaEQ2m0/f693ZPpg==", - "license": "Apache-2.0", - "dependencies": { - "@jsonjoy.com/base64": "^1.1.2", - "@jsonjoy.com/buffers": "^1.2.0", - "@jsonjoy.com/codegen": "^1.0.0", - "@jsonjoy.com/json-pointer": "^1.0.2", - "@jsonjoy.com/util": "^1.9.0", - "hyperdyperid": "^1.2.0", - "thingies": "^2.5.0", - "tree-dump": "^1.1.0" - }, - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@jsonjoy.com/json-pointer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/json-pointer/-/json-pointer-1.0.2.tgz", - "integrity": "sha512-Fsn6wM2zlDzY1U+v4Nc8bo3bVqgfNTGcn6dMgs6FjrEnt4ZCe60o6ByKRjOGlI2gow0aE/Q41QOigdTqkyK5fg==", - "license": "Apache-2.0", - "dependencies": { - "@jsonjoy.com/codegen": "^1.0.0", - "@jsonjoy.com/util": "^1.9.0" - }, - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@jsonjoy.com/util": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@jsonjoy.com/util/-/util-1.9.0.tgz", - "integrity": "sha512-pLuQo+VPRnN8hfPqUTLTHk126wuYdXVxE6aDmjSeV4NCAgyxWbiOIeNJVtID3h1Vzpoi9m4jXezf73I6LgabgQ==", - "license": "Apache-2.0", - "dependencies": { - "@jsonjoy.com/buffers": "^1.0.0", - "@jsonjoy.com/codegen": "^1.0.0" - }, - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", - "integrity": "sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==", - "license": "MIT" - }, - "node_modules/@mdx-js/mdx": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@mdx-js/mdx/-/mdx-3.1.1.tgz", - "integrity": "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "@types/mdx": "^2.0.0", - "acorn": "^8.0.0", - "collapse-white-space": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "estree-util-scope": "^1.0.0", - "estree-walker": "^3.0.0", - "hast-util-to-jsx-runtime": "^2.0.0", - "markdown-extensions": "^2.0.0", - "recma-build-jsx": "^1.0.0", - "recma-jsx": "^1.0.0", - "recma-stringify": "^1.0.0", - "rehype-recma": "^1.0.0", - "remark-mdx": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.0.0", - "source-map": "^0.7.0", - "unified": "^11.0.0", - "unist-util-position-from-estree": "^2.0.0", - "unist-util-stringify-position": "^4.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/@mermaid-js/parser": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz", - "integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==", - "license": "MIT", - "dependencies": { - "langium": "3.3.1" - } - }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -4736,25 +1853,25 @@ } }, "node_modules/@next/env": { - "version": "14.2.35", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.35.tgz", - "integrity": "sha512-DuhvCtj4t9Gwrx80dmz2F4t/zKQ4ktN8WrMwOuVzkJfBilwAwGr6v16M5eI8yCuZ63H9TTuEU09Iu2HqkzFPVQ==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", + "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "14.2.32", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.32.tgz", - "integrity": "sha512-tyZMX8g4cWg/uPW4NxiJK13t62Pab47SKGJGVZJa6YtFwtfrXovH4j1n9tdpRdXW03PGQBugYEVGM7OhWfytdA==", + "version": "15.5.10", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.10.tgz", + "integrity": "sha512-fDpxcy6G7Il4lQVVsaJD0fdC2/+SmuBGTF+edRLlsR4ZFOE3W2VyzrrGYdg/pHW8TydeAdSVM+mIzITGtZ3yWA==", "dev": true, "license": "MIT", "dependencies": { - "glob": "10.3.10" + "fast-glob": "3.3.1" } }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.33.tgz", - "integrity": "sha512-HqYnb6pxlsshoSTubdXKu15g3iivcbsMXg4bYpjL2iS/V6aQot+iyF4BUc2qA/J/n55YtvE4PHMKWBKGCF/+wA==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz", + "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==", "cpu": [ "arm64" ], @@ -4768,9 +1885,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.33.tgz", - "integrity": "sha512-8HGBeAE5rX3jzKvF593XTTFg3gxeU4f+UWnswa6JPhzaR6+zblO5+fjltJWIZc4aUalqTclvN2QtTC37LxvZAA==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz", + "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==", "cpu": [ "x64" ], @@ -4784,9 +1901,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.33.tgz", - "integrity": "sha512-JXMBka6lNNmqbkvcTtaX8Gu5by9547bukHQvPoLe9VRBx1gHwzf5tdt4AaezW85HAB3pikcvyqBToRTDA4DeLw==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz", + "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==", "cpu": [ "arm64" ], @@ -4800,9 +1917,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.33.tgz", - "integrity": "sha512-Bm+QulsAItD/x6Ih8wGIMfRJy4G73tu1HJsrccPW6AfqdZd0Sfm5Imhgkgq2+kly065rYMnCOxTBvmvFY1BKfg==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz", + "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==", "cpu": [ "arm64" ], @@ -4816,9 +1933,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.33.tgz", - "integrity": "sha512-FnFn+ZBgsVMbGDsTqo8zsnRzydvsGV8vfiWwUo1LD8FTmPTdV+otGSWKc4LJec0oSexFnCYVO4hX8P8qQKaSlg==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz", + "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==", "cpu": [ "x64" ], @@ -4832,9 +1949,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.33.tgz", - "integrity": "sha512-345tsIWMzoXaQndUTDv1qypDRiebFxGYx9pYkhwY4hBRaOLt8UGfiWKr9FSSHs25dFIf8ZqIFaPdy5MljdoawA==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz", + "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==", "cpu": [ "x64" ], @@ -4848,9 +1965,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.33.tgz", - "integrity": "sha512-nscpt0G6UCTkrT2ppnJnFsYbPDQwmum4GNXYTeoTIdsmMydSKFz9Iny2jpaRupTb+Wl298+Rh82WKzt9LCcqSQ==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz", + "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==", "cpu": [ "arm64" ], @@ -4863,26 +1980,10 @@ "node": ">= 10" } }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.33.tgz", - "integrity": "sha512-pc9LpGNKhJ0dXQhZ5QMmYxtARwwmWLpeocFmVG5Z0DzWq5Uf0izcI8tLc+qOpqxO1PWqZ5A7J1blrUIKrIFc7Q==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.33", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.33.tgz", - "integrity": "sha512-nOjfZMy8B94MdisuzZo9/57xuFVLHJaDj5e/xrduJp9CV2/HrfxTRH2fbyLe+K9QT41WBLUd4iXX3R7jBp0EUg==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz", + "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==", "cpu": [ "x64" ], @@ -4899,6 +2000,7 @@ "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", @@ -4912,6 +2014,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -4921,6 +2024,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", @@ -4941,13 +2045,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.57.0.tgz", - "integrity": "sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==", + "version": "1.58.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.1.tgz", + "integrity": "sha512-6LdVIUERWxQMmUSSQi0I53GgCBYgM2RpGngCPY7hSeju+VrKjq3lvs7HpJoPbDiY5QM5EYRtRX5fvrinnMAz3w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.57.0" + "playwright": "1.58.1" }, "bin": { "playwright": "cli.js" @@ -4956,57 +2060,17 @@ "node": ">=18" } }, - "node_modules/@pnpm/config.env-replace": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz", - "integrity": "sha512-htyl8TWnKL7K/ESFa1oW2UB5lVDxuF5DpM7tBi6Hu2LNL3mWkIzNLG6N4zoCUP1lCKNxWy/3iu8mS8MvToGd6w==", - "license": "MIT", - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@pnpm/network.ca-file/-/network.ca-file-1.0.2.tgz", - "integrity": "sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==", - "license": "MIT", - "dependencies": { - "graceful-fs": "4.2.10" - }, - "engines": { - "node": ">=12.22.0" - } - }, - "node_modules/@pnpm/network.ca-file/node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "license": "ISC" - }, - "node_modules/@pnpm/npm-conf": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@pnpm/npm-conf/-/npm-conf-2.3.1.tgz", - "integrity": "sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==", - "license": "MIT", - "dependencies": { - "@pnpm/config.env-replace": "^1.1.0", - "@pnpm/network.ca-file": "^1.0.1", - "config-chain": "^1.1.11" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/@polka/url": { "version": "1.0.0-next.29", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.29.tgz", "integrity": "sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==", + "dev": true, "license": "MIT" }, "node_modules/@rc-component/async-validator": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz", - "integrity": "sha512-qgGdcVIF604M9EqjNF0hbUTz42bz/RDtxWdWuU5EQe3hi7M8ob54B6B35rOsvX5eSvIHIzT9iH1R3n+hk3CGfg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.1.0.tgz", + "integrity": "sha512-n4HcR5siNUXRX23nDizbZBQPO0ZM/5oTtmKZ6/eqL0L2bo747cklFdZGRN2f+c9qWGICwDzrhW0H7tE9PptdcA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.24.4" @@ -5094,13 +2158,12 @@ } }, "node_modules/@rc-component/qrcode": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.1.0.tgz", - "integrity": "sha512-ABA80Yer0c6I2+moqNY0kF3Y1NxIT6wDP/EINIqbiRbfZKP1HtHpKMh8WuTXLgVGYsoWG2g9/n0PgM8KdnJb4Q==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@rc-component/qrcode/-/qrcode-1.1.1.tgz", + "integrity": "sha512-LfLGNymzKdUPjXUbRP+xOhIWY4jQ+YMj5MmWAcgcAq1Ij8XP7tRmAXqyuv96XvLUBE/5cA8hLFl9eO1JQMujrA==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.24.7", - "classnames": "^2.3.2" + "@babel/runtime": "^7.24.7" }, "engines": { "node": ">=8.x" @@ -5131,9 +2194,9 @@ } }, "node_modules/@rc-component/trigger": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.3.0.tgz", - "integrity": "sha512-iwaxZyzOuK0D7lS+0AQEtW52zUWxoGqTGkke3dRyb8pYiShmRpCjB/8TzPI4R6YySCH7Vm9BZj/31VPiiQTLBg==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.3.1.tgz", + "integrity": "sha512-ORENF39PeXTzM+gQEshuk460Z8N4+6DkjpxlpE7Q3gYy1iBpLrx0FOJz3h62ryrJZ/3zCAUIkT1Pb/8hHWpb3A==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.23.2", @@ -5152,13 +2215,13 @@ } }, "node_modules/@react-aria/focus": { - "version": "3.21.2", - "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.21.2.tgz", - "integrity": "sha512-JWaCR7wJVggj+ldmM/cb/DXFg47CXR55lznJhZBh4XVqJjMKwaOOqpT5vNN7kpC1wUpXicGNuDnJDN1S/+6dhQ==", + "version": "3.21.3", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.21.3.tgz", + "integrity": "sha512-FsquWvjSCwC2/sBk4b+OqJyONETUIXQ2vM0YdPAuC+QFQh2DT6TIBo6dOZVSezlhudDla69xFBd6JvCFq1AbUw==", "license": "Apache-2.0", "dependencies": { - "@react-aria/interactions": "^3.25.6", - "@react-aria/utils": "^3.31.0", + "@react-aria/interactions": "^3.26.0", + "@react-aria/utils": "^3.32.0", "@react-types/shared": "^3.32.1", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" @@ -5169,13 +2232,13 @@ } }, "node_modules/@react-aria/interactions": { - "version": "3.25.6", - "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.6.tgz", - "integrity": "sha512-5UgwZmohpixwNMVkMvn9K1ceJe6TzlRlAfuYoQDUuOkk62/JVJNDLAPKIf5YMRc7d2B0rmfgaZLMtbREb0Zvkw==", + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.26.0.tgz", + "integrity": "sha512-AAEcHiltjfbmP1i9iaVw34Mb7kbkiHpYdqieWufldh4aplWgsF11YQZOfaCJW4QoR2ML4Zzoa9nfFwLXA52R7Q==", "license": "Apache-2.0", "dependencies": { "@react-aria/ssr": "^3.9.10", - "@react-aria/utils": "^3.31.0", + "@react-aria/utils": "^3.32.0", "@react-stately/flags": "^3.1.2", "@react-types/shared": "^3.32.1", "@swc/helpers": "^0.5.0" @@ -5201,14 +2264,14 @@ } }, "node_modules/@react-aria/utils": { - "version": "3.31.0", - "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.31.0.tgz", - "integrity": "sha512-ABOzCsZrWzf78ysswmguJbx3McQUja7yeGj6/vZo4JVsZNlxAN+E9rs381ExBRI0KzVo6iBTeX5De8eMZPJXig==", + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.32.0.tgz", + "integrity": "sha512-/7Rud06+HVBIlTwmwmJa2W8xVtgxgzm0+kLbuFooZRzKDON6hhozS1dOMR/YLMxyJOaYOTpImcP4vRR9gL1hEg==", "license": "Apache-2.0", "dependencies": { "@react-aria/ssr": "^3.9.10", "@react-stately/flags": "^3.1.2", - "@react-stately/utils": "^3.10.8", + "@react-stately/utils": "^3.11.0", "@react-types/shared": "^3.32.1", "@swc/helpers": "^0.5.0", "clsx": "^2.0.0" @@ -5228,9 +2291,9 @@ } }, "node_modules/@react-stately/utils": { - "version": "3.10.8", - "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.8.tgz", - "integrity": "sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==", + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.11.0.tgz", + "integrity": "sha512-8LZpYowJ9eZmmYLpudbo/eclIRnbhWIJZ994ncmlKlouNzKohtM8qTC6B1w1pwUbiwGdUoyzLuQbeaIor5Dvcw==", "license": "Apache-2.0", "dependencies": { "@swc/helpers": "^0.5.0" @@ -5249,25 +2312,18 @@ } }, "node_modules/@remixicon/react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@remixicon/react/-/react-4.7.0.tgz", - "integrity": "sha512-ODBQjdbOjnFguCqctYkpDjERXOInNaBnRPDKfZOBvbzExBAwr2BaH/6AHFTg/UAFzBDkwtylfMT8iKPAkLwPLQ==", - "license": "Apache-2.0", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@remixicon/react/-/react-4.9.0.tgz", + "integrity": "sha512-5/jLDD4DtKxH2B4QVXTobvV1C2uL8ab9D5yAYNtFt+w80O0Ys1xFOrspqROL3fjrZi+7ElFUWE37hBfaAl6U+Q==", + "license": "Remix Icon License 1.0", "peerDependencies": { "react": ">=18.2.0" } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.47", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz", - "integrity": "sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==", - "dev": true, - "license": "MIT" - }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", - "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", "cpu": [ "arm" ], @@ -5279,9 +2335,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", - "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", "cpu": [ "arm64" ], @@ -5293,9 +2349,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", - "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", "cpu": [ "arm64" ], @@ -5307,9 +2363,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", - "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", "cpu": [ "x64" ], @@ -5321,9 +2377,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", - "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", "cpu": [ "arm64" ], @@ -5335,9 +2391,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", - "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", "cpu": [ "x64" ], @@ -5349,9 +2405,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", - "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", "cpu": [ "arm" ], @@ -5363,9 +2419,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", - "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", "cpu": [ "arm" ], @@ -5377,9 +2433,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", - "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", "cpu": [ "arm64" ], @@ -5391,9 +2447,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", - "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", "cpu": [ "arm64" ], @@ -5405,9 +2461,23 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", - "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", "cpu": [ "loong64" ], @@ -5419,9 +2489,23 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", - "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", "cpu": [ "ppc64" ], @@ -5433,9 +2517,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", - "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", "cpu": [ "riscv64" ], @@ -5447,9 +2531,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", - "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", "cpu": [ "riscv64" ], @@ -5461,9 +2545,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", - "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", "cpu": [ "s390x" ], @@ -5475,9 +2559,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", - "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", "cpu": [ "x64" ], @@ -5489,9 +2573,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", - "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", "cpu": [ "x64" ], @@ -5502,10 +2586,24 @@ "linux" ] }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", - "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", "cpu": [ "arm64" ], @@ -5517,9 +2615,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", - "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", "cpu": [ "arm64" ], @@ -5531,9 +2629,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", - "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", "cpu": [ "ia32" ], @@ -5545,9 +2643,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", - "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", "cpu": [ "x64" ], @@ -5559,9 +2657,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", - "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", "cpu": [ "x64" ], @@ -5586,88 +2684,19 @@ "dev": true, "license": "MIT" }, - "node_modules/@sideway/address": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", - "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", - "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", - "license": "BSD-3-Clause" - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "license": "MIT" - }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/@slorber/remark-comment": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@slorber/remark-comment/-/remark-comment-1.0.0.tgz", - "integrity": "sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==", - "license": "MIT", - "dependencies": { - "micromark-factory-space": "^1.0.0", - "micromark-util-character": "^1.1.0", - "micromark-util-symbol": "^1.0.1" - } - }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", - "license": "Apache-2.0" - }, "node_modules/@swc/helpers": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz", - "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==", + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz", + "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==", "license": "Apache-2.0", "dependencies": { - "@swc/counter": "^0.1.3", - "tslib": "^2.4.0" - } - }, - "node_modules/@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", - "license": "MIT", - "dependencies": { - "defer-to-connect": "^2.0.1" - }, - "engines": { - "node": ">=14.16" + "tslib": "^2.8.0" } }, "node_modules/@tailwindcss/forms": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.10.tgz", - "integrity": "sha512-utI1ONF6uf/pPNO68kmN1b8rEwNXv3czukalo8VtJH8ksIkZXr3Q3VYudZLkCsDd4Wku120uF02hYK25XGPorw==", + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.11.tgz", + "integrity": "sha512-h9wegbZDPurxG22xZSoWtdzc41/OlNEUQERNqI/0fOwa2aVlWGu7C35E/x6LDyD3lgtztFSSjKZyuVM0hxhbgA==", "dev": true, "license": "MIT", "dependencies": { @@ -5691,9 +2720,9 @@ } }, "node_modules/@tanstack/query-core": { - "version": "5.90.10", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.10.tgz", - "integrity": "sha512-EhZVFu9rl7GfRNuJLJ3Y7wtbTnENsvzp+YpcAV7kCYiXni1v8qZh++lpw4ch4rrwC0u/EZRnBHIehzCGzwXDSQ==", + "version": "5.90.20", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz", + "integrity": "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==", "license": "MIT", "funding": { "type": "github", @@ -5721,12 +2750,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.90.10", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.10.tgz", - "integrity": "sha512-BKLss9Y8PQ9IUjPYQiv3/Zmlx92uxffUOX8ZZNoQlCIZBJPT5M+GOMQj7xislvVQ6l1BstBjcX0XB/aHfFYVNw==", + "version": "5.90.20", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.20.tgz", + "integrity": "sha512-vXBxa+qeyveVO7OA0jX1z+DeyCA4JKnThKv411jd5SORpBKgkcVnYKCiBgECvADvniBX7tobwBmg01qq9JmMJw==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.90.10" + "@tanstack/query-core": "5.90.20" }, "funding": { "type": "github", @@ -5757,12 +2786,12 @@ } }, "node_modules/@tanstack/react-virtual": { - "version": "3.13.12", - "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz", - "integrity": "sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==", + "version": "3.13.18", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.18.tgz", + "integrity": "sha512-dZkhyfahpvlaV0rIKnvQiVoWPyURppl6w4m9IwMDpuIjcJ1sD9YGWrt0wISvgU7ewACXx2Ct46WPgI6qAD4v6A==", "license": "MIT", "dependencies": { - "@tanstack/virtual-core": "3.13.12" + "@tanstack/virtual-core": "3.13.18" }, "funding": { "type": "github", @@ -5787,9 +2816,9 @@ } }, "node_modules/@tanstack/virtual-core": { - "version": "3.13.12", - "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz", - "integrity": "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==", + "version": "3.13.18", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.18.tgz", + "integrity": "sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg==", "license": "MIT", "funding": { "type": "github", @@ -5844,9 +2873,9 @@ "license": "MIT" }, "node_modules/@testing-library/react": { - "version": "16.3.0", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.0.tgz", - "integrity": "sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==", + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", "dev": true, "license": "MIT", "dependencies": { @@ -5904,72 +2933,16 @@ "react-dom": ">=16.6.0" } }, - "node_modules/@tremor/react/node_modules/@floating-ui/react-dom": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", - "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", - "license": "MIT", - "dependencies": { - "@floating-ui/dom": "^1.7.4" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, - "node_modules/@tremor/react/node_modules/@headlessui/react": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.0.tgz", - "integrity": "sha512-RzCEg+LXsuI7mHiSomsu/gBJSjpupm6A1qIZ5sWjd7JhARNlMiSA4kKfJpCKwU9tE+zMRterhhrP74PvfJrpXQ==", - "license": "MIT", - "dependencies": { - "@floating-ui/react": "^0.26.16", - "@react-aria/focus": "^3.17.1", - "@react-aria/interactions": "^3.21.3", - "@tanstack/react-virtual": "^3.8.1" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "react": "^18 || ^19 || ^19.0.0-rc", - "react-dom": "^18 || ^19 || ^19.0.0-rc" - } - }, - "node_modules/@tremor/react/node_modules/@headlessui/react/node_modules/@floating-ui/react": { - "version": "0.26.28", - "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", - "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.1.2", - "@floating-ui/utils": "^0.2.8", - "tabbable": "^6.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0", - "react-dom": ">=16.8.0" - } - }, "node_modules/@tremor/react/node_modules/tailwind-merge": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", - "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.1.tgz", + "integrity": "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==", "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/dcastil" } }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "license": "ISC", - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/@tybys/wasm-util": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", @@ -5988,41 +2961,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, "node_modules/@types/babel__traverse": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", @@ -6033,25 +2971,6 @@ "@babel/types": "^7.28.2" } }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bonjour": { - "version": "3.5.13", - "resolved": "https://registry.npmjs.org/@types/bonjour/-/bonjour-3.5.13.tgz", - "integrity": "sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/chai": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", @@ -6063,178 +2982,24 @@ "assertion-error": "^2.0.1" } }, - "node_modules/@types/connect": { - "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz", - "integrity": "sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==", - "license": "MIT", - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "node_modules/@types/d3": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", - "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", - "license": "MIT", - "dependencies": { - "@types/d3-array": "*", - "@types/d3-axis": "*", - "@types/d3-brush": "*", - "@types/d3-chord": "*", - "@types/d3-color": "*", - "@types/d3-contour": "*", - "@types/d3-delaunay": "*", - "@types/d3-dispatch": "*", - "@types/d3-drag": "*", - "@types/d3-dsv": "*", - "@types/d3-ease": "*", - "@types/d3-fetch": "*", - "@types/d3-force": "*", - "@types/d3-format": "*", - "@types/d3-geo": "*", - "@types/d3-hierarchy": "*", - "@types/d3-interpolate": "*", - "@types/d3-path": "*", - "@types/d3-polygon": "*", - "@types/d3-quadtree": "*", - "@types/d3-random": "*", - "@types/d3-scale": "*", - "@types/d3-scale-chromatic": "*", - "@types/d3-selection": "*", - "@types/d3-shape": "*", - "@types/d3-time": "*", - "@types/d3-time-format": "*", - "@types/d3-timer": "*", - "@types/d3-transition": "*", - "@types/d3-zoom": "*" - } - }, "node_modules/@types/d3-array": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", "license": "MIT" }, - "node_modules/@types/d3-axis": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", - "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-brush": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", - "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-chord": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", - "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", - "license": "MIT" - }, "node_modules/@types/d3-color": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", "license": "MIT" }, - "node_modules/@types/d3-contour": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", - "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", - "license": "MIT", - "dependencies": { - "@types/d3-array": "*", - "@types/geojson": "*" - } - }, - "node_modules/@types/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", - "license": "MIT" - }, - "node_modules/@types/d3-dispatch": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", - "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", - "license": "MIT" - }, - "node_modules/@types/d3-drag": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", - "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-dsv": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", - "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", - "license": "MIT" - }, "node_modules/@types/d3-ease": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", "license": "MIT" }, - "node_modules/@types/d3-fetch": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", - "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", - "license": "MIT", - "dependencies": { - "@types/d3-dsv": "*" - } - }, - "node_modules/@types/d3-force": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", - "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", - "license": "MIT" - }, - "node_modules/@types/d3-format": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", - "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", - "license": "MIT" - }, - "node_modules/@types/d3-geo": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", - "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", - "license": "MIT", - "dependencies": { - "@types/geojson": "*" - } - }, - "node_modules/@types/d3-hierarchy": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", - "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", - "license": "MIT" - }, "node_modules/@types/d3-interpolate": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", @@ -6250,24 +3015,6 @@ "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", "license": "MIT" }, - "node_modules/@types/d3-polygon": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", - "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", - "license": "MIT" - }, - "node_modules/@types/d3-quadtree": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", - "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", - "license": "MIT" - }, - "node_modules/@types/d3-random": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", - "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", - "license": "MIT" - }, "node_modules/@types/d3-scale": { "version": "4.0.9", "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", @@ -6277,22 +3024,10 @@ "@types/d3-time": "*" } }, - "node_modules/@types/d3-scale-chromatic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", - "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", - "license": "MIT" - }, - "node_modules/@types/d3-selection": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", - "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", - "license": "MIT" - }, "node_modules/@types/d3-shape": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz", - "integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", "license": "MIT", "dependencies": { "@types/d3-path": "*" @@ -6304,37 +3039,12 @@ "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", "license": "MIT" }, - "node_modules/@types/d3-time-format": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", - "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", - "license": "MIT" - }, "node_modules/@types/d3-timer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", "license": "MIT" }, - "node_modules/@types/d3-transition": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", - "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", - "license": "MIT", - "dependencies": { - "@types/d3-selection": "*" - } - }, - "node_modules/@types/d3-zoom": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", - "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", - "license": "MIT", - "dependencies": { - "@types/d3-interpolate": "*", - "@types/d3-selection": "*" - } - }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -6351,26 +3061,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/eslint": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz", - "integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==", - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -6386,36 +3076,6 @@ "@types/estree": "*" } }, - "node_modules/@types/express": { - "version": "4.17.25", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", - "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "^1" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.19.7", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.7.tgz", - "integrity": "sha512-FvPtiIf1LfhzsaIXhv/PHan/2FeQBbtBDtfX2QfvPxdUelMDEckK08SM6nqo1MIZY3RUlfA+HV8+hFUSio78qg==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/geojson": { - "version": "7946.0.16", - "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", - "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", - "license": "MIT" - }, "node_modules/@types/hast": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", @@ -6425,67 +3085,11 @@ "@types/unist": "*" } }, - "node_modules/@types/history": { - "version": "4.7.11", - "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", - "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==", - "license": "MIT" - }, - "node_modules/@types/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==", - "license": "MIT" - }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", - "license": "MIT" - }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", - "license": "MIT" - }, - "node_modules/@types/http-proxy": { - "version": "1.17.17", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", - "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, "license": "MIT" }, "node_modules/@types/json5": { @@ -6496,9 +3100,9 @@ "license": "MIT" }, "node_modules/@types/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==", "dev": true, "license": "MIT" }, @@ -6511,18 +3115,6 @@ "@types/unist": "*" } }, - "node_modules/@types/mdx": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", - "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==", - "license": "MIT" - }, - "node_modules/@types/mime": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", - "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", - "license": "MIT" - }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -6530,9 +3122,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz", - "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", + "version": "20.19.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.30.tgz", + "integrity": "sha512-WJtwWJu7UdlvzEAUm484QNg5eAoq5QR08KDNx7g45Usrs2NtOPiX8ugDqmKdXkyL03rBqU5dYNYVQetEpBHq2g==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -6548,52 +3140,27 @@ "form-data": "^4.0.4" } }, - "node_modules/@types/node-forge": { - "version": "1.3.14", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.14.tgz", - "integrity": "sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/papaparse": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.0.tgz", - "integrity": "sha512-GVs5iMQmUr54BAZYYkByv8zPofFxmyxUpISPb2oh8sayR3+1zbxasrOvoKiHJ/nnoq/uULuPsu1Lze1EkagVFg==", + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/@types/papaparse/-/papaparse-5.5.2.tgz", + "integrity": "sha512-gFnFp/JMzLHCwRf7tQHrNnfhN4eYBVYYI897CGX4MY1tzY9l2aLkVyx2IlKZ/SAqDbB3I1AOZW5gTMGGsqWliA==", "license": "MIT", "dependencies": { "@types/node": "*" } }, - "node_modules/@types/prismjs": { - "version": "1.26.5", - "resolved": "https://registry.npmjs.org/@types/prismjs/-/prismjs-1.26.5.tgz", - "integrity": "sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==", - "license": "MIT" - }, "node_modules/@types/prop-types": { "version": "15.7.15", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "license": "MIT" - }, - "node_modules/@types/qs": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", - "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true, "license": "MIT" }, "node_modules/@types/react": { "version": "18.2.48", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.48.tgz", "integrity": "sha512-qboRCl6Ie70DQQG9hhNREz81jqC1cs9EVNcjQ1AU+jH6NFfSAhVVbrrY/+nSF+Bsk4AOwm9Qa61InvMCyV+H3w==", + "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -6621,38 +3188,6 @@ "@types/react": "^18.0.0" } }, - "node_modules/@types/react-router": { - "version": "5.1.20", - "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", - "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", - "license": "MIT", - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*" - } - }, - "node_modules/@types/react-router-config": { - "version": "5.0.11", - "resolved": "https://registry.npmjs.org/@types/react-router-config/-/react-router-config-5.0.11.tgz", - "integrity": "sha512-WmSAg7WgqW7m4x8Mt4N6ZyKz0BubSj/2tVUMsAHp+Yd2AMwcSbeFq9WympT19p5heCFmF97R9eD5uUR/t4HEqw==", - "license": "MIT", - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router": "^5.1.0" - } - }, - "node_modules/@types/react-router-dom": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", - "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", - "license": "MIT", - "dependencies": { - "@types/history": "^4.7.11", - "@types/react": "*", - "@types/react-router": "*" - } - }, "node_modules/@types/react-syntax-highlighter": { "version": "15.5.13", "resolved": "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz", @@ -6663,73 +3198,13 @@ "@types/react": "*" } }, - "node_modules/@types/retry": { - "version": "0.12.2", - "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.2.tgz", - "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", - "license": "MIT" - }, "node_modules/@types/scheduler": { "version": "0.26.0", "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.26.0.tgz", "integrity": "sha512-WFHp9YUJQ6CKshqoC37iOlHnQSmxNc795UhB26CyBBttrN9svdIrUjl/NjnNmfcwtncN0h/0PPAFWv9ovP8mLA==", + "dev": true, "license": "MIT" }, - "node_modules/@types/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-index": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/@types/serve-index/-/serve-index-1.9.4.tgz", - "integrity": "sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==", - "license": "MIT", - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", - "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*", - "@types/send": "<1" - } - }, - "node_modules/@types/serve-static/node_modules/@types/send": { - "version": "0.17.6", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", - "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/sockjs": { - "version": "0.3.36", - "resolved": "https://registry.npmjs.org/@types/sockjs/-/sockjs-0.3.36.tgz", - "integrity": "sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT", - "optional": true - }, "node_modules/@types/unist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", @@ -6743,46 +3218,21 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/yargs": { - "version": "17.0.35", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", - "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "license": "MIT" - }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.47.0.tgz", - "integrity": "sha512-fe0rz9WJQ5t2iaLfdbDc9T80GJy0AeO453q8C3YCilnGozvOyCG5t+EZtg7j7D88+c3FipfP/x+wzGnh1xp8ZA==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", + "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.47.0", - "@typescript-eslint/type-utils": "8.47.0", - "@typescript-eslint/utils": "8.47.0", - "@typescript-eslint/visitor-keys": "8.47.0", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/type-utils": "8.54.0", + "@typescript-eslint/utils": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6792,7 +3242,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.47.0", + "@typescript-eslint/parser": "^8.54.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -6808,17 +3258,17 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.47.0.tgz", - "integrity": "sha512-lJi3PfxVmo0AkEY93ecfN+r8SofEqZNGByvHAI3GBLrvt1Cw6H5k1IM02nSzu0RfUafr2EvFSw0wAsZgubNplQ==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", + "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.47.0", - "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/typescript-estree": "8.47.0", - "@typescript-eslint/visitor-keys": "8.47.0", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6833,15 +3283,15 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.47.0.tgz", - "integrity": "sha512-2X4BX8hUeB5JcA1TQJ7GjcgulXQ+5UkNb0DL8gHsHUHdFoiCTJoYLTpib3LtSDPZsRET5ygN4qqIWrHyYIKERA==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", + "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.47.0", - "@typescript-eslint/types": "^8.47.0", - "debug": "^4.3.4" + "@typescript-eslint/tsconfig-utils": "^8.54.0", + "@typescript-eslint/types": "^8.54.0", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6855,14 +3305,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.47.0.tgz", - "integrity": "sha512-a0TTJk4HXMkfpFkL9/WaGTNuv7JWfFTQFJd6zS9dVAjKsojmv9HT55xzbEpnZoY+VUb+YXLMp+ihMLz/UlZfDg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", + "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/visitor-keys": "8.47.0" + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6873,9 +3323,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.47.0.tgz", - "integrity": "sha512-ybUAvjy4ZCL11uryalkKxuT3w3sXJAuWhOoGS3T/Wu+iUu1tGJmk5ytSY8gbdACNARmcYEB0COksD2j6hfGK2g==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", + "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", "dev": true, "license": "MIT", "engines": { @@ -6890,17 +3340,17 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.47.0.tgz", - "integrity": "sha512-QC9RiCmZ2HmIdCEvhd1aJELBlD93ErziOXXlHEZyuBo3tBiAZieya0HLIxp+DoDWlsQqDawyKuNEhORyku+P8A==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", + "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/typescript-estree": "8.47.0", - "@typescript-eslint/utils": "8.47.0", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/utils": "8.54.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6915,9 +3365,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.47.0.tgz", - "integrity": "sha512-nHAE6bMKsizhA2uuYZbEbmp5z2UpffNrPEqiKIeN7VsV6UY/roxanWfoRrf6x/k9+Obf+GQdkm0nPU+vnMXo9A==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", + "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", "dev": true, "license": "MIT", "engines": { @@ -6929,22 +3379,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.47.0.tgz", - "integrity": "sha512-k6ti9UepJf5NpzCjH31hQNLHQWupTRPhZ+KFF8WtTuTpy7uHPfeg2NM7cP27aCGajoEplxJDFVCEm9TGPYyiVg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", + "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.47.0", - "@typescript-eslint/tsconfig-utils": "8.47.0", - "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/visitor-keys": "8.47.0", - "debug": "^4.3.4", - "fast-glob": "^3.3.2", - "is-glob": "^4.0.3", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/project-service": "8.54.0", + "@typescript-eslint/tsconfig-utils": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6984,16 +3433,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.47.0.tgz", - "integrity": "sha512-g7XrNf25iL4TJOiPqatNuaChyqt49a/onq5YsJ9+hXeugK+41LVg7AxikMfM02PC6jbNtZLCJj6AUcQXJS/jGQ==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", + "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.47.0", - "@typescript-eslint/types": "8.47.0", - "@typescript-eslint/typescript-estree": "8.47.0" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7008,13 +3457,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.47.0.tgz", - "integrity": "sha512-SIV3/6eftCy1bNzCQoPmbWsRLujS8t5iDIZ4spZOBHqrM+yfX2ogg8Tt3PDTAVKw3sSCiUgg30uOAvK2r9zGjQ==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", + "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.47.0", + "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -7025,19 +3474,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", @@ -7313,27 +3749,6 @@ "win32" ] }, - "node_modules/@vitejs/plugin-react": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.1.tgz", - "integrity": "sha512-WQfkSw0QbQ5aJ2CHYw23ZGkqnRwqKHD/KYsMeTkZzPT4Jcf0DcBxBtwMJxnu6E7oxw5+JC6ZAiePgh28uJ1HBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.5", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.47", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.18.0" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, "node_modules/@vitest/coverage-v8": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-3.2.4.tgz", @@ -7505,164 +3920,6 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@webassemblyjs/ast": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", - "integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz", - "integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz", - "integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz", - "integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz", - "integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.13.2", - "@webassemblyjs/helper-api-error": "1.13.2", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz", - "integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz", - "integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/wasm-gen": "1.14.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz", - "integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==", - "license": "MIT", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz", - "integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==", - "license": "Apache-2.0", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz", - "integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==", - "license": "MIT" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz", - "integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/helper-wasm-section": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-opt": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1", - "@webassemblyjs/wast-printer": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz", - "integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz", - "integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-buffer": "1.14.1", - "@webassemblyjs/wasm-gen": "1.14.1", - "@webassemblyjs/wasm-parser": "1.14.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz", - "integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@webassemblyjs/helper-api-error": "1.13.2", - "@webassemblyjs/helper-wasm-bytecode": "1.13.2", - "@webassemblyjs/ieee754": "1.13.2", - "@webassemblyjs/leb128": "1.13.2", - "@webassemblyjs/utf8": "1.13.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz", - "integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==", - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.14.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "license": "BSD-3-Clause" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "license": "Apache-2.0" - }, "node_modules/abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -7675,32 +3932,11 @@ "node": ">=6.5" } }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/accepts/node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -7709,48 +3945,16 @@ "node": ">=0.4.0" } }, - "node_modules/acorn-import-phases": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", - "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", - "license": "MIT", - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "acorn": "^8.14.0" - } - }, "node_modules/acorn-jsx": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/address": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz", - "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -7773,23 +3977,11 @@ "node": ">= 8.0.0" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "license": "MIT", - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -7802,126 +3994,11 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/ansi-align": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", - "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", - "license": "ISC", - "dependencies": { - "string-width": "^4.1.0" - } - }, - "node_modules/ansi-align/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/ansi-align/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/ansi-html-community/-/ansi-html-community-0.0.8.tgz", - "integrity": "sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==", - "engines": [ - "node >= 0.8.0" - ], - "license": "Apache-2.0", - "bin": { - "ansi-html": "bin/ansi-html" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -7931,6 +4008,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -7943,9 +4021,9 @@ } }, "node_modules/antd": { - "version": "5.29.1", - "resolved": "https://registry.npmjs.org/antd/-/antd-5.29.1.tgz", - "integrity": "sha512-TTFVbpKbyL6cPfEoKq6Ya3BIjTUr7uDW9+7Z+1oysRv1gpcN7kQ4luH8r/+rXXwz4n6BIz1iBJ1ezKCdsdNW0w==", + "version": "5.29.3", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.29.3.tgz", + "integrity": "sha512-3DdbGCa9tWAJGcCJ6rzR8EJFsv2CtyEbkVabZE14pfgUHfCicWCj0/QzQVLDYg8CPfQk9BH7fHCoTXHTy7MP/A==", "license": "MIT", "dependencies": { "@ant-design/colors": "^7.2.1", @@ -8018,6 +4096,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -8038,6 +4117,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, "license": "Python-2.0" }, "node_modules/aria-hidden": { @@ -8079,12 +4159,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, "node_modules/array-includes": { "version": "3.1.9", "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", @@ -8108,15 +4182,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/array.prototype.findlast": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", @@ -8255,33 +4320,24 @@ "license": "MIT" }, "node_modules/ast-v8-to-istanbul": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.8.tgz", - "integrity": "sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.11.tgz", + "integrity": "sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", - "js-tokens": "^9.0.1" + "js-tokens": "^10.0.0" } }, "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", "dev": true, "license": "MIT" }, - "node_modules/astring": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/astring/-/astring-1.9.0.tgz", - "integrity": "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==", - "license": "MIT", - "bin": { - "astring": "bin/astring" - } - }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -8299,9 +4355,10 @@ "license": "MIT" }, "node_modules/autoprefixer": { - "version": "10.4.22", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz", - "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==", + "version": "10.4.24", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz", + "integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==", + "dev": true, "funding": [ { "type": "opencollective", @@ -8318,10 +4375,9 @@ ], "license": "MIT", "dependencies": { - "browserslist": "^4.27.0", - "caniuse-lite": "^1.0.30001754", + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001766", "fraction.js": "^5.3.4", - "normalize-range": "^0.1.2", "picocolors": "^1.1.1", "postcss-value-parser": "^4.2.0" }, @@ -8352,9 +4408,9 @@ } }, "node_modules/axe-core": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.0.tgz", - "integrity": "sha512-ilYanEU8vxxBexpJd8cWM4ElSQq4QctCLKih0TSfjIfCQTeyH/6zVrmIJfLPrKTKJRbiG+cfnZbQIjAlJmF1jQ==", + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz", + "integrity": "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==", "dev": true, "license": "MPL-2.0", "engines": { @@ -8362,9 +4418,9 @@ } }, "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", + "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", "dev": true, "license": "MIT", "dependencies": { @@ -8383,80 +4439,6 @@ "node": ">= 0.4" } }, - "node_modules/babel-loader": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.2.1.tgz", - "integrity": "sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==", - "license": "MIT", - "dependencies": { - "find-cache-dir": "^4.0.0", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 14.15.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0", - "webpack": ">=5" - } - }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "license": "MIT", - "dependencies": { - "object.assign": "^4.1.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", - "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.7", - "@babel/helper-define-polyfill-provider": "^0.6.5", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", - "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5", - "core-js-compat": "^3.43.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", - "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, "node_modules/bail": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", @@ -8471,23 +4453,18 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.30", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.30.tgz", - "integrity": "sha512-aTUKW4ptQhS64+v2d6IkPzymEzzhw+G0bA1g3uBRV3+ntkH+svttKseW5IOR4Ed6NUVKqnY7qT3dKvzQ7io4AA==", + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" } }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "license": "MIT" - }, "node_modules/bidi-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", @@ -8498,19 +4475,11 @@ "require-from-string": "^2.0.2" } }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -8519,137 +4488,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.14.0", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/body-parser/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/bonjour-service": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.3.0.tgz", - "integrity": "sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "license": "ISC" - }, - "node_modules/boxen": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-6.2.1.tgz", - "integrity": "sha512-H4PEsJXfFI/Pt8sjDWbHlQPx4zL/bvSQjcilJmaulGt5mLDorHOHpmdXAJcBcmru7PhYSp/cDMWRko4ZUMFkSw==", - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^6.2.0", - "chalk": "^4.1.2", - "cli-boxes": "^3.0.0", - "string-width": "^5.0.1", - "type-fest": "^2.5.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.0.1" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -8660,6 +4503,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, "license": "MIT", "dependencies": { "fill-range": "^7.1.1" @@ -8669,9 +4513,10 @@ } }, "node_modules/browserslist": { - "version": "4.28.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz", - "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==", + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, "funding": [ { "type": "opencollective", @@ -8688,11 +4533,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.25", - "caniuse-lite": "^1.0.30001754", - "electron-to-chromium": "^1.5.249", + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", - "update-browserslist-db": "^1.1.4" + "update-browserslist-db": "^1.2.0" }, "bin": { "browserslist": "cli.js" @@ -8701,53 +4546,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer-equal-constant-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", - "license": "BSD-3-Clause" - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT" - }, - "node_modules/bundle-name": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", - "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", - "license": "MIT", - "dependencies": { - "run-applescript": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -8758,37 +4556,11 @@ "node": ">=8" } }, - "node_modules/cacheable-lookup": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", - "license": "MIT", - "engines": { - "node": ">=14.16" - } - }, - "node_modules/cacheable-request": { - "version": "10.2.14", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", - "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", - "license": "MIT", - "dependencies": { - "@types/http-cache-semantics": "^4.0.2", - "get-stream": "^6.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.3", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - } - }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.0", @@ -8820,6 +4592,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -8836,33 +4609,12 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/camel-case": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", - "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", - "license": "MIT", - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -8873,22 +4625,10 @@ "node": ">= 6" } }, - "node_modules/caniuse-api": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", - "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.0.0", - "caniuse-lite": "^1.0.0", - "lodash.memoize": "^4.1.2", - "lodash.uniq": "^4.5.0" - } - }, "node_modules/caniuse-lite": { - "version": "1.0.30001756", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz", - "integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==", + "version": "1.0.30001766", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", "funding": [ { "type": "opencollective", @@ -8936,6 +4676,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -8948,15 +4689,6 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/character-entities": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", @@ -8998,45 +4730,20 @@ } }, "node_modules/check-error": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", - "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", "dev": true, "license": "MIT", "engines": { "node": ">= 16" } }, - "node_modules/chevrotain": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", - "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", - "license": "Apache-2.0", - "dependencies": { - "@chevrotain/cst-dts-gen": "11.0.3", - "@chevrotain/gast": "11.0.3", - "@chevrotain/regexp-to-ast": "11.0.3", - "@chevrotain/types": "11.0.3", - "@chevrotain/utils": "11.0.3", - "lodash-es": "4.17.21" - } - }, - "node_modules/chevrotain-allstar": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", - "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", - "license": "MIT", - "dependencies": { - "lodash-es": "^4.17.21" - }, - "peerDependencies": { - "chevrotain": "^11.0.0" - } - }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -9057,28 +4764,17 @@ "fsevents": "~2.3.2" } }, - "node_modules/chrome-trace-event": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", - "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", - "license": "MIT", + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, "engines": { - "node": ">=6.0" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" + "node": ">= 6" } }, "node_modules/classnames": { @@ -9087,103 +4783,12 @@ "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", "license": "MIT" }, - "node_modules/clean-css": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz", - "integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==", - "license": "MIT", - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 10.0" - } - }, - "node_modules/clean-css/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/cli-boxes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", - "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", - "license": "MIT", - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, - "node_modules/cli-table3/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/cli-table3/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", "license": "MIT" }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/clsx": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", @@ -9193,20 +4798,11 @@ "node": ">=6" } }, - "node_modules/collapse-white-space": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", - "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -9219,29 +4815,9 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, - "node_modules/colord": { - "version": "2.9.3", - "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", - "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "license": "MIT" - }, - "node_modules/combine-promises": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/combine-promises/-/combine-promises-1.2.0.tgz", - "integrity": "sha512-VcQB1ziGD0NXrhKxiwyNbCDmRzs/OShMs2GqW2DlU2A/Sd0nQxE1oWDAE5O0ygSx5mgQOn9eIFh7yKPgFRVkPQ==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -9265,74 +4841,15 @@ } }, "node_modules/commander": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", - "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 6" } }, - "node_modules/common-path-prefix": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", - "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==", - "license": "ISC" - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "license": "MIT", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", - "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "compressible": "~2.0.18", - "debug": "2.6.9", - "negotiator": "~0.6.4", - "on-headers": "~1.1.0", - "safe-buffer": "5.2.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/compute-scroll-into-view": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.1.tgz", @@ -9343,104 +4860,7 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "license": "MIT" - }, - "node_modules/confbox": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", - "integrity": "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==", - "license": "MIT" - }, - "node_modules/config-chain": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", - "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", - "license": "MIT", - "dependencies": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" - } - }, - "node_modules/config-chain/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/configstore": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-6.0.0.tgz", - "integrity": "sha512-cD31W1v3GqUlQvbBCGcXmd2Nj9SvLDOP1oQ0YFuLETufzSPaKp11rYBsSOm7rCsW3OnIRAFM3OxRhceaXNYHkA==", - "license": "BSD-2-Clause", - "dependencies": { - "dot-prop": "^6.0.1", - "graceful-fs": "^4.2.6", - "unique-string": "^3.0.0", - "write-file-atomic": "^3.0.3", - "xdg-basedir": "^5.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/yeoman/configstore?sponsor=1" - } - }, - "node_modules/connect-history-api-fallback": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-2.0.0.tgz", - "integrity": "sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==", - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", - "license": "MIT", - "engines": { - "node": "^14.18.0 || >=16.10.0" - } - }, - "node_modules/content-disposition": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", - "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "dev": true, "license": "MIT" }, "node_modules/copy-to-clipboard": { @@ -9452,153 +4872,11 @@ "toggle-selection": "^1.0.6" } }, - "node_modules/copy-webpack-plugin": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-11.0.0.tgz", - "integrity": "sha512-fX2MWpamkW0hZxMEg0+mYnA40LTosOSa5TqZ9GYIBzyJa9C3QUaMPSE2xAi/buNr8u89SfD9wHSQVBzrRa/SOQ==", - "license": "MIT", - "dependencies": { - "fast-glob": "^3.2.11", - "glob-parent": "^6.0.1", - "globby": "^13.1.1", - "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/globby": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz", - "integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==", - "license": "MIT", - "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.3.0", - "ignore": "^5.2.4", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/copy-webpack-plugin/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/core-js": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.47.0.tgz", - "integrity": "sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==", - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-compat": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.47.0.tgz", - "integrity": "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.28.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-pure": { - "version": "3.47.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.47.0.tgz", - "integrity": "sha512-BcxeDbzUrRnXGYIVAGFtcGQVNpFcUhVjr6W7F8XktvQW2iJP9e66GP6xdKotCRFlrxBvNIBrhwKteRXqMV86Nw==", - "hasInstallScript": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, - "node_modules/cose-base": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", - "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", - "license": "MIT", - "dependencies": { - "layout-base": "^1.0.0" - } - }, - "node_modules/cosmiconfig": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", - "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", - "license": "MIT", - "dependencies": { - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0", - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -9609,262 +4887,6 @@ "node": ">= 8" } }, - "node_modules/crypto-random-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-4.0.0.tgz", - "integrity": "sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==", - "license": "MIT", - "dependencies": { - "type-fest": "^1.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/crypto-random-string/node_modules/type-fest": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz", - "integrity": "sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/css-blank-pseudo": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-7.0.1.tgz", - "integrity": "sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-blank-pseudo/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/css-declaration-sorter": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-7.3.0.tgz", - "integrity": "sha512-LQF6N/3vkAMYF4xoHLJfG718HRJh34Z8BnNhd6bosOMIVjMlhuZK5++oZa3uYAgrI5+7x2o27gUqTR2U/KjUOQ==", - "license": "ISC", - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "postcss": "^8.0.9" - } - }, - "node_modules/css-has-pseudo": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-7.0.3.tgz", - "integrity": "sha512-oG+vKuGyqe/xvEMoxAQrhi7uY16deJR3i7wwhBerVrGQKSqUC5GiOVxTpM9F9B9hw0J+eKeOWLH7E9gZ1Dr5rA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-has-pseudo/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/css-has-pseudo/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/css-loader": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", - "integrity": "sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==", - "license": "MIT", - "dependencies": { - "icss-utils": "^5.1.0", - "postcss": "^8.4.33", - "postcss-modules-extract-imports": "^3.1.0", - "postcss-modules-local-by-default": "^4.0.5", - "postcss-modules-scope": "^3.2.0", - "postcss-modules-values": "^4.0.0", - "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/css-minimizer-webpack-plugin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", - "integrity": "sha512-3caImjKFQkS+ws1TGcFn0V1HyDJFq1Euy589JlD6/3rV2kj+w7r5G9WDMgSHvpvXHNZ2calVypZWuEDQd9wfLg==", - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "cssnano": "^6.0.1", - "jest-worker": "^29.4.3", - "postcss": "^8.4.24", - "schema-utils": "^4.0.1", - "serialize-javascript": "^6.0.1" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@parcel/css": { - "optional": true - }, - "@swc/css": { - "optional": true - }, - "clean-css": { - "optional": true - }, - "csso": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "lightningcss": { - "optional": true - } - } - }, - "node_modules/css-prefers-color-scheme": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-10.0.0.tgz", - "integrity": "sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/css-select": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.3.0.tgz", - "integrity": "sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, "node_modules/css-tree": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", @@ -9879,18 +4901,6 @@ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" } }, - "node_modules/css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, "node_modules/css.escape": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", @@ -9898,26 +4908,11 @@ "dev": true, "license": "MIT" }, - "node_modules/cssdb": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-8.4.2.tgz", - "integrity": "sha512-PzjkRkRUS+IHDJohtxkIczlxPPZqRo0nXplsYXOMBRPjcVRjj1W4DfvRgshUYTVuUigU7ptVYkFJQ7abUB0nyg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - }, - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - } - ], - "license": "MIT-0" - }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, "license": "MIT", "bin": { "cssesc": "bin/cssesc" @@ -9926,146 +4921,17 @@ "node": ">=4" } }, - "node_modules/cssnano": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-6.1.2.tgz", - "integrity": "sha512-rYk5UeX7VAM/u0lNqewCdasdtPK81CgX8wJFLEIXHbV2oldWRgJAsZrdhRXkV1NJzA2g850KiFm9mMU2HxNxMA==", - "license": "MIT", - "dependencies": { - "cssnano-preset-default": "^6.1.2", - "lilconfig": "^3.1.1" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/cssnano" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/cssnano-preset-advanced": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-advanced/-/cssnano-preset-advanced-6.1.2.tgz", - "integrity": "sha512-Nhao7eD8ph2DoHolEzQs5CfRpiEP0xa1HBdnFZ82kvqdmbwVBUr2r1QuQ4t1pi+D1ZpqpcO4T+wy/7RxzJ/WPQ==", - "license": "MIT", - "dependencies": { - "autoprefixer": "^10.4.19", - "browserslist": "^4.23.0", - "cssnano-preset-default": "^6.1.2", - "postcss-discard-unused": "^6.0.5", - "postcss-merge-idents": "^6.0.3", - "postcss-reduce-idents": "^6.0.3", - "postcss-zindex": "^6.0.2" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/cssnano-preset-default": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-6.1.2.tgz", - "integrity": "sha512-1C0C+eNaeN8OcHQa193aRgYexyJtU8XwbdieEjClw+J9d94E41LwT6ivKH0WT+fYwYWB0Zp3I3IZ7tI/BbUbrg==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "css-declaration-sorter": "^7.2.0", - "cssnano-utils": "^4.0.2", - "postcss-calc": "^9.0.1", - "postcss-colormin": "^6.1.0", - "postcss-convert-values": "^6.1.0", - "postcss-discard-comments": "^6.0.2", - "postcss-discard-duplicates": "^6.0.3", - "postcss-discard-empty": "^6.0.3", - "postcss-discard-overridden": "^6.0.2", - "postcss-merge-longhand": "^6.0.5", - "postcss-merge-rules": "^6.1.1", - "postcss-minify-font-values": "^6.1.0", - "postcss-minify-gradients": "^6.0.3", - "postcss-minify-params": "^6.1.0", - "postcss-minify-selectors": "^6.0.4", - "postcss-normalize-charset": "^6.0.2", - "postcss-normalize-display-values": "^6.0.2", - "postcss-normalize-positions": "^6.0.2", - "postcss-normalize-repeat-style": "^6.0.2", - "postcss-normalize-string": "^6.0.2", - "postcss-normalize-timing-functions": "^6.0.2", - "postcss-normalize-unicode": "^6.1.0", - "postcss-normalize-url": "^6.0.2", - "postcss-normalize-whitespace": "^6.0.2", - "postcss-ordered-values": "^6.0.2", - "postcss-reduce-initial": "^6.1.0", - "postcss-reduce-transforms": "^6.0.2", - "postcss-svgo": "^6.0.3", - "postcss-unique-selectors": "^6.0.4" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/cssnano-utils": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-4.0.2.tgz", - "integrity": "sha512-ZR1jHg+wZ8o4c3zqf1SIUSTIvm/9mU343FMR6Obe/unskbvpGhZOo1J6d/r8D1pzkRQYuwbcH3hToOuoA2G7oQ==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/csso": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", - "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", - "license": "MIT", - "dependencies": { - "css-tree": "~2.2.0" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/css-tree": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", - "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.28", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", - "npm": ">=7.0.0" - } - }, - "node_modules/csso/node_modules/mdn-data": { - "version": "2.0.28", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", - "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", - "license": "CC0-1.0" - }, "node_modules/cssstyle": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.3.tgz", - "integrity": "sha512-OytmFH+13/QXONJcC75QNdMtKpceNk3u8ThBjyyYjkEcy/ekBwR1mMAuNvi3gdBPW3N5TlCzQ0WZw8H0lN/bDw==", + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz", + "integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==", "dev": true, "license": "MIT", "dependencies": { - "@asamuzakjp/css-color": "^4.0.3", - "@csstools/css-syntax-patches-for-csstree": "^1.0.14", - "css-tree": "^3.1.0" + "@asamuzakjp/css-color": "^4.1.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.21", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.4" }, "engines": { "node": ">=20" @@ -10097,95 +4963,6 @@ } } }, - "node_modules/cytoscape": { - "version": "3.33.1", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", - "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/cytoscape-cose-bilkent": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", - "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", - "license": "MIT", - "dependencies": { - "cose-base": "^1.0.0" - }, - "peerDependencies": { - "cytoscape": "^3.2.0" - } - }, - "node_modules/cytoscape-fcose": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", - "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", - "license": "MIT", - "dependencies": { - "cose-base": "^2.2.0" - }, - "peerDependencies": { - "cytoscape": "^3.2.0" - } - }, - "node_modules/cytoscape-fcose/node_modules/cose-base": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", - "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", - "license": "MIT", - "dependencies": { - "layout-base": "^2.0.0" - } - }, - "node_modules/cytoscape-fcose/node_modules/layout-base": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", - "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", - "license": "MIT" - }, - "node_modules/d3": { - "version": "7.9.0", - "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", - "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", - "license": "ISC", - "dependencies": { - "d3-array": "3", - "d3-axis": "3", - "d3-brush": "3", - "d3-chord": "3", - "d3-color": "3", - "d3-contour": "4", - "d3-delaunay": "6", - "d3-dispatch": "3", - "d3-drag": "3", - "d3-dsv": "3", - "d3-ease": "3", - "d3-fetch": "3", - "d3-force": "3", - "d3-format": "3", - "d3-geo": "3", - "d3-hierarchy": "3", - "d3-interpolate": "3", - "d3-path": "3", - "d3-polygon": "3", - "d3-quadtree": "3", - "d3-random": "3", - "d3-scale": "4", - "d3-scale-chromatic": "3", - "d3-selection": "3", - "d3-shape": "3", - "d3-time": "3", - "d3-time-format": "4", - "d3-timer": "3", - "d3-transition": "3", - "d3-zoom": "3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/d3-array": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", @@ -10198,43 +4975,6 @@ "node": ">=12" } }, - "node_modules/d3-axis": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", - "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-brush": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", - "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "3", - "d3-transition": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-chord": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", - "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", - "license": "ISC", - "dependencies": { - "d3-path": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/d3-color": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", @@ -10244,86 +4984,6 @@ "node": ">=12" } }, - "node_modules/d3-contour": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", - "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", - "license": "ISC", - "dependencies": { - "d3-array": "^3.2.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-delaunay": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", - "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", - "license": "ISC", - "dependencies": { - "delaunator": "5" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dispatch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", - "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-drag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", - "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-selection": "3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dsv": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", - "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", - "license": "ISC", - "dependencies": { - "commander": "7", - "iconv-lite": "0.6", - "rw": "1" - }, - "bin": { - "csv2json": "bin/dsv2json.js", - "csv2tsv": "bin/dsv2dsv.js", - "dsv2dsv": "bin/dsv2dsv.js", - "dsv2json": "bin/dsv2json.js", - "json2csv": "bin/json2dsv.js", - "json2dsv": "bin/json2dsv.js", - "json2tsv": "bin/json2dsv.js", - "tsv2csv": "bin/dsv2dsv.js", - "tsv2json": "bin/dsv2json.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-dsv/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, "node_modules/d3-ease": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", @@ -10333,57 +4993,10 @@ "node": ">=12" } }, - "node_modules/d3-fetch": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", - "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", - "license": "ISC", - "dependencies": { - "d3-dsv": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-force": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", - "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-quadtree": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-geo": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", - "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", - "license": "ISC", - "dependencies": { - "d3-array": "2.5.0 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-hierarchy": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", - "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", "license": "ISC", "engines": { "node": ">=12" @@ -10410,73 +5023,6 @@ "node": ">=12" } }, - "node_modules/d3-polygon": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", - "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-quadtree": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", - "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-random": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", - "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-sankey": { - "version": "0.12.3", - "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", - "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", - "license": "BSD-3-Clause", - "dependencies": { - "d3-array": "1 - 2", - "d3-shape": "^1.2.0" - } - }, - "node_modules/d3-sankey/node_modules/d3-array": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", - "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", - "license": "BSD-3-Clause", - "dependencies": { - "internmap": "^1.0.0" - } - }, - "node_modules/d3-sankey/node_modules/d3-path": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", - "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", - "license": "BSD-3-Clause" - }, - "node_modules/d3-sankey/node_modules/d3-shape": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", - "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", - "license": "BSD-3-Clause", - "dependencies": { - "d3-path": "1" - } - }, - "node_modules/d3-sankey/node_modules/internmap": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", - "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", - "license": "ISC" - }, "node_modules/d3-scale": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", @@ -10493,28 +5039,6 @@ "node": ">=12" } }, - "node_modules/d3-scale-chromatic": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", - "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3", - "d3-interpolate": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-selection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", - "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/d3-shape": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", @@ -10560,51 +5084,6 @@ "node": ">=12" } }, - "node_modules/d3-transition": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", - "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", - "license": "ISC", - "dependencies": { - "d3-color": "1 - 3", - "d3-dispatch": "1 - 3", - "d3-ease": "1 - 3", - "d3-interpolate": "1 - 3", - "d3-timer": "1 - 3" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "d3-selection": "2 - 3" - } - }, - "node_modules/d3-zoom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", - "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", - "license": "ISC", - "dependencies": { - "d3-dispatch": "1 - 3", - "d3-drag": "2 - 3", - "d3-interpolate": "1 - 3", - "d3-selection": "2 - 3", - "d3-transition": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/dagre-d3-es": { - "version": "7.0.13", - "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz", - "integrity": "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==", - "license": "MIT", - "dependencies": { - "d3": "^7.9.0", - "lodash-es": "^4.17.21" - } - }, "node_modules/damerau-levenshtein": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", @@ -10613,19 +5092,29 @@ "license": "BSD-2-Clause" }, "node_modules/data-urls": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.0.tgz", - "integrity": "sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.1.tgz", + "integrity": "sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==", "dev": true, "license": "MIT", "dependencies": { - "whatwg-mimetype": "^4.0.0", - "whatwg-url": "^15.0.0" + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^15.1.0" }, "engines": { "node": ">=20" } }, + "node_modules/data-urls/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -10696,12 +5185,6 @@ "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", "license": "MIT" }, - "node_modules/debounce": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", - "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", - "license": "MIT" - }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -10733,9 +5216,9 @@ "license": "MIT" }, "node_modules/decode-named-character-reference": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", - "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", "license": "MIT", "dependencies": { "character-entities": "^2.0.0" @@ -10745,33 +5228,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "license": "MIT", - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/deep-eql": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", @@ -10782,15 +5238,6 @@ "node": ">=6" } }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -10798,47 +5245,11 @@ "dev": true, "license": "MIT" }, - "node_modules/default-browser": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.4.0.tgz", - "integrity": "sha512-XDuvSq38Hr1MdN47EDvYtx3U0MTqpCEn+F6ft8z2vYDzMrvQhVp0ui9oQdqW3MvK3vqUETglt1tVGgjLuJ5izg==", - "license": "MIT", - "dependencies": { - "bundle-name": "^4.1.0", - "default-browser-id": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-browser-id": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", - "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -10852,19 +5263,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/define-properties": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.0.1", @@ -10878,15 +5281,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/delaunator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", - "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", - "license": "ISC", - "dependencies": { - "robust-predicates": "^3.0.2" - } - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -10896,15 +5290,6 @@ "node": ">=0.4.0" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/dequal": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", @@ -10914,37 +5299,14 @@ "node": ">=6" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "optional": true, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "license": "MIT" - }, - "node_modules/detect-port": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/detect-port/-/detect-port-1.6.1.tgz", - "integrity": "sha512-CmnVc+Hek2egPx1PeTFVta2W78xy2K/9Rkf6cC4T59S50tVnzKj+tnx5mmx5lwvCkujZ4uRrpRSuV+IVs3f90Q==", - "license": "MIT", - "dependencies": { - "address": "^1.0.1", - "debug": "4" - }, - "bin": { - "detect": "bin/detect-port.js", - "detect-port": "bin/detect-port.js" - }, - "engines": { - "node": ">= 4.0.0" + "node": ">=8" } }, "node_modules/devlop": { @@ -10967,18 +5329,6 @@ "dev": true, "license": "Apache-2.0" }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -10986,29 +5336,17 @@ "dev": true, "license": "MIT" }, - "node_modules/dns-packet": { - "version": "5.6.1", - "resolved": "https://registry.npmjs.org/dns-packet/-/dns-packet-5.6.1.tgz", - "integrity": "sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==", - "license": "MIT", - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "license": "Apache-2.0", "dependencies": { "esutils": "^2.0.2" }, "engines": { - "node": ">=6.0.0" + "node": ">=0.10.0" } }, "node_modules/dom-accessibility-api": { @@ -11018,15 +5356,6 @@ "dev": true, "license": "MIT" }, - "node_modules/dom-converter": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", - "integrity": "sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==", - "license": "MIT", - "dependencies": { - "utila": "~0.4" - } - }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -11037,113 +5366,6 @@ "csstype": "^3.0.2" } }, - "node_modules/dom-serializer": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", - "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/dom-serializer/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "license": "BSD-2-Clause", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", - "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/dompurify": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.0.tgz", - "integrity": "sha512-r+f6MYR1gGN1eJv0TVQbhA7if/U7P87cdPl3HN5rikqaBSBxLiCb/b9O+2eG0cxz0ghyU+mU1QkbsOwERMYlWQ==", - "license": "(MPL-2.0 OR Apache-2.0)", - "optionalDependencies": { - "@types/trusted-types": "^2.0.7" - } - }, - "node_modules/domutils": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", - "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dot-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", - "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/dot-prop": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", - "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", - "license": "MIT", - "dependencies": { - "is-obj": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/dot-prop/node_modules/is-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", - "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/dotenv": { "version": "17.2.3", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", @@ -11171,96 +5393,25 @@ "node": ">= 0.4" } }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "license": "MIT" - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "license": "MIT" - }, - "node_modules/ecdsa-sig-formatter": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, "node_modules/electron-to-chromium": { - "version": "1.5.259", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.259.tgz", - "integrity": "sha512-I+oLXgpEJzD6Cwuwt1gYjxsDmu/S/Kd41mmLA3O+/uH2pFRO/DvOjUyGozL8j3KeLV6WyZ7ssPwELMsXCcsJAQ==", + "version": "1.5.283", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz", + "integrity": "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==", + "dev": true, "license": "ISC" }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, "license": "MIT" }, - "node_modules/emojilib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/emojilib/-/emojilib-2.4.0.tgz", - "integrity": "sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==", - "license": "MIT" - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/emoticon": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/emoticon/-/emoticon-4.1.0.tgz", - "integrity": "sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.18.3", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.3.tgz", - "integrity": "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -11269,19 +5420,10 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/error-ex": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", - "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, "node_modules/es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", "dev": true, "license": "MIT", "dependencies": { @@ -11366,27 +5508,27 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", - "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", + "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", - "call-bound": "^1.0.3", + "call-bound": "^1.0.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", + "es-abstract": "^1.24.1", "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.3", + "es-set-tostringtag": "^2.1.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.6", + "get-intrinsic": "^1.3.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "internal-slot": "^1.1.0", - "iterator.prototype": "^1.1.4", + "iterator.prototype": "^1.1.5", "safe-array-concat": "^1.1.3" }, "engines": { @@ -11397,6 +5539,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, "license": "MIT" }, "node_modules/es-object-atoms": { @@ -11457,42 +5600,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esast-util-from-estree": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/esast-util-from-estree/-/esast-util-from-estree-2.0.0.tgz", - "integrity": "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-visit": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/esast-util-from-js": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz", - "integrity": "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "acorn": "^8.0.0", - "esast-util-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -11503,65 +5614,49 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/escape-goat": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-goat/-/escape-goat-4.0.0.tgz", - "integrity": "sha512-2Sd4ShcWxbx6OY1IHyla/CVNwvg7XwZVoXZHcSu9w9SReNP1EzzD5T8NWKIR38fIqEns9kDWKUQTXXAmlDrdPg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -11571,82 +5666,85 @@ } }, "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-config-next": { - "version": "14.2.32", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.32.tgz", - "integrity": "sha512-mP/NmYtDBsKlKIOBnH+CW+pYeyR3wBhE+26DAqQ0/aRtEBeTEjgY2wAFUugUELkTLmrX6PpuMSSTpOhz7j9kdQ==", + "version": "15.5.10", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.10.tgz", + "integrity": "sha512-AeYOVGiSbIfH4KXFT3d0fIDm7yTslR/AWGoHLdsXQ99MH0zFWmkRIin1H7I9SFlkKgf4PKm9ncsyWHq1aAfHBA==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "14.2.32", - "@rushstack/eslint-patch": "^1.3.3", + "@next/eslint-plugin-next": "15.5.10", + "@rushstack/eslint-patch": "^1.10.3", "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", - "eslint-plugin-import": "^2.28.1", - "eslint-plugin-jsx-a11y": "^6.7.1", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-hooks": "^4.5.0 || 5.0.0-canary-7118f5dd7-20230705" + "eslint-plugin-import": "^2.31.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^5.0.0" }, "peerDependencies": { - "eslint": "^7.23.0 || ^8.0.0", + "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", "typescript": ">=3.3.1" }, "peerDependenciesMeta": { @@ -11800,19 +5898,6 @@ "ms": "^2.1.1" } }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/eslint-plugin-import/node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -11897,29 +5982,16 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "5.0.0-canary-7118f5dd7-20230705", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.0.0-canary-7118f5dd7-20230705.tgz", - "integrity": "sha512-AZYbMo/NW9chdL7vk6HQzQhT+PvTAEVqWk9ziruUoW2kAOcN5qNyelv70e0F1VNQAbvutOC9oc+xfWycI9FxDw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", "dev": true, "license": "MIT", "engines": { "node": ">=10" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "node_modules/eslint-plugin-react/node_modules/resolve": { @@ -11967,9 +6039,9 @@ } }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -11977,60 +6049,47 @@ "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -12044,6 +6103,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" @@ -12056,40 +6116,12 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=4.0" } }, - "node_modules/estree-util-attach-comments": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz", - "integrity": "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-build-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/estree-util-build-jsx/-/estree-util-build-jsx-3.0.1.tgz", - "integrity": "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "estree-walker": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/estree-util-is-identifier-name": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", @@ -12100,65 +6132,11 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/estree-util-scope": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/estree-util-scope/-/estree-util-scope-1.0.0.tgz", - "integrity": "sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-to-js": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/estree-util-to-js/-/estree-util-to-js-2.0.0.tgz", - "integrity": "sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "astring": "^1.8.0", - "source-map": "^0.7.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/estree-util-value-to-estree": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/estree-util-value-to-estree/-/estree-util-value-to-estree-3.5.0.tgz", - "integrity": "sha512-aMV56R27Gv3QmfmF1MY12GWkGzzeAezAX+UplqHVASfjc9wNzI/X6hC0S9oxq61WT4aQesLGslWP9tKk6ghRZQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/remcohaszing" - } - }, - "node_modules/estree-util-visit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/estree-util-visit/-/estree-util-visit-2.0.0.tgz", - "integrity": "sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==", - "license": "MIT", - "dependencies": { - "@types/estree-jsx": "^1.0.0", - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "^1.0.0" @@ -12168,44 +6146,12 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, - "node_modules/eta": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/eta/-/eta-2.2.0.tgz", - "integrity": "sha512-UVQ72Rqjy/ZKQalzV5dCCJP80GrmPrMxh6NlNf+erV6ObL0ZFkhCstWRawS85z3smdr3d2wXPsZEY7rDPfGd2g==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - }, - "funding": { - "url": "https://github.com/eta-dev/eta?sponsor=1" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eval": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eval/-/eval-0.1.8.tgz", - "integrity": "sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==", - "dependencies": { - "@types/node": "*", - "require-like": ">= 0.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/event-target-shim": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", @@ -12221,195 +6167,73 @@ "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", "license": "MIT" }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, "node_modules/expect-type": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.2.tgz", - "integrity": "sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", "dev": true, "license": "Apache-2.0", "engines": { "node": ">=12.0.0" } }, - "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.3", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.12", - "proxy-addr": "~2.0.7", - "qs": "~6.14.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express/node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/express/node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/exsolve": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", - "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", - "license": "MIT" - }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "license": "MIT" }, - "node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "license": "MIT", - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, "license": "MIT" }, "node_modules/fast-equals": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.3.tgz", - "integrity": "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.8" + "micromatch": "^4.0.4" }, "engines": { "node": ">=8.6.0" } }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { @@ -12419,26 +6243,11 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" - }, "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, "license": "ISC", "dependencies": { "reusify": "^1.0.4" @@ -12457,16 +6266,22 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", - "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", - "license": "Apache-2.0", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, "node_modules/fflate": { @@ -12476,85 +6291,24 @@ "dev": true, "license": "MIT" }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/file-loader": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-6.2.0.tgz", - "integrity": "sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==", - "license": "MIT", - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/file-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "node": ">=16.0.0" } }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" @@ -12563,55 +6317,6 @@ "node": ">=8" } }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/find-cache-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-4.0.0.tgz", - "integrity": "sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==", - "license": "MIT", - "dependencies": { - "common-path-prefix": "^3.0.0", - "pkg-dir": "^7.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -12629,28 +6334,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } - }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { @@ -12664,6 +6359,7 @@ "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, "funding": [ { "type": "individual", @@ -12739,19 +6435,11 @@ "node": ">= 12.20" } }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/fraction.js": { "version": "5.3.4", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, "license": "MIT", "engines": { "node": "*" @@ -12761,39 +6449,11 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs": { - "version": "0.0.1-security", - "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz", - "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==", - "license": "ISC" - }, - "node_modules/fs-extra": { - "version": "11.3.2", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", - "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=14.14" - } - }, "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -12854,15 +6514,6 @@ "node": ">= 0.4" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -12887,12 +6538,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-own-enumerable-property-symbols": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", - "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", - "license": "ISC" - }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -12906,18 +6551,6 @@ "node": ">= 0.4" } }, - "node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/get-symbol-description": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", @@ -12937,9 +6570,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.1.tgz", + "integrity": "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w==", "dev": true, "license": "MIT", "dependencies": { @@ -12949,12 +6582,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/github-slugger": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-1.5.0.tgz", - "integrity": "sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==", - "license": "ISC" - }, "node_modules/glob": { "version": "13.0.0", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.0.tgz", @@ -12974,39 +6601,18 @@ } }, "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, "license": "ISC", "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" } }, - "node_modules/glob-to-regex.js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/glob-to-regex.js/-/glob-to-regex.js-1.2.0.tgz", - "integrity": "sha512-QMwlOQKU/IzqMUOAZWubUOT8Qft+Y0KQWnX9nK3ch0CJg0tTp4TvGZsTfudYKv2NzoQSyPcnA6TYeIQ3jGichQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "license": "BSD-2-Clause" - }, "node_modules/glob/node_modules/minimatch": { "version": "10.1.1", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", @@ -13023,45 +6629,14 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/global-dirs": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", - "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", - "license": "MIT", - "dependencies": { - "ini": "2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globals/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -13084,26 +6659,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -13116,107 +6671,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/got": { - "version": "12.6.1", - "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", - "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", - "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^5.2.0", - "@szmarczak/http-timer": "^5.0.1", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^10.2.8", - "decompress-response": "^6.0.0", - "form-data-encoder": "^2.1.2", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/got/node_modules/@sindresorhus/is": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", - "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/got/node_modules/form-data-encoder": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", - "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", - "license": "MIT", - "engines": { - "node": ">= 14.17" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, - "node_modules/gray-matter": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", - "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", - "license": "MIT", - "dependencies": { - "js-yaml": "^3.13.1", - "kind-of": "^6.0.2", - "section-matter": "^1.0.0", - "strip-bom-string": "^1.0.0" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/gzip-size": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", - "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", - "license": "MIT", - "dependencies": { - "duplexer": "^0.1.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hachure-fill": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", - "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", - "license": "MIT" - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", - "integrity": "sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==", - "license": "MIT" - }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -13234,6 +6688,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -13243,6 +6698,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -13294,18 +6750,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-yarn": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-3.0.0.tgz", - "integrity": "sha512-IrsVwUHhEULx3R8f/aA8AHuEzAorplsab/v8HBzEiIukwq5i/EC+xmOW+HfP1OaDP+2JkgT1yILHN2O3UFIbcA==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -13318,56 +6762,6 @@ "node": ">= 0.4" } }, - "node_modules/hast-util-from-parse5": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", - "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "hastscript": "^9.0.0", - "property-information": "^7.0.0", - "vfile": "^6.0.0", - "vfile-location": "^5.0.0", - "web-namespaces": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-from-parse5/node_modules/hast-util-parse-selector": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", - "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-from-parse5/node_modules/hastscript": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", - "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-parse-selector": "^4.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/hast-util-parse-selector": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz", @@ -13378,83 +6772,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-raw": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", - "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "@ungap/structured-clone": "^1.0.0", - "hast-util-from-parse5": "^8.0.0", - "hast-util-to-parse5": "^8.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "parse5": "^7.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-raw/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/hast-util-raw/node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/hast-util-to-estree": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz", - "integrity": "sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/estree-jsx": "^1.0.0", - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "estree-util-attach-comments": "^3.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "hast-util-whitespace": "^3.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "style-to-js": "^1.0.0", - "unist-util-position": "^5.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/hast-util-to-jsx-runtime": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", @@ -13482,35 +6799,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/hast-util-to-parse5": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", - "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "comma-separated-tokens": "^2.0.0", - "devlop": "^1.0.0", - "property-information": "^6.0.0", - "space-separated-tokens": "^2.0.0", - "web-namespaces": "^2.0.0", - "zwitch": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hast-util-to-parse5/node_modules/property-information": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz", - "integrity": "sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/hast-util-whitespace": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", @@ -13589,15 +6877,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, "node_modules/highlight.js": { "version": "10.7.3", "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz", @@ -13613,144 +6892,26 @@ "integrity": "sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==", "license": "CC0-1.0" }, - "node_modules/history": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/history/-/history-4.10.1.tgz", - "integrity": "sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.1.2", - "loose-envify": "^1.2.0", - "resolve-pathname": "^3.0.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0", - "value-equal": "^1.0.1" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "license": "BSD-3-Clause", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hoist-non-react-statics/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hpack.js/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/hpack.js/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/html-encoding-sniffer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", - "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", "dev": true, "license": "MIT", "dependencies": { - "whatwg-encoding": "^3.1.1" + "@exodus/bytes": "^1.6.0" }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, "license": "MIT" }, - "node_modules/html-minifier-terser": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-7.2.0.tgz", - "integrity": "sha512-tXgn3QfqPIpGl9o+K5tpcj3/MN4SfLtsx2GWwBC3SSd0tXQGyF3gsSqad8loJgKZGM3ZxbYDd5yhiBIdWpmvLA==", - "license": "MIT", - "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "~5.3.2", - "commander": "^10.0.0", - "entities": "^4.4.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.15.1" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": "^14.13.1 || >=16.0.0" - } - }, - "node_modules/html-minifier-terser/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/html-tags": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", - "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/html-url-attributes": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", @@ -13761,154 +6922,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/html-webpack-plugin": { - "version": "5.6.5", - "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.5.tgz", - "integrity": "sha512-4xynFbKNNk+WlzXeQQ+6YYsH2g7mpfPszQZUi3ovKlj+pDmngQ7vRXjrrmGROabmKwyQkcgcX5hqfOwHbFmK5g==", - "license": "MIT", - "dependencies": { - "@types/html-minifier-terser": "^6.0.0", - "html-minifier-terser": "^6.0.2", - "lodash": "^4.17.21", - "pretty-error": "^4.0.0", - "tapable": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/html-webpack-plugin" - }, - "peerDependencies": { - "@rspack/core": "0.x || 1.x", - "webpack": "^5.20.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } - } - }, - "node_modules/html-webpack-plugin/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/html-webpack-plugin/node_modules/html-minifier-terser": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", - "integrity": "sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==", - "license": "MIT", - "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/htmlparser2": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", - "integrity": "sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/htmlparser2/node_modules/entities": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", - "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", - "license": "BSD-2-Clause", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", - "license": "BSD-2-Clause" - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", - "license": "MIT" - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz", - "integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==", - "license": "MIT" - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "license": "MIT", - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -13923,55 +6936,6 @@ "node": ">= 14" } }, - "node_modules/http-proxy-middleware": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz", - "integrity": "sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==", - "license": "MIT", - "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } - } - }, - "node_modules/http-proxy-middleware/node_modules/is-plain-obj": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", - "license": "MIT", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, "node_modules/https-proxy-agent": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", @@ -13986,15 +6950,6 @@ "node": ">= 14" } }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, "node_modules/humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", @@ -14004,64 +6959,21 @@ "ms": "^2.0.0" } }, - "node_modules/hyperdyperid": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/hyperdyperid/-/hyperdyperid-1.2.0.tgz", - "integrity": "sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==", - "license": "MIT", - "engines": { - "node": ">=10.18" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/icss-utils": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", - "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "license": "ISC", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, "license": "MIT", "engines": { "node": ">= 4" } }, - "node_modules/image-size": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-2.0.2.tgz", - "integrity": "sha512-IRqXKlaXwgSMAMtpNzZa1ZAe8m+Sa1770Dhk8VkSsP9LS+iHD62Zd8FQKs8fbPiagBE7BzoFX23cxFnwshpV6w==", - "license": "MIT", - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=16.x" - } - }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, "license": "MIT", "dependencies": { "parent-module": "^1.0.0", @@ -14074,19 +6986,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/import-lazy": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", - "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -14096,26 +7000,12 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ini": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "license": "ISC", - "engines": { - "node": ">=10" - } - }, "node_modules/inline-style-parser": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", @@ -14146,24 +7036,6 @@ "node": ">=12" } }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/ipaddr.js": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", - "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, "node_modules/is-alphabetical": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", @@ -14206,12 +7078,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "license": "MIT" - }, "node_modules/is-async-function": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", @@ -14252,6 +7118,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -14300,22 +7167,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-ci": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", - "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", - "license": "MIT", - "dependencies": { - "ci-info": "^3.2.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, "node_modules/is-core-module": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -14372,34 +7228,11 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -14421,15 +7254,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-generator-function": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", @@ -14454,6 +7278,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -14472,55 +7297,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-inside-container": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", - "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", - "license": "MIT", - "dependencies": { - "is-docker": "^3.0.0" - }, - "bin": { - "is-inside-container": "cli.js" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-inside-container/node_modules/is-docker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", - "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-installed-globally": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", - "license": "MIT", - "dependencies": { - "global-dirs": "^3.0.0", - "is-path-inside": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -14547,34 +7323,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-network-error": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz", - "integrity": "sha512-6oIwpsgRfnDiyEDLMay/GqCl3HoAtH5+RUKW29gYkL0QA+ipzpDLA16yQs7/RHCSu+BwgbJaOUqa4A99qNVQVw==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-npm": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-6.1.0.tgz", - "integrity": "sha512-O2z4/kNgyjhQwVR1Wpkbfc19JIhggF97NZNCpWTnjH7kVcZMUrnut9XSN7txI7VdyIYk5ZatOq3zvSuWpU8hoA==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.12.0" @@ -14597,24 +7350,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-obj": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", @@ -14627,18 +7362,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-potential-custom-element-name": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", @@ -14665,15 +7388,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-set": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", @@ -14703,18 +7417,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-string": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", @@ -14766,12 +7468,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "license": "MIT" - }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -14818,48 +7514,20 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-yarn-global": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.4.1.tgz", - "integrity": "sha512-/kppl+R+LO5VmhYSEWARUFjodS25D68gvj8W7z0I7OWhUla5xWu8KL6CtB2V0R6yqhnRgbcaREMr4EEM6htLPQ==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, "node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, "license": "ISC" }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", @@ -14932,75 +7600,16 @@ "node": ">= 0.4" } }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/jiti": { "version": "1.21.7", "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, "license": "MIT", "bin": { "jiti": "bin/jiti.js" } }, - "node_modules/joi": { - "version": "17.13.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", - "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", - "license": "BSD-3-Clause", - "dependencies": { - "@hapi/hoek": "^9.3.0", - "@hapi/topo": "^5.1.0", - "@sideway/address": "^4.1.5", - "@sideway/formula": "^3.0.1", - "@sideway/pinpoint": "^2.0.0" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -15011,6 +7620,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -15020,18 +7630,19 @@ } }, "node_modules/jsdom": { - "version": "27.2.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.2.0.tgz", - "integrity": "sha512-454TI39PeRDW1LgpyLPyURtB4Zx1tklSr6+OFOipsxGUH1WMTvk6C65JQdrj455+DP2uJ1+veBEHTGFKWVLFoA==", + "version": "27.4.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.4.0.tgz", + "integrity": "sha512-mjzqwWRD9Y1J1KUi7W97Gja1bwOOM5Ug0EZ6UDK3xS7j7mndrkwozHtSblfomlzyB4NepioNt+B2sOSzczVgtQ==", "dev": true, "license": "MIT", "dependencies": { - "@acemir/cssom": "^0.9.23", - "@asamuzakjp/dom-selector": "^6.7.4", - "cssstyle": "^5.3.3", + "@acemir/cssom": "^0.9.28", + "@asamuzakjp/dom-selector": "^6.7.6", + "@exodus/bytes": "^1.6.0", + "cssstyle": "^5.3.4", "data-urls": "^6.0.0", "decimal.js": "^10.6.0", - "html-encoding-sniffer": "^4.0.0", + "html-encoding-sniffer": "^6.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", @@ -15041,7 +7652,6 @@ "tough-cookie": "^6.0.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.0", - "whatwg-encoding": "^3.1.1", "whatwg-mimetype": "^4.0.0", "whatwg-url": "^15.1.0", "ws": "^8.18.3", @@ -15059,34 +7669,18 @@ } } }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "license": "MIT" - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { @@ -15106,49 +7700,16 @@ } }, "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, "bin": { "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", - "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", - "license": "MIT", - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsonwebtoken": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", - "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", - "license": "MIT", - "dependencies": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=12", - "npm": ">=6" } }, "node_modules/jsx-ast-utils": { @@ -15167,27 +7728,6 @@ "node": ">=4.0" } }, - "node_modules/jwa": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.2.tgz", - "integrity": "sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw==", - "license": "MIT", - "dependencies": { - "buffer-equal-constant-time": "^1.0.1", - "ecdsa-sig-formatter": "1.0.11", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/jws": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", - "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", - "license": "MIT", - "dependencies": { - "jwa": "^1.4.2", - "safe-buffer": "^5.0.1" - } - }, "node_modules/jwt-decode": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", @@ -15197,85 +7737,16 @@ "node": ">=18" } }, - "node_modules/katex": { - "version": "0.16.25", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.25.tgz", - "integrity": "sha512-woHRUZ/iF23GBP1dkDQMh1QBad9dmr8/PAwNA54VrSOVYgI12MAcE14TqnDdQOdzyEonGzMepYnqBMYdsoAr8Q==", - "funding": [ - "https://opencollective.com/katex", - "https://github.com/sponsors/katex" - ], - "license": "MIT", - "dependencies": { - "commander": "^8.3.0" - }, - "bin": { - "katex": "cli.js" - } - }, - "node_modules/katex/node_modules/commander": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } }, - "node_modules/khroma": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", - "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/kolorist": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz", - "integrity": "sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==", - "license": "MIT" - }, - "node_modules/langium": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", - "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", - "license": "MIT", - "dependencies": { - "chevrotain": "~11.0.3", - "chevrotain-allstar": "~0.3.0", - "vscode-languageserver": "~9.0.1", - "vscode-languageserver-textdocument": "~1.0.11", - "vscode-uri": "~3.0.8" - }, - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/language-subtag-registry": { "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", @@ -15296,46 +7767,6 @@ "node": ">=0.10" } }, - "node_modules/latest-version": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-7.0.0.tgz", - "integrity": "sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==", - "license": "MIT", - "dependencies": { - "package-json": "^8.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/launch-editor": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz", - "integrity": "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==", - "license": "MIT", - "dependencies": { - "picocolors": "^1.1.1", - "shell-quote": "^1.8.3" - } - }, - "node_modules/layout-base": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", - "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", - "license": "MIT" - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -15354,6 +7785,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, "license": "MIT", "engines": { "node": ">=14" @@ -15366,52 +7798,9 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, "license": "MIT" }, - "node_modules/loader-runner": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", - "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", - "license": "MIT", - "engines": { - "node": ">=6.11.5" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/loader-utils": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", - "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", - "license": "MIT", - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - }, - "engines": { - "node": ">=8.9.0" - } - }, - "node_modules/local-pkg": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", - "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", - "license": "MIT", - "dependencies": { - "mlly": "^1.7.4", - "pkg-types": "^2.3.0", - "quansync": "^0.2.11" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -15434,60 +7823,6 @@ "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, - "node_modules/lodash-es": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", - "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", - "license": "MIT" - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "license": "MIT" - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "license": "MIT" - }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", - "license": "MIT" - }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", - "license": "MIT" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", - "license": "MIT" - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", - "license": "MIT" - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -15495,18 +7830,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.once": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", - "license": "MIT" - }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==", - "license": "MIT" - }, "node_modules/longest-streak": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", @@ -15536,27 +7859,6 @@ "dev": true, "license": "MIT" }, - "node_modules/lower-case": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", - "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lowlight": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", @@ -15572,12 +7874,13 @@ } }, "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" + "version": "11.2.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", + "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" } }, "node_modules/lucide-react": { @@ -15637,40 +7940,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/markdown-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-extensions/-/markdown-extensions-2.0.0.tgz", - "integrity": "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==", - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/markdown-table": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", - "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/marked": { - "version": "16.4.2", - "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", - "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", - "license": "MIT", - "bin": { - "marked": "bin/marked.js" - }, - "engines": { - "node": ">= 20" - } - }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -15680,55 +7949,6 @@ "node": ">= 0.4" } }, - "node_modules/mdast-util-directive": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.1.0.tgz", - "integrity": "sha512-I3fNFt+DHmpWCYAT7quoM6lHf9wuqtI+oCOfvILnoicNIqjh5E3dEJWiXuYME2gNe8vl1iMQwyUHa7bgFmak6Q==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "parse-entities": "^4.0.0", - "stringify-entities": "^4.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-find-and-replace": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", - "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "escape-string-regexp": "^5.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/mdast-util-from-markdown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", @@ -15753,206 +7973,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/mdast-util-from-markdown/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/mdast-util-frontmatter": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", - "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "escape-string-regexp": "^5.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-extension-frontmatter": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-frontmatter/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/mdast-util-gfm": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", - "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-gfm-autolink-literal": "^2.0.0", - "mdast-util-gfm-footnote": "^2.0.0", - "mdast-util-gfm-strikethrough": "^2.0.0", - "mdast-util-gfm-table": "^2.0.0", - "mdast-util-gfm-task-list-item": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", - "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "ccount": "^2.0.0", - "devlop": "^1.0.0", - "mdast-util-find-and-replace": "^3.0.0", - "micromark-util-character": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/mdast-util-gfm-autolink-literal/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/mdast-util-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.1.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-strikethrough": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", - "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", - "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "markdown-table": "^3.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-gfm-task-list-item": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", - "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "devlop": "^1.0.0", - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/mdast-util-mdx": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", - "integrity": "sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==", - "license": "MIT", - "dependencies": { - "mdast-util-from-markdown": "^2.0.0", - "mdast-util-mdx-expression": "^2.0.0", - "mdast-util-mdx-jsx": "^3.0.0", - "mdast-util-mdxjs-esm": "^2.0.0", - "mdast-util-to-markdown": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/mdast-util-mdx-expression": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", @@ -16089,94 +8109,16 @@ "dev": true, "license": "CC0-1.0" }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memfs": { - "version": "4.51.0", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-4.51.0.tgz", - "integrity": "sha512-4zngfkVM/GpIhC8YazOsM6E8hoB33NP0BCESPOA6z7qaL6umPJNqkO8CNYaLV2FB2MV6H1O3x2luHHOSqppv+A==", - "license": "Apache-2.0", - "dependencies": { - "@jsonjoy.com/json-pack": "^1.11.0", - "@jsonjoy.com/util": "^1.9.0", - "glob-to-regex.js": "^1.0.1", - "thingies": "^2.5.0", - "tree-dump": "^1.0.3", - "tslib": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "license": "MIT" - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" } }, - "node_modules/mermaid": { - "version": "11.12.1", - "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.1.tgz", - "integrity": "sha512-UlIZrRariB11TY1RtTgUWp65tphtBv4CSq7vyS2ZZ2TgoMjs2nloq+wFqxiwcxlhHUvs7DPGgMjs2aeQxz5h9g==", - "license": "MIT", - "dependencies": { - "@braintree/sanitize-url": "^7.1.1", - "@iconify/utils": "^3.0.1", - "@mermaid-js/parser": "^0.6.3", - "@types/d3": "^7.4.3", - "cytoscape": "^3.29.3", - "cytoscape-cose-bilkent": "^4.1.0", - "cytoscape-fcose": "^2.2.0", - "d3": "^7.9.0", - "d3-sankey": "^0.12.3", - "dagre-d3-es": "7.0.13", - "dayjs": "^1.11.18", - "dompurify": "^3.2.5", - "katex": "^0.16.22", - "khroma": "^2.1.0", - "lodash-es": "^4.17.21", - "marked": "^16.2.1", - "roughjs": "^4.6.6", - "stylis": "^4.3.6", - "ts-dedent": "^2.2.0", - "uuid": "^11.1.0" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/micromark": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", @@ -16246,793 +8188,6 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-core-commonmark/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-core-commonmark/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-directive": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/micromark-extension-directive/-/micromark-extension-directive-3.0.2.tgz", - "integrity": "sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-factory-whitespace": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "parse-entities": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-directive/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-directive/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-directive/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-frontmatter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", - "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", - "license": "MIT", - "dependencies": { - "fault": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-frontmatter/node_modules/fault": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", - "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", - "license": "MIT", - "dependencies": { - "format": "^0.2.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-frontmatter/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-gfm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", - "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", - "license": "MIT", - "dependencies": { - "micromark-extension-gfm-autolink-literal": "^2.0.0", - "micromark-extension-gfm-footnote": "^2.0.0", - "micromark-extension-gfm-strikethrough": "^2.0.0", - "micromark-extension-gfm-table": "^2.0.0", - "micromark-extension-gfm-tagfilter": "^2.0.0", - "micromark-extension-gfm-task-list-item": "^2.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", - "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-autolink-literal/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-gfm-footnote": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", - "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-normalize-identifier": "^2.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-footnote/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-gfm-strikethrough": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", - "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-util-chunked": "^2.0.0", - "micromark-util-classify-character": "^2.0.0", - "micromark-util-resolve-all": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-strikethrough/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-gfm-table": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", - "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-table/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-table/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-gfm-tagfilter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", - "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", - "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", - "license": "MIT", - "dependencies": { - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-gfm-task-list-item/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-mdx-expression": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", - "integrity": "sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-expression/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-expression/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-mdx-jsx": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-jsx/-/micromark-extension-mdx-jsx-3.0.2.tgz", - "integrity": "sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "estree-util-is-identifier-name": "^3.0.0", - "micromark-factory-mdx-expression": "^2.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdx-jsx/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-extension-mdx-md": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdx-md/-/micromark-extension-mdx-md-2.0.0.tgz", - "integrity": "sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==", - "license": "MIT", - "dependencies": { - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs/-/micromark-extension-mdxjs-3.0.0.tgz", - "integrity": "sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==", - "license": "MIT", - "dependencies": { - "acorn": "^8.0.0", - "acorn-jsx": "^5.0.0", - "micromark-extension-mdx-expression": "^3.0.0", - "micromark-extension-mdx-jsx": "^3.0.0", - "micromark-extension-mdx-md": "^2.0.0", - "micromark-extension-mdxjs-esm": "^3.0.0", - "micromark-util-combine-extensions": "^2.0.0", - "micromark-util-types": "^2.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs-esm": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/micromark-extension-mdxjs-esm/-/micromark-extension-mdxjs-esm-3.0.0.tgz", - "integrity": "sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-core-commonmark": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-extension-mdxjs-esm/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, "node_modules/micromark-factory-destination": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", @@ -17054,42 +8209,6 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-destination/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-destination/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, "node_modules/micromark-factory-label": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", @@ -17112,70 +8231,7 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-label/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-label/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-factory-mdx-expression": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-factory-mdx-expression/-/micromark-factory-mdx-expression-2.0.3.tgz", - "integrity": "sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "devlop": "^1.0.0", - "micromark-factory-space": "^2.0.0", - "micromark-util-character": "^2.0.0", - "micromark-util-events-to-acorn": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "unist-util-position-from-estree": "^2.0.0", - "vfile-message": "^4.0.0" - } - }, - "node_modules/micromark-factory-mdx-expression/node_modules/micromark-factory-space": { + "node_modules/micromark-factory-space": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", @@ -17195,78 +8251,6 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-mdx-expression/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-factory-space": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz", - "integrity": "sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-factory-space/node_modules/micromark-util-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", - "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, "node_modules/micromark-factory-title": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", @@ -17289,62 +8273,6 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-title/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-title/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, "node_modules/micromark-factory-whitespace": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", @@ -17367,27 +8295,7 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-whitespace/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-factory-whitespace/node_modules/micromark-util-character": { + "node_modules/micromark-util-character": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", @@ -17407,58 +8315,6 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-factory-whitespace/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-character": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.2.0.tgz", - "integrity": "sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^1.0.0", - "micromark-util-types": "^1.0.0" - } - }, - "node_modules/micromark-util-character/node_modules/micromark-util-types": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.1.0.tgz", - "integrity": "sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, "node_modules/micromark-util-chunked": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", @@ -17478,22 +8334,6 @@ "micromark-util-symbol": "^2.0.0" } }, - "node_modules/micromark-util-chunked/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, "node_modules/micromark-util-classify-character": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", @@ -17515,42 +8355,6 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-classify-character/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-classify-character/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, "node_modules/micromark-util-combine-extensions": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", @@ -17590,22 +8394,6 @@ "micromark-util-symbol": "^2.0.0" } }, - "node_modules/micromark-util-decode-numeric-character-reference/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, "node_modules/micromark-util-decode-string": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", @@ -17628,42 +8416,6 @@ "micromark-util-symbol": "^2.0.0" } }, - "node_modules/micromark-util-decode-string/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-decode-string/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, "node_modules/micromark-util-encode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", @@ -17680,47 +8432,6 @@ ], "license": "MIT" }, - "node_modules/micromark-util-events-to-acorn": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/micromark-util-events-to-acorn/-/micromark-util-events-to-acorn-2.0.3.tgz", - "integrity": "sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/unist": "^3.0.0", - "devlop": "^1.0.0", - "estree-util-visit": "^2.0.0", - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0", - "vfile-message": "^4.0.0" - } - }, - "node_modules/micromark-util-events-to-acorn/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, "node_modules/micromark-util-html-tag-name": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", @@ -17756,22 +8467,6 @@ "micromark-util-symbol": "^2.0.0" } }, - "node_modules/micromark-util-normalize-identifier/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, "node_modules/micromark-util-resolve-all": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", @@ -17812,42 +8507,6 @@ "micromark-util-symbol": "^2.0.0" } }, - "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark-util-sanitize-uri/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, "node_modules/micromark-util-subtokenize": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", @@ -17870,7 +8529,7 @@ "micromark-util-types": "^2.0.0" } }, - "node_modules/micromark-util-subtokenize/node_modules/micromark-util-symbol": { + "node_modules/micromark-util-symbol": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", @@ -17886,22 +8545,6 @@ ], "license": "MIT" }, - "node_modules/micromark-util-symbol": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz", - "integrity": "sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, "node_modules/micromark-util-types": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", @@ -17918,66 +8561,11 @@ ], "license": "MIT" }, - "node_modules/micromark/node_modules/micromark-factory-space": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", - "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark/node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" - } - }, - "node_modules/micromark/node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, "license": "MIT", "dependencies": { "braces": "^3.0.3", @@ -17987,18 +8575,6 @@ "node": ">=8.6" } }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -18020,27 +8596,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-response": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/min-indent": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", @@ -18051,26 +8606,6 @@ "node": ">=4" } }, - "node_modules/mini-css-extract-plugin": { - "version": "2.9.4", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.4.tgz", - "integrity": "sha512-ZWYT7ln73Hptxqxk2DxPU9MmapXRhxkJD6tkSR04dnQxm8BGu2hzgKLugK5yySD97u/8yy7Ma7E76k9ZdvtjkQ==", - "license": "MIT", - "dependencies": { - "schema-utils": "^4.0.0", - "tapable": "^2.2.1" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, "node_modules/mini-svg-data-uri": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", @@ -18081,16 +8616,11 @@ "mini-svg-data-uri": "cli.js" } }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "license": "ISC" - }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -18103,6 +8633,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -18118,35 +8649,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/mlly": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", - "license": "MIT", - "dependencies": { - "acorn": "^8.15.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.1" - } - }, - "node_modules/mlly/node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "license": "MIT" - }, - "node_modules/mlly/node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", - "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" - } - }, "node_modules/moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", @@ -18160,6 +8662,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", "integrity": "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -18171,19 +8674,6 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, - "node_modules/multicast-dns": { - "version": "7.2.5", - "resolved": "https://registry.npmjs.org/multicast-dns/-/multicast-dns-7.2.5.tgz", - "integrity": "sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==", - "license": "MIT", - "dependencies": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -18237,57 +8727,42 @@ "dev": true, "license": "MIT" }, - "node_modules/negotiator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", - "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "license": "MIT" - }, "node_modules/next": { - "version": "14.2.35", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.35.tgz", - "integrity": "sha512-KhYd2Hjt/O1/1aZVX3dCwGXM1QmOV4eNM2UTacK5gipDdPN/oHHK/4oVGy7X8GMfPMsUTUEmGlsy0EY1YGAkig==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", + "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==", "license": "MIT", "dependencies": { - "@next/env": "14.2.35", - "@swc/helpers": "0.5.5", - "busboy": "1.6.0", + "@next/env": "16.1.6", + "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", - "graceful-fs": "^4.2.11", "postcss": "8.4.31", - "styled-jsx": "5.1.1" + "styled-jsx": "5.1.6" }, "bin": { "next": "dist/bin/next" }, "engines": { - "node": ">=18.17.0" + "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.33", - "@next/swc-darwin-x64": "14.2.33", - "@next/swc-linux-arm64-gnu": "14.2.33", - "@next/swc-linux-arm64-musl": "14.2.33", - "@next/swc-linux-x64-gnu": "14.2.33", - "@next/swc-linux-x64-musl": "14.2.33", - "@next/swc-win32-arm64-msvc": "14.2.33", - "@next/swc-win32-ia32-msvc": "14.2.33", - "@next/swc-win32-x64-msvc": "14.2.33" + "@next/swc-darwin-arm64": "16.1.6", + "@next/swc-darwin-x64": "16.1.6", + "@next/swc-linux-arm64-gnu": "16.1.6", + "@next/swc-linux-arm64-musl": "16.1.6", + "@next/swc-linux-x64-gnu": "16.1.6", + "@next/swc-linux-x64-musl": "16.1.6", + "@next/swc-win32-arm64-msvc": "16.1.6", + "@next/swc-win32-x64-msvc": "16.1.6", + "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.41.2", - "react": "^18.2.0", - "react-dom": "^18.2.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "peerDependenciesMeta": { @@ -18297,11 +8772,23 @@ "@playwright/test": { "optional": true }, + "babel-plugin-react-compiler": { + "optional": true + }, "sass": { "optional": true } } }, + "node_modules/next/node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -18330,16 +8817,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/no-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", - "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", - "license": "MIT", - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -18360,21 +8837,6 @@ "node": ">=10.5.0" } }, - "node_modules/node-emoji": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-2.2.0.tgz", - "integrity": "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==", - "license": "MIT", - "dependencies": { - "@sindresorhus/is": "^4.6.0", - "char-regex": "^1.0.2", - "emojilib": "^2.4.0", - "skin-tone": "^2.0.0" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -18417,113 +8879,23 @@ "webidl-conversions": "^3.0.0" } }, - "node_modules/node-forge": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.2.tgz", - "integrity": "sha512-6xKiQ+cph9KImrRh0VsjH2d8/GXA4FIMlgU4B757iI1ApvcyA9VlouP0yZJha01V+huImO+kKMU7ih+2+E14fw==", - "license": "(BSD-3-Clause OR GPL-2.0)", - "engines": { - "node": ">= 6.13.0" - } - }, "node_modules/node-releases": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/normalize-range": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", - "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/null-loader": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/null-loader/-/null-loader-4.0.1.tgz", - "integrity": "sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==", - "license": "MIT", - "dependencies": { - "loader-utils": "^2.0.0", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/null-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -18547,6 +8919,7 @@ "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -18559,6 +8932,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -18568,6 +8942,7 @@ "version": "4.1.7", "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.8", @@ -18653,65 +9028,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/obuf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", - "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==", - "license": "MIT" - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", - "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "license": "MIT", - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/openai": { "version": "4.104.0", "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz", @@ -18757,15 +9073,6 @@ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", "license": "MIT" }, - "node_modules/opener": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", - "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", - "license": "(WTFPL OR MIT)", - "bin": { - "opener": "bin/opener-bin.js" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -18802,24 +9109,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/p-cancelable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", - "license": "MIT", - "engines": { - "node": ">=12.20" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -18852,110 +9141,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "license": "MIT", - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-queue": { - "version": "6.6.2", - "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.2.tgz", - "integrity": "sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==", - "license": "MIT", - "dependencies": { - "eventemitter3": "^4.0.4", - "p-timeout": "^3.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-6.2.1.tgz", - "integrity": "sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==", - "license": "MIT", - "dependencies": { - "@types/retry": "0.12.2", - "is-network-error": "^1.0.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-timeout": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", - "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", - "license": "MIT", - "dependencies": { - "p-finally": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/package-json": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-8.1.1.tgz", - "integrity": "sha512-cbH9IAIJHNj9uXi196JVsRlt7cHKak6u/e6AkL/bkRelZ7rlL3X1YKxsZwa36xipOEKAsdtmaG6aAJoM1fx2zA==", - "license": "MIT", - "dependencies": { - "got": "^12.1.0", - "registry-auth-token": "^5.0.1", - "registry-url": "^6.0.0", - "semver": "^7.3.7" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/package-manager-detector": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.5.0.tgz", - "integrity": "sha512-uBj69dVlYe/+wxj8JOpr97XfsxH/eumMt6HqjNTmJDf/6NO9s+0uxeOneIz3AsPt2m6y9PqzDzd3ATcU17MNfw==", - "license": "MIT" - }, "node_modules/papaparse": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/papaparse/-/papaparse-5.5.3.tgz", "integrity": "sha512-5QvjGxYVjxO59MGU2lHVYpRWBBtKHnlIAcSe1uNFCkkptUh63NFRj0FJQm7nR67puEruUci/ZkjmEFrjCAyP4A==", "license": "MIT" }, - "node_modules/param-case": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", - "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", - "license": "MIT", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, "license": "MIT", "dependencies": { "callsites": "^3.0.0" @@ -18989,30 +9185,6 @@ "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", "license": "MIT" }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-numeric-range": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/parse-numeric-range/-/parse-numeric-range-1.3.0.tgz", - "integrity": "sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==", - "license": "ISC" - }, "node_modules/parse5": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", @@ -19026,44 +9198,6 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/parse5/node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascal-case": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", - "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/path-data-parser": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", - "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", - "license": "MIT" - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -19074,16 +9208,11 @@ "node": ">=8" } }, - "node_modules/path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", - "license": "(WTFPL OR MIT)" - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -19093,6 +9222,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, "license": "MIT" }, "node_modules/path-scurry": { @@ -19112,38 +9242,11 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "11.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.2.tgz", - "integrity": "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/path-to-regexp": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", - "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", - "license": "MIT", - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/pathe": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, "license": "MIT" }, "node_modules/pathval": { @@ -19166,6 +9269,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=8.6" @@ -19194,122 +9298,14 @@ "node": ">= 6" } }, - "node_modules/pkg-dir": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-7.0.0.tgz", - "integrity": "sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==", - "license": "MIT", - "dependencies": { - "find-up": "^6.3.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", - "license": "MIT", - "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", - "license": "MIT", - "dependencies": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", - "license": "MIT", - "dependencies": { - "p-limit": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/pkg-dir/node_modules/yocto-queue": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", - "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "license": "MIT", - "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", - "pathe": "^2.0.3" - } - }, "node_modules/playwright": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", - "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "version": "1.58.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.1.tgz", + "integrity": "sha512-+2uTZHxSCcxjvGc5C891LrS1/NlxglGxzrC4seZiVjcYVQfUa87wBL6rTDqzGjuoWNjnBzRqKmF6zRYGMvQUaQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.57.0" + "playwright-core": "1.58.1" }, "bin": { "playwright": "cli.js" @@ -19322,9 +9318,9 @@ } }, "node_modules/playwright-core": { - "version": "1.57.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", - "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", + "version": "1.58.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.1.tgz", + "integrity": "sha512-bcWzOaTxcW+VOOGBCQgnaKToLJ65d6AqfLVKEWvexyS3AS6rbXl+xdpYRMGSRBClPvyj44njOWoxjNdL/H9UNg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -19334,37 +9330,6 @@ "node": ">=18" } }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/points-on-curve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", - "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", - "license": "MIT" - }, - "node_modules/points-on-path": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", - "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", - "license": "MIT", - "dependencies": { - "path-data-parser": "0.1.0", - "points-on-curve": "0.2.0" - } - }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -19379,6 +9344,7 @@ "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -19403,549 +9369,6 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/postcss-attribute-case-insensitive": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-7.0.1.tgz", - "integrity": "sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-attribute-case-insensitive/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-calc": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-9.0.1.tgz", - "integrity": "sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.0.11", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.2.2" - } - }, - "node_modules/postcss-clamp": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-clamp/-/postcss-clamp-4.1.0.tgz", - "integrity": "sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=7.6.0" - }, - "peerDependencies": { - "postcss": "^8.4.6" - } - }, - "node_modules/postcss-color-functional-notation": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.12.tgz", - "integrity": "sha512-TLCW9fN5kvO/u38/uesdpbx3e8AkTYhMvDZYa9JpmImWuTE99bDQ7GU7hdOADIZsiI9/zuxfAJxny/khknp1Zw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-color-hex-alpha": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-10.0.0.tgz", - "integrity": "sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-color-rebeccapurple": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-10.0.0.tgz", - "integrity": "sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-colormin": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-6.1.0.tgz", - "integrity": "sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0", - "colord": "^2.9.3", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-convert-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-6.1.0.tgz", - "integrity": "sha512-zx8IwP/ts9WvUM6NkVSkiU902QZL1bwPhaVaLynPtCsOTqp+ZKbNi+s6XJg3rfqpKGA/oc7Oxk5t8pOQJcwl/w==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-custom-media": { - "version": "11.0.6", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.6.tgz", - "integrity": "sha512-C4lD4b7mUIw+RZhtY7qUbf4eADmb7Ey8BFA2px9jUbwg7pjTZDl4KY4bvlUV+/vXQvzQRfiGEVJyAbtOsCMInw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/cascade-layer-name-parser": "^2.0.5", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/media-query-list-parser": "^4.0.3" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-custom-properties": { - "version": "14.0.6", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.6.tgz", - "integrity": "sha512-fTYSp3xuk4BUeVhxCSJdIPhDLpJfNakZKoiTDx7yRGCdlZrSJR7mWKVOBS4sBF+5poPQFMj2YdXx1VHItBGihQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/cascade-layer-name-parser": "^2.0.5", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-custom-selectors": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.5.tgz", - "integrity": "sha512-9PGmckHQswiB2usSO6XMSswO2yFWVoCAuih1yl9FVcwkscLjRKjwsjM3t+NIWpSU2Jx3eOiK2+t4vVTQaoCHHg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/cascade-layer-name-parser": "^2.0.5", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-custom-selectors/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-dir-pseudo-class": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-9.0.1.tgz", - "integrity": "sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-dir-pseudo-class/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-discard-comments": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-6.0.2.tgz", - "integrity": "sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-discard-duplicates": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-6.0.3.tgz", - "integrity": "sha512-+JA0DCvc5XvFAxwx6f/e68gQu/7Z9ud584VLmcgto28eB8FqSFZwtrLwB5Kcp70eIoWP/HXqz4wpo8rD8gpsTw==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-discard-empty": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-6.0.3.tgz", - "integrity": "sha512-znyno9cHKQsK6PtxL5D19Fj9uwSzC2mB74cpT66fhgOadEUPyXFkbgwm5tvc3bt3NAy8ltE5MrghxovZRVnOjQ==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-discard-overridden": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-6.0.2.tgz", - "integrity": "sha512-j87xzI4LUggC5zND7KdjsI25APtyMuynXZSujByMaav2roV6OZX+8AaCUcZSWqckZpjAjRyFDdpqybgjFO0HJQ==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-discard-unused": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-6.0.5.tgz", - "integrity": "sha512-wHalBlRHkaNnNwfC8z+ppX57VhvS+HWgjW508esjdaEYr3Mx7Gnn2xA4R/CKf5+Z9S5qsqC+Uzh4ueENWwCVUA==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.0.16" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-double-position-gradients": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-6.0.4.tgz", - "integrity": "sha512-m6IKmxo7FxSP5nF2l63QbCC3r+bWpFUWmZXZf096WxG0m7Vl1Q1+ruFOhpdDRmKrRS+S3Jtk+TVk/7z0+BVK6g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-visible": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-10.0.1.tgz", - "integrity": "sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-visible/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-focus-within": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-9.0.1.tgz", - "integrity": "sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-focus-within/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-font-variant": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-5.0.0.tgz", - "integrity": "sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==", - "license": "MIT", - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-gap-properties": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-6.0.0.tgz", - "integrity": "sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-image-set-function": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-7.0.0.tgz", - "integrity": "sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/utilities": "^2.0.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, "node_modules/postcss-import": { "version": "15.1.0", "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", @@ -19990,35 +9413,6 @@ "postcss": "^8.4.21" } }, - "node_modules/postcss-lab-function": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.12.tgz", - "integrity": "sha512-tUcyRk1ZTPec3OuKFsqtRzW2Go5lehW29XA21lZ65XmzQkz43VY2tyWEC202F7W3mILOjw0voOiuxRGTsN+J9w==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/css-color-parser": "^3.1.0", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/utilities": "^2.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, "node_modules/postcss-load-config": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", @@ -20062,252 +9456,6 @@ } } }, - "node_modules/postcss-loader": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-7.3.4.tgz", - "integrity": "sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==", - "license": "MIT", - "dependencies": { - "cosmiconfig": "^8.3.5", - "jiti": "^1.20.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "postcss": "^7.0.0 || ^8.0.1", - "webpack": "^5.0.0" - } - }, - "node_modules/postcss-logical": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-8.1.0.tgz", - "integrity": "sha512-pL1hXFQ2fEXNKiNiAgtfA005T9FBxky5zkX6s4GZM2D8RkVgRqz3f4g1JUoq925zXv495qk8UNldDwh8uGEDoA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-merge-idents": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-6.0.3.tgz", - "integrity": "sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g==", - "license": "MIT", - "dependencies": { - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-merge-longhand": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-6.0.5.tgz", - "integrity": "sha512-5LOiordeTfi64QhICp07nzzuTDjNSO8g5Ksdibt44d+uvIIAE1oZdRn8y/W5ZtYgRH/lnLDlvi9F8btZcVzu3w==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0", - "stylehacks": "^6.1.1" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-merge-rules": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-6.1.1.tgz", - "integrity": "sha512-KOdWF0gju31AQPZiD+2Ar9Qjowz1LTChSjFFbS+e2sFgc4uHOp3ZvVX4sNeTlk0w2O31ecFGgrFzhO0RSWbWwQ==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0", - "cssnano-utils": "^4.0.2", - "postcss-selector-parser": "^6.0.16" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-minify-font-values": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-6.1.0.tgz", - "integrity": "sha512-gklfI/n+9rTh8nYaSJXlCo3nOKqMNkxuGpTn/Qm0gstL3ywTr9/WRKznE+oy6fvfolH6dF+QM4nCo8yPLdvGJg==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-minify-gradients": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-6.0.3.tgz", - "integrity": "sha512-4KXAHrYlzF0Rr7uc4VrfwDJ2ajrtNEpNEuLxFgwkhFZ56/7gaE4Nr49nLsQDZyUe+ds+kEhf+YAUolJiYXF8+Q==", - "license": "MIT", - "dependencies": { - "colord": "^2.9.3", - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-minify-params": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-6.1.0.tgz", - "integrity": "sha512-bmSKnDtyyE8ujHQK0RQJDIKhQ20Jq1LYiez54WiaOoBtcSuflfK3Nm596LvbtlFcpipMjgClQGyGr7GAs+H1uA==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-minify-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-6.0.4.tgz", - "integrity": "sha512-L8dZSwNLgK7pjTto9PzWRoMbnLq5vsZSTu8+j1P/2GB8qdtGQfn+K1uSvFgYvgh83cbyxT5m43ZZhUMTJDSClQ==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.0.16" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.1.0.tgz", - "integrity": "sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==", - "license": "ISC", - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.2.0.tgz", - "integrity": "sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==", - "license": "MIT", - "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^7.0.0", - "postcss-value-parser": "^4.1.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-local-by-default/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-modules-scope": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.2.1.tgz", - "integrity": "sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==", - "license": "ISC", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/postcss-modules-scope/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", - "license": "ISC", - "dependencies": { - "icss-utils": "^5.0.0" - }, - "engines": { - "node": "^10 || ^12 || >= 14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, "node_modules/postcss-nested": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", @@ -20334,546 +9482,11 @@ "postcss": "^8.2.14" } }, - "node_modules/postcss-nesting": { - "version": "13.0.2", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-13.0.2.tgz", - "integrity": "sha512-1YCI290TX+VP0U/K/aFxzHzQWHWURL+CtHMSbex1lCdpXD1SoR2sYuxDu5aNI9lPoXpKTCggFZiDJbwylU0LEQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/selector-resolve-nested": "^3.1.0", - "@csstools/selector-specificity": "^5.0.0", - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-nesting/node_modules/@csstools/selector-resolve-nested": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-resolve-nested/-/selector-resolve-nested-3.1.0.tgz", - "integrity": "sha512-mf1LEW0tJLKfWyvn5KdDrhpxHyuxpbNwTIwOYLIvsTffeyOf85j5oIzfG0yosxDgx/sswlqBnESYUcQH0vgZ0g==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/postcss-nesting/node_modules/@csstools/selector-specificity": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", - "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^7.0.0" - } - }, - "node_modules/postcss-nesting/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-normalize-charset": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-6.0.2.tgz", - "integrity": "sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-display-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-6.0.2.tgz", - "integrity": "sha512-8H04Mxsb82ON/aAkPeq8kcBbAtI5Q2a64X/mnRRfPXBq7XeogoQvReqxEfc0B4WPq1KimjezNC8flUtC3Qz6jg==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-positions": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-6.0.2.tgz", - "integrity": "sha512-/JFzI441OAB9O7VnLA+RtSNZvQ0NCFZDOtp6QPFo1iIyawyXg0YI3CYM9HBy1WvwCRHnPep/BvI1+dGPKoXx/Q==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-repeat-style": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-6.0.2.tgz", - "integrity": "sha512-YdCgsfHkJ2jEXwR4RR3Tm/iOxSfdRt7jplS6XRh9Js9PyCR/aka/FCb6TuHT2U8gQubbm/mPmF6L7FY9d79VwQ==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-string": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-6.0.2.tgz", - "integrity": "sha512-vQZIivlxlfqqMp4L9PZsFE4YUkWniziKjQWUtsxUiVsSSPelQydwS8Wwcuw0+83ZjPWNTl02oxlIvXsmmG+CiQ==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-timing-functions": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-6.0.2.tgz", - "integrity": "sha512-a+YrtMox4TBtId/AEwbA03VcJgtyW4dGBizPl7e88cTFULYsprgHWTbfyjSLyHeBcK/Q9JhXkt2ZXiwaVHoMzA==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-unicode": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-6.1.0.tgz", - "integrity": "sha512-QVC5TQHsVj33otj8/JD869Ndr5Xcc/+fwRh4HAsFsAeygQQXm+0PySrKbr/8tkDKzW+EVT3QkqZMfFrGiossDg==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-url": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-6.0.2.tgz", - "integrity": "sha512-kVNcWhCeKAzZ8B4pv/DnrU1wNh458zBNp8dh4y5hhxih5RZQ12QWMuQrDgPRw3LRl8mN9vOVfHl7uhvHYMoXsQ==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-normalize-whitespace": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-6.0.2.tgz", - "integrity": "sha512-sXZ2Nj1icbJOKmdjXVT9pnyHQKiSAyuNQHSgRCUgThn2388Y9cGVDR+E9J9iAYbSbLHI+UUwLVl1Wzco/zgv0Q==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-opacity-percentage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-opacity-percentage/-/postcss-opacity-percentage-3.0.0.tgz", - "integrity": "sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==", - "funding": [ - { - "type": "kofi", - "url": "https://ko-fi.com/mrcgrtz" - }, - { - "type": "liberapay", - "url": "https://liberapay.com/mrcgrtz" - } - ], - "license": "MIT", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-ordered-values": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-6.0.2.tgz", - "integrity": "sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==", - "license": "MIT", - "dependencies": { - "cssnano-utils": "^4.0.2", - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-overflow-shorthand": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-6.0.0.tgz", - "integrity": "sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-page-break": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-3.0.4.tgz", - "integrity": "sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==", - "license": "MIT", - "peerDependencies": { - "postcss": "^8" - } - }, - "node_modules/postcss-place": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-10.0.0.tgz", - "integrity": "sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-preset-env": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.4.0.tgz", - "integrity": "sha512-2kqpOthQ6JhxqQq1FSAAZGe9COQv75Aw8WbsOvQVNJ2nSevc9Yx/IKZGuZ7XJ+iOTtVon7LfO7ELRzg8AZ+sdw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "@csstools/postcss-alpha-function": "^1.0.1", - "@csstools/postcss-cascade-layers": "^5.0.2", - "@csstools/postcss-color-function": "^4.0.12", - "@csstools/postcss-color-function-display-p3-linear": "^1.0.1", - "@csstools/postcss-color-mix-function": "^3.0.12", - "@csstools/postcss-color-mix-variadic-function-arguments": "^1.0.2", - "@csstools/postcss-content-alt-text": "^2.0.8", - "@csstools/postcss-contrast-color-function": "^2.0.12", - "@csstools/postcss-exponential-functions": "^2.0.9", - "@csstools/postcss-font-format-keywords": "^4.0.0", - "@csstools/postcss-gamut-mapping": "^2.0.11", - "@csstools/postcss-gradients-interpolation-method": "^5.0.12", - "@csstools/postcss-hwb-function": "^4.0.12", - "@csstools/postcss-ic-unit": "^4.0.4", - "@csstools/postcss-initial": "^2.0.1", - "@csstools/postcss-is-pseudo-class": "^5.0.3", - "@csstools/postcss-light-dark-function": "^2.0.11", - "@csstools/postcss-logical-float-and-clear": "^3.0.0", - "@csstools/postcss-logical-overflow": "^2.0.0", - "@csstools/postcss-logical-overscroll-behavior": "^2.0.0", - "@csstools/postcss-logical-resize": "^3.0.0", - "@csstools/postcss-logical-viewport-units": "^3.0.4", - "@csstools/postcss-media-minmax": "^2.0.9", - "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.5", - "@csstools/postcss-nested-calc": "^4.0.0", - "@csstools/postcss-normalize-display-values": "^4.0.0", - "@csstools/postcss-oklab-function": "^4.0.12", - "@csstools/postcss-progressive-custom-properties": "^4.2.1", - "@csstools/postcss-random-function": "^2.0.1", - "@csstools/postcss-relative-color-syntax": "^3.0.12", - "@csstools/postcss-scope-pseudo-class": "^4.0.1", - "@csstools/postcss-sign-functions": "^1.1.4", - "@csstools/postcss-stepped-value-functions": "^4.0.9", - "@csstools/postcss-text-decoration-shorthand": "^4.0.3", - "@csstools/postcss-trigonometric-functions": "^4.0.9", - "@csstools/postcss-unset-value": "^4.0.0", - "autoprefixer": "^10.4.21", - "browserslist": "^4.26.0", - "css-blank-pseudo": "^7.0.1", - "css-has-pseudo": "^7.0.3", - "css-prefers-color-scheme": "^10.0.0", - "cssdb": "^8.4.2", - "postcss-attribute-case-insensitive": "^7.0.1", - "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^7.0.12", - "postcss-color-hex-alpha": "^10.0.0", - "postcss-color-rebeccapurple": "^10.0.0", - "postcss-custom-media": "^11.0.6", - "postcss-custom-properties": "^14.0.6", - "postcss-custom-selectors": "^8.0.5", - "postcss-dir-pseudo-class": "^9.0.1", - "postcss-double-position-gradients": "^6.0.4", - "postcss-focus-visible": "^10.0.1", - "postcss-focus-within": "^9.0.1", - "postcss-font-variant": "^5.0.0", - "postcss-gap-properties": "^6.0.0", - "postcss-image-set-function": "^7.0.0", - "postcss-lab-function": "^7.0.12", - "postcss-logical": "^8.1.0", - "postcss-nesting": "^13.0.2", - "postcss-opacity-percentage": "^3.0.0", - "postcss-overflow-shorthand": "^6.0.0", - "postcss-page-break": "^3.0.4", - "postcss-place": "^10.0.0", - "postcss-pseudo-class-any-link": "^10.0.1", - "postcss-replace-overflow-wrap": "^4.0.0", - "postcss-selector-not": "^8.0.1" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-pseudo-class-any-link": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-10.0.1.tgz", - "integrity": "sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-pseudo-class-any-link/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/postcss-reduce-idents": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-6.0.3.tgz", - "integrity": "sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-reduce-initial": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-6.1.0.tgz", - "integrity": "sha512-RarLgBK/CrL1qZags04oKbVbrrVK2wcxhvta3GCxrZO4zveibqbRPmm2VI8sSgCXwoUHEliRSbOfpR0b/VIoiw==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "caniuse-api": "^3.0.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-reduce-transforms": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-6.0.2.tgz", - "integrity": "sha512-sB+Ya++3Xj1WaT9+5LOOdirAxP7dJZms3GRcYheSPi1PiTMigsxHAdkrbItHxwYHr4kt1zL7mmcHstgMYT+aiA==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-replace-overflow-wrap": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-4.0.0.tgz", - "integrity": "sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==", - "license": "MIT", - "peerDependencies": { - "postcss": "^8.0.3" - } - }, - "node_modules/postcss-selector-not": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-8.0.1.tgz", - "integrity": "sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^7.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss": "^8.4" - } - }, - "node_modules/postcss-selector-not/node_modules/postcss-selector-parser": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.0.tgz", - "integrity": "sha512-8sLjZwK0R+JlxlYcTuVnyT2v+htpdrjDOKuMcOVdYjt52Lh8hWRYpxBPoKx/Zg+bcjc3wx6fmQevMmUztS/ccA==", - "license": "MIT", - "dependencies": { - "cssesc": "^3.0.0", - "util-deprecate": "^1.0.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/postcss-selector-parser": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, "license": "MIT", "dependencies": { "cssesc": "^3.0.0", @@ -20883,70 +9496,13 @@ "node": ">=4" } }, - "node_modules/postcss-sort-media-queries": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/postcss-sort-media-queries/-/postcss-sort-media-queries-5.2.0.tgz", - "integrity": "sha512-AZ5fDMLD8SldlAYlvi8NIqo0+Z8xnXU2ia0jxmuhxAU+Lqt9K+AlmLNJ/zWEnE9x+Zx3qL3+1K20ATgNOr3fAA==", - "license": "MIT", - "dependencies": { - "sort-css-media-queries": "2.2.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "postcss": "^8.4.23" - } - }, - "node_modules/postcss-svgo": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-6.0.3.tgz", - "integrity": "sha512-dlrahRmxP22bX6iKEjOM+c8/1p+81asjKT+V5lrgOH944ryx/OHpclnIbGsKVd3uWOXFLYJwCVf0eEkJGvO96g==", - "license": "MIT", - "dependencies": { - "postcss-value-parser": "^4.2.0", - "svgo": "^3.2.0" - }, - "engines": { - "node": "^14 || ^16 || >= 18" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, - "node_modules/postcss-unique-selectors": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-6.0.4.tgz", - "integrity": "sha512-K38OCaIrO8+PzpArzkLKB42dSARtC2tmG6PvD4b1o1Q2E9Os8jzfWFfSy/rixsHwohtsDdFtAWGjFVFUdwYaMg==", - "license": "MIT", - "dependencies": { - "postcss-selector-parser": "^6.0.16" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, "node_modules/postcss-value-parser": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, "license": "MIT" }, - "node_modules/postcss-zindex": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-6.0.2.tgz", - "integrity": "sha512-5BxW9l1evPB/4ZIc+2GobEBoKC+h8gPGCMi+jxsYvd2x0mjq7wazk6DrP71pStqxE9Foxh5TVnonbWpFZzXaYg==", - "license": "MIT", - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -20973,16 +9529,6 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, - "node_modules/pretty-error": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", - "integrity": "sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==", - "license": "MIT", - "dependencies": { - "lodash": "^4.17.20", - "renderkid": "^3.0.0" - } - }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -21011,28 +9557,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/pretty-time": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz", - "integrity": "sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/prism-react-renderer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prism-react-renderer/-/prism-react-renderer-2.4.1.tgz", - "integrity": "sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==", - "license": "MIT", - "dependencies": { - "@types/prismjs": "^1.26.0", - "clsx": "^2.0.0" - }, - "peerDependencies": { - "react": ">=16.0.0" - } - }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", @@ -21042,25 +9566,6 @@ "node": ">=6" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -21088,34 +9593,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", - "license": "ISC" - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-addr/node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -21127,61 +9604,17 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, - "node_modules/pupa": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/pupa/-/pupa-3.3.0.tgz", - "integrity": "sha512-LjgDO2zPtoXP2wJpDjZrGdojii1uqO0cnwKoIoUzkfS98HDmbeiGmYiXo3lXeFlq2xvne1QFQhwYXSUCLKtEuA==", - "license": "MIT", - "dependencies": { - "escape-goat": "^4.0.0" - }, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/quansync": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", - "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/antfu" - }, - { - "type": "individual", - "url": "https://github.com/sponsors/sxzz" - } - ], - "license": "MIT" - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, "funding": [ { "type": "github", @@ -21198,116 +9631,6 @@ ], "license": "MIT" }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", - "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/raw-body/node_modules/statuses": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", - "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", - "dependencies": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "bin": { - "rc": "cli.js" - } - }, "node_modules/rc-cascader": { "version": "3.34.0", "resolved": "https://registry.npmjs.org/rc-cascader/-/rc-cascader-3.34.0.tgz", @@ -21665,9 +9988,9 @@ } }, "node_modules/rc-segmented": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.7.0.tgz", - "integrity": "sha512-liijAjXz+KnTRVnxxXG2sYDGd6iLL7VpGGdR8gwoxAXy2KglviKCxLWZdjKYJzYzGSUwKDSTdYk8brj54Bn5BA==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rc-segmented/-/rc-segmented-2.7.1.tgz", + "integrity": "sha512-izj1Nw/Dw2Vb7EVr+D/E9lUTkBe+kKC+SAFSU9zqr7WV2W5Ktaa9Gc7cB2jTqgk8GROJayltaec+DBlYKc6d+g==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.11.1", @@ -21920,21 +10243,6 @@ "react-dom": ">=16.9.0" } }, - "node_modules/rc/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "license": "ISC" - }, - "node_modules/rc/node_modules/strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", @@ -21987,30 +10295,6 @@ "react": "^18.3.1" } }, - "node_modules/react-fast-compare": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", - "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==", - "license": "MIT" - }, - "node_modules/react-helmet-async": { - "name": "@slorber/react-helmet-async", - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@slorber/react-helmet-async/-/react-helmet-async-1.3.0.tgz", - "integrity": "sha512-e9/OK8VhwUSc67diWI8Rb3I0YgI9/SBQtnhe9aEuK6MhZm7ntZZimXgwXnd8W96YTmSOb9M4d8LwhRZyhWr/1A==", - "license": "Apache-2.0", - "dependencies": { - "@babel/runtime": "^7.12.5", - "invariant": "^2.2.4", - "prop-types": "^15.7.2", - "react-fast-compare": "^3.2.0", - "shallowequal": "^1.1.0" - }, - "peerDependencies": { - "react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -22030,35 +10314,6 @@ "react": "^18.0.0 || ^19.0.0" } }, - "node_modules/react-loadable": { - "name": "@docusaurus/react-loadable", - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@docusaurus/react-loadable/-/react-loadable-6.0.0.tgz", - "integrity": "sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==", - "license": "MIT", - "dependencies": { - "@types/react": "*" - }, - "peerDependencies": { - "react": "*" - } - }, - "node_modules/react-loadable-ssr-addon-v5-slorber": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/react-loadable-ssr-addon-v5-slorber/-/react-loadable-ssr-addon-v5-slorber-1.0.1.tgz", - "integrity": "sha512-lq3Lyw1lGku8zUEJPDxsNm1AfYHBrO9Y1+olAYwpUJ2IGFBskM0DMKok97A6LWUpHm+o7IvQBOWu9MLenp9Z+A==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.10.3" - }, - "engines": { - "node": ">=10.13.0" - }, - "peerDependencies": { - "react-loadable": "*", - "webpack": ">=4.41.1 || 5.x" - } - }, "node_modules/react-markdown": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-9.1.0.tgz", @@ -22086,73 +10341,6 @@ "react": ">=18" } }, - "node_modules/react-refresh": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", - "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-router": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.3.4.tgz", - "integrity": "sha512-Ys9K+ppnJah3QuaRiLxk+jDWOR1MekYQrlytiXxC1RyfbdsZkS5pvKAzCCr031xHixZwpnsYNT5xysdFHQaYsA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "hoist-non-react-statics": "^3.1.0", - "loose-envify": "^1.3.1", - "path-to-regexp": "^1.7.0", - "prop-types": "^15.6.2", - "react-is": "^16.6.0", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - }, - "peerDependencies": { - "react": ">=15" - } - }, - "node_modules/react-router-config": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/react-router-config/-/react-router-config-5.1.1.tgz", - "integrity": "sha512-DuanZjaD8mQp1ppHjgnnUnyOlqYXZVjnov/JzFhjLEwd3Z4dYjMSnqrEzzGThH47vpCOqPPwJM2FtthLeJ8Pbg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.1.2" - }, - "peerDependencies": { - "react": ">=15", - "react-router": ">=5" - } - }, - "node_modules/react-router-dom": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.3.4.tgz", - "integrity": "sha512-m4EqFMHv/Ih4kpcBCONHbkT68KoAeHN4p3lAGoNryfHi0dMy0kCzEZakiKRsvg5wHZ/JLrLW8o8KomWiz/qbYQ==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.13", - "history": "^4.9.0", - "loose-envify": "^1.3.1", - "prop-types": "^15.6.2", - "react-router": "5.3.4", - "tiny-invariant": "^1.0.2", - "tiny-warning": "^1.0.0" - }, - "peerDependencies": { - "react": ">=15" - } - }, - "node_modules/react-router/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, "node_modules/react-smooth": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", @@ -22202,9 +10390,9 @@ } }, "node_modules/react-transition-state": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/react-transition-state/-/react-transition-state-2.3.1.tgz", - "integrity": "sha512-Z48el73x+7HUEM131dof9YpcQ5IlM4xB+pKWH/lX3FhxGfQaNTZa16zb7pWkC/y5btTZzXfCtglIJEGc57giOw==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/react-transition-state/-/react-transition-state-2.3.3.tgz", + "integrity": "sha512-wsIyg07ohlWEAYDZHvuXh/DY7mxlcLb0iqVv2aMXJ0gwgPVKNWKhOyNyzuJy/tt/6urSq0WT6BBZ/tdpybaAsQ==", "license": "MIT", "peerDependencies": { "react": ">=16.8.0", @@ -22221,24 +10409,11 @@ "pify": "^2.3.0" } }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -22285,73 +10460,6 @@ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", "license": "MIT" }, - "node_modules/recma-build-jsx": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", - "integrity": "sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-util-build-jsx": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/recma-jsx": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/recma-jsx/-/recma-jsx-1.0.1.tgz", - "integrity": "sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==", - "license": "MIT", - "dependencies": { - "acorn-jsx": "^5.0.0", - "estree-util-to-js": "^2.0.0", - "recma-parse": "^1.0.0", - "recma-stringify": "^1.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - }, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/recma-parse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-parse/-/recma-parse-1.0.0.tgz", - "integrity": "sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "esast-util-from-js": "^2.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/recma-stringify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/recma-stringify/-/recma-stringify-1.0.0.tgz", - "integrity": "sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-util-to-js": "^2.0.0", - "unified": "^11.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/redent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", @@ -22496,24 +10604,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "license": "MIT" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", - "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -22535,187 +10625,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/regexpu-core": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", - "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.2", - "regjsgen": "^0.8.0", - "regjsparser": "^0.13.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.2.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/registry-auth-token": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-5.1.0.tgz", - "integrity": "sha512-GdekYuwLXLxMuFTwAPg5UKGLW/UXzQrZvH/Zj791BQif5T05T0RsaLfHc9q3ZOKi7n+BoprPD9mJ0O0k4xzUlw==", - "license": "MIT", - "dependencies": { - "@pnpm/npm-conf": "^2.1.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/registry-url": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-6.0.1.tgz", - "integrity": "sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==", - "license": "MIT", - "dependencies": { - "rc": "1.2.8" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/regjsgen": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", - "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "license": "MIT" - }, - "node_modules/regjsparser": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", - "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", - "license": "BSD-2-Clause", - "dependencies": { - "jsesc": "~3.1.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/rehype-raw": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", - "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "hast-util-raw": "^9.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/rehype-recma": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rehype-recma/-/rehype-recma-1.0.0.tgz", - "integrity": "sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==", - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "@types/hast": "^3.0.0", - "hast-util-to-estree": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/remark-directive": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/remark-directive/-/remark-directive-3.0.1.tgz", - "integrity": "sha512-gwglrEQEZcZYgVyG1tQuA+h58EZfq5CSULw7J90AFuCTyib1thgHPoqQ+h9iFvU6R+vnZ5oNFQR5QKgGpk741A==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-directive": "^3.0.0", - "micromark-extension-directive": "^3.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-emoji": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/remark-emoji/-/remark-emoji-4.0.1.tgz", - "integrity": "sha512-fHdvsTR1dHkWKev9eNyhTo4EFwbUvJ8ka9SgeWkMPYFX4WoI7ViVBms3PjlQYgw5TLvNQso3GUB/b/8t3yo+dg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.2", - "emoticon": "^4.0.1", - "mdast-util-find-and-replace": "^3.0.1", - "node-emoji": "^2.1.0", - "unified": "^11.0.4" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/remark-frontmatter": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz", - "integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-frontmatter": "^2.0.0", - "micromark-extension-frontmatter": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-gfm": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", - "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-gfm": "^3.0.0", - "micromark-extension-gfm": "^3.0.0", - "remark-parse": "^11.0.0", - "remark-stringify": "^11.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/remark-mdx": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.1.tgz", - "integrity": "sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==", - "license": "MIT", - "dependencies": { - "mdast-util-mdx": "^3.0.0", - "micromark-extension-mdxjs": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/remark-parse": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", @@ -22749,66 +10658,16 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/remark-stringify": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", - "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", - "license": "MIT", - "dependencies": { - "@types/mdast": "^4.0.0", - "mdast-util-to-markdown": "^2.0.0", - "unified": "^11.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/renderkid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", - "integrity": "sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==", - "license": "MIT", - "dependencies": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.1" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/require-like": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", - "integrity": "sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==", - "engines": { - "node": "*" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "license": "MIT" - }, "node_modules/resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", @@ -22819,6 +10678,7 @@ "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.16.1", @@ -22835,27 +10695,16 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "license": "MIT" - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, - "node_modules/resolve-pathname": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz", - "integrity": "sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==", - "license": "MIT" - }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -22866,67 +10715,21 @@ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/responselike": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", - "license": "MIT", - "dependencies": { - "lowercase-keys": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/retry": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", - "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/robust-predicates": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", - "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", - "license": "Unlicense" - }, "node_modules/rollup": { - "version": "4.53.3", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", - "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", "dev": true, "license": "MIT", "dependencies": { @@ -22940,59 +10743,39 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.53.3", - "@rollup/rollup-android-arm64": "4.53.3", - "@rollup/rollup-darwin-arm64": "4.53.3", - "@rollup/rollup-darwin-x64": "4.53.3", - "@rollup/rollup-freebsd-arm64": "4.53.3", - "@rollup/rollup-freebsd-x64": "4.53.3", - "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", - "@rollup/rollup-linux-arm-musleabihf": "4.53.3", - "@rollup/rollup-linux-arm64-gnu": "4.53.3", - "@rollup/rollup-linux-arm64-musl": "4.53.3", - "@rollup/rollup-linux-loong64-gnu": "4.53.3", - "@rollup/rollup-linux-ppc64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-gnu": "4.53.3", - "@rollup/rollup-linux-riscv64-musl": "4.53.3", - "@rollup/rollup-linux-s390x-gnu": "4.53.3", - "@rollup/rollup-linux-x64-gnu": "4.53.3", - "@rollup/rollup-linux-x64-musl": "4.53.3", - "@rollup/rollup-openharmony-arm64": "4.53.3", - "@rollup/rollup-win32-arm64-msvc": "4.53.3", - "@rollup/rollup-win32-ia32-msvc": "4.53.3", - "@rollup/rollup-win32-x64-gnu": "4.53.3", - "@rollup/rollup-win32-x64-msvc": "4.53.3", + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" } }, - "node_modules/roughjs": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", - "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", - "license": "MIT", - "dependencies": { - "hachure-fill": "^0.5.2", - "path-data-parser": "^0.1.0", - "points-on-curve": "^0.2.0", - "points-on-path": "^0.2.1" - } - }, - "node_modules/run-applescript": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", - "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, "funding": [ { "type": "github", @@ -23012,12 +10795,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rw": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", - "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", - "license": "BSD-3-Clause" - }, "node_modules/safe-array-concat": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", @@ -23038,33 +10815,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-array-concat/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -23082,13 +10832,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safe-push-apply/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, "node_modules/safe-regex-test": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", @@ -23107,12 +10850,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -23135,59 +10872,6 @@ "loose-envify": "^1.1.0" } }, - "node_modules/schema-utils": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", - "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/schema-utils/node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/schema-utils/node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/schema-utils/node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT" - }, "node_modules/scroll-into-view-if-needed": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/scroll-into-view-if-needed/-/scroll-into-view-if-needed-3.1.0.tgz", @@ -23197,42 +10881,11 @@ "compute-scroll-into-view": "^3.0.2" } }, - "node_modules/section-matter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", - "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", - "license": "MIT", - "dependencies": { - "extend-shallow": "^2.0.1", - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/select-hose": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", - "license": "MIT" - }, - "node_modules/selfsigned": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", - "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", - "license": "MIT", - "dependencies": { - "@types/node-forge": "^1.3.0", - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/semver": { "version": "7.7.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "devOptional": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -23241,226 +10894,11 @@ "node": ">=10" } }, - "node_modules/semver-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-4.0.0.tgz", - "integrity": "sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==", - "license": "MIT", - "dependencies": { - "semver": "^7.3.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/send/node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-handler": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", - "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", - "license": "MIT", - "dependencies": { - "bytes": "3.0.0", - "content-disposition": "0.5.2", - "mime-types": "2.1.18", - "minimatch": "3.1.2", - "path-is-inside": "1.0.2", - "path-to-regexp": "3.3.0", - "range-parser": "1.2.0" - } - }, - "node_modules/serve-handler/node_modules/mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-handler/node_modules/mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "license": "MIT", - "dependencies": { - "mime-db": "~1.33.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-handler/node_modules/path-to-regexp": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", - "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", - "license": "MIT" - }, - "node_modules/serve-index": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "license": "MIT", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "license": "ISC" - }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "license": "ISC" - }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -23505,34 +10943,56 @@ "node": ">= 0.4" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "license": "MIT", + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, "dependencies": { - "kind-of": "^6.0.2" + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" }, "engines": { - "node": ">=8" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" } }, - "node_modules/shallowequal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", - "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", - "license": "MIT" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" @@ -23545,27 +11005,17 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/shell-quote": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -23585,6 +11035,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -23601,6 +11052,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -23619,6 +11071,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -23641,12 +11094,6 @@ "dev": true, "license": "ISC" }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "license": "ISC" - }, "node_modules/sirv": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.2.tgz", @@ -23662,71 +11109,6 @@ "node": ">=18" } }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "license": "MIT" - }, - "node_modules/skin-tone": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/skin-tone/-/skin-tone-2.0.0.tgz", - "integrity": "sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==", - "license": "MIT", - "dependencies": { - "unicode-emoji-modifier-base": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/sockjs": { - "version": "0.3.24", - "resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.24.tgz", - "integrity": "sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==", - "license": "MIT", - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, - "node_modules/sockjs/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/sort-css-media-queries": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/sort-css-media-queries/-/sort-css-media-queries-2.2.0.tgz", - "integrity": "sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==", - "license": "MIT", - "engines": { - "node": ">= 6.3.0" - } - }, - "node_modules/source-map": { - "version": "0.7.6", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", - "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">= 12" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -23736,25 +11118,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/space-separated-tokens": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", @@ -23765,36 +11128,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/spdy": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", - "integrity": "sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-3.0.0.tgz", - "integrity": "sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==", - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, "node_modules/stable-hash": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", @@ -23809,19 +11142,11 @@ "dev": true, "license": "MIT" }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/std-env": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, "license": "MIT" }, "node_modules/stop-iteration-iterator": { @@ -23838,73 +11163,12 @@ "node": ">= 0.4" } }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, "node_modules/string-convert": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz", "integrity": "sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==", "license": "MIT" }, - "node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", @@ -24032,32 +11296,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/stringify-object": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", - "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", - "license": "BSD-2-Clause", - "dependencies": { - "get-own-enumerable-property-symbols": "^3.0.0", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", @@ -24068,24 +11306,6 @@ "node": ">=4" } }, - "node_modules/strip-bom-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", - "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/strip-indent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", @@ -24151,9 +11371,9 @@ } }, "node_modules/styled-jsx": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", - "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", "license": "MIT", "dependencies": { "client-only": "0.0.1" @@ -24162,7 +11382,7 @@ "node": ">= 12.0.0" }, "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" }, "peerDependenciesMeta": { "@babel/core": { @@ -24173,22 +11393,6 @@ } } }, - "node_modules/stylehacks": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-6.1.1.tgz", - "integrity": "sha512-gSTTEQ670cJNoaeIp9KX6lZmm8LJ3jPB5yJmX8Zq/wQxOsAFXV3qjWzHas3YYk1qesuVIyYWWUpZ0vSE/dTSGg==", - "license": "MIT", - "dependencies": { - "browserslist": "^4.23.0", - "postcss-selector-parser": "^6.0.16" - }, - "engines": { - "node": "^14 || ^16 || >=18.0" - }, - "peerDependencies": { - "postcss": "^8.4.31" - } - }, "node_modules/stylis": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", @@ -24218,20 +11422,11 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/sucrase/node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -24244,6 +11439,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -24252,118 +11448,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/svgo": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz", - "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==", - "license": "MIT", - "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", - "css-select": "^5.1.0", - "css-tree": "^2.3.1", - "css-what": "^6.1.0", - "csso": "^5.0.5", - "picocolors": "^1.0.0" - }, - "bin": { - "svgo": "bin/svgo" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/svgo" - } - }, - "node_modules/svgo/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/svgo/node_modules/css-select": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", - "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/svgo/node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/svgo/node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/svgo/node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/svgo/node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/svgo/node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "license": "CC0-1.0" - }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -24372,9 +11456,9 @@ "license": "MIT" }, "node_modules/tabbable": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.3.0.tgz", - "integrity": "sha512-EIHvdY5bPLuWForiR/AN2Bxngzpuwn1is4asboytXtpTgsArc+WmSJKVLlhdh71u7jFcryDqB2A8lQvj78MkyQ==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", "license": "MIT" }, "node_modules/tailwind-merge": { @@ -24388,9 +11472,9 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.18", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz", - "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==", + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", "dev": true, "license": "MIT", "dependencies": { @@ -24425,119 +11509,36 @@ "node": ">=14.0.0" } }, - "node_modules/tailwindcss/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "node_modules/tailwindcss/node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/tailwindcss/node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, "license": "ISC", "dependencies": { - "is-glob": "^4.0.3" + "is-glob": "^4.0.1" }, "engines": { - "node": ">=10.13.0" + "node": ">= 6" } }, - "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/terser": { - "version": "5.44.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", - "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.15.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.25", - "jest-worker": "^27.4.5", - "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", - "terser": "^5.31.1" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT" - }, "node_modules/test-exclude": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", @@ -24579,13 +11580,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -24609,22 +11603,6 @@ "node": ">=0.8" } }, - "node_modules/thingies": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/thingies/-/thingies-2.5.0.tgz", - "integrity": "sha512-s+2Bwztg6PhWUD7XMfeYm5qliDdSiZm7M7n8KjTkIsm3l/2lgVRc2/Gx/v+ZX8lT4FMA+i8aQvhcWylldc+ZNw==", - "license": "MIT", - "engines": { - "node": ">=10.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "^2" - } - }, "node_modules/throttle-debounce": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-5.0.2.tgz", @@ -24634,24 +11612,12 @@ "node": ">=12.22" } }, - "node_modules/thunky": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", - "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", - "license": "MIT" - }, "node_modules/tiny-invariant": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, - "node_modules/tiny-warning": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", - "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==", - "license": "MIT" - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -24660,13 +11626,11 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", - "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", - "license": "MIT", - "engines": { - "node": ">=18" - } + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" }, "node_modules/tinyglobby": { "version": "0.2.15", @@ -24685,24 +11649,6 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, "node_modules/tinyglobby/node_modules/picomatch": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", @@ -24720,6 +11666,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" @@ -24746,22 +11693,22 @@ } }, "node_modules/tldts": { - "version": "7.0.18", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.18.tgz", - "integrity": "sha512-lCcgTAgMxQ1JKOWrVGo6E69Ukbnx4Gc1wiYLRf6J5NN4HRYJtCby1rPF8rkQ4a6qqoFBK5dvjJ1zJ0F7VfDSvw==", + "version": "7.0.21", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.21.tgz", + "integrity": "sha512-Plu6V8fF/XU6d2k8jPtlQf5F4Xx2hAin4r2C2ca7wR8NK5MbRTo9huLUWRe28f3Uk8bYZfg74tit/dSjc18xnw==", "dev": true, "license": "MIT", "dependencies": { - "tldts-core": "^7.0.18" + "tldts-core": "^7.0.21" }, "bin": { "tldts": "bin/cli.js" } }, "node_modules/tldts-core": { - "version": "7.0.18", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.18.tgz", - "integrity": "sha512-jqJC13oP4FFAahv4JT/0WTDrCF9Okv7lpKtOZUGPLiAnNbACcSg8Y8T+Z9xthOmRBqi/Sob4yi0TE0miRCvF7Q==", + "version": "7.0.21", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.21.tgz", + "integrity": "sha512-oVOMdHvgjqyzUZH1rOESgJP1uNe2bVrfK0jUHHmiM2rpEiRbf3j4BrsIc6JigJRbHGanQwuZv/R+LTcHsw+bLA==", "dev": true, "license": "MIT" }, @@ -24769,6 +11716,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { "is-number": "^7.0.0" @@ -24783,19 +11731,11 @@ "integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==", "license": "MIT" }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, "node_modules/totalist": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -24827,22 +11767,6 @@ "node": ">=20" } }, - "node_modules/tree-dump": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.1.0.tgz", - "integrity": "sha512-rMuvhU4MCDbcbnleZTFezWsaZXRFemSqAM+7jPnzUl1fo9w3YEKOxAeui0fz3OI4EU4hf23iyA7uQRVko+UaBA==", - "license": "Apache-2.0", - "engines": { - "node": ">=10.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/streamich" - }, - "peerDependencies": { - "tslib": "2" - } - }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -24864,9 +11788,9 @@ } }, "node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", "dev": true, "license": "MIT", "engines": { @@ -24876,15 +11800,6 @@ "typescript": ">=4.8.4" } }, - "node_modules/ts-dedent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", - "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", - "license": "MIT", - "engines": { - "node": ">=6.10" - } - }, "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", @@ -24905,19 +11820,6 @@ "strip-bom": "^3.0.0" } }, - "node_modules/tsconfig-paths/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -24937,31 +11839,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/typed-array-buffer": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", @@ -25040,15 +11917,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "license": "MIT", - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, "node_modules/typescript": { "version": "5.3.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", @@ -25063,12 +11931,6 @@ "node": ">=14.17" } }, - "node_modules/ufo": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", - "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", - "license": "MIT" - }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -25094,55 +11956,6 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "license": "MIT" }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", - "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-emoji-modifier-base": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unicode-emoji-modifier-base/-/unicode-emoji-modifier-base-1.0.0.tgz", - "integrity": "sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "license": "MIT", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", - "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", - "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -25162,21 +11975,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/unique-string": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-3.0.0.tgz", - "integrity": "sha512-VGXBUVwxKMBUznyffQweQABPRRW1vHZAbadFZud4pLFAqRGvv/96vafgjWFqzourzr8YonlQiPgH0YCJfawoGQ==", - "license": "MIT", - "dependencies": { - "crypto-random-string": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/unist-util-is": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", @@ -25203,19 +12001,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/unist-util-position-from-estree": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position-from-estree/-/unist-util-position-from-estree-2.0.0.tgz", - "integrity": "sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/unist-util-stringify-position": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", @@ -25230,9 +12015,9 @@ } }, "node_modules/unist-util-visit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", @@ -25258,24 +12043,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/unrs-resolver": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", @@ -25312,9 +12079,10 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, "funding": [ { "type": "opencollective", @@ -25341,164 +12109,23 @@ "browserslist": ">= 4.21.0" } }, - "node_modules/update-notifier": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-6.0.2.tgz", - "integrity": "sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==", - "license": "BSD-2-Clause", - "dependencies": { - "boxen": "^7.0.0", - "chalk": "^5.0.1", - "configstore": "^6.0.0", - "has-yarn": "^3.0.0", - "import-lazy": "^4.0.0", - "is-ci": "^3.0.1", - "is-installed-globally": "^0.4.0", - "is-npm": "^6.0.0", - "is-yarn-global": "^0.4.0", - "latest-version": "^7.0.0", - "pupa": "^3.1.0", - "semver": "^7.3.7", - "semver-diff": "^4.0.0", - "xdg-basedir": "^5.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/yeoman/update-notifier?sponsor=1" - } - }, - "node_modules/update-notifier/node_modules/boxen": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.1.1.tgz", - "integrity": "sha512-2hCgjEmP8YLWQ130n2FerGv7rYpfBmnmp9Uy2Le1vge6X3gZIfSmEzP5QTDElFxcvVcXlEn8Aq6MU/PZygIOog==", - "license": "MIT", - "dependencies": { - "ansi-align": "^3.0.1", - "camelcase": "^7.0.1", - "chalk": "^5.2.0", - "cli-boxes": "^3.0.0", - "string-width": "^5.1.2", - "type-fest": "^2.13.0", - "widest-line": "^4.0.1", - "wrap-ansi": "^8.1.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-notifier/node_modules/camelcase": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", - "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/update-notifier/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } }, - "node_modules/url-loader": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", - "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", - "license": "MIT", - "dependencies": { - "loader-utils": "^2.0.0", - "mime-types": "^2.1.27", - "schema-utils": "^3.0.0" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "file-loader": "*", - "webpack": "^4.0.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "file-loader": { - "optional": true - } - } - }, - "node_modules/url-loader/node_modules/schema-utils": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz", - "integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==", - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, "license": "MIT" }, - "node_modules/utila": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/utila/-/utila-0.4.0.tgz", - "integrity": "sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==", - "license": "MIT" - }, - "node_modules/utility-types": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", - "integrity": "sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==", - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/uuid": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", @@ -25512,21 +12139,6 @@ "uuid": "dist/esm/bin/uuid" } }, - "node_modules/value-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz", - "integrity": "sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==", - "license": "MIT" - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, "node_modules/vfile": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", @@ -25541,20 +12153,6 @@ "url": "https://opencollective.com/unified" } }, - "node_modules/vfile-location": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", - "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, "node_modules/vfile-message": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", @@ -25592,13 +12190,13 @@ } }, "node_modules/vite": { - "version": "7.2.4", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.4.tgz", - "integrity": "sha512-NL8jTlbo0Tn4dUEXEsUg8KeyG/Lkmc4Fnzb8JXN/Ykm9G4HNImjtABMJgkQoVjOBN/j2WAwDTRytdqJbZsah7w==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.25.0", + "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", @@ -25689,22 +12287,19 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/vite/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "node_modules/vite/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, "node_modules/vite/node_modules/picomatch": { @@ -25806,62 +12401,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/vitest/node_modules/tinyexec": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", - "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/vscode-jsonrpc": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", - "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/vscode-languageserver": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", - "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", - "license": "MIT", - "dependencies": { - "vscode-languageserver-protocol": "3.17.5" - }, - "bin": { - "installServerIntoExtension": "bin/installServerIntoExtension" - } - }, - "node_modules/vscode-languageserver-protocol": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", - "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", - "license": "MIT", - "dependencies": { - "vscode-jsonrpc": "8.2.0", - "vscode-languageserver-types": "3.17.5" - } - }, - "node_modules/vscode-languageserver-textdocument": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", - "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", - "license": "MIT" - }, - "node_modules/vscode-languageserver-types": { - "version": "3.17.5", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", - "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", - "license": "MIT" - }, - "node_modules/vscode-uri": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", - "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", - "license": "MIT" - }, "node_modules/w3c-xmlserializer": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", @@ -25875,38 +12414,6 @@ "node": ">=18" } }, - "node_modules/watchpack": { - "version": "2.4.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.4.tgz", - "integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==", - "license": "MIT", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wbuf": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", - "integrity": "sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==", - "license": "MIT", - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/web-namespaces": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", - "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/web-streams-polyfill": { "version": "4.0.0-beta.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", @@ -25917,436 +12424,15 @@ } }, "node_modules/webidl-conversions": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.0.tgz", - "integrity": "sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=20" } }, - "node_modules/webpack": { - "version": "5.103.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.103.0.tgz", - "integrity": "sha512-HU1JOuV1OavsZ+mfigY0j8d1TgQgbZ6M+J75zDkpEAwYeXjWSqrGJtgnPblJjd/mAyTNQ7ygw0MiKOn6etz8yw==", - "license": "MIT", - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.8", - "@types/json-schema": "^7.0.15", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.15.0", - "acorn-import-phases": "^1.0.3", - "browserslist": "^4.26.3", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.3", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.3.1", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^4.3.3", - "tapable": "^2.3.0", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.4", - "webpack-sources": "^3.3.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-bundle-analyzer": { - "version": "4.10.2", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", - "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", - "license": "MIT", - "dependencies": { - "@discoveryjs/json-ext": "0.5.7", - "acorn": "^8.0.4", - "acorn-walk": "^8.0.0", - "commander": "^7.2.0", - "debounce": "^1.2.1", - "escape-string-regexp": "^4.0.0", - "gzip-size": "^6.0.0", - "html-escaper": "^2.0.2", - "opener": "^1.5.2", - "picocolors": "^1.0.0", - "sirv": "^2.0.3", - "ws": "^7.3.1" - }, - "bin": { - "webpack-bundle-analyzer": "lib/bin/analyzer.js" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/sirv": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", - "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", - "license": "MIT", - "dependencies": { - "@polka/url": "^1.0.0-next.24", - "mrmime": "^2.0.0", - "totalist": "^3.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/webpack-bundle-analyzer/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "license": "MIT", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/webpack-dev-middleware": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/webpack-dev-middleware/-/webpack-dev-middleware-7.4.5.tgz", - "integrity": "sha512-uxQ6YqGdE4hgDKNf7hUiPXOdtkXvBJXrfEGYSx7P7LC8hnUYGK70X6xQXUvXeNyBDDcsiQXpG2m3G9vxowaEuA==", - "license": "MIT", - "dependencies": { - "colorette": "^2.0.10", - "memfs": "^4.43.1", - "mime-types": "^3.0.1", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - } - } - }, - "node_modules/webpack-dev-middleware/node_modules/mime-db": { - "version": "1.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-middleware/node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/webpack-dev-middleware/node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/webpack-dev-server": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-5.2.2.tgz", - "integrity": "sha512-QcQ72gh8a+7JO63TAx/6XZf/CWhgMzu5m0QirvPfGvptOusAxG12w2+aua1Jkjr7hzaWDnJ2n6JFeexMHI+Zjg==", - "license": "MIT", - "dependencies": { - "@types/bonjour": "^3.5.13", - "@types/connect-history-api-fallback": "^1.5.4", - "@types/express": "^4.17.21", - "@types/express-serve-static-core": "^4.17.21", - "@types/serve-index": "^1.9.4", - "@types/serve-static": "^1.15.5", - "@types/sockjs": "^0.3.36", - "@types/ws": "^8.5.10", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.2.1", - "chokidar": "^3.6.0", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "express": "^4.21.2", - "graceful-fs": "^4.2.6", - "http-proxy-middleware": "^2.0.9", - "ipaddr.js": "^2.1.0", - "launch-editor": "^2.6.1", - "open": "^10.0.3", - "p-retry": "^6.2.0", - "schema-utils": "^4.2.0", - "selfsigned": "^2.4.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^7.4.2", - "ws": "^8.18.0" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-dev-server/node_modules/define-lazy-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", - "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webpack-dev-server/node_modules/open": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", - "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", - "license": "MIT", - "dependencies": { - "default-browser": "^5.2.1", - "define-lazy-prop": "^3.0.0", - "is-inside-container": "^1.0.0", - "wsl-utils": "^0.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/webpack-merge": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-6.0.1.tgz", - "integrity": "sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==", - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "flat": "^5.0.2", - "wildcard": "^2.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.3.3.tgz", - "integrity": "sha512-yd1RBzSGanHkitROoPFd6qsrxt+oFhg/129YzheDGqeustzX0vTZJZsSsQjVQC4yzBQ56K55XU8gaNCtIzOnTg==", - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/webpack/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/webpackbar": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/webpackbar/-/webpackbar-6.0.1.tgz", - "integrity": "sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q==", - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.3.2", - "chalk": "^4.1.2", - "consola": "^3.2.3", - "figures": "^3.2.0", - "markdown-table": "^2.0.0", - "pretty-time": "^1.1.0", - "std-env": "^3.7.0", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=14.21.3" - }, - "peerDependencies": { - "webpack": "3 || 4 || 5" - } - }, - "node_modules/webpackbar/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/webpackbar/node_modules/markdown-table": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-2.0.0.tgz", - "integrity": "sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==", - "license": "MIT", - "dependencies": { - "repeat-string": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/webpackbar/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/webpackbar/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "license": "Apache-2.0", - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/whatwg-mimetype": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", @@ -26375,6 +12461,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, "license": "ISC", "dependencies": { "isexe": "^2.0.0" @@ -26434,13 +12521,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/which-builtin-type/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, "node_modules/which-collection": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", @@ -26461,9 +12541,9 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", "dev": true, "license": "MIT", "dependencies": { @@ -26499,27 +12579,6 @@ "node": ">=8" } }, - "node_modules/widest-line": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", - "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", - "license": "MIT", - "dependencies": { - "string-width": "^5.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/wildcard": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", - "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", - "license": "MIT" - }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -26530,78 +12589,11 @@ "node": ">=0.10.0" } }, - "node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", - "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, "node_modules/ws": { - "version": "8.18.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", - "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" @@ -26619,48 +12611,6 @@ } } }, - "node_modules/wsl-utils": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", - "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", - "license": "MIT", - "dependencies": { - "is-wsl": "^3.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/wsl-utils/node_modules/is-wsl": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", - "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", - "license": "MIT", - "dependencies": { - "is-inside-container": "^1.0.0" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/xdg-basedir": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-5.1.0.tgz", - "integrity": "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/xml-name-validator": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", @@ -26687,12 +12637,6 @@ "node": ">=0.4" } }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "license": "ISC" - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/ui/litellm-dashboard/package.json b/ui/litellm-dashboard/package.json index 385c1d82af7..23c6aa7c09e 100644 --- a/ui/litellm-dashboard/package.json +++ b/ui/litellm-dashboard/package.json @@ -8,6 +8,7 @@ "start": "next start", "lint": "next lint", "test": "vitest", + "test:dot": "vitest --reporter=dot", "test:watch": "vitest -w", "test:coverage": "vitest run --coverage", "format": "prettier --write .", @@ -17,8 +18,6 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.54.0", - "@docusaurus/theme-mermaid": "^3.9.0", - "@headlessui/react": "^1.7.18", "@headlessui/tailwindcss": "^0.2.0", "@heroicons/react": "^1.0.6", "@remixicon/react": "^4.1.1", @@ -29,17 +28,15 @@ "@types/papaparse": "^5.3.15", "antd": "^5.13.2", "cva": "^1.0.0-beta.3", - "fs": "^0.0.1-security", - "jsonwebtoken": "^9.0.2", "jwt-decode": "^4.0.0", "lucide-react": "^0.513.0", "moment": "^2.30.1", - "next": "^14.2.32", + "next": "^16.1.6", "openai": "^4.93.0", "papaparse": "^5.5.2", - "react": "^18", + "react": "^18.3.1", "react-copy-to-clipboard": "^5.1.0", - "react-dom": "^18", + "react-dom": "^18.3.1", "react-json-view-lite": "^2.5.0", "react-markdown": "^9.0.1", "react-syntax-highlighter": "^15.6.6", @@ -62,13 +59,12 @@ "@types/react-dom": "^18", "@types/react-syntax-highlighter": "^15.5.11", "@types/uuid": "^10.0.0", - "@vitejs/plugin-react": "^5.0.4", "@vitest/coverage-v8": "^3.2.4", "@vitest/ui": "^3.2.4", "autoprefixer": "^10.4.17", "dotenv": "^17.2.3", - "eslint": "^8", - "eslint-config-next": "14.2.32", + "eslint": "^9.39.2", + "eslint-config-next": "15.5.10", "eslint-config-prettier": "^10.1.8", "eslint-plugin-unused-imports": "^4.2.0", "jsdom": "^27.0.0", diff --git a/ui/litellm-dashboard/public/assets/logos/s3_vector.png b/ui/litellm-dashboard/public/assets/logos/s3_vector.png new file mode 100644 index 0000000000000000000000000000000000000000..15a1a456e129f9e2544d82399c6db3f2b9d8d204 GIT binary patch literal 191076 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
    ^jr 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

    3YYKR6{ z;eVZ|Xc9msvJQ0a1f4y;rYN*Z_5;#k5yNt59}4U~2)o?E#zdx9 zPC9b=LLI}SMFA4?dA#P8f6NaE>~Tu@QWvI++oir9CgMp4kI?3l&Tc?W#tuAvXxG2o z`NVw(c1}(WAD$T*oemMiJO;GrH~(E)nul4O$X+P9a_fkAVD z)hZipkv%7T(FL6-59!fX{`4|rAO;L`<9N}%Cs89xXqbMdTHEdj8JHIZz{o}u-W2m& z&OGPICmy}@;!E$o_wIVN##_x`8~?yLH50a{KMsbN9U!j5>|hlU)K;d7G`;mLYz=69 z@FSTG8xx#x2fCdkKlwrJhR-*PU73v=dAD4%Qo-r@7FR?hwV$bPA#{vP6+_^1VGJ|E zLL*-s>`D5G_ekwzlk&_{d-mLT|Ks0&a`%bxBO~Jzz9%w4R{F6Z%D<5;GYH^m<+}P_ zbo!~kzHZ|SPTknKaN$5_sl+x!HLu0%+Ibsna-uwqRp4hNNnt1sJ|KZ)syZd21zDGf!+Igp7&cJY zzz_l(3D`7?1sAD}VWLaSP|c}O4bxI*8?I4Ky+xiq>Fs3Q$KCivfH$!3Aa1V?PnRp> zhxd-`|HigwzH)5;(L?)^<`j`6Ipd_+MFz|NV=$Uvu-4yQ>M1Q)-?!|wtIvAl>1Qrk zy4q@0e#2%usZUlXCZ|~zWP1RKV}GfwsA@S$OEZ>|JQAo*z2dzoxul!rgPF{udvfa* z|m);sUsmyWd7yW+Q0uOJ2I7A|K`GLFLi;5Y+A}Tnc-hBbM7Qq(`KuMwR(j& zY#bk+*tBW=$ndfAF1+}``|hojEBvT0axmKgHICZ+Tl4NDr|U zsJKSDIc6&xTed0)dRLdLkvsMr8XKGH@9$0@B1|PpkQ8^jX!kjXpoJl>X^+jiTMCw+ z(mHV>cm4IXH(Zyz;+Lsw^{ZcAe8JVVkN{2QA;jEksMcJA8qjmHi?v-gQ(BL^Pd zKYcKLZjg!*(pl|Df!LCK* zjm?cG)1%2mBbl1yEl^3bQmxbsi|`K}B{di?<}O8DJATLHwFUqc@eyYaku`8^(B5&vr@jjM74WbTM8z=LJ8o4fRjEQSDd`OcM)FMU+K_FsaG}KJ( z1R_oul!0_iVJMtHa|bZFg{7k=izONcT4)9^>cqF`d@xsGlZAZnBVbqt%#N6RGxbL`0(z%2PURZ>>57!(7~NgA3d~vm{mI8K$+zGl7YSi5oK

    @@ -69,3 +71,11 @@ export default function Layout({ children }: { children: React.ReactNode }) { ); } + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + Loading...
    }> + {children} + + ); +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx index f707ef04ffb..6a4882a92a2 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/ModelsAndEndpointsView.tsx @@ -318,7 +318,6 @@ const ModelsAndEndpointsView: React.FC = ({ premiumUser, te onClose={() => { setSelectedModelId(null); }} - modelData={processedModelData.data.find((model: any) => model.model_info.id === selectedModelId)} accessToken={accessToken} userID={userID} userRole={userRole} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.test.tsx index 8a2298361c5..813a365d367 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.test.tsx @@ -11,7 +11,7 @@ const mockUseModelsInfo = vi.fn(() => ({ })) as any; vi.mock("../../hooks/models/useModels", () => ({ - useModelsInfo: (page?: number, size?: number) => mockUseModelsInfo(page, size), + useModelsInfo: (page?: number, size?: number, search?: string) => mockUseModelsInfo(page, size, search), })); // Mock the useModelCostMap hook @@ -74,7 +74,6 @@ describe("AllModelsTab", () => { const mockSetSelectedModelGroup = vi.fn(); const mockSetSelectedModelId = vi.fn(); const mockSetSelectedTeamId = vi.fn(); - const mockSetEditModel = vi.fn(); const defaultProps = { selectedModelGroup: "all", @@ -83,7 +82,6 @@ describe("AllModelsTab", () => { availableModelAccessGroups: ["sales-team", "engineering-team"], setSelectedModelId: mockSetSelectedModelId, setSelectedTeamId: mockSetSelectedTeamId, - setEditModel: mockSetEditModel, }; const mockUseAuthorized = { @@ -176,8 +174,10 @@ describe("AllModelsTab", () => { render(); + // Component shows API total_count (2), not filtered count + // Since default is "personal" team and models don't have direct_access, they're filtered out await waitFor(() => { - expect(screen.getByText("Showing 0 results")).toBeInTheDocument(); + expect(screen.getByText("Showing 1 - 2 of 2 results")).toBeInTheDocument(); }); }); @@ -235,8 +235,10 @@ describe("AllModelsTab", () => { render(); + // Component shows API total_count (2), not filtered count + // Since default is "personal" team and models don't have direct_access, they're filtered out await waitFor(() => { - expect(screen.getByText("Showing 0 results")).toBeInTheDocument(); + expect(screen.getByText("Showing 1 - 2 of 2 results")).toBeInTheDocument(); }); }); @@ -280,8 +282,9 @@ describe("AllModelsTab", () => { render(); + // Component shows API total_count (2), but only 1 model has direct_access await waitFor(() => { - expect(screen.getByText("Showing 1 - 1 of 1 results")).toBeInTheDocument(); + expect(screen.getByText("Showing 1 - 2 of 2 results")).toBeInTheDocument(); }); }); @@ -419,14 +422,15 @@ describe("AllModelsTab", () => { ); // Set up mock to return page1Data for page 1 - mockUseModelsInfo.mockImplementation((page: number = 1) => { + mockUseModelsInfo.mockImplementation((page: number = 1, size?: number, search?: string) => { return { data: page1Data, isLoading: false, error: null }; }); render(); await waitFor(() => { - expect(screen.getByText("Showing 1 - 1 of 1 results")).toBeInTheDocument(); + // Component calculates: ((1-1)*50)+1 = 1, Math.min(1*50, 2) = 2 + expect(screen.getByText("Showing 1 - 2 of 2 results")).toBeInTheDocument(); }); // Check that Previous button is disabled on first page @@ -471,7 +475,7 @@ describe("AllModelsTab", () => { 50, // size ); - mockUseModelsInfo.mockImplementation(() => { + mockUseModelsInfo.mockImplementation((page?: number, size?: number, search?: string) => { return { data: singlePageData, isLoading: false, error: null }; }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx index 04300b7fd16..a5553fa5f56 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/models-and-endpoints/components/AllModelsTab.tsx @@ -2,16 +2,17 @@ import { useModelCostMap } from "@/app/(dashboard)/hooks/models/useModelCostMap" import { useTeams } from "@/app/(dashboard)/hooks/teams/useTeams"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import { Team } from "@/components/key_team_helpers/key_list"; -import { ModelDataTable } from "@/components/model_dashboard/table"; +import { AllModelsDataTable } from "@/components/model_dashboard/all_models_table"; import { columns } from "@/components/molecules/models/columns"; import { getDisplayModelName } from "@/components/view_model/model_name_display"; import { InfoCircleOutlined } from "@ant-design/icons"; -import { PaginationState } from "@tanstack/react-table"; +import { PaginationState, SortingState } from "@tanstack/react-table"; import { Grid, Select, SelectItem, TabPanel, Text } from "@tremor/react"; +import { Skeleton, Spin } from "antd"; +import debounce from "lodash/debounce"; import { useEffect, useMemo, useState } from "react"; import { useModelsInfo } from "../../hooks/models/useModels"; import { transformModelData } from "../utils/modelDataTransformer"; -import { Skeleton } from "antd"; type ModelViewMode = "all" | "current_team"; interface AllModelsTabProps { @@ -33,9 +34,10 @@ const AllModelsTab = ({ }: AllModelsTabProps) => { const { data: modelCostMapData, isLoading: isLoadingModelCostMap } = useModelCostMap(); const { userId, userRole, premiumUser } = useAuthorized(); - const { data: teams } = useTeams(); + const { data: teams, isLoading: isLoadingTeams } = useTeams(); const [modelNameSearch, setModelNameSearch] = useState(""); + const [debouncedSearch, setDebouncedSearch] = useState(""); const [modelViewMode, setModelViewMode] = useState("current_team"); const [currentTeam, setCurrentTeam] = useState("personal"); const [showFilters, setShowFilters] = useState(false); @@ -47,8 +49,58 @@ const AllModelsTab = ({ pageIndex: 0, pageSize: 50, }); + const [sorting, setSorting] = useState([]); - const { data: rawModelData, isLoading: isLoadingModelsInfo } = useModelsInfo(currentPage, pageSize); + // Debounce search input + const debouncedUpdateSearch = useMemo( + () => + debounce((value: string) => { + setDebouncedSearch(value); + // Reset to page 1 when search changes + setCurrentPage(1); + setPagination((prev: PaginationState) => ({ ...prev, pageIndex: 0 })); + }, 200), + [] + ); + + useEffect(() => { + debouncedUpdateSearch(modelNameSearch); + return () => { + debouncedUpdateSearch.cancel(); + }; + }, [modelNameSearch, debouncedUpdateSearch]); + + // Determine teamId to pass to the query - only pass if not "personal" + const teamIdForQuery = currentTeam === "personal" ? undefined : currentTeam.team_id; + + // Convert sorting state to sortBy and sortOrder for API + const sortBy = useMemo(() => { + if (sorting.length === 0) return undefined; + const sort = sorting[0]; + const columnIdToServerField: Record = { + input_cost: "costs", // Map input_cost column to "costs" for server-side sorting + model_info_db_model: "status", // Map model_info.db_model column to "status" for server-side sorting + model_info_created_by: "created_at", // Map model_info.created_by column to "created_at" for server-side sorting + model_info_updated_at: "updated_at", // Map model_info.updated_at column to "updated_at" for server-side sorting + }; + return columnIdToServerField[sort.id] || sort.id; + }, [sorting]); + + const sortOrder = useMemo(() => { + if (sorting.length === 0) return undefined; + const sort = sorting[0]; + return sort.desc ? "desc" : "asc"; + }, [sorting]); + + const { data: rawModelData, isLoading: isLoadingModelsInfo } = useModelsInfo( + currentPage, + pageSize, + debouncedSearch || undefined, + undefined, + teamIdForQuery, + sortBy, + sortOrder + ); const isLoading = isLoadingModelsInfo || isLoadingModelCostMap; const getProviderFromModel = (model: string) => { @@ -88,10 +140,8 @@ const AllModelsTab = ({ return []; } + // Server-side search is now handled by the API, so we only filter by other criteria return modelData.data.filter((model: any) => { - const searchMatch = - modelNameSearch === "" || model.model_name.toLowerCase().includes(modelNameSearch.toLowerCase()); - const modelNameMatch = selectedModelGroup === "all" || model.model_name === selectedModelGroup || @@ -103,30 +153,28 @@ const AllModelsTab = ({ model.model_info["access_groups"]?.includes(selectedModelAccessGroupFilter) || !selectedModelAccessGroupFilter; - let teamAccessMatch = true; - if (modelViewMode === "current_team") { - if (currentTeam === "personal") { - teamAccessMatch = model.model_info?.direct_access === true; - } else { - // Check if model is directly associated with the team via team_ids - const directTeamAccess = model.model_info?.access_via_team_ids?.includes(currentTeam.team_id) === true; - - // Check if any of the team's models match the model's access groups - const accessGroupMatch = - currentTeam.models?.some((teamModel: string) => model.model_info?.access_groups?.includes(teamModel)) === - true; - - teamAccessMatch = directTeamAccess || accessGroupMatch; - } - } - - return searchMatch && modelNameMatch && accessGroupMatch && teamAccessMatch; + // Team filtering is now handled server-side via teamId query parameter + // Only apply client-side filtering for model groups and access groups + return modelNameMatch && accessGroupMatch; }); - }, [modelData, modelNameSearch, selectedModelGroup, selectedModelAccessGroupFilter, currentTeam, modelViewMode]); + }, [modelData, selectedModelGroup, selectedModelAccessGroupFilter]); useEffect(() => { setPagination((prev: PaginationState) => ({ ...prev, pageIndex: 0 })); - }, [modelNameSearch, selectedModelGroup, selectedModelAccessGroupFilter, currentTeam, modelViewMode]); + setCurrentPage(1); + }, [selectedModelGroup, selectedModelAccessGroupFilter]); + + // Reset pagination when team changes + useEffect(() => { + setCurrentPage(1); + setPagination((prev: PaginationState) => ({ ...prev, pageIndex: 0 })); + }, [teamIdForQuery]); + + // Reset pagination when sorting changes + useEffect(() => { + setCurrentPage(1); + setPagination((prev: PaginationState) => ({ ...prev, pageIndex: 0 })); + }, [sorting]); const resetFilters = () => { setModelNameSearch(""); @@ -136,6 +184,7 @@ const AllModelsTab = ({ setModelViewMode("current_team"); setCurrentPage(1); setPagination({ pageIndex: 0, pageSize: 50 }); + setSorting([]); }; return ( @@ -158,9 +207,17 @@ const AllModelsTab = ({ onValueChange={(value) => { if (value === "personal") { setCurrentTeam("personal"); + // Reset to page 1 when team changes + setCurrentPage(1); + setPagination((prev: PaginationState) => ({ ...prev, pageIndex: 0 })); } else { const team = teams?.find((t) => t.team_id === value); - if (team) setCurrentTeam(team); + if (team) { + setCurrentTeam(team); + // Reset to page 1 when team changes + setCurrentPage(1); + setPagination((prev: PaginationState) => ({ ...prev, pageIndex: 0 })); + } } }} > @@ -170,20 +227,29 @@ const AllModelsTab = ({ Personal
    - {teams - ?.filter((team) => team.team_id) - .map((team) => ( - -
    -
    - - {team.team_alias - ? `${team.team_alias.slice(0, 30)}...` - : `Team ${team.team_id.slice(0, 30)}...`} - -
    -
    - ))} + {isLoadingTeams ? ( + +
    + + Loading teams... +
    +
    + ) : ( + teams + ?.filter((team) => team.team_id) + .map((team) => ( + +
    +
    + + {team.team_alias + ? `${team.team_alias.slice(0, 30)}...` + : `Team ${team.team_id.slice(0, 30)}...`} + +
    +
    + )) + )} )} @@ -354,8 +420,8 @@ const AllModelsTab = ({ ) : ( - {filteredData.length > 0 - ? `Showing 1 - ${filteredData.length} of ${filteredData.length} results` + {paginationMeta.total_count > 0 + ? `Showing ${((currentPage - 1) * pageSize) + 1} - ${Math.min(currentPage * pageSize, paginationMeta.total_count)} of ${paginationMeta.total_count} results` : "Showing 0 results"} )} @@ -403,7 +469,7 @@ const AllModelsTab = ({ - { const { accessToken, userRole } = useAuthorized(); @@ -21,15 +10,6 @@ const PoliciesPage = () => { ); }; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/teams/components/modals/CreateTeamModal.tsx b/ui/litellm-dashboard/src/app/(dashboard)/teams/components/modals/CreateTeamModal.tsx index 972c39d49d8..0aa42b69a04 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/teams/components/modals/CreateTeamModal.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/teams/components/modals/CreateTeamModal.tsx @@ -14,7 +14,7 @@ import PremiumLoggingSettings from "@/components/common_components/PremiumLoggin import ModelAliasManager from "@/components/common_components/ModelAliasManager"; import React, { useEffect, useState } from "react"; import NotificationsManager from "@/components/molecules/notifications_manager"; -import { fetchMCPAccessGroups, getGuardrailsList, Organization, Team, teamCreateCall } from "@/components/networking"; +import { fetchMCPAccessGroups, getGuardrailsList, getPoliciesList, Organization, Team, teamCreateCall } from "@/components/networking"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import MCPToolPermissions from "@/components/mcp_server_management/MCPToolPermissions"; @@ -76,6 +76,7 @@ const CreateTeamModal = ({ const [currentOrgForCreateTeam, setCurrentOrgForCreateTeam] = useState(null); const [modelsToPick, setModelsToPick] = useState([]); const [guardrailsList, setGuardrailsList] = useState([]); + const [policiesList, setPoliciesList] = useState([]); const [mcpAccessGroups, setMcpAccessGroups] = useState([]); const [mcpAccessGroupsLoaded, setMcpAccessGroupsLoaded] = useState(false); @@ -136,7 +137,22 @@ const CreateTeamModal = ({ } }; + const fetchPolicies = async () => { + try { + if (accessToken == null) { + return; + } + + const response = await getPoliciesList(accessToken); + const policyNames = response.policies.map((p: { policy_name: string }) => p.policy_name); + setPoliciesList(policyNames); + } catch (error) { + console.error("Failed to fetch policies:", error); + } + }; + fetchGuardrails(); + fetchPolicies(); }, [accessToken]); const handleCreate = async (formValues: Record) => { @@ -531,6 +547,36 @@ const CreateTeamModal = ({ unCheckedChildren="No" /> + + Policies{" "} + +
    e.stopPropagation()} + > + + + + + } + name="policies" + className="mt-8" + help="Select existing policies or enter new ones" + > + ({ + value: name, + label: name, + }))} + /> + diff --git a/ui/litellm-dashboard/src/app/globals.css b/ui/litellm-dashboard/src/app/globals.css index a702982678e..0d453e284ef 100644 --- a/ui/litellm-dashboard/src/app/globals.css +++ b/ui/litellm-dashboard/src/app/globals.css @@ -37,31 +37,3 @@ body { .custom-border { border: 1px solid var(--neutral-border); } - -/* Custom dropdown styles */ -.ant-dropdown-menu-item { - padding: 0 !important; -} - -.ant-dropdown-menu-item > div { - transition: all 0.2s ease; -} - -/* Don't apply hover to user info section */ -.ant-dropdown-menu-item[data-menu-id$="user-info"]:hover { - background-color: transparent !important; - cursor: default; -} - -.ant-dropdown-menu-item[data-menu-id$="user-info"] > div { - cursor: default; -} - -.ant-dropdown-menu { - padding: 4px !important; - min-width: 280px !important; -} - -.ant-dropdown-menu-item-divider { - margin: 4px 0; -} diff --git a/ui/litellm-dashboard/src/app/mcp/oauth/callback/page.tsx b/ui/litellm-dashboard/src/app/mcp/oauth/callback/page.tsx index 252640cef71..f005eab4142 100644 --- a/ui/litellm-dashboard/src/app/mcp/oauth/callback/page.tsx +++ b/ui/litellm-dashboard/src/app/mcp/oauth/callback/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useMemo } from "react"; +import { Suspense, useEffect, useMemo } from "react"; import { useSearchParams } from "next/navigation"; const RESULT_STORAGE_KEY = "litellm-mcp-oauth-result"; @@ -21,7 +21,7 @@ const resolveDefaultRedirect = () => { return "/"; }; -const McpOAuthCallbackPage = () => { +const McpOAuthCallbackContent = () => { const searchParams = useSearchParams(); const payload = useMemo(() => { @@ -67,4 +67,12 @@ const McpOAuthCallbackPage = () => { ); }; +const McpOAuthCallbackPage = () => { + return ( + Loading...}> + + + ); +}; + export default McpOAuthCallbackPage; diff --git a/ui/litellm-dashboard/src/app/model_hub/page.tsx b/ui/litellm-dashboard/src/app/model_hub/page.tsx index d42f8576eb6..df6228f3b36 100644 --- a/ui/litellm-dashboard/src/app/model_hub/page.tsx +++ b/ui/litellm-dashboard/src/app/model_hub/page.tsx @@ -1,9 +1,9 @@ "use client"; -import React, { useEffect, useState } from "react"; +import React, { Suspense, useEffect, useState } from "react"; import { useSearchParams } from "next/navigation"; import PublicModelHubPage from "@/components/public_model_hub"; -export default function PublicModelHub() { +function PublicModelHubContent() { const searchParams = useSearchParams()!; const key = searchParams.get("key"); const [accessToken, setAccessToken] = useState(null); @@ -14,9 +14,14 @@ export default function PublicModelHub() { } setAccessToken(key); }, [key]); - /** - * populate navbar - * - */ + return ; } + +export default function PublicModelHub() { + return ( + Loading...}> + + + ); +} diff --git a/ui/litellm-dashboard/src/app/model_hub_table/page.tsx b/ui/litellm-dashboard/src/app/model_hub_table/page.tsx index dc5ae01935e..3f14c4fc3f2 100644 --- a/ui/litellm-dashboard/src/app/model_hub_table/page.tsx +++ b/ui/litellm-dashboard/src/app/model_hub_table/page.tsx @@ -1,12 +1,12 @@ "use client"; -import React, { useEffect, useState } from "react"; +import React, { Suspense, useEffect, useState } from "react"; import { useSearchParams } from "next/navigation"; import ModelHubTable from "@/components/AIHub/ModelHubTable"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; const queryClient = new QueryClient(); -export default function PublicModelHubTable() { +function PublicModelHubTableContent() { const searchParams = useSearchParams()!; const key = searchParams.get("key"); const [accessToken, setAccessToken] = useState(null); @@ -18,13 +18,18 @@ export default function PublicModelHubTable() { } setAccessToken(key); }, [key]); - /** - * populate navbar - * - */ + return ( ); } + +export default function PublicModelHubTable() { + return ( + Loading...}> + + + ); +} diff --git a/ui/litellm-dashboard/src/app/onboarding/page.tsx b/ui/litellm-dashboard/src/app/onboarding/page.tsx index 7e5d91c001f..3bdf57907ee 100644 --- a/ui/litellm-dashboard/src/app/onboarding/page.tsx +++ b/ui/litellm-dashboard/src/app/onboarding/page.tsx @@ -1,5 +1,5 @@ "use client"; -import React, { useEffect, useState } from "react"; +import React, { Suspense, useEffect, useState } from "react"; import { useSearchParams } from "next/navigation"; import { Card, Title, Text, TextInput, Callout, Button, Grid, Col } from "@tremor/react"; import { RiCheckboxCircleLine } from "@remixicon/react"; @@ -13,7 +13,7 @@ import { jwtDecode } from "jwt-decode"; import { Form, Button as Button2 } from "antd"; import { getCookie } from "@/utils/cookieUtils"; -export default function Onboarding() { +function OnboardingContent() { const [form] = Form.useForm(); const searchParams = useSearchParams()!; const token = getCookie("token"); @@ -140,3 +140,11 @@ export default function Onboarding() { ); } + +export default function Onboarding() { + return ( + Loading...}> + + + ); +} diff --git a/ui/litellm-dashboard/src/app/page.tsx b/ui/litellm-dashboard/src/app/page.tsx index 8ac1f756f96..5f94db7e9f4 100644 --- a/ui/litellm-dashboard/src/app/page.tsx +++ b/ui/litellm-dashboard/src/app/page.tsx @@ -43,8 +43,9 @@ import { isJwtExpired } from "@/utils/jwtUtils"; import { isAdminRole } from "@/utils/roles"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { jwtDecode } from "jwt-decode"; -import { useSearchParams } from "next/navigation"; +import { useSearchParams, ReadonlyURLSearchParams } from "next/navigation"; import { Suspense, useEffect, useState } from "react"; +import { ConfigProvider, theme } from "antd"; function getCookie(name: string) { // Safer cookie read + decoding; handles '=' inside values @@ -100,7 +101,7 @@ interface ProxySettings { const queryClient = new QueryClient(); -export default function CreateKeyPage() { +function CreateKeyPageContent() { const [userRole, setUserRole] = useState(""); const [premiumUser, setPremiumUser] = useState(false); const [disabledPersonalKeyCreation, setDisabledPersonalKeyCreation] = useState(false); @@ -131,6 +132,12 @@ export default function CreateKeyPage() { const [showClaudeCodePrompt, setShowClaudeCodePrompt] = useState(false); const [showClaudeCodeModal, setShowClaudeCodeModal] = useState(false); + // Dark mode state + const [isDarkMode, setIsDarkMode] = useState(false); + const toggleDarkMode = () => { + setIsDarkMode(!isDarkMode); + }; + const invitation_id = searchParams.get("invitation_id"); // Get page from URL, default to 'api-keys' if not present @@ -282,7 +289,7 @@ export default function CreateKeyPage() { const nudgesConfig = await getInProductNudgesCall(accessToken); const isUsingClaudeCode = nudgesConfig?.is_claude_code_enabled || false; setIsClaudeCode(isUsingClaudeCode); - + // Show Claude Code prompt on login if enabled if (isUsingClaudeCode) { setShowClaudeCodePrompt(true); @@ -362,226 +369,240 @@ export default function CreateKeyPage() { return ( }> - - {invitation_id ? ( - - ) : ( -
    - + + {invitation_id ? ( + -
    -
    - -
    + ) : ( +
    + +
    +
    + +
    - {page == "api-keys" ? ( - - ) : page == "models" ? ( - - ) : page == "llm-playground" ? ( - - ) : page == "users" ? ( - - ) : page == "teams" ? ( - - ) : page == "organizations" ? ( - - ) : page == "admin-panel" ? ( - - ) : page == "api_ref" ? ( - - ) : page == "logging-and-alerts" ? ( - - ) : page == "budgets" ? ( - - ) : page == "guardrails" ? ( - - ) : page == "policies" ? ( - - ) : page == "agents" ? ( - - ) : page == "prompts" ? ( - - ) : page == "transform-request" ? ( - - ) : page == "router-settings" ? ( - - ) : page == "ui-theme" ? ( - - ) : page == "cost-tracking" ? ( - - ) : page == "model-hub-table" ? ( - isAdminRole(userRole) ? ( - + ) : page == "models" ? ( + + ) : page == "llm-playground" ? ( + + ) : page == "users" ? ( + + ) : page == "teams" ? ( + + ) : page == "organizations" ? ( + + ) : page == "admin-panel" ? ( + + ) : page == "api_ref" ? ( + + ) : page == "logging-and-alerts" ? ( + + ) : page == "budgets" ? ( + + ) : page == "guardrails" ? ( + + ) : page == "policies" ? ( + + ) : page == "agents" ? ( + + ) : page == "prompts" ? ( + + ) : page == "transform-request" ? ( + + ) : page == "router-settings" ? ( + + ) : page == "ui-theme" ? ( + + ) : page == "cost-tracking" ? ( + + ) : page == "model-hub-table" ? ( + isAdminRole(userRole) ? ( + + ) : ( + + ) + ) : page == "caching" ? ( + + ) : page == "pass-through-settings" ? ( + + ) : page == "logs" ? ( + + ) : page == "mcp-servers" ? ( + + ) : page == "search-tools" ? ( + + ) : page == "tag-management" ? ( + + ) : page == "claude-code-plugins" ? ( + + ) : page == "vector-stores" ? ( + + ) : page == "new_usage" ? ( + ) : ( - - ) - ) : page == "caching" ? ( - - ) : page == "pass-through-settings" ? ( - - ) : page == "logs" ? ( - - ) : page == "mcp-servers" ? ( - - ) : page == "search-tools" ? ( - - ) : page == "tag-management" ? ( - - ) : page == "claude-code-plugins" ? ( - - ) : page == "vector-stores" ? ( - - ) : page == "new_usage" ? ( - - ) : ( - - )} + + )} +
    + + {/* Survey Components */} + + + + {/* Claude Code Components */} + +
    - - {/* Survey Components */} - - - - {/* Claude Code Components */} - - -
    - )} -
    + )} + + ); } + +export default function CreateKeyPage() { + return ( + }> + + + ); +} diff --git a/ui/litellm-dashboard/src/components/BulkEditUsers.test.tsx b/ui/litellm-dashboard/src/components/BulkEditUsers.test.tsx new file mode 100644 index 00000000000..4185625e746 --- /dev/null +++ b/ui/litellm-dashboard/src/components/BulkEditUsers.test.tsx @@ -0,0 +1,343 @@ +import userEvent from "@testing-library/user-event"; +import { describe, expect, it, vi, beforeEach } from "vitest"; +import { renderWithProviders, screen, waitFor } from "../../tests/test-utils"; +import BulkEditUserModal from "./BulkEditUsers"; +import { userBulkUpdateUserCall, teamBulkMemberAddCall } from "./networking"; +import NotificationsManager from "./molecules/notifications_manager"; + +vi.mock("./networking", () => ({ + userBulkUpdateUserCall: vi.fn(), + teamBulkMemberAddCall: vi.fn(), +})); + +vi.mock("./user_edit_view", () => ({ + UserEditView: ({ onSubmit, onCancel }: { onSubmit: (values: any) => void; onCancel: () => void }) => ( +
    + + +
    + ), +})); + +const mockUserBulkUpdateUserCall = vi.mocked(userBulkUpdateUserCall); +const mockTeamBulkMemberAddCall = vi.mocked(teamBulkMemberAddCall); + +const defaultProps = { + open: true, + onCancel: vi.fn(), + selectedUsers: [ + { user_id: "user1", user_email: "user1@example.com", user_role: "user", max_budget: 50 }, + { user_id: "user2", user_email: "user2@example.com", user_role: "admin", max_budget: null }, + ], + possibleUIRoles: { + admin: { ui_label: "Admin", description: "Administrator role" }, + user: { ui_label: "User", description: "Regular user role" }, + }, + accessToken: "test-token", + onSuccess: vi.fn(), + teams: [ + { team_id: "team1", team_alias: "Team 1" }, + { team_id: "team2", team_alias: "Team 2" }, + ], + userRole: "Admin", + userModels: ["gpt-4", "gpt-3.5-turbo"], + allowAllUsers: false, +}; + +describe("BulkEditUserModal", () => { + beforeEach(() => { + vi.clearAllMocks(); + mockUserBulkUpdateUserCall.mockResolvedValue({ + results: [], + total_requested: 2, + successful_updates: 2, + failed_updates: 0, + }); + mockTeamBulkMemberAddCall.mockResolvedValue({ + successful_additions: 2, + failed_additions: 0, + }); + }); + + it("should render without crashing", () => { + renderWithProviders(); + + expect(screen.getByText(`Bulk Edit ${defaultProps.selectedUsers.length} User(s)`)).toBeInTheDocument(); + }); + + it("should display modal title with correct user count", () => { + renderWithProviders(); + + expect(screen.getByText("Bulk Edit 2 User(s)")).toBeInTheDocument(); + }); + + it("should display selected users table when modal is open", () => { + renderWithProviders(); + + expect(screen.getByText("Selected Users (2):")).toBeInTheDocument(); + expect(screen.getByText("user1")).toBeInTheDocument(); + expect(screen.getByText("user2")).toBeInTheDocument(); + expect(screen.getByText("user1@example.com")).toBeInTheDocument(); + expect(screen.getByText("user2@example.com")).toBeInTheDocument(); + }); + + it("should display user roles in table", () => { + renderWithProviders(); + + expect(screen.getByText("User")).toBeInTheDocument(); + expect(screen.getByText("Admin")).toBeInTheDocument(); + }); + + it("should display budget information in table", () => { + renderWithProviders(); + + expect(screen.getByText("$50")).toBeInTheDocument(); + expect(screen.getByText("Unlimited")).toBeInTheDocument(); + }); + + it("should call onCancel when cancel button is clicked", async () => { + const user = userEvent.setup(); + const onCancel = vi.fn(); + renderWithProviders(); + + const cancelButton = screen.getByRole("button", { name: "Cancel" }); + await user.click(cancelButton); + + expect(onCancel).toHaveBeenCalledTimes(1); + }); + + + it("should show update all users checkbox when allowAllUsers is true", () => { + renderWithProviders(); + + expect(screen.getByRole("checkbox", { name: /update all users/i })).toBeInTheDocument(); + }); + + it("should not show update all users checkbox when allowAllUsers is false", () => { + renderWithProviders(); + + expect(screen.queryByRole("checkbox", { name: /update all users/i })).not.toBeInTheDocument(); + }); + + it("should toggle update all users mode", async () => { + const user = userEvent.setup(); + renderWithProviders(); + + const checkbox = screen.getByRole("checkbox", { name: /update all users/i }); + expect(checkbox).not.toBeChecked(); + + await user.click(checkbox); + + expect(checkbox).toBeChecked(); + expect(screen.getByText("Bulk Edit All Users")).toBeInTheDocument(); + }); + + it("should show warning message when update all users is enabled", async () => { + const user = userEvent.setup(); + renderWithProviders(); + + const checkbox = screen.getByRole("checkbox", { name: /update all users/i }); + await user.click(checkbox); + + expect(screen.getByText(/this will apply changes to all users/i)).toBeInTheDocument(); + }); + + it("should hide selected users table when update all users is enabled", async () => { + const user = userEvent.setup(); + renderWithProviders(); + + expect(screen.getByText("Selected Users (2):")).toBeInTheDocument(); + + const checkbox = screen.getByRole("checkbox", { name: /update all users/i }); + await user.click(checkbox); + + expect(screen.queryByText("Selected Users (2):")).not.toBeInTheDocument(); + }); + + it("should display team management section", () => { + renderWithProviders(); + + expect(screen.getByText("Team Management")).toBeInTheDocument(); + expect(screen.getByRole("checkbox", { name: /add selected users to teams/i })).toBeInTheDocument(); + }); + + it("should show team budget input when add to teams is checked", async () => { + const user = userEvent.setup(); + renderWithProviders(); + + const addToTeamsCheckbox = screen.getByRole("checkbox", { name: /add selected users to teams/i }); + await user.click(addToTeamsCheckbox); + + expect(screen.getByText("Team Budget (Optional):")).toBeInTheDocument(); + expect(screen.getByPlaceholderText("Max budget per user in team")).toBeInTheDocument(); + }); + + it("should render UserEditView component", () => { + renderWithProviders(); + + expect(screen.getByTestId("user-edit-view")).toBeInTheDocument(); + }); + + it("should show error when access token is missing", async () => { + const user = userEvent.setup(); + renderWithProviders(); + + const submitButton = screen.getByRole("button", { name: "Submit" }); + await user.click(submitButton); + + await waitFor(() => { + expect(NotificationsManager.fromBackend).toHaveBeenCalledWith("Access token not found"); + }); + }); + + it("should call userBulkUpdateUserCall with correct payload for selected users", async () => { + const user = userEvent.setup(); + renderWithProviders(); + + const submitButton = screen.getByRole("button", { name: "Submit" }); + await user.click(submitButton); + + await waitFor(() => { + expect(mockUserBulkUpdateUserCall).toHaveBeenCalledWith( + "test-token", + { user_role: "admin", max_budget: 100 }, + ["user1", "user2"], + ); + }); + }); + + it("should call userBulkUpdateUserCall with allUsers flag when update all users is enabled", async () => { + const user = userEvent.setup(); + renderWithProviders(); + + const updateAllCheckbox = screen.getByRole("checkbox", { name: /update all users/i }); + await user.click(updateAllCheckbox); + + const submitButton = screen.getByRole("button", { name: "Submit" }); + await user.click(submitButton); + + await waitFor(() => { + expect(mockUserBulkUpdateUserCall).toHaveBeenCalledWith( + "test-token", + expect.objectContaining({ user_role: "admin", max_budget: 100 }), + undefined, + true, + ); + }); + }); + + + it("should show success message after successful user update", async () => { + const user = userEvent.setup(); + mockUserBulkUpdateUserCall.mockResolvedValue({ + results: [], + total_requested: 2, + successful_updates: 2, + failed_updates: 0, + }); + + renderWithProviders(); + + const submitButton = screen.getByRole("button", { name: "Submit" }); + await user.click(submitButton); + + await waitFor(() => { + expect(NotificationsManager.success).toHaveBeenCalledWith("Updated 2 user(s)"); + }); + }); + + it("should show success message for all users update", async () => { + const user = userEvent.setup(); + mockUserBulkUpdateUserCall.mockResolvedValue({ + results: [], + total_requested: 100, + successful_updates: 100, + failed_updates: 0, + }); + + renderWithProviders(); + + const updateAllCheckbox = screen.getByRole("checkbox", { name: /update all users/i }); + await user.click(updateAllCheckbox); + + const submitButton = screen.getByRole("button", { name: "Submit" }); + await user.click(submitButton); + + await waitFor(() => { + expect(NotificationsManager.success).toHaveBeenCalledWith("Updated all users (100 total)"); + }); + }); + + + it("should show error message when bulk update fails", async () => { + const user = userEvent.setup(); + mockUserBulkUpdateUserCall.mockRejectedValueOnce(new Error("Update failed")); + + renderWithProviders(); + + const submitButton = screen.getByRole("button", { name: "Submit" }); + await user.click(submitButton); + + await waitFor(() => { + expect(NotificationsManager.fromBackend).toHaveBeenCalledWith("Failed to perform bulk operations"); + }); + }); + + it("should call onSuccess and onCancel after successful update", async () => { + const user = userEvent.setup(); + const onSuccess = vi.fn(); + const onCancel = vi.fn(); + + renderWithProviders(); + + const submitButton = screen.getByRole("button", { name: "Submit" }); + await user.click(submitButton); + + await waitFor(() => { + expect(onSuccess).toHaveBeenCalledTimes(1); + expect(onCancel).toHaveBeenCalledTimes(1); + }); + }); + + it("should truncate long user IDs in table", () => { + const longUserId = "a".repeat(30); + const propsWithLongId = { + ...defaultProps, + selectedUsers: [{ user_id: longUserId, user_email: "test@example.com", user_role: "user", max_budget: null }], + }; + + renderWithProviders(); + + expect(screen.getByText(new RegExp(`${longUserId.slice(0, 20)}...`))).toBeInTheDocument(); + }); + + it("should display no email text when user email is missing", () => { + const propsWithoutEmail = { + ...defaultProps, + selectedUsers: [{ user_id: "user1", user_email: null, user_role: "user", max_budget: null }], + }; + + renderWithProviders(); + + expect(screen.getByText("No email")).toBeInTheDocument(); + }); + + it("should display role label from possibleUIRoles when available", () => { + renderWithProviders(); + + expect(screen.getByText("Admin")).toBeInTheDocument(); + expect(screen.getByText("User")).toBeInTheDocument(); + }); + + it("should display role key when ui_label is not available", () => { + const propsWithoutUIRoles = { + ...defaultProps, + possibleUIRoles: null, + }; + + renderWithProviders(); + + expect(screen.getByText("user")).toBeInTheDocument(); + expect(screen.getByText("admin")).toBeInTheDocument(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/bulk_edit_user.tsx b/ui/litellm-dashboard/src/components/BulkEditUsers.tsx similarity index 98% rename from ui/litellm-dashboard/src/components/bulk_edit_user.tsx rename to ui/litellm-dashboard/src/components/BulkEditUsers.tsx index b3847911cc0..2f3e57ff2a8 100644 --- a/ui/litellm-dashboard/src/components/bulk_edit_user.tsx +++ b/ui/litellm-dashboard/src/components/BulkEditUsers.tsx @@ -18,7 +18,7 @@ import NotificationsManager from "./molecules/notifications_manager"; const { Text, Title } = Typography; interface BulkEditUserModalProps { - visible: boolean; + open: boolean; onCancel: () => void; selectedUsers: any[]; possibleUIRoles: Record> | null; @@ -31,7 +31,7 @@ interface BulkEditUserModalProps { } const BulkEditUserModal: React.FC = ({ - visible, + open, onCancel, selectedUsers, possibleUIRoles, @@ -75,7 +75,7 @@ const BulkEditUserModal: React.FC = ({ keys: [], teams: teams || [], }), - [teams, visible], + [teams, open], ); const handleSubmit = async (formValues: any) => { @@ -145,7 +145,7 @@ const BulkEditUserModal: React.FC = ({ if (updateAllUsers) { members = null; } else { - const members = selectedUsers.map((user) => ({ + members = selectedUsers.map((user) => ({ user_id: user.user_id, role: "user" as const, // Default role for bulk add user_email: user.user_email || null, @@ -214,7 +214,7 @@ const BulkEditUserModal: React.FC = ({ return ( = ({ value, onChange + +
    - {config.fields.map((field, index) => ( + {config.fields.map((field, index) => field && ( {field.render(values)} diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/utils.test.ts b/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/utils.test.ts index 718302d35fe..722d52d64f9 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/utils.test.ts +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/utils.test.ts @@ -12,6 +12,8 @@ describe("processSSOSettingsPayload", () => { default_role: "proxy_admin", group_claim: "groups", use_role_mappings: false, + use_team_mappings: false, + team_ids_jwt_field: "teams", other_field: "value", another_field: 123, }; @@ -23,6 +25,7 @@ describe("processSSOSettingsPayload", () => { another_field: 123, }); expect(result.role_mappings).toBeUndefined(); + expect(result.team_mappings).toBeUndefined(); }); it("should return all fields except role mapping fields when use_role_mappings is not present", () => { @@ -33,6 +36,8 @@ describe("processSSOSettingsPayload", () => { internal_viewer_teams: "viewer1", default_role: "proxy_admin", group_claim: "groups", + use_team_mappings: false, + team_ids_jwt_field: "teams", other_field: "value", }; @@ -42,6 +47,7 @@ describe("processSSOSettingsPayload", () => { other_field: "value", }); expect(result.role_mappings).toBeUndefined(); + expect(result.team_mappings).toBeUndefined(); }); }); @@ -253,6 +259,143 @@ describe("processSSOSettingsPayload", () => { }); }); + describe("without team mappings", () => { + it("should return all fields except team mapping fields when use_team_mappings is false", () => { + const formValues = { + use_team_mappings: false, + team_ids_jwt_field: "teams", + sso_provider: "okta", + other_field: "value", + }; + + const result = processSSOSettingsPayload(formValues); + + expect(result).toEqual({ + sso_provider: "okta", + other_field: "value", + }); + expect(result.team_mappings).toBeUndefined(); + }); + + it("should return all fields except team mapping fields when use_team_mappings is not present", () => { + const formValues = { + team_ids_jwt_field: "teams", + sso_provider: "generic", + other_field: "value", + }; + + const result = processSSOSettingsPayload(formValues); + + expect(result).toEqual({ + sso_provider: "generic", + other_field: "value", + }); + expect(result.team_mappings).toBeUndefined(); + }); + + it("should not include team mappings for unsupported providers even when use_team_mappings is true", () => { + const formValues = { + use_team_mappings: true, + team_ids_jwt_field: "teams", + sso_provider: "google", + other_field: "value", + }; + + const result = processSSOSettingsPayload(formValues); + + expect(result).toEqual({ + sso_provider: "google", + other_field: "value", + }); + expect(result.team_mappings).toBeUndefined(); + }); + + it("should not include team mappings for microsoft provider even when use_team_mappings is true", () => { + const formValues = { + use_team_mappings: true, + team_ids_jwt_field: "teams", + sso_provider: "microsoft", + other_field: "value", + }; + + const result = processSSOSettingsPayload(formValues); + + expect(result).toEqual({ + sso_provider: "microsoft", + other_field: "value", + }); + expect(result.team_mappings).toBeUndefined(); + }); + }); + + describe("with team mappings enabled", () => { + it("should create team mappings for okta provider when use_team_mappings is true", () => { + const formValues = { + use_team_mappings: true, + team_ids_jwt_field: "teams", + sso_provider: "okta", + other_field: "value", + }; + + const result = processSSOSettingsPayload(formValues); + + expect(result.other_field).toBe("value"); + expect(result.team_mappings).toEqual({ + team_ids_jwt_field: "teams", + }); + }); + + it("should create team mappings for generic provider when use_team_mappings is true", () => { + const formValues = { + use_team_mappings: true, + team_ids_jwt_field: "custom_teams", + sso_provider: "generic", + other_field: "value", + }; + + const result = processSSOSettingsPayload(formValues); + + expect(result.other_field).toBe("value"); + expect(result.team_mappings).toEqual({ + team_ids_jwt_field: "custom_teams", + }); + }); + + it("should exclude team mapping fields from payload when team mappings are included", () => { + const formValues = { + use_team_mappings: true, + team_ids_jwt_field: "teams", + sso_provider: "okta", + other_field: "value", + }; + + const result = processSSOSettingsPayload(formValues); + + expect(result.use_team_mappings).toBeUndefined(); + expect(result.team_ids_jwt_field).toBeUndefined(); + }); + + it("should handle team mappings and role mappings together", () => { + const formValues = { + use_team_mappings: true, + team_ids_jwt_field: "teams", + use_role_mappings: true, + group_claim: "groups", + default_role: "internal_user", + sso_provider: "okta", + other_field: "value", + }; + + const result = processSSOSettingsPayload(formValues); + + expect(result.team_mappings).toEqual({ + team_ids_jwt_field: "teams", + }); + expect(result.role_mappings).toBeDefined(); + expect(result.role_mappings.group_claim).toBe("groups"); + }); + }); + describe("edge cases", () => { it("should handle empty form values", () => { const result = processSSOSettingsPayload({}); @@ -263,6 +406,7 @@ describe("processSSOSettingsPayload", () => { it("should preserve other fields in the payload", () => { const formValues = { use_role_mappings: false, + use_team_mappings: false, sso_provider: "google", client_id: "123", client_secret: "secret", diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/utils.ts b/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/utils.ts index c199048df3e..948ed4d2bfe 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/utils.ts +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/SSOSettings/utils.ts @@ -13,6 +13,8 @@ export const processSSOSettingsPayload = (formValues: Record): Reco default_role, group_claim, use_role_mappings, + use_team_mappings, + team_ids_jwt_field, ...rest } = formValues; @@ -21,7 +23,9 @@ export const processSSOSettingsPayload = (formValues: Record): Reco }; // Add role mappings only if use_role_mappings is checked AND provider supports role mappings - if (use_role_mappings) { + const provider = rest.sso_provider; + const supportsRoleMappings = provider === "okta" || provider === "generic"; + if (use_role_mappings && supportsRoleMappings) { // Helper function to split comma-separated string into array const splitTeams = (teams: string | undefined): string[] => { if (!teams || teams.trim() === "") return []; @@ -52,6 +56,14 @@ export const processSSOSettingsPayload = (formValues: Record): Reco }; } + // Add team mappings only if use_team_mappings is checked AND provider supports team mappings + const supportsTeamMappings = provider === "okta" || provider === "generic"; + if (use_team_mappings && supportsTeamMappings) { + payload.team_mappings = { + team_ids_jwt_field: team_ids_jwt_field, + }; + } + return payload; }; diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/PageVisibilitySettings.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/PageVisibilitySettings.tsx new file mode 100644 index 00000000000..47e4a8bb7f3 --- /dev/null +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/PageVisibilitySettings.tsx @@ -0,0 +1,146 @@ +"use client"; + +import { getAvailablePages } from "@/components/page_utils"; +import { Button, Checkbox, Collapse, Space, Tag, Typography } from "antd"; +import { useMemo, useState } from "react"; + +interface PageVisibilitySettingsProps { + enabledPagesInternalUsers: string[] | null | undefined; + enabledPagesPropertyDescription?: string; + isUpdating: boolean; + onUpdate: (settings: { enabled_ui_pages_internal_users: string[] | null }) => void; +} + +export default function PageVisibilitySettings({ + enabledPagesInternalUsers, + enabledPagesPropertyDescription, + isUpdating, + onUpdate, +}: PageVisibilitySettingsProps) { + // Check if page visibility is set (null/undefined means "not set" = all pages visible) + const isPageVisibilitySet = enabledPagesInternalUsers !== null && enabledPagesInternalUsers !== undefined; + + // Get available pages from leftnav configuration + const availablePages = useMemo(() => getAvailablePages(), []); + + // Group pages by their group for better UI + const pagesByGroup = useMemo(() => { + const grouped: Record = {}; + availablePages.forEach((page) => { + if (!grouped[page.group]) { + grouped[page.group] = []; + } + grouped[page.group].push(page); + }); + return grouped; + }, [availablePages]); + + // Local state for page selection + const [selectedPages, setSelectedPages] = useState(enabledPagesInternalUsers || []); + + // Update local state when data changes + useMemo(() => { + if (enabledPagesInternalUsers) { + setSelectedPages(enabledPagesInternalUsers); + } else { + setSelectedPages([]); + } + }, [enabledPagesInternalUsers]); + + const handleSavePageVisibility = () => { + onUpdate({ enabled_ui_pages_internal_users: selectedPages.length > 0 ? selectedPages : null }); + }; + + const handleResetToDefault = () => { + setSelectedPages([]); + onUpdate({ enabled_ui_pages_internal_users: null }); + }; + + return ( + + + + Internal User Page Visibility + {!isPageVisibilitySet && ( + + Not set (all pages visible) + + )} + {isPageVisibilitySet && ( + + {selectedPages.length} page{selectedPages.length !== 1 ? "s" : ""} selected + + )} + + {enabledPagesPropertyDescription && ( + {enabledPagesPropertyDescription} + )} + + By default, all pages are visible to internal users. Select specific pages to restrict visibility. + + + Note: Only pages accessible to internal user roles are shown here. Admin-only pages are excluded as they + cannot be made visible to internal users regardless of this setting. + + + + + + + {Object.entries(pagesByGroup).map(([groupName, pages]) => ( +
    + + {groupName} + + + {pages.map((page) => ( +
    + + + {page.label} + + {page.description} + + + +
    + ))} +
    +
    + ))} +
    +
    + + + + {isPageVisibilitySet && ( + + )} + +
    + ), + }, + ]} + /> + + ); +} diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx index 1cc493194e9..6cc9cbf4309 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/UISettings/UISettings.tsx @@ -4,7 +4,8 @@ import { useUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUISettings" import { useUpdateUISettings } from "@/app/(dashboard)/hooks/uiSettings/useUpdateUISettings"; import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized"; import NotificationManager from "@/components/molecules/notifications_manager"; -import { Alert, Card, Skeleton, Space, Switch, Typography } from "antd"; +import PageVisibilitySettings from "./PageVisibilitySettings"; +import { Alert, Card, Divider, Skeleton, Space, Switch, Typography } from "antd"; export default function UISettings() { const { accessToken } = useAuthorized(); @@ -14,6 +15,7 @@ export default function UISettings() { const schema = data?.field_schema; const property = schema?.properties?.disable_model_add_for_internal_users; const disableTeamAdminDeleteProperty = schema?.properties?.disable_team_admin_delete_team_user; + const enabledPagesProperty = schema?.properties?.enabled_ui_pages_internal_users; const values = data?.values ?? {}; const isDisabledForInternalUsers = Boolean(values.disable_model_add_for_internal_users); const isDisabledTeamAdminDeleteTeamUser = Boolean(values.disable_team_admin_delete_team_user); @@ -46,6 +48,17 @@ export default function UISettings() { ); }; + const handleUpdatePageVisibility = (settings: { enabled_ui_pages_internal_users: string[] | null }) => { + updateSettings(settings, { + onSuccess: () => { + NotificationManager.success("Page visibility settings updated successfully"); + }, + onError: (error) => { + NotificationManager.fromBackend(error); + }, + }); + }; + return ( {isLoading ? ( @@ -99,6 +112,16 @@ export default function UISettings() { )} + + + + {/* Page Visibility for Internal Users */} + )} diff --git a/ui/litellm-dashboard/src/components/Settings/RouterSettings/Fallbacks/Fallbacks.test.tsx b/ui/litellm-dashboard/src/components/Settings/RouterSettings/Fallbacks/Fallbacks.test.tsx index bc6f6a4856c..8a7119aa4a3 100644 --- a/ui/litellm-dashboard/src/components/Settings/RouterSettings/Fallbacks/Fallbacks.test.tsx +++ b/ui/litellm-dashboard/src/components/Settings/RouterSettings/Fallbacks/Fallbacks.test.tsx @@ -1,7 +1,7 @@ import { render, screen, waitFor } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { beforeEach, describe, expect, it, vi } from "vitest"; -import Fallbacks from "./fallbacks"; +import Fallbacks from "./Fallbacks"; import * as networkingModule from "../../../networking"; import * as fetchModelsModule from "../../../playground/llm_calls/fetch_models"; @@ -108,7 +108,7 @@ describe("Fallbacks", () => { return buttons[buttons.length - 1]; } const clickableElements = lastCell.querySelectorAll("[class*='cursor-pointer'], button"); - return Array.from(clickableElements).find((el) => + return Array.from(clickableElements).find((el) => el.className.includes("red") || el.className.includes("hover:text-red") ) || clickableElements[clickableElements.length - 1]; }; @@ -202,7 +202,7 @@ describe("Fallbacks", () => { await waitFor(() => { expect(networkingModule.setCallbacksCall).toHaveBeenCalled(); - const callArgs = networkingModule.setCallbacksCall.mock.calls[0]; + const callArgs = (networkingModule.setCallbacksCall as any).mock.calls[0]; expect(callArgs[0]).toBe(mockAccessToken); expect(callArgs[1].router_settings.fallbacks).toHaveLength(1); }); diff --git a/ui/litellm-dashboard/src/components/TeamSSOSettings.test.tsx b/ui/litellm-dashboard/src/components/TeamSSOSettings.test.tsx index f5e43fc3d5f..34085df8f10 100644 --- a/ui/litellm-dashboard/src/components/TeamSSOSettings.test.tsx +++ b/ui/litellm-dashboard/src/components/TeamSSOSettings.test.tsx @@ -1,63 +1,653 @@ -import { screen } from "@testing-library/react"; +import React from "react"; +import { screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { renderWithProviders } from "../../tests/test-utils"; import TeamSSOSettings from "./TeamSSOSettings"; import * as networking from "./networking"; +import NotificationsManager from "./molecules/notifications_manager"; -// Mock the networking functions vi.mock("./networking"); -// Mock the budget duration dropdown +vi.mock("@tremor/react", async (importOriginal) => { + const actual = await importOriginal(); + const React = await import("react"); + return { + ...actual, + Card: ({ children }: { children: React.ReactNode }) => React.createElement("div", { "data-testid": "card" }, children), + Title: ({ children }: { children: React.ReactNode }) => React.createElement("h2", {}, children), + Text: ({ children }: { children: React.ReactNode }) => React.createElement("span", {}, children), + Divider: () => React.createElement("hr", {}), + TextInput: ({ value, onChange, placeholder, className }: any) => + React.createElement("input", { + type: "text", + value: value || "", + onChange, + placeholder, + className, + }), + }; +}); + vi.mock("./common_components/budget_duration_dropdown", () => ({ default: ({ value, onChange }: { value: string | null; onChange: (value: string) => void }) => ( - onChange(e.target.value)} + aria-label="Budget duration" + > ), - getBudgetDurationLabel: vi.fn((value: string) => value), + getBudgetDurationLabel: vi.fn((value: string) => `Budget: ${value}`), })); -// Mock the model display name helper vi.mock("./key_team_helpers/fetch_available_models_team_key", () => ({ getModelDisplayName: vi.fn((model: string) => model), })); +vi.mock("./ModelSelect/ModelSelect", () => ({ + ModelSelect: ({ value, onChange }: { value: string[]; onChange: (value: string[]) => void }) => ( + + ), +})); + +vi.mock("antd", async (importOriginal) => { + const actual = await importOriginal(); + const React = await import("react"); + const SelectComponent = ({ + value, + onChange, + mode, + children, + className, + }: { + value: any; + onChange: (value: any) => void; + mode?: string; + children: React.ReactNode; + className?: string; + }) => { + const isMultiple = mode === "multiple"; + const selectValue = isMultiple ? (Array.isArray(value) ? value : []) : value || ""; + return React.createElement( + "select", + { + multiple: isMultiple, + value: selectValue, + onChange: (e: React.ChangeEvent) => { + const selectedValues = Array.from(e.target.selectedOptions, (option) => option.value); + onChange(isMultiple ? selectedValues : selectedValues[0] || undefined); + }, + className, + "aria-label": "Select", + role: "listbox", + }, + children, + ); + }; + SelectComponent.Option = ({ value: optionValue, children: optionChildren }: { value: string; children: React.ReactNode }) => + React.createElement("option", { value: optionValue }, optionChildren); + return { + ...actual, + Spin: ({ size }: { size?: string }) => React.createElement("div", { "data-testid": "spinner", "data-size": size }), + Switch: ({ checked, onChange }: { checked: boolean; onChange: (checked: boolean) => void }) => + React.createElement("input", { + type: "checkbox", + role: "switch", + checked: checked, + onChange: (e) => onChange(e.target.checked), + "aria-label": "Toggle switch", + }), + Select: SelectComponent, + Typography: { + Paragraph: ({ children }: { children: React.ReactNode }) => React.createElement("p", {}, children), + }, + }; +}); + +const mockGetDefaultTeamSettings = vi.mocked(networking.getDefaultTeamSettings); +const mockUpdateDefaultTeamSettings = vi.mocked(networking.updateDefaultTeamSettings); +const mockModelAvailableCall = vi.mocked(networking.modelAvailableCall); +const mockNotificationsManager = vi.mocked(NotificationsManager); + describe("TeamSSOSettings", () => { + const defaultProps = { + accessToken: "test-token", + userID: "test-user", + userRole: "admin", + }; + + const mockSettings = { + values: { + budget_duration: "monthly", + max_budget: 1000, + enabled: true, + allowed_models: ["gpt-4", "claude-3"], + models: ["gpt-4"], + status: "active", + }, + field_schema: { + description: "Default team settings schema", + properties: { + budget_duration: { + type: "string", + description: "Budget duration setting", + }, + max_budget: { + type: "number", + description: "Maximum budget amount", + }, + enabled: { + type: "boolean", + description: "Enable feature", + }, + allowed_models: { + type: "array", + items: { + enum: ["gpt-4", "claude-3", "gpt-3.5-turbo"], + }, + description: "Allowed models", + }, + models: { + type: "array", + description: "Selected models", + }, + status: { + type: "string", + enum: ["active", "inactive", "pending"], + description: "Status", + }, + }, + }, + }; + beforeEach(() => { vi.clearAllMocks(); + mockModelAvailableCall.mockResolvedValue({ + data: [{ id: "gpt-4" }, { id: "claude-3" }], + }); }); - it("renders the component", async () => { - // Mock successful API responses - vi.mocked(networking.getDefaultTeamSettings).mockResolvedValue({ + it("should render", async () => { + mockGetDefaultTeamSettings.mockResolvedValue(mockSettings); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByText("Default Team Settings")).toBeInTheDocument(); + }); + }); + + it("should show loading spinner while fetching settings", () => { + mockGetDefaultTeamSettings.mockImplementation(() => new Promise(() => { })); + + renderWithProviders(); + + expect(screen.getByTestId("spinner")).toBeInTheDocument(); + }); + + it("should display message when no settings are available", async () => { + mockGetDefaultTeamSettings.mockResolvedValue(null as any); + + renderWithProviders(); + + await waitFor(() => { + expect( + screen.getByText("No team settings available or you do not have permission to view them."), + ).toBeInTheDocument(); + }); + }); + + it("should not fetch settings when access token is null", async () => { + renderWithProviders(); + + await waitFor(() => { + expect(mockGetDefaultTeamSettings).not.toHaveBeenCalled(); + }); + }); + + it("should display settings fields with correct values", async () => { + mockGetDefaultTeamSettings.mockResolvedValue(mockSettings); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByText("Budget Duration")).toBeInTheDocument(); + expect(screen.getByText("Max Budget")).toBeInTheDocument(); + }); + + expect(screen.getByText("Budget: monthly")).toBeInTheDocument(); + expect(screen.getByText("1000")).toBeInTheDocument(); + const enabledTexts = screen.getAllByText("Enabled"); + expect(enabledTexts.length).toBeGreaterThan(0); + }); + + it("should display 'Not set' for null values", async () => { + const settingsWithNulls = { + ...mockSettings, values: { - budget_duration: "monthly", - max_budget: 1000, + ...mockSettings.values, + max_budget: null, }, + }; + mockGetDefaultTeamSettings.mockResolvedValue(settingsWithNulls); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByText("Not set")).toBeInTheDocument(); + }); + }); + + it("should toggle edit mode when edit button is clicked", async () => { + mockGetDefaultTeamSettings.mockResolvedValue(mockSettings); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByRole("button", { name: "Edit Settings" })).toBeInTheDocument(); + }); + + const editButton = screen.getByRole("button", { name: "Edit Settings" }); + await userEvent.click(editButton); + + expect(screen.getByRole("button", { name: "Cancel" })).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Save Changes" })).toBeInTheDocument(); + expect(screen.queryByRole("button", { name: "Edit Settings" })).not.toBeInTheDocument(); + }); + + it("should cancel edit mode and reset values", async () => { + mockGetDefaultTeamSettings.mockResolvedValue(mockSettings); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByRole("button", { name: "Edit Settings" })).toBeInTheDocument(); + }); + + const editButton = screen.getByRole("button", { name: "Edit Settings" }); + await userEvent.click(editButton); + + const cancelButton = screen.getByRole("button", { name: "Cancel" }); + await userEvent.click(cancelButton); + + expect(screen.getByRole("button", { name: "Edit Settings" })).toBeInTheDocument(); + expect(screen.queryByRole("button", { name: "Cancel" })).not.toBeInTheDocument(); + }); + + it("should save settings when save button is clicked", async () => { + mockGetDefaultTeamSettings.mockResolvedValue(mockSettings); + mockUpdateDefaultTeamSettings.mockResolvedValue({ + settings: mockSettings.values, + }); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByRole("button", { name: "Edit Settings" })).toBeInTheDocument(); + }); + + const editButton = screen.getByRole("button", { name: "Edit Settings" }); + await userEvent.click(editButton); + + await waitFor(() => { + expect(screen.getByRole("button", { name: "Save Changes" })).toBeInTheDocument(); + }); + + const saveButton = screen.getByRole("button", { name: "Save Changes" }); + await userEvent.click(saveButton); + + await waitFor(() => { + expect(mockUpdateDefaultTeamSettings).toHaveBeenCalledWith("test-token", mockSettings.values); + }); + + expect(mockNotificationsManager.success).toHaveBeenCalledWith("Default team settings updated successfully"); + }); + + it("should show error notification when save fails", async () => { + mockGetDefaultTeamSettings.mockResolvedValue(mockSettings); + mockUpdateDefaultTeamSettings.mockRejectedValue(new Error("Save failed")); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByRole("button", { name: "Edit Settings" })).toBeInTheDocument(); + }); + + const editButton = screen.getByRole("button", { name: "Edit Settings" }); + await userEvent.click(editButton); + + await waitFor(() => { + expect(screen.getByRole("button", { name: "Save Changes" })).toBeInTheDocument(); + }); + + const saveButton = screen.getByRole("button", { name: "Save Changes" }); + await userEvent.click(saveButton); + + await waitFor(() => { + expect(mockNotificationsManager.fromBackend).toHaveBeenCalledWith("Failed to update team settings"); + }); + }); + + it("should render boolean field as switch in edit mode", async () => { + mockGetDefaultTeamSettings.mockResolvedValue(mockSettings); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByRole("button", { name: "Edit Settings" })).toBeInTheDocument(); + }); + + const editButton = screen.getByRole("button", { name: "Edit Settings" }); + await userEvent.click(editButton); + + await waitFor(() => { + const switchElement = screen.getByRole("switch"); + expect(switchElement).toBeInTheDocument(); + expect(switchElement).toBeChecked(); + }); + }); + + it("should update boolean value when switch is toggled", async () => { + mockGetDefaultTeamSettings.mockResolvedValue(mockSettings); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByRole("button", { name: "Edit Settings" })).toBeInTheDocument(); + }); + + const editButton = screen.getByRole("button", { name: "Edit Settings" }); + await userEvent.click(editButton); + + await waitFor(() => { + expect(screen.getByRole("switch")).toBeInTheDocument(); + }); + + const switchElement = screen.getByRole("switch"); + await userEvent.click(switchElement); + + expect(switchElement).not.toBeChecked(); + }); + + it("should render budget duration dropdown in edit mode", async () => { + mockGetDefaultTeamSettings.mockResolvedValue(mockSettings); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByRole("button", { name: "Edit Settings" })).toBeInTheDocument(); + }); + + const editButton = screen.getByRole("button", { name: "Edit Settings" }); + await userEvent.click(editButton); + + await waitFor(() => { + expect(screen.getByLabelText("Budget duration")).toBeInTheDocument(); + }); + }); + + it("should update budget duration when dropdown value changes", async () => { + mockGetDefaultTeamSettings.mockResolvedValue(mockSettings); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByRole("button", { name: "Edit Settings" })).toBeInTheDocument(); + }); + + const editButton = screen.getByRole("button", { name: "Edit Settings" }); + await userEvent.click(editButton); + + await waitFor(() => { + expect(screen.getByLabelText("Budget duration")).toBeInTheDocument(); + }); + + const dropdown = screen.getByLabelText("Budget duration"); + await userEvent.selectOptions(dropdown, "daily"); + + expect(dropdown).toHaveValue("daily"); + }); + + it("should render text input for string fields in edit mode", async () => { + const settingsWithString = { + ...mockSettings, field_schema: { - description: "Default team settings", + ...mockSettings.field_schema, properties: { - budget_duration: { + ...mockSettings.field_schema.properties, + team_name: { type: "string", - description: "Budget duration", - }, - max_budget: { - type: "number", - description: "Maximum budget", + description: "Team name", }, }, }, + values: { + ...mockSettings.values, + team_name: "Test Team", + }, + }; + mockGetDefaultTeamSettings.mockResolvedValue(settingsWithString); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByRole("button", { name: "Edit Settings" })).toBeInTheDocument(); }); - vi.mocked(networking.modelAvailableCall).mockResolvedValue({ - data: [{ id: "gpt-4" }, { id: "claude-3" }], + const editButton = screen.getByRole("button", { name: "Edit Settings" }); + await userEvent.click(editButton); + + await waitFor(() => { + const textInput = screen.getByDisplayValue("Test Team"); + expect(textInput).toBeInTheDocument(); + }); + }); + + it("should render enum select for string enum fields in edit mode", async () => { + mockGetDefaultTeamSettings.mockResolvedValue(mockSettings); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByRole("button", { name: "Edit Settings" })).toBeInTheDocument(); }); - renderWithProviders(); + const editButton = screen.getByRole("button", { name: "Edit Settings" }); + await userEvent.click(editButton); - const container = await screen.findByText("Default Team Settings"); - expect(container).toBeInTheDocument(); + await waitFor(() => { + const statusSelect = screen.getAllByRole("listbox")[0]; + expect(statusSelect).toBeInTheDocument(); + }); + }); + + it("should render multi-select for array enum fields in edit mode", async () => { + mockGetDefaultTeamSettings.mockResolvedValue(mockSettings); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByRole("button", { name: "Edit Settings" })).toBeInTheDocument(); + }); + + const editButton = screen.getByRole("button", { name: "Edit Settings" }); + await userEvent.click(editButton); + + await waitFor(() => { + const multiSelects = screen.getAllByRole("listbox"); + expect(multiSelects.length).toBeGreaterThan(0); + }); + }); + + it("should render ModelSelect for models field in edit mode", async () => { + mockGetDefaultTeamSettings.mockResolvedValue(mockSettings); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByRole("button", { name: "Edit Settings" })).toBeInTheDocument(); + }); + + const editButton = screen.getByRole("button", { name: "Edit Settings" }); + await userEvent.click(editButton); + + await waitFor(() => { + expect(screen.getByTestId("model-select")).toBeInTheDocument(); + }); + }); + + it("should display models as badges in view mode", async () => { + mockGetDefaultTeamSettings.mockResolvedValue(mockSettings); + + renderWithProviders(); + + await waitFor(() => { + const gpt4Elements = screen.getAllByText("gpt-4"); + expect(gpt4Elements.length).toBeGreaterThan(0); + }); + }); + + it("should display 'None' for empty arrays in view mode", async () => { + const settingsWithEmptyArray = { + ...mockSettings, + values: { + ...mockSettings.values, + models: [], + }, + }; + mockGetDefaultTeamSettings.mockResolvedValue(settingsWithEmptyArray); + + renderWithProviders(); + + await waitFor(() => { + const noneTexts = screen.getAllByText("None"); + expect(noneTexts.length).toBeGreaterThan(0); + }); + }); + + it("should display schema description when available", async () => { + mockGetDefaultTeamSettings.mockResolvedValue(mockSettings); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByText("Default team settings schema")).toBeInTheDocument(); + }); + }); + + it("should show error notification when fetching settings fails", async () => { + mockGetDefaultTeamSettings.mockRejectedValue(new Error("Fetch failed")); + + renderWithProviders(); + + await waitFor(() => { + expect(mockNotificationsManager.fromBackend).toHaveBeenCalledWith("Failed to fetch team settings"); + }); + }); + + it("should handle model fetch error gracefully", async () => { + mockGetDefaultTeamSettings.mockResolvedValue(mockSettings); + mockModelAvailableCall.mockRejectedValue(new Error("Model fetch failed")); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByText("Default Team Settings")).toBeInTheDocument(); + }); + }); + + it("should disable cancel button while saving", async () => { + mockGetDefaultTeamSettings.mockResolvedValue(mockSettings); + mockUpdateDefaultTeamSettings.mockImplementation( + () => new Promise((resolve) => setTimeout(() => resolve({ settings: mockSettings.values }), 100)), + ); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByRole("button", { name: "Edit Settings" })).toBeInTheDocument(); + }); + + const editButton = screen.getByRole("button", { name: "Edit Settings" }); + await userEvent.click(editButton); + + await waitFor(() => { + expect(screen.getByRole("button", { name: "Save Changes" })).toBeInTheDocument(); + }); + + const saveButton = screen.getByRole("button", { name: "Save Changes" }); + await userEvent.click(saveButton); + + const cancelButton = screen.getByRole("button", { name: "Cancel" }); + expect(cancelButton).toBeDisabled(); + }); + + it("should display field descriptions", async () => { + mockGetDefaultTeamSettings.mockResolvedValue(mockSettings); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByText("Budget duration setting")).toBeInTheDocument(); + expect(screen.getByText("Maximum budget amount")).toBeInTheDocument(); + }); + }); + + it("should format field names by replacing underscores and capitalizing", async () => { + const settingsWithUnderscores = { + ...mockSettings, + field_schema: { + ...mockSettings.field_schema, + properties: { + ...mockSettings.field_schema.properties, + max_budget_per_user: { + type: "number", + description: "Max budget per user", + }, + }, + }, + values: { + ...mockSettings.values, + max_budget_per_user: 500, + }, + }; + mockGetDefaultTeamSettings.mockResolvedValue(settingsWithUnderscores); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByText("Max Budget Per User")).toBeInTheDocument(); + }); + }); + + it("should display 'No schema information available' when schema is missing", async () => { + const settingsWithoutSchema = { + values: {}, + field_schema: null, + }; + mockGetDefaultTeamSettings.mockResolvedValue(settingsWithoutSchema); + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByText("No schema information available")).toBeInTheDocument(); + }); }); }); diff --git a/ui/litellm-dashboard/src/components/TeamSSOSettings.tsx b/ui/litellm-dashboard/src/components/TeamSSOSettings.tsx index 8537b108cdc..33bfc783afd 100644 --- a/ui/litellm-dashboard/src/components/TeamSSOSettings.tsx +++ b/ui/litellm-dashboard/src/components/TeamSSOSettings.tsx @@ -5,6 +5,7 @@ import { getDefaultTeamSettings, updateDefaultTeamSettings, modelAvailableCall } import BudgetDurationDropdown, { getBudgetDurationLabel } from "./common_components/budget_duration_dropdown"; import { getModelDisplayName } from "./key_team_helpers/fetch_available_models_team_key"; import NotificationsManager from "./molecules/notifications_manager"; +import { ModelSelect } from "./ModelSelect/ModelSelect"; interface TeamSSOSettingsProps { accessToken: string | null; @@ -116,22 +117,15 @@ const TeamSSOSettings: React.FC = ({ accessToken, userID, ); } else if (key === "models") { return ( - + context="global" + style={{ width: "100%" }} + options={{ + includeSpecialOptions: true, + }} + /> ); } else if (type === "string" && property.enum) { return ( diff --git a/ui/litellm-dashboard/src/components/UsagePage/components/KeyModelUsageView.test.tsx b/ui/litellm-dashboard/src/components/UsagePage/components/KeyModelUsageView.test.tsx new file mode 100644 index 00000000000..61968294e18 --- /dev/null +++ b/ui/litellm-dashboard/src/components/UsagePage/components/KeyModelUsageView.test.tsx @@ -0,0 +1,298 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it } from "vitest"; +import KeyModelUsageView from "./KeyModelUsageView"; +import { TopModelData } from "../types"; + +describe("KeyModelUsageView", () => { + const mockTopModels: TopModelData[] = [ + { + model: "gpt-4", + spend: 150.5, + requests: 105, + successful_requests: 100, + failed_requests: 5, + tokens: 50000, + }, + { + model: "gpt-3.5-turbo", + spend: 75.25, + requests: 200, + successful_requests: 195, + failed_requests: 5, + tokens: 100000, + }, + ]; + + it("should render", () => { + render(); + expect(screen.getByText("Model Usage")).toBeInTheDocument(); + }); + + it("should return null when topModels is empty", () => { + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it("should display Model Usage title", () => { + render(); + expect(screen.getByText("Model Usage")).toBeInTheDocument(); + }); + + it("should display Table view button", () => { + render(); + expect(screen.getByRole("button", { name: "Table" })).toBeInTheDocument(); + }); + + it("should display Chart view button", () => { + render(); + expect(screen.getByRole("button", { name: "Chart" })).toBeInTheDocument(); + }); + + it("should default to table view", () => { + render(); + const tableButton = screen.getByRole("button", { name: "Table" }); + expect(tableButton).toHaveClass("bg-blue-100"); + }); + + it("should display all table column headers", () => { + render(); + expect(screen.getByText("Model")).toBeInTheDocument(); + expect(screen.getByText("Spend (USD)")).toBeInTheDocument(); + expect(screen.getByText("Successful")).toBeInTheDocument(); + expect(screen.getByText("Failed")).toBeInTheDocument(); + expect(screen.getByText("Tokens")).toBeInTheDocument(); + }); + + it("should display model data in table view", () => { + render(); + expect(screen.getByText("gpt-4")).toBeInTheDocument(); + expect(screen.getByText("gpt-3.5-turbo")).toBeInTheDocument(); + expect(screen.getByText("$150.50")).toBeInTheDocument(); + expect(screen.getByText("$75.25")).toBeInTheDocument(); + }); + + it("should format spend values with two decimal places", () => { + const modelsWithDecimalSpend: TopModelData[] = [ + { + model: "test-model", + spend: 123.456, + requests: 10, + successful_requests: 10, + failed_requests: 0, + tokens: 1000, + }, + ]; + render(); + expect(screen.getByText("$123.46")).toBeInTheDocument(); + }); + + it("should format large spend values with commas", () => { + const modelsWithLargeSpend: TopModelData[] = [ + { + model: "test-model", + spend: 1234567.89, + requests: 10, + successful_requests: 10, + failed_requests: 0, + tokens: 1000, + }, + ]; + render(); + expect(screen.getByText("$1,234,567.89")).toBeInTheDocument(); + }); + + it("should display successful requests with green styling", () => { + render(); + const successfulElements = screen.getAllByText("100"); + const greenElement = successfulElements.find((el) => el.closest("span")?.classList.contains("text-green-600")); + expect(greenElement).toBeDefined(); + }); + + it("should display failed requests with red styling", () => { + render(); + const failedElements = screen.getAllByText("5"); + const redElement = failedElements.find((el) => el.closest("span")?.classList.contains("text-red-600")); + expect(redElement).toBeDefined(); + }); + + it("should format token numbers with commas", () => { + const modelsWithLargeTokens: TopModelData[] = [ + { + model: "test-model", + spend: 100, + requests: 10, + successful_requests: 10, + failed_requests: 0, + tokens: 1234567, + }, + ]; + render(); + expect(screen.getByText("1,234,567")).toBeInTheDocument(); + }); + + it("should display dash for missing model value", () => { + const modelsWithMissingModel: TopModelData[] = [ + { + model: "", + spend: 100, + requests: 10, + successful_requests: 10, + failed_requests: 0, + tokens: 1000, + }, + ]; + render(); + expect(screen.getByText("-")).toBeInTheDocument(); + }); + + it("should display zero values correctly", () => { + const modelsWithZeros: TopModelData[] = [ + { + model: "test-model", + spend: 0, + requests: 0, + successful_requests: 0, + failed_requests: 0, + tokens: 0, + }, + ]; + render(); + expect(screen.getByText("$0.00")).toBeInTheDocument(); + expect(screen.getAllByText("0").length).toBeGreaterThan(0); + }); + + it("should switch to chart view when chart button is clicked", async () => { + const user = userEvent.setup(); + render(); + + const chartButton = screen.getByRole("button", { name: "Chart" }); + await user.click(chartButton); + + expect(chartButton).toHaveClass("bg-blue-100"); + const tableButton = screen.getByRole("button", { name: "Table" }); + expect(tableButton).not.toHaveClass("bg-blue-100"); + }); + + it("should switch back to table view when table button is clicked", async () => { + const user = userEvent.setup(); + render(); + + const chartButton = screen.getByRole("button", { name: "Chart" }); + const tableButton = screen.getByRole("button", { name: "Table" }); + + await user.click(chartButton); + await user.click(tableButton); + + expect(tableButton).toHaveClass("bg-blue-100"); + expect(chartButton).not.toHaveClass("bg-blue-100"); + }); + + it("should display chart when chart view is selected", async () => { + const user = userEvent.setup(); + render(); + + const chartButton = screen.getByRole("button", { name: "Chart" }); + await user.click(chartButton); + + expect(screen.queryByText("Model")).not.toBeInTheDocument(); + }); + + it("should display table when table view is selected", () => { + render(); + expect(screen.getByText("Model")).toBeInTheDocument(); + expect(screen.getByText("gpt-4")).toBeInTheDocument(); + }); + + it("should handle multiple model entries", () => { + const manyModels: TopModelData[] = Array.from({ length: 10 }, (_, i) => ({ + model: `model-${i + 1}`, + spend: 100 + i, + requests: 50 + i, + successful_requests: 45 + i, + failed_requests: 5, + tokens: 10000 + i * 1000, + })); + + render(); + expect(screen.getByText("model-1")).toBeInTheDocument(); + expect(screen.getByText("model-10")).toBeInTheDocument(); + }); + + it("should format successful requests with toLocaleString", () => { + const modelsWithLargeNumbers: TopModelData[] = [ + { + model: "test-model", + spend: 100, + requests: 1000, + successful_requests: 999999, + failed_requests: 1, + tokens: 1000, + }, + ]; + render(); + expect(screen.getByText("999,999")).toBeInTheDocument(); + }); + + it("should format failed requests with toLocaleString", () => { + const modelsWithLargeNumbers: TopModelData[] = [ + { + model: "test-model", + spend: 100, + requests: 1000, + successful_requests: 1, + failed_requests: 999999, + tokens: 1000, + }, + ]; + render(); + expect(screen.getByText("999,999")).toBeInTheDocument(); + }); + + it("should display zero for missing successful_requests", () => { + const modelsWithMissingFields: TopModelData[] = [ + { + model: "test-model", + spend: 100, + requests: 10, + successful_requests: undefined as any, + failed_requests: 0, + tokens: 1000, + }, + ]; + render(); + const zeroElements = screen.getAllByText("0"); + const successfulZero = zeroElements.find((el) => el.closest("span")?.classList.contains("text-green-600")); + expect(successfulZero).toBeDefined(); + }); + + it("should display zero for missing failed_requests", () => { + const modelsWithMissingFields: TopModelData[] = [ + { + model: "test-model", + spend: 100, + requests: 10, + successful_requests: 10, + failed_requests: undefined as any, + tokens: 1000, + }, + ]; + render(); + expect(screen.getAllByText("0").length).toBeGreaterThan(0); + }); + + it("should display zero for missing tokens", () => { + const modelsWithMissingFields: TopModelData[] = [ + { + model: "test-model", + spend: 100, + requests: 10, + successful_requests: 10, + failed_requests: 0, + tokens: undefined as any, + }, + ]; + render(); + expect(screen.getAllByText("0").length).toBeGreaterThan(0); + }); +}); diff --git a/ui/litellm-dashboard/src/components/UsagePage/components/KeyModelUsageView.tsx b/ui/litellm-dashboard/src/components/UsagePage/components/KeyModelUsageView.tsx new file mode 100644 index 00000000000..a4292de4802 --- /dev/null +++ b/ui/litellm-dashboard/src/components/UsagePage/components/KeyModelUsageView.tsx @@ -0,0 +1,108 @@ +import { formatNumberWithCommas } from "@/utils/dataUtils"; +import { BarChart, Card, Title } from "@tremor/react"; +import { Table } from "antd"; +import type { ColumnsType } from "antd/es/table"; +import React, { useState } from "react"; +import { TopModelData } from "../types"; + +interface KeyModelUsageViewProps { + topModels: TopModelData[]; +} + +const VISIBLE_ROWS = 5; +// antd Table with size="small" has a row height of ~39px +const ANTD_SMALL_TABLE_ROW_HEIGHT = 39; + +const columns: ColumnsType = [ + { + title: "Model", + dataIndex: "model", + key: "model", + render: (value) => value || "-", + }, + { + title: "Spend (USD)", + dataIndex: "spend", + key: "spend", + render: (value) => `$${formatNumberWithCommas(value, 2)}`, + }, + { + title: "Successful", + dataIndex: "successful_requests", + key: "successful_requests", + render: (value) => {value?.toLocaleString() || 0}, + }, + { + title: "Failed", + dataIndex: "failed_requests", + key: "failed_requests", + render: (value) => {value?.toLocaleString() || 0}, + }, + { + title: "Tokens", + dataIndex: "tokens", + key: "tokens", + render: (value) => value?.toLocaleString() || 0, + }, +]; + +const KeyModelUsageView: React.FC = ({ topModels }) => { + const [viewMode, setViewMode] = useState<"chart" | "table">("table"); + + if (topModels.length === 0) { + return null; + } + + return ( + +
    + Model Usage +
    + + +
    +
    + {viewMode === "chart" ? ( +
    + ({ key: m.model, spend: m.spend }))} + index="key" + categories={["spend"]} + colors={["cyan"]} + valueFormatter={(value) => `$${formatNumberWithCommas(value, 2)}`} + layout="vertical" + yAxisWidth={180} + tickGap={5} + showLegend={false} + /> +
    + ) : ( + VISIBLE_ROWS + ? { y: VISIBLE_ROWS * ANTD_SMALL_TABLE_ROW_HEIGHT } + : undefined + } + /> + )} + + ); +}; + +export default KeyModelUsageView; diff --git a/ui/litellm-dashboard/src/components/UsagePage/types.ts b/ui/litellm-dashboard/src/components/UsagePage/types.ts index 2539dfaec16..c70a6a0ed92 100644 --- a/ui/litellm-dashboard/src/components/UsagePage/types.ts +++ b/ui/litellm-dashboard/src/components/UsagePage/types.ts @@ -52,6 +52,15 @@ export interface TopApiKeyData { tokens: number; } +export interface TopModelData { + model: string; + spend: number; + requests: number; + successful_requests: number; + failed_requests: number; + tokens: number; +} + export interface ModelActivityData { label: string; total_requests: number; @@ -64,6 +73,7 @@ export interface ModelActivityData { completion_tokens: number; total_spend: number; top_api_keys: TopApiKeyData[]; + top_models: TopModelData[]; daily_data: { date: string; metrics: { diff --git a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx index deb532d33b3..ef5bb2a0371 100644 --- a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx +++ b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.test.tsx @@ -97,6 +97,7 @@ const mockKey: KeyResponse = { litellm_budget_table: {}, organization_id: "org-1", created_at: "2024-11-01T10:00:00Z", + created_by: "user-1", updated_at: "2024-11-15T10:00:00Z", team_spend: 5.5, team_alias: "Test Team", @@ -177,6 +178,7 @@ beforeEach(() => { total_pages: 1, } as KeysResponse, isPending: false, + isFetching: false, refetch: vi.fn(), } as any); @@ -265,6 +267,7 @@ it("should show skeleton loaders when isLoading is true", () => { mockUseKeys.mockReturnValue({ data: null, isPending: true, + isFetching: true, refetch: vi.fn(), } as any); @@ -439,8 +442,8 @@ it("should open KeyInfoView when clicking on a key ID button", async () => { // Verify table is visible before clicking - check for table-specific text expect(screen.getByText(/Showing.*results/)).toBeInTheDocument(); - // Find the key ID button (it should show the truncated token) - const keyIdButton = screen.getByText("sk-1234..."); + // Find the key ID button (it shows the full token value, truncation is CSS-only) + const keyIdButton = screen.getByText("sk-1234567890abcdef"); expect(keyIdButton).toBeInTheDocument(); // Click on the key ID button @@ -457,3 +460,85 @@ it("should open KeyInfoView when clicking on a key ID button", async () => { // The "Showing X of Y results" text should not be visible when KeyInfoView is open expect(screen.queryByText(/Showing.*results/)).not.toBeInTheDocument(); }); + +it("should display 'Default Proxy Admin' for user_id when value is 'default_user_id'", async () => { + const keyWithDefaultUserId = { + ...mockKey, + user_id: "default_user_id", + }; + + mockUseFilterLogic.mockReturnValue({ + filters: { + "Team ID": "", + "Organization ID": "", + "Key Alias": "", + "User ID": "", + "Sort By": "created_at", + "Sort Order": "desc", + }, + filteredKeys: [keyWithDefaultUserId], + allKeyAliases: ["test-key-alias"], + allTeams: [mockTeam], + allOrganizations: [mockOrganization], + handleFilterChange: vi.fn(), + handleFilterReset: vi.fn(), + }); + + const mockProps = { + teams: [mockTeam], + organizations: [mockOrganization], + onSortChange: vi.fn(), + currentSort: { + sortBy: "created_at", + sortOrder: "desc" as const, + }, + }; + + renderWithProviders(); + + await waitFor(() => { + expect(screen.getByText("Default Proxy Admin")).toBeInTheDocument(); + }); +}); + +it("should display 'Default Proxy Admin' for created_by when value is 'default_user_id'", async () => { + const keyWithDefaultCreatedBy = { + ...mockKey, + created_by: "default_user_id", + }; + + mockUseFilterLogic.mockReturnValue({ + filters: { + "Team ID": "", + "Organization ID": "", + "Key Alias": "", + "User ID": "", + "Sort By": "created_at", + "Sort Order": "desc", + }, + filteredKeys: [keyWithDefaultCreatedBy], + allKeyAliases: ["test-key-alias"], + allTeams: [mockTeam], + allOrganizations: [mockOrganization], + handleFilterChange: vi.fn(), + handleFilterReset: vi.fn(), + }); + + const mockProps = { + teams: [mockTeam], + organizations: [mockOrganization], + onSortChange: vi.fn(), + currentSort: { + sortBy: "created_at", + sortOrder: "desc" as const, + }, + }; + + renderWithProviders(); + + await waitFor(() => { + // The created_by column should display "Default Proxy Admin" + const defaultProxyAdminElements = screen.getAllByText("Default Proxy Admin"); + expect(defaultProxyAdminElements.length).toBeGreaterThan(0); + }); +}); diff --git a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx index 6d968047920..465b9b8fbe0 100644 --- a/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx +++ b/ui/litellm-dashboard/src/components/VirtualKeysPage/VirtualKeysTable.tsx @@ -129,22 +129,25 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo id: "token", accessorKey: "token", header: "Key ID", - size: 150, + size: 100, enableSorting: true, - cell: (info) => ( -
    - + cell: (info) => { + const value = info.getValue() as string; + const width = info.cell.column.getSize(); + return ( + -
    - ), + ); + }, }, { id: "key_alias", @@ -188,13 +191,19 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo id: "team_id", accessorKey: "team_id", header: "Team ID", - size: 120, + size: 80, enableSorting: false, - cell: (info) => ( - - {info.getValue() ? `${(info.getValue() as string).slice(0, 7)}...` : "-"} - - ), + cell: (info) => { + const value = info.getValue() as string | null; + const width = info.cell.column.getSize(); + return ( + + + {value ?? "-"} + + + ); + }, }, { id: "organization_id", @@ -227,18 +236,19 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo id: "user_id", accessorKey: "user_id", header: "User ID", - size: 120, + size: 70, enableSorting: false, cell: (info) => { const userId = info.getValue() as string | null; - if (userId && userId.length > 15) { - return ( - - {userId.slice(0, 7)}... - - ); - } - return userId ? userId : "-"; + const displayValue = userId === "default_user_id" ? "Default Proxy Admin" : userId; + const width = info.cell.column.getSize(); + return ( + + + {displayValue ?? "-"} + + + ); }, }, { @@ -256,18 +266,19 @@ export function VirtualKeysTable({ teams, organizations, onSortChange, currentSo id: "created_by", accessorKey: "created_by", header: "Created By", - size: 120, + size: 70, enableSorting: false, cell: (info) => { const value = info.getValue() as string | null; - if (value && value.length > 15) { - return ( - - {value.slice(0, 7)}... - - ); - } - return value; + const displayValue = value === "default_user_id" ? "Default Proxy Admin" : value; + const width = info.cell.column.getSize(); + return ( + + + {displayValue ?? "-"} + + + ); }, }, { diff --git a/ui/litellm-dashboard/src/components/activity_metrics.tsx b/ui/litellm-dashboard/src/components/activity_metrics.tsx index be12ae2a888..61006fa6147 100644 --- a/ui/litellm-dashboard/src/components/activity_metrics.tsx +++ b/ui/litellm-dashboard/src/components/activity_metrics.tsx @@ -5,7 +5,8 @@ import { Collapse } from "antd"; import React from "react"; import { CustomLegend, CustomTooltip } from "./common_components/chartUtils"; import { Team } from "./key_team_helpers/key_list"; -import { DailyData, KeyMetricWithMetadata, ModelActivityData, TopApiKeyData } from "./UsagePage/types"; +import KeyModelUsageView from "./UsagePage/components/KeyModelUsageView"; +import { DailyData, KeyMetricWithMetadata, ModelActivityData, TopApiKeyData, TopModelData } from "./UsagePage/types"; import { valueFormatter } from "./UsagePage/utils/value_formatters"; interface ActivityMetricsProps { @@ -72,8 +73,29 @@ const ModelSection = ({ )} + {metrics.top_models && metrics.top_models.length > 0 && ( + + )} + + {/* Spend per day - Full width card */} + +
    + Spend per day + +
    + `$${formatNumberWithCommas(value, 2, true)}`} + yAxisWidth={72} + /> +
    + {/* Charts */} - +
    Total Tokens @@ -111,22 +133,6 @@ const ModelSection = ({ /> - -
    - Spend per day - -
    - `$${formatNumberWithCommas(value, 2, true)}`} - yAxisWidth={72} - /> -
    -
    Success vs Failed Requests @@ -377,6 +383,7 @@ export const processActivityData = ( total_cache_read_input_tokens: 0, total_cache_creation_input_tokens: 0, top_api_keys: [], + top_models: [], daily_data: [], }; } @@ -444,6 +451,45 @@ export const processActivityData = ( }); } + // Process Model breakdowns for each API key (only when key is 'api_keys') + if (key === "api_keys") { + Object.entries(modelMetrics).forEach(([apiKeyHash, _]) => { + const modelBreakdown: Record = {}; + + // Aggregate Model data for this key across all days + // We need to look in breakdown.models[model].api_key_breakdown[apiKeyHash] + dailyActivity.results.forEach((day) => { + Object.entries(day.breakdown.models || {}).forEach(([modelName, modelData]) => { + if (modelData && "api_key_breakdown" in modelData) { + const keyDataForModel = modelData.api_key_breakdown?.[apiKeyHash]; + if (keyDataForModel) { + if (!modelBreakdown[modelName]) { + modelBreakdown[modelName] = { + model: modelName, + spend: 0, + requests: 0, + successful_requests: 0, + failed_requests: 0, + tokens: 0, + }; + } + + modelBreakdown[modelName].spend += keyDataForModel.metrics.spend; + modelBreakdown[modelName].requests += keyDataForModel.metrics.api_requests; + modelBreakdown[modelName].successful_requests += keyDataForModel.metrics.successful_requests || 0; + modelBreakdown[modelName].failed_requests += keyDataForModel.metrics.failed_requests || 0; + modelBreakdown[modelName].tokens += keyDataForModel.metrics.total_tokens; + } + } + }); + }); + + // Sort by spend + modelMetrics[apiKeyHash].top_models = Object.values(modelBreakdown) + .sort((a, b) => b.spend - a.spend); + }); + } + // Sort daily data Object.values(modelMetrics).forEach((metrics) => { metrics.daily_data.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); diff --git a/ui/litellm-dashboard/src/components/common_components/DeleteResourceModal.test.tsx b/ui/litellm-dashboard/src/components/common_components/DeleteResourceModal.test.tsx index e9c4500205b..a850fa8d2df 100644 --- a/ui/litellm-dashboard/src/components/common_components/DeleteResourceModal.test.tsx +++ b/ui/litellm-dashboard/src/components/common_components/DeleteResourceModal.test.tsx @@ -1,68 +1,193 @@ -import { describe, it, expect, vi } from "vitest"; +import { describe, it, expect, vi, beforeEach } from "vitest"; import userEvent from "@testing-library/user-event"; -import { renderWithProviders } from "../../../tests/test-utils"; +import { renderWithProviders, screen } from "../../../tests/test-utils"; import DeleteResourceModal from "./DeleteResourceModal"; describe("DeleteResourceModal", () => { + const mockOnCancel = vi.fn(); + const mockOnOk = vi.fn(); + const defaultProps = { isOpen: true, title: "Delete Resource", message: "Are you sure you want to delete this resource?", - onCancel: vi.fn(), - onOk: vi.fn(), + onCancel: mockOnCancel, + onOk: mockOnOk, confirmLoading: false, }; - it("renders", () => { - const { getByText } = renderWithProviders(); - expect(getByText("Delete Resource")).toBeInTheDocument(); + beforeEach(() => { + vi.clearAllMocks(); }); - it("renders the title correctly", () => { - const { getByText } = renderWithProviders(); - expect(getByText("Custom Delete Title")).toBeInTheDocument(); + it("should render", () => { + renderWithProviders(); + expect(screen.getByText("Delete Resource")).toBeInTheDocument(); }); - it("renders the message correctly", () => { - const { getByText } = renderWithProviders( - , - ); - expect(getByText("This is a custom message")).toBeInTheDocument(); + it("should render the title correctly", () => { + renderWithProviders(); + expect(screen.getByText("Custom Delete Title")).toBeInTheDocument(); }); - it("renders the resourceInformation and resourceInformationTitle correctly", () => { + it("should render the message correctly", () => { + renderWithProviders(); + expect(screen.getByText("This is a custom message")).toBeInTheDocument(); + }); + + it("should render alert message when provided", () => { + renderWithProviders(); + expect(screen.getByText("Warning: This action cannot be undone")).toBeInTheDocument(); + }); + + it("should not render alert message when not provided", () => { + renderWithProviders(); + expect(screen.queryByRole("alert")).not.toBeInTheDocument(); + }); + + it("should render resourceInformation and resourceInformationTitle correctly", () => { const resourceInformation = [ { label: "Name", value: "Test Resource" }, { label: "ID", value: "123" }, ]; - const { getByText } = renderWithProviders( + renderWithProviders( , ); - expect(getByText("Resource Details")).toBeInTheDocument(); - expect(getByText("Name")).toBeInTheDocument(); - expect(getByText("Test Resource")).toBeInTheDocument(); - expect(getByText("ID")).toBeInTheDocument(); - expect(getByText("123")).toBeInTheDocument(); + expect(screen.getByText("Resource Details")).toBeInTheDocument(); + expect(screen.getByText("Name")).toBeInTheDocument(); + expect(screen.getByText("Test Resource")).toBeInTheDocument(); + expect(screen.getByText("ID")).toBeInTheDocument(); + expect(screen.getByText("123")).toBeInTheDocument(); }); - it("disables the delete button when requiredConfirmation is not in the input (empty state)", async () => { - const { getByRole } = renderWithProviders(); - const deleteButton = getByRole("button", { name: /delete/i }); + it("should render dash for null or undefined resource information values", () => { + const resourceInformation = [ + { label: "Name", value: null }, + { label: "ID", value: undefined }, + { label: "Status", value: "Active" }, + ]; + renderWithProviders( + , + ); + expect(screen.getAllByText("-")).toHaveLength(2); + expect(screen.getByText("Active")).toBeInTheDocument(); + }); + + it("should render resource information with number values", () => { + const resourceInformation = [{ label: "Count", value: 42 }]; + renderWithProviders(); + expect(screen.getByText("42")).toBeInTheDocument(); + }); + + it("should call onCancel when cancel button is clicked", async () => { + const user = userEvent.setup(); + renderWithProviders(); + const cancelButton = screen.getByRole("button", { name: "Cancel" }); + await user.click(cancelButton); + expect(mockOnCancel).toHaveBeenCalledTimes(1); + }); + + it("should call onOk when delete button is clicked", async () => { + const user = userEvent.setup(); + renderWithProviders(); + const deleteButton = screen.getByRole("button", { name: /delete/i }); + await user.click(deleteButton); + expect(mockOnOk).toHaveBeenCalledTimes(1); + }); + + it("should disable delete button when requiredConfirmation is not entered", () => { + renderWithProviders(); + const deleteButton = screen.getByRole("button", { name: /delete/i }); expect(deleteButton).toBeDisabled(); }); - it("enables the delete button when the input equals requiredConfirmation", async () => { + it("should disable delete button when requiredConfirmation input does not match exactly", async () => { const user = userEvent.setup(); - const { getByRole, getByPlaceholderText } = renderWithProviders( - , - ); - const input = getByPlaceholderText("DELETE"); + renderWithProviders(); + const input = screen.getByPlaceholderText("DELETE"); + await user.type(input, "DELET"); + const deleteButton = screen.getByRole("button", { name: /delete/i }); + expect(deleteButton).toBeDisabled(); + }); + + it("should enable delete button when requiredConfirmation input matches exactly", async () => { + const user = userEvent.setup(); + renderWithProviders(); + const input = screen.getByPlaceholderText("DELETE"); await user.type(input, "DELETE"); - const deleteButton = getByRole("button", { name: /delete/i }); + const deleteButton = screen.getByRole("button", { name: /delete/i }); expect(deleteButton).not.toBeDisabled(); }); + + it("should reset requiredConfirmation input when modal opens", async () => { + const user = userEvent.setup(); + const { rerender } = renderWithProviders( + , + ); + const input = screen.getByPlaceholderText("DELETE"); + await user.type(input, "DELETE"); + expect(input).toHaveValue("DELETE"); + + rerender(); + rerender(); + + const newInput = screen.getByPlaceholderText("DELETE"); + expect(newInput).toHaveValue(""); + }); + + it("should display deleting text on delete button when confirmLoading is true", () => { + renderWithProviders(); + expect(screen.getByText("Deleting...")).toBeInTheDocument(); + }); + + it("should display delete text on delete button when confirmLoading is false", () => { + renderWithProviders(); + const deleteButton = screen.getByRole("button", { name: /delete/i }); + expect(deleteButton).toBeInTheDocument(); + expect(screen.queryByText("Deleting...")).not.toBeInTheDocument(); + }); + + it("should disable delete button when confirmLoading is true", () => { + renderWithProviders(); + const deleteButton = screen.getByText("Deleting...").closest("button"); + expect(deleteButton).toBeDisabled(); + }); + + it("should disable cancel button when confirmLoading is true", () => { + renderWithProviders(); + const cancelButton = screen.getByRole("button", { name: "Cancel" }); + expect(cancelButton).toBeDisabled(); + }); + + it("should disable delete button when confirmLoading is true even if requiredConfirmation matches", async () => { + const user = userEvent.setup(); + renderWithProviders( + , + ); + const input = screen.getByPlaceholderText("DELETE"); + await user.type(input, "DELETE"); + const deleteButton = screen.getByText("Deleting...").closest("button"); + expect(deleteButton).toBeDisabled(); + }); + + it("should render required confirmation prompt with correct text", () => { + renderWithProviders(); + expect(screen.getByText(/Type/i)).toBeInTheDocument(); + expect(screen.getByText("DELETE")).toBeInTheDocument(); + expect(screen.getByText(/to confirm deletion/i)).toBeInTheDocument(); + }); + + it("should not render required confirmation section when not provided", () => { + renderWithProviders(); + expect(screen.queryByPlaceholderText("DELETE")).not.toBeInTheDocument(); + }); + + it("should not render modal when isOpen is false", () => { + renderWithProviders(); + expect(screen.queryByText("Delete Resource")).not.toBeInTheDocument(); + }); }); diff --git a/ui/litellm-dashboard/src/components/common_components/DeleteResourceModal.tsx b/ui/litellm-dashboard/src/components/common_components/DeleteResourceModal.tsx index 93de859dd7d..26419585a4c 100644 --- a/ui/litellm-dashboard/src/components/common_components/DeleteResourceModal.tsx +++ b/ui/litellm-dashboard/src/components/common_components/DeleteResourceModal.tsx @@ -1,4 +1,5 @@ -import { Alert, Descriptions, Input, Modal, Typography } from "antd"; +import { Alert, Card, Descriptions, Input, Modal, Typography, theme } from "antd"; +import { ExclamationCircleOutlined } from "@ant-design/icons"; import React, { useState, useEffect } from "react"; interface DeleteResourceModalProps { @@ -32,6 +33,7 @@ export default function DeleteResourceModal({ requiredConfirmation, }: DeleteResourceModalProps) { const { Title, Text } = Typography; + const { token } = theme.useToken(); const [requiredConfirmationInput, setRequiredConfirmationInput] = useState(""); useEffect(() => { @@ -57,25 +59,36 @@ export default function DeleteResourceModal({ >
    {alertMessage && } -
    - - {resourceInformationTitle} - + {resourceInformation && resourceInformation.map(({ label, value, ...textProps }) => ( - {label}}> + {label}}> {value ?? "-"} ))} -
    +
    {message}
    {requiredConfirmation && ( -
    - +
    + Type {requiredConfirmation} @@ -86,7 +99,8 @@ export default function DeleteResourceModal({ value={requiredConfirmationInput} onChange={(e) => setRequiredConfirmationInput(e.target.value)} placeholder={requiredConfirmation} - className="rounded-md text-base border-gray-200" + className="rounded-md" + prefix={} autoFocus />
    diff --git a/ui/litellm-dashboard/src/components/common_components/KeyLifecycleSettings.test.tsx b/ui/litellm-dashboard/src/components/common_components/KeyLifecycleSettings.test.tsx new file mode 100644 index 00000000000..f6fcfb2fb74 --- /dev/null +++ b/ui/litellm-dashboard/src/components/common_components/KeyLifecycleSettings.test.tsx @@ -0,0 +1,383 @@ +import userEvent from "@testing-library/user-event"; +import { describe, expect, it, vi, beforeEach } from "vitest"; +import { renderWithProviders, screen } from "../../../tests/test-utils"; +import KeyLifecycleSettings from "./KeyLifecycleSettings"; + +vi.mock("antd", () => { + const Option = ({ children, value }: any) => ( + + ); + const Select = ({ children, value, onChange, placeholder }: any) => ( + + ); + Select.Option = Option; + return { + Select, + Tooltip: ({ children, title }: any) => ( +
    + {children} +
    + ), + Switch: ({ checked, onChange }: any) => ( + onChange(e.target.checked)} + /> + ), + Divider: () =>
    , + }; +}); + +vi.mock("@ant-design/icons", () => ({ + InfoCircleOutlined: () => ℹ, +})); + +vi.mock("@tremor/react", () => ({ + TextInput: ({ value, onValueChange, onChange, placeholder, name, className }: any) => { + const handleChange = (e: React.ChangeEvent) => { + if (onChange) { + onChange(e); + } + if (onValueChange) { + onValueChange(e.target.value); + } + }; + return ( + + ); + }, +})); + +describe("KeyLifecycleSettings", () => { + const mockForm = { + getFieldValue: vi.fn(), + setFieldValue: vi.fn(), + setFieldsValue: vi.fn(), + }; + + const defaultProps = { + form: mockForm, + autoRotationEnabled: false, + onAutoRotationChange: vi.fn(), + rotationInterval: "", + onRotationIntervalChange: vi.fn(), + isCreateMode: false, + }; + + beforeEach(() => { + vi.clearAllMocks(); + mockForm.getFieldValue.mockReturnValue(""); + }); + + it("should render without crashing", () => { + renderWithProviders(); + + expect(screen.getByText("Key Expiry Settings")).toBeInTheDocument(); + expect(screen.getByText("Auto-Rotation Settings")).toBeInTheDocument(); + }); + + describe("Key Expiry Settings", () => { + it("should render expiry input field", () => { + renderWithProviders(); + + expect(screen.getByText("Expire Key")).toBeInTheDocument(); + expect(screen.getByTestId("duration-input")).toBeInTheDocument(); + }); + + it("should show correct placeholder in create mode", () => { + renderWithProviders(); + + const input = screen.getByTestId("duration-input"); + expect(input).toHaveAttribute( + "placeholder", + "e.g., 30d or leave empty to never expire" + ); + }); + + it("should show correct placeholder in edit mode", () => { + renderWithProviders(); + + const input = screen.getByTestId("duration-input"); + expect(input).toHaveAttribute("placeholder", "e.g., 30d or -1 to never expire"); + }); + + it("should show correct tooltip in create mode", () => { + renderWithProviders(); + + const tooltips = screen.getAllByTestId("tooltip"); + const expiryTooltip = tooltips.find((tooltip) => + tooltip.getAttribute("title")?.includes("Leave empty to never expire") + ); + expect(expiryTooltip).toBeInTheDocument(); + expect(expiryTooltip).toHaveAttribute( + "title", + "Set when this key should expire. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days). Leave empty to never expire." + ); + }); + + it("should show correct tooltip in edit mode", () => { + renderWithProviders(); + + const tooltips = screen.getAllByTestId("tooltip"); + const expiryTooltip = tooltips.find((tooltip) => + tooltip.getAttribute("title")?.includes("Use -1 to never expire") + ); + expect(expiryTooltip).toBeInTheDocument(); + expect(expiryTooltip).toHaveAttribute( + "title", + "Set when this key should expire. Format: 30s (seconds), 30m (minutes), 30h (hours), 30d (days). Use -1 to never expire." + ); + }); + + it("should initialize with form value if present", () => { + mockForm.getFieldValue.mockReturnValue("30d"); + renderWithProviders(); + + const input = screen.getByTestId("duration-input") as HTMLInputElement; + expect(input.value).toBe("30d"); + }); + + it("should update form using setFieldValue when duration changes", async () => { + const user = userEvent.setup(); + renderWithProviders(); + + const input = screen.getByTestId("duration-input"); + await user.type(input, "60d"); + + expect(mockForm.setFieldValue).toHaveBeenCalledWith("duration", "60d"); + }); + + it("should update form using setFieldsValue when setFieldValue is not available", async () => { + const user = userEvent.setup(); + const formWithoutSetFieldValue = { + getFieldValue: vi.fn().mockReturnValue(""), + setFieldsValue: vi.fn(), + }; + renderWithProviders( + + ); + + const input = screen.getByTestId("duration-input"); + await user.type(input, "90d"); + + expect(formWithoutSetFieldValue.setFieldsValue).toHaveBeenCalledWith({ duration: "90d" }); + }); + }); + + describe("Auto-Rotation Settings", () => { + it("should render auto-rotation switch", () => { + renderWithProviders(); + + expect(screen.getByText("Enable Auto-Rotation")).toBeInTheDocument(); + expect(screen.getByTestId("switch")).toBeInTheDocument(); + }); + + it("should show switch as unchecked when autoRotationEnabled is false", () => { + renderWithProviders(); + + const switchElement = screen.getByTestId("switch") as HTMLInputElement; + expect(switchElement.checked).toBe(false); + }); + + it("should show switch as checked when autoRotationEnabled is true", () => { + renderWithProviders(); + + const switchElement = screen.getByTestId("switch") as HTMLInputElement; + expect(switchElement.checked).toBe(true); + }); + + it("should call onAutoRotationChange when switch is toggled", async () => { + const user = userEvent.setup(); + const onAutoRotationChange = vi.fn(); + renderWithProviders( + + ); + + const switchElement = screen.getByTestId("switch"); + await user.click(switchElement); + + expect(onAutoRotationChange).toHaveBeenCalledWith(true); + }); + + it("should not show rotation interval section when auto-rotation is disabled", () => { + renderWithProviders(); + + expect(screen.queryByText("Rotation Interval")).not.toBeInTheDocument(); + expect(screen.queryByTestId("select")).not.toBeInTheDocument(); + }); + + it("should show rotation interval section when auto-rotation is enabled", () => { + renderWithProviders( + + ); + + expect(screen.getByText("Rotation Interval")).toBeInTheDocument(); + expect(screen.getByTestId("select")).toBeInTheDocument(); + }); + + it("should show all predefined interval options", () => { + renderWithProviders( + + ); + + expect(screen.getByText("7 days")).toBeInTheDocument(); + expect(screen.getByText("30 days")).toBeInTheDocument(); + expect(screen.getByText("90 days")).toBeInTheDocument(); + expect(screen.getByText("180 days")).toBeInTheDocument(); + expect(screen.getByText("365 days")).toBeInTheDocument(); + expect(screen.getByText("Custom interval")).toBeInTheDocument(); + }); + + it("should display current rotation interval in select", () => { + renderWithProviders( + + ); + + const select = screen.getByTestId("select") as HTMLSelectElement; + expect(select.value).toBe("90d"); + }); + + it("should call onRotationIntervalChange when predefined interval is selected", async () => { + const user = userEvent.setup(); + const onRotationIntervalChange = vi.fn(); + renderWithProviders( + + ); + + const select = screen.getByTestId("select"); + await user.selectOptions(select, "30d"); + + expect(onRotationIntervalChange).toHaveBeenCalledWith("30d"); + }); + + it("should show custom input when custom option is selected", async () => { + const user = userEvent.setup(); + renderWithProviders( + + ); + + const select = screen.getByTestId("select"); + await user.selectOptions(select, "custom"); + + expect(screen.getByTestId("custom-interval-input")).toBeInTheDocument(); + expect(screen.getByText("Supported formats: seconds (s), minutes (m), hours (h), days (d)")).toBeInTheDocument(); + }); + + it("should hide custom input when predefined interval is selected after custom", async () => { + const user = userEvent.setup(); + const onRotationIntervalChange = vi.fn(); + renderWithProviders( + + ); + + const select = screen.getByTestId("select"); + await user.selectOptions(select, "7d"); + + expect(screen.queryByTestId("custom-interval-input")).not.toBeInTheDocument(); + expect(onRotationIntervalChange).toHaveBeenCalledWith("7d"); + }); + + it("should call onRotationIntervalChange when custom interval is entered", async () => { + const user = userEvent.setup(); + const onRotationIntervalChange = vi.fn(); + renderWithProviders( + + ); + + const select = screen.getByTestId("select"); + await user.selectOptions(select, "custom"); + + const customInput = screen.getByTestId("custom-interval-input"); + await user.type(customInput, "14d"); + + expect(onRotationIntervalChange).toHaveBeenCalledWith("14d"); + }); + + it("should show info message when auto-rotation is enabled", () => { + renderWithProviders(); + + expect( + screen.getByText( + "When rotation occurs, you'll receive a notification with the new key. The old key will be deactivated after a brief grace period." + ) + ).toBeInTheDocument(); + }); + + it("should not show info message when auto-rotation is disabled", () => { + renderWithProviders(); + + expect( + screen.queryByText( + "When rotation occurs, you'll receive a notification with the new key. The old key will be deactivated after a brief grace period." + ) + ).not.toBeInTheDocument(); + }); + + it("should initialize with custom interval input visible when custom interval is provided", () => { + renderWithProviders( + + ); + + expect(screen.getByTestId("custom-interval-input")).toBeInTheDocument(); + const customInput = screen.getByTestId("custom-interval-input") as HTMLInputElement; + expect(customInput.value).toBe("14d"); + }); + + it("should show custom option selected when custom interval is provided", () => { + renderWithProviders( + + ); + + const select = screen.getByTestId("select") as HTMLSelectElement; + expect(select.value).toBe("custom"); + }); + + it("should not call onRotationIntervalChange when selecting custom option", async () => { + const user = userEvent.setup(); + const onRotationIntervalChange = vi.fn(); + renderWithProviders( + + ); + + const select = screen.getByTestId("select"); + await user.selectOptions(select, "custom"); + + expect(onRotationIntervalChange).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/ui/litellm-dashboard/src/components/common_components/KeyLifecycleSettings.tsx b/ui/litellm-dashboard/src/components/common_components/KeyLifecycleSettings.tsx index 81d22b56347..0f29a47d1dc 100644 --- a/ui/litellm-dashboard/src/components/common_components/KeyLifecycleSettings.tsx +++ b/ui/litellm-dashboard/src/components/common_components/KeyLifecycleSettings.tsx @@ -11,6 +11,7 @@ interface KeyLifecycleSettingsProps { onAutoRotationChange: (enabled: boolean) => void; rotationInterval: string; onRotationIntervalChange: (interval: string) => void; + isCreateMode?: boolean; // If true, shows "leave empty to never expire" instead of "-1 to never expire" } const KeyLifecycleSettings: React.FC = ({ @@ -19,6 +20,7 @@ const KeyLifecycleSettings: React.FC = ({ onAutoRotationChange, rotationInterval, onRotationIntervalChange, + isCreateMode = false, }) => { // Predefined intervals const predefinedIntervals = ["7d", "30d", "90d", "180d", "365d"]; @@ -64,13 +66,19 @@ const KeyLifecycleSettings: React.FC = ({
    { expect(container.firstChild).toBeNull(); }); + + it("should render badge with dot when dot prop is true", () => { + mockUseDisableShowNewBadge.mockReturnValue(false); + + render(Test Content); + + expect(screen.queryByText("New")).not.toBeInTheDocument(); + expect(screen.getByText("Test Content")).toBeInTheDocument(); + }); + + it("should render badge with 'New' text when dot prop is false", () => { + mockUseDisableShowNewBadge.mockReturnValue(false); + + render(Test Content); + + expect(screen.getByText("New")).toBeInTheDocument(); + expect(screen.getByText("Test Content")).toBeInTheDocument(); + }); + + it("should render badge with 'New' text when dot prop is not provided (defaults to false)", () => { + mockUseDisableShowNewBadge.mockReturnValue(false); + + render(Test Content); + + expect(screen.getByText("New")).toBeInTheDocument(); + expect(screen.getByText("Test Content")).toBeInTheDocument(); + }); + + it("should render badge with dot when dot is true and no children", () => { + mockUseDisableShowNewBadge.mockReturnValue(false); + + render(); + + expect(screen.queryByText("New")).not.toBeInTheDocument(); + }); }); diff --git a/ui/litellm-dashboard/src/components/common_components/NewBadge.tsx b/ui/litellm-dashboard/src/components/common_components/NewBadge.tsx index 97cdea8cfbb..c9223a3c380 100644 --- a/ui/litellm-dashboard/src/components/common_components/NewBadge.tsx +++ b/ui/litellm-dashboard/src/components/common_components/NewBadge.tsx @@ -1,7 +1,13 @@ import { Badge } from "antd"; import { useDisableShowNewBadge } from "@/app/(dashboard)/hooks/useDisableShowNewBadge"; -export default function NewBadge({ children }: { children?: React.ReactNode }) { +export default function NewBadge({ + children, + dot = false +}: { + children?: React.ReactNode; + dot?: boolean; +}) { const disableShowNewBadge = useDisableShowNewBadge(); if (disableShowNewBadge) { @@ -9,10 +15,10 @@ export default function NewBadge({ children }: { children?: React.ReactNode }) { } return children ? ( - + {children} ) : ( - + ); } diff --git a/ui/litellm-dashboard/src/components/common_components/RouterSettingsAccordion.tsx b/ui/litellm-dashboard/src/components/common_components/RouterSettingsAccordion.tsx new file mode 100644 index 00000000000..1f691715d30 --- /dev/null +++ b/ui/litellm-dashboard/src/components/common_components/RouterSettingsAccordion.tsx @@ -0,0 +1,373 @@ +import React, { useEffect, useState, useImperativeHandle, forwardRef, useRef } from "react"; +import { TabPanel, TabPanels, TabGroup, TabList, Tab } from "@tremor/react"; +import { getRouterSettingsCall } from "../networking"; +import RouterSettingsForm, { RouterSettingsFormValue } from "../router_settings/RouterSettingsForm"; +import { Fallbacks } from "../Settings/RouterSettings/Fallbacks/AddFallbacks"; +import { FallbackSelectionForm } from "../Settings/RouterSettings/Fallbacks/FallbackSelectionForm"; +import { FallbackGroup } from "../Settings/RouterSettings/Fallbacks/FallbackGroupConfig"; +import { fetchAvailableModels, ModelGroup } from "../playground/llm_calls/fetch_models"; + +export interface RouterSettingsAccordionValue { + router_settings: { + routing_strategy?: string | null; + allowed_fails?: number | null; + cooldown_time?: number | null; + num_retries?: number | null; + timeout?: number | null; + retry_after?: number | null; + fallbacks?: Fallbacks | null; + context_window_fallbacks?: any | null; + retry_policy?: any | null; + model_group_alias?: { [key: string]: any } | null; + enable_tag_filtering?: boolean; + routing_strategy_args?: { [key: string]: any } | null; + }; +} + +interface RouterSettingsAccordionProps { + accessToken: string; + value?: RouterSettingsAccordionValue; + onChange?: (value: RouterSettingsAccordionValue) => void; + modelData?: any; +} + +export interface RouterSettingsAccordionRef { + getValue: () => RouterSettingsAccordionValue; +} + +const RouterSettingsAccordion = forwardRef( + ({ accessToken, value, onChange, modelData }, ref) => { + const [formValue, setFormValue] = useState({ + routerSettings: {}, + selectedStrategy: null, + enableTagFiltering: false, + }); + const [fallbacks, setFallbacks] = useState([]); + const [fallbackGroups, setFallbackGroups] = useState([]); + const [modelInfo, setModelInfo] = useState([]); + const [availableRoutingStrategies, setAvailableRoutingStrategies] = useState([]); + const [routerFieldsMetadata, setRouterFieldsMetadata] = useState<{ [key: string]: any }>({}); + const [routingStrategyDescriptions, setRoutingStrategyDescriptions] = useState<{ [key: string]: string }>({}); + const isInternalUpdateRef = useRef(false); + const lastInitializedValueRef = useRef(null); + + // Convert fallbacks format to groups format + const fallbacksToGroups = (fallbacks: Fallbacks): FallbackGroup[] => { + if (!fallbacks || fallbacks.length === 0) { + return [ + { + id: "1", + primaryModel: null, + fallbackModels: [], + }, + ]; + } + return fallbacks.map((entry, index) => { + const [primaryModel, fallbackModels] = Object.entries(entry)[0]; + return { + id: (index + 1).toString(), + primaryModel: primaryModel || null, + fallbackModels: fallbackModels || [], + }; + }); + }; + + // Convert groups format to fallbacks format + const groupsToFallbacks = (groups: FallbackGroup[]): Fallbacks => { + return groups + .filter((g) => g.primaryModel && g.fallbackModels.length > 0) + .map((g) => ({ + [g.primaryModel!]: g.fallbackModels, + })); + }; + + // Initialize from value prop if provided (only when value actually changes externally) + useEffect(() => { + // Create a stable key from the value to detect actual external changes + const valueKey = value?.router_settings + ? JSON.stringify({ + routing_strategy: value.router_settings.routing_strategy, + fallbacks: value.router_settings.fallbacks, + enable_tag_filtering: value.router_settings.enable_tag_filtering, + }) + : null; + + // Skip if this is an internal update (from our own onChange) and the value hasn't actually changed + if (isInternalUpdateRef.current && valueKey === lastInitializedValueRef.current) { + isInternalUpdateRef.current = false; + return; + } + + // Reset the flag if value actually changed externally + if (isInternalUpdateRef.current && valueKey !== lastInitializedValueRef.current) { + isInternalUpdateRef.current = false; + } + + // Only update if the value actually changed from an external source + if (valueKey === lastInitializedValueRef.current) { + return; + } + + lastInitializedValueRef.current = valueKey; + + if (value?.router_settings) { + const rs = value.router_settings; + const { fallbacks: _, ...routerSettingsWithoutFallbacks } = rs; + setFormValue({ + routerSettings: routerSettingsWithoutFallbacks as { [key: string]: any }, + selectedStrategy: rs.routing_strategy || null, + enableTagFiltering: rs.enable_tag_filtering ?? false, + }); + const initialFallbacks = rs.fallbacks || []; + setFallbacks(initialFallbacks); + setFallbackGroups(fallbacksToGroups(initialFallbacks)); + } else { + // Initialize with empty defaults if no value provided + setFormValue({ + routerSettings: {}, + selectedStrategy: null, + enableTagFiltering: false, + }); + setFallbacks([]); + setFallbackGroups([ + { + id: "1", + primaryModel: null, + fallbackModels: [], + }, + ]); + } + }, [value]); + + // Fetch router settings metadata + useEffect(() => { + if (!accessToken) { + return; + } + getRouterSettingsCall(accessToken).then((data) => { + if (data.fields) { + // Build metadata map for easy lookup + const fieldsMap: { [key: string]: any } = {}; + data.fields.forEach((field: any) => { + fieldsMap[field.field_name] = { + ui_field_name: field.ui_field_name, + field_description: field.field_description, + options: field.options, + link: field.link, + }; + }); + setRouterFieldsMetadata(fieldsMap); + + // Extract routing strategies from the routing_strategy field's options + const routingStrategyField = data.fields.find((field: any) => field.field_name === "routing_strategy"); + if (routingStrategyField?.options) { + setAvailableRoutingStrategies(routingStrategyField.options); + } + + // Store routing strategy descriptions + if (data.routing_strategy_descriptions) { + setRoutingStrategyDescriptions(data.routing_strategy_descriptions); + } + } + }); + }, [accessToken]); + + // Fetch available models for fallbacks + useEffect(() => { + if (!accessToken) { + return; + } + const loadModels = async () => { + try { + const uniqueModels = await fetchAvailableModels(accessToken); + setModelInfo(uniqueModels); + } catch (error) { + console.error("Error fetching model info for fallbacks:", error); + } + }; + loadModels(); + }, [accessToken]); + + // Helper function to build router_settings from current state + const buildRouterSettings = (): RouterSettingsAccordionValue["router_settings"] => { + // Parse input values from DOM (similar to RouterSettings component) + const numberKeys = new Set(["allowed_fails", "cooldown_time", "num_retries", "timeout", "retry_after"]); + const jsonKeys = new Set(["model_group_alias", "retry_policy"]); + + const parseInputValue = (key: string, raw: string | undefined, fallback: unknown) => { + if (raw === undefined || raw === null) return fallback; + + const v = String(raw).trim(); + + if (v === "" || v.toLowerCase() === "null") return null; + + if (numberKeys.has(key)) { + const n = Number(v); + return Number.isNaN(n) ? fallback : n; + } + + if (jsonKeys.has(key)) { + if (v === "") return null; + try { + return JSON.parse(v); + } catch { + return fallback; + } + } + + if (v.toLowerCase() === "true") return true; + if (v.toLowerCase() === "false") return false; + + return v; + }; + + // Build router_settings object + const routerSettings = formValue.routerSettings; + const settingsToUpdate: { [key: string]: any } = { + ...routerSettings, + enable_tag_filtering: formValue.enableTagFiltering, + routing_strategy: formValue.selectedStrategy, + fallbacks: fallbacks.length > 0 ? fallbacks : null, + }; + + // Parse values from DOM inputs for reliability/retry fields + const updatedVariables = Object.fromEntries( + Object.entries(settingsToUpdate) + .map(([key, value]) => { + if ( + key !== "routing_strategy_args" && + key !== "routing_strategy" && + key !== "enable_tag_filtering" && + key !== "fallbacks" + ) { + const inputEl = document.querySelector(`input[name="${key}"]`) as HTMLInputElement | null; + if (inputEl && inputEl.value !== undefined && inputEl.value !== "") { + const parsed = parseInputValue(key, inputEl.value, value); + return [key, parsed]; + } + return [key, value]; + } else if (key === "routing_strategy") { + return [key, formValue.selectedStrategy]; + } else if (key === "enable_tag_filtering") { + return [key, formValue.enableTagFiltering]; + } else if (key === "fallbacks") { + return [key, fallbacks.length > 0 ? fallbacks : null]; + } else if (key === "routing_strategy_args" && formValue.selectedStrategy === "latency-based-routing") { + const lowestLatencyBufferElement = document.querySelector( + `input[name="lowest_latency_buffer"]`, + ) as HTMLInputElement; + const ttlElement = document.querySelector(`input[name="ttl"]`) as HTMLInputElement; + + const routingStrategyArgs: { [key: string]: any } = {}; + if (lowestLatencyBufferElement?.value) { + routingStrategyArgs["lowest_latency_buffer"] = Number(lowestLatencyBufferElement.value); + } + if (ttlElement?.value) { + routingStrategyArgs["ttl"] = Number(ttlElement.value); + } + return ["routing_strategy_args", Object.keys(routingStrategyArgs).length > 0 ? routingStrategyArgs : null]; + } + return [key, value]; + }) + .filter((entry) => entry !== null && entry !== undefined) as Iterable<[string, unknown]>, + ); + + // Ensure all required fields are present with null defaults if not set + // Convert empty objects/undefined to null for proper type safety + const normalizeValue = (val: any, isNumber = false): any => { + if (val === undefined || val === null) return null; + if (typeof val === "object" && !Array.isArray(val) && Object.keys(val).length === 0) return null; + if (isNumber && (typeof val !== "number" || Number.isNaN(val))) return null; + return val; + }; + + return { + routing_strategy: normalizeValue(updatedVariables.routing_strategy) as string | null | undefined, + allowed_fails: normalizeValue(updatedVariables.allowed_fails, true) as number | null | undefined, + cooldown_time: normalizeValue(updatedVariables.cooldown_time, true) as number | null | undefined, + num_retries: normalizeValue(updatedVariables.num_retries, true) as number | null | undefined, + timeout: normalizeValue(updatedVariables.timeout, true) as number | null | undefined, + retry_after: normalizeValue(updatedVariables.retry_after, true) as number | null | undefined, + fallbacks: fallbacks.length > 0 ? fallbacks : null, + context_window_fallbacks: normalizeValue(updatedVariables.context_window_fallbacks), + retry_policy: normalizeValue(updatedVariables.retry_policy), + model_group_alias: normalizeValue(updatedVariables.model_group_alias), + enable_tag_filtering: formValue.enableTagFiltering, + routing_strategy_args: normalizeValue(updatedVariables.routing_strategy_args), + }; + }; + + // Update parent when form values change (with debounce to avoid infinite loops) + useEffect(() => { + if (!onChange) { + return; + } + + const timeoutId = setTimeout(() => { + isInternalUpdateRef.current = true; + const finalRouterSettings = buildRouterSettings(); + onChange({ + router_settings: finalRouterSettings, + }); + }, 100); + + return () => clearTimeout(timeoutId); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [formValue, fallbacks]); + + const handleFallbackGroupsChange = (newGroups: FallbackGroup[]): void => { + setFallbackGroups(newGroups); + const newFallbacks = groupsToFallbacks(newGroups); + setFallbacks(newFallbacks); + }; + + const availableModels = Array.from(new Set(modelInfo.map((option) => option.model_group))).sort(); + + // Expose getValue method via ref + useImperativeHandle(ref, () => ({ + getValue: () => { + return { + router_settings: buildRouterSettings(), + }; + }, + })); + + if (!accessToken) { + return null; + } + + return ( +
    + + + Loadbalancing + Fallbacks + + + + + + + + + + +
    + ); + }); + +RouterSettingsAccordion.displayName = "RouterSettingsAccordion"; + +export default RouterSettingsAccordion; diff --git a/ui/litellm-dashboard/src/components/common_components/TableHeaderSortDropdown/TableHeaderSortDropdown.test.tsx b/ui/litellm-dashboard/src/components/common_components/TableHeaderSortDropdown/TableHeaderSortDropdown.test.tsx new file mode 100644 index 00000000000..58395371bbe --- /dev/null +++ b/ui/litellm-dashboard/src/components/common_components/TableHeaderSortDropdown/TableHeaderSortDropdown.test.tsx @@ -0,0 +1,148 @@ +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { describe, expect, it, vi } from "vitest"; +import { TableHeaderSortDropdown } from "./TableHeaderSortDropdown"; + +describe("TableHeaderSortDropdown", () => { + it("should render", () => { + const onSortChange = vi.fn(); + render(); + expect(screen.getByRole("button")).toBeInTheDocument(); + }); + + it("should open dropdown menu when button is clicked", async () => { + const user = userEvent.setup(); + const onSortChange = vi.fn(); + render(); + + const button = screen.getByRole("button"); + await user.click(button); + + await waitFor(() => { + expect(screen.getByText("Ascending")).toBeInTheDocument(); + expect(screen.getByText("Descending")).toBeInTheDocument(); + expect(screen.getByText("Reset")).toBeInTheDocument(); + }); + }); + + it("should call onSortChange with asc when ascending option is clicked", async () => { + const user = userEvent.setup(); + const onSortChange = vi.fn(); + render(); + + const button = screen.getByRole("button"); + await user.click(button); + + await waitFor(() => { + expect(screen.getByText("Ascending")).toBeInTheDocument(); + }); + + const ascendingOption = screen.getByText("Ascending"); + await user.click(ascendingOption); + + expect(onSortChange).toHaveBeenCalledTimes(1); + expect(onSortChange).toHaveBeenCalledWith("asc"); + }); + + it("should call onSortChange with desc when descending option is clicked", async () => { + const user = userEvent.setup(); + const onSortChange = vi.fn(); + render(); + + const button = screen.getByRole("button"); + await user.click(button); + + await waitFor(() => { + expect(screen.getByText("Descending")).toBeInTheDocument(); + }); + + const descendingOption = screen.getByText("Descending"); + await user.click(descendingOption); + + expect(onSortChange).toHaveBeenCalledTimes(1); + expect(onSortChange).toHaveBeenCalledWith("desc"); + }); + + it("should call onSortChange with false when reset option is clicked", async () => { + const user = userEvent.setup(); + const onSortChange = vi.fn(); + render(); + + const button = screen.getByRole("button"); + await user.click(button); + + await waitFor(() => { + expect(screen.getByText("Reset")).toBeInTheDocument(); + }); + + const resetOption = screen.getByText("Reset"); + await user.click(resetOption); + + expect(onSortChange).toHaveBeenCalledTimes(1); + expect(onSortChange).toHaveBeenCalledWith(false); + }); + + it("should highlight ascending option when sort state is asc", async () => { + const user = userEvent.setup(); + const onSortChange = vi.fn(); + render(); + + const button = screen.getByRole("button"); + await user.click(button); + + await waitFor(() => { + const ascendingOption = screen.getByText("Ascending"); + const menuItem = ascendingOption.closest(".ant-dropdown-menu-item"); + expect(menuItem).toHaveClass("ant-dropdown-menu-item-selected"); + }); + }); + + it("should highlight descending option when sort state is desc", async () => { + const user = userEvent.setup(); + const onSortChange = vi.fn(); + render(); + + const button = screen.getByRole("button"); + await user.click(button); + + await waitFor(() => { + const descendingOption = screen.getByText("Descending"); + const menuItem = descendingOption.closest(".ant-dropdown-menu-item"); + expect(menuItem).toHaveClass("ant-dropdown-menu-item-selected"); + }); + }); + + it("should not highlight any option when sort state is false", async () => { + const user = userEvent.setup(); + const onSortChange = vi.fn(); + render(); + + const button = screen.getByRole("button"); + await user.click(button); + + await waitFor(() => { + expect(screen.getByText("Ascending")).toBeInTheDocument(); + }); + + const ascendingOption = screen.getByText("Ascending"); + const menuItem = ascendingOption.closest(".ant-dropdown-menu-item"); + expect(menuItem).not.toHaveClass("ant-dropdown-menu-item-selected"); + }); + + it("should stop event propagation when button is clicked", async () => { + const user = userEvent.setup(); + const onSortChange = vi.fn(); + const onParentClick = vi.fn(); + + render( +
    + +
    , + ); + + const button = screen.getByRole("button"); + await user.click(button); + + expect(onParentClick).not.toHaveBeenCalled(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/common_components/TableHeaderSortDropdown/TableHeaderSortDropdown.tsx b/ui/litellm-dashboard/src/components/common_components/TableHeaderSortDropdown/TableHeaderSortDropdown.tsx new file mode 100644 index 00000000000..cbdc8beaaa4 --- /dev/null +++ b/ui/litellm-dashboard/src/components/common_components/TableHeaderSortDropdown/TableHeaderSortDropdown.tsx @@ -0,0 +1,85 @@ +import React from "react"; +import { Button, Dropdown, MenuProps } from "antd"; +import { SwitchVerticalIcon, ChevronUpIcon, ChevronDownIcon, XIcon } from "@heroicons/react/outline"; + +export type SortState = "asc" | "desc" | false; + +interface TableHeaderSortDropdownProps { + /** + * Current sort state: "asc", "desc", or false for neutral + */ + sortState: SortState; + /** + * Callback when sort state changes + * @param newState - The new sort state: "asc", "desc", or false + */ + onSortChange: (newState: SortState) => void; + /** + * Optional column ID for identification + */ + columnId?: string; +} + +export const TableHeaderSortDropdown: React.FC = ({ + sortState, + onSortChange, +}) => { + const handleMenuClick: MenuProps["onClick"] = ({ key }) => { + if (key === "asc") { + onSortChange("asc"); + } else if (key === "desc") { + onSortChange("desc"); + } else if (key === "reset") { + onSortChange(false); + } + }; + + const menuItems: MenuProps["items"] = [ + { + key: "asc", + label: "Ascending", + icon: , + }, + { + key: "desc", + label: "Descending", + icon: , + }, + { + key: "reset", + label: "Reset", + icon: , + }, + ]; + + // Determine which icon to display based on current sort state + const renderIcon = () => { + if (sortState === "asc") { + return ; + } else if (sortState === "desc") { + return ; + } else { + return ; + } + }; + + return ( + + + )} +
    + ), + formatContentFilterDataForAPI: (patterns: any[], blockedWords: any[]) => ({ + patterns, + blocked_words: blockedWords, + }), +})); + describe("Guardrail Info", () => { afterEach(() => { vi.clearAllMocks(); @@ -147,4 +169,101 @@ describe("Guardrail Info", () => { expect(getByText("PII Entity Configuration")).toBeInTheDocument(); }); }); + it("should handle content filter updates correctly", async () => { + // Mock the network responses + vi.mocked(networking.getGuardrailInfo).mockResolvedValue({ + guardrail_id: "123", + guardrail_name: "Content Filter Guardrail", + litellm_params: { + guardrail: "litellm_content_filter", + mode: "pre_call", + default_on: true, + patterns: ["initial_pattern"], + blocked_words: ["initial_word"], + }, + created_at: "2024-01-01T00:00:00Z", + updated_at: "2024-01-01T00:00:00Z", + guardrail_definition_location: "database", + }); + + vi.mocked(networking.getGuardrailUISettings).mockResolvedValue({ + supported_entities: [], + supported_actions: [], + pii_entity_categories: [], + supported_modes: ["pre_call", "post_call"], + }); + + vi.mocked(networking.getGuardrailProviderSpecificParams).mockResolvedValue({}); + vi.mocked(networking.updateGuardrailCall).mockResolvedValue({ status: "success" }); + + const { getByText, getByRole, getAllByRole, getByLabelText } = render( + { }} accessToken="123" isAdmin={true} />, + ); + + await waitFor(() => { + expect(getByText("Settings")).toBeInTheDocument(); + }); + + // Go to Settings tab + fireEvent.click(getByText("Settings")); + + await waitFor(() => { + expect(getByText("Guardrail Settings")).toBeInTheDocument(); + }); + + // Enter Edit Mode + fireEvent.click(getByText("Edit Settings")); + + // Modify Guardrail Name to force an update + const nameInput = getByLabelText("Guardrail Name"); + fireEvent.change(nameInput, { target: { value: "Updated Name" } }); + + // Save with only name change + const saveButton = getByText("Save Changes"); + fireEvent.click(saveButton); + + await waitFor(() => { + expect(networking.updateGuardrailCall).toHaveBeenCalled(); + }); + + // Verify call did NOT include patterns or blocked_words (because no changes) + // updateGuardrailCall(accessToken, guardrailId, updateData) -> index 2 is updateData + const firstCallArgs: any = vi.mocked(networking.updateGuardrailCall).mock.calls[0][2]; + + // Verify attributes that definitely changed + expect(firstCallArgs.guardrail_name).toBe("Updated Name"); + + // litellm_params might be undefined if empty, which is correct. + // If it exists, ensure patterns/blocked_words are not in it. + if (firstCallArgs.litellm_params) { + expect(firstCallArgs.litellm_params.patterns).toBeUndefined(); + expect(firstCallArgs.litellm_params.blocked_words).toBeUndefined(); + } + + // Clear mocks to reset call count + vi.clearAllMocks(); + + // Enter Edit Mode again to make changes + await waitFor(() => { + expect(getByText("Edit Settings")).toBeInTheDocument(); + }); + fireEvent.click(getByText("Edit Settings")); + + // Now modify the values using the mock button + const simulateChangeButton = getByText("Simulate Change"); + fireEvent.click(simulateChangeButton); + + // Save again + fireEvent.click(getByText("Save Changes")); + + await waitFor(() => { + expect(networking.updateGuardrailCall).toHaveBeenCalled(); + }); + + // Verify call INCLUDES patterns and blocked_words + const secondCallArgs: any = vi.mocked(networking.updateGuardrailCall).mock.calls[0][2]; + expect(secondCallArgs.litellm_params).toBeDefined(); + expect(secondCallArgs.litellm_params.patterns).toEqual(["new_pattern"]); + expect(secondCallArgs.litellm_params.blocked_words).toEqual(["new_word"]); + }); }); diff --git a/ui/litellm-dashboard/src/components/guardrails/guardrail_info.tsx b/ui/litellm-dashboard/src/components/guardrails/guardrail_info.tsx index 0d1a205f66f..0a15446db97 100644 --- a/ui/litellm-dashboard/src/components/guardrails/guardrail_info.tsx +++ b/ui/litellm-dashboard/src/components/guardrails/guardrail_info.tsx @@ -271,22 +271,17 @@ const GuardrailInfoView: React.FC = ({ guardrailId, onClose, } // Only add Content Filter patterns if there are changes - if (guardrailData.litellm_params?.guardrail === "litellm_content_filter") { + if (guardrailData.litellm_params?.guardrail === "litellm_content_filter" && hasUnsavedContentFilterChanges) { const originalPatterns = guardrailData.litellm_params?.patterns || []; const originalBlockedWords = guardrailData.litellm_params?.blocked_words || []; const formattedData = formatContentFilterDataForAPI( - contentFilterDataRef.current.patterns, - contentFilterDataRef.current.blockedWords, + contentFilterDataRef.current.patterns || [], + contentFilterDataRef.current.blockedWords || [], ); - if (JSON.stringify(originalPatterns) !== JSON.stringify(formattedData.patterns)) { - updateData.litellm_params.patterns = formattedData.patterns; - } - - if (JSON.stringify(originalBlockedWords) !== JSON.stringify(formattedData.blocked_words)) { - updateData.litellm_params.blocked_words = formattedData.blocked_words; - } + updateData.litellm_params.patterns = formattedData.patterns; + updateData.litellm_params.blocked_words = formattedData.blocked_words; } if (guardrailData.litellm_params?.guardrail === "tool_permission") { @@ -357,6 +352,9 @@ const GuardrailInfoView: React.FC = ({ guardrailId, onClose, console.log("allowedParams: ", allowedParams); allowedParams.forEach((paramName) => { + if (paramName === "patterns" || paramName === "blocked_words") { + return; + } // Check for both direct parameter name and nested optional_params object let paramValue = values[paramName]; if (paramValue === undefined || paramValue === null || paramValue === "") { diff --git a/ui/litellm-dashboard/src/components/leftnav.tsx b/ui/litellm-dashboard/src/components/leftnav.tsx index 543d95d2ccd..b26590989d2 100644 --- a/ui/litellm-dashboard/src/components/leftnav.tsx +++ b/ui/litellm-dashboard/src/components/leftnav.tsx @@ -39,6 +39,7 @@ interface SidebarProps { setPage: (page: string) => void; defaultSelectedKey: string; collapsed?: boolean; + enabledPagesInternalUsers?: string[] | null; } // Menu item configuration @@ -59,7 +60,264 @@ interface MenuGroup { roles?: string[]; } -const Sidebar: React.FC = ({ setPage, defaultSelectedKey, collapsed = false }) => { +// Menu groups organized by category - defined outside component for export +const menuGroups: MenuGroup[] = [ + { + groupLabel: "AI GATEWAY", + items: [ + { + key: "api-keys", + page: "api-keys", + label: "Virtual Keys", + icon: , + }, + { + key: "llm-playground", + page: "llm-playground", + label: "Playground", + icon: , + roles: rolesWithWriteAccess, + }, + { + key: "models", + page: "models", + label: "Models + Endpoints", + icon: , + roles: rolesWithWriteAccess, + }, + { + key: "agents", + page: "agents", + label: "Agents", + icon: , + roles: rolesWithWriteAccess, + }, + { + key: "mcp-servers", + page: "mcp-servers", + label: "MCP Servers", + icon: , + }, + { + key: "guardrails", + page: "guardrails", + label: "Guardrails", + icon: , + roles: all_admin_roles, + }, + { + key: "policies", + page: "policies", + label: ( + + Policies + + ), + icon: , + roles: all_admin_roles, + }, + { + key: "tools", + page: "tools", + label: "Tools", + icon: , + children: [ + { + key: "search-tools", + page: "search-tools", + label: "Search Tools", + icon: , + }, + { + key: "vector-stores", + page: "vector-stores", + label: "Vector Stores", + icon: , + }, + ], + }, + ], + }, + { + groupLabel: "OBSERVABILITY", + items: [ + { + key: "new_usage", + page: "new_usage", + icon: , + roles: [...all_admin_roles, ...internalUserRoles], + label: "Usage", + }, + { + key: "logs", + page: "logs", + label: ( + + Logs + + ), + icon: , + }, + ], + }, + { + groupLabel: "ACCESS CONTROL", + items: [ + { + key: "users", + page: "users", + label: "Internal Users", + icon: , + roles: all_admin_roles, + }, + { + key: "teams", + page: "teams", + label: "Teams", + icon: , + }, + { + key: "organizations", + page: "organizations", + label: "Organizations", + icon: , + roles: all_admin_roles, + }, + { + key: "budgets", + page: "budgets", + label: "Budgets", + icon: , + roles: all_admin_roles, + }, + ], + }, + { + groupLabel: "DEVELOPER TOOLS", + items: [ + { + key: "api_ref", + page: "api_ref", + label: "API Reference", + icon: , + }, + { + key: "model-hub-table", + page: "model-hub-table", + label: "AI Hub", + icon: , + }, + { + key: "learning-resources", + page: "learning-resources", + label: "Learning Resources", + icon: , + external_url: "https://models.litellm.ai/cookbook", + }, + { + key: "experimental", + page: "experimental", + label: "Experimental", + icon: , + children: [ + { + key: "caching", + page: "caching", + label: "Caching", + icon: , + roles: all_admin_roles, + }, + { + key: "prompts", + page: "prompts", + label: "Prompts", + icon: , + roles: all_admin_roles, + }, + { + key: "transform-request", + page: "transform-request", + label: "API Playground", + icon: , + roles: [...all_admin_roles, ...internalUserRoles], + }, + { + key: "tag-management", + page: "tag-management", + label: "Tag Management", + icon: , + roles: all_admin_roles, + }, + { + key: "claude-code-plugins", + page: "claude-code-plugins", + label: "Claude Code Plugins", + icon: , + roles: all_admin_roles, + }, + { + key: "4", + page: "usage", + label: "Old Usage", + icon: , + } + ], + }, + ], + }, + { + groupLabel: "SETTINGS", + roles: all_admin_roles, + items: [ + { + key: "settings", + page: "settings", + label: Settings, + icon: , + roles: all_admin_roles, + children: [ + { + key: "router-settings", + page: "router-settings", + label: "Router Settings", + icon: , + roles: all_admin_roles, + }, + { + key: "logging-and-alerts", + page: "logging-and-alerts", + label: "Logging & Alerts", + icon: , + roles: all_admin_roles, + }, + { + key: "admin-panel", + page: "admin-panel", + label: "Admin Settings", + icon: , + roles: all_admin_roles, + }, + { + key: "cost-tracking", + page: "cost-tracking", + label: "Cost Tracking", + icon: , + roles: all_admin_roles, + }, + { + key: "ui-theme", + page: "ui-theme", + label: "UI Theme", + icon: , + roles: all_admin_roles, + }, + ], + }, + ], + }, +]; + +const Sidebar: React.FC = ({ setPage, defaultSelectedKey, collapsed = false, enabledPagesInternalUsers }) => { const { userId, accessToken, userRole } = useAuthorized(); const { data: organizations } = useOrganizations(); @@ -79,274 +337,62 @@ const Sidebar: React.FC = ({ setPage, defaultSelectedKey, collapse setPage(page); }; - // Menu groups organized by category - const menuGroups: MenuGroup[] = [ - { - groupLabel: "AI GATEWAY", - items: [ - { - key: "api-keys", - page: "api-keys", - label: "Virtual Keys", - icon: , - }, - { - key: "llm-playground", - page: "llm-playground", - label: "Playground", - icon: , - roles: rolesWithWriteAccess, - }, - { - key: "models", - page: "models", - label: "Models + Endpoints", - icon: , - roles: rolesWithWriteAccess, - }, - { - key: "agents", - page: "agents", - label: Agents, - icon: , - roles: rolesWithWriteAccess, - }, - { - key: "mcp-servers", - page: "mcp-servers", - label: "MCP Servers", - icon: , - }, - { - key: "guardrails", - page: "guardrails", - label: "Guardrails", - icon: , - roles: all_admin_roles, - }, - { - key: "policies", - page: "policies", - label: "Policies", - icon: , - roles: all_admin_roles, - }, - { - key: "tools", - page: "tools", - label: "Tools", - icon: , - children: [ - { - key: "search-tools", - page: "search-tools", - label: "Search Tools", - icon: , - }, - { - key: "vector-stores", - page: "vector-stores", - label: "Vector Stores", - icon: , - roles: all_admin_roles, - }, - ], - }, - ], - }, - { - groupLabel: "OBSERVABILITY", - items: [ - { - key: "new_usage", - page: "new_usage", - icon: , - roles: [...all_admin_roles, ...internalUserRoles], - label: "Usage", - }, - { - key: "logs", - page: "logs", - label: ( - - Logs - - ), - icon: , - }, - ], - }, - { - groupLabel: "ACCESS CONTROL", - items: [ - { - key: "users", - page: "users", - label: "Internal Users", - icon: , - roles: all_admin_roles, - }, - { - key: "teams", - page: "teams", - label: "Teams", - icon: , - }, - { - key: "organizations", - page: "organizations", - label: "Organizations", - icon: , - roles: all_admin_roles, - }, - { - key: "budgets", - page: "budgets", - label: "Budgets", - icon: , - roles: all_admin_roles, - }, - ], - }, - { - groupLabel: "DEVELOPER TOOLS", - items: [ - { - key: "api_ref", - page: "api_ref", - label: "API Reference", - icon: , - }, - { - key: "model-hub-table", - page: "model-hub-table", - label: "AI Hub", - icon: , - }, - { - key: "learning-resources", - page: "learning-resources", - label: "Learning Resources", - icon: , - external_url: "https://models.litellm.ai/cookbook", - }, - { - key: "experimental", - page: "experimental", - label: "Experimental", - icon: , - children: [ - { - key: "caching", - page: "caching", - label: "Caching", - icon: , - roles: all_admin_roles, - }, - { - key: "prompts", - page: "prompts", - label: "Prompts", - icon: , - roles: all_admin_roles, - }, - { - key: "transform-request", - page: "transform-request", - label: "API Playground", - icon: , - roles: [...all_admin_roles, ...internalUserRoles], - }, - { - key: "tag-management", - page: "tag-management", - label: "Tag Management", - icon: , - roles: all_admin_roles, - }, - { - key: "claude-code-plugins", - page: "claude-code-plugins", - label: "Claude Code Plugins", - icon: , - roles: all_admin_roles, - }, - { - key: "4", - page: "usage", - label: "Old Usage", - icon: , - } - ], - }, - ], - }, - { - groupLabel: "SETTINGS", - roles: all_admin_roles, - items: [ - { - key: "settings", - page: "settings", - label: Settings, - icon: , - roles: all_admin_roles, - children: [ - { - key: "router-settings", - page: "router-settings", - label: "Router Settings", - icon: , - roles: all_admin_roles, - }, - { - key: "logging-and-alerts", - page: "logging-and-alerts", - label: "Logging & Alerts", - icon: , - roles: all_admin_roles, - }, - { - key: "admin-panel", - page: "admin-panel", - label: "Admin Settings", - icon: , - roles: all_admin_roles, - }, - { - key: "cost-tracking", - page: "cost-tracking", - label: "Cost Tracking", - icon: , - roles: all_admin_roles, - }, - { - key: "ui-theme", - page: "ui-theme", - label: "UI Theme", - icon: , - roles: all_admin_roles, - }, - ], - }, - ], - }, - ]; - - // Filter items based on user role + // Filter items based on user role and enabled pages for internal users const filterItemsByRole = (items: MenuItem[]): MenuItem[] => { + const isAdmin = isAdminRole(userRole); + + // Debug logging + if (enabledPagesInternalUsers !== null && enabledPagesInternalUsers !== undefined) { + console.log("[LeftNav] Filtering with enabled pages:", { + userRole, + isAdmin, + enabledPagesInternalUsers, + }); + } + return items - .filter((item) => { - // Special handling for organizations menu item - allow org_admins - if (item.key === "organizations") { - return !item.roles || item.roles.includes(userRole) || isOrgAdmin; - } - return !item.roles || item.roles.includes(userRole); - }) .map((item) => ({ ...item, children: item.children ? filterItemsByRole(item.children) : undefined, - })); + })) + .filter((item) => { + // Special handling for organizations menu item - allow org_admins + if (item.key === "organizations") { + const hasRoleAccess = !item.roles || item.roles.includes(userRole) || isOrgAdmin; + if (!hasRoleAccess) return false; + + // Check enabled pages for internal users (non-admins) + if (!isAdmin && enabledPagesInternalUsers !== null && enabledPagesInternalUsers !== undefined) { + const isIncluded = enabledPagesInternalUsers.includes(item.page); + console.log(`[LeftNav] Page "${item.page}" (${item.key}): ${isIncluded ? "VISIBLE" : "HIDDEN"}`); + return isIncluded; + } + return true; + } + + // Existing role check + if (item.roles && !item.roles.includes(userRole)) return false; + + // Check enabled pages for internal users (non-admins) + if (!isAdmin && enabledPagesInternalUsers !== null && enabledPagesInternalUsers !== undefined) { + // If item has children, check if any children are visible + if (item.children && item.children.length > 0) { + const hasVisibleChildren = item.children.some((child) => + enabledPagesInternalUsers.includes(child.page) + ); + if (hasVisibleChildren) { + console.log(`[LeftNav] Parent "${item.page}" (${item.key}): VISIBLE (has visible children)`); + return true; + } + } + + const isIncluded = enabledPagesInternalUsers.includes(item.page); + console.log(`[LeftNav] Page "${item.page}" (${item.key}): ${isIncluded ? "VISIBLE" : "HIDDEN"}`); + return isIncluded; + } + + return true; + }); }; // Build menu items with groups @@ -398,12 +444,12 @@ const Sidebar: React.FC = ({ setPage, defaultSelectedKey, collapse })), onClick: !item.children ? () => { - if (item.external_url) { - window.open(item.external_url, "_blank"); - } else { - navigateToPage(item.page); - } + if (item.external_url) { + window.open(item.external_url, "_blank"); + } else { + navigateToPage(item.page); } + } : undefined, })), }); @@ -481,3 +527,6 @@ const Sidebar: React.FC = ({ setPage, defaultSelectedKey, collapse }; export default Sidebar; + +// Also export menuGroups for advanced use cases +export { menuGroups }; diff --git a/ui/litellm-dashboard/src/components/mcp_tools/mcp_connect.tsx b/ui/litellm-dashboard/src/components/mcp_tools/mcp_connect.tsx index 5a012c1fc5c..c48b9a755b7 100644 --- a/ui/litellm-dashboard/src/components/mcp_tools/mcp_connect.tsx +++ b/ui/litellm-dashboard/src/components/mcp_tools/mcp_connect.tsx @@ -32,7 +32,7 @@ const FeatureCard: React.FC = ({ description, children, serverName, - accessGroups = ["dev"], + accessGroups = ["dev-group"], }) => { const [useServerHeader, setUseServerHeader] = useState(false); @@ -42,9 +42,9 @@ const FeatureCard: React.FC = ({ }; if (useServerHeader && serverName) { const formattedServerName = serverName.replace(/\s+/g, "_"); - // Include both server name and access groups in the same header + // Include both server name and access groups in the same header (comma-separated string) const serverAndGroups = [formattedServerName, ...accessGroups].join(","); - headers["x-mcp-servers"] = [serverAndGroups]; + headers["x-mcp-servers"] = serverAndGroups; } return headers; }; @@ -77,13 +77,13 @@ const FeatureCard: React.FC = ({ description={

    - Option 1: Get a specific server: ["{serverName.replace(/\s+/g, "_")}"] + Option 1: Get a specific server: "{serverName.replace(/\s+/g, "_")}"

    - Option 2: Get a group of MCPs: ["dev-group"] + Option 2: Get a group of MCPs: "dev-group"

    - You can also mix both: ["Server1,dev-group"] + You can also mix both: "Server1,dev-group"

    } @@ -144,8 +144,8 @@ const MCPConnect: React.FC = ({ currentServerAccessGroups = [] // Format server names (replace spaces with underscores) const formattedServers = serverHeaders[type].map((s) => s.replace(/\s+/g, "_")); - // Use comma-separated format (can include both servers and access groups) - headers["x-mcp-servers"] = [formattedServers.join(",")]; + // Use comma-separated string (can include both servers and access groups) + headers["x-mcp-servers"] = formattedServers.join(","); } return headers; @@ -244,7 +244,7 @@ const MCPConnect: React.FC = ({ currentServerAccessGroups = [] title="Implementation Example" description="Complete cURL example for using the LiteLLM Proxy Responses API" serverName={currentServer} - accessGroups={["dev"]} + accessGroups={["dev-group"]} > = ({ currentServerAccessGroups = [] "require_approval": "never", "headers": { "x-litellm-api-key": "Bearer YOUR_LITELLM_VIRTUAL_KEY", - "x-mcp-servers": ["Zapier_MCP,dev"] + "x-mcp-servers": "Zapier_MCP,dev-group" } } ], @@ -327,7 +327,7 @@ const MCPConnect: React.FC = ({ currentServerAccessGroups = [] title="Implementation Example" description="Complete cURL example for using the Responses API" serverName="Zapier Gmail" - accessGroups={["dev"]} + accessGroups={["dev-group"]} > = ({ currentServerAccessGroups = [] "require_approval": "never", "headers": { "x-litellm-api-key": "Bearer YOUR_LITELLM_API_KEY", - "x-mcp-servers": ["Zapier_MCP,dev"] + "x-mcp-servers": "Zapier_MCP,dev-group" } } ], @@ -400,7 +400,7 @@ const MCPConnect: React.FC = ({ currentServerAccessGroups = [] title="Configuration" description="Cursor MCP configuration" serverName="Zapier Gmail" - accessGroups={["dev"]} + accessGroups={["dev-group"]} > = ({ currentServerAccessGroups = [] "url": "${proxyBaseUrl}/mcp", "headers": { "x-litellm-api-key": "Bearer YOUR_LITELLM_API_KEY", - "x-mcp-servers": ["Zapier_MCP,dev"] + "x-mcp-servers": "Zapier_MCP,dev-group" } } } diff --git a/ui/litellm-dashboard/src/components/model_dashboard/all_models_table.tsx b/ui/litellm-dashboard/src/components/model_dashboard/all_models_table.tsx new file mode 100644 index 00000000000..1ae3335a823 --- /dev/null +++ b/ui/litellm-dashboard/src/components/model_dashboard/all_models_table.tsx @@ -0,0 +1,204 @@ +import { + ColumnDef, + flexRender, + getCoreRowModel, + getPaginationRowModel, + SortingState, + useReactTable, + ColumnResizeMode, + VisibilityState, + PaginationState, + OnChangeFn, +} from "@tanstack/react-table"; +import React from "react"; +import { Table, TableHead, TableHeaderCell, TableBody, TableRow, TableCell } from "@tremor/react"; +import { TableHeaderSortDropdown, SortState } from "../common_components/TableHeaderSortDropdown/TableHeaderSortDropdown"; + +// Extend the column meta type to include className +declare module "@tanstack/react-table" { + interface ColumnMeta { + className?: string; + } +} + +interface AllModelsDataTableProps { + data: TData[]; + columns: ColumnDef[]; + isLoading?: boolean; + sorting?: SortingState; + onSortingChange?: OnChangeFn; + pagination?: PaginationState; + onPaginationChange?: OnChangeFn; + enablePagination?: boolean; +} + +export function AllModelsDataTable({ + data = [], + columns, + isLoading = false, + sorting = [], + onSortingChange, + pagination, + onPaginationChange, + enablePagination = false, +}: AllModelsDataTableProps) { + const [columnResizeMode] = React.useState("onChange"); + const [columnSizing, setColumnSizing] = React.useState({}); + const [columnVisibility, setColumnVisibility] = React.useState({}); + + const tableInstance = useReactTable({ + data, + columns, + state: { + sorting, + columnSizing, + columnVisibility, + ...(enablePagination && pagination ? { pagination } : {}), + }, + columnResizeMode, + onSortingChange: onSortingChange, + onColumnSizingChange: setColumnSizing, + onColumnVisibilityChange: setColumnVisibility, + ...(enablePagination && onPaginationChange ? { onPaginationChange } : {}), + getCoreRowModel: getCoreRowModel(), + // NO getSortedRowModel - sorting is handled server-side + ...(enablePagination ? { getPaginationRowModel: getPaginationRowModel() } : {}), + enableSorting: true, + enableColumnResizing: true, + manualSorting: true, // Enable manual sorting for server-side sorting + defaultColumn: { + minSize: 40, + maxSize: 500, + }, + }); + + const getHeaderText = (header: any): string => { + if (typeof header === "string") { + return header; + } + if (typeof header === "function") { + const headerElement = header(); + if (headerElement && headerElement.props && headerElement.props.children) { + const children = headerElement.props.children; + if (typeof children === "string") { + return children; + } + if (children.props && children.props.children) { + return children.props.children; + } + } + } + return ""; + }; + + return ( +
    +
    +
    +
    + + {tableInstance.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => ( + +
    +
    + {header.isPlaceholder + ? null + : flexRender(header.column.columnDef.header, header.getContext())} +
    + {header.id !== "actions" && header.column.getCanSort() && onSortingChange && ( + { + // Convert SortState to TanStack SortingState + // Only allow one column to be sorted at a time + if (newState === false) { + onSortingChange([]); + } else { + onSortingChange([ + { + id: header.column.id, + desc: newState === "desc", + }, + ]); + } + }} + columnId={header.column.id} + /> + )} +
    + {header.column.getCanResize() && ( +
    + )} + + ))} + + ))} + + + {isLoading ? ( + + +
    +

    🚅 Loading models...

    +
    +
    +
    + ) : tableInstance.getRowModel().rows.length > 0 ? ( + tableInstance.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + +
    +

    No models found

    +
    +
    +
    + )} +
    +
    + + + + ); +} diff --git a/ui/litellm-dashboard/src/components/model_info_view.test.tsx b/ui/litellm-dashboard/src/components/model_info_view.test.tsx index 21402ad4671..4b9f7d217dc 100644 --- a/ui/litellm-dashboard/src/components/model_info_view.test.tsx +++ b/ui/litellm-dashboard/src/components/model_info_view.test.tsx @@ -1,112 +1,38 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { render, waitFor } from "@testing-library/react"; -import { describe, expect, it, vi } from "vitest"; +import React, { ReactNode } from "react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; import ModelInfoView from "./model_info_view"; vi.mock("../../utils/dataUtils", () => ({ - copyToClipboard: vi.fn(), + copyToClipboard: vi.fn().mockResolvedValue(true), })); vi.mock("./networking", () => ({ modelInfoV1Call: vi.fn().mockResolvedValue({ data: [ { - model_name: "aws/anthropic/bedrock-claude-3-5-sonnet-v1", + model_name: "GPT-4", litellm_params: { - aws_region_name: "us-east-1", - custom_llm_provider: "bedrock", - use_in_pass_through: false, - use_litellm_proxy: false, - merge_reasoning_content_in_choices: false, - model: "bedrock/us.anthropic.claude-3-5-sonnet-20240620-v1:0", + model: "gpt-4", + api_base: "https://api.openai.com/v1", + custom_llm_provider: "openai", }, model_info: { - id: "70b94bbd2af4a75215f7e3e465b5b199529dc15deb5d395d0668a4aabc496c84", - db_model: false, - access_via_team_ids: [ - "4fe3cfea-c907-412a-a645-60915b618d11", - "9a4b2d15-4198-47e4-971b-7329b77f40e4", - "14d55eef-b8d4-4cb8-b080-d973269dae54", - "693ce1d2-9fae-4605-a5c9-1c9829415e1a", - "fe29d910-4968-45bc-9fe0-6716e89c6270", - ], - direct_access: true, - key: "anthropic.claude-3-5-sonnet-20240620-v1:0", - max_tokens: 4096, - max_input_tokens: 200000, - max_output_tokens: 4096, - input_cost_per_token: 0.000003, - input_cost_per_token_flex: null, - input_cost_per_token_priority: null, - cache_creation_input_token_cost: null, - cache_read_input_token_cost: null, - cache_read_input_token_cost_flex: null, - cache_read_input_token_cost_priority: null, - cache_creation_input_token_cost_above_1hr: null, - input_cost_per_character: null, - input_cost_per_token_above_128k_tokens: null, - input_cost_per_token_above_200k_tokens: null, - input_cost_per_query: null, - input_cost_per_second: null, - input_cost_per_audio_token: null, - input_cost_per_token_batches: null, - output_cost_per_token_batches: null, - output_cost_per_token: 0.000015, - output_cost_per_token_flex: null, - output_cost_per_token_priority: null, - output_cost_per_audio_token: null, - output_cost_per_character: null, - output_cost_per_reasoning_token: null, - output_cost_per_token_above_128k_tokens: null, - output_cost_per_character_above_128k_tokens: null, - output_cost_per_token_above_200k_tokens: null, - output_cost_per_second: null, - output_cost_per_video_per_second: null, - output_cost_per_image: null, - output_vector_size: null, - citation_cost_per_token: null, - tiered_pricing: null, - litellm_provider: "bedrock", - mode: "chat", - supports_system_messages: null, - supports_response_schema: true, - supports_vision: true, - supports_function_calling: true, - supports_tool_choice: true, - supports_assistant_prefill: null, - supports_prompt_caching: null, - supports_audio_input: null, - supports_audio_output: null, - supports_pdf_input: true, - supports_embedding_image_input: null, - supports_native_streaming: null, - supports_web_search: null, - supports_url_context: null, - supports_reasoning: null, - supports_computer_use: null, - search_context_cost_per_query: null, - tpm: null, - rpm: null, - ocr_cost_per_page: null, - annotation_cost_per_page: null, - supported_openai_params: [ - "max_tokens", - "max_completion_tokens", - "stream", - "stream_options", - "stop", - "temperature", - "top_p", - "extra_headers", - "response_format", - "requestMetadata", - "tools", - "tool_choice", - ], + id: "123", + created_by: "123", + db_model: true, + input_cost_per_token: 0.00003, + output_cost_per_token: 0.00006, }, }, ], }), - credentialGetCall: vi.fn().mockResolvedValue({}), + credentialGetCall: vi.fn().mockResolvedValue({ + credential_name: "test-credential", + credential_values: {}, + credential_info: {}, + }), getGuardrailsList: vi.fn().mockResolvedValue({ guardrails: [{ guardrail_name: "content_filter" }, { guardrail_name: "toxicity_filter" }], }), @@ -120,99 +46,135 @@ vi.mock("./networking", () => ({ description: "Production ready models", }, }), + testConnectionRequest: vi.fn().mockResolvedValue({ + status: "success", + }), + modelPatchUpdateCall: vi.fn().mockResolvedValue({}), + modelDeleteCall: vi.fn().mockResolvedValue({}), })); // Mock the useModelsInfo hook since it uses React Query +const mockUseModelsInfo = vi.fn(); +const mockUseModelHub = vi.fn(); + vi.mock("@/app/(dashboard)/hooks/models/useModels", () => ({ - useModelsInfo: vi.fn().mockReturnValue({ - data: { - data: [ - { - model_name: "bedrock/us.anthropic.claude-3-5-sonnet-20240620-v1:0", - provider: "bedrock", - litellm_model_name: "bedrock/us.anthropic.claude-3-5-sonnet-20240620-v1:0", - }, - { - model_name: "openai/gpt-4", - provider: "openai", - litellm_model_name: "gpt-4", - }, - ], - }, - isLoading: false, - error: null, - }), + useModelsInfo: (...args: any[]) => mockUseModelsInfo(...args), + useModelHub: (...args: any[]) => mockUseModelHub(...args), +})); + +// Mock the useModelCostMap hook +const mockUseModelCostMap = vi.fn(); +vi.mock("@/app/(dashboard)/hooks/models/useModelCostMap", () => ({ + useModelCostMap: (...args: any[]) => mockUseModelCostMap(...args), })); describe("ModelInfoView", () => { - const modelData = { - model_info: { - id: "123", - created_by: "123", - db_model: true, - }, + let queryClient: QueryClient; + + const defaultModelData = { + model_name: "GPT-4", litellm_params: { + model: "gpt-4", api_base: "https://api.openai.com/v1", custom_llm_provider: "openai", }, - litellm_model_name: "gpt-4", - model_name: "GPT-4", - litellm_provider: "openai", - mode: "chat", - supported_openai_params: ["temperature", "max_tokens", "top_p", "frequency_penalty", "presence_penalty"], + model_info: { + id: "123", + created_by: "123", + created_at: "2024-01-01T00:00:00Z", + db_model: true, + input_cost_per_token: 0.00003, + output_cost_per_token: 0.00006, + }, }; + beforeEach(() => { + queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }); + vi.clearAllMocks(); + + // Set up default mocks + mockUseModelsInfo.mockReturnValue({ + data: { + data: [defaultModelData], + }, + isLoading: false, + error: null, + }); + + mockUseModelHub.mockReturnValue({ + data: { + data: [], + }, + isLoading: false, + error: null, + }); + + mockUseModelCostMap.mockReturnValue({ + data: {}, + isLoading: false, + error: null, + }); + }); + + const wrapper = ({ children }: { children: ReactNode }) => + React.createElement(QueryClientProvider, { client: queryClient }, children); + const DEFAULT_ADMIN_PROPS = { modelId: "123", onClose: () => {}, - modelData: modelData, accessToken: "123", userID: "123", userRole: "Admin", - editModel: false, - setEditModalVisible: () => {}, - setSelectedModel: () => {}, onModelUpdate: () => {}, modelAccessGroups: [], }; describe("Edit Model", () => { it("should render the model info view", async () => { - const { getByText } = render(); + const { getByText } = render(, { wrapper }); await waitFor(() => { expect(getByText("Model Settings")).toBeInTheDocument(); }); }); - it("should not render an edit model button if the model is not a DB model", async () => { + it("should not render an edit settings button if the model is not a DB model", async () => { const nonDbModelData = { - ...modelData, + ...defaultModelData, model_info: { - ...modelData.model_info, + ...defaultModelData.model_info, db_model: false, }, }; - const NON_DB_ADMIN_PROPS = { - ...DEFAULT_ADMIN_PROPS, - modelData: nonDbModelData, - }; + mockUseModelsInfo.mockReturnValue({ + data: { + data: [nonDbModelData], + }, + isLoading: false, + error: null, + }); - const { queryByText } = render(); + const { queryByText } = render(, { wrapper }); await waitFor(() => { - expect(queryByText("Edit Model")).not.toBeInTheDocument(); + expect(queryByText("Edit Settings")).not.toBeInTheDocument(); }); }); it("should render tags in the edit model", async () => { - const { getByText } = render(); + const { getByText } = render(, { wrapper }); await waitFor(() => { expect(getByText("Tags")).toBeInTheDocument(); }); }); it("should render the litellm params in the edit model", async () => { - const { getByText } = render(); + const { getByText } = render(, { wrapper }); await waitFor(() => { expect(getByText("LiteLLM Params")).toBeInTheDocument(); }); @@ -220,21 +182,21 @@ describe("ModelInfoView", () => { }); it("should render a test connection button", async () => { - const { getByTestId } = render(); + const { getByTestId } = render(, { wrapper }); await waitFor(() => { expect(getByTestId("test-connection-button")).toBeInTheDocument(); }); }); it("should render a reuse credentials button", async () => { - const { getByTestId } = render(); + const { getByTestId } = render(, { wrapper }); await waitFor(() => { expect(getByTestId("reuse-credentials-button")).toBeInTheDocument(); }); }); it("should render a delete model button", async () => { - const { getByTestId } = render(); + const { getByTestId } = render(, { wrapper }); await waitFor(() => { expect(getByTestId("delete-model-button")).toBeInTheDocument(); }); @@ -242,17 +204,22 @@ describe("ModelInfoView", () => { it("should render a disabled delete model button if the model is not a DB model", async () => { const nonDbModelData = { - ...modelData, + ...defaultModelData, model_info: { - ...modelData.model_info, + ...defaultModelData.model_info, db_model: false, }, }; - const NON_DB_ADMIN_PROPS = { - ...DEFAULT_ADMIN_PROPS, - modelData: nonDbModelData, - }; - const { getByTestId } = render(); + + mockUseModelsInfo.mockReturnValue({ + data: { + data: [nonDbModelData], + }, + isLoading: false, + error: null, + }); + + const { getByTestId } = render(, { wrapper }); await waitFor(() => { expect(getByTestId("delete-model-button")).toBeDisabled(); }); @@ -260,18 +227,27 @@ describe("ModelInfoView", () => { it("should render a disabled delete model button if the user is not an admin and model is not created by the user", async () => { const nonCreatedByUserModelData = { - ...modelData, + ...defaultModelData, model_info: { - ...modelData.model_info, + ...defaultModelData.model_info, created_by: "456", }, }; + + mockUseModelsInfo.mockReturnValue({ + data: { + data: [nonCreatedByUserModelData], + }, + isLoading: false, + error: null, + }); + const NON_CREATED_BY_USER_ADMIN_PROPS = { ...DEFAULT_ADMIN_PROPS, - modelData: nonCreatedByUserModelData, userRole: "User", }; - const { getByTestId } = render(); + + const { getByTestId } = render(, { wrapper }); await waitFor(() => { expect(getByTestId("delete-model-button")).toBeDisabled(); }); @@ -279,16 +255,22 @@ describe("ModelInfoView", () => { it("should render health check model field for wildcard routes", async () => { const wildcardModelData = { - ...modelData, - litellm_model_name: "openai/gpt-4*", + ...defaultModelData, + litellm_params: { + ...defaultModelData.litellm_params, + model: "openai/gpt-4*", + }, }; - const WILDCARD_ADMIN_PROPS = { - ...DEFAULT_ADMIN_PROPS, - modelData: wildcardModelData, - }; + mockUseModelsInfo.mockReturnValue({ + data: { + data: [wildcardModelData], + }, + isLoading: false, + error: null, + }); - const { getByText } = render(); + const { getByText } = render(, { wrapper }); await waitFor(() => { expect(getByText("Model Settings")).toBeInTheDocument(); }); @@ -298,7 +280,7 @@ describe("ModelInfoView", () => { }); it("should not render health check model field for non-wildcard routes", async () => { - const { queryByText } = render(); + const { queryByText } = render(, { wrapper }); await waitFor(() => { expect(queryByText("Model Settings")).toBeInTheDocument(); }); @@ -309,14 +291,14 @@ describe("ModelInfoView", () => { describe("View Model", () => { it("should render the model info view", async () => { - const { getByText } = render(); + const { getByText } = render(, { wrapper }); await waitFor(() => { expect(getByText("Model Settings")).toBeInTheDocument(); }); }); it("should render tags in the view model", async () => { - const { getByText } = render(); + const { getByText } = render(, { wrapper }); await waitFor(() => { expect(getByText("Tags")).toBeInTheDocument(); }); diff --git a/ui/litellm-dashboard/src/components/model_info_view.tsx b/ui/litellm-dashboard/src/components/model_info_view.tsx index faeff5f5204..a55149124c9 100644 --- a/ui/litellm-dashboard/src/components/model_info_view.tsx +++ b/ui/litellm-dashboard/src/components/model_info_view.tsx @@ -1,4 +1,6 @@ -import { useModelsInfo } from "@/app/(dashboard)/hooks/models/useModels"; +import { useModelCostMap } from "@/app/(dashboard)/hooks/models/useModelCostMap"; +import { useModelHub, useModelsInfo } from "@/app/(dashboard)/hooks/models/useModels"; +import { transformModelData } from "@/app/(dashboard)/models-and-endpoints/utils/modelDataTransformer"; import { InfoCircleOutlined } from "@ant-design/icons"; import { ArrowLeftIcon, KeyIcon, RefreshIcon, TrashIcon } from "@heroicons/react/outline"; import { @@ -16,7 +18,7 @@ import { } from "@tremor/react"; import { Button, Form, Input, Modal, Select, Tooltip } from "antd"; import { CheckIcon, CopyIcon } from "lucide-react"; -import { useEffect, useState } from "react"; +import { useEffect, useMemo, useState } from "react"; import { copyToClipboard as utilCopyToClipboard } from "../utils/dataUtils"; import { formItemValidateJSON, truncateString } from "../utils/textUtils"; import CacheControlSettings from "./add_model/cache_control_settings"; @@ -43,7 +45,6 @@ import { getDisplayModelName } from "./view_model/model_name_display"; interface ModelInfoViewProps { modelId: string; onClose: () => void; - modelData: any; accessToken: string | null; userID: string | null; userRole: string | null; @@ -54,7 +55,6 @@ interface ModelInfoViewProps { export default function ModelInfoView({ modelId, onClose, - modelData, accessToken, userID, userRole, @@ -75,20 +75,64 @@ export default function ModelInfoView({ const [isAutoRouterModalOpen, setIsAutoRouterModalOpen] = useState(false); const [guardrailsList, setGuardrailsList] = useState([]); const [tagsList, setTagsList] = useState>({}); + + // Fetch model data using hook + const { data: rawModelDataResponse, isLoading: isLoadingModel } = useModelsInfo(1, 50, undefined, modelId); + const { data: modelCostMapData } = useModelCostMap(); + const { data: modelHubData } = useModelHub(); + + // Transform the model data + const getProviderFromModel = (model: string) => { + if (modelCostMapData !== null && modelCostMapData !== undefined) { + if (typeof modelCostMapData == "object" && model in modelCostMapData) { + return modelCostMapData[model]["litellm_provider"]; + } + } + return "openai"; + }; + + const transformedModelData = useMemo(() => { + if (!rawModelDataResponse?.data || rawModelDataResponse.data.length === 0) { + return null; + } + const transformed = transformModelData(rawModelDataResponse, getProviderFromModel); + return transformed.data[0] || null; + }, [rawModelDataResponse, modelCostMapData]); + + // Keep modelData variable name for backwards compatibility + const modelData = transformedModelData; + const canEditModel = (userRole === "Admin" || modelData?.model_info?.created_by === userID) && modelData?.model_info?.db_model; const isAdmin = userRole === "Admin"; const isAutoRouter = modelData?.litellm_params?.auto_router_config != null; - const { data: modelsInfoData } = useModelsInfo(); - console.log("modelsInfoData, ", modelsInfoData); const usingExistingCredential = modelData?.litellm_params?.litellm_credential_name != null && modelData?.litellm_params?.litellm_credential_name != undefined; - console.log("usingExistingCredential, ", usingExistingCredential); - console.log("modelData.litellm_params.litellm_credential_name, ", modelData?.litellm_params?.litellm_credential_name); - console.log("tagsList, ", modelData.litellm_params?.tags); + // Initialize localModelData from modelData when available + useEffect(() => { + if (modelData && !localModelData) { + let processedModelData = modelData; + if (!processedModelData.litellm_model_name) { + processedModelData = { + ...processedModelData, + litellm_model_name: + processedModelData?.litellm_params?.litellm_model_name ?? + processedModelData?.litellm_params?.model ?? + processedModelData?.model_info?.key ?? + null, + }; + } + setLocalModelData(processedModelData); + + // Check if cache control is enabled + if (processedModelData?.litellm_params?.cache_control_injection_points) { + setShowCacheControl(true); + } + } + }, [modelData, localModelData]); useEffect(() => { const getExistingCredential = async () => { @@ -106,6 +150,8 @@ export default function ModelInfoView({ const getModelInfo = async () => { if (!accessToken) return; + // Only fetch if we don't have modelData yet + if (modelData) return; let modelInfoResponse = await modelInfoV1Call(accessToken, modelId); console.log("modelInfoResponse, ", modelInfoResponse); let specificModelData = modelInfoResponse.data[0]; @@ -270,6 +316,19 @@ export default function ModelInfoView({ } }; + // Show loading state + if (isLoadingModel) { + return ( +
    + + Back to Models + + Loading... +
    + ); + } + + // Show not found if model is not found if (!modelData) { return (
    @@ -353,6 +412,7 @@ export default function ModelInfoView({ } }; const isWildcardModel = modelData.litellm_model_name.includes("*"); + console.log("isWildcardModel, ", isWildcardModel); return (
    @@ -369,11 +429,10 @@ export default function ModelInfoView({ size="small" icon={copiedStates["model-id"] ? : } onClick={() => copyToClipboard(modelData.model_info.id, "model-id")} - className={`left-2 z-10 transition-all duration-200 ${ - copiedStates["model-id"] - ? "text-green-600 bg-green-50 border-green-200" - : "text-gray-500 hover:text-gray-700 hover:bg-gray-100" - }`} + className={`left-2 z-10 transition-all duration-200 ${copiedStates["model-id"] + ? "text-green-600 bg-green-50 border-green-200" + : "text-gray-500 hover:text-gray-700 hover:bg-gray-100" + }`} />
    @@ -484,10 +543,10 @@ export default function ModelInfoView({ Created At{" "} {modelData.model_info.created_at ? new Date(modelData.model_info.created_at).toLocaleDateString("en-US", { - month: "short", - day: "numeric", - year: "numeric", - }) + month: "short", + day: "numeric", + year: "numeric", + }) : "Not Set"}
    @@ -891,27 +950,19 @@ export default function ModelInfoView({ optionFilterProp="children" allowClear options={(() => { - const seen = new Set(); - return modelsInfoData?.data + const wildcardProvider = modelData.litellm_model_name.split("/")[0]; + return modelHubData?.data ?.filter((model: any) => { - const modelProvider = model.provider; - const wildcardProvider = modelData.litellm_model_name.split("/")[0]; + // Filter by provider to match the wildcard provider return ( - modelProvider === wildcardProvider && - model.model_name !== modelData.litellm_model_name + model.providers?.includes(wildcardProvider) && + model.model_group !== modelData.litellm_model_name ); }) - .filter((model: any) => { - if (seen.has(model.model_name)) { - return false; - } - seen.add(model.model_name); - return true; - }) .map((model: any) => ({ - value: model.model_name, - label: model.model_name, - })); + value: model.model_group, + label: model.model_group, + })) || []; })()} /> diff --git a/ui/litellm-dashboard/src/components/molecules/filter.test.tsx b/ui/litellm-dashboard/src/components/molecules/filter.test.tsx new file mode 100644 index 00000000000..bd06d110e95 --- /dev/null +++ b/ui/litellm-dashboard/src/components/molecules/filter.test.tsx @@ -0,0 +1,127 @@ +import { screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { renderWithProviders } from "../../../tests/test-utils"; +import FilterComponent, { FilterOption } from "./filter"; + +describe("FilterComponent", () => { + const mockOnApplyFilters = vi.fn(); + const mockOnResetFilters = vi.fn(); + + const defaultOptions: FilterOption[] = [ + { + name: "teamId", + label: "Team ID", + options: [ + { label: "Team 1", value: "team1" }, + { label: "Team 2", value: "team2" }, + ], + }, + { + name: "status", + label: "Status", + options: [ + { label: "Active", value: "active" }, + { label: "Inactive", value: "inactive" }, + ], + }, + { + name: "userId", + label: "User ID", + }, + ]; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("should render", () => { + renderWithProviders( + , + ); + expect(screen.getByRole("button", { name: "Filters" })).toBeInTheDocument(); + }); + + it("should display custom button label", () => { + renderWithProviders( + , + ); + expect(screen.getByRole("button", { name: "Custom Filters" })).toBeInTheDocument(); + }); + + it("should call onResetFilters when reset button is clicked", async () => { + const user = userEvent.setup(); + renderWithProviders( + , + ); + + const resetButton = screen.getByRole("button", { name: "Reset Filters" }); + await user.click(resetButton); + + await waitFor(() => { + expect(mockOnResetFilters).toHaveBeenCalledTimes(1); + }); + }); + + it("should render filters in correct order", async () => { + const user = userEvent.setup(); + const options: FilterOption[] = [ + { name: "model", label: "Model" }, + { name: "teamId", label: "Team ID" }, + { name: "status", label: "Status" }, + { name: "userId", label: "User ID" }, + ]; + + renderWithProviders( + , + ); + + const filterButton = screen.getByRole("button", { name: "Filters" }); + await user.click(filterButton); + + await waitFor(() => { + const labels = screen.getAllByText(/^(Team ID|Status|User ID|Model)$/); + expect(labels[0]).toHaveTextContent("Team ID"); + expect(labels[1]).toHaveTextContent("Status"); + }); + }); + + it("should handle input filter changes", async () => { + const user = userEvent.setup(); + renderWithProviders( + , + ); + + const filterButton = screen.getByRole("button", { name: "Filters" }); + await user.click(filterButton); + + const userIdInput = screen.getByPlaceholderText("Enter User ID..."); + await user.type(userIdInput, "user123"); + + await waitFor(() => { + expect(mockOnApplyFilters).toHaveBeenCalledWith({ userId: "user123" }); + }); + }); +}); diff --git a/ui/litellm-dashboard/src/components/molecules/filter.tsx b/ui/litellm-dashboard/src/components/molecules/filter.tsx index d7c40ae0399..6c46392410d 100644 --- a/ui/litellm-dashboard/src/components/molecules/filter.tsx +++ b/ui/litellm-dashboard/src/components/molecules/filter.tsx @@ -130,6 +130,7 @@ const FilterComponent: React.FC = ({ "User ID", "End User", "Error Code", + "Error Message", "Key Hash", "Model", ]; diff --git a/ui/litellm-dashboard/src/components/molecules/models/columns.tsx b/ui/litellm-dashboard/src/components/molecules/models/columns.tsx index 43813852f8f..1fc08e502ac 100644 --- a/ui/litellm-dashboard/src/components/molecules/models/columns.tsx +++ b/ui/litellm-dashboard/src/components/molecules/models/columns.tsx @@ -17,297 +17,300 @@ export const columns = ( expandedRows: Set, setExpandedRows: (expandedRows: Set) => void, ): ColumnDef[] => [ - { - header: () => Model ID, - accessorKey: "model_info.id", - cell: ({ row }) => { - const model = row.original; - return ( - -
    setSelectedModelId(model.model_info.id)} - > - {model.model_info.id} -
    -
    - ); - }, - }, - { - header: () => Model Information, - accessorKey: "model_name", - size: 250, // Fixed column width - cell: ({ row }) => { - const model = row.original; - const displayName = getDisplayModelName(row.original) || "-"; - const tooltipContent = ( -
    -
    - Provider: {model.provider || "-"} -
    -
    - Public Model Name: {displayName} -
    -
    - LiteLLM Model Name: {model.litellm_model_name || "-"} -
    -
    - ); - - return ( - -
    - {/* Provider Icon */} -
    - {model.provider ? ( - - ) : ( -
    -
    - )} -
    - - {/* Model Names Container */} -
    - {/* Public Model Name */} -
    {displayName}
    - {/* LiteLLM Model Name */} -
    - {model.litellm_model_name || "-"} -
    -
    -
    -
    - ); - }, - }, - { - header: () => Credentials, - accessorKey: "litellm_credential_name", - size: 180, // Fixed column width - cell: ({ row }) => { - const model = row.original; - const credentialName = model.litellm_params?.litellm_credential_name; - - return credentialName ? ( - -
    - - - {credentialName} - -
    -
    - ) : ( -
    - - No credentials -
    - ); - }, - }, - { - header: () => Created By, - accessorKey: "model_info.created_by", - sortingFn: "datetime", - size: 160, // Fixed column width - cell: ({ row }) => { - const model = row.original; - const isConfigModel = !model.model_info?.db_model; - const createdBy = model.model_info.created_by; - const createdAt = model.model_info.created_at ? new Date(model.model_info.created_at).toLocaleDateString() : null; - - return ( -
    - {/* Created By - Primary */} -
    - {isConfigModel ? "Defined in config" : createdBy || "Unknown"} -
    - {/* Created At - Secondary */} -
    - {isConfigModel ? "-" : createdAt || "Unknown date"} -
    -
    - ); - }, - }, - { - header: () => Updated At, - accessorKey: "model_info.updated_at", - sortingFn: "datetime", - cell: ({ row }) => { - const model = row.original; - return ( - - {model.model_info.updated_at ? new Date(model.model_info.updated_at).toLocaleDateString() : "-"} - - ); - }, - }, - { - header: () => Costs, - accessorKey: "input_cost", - size: 120, // Fixed column width - cell: ({ row }) => { - const model = row.original; - const inputCost = model.input_cost; - const outputCost = model.output_cost; - - // If both costs are missing or undefined, show "-" - if (!inputCost && !outputCost) { + { + header: () => Model ID, + accessorKey: "model_info.id", + enableSorting: false, + cell: ({ row }) => { + const model = row.original; return ( -
    - - + +
    setSelectedModelId(model.model_info.id)} + > + {model.model_info.id} +
    +
    + ); + }, + }, + { + header: () => Model Information, + accessorKey: "model_name", + size: 250, // Fixed column width + cell: ({ row }) => { + const model = row.original; + const displayName = getDisplayModelName(row.original) || "-"; + const tooltipContent = ( +
    +
    + Provider: {model.provider || "-"} +
    +
    + Public Model Name: {displayName} +
    +
    + LiteLLM Model Name: {model.litellm_model_name || "-"} +
    ); - } - return ( - -
    - {/* Input Cost - Primary */} - {inputCost &&
    In: ${inputCost}
    } - {/* Output Cost - Secondary */} - {outputCost &&
    Out: ${outputCost}
    } -
    -
    - ); - }, - }, - { - header: () => Team ID, - accessorKey: "model_info.team_id", - cell: ({ row }) => { - const model = row.original; - return model.model_info.team_id ? ( -
    - - + return ( + +
    + {/* Provider Icon */} +
    + {model.provider ? ( + + ) : ( +
    -
    + )} +
    + + {/* Model Names Container */} +
    + {/* Public Model Name */} +
    {displayName}
    + {/* LiteLLM Model Name */} +
    + {model.litellm_model_name || "-"} +
    +
    +
    -
    - ) : ( - "-" - ); + ); + }, }, - }, - { - header: () => Model Access Group, - accessorKey: "model_info.model_access_group", - enableSorting: false, - cell: ({ row }) => { - const model = row.original; - const accessGroups = model.model_info.access_groups; + { + header: () => Credentials, + accessorKey: "litellm_credential_name", + enableSorting: false, + size: 180, // Fixed column width + cell: ({ row }) => { + const model = row.original; + const credentialName = model.litellm_params?.litellm_credential_name; - if (!accessGroups || accessGroups.length === 0) { - return "-"; - } + return credentialName ? ( + +
    + + + {credentialName} + +
    +
    + ) : ( +
    + + No credentials +
    + ); + }, + }, + { + header: () => Created By, + accessorKey: "model_info.created_by", + sortingFn: "datetime", + size: 160, // Fixed column width + cell: ({ row }) => { + const model = row.original; + const isConfigModel = !model.model_info?.db_model; + const createdBy = model.model_info.created_by; + const createdAt = model.model_info.created_at ? new Date(model.model_info.created_at).toLocaleDateString() : null; - const modelId = model.model_info.id; - const isExpanded = expandedRows.has(modelId); - const shouldShowExpandButton = accessGroups.length > 1; - - const toggleExpanded = () => { - const newExpanded = new Set(expandedRows); - if (isExpanded) { - newExpanded.delete(modelId); - } else { - newExpanded.add(modelId); - } - setExpandedRows(newExpanded); - }; - - return ( -
    - - {accessGroups[0]} - - - {(isExpanded || (!shouldShowExpandButton && accessGroups.length === 2)) && - accessGroups.slice(1).map((group: string, index: number) => ( - - {group} - - ))} - - {shouldShowExpandButton && ( - - )} -
    - ); + {isConfigModel ? "Defined in config" : createdBy || "Unknown"} +
    + {/* Created At - Secondary */} +
    + {isConfigModel ? "-" : createdAt || "Unknown date"} +
    +
    + ); + }, }, - }, - { - header: () => Status, - accessorKey: "model_info.db_model", - cell: ({ row }) => { - const model = row.original; - return ( -
    Updated At, + accessorKey: "model_info.updated_at", + sortingFn: "datetime", + cell: ({ row }) => { + const model = row.original; + return ( + + {model.model_info.updated_at ? new Date(model.model_info.updated_at).toLocaleDateString() : "-"} + + ); + }, + }, + { + header: () => Costs, + accessorKey: "input_cost", + size: 120, // Fixed column width + cell: ({ row }) => { + const model = row.original; + const inputCost = model.input_cost; + const outputCost = model.output_cost; + + // If both costs are missing or undefined, show "-" + if (!inputCost && !outputCost) { + return ( +
    + - +
    + ); + } + + return ( + +
    + {/* Input Cost - Primary */} + {inputCost &&
    In: ${inputCost}
    } + {/* Output Cost - Secondary */} + {outputCost &&
    Out: ${outputCost}
    } +
    +
    + ); + }, + }, + { + header: () => Team ID, + accessorKey: "model_info.team_id", + enableSorting: false, + cell: ({ row }) => { + const model = row.original; + return model.model_info.team_id ? ( +
    + + + +
    + ) : ( + "-" + ); + }, + }, + { + header: () => Model Access Group, + accessorKey: "model_info.model_access_group", + enableSorting: false, + cell: ({ row }) => { + const model = row.original; + const accessGroups = model.model_info.access_groups; + + if (!accessGroups || accessGroups.length === 0) { + return "-"; + } + + const modelId = model.model_info.id; + const isExpanded = expandedRows.has(modelId); + const shouldShowExpandButton = accessGroups.length > 1; + + const toggleExpanded = () => { + const newExpanded = new Set(expandedRows); + if (isExpanded) { + newExpanded.delete(modelId); + } else { + newExpanded.add(modelId); + } + setExpandedRows(newExpanded); + }; + + return ( +
    + + {accessGroups[0]} + + + {(isExpanded || (!shouldShowExpandButton && accessGroups.length === 2)) && + accessGroups.slice(1).map((group: string, index: number) => ( + + {group} + + ))} + + {shouldShowExpandButton && ( + + )} +
    + ); + }, + }, + { + header: () => Status, + accessorKey: "model_info.db_model", + cell: ({ row }) => { + const model = row.original; + return ( +
    - {model.model_info.db_model ? "DB Model" : "Config Model"} -
    - ); + > + {model.model_info.db_model ? "DB Model" : "Config Model"} +
    + ); + }, }, - }, - { - id: "actions", - header: () => Actions, - cell: ({ row }) => { - const model = row.original; - const canEditModel = userRole === "Admin" || model.model_info?.created_by === userID; - const isConfigModel = !model.model_info?.db_model; - return ( -
    - {isConfigModel ? ( - - - - ) : ( - - { - if (canEditModel) { - setSelectedModelId(model.model_info.id); - } - }} - className={!canEditModel ? "opacity-50 cursor-not-allowed" : "cursor-pointer hover:text-red-600"} - /> - - )} -
    - ); + { + id: "actions", + header: () => Actions, + cell: ({ row }) => { + const model = row.original; + const canEditModel = userRole === "Admin" || model.model_info?.created_by === userID; + const isConfigModel = !model.model_info?.db_model; + return ( +
    + {isConfigModel ? ( + + + + ) : ( + + { + if (canEditModel) { + setSelectedModelId(model.model_info.id); + } + }} + className={!canEditModel ? "opacity-50 cursor-not-allowed" : "cursor-pointer hover:text-red-600"} + /> + + )} +
    + ); + }, }, - }, -]; + ]; diff --git a/ui/litellm-dashboard/src/components/navbar.test.tsx b/ui/litellm-dashboard/src/components/navbar.test.tsx index 5d3c7254ff9..125187e2340 100644 --- a/ui/litellm-dashboard/src/components/navbar.test.tsx +++ b/ui/litellm-dashboard/src/components/navbar.test.tsx @@ -12,10 +12,30 @@ vi.mock("@/utils/proxyUtils", () => ({ fetchProxySettings: vi.fn(), })); +// Mock CommunityEngagementButtons component +vi.mock("./Navbar/CommunityEngagementButtons/CommunityEngagementButtons", () => ({ + CommunityEngagementButtons: () => ( + + ), +})); + // Create mock functions that can be controlled in tests let mockUseThemeImpl = () => ({ logoUrl: null as string | null }); let mockUseHealthReadinessImpl = () => ({ data: null as any }); -let mockGetLocalStorageItemImpl = () => null as string | null; +let mockGetLocalStorageItemImpl = (key: string) => null as string | null; +let mockUseAuthorizedImpl = () => ({ + userId: "test-user", + userEmail: "test@example.com", + userRole: "Admin", + premiumUser: false, +}); vi.mock("@/contexts/ThemeContext", () => ({ useTheme: () => mockUseThemeImpl(), @@ -25,8 +45,13 @@ vi.mock("@/app/(dashboard)/hooks/healthReadiness/useHealthReadiness", () => ({ useHealthReadiness: () => mockUseHealthReadinessImpl(), })); +vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({ + default: () => mockUseAuthorizedImpl(), +})); + vi.mock("@/utils/localStorageUtils", () => ({ - getLocalStorageItem: () => mockGetLocalStorageItemImpl(), + LOCAL_STORAGE_EVENT: "local-storage-change", + getLocalStorageItem: (key: string) => mockGetLocalStorageItemImpl(key), setLocalStorageItem: vi.fn(), removeLocalStorageItem: vi.fn(), emitLocalStorageChange: vi.fn(), @@ -52,6 +77,8 @@ describe("Navbar", () => { setProxySettings: vi.fn(), accessToken: "test-token", isPublicPage: false, + isDarkMode: false, + toggleDarkMode: vi.fn(), }; it("should render without crashing", () => { @@ -61,26 +88,6 @@ describe("Navbar", () => { expect(screen.getByText("User")).toBeInTheDocument(); }); - it("should render Join Slack button with correct link", () => { - renderWithProviders(); - - const joinSlackLink = screen.getByRole("link", { name: /join slack/i }); - expect(joinSlackLink).toBeInTheDocument(); - expect(joinSlackLink).toHaveAttribute("href", "https://www.litellm.ai/support"); - expect(joinSlackLink).toHaveAttribute("target", "_blank"); - expect(joinSlackLink).toHaveAttribute("rel", "noopener noreferrer"); - }); - - it("should render Star us on GitHub button with correct link", () => { - renderWithProviders(); - - const starOnGithubLink = screen.getByRole("link", { name: /star us on github/i }); - expect(starOnGithubLink).toBeInTheDocument(); - expect(starOnGithubLink).toHaveAttribute("href", "https://github.com/BerriAI/litellm"); - expect(starOnGithubLink).toHaveAttribute("target", "_blank"); - expect(starOnGithubLink).toHaveAttribute("rel", "noopener noreferrer"); - }); - it("should display user information in dropdown", async () => { const user = userEvent.setup(); renderWithProviders(); @@ -115,14 +122,27 @@ describe("Navbar", () => { it("should show premium user badge when premiumUser is true", async () => { const user = userEvent.setup(); - const premiumProps = { ...defaultProps, premiumUser: true }; - renderWithProviders(); + mockUseAuthorizedImpl = () => ({ + userId: "test-user", + userEmail: "test@example.com", + userRole: "Admin", + premiumUser: true, + }); + renderWithProviders(); await user.click(screen.getByText("User")); await waitFor(() => { expect(screen.getByText("Premium")).toBeInTheDocument(); }); + + // Reset mock + mockUseAuthorizedImpl = () => ({ + userId: "test-user", + userEmail: "test@example.com", + userRole: "Admin", + premiumUser: false, + }); }); it("should show version badge when health data contains version", () => { @@ -159,7 +179,10 @@ describe("Navbar", () => { const user = userEvent.setup(); // Initially disabled - mockGetLocalStorageItemImpl = () => "false"; + mockGetLocalStorageItemImpl = (key: string) => { + if (key === "disableShowNewBadge") return "false"; + return null; + }; renderWithProviders(); @@ -178,6 +201,9 @@ describe("Navbar", () => { const localStorageUtils = vi.mocked(await import("@/utils/localStorageUtils")); expect(localStorageUtils.setLocalStorageItem).toHaveBeenCalledWith("disableShowNewBadge", "true"); expect(localStorageUtils.emitLocalStorageChange).toHaveBeenCalledWith("disableShowNewBadge"); + + // Reset mock + mockGetLocalStorageItemImpl = (key: string) => null; }); it("should handle logout functionality", async () => { @@ -198,4 +224,11 @@ describe("Navbar", () => { expect(cookieUtils.clearTokenCookies).toHaveBeenCalled(); expect(window.location.href).toBe(""); }); + + it("should not render dark mode toggle slider", () => { + renderWithProviders(); + + // DO NOT RENDER THIS UNTIL ALL COMPONENTS ARE CONFIRMED TO SUPPORT DARK MODE STYLES. IT IS AN ISSUE IF THIS TEST FAILS. + expect(screen.queryByTestId("dark-mode-toggle")).not.toBeInTheDocument(); + }); }); diff --git a/ui/litellm-dashboard/src/components/navbar.tsx b/ui/litellm-dashboard/src/components/navbar.tsx index 6dac073b3a6..2ffa0632f27 100644 --- a/ui/litellm-dashboard/src/components/navbar.tsx +++ b/ui/litellm-dashboard/src/components/navbar.tsx @@ -2,28 +2,18 @@ import { useHealthReadiness } from "@/app/(dashboard)/hooks/healthReadiness/useH import { getProxyBaseUrl } from "@/components/networking"; import { useTheme } from "@/contexts/ThemeContext"; import { clearTokenCookies } from "@/utils/cookieUtils"; -import { - emitLocalStorageChange, - getLocalStorageItem, - removeLocalStorageItem, - setLocalStorageItem, -} from "@/utils/localStorageUtils"; import { fetchProxySettings } from "@/utils/proxyUtils"; import { - CrownOutlined, - GithubOutlined, - LogoutOutlined, - MailOutlined, MenuFoldOutlined, MenuUnfoldOutlined, - SafetyOutlined, - SlackOutlined, - UserOutlined, + MoonOutlined, + SunOutlined, } from "@ant-design/icons"; -import type { MenuProps } from "antd"; -import { Button, Dropdown, Switch, Tooltip } from "antd"; +import { Switch, Tag } from "antd"; import Link from "next/link"; import React, { useEffect, useState } from "react"; +import { CommunityEngagementButtons } from "./Navbar/CommunityEngagementButtons/CommunityEngagementButtons"; +import UserDropdown from "./Navbar/UserDropdown/UserDropdown"; interface NavbarProps { userID: string | null; @@ -36,6 +26,8 @@ interface NavbarProps { isPublicPage: boolean; sidebarCollapsed?: boolean; onToggleSidebar?: () => void; + isDarkMode: boolean; + toggleDarkMode: () => void; } const Navbar: React.FC = ({ @@ -49,11 +41,11 @@ const Navbar: React.FC = ({ isPublicPage = false, sidebarCollapsed = false, onToggleSidebar, + isDarkMode, + toggleDarkMode }) => { const baseUrl = getProxyBaseUrl(); - console.log("baseUrl", baseUrl); const [logoutUrl, setLogoutUrl] = useState(""); - const [disableShowNewBadge, setDisableShowNewBadge] = useState(false); const { logoUrl } = useTheme(); const { data: healthData } = useHealthReadiness(); const version = healthData?.litellm_version; @@ -75,11 +67,6 @@ const Navbar: React.FC = ({ initializeProxySettings(); }, [accessToken]); - useEffect(() => { - const storedValue = getLocalStorageItem("disableShowNewBadge"); - setDisableShowNewBadge(storedValue === "true"); - }, []); - useEffect(() => { setLogoutUrl(proxySettings?.PROXY_LOGOUT_URL || ""); }, [proxySettings]); @@ -89,84 +76,6 @@ const Navbar: React.FC = ({ window.location.href = logoutUrl; }; - const userItems: MenuProps["items"] = [ - { - key: "user-info", - // Prevent dropdown from closing when interacting with the toggle - onClick: (info) => info.domEvent?.stopPropagation(), - label: ( -
    -
    -
    - - {userID} -
    - {premiumUser ? ( - -
    - - Premium -
    -
    - ) : ( - -
    - - Standard -
    -
    - )} -
    -
    -
    - - Role - {userRole} -
    -
    - - Email - - {userEmail || "Unknown"} - -
    -
    e.stopPropagation()} - > - Hide New Feature Indicators - { - setDisableShowNewBadge(checked); - if (checked) { - setLocalStorageItem("disableShowNewBadge", "true"); - emitLocalStorageChange("disableShowNewBadge"); - } else { - removeLocalStorageItem("disableShowNewBadge"); - emitLocalStorageChange("disableShowNewBadge"); - } - }} - aria-label="Toggle hide new feature indicators" - /> -
    -
    -
    - ), - }, - { - key: "logout", - label: ( -
    - - Logout -
    - ), - }, - ]; - return (

    r3Jp0Uc^q-SbK3x%nZpgSinP)CI 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%0Av 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 O0000 void; @@ -7,7 +12,44 @@ interface SidebarProviderProps { } const SidebarProvider = ({ setPage, defaultSelectedKey, sidebarCollapsed }: SidebarProviderProps) => { - return ; + const { accessToken } = useAuthorized(); + const [enabledPagesInternalUsers, setEnabledPagesInternalUsers] = useState(null); + + useEffect(() => { + const fetchUISettings = async () => { + if (!accessToken) { + console.log("[SidebarProvider] No access token, skipping UI settings fetch"); + return; + } + + try { + console.log("[SidebarProvider] Fetching UI settings from /get/ui_settings"); + const settings = await getUISettings(accessToken); + console.log("[SidebarProvider] UI settings response:", settings); + + // API returns 'values' not 'settings' + if (settings?.values?.enabled_ui_pages_internal_users !== undefined) { + console.log("[SidebarProvider] Setting enabled pages:", settings.values.enabled_ui_pages_internal_users); + setEnabledPagesInternalUsers(settings.values.enabled_ui_pages_internal_users); + } else { + console.log("[SidebarProvider] No enabled_ui_pages_internal_users in response (all pages visible by default)"); + } + } catch (error) { + console.error("[SidebarProvider] Failed to fetch UI settings:", error); + } + }; + + fetchUISettings(); + }, [accessToken]); + + return ( + + ); }; export default SidebarProvider; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.test.ts index c6629e5396a..4985206092f 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.test.ts @@ -101,7 +101,12 @@ describe("useModelsInfo", () => { "test-user-id", "Admin", 1, - 50 + 50, + undefined, + undefined, + undefined, + undefined, + undefined ); expect(modelInfoCall).toHaveBeenCalledTimes(1); }); @@ -120,7 +125,12 @@ describe("useModelsInfo", () => { "test-user-id", "Admin", 2, - 25 + 25, + undefined, + undefined, + undefined, + undefined, + undefined ); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.ts index 1dbd79eacf9..c57de675e0e 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/models/useModels.ts @@ -27,7 +27,7 @@ const modelHubKeys = createQueryKeys("modelHub"); const allProxyModelsKeys = createQueryKeys("allProxyModels"); const selectedTeamModelsKeys = createQueryKeys("selectedTeamModels"); -export const useModelsInfo = (page: number = 1, size: number = 50) => { +export const useModelsInfo = (page: number = 1, size: number = 50, search?: string, modelId?: string, teamId?: string, sortBy?: string, sortOrder?: string) => { const { accessToken, userId, userRole } = useAuthorized(); return useQuery({ queryKey: modelKeys.list({ @@ -36,9 +36,14 @@ export const useModelsInfo = (page: number = 1, size: number = 50) => { ...(userRole && { userRole }), page, size, + ...(search && { search }), + ...(modelId && { modelId }), + ...(teamId && { teamId }), + ...(sortBy && { sortBy }), + ...(sortOrder && { sortOrder }), }, }), - queryFn: async () => await modelInfoCall(accessToken!, userId!, userRole!, page, size), + queryFn: async () => await modelInfoCall(accessToken!, userId!, userRole!, page, size, search, modelId, teamId, sortBy, sortOrder), enabled: Boolean(accessToken && userId && userRole), }); }; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/proxyConfig/useProxyConfig.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/proxyConfig/useProxyConfig.test.ts new file mode 100644 index 00000000000..a8ce55d2745 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/proxyConfig/useProxyConfig.test.ts @@ -0,0 +1,554 @@ +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; +import { renderHook, waitFor } from "@testing-library/react"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import React, { ReactNode } from "react"; +import { + useProxyConfig, + useDeleteProxyConfigField, + getProxyConfigCall, + deleteProxyConfigFieldCall, + ConfigType, + GeneralSettingsFieldName, + type ProxyConfigResponse, + type DeleteProxyConfigFieldRequest, + type DeleteProxyConfigFieldResponse, +} from "./useProxyConfig"; + +const { + mockProxyBaseUrl, + mockAccessToken, + mockHeaderName, + mockProxyConfigResponse, + mockDeleteResponse, + mockUseAuthorized, + mockGetGlobalLitellmHeaderName, + mockDeriveErrorMessage, + mockHandleError, +} = vi.hoisted(() => { + const mockProxyBaseUrl = "https://proxy.example.com"; + const mockAccessToken = "test-access-token"; + const mockHeaderName = "X-LiteLLM-API-Key"; + + const mockProxyConfigResponse: ProxyConfigResponse = [ + { + field_name: "maximum_spend_logs_retention_period", + field_type: "int", + field_description: "Maximum retention period for spend logs", + field_value: 30, + stored_in_db: true, + field_default_value: 7, + premium_field: false, + nested_fields: null, + }, + { + field_name: "another_field", + field_type: "string", + field_description: "Another config field", + field_value: "test-value", + stored_in_db: false, + field_default_value: "default-value", + premium_field: true, + nested_fields: [ + { + field_name: "nested_field", + field_type: "string", + field_description: "Nested field description", + field_default_value: "nested-default", + stored_in_db: true, + }, + ], + }, + ]; + + const mockDeleteResponse: DeleteProxyConfigFieldResponse = { + message: "Field deleted successfully", + }; + + const mockUseAuthorized = vi.fn(); + const mockGetGlobalLitellmHeaderName = vi.fn(() => mockHeaderName); + const mockDeriveErrorMessage = vi.fn((errorData: any) => { + if (typeof errorData === "string") return errorData; + return errorData?.message || errorData?.error || "An error occurred"; + }); + const mockHandleError = vi.fn(); + + return { + mockProxyBaseUrl, + mockAccessToken, + mockHeaderName, + mockProxyConfigResponse, + mockDeleteResponse, + mockUseAuthorized, + mockGetGlobalLitellmHeaderName, + mockDeriveErrorMessage, + mockHandleError, + }; +}); + +vi.mock("../useAuthorized", () => ({ + default: () => mockUseAuthorized(), +})); + +vi.mock("@/components/networking", () => ({ + proxyBaseUrl: mockProxyBaseUrl, + getGlobalLitellmHeaderName: mockGetGlobalLitellmHeaderName, + deriveErrorMessage: mockDeriveErrorMessage, + handleError: mockHandleError, +})); + +vi.mock("../common/queryKeysFactory", () => ({ + createQueryKeys: vi.fn((resource: string) => ({ + all: [resource], + lists: () => [resource, "list"], + list: (params?: any) => [resource, "list", { params }], + details: () => [resource, "detail"], + detail: (uid: string) => [resource, "detail", uid], + })), +})); + +describe("useProxyConfig", () => { + let queryClient: QueryClient; + let fetchSpy: ReturnType; + + beforeEach(() => { + queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, + }); + + vi.clearAllMocks(); + + mockUseAuthorized.mockReturnValue({ + accessToken: mockAccessToken, + userId: "test-user-id", + userRole: "Admin", + token: "test-token", + userEmail: "test@example.com", + premiumUser: false, + disabledPersonalKeyCreation: null, + showSSOBanner: false, + }); + + fetchSpy = vi.fn(); + global.fetch = fetchSpy; + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + const wrapper = ({ children }: { children: ReactNode }) => + React.createElement(QueryClientProvider, { client: queryClient }, children); + + it("should render successfully", () => { + (fetchSpy as any).mockResolvedValue({ + ok: true, + json: async () => mockProxyConfigResponse, + }); + + const { result } = renderHook(() => useProxyConfig(ConfigType.GENERAL_SETTINGS), { wrapper }); + + expect(result.current).toBeDefined(); + expect(result.current.isLoading).toBe(true); + }); + + it("should return proxy config data when query is successful", async () => { + (fetchSpy as any).mockResolvedValue({ + ok: true, + json: async () => mockProxyConfigResponse, + }); + + const { result } = renderHook(() => useProxyConfig(ConfigType.GENERAL_SETTINGS), { wrapper }); + + expect(result.current.isLoading).toBe(true); + expect(result.current.data).toBeUndefined(); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + expect(result.current.isSuccess).toBe(true); + }); + + expect(result.current.data).toEqual(mockProxyConfigResponse); + expect(result.current.error).toBeNull(); + expect(fetchSpy).toHaveBeenCalledWith( + `${mockProxyBaseUrl}/config/list?config_type=${ConfigType.GENERAL_SETTINGS}`, + { + method: "GET", + headers: { + [mockHeaderName]: `Bearer ${mockAccessToken}`, + "Content-Type": "application/json", + }, + }, + ); + expect(fetchSpy).toHaveBeenCalledTimes(1); + }); + + it("should handle error when API call fails", async () => { + const errorMessage = "Failed to fetch proxy config"; + const errorResponse = { message: errorMessage }; + + (fetchSpy as any).mockResolvedValue({ + ok: false, + json: async () => errorResponse, + }); + + const { result } = renderHook(() => useProxyConfig(ConfigType.GENERAL_SETTINGS), { wrapper }); + + expect(result.current.isLoading).toBe(true); + + await waitFor(() => { + expect(result.current.isLoading).toBe(false); + expect(result.current.isError).toBe(true); + }); + + expect(result.current.error).toBeDefined(); + expect(result.current.data).toBeUndefined(); + }); + + it("should not execute query when accessToken is missing", async () => { + mockUseAuthorized.mockReturnValue({ + accessToken: null, + userId: "test-user-id", + userRole: "Admin", + token: null, + userEmail: "test@example.com", + premiumUser: false, + disabledPersonalKeyCreation: null, + showSSOBanner: false, + }); + + const { result } = renderHook(() => useProxyConfig(ConfigType.GENERAL_SETTINGS), { wrapper }); + + expect(result.current.isLoading).toBe(false); + expect(result.current.data).toBeUndefined(); + expect(result.current.isFetched).toBe(false); + + expect(fetchSpy).not.toHaveBeenCalled(); + }); + + it("should use correct query key with config type filter", async () => { + (fetchSpy as any).mockResolvedValue({ + ok: true, + json: async () => mockProxyConfigResponse, + }); + + const { result } = renderHook(() => useProxyConfig(ConfigType.GENERAL_SETTINGS), { wrapper }); + + await waitFor(() => { + expect(result.current.isSuccess).toBe(true); + }); + + expect(result.current.data).toEqual(mockProxyConfigResponse); + }); + + it("should handle network errors", async () => { + const networkError = new Error("Network error"); + (fetchSpy as any).mockRejectedValue(networkError); + + const { result } = renderHook(() => useProxyConfig(ConfigType.GENERAL_SETTINGS), { wrapper }); + + await waitFor(() => { + expect(result.current.isError).toBe(true); + }); + + expect(result.current.error).toBeDefined(); + expect(result.current.data).toBeUndefined(); + }); + + it("should handle empty config response", async () => { + (fetchSpy as any).mockResolvedValue({ + ok: true, + json: async () => [], + }); + + const { result } = renderHook(() => useProxyConfig(ConfigType.GENERAL_SETTINGS), { wrapper }); + + await waitFor(() => { + expect(result.current.isSuccess).toBe(true); + }); + + expect(result.current.data).toEqual([]); + }); +}); + +describe("useDeleteProxyConfigField", () => { + let queryClient: QueryClient; + let fetchSpy: ReturnType; + + beforeEach(() => { + queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + mutations: { + retry: false, + }, + }, + }); + + vi.clearAllMocks(); + + mockUseAuthorized.mockReturnValue({ + accessToken: mockAccessToken, + userId: "test-user-id", + userRole: "Admin", + token: "test-token", + userEmail: "test@example.com", + premiumUser: false, + disabledPersonalKeyCreation: null, + showSSOBanner: false, + }); + + fetchSpy = vi.fn(); + global.fetch = fetchSpy; + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + const wrapper = ({ children }: { children: ReactNode }) => + React.createElement(QueryClientProvider, { client: queryClient }, children); + + it("should render successfully", () => { + const { result } = renderHook(() => useDeleteProxyConfigField(), { wrapper }); + + expect(result.current).toBeDefined(); + expect(result.current.isIdle).toBe(true); + }); + + it("should successfully delete a proxy config field", async () => { + (fetchSpy as any).mockResolvedValue({ + ok: true, + json: async () => mockDeleteResponse, + }); + + const { result } = renderHook(() => useDeleteProxyConfigField(), { wrapper }); + + const deleteRequest: DeleteProxyConfigFieldRequest = { + config_type: ConfigType.GENERAL_SETTINGS, + field_name: GeneralSettingsFieldName.MAXIMUM_SPEND_LOGS_RETENTION_PERIOD, + }; + + result.current.mutate(deleteRequest); + + await waitFor(() => { + expect(result.current.isSuccess).toBe(true); + }); + + expect(result.current.data).toEqual(mockDeleteResponse); + expect(fetchSpy).toHaveBeenCalledWith(`${mockProxyBaseUrl}/config/field/delete`, { + method: "POST", + headers: { + [mockHeaderName]: `Bearer ${mockAccessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(deleteRequest), + }); + expect(fetchSpy).toHaveBeenCalledTimes(1); + }); + + it("should handle error when delete request fails", async () => { + const errorMessage = "Failed to delete field"; + const errorResponse = { message: errorMessage }; + + (fetchSpy as any).mockResolvedValue({ + ok: false, + json: async () => errorResponse, + }); + + const { result } = renderHook(() => useDeleteProxyConfigField(), { wrapper }); + + const deleteRequest: DeleteProxyConfigFieldRequest = { + config_type: ConfigType.GENERAL_SETTINGS, + field_name: GeneralSettingsFieldName.MAXIMUM_SPEND_LOGS_RETENTION_PERIOD, + }; + + result.current.mutate(deleteRequest); + + await waitFor(() => { + expect(result.current.isError).toBe(true); + }); + + expect(result.current.error).toBeDefined(); + expect(result.current.data).toBeUndefined(); + }); + + it("should throw error when accessToken is missing", async () => { + mockUseAuthorized.mockReturnValue({ + accessToken: null, + userId: "test-user-id", + userRole: "Admin", + token: null, + userEmail: "test@example.com", + premiumUser: false, + disabledPersonalKeyCreation: null, + showSSOBanner: false, + }); + + const { result } = renderHook(() => useDeleteProxyConfigField(), { wrapper }); + + const deleteRequest: DeleteProxyConfigFieldRequest = { + config_type: ConfigType.GENERAL_SETTINGS, + field_name: GeneralSettingsFieldName.MAXIMUM_SPEND_LOGS_RETENTION_PERIOD, + }; + + result.current.mutate(deleteRequest); + + await waitFor(() => { + expect(result.current.isError).toBe(true); + }); + + expect(result.current.error?.message).toBe("Access token is required"); + expect(fetchSpy).not.toHaveBeenCalled(); + }); + + it("should handle network errors during delete", async () => { + const networkError = new Error("Network error"); + (fetchSpy as any).mockRejectedValue(networkError); + + const { result } = renderHook(() => useDeleteProxyConfigField(), { wrapper }); + + const deleteRequest: DeleteProxyConfigFieldRequest = { + config_type: ConfigType.GENERAL_SETTINGS, + field_name: GeneralSettingsFieldName.MAXIMUM_SPEND_LOGS_RETENTION_PERIOD, + }; + + result.current.mutate(deleteRequest); + + await waitFor(() => { + expect(result.current.isError).toBe(true); + }); + + expect(result.current.error).toBeDefined(); + }); +}); + +describe("getProxyConfigCall", () => { + let fetchSpy: ReturnType; + let consoleErrorSpy: ReturnType; + + beforeEach(() => { + fetchSpy = vi.fn(); + global.fetch = fetchSpy; + consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("should successfully fetch proxy config", async () => { + (fetchSpy as any).mockResolvedValue({ + ok: true, + json: async () => mockProxyConfigResponse, + }); + + const result = await getProxyConfigCall(mockAccessToken, ConfigType.GENERAL_SETTINGS); + + expect(result).toEqual(mockProxyConfigResponse); + expect(fetchSpy).toHaveBeenCalledWith( + `${mockProxyBaseUrl}/config/list?config_type=${ConfigType.GENERAL_SETTINGS}`, + { + method: "GET", + headers: { + [mockHeaderName]: `Bearer ${mockAccessToken}`, + "Content-Type": "application/json", + }, + }, + ); + }); + + it("should throw error when API returns error response", async () => { + const errorMessage = "Failed to fetch config"; + const errorResponse = { message: errorMessage }; + + (fetchSpy as any).mockResolvedValue({ + ok: false, + json: async () => errorResponse, + }); + + await expect(getProxyConfigCall(mockAccessToken, ConfigType.GENERAL_SETTINGS)).rejects.toThrow(errorMessage); + }); + + it("should handle network errors", async () => { + const networkError = new Error("Network error"); + (fetchSpy as any).mockRejectedValue(networkError); + + await expect(getProxyConfigCall(mockAccessToken, ConfigType.GENERAL_SETTINGS)).rejects.toThrow("Network error"); + expect(consoleErrorSpy).toHaveBeenCalled(); + }); +}); + +describe("deleteProxyConfigFieldCall", () => { + let fetchSpy: ReturnType; + let consoleErrorSpy: ReturnType; + + beforeEach(() => { + fetchSpy = vi.fn(); + global.fetch = fetchSpy; + consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("should successfully delete proxy config field", async () => { + (fetchSpy as any).mockResolvedValue({ + ok: true, + json: async () => mockDeleteResponse, + }); + + const deleteRequest: DeleteProxyConfigFieldRequest = { + config_type: ConfigType.GENERAL_SETTINGS, + field_name: GeneralSettingsFieldName.MAXIMUM_SPEND_LOGS_RETENTION_PERIOD, + }; + + const result = await deleteProxyConfigFieldCall(mockAccessToken, deleteRequest); + + expect(result).toEqual(mockDeleteResponse); + expect(fetchSpy).toHaveBeenCalledWith(`${mockProxyBaseUrl}/config/field/delete`, { + method: "POST", + headers: { + [mockHeaderName]: `Bearer ${mockAccessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(deleteRequest), + }); + }); + + it("should throw error when API returns error response", async () => { + const errorMessage = "Failed to delete field"; + const errorResponse = { message: errorMessage }; + + (fetchSpy as any).mockResolvedValue({ + ok: false, + json: async () => errorResponse, + }); + + const deleteRequest: DeleteProxyConfigFieldRequest = { + config_type: ConfigType.GENERAL_SETTINGS, + field_name: GeneralSettingsFieldName.MAXIMUM_SPEND_LOGS_RETENTION_PERIOD, + }; + + await expect(deleteProxyConfigFieldCall(mockAccessToken, deleteRequest)).rejects.toThrow(errorMessage); + }); + + it("should handle network errors", async () => { + const networkError = new Error("Network error"); + (fetchSpy as any).mockRejectedValue(networkError); + + const deleteRequest: DeleteProxyConfigFieldRequest = { + config_type: ConfigType.GENERAL_SETTINGS, + field_name: GeneralSettingsFieldName.MAXIMUM_SPEND_LOGS_RETENTION_PERIOD, + }; + + await expect(deleteProxyConfigFieldCall(mockAccessToken, deleteRequest)).rejects.toThrow("Network error"); + expect(consoleErrorSpy).toHaveBeenCalled(); + }); +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/proxyConfig/useProxyConfig.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/proxyConfig/useProxyConfig.ts new file mode 100644 index 00000000000..b823ce4ffd8 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/proxyConfig/useProxyConfig.ts @@ -0,0 +1,180 @@ +import { useQuery, useMutation, UseMutationResult } from "@tanstack/react-query"; +import { createQueryKeys } from "../common/queryKeysFactory"; +import useAuthorized from "../useAuthorized"; +import { proxyBaseUrl, getGlobalLitellmHeaderName, deriveErrorMessage, handleError } from "@/components/networking"; + +/** + * Enum for config types that can be fetched from the proxy config endpoint. + * Currently supports general_settings, but can be extended as more config types are added. + */ +export enum ConfigType { + GENERAL_SETTINGS = "general_settings", +} + +/** + * Enum for supported field names that can be deleted from general_settings. + * This should match the fields available in ConfigGeneralSettings. + */ +export enum GeneralSettingsFieldName { + MAXIMUM_SPEND_LOGS_RETENTION_PERIOD = "maximum_spend_logs_retention_period", + // Add more field names here as needed +} + +/** + * Field detail for nested fields within a config field + */ +export interface FieldDetail { + field_name: string; + field_type: string; + field_description: string; + field_default_value: any; + stored_in_db: boolean | null; +} + +/** + * Configuration list item returned from /config/list endpoint + */ +export interface ConfigListItem { + field_name: string; + field_type: string; + field_description: string; + field_value: any; + stored_in_db: boolean | null; + field_default_value: any; + premium_field?: boolean; + nested_fields?: FieldDetail[] | null; +} + +/** + * Response type for /config/list endpoint + */ +export type ProxyConfigResponse = ConfigListItem[]; + +/** + * Request body for /config/field/delete endpoint + */ +export interface DeleteProxyConfigFieldRequest { + config_type: ConfigType; + field_name: string; +} + +/** + * Response type for /config/field/delete endpoint + */ +export interface DeleteProxyConfigFieldResponse { + message?: string; + [key: string]: any; +} + +/** + * Network call function to fetch proxy config by config type + * @param accessToken - The access token for authentication + * @param configType - The type of config to fetch (from ConfigType enum) + * @returns Promise resolving to the config list response + */ +export const getProxyConfigCall = async (accessToken: string, configType: ConfigType): Promise => { + try { + const url = proxyBaseUrl + ? `${proxyBaseUrl}/config/list?config_type=${configType}` + : `/config/list?config_type=${configType}`; + + const response = await fetch(url, { + method: "GET", + headers: { + [getGlobalLitellmHeaderName()]: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + const errorData = await response.json(); + const errorMessage = deriveErrorMessage(errorData); + handleError(errorMessage); + throw new Error(errorMessage); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error(`Failed to get proxy config for ${configType}:`, error); + throw error; + } +}; + +const proxyConfigKeys = createQueryKeys("proxyConfig"); + +/** + * Network call function to delete a proxy config field + * @param accessToken - The access token for authentication + * @param request - The delete request containing config_type and field_name + * @returns Promise resolving to the delete response + */ +export const deleteProxyConfigFieldCall = async ( + accessToken: string, + request: DeleteProxyConfigFieldRequest, +): Promise => { + try { + const url = proxyBaseUrl ? `${proxyBaseUrl}/config/field/delete` : `/config/field/delete`; + + const response = await fetch(url, { + method: "POST", + headers: { + [getGlobalLitellmHeaderName()]: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(request), + }); + + if (!response.ok) { + const errorData = await response.json(); + const errorMessage = deriveErrorMessage(errorData); + handleError(errorMessage); + throw new Error(errorMessage); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error(`Failed to delete proxy config field ${request.field_name}:`, error); + throw error; + } +}; + +/** + * React Query hook to fetch proxy config by config type + * @param configType - The type of config to fetch (from ConfigType enum) + * @returns React Query result with the config list data + */ +export const useProxyConfig = (configType: ConfigType) => { + const { accessToken } = useAuthorized(); + return useQuery({ + queryKey: proxyConfigKeys.list({ + filters: { + configType, + }, + }), + queryFn: async () => await getProxyConfigCall(accessToken!, configType), + enabled: Boolean(accessToken), + }); +}; + +/** + * React Query hook to delete a proxy config field + * @returns React Query mutation result for deleting config fields + */ +export const useDeleteProxyConfigField = (): UseMutationResult< + DeleteProxyConfigFieldResponse, + Error, + DeleteProxyConfigFieldRequest +> => { + const { accessToken } = useAuthorized(); + + return useMutation({ + mutationFn: async (request: DeleteProxyConfigFieldRequest) => { + if (!accessToken) { + throw new Error("Access token is required"); + } + return await deleteProxyConfigFieldCall(accessToken, request); + }, + }); +}; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/sso/useSSOSettings.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/sso/useSSOSettings.ts index f03f3977115..0431a8d39f7 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/sso/useSSOSettings.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/sso/useSSOSettings.ts @@ -28,6 +28,7 @@ export interface SSOSettingsValues { user_email: string | null; ui_access_mode: string | null; role_mappings: RoleMappings; + team_mappings: TeamMappings; } export interface RoleMappings { @@ -39,6 +40,10 @@ export interface RoleMappings { }; } +export interface TeamMappings { + team_ids_jwt_field: string; +} + export interface SSOSettingsResponse { values: SSOSettingsValues; field_schema: SSOFieldSchema; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/storeRequestInSpendLogs/useStoreRequestInSpendLogs.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/storeRequestInSpendLogs/useStoreRequestInSpendLogs.ts new file mode 100644 index 00000000000..9c6211c3086 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/storeRequestInSpendLogs/useStoreRequestInSpendLogs.ts @@ -0,0 +1,63 @@ +import { useMutation, UseMutationResult } from "@tanstack/react-query"; +import { getProxyBaseUrl, getGlobalLitellmHeaderName } from "@/components/networking"; +import useAuthorized from "../useAuthorized"; + +export interface StoreRequestInSpendLogsParams { + store_prompts_in_spend_logs: boolean; + maximum_spend_logs_retention_period?: string; +} + +export interface StoreRequestInSpendLogsResponse { + message: string; +} + +const performStoreRequestInSpendLogs = async ( + accessToken: string, + params: StoreRequestInSpendLogsParams +): Promise => { + const proxyBaseUrl = getProxyBaseUrl(); + const url = proxyBaseUrl ? `${proxyBaseUrl}/config/update` : `/config/update`; + + const response = await fetch(url, { + method: "POST", + headers: { + [getGlobalLitellmHeaderName()]: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + general_settings: { + store_prompts_in_spend_logs: params.store_prompts_in_spend_logs, + ...(params.maximum_spend_logs_retention_period && { + maximum_spend_logs_retention_period: params.maximum_spend_logs_retention_period, + }), + }, + }), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + const errorMessage = + errorData?.error?.message || errorData?.message || errorData?.detail || "Failed to update spend logs settings"; + throw new Error(errorMessage); + } + + const data = await response.json(); + return data; +}; + +export const useStoreRequestInSpendLogs = (): UseMutationResult< + StoreRequestInSpendLogsResponse, + Error, + StoreRequestInSpendLogsParams +> => { + const { accessToken } = useAuthorized(); + + return useMutation({ + mutationFn: async (params: StoreRequestInSpendLogsParams) => { + if (!accessToken) { + throw new Error("Access token is required"); + } + return await performStoreRequestInSpendLogs(accessToken, params); + }, + }); +}; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.test.ts index 3da27d3ff9b..78eddbd8d3c 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.test.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.test.ts @@ -8,11 +8,12 @@ import useAuthorized from "./useAuthorized"; // Unmock useAuthorized to test the actual implementation vi.unmock("@/app/(dashboard)/hooks/useAuthorized"); -const { replaceMock, clearTokenCookiesMock, getProxyBaseUrlMock, getUiConfigMock } = vi.hoisted(() => ({ +const { replaceMock, clearTokenCookiesMock, getProxyBaseUrlMock, getUiConfigMock, isJwtExpiredMock } = vi.hoisted(() => ({ replaceMock: vi.fn(), clearTokenCookiesMock: vi.fn(), getProxyBaseUrlMock: vi.fn(() => "http://proxy.example"), getUiConfigMock: vi.fn(), + isJwtExpiredMock: vi.fn(), })); vi.mock("next/navigation", () => ({ @@ -38,6 +39,14 @@ vi.mock("@/utils/cookieUtils", async (importOriginal) => { }; }); +vi.mock("@/utils/jwtUtils", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + isJwtExpired: isJwtExpiredMock, + }; +}); + const createQueryClient = () => new QueryClient({ defaultOptions: { @@ -68,6 +77,7 @@ describe("useAuthorized", () => { clearTokenCookiesMock.mockReset(); getProxyBaseUrlMock.mockClear(); getUiConfigMock.mockReset(); + isJwtExpiredMock.mockReset(); clearCookie(); }); @@ -78,6 +88,7 @@ describe("useAuthorized", () => { auto_redirect_to_sso: false, admin_ui_disabled: false, }); + isJwtExpiredMock.mockReturnValue(false); const token = createJwt({ key: "api-key-123", @@ -104,6 +115,7 @@ describe("useAuthorized", () => { expect(result.current.disabledPersonalKeyCreation).toBe(false); expect(result.current.showSSOBanner).toBe(true); expect(replaceMock).not.toHaveBeenCalled(); + expect(clearTokenCookiesMock).not.toHaveBeenCalled(); }); it("should clear cookies and redirect on an invalid token", async () => { @@ -134,6 +146,7 @@ describe("useAuthorized", () => { auto_redirect_to_sso: false, admin_ui_disabled: true, }); + isJwtExpiredMock.mockReturnValue(false); const token = createJwt({ key: "api-key-123", @@ -156,4 +169,50 @@ describe("useAuthorized", () => { expect(result.current.userId).toBe("user-1"); expect(result.current.userEmail).toBe("user@example.com"); }); + + it("should redirect when token is missing", async () => { + getUiConfigMock.mockResolvedValue({ + server_root_path: "/", + proxy_base_url: null, + auto_redirect_to_sso: false, + admin_ui_disabled: false, + }); + + // No token cookie set + const { result } = renderHook(() => useAuthorized(), { wrapper }); + + await waitFor(() => { + expect(replaceMock).toHaveBeenCalledWith("http://proxy.example/ui/login"); + }); + + expect(clearTokenCookiesMock).not.toHaveBeenCalled(); + expect(result.current.token).toBeNull(); + }); + + it("should clear cookies and redirect when token is expired", async () => { + getUiConfigMock.mockResolvedValue({ + server_root_path: "/", + proxy_base_url: null, + auto_redirect_to_sso: false, + admin_ui_disabled: false, + }); + isJwtExpiredMock.mockReturnValue(true); + + const token = createJwt({ + key: "api-key-123", + user_id: "user-1", + user_email: "user@example.com", + user_role: "app_admin", + }); + document.cookie = `token=${token}; path=/;`; + + const { result } = renderHook(() => useAuthorized(), { wrapper }); + + await waitFor(() => { + expect(clearTokenCookiesMock).toHaveBeenCalled(); + }); + + expect(replaceMock).toHaveBeenCalledWith("http://proxy.example/ui/login"); + expect(isJwtExpiredMock).toHaveBeenCalledWith(token); + }); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts index 62d514f0668..531a240a371 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useAuthorized.ts @@ -2,6 +2,7 @@ import { getProxyBaseUrl } from "@/components/networking"; import { clearTokenCookies, getCookie } from "@/utils/cookieUtils"; +import { isJwtExpired } from "@/utils/jwtUtils"; import { jwtDecode } from "jwt-decode"; import { useRouter } from "next/navigation"; import { useEffect, useMemo } from "react"; @@ -42,15 +43,24 @@ const useAuthorized = () => { const token = typeof document !== "undefined" ? getCookie("token") : null; - // Redirect after mount if missing/invalid token + // Step 1: Check for missing token or expired JWT - kick out immediately (even if UI Config is loading) + useEffect(() => { + if (!token || (token && isJwtExpired(token))) { + if (token) { + clearTokenCookies(); + } + router.replace(`${getProxyBaseUrl()}/ui/login`); + } + }, [token, router]); + useEffect(() => { if (isUIConfigLoading) { return; } - if (!token || uiConfig?.admin_ui_disabled) { + if (uiConfig?.admin_ui_disabled) { router.replace(`${getProxyBaseUrl()}/ui/login`); } - }, [token, router, isUIConfigLoading, uiConfig]); + }, [router, isUIConfigLoading, uiConfig]); // Decode safely const decoded = useMemo(() => { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/hooks/useDisableShowPrompts.ts b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useDisableShowPrompts.ts new file mode 100644 index 00000000000..801fbdbb99d --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/hooks/useDisableShowPrompts.ts @@ -0,0 +1,35 @@ +// hooks/useDisableShowPrompts.ts +import { useSyncExternalStore } from "react"; +import { getLocalStorageItem } from "@/utils/localStorageUtils"; +import { LOCAL_STORAGE_EVENT } from "@/utils/localStorageUtils"; + +function subscribe(callback: () => void) { + const onStorage = (e: StorageEvent) => { + if (e.key === "disableShowPrompts") { + callback(); + } + }; + + const onCustom = (e: Event) => { + const { key } = (e as CustomEvent).detail; + if (key === "disableShowPrompts") { + callback(); + } + }; + + window.addEventListener("storage", onStorage); + window.addEventListener(LOCAL_STORAGE_EVENT, onCustom); + + return () => { + window.removeEventListener("storage", onStorage); + window.removeEventListener(LOCAL_STORAGE_EVENT, onCustom); + }; +} + +function getSnapshot() { + return getLocalStorageItem("disableShowPrompts") === "true"; +} + +export function useDisableShowPrompts() { + return useSyncExternalStore(subscribe, getSnapshot); +} diff --git a/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx b/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx index 97837ff8e0a..b387380ff72 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useEffect, useState } from "react"; +import React, { Suspense, useEffect, useState } from "react"; import Navbar from "@/components/navbar"; import { ThemeProvider } from "@/contexts/ThemeContext"; import Sidebar2 from "@/app/(dashboard)/components/Sidebar2"; @@ -22,7 +22,7 @@ function withBase(path: string): string { } /** -------------------------------- */ -export default function Layout({ children }: { children: React.ReactNode }) { +function LayoutContent({ children }: { children: React.ReactNode }) { const router = useRouter(); const searchParams = useSearchParams(); const { accessToken, userRole, userId, userEmail, premiumUser } = useAuthorized(); @@ -56,8 +56,10 @@ export default function Layout({ children }: { children: React.ReactNode }) { userRole={userRole} premiumUser={premiumUser} proxySettings={undefined} - setProxySettings={() => {}} + setProxySettings={() => { }} accessToken={accessToken} + isDarkMode={false} + toggleDarkMode={() => { }} />

    r3Jp0Uc^q-SbK3x%nZpgSinP)CI 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%0Av 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 O0000LiteLLM Dashboard \ No newline at end of file +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/experimental/api-playground.txt b/litellm/proxy/_experimental/out/experimental/api-playground.txt index 3957430fd04..10055dbdc43 100644 --- a/litellm/proxy/_experimental/out/experimental/api-playground.txt +++ b/litellm/proxy/_experimental/out/experimental/api-playground.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[16643,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","8049","static/chunks/8049-cb52b16664f13e28.js","3425","static/chunks/app/(dashboard)/experimental/api-playground/page-67c4f150eba92e64.js"],"default",1] +3:I[16643,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","8049","static/chunks/8049-98da62d72b2b7dad.js","3425","static/chunks/app/(dashboard)/experimental/api-playground/page-b8b443caa67af654.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["experimental",{"children":["api-playground",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["experimental",{"children":["api-playground",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children","api-playground","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["experimental",{"children":["api-playground",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["experimental",{"children":["api-playground",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children","api-playground","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/experimental/budgets.html b/litellm/proxy/_experimental/out/experimental/budgets.html new file mode 100644 index 00000000000..a1b2bee9fed --- /dev/null +++ b/litellm/proxy/_experimental/out/experimental/budgets.html @@ -0,0 +1 @@ +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/experimental/budgets.txt b/litellm/proxy/_experimental/out/experimental/budgets.txt index bc78a253901..7b793690944 100644 --- a/litellm/proxy/_experimental/out/experimental/budgets.txt +++ b/litellm/proxy/_experimental/out/experimental/budgets.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[78858,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","2409","static/chunks/2409-e94c05c6f11bb939.js","7851","static/chunks/7851-c10cbe6fcac2f9d6.js","2618","static/chunks/2618-062177b80fc4a38e.js","7906","static/chunks/7906-59ba450db59c8efa.js","2699","static/chunks/2699-38ff37315d78ae04.js","8049","static/chunks/8049-cb52b16664f13e28.js","5649","static/chunks/app/(dashboard)/experimental/budgets/page-e471b9b73cfc894b.js"],"default",1] +3:I[78858,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","536","static/chunks/536-8fae454c1d779890.js","9258","static/chunks/9258-6907841794d6c1e1.js","2618","static/chunks/2618-062177b80fc4a38e.js","7906","static/chunks/7906-1b1cdd8da2773bb2.js","6697","static/chunks/6697-c1306587e479be83.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5649","static/chunks/app/(dashboard)/experimental/budgets/page-ae754695901b9376.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["experimental",{"children":["budgets",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["experimental",{"children":["budgets",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children","budgets","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["experimental",{"children":["budgets",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["experimental",{"children":["budgets",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children","budgets","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/experimental/budgets/index.html b/litellm/proxy/_experimental/out/experimental/budgets/index.html deleted file mode 100644 index cc2b3b0abcb..00000000000 --- a/litellm/proxy/_experimental/out/experimental/budgets/index.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/experimental/caching.html b/litellm/proxy/_experimental/out/experimental/caching.html new file mode 100644 index 00000000000..a1dc7776a54 --- /dev/null +++ b/litellm/proxy/_experimental/out/experimental/caching.html @@ -0,0 +1 @@ +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/experimental/caching.txt b/litellm/proxy/_experimental/out/experimental/caching.txt index aeb47bfa763..851707a9ed7 100644 --- a/litellm/proxy/_experimental/out/experimental/caching.txt +++ b/litellm/proxy/_experimental/out/experimental/caching.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[37492,["1047","static/chunks/e228588e-635e9029d9d88215.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","5333","static/chunks/5333-1540faf81c7d7006.js","1108","static/chunks/1108-c2d0c742b6e72436.js","5733","static/chunks/5733-aa80f52062105ad2.js","4817","static/chunks/4817-59d642defb0e86f2.js","8049","static/chunks/8049-cb52b16664f13e28.js","6600","static/chunks/6600-0ec5e2dc66d8b41a.js","1979","static/chunks/app/(dashboard)/experimental/caching/page-d31cc105402ab7e0.js"],"default",1] +3:I[37492,["1047","static/chunks/e228588e-635e9029d9d88215.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","2353","static/chunks/2353-c94748c0aac514ff.js","1108","static/chunks/1108-8b678b0704cb239b.js","5733","static/chunks/5733-6e7eac59c8bc246c.js","7187","static/chunks/7187-ee86be841e859eb1.js","8049","static/chunks/8049-98da62d72b2b7dad.js","6600","static/chunks/6600-077d81439e75d3a3.js","1979","static/chunks/app/(dashboard)/experimental/caching/page-a570d0f7ab5db7bf.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["experimental",{"children":["caching",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["experimental",{"children":["caching",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children","caching","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["experimental",{"children":["caching",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["experimental",{"children":["caching",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children","caching","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/experimental/caching/index.html b/litellm/proxy/_experimental/out/experimental/caching/index.html deleted file mode 100644 index 8bba9a13f65..00000000000 --- a/litellm/proxy/_experimental/out/experimental/caching/index.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/experimental/claude-code-plugins.html b/litellm/proxy/_experimental/out/experimental/claude-code-plugins.html new file mode 100644 index 00000000000..69c45840327 --- /dev/null +++ b/litellm/proxy/_experimental/out/experimental/claude-code-plugins.html @@ -0,0 +1 @@ +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/experimental/claude-code-plugins.txt b/litellm/proxy/_experimental/out/experimental/claude-code-plugins.txt new file mode 100644 index 00000000000..14bd45e88e1 --- /dev/null +++ b/litellm/proxy/_experimental/out/experimental/claude-code-plugins.txt @@ -0,0 +1,13 @@ +2:I[19107,[],"ClientPageRoot"] +3:I[23689,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","353","static/chunks/353-347e4836f09d94a0.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","6894","static/chunks/6894-8c74216e23aa271e.js","8049","static/chunks/8049-98da62d72b2b7dad.js","1112","static/chunks/1112-0b9bd4ebde18e77b.js","5696","static/chunks/app/(dashboard)/experimental/claude-code-plugins/page-84a3290b0c10981d.js"],"default",1] +4:I[4707,[],""] +5:I[36423,[],""] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] +7:{} +8:{"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"} +9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} +a:{"display":"inline-block"} +b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["experimental",{"children":["claude-code-plugins",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["experimental",{"children":["claude-code-plugins",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children","claude-code-plugins","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] +1:null diff --git a/litellm/proxy/_experimental/out/experimental/old-usage.html b/litellm/proxy/_experimental/out/experimental/old-usage.html new file mode 100644 index 00000000000..23b96d95cf3 --- /dev/null +++ b/litellm/proxy/_experimental/out/experimental/old-usage.html @@ -0,0 +1 @@ +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/experimental/old-usage.txt b/litellm/proxy/_experimental/out/experimental/old-usage.txt index 3bf8de3f17e..daac77ce4e8 100644 --- a/litellm/proxy/_experimental/out/experimental/old-usage.txt +++ b/litellm/proxy/_experimental/out/experimental/old-usage.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[42954,["1047","static/chunks/e228588e-635e9029d9d88215.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","2409","static/chunks/2409-e94c05c6f11bb939.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","353","static/chunks/353-e55516ea4730f9d4.js","1994","static/chunks/1994-6637a121c9ee1602.js","7138","static/chunks/7138-5b134dc8ad670770.js","7851","static/chunks/7851-c10cbe6fcac2f9d6.js","2068","static/chunks/2068-2c78bfc32dc0de5f.js","8565","static/chunks/8565-5c05f6bbb9d0662f.js","5319","static/chunks/5319-5b2d4bf2dc450f99.js","5333","static/chunks/5333-1540faf81c7d7006.js","8582","static/chunks/8582-3a775364dbf07fa8.js","6609","static/chunks/6609-3e081758ffbe3786.js","4105","static/chunks/4105-9c3c0ee7c494102f.js","1108","static/chunks/1108-c2d0c742b6e72436.js","5733","static/chunks/5733-aa80f52062105ad2.js","2227","static/chunks/2227-5ae3f36b0a81c5b4.js","8049","static/chunks/8049-cb52b16664f13e28.js","5144","static/chunks/5144-bbc18c43eade9aef.js","2202","static/chunks/2202-a83ad035a17401aa.js","1098","static/chunks/1098-c3e95c9684ff5e95.js","665","static/chunks/665-d94073042ee5b874.js","8143","static/chunks/8143-9e4312f059e9ed27.js","813","static/chunks/app/(dashboard)/experimental/old-usage/page-5e097dbb8ce40bb4.js"],"default",1] +3:I[42954,["1047","static/chunks/e228588e-635e9029d9d88215.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","353","static/chunks/353-347e4836f09d94a0.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7971","static/chunks/7971-76912e9c9a840367.js","536","static/chunks/536-8fae454c1d779890.js","6894","static/chunks/6894-8c74216e23aa271e.js","7474","static/chunks/7474-79e3343f32c7e661.js","9258","static/chunks/9258-6907841794d6c1e1.js","3178","static/chunks/3178-47bc3b9e8cf9bf6c.js","5319","static/chunks/5319-7f07d87ef011d5c9.js","9190","static/chunks/9190-e32c76b5b1affa7b.js","6609","static/chunks/6609-a69ca4ee5a2c4a9d.js","2353","static/chunks/2353-c94748c0aac514ff.js","7967","static/chunks/7967-1ac5097c3d83016f.js","1108","static/chunks/1108-8b678b0704cb239b.js","5733","static/chunks/5733-6e7eac59c8bc246c.js","5238","static/chunks/5238-3fa69435be59fb79.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5144","static/chunks/5144-ddfa7a8f89c5d465.js","7914","static/chunks/7914-25af99af34bee64b.js","1098","static/chunks/1098-a1702da59647cf14.js","665","static/chunks/665-f361bd1c21e3bf25.js","8143","static/chunks/8143-774574f553d5fa4b.js","813","static/chunks/app/(dashboard)/experimental/old-usage/page-1e4535b4f65e91c3.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["experimental",{"children":["old-usage",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["experimental",{"children":["old-usage",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children","old-usage","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["experimental",{"children":["old-usage",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["experimental",{"children":["old-usage",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children","old-usage","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/experimental/old-usage/index.html b/litellm/proxy/_experimental/out/experimental/old-usage/index.html deleted file mode 100644 index f8ab27c72f7..00000000000 --- a/litellm/proxy/_experimental/out/experimental/old-usage/index.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/experimental/prompts.html b/litellm/proxy/_experimental/out/experimental/prompts.html new file mode 100644 index 00000000000..efcebc1ac71 --- /dev/null +++ b/litellm/proxy/_experimental/out/experimental/prompts.html @@ -0,0 +1 @@ +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/experimental/prompts.txt b/litellm/proxy/_experimental/out/experimental/prompts.txt index 633a98ee7fc..c8b505cf77e 100644 --- a/litellm/proxy/_experimental/out/experimental/prompts.txt +++ b/litellm/proxy/_experimental/out/experimental/prompts.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[51599,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","2409","static/chunks/2409-e94c05c6f11bb939.js","3367","static/chunks/3367-58830187e9e5b9fa.js","353","static/chunks/353-e55516ea4730f9d4.js","2068","static/chunks/2068-2c78bfc32dc0de5f.js","8565","static/chunks/8565-5c05f6bbb9d0662f.js","5869","static/chunks/5869-426268ba6ad0ce0c.js","5319","static/chunks/5319-5b2d4bf2dc450f99.js","8582","static/chunks/8582-3a775364dbf07fa8.js","7906","static/chunks/7906-59ba450db59c8efa.js","816","static/chunks/816-37c57b39f4e7ece1.js","605","static/chunks/605-102c0e6d8bb7517c.js","8473","static/chunks/8473-7749355a9a4b1818.js","8049","static/chunks/8049-cb52b16664f13e28.js","6399","static/chunks/6399-ccf9cdbdcd5f7abb.js","2099","static/chunks/app/(dashboard)/experimental/prompts/page-8236f1efda3366f0.js"],"default",1] +3:I[51599,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","353","static/chunks/353-347e4836f09d94a0.js","6894","static/chunks/6894-8c74216e23aa271e.js","3178","static/chunks/3178-47bc3b9e8cf9bf6c.js","5319","static/chunks/5319-7f07d87ef011d5c9.js","9190","static/chunks/9190-e32c76b5b1affa7b.js","7906","static/chunks/7906-1b1cdd8da2773bb2.js","816","static/chunks/816-924f34bbf6b36a05.js","5631","static/chunks/5631-586d726ad939cea0.js","896","static/chunks/896-94547c54b334065c.js","8049","static/chunks/8049-98da62d72b2b7dad.js","6399","static/chunks/6399-3ed249931e03bab9.js","2099","static/chunks/app/(dashboard)/experimental/prompts/page-67bc04a61159c00a.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["experimental",{"children":["prompts",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["experimental",{"children":["prompts",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children","prompts","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["experimental",{"children":["prompts",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["experimental",{"children":["prompts",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children","prompts","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/experimental/tag-management.html b/litellm/proxy/_experimental/out/experimental/tag-management.html new file mode 100644 index 00000000000..78763e5c462 --- /dev/null +++ b/litellm/proxy/_experimental/out/experimental/tag-management.html @@ -0,0 +1 @@ +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/experimental/tag-management.txt b/litellm/proxy/_experimental/out/experimental/tag-management.txt index 0a255e96e0b..106f89a872e 100644 --- a/litellm/proxy/_experimental/out/experimental/tag-management.txt +++ b/litellm/proxy/_experimental/out/experimental/tag-management.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[21933,["1047","static/chunks/e228588e-635e9029d9d88215.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2409","static/chunks/2409-e94c05c6f11bb939.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","353","static/chunks/353-e55516ea4730f9d4.js","1994","static/chunks/1994-6637a121c9ee1602.js","7138","static/chunks/7138-5b134dc8ad670770.js","2068","static/chunks/2068-2c78bfc32dc0de5f.js","8565","static/chunks/8565-5c05f6bbb9d0662f.js","5319","static/chunks/5319-5b2d4bf2dc450f99.js","5333","static/chunks/5333-1540faf81c7d7006.js","8582","static/chunks/8582-3a775364dbf07fa8.js","6609","static/chunks/6609-3e081758ffbe3786.js","4105","static/chunks/4105-9c3c0ee7c494102f.js","8049","static/chunks/8049-cb52b16664f13e28.js","5144","static/chunks/5144-bbc18c43eade9aef.js","2202","static/chunks/2202-a83ad035a17401aa.js","1098","static/chunks/1098-c3e95c9684ff5e95.js","9145","static/chunks/9145-9507437d5b599cea.js","6061","static/chunks/app/(dashboard)/experimental/tag-management/page-5627fd94813402eb.js"],"default",1] +3:I[21933,["1047","static/chunks/e228588e-635e9029d9d88215.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","353","static/chunks/353-347e4836f09d94a0.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7971","static/chunks/7971-76912e9c9a840367.js","6894","static/chunks/6894-8c74216e23aa271e.js","7474","static/chunks/7474-79e3343f32c7e661.js","3178","static/chunks/3178-47bc3b9e8cf9bf6c.js","5319","static/chunks/5319-7f07d87ef011d5c9.js","9190","static/chunks/9190-e32c76b5b1affa7b.js","6609","static/chunks/6609-a69ca4ee5a2c4a9d.js","2353","static/chunks/2353-c94748c0aac514ff.js","7967","static/chunks/7967-1ac5097c3d83016f.js","3634","static/chunks/3634-5083d080185955ff.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5144","static/chunks/5144-ddfa7a8f89c5d465.js","7914","static/chunks/7914-25af99af34bee64b.js","1098","static/chunks/1098-a1702da59647cf14.js","6891","static/chunks/6891-4d6d997a2bca3514.js","6061","static/chunks/app/(dashboard)/experimental/tag-management/page-f6f7f1dd17bed0fe.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["experimental",{"children":["tag-management",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["experimental",{"children":["tag-management",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children","tag-management","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["experimental",{"children":["tag-management",{"children":["__PAGE__",{}]}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["experimental",{"children":["tag-management",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children","tag-management","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","experimental","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/experimental/tag-management/index.html b/litellm/proxy/_experimental/out/experimental/tag-management/index.html deleted file mode 100644 index 06bb449f538..00000000000 --- a/litellm/proxy/_experimental/out/experimental/tag-management/index.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/guardrails.html b/litellm/proxy/_experimental/out/guardrails.html new file mode 100644 index 00000000000..172cff093b2 --- /dev/null +++ b/litellm/proxy/_experimental/out/guardrails.html @@ -0,0 +1 @@ +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/guardrails.txt b/litellm/proxy/_experimental/out/guardrails.txt index 36d6418a1be..ad8d37948b1 100644 --- a/litellm/proxy/_experimental/out/guardrails.txt +++ b/litellm/proxy/_experimental/out/guardrails.txt @@ -1,13 +1,13 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[49514,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","2409","static/chunks/2409-e94c05c6f11bb939.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","353","static/chunks/353-e55516ea4730f9d4.js","1994","static/chunks/1994-6637a121c9ee1602.js","7138","static/chunks/7138-5b134dc8ad670770.js","7851","static/chunks/7851-c10cbe6fcac2f9d6.js","2068","static/chunks/2068-2c78bfc32dc0de5f.js","8565","static/chunks/8565-5c05f6bbb9d0662f.js","5869","static/chunks/5869-426268ba6ad0ce0c.js","5319","static/chunks/5319-5b2d4bf2dc450f99.js","8582","static/chunks/8582-3a775364dbf07fa8.js","6609","static/chunks/6609-3e081758ffbe3786.js","5945","static/chunks/5945-8b3b7713d7f416a2.js","4077","static/chunks/4077-50cf2a28a79fdcd4.js","4470","static/chunks/4470-3ef8ade20eaf2875.js","8049","static/chunks/8049-cb52b16664f13e28.js","137","static/chunks/137-c6f74fedf576a11b.js","6607","static/chunks/app/(dashboard)/guardrails/page-060e61cb783d32ef.js"],"default",1] +3:I[49514,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","353","static/chunks/353-347e4836f09d94a0.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7971","static/chunks/7971-76912e9c9a840367.js","536","static/chunks/536-8fae454c1d779890.js","6894","static/chunks/6894-8c74216e23aa271e.js","7474","static/chunks/7474-79e3343f32c7e661.js","9258","static/chunks/9258-6907841794d6c1e1.js","3178","static/chunks/3178-47bc3b9e8cf9bf6c.js","5319","static/chunks/5319-7f07d87ef011d5c9.js","9190","static/chunks/9190-e32c76b5b1affa7b.js","6609","static/chunks/6609-a69ca4ee5a2c4a9d.js","4851","static/chunks/4851-0dc9f6cfeabb43d0.js","4341","static/chunks/4341-3e3f04c866417786.js","5631","static/chunks/5631-586d726ad939cea0.js","831","static/chunks/831-26544e9debf34eba.js","8049","static/chunks/8049-98da62d72b2b7dad.js","6868","static/chunks/6868-c5f994b9d687f7b6.js","6607","static/chunks/app/(dashboard)/guardrails/page-1528b2c6a3288963.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","7138","static/chunks/7138-5b134dc8ad670770.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","4388","static/chunks/4388-2f4ca3419d20af67.js","8049","static/chunks/8049-cb52b16664f13e28.js","5642","static/chunks/app/(dashboard)/layout-f7f722423efd1c5b.js"],"default",1] +6:I[53104,["9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7474","static/chunks/7474-79e3343f32c7e661.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1070","static/chunks/1070-ab9dafb0fc6e0b85.js","3331","static/chunks/3331-37f4428be6db0332.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5642","static/chunks/app/(dashboard)/layout-534e351316fbcd53.js"],"default",1] 7:{} 8:{"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"} 9:{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"} a:{"display":"inline-block"} b:{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0} -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["(dashboard)",{"children":["guardrails",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["guardrails",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","guardrails","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["(dashboard)",{"children":["guardrails",{"children":["__PAGE__",{}]}]}]},"$undefined","$undefined",true],["",{"children":["(dashboard)",{"children":["guardrails",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],null],null],null]},[null,["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children","guardrails","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$undefined","notFoundStyles":"$undefined"}]],null]},[[null,["$","$L6",null,{"children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children","(dashboard)","children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}],"params":"$7"}]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$8","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":"$9","children":"404"}],["$","div",null,{"style":"$a","children":["$","h2",null,{"style":"$b","children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$Lc",null]]]] c:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/index.html b/litellm/proxy/_experimental/out/index.html index c6663a465f0..40fd692bc47 100644 --- a/litellm/proxy/_experimental/out/index.html +++ b/litellm/proxy/_experimental/out/index.html @@ -1 +1 @@ -LiteLLM Dashboard \ No newline at end of file +LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/index.txt b/litellm/proxy/_experimental/out/index.txt index 870cfc1999f..d5b72488f38 100644 --- a/litellm/proxy/_experimental/out/index.txt +++ b/litellm/proxy/_experimental/out/index.txt @@ -1,7 +1,7 @@ 2:I[19107,[],"ClientPageRoot"] -3:I[37028,["1047","static/chunks/e228588e-635e9029d9d88215.js","3665","static/chunks/3014691f-ba91873bc8fe3fad.js","6990","static/chunks/13b76428-e1bf383848c17260.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","9409","static/chunks/9409-6eefc92a7f8433ff.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","337","static/chunks/337-bb33d149e9f461b3.js","8135","static/chunks/8135-881fe2cea0032570.js","1442","static/chunks/1442-024f7e51804e0d7e.js","2926","static/chunks/2926-a9cb83e61fc8ad20.js","2409","static/chunks/2409-e94c05c6f11bb939.js","3367","static/chunks/3367-58830187e9e5b9fa.js","3709","static/chunks/3709-34dbb332d3a3ac26.js","353","static/chunks/353-e55516ea4730f9d4.js","1994","static/chunks/1994-6637a121c9ee1602.js","7138","static/chunks/7138-5b134dc8ad670770.js","7851","static/chunks/7851-c10cbe6fcac2f9d6.js","2068","static/chunks/2068-2c78bfc32dc0de5f.js","8565","static/chunks/8565-5c05f6bbb9d0662f.js","5869","static/chunks/5869-426268ba6ad0ce0c.js","5319","static/chunks/5319-5b2d4bf2dc450f99.js","5333","static/chunks/5333-1540faf81c7d7006.js","8582","static/chunks/8582-3a775364dbf07fa8.js","6609","static/chunks/6609-3e081758ffbe3786.js","2618","static/chunks/2618-062177b80fc4a38e.js","7906","static/chunks/7906-59ba450db59c8efa.js","4105","static/chunks/4105-9c3c0ee7c494102f.js","8211","static/chunks/8211-8dd5691abf54d0ca.js","1108","static/chunks/1108-c2d0c742b6e72436.js","816","static/chunks/816-37c57b39f4e7ece1.js","7271","static/chunks/7271-46e4c11ee6b0a4d6.js","4077","static/chunks/4077-50cf2a28a79fdcd4.js","766","static/chunks/766-baf0336e8ba5c686.js","3567","static/chunks/3567-9a29feedd7b63950.js","5733","static/chunks/5733-aa80f52062105ad2.js","2227","static/chunks/2227-5ae3f36b0a81c5b4.js","4306","static/chunks/4306-f891b96cf0ee333b.js","605","static/chunks/605-102c0e6d8bb7517c.js","2378","static/chunks/2378-252212b7a5e313ce.js","5752","static/chunks/5752-f504fb38ff5e13e8.js","8049","static/chunks/8049-cb52b16664f13e28.js","5144","static/chunks/5144-bbc18c43eade9aef.js","2202","static/chunks/2202-a83ad035a17401aa.js","1098","static/chunks/1098-c3e95c9684ff5e95.js","665","static/chunks/665-d94073042ee5b874.js","7526","static/chunks/7526-da6b2857a3ca248d.js","5992","static/chunks/5992-287cec06808c74ae.js","3862","static/chunks/3862-064a3fb795c75b62.js","9584","static/chunks/9584-9d4fd7b3d6a7c9e7.js","5706","static/chunks/5706-1e314cef9ea5c5d6.js","9841","static/chunks/9841-721a173be76941d1.js","1901","static/chunks/1901-4d02d1f2a71cdbf7.js","6537","static/chunks/6537-f70f2c4278e93458.js","292","static/chunks/292-aaba6c4e7c8d416d.js","137","static/chunks/137-c6f74fedf576a11b.js","6399","static/chunks/6399-ccf9cdbdcd5f7abb.js","9818","static/chunks/9818-6f03d7efd4fb8533.js","6653","static/chunks/6653-e61fdc06093fc0a8.js","9264","static/chunks/9264-fd8ab51d702e9535.js","9140","static/chunks/9140-09af618948244b82.js","6600","static/chunks/6600-0ec5e2dc66d8b41a.js","9039","static/chunks/9039-e44ff08ca4f37a12.js","8143","static/chunks/8143-9e4312f059e9ed27.js","5975","static/chunks/5975-758334d6641b9c63.js","5695","static/chunks/5695-dbbcbf2da21d2bab.js","9145","static/chunks/9145-9507437d5b599cea.js","1931","static/chunks/app/page-587b0acf34f8c747.js"],"default",1] +3:I[84406,["1047","static/chunks/e228588e-635e9029d9d88215.js","3665","static/chunks/3014691f-ba91873bc8fe3fad.js","6990","static/chunks/13b76428-e1bf383848c17260.js","9028","static/chunks/9028-2bfc9f09930a0d61.js","8745","static/chunks/8745-83ff3a8036a70abb.js","1713","static/chunks/1713-b3fdb241d0f3ae7a.js","4865","static/chunks/4865-c1c0885a93c327fa.js","7840","static/chunks/7840-0952e7293502ce83.js","337","static/chunks/337-929caaa1bd1d68cc.js","2652","static/chunks/2652-61deef051e2dc3b2.js","2926","static/chunks/2926-ac542d9fa707b8a4.js","3367","static/chunks/3367-33bb84b3d3d247b2.js","5869","static/chunks/5869-a383009914cbdb01.js","353","static/chunks/353-347e4836f09d94a0.js","3709","static/chunks/3709-7f9257c8a6221d7f.js","7971","static/chunks/7971-76912e9c9a840367.js","536","static/chunks/536-8fae454c1d779890.js","6894","static/chunks/6894-8c74216e23aa271e.js","7474","static/chunks/7474-79e3343f32c7e661.js","9258","static/chunks/9258-6907841794d6c1e1.js","3178","static/chunks/3178-47bc3b9e8cf9bf6c.js","5319","static/chunks/5319-7f07d87ef011d5c9.js","9190","static/chunks/9190-e32c76b5b1affa7b.js","6609","static/chunks/6609-a69ca4ee5a2c4a9d.js","2353","static/chunks/2353-c94748c0aac514ff.js","2618","static/chunks/2618-062177b80fc4a38e.js","7906","static/chunks/7906-1b1cdd8da2773bb2.js","7967","static/chunks/7967-1ac5097c3d83016f.js","3885","static/chunks/3885-e5f4fc4a4724e9b8.js","1108","static/chunks/1108-8b678b0704cb239b.js","816","static/chunks/816-924f34bbf6b36a05.js","7271","static/chunks/7271-46e4c11ee6b0a4d6.js","4341","static/chunks/4341-3e3f04c866417786.js","3138","static/chunks/3138-faa6fb0b1d7f2d67.js","9078","static/chunks/9078-e3b627680692b3fd.js","5733","static/chunks/5733-6e7eac59c8bc246c.js","5238","static/chunks/5238-3fa69435be59fb79.js","5720","static/chunks/5720-a8df9dd74eea4daa.js","5736","static/chunks/5736-9031c5108cb49a26.js","8049","static/chunks/8049-98da62d72b2b7dad.js","5144","static/chunks/5144-ddfa7a8f89c5d465.js","7914","static/chunks/7914-25af99af34bee64b.js","1098","static/chunks/1098-a1702da59647cf14.js","665","static/chunks/665-f361bd1c21e3bf25.js","7526","static/chunks/7526-f6a7e2b51a17dd02.js","5992","static/chunks/5992-ee986583db978ba0.js","6554","static/chunks/6554-265013ca56622e1f.js","9584","static/chunks/9584-4d5bef7e60cfea45.js","5706","static/chunks/5706-b92e3cca4b167e71.js","1658","static/chunks/1658-c301cddaf7772753.js","7794","static/chunks/7794-37e92993b04b6bb9.js","6728","static/chunks/6728-a6b270885bc8863f.js","5276","static/chunks/5276-22fb90a28ebcab8b.js","6868","static/chunks/6868-c5f994b9d687f7b6.js","1789","static/chunks/1789-c534ff8966aa231a.js","6399","static/chunks/6399-3ed249931e03bab9.js","2318","static/chunks/2318-8bec43289448e95d.js","6213","static/chunks/6213-6c1fab5854e4401f.js","9264","static/chunks/9264-5009b962427411a5.js","9120","static/chunks/9120-dc2d8129a3d2175b.js","6600","static/chunks/6600-077d81439e75d3a3.js","9039","static/chunks/9039-2037889778daf211.js","8143","static/chunks/8143-774574f553d5fa4b.js","5975","static/chunks/5975-60599e8984464729.js","6891","static/chunks/6891-4d6d997a2bca3514.js","1112","static/chunks/1112-0b9bd4ebde18e77b.js","1931","static/chunks/app/page-850191a6e6250635.js"],"default",1] 4:I[4707,[],""] 5:I[36423,[],""] -0:["zHD7JXLXiWgn1NPp82VmF",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/0fc668a8750043fe.css","precedence":"next","crossOrigin":"$undefined"}]]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/83c095d0528a2e35.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]] +0:["MkHZcSjEBwlJY7dIHtt6n",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},[["$L1",["$","$L2",null,{"props":{"params":{},"searchParams":{}},"Component":"$3"}],[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/0fc668a8750043fe.css","precedence":"next","crossOrigin":"$undefined"}]]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/349654da14372cd9.css","precedence":"next","crossOrigin":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/css/4fd2d0c1b251ee22.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_1c856b","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",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."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]] 6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"LiteLLM Dashboard"}],["$","meta","3",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","link","5",{"rel":"icon","href":"./favicon.ico"}],["$","meta","6",{"name":"next-size-adjust"}]] 1:null diff --git a/litellm/proxy/_experimental/out/login.html b/litellm/proxy/_experimental/out/login.html new file mode 100644 index 00000000000..fb2c8ad15d3 --- /dev/null +++ b/litellm/proxy/_experimental/out/login.html @@ -0,0 +1 @@ +LiteLLM Dashboard