diff --git a/.circleci/config.yml b/.circleci/config.yml
index 7a982d74cbe..77f3c2b9a7d 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -44,8 +44,8 @@ commands:
pip install "pytest-asyncio==0.21.1"
pip install "respx==0.22.0"
pip install "hypercorn==0.17.3"
- pip install "pydantic==2.10.2"
- pip install "mcp==1.10.1"
+ pip install "pydantic==2.11.0"
+ pip install "mcp==1.25.0"
pip install "requests-mock>=1.12.1"
pip install "responses==0.25.7"
pip install "pytest-xdist==3.6.1"
@@ -112,14 +112,14 @@ jobs:
python -m mypy .
cd ..
no_output_timeout: 10m
- local_testing:
+ local_testing_part1:
docker:
- image: cimg/python:3.12
auth:
username: ${DOCKERHUB_USERNAME}
password: ${DOCKERHUB_PASSWORD}
working_directory: ~/project
-
+ parallelism: 4
steps:
- checkout
- setup_google_dns
@@ -178,6 +178,7 @@ jobs:
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
@@ -204,17 +205,32 @@ jobs:
# Run pytest and generate JUnit XML report
- run:
- name: Run tests
+ name: Run tests (Part 1 - A-M)
command: |
- pwd
- ls
- python -m pytest -vv tests/local_testing --cov=litellm --cov-report=xml --junitxml=test-results/junit.xml --durations=5 -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
+ mkdir test-results
+
+ # 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=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_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:
@@ -222,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
@@ -495,7 +639,6 @@ jobs:
username: ${DOCKERHUB_USERNAME}
password: ${DOCKERHUB_PASSWORD}
working_directory: ~/project
-
steps:
- checkout
- setup_google_dns
@@ -509,6 +652,7 @@ jobs:
pip install "pytest-cov==5.0.0"
pip install "pytest-retry==1.6.3"
pip install "pytest-asyncio==0.21.1"
+ pip install "pytest-xdist==3.6.1"
pip install semantic_router --no-deps
pip install aurelio_sdk --no-deps
# Run pytest and generate JUnit XML report
@@ -614,6 +758,12 @@ jobs:
- run:
name: Install Dependencies
command: |
+ export PATH="$HOME/miniconda/bin:$PATH"
+ source $HOME/miniconda/etc/profile.d/conda.sh
+ conda activate myenv
+ python --version
+ which python
+ pip install --upgrade typing-extensions>=4.12.0
pip install "pytest==7.3.1"
pip install "pytest-asyncio==0.21.1"
pip install aiohttp
@@ -677,6 +827,9 @@ jobs:
- run:
name: Run prisma ./docker/entrypoint.sh
command: |
+ export PATH="$HOME/miniconda/bin:$PATH"
+ source $HOME/miniconda/etc/profile.d/conda.sh
+ conda activate myenv
set +e
chmod +x docker/entrypoint.sh
./docker/entrypoint.sh
@@ -685,6 +838,9 @@ jobs:
- run:
name: Run tests
command: |
+ export PATH="$HOME/miniconda/bin:$PATH"
+ source $HOME/miniconda/etc/profile.d/conda.sh
+ conda activate myenv
pwd
ls
python -m pytest tests/proxy_security_tests --cov=litellm --cov-report=xml -vv -x -v --junitxml=test-results/junit.xml --durations=5
@@ -1090,13 +1246,16 @@ jobs:
pip install "pytest-asyncio==0.21.1"
pip install "respx==0.22.0"
pip install "pytest-xdist==3.6.1"
+ pip install "pytest-timeout==2.2.0"
# Run pytest and generate JUnit XML report
- run:
name: Run tests
command: |
pwd
ls
- python -m pytest -vv tests/llm_translation --cov=litellm --cov-report=xml -v --junitxml=test-results/junit.xml --durations=5 -n 4
+ # Add --timeout to kill hanging tests after 120s (2 min)
+ # Add --durations=20 to show 20 slowest tests for debugging
+ python -m pytest -vv tests/llm_translation --cov=litellm --cov-report=xml -v --junitxml=test-results/junit.xml --durations=20 -n 4 --timeout=120 --timeout_method=thread
no_output_timeout: 120m
- run:
name: Rename the coverage files
@@ -1133,8 +1292,8 @@ jobs:
pip install "pytest-cov==5.0.0"
pip install "pytest-asyncio==0.21.1"
pip install "respx==0.22.0"
- pip install "pydantic==2.10.2"
- pip install "mcp==1.10.1"
+ pip install "pydantic==2.11.0"
+ pip install "mcp==1.25.0"
# Run pytest and generate JUnit XML report
- run:
name: Run tests
@@ -1446,7 +1605,7 @@ jobs:
- run:
name: Run core tests
command: |
- python -m pytest tests/test_litellm --ignore=tests/test_litellm/proxy --ignore=tests/test_litellm/llms --cov=litellm --cov-report=xml --junitxml=test-results/junit-core.xml --durations=10 -n 16 --maxfail=5 --timeout=300 -vv --log-cli-level=WARNING
+ python -m pytest tests/test_litellm --ignore=tests/test_litellm/proxy --ignore=tests/test_litellm/llms --ignore=tests/test_litellm/integrations --ignore=tests/test_litellm/litellm_core_utils --cov=litellm --cov-report=xml --junitxml=test-results/junit-core.xml --durations=10 -n 16 --maxfail=5 --timeout=300 -vv --log-cli-level=WARNING
no_output_timeout: 120m
- run:
name: Rename the coverage files
@@ -1460,6 +1619,60 @@ jobs:
paths:
- litellm_core_tests_coverage.xml
- litellm_core_tests_coverage
+ litellm_mapped_tests_litellm_core_utils:
+ docker:
+ - image: cimg/python:3.11
+ auth:
+ username: ${DOCKERHUB_USERNAME}
+ password: ${DOCKERHUB_PASSWORD}
+ working_directory: ~/project
+ resource_class: xlarge
+ steps:
+ - setup_litellm_test_deps
+ - run:
+ name: Run litellm_core_utils tests
+ command: |
+ python -m pytest tests/test_litellm/litellm_core_utils --cov=litellm --cov-report=xml --junitxml=test-results/junit-litellm-core-utils.xml --durations=10 -n 16 --maxfail=5 --timeout=300 -vv --log-cli-level=WARNING
+ no_output_timeout: 120m
+ - run:
+ name: Rename the coverage files
+ command: |
+ mv coverage.xml litellm_core_utils_tests_coverage.xml
+ mv .coverage litellm_core_utils_tests_coverage
+ - store_test_results:
+ path: test-results
+ - persist_to_workspace:
+ root: .
+ paths:
+ - litellm_core_utils_tests_coverage.xml
+ - litellm_core_utils_tests_coverage
+ litellm_mapped_tests_integrations:
+ docker:
+ - image: cimg/python:3.11
+ auth:
+ username: ${DOCKERHUB_USERNAME}
+ password: ${DOCKERHUB_PASSWORD}
+ working_directory: ~/project
+ resource_class: xlarge
+ steps:
+ - setup_litellm_test_deps
+ - run:
+ name: Run integrations tests
+ command: |
+ python -m pytest tests/test_litellm/integrations --cov=litellm --cov-report=xml --junitxml=test-results/junit-integrations.xml --durations=10 -n 16 --maxfail=5 --timeout=300 -vv --log-cli-level=WARNING
+ no_output_timeout: 120m
+ - run:
+ name: Rename the coverage files
+ command: |
+ mv coverage.xml litellm_integrations_tests_coverage.xml
+ mv .coverage litellm_integrations_tests_coverage
+ - store_test_results:
+ path: test-results
+ - persist_to_workspace:
+ root: .
+ paths:
+ - litellm_integrations_tests_coverage.xml
+ - litellm_integrations_tests_coverage
litellm_mapped_enterprise_tests:
docker:
- image: cimg/python:3.11
@@ -1483,8 +1696,8 @@ jobs:
pip install "pytest-asyncio==0.21.1"
pip install "respx==0.22.0"
pip install "hypercorn==0.17.3"
- pip install "pydantic==2.10.2"
- pip install "mcp==1.10.1"
+ pip install "pydantic==2.11.0"
+ pip install "mcp==1.25.0"
pip install "requests-mock>=1.12.1"
pip install "responses==0.25.7"
pip install "pytest-xdist==3.6.1"
@@ -1670,13 +1883,14 @@ jobs:
pip install "pytest-cov==5.0.0"
pip install "pytest-asyncio==0.21.1"
pip install "respx==0.22.0"
+ pip install "pytest-xdist==3.6.1"
# Run pytest and generate JUnit XML report
- run:
name: Run tests
command: |
pwd
ls
- python -m pytest -vv tests/image_gen_tests --cov=litellm --cov-report=xml -x -v --junitxml=test-results/junit.xml --durations=5
+ python -m pytest -vv tests/image_gen_tests -n 4 --cov=litellm --cov-report=xml -x -v --junitxml=test-results/junit.xml --durations=5
no_output_timeout: 120m
- run:
name: Rename the coverage files
@@ -1719,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:
@@ -1726,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
@@ -1842,7 +2057,7 @@ jobs:
pip install "pytest-asyncio==0.21.1"
pip install "pytest-cov==5.0.0"
pip install "tomli==2.2.1"
- pip install "mcp==1.10.1"
+ pip install "mcp==1.25.0"
- run:
name: Run tests
command: |
@@ -1886,6 +2101,18 @@ jobs:
command: |
kind create cluster --name litellm-test
+ - run:
+ name: Build Docker image for helm tests
+ command: |
+ IMAGE_TAG=${CIRCLE_SHA1:-ci}
+ docker build -t litellm-ci:${IMAGE_TAG} -f docker/Dockerfile.database .
+
+ - run:
+ name: Load Docker image into Kind
+ command: |
+ IMAGE_TAG=${CIRCLE_SHA1:-ci}
+ kind load docker-image litellm-ci:${IMAGE_TAG} --name litellm-test
+
# Run helm lint
- run:
name: Run helm lint
@@ -1896,7 +2123,11 @@ jobs:
- run:
name: Run helm tests
command: |
- helm install litellm ./deploy/charts/litellm-helm -f ./deploy/charts/litellm-helm/ci/test-values.yaml
+ IMAGE_TAG=${CIRCLE_SHA1:-ci}
+ helm install litellm ./deploy/charts/litellm-helm -f ./deploy/charts/litellm-helm/ci/test-values.yaml \
+ --set image.repository=litellm-ci \
+ --set image.tag=${IMAGE_TAG} \
+ --set image.pullPolicy=Never
# Wait for pod to be ready
echo "Waiting 30 seconds for pod to be ready..."
sleep 30
@@ -1941,11 +2172,13 @@ jobs:
- run: ruff check ./litellm
# - run: python ./tests/documentation_tests/test_general_setting_keys.py
- run: python ./tests/code_coverage_tests/check_licenses.py
+ - run: python ./tests/code_coverage_tests/check_provider_folders_documented.py
- run: python ./tests/code_coverage_tests/router_code_coverage.py
- run: python ./tests/code_coverage_tests/test_chat_completion_imports.py
- run: python ./tests/code_coverage_tests/info_log_check.py
- run: python ./tests/code_coverage_tests/test_ban_set_verbose.py
- run: python ./tests/code_coverage_tests/code_qa_check_tests.py
+ - run: python ./tests/code_coverage_tests/check_get_model_cost_key_performance.py
- run: python ./tests/code_coverage_tests/test_proxy_types_import.py
- run: python ./tests/code_coverage_tests/callback_manager_test.py
- run: python ./tests/code_coverage_tests/recursive_detector.py
@@ -1961,6 +2194,7 @@ jobs:
- run: python ./tests/code_coverage_tests/check_unsafe_enterprise_import.py
- run: python ./tests/code_coverage_tests/ban_copy_deepcopy_kwargs.py
- run: python ./tests/code_coverage_tests/check_fastuuid_usage.py
+ - run: python ./tests/code_coverage_tests/memory_test.py
- run: helm lint ./deploy/charts/litellm-helm
db_migration_disable_update_check:
@@ -1989,10 +2223,13 @@ jobs:
pip install "pytest-asyncio==0.21.1"
pip install aiohttp
pip install apscheduler
+ - attach_workspace:
+ at: ~/project
- run:
- name: Build Docker image
+ name: Load Docker Database Image
command: |
- docker build -t myapp . -f ./docker/Dockerfile.database
+ gunzip -c litellm-docker-database.tar.gz | docker load
+ docker images | grep litellm-docker-database
- run:
name: Run Docker container
command: |
@@ -2005,7 +2242,7 @@ jobs:
-v $(pwd)/litellm/proxy/example_config_yaml/bad_schema.prisma:/app/litellm/proxy/schema.prisma \
-v $(pwd)/litellm/proxy/example_config_yaml/disable_schema_update.yaml:/app/config.yaml \
--name my-app \
- myapp:latest \
+ litellm-docker-database:ci \
--config /app/config.yaml \
--port 4000
- run:
@@ -2024,10 +2261,11 @@ jobs:
name: Check container logs for expected message
command: |
echo "=== Printing Full Container Startup Logs ==="
- docker logs my-app
+ LOG_OUTPUT="$(docker logs my-app 2>&1)"
+ printf '%s\n' "$LOG_OUTPUT"
echo "=== End of Full Container Startup Logs ==="
- if docker logs my-app 2>&1 | grep -q "prisma schema out of sync with db. Consider running these sql_commands to sync the two"; then
+ if printf '%s\n' "$LOG_OUTPUT" | grep -q "prisma schema out of sync with db. Consider running these sql_commands to sync the two"; then
echo "Expected message found in logs. Test passed."
else
echo "Expected message not found in logs. Test failed."
@@ -2096,6 +2334,8 @@ jobs:
pip install "asyncio==3.4.3"
pip install "PyGithub==1.59.1"
pip install "openai==1.100.1"
+ pip install "litellm[proxy]"
+ pip install "pytest-xdist==3.6.1"
- run:
name: Install dockerize
command: |
@@ -2172,7 +2412,7 @@ jobs:
command: |
pwd
ls
- python -m pytest -s -vv tests/*.py -x --junitxml=test-results/junit.xml --durations=5 --ignore=tests/otel_tests --ignore=tests/spend_tracking_tests --ignore=tests/pass_through_tests --ignore=tests/proxy_admin_ui_tests --ignore=tests/load_tests --ignore=tests/llm_translation --ignore=tests/llm_responses_api_testing --ignore=tests/mcp_tests --ignore=tests/guardrails_tests --ignore=tests/image_gen_tests --ignore=tests/pass_through_unit_tests
+ python -m pytest -s -vv tests/*.py -x --junitxml=test-results/junit.xml -n 4 --durations=5 --ignore=tests/otel_tests --ignore=tests/spend_tracking_tests --ignore=tests/pass_through_tests --ignore=tests/proxy_admin_ui_tests --ignore=tests/load_tests --ignore=tests/llm_translation --ignore=tests/llm_responses_api_testing --ignore=tests/mcp_tests --ignore=tests/guardrails_tests --ignore=tests/image_gen_tests --ignore=tests/pass_through_unit_tests
no_output_timeout: 120m
# Store test results
@@ -2257,9 +2497,13 @@ jobs:
- run:
name: Wait for PostgreSQL to be ready
command: dockerize -wait tcp://localhost:5432 -timeout 1m
+ - attach_workspace:
+ at: ~/project
- run:
- name: Build Docker image
- command: docker build -t my-app:latest -f ./docker/Dockerfile.database .
+ 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
command: |
@@ -2294,7 +2538,7 @@ jobs:
--add-host host.docker.internal:host-gateway \
--name my-app \
-v $(pwd)/litellm/proxy/example_config_yaml/oai_misc_config.yaml:/app/config.yaml \
- my-app:latest \
+ litellm-docker-database:ci \
--config /app/config.yaml \
--port 4000 \
--detailed_debug \
@@ -2397,9 +2641,13 @@ jobs:
- run:
name: Wait for PostgreSQL to be ready
command: dockerize -wait tcp://localhost:5432 -timeout 1m
+ - attach_workspace:
+ at: ~/project
- run:
- name: Build Docker image
- command: docker build -t my-app:latest -f ./docker/Dockerfile.database .
+ 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
# intentionally give bad redis credentials here
@@ -2432,7 +2680,7 @@ jobs:
--name my-app \
-v $(pwd)/litellm/proxy/example_config_yaml/otel_test_config.yaml:/app/config.yaml \
-v $(pwd)/litellm/proxy/example_config_yaml/custom_guardrail.py:/app/custom_guardrail.py \
- my-app:latest \
+ litellm-docker-database:ci \
--config /app/config.yaml \
--port 4000 \
--detailed_debug \
@@ -2483,7 +2731,7 @@ jobs:
--add-host host.docker.internal:host-gateway \
--name my-app-3 \
-v $(pwd)/litellm/proxy/example_config_yaml/enterprise_config.yaml:/app/config.yaml \
- my-app:latest \
+ litellm-docker-database:ci \
--config /app/config.yaml \
--port 4000 \
--detailed_debug
@@ -2558,9 +2806,13 @@ jobs:
- run:
name: Wait for PostgreSQL to be ready
command: dockerize -wait tcp://localhost:5432 -timeout 1m
+ - attach_workspace:
+ at: ~/project
- run:
- name: Build Docker image
- command: docker build -t my-app:latest -f ./docker/Dockerfile.database .
+ 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
# intentionally give bad redis credentials here
@@ -2584,7 +2836,7 @@ jobs:
--add-host host.docker.internal:host-gateway \
--name my-app \
-v $(pwd)/litellm/proxy/example_config_yaml/spend_tracking_config.yaml:/app/config.yaml \
- my-app:latest \
+ litellm-docker-database:ci \
--config /app/config.yaml \
--port 4000 \
--detailed_debug \
@@ -2671,9 +2923,13 @@ jobs:
- run:
name: Wait for PostgreSQL to be ready
command: dockerize -wait tcp://localhost:5432 -timeout 1m
+ - attach_workspace:
+ at: ~/project
- run:
- name: Build Docker image
- command: docker build -t my-app:latest -f ./docker/Dockerfile.database .
+ 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 1
# intentionally give bad redis credentials here
@@ -2693,7 +2949,7 @@ jobs:
--add-host host.docker.internal:host-gateway \
--name my-app \
-v $(pwd)/litellm/proxy/example_config_yaml/multi_instance_simple_config.yaml:/app/config.yaml \
- my-app:latest \
+ litellm-docker-database:ci \
--config /app/config.yaml \
--port 4000 \
--detailed_debug \
@@ -2714,7 +2970,7 @@ jobs:
--add-host host.docker.internal:host-gateway \
--name my-app-2 \
-v $(pwd)/litellm/proxy/example_config_yaml/multi_instance_simple_config.yaml:/app/config.yaml \
- my-app:latest \
+ litellm-docker-database:ci \
--config /app/config.yaml \
--port 4001 \
--detailed_debug
@@ -2807,9 +3063,13 @@ jobs:
- run:
name: Wait for PostgreSQL to be ready
command: dockerize -wait tcp://localhost:5432 -timeout 1m
+ - attach_workspace:
+ at: ~/project
- run:
- name: Build Docker image
- command: docker build -t my-app:latest -f ./docker/Dockerfile.database .
+ 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
# intentionally give bad redis credentials here
@@ -2824,7 +3084,7 @@ jobs:
--add-host host.docker.internal:host-gateway \
--name my-app \
-v $(pwd)/litellm/proxy/example_config_yaml/store_model_db_config.yaml:/app/config.yaml \
- my-app:latest \
+ litellm-docker-database:ci \
--config /app/config.yaml \
--port 4000 \
--detailed_debug \
@@ -3039,10 +3299,13 @@ jobs:
- run:
name: Wait for PostgreSQL to be ready
command: dockerize -wait tcp://localhost:5432 -timeout 1m
- # Run pytest and generate JUnit XML report
+ - attach_workspace:
+ at: ~/project
- run:
- name: Build Docker image
- command: docker build -t my-app:latest -f ./docker/Dockerfile.database .
+ 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
command: |
@@ -3064,7 +3327,7 @@ jobs:
--name my-app \
-v $(pwd)/litellm/proxy/example_config_yaml/pass_through_config.yaml:/app/config.yaml \
-v $(pwd)/litellm/proxy/example_config_yaml/custom_auth_basic.py:/app/custom_auth_basic.py \
- my-app:latest \
+ litellm-docker-database:ci \
--config /app/config.yaml \
--port 4000 \
--detailed_debug \
@@ -3165,7 +3428,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 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
@@ -3402,6 +3665,37 @@ jobs:
--coverage.reporter=html \
--coverage.reportsDirectory=coverage/html
+ build_docker_database_image:
+ machine:
+ image: ubuntu-2204:2023.10.1
+ resource_class: xlarge
+ working_directory: ~/project
+ steps:
+ - checkout
+
+ - run:
+ name: Upgrade Docker
+ command: |
+ curl -fsSL https://get.docker.com | sh
+ docker version
+
+ - run:
+ name: Build Docker image
+ command: |
+ docker build \
+ -t litellm-docker-database:ci \
+ -f docker/Dockerfile.database .
+
+ - run:
+ name: Save Docker image to workspace root
+ command: |
+ docker save litellm-docker-database:ci | gzip > litellm-docker-database.tar.gz
+
+ - persist_to_workspace:
+ root: .
+ paths:
+ - litellm-docker-database.tar.gz
+
e2e_ui_testing:
machine:
image: ubuntu-2204:2023.10.1
@@ -3413,68 +3707,54 @@ jobs:
- attach_workspace:
at: ~/project
- run:
- name: Upgrade Docker to v24.x (API 1.44+)
+ name: Load Docker Database Image
command: |
- curl -fsSL https://get.docker.com | sh
- sudo usermod -aG docker $USER
- docker version
- - run:
- name: Install Python 3.9
- 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.9 -y
- conda activate myenv
- python --version
+ gunzip -c litellm-docker-database.tar.gz | docker load
+ docker images | grep litellm-docker-database
- run:
name: Install Dependencies
command: |
npm install -D @playwright/test
- npm install @google-cloud/vertexai
- pip install "pytest==7.3.1"
- pip install "pytest-retry==1.6.3"
- pip install "pytest-asyncio==0.21.1"
- pip install aiohttp
- pip install "openai==1.100.1"
- python -m pip install --upgrade pip
- pip install "pydantic==2.10.2"
- pip install "pytest==7.3.1"
- pip install "pytest-mock==3.12.0"
- pip install "pytest-asyncio==0.21.1"
- pip install "mypy==1.18.2"
- pip install pyarrow
- pip install numpydoc
- pip install prisma
- pip install fastapi
- pip install jsonschema
- pip install "httpx==0.24.1"
- pip install "anyio==3.7.1"
- pip install "asyncio==3.4.3"
- run:
name: Install Playwright Browsers
command: |
npx playwright install
-
- run:
- name: Build Docker image
- command: docker build -t my-app:latest -f ./docker/Dockerfile.database .
+ name: Install Neon CLI
+ command: |
+ npm i -g neonctl
+ - run:
+ name: Create Neon branch
+ command: |
+ export EXPIRES_AT=$(date -u -d "+3 hours" +"%Y-%m-%dT%H:%M:%SZ")
+ echo "Expires at: $EXPIRES_AT"
+ neon branches create \
+ --project-id $NEON_PROJECT_ID \
+ --name preview/commit-${CIRCLE_SHA1:0:7} \
+ --expires-at $EXPIRES_AT \
+ --parent br-fancy-paper-ad1olsb3 \
+ --api-key $NEON_API_KEY || true
- run:
name: Run Docker container
command: |
+ E2E_UI_TEST_DATABASE_URL=$(neon connection-string \
+ --project-id $NEON_PROJECT_ID \
+ --api-key $NEON_API_KEY \
+ --branch preview/commit-${CIRCLE_SHA1:0:7} \
+ --database-name yuneng-trial-db \
+ --role neondb_owner)
+ echo $E2E_UI_TEST_DATABASE_URL
docker run -d \
-p 4000:4000 \
- -e DATABASE_URL=$SMALL_DATABASE_URL \
+ -e DATABASE_URL=$E2E_UI_TEST_DATABASE_URL \
-e LITELLM_MASTER_KEY="sk-1234" \
-e OPENAI_API_KEY=$OPENAI_API_KEY \
-e UI_USERNAME="admin" \
-e UI_PASSWORD="gm" \
-e LITELLM_LICENSE=$LITELLM_LICENSE \
- --name my-app \
+ --name litellm-docker-database \
-v $(pwd)/litellm/proxy/example_config_yaml/simple_config.yaml:/app/config.yaml \
- my-app:latest \
+ litellm-docker-database:ci \
--config /app/config.yaml \
--port 4000 \
--detailed_debug
@@ -3488,7 +3768,7 @@ jobs:
sudo rm dockerize-linux-amd64-v0.6.1.tar.gz
- run:
name: Start outputting logs
- command: docker logs -f my-app
+ command: docker logs -f litellm-docker-database
background: true
- run:
name: Wait for app to be ready
@@ -3496,7 +3776,10 @@ jobs:
- run:
name: Run Playwright Tests
command: |
- npx playwright test e2e_ui_tests/ --reporter=html --output=test-results
+ npx playwright test \
+ --config ui/litellm-dashboard/e2e_tests/playwright.config.ts \
+ --reporter=html \
+ --output=test-results
no_output_timeout: 120m
- store_artifacts:
path: test-results
@@ -3600,7 +3883,13 @@ workflows:
only:
- main
- /litellm_.*/
- - local_testing:
+ - local_testing_part1:
+ filters:
+ branches:
+ only:
+ - main
+ - /litellm_.*/
+ - local_testing_part2:
filters:
branches:
only:
@@ -3686,9 +3975,17 @@ workflows:
only:
- main
- /litellm_.*/
+ - build_docker_database_image:
+ filters:
+ branches:
+ only:
+ - main
+ - /litellm_.*/
- e2e_ui_testing:
+ context: e2e_ui_tests
requires:
- ui_build
+ - build_docker_database_image
filters:
branches:
only:
@@ -3701,30 +3998,40 @@ workflows:
- main
- /litellm_.*/
- e2e_openai_endpoints:
+ requires:
+ - build_docker_database_image
filters:
branches:
only:
- main
- /litellm_.*/
- proxy_logging_guardrails_model_info_tests:
+ requires:
+ - build_docker_database_image
filters:
branches:
only:
- main
- /litellm_.*/
- proxy_spend_accuracy_tests:
+ requires:
+ - build_docker_database_image
filters:
branches:
only:
- main
- /litellm_.*/
- proxy_multi_instance_tests:
+ requires:
+ - build_docker_database_image
filters:
branches:
only:
- main
- /litellm_.*/
- proxy_store_model_in_db_tests:
+ requires:
+ - build_docker_database_image
filters:
branches:
only:
@@ -3737,6 +4044,8 @@ workflows:
- main
- /litellm_.*/
- proxy_pass_through_endpoint_tests:
+ requires:
+ - build_docker_database_image
filters:
branches:
only:
@@ -3808,6 +4117,18 @@ workflows:
only:
- main
- /litellm_.*/
+ - litellm_mapped_tests_integrations:
+ filters:
+ branches:
+ only:
+ - main
+ - /litellm_.*/
+ - litellm_mapped_tests_litellm_core_utils:
+ filters:
+ branches:
+ only:
+ - main
+ - /litellm_.*/
- batches_testing:
filters:
branches:
@@ -3856,6 +4177,8 @@ workflows:
- litellm_mapped_tests_proxy
- litellm_mapped_tests_llms
- litellm_mapped_tests_core
+ - litellm_mapped_tests_integrations
+ - litellm_mapped_tests_litellm_core_utils
- litellm_mapped_enterprise_tests
- batches_testing
- litellm_utils_testing
@@ -3871,10 +4194,13 @@ 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:
+ requires:
+ - build_docker_database_image
filters:
branches:
only:
@@ -3912,7 +4238,8 @@ workflows:
- publish_to_pypi:
requires:
- mypy_linting
- - local_testing
+ - local_testing_part1
+ - local_testing_part2
- build_and_test
- e2e_openai_endpoints
- test_bad_database_url
@@ -3925,6 +4252,8 @@ workflows:
- litellm_mapped_tests_proxy
- litellm_mapped_tests_llms
- litellm_mapped_tests_core
+ - litellm_mapped_tests_integrations
+ - litellm_mapped_tests_litellm_core_utils
- litellm_mapped_enterprise_tests
- batches_testing
- litellm_utils_testing
diff --git a/.circleci/requirements.txt b/.circleci/requirements.txt
index 2294c84813c..8c44dc18305 100644
--- a/.circleci/requirements.txt
+++ b/.circleci/requirements.txt
@@ -8,12 +8,12 @@ redis==5.2.1
redisvl==0.4.1
anthropic
orjson==3.10.12 # fast /embedding responses
-pydantic==2.10.2
+pydantic==2.11.0
google-cloud-aiplatform==1.43.0
google-cloud-iam==2.19.1
fastapi-sso==0.16.0
uvloop==0.21.0
-mcp==1.10.1 # for MCP server
+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
diff --git a/.gitguardian.yaml b/.gitguardian.yaml
new file mode 100644
index 00000000000..1eeec0677af
--- /dev/null
+++ b/.gitguardian.yaml
@@ -0,0 +1,111 @@
+version: 2
+
+secret:
+ # Exclude files and paths by globbing
+ ignored_paths:
+ - "**/*.whl"
+ - "**/*.pyc"
+ - "**/__pycache__/**"
+ - "**/node_modules/**"
+ - "**/dist/**"
+ - "**/build/**"
+ - "**/.git/**"
+ - "**/venv/**"
+ - "**/.venv/**"
+
+ # Large data/metadata files that don't need scanning
+ - "**/model_prices_and_context_window*.json"
+ - "**/*_metadata/*.txt"
+ - "**/tokenizers/*.json"
+ - "**/tokenizers/*"
+ - "miniconda.sh"
+
+ # Build outputs and static assets
+ - "litellm/proxy/_experimental/out/**"
+ - "ui/litellm-dashboard/public/**"
+ - "**/swagger/*.js"
+ - "**/*.woff"
+ - "**/*.woff2"
+ - "**/*.avif"
+ - "**/*.webp"
+
+ # Test data files
+ - "**/tests/**/data_map.txt"
+ - "tests/**/*.txt"
+
+ # Documentation and other non-code files
+ - "docs/**"
+ - "**/*.md"
+ - "**/*.lock"
+ - "poetry.lock"
+ - "package-lock.json"
+
+ # Ignore security incidents with the SHA256 of the occurrence (false positives)
+ ignored_matches:
+ # === Current detected false positives (SHA-based) ===
+
+ # gcs_pub_sub_body - folder name, not a password
+ - name: GCS pub/sub test folder name
+ match: 75f377c456eede69e5f6e47399ccee6016a2a93cc5dd11db09cc5b1359ae569a
+
+ # os.environ/APORIA_API_KEY_1 - environment variable reference
+ - name: Environment variable reference APORIA_API_KEY_1
+ match: e2ddeb8b88eca97a402559a2be2117764e11c074d86159ef9ad2375dea188094
+
+ # os.environ/APORIA_API_KEY_2 - environment variable reference
+ - name: Environment variable reference APORIA_API_KEY_2
+ match: 09aa39a29e050b86603aa55138af1ff08fb86a4582aa965c1bd0672e1575e052
+
+ # oidc/circleci_v2/ - test authentication path, not a secret
+ - name: OIDC CircleCI test path
+ match: feb3475e1f89a65b7b7815ac4ec597e18a9ec1847742ad445c36ca617b536e15
+
+ # text-davinci-003 - OpenAI model identifier, not a secret
+ - name: OpenAI model identifier text-davinci-003
+ match: c489000cf6c7600cee0eefb80ad0965f82921cfb47ece880930eb7e7635cf1f1
+
+ # Base64 Basic Auth in test_pass_through_endpoints.py - test fixture, not a real secret
+ - name: Test Base64 Basic Auth header in pass_through_endpoints test
+ match: 61bac0491f395040617df7ef6d06029eac4d92a4457ac784978db80d97be1ae0
+
+ # PostgreSQL password "postgres" in CI configs - standard test database password
+ - name: Test PostgreSQL password in CI configurations
+ match: 6e0d657eb1f0fbc40cf0b8f3c3873ef627cc9cb7c4108d1c07d979c04bc8a4bb
+
+ # Bearer token in locustfile.py - test/example API key for load testing
+ - name: Test Bearer token in locustfile load test
+ match: 2a0abc2b0c3c1760a51ffcdf8d6b1d384cef69af740504b1cfa82dd70cdc7ff9
+
+ # Inkeep API key in docusaurus.config.js - public documentation site key
+ - name: Inkeep API key in documentation config
+ match: c366657791bfb5fc69045ec11d49452f09a0aebbc8648f94e2469b4025e29a75
+
+ # Langfuse credentials in test_completion.py - test credentials for integration test
+ - name: Langfuse test credentials in test_completion
+ match: c39310f68cc3d3e22f7b298bb6353c4f45759adcc37080d8b7f4e535d3cfd7f4
+
+ # Test password "sk-1234" in e2e test fixtures - test fixture, not a real secret
+ - name: Test password in e2e test fixtures
+ match: ce32b547202e209ec1dd50107b64be4cfcf2eb15c3b4f8e9dc611ef747af634f
+
+ # === Preventive patterns for test keys (pattern-based) ===
+
+ # Test API keys (124 instances across 45 files)
+ - name: Test API keys with sk-test prefix
+ match: sk-test-
+
+ # Mock API keys
+ - name: Mock API keys with sk-mock prefix
+ match: sk-mock-
+
+ # Fake API keys
+ - name: Fake API keys with sk-fake prefix
+ match: sk-fake-
+
+ # Generic test API key patterns
+ - name: Test API key patterns
+ match: test-api-key
+
+ - name: Short fake sk keys (1–9 digits only)
+ match: \bsk-\d{1,9}\b
+
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
index 39b46cba999..bbe4b76775d 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.yml
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -7,6 +7,16 @@ body:
attributes:
value: |
Thanks for taking the time to fill out this bug report!
+
+ **💡 Tip:** See our [Troubleshooting Guide](https://docs.litellm.ai/docs/troubleshoot) for what information to include.
+ - type: checkboxes
+ id: duplicate-check
+ attributes:
+ label: Check for existing issues
+ description: Please search to see if an issue already exists for the bug you encountered.
+ options:
+ - label: I have searched the existing issues and checked that my issue is not a duplicate.
+ required: true
- type: textarea
id: what-happened
attributes:
@@ -16,6 +26,21 @@ body:
value: "A bug happened!"
validations:
required: true
+ - type: textarea
+ id: steps-to-reproduce
+ attributes:
+ label: Steps to Reproduce
+ description: Please provide detailed steps to reproduce this bug(A curl/python code to reproduce the bug)
+ placeholder: |
+ 1. config.yaml file/ .env file/ etc.
+ 2. Run the following code...
+ 3. Observe the error...
+ value: |
+ 1.
+ 2.
+ 3.
+ validations:
+ required: true
- type: textarea
id: logs
attributes:
@@ -27,6 +52,7 @@ body:
attributes:
label: What part of LiteLLM is this about?
options:
+ - ''
- "SDK (litellm Python package)"
- "Proxy"
- "UI Dashboard"
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
index 96b95cc7f02..4cc42901897 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.yml
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -7,6 +7,14 @@ body:
attributes:
value: |
Thanks for making LiteLLM better!
+ - type: checkboxes
+ id: duplicate-check
+ attributes:
+ label: Check for existing issues
+ description: Please search to see if an issue already exists for the feature you are requesting.
+ options:
+ - label: I have searched the existing issues and checked that my issue is not a duplicate.
+ required: true
- type: textarea
id: the-feature
attributes:
@@ -27,6 +35,7 @@ body:
attributes:
label: What part of LiteLLM is this about?
options:
+ - ''
- "SDK (litellm Python package)"
- "Proxy"
- "UI Dashboard"
diff --git a/.github/workflows/check_duplicate_issues.yml b/.github/workflows/check_duplicate_issues.yml
new file mode 100644
index 00000000000..14d6964fcdb
--- /dev/null
+++ b/.github/workflows/check_duplicate_issues.yml
@@ -0,0 +1,29 @@
+name: Check Duplicate Issues
+
+on:
+ issues:
+ types: [opened, edited]
+
+jobs:
+ check-duplicate:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ contents: read
+ steps:
+ - name: Check for potential duplicates
+ uses: wow-actions/potential-duplicates@v1
+ with:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ label: potential-duplicate
+ threshold: 0.6
+ reaction: eyes
+ comment: |
+ **⚠️ Potential duplicate detected**
+
+ This issue appears similar to existing issue(s):
+ {{#issues}}
+ - [#{{number}}]({{html_url}}) - {{title}} ({{accuracy}}% similar)
+ {{/issues}}
+
+ Please review the linked issue(s) to see if they address your concern. If this is not a duplicate, please provide additional context to help us understand the difference.
diff --git a/.github/workflows/create_daily_staging_branch.yml b/.github/workflows/create_daily_staging_branch.yml
index a97cf6f9740..9d0093e8b16 100644
--- a/.github/workflows/create_daily_staging_branch.yml
+++ b/.github/workflows/create_daily_staging_branch.yml
@@ -2,7 +2,7 @@ name: Create Daily Staging Branch
on:
schedule:
- - cron: '0 0 * * *' # Runs daily at midnight UTC
+ - cron: '0 0,12 * * *' # Runs every 12 hours at midnight and noon UTC
workflow_dispatch: # Allow manual trigger
jobs:
@@ -24,7 +24,7 @@ jobs:
git config user.email "github-actions[bot]@users.noreply.github.com"
# Generate branch name with MM_DD_YYYY format
- BRANCH_NAME="litellm_staging_$(date +'%m_%d_%Y')"
+ BRANCH_NAME="litellm_oss_staging_$(date +'%m_%d_%Y')"
echo "Creating branch: $BRANCH_NAME"
# Fetch all branches
diff --git a/.github/workflows/ghcr_deploy.yml b/.github/workflows/ghcr_deploy.yml
index f574ec9c202..f67538a4272 100644
--- a/.github/workflows/ghcr_deploy.yml
+++ b/.github/workflows/ghcr_deploy.yml
@@ -5,6 +5,7 @@ on:
inputs:
tag:
description: "The tag version you want to build"
+ required: true
release_type:
description: "The release type you want to build. Can be 'latest', 'stable', 'dev', 'rc'"
type: string
@@ -319,59 +320,37 @@ jobs:
run: |
echo "REPO_OWNER=`echo ${{github.repository_owner}} | tr '[:upper:]' '[:lower:]'`" >>${GITHUB_ENV}
- - name: Get LiteLLM Latest Tag
- id: current_app_tag
- shell: bash
- run: |
- LATEST_TAG=$(git describe --tags --exclude "*dev*" --abbrev=0)
- if [ -z "${LATEST_TAG}" ]; then
- echo "latest_tag=latest" | tee -a $GITHUB_OUTPUT
- else
- echo "latest_tag=${LATEST_TAG}" | tee -a $GITHUB_OUTPUT
- fi
-
- - name: Get last published chart version
- id: current_version
- shell: bash
- run: |
- CHART_LIST=$(helm show chart oci://${{ env.REGISTRY }}/${{ env.REPO_OWNER }}/${{ env.CHART_NAME }} 2>/dev/null || true)
- if [ -z "${CHART_LIST}" ]; then
- echo "current-version=0.1.0" | tee -a $GITHUB_OUTPUT
- else
- # Extract version and strip any prerelease suffix (e.g., 0.1.827-latest -> 0.1.827)
- VERSION=$(printf '%s' "${CHART_LIST}" | grep '^version:' | awk 'BEGIN{FS=":"}{print $2}' | tr -d " " | cut -d'-' -f1)
- echo "current-version=${VERSION}" | tee -a $GITHUB_OUTPUT
- fi
- env:
- HELM_EXPERIMENTAL_OCI: '1'
-
- # Automatically update the helm chart version one "patch" level
- - name: Bump release version
- id: bump_version
- uses: christian-draeger/increment-semantic-version@1.1.0
- with:
- current-version: ${{ steps.current_version.outputs.current-version || '0.1.0' }}
- version-fragment: 'bug'
-
- # Add suffix for non-stable releases (semantic versioning)
- - name: Calculate chart version with prerelease suffix
+ # Sync Helm chart version with LiteLLM release version (1-1 versioning)
+ # This allows users to easily map Helm chart versions to LiteLLM versions
+ # See: https://codefresh.io/docs/docs/ci-cd-guides/helm-best-practices/
+ - name: Calculate chart and app versions
id: chart_version
shell: bash
run: |
- BASE_VERSION="${{ steps.bump_version.outputs.next-version || '0.1.0' }}"
+ INPUT_TAG="${{ github.event.inputs.tag }}"
RELEASE_TYPE="${{ github.event.inputs.release_type }}"
- if [ "$RELEASE_TYPE" = "stable" ]; then
- echo "version=${BASE_VERSION}" | tee -a $GITHUB_OUTPUT
- else
- echo "version=${BASE_VERSION}-${RELEASE_TYPE}" | tee -a $GITHUB_OUTPUT
+
+ # Chart version = LiteLLM version without 'v' prefix (Helm semver convention)
+ # v1.81.0 -> 1.81.0, v1.81.0.rc.1 -> 1.81.0.rc.1
+ CHART_VERSION="${INPUT_TAG#v}"
+
+ # Add suffix for 'latest' releases (rc already has suffix in tag)
+ if [ "$RELEASE_TYPE" = "latest" ]; then
+ CHART_VERSION="${CHART_VERSION}-latest"
fi
+ # App version = Docker tag (keeps 'v' prefix to match Docker image tags)
+ APP_VERSION="${INPUT_TAG}"
+
+ echo "version=${CHART_VERSION}" | tee -a $GITHUB_OUTPUT
+ echo "app_version=${APP_VERSION}" | tee -a $GITHUB_OUTPUT
+
- uses: ./.github/actions/helm-oci-chart-releaser
with:
name: ${{ env.CHART_NAME }}
repository: ${{ env.REPO_OWNER }}
- tag: ${{ github.event.inputs.chartVersion || steps.chart_version.outputs.version || '0.1.0' }}
- app_version: ${{ steps.current_app_tag.outputs.latest_tag }}
+ tag: ${{ steps.chart_version.outputs.version }}
+ app_version: ${{ steps.chart_version.outputs.app_version }}
path: deploy/charts/${{ env.CHART_NAME }}
registry: ${{ env.REGISTRY }}
registry_username: ${{ github.actor }}
diff --git a/.github/workflows/ghcr_helm_deploy.yml b/.github/workflows/ghcr_helm_deploy.yml
index f78dc6f0f3f..21b2eaafe19 100644
--- a/.github/workflows/ghcr_helm_deploy.yml
+++ b/.github/workflows/ghcr_helm_deploy.yml
@@ -1,10 +1,12 @@
-# this workflow is triggered by an API call when there is a new PyPI release of LiteLLM
+# Standalone workflow to publish LiteLLM Helm Chart
+# Note: The main ghcr_deploy.yml workflow also publishes the Helm chart as part of a full release
name: Build, Publish LiteLLM Helm Chart. New Release
on:
workflow_dispatch:
inputs:
- chartVersion:
- description: "Update the helm chart's version to this"
+ tag:
+ description: "LiteLLM version tag (e.g., v1.81.0)"
+ required: true
# Defines two custom environment variables for the workflow. Used for the Container registry domain, and a name for the Docker image that this workflow builds.
env:
@@ -31,24 +33,22 @@ jobs:
run: |
echo "REPO_OWNER=`echo ${{github.repository_owner}} | tr '[:upper:]' '[:lower:]'`" >>${GITHUB_ENV}
- - name: Get LiteLLM Latest Tag
- id: current_app_tag
- uses: WyriHaximus/github-action-get-previous-tag@v1.3.0
-
- - name: Get last published chart version
- id: current_version
+ # Sync Helm chart version with LiteLLM release version (1-1 versioning)
+ - name: Calculate chart and app versions
+ id: chart_version
shell: bash
- run: helm show chart oci://${{ env.REGISTRY }}/${{ env.REPO_OWNER }}/litellm-helm | grep '^version:' | awk 'BEGIN{FS=":"}{print "current-version="$2}' | tr -d " " | tee -a $GITHUB_OUTPUT
- env:
- HELM_EXPERIMENTAL_OCI: '1'
+ run: |
+ INPUT_TAG="${{ github.event.inputs.tag }}"
- # Automatically update the helm chart version one "patch" level
- - name: Bump release version
- id: bump_version
- uses: christian-draeger/increment-semantic-version@1.1.0
- with:
- current-version: ${{ steps.current_version.outputs.current-version || '0.1.0' }}
- version-fragment: 'bug'
+ # Chart version = LiteLLM version without 'v' prefix
+ # v1.81.0 -> 1.81.0
+ CHART_VERSION="${INPUT_TAG#v}"
+
+ # App version = Docker tag (keeps 'v' prefix)
+ APP_VERSION="${INPUT_TAG}"
+
+ echo "version=${CHART_VERSION}" | tee -a $GITHUB_OUTPUT
+ echo "app_version=${APP_VERSION}" | tee -a $GITHUB_OUTPUT
- name: Lint helm chart
run: helm lint deploy/charts/litellm-helm
@@ -57,8 +57,8 @@ jobs:
with:
name: litellm-helm
repository: ${{ env.REPO_OWNER }}
- tag: ${{ github.event.inputs.chartVersion || steps.bump_version.outputs.next-version || '0.1.0' }}
- app_version: ${{ steps.current_app_tag.outputs.tag || 'latest' }}
+ tag: ${{ steps.chart_version.outputs.version }}
+ app_version: ${{ steps.chart_version.outputs.app_version }}
path: deploy/charts/litellm-helm
registry: ${{ env.REGISTRY }}
registry_username: ${{ github.actor }}
diff --git a/.github/workflows/label-component.yml b/.github/workflows/label-component.yml
index c0f9436288c..fd079fce6c1 100644
--- a/.github/workflows/label-component.yml
+++ b/.github/workflows/label-component.yml
@@ -11,134 +11,106 @@ jobs:
permissions:
issues: write
steps:
- - name: Add SDK label
- if: contains(github.event.issue.body, 'SDK (litellm Python package)')
+ - name: Add component labels
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
- const labelName = 'sdk';
- try {
- await github.rest.issues.getLabel({
- owner: context.repo.owner,
- repo: context.repo.repo,
- name: labelName
- });
- } catch (error) {
- if (error.status === 404) {
- await github.rest.issues.createLabel({
- owner: context.repo.owner,
- repo: context.repo.repo,
- name: labelName,
- color: '0E7C86',
- description: 'Issues related to the litellm Python SDK'
- });
- } else {
- throw error;
- }
- }
- await github.rest.issues.addLabels({
- owner: context.repo.owner,
- repo: context.repo.repo,
- issue_number: context.issue.number,
- labels: [labelName]
- });
+ const body = context.payload.issue.body;
+ if (!body) return;
- - name: Add Proxy label
- if: contains(github.event.issue.body, 'Proxy')
- uses: actions/github-script@v7
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- const labelName = 'proxy';
- try {
- await github.rest.issues.getLabel({
- owner: context.repo.owner,
- repo: context.repo.repo,
- name: labelName
- });
- } catch (error) {
- if (error.status === 404) {
- await github.rest.issues.createLabel({
- owner: context.repo.owner,
- repo: context.repo.repo,
- name: labelName,
- color: '5319E7',
- description: 'Issues related to the LiteLLM Proxy'
- });
- } else {
- throw error;
+ // Define component mappings with regex patterns that handle flexible whitespace
+ const components = [
+ {
+ pattern: /What part of LiteLLM is this about\?\s*SDK \(litellm Python package\)/,
+ label: 'sdk',
+ color: '0E7C86',
+ description: 'Issues related to the litellm Python SDK'
+ },
+ {
+ pattern: /What part of LiteLLM is this about\?\s*Proxy/,
+ label: 'proxy',
+ color: '5319E7',
+ description: 'Issues related to the LiteLLM Proxy'
+ },
+ {
+ pattern: /What part of LiteLLM is this about\?\s*UI Dashboard/,
+ label: 'ui-dashboard',
+ color: 'D876E3',
+ description: 'Issues related to the LiteLLM UI Dashboard'
+ },
+ {
+ pattern: /What part of LiteLLM is this about\?\s*Docs/,
+ label: 'docs',
+ color: 'FBCA04',
+ description: 'Issues related to LiteLLM documentation'
}
- }
- await github.rest.issues.addLabels({
- owner: context.repo.owner,
- repo: context.repo.repo,
- issue_number: context.issue.number,
- labels: [labelName]
- });
+ ];
- - name: Add UI Dashboard label
- if: contains(github.event.issue.body, 'UI Dashboard')
- uses: actions/github-script@v7
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- const labelName = 'ui-dashboard';
- try {
- await github.rest.issues.getLabel({
- owner: context.repo.owner,
- repo: context.repo.repo,
- name: labelName
- });
- } catch (error) {
- if (error.status === 404) {
- await github.rest.issues.createLabel({
- owner: context.repo.owner,
- repo: context.repo.repo,
- name: labelName,
- color: 'D876E3',
- description: 'Issues related to the LiteLLM UI Dashboard'
- });
- } else {
- throw error;
- }
- }
- await github.rest.issues.addLabels({
- owner: context.repo.owner,
- repo: context.repo.repo,
- issue_number: context.issue.number,
- labels: [labelName]
- });
+ // Find matching component
+ for (const component of components) {
+ if (component.pattern.test(body)) {
+ // Ensure label exists
+ try {
+ await github.rest.issues.getLabel({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ name: component.label
+ });
+ } catch (error) {
+ if (error.status === 404) {
+ await github.rest.issues.createLabel({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ name: component.label,
+ color: component.color,
+ description: component.description
+ });
+ }
+ }
- - name: Add Docs label
- if: contains(github.event.issue.body, 'Docs')
- uses: actions/github-script@v7
- with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
- script: |
- const labelName = 'docs';
- try {
- await github.rest.issues.getLabel({
- owner: context.repo.owner,
- repo: context.repo.repo,
- name: labelName
- });
- } catch (error) {
- if (error.status === 404) {
- await github.rest.issues.createLabel({
+ // Add label to issue
+ await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
- name: labelName,
- color: 'FBCA04',
- description: 'Issues related to LiteLLM documentation'
+ issue_number: context.issue.number,
+ labels: [component.label]
});
- } else {
- throw error;
+
+ break;
}
}
- await github.rest.issues.addLabels({
- owner: context.repo.owner,
- repo: context.repo.repo,
- issue_number: context.issue.number,
- labels: [labelName]
- });
+
+ // Check for 'claude code' keyword (can be applied alongside component labels)
+ if (/claude code/i.test(body)) {
+ const claudeLabel = {
+ name: 'claude code',
+ color: '7c3aed',
+ description: 'Issues related to Claude Code usage'
+ };
+
+ try {
+ await github.rest.issues.getLabel({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ name: claudeLabel.name
+ });
+ } catch (error) {
+ if (error.status === 404) {
+ await github.rest.issues.createLabel({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ name: claudeLabel.name,
+ color: claudeLabel.color,
+ description: claudeLabel.description
+ });
+ }
+ }
+
+ await github.rest.issues.addLabels({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: context.issue.number,
+ labels: [claudeLabel.name]
+ });
+ }
diff --git a/.github/workflows/publish-migrations.yml b/.github/workflows/publish-migrations.yml
index 8e5a67bcf85..a5187cb2f55 100644
--- a/.github/workflows/publish-migrations.yml
+++ b/.github/workflows/publish-migrations.yml
@@ -13,6 +13,7 @@ on:
jobs:
publish-migrations:
+ if: github.repository == 'BerriAI/litellm'
runs-on: ubuntu-latest
services:
postgres:
diff --git a/.github/workflows/test-litellm.yml b/.github/workflows/test-litellm.yml
index a38a29491ef..ba32dc1bf54 100644
--- a/.github/workflows/test-litellm.yml
+++ b/.github/workflows/test-litellm.yml
@@ -35,6 +35,7 @@ jobs:
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 "openapi-core"
- name: Setup litellm-enterprise as local package
run: |
cd enterprise
diff --git a/.github/workflows/test-mcp.yml b/.github/workflows/test-mcp.yml
index 64363c6f96d..e19e67c9c4f 100644
--- a/.github/workflows/test-mcp.yml
+++ b/.github/workflows/test-mcp.yml
@@ -34,8 +34,8 @@ jobs:
poetry run pip install "pytest-cov==5.0.0"
poetry run pip install "pytest-asyncio==0.21.1"
poetry run pip install "respx==0.22.0"
- poetry run pip install "pydantic==2.10.2"
- poetry run pip install "mcp==1.10.1"
+ poetry run pip install "pydantic==2.11.0"
+ poetry run pip install "mcp==1.25.0"
poetry run pip install pytest-xdist
- name: Setup litellm-enterprise as local package
diff --git a/.gitignore b/.gitignore
index aa973201fd1..0248d68c1e1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,6 @@
.python-version
.venv
+.venv_policy_test
.env
.newenv
newenv/*
@@ -59,6 +60,7 @@ 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
@@ -100,3 +102,8 @@ 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
+**/test-results
+**/playwright-report
+**/*.storageState.json
+**/coverage
\ No newline at end of file
diff --git a/AGENTS.md b/AGENTS.md
index 2c778dc0d71..61afbd035fe 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -49,6 +49,27 @@ LiteLLM is a unified interface for 100+ LLMs that:
- Test provider-specific functionality thoroughly
- Consider adding load tests for performance-critical changes
+### MAKING CODE CHANGES FOR THE UI (IGNORE FOR BACKEND)
+
+1. **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**:
+ - 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`)
+ - **Wrap user interactions in `act()`**: Always wrap `fireEvent` calls with `act()` to ensure React state updates are properly handled
+ - **Use `query` methods for absence checks**: Use `queryBy*` methods (not `getBy*`) when expecting an element to NOT be present
+ - **Test names must start with "should"**: All test names should follow the pattern `it("should ...")`
+ - **Mock external dependencies**: Check `setupTests.ts` for global mocks and mock child components/networking calls as needed
+ - **Structure tests properly**:
+ - First test should verify the component renders successfully
+ - Subsequent tests should focus on functionality and user interactions
+ - Use `waitFor` for async operations that aren't already awaited
+ - **Avoid using `querySelector`**: Prefer React Testing Library queries over direct DOM manipulation
+
### IMPORTANT PATTERNS
1. **Function/Tool Calling**:
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
new file mode 100644
index 00000000000..c114a838d6d
--- /dev/null
+++ b/ARCHITECTURE.md
@@ -0,0 +1,398 @@
+# LiteLLM Architecture - LiteLLM SDK + AI Gateway
+
+This document helps contributors understand where to make changes in LiteLLM.
+
+---
+
+## How It Works
+
+The LiteLLM AI Gateway (Proxy) uses the LiteLLM SDK internally for all LLM calls:
+
+```
+OpenAI SDK (client) ──▶ LiteLLM AI Gateway (proxy/) ──▶ LiteLLM SDK (litellm/) ──▶ LLM API
+Anthropic SDK (client) ──▶ LiteLLMAI Gateway (proxy/) ──▶ LiteLLM SDK (litellm/) ──▶ LLM API
+Any HTTP client ──▶ LiteLLMAI Gateway (proxy/) ──▶ LiteLLM SDK (litellm/) ──▶ LLM API
+```
+
+The **AI Gateway** adds authentication, rate limiting, budgets, and routing on top of the SDK.
+The **SDK** handles the actual LLM provider calls, request/response transformations, and streaming.
+
+---
+
+## 1. AI Gateway (Proxy) Request Flow
+
+The AI Gateway (`litellm/proxy/`) wraps the SDK with authentication, rate limiting, and management features.
+
+```mermaid
+sequenceDiagram
+ participant Client
+ participant ProxyServer as proxy/proxy_server.py
+ participant Auth as proxy/auth/user_api_key_auth.py
+ participant Redis as Redis Cache
+ participant Hooks as proxy/hooks/
+ participant Router as router.py
+ participant Main as main.py + utils.py
+ participant Handler as llms/custom_httpx/llm_http_handler.py
+ participant Transform as llms/{provider}/chat/transformation.py
+ participant Provider as LLM Provider API
+ participant CostCalc as cost_calculator.py
+ participant LoggingObj as litellm_logging.py
+ participant DBWriter as db/db_spend_update_writer.py
+ participant Postgres as PostgreSQL
+
+ %% Request Flow
+ Client->>ProxyServer: POST /v1/chat/completions
+ ProxyServer->>Auth: user_api_key_auth()
+ Auth->>Redis: Check API key cache
+ Redis-->>Auth: Key info + spend limits
+ ProxyServer->>Hooks: max_budget_limiter, parallel_request_limiter
+ Hooks->>Redis: Check/increment rate limit counters
+ ProxyServer->>Router: route_request()
+ Router->>Main: litellm.acompletion()
+ Main->>Handler: BaseLLMHTTPHandler.completion()
+ Handler->>Transform: ProviderConfig.transform_request()
+ Handler->>Provider: HTTP Request
+ Provider-->>Handler: Response
+ Handler->>Transform: ProviderConfig.transform_response()
+ Transform-->>Handler: ModelResponse
+ Handler-->>Main: ModelResponse
+
+ %% Cost Attribution (in utils.py wrapper)
+ Main->>LoggingObj: update_response_metadata()
+ LoggingObj->>CostCalc: _response_cost_calculator()
+ CostCalc->>CostCalc: completion_cost(tokens × price)
+ CostCalc-->>LoggingObj: response_cost
+ LoggingObj-->>Main: Set response._hidden_params["response_cost"]
+ Main-->>ProxyServer: ModelResponse (with cost in _hidden_params)
+
+ %% Response Headers + Async Logging
+ ProxyServer->>ProxyServer: Extract cost from hidden_params
+ ProxyServer->>LoggingObj: async_success_handler()
+ LoggingObj->>Hooks: async_log_success_event()
+ Hooks->>DBWriter: update_database(response_cost)
+ DBWriter->>Redis: Queue spend increment
+ DBWriter->>Postgres: Batch write spend logs (async)
+ ProxyServer-->>Client: ModelResponse + x-litellm-response-cost header
+```
+
+### Proxy Components
+
+```mermaid
+graph TD
+ subgraph "Incoming Request"
+ Client["POST /v1/chat/completions"]
+ end
+
+ subgraph "proxy/proxy_server.py"
+ Endpoint["chat_completion()"]
+ end
+
+ subgraph "proxy/auth/"
+ Auth["user_api_key_auth()"]
+ end
+
+ subgraph "proxy/"
+ PreCall["litellm_pre_call_utils.py"]
+ RouteRequest["route_llm_request.py"]
+ end
+
+ subgraph "litellm/"
+ Router["router.py"]
+ Main["main.py"]
+ end
+
+ subgraph "Infrastructure"
+ DualCache["DualCache
(in-memory + Redis)"]
+ Postgres["PostgreSQL
(keys, teams, spend logs)"]
+ end
+
+ Client --> Endpoint
+ Endpoint --> Auth
+ Auth --> DualCache
+ DualCache -.->|cache miss| Postgres
+ Auth --> PreCall
+ PreCall --> RouteRequest
+ RouteRequest --> Router
+ Router --> DualCache
+ Router --> Main
+ Main --> Client
+```
+
+**Key proxy files:**
+- `proxy/proxy_server.py` - Main API endpoints
+- `proxy/auth/` - Authentication (API keys, JWT, OAuth2)
+- `proxy/hooks/` - Proxy-level callbacks
+- `router.py` - Load balancing, fallbacks
+- `router_strategy/` - Routing algorithms (`lowest_latency.py`, `simple_shuffle.py`, etc.)
+
+**LLM-specific proxy endpoints:**
+
+| Endpoint | Directory | Purpose |
+|----------|-----------|---------|
+| `/v1/messages` | `proxy/anthropic_endpoints/` | Anthropic Messages API |
+| `/vertex-ai/*` | `proxy/vertex_ai_endpoints/` | Vertex AI passthrough |
+| `/gemini/*` | `proxy/google_endpoints/` | Google AI Studio passthrough |
+| `/v1/images/*` | `proxy/image_endpoints/` | Image generation |
+| `/v1/batches` | `proxy/batches_endpoints/` | Batch processing |
+| `/v1/files` | `proxy/openai_files_endpoints/` | File uploads |
+| `/v1/fine_tuning` | `proxy/fine_tuning_endpoints/` | Fine-tuning jobs |
+| `/v1/rerank` | `proxy/rerank_endpoints/` | Reranking |
+| `/v1/responses` | `proxy/response_api_endpoints/` | OpenAI Responses API |
+| `/v1/vector_stores` | `proxy/vector_store_endpoints/` | Vector stores |
+| `/*` (passthrough) | `proxy/pass_through_endpoints/` | Direct provider passthrough |
+
+**Proxy Hooks** (`proxy/hooks/__init__.py`):
+
+| Hook | File | Purpose |
+|------|------|---------|
+| `max_budget_limiter` | `proxy/hooks/max_budget_limiter.py` | Enforce budget limits |
+| `parallel_request_limiter` | `proxy/hooks/parallel_request_limiter_v3.py` | Rate limiting per key/user |
+| `cache_control_check` | `proxy/hooks/cache_control_check.py` | Cache validation |
+| `responses_id_security` | `proxy/hooks/responses_id_security.py` | Response ID validation |
+| `litellm_skills` | `proxy/hooks/skills_injection.py` | Skills injection |
+
+To add a new proxy hook, implement `CustomLogger` and register in `PROXY_HOOKS`.
+
+### Infrastructure Components
+
+The AI Gateway uses external infrastructure for persistence and caching:
+
+```mermaid
+graph LR
+ subgraph "AI Gateway (proxy/)"
+ Proxy["proxy_server.py"]
+ Auth["auth/user_api_key_auth.py"]
+ DBWriter["db/db_spend_update_writer.py
DBSpendUpdateWriter"]
+ InternalCache["utils.py
InternalUsageCache"]
+ CostCallback["hooks/proxy_track_cost_callback.py
_ProxyDBLogger"]
+ Scheduler["APScheduler
ProxyStartupEvent"]
+ end
+
+ subgraph "SDK (litellm/)"
+ Router["router.py
Router.cache (DualCache)"]
+ LLMCache["caching/caching_handler.py
LLMCachingHandler"]
+ CacheClass["caching/caching.py
Cache"]
+ end
+
+ subgraph "Redis (caching/redis_cache.py)"
+ RateLimit["Rate Limit Counters"]
+ SpendQueue["Spend Increment Queue"]
+ KeyCache["API Key Cache"]
+ TPM_RPM["TPM/RPM Tracking"]
+ Cooldowns["Deployment Cooldowns"]
+ LLMResponseCache["LLM Response Cache"]
+ end
+
+ subgraph "PostgreSQL (proxy/schema.prisma)"
+ Keys["LiteLLM_VerificationToken"]
+ Teams["LiteLLM_TeamTable"]
+ SpendLogs["LiteLLM_SpendLogs"]
+ Users["LiteLLM_UserTable"]
+ end
+
+ Auth --> InternalCache
+ InternalCache --> KeyCache
+ InternalCache -.->|cache miss| Keys
+ InternalCache --> RateLimit
+ Router --> TPM_RPM
+ Router --> Cooldowns
+ LLMCache --> CacheClass
+ CacheClass --> LLMResponseCache
+ CostCallback --> DBWriter
+ DBWriter --> SpendQueue
+ DBWriter --> SpendLogs
+ Scheduler --> SpendLogs
+ Scheduler --> Keys
+```
+
+| Component | Purpose | Key Files/Classes |
+|-----------|---------|-------------------|
+| **Redis** | Rate limiting, API key caching, TPM/RPM tracking, cooldowns, LLM response caching, spend queuing | `caching/redis_cache.py` (`RedisCache`), `caching/dual_cache.py` (`DualCache`) |
+| **PostgreSQL** | API keys, teams, users, spend logs | `proxy/utils.py` (`PrismaClient`), `proxy/schema.prisma` |
+| **InternalUsageCache** | Proxy-level cache for rate limits + API keys (in-memory + Redis) | `proxy/utils.py` (`InternalUsageCache`) |
+| **Router.cache** | TPM/RPM tracking, deployment cooldowns, client caching (in-memory + Redis) | `router.py` (`Router.cache: DualCache`) |
+| **LLMCachingHandler** | SDK-level LLM response/embedding caching | `caching/caching_handler.py` (`LLMCachingHandler`), `caching/caching.py` (`Cache`) |
+| **DBSpendUpdateWriter** | Batches spend updates to reduce DB writes | `proxy/db/db_spend_update_writer.py` (`DBSpendUpdateWriter`) |
+| **Cost Tracking** | Calculates and logs response costs | `proxy/hooks/proxy_track_cost_callback.py` (`_ProxyDBLogger`) |
+
+**Background Jobs** (APScheduler, initialized in `proxy/proxy_server.py` → `ProxyStartupEvent.initialize_scheduled_background_jobs()`):
+
+| Job | Interval | Purpose | Key Files |
+|-----|----------|---------|-----------|
+| `update_spend` | 60s | Batch write spend logs to PostgreSQL | `proxy/db/db_spend_update_writer.py` |
+| `reset_budget` | 10-12min | Reset budgets for keys/users/teams | `proxy/management_helpers/budget_reset_job.py` |
+| `add_deployment` | 10s | Sync new model deployments from DB | `proxy/proxy_server.py` (`ProxyConfig`) |
+| `cleanup_old_spend_logs` | cron/interval | Delete old spend logs | `proxy/management_helpers/spend_log_cleanup.py` |
+| `check_batch_cost` | 30min | Calculate costs for batch jobs | `proxy/management_helpers/check_batch_cost_job.py` |
+| `check_responses_cost` | 30min | Calculate costs for responses API | `proxy/management_helpers/check_responses_cost_job.py` |
+| `process_rotations` | 1hr | Auto-rotate API keys | `proxy/management_helpers/key_rotation_manager.py` |
+| `_run_background_health_check` | continuous | Health check model deployments | `proxy/proxy_server.py` |
+| `send_weekly_spend_report` | weekly | Slack spend alerts | `proxy/utils.py` (`SlackAlerting`) |
+| `send_monthly_spend_report` | monthly | Slack spend alerts | `proxy/utils.py` (`SlackAlerting`) |
+
+**Cost Attribution Flow:**
+1. LLM response returns to `utils.py` wrapper after `litellm.acompletion()` completes
+2. `update_response_metadata()` (`llm_response_utils/response_metadata.py`) is called
+3. `logging_obj._response_cost_calculator()` (`litellm_logging.py`) calculates cost via `litellm.completion_cost()` (`cost_calculator.py`)
+4. Cost is stored in `response._hidden_params["response_cost"]`
+5. `proxy/common_request_processing.py` extracts cost from `hidden_params` and adds to response headers (`x-litellm-response-cost`)
+6. `logging_obj.async_success_handler()` triggers callbacks including `_ProxyDBLogger.async_log_success_event()`
+7. `DBSpendUpdateWriter.update_database()` queues spend increments to Redis
+8. Background job `update_spend` flushes queued spend to PostgreSQL every 60s
+
+---
+
+## 2. SDK Request Flow
+
+The SDK (`litellm/`) provides the core LLM calling functionality used by both direct SDK users and the AI Gateway.
+
+```mermaid
+graph TD
+ subgraph "SDK Entry Points"
+ Completion["litellm.completion()"]
+ Messages["litellm.messages()"]
+ end
+
+ subgraph "main.py"
+ Main["completion()
acompletion()"]
+ end
+
+ subgraph "utils.py"
+ GetProvider["get_llm_provider()"]
+ end
+
+ subgraph "llms/custom_httpx/"
+ Handler["llm_http_handler.py
BaseLLMHTTPHandler"]
+ HTTP["http_handler.py
HTTPHandler / AsyncHTTPHandler"]
+ end
+
+ subgraph "llms/{provider}/chat/"
+ TransformReq["transform_request()"]
+ TransformResp["transform_response()"]
+ end
+
+ subgraph "litellm_core_utils/"
+ Streaming["streaming_handler.py"]
+ end
+
+ subgraph "integrations/ (async, off main thread)"
+ Callbacks["custom_logger.py
Langfuse, Datadog, etc."]
+ end
+
+ Completion --> Main
+ Messages --> Main
+ Main --> GetProvider
+ GetProvider --> Handler
+ Handler --> TransformReq
+ TransformReq --> HTTP
+ HTTP --> Provider["LLM Provider API"]
+ Provider --> HTTP
+ HTTP --> TransformResp
+ TransformResp --> Streaming
+ Streaming --> Response["ModelResponse"]
+ Response -.->|async| Callbacks
+```
+
+**Key SDK files:**
+- `main.py` - Entry points: `completion()`, `acompletion()`, `embedding()`
+- `utils.py` - `get_llm_provider()` resolves model → provider
+- `llms/custom_httpx/llm_http_handler.py` - Central HTTP orchestrator
+- `llms/custom_httpx/http_handler.py` - Low-level HTTP client
+- `llms/{provider}/chat/transformation.py` - Provider-specific transformations
+- `litellm_core_utils/streaming_handler.py` - Streaming response handling
+- `integrations/` - Async callbacks (Langfuse, Datadog, etc.)
+
+---
+
+## 3. Translation Layer
+
+When a request comes in, it goes through a **translation layer** that converts between API formats.
+Each translation is isolated in its own file, making it easy to test and modify independently.
+
+### Where to find translations
+
+| Incoming API | Provider | Translation File |
+|--------------|----------|------------------|
+| `/v1/chat/completions` | Anthropic | `llms/anthropic/chat/transformation.py` |
+| `/v1/chat/completions` | Bedrock Converse | `llms/bedrock/chat/converse_transformation.py` |
+| `/v1/chat/completions` | Bedrock Invoke | `llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py` |
+| `/v1/chat/completions` | Gemini | `llms/gemini/chat/transformation.py` |
+| `/v1/chat/completions` | Vertex AI | `llms/vertex_ai/gemini/transformation.py` |
+| `/v1/chat/completions` | OpenAI | `llms/openai/chat/gpt_transformation.py` |
+| `/v1/messages` (passthrough) | Anthropic | `llms/anthropic/experimental_pass_through/messages/transformation.py` |
+| `/v1/messages` (passthrough) | Bedrock | `llms/bedrock/messages/invoke_transformations/anthropic_claude3_transformation.py` |
+| `/v1/messages` (passthrough) | Vertex AI | `llms/vertex_ai/vertex_ai_partner_models/anthropic/experimental_pass_through/transformation.py` |
+| Passthrough endpoints | All | `proxy/pass_through_endpoints/llm_provider_handlers/` |
+
+### Example: Debugging prompt caching
+
+If `/v1/messages` → Bedrock Converse prompt caching isn't working but Bedrock Invoke works:
+
+1. **Bedrock Converse translation**: `llms/bedrock/chat/converse_transformation.py`
+2. **Bedrock Invoke translation**: `llms/bedrock/chat/invoke_transformations/anthropic_claude3_transformation.py`
+3. Compare how each handles `cache_control` in `transform_request()`
+
+### How translations work
+
+Each provider has a `Config` class that inherits from `BaseConfig` (`llms/base_llm/chat/transformation.py`):
+
+```python
+class ProviderConfig(BaseConfig):
+ def transform_request(self, model, messages, optional_params, litellm_params, headers):
+ # Convert OpenAI format → Provider format
+ return {"messages": transformed_messages, ...}
+
+ def transform_response(self, model, raw_response, model_response, logging_obj, ...):
+ # Convert Provider format → OpenAI format
+ return ModelResponse(choices=[...], usage=Usage(...))
+```
+
+The `BaseLLMHTTPHandler` (`llms/custom_httpx/llm_http_handler.py`) calls these methods - you never need to modify the handler itself.
+
+---
+
+## 4. Adding/Modifying Providers
+
+### To add a new provider:
+
+1. Create `llms/{provider}/chat/transformation.py`
+2. Implement `Config` class with `transform_request()` and `transform_response()`
+3. Add tests in `tests/llm_translation/test_{provider}.py`
+
+### To add a feature (e.g., prompt caching):
+
+1. Find the translation file from the table above
+2. Modify `transform_request()` to handle the new parameter
+3. Add unit tests that verify the transformation
+
+### Testing checklist
+
+When adding a feature, verify it works across all paths:
+
+| Test | File Pattern |
+|------|--------------|
+| OpenAI passthrough | `tests/llm_translation/test_openai*.py` |
+| Anthropic direct | `tests/llm_translation/test_anthropic*.py` |
+| Bedrock Invoke | `tests/llm_translation/test_bedrock*.py` |
+| Bedrock Converse | `tests/llm_translation/test_bedrock*converse*.py` |
+| Vertex AI | `tests/llm_translation/test_vertex*.py` |
+| Gemini | `tests/llm_translation/test_gemini*.py` |
+
+### Unit testing translations
+
+Translations are designed to be unit testable without making API calls:
+
+```python
+from litellm.llms.bedrock.chat.converse_transformation import BedrockConverseConfig
+
+def test_prompt_caching_transform():
+ config = BedrockConverseConfig()
+ result = config.transform_request(
+ model="anthropic.claude-3-opus",
+ messages=[{"role": "user", "content": "test", "cache_control": {"type": "ephemeral"}}],
+ optional_params={},
+ litellm_params={},
+ headers={}
+ )
+ assert "cachePoint" in str(result) # Verify cache_control was translated
+```
diff --git a/Dockerfile b/Dockerfile
index d8397ec4811..0e7a8412bbc 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -20,7 +20,8 @@ RUN python -m pip install build
COPY . .
# Build Admin UI
-RUN chmod +x docker/build_admin_ui.sh && ./docker/build_admin_ui.sh
+# Convert Windows line endings to Unix and make executable
+RUN sed -i 's/\r$//' docker/build_admin_ui.sh && chmod +x docker/build_admin_ui.sh && ./docker/build_admin_ui.sh
# Build the package
RUN rm -rf dist/* && python -m build
@@ -65,12 +66,14 @@ RUN find /usr/lib -type f -path "*/tornado/test/*" -delete && \
find /usr/lib -type d -path "*/tornado/test" -delete
# Install semantic_router and aurelio-sdk using script
-RUN chmod +x docker/install_auto_router.sh && ./docker/install_auto_router.sh
+# Convert Windows line endings to Unix and make executable
+RUN sed -i 's/\r$//' docker/install_auto_router.sh && chmod +x docker/install_auto_router.sh && ./docker/install_auto_router.sh
# Generate prisma client
RUN prisma generate
-RUN chmod +x docker/entrypoint.sh
-RUN chmod +x docker/prod_entrypoint.sh
+# Convert Windows line endings to Unix for entrypoint scripts
+RUN sed -i 's/\r$//' docker/entrypoint.sh && chmod +x docker/entrypoint.sh
+RUN sed -i 's/\r$//' docker/prod_entrypoint.sh && chmod +x docker/prod_entrypoint.sh
EXPOSE 4000/tcp
diff --git a/Makefile b/Makefile
index 1614a58fc7d..0da83c363cd 100644
--- a/Makefile
+++ b/Makefile
@@ -45,6 +45,7 @@ install-proxy-dev-ci:
install-test-deps: install-proxy-dev
poetry run pip install "pytest-retry==1.6.3"
poetry run pip install pytest-xdist
+ poetry run pip install openapi-core
cd enterprise && poetry run pip install -e . && cd ..
install-helm-unittest:
@@ -100,4 +101,4 @@ test-llm-translation-single: install-test-deps
@mkdir -p test-results
poetry run pytest tests/llm_translation/$(FILE) \
--junitxml=test-results/junit.xml \
- -v --tb=short --maxfail=100 --timeout=300
\ No newline at end of file
+ -v --tb=short --maxfail=100 --timeout=300
diff --git a/README.md b/README.md
index 3eb0547f21b..914fda384b0 100644
--- a/README.md
+++ b/README.md
@@ -2,16 +2,16 @@
🚅 LiteLLM
+
Call 100+ LLMs in OpenAI format. [Bedrock, Azure, OpenAI, VertexAI, Anthropic, Groq, etc.] +
-Call all LLM APIs using the OpenAI format [Bedrock, Huggingface, VertexAI, TogetherAI, Azure, OpenAI, Groq etc.]
-
| + | LiteLLM AI Gateway | +LiteLLM Python SDK | +
|---|---|---|
| Use Case | +Central service (LLM Gateway) to access multiple LLMs | +Use LiteLLM directly in your Python code | +
| Who Uses It? | +Gen AI Enablement / ML Platform Teams | +Developers building LLM projects | +
| Key Features | +Centralized API gateway with authentication and authorization, multi-tenant cost tracking and spend management per project/user, per-project customization (logging, guardrails, caching), virtual keys for secure access control, admin dashboard UI for monitoring and management | +Direct Python library integration in your codebase, Router with retry/fallback logic across multiple deployments (e.g. Azure/OpenAI) - Router, application-level load balancing and cost tracking, exception handling with OpenAI-compatible errors, observability callbacks (Lunary, MLflow, Langfuse, etc.) | +
Netflix |
+
-AICORE_AUTH_URL = "https://* * * .authentication.sap.hana.ondemand.com/oauth/token", -AICORE_CLIENT_ID = " *** ", -AICORE_CLIENT_SECRET = " *** ", -AICORE_RESOURCE_GROUP = " *** ", -AICORE_BASE_URL = "https://api.ai.***.cfapps.sap.hana.ondemand.com/v2" --## Usage - LiteLLM Python SDK -```python showLineNumbers title="SAP Chat Completion" -from litellm import completion -import os +### Proxy Usage -os.environ["AICORE_SERVICE_KEY"] = '{"clientid": "...", "clientsecret": "...", ...}' +When using the LiteLLM Proxy, you use the **friendly `model_name`** defined in your configuration. The proxy automatically handles the `sap/` prefix routing. -response = completion( - model="sap/gpt-4", - messages=[{"role": "user", "content": "Hello from LiteLLM"}] +```yaml +# In config.yaml, define the mapping +model_list: + - model_name: gpt-4o # ← Use this name in client requests + litellm_params: + model: sap/gpt-4o # ← Proxy handles the sap/ prefix +``` + +```python +# Client request - no sap/ prefix needed +client.chat.completions.create( + model="gpt-4o", # ✓ Correct for proxy usage + messages=[...] ) -print(response) ``` -```python showLineNumbers title="SAP Chat Completion - Streaming" +### Anthropic Models Special Syntax + +Anthropic models use a double-dash (`--`) prefix convention: + +| Provider | Model Example | LiteLLM Format | +|----------|---------------|----------------| +| OpenAI | GPT-4o | `sap/gpt-4o` | +| Anthropic | Claude 4.5 Sonnet | `sap/anthropic--claude-4.5-sonnet` | +| Google | Gemini 2.5 Pro | `sap/gemini-2.5-pro` | +| Mistral | Mistral Large | `sap/mistral-large` | + +### Quick Reference Table + +| Usage Type | Model Format | Example | +|------------|--------------|---------| +| Direct SDK | `sap/
diff --git a/docs/my-website/docs/tutorials/claude_non_anthropic_models.md b/docs/my-website/docs/tutorials/claude_non_anthropic_models.md
new file mode 100644
index 00000000000..75ac08e3094
--- /dev/null
+++ b/docs/my-website/docs/tutorials/claude_non_anthropic_models.md
@@ -0,0 +1,316 @@
+import Image from '@theme/IdealImage';
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+# Use Claude Code with Non-Anthropic Models
+
+This tutorial shows how to use Claude Code with non-Anthropic models like OpenAI, Gemini, and other LLM providers through LiteLLM proxy.
+
+:::info
+
+LiteLLM automatically translates between different provider formats, allowing you to use any supported LLM provider with Claude Code while maintaining the Anthropic Messages API format.
+
+:::
+
+## Prerequisites
+
+- [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) installed
+- API keys for your chosen providers (OpenAI, Vertex AI, etc.)
+
+## Installation
+
+First, install LiteLLM with proxy support:
+
+```bash
+pip install 'litellm[proxy]'
+```
+
+## Configuration
+
+### 1. Setup config.yaml
+
+Create a configuration file with your preferred non-Anthropic models:
+
+ Hi {recipient_email},
+
+ Your LiteLLM API key has crossed its soft budget limit of {soft_budget}.
+
+ Current Spend: {spend}
+ Soft Budget: {soft_budget}
+ {max_budget_info}
+
+
+ ⚠️ Note: Your API requests will continue to work, but you should monitor your usage closely. + If you reach your maximum budget, requests will be rejected. +
+ + You can view your usage and manage your budget in the LiteLLM Dashboard. Hi {recipient_email},
+
+ Your LiteLLM API key has reached {percentage}% of its maximum budget.
+
+ Current Spend: {spend}
+ Maximum Budget: {max_budget}
+ Alert Threshold: {alert_threshold} ({percentage}%)
+
+
+ ⚠️ Warning: You are approaching your maximum budget limit. + Once you reach your maximum budget of {max_budget}, all API requests will be rejected. +
+ + You can view your usage and manage your budget in the LiteLLM Dashboard.=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,P 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-1&&t%1==0&&t0&&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(87602),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=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;nf()?void 0:b()).then(()=>{r?v(e):m()})})};return{promise:l,status:()=>l.status,cancel:e=>{if(!d()){let r=new c(e);v(r),t.onCancel?.(r)}},continue:()=>(e?.(),l),cancelRetry:()=>{r=!0},continueRetry:()=>{r=!1},canStart:p,start:()=>(p()?m():b().then(m),l)}}},24112:function(t,e,r){r.d(e,{l:function(){return s}});var s=class{constructor(){this.listeners=new Set,this.subscribe=this.subscribe.bind(this)}subscribe(t){return this.listeners.add(t),this.onSubscribe(),()=>{this.listeners.delete(t),this.onUnsubscribe()}}hasListeners(){return this.listeners.size>0}onSubscribe(){}onUnsubscribe(){}}},16803:function(t,e,r){r.d(e,{O:function(){return s}});function s(){let t,e;let r=new Promise((r,s)=>{t=r,e=s});function s(t){Object.assign(r,t),delete r.resolve,delete r.reject}return r.status="pending",r.catch(()=>{}),r.resolve=e=>{s({status:"fulfilled",value:e}),t(e)},r.reject=t=>{s({status:"rejected",reason:t}),e(t)},r}},84554:function(t,e,r){r.d(e,{Hp:function(){return n},mr:function(){return i}});var s={setTimeout:(t,e)=>setTimeout(t,e),clearTimeout:t=>clearTimeout(t),setInterval:(t,e)=>setInterval(t,e),clearInterval:t=>clearInterval(t)},i=new class{#f=s;#p=!1;setTimeoutProvider(t){this.#f=t}setTimeout(t,e){return this.#f.setTimeout(t,e)}clearTimeout(t){this.#f.clearTimeout(t)}setInterval(t,e){return this.#f.setInterval(t,e)}clearInterval(t){this.#f.clearInterval(t)}};function n(t){setTimeout(t,0)}},45345:function(t,e,r){r.d(e,{CN:function(){return Q},Ht:function(){return T},KC:function(){return c},Kp:function(){return a},L3:function(){return I},Nc:function(){return h},PN:function(){return o},Rm:function(){return f},SE:function(){return u},VS:function(){return b},VX:function(){return w},Wk:function(){return C},X7:function(){return d},Ym:function(){return p},ZT:function(){return n},_v:function(){return O},_x:function(){return l},cG:function(){return F},oE:function(){return S},sk:function(){return i},to:function(){return y}});var s=r(84554),i="undefined"==typeof window||"Deno"in globalThis;function n(){}function u(t,e){return"function"==typeof t?t(e):t}function o(t){return"number"==typeof t&&t>=0&&t!==1/0}function a(t,e){return Math.max(t+(e||0)-Date.now(),0)}function c(t,e){return"function"==typeof t?t(e):t}function h(t,e){return"function"==typeof t?t(e):t}function l(t,e){let{type:r="all",exact:s,fetchStatus:i,predicate:n,queryKey:u,stale:o}=t;if(u){if(s){if(e.queryHash!==f(u,e.options))return!1}else if(!y(e.queryKey,u))return!1}if("all"!==r){let t=e.isActive();if("active"===r&&!t||"inactive"===r&&t)return!1}return("boolean"!=typeof o||e.isStale()===o)&&(!i||i===e.state.fetchStatus)&&(!n||!!n(e))}function d(t,e){let{exact:r,status:s,predicate:i,mutationKey:n}=t;if(n){if(!e.options.mutationKey)return!1;if(r){if(p(e.options.mutationKey)!==p(n))return!1}else if(!y(e.options.mutationKey,n))return!1}return(!s||e.state.status===s)&&(!i||!!i(e))}function f(t,e){return(e?.queryKeyHashFn||p)(t)}function p(t){return JSON.stringify(t,(t,e)=>g(e)?Object.keys(e).sort().reduce((t,r)=>(t[r]=e[r],t),{}):e)}function y(t,e){return t===e||typeof t==typeof e&&!!t&&!!e&&"object"==typeof t&&"object"==typeof e&&Object.keys(e).every(r=>y(t[r],e[r]))}var v=Object.prototype.hasOwnProperty;function b(t,e){if(!e||Object.keys(t).length!==Object.keys(e).length)return!1;for(let r in t)if(t[r]!==e[r])return!1;return!0}function m(t){return Array.isArray(t)&&t.length===Object.keys(t).length}function g(t){if(!R(t))return!1;let e=t.constructor;if(void 0===e)return!0;let r=e.prototype;return!!(R(r)&&r.hasOwnProperty("isPrototypeOf"))&&Object.getPrototypeOf(t)===Object.prototype}function R(t){return"[object Object]"===Object.prototype.toString.call(t)}function O(t){return new Promise(e=>{s.mr.setTimeout(e,t)})}function S(t,e,r){return"function"==typeof r.structuralSharing?r.structuralSharing(t,e):!1!==r.structuralSharing?function t(e,r){if(e===r)return e;let s=m(e)&&m(r);if(!s&&!(g(e)&&g(r)))return r;let i=(s?e:Object.keys(e)).length,n=s?r:Object.keys(r),u=n.length,o=s?Array(u):{},a=0;for(let c=0;cr?s.slice(1):s}function T(t,e,r=0){let s=[e,...t];return r&&s.length>r?s.slice(0,-1):s}var Q=Symbol();function F(t,e){return!t.queryFn&&e?.initialPromise?()=>e.initialPromise:t.queryFn&&t.queryFn!==Q?t.queryFn:()=>Promise.reject(Error(`Missing queryFn: '${t.queryHash}'`))}function I(t,e){return"function"==typeof t?t(...e):!!t}},29827:function(t,e,r){r.d(e,{NL:function(){return u},aH:function(){return o}});var s=r(2265),i=r(57437),n=s.createContext(void 0),u=t=>{let e=s.useContext(n);if(t)return t;if(!e)throw Error("No QueryClient set, use QueryClientProvider to set one");return e},o=t=>{let{client:e,children:r}=t;return s.useEffect(()=>(e.mount(),()=>{e.unmount()}),[e]),(0,i.jsx)(n.Provider,{value:e,children:r})}},11713:function(t,e,r){let s;r.d(e,{a:function(){return E}});var i=r(87045),n=r(18238),u=r(21733),o=r(24112),a=r(16803),c=r(45345),h=r(84554),l=class extends o.l{constructor(t,e){super(),this.options=e,this.#o=t,this.#y=null,this.#v=(0,a.O)(),this.bindMethods(),this.setOptions(e)}#o;#b=void 0;#m=void 0;#g=void 0;#R;#O;#v;#y;#S;#C;#w;#T;#Q;#F;#I=new Set;bindMethods(){this.refetch=this.refetch.bind(this)}onSubscribe(){1===this.listeners.size&&(this.#b.addObserver(this),d(this.#b,this.options)?this.#E():this.updateResult(),this.#k())}onUnsubscribe(){this.hasListeners()||this.destroy()}shouldFetchOnReconnect(){return f(this.#b,this.options,this.options.refetchOnReconnect)}shouldFetchOnWindowFocus(){return f(this.#b,this.options,this.options.refetchOnWindowFocus)}destroy(){this.listeners=new Set,this.#U(),this.#P(),this.#b.removeObserver(this)}setOptions(t){let e=this.options,r=this.#b;if(this.options=this.#o.defaultQueryOptions(t),void 0!==this.options.enabled&&"boolean"!=typeof this.options.enabled&&"function"!=typeof this.options.enabled&&"boolean"!=typeof(0,c.Nc)(this.options.enabled,this.#b))throw Error("Expected enabled to be a boolean or a callback that returns a boolean");this.#j(),this.#b.setOptions(this.options),e._defaulted&&!(0,c.VS)(this.options,e)&&this.#o.getQueryCache().notify({type:"observerOptionsUpdated",query:this.#b,observer:this});let s=this.hasListeners();s&&p(this.#b,r,this.options,e)&&this.#E(),this.updateResult(),s&&(this.#b!==r||(0,c.Nc)(this.options.enabled,this.#b)!==(0,c.Nc)(e.enabled,this.#b)||(0,c.KC)(this.options.staleTime,this.#b)!==(0,c.KC)(e.staleTime,this.#b))&&this.#q();let i=this.#D();s&&(this.#b!==r||(0,c.Nc)(this.options.enabled,this.#b)!==(0,c.Nc)(e.enabled,this.#b)||i!==this.#F)&&this.#x(i)}getOptimisticResult(t){let e=this.#o.getQueryCache().build(this.#o,t),r=this.createResult(e,t);return(0,c.VS)(this.getCurrentResult(),r)||(this.#g=r,this.#O=this.options,this.#R=this.#b.state),r}getCurrentResult(){return this.#g}trackResult(t,e){return new Proxy(t,{get:(t,r)=>(this.trackProp(r),e?.(r),"promise"!==r||(this.trackProp("data"),this.options.experimental_prefetchInRender||"pending"!==this.#v.status||this.#v.reject(Error("experimental_prefetchInRender feature flag is not enabled"))),Reflect.get(t,r))})}trackProp(t){this.#I.add(t)}getCurrentQuery(){return this.#b}refetch({...t}={}){return this.fetch({...t})}fetchOptimistic(t){let e=this.#o.defaultQueryOptions(t),r=this.#o.getQueryCache().build(this.#o,e);return r.fetch().then(()=>this.createResult(r,e))}fetch(t){return this.#E({...t,cancelRefetch:t.cancelRefetch??!0}).then(()=>(this.updateResult(),this.#g))}#E(t){this.#j();let e=this.#b.fetch(this.options,t);return t?.throwOnError||(e=e.catch(c.ZT)),e}#q(){this.#U();let t=(0,c.KC)(this.options.staleTime,this.#b);if(c.sk||this.#g.isStale||!(0,c.PN)(t))return;let e=(0,c.Kp)(this.#g.dataUpdatedAt,t);this.#T=h.mr.setTimeout(()=>{this.#g.isStale||this.updateResult()},e+1)}#D(){return("function"==typeof this.options.refetchInterval?this.options.refetchInterval(this.#b):this.options.refetchInterval)??!1}#x(t){this.#P(),this.#F=t,!c.sk&&!1!==(0,c.Nc)(this.options.enabled,this.#b)&&(0,c.PN)(this.#F)&&0!==this.#F&&(this.#Q=h.mr.setInterval(()=>{(this.options.refetchIntervalInBackground||i.j.isFocused())&&this.#E()},this.#F))}#k(){this.#q(),this.#x(this.#D())}#U(){this.#T&&(h.mr.clearTimeout(this.#T),this.#T=void 0)}#P(){this.#Q&&(h.mr.clearInterval(this.#Q),this.#Q=void 0)}createResult(t,e){let r;let s=this.#b,i=this.options,n=this.#g,o=this.#R,h=this.#O,l=t!==s?t.state:this.#m,{state:f}=t,v={...f},b=!1;if(e._optimisticResults){let r=this.hasListeners(),n=!r&&d(t,e),o=r&&p(t,s,e,i);(n||o)&&(v={...v,...(0,u.z)(f.data,t.options)}),"isRestoring"===e._optimisticResults&&(v.fetchStatus="idle")}let{error:m,errorUpdatedAt:g,status:R}=v;r=v.data;let O=!1;if(void 0!==e.placeholderData&&void 0===r&&"pending"===R){let t;n?.isPlaceholderData&&e.placeholderData===h?.placeholderData?(t=n.data,O=!0):t="function"==typeof e.placeholderData?e.placeholderData(this.#w?.state.data,this.#w):e.placeholderData,void 0!==t&&(R="success",r=(0,c.oE)(n?.data,t,e),b=!0)}if(e.select&&void 0!==r&&!O){if(n&&r===o?.data&&e.select===this.#S)r=this.#C;else try{this.#S=e.select,r=e.select(r),r=(0,c.oE)(n?.data,r,e),this.#C=r,this.#y=null}catch(t){this.#y=t}}this.#y&&(m=this.#y,r=this.#C,g=Date.now(),R="error");let S="fetching"===v.fetchStatus,C="pending"===R,w="error"===R,T=C&&S,Q=void 0!==r,F={status:R,fetchStatus:v.fetchStatus,isPending:C,isSuccess:"success"===R,isError:w,isInitialLoading:T,isLoading:T,data:r,dataUpdatedAt:v.dataUpdatedAt,error:m,errorUpdatedAt:g,failureCount:v.fetchFailureCount,failureReason:v.fetchFailureReason,errorUpdateCount:v.errorUpdateCount,isFetched:v.dataUpdateCount>0||v.errorUpdateCount>0,isFetchedAfterMount:v.dataUpdateCount>l.dataUpdateCount||v.errorUpdateCount>l.errorUpdateCount,isFetching:S,isRefetching:S&&!C,isLoadingError:w&&!Q,isPaused:"paused"===v.fetchStatus,isPlaceholderData:b,isRefetchError:w&&Q,isStale:y(t,e),refetch:this.refetch,promise:this.#v,isEnabled:!1!==(0,c.Nc)(e.enabled,t)};if(this.options.experimental_prefetchInRender){let e=t=>{"error"===F.status?t.reject(F.error):void 0!==F.data&&t.resolve(F.data)},r=()=>{e(this.#v=F.promise=(0,a.O)())},i=this.#v;switch(i.status){case"pending":t.queryHash===s.queryHash&&e(i);break;case"fulfilled":("error"===F.status||F.data!==i.value)&&r();break;case"rejected":("error"!==F.status||F.error!==i.reason)&&r()}}return F}updateResult(){let t=this.#g,e=this.createResult(this.#b,this.options);this.#R=this.#b.state,this.#O=this.options,void 0!==this.#R.data&&(this.#w=this.#b),(0,c.VS)(e,t)||(this.#g=e,this.#N({listeners:(()=>{if(!t)return!0;let{notifyOnChangeProps:e}=this.options,r="function"==typeof e?e():e;if("all"===r||!r&&!this.#I.size)return!0;let s=new Set(r??this.#I);return this.options.throwOnError&&s.add("error"),Object.keys(this.#g).some(e=>this.#g[e]!==t[e]&&s.has(e))})()}))}#j(){let t=this.#o.getQueryCache().build(this.#o,this.options);if(t===this.#b)return;let e=this.#b;this.#b=t,this.#m=t.state,this.hasListeners()&&(e?.removeObserver(this),t.addObserver(this))}onQueryUpdate(){this.updateResult(),this.hasListeners()&&this.#k()}#N(t){n.Vr.batch(()=>{t.listeners&&this.listeners.forEach(t=>{t(this.#g)}),this.#o.getQueryCache().notify({query:this.#b,type:"observerResultsUpdated"})})}};function d(t,e){return!1!==(0,c.Nc)(e.enabled,t)&&void 0===t.state.data&&!("error"===t.state.status&&!1===e.retryOnMount)||void 0!==t.state.data&&f(t,e,e.refetchOnMount)}function f(t,e,r){if(!1!==(0,c.Nc)(e.enabled,t)&&"static"!==(0,c.KC)(e.staleTime,t)){let s="function"==typeof r?r(t):r;return"always"===s||!1!==s&&y(t,e)}return!1}function p(t,e,r,s){return(t!==e||!1===(0,c.Nc)(s.enabled,t))&&(!r.suspense||"error"!==t.state.status)&&y(t,r)}function y(t,e){return!1!==(0,c.Nc)(e.enabled,t)&&t.isStaleByTime((0,c.KC)(e.staleTime,t))}var v=r(2265),b=r(29827);r(57437);var m=v.createContext((s=!1,{clearReset:()=>{s=!1},reset:()=>{s=!0},isReset:()=>s})),g=()=>v.useContext(m),R=(t,e)=>{(t.suspense||t.throwOnError||t.experimental_prefetchInRender)&&!e.isReset()&&(t.retryOnMount=!1)},O=t=>{v.useEffect(()=>{t.clearReset()},[t])},S=t=>{let{result:e,errorResetBoundary:r,throwOnError:s,query:i,suspense:n}=t;return e.isError&&!r.isReset()&&!e.isFetching&&i&&(n&&void 0===e.data||(0,c.L3)(s,[e.error,i]))},C=v.createContext(!1),w=()=>v.useContext(C);C.Provider;var T=t=>{if(t.suspense){let e=t=>"static"===t?t:Math.max(t??1e3,1e3),r=t.staleTime;t.staleTime="function"==typeof r?(...t)=>e(r(...t)):e(r),"number"==typeof t.gcTime&&(t.gcTime=Math.max(t.gcTime,1e3))}},Q=(t,e)=>t.isLoading&&t.isFetching&&!e,F=(t,e)=>t?.suspense&&e.isPending,I=(t,e,r)=>e.fetchOptimistic(t).catch(()=>{r.clearReset()});function E(t,e){return function(t,e,r){var s,i,u,o,a;let h=w(),l=g(),d=(0,b.NL)(r),f=d.defaultQueryOptions(t);null===(i=d.getDefaultOptions().queries)||void 0===i||null===(s=i._experimental_beforeQuery)||void 0===s||s.call(i,f),f._optimisticResults=h?"isRestoring":"optimistic",T(f),R(f,l),O(l);let p=!d.getQueryCache().get(f.queryHash),[y]=v.useState(()=>new e(d,f)),m=y.getOptimisticResult(f),C=!h&&!1!==t.subscribed;if(v.useSyncExternalStore(v.useCallback(t=>{let e=C?y.subscribe(n.Vr.batchCalls(t)):c.ZT;return y.updateResult(),e},[y,C]),()=>y.getCurrentResult(),()=>y.getCurrentResult()),v.useEffect(()=>{y.setOptions(f)},[f,y]),F(f,m))throw I(f,y,l);if(S({result:m,errorResetBoundary:l,throwOnError:f.throwOnError,query:d.getQueryCache().get(f.queryHash),suspense:f.suspense}))throw m.error;if(null===(o=d.getDefaultOptions().queries)||void 0===o||null===(u=o._experimental_afterQuery)||void 0===u||u.call(o,f,m),f.experimental_prefetchInRender&&!c.sk&&Q(m,h)){let t=p?I(f,y,l):null===(a=d.getQueryCache().get(f.queryHash))||void 0===a?void 0:a.promise;null==t||t.catch(c.ZT).finally(()=>{y.updateResult()})}return f.notifyOnChangeProps?m:y.trackResult(m)}(t,l,e)}}}]);
\ No newline at end of file
diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1713-ce16d8a0e658a15d.js b/litellm/proxy/_experimental/out/_next/static/chunks/1713-ce16d8a0e658a15d.js
deleted file mode 100644
index 6aae7a1e1e9..00000000000
--- a/litellm/proxy/_experimental/out/_next/static/chunks/1713-ce16d8a0e658a15d.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1713],{87045:function(t,e,r){r.d(e,{j:function(){return n}});var s=r(24112),i=r(45345),n=new class extends s.l{#t;#e;#r;constructor(){super(),this.#r=t=>{if(!i.sk&&window.addEventListener){let e=()=>t();return window.addEventListener("visibilitychange",e,!1),()=>{window.removeEventListener("visibilitychange",e)}}}}onSubscribe(){this.#e||this.setEventListener(this.#r)}onUnsubscribe(){this.hasListeners()||(this.#e?.(),this.#e=void 0)}setEventListener(t){this.#r=t,this.#e?.(),this.#e=t(t=>{"boolean"==typeof t?this.setFocused(t):this.onFocus()})}setFocused(t){this.#t!==t&&(this.#t=t,this.onFocus())}onFocus(){let t=this.isFocused();this.listeners.forEach(e=>{e(t)})}isFocused(){return"boolean"==typeof this.#t?this.#t:globalThis.document?.visibilityState!=="hidden"}}},18238:function(t,e,r){r.d(e,{Vr:function(){return i}});var s=r(84554).Hp,i=function(){let t=[],e=0,r=t=>{t()},i=t=>{t()},n=s,u=s=>{e?t.push(s):n(()=>{r(s)})},o=()=>{let e=t;t=[],e.length&&n(()=>{i(()=>{e.forEach(t=>{r(t)})})})};return{batch:t=>{let r;e++;try{r=t()}finally{--e||o()}return r},batchCalls:t=>(...e)=>{u(()=>{t(...e)})},schedule:u,setNotifyFunction:t=>{r=t},setBatchNotifyFunction:t=>{i=t},setScheduler:t=>{n=t}}}()},57853:function(t,e,r){r.d(e,{N:function(){return n}});var s=r(24112),i=r(45345),n=new class extends s.l{#s=!0;#e;#r;constructor(){super(),this.#r=t=>{if(!i.sk&&window.addEventListener){let e=()=>t(!0),r=()=>t(!1);return window.addEventListener("online",e,!1),window.addEventListener("offline",r,!1),()=>{window.removeEventListener("online",e),window.removeEventListener("offline",r)}}}}onSubscribe(){this.#e||this.setEventListener(this.#r)}onUnsubscribe(){this.hasListeners()||(this.#e?.(),this.#e=void 0)}setEventListener(t){this.#r=t,this.#e?.(),this.#e=t(this.setOnline.bind(this))}setOnline(t){this.#s!==t&&(this.#s=t,this.listeners.forEach(e=>{e(t)}))}isOnline(){return this.#s}}},21733:function(t,e,r){r.d(e,{A:function(){return o},z:function(){return a}});var s=r(45345),i=r(18238),n=r(11255),u=r(7989),o=class extends u.F{#i;#n;#u;#o;#a;#c;#h;constructor(t){super(),this.#h=!1,this.#c=t.defaultOptions,this.setOptions(t.options),this.observers=[],this.#o=t.client,this.#u=this.#o.getQueryCache(),this.queryKey=t.queryKey,this.queryHash=t.queryHash,this.#i=h(this.options),this.state=t.state??this.#i,this.scheduleGc()}get meta(){return this.options.meta}get promise(){return this.#a?.promise}setOptions(t){if(this.options={...this.#c,...t},this.updateGcTime(this.options.gcTime),this.state&&void 0===this.state.data){let t=h(this.options);void 0!==t.data&&(this.setState(c(t.data,t.dataUpdatedAt)),this.#i=t)}}optionalRemove(){this.observers.length||"idle"!==this.state.fetchStatus||this.#u.remove(this)}setData(t,e){let r=(0,s.oE)(this.state.data,t,this.options);return this.#l({data:r,type:"success",dataUpdatedAt:e?.updatedAt,manual:e?.manual}),r}setState(t,e){this.#l({type:"setState",state:t,setStateOptions:e})}cancel(t){let e=this.#a?.promise;return this.#a?.cancel(t),e?e.then(s.ZT).catch(s.ZT):Promise.resolve()}destroy(){super.destroy(),this.cancel({silent:!0})}reset(){this.destroy(),this.setState(this.#i)}isActive(){return this.observers.some(t=>!1!==(0,s.Nc)(t.options.enabled,this))}isDisabled(){return this.getObserversCount()>0?!this.isActive():this.options.queryFn===s.CN||this.state.dataUpdateCount+this.state.errorUpdateCount===0}isStatic(){return this.getObserversCount()>0&&this.observers.some(t=>"static"===(0,s.KC)(t.options.staleTime,this))}isStale(){return this.getObserversCount()>0?this.observers.some(t=>t.getCurrentResult().isStale):void 0===this.state.data||this.state.isInvalidated}isStaleByTime(t=0){return void 0===this.state.data||"static"!==t&&(!!this.state.isInvalidated||!(0,s.Kp)(this.state.dataUpdatedAt,t))}onFocus(){let t=this.observers.find(t=>t.shouldFetchOnWindowFocus());t?.refetch({cancelRefetch:!1}),this.#a?.continue()}onOnline(){let t=this.observers.find(t=>t.shouldFetchOnReconnect());t?.refetch({cancelRefetch:!1}),this.#a?.continue()}addObserver(t){this.observers.includes(t)||(this.observers.push(t),this.clearGcTimeout(),this.#u.notify({type:"observerAdded",query:this,observer:t}))}removeObserver(t){this.observers.includes(t)&&(this.observers=this.observers.filter(e=>e!==t),this.observers.length||(this.#a&&(this.#h?this.#a.cancel({revert:!0}):this.#a.cancelRetry()),this.scheduleGc()),this.#u.notify({type:"observerRemoved",query:this,observer:t}))}getObserversCount(){return this.observers.length}invalidate(){this.state.isInvalidated||this.#l({type:"invalidate"})}async fetch(t,e){if("idle"!==this.state.fetchStatus&&this.#a?.status()!=="rejected"){if(void 0!==this.state.data&&e?.cancelRefetch)this.cancel({silent:!0});else if(this.#a)return this.#a.continueRetry(),this.#a.promise}if(t&&this.setOptions(t),!this.options.queryFn){let t=this.observers.find(t=>t.options.queryFn);t&&this.setOptions(t.options)}let r=new AbortController,i=t=>{Object.defineProperty(t,"signal",{enumerable:!0,get:()=>(this.#h=!0,r.signal)})},u=()=>{let t=(0,s.cG)(this.options,e),r=(()=>{let t={client:this.#o,queryKey:this.queryKey,meta:this.meta};return i(t),t})();return(this.#h=!1,this.options.persister)?this.options.persister(t,r,this):t(r)},o=(()=>{let t={fetchOptions:e,options:this.options,queryKey:this.queryKey,client:this.#o,state:this.state,fetchFn:u};return i(t),t})();this.options.behavior?.onFetch(o,this),this.#n=this.state,("idle"===this.state.fetchStatus||this.state.fetchMeta!==o.fetchOptions?.meta)&&this.#l({type:"fetch",meta:o.fetchOptions?.meta}),this.#a=(0,n.Mz)({initialPromise:e?.initialPromise,fn:o.fetchFn,onCancel:t=>{t instanceof n.p8&&t.revert&&this.setState({...this.#n,fetchStatus:"idle"}),r.abort()},onFail:(t,e)=>{this.#l({type:"failed",failureCount:t,error:e})},onPause:()=>{this.#l({type:"pause"})},onContinue:()=>{this.#l({type:"continue"})},retry:o.options.retry,retryDelay:o.options.retryDelay,networkMode:o.options.networkMode,canRun:()=>!0});try{let t=await this.#a.start();if(void 0===t)throw Error(`${this.queryHash} data is undefined`);return this.setData(t),this.#u.config.onSuccess?.(t,this),this.#u.config.onSettled?.(t,this.state.error,this),t}catch(t){if(t instanceof n.p8){if(t.silent)return this.#a.promise;if(t.revert){if(void 0===this.state.data)throw t;return this.state.data}}throw this.#l({type:"error",error:t}),this.#u.config.onError?.(t,this),this.#u.config.onSettled?.(this.state.data,t,this),t}finally{this.scheduleGc()}}#l(t){this.state=(e=>{switch(t.type){case"failed":return{...e,fetchFailureCount:t.failureCount,fetchFailureReason:t.error};case"pause":return{...e,fetchStatus:"paused"};case"continue":return{...e,fetchStatus:"fetching"};case"fetch":return{...e,...a(e.data,this.options),fetchMeta:t.meta??null};case"success":let r={...e,...c(t.data,t.dataUpdatedAt),dataUpdateCount:e.dataUpdateCount+1,...!t.manual&&{fetchStatus:"idle",fetchFailureCount:0,fetchFailureReason:null}};return this.#n=t.manual?r:void 0,r;case"error":let s=t.error;return{...e,error:s,errorUpdateCount:e.errorUpdateCount+1,errorUpdatedAt:Date.now(),fetchFailureCount:e.fetchFailureCount+1,fetchFailureReason:s,fetchStatus:"idle",status:"error"};case"invalidate":return{...e,isInvalidated:!0};case"setState":return{...e,...t.state}}})(this.state),i.Vr.batch(()=>{this.observers.forEach(t=>{t.onQueryUpdate()}),this.#u.notify({query:this,type:"updated",action:t})})}};function a(t,e){return{fetchFailureCount:0,fetchFailureReason:null,fetchStatus:(0,n.Kw)(e.networkMode)?"fetching":"paused",...void 0===t&&{error:null,status:"pending"}}}function c(t,e){return{data:t,dataUpdatedAt:e??Date.now(),error:null,isInvalidated:!1,status:"success"}}function h(t){let e="function"==typeof t.initialData?t.initialData():t.initialData,r=void 0!==e,s=r?"function"==typeof t.initialDataUpdatedAt?t.initialDataUpdatedAt():t.initialDataUpdatedAt:0;return{data:e,dataUpdateCount:0,dataUpdatedAt:r?s??Date.now():0,error:null,errorUpdateCount:0,errorUpdatedAt:0,fetchFailureCount:0,fetchFailureReason:null,fetchMeta:null,isInvalidated:!1,status:r?"success":"pending",fetchStatus:"idle"}}},7989:function(t,e,r){r.d(e,{F:function(){return n}});var s=r(84554),i=r(45345),n=class{#d;destroy(){this.clearGcTimeout()}scheduleGc(){this.clearGcTimeout(),(0,i.PN)(this.gcTime)&&(this.#d=s.mr.setTimeout(()=>{this.optionalRemove()},this.gcTime))}updateGcTime(t){this.gcTime=Math.max(this.gcTime||0,t??(i.sk?1/0:3e5))}clearGcTimeout(){this.#d&&(s.mr.clearTimeout(this.#d),this.#d=void 0)}}},11255:function(t,e,r){r.d(e,{Kw:function(){return a},Mz:function(){return h},p8:function(){return c}});var s=r(87045),i=r(57853),n=r(16803),u=r(45345);function o(t){return Math.min(1e3*2**t,3e4)}function a(t){return(t??"online")!=="online"||i.N.isOnline()}var c=class extends Error{constructor(t){super("CancelledError"),this.revert=t?.revert,this.silent=t?.silent}};function h(t){let e,r=!1,h=0,l=(0,n.O)(),d=()=>"pending"!==l.status,f=()=>s.j.isFocused()&&("always"===t.networkMode||i.N.isOnline())&&t.canRun(),p=()=>a(t.networkMode)&&t.canRun(),y=t=>{d()||(e?.(),l.resolve(t))},v=t=>{d()||(e?.(),l.reject(t))},b=()=>new Promise(r=>{e=t=>{(d()||f())&&r(t)},t.onPause?.()}).then(()=>{e=void 0,d()||t.onContinue?.()}),m=()=>{let e;if(d())return;let s=0===h?t.initialPromise:void 0;try{e=s??t.fn()}catch(t){e=Promise.reject(t)}Promise.resolve(e).then(y).catch(e=>{if(d())return;let s=t.retry??(u.sk?0:3),i=t.retryDelay??o,n="function"==typeof i?i(h,e):i,a=!0===s||"number"==typeof s&&hf()?void 0:b()).then(()=>{r?v(e):m()})})};return{promise:l,status:()=>l.status,cancel:e=>{if(!d()){let r=new c(e);v(r),t.onCancel?.(r)}},continue:()=>(e?.(),l),cancelRetry:()=>{r=!0},continueRetry:()=>{r=!1},canStart:p,start:()=>(p()?m():b().then(m),l)}}},24112:function(t,e,r){r.d(e,{l:function(){return s}});var s=class{constructor(){this.listeners=new Set,this.subscribe=this.subscribe.bind(this)}subscribe(t){return this.listeners.add(t),this.onSubscribe(),()=>{this.listeners.delete(t),this.onUnsubscribe()}}hasListeners(){return this.listeners.size>0}onSubscribe(){}onUnsubscribe(){}}},16803:function(t,e,r){r.d(e,{O:function(){return s}});function s(){let t,e;let r=new Promise((r,s)=>{t=r,e=s});function s(t){Object.assign(r,t),delete r.resolve,delete r.reject}return r.status="pending",r.catch(()=>{}),r.resolve=e=>{s({status:"fulfilled",value:e}),t(e)},r.reject=t=>{s({status:"rejected",reason:t}),e(t)},r}},84554:function(t,e,r){r.d(e,{Hp:function(){return n},mr:function(){return i}});var s={setTimeout:(t,e)=>setTimeout(t,e),clearTimeout:t=>clearTimeout(t),setInterval:(t,e)=>setInterval(t,e),clearInterval:t=>clearInterval(t)},i=new class{#f=s;#p=!1;setTimeoutProvider(t){this.#f=t}setTimeout(t,e){return this.#f.setTimeout(t,e)}clearTimeout(t){this.#f.clearTimeout(t)}setInterval(t,e){return this.#f.setInterval(t,e)}clearInterval(t){this.#f.clearInterval(t)}};function n(t){setTimeout(t,0)}},45345:function(t,e,r){r.d(e,{CN:function(){return T},Ht:function(){return w},KC:function(){return c},Kp:function(){return a},L3:function(){return F},Nc:function(){return h},PN:function(){return o},Rm:function(){return f},SE:function(){return u},VS:function(){return b},VX:function(){return C},X7:function(){return d},Ym:function(){return p},ZT:function(){return n},_v:function(){return O},_x:function(){return l},cG:function(){return Q},oE:function(){return S},sk:function(){return i},to:function(){return y}});var s=r(84554),i="undefined"==typeof window||"Deno"in globalThis;function n(){}function u(t,e){return"function"==typeof t?t(e):t}function o(t){return"number"==typeof t&&t>=0&&t!==1/0}function a(t,e){return Math.max(t+(e||0)-Date.now(),0)}function c(t,e){return"function"==typeof t?t(e):t}function h(t,e){return"function"==typeof t?t(e):t}function l(t,e){let{type:r="all",exact:s,fetchStatus:i,predicate:n,queryKey:u,stale:o}=t;if(u){if(s){if(e.queryHash!==f(u,e.options))return!1}else if(!y(e.queryKey,u))return!1}if("all"!==r){let t=e.isActive();if("active"===r&&!t||"inactive"===r&&t)return!1}return("boolean"!=typeof o||e.isStale()===o)&&(!i||i===e.state.fetchStatus)&&(!n||!!n(e))}function d(t,e){let{exact:r,status:s,predicate:i,mutationKey:n}=t;if(n){if(!e.options.mutationKey)return!1;if(r){if(p(e.options.mutationKey)!==p(n))return!1}else if(!y(e.options.mutationKey,n))return!1}return(!s||e.state.status===s)&&(!i||!!i(e))}function f(t,e){return(e?.queryKeyHashFn||p)(t)}function p(t){return JSON.stringify(t,(t,e)=>g(e)?Object.keys(e).sort().reduce((t,r)=>(t[r]=e[r],t),{}):e)}function y(t,e){return t===e||typeof t==typeof e&&!!t&&!!e&&"object"==typeof t&&"object"==typeof e&&Object.keys(e).every(r=>y(t[r],e[r]))}var v=Object.prototype.hasOwnProperty;function b(t,e){if(!e||Object.keys(t).length!==Object.keys(e).length)return!1;for(let r in t)if(t[r]!==e[r])return!1;return!0}function m(t){return Array.isArray(t)&&t.length===Object.keys(t).length}function g(t){if(!R(t))return!1;let e=t.constructor;if(void 0===e)return!0;let r=e.prototype;return!!(R(r)&&r.hasOwnProperty("isPrototypeOf"))&&Object.getPrototypeOf(t)===Object.prototype}function R(t){return"[object Object]"===Object.prototype.toString.call(t)}function O(t){return new Promise(e=>{s.mr.setTimeout(e,t)})}function S(t,e,r){return"function"==typeof r.structuralSharing?r.structuralSharing(t,e):!1!==r.structuralSharing?function t(e,r){if(e===r)return e;let s=m(e)&&m(r);if(!s&&!(g(e)&&g(r)))return r;let i=(s?e:Object.keys(e)).length,n=s?r:Object.keys(r),u=n.length,o=s?Array(u):{},a=0;for(let c=0;cr?s.slice(1):s}function w(t,e,r=0){let s=[e,...t];return r&&s.length>r?s.slice(0,-1):s}var T=Symbol();function Q(t,e){return!t.queryFn&&e?.initialPromise?()=>e.initialPromise:t.queryFn&&t.queryFn!==T?t.queryFn:()=>Promise.reject(Error(`Missing queryFn: '${t.queryHash}'`))}function F(t,e){return"function"==typeof t?t(...e):!!t}},29827:function(t,e,r){r.d(e,{NL:function(){return u},aH:function(){return o}});var s=r(2265),i=r(57437),n=s.createContext(void 0),u=t=>{let e=s.useContext(n);if(t)return t;if(!e)throw Error("No QueryClient set, use QueryClientProvider to set one");return e},o=t=>{let{client:e,children:r}=t;return s.useEffect(()=>(e.mount(),()=>{e.unmount()}),[e]),(0,i.jsx)(n.Provider,{value:e,children:r})}},11713:function(t,e,r){let s;r.d(e,{a:function(){return E}});var i=r(87045),n=r(18238),u=r(21733),o=r(24112),a=r(16803),c=r(45345),h=r(84554),l=class extends o.l{constructor(t,e){super(),this.options=e,this.#o=t,this.#y=null,this.#v=(0,a.O)(),this.bindMethods(),this.setOptions(e)}#o;#b=void 0;#m=void 0;#g=void 0;#R;#O;#v;#y;#S;#C;#w;#T;#Q;#F;#I=new Set;bindMethods(){this.refetch=this.refetch.bind(this)}onSubscribe(){1===this.listeners.size&&(this.#b.addObserver(this),d(this.#b,this.options)?this.#E():this.updateResult(),this.#U())}onUnsubscribe(){this.hasListeners()||this.destroy()}shouldFetchOnReconnect(){return f(this.#b,this.options,this.options.refetchOnReconnect)}shouldFetchOnWindowFocus(){return f(this.#b,this.options,this.options.refetchOnWindowFocus)}destroy(){this.listeners=new Set,this.#k(),this.#P(),this.#b.removeObserver(this)}setOptions(t){let e=this.options,r=this.#b;if(this.options=this.#o.defaultQueryOptions(t),void 0!==this.options.enabled&&"boolean"!=typeof this.options.enabled&&"function"!=typeof this.options.enabled&&"boolean"!=typeof(0,c.Nc)(this.options.enabled,this.#b))throw Error("Expected enabled to be a boolean or a callback that returns a boolean");this.#j(),this.#b.setOptions(this.options),e._defaulted&&!(0,c.VS)(this.options,e)&&this.#o.getQueryCache().notify({type:"observerOptionsUpdated",query:this.#b,observer:this});let s=this.hasListeners();s&&p(this.#b,r,this.options,e)&&this.#E(),this.updateResult(),s&&(this.#b!==r||(0,c.Nc)(this.options.enabled,this.#b)!==(0,c.Nc)(e.enabled,this.#b)||(0,c.KC)(this.options.staleTime,this.#b)!==(0,c.KC)(e.staleTime,this.#b))&&this.#q();let i=this.#D();s&&(this.#b!==r||(0,c.Nc)(this.options.enabled,this.#b)!==(0,c.Nc)(e.enabled,this.#b)||i!==this.#F)&&this.#x(i)}getOptimisticResult(t){let e=this.#o.getQueryCache().build(this.#o,t),r=this.createResult(e,t);return(0,c.VS)(this.getCurrentResult(),r)||(this.#g=r,this.#O=this.options,this.#R=this.#b.state),r}getCurrentResult(){return this.#g}trackResult(t,e){return new Proxy(t,{get:(t,r)=>(this.trackProp(r),e?.(r),"promise"!==r||(this.trackProp("data"),this.options.experimental_prefetchInRender||"pending"!==this.#v.status||this.#v.reject(Error("experimental_prefetchInRender feature flag is not enabled"))),Reflect.get(t,r))})}trackProp(t){this.#I.add(t)}getCurrentQuery(){return this.#b}refetch({...t}={}){return this.fetch({...t})}fetchOptimistic(t){let e=this.#o.defaultQueryOptions(t),r=this.#o.getQueryCache().build(this.#o,e);return r.fetch().then(()=>this.createResult(r,e))}fetch(t){return this.#E({...t,cancelRefetch:t.cancelRefetch??!0}).then(()=>(this.updateResult(),this.#g))}#E(t){this.#j();let e=this.#b.fetch(this.options,t);return t?.throwOnError||(e=e.catch(c.ZT)),e}#q(){this.#k();let t=(0,c.KC)(this.options.staleTime,this.#b);if(c.sk||this.#g.isStale||!(0,c.PN)(t))return;let e=(0,c.Kp)(this.#g.dataUpdatedAt,t);this.#T=h.mr.setTimeout(()=>{this.#g.isStale||this.updateResult()},e+1)}#D(){return("function"==typeof this.options.refetchInterval?this.options.refetchInterval(this.#b):this.options.refetchInterval)??!1}#x(t){this.#P(),this.#F=t,!c.sk&&!1!==(0,c.Nc)(this.options.enabled,this.#b)&&(0,c.PN)(this.#F)&&0!==this.#F&&(this.#Q=h.mr.setInterval(()=>{(this.options.refetchIntervalInBackground||i.j.isFocused())&&this.#E()},this.#F))}#U(){this.#q(),this.#x(this.#D())}#k(){this.#T&&(h.mr.clearTimeout(this.#T),this.#T=void 0)}#P(){this.#Q&&(h.mr.clearInterval(this.#Q),this.#Q=void 0)}createResult(t,e){let r;let s=this.#b,i=this.options,n=this.#g,o=this.#R,h=this.#O,l=t!==s?t.state:this.#m,{state:f}=t,v={...f},b=!1;if(e._optimisticResults){let r=this.hasListeners(),n=!r&&d(t,e),o=r&&p(t,s,e,i);(n||o)&&(v={...v,...(0,u.z)(f.data,t.options)}),"isRestoring"===e._optimisticResults&&(v.fetchStatus="idle")}let{error:m,errorUpdatedAt:g,status:R}=v;r=v.data;let O=!1;if(void 0!==e.placeholderData&&void 0===r&&"pending"===R){let t;n?.isPlaceholderData&&e.placeholderData===h?.placeholderData?(t=n.data,O=!0):t="function"==typeof e.placeholderData?e.placeholderData(this.#w?.state.data,this.#w):e.placeholderData,void 0!==t&&(R="success",r=(0,c.oE)(n?.data,t,e),b=!0)}if(e.select&&void 0!==r&&!O){if(n&&r===o?.data&&e.select===this.#S)r=this.#C;else try{this.#S=e.select,r=e.select(r),r=(0,c.oE)(n?.data,r,e),this.#C=r,this.#y=null}catch(t){this.#y=t}}this.#y&&(m=this.#y,r=this.#C,g=Date.now(),R="error");let S="fetching"===v.fetchStatus,C="pending"===R,w="error"===R,T=C&&S,Q=void 0!==r,F={status:R,fetchStatus:v.fetchStatus,isPending:C,isSuccess:"success"===R,isError:w,isInitialLoading:T,isLoading:T,data:r,dataUpdatedAt:v.dataUpdatedAt,error:m,errorUpdatedAt:g,failureCount:v.fetchFailureCount,failureReason:v.fetchFailureReason,errorUpdateCount:v.errorUpdateCount,isFetched:v.dataUpdateCount>0||v.errorUpdateCount>0,isFetchedAfterMount:v.dataUpdateCount>l.dataUpdateCount||v.errorUpdateCount>l.errorUpdateCount,isFetching:S,isRefetching:S&&!C,isLoadingError:w&&!Q,isPaused:"paused"===v.fetchStatus,isPlaceholderData:b,isRefetchError:w&&Q,isStale:y(t,e),refetch:this.refetch,promise:this.#v,isEnabled:!1!==(0,c.Nc)(e.enabled,t)};if(this.options.experimental_prefetchInRender){let e=t=>{"error"===F.status?t.reject(F.error):void 0!==F.data&&t.resolve(F.data)},r=()=>{e(this.#v=F.promise=(0,a.O)())},i=this.#v;switch(i.status){case"pending":t.queryHash===s.queryHash&&e(i);break;case"fulfilled":("error"===F.status||F.data!==i.value)&&r();break;case"rejected":("error"!==F.status||F.error!==i.reason)&&r()}}return F}updateResult(){let t=this.#g,e=this.createResult(this.#b,this.options);this.#R=this.#b.state,this.#O=this.options,void 0!==this.#R.data&&(this.#w=this.#b),(0,c.VS)(e,t)||(this.#g=e,this.#N({listeners:(()=>{if(!t)return!0;let{notifyOnChangeProps:e}=this.options,r="function"==typeof e?e():e;if("all"===r||!r&&!this.#I.size)return!0;let s=new Set(r??this.#I);return this.options.throwOnError&&s.add("error"),Object.keys(this.#g).some(e=>this.#g[e]!==t[e]&&s.has(e))})()}))}#j(){let t=this.#o.getQueryCache().build(this.#o,this.options);if(t===this.#b)return;let e=this.#b;this.#b=t,this.#m=t.state,this.hasListeners()&&(e?.removeObserver(this),t.addObserver(this))}onQueryUpdate(){this.updateResult(),this.hasListeners()&&this.#U()}#N(t){n.Vr.batch(()=>{t.listeners&&this.listeners.forEach(t=>{t(this.#g)}),this.#o.getQueryCache().notify({query:this.#b,type:"observerResultsUpdated"})})}};function d(t,e){return!1!==(0,c.Nc)(e.enabled,t)&&void 0===t.state.data&&!("error"===t.state.status&&!1===e.retryOnMount)||void 0!==t.state.data&&f(t,e,e.refetchOnMount)}function f(t,e,r){if(!1!==(0,c.Nc)(e.enabled,t)&&"static"!==(0,c.KC)(e.staleTime,t)){let s="function"==typeof r?r(t):r;return"always"===s||!1!==s&&y(t,e)}return!1}function p(t,e,r,s){return(t!==e||!1===(0,c.Nc)(s.enabled,t))&&(!r.suspense||"error"!==t.state.status)&&y(t,r)}function y(t,e){return!1!==(0,c.Nc)(e.enabled,t)&&t.isStaleByTime((0,c.KC)(e.staleTime,t))}var v=r(2265),b=r(29827);r(57437);var m=v.createContext((s=!1,{clearReset:()=>{s=!1},reset:()=>{s=!0},isReset:()=>s})),g=()=>v.useContext(m),R=(t,e)=>{(t.suspense||t.throwOnError||t.experimental_prefetchInRender)&&!e.isReset()&&(t.retryOnMount=!1)},O=t=>{v.useEffect(()=>{t.clearReset()},[t])},S=t=>{let{result:e,errorResetBoundary:r,throwOnError:s,query:i,suspense:n}=t;return e.isError&&!r.isReset()&&!e.isFetching&&i&&(n&&void 0===e.data||(0,c.L3)(s,[e.error,i]))},C=v.createContext(!1),w=()=>v.useContext(C);C.Provider;var T=t=>{if(t.suspense){let e=t=>"static"===t?t:Math.max(t??1e3,1e3),r=t.staleTime;t.staleTime="function"==typeof r?(...t)=>e(r(...t)):e(r),"number"==typeof t.gcTime&&(t.gcTime=Math.max(t.gcTime,1e3))}},Q=(t,e)=>t.isLoading&&t.isFetching&&!e,F=(t,e)=>t?.suspense&&e.isPending,I=(t,e,r)=>e.fetchOptimistic(t).catch(()=>{r.clearReset()});function E(t,e){return function(t,e,r){var s,i,u,o,a;let h=w(),l=g(),d=(0,b.NL)(r),f=d.defaultQueryOptions(t);null===(i=d.getDefaultOptions().queries)||void 0===i||null===(s=i._experimental_beforeQuery)||void 0===s||s.call(i,f),f._optimisticResults=h?"isRestoring":"optimistic",T(f),R(f,l),O(l);let p=!d.getQueryCache().get(f.queryHash),[y]=v.useState(()=>new e(d,f)),m=y.getOptimisticResult(f),C=!h&&!1!==t.subscribed;if(v.useSyncExternalStore(v.useCallback(t=>{let e=C?y.subscribe(n.Vr.batchCalls(t)):c.ZT;return y.updateResult(),e},[y,C]),()=>y.getCurrentResult(),()=>y.getCurrentResult()),v.useEffect(()=>{y.setOptions(f)},[f,y]),F(f,m))throw I(f,y,l);if(S({result:m,errorResetBoundary:l,throwOnError:f.throwOnError,query:d.getQueryCache().get(f.queryHash),suspense:f.suspense}))throw m.error;if(null===(o=d.getDefaultOptions().queries)||void 0===o||null===(u=o._experimental_afterQuery)||void 0===u||u.call(o,f,m),f.experimental_prefetchInRender&&!c.sk&&Q(m,h)){let t=p?I(f,y,l):null===(a=d.getQueryCache().get(f.queryHash))||void 0===a?void 0:a.promise;null==t||t.catch(c.ZT).finally(()=>{y.updateResult()})}return f.notifyOnChangeProps?m:y.trackResult(m)}(t,l,e)}}}]);
\ No newline at end of file
diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/1739-d3bc839f59e07ce9.js b/litellm/proxy/_experimental/out/_next/static/chunks/1739-d3bc839f59e07ce9.js
deleted file mode 100644
index 68196561bb4..00000000000
--- a/litellm/proxy/_experimental/out/_next/static/chunks/1739-d3bc839f59e07ce9.js
+++ /dev/null
@@ -1 +0,0 @@
-"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1739],{25512:function(e,t,l){l.d(t,{P:function(){return s.Z},Q:function(){return a.Z}});var s=l(27281),a=l(57365)},12011:function(e,t,l){l.r(t),l.d(t,{default:function(){return w}});var s=l(57437),a=l(2265),r=l(99376),n=l(78489),i=l(94789),o=l(12514),c=l(49804),d=l(67101),u=l(84264),m=l(49566),g=l(96761),x=l(84566),h=l(19250),y=l(14474),f=l(10032),p=l(5545),j=l(3914);function w(){let[e]=f.Z.useForm(),t=(0,r.useSearchParams)();(0,j.e)("token");let l=t.get("invitation_id"),w=t.get("action"),[b,v]=(0,a.useState)(null),[k,S]=(0,a.useState)(""),[_,N]=(0,a.useState)(""),[C,D]=(0,a.useState)(null),[I,Z]=(0,a.useState)(""),[K,E]=(0,a.useState)(""),[A,T]=(0,a.useState)(!0);return(0,a.useEffect)(()=>{(0,h.getUiConfig)().then(e=>{console.log("ui config in onboarding.tsx:",e),T(!1)})},[]),(0,a.useEffect)(()=>{l&&!A&&(0,h.getOnboardingCredentials)(l).then(e=>{let t=e.login_url;console.log("login_url:",t),Z(t);let l=e.token,s=(0,y.o)(l);E(l),console.log("decoded:",s),v(s.key),console.log("decoded user email:",s.user_email),N(s.user_email),D(s.user_id)})},[l,A]),(0,s.jsx)("div",{className:"mx-auto w-full max-w-md mt-10",children:(0,s.jsxs)(o.Z,{children:[(0,s.jsx)(g.Z,{className:"text-sm mb-5 text-center",children:"\uD83D\uDE85 LiteLLM"}),(0,s.jsx)(g.Z,{className:"text-xl",children:"reset_password"===w?"Reset Password":"Sign up"}),(0,s.jsx)(u.Z,{children:"reset_password"===w?"Reset your password to access Admin UI.":"Claim your user account to login to Admin UI."}),"reset_password"!==w&&(0,s.jsx)(i.Z,{className:"mt-4",title:"SSO",icon:x.GH$,color:"sky",children:(0,s.jsxs)(d.Z,{numItems:2,className:"flex justify-between items-center",children:[(0,s.jsx)(c.Z,{children:"SSO is under the Enterprise Tier."}),(0,s.jsx)(c.Z,{children:(0,s.jsx)(n.Z,{variant:"primary",className:"mb-2",children:(0,s.jsx)("a",{href:"https://forms.gle/W3U4PZpJGFHWtHyA9",target:"_blank",children:"Get Free Trial"})})})]})}),(0,s.jsxs)(f.Z,{className:"mt-10 mb-5 mx-auto",layout:"vertical",onFinish:e=>{console.log("in handle submit. accessToken:",b,"token:",K,"formValues:",e),b&&K&&(e.user_email=_,C&&l&&(0,h.claimOnboardingToken)(b,l,C,e.password).then(e=>{document.cookie="token="+K;let t=(0,h.getProxyBaseUrl)();console.log("proxyBaseUrl:",t);let l=t?"".concat(t,"/ui/?login=success"):"/ui/?login=success";console.log("redirecting to:",l),window.location.href=l}))},children:[(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(f.Z.Item,{label:"Email Address",name:"user_email",children:(0,s.jsx)(m.Z,{type:"email",disabled:!0,value:_,defaultValue:_,className:"max-w-md"})}),(0,s.jsx)(f.Z.Item,{label:"Password",name:"password",rules:[{required:!0,message:"password required to sign up"}],help:"reset_password"===w?"Enter your new password":"Create a password for your account",children:(0,s.jsx)(m.Z,{placeholder:"",type:"password",className:"max-w-md"})})]}),(0,s.jsx)("div",{className:"mt-10",children:(0,s.jsx)(p.ZP,{htmlType:"submit",children:"reset_password"===w?"Reset Password":"Sign Up"})})]})]})})}},39210:function(e,t,l){l.d(t,{Z:function(){return a}});var s=l(19250);let a=async(e,t,l,a,r)=>{let n;n="Admin"!=l&&"Admin Viewer"!=l?await (0,s.teamListCall)(e,(null==a?void 0:a.organization_id)||null,t):await (0,s.teamListCall)(e,(null==a?void 0:a.organization_id)||null),console.log("givenTeams: ".concat(n)),r(n)}},49924:function(e,t,l){var s=l(2265),a=l(19250);t.Z=e=>{let{selectedTeam:t,currentOrg:l,selectedKeyAlias:r,accessToken:n,createClicked:i}=e,[o,c]=(0,s.useState)({keys:[],total_count:0,current_page:1,total_pages:0}),[d,u]=(0,s.useState)(!0),[m,g]=(0,s.useState)(null),x=async function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};try{if(console.log("calling fetchKeys"),!n){console.log("accessToken",n);return}u(!0);let t="number"==typeof e.page?e.page:1,l="number"==typeof e.pageSize?e.pageSize:100,s=await (0,a.keyListCall)(n,null,null,null,null,null,t,l);console.log("data",s),c(s),g(null)}catch(e){g(e instanceof Error?e:Error("An error occurred"))}finally{u(!1)}};return(0,s.useEffect)(()=>{x(),console.log("selectedTeam",t,"currentOrg",l,"accessToken",n,"selectedKeyAlias",r)},[t,l,n,r,i]),{keys:o.keys,isLoading:d,error:m,pagination:{currentPage:o.current_page,totalPages:o.total_pages,totalCount:o.total_count},refresh:x,setKeys:e=>{c(t=>{let l="function"==typeof e?e(t.keys):e;return{...t,keys:l}})}}}},21739:function(e,t,l){l.d(t,{Z:function(){return B}});var s=l(57437),a=l(2265),r=l(19250),n=l(39210),i=l(49804),o=l(67101),c=l(30874),d=l(92668),u=l(16721),m=l(46468),g=l(10032),x=l(22116),h=l(19015),y=l(29233),f=l(49924);l(25512);var p=l(16312),j=l(94292),w=l(99981),b=l(23048),v=l(11713),k=l(30841),S=l(7310),_=l.n(S),N=l(12363),C=l(59872),D=l(71594),I=l(24525),Z=l(10178),K=l(86462),E=l(47686),A=l(44633),T=l(49084),O=l(40728);function L(e){let{keys:t,setKeys:l,isLoading:n=!1,pagination:i,onPageChange:o,pageSize:c=50,teams:d,selectedTeam:u,setSelectedTeam:g,selectedKeyAlias:x,setSelectedKeyAlias:h,accessToken:y,userID:f,userRole:S,organizations:L,setCurrentOrg:U,refresh:z,onSortChange:V,currentSort:P,premiumUser:R,setAccessToken:F}=e,[M,B]=(0,a.useState)(null),[J,W]=(0,a.useState)([]),[H,q]=a.useState(()=>P?[{id:P.sortBy,desc:"desc"===P.sortOrder}]:[{id:"created_at",desc:!0}]),[G,X]=(0,a.useState)({}),{filters:$,filteredKeys:Q,allKeyAliases:Y,allTeams:ee,allOrganizations:et,handleFilterChange:el,handleFilterReset:es}=function(e){let{keys:t,teams:l,organizations:s,accessToken:n}=e,i={"Team ID":"","Organization ID":"","Key Alias":"","User ID":"","Sort By":"created_at","Sort Order":"desc"},[o,c]=(0,a.useState)(i),[d,u]=(0,a.useState)(l||[]),[m,g]=(0,a.useState)(s||[]),[x,h]=(0,a.useState)(t),y=(0,a.useRef)(0),f=(0,a.useCallback)(_()(async e=>{if(!n)return;let t=Date.now();y.current=t;try{let l=await (0,r.keyListCall)(n,e["Organization ID"]||null,e["Team ID"]||null,e["Key Alias"]||null,e["User ID"]||null,e["Key Hash"]||null,1,N.d,e["Sort By"]||null,e["Sort Order"]||null);t===y.current&&l&&(h(l.keys),console.log("called from debouncedSearch filters:",JSON.stringify(e)),console.log("called from debouncedSearch data:",JSON.stringify(l)))}catch(e){console.error("Error searching users:",e)}},300),[n]);(0,a.useEffect)(()=>{if(!t){h([]);return}let e=[...t];o["Team ID"]&&(e=e.filter(e=>e.team_id===o["Team ID"])),o["Organization ID"]&&(e=e.filter(e=>e.organization_id===o["Organization ID"])),h(e)},[t,o]),(0,a.useEffect)(()=>{let e=async()=>{let e=await (0,k.IE)(n);e.length>0&&u(e);let t=await (0,k.cT)(n);t.length>0&&g(t)};n&&e()},[n]);let p=(0,v.a)({queryKey:["allKeys"],queryFn:async()=>{if(!n)throw Error("Access token required");return await (0,k.LO)(n)},enabled:!!n}).data||[];return(0,a.useEffect)(()=>{l&&l.length>0&&u(e=>e.length