From aca7f5732438057b76314c2ecdc39208815b401e Mon Sep 17 00:00:00 2001 From: shivam Date: Wed, 22 Jul 2026 04:23:35 +0000 Subject: [PATCH 01/75] fix(jwt_auth): allow /v1/messages for JWT teams by default Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/proxy/_types.py | 2 +- .../proxy/auth/test_handle_jwt.py | 59 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 7df725bf965..53b6e51aaef 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -4254,7 +4254,7 @@ class LiteLLM_JWTAuth(LiteLLMPydanticObjectBase): team_id_upsert: bool = False team_ids_jwt_field: Optional[str] = None upsert_sso_user_to_team: bool = False - team_allowed_routes: List[str] = ["openai_routes", "info_routes", "mcp_routes"] + team_allowed_routes: List[str] = ["openai_routes", "anthropic_routes", "info_routes", "mcp_routes"] team_id_default: Optional[str] = Field( default=None, description="If no team_id given, default permissions/spend-tracking to this team.s", diff --git a/tests/test_litellm/proxy/auth/test_handle_jwt.py b/tests/test_litellm/proxy/auth/test_handle_jwt.py index ffc5241d027..35aaa0f1254 100644 --- a/tests/test_litellm/proxy/auth/test_handle_jwt.py +++ b/tests/test_litellm/proxy/auth/test_handle_jwt.py @@ -1240,6 +1240,65 @@ async def test_find_team_with_model_access_model_group(monkeypatch): assert team_obj.team_id == "team-1" +@pytest.mark.asyncio +async def test_find_team_with_model_access_v1_messages_default_routes(monkeypatch): + """Regression for #31189: a single-team JWT that grants the requested model + through an access group must resolve on /v1/messages without an explicit + x-litellm-team-id header. /v1/messages lives in `anthropic_routes`, so when a + team has no `team_allowed_routes` configured the default allowlist must cover + it just like /chat/completions and /v1/responses; otherwise the internal route + check fails and surfaces a misleading "No team has access to the requested + model" 403.""" + from litellm.caching import DualCache + from litellm.proxy.utils import ProxyLogging + from litellm.router import Router + + router = Router( + model_list=[ + { + "model_name": "claude-sonnet-4-6", + "litellm_params": {"model": "claude-sonnet-4-6"}, + "model_info": {"access_groups": ["coding_only_models"]}, + } + ] + ) + import sys + import types + + proxy_server_module = types.ModuleType("proxy_server") + proxy_server_module.llm_router = router + monkeypatch.setitem(sys.modules, "litellm.proxy.proxy_server", proxy_server_module) + + team = LiteLLM_TeamTable(team_id="coding-team", models=["coding_only_models"]) + + async def mock_get_team_object(*args, **kwargs): # type: ignore + return team + + monkeypatch.setattr( + "litellm.proxy.auth.handle_jwt.get_team_object", mock_get_team_object + ) + + jwt_handler = JWTHandler() + jwt_handler.litellm_jwtauth = LiteLLM_JWTAuth() + + user_api_key_cache = DualCache() + proxy_logging_obj = ProxyLogging(user_api_key_cache=user_api_key_cache) + + team_id, team_obj = await JWTAuthManager.find_team_with_model_access( + team_ids={"coding-team"}, + requested_model="claude-sonnet-4-6", + route="/v1/messages", + jwt_handler=jwt_handler, + prisma_client=None, + user_api_key_cache=user_api_key_cache, + parent_otel_span=None, + proxy_logging_obj=proxy_logging_obj, + ) + + assert team_id == "coding-team" + assert team_obj.team_id == "coding-team" + + @pytest.mark.asyncio async def test_auth_builder_returns_team_membership_object(): """ From 221b1859db12f76e730e3c3a8eb1a410814744b0 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 22 Jul 2026 17:53:38 +0000 Subject: [PATCH 02/75] fix(proxy): stop serving stale team model allowlist after /team/update get_team_object consults proxy_logging_obj.internal_usage_cache before user_api_key_cache, but _cache_team_object (the refresh every team mutation goes through) only wrote user_api_key_cache. With enable_redis_auth_cache both caches share one Redis, so any request backfills the internal cache's in-memory tier with the team object and that copy keeps shadowing the freshly written team until its TTL expires. The auth builder then wrote the team object it had just read back into the cache after check 6, clobbering the fresh Redis value with the stale one, which made the staleness self-sustaining under traffic: keys with models=["all-team-models"] kept getting 403 team_model_access_denied for models added via /team/update, and kept access to removed ones. _cache_team_object now deletes the internal usage cache entry before writing the refreshed team, and the auth-time write-back is removed so only authoritative writers (DB reads and team mutations) populate the team cache, mirroring how key objects already handle this (see test_auth_does_not_rewrite_cached_key_object_back_into_cache). The LIT-4000 test pinning the removed write-back is deleted; its concern (team object cached under the canonical key) is handled by _cache_team_object inside get_team_object's DB path and pinned by test_cache_team_object_writes_team_id_and_invalidates_team_alias Resolves LIT-4391 --- litellm/proxy/auth/auth_checks.py | 7 +- litellm/proxy/auth/user_api_key_auth.py | 11 - .../proxy/auth/test_auth_checks.py | 121 +++++++++- .../proxy/auth/test_user_api_key_auth.py | 212 ++++++++++-------- .../test_team_endpoints.py | 1 + 5 files changed, 241 insertions(+), 111 deletions(-) diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index 99a867a5d07..4c998f997d9 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -1764,9 +1764,14 @@ async def _cache_team_object( ## CACHE REFRESH TIME! team_table.last_refreshed_at = time.time() + key = "team_id:{}".format(team_id) + + if proxy_logging_obj is not None: + await proxy_logging_obj.internal_usage_cache.dual_cache.async_delete_cache(key=key) + # team_id is the table primary key — guaranteed unique, safe to write. await _cache_management_object( - key="team_id:{}".format(team_id), + key=key, value=team_table, user_api_key_cache=user_api_key_cache, proxy_logging_obj=proxy_logging_obj, diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index 83a8a69511b..620669729f7 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -1945,17 +1945,6 @@ async def _user_api_key_auth_builder( else: valid_token.team_object_permission = None - # Cache under the canonical "team_id:{id}" key so get_team_object and - # _update_team_cache serve this write from the L2 cache. The guard keeps a - # non-team (personal) key, whose team_id is None, from reaching the cache - # layer, which Redis rejects with a NoneType key error. - if valid_token.team_id is not None and _team_obj is not None: - await user_api_key_cache.async_set_cache( - key=f"team_id:{valid_token.team_id}", - value=_team_obj, - model_type=LiteLLM_TeamTableCachedObj, - ) - # Fetch project object if key belongs to a project _project_obj = None if valid_token.project_id is not None: diff --git a/tests/test_litellm/proxy/auth/test_auth_checks.py b/tests/test_litellm/proxy/auth/test_auth_checks.py index 5e07d1bcbc5..b7bf5728cbd 100644 --- a/tests/test_litellm/proxy/auth/test_auth_checks.py +++ b/tests/test_litellm/proxy/auth/test_auth_checks.py @@ -50,6 +50,7 @@ from litellm.proxy.auth.auth_checks import ( vector_store_access_check, ) from litellm.caching.in_memory_cache import InMemoryCache +from litellm.caching.redis_cache import RedisCache from litellm.constants import DEFAULT_MANAGEMENT_OBJECT_IN_MEMORY_CACHE_TTL from litellm.proxy.common_utils.encrypt_decrypt_utils import decrypt_value_helper from litellm.proxy.common_utils.user_api_key_cache import UserApiKeyCache @@ -4156,6 +4157,11 @@ async def test_cache_team_object_writes_team_id_and_invalidates_team_alias(): len(teams)==1 before populating the cache. 3. When team_alias is None, NO alias-key operation happens (no delete of an empty-keyed entry, no spurious write). + 4. DELETES the team_id-keyed entry from the internal usage cache + BEFORE the fresh write (LIT-4391). `_get_team_object_from_cache` + consults the internal usage cache first, so a leftover copy there + (backfilled from a Redis shared with `user_api_key_cache`) would + keep serving the pre-update team allowlist. """ from unittest.mock import AsyncMock, MagicMock @@ -4202,9 +4208,14 @@ async def test_cache_team_object_writes_team_id_and_invalidates_team_alias(): # (2) team_alias-keyed entry is deleted in BOTH the in-memory cache # and the Redis dual cache (mirrors _delete_cache_key_object pattern). cache.delete_cache.assert_called_once_with(key="team_alias:H-Capacity") - logging_obj.internal_usage_cache.dual_cache.async_delete_cache.assert_awaited_once_with( - key="team_alias:H-Capacity" - ) + + # (4) internal usage cache: team_id entry deleted BEFORE the fresh + # write, alias entry deleted as before. + internal_deleted_keys = [ + (c.kwargs.get("key") or c.args[0]) + for c in logging_obj.internal_usage_cache.dual_cache.async_delete_cache.await_args_list + ] + assert internal_deleted_keys == ["team_id:team-1234", "team_alias:H-Capacity"] # ===== team_alias is None: no alias-key operation ===== aliasless = LiteLLM_TeamTableCachedObj(**{**base_team_row, "team_alias": None}) @@ -4222,7 +4233,9 @@ async def test_cache_team_object_writes_team_id_and_invalidates_team_alias(): ) cache2.delete_cache.assert_not_called() - logging_obj2.internal_usage_cache.dual_cache.async_delete_cache.assert_not_awaited() + logging_obj2.internal_usage_cache.dual_cache.async_delete_cache.assert_awaited_once_with( + key="team_id:team-no-alias" + ) written_keys_aliasless = [ (c.kwargs.get("key") or c.args[0]) for c in cache2.async_set_cache.await_args_list @@ -4230,6 +4243,106 @@ async def test_cache_team_object_writes_team_id_and_invalidates_team_alias(): assert written_keys_aliasless == ["team_id:team-no-alias"] +class _SharedFakeRedis(RedisCache): + """Dict-backed stand-in for the single Redis that both + ``user_api_key_cache`` (enable_redis_auth_cache) and + ``proxy_logging_obj.internal_usage_cache.dual_cache`` share in the + LIT-4391 deployment topology. Only the methods DualCache calls are + implemented; ``super().__init__`` is skipped intentionally.""" + + def __init__(self): + self._store: dict = {} + + async def async_set_cache(self, key, value, **kwargs): + self._store[key] = json.dumps(value) + + async def async_get_cache(self, key, **kwargs): + raw = self._store.get(key) + return json.loads(raw) if raw is not None else None + + async def async_delete_cache(self, key): + self._store.pop(key, None) + + +@pytest.mark.asyncio +async def test_team_update_not_shadowed_by_internal_usage_cache_lit_4391(): + """ + Regression test for LIT-4391: keys with models=["all-team-models"] kept + getting 403 team_model_access_denied for models added via /team/update. + + `_get_team_object_from_cache` consults the internal usage cache BEFORE + `user_api_key_cache`. When both share one Redis (enable_redis_auth_cache), + any team read backfills the internal cache's in-memory tier with the team + object. `_cache_team_object` (the /team/update refresh) only wrote + `user_api_key_cache`, so that backfilled copy kept shadowing the update + until its TTL expired — and the auth-time write-back then pushed the stale + copy back into the shared Redis, making the staleness self-sustaining. + + Pins: + 1. After `_cache_team_object` writes an updated team, `get_team_object` + returns the UPDATED model list even though the internal usage cache's + in-memory tier was backfilled with the pre-update team. + 2. The shared Redis still holds the updated team afterwards — the + internal-cache invalidation must happen BEFORE the fresh write, or it + would wipe the value it just wrote. + """ + from litellm.caching.dual_cache import DualCache + from litellm.proxy._types import LiteLLM_TeamTableCachedObj + from litellm.proxy.auth.auth_checks import _cache_team_object, get_team_object + + team_id = "team-lit-4391" + shared_redis = _SharedFakeRedis() + user_api_key_cache = UserApiKeyCache(redis_cache=shared_redis) + proxy_logging_obj = MagicMock() + proxy_logging_obj.internal_usage_cache.dual_cache = DualCache( + redis_cache=shared_redis, + default_in_memory_ttl=300, + ) + prisma_client = MagicMock() + + await _cache_team_object( + team_id=team_id, + team_table=LiteLLM_TeamTableCachedObj(team_id=team_id, models=["model-a"]), + user_api_key_cache=user_api_key_cache, + proxy_logging_obj=proxy_logging_obj, + ) + + primed = await get_team_object( + team_id=team_id, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + proxy_logging_obj=proxy_logging_obj, + ) + assert primed is not None and primed.models == ["model-a"] + + await _cache_team_object( + team_id=team_id, + team_table=LiteLLM_TeamTableCachedObj( + team_id=team_id, models=["model-a", "model-b"] + ), + user_api_key_cache=user_api_key_cache, + proxy_logging_obj=proxy_logging_obj, + ) + + refreshed = await get_team_object( + team_id=team_id, + prisma_client=prisma_client, + user_api_key_cache=user_api_key_cache, + proxy_logging_obj=proxy_logging_obj, + ) + assert refreshed is not None and refreshed.models == ["model-a", "model-b"], ( + "get_team_object served a stale team allowlist after _cache_team_object " + f"refreshed it. Got models={refreshed.models if refreshed else None}" + ) + + redis_copy = await shared_redis.async_get_cache(f"team_id:{team_id}") + assert redis_copy is not None and redis_copy["models"] == ["model-a", "model-b"], ( + "The shared Redis lost the refreshed team object — the internal-cache " + "invalidation must run BEFORE the fresh write, not after. " + f"Got: {redis_copy}" + ) + + MODEL_DISCOVERY_ROUTES = [ "/v1/models", "/models", diff --git a/tests/test_litellm/proxy/auth/test_user_api_key_auth.py b/tests/test_litellm/proxy/auth/test_user_api_key_auth.py index 2c1948adca1..d70e53f2c8a 100644 --- a/tests/test_litellm/proxy/auth/test_user_api_key_auth.py +++ b/tests/test_litellm/proxy/auth/test_user_api_key_auth.py @@ -2635,6 +2635,123 @@ async def test_team_metadata_refreshed_from_team_object_during_auth(): setattr(_proxy_server_mod, k, v) +@pytest.mark.asyncio +async def test_auth_flow_never_persists_fallback_team_object_lit_4391(): + """ + Regression test for LIT-4391 (stale team allowlist poisoning). + + When `get_team_object` fails at the "Check 6" team-auth step (cache miss + inside the DB-throttle window, DB blip, ...), the builder falls back to a + team object reconstructed from the CACHED token's team_* snapshot — which + can be arbitrarily stale (e.g. pre-/team/update models). + + The builder used to write that team object back into `user_api_key_cache` + under "team_id:" after Check 6. Writing a cache-read (or worse, a + token-snapshot) value back into the shared cache re-poisons it — with + enable_redis_auth_cache it clobbered the fresh team `/team/update` had + just written to Redis, making the stale allowlist self-sustaining across + requests. Only authoritative writers (`_cache_team_object` on DB reads and + team mutations) may populate the team cache. + + Pins: the auth flow completes on the fallback path WITHOUT writing any + "team_id:*" cache entry. + """ + from starlette.datastructures import URL + from starlette.requests import Request + from fastapi import HTTPException + + from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth + from litellm.proxy.auth.user_api_key_auth import _user_api_key_auth_builder + + api_key = "sk-test-lit-4391-no-team-writeback" + valid_token = UserAPIKeyAuth( + api_key=api_key, + token=api_key, + user_role=LitellmUserRoles.INTERNAL_USER, + team_id="team-lit-4391", + team_models=["model-a"], + models=["all-team-models"], + ) + + mock_cache = AsyncMock() + mock_cache.async_get_cache = AsyncMock(return_value=valid_token) + mock_cache.async_set_cache = AsyncMock(return_value=None) + + mock_proxy_logging_obj = MagicMock() + mock_proxy_logging_obj.internal_usage_cache = MagicMock() + mock_proxy_logging_obj.internal_usage_cache.dual_cache = AsyncMock() + mock_proxy_logging_obj.post_call_failure_hook = AsyncMock(return_value=None) + + import litellm.proxy.proxy_server as _proxy_server_mod + + _attrs = { + "prisma_client": MagicMock(), + "user_api_key_cache": mock_cache, + "proxy_logging_obj": mock_proxy_logging_obj, + "master_key": "sk-master-key", + "general_settings": {}, + "llm_model_list": [], + "llm_router": None, + "open_telemetry_logger": None, + "model_max_budget_limiter": MagicMock(), + "user_custom_auth": None, + "jwt_handler": None, + "litellm_proxy_admin_name": "admin", + } + _originals = {k: getattr(_proxy_server_mod, k, None) for k in _attrs} + + try: + for k, v in _attrs.items(): + setattr(_proxy_server_mod, k, v) + + request = Request(scope={"type": "http"}) + request._url = URL(url="/chat/completions") + + with ( + patch( + "litellm.proxy.auth.resolvers.store.IdentityStore._resolve_key", + new_callable=AsyncMock, + return_value=valid_token, + ), + patch( + "litellm.proxy.auth.user_api_key_auth.get_team_object", + new_callable=AsyncMock, + side_effect=HTTPException( + status_code=404, + detail={"error": "Team doesn't exist in db."}, + ), + ), + ): + result = await _user_api_key_auth_builder( + request=request, + api_key=f"Bearer {api_key}", + azure_api_key_header="", + anthropic_api_key_header=None, + google_ai_studio_api_key_header=None, + azure_apim_header=None, + request_data={}, + ) + + assert result.team_id == "team-lit-4391" + + team_cache_writes = [ + key + for c in mock_cache.async_set_cache.await_args_list + if isinstance(key := (c.kwargs.get("key") if "key" in c.kwargs else c.args[0]), str) + and key.startswith("team_id:") + ] + assert team_cache_writes == [], ( + "The auth flow wrote a team object into the cache. Fallback/" + "cache-read team objects must never be persisted — only " + "_cache_team_object (DB reads and team mutations) may write " + f"'team_id:*' entries. Got writes: {team_cache_writes}" + ) + + finally: + for k, v in _originals.items(): + setattr(_proxy_server_mod, k, v) + + # --------------------------------------------------------------------------- # _run_centralized_common_checks — centralized authz gate @@ -4073,101 +4190,6 @@ async def test_real_jwt_still_requires_license_when_jwt_auth_enabled(monkeypatch assert "enterprise only feature" in message -@pytest.mark.asyncio -async def test_auth_path_caches_team_object_under_canonical_team_id_key(): - """Regression for LIT-4000: the auth builder must cache the team object under - the canonical ``team_id:{id}`` key that ``get_team_object`` and - ``_update_team_cache`` read, never under the raw ``team_id`` (and never under - a ``None`` key, which Redis rejects with a NoneType key error). A raw or None - key is silently dropped by Redis / never served back, so every request - re-hits Postgres for the team object instead of the L2 cache. - - Drives the real builder for a team-scoped key against a real in-memory - ``UserApiKeyCache`` and reads the team object back. Mutating the cache key at - the write site to the raw ``valid_token.team_id`` (or ``None``) makes the - canonical-key read miss and fails this test. - """ - from fastapi import Request - from starlette.datastructures import URL - - import litellm.proxy.proxy_server as _proxy_server_mod - from litellm.proxy._types import LiteLLM_TeamTableCachedObj - from litellm.proxy.auth.user_api_key_auth import _user_api_key_auth_builder - from litellm.proxy.common_utils.user_api_key_cache import UserApiKeyCache - from litellm.proxy.proxy_server import hash_token - - team_id = "team-lit-4000" - api_key = "sk-lit-4000-team-key" - cache = UserApiKeyCache() - - team_token = UserAPIKeyAuth(token=hash_token(api_key), team_id=team_id) - team_obj = LiteLLM_TeamTableCachedObj(team_id=team_id) - - proxy_logging_obj = MagicMock() - proxy_logging_obj.post_call_failure_hook = AsyncMock(return_value=None) - attrs = { - "prisma_client": MagicMock(), - "user_api_key_cache": cache, - "proxy_logging_obj": proxy_logging_obj, - "master_key": "sk-test-master", - "general_settings": {"allow_requests_on_db_unavailable": False}, - "llm_model_list": [], - "llm_router": None, - "open_telemetry_logger": None, - "model_max_budget_limiter": MagicMock(), - "user_custom_auth": None, - "jwt_handler": None, - "litellm_proxy_admin_name": "admin", - } - originals = {a: getattr(_proxy_server_mod, a, None) for a in attrs} - try: - for k, v in attrs.items(): - setattr(_proxy_server_mod, k, v) - request = Request(scope={"type": "http"}) - request._url = URL(url="/chat/completions") - with ( - patch( - "litellm.proxy.auth.resolvers.store.IdentityStore._resolve_key", - AsyncMock(return_value=team_token), - ), - patch( - "litellm.proxy.auth.user_api_key_auth.get_team_object", - AsyncMock(return_value=team_obj), - ), - patch( - "litellm.proxy.auth.user_api_key_auth._enforce_key_and_fallback_model_access", - new_callable=AsyncMock, - ), - patch( - "litellm.proxy.auth.user_api_key_auth._return_user_api_key_auth_obj", - new_callable=AsyncMock, - return_value=team_token, - ), - patch( - "litellm.proxy.auth.auth_exception_handler.seed_request_identity", - ), - ): - await _user_api_key_auth_builder( - request=request, - api_key=f"Bearer {api_key}", - azure_api_key_header="", - anthropic_api_key_header=None, - google_ai_studio_api_key_header=None, - azure_apim_header=None, - request_data={}, - ) - finally: - for k, v in originals.items(): - setattr(_proxy_server_mod, k, v) - - served = cache.get_cache( - key=f"team_id:{team_id}", model_type=LiteLLM_TeamTableCachedObj - ) - assert served is not None and served.team_id == team_id - assert cache.get_cache(key=team_id) is None - assert cache.get_cache(key=None) is None - - @pytest.mark.asyncio async def test_auth_does_not_rewrite_cached_key_object_back_into_cache(): """A cache-hit auth must not write the token back into the cache. diff --git a/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py index 4936191c344..2b2e0681ee1 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_team_endpoints.py @@ -6500,6 +6500,7 @@ async def test_update_team_org_scoped_tpm_rpm_bypasses_user_limit(): return_value=mock_existing_team ) mock_cache.async_set_cache = AsyncMock() + mock_logging.internal_usage_cache.dual_cache.async_delete_cache = AsyncMock() # Mock team update mock_updated_team = MagicMock(spec=LiteLLM_TeamTable) From 769f434bd252f62b26146d42b46407771e0cb2c0 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 22 Jul 2026 18:06:39 +0000 Subject: [PATCH 03/75] fix(proxy): make team cache invalidation best-effort Greptile review: _cache_team_object runs after a successful DB fetch in get_team_object and after every team mutation's DB write, but DualCache.async_delete_cache propagates backend errors, so a Redis blip during invalidation would turn a healthy team lookup into a 404 and a committed /team/update into a 500. Both the internal usage cache delete and the alias-key invalidation now log a warning and continue on failure, matching how DualCache.async_set_cache already swallows write errors. Worst case on failure is worker-local staleness bounded by the internal cache's in-memory TTL, the same bound other workers already have --- litellm/proxy/auth/auth_checks.py | 24 ++++++++++-- .../proxy/auth/test_auth_checks.py | 39 +++++++++++++++++++ 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index 4c998f997d9..21b455c5d44 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -1767,7 +1767,15 @@ async def _cache_team_object( key = "team_id:{}".format(team_id) if proxy_logging_obj is not None: - await proxy_logging_obj.internal_usage_cache.dual_cache.async_delete_cache(key=key) + try: + await proxy_logging_obj.internal_usage_cache.dual_cache.async_delete_cache(key=key) + except Exception as e: + verbose_proxy_logger.warning( + "Failed to invalidate internal usage cache entry %s; " + "a stale team object may be served until its TTL expires: %s", + key, + e, + ) # team_id is the table primary key — guaranteed unique, safe to write. await _cache_management_object( @@ -1793,9 +1801,17 @@ async def _cache_team_object( # the cache from a verified single row. if team_table.team_alias: alias_key = "team_alias:{}".format(team_table.team_alias) - user_api_key_cache.delete_cache(key=alias_key) - if proxy_logging_obj is not None: - await proxy_logging_obj.internal_usage_cache.dual_cache.async_delete_cache(key=alias_key) + try: + user_api_key_cache.delete_cache(key=alias_key) + if proxy_logging_obj is not None: + await proxy_logging_obj.internal_usage_cache.dual_cache.async_delete_cache(key=alias_key) + except Exception as e: + verbose_proxy_logger.warning( + "Failed to invalidate cached team alias entry %s; " + "a stale team object may be served until its TTL expires: %s", + alias_key, + e, + ) async def _cache_key_object( diff --git a/tests/test_litellm/proxy/auth/test_auth_checks.py b/tests/test_litellm/proxy/auth/test_auth_checks.py index b7bf5728cbd..fa7ee5b531a 100644 --- a/tests/test_litellm/proxy/auth/test_auth_checks.py +++ b/tests/test_litellm/proxy/auth/test_auth_checks.py @@ -4343,6 +4343,45 @@ async def test_team_update_not_shadowed_by_internal_usage_cache_lit_4391(): ) +@pytest.mark.asyncio +async def test_cache_team_object_tolerates_cache_invalidation_failures(): + """ + Greptile review on the LIT-4391 fix: `_cache_team_object` runs after a + successful DB fetch (inside `get_team_object`) and after every team + mutation's DB write. A cache-backend error during the best-effort + invalidations must NOT fail those operations — otherwise a Redis blip + turns a healthy team lookup into a 404 and a committed /team/update into + a 500. The authoritative team_id-keyed write must still happen. + """ + from litellm.proxy._types import LiteLLM_TeamTableCachedObj + from litellm.proxy.auth.auth_checks import _cache_team_object + + cache = MagicMock() + cache.async_set_cache = AsyncMock() + cache.delete_cache = MagicMock(side_effect=Exception("redis down")) + logging_obj = MagicMock() + logging_obj.internal_usage_cache.dual_cache.async_delete_cache = AsyncMock( + side_effect=Exception("redis down") + ) + + await _cache_team_object( + team_id="team-cache-outage", + team_table=LiteLLM_TeamTableCachedObj( + team_id="team-cache-outage", + team_alias="cache-outage-alias", + models=["model-a"], + ), + user_api_key_cache=cache, + proxy_logging_obj=logging_obj, + ) + + written_keys = [ + (c.kwargs.get("key") or c.args[0]) + for c in cache.async_set_cache.await_args_list + ] + assert written_keys == ["team_id:team-cache-outage"] + + MODEL_DISCOVERY_ROUTES = [ "/v1/models", "/models", From a1504452d9937c9e31a470016f93853382d2a795 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Wed, 22 Jul 2026 18:12:32 +0000 Subject: [PATCH 04/75] fix(proxy): suppress BLE001 for best-effort cache invalidation guards The ruff strict gate flagged the two new blind excepts. They are deliberate: the guards exist so that any cache backend failure, not just an enumerable set of Redis errors, leaves the authoritative team write and the mutation response intact --- litellm/proxy/auth/auth_checks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index 21b455c5d44..72f59238ec3 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -1769,7 +1769,7 @@ async def _cache_team_object( if proxy_logging_obj is not None: try: await proxy_logging_obj.internal_usage_cache.dual_cache.async_delete_cache(key=key) - except Exception as e: + except Exception as e: # noqa: BLE001 # best-effort invalidation: any cache backend error must not fail the write verbose_proxy_logger.warning( "Failed to invalidate internal usage cache entry %s; " "a stale team object may be served until its TTL expires: %s", @@ -1805,7 +1805,7 @@ async def _cache_team_object( user_api_key_cache.delete_cache(key=alias_key) if proxy_logging_obj is not None: await proxy_logging_obj.internal_usage_cache.dual_cache.async_delete_cache(key=alias_key) - except Exception as e: + except Exception as e: # noqa: BLE001 # best-effort invalidation: any cache backend error must not fail the mutation verbose_proxy_logger.warning( "Failed to invalidate cached team alias entry %s; " "a stale team object may be served until its TTL expires: %s", From b5c363e016cf4bf6c6af89109f084eaa621b62f0 Mon Sep 17 00:00:00 2001 From: Mihidum Hettiyahandi <55163074+mihidumh@users.noreply.github.com> Date: Wed, 15 Jul 2026 08:24:09 +1000 Subject: [PATCH 05/75] fix(router_strategy): serialize latency for non-chat responses in lowest-latency routing log_success_event/async_log_success_event only converted the end_time - start_time timedelta to float seconds inside the isinstance(response_obj, ModelResponse) branch, so every embedding / speech / image response appended a raw timedelta to the latency list and broke the Redis cache sync with 'Object of type timedelta is not JSON serializable' (no cross-replica latency sharing for those model groups + error-log spam). Normalize response_ms to float seconds up-front in both handlers. Completes the partial fix from #14040. Fixes #33169 Co-Authored-By: Claude Fable 5 --- litellm/router_strategy/lowest_latency.py | 14 +++ .../router_strategy/test_lowest_latency.py | 96 +++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 tests/test_litellm/router_strategy/test_lowest_latency.py diff --git a/litellm/router_strategy/lowest_latency.py b/litellm/router_strategy/lowest_latency.py index 23476fe7dcc..14fca48c0d6 100644 --- a/litellm/router_strategy/lowest_latency.py +++ b/litellm/router_strategy/lowest_latency.py @@ -73,6 +73,13 @@ class LowestLatencyLoggingHandler(CustomLogger): precise_minute = f"{current_date}-{current_hour}-{current_minute}" response_ms = end_time - start_time + if isinstance(response_ms, timedelta): + # normalize to float seconds up-front: non-chat responses + # (embeddings, speech, image) skip the ModelResponse branch + # below, and a raw timedelta appended to the latency list + # breaks JSON serialization when the router cache syncs to + # Redis (issue #33169) + response_ms = response_ms.total_seconds() time_to_first_token_response_time = None if kwargs.get("stream", None) is not None and kwargs["stream"] is True: @@ -262,6 +269,13 @@ class LowestLatencyLoggingHandler(CustomLogger): precise_minute = f"{current_date}-{current_hour}-{current_minute}" response_ms = end_time - start_time + if isinstance(response_ms, timedelta): + # normalize to float seconds up-front: non-chat responses + # (embeddings, speech, image) skip the ModelResponse branch + # below, and a raw timedelta appended to the latency list + # breaks JSON serialization when the router cache syncs to + # Redis (issue #33169) + response_ms = response_ms.total_seconds() time_to_first_token_response_time = None if kwargs.get("stream", None) is not None and kwargs["stream"] is True: # only log ttft for streaming request diff --git a/tests/test_litellm/router_strategy/test_lowest_latency.py b/tests/test_litellm/router_strategy/test_lowest_latency.py new file mode 100644 index 00000000000..4ec89f281e8 --- /dev/null +++ b/tests/test_litellm/router_strategy/test_lowest_latency.py @@ -0,0 +1,96 @@ +#### What this tests #### +# Latency values recorded by lowest-latency routing must be JSON +# serializable for non-chat responses too (embeddings/speech/image skip +# the ModelResponse branch, so the raw timedelta used to leak into the +# latency list and break the Redis cache sync). Issue #33169. + +import json +import os +import sys +from datetime import datetime, timedelta + +import pytest + +sys.path.insert( + 0, os.path.abspath("../../..") +) # Adds the parent directory to the system path + +import litellm +from litellm.caching.caching import DualCache +from litellm.router_strategy.lowest_latency import LowestLatencyLoggingHandler + +DEPLOYMENT_ID = "9876" +KWARGS = { + "litellm_params": { + "metadata": { + "model_group": "gemini-embedding-001", + "deployment": "vertex_ai/gemini-embedding-001", + }, + "model_info": {"id": DEPLOYMENT_ID}, + } +} + + +def _embedding_response(): + return litellm.EmbeddingResponse( + model="gemini-embedding-001", + data=[{"embedding": [0.1, 0.2], "index": 0, "object": "embedding"}], + object="list", + usage=litellm.Usage(prompt_tokens=5, completion_tokens=0, total_tokens=5), + ) + + +def _recorded_latencies(cache: DualCache): + cached = cache.get_cache(key="gemini-embedding-001_map") or {} + return cached.get(DEPLOYMENT_ID, {}).get("latency", []) + + +def test_sync_embedding_latency_is_json_serializable(): + """log_success_event with datetime start/end (as the proxy passes) must not + record a raw timedelta for non-ModelResponse results.""" + cache = DualCache() + handler = LowestLatencyLoggingHandler(router_cache=cache) + + start_time = datetime(2026, 1, 1, 12, 0, 0) + end_time = datetime(2026, 1, 1, 12, 0, 2) + + handler.log_success_event( + response_obj=_embedding_response(), + kwargs=KWARGS, + start_time=start_time, + end_time=end_time, + ) + + latencies = _recorded_latencies(cache) + assert latencies, "expected a latency entry to be recorded" + assert all( + not isinstance(value, timedelta) for value in latencies + ), f"raw timedelta leaked into latency list: {latencies}" + assert latencies[-1] == pytest.approx(2.0) + # the exact failure mode from production: redis cache sync json.dumps + json.dumps({"latency": latencies}) + + +@pytest.mark.asyncio +async def test_async_embedding_latency_is_json_serializable(): + """async_log_success_event is the path the proxy actually hits.""" + cache = DualCache() + handler = LowestLatencyLoggingHandler(router_cache=cache) + + start_time = datetime(2026, 1, 1, 12, 0, 0) + end_time = datetime(2026, 1, 1, 12, 0, 3) + + await handler.async_log_success_event( + response_obj=_embedding_response(), + kwargs=KWARGS, + start_time=start_time, + end_time=end_time, + ) + + latencies = _recorded_latencies(cache) + assert latencies, "expected a latency entry to be recorded" + assert all( + not isinstance(value, timedelta) for value in latencies + ), f"raw timedelta leaked into latency list: {latencies}" + assert latencies[-1] == pytest.approx(3.0) + json.dumps({"latency": latencies}) From b9a7b807b08e72f5390af89fdca7b55186fa4b81 Mon Sep 17 00:00:00 2001 From: Mihidum Hettiyahandi <55163074+mihidumh@users.noreply.github.com> Date: Wed, 15 Jul 2026 09:30:53 +1000 Subject: [PATCH 06/75] refactor(router_strategy): drop now-dead timedelta branch after up-front normalization response_ms is normalized to float seconds at the top of both handlers, so the isinstance(response_ms, timedelta) guard inside the ModelResponse branch was unreachable and the Union[float, timedelta] annotation on final_value was wider than reality. Review follow-up, no behavior change. Co-Authored-By: Claude Fable 5 --- litellm/router_strategy/lowest_latency.py | 30 +++++++++-------------- 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/litellm/router_strategy/lowest_latency.py b/litellm/router_strategy/lowest_latency.py index 14fca48c0d6..da81534f389 100644 --- a/litellm/router_strategy/lowest_latency.py +++ b/litellm/router_strategy/lowest_latency.py @@ -86,7 +86,7 @@ class LowestLatencyLoggingHandler(CustomLogger): # only log ttft for streaming request time_to_first_token_response_time = kwargs.get("completion_start_time", end_time) - start_time - final_value: Union[float, timedelta] = response_ms + final_value: float = response_ms time_to_first_token: Optional[float] = None total_tokens = 0 @@ -96,15 +96,12 @@ class LowestLatencyLoggingHandler(CustomLogger): completion_tokens = _usage.completion_tokens total_tokens = _usage.total_tokens - # Handle both timedelta and float response times - if isinstance(response_ms, timedelta): - response_seconds = response_ms.total_seconds() - else: - response_seconds = response_ms + # response_ms is already normalized to float seconds above + response_seconds = response_ms - final_value = safe_divide_seconds(response_seconds, completion_tokens) - if final_value is not None: - final_value = float(final_value) + normalized_value = safe_divide_seconds(response_seconds, completion_tokens) + if normalized_value is not None: + final_value = float(normalized_value) else: final_value = response_seconds @@ -281,7 +278,7 @@ class LowestLatencyLoggingHandler(CustomLogger): # only log ttft for streaming request time_to_first_token_response_time = kwargs.get("completion_start_time", end_time) - start_time - final_value: Union[float, timedelta] = response_ms + final_value: float = response_ms total_tokens = 0 time_to_first_token: Optional[float] = None @@ -291,15 +288,12 @@ class LowestLatencyLoggingHandler(CustomLogger): completion_tokens = _usage.completion_tokens total_tokens = _usage.total_tokens - # Handle both timedelta and float response times - if isinstance(response_ms, timedelta): - response_seconds = response_ms.total_seconds() - else: - response_seconds = response_ms + # response_ms is already normalized to float seconds above + response_seconds = response_ms - final_value = safe_divide_seconds(response_seconds, completion_tokens) - if final_value is not None: - final_value = float(final_value) + normalized_value = safe_divide_seconds(response_seconds, completion_tokens) + if normalized_value is not None: + final_value = float(normalized_value) else: final_value = response_ms From 3cde96d84822316b8ea69330e7585b155ded8592 Mon Sep 17 00:00:00 2001 From: Mihidum Hettiyahandi <55163074+mihidumh@users.noreply.github.com> Date: Wed, 15 Jul 2026 12:14:32 +1000 Subject: [PATCH 07/75] refactor(router_strategy): align async else-branch with sync (response_seconds) Review nit: no behavioral difference (response_seconds = response_ms at that point), symmetry only. Co-Authored-By: Claude Fable 5 --- litellm/router_strategy/lowest_latency.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/router_strategy/lowest_latency.py b/litellm/router_strategy/lowest_latency.py index da81534f389..ffe8245b012 100644 --- a/litellm/router_strategy/lowest_latency.py +++ b/litellm/router_strategy/lowest_latency.py @@ -295,7 +295,7 @@ class LowestLatencyLoggingHandler(CustomLogger): if normalized_value is not None: final_value = float(normalized_value) else: - final_value = response_ms + final_value = response_seconds if time_to_first_token_response_time is not None: if isinstance(time_to_first_token_response_time, timedelta): From ac8ee71512658a9911a638d8bbfb8746c5d848fe Mon Sep 17 00:00:00 2001 From: Mihidum Hettiyahandi <55163074+mihidumh@users.noreply.github.com> Date: Thu, 16 Jul 2026 12:46:54 +1000 Subject: [PATCH 08/75] test(router_strategy): cover chat-path normalization branches in both handlers Codecov flagged the ModelResponse-branch lines as uncovered: add async per-token normalization, plus zero-completion-token fallback tests for both handlers (the else branch storing plain float seconds). Co-Authored-By: Claude Fable 5 --- .../router_strategy/test_lowest_latency.py | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/tests/test_litellm/router_strategy/test_lowest_latency.py b/tests/test_litellm/router_strategy/test_lowest_latency.py index 4ec89f281e8..4edc1e21d6b 100644 --- a/tests/test_litellm/router_strategy/test_lowest_latency.py +++ b/tests/test_litellm/router_strategy/test_lowest_latency.py @@ -94,3 +94,77 @@ async def test_async_embedding_latency_is_json_serializable(): ), f"raw timedelta leaked into latency list: {latencies}" assert latencies[-1] == pytest.approx(3.0) json.dumps({"latency": latencies}) + + +def _chat_response(completion_tokens: int): + return litellm.ModelResponse( + model="gpt-4o-mini", + choices=[ + litellm.Choices( + finish_reason="stop", + index=0, + message=litellm.Message(content="hi", role="assistant"), + ) + ], + usage=litellm.Usage( + prompt_tokens=10, + completion_tokens=completion_tokens, + total_tokens=10 + completion_tokens, + ), + ) + + +@pytest.mark.asyncio +async def test_async_chat_latency_normalized_per_token(): + """Chat responses go through the per-token normalization branch — with the + up-front timedelta conversion the stored value must be seconds/token.""" + cache = DualCache() + handler = LowestLatencyLoggingHandler(router_cache=cache) + + await handler.async_log_success_event( + response_obj=_chat_response(completion_tokens=4), + kwargs=KWARGS, + start_time=datetime(2026, 1, 1, 12, 0, 0), + end_time=datetime(2026, 1, 1, 12, 0, 2), + ) + + latencies = _recorded_latencies(cache) + assert latencies and latencies[-1] == pytest.approx(0.5) # 2s / 4 tokens + json.dumps({"latency": latencies}) + + +@pytest.mark.asyncio +async def test_async_chat_zero_completion_tokens_falls_back_to_seconds(): + """safe_divide_seconds returns None for zero tokens — the fallback branch + must store plain float seconds, not a timedelta.""" + cache = DualCache() + handler = LowestLatencyLoggingHandler(router_cache=cache) + + await handler.async_log_success_event( + response_obj=_chat_response(completion_tokens=0), + kwargs=KWARGS, + start_time=datetime(2026, 1, 1, 12, 0, 0), + end_time=datetime(2026, 1, 1, 12, 0, 3), + ) + + latencies = _recorded_latencies(cache) + assert latencies and latencies[-1] == pytest.approx(3.0) + assert not isinstance(latencies[-1], timedelta) + json.dumps({"latency": latencies}) + + +def test_sync_chat_zero_completion_tokens_falls_back_to_seconds(): + cache = DualCache() + handler = LowestLatencyLoggingHandler(router_cache=cache) + + handler.log_success_event( + response_obj=_chat_response(completion_tokens=0), + kwargs=KWARGS, + start_time=datetime(2026, 1, 1, 12, 0, 0), + end_time=datetime(2026, 1, 1, 12, 0, 2), + ) + + latencies = _recorded_latencies(cache) + assert latencies and latencies[-1] == pytest.approx(2.0) + assert not isinstance(latencies[-1], timedelta) + json.dumps({"latency": latencies}) From 198c1219445ba01758f9aaf59e02a489a458ac4c Mon Sep 17 00:00:00 2001 From: mateo Date: Fri, 24 Jul 2026 20:09:05 +0000 Subject: [PATCH 09/75] fix(responses_bridge): keep one chat completion id per stream and always stream completed responses Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../handler.py | 39 +++- .../transformation.py | 12 +- .../coverage_registry/llm_conversational.yaml | 3 + tests/e2e/e2e_http.py | 7 +- .../test_responses_bridge_streaming_e2e.py | 173 ++++++++++++++++++ ...itellm_responses_transformation_handler.py | 62 +++++++ ...responses_transformation_transformation.py | 36 ++++ 7 files changed, 328 insertions(+), 4 deletions(-) create mode 100644 tests/e2e/llm_translation/test_responses_bridge_streaming_e2e.py diff --git a/litellm/completion_extras/litellm_responses_transformation/handler.py b/litellm/completion_extras/litellm_responses_transformation/handler.py index 8f12d855880..cf517440cd5 100644 --- a/litellm/completion_extras/litellm_responses_transformation/handler.py +++ b/litellm/completion_extras/litellm_responses_transformation/handler.py @@ -209,7 +209,15 @@ class ResponsesToCompletionBridgeHandler: json_mode=kwargs.get("json_mode"), ) elif isinstance(result, ModelResponse): - return result + if not stream: + return result + return self._completed_response_as_stream( + response=result, + model=model, + custom_llm_provider=custom_llm_provider, + logging_obj=logging_obj, + json_mode=kwargs.get("json_mode"), + ) elif not stream: responses_api_response = self._collect_response_from_stream(result) return self.transformation_handler.transform_response( @@ -299,7 +307,15 @@ class ResponsesToCompletionBridgeHandler: json_mode=kwargs.get("json_mode"), ) elif isinstance(result, ModelResponse): - return result + if not stream: + return result + return self._completed_response_as_stream( + response=result, + model=model, + custom_llm_provider=custom_llm_provider, + logging_obj=logging_obj, + json_mode=kwargs.get("json_mode"), + ) elif not stream: responses_api_response = await self._collect_response_from_stream_async(result) return self.transformation_handler.transform_response( @@ -331,6 +347,25 @@ class ResponsesToCompletionBridgeHandler: ) return self._apply_post_stream_processing(streamwrapper, model, custom_llm_provider) + def _completed_response_as_stream( + self, + response: "ModelResponse", + model: str, + custom_llm_provider: str, + logging_obj: "LiteLLMLoggingObj", + json_mode: bool | None, + ) -> Any: + from litellm.litellm_core_utils.streaming_handler import CustomStreamWrapper + from litellm.llms.base_llm.base_model_iterator import MockResponseIterator + + streamwrapper = CustomStreamWrapper( + completion_stream=MockResponseIterator(model_response=response, json_mode=json_mode), + model=model, + custom_llm_provider=custom_llm_provider, + logging_obj=logging_obj, + ) + return self._apply_post_stream_processing(streamwrapper, model, custom_llm_provider) + @staticmethod def _apply_post_stream_processing( stream: "CustomStreamWrapper", diff --git a/litellm/completion_extras/litellm_responses_transformation/transformation.py b/litellm/completion_extras/litellm_responses_transformation/transformation.py index aecb2552b53..d96f3e110d9 100644 --- a/litellm/completion_extras/litellm_responses_transformation/transformation.py +++ b/litellm/completion_extras/litellm_responses_transformation/transformation.py @@ -1074,6 +1074,7 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator): def __init__(self, streaming_response, sync_stream: bool, json_mode: Optional[bool] = False): super().__init__(streaming_response, sync_stream, json_mode) + self._chat_completion_id: str | None = None def _handle_string_chunk( self, str_line: Union[str, "BaseModel"] @@ -1381,4 +1382,13 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator): ModelResponseStream: OpenAI-formatted streaming chunk """ verbose_logger.debug(f"Chat provider: transform_streaming_response called with chunk: {chunk}") - return OpenAiResponsesToChatCompletionStreamIterator.translate_responses_chunk_to_openai_stream(chunk) + return self._with_stream_scoped_id( + OpenAiResponsesToChatCompletionStreamIterator.translate_responses_chunk_to_openai_stream(chunk) + ) + + def _with_stream_scoped_id(self, chunk: "ModelResponseStream") -> "ModelResponseStream": + if self._chat_completion_id is None: + self._chat_completion_id = chunk.id + else: + chunk.id = self._chat_completion_id + return chunk diff --git a/tests/e2e/coverage_registry/llm_conversational.yaml b/tests/e2e/coverage_registry/llm_conversational.yaml index 26280d35da0..a8e1e8a0baf 100644 --- a/tests/e2e/coverage_registry/llm_conversational.yaml +++ b/tests/e2e/coverage_registry/llm_conversational.yaml @@ -4,6 +4,9 @@ - {id: llm.chat_completions.openai.basic.nonstream.cost_logged, module: llm, tier: P0, subject_endpoint: chat_completions, route: openai, capability: basic, streaming: nonstream, assertions: [works, cost_logged], source: "proxy_server.py:8455", rationale: "Cost logging regression catch"} - {id: llm.chat_completions.openai.tool_use.nonstream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: openai, capability: tool_use, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "OpenAI function_calling; high usage"} - {id: llm.chat_completions.openai.tool_use.stream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: openai, capability: tool_use, streaming: stream, assertions: [works], source: "model_prices json", rationale: "Tool calls over streaming"} +- {id: llm.chat_completions.openai.basic.stream.bridge_shares_chunk_id, module: llm, tier: P0, subject_endpoint: chat_completions, route: openai, capability: basic, streaming: stream, assertions: [stable_chunk_id], source: "completion_extras/litellm_responses_transformation/transformation.py", rationale: "A responses-only model served over /chat/completions must stream every chunk under one chat completion id; per-chunk ids make id-accumulating SDKs drop the response", fail_before_fix: proven} +- {id: llm.chat_completions.openai.basic.stream.bridge_streams_sse, module: llm, tier: P0, subject_endpoint: chat_completions, route: openai, capability: basic, streaming: stream, assertions: [works], source: "completion_extras/litellm_responses_transformation/handler.py", rationale: "The Responses bridge must answer a streaming chat request with real SSE (content deltas, finish_reason, [DONE]), never a completed response the SSE generator cannot iterate"} +- {id: llm.chat_completions.openai.tool_use.stream.bridge_streams_tool_call, module: llm, tier: P0, subject_endpoint: chat_completions, route: openai, capability: tool_use, streaming: stream, assertions: [works], source: "completion_extras/litellm_responses_transformation/transformation.py", rationale: "Tool calls translated from Responses events must reassemble into one named call with parseable argument JSON over the bridged stream"} - {id: llm.chat_completions.openai.vision.nonstream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: openai, capability: vision, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "gpt-4o vision; high usage"} - {id: llm.chat_completions.openai.prompt_cache_5m.nonstream.works, module: llm, tier: P0, subject_endpoint: chat_completions, route: openai, capability: prompt_cache_5m, streaming: nonstream, assertions: [works], source: "model_prices json", rationale: "Prompt caching cost optimization"} - {id: llm.chat_completions.openai.service_tier.nonstream.works, module: llm, tier: P1, subject_endpoint: chat_completions, route: openai, capability: service_tier, streaming: nonstream, assertions: [works], source: "OpenAI service_tier param", rationale: "OpenAI scale-tier request option is forwarded and echoed"} diff --git a/tests/e2e/e2e_http.py b/tests/e2e/e2e_http.py index 03d7b5d051a..f22438ac428 100644 --- a/tests/e2e/e2e_http.py +++ b/tests/e2e/e2e_http.py @@ -137,6 +137,7 @@ class StreamingResponse(BaseModel): # quota) arrive as SSE error events inside an otherwise-successful response; # the consumed body is elided, so this is the only place they surface. stream_error: str | None = None + stream_done: bool = False @property def ok(self) -> bool: @@ -408,6 +409,7 @@ def _streaming_outcome(resp: requests.Response, stream: bool) -> StreamingRespon chunks = 0 stream_error: str | None = None stream_events: list[str] = [] + stream_done = False for line in lines: if not line: continue @@ -415,7 +417,9 @@ def _streaming_outcome(resp: requests.Response, stream: bool) -> StreamingRespon decoded_line = line.decode(errors="replace") if decoded_line.startswith("data: "): payload = decoded_line.removeprefix("data: ") - if payload != "[DONE]": + if payload == "[DONE]": + stream_done = True + else: stream_events.append(payload) if stream_error is None and ( line.startswith(b"event: error") @@ -433,6 +437,7 @@ def _streaming_outcome(resp: requests.Response, stream: bool) -> StreamingRespon body="", chunks=chunks, stream_events=stream_events, + stream_done=stream_done, stream_error=stream_error, ) diff --git a/tests/e2e/llm_translation/test_responses_bridge_streaming_e2e.py b/tests/e2e/llm_translation/test_responses_bridge_streaming_e2e.py new file mode 100644 index 00000000000..9a45743a0cd --- /dev/null +++ b/tests/e2e/llm_translation/test_responses_bridge_streaming_e2e.py @@ -0,0 +1,173 @@ +"""Live /chat/completions streaming through the Responses API bridge. + +Responses-only models (gpt-5.3-codex here, the same shape as the GPT-5.6 models +customers reach over bedrock_mantle) cannot serve /chat/completions natively, so the +proxy translates the request to /v1/responses and translates each Responses event back +into a chat completion chunk. Two customer-visible contracts only hold on that path: + +- every chunk of one stream carries the same ``id`` (#32854). The bridge builds a chunk + per Responses event, so a regression there hands each chunk a fresh ``chatcmpl-`` + and SDKs that accumulate by id (openai-go's ChatCompletionAccumulator) silently drop + everything after the first chunk while the HTTP response still looks healthy +- the bridge always answers a streaming request with a real SSE stream (#33154). When it + hands back an already-completed response instead, the proxy's SSE generator dies with + "'async for' requires an object with __aiter__ method" mid-stream +""" + +from __future__ import annotations + +import pytest +from pydantic import BaseModel + +from e2e_config import unique_marker +from e2e_http import StreamingResponse +from lifecycle import ResourceManager +from models import ChatBody, ChatMessage, ChatTool, ChatToolFunction, LiteLLMParamsBody +from passthrough_client import PassthroughClient + +pytestmark = pytest.mark.e2e + +RESPONSES_ONLY_BACKEND = "openai/gpt-5.3-codex" + + +class _BridgeToolCallFunction(BaseModel): + name: str | None = None + arguments: str | None = None + + +class _BridgeToolCall(BaseModel): + function: _BridgeToolCallFunction = _BridgeToolCallFunction() + + +class _BridgeDelta(BaseModel): + content: str | None = None + tool_calls: list[_BridgeToolCall] | None = None + + +class _BridgeChoice(BaseModel): + delta: _BridgeDelta = _BridgeDelta() + finish_reason: str | None = None + + +class _BridgeChunk(BaseModel): + id: str + choices: list[_BridgeChoice] = [] + + +class _WeatherArgs(BaseModel): + location: str + + +_WEATHER_TOOL = ChatTool( + function=ChatToolFunction( + name="get_weather", + description="Get the current weather for a location", + parameters={ + "type": "object", + "properties": {"location": {"type": "string"}}, + "required": ["location"], + }, + ) +) + + +def _bridge_chunks(result: StreamingResponse) -> list[_BridgeChunk]: + """Parse the SSE events of a bridged stream, failing loudly on a stream that never + established, carried an error event, or delivered no chunks.""" + assert result.ok and result.is_streaming, f"bridged stream was not established: {result}" + assert result.stream_error is None, f"bridged stream carried an error event: {result.stream_error}" + chunks = [_BridgeChunk.model_validate_json(event) for event in result.stream_events] + assert chunks, f"bridged stream delivered no chunks: {result.body[:500]}" + return chunks + + +class TestResponsesBridgeChatCompletionsStreaming: + @pytest.fixture + def bridged_model(self, client: PassthroughClient, resources: ResourceManager) -> str: + model = f"e2e-bridge-stream-{unique_marker()}" + model_id = client.proxy.create_model( + model, + LiteLLMParamsBody(model=RESPONSES_ONLY_BACKEND, api_key="os.environ/OPENAI_API_KEY"), + ) + resources.defer(lambda: client.proxy.delete_model(model_id)) + return model + + @pytest.mark.covers( + "llm.chat_completions.openai.basic.stream.bridge_shares_chunk_id", + exercised_on=["chat_completions"], + ) + def test_bridged_stream_shares_one_chunk_id( + self, client: PassthroughClient, resources: ResourceManager, bridged_model: str + ) -> None: + result = client.proxy.chat_stream( + resources.key(), + ChatBody( + model=bridged_model, + messages=[ChatMessage(role="user", content=f"Count from 1 to 5, one number per line. {unique_marker()}")], + max_tokens=64, + stream=True, + ), + ) + + chunks = _bridge_chunks(result) + ids = {chunk.id for chunk in chunks} + assert len(ids) == 1, f"bridged stream used {len(ids)} different chunk ids: {sorted(ids)[:5]}" + assert ids.pop().startswith("chatcmpl-"), f"bridged chunk id is not chat-completion shaped: {chunks[0].id}" + + @pytest.mark.covers( + "llm.chat_completions.openai.basic.stream.bridge_streams_sse", + exercised_on=["chat_completions"], + ) + def test_bridged_stream_delivers_content_finish_reason_and_done( + self, client: PassthroughClient, resources: ResourceManager, bridged_model: str + ) -> None: + result = client.proxy.chat_stream( + resources.key(), + ChatBody( + model=bridged_model, + messages=[ChatMessage(role="user", content=f"Reply with the single word pong. {unique_marker()}")], + max_tokens=32, + stream=True, + ), + ) + + chunks = _bridge_chunks(result) + content = "".join(choice.delta.content or "" for chunk in chunks for choice in chunk.choices) + assert content.strip(), f"bridged stream completed with no content deltas: {result.stream_events[:3]}" + assert any( + choice.finish_reason for chunk in chunks for choice in chunk.choices + ), f"bridged stream never emitted a finish_reason: {result.stream_events[-3:]}" + assert result.stream_done, f"bridged stream did not terminate with [DONE]: {result.stream_events[-2:]}" + + @pytest.mark.covers( + "llm.chat_completions.openai.tool_use.stream.bridge_streams_tool_call", + exercised_on=["chat_completions"], + ) + def test_bridged_stream_reassembles_tool_call( + self, client: PassthroughClient, resources: ResourceManager, bridged_model: str + ) -> None: + result = client.proxy.chat_stream( + resources.key(), + ChatBody( + model=bridged_model, + messages=[ + ChatMessage( + role="user", + content="What is the weather in San Francisco? Use the get_weather tool.", + ) + ], + tools=[_WEATHER_TOOL], + tool_choice="required", + max_tokens=256, + stream=True, + ), + ) + + chunks = _bridge_chunks(result) + calls = [call for chunk in chunks for choice in chunk.choices for call in (choice.delta.tool_calls or [])] + assert calls, f"bridged stream returned no tool call for a tool-forced prompt: {result.stream_events[:5]}" + name = "".join(call.function.name or "" for call in calls) + arguments = "".join(call.function.arguments or "" for call in calls) + assert name == "get_weather", f"bridged stream streamed the wrong tool name: {name!r}" + args = _WeatherArgs.model_validate_json(arguments) + assert args.location.strip(), f"bridged tool call arguments missing location: {arguments!r}" diff --git a/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_handler.py b/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_handler.py index a5bc01c2b74..8ecb7f4c6f0 100644 --- a/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_handler.py +++ b/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_handler.py @@ -203,3 +203,65 @@ async def test_acompletion_preserves_top_level_stream_flag_in_responses_request( assert result is stream assert transform_request.call_args.kwargs["optional_params"]["stream"] is True + + +def _completed_chat_response() -> ModelResponse: + return ModelResponse( + id="chatcmpl-completed", + model="gpt-5.4", + choices=[ + { + "index": 0, + "message": {"role": "assistant", "content": "pong"}, + "finish_reason": "stop", + } + ], + ) + + +@pytest.mark.asyncio +async def test_acompletion_streams_completed_model_response(): + """A streaming request whose bridge call comes back already completed must still be + handed back as an async-iterable stream. Returning the bare ModelResponse crashed the + proxy's SSE generator with "'async for' requires an object with __aiter__ method". + Regression for #33154.""" + completed = _completed_chat_response() + bridge = ResponsesToCompletionBridgeHandler() + + with ( + patch.object( + bridge.transformation_handler, + "transform_request", + return_value={"model": "gpt-5.4", "input": "hi"}, + ), + patch("litellm.aresponses", new=AsyncMock(return_value=completed)), + ): + result = await bridge.acompletion(**_bridge_kwargs(stream=True)) + + assert isinstance(result, CustomStreamWrapper), f"streaming request got {type(result)}" + chunks = [chunk async for chunk in result] + assert "".join( + chunk.choices[0].delta.content or "" for chunk in chunks + ) == "pong", f"completed response did not stream its content: {chunks}" + assert [c for c in chunks if c.choices[0].finish_reason], "stream never emitted a finish_reason" + + +def test_completion_streams_completed_model_response(): + completed = _completed_chat_response() + bridge = ResponsesToCompletionBridgeHandler() + + with ( + patch.object( + bridge.transformation_handler, + "transform_request", + return_value={"model": "gpt-5.4", "input": "hi"}, + ), + patch("litellm.responses", return_value=completed), + ): + result = bridge.completion(**_bridge_kwargs(stream=True)) + + assert isinstance(result, CustomStreamWrapper), f"streaming request got {type(result)}" + chunks = list(result) + assert "".join(chunk.choices[0].delta.content or "" for chunk in chunks) == "pong", ( + f"completed response did not stream its content: {chunks}" + ) diff --git a/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py b/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py index 6a1de0586dd..2789b4e61d5 100644 --- a/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py +++ b/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py @@ -2853,3 +2853,39 @@ def test_streaming_function_call_tool_id_for_degenerate_call_id(): assert stream_tool_id("fc_unique_abc123", "call_0") == "fc_unique_abc123" assert stream_tool_id("fc_2", "call_tokyo") == "call_tokyo" + + +def test_streaming_chunks_share_one_chat_completion_id(): + """Every chunk of one streamed chat completion must carry the same ``id``, per the + OpenAI spec. The bridge builds a fresh ``ModelResponseStream`` per Responses event, + so without a stream-scoped id each chunk got a new ``chatcmpl-`` and clients + that validate id consistency (openai-go's ChatCompletionAccumulator) silently + dropped every chunk after the first. Regression for #32854.""" + from litellm.completion_extras.litellm_responses_transformation.transformation import ( + OpenAiResponsesToChatCompletionStreamIterator, + ) + + iterator = OpenAiResponsesToChatCompletionStreamIterator( + streaming_response=None, sync_stream=True + ) + events = [ + {"type": "response.created", "response": {"id": "resp_abc", "output": []}}, + {"type": "response.output_text.delta", "delta": "Hel"}, + {"type": "response.output_text.delta", "delta": "lo"}, + { + "type": "response.completed", + "response": {"id": "resp_abc", "output": [{"type": "message"}]}, + }, + ] + + ids = [iterator.chunk_parser(event).id for event in events] + + assert len(set(ids)) == 1, f"streamed chunks carried different ids: {ids}" + assert ids[0], "streamed chunks carried an empty id" + + other_stream = OpenAiResponsesToChatCompletionStreamIterator( + streaming_response=None, sync_stream=True + ) + assert ( + other_stream.chunk_parser(events[1]).id != ids[0] + ), "a separate stream must get its own id, not a process-wide one" From b3e27a0bc30985300b8986ca1a3fcd7638cc0ab9 Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Fri, 24 Jul 2026 18:07:00 -0700 Subject: [PATCH 10/75] fix(anthropic-adapter): translate stop_sequences and disabled thinking for non-Claude targets Claude Code's auto-mode classifier sends stop_sequences and thinking: {type: disabled} on /v1/messages. The Anthropic adapter passed stop_sequences through unchanged instead of mapping it to OpenAI's stop, which Fireworks' OpenAI-compatible endpoint rejects with HTTP 400. It also dropped disabled thinking instead of mapping it to reasoning_effort: none, so the model spent its output budget on reasoning it was told to skip. Resolves LIT-4798 --- .../adapters/transformation.py | 20 ++++++++++++- ...al_pass_through_adapters_transformation.py | 28 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py b/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py index 4b6617fbeac..edcb474b01f 100644 --- a/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py +++ b/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py @@ -331,6 +331,7 @@ class LiteLLMAnthropicMessagesAdapter: "thinking", "output_format", "output_config", + "stop_sequences", ] def _is_web_search_tool(self, tool: Dict[str, Any]) -> bool: @@ -615,7 +616,7 @@ class LiteLLMAnthropicMessagesAdapter: thinking_type = thinking.get("type", "disabled") if thinking_type == "disabled": - return None + return "none" elif thinking_type == "enabled": return reasoning_effort_from_thinking_budget(thinking.get("budget_tokens", 0)) elif thinking_type == "adaptive": @@ -919,6 +920,18 @@ class LiteLLMAnthropicMessagesAdapter: tool_choice=cast(AnthropicMessagesToolChoice, tool_choice) ) + def _translate_stop_sequences_to_openai( + self, + anthropic_message_request: AnthropicMessagesRequest, + new_kwargs: ChatCompletionRequest, + ) -> None: + if "stop_sequences" not in anthropic_message_request: + return + stop_sequences = anthropic_message_request["stop_sequences"] + if not stop_sequences: + return + new_kwargs["stop"] = stop_sequences + def _translate_tools_to_openai( self, anthropic_message_request: AnthropicMessagesRequest, @@ -1098,6 +1111,11 @@ class LiteLLMAnthropicMessagesAdapter: anthropic_message_request=anthropic_message_request, new_kwargs=new_kwargs, ) + ## CONVERT STOP_SEQUENCES + self._translate_stop_sequences_to_openai( + anthropic_message_request=anthropic_message_request, + new_kwargs=new_kwargs, + ) ## CONVERT OUTPUT_FORMAT to RESPONSE_FORMAT self._translate_output_format_to_openai( anthropic_message_request=anthropic_message_request, diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py index dfe7e0c3a51..f8984e6f4e6 100644 --- a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py +++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py @@ -1582,6 +1582,34 @@ def test_thinking_still_translated_to_reasoning_effort_for_non_claude_model(): assert new_kwargs["reasoning_effort"] == "low" +def test_thinking_disabled_translated_to_reasoning_effort_none_for_non_claude_model(): + adapter = LiteLLMAnthropicMessagesAdapter() + thinking = {"type": "disabled"} + + new_kwargs = {"model": CACHE_CONTROL_NON_ANTHROPIC_MODEL} + adapter._translate_thinking_to_openai(cast(Any, {"thinking": thinking}), cast(Any, new_kwargs)) + + assert "thinking" not in new_kwargs + assert new_kwargs["reasoning_effort"] == "none" + + +def test_stop_sequences_translated_to_stop_for_non_claude_model(): + from litellm.types.llms.anthropic import AnthropicMessagesRequest + + anthropic_request = AnthropicMessagesRequest( + model=CACHE_CONTROL_NON_ANTHROPIC_MODEL, + max_tokens=1024, + messages=[{"role": "user", "content": "hi"}], + stop_sequences=[""], + ) + + adapter = LiteLLMAnthropicMessagesAdapter() + openai_request, _ = adapter.translate_anthropic_to_openai(anthropic_message_request=anthropic_request) + + assert openai_request["stop"] == [""] + assert "stop_sequences" not in openai_request + + def test_cache_control_preserved_in_image_content_for_claude(): """Cache control should be preserved in image content for Claude models.""" anthropic_messages = [ From 9da21f38a92d2d3165482f5ec07049f8bef4f93b Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Fri, 24 Jul 2026 18:29:58 -0700 Subject: [PATCH 11/75] fix(anthropic-adapter): keep disabled-thinking reasoning_effort a plain string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Guard against reasoning_auto_summary wrapping "none" into a dict when thinking is disabled — there's no reasoning trace to summarize, and non-Claude providers (e.g. Fireworks) expect reasoning_effort as a plain string. --- .../adapters/transformation.py | 8 +++++++- ...ntal_pass_through_adapters_transformation.py | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py b/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py index edcb474b01f..3d4233e719a 100644 --- a/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py +++ b/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py @@ -989,12 +989,18 @@ class LiteLLMAnthropicMessagesAdapter: if not reasoning_effort: return + thinking_type = thinking.get("type") if isinstance(thinking, dict) else None + # For adaptive thinking, override with output_config.effort if available - if isinstance(thinking, dict) and thinking.get("type") == "adaptive": + if thinking_type == "adaptive": output_config = anthropic_message_request.get("output_config") if isinstance(output_config, dict) and output_config.get("effort"): reasoning_effort = output_config["effort"] + if thinking_type == "disabled": + new_kwargs["reasoning_effort"] = reasoning_effort + return + summary = thinking.get("summary") if isinstance(thinking, dict) else None auto_summary = is_reasoning_auto_summary_enabled() if summary: diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py index f8984e6f4e6..326c8858551 100644 --- a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py +++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py @@ -1593,6 +1593,23 @@ def test_thinking_disabled_translated_to_reasoning_effort_none_for_non_claude_mo assert new_kwargs["reasoning_effort"] == "none" +def test_thinking_disabled_stays_plain_string_when_auto_summary_enabled(): + import litellm + + adapter = LiteLLMAnthropicMessagesAdapter() + thinking = {"type": "disabled"} + + original = litellm.reasoning_auto_summary + try: + litellm.reasoning_auto_summary = True + new_kwargs = {"model": CACHE_CONTROL_NON_ANTHROPIC_MODEL} + adapter._translate_thinking_to_openai(cast(Any, {"thinking": thinking}), cast(Any, new_kwargs)) + finally: + litellm.reasoning_auto_summary = original + + assert new_kwargs["reasoning_effort"] == "none" + + def test_stop_sequences_translated_to_stop_for_non_claude_model(): from litellm.types.llms.anthropic import AnthropicMessagesRequest From fed03a41d1e47be0c41e87ee3b24d688ba83f0a8 Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Fri, 24 Jul 2026 19:00:20 -0700 Subject: [PATCH 12/75] test(anthropic-adapter): cover empty stop_sequences edge case MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codecov flagged the empty-list early-return in _translate_stop_sequences_to_openai as an uncovered line in the diff — add a regression test asserting stop_sequences=[] does not set new_kwargs["stop"]. --- ...ental_pass_through_adapters_transformation.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py index 326c8858551..c0c6e315b5b 100644 --- a/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py +++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/adapters/test_anthropic_experimental_pass_through_adapters_transformation.py @@ -1627,6 +1627,22 @@ def test_stop_sequences_translated_to_stop_for_non_claude_model(): assert "stop_sequences" not in openai_request +def test_empty_stop_sequences_does_not_set_stop(): + from litellm.types.llms.anthropic import AnthropicMessagesRequest + + anthropic_request = AnthropicMessagesRequest( + model=CACHE_CONTROL_NON_ANTHROPIC_MODEL, + max_tokens=1024, + messages=[{"role": "user", "content": "hi"}], + stop_sequences=[], + ) + + adapter = LiteLLMAnthropicMessagesAdapter() + openai_request, _ = adapter.translate_anthropic_to_openai(anthropic_message_request=anthropic_request) + + assert "stop" not in openai_request + + def test_cache_control_preserved_in_image_content_for_claude(): """Cache control should be preserved in image content for Claude models.""" anthropic_messages = [ From 5072590c27aba27212be4d86c2daba03c05ea0cf Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Fri, 24 Jul 2026 19:31:29 -0700 Subject: [PATCH 13/75] fix(anthropic-adapter): dedupe reasoning_effort wrapping to close sibling gap translate_thinking_for_model duplicated the same summary/auto_summary wrapping logic as _translate_thinking_to_openai without the disabled-thinking guard, so it could still wrap "none" into an {effort, summary} dict when reasoning_auto_summary is enabled (caught by Cursor Bugbot). Extract the wrapping rule into one shared _apply_reasoning_summary_wrapping helper used by both call sites so this invariant can't drift apart again. --- .../adapters/transformation.py | 73 ++++++++----------- ...erimental_pass_through_messages_handler.py | 20 +++++ 2 files changed, 52 insertions(+), 41 deletions(-) diff --git a/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py b/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py index 3d4233e719a..d046ff1eaeb 100644 --- a/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py +++ b/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py @@ -684,25 +684,37 @@ class LiteLLMAnthropicMessagesAdapter: thinking ) if reasoning_effort: - summary = thinking.get("summary") if isinstance(thinking, dict) else None - auto_summary = is_reasoning_auto_summary_enabled() - if summary: - return { - "reasoning_effort": { - "effort": reasoning_effort, - "summary": summary, - } - } - elif auto_summary: - return { - "reasoning_effort": { - "effort": reasoning_effort, - "summary": "detailed", - } - } - return {"reasoning_effort": reasoning_effort} + return { + "reasoning_effort": LiteLLMAnthropicMessagesAdapter._apply_reasoning_summary_wrapping( + reasoning_effort, thinking + ) + } return {} + @staticmethod + def _apply_reasoning_summary_wrapping( + reasoning_effort: str, + thinking: Dict[str, Any], + ) -> Any: + """ + Apply the reasoning_effort/summary wrapping rules shared by every + thinking->reasoning_effort translation path. + + Disabled thinking always stays a plain string - there's no reasoning + trace to summarize, and non-Claude providers (e.g. Fireworks) expect + reasoning_effort as a plain string, not a summary dict. + """ + thinking_type = thinking.get("type") if isinstance(thinking, dict) else None + if thinking_type == "disabled": + return reasoning_effort + + summary = thinking.get("summary") if isinstance(thinking, dict) else None + if summary: + return cast(Any, {"effort": reasoning_effort, "summary": summary}) + if is_reasoning_auto_summary_enabled(): + return cast(Any, {"effort": reasoning_effort, "summary": "detailed"}) + return reasoning_effort + def translate_anthropic_tool_choice_to_openai( self, tool_choice: AnthropicMessagesToolChoice ) -> ChatCompletionToolChoiceValues: @@ -997,30 +1009,9 @@ class LiteLLMAnthropicMessagesAdapter: if isinstance(output_config, dict) and output_config.get("effort"): reasoning_effort = output_config["effort"] - if thinking_type == "disabled": - new_kwargs["reasoning_effort"] = reasoning_effort - return - - summary = thinking.get("summary") if isinstance(thinking, dict) else None - auto_summary = is_reasoning_auto_summary_enabled() - if summary: - new_kwargs["reasoning_effort"] = cast( - Any, - { - "effort": reasoning_effort, - "summary": summary, - }, - ) - elif auto_summary: - new_kwargs["reasoning_effort"] = cast( - Any, - { - "effort": reasoning_effort, - "summary": "detailed", - }, - ) - else: - new_kwargs["reasoning_effort"] = reasoning_effort + new_kwargs["reasoning_effort"] = self._apply_reasoning_summary_wrapping( + reasoning_effort, cast(Dict[str, Any], thinking) + ) def _translate_output_format_to_openai( self, diff --git a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_experimental_pass_through_messages_handler.py b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_experimental_pass_through_messages_handler.py index 8875a75e86f..df3db3d2c57 100644 --- a/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_experimental_pass_through_messages_handler.py +++ b/tests/test_litellm/llms/anthropic/experimental_pass_through/messages/test_anthropic_experimental_pass_through_messages_handler.py @@ -651,6 +651,26 @@ class TestThinkingSummaryPreservation: "reasoning_effort": {"effort": "high", "summary": "concise"} } + def test_translate_thinking_for_model_disabled_stays_plain_string_when_auto_summary_enabled(self): + """Disabled thinking must stay a plain string even when reasoning_auto_summary is on.""" + import litellm + from litellm.llms.anthropic.experimental_pass_through.adapters.transformation import ( + LiteLLMAnthropicMessagesAdapter, + ) + + original = litellm.reasoning_auto_summary + try: + litellm.reasoning_auto_summary = True + thinking = {"type": "disabled"} + result = LiteLLMAnthropicMessagesAdapter.translate_thinking_for_model( + thinking=thinking, + model="openai/gpt-5.2", + ) + finally: + litellm.reasoning_auto_summary = original + + assert result == {"reasoning_effort": "none"} + # --------------------------------------------------------------------------- # Parity tests: redundant empty-text-block sanitization scan removal. From d478b9955e2fcd0baaf7e40eb6f5b35e805a918c Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Fri, 24 Jul 2026 20:01:43 -0700 Subject: [PATCH 14/75] fix(anthropic-adapter): drop redundant casts to satisfy type-discipline budget _apply_reasoning_summary_wrapping already returns Any, so wrapping its dict-literal returns in cast(Any, ...) was a no-op that only inflated the LIT006 cast-count budget the lint gate enforces. --- .../experimental_pass_through/adapters/transformation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py b/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py index d046ff1eaeb..86c9c1db481 100644 --- a/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py +++ b/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py @@ -710,9 +710,9 @@ class LiteLLMAnthropicMessagesAdapter: summary = thinking.get("summary") if isinstance(thinking, dict) else None if summary: - return cast(Any, {"effort": reasoning_effort, "summary": summary}) + return {"effort": reasoning_effort, "summary": summary} if is_reasoning_auto_summary_enabled(): - return cast(Any, {"effort": reasoning_effort, "summary": "detailed"}) + return {"effort": reasoning_effort, "summary": "detailed"} return reasoning_effort def translate_anthropic_tool_choice_to_openai( From 5e34e0460bb8375b6911a036e699e3690be24689 Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Fri, 24 Jul 2026 22:16:28 -0700 Subject: [PATCH 15/75] fix(proxy): sanitize per-key callback config out of logged metadata get_sanitized_user_information_from_key copied UserAPIKeyAuth.metadata verbatim into user_api_key_auth_metadata, so the key's callback configuration - including the integration credentials inside callback_vars - reached the StandardLoggingPayload every integration receives. The two other sites that stamp key/team metadata into request metadata did the same. Sanitize at those sources with strip_callback_config, which drops the `logging` and `callback_settings` slots and leaves everything else (notably `priority`, read back by the dynamic rate limiter) untouched. Those slots are resolved from UserAPIKeyAuth during pre-call setup and never read off the logged copies, so nothing downstream loses input. This makes the scrub in scrub_sensitive_keys_in_metadata dead - it only matched the string "logging" under one of the two field names and never covered callback_settings - so it is removed. Separately, LangSmith set the run's `inputs` to the raw StandardLoggingPayload while redacting only `extra`, so redact_user_api_key_info left every user_api_key_* field in inputs.metadata. Both now go through one _redact_metadata helper, which also covers the nested requester_metadata copy. --- litellm/integrations/langsmith.py | 20 +++-- litellm/litellm_core_utils/litellm_logging.py | 12 --- litellm/proxy/common_utils/callback_utils.py | 11 +++ litellm/proxy/litellm_pre_call_utils.py | 7 +- litellm/proxy/proxy_server.py | 3 +- .../integrations/test_langsmith_init.py | 87 +++++++++++++++++++ .../proxy/common_utils/test_callback_utils.py | 39 +++++++++ .../proxy/test_litellm_pre_call_utils.py | 36 ++++++++ 8 files changed, 191 insertions(+), 24 deletions(-) diff --git a/litellm/integrations/langsmith.py b/litellm/integrations/langsmith.py index 18c4baccd51..565ea833768 100644 --- a/litellm/integrations/langsmith.py +++ b/litellm/integrations/langsmith.py @@ -133,6 +133,15 @@ class LangsmithLogger(CustomBatchLogger): "dotted_order": metadata.get("dotted_order", None), } + def _redact_metadata(self, metadata: dict) -> dict: + # helper is shallow; also scrub nested requester_metadata since + # LangSmith forwards the whole dict into the run + redacted = redact_user_api_key_info(metadata=dict(metadata)) + nested = redacted.get("requester_metadata") + if isinstance(nested, dict): + redacted["requester_metadata"] = redact_user_api_key_info(metadata=nested) + return redacted + def _build_extra_metadata(self, metadata: Dict): extra_metadata = dict(metadata) requester_metadata = extra_metadata.get("requester_metadata") @@ -141,13 +150,7 @@ class LangsmithLogger(CustomBatchLogger): if key in requester_metadata and key not in extra_metadata: extra_metadata[key] = requester_metadata[key] - # helper is shallow; also scrub nested requester_metadata since - # LangSmith forwards the whole dict into `extra` - extra_metadata = redact_user_api_key_info(metadata=extra_metadata) - nested = extra_metadata.get("requester_metadata") - if isinstance(nested, dict): - extra_metadata["requester_metadata"] = redact_user_api_key_info(metadata=nested) - return extra_metadata + return self._redact_metadata(extra_metadata) def _build_outputs_with_usage(self, payload: StandardLoggingPayload) -> Dict[str, Any]: response = payload["response"] @@ -200,12 +203,13 @@ class LangsmithLogger(CustomBatchLogger): metadata = payload["metadata"] extra_metadata = self._build_extra_metadata(dict(metadata)) + inputs = {**payload, "metadata": self._redact_metadata(dict(metadata))} outputs = self._build_outputs_with_usage(payload) data = { "name": fields["run_name"], "run_type": "llm", - "inputs": payload, + "inputs": inputs, "outputs": outputs, "session_name": fields["project_name"], "start_time": payload["startTime"], diff --git a/litellm/litellm_core_utils/litellm_logging.py b/litellm/litellm_core_utils/litellm_logging.py index c9e70b7db73..3a787acbf6e 100644 --- a/litellm/litellm_core_utils/litellm_logging.py +++ b/litellm/litellm_core_utils/litellm_logging.py @@ -5547,18 +5547,6 @@ def scrub_sensitive_keys_in_metadata(litellm_params: Optional[dict]): litellm_params["_langfuse_masking_function"] = masking_fn litellm_params["metadata"] = metadata - ## check user_api_key_metadata for sensitive logging keys - cleaned_user_api_key_metadata = {} - if "user_api_key_metadata" in metadata and isinstance(metadata["user_api_key_metadata"], dict): - for k, v in metadata["user_api_key_metadata"].items(): - if k == "logging": # prevent logging user logging keys - cleaned_user_api_key_metadata[k] = "scrubbed_by_litellm_for_sensitive_keys" - else: - cleaned_user_api_key_metadata[k] = v - - metadata["user_api_key_metadata"] = cleaned_user_api_key_metadata - litellm_params["metadata"] = metadata - return litellm_params diff --git a/litellm/proxy/common_utils/callback_utils.py b/litellm/proxy/common_utils/callback_utils.py index 33bca782e0b..8eb122f23af 100644 --- a/litellm/proxy/common_utils/callback_utils.py +++ b/litellm/proxy/common_utils/callback_utils.py @@ -31,6 +31,10 @@ _EXTRA_SENSITIVE_CALLBACK_KEYS = {"gcs_path_service_account"} # already-encrypted input cheaply (no decrypt-attempt round trip) and # avoid double-encrypting if `LITELLM_SALT_KEY` is rotated between writes. _CALLBACK_VAR_ENCRYPTED_PREFIX = "litellm_enc::" +# Metadata slots that hold operator-configured callback setup (and therefore +# integration credentials). Resolved from UserAPIKeyAuth during pre-call setup, +# never read back off the copies stamped into request metadata. +_CALLBACK_CONFIG_SLOTS = frozenset({"logging", "callback_settings"}) blue_color_code = "\033[94m" reset_color_code = "\033[0m" @@ -547,6 +551,13 @@ def normalize_callback_names(callbacks: Iterable[Any]) -> List[Any]: return [c.lower() if isinstance(c, str) else c for c in callbacks] +def strip_callback_config(metadata: dict[str, Any] | None) -> dict[str, Any] | None: + """Return key/team metadata without the slots that carry callback credentials.""" + if not isinstance(metadata, dict): + return metadata + return {k: v for k, v in metadata.items() if k not in _CALLBACK_CONFIG_SLOTS} + + def encrypt_callback_vars(metadata: Any) -> Any: """Return a deep copy of metadata with callback_vars values encrypted at rest. diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index a4cc4a62009..d94fed0ee5b 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -35,6 +35,7 @@ from litellm.proxy._types import ( from litellm.proxy.common_utils.callback_utils import ( decrypt_callback_vars, get_metadata_variable_name_from_kwargs, + strip_callback_config, ) from litellm.proxy.common_utils.http_parsing_utils import _safe_get_request_headers @@ -1032,7 +1033,7 @@ class LiteLLMProxyRequestSetup: user_api_key_budget_reset_at=( user_api_key_dict.budget_reset_at.isoformat() if user_api_key_dict.budget_reset_at else None ), - user_api_key_auth_metadata=user_api_key_dict.metadata, + user_api_key_auth_metadata=strip_callback_config(user_api_key_dict.metadata), ) return user_api_key_logged_metadata @@ -1670,8 +1671,8 @@ async def add_litellm_data_to_request( data[_metadata_variable_name]["user_api_key_user_spend"] = user_api_key_dict.user_spend data[_metadata_variable_name]["user_api_key_user_max_budget"] = user_api_key_dict.user_max_budget - data[_metadata_variable_name]["user_api_key_metadata"] = user_api_key_dict.metadata - data[_metadata_variable_name]["user_api_key_team_metadata"] = user_api_key_dict.team_metadata + data[_metadata_variable_name]["user_api_key_metadata"] = strip_callback_config(user_api_key_dict.metadata) + data[_metadata_variable_name]["user_api_key_team_metadata"] = strip_callback_config(user_api_key_dict.team_metadata) data[_metadata_variable_name]["user_api_key_object_permission_id"] = getattr( user_api_key_dict, "object_permission_id", None ) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index a20b557e38b..13635cb4f09 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -109,6 +109,7 @@ from litellm.proxy.common_utils.callback_utils import ( is_sensitive_callback_key, normalize_callback_names, process_callback, + strip_callback_config, ) from litellm.proxy.common_utils.realtime_utils import _realtime_request_body from litellm.router_utils.add_retry_fallback_headers import ( @@ -13375,7 +13376,7 @@ async def async_queue_request( # extra_body); see above for the same guard upstream. data["metadata"] = {} data["metadata"]["user_api_key"] = user_api_key_dict.api_key - data["metadata"]["user_api_key_metadata"] = user_api_key_dict.metadata + data["metadata"]["user_api_key_metadata"] = strip_callback_config(user_api_key_dict.metadata) _headers = _safe_get_request_headers(request).copy() _headers.pop("authorization", None) # do not store the original `sk-..` api key in the db data["metadata"]["headers"] = _headers diff --git a/tests/test_litellm/integrations/test_langsmith_init.py b/tests/test_litellm/integrations/test_langsmith_init.py index 5d6b7c74690..129dda4abde 100644 --- a/tests/test_litellm/integrations/test_langsmith_init.py +++ b/tests/test_litellm/integrations/test_langsmith_init.py @@ -347,3 +347,90 @@ class TestLangsmithRedactUserApiKeyInfo: assert "user_api_key_user_id" not in nested assert nested["session_id"] == "sess-1" assert extra["session_id"] == "sess-1" + + def test_redact_enabled_strips_user_api_key_info_from_inputs(self, reset_redact_flag): + """ + Regression (LIT-4306): `inputs` is the whole StandardLoggingPayload, so + `redact_user_api_key_info` has to cover `inputs.metadata` the same way it + covers `extra` - including the nested `requester_metadata` copy. Before + the fix `extra` was redacted and `inputs` shipped every user_api_key_* + field verbatim. + """ + litellm.redact_user_api_key_info = True + logger = self._logger() + metadata = self._metadata_with_user_api_key_fields() + metadata["user_api_key_auth_metadata"] = {"priority": "high"} + payload = { + "id": "run-1", + "response": {"choices": []}, + "metadata": metadata, + "startTime": 1.0, + "endTime": 2.0, + "request_tags": [], + "error_str": None, + "status": "success", + "response_cost": 0.0, + "prompt_tokens": 1, + "completion_tokens": 1, + "total_tokens": 2, + } + credentials = { + "LANGSMITH_API_KEY": "test-key", + "LANGSMITH_PROJECT": "test-project", + "LANGSMITH_BASE_URL": "https://api.smith.langchain.com", + } + + data = logger._prepare_log_data( + kwargs={"litellm_params": {"metadata": metadata}, "standard_logging_object": payload}, + response_obj=None, + start_time=1.0, + end_time=2.0, + credentials=credentials, + ) + + inputs_metadata = data["inputs"]["metadata"] + assert [k for k in inputs_metadata if k.startswith("user_api_key")] == [] + assert [k for k in inputs_metadata["requester_metadata"] if k.startswith("user_api_key")] == [] + # inputs and extra must agree - they go through the same redaction now + assert [k for k in data["extra"] if k.startswith("user_api_key")] == [] + # non-identity payload is untouched + assert inputs_metadata["model"] == "gpt-4" + assert inputs_metadata["requester_metadata"]["session_id"] == "sess-1" + assert data["inputs"]["total_tokens"] == 2 + # the shared standard_logging_object other loggers read is not mutated + assert "user_api_key_hash" in payload["metadata"] + assert "user_api_key_user_id" in payload["metadata"]["requester_metadata"] + + def test_redact_disabled_keeps_user_api_key_info_in_inputs(self, reset_redact_flag): + """Flag off: the identity fields stay. The flag governs them, not this fix.""" + litellm.redact_user_api_key_info = False + logger = self._logger() + metadata = self._metadata_with_user_api_key_fields() + payload = { + "id": "run-1", + "response": {"choices": []}, + "metadata": metadata, + "startTime": 1.0, + "endTime": 2.0, + "request_tags": [], + "error_str": None, + "status": "success", + "response_cost": 0.0, + "prompt_tokens": 1, + "completion_tokens": 1, + "total_tokens": 2, + } + + data = logger._prepare_log_data( + kwargs={"litellm_params": {"metadata": metadata}, "standard_logging_object": payload}, + response_obj=None, + start_time=1.0, + end_time=2.0, + credentials={ + "LANGSMITH_API_KEY": "test-key", + "LANGSMITH_PROJECT": "test-project", + "LANGSMITH_BASE_URL": "https://api.smith.langchain.com", + }, + ) + + assert data["inputs"]["metadata"]["user_api_key_hash"] == "abc123" diff --git a/tests/test_litellm/proxy/common_utils/test_callback_utils.py b/tests/test_litellm/proxy/common_utils/test_callback_utils.py index 8f390c096d7..bfd4ffe1593 100644 --- a/tests/test_litellm/proxy/common_utils/test_callback_utils.py +++ b/tests/test_litellm/proxy/common_utils/test_callback_utils.py @@ -18,6 +18,7 @@ from litellm.proxy.common_utils.callback_utils import ( get_remaining_tokens_and_requests_from_request_data, normalize_callback_names, sanitize_openai_provider_metadata, + strip_callback_config, ) import litellm @@ -452,3 +453,41 @@ def test_initialize_callbacks_on_proxy_non_dict_callback_specific_params_root( ) finally: litellm.callbacks = original_callbacks + + +def test_strip_callback_config_drops_credential_bearing_slots(): + """ + `logging` and `callback_settings` hold operator-configured integration + credentials. Both must be dropped from the key/team metadata the proxy + stamps into request metadata, while every other field survives untouched + (`priority` is read back by the dynamic rate limiter, `guardrails` by the + guardrail hooks). + """ + metadata = { + "logging": [ + { + "callback_name": "langsmith", + "callback_vars": {"langsmith_api_key": "litellm_enc::ciphertext"}, + } + ], + "callback_settings": {"callback_vars": {"langfuse_secret_key": "litellm_enc::other"}}, + "priority": "high", + "guardrails": ["presidio"], + "langsmith_provisioning": {"api_key_id": "prov-1"}, + } + + stripped = strip_callback_config(metadata) + + assert "logging" not in stripped + assert "callback_settings" not in stripped + assert stripped["priority"] == "high" + assert stripped["guardrails"] == ["presidio"] + assert stripped["langsmith_provisioning"] == {"api_key_id": "prov-1"} + # the caller's dict (UserAPIKeyAuth.metadata) is shared state - never mutate it + assert "logging" in metadata + assert "callback_settings" in metadata + + +@pytest.mark.parametrize("value", [None, "not-a-dict", 42]) +def test_strip_callback_config_passes_through_non_dicts(value): + assert strip_callback_config(value) is value diff --git a/tests/test_litellm/proxy/test_litellm_pre_call_utils.py b/tests/test_litellm/proxy/test_litellm_pre_call_utils.py index 1437899f561..143884c8a0d 100644 --- a/tests/test_litellm/proxy/test_litellm_pre_call_utils.py +++ b/tests/test_litellm/proxy/test_litellm_pre_call_utils.py @@ -5421,3 +5421,39 @@ async def test_overwrite_user_with_key_hash_rejects_alias_without_marker(monkeyp ) assert updated_data["user"] == "caller-chosen-id" + + +def test_get_sanitized_user_information_from_key_drops_callback_config(): + """ + Regression (LIT-4306): `user_api_key_auth_metadata` lands in the + StandardLoggingPayload every integration receives, so the per-key callback + config (and the integration credentials inside it) must not ride along. + Everything else - notably `priority`, which the dynamic rate limiter reads + back off this exact field - has to survive. + """ + user_api_key_dict = UserAPIKeyAuth( + api_key="test-key-hash", + metadata={ + "logging": [ + { + "callback_name": "langsmith", + "callback_vars": {"langsmith_api_key": "litellm_enc::ciphertext"}, + } + ], + "callback_settings": {"callback_vars": {"langfuse_secret_key": "litellm_enc::other"}}, + "priority": "high", + }, + ) + + result = LiteLLMProxyRequestSetup.get_sanitized_user_information_from_key( + user_api_key_dict=user_api_key_dict + ) + + auth_metadata = result["user_api_key_auth_metadata"] + assert "logging" not in auth_metadata + assert "callback_settings" not in auth_metadata + assert "litellm_enc::" not in json.dumps(auth_metadata) + assert auth_metadata["priority"] == "high" + # UserAPIKeyAuth is the live auth object; the per-key callbacks are resolved + # from it during pre-call, so it must not be mutated by building the log view + assert "logging" in (user_api_key_dict.metadata or {}) From 970ea2949eec6c40091b64098a186560747d301d Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Sat, 25 Jul 2026 17:13:23 -0700 Subject: [PATCH 16/75] fix(vertex): decide rawPredict passthrough streaming from the request body Vertex passthrough classified any target URL containing "stream" as a streaming request. `:streamRawPredict` carries that substring, so a unary Claude-on-Vertex call whose body omits `stream` was routed through the streaming logging path. That path never consults the response content-type, so a complete `"type": "message"` JSON body was handed to the Anthropic SSE chunk parser, which recognises none of it; the spend log recorded 0 prompt tokens, 0 completion tokens and zero cost Streaming for the rawPredict family now comes from the request body, which is what the Anthropic Messages contract uses for those endpoints. The generateContent family keeps its URL signal because the Gemini REST body has no `stream` field, and `?alt=sse` is still appended for every request that is classified as streaming, so Gemini framing and its usage parsing are unchanged Both passthrough streaming predicates read `.get("stream")` off a body that is only annotated as a dict; `_read_request_body` returns whatever the JSON parser produced, so an array body raised AttributeError. The two predicates are now one owner that answers False for any non-object body, which covers the vertex, mistral, anthropic, vllm and azure passthrough routes --- .../llm_passthrough_endpoints.py | 22 ++- .../test_llm_pass_through_endpoints.py | 169 ++++++++++++++++++ 2 files changed, 183 insertions(+), 8 deletions(-) diff --git a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py index 7e573de261b..28d2c62f1f1 100644 --- a/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py +++ b/litellm/proxy/pass_through_endpoints/llm_passthrough_endpoints.py @@ -86,11 +86,16 @@ def is_passthrough_request_using_router_model(request_body: dict, llm_router: Op return False -def is_passthrough_request_streaming(request_body: dict) -> bool: +def is_passthrough_request_streaming(request_body: object) -> bool: """ - Returns True if the request is streaming + Returns True if the request is streaming. + + A JSON body need not be an object, so a list or scalar can reach here; it + carries no streaming flag. """ - return request_body.get("stream", False) + if not isinstance(request_body, dict): + return False + return bool(request_body.get("stream", False)) async def llm_passthrough_factory_proxy_route( @@ -551,8 +556,7 @@ async def is_streaming_request_fn(request: Request) -> bool: _request_body = await get_form_data(request) else: _request_body = await _read_request_body(request) - if _request_body.get("stream"): - return True + return is_passthrough_request_streaming(_request_body) return False @@ -1755,9 +1759,11 @@ async def _base_vertex_proxy_route( ## check for streaming target = str(updated_url) - is_streaming_request = False - if "stream" in str(updated_url): - is_streaming_request = True + if ":rawPredict" in target or ":streamRawPredict" in target: + is_streaming_request = await is_streaming_request_fn(request) + else: + is_streaming_request = "stream" in target + if is_streaming_request: target += "?alt=sse" ## CREATE PASS-THROUGH diff --git a/tests/test_litellm/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py b/tests/test_litellm/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py index cf3351c4ff8..181846fe289 100644 --- a/tests/test_litellm/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py +++ b/tests/test_litellm/proxy/pass_through_endpoints/test_llm_pass_through_endpoints.py @@ -3022,3 +3022,172 @@ class TestCursorProxyRoute: assert call_args["target"] == "https://api.cursor.com/v0/agents" assert result["id"] == "bc_abc123" assert result["status"] == "CREATING" + + +class TestVertexRawPredictStreamingClassification: + """ + Regression coverage for LIT-4761. + + `_base_vertex_proxy_route` classified any target URL containing "stream" as a + streaming request. `:streamRawPredict` carries that substring, so a unary + Anthropic-on-Vertex call (no `stream` field in the body) was sent with + `?alt=sse` and logged through the streaming chunk collector, which parses + Anthropic SSE deltas and finds no usage in a complete `"type": "message"` + body; the spend log recorded 0 tokens and $0 cost. + + Streaming for the rawPredict family is decided by the request body, per the + Anthropic Messages contract. The Gemini generateContent family stays + URL-signalled because the Gemini REST body has no `stream` field. + """ + + RAW_PREDICT_ENDPOINT = ( + "v1/projects/test-project/locations/us-east5/publishers/anthropic/models/" + "claude-sonnet-4-6:streamRawPredict" + ) + GENERATE_CONTENT_ENDPOINT = ( + "v1/projects/test-project/locations/us-east5/publishers/google/models/" + "gemini-2.5-flash:streamGenerateContent" + ) + + async def _capture_passthrough_kwargs(self, endpoint: str, body: object) -> dict: + raw_body = json.dumps(body).encode("utf-8") + + async def receive(): + return {"type": "http.request", "body": raw_body, "more_body": False} + + request = Request( + { + "type": "http", + "method": "POST", + "path": f"/vertex_ai/{endpoint}", + "headers": [(b"content-type", b"application/json")], + "query_string": b"", + }, + receive=receive, + ) + + captured: dict = {} + + def fake_create_pass_through_route(**kwargs): + captured.update(kwargs) + return AsyncMock(return_value={"status": "success"}) + + mock_credentials = Mock() + mock_credentials.token = "test-token" + + base_url = "https://us-east5-aiplatform.googleapis.com/" + mock_handler = Mock() + mock_handler.get_default_base_target_url.return_value = base_url + mock_handler.update_base_target_url_with_credential_location = Mock(return_value=base_url) + + module = "litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints" + with ( + mock.patch( + "litellm.llms.vertex_ai.vertex_llm_base.VertexBase.load_auth", + return_value=(mock_credentials, "test-project"), + ), + mock.patch(f"{module}.create_pass_through_route", side_effect=fake_create_pass_through_route), + mock.patch(f"{module}.get_litellm_virtual_key", return_value="Bearer test-key"), + mock.patch(f"{module}.user_api_key_auth", new=AsyncMock(return_value={"api_key": "test-key"})), + mock.patch(f"{module}.get_vertex_pass_through_handler", return_value=mock_handler), + ): + await vertex_proxy_route( + endpoint=endpoint, + request=request, + fastapi_response=Response(), + user_api_key_dict=UserAPIKeyAuth(token="test-key"), + ) + + assert captured, "create_pass_through_route was never called" + return captured + + @pytest.mark.asyncio + async def test_raw_predict_without_stream_field_is_not_streaming(self): + captured = await self._capture_passthrough_kwargs( + endpoint=self.RAW_PREDICT_ENDPOINT, + body={ + "anthropic_version": "vertex-2023-10-16", + "messages": [{"role": "user", "content": "Explain MLOps"}], + "max_tokens": 5000, + }, + ) + + assert captured["is_streaming_request"] is False + assert "alt=sse" not in captured["target"] + + @pytest.mark.asyncio + async def test_raw_predict_with_stream_false_is_not_streaming(self): + captured = await self._capture_passthrough_kwargs( + endpoint=self.RAW_PREDICT_ENDPOINT, + body={ + "anthropic_version": "vertex-2023-10-16", + "stream": False, + "messages": [{"role": "user", "content": "Explain MLOps"}], + "max_tokens": 5000, + }, + ) + + assert captured["is_streaming_request"] is False + assert "alt=sse" not in captured["target"] + + @pytest.mark.asyncio + async def test_raw_predict_with_stream_true_still_streams(self): + captured = await self._capture_passthrough_kwargs( + endpoint=self.RAW_PREDICT_ENDPOINT, + body={ + "anthropic_version": "vertex-2023-10-16", + "stream": True, + "messages": [{"role": "user", "content": "Explain MLOps"}], + "max_tokens": 5000, + }, + ) + + assert captured["is_streaming_request"] is True + assert captured["target"].endswith("?alt=sse") + + @pytest.mark.asyncio + async def test_gemini_stream_generate_content_stays_url_signalled(self): + captured = await self._capture_passthrough_kwargs( + endpoint=self.GENERATE_CONTENT_ENDPOINT, + body={"contents": [{"role": "user", "parts": [{"text": "Explain MLOps"}]}]}, + ) + + assert captured["is_streaming_request"] is True + assert captured["target"].endswith("?alt=sse") + + @pytest.mark.asyncio + async def test_raw_predict_with_non_object_body_is_not_streaming(self): + captured = await self._capture_passthrough_kwargs( + endpoint=self.RAW_PREDICT_ENDPOINT, + body=[{"role": "user", "content": "Explain MLOps"}], + ) + + assert captured["is_streaming_request"] is False + assert "alt=sse" not in captured["target"] + + +@pytest.mark.parametrize( + "request_body, expected", + [ + ({"stream": True}, True), + ({"stream": "true"}, True), + ({"stream": False}, False), + ({}, False), + ([{"role": "user"}], False), + ([], False), + ("stream", False), + (7, False), + (None, False), + ], +) +def test_is_passthrough_request_streaming_tolerates_non_object_bodies(request_body, expected): + """ + A JSON request body is not required to be an object. Every passthrough + streaming decision funnels through this predicate, so a list or scalar body + must answer False instead of raising AttributeError. + """ + from litellm.proxy.pass_through_endpoints.llm_passthrough_endpoints import ( + is_passthrough_request_streaming, + ) + + assert is_passthrough_request_streaming(request_body) is expected From c8b0530c30c678f27f0b70359566925d303aca99 Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Sat, 25 Jul 2026 17:38:58 -0700 Subject: [PATCH 17/75] fix(proxy): roll up tool spend daily instead of scanning SpendLogs GET /v1/tool/spend served the Cost Optimization card with two raw queries over LiteLLM_SpendLogToolIndex x LiteLLM_SpendLogs on every dashboard load; the totals query's driving scan was all of SpendLogs in the window. Both per-request tables reach 1M+ rows at customer scale, so the card cost O(traffic) per view and had to be capped at 30 days. The index writer also mined proxy_server_request.tools, i.e. tools DECLARED in the request body, attributing each request's full spend to tools that never ran; and all non-MCP mining ran against payload fields that are '{}' unless store_prompts_in_spend_logs is enabled, so non-MCP coverage silently depended on a privacy setting. Now the spend writer builds a ToolUsageTransaction at request time from invoked tools only, resolved by the shared get_tool_calls_from_response normalizer so every response surface (chat completions, Responses API, Anthropic Messages) is covered; the tool registry's response arm delegates to the same owner. Transactions queue beside the spend-log queue and the flush job writes index rows plus a new LiteLLM_DailyToolSpend rollup (date, tool_name PK) in one transaction, retrying connection errors with backoff (a failed batch commits nothing, so the retry cannot double-count) and dropping the batch with an error log on anything else. The endpoint aggregates in SQL: by_tool is the top TOOL_SPEND_TOP_TOOLS tools by spend via group_by and daily covers only those tools, so the response is bounded by days x TOOL_SPEND_TOP_TOOLS regardless of range or tool-name cardinality; the 30-day clamp is gone. total_spend is dropped from the response; it was never rendered and its deduplicated semantics are not computable from a rollup. Spend-log retention deliberately does not touch the rollup, so tool spend history outlives per-request rows. --- db_scripts/backfill_daily_tool_spend.sql | 44 +++ .../migration.sql | 12 + .../litellm_proxy_extras/schema.prisma | 13 + litellm/constants.py | 2 +- .../prompt_templates/factory.py | 5 +- litellm/proxy/_lazy_openapi_snapshot.json | 10 +- litellm/proxy/db/db_spend_update_writer.py | 55 +++- litellm/proxy/db/spend_log_tool_index.py | 261 ++++++++------- .../tool_management_endpoints.py | 170 ++++------ litellm/proxy/schema.prisma | 13 + litellm/proxy/utils.py | 42 ++- litellm/repositories/__init__.py | 2 + litellm/repositories/table_repositories.py | 4 + litellm/types/tool_management.py | 7 - schema.prisma | 13 + tests/proxy_unit_tests/test_update_spend.py | 4 +- .../proxy/db/test_db_spend_update_writer.py | 138 ++++++++ .../proxy/db/test_spend_log_tool_index.py | 309 ++++++++++++++++++ .../test_tool_management_endpoints.py | 220 ++++++------- .../proxy/test_spend_log_cleanup.py | 6 + .../proxy/utils/prisma_and_spend/conftest.py | 2 + .../prisma_and_spend/test_spend_functions.py | 31 +- ui/litellm-dashboard/eslint-suppressions.json | 7 +- .../CostOptimizationView.activity.test.tsx | 2 +- .../_components/UsageTab.test.tsx | 30 +- .../_components/UsageTab.tsx | 13 +- .../src/components/ToolDetail.tsx | 2 +- .../src/components/networking.tsx | 1 - ui/litellm-dashboard/src/lib/http/schema.d.ts | 27 +- 29 files changed, 981 insertions(+), 464 deletions(-) create mode 100644 db_scripts/backfill_daily_tool_spend.sql create mode 100644 litellm-proxy-extras/litellm_proxy_extras/migrations/20260725000000_add_daily_tool_spend/migration.sql create mode 100644 tests/test_litellm/proxy/db/test_spend_log_tool_index.py diff --git a/db_scripts/backfill_daily_tool_spend.sql b/db_scripts/backfill_daily_tool_spend.sql new file mode 100644 index 00000000000..358ebf1f23f --- /dev/null +++ b/db_scripts/backfill_daily_tool_spend.sql @@ -0,0 +1,44 @@ +-- One-shot backfill of the LiteLLM_DailyToolSpend rollup from the per-request +-- LiteLLM_SpendLogToolIndex x LiteLLM_SpendLogs tables. +-- +-- This is an opt-in, manual operation. New deployments do not need it: the +-- rollup is written at request time from the moment the release is deployed. +-- Run it only if you want the Cost Optimization "Spend by tool" card to show +-- history from before the deploy, and only once. +-- +-- IMPORTANT caveats before running: +-- +-- 1. Pre-deploy index rows may include tools that were merely DECLARED in a +-- request body but never invoked (the release this ships with stops +-- recording those). For agentic clients that declare many tools per +-- request, backfilled history attributes each request's full spend to +-- every declared tool, overstating per-tool spend. Post-deploy rows do not +-- have this problem. If your traffic is mostly such clients, consider not +-- backfilling. +-- +-- 2. Coverage is bounded by spend-log retention: rows older than +-- maximum_spend_logs_retention_period are already gone. +-- +-- 3. Replace the cutover timestamp below with the time you deployed the +-- release, so backfilled per-request rows cannot double-count on top of +-- rollup rows the new writer already created. ON CONFLICT DO NOTHING is a +-- second guard for (date, tool_name) buckets the writer already touched: +-- such buckets keep the writer's numbers and skip the backfill's. +-- +-- Usage: +-- psql "$DATABASE_URL" -v cutover="'2026-07-25T00:00:00Z'" -f db_scripts/backfill_daily_tool_spend.sql + +INSERT INTO "LiteLLM_DailyToolSpend" (date, tool_name, spend, total_tokens, request_count, created_at, updated_at) +SELECT + to_char(ti.start_time, 'YYYY-MM-DD') AS date, + ti.tool_name, + COALESCE(SUM(sl.spend), 0) AS spend, + COALESCE(SUM(sl.total_tokens), 0) AS total_tokens, + COUNT(*) AS request_count, + now() AS created_at, + now() AS updated_at +FROM "LiteLLM_SpendLogToolIndex" ti +JOIN "LiteLLM_SpendLogs" sl ON sl.request_id = ti.request_id +WHERE ti.start_time < :cutover::timestamptz +GROUP BY 1, 2 +ON CONFLICT (date, tool_name) DO NOTHING; diff --git a/litellm-proxy-extras/litellm_proxy_extras/migrations/20260725000000_add_daily_tool_spend/migration.sql b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260725000000_add_daily_tool_spend/migration.sql new file mode 100644 index 00000000000..e02ed01a554 --- /dev/null +++ b/litellm-proxy-extras/litellm_proxy_extras/migrations/20260725000000_add_daily_tool_spend/migration.sql @@ -0,0 +1,12 @@ +-- CreateTable +CREATE TABLE IF NOT EXISTS "LiteLLM_DailyToolSpend" ( + "date" TEXT NOT NULL, + "tool_name" TEXT NOT NULL, + "spend" DOUBLE PRECISION NOT NULL DEFAULT 0.0, + "total_tokens" BIGINT NOT NULL DEFAULT 0, + "request_count" BIGINT NOT NULL DEFAULT 0, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "LiteLLM_DailyToolSpend_pkey" PRIMARY KEY ("date","tool_name") +); diff --git a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma index 6713b212314..37ea55f8c13 100644 --- a/litellm-proxy-extras/litellm_proxy_extras/schema.prisma +++ b/litellm-proxy-extras/litellm_proxy_extras/schema.prisma @@ -1097,6 +1097,19 @@ model LiteLLM_SpendLogToolIndex { @@index([start_time]) } +// Daily tool spend rollup (one row per tool per day) – the Cost Optimization card reads this, never SpendLogs +model LiteLLM_DailyToolSpend { + date String + tool_name String + spend Float @default(0.0) + total_tokens BigInt @default(0) + request_count BigInt @default(0) + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + @@id([date, tool_name]) +} + // Prompt table for storing prompt configurations model LiteLLM_PromptTable { id String @id @default(uuid()) diff --git a/litellm/constants.py b/litellm/constants.py index a9edf135731..1014b472c61 100644 --- a/litellm/constants.py +++ b/litellm/constants.py @@ -1457,7 +1457,7 @@ SPEND_LOG_CLEANUP_MAX_CONSECUTIVE_BATCH_FAILURES = int(os.getenv("SPEND_LOG_CLEA SPEND_LOG_CLEANUP_BATCH_FAILURE_BACKOFF_SECONDS = float( os.getenv("SPEND_LOG_CLEANUP_BATCH_FAILURE_BACKOFF_SECONDS", 0.5) ) -TOOL_SPEND_MAX_WINDOW_DAYS = 30 +TOOL_SPEND_TOP_TOOLS = 100 SPEND_LOG_PARTITION_INTERVAL = os.getenv("SPEND_LOG_PARTITION_INTERVAL", "day") SPEND_LOG_PARTITION_PRECREATE_AHEAD = int(os.getenv("SPEND_LOG_PARTITION_PRECREATE_AHEAD", 7)) SPEND_LOG_QUEUE_SIZE_THRESHOLD = int(os.getenv("SPEND_LOG_QUEUE_SIZE_THRESHOLD", 100)) diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index f7ff4d6b16f..c13cf0817b5 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -6,6 +6,7 @@ import mimetypes import re import xml.etree.ElementTree as ET from enum import Enum +from collections.abc import Mapping from typing import Any, Dict, List, Optional, Set, Tuple, TypedDict, Union, cast, overload from jinja2.sandbox import ImmutableSandboxedEnvironment @@ -5350,7 +5351,9 @@ def prompt_factory( def get_attribute_or_key(tool_or_function, attribute, default=None): if hasattr(tool_or_function, attribute): return getattr(tool_or_function, attribute) - return tool_or_function.get(attribute, default) + if isinstance(tool_or_function, Mapping): + return tool_or_function.get(attribute, default) + return default class NormalizedToolCall(TypedDict): diff --git a/litellm/proxy/_lazy_openapi_snapshot.json b/litellm/proxy/_lazy_openapi_snapshot.json index 972c831073f..96f6ee89d56 100644 --- a/litellm/proxy/_lazy_openapi_snapshot.json +++ b/litellm/proxy/_lazy_openapi_snapshot.json @@ -26858,12 +26858,6 @@ } ], "title": "Start Date" - }, - "total_spend": { - "default": 0.0, - "description": "Deduplicated spend of every request that called at least one tool in the window; less than the sum of per-tool attributed spend whenever multi-tool requests exist", - "title": "Total Spend", - "type": "number" } }, "title": "ToolSpendResponse", @@ -27417,7 +27411,7 @@ }, "/v1/tool/spend": { "get": { - "description": "Spend attributed to each tool over a date range, for the Cost Optimization dashboard.\n\nJoins ``LiteLLM_SpendLogToolIndex`` (which tool names ran on which request) to\n``LiteLLM_SpendLogs`` (what the request cost). A request that used multiple tools\ncounts its full spend toward each of those tools, so per-tool numbers are\nattributions. ``total_spend`` is the deduplicated spend of every request that\ncalled at least one tool in the window, so it never double counts.\n\n``start_date`` is clamped to at most 30 days before ``end_date`` (serving up to\n31 calendar dates inclusive, the same width as the endpoint's default window):\na wider requested range is clamped, and the response's ``start_date`` reflects\nthe effective window actually served.", + "description": "Spend attributed to each tool over a date range, for the Cost Optimization dashboard.\n\nReads the ``LiteLLM_DailyToolSpend`` rollup, written at request time from invoked\ntools only (MCP tool calls and response tool_calls; declaring a tool without\ninvoking it does not count). A request that invoked multiple tools counts its\nfull spend toward each of them, so per-tool numbers are attributions and do not\nsum to a deduplicated total.\n\n``by_tool`` is the top ``TOOL_SPEND_TOP_TOOLS`` tools by spend, aggregated in\nSQL, and ``daily`` covers only those tools, so the response is bounded by\ndays x TOOL_SPEND_TOP_TOOLS regardless of the requested range or how many\ndistinct tool names exist.", "operationId": "get_tool_spend_v1_tool_spend_get", "parameters": [ { @@ -27588,7 +27582,7 @@ }, "/v1/tool/{tool_name}/logs": { "get": { - "description": "Return paginated spend logs for requests that used this tool (from SpendLogToolIndex).", + "description": "Return paginated spend logs for requests that invoked this tool (from SpendLogToolIndex).\nDeclaring a tool in a request body without the model invoking it does not create an entry.", "operationId": "get_tool_usage_logs_v1_tool__tool_name__logs_get", "parameters": [ { diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index 2262141f426..ebdb08a681a 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -182,6 +182,12 @@ class DBSpendUpdateWriter: payload=payload, prisma_client=prisma_client, ) + await self._enqueue_tool_usage_transaction( + payload=payload, + completion_response=completion_response, + prisma_client=prisma_client, + kwargs=kwargs, + ) else: verbose_proxy_logger.debug( "disable_spend_logs=True. Skipping writing spend logs to db. Other spend updates - Key/User/Team table will still occur." @@ -223,6 +229,36 @@ class DBSpendUpdateWriter: end_user_id, ) + async def _enqueue_tool_usage_transaction( + self, + payload: SpendLogsPayload, + completion_response: "litellm.ModelResponse | Any | Exception | None", + prisma_client: "PrismaClient | None", + kwargs: "dict | None" = None, + ) -> None: + try: + if prisma_client is None: + return + from litellm.proxy.db.spend_log_tool_index import ( + build_tool_usage_transaction, + ) + + transaction = build_tool_usage_transaction( + request_id=payload["request_id"], + start_time_iso=str(payload["startTime"]), + mcp_namespaced_tool_name=payload.get("mcp_namespaced_tool_name"), + spend=payload["spend"], + total_tokens=payload["total_tokens"], + completion_response=completion_response, + realtime_tool_calls=(kwargs or {}).get("realtime_tool_calls"), + ) + if transaction is None: + return + async with prisma_client._tool_usage_transactions_lock: + prisma_client.tool_usage_transactions.append(transaction) + except Exception as e: + verbose_proxy_logger.debug("_enqueue_tool_usage_transaction error (non-blocking): %s", e) + def _enqueue_tool_registry_upsert( self, kwargs: Optional[dict], @@ -299,21 +335,10 @@ class DBSpendUpdateWriter: _enqueue(name) # --- Response tool_calls (OpenAI format; Anthropic pass-through converts tool_use here) --- - if completion_response is not None and hasattr(completion_response, "choices"): - for choice in completion_response.choices or []: - message = getattr(choice, "message", None) - if message is None: - continue - tool_calls = getattr(message, "tool_calls", None) - if not tool_calls: - continue - for tc in tool_calls: - fn = getattr(tc, "function", None) - if fn is None: - continue - tool_name = getattr(fn, "name", None) - if tool_name: - _enqueue(tool_name) + from litellm.proxy.db.spend_log_tool_index import response_tool_call_names + + for tool_name in response_tool_call_names(completion_response): + _enqueue(tool_name) except Exception as e: verbose_proxy_logger.debug("_enqueue_tool_registry_upsert error (non-blocking): %s", e) diff --git a/litellm/proxy/db/spend_log_tool_index.py b/litellm/proxy/db/spend_log_tool_index.py index 80036e235f7..064d08acb59 100644 --- a/litellm/proxy/db/spend_log_tool_index.py +++ b/litellm/proxy/db/spend_log_tool_index.py @@ -1,140 +1,147 @@ """ -Track tool usage for the dashboard: insert into SpendLogToolIndex when spend logs -are written, so "last N requests for tool X" and "how is this tool called in production" -queries are fast. +Tool usage tracking for the dashboard. + +At request time the spend writer builds one ToolUsageTransaction per request that +invoked tools (MCP namespaced tool name plus response tool_calls; declared-but-not- +invoked tools are excluded) and queues it on the prisma client. The spend-log flush +job drains the queue into LiteLLM_SpendLogToolIndex (per-request drill-down) and +LiteLLM_DailyToolSpend (the per-day rollup the Cost Optimization card reads) in a +single transaction, so a failed flush never leaves a partial rollup increment. """ +from __future__ import annotations + +import asyncio +import random +from dataclasses import dataclass from datetime import datetime, timezone -from typing import Any, Dict, List, Set +from itertools import groupby +from typing import TYPE_CHECKING, Any, Sequence -from litellm._logging import verbose_proxy_logger -from litellm.litellm_core_utils.safe_json_loads import safe_json_loads -from litellm.proxy.utils import PrismaClient -from litellm.repositories.table_repositories import SpendLogToolIndexRepository +from litellm.proxy._types import DB_CONNECTION_ERROR_TYPES + +if TYPE_CHECKING: + from litellm.proxy.utils import PrismaClient -def _add_tool_calls_to_set(tool_calls: Any, out: Set[str]) -> None: - """Extract tool names from OpenAI-style tool_calls list into out.""" - if not isinstance(tool_calls, list): - return - for tc in tool_calls: - if not isinstance(tc, dict): - continue - fn = tc.get("function") - if isinstance(fn, dict): - name = fn.get("name") - if name and isinstance(name, str) and name.strip(): - out.add(name.strip()) +@dataclass(frozen=True, slots=True) +class ToolUsageTransaction: + request_id: str + date: str + start_time: datetime + tool_names: tuple[str, ...] + spend: float + total_tokens: int -def _parse_tool_names_from_payload(payload: Dict[str, Any]) -> Set[str]: - """ - Extract deduplicated tool names from a spend log payload. - Sources: mcp_namespaced_tool_name, response (tool_calls), proxy_server_request (tools). - """ - tool_names: Set[str] = set() +def response_tool_call_names(completion_response: Any) -> tuple[str, ...]: + """Tool names invoked in a completion response, in call order, for any response + surface get_tool_calls_from_response understands (chat completions, Responses + API output items, Anthropic Messages tool_use blocks).""" + if completion_response is None or isinstance(completion_response, Exception): + return () + from litellm.litellm_core_utils.prompt_templates.factory import ( + get_tool_calls_from_response, + ) - # Top-level MCP tool name (single tool per request for that flow) - mcp_name = payload.get("mcp_namespaced_tool_name") - if mcp_name and isinstance(mcp_name, str) and mcp_name.strip(): - tool_names.add(mcp_name.strip()) - - # Response: OpenAI-style tool_calls[].function.name or choices[0].message.tool_calls - response_raw = payload.get("response") - if response_raw: - response_obj = safe_json_loads(response_raw, default=None) if isinstance(response_raw, str) else response_raw - if isinstance(response_obj, dict): - _add_tool_calls_to_set(response_obj.get("tool_calls"), tool_names) - choices = response_obj.get("choices") - if isinstance(choices, list) and choices: - msg = choices[0].get("message") if isinstance(choices[0], dict) else None - if isinstance(msg, dict): - _add_tool_calls_to_set(msg.get("tool_calls"), tool_names) - - # Request body: tools[].function.name - request_raw = payload.get("proxy_server_request") - if request_raw: - request_obj = safe_json_loads(request_raw, default=None) if isinstance(request_raw, str) else request_raw - if isinstance(request_obj, dict): - body = request_obj.get("body", request_obj) - if isinstance(body, dict): - request_obj = body - if isinstance(request_obj, dict): - tools = request_obj.get("tools") - if isinstance(tools, list): - for t in tools: - if isinstance(t, dict): - fn = t.get("function") - if isinstance(fn, dict): - name = fn.get("name") - if name and isinstance(name, str) and name.strip(): - tool_names.add(name.strip()) - - return tool_names + return tuple( + stripped + for tool_call in get_tool_calls_from_response(completion_response) + if isinstance(name := tool_call.get("name"), str) and (stripped := name.strip()) + ) -async def process_spend_logs_tool_usage( - prisma_client: PrismaClient, - logs_to_process: List[Dict[str, Any]], -) -> None: - """ - After spend logs are written: insert SpendLogToolIndex rows from each payload. - Extracts tool names from mcp_namespaced_tool_name, response tool_calls, and - proxy_server_request tools. - """ - if not logs_to_process: - return - - index_rows: List[Dict[str, Any]] = [] - - for payload in logs_to_process: - request_id = payload.get("request_id") - start_time = payload.get("startTime") - if not request_id or not start_time: - continue - if isinstance(start_time, str): - try: - start_time = datetime.fromisoformat(start_time.replace("Z", "+00:00")) - except (ValueError, TypeError): - continue - if start_time.tzinfo is None: - start_time = start_time.replace(tzinfo=timezone.utc) - - tool_names = _parse_tool_names_from_payload(payload) - for tool_name in tool_names: - index_rows.append( - { - "request_id": request_id, - "tool_name": tool_name, - "start_time": start_time, - } - ) - - if not index_rows: - return - +def build_tool_usage_transaction( + request_id: str, + start_time_iso: str, + mcp_namespaced_tool_name: str | None, + spend: float, + total_tokens: int, + completion_response: Any, + realtime_tool_calls: Any = None, +) -> ToolUsageTransaction | None: + """None when the request invoked no tools. Realtime sessions carry invoked + tools in kwargs["realtime_tool_calls"] (OpenAI tool_calls shape) rather than + on a response object, so they are normalized through the same owner by + wrapping them in the chat-completion shape. Date derivation must match the + daily spend writer's ``startTime.split("T")[0]`` so rollup rows land in the + same UTC day bucket as LiteLLM_DailyUserSpend.""" + mcp_names = ( + (mcp_namespaced_tool_name.strip(),) if mcp_namespaced_tool_name and mcp_namespaced_tool_name.strip() else () + ) + realtime_names = ( + response_tool_call_names({"choices": [{"message": {"tool_calls": realtime_tool_calls}}]}) + if realtime_tool_calls + else () + ) + tool_names = tuple(dict.fromkeys(mcp_names + response_tool_call_names(completion_response) + realtime_names)) + if not tool_names: + return None try: - index_data = [] - for r in index_rows: - st = r["start_time"] - if isinstance(st, str): - try: - st = datetime.fromisoformat(st.replace("Z", "+00:00")) - except (ValueError, TypeError): - continue - if st.tzinfo is None: - st = st.replace(tzinfo=timezone.utc) - index_data.append( - { - "request_id": r["request_id"], - "tool_name": r["tool_name"], - "start_time": st, - } - ) - if index_data: - await SpendLogToolIndexRepository(prisma_client).table.create_many( - data=index_data, - skip_duplicates=True, - ) - except Exception as e: - verbose_proxy_logger.warning("Tool usage tracking (SpendLogToolIndex) failed (non-fatal): %s", e) + start_time = datetime.fromisoformat(start_time_iso.replace("Z", "+00:00")) + except ValueError: + return None + return ToolUsageTransaction( + request_id=request_id, + date=start_time_iso.split("T")[0], + start_time=start_time if start_time.tzinfo else start_time.replace(tzinfo=timezone.utc), + tool_names=tool_names, + spend=spend, + total_tokens=total_tokens, + ) + + +async def flush_tool_usage_transactions( + prisma_client: PrismaClient, + transactions: Sequence[ToolUsageTransaction], + n_retry_times: int = 3, +) -> None: + """Write index rows and rollup upserts for a drained queue batch in one + transaction. Connection errors are retried with backoff, which cannot + double-count because a failed batch commits nothing; every other error + propagates so the caller drops the batch. Callers must not add their own + retry around this function: a batch that DID commit must never run again, + since the rollup update increments counters.""" + if not transactions: + return + + index_rows = [ + {"request_id": txn.request_id, "tool_name": tool_name, "start_time": txn.start_time} + for txn in transactions + for tool_name in txn.tool_names + ] + per_tool_day = sorted( + ((txn.date, tool_name, txn.spend, txn.total_tokens) for txn in transactions for tool_name in txn.tool_names), + key=lambda entry: (entry[0], entry[1]), + ) + + for attempt in range(n_retry_times + 1): + try: + async with prisma_client.db.batch_() as batcher: + batcher.litellm_spendlogtoolindex.create_many(data=index_rows, skip_duplicates=True) + for (date_key, tool_name), grouped in groupby(per_tool_day, key=lambda entry: (entry[0], entry[1])): + entries = tuple(grouped) + spend = sum(entry[2] for entry in entries) + total_tokens = sum(entry[3] for entry in entries) + batcher.litellm_dailytoolspend.upsert( + where={"date_tool_name": {"date": date_key, "tool_name": tool_name}}, + data={ + "create": { + "date": date_key, + "tool_name": tool_name, + "spend": spend, + "total_tokens": total_tokens, + "request_count": len(entries), + }, + "update": { + "spend": {"increment": spend}, + "total_tokens": {"increment": total_tokens}, + "request_count": {"increment": len(entries)}, + }, + }, + ) + return + except DB_CONNECTION_ERROR_TYPES: + if attempt >= n_retry_times: + raise + await asyncio.sleep(2**attempt + random.uniform(0, 1)) diff --git a/litellm/proxy/management_endpoints/tool_management_endpoints.py b/litellm/proxy/management_endpoints/tool_management_endpoints.py index b6a445ef327..ad0b4f7444c 100644 --- a/litellm/proxy/management_endpoints/tool_management_endpoints.py +++ b/litellm/proxy/management_endpoints/tool_management_endpoints.py @@ -11,21 +11,21 @@ POST /v1/tool/policy - Update the input_policy / output_policy for a import uuid from datetime import datetime, timedelta, timezone -from itertools import groupby from typing import TYPE_CHECKING, Annotated, Any, List, Optional from fastapi import APIRouter, Depends, HTTPException, Query -from pydantic import BaseModel, TypeAdapter +from pydantic import BaseModel, Field, TypeAdapter if TYPE_CHECKING: from litellm.proxy.utils import PrismaClient from litellm._logging import verbose_proxy_logger -from litellm.constants import TOOL_SPEND_MAX_WINDOW_DAYS +from litellm.constants import TOOL_SPEND_TOP_TOOLS from litellm.proxy._types import CommonProxyErrors, LitellmUserRoles, UserAPIKeyAuth from litellm.proxy.auth.user_api_key_auth import user_api_key_auth from litellm.repositories.object_permission_repository import ObjectPermissionRepository from litellm.repositories.table_repositories import ( + DailyToolSpendRepository, SpendLogsRepository, SpendLogToolIndexRepository, ) @@ -142,53 +142,18 @@ def _parse_day_start(value: str | None) -> datetime | None: ) -class _ToolSpendRow(BaseModel): - date: str +class _ToolSpendSums(BaseModel): + spend: float = 0.0 + total_tokens: int = 0 + request_count: int = 0 + + +class _TopToolRow(BaseModel): tool_name: str - call_count: int - spend: float - total_tokens: int + sums: _ToolSpendSums = Field(alias="_sum") -class _RequestTotalRow(BaseModel): - total_spend: float - - -_TOOL_SPEND_ROWS = TypeAdapter(list[_ToolSpendRow]) -_REQUEST_TOTAL_ROWS = TypeAdapter(list[_RequestTotalRow]) - - -def _summarize_tool(name: str, grp: tuple[_ToolSpendRow, ...]) -> ToolSpendEntry: - return ToolSpendEntry( - tool_name=name, - spend=sum(r.spend for r in grp), - call_count=sum(r.call_count for r in grp), - total_tokens=sum(r.total_tokens for r in grp), - ) - - -def _build_tool_spend_response( - rows: list[_ToolSpendRow], - total_spend: float, - start_date: str, - end_date: str, -) -> ToolSpendResponse: - daily = [ - ToolSpendDailyEntry(date=r.date, tool_name=r.tool_name, spend=r.spend, call_count=r.call_count) for r in rows - ] - grouped = groupby(sorted(rows, key=lambda r: r.tool_name), key=lambda r: r.tool_name) - by_tool = sorted( - (_summarize_tool(name, tuple(grp)) for name, grp in grouped), - key=lambda e: e.spend, - reverse=True, - ) - return ToolSpendResponse( - by_tool=by_tool, - daily=daily, - total_spend=total_spend, - start_date=start_date, - end_date=end_date, - ) +_TOP_TOOL_ROWS = TypeAdapter(list[_TopToolRow]) @router.get( @@ -205,16 +170,16 @@ async def get_tool_spend( """ Spend attributed to each tool over a date range, for the Cost Optimization dashboard. - Joins ``LiteLLM_SpendLogToolIndex`` (which tool names ran on which request) to - ``LiteLLM_SpendLogs`` (what the request cost). A request that used multiple tools - counts its full spend toward each of those tools, so per-tool numbers are - attributions. ``total_spend`` is the deduplicated spend of every request that - called at least one tool in the window, so it never double counts. + Reads the ``LiteLLM_DailyToolSpend`` rollup, written at request time from invoked + tools only (MCP tool calls and response tool_calls; declaring a tool without + invoking it does not count). A request that invoked multiple tools counts its + full spend toward each of them, so per-tool numbers are attributions and do not + sum to a deduplicated total. - ``start_date`` is clamped to at most 30 days before ``end_date`` (serving up to - 31 calendar dates inclusive, the same width as the endpoint's default window): - a wider requested range is clamped, and the response's ``start_date`` reflects - the effective window actually served. + ``by_tool`` is the top ``TOOL_SPEND_TOP_TOOLS`` tools by spend, aggregated in + SQL, and ``daily`` covers only those tools, so the response is bounded by + days x TOOL_SPEND_TOP_TOOLS regardless of the requested range or how many + distinct tool names exist. """ from litellm.proxy.proxy_server import prisma_client @@ -230,64 +195,46 @@ async def get_tool_spend( if prisma_client is None: raise HTTPException(status_code=500, detail=CommonProxyErrors.db_not_connected_error.value) - now = datetime.now(timezone.utc) - end_day = _parse_day_start(end_date) - # Anchor the floor to a midnight so the clamp compares dates with dates: - # parsed start_dates are midnight-aligned, and a floor carrying now's - # time-of-day would invisibly truncate an explicit start_date to mid-day. - today = now.replace(hour=0, minute=0, second=0, microsecond=0) - window_floor = (end_day or today) - timedelta(days=TOOL_SPEND_MAX_WINDOW_DAYS) - start_dt = _parse_day_start(start_date) or window_floor - if start_dt < window_floor: - start_dt = window_floor - end_exclusive = (end_day + timedelta(days=1)) if end_day else now + end_day = _parse_day_start(end_date) or datetime.now(timezone.utc) + start_day = _parse_day_start(start_date) or end_day - timedelta(days=30) + start_str = start_day.strftime("%Y-%m-%d") + end_str = end_day.strftime("%Y-%m-%d") + date_window = {"date": {"gte": start_str, "lte": end_str}} - # ti.start_time defines the window in both queries; the sl."startTime" bounds - # exist only so the planner can use the SpendLogs startTime index, and carry a - # 1s margin because the two writers can disagree by ~1ms on the same request. - rows = await prisma_client.db.query_raw( - """ - SELECT to_char(ti.start_time, 'YYYY-MM-DD') AS date, - ti.tool_name AS tool_name, - COUNT(*)::int AS call_count, - COALESCE(SUM(sl.spend), 0)::double precision AS spend, - COALESCE(SUM(sl.total_tokens), 0)::bigint AS total_tokens - FROM "LiteLLM_SpendLogToolIndex" ti - JOIN "LiteLLM_SpendLogs" sl ON sl.request_id = ti.request_id - WHERE ti.start_time >= ($1::timestamptz AT TIME ZONE 'UTC') - AND ti.start_time < ($2::timestamptz AT TIME ZONE 'UTC') - AND sl."startTime" >= ($1::timestamptz AT TIME ZONE 'UTC') - interval '1 second' - AND sl."startTime" < ($2::timestamptz AT TIME ZONE 'UTC') + interval '1 second' - GROUP BY date, ti.tool_name - ORDER BY date ASC, spend DESC - """, - start_dt.isoformat(), - end_exclusive.isoformat(), - ) - totals = await prisma_client.db.query_raw( - """ - SELECT COALESCE(SUM(sl.spend), 0)::double precision AS total_spend - FROM "LiteLLM_SpendLogs" sl - WHERE sl."startTime" >= ($1::timestamptz AT TIME ZONE 'UTC') - interval '1 second' - AND sl."startTime" < ($2::timestamptz AT TIME ZONE 'UTC') + interval '1 second' - AND EXISTS ( - SELECT 1 - FROM "LiteLLM_SpendLogToolIndex" ti - WHERE ti.request_id = sl.request_id - AND ti.start_time >= ($1::timestamptz AT TIME ZONE 'UTC') - AND ti.start_time < ($2::timestamptz AT TIME ZONE 'UTC') + table = DailyToolSpendRepository(prisma_client).table + top_tools = _TOP_TOOL_ROWS.validate_python( + await table.group_by( + by=["tool_name"], + sum={"spend": True, "total_tokens": True, "request_count": True}, + where=date_window, + order={"_sum": {"spend": "desc"}}, + take=TOOL_SPEND_TOP_TOOLS, ) - """, - start_dt.isoformat(), - end_exclusive.isoformat(), + or [] ) - total_rows = _REQUEST_TOTAL_ROWS.validate_python(totals or []) - return _build_tool_spend_response( - rows=_TOOL_SPEND_ROWS.validate_python(rows or []), - total_spend=total_rows[0].total_spend if total_rows else 0.0, - start_date=start_dt.strftime("%Y-%m-%d"), - end_date=(end_day or now).strftime("%Y-%m-%d"), + by_tool = [ + ToolSpendEntry( + tool_name=row.tool_name, + spend=row.sums.spend, + call_count=row.sums.request_count, + total_tokens=row.sums.total_tokens, + ) + for row in top_tools + ] + + daily_rows = ( + await table.find_many( + where={**date_window, "tool_name": {"in": [row.tool_name for row in top_tools]}}, + order=[{"date": "asc"}, {"spend": "desc"}], + ) + if top_tools + else [] ) + daily = [ + ToolSpendDailyEntry(date=row.date, tool_name=row.tool_name, spend=row.spend, call_count=row.request_count) + for row in daily_rows + ] + return ToolSpendResponse(by_tool=by_tool, daily=daily, start_date=start_str, end_date=end_str) @router.get( @@ -388,7 +335,8 @@ async def get_tool_usage_logs( user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), ): """ - Return paginated spend logs for requests that used this tool (from SpendLogToolIndex). + Return paginated spend logs for requests that invoked this tool (from SpendLogToolIndex). + Declaring a tool in a request body without the model invoking it does not create an entry. """ from litellm.proxy.proxy_server import prisma_client diff --git a/litellm/proxy/schema.prisma b/litellm/proxy/schema.prisma index 6713b212314..37ea55f8c13 100644 --- a/litellm/proxy/schema.prisma +++ b/litellm/proxy/schema.prisma @@ -1097,6 +1097,19 @@ model LiteLLM_SpendLogToolIndex { @@index([start_time]) } +// Daily tool spend rollup (one row per tool per day) – the Cost Optimization card reads this, never SpendLogs +model LiteLLM_DailyToolSpend { + date String + tool_name String + spend Float @default(0.0) + total_tokens BigInt @default(0) + request_count BigInt @default(0) + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + @@id([date, tool_name]) +} + // Prompt table for storing prompt configurations model LiteLLM_PromptTable { id String @id @default(uuid()) diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index e85ccf150d2..d7a95284818 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -175,6 +175,7 @@ if TYPE_CHECKING: from prisma.client import TransactionManager from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj + from litellm.proxy.db.spend_log_tool_index import ToolUsageTransaction Span = Union[_Span, Any] else: @@ -2917,6 +2918,8 @@ async def prefetch_config_params(prisma_client: Any, param_names: List[str]) -> class PrismaClient: spend_log_transactions: List = [] _spend_log_transactions_lock = asyncio.Lock() + tool_usage_transactions: List["ToolUsageTransaction"] = [] + _tool_usage_transactions_lock = asyncio.Lock() def __init__( self, @@ -5473,12 +5476,15 @@ async def update_spend( queue_size = len(prisma_client.spend_log_transactions) verbose_proxy_logger.debug("Spend Logs transactions: {}".format(queue_size)) + async with prisma_client._tool_usage_transactions_lock: + tool_usage_queue_size = len(prisma_client.tool_usage_transactions) + # Process spend log transactions when called directly. # This keeps backwards compatibility with the old behavior. # See update_spend_logs_job and _monitor_spend_logs_queue for the new behavior. # Safe to keep: under high concurrency this can take up to ~30s to run, # so it's unlikely to overlap with monitor_spend_logs_queue. - if queue_size > 0: + if queue_size > 0 or tool_usage_queue_size > 0: await update_spend_logs_job( prisma_client=prisma_client, db_writer_client=db_writer_client, @@ -5545,10 +5551,14 @@ async def update_spend_logs_job( n_retry_times = 3 MAX_LOGS_PER_INTERVAL = 10000 - # Atomically pop batch from queue + # Atomically pop batch from queue. The tool usage queue counts toward the + # emptiness check: a spend-log write failure aborts a run before the tool + # drain below, and those entries must not strand once the spend queue drains. async with prisma_client._spend_log_transactions_lock: queue_size = len(prisma_client.spend_log_transactions) - if queue_size == 0: + async with prisma_client._tool_usage_transactions_lock: + tool_queue_size = len(prisma_client.tool_usage_transactions) + if queue_size == 0 and tool_queue_size == 0: return async with prisma_client._spend_log_transactions_lock: @@ -5579,17 +5589,23 @@ async def update_spend_logs_job( guardrail_tracking_err, ) - # Tool usage tracking (same batch): SpendLogToolIndex for "last N requests for tool X" + # Tool usage tracking: drain the request-time queue into the tool index and the + # LiteLLM_DailyToolSpend rollup. Never retried; a dropped batch is permanently + # absent from the rollup, so failures log at error. + async with prisma_client._tool_usage_transactions_lock: + tool_usage_to_process = prisma_client.tool_usage_transactions[:MAX_LOGS_PER_INTERVAL] + prisma_client.tool_usage_transactions = prisma_client.tool_usage_transactions[len(tool_usage_to_process) :] try: - from litellm.proxy.db.spend_log_tool_index import process_spend_logs_tool_usage + from litellm.proxy.db.spend_log_tool_index import flush_tool_usage_transactions - await process_spend_logs_tool_usage( + await flush_tool_usage_transactions( prisma_client=prisma_client, - logs_to_process=logs_to_process, + transactions=tool_usage_to_process, ) except Exception as tool_tracking_err: - verbose_proxy_logger.warning( - "Spend tracking - tool usage tracking failed (non-fatal): %s", + verbose_proxy_logger.error( + "Spend tracking - tool usage flush failed; %s tool usage transactions dropped: %s", + len(tool_usage_to_process), tool_tracking_err, ) @@ -5625,9 +5641,13 @@ async def _monitor_spend_logs_queue( while True: try: - # Check queue size with lock protection + # Check queue sizes with lock protection; the tool usage queue keeps + # the monitor firing when a prior failed run left it nonempty. async with prisma_client._spend_log_transactions_lock: - queue_size = len(prisma_client.spend_log_transactions) + spend_queue_size = len(prisma_client.spend_log_transactions) + async with prisma_client._tool_usage_transactions_lock: + tool_queue_size = len(prisma_client.tool_usage_transactions) + queue_size = spend_queue_size + tool_queue_size if queue_size > 0: if queue_size >= threshold: diff --git a/litellm/repositories/__init__.py b/litellm/repositories/__init__.py index 4451f0865da..29c953e06cf 100644 --- a/litellm/repositories/__init__.py +++ b/litellm/repositories/__init__.py @@ -23,6 +23,7 @@ from litellm.repositories.table_repositories import ( DailyGuardrailMetricsRepository, DailyPolicyMetricsRepository, DailyTagSpendRepository, + DailyToolSpendRepository, DeletedTeamRepository, DeletedVerificationTokenRepository, DeprecatedVerificationTokenRepository, @@ -104,6 +105,7 @@ __all__ = [ "ManagedVectorStoreIndexRepository", "WorkflowMessageRepository", "DailyTagSpendRepository", + "DailyToolSpendRepository", "SpendLogToolIndexRepository", "SpendLogGuardrailIndexRepository", "UserNotificationsRepository", diff --git a/litellm/repositories/table_repositories.py b/litellm/repositories/table_repositories.py index dc2a7d25259..54008c0950c 100644 --- a/litellm/repositories/table_repositories.py +++ b/litellm/repositories/table_repositories.py @@ -181,6 +181,10 @@ class SpendLogToolIndexRepository(PrismaTableRepository): table_name = "litellm_spendlogtoolindex" +class DailyToolSpendRepository(PrismaTableRepository): + table_name = "litellm_dailytoolspend" + + class SpendLogGuardrailIndexRepository(PrismaTableRepository): table_name = "litellm_spendlogguardrailindex" diff --git a/litellm/types/tool_management.py b/litellm/types/tool_management.py index 71ec412e8ef..ccf4b7dbc9f 100644 --- a/litellm/types/tool_management.py +++ b/litellm/types/tool_management.py @@ -124,12 +124,5 @@ class ToolSpendDailyEntry(BaseModel): class ToolSpendResponse(BaseModel): by_tool: List[ToolSpendEntry] = Field(default_factory=list) daily: List[ToolSpendDailyEntry] = Field(default_factory=list) - total_spend: float = Field( - 0.0, - description=( - "Deduplicated spend of every request that called at least one tool in the window; " - "less than the sum of per-tool attributed spend whenever multi-tool requests exist" - ), - ) start_date: str | None = None end_date: str | None = None diff --git a/schema.prisma b/schema.prisma index 6713b212314..37ea55f8c13 100644 --- a/schema.prisma +++ b/schema.prisma @@ -1097,6 +1097,19 @@ model LiteLLM_SpendLogToolIndex { @@index([start_time]) } +// Daily tool spend rollup (one row per tool per day) – the Cost Optimization card reads this, never SpendLogs +model LiteLLM_DailyToolSpend { + date String + tool_name String + spend Float @default(0.0) + total_tokens BigInt @default(0) + request_count BigInt @default(0) + created_at DateTime @default(now()) + updated_at DateTime @updatedAt + + @@id([date, tool_name]) +} + // Prompt table for storing prompt configurations model LiteLLM_PromptTable { id String @id @default(uuid()) diff --git a/tests/proxy_unit_tests/test_update_spend.py b/tests/proxy_unit_tests/test_update_spend.py index e2dca0a0f81..131f46a3e21 100644 --- a/tests/proxy_unit_tests/test_update_spend.py +++ b/tests/proxy_unit_tests/test_update_spend.py @@ -28,11 +28,13 @@ class MockPrismaClient: # Initialize transaction lists self.spend_log_transactions = [] self.daily_user_spend_transactions = {} + self.tool_usage_transactions = [] - # Add lock for spend_log_transactions (matches real PrismaClient) + # Add locks for the transaction queues (matches real PrismaClient) import asyncio self._spend_log_transactions_lock = asyncio.Lock() + self._tool_usage_transactions_lock = asyncio.Lock() def jsonify_object(self, obj): return obj diff --git a/tests/test_litellm/proxy/db/test_db_spend_update_writer.py b/tests/test_litellm/proxy/db/test_db_spend_update_writer.py index 8149cf90e70..8759b008549 100644 --- a/tests/test_litellm/proxy/db/test_db_spend_update_writer.py +++ b/tests/test_litellm/proxy/db/test_db_spend_update_writer.py @@ -76,6 +76,144 @@ async def test_daily_spend_tracking_with_disabled_spend_logs(): assert call_args["payload"]["custom_llm_provider"] == "openai" +def _tool_call_response(*names: str) -> object: + from types import SimpleNamespace + + tool_calls = [SimpleNamespace(function=SimpleNamespace(name=name)) for name in names] + return SimpleNamespace(choices=[SimpleNamespace(message=SimpleNamespace(tool_calls=tool_calls))]) + + +def _tool_usage_prisma() -> MagicMock: + prisma = MagicMock() + prisma.tool_usage_transactions = [] + prisma._tool_usage_transactions_lock = asyncio.Lock() + prisma.spend_log_transactions = [] + prisma._spend_log_transactions_lock = asyncio.Lock() + return prisma + + +def _minimal_spend_payload() -> dict: + return { + "request_id": "req-tool-1", + "startTime": datetime(2026, 7, 25, 10, 0, tzinfo=timezone.utc), + "endTime": datetime(2026, 7, 25, 10, 0, 1, tzinfo=timezone.utc), + "spend": 0.0, + "total_tokens": 42, + "mcp_namespaced_tool_name": None, + } + + +@pytest.mark.asyncio +async def test_update_database_enqueues_tool_usage_for_invoked_tools(): + db_writer = DBSpendUpdateWriter() + db_writer._insert_spend_log_to_db = AsyncMock() + db_writer._batch_database_updates = AsyncMock() + prisma = _tool_usage_prisma() + + with ( + patch("litellm.proxy.proxy_server.disable_spend_logs", False), + patch("litellm.proxy.proxy_server.prisma_client", prisma), + patch("litellm.proxy.proxy_server.litellm_proxy_budget_name", "test-budget"), + patch( + "litellm.proxy.spend_tracking.spend_tracking_utils.get_logging_payload", + return_value=_minimal_spend_payload(), + ), + ): + await db_writer.update_database( + token="test-token", + user_id="test-user", + end_user_id=None, + team_id=None, + org_id=None, + kwargs={"model": "gpt-4"}, + completion_response=_tool_call_response("get_weather"), + start_time=datetime.now(timezone.utc), + end_time=datetime.now(timezone.utc), + response_cost=0.1, + ) + await asyncio.sleep(0) + + assert len(prisma.tool_usage_transactions) == 1 + transaction = prisma.tool_usage_transactions[0] + assert transaction.request_id == "req-tool-1" + assert transaction.tool_names == ("get_weather",) + assert transaction.spend == 0.1 + assert transaction.total_tokens == 42 + assert transaction.date == "2026-07-25" + + +@pytest.mark.asyncio +async def test_update_database_enqueues_realtime_tool_usage(): + db_writer = DBSpendUpdateWriter() + db_writer._insert_spend_log_to_db = AsyncMock() + db_writer._batch_database_updates = AsyncMock() + prisma = _tool_usage_prisma() + + with ( + patch("litellm.proxy.proxy_server.disable_spend_logs", False), + patch("litellm.proxy.proxy_server.prisma_client", prisma), + patch("litellm.proxy.proxy_server.litellm_proxy_budget_name", "test-budget"), + patch( + "litellm.proxy.spend_tracking.spend_tracking_utils.get_logging_payload", + return_value=_minimal_spend_payload(), + ), + ): + await db_writer.update_database( + token="test-token", + user_id="test-user", + end_user_id=None, + team_id=None, + org_id=None, + kwargs={ + "model": "gpt-realtime", + "realtime_tool_calls": [ + {"id": "call_1", "type": "function", "function": {"name": "rt_tool", "arguments": "{}"}} + ], + }, + completion_response=None, + start_time=datetime.now(timezone.utc), + end_time=datetime.now(timezone.utc), + response_cost=0.2, + ) + await asyncio.sleep(0) + + assert len(prisma.tool_usage_transactions) == 1 + assert prisma.tool_usage_transactions[0].tool_names == ("rt_tool",) + + +@pytest.mark.asyncio +async def test_update_database_skips_tool_usage_when_spend_logs_disabled(): + db_writer = DBSpendUpdateWriter() + db_writer._insert_spend_log_to_db = AsyncMock() + db_writer._batch_database_updates = AsyncMock() + prisma = _tool_usage_prisma() + + with ( + patch("litellm.proxy.proxy_server.disable_spend_logs", True), + patch("litellm.proxy.proxy_server.prisma_client", prisma), + patch("litellm.proxy.proxy_server.litellm_proxy_budget_name", "test-budget"), + patch( + "litellm.proxy.spend_tracking.spend_tracking_utils.get_logging_payload", + return_value=_minimal_spend_payload(), + ), + ): + await db_writer.update_database( + token="test-token", + user_id="test-user", + end_user_id=None, + team_id=None, + org_id=None, + kwargs={"model": "gpt-4"}, + completion_response=_tool_call_response("get_weather"), + start_time=datetime.now(timezone.utc), + end_time=datetime.now(timezone.utc), + response_cost=0.1, + ) + await asyncio.sleep(0) + + assert prisma.tool_usage_transactions == [] + + @pytest.mark.asyncio async def test_update_daily_spend_with_null_entity_id(): """ diff --git a/tests/test_litellm/proxy/db/test_spend_log_tool_index.py b/tests/test_litellm/proxy/db/test_spend_log_tool_index.py new file mode 100644 index 00000000000..3b6acaa1eb3 --- /dev/null +++ b/tests/test_litellm/proxy/db/test_spend_log_tool_index.py @@ -0,0 +1,309 @@ +""" +Tests for the tool usage writer: ToolUsageTransaction construction (invoked tools +only) and the flush that writes LiteLLM_SpendLogToolIndex plus the +LiteLLM_DailyToolSpend rollup in one transaction. +""" + +from types import SimpleNamespace +from typing import Any +from unittest.mock import AsyncMock, MagicMock + +import pytest + +from litellm.proxy.db.spend_log_tool_index import ( + ToolUsageTransaction, + build_tool_usage_transaction, + flush_tool_usage_transactions, + response_tool_call_names, +) + + +def _response_with_tool_calls(*names: str) -> SimpleNamespace: + tool_calls = [SimpleNamespace(function=SimpleNamespace(name=name)) for name in names] + return SimpleNamespace(choices=[SimpleNamespace(message=SimpleNamespace(tool_calls=tool_calls))]) + + +class _FakeBatcher: + def __init__(self) -> None: + self.litellm_spendlogtoolindex = MagicMock() + self.litellm_dailytoolspend = MagicMock() + + async def __aenter__(self) -> "_FakeBatcher": + return self + + async def __aexit__(self, *args: Any) -> None: + return None + + +def _prisma_with_batcher() -> tuple[MagicMock, _FakeBatcher]: + batcher = _FakeBatcher() + prisma = MagicMock() + prisma.db.batch_ = MagicMock(return_value=batcher) + return prisma, batcher + + +class TestBuildToolUsageTransaction: + def test_declared_tools_never_reach_the_transaction(self): + # Regression for the inflation bug: the builder's only non-MCP source is + # the response's tool_calls, so a request declaring N tools while the + # model invokes one produces exactly one attribution. + transaction = build_tool_usage_transaction( + request_id="r1", + start_time_iso="2026-07-25T10:00:00+00:00", + mcp_namespaced_tool_name=None, + spend=0.5, + total_tokens=100, + completion_response=_response_with_tool_calls("get_weather"), + ) + assert transaction is not None + assert transaction.tool_names == ("get_weather",) + + def test_no_invoked_tools_returns_none(self): + assert ( + build_tool_usage_transaction( + request_id="r1", + start_time_iso="2026-07-25T10:00:00+00:00", + mcp_namespaced_tool_name=None, + spend=0.5, + total_tokens=100, + completion_response=SimpleNamespace(choices=[SimpleNamespace(message=SimpleNamespace(tool_calls=None))]), + ) + is None + ) + + def test_mcp_name_and_response_names_dedupe(self): + transaction = build_tool_usage_transaction( + request_id="r1", + start_time_iso="2026-07-25T10:00:00+00:00", + mcp_namespaced_tool_name="srv/tool_a", + spend=0.5, + total_tokens=100, + completion_response=_response_with_tool_calls("srv/tool_a", "tool_b", "tool_b"), + ) + assert transaction is not None + assert transaction.tool_names == ("srv/tool_a", "tool_b") + + def test_date_matches_daily_spend_writer_derivation(self): + # The daily spend writer derives its date bucket as + # payload["startTime"].split("T")[0] (db_spend_update_writer.py), i.e. the + # timestamp's own calendar date, NOT the astimezone-UTC date. A non-UTC + # isoformat pins the difference: 2026-07-25T22:00:00-07:00 is 2026-07-26 + # in UTC but must bucket as 2026-07-25 to match LiteLLM_DailyUserSpend. + start_time_iso = "2026-07-25T22:00:00-07:00" + transaction = build_tool_usage_transaction( + request_id="r1", + start_time_iso=start_time_iso, + mcp_namespaced_tool_name="srv/tool_a", + spend=0.5, + total_tokens=100, + completion_response=None, + ) + assert transaction is not None + assert transaction.date == start_time_iso.split("T")[0] == "2026-07-25" + + def test_realtime_tool_calls_reach_the_transaction(self): + # Realtime sessions carry invoked tools in kwargs["realtime_tool_calls"] + # (OpenAI tool_calls dict shape, built in realtime_streaming.py), not on a + # response object; they must land in the rollup like any other invocation. + realtime_tool_calls = [ + {"id": "call_1", "type": "function", "function": {"name": "rt_get_weather", "arguments": "{}"}}, + ] + transaction = build_tool_usage_transaction( + request_id="r1", + start_time_iso="2026-07-25T10:00:00+00:00", + mcp_namespaced_tool_name=None, + spend=0.5, + total_tokens=100, + completion_response=None, + realtime_tool_calls=realtime_tool_calls, + ) + assert transaction is not None + assert transaction.tool_names == ("rt_get_weather",) + + def test_realtime_names_dedupe_against_response_names(self): + realtime_tool_calls = [{"type": "function", "function": {"name": "get_weather", "arguments": "{}"}}] + transaction = build_tool_usage_transaction( + request_id="r1", + start_time_iso="2026-07-25T10:00:00+00:00", + mcp_namespaced_tool_name=None, + spend=0.5, + total_tokens=100, + completion_response=_response_with_tool_calls("get_weather"), + realtime_tool_calls=realtime_tool_calls, + ) + assert transaction is not None + assert transaction.tool_names == ("get_weather",) + + def test_unparseable_start_time_returns_none(self): + assert ( + build_tool_usage_transaction( + request_id="r1", + start_time_iso="not-a-timestamp", + mcp_namespaced_tool_name="srv/tool_a", + spend=0.5, + total_tokens=100, + completion_response=None, + ) + is None + ) + + +class TestResponseToolCallNames: + def test_unrecognized_shapes_yield_nothing(self): + assert response_tool_call_names(None) == () + assert response_tool_call_names(SimpleNamespace()) == () + assert response_tool_call_names(ValueError("boom")) == () + + def test_blank_names_are_dropped(self): + assert response_tool_call_names(_response_with_tool_calls(" ", "real_tool")) == ("real_tool",) + + def test_responses_api_output_function_calls(self): + # Regression: /v1/responses carries invocations in output[] items of + # type function_call, not in choices; they must reach the rollup. + response = SimpleNamespace( + output=[ + SimpleNamespace(type="function_call", name="get_weather", call_id="c1", arguments="{}"), + SimpleNamespace(type="message", name=None, call_id=None, arguments=None), + ] + ) + assert response_tool_call_names(response) == ("get_weather",) + + def test_anthropic_messages_tool_use_blocks(self): + response = { + "content": [ + {"type": "text", "text": "checking"}, + {"type": "tool_use", "id": "t1", "name": "ant_get_weather", "input": {"city": "Paris"}}, + ] + } + assert response_tool_call_names(response) == ("ant_get_weather",) + + +def _transaction( + request_id: str, + date: str = "2026-07-25", + tool_names: tuple = ("tool_a",), + spend: float = 1.0, + total_tokens: int = 10, +) -> ToolUsageTransaction: + from datetime import datetime, timezone + + return ToolUsageTransaction( + request_id=request_id, + date=date, + start_time=datetime(2026, 7, 25, 10, 0, tzinfo=timezone.utc), + tool_names=tool_names, + spend=spend, + total_tokens=total_tokens, + ) + + +class TestFlushToolUsageTransactions: + @pytest.mark.asyncio + async def test_multi_tool_request_attributes_full_spend_to_each_tool(self): + prisma, batcher = _prisma_with_batcher() + await flush_tool_usage_transactions( + prisma_client=prisma, + transactions=[_transaction("r1", tool_names=("tool_a", "tool_b"), spend=0.10, total_tokens=100)], + ) + index_rows = batcher.litellm_spendlogtoolindex.create_many.call_args.kwargs["data"] + assert [(r["request_id"], r["tool_name"]) for r in index_rows] == [("r1", "tool_a"), ("r1", "tool_b")] + assert batcher.litellm_spendlogtoolindex.create_many.call_args.kwargs["skip_duplicates"] is True + + upserts = { + c.kwargs["where"]["date_tool_name"]["tool_name"]: c.kwargs["data"] + for c in batcher.litellm_dailytoolspend.upsert.call_args_list + } + assert set(upserts) == {"tool_a", "tool_b"} + for data in upserts.values(): + assert data["create"]["spend"] == 0.10 + assert data["create"]["request_count"] == 1 + assert data["update"]["spend"] == {"increment": 0.10} + assert data["update"]["request_count"] == {"increment": 1} + + @pytest.mark.asyncio + async def test_same_day_same_tool_aggregates_within_batch(self): + prisma, batcher = _prisma_with_batcher() + await flush_tool_usage_transactions( + prisma_client=prisma, + transactions=[ + _transaction("r1", spend=0.10, total_tokens=100), + _transaction("r2", spend=0.30, total_tokens=200), + ], + ) + assert batcher.litellm_dailytoolspend.upsert.call_count == 1 + data = batcher.litellm_dailytoolspend.upsert.call_args.kwargs["data"] + assert data["create"] == { + "date": "2026-07-25", + "tool_name": "tool_a", + "spend": pytest.approx(0.40), + "total_tokens": 300, + "request_count": 2, + } + assert data["update"]["spend"] == {"increment": pytest.approx(0.40)} + assert data["update"]["total_tokens"] == {"increment": 300} + assert data["update"]["request_count"] == {"increment": 2} + + @pytest.mark.asyncio + async def test_index_rows_and_rollup_share_one_transaction(self): + # Both writes go through the same batch_() so a failed flush cannot leave + # index rows without their rollup increments (or vice versa); increments + # are not idempotent, so partial states must be unreachable. + prisma, batcher = _prisma_with_batcher() + await flush_tool_usage_transactions( + prisma_client=prisma, + transactions=[_transaction("r1")], + ) + prisma.db.batch_.assert_called_once() + batcher.litellm_spendlogtoolindex.create_many.assert_called_once() + batcher.litellm_dailytoolspend.upsert.assert_called_once() + + @pytest.mark.asyncio + async def test_empty_batch_touches_nothing(self): + prisma, _ = _prisma_with_batcher() + await flush_tool_usage_transactions(prisma_client=prisma, transactions=[]) + prisma.db.batch_.assert_not_called() + + @pytest.mark.asyncio + async def test_connection_errors_retry_and_succeed(self, monkeypatch): + # A failed batch commits nothing, so retrying a connection error cannot + # double-count; the flush must retry rather than drop the batch. + import httpx + + batcher = _FakeBatcher() + prisma = MagicMock() + prisma.db.batch_ = MagicMock(side_effect=[httpx.ConnectError("down"), batcher]) + sleeps: list[float] = [] + + async def fake_sleep(seconds: float) -> None: + sleeps.append(seconds) + + monkeypatch.setattr("litellm.proxy.db.spend_log_tool_index.asyncio.sleep", fake_sleep) + await flush_tool_usage_transactions(prisma_client=prisma, transactions=[_transaction("r1")]) + assert prisma.db.batch_.call_count == 2 + assert len(sleeps) == 1 + batcher.litellm_dailytoolspend.upsert.assert_called_once() + + @pytest.mark.asyncio + async def test_connection_errors_exhaust_retries_then_raise(self, monkeypatch): + import httpx + + prisma = MagicMock() + prisma.db.batch_ = MagicMock(side_effect=httpx.ConnectError("down")) + + async def fake_sleep(seconds: float) -> None: + return None + + monkeypatch.setattr("litellm.proxy.db.spend_log_tool_index.asyncio.sleep", fake_sleep) + with pytest.raises(httpx.ConnectError): + await flush_tool_usage_transactions( + prisma_client=prisma, transactions=[_transaction("r1")], n_retry_times=2 + ) + assert prisma.db.batch_.call_count == 3 + + @pytest.mark.asyncio + async def test_non_connection_errors_do_not_retry(self): + prisma = MagicMock() + prisma.db.batch_ = MagicMock(side_effect=ValueError("bad data")) + with pytest.raises(ValueError): + await flush_tool_usage_transactions(prisma_client=prisma, transactions=[_transaction("r1")]) + prisma.db.batch_.assert_called_once() diff --git a/tests/test_litellm/proxy/management_endpoints/test_tool_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_tool_management_endpoints.py index c908250fa64..45c3c6c2466 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_tool_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_tool_management_endpoints.py @@ -19,11 +19,7 @@ from fastapi.testclient import TestClient sys.path.insert(0, os.path.abspath("../../..")) -from litellm.proxy.management_endpoints.tool_management_endpoints import ( - _build_tool_spend_response, - _ToolSpendRow, - router, -) +from litellm.proxy.management_endpoints.tool_management_endpoints import router from litellm.types.tool_management import LiteLLM_ToolTableRow # --- helpers --- @@ -64,6 +60,30 @@ def _override_auth(): _MOCK_PRISMA = MagicMock() +def _rollup_row(date: str, tool_name: str, spend: float, request_count: int, total_tokens: int) -> MagicMock: + row = MagicMock() + row.date = date + row.tool_name = tool_name + row.spend = spend + row.request_count = request_count + row.total_tokens = total_tokens + return row + + +def _group_row(tool_name: str, spend: float, request_count: int, total_tokens: int) -> dict: + return {"tool_name": tool_name, "_sum": {"spend": spend, "total_tokens": total_tokens, "request_count": request_count}} + + +def _rollup_prisma(group_rows: list, daily_rows: list | None = None) -> MagicMock: + prisma = MagicMock() + prisma.db.query_raw = AsyncMock(return_value=[]) + prisma.db.litellm_spendlogs.find_many = AsyncMock(return_value=[]) + prisma.db.litellm_spendlogtoolindex.find_many = AsyncMock(return_value=[]) + prisma.db.litellm_dailytoolspend.group_by = AsyncMock(return_value=group_rows) + prisma.db.litellm_dailytoolspend.find_many = AsyncMock(return_value=daily_rows or []) + return prisma + + # --- test class --- @@ -154,21 +174,23 @@ class TestToolManagementEndpoints: assert resp.status_code == 422 def test_tool_spend_route_not_shadowed_by_get_tool(self): - prisma = MagicMock() - prisma.db.query_raw = AsyncMock(return_value=[]) + prisma = _rollup_prisma([]) with patch("litellm.proxy.proxy_server.prisma_client", prisma): resp = self.client.get("/v1/tool/spend") assert resp.status_code == 200 assert resp.json()["by_tool"] == [] - def test_tool_spend_aggregates_and_sorts(self): - rows = [ - {"date": "2026-07-01", "tool_name": "search", "call_count": 2, "spend": 1.0, "total_tokens": 100}, - {"date": "2026-07-02", "tool_name": "search", "call_count": 1, "spend": 4.0, "total_tokens": 50}, - {"date": "2026-07-01", "tool_name": "read_file", "call_count": 3, "spend": 2.0, "total_tokens": 300}, + def test_tool_spend_serves_sql_aggregates_and_daily_series(self): + group_rows = [ + _group_row("search", spend=5.0, request_count=3, total_tokens=150), + _group_row("read_file", spend=2.0, request_count=3, total_tokens=300), ] - prisma = MagicMock() - prisma.db.query_raw = AsyncMock(side_effect=[rows, [{"total_spend": 5.5}]]) + daily_rows = [ + _rollup_row("2026-07-01", "search", spend=1.0, request_count=2, total_tokens=100), + _rollup_row("2026-07-01", "read_file", spend=2.0, request_count=3, total_tokens=300), + _rollup_row("2026-07-02", "search", spend=4.0, request_count=1, total_tokens=50), + ] + prisma = _rollup_prisma(group_rows, daily_rows) with patch("litellm.proxy.proxy_server.prisma_client", prisma): resp = self.client.get("/v1/tool/spend?start_date=2026-07-01&end_date=2026-07-02") assert resp.status_code == 200 @@ -179,94 +201,89 @@ class TestToolManagementEndpoints: assert search["call_count"] == 3 assert search["total_tokens"] == 150 assert len(body["daily"]) == 3 + assert body["daily"][0]["call_count"] == 2 assert body["start_date"] == "2026-07-01" assert body["end_date"] == "2026-07-02" - assert body["total_spend"] == 5.5 + + def test_tool_spend_coerces_bigint_string_sums(self): + # prisma group_by returns BigInt sums as strings ("808"); the response + # must coerce them to ints rather than 500 on validation. + group_rows = [{"tool_name": "search", "_sum": {"spend": 0.5, "total_tokens": "808", "request_count": "3"}}] + prisma = _rollup_prisma(group_rows) + with patch("litellm.proxy.proxy_server.prisma_client", prisma): + resp = self.client.get("/v1/tool/spend?start_date=2026-07-01&end_date=2026-07-02") + assert resp.status_code == 200 + assert resp.json()["by_tool"][0]["total_tokens"] == 808 + assert resp.json()["by_tool"][0]["call_count"] == 3 + + def test_tool_spend_daily_restricted_to_top_tools_and_capped(self): + from litellm.constants import TOOL_SPEND_TOP_TOOLS + + group_rows = [_group_row("search", spend=5.0, request_count=1, total_tokens=10)] + prisma = _rollup_prisma(group_rows) + with patch("litellm.proxy.proxy_server.prisma_client", prisma): + resp = self.client.get("/v1/tool/spend?start_date=2026-07-01&end_date=2026-07-02") + assert resp.status_code == 200 + group_kwargs = prisma.db.litellm_dailytoolspend.group_by.await_args.kwargs + assert group_kwargs["take"] == TOOL_SPEND_TOP_TOOLS + assert group_kwargs["order"] == {"_sum": {"spend": "desc"}} + daily_where = prisma.db.litellm_dailytoolspend.find_many.await_args.kwargs["where"] + assert daily_where["tool_name"] == {"in": ["search"]} + + def test_tool_spend_skips_daily_query_when_no_tools(self): + prisma = _rollup_prisma([]) + with patch("litellm.proxy.proxy_server.prisma_client", prisma): + resp = self.client.get("/v1/tool/spend?start_date=2026-07-01&end_date=2026-07-02") + assert resp.status_code == 200 + prisma.db.litellm_dailytoolspend.find_many.assert_not_awaited() @patch("litellm.proxy.proxy_server.prisma_client", None) def test_tool_spend_no_db_returns_500(self): resp = self.client.get("/v1/tool/spend") assert resp.status_code == 500 - def test_tool_spend_end_date_is_inclusive_via_exclusive_next_day_bound(self): - prisma = MagicMock() - prisma.db.query_raw = AsyncMock(return_value=[]) + def test_tool_spend_reads_rollup_only_never_spendlogs(self): + # Regression for the GA blocker: the dashboard aggregate must be served + # entirely from LiteLLM_DailyToolSpend; any query_raw or SpendLogs table + # access on this path reintroduces the per-request scan. + prisma = _rollup_prisma([]) with patch("litellm.proxy.proxy_server.prisma_client", prisma): resp = self.client.get("/v1/tool/spend?start_date=2026-07-01&end_date=2026-07-02") assert resp.status_code == 200 - expected_binds = ( - datetime(2026, 7, 1, tzinfo=timezone.utc).isoformat(), - datetime(2026, 7, 3, tzinfo=timezone.utc).isoformat(), - ) - assert prisma.db.query_raw.await_count == 2 - for call in prisma.db.query_raw.await_args_list: - assert tuple(call.args[1:]) == expected_binds + prisma.db.query_raw.assert_not_awaited() + prisma.db.litellm_spendlogs.find_many.assert_not_awaited() + prisma.db.litellm_spendlogtoolindex.find_many.assert_not_awaited() + prisma.db.litellm_dailytoolspend.group_by.assert_awaited_once() + + def test_tool_spend_windows_rollup_by_inclusive_date_strings(self): + prisma = _rollup_prisma([]) + with patch("litellm.proxy.proxy_server.prisma_client", prisma): + resp = self.client.get("/v1/tool/spend?start_date=2026-07-01&end_date=2026-07-02") + assert resp.status_code == 200 + where = prisma.db.litellm_dailytoolspend.group_by.await_args.kwargs["where"] + assert where == {"date": {"gte": "2026-07-01", "lte": "2026-07-02"}} assert resp.json()["end_date"] == "2026-07-02" - def test_tool_spend_start_clamped_to_30_days_before_end(self): - # Clamped floor is end_date minus 30 days, serving up to 31 calendar dates - # inclusive: deliberately the same width as the endpoint's default window, - # so the dashboard's default range never triggers the clamp. - prisma = MagicMock() - prisma.db.query_raw = AsyncMock(return_value=[]) + def test_tool_spend_wide_range_served_fully(self): + # Regression: the 30-day clamp is gone; a 182-day request is served as + # requested because the rollup read is O(tools x dates). + prisma = _rollup_prisma([]) with patch("litellm.proxy.proxy_server.prisma_client", prisma): resp = self.client.get("/v1/tool/spend?start_date=2026-01-01&end_date=2026-07-01") assert resp.status_code == 200 - expected_binds = ( - datetime(2026, 6, 1, tzinfo=timezone.utc).isoformat(), - datetime(2026, 7, 2, tzinfo=timezone.utc).isoformat(), - ) - assert prisma.db.query_raw.await_count == 2 - for call in prisma.db.query_raw.await_args_list: - assert tuple(call.args[1:]) == expected_binds - assert resp.json()["start_date"] == "2026-06-01" + where = prisma.db.litellm_dailytoolspend.group_by.await_args.kwargs["where"] + assert where == {"date": {"gte": "2026-01-01", "lte": "2026-07-01"}} + assert resp.json()["start_date"] == "2026-01-01" assert resp.json()["end_date"] == "2026-07-01" - def test_tool_spend_range_within_cap_is_not_clamped(self): - prisma = MagicMock() - prisma.db.query_raw = AsyncMock(return_value=[]) + def test_tool_spend_defaults_to_trailing_30_days(self): + prisma = _rollup_prisma([]) with patch("litellm.proxy.proxy_server.prisma_client", prisma): - resp = self.client.get("/v1/tool/spend?start_date=2026-06-25&end_date=2026-07-01") + resp = self.client.get("/v1/tool/spend") assert resp.status_code == 200 - for call in prisma.db.query_raw.await_args_list: - assert call.args[1] == datetime(2026, 6, 25, tzinfo=timezone.utc).isoformat() - assert resp.json()["start_date"] == "2026-06-25" - - def test_tool_spend_start_honored_when_end_date_omitted(self): - # Regression: with end_date omitted the floor anchors to today's UTC - # midnight, not now's time-of-day, so an explicit start_date exactly 30 - # days back is served from midnight rather than truncated to mid-day. - prisma = MagicMock() - prisma.db.query_raw = AsyncMock(return_value=[]) - floor_day = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) - timedelta(days=30) - with patch("litellm.proxy.proxy_server.prisma_client", prisma): - resp = self.client.get(f"/v1/tool/spend?start_date={floor_day.strftime('%Y-%m-%d')}") - assert resp.status_code == 200 - for call in prisma.db.query_raw.await_args_list: - assert call.args[1] == floor_day.isoformat() - assert resp.json()["start_date"] == floor_day.strftime("%Y-%m-%d") - - def test_tool_spend_clamp_without_end_date_lands_on_midnight(self): - prisma = MagicMock() - prisma.db.query_raw = AsyncMock(return_value=[]) - floor_day = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) - timedelta(days=30) - with patch("litellm.proxy.proxy_server.prisma_client", prisma): - resp = self.client.get("/v1/tool/spend?start_date=2020-01-01") - assert resp.status_code == 200 - for call in prisma.db.query_raw.await_args_list: - assert call.args[1] == floor_day.isoformat() - assert resp.json()["start_date"] == floor_day.strftime("%Y-%m-%d") - - def test_tool_spend_total_query_bounds_outer_spendlogs_scan(self): - prisma = MagicMock() - prisma.db.query_raw = AsyncMock(return_value=[]) - with patch("litellm.proxy.proxy_server.prisma_client", prisma): - resp = self.client.get("/v1/tool/spend?start_date=2026-07-01&end_date=2026-07-02") - assert resp.status_code == 200 - for call in prisma.db.query_raw.await_args_list: - sql = call.args[0] - assert 'sl."startTime" >=' in sql - assert 'sl."startTime" <' in sql + today = datetime.now(timezone.utc) + assert resp.json()["end_date"] == today.strftime("%Y-%m-%d") + assert resp.json()["start_date"] == (today - timedelta(days=30)).strftime("%Y-%m-%d") @pytest.mark.parametrize( "query", @@ -279,13 +296,12 @@ class TestToolManagementEndpoints: ], ) def test_tool_spend_malformed_date_returns_400(self, query: str): - prisma = MagicMock() - prisma.db.query_raw = AsyncMock(return_value=[]) + prisma = _rollup_prisma([]) with patch("litellm.proxy.proxy_server.prisma_client", prisma): resp = self.client.get(f"/v1/tool/spend?{query}") assert resp.status_code == 400 assert "Invalid date format" in resp.json()["detail"] - prisma.db.query_raw.assert_not_awaited() + prisma.db.litellm_dailytoolspend.group_by.assert_not_awaited() def test_tool_spend_non_admin_returns_403(self): from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth @@ -296,38 +312,8 @@ class TestToolManagementEndpoints: api_key="sk-user", user_id="u1", user_role=LitellmUserRoles.INTERNAL_USER ) client = TestClient(app, raise_server_exceptions=True) - prisma = MagicMock() - prisma.db.query_raw = AsyncMock(return_value=[]) + prisma = _rollup_prisma([]) with patch("litellm.proxy.proxy_server.prisma_client", prisma): resp = client.get("/v1/tool/spend") assert resp.status_code == 403 - prisma.db.query_raw.assert_not_awaited() - - -def _spend_row(date: str, tool_name: str, spend: float, call_count: int = 1, total_tokens: int = 10) -> _ToolSpendRow: - return _ToolSpendRow(date=date, tool_name=tool_name, call_count=call_count, spend=spend, total_tokens=total_tokens) - - -class TestBuildToolSpendResponse: - def test_multi_tool_attribution_double_counts_per_tool_but_not_total(self): - rows = [ - _spend_row("2026-07-01", "a", spend=3.0), - _spend_row("2026-07-01", "b", spend=3.0), - ] - resp = _build_tool_spend_response(rows, total_spend=3.0, start_date="2026-07-01", end_date="2026-07-01") - by_tool = {t.tool_name: t.spend for t in resp.by_tool} - assert by_tool == {"a": 3.0, "b": 3.0} - assert resp.total_spend == 3.0 - - def test_groups_across_days_and_sorts_by_spend(self): - rows = [ - _spend_row("2026-07-01", "b", spend=1.0, call_count=2, total_tokens=100), - _spend_row("2026-07-02", "b", spend=4.0, call_count=1, total_tokens=50), - _spend_row("2026-07-01", "a", spend=2.0, call_count=3, total_tokens=300), - ] - resp = _build_tool_spend_response(rows, total_spend=7.0, start_date="2026-07-01", end_date="2026-07-02") - assert [(t.tool_name, t.spend, t.call_count, t.total_tokens) for t in resp.by_tool] == [ - ("b", 5.0, 3, 150), - ("a", 2.0, 3, 300), - ] - assert len(resp.daily) == 3 + prisma.db.litellm_dailytoolspend.group_by.assert_not_awaited() diff --git a/tests/test_litellm/proxy/test_spend_log_cleanup.py b/tests/test_litellm/proxy/test_spend_log_cleanup.py index f969b040a0d..2ba9257e1da 100644 --- a/tests/test_litellm/proxy/test_spend_log_cleanup.py +++ b/tests/test_litellm/proxy/test_spend_log_cleanup.py @@ -193,6 +193,12 @@ async def test_cleanup_old_spend_logs_batch_deletion(): tool_index_sql = mock_db.execute_raw.call_args_list[3][0][0] assert 'DELETE FROM "LiteLLM_SpendLogToolIndex"' in tool_index_sql + # The LiteLLM_DailyToolSpend rollup must outlive spend-log retention: it is + # the only copy of tool spend history once its per-request sources expire, + # so spend-log cleanup must never touch it. + for call in mock_db.execute_raw.call_args_list: + assert "LiteLLM_DailyToolSpend" not in call[0][0] + @pytest.mark.asyncio async def test_cleanup_old_spend_logs_retention_period_cutoff(): diff --git a/tests/test_litellm/proxy/utils/prisma_and_spend/conftest.py b/tests/test_litellm/proxy/utils/prisma_and_spend/conftest.py index af62b7eef62..74c9abd9978 100644 --- a/tests/test_litellm/proxy/utils/prisma_and_spend/conftest.py +++ b/tests/test_litellm/proxy/utils/prisma_and_spend/conftest.py @@ -128,6 +128,8 @@ def mock_prisma_client() -> MagicMock: client.proxy_logging_obj.failure_handler = AsyncMock() client.spend_log_transactions = [] client._spend_log_transactions_lock = asyncio.Lock() + client.tool_usage_transactions = [] + client._tool_usage_transactions_lock = asyncio.Lock() client.jsonify_object = lambda data: dict(data) client.db.is_connected = MagicMock(return_value=False) client.db.connect = AsyncMock() diff --git a/tests/test_litellm/proxy/utils/prisma_and_spend/test_spend_functions.py b/tests/test_litellm/proxy/utils/prisma_and_spend/test_spend_functions.py index a0b3af54750..d9eeb168611 100644 --- a/tests/test_litellm/proxy/utils/prisma_and_spend/test_spend_functions.py +++ b/tests/test_litellm/proxy/utils/prisma_and_spend/test_spend_functions.py @@ -188,6 +188,35 @@ async def test_update_spend_logs_job_skips_when_queue_empty( assert mock_prisma_client.db.litellm_spendlogs.create_many.await_count == 0 +@pytest.mark.asyncio +async def test_update_spend_logs_job_drains_tool_queue_when_spend_queue_empty( + mock_prisma_client: Any, monkeypatch: pytest.MonkeyPatch +) -> None: + # Regression: a spend-log write failure aborts a run before the tool drain, + # so tool transactions can outlive the spend queue; the job must still run + # for them instead of early-returning on the empty spend queue. + import litellm.proxy.db.spend_log_tool_index as tool_mod + import litellm.proxy.guardrails.usage_tracking as guard_mod + + proxy_logging = MagicMock() + proxy_logging.failure_handler = AsyncMock() + mock_prisma_client.spend_log_transactions = [] + mock_prisma_client.tool_usage_transactions = [MagicMock()] + mock_prisma_client.db.litellm_spendlogs.create_many = AsyncMock() + monkeypatch.setattr(guard_mod, "process_spend_logs_guardrail_usage", AsyncMock(), raising=False) + flush_stub = AsyncMock() + monkeypatch.setattr(tool_mod, "flush_tool_usage_transactions", flush_stub, raising=False) + + await update_spend_logs_job( + prisma_client=mock_prisma_client, + db_writer_client=None, + proxy_logging_obj=proxy_logging, + ) + + assert len(flush_stub.await_args.kwargs["transactions"]) == 1 + assert mock_prisma_client.tool_usage_transactions == [] + + @pytest.mark.asyncio async def test_update_spend_logs_job_processes_and_clears_queue( mock_prisma_client: Any, make_spend_log_row: Any, monkeypatch: pytest.MonkeyPatch @@ -208,7 +237,7 @@ async def test_update_spend_logs_job_processes_and_clears_queue( guard_mod, "process_spend_logs_guardrail_usage", AsyncMock(), raising=False ) monkeypatch.setattr( - tool_mod, "process_spend_logs_tool_usage", AsyncMock(), raising=False + tool_mod, "flush_tool_usage_transactions", AsyncMock(), raising=False ) await update_spend_logs_job( diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index ae09a893194..26f19f1b35c 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -225,11 +225,6 @@ "count": 1 } }, - "src/app/(dashboard)/cost-optimization/_components/UsageTab.tsx": { - "no-restricted-imports": { - "count": 1 - } - }, "src/app/(dashboard)/cost-tracking/_components/add_margin_form.tsx": { "local/filename-pascal-case": { "count": 1 @@ -4350,4 +4345,4 @@ "count": 1 } } -} +} \ No newline at end of file diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.activity.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.activity.test.tsx index 27261768b8d..305a65ec5a2 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.activity.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.activity.test.tsx @@ -5,7 +5,7 @@ const mockUserDailyActivityCall = vi.fn(); vi.mock("@/components/networking", () => ({ userDailyActivityCall: (...args: unknown[]) => mockUserDailyActivityCall(...args), - getToolSpend: vi.fn().mockResolvedValue({ by_tool: [], daily: [], total_spend: 0, start_date: null, end_date: null }), + getToolSpend: vi.fn().mockResolvedValue({ by_tool: [], daily: [], start_date: null, end_date: null }), getGeneralSettingsCall: vi.fn().mockResolvedValue([]), })); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.test.tsx index f84167f5a82..125c8dff694 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.test.tsx @@ -34,7 +34,7 @@ vi.mock("@/components/shared/charts", () => ({ import UsageTab from "./UsageTab"; -const emptyToolSpend: ToolSpendResponse = { by_tool: [], daily: [], total_spend: 0, start_date: null, end_date: null }; +const emptyToolSpend: ToolSpendResponse = { by_tool: [], daily: [], start_date: null, end_date: null }; const baseMetrics = (overrides: Partial): SpendMetrics => ({ spend: 0, @@ -216,7 +216,6 @@ describe("UsageTab", () => { { tool_name: "read_file", spend: 1.0, call_count: 2, total_tokens: 50 }, ], daily: [{ date: "2026-07-12", tool_name: "search", spend: 4.0, call_count: 3 }], - total_spend: 5.0, start_date: "2026-07-12", end_date: "2026-07-12", }; @@ -226,31 +225,4 @@ describe("UsageTab", () => { const series = JSON.parse(bars[0].getAttribute("data-series") ?? "[]"); expect(series[0]).toMatchObject({ tool_name: "search", spend: 4.0 }); }); - - it("notes the 30-day cap when the server clamps the tool spend window", async () => { - const toolSpend = { - by_tool: [{ tool_name: "search", spend: 4.0, call_count: 3, total_tokens: 150 }], - daily: [{ date: "2026-07-12", tool_name: "search", spend: 4.0, call_count: 3 }], - total_spend: 4.0, - start_date: "2026-07-05", - end_date: "2026-07-14", - }; - const { findByText } = renderWith([day("2026-07-12", {})], { toolSpend }); - - expect(await findByText(/capped at 30 days before the end of the selected range/)).toBeInTheDocument(); - }); - - it("shows no cap note when the served window matches the request", async () => { - const toolSpend = { - by_tool: [{ tool_name: "search", spend: 4.0, call_count: 3, total_tokens: 150 }], - daily: [{ date: "2026-07-12", tool_name: "search", spend: 4.0, call_count: 3 }], - total_spend: 4.0, - start_date: "2026-07-01", - end_date: "2026-07-14", - }; - const { findAllByTestId, queryByText } = renderWith([day("2026-07-12", {})], { toolSpend }); - - await findAllByTestId("bar-chart"); - expect(queryByText(/capped at 30 days before the end of the selected range/)).not.toBeInTheDocument(); - }); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.tsx index 15ce84b8445..508bc13496c 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.tsx @@ -34,7 +34,6 @@ interface UsageTabProps { const EMPTY_TOOL_SPEND: ToolSpendResponse = { by_tool: [], daily: [], - total_spend: 0, start_date: null, end_date: null, }; @@ -103,7 +102,6 @@ const UsageTab: React.FC = ({ accessToken, activity }) => { const toolSpend = toolSpendState?.key === rangeKey ? toolSpendState.data : null; const toolSpendLoading = toolSpendEnabled && toolSpend === null; - const toolSpendWindowClamped = !!toolSpend?.start_date && !!startTime && toolSpend.start_date > isoDay(startTime); const compressionTotal = useMemo(() => results.reduce((sum, d) => sum + compressionOf(d.metrics), 0), [results]); const cachingTotal = useMemo(() => results.reduce((sum, d) => sum + cachingOf(d.metrics), 0), [results]); @@ -262,15 +260,10 @@ const UsageTab: React.FC = ({ accessToken, activity }) => { Spend by tool

- Spend on requests that called each tool (MCP and client-side tools). A request that used multiple tools - counts its full spend toward each, so this attributes rather than partitions spend. + Spend on requests that invoked each tool (MCP and client-side tools); declaring a tool without invoking it + does not count. A request that invoked multiple tools counts its full spend toward each, so this attributes + rather than partitions spend.

- {toolSpendWindowClamped && ( -

- Tool spend is capped at 30 days before the end of the selected range; showing spend since{" "} - {toolSpend?.start_date}. -

- )}
{topTools.length === 0 ? ( diff --git a/ui/litellm-dashboard/src/components/ToolDetail.tsx b/ui/litellm-dashboard/src/components/ToolDetail.tsx index 6a8457a559f..06f14638141 100644 --- a/ui/litellm-dashboard/src/components/ToolDetail.tsx +++ b/ui/litellm-dashboard/src/components/ToolDetail.tsx @@ -430,7 +430,7 @@ export function ToolDetail({ toolName, onBack, accessToken }: ToolDetailProps) {

- Recent logs + Recent invocations

Date: Sun, 26 Jul 2026 04:58:28 +0000 Subject: [PATCH 18/75] fix(ui): keep the spend-by-tool legend from overlapping the charts Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../_components/UsageTab.test.tsx | 41 ++++++++++++++++++- .../_components/UsageTab.tsx | 5 ++- .../shared/charts/bar_chart.test.tsx | 34 +++++++++++++++ .../components/shared/charts/bar_chart.tsx | 13 ++++-- .../shared/charts/chart_legend.test.tsx | 8 ++++ .../components/shared/charts/chart_legend.tsx | 2 +- .../src/components/ui/chart.tsx | 6 ++- 7 files changed, 101 insertions(+), 8 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.test.tsx index 125c8dff694..4d1f1c182db 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.test.tsx @@ -23,8 +23,24 @@ vi.mock("@/components/shared/charts", () => ({ DonutChart: ({ data, label }: { data: unknown; label: string }) => (
), - BarChart: ({ data, categories }: { data: unknown; categories: string[] }) => ( -
+ BarChart: ({ + data, + categories, + colors, + showLegend, + }: { + data: unknown; + categories: string[]; + colors?: readonly string[]; + showLegend?: boolean; + }) => ( +
), CustomLegend: ({ categories }: { categories: readonly string[] }) => (
{categories.join(",")}
@@ -225,4 +241,25 @@ describe("UsageTab", () => { const series = JSON.parse(bars[0].getAttribute("data-series") ?? "[]"); expect(series[0]).toMatchObject({ tool_name: "search", spend: 4.0 }); }); + + it("renders the tool legend once outside the charts, with both charts sharing the tool colors", async () => { + const toolSpend = { + by_tool: [ + { tool_name: "search", spend: 4.0, call_count: 3, total_tokens: 150 }, + { tool_name: "read_file", spend: 1.0, call_count: 2, total_tokens: 50 }, + ], + daily: [{ date: "2026-07-12", tool_name: "search", spend: 4.0, call_count: 3 }], + start_date: "2026-07-12", + end_date: "2026-07-12", + }; + const { findAllByTestId, getAllByTestId } = renderWith([day("2026-07-12", {})], { toolSpend }); + + const bars = await findAllByTestId("bar-chart"); + const [totalByTool, dailyByTool] = bars.slice(-2); + expect(dailyByTool.getAttribute("data-show-legend")).toBe("false"); + expect(totalByTool.getAttribute("data-colors")).toBe(dailyByTool.getAttribute("data-colors")); + + const toolLegends = getAllByTestId("chart-legend").filter((legend) => legend.textContent === "search,read_file"); + expect(toolLegends).toHaveLength(1); + }); }); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.tsx index 508bc13496c..68f9c1d0ba4 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.tsx @@ -278,7 +278,8 @@ const UsageTab: React.FC = ({ accessToken, activity }) => { data={topToolsChart} index="tool_name" categories={["spend"]} - colors={["emerald"]} + colors={toolColors} + colorByDatum layout="vertical" yAxisWidth={140} showLegend={false} @@ -287,6 +288,7 @@ const UsageTab: React.FC = ({ accessToken, activity }) => {

Daily spend by tool

+ = ({ accessToken, activity }) => { colors={toolColors} stack valueFormatter={usd} + showLegend={false} />
diff --git a/ui/litellm-dashboard/src/components/shared/charts/bar_chart.test.tsx b/ui/litellm-dashboard/src/components/shared/charts/bar_chart.test.tsx index b30a252659f..cb0d5c603a4 100644 --- a/ui/litellm-dashboard/src/components/shared/charts/bar_chart.test.tsx +++ b/ui/litellm-dashboard/src/components/shared/charts/bar_chart.test.tsx @@ -113,6 +113,40 @@ describe("BarChart", () => { expect(container.querySelector("style")).toBeNull(); }); + it("colors each bar by its datum when colorByDatum is set, instead of one fill for the series", () => { + const singleCategory = [ + { tool: "alpha", spend: 3 }, + { tool: "beta", spend: 2 }, + { tool: "gamma", spend: 1 }, + ]; + + const { container, rerender } = render( + , + ); + const sharedFills = Array.from(container.querySelectorAll("path.recharts-rectangle")).map((rect) => + rect.getAttribute("fill"), + ); + expect(new Set(sharedFills).size).toBe(1); + + rerender( + , + ); + const perDatumFills = Array.from(container.querySelectorAll("path.recharts-rectangle")).map((rect) => + rect.getAttribute("fill"), + ); + expect(perDatumFills).toEqual([ + "var(--color-blue-500, #3b82f6)", + "var(--color-cyan-500, #06b6d4)", + "var(--color-violet-500, #8b5cf6)", + ]); + }); + it("stacks bars into a single column per index when stack is set", () => { const { container } = render( , diff --git a/ui/litellm-dashboard/src/components/shared/charts/bar_chart.tsx b/ui/litellm-dashboard/src/components/shared/charts/bar_chart.tsx index 7069ececb70..6bfcf14c2a0 100644 --- a/ui/litellm-dashboard/src/components/shared/charts/bar_chart.tsx +++ b/ui/litellm-dashboard/src/components/shared/charts/bar_chart.tsx @@ -1,17 +1,20 @@ "use client"; import * as React from "react"; -import { Bar, BarChart as RechartsBarChart, CartesianGrid, XAxis, YAxis } from "recharts"; +import { Bar, BarChart as RechartsBarChart, CartesianGrid, Cell, XAxis, YAxis } from "recharts"; import { ChartContainer, ChartLegend, ChartLegendContent, ChartTooltip, type ChartConfig } from "@/components/ui/chart"; import { cn } from "@/lib/cva.config"; import { ValueTooltip, type ChartTooltipComponent } from "./chart_tooltip"; import { categoryFills, type ChartColor } from "./colors"; +const MAX_BAR_SIZE = 64; + export type BarChartProps> = { data: readonly TDatum[]; index: string; categories: readonly string[]; colors?: readonly ChartColor[]; + colorByDatum?: boolean; valueFormatter?: (value: number) => string; stack?: boolean; layout?: "horizontal" | "vertical"; @@ -32,6 +35,7 @@ export function BarChart>({ index, categories, colors, + colorByDatum = false, valueFormatter, stack = false, layout = "horizontal", @@ -57,7 +61,7 @@ export function BarChart>({ ); } - const fills = categoryFills(categories.length, colors); + const fills = categoryFills(colorByDatum ? data.length : categories.length, colors); const config: ChartConfig = Object.fromEntries(categories.map((category) => [category, { label: category }])); const vertical = layout === "vertical"; const TooltipContent = customTooltip ?? ValueTooltip; @@ -115,6 +119,7 @@ export function BarChart>({ fill={fills[i]} stackId={stack ? "stack" : undefined} isAnimationActive={false} + maxBarSize={MAX_BAR_SIZE} onClick={ onValueChange ? (item: { payload?: TDatum }) => { @@ -122,7 +127,9 @@ export function BarChart>({ } : undefined } - /> + > + {colorByDatum && data.map((_, dataIndex) => )} + ))} diff --git a/ui/litellm-dashboard/src/components/shared/charts/chart_legend.test.tsx b/ui/litellm-dashboard/src/components/shared/charts/chart_legend.test.tsx index 889927aca43..28afe5faf9c 100644 --- a/ui/litellm-dashboard/src/components/shared/charts/chart_legend.test.tsx +++ b/ui/litellm-dashboard/src/components/shared/charts/chart_legend.test.tsx @@ -21,6 +21,14 @@ describe("CustomLegend", () => { expect(dots[1]?.getAttribute("style")).toContain("--color-green-500"); }); + it("wraps onto multiple lines instead of overflowing when there are many categories", () => { + const { container } = render( + `metrics.tool_${i}`)} colors={["blue", "green"]} />, + ); + + expect(container.firstElementChild?.className).toContain("flex-wrap"); + }); + it("cycles colors when there are more categories than colors", () => { const { container } = render( , diff --git a/ui/litellm-dashboard/src/components/shared/charts/chart_legend.tsx b/ui/litellm-dashboard/src/components/shared/charts/chart_legend.tsx index da252d8bf63..1551f3d0e39 100644 --- a/ui/litellm-dashboard/src/components/shared/charts/chart_legend.tsx +++ b/ui/litellm-dashboard/src/components/shared/charts/chart_legend.tsx @@ -11,7 +11,7 @@ export const CustomLegend = ({ categories: readonly string[]; colors: readonly ChartColor[]; }) => ( -
+
{categories.map((category, idx) => (
{payload .filter((item) => item.type !== "none") From 5d77c39bbba17dc37e8683f33017b5e9f3be0733 Mon Sep 17 00:00:00 2001 From: tin Date: Sun, 26 Jul 2026 05:23:57 +0000 Subject: [PATCH 19/75] fix(ui): color spend-by-tool charts with an ordered ramp Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../CostOptimizationView.activity.test.tsx | 2 +- .../cost-optimization/_components/UsageTab.test.tsx | 2 +- .../cost-optimization/_components/UsageTab.tsx | 4 ++-- .../src/components/shared/charts/colors.ts | 11 +++++++++++ .../src/components/shared/charts/index.ts | 9 ++++++++- 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.activity.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.activity.test.tsx index 305a65ec5a2..363525c48af 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.activity.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.activity.test.tsx @@ -19,7 +19,7 @@ vi.mock("@/components/shared/charts", () => ({ DonutChart: () =>
, BarChart: () =>
, CustomLegend: () =>
, - DEFAULT_COLOR_CYCLE: ["emerald"], + SEQUENTIAL_COLOR_RAMP: ["indigo"], })); vi.mock("@/app/(dashboard)/router-settings/_components/general_settings", () => ({ diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.test.tsx index 4d1f1c182db..beeb9466b1d 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.test.tsx @@ -45,7 +45,7 @@ vi.mock("@/components/shared/charts", () => ({ CustomLegend: ({ categories }: { categories: readonly string[] }) => (
{categories.join(",")}
), - DEFAULT_COLOR_CYCLE: ["emerald", "blue", "violet", "amber"], + SEQUENTIAL_COLOR_RAMP: ["indigo", "blue", "sky", "cyan"], })); import UsageTab from "./UsageTab"; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.tsx index 68f9c1d0ba4..829a79d7638 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useMemo, useState } from "react"; import { Info } from "lucide-react"; -import { AreaChart, BarChart, CustomLegend, DonutChart, DEFAULT_COLOR_CYCLE } from "@/components/shared/charts"; +import { AreaChart, BarChart, CustomLegend, DonutChart, SEQUENTIAL_COLOR_RAMP } from "@/components/shared/charts"; import AdvancedDatePicker from "@/components/shared/advanced_date_picker"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; @@ -166,7 +166,7 @@ const UsageTab: React.FC = ({ accessToken, activity }) => { })), [toolSpend, topToolNames], ); - const toolColors = useMemo(() => DEFAULT_COLOR_CYCLE.slice(0, Math.max(topToolNames.length, 1)), [topToolNames]); + const toolColors = useMemo(() => SEQUENTIAL_COLOR_RAMP.slice(0, Math.max(topToolNames.length, 1)), [topToolNames]); return (
diff --git a/ui/litellm-dashboard/src/components/shared/charts/colors.ts b/ui/litellm-dashboard/src/components/shared/charts/colors.ts index c30f58e9e4d..3efbd54cfd2 100644 --- a/ui/litellm-dashboard/src/components/shared/charts/colors.ts +++ b/ui/litellm-dashboard/src/components/shared/charts/colors.ts @@ -50,6 +50,17 @@ export const DEFAULT_COLOR_CYCLE: readonly ChartColor[] = [ "rose", ]; +export const SEQUENTIAL_COLOR_RAMP: readonly ChartColor[] = [ + "indigo", + "blue", + "sky", + "cyan", + "teal", + "emerald", + "green", + "lime", +]; + export const chartColorValue = (color: ChartColor): string => `var(--color-${color}-500, ${CHART_COLOR_HEX[color]})`; export const categoryFills = (count: number, colors?: readonly ChartColor[]): readonly string[] => { diff --git a/ui/litellm-dashboard/src/components/shared/charts/index.ts b/ui/litellm-dashboard/src/components/shared/charts/index.ts index 8383c767064..69edd3fb13f 100644 --- a/ui/litellm-dashboard/src/components/shared/charts/index.ts +++ b/ui/litellm-dashboard/src/components/shared/charts/index.ts @@ -8,6 +8,13 @@ export { type ChartTooltipComponent, type ChartTooltipProps, } from "./chart_tooltip"; -export { CHART_COLOR_HEX, DEFAULT_COLOR_CYCLE, categoryFills, chartColorValue, type ChartColor } from "./colors"; +export { + CHART_COLOR_HEX, + DEFAULT_COLOR_CYCLE, + SEQUENTIAL_COLOR_RAMP, + categoryFills, + chartColorValue, + type ChartColor, +} from "./colors"; export { DonutChart, type DonutChartProps } from "./donut_chart"; export { LineChart, type LineChartCurveType, type LineChartProps } from "./line_chart"; From 708a3a19df8d9f803fcbad4f6c365438033a39c8 Mon Sep 17 00:00:00 2001 From: tin Date: Sun, 26 Jul 2026 06:53:26 +0000 Subject: [PATCH 20/75] fix(ui): use a single muted blue ramp for the tool charts Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../src/components/shared/charts/colors.ts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/ui/litellm-dashboard/src/components/shared/charts/colors.ts b/ui/litellm-dashboard/src/components/shared/charts/colors.ts index 3efbd54cfd2..8b5717cc58e 100644 --- a/ui/litellm-dashboard/src/components/shared/charts/colors.ts +++ b/ui/litellm-dashboard/src/components/shared/charts/colors.ts @@ -23,7 +23,7 @@ export const CHART_COLOR_HEX = { rose: "#f43f5e", } as const; -export type ChartColor = keyof typeof CHART_COLOR_HEX; +export type ChartColor = keyof typeof CHART_COLOR_HEX | `#${string}`; export const DEFAULT_COLOR_CYCLE: readonly ChartColor[] = [ "blue", @@ -51,17 +51,20 @@ export const DEFAULT_COLOR_CYCLE: readonly ChartColor[] = [ ]; export const SEQUENTIAL_COLOR_RAMP: readonly ChartColor[] = [ - "indigo", - "blue", - "sky", - "cyan", - "teal", - "emerald", - "green", - "lime", + "#1e3a8a", + "#1d4ed8", + "#2563eb", + "#3b82f6", + "#60a5fa", + "#93c5fd", + "#bfdbfe", + "#dbeafe", ]; -export const chartColorValue = (color: ChartColor): string => `var(--color-${color}-500, ${CHART_COLOR_HEX[color]})`; +const NAMED_COLOR_HEX: Readonly> = CHART_COLOR_HEX; + +export const chartColorValue = (color: ChartColor): string => + color in NAMED_COLOR_HEX ? `var(--color-${color}-500, ${NAMED_COLOR_HEX[color]})` : color; export const categoryFills = (count: number, colors?: readonly ChartColor[]): readonly string[] => { const cycle = colors && colors.length > 0 ? colors : DEFAULT_COLOR_CYCLE; From 1240c1a76d12b8d9643af799a755b57b09d7b5ec Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Sun, 26 Jul 2026 01:55:23 -0700 Subject: [PATCH 21/75] fix(proxy): close the adversarial-review findings on the tool spend rollup Three fixes from an adversarial review of this branch, each at the owning seam rather than the report site. The flush retried DB_CONNECTION_ERROR_TYPES, which includes ReadTimeout. A ReadTimeout is the committed-but-unacked case: the review reproduced the engine abandoning the transaction open on the pooled connection, the retry stacking its statements into it, and one commit applying both increment sets while the flush reports success. The retry now covers only ConnectError, the one failure that proves the statements never reached the database; post-send failures drop the batch with an error log. The docstring no longer claims an idempotency the pattern does not have. The same hazard exists in the untouched daily spend writer and is left for its own change. get_tool_calls_from_response read choices[0] only, so a tool invoked in a later choice of an n>1 response earned spend but never reached the rollup, the index, or the registry. Choice scope is now an explicit parameter: accounting passes include_all_choices=True because every choice costs money; guardrails keep the primary-choice default because they rebuild the primary assistant message. First multi-choice fixtures in the suite pin both scopes. maxBarSize=64 had been added to the shared BarChart unconditionally, resizing every existing consumer. It is now a prop; only the tool spend charts opt in. The legend flex-wrap changes stay global because clipping overflow was a defect, not a preference. --- .../prompt_templates/factory.py | 27 +++++++++---- litellm/proxy/db/spend_log_tool_index.py | 21 +++++----- ...llm_core_utils_prompt_templates_factory.py | 31 +++++++++++++++ .../proxy/db/test_db_spend_update_writer.py | 18 +++++++++ .../proxy/db/test_spend_log_tool_index.py | 39 +++++++++++++++++++ .../_components/UsageTab.test.tsx | 6 +++ .../_components/UsageTab.tsx | 2 + .../components/shared/charts/bar_chart.tsx | 6 +-- 8 files changed, 131 insertions(+), 19 deletions(-) diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index c13cf0817b5..4e3d94e2ab3 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -5382,14 +5382,18 @@ def _parse_tool_call_arguments(raw: Any, tool_name: Optional[str], context: str) return parsed if isinstance(parsed, dict) else {} -def _tool_calls_from_chat_completion_response(response: Any) -> list[NormalizedToolCall]: +def _tool_calls_from_chat_completion_response( + response: Any, include_all_choices: bool = False +) -> list[NormalizedToolCall]: choices = get_attribute_or_key(response, "choices", None) if not (isinstance(choices, list) and choices): return [] - message = get_attribute_or_key(choices[0], "message", None) - tool_calls = get_attribute_or_key(message, "tool_calls", None) if message else None - if not isinstance(tool_calls, list): - return [] + tool_calls: list[Any] = [] + for choice in choices if include_all_choices else choices[:1]: + message = get_attribute_or_key(choice, "message", None) + choice_tool_calls = get_attribute_or_key(message, "tool_calls", None) if message else None + if isinstance(choice_tool_calls, list): + tool_calls.extend(choice_tool_calls) result: list[NormalizedToolCall] = [] for tc in tool_calls: fn = get_attribute_or_key(tc, "function", None) @@ -5452,7 +5456,7 @@ def _tool_calls_from_anthropic_messages_response(response: Any) -> list[Normaliz return result -def get_tool_calls_from_response(response: Any) -> list[NormalizedToolCall]: +def get_tool_calls_from_response(response: Any, include_all_choices: bool = False) -> list[NormalizedToolCall]: """ Extract tool/function calls from a response object into a normalized ``{"id", "name", "arguments"}`` shape, regardless of which API surface @@ -5460,11 +5464,20 @@ def get_tool_calls_from_response(response: Any) -> list[NormalizedToolCall]: the Responses API (``output`` items of type ``function_call``), or the Anthropic Messages API (``content`` blocks of type ``tool_use``). + ``include_all_choices`` decides the chat-completions scope: the default + reads only ``choices[0]``, which is what consumers that act on THE reply + (e.g. guardrails rebuilding the primary assistant message) want; usage + accounting passes True because every choice of an ``n>1`` request costs + money and its tool calls really ran. The other surfaces have a single + output, so the flag has no effect on them. + Callers that only care about a specific tool should filter the result by ``name`` themselves -- this returns every tool call found. """ + chat_tool_calls = _tool_calls_from_chat_completion_response(response, include_all_choices=include_all_choices) + if chat_tool_calls: + return chat_tool_calls for extractor in ( - _tool_calls_from_chat_completion_response, _tool_calls_from_responses_api_response, _tool_calls_from_anthropic_messages_response, ): diff --git a/litellm/proxy/db/spend_log_tool_index.py b/litellm/proxy/db/spend_log_tool_index.py index 064d08acb59..a478248b0fa 100644 --- a/litellm/proxy/db/spend_log_tool_index.py +++ b/litellm/proxy/db/spend_log_tool_index.py @@ -18,7 +18,7 @@ from datetime import datetime, timezone from itertools import groupby from typing import TYPE_CHECKING, Any, Sequence -from litellm.proxy._types import DB_CONNECTION_ERROR_TYPES +import httpx if TYPE_CHECKING: from litellm.proxy.utils import PrismaClient @@ -37,7 +37,8 @@ class ToolUsageTransaction: def response_tool_call_names(completion_response: Any) -> tuple[str, ...]: """Tool names invoked in a completion response, in call order, for any response surface get_tool_calls_from_response understands (chat completions, Responses - API output items, Anthropic Messages tool_use blocks).""" + API output items, Anthropic Messages tool_use blocks). Reads every choice of + an ``n>1`` chat response: each choice cost money and its tool calls ran.""" if completion_response is None or isinstance(completion_response, Exception): return () from litellm.litellm_core_utils.prompt_templates.factory import ( @@ -46,7 +47,7 @@ def response_tool_call_names(completion_response: Any) -> tuple[str, ...]: return tuple( stripped - for tool_call in get_tool_calls_from_response(completion_response) + for tool_call in get_tool_calls_from_response(completion_response, include_all_choices=True) if isinstance(name := tool_call.get("name"), str) and (stripped := name.strip()) ) @@ -97,11 +98,13 @@ async def flush_tool_usage_transactions( n_retry_times: int = 3, ) -> None: """Write index rows and rollup upserts for a drained queue batch in one - transaction. Connection errors are retried with backoff, which cannot - double-count because a failed batch commits nothing; every other error - propagates so the caller drops the batch. Callers must not add their own - retry around this function: a batch that DID commit must never run again, - since the rollup update increments counters.""" + transaction. Retries only ConnectError, the one failure that proves the + statements never reached the database. Post-send failures (Read timeouts + and errors) are ambiguous and are NOT retried: the engine can abandon the + transaction open on the pooled connection, so a retry's statements stack + into the same transaction and one commit applies both increment sets. + Ambiguous failures drop the batch; the caller logs it at error. Callers + must not add their own retry around this function.""" if not transactions: return @@ -141,7 +144,7 @@ async def flush_tool_usage_transactions( }, ) return - except DB_CONNECTION_ERROR_TYPES: + except httpx.ConnectError: if attempt >= n_retry_times: raise await asyncio.sleep(2**attempt + random.uniform(0, 1)) diff --git a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py index bcda88ea609..9565de1139c 100644 --- a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py +++ b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py @@ -3166,3 +3166,34 @@ async def test_bedrock_converse_message_level_cache_point_preserves_ttl_async(): ) assert _collect_cache_points(result) == [{"type": "default", "ttl": "1h"}] + + +def _n_choices_response(*names_per_choice): + from types import SimpleNamespace + + choices = [ + SimpleNamespace( + message=SimpleNamespace( + tool_calls=[SimpleNamespace(id=f"c{i}", function=SimpleNamespace(name=name, arguments="{}"))] + ) + ) + for i, name in enumerate(names_per_choice) + ] + return SimpleNamespace(choices=choices) + + +def test_get_tool_calls_from_response_defaults_to_primary_choice_only(): + from litellm.litellm_core_utils.prompt_templates.factory import get_tool_calls_from_response + + response = _n_choices_response("tool_alpha", "tool_beta") + + assert [tc["name"] for tc in get_tool_calls_from_response(response)] == ["tool_alpha"] + + +def test_get_tool_calls_from_response_include_all_choices_reads_every_choice(): + from litellm.litellm_core_utils.prompt_templates.factory import get_tool_calls_from_response + + response = _n_choices_response("tool_alpha", "tool_beta") + + names = [tc["name"] for tc in get_tool_calls_from_response(response, include_all_choices=True)] + assert names == ["tool_alpha", "tool_beta"] diff --git a/tests/test_litellm/proxy/db/test_db_spend_update_writer.py b/tests/test_litellm/proxy/db/test_db_spend_update_writer.py index 8759b008549..cd293325c15 100644 --- a/tests/test_litellm/proxy/db/test_db_spend_update_writer.py +++ b/tests/test_litellm/proxy/db/test_db_spend_update_writer.py @@ -181,6 +181,24 @@ async def test_update_database_enqueues_realtime_tool_usage(): assert prisma.tool_usage_transactions[0].tool_names == ("rt_tool",) +def test_enqueue_tool_registry_upsert_reads_every_choice(): + from types import SimpleNamespace as NS + + db_writer = DBSpendUpdateWriter() + db_writer.tool_discovery_queue = MagicMock() + response = NS( + choices=[ + NS(message=NS(tool_calls=[NS(function=NS(name="tool_alpha"))])), + NS(message=NS(tool_calls=[NS(function=NS(name="tool_beta"))])), + ] + ) + + db_writer._enqueue_tool_registry_upsert(kwargs={}, completion_response=response) + + enqueued = [call.args[0]["tool_name"] for call in db_writer.tool_discovery_queue.add_update.call_args_list] + assert enqueued == ["tool_alpha", "tool_beta"] + + @pytest.mark.asyncio async def test_update_database_skips_tool_usage_when_spend_logs_disabled(): db_writer = DBSpendUpdateWriter() diff --git a/tests/test_litellm/proxy/db/test_spend_log_tool_index.py b/tests/test_litellm/proxy/db/test_spend_log_tool_index.py index 3b6acaa1eb3..71073fd216e 100644 --- a/tests/test_litellm/proxy/db/test_spend_log_tool_index.py +++ b/tests/test_litellm/proxy/db/test_spend_log_tool_index.py @@ -134,6 +134,29 @@ class TestBuildToolUsageTransaction: assert transaction is not None assert transaction.tool_names == ("get_weather",) + def test_n_greater_than_one_tools_from_every_choice_reach_the_transaction(self): + # Regression: an n>1 request pays for every choice, and a tool invoked + # only in a later choice really ran; it must not be dropped because the + # extractor read choices[0] alone. + from types import SimpleNamespace as NS + + response = NS( + choices=[ + NS(message=NS(tool_calls=[NS(function=NS(name="tool_alpha"))])), + NS(message=NS(tool_calls=[NS(function=NS(name="tool_beta"))])), + ] + ) + transaction = build_tool_usage_transaction( + request_id="r1", + start_time_iso="2026-07-25T10:00:00+00:00", + mcp_namespaced_tool_name=None, + spend=0.5, + total_tokens=100, + completion_response=response, + ) + assert transaction is not None + assert transaction.tool_names == ("tool_alpha", "tool_beta") + def test_unparseable_start_time_returns_none(self): assert ( build_tool_usage_transaction( @@ -307,3 +330,19 @@ class TestFlushToolUsageTransactions: with pytest.raises(ValueError): await flush_tool_usage_transactions(prisma_client=prisma, transactions=[_transaction("r1")]) prisma.db.batch_.assert_called_once() + + @pytest.mark.asyncio + @pytest.mark.parametrize("ambiguous_error", ["ReadTimeout", "ReadError"]) + async def test_post_send_ambiguous_errors_drop_without_retry(self, ambiguous_error): + # A ReadTimeout means the statements were sent and the outcome is + # unknown; the engine can leave the transaction open on the pooled + # connection, so a retry's statements would stack into it and one + # commit would apply both increment sets. These must never retry. + import httpx + + error = getattr(httpx, ambiguous_error)("ambiguous") + prisma = MagicMock() + prisma.db.batch_ = MagicMock(side_effect=error) + with pytest.raises((httpx.ReadTimeout, httpx.ReadError)): + await flush_tool_usage_transactions(prisma_client=prisma, transactions=[_transaction("r1")]) + prisma.db.batch_.assert_called_once() diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.test.tsx index beeb9466b1d..5c26ac30477 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.test.tsx @@ -28,17 +28,20 @@ vi.mock("@/components/shared/charts", () => ({ categories, colors, showLegend, + maxBarSize, }: { data: unknown; categories: string[]; colors?: readonly string[]; showLegend?: boolean; + maxBarSize?: number; }) => (
), @@ -240,6 +243,9 @@ describe("UsageTab", () => { const bars = await findAllByTestId("bar-chart"); const series = JSON.parse(bars[0].getAttribute("data-series") ?? "[]"); expect(series[0]).toMatchObject({ tool_name: "search", spend: 4.0 }); + // The 64px bar cap is this card's opt-in; the shared BarChart must not cap + // by default (other consumers keep their pre-existing geometry). + expect(bars[0].getAttribute("data-max-bar-size")).toBe("64"); }); it("renders the tool legend once outside the charts, with both charts sharing the tool colors", async () => { diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.tsx index 829a79d7638..ec37418e0b5 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/UsageTab.tsx @@ -282,6 +282,7 @@ const UsageTab: React.FC = ({ accessToken, activity }) => { colorByDatum layout="vertical" yAxisWidth={140} + maxBarSize={64} showLegend={false} valueFormatter={usd} /> @@ -295,6 +296,7 @@ const UsageTab: React.FC = ({ accessToken, activity }) => { categories={topToolNames} colors={toolColors} stack + maxBarSize={64} valueFormatter={usd} showLegend={false} /> diff --git a/ui/litellm-dashboard/src/components/shared/charts/bar_chart.tsx b/ui/litellm-dashboard/src/components/shared/charts/bar_chart.tsx index 6bfcf14c2a0..ab2cc66eaf4 100644 --- a/ui/litellm-dashboard/src/components/shared/charts/bar_chart.tsx +++ b/ui/litellm-dashboard/src/components/shared/charts/bar_chart.tsx @@ -7,14 +7,13 @@ import { cn } from "@/lib/cva.config"; import { ValueTooltip, type ChartTooltipComponent } from "./chart_tooltip"; import { categoryFills, type ChartColor } from "./colors"; -const MAX_BAR_SIZE = 64; - export type BarChartProps> = { data: readonly TDatum[]; index: string; categories: readonly string[]; colors?: readonly ChartColor[]; colorByDatum?: boolean; + maxBarSize?: number; valueFormatter?: (value: number) => string; stack?: boolean; layout?: "horizontal" | "vertical"; @@ -36,6 +35,7 @@ export function BarChart>({ categories, colors, colorByDatum = false, + maxBarSize, valueFormatter, stack = false, layout = "horizontal", @@ -119,7 +119,7 @@ export function BarChart>({ fill={fills[i]} stackId={stack ? "stack" : undefined} isAnimationActive={false} - maxBarSize={MAX_BAR_SIZE} + maxBarSize={maxBarSize} onClick={ onValueChange ? (item: { payload?: TDatum }) => { From 33fadd70a3b6d5711baaa86a31081710540f38e3 Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Mon, 27 Jul 2026 11:22:06 -0700 Subject: [PATCH 22/75] fix(guardrails): compress content-parts messages in headroom guardrail Anthropic-format requests translate to messages whose content is a list of part dicts, which the headroom compression service's transforms silently skip (they only rewrite string content), so compression never applied to Anthropic client traffic while the guardrail still reported itself as applied. Flatten all-text part lists to plain strings for /v1/compress and restore the original shapes from the response: untouched rows keep their exact original parts, a rewritten row collapses to one part carrying the last declared cache_control breakpoint (a breakpoint caches the prefix ending at its part, so the last one and its TTL still describe the merged row). Rows with any non-text part are never flattened, since merging text across a non-text part would move a later breakpoint to the other side of it; they pass through the service untouched, matching its own behavior for non-string content. Flattening and write-back use the shared content_text helpers that compresr's breakpoint fix also uses. Resolves LIT-4795 Co-Authored-By: Claude Fable 5 --- .../guardrail_hooks/headroom/headroom.py | 62 ++++- .../guardrail_hooks/test_headroom.py | 231 ++++++++++++++++++ 2 files changed, 292 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py b/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py index 7b166185865..2735acd7787 100644 --- a/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py +++ b/litellm/proxy/guardrails/guardrail_hooks/headroom/headroom.py @@ -28,6 +28,11 @@ from litellm.llms.custom_httpx.http_handler import ( get_async_httpx_client, # pyright: ignore[reportUnknownVariableType] httpxSpecialProvider, ) +from litellm.proxy.guardrails.guardrail_hooks.content_text import ( + content_to_text, + is_all_text_parts, + merge_rewritten_text_parts, +) from litellm.secret_managers.main import get_secret_str from litellm.types.guardrails import GuardrailEventHooks, Mode from litellm.types.integrations.custom_logger import AgenticLoopPlan, AgenticLoopRequestPatch @@ -51,6 +56,60 @@ def _is_object_list(value: object) -> TypeGuard[list[object]]: # guard-ok: isin return isinstance(value, list) +def _flatten_messages_for_compression(messages: list[dict[str, object]]) -> list[dict[str, object]]: + """Collapse all-text list-of-parts content to plain strings for /v1/compress. + + The compression service's transforms only rewrite string content and skip + the OpenAI list-of-parts shape, which is what every Anthropic-format + request translates to. Only rows whose parts are ALL text are flattened: + cache_control breakpoints are positional (each caches the prefix ending + at its part), so merging text across a non-text part would move a later + breakpoint to the other side of it. Rows with non-text parts are sent + unchanged and pass through the service untouched. + """ + flattened: list[dict[str, object]] = [] + for msg in messages: + content = msg.get("content") + if is_all_text_parts(content): + text = content_to_text(content) + if text: + flattened.append({**msg, "content": text}) + continue + flattened.append(msg) + return flattened + + +def _restore_content_shapes( + originals: list[dict[str, object]], returned: list[dict[str, object]] +) -> list[dict[str, object]]: + """Write compressed text back into each original row's content shape. + + Rows are matched positionally; the pairing is only trusted when the + service kept the row count and every role lines up. If it restructured + the conversation (e.g. dropped rows), its output is adopted as-is, which + is the pre-flattening behavior. + """ + if len(returned) != len(originals): + return returned + for orig, ret in zip(originals, returned): + if orig.get("role") != ret.get("role"): + return returned + restored: list[dict[str, object]] = [] + for orig, ret in zip(originals, returned): + orig_content = orig.get("content") + ret_content = ret.get("content") + if isinstance(orig_content, list) and isinstance(ret_content, str): + if ret_content == content_to_text(orig_content): + # Untouched row: keep the exact original parts, including + # per-part fields like cache_control on later text parts. + restored.append({**ret, "content": orig_content}) + else: + restored.append({**ret, "content": merge_rewritten_text_parts(orig_content, ret_content)}) + else: + restored.append(ret) + return restored + + def extract_hashes_from_messages(messages: list[dict[str, object]]) -> list[str]: hashes: list[str] = [] for msg in messages: @@ -491,10 +550,11 @@ class HeadroomGuardrail(CustomGuardrail): model = self.headroom_model or request_data.get("model") start_time = time.time() compressed, compression_succeeded, stats = await self._call_compress( - messages=messages, + messages=_flatten_messages_for_compression(messages), model=model if isinstance(model, str) else None, ) end_time = time.time() + compressed = _restore_content_shapes(originals=messages, returned=compressed) from litellm.proxy.common_utils.callback_utils import ( add_guardrail_to_applied_guardrails_header, diff --git a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py index 4dc527ca45d..248893ed153 100644 --- a/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py +++ b/tests/test_litellm/proxy/guardrails/guardrail_hooks/test_headroom.py @@ -1551,3 +1551,234 @@ async def test_apply_guardrail_litellm_timeout_fail_open_forwards_uncompressed() ) assert result["structured_messages"] == ORIGINAL_MESSAGES + + + + +# --------------------------------------------------------------------------- +# Content-parts flattening (LIT-4795) +# +# Anthropic-format requests translate to messages whose content is a list of +# part dicts. The compression service only rewrites string content, so the +# guardrail flattens ALL-TEXT part lists on the wire and restores the +# original shapes afterwards. Rows with non-text parts are never flattened: +# cache_control breakpoints are positional, and merging text across a +# non-text part would move a later breakpoint to the other side of it. +# --------------------------------------------------------------------------- + +PARTS_MESSAGES = [ + { + "role": "system", + "content": [ + {"type": "text", "text": "You are Claude Code.", "cache_control": {"type": "ephemeral"}}, + { + "type": "text", + "text": "Second system block. " + "B" * 5000, + "cache_control": {"type": "ephemeral", "ttl": "1h"}, + }, + ], + }, + { + "role": "user", + "content": [ + {"type": "text", "text": "Mixed row text."}, + {"type": "image_url", "image_url": {"url": "https://example.com/x.png"}}, + ], + }, + {"role": "tool", "content": "tool output " + "C" * 500}, +] + +FLATTENED_SYSTEM_TEXT = "You are Claude Code.\n\nSecond system block. " + "B" * 5000 + + +def _parts_copy() -> list: + return json.loads(json.dumps(PARTS_MESSAGES)) + + +def _echo_wire_view() -> list: + """What the service receives (and echoes back when it changes nothing).""" + return [ + {"role": "system", "content": FLATTENED_SYSTEM_TEXT}, + json.loads(json.dumps(PARTS_MESSAGES[1])), + {"role": "tool", "content": "tool output " + "C" * 500}, + ] + + +@pytest.mark.asyncio +async def test_apply_guardrail_flattens_all_text_rows_only( + guardrail: HeadroomGuardrail, +): + inputs = GenericGuardrailAPIInputs( + texts=["B" * 5000], + structured_messages=_parts_copy(), + ) + mock_response = _make_compress_response(_echo_wire_view()) + + with patch.object( + guardrail.async_handler, + "post", + new_callable=AsyncMock, + return_value=mock_response, + ) as mock_post: + await guardrail.apply_guardrail( + inputs=inputs, + request_data={"model": "claude-fable-5"}, + input_type="request", + ) + + wire_messages = mock_post.call_args.kwargs["json"]["messages"] + assert wire_messages[0]["content"] == FLATTENED_SYSTEM_TEXT + # Mixed text+image row is never flattened: merging its text would move a + # later cache_control breakpoint across the image part. + assert isinstance(wire_messages[1]["content"], list) + assert wire_messages[2]["content"] == "tool output " + "C" * 500 + + +@pytest.mark.asyncio +async def test_apply_guardrail_restores_rewritten_all_text_row( + guardrail: HeadroomGuardrail, +): + inputs = GenericGuardrailAPIInputs( + texts=["B" * 5000], + structured_messages=_parts_copy(), + ) + compressed = _echo_wire_view() + compressed[0]["content"] = "compressed system. Retrieve more: hash=b573993006976af767214fac" + mock_response = _make_compress_response(compressed) + + with patch.object( + guardrail.async_handler, + "post", + new_callable=AsyncMock, + return_value=mock_response, + ): + result = await guardrail.apply_guardrail( + inputs=inputs, + request_data={"model": "claude-fable-5"}, + input_type="request", + ) + + messages = result["structured_messages"] + system_content = messages[0]["content"] + # Rewritten all-text row collapses to one part carrying the LAST declared + # breakpoint: an Anthropic breakpoint caches the prefix ending at its + # part, so after the merge the last one (and its TTL) still describes the + # row. + assert isinstance(system_content, list) + assert len(system_content) == 1 + assert system_content[0]["text"] == "compressed system. Retrieve more: hash=b573993006976af767214fac" + assert system_content[0]["cache_control"] == {"type": "ephemeral", "ttl": "1h"} + # Mixed row passes through byte-identical. + assert messages[1]["content"] == PARTS_MESSAGES[1]["content"] + # Hashes inside restored parts still drive retrieve-tool injection. + assert has_headroom_retrieve_tool(result.get("tools") or []) + + +@pytest.mark.asyncio +async def test_apply_guardrail_keeps_originals_when_service_echoes_unchanged( + guardrail: HeadroomGuardrail, +): + inputs = GenericGuardrailAPIInputs( + texts=["B" * 5000], + structured_messages=_parts_copy(), + ) + mock_response = _make_compress_response(_echo_wire_view()) + + with patch.object( + guardrail.async_handler, + "post", + new_callable=AsyncMock, + return_value=mock_response, + ): + result = await guardrail.apply_guardrail( + inputs=inputs, + request_data={"model": "claude-fable-5"}, + input_type="request", + ) + + messages = result["structured_messages"] + assert [m["content"] for m in messages] == [m["content"] for m in PARTS_MESSAGES] + + +@pytest.mark.asyncio +async def test_apply_guardrail_adopts_service_output_when_rows_dropped( + guardrail: HeadroomGuardrail, +): + inputs = GenericGuardrailAPIInputs( + texts=["B" * 5000], + structured_messages=_parts_copy(), + ) + dropped = [ + {"role": "system", "content": FLATTENED_SYSTEM_TEXT}, + {"role": "user", "content": "B" * 50}, + ] + mock_response = _make_compress_response(dropped) + + with patch.object( + guardrail.async_handler, + "post", + new_callable=AsyncMock, + return_value=mock_response, + ): + result = await guardrail.apply_guardrail( + inputs=inputs, + request_data={"model": "claude-fable-5"}, + input_type="request", + ) + + assert result["structured_messages"] == dropped + + +@pytest.mark.asyncio +async def test_apply_guardrail_sends_textless_parts_rows_unflattened( + guardrail: HeadroomGuardrail, +): + image_only = [ + {"role": "user", "content": [{"type": "image_url", "image_url": {"url": "https://example.com/x.png"}}]}, + {"role": "user", "content": "D" * 5000}, + ] + inputs = GenericGuardrailAPIInputs( + texts=["D" * 5000], + structured_messages=json.loads(json.dumps(image_only)), + ) + mock_response = _make_compress_response(json.loads(json.dumps(image_only))) + + with patch.object( + guardrail.async_handler, + "post", + new_callable=AsyncMock, + return_value=mock_response, + ) as mock_post: + await guardrail.apply_guardrail( + inputs=inputs, + request_data={"model": "claude-fable-5"}, + input_type="request", + ) + + wire_messages = mock_post.call_args.kwargs["json"]["messages"] + assert isinstance(wire_messages[0]["content"], list) + assert wire_messages[1]["content"] == "D" * 5000 + + +@pytest.mark.asyncio +async def test_fail_open_returns_original_parts_shapes(): + guardrail = _make_guardrail(unreachable_fallback="fail_open") + inputs = GenericGuardrailAPIInputs( + texts=["B" * 5000], + structured_messages=_parts_copy(), + ) + + with patch.object( + guardrail.async_handler, + "post", + new_callable=AsyncMock, + side_effect=httpx.ConnectError("boom"), + ): + result = await guardrail.apply_guardrail( + inputs=inputs, + request_data={}, + input_type="request", + ) + + messages = result["structured_messages"] + assert [m["content"] for m in messages] == [m["content"] for m in PARTS_MESSAGES] From 2e12614a5b1f175d602e33ab079a4c0fb8d36c94 Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Mon, 27 Jul 2026 11:13:49 -0700 Subject: [PATCH 23/75] fix(proxy): stop retrying post-send ambiguous DB errors in every spend writer Resolves LIT-4823. An adversarial review reproduced against real Postgres that a batched increment upsert stalling past the prisma engine timeout leaves its transaction open on the pooled connection; the retry draws the same connection, its statements stack into the still-open transaction, and one commit applies both increment sets while the writer reports success. httpx.ReadTimeout is exactly that post-send case and every spend writer retried it. DB_RETRY_SAFE_ERROR_TYPES (ConnectError only, the failure that proves the statements never reached the database) is now the single owner of what a non-idempotent writer may retry. All seven entity and daily spend writer retry arms and the tool usage flush consume it. DB_CONNECTION_ERROR_TYPES is unchanged for the idempotent spend-log writer, whose create_many with skip_duplicates may safely retry the full tuple. The corruption was reproduced on update_daily_user_spend (seeded 10|100|1, expected 11|110|2, observed 12|120|3); the new policy tests pin that a ReadTimeout drops the batch loudly on the first attempt and a ConnectError still retries. --- litellm/proxy/_types.py | 7 ++ litellm/proxy/db/db_spend_update_writer.py | 16 ++-- litellm/proxy/db/spend_log_tool_index.py | 4 +- litellm/proxy/utils.py | 3 +- .../proxy/db/test_db_spend_update_writer.py | 78 +++++++++++++++++++ .../test_proxy_update_spend.py | 35 +++++++-- 6 files changed, 126 insertions(+), 17 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 7575091be54..b94a34fa14c 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -3638,6 +3638,13 @@ DB_CONNECTION_ERROR_TYPES = ( httpx.ReadTimeout, ) +# What a NON-IDEMPOTENT write (increment upsert) may retry: only ConnectError +# proves the statements never reached the database. Post-send errors are +# ambiguous; a stalled statement can leave its transaction open on the pooled +# connection, where a retry stacks a second increment set into the same commit. +# Idempotent writes (create_many with skip_duplicates) may retry the full tuple. +DB_RETRY_SAFE_ERROR_TYPES = (httpx.ConnectError,) + class SSOUserDefinedValues(TypedDict): models: List[str] diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py index ebdb08a681a..fd8132fef22 100644 --- a/litellm/proxy/db/db_spend_update_writer.py +++ b/litellm/proxy/db/db_spend_update_writer.py @@ -34,7 +34,7 @@ from litellm.constants import ( ) from litellm.litellm_core_utils.safe_json_loads import safe_json_loads from litellm.proxy._types import ( - DB_CONNECTION_ERROR_TYPES, + DB_RETRY_SAFE_ERROR_TYPES, BaseDailySpendTransaction, DailyAgentSpendTransaction, DailyEndUserSpendTransaction, @@ -1121,7 +1121,7 @@ class DBSpendUpdateWriter: data={"spend": {"increment": response_cost}}, ) break - except DB_CONNECTION_ERROR_TYPES as e: + except DB_RETRY_SAFE_ERROR_TYPES as e: if i >= n_retry_times: # If we've reached the maximum number of retries _raise_failed_update_spend_exception( e=e, @@ -1164,7 +1164,7 @@ class DBSpendUpdateWriter: }, ) break - except DB_CONNECTION_ERROR_TYPES as e: + except DB_RETRY_SAFE_ERROR_TYPES as e: if i >= n_retry_times: # If we've reached the maximum number of retries _raise_failed_update_spend_exception( e=e, @@ -1197,7 +1197,7 @@ class DBSpendUpdateWriter: data={"spend": {"increment": response_cost}}, ) break - except DB_CONNECTION_ERROR_TYPES as e: + except DB_RETRY_SAFE_ERROR_TYPES as e: if i >= n_retry_times: # If we've reached the maximum number of retries _raise_failed_update_spend_exception( e=e, @@ -1244,7 +1244,7 @@ class DBSpendUpdateWriter: ) # Transaction succeeded, break out of retry loop break - except DB_CONNECTION_ERROR_TYPES as e: + except DB_RETRY_SAFE_ERROR_TYPES as e: if i >= n_retry_times: # If we've reached the maximum number of retries _raise_failed_update_spend_exception( e=e, @@ -1286,7 +1286,7 @@ class DBSpendUpdateWriter: data={"spend": {"increment": response_cost}}, ) break - except DB_CONNECTION_ERROR_TYPES as e: + except DB_RETRY_SAFE_ERROR_TYPES as e: if i >= n_retry_times: # If we've reached the maximum number of retries _raise_failed_update_spend_exception( e=e, @@ -1372,7 +1372,7 @@ class DBSpendUpdateWriter: data={"spend": {"increment": response_cost}}, ) break - except DB_CONNECTION_ERROR_TYPES as e: + except DB_RETRY_SAFE_ERROR_TYPES as e: if i >= n_retry_times: _raise_failed_update_spend_exception( e=e, @@ -1669,7 +1669,7 @@ class DBSpendUpdateWriter: break - except DB_CONNECTION_ERROR_TYPES as e: + except DB_RETRY_SAFE_ERROR_TYPES as e: if i >= n_retry_times: _raise_failed_update_spend_exception( e=e, diff --git a/litellm/proxy/db/spend_log_tool_index.py b/litellm/proxy/db/spend_log_tool_index.py index a478248b0fa..802e893d473 100644 --- a/litellm/proxy/db/spend_log_tool_index.py +++ b/litellm/proxy/db/spend_log_tool_index.py @@ -18,7 +18,7 @@ from datetime import datetime, timezone from itertools import groupby from typing import TYPE_CHECKING, Any, Sequence -import httpx +from litellm.proxy._types import DB_RETRY_SAFE_ERROR_TYPES if TYPE_CHECKING: from litellm.proxy.utils import PrismaClient @@ -144,7 +144,7 @@ async def flush_tool_usage_transactions( }, ) return - except httpx.ConnectError: + except DB_RETRY_SAFE_ERROR_TYPES: if attempt >= n_retry_times: raise await asyncio.sleep(2**attempt + random.uniform(0, 1)) diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index d7a95284818..924189fed4b 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -41,6 +41,7 @@ from litellm.constants import ( ) from litellm.proxy._types import ( DB_CONNECTION_ERROR_TYPES, + DB_RETRY_SAFE_ERROR_TYPES, CommonProxyErrors, ProxyErrorTypes, ProxyException, @@ -5337,7 +5338,7 @@ class ProxyUpdateSpend: ) break - except DB_CONNECTION_ERROR_TYPES as e: + except DB_RETRY_SAFE_ERROR_TYPES as e: if i >= n_retry_times: # If we've reached the maximum number of retries _raise_failed_update_spend_exception( e=e, start_time=start_time, proxy_logging_obj=proxy_logging_obj diff --git a/tests/test_litellm/proxy/db/test_db_spend_update_writer.py b/tests/test_litellm/proxy/db/test_db_spend_update_writer.py index cd293325c15..191080e3a48 100644 --- a/tests/test_litellm/proxy/db/test_db_spend_update_writer.py +++ b/tests/test_litellm/proxy/db/test_db_spend_update_writer.py @@ -308,6 +308,84 @@ async def test_update_daily_spend_with_null_entity_id(): assert create_data["failed_requests"] == 0 +def _daily_txn(user_id: str = "user1") -> dict: + return { + "user_id": user_id, + "date": "2024-01-01", + "api_key": "test-api-key", + "model": "gpt-4", + "custom_llm_provider": "openai", + "prompt_tokens": 10, + "completion_tokens": 20, + "spend": 0.1, + "api_requests": 1, + "successful_requests": 1, + "failed_requests": 0, + } + + +@pytest.mark.asyncio +async def test_update_daily_spend_does_not_retry_post_send_ambiguous_errors(): + # Regression for the double-apply hazard: a ReadTimeout means the batch was + # sent and its outcome is unknown; the engine can leave the transaction open + # on the pooled connection, so retrying stacks a second set of increments + # into it and one commit applies both. Post-send failures must drop the + # batch (loudly), never retry it. + import httpx + + mock_prisma_client = MagicMock() + mock_prisma_client.db.batch_ = MagicMock(side_effect=httpx.ReadTimeout("ambiguous")) + proxy_logging = MagicMock() + proxy_logging.failure_handler = AsyncMock() + + with pytest.raises(httpx.ReadTimeout): + await DBSpendUpdateWriter._update_daily_spend( + n_retry_times=3, + prisma_client=mock_prisma_client, + proxy_logging_obj=proxy_logging, + daily_spend_transactions={"k1": _daily_txn()}, + entity_type="user", + entity_id_field="user_id", + table_name="litellm_dailyuserspend", + unique_constraint_name="user_id_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name_endpoint", + ) + + mock_prisma_client.db.batch_.assert_called_once() + + +@pytest.mark.asyncio +async def test_update_daily_spend_retries_connect_errors(monkeypatch): + # ConnectError proves the statements never reached the database, so it is + # the one failure the writer may retry. + import httpx + + mock_batcher = MagicMock() + good_ctx = MagicMock() + good_ctx.__aenter__ = AsyncMock(return_value=mock_batcher) + good_ctx.__aexit__ = AsyncMock(return_value=None) + mock_prisma_client = MagicMock() + mock_prisma_client.db.batch_ = MagicMock(side_effect=[httpx.ConnectError("down"), good_ctx]) + proxy_logging = MagicMock() + proxy_logging.failure_handler = AsyncMock() + + async def fake_sleep(seconds: float) -> None: + return None + + monkeypatch.setattr("litellm.proxy.db.db_spend_update_writer.asyncio.sleep", fake_sleep) + await DBSpendUpdateWriter._update_daily_spend( + n_retry_times=3, + prisma_client=mock_prisma_client, + proxy_logging_obj=proxy_logging, + daily_spend_transactions={"k1": _daily_txn()}, + entity_type="user", + entity_id_field="user_id", + table_name="litellm_dailyuserspend", + unique_constraint_name="user_id_date_api_key_model_custom_llm_provider_mcp_namespaced_tool_name_endpoint", + ) + + assert mock_prisma_client.db.batch_.call_count == 2 + + @pytest.mark.asyncio async def test_update_daily_spend_sorting(): """ diff --git a/tests/test_litellm/proxy/utils/prisma_and_spend/test_proxy_update_spend.py b/tests/test_litellm/proxy/utils/prisma_and_spend/test_proxy_update_spend.py index d5d4de7f2cf..f075acc7307 100644 --- a/tests/test_litellm/proxy/utils/prisma_and_spend/test_proxy_update_spend.py +++ b/tests/test_litellm/proxy/utils/prisma_and_spend/test_proxy_update_spend.py @@ -68,12 +68,12 @@ async def test_update_end_user_spend_upserts_each_end_user( @pytest.mark.asyncio -async def test_update_end_user_spend_retries_on_connection_error( +async def test_update_end_user_spend_retries_on_connect_error( mock_prisma_client: Any, monkeypatch: pytest.MonkeyPatch ) -> None: - """``DB_CONNECTION_ERROR_TYPES`` failures should be retried with backoff; - once retries are exhausted, ``_raise_failed_update_spend_exception`` is - invoked and the original exception bubbles up. + """``DB_RETRY_SAFE_ERROR_TYPES`` (ConnectError, statements provably never + sent) retries with backoff; once retries are exhausted the original + exception bubbles up via ``_raise_failed_update_spend_exception``. """ import httpx import litellm.proxy.utils as utils_mod @@ -85,11 +85,11 @@ async def test_update_end_user_spend_retries_on_connection_error( monkeypatch.setattr(utils_mod.asyncio, "sleep", _fake_sleep) - err = httpx.ReadError("conn reset") + err = httpx.ConnectError("down") mock_prisma_client.db.tx = MagicMock(side_effect=err) proxy_logging = MagicMock() proxy_logging.failure_handler = AsyncMock() - with pytest.raises(httpx.ReadError): + with pytest.raises(httpx.ConnectError): await ProxyUpdateSpend.update_end_user_spend( n_retry_times=1, prisma_client=mock_prisma_client, @@ -99,6 +99,29 @@ async def test_update_end_user_spend_retries_on_connection_error( assert sleeps == [1.0] +@pytest.mark.asyncio +@pytest.mark.parametrize("ambiguous_error_name", ["ReadTimeout", "ReadError"]) +async def test_update_end_user_spend_does_not_retry_post_send_ambiguous_errors( + mock_prisma_client: Any, ambiguous_error_name: str +) -> None: + """Post-send errors are ambiguous and retrying can double-apply increments + (see DB_RETRY_SAFE_ERROR_TYPES); they must raise on the first attempt.""" + import httpx + + err = getattr(httpx, ambiguous_error_name)("ambiguous") + mock_prisma_client.db.tx = MagicMock(side_effect=err) + proxy_logging = MagicMock() + proxy_logging.failure_handler = AsyncMock() + with pytest.raises((httpx.ReadTimeout, httpx.ReadError)): + await ProxyUpdateSpend.update_end_user_spend( + n_retry_times=3, + prisma_client=mock_prisma_client, + proxy_logging_obj=proxy_logging, + end_user_list_transactions={"u": 1.0}, + ) + mock_prisma_client.db.tx.assert_called_once() + + @pytest.mark.asyncio async def test_update_end_user_spend_non_connection_error_raises_immediately( mock_prisma_client: Any, From 4556dfa930c46585ccfa03e9d212386323044b69 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Mon, 27 Jul 2026 12:11:17 -0700 Subject: [PATCH 24/75] ci: publish a generated JSON schema for model_prices_and_context_window.json --- .../auto_update_price_and_context_window.yml | 5 +- .github/workflows/test-model-map.yaml | 9 + ci_cd/generate_model_prices_schema.py | 322 ++++++++ model_prices_and_context_window.schema.json | 741 ++++++++++++++++++ .../test_litellm/test_model_prices_schema.py | 85 ++ 5 files changed, 1161 insertions(+), 1 deletion(-) create mode 100644 ci_cd/generate_model_prices_schema.py create mode 100644 model_prices_and_context_window.schema.json create mode 100644 tests/test_litellm/test_model_prices_schema.py diff --git a/.github/workflows/auto_update_price_and_context_window.yml b/.github/workflows/auto_update_price_and_context_window.yml index 1a638a4a331..d391c0bd6ce 100644 --- a/.github/workflows/auto_update_price_and_context_window.yml +++ b/.github/workflows/auto_update_price_and_context_window.yml @@ -24,9 +24,12 @@ jobs: - name: Update JSON Data run: | uv run --frozen --with 'aiohttp==3.13.3' python ".github/workflows/auto_update_price_and_context_window_file.py" + - name: Regenerate JSON Schema + run: | + uv run --frozen python ci_cd/generate_model_prices_schema.py - name: Create Pull Request run: | - git add model_prices_and_context_window.json + git add model_prices_and_context_window.json model_prices_and_context_window.schema.json git commit -m "Update model_prices_and_context_window.json file: $(date +'%Y-%m-%d')" gh pr create --title "Update model_prices_and_context_window.json file" \ --body "Automated update for model_prices_and_context_window.json" \ diff --git a/.github/workflows/test-model-map.yaml b/.github/workflows/test-model-map.yaml index b2170d9f6a4..cf4b0eb21a1 100644 --- a/.github/workflows/test-model-map.yaml +++ b/.github/workflows/test-model-map.yaml @@ -22,3 +22,12 @@ jobs: - name: Validate model_prices_and_context_window.json run: | jq empty model_prices_and_context_window.json + + - name: Set up uv + uses: ./.github/actions/setup-uv-with-retries + with: + version: "0.10.9" + + - name: Check model_prices_and_context_window.schema.json is in sync + run: | + uv run --frozen python ci_cd/generate_model_prices_schema.py --check diff --git a/ci_cd/generate_model_prices_schema.py b/ci_cd/generate_model_prices_schema.py new file mode 100644 index 00000000000..83c48f788f8 --- /dev/null +++ b/ci_cd/generate_model_prices_schema.py @@ -0,0 +1,322 @@ +from __future__ import annotations + +import json +import sys +from pathlib import Path +from typing import Optional + +import jsonschema + +REPO_ROOT = Path(__file__).parent.parent +PRICES_PATH = REPO_ROOT / "model_prices_and_context_window.json" +SCHEMA_PATH = REPO_ROOT / "model_prices_and_context_window.schema.json" + +SPECIAL_ROOT_KEYS = frozenset({"sample_spec", "fallback_generalizations"}) + +JsonSchema = dict + +NONNEG_NUMBER: JsonSchema = {"type": "number", "minimum": 0} +NONNEG_INTEGER: JsonSchema = {"type": "integer", "minimum": 0} +BOOLEAN: JsonSchema = {"type": "boolean"} +STRING: JsonSchema = {"type": "string"} + +EXTRA_BOOLEAN_KEYS = frozenset( + { + "gemini_native_audio", + "gemini_audio_only_live", + "uses_embed_content", + "use_openai_responses_path", + "bedrock_converse_supports_strict_tools", + } +) + +OBJECT_KEYS: dict[str, JsonSchema] = { + "search_context_cost_per_query": { + "type": "object", + "description": "USD cost per web search query, keyed by search context size.", + "properties": { + "search_context_size_low": NONNEG_NUMBER, + "search_context_size_medium": NONNEG_NUMBER, + "search_context_size_high": NONNEG_NUMBER, + }, + "additionalProperties": False, + }, + "metadata": { + "type": "object", + "description": "Free-form notes about the entry (e.g. pricing derivation).", + }, + "provider_specific_entry": { + "type": "object", + "description": "Provider-internal routing hints (e.g. bedrock_invocation_schema).", + }, +} + +ARRAY_KEYS: dict[str, JsonSchema] = { + "supported_endpoints": { + "type": "array", + "description": "OpenAI-style API routes this model can be called through, e.g. /v1/chat/completions.", + "items": STRING, + }, + "supported_modalities": { + "type": "array", + "description": "Input modalities the model accepts.", + "items": {"type": "string", "enum": ["text", "image", "audio", "video"]}, + }, + "supported_output_modalities": { + "type": "array", + "description": "Output modalities the model can produce.", + "items": {"type": "string", "enum": ["text", "image", "audio", "video", "code"]}, + }, + "supported_regions": { + "type": "array", + "description": "Cloud regions the model is available in ('global' or region ids).", + "items": STRING, + }, + "tiered_pricing": { + "type": "array", + "description": "Context-length or result-count tiered rates; each tier's costs apply within its range.", + "items": { + "type": "object", + "properties": { + "range": { + "type": "array", + "description": "[min, max] prompt-token span this tier applies to.", + "items": NONNEG_NUMBER, + "minItems": 2, + "maxItems": 2, + }, + "max_results_range": { + "type": "array", + "description": "[min, max] result-count span this tier applies to (search models).", + "items": NONNEG_NUMBER, + "minItems": 2, + "maxItems": 2, + }, + "input_cost_per_token": NONNEG_NUMBER, + "output_cost_per_token": NONNEG_NUMBER, + "output_cost_per_reasoning_token": NONNEG_NUMBER, + "cache_read_input_token_cost": NONNEG_NUMBER, + "input_cost_per_query": NONNEG_NUMBER, + }, + "additionalProperties": False, + }, + }, +} + +INTEGER_KEYS: dict[str, JsonSchema] = { + "max_tokens": { + **NONNEG_INTEGER, + "description": "Legacy field: max output tokens if the provider specifies it, else max input tokens.", + }, + "max_input_tokens": { + **NONNEG_INTEGER, + "description": "Maximum prompt/context tokens the model accepts.", + }, + "max_output_tokens": { + **NONNEG_INTEGER, + "description": "Maximum tokens the model can generate in one response.", + }, + "output_vector_size": { + **NONNEG_INTEGER, + "description": "Embedding dimension for embedding models.", + }, + "prompt_cache_min_tokens": { + **NONNEG_INTEGER, + "description": "Smallest prefix the provider will actually cache; absent means the provider default applies.", + }, + "tpm": {**NONNEG_INTEGER, "description": "Provider default tokens-per-minute limit."}, + "rpm": {**NONNEG_INTEGER, "description": "Provider default requests-per-minute limit."}, +} + +NUMBER_KEYS: dict[str, JsonSchema] = { + "regional_processing_uplift_multiplier_eu": { + "type": "number", + "minimum": 1, + "description": "Multiplier applied to all token costs for EU data residency (e.g. 1.10 = +10%).", + }, + "regional_processing_uplift_multiplier_us": { + "type": "number", + "minimum": 1, + "description": "Multiplier applied to all token costs for US data residency (e.g. 1.10 = +10%).", + }, +} + +COST_DESCRIPTIONS: dict[str, str] = { + "input_cost_per_token": "USD per prompt token.", + "output_cost_per_token": "USD per generated token.", + "output_cost_per_reasoning_token": "USD per reasoning/thinking token, when billed separately.", + "cache_creation_input_token_cost": "USD per token written to the provider's prompt cache.", + "cache_read_input_token_cost": "USD per prompt token served from the provider's prompt cache.", + "input_cost_per_token_batches": "USD per prompt token via the provider's batch API.", + "output_cost_per_token_batches": "USD per generated token via the provider's batch API.", +} + + +def cost_description(key: str) -> Optional[str]: + if key in COST_DESCRIPTIONS: + return COST_DESCRIPTIONS[key] + if key.endswith("_flex"): + return "Flex service-tier rate for the same-named base field." + if key.endswith("_priority"): + return "Priority service-tier rate for the same-named base field." + if "_above_" in key: + return "Rate applied once the prompt exceeds the token threshold in the field name." + return None + + +def cost_schema(key: str) -> JsonSchema: + description = cost_description(key) + return {**NONNEG_NUMBER, "description": description} if description else dict(NONNEG_NUMBER) + + +def string_key_schemas(modes: tuple) -> dict[str, JsonSchema]: + return { + "litellm_provider": { + "type": "string", + "description": "LiteLLM provider slug; one of https://docs.litellm.ai/docs/providers.", + }, + "mode": { + "type": "string", + "description": "Primary API surface / task type of the model.", + "enum": list(modes), + }, + "source": { + "type": "string", + "description": "URL of the provider pricing/model page this entry was taken from.", + }, + "deprecation_date": { + "type": "string", + "description": "Date the provider deprecates the model, YYYY-MM-DD.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + }, + "web_search_billing_unit": { + "type": "string", + "description": "Whether web search is billed per query or per prompt.", + "enum": ["per_query", "per_prompt"], + }, + "bedrock_output_config_effort_ceiling": { + "type": "string", + "description": "Highest reasoning effort the Bedrock output_config accepts for this model.", + "enum": ["low", "medium", "high", "max", "xhigh"], + }, + "comment": STRING, + "audio_transcription_config": STRING, + } + + +def classify(key: str, modes: tuple) -> Optional[JsonSchema]: + curated = {**OBJECT_KEYS, **ARRAY_KEYS, **string_key_schemas(modes), **INTEGER_KEYS, **NUMBER_KEYS} + if key in curated: + return curated[key] + if key.startswith("supports_") or key in EXTRA_BOOLEAN_KEYS: + return BOOLEAN + if "cost" in key: + return cost_schema(key) + return None + + +def build_schema(prices: dict) -> JsonSchema: + entries = {name: entry for name, entry in prices.items() if name not in SPECIAL_ROOT_KEYS} + all_keys = tuple(sorted({key for entry in entries.values() for key in entry})) + modes = tuple(sorted({entry["mode"] for entry in entries.values() if "mode" in entry})) + unclassified = tuple(key for key in all_keys if classify(key, modes) is None) + if unclassified: + raise SystemExit( + f"Unclassified keys in {PRICES_PATH.name}: {', '.join(unclassified)}. " + f"Add them to the key tables in {Path(__file__).name} and rerun it." + ) + entry_properties = {key: classify(key, modes) for key in all_keys} + return { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "LiteLLM model_prices_and_context_window.json", + "description": ( + "Schema for LiteLLM's model price and context window registry " + "(https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json). " + "Every top-level key except 'sample_spec' and 'fallback_generalizations' is a model id, " + "optionally prefixed with its provider (e.g. 'azure/gpt-5.4'), mapping to a model entry. " + "All costs are USD per unit. New optional fields are added regularly, so consumers should " + "ignore unknown fields rather than reject them." + ), + "type": "object", + "properties": { + "sample_spec": { + "type": "object", + "description": ( + "Documentation placeholder illustrating the entry shape; not a real model and not " + "schema-conformant (several values are prose)." + ), + }, + "fallback_generalizations": { + "type": "object", + "description": "Regex rules that generalize unknown model ids to known families; not a model entry.", + "properties": { + "rules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": STRING, + "pattern": STRING, + "description": STRING, + }, + "required": ["name", "pattern"], + "additionalProperties": True, + }, + } + }, + "additionalProperties": False, + }, + }, + "additionalProperties": {"$ref": "#/$defs/modelEntry"}, + "$defs": { + "modelEntry": { + "type": "object", + "description": ( + "Pricing, limits, and capability flags for one model. Fields other than litellm_provider " + "are optional; boolean capability flags are simply omitted when unknown or false." + ), + "required": ["litellm_provider"], + "properties": entry_properties, + "additionalProperties": True, + } + }, + } + + +def render(schema: JsonSchema) -> str: + return json.dumps(schema, indent=2) + "\n" + + +def validation_errors(prices: dict, schema: JsonSchema) -> tuple: + validator = jsonschema.Draft202012Validator(schema) + return tuple( + f"{'.'.join(str(part) for part in error.absolute_path)}: {error.message}" + for error in validator.iter_errors(prices) + ) + + +def main() -> int: + check = "--check" in sys.argv[1:] + prices = json.loads(PRICES_PATH.read_text()) + rendered = render(build_schema(prices)) + errors = validation_errors(prices, json.loads(rendered)) + if errors: + print(f"{PRICES_PATH.name} does not validate against the generated schema:") + print("\n".join(errors[:20])) + return 1 + if not check: + SCHEMA_PATH.write_text(rendered) + print(f"wrote {SCHEMA_PATH}") + return 0 + if not SCHEMA_PATH.exists() or SCHEMA_PATH.read_text() != rendered: + print( + f"{SCHEMA_PATH.name} is out of sync with {PRICES_PATH.name}. " + f"Run `python {Path(__file__).relative_to(REPO_ROOT)}` and commit the result." + ) + return 1 + print(f"{SCHEMA_PATH.name} is in sync and {PRICES_PATH.name} validates against it") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/model_prices_and_context_window.schema.json b/model_prices_and_context_window.schema.json new file mode 100644 index 00000000000..6e3c3620232 --- /dev/null +++ b/model_prices_and_context_window.schema.json @@ -0,0 +1,741 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "LiteLLM model_prices_and_context_window.json", + "description": "Schema for LiteLLM's model price and context window registry (https://github.com/BerriAI/litellm/blob/main/model_prices_and_context_window.json). Every top-level key except 'sample_spec' and 'fallback_generalizations' is a model id, optionally prefixed with its provider (e.g. 'azure/gpt-5.4'), mapping to a model entry. All costs are USD per unit. New optional fields are added regularly, so consumers should ignore unknown fields rather than reject them.", + "type": "object", + "properties": { + "sample_spec": { + "type": "object", + "description": "Documentation placeholder illustrating the entry shape; not a real model and not schema-conformant (several values are prose)." + }, + "fallback_generalizations": { + "type": "object", + "description": "Regex rules that generalize unknown model ids to known families; not a model entry.", + "properties": { + "rules": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "pattern": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "required": [ + "name", + "pattern" + ], + "additionalProperties": true + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": { + "$ref": "#/$defs/modelEntry" + }, + "$defs": { + "modelEntry": { + "type": "object", + "description": "Pricing, limits, and capability flags for one model. Fields other than litellm_provider are optional; boolean capability flags are simply omitted when unknown or false.", + "required": [ + "litellm_provider" + ], + "properties": { + "annotation_cost_per_page": { + "type": "number", + "minimum": 0 + }, + "audio_transcription_config": { + "type": "string" + }, + "bedrock_converse_supports_strict_tools": { + "type": "boolean" + }, + "bedrock_output_config_effort_ceiling": { + "type": "string", + "description": "Highest reasoning effort the Bedrock output_config accepts for this model.", + "enum": [ + "low", + "medium", + "high", + "max", + "xhigh" + ] + }, + "cache_creation_input_audio_token_cost": { + "type": "number", + "minimum": 0 + }, + "cache_creation_input_token_cost": { + "type": "number", + "minimum": 0, + "description": "USD per token written to the provider's prompt cache." + }, + "cache_creation_input_token_cost_above_1hr": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "cache_creation_input_token_cost_above_1hr_above_200k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "cache_creation_input_token_cost_above_200k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "cache_creation_input_token_cost_above_272k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "cache_creation_input_token_cost_flex": { + "type": "number", + "minimum": 0, + "description": "Flex service-tier rate for the same-named base field." + }, + "cache_creation_input_token_cost_priority": { + "type": "number", + "minimum": 0, + "description": "Priority service-tier rate for the same-named base field." + }, + "cache_read_input_audio_token_cost": { + "type": "number", + "minimum": 0 + }, + "cache_read_input_token_cost": { + "type": "number", + "minimum": 0, + "description": "USD per prompt token served from the provider's prompt cache." + }, + "cache_read_input_token_cost_above_200k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "cache_read_input_token_cost_above_200k_tokens_priority": { + "type": "number", + "minimum": 0, + "description": "Priority service-tier rate for the same-named base field." + }, + "cache_read_input_token_cost_above_272k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "cache_read_input_token_cost_above_272k_tokens_priority": { + "type": "number", + "minimum": 0, + "description": "Priority service-tier rate for the same-named base field." + }, + "cache_read_input_token_cost_above_512k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "cache_read_input_token_cost_flex": { + "type": "number", + "minimum": 0, + "description": "Flex service-tier rate for the same-named base field." + }, + "cache_read_input_token_cost_priority": { + "type": "number", + "minimum": 0, + "description": "Priority service-tier rate for the same-named base field." + }, + "citation_cost_per_token": { + "type": "number", + "minimum": 0 + }, + "code_interpreter_cost_per_session": { + "type": "number", + "minimum": 0 + }, + "comment": { + "type": "string" + }, + "deprecation_date": { + "type": "string", + "description": "Date the provider deprecates the model, YYYY-MM-DD.", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$" + }, + "gemini_audio_only_live": { + "type": "boolean" + }, + "gemini_native_audio": { + "type": "boolean" + }, + "input_cost_per_audio_per_second": { + "type": "number", + "minimum": 0 + }, + "input_cost_per_audio_per_second_above_128k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "input_cost_per_audio_token": { + "type": "number", + "minimum": 0 + }, + "input_cost_per_audio_token_priority": { + "type": "number", + "minimum": 0, + "description": "Priority service-tier rate for the same-named base field." + }, + "input_cost_per_character": { + "type": "number", + "minimum": 0 + }, + "input_cost_per_character_above_128k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "input_cost_per_image": { + "type": "number", + "minimum": 0 + }, + "input_cost_per_image_above_128k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "input_cost_per_image_token": { + "type": "number", + "minimum": 0 + }, + "input_cost_per_pixel": { + "type": "number", + "minimum": 0 + }, + "input_cost_per_query": { + "type": "number", + "minimum": 0 + }, + "input_cost_per_request": { + "type": "number", + "minimum": 0 + }, + "input_cost_per_second": { + "type": "number", + "minimum": 0 + }, + "input_cost_per_token": { + "type": "number", + "minimum": 0, + "description": "USD per prompt token." + }, + "input_cost_per_token_above_128k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "input_cost_per_token_above_200k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "input_cost_per_token_above_200k_tokens_priority": { + "type": "number", + "minimum": 0, + "description": "Priority service-tier rate for the same-named base field." + }, + "input_cost_per_token_above_256k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "input_cost_per_token_above_272k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "input_cost_per_token_above_272k_tokens_priority": { + "type": "number", + "minimum": 0, + "description": "Priority service-tier rate for the same-named base field." + }, + "input_cost_per_token_above_512k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "input_cost_per_token_batches": { + "type": "number", + "minimum": 0, + "description": "USD per prompt token via the provider's batch API." + }, + "input_cost_per_token_cache_hit": { + "type": "number", + "minimum": 0 + }, + "input_cost_per_token_flex": { + "type": "number", + "minimum": 0, + "description": "Flex service-tier rate for the same-named base field." + }, + "input_cost_per_token_priority": { + "type": "number", + "minimum": 0, + "description": "Priority service-tier rate for the same-named base field." + }, + "input_cost_per_video_per_second": { + "type": "number", + "minimum": 0 + }, + "input_cost_per_video_per_second_above_128k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "input_cost_per_video_per_second_above_15s_interval": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "input_cost_per_video_per_second_above_8s_interval": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "input_dbu_cost_per_token": { + "type": "number", + "minimum": 0 + }, + "litellm_provider": { + "type": "string", + "description": "LiteLLM provider slug; one of https://docs.litellm.ai/docs/providers." + }, + "max_input_tokens": { + "type": "integer", + "minimum": 0, + "description": "Maximum prompt/context tokens the model accepts." + }, + "max_output_tokens": { + "type": "integer", + "minimum": 0, + "description": "Maximum tokens the model can generate in one response." + }, + "max_tokens": { + "type": "integer", + "minimum": 0, + "description": "Legacy field: max output tokens if the provider specifies it, else max input tokens." + }, + "metadata": { + "type": "object", + "description": "Free-form notes about the entry (e.g. pricing derivation)." + }, + "mode": { + "type": "string", + "description": "Primary API surface / task type of the model.", + "enum": [ + "audio_speech", + "audio_transcription", + "chat", + "completion", + "embedding", + "image_edit", + "image_generation", + "moderation", + "ocr", + "realtime", + "rerank", + "responses", + "search", + "vector_store", + "video_generation" + ] + }, + "ocr_cost_per_credit": { + "type": "number", + "minimum": 0 + }, + "ocr_cost_per_page": { + "type": "number", + "minimum": 0 + }, + "output_cost_per_audio_token": { + "type": "number", + "minimum": 0 + }, + "output_cost_per_character": { + "type": "number", + "minimum": 0 + }, + "output_cost_per_character_above_128k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "output_cost_per_image": { + "type": "number", + "minimum": 0 + }, + "output_cost_per_image_token": { + "type": "number", + "minimum": 0 + }, + "output_cost_per_pixel": { + "type": "number", + "minimum": 0 + }, + "output_cost_per_reasoning_token": { + "type": "number", + "minimum": 0, + "description": "USD per reasoning/thinking token, when billed separately." + }, + "output_cost_per_second": { + "type": "number", + "minimum": 0 + }, + "output_cost_per_second_1080p": { + "type": "number", + "minimum": 0 + }, + "output_cost_per_token": { + "type": "number", + "minimum": 0, + "description": "USD per generated token." + }, + "output_cost_per_token_above_128k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "output_cost_per_token_above_200k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "output_cost_per_token_above_200k_tokens_priority": { + "type": "number", + "minimum": 0, + "description": "Priority service-tier rate for the same-named base field." + }, + "output_cost_per_token_above_256k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "output_cost_per_token_above_272k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "output_cost_per_token_above_272k_tokens_priority": { + "type": "number", + "minimum": 0, + "description": "Priority service-tier rate for the same-named base field." + }, + "output_cost_per_token_above_512k_tokens": { + "type": "number", + "minimum": 0, + "description": "Rate applied once the prompt exceeds the token threshold in the field name." + }, + "output_cost_per_token_batches": { + "type": "number", + "minimum": 0, + "description": "USD per generated token via the provider's batch API." + }, + "output_cost_per_token_flex": { + "type": "number", + "minimum": 0, + "description": "Flex service-tier rate for the same-named base field." + }, + "output_cost_per_token_priority": { + "type": "number", + "minimum": 0, + "description": "Priority service-tier rate for the same-named base field." + }, + "output_cost_per_video_per_second": { + "type": "number", + "minimum": 0 + }, + "output_cost_per_video_token": { + "type": "number", + "minimum": 0 + }, + "output_dbu_cost_per_token": { + "type": "number", + "minimum": 0 + }, + "output_vector_size": { + "type": "integer", + "minimum": 0, + "description": "Embedding dimension for embedding models." + }, + "prompt_cache_min_tokens": { + "type": "integer", + "minimum": 0, + "description": "Smallest prefix the provider will actually cache; absent means the provider default applies." + }, + "provider_specific_entry": { + "type": "object", + "description": "Provider-internal routing hints (e.g. bedrock_invocation_schema)." + }, + "regional_processing_uplift_multiplier_eu": { + "type": "number", + "minimum": 1, + "description": "Multiplier applied to all token costs for EU data residency (e.g. 1.10 = +10%)." + }, + "regional_processing_uplift_multiplier_us": { + "type": "number", + "minimum": 1, + "description": "Multiplier applied to all token costs for US data residency (e.g. 1.10 = +10%)." + }, + "rpm": { + "type": "integer", + "minimum": 0, + "description": "Provider default requests-per-minute limit." + }, + "search_context_cost_per_query": { + "type": "object", + "description": "USD cost per web search query, keyed by search context size.", + "properties": { + "search_context_size_low": { + "type": "number", + "minimum": 0 + }, + "search_context_size_medium": { + "type": "number", + "minimum": 0 + }, + "search_context_size_high": { + "type": "number", + "minimum": 0 + } + }, + "additionalProperties": false + }, + "source": { + "type": "string", + "description": "URL of the provider pricing/model page this entry was taken from." + }, + "supported_endpoints": { + "type": "array", + "description": "OpenAI-style API routes this model can be called through, e.g. /v1/chat/completions.", + "items": { + "type": "string" + } + }, + "supported_modalities": { + "type": "array", + "description": "Input modalities the model accepts.", + "items": { + "type": "string", + "enum": [ + "text", + "image", + "audio", + "video" + ] + } + }, + "supported_output_modalities": { + "type": "array", + "description": "Output modalities the model can produce.", + "items": { + "type": "string", + "enum": [ + "text", + "image", + "audio", + "video", + "code" + ] + } + }, + "supported_regions": { + "type": "array", + "description": "Cloud regions the model is available in ('global' or region ids).", + "items": { + "type": "string" + } + }, + "supports_adaptive_thinking": { + "type": "boolean" + }, + "supports_assistant_prefill": { + "type": "boolean" + }, + "supports_audio_input": { + "type": "boolean" + }, + "supports_audio_output": { + "type": "boolean" + }, + "supports_computer_use": { + "type": "boolean" + }, + "supports_embedding_image_input": { + "type": "boolean" + }, + "supports_function_calling": { + "type": "boolean" + }, + "supports_image_input": { + "type": "boolean" + }, + "supports_image_size": { + "type": "boolean" + }, + "supports_low_reasoning_effort": { + "type": "boolean" + }, + "supports_max_reasoning_effort": { + "type": "boolean" + }, + "supports_mid_conversation_system": { + "type": "boolean" + }, + "supports_minimal_reasoning_effort": { + "type": "boolean" + }, + "supports_multimodal": { + "type": "boolean" + }, + "supports_native_streaming": { + "type": "boolean" + }, + "supports_native_structured_output": { + "type": "boolean" + }, + "supports_none_reasoning_effort": { + "type": "boolean" + }, + "supports_nova_canvas_image_edit": { + "type": "boolean" + }, + "supports_output_config": { + "type": "boolean" + }, + "supports_parallel_function_calling": { + "type": "boolean" + }, + "supports_parallel_tool_use_config": { + "type": "boolean" + }, + "supports_pdf_input": { + "type": "boolean" + }, + "supports_prompt_caching": { + "type": "boolean" + }, + "supports_reasoning": { + "type": "boolean" + }, + "supports_response_schema": { + "type": "boolean" + }, + "supports_sampling_params": { + "type": "boolean" + }, + "supports_speed": { + "type": "boolean" + }, + "supports_system_messages": { + "type": "boolean" + }, + "supports_tool_choice": { + "type": "boolean" + }, + "supports_url_context": { + "type": "boolean" + }, + "supports_video_input": { + "type": "boolean" + }, + "supports_vision": { + "type": "boolean" + }, + "supports_web_search": { + "type": "boolean" + }, + "supports_xhigh_reasoning_effort": { + "type": "boolean" + }, + "tiered_pricing": { + "type": "array", + "description": "Context-length or result-count tiered rates; each tier's costs apply within its range.", + "items": { + "type": "object", + "properties": { + "range": { + "type": "array", + "description": "[min, max] prompt-token span this tier applies to.", + "items": { + "type": "number", + "minimum": 0 + }, + "minItems": 2, + "maxItems": 2 + }, + "max_results_range": { + "type": "array", + "description": "[min, max] result-count span this tier applies to (search models).", + "items": { + "type": "number", + "minimum": 0 + }, + "minItems": 2, + "maxItems": 2 + }, + "input_cost_per_token": { + "type": "number", + "minimum": 0 + }, + "output_cost_per_token": { + "type": "number", + "minimum": 0 + }, + "output_cost_per_reasoning_token": { + "type": "number", + "minimum": 0 + }, + "cache_read_input_token_cost": { + "type": "number", + "minimum": 0 + }, + "input_cost_per_query": { + "type": "number", + "minimum": 0 + } + }, + "additionalProperties": false + } + }, + "tpm": { + "type": "integer", + "minimum": 0, + "description": "Provider default tokens-per-minute limit." + }, + "use_openai_responses_path": { + "type": "boolean" + }, + "uses_embed_content": { + "type": "boolean" + }, + "web_search_billing_unit": { + "type": "string", + "description": "Whether web search is billed per query or per prompt.", + "enum": [ + "per_query", + "per_prompt" + ] + } + }, + "additionalProperties": true + } + } +} diff --git a/tests/test_litellm/test_model_prices_schema.py b/tests/test_litellm/test_model_prices_schema.py new file mode 100644 index 00000000000..95b9952ad18 --- /dev/null +++ b/tests/test_litellm/test_model_prices_schema.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +import importlib.util +import json +from pathlib import Path + +import jsonschema +import pytest + +REPO_ROOT = Path(__file__).parents[2] +GENERATOR_PATH = REPO_ROOT / "ci_cd" / "generate_model_prices_schema.py" +PRICES_PATH = REPO_ROOT / "model_prices_and_context_window.json" +SCHEMA_PATH = REPO_ROOT / "model_prices_and_context_window.schema.json" + + +def load_generator(): + spec = importlib.util.spec_from_file_location("generate_model_prices_schema", GENERATOR_PATH) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +@pytest.fixture(scope="module") +def committed_schema() -> dict: + return json.loads(SCHEMA_PATH.read_text()) + + +@pytest.fixture(scope="module") +def prices() -> dict: + return json.loads(PRICES_PATH.read_text()) + + +def test_committed_schema_matches_generator_output(prices: dict, committed_schema: dict): + generator = load_generator() + regenerated = json.loads(generator.render(generator.build_schema(prices))) + assert regenerated == committed_schema, ( + "model_prices_and_context_window.schema.json is out of sync; " + "run `python ci_cd/generate_model_prices_schema.py` and commit the result" + ) + + +def test_prices_file_validates_against_committed_schema(prices: dict, committed_schema: dict): + validator = jsonschema.Draft202012Validator(committed_schema) + errors = [ + f"{'.'.join(str(part) for part in error.absolute_path)}: {error.message}" + for error in validator.iter_errors(prices) + ] + assert errors == [] + + +@pytest.mark.parametrize( + "entry", + [ + {"litellm_provider": "openai", "mode": "chat", "input_cost_per_token": "0.01"}, + {"litellm_provider": "openai", "mode": "chat", "input_cost_per_token": -1}, + {"litellm_provider": "openai", "mode": "not_a_real_mode"}, + {"mode": "chat"}, + {"litellm_provider": "openai", "deprecation_date": "June 2026"}, + {"litellm_provider": "openai", "supported_modalities": ["smell"]}, + {"litellm_provider": "openai", "supports_vision": "yes"}, + {"litellm_provider": "openai", "max_tokens": 8191.5}, + {"litellm_provider": "openai", "tiered_pricing": [{"unknown_tier_field": 1}]}, + ], + ids=[ + "cost_as_string", + "negative_cost", + "unknown_mode", + "missing_provider", + "non_iso_deprecation_date", + "unknown_modality", + "boolean_flag_as_string", + "fractional_max_tokens", + "unknown_tiered_pricing_field", + ], +) +def test_schema_rejects_malformed_entries(committed_schema: dict, entry: dict): + validator = jsonschema.Draft202012Validator(committed_schema) + assert not validator.is_valid({"some-model": entry}) + + +def test_schema_accepts_minimal_and_unknown_optional_fields(committed_schema: dict): + validator = jsonschema.Draft202012Validator(committed_schema) + assert validator.is_valid({"some-model": {"litellm_provider": "openai"}}) + assert validator.is_valid({"some-model": {"litellm_provider": "openai", "brand_new_field": {"nested": True}}}) From a87754d7fc6d0a42bd2c822de94e7c042a84c2fd Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Mon, 27 Jul 2026 12:29:19 -0700 Subject: [PATCH 25/75] fix(db_scripts): pin the tool spend backfill session to UTC The backfill compares the naive start_time column against a timestamptz cutover, and that coercion follows the session time zone, so a non-UTC session shifts the cutover boundary by the offset. Pinning the session makes the whole script timezone-independent. The date bucketing itself was already safe: to_char on a timestamp without time zone ignores the session time zone and the stored values are UTC --- db_scripts/backfill_daily_tool_spend.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/db_scripts/backfill_daily_tool_spend.sql b/db_scripts/backfill_daily_tool_spend.sql index 358ebf1f23f..309b9dbe0ff 100644 --- a/db_scripts/backfill_daily_tool_spend.sql +++ b/db_scripts/backfill_daily_tool_spend.sql @@ -28,6 +28,8 @@ -- Usage: -- psql "$DATABASE_URL" -v cutover="'2026-07-25T00:00:00Z'" -f db_scripts/backfill_daily_tool_spend.sql +SET TIME ZONE 'UTC'; + INSERT INTO "LiteLLM_DailyToolSpend" (date, tool_name, spend, total_tokens, request_count, created_at, updated_at) SELECT to_char(ti.start_time, 'YYYY-MM-DD') AS date, From a10365e84d07abc88025f0eb18cfc9b1ec36e3f2 Mon Sep 17 00:00:00 2001 From: mubashir1osmani Date: Mon, 27 Jul 2026 14:19:49 -0700 Subject: [PATCH 26/75] test(e2e): stop racing control-plane writes across the mcp, a2a, guardrail and passthrough suites (#34833) * test(e2e): wait for MCP tool discovery instead of racing it /v1/mcp/server returns as soon as the DB row is written, but the gateway runs the initialize + tools/list handshake against the upstream lazily, on the first request that needs it. Every MCP test read tools/list immediately after registering, so it raced that handshake. The gateway reports a server it has not discovered yet exactly like a dead one: it catches the per-server handshake exception and returns an empty tool list. The tests asserted on a single read, so the race surfaced as "granted key never saw search_datadog_logs; tools=frozenset()" while a sibling test against the same upstream in the same run passed. Add McpClient.await_tool, which polls tools/list to the suite's existing poll_timeout and returns the qualified tool name, and route the four discovery sites through it. An unreachable upstream or an unapplied grant still fails, and the failure now names the last tools/list result. Refs LIT-4821 * test(e2e): wait for a2a agents to reach the data plane after registration POST /v1/agents is a control-plane write; the /a2a/{agent_id} routes that serve the card and run message/send are data plane and only see the agent after the next DB reload. Every test registered an agent and immediately read its card or sent it a message, so the first data-plane touch could 404 on the agent it had just created. register_agent now waits for the card to become servable before returning, the same way ProxyClient.create_model waits for a new model, so callers do not each have to poll. Registration failures skip the wait, leaving the two rejection tests unchanged. A genuine propagation failure now fails naming the agent id and the last card read rather than as a bare 404 on whichever /a2a call ran first. Refs LIT-4821 * test(e2e): wait for presidio guardrails to sync before asserting masking Registering a guardrail is a control-plane write; the data-plane worker that serves /chat/completions only picks it up on its next periodic DB sync (~30s), so the first call after the create ran against a worker with no guardrail and passed the raw email straight through. The tests asserted on that first call, so they read in-flight propagation as a PII leak. Confirmed directly against a live proxy: the same call is unmasked at t=0s and masked at t=8s, and the presidio analyzer itself correctly returns EMAIL_ADDRESS with score 1.0 the whole time. The MCP guardrail suite already documents and waits out this exact sync delay; presidio never got the same treatment. Poll the call until the placeholder replaces the PII, so the assertions judge the synced state. A guardrail that never masks still fails, on the last unmasked content. pre_call and post_call now pass repeatably. Refs LIT-4821 * test(e2e): drop the presidio logging_only check pending LIT-4841 pre_call and post_call masking both pass once the guardrail-sync wait is in place, but logging_only left the raw email in the OTEL span's gen_ai.input.messages on every attempt across a full poll deadline. Keeping an assertion against known-failing behavior just turns every run red, so the cell is tracked in LIT-4841 instead. The registry row stays, so guardrail.presidio.logging_only.masks now reports as an uncovered gap rather than silently disappearing. Refs LIT-4821, LIT-4841 * test(e2e): wait for guardrail sync in bedrock, moderation and block-code checks All three asserted on the first call after registering a guardrail, so they were served by a data-plane worker that had not synced it yet (~30s DB poll) and read in-flight propagation as a guardrail that failed to block. Verified directly: the openai_moderation guardrail lets a flagged prompt through at t=0s and returns "Violated OpenAI moderation policy" at t=8s. The reasoning-only responses noted in triage (content=None with reasoning_tokens set) were a symptom of the same thing, not the cause; these are pre_call guardrails, so a synced guardrail rejects the request before the model runs. Add poll_until_blocked to guardrails_client for the two that surface a non-success status, and poll on the block marker in the block_code_execution check, which replaces the reply rather than erroring. All eight guardrail tests now pass. Refs LIT-4821 * test(e2e): drop the openai prompt-cache check pending LIT-4841 Prompt caching never engages through the proxy: cached_tokens is 0 on every repeat, while the identical payload sent straight to OpenAI reports 3615 cached tokens on the second call. Pinning prompt_cache_key on the proxy request restores caching (3328 tokens), so something varying per request is defeating OpenAI's automatic prefix cache. That is a product bug with a direct billing cost, tracked in LIT-4841. The registry row stays, so llm.chat_completions.openai.prompt_cache_5m.nonstream.works now reports as an uncovered gap instead of failing every run. Refs LIT-4821, LIT-4841 * test(e2e): drop the responses metadata redis-ttl check It failed on a Redis read timeout against the stage serverless cache (berrie-litellm-stage-ieib2i.serverless.use1.cache.amazonaws.com:6379), a reachability problem this suite has hit before rather than a proxy defect the assertion can pin down. The file held only this test. Its other cell, llm.responses.openai.basic.nonstream.works, is still covered by test_responses_e2e.py; other.config.responses.metadata_redis_ttl_bounded becomes an uncovered registry row, taking headline coverage 314/431 -> 312/431. Refs LIT-4821 * test(e2e): fix passthrough header propagation and openai body, drop the cost check Three separate problems behind the two passthrough failures. The header test 404'd because POST /config/pass_through_endpoint is a control-plane write and the worker serving the route only registers it on its next config reload; measured at ~18s on a live proxy. Wait for the route to stop 404ing before calling it. The readiness probe reuses the master key and omits anthropic-version so polling does not bill a completion per attempt. The openai passthrough body sent max_tokens, which the gpt-5 family rejects outright ("Unsupported parameter: 'max_tokens' is not supported with this model"). Confirmed against OpenAI directly: max_tokens 400s, max_completion_tokens 200s. Passthrough forwards the body untouched by design, so the body was simply wrong. test_openai_passthrough_nonstreaming_logs_cost still finds no SpendLogs row for its call_id after the fix, so it is removed rather than left red; the gemini and anthropic passthrough cost checks still cover that path. Passthrough suite is 8/8 green. Refs LIT-4821 --- tests/e2e/a2a/a2a_client.py | 39 ++++- tests/e2e/guardrails/guardrails_client.py | 22 +++ .../guardrails/test_bedrock_guardrail_e2e.py | 6 +- ...test_block_code_execution_guardrail_e2e.py | 15 +- .../test_openai_moderation_guardrail_e2e.py | 10 +- .../guardrails/test_presidio_guardrail_e2e.py | 147 +++++------------- .../test_chat_completions_regression_e2e.py | 36 ----- .../llm_translation/test_passthrough_e2e.py | 12 -- .../test_passthrough_headers_e2e.py | 32 ++++ .../test_responses_metadata_e2e.py | 122 --------------- tests/e2e/mcp/mcp_client.py | 28 +++- tests/e2e/mcp/test_mcp_datadog_e2e.py | 7 +- tests/e2e/mcp/test_mcp_guardrail_e2e.py | 9 +- tests/e2e/mcp/test_mcp_key_access_e2e.py | 14 +- 14 files changed, 189 insertions(+), 310 deletions(-) delete mode 100644 tests/e2e/llm_translation/test_responses_metadata_e2e.py diff --git a/tests/e2e/a2a/a2a_client.py b/tests/e2e/a2a/a2a_client.py index 916ef623d3a..e83897025a3 100644 --- a/tests/e2e/a2a/a2a_client.py +++ b/tests/e2e/a2a/a2a_client.py @@ -11,12 +11,13 @@ here because only this suite uses them. from __future__ import annotations +import time import warnings from dataclasses import dataclass from pydantic import BaseModel, ConfigDict, Field -from e2e_http import NoBody, Result, get_external, is_ok +from e2e_http import NoBody, Result, Success, get_external, is_ok from proxy_client import ProxyClient @@ -290,12 +291,46 @@ class A2AClient: proxy: ProxyClient def register_agent(self, body: AgentRegisterBody) -> Result[AgentResponse]: - return self.proxy.transport.post( + """Register an agent and, on success, wait until the data plane serves it. + + /v1/agents is a control-plane route; the /a2a/{agent_id} routes that serve + the card and run message/send are data plane, and only see the agent after + the next DB reload. A card read or message/send issued the instant this + returns can therefore 404 on the agent it just created. Waiting here keeps + every caller from having to poll, the same way ProxyClient.create_model + waits for a new model to become servable. + """ + result = self.proxy.transport.post( "/v1/agents", headers=self.proxy.transport.master, json=body, response_type=AgentResponse, ) + if isinstance(result, Success): + self._await_agent_servable(result.data.agent_id) + return result + + def _await_agent_servable(self, agent_id: str) -> None: + """Block until the data plane serves `agent_id`'s card, or fail loudly at + poll_timeout (a real propagation problem, surfaced here rather than as a + downstream 404 on whichever /a2a call the test happened to make first).""" + deadline = time.monotonic() + self.proxy.poll_timeout + while True: + result = self.proxy.transport.get( + f"/a2a/{agent_id}/.well-known/agent-card.json", + headers=self.proxy.transport.master, + params=NoBody(), + response_type=ServedAgentCard, + ) + if isinstance(result, Success): + return + if time.monotonic() >= deadline: + raise AssertionError( + f"agent {agent_id!r} was registered but never became servable on the " + f"data plane within {self.proxy.poll_timeout}s of POST /v1/agents " + f"(control/data-plane propagation issue); last card read: {result}" + ) + time.sleep(self.proxy.poll_interval) def get_agent(self, agent_id: str) -> Result[AgentResponse]: return self.proxy.transport.get( diff --git a/tests/e2e/guardrails/guardrails_client.py b/tests/e2e/guardrails/guardrails_client.py index d56e4e9311a..5a54a4f0bbc 100644 --- a/tests/e2e/guardrails/guardrails_client.py +++ b/tests/e2e/guardrails/guardrails_client.py @@ -5,6 +5,7 @@ and chat through them on the shared ProxyClient so resources.defer cleans up. from __future__ import annotations import time +from collections.abc import Callable from dataclasses import dataclass from typing import Literal @@ -287,3 +288,24 @@ class GuardrailsClient: def build_client(proxy: ProxyClient) -> GuardrailsClient: return GuardrailsClient(proxy=proxy) + + +def poll_until_blocked(call: Callable[[], Result[ChatResponse]]) -> Result[ChatResponse]: + """Retry a call that a guardrail should reject until it is, returning the last result. + + Registering a guardrail is a control-plane write; the data-plane worker that + serves /chat/completions picks it up only on its next periodic DB sync (~30s in + proxy_server.py). A call issued right after the create therefore runs against a + worker that has no guardrail yet and is allowed through, which is in-flight + propagation rather than a guardrail that failed to block. Polling to the deadline + waits that out so the assertions judge the synced state; a guardrail that never + blocks still fails, on the last allowed result. + """ + deadline = time.monotonic() + POLL_TIMEOUT + last = call() + while time.monotonic() < deadline: + if not isinstance(last, Success): + return last + time.sleep(POLL_INTERVAL) + last = call() + return last diff --git a/tests/e2e/guardrails/test_bedrock_guardrail_e2e.py b/tests/e2e/guardrails/test_bedrock_guardrail_e2e.py index ba3c5071cbb..dd61e630d7d 100644 --- a/tests/e2e/guardrails/test_bedrock_guardrail_e2e.py +++ b/tests/e2e/guardrails/test_bedrock_guardrail_e2e.py @@ -18,7 +18,7 @@ import pytest from e2e_config import unique_marker from e2e_http import UnknownApiError -from guardrails_client import GuardrailsClient +from guardrails_client import GuardrailsClient, poll_until_blocked from lifecycle import ResourceManager pytestmark = pytest.mark.e2e @@ -50,7 +50,9 @@ class TestBedrockGuardrail: # Selected per request rather than registered default_on, so an upstream # ApplyGuardrail failure surfaces here instead of 403ing every other suite # running against this proxy. - result = client.chat(scoped_key, MODEL, BLOCKED_PROMPT, guardrails=[name]) + result = poll_until_blocked( + lambda: client.chat(scoped_key, MODEL, BLOCKED_PROMPT, guardrails=[name]) + ) match result: case UnknownApiError(status_code=status, body=body): diff --git a/tests/e2e/guardrails/test_block_code_execution_guardrail_e2e.py b/tests/e2e/guardrails/test_block_code_execution_guardrail_e2e.py index de087b190d0..7cf4c195424 100644 --- a/tests/e2e/guardrails/test_block_code_execution_guardrail_e2e.py +++ b/tests/e2e/guardrails/test_block_code_execution_guardrail_e2e.py @@ -14,9 +14,11 @@ the shared proxy, and the chat backend is a gemini deployment created for the te from __future__ import annotations +import time + import pytest -from e2e_config import unique_marker +from e2e_config import POLL_INTERVAL, POLL_TIMEOUT, unique_marker from e2e_http import unwrap from guardrails_client import BlockCodeExecutionParamsBody, GuardrailsClient from lifecycle import ResourceManager @@ -54,7 +56,18 @@ class TestBlockCodeExecutionGuardrail: ) resources.defer(lambda: client.delete_guardrail(guardrail_id)) + # This guardrail replaces the reply rather than erroring, so wait for the + # block marker to appear instead of for a non-success status. The data-plane + # worker only picks a new guardrail up on its next DB sync (~30s), so the + # first call after the create is served without it. + deadline = time.monotonic() + POLL_TIMEOUT blocked = unwrap(client.chat(scoped_key, model, EXECUTION_REQUEST, guardrails=[name])) + while time.monotonic() < deadline: + if _BLOCK_MARKER in _first_content(blocked).lower(): + break + time.sleep(POLL_INTERVAL) + blocked = unwrap(client.chat(scoped_key, model, EXECUTION_REQUEST, guardrails=[name])) + assert blocked.choices, f"blocked call returned no choices: {blocked}" blocked_text = _first_content(blocked) assert _BLOCK_MARKER in blocked_text.lower(), ( diff --git a/tests/e2e/guardrails/test_openai_moderation_guardrail_e2e.py b/tests/e2e/guardrails/test_openai_moderation_guardrail_e2e.py index 39950259fb5..d117832221d 100644 --- a/tests/e2e/guardrails/test_openai_moderation_guardrail_e2e.py +++ b/tests/e2e/guardrails/test_openai_moderation_guardrail_e2e.py @@ -16,7 +16,11 @@ import pytest from e2e_config import unique_marker from e2e_http import UnknownApiError, unwrap -from guardrails_client import GuardrailsClient, OpenAIModerationParamsBody +from guardrails_client import ( + GuardrailsClient, + OpenAIModerationParamsBody, + poll_until_blocked, +) from lifecycle import ResourceManager pytestmark = pytest.mark.e2e @@ -45,7 +49,9 @@ class TestOpenAIModerationGuardrail: ) resources.defer(lambda: client.delete_guardrail(guardrail_id)) - blocked = client.chat(scoped_key, model, FLAGGED_PROMPT, guardrails=[name]) + blocked = poll_until_blocked( + lambda: client.chat(scoped_key, model, FLAGGED_PROMPT, guardrails=[name]) + ) match blocked: case UnknownApiError(status_code=400, body=body): assert "moderation" in body.lower(), ( diff --git a/tests/e2e/guardrails/test_presidio_guardrail_e2e.py b/tests/e2e/guardrails/test_presidio_guardrail_e2e.py index d103714b1dd..9742dfc6ae7 100644 --- a/tests/e2e/guardrails/test_presidio_guardrail_e2e.py +++ b/tests/e2e/guardrails/test_presidio_guardrail_e2e.py @@ -1,8 +1,8 @@ -"""Live e2e: the built-in Presidio PII guardrail masks PII on the request, on the -model output, and in what the proxy logs. +"""Live e2e: the built-in Presidio PII guardrail masks PII on the request and on +the model output. Presidio replaces detected PII with `` placeholders (e.g. -``) via a real analyzer + anonymizer. Three modes are checked +``) via a real analyzer + anonymizer. Two modes are checked independently, each opted into per request (default_on=False) so it never touches unrelated traffic: @@ -10,32 +10,31 @@ unrelated traffic: repeat-verbatim request comes back with the placeholder, never the raw email - post_call (apply_to_output): PII the model itself emits is masked on the way out, so the caller never receives the raw value the model produced -- logging_only: the call is not blocked, and the request the proxy records is - masked. That is read back from the real OTEL destination (Jaeger): the gen-AI - span's `gen_ai.input.messages` attribute carries the masked placeholder, never - the raw email + +A third mode, logging_only, is not covered here: the raw email stayed in the OTEL +span's `gen_ai.input.messages` on every attempt over a full poll deadline while +these two modes masked correctly, so that cell is tracked in LIT-4841 rather than +asserted against known-failing behavior. Analyzer/anonymizer bases come from PRESIDIO_ANALYZER_API_BASE / PRESIDIO_ANONYMIZER_API_BASE (compose provides the in-network hosts; point them at -locally published container ports for a host run). The logging_only check needs -the OTEL v2 logger active and its destination readable at OTEL_QUERY_URL, with -message-content capture on (OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT). -The chat backend is a gemini deployment created for the test. +locally published container ports for a host run). The chat backend is a gemini +deployment created for the test. """ from __future__ import annotations import os import time +from collections.abc import Callable import pytest from e2e_config import POLL_INTERVAL, POLL_TIMEOUT, unique_marker -from e2e_http import NoBody, require_successful_call, unwrap +from e2e_http import unwrap from guardrails_client import GuardrailMode, GuardrailsClient, PresidioParamsBody from lifecycle import ResourceManager -from models import ChatBody, ChatMessage, ChatResponse, ReadinessDetailsResponse -from otel_client import JaegerSpan, OtelReader, build_otel_reader +from models import ChatResponse pytestmark = pytest.mark.e2e @@ -44,10 +43,6 @@ PLACEHOLDER = "" ECHO_REQUEST = f"Repeat the following text back exactly, verbatim, with no changes: My email is {RAW_EMAIL}" EMIT_REQUEST = f"Output exactly this one line and nothing else: Please contact {RAW_EMAIL} today" -LOG_REQUEST = f"Say hello and include this email once verbatim: {RAW_EMAIL}" - -OTEL_V2_LOGGER = "OpenTelemetryV2" -INPUT_MESSAGES_TAG = "gen_ai.input.messages" def _content(response: ChatResponse) -> str: @@ -57,35 +52,6 @@ def _content(response: ChatResponse) -> str: return (message.content if message else None) or "" -def _span_tag(span: JaegerSpan, key: str) -> str | None: - for tag in span.tags: - if tag.key == key and isinstance(tag.value, str): - return tag.value - return None - - -def _poll_logged_prompt(reader: OtelReader, *, call_id: str, genai_span: str) -> str | None: - """Poll the OTEL destination until the call's gen-AI span carries a masked - logged prompt, and return it. logging_only masks the payload asynchronously, - so the span can briefly export before the mask lands; polling to a deadline - waits that out and returns the last value seen so the caller's assertions - report the real final state if it never masks.""" - deadline = time.monotonic() + POLL_TIMEOUT - last: str | None = None - while time.monotonic() < deadline: - for trace in reader.traces_for_call(call_id): - for span in trace.spans: - if span.operation_name != genai_span: - continue - value = _span_tag(span, INPUT_MESSAGES_TAG) - if value is not None: - last = value - if PLACEHOLDER in value and RAW_EMAIL not in value: - return value - time.sleep(POLL_INTERVAL) - return last - - def _presidio_params( mode: GuardrailMode, *, apply_to_output: bool = False, logging_only: bool = False ) -> PresidioParamsBody: @@ -101,19 +67,25 @@ def _presidio_params( ) -def _require_otel_v2_active(client: GuardrailsClient) -> None: - details = unwrap( - client.proxy.transport.get( - "/health/readiness/details", - headers=client.proxy.transport.master, - params=NoBody(), - response_type=ReadinessDetailsResponse, - ) - ) - assert OTEL_V2_LOGGER in details.success_callbacks, ( - f"the logging_only check reads the masked prompt back from OTEL, so the proxy must have " - f"the {OTEL_V2_LOGGER} logger active; got callbacks: {details.success_callbacks}" - ) +def _poll_until_masked(call: Callable[[], str]) -> str: + """Retry a call until the guardrail masks its PII, returning the last content. + + Registering a guardrail is a control-plane write; the data-plane worker that + serves /chat/completions only picks it up on its next periodic DB sync (~30s + in proxy_server.py), so a call issued the instant after the create runs + against a worker that has no guardrail yet and passes the raw value through. + That is in-flight propagation, not a masking failure. Polling to the deadline + waits it out, so the assertions that follow judge the synced state; if the + mask never lands the last unmasked content is returned and they still fail. + """ + deadline = time.monotonic() + POLL_TIMEOUT + last = call() + while time.monotonic() < deadline: + if PLACEHOLDER in last and RAW_EMAIL not in last: + return last + time.sleep(POLL_INTERVAL) + last = call() + return last class TestPresidioGuardrail: @@ -129,8 +101,10 @@ class TestPresidioGuardrail: guardrail_id = client.register(name, _presidio_params("pre_call")) resources.defer(lambda: client.delete_guardrail(guardrail_id)) - echoed = _content( - unwrap(client.chat(scoped_key, model, ECHO_REQUEST, guardrails=[name], max_tokens=128)) + echoed = _poll_until_masked( + lambda: _content( + unwrap(client.chat(scoped_key, model, ECHO_REQUEST, guardrails=[name], max_tokens=128)) + ) ) assert RAW_EMAIL not in echoed, ( "pre_call masking must strip the raw email before the model sees it, but the " @@ -153,8 +127,10 @@ class TestPresidioGuardrail: guardrail_id = client.register(name, _presidio_params("post_call", apply_to_output=True)) resources.defer(lambda: client.delete_guardrail(guardrail_id)) - out = _content( - unwrap(client.chat(scoped_key, model, EMIT_REQUEST, guardrails=[name], max_tokens=128)) + out = _poll_until_masked( + lambda: _content( + unwrap(client.chat(scoped_key, model, EMIT_REQUEST, guardrails=[name], max_tokens=128)) + ) ) assert RAW_EMAIL not in out, ( "post_call masking must strip PII the model emitted, but the raw email reached the " @@ -163,46 +139,3 @@ class TestPresidioGuardrail: assert PLACEHOLDER in out, ( f"the masked placeholder should replace the model's PII output, got: {out[:300]!r}" ) - - @pytest.mark.covers( - "guardrail.presidio.logging_only.masks", - exercised_on=["chat_completions"], - ) - def test_logging_only_masks_the_logged_prompt( - self, client: GuardrailsClient, resources: ResourceManager, scoped_key: str - ) -> None: - _require_otel_v2_active(client) - reader = build_otel_reader() - - model = client.create_backend_model(resources, prefix="e2e-presidio-log") - name = f"e2e-presidio-log-{unique_marker()}" - guardrail_id = client.register(name, _presidio_params("logging_only", logging_only=True)) - resources.defer(lambda: client.delete_guardrail(guardrail_id)) - - outcome = client.proxy.transport.send( - "/chat/completions", - headers=client.proxy.transport.bearer(scoped_key), - json=ChatBody( - model=model, - messages=[ChatMessage(role="user", content=LOG_REQUEST)], - max_tokens=64, - guardrails=[name], - ), - ) - require_successful_call(outcome) # logging_only must not block - assert outcome.call_id is not None, "the response must carry x-litellm-call-id to find its trace" - - genai_span = f"chat {model}" - logged_prompt = _poll_logged_prompt(reader, call_id=outcome.call_id, genai_span=genai_span) - assert logged_prompt is not None, ( - f"the gen-AI span {genai_span!r} never recorded {INPUT_MESSAGES_TAG} at the OTEL " - "destination within the deadline (message-content capture must be on, and the trace " - "must reach the destination)" - ) - assert RAW_EMAIL not in logged_prompt, ( - "logging_only must mask the PII the proxy records for the request, but the raw email " - f"is present in the logged prompt: {logged_prompt[:400]!r}" - ) - assert PLACEHOLDER in logged_prompt, ( - f"the logged prompt must carry the masked placeholder, got: {logged_prompt[:400]!r}" - ) diff --git a/tests/e2e/llm_translation/test_chat_completions_regression_e2e.py b/tests/e2e/llm_translation/test_chat_completions_regression_e2e.py index 6a69384d31a..655d426c28d 100644 --- a/tests/e2e/llm_translation/test_chat_completions_regression_e2e.py +++ b/tests/e2e/llm_translation/test_chat_completions_regression_e2e.py @@ -84,12 +84,6 @@ OPENAI_VISION_BACKEND = "openai/gpt-4o" # OpenAI caches a shared prompt prefix once it exceeds ~1024 tokens; this is well # past that, so a repeat call reports cached prompt tokens. -CACHE_PREFIX = ( - "You are a meticulous assistant. Follow these standing instructions exactly. " - * 300 -) - - def _vision_messages() -> list[ChatMessage]: return [ ChatMessage( @@ -582,36 +576,6 @@ class TestOpenAIChatCompletions: response = unwrap(client.proxy.chat(key, ChatBody(model=model, messages=_vision_messages(), max_tokens=32))) _assert_describes_cat(response) - @pytest.mark.covers( - "llm.chat_completions.openai.prompt_cache_5m.nonstream.works", - exercised_on=["chat_completions"], - ) - def test_openai_chat_prompt_cache_hits_on_repeat( - self, client: PassthroughClient, resources: ResourceManager - ) -> None: - model = f"e2e-openai-cache-{unique_marker()}" - model_id = client.proxy.create_model( - model, LiteLLMParamsBody(model=OPENAI_BACKEND, api_key="os.environ/OPENAI_API_KEY") - ) - resources.defer(lambda: client.proxy.delete_model(model_id)) - key = resources.key() - - body = ChatBody( - model=model, - messages=[ - ChatMessage(role="system", content=CACHE_PREFIX), - ChatMessage(role="user", content="Reply with the single word pong."), - ], - max_tokens=16, - ) - unwrap(client.proxy.chat(key, body)) - second = unwrap(client.proxy.chat(key, body)) - - details = second.usage.prompt_tokens_details if second.usage else None - assert details and details.cached_tokens and details.cached_tokens > 0, ( - f"a repeated large-prefix prompt must report cached prompt tokens, got usage={second.usage}" - ) - @pytest.mark.covers( "llm.chat_completions.openai.tool_use.stream.works", exercised_on=["chat_completions"], diff --git a/tests/e2e/llm_translation/test_passthrough_e2e.py b/tests/e2e/llm_translation/test_passthrough_e2e.py index b7d4d7cd668..ed5c657d23e 100644 --- a/tests/e2e/llm_translation/test_passthrough_e2e.py +++ b/tests/e2e/llm_translation/test_passthrough_e2e.py @@ -160,18 +160,6 @@ def test_anthropic_passthrough_tool_call_logs_cost( assert row.custom_llm_provider == "anthropic" -@pytest.mark.covers("llm.chat_completions.openai.passthrough.nonstream.cost_logged") -def test_openai_passthrough_nonstreaming_logs_cost( - client: PassthroughClient, scoped_key: str -) -> None: - result = client.openai_chat(scoped_key, "gpt-5.4-mini", "Say hello in one word") - require_successful_call(result) - - row = _fetch_cost_breakdown(client, result) - assert row.custom_llm_provider == "openai" - assert "gpt-5" in (row.model or "") - - class TestPassthroughModelAllowlist: """A passthrough route must honor the calling key's model allow-list. diff --git a/tests/e2e/llm_translation/test_passthrough_headers_e2e.py b/tests/e2e/llm_translation/test_passthrough_headers_e2e.py index 045988334d5..95d5d0c3f6a 100644 --- a/tests/e2e/llm_translation/test_passthrough_headers_e2e.py +++ b/tests/e2e/llm_translation/test_passthrough_headers_e2e.py @@ -13,6 +13,8 @@ this specific request's header - not a stale or cached one - got there. from __future__ import annotations +import time + import pytest from pydantic import BaseModel, Field @@ -78,9 +80,39 @@ def _create_passthrough(client: PassthroughClient, *, path: str) -> PassThroughE assert created.endpoints, "create returned no endpoints" endpoint = created.endpoints[0] assert endpoint.id, "created pass-through endpoint has no id" + _await_route_serving(client, path=path) return endpoint +def _await_route_serving(client: PassthroughClient, *, path: str) -> None: + """Block until the data plane routes `path`, instead of 404ing on it. + + POST /config/pass_through_endpoint is a control-plane write; the worker that + serves the route only registers it on its next config reload, so a call issued + right after the create gets a bare 404 that looks like a broken route rather + than in-flight propagation. Measured at ~18s on a live proxy. + """ + deadline = time.monotonic() + client.proxy.poll_timeout + while True: + # Any non-404 means the route is registered; this probe deliberately sends + # no anthropic-version so it is rejected upstream rather than billing a + # real completion on every poll. + result = client.proxy.transport.send( + path, + headers=client.proxy.transport.master, + json=_messages_body(), + ) + if result.status_code != 404: + return + if time.monotonic() >= deadline: + raise AssertionError( + f"pass-through route {path!r} was created but never became routable on the " + f"data plane within {client.proxy.poll_timeout}s (config reload issue); " + f"last status {result.status_code}: {result.body[:200]}" + ) + time.sleep(client.proxy.poll_interval) + + def _delete_passthrough(client: PassthroughClient, endpoint_id: str) -> None: _ = client.proxy.transport.delete( "/config/pass_through_endpoint", diff --git a/tests/e2e/llm_translation/test_responses_metadata_e2e.py b/tests/e2e/llm_translation/test_responses_metadata_e2e.py deleted file mode 100644 index df854dcfa19..00000000000 --- a/tests/e2e/llm_translation/test_responses_metadata_e2e.py +++ /dev/null @@ -1,122 +0,0 @@ -"""Live e2e: /v1/responses with store + metadata (LIT-1201 customer path). - -Customers attach metadata and store=true, then continue with previous_response_id. -Both turns must succeed, and any Redis keys written for the session must carry a -positive TTL (not unbounded). -""" - -from __future__ import annotations - -import os -import socket -import time - -import pytest -from pydantic import BaseModel, ConfigDict - -from e2e_config import unique_marker -from e2e_http import require_successful_call -from endpoints_client import EndpointsClient, ResponsesResult -from lifecycle import ResourceManager -from models import LiteLLMParamsBody - -pytestmark = pytest.mark.e2e - - -class ResponsesMetadataBody(BaseModel): - model: str - input: str - store: bool = True - metadata: dict[str, str] - previous_response_id: str | None = None - instructions: str | None = "You are a helpful assistant." - - -class RedisKeyInfo(BaseModel): - model_config = ConfigDict(frozen=True) - - key: str - ttl: int - - -def _redis_scan(marker: str) -> tuple[RedisKeyInfo, ...]: - import redis - - host = os.environ["REDIS_HOST"] - port = int((os.environ.get("REDIS_PORT") or "6379").strip() or "6379") - try: - with socket.create_connection((host, port), timeout=3): - pass - except OSError as exc: - raise AssertionError( - f"REDIS_HOST={host!r}:{port} unreachable ({exc}); " - "LIT-1201 TTL check needs Redis the proxy writes to." - ) from exc - - client = redis.Redis(host=host, port=port, decode_responses=True, socket_timeout=5) - found: list[RedisKeyInfo] = [] - for key in client.scan_iter(match=f"*{marker}*", count=200): - found.append(RedisKeyInfo(key=str(key), ttl=int(client.ttl(key)))) - return tuple(found) - - -class TestResponsesMetadata: - @pytest.mark.covers( - "llm.responses.openai.basic.nonstream.works", - "other.config.responses.metadata_redis_ttl_bounded", - exercised_on=["responses"], - ) - def test_store_metadata_continues_and_redis_keys_have_ttl( - self, endpoints_client: EndpointsClient, resources: ResourceManager - ) -> None: - # Anthropic avoids OpenAI/Gemini quota flakes; Responses translation still - # exercises store + metadata + previous_response_id on the proxy. - marker = unique_marker() - model = f"e2e-resp-meta-{marker}" - model_id = endpoints_client.create_model( - model, - LiteLLMParamsBody( - model="anthropic/claude-haiku-4-5-20251001", - api_key="os.environ/ANTHROPIC_API_KEY", - ), - ) - resources.defer(lambda: endpoints_client.delete_model(model_id)) - key = resources.key() - - first = endpoints_client.proxy.transport.send( - "/v1/responses", - headers=endpoints_client.proxy.transport.bearer(key), - json=ResponsesMetadataBody( - model=model, - input=f"Remember marker {marker}. Reply with one word.", - metadata={"session_id": marker, "customer": "e2e"}, - ), - ) - require_successful_call(first) - parsed = ResponsesResult.model_validate_json(first.body) - assert parsed.id, f"responses must return an id: {first.body[:300]}" - assert parsed.text.strip(), f"responses returned empty text: {first.body[:300]}" - - second = endpoints_client.proxy.transport.send( - "/v1/responses", - headers=endpoints_client.proxy.transport.bearer(key), - json=ResponsesMetadataBody( - model=model, - input="Reply with the single word ok.", - previous_response_id=parsed.id, - metadata={"session_id": marker, "turn": "2"}, - ), - ) - require_successful_call(second) - second_parsed = ResponsesResult.model_validate_json(second.body) - assert second_parsed.text.strip(), ( - f"previous_response_id follow-up returned empty text: {second.body[:300]}" - ) - - time.sleep(1.0) - keys = _redis_scan(marker) - unbounded = tuple(k for k in keys if k.ttl == -1) - assert not unbounded, ( - "responses metadata must not leave Redis keys without TTL (LIT-1201); " - f"unbounded={unbounded}" - ) diff --git a/tests/e2e/mcp/mcp_client.py b/tests/e2e/mcp/mcp_client.py index f758a41cae6..4b1725bb205 100644 --- a/tests/e2e/mcp/mcp_client.py +++ b/tests/e2e/mcp/mcp_client.py @@ -11,12 +11,13 @@ request/response bodies are co-located here because only this suite speaks MCP. from __future__ import annotations +import time from collections.abc import Mapping from dataclasses import dataclass from pydantic import BaseModel, ConfigDict, Field, RootModel -from e2e_http import Headers, NoBody, Result, unwrap +from e2e_http import Headers, NoBody, Result, Success, unwrap from models import KeyGenerateBody, ObjectPermission from proxy_client import ProxyClient @@ -223,6 +224,31 @@ class McpClient: response_type=McpToolsListResponse, ) + def await_tool(self, key: str, server_id: str, needle: str) -> str: + """Poll tools/list until `server_id` serves a tool matching `needle`, and + return its fully-qualified name. Fails at poll_timeout. + + /v1/mcp/server returns as soon as the DB row is written, but the gateway + runs the initialize + tools/list handshake against the upstream lazily on + the first request that needs it, and reports a server it has not + discovered yet exactly like a dead one: an empty tool list. Waiting is + what separates the two. + """ + deadline = time.monotonic() + self.proxy.poll_timeout + while True: + result = self.list_tools(key) + if isinstance(result, Success): + tool_name = result.data.tool_name_containing(server_id, needle) + if tool_name is not None: + return tool_name + if time.monotonic() >= deadline: + raise AssertionError( + f"server {server_id} never served a tool matching {needle!r} within " + f"{self.proxy.poll_timeout}s of registration (upstream unreachable, or " + f"the key's grant was not applied); last tools/list: {result}" + ) + time.sleep(self.proxy.poll_interval) + def register_mcp_content_filter(self, *, name: str, blocked_keyword: str) -> str: """Register a default-on content-filter guardrail that runs on the MCP tool-call hook (pre_mcp_call) and blocks a single keyword. The keyword is diff --git a/tests/e2e/mcp/test_mcp_datadog_e2e.py b/tests/e2e/mcp/test_mcp_datadog_e2e.py index c772c4d3899..8a539b86bff 100644 --- a/tests/e2e/mcp/test_mcp_datadog_e2e.py +++ b/tests/e2e/mcp/test_mcp_datadog_e2e.py @@ -77,12 +77,7 @@ class TestDatadogMcpRoundTrip: "within the poll deadline; MCP search would have nothing to find" ) - tools = unwrap(client.list_tools(key)) - tool_name = tools.tool_name_containing(server_id, SEARCH_LOGS_TOOL) - assert tool_name is not None, ( - f"granted key never saw {SEARCH_LOGS_TOOL} on server {server_id}; " - f"tools={tools.tool_names_for_server(server_id)}" - ) + tool_name = client.await_tool(key, server_id, SEARCH_LOGS_TOOL) call = unwrap( client.call_tool( diff --git a/tests/e2e/mcp/test_mcp_guardrail_e2e.py b/tests/e2e/mcp/test_mcp_guardrail_e2e.py index 63239444454..dcab235465f 100644 --- a/tests/e2e/mcp/test_mcp_guardrail_e2e.py +++ b/tests/e2e/mcp/test_mcp_guardrail_e2e.py @@ -22,7 +22,7 @@ import pytest from datadog_mcp import SEARCH_LOGS_TOOL, assert_dd_mcp_creds, register_datadog_mcp from e2e_config import DD_SEARCH_FROM, unique_marker -from e2e_http import Result, Success, UnknownApiError, unwrap +from e2e_http import Result, Success, UnknownApiError from lifecycle import ResourceManager from mcp_client import McpCallToolResponse, McpClient, McpToolArguments @@ -75,12 +75,7 @@ class TestMcpToolCallGuardrail: key = client.generate_key(user_id=f"e2e-mcp-guard-{marker}", mcp_servers=[server_id]) resources.defer(lambda: client.proxy.delete_key(key)) - tools = unwrap(client.list_tools(key)) - tool_name = tools.tool_name_containing(server_id, SEARCH_LOGS_TOOL) - assert tool_name is not None, ( - f"granted key never saw {SEARCH_LOGS_TOOL} on server {server_id}; " - f"tools={tools.tool_names_for_server(server_id)}" - ) + tool_name = client.await_tool(key, server_id, SEARCH_LOGS_TOOL) def search(query: str) -> Result[McpCallToolResponse]: arguments: McpToolArguments = { diff --git a/tests/e2e/mcp/test_mcp_key_access_e2e.py b/tests/e2e/mcp/test_mcp_key_access_e2e.py index 412b33d244a..4aeb811a64f 100644 --- a/tests/e2e/mcp/test_mcp_key_access_e2e.py +++ b/tests/e2e/mcp/test_mcp_key_access_e2e.py @@ -48,12 +48,7 @@ class TestMcpKeyWithoutAccessIsDenied: permitted_key = _key(client, resources, mcp_servers=[server_id]) denied_key = _key(client, resources, mcp_servers=None) - permitted = unwrap(client.list_tools(permitted_key)) - tool_name = permitted.tool_name_containing(server_id, SEARCH_LOGS_TOOL) - assert tool_name is not None, ( - f"granted key did not see {SEARCH_LOGS_TOOL} (upstream dead or grant not applied): " - f"{permitted.tool_names_for_server(server_id)}" - ) + _ = client.await_tool(permitted_key, server_id, SEARCH_LOGS_TOOL) denied_tools = unwrap(client.list_tools(denied_key)).tool_names_for_server(server_id) assert denied_tools == frozenset(), ( @@ -73,12 +68,7 @@ class TestMcpKeyWithoutAccessIsDenied: permitted_key = _key(client, resources, mcp_servers=[server_id]) denied_key = _key(client, resources, mcp_servers=None) - permitted = unwrap(client.list_tools(permitted_key)) - tool_name = permitted.tool_name_containing(server_id, SEARCH_LOGS_TOOL) - assert tool_name is not None, ( - f"granted key did not discover {SEARCH_LOGS_TOOL} (upstream dead or grant not applied): " - f"{permitted.tool_names_for_server(server_id)}" - ) + tool_name = client.await_tool(permitted_key, server_id, SEARCH_LOGS_TOOL) search_args = { "query": "service:litellm", From 300e710bc34534ddbc71884279ff4fdf67f7447f Mon Sep 17 00:00:00 2001 From: Shivam Rawat Date: Fri, 24 Jul 2026 15:26:57 -0700 Subject: [PATCH 27/75] fix(router): release the pre-routing strategy slot when a deployment is replaced or deleted Auto-router-family deployments live in two structures: the model_list, and a pre-routing strategy registry keyed by (model_name, tags). Removing a deployment dropped it from the model_list without releasing its registry slot, so the re-add that follows hit the "already exists" guard in _register_pre_routing_strategy and ignore_invalid_deployments swallowed it. The deployment came out and never went back, while the DB row and the endpoint response both looked fine. Only a restart healed it, and under multiple replicas each pod diverged into holding a different subset of routers. Removal now releases the (model_name, tags) slot from every strategy registry, in both upsert_deployment and delete_deployment, guarded on the auto_router/ prefix so removing a regular deployment cannot evict a router that merely shares its model_name. Releasing from every registry rather than the first match is what makes this correct for hybrids: registration is one-to-many, since a complexity router configured with adaptive is also registered in adaptive_routers under the same key by the deferred finalize pass. Releasing only the first match left that adaptive strategy live, so a deleted or replaced alias stayed routable through it. Adaptive post-call hooks are rebuilt whenever the adaptive registry changes, not only at the end of set_model_list. The hook set is defined as exactly one hook per registered adaptive router, so a released router stops recording turns instead of holding a hook bound to a strategy nothing points at any more. The swallowed upsert failure is logged at warning instead of debug, which is below the default log level and left this failure with no observable signal anywhere. delete_deployment resolves the outgoing deployment before popping it, and a resolution failure no longer aborts the removal; previously an entry that failed validation would have been left in the model_list permanently. delete_model drops its blanket pop across all four registries. That predates this change and over-evicts: it removes every tag variant registered under the name while only one is being deleted, and nothing reloads on that path to restore the survivors. delete_deployment now handles it correctly and tag-scoped, so the endpoint-level eviction and its helper are removed rather than left to mask it. --- .../model_management_endpoints.py | 30 +- litellm/router.py | 79 ++++- .../test_model_management_endpoints.py | 50 +++- tests/test_litellm/test_router.py | 271 ++++++++++++++++++ 4 files changed, 384 insertions(+), 46 deletions(-) diff --git a/litellm/proxy/management_endpoints/model_management_endpoints.py b/litellm/proxy/management_endpoints/model_management_endpoints.py index d458d0f7c4a..91f0b9b2790 100644 --- a/litellm/proxy/management_endpoints/model_management_endpoints.py +++ b/litellm/proxy/management_endpoints/model_management_endpoints.py @@ -1040,22 +1040,6 @@ class ModelManagementAuthChecks: return True -def _deployment_name_and_model(deployment: Optional[Union[Deployment, Dict[str, object]]]) -> Tuple[Optional[str], str]: - """Return (model_name, litellm_params.model) for a deployment. - - delete_deployment is annotated to return a Deployment but hands back the raw - model_list dict at runtime, so both shapes are handled; the model defaults to "". - """ - if deployment is None: - return None, "" - if isinstance(deployment, dict): - name = deployment.get("model_name") - params = deployment.get("litellm_params") - model = params.get("model") if isinstance(params, dict) else None - return (name if isinstance(name, str) else None), (model if isinstance(model, str) else "") - return deployment.model_name, str(getattr(deployment.litellm_params, "model", "") or "") - - #### [BETA] - This is a beta endpoint, format might change based on user feedback. - https://github.com/BerriAI/litellm/issues/964 @router.post( "/model/delete", @@ -1127,19 +1111,7 @@ async def delete_model( ## DELETE FROM ROUTER ## if llm_router is not None: - deleted_deployment = llm_router.delete_deployment(id=model_info.id) - # delete_deployment only drops the deployment from model_list; the auto/ - # complexity router registries are keyed by model_name and would otherwise - # retain a stale (now unbacked) entry, so evict it here too. Guard on the - # auto_router/ prefix (as clear_cache does): a regular DB model that merely - # shares a model_name with a config-defined router must not evict that router, - # since add_deployment never restores config-defined routers. - deleted_name, deleted_model = _deployment_name_and_model(deleted_deployment) - if deleted_name is not None and deleted_model.startswith("auto_router/"): - llm_router.auto_routers.pop(deleted_name, None) - llm_router.complexity_routers.pop(deleted_name, None) - llm_router.adaptive_routers.pop(deleted_name, None) - llm_router.quality_routers.pop(deleted_name, None) + llm_router.delete_deployment(id=model_info.id) # Runs after the row delete so the sibling check sees post-delete state. if model_params.model_info.team_id is not None: diff --git a/litellm/router.py b/litellm/router.py index 78fe3ff025e..4aa2731466e 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -7734,6 +7734,51 @@ class Router: TaggedPreRoutingStrategy(tags=tags, strategy=strategy), ] + @staticmethod + def _unregister_pre_routing_strategy( + registry: dict[str, list[TaggedPreRoutingStrategy[_PreRoutingStrategyT]]], + model_name: str, + tags: tuple[str, ...], + ) -> bool: + """Drop the strategy registered for this exact (model_name, tags) pair, leaving + strategies registered under the same name with different tags in place. Returns + whether anything was actually dropped.""" + existing = registry.get(model_name, []) + remaining = [entry for entry in existing if entry.tags != tags] + if len(remaining) == len(existing): + return False + if remaining: + registry[model_name] = remaining + else: + registry.pop(model_name, None) + return True + + def _unregister_pre_routing_strategy_for_deployment(self, deployment: Deployment) -> None: + """ + Release the pre-routing strategy a deployment holds, so removing it from the + model_list also frees its (model_name, tags) slot. + + Without this, re-adding the deployment (an edit arriving via upsert_deployment, + or a router recreated under a name that was deleted earlier) hits the + "already exists" guard in `_register_pre_routing_strategy`, which + `ignore_invalid_deployments` swallows - the deployment then silently never + makes it back into the model_list. + + Released from every registry rather than the first match, because registration is + one-to-many: a complexity router configured with `adaptive` is also registered in + `adaptive_routers` under the same (model_name, tags) by the deferred finalize pass. + Guarded on the auto_router/ prefix so removing a *regular* deployment can't evict a + router that merely shares its model_name. + """ + if not deployment.litellm_params.model.startswith("auto_router/"): + return + model_name = deployment.model_name + tags = self._deployment_tags(deployment) + for registry in (self.auto_routers, self.complexity_routers, self.quality_routers): + self._unregister_pre_routing_strategy(registry, model_name, tags) + if self._unregister_pre_routing_strategy(self.adaptive_routers, model_name, tags): + self._sync_adaptive_router_hooks() + def _finalize_adaptive_router_if_configured(self) -> None: """Locate every adaptive-router deployment in the finalized model_list and build an AdaptiveRouter for each. Safe no-op when none are configured. @@ -7779,6 +7824,16 @@ class Router: TaggedPreRoutingStrategy(tags=tagged.tags, strategy=adaptive_router), ] + self._sync_adaptive_router_hooks() + + def _sync_adaptive_router_hooks(self) -> None: + """Rebuild the AdaptiveRouterPostCallHook set so it is exactly one hook per + currently registered adaptive router. Run at every point the adaptive registry + changes, otherwise a released router keeps recording turns through its hook.""" + from litellm.router_strategy.adaptive_router.hooks import ( + AdaptiveRouterPostCallHook, + ) + for callback in litellm.logging_callback_manager.get_custom_loggers_for_type(AdaptiveRouterPostCallHook): litellm.logging_callback_manager.remove_callback_from_all_lists(callback) for tagged_adaptive_routers in self.adaptive_routers.values(): @@ -8401,13 +8456,27 @@ class Router: self._invalidate_access_groups_cache() self._update_deployment_indices_after_removal(model_id=deployment_id, removal_idx=removal_idx) + # Free the outgoing deployment's pre-routing strategy slot (keyed by the + # OLD model_name/tags) before the re-add below re-registers it. + self._unregister_pre_routing_strategy_for_deployment(deployment=_deployment_on_router) + # if the model_id is not in router self.add_deployment(deployment=deployment) + # add_deployment() builds every strategy EXCEPT the adaptive one, which + # set_model_list() defers until the whole model_list is visible. Re-run that + # deferred pass so an adaptive router whose slot was just released above is + # rebuilt rather than left unregistered. + if self._is_adaptive_router_deployment(litellm_params=deployment.litellm_params) or ( + _deployment_on_router is not None + and self._is_adaptive_router_deployment(litellm_params=_deployment_on_router.litellm_params) + ): + self._finalize_adaptive_router_if_configured() return deployment except Exception as e: if self.ignore_invalid_deployments: - verbose_router_logger.debug( - f"Error upserting deployment: {e}, ignoring and continuing with other deployments." + verbose_router_logger.warning( + f"Error upserting deployment {deployment.model_name} (id={deployment.model_info.id}): {e}. " + "Dropping it and continuing with other deployments." ) return None else: @@ -8428,8 +8497,14 @@ class Router: try: if deployment_idx is not None: + try: + deployment_to_remove = self.get_deployment(model_id=id) + except Exception: + deployment_to_remove = None # Pop the item from the list first item = self.model_list.pop(deployment_idx) + if deployment_to_remove is not None: + self._unregister_pre_routing_strategy_for_deployment(deployment=deployment_to_remove) self._invalidate_model_group_info_cache() self._invalidate_access_groups_cache() self._update_deployment_indices_after_removal(model_id=id, removal_idx=deployment_idx) diff --git a/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py index f3e5e2c9b71..bd5eda0197b 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py @@ -636,14 +636,33 @@ class TestDeleteModelClearsRouterRegistry: not just from model_list, or a stale (now unbacked) router entry lingers until restart. """ + @staticmethod + def _complexity_router_deployment(model_id: str, tags: list | None = None) -> dict: + return { + "model_name": "smart-router", + "litellm_params": { + "model": "auto_router/complexity_router", + "complexity_router_config": {"tiers": {"SIMPLE": "gpt-4o-mini", "MEDIUM": "gpt-4o"}}, + "complexity_router_default_model": "gpt-4o", + **({"tags": tags} if tags else {}), + }, + "model_info": {"id": model_id, "db_model": True}, + } + @pytest.mark.asyncio - async def test_delete_model_pops_router_registries(self): + async def test_delete_model_releases_only_the_deleted_routers_slot(self): + """Deleting one tagged router must release its own slot and leave a sibling + sharing the model_name registered. A blanket pop(model_name) here would take + both down, and nothing reloads on the delete path to restore the survivor. + """ + import litellm + from litellm.proxy.management_endpoints.model_management_endpoints import ModelInfoDelete from litellm.proxy.management_endpoints.model_management_endpoints import ( delete_model as delete_model_endpoint, ) - from litellm.proxy.management_endpoints.model_management_endpoints import ModelInfoDelete model_id = "router-del-1" + surviving_id = "router-del-2" admin_user = UserAPIKeyAuth(user_id="admin", user_role=LitellmUserRoles.PROXY_ADMIN) db_row = LiteLLM_ProxyModelTable( model_id=model_id, @@ -660,16 +679,16 @@ class TestDeleteModelClearsRouterRegistry: mock_prisma.db.litellm_proxymodeltable.find_unique = AsyncMock(return_value=db_row) mock_prisma.db.litellm_proxymodeltable.delete = AsyncMock(return_value=db_row) - mock_router = MagicMock() - mock_router.delete_deployment = MagicMock( - return_value={ - "model_name": "smart-router", - "litellm_params": {"model": "auto_router/complexity_router"}, - "model_info": {"id": model_id}, - } + real_router = litellm.Router( + model_list=[ + {"model_name": "gpt-4o", "litellm_params": {"model": "gpt-4o"}}, + {"model_name": "gpt-4o-mini", "litellm_params": {"model": "gpt-4o-mini"}}, + self._complexity_router_deployment(model_id, tags=["team-a"]), + self._complexity_router_deployment(surviving_id, tags=["team-b"]), + ], + ignore_invalid_deployments=True, ) - mock_router.auto_routers = {"smart-router": MagicMock()} - mock_router.complexity_routers = {"smart-router": MagicMock()} + assert len(real_router.complexity_routers["smart-router"]) == 2 _PS = "litellm.proxy.proxy_server" with ( @@ -679,16 +698,17 @@ class TestDeleteModelClearsRouterRegistry: patch(f"{_PS}.proxy_logging_obj", MagicMock()), patch(f"{_PS}.general_settings", {}), patch(f"{_PS}.premium_user", True), - patch(f"{_PS}.llm_router", mock_router), + patch(f"{_PS}.llm_router", real_router), ): await delete_model_endpoint( model_info=ModelInfoDelete(id=model_id), user_api_key_dict=admin_user, ) - mock_router.delete_deployment.assert_called_once_with(id=model_id) - assert "smart-router" not in mock_router.auto_routers - assert "smart-router" not in mock_router.complexity_routers + assert model_id not in [m["model_info"]["id"] for m in real_router.model_list] + surviving = real_router.complexity_routers["smart-router"] + assert len(surviving) == 1 + assert surviving[0].tags == ("team-b",) @pytest.mark.asyncio async def test_delete_regular_model_preserves_config_router_sharing_name(self): diff --git a/tests/test_litellm/test_router.py b/tests/test_litellm/test_router.py index a9e5b3316e0..3b7bcad78b0 100644 --- a/tests/test_litellm/test_router.py +++ b/tests/test_litellm/test_router.py @@ -5936,3 +5936,274 @@ async def test_acreate_batch_request_bedrock_tags_override_deployment_tags(): bedrock_tags=request_tags, ) assert mock_sign.call_args.kwargs["data"]["tags"] == request_tags + + +class TestPreRoutingStrategyRegistryLifecycle: + """ + Regression tests: a deployment leaving the model_list must release the + pre-routing strategy slot it holds in `auto_routers` / `complexity_routers` / + `adaptive_routers` / `quality_routers`. + + Before this fix, editing an auto-router-family model (a UI save, which reaches + every other pod as an `upsert_deployment` from the periodic DB reload) popped + the deployment out of the model_list and then failed to re-add it: registration + raised "already exists" against the stale registry entry, and + `ignore_invalid_deployments=True` swallowed the error. The router vanished from + the Models page and stayed gone until a proxy restart, while the DB row and the + "saved successfully" response both looked fine. + """ + + @staticmethod + def _complexity_router_params(default_model: str, tags=None) -> dict: + return { + "model": "auto_router/complexity_router", + "complexity_router_config": { + "tiers": {"SIMPLE": "gpt-4o-mini", "MEDIUM": "gpt-4o", "COMPLEX": "gpt-4o"} + }, + "complexity_router_default_model": default_model, + **({"tags": tags} if tags else {}), + } + + @classmethod + def _router_with_complexity_router(cls, default_model: str = "gpt-4o") -> "litellm.Router": + return litellm.Router( + model_list=[ + {"model_name": "gpt-4o", "litellm_params": {"model": "gpt-4o"}}, + {"model_name": "gpt-4o-mini", "litellm_params": {"model": "gpt-4o-mini"}}, + { + "model_name": "smart-router", + "litellm_params": cls._complexity_router_params(default_model), + "model_info": {"id": "router-1", "db_model": True}, + }, + ], + ignore_invalid_deployments=True, + ) + + @staticmethod + def _model_names(router: "litellm.Router") -> list: + return [model["model_name"] for model in router.model_list] + + def test_upsert_of_edited_router_keeps_it_routable(self): + from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo + + router = self._router_with_complexity_router() + + router.upsert_deployment( + deployment=Deployment( + model_name="smart-router", + litellm_params=LiteLLM_Params(**self._complexity_router_params("gpt-4o-mini")), + model_info=ModelInfo(id="router-1", db_model=True), + ) + ) + + assert "smart-router" in self._model_names(router) + registered = router.complexity_routers["smart-router"] + assert len(registered) == 1 + # the surviving strategy is the edited one, not the pre-edit leftover + assert registered[0].strategy.config.default_model == "gpt-4o-mini" + + def test_unchanged_upsert_leaves_router_untouched(self): + from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo + + router = self._router_with_complexity_router() + strategy_before = router.complexity_routers["smart-router"][0].strategy + + for _ in range(3): + router.upsert_deployment( + deployment=Deployment( + model_name="smart-router", + litellm_params=LiteLLM_Params(**self._complexity_router_params("gpt-4o")), + model_info=ModelInfo(id="router-1", db_model=True), + ) + ) + + assert "smart-router" in self._model_names(router) + assert router.complexity_routers["smart-router"][0].strategy is strategy_before + + def test_delete_frees_the_name_for_a_new_router(self): + from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo + + router = self._router_with_complexity_router() + + router.delete_deployment(id="router-1") + assert "smart-router" not in router.complexity_routers + + router.add_deployment( + deployment=Deployment( + model_name="smart-router", + litellm_params=LiteLLM_Params(**self._complexity_router_params("gpt-4o-mini")), + model_info=ModelInfo(id="router-2", db_model=True), + ) + ) + + assert "smart-router" in self._model_names(router) + assert router.complexity_routers["smart-router"][0].strategy.config.default_model == "gpt-4o-mini" + + def test_delete_only_frees_the_matching_tag_slot(self): + router = litellm.Router( + model_list=[ + {"model_name": "gpt-4o", "litellm_params": {"model": "gpt-4o"}}, + {"model_name": "gpt-4o-mini", "litellm_params": {"model": "gpt-4o-mini"}}, + { + "model_name": "shared-router", + "litellm_params": self._complexity_router_params("gpt-4o", tags=["team-a"]), + "model_info": {"id": "router-a"}, + }, + { + "model_name": "shared-router", + "litellm_params": self._complexity_router_params("gpt-4o-mini", tags=["team-b"]), + "model_info": {"id": "router-b"}, + }, + ], + ignore_invalid_deployments=True, + ) + assert len(router.complexity_routers["shared-router"]) == 2 + + router.delete_deployment(id="router-a") + + remaining = router.complexity_routers["shared-router"] + assert len(remaining) == 1 + assert remaining[0].tags == ("team-b",) + + def test_delete_of_regular_model_preserves_router_sharing_its_name(self): + router = litellm.Router( + model_list=[ + {"model_name": "gpt-4o", "litellm_params": {"model": "gpt-4o"}}, + {"model_name": "gpt-4o-mini", "litellm_params": {"model": "gpt-4o-mini"}}, + { + "model_name": "shared-name", + "litellm_params": self._complexity_router_params("gpt-4o"), + "model_info": {"id": "router-1"}, + }, + { + "model_name": "shared-name", + "litellm_params": {"model": "openai/gpt-4o"}, + "model_info": {"id": "regular-1"}, + }, + ], + ignore_invalid_deployments=True, + ) + strategy = router.complexity_routers["shared-name"][0].strategy + + router.delete_deployment(id="regular-1") + + assert router.complexity_routers["shared-name"][0].strategy is strategy + + def test_upsert_of_edited_adaptive_router_rebuilds_it(self): + """Adaptive routers are built by set_model_list()'s deferred pass, not by + add_deployment(), so releasing the slot on edit must be paired with a rebuild - + otherwise the edit silently turns adaptive routing off.""" + from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo + + def adaptive_params(available_models: list) -> dict: + return { + "model": "auto_router/adaptive_router", + "adaptive_router_config": {"available_models": available_models}, + } + + router = litellm.Router( + model_list=[ + {"model_name": "gpt-4o", "litellm_params": {"model": "openai/gpt-4o"}}, + {"model_name": "gpt-4o-mini", "litellm_params": {"model": "openai/gpt-4o-mini"}}, + { + "model_name": "adaptive-router", + "litellm_params": adaptive_params(["gpt-4o-mini"]), + "model_info": {"id": "router-1", "db_model": True}, + }, + ], + ignore_invalid_deployments=True, + ) + assert "adaptive-router" in router.adaptive_routers + + router.upsert_deployment( + deployment=Deployment( + model_name="adaptive-router", + litellm_params=LiteLLM_Params(**adaptive_params(["gpt-4o", "gpt-4o-mini"])), + model_info=ModelInfo(id="router-1", db_model=True), + ) + ) + + assert "adaptive-router" in self._model_names(router) + registered = router.adaptive_routers["adaptive-router"] + assert len(registered) == 1 + assert set(registered[0].strategy.config.available_models) == {"gpt-4o", "gpt-4o-mini"} + + def test_delete_of_adaptive_enabled_complexity_router_frees_both_registries(self): + """A complexity router with adaptive set is registered in BOTH complexity_routers + and adaptive_routers under the same (model_name, tags). Releasing only the first + match leaves the adaptive strategy live, so a deleted alias stays routable and its + post-call hook keeps recording.""" + import litellm as litellm_module + from litellm.router_strategy.adaptive_router.hooks import AdaptiveRouterPostCallHook + + params = { + "model": "auto_router/complexity_router", + "complexity_router_config": { + "tiers": {"SIMPLE": "gpt-4o-mini", "MEDIUM": "gpt-4o"}, + "adaptive": True, + }, + "complexity_router_default_model": "gpt-4o", + } + router = litellm.Router( + model_list=[ + {"model_name": "gpt-4o", "litellm_params": {"model": "openai/gpt-4o"}}, + {"model_name": "gpt-4o-mini", "litellm_params": {"model": "openai/gpt-4o-mini"}}, + { + "model_name": "hybrid-router", + "litellm_params": params, + "model_info": {"id": "router-1", "db_model": True}, + }, + ], + ignore_invalid_deployments=True, + ) + assert "hybrid-router" in router.complexity_routers + assert "hybrid-router" in router.adaptive_routers + hooks = litellm_module.logging_callback_manager.get_custom_loggers_for_type(AdaptiveRouterPostCallHook) + assert len(hooks) == 1 + + router.delete_deployment(id="router-1") + + assert "hybrid-router" not in router.complexity_routers + assert "hybrid-router" not in router.adaptive_routers + remaining_hooks = litellm_module.logging_callback_manager.get_custom_loggers_for_type( + AdaptiveRouterPostCallHook + ) + assert remaining_hooks == [] + + def test_upsert_of_edited_quality_router_keeps_it_routable(self): + """_unregister_pre_routing_strategy_for_deployment dispatches on four prefixes; + quality_router is one of them and would otherwise go unexercised.""" + from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo + + def quality_params(default_model: str) -> dict: + return { + "model": "auto_router/quality_router", + "quality_router_default_model": default_model, + } + + router = litellm.Router( + model_list=[ + {"model_name": "gpt-4o", "litellm_params": {"model": "openai/gpt-4o"}}, + {"model_name": "gpt-4o-mini", "litellm_params": {"model": "openai/gpt-4o-mini"}}, + { + "model_name": "quality-router", + "litellm_params": quality_params("gpt-4o"), + "model_info": {"id": "router-1", "db_model": True}, + }, + ], + ignore_invalid_deployments=True, + ) + assert "quality-router" in router.quality_routers + + router.upsert_deployment( + deployment=Deployment( + model_name="quality-router", + litellm_params=LiteLLM_Params(**quality_params("gpt-4o-mini")), + model_info=ModelInfo(id="router-1", db_model=True), + ) + ) + + assert "quality-router" in self._model_names(router) + registered = router.quality_routers["quality-router"] + assert len(registered) == 1 + assert registered[0].strategy.config.default_model == "gpt-4o-mini" From 4299c6d191057ac06af49700baba16028481c8fc Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Mon, 27 Jul 2026 14:48:18 -0700 Subject: [PATCH 28/75] fix(responses-bridge): return CustomStreamWrapper from the completed-response stream helper --- .../litellm_responses_transformation/handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litellm/completion_extras/litellm_responses_transformation/handler.py b/litellm/completion_extras/litellm_responses_transformation/handler.py index cf517440cd5..15f5b28e30e 100644 --- a/litellm/completion_extras/litellm_responses_transformation/handler.py +++ b/litellm/completion_extras/litellm_responses_transformation/handler.py @@ -354,7 +354,7 @@ class ResponsesToCompletionBridgeHandler: custom_llm_provider: str, logging_obj: "LiteLLMLoggingObj", json_mode: bool | None, - ) -> Any: + ) -> "CustomStreamWrapper": from litellm.litellm_core_utils.streaming_handler import CustomStreamWrapper from litellm.llms.base_llm.base_model_iterator import MockResponseIterator From 47a0c22f64a3fb0fc69032ae3e711213d648a936 Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Mon, 27 Jul 2026 14:54:39 -0700 Subject: [PATCH 29/75] fix(router): rebuild the adaptive companion when an upserted complexity router participates in adaptive routing The finalize re-run in upsert_deployment keyed off the auto_router/adaptive_router prefix only, so editing a complexity router with adaptive enabled released its adaptive_routers entry (and post-call hook) without rebuilding it: complexity routing kept serving while bandit recording, DB persistence and /adaptive_router/state went silently dark until the next full reload. Gate the re-run on a participation predicate that mirrors both arms of the finalize pass, drop the import that pass no longer uses, and pin the registry helpers with direct contract tests --- litellm/router.py | 30 +++--- tests/test_litellm/test_router.py | 156 ++++++++++++++++++++++++++++++ 2 files changed, 175 insertions(+), 11 deletions(-) diff --git a/litellm/router.py b/litellm/router.py index 4aa2731466e..29f548ca284 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -7702,6 +7702,21 @@ class Router: """True when this deployment opts in via the `auto_router/adaptive_router` model prefix.""" return litellm_params.model.startswith("auto_router/adaptive_router") + def _deployment_participates_in_adaptive_routing(self, litellm_params: LiteLLM_Params) -> bool: + """True when this deployment owns an `adaptive_routers` entry once finalized: + a dedicated adaptive router, or a complexity router whose config enables the + adaptive companion. Mirrors the two arms of + `_finalize_adaptive_router_if_configured`, which is the registry's only writer.""" + if self._is_adaptive_router_deployment(litellm_params=litellm_params): + return True + if not self._is_complexity_router_deployment(litellm_params=litellm_params): + return False + config = litellm_params.complexity_router_config + if not config: + return False + adaptive_flag: object = config.get("adaptive") + return bool(adaptive_flag) + @staticmethod def _has_registered_strategy( registry: dict[str, list[TaggedPreRoutingStrategy[_PreRoutingStrategyT]]], @@ -7784,15 +7799,6 @@ class Router: build an AdaptiveRouter for each. Safe no-op when none are configured. Idempotent: skips any deployment whose (model_name, tags) pair is already initialized, so hot-reloads don't rebuild routers that would lose state.""" - # Drop any adaptive-router hooks left over from a previous Router - # instance (e.g. after `/config/reload` replaced `llm_router`). Without - # this, stale AdaptiveRouterPostCallHook callbacks from the old Router - # remain wired up in `litellm.callbacks` and double-fire signal - # recording for every request. - from litellm.router_strategy.adaptive_router.hooks import ( - AdaptiveRouterPostCallHook, - ) - for entry in self.model_list or []: lp = entry.get("litellm_params") if isinstance(entry, dict) else entry.litellm_params lp_model = (lp.get("model") if isinstance(lp, dict) else lp.model) if lp else None @@ -8466,9 +8472,11 @@ class Router: # set_model_list() defers until the whole model_list is visible. Re-run that # deferred pass so an adaptive router whose slot was just released above is # rebuilt rather than left unregistered. - if self._is_adaptive_router_deployment(litellm_params=deployment.litellm_params) or ( + if self._deployment_participates_in_adaptive_routing(litellm_params=deployment.litellm_params) or ( _deployment_on_router is not None - and self._is_adaptive_router_deployment(litellm_params=_deployment_on_router.litellm_params) + and self._deployment_participates_in_adaptive_routing( + litellm_params=_deployment_on_router.litellm_params + ) ): self._finalize_adaptive_router_if_configured() return deployment diff --git a/tests/test_litellm/test_router.py b/tests/test_litellm/test_router.py index 3b7bcad78b0..f04e4a60283 100644 --- a/tests/test_litellm/test_router.py +++ b/tests/test_litellm/test_router.py @@ -6207,3 +6207,159 @@ class TestPreRoutingStrategyRegistryLifecycle: registered = router.quality_routers["quality-router"] assert len(registered) == 1 assert registered[0].strategy.config.default_model == "gpt-4o-mini" + + @staticmethod + def _hybrid_router_params(tiers: dict) -> dict: + return { + "model": "auto_router/complexity_router", + "complexity_router_config": {"tiers": tiers, "adaptive": True}, + "complexity_router_default_model": "gpt-4o", + } + + @classmethod + def _router_with_hybrid_router(cls) -> "litellm.Router": + return litellm.Router( + model_list=[ + {"model_name": "gpt-4o", "litellm_params": {"model": "openai/gpt-4o"}}, + {"model_name": "gpt-4o-mini", "litellm_params": {"model": "openai/gpt-4o-mini"}}, + { + "model_name": "hybrid-router", + "litellm_params": cls._hybrid_router_params({"SIMPLE": "gpt-4o-mini", "MEDIUM": "gpt-4o"}), + "model_info": {"id": "router-1", "db_model": True}, + }, + ], + ignore_invalid_deployments=True, + ) + + def test_upsert_of_edited_hybrid_complexity_router_relinks_adaptive(self): + """Editing an adaptive-enabled complexity router releases its adaptive companion + along with the complexity slot; the finalize re-run must fire for it (not just for + `auto_router/adaptive_router` deployments) or the rebuilt complexity router keeps + routing while bandit recording, DB persistence and /adaptive_router/state all + silently stop until the next full reload.""" + import litellm as litellm_module + from litellm.router_strategy.adaptive_router.hooks import AdaptiveRouterPostCallHook + from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo + + router = self._router_with_hybrid_router() + assert "hybrid-router" in router.adaptive_routers + + router.upsert_deployment( + deployment=Deployment( + model_name="hybrid-router", + litellm_params=LiteLLM_Params( + **self._hybrid_router_params( + {"SIMPLE": "gpt-4o-mini", "MEDIUM": "gpt-4o", "COMPLEX": "gpt-4o"} + ) + ), + model_info=ModelInfo(id="router-1", db_model=True), + ) + ) + + assert "hybrid-router" in self._model_names(router) + assert "hybrid-router" in router.complexity_routers + assert "hybrid-router" in router.adaptive_routers + rebuilt = router.complexity_routers["hybrid-router"][0].strategy + assert router.adaptive_routers["hybrid-router"][0].strategy is rebuilt.adaptive_router + hooks = litellm_module.logging_callback_manager.get_custom_loggers_for_type(AdaptiveRouterPostCallHook) + assert len(hooks) == 1 + + def test_upsert_turning_adaptive_on_builds_the_companion(self): + """An edit that flips `adaptive: true` on an existing complexity router must + register the companion immediately; neither side of the old prefix-only gate + matches a complexity deployment, so the flip was a silent no-op until restart.""" + from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo + + router = self._router_with_complexity_router() + assert "smart-router" not in router.adaptive_routers + + params = self._complexity_router_params("gpt-4o") + params["complexity_router_config"] = {**params["complexity_router_config"], "adaptive": True} + router.upsert_deployment( + deployment=Deployment( + model_name="smart-router", + litellm_params=LiteLLM_Params(**params), + model_info=ModelInfo(id="router-1", db_model=True), + ) + ) + + assert "smart-router" in router.adaptive_routers + + def test_unregister_pre_routing_strategy_scopes_the_drop_by_tags(self): + """The bool return drives the hook re-sync; a tag mismatch must report False and + leave the registry untouched, and dropping the last entry must free the key.""" + from litellm.types.router import TaggedPreRoutingStrategy + + registry = { + "m": [ + TaggedPreRoutingStrategy(tags=("team-a",), strategy=object()), + TaggedPreRoutingStrategy(tags=(), strategy=object()), + ] + } + + assert litellm.Router._unregister_pre_routing_strategy(registry, "m", ("team-b",)) is False + assert len(registry["m"]) == 2 + + assert litellm.Router._unregister_pre_routing_strategy(registry, "m", ("team-a",)) is True + assert [entry.tags for entry in registry["m"]] == [()] + + assert litellm.Router._unregister_pre_routing_strategy(registry, "m", ()) is True + assert "m" not in registry + + def test_unregister_for_deployment_ignores_non_router_deployments(self): + """Direct twin of the endpoint-level test: a regular deployment that shares a + router's model_name must not evict the router's registry slot.""" + from litellm.types.router import Deployment, LiteLLM_Params, ModelInfo + + router = self._router_with_complexity_router() + + router._unregister_pre_routing_strategy_for_deployment( + deployment=Deployment( + model_name="smart-router", + litellm_params=LiteLLM_Params(model="openai/gpt-4o"), + model_info=ModelInfo(id="plain-1", db_model=True), + ) + ) + + assert "smart-router" in router.complexity_routers + + def test_sync_adaptive_router_hooks_keeps_one_hook_per_registered_router(self): + """Re-syncing must replace, not accumulate: a duplicated hook double-fires + bandit signal recording for every request.""" + import litellm as litellm_module + from litellm.router_strategy.adaptive_router.hooks import AdaptiveRouterPostCallHook + + router = self._router_with_hybrid_router() + + router._sync_adaptive_router_hooks() + router._sync_adaptive_router_hooks() + + hooks = litellm_module.logging_callback_manager.get_custom_loggers_for_type(AdaptiveRouterPostCallHook) + assert len(hooks) == 1 + + def test_deployment_participates_in_adaptive_routing_matrix(self): + """The upsert finalize re-run keys off this predicate for both the incoming and + outgoing deployment; a false negative silently strands the adaptive companion.""" + from litellm.types.router import LiteLLM_Params + + router = self._router_with_complexity_router() + + cases = [ + ({"model": "auto_router/adaptive_router", "adaptive_router_config": {}}, True), + (self._hybrid_router_params({"SIMPLE": "gpt-4o-mini"}), True), + (self._complexity_router_params("gpt-4o"), False), + ( + { + "model": "auto_router/complexity_router", + "complexity_router_config": {"tiers": {"SIMPLE": "gpt-4o-mini"}, "adaptive": False}, + "complexity_router_default_model": "gpt-4o", + }, + False, + ), + ({"model": "openai/gpt-4o"}, False), + ] + for params, expected in cases: + actual = router._deployment_participates_in_adaptive_routing( + litellm_params=LiteLLM_Params(**params) + ) + assert actual is expected, params["model"] From 0171170fc7836a86083b8675a951ffd449bafd8c Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Mon, 27 Jul 2026 15:05:51 -0700 Subject: [PATCH 30/75] fix(ui): validate default team values in Default User Settings (#34815) * fix(ui): validate default team values in Default User Settings The Default User Settings form accepted any free-text team id, and the proxy persisted it without checking the team exists. New users were then silently never added to the default team because the consume-time 404 from team_member_add was swallowed at debug level. Backend: PATCH /update/internal_user_settings now rejects unknown and duplicate team ids with a 400 naming them, before any persistence or team budget side effects. Team-add failures in _add_user_to_team now log at ERROR with user and team ids. UI: DefaultUserSettings rewritten as a shadcn + react-hook-form + zod form following the org-settings pattern. The team id free-text input is replaced with a searchable server-backed team picker, so only existing teams can be selected; zod blocks empty and duplicate rows. The shared deriveErrorMessage helper now unwraps the HTTPException detail.error shape so backend validation errors surface readably in toasts. * fix(ui): restore read-only view with Edit Settings toggle on default user settings Parity with the pre-migration form: the tab renders a read-only summary of the saved defaults, Edit Settings opens the RHF form, Cancel discards pending edits and returns to the summary, and a successful save returns to the summary showing the new values. Model sentinel labels in the summary are derived from ModelSelect's now-exported special values instead of duplicating the strings. * refactor(ui): rename MODEL_SELECT_SPECIAL_VALUES_ARRAY to MODEL_SENTINEL_OPTIONS * fix(ui): move Edit Settings into the card header action slot --- .../internal_user_endpoints.py | 17 +- .../proxy_setting_endpoints.py | 50 ++ .../test_internal_user_endpoints.py | 62 +++ .../test_proxy_setting_endpoints.py | 136 +++++ ui/litellm-dashboard/eslint-suppressions.json | 14 +- .../_components/DefaultUserSettings.test.tsx | 153 ------ .../users/_components/DefaultUserSettings.tsx | 492 ------------------ .../DefaultUserSettingsForm.test.tsx | 296 +++++++++++ .../DefaultUserSettingsForm.tsx | 433 +++++++++++++++ .../default-user-settings/mapper.test.ts | 116 +++++ .../default-user-settings/mapper.ts | 75 +++ .../default-user-settings/schema.test.ts | 64 +++ .../default-user-settings/schema.ts | 44 ++ .../users/_components/view_users.test.tsx | 1 - .../users/_components/view_users.tsx | 9 +- .../components/ModelSelect/ModelSelect.tsx | 4 +- .../src/components/networking.tsx | 39 -- .../shared/PaginatedSearchSelect.tsx | 9 + .../src/lib/http/client.test.ts | 6 + ui/litellm-dashboard/src/lib/http/client.ts | 14 +- 20 files changed, 1318 insertions(+), 716 deletions(-) delete mode 100644 ui/litellm-dashboard/src/app/(dashboard)/users/_components/DefaultUserSettings.test.tsx delete mode 100644 ui/litellm-dashboard/src/app/(dashboard)/users/_components/DefaultUserSettings.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/DefaultUserSettingsForm.test.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/DefaultUserSettingsForm.tsx create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/mapper.test.ts create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/mapper.ts create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/schema.test.ts create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/schema.ts diff --git a/litellm/proxy/management_endpoints/internal_user_endpoints.py b/litellm/proxy/management_endpoints/internal_user_endpoints.py index 89781b9d92c..a2c16e88839 100644 --- a/litellm/proxy/management_endpoints/internal_user_endpoints.py +++ b/litellm/proxy/management_endpoints/internal_user_endpoints.py @@ -260,10 +260,12 @@ async def _add_user_to_team( ) ) else: - verbose_proxy_logger.debug( - "litellm.proxy.management_endpoints.internal_user_endpoints.new_user(): Exception occured - {}".format( - str(e) - ) + verbose_proxy_logger.error( + "litellm.proxy.management_endpoints.internal_user_endpoints._add_user_to_team(): " + "failed to add user %s to team %s - %s", + user_id, + team_id, + str(e), ) except Exception as e: if "already exists" in str(e) or "doesn't exist" in str(e): @@ -279,6 +281,13 @@ async def _add_user_to_team( ) ) else: + verbose_proxy_logger.error( + "litellm.proxy.management_endpoints.internal_user_endpoints._add_user_to_team(): " + "failed to add user %s to team %s - %s", + user_id, + team_id, + str(e), + ) raise e diff --git a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py index 10c71c00110..8f3f8ad1bfc 100644 --- a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py +++ b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py @@ -2,6 +2,7 @@ import asyncio import json import os +from collections import Counter from collections.abc import Mapping from typing import Any, Dict, List, Optional, Set, Tuple, Type, Union from urllib.parse import urlparse @@ -25,6 +26,7 @@ from litellm.repositories.table_repositories import ( SSOConfigRepository, UISettingsRepository, ) +from litellm.repositories.team_repository import TeamRepository from litellm.types.proxy.management_endpoints.ui_sso import ( DefaultTeamSSOParams, SSOConfig, @@ -598,6 +600,51 @@ async def get_default_team_settings(): ) +def _default_team_ids(teams: list[str] | list[NewUserRequestTeam]) -> tuple[str, ...]: + return tuple(team if isinstance(team, str) else team.team_id for team in teams) + + +async def _validate_default_teams_exist(teams: list[str] | list[NewUserRequestTeam]) -> None: + """Reject default teams that cannot be assigned. + + New users are added to these teams long after the settings are saved, and that + consume path swallows the resulting 404, so an unknown team id would silently + drop every future user's team assignment unless it is caught here. + """ + team_ids = _default_team_ids(teams) + if not team_ids: + return + + duplicate_ids = tuple(team_id for team_id, count in Counter(team_ids).items() if count > 1) + if duplicate_ids: + raise HTTPException( + status_code=400, + detail={ + "error": f"Duplicate default team id(s): {', '.join(duplicate_ids)}. List each default team only once." + }, + ) + + from litellm.proxy.proxy_server import prisma_client + + if prisma_client is None: + raise HTTPException( + status_code=500, + detail={"error": "Database not connected. Please connect a database."}, + ) + + existing_teams = await TeamRepository(prisma_client).find_many(where={"team_id": {"in": list(team_ids)}}) + existing_team_ids = {team.team_id for team in existing_teams} + missing_ids = tuple(team_id for team_id in team_ids if team_id not in existing_team_ids) + if missing_ids: + raise HTTPException( + status_code=400, + detail={ + "error": f"Team(s) not found: {', '.join(missing_ids)}. " + "A team must exist before it can be set as a default team for new users." + }, + ) + + async def update_default_team_member_budget(teams: List[NewUserRequestTeam], user_api_key_dict: UserAPIKeyAuth): """ 1. Update the max member budget for the team @@ -706,6 +753,9 @@ async def update_internal_user_settings( Update the default internal user parameters for SSO users. These settings will be applied to new users who sign in via SSO. """ + if settings.teams is not None: + await _validate_default_teams_exist(settings.teams) + if settings.teams is not None and all(isinstance(team, NewUserRequestTeam) for team in settings.teams): await update_default_team_member_budget( settings.teams, diff --git a/tests/test_litellm/proxy/management_endpoints/test_internal_user_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_internal_user_endpoints.py index be0267d69c5..5cbc3e72d83 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_internal_user_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_internal_user_endpoints.py @@ -3605,3 +3605,65 @@ async def test_add_new_user_to_default_team_string_teams_have_no_member_budget(m assert mock_add.call_args.kwargs["max_budget_in_team"] is None assert mock_add.call_args.kwargs["team_id"] == "string-team" + + +@pytest.mark.asyncio +async def test_add_user_to_team_logs_unknown_team_at_error(mocker, caplog): + """A default team that no longer exists makes every membership write 404. + + The failure is swallowed so user creation still succeeds, so the log line is + the only signal an operator gets; it must be ERROR and name the team. + """ + import logging + + from fastapi import HTTPException + + from litellm.proxy.management_endpoints.internal_user_endpoints import ( + _add_user_to_team, + ) + + mocker.patch( + "litellm.proxy.management_endpoints.team_endpoints.team_member_add", + new_callable=mocker.AsyncMock, + side_effect=HTTPException(status_code=404, detail={"error": "Team not found"}), + ) + + with caplog.at_level(logging.DEBUG, logger="LiteLLM Proxy"): + await _add_user_to_team( + user_id="sso-user", + team_id="deleted-team", + user_api_key_dict=UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN), + ) + + errors = [r.getMessage() for r in caplog.records if r.levelno >= logging.ERROR] + assert len(errors) == 1, f"expected exactly one ERROR log, got {errors}" + assert "deleted-team" in errors[0] + assert "sso-user" in errors[0] + + +@pytest.mark.asyncio +async def test_add_user_to_team_keeps_already_a_member_quiet(mocker, caplog): + """Re-adding an existing member is expected on every login and must not + produce an ERROR, otherwise the real failures above are lost in the noise.""" + import logging + + from fastapi import HTTPException + + from litellm.proxy.management_endpoints.internal_user_endpoints import ( + _add_user_to_team, + ) + + mocker.patch( + "litellm.proxy.management_endpoints.team_endpoints.team_member_add", + new_callable=mocker.AsyncMock, + side_effect=HTTPException(status_code=400, detail={"error": "User already exists in team"}), + ) + + with caplog.at_level(logging.DEBUG, logger="LiteLLM Proxy"): + await _add_user_to_team( + user_id="sso-user", + team_id="existing-team", + user_api_key_dict=UserAPIKeyAuth(user_role=LitellmUserRoles.PROXY_ADMIN), + ) + + assert [r.getMessage() for r in caplog.records if r.levelno >= logging.ERROR] == [] diff --git a/tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py b/tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py index 20451f5d0ac..d4fd5bc2dce 100644 --- a/tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py +++ b/tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py @@ -2680,6 +2680,142 @@ def test_update_ui_settings_writes_audit_log(monkeypatch): app.dependency_overrides.pop(user_api_key_auth, None) +@pytest.fixture +def mock_team_lookup(monkeypatch): + """Back /update/internal_user_settings with a fake team table. + + Yields the set of team ids that exist; the test mutates it before the call. + Also exposes the find_many mock so a test can assert the lookup was skipped. + """ + from unittest.mock import AsyncMock, MagicMock + + import litellm + import litellm.proxy.proxy_server as proxy_server_module + + existing_team_ids: set = set() + + async def _find_many(where): + requested = where["team_id"]["in"] + return [{"team_id": team_id} for team_id in requested if team_id in existing_team_ids] + + find_many = AsyncMock(side_effect=_find_many) + fake_prisma = MagicMock() + fake_prisma.db.litellm_teamtable.find_many = find_many + + member_budget_update = AsyncMock() + + monkeypatch.setattr(proxy_server_module, "prisma_client", fake_prisma) + monkeypatch.setattr("litellm.proxy.proxy_server.store_model_in_db", True) + monkeypatch.setattr(litellm, "default_internal_user_params", {}) + monkeypatch.setattr( + "litellm.proxy.ui_crud_endpoints.proxy_setting_endpoints.update_default_team_member_budget", + member_budget_update, + ) + + return { + "existing_team_ids": existing_team_ids, + "find_many": find_many, + "member_budget_update": member_budget_update, + } + + +def test_update_internal_user_settings_rejects_unknown_team_object(mock_proxy_config, mock_auth, mock_team_lookup): + """Regression: saving a default team that doesn't exist used to return 200, + then silently fail for every SSO user because the membership write 404s.""" + mock_team_lookup["existing_team_ids"].add("real-team") + + resp = client.patch( + "/update/internal_user_settings", + json={ + "max_budget": 10.0, + "teams": [ + {"team_id": "real-team", "max_budget_in_team": 5.0}, + {"team_id": "ghost-team"}, + ], + }, + ) + + assert resp.status_code == 400, resp.text + assert "ghost-team" in resp.json()["detail"]["error"] + assert "real-team" not in resp.json()["detail"]["error"] + assert mock_proxy_config["save_call_count"]() == 0 + assert mock_team_lookup["member_budget_update"].await_count == 0, ( + "per-member budgets must not be written before the team ids are validated" + ) + + import litellm + + assert litellm.default_internal_user_params == {} + + +def test_update_internal_user_settings_rejects_unknown_team_string(mock_proxy_config, mock_auth, mock_team_lookup): + """The bare-string team shape must be validated too.""" + resp = client.patch( + "/update/internal_user_settings", + json={"teams": ["ghost-team"]}, + ) + + assert resp.status_code == 400, resp.text + assert "ghost-team" in resp.json()["detail"]["error"] + assert mock_proxy_config["save_call_count"]() == 0 + + +def test_update_internal_user_settings_rejects_duplicate_team_ids(mock_proxy_config, mock_auth, mock_team_lookup): + """Listing a team twice makes its per-member budget a race between the two + entries, so the payload is rejected rather than silently resolved.""" + mock_team_lookup["existing_team_ids"].add("real-team") + + resp = client.patch( + "/update/internal_user_settings", + json={ + "teams": [ + {"team_id": "real-team", "max_budget_in_team": 5.0}, + {"team_id": "real-team", "max_budget_in_team": 50.0}, + ] + }, + ) + + assert resp.status_code == 400, resp.text + assert "real-team" in resp.json()["detail"]["error"] + assert mock_proxy_config["save_call_count"]() == 0 + + +def test_update_internal_user_settings_saves_when_all_teams_exist(mock_proxy_config, mock_auth, mock_team_lookup): + """Valid team ids still save, and still reach the per-member budget update.""" + mock_team_lookup["existing_team_ids"].update({"team-a", "team-b"}) + + resp = client.patch( + "/update/internal_user_settings", + json={ + "max_budget": 10.0, + "teams": [ + {"team_id": "team-a", "max_budget_in_team": 5.0}, + {"team_id": "team-b"}, + ], + }, + ) + + assert resp.status_code == 200, resp.text + assert [team["team_id"] for team in resp.json()["settings"]["teams"]] == [ + "team-a", + "team-b", + ] + assert mock_proxy_config["save_call_count"]() == 1 + mock_team_lookup["member_budget_update"].assert_awaited_once() + + +def test_update_internal_user_settings_without_teams_skips_team_lookup(mock_proxy_config, mock_auth, mock_team_lookup): + """Settings changes that don't touch teams must not pay for a DB round trip.""" + resp = client.patch( + "/update/internal_user_settings", + json={"max_budget": 10.0}, + ) + + assert resp.status_code == 200, resp.text + mock_team_lookup["find_many"].assert_not_awaited() + assert mock_proxy_config["save_call_count"]() == 1 + + def test_update_mcp_semantic_filter_settings_requires_proxy_admin(monkeypatch): """Non-admin callers must not mutate global MCP semantic filter settings.""" from litellm.proxy._types import UserAPIKeyAuth diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index 998422a78d2..d79c755ebdd 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -1867,11 +1867,6 @@ "count": 1 } }, - "src/app/(dashboard)/users/_components/DefaultUserSettings.tsx": { - "no-restricted-imports": { - "count": 2 - } - }, "src/app/(dashboard)/users/_components/edit_user.tsx": { "local/filename-pascal-case": { "count": 1 @@ -3353,10 +3348,10 @@ "count": 5 }, "no-restricted-syntax": { - "count": 154 + "count": 153 }, "prefer-const": { - "count": 33 + "count": 32 } }, "src/components/object_permissions_view.tsx": { @@ -4321,11 +4316,6 @@ "count": 1 } }, - "src/lib/http/client.ts": { - "no-nested-ternary": { - "count": 1 - } - }, "src/utils/dataUtils.test.ts": { "max-nested-callbacks": { "count": 1 diff --git a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/DefaultUserSettings.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/DefaultUserSettings.test.tsx deleted file mode 100644 index 06dafcfcffd..00000000000 --- a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/DefaultUserSettings.test.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import { act, fireEvent, render, screen, waitFor } from "@testing-library/react"; -import { beforeEach, describe, expect, it, vi } from "vitest"; -import DefaultUserSettings from "./DefaultUserSettings"; -import * as networking from "@/components/networking"; - -vi.mock("@/components/networking", () => ({ - getInternalUserSettings: vi.fn(), - updateInternalUserSettings: vi.fn(), - modelAvailableCall: vi.fn(), -})); - -vi.mock("@/components/common_components/budget_duration_dropdown", () => ({ - default: ({ value, onChange }: { value: string | null; onChange: (value: string | null) => void }) => ( - - ), - getBudgetDurationLabel: (value: string) => value, -})); - -vi.mock("@/components/key_team_helpers/fetch_available_models_team_key", () => ({ - getModelDisplayName: (model: string) => model, -})); - -describe("DefaultUserSettings", () => { - const mockGetInternalUserSettings = vi.mocked(networking.getInternalUserSettings); - const mockUpdateInternalUserSettings = vi.mocked(networking.updateInternalUserSettings); - const mockModelAvailableCall = vi.mocked(networking.modelAvailableCall); - - const defaultProps = { - accessToken: "test-token", - userID: "user-123", - userRole: "Admin", - possibleUIRoles: { - internal_user_admin: { - ui_label: "Admin", - description: "Full access", - }, - internal_user_viewer: { - ui_label: "Viewer", - description: "Read-only access", - }, - }, - }; - - const mockSettings = { - values: { - user_role: "internal_user_admin", - budget_duration: "monthly", - max_budget: 1000, - teams: [], - }, - field_schema: { - description: "Default user settings", - properties: { - user_role: { - type: "string", - description: "User role", - }, - budget_duration: { - type: "string", - description: "Budget duration", - }, - max_budget: { - type: "number", - description: "Maximum budget", - }, - teams: { - type: "array", - description: "Teams", - }, - }, - }, - }; - - beforeEach(() => { - mockGetInternalUserSettings.mockClear(); - mockUpdateInternalUserSettings.mockClear(); - mockModelAvailableCall.mockClear(); - mockModelAvailableCall.mockResolvedValue({ - data: [{ id: "gpt-4" }, { id: "gpt-3.5-turbo" }], - }); - }); - - it("should render", async () => { - mockGetInternalUserSettings.mockResolvedValue(mockSettings); - - render(); - - await waitFor(() => { - expect(mockGetInternalUserSettings).toHaveBeenCalled(); - }); - - expect(screen.getByText("Default User Settings")).toBeInTheDocument(); - }); - - it("should toggle edit mode when edit button is clicked", async () => { - mockGetInternalUserSettings.mockResolvedValue(mockSettings); - - render(); - - await waitFor(() => { - expect(screen.getByText("Edit Settings")).toBeInTheDocument(); - }); - - const editButton = screen.getByText("Edit Settings"); - act(() => { - fireEvent.click(editButton); - }); - - expect(screen.getByText("Cancel")).toBeInTheDocument(); - expect(screen.getByText("Save Changes")).toBeInTheDocument(); - expect(screen.queryByText("Edit Settings")).not.toBeInTheDocument(); - }); - - it("should save settings when save button is clicked", async () => { - mockGetInternalUserSettings.mockResolvedValue(mockSettings); - mockUpdateInternalUserSettings.mockResolvedValue({ - settings: { - ...mockSettings.values, - max_budget: 2000, - }, - }); - - render(); - - await waitFor(() => { - expect(screen.getByText("Edit Settings")).toBeInTheDocument(); - }); - - const editButton = screen.getByText("Edit Settings"); - act(() => { - fireEvent.click(editButton); - }); - - await waitFor(() => { - expect(screen.getByText("Save Changes")).toBeInTheDocument(); - }); - - const saveButton = screen.getByText("Save Changes"); - act(() => { - fireEvent.click(saveButton); - }); - - await waitFor(() => { - expect(mockUpdateInternalUserSettings).toHaveBeenCalled(); - }); - - expect(screen.getByText("Edit Settings")).toBeInTheDocument(); - }); -}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/DefaultUserSettings.tsx b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/DefaultUserSettings.tsx deleted file mode 100644 index 7fee2e14b27..00000000000 --- a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/DefaultUserSettings.tsx +++ /dev/null @@ -1,492 +0,0 @@ -import React, { useState, useEffect } from "react"; -import { Card, Title, Text, Divider, TextInput } from "@tremor/react"; -import { Button, Typography, Spin, Switch, Select, InputNumber } from "antd"; -import { PlusOutlined, DeleteOutlined } from "@ant-design/icons"; -import { getInternalUserSettings, updateInternalUserSettings, modelAvailableCall } from "@/components/networking"; -import BudgetDurationDropdown, { - getBudgetDurationLabel, -} from "@/components/common_components/budget_duration_dropdown"; -import { getModelDisplayName } from "@/components/key_team_helpers/fetch_available_models_team_key"; -import { formatNumberWithCommas } from "@/utils/dataUtils"; -import NotificationManager from "@/components/molecules/notifications_manager"; - -interface DefaultUserSettingsProps { - accessToken: string | null; - possibleUIRoles?: Record> | null; - userID: string; - userRole: string; -} - -interface TeamEntry { - team_id: string; - max_budget_in_team?: number; - user_role: "user" | "admin"; -} - -const DefaultUserSettings: React.FC = ({ - accessToken, - possibleUIRoles, - userID, - userRole, -}) => { - const [loading, setLoading] = useState(true); - const [settings, setSettings] = useState(null); - const [isEditing, setIsEditing] = useState(false); - const [editedValues, setEditedValues] = useState({}); - const [saving, setSaving] = useState(false); - const [availableModels, setAvailableModels] = useState([]); - const { Paragraph } = Typography; - const { Option } = Select; - - useEffect(() => { - const fetchSSOSettings = async () => { - if (!accessToken) { - setLoading(false); - return; - } - - try { - const data = await getInternalUserSettings(accessToken); - setSettings(data); - setEditedValues(data.values || {}); - - // Fetch available models - if (accessToken) { - try { - const modelResponse = await modelAvailableCall(accessToken, userID, userRole); - if (modelResponse && modelResponse.data) { - const modelNames = modelResponse.data.map((model: { id: string }) => model.id); - setAvailableModels(modelNames); - } - } catch (error) { - console.error("Error fetching available models:", error); - } - } - } catch (error) { - console.error("Error fetching SSO settings:", error); - NotificationManager.fromBackend("Failed to fetch SSO settings"); - } finally { - setLoading(false); - } - }; - - fetchSSOSettings(); - }, [accessToken]); - - const handleSaveSettings = async () => { - if (!accessToken) return; - - setSaving(true); - try { - // Convert empty strings to null - const processedValues = Object.entries(editedValues).reduce( - (acc, [key, value]) => { - acc[key] = value === "" ? null : value; - return acc; - }, - {} as Record, - ); - - const updatedSettings = await updateInternalUserSettings(accessToken, processedValues); - setSettings({ ...settings, values: updatedSettings.settings }); - setIsEditing(false); - } catch (error) { - console.error("Error updating SSO settings:", error); - NotificationManager.fromBackend("Failed to update settings: " + error); - } finally { - setSaving(false); - } - }; - - const handleTextInputChange = (key: string, value: any) => { - setEditedValues((prev: Record) => ({ - ...prev, - [key]: value, - })); - }; - - // Helper function to normalize teams array to consistent format - const normalizeTeams = (teams: any[]): TeamEntry[] => { - if (!teams || !Array.isArray(teams)) return []; - - return teams.map((team) => { - if (typeof team === "string") { - return { - team_id: team, - user_role: "user" as const, - }; - } else if (typeof team === "object" && team.team_id) { - return { - team_id: team.team_id, - max_budget_in_team: team.max_budget_in_team, - user_role: team.user_role || "user", - }; - } - return { - team_id: "", - user_role: "user" as const, - }; - }); - }; - - // Teams editor component - const renderTeamsEditor = (teams: any[]) => { - const normalizedTeams = normalizeTeams(teams); - - const updateTeam = (index: number, field: keyof TeamEntry, value: any) => { - const updatedTeams = [...normalizedTeams]; - updatedTeams[index] = { - ...updatedTeams[index], - [field]: value, - }; - handleTextInputChange("teams", updatedTeams); - }; - - const addTeam = () => { - const newTeam: TeamEntry = { - team_id: "", - user_role: "user", - }; - handleTextInputChange("teams", [...normalizedTeams, newTeam]); - }; - - const removeTeam = (index: number) => { - const updatedTeams = normalizedTeams.filter((_, i) => i !== index); - handleTextInputChange("teams", updatedTeams); - }; - - return ( -
- {normalizedTeams.map((team, index) => ( -
-
- Team {index + 1} - -
- -
-
- Team ID - updateTeam(index, "team_id", e.target.value)} - placeholder="Enter team ID" - /> -
- -
- Max Budget in Team - updateTeam(index, "max_budget_in_team", value)} - placeholder="Optional" - min={0} - step={0.01} - precision={2} - /> -
- -
- User Role - -
-
-
- ))} - - -
- ); - }; - - const renderEditableField = (key: string, property: any, value: any) => { - const type = property.type; - - if (key === "teams") { - return
{renderTeamsEditor(editedValues[key] || [])}
; - } else if (key === "user_role" && possibleUIRoles) { - return ( - - ); - } else if (key === "budget_duration") { - return ( - handleTextInputChange(key, value)} - className="mt-2" - /> - ); - } else if (type === "boolean") { - return ( -
- handleTextInputChange(key, checked)} /> -
- ); - } else if (type === "array" && property.items?.enum) { - return ( - - ); - } else if (key === "models") { - return ( - - ); - } else if (type === "string" && property.enum) { - return ( - - ); - } else { - return ( - handleTextInputChange(key, e.target.value)} - placeholder={property.description || ""} - className="mt-2" - /> - ); - } - }; - - const renderValue = (key: string, value: any): JSX.Element => { - if (value === null || value === undefined) return Not set; - - if (key === "teams" && Array.isArray(value)) { - if (value.length === 0) return No teams assigned; - - const normalizedTeams = normalizeTeams(value); - - return ( -
- {normalizedTeams.map((team, index) => ( -
-
-
- Team ID: -

{team.team_id || "Not specified"}

-
-
- Max Budget: -

- {team.max_budget_in_team !== undefined - ? `$${formatNumberWithCommas(team.max_budget_in_team, 4)}` - : "No limit"} -

-
-
- Role: -

{team.user_role}

-
-
-
- ))} -
- ); - } - - if (key === "user_role" && possibleUIRoles && possibleUIRoles[value]) { - const { ui_label, description } = possibleUIRoles[value]; - return ( -
- {ui_label} - {description &&

{description}

} -
- ); - } - - if (key === "budget_duration") { - return {getBudgetDurationLabel(value)}; - } - - if (typeof value === "boolean") { - return {value ? "Enabled" : "Disabled"}; - } - - if (key === "models" && Array.isArray(value)) { - if (value.length === 0) return None; - - return ( -
- {value.map((model, index) => ( - - {getModelDisplayName(model)} - - ))} -
- ); - } - - if (typeof value === "object") { - if (Array.isArray(value)) { - if (value.length === 0) return None; - - return ( -
- {value.map((item, index) => ( - - {typeof item === "object" ? JSON.stringify(item) : String(item)} - - ))} -
- ); - } - - return ( -
{JSON.stringify(value, null, 2)}
- ); - } - - return {String(value)}; - }; - - if (loading) { - return ( -
- -
- ); - } - - if (!settings) { - return ( - - No settings available or you do not have permission to view them. - - ); - } - - // Dynamically render settings based on the schema - const renderSettings = () => { - const { values, field_schema } = settings; - - if (!field_schema || !field_schema.properties) { - return No schema information available; - } - - return Object.entries(field_schema.properties).map(([key, property]: [string, any]) => { - const value = values[key]; - const displayName = key.replace(/_/g, " ").replace(/\b\w/g, (l) => l.toUpperCase()); - - return ( -
- {displayName} - - {property.description || "No description available"} - - - {isEditing ? ( -
{renderEditableField(key, property, value)}
- ) : ( -
{renderValue(key, value)}
- )} -
- ); - }); - }; - - return ( - -
- Default User Settings - {!loading && - settings && - (isEditing ? ( -
- - -
- ) : ( - - ))} -
- - {settings?.field_schema?.description && ( - {settings.field_schema.description} - )} - - -
{renderSettings()}
-
- ); -}; - -export default DefaultUserSettings; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/DefaultUserSettingsForm.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/DefaultUserSettingsForm.test.tsx new file mode 100644 index 00000000000..bfdcc70fb0c --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/DefaultUserSettingsForm.test.tsx @@ -0,0 +1,296 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { render, screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import React from "react"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +vi.mock("@/app/(dashboard)/hooks/teams/useTeams", () => ({ + useInfiniteTeams: () => ({ + data: { + pages: [ + { + teams: [ + { team_id: "team-alpha", team_alias: "Alpha" }, + { team_id: "team-beta", team_alias: "Beta" }, + ], + }, + ], + }, + fetchNextPage: vi.fn(), + hasNextPage: false, + isFetchingNextPage: false, + isLoading: false, + }), +})); + +vi.mock("@/components/ModelSelect/ModelSelect", async (importOriginal) => { + const actual = await importOriginal(); + return { + MODEL_SENTINEL_OPTIONS: actual.MODEL_SENTINEL_OPTIONS, + ModelSelect: ({ onChange }: { onChange: (values: string[]) => void }) => ( + + ), + }; +}); + +import NotificationsManager from "@/components/molecules/notifications_manager"; + +import { DefaultUserSettingsForm } from "./DefaultUserSettingsForm"; +import type { InternalUserSettings } from "./mapper"; + +const POSSIBLE_UI_ROLES = { + internal_user: { ui_label: "Internal User", description: "create and view own keys" }, + internal_user_viewer: { ui_label: "Internal Viewer", description: "view own keys" }, + proxy_admin: { ui_label: "Admin", description: "all permissions" }, +}; + +const SETTINGS: InternalUserSettings = { + values: { + user_role: "internal_user", + max_budget: 100, + budget_duration: "30d", + models: ["gpt-5.2"], + teams: [{ team_id: "team-alpha", max_budget_in_team: 25, user_role: "user" }], + }, + field_schema: {}, +}; + +const SAVED_BODY = { + user_role: "internal_user", + max_budget: 100, + budget_duration: "30d", + models: ["gpt-5.2"], + teams: [{ team_id: "team-alpha", max_budget_in_team: 25, user_role: "user" }], +}; + +const renderForm = (overrides?: { + fetchSettings?: ReturnType; + updateSettings?: ReturnType; +}) => { + const fetchSettings = overrides?.fetchSettings ?? vi.fn().mockResolvedValue(SETTINGS); + const updateSettings = overrides?.updateSettings ?? vi.fn().mockResolvedValue(undefined); + const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }); + + render( + + + , + ); + + return { fetchSettings, updateSettings }; +}; + +const saveButton = async () => await screen.findByRole("button", { name: "Save Changes" }); + +const enterEditMode = async (user: ReturnType) => { + await user.click(await screen.findByRole("button", { name: "Edit Settings" })); +}; + +describe("DefaultUserSettingsForm", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("shows a read-only summary until Edit Settings is clicked", async () => { + renderForm(); + + expect(await screen.findByText("Internal User")).toBeInTheDocument(); + expect(screen.getByText("100")).toBeInTheDocument(); + expect(screen.getByText("monthly")).toBeInTheDocument(); + expect(screen.getByText("gpt-5.2")).toBeInTheDocument(); + expect(screen.getByText(/team-alpha/)).toBeInTheDocument(); + expect(screen.queryByRole("button", { name: "Save Changes" })).not.toBeInTheDocument(); + expect(screen.queryByLabelText("Max Budget (USD)")).not.toBeInTheDocument(); + }); + + it("labels model sentinels in the read-only summary", async () => { + renderForm({ + fetchSettings: vi + .fn() + .mockResolvedValue({ ...SETTINGS, values: { ...SETTINGS.values, models: ["all-proxy-models"] } }), + }); + + expect(await screen.findByText("All Proxy Models")).toBeInTheDocument(); + }); + + it("disables Save until the loaded settings are edited", async () => { + const user = userEvent.setup(); + renderForm(); + + await enterEditMode(user); + + expect(await saveButton()).toBeDisabled(); + }); + + it("shows an error instead of the form when the settings cannot be loaded", async () => { + renderForm({ fetchSettings: vi.fn().mockRejectedValue(new Error("nope")) }); + + expect(await screen.findByRole("alert")).toHaveTextContent("Could not load the default user settings."); + expect(screen.queryByRole("button", { name: "Edit Settings" })).not.toBeInTheDocument(); + expect(screen.queryByRole("button", { name: "Save Changes" })).not.toBeInTheDocument(); + }); + + it("sends every field on save, not only the edited one", async () => { + const user = userEvent.setup(); + const { updateSettings } = renderForm(); + + await enterEditMode(user); + await user.clear(await screen.findByLabelText("Max Budget (USD)")); + await user.type(screen.getByLabelText("Max Budget (USD)"), "250"); + await user.click(await saveButton()); + + await waitFor(() => expect(updateSettings).toHaveBeenCalledTimes(1)); + expect(updateSettings).toHaveBeenCalledWith({ ...SAVED_BODY, max_budget: 250 }); + }); + + it("clears an emptied budget with null", async () => { + const user = userEvent.setup(); + const { updateSettings } = renderForm(); + + await enterEditMode(user); + await user.clear(await screen.findByLabelText("Max Budget (USD)")); + await user.click(await saveButton()); + + await waitFor(() => expect(updateSettings).toHaveBeenCalledTimes(1)); + expect(updateSettings).toHaveBeenCalledWith({ ...SAVED_BODY, max_budget: null }); + }); + + it("sends the models selection through unchanged, sentinel values included", async () => { + const user = userEvent.setup(); + const { updateSettings } = renderForm(); + + await enterEditMode(user); + await user.click(await screen.findByRole("button", { name: "set-models" })); + await user.click(await saveButton()); + + await waitFor(() => expect(updateSettings).toHaveBeenCalledTimes(1)); + expect(updateSettings).toHaveBeenCalledWith({ ...SAVED_BODY, models: ["all-proxy-models"] }); + }); + + it("saves a team that was picked from the searchable list", async () => { + const user = userEvent.setup(); + const { updateSettings } = renderForm(); + + await enterEditMode(user); + await user.click(await screen.findByRole("button", { name: "Add Team" })); + await user.click(screen.getAllByLabelText("Team")[1]); + await user.click(await screen.findByText("Beta")); + await user.click(await saveButton()); + + await waitFor(() => expect(updateSettings).toHaveBeenCalledTimes(1)); + expect(updateSettings).toHaveBeenCalledWith({ + ...SAVED_BODY, + teams: [ + { team_id: "team-alpha", max_budget_in_team: 25, user_role: "user" }, + { team_id: "team-beta", max_budget_in_team: null, user_role: "user" }, + ], + }); + }); + + it("never turns a team id typed into the picker into a saved team", async () => { + const user = userEvent.setup(); + const { updateSettings } = renderForm(); + + await enterEditMode(user); + await user.click(await screen.findByRole("button", { name: "Add Team" })); + await user.type(screen.getAllByLabelText("Team")[1], "team-alhpa"); + await user.keyboard("{Escape}"); + await user.click(await saveButton()); + + expect(await screen.findByText("Select a team")).toBeInTheDocument(); + expect(screen.getAllByLabelText("Team")[1]).toHaveValue(""); + expect(updateSettings).not.toHaveBeenCalled(); + }); + + it("blocks saving the same default team twice", async () => { + const user = userEvent.setup(); + const { updateSettings } = renderForm(); + + await enterEditMode(user); + await user.click(await screen.findByRole("button", { name: "Add Team" })); + await user.click(screen.getAllByLabelText("Team")[1]); + await user.click(await screen.findByText("Alpha")); + await user.click(await saveButton()); + + expect(await screen.findByText("This team is already listed")).toBeInTheDocument(); + expect(updateSettings).not.toHaveBeenCalled(); + }); + + it("drops a removed team row from the saved settings", async () => { + const user = userEvent.setup(); + const { updateSettings } = renderForm(); + + await enterEditMode(user); + await user.click(await screen.findByRole("button", { name: "Remove" })); + await user.click(await saveButton()); + + await waitFor(() => expect(updateSettings).toHaveBeenCalledTimes(1)); + expect(updateSettings).toHaveBeenCalledWith({ ...SAVED_BODY, teams: null }); + }); + + it("returns to the read-only view showing the new values after a successful save", async () => { + const user = userEvent.setup(); + const updated = { ...SETTINGS, values: { ...SETTINGS.values, max_budget: 250 } }; + const { updateSettings } = renderForm({ + fetchSettings: vi.fn().mockResolvedValueOnce(SETTINGS).mockResolvedValue(updated), + }); + + await enterEditMode(user); + await user.clear(await screen.findByLabelText("Max Budget (USD)")); + await user.type(screen.getByLabelText("Max Budget (USD)"), "250"); + await user.click(await saveButton()); + + await waitFor(() => expect(updateSettings).toHaveBeenCalledTimes(1)); + expect(await screen.findByRole("button", { name: "Edit Settings" })).toBeInTheDocument(); + expect(await screen.findByText("250")).toBeInTheDocument(); + expect(screen.queryByRole("button", { name: "Save Changes" })).not.toBeInTheDocument(); + expect(NotificationsManager.success).toHaveBeenCalledWith("Default user settings updated successfully"); + + await enterEditMode(user); + expect(await saveButton()).toBeDisabled(); + }); + + it("keeps the edit and surfaces the backend error when the save fails", async () => { + const user = userEvent.setup(); + const { updateSettings } = renderForm({ + updateSettings: vi.fn().mockRejectedValue(new Error("Team(s) not found: team-alhpa.")), + }); + + await enterEditMode(user); + await user.clear(await screen.findByLabelText("Max Budget (USD)")); + await user.type(screen.getByLabelText("Max Budget (USD)"), "250"); + await user.click(await saveButton()); + + await waitFor(() => expect(updateSettings).toHaveBeenCalledTimes(1)); + await waitFor(() => + expect(NotificationsManager.fromBackend).toHaveBeenCalledWith("Team(s) not found: team-alhpa."), + ); + expect(await saveButton()).toBeEnabled(); + expect(screen.getByLabelText("Max Budget (USD)")).toHaveValue(250); + }); + + it("discards edits and returns to the read-only view when Cancel is pressed", async () => { + const user = userEvent.setup(); + const { updateSettings } = renderForm(); + + await enterEditMode(user); + await user.clear(await screen.findByLabelText("Max Budget (USD)")); + await user.type(screen.getByLabelText("Max Budget (USD)"), "250"); + await user.click(screen.getByRole("button", { name: "Cancel" })); + + expect(await screen.findByRole("button", { name: "Edit Settings" })).toBeInTheDocument(); + expect(screen.getByText("100")).toBeInTheDocument(); + expect(screen.queryByRole("button", { name: "Save Changes" })).not.toBeInTheDocument(); + expect(updateSettings).not.toHaveBeenCalled(); + + await enterEditMode(user); + expect(screen.getByLabelText("Max Budget (USD)")).toHaveValue(100); + expect(await saveButton()).toBeDisabled(); + }); +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/DefaultUserSettingsForm.tsx b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/DefaultUserSettingsForm.tsx new file mode 100644 index 00000000000..b1474e7cd0c --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/DefaultUserSettingsForm.tsx @@ -0,0 +1,433 @@ +"use client"; + +import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; +import * as React from "react"; +import { useFieldArray, type Control } from "react-hook-form"; + +import { useInfiniteTeams } from "@/app/(dashboard)/hooks/teams/useTeams"; +import { ModelSelect, MODEL_SENTINEL_OPTIONS } from "@/components/ModelSelect/ModelSelect"; +import NotificationsManager from "@/components/molecules/notifications_manager"; +import { PaginatedSearchSelect } from "@/components/shared/PaginatedSearchSelect"; +import { FieldGroup } from "@/components/shared/form/field"; +import { FormField } from "@/components/shared/form/FormField"; +import type { SearchSelectOption } from "@/components/shared/SearchSelect"; +import { Button } from "@/components/ui/button"; +import { Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Input } from "@/components/ui/input"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Skeleton } from "@/components/ui/skeleton"; +import { useZodForm } from "@/lib/forms/useZodForm"; +import { fetchClient } from "@/lib/http/api"; + +import { buildBody, settingsToForm, type DefaultInternalUserParams, type InternalUserSettings } from "./mapper"; +import { defaultUserSettingsSchema, EMPTY_TEAM_ROW, type DefaultUserSettingsFormValues } from "./schema"; + +const NO_RESET = "never"; + +const BUDGET_DURATION_OPTIONS = [ + { value: NO_RESET, label: "No reset" }, + { value: "1h", label: "hourly" }, + { value: "24h", label: "daily" }, + { value: "7d", label: "weekly" }, + { value: "30d", label: "monthly" }, +] as const; + +const TEAM_ROLE_OPTIONS = [ + { value: "user", label: "User" }, + { value: "admin", label: "Admin" }, +] as const; + +const MODEL_SENTINEL_LABELS: ReadonlyMap = new Map( + MODEL_SENTINEL_OPTIONS.map(({ value, label }) => [value, label]), +); + +const TEAMS_PAGE_SIZE = 50; + +const SETTINGS_QUERY_KEY = ["internalUserSettings"] as const; + +const defaultFetchSettings = async (): Promise => { + const { data } = await fetchClient.GET("/get/internal_user_settings"); + if (data === undefined) { + throw new Error("Failed to load default user settings"); + } + return data; +}; + +const defaultUpdateSettings = async (body: DefaultInternalUserParams): Promise => { + await fetchClient.PATCH("/update/internal_user_settings", { body }); +}; + +interface RoleOption { + value: string; + label: string; + description: string; +} + +type SettingsControl = Control; + +const TeamPickerField = ({ control, index }: { control: SettingsControl; index: number }) => { + const [search, setSearch] = React.useState(""); + const { data, fetchNextPage, hasNextPage, isFetchingNextPage, isLoading } = useInfiniteTeams( + TEAMS_PAGE_SIZE, + search === "" ? undefined : search, + ); + + const options = React.useMemo( + () => + (data?.pages ?? []).flatMap((page) => + page.teams.map((team) => ({ + label: team.team_alias || team.team_id, + value: team.team_id, + sublabel: team.team_id, + })), + ), + [data], + ); + + return ( + + {({ id, value, onChange, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy }) => ( + void fetchNextPage()} + hasNextPage={hasNextPage} + isLoading={isLoading} + isFetchingNextPage={isFetchingNextPage} + placeholder="Search a team" + emptyText="No teams found" + inputId={id} + aria-invalid={ariaInvalid} + aria-describedby={ariaDescribedBy} + /> + )} + + ); +}; + +const TeamsField = ({ control }: { control: SettingsControl }) => { + const { fields, append, remove } = useFieldArray({ control, name: "teams" }); + + return ( +
+
+

Default Teams

+

+ New users are added to these teams. Only teams that already exist can be selected. +

+
+ + {fields.map((field, index) => ( +
+
+

Team {index + 1}

+ +
+ +
+ + + + {({ ref, ...budgetField }) => ( + + )} + + + + {({ id, value, onChange, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy }) => ( + + )} + +
+
+ ))} + + +
+ ); +}; + +const ViewRow = ({ label, children }: { label: string; children: React.ReactNode }) => ( +
+

{label}

+

{children}

+
+); + +interface SettingsViewProps { + values: DefaultUserSettingsFormValues; + roleOptions: readonly RoleOption[]; +} + +const SettingsView = ({ values, roleOptions }: SettingsViewProps) => { + const roleLabel = roleOptions.find((option) => option.value === values.user_role)?.label ?? values.user_role; + const durationValue = values.budget_duration === "" ? NO_RESET : values.budget_duration; + const durationLabel = + BUDGET_DURATION_OPTIONS.find((option) => option.value === durationValue)?.label ?? values.budget_duration; + + return ( +
+ {roleLabel === "" ? "Not set" : roleLabel} + {values.max_budget === "" ? "Not set" : values.max_budget} + {durationLabel} + + {values.models.length === 0 + ? "Not set" + : values.models.map((model) => MODEL_SENTINEL_LABELS.get(model) ?? model).join(", ")} + +
+

Default Teams

+ {values.teams.length === 0 ? ( +

None

+ ) : ( + values.teams.map((team) => ( +

+ {team.team_id} + {team.max_budget_in_team !== "" && <> · ${team.max_budget_in_team} max budget} + <> · {team.user_role} +

+ )) + )} +
+
+ ); +}; + +interface SettingsFormProps { + initialValues: DefaultUserSettingsFormValues; + roleOptions: readonly RoleOption[]; + updateSettings: (body: DefaultInternalUserParams) => Promise; + onCancel: () => void; + onSaved: () => void; +} + +const SettingsForm = ({ initialValues, roleOptions, updateSettings, onCancel, onSaved }: SettingsFormProps) => { + const queryClient = useQueryClient(); + const form = useZodForm(defaultUserSettingsSchema, { defaultValues: initialValues }); + const { isDirty } = form.formState; + + const mutation = useMutation({ + mutationFn: (values: DefaultUserSettingsFormValues) => updateSettings(buildBody(values)), + onSuccess: (_result, values) => { + NotificationsManager.success("Default user settings updated successfully"); + queryClient.invalidateQueries({ queryKey: SETTINGS_QUERY_KEY }); + form.reset(values); + onSaved(); + }, + onError: (error: unknown) => + NotificationsManager.fromBackend( + error instanceof Error ? error.message : "Failed to update default user settings", + ), + }); + + const onSubmit = form.handleSubmit((values) => mutation.mutate(values)); + + return ( +
+ + + {({ id, value, onChange, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy }) => ( + + )} + + + + {({ ref, ...field }) => } + + + + {({ id, value, onChange, "aria-invalid": ariaInvalid, "aria-describedby": ariaDescribedBy }) => ( + + )} + + + + {(field) => ( + + )} + + + + + +
+ + +
+
+ ); +}; + +const SettingsCard = ({ action, children }: { action?: React.ReactNode; children: React.ReactNode }) => ( + + + Default User Settings + + Applied to every new internal user created through SSO or the user management APIs. + + {action !== undefined && {action}} + + {children} + +); + +export interface DefaultUserSettingsFormProps { + possibleUIRoles?: Record> | null; + fetchSettings?: () => Promise; + updateSettings?: (body: DefaultInternalUserParams) => Promise; +} + +export const DefaultUserSettingsForm = ({ + possibleUIRoles, + fetchSettings = defaultFetchSettings, + updateSettings = defaultUpdateSettings, +}: DefaultUserSettingsFormProps) => { + const [isEditing, setIsEditing] = React.useState(false); + const { data, isPending, isError } = useQuery({ queryKey: SETTINGS_QUERY_KEY, queryFn: fetchSettings }); + + const roleOptions = React.useMemo( + () => + Object.entries(possibleUIRoles ?? {}) + .filter(([role]) => role.includes("internal_user")) + .map(([role, meta]) => ({ value: role, label: meta.ui_label || role, description: meta.description ?? "" })), + [possibleUIRoles], + ); + + const initialValues = React.useMemo(() => (data === undefined ? undefined : settingsToForm(data.values)), [data]); + + if (isPending) { + return ( + + + + ); + } + + if (isError || initialValues === undefined) { + return ( + +

Could not load the default user settings.

+
+ ); + } + + return ( + setIsEditing(true)}> + Edit Settings + + ) + } + > + {isEditing ? ( + setIsEditing(false)} + onSaved={() => setIsEditing(false)} + /> + ) : ( + + )} + + ); +}; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/mapper.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/mapper.test.ts new file mode 100644 index 00000000000..e8b350332c8 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/mapper.test.ts @@ -0,0 +1,116 @@ +import { describe, expect, it } from "vitest"; + +import { buildBody, settingsToForm } from "./mapper"; +import type { DefaultUserSettingsFormValues } from "./schema"; + +const CONFIGURED_SETTINGS = { + user_role: "internal_user", + max_budget: 100.5, + budget_duration: "30d", + models: ["gpt-5.2"], + teams: [{ team_id: "team-1", max_budget_in_team: 25, user_role: "admin" }], +}; + +const UNCONFIGURED_SETTINGS = { + user_role: "internal_user_viewer", + max_budget: null, + budget_duration: null, + models: null, + teams: null, +}; + +const CONFIGURED_FORM = { + user_role: "internal_user", + max_budget: "100.5", + budget_duration: "30d", + models: ["gpt-5.2"], + teams: [{ team_id: "team-1", max_budget_in_team: "25", user_role: "admin" }], +}; + +const UNCONFIGURED_FORM = { + user_role: "internal_user_viewer", + max_budget: "", + budget_duration: "", + models: [], + teams: [], +}; + +describe("settingsToForm", () => { + it("maps a fully populated settings blob onto widget-space strings", () => { + expect(settingsToForm(CONFIGURED_SETTINGS)).toStrictEqual(CONFIGURED_FORM); + }); + + it("maps an unconfigured settings blob onto empty widget state", () => { + expect(settingsToForm(UNCONFIGURED_SETTINGS)).toStrictEqual(UNCONFIGURED_FORM); + }); + + it("hydrates the legacy list-of-team-ids shape as full team rows", () => { + expect(settingsToForm({ teams: ["team-1", "team-2"] }).teams).toStrictEqual([ + { team_id: "team-1", max_budget_in_team: "", user_role: "user" }, + { team_id: "team-2", max_budget_in_team: "", user_role: "user" }, + ]); + }); + + it("defaults a team row's role to user and leaves an absent in-team budget blank", () => { + expect(settingsToForm({ teams: [{ team_id: "team-1" }] }).teams).toStrictEqual([ + { team_id: "team-1", max_budget_in_team: "", user_role: "user" }, + ]); + }); + + it("degrades an unrecognisable team entry to a blank row instead of throwing", () => { + expect(settingsToForm({ teams: [{ max_budget_in_team: 5 }, 7] }).teams).toStrictEqual([ + { team_id: "", max_budget_in_team: "", user_role: "user" }, + { team_id: "", max_budget_in_team: "", user_role: "user" }, + ]); + }); +}); + +const formValues = (overrides: Partial = {}): DefaultUserSettingsFormValues => ({ + user_role: "internal_user", + max_budget: "100", + budget_duration: "30d", + models: ["gpt-5.2"], + teams: [{ team_id: "team-1", max_budget_in_team: "25", user_role: "admin" }], + ...overrides, +}); + +const SAVED_BODY = { + user_role: "internal_user", + max_budget: 100, + budget_duration: "30d", + models: ["gpt-5.2"], + teams: [{ team_id: "team-1", max_budget_in_team: 25, user_role: "admin" }], +}; + +const CLEARED_FORM = { user_role: "", max_budget: "", budget_duration: "", models: [], teams: [] }; + +const CLEARED_BODY = { + user_role: null, + max_budget: null, + budget_duration: null, + models: null, + teams: null, +}; + +describe("buildBody", () => { + it("sends every field, because the endpoint replaces the whole settings object", () => { + expect(buildBody(formValues())).toStrictEqual(SAVED_BODY); + }); + + it("clears emptied fields with null so the backend drops them", () => { + expect(buildBody(formValues(CLEARED_FORM))).toStrictEqual(CLEARED_BODY); + }); + + it("sends teams as objects and nulls an in-team budget that was left blank", () => { + expect( + buildBody(formValues({ teams: [{ team_id: "team-9", max_budget_in_team: "", user_role: "user" }] })), + ).toStrictEqual({ + ...SAVED_BODY, + teams: [{ team_id: "team-9", max_budget_in_team: null, user_role: "user" }], + }); + }); + + it("refuses to send a role the backend does not accept", () => { + expect(buildBody(formValues({ user_role: "made_up_role" })).user_role).toBeNull(); + }); +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/mapper.ts b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/mapper.ts new file mode 100644 index 00000000000..50365081afb --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/mapper.ts @@ -0,0 +1,75 @@ +import { z } from "zod/v4"; + +import type { components } from "@/lib/http/schema"; + +import { EMPTY_TEAM_ROW, type DefaultTeamRowValues, type DefaultUserSettingsFormValues } from "./schema"; + +export type InternalUserSettings = components["schemas"]["InternalUserSettingsResponse"]; +export type DefaultInternalUserParams = components["schemas"]["DefaultInternalUserParams"]; +type DefaultTeamBody = components["schemas"]["NewUserRequestTeam"]; + +const teamRowFromServer = z + .union([ + z.string().transform((teamId): DefaultTeamRowValues => ({ ...EMPTY_TEAM_ROW, team_id: teamId })), + z + .object({ + team_id: z.string(), + max_budget_in_team: z.number().nullish(), + user_role: z.enum(["user", "admin"]).catch("user"), + }) + .transform( + (team): DefaultTeamRowValues => ({ + team_id: team.team_id, + max_budget_in_team: team.max_budget_in_team?.toString() ?? "", + user_role: team.user_role, + }), + ), + ]) + .catch(EMPTY_TEAM_ROW); + +const serverValuesShape = { + user_role: z.string().nullish().catch(null), + max_budget: z.number().nullish().catch(null), + budget_duration: z.string().nullish().catch(null), + models: z.array(z.string()).nullish().catch(null), + teams: z.array(teamRowFromServer).nullish().catch(null), +}; + +const serverValuesSchema = z.object(serverValuesShape); + +export const settingsToForm = (values: InternalUserSettings["values"]): DefaultUserSettingsFormValues => { + const parsed = serverValuesSchema.parse(values); + + return { + user_role: parsed.user_role ?? "", + max_budget: parsed.max_budget?.toString() ?? "", + budget_duration: parsed.budget_duration ?? "", + models: parsed.models ?? [], + teams: parsed.teams ?? [], + }; +}; + +const DEFAULT_USER_ROLES = ["internal_user", "internal_user_viewer", "proxy_admin", "proxy_admin_viewer"] as const; + +const asDefaultUserRole = (raw: string): DefaultInternalUserParams["user_role"] => + DEFAULT_USER_ROLES.find((role) => role === raw) ?? null; + +const numberOrNull = (raw: string): number | null => (raw.trim() === "" ? null : Number(raw)); + +const textOrNull = (raw: string): string | null => (raw.trim() === "" ? null : raw); + +const listOrNull = (items: readonly T[]): T[] | null => (items.length === 0 ? null : [...items]); + +const toTeamBody = (team: DefaultTeamRowValues): DefaultTeamBody => ({ + team_id: team.team_id, + max_budget_in_team: numberOrNull(team.max_budget_in_team), + user_role: team.user_role, +}); + +export const buildBody = (values: DefaultUserSettingsFormValues): DefaultInternalUserParams => ({ + user_role: asDefaultUserRole(values.user_role), + max_budget: numberOrNull(values.max_budget), + budget_duration: textOrNull(values.budget_duration), + models: listOrNull(values.models), + teams: listOrNull(values.teams.map(toTeamBody)), +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/schema.test.ts b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/schema.test.ts new file mode 100644 index 00000000000..889f87f2203 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/schema.test.ts @@ -0,0 +1,64 @@ +import { describe, expect, it } from "vitest"; + +import { defaultUserSettingsSchema, type DefaultUserSettingsFormValues } from "./schema"; + +const values = (overrides: Partial = {}): DefaultUserSettingsFormValues => ({ + user_role: "internal_user", + max_budget: "", + budget_duration: "", + models: [], + teams: [], + ...overrides, +}); + +const issuesFor = (input: DefaultUserSettingsFormValues) => { + const result = defaultUserSettingsSchema.safeParse(input); + return result.success ? [] : result.error.issues.map((issue) => ({ path: issue.path, message: issue.message })); +}; + +describe("defaultUserSettingsSchema", () => { + it("accepts settings with no default teams", () => { + expect(defaultUserSettingsSchema.safeParse(values()).success).toBe(true); + }); + + it("rejects a team row that has no team selected", () => { + expect(issuesFor(values({ teams: [{ team_id: "", max_budget_in_team: "", user_role: "user" }] }))).toStrictEqual([ + { path: ["teams", 0, "team_id"], message: "Select a team" }, + ]); + }); + + it("rejects the same team appearing twice", () => { + expect( + issuesFor( + values({ + teams: [ + { team_id: "team-1", max_budget_in_team: "", user_role: "user" }, + { team_id: "team-1", max_budget_in_team: "", user_role: "admin" }, + ], + }), + ), + ).toStrictEqual([{ path: ["teams", 1, "team_id"], message: "This team is already listed" }]); + }); + + it("does not treat two blank rows as duplicates of each other", () => { + const issues = issuesFor( + values({ + teams: [ + { team_id: "", max_budget_in_team: "", user_role: "user" }, + { team_id: "", max_budget_in_team: "", user_role: "user" }, + ], + }), + ); + + expect(issues.map((issue) => issue.message)).toStrictEqual(["Select a team", "Select a team"]); + }); + + it("rejects non-numeric budgets on the form and on a team row", () => { + expect(issuesFor(values({ max_budget: "lots" }))).toStrictEqual([ + { path: ["max_budget"], message: "Must be a non-negative number" }, + ]); + expect( + issuesFor(values({ teams: [{ team_id: "team-1", max_budget_in_team: "-5", user_role: "user" }] })), + ).toStrictEqual([{ path: ["teams", 0, "max_budget_in_team"], message: "Must be a non-negative number" }]); + }); +}); diff --git a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/schema.ts b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/schema.ts new file mode 100644 index 00000000000..7309e3745da --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/default-user-settings/schema.ts @@ -0,0 +1,44 @@ +import { z } from "zod/v4"; + +const isBlank = (value: string): boolean => value.trim() === ""; + +const amountOrEmpty = z + .string() + .refine( + (value) => isBlank(value) || (Number.isFinite(Number(value)) && Number(value) >= 0), + "Must be a non-negative number", + ); + +const defaultTeamRowSchema = z.object({ + team_id: z.string().min(1, "Select a team"), + max_budget_in_team: amountOrEmpty, + user_role: z.enum(["user", "admin"]), +}); + +export type DefaultTeamRowValues = z.output; + +export const EMPTY_TEAM_ROW: DefaultTeamRowValues = { team_id: "", max_budget_in_team: "", user_role: "user" }; + +const defaultUserSettingsShape = { + user_role: z.string(), + max_budget: amountOrEmpty, + budget_duration: z.string(), + models: z.array(z.string()), + teams: z.array(defaultTeamRowSchema), +}; + +export const defaultUserSettingsSchema = z.object(defaultUserSettingsShape).superRefine((values, ctx) => { + const repeatedRows = values.teams.flatMap((team, index) => + team.team_id !== "" && values.teams.findIndex((other) => other.team_id === team.team_id) < index ? [index] : [], + ); + + repeatedRows.forEach((index) => + ctx.addIssue({ + code: "custom", + message: "This team is already listed", + path: ["teams", index, "team_id"], + }), + ); +}); + +export type DefaultUserSettingsFormValues = z.output; diff --git a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.test.tsx index 8dc11babd72..5fcc55c1e98 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.test.tsx @@ -27,7 +27,6 @@ vi.mock("@/components/networking", () => ({ DEFAULT_TEAM_DISABLED: false, SSO_ENABLED: false, }), - getInternalUserSettings: vi.fn().mockResolvedValue({}), })); // The detail view has its own test; stub it so this file covers the parent's swap. diff --git a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.tsx b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.tsx index ce912c09373..2c1d28d82f8 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users.tsx @@ -30,7 +30,7 @@ import { import DeleteResourceModal from "@/components/common_components/DeleteResourceModal"; import NotificationsManager from "@/components/molecules/notifications_manager"; import { modelAvailableCall, userDeleteCall } from "@/components/networking"; -import DefaultUserSettings from "./DefaultUserSettings"; +import { DefaultUserSettingsForm } from "./default-user-settings/DefaultUserSettingsForm"; import { UsersTable } from "./view_users/UsersTable"; import UserInfoView from "./view_users/user_info_view"; import { UserInfo } from "@/components/networking"; @@ -412,12 +412,7 @@ const ViewUserDashboard: React.FC = ({
) : ( - + )} diff --git a/ui/litellm-dashboard/src/components/ModelSelect/ModelSelect.tsx b/ui/litellm-dashboard/src/components/ModelSelect/ModelSelect.tsx index 898374d2a61..0965683c241 100644 --- a/ui/litellm-dashboard/src/components/ModelSelect/ModelSelect.tsx +++ b/ui/litellm-dashboard/src/components/ModelSelect/ModelSelect.tsx @@ -16,7 +16,7 @@ const MODEL_SELECT_NO_DEFAULT_MODELS_SPECIAL_VALUE = { value: "no-default-models", } as const; -const MODEL_SELECT_SPECIAL_VALUES_ARRAY = [ +export const MODEL_SENTINEL_OPTIONS = [ MODEL_SELECT_ALL_PROXY_MODELS_SPECIAL_VALUE, MODEL_SELECT_NO_DEFAULT_MODELS_SPECIAL_VALUE, ] as const; @@ -100,7 +100,7 @@ export const ModelSelect = (props: ModelSelectProps) => { const { data: organization, isLoading: isLoadingOrganization } = useOrganization(organizationID); const { data: currentUser, isLoading: isCurrentUserLoading } = useCurrentUser(); - const isSpecialOption = (value: string) => MODEL_SELECT_SPECIAL_VALUES_ARRAY.some((sv) => sv.value === value); + const isSpecialOption = (value: string) => MODEL_SENTINEL_OPTIONS.some((sv) => sv.value === value); const hasSpecialOptionSelected = value.some(isSpecialOption); const isLoading = isLoadingAllProxyModels || isLoadingTeam || isLoadingOrganization || isCurrentUserLoading; const organizationHasAllProxyModels = diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 576e16cbb37..22dc087e150 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -4749,45 +4749,6 @@ export const uiSpendLogDetailsCall = async (accessToken: string, logId: string, } }; -export const getInternalUserSettings = async (accessToken: string) => { - try { - const data = await apiClient.get(`/get/internal_user_settings`, { accessToken }); - return data; - } catch (error) { - console.error("Failed to fetch SSO settings:", error); - throw error; - } -}; - -export const updateInternalUserSettings = async (accessToken: string, settings: Record) => { - try { - // Construct base URL - let url = proxyBaseUrl ? `${proxyBaseUrl}/update/internal_user_settings` : `/update/internal_user_settings`; - - const response = await fetch(url, { - method: "PATCH", - headers: { - [globalLitellmHeaderName]: `Bearer ${accessToken}`, - "Content-Type": "application/json", - }, - body: JSON.stringify(settings), - }); - - if (!response.ok) { - const errorData = await response.text(); - handleError(errorData); - throw new Error(errorData); - } - - const data = await response.json(); - NotificationsManager.success("Internal user settings updated successfully"); - return data; - } catch (error) { - console.error("Failed to update internal user settings:", error); - throw error; - } -}; - export const fetchOpenAPIRegistry = async (accessToken: string) => { try { const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/mcp/openapi-registry` : `/v1/mcp/openapi-registry`; diff --git a/ui/litellm-dashboard/src/components/shared/PaginatedSearchSelect.tsx b/ui/litellm-dashboard/src/components/shared/PaginatedSearchSelect.tsx index b59cd1263ea..fde29fd5362 100644 --- a/ui/litellm-dashboard/src/components/shared/PaginatedSearchSelect.tsx +++ b/ui/litellm-dashboard/src/components/shared/PaginatedSearchSelect.tsx @@ -34,6 +34,9 @@ interface PaginatedSearchSelectProps { loadingText?: string; disabled?: boolean; className?: string; + inputId?: string; + "aria-invalid"?: true | undefined; + "aria-describedby"?: string; } export function PaginatedSearchSelect({ @@ -50,6 +53,9 @@ export function PaginatedSearchSelect({ loadingText = "Loading…", disabled = false, className, + inputId, + "aria-invalid": ariaInvalid, + "aria-describedby": ariaDescribedBy, }: PaginatedSearchSelectProps) { const selected = useMemo(() => { if (value === undefined || value === "") return null; @@ -90,6 +96,9 @@ export function PaginatedSearchSelect({ disabled={disabled} > { it("falls back to a string detail field", () => { expect(deriveErrorMessage({ detail: "detail text" })).toBe("detail text"); }); + + it("unwraps the HTTPException detail.error shape management endpoints raise", () => { + expect(deriveErrorMessage({ detail: { error: "Team(s) not found: ghost-team" } })).toBe( + "Team(s) not found: ghost-team", + ); + }); }); diff --git a/ui/litellm-dashboard/src/lib/http/client.ts b/ui/litellm-dashboard/src/lib/http/client.ts index e2b47f7b354..1370d3e9273 100644 --- a/ui/litellm-dashboard/src/lib/http/client.ts +++ b/ui/litellm-dashboard/src/lib/http/client.ts @@ -44,13 +44,15 @@ export class ApiError extends Error { * Lives here because error parsing is the client's job; networking.tsx re-exports * it so existing `@/components/networking` import paths keep working. */ +const deriveDetailMessage = (detail: any): string | undefined => { + if (Array.isArray(detail)) return detail.map((d: any) => d?.msg || JSON.stringify(d)).join("; "); + if (typeof detail === "string") return detail; + if (typeof detail?.error === "string") return detail.error; + return undefined; +}; + export const deriveErrorMessage = (errorData: any): string => { - const detail = errorData?.detail; - const detailStr = Array.isArray(detail) - ? detail.map((d: any) => d?.msg || JSON.stringify(d)).join("; ") - : typeof detail === "string" - ? detail - : undefined; + const detailStr = deriveDetailMessage(errorData?.detail); return ( (errorData?.error && (errorData.error.message || (typeof errorData.error === "string" ? errorData.error : undefined))) || From 50bdf250f652ae46657b204268df77fe6e3b9da1 Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Mon, 27 Jul 2026 15:32:27 -0700 Subject: [PATCH 31/75] fix(router): repair deployment indices before releasing strategies on delete delete_deployment resolved the outgoing deployment through get_deployment before popping it, and ran the strategy release before repairing the index maps. Both halves of that ordering could leave the router inconsistent. A resolution failure meant the entry left the model_list with its registry slots still held, so the alias stayed routable and the name could not be reused; a failure inside the release meant the outer handler returned None with the entry already popped and model_id_to_deployment_index_map never repaired, breaking every later lookup and delete until a restart. upsert_deployment already had this right: it pops, repairs the caches and indices, and only then releases the slot. delete_deployment now follows the same sequence and resolves the deployment from the item it just popped rather than through a lookup that can fail. Releasing the slot is secondary to structural integrity, so it runs last and a failure there is logged instead of abandoning a removal that has already happened. --- litellm/router.py | 16 ++++++++++------ tests/test_litellm/test_router.py | 16 ++++++++++++++++ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/litellm/router.py b/litellm/router.py index 29f548ca284..487d6a31226 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -8505,20 +8505,24 @@ class Router: try: if deployment_idx is not None: - try: - deployment_to_remove = self.get_deployment(model_id=id) - except Exception: - deployment_to_remove = None # Pop the item from the list first item = self.model_list.pop(deployment_idx) - if deployment_to_remove is not None: - self._unregister_pre_routing_strategy_for_deployment(deployment=deployment_to_remove) self._invalidate_model_group_info_cache() self._invalidate_access_groups_cache() self._update_deployment_indices_after_removal(model_id=id, removal_idx=deployment_idx) _budget_limiter = self._get_router_deployment_budget_limiter() if _budget_limiter is not None: _budget_limiter.unregister_deployment_budget(model_id=id) + try: + self._unregister_pre_routing_strategy_for_deployment( + deployment=item if isinstance(item, Deployment) else Deployment(**item) + ) + except Exception: + verbose_router_logger.exception( + "delete_deployment: could not release pre-routing strategies for model_id=%s; " + "the deployment is out of the model_list and its indices are repaired", + id, + ) return item else: return None diff --git a/tests/test_litellm/test_router.py b/tests/test_litellm/test_router.py index f04e4a60283..ad4e430c603 100644 --- a/tests/test_litellm/test_router.py +++ b/tests/test_litellm/test_router.py @@ -6128,6 +6128,22 @@ class TestPreRoutingStrategyRegistryLifecycle: assert len(registered) == 1 assert set(registered[0].strategy.config.available_models) == {"gpt-4o", "gpt-4o-mini"} + def test_delete_repairs_indices_even_when_strategy_release_fails(self): + """Structural removal and strategy release are not equally critical. Once the entry + leaves model_list the index maps must be repaired no matter what, so releasing the + registry slot runs after that repair and cannot abandon the router half-updated.""" + router = self._router_with_complexity_router() + idx = router.model_id_to_deployment_index_map["router-1"] + router.model_list[idx] = {"model_name": "smart-router", "litellm_params": None} + + returned = router.delete_deployment(id="router-1") + + assert returned is not None + assert "router-1" not in router.model_id_to_deployment_index_map + assert all(entry.get("model_info", {}).get("id") != "router-1" for entry in router.model_list) + assert router.get_deployment(model_id="router-1") is None + assert "gpt-4o" in self._model_names(router) + def test_delete_of_adaptive_enabled_complexity_router_frees_both_registries(self): """A complexity router with adaptive set is registered in BOTH complexity_routers and adaptive_routers under the same (model_name, tags). Releasing only the first From bdf8f8c309ffdcbcc48760b4fb37f85b002dda77 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Mon, 27 Jul 2026 16:50:13 -0700 Subject: [PATCH 32/75] fix(guardrails): classify all 4xx HTTPException guardrail blocks as intervened (#33821) * fix(guardrails): classify all 4xx HTTPException guardrail blocks as intervened * fix(guardrails): narrow HTTPException block classification to 400/403/422 --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/integrations/custom_guardrail.py | 17 +++++++- .../integrations/test_custom_guardrail.py | 41 +++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/litellm/integrations/custom_guardrail.py b/litellm/integrations/custom_guardrail.py index 57b05c9bec8..9c7bbbd3b4c 100644 --- a/litellm/integrations/custom_guardrail.py +++ b/litellm/integrations/custom_guardrail.py @@ -69,6 +69,8 @@ from litellm.exceptions import ( # proxy's metadata sanitizer. _PRE_CALL_EXECUTED_TOKEN = secrets.token_hex(16) +_GUARDRAIL_BLOCK_STATUS_CODES = frozenset({400, 403, 422}) + _guardrail_self_recorded: contextvars.ContextVar[bool] = contextvars.ContextVar( "litellm_guardrail_self_recorded", default=False ) @@ -1055,8 +1057,15 @@ class CustomGuardrail(CustomLogger): - GuardrailRaisedException (generic guardrail API, tool permission) - BlockedPiiEntityError (Presidio PII detection) - SensitiveDataRouteException (sensitive-data reroute to on-premise model) - - HTTPException with status 400 (content policy violation) + - HTTPException with a block-signalling status (400, 403, 422) - ModifyResponseException (passthrough mode violation) + + Only the statuses guardrails use in-tree to signal a deliberate rejection + count as an intervention: 400 (content policy), 403 (e.g. akto) and 422 + (e.g. llm_as_a_judge). Other 4xx codes are commonly propagated from an + upstream guardrail provider response (401 bad key, 408 timeout, 429 rate + limit, or a raw upstream status), which are technical failures, not + blocks, so they stay guardrail_failed_to_respond. """ if isinstance(e, ModifyResponseException): return True @@ -1069,7 +1078,11 @@ class CustomGuardrail(CustomLogger): ), ): return True - if HTTPException is not None and isinstance(e, HTTPException) and e.status_code == 400: + if ( + HTTPException is not None + and isinstance(e, HTTPException) + and e.status_code in _GUARDRAIL_BLOCK_STATUS_CODES + ): return True return False diff --git a/tests/test_litellm/integrations/test_custom_guardrail.py b/tests/test_litellm/integrations/test_custom_guardrail.py index bac0ae54033..d61467a40ed 100644 --- a/tests/test_litellm/integrations/test_custom_guardrail.py +++ b/tests/test_litellm/integrations/test_custom_guardrail.py @@ -1668,6 +1668,47 @@ class TestGuardrailInterventionClassification: ) assert CustomGuardrail._is_guardrail_intervention(exc) is True + @pytest.mark.parametrize("status_code", [400, 403, 422]) + def test_block_signalling_http_exception_is_intervention(self, status_code): + from fastapi.exceptions import HTTPException + + exc = HTTPException(status_code=status_code, detail="blocked by guardrail") + assert CustomGuardrail._is_guardrail_intervention(exc) is True + + @pytest.mark.parametrize("status_code", [300, 401, 408, 429, 451, 499, 500, 502, 503]) + def test_non_block_http_exception_is_not_intervention(self, status_code): + from fastapi.exceptions import HTTPException + + exc = HTTPException(status_code=status_code, detail="guardrail api error") + assert CustomGuardrail._is_guardrail_intervention(exc) is False + + @pytest.mark.asyncio + async def test_non_400_4xx_logged_as_intervened_not_failed(self): + from fastapi.exceptions import HTTPException + + from litellm.integrations.custom_guardrail import log_guardrail_information + from litellm.types.guardrails import GuardrailEventHooks + + class BlockingGuardrail(CustomGuardrail): + def __init__(self): + super().__init__( + guardrail_name="block-rail", + event_hook=GuardrailEventHooks.pre_call, + ) + + @log_guardrail_information + async def async_pre_call_hook(self, data, **kwargs): + raise HTTPException(status_code=403, detail="blocked by guardrail") + + guardrail = BlockingGuardrail() + request_data: dict = {"metadata": {}} + + with pytest.raises(HTTPException): + await guardrail.async_pre_call_hook(data=request_data) + + slg = request_data["metadata"]["standard_logging_guardrail_information"][0] + assert slg["guardrail_status"] == "guardrail_intervened" + @pytest.mark.asyncio async def test_routing_logged_as_intervened_not_failed(self): from litellm.exceptions import SensitiveDataRouteException From c2f0014a632f465f5c242085718873f1de3e6d4f Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Mon, 27 Jul 2026 17:15:09 -0700 Subject: [PATCH 33/75] fix(jwt_auth): grant only /v1/messages routes to JWT teams by default, not all anthropic_routes --- litellm/proxy/_types.py | 8 ++++++- .../proxy/auth/test_handle_jwt.py | 22 +++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 53b6e51aaef..1608811b5a1 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -4254,7 +4254,13 @@ class LiteLLM_JWTAuth(LiteLLMPydanticObjectBase): team_id_upsert: bool = False team_ids_jwt_field: Optional[str] = None upsert_sso_user_to_team: bool = False - team_allowed_routes: List[str] = ["openai_routes", "anthropic_routes", "info_routes", "mcp_routes"] + team_allowed_routes: List[str] = [ + "openai_routes", + "info_routes", + "mcp_routes", + "/v1/messages", + "/v1/messages/count_tokens", + ] team_id_default: Optional[str] = Field( default=None, description="If no team_id given, default permissions/spend-tracking to this team.s", diff --git a/tests/test_litellm/proxy/auth/test_handle_jwt.py b/tests/test_litellm/proxy/auth/test_handle_jwt.py index 35aaa0f1254..3840c90d691 100644 --- a/tests/test_litellm/proxy/auth/test_handle_jwt.py +++ b/tests/test_litellm/proxy/auth/test_handle_jwt.py @@ -1299,6 +1299,28 @@ async def test_find_team_with_model_access_v1_messages_default_routes(monkeypatc assert team_obj.team_id == "coding-team" +@pytest.mark.parametrize( + "route,expected", + [ + ("/v1/messages", True), + ("/v1/messages/count_tokens", True), + ("/v1/skills", False), + ("/v1/skills/skill_abc123", False), + ], +) +def test_default_team_allowed_routes_cover_messages_but_not_skills(route, expected): + from litellm.proxy.auth.auth_checks import allowed_routes_check + + assert ( + allowed_routes_check( + user_role=LitellmUserRoles.TEAM, + user_route=route, + litellm_proxy_roles=LiteLLM_JWTAuth(), + ) + is expected + ) + + @pytest.mark.asyncio async def test_auth_builder_returns_team_membership_object(): """ From f2cda740f74c44538e3291e5feb9022d37dbf804 Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Mon, 27 Jul 2026 17:20:51 -0700 Subject: [PATCH 34/75] chore: update Next.js build artifacts (2026-07-28 00:06 UTC, node v20.20.2) (#34859) --- litellm/proxy/_experimental/out/404.html | 2 +- .../proxy/_experimental/out/404/index.html | 2 +- .../out/__next.!KGRhc2hib2FyZCk.__PAGE__.txt | 8 +- .../out/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../proxy/_experimental/out/__next._full.txt | 36 +- .../proxy/_experimental/out/__next._head.txt | 8 +- .../proxy/_experimental/out/__next._index.txt | 14 +- .../proxy/_experimental/out/__next._tree.txt | 4 +- .../out/_next/static/chunks/0-k_4_s7m108w.js | 7 - .../out/_next/static/chunks/025ocjcb8e481.js | 7 + .../out/_next/static/chunks/046q5lwe95zp6.js | 1 + .../out/_next/static/chunks/05287rwl48hh2.js | 13 + .../out/_next/static/chunks/054z32giulaw7.js | 1 + .../out/_next/static/chunks/058j9m4b8p4wx.js | 1 + .../out/_next/static/chunks/06q8aep867ss7.js | 1 + .../out/_next/static/chunks/08691-q-pz235.js | 1 - .../out/_next/static/chunks/08goggic_ad66.js | 2 + .../{3y674jhwchpcq.js => 0_6rii-l50y-j.js} | 2 +- .../out/_next/static/chunks/0_mw8gm-qowti.js | 1 + .../out/_next/static/chunks/0_v0ovphg1p2h.js | 1 - .../out/_next/static/chunks/0a-3zns09ja98.js | 10 + .../out/_next/static/chunks/0afqpg84x7rak.js | 7 - .../out/_next/static/chunks/0ald9tfsbz2e-.js | 7 + .../out/_next/static/chunks/0am68mi9t9cb6.js | 7 + .../out/_next/static/chunks/0bcwe_r_o0z3w.js | 26 -- .../out/_next/static/chunks/0bzsgiuwi1q0e.js | 7 + .../out/_next/static/chunks/0cgtkk6qelb_j.js | 1 + .../out/_next/static/chunks/0cu_67b262ror.js | 1 - .../out/_next/static/chunks/0cxlei71txljy.js | 1 - .../out/_next/static/chunks/0d6y38dwy2fvp.js | 1 + .../out/_next/static/chunks/0denwarlgmop7.js | 1 - .../out/_next/static/chunks/0dsiq_ok1yngk.js | 2 + .../out/_next/static/chunks/0e731ri10cro_.js | 48 -- .../out/_next/static/chunks/0eamb3kk74kws.js | 7 + .../out/_next/static/chunks/0fk0i3e2aixp7.js | 1 + .../out/_next/static/chunks/0g8wwba6umbim.js | 1 + .../out/_next/static/chunks/0i25zatajbma2.js | 1 + .../out/_next/static/chunks/0if4h9a-qzqx4.js | 427 ++++++++++++++++++ .../out/_next/static/chunks/0j0zka6472o9x.js | 1 + .../{0xat75fur-vdx.js => 0j43vc4hvn3oe.js} | 2 +- .../out/_next/static/chunks/0l9pipu0v15od.js | 10 - .../out/_next/static/chunks/0m-x8i06te864.js | 1 + .../out/_next/static/chunks/0maan-7nzqqca.js | 1 + .../out/_next/static/chunks/0mk_ui2mxovtr.js | 1 - .../out/_next/static/chunks/0p_yh7pymv-5p.js | 7 + .../out/_next/static/chunks/0qi5f31t0jtxn.js | 1 - .../out/_next/static/chunks/0qtmfaeayrb_n.js | 10 + .../out/_next/static/chunks/0rai6y402ozrh.js | 10 - .../out/_next/static/chunks/0sjkobnebgkxj.js | 1 - .../out/_next/static/chunks/0taea1jhojoz5.js | 11 + .../out/_next/static/chunks/0tut58foro5b1.js | 1 - .../out/_next/static/chunks/0u6stnlvnicfs.js | 2 - .../out/_next/static/chunks/0ww76lz_0cphv.js | 1 + .../out/_next/static/chunks/0x16e8q2e1nn1.js | 1 - .../out/_next/static/chunks/0y00ve1sk9qox.js | 2 + .../out/_next/static/chunks/118otmxezkouq.js | 1 + .../out/_next/static/chunks/11khk745tfruy.js | 1 - .../out/_next/static/chunks/14aik5-j--wpq.js | 16 + .../out/_next/static/chunks/14fuqgkm8u5ry.js | 10 - .../out/_next/static/chunks/17ujqh1-hjhsw.js | 2 + .../out/_next/static/chunks/18p2cbxot7jjn.js | 7 - .../out/_next/static/chunks/18xsk13eujd67.js | 7 - .../out/_next/static/chunks/19283pb0f3m0p.js | 1 + .../out/_next/static/chunks/1bi3j49b6k_jv.js | 7 + .../out/_next/static/chunks/1c1tt3xmp9cgs.js | 1 - .../out/_next/static/chunks/1di-caw05k3tq.js | 1 + .../out/_next/static/chunks/1e6u2xmtlu2ip.js | 1 - .../out/_next/static/chunks/1ehpup-6tbb0n.js | 1 + .../{1wa0r8pkfuo3z.js => 1fgqa8zynis07.js} | 4 +- .../out/_next/static/chunks/1fmx49l6q8v39.js | 10 + .../out/_next/static/chunks/1fw9aqdy3b9m6.js | 1 - .../out/_next/static/chunks/1gmcfcb5o49sk.js | 2 + .../out/_next/static/chunks/1hjnn9czeys5v.js | 1 - .../out/_next/static/chunks/1ivfvx86dix7-.js | 13 - .../out/_next/static/chunks/1l2mgm5v3tjci.js | 1 - .../out/_next/static/chunks/1le_uicmibz6_.js | 1 - .../out/_next/static/chunks/1m53s0r6v_2z7.js | 10 - .../out/_next/static/chunks/1mj4rwdo0gb12.js | 1 - .../out/_next/static/chunks/1mp27hsvdhxkc.js | 1 - .../out/_next/static/chunks/1o1l-d7k6z8y3.js | 8 - .../out/_next/static/chunks/1o8x1l2hhet9i.js | 2 + .../out/_next/static/chunks/1r8dr-m94xgwo.js | 10 + .../out/_next/static/chunks/1sj1psk403aes.js | 2 - .../out/_next/static/chunks/1t-d4xiuay30_.js | 10 + .../out/_next/static/chunks/1t9h71-jh-nt0.js | 10 + .../out/_next/static/chunks/1u00qbe-ox8tr.js | 420 ----------------- .../out/_next/static/chunks/1uq2fo6k6zezb.js | 1 - .../out/_next/static/chunks/1uxrlxeosisc9.js | 7 + .../{1cwvvc8qgv3ru.js => 1uy2av_f_ojad.js} | 4 +- .../out/_next/static/chunks/1w3671zqgse91.js | 2 + .../out/_next/static/chunks/1w3c882l9ff7z.js | 14 - .../out/_next/static/chunks/1xuhivu7ukxx1.js | 1 + .../out/_next/static/chunks/1y4bfj-ui9wk1.js | 1 - .../out/_next/static/chunks/1ys3sui-_ujuc.js | 89 ++++ .../out/_next/static/chunks/1zhc7xkjz01rc.js | 10 - .../out/_next/static/chunks/1zkw9jo-mbcpr.js | 2 + .../out/_next/static/chunks/2-a_yn53fgb-5.js | 1 + .../out/_next/static/chunks/22ujkf10ty06o.js | 10 + .../out/_next/static/chunks/23vtcpdpp2h9h.css | 1 - .../out/_next/static/chunks/250thbgz3q1h0.js | 2 - .../out/_next/static/chunks/279q69zxpub5q.js | 2 - .../{3numd45hxsqx_.js => 28hnu_qv5e_c_.js} | 2 +- .../out/_next/static/chunks/2_o_2f57j_-wv.js | 7 + .../out/_next/static/chunks/2_r1-ssk6qj_g.js | 10 + .../{3c__kf1saz5q1.js => 2a-z_e49tyoo9.js} | 2 +- .../out/_next/static/chunks/2cbf4k2g_n-5n.js | 1 + .../out/_next/static/chunks/2cd8z85o5pd_-.js | 11 + .../out/_next/static/chunks/2csos-a4xcbdo.js | 1 - .../out/_next/static/chunks/2di7gurm0ukkn.js | 1 + .../out/_next/static/chunks/2dk1crwazaaeo.js | 1 - .../out/_next/static/chunks/2dsrb9323jnso.js | 11 - .../out/_next/static/chunks/2f9ut03jhmdi3.js | 7 + .../out/_next/static/chunks/2frpidyqqrenq.js | 10 + .../out/_next/static/chunks/2iwsg18rpz6hv.js | 1 + .../out/_next/static/chunks/2n26sdz53rm0a.js | 1 - .../{25mdk9s3y899y.js => 2nch9p216bkna.js} | 2 +- .../out/_next/static/chunks/2om7p3yr7inpq.js | 1 - .../out/_next/static/chunks/2p3h6991b9qoi.js | 1 + .../out/_next/static/chunks/2pazoe5r3wvod.js | 1 - .../out/_next/static/chunks/2pj8_ri31z7q7.js | 1 - .../out/_next/static/chunks/2ptdxz8qnchh_.js | 1 - .../out/_next/static/chunks/2qtobvowg08en.js | 10 + .../out/_next/static/chunks/2s3jwhs4py7sf.js | 1 - .../out/_next/static/chunks/2s_ce-opzrkzr.js | 2 - .../out/_next/static/chunks/2se5kcdf7ihc3.js | 1 + .../{0mgvhfl1hy6ff.js => 2stnfrjosi49a.js} | 2 +- .../{3c4jvsdr97f90.js => 2uc2pi4ob086w.js} | 2 +- .../out/_next/static/chunks/2vuo01d0b8c1k.js | 1 + .../out/_next/static/chunks/2wzbftaqumx8j.js | 10 + .../out/_next/static/chunks/2x6bixy54rehh.js | 1 + .../out/_next/static/chunks/2zsy6czb10dof.js | 1 - .../{33i2s0mxd659a.js => 3-9r9qzlv5bdt.js} | 2 +- .../out/_next/static/chunks/3-_nx473x7j2c.js | 26 ++ .../out/_next/static/chunks/310jfkx44dv17.js | 7 + .../out/_next/static/chunks/3254j4ut19q6_.css | 1 + .../out/_next/static/chunks/32srfurefj1bf.js | 1 + .../out/_next/static/chunks/33bgg52xnwqaf.js | 1 + .../{0vmmr0cztka2n.js => 33cg4kshh4bdo.js} | 4 +- .../out/_next/static/chunks/352mlo4k4azve.js | 10 - .../out/_next/static/chunks/35s0c1u_z6dbt.js | 39 ++ .../out/_next/static/chunks/3809uirt2jusa.js | 8 + .../out/_next/static/chunks/395_vbpmrlvpu.js | 10 - .../{1xojmvxlvhrja.js => 395p6a6cbvfah.js} | 2 +- .../out/_next/static/chunks/39u3feg0b-gml.js | 1 - .../out/_next/static/chunks/3_fum429at8kg.js | 1 + .../{0map77ee0fk0e.js => 3c02m_kr-u94p.js} | 24 +- .../out/_next/static/chunks/3cxlhog-5qqg1.js | 7 - .../{04m0obyskflau.js => 3dqt2-fiow5k8.js} | 2 +- .../out/_next/static/chunks/3f-3kisu7wrvc.js | 7 + .../out/_next/static/chunks/3f9uewf5w-e-p.js | 1 - .../out/_next/static/chunks/3fkpwmoe75b5k.js | 1 - .../out/_next/static/chunks/3isuz7fxdnfb4.js | 2 - .../out/_next/static/chunks/3joc95ez470xr.js | 1 - .../out/_next/static/chunks/3js0nq3cf5adx.js | 1 + .../out/_next/static/chunks/3kmoa-63y6leb.js | 10 + .../out/_next/static/chunks/3lp21rcjbjj72.js | 10 + .../out/_next/static/chunks/3m3ycuiz_2ybr.js | 11 - .../out/_next/static/chunks/3pl61y6w4hwya.js | 1 - .../{2wzxf6lnnwc_m.js => 3ppnfh2ysdwqp.js} | 2 +- .../out/_next/static/chunks/3qmqisehp5fz5.js | 89 ---- .../out/_next/static/chunks/3rxr_fmvlxjkw.js | 1 + .../out/_next/static/chunks/3srzg1la93pwv.js | 1 + .../out/_next/static/chunks/3t4iumhktznmc.js | 26 ++ .../out/_next/static/chunks/3xy9k5gh9tycj.js | 1 - .../out/_next/static/chunks/42mnrfftrvhcn.js | 10 + .../out/_next/static/chunks/43hu9sdfrq-xw.js | 16 - .../_buildManifest.js | 0 .../_clientMiddlewareManifest.js | 0 .../_ssgManifest.js | 0 .../out/_not-found/__next._full.txt | 24 +- .../out/_not-found/__next._head.txt | 8 +- .../out/_not-found/__next._index.txt | 14 +- .../_not-found/__next._not-found.__PAGE__.txt | 4 +- .../out/_not-found/__next._not-found.txt | 6 +- .../out/_not-found/__next._tree.txt | 4 +- .../_experimental/out/_not-found/index.html | 2 +- .../_experimental/out/_not-found/index.txt | 24 +- ...KGRhc2hib2FyZCk.access-groups.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.access-groups.txt | 6 +- .../access-groups/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/access-groups/__next._full.txt | 36 +- .../out/access-groups/__next._head.txt | 8 +- .../out/access-groups/__next._index.txt | 14 +- .../out/access-groups/__next._tree.txt | 4 +- .../out/access-groups/index.html | 2 +- .../_experimental/out/access-groups/index.txt | 36 +- ....!KGRhc2hib2FyZCk.admin-panel.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.admin-panel.txt | 6 +- .../admin-panel/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/admin-panel/__next._full.txt | 36 +- .../out/admin-panel/__next._head.txt | 8 +- .../out/admin-panel/__next._index.txt | 14 +- .../out/admin-panel/__next._tree.txt | 4 +- .../_experimental/out/admin-panel/index.html | 2 +- .../_experimental/out/admin-panel/index.txt | 36 +- ..._next.!KGRhc2hib2FyZCk.agents.__PAGE__.txt | 8 +- .../agents/__next.!KGRhc2hib2FyZCk.agents.txt | 6 +- .../out/agents/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../_experimental/out/agents/__next._full.txt | 36 +- .../_experimental/out/agents/__next._head.txt | 8 +- .../out/agents/__next._index.txt | 14 +- .../_experimental/out/agents/__next._tree.txt | 4 +- .../proxy/_experimental/out/agents/index.html | 2 +- .../proxy/_experimental/out/agents/index.txt | 36 +- ...ext.!KGRhc2hib2FyZCk.api-keys.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.api-keys.txt | 6 +- .../out/api-keys/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/api-keys/__next._full.txt | 36 +- .../out/api-keys/__next._head.txt | 8 +- .../out/api-keys/__next._index.txt | 14 +- .../out/api-keys/__next._tree.txt | 4 +- .../_experimental/out/api-keys/index.html | 2 +- .../_experimental/out/api-keys/index.txt | 36 +- ...KGRhc2hib2FyZCk.api-reference.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.api-reference.txt | 6 +- .../api-reference/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/api-reference/__next._full.txt | 34 +- .../out/api-reference/__next._head.txt | 8 +- .../out/api-reference/__next._index.txt | 14 +- .../out/api-reference/__next._tree.txt | 4 +- .../out/api-reference/index.html | 2 +- .../_experimental/out/api-reference/index.txt | 34 +- ...next.!KGRhc2hib2FyZCk.budgets.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.budgets.txt | 6 +- .../out/budgets/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/budgets/__next._full.txt | 36 +- .../out/budgets/__next._head.txt | 8 +- .../out/budgets/__next._index.txt | 14 +- .../out/budgets/__next._tree.txt | 4 +- .../_experimental/out/budgets/index.html | 2 +- .../proxy/_experimental/out/budgets/index.txt | 36 +- ...next.!KGRhc2hib2FyZCk.caching.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.caching.txt | 6 +- .../out/caching/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/caching/__next._full.txt | 36 +- .../out/caching/__next._head.txt | 8 +- .../out/caching/__next._index.txt | 14 +- .../out/caching/__next._tree.txt | 4 +- .../_experimental/out/caching/index.html | 2 +- .../proxy/_experimental/out/caching/index.txt | 36 +- .../_experimental/out/chat/__next._full.txt | 36 +- .../_experimental/out/chat/__next._head.txt | 8 +- .../_experimental/out/chat/__next._index.txt | 14 +- .../_experimental/out/chat/__next._tree.txt | 4 +- .../out/chat/__next.chat.__PAGE__.txt | 8 +- .../_experimental/out/chat/__next.chat.txt | 10 +- .../out/chat/api-keys/__next._full.txt | 34 +- .../out/chat/api-keys/__next._head.txt | 8 +- .../out/chat/api-keys/__next._index.txt | 14 +- .../out/chat/api-keys/__next._tree.txt | 4 +- .../__next.chat.api-keys.__PAGE__.txt | 8 +- .../chat/api-keys/__next.chat.api-keys.txt | 6 +- .../out/chat/api-keys/__next.chat.txt | 10 +- .../out/chat/api-keys/index.html | 2 +- .../_experimental/out/chat/api-keys/index.txt | 34 +- .../out/chat/credentials/__next._full.txt | 34 +- .../out/chat/credentials/__next._head.txt | 8 +- .../out/chat/credentials/__next._index.txt | 14 +- .../out/chat/credentials/__next._tree.txt | 4 +- .../__next.chat.credentials.__PAGE__.txt | 8 +- .../credentials/__next.chat.credentials.txt | 6 +- .../out/chat/credentials/__next.chat.txt | 10 +- .../out/chat/credentials/index.html | 2 +- .../out/chat/credentials/index.txt | 34 +- .../proxy/_experimental/out/chat/index.html | 2 +- .../proxy/_experimental/out/chat/index.txt | 36 +- .../out/chat/integrations/__next._full.txt | 36 +- .../out/chat/integrations/__next._head.txt | 8 +- .../out/chat/integrations/__next._index.txt | 14 +- .../out/chat/integrations/__next._tree.txt | 4 +- .../__next.chat.integrations.__PAGE__.txt | 8 +- .../integrations/__next.chat.integrations.txt | 6 +- .../out/chat/integrations/__next.chat.txt | 10 +- .../out/chat/integrations/index.html | 2 +- .../out/chat/integrations/index.txt | 36 +- .../out/chat/logs/__next._full.txt | 34 +- .../out/chat/logs/__next._head.txt | 8 +- .../out/chat/logs/__next._index.txt | 14 +- .../out/chat/logs/__next._tree.txt | 4 +- .../chat/logs/__next.chat.logs.__PAGE__.txt | 8 +- .../out/chat/logs/__next.chat.logs.txt | 6 +- .../out/chat/logs/__next.chat.txt | 10 +- .../_experimental/out/chat/logs/index.html | 2 +- .../_experimental/out/chat/logs/index.txt | 34 +- .../out/chat/usage/__next._full.txt | 34 +- .../out/chat/usage/__next._head.txt | 8 +- .../out/chat/usage/__next._index.txt | 14 +- .../out/chat/usage/__next._tree.txt | 4 +- .../out/chat/usage/__next.chat.txt | 10 +- .../chat/usage/__next.chat.usage.__PAGE__.txt | 8 +- .../out/chat/usage/__next.chat.usage.txt | 6 +- .../_experimental/out/chat/usage/index.html | 2 +- .../_experimental/out/chat/usage/index.txt | 34 +- .../out/connect/__next._full.txt | 34 +- .../out/connect/__next._head.txt | 8 +- .../out/connect/__next._index.txt | 14 +- .../out/connect/__next._tree.txt | 4 +- .../out/connect/__next.connect.__PAGE__.txt | 8 +- .../out/connect/__next.connect.txt | 10 +- .../_experimental/out/connect/index.html | 2 +- .../proxy/_experimental/out/connect/index.txt | 34 +- ...c2hib2FyZCk.cost-optimization.__PAGE__.txt | 8 +- ...ext.!KGRhc2hib2FyZCk.cost-optimization.txt | 6 +- .../__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/cost-optimization/__next._full.txt | 36 +- .../out/cost-optimization/__next._head.txt | 8 +- .../out/cost-optimization/__next._index.txt | 14 +- .../out/cost-optimization/__next._tree.txt | 4 +- .../out/cost-optimization/index.html | 2 +- .../out/cost-optimization/index.txt | 36 +- ...KGRhc2hib2FyZCk.cost-tracking.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.cost-tracking.txt | 6 +- .../cost-tracking/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/cost-tracking/__next._full.txt | 36 +- .../out/cost-tracking/__next._head.txt | 8 +- .../out/cost-tracking/__next._index.txt | 14 +- .../out/cost-tracking/__next._tree.txt | 4 +- .../out/cost-tracking/index.html | 2 +- .../_experimental/out/cost-tracking/index.txt | 36 +- ...2hib2FyZCk.guardrails-monitor.__PAGE__.txt | 8 +- ...xt.!KGRhc2hib2FyZCk.guardrails-monitor.txt | 6 +- .../__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/guardrails-monitor/__next._full.txt | 36 +- .../out/guardrails-monitor/__next._head.txt | 8 +- .../out/guardrails-monitor/__next._index.txt | 14 +- .../out/guardrails-monitor/__next._tree.txt | 4 +- .../out/guardrails-monitor/index.html | 2 +- .../out/guardrails-monitor/index.txt | 36 +- ...t.!KGRhc2hib2FyZCk.guardrails.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.guardrails.txt | 6 +- .../guardrails/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/guardrails/__next._full.txt | 36 +- .../out/guardrails/__next._head.txt | 8 +- .../out/guardrails/__next._index.txt | 14 +- .../out/guardrails/__next._tree.txt | 4 +- .../_experimental/out/guardrails/index.html | 2 +- .../_experimental/out/guardrails/index.txt | 36 +- litellm/proxy/_experimental/out/index.html | 2 +- litellm/proxy/_experimental/out/index.txt | 36 +- ...2hib2FyZCk.logging-and-alerts.__PAGE__.txt | 8 +- ...xt.!KGRhc2hib2FyZCk.logging-and-alerts.txt | 6 +- .../__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/logging-and-alerts/__next._full.txt | 36 +- .../out/logging-and-alerts/__next._head.txt | 8 +- .../out/logging-and-alerts/__next._index.txt | 14 +- .../out/logging-and-alerts/__next._tree.txt | 4 +- .../out/logging-and-alerts/index.html | 2 +- .../out/logging-and-alerts/index.txt | 36 +- .../_experimental/out/login/__next._full.txt | 28 +- .../_experimental/out/login/__next._head.txt | 8 +- .../_experimental/out/login/__next._index.txt | 14 +- .../_experimental/out/login/__next._tree.txt | 4 +- .../out/login/__next.login.__PAGE__.txt | 8 +- .../_experimental/out/login/__next.login.txt | 6 +- .../proxy/_experimental/out/login/index.html | 2 +- .../proxy/_experimental/out/login/index.txt | 28 +- .../__next.!KGRhc2hib2FyZCk.logs.__PAGE__.txt | 8 +- .../out/logs/__next.!KGRhc2hib2FyZCk.logs.txt | 6 +- .../out/logs/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../_experimental/out/logs/__next._full.txt | 36 +- .../_experimental/out/logs/__next._head.txt | 8 +- .../_experimental/out/logs/__next._index.txt | 14 +- .../_experimental/out/logs/__next._tree.txt | 4 +- .../proxy/_experimental/out/logs/index.html | 2 +- .../proxy/_experimental/out/logs/index.txt | 36 +- ....!KGRhc2hib2FyZCk.mcp-servers.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.mcp-servers.txt | 6 +- .../mcp-servers/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/mcp-servers/__next._full.txt | 36 +- .../out/mcp-servers/__next._head.txt | 8 +- .../out/mcp-servers/__next._index.txt | 14 +- .../out/mcp-servers/__next._tree.txt | 4 +- .../_experimental/out/mcp-servers/index.html | 2 +- .../_experimental/out/mcp-servers/index.txt | 36 +- .../out/mcp/oauth/callback/__next._full.txt | 28 +- .../out/mcp/oauth/callback/__next._head.txt | 8 +- .../out/mcp/oauth/callback/__next._index.txt | 14 +- .../out/mcp/oauth/callback/__next._tree.txt | 4 +- .../__next.mcp.oauth.callback.__PAGE__.txt | 8 +- .../callback/__next.mcp.oauth.callback.txt | 6 +- .../mcp/oauth/callback/__next.mcp.oauth.txt | 6 +- .../out/mcp/oauth/callback/__next.mcp.txt | 6 +- .../out/mcp/oauth/callback/index.html | 2 +- .../out/mcp/oauth/callback/index.txt | 28 +- ..._next.!KGRhc2hib2FyZCk.memory.__PAGE__.txt | 8 +- .../memory/__next.!KGRhc2hib2FyZCk.memory.txt | 6 +- .../out/memory/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../_experimental/out/memory/__next._full.txt | 36 +- .../_experimental/out/memory/__next._head.txt | 8 +- .../out/memory/__next._index.txt | 14 +- .../_experimental/out/memory/__next._tree.txt | 4 +- .../proxy/_experimental/out/memory/index.html | 2 +- .../proxy/_experimental/out/memory/index.txt | 36 +- ...Rhc2hib2FyZCk.model-hub-table.__PAGE__.txt | 8 +- ..._next.!KGRhc2hib2FyZCk.model-hub-table.txt | 6 +- .../__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/model-hub-table/__next._full.txt | 34 +- .../out/model-hub-table/__next._head.txt | 8 +- .../out/model-hub-table/__next._index.txt | 14 +- .../out/model-hub-table/__next._tree.txt | 4 +- .../out/model-hub-table/index.html | 2 +- .../out/model-hub-table/index.txt | 34 +- .../out/model_hub/__next._full.txt | 30 +- .../out/model_hub/__next._head.txt | 8 +- .../out/model_hub/__next._index.txt | 14 +- .../out/model_hub/__next._tree.txt | 4 +- .../model_hub/__next.model_hub.__PAGE__.txt | 8 +- .../out/model_hub/__next.model_hub.txt | 6 +- .../_experimental/out/model_hub/index.html | 2 +- .../_experimental/out/model_hub/index.txt | 30 +- .../out/model_hub_table/__next._full.txt | 49 +- .../out/model_hub_table/__next._head.txt | 8 +- .../out/model_hub_table/__next._index.txt | 14 +- .../out/model_hub_table/__next._tree.txt | 4 +- .../__next.model_hub_table.__PAGE__.txt | 8 +- .../__next.model_hub_table.txt | 6 +- .../out/model_hub_table/index.html | 2 +- .../out/model_hub_table/index.txt | 49 +- ...ib2FyZCk.models-and-endpoints.__PAGE__.txt | 8 +- ....!KGRhc2hib2FyZCk.models-and-endpoints.txt | 6 +- .../__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/models-and-endpoints/__next._full.txt | 36 +- .../out/models-and-endpoints/__next._head.txt | 8 +- .../models-and-endpoints/__next._index.txt | 14 +- .../out/models-and-endpoints/__next._tree.txt | 4 +- .../out/models-and-endpoints/index.html | 2 +- .../out/models-and-endpoints/index.txt | 36 +- ...xt.!KGRhc2hib2FyZCk.old-usage.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.old-usage.txt | 6 +- .../out/old-usage/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/old-usage/__next._full.txt | 36 +- .../out/old-usage/__next._head.txt | 8 +- .../out/old-usage/__next._index.txt | 14 +- .../out/old-usage/__next._tree.txt | 4 +- .../_experimental/out/old-usage/index.html | 2 +- .../_experimental/out/old-usage/index.txt | 36 +- .../out/onboarding/__next._full.txt | 28 +- .../out/onboarding/__next._head.txt | 8 +- .../out/onboarding/__next._index.txt | 14 +- .../out/onboarding/__next._tree.txt | 4 +- .../onboarding/__next.onboarding.__PAGE__.txt | 8 +- .../out/onboarding/__next.onboarding.txt | 6 +- .../_experimental/out/onboarding/index.html | 2 +- .../_experimental/out/onboarding/index.txt | 28 +- ...KGRhc2hib2FyZCk.organizations.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.organizations.txt | 6 +- .../organizations/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/organizations/__next._full.txt | 36 +- .../out/organizations/__next._head.txt | 8 +- .../out/organizations/__next._index.txt | 14 +- .../out/organizations/__next._tree.txt | 4 +- .../out/organizations/index.html | 2 +- .../_experimental/out/organizations/index.txt | 36 +- ...t.!KGRhc2hib2FyZCk.playground.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.playground.txt | 6 +- .../playground/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/playground/__next._full.txt | 36 +- .../out/playground/__next._head.txt | 8 +- .../out/playground/__next._index.txt | 14 +- .../out/playground/__next._tree.txt | 4 +- .../_experimental/out/playground/index.html | 2 +- .../_experimental/out/playground/index.txt | 36 +- ...ext.!KGRhc2hib2FyZCk.policies.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.policies.txt | 6 +- .../out/policies/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/policies/__next._full.txt | 36 +- .../out/policies/__next._head.txt | 8 +- .../out/policies/__next._index.txt | 14 +- .../out/policies/__next._tree.txt | 4 +- .../_experimental/out/policies/index.html | 2 +- .../_experimental/out/policies/index.txt | 36 +- ...ext.!KGRhc2hib2FyZCk.projects.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.projects.txt | 6 +- .../out/projects/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/projects/__next._full.txt | 36 +- .../out/projects/__next._head.txt | 8 +- .../out/projects/__next._index.txt | 14 +- .../out/projects/__next._tree.txt | 4 +- .../_experimental/out/projects/index.html | 2 +- .../_experimental/out/projects/index.txt | 36 +- ...next.!KGRhc2hib2FyZCk.prompts.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.prompts.txt | 6 +- .../out/prompts/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/prompts/__next._full.txt | 36 +- .../out/prompts/__next._head.txt | 8 +- .../out/prompts/__next._index.txt | 14 +- .../out/prompts/__next._tree.txt | 4 +- .../_experimental/out/prompts/index.html | 2 +- .../proxy/_experimental/out/prompts/index.txt | 36 +- ...Rhc2hib2FyZCk.router-settings.__PAGE__.txt | 8 +- ..._next.!KGRhc2hib2FyZCk.router-settings.txt | 6 +- .../__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/router-settings/__next._full.txt | 36 +- .../out/router-settings/__next._head.txt | 8 +- .../out/router-settings/__next._index.txt | 14 +- .../out/router-settings/__next._tree.txt | 4 +- .../out/router-settings/index.html | 2 +- .../out/router-settings/index.txt | 36 +- ...!KGRhc2hib2FyZCk.search-tools.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.search-tools.txt | 6 +- .../search-tools/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/search-tools/__next._full.txt | 34 +- .../out/search-tools/__next._head.txt | 8 +- .../out/search-tools/__next._index.txt | 14 +- .../out/search-tools/__next._tree.txt | 4 +- .../_experimental/out/search-tools/index.html | 2 +- .../_experimental/out/search-tools/index.txt | 34 +- ..._next.!KGRhc2hib2FyZCk.skills.__PAGE__.txt | 8 +- .../skills/__next.!KGRhc2hib2FyZCk.skills.txt | 6 +- .../out/skills/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../_experimental/out/skills/__next._full.txt | 34 +- .../_experimental/out/skills/__next._head.txt | 8 +- .../out/skills/__next._index.txt | 14 +- .../_experimental/out/skills/__next._tree.txt | 4 +- .../proxy/_experimental/out/skills/index.html | 2 +- .../proxy/_experimental/out/skills/index.txt | 34 +- ...GRhc2hib2FyZCk.tag-management.__PAGE__.txt | 8 +- ...__next.!KGRhc2hib2FyZCk.tag-management.txt | 6 +- .../__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/tag-management/__next._full.txt | 36 +- .../out/tag-management/__next._head.txt | 8 +- .../out/tag-management/__next._index.txt | 14 +- .../out/tag-management/__next._tree.txt | 4 +- .../out/tag-management/index.html | 2 +- .../out/tag-management/index.txt | 36 +- ...__next.!KGRhc2hib2FyZCk.teams.__PAGE__.txt | 8 +- .../teams/__next.!KGRhc2hib2FyZCk.teams.txt | 6 +- .../out/teams/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../_experimental/out/teams/__next._full.txt | 36 +- .../_experimental/out/teams/__next._head.txt | 8 +- .../_experimental/out/teams/__next._index.txt | 14 +- .../_experimental/out/teams/__next._tree.txt | 4 +- .../proxy/_experimental/out/teams/index.html | 2 +- .../proxy/_experimental/out/teams/index.txt | 36 +- ...KGRhc2hib2FyZCk.tool-policies.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.tool-policies.txt | 6 +- .../tool-policies/__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/tool-policies/__next._full.txt | 36 +- .../out/tool-policies/__next._head.txt | 8 +- .../out/tool-policies/__next._index.txt | 14 +- .../out/tool-policies/__next._tree.txt | 4 +- .../out/tool-policies/index.html | 2 +- .../_experimental/out/tool-policies/index.txt | 36 +- ...c2hib2FyZCk.transform-request.__PAGE__.txt | 8 +- ...ext.!KGRhc2hib2FyZCk.transform-request.txt | 6 +- .../__next.!KGRhc2hib2FyZCk.txt | 10 +- .../out/transform-request/__next._full.txt | 34 +- .../out/transform-request/__next._head.txt | 8 +- .../out/transform-request/__next._index.txt | 14 +- .../out/transform-request/__next._tree.txt | 4 +- .../out/transform-request/index.html | 2 +- .../out/transform-request/index.txt | 34 +- .../out/ui-theme/__next.!KGRhc2hib2FyZCk.txt | 10 +- ...ext.!KGRhc2hib2FyZCk.ui-theme.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.ui-theme.txt | 6 +- .../out/ui-theme/__next._full.txt | 34 +- .../out/ui-theme/__next._head.txt | 8 +- .../out/ui-theme/__next._index.txt | 14 +- .../out/ui-theme/__next._tree.txt | 4 +- .../_experimental/out/ui-theme/index.html | 2 +- .../_experimental/out/ui-theme/index.txt | 34 +- .../out/usage/__next.!KGRhc2hib2FyZCk.txt | 10 +- ...__next.!KGRhc2hib2FyZCk.usage.__PAGE__.txt | 8 +- .../usage/__next.!KGRhc2hib2FyZCk.usage.txt | 6 +- .../_experimental/out/usage/__next._full.txt | 36 +- .../_experimental/out/usage/__next._head.txt | 8 +- .../_experimental/out/usage/__next._index.txt | 14 +- .../_experimental/out/usage/__next._tree.txt | 4 +- .../proxy/_experimental/out/usage/index.html | 2 +- .../proxy/_experimental/out/usage/index.txt | 36 +- .../out/users/__next.!KGRhc2hib2FyZCk.txt | 10 +- ...__next.!KGRhc2hib2FyZCk.users.__PAGE__.txt | 8 +- .../users/__next.!KGRhc2hib2FyZCk.users.txt | 6 +- .../_experimental/out/users/__next._full.txt | 36 +- .../_experimental/out/users/__next._head.txt | 8 +- .../_experimental/out/users/__next._index.txt | 14 +- .../_experimental/out/users/__next._tree.txt | 4 +- .../proxy/_experimental/out/users/index.html | 2 +- .../proxy/_experimental/out/users/index.txt | 36 +- .../vector-stores/__next.!KGRhc2hib2FyZCk.txt | 10 +- ...KGRhc2hib2FyZCk.vector-stores.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.vector-stores.txt | 6 +- .../out/vector-stores/__next._full.txt | 34 +- .../out/vector-stores/__next._head.txt | 8 +- .../out/vector-stores/__next._index.txt | 14 +- .../out/vector-stores/__next._tree.txt | 4 +- .../out/vector-stores/index.html | 2 +- .../_experimental/out/vector-stores/index.txt | 34 +- .../out/workflows/__next.!KGRhc2hib2FyZCk.txt | 10 +- ...xt.!KGRhc2hib2FyZCk.workflows.__PAGE__.txt | 8 +- .../__next.!KGRhc2hib2FyZCk.workflows.txt | 6 +- .../out/workflows/__next._full.txt | 34 +- .../out/workflows/__next._head.txt | 8 +- .../out/workflows/__next._index.txt | 14 +- .../out/workflows/__next._tree.txt | 4 +- .../_experimental/out/workflows/index.html | 2 +- .../_experimental/out/workflows/index.txt | 34 +- 597 files changed, 3918 insertions(+), 3790 deletions(-) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0-k_4_s7m108w.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/025ocjcb8e481.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/046q5lwe95zp6.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/05287rwl48hh2.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/054z32giulaw7.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/058j9m4b8p4wx.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/06q8aep867ss7.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/08691-q-pz235.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/08goggic_ad66.js rename litellm/proxy/_experimental/out/_next/static/chunks/{3y674jhwchpcq.js => 0_6rii-l50y-j.js} (68%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0_mw8gm-qowti.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0_v0ovphg1p2h.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0a-3zns09ja98.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0afqpg84x7rak.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0ald9tfsbz2e-.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0am68mi9t9cb6.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0bcwe_r_o0z3w.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0bzsgiuwi1q0e.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0cgtkk6qelb_j.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0cu_67b262ror.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0cxlei71txljy.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0d6y38dwy2fvp.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0denwarlgmop7.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0dsiq_ok1yngk.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0e731ri10cro_.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0eamb3kk74kws.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0fk0i3e2aixp7.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0g8wwba6umbim.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0i25zatajbma2.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0if4h9a-qzqx4.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0j0zka6472o9x.js rename litellm/proxy/_experimental/out/_next/static/chunks/{0xat75fur-vdx.js => 0j43vc4hvn3oe.js} (84%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0l9pipu0v15od.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0m-x8i06te864.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0maan-7nzqqca.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0mk_ui2mxovtr.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0p_yh7pymv-5p.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0qi5f31t0jtxn.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0qtmfaeayrb_n.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0rai6y402ozrh.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0sjkobnebgkxj.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0taea1jhojoz5.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0tut58foro5b1.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0u6stnlvnicfs.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0ww76lz_0cphv.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0x16e8q2e1nn1.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/0y00ve1sk9qox.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/118otmxezkouq.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/11khk745tfruy.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/14aik5-j--wpq.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/14fuqgkm8u5ry.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/17ujqh1-hjhsw.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/18p2cbxot7jjn.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/18xsk13eujd67.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/19283pb0f3m0p.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1bi3j49b6k_jv.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1c1tt3xmp9cgs.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1di-caw05k3tq.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1e6u2xmtlu2ip.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1ehpup-6tbb0n.js rename litellm/proxy/_experimental/out/_next/static/chunks/{1wa0r8pkfuo3z.js => 1fgqa8zynis07.js} (53%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1fmx49l6q8v39.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1fw9aqdy3b9m6.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1gmcfcb5o49sk.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1hjnn9czeys5v.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1ivfvx86dix7-.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1l2mgm5v3tjci.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1le_uicmibz6_.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1m53s0r6v_2z7.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1mj4rwdo0gb12.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1mp27hsvdhxkc.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1o1l-d7k6z8y3.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1o8x1l2hhet9i.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1r8dr-m94xgwo.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1sj1psk403aes.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1t-d4xiuay30_.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1t9h71-jh-nt0.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1u00qbe-ox8tr.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1uq2fo6k6zezb.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1uxrlxeosisc9.js rename litellm/proxy/_experimental/out/_next/static/chunks/{1cwvvc8qgv3ru.js => 1uy2av_f_ojad.js} (74%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1w3671zqgse91.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1w3c882l9ff7z.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1xuhivu7ukxx1.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1y4bfj-ui9wk1.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1ys3sui-_ujuc.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1zhc7xkjz01rc.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/1zkw9jo-mbcpr.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2-a_yn53fgb-5.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/22ujkf10ty06o.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/23vtcpdpp2h9h.css delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/250thbgz3q1h0.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/279q69zxpub5q.js rename litellm/proxy/_experimental/out/_next/static/chunks/{3numd45hxsqx_.js => 28hnu_qv5e_c_.js} (86%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2_o_2f57j_-wv.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2_r1-ssk6qj_g.js rename litellm/proxy/_experimental/out/_next/static/chunks/{3c__kf1saz5q1.js => 2a-z_e49tyoo9.js} (73%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2cbf4k2g_n-5n.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2cd8z85o5pd_-.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2csos-a4xcbdo.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2di7gurm0ukkn.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2dk1crwazaaeo.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2dsrb9323jnso.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2f9ut03jhmdi3.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2frpidyqqrenq.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2iwsg18rpz6hv.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2n26sdz53rm0a.js rename litellm/proxy/_experimental/out/_next/static/chunks/{25mdk9s3y899y.js => 2nch9p216bkna.js} (51%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2om7p3yr7inpq.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2p3h6991b9qoi.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2pazoe5r3wvod.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2pj8_ri31z7q7.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2ptdxz8qnchh_.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2qtobvowg08en.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2s3jwhs4py7sf.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2s_ce-opzrkzr.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2se5kcdf7ihc3.js rename litellm/proxy/_experimental/out/_next/static/chunks/{0mgvhfl1hy6ff.js => 2stnfrjosi49a.js} (88%) rename litellm/proxy/_experimental/out/_next/static/chunks/{3c4jvsdr97f90.js => 2uc2pi4ob086w.js} (79%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2vuo01d0b8c1k.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2wzbftaqumx8j.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2x6bixy54rehh.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/2zsy6czb10dof.js rename litellm/proxy/_experimental/out/_next/static/chunks/{33i2s0mxd659a.js => 3-9r9qzlv5bdt.js} (66%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3-_nx473x7j2c.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/310jfkx44dv17.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3254j4ut19q6_.css create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/32srfurefj1bf.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/33bgg52xnwqaf.js rename litellm/proxy/_experimental/out/_next/static/chunks/{0vmmr0cztka2n.js => 33cg4kshh4bdo.js} (53%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/352mlo4k4azve.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/35s0c1u_z6dbt.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3809uirt2jusa.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/395_vbpmrlvpu.js rename litellm/proxy/_experimental/out/_next/static/chunks/{1xojmvxlvhrja.js => 395p6a6cbvfah.js} (62%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/39u3feg0b-gml.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3_fum429at8kg.js rename litellm/proxy/_experimental/out/_next/static/chunks/{0map77ee0fk0e.js => 3c02m_kr-u94p.js} (81%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3cxlhog-5qqg1.js rename litellm/proxy/_experimental/out/_next/static/chunks/{04m0obyskflau.js => 3dqt2-fiow5k8.js} (53%) create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3f-3kisu7wrvc.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3f9uewf5w-e-p.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3fkpwmoe75b5k.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3isuz7fxdnfb4.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3joc95ez470xr.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3js0nq3cf5adx.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3kmoa-63y6leb.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3lp21rcjbjj72.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3m3ycuiz_2ybr.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3pl61y6w4hwya.js rename litellm/proxy/_experimental/out/_next/static/chunks/{2wzxf6lnnwc_m.js => 3ppnfh2ysdwqp.js} (57%) delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3qmqisehp5fz5.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3rxr_fmvlxjkw.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3srzg1la93pwv.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3t4iumhktznmc.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/3xy9k5gh9tycj.js create mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/42mnrfftrvhcn.js delete mode 100644 litellm/proxy/_experimental/out/_next/static/chunks/43hu9sdfrq-xw.js rename litellm/proxy/_experimental/out/_next/static/{0ljiPmkOdq7_yE4sZoXlJ => qXutWsQW5C1Pf62WxTkEI}/_buildManifest.js (100%) rename litellm/proxy/_experimental/out/_next/static/{0ljiPmkOdq7_yE4sZoXlJ => qXutWsQW5C1Pf62WxTkEI}/_clientMiddlewareManifest.js (100%) rename litellm/proxy/_experimental/out/_next/static/{0ljiPmkOdq7_yE4sZoXlJ => qXutWsQW5C1Pf62WxTkEI}/_ssgManifest.js (100%) diff --git a/litellm/proxy/_experimental/out/404.html b/litellm/proxy/_experimental/out/404.html index 96452afb6d3..0a164642dab 100644 --- a/litellm/proxy/_experimental/out/404.html +++ b/litellm/proxy/_experimental/out/404.html @@ -1 +1 @@ -404: This page could not be found.LiteLLM Dashboard

404

This page could not be found.

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

404

This page could not be found.

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

404

This page could not be found.

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

404

This page could not be found.

\ No newline at end of file diff --git a/litellm/proxy/_experimental/out/__next.!KGRhc2hib2FyZCk.__PAGE__.txt b/litellm/proxy/_experimental/out/__next.!KGRhc2hib2FyZCk.__PAGE__.txt index 657acb4c2e5..c10ced8b6bc 100644 --- a/litellm/proxy/_experimental/out/__next.!KGRhc2hib2FyZCk.__PAGE__.txt +++ b/litellm/proxy/_experimental/out/__next.!KGRhc2hib2FyZCk.__PAGE__.txt @@ -1,9 +1,9 @@ 1:"$Sreact.fragment" -2:I[347257,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ClientPageRoot"] -3:I[871135,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","/litellm-asset-prefix/_next/static/chunks/1ioy8obpggx93.js","/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","/litellm-asset-prefix/_next/static/chunks/1zr7rrk4wkmju.js","/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","/litellm-asset-prefix/_next/static/chunks/1l2mgm5v3tjci.js","/litellm-asset-prefix/_next/static/chunks/0zduf1gntl_f8.js","/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","/litellm-asset-prefix/_next/static/chunks/1a0bgy7kzrj91.js","/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","/litellm-asset-prefix/_next/static/chunks/2n26sdz53rm0a.js","/litellm-asset-prefix/_next/static/chunks/11khk745tfruy.js","/litellm-asset-prefix/_next/static/chunks/00g6xfr4yow7h.js","/litellm-asset-prefix/_next/static/chunks/0-k_4_s7m108w.js","/litellm-asset-prefix/_next/static/chunks/2kcxwg1mpncp6.js","/litellm-asset-prefix/_next/static/chunks/3drq2_k-jeio2.js","/litellm-asset-prefix/_next/static/chunks/395_vbpmrlvpu.js","/litellm-asset-prefix/_next/static/chunks/2hu1vyy-5pv13.js","/litellm-asset-prefix/_next/static/chunks/2l25bmiiw9ixp.js","/litellm-asset-prefix/_next/static/chunks/1uz3jt-tj9lkf.js","/litellm-asset-prefix/_next/static/chunks/199uwr871eene.js","/litellm-asset-prefix/_next/static/chunks/2s_ce-opzrkzr.js","/litellm-asset-prefix/_next/static/chunks/2ptdxz8qnchh_.js","/litellm-asset-prefix/_next/static/chunks/17nqbxvhztf3k.js","/litellm-asset-prefix/_next/static/chunks/2c90xukbd3il6.js","/litellm-asset-prefix/_next/static/chunks/0kap_rdm2-lem.js","/litellm-asset-prefix/_next/static/chunks/09l_m9l1emin2.js","/litellm-asset-prefix/_next/static/chunks/1cea03gg5a_c7.js","/litellm-asset-prefix/_next/static/chunks/323l6h8s7ahat.js","/litellm-asset-prefix/_next/static/chunks/112n0hv3cc2rg.js","/litellm-asset-prefix/_next/static/chunks/3y674jhwchpcq.js","/litellm-asset-prefix/_next/static/chunks/105643dvf00hu.js","/litellm-asset-prefix/_next/static/chunks/12wsfsljxg4xv.js","/litellm-asset-prefix/_next/static/chunks/22iools_e0k44.js","/litellm-asset-prefix/_next/static/chunks/3f9uewf5w-e-p.js","/litellm-asset-prefix/_next/static/chunks/23-g73xaw3kap.js"],"default"] -6:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"OutletBoundary"] +2:I[347257,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ClientPageRoot"] +3:I[871135,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","/litellm-asset-prefix/_next/static/chunks/1ioy8obpggx93.js","/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","/litellm-asset-prefix/_next/static/chunks/1zr7rrk4wkmju.js","/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","/litellm-asset-prefix/_next/static/chunks/0fk0i3e2aixp7.js","/litellm-asset-prefix/_next/static/chunks/0zduf1gntl_f8.js","/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","/litellm-asset-prefix/_next/static/chunks/1a0bgy7kzrj91.js","/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","/litellm-asset-prefix/_next/static/chunks/0g8wwba6umbim.js","/litellm-asset-prefix/_next/static/chunks/1di-caw05k3tq.js","/litellm-asset-prefix/_next/static/chunks/00g6xfr4yow7h.js","/litellm-asset-prefix/_next/static/chunks/2kcxwg1mpncp6.js","/litellm-asset-prefix/_next/static/chunks/1fmx49l6q8v39.js","/litellm-asset-prefix/_next/static/chunks/0ww76lz_0cphv.js","/litellm-asset-prefix/_next/static/chunks/2uc2pi4ob086w.js","/litellm-asset-prefix/_next/static/chunks/2hu1vyy-5pv13.js","/litellm-asset-prefix/_next/static/chunks/2l25bmiiw9ixp.js","/litellm-asset-prefix/_next/static/chunks/1uz3jt-tj9lkf.js","/litellm-asset-prefix/_next/static/chunks/3drq2_k-jeio2.js","/litellm-asset-prefix/_next/static/chunks/0dsiq_ok1yngk.js","/litellm-asset-prefix/_next/static/chunks/3srzg1la93pwv.js","/litellm-asset-prefix/_next/static/chunks/17nqbxvhztf3k.js","/litellm-asset-prefix/_next/static/chunks/2c90xukbd3il6.js","/litellm-asset-prefix/_next/static/chunks/0kap_rdm2-lem.js","/litellm-asset-prefix/_next/static/chunks/09l_m9l1emin2.js","/litellm-asset-prefix/_next/static/chunks/1cea03gg5a_c7.js","/litellm-asset-prefix/_next/static/chunks/323l6h8s7ahat.js","/litellm-asset-prefix/_next/static/chunks/112n0hv3cc2rg.js","/litellm-asset-prefix/_next/static/chunks/199uwr871eene.js","/litellm-asset-prefix/_next/static/chunks/105643dvf00hu.js","/litellm-asset-prefix/_next/static/chunks/12wsfsljxg4xv.js","/litellm-asset-prefix/_next/static/chunks/22iools_e0k44.js","/litellm-asset-prefix/_next/static/chunks/0am68mi9t9cb6.js","/litellm-asset-prefix/_next/static/chunks/23-g73xaw3kap.js"],"default"] +6:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"OutletBoundary"] 7:"$Sreact.suspense" -0:{"rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/00g6xfr4yow7h.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/0-k_4_s7m108w.js","async":true}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/2kcxwg1mpncp6.js","async":true}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/3drq2_k-jeio2.js","async":true}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/395_vbpmrlvpu.js","async":true}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/2hu1vyy-5pv13.js","async":true}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/2l25bmiiw9ixp.js","async":true}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/1uz3jt-tj9lkf.js","async":true}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/199uwr871eene.js","async":true}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/2s_ce-opzrkzr.js","async":true}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/2ptdxz8qnchh_.js","async":true}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/17nqbxvhztf3k.js","async":true}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/2c90xukbd3il6.js","async":true}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/0kap_rdm2-lem.js","async":true}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/09l_m9l1emin2.js","async":true}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/1cea03gg5a_c7.js","async":true}],["$","script","script-16",{"src":"/litellm-asset-prefix/_next/static/chunks/323l6h8s7ahat.js","async":true}],["$","script","script-17",{"src":"/litellm-asset-prefix/_next/static/chunks/112n0hv3cc2rg.js","async":true}],["$","script","script-18",{"src":"/litellm-asset-prefix/_next/static/chunks/3y674jhwchpcq.js","async":true}],["$","script","script-19",{"src":"/litellm-asset-prefix/_next/static/chunks/105643dvf00hu.js","async":true}],["$","script","script-20",{"src":"/litellm-asset-prefix/_next/static/chunks/12wsfsljxg4xv.js","async":true}],["$","script","script-21",{"src":"/litellm-asset-prefix/_next/static/chunks/22iools_e0k44.js","async":true}],["$","script","script-22",{"src":"/litellm-asset-prefix/_next/static/chunks/3f9uewf5w-e-p.js","async":true}],["$","script","script-23",{"src":"/litellm-asset-prefix/_next/static/chunks/23-g73xaw3kap.js","async":true}]],["$","$L6",null,{"children":["$","$7",null,{"name":"Next.MetadataOutlet","children":"$@8"}]}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"0ljiPmkOdq7_yE4sZoXlJ"} +0:{"rsc":["$","$1","c",{"children":[["$","$L2",null,{"Component":"$3","serverProvidedParams":{"searchParams":{},"params":{},"promises":["$@4","$@5"]}}],[["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/00g6xfr4yow7h.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/2kcxwg1mpncp6.js","async":true}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/1fmx49l6q8v39.js","async":true}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/0ww76lz_0cphv.js","async":true}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/2uc2pi4ob086w.js","async":true}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/2hu1vyy-5pv13.js","async":true}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/2l25bmiiw9ixp.js","async":true}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/1uz3jt-tj9lkf.js","async":true}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/3drq2_k-jeio2.js","async":true}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/0dsiq_ok1yngk.js","async":true}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/3srzg1la93pwv.js","async":true}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/17nqbxvhztf3k.js","async":true}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/2c90xukbd3il6.js","async":true}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/0kap_rdm2-lem.js","async":true}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/09l_m9l1emin2.js","async":true}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/1cea03gg5a_c7.js","async":true}],["$","script","script-16",{"src":"/litellm-asset-prefix/_next/static/chunks/323l6h8s7ahat.js","async":true}],["$","script","script-17",{"src":"/litellm-asset-prefix/_next/static/chunks/112n0hv3cc2rg.js","async":true}],["$","script","script-18",{"src":"/litellm-asset-prefix/_next/static/chunks/199uwr871eene.js","async":true}],["$","script","script-19",{"src":"/litellm-asset-prefix/_next/static/chunks/105643dvf00hu.js","async":true}],["$","script","script-20",{"src":"/litellm-asset-prefix/_next/static/chunks/12wsfsljxg4xv.js","async":true}],["$","script","script-21",{"src":"/litellm-asset-prefix/_next/static/chunks/22iools_e0k44.js","async":true}],["$","script","script-22",{"src":"/litellm-asset-prefix/_next/static/chunks/0am68mi9t9cb6.js","async":true}],["$","script","script-23",{"src":"/litellm-asset-prefix/_next/static/chunks/23-g73xaw3kap.js","async":true}]],["$","$L6",null,{"children":["$","$7",null,{"name":"Next.MetadataOutlet","children":"$@8"}]}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"qXutWsQW5C1Pf62WxTkEI"} 4:{} 5:"$0:rsc:props:children:0:props:serverProvidedParams:params" 8:null diff --git a/litellm/proxy/_experimental/out/__next.!KGRhc2hib2FyZCk.txt b/litellm/proxy/_experimental/out/__next.!KGRhc2hib2FyZCk.txt index 0eba32f6bf2..ef8a75b27ce 100644 --- a/litellm/proxy/_experimental/out/__next.!KGRhc2hib2FyZCk.txt +++ b/litellm/proxy/_experimental/out/__next.!KGRhc2hib2FyZCk.txt @@ -1,7 +1,7 @@ 1:"$Sreact.fragment" -2:I[92825,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ClientSegmentRoot"] -3:I[216370,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","/litellm-asset-prefix/_next/static/chunks/1ioy8obpggx93.js","/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","/litellm-asset-prefix/_next/static/chunks/1zr7rrk4wkmju.js","/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","/litellm-asset-prefix/_next/static/chunks/1l2mgm5v3tjci.js","/litellm-asset-prefix/_next/static/chunks/0zduf1gntl_f8.js","/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","/litellm-asset-prefix/_next/static/chunks/1a0bgy7kzrj91.js","/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","/litellm-asset-prefix/_next/static/chunks/2n26sdz53rm0a.js","/litellm-asset-prefix/_next/static/chunks/11khk745tfruy.js"],"default"] -4:I[339756,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] -5:I[837457,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] -0:{"rsc":["$","$1","c",{"children":[[["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/1ioy8obpggx93.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","async":true}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/1zr7rrk4wkmju.js","async":true}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","async":true}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","async":true}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/1l2mgm5v3tjci.js","async":true}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/0zduf1gntl_f8.js","async":true}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","async":true}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/1a0bgy7kzrj91.js","async":true}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","async":true}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","async":true}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","async":true}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","async":true}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","async":true}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/2n26sdz53rm0a.js","async":true}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/11khk745tfruy.js","async":true}]],["$","$L2",null,{"Component":"$3","slots":{"children":["$","$L4",null,{"parallelRouterKey":"children","template":["$","$L5",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]},"serverProvidedParams":{"params":{},"promises":["$@6"]}}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"0ljiPmkOdq7_yE4sZoXlJ"} +2:I[92825,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ClientSegmentRoot"] +3:I[216370,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","/litellm-asset-prefix/_next/static/chunks/1ioy8obpggx93.js","/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","/litellm-asset-prefix/_next/static/chunks/1zr7rrk4wkmju.js","/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","/litellm-asset-prefix/_next/static/chunks/0fk0i3e2aixp7.js","/litellm-asset-prefix/_next/static/chunks/0zduf1gntl_f8.js","/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","/litellm-asset-prefix/_next/static/chunks/1a0bgy7kzrj91.js","/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","/litellm-asset-prefix/_next/static/chunks/0g8wwba6umbim.js","/litellm-asset-prefix/_next/static/chunks/1di-caw05k3tq.js"],"default"] +4:I[339756,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] +5:I[837457,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] +0:{"rsc":["$","$1","c",{"children":[[["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/1ioy8obpggx93.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","async":true}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/1zr7rrk4wkmju.js","async":true}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","async":true}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","async":true}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/0fk0i3e2aixp7.js","async":true}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/0zduf1gntl_f8.js","async":true}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","async":true}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","async":true}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","async":true}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","async":true}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","async":true}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/1a0bgy7kzrj91.js","async":true}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","async":true}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/0g8wwba6umbim.js","async":true}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/1di-caw05k3tq.js","async":true}]],["$","$L2",null,{"Component":"$3","slots":{"children":["$","$L4",null,{"parallelRouterKey":"children","template":["$","$L5",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]},"serverProvidedParams":{"params":{},"promises":["$@6"]}}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"qXutWsQW5C1Pf62WxTkEI"} 6:"$0:rsc:props:children:1:props:serverProvidedParams:params" diff --git a/litellm/proxy/_experimental/out/__next._full.txt b/litellm/proxy/_experimental/out/__next._full.txt index 7b75f27b9e6..3ee486db39b 100644 --- a/litellm/proxy/_experimental/out/__next._full.txt +++ b/litellm/proxy/_experimental/out/__next._full.txt @@ -1,32 +1,32 @@ 1:"$Sreact.fragment" -2:I[867271,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] -3:I[71195,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] -4:I[557951,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"AuthProvider"] -5:I[339756,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] -6:I[837457,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] -7:I[92825,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ClientSegmentRoot"] -8:I[216370,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","/litellm-asset-prefix/_next/static/chunks/1ioy8obpggx93.js","/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","/litellm-asset-prefix/_next/static/chunks/1zr7rrk4wkmju.js","/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","/litellm-asset-prefix/_next/static/chunks/1l2mgm5v3tjci.js","/litellm-asset-prefix/_next/static/chunks/0zduf1gntl_f8.js","/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","/litellm-asset-prefix/_next/static/chunks/1a0bgy7kzrj91.js","/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","/litellm-asset-prefix/_next/static/chunks/2n26sdz53rm0a.js","/litellm-asset-prefix/_next/static/chunks/11khk745tfruy.js"],"default"] -e:I[168027,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default",1] +2:I[867271,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] +3:I[71195,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] +4:I[557951,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"AuthProvider"] +5:I[339756,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] +6:I[837457,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] +7:I[92825,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ClientSegmentRoot"] +8:I[216370,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","/litellm-asset-prefix/_next/static/chunks/1ioy8obpggx93.js","/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","/litellm-asset-prefix/_next/static/chunks/1zr7rrk4wkmju.js","/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","/litellm-asset-prefix/_next/static/chunks/0fk0i3e2aixp7.js","/litellm-asset-prefix/_next/static/chunks/0zduf1gntl_f8.js","/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","/litellm-asset-prefix/_next/static/chunks/1a0bgy7kzrj91.js","/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","/litellm-asset-prefix/_next/static/chunks/0g8wwba6umbim.js","/litellm-asset-prefix/_next/static/chunks/1di-caw05k3tq.js"],"default"] +e:I[168027,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default",1] :HL["/litellm-asset-prefix/_next/static/chunks/1kid9zr1--h6y.css","style"] -:HL["/litellm-asset-prefix/_next/static/chunks/23vtcpdpp2h9h.css","style"] +:HL["/litellm-asset-prefix/_next/static/chunks/3254j4ut19q6_.css","style"] :HL["/litellm-asset-prefix/_next/static/media/83afe278b6a6bb3c-s.p.2bn3s6zvc0dyp.woff2","font",{"crossOrigin":"","type":"font/woff2"}] -0:{"P":null,"c":["",""],"q":"","i":false,"f":[[["",{"children":["(dashboard)",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",16],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/1kid9zr1--h6y.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/23vtcpdpp2h9h.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"inter_5972bc34-module__OU16Qa__className","children":["$","$L2",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L6",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]}]}]]}],{"children":[["$","$1","c",{"children":[[["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/1ioy8obpggx93.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/1zr7rrk4wkmju.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/1l2mgm5v3tjci.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/0zduf1gntl_f8.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/1a0bgy7kzrj91.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","async":true,"nonce":"$undefined"}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","async":true,"nonce":"$undefined"}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","async":true,"nonce":"$undefined"}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","async":true,"nonce":"$undefined"}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","async":true,"nonce":"$undefined"}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/2n26sdz53rm0a.js","async":true,"nonce":"$undefined"}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/11khk745tfruy.js","async":true,"nonce":"$undefined"}]],["$","$L7",null,{"Component":"$8","slots":{"children":["$","$L5",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":"$L9","templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$La","forbidden":"$undefined","unauthorized":"$undefined"}]},"serverProvidedParams":{"params":{},"promises":["$@b"]}}]]}],{"children":["$Lc",{},null,false,null]},null,false,null]},null,false,null],"$Ld",false]],"m":"$undefined","G":["$e",["$Lf","$L10"]],"S":true,"h":null,"s":"$undefined","l":"$undefined","p":"$undefined","d":"$undefined","b":"0ljiPmkOdq7_yE4sZoXlJ"} -11:I[347257,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ClientPageRoot"] -12:I[871135,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","/litellm-asset-prefix/_next/static/chunks/1ioy8obpggx93.js","/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","/litellm-asset-prefix/_next/static/chunks/1zr7rrk4wkmju.js","/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","/litellm-asset-prefix/_next/static/chunks/1l2mgm5v3tjci.js","/litellm-asset-prefix/_next/static/chunks/0zduf1gntl_f8.js","/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","/litellm-asset-prefix/_next/static/chunks/1a0bgy7kzrj91.js","/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","/litellm-asset-prefix/_next/static/chunks/2n26sdz53rm0a.js","/litellm-asset-prefix/_next/static/chunks/11khk745tfruy.js","/litellm-asset-prefix/_next/static/chunks/00g6xfr4yow7h.js","/litellm-asset-prefix/_next/static/chunks/0-k_4_s7m108w.js","/litellm-asset-prefix/_next/static/chunks/2kcxwg1mpncp6.js","/litellm-asset-prefix/_next/static/chunks/3drq2_k-jeio2.js","/litellm-asset-prefix/_next/static/chunks/395_vbpmrlvpu.js","/litellm-asset-prefix/_next/static/chunks/2hu1vyy-5pv13.js","/litellm-asset-prefix/_next/static/chunks/2l25bmiiw9ixp.js","/litellm-asset-prefix/_next/static/chunks/1uz3jt-tj9lkf.js","/litellm-asset-prefix/_next/static/chunks/199uwr871eene.js","/litellm-asset-prefix/_next/static/chunks/2s_ce-opzrkzr.js","/litellm-asset-prefix/_next/static/chunks/2ptdxz8qnchh_.js","/litellm-asset-prefix/_next/static/chunks/17nqbxvhztf3k.js","/litellm-asset-prefix/_next/static/chunks/2c90xukbd3il6.js","/litellm-asset-prefix/_next/static/chunks/0kap_rdm2-lem.js","/litellm-asset-prefix/_next/static/chunks/09l_m9l1emin2.js","/litellm-asset-prefix/_next/static/chunks/1cea03gg5a_c7.js","/litellm-asset-prefix/_next/static/chunks/323l6h8s7ahat.js","/litellm-asset-prefix/_next/static/chunks/112n0hv3cc2rg.js","/litellm-asset-prefix/_next/static/chunks/3y674jhwchpcq.js","/litellm-asset-prefix/_next/static/chunks/105643dvf00hu.js","/litellm-asset-prefix/_next/static/chunks/12wsfsljxg4xv.js","/litellm-asset-prefix/_next/static/chunks/22iools_e0k44.js","/litellm-asset-prefix/_next/static/chunks/3f9uewf5w-e-p.js","/litellm-asset-prefix/_next/static/chunks/23-g73xaw3kap.js"],"default"] -15:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"OutletBoundary"] +0:{"P":null,"c":["",""],"q":"","i":false,"f":[[["",{"children":["(dashboard)",{"children":["__PAGE__",{}]}]},"$undefined","$undefined",16],[["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/1kid9zr1--h6y.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/3254j4ut19q6_.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","async":true,"nonce":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"inter_5972bc34-module__OU16Qa__className","children":["$","$L2",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L6",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]],"forbidden":"$undefined","unauthorized":"$undefined"}]}]}]}]}]}]]}],{"children":[["$","$1","c",{"children":[[["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/1ioy8obpggx93.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/1zr7rrk4wkmju.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/0fk0i3e2aixp7.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/0zduf1gntl_f8.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","async":true,"nonce":"$undefined"}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","async":true,"nonce":"$undefined"}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","async":true,"nonce":"$undefined"}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/1a0bgy7kzrj91.js","async":true,"nonce":"$undefined"}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","async":true,"nonce":"$undefined"}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/0g8wwba6umbim.js","async":true,"nonce":"$undefined"}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/1di-caw05k3tq.js","async":true,"nonce":"$undefined"}]],["$","$L7",null,{"Component":"$8","slots":{"children":["$","$L5",null,{"parallelRouterKey":"children","error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":"$L9","templateStyles":"$undefined","templateScripts":"$undefined","notFound":"$La","forbidden":"$undefined","unauthorized":"$undefined"}]},"serverProvidedParams":{"params":{},"promises":["$@b"]}}]]}],{"children":["$Lc",{},null,false,null]},null,false,null]},null,false,null],"$Ld",false]],"m":"$undefined","G":["$e",["$Lf","$L10"]],"S":true,"h":null,"s":"$undefined","l":"$undefined","p":"$undefined","d":"$undefined","b":"qXutWsQW5C1Pf62WxTkEI"} +11:I[347257,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ClientPageRoot"] +12:I[871135,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","/litellm-asset-prefix/_next/static/chunks/1ioy8obpggx93.js","/litellm-asset-prefix/_next/static/chunks/3_3dj4vdy-3xy.js","/litellm-asset-prefix/_next/static/chunks/1zr7rrk4wkmju.js","/litellm-asset-prefix/_next/static/chunks/2cngn5bal3278.js","/litellm-asset-prefix/_next/static/chunks/2up3bks93iqds.js","/litellm-asset-prefix/_next/static/chunks/0fk0i3e2aixp7.js","/litellm-asset-prefix/_next/static/chunks/0zduf1gntl_f8.js","/litellm-asset-prefix/_next/static/chunks/0dbvgsc7ha049.js","/litellm-asset-prefix/_next/static/chunks/0g_w4tf2inv3i.js","/litellm-asset-prefix/_next/static/chunks/1vquuz09jxl5_.js","/litellm-asset-prefix/_next/static/chunks/17-6zku8f68gf.js","/litellm-asset-prefix/_next/static/chunks/1iakmimqrlpn0.js","/litellm-asset-prefix/_next/static/chunks/1a0bgy7kzrj91.js","/litellm-asset-prefix/_next/static/chunks/0f5fel02jwglw.js","/litellm-asset-prefix/_next/static/chunks/0g8wwba6umbim.js","/litellm-asset-prefix/_next/static/chunks/1di-caw05k3tq.js","/litellm-asset-prefix/_next/static/chunks/00g6xfr4yow7h.js","/litellm-asset-prefix/_next/static/chunks/2kcxwg1mpncp6.js","/litellm-asset-prefix/_next/static/chunks/1fmx49l6q8v39.js","/litellm-asset-prefix/_next/static/chunks/0ww76lz_0cphv.js","/litellm-asset-prefix/_next/static/chunks/2uc2pi4ob086w.js","/litellm-asset-prefix/_next/static/chunks/2hu1vyy-5pv13.js","/litellm-asset-prefix/_next/static/chunks/2l25bmiiw9ixp.js","/litellm-asset-prefix/_next/static/chunks/1uz3jt-tj9lkf.js","/litellm-asset-prefix/_next/static/chunks/3drq2_k-jeio2.js","/litellm-asset-prefix/_next/static/chunks/0dsiq_ok1yngk.js","/litellm-asset-prefix/_next/static/chunks/3srzg1la93pwv.js","/litellm-asset-prefix/_next/static/chunks/17nqbxvhztf3k.js","/litellm-asset-prefix/_next/static/chunks/2c90xukbd3il6.js","/litellm-asset-prefix/_next/static/chunks/0kap_rdm2-lem.js","/litellm-asset-prefix/_next/static/chunks/09l_m9l1emin2.js","/litellm-asset-prefix/_next/static/chunks/1cea03gg5a_c7.js","/litellm-asset-prefix/_next/static/chunks/323l6h8s7ahat.js","/litellm-asset-prefix/_next/static/chunks/112n0hv3cc2rg.js","/litellm-asset-prefix/_next/static/chunks/199uwr871eene.js","/litellm-asset-prefix/_next/static/chunks/105643dvf00hu.js","/litellm-asset-prefix/_next/static/chunks/12wsfsljxg4xv.js","/litellm-asset-prefix/_next/static/chunks/22iools_e0k44.js","/litellm-asset-prefix/_next/static/chunks/0am68mi9t9cb6.js","/litellm-asset-prefix/_next/static/chunks/23-g73xaw3kap.js"],"default"] +15:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"OutletBoundary"] 16:"$Sreact.suspense" -18:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ViewportBoundary"] -1a:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"MetadataBoundary"] +18:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ViewportBoundary"] +1a:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"MetadataBoundary"] 9:["$","$L6",null,{}] a:[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:style","children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:1:props:style","children":404}],["$","div",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:style","children":["$","h2",null,{"style":"$0:f:0:1:0:props:children:1:props:children:props:children:props:children:props:children:props:children:props:notFound:0:1:props:children:props:children:2:props:children:props:style","children":"This page could not be found."}]}]]}]}]],[]] -c:["$","$1","c",{"children":[["$","$L11",null,{"Component":"$12","serverProvidedParams":{"searchParams":{},"params":"$0:f:0:1:1:children:0:props:children:1:props:serverProvidedParams:params","promises":["$@13","$@14"]}}],[["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/00g6xfr4yow7h.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/0-k_4_s7m108w.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/2kcxwg1mpncp6.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/3drq2_k-jeio2.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/395_vbpmrlvpu.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/2hu1vyy-5pv13.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/2l25bmiiw9ixp.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/1uz3jt-tj9lkf.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/199uwr871eene.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/2s_ce-opzrkzr.js","async":true,"nonce":"$undefined"}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/2ptdxz8qnchh_.js","async":true,"nonce":"$undefined"}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/17nqbxvhztf3k.js","async":true,"nonce":"$undefined"}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/2c90xukbd3il6.js","async":true,"nonce":"$undefined"}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/0kap_rdm2-lem.js","async":true,"nonce":"$undefined"}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/09l_m9l1emin2.js","async":true,"nonce":"$undefined"}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/1cea03gg5a_c7.js","async":true,"nonce":"$undefined"}],["$","script","script-16",{"src":"/litellm-asset-prefix/_next/static/chunks/323l6h8s7ahat.js","async":true,"nonce":"$undefined"}],["$","script","script-17",{"src":"/litellm-asset-prefix/_next/static/chunks/112n0hv3cc2rg.js","async":true,"nonce":"$undefined"}],["$","script","script-18",{"src":"/litellm-asset-prefix/_next/static/chunks/3y674jhwchpcq.js","async":true,"nonce":"$undefined"}],["$","script","script-19",{"src":"/litellm-asset-prefix/_next/static/chunks/105643dvf00hu.js","async":true,"nonce":"$undefined"}],["$","script","script-20",{"src":"/litellm-asset-prefix/_next/static/chunks/12wsfsljxg4xv.js","async":true,"nonce":"$undefined"}],["$","script","script-21",{"src":"/litellm-asset-prefix/_next/static/chunks/22iools_e0k44.js","async":true,"nonce":"$undefined"}],["$","script","script-22",{"src":"/litellm-asset-prefix/_next/static/chunks/3f9uewf5w-e-p.js","async":true,"nonce":"$undefined"}],["$","script","script-23",{"src":"/litellm-asset-prefix/_next/static/chunks/23-g73xaw3kap.js","async":true,"nonce":"$undefined"}]],["$","$L15",null,{"children":["$","$16",null,{"name":"Next.MetadataOutlet","children":"$@17"}]}]]}] +c:["$","$1","c",{"children":[["$","$L11",null,{"Component":"$12","serverProvidedParams":{"searchParams":{},"params":"$0:f:0:1:1:children:0:props:children:1:props:serverProvidedParams:params","promises":["$@13","$@14"]}}],[["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/00g6xfr4yow7h.js","async":true,"nonce":"$undefined"}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/2kcxwg1mpncp6.js","async":true,"nonce":"$undefined"}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/1fmx49l6q8v39.js","async":true,"nonce":"$undefined"}],["$","script","script-3",{"src":"/litellm-asset-prefix/_next/static/chunks/0ww76lz_0cphv.js","async":true,"nonce":"$undefined"}],["$","script","script-4",{"src":"/litellm-asset-prefix/_next/static/chunks/2uc2pi4ob086w.js","async":true,"nonce":"$undefined"}],["$","script","script-5",{"src":"/litellm-asset-prefix/_next/static/chunks/2hu1vyy-5pv13.js","async":true,"nonce":"$undefined"}],["$","script","script-6",{"src":"/litellm-asset-prefix/_next/static/chunks/2l25bmiiw9ixp.js","async":true,"nonce":"$undefined"}],["$","script","script-7",{"src":"/litellm-asset-prefix/_next/static/chunks/1uz3jt-tj9lkf.js","async":true,"nonce":"$undefined"}],["$","script","script-8",{"src":"/litellm-asset-prefix/_next/static/chunks/3drq2_k-jeio2.js","async":true,"nonce":"$undefined"}],["$","script","script-9",{"src":"/litellm-asset-prefix/_next/static/chunks/0dsiq_ok1yngk.js","async":true,"nonce":"$undefined"}],["$","script","script-10",{"src":"/litellm-asset-prefix/_next/static/chunks/3srzg1la93pwv.js","async":true,"nonce":"$undefined"}],["$","script","script-11",{"src":"/litellm-asset-prefix/_next/static/chunks/17nqbxvhztf3k.js","async":true,"nonce":"$undefined"}],["$","script","script-12",{"src":"/litellm-asset-prefix/_next/static/chunks/2c90xukbd3il6.js","async":true,"nonce":"$undefined"}],["$","script","script-13",{"src":"/litellm-asset-prefix/_next/static/chunks/0kap_rdm2-lem.js","async":true,"nonce":"$undefined"}],["$","script","script-14",{"src":"/litellm-asset-prefix/_next/static/chunks/09l_m9l1emin2.js","async":true,"nonce":"$undefined"}],["$","script","script-15",{"src":"/litellm-asset-prefix/_next/static/chunks/1cea03gg5a_c7.js","async":true,"nonce":"$undefined"}],["$","script","script-16",{"src":"/litellm-asset-prefix/_next/static/chunks/323l6h8s7ahat.js","async":true,"nonce":"$undefined"}],["$","script","script-17",{"src":"/litellm-asset-prefix/_next/static/chunks/112n0hv3cc2rg.js","async":true,"nonce":"$undefined"}],["$","script","script-18",{"src":"/litellm-asset-prefix/_next/static/chunks/199uwr871eene.js","async":true,"nonce":"$undefined"}],["$","script","script-19",{"src":"/litellm-asset-prefix/_next/static/chunks/105643dvf00hu.js","async":true,"nonce":"$undefined"}],["$","script","script-20",{"src":"/litellm-asset-prefix/_next/static/chunks/12wsfsljxg4xv.js","async":true,"nonce":"$undefined"}],["$","script","script-21",{"src":"/litellm-asset-prefix/_next/static/chunks/22iools_e0k44.js","async":true,"nonce":"$undefined"}],["$","script","script-22",{"src":"/litellm-asset-prefix/_next/static/chunks/0am68mi9t9cb6.js","async":true,"nonce":"$undefined"}],["$","script","script-23",{"src":"/litellm-asset-prefix/_next/static/chunks/23-g73xaw3kap.js","async":true,"nonce":"$undefined"}]],["$","$L15",null,{"children":["$","$16",null,{"name":"Next.MetadataOutlet","children":"$@17"}]}]]}] d:["$","$1","h",{"children":[null,["$","$L18",null,{"children":"$L19"}],["$","div",null,{"hidden":true,"children":["$","$L1a",null,{"children":["$","$16",null,{"name":"Next.Metadata","children":"$L1b"}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}] f:["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/1kid9zr1--h6y.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}] -10:["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/23vtcpdpp2h9h.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}] +10:["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/3254j4ut19q6_.css","precedence":"next","crossOrigin":"$undefined","nonce":"$undefined"}] b:"$0:f:0:1:1:children:0:props:children:1:props:serverProvidedParams:params" 13:{} 14:"$0:f:0:1:1:children:0:props:children:1:props:serverProvidedParams:params" 19:[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]] -1c:I[27201,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"IconMark"] +1c:I[27201,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"IconMark"] 17:null 1b:[["$","title","0",{"children":"LiteLLM Dashboard"}],["$","meta","1",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.3arlap5n8tyzg.ico","sizes":"48x48","type":"image/x-icon"}],["$","link","3",{"rel":"icon","href":"/get_favicon"}],["$","$L1c","4",{}]] diff --git a/litellm/proxy/_experimental/out/__next._head.txt b/litellm/proxy/_experimental/out/__next._head.txt index 8e68b3a038e..9b12cf54d0c 100644 --- a/litellm/proxy/_experimental/out/__next._head.txt +++ b/litellm/proxy/_experimental/out/__next._head.txt @@ -1,6 +1,6 @@ 1:"$Sreact.fragment" -2:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ViewportBoundary"] -3:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"MetadataBoundary"] +2:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"ViewportBoundary"] +3:I[897367,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"MetadataBoundary"] 4:"$Sreact.suspense" -5:I[27201,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"IconMark"] -0:{"rsc":["$","$1","h",{"children":[null,["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"LiteLLM Dashboard"}],["$","meta","1",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.3arlap5n8tyzg.ico","sizes":"48x48","type":"image/x-icon"}],["$","link","3",{"rel":"icon","href":"/get_favicon"}],["$","$L5","4",{}]]}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"0ljiPmkOdq7_yE4sZoXlJ"} +5:I[27201,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"IconMark"] +0:{"rsc":["$","$1","h",{"children":[null,["$","$L2",null,{"children":[["$","meta","0",{"charSet":"utf-8"}],["$","meta","1",{"name":"viewport","content":"width=device-width, initial-scale=1"}]]}],["$","div",null,{"hidden":true,"children":["$","$L3",null,{"children":["$","$4",null,{"name":"Next.Metadata","children":[["$","title","0",{"children":"LiteLLM Dashboard"}],["$","meta","1",{"name":"description","content":"LiteLLM Proxy Admin UI"}],["$","link","2",{"rel":"icon","href":"/favicon.ico?favicon.3arlap5n8tyzg.ico","sizes":"48x48","type":"image/x-icon"}],["$","link","3",{"rel":"icon","href":"/get_favicon"}],["$","$L5","4",{}]]}]}]}],["$","meta",null,{"name":"next-size-adjust","content":""}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"qXutWsQW5C1Pf62WxTkEI"} diff --git a/litellm/proxy/_experimental/out/__next._index.txt b/litellm/proxy/_experimental/out/__next._index.txt index f5dd3d69ad7..8649901b01b 100644 --- a/litellm/proxy/_experimental/out/__next._index.txt +++ b/litellm/proxy/_experimental/out/__next._index.txt @@ -1,9 +1,9 @@ 1:"$Sreact.fragment" -2:I[867271,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] -3:I[71195,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] -4:I[557951,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"AuthProvider"] -5:I[339756,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] -6:I[837457,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] +2:I[867271,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] +3:I[71195,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] +4:I[557951,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"AuthProvider"] +5:I[339756,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] +6:I[837457,["/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js"],"default"] :HL["/litellm-asset-prefix/_next/static/chunks/1kid9zr1--h6y.css","style"] -:HL["/litellm-asset-prefix/_next/static/chunks/23vtcpdpp2h9h.css","style"] -0:{"rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/1kid9zr1--h6y.css","precedence":"next"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/23vtcpdpp2h9h.css","precedence":"next"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/0map77ee0fk0e.js","async":true}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","async":true}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"inter_5972bc34-module__OU16Qa__className","children":["$","$L2",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","template":["$","$L6",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]}]}]}]}]}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"0ljiPmkOdq7_yE4sZoXlJ"} +:HL["/litellm-asset-prefix/_next/static/chunks/3254j4ut19q6_.css","style"] +0:{"rsc":["$","$1","c",{"children":[[["$","link","0",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/1kid9zr1--h6y.css","precedence":"next"}],["$","link","1",{"rel":"stylesheet","href":"/litellm-asset-prefix/_next/static/chunks/3254j4ut19q6_.css","precedence":"next"}],["$","script","script-0",{"src":"/litellm-asset-prefix/_next/static/chunks/0vggytdohwe7o.js","async":true}],["$","script","script-1",{"src":"/litellm-asset-prefix/_next/static/chunks/3c02m_kr-u94p.js","async":true}],["$","script","script-2",{"src":"/litellm-asset-prefix/_next/static/chunks/1jfookxfajkeo.js","async":true}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"inter_5972bc34-module__OU16Qa__className","children":["$","$L2",null,{"children":["$","$L3",null,{"children":["$","$L4",null,{"children":["$","$L5",null,{"parallelRouterKey":"children","template":["$","$L6",null,{}],"notFound":[[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":404}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],[]]}]}]}]}]}]}]]}],"isPartial":false,"staleTime":300,"varyParams":null,"buildId":"qXutWsQW5C1Pf62WxTkEI"} diff --git a/litellm/proxy/_experimental/out/__next._tree.txt b/litellm/proxy/_experimental/out/__next._tree.txt index 6bec08d009f..db0015f1f41 100644 --- a/litellm/proxy/_experimental/out/__next._tree.txt +++ b/litellm/proxy/_experimental/out/__next._tree.txt @@ -1,4 +1,4 @@ :HL["/litellm-asset-prefix/_next/static/chunks/1kid9zr1--h6y.css","style"] -:HL["/litellm-asset-prefix/_next/static/chunks/23vtcpdpp2h9h.css","style"] +:HL["/litellm-asset-prefix/_next/static/chunks/3254j4ut19q6_.css","style"] :HL["/litellm-asset-prefix/_next/static/media/83afe278b6a6bb3c-s.p.2bn3s6zvc0dyp.woff2","font",{"crossOrigin":"","type":"font/woff2"}] -0:{"tree":{"name":"","param":null,"prefetchHints":16,"slots":{"children":{"name":"(dashboard)","param":null,"prefetchHints":0,"slots":{"children":{"name":"__PAGE__","param":null,"prefetchHints":0,"slots":null}}}}},"staleTime":300,"buildId":"0ljiPmkOdq7_yE4sZoXlJ"} +0:{"tree":{"name":"","param":null,"prefetchHints":16,"slots":{"children":{"name":"(dashboard)","param":null,"prefetchHints":0,"slots":{"children":{"name":"__PAGE__","param":null,"prefetchHints":0,"slots":null}}}}},"staleTime":300,"buildId":"qXutWsQW5C1Pf62WxTkEI"} diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/0-k_4_s7m108w.js b/litellm/proxy/_experimental/out/_next/static/chunks/0-k_4_s7m108w.js deleted file mode 100644 index 0729d64a1ba..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/0-k_4_s7m108w.js +++ /dev/null @@ -1,7 +0,0 @@ -(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,91874,681216,e=>{"use strict";var t=e.i(931067),r=e.i(209428),n=e.i(211577),o=e.i(392221),i=e.i(703923),l=e.i(343794),a=e.i(914949),s=e.i(271645),c=["prefixCls","className","style","checked","disabled","defaultChecked","type","title","onChange"],u=(0,s.forwardRef)(function(e,u){var d=e.prefixCls,p=void 0===d?"rc-checkbox":d,f=e.className,g=e.style,m=e.checked,b=e.disabled,h=e.defaultChecked,v=e.type,y=void 0===v?"checkbox":v,$=e.title,C=e.onChange,k=(0,i.default)(e,c),x=(0,s.useRef)(null),S=(0,s.useRef)(null),O=(0,a.default)(void 0!==h&&h,{value:m}),w=(0,o.default)(O,2),E=w[0],j=w[1];(0,s.useImperativeHandle)(u,function(){return{focus:function(e){var t;null==(t=x.current)||t.focus(e)},blur:function(){var e;null==(e=x.current)||e.blur()},input:x.current,nativeElement:S.current}});var N=(0,l.default)(p,f,(0,n.default)((0,n.default)({},"".concat(p,"-checked"),E),"".concat(p,"-disabled"),b));return s.createElement("span",{className:N,title:$,style:g,ref:S},s.createElement("input",(0,t.default)({},k,{className:"".concat(p,"-input"),ref:x,onChange:function(t){b||("checked"in e||j(t.target.checked),null==C||C({target:(0,r.default)((0,r.default)({},e),{},{type:y,checked:t.target.checked}),stopPropagation:function(){t.stopPropagation()},preventDefault:function(){t.preventDefault()},nativeEvent:t.nativeEvent}))},disabled:b,checked:!!E,type:y})),s.createElement("span",{className:"".concat(p,"-inner")}))});e.s(["default",0,u],91874);var d=e.i(963188);e.s(["default",0,function(e){let t=s.default.useRef(null),r=()=>{d.default.cancel(t.current),t.current=null};return[()=>{r(),t.current=(0,d.default)(()=>{t.current=null})},n=>{t.current&&(n.stopPropagation(),r()),null==e||e(n)}]}],681216)},374276,236836,e=>{"use strict";e.i(247167);var t=e.i(271645),r=e.i(343794),n=e.i(91874),o=e.i(611935),i=e.i(121872),l=e.i(26905),a=e.i(242064),s=e.i(937328),c=e.i(321883),u=e.i(62139);let d=t.default.createContext(null);e.i(296059);var p=e.i(915654),f=e.i(183293),g=e.i(246422),m=e.i(838378);function b(e,t){return(e=>{let{checkboxCls:t}=e,r=`${t}-wrapper`;return[{[`${t}-group`]:Object.assign(Object.assign({},(0,f.resetComponent)(e)),{display:"inline-flex",flexWrap:"wrap",columnGap:e.marginXS,[`> ${e.antCls}-row`]:{flex:1}}),[r]:Object.assign(Object.assign({},(0,f.resetComponent)(e)),{display:"inline-flex",alignItems:"baseline",cursor:"pointer","&:after":{display:"inline-block",width:0,overflow:"hidden",content:"'\\a0'"},[`& + ${r}`]:{marginInlineStart:0},[`&${r}-in-form-item`]:{'input[type="checkbox"]':{width:14,height:14}}}),[t]:Object.assign(Object.assign({},(0,f.resetComponent)(e)),{position:"relative",whiteSpace:"nowrap",lineHeight:1,cursor:"pointer",borderRadius:e.borderRadiusSM,alignSelf:"center",[`${t}-input`]:{position:"absolute",inset:0,zIndex:1,cursor:"pointer",opacity:0,margin:0,[`&:focus-visible + ${t}-inner`]:(0,f.genFocusOutline)(e)},[`${t}-inner`]:{boxSizing:"border-box",display:"block",width:e.checkboxSize,height:e.checkboxSize,direction:"ltr",backgroundColor:e.colorBgContainer,border:`${(0,p.unit)(e.lineWidth)} ${e.lineType} ${e.colorBorder}`,borderRadius:e.borderRadiusSM,borderCollapse:"separate",transition:`all ${e.motionDurationSlow}`,"&:after":{boxSizing:"border-box",position:"absolute",top:"50%",insetInlineStart:"25%",display:"table",width:e.calc(e.checkboxSize).div(14).mul(5).equal(),height:e.calc(e.checkboxSize).div(14).mul(8).equal(),border:`${(0,p.unit)(e.lineWidthBold)} solid ${e.colorWhite}`,borderTop:0,borderInlineStart:0,transform:"rotate(45deg) scale(0) translate(-50%,-50%)",opacity:0,content:'""',transition:`all ${e.motionDurationFast} ${e.motionEaseInBack}, opacity ${e.motionDurationFast}`}},"& + span":{paddingInlineStart:e.paddingXS,paddingInlineEnd:e.paddingXS}})},{[` - ${r}:not(${r}-disabled), - ${t}:not(${t}-disabled) - `]:{[`&:hover ${t}-inner`]:{borderColor:e.colorPrimary}},[`${r}:not(${r}-disabled)`]:{[`&:hover ${t}-checked:not(${t}-disabled) ${t}-inner`]:{backgroundColor:e.colorPrimaryHover,borderColor:"transparent"},[`&:hover ${t}-checked:not(${t}-disabled):after`]:{borderColor:e.colorPrimaryHover}}},{[`${t}-checked`]:{[`${t}-inner`]:{backgroundColor:e.colorPrimary,borderColor:e.colorPrimary,"&:after":{opacity:1,transform:"rotate(45deg) scale(1) translate(-50%,-50%)",transition:`all ${e.motionDurationMid} ${e.motionEaseOutBack} ${e.motionDurationFast}`}}},[` - ${r}-checked:not(${r}-disabled), - ${t}-checked:not(${t}-disabled) - `]:{[`&:hover ${t}-inner`]:{backgroundColor:e.colorPrimaryHover,borderColor:"transparent"}}},{[t]:{"&-indeterminate":{"&":{[`${t}-inner`]:{backgroundColor:`${e.colorBgContainer}`,borderColor:`${e.colorBorder}`,"&:after":{top:"50%",insetInlineStart:"50%",width:e.calc(e.fontSizeLG).div(2).equal(),height:e.calc(e.fontSizeLG).div(2).equal(),backgroundColor:e.colorPrimary,border:0,transform:"translate(-50%, -50%) scale(1)",opacity:1,content:'""'}},[`&:hover ${t}-inner`]:{backgroundColor:`${e.colorBgContainer}`,borderColor:`${e.colorPrimary}`}}}}},{[`${r}-disabled`]:{cursor:"not-allowed"},[`${t}-disabled`]:{[`&, ${t}-input`]:{cursor:"not-allowed",pointerEvents:"none"},[`${t}-inner`]:{background:e.colorBgContainerDisabled,borderColor:e.colorBorder,"&:after":{borderColor:e.colorTextDisabled}},"&:after":{display:"none"},"& + span":{color:e.colorTextDisabled},[`&${t}-indeterminate ${t}-inner::after`]:{background:e.colorTextDisabled}}}]})((0,m.mergeToken)(t,{checkboxCls:`.${e}`,checkboxSize:t.controlInteractiveSize}))}let h=(0,g.genStyleHooks)("Checkbox",(e,{prefixCls:t})=>[b(t,e)]);e.s(["default",0,h,"getStyle",0,b],236836);var v=e.i(681216),y=function(e,t){var r={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&0>t.indexOf(n)&&(r[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,n=Object.getOwnPropertySymbols(e);ot.indexOf(n[o])&&Object.prototype.propertyIsEnumerable.call(e,n[o])&&(r[n[o]]=e[n[o]]);return r};let $=t.forwardRef((e,p)=>{var f;let{prefixCls:g,className:m,rootClassName:b,children:$,indeterminate:C=!1,style:k,onMouseEnter:x,onMouseLeave:S,skipGroup:O=!1,disabled:w}=e,E=y(e,["prefixCls","className","rootClassName","children","indeterminate","style","onMouseEnter","onMouseLeave","skipGroup","disabled"]),{getPrefixCls:j,direction:N,checkbox:I}=t.useContext(a.ConfigContext),P=t.useContext(d),{isFormItemInput:D}=t.useContext(u.FormItemInputContext),R=t.useContext(s.default),z=null!=(f=(null==P?void 0:P.disabled)||w)?f:R,A=t.useRef(E.value),M=t.useRef(null),T=(0,o.composeRef)(p,M);t.useEffect(()=>{null==P||P.registerValue(E.value)},[]),t.useEffect(()=>{if(!O)return E.value!==A.current&&(null==P||P.cancelValue(A.current),null==P||P.registerValue(E.value),A.current=E.value),()=>null==P?void 0:P.cancelValue(E.value)},[E.value]),t.useEffect(()=>{var e;(null==(e=M.current)?void 0:e.input)&&(M.current.input.indeterminate=C)},[C]);let W=j("checkbox",g),B=(0,c.default)(W),[F,X,L]=h(W,B),H=Object.assign({},E);P&&!O&&(H.onChange=(...e)=>{E.onChange&&E.onChange.apply(E,e),P.toggleOption&&P.toggleOption({label:$,value:E.value})},H.name=P.name,H.checked=P.value.includes(E.value));let _=(0,r.default)(`${W}-wrapper`,{[`${W}-rtl`]:"rtl"===N,[`${W}-wrapper-checked`]:H.checked,[`${W}-wrapper-disabled`]:z,[`${W}-wrapper-in-form-item`]:D},null==I?void 0:I.className,m,b,L,B,X),q=(0,r.default)({[`${W}-indeterminate`]:C},l.TARGET_CLS,X),[G,V]=(0,v.default)(H.onClick);return F(t.createElement(i.default,{component:"Checkbox",disabled:z},t.createElement("label",{className:_,style:Object.assign(Object.assign({},null==I?void 0:I.style),k),onMouseEnter:x,onMouseLeave:S,onClick:G},t.createElement(n.default,Object.assign({},H,{onClick:V,prefixCls:W,className:q,disabled:z,ref:T})),null!=$&&t.createElement("span",{className:`${W}-label`},$))))});var C=e.i(8211),k=e.i(529681),x=function(e,t){var r={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&0>t.indexOf(n)&&(r[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,n=Object.getOwnPropertySymbols(e);ot.indexOf(n[o])&&Object.prototype.propertyIsEnumerable.call(e,n[o])&&(r[n[o]]=e[n[o]]);return r};let S=t.forwardRef((e,n)=>{let{defaultValue:o,children:i,options:l=[],prefixCls:s,className:u,rootClassName:p,style:f,onChange:g}=e,m=x(e,["defaultValue","children","options","prefixCls","className","rootClassName","style","onChange"]),{getPrefixCls:b,direction:v}=t.useContext(a.ConfigContext),[y,S]=t.useState(m.value||o||[]),[O,w]=t.useState([]);t.useEffect(()=>{"value"in m&&S(m.value||[])},[m.value]);let E=t.useMemo(()=>l.map(e=>"string"==typeof e||"number"==typeof e?{label:e,value:e}:e),[l]),j=e=>{w(t=>t.filter(t=>t!==e))},N=e=>{w(t=>[].concat((0,C.default)(t),[e]))},I=e=>{let t=y.indexOf(e.value),r=(0,C.default)(y);-1===t?r.push(e.value):r.splice(t,1),"value"in m||S(r),null==g||g(r.filter(e=>O.includes(e)).sort((e,t)=>E.findIndex(t=>t.value===e)-E.findIndex(e=>e.value===t)))},P=b("checkbox",s),D=`${P}-group`,R=(0,c.default)(P),[z,A,M]=h(P,R),T=(0,k.default)(m,["value","disabled"]),W=l.length?E.map(e=>t.createElement($,{prefixCls:P,key:e.value.toString(),disabled:"disabled"in e?e.disabled:m.disabled,value:e.value,checked:y.includes(e.value),onChange:e.onChange,className:(0,r.default)(`${D}-item`,e.className),style:e.style,title:e.title,id:e.id,required:e.required},e.label)):i,B=t.useMemo(()=>({toggleOption:I,value:y,disabled:m.disabled,name:m.name,registerValue:N,cancelValue:j}),[I,y,m.disabled,m.name,N,j]),F=(0,r.default)(D,{[`${D}-rtl`]:"rtl"===v},u,p,M,R,A);return z(t.createElement("div",Object.assign({className:F,style:f},T,{ref:n}),t.createElement(d.Provider,{value:B},W)))});$.Group=S,$.__ANT_CHECKBOX=!0,e.s(["default",0,$],374276)},536916,e=>{"use strict";var t=e.i(374276);e.s(["Checkbox",()=>t.default])},309821,e=>{"use strict";e.i(247167);var t=e.i(271645),r=e.i(135551),n=e.i(201072),o=e.i(121229),i=e.i(726289),l=e.i(864517),a=e.i(343794),s=e.i(529681),c=e.i(242064),u=e.i(931067),d=e.i(209428),p=e.i(703923),f={percent:0,prefixCls:"rc-progress",strokeColor:"#2db7f5",strokeLinecap:"round",strokeWidth:1,trailColor:"#D9D9D9",trailWidth:1,gapPosition:"bottom"},g=function(){var e=(0,t.useRef)([]),r=(0,t.useRef)(null);return(0,t.useEffect)(function(){var t=Date.now(),n=!1;e.current.forEach(function(e){if(e){n=!0;var o=e.style;o.transitionDuration=".3s, .3s, .3s, .06s",r.current&&t-r.current<100&&(o.transitionDuration="0s, 0s")}}),n&&(r.current=Date.now())}),e.current},m=e.i(410160),b=e.i(392221),h=e.i(654310),v=0,y=(0,h.default)();let $=function(e){var r=t.useState(),n=(0,b.default)(r,2),o=n[0],i=n[1];return t.useEffect(function(){var e;i("rc_progress_".concat((y?(e=v,v+=1):e="TEST_OR_SSR",e)))},[]),e||o};var C=function(e){var r=e.bg,n=e.children;return t.createElement("div",{style:{width:"100%",height:"100%",background:r}},n)};function k(e,t){return Object.keys(e).map(function(r){var n=parseFloat(r),o="".concat(Math.floor(n*t),"%");return"".concat(e[r]," ").concat(o)})}var x=t.forwardRef(function(e,r){var n=e.prefixCls,o=e.color,i=e.gradientId,l=e.radius,a=e.style,s=e.ptg,c=e.strokeLinecap,u=e.strokeWidth,d=e.size,p=e.gapDegree,f=o&&"object"===(0,m.default)(o),g=d/2,b=t.createElement("circle",{className:"".concat(n,"-circle-path"),r:l,cx:g,cy:g,stroke:f?"#FFF":void 0,strokeLinecap:c,strokeWidth:u,opacity:+(0!==s),style:a,ref:r});if(!f)return b;var h="".concat(i,"-conic"),v=k(o,(360-p)/360),y=k(o,1),$="conic-gradient(from ".concat(p?"".concat(180+p/2,"deg"):"0deg",", ").concat(v.join(", "),")"),x="linear-gradient(to ".concat(p?"bottom":"top",", ").concat(y.join(", "),")");return t.createElement(t.Fragment,null,t.createElement("mask",{id:h},b),t.createElement("foreignObject",{x:0,y:0,width:d,height:d,mask:"url(#".concat(h,")")},t.createElement(C,{bg:x},t.createElement(C,{bg:$}))))}),S=function(e,t,r,n,o,i,l,a,s,c){var u=arguments.length>10&&void 0!==arguments[10]?arguments[10]:0,d=(100-n)/100*t;return"round"===s&&100!==n&&(d+=c/2)>=t&&(d=t-.01),{stroke:"string"==typeof a?a:void 0,strokeDasharray:"".concat(t,"px ").concat(e),strokeDashoffset:d+u,transform:"rotate(".concat(o+r/100*360*((360-i)/360)+(0===i?0:({bottom:0,top:180,left:90,right:-90})[l]),"deg)"),transformOrigin:"".concat(50,"px ").concat(50,"px"),transition:"stroke-dashoffset .3s ease 0s, stroke-dasharray .3s ease 0s, stroke .3s, stroke-width .06s ease .3s, opacity .3s ease 0s",fillOpacity:0}},O=["id","prefixCls","steps","strokeWidth","trailWidth","gapDegree","gapPosition","trailColor","strokeLinecap","style","className","strokeColor","percent"];function w(e){var t=null!=e?e:[];return Array.isArray(t)?t:[t]}let E=function(e){var r,n,o,i,l=(0,d.default)((0,d.default)({},f),e),s=l.id,c=l.prefixCls,b=l.steps,h=l.strokeWidth,v=l.trailWidth,y=l.gapDegree,C=void 0===y?0:y,k=l.gapPosition,E=l.trailColor,j=l.strokeLinecap,N=l.style,I=l.className,P=l.strokeColor,D=l.percent,R=(0,p.default)(l,O),z=$(s),A="".concat(z,"-gradient"),M=50-h/2,T=2*Math.PI*M,W=C>0?90+C/2:-90,B=(360-C)/360*T,F="object"===(0,m.default)(b)?b:{count:b,gap:2},X=F.count,L=F.gap,H=w(D),_=w(P),q=_.find(function(e){return e&&"object"===(0,m.default)(e)}),G=q&&"object"===(0,m.default)(q)?"butt":j,V=S(T,B,0,100,W,C,k,E,G,h),K=g();return t.createElement("svg",(0,u.default)({className:(0,a.default)("".concat(c,"-circle"),I),viewBox:"0 0 ".concat(100," ").concat(100),style:N,id:s,role:"presentation"},R),!X&&t.createElement("circle",{className:"".concat(c,"-circle-trail"),r:M,cx:50,cy:50,stroke:E,strokeLinecap:G,strokeWidth:v||h,style:V}),X?(r=Math.round(X*(H[0]/100)),n=100/X,o=0,Array(X).fill(null).map(function(e,i){var l=i<=r-1?_[0]:E,a=l&&"object"===(0,m.default)(l)?"url(#".concat(A,")"):void 0,s=S(T,B,o,n,W,C,k,l,"butt",h,L);return o+=(B-s.strokeDashoffset+L)*100/B,t.createElement("circle",{key:i,className:"".concat(c,"-circle-path"),r:M,cx:50,cy:50,stroke:a,strokeWidth:h,opacity:1,style:s,ref:function(e){K[i]=e}})})):(i=0,H.map(function(e,r){var n=_[r]||_[_.length-1],o=S(T,B,i,e,W,C,k,n,G,h);return i+=e,t.createElement(x,{key:r,color:n,ptg:e,radius:M,prefixCls:c,gradientId:A,style:o,strokeLinecap:G,strokeWidth:h,gapDegree:C,ref:function(e){K[r]=e},size:100})}).reverse()))};var j=e.i(491816);e.i(765846);var N=e.i(896091);function I(e){return!e||e<0?0:e>100?100:e}function P({success:e,successPercent:t}){let r=t;return e&&"progress"in e&&(r=e.progress),e&&"percent"in e&&(r=e.percent),r}let D=(e,t,r)=>{var n,o,i,l;let a=-1,s=-1;if("step"===t){let t=r.steps,n=r.strokeWidth;"string"==typeof e||void 0===e?(a="small"===e?2:14,s=null!=n?n:8):"number"==typeof e?[a,s]=[e,e]:[a=14,s=8]=Array.isArray(e)?e:[e.width,e.height],a*=t}else if("line"===t){let t=null==r?void 0:r.strokeWidth;"string"==typeof e||void 0===e?s=t||("small"===e?6:8):"number"==typeof e?[a,s]=[e,e]:[a=-1,s=8]=Array.isArray(e)?e:[e.width,e.height]}else("circle"===t||"dashboard"===t)&&("string"==typeof e||void 0===e?[a,s]="small"===e?[60,60]:[120,120]:"number"==typeof e?[a,s]=[e,e]:Array.isArray(e)&&(a=null!=(o=null!=(n=e[0])?n:e[1])?o:120,s=null!=(l=null!=(i=e[0])?i:e[1])?l:120));return[a,s]},R=e=>{let{prefixCls:r,trailColor:n=null,strokeLinecap:o="round",gapPosition:i,gapDegree:l,width:s=120,type:c,children:u,success:d,size:p=s,steps:f}=e,[g,m]=D(p,"circle"),{strokeWidth:b}=e;void 0===b&&(b=Math.max(3/g*100,6));let h=t.useMemo(()=>l||0===l?l:"dashboard"===c?75:void 0,[l,c]),v=(({percent:e,success:t,successPercent:r})=>{let n=I(P({success:t,successPercent:r}));return[n,I(I(e)-n)]})(e),y="[object Object]"===Object.prototype.toString.call(e.strokeColor),$=(({success:e={},strokeColor:t})=>{let{strokeColor:r}=e;return[r||N.presetPrimaryColors.green,t||null]})({success:d,strokeColor:e.strokeColor}),C=(0,a.default)(`${r}-inner`,{[`${r}-circle-gradient`]:y}),k=t.createElement(E,{steps:f,percent:f?v[1]:v,strokeWidth:b,trailWidth:b,strokeColor:f?$[1]:$,strokeLinecap:o,trailColor:n,prefixCls:r,gapDegree:h,gapPosition:i||"dashboard"===c&&"bottom"||void 0}),x=g<=20,S=t.createElement("div",{className:C,style:{width:g,height:m,fontSize:.15*g+6}},k,!x&&u);return x?t.createElement(j.default,{title:u},S):S};e.i(296059);var z=e.i(694758),A=e.i(915654),M=e.i(183293),T=e.i(246422),W=e.i(838378);let B="--progress-line-stroke-color",F="--progress-percent",X=e=>{let t=e?"100%":"-100%";return new z.Keyframes(`antProgress${e?"RTL":"LTR"}Active`,{"0%":{transform:`translateX(${t}) scaleX(0)`,opacity:.1},"20%":{transform:`translateX(${t}) scaleX(0)`,opacity:.5},to:{transform:"translateX(0) scaleX(1)",opacity:0}})},L=(0,T.genStyleHooks)("Progress",e=>{let t=e.calc(e.marginXXS).div(2).equal(),r=(0,W.mergeToken)(e,{progressStepMarginInlineEnd:t,progressStepMinWidth:t,progressActiveMotionDuration:"2.4s"});return[(e=>{let{componentCls:t,iconCls:r}=e;return{[t]:Object.assign(Object.assign({},(0,M.resetComponent)(e)),{display:"inline-block","&-rtl":{direction:"rtl"},"&-line":{position:"relative",width:"100%",fontSize:e.fontSize},[`${t}-outer`]:{display:"inline-flex",alignItems:"center",width:"100%"},[`${t}-inner`]:{position:"relative",display:"inline-block",width:"100%",flex:1,overflow:"hidden",verticalAlign:"middle",backgroundColor:e.remainingColor,borderRadius:e.lineBorderRadius},[`${t}-inner:not(${t}-circle-gradient)`]:{[`${t}-circle-path`]:{stroke:e.defaultColor}},[`${t}-success-bg, ${t}-bg`]:{position:"relative",background:e.defaultColor,borderRadius:e.lineBorderRadius,transition:`all ${e.motionDurationSlow} ${e.motionEaseInOutCirc}`},[`${t}-layout-bottom`]:{display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",[`${t}-text`]:{width:"max-content",marginInlineStart:0,marginTop:e.marginXXS}},[`${t}-bg`]:{overflow:"hidden","&::after":{content:'""',background:{_multi_value_:!0,value:["inherit",`var(${B})`]},height:"100%",width:`calc(1 / var(${F}) * 100%)`,display:"block"},[`&${t}-bg-inner`]:{minWidth:"max-content","&::after":{content:"none"},[`${t}-text-inner`]:{color:e.colorWhite,[`&${t}-text-bright`]:{color:"rgba(0, 0, 0, 0.45)"}}}},[`${t}-success-bg`]:{position:"absolute",insetBlockStart:0,insetInlineStart:0,backgroundColor:e.colorSuccess},[`${t}-text`]:{display:"inline-block",marginInlineStart:e.marginXS,color:e.colorText,lineHeight:1,width:"2em",whiteSpace:"nowrap",textAlign:"start",verticalAlign:"middle",wordBreak:"normal",[r]:{fontSize:e.fontSize},[`&${t}-text-outer`]:{width:"max-content"},[`&${t}-text-outer${t}-text-start`]:{width:"max-content",marginInlineStart:0,marginInlineEnd:e.marginXS}},[`${t}-text-inner`]:{display:"flex",justifyContent:"center",alignItems:"center",width:"100%",height:"100%",marginInlineStart:0,padding:`0 ${(0,A.unit)(e.paddingXXS)}`,[`&${t}-text-start`]:{justifyContent:"start"},[`&${t}-text-end`]:{justifyContent:"end"}},[`&${t}-status-active`]:{[`${t}-bg::before`]:{position:"absolute",inset:0,backgroundColor:e.colorBgContainer,borderRadius:e.lineBorderRadius,opacity:0,animationName:X(),animationDuration:e.progressActiveMotionDuration,animationTimingFunction:e.motionEaseOutQuint,animationIterationCount:"infinite",content:'""'}},[`&${t}-rtl${t}-status-active`]:{[`${t}-bg::before`]:{animationName:X(!0)}},[`&${t}-status-exception`]:{[`${t}-bg`]:{backgroundColor:e.colorError},[`${t}-text`]:{color:e.colorError}},[`&${t}-status-exception ${t}-inner:not(${t}-circle-gradient)`]:{[`${t}-circle-path`]:{stroke:e.colorError}},[`&${t}-status-success`]:{[`${t}-bg`]:{backgroundColor:e.colorSuccess},[`${t}-text`]:{color:e.colorSuccess}},[`&${t}-status-success ${t}-inner:not(${t}-circle-gradient)`]:{[`${t}-circle-path`]:{stroke:e.colorSuccess}}})}})(r),(e=>{let{componentCls:t,iconCls:r}=e;return{[t]:{[`${t}-circle-trail`]:{stroke:e.remainingColor},[`&${t}-circle ${t}-inner`]:{position:"relative",lineHeight:1,backgroundColor:"transparent"},[`&${t}-circle ${t}-text`]:{position:"absolute",insetBlockStart:"50%",insetInlineStart:0,width:"100%",margin:0,padding:0,color:e.circleTextColor,fontSize:e.circleTextFontSize,lineHeight:1,whiteSpace:"normal",textAlign:"center",transform:"translateY(-50%)",[r]:{fontSize:e.circleIconFontSize}},[`${t}-circle&-status-exception`]:{[`${t}-text`]:{color:e.colorError}},[`${t}-circle&-status-success`]:{[`${t}-text`]:{color:e.colorSuccess}}},[`${t}-inline-circle`]:{lineHeight:1,[`${t}-inner`]:{verticalAlign:"bottom"}}}})(r),(e=>{let{componentCls:t}=e;return{[t]:{[`${t}-steps`]:{display:"inline-block","&-outer":{display:"flex",flexDirection:"row",alignItems:"center"},"&-item":{flexShrink:0,minWidth:e.progressStepMinWidth,marginInlineEnd:e.progressStepMarginInlineEnd,backgroundColor:e.remainingColor,transition:`all ${e.motionDurationSlow}`,"&-active":{backgroundColor:e.defaultColor}}}}}})(r),(e=>{let{componentCls:t,iconCls:r}=e;return{[t]:{[`${t}-small&-line, ${t}-small&-line ${t}-text ${r}`]:{fontSize:e.fontSizeSM}}}})(r)]},e=>({circleTextColor:e.colorText,defaultColor:e.colorInfo,remainingColor:e.colorFillSecondary,lineBorderRadius:100,circleTextFontSize:"1em",circleIconFontSize:`${e.fontSize/e.fontSizeSM}em`}));var H=function(e,t){var r={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&0>t.indexOf(n)&&(r[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,n=Object.getOwnPropertySymbols(e);ot.indexOf(n[o])&&Object.prototype.propertyIsEnumerable.call(e,n[o])&&(r[n[o]]=e[n[o]]);return r};let _=e=>{let{prefixCls:r,direction:n,percent:o,size:i,strokeWidth:l,strokeColor:s,strokeLinecap:c="round",children:u,trailColor:d=null,percentPosition:p,success:f}=e,{align:g,type:m}=p,b=s&&"string"!=typeof s?((e,t)=>{let{from:r=N.presetPrimaryColors.blue,to:n=N.presetPrimaryColors.blue,direction:o="rtl"===t?"to left":"to right"}=e,i=H(e,["from","to","direction"]);if(0!==Object.keys(i).length){let e,t=(e=[],Object.keys(i).forEach(t=>{let r=Number.parseFloat(t.replace(/%/g,""));Number.isNaN(r)||e.push({key:r,value:i[t]})}),(e=e.sort((e,t)=>e.key-t.key)).map(({key:e,value:t})=>`${t} ${e}%`).join(", ")),r=`linear-gradient(${o}, ${t})`;return{background:r,[B]:r}}let l=`linear-gradient(${o}, ${r}, ${n})`;return{background:l,[B]:l}})(s,n):{[B]:s,background:s},h="square"===c||"butt"===c?0:void 0,[v,y]=D(null!=i?i:[-1,l||("small"===i?6:8)],"line",{strokeWidth:l}),$=Object.assign(Object.assign({width:`${I(o)}%`,height:y,borderRadius:h},b),{[F]:I(o)/100}),C=P(e),k={width:`${I(C)}%`,height:y,borderRadius:h,backgroundColor:null==f?void 0:f.strokeColor},x=t.createElement("div",{className:`${r}-inner`,style:{backgroundColor:d||void 0,borderRadius:h}},t.createElement("div",{className:(0,a.default)(`${r}-bg`,`${r}-bg-${m}`),style:$},"inner"===m&&u),void 0!==C&&t.createElement("div",{className:`${r}-success-bg`,style:k})),S="outer"===m&&"start"===g,O="outer"===m&&"end"===g;return"outer"===m&&"center"===g?t.createElement("div",{className:`${r}-layout-bottom`},x,u):t.createElement("div",{className:`${r}-outer`,style:{width:v<0?"100%":v}},S&&u,x,O&&u)},q=e=>{let{size:r,steps:n,rounding:o=Math.round,percent:i=0,strokeWidth:l=8,strokeColor:s,trailColor:c=null,prefixCls:u,children:d}=e,p=o(i/100*n),[f,g]=D(null!=r?r:["small"===r?2:14,l],"step",{steps:n,strokeWidth:l}),m=f/n,b=Array.from({length:n});for(let e=0;et.indexOf(n)&&(r[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,n=Object.getOwnPropertySymbols(e);ot.indexOf(n[o])&&Object.prototype.propertyIsEnumerable.call(e,n[o])&&(r[n[o]]=e[n[o]]);return r};let V=["normal","exception","active","success"],K=t.forwardRef((e,u)=>{let d,{prefixCls:p,className:f,rootClassName:g,steps:m,strokeColor:b,percent:h=0,size:v="default",showInfo:y=!0,type:$="line",status:C,format:k,style:x,percentPosition:S={}}=e,O=G(e,["prefixCls","className","rootClassName","steps","strokeColor","percent","size","showInfo","type","status","format","style","percentPosition"]),{align:w="end",type:E="outer"}=S,j=Array.isArray(b)?b[0]:b,N="string"==typeof b||Array.isArray(b)?b:void 0,z=t.useMemo(()=>{if(j){let e="string"==typeof j?j:Object.values(j)[0];return new r.FastColor(e).isLight()}return!1},[b]),A=t.useMemo(()=>{var t,r;let n=P(e);return Number.parseInt(void 0!==n?null==(t=null!=n?n:0)?void 0:t.toString():null==(r=null!=h?h:0)?void 0:r.toString(),10)},[h,e.success,e.successPercent]),M=t.useMemo(()=>!V.includes(C)&&A>=100?"success":C||"normal",[C,A]),{getPrefixCls:T,direction:W,progress:B}=t.useContext(c.ConfigContext),F=T("progress",p),[X,H,K]=L(F),U="line"===$,Q=U&&!m,Y=t.useMemo(()=>{let r;if(!y)return null;let s=P(e),c=k||(e=>`${e}%`),u=U&&z&&"inner"===E;return"inner"===E||k||"exception"!==M&&"success"!==M?r=c(I(h),I(s)):"exception"===M?r=U?t.createElement(i.default,null):t.createElement(l.default,null):"success"===M&&(r=U?t.createElement(n.default,null):t.createElement(o.default,null)),t.createElement("span",{className:(0,a.default)(`${F}-text`,{[`${F}-text-bright`]:u,[`${F}-text-${w}`]:Q,[`${F}-text-${E}`]:Q}),title:"string"==typeof r?r:void 0},r)},[y,h,A,M,$,F,k]);"line"===$?d=m?t.createElement(q,Object.assign({},e,{strokeColor:N,prefixCls:F,steps:"object"==typeof m?m.count:m}),Y):t.createElement(_,Object.assign({},e,{strokeColor:j,prefixCls:F,direction:W,percentPosition:{align:w,type:E}}),Y):("circle"===$||"dashboard"===$)&&(d=t.createElement(R,Object.assign({},e,{strokeColor:j,prefixCls:F,progressStatus:M}),Y));let J=(0,a.default)(F,`${F}-status-${M}`,{[`${F}-${"dashboard"===$&&"circle"||$}`]:"line"!==$,[`${F}-inline-circle`]:"circle"===$&&D(v,"circle")[0]<=20,[`${F}-line`]:Q,[`${F}-line-align-${w}`]:Q,[`${F}-line-position-${E}`]:Q,[`${F}-steps`]:m,[`${F}-show-info`]:y,[`${F}-${v}`]:"string"==typeof v,[`${F}-rtl`]:"rtl"===W},null==B?void 0:B.className,f,g,H,K);return X(t.createElement("div",Object.assign({ref:u,style:Object.assign(Object.assign({},null==B?void 0:B.style),x),className:J,role:"progressbar","aria-valuenow":A,"aria-valuemin":0,"aria-valuemax":100},(0,s.default)(O,["trailColor","strokeWidth","width","gapDegree","gapPosition","strokeLinecap","success","successPercent"])),d))});e.s(["default",0,K],309821)}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/025ocjcb8e481.js b/litellm/proxy/_experimental/out/_next/static/chunks/025ocjcb8e481.js new file mode 100644 index 00000000000..d45da443d18 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/025ocjcb8e481.js @@ -0,0 +1,7 @@ +(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,244451,e=>{"use strict";let t;e.i(247167);var r=e.i(271645),a=e.i(343794),i=e.i(242064),o=e.i(763731),l=e.i(174428);let n=80*Math.PI,s=e=>{let{dotClassName:t,style:i,hasCircleCls:o}=e;return r.createElement("circle",{className:(0,a.default)(`${t}-circle`,{[`${t}-circle-bg`]:o}),r:40,cx:50,cy:50,strokeWidth:20,style:i})},c=({percent:e,prefixCls:t})=>{let i=`${t}-dot`,o=`${i}-holder`,c=`${o}-hidden`,[d,u]=r.useState(!1);(0,l.default)(()=>{0!==e&&u(!0)},[0!==e]);let f=Math.max(Math.min(e,100),0);if(!d)return null;let m={strokeDashoffset:`${n/4}`,strokeDasharray:`${n*f/100} ${n*(100-f)/100}`};return r.createElement("span",{className:(0,a.default)(o,`${i}-progress`,f<=0&&c)},r.createElement("svg",{viewBox:"0 0 100 100",role:"progressbar","aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":f},r.createElement(s,{dotClassName:i,hasCircleCls:!0}),r.createElement(s,{dotClassName:i,style:m})))};function d(e){let{prefixCls:t,percent:i=0}=e,o=`${t}-dot`,l=`${o}-holder`,n=`${l}-hidden`;return r.createElement(r.Fragment,null,r.createElement("span",{className:(0,a.default)(l,i>0&&n)},r.createElement("span",{className:(0,a.default)(o,`${t}-dot-spin`)},[1,2,3,4].map(e=>r.createElement("i",{className:`${t}-dot-item`,key:e})))),r.createElement(c,{prefixCls:t,percent:i}))}function u(e){var t;let{prefixCls:i,indicator:l,percent:n}=e,s=`${i}-dot`;return l&&r.isValidElement(l)?(0,o.cloneElement)(l,{className:(0,a.default)(null==(t=l.props)?void 0:t.className,s),percent:n}):r.createElement(d,{prefixCls:i,percent:n})}e.i(296059);var f=e.i(694758),m=e.i(183293),p=e.i(246422),v=e.i(838378);let h=new f.Keyframes("antSpinMove",{to:{opacity:1}}),g=new f.Keyframes("antRotate",{to:{transform:"rotate(405deg)"}}),b=(0,p.genStyleHooks)("Spin",e=>(e=>{let{componentCls:t,calc:r}=e;return{[t]:Object.assign(Object.assign({},(0,m.resetComponent)(e)),{position:"absolute",display:"none",color:e.colorPrimary,fontSize:0,textAlign:"center",verticalAlign:"middle",opacity:0,transition:`transform ${e.motionDurationSlow} ${e.motionEaseInOutCirc}`,"&-spinning":{position:"relative",display:"inline-block",opacity:1},[`${t}-text`]:{fontSize:e.fontSize,paddingTop:r(r(e.dotSize).sub(e.fontSize)).div(2).add(2).equal()},"&-fullscreen":{position:"fixed",width:"100vw",height:"100vh",backgroundColor:e.colorBgMask,zIndex:e.zIndexPopupBase,inset:0,display:"flex",alignItems:"center",flexDirection:"column",justifyContent:"center",opacity:0,visibility:"hidden",transition:`all ${e.motionDurationMid}`,"&-show":{opacity:1,visibility:"visible"},[t]:{[`${t}-dot-holder`]:{color:e.colorWhite},[`${t}-text`]:{color:e.colorTextLightSolid}}},"&-nested-loading":{position:"relative",[`> div > ${t}`]:{position:"absolute",top:0,insetInlineStart:0,zIndex:4,display:"block",width:"100%",height:"100%",maxHeight:e.contentHeight,[`${t}-dot`]:{position:"absolute",top:"50%",insetInlineStart:"50%",margin:r(e.dotSize).mul(-1).div(2).equal()},[`${t}-text`]:{position:"absolute",top:"50%",width:"100%",textShadow:`0 1px 2px ${e.colorBgContainer}`},[`&${t}-show-text ${t}-dot`]:{marginTop:r(e.dotSize).div(2).mul(-1).sub(10).equal()},"&-sm":{[`${t}-dot`]:{margin:r(e.dotSizeSM).mul(-1).div(2).equal()},[`${t}-text`]:{paddingTop:r(r(e.dotSizeSM).sub(e.fontSize)).div(2).add(2).equal()},[`&${t}-show-text ${t}-dot`]:{marginTop:r(e.dotSizeSM).div(2).mul(-1).sub(10).equal()}},"&-lg":{[`${t}-dot`]:{margin:r(e.dotSizeLG).mul(-1).div(2).equal()},[`${t}-text`]:{paddingTop:r(r(e.dotSizeLG).sub(e.fontSize)).div(2).add(2).equal()},[`&${t}-show-text ${t}-dot`]:{marginTop:r(e.dotSizeLG).div(2).mul(-1).sub(10).equal()}}},[`${t}-container`]:{position:"relative",transition:`opacity ${e.motionDurationSlow}`,"&::after":{position:"absolute",top:0,insetInlineEnd:0,bottom:0,insetInlineStart:0,zIndex:10,width:"100%",height:"100%",background:e.colorBgContainer,opacity:0,transition:`all ${e.motionDurationSlow}`,content:'""',pointerEvents:"none"}},[`${t}-blur`]:{clear:"both",opacity:.5,userSelect:"none",pointerEvents:"none","&::after":{opacity:.4,pointerEvents:"auto"}}},"&-tip":{color:e.spinDotDefault},[`${t}-dot-holder`]:{width:"1em",height:"1em",fontSize:e.dotSize,display:"inline-block",transition:`transform ${e.motionDurationSlow} ease, opacity ${e.motionDurationSlow} ease`,transformOrigin:"50% 50%",lineHeight:1,color:e.colorPrimary,"&-hidden":{transform:"scale(0.3)",opacity:0}},[`${t}-dot-progress`]:{position:"absolute",inset:0},[`${t}-dot`]:{position:"relative",display:"inline-block",fontSize:e.dotSize,width:"1em",height:"1em","&-item":{position:"absolute",display:"block",width:r(e.dotSize).sub(r(e.marginXXS).div(2)).div(2).equal(),height:r(e.dotSize).sub(r(e.marginXXS).div(2)).div(2).equal(),background:"currentColor",borderRadius:"100%",transform:"scale(0.75)",transformOrigin:"50% 50%",opacity:.3,animationName:h,animationDuration:"1s",animationIterationCount:"infinite",animationTimingFunction:"linear",animationDirection:"alternate","&:nth-child(1)":{top:0,insetInlineStart:0,animationDelay:"0s"},"&:nth-child(2)":{top:0,insetInlineEnd:0,animationDelay:"0.4s"},"&:nth-child(3)":{insetInlineEnd:0,bottom:0,animationDelay:"0.8s"},"&:nth-child(4)":{bottom:0,insetInlineStart:0,animationDelay:"1.2s"}},"&-spin":{transform:"rotate(45deg)",animationName:g,animationDuration:"1.2s",animationIterationCount:"infinite",animationTimingFunction:"linear"},"&-circle":{strokeLinecap:"round",transition:["stroke-dashoffset","stroke-dasharray","stroke","stroke-width","opacity"].map(t=>`${t} ${e.motionDurationSlow} ease`).join(","),fillOpacity:0,stroke:"currentcolor"},"&-circle-bg":{stroke:e.colorFillSecondary}},[`&-sm ${t}-dot`]:{"&, &-holder":{fontSize:e.dotSizeSM}},[`&-sm ${t}-dot-holder`]:{i:{width:r(r(e.dotSizeSM).sub(r(e.marginXXS).div(2))).div(2).equal(),height:r(r(e.dotSizeSM).sub(r(e.marginXXS).div(2))).div(2).equal()}},[`&-lg ${t}-dot`]:{"&, &-holder":{fontSize:e.dotSizeLG}},[`&-lg ${t}-dot-holder`]:{i:{width:r(r(e.dotSizeLG).sub(e.marginXXS)).div(2).equal(),height:r(r(e.dotSizeLG).sub(e.marginXXS)).div(2).equal()}},[`&${t}-show-text ${t}-text`]:{display:"block"}})}})((0,v.mergeToken)(e,{spinDotDefault:e.colorTextDescription})),e=>{let{controlHeightLG:t,controlHeight:r}=e;return{contentHeight:400,dotSize:t/2,dotSizeSM:.35*t,dotSizeLG:r}}),y=[[30,.05],[70,.03],[96,.01]];var $=function(e,t){var r={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(r[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var i=0,a=Object.getOwnPropertySymbols(e);it.indexOf(a[i])&&Object.prototype.propertyIsEnumerable.call(e,a[i])&&(r[a[i]]=e[a[i]]);return r};let S=e=>{var o;let{prefixCls:l,spinning:n=!0,delay:s=0,className:c,rootClassName:d,size:f="default",tip:m,wrapperClassName:p,style:v,children:h,fullscreen:g=!1,indicator:S,percent:C}=e,x=$(e,["prefixCls","spinning","delay","className","rootClassName","size","tip","wrapperClassName","style","children","fullscreen","indicator","percent"]),{getPrefixCls:w,direction:k,className:E,style:z,indicator:O}=(0,i.useComponentConfig)("spin"),N=w("spin",l),[M,D,j]=b(N),[P,I]=r.useState(()=>n&&(!n||!s||!!Number.isNaN(Number(s)))),T=function(e,t){let[a,i]=r.useState(0),o=r.useRef(null),l="auto"===t;return r.useEffect(()=>(l&&e&&(i(0),o.current=setInterval(()=>{i(e=>{let t=100-e;for(let r=0;r{o.current&&(clearInterval(o.current),o.current=null)}),[l,e]),l?a:t}(P,C);r.useEffect(()=>{if(n){let e=function(e,t,r){var a,i=r||{},o=i.noTrailing,l=void 0!==o&&o,n=i.noLeading,s=void 0!==n&&n,c=i.debounceMode,d=void 0===c?void 0:c,u=!1,f=0;function m(){a&&clearTimeout(a)}function p(){for(var r=arguments.length,i=Array(r),o=0;oe?s?(f=Date.now(),l||(a=setTimeout(d?v:p,e))):p():!0!==l&&(a=setTimeout(d?v:p,void 0===d?e-c:e)))}return p.cancel=function(e){var t=(e||{}).upcomingOnly;m(),u=!(void 0!==t&&t)},p}(s,()=>{I(!0)},{debounceMode:false});return e(),()=>{var t;null==(t=null==e?void 0:e.cancel)||t.call(e)}}I(!1)},[s,n]);let _=r.useMemo(()=>void 0!==h&&!g,[h,g]),H=(0,a.default)(N,E,{[`${N}-sm`]:"small"===f,[`${N}-lg`]:"large"===f,[`${N}-spinning`]:P,[`${N}-show-text`]:!!m,[`${N}-rtl`]:"rtl"===k},c,!g&&d,D,j),R=(0,a.default)(`${N}-container`,{[`${N}-blur`]:P}),B=null!=(o=null!=S?S:O)?o:t,L=Object.assign(Object.assign({},z),v),q=r.createElement("div",Object.assign({},x,{style:L,className:H,"aria-live":"polite","aria-busy":P}),r.createElement(u,{prefixCls:N,indicator:B,percent:T}),m&&(_||g)?r.createElement("div",{className:`${N}-text`},m):null);return M(_?r.createElement("div",Object.assign({},x,{className:(0,a.default)(`${N}-nested-loading`,p,D,j)}),P&&r.createElement("div",{key:"loading"},q),r.createElement("div",{className:R,key:"container"},h)):g?r.createElement("div",{className:(0,a.default)(`${N}-fullscreen`,{[`${N}-fullscreen-show`]:P},d,D,j)},q):q)};S.setDefaultIndicator=e=>{t=e},e.s(["default",0,S],244451)},482725,e=>{"use strict";var t=e.i(244451);e.s(["Spin",()=>t.default])},184163,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M505.7 661a8 8 0 0012.6 0l112-141.7c4.1-5.2.4-12.9-6.3-12.9h-74.1V168c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v338.3H400c-6.7 0-10.4 7.7-6.3 12.9l112 141.8zM878 626h-60c-4.4 0-8 3.6-8 8v154H214V634c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v198c0 17.7 14.3 32 32 32h684c17.7 0 32-14.3 32-32V634c0-4.4-3.6-8-8-8z"}}]},name:"download",theme:"outlined"};var i=e.i(9583),o=r.forwardRef(function(e,o){return r.createElement(i.default,(0,t.default)({},e,{ref:o,icon:a}))});e.s(["default",0,o],184163)},737434,e=>{"use strict";var t=e.i(184163);e.s(["DownloadOutlined",()=>t.default])},107233,603908,e=>{"use strict";let t=(0,e.i(475254).default)("plus",[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]]);e.s(["default",0,t],603908),e.s(["Plus",0,t],107233)},695411,e=>{"use strict";var t=e.i(602869);let r=async e=>{try{let r=await (0,t.modelHubCall)(e);if(r?.data.length>0){let e=r.data.map(e=>({model_group:e.model_group,mode:e?.mode}));return e.sort((e,t)=>e.model_group.localeCompare(t.model_group)),e}return[]}catch(e){throw console.error("Error fetching model info:",e),e}};e.s(["fetchAvailableModels",0,r])},343488,e=>{"use strict";var t=e.i(540626),r=e.i(271645);e.s(["useDebouncedCallback",0,function(e,a){let i=(0,t.useDebouncer)(e,a).maybeExecute;return(0,r.useCallback)((...e)=>i(...e),[i])}])},983561,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M300 328a60 60 0 10120 0 60 60 0 10-120 0zM852 64H172c-17.7 0-32 14.3-32 32v660c0 17.7 14.3 32 32 32h680c17.7 0 32-14.3 32-32V96c0-17.7-14.3-32-32-32zm-32 660H204V128h616v596zM604 328a60 60 0 10120 0 60 60 0 10-120 0zm250.2 556H169.8c-16.5 0-29.8 14.3-29.8 32v36c0 4.4 3.3 8 7.4 8h729.1c4.1 0 7.4-3.6 7.4-8v-36c.1-17.7-13.2-32-29.7-32zM664 508H360c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h304c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"}}]},name:"robot",theme:"outlined"};var i=e.i(9583),o=r.forwardRef(function(e,o){return r.createElement(i.default,(0,t.default)({},e,{ref:o,icon:a}))});e.s(["RobotOutlined",0,o],983561)},916940,e=>{"use strict";var t=e.i(843476),r=e.i(271645),a=e.i(199133),i=e.i(602869);e.s(["default",0,({onChange:e,value:o,className:l,accessToken:n,placeholder:s="Select vector stores",disabled:c=!1})=>{let[d,u]=(0,r.useState)([]),[f,m]=(0,r.useState)(!1);return(0,r.useEffect)(()=>{(async()=>{if(n){m(!0);try{let e=await (0,i.vectorStoreListCall)(n);e.data&&u(e.data)}catch(e){console.error("Error fetching vector stores:",e)}finally{m(!1)}}})()},[n]),(0,t.jsx)("div",{children:(0,t.jsx)(a.Select,{mode:"multiple",placeholder:s,onChange:e,value:o,loading:f,className:l,allowClear:!0,options:d.map(e=>({label:`${e.vector_store_name||e.vector_store_id} (${e.vector_store_id})`,value:e.vector_store_id,title:e.vector_store_description||e.vector_store_id})),optionFilterProp:"label",showSearch:!0,style:{width:"100%"},disabled:c})})}])},245704,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M699 353h-46.9c-10.2 0-19.9 4.9-25.9 13.3L469 584.3l-71.2-98.8c-6-8.3-15.6-13.3-25.9-13.3H325c-6.5 0-10.3 7.4-6.5 12.7l124.6 172.8a31.8 31.8 0 0051.7 0l210.6-292c3.9-5.3.1-12.7-6.4-12.7z"}},{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}}]},name:"check-circle",theme:"outlined"};var i=e.i(9583),o=r.forwardRef(function(e,o){return r.createElement(i.default,(0,t.default)({},e,{ref:o,icon:a}))});e.s(["CheckCircleOutlined",0,o],245704)},637235,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}},{tag:"path",attrs:{d:"M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.8-11.2z"}}]},name:"clock-circle",theme:"outlined"};var i=e.i(9583),o=r.forwardRef(function(e,o){return r.createElement(i.default,(0,t.default)({},e,{ref:o,icon:a}))});e.s(["ClockCircleOutlined",0,o],637235)},646563,e=>{"use strict";var t=e.i(959013);e.s(["PlusOutlined",()=>t.default])},597440,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M360 184h-8c4.4 0 8-3.6 8-8v8h304v-8c0 4.4 3.6 8 8 8h-8v72h72v-80c0-35.3-28.7-64-64-64H352c-35.3 0-64 28.7-64 64v80h72v-72zm504 72H160c-17.7 0-32 14.3-32 32v32c0 4.4 3.6 8 8 8h60.4l24.7 523c1.6 34.1 29.8 61 63.9 61h454c34.2 0 62.3-26.8 63.9-61l24.7-523H888c4.4 0 8-3.6 8-8v-32c0-17.7-14.3-32-32-32zM731.3 840H292.7l-24.2-512h487l-24.2 512z"}}]},name:"delete",theme:"outlined"};var i=e.i(9583),o=r.forwardRef(function(e,o){return r.createElement(i.default,(0,t.default)({},e,{ref:o,icon:a}))});e.s(["default",0,o],597440)},955135,e=>{"use strict";var t=e.i(597440);e.s(["DeleteOutlined",()=>t.default])},166406,e=>{"use strict";var t=e.i(190144);e.s(["CopyOutlined",()=>t.default])},447566,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M872 474H286.9l350.2-304c5.6-4.9 2.2-14-5.2-14h-88.5c-3.9 0-7.6 1.4-10.5 3.9L155 487.8a31.96 31.96 0 000 48.3L535.1 866c1.5 1.3 3.3 2 5.2 2h91.5c7.4 0 10.8-9.2 5.2-14L286.9 550H872c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z"}}]},name:"arrow-left",theme:"outlined"};var i=e.i(9583),o=r.forwardRef(function(e,o){return r.createElement(i.default,(0,t.default)({},e,{ref:o,icon:a}))});e.s(["ArrowLeftOutlined",0,o],447566)},653496,e=>{"use strict";var t=e.i(721369);e.s(["Tabs",()=>t.default])},629569,e=>{"use strict";var t=e.i(290571),r=e.i(95779),a=e.i(444755),i=e.i(673706),o=e.i(271645);let l=o.default.forwardRef((e,l)=>{let{color:n,children:s,className:c}=e,d=(0,t.__rest)(e,["color","children","className"]);return o.default.createElement("p",Object.assign({ref:l,className:(0,a.tremorTwMerge)("font-medium text-tremor-title",n?(0,i.getColorClassNames)(n,r.colorPalette.darkText).textColor:"text-tremor-content-strong dark:text-dark-tremor-content-strong",c)},d),s)});l.displayName="Title",e.s(["Title",0,l],629569)},304967,e=>{"use strict";var t=e.i(290571),r=e.i(271645),a=e.i(480731),i=e.i(95779),o=e.i(444755),l=e.i(673706);let n=(0,l.makeClassName)("Card"),s=r.default.forwardRef((e,s)=>{let{decoration:c="",decorationColor:d,children:u,className:f}=e,m=(0,t.__rest)(e,["decoration","decorationColor","children","className"]);return r.default.createElement("div",Object.assign({ref:s,className:(0,o.tremorTwMerge)(n("root"),"relative w-full text-left ring-1 rounded-tremor-default p-6","bg-tremor-background ring-tremor-ring shadow-tremor-card","dark:bg-dark-tremor-background dark:ring-dark-tremor-ring dark:shadow-dark-tremor-card",d?(0,l.getColorClassNames)(d,i.colorPalette.border).borderColor:"border-tremor-brand dark:border-dark-tremor-brand",(e=>{if(!e)return"";switch(e){case a.HorizontalPositions.Left:return"border-l-4";case a.VerticalPositions.Top:return"border-t-4";case a.HorizontalPositions.Right:return"border-r-4";case a.VerticalPositions.Bottom:return"border-b-4";default:return""}})(c),f)},m),u)});s.displayName="Card",e.s(["Card",0,s],304967)},91874,681216,e=>{"use strict";var t=e.i(931067),r=e.i(209428),a=e.i(211577),i=e.i(392221),o=e.i(703923),l=e.i(343794),n=e.i(914949),s=e.i(271645),c=["prefixCls","className","style","checked","disabled","defaultChecked","type","title","onChange"],d=(0,s.forwardRef)(function(e,d){var u=e.prefixCls,f=void 0===u?"rc-checkbox":u,m=e.className,p=e.style,v=e.checked,h=e.disabled,g=e.defaultChecked,b=e.type,y=void 0===b?"checkbox":b,$=e.title,S=e.onChange,C=(0,o.default)(e,c),x=(0,s.useRef)(null),w=(0,s.useRef)(null),k=(0,n.default)(void 0!==g&&g,{value:v}),E=(0,i.default)(k,2),z=E[0],O=E[1];(0,s.useImperativeHandle)(d,function(){return{focus:function(e){var t;null==(t=x.current)||t.focus(e)},blur:function(){var e;null==(e=x.current)||e.blur()},input:x.current,nativeElement:w.current}});var N=(0,l.default)(f,m,(0,a.default)((0,a.default)({},"".concat(f,"-checked"),z),"".concat(f,"-disabled"),h));return s.createElement("span",{className:N,title:$,style:p,ref:w},s.createElement("input",(0,t.default)({},C,{className:"".concat(f,"-input"),ref:x,onChange:function(t){h||("checked"in e||O(t.target.checked),null==S||S({target:(0,r.default)((0,r.default)({},e),{},{type:y,checked:t.target.checked}),stopPropagation:function(){t.stopPropagation()},preventDefault:function(){t.preventDefault()},nativeEvent:t.nativeEvent}))},disabled:h,checked:!!z,type:y})),s.createElement("span",{className:"".concat(f,"-inner")}))});e.s(["default",0,d],91874);var u=e.i(963188);e.s(["default",0,function(e){let t=s.default.useRef(null),r=()=>{u.default.cancel(t.current),t.current=null};return[()=>{r(),t.current=(0,u.default)(()=>{t.current=null})},a=>{t.current&&(a.stopPropagation(),r()),null==e||e(a)}]}],681216)},374276,236836,e=>{"use strict";e.i(247167);var t=e.i(271645),r=e.i(343794),a=e.i(91874),i=e.i(611935),o=e.i(121872),l=e.i(26905),n=e.i(242064),s=e.i(937328),c=e.i(321883),d=e.i(62139);let u=t.default.createContext(null);e.i(296059);var f=e.i(915654),m=e.i(183293),p=e.i(246422),v=e.i(838378);function h(e,t){return(e=>{let{checkboxCls:t}=e,r=`${t}-wrapper`;return[{[`${t}-group`]:Object.assign(Object.assign({},(0,m.resetComponent)(e)),{display:"inline-flex",flexWrap:"wrap",columnGap:e.marginXS,[`> ${e.antCls}-row`]:{flex:1}}),[r]:Object.assign(Object.assign({},(0,m.resetComponent)(e)),{display:"inline-flex",alignItems:"baseline",cursor:"pointer","&:after":{display:"inline-block",width:0,overflow:"hidden",content:"'\\a0'"},[`& + ${r}`]:{marginInlineStart:0},[`&${r}-in-form-item`]:{'input[type="checkbox"]':{width:14,height:14}}}),[t]:Object.assign(Object.assign({},(0,m.resetComponent)(e)),{position:"relative",whiteSpace:"nowrap",lineHeight:1,cursor:"pointer",borderRadius:e.borderRadiusSM,alignSelf:"center",[`${t}-input`]:{position:"absolute",inset:0,zIndex:1,cursor:"pointer",opacity:0,margin:0,[`&:focus-visible + ${t}-inner`]:(0,m.genFocusOutline)(e)},[`${t}-inner`]:{boxSizing:"border-box",display:"block",width:e.checkboxSize,height:e.checkboxSize,direction:"ltr",backgroundColor:e.colorBgContainer,border:`${(0,f.unit)(e.lineWidth)} ${e.lineType} ${e.colorBorder}`,borderRadius:e.borderRadiusSM,borderCollapse:"separate",transition:`all ${e.motionDurationSlow}`,"&:after":{boxSizing:"border-box",position:"absolute",top:"50%",insetInlineStart:"25%",display:"table",width:e.calc(e.checkboxSize).div(14).mul(5).equal(),height:e.calc(e.checkboxSize).div(14).mul(8).equal(),border:`${(0,f.unit)(e.lineWidthBold)} solid ${e.colorWhite}`,borderTop:0,borderInlineStart:0,transform:"rotate(45deg) scale(0) translate(-50%,-50%)",opacity:0,content:'""',transition:`all ${e.motionDurationFast} ${e.motionEaseInBack}, opacity ${e.motionDurationFast}`}},"& + span":{paddingInlineStart:e.paddingXS,paddingInlineEnd:e.paddingXS}})},{[` + ${r}:not(${r}-disabled), + ${t}:not(${t}-disabled) + `]:{[`&:hover ${t}-inner`]:{borderColor:e.colorPrimary}},[`${r}:not(${r}-disabled)`]:{[`&:hover ${t}-checked:not(${t}-disabled) ${t}-inner`]:{backgroundColor:e.colorPrimaryHover,borderColor:"transparent"},[`&:hover ${t}-checked:not(${t}-disabled):after`]:{borderColor:e.colorPrimaryHover}}},{[`${t}-checked`]:{[`${t}-inner`]:{backgroundColor:e.colorPrimary,borderColor:e.colorPrimary,"&:after":{opacity:1,transform:"rotate(45deg) scale(1) translate(-50%,-50%)",transition:`all ${e.motionDurationMid} ${e.motionEaseOutBack} ${e.motionDurationFast}`}}},[` + ${r}-checked:not(${r}-disabled), + ${t}-checked:not(${t}-disabled) + `]:{[`&:hover ${t}-inner`]:{backgroundColor:e.colorPrimaryHover,borderColor:"transparent"}}},{[t]:{"&-indeterminate":{"&":{[`${t}-inner`]:{backgroundColor:`${e.colorBgContainer}`,borderColor:`${e.colorBorder}`,"&:after":{top:"50%",insetInlineStart:"50%",width:e.calc(e.fontSizeLG).div(2).equal(),height:e.calc(e.fontSizeLG).div(2).equal(),backgroundColor:e.colorPrimary,border:0,transform:"translate(-50%, -50%) scale(1)",opacity:1,content:'""'}},[`&:hover ${t}-inner`]:{backgroundColor:`${e.colorBgContainer}`,borderColor:`${e.colorPrimary}`}}}}},{[`${r}-disabled`]:{cursor:"not-allowed"},[`${t}-disabled`]:{[`&, ${t}-input`]:{cursor:"not-allowed",pointerEvents:"none"},[`${t}-inner`]:{background:e.colorBgContainerDisabled,borderColor:e.colorBorder,"&:after":{borderColor:e.colorTextDisabled}},"&:after":{display:"none"},"& + span":{color:e.colorTextDisabled},[`&${t}-indeterminate ${t}-inner::after`]:{background:e.colorTextDisabled}}}]})((0,v.mergeToken)(t,{checkboxCls:`.${e}`,checkboxSize:t.controlInteractiveSize}))}let g=(0,p.genStyleHooks)("Checkbox",(e,{prefixCls:t})=>[h(t,e)]);e.s(["default",0,g,"getStyle",0,h],236836);var b=e.i(681216),y=function(e,t){var r={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(r[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var i=0,a=Object.getOwnPropertySymbols(e);it.indexOf(a[i])&&Object.prototype.propertyIsEnumerable.call(e,a[i])&&(r[a[i]]=e[a[i]]);return r};let $=t.forwardRef((e,f)=>{var m;let{prefixCls:p,className:v,rootClassName:h,children:$,indeterminate:S=!1,style:C,onMouseEnter:x,onMouseLeave:w,skipGroup:k=!1,disabled:E}=e,z=y(e,["prefixCls","className","rootClassName","children","indeterminate","style","onMouseEnter","onMouseLeave","skipGroup","disabled"]),{getPrefixCls:O,direction:N,checkbox:M}=t.useContext(n.ConfigContext),D=t.useContext(u),{isFormItemInput:j}=t.useContext(d.FormItemInputContext),P=t.useContext(s.default),I=null!=(m=(null==D?void 0:D.disabled)||E)?m:P,T=t.useRef(z.value),_=t.useRef(null),H=(0,i.composeRef)(f,_);t.useEffect(()=>{null==D||D.registerValue(z.value)},[]),t.useEffect(()=>{if(!k)return z.value!==T.current&&(null==D||D.cancelValue(T.current),null==D||D.registerValue(z.value),T.current=z.value),()=>null==D?void 0:D.cancelValue(z.value)},[z.value]),t.useEffect(()=>{var e;(null==(e=_.current)?void 0:e.input)&&(_.current.input.indeterminate=S)},[S]);let R=O("checkbox",p),B=(0,c.default)(R),[L,q,X]=g(R,B),G=Object.assign({},z);D&&!k&&(G.onChange=(...e)=>{z.onChange&&z.onChange.apply(z,e),D.toggleOption&&D.toggleOption({label:$,value:z.value})},G.name=D.name,G.checked=D.value.includes(z.value));let V=(0,r.default)(`${R}-wrapper`,{[`${R}-rtl`]:"rtl"===N,[`${R}-wrapper-checked`]:G.checked,[`${R}-wrapper-disabled`]:I,[`${R}-wrapper-in-form-item`]:j},null==M?void 0:M.className,v,h,X,B,q),F=(0,r.default)({[`${R}-indeterminate`]:S},l.TARGET_CLS,q),[A,W]=(0,b.default)(G.onClick);return L(t.createElement(o.default,{component:"Checkbox",disabled:I},t.createElement("label",{className:V,style:Object.assign(Object.assign({},null==M?void 0:M.style),C),onMouseEnter:x,onMouseLeave:w,onClick:A},t.createElement(a.default,Object.assign({},G,{onClick:W,prefixCls:R,className:F,disabled:I,ref:H})),null!=$&&t.createElement("span",{className:`${R}-label`},$))))});var S=e.i(8211),C=e.i(529681),x=function(e,t){var r={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(r[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var i=0,a=Object.getOwnPropertySymbols(e);it.indexOf(a[i])&&Object.prototype.propertyIsEnumerable.call(e,a[i])&&(r[a[i]]=e[a[i]]);return r};let w=t.forwardRef((e,a)=>{let{defaultValue:i,children:o,options:l=[],prefixCls:s,className:d,rootClassName:f,style:m,onChange:p}=e,v=x(e,["defaultValue","children","options","prefixCls","className","rootClassName","style","onChange"]),{getPrefixCls:h,direction:b}=t.useContext(n.ConfigContext),[y,w]=t.useState(v.value||i||[]),[k,E]=t.useState([]);t.useEffect(()=>{"value"in v&&w(v.value||[])},[v.value]);let z=t.useMemo(()=>l.map(e=>"string"==typeof e||"number"==typeof e?{label:e,value:e}:e),[l]),O=e=>{E(t=>t.filter(t=>t!==e))},N=e=>{E(t=>[].concat((0,S.default)(t),[e]))},M=e=>{let t=y.indexOf(e.value),r=(0,S.default)(y);-1===t?r.push(e.value):r.splice(t,1),"value"in v||w(r),null==p||p(r.filter(e=>k.includes(e)).sort((e,t)=>z.findIndex(t=>t.value===e)-z.findIndex(e=>e.value===t)))},D=h("checkbox",s),j=`${D}-group`,P=(0,c.default)(D),[I,T,_]=g(D,P),H=(0,C.default)(v,["value","disabled"]),R=l.length?z.map(e=>t.createElement($,{prefixCls:D,key:e.value.toString(),disabled:"disabled"in e?e.disabled:v.disabled,value:e.value,checked:y.includes(e.value),onChange:e.onChange,className:(0,r.default)(`${j}-item`,e.className),style:e.style,title:e.title,id:e.id,required:e.required},e.label)):o,B=t.useMemo(()=>({toggleOption:M,value:y,disabled:v.disabled,name:v.name,registerValue:N,cancelValue:O}),[M,y,v.disabled,v.name,N,O]),L=(0,r.default)(j,{[`${j}-rtl`]:"rtl"===b},d,f,_,P,T);return I(t.createElement("div",Object.assign({className:L,style:m},H,{ref:a}),t.createElement(u.Provider,{value:B},R)))});$.Group=w,$.__ANT_CHECKBOX=!0,e.s(["default",0,$],374276)},536916,e=>{"use strict";var t=e.i(374276);e.s(["Checkbox",()=>t.default])},921511,e=>{"use strict";var t=e.i(843476),r=e.i(271645),a=e.i(199133),i=e.i(602869);function o(e){return e.filter(e=>(e.version_status??"draft")!=="draft").map(e=>{var t;let r=e.version_number??1,a=e.version_status??"draft";return{label:`${e.policy_name} — v${r} (${a})${e.description?` — ${e.description}`:""}`,value:"production"===a?e.policy_name:e.policy_id?(t=e.policy_id,`policy_${t}`):e.policy_name}})}e.s(["default",0,({onChange:e,value:l,className:n,accessToken:s,disabled:c,onPoliciesLoaded:d})=>{let[u,f]=(0,r.useState)([]),[m,p]=(0,r.useState)(!1);return(0,r.useEffect)(()=>{(async()=>{if(s){p(!0);try{let e=await (0,i.getPoliciesList)(s);e.policies&&(f(e.policies),d?.(e.policies))}catch(e){console.error("Error fetching policies:",e)}finally{p(!1)}}})()},[s,d]),(0,t.jsx)("div",{children:(0,t.jsx)(a.Select,{mode:"multiple",disabled:c,placeholder:c?"Setting policies is a premium feature.":"Select policies (production or published versions)",onChange:t=>{e(t)},value:l,loading:m,className:n,allowClear:!0,options:o(u),optionFilterProp:"label",showSearch:!0,style:{width:"100%"}})})},"getPolicyOptionEntries",0,o])},891547,e=>{"use strict";var t=e.i(843476),r=e.i(271645),a=e.i(199133),i=e.i(602869);e.s(["default",0,({onChange:e,value:o,className:l,accessToken:n,disabled:s})=>{let[c,d]=(0,r.useState)([]),[u,f]=(0,r.useState)(!1);return(0,r.useEffect)(()=>{(async()=>{if(n){f(!0);try{let e=await (0,i.getGuardrailsList)(n);e.guardrails&&d(e.guardrails)}catch(e){console.error("Error fetching guardrails:",e)}finally{f(!1)}}})()},[n]),(0,t.jsx)("div",{children:(0,t.jsx)(a.Select,{mode:"multiple",disabled:s,placeholder:s?"Setting guardrails is a premium feature.":"Select guardrails",onChange:t=>{e(t)},value:o,loading:u,className:l,allowClear:!0,options:c.map(e=>({label:`${e.guardrail_name}`,value:e.guardrail_name})),optionFilterProp:"label",showSearch:!0,style:{width:"100%"}})})}])}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/046q5lwe95zp6.js b/litellm/proxy/_experimental/out/_next/static/chunks/046q5lwe95zp6.js new file mode 100644 index 00000000000..947a1f5f744 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/046q5lwe95zp6.js @@ -0,0 +1 @@ +(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,727612,e=>{"use strict";let t=(0,e.i(475254).default)("trash-2",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}],["line",{x1:"10",x2:"10",y1:"11",y2:"17",key:"1uufr5"}],["line",{x1:"14",x2:"14",y1:"11",y2:"17",key:"xtxkd"}]]);e.s(["Trash2",0,t],727612)},541071,373488,e=>{"use strict";let t=(0,e.i(475254).default)("ellipsis",[["circle",{cx:"12",cy:"12",r:"1",key:"41hilf"}],["circle",{cx:"19",cy:"12",r:"1",key:"1wjl8i"}],["circle",{cx:"5",cy:"12",r:"1",key:"1pcz8c"}]]);e.s(["default",0,t],373488),e.s(["MoreHorizontal",0,t],541071)},755146,e=>{"use strict";var t=e.i(843476),r=e.i(451512),a=e.i(115504);e.i(233565),e.i(678784),e.s(["DropdownMenu",0,function({...e}){return(0,t.jsx)(r.Menu.Root,{"data-slot":"dropdown-menu",...e})},"DropdownMenuContent",0,function({align:e="start",alignOffset:n=0,side:s="bottom",sideOffset:i=4,className:l,...o}){return(0,t.jsx)(r.Menu.Portal,{children:(0,t.jsx)(r.Menu.Positioner,{className:"isolate z-50 outline-none",align:e,alignOffset:n,side:s,sideOffset:i,children:(0,t.jsx)(r.Menu.Popup,{"data-slot":"dropdown-menu-content",className:(0,a.cn)("z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95",l),...o})})})},"DropdownMenuItem",0,function({className:e,inset:n,variant:s="default",...i}){return(0,t.jsx)(r.Menu.Item,{"data-slot":"dropdown-menu-item","data-inset":n,"data-variant":s,className:(0,a.cn)("group/dropdown-menu-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",e),...i})},"DropdownMenuSeparator",0,function({className:e,...n}){return(0,t.jsx)(r.Menu.Separator,{"data-slot":"dropdown-menu-separator",className:(0,a.cn)("-mx-1 my-1 h-px bg-border",e),...n})},"DropdownMenuTrigger",0,function({...e}){return(0,t.jsx)(r.Menu.Trigger,{"data-slot":"dropdown-menu-trigger",...e})}])},788699,e=>{"use strict";let t=(0,e.i(475254).default)("pencil",[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",key:"1a8usu"}],["path",{d:"m15 5 4 4",key:"1mk7zo"}]]);e.s(["Pencil",0,t],788699)},118366,e=>{"use strict";var t=e.i(991124);e.s(["CopyIcon",()=>t.default])},21548,e=>{"use strict";var t=e.i(616303);e.s(["Empty",()=>t.default])},655063,e=>{"use strict";var t=e.i(399029),r=e.i(271645);e.s(["useDebouncedValue",0,function(e,a,n){let[s,i,l]=(0,t.useDebouncedState)(e,a,n);return(0,r.useEffect)(()=>{i(e)},[e,i]),[s,l]}])},624687,e=>{"use strict";var t=e.i(843476),r=e.i(271645),a=e.i(115504);let n=r.forwardRef(({className:e,...r},n)=>(0,t.jsx)("textarea",{ref:n,"data-slot":"textarea",className:(0,a.cn)("flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",e),...r}));n.displayName="Textarea",e.s(["Textarea",0,n])},950594,e=>{"use strict";var t=e.i(843476),r=e.i(271645),a=e.i(115504),n=e.i(519455),s=e.i(793479),i=e.i(624687);let l=(0,a.cva)({base:"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",variants:{align:{"inline-start":"order-first pl-2 has-[>button]:-ml-1 has-[>kbd]:ml-[-0.15rem]","inline-end":"order-last pr-2 has-[>button]:-mr-1 has-[>kbd]:mr-[-0.15rem]","block-start":"order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2","block-end":"order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2"}},defaultVariants:{align:"inline-start"}}),o=(0,a.cva)({base:"flex items-center gap-2 text-sm shadow-none",variants:{size:{xs:"h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",sm:"","icon-xs":"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0","icon-sm":"size-8 p-0 has-[>svg]:p-0"}},defaultVariants:{size:"xs"}}),d=r.forwardRef(({className:e,type:r="button",variant:s="ghost",size:i="xs",...l},d)=>(0,t.jsx)(n.Button,{ref:d,type:r,"data-size":i,variant:s,className:(0,a.cn)(o({size:i}),e),...l}));d.displayName="InputGroupButton";let u=r.forwardRef(({className:e,...r},n)=>(0,t.jsx)(s.Input,{ref:n,"data-slot":"input-group-control",className:(0,a.cn)("flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",e),...r}));u.displayName="InputGroupInput",r.forwardRef(({className:e,...r},n)=>(0,t.jsx)(i.Textarea,{ref:n,"data-slot":"input-group-control",className:(0,a.cn)("flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",e),...r})).displayName="InputGroupTextarea",e.s(["InputGroup",0,function({className:e,...r}){return(0,t.jsx)("div",{"data-slot":"input-group",role:"group",className:(0,a.cn)("group/input-group relative flex h-9 w-full min-w-0 items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",e),...r})},"InputGroupAddon",0,function({className:e,align:r="inline-start",...n}){return(0,t.jsx)("div",{role:"group","data-slot":"input-group-addon","data-align":r,className:(0,a.cn)(l({align:r}),e),onClick:e=>{e.target.closest("button")||e.currentTarget.parentElement?.querySelector("input")?.focus()},...n})},"InputGroupButton",0,d,"InputGroupInput",0,u,"InputGroupText",0,function({className:e,...r}){return(0,t.jsx)("span",{className:(0,a.cn)("flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",e),...r})}])},552546,e=>{"use strict";var t=e.i(843476),r=e.i(131792);let a=(e,t)=>{let r=t.trim().toLowerCase();return!r||e.label.toLowerCase().includes(r)||(e.sublabel?.toLowerCase().includes(r)??!1)};e.s(["SearchSelect",0,function({options:e,value:n,onValueChange:s,placeholder:i="Select…",emptyText:l="No results",disabled:o=!1,className:d}){let u=e.find(e=>e.value===n)??null;return(0,t.jsxs)(r.Combobox,{items:e,value:u,onValueChange:e=>s(e?.value??""),isItemEqualToValue:(e,t)=>e.value===t.value,itemToStringLabel:e=>e.label,filter:a,disabled:o,children:[(0,t.jsx)(r.ComboboxInput,{placeholder:i,showClear:null!=n&&""!==n,className:`w-full ${d??""}`}),(0,t.jsxs)(r.ComboboxContent,{children:[(0,t.jsx)(r.ComboboxEmpty,{children:l}),(0,t.jsx)(r.ComboboxList,{children:e=>(0,t.jsx)(r.ComboboxItem,{value:e,children:(0,t.jsxs)("span",{className:"flex min-w-0 flex-col",children:[(0,t.jsx)("span",{className:"truncate",children:e.label}),null!=e.sublabel&&""!==e.sublabel&&(0,t.jsx)("span",{className:"truncate text-xs text-muted-foreground",children:e.sublabel})]})},e.value)})]})]})}])},751904,e=>{"use strict";var t=e.i(401361);e.s(["EditOutlined",()=>t.default])},263005,e=>{"use strict";var t=e.i(843476);e.s(["PageHeader",0,function({title:e,subtitle:r,icon:a,actions:n}){return(0,t.jsxs)("div",{className:"flex flex-wrap items-start justify-between gap-4",children:[(0,t.jsxs)("div",{className:"flex items-center gap-2.5",children:[null!=a&&(0,t.jsx)("span",{className:"flex flex-none items-center text-foreground",children:a}),(0,t.jsxs)("div",{className:"min-w-0",children:[(0,t.jsx)("h1",{className:"text-xl font-semibold tracking-tight text-foreground",children:e}),null!=r&&(0,t.jsx)("p",{className:"mt-0.5 text-sm text-muted-foreground",children:r})]})]}),null!=n&&(0,t.jsx)("div",{className:"flex items-center gap-2",children:n})]})}])},91979,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M909.1 209.3l-56.4 44.1C775.8 155.1 656.2 92 521.9 92 290 92 102.3 279.5 102 511.5 101.7 743.7 289.8 932 521.9 932c181.3 0 335.8-115 394.6-276.1 1.5-4.2-.7-8.9-4.9-10.3l-56.7-19.5a8 8 0 00-10.1 4.8c-1.8 5-3.8 10-5.9 14.9-17.3 41-42.1 77.8-73.7 109.4A344.77 344.77 0 01655.9 829c-42.3 17.9-87.4 27-133.8 27-46.5 0-91.5-9.1-133.8-27A341.5 341.5 0 01279 755.2a342.16 342.16 0 01-73.7-109.4c-17.9-42.4-27-87.4-27-133.9s9.1-91.5 27-133.9c17.3-41 42.1-77.8 73.7-109.4 31.6-31.6 68.4-56.4 109.3-73.8 42.3-17.9 87.4-27 133.8-27 46.5 0 91.5 9.1 133.8 27a341.5 341.5 0 01109.3 73.8c9.9 9.9 19.2 20.4 27.8 31.4l-60.2 47a8 8 0 003 14.1l175.6 43c5 1.2 9.9-2.6 9.9-7.7l.8-180.9c-.1-6.6-7.8-10.3-13-6.2z"}}]},name:"reload",theme:"outlined"};var n=e.i(9583),s=r.forwardRef(function(e,s){return r.createElement(n.default,(0,t.default)({},e,{ref:s,icon:a}))});e.s(["ReloadOutlined",0,s],91979)},987432,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M893.3 293.3L730.7 130.7c-7.5-7.5-16.7-13-26.7-16V112H144c-17.7 0-32 14.3-32 32v736c0 17.7 14.3 32 32 32h736c17.7 0 32-14.3 32-32V338.5c0-17-6.7-33.2-18.7-45.2zM384 184h256v104H384V184zm456 656H184V184h136v136c0 17.7 14.3 32 32 32h320c17.7 0 32-14.3 32-32V205.8l136 136V840zM512 442c-79.5 0-144 64.5-144 144s64.5 144 144 144 144-64.5 144-144-64.5-144-144-144zm0 224c-44.2 0-80-35.8-80-80s35.8-80 80-80 80 35.8 80 80-35.8 80-80 80z"}}]},name:"save",theme:"outlined"};var n=e.i(9583),s=r.forwardRef(function(e,s){return r.createElement(n.default,(0,t.default)({},e,{ref:s,icon:a}))});e.s(["SaveOutlined",0,s],987432)},564897,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M696 480H328c-4.4 0-8 3.6-8 8v48c0 4.4 3.6 8 8 8h368c4.4 0 8-3.6 8-8v-48c0-4.4-3.6-8-8-8z"}},{tag:"path",attrs:{d:"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"}}]},name:"minus-circle",theme:"outlined"};var n=e.i(9583),s=r.forwardRef(function(e,s){return r.createElement(n.default,(0,t.default)({},e,{ref:s,icon:a}))});e.s(["MinusCircleOutlined",0,s],564897)},822315,(e,t,r)=>{e.e,t.exports=function(){"use strict";var e="millisecond",t="second",r="minute",a="hour",n="week",s="month",i="quarter",l="year",o="date",d="Invalid Date",u=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,c=/\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,m=function(e,t,r){var a=String(e);return!a||a.length>=t?e:""+Array(t+1-a.length).join(r)+e},f="en",h={};h[f]={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:function(e){var t=["th","st","nd","rd"],r=e%100;return"["+e+(t[(r-20)%10]||t[r]||t[0])+"]"}};var p="$isDayjsObject",g=function(e){return e instanceof w||!(!e||!e[p])},x=function e(t,r,a){var n;if(!t)return f;if("string"==typeof t){var s=t.toLowerCase();h[s]&&(n=s),r&&(h[s]=r,n=s);var i=t.split("-");if(!n&&i.length>1)return e(i[0])}else{var l=t.name;h[l]=t,n=l}return!a&&n&&(f=n),n||!a&&f},b=function(e,t){if(g(e))return e.clone();var r="object"==typeof t?t:{};return r.date=e,r.args=arguments,new w(r)},v={s:m,z:function(e){var t=-e.utcOffset(),r=Math.abs(t);return(t<=0?"+":"-")+m(Math.floor(r/60),2,"0")+":"+m(r%60,2,"0")},m:function e(t,r){if(t.date(){"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let a={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M854.4 800.9c.2-.3.5-.6.7-.9C920.6 722.1 960 621.7 960 512s-39.4-210.1-104.8-288c-.2-.3-.5-.5-.7-.8-1.1-1.3-2.1-2.5-3.2-3.7-.4-.5-.8-.9-1.2-1.4l-4.1-4.7-.1-.1c-1.5-1.7-3.1-3.4-4.6-5.1l-.1-.1c-3.2-3.4-6.4-6.8-9.7-10.1l-.1-.1-4.8-4.8-.3-.3c-1.5-1.5-3-2.9-4.5-4.3-.5-.5-1-1-1.6-1.5-1-1-2-1.9-3-2.8-.3-.3-.7-.6-1-1C736.4 109.2 629.5 64 512 64s-224.4 45.2-304.3 119.2c-.3.3-.7.6-1 1-1 .9-2 1.9-3 2.9-.5.5-1 1-1.6 1.5-1.5 1.4-3 2.9-4.5 4.3l-.3.3-4.8 4.8-.1.1c-3.3 3.3-6.5 6.7-9.7 10.1l-.1.1c-1.6 1.7-3.1 3.4-4.6 5.1l-.1.1c-1.4 1.5-2.8 3.1-4.1 4.7-.4.5-.8.9-1.2 1.4-1.1 1.2-2.1 2.5-3.2 3.7-.2.3-.5.5-.7.8C103.4 301.9 64 402.3 64 512s39.4 210.1 104.8 288c.2.3.5.6.7.9l3.1 3.7c.4.5.8.9 1.2 1.4l4.1 4.7c0 .1.1.1.1.2 1.5 1.7 3 3.4 4.6 5l.1.1c3.2 3.4 6.4 6.8 9.6 10.1l.1.1c1.6 1.6 3.1 3.2 4.7 4.7l.3.3c3.3 3.3 6.7 6.5 10.1 9.6 80.1 74 187 119.2 304.5 119.2s224.4-45.2 304.3-119.2a300 300 0 0010-9.6l.3-.3c1.6-1.6 3.2-3.1 4.7-4.7l.1-.1c3.3-3.3 6.5-6.7 9.6-10.1l.1-.1c1.5-1.7 3.1-3.3 4.6-5 0-.1.1-.1.1-.2 1.4-1.5 2.8-3.1 4.1-4.7.4-.5.8-.9 1.2-1.4a99 99 0 003.3-3.7zm4.1-142.6c-13.8 32.6-32 62.8-54.2 90.2a444.07 444.07 0 00-81.5-55.9c11.6-46.9 18.8-98.4 20.7-152.6H887c-3 40.9-12.6 80.6-28.5 118.3zM887 484H743.5c-1.9-54.2-9.1-105.7-20.7-152.6 29.3-15.6 56.6-34.4 81.5-55.9A373.86 373.86 0 01887 484zM658.3 165.5c39.7 16.8 75.8 40 107.6 69.2a394.72 394.72 0 01-59.4 41.8c-15.7-45-35.8-84.1-59.2-115.4 3.7 1.4 7.4 2.9 11 4.4zm-90.6 700.6c-9.2 7.2-18.4 12.7-27.7 16.4V697a389.1 389.1 0 01115.7 26.2c-8.3 24.6-17.9 47.3-29 67.8-17.4 32.4-37.8 58.3-59 75.1zm59-633.1c11 20.6 20.7 43.3 29 67.8A389.1 389.1 0 01540 327V141.6c9.2 3.7 18.5 9.1 27.7 16.4 21.2 16.7 41.6 42.6 59 75zM540 640.9V540h147.5c-1.6 44.2-7.1 87.1-16.3 127.8l-.3 1.2A445.02 445.02 0 00540 640.9zm0-156.9V383.1c45.8-2.8 89.8-12.5 130.9-28.1l.3 1.2c9.2 40.7 14.7 83.5 16.3 127.8H540zm-56 56v100.9c-45.8 2.8-89.8 12.5-130.9 28.1l-.3-1.2c-9.2-40.7-14.7-83.5-16.3-127.8H484zm-147.5-56c1.6-44.2 7.1-87.1 16.3-127.8l.3-1.2c41.1 15.6 85 25.3 130.9 28.1V484H336.5zM484 697v185.4c-9.2-3.7-18.5-9.1-27.7-16.4-21.2-16.7-41.7-42.7-59.1-75.1-11-20.6-20.7-43.3-29-67.8 37.2-14.6 75.9-23.3 115.8-26.1zm0-370a389.1 389.1 0 01-115.7-26.2c8.3-24.6 17.9-47.3 29-67.8 17.4-32.4 37.8-58.4 59.1-75.1 9.2-7.2 18.4-12.7 27.7-16.4V327zM365.7 165.5c3.7-1.5 7.3-3 11-4.4-23.4 31.3-43.5 70.4-59.2 115.4-21-12-40.9-26-59.4-41.8 31.8-29.2 67.9-52.4 107.6-69.2zM165.5 365.7c13.8-32.6 32-62.8 54.2-90.2 24.9 21.5 52.2 40.3 81.5 55.9-11.6 46.9-18.8 98.4-20.7 152.6H137c3-40.9 12.6-80.6 28.5-118.3zM137 540h143.5c1.9 54.2 9.1 105.7 20.7 152.6a444.07 444.07 0 00-81.5 55.9A373.86 373.86 0 01137 540zm228.7 318.5c-39.7-16.8-75.8-40-107.6-69.2 18.5-15.8 38.4-29.7 59.4-41.8 15.7 45 35.8 84.1 59.2 115.4-3.7-1.4-7.4-2.9-11-4.4zm292.6 0c-3.7 1.5-7.3 3-11 4.4 23.4-31.3 43.5-70.4 59.2-115.4 21 12 40.9 26 59.4 41.8a373.81 373.81 0 01-107.6 69.2z"}}]},name:"global",theme:"outlined"};var n=e.i(9583),s=r.forwardRef(function(e,s){return r.createElement(n.default,(0,t.default)({},e,{ref:s,icon:a}))});e.s(["GlobalOutlined",0,s],160818)},728889,e=>{"use strict";var t=e.i(290571),r=e.i(271645),a=e.i(829087),n=e.i(480731),s=e.i(444755),i=e.i(673706),l=e.i(95779);let o={xs:{paddingX:"px-1.5",paddingY:"py-1.5"},sm:{paddingX:"px-1.5",paddingY:"py-1.5"},md:{paddingX:"px-2",paddingY:"py-2"},lg:{paddingX:"px-2",paddingY:"py-2"},xl:{paddingX:"px-2.5",paddingY:"py-2.5"}},d={xs:{height:"h-3",width:"w-3"},sm:{height:"h-5",width:"w-5"},md:{height:"h-5",width:"w-5"},lg:{height:"h-7",width:"w-7"},xl:{height:"h-9",width:"w-9"}},u={simple:{rounded:"",border:"",ring:"",shadow:""},light:{rounded:"rounded-tremor-default",border:"",ring:"",shadow:""},shadow:{rounded:"rounded-tremor-default",border:"border",ring:"",shadow:"shadow-tremor-card dark:shadow-dark-tremor-card"},solid:{rounded:"rounded-tremor-default",border:"border-2",ring:"ring-1",shadow:""},outlined:{rounded:"rounded-tremor-default",border:"border",ring:"ring-2",shadow:""}},c=(0,i.makeClassName)("Icon"),m=r.default.forwardRef((e,m)=>{let{icon:f,variant:h="simple",tooltip:p,size:g=n.Sizes.SM,color:x,className:b}=e,v=(0,t.__rest)(e,["icon","variant","tooltip","size","color","className"]),w=((e,t)=>{switch(e){case"simple":return{textColor:t?(0,i.getColorClassNames)(t,l.colorPalette.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:"",borderColor:"",ringColor:""};case"light":return{textColor:t?(0,i.getColorClassNames)(t,l.colorPalette.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,s.tremorTwMerge)((0,i.getColorClassNames)(t,l.colorPalette.background).bgColor,"bg-opacity-20"):"bg-tremor-brand-muted dark:bg-dark-tremor-brand-muted",borderColor:"",ringColor:""};case"shadow":return{textColor:t?(0,i.getColorClassNames)(t,l.colorPalette.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,s.tremorTwMerge)((0,i.getColorClassNames)(t,l.colorPalette.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:"border-tremor-border dark:border-dark-tremor-border",ringColor:""};case"solid":return{textColor:t?(0,i.getColorClassNames)(t,l.colorPalette.text).textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",bgColor:t?(0,s.tremorTwMerge)((0,i.getColorClassNames)(t,l.colorPalette.background).bgColor,"bg-opacity-20"):"bg-tremor-brand dark:bg-dark-tremor-brand",borderColor:"border-tremor-brand-inverted dark:border-dark-tremor-brand-inverted",ringColor:"ring-tremor-ring dark:ring-dark-tremor-ring"};case"outlined":return{textColor:t?(0,i.getColorClassNames)(t,l.colorPalette.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,s.tremorTwMerge)((0,i.getColorClassNames)(t,l.colorPalette.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:t?(0,i.getColorClassNames)(t,l.colorPalette.ring).borderColor:"border-tremor-brand-subtle dark:border-dark-tremor-brand-subtle",ringColor:t?(0,s.tremorTwMerge)((0,i.getColorClassNames)(t,l.colorPalette.ring).ringColor,"ring-opacity-40"):"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"}}})(h,x),{tooltipProps:y,getReferenceProps:C}=(0,a.useTooltip)();return r.default.createElement("span",Object.assign({ref:(0,i.mergeRefs)([m,y.refs.setReference]),className:(0,s.tremorTwMerge)(c("root"),"inline-flex shrink-0 items-center justify-center",w.bgColor,w.textColor,w.borderColor,w.ringColor,u[h].rounded,u[h].border,u[h].shadow,u[h].ring,o[g].paddingX,o[g].paddingY,b)},C,v),r.default.createElement(a.default,Object.assign({text:p},y)),r.default.createElement(f,{className:(0,s.tremorTwMerge)(c("icon"),"shrink-0",d[g].height,d[g].width)}))});m.displayName="Icon",e.s(["default",0,m],728889)},752978,e=>{"use strict";var t=e.i(728889);e.s(["Icon",()=>t.default])},591935,e=>{"use strict";var t=e.i(271645);let r=t.forwardRef(function(e,r){return t.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),t.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"}))});e.s(["PencilAltIcon",0,r],591935)},122577,e=>{"use strict";var t=e.i(271645);let r=t.forwardRef(function(e,r){return t.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),t.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"}),t.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M21 12a9 9 0 11-18 0 9 9 0 0118 0z"}))});e.s(["PlayIcon",0,r],122577)},360820,e=>{"use strict";var t=e.i(271645);let r=t.forwardRef(function(e,r){return t.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),t.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M5 15l7-7 7 7"}))});e.s(["ChevronUpIcon",0,r],360820)},434626,e=>{"use strict";var t=e.i(271645);let r=t.forwardRef(function(e,r){return t.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),t.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"}))});e.s(["ExternalLinkIcon",0,r],434626)},902555,e=>{"use strict";var t=e.i(843476),r=e.i(591935),a=e.i(122577),n=e.i(278587),s=e.i(68155),i=e.i(360820),l=e.i(871943),o=e.i(434626),d=e.i(271645);let u=d.forwardRef(function(e,t){return d.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:t},e),d.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"}))});var c=e.i(592968),m=e.i(115504),f=e.i(752978);function h({icon:e,onClick:r,className:a,disabled:n,dataTestId:s}){return n?(0,t.jsx)(f.Icon,{icon:e,size:"sm",className:"opacity-50 cursor-not-allowed","data-testid":s}):(0,t.jsx)(f.Icon,{icon:e,size:"sm",onClick:r,className:(0,m.cx)("cursor-pointer",a),"data-testid":s})}let p={Edit:{icon:r.PencilAltIcon,className:"hover:text-blue-600"},Delete:{icon:s.TrashIcon,className:"hover:text-red-600"},Test:{icon:a.PlayIcon,className:"hover:text-blue-600"},Regenerate:{icon:n.RefreshIcon,className:"hover:text-green-600"},Up:{icon:i.ChevronUpIcon,className:"hover:text-blue-600"},Down:{icon:l.ChevronDownIcon,className:"hover:text-blue-600"},Open:{icon:o.ExternalLinkIcon,className:"hover:text-green-600"},Copy:{icon:u,className:"hover:text-blue-600"}};e.s(["default",0,function({onClick:e,tooltipText:r,disabled:a=!1,disabledTooltipText:n,dataTestId:s,variant:i}){let{icon:l,className:o}=p[i];return(0,t.jsx)(c.Tooltip,{title:a?n:r,children:(0,t.jsx)("span",{children:(0,t.jsx)(h,{icon:l,onClick:e,className:o,disabled:a,dataTestId:s})})})}],902555)},738014,e=>{"use strict";var t=e.i(135214),r=e.i(602869),a=e.i(266027);let n=(0,e.i(243652).createQueryKeys)("users");e.s(["useCurrentUser",0,()=>{let{accessToken:e,userId:s}=(0,t.default)();return(0,a.useQuery)({queryKey:n.detail(s),queryFn:async()=>await (0,r.userGetInfoV2)(e),enabled:!!(e&&s)})}])},162386,e=>{"use strict";var t=e.i(843476),r=e.i(625901),a=e.i(109799),n=e.i(785242),s=e.i(738014),i=e.i(199133),l=e.i(981339),o=e.i(592968);let d={label:"All Proxy Models",value:"all-proxy-models"},u={label:"No Default Models",value:"no-default-models"},c=[d,u],m={user:({allProxyModels:e,userModels:t,options:r})=>t&&r?.includeUserModels?t:[],team:({allProxyModels:e,selectedOrganization:t,userModels:r})=>t?t.models.includes(d.value)||0===t.models.length?e:e.filter(e=>t.models.includes(e)):e??[],organization:({allProxyModels:e})=>e,global:({allProxyModels:e})=>e};e.s(["MODEL_SENTINEL_OPTIONS",0,c,"ModelSelect",0,e=>{let{teamID:f,organizationID:h,options:p,context:g,dataTestId:x,value:b=[],onChange:v,style:w}=e,{includeUserModels:y,showAllTeamModelsOption:C,showAllProxyModelsOverride:j,includeSpecialOptions:k}=p||{},{data:M,isLoading:N}=(0,r.useAllProxyModels)(),{data:S,isLoading:$}=(0,n.useTeam)(f),{data:O,isLoading:_}=(0,a.useOrganization)(h),{data:I,isLoading:z}=(0,s.useCurrentUser)(),T=e=>c.some(t=>t.value===e),D=b.some(T),E=O?.models.includes(d.value)||O?.models.length===0;if(N||$||_||z)return(0,t.jsx)(l.Skeleton.Input,{active:!0,block:!0});let{wildcard:L,regular:A}=(e=>{let t=[],r=[];for(let a of e)a.endsWith("/*")?t.push(a):r.push(a);return{wildcard:t,regular:r}})(((e,t,r)=>{let a=Array.from(new Map(e.map(e=>[e.id,e])).values()).map(e=>e.id);if(t.options?.showAllProxyModelsOverride)return a;let n=m[t.context];return n?n({allProxyModels:a,...r,options:t.options}):[]})(M?.data??[],e,{selectedTeam:S,selectedOrganization:O,userModels:I?.models}));return(0,t.jsx)(i.Select,{"data-testid":x,value:b,onChange:e=>{let t=e.filter(T);v(t.length>0?[t[t.length-1]]:e)},style:w,options:[...k?[{label:(0,t.jsx)("span",{children:"Special Options"}),title:"Special Options",options:[...j||E&&k||"global"===g?[{label:(0,t.jsx)("span",{children:"All Proxy Models"}),value:d.value,disabled:b.length>0&&b.some(e=>T(e)&&e!==d.value),key:d.value}]:[],{label:(0,t.jsx)("span",{children:"No Default Models"}),value:u.value,disabled:b.length>0&&b.some(e=>T(e)&&e!==u.value),key:u.value}]}]:[],...L.length>0?[{label:(0,t.jsx)("span",{children:"Wildcard Options"}),title:"Wildcard Options",options:L.map(e=>{let r=e.replace("/*",""),a=r.charAt(0).toUpperCase()+r.slice(1);return{label:(0,t.jsx)("span",{children:`All ${a} models`}),value:e,disabled:D}})}]:[],{label:(0,t.jsx)("span",{children:"Models"}),title:"Models",options:A.map(e=>({label:(0,t.jsx)("span",{children:e}),value:e,disabled:D}))}],mode:"multiple",placeholder:"Select Models",allowClear:!0,maxTagCount:"responsive",maxTagPlaceholder:e=>(0,t.jsx)(o.Tooltip,{styles:{root:{pointerEvents:"none"}},title:e.map(({value:e})=>e).join(", "),children:(0,t.jsxs)("span",{children:["+",e.length," more"]})})})}],162386)},907308,276173,e=>{"use strict";var t=e.i(843476),r=e.i(271645),a=e.i(212931),n=e.i(808613),s=e.i(464571),i=e.i(199133),l=e.i(592968),o=e.i(213205),d=e.i(343488),u=e.i(602869),c=e.i(741466);e.s(["default",0,({isVisible:e,onCancel:m,onSubmit:f,accessToken:h,title:p="Add Team Member",roles:g=[{label:"admin",value:"admin",description:"Admin role. Can create team keys, add members, and manage settings."},{label:"user",value:"user",description:"User role. Can view team info, but not manage it."}],defaultRole:x="user",teamId:b})=>{let[v]=n.Form.useForm(),[w,y]=(0,r.useState)([]),[C,j]=(0,r.useState)(!1),[k,M]=(0,r.useState)("user_email"),[N,S]=(0,r.useState)(!1),$=async(e,t)=>{if(!e)return void y([]);j(!0);try{let r=new URLSearchParams;if(r.append(t,e),b&&r.append("team_id",b),null==h)return;let a=(await (0,u.userFilterUICall)(h,r)).map(e=>({label:"user_email"===t?`${e.user_email}`:`${e.user_id}`,value:"user_email"===t?e.user_email:e.user_id,user:e}));y(a)}catch(e){console.error("Error fetching users:",e)}finally{j(!1)}},O=(0,d.useDebouncedCallback)((e,t)=>$(e,t),{wait:c.DEBOUNCE_WAIT_MS}),_=(e,t)=>{M(t),O(e,t)},I=(e,t)=>{let r=t.user;v.setFieldsValue({user_email:r.user_email,user_id:r.user_id,role:v.getFieldValue("role")})},z=async e=>{S(!0);try{await f(e)}finally{S(!1)}};return(0,t.jsx)(a.Modal,{title:p,open:e,onCancel:()=>{v.resetFields(),y([]),m()},footer:null,width:800,maskClosable:!N,children:(0,t.jsxs)(n.Form,{form:v,onFinish:z,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",initialValues:{role:x},children:[(0,t.jsx)(n.Form.Item,{label:"Email",name:"user_email",className:"mb-4",children:(0,t.jsx)(i.Select,{showSearch:!0,className:"w-full",placeholder:"Search by email",filterOption:!1,onSearch:e=>_(e,"user_email"),onSelect:(e,t)=>I(e,t),options:"user_email"===k?w:[],loading:C,allowClear:!0,"data-testid":"member-email-search"})}),(0,t.jsx)("div",{className:"text-center mb-4",children:"OR"}),(0,t.jsx)(n.Form.Item,{label:"User ID",name:"user_id",className:"mb-4",children:(0,t.jsx)(i.Select,{showSearch:!0,className:"w-full",placeholder:"Search by user ID",filterOption:!1,onSearch:e=>_(e,"user_id"),onSelect:(e,t)=>I(e,t),options:"user_id"===k?w:[],loading:C,allowClear:!0})}),(0,t.jsx)(n.Form.Item,{label:"Member Role",name:"role",className:"mb-4",children:(0,t.jsx)(i.Select,{defaultValue:x,children:g.map(e=>(0,t.jsx)(i.Select.Option,{value:e.value,children:(0,t.jsxs)(l.Tooltip,{title:e.description,children:[(0,t.jsx)("span",{className:"font-medium",children:e.label}),(0,t.jsxs)("span",{className:"ml-2 text-gray-500 text-sm",children:["- ",e.description]})]})},e.value))})}),(0,t.jsx)("div",{className:"text-right mt-4",children:(0,t.jsx)(s.Button,{type:"primary",htmlType:"submit",icon:(0,t.jsx)(o.UserAddOutlined,{}),loading:N,children:N?"Adding...":"Add Member"})})]})})}],907308);var m=e.i(599724),f=e.i(779241),h=e.i(435451),p=e.i(860585);e.s(["default",0,({visible:e,onCancel:l,onSubmit:o,initialData:d,mode:u,config:c})=>{let g,[x]=n.Form.useForm(),[b,v]=(0,r.useState)(!1);(0,r.useEffect)(()=>{if(e)if("edit"===u&&d){let e={...d,role:d.role||c.defaultRole,max_budget_in_team:d.max_budget_in_team||null,tpm_limit:d.tpm_limit||null,rpm_limit:d.rpm_limit||null,budget_duration:d.budget_duration||null,allowed_models:d.allowed_models||[]};x.setFieldsValue(e)}else x.resetFields(),x.setFieldsValue({role:c.defaultRole||c.roleOptions[0]?.value})},[e,d,u,x,c.defaultRole,c.roleOptions]);let w=async e=>{try{v(!0);let t=Object.entries(e).reduce((e,[t,r])=>{if("string"==typeof r){let a=r.trim();return""===a&&("max_budget_in_team"===t||"tpm_limit"===t||"rpm_limit"===t)?{...e,[t]:null}:{...e,[t]:a}}return{...e,[t]:r}},{});await Promise.resolve(o(t)),x.resetFields()}catch(e){console.error("Form submission error:",e)}finally{v(!1)}};return(0,t.jsx)(a.Modal,{title:c.title||("add"===u?"Add Member":"Edit Member"),open:e,width:1e3,footer:null,onCancel:l,children:(0,t.jsxs)(n.Form,{form:x,onFinish:w,labelCol:{span:8},wrapperCol:{span:16},labelAlign:"left",children:[c.showEmail&&(0,t.jsx)(n.Form.Item,{label:"Email",name:"user_email",className:"mb-4",rules:[{type:"email",message:"Please enter a valid email!"}],children:(0,t.jsx)(f.TextInput,{placeholder:"user@example.com"})}),c.showEmail&&c.showUserId&&(0,t.jsx)("div",{className:"text-center mb-4",children:(0,t.jsx)(m.Text,{children:"OR"})}),c.showUserId&&(0,t.jsx)(n.Form.Item,{label:"User ID",name:"user_id",className:"mb-4",children:(0,t.jsx)(f.TextInput,{placeholder:"user_123"})}),(0,t.jsx)(n.Form.Item,{label:(0,t.jsxs)("div",{className:"flex items-center gap-2",children:[(0,t.jsx)("span",{children:"Role"}),"edit"===u&&d&&(0,t.jsxs)("span",{className:"text-gray-500 text-sm",children:["(Current: ",(g=d.role,c.roleOptions.find(e=>e.value===g)?.label||g),")"]})]}),name:"role",className:"mb-4",rules:[{required:!0,message:"Please select a role!"}],children:(0,t.jsx)(i.Select,{children:"edit"===u&&d?[...c.roleOptions.filter(e=>e.value===d.role),...c.roleOptions.filter(e=>e.value!==d.role)].map(e=>(0,t.jsx)(i.Select.Option,{value:e.value,children:e.label},e.value)):c.roleOptions.map(e=>(0,t.jsx)(i.Select.Option,{value:e.value,children:e.label},e.value))})}),c.additionalFields?.map(e=>(0,t.jsx)(n.Form.Item,{label:e.label,name:e.name,className:"mb-4",rules:e.rules,children:(e=>{switch(e.type){case"input":return(0,t.jsx)(f.TextInput,{placeholder:e.placeholder});case"numerical":return(0,t.jsx)(h.default,{step:e.step||1,min:e.min||0,style:{width:"100%"},placeholder:e.placeholder||"Enter a numerical value"});case"select":return(0,t.jsx)(i.Select,{children:e.options?.map(e=>(0,t.jsx)(i.Select.Option,{value:e.value,children:e.label},e.value))});case"multi-select":return(0,t.jsx)(i.Select,{mode:"multiple",placeholder:e.placeholder||"Select options",options:e.options,allowClear:!0});case"budget-duration":return(0,t.jsx)(p.default,{});default:return null}})(e)},e.name)),(0,t.jsxs)("div",{className:"text-right mt-6",children:[(0,t.jsx)(s.Button,{onClick:l,className:"mr-2",disabled:b,children:"Cancel"}),(0,t.jsx)(s.Button,{type:"default",htmlType:"submit",loading:b,children:"add"===u?b?"Adding...":"Add Member":b?"Saving...":"Save Changes"})]})]})})}],276173)},294612,e=>{"use strict";var t=e.i(843476),r=e.i(100486),a=e.i(827252),n=e.i(213205),s=e.i(771674),i=e.i(464571),l=e.i(770914),o=e.i(291542),d=e.i(262218),u=e.i(592968),c=e.i(898586),m=e.i(902555);let{Text:f}=c.Typography;e.s(["default",0,function({members:e,canEdit:c,onEdit:h,onDelete:p,onAddMember:g,roleColumnTitle:x="Role",roleTooltip:b,extraColumns:v=[],showDeleteForMember:w,emptyText:y}){let C=[{title:"User Email",dataIndex:"user_email",key:"user_email",render:e=>(0,t.jsx)(f,{children:e||"-"})},{title:"User ID",dataIndex:"user_id",key:"user_id",render:e=>"default_user_id"===e?(0,t.jsx)(d.Tag,{color:"blue",children:"Default Proxy Admin"}):(0,t.jsx)(f,{children:e||"-"})},{title:b?(0,t.jsxs)(l.Space,{direction:"horizontal",children:[x,(0,t.jsx)(u.Tooltip,{title:b,children:(0,t.jsx)(a.InfoCircleOutlined,{})})]}):x,dataIndex:"role",key:"role",render:e=>(0,t.jsxs)(l.Space,{children:[e?.toLowerCase()==="admin"||e?.toLowerCase()==="org_admin"?(0,t.jsx)(r.CrownOutlined,{}):(0,t.jsx)(s.UserOutlined,{}),(0,t.jsx)(f,{style:{textTransform:"capitalize"},children:e||"-"})]})},...v,{title:"Actions",key:"actions",fixed:"right",width:120,render:(e,r)=>c?(0,t.jsxs)(l.Space,{children:[(0,t.jsx)(m.default,{variant:"Edit",tooltipText:"Edit member",dataTestId:"edit-member",onClick:()=>h(r)}),(!w||w(r))&&(0,t.jsx)(m.default,{variant:"Delete",tooltipText:"Delete member",dataTestId:"delete-member",onClick:()=>p(r)})]}):null}];return(0,t.jsxs)(l.Space,{direction:"vertical",style:{width:"100%"},children:[(0,t.jsxs)("span",{className:"inline-flex text-sm text-gray-700",children:[e.length," Member",1!==e.length?"s":""]}),(0,t.jsx)(o.Table,{columns:C,dataSource:e,rowKey:e=>e.user_id??e.user_email??JSON.stringify(e),pagination:!1,size:"small",scroll:{x:"max-content"},locale:y?{emptyText:y}:void 0}),g&&c&&(0,t.jsx)(i.Button,{icon:(0,t.jsx)(n.UserAddOutlined,{}),type:"primary",onClick:g,children:"Add Member"})]})}])},113625,e=>{"use strict";let t=(0,e.i(475254).default)("layers",[["path",{d:"M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83z",key:"zw3jo"}],["path",{d:"M2 12a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 12",key:"1wduqc"}],["path",{d:"M2 17a1 1 0 0 0 .58.91l8.6 3.91a2 2 0 0 0 1.65 0l8.58-3.9A1 1 0 0 0 22 17",key:"kqbvx6"}]]);e.s(["default",0,t])},852008,e=>{"use strict";var t=e.i(113625);e.s(["Layers",()=>t.default])},372943,e=>{"use strict";e.i(247167);var t=e.i(8211),r=e.i(271645),a=e.i(343794),n=e.i(529681),s=e.i(242064),i=e.i(704914),l=e.i(876556),o=e.i(290224),d=e.i(251224),u=function(e,t){var r={};for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&0>t.indexOf(a)&&(r[a]=e[a]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var n=0,a=Object.getOwnPropertySymbols(e);nt.indexOf(a[n])&&Object.prototype.propertyIsEnumerable.call(e,a[n])&&(r[a[n]]=e[a[n]]);return r};function c({suffixCls:e,tagName:t,displayName:a}){return a=>r.forwardRef((n,s)=>r.createElement(a,Object.assign({ref:s,suffixCls:e,tagName:t},n)))}let m=r.forwardRef((e,t)=>{let{prefixCls:n,suffixCls:i,className:l,tagName:o}=e,c=u(e,["prefixCls","suffixCls","className","tagName"]),{getPrefixCls:m}=r.useContext(s.ConfigContext),f=m("layout",n),[h,p,g]=(0,d.default)(f),x=i?`${f}-${i}`:f;return h(r.createElement(o,Object.assign({className:(0,a.default)(n||x,l,p,g),ref:t},c)))}),f=r.forwardRef((e,c)=>{let{direction:m}=r.useContext(s.ConfigContext),[f,h]=r.useState([]),{prefixCls:p,className:g,rootClassName:x,children:b,hasSider:v,tagName:w,style:y}=e,C=u(e,["prefixCls","className","rootClassName","children","hasSider","tagName","style"]),j=(0,n.default)(C,["suffixCls"]),{getPrefixCls:k,className:M,style:N}=(0,s.useComponentConfig)("layout"),S=k("layout",p),$="boolean"==typeof v?v:!!f.length||(0,l.default)(b).some(e=>e.type===o.default),[O,_,I]=(0,d.default)(S),z=(0,a.default)(S,{[`${S}-has-sider`]:$,[`${S}-rtl`]:"rtl"===m},M,g,x,_,I),T=r.useMemo(()=>({siderHook:{addSider:e=>{h(r=>[].concat((0,t.default)(r),[e]))},removeSider:e=>{h(t=>t.filter(t=>t!==e))}}}),[]);return O(r.createElement(i.LayoutContext.Provider,{value:T},r.createElement(w,Object.assign({ref:c,className:z,style:Object.assign(Object.assign({},N),y)},j),b)))}),h=c({tagName:"div",displayName:"Layout"})(f),p=c({suffixCls:"header",tagName:"header",displayName:"Header"})(m),g=c({suffixCls:"footer",tagName:"footer",displayName:"Footer"})(m),x=c({suffixCls:"content",tagName:"main",displayName:"Content"})(m);h.Header=p,h.Footer=g,h.Content=x,h.Sider=o.default,h._InternalSiderContext=o.SiderContext,e.s(["Layout",0,h],372943)}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/05287rwl48hh2.js b/litellm/proxy/_experimental/out/_next/static/chunks/05287rwl48hh2.js new file mode 100644 index 00000000000..dd0196da59e --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/05287rwl48hh2.js @@ -0,0 +1,13 @@ +(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,244451,e=>{"use strict";let t;e.i(247167);var i=e.i(271645),n=e.i(343794),o=e.i(242064),a=e.i(763731),l=e.i(174428);let r=80*Math.PI,c=e=>{let{dotClassName:t,style:o,hasCircleCls:a}=e;return i.createElement("circle",{className:(0,n.default)(`${t}-circle`,{[`${t}-circle-bg`]:a}),r:40,cx:50,cy:50,strokeWidth:20,style:o})},s=({percent:e,prefixCls:t})=>{let o=`${t}-dot`,a=`${o}-holder`,s=`${a}-hidden`,[d,u]=i.useState(!1);(0,l.default)(()=>{0!==e&&u(!0)},[0!==e]);let m=Math.max(Math.min(e,100),0);if(!d)return null;let p={strokeDashoffset:`${r/4}`,strokeDasharray:`${r*m/100} ${r*(100-m)/100}`};return i.createElement("span",{className:(0,n.default)(a,`${o}-progress`,m<=0&&s)},i.createElement("svg",{viewBox:"0 0 100 100",role:"progressbar","aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":m},i.createElement(c,{dotClassName:o,hasCircleCls:!0}),i.createElement(c,{dotClassName:o,style:p})))};function d(e){let{prefixCls:t,percent:o=0}=e,a=`${t}-dot`,l=`${a}-holder`,r=`${l}-hidden`;return i.createElement(i.Fragment,null,i.createElement("span",{className:(0,n.default)(l,o>0&&r)},i.createElement("span",{className:(0,n.default)(a,`${t}-dot-spin`)},[1,2,3,4].map(e=>i.createElement("i",{className:`${t}-dot-item`,key:e})))),i.createElement(s,{prefixCls:t,percent:o}))}function u(e){var t;let{prefixCls:o,indicator:l,percent:r}=e,c=`${o}-dot`;return l&&i.isValidElement(l)?(0,a.cloneElement)(l,{className:(0,n.default)(null==(t=l.props)?void 0:t.className,c),percent:r}):i.createElement(d,{prefixCls:o,percent:r})}e.i(296059);var m=e.i(694758),p=e.i(183293),g=e.i(246422),f=e.i(838378);let b=new m.Keyframes("antSpinMove",{to:{opacity:1}}),h=new m.Keyframes("antRotate",{to:{transform:"rotate(405deg)"}}),v=(0,g.genStyleHooks)("Spin",e=>(e=>{let{componentCls:t,calc:i}=e;return{[t]:Object.assign(Object.assign({},(0,p.resetComponent)(e)),{position:"absolute",display:"none",color:e.colorPrimary,fontSize:0,textAlign:"center",verticalAlign:"middle",opacity:0,transition:`transform ${e.motionDurationSlow} ${e.motionEaseInOutCirc}`,"&-spinning":{position:"relative",display:"inline-block",opacity:1},[`${t}-text`]:{fontSize:e.fontSize,paddingTop:i(i(e.dotSize).sub(e.fontSize)).div(2).add(2).equal()},"&-fullscreen":{position:"fixed",width:"100vw",height:"100vh",backgroundColor:e.colorBgMask,zIndex:e.zIndexPopupBase,inset:0,display:"flex",alignItems:"center",flexDirection:"column",justifyContent:"center",opacity:0,visibility:"hidden",transition:`all ${e.motionDurationMid}`,"&-show":{opacity:1,visibility:"visible"},[t]:{[`${t}-dot-holder`]:{color:e.colorWhite},[`${t}-text`]:{color:e.colorTextLightSolid}}},"&-nested-loading":{position:"relative",[`> div > ${t}`]:{position:"absolute",top:0,insetInlineStart:0,zIndex:4,display:"block",width:"100%",height:"100%",maxHeight:e.contentHeight,[`${t}-dot`]:{position:"absolute",top:"50%",insetInlineStart:"50%",margin:i(e.dotSize).mul(-1).div(2).equal()},[`${t}-text`]:{position:"absolute",top:"50%",width:"100%",textShadow:`0 1px 2px ${e.colorBgContainer}`},[`&${t}-show-text ${t}-dot`]:{marginTop:i(e.dotSize).div(2).mul(-1).sub(10).equal()},"&-sm":{[`${t}-dot`]:{margin:i(e.dotSizeSM).mul(-1).div(2).equal()},[`${t}-text`]:{paddingTop:i(i(e.dotSizeSM).sub(e.fontSize)).div(2).add(2).equal()},[`&${t}-show-text ${t}-dot`]:{marginTop:i(e.dotSizeSM).div(2).mul(-1).sub(10).equal()}},"&-lg":{[`${t}-dot`]:{margin:i(e.dotSizeLG).mul(-1).div(2).equal()},[`${t}-text`]:{paddingTop:i(i(e.dotSizeLG).sub(e.fontSize)).div(2).add(2).equal()},[`&${t}-show-text ${t}-dot`]:{marginTop:i(e.dotSizeLG).div(2).mul(-1).sub(10).equal()}}},[`${t}-container`]:{position:"relative",transition:`opacity ${e.motionDurationSlow}`,"&::after":{position:"absolute",top:0,insetInlineEnd:0,bottom:0,insetInlineStart:0,zIndex:10,width:"100%",height:"100%",background:e.colorBgContainer,opacity:0,transition:`all ${e.motionDurationSlow}`,content:'""',pointerEvents:"none"}},[`${t}-blur`]:{clear:"both",opacity:.5,userSelect:"none",pointerEvents:"none","&::after":{opacity:.4,pointerEvents:"auto"}}},"&-tip":{color:e.spinDotDefault},[`${t}-dot-holder`]:{width:"1em",height:"1em",fontSize:e.dotSize,display:"inline-block",transition:`transform ${e.motionDurationSlow} ease, opacity ${e.motionDurationSlow} ease`,transformOrigin:"50% 50%",lineHeight:1,color:e.colorPrimary,"&-hidden":{transform:"scale(0.3)",opacity:0}},[`${t}-dot-progress`]:{position:"absolute",inset:0},[`${t}-dot`]:{position:"relative",display:"inline-block",fontSize:e.dotSize,width:"1em",height:"1em","&-item":{position:"absolute",display:"block",width:i(e.dotSize).sub(i(e.marginXXS).div(2)).div(2).equal(),height:i(e.dotSize).sub(i(e.marginXXS).div(2)).div(2).equal(),background:"currentColor",borderRadius:"100%",transform:"scale(0.75)",transformOrigin:"50% 50%",opacity:.3,animationName:b,animationDuration:"1s",animationIterationCount:"infinite",animationTimingFunction:"linear",animationDirection:"alternate","&:nth-child(1)":{top:0,insetInlineStart:0,animationDelay:"0s"},"&:nth-child(2)":{top:0,insetInlineEnd:0,animationDelay:"0.4s"},"&:nth-child(3)":{insetInlineEnd:0,bottom:0,animationDelay:"0.8s"},"&:nth-child(4)":{bottom:0,insetInlineStart:0,animationDelay:"1.2s"}},"&-spin":{transform:"rotate(45deg)",animationName:h,animationDuration:"1.2s",animationIterationCount:"infinite",animationTimingFunction:"linear"},"&-circle":{strokeLinecap:"round",transition:["stroke-dashoffset","stroke-dasharray","stroke","stroke-width","opacity"].map(t=>`${t} ${e.motionDurationSlow} ease`).join(","),fillOpacity:0,stroke:"currentcolor"},"&-circle-bg":{stroke:e.colorFillSecondary}},[`&-sm ${t}-dot`]:{"&, &-holder":{fontSize:e.dotSizeSM}},[`&-sm ${t}-dot-holder`]:{i:{width:i(i(e.dotSizeSM).sub(i(e.marginXXS).div(2))).div(2).equal(),height:i(i(e.dotSizeSM).sub(i(e.marginXXS).div(2))).div(2).equal()}},[`&-lg ${t}-dot`]:{"&, &-holder":{fontSize:e.dotSizeLG}},[`&-lg ${t}-dot-holder`]:{i:{width:i(i(e.dotSizeLG).sub(e.marginXXS)).div(2).equal(),height:i(i(e.dotSizeLG).sub(e.marginXXS)).div(2).equal()}},[`&${t}-show-text ${t}-text`]:{display:"block"}})}})((0,f.mergeToken)(e,{spinDotDefault:e.colorTextDescription})),e=>{let{controlHeightLG:t,controlHeight:i}=e;return{contentHeight:400,dotSize:t/2,dotSizeSM:.35*t,dotSizeLG:i}}),S=[[30,.05],[70,.03],[96,.01]];var $=function(e,t){var i={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&0>t.indexOf(n)&&(i[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,n=Object.getOwnPropertySymbols(e);ot.indexOf(n[o])&&Object.prototype.propertyIsEnumerable.call(e,n[o])&&(i[n[o]]=e[n[o]]);return i};let y=e=>{var a;let{prefixCls:l,spinning:r=!0,delay:c=0,className:s,rootClassName:d,size:m="default",tip:p,wrapperClassName:g,style:f,children:b,fullscreen:h=!1,indicator:y,percent:C}=e,k=$(e,["prefixCls","spinning","delay","className","rootClassName","size","tip","wrapperClassName","style","children","fullscreen","indicator","percent"]),{getPrefixCls:x,direction:z,className:E,style:N,indicator:w}=(0,o.useComponentConfig)("spin"),j=x("spin",l),[I,O,M]=v(j),[B,D]=i.useState(()=>r&&(!r||!c||!!Number.isNaN(Number(c)))),T=function(e,t){let[n,o]=i.useState(0),a=i.useRef(null),l="auto"===t;return i.useEffect(()=>(l&&e&&(o(0),a.current=setInterval(()=>{o(e=>{let t=100-e;for(let i=0;i{a.current&&(clearInterval(a.current),a.current=null)}),[l,e]),l?n:t}(B,C);i.useEffect(()=>{if(r){let e=function(e,t,i){var n,o=i||{},a=o.noTrailing,l=void 0!==a&&a,r=o.noLeading,c=void 0!==r&&r,s=o.debounceMode,d=void 0===s?void 0:s,u=!1,m=0;function p(){n&&clearTimeout(n)}function g(){for(var i=arguments.length,o=Array(i),a=0;ae?c?(m=Date.now(),l||(n=setTimeout(d?f:g,e))):g():!0!==l&&(n=setTimeout(d?f:g,void 0===d?e-s:e)))}return g.cancel=function(e){var t=(e||{}).upcomingOnly;p(),u=!(void 0!==t&&t)},g}(c,()=>{D(!0)},{debounceMode:false});return e(),()=>{var t;null==(t=null==e?void 0:e.cancel)||t.call(e)}}D(!1)},[c,r]);let P=i.useMemo(()=>void 0!==b&&!h,[b,h]),H=(0,n.default)(j,E,{[`${j}-sm`]:"small"===m,[`${j}-lg`]:"large"===m,[`${j}-spinning`]:B,[`${j}-show-text`]:!!p,[`${j}-rtl`]:"rtl"===z},s,!h&&d,O,M),A=(0,n.default)(`${j}-container`,{[`${j}-blur`]:B}),q=null!=(a=null!=y?y:w)?a:t,R=Object.assign(Object.assign({},N),f),_=i.createElement("div",Object.assign({},k,{style:R,className:H,"aria-live":"polite","aria-busy":B}),i.createElement(u,{prefixCls:j,indicator:q,percent:T}),p&&(P||h)?i.createElement("div",{className:`${j}-text`},p):null);return I(P?i.createElement("div",Object.assign({},k,{className:(0,n.default)(`${j}-nested-loading`,g,O,M)}),B&&i.createElement("div",{key:"loading"},_),i.createElement("div",{className:A,key:"container"},b)):h?i.createElement("div",{className:(0,n.default)(`${j}-fullscreen`,{[`${j}-fullscreen-show`]:B},d,O,M)},_):_)};y.setDefaultIndicator=e=>{t=e},e.s(["default",0,y],244451)},482725,e=>{"use strict";var t=e.i(244451);e.s(["Spin",()=>t.default])},165370,e=>{"use strict";e.i(247167);var t=e.i(271645),i=e.i(931067);let n={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M272.9 512l265.4-339.1c4.1-5.2.4-12.9-6.3-12.9h-77.3c-4.9 0-9.6 2.3-12.6 6.1L186.8 492.3a31.99 31.99 0 000 39.5l255.3 326.1c3 3.9 7.7 6.1 12.6 6.1H532c6.7 0 10.4-7.7 6.3-12.9L272.9 512zm304 0l265.4-339.1c4.1-5.2.4-12.9-6.3-12.9h-77.3c-4.9 0-9.6 2.3-12.6 6.1L490.8 492.3a31.99 31.99 0 000 39.5l255.3 326.1c3 3.9 7.7 6.1 12.6 6.1H836c6.7 0 10.4-7.7 6.3-12.9L576.9 512z"}}]},name:"double-left",theme:"outlined"};var o=e.i(9583),a=t.forwardRef(function(e,a){return t.createElement(o.default,(0,i.default)({},e,{ref:a,icon:n}))});let l={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M533.2 492.3L277.9 166.1c-3-3.9-7.7-6.1-12.6-6.1H188c-6.7 0-10.4 7.7-6.3 12.9L447.1 512 181.7 851.1A7.98 7.98 0 00188 864h77.3c4.9 0 9.6-2.3 12.6-6.1l255.3-326.1c9.1-11.7 9.1-27.9 0-39.5zm304 0L581.9 166.1c-3-3.9-7.7-6.1-12.6-6.1H492c-6.7 0-10.4 7.7-6.3 12.9L751.1 512 485.7 851.1A7.98 7.98 0 00492 864h77.3c4.9 0 9.6-2.3 12.6-6.1l255.3-326.1c9.1-11.7 9.1-27.9 0-39.5z"}}]},name:"double-right",theme:"outlined"};var r=t.forwardRef(function(e,n){return t.createElement(o.default,(0,i.default)({},e,{ref:n,icon:l}))}),c=e.i(801312),s=e.i(286612),d=e.i(343794),u=e.i(211577),m=e.i(410160),p=e.i(209428),g=e.i(392221),f=e.i(914949),b=e.i(404948),h=e.i(244009);e.i(883110);let v={items_per_page:"条/页",jump_to:"跳至",jump_to_confirm:"确定",page:"页",prev_page:"上一页",next_page:"下一页",prev_5:"向前 5 页",next_5:"向后 5 页",prev_3:"向前 3 页",next_3:"向后 3 页",page_size:"页码"};var S=[10,20,50,100];let $=function(e){var i=e.pageSizeOptions,n=void 0===i?S:i,o=e.locale,a=e.changeSize,l=e.pageSize,r=e.goButton,c=e.quickGo,s=e.rootPrefixCls,d=e.disabled,u=e.buildOptionText,m=e.showSizeChanger,p=e.sizeChangerRender,f=t.default.useState(""),h=(0,g.default)(f,2),v=h[0],$=h[1],y=function(){return!v||Number.isNaN(v)?void 0:Number(v)},C="function"==typeof u?u:function(e){return"".concat(e," ").concat(o.items_per_page)},k=function(e){""!==v&&(e.keyCode===b.default.ENTER||"click"===e.type)&&($(""),null==c||c(y()))},x="".concat(s,"-options");if(!m&&!c)return null;var z=null,E=null,N=null;return m&&p&&(z=p({disabled:d,size:l,onSizeChange:function(e){null==a||a(Number(e))},"aria-label":o.page_size,className:"".concat(x,"-size-changer"),options:(n.some(function(e){return e.toString()===l.toString()})?n:n.concat([l]).sort(function(e,t){return(Number.isNaN(Number(e))?0:Number(e))-(Number.isNaN(Number(t))?0:Number(t))})).map(function(e){return{label:C(e),value:e}})})),c&&(r&&(N="boolean"==typeof r?t.default.createElement("button",{type:"button",onClick:k,onKeyUp:k,disabled:d,className:"".concat(x,"-quick-jumper-button")},o.jump_to_confirm):t.default.createElement("span",{onClick:k,onKeyUp:k},r)),E=t.default.createElement("div",{className:"".concat(x,"-quick-jumper")},o.jump_to,t.default.createElement("input",{disabled:d,type:"text",value:v,onChange:function(e){$(e.target.value)},onKeyUp:k,onBlur:function(e){r||""===v||($(""),e.relatedTarget&&(e.relatedTarget.className.indexOf("".concat(s,"-item-link"))>=0||e.relatedTarget.className.indexOf("".concat(s,"-item"))>=0)||null==c||c(y()))},"aria-label":o.page}),o.page,N)),t.default.createElement("li",{className:x},z,E)},y=function(e){var i=e.rootPrefixCls,n=e.page,o=e.active,a=e.className,l=e.showTitle,r=e.onClick,c=e.onKeyPress,s=e.itemRender,m="".concat(i,"-item"),p=(0,d.default)(m,"".concat(m,"-").concat(n),(0,u.default)((0,u.default)({},"".concat(m,"-active"),o),"".concat(m,"-disabled"),!n),a),g=s(n,"page",t.default.createElement("a",{rel:"nofollow"},n));return g?t.default.createElement("li",{title:l?String(n):null,className:p,onClick:function(){r(n)},onKeyDown:function(e){c(e,r,n)},tabIndex:0},g):null};var C=function(e,t,i){return i};function k(){}function x(e){var t=Number(e);return"number"==typeof t&&!Number.isNaN(t)&&isFinite(t)&&Math.floor(t)===t}function z(e,t,i){return Math.floor((i-1)/(void 0===e?t:e))+1}let E=function(e){var n,o,a,l,r=e.prefixCls,c=void 0===r?"rc-pagination":r,s=e.selectPrefixCls,S=e.className,E=e.current,N=e.defaultCurrent,w=e.total,j=void 0===w?0:w,I=e.pageSize,O=e.defaultPageSize,M=e.onChange,B=void 0===M?k:M,D=e.hideOnSinglePage,T=e.align,P=e.showPrevNextJumpers,H=e.showQuickJumper,A=e.showLessItems,q=e.showTitle,R=void 0===q||q,_=e.onShowSizeChange,L=void 0===_?k:_,X=e.locale,W=void 0===X?v:X,K=e.style,F=e.totalBoundaryShowSizeChanger,G=e.disabled,U=e.simple,J=e.showTotal,V=e.showSizeChanger,Q=void 0===V?j>(void 0===F?50:F):V,Y=e.sizeChangerRender,Z=e.pageSizeOptions,ee=e.itemRender,et=void 0===ee?C:ee,ei=e.jumpPrevIcon,en=e.jumpNextIcon,eo=e.prevIcon,ea=e.nextIcon,el=t.default.useRef(null),er=(0,f.default)(10,{value:I,defaultValue:void 0===O?10:O}),ec=(0,g.default)(er,2),es=ec[0],ed=ec[1],eu=(0,f.default)(1,{value:E,defaultValue:void 0===N?1:N,postState:function(e){return Math.max(1,Math.min(e,z(void 0,es,j)))}}),em=(0,g.default)(eu,2),ep=em[0],eg=em[1],ef=t.default.useState(ep),eb=(0,g.default)(ef,2),eh=eb[0],ev=eb[1];(0,t.useEffect)(function(){ev(ep)},[ep]);var eS=Math.max(1,ep-(A?3:5)),e$=Math.min(z(void 0,es,j),ep+(A?3:5));function ey(i,n){var o=i||t.default.createElement("button",{type:"button","aria-label":n,className:"".concat(c,"-item-link")});return"function"==typeof i&&(o=t.default.createElement(i,(0,p.default)({},e))),o}function eC(e){var t=e.target.value,i=z(void 0,es,j);return""===t?t:Number.isNaN(Number(t))?eh:t>=i?i:Number(t)}var ek=j>es&&H;function ex(e){var t=eC(e);switch(t!==eh&&ev(t),e.keyCode){case b.default.ENTER:ez(t);break;case b.default.UP:ez(t-1);break;case b.default.DOWN:ez(t+1)}}function ez(e){if(x(e)&&e!==ep&&x(j)&&j>0&&!G){var t=z(void 0,es,j),i=e;return e>t?i=t:e<1&&(i=1),i!==eh&&ev(i),eg(i),null==B||B(i,es),i}return ep}var eE=ep>1,eN=ep2?i-2:0),o=2;oj?j:ep*es])),eH=null,eA=z(void 0,es,j);if(D&&j<=es)return null;var eq=[],eR={rootPrefixCls:c,onClick:ez,onKeyPress:eM,showTitle:R,itemRender:et,page:-1},e_=ep-1>0?ep-1:0,eL=ep+1=2*eG&&3!==ep&&(eq[0]=t.default.cloneElement(eq[0],{className:(0,d.default)("".concat(c,"-item-after-jump-prev"),eq[0].props.className)}),eq.unshift(eD)),eA-ep>=2*eG&&ep!==eA-2){var e2=eq[eq.length-1];eq[eq.length-1]=t.default.cloneElement(e2,{className:(0,d.default)("".concat(c,"-item-before-jump-next"),e2.props.className)}),eq.push(eH)}1!==eZ&&eq.unshift(t.default.createElement(y,(0,i.default)({},eR,{key:1,page:1}))),e0!==eA&&eq.push(t.default.createElement(y,(0,i.default)({},eR,{key:eA,page:eA})))}var e3=(n=et(e_,"prev",ey(eo,"prev page")),t.default.isValidElement(n)?t.default.cloneElement(n,{disabled:!eE}):n);if(e3){var e4=!eE||!eA;e3=t.default.createElement("li",{title:R?W.prev_page:null,onClick:ew,tabIndex:e4?null:0,onKeyDown:function(e){eM(e,ew)},className:(0,d.default)("".concat(c,"-prev"),(0,u.default)({},"".concat(c,"-disabled"),e4)),"aria-disabled":e4},e3)}var e9=(o=et(eL,"next",ey(ea,"next page")),t.default.isValidElement(o)?t.default.cloneElement(o,{disabled:!eN}):o);e9&&(U?(a=!eN,l=eE?0:null):l=(a=!eN||!eA)?null:0,e9=t.default.createElement("li",{title:R?W.next_page:null,onClick:ej,tabIndex:l,onKeyDown:function(e){eM(e,ej)},className:(0,d.default)("".concat(c,"-next"),(0,u.default)({},"".concat(c,"-disabled"),a)),"aria-disabled":a},e9));var e5=(0,d.default)(c,S,(0,u.default)((0,u.default)((0,u.default)((0,u.default)((0,u.default)({},"".concat(c,"-start"),"start"===T),"".concat(c,"-center"),"center"===T),"".concat(c,"-end"),"end"===T),"".concat(c,"-simple"),U),"".concat(c,"-disabled"),G));return t.default.createElement("ul",(0,i.default)({className:e5,style:K,ref:el},eT),eP,e3,U?eF:eq,e9,t.default.createElement($,{locale:W,rootPrefixCls:c,disabled:G,selectPrefixCls:void 0===s?"rc-select":s,changeSize:function(e){var t=z(e,es,j),i=ep>t&&0!==t?t:ep;ed(e),ev(i),null==L||L(ep,e),eg(i),null==B||B(i,e)},pageSize:es,pageSizeOptions:Z,quickGo:ek?ez:null,goButton:eK,showSizeChanger:Q,sizeChangerRender:Y}))};var N=e.i(727214),w=e.i(242064),j=e.i(517455),I=e.i(150073),O=e.i(408850),M=e.i(327494),B=e.i(104458);e.i(296059);var D=e.i(915654),T=e.i(349942),P=e.i(517458),H=e.i(889943),A=e.i(183293),q=e.i(246422),R=e.i(838378);let _=e=>Object.assign({itemBg:e.colorBgContainer,itemSize:e.controlHeight,itemSizeSM:e.controlHeightSM,itemActiveBg:e.colorBgContainer,itemActiveColor:e.colorPrimary,itemActiveColorHover:e.colorPrimaryHover,itemLinkBg:e.colorBgContainer,itemActiveColorDisabled:e.colorTextDisabled,itemActiveBgDisabled:e.controlItemBgActiveDisabled,itemInputBg:e.colorBgContainer,miniOptionsSizeChangerTop:0},(0,P.initComponentToken)(e)),L=e=>(0,R.mergeToken)(e,{inputOutlineOffset:0,quickJumperInputWidth:e.calc(e.controlHeightLG).mul(1.25).equal(),paginationMiniOptionsMarginInlineStart:e.calc(e.marginXXS).div(2).equal(),paginationMiniQuickJumperInputWidth:e.calc(e.controlHeightLG).mul(1.1).equal(),paginationItemPaddingInline:e.calc(e.marginXXS).mul(1.5).equal(),paginationEllipsisLetterSpacing:e.calc(e.marginXXS).div(2).equal(),paginationSlashMarginInlineStart:e.marginSM,paginationSlashMarginInlineEnd:e.marginSM,paginationEllipsisTextIndent:"0.13em"},(0,P.initInputToken)(e)),X=(0,q.genStyleHooks)("Pagination",e=>{let t=L(e);return[(e=>{let{componentCls:t}=e;return{[t]:Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({},(0,A.resetComponent)(e)),{display:"flex",flexWrap:"wrap",rowGap:e.paddingXS,"&-start":{justifyContent:"start"},"&-center":{justifyContent:"center"},"&-end":{justifyContent:"end"},"ul, ol":{margin:0,padding:0,listStyle:"none"},"&::after":{display:"block",clear:"both",height:0,overflow:"hidden",visibility:"hidden",content:'""'},[`${t}-total-text`]:{display:"inline-block",height:e.itemSize,marginInlineEnd:e.marginXS,lineHeight:(0,D.unit)(e.calc(e.itemSize).sub(2).equal()),verticalAlign:"middle"}}),(e=>{let{componentCls:t}=e;return{[`${t}-item`]:{display:"inline-block",minWidth:e.itemSize,height:e.itemSize,marginInlineEnd:e.marginXS,fontFamily:e.fontFamily,lineHeight:(0,D.unit)(e.calc(e.itemSize).sub(2).equal()),textAlign:"center",verticalAlign:"middle",listStyle:"none",backgroundColor:e.itemBg,border:`${(0,D.unit)(e.lineWidth)} ${e.lineType} transparent`,borderRadius:e.borderRadius,outline:0,cursor:"pointer",userSelect:"none",a:{display:"block",padding:`0 ${(0,D.unit)(e.paginationItemPaddingInline)}`,color:e.colorText,"&:hover":{textDecoration:"none"}},[`&:not(${t}-item-active)`]:{"&:hover":{transition:`all ${e.motionDurationMid}`,backgroundColor:e.colorBgTextHover},"&:active":{backgroundColor:e.colorBgTextActive}},"&-active":{fontWeight:e.fontWeightStrong,backgroundColor:e.itemActiveBg,borderColor:e.colorPrimary,a:{color:e.itemActiveColor},"&:hover":{borderColor:e.colorPrimaryHover},"&:hover a":{color:e.itemActiveColorHover}}}}})(e)),(e=>{let{componentCls:t}=e;return{[`${t}-jump-prev, ${t}-jump-next`]:{outline:0,[`${t}-item-container`]:{position:"relative",[`${t}-item-link-icon`]:{color:e.colorPrimary,fontSize:e.fontSizeSM,opacity:0,transition:`all ${e.motionDurationMid}`,"&-svg":{top:0,insetInlineEnd:0,bottom:0,insetInlineStart:0,margin:"auto"}},[`${t}-item-ellipsis`]:{position:"absolute",top:0,insetInlineEnd:0,bottom:0,insetInlineStart:0,display:"block",margin:"auto",color:e.colorTextDisabled,letterSpacing:e.paginationEllipsisLetterSpacing,textAlign:"center",textIndent:e.paginationEllipsisTextIndent,opacity:1,transition:`all ${e.motionDurationMid}`}},"&:hover":{[`${t}-item-link-icon`]:{opacity:1},[`${t}-item-ellipsis`]:{opacity:0}}},[` + ${t}-prev, + ${t}-jump-prev, + ${t}-jump-next + `]:{marginInlineEnd:e.marginXS},[` + ${t}-prev, + ${t}-next, + ${t}-jump-prev, + ${t}-jump-next + `]:{display:"inline-block",minWidth:e.itemSize,height:e.itemSize,color:e.colorText,fontFamily:e.fontFamily,lineHeight:(0,D.unit)(e.itemSize),textAlign:"center",verticalAlign:"middle",listStyle:"none",borderRadius:e.borderRadius,cursor:"pointer",transition:`all ${e.motionDurationMid}`},[`${t}-prev, ${t}-next`]:{outline:0,button:{color:e.colorText,cursor:"pointer",userSelect:"none"},[`${t}-item-link`]:{display:"block",width:"100%",height:"100%",padding:0,fontSize:e.fontSizeSM,textAlign:"center",backgroundColor:"transparent",border:`${(0,D.unit)(e.lineWidth)} ${e.lineType} transparent`,borderRadius:e.borderRadius,outline:"none",transition:`all ${e.motionDurationMid}`},[`&:hover ${t}-item-link`]:{backgroundColor:e.colorBgTextHover},[`&:active ${t}-item-link`]:{backgroundColor:e.colorBgTextActive},[`&${t}-disabled:hover`]:{[`${t}-item-link`]:{backgroundColor:"transparent"}}},[`${t}-slash`]:{marginInlineEnd:e.paginationSlashMarginInlineEnd,marginInlineStart:e.paginationSlashMarginInlineStart},[`${t}-options`]:{display:"inline-block",marginInlineStart:e.margin,verticalAlign:"middle","&-size-changer":{display:"inline-block",width:"auto"},"&-quick-jumper":{display:"inline-block",height:e.controlHeight,marginInlineStart:e.marginXS,lineHeight:(0,D.unit)(e.controlHeight),verticalAlign:"top",input:Object.assign(Object.assign(Object.assign({},(0,T.genBasicInputStyle)(e)),(0,H.genBaseOutlinedStyle)(e,{borderColor:e.colorBorder,hoverBorderColor:e.colorPrimaryHover,activeBorderColor:e.colorPrimary,activeShadow:e.activeShadow})),{"&[disabled]":Object.assign({},(0,H.genDisabledStyle)(e)),width:e.quickJumperInputWidth,height:e.controlHeight,boxSizing:"border-box",margin:0,marginInlineStart:e.marginXS,marginInlineEnd:e.marginXS})}}}})(e)),(e=>{let{componentCls:t}=e;return{[`&${t}-simple`]:{[`${t}-prev, ${t}-next`]:{height:e.itemSize,lineHeight:(0,D.unit)(e.itemSize),verticalAlign:"top",[`${t}-item-link`]:{height:e.itemSize,backgroundColor:"transparent",border:0,"&:hover":{backgroundColor:e.colorBgTextHover},"&:active":{backgroundColor:e.colorBgTextActive},"&::after":{height:e.itemSize,lineHeight:(0,D.unit)(e.itemSize)}}},[`${t}-simple-pager`]:{display:"inline-flex",alignItems:"center",height:e.itemSize,marginInlineEnd:e.marginXS,input:{boxSizing:"border-box",height:"100%",width:e.quickJumperInputWidth,padding:`0 ${(0,D.unit)(e.paginationItemPaddingInline)}`,textAlign:"center",backgroundColor:e.itemInputBg,border:`${(0,D.unit)(e.lineWidth)} ${e.lineType} ${e.colorBorder}`,borderRadius:e.borderRadius,outline:"none",transition:`border-color ${e.motionDurationMid}`,color:"inherit","&:hover":{borderColor:e.colorPrimary},"&:focus":{borderColor:e.colorPrimaryHover,boxShadow:`${(0,D.unit)(e.inputOutlineOffset)} 0 ${(0,D.unit)(e.controlOutlineWidth)} ${e.controlOutline}`},"&[disabled]":{color:e.colorTextDisabled,backgroundColor:e.colorBgContainerDisabled,borderColor:e.colorBorder,cursor:"not-allowed"}}},[`&${t}-disabled`]:{[`${t}-prev, ${t}-next`]:{[`${t}-item-link`]:{"&:hover, &:active":{backgroundColor:"transparent"}}}},[`&${t}-mini`]:{[`${t}-prev, ${t}-next`]:{height:e.itemSizeSM,lineHeight:(0,D.unit)(e.itemSizeSM),[`${t}-item-link`]:{height:e.itemSizeSM,"&::after":{height:e.itemSizeSM,lineHeight:(0,D.unit)(e.itemSizeSM)}}},[`${t}-simple-pager`]:{height:e.itemSizeSM,input:{width:e.paginationMiniQuickJumperInputWidth}}}}}})(e)),(e=>{let{componentCls:t}=e;return{[`&${t}-mini ${t}-total-text, &${t}-mini ${t}-simple-pager`]:{height:e.itemSizeSM,lineHeight:(0,D.unit)(e.itemSizeSM)},[`&${t}-mini ${t}-item`]:{minWidth:e.itemSizeSM,height:e.itemSizeSM,margin:0,lineHeight:(0,D.unit)(e.calc(e.itemSizeSM).sub(2).equal())},[`&${t}-mini ${t}-prev, &${t}-mini ${t}-next`]:{minWidth:e.itemSizeSM,height:e.itemSizeSM,margin:0,lineHeight:(0,D.unit)(e.itemSizeSM)},[`&${t}-mini:not(${t}-disabled)`]:{[`${t}-prev, ${t}-next`]:{[`&:hover ${t}-item-link`]:{backgroundColor:e.colorBgTextHover},[`&:active ${t}-item-link`]:{backgroundColor:e.colorBgTextActive},[`&${t}-disabled:hover ${t}-item-link`]:{backgroundColor:"transparent"}}},[` + &${t}-mini ${t}-prev ${t}-item-link, + &${t}-mini ${t}-next ${t}-item-link + `]:{backgroundColor:"transparent",borderColor:"transparent","&::after":{height:e.itemSizeSM,lineHeight:(0,D.unit)(e.itemSizeSM)}},[`&${t}-mini ${t}-jump-prev, &${t}-mini ${t}-jump-next`]:{height:e.itemSizeSM,marginInlineEnd:0,lineHeight:(0,D.unit)(e.itemSizeSM)},[`&${t}-mini ${t}-options`]:{marginInlineStart:e.paginationMiniOptionsMarginInlineStart,"&-size-changer":{top:e.miniOptionsSizeChangerTop},"&-quick-jumper":{height:e.itemSizeSM,lineHeight:(0,D.unit)(e.itemSizeSM),input:Object.assign(Object.assign({},(0,T.genInputSmallStyle)(e)),{width:e.paginationMiniQuickJumperInputWidth,height:e.controlHeightSM})}}}})(e)),(e=>{let{componentCls:t}=e;return{[`${t}-disabled`]:{"&, &:hover":{cursor:"not-allowed",[`${t}-item-link`]:{color:e.colorTextDisabled,cursor:"not-allowed"}},"&:focus-visible":{cursor:"not-allowed",[`${t}-item-link`]:{color:e.colorTextDisabled,cursor:"not-allowed"}}},[`&${t}-disabled`]:{cursor:"not-allowed",[`${t}-item`]:{cursor:"not-allowed",backgroundColor:"transparent","&:hover, &:active":{backgroundColor:"transparent"},a:{color:e.colorTextDisabled,backgroundColor:"transparent",border:"none",cursor:"not-allowed"},"&-active":{borderColor:e.colorBorder,backgroundColor:e.itemActiveBgDisabled,"&:hover, &:active":{backgroundColor:e.itemActiveBgDisabled},a:{color:e.itemActiveColorDisabled}}},[`${t}-item-link`]:{color:e.colorTextDisabled,cursor:"not-allowed","&:hover, &:active":{backgroundColor:"transparent"},[`${t}-simple&`]:{backgroundColor:"transparent","&:hover, &:active":{backgroundColor:"transparent"}}},[`${t}-simple-pager`]:{color:e.colorTextDisabled},[`${t}-jump-prev, ${t}-jump-next`]:{[`${t}-item-link-icon`]:{opacity:0},[`${t}-item-ellipsis`]:{opacity:1}}}}})(e)),{[`@media only screen and (max-width: ${e.screenLG}px)`]:{[`${t}-item`]:{"&-after-jump-prev, &-before-jump-next":{display:"none"}}},[`@media only screen and (max-width: ${e.screenSM}px)`]:{[`${t}-options`]:{display:"none"}}}),[`&${e.componentCls}-rtl`]:{direction:"rtl"}}})(t),(e=>{let{componentCls:t}=e;return{[`${t}:not(${t}-disabled)`]:{[`${t}-item`]:Object.assign({},(0,A.genFocusStyle)(e)),[`${t}-jump-prev, ${t}-jump-next`]:{"&:focus-visible":Object.assign({[`${t}-item-link-icon`]:{opacity:1},[`${t}-item-ellipsis`]:{opacity:0}},(0,A.genFocusOutline)(e))},[`${t}-prev, ${t}-next`]:{[`&:focus-visible ${t}-item-link`]:(0,A.genFocusOutline)(e)}}}})(t)]},_),W=(0,q.genSubStyleComponent)(["Pagination","bordered"],e=>(e=>{let{componentCls:t}=e;return{[`${t}${t}-bordered${t}-disabled:not(${t}-mini)`]:{"&, &:hover":{[`${t}-item-link`]:{borderColor:e.colorBorder}},"&:focus-visible":{[`${t}-item-link`]:{borderColor:e.colorBorder}},[`${t}-item, ${t}-item-link`]:{backgroundColor:e.colorBgContainerDisabled,borderColor:e.colorBorder,[`&:hover:not(${t}-item-active)`]:{backgroundColor:e.colorBgContainerDisabled,borderColor:e.colorBorder,a:{color:e.colorTextDisabled}},[`&${t}-item-active`]:{backgroundColor:e.itemActiveBgDisabled}},[`${t}-prev, ${t}-next`]:{"&:hover button":{backgroundColor:e.colorBgContainerDisabled,borderColor:e.colorBorder,color:e.colorTextDisabled},[`${t}-item-link`]:{backgroundColor:e.colorBgContainerDisabled,borderColor:e.colorBorder}}},[`${t}${t}-bordered:not(${t}-mini)`]:{[`${t}-prev, ${t}-next`]:{"&:hover button":{borderColor:e.colorPrimaryHover,backgroundColor:e.itemBg},[`${t}-item-link`]:{backgroundColor:e.itemLinkBg,borderColor:e.colorBorder},[`&:hover ${t}-item-link`]:{borderColor:e.colorPrimary,backgroundColor:e.itemBg,color:e.colorPrimary},[`&${t}-disabled`]:{[`${t}-item-link`]:{borderColor:e.colorBorder,color:e.colorTextDisabled}}},[`${t}-item`]:{backgroundColor:e.itemBg,border:`${(0,D.unit)(e.lineWidth)} ${e.lineType} ${e.colorBorder}`,[`&:hover:not(${t}-item-active)`]:{borderColor:e.colorPrimary,backgroundColor:e.itemBg,a:{color:e.colorPrimary}},"&-active":{borderColor:e.colorPrimary}}}}})(L(e)),_);function K(e){return(0,t.useMemo)(()=>"boolean"==typeof e?[e,{}]:e&&"object"==typeof e?[!0,e]:[void 0,void 0],[e])}var F=function(e,t){var i={};for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&0>t.indexOf(n)&&(i[n]=e[n]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var o=0,n=Object.getOwnPropertySymbols(e);ot.indexOf(n[o])&&Object.prototype.propertyIsEnumerable.call(e,n[o])&&(i[n[o]]=e[n[o]]);return i};e.s(["default",0,e=>{let{align:i,prefixCls:n,selectPrefixCls:o,className:l,rootClassName:u,style:m,size:p,locale:g,responsive:f,showSizeChanger:b,selectComponentClass:h,pageSizeOptions:v}=e,S=F(e,["align","prefixCls","selectPrefixCls","className","rootClassName","style","size","locale","responsive","showSizeChanger","selectComponentClass","pageSizeOptions"]),{xs:$}=(0,I.default)(f),[,y]=(0,B.useToken)(),{getPrefixCls:C,direction:k,showSizeChanger:x,className:z,style:D}=(0,w.useComponentConfig)("pagination"),T=C("pagination",n),[P,H,A]=X(T),q=(0,j.default)(p),R="small"===q||!!($&&!q&&f),[_]=(0,O.useLocale)("Pagination",N.default),L=Object.assign(Object.assign({},_),g),[G,U]=K(b),[J,V]=K(x),Q=null!=U?U:V,Y=h||M.default,Z=t.useMemo(()=>v?v.map(e=>Number(e)):void 0,[v]),ee=t.useMemo(()=>{let e=t.createElement("span",{className:`${T}-item-ellipsis`},"•••"),i=t.createElement("button",{className:`${T}-item-link`,type:"button",tabIndex:-1},"rtl"===k?t.createElement(s.default,null):t.createElement(c.default,null)),n=t.createElement("button",{className:`${T}-item-link`,type:"button",tabIndex:-1},"rtl"===k?t.createElement(c.default,null):t.createElement(s.default,null));return{prevIcon:i,nextIcon:n,jumpPrevIcon:t.createElement("a",{className:`${T}-item-link`},t.createElement("div",{className:`${T}-item-container`},"rtl"===k?t.createElement(r,{className:`${T}-item-link-icon`}):t.createElement(a,{className:`${T}-item-link-icon`}),e)),jumpNextIcon:t.createElement("a",{className:`${T}-item-link`},t.createElement("div",{className:`${T}-item-container`},"rtl"===k?t.createElement(a,{className:`${T}-item-link-icon`}):t.createElement(r,{className:`${T}-item-link-icon`}),e))}},[k,T]),et=C("select",o),ei=(0,d.default)({[`${T}-${i}`]:!!i,[`${T}-mini`]:R,[`${T}-rtl`]:"rtl"===k,[`${T}-bordered`]:y.wireframe},z,l,u,H,A),en=Object.assign(Object.assign({},D),m);return P(t.createElement(t.Fragment,null,y.wireframe&&t.createElement(W,{prefixCls:T}),t.createElement(E,Object.assign({},ee,S,{style:en,prefixCls:T,selectPrefixCls:et,className:ei,locale:L,pageSizeOptions:Z,showSizeChanger:null!=G?G:J,sizeChangerRender:e=>{var i;let{disabled:n,size:o,onSizeChange:a,"aria-label":l,className:r,options:c}=e,{className:s,onChange:u}=Q||{},m=null==(i=c.find(e=>String(e.value)===String(o)))?void 0:i.value;return t.createElement(Y,Object.assign({disabled:n,showSearch:!0,popupMatchSelectWidth:!1,getPopupContainer:e=>e.parentNode,"aria-label":l,options:c},Q,{value:m,onChange:(e,t)=>{null==a||a(e),null==u||u(e,t)},size:R?"small":"middle",className:(0,d.default)(r,s)}))}}))))}],165370)}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/054z32giulaw7.js b/litellm/proxy/_experimental/out/_next/static/chunks/054z32giulaw7.js new file mode 100644 index 00000000000..e5097101fb1 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/054z32giulaw7.js @@ -0,0 +1 @@ +(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,799062,e=>{"use strict";var a=e.i(843476),t=e.i(271645),l=e.i(197647),s=e.i(653824),i=e.i(881073),n=e.i(404206),r=e.i(723731),o=e.i(560445),d=e.i(207082),c=e.i(135214),u=e.i(332102);e.i(707701);var m=e.i(807235),g=e.i(494862);e.i(622826);var x=e.i(200208),h=e.i(399536),p=e.i(964471);function b({value:e}){return e?(0,a.jsx)("span",{className:"block max-w-60 truncate",title:e,children:e}):(0,a.jsx)("span",{className:"text-muted-foreground",children:"-"})}let j=[{id:"deleted_at",desc:!0}];function f(){return(0,a.jsxs)("div",{className:"flex flex-col items-center gap-1 py-6",children:[(0,a.jsx)("div",{className:"mb-1 flex size-10 items-center justify-center rounded-lg bg-muted",children:(0,a.jsx)(u.Inbox,{className:"size-5 text-muted-foreground"})}),(0,a.jsx)("div",{className:"text-sm font-medium text-foreground",children:"No deleted keys found"}),(0,a.jsx)("div",{className:"text-sm text-muted-foreground",children:"Keys deleted from this proxy will show up here."})]})}function y({keys:e,totalCount:l,isLoading:s,pagination:i,onPaginationChange:n}){let[r,o]=(0,t.useState)(j),d=(0,t.useMemo)(()=>[{id:"token",accessorKey:"token",meta:{title:"Key ID"},header:"Key ID",size:150,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(h.IdCell,{value:e.original.token,variant:"plain"})},{id:"key_alias",accessorKey:"key_alias",meta:{title:"Key Alias"},header:"Key Alias",size:150,enableSorting:!1,cell:({row:e})=>{let t=e.original.key_alias;return t?(0,a.jsx)("span",{className:"block max-w-60 truncate font-mono text-xs",title:t,children:t}):(0,a.jsx)("span",{className:"text-muted-foreground",children:"-"})}},{id:"team_alias",accessorKey:"team_alias",meta:{title:"Team Alias"},header:"Team Alias",size:120,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(b,{value:e.original.team_alias})},{id:"spend",accessorKey:"spend",meta:{title:"Spend (USD)",numeric:!0},header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Spend (USD)"}),size:100,enableSorting:!0,cell:({row:e})=>(0,a.jsx)(p.MoneyCell,{value:e.original.spend,decimals:4})},{id:"max_budget",accessorKey:"max_budget",meta:{title:"Budget (USD)",numeric:!0},header:"Budget (USD)",size:110,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(p.MoneyCell,{value:e.original.max_budget,decimals:0,emptyText:"Unlimited",showZero:!0})},{id:"user_email",accessorKey:"user_email",meta:{title:"User Email"},header:"User Email",size:160,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(b,{value:e.original.user_email})},{id:"user_id",accessorKey:"user_id",meta:{title:"User ID"},header:"User ID",size:120,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(h.IdCell,{value:e.original.user_id,variant:"plain"})},{id:"created_at",accessorKey:"created_at",meta:{title:"Created At"},header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Created At"}),size:120,enableSorting:!0,cell:({row:e})=>(0,a.jsx)(x.DateCell,{value:e.original.created_at,precision:"date"})},{id:"created_by",accessorKey:"created_by",meta:{title:"Created By"},header:"Created By",size:120,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(b,{value:e.original.created_by})},{id:"deleted_at",accessorKey:"deleted_at",meta:{title:"Deleted At"},header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Deleted At"}),size:120,enableSorting:!0,cell:({row:e})=>(0,a.jsx)(x.DateCell,{value:e.original.deleted_at,precision:"date"})},{id:"deleted_by",accessorKey:"deleted_by",meta:{title:"Deleted By"},header:"Deleted By",size:120,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(b,{value:e.original.deleted_by})}],[]);return(0,a.jsx)(m.DataTable,{data:e,columns:d,getRowId:(e,a)=>e.token||String(a),sortingMode:"client",sorting:r,onSortingChange:o,paginationMode:"server",pagination:i,onPaginationChange:n,rowCount:l,isLoading:s,loadingMessage:"Loading deleted keys…",noDataMessage:(0,a.jsx)(f,{}),size:"compact"})}function _(){let{premiumUser:e}=(0,c.default)(),[l,s]=(0,t.useState)({pageIndex:0,pageSize:50}),{data:i,isLoading:n}=(0,d.useDeletedKeys)(l.pageIndex+1,l.pageSize);return(0,a.jsxs)("div",{className:"flex flex-col gap-4",children:[!e&&(0,a.jsx)(o.Alert,{type:"info",banner:!0,showIcon:!0,message:"Coming soon to Enterprise",description:"Deleted key auditing is graduating from beta into our Enterprise audit & compliance suite."}),(0,a.jsx)(y,{keys:i?.keys||[],totalCount:i?.total_count||0,isLoading:n,pagination:l,onPaginationChange:s})]})}var v=e.i(785242),S=e.i(547227);let C=[{id:"deleted_at",desc:!0}];function T(){return(0,a.jsxs)("div",{className:"flex flex-col items-center gap-1 py-6",children:[(0,a.jsx)("div",{className:"mb-1 flex size-10 items-center justify-center rounded-lg bg-muted",children:(0,a.jsx)(u.Inbox,{className:"size-5 text-muted-foreground"})}),(0,a.jsx)("div",{className:"text-sm font-medium text-foreground",children:"No deleted teams found"}),(0,a.jsx)("div",{className:"text-sm text-muted-foreground",children:"Teams deleted from this proxy will show up here."})]})}function N({teams:e,isLoading:l}){let[s,i]=(0,t.useState)(C),n=(0,t.useMemo)(()=>[{id:"team_alias",accessorKey:"team_alias",meta:{title:"Team Name"},header:"Team Name",size:150,enableSorting:!1,cell:({row:e})=>{let t=e.original.team_alias;return t?(0,a.jsx)("span",{className:"block max-w-60 truncate font-medium",title:t,children:t}):(0,a.jsx)("span",{className:"text-muted-foreground",children:"-"})}},{id:"team_id",accessorKey:"team_id",meta:{title:"Team ID"},header:"Team ID",size:150,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(h.IdCell,{value:e.original.team_id,variant:"plain"})},{id:"created_at",accessorKey:"created_at",meta:{title:"Created"},header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Created"}),size:120,enableSorting:!0,cell:({row:e})=>(0,a.jsx)(x.DateCell,{value:e.original.created_at,precision:"date"})},{id:"spend",accessorKey:"spend",meta:{title:"Spend (USD)",numeric:!0},header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Spend (USD)"}),size:100,enableSorting:!0,cell:({row:e})=>(0,a.jsx)(p.MoneyCell,{value:e.original.spend,decimals:4})},{id:"max_budget",accessorKey:"max_budget",meta:{title:"Budget (USD)",numeric:!0},header:"Budget (USD)",size:110,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(p.MoneyCell,{value:e.original.max_budget,decimals:0,emptyText:"Unlimited",showZero:!0})},{id:"models",accessorKey:"models",meta:{title:"Models",skeleton:"chips"},header:"Models",size:200,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(S.ModelsCell,{models:e.original.models})},{id:"organization_id",accessorKey:"organization_id",meta:{title:"Organization"},header:"Organization",size:150,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(h.IdCell,{value:e.original.organization_id,variant:"plain"})},{id:"deleted_at",accessorKey:"deleted_at",meta:{title:"Deleted At"},header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Deleted At"}),size:120,enableSorting:!0,cell:({row:e})=>(0,a.jsx)(x.DateCell,{value:e.original.deleted_at,precision:"date"})},{id:"deleted_by",accessorKey:"deleted_by",meta:{title:"Deleted By"},header:"Deleted By",size:120,enableSorting:!1,cell:({row:e})=>{let t=e.original.deleted_by;return t?(0,a.jsx)("span",{className:"block max-w-60 truncate",title:t,children:t}):(0,a.jsx)("span",{className:"text-muted-foreground",children:"-"})}}],[]);return(0,a.jsx)(m.DataTable,{data:e,columns:n,getRowId:(e,a)=>e.team_id||String(a),sortingMode:"client",sorting:s,onSortingChange:i,isLoading:l,loadingMessage:"Loading deleted teams…",noDataMessage:(0,a.jsx)(T,{}),size:"compact"})}function k(){let{premiumUser:e}=(0,c.default)(),{data:t,isLoading:l}=(0,v.useDeletedTeams)(1,100);return(0,a.jsxs)("div",{className:"flex flex-col gap-4",children:[!e&&(0,a.jsx)(o.Alert,{type:"info",banner:!0,showIcon:!0,message:"Coming soon to Enterprise",description:"Deleted team auditing is graduating from beta into our Enterprise audit & compliance suite."}),(0,a.jsx)(N,{teams:t||[],isLoading:l})]})}var D=e.i(266027),M=e.i(619273),L=e.i(555987),w=e.i(602869),I=e.i(176516),z=e.i(981080),F=e.i(531649),K=e.i(793479),P=e.i(967489),O=e.i(997422),A=e.i(112179),E=e.i(304911);let Y={LiteLLM_VerificationToken:"Keys",LiteLLM_TeamTable:"Teams",LiteLLM_UserTable:"Users",LiteLLM_OrganizationTable:"Organizations",LiteLLM_ProxyModelTable:"Models"},H={created:"success",updated:"info",deleted:"error",rotated:"warning"},q=[{label:"Created",value:"created"},{label:"Updated",value:"updated"},{label:"Deleted",value:"deleted"},{label:"Rotated",value:"rotated"}],R=[{label:"Keys",value:"LiteLLM_VerificationToken"},{label:"Teams",value:"LiteLLM_TeamTable"},{label:"Users",value:"LiteLLM_UserTable"},{label:"Organizations",value:"LiteLLM_OrganizationTable"},{label:"Models",value:"LiteLLM_ProxyModelTable"}],U={object_id:"Object ID",changed_by:"Changed By",team_id:"Team ID",key_hash:"Key Hash",action:"Action",table_name:"Table"},B=(e,a)=>{let t=String(a);return"action"===e?q.find(e=>e.value===t)?.label??t:"table_name"===e?Y[t]??t:t};function V({filtered:e}){return(0,a.jsxs)("div",{className:"flex flex-col items-center gap-1 py-6",children:[(0,a.jsx)("div",{className:"mb-1 flex size-10 items-center justify-center rounded-lg bg-muted",children:(0,a.jsx)(I.ScrollText,{className:"size-5 text-muted-foreground"})}),(0,a.jsx)("div",{className:"text-sm font-medium text-foreground",children:e?"No matching audit logs":"No audit logs yet"}),(0,a.jsx)("div",{className:"max-w-xs text-center text-sm text-muted-foreground",children:e?"No audit log entries match your filters.":"Administrative changes to keys, teams, users, and models will appear here."})]})}function $({data:e,rowCount:l,isLoading:s,isRefreshing:i,pagination:n,onPaginationChange:r,columnFilters:o,onColumnFiltersChange:d,onRefresh:c,onViewLog:u}){let[g,p]=(0,t.useState)(!1),b=(0,t.useMemo)(()=>(({onViewLog:e})=>[{id:"updated_at",accessorKey:"updated_at",header:"Timestamp",size:200,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(x.DateCell,{value:e.original.updated_at})},{id:"action",accessorKey:"action",header:"Action",size:110,enableSorting:!1,cell:({row:e})=>{let t;return(0,a.jsx)(A.StatusBadge,{tone:H[e.original.action]??"neutral",label:(t=e.original.action)?t.charAt(0).toUpperCase()+t.slice(1):t})}},{id:"table_name",accessorKey:"table_name",header:"Table",size:130,enableSorting:!1,cell:({row:e})=>(0,a.jsx)("span",{className:"text-sm",children:Y[e.original.table_name]??e.original.table_name})},{id:"object_id",accessorKey:"object_id",header:"Object ID",minSize:220,enableSorting:!1,cell:({row:t})=>(0,a.jsx)(O.IdentityCell,{title:t.original.object_id,titleClassName:"font-mono text-xs font-normal text-primary",className:"max-w-72",onClick:()=>e(t.original)})},{id:"changed_by",accessorKey:"changed_by",header:"Changed By",size:200,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(E.default,{userId:e.original.changed_by})},{id:"changed_by_api_key",accessorKey:"changed_by_api_key",header:"API Key (Hash)",size:160,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(h.IdCell,{value:e.original.changed_by_api_key,variant:"plain"})}])({onViewLog:u}),[u]);return(0,a.jsx)(m.DataTable,{data:e,columns:b,getRowId:e=>e.id,paginationMode:"server",pagination:n,onPaginationChange:r,rowCount:l,filterMode:"server",columnFilters:o,onColumnFiltersChange:d,isLoading:s,loadingMessage:"Loading audit logs…",noDataMessage:(0,a.jsx)(V,{filtered:o.length>0}),size:"compact",toolbar:e=>(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(F.DataTableToolbar,{table:e,onRefresh:c,isRefreshing:i,onOpenFilters:()=>p(!0),filterLabels:U,formatFilterValue:B,showViewOptions:!1}),(0,a.jsx)(z.DataTableFilterDrawer,{table:e,open:g,onOpenChange:p,title:"Filters",description:"Narrow down audit log entries",children:({get:e,set:t})=>(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(z.DataTableFilterField,{label:"Object ID",children:(0,a.jsx)(K.Input,{value:e("object_id")??"",onChange:e=>t("object_id",e.target.value),placeholder:"Enter object ID…"})}),(0,a.jsx)(z.DataTableFilterField,{label:"Changed By",children:(0,a.jsx)(K.Input,{value:e("changed_by")??"",onChange:e=>t("changed_by",e.target.value),placeholder:"Enter user ID…"})}),(0,a.jsx)(z.DataTableFilterField,{label:"Team ID",children:(0,a.jsx)(K.Input,{value:e("team_id")??"",onChange:e=>t("team_id",e.target.value),placeholder:"Enter team ID…"})}),(0,a.jsx)(z.DataTableFilterField,{label:"Key Hash",children:(0,a.jsx)(K.Input,{value:e("key_hash")??"",onChange:e=>t("key_hash",e.target.value),placeholder:"Enter key hash…"})}),(0,a.jsx)(z.DataTableFilterField,{label:"Action",children:(0,a.jsxs)(P.Select,{value:e("action")??"all",onValueChange:e=>t("action","all"===e?void 0:e),children:[(0,a.jsx)(P.SelectTrigger,{className:"w-full",children:(0,a.jsx)(P.SelectValue,{placeholder:"All Actions"})}),(0,a.jsxs)(P.SelectContent,{children:[(0,a.jsx)(P.SelectItem,{value:"all",children:"All Actions"}),q.map(e=>(0,a.jsx)(P.SelectItem,{value:e.value,children:e.label},e.value))]})]})}),(0,a.jsx)(z.DataTableFilterField,{label:"Table",children:(0,a.jsxs)(P.Select,{value:e("table_name")??"all",onValueChange:e=>t("table_name","all"===e?void 0:e),children:[(0,a.jsx)(P.SelectTrigger,{className:"w-full",children:(0,a.jsx)(P.SelectValue,{placeholder:"All Tables"})}),(0,a.jsxs)(P.SelectContent,{children:[(0,a.jsx)(P.SelectItem,{value:"all",children:"All Tables"}),R.map(e=>(0,a.jsx)(P.SelectItem,{value:e.value,children:e.label},e.value))]})]})})]})})]})})}var Q=e.i(608856),J=e.i(262218),W=e.i(898586),G=e.i(149192),Z=e.i(166406),X=e.i(492030),ee=e.i(166540);let{Text:ea}=W.Typography,et={LiteLLM_VerificationToken:"Keys",LiteLLM_TeamTable:"Teams",LiteLLM_UserTable:"Users",LiteLLM_OrganizationTable:"Organizations",LiteLLM_ProxyModelTable:"Models"},el={created:"green",updated:"blue",deleted:"red",rotated:"orange"};function es({label:e,value:l}){let[s,i]=(0,t.useState)(!1),n=(0,t.useCallback)(async()=>{try{let e=JSON.stringify(l,null,2);if(navigator.clipboard&&window.isSecureContext)await navigator.clipboard.writeText(e);else{let a=document.createElement("textarea");a.value=e,a.style.position="fixed",a.style.opacity="0",document.body.appendChild(a),a.focus(),a.select(),document.execCommand("copy"),document.body.removeChild(a)}i(!0),setTimeout(()=>i(!1),2e3)}catch(e){console.error("Copy failed:",e)}},[l]);return(0,a.jsxs)("div",{className:"bg-white rounded-sm border overflow-hidden",children:[(0,a.jsxs)("div",{className:"flex justify-between items-center px-3 py-2 border-b bg-gray-50",children:[(0,a.jsx)("span",{className:"text-xs font-semibold text-gray-600",children:e}),(0,a.jsx)("button",{onClick:n,className:"p-1 hover:bg-gray-200 rounded-sm text-gray-500 hover:text-gray-700 transition-colors",title:"Copy JSON",children:s?(0,a.jsx)(X.CheckOutlined,{className:"text-green-600"}):(0,a.jsx)(Z.CopyOutlined,{})})]}),(0,a.jsx)("pre",{className:"p-3 bg-white text-xs font-mono overflow-auto max-h-96 whitespace-pre-wrap break-all m-0",children:JSON.stringify(l,null,2)})]})}function ei({label:e,value:t}){return(0,a.jsxs)("div",{className:"flex items-start gap-2 py-1.5",children:[(0,a.jsx)("span",{className:"text-xs text-gray-500 w-36 shrink-0",children:e}),(0,a.jsx)("span",{className:"text-xs text-gray-900 break-all",children:t})]})}function en({log:e}){let{action:t,table_name:l,before_value:s,updated_values:i}=e,n="LiteLLM_VerificationToken"===l,r="updated"===t||"rotated"===t,o=s,d=i;if(r&&s&&i){let e={},a={};new Set([...Object.keys(s),...Object.keys(i)]).forEach(t=>{JSON.stringify(s[t])!==JSON.stringify(i[t])&&(t in s&&(e[t]=s[t]),t in i&&(a[t]=i[t]))}),Object.keys(s).forEach(t=>{t in i||t in e||(e[t]=s[t],a[t]=void 0)}),Object.keys(i).forEach(t=>{t in s||t in a||(a[t]=i[t],e[t]=void 0)}),o=Object.keys(e).length>0?e:{note:"No differing fields detected"},d=Object.keys(a).length>0?a:{note:"No differing fields detected"}}let c=(e,t)=>{if(!t||0===Object.keys(t).length)return(0,a.jsxs)("div",{className:"bg-white rounded-sm border overflow-hidden",children:[(0,a.jsx)("div",{className:"flex items-center px-3 py-2 border-b bg-gray-50",children:(0,a.jsx)("span",{className:"text-xs font-semibold text-gray-600",children:e})}),(0,a.jsx)("p",{className:"px-3 py-3 text-xs text-gray-400 italic m-0",children:"N/A"})]});if(n&&r){let l=["token","spend","max_budget"];if(Object.keys(t).every(e=>l.includes(e))&&!("note"in t))return(0,a.jsxs)("div",{className:"bg-white rounded-sm border overflow-hidden",children:[(0,a.jsx)("div",{className:"flex items-center px-3 py-2 border-b bg-gray-50",children:(0,a.jsx)("span",{className:"text-xs font-semibold text-gray-600",children:e})}),(0,a.jsxs)("div",{className:"px-3 py-3 space-y-1 text-xs",children:[void 0!==t.token&&(0,a.jsxs)("p",{children:[(0,a.jsx)("span",{className:"text-gray-500",children:"Token:"})," ",t.token??"N/A"]}),void 0!==t.spend&&(0,a.jsxs)("p",{children:[(0,a.jsx)("span",{className:"text-gray-500",children:"Spend:"})," $",Number(t.spend).toFixed(6)]}),void 0!==t.max_budget&&(0,a.jsxs)("p",{children:[(0,a.jsx)("span",{className:"text-gray-500",children:"Max Budget:"})," $",Number(t.max_budget).toFixed(6)]})]})]})}return(0,a.jsx)(es,{label:e,value:t})};return(0,a.jsxs)("div",{className:"grid grid-cols-1 md:grid-cols-2 gap-4 mt-4",children:[c("Before",o),c("After",d)]})}function er({open:e,onClose:t,log:l}){if(!l)return null;let s=et[l.table_name]??l.table_name,i=el[l.action]??"default";return(0,a.jsxs)(Q.Drawer,{placement:"right",width:"60%",open:e,onClose:t,closable:!1,mask:!0,maskClosable:!0,styles:{body:{padding:0,display:"flex",flexDirection:"column"},header:{display:"none"}},children:[(0,a.jsxs)("div",{className:"flex items-center justify-between px-6 py-4 border-b bg-white shrink-0",children:[(0,a.jsxs)("div",{className:"flex items-center gap-3",children:[(0,a.jsx)(J.Tag,{color:i,className:"capitalize m-0",children:l.action}),(0,a.jsx)("span",{className:"text-sm text-gray-500",children:ee.default.utc(l.updated_at).local().format("MMM D, YYYY HH:mm:ss")})]}),(0,a.jsx)("button",{onClick:t,className:"w-8 h-8 flex items-center justify-center rounded-sm hover:bg-gray-100 text-gray-500","aria-label":"Close",children:(0,a.jsx)(G.CloseOutlined,{})})]}),(0,a.jsxs)("div",{className:"px-6 py-5",children:[(0,a.jsxs)("div",{className:"bg-gray-50 border rounded-lg p-4 mb-5",children:[(0,a.jsx)("p",{className:"text-xs font-semibold text-gray-700 mb-2 uppercase tracking-wide",children:"Details"}),(0,a.jsx)(ei,{label:"Table",value:s}),(0,a.jsx)(ei,{label:"Object ID",value:(0,a.jsx)(ea,{copyable:!0,className:"font-mono text-xs",children:l.object_id})}),(0,a.jsx)(ei,{label:"Changed By",value:(0,a.jsx)(E.default,{userId:l.changed_by})}),(0,a.jsx)(ei,{label:"API Key (Hash)",value:l.changed_by_api_key?(0,a.jsx)(ea,{copyable:!0,className:"font-mono text-xs break-all",children:l.changed_by_api_key}):"—"})]}),(0,a.jsx)(en,{log:l})]})]})}function eo({userID:e,userRole:l,token:s,accessToken:i,isActive:n,premiumUser:r}){let[o,d]=(0,t.useState)({pageIndex:0,pageSize:50}),[c,u]=(0,t.useState)([]),[m,g]=(0,t.useState)(null),[x,h]=(0,t.useState)(!1),p=e=>{let a=c.find(a=>a.id===e);return"string"==typeof a?.value&&a.value.trim()?a.value.trim():void 0},b=!!i&&!!s&&!!l&&!!e&&n&&r,j=(0,D.useQuery)({queryKey:["audit_logs",o.pageIndex,o.pageSize,c],queryFn:async()=>i?(0,w.uiAuditLogsCall)({accessToken:i,page:o.pageIndex+1,page_size:o.pageSize,params:{object_id:p("object_id"),changed_by:p("changed_by"),object_key_hash:p("key_hash"),object_team_id:p("team_id"),action:p("action"),table_name:p("table_name"),sort_by:"updated_at",sort_order:"desc"}}):{audit_logs:[],total:0,page:1,page_size:o.pageSize,total_pages:0},enabled:b,placeholderData:M.keepPreviousData}),f=(0,t.useCallback)(e=>{u(e),d(e=>({...e,pageIndex:0}))},[]),y=(0,t.useCallback)(e=>{g(e),h(!0)},[]);return r?(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)("div",{className:"flex items-center justify-between mb-4",children:(0,a.jsx)("h1",{className:"text-xl font-semibold",children:"Audit Logs"})}),(0,a.jsx)($,{data:j.data?.audit_logs??[],rowCount:j.data?.total??0,isLoading:j.isLoading,isRefreshing:j.isFetching,pagination:o,onPaginationChange:d,columnFilters:c,onColumnFiltersChange:f,onRefresh:()=>j.refetch(),onViewLog:y}),(0,a.jsx)(er,{open:x,onClose:()=>h(!1),log:m})]}):(0,a.jsxs)("div",{style:{textAlign:"center",marginTop:"20px"},children:[(0,a.jsx)("h1",{style:{display:"block",marginBottom:"10px"},children:"✨ Enterprise Feature."}),(0,a.jsx)("p",{style:{display:"block",marginBottom:"10px"},children:"This is a LiteLLM Enterprise feature, and requires a valid key to use."}),(0,a.jsx)("p",{style:{display:"block",marginBottom:"20px",fontStyle:"italic"},children:"Here's a preview of what Audit Logs offer:"}),(0,a.jsx)("img",{src:(0,L.resolveLogoSrc)("/ui/assets/audit-logs-preview.png"),alt:"Audit Logs Preview",style:{maxWidth:"100%",maxHeight:"700px",borderRadius:"8px",boxShadow:"0 4px 8px rgba(0,0,0,0.1)",margin:"0 auto"},onError:e=>{e.target.style.display="none"}})]})}var ed=e.i(548151),ec=e.i(708347),eu=e.i(20147),em=e.i(97859);let eg=async(e,a)=>{if(!e)return[];try{let t=[],l=1,s=!0;for(;s;){let i=await (0,w.teamListCall)(e,a||null,null);t=[...t,...i],l({start_date:(0,ee.default)(e).utc().format("YYYY-MM-DD HH:mm:ss"),end_date:t?(0,ee.default)(a).utc().format("YYYY-MM-DD HH:mm:ss"):(0,ee.default)(l).utc().format("YYYY-MM-DD HH:mm:ss")}),eM=[{id:"startTime",desc:!0}],eL=(e,a)=>{let t=e.find(e=>e.id===a);if("string"!=typeof t?.value)return;let l=t.value.trim();return""===l?void 0:l};e.i(3565);var ew=e.i(502626);let eI=(0,e.i(475254).default)("calendar-days",[["path",{d:"M8 2v4",key:"1cmpym"}],["path",{d:"M16 2v4",key:"4m81vk"}],["rect",{width:"18",height:"18",x:"3",y:"4",rx:"2",key:"1hopcy"}],["path",{d:"M3 10h18",key:"8toen8"}],["path",{d:"M8 14h.01",key:"6423bh"}],["path",{d:"M12 14h.01",key:"1etili"}],["path",{d:"M16 14h.01",key:"1gbofw"}],["path",{d:"M8 18h.01",key:"lrp35t"}],["path",{d:"M12 18h.01",key:"mhygvu"}],["path",{d:"M16 18h.01",key:"kzsmim"}]]);var ez=e.i(519455),eF=e.i(337822),eK=e.i(699375);function eP({startTime:e,onStartTimeChange:l,endTime:s,onEndTimeChange:i,isCustomDate:n,onIsCustomDateChange:r,selectedTimeInterval:o,onSelectedTimeIntervalChange:d,isLiveTail:c,onIsLiveTailChange:u,onResetToFirstPage:m,onResetFilters:g}){let[x,h]=(0,t.useState)(!1),p=em.QUICK_SELECT_OPTIONS.find(e=>e.value===o.value&&e.unit===o.unit),b=n?((e,a,t)=>{if(e)return`${(0,ee.default)(a).format("MMM D, h:mm A")} - ${(0,ee.default)(t).format("MMM D, h:mm A")}`;let l=(0,ee.default)(),s=(0,ee.default)(a),i=l.diff(s,"minutes");if(i>=0&&i<2)return"Last 1 Minute";if(i>=2&&i<16)return"Last 15 Minutes";if(i>=16&&i<61)return"Last Hour";let n=l.diff(s,"hours");return n>=1&&n<5?"Last 4 Hours":n>=5&&n<25?"Last 24 Hours":n>=25&&n<169?"Last 7 Days":`${s.format("MMM D")} - ${l.format("MMM D")}`})(n,e,s):p?.label;return(0,a.jsxs)("div",{className:"flex flex-wrap items-center gap-2",children:[(0,a.jsxs)(eF.Popover,{open:x,onOpenChange:h,children:[(0,a.jsx)(eF.PopoverTrigger,{render:(0,a.jsxs)(ez.Button,{variant:"outline",size:"sm",className:"gap-2",children:[(0,a.jsx)(eI,{className:"size-4"}),b]})}),(0,a.jsx)(eF.PopoverContent,{align:"start",className:"w-64 p-2",children:(0,a.jsxs)("div",{className:"space-y-1",children:[em.QUICK_SELECT_OPTIONS.map(e=>(0,a.jsx)(ez.Button,{variant:"ghost",className:"w-full justify-start font-normal",onClick:()=>{m(),i((0,ee.default)().format("YYYY-MM-DDTHH:mm")),l((0,ee.default)().subtract(e.value,e.unit).format("YYYY-MM-DDTHH:mm")),d({value:e.value,unit:e.unit}),r(!1),h(!1)},children:e.label},e.label)),(0,a.jsx)("div",{className:"my-2 border-t"}),(0,a.jsx)(ez.Button,{variant:"ghost",className:"w-full justify-start font-normal",onClick:()=>r(!n),children:"Custom Range"})]})})]}),n&&(0,a.jsxs)("div",{className:"flex items-center gap-2",children:[(0,a.jsx)(K.Input,{type:"datetime-local",className:"w-auto",value:e,onChange:e=>{l(e.target.value),m()}}),(0,a.jsx)("span",{className:"text-sm text-muted-foreground",children:"to"}),(0,a.jsx)(K.Input,{type:"datetime-local",className:"w-auto",value:s,onChange:e=>{i(e.target.value),m()}})]}),(0,a.jsxs)("div",{className:"flex items-center gap-2",children:[(0,a.jsx)("span",{className:"text-sm font-medium",children:"Live Tail"}),(0,a.jsx)(eK.Switch,{checked:c,onCheckedChange:u,"aria-label":"Live Tail"})]}),(0,a.jsx)(ez.Button,{variant:"outline",size:"sm",onClick:g,children:"Reset Filters"})]})}function eO({onStop:e}){return(0,a.jsxs)("div",{className:"mb-4 flex items-center justify-between rounded-md border border-green-200 bg-green-50 px-4 py-2",children:[(0,a.jsx)("span",{className:"text-sm text-green-700",children:"Auto-refreshing every 15 seconds"}),(0,a.jsx)("button",{type:"button",onClick:e,className:"text-sm text-green-600 hover:text-green-800",children:"Stop"})]})}var eA=e.i(768371);let eE=e=>{let a=e.links.next;if(!a)return;let t=new URLSearchParams(a.slice(a.indexOf("?")+1)).get("page");return null===t?void 0:Number(t)};var eY=e.i(621482);let eH=(0,e.i(243652).createQueryKeys)("infiniteKeyAliases");var eq=e.i(625901),eR=e.i(744582),eU=e.i(552546),eB=e.i(131792);let eV=e=>""===e?void 0:e;function e$({value:e,onChange:l,teams:s}){let i=(0,t.useMemo)(()=>s.map(e=>({label:e.team_alias||e.team_id,value:e.team_id,sublabel:e.team_id})),[s]);return(0,a.jsx)(z.DataTableFilterField,{label:"Team ID",children:(0,a.jsx)(eU.SearchSelect,{options:i,value:e,onValueChange:e=>l(eV(e)),placeholder:"Search or select a team",emptyText:"No teams found"})})}function eQ({value:e,onChange:l,teamId:s}){let[i,n]=(0,t.useState)(""),{data:r,fetchNextPage:o,hasNextPage:d,isFetchingNextPage:u,isLoading:m}=((e=50,a,t)=>{let{accessToken:l}=(0,c.default)();return(0,eY.useInfiniteQuery)({queryKey:eH.list({filters:{size:e,...a&&{search:a},...t&&{team_id:t}}}),queryFn:async({pageParam:s})=>await (0,w.keyAliasesCall)(l,s,e,a,t),initialPageParam:1,getNextPageParam:e=>{if(e.current_page{let e=new Set;return(r?.pages??[]).flatMap(a=>a.aliases.flatMap(a=>!a||e.has(a)?[]:(e.add(a),[{label:a,value:a}])))},[r]);return(0,a.jsx)(z.DataTableFilterField,{label:"Key Alias",children:(0,a.jsx)(eR.PaginatedSearchSelect,{options:g,value:e,onValueChange:e=>l(eV(e)),onSearchChange:n,onLoadMore:()=>void o(),hasNextPage:d,isLoading:m,isFetchingNextPage:u,placeholder:"Search a key alias",emptyText:"No key aliases found"})})}function eJ({value:e,onChange:l}){let[s,i]=(0,t.useState)(""),{data:n,fetchNextPage:r,hasNextPage:o,isFetchingNextPage:d,isLoading:c}=(0,eq.useInfiniteModelInfo)(50,eV(s)),u=(0,t.useMemo)(()=>{let e=new Set;return(n?.pages??[]).flatMap(a=>a.data.flatMap(a=>{let t=a.model_info?.id??"",l=a.model_name??"";return!t||e.has(t)?[]:(e.add(t),[{label:l||t,value:t,sublabel:`Model ID: ${t}`}])}))},[n]);return(0,a.jsx)(z.DataTableFilterField,{label:"Model",children:(0,a.jsx)(eR.PaginatedSearchSelect,{options:u,value:e,onValueChange:e=>l(eV(e)),onSearchChange:i,onLoadMore:()=>void r(),hasNextPage:o,isLoading:c,isFetchingNextPage:d,placeholder:"Search a model",emptyText:"No models found"})})}function eW({value:e,onChange:l,logsWindow:s}){let[i,n]=(0,t.useState)(""),{data:r,fetchNextPage:o,hasNextPage:d,isFetchingNextPage:u,isLoading:m}=((e,a=50,t)=>{let{accessToken:l}=(0,c.default)(),s={"filter[startTime][gte]":e.start_date,"filter[startTime][lte]":e.end_date,page_size:a,...void 0!==t&&""!==t?{q:t}:{}};return eA.$api.useInfiniteQuery("get","/management/v1/spend_logs/end_users",{params:{query:s}},{pageParamName:"page",initialPageParam:1,getNextPageParam:eE,enabled:!!l})})(s,50,eV(i)),g=(0,t.useMemo)(()=>{let e=new Set;return(r?.pages??[]).flatMap(a=>a.data.flatMap(a=>!a||e.has(a)?[]:(e.add(a),[{label:a,value:a}])))},[r]);return(0,a.jsx)(z.DataTableFilterField,{label:"End User",children:(0,a.jsx)(eR.PaginatedSearchSelect,{options:g,value:e,onValueChange:e=>l(eV(e)),onSearchChange:n,onLoadMore:()=>void o(),hasNextPage:d,isLoading:m,isFetchingNextPage:u,placeholder:"Search an end user",emptyText:"No end users in this time range"})})}function eG({value:e,onChange:l}){let[s,i]=(0,t.useState)(""),n=(0,t.useMemo)(()=>{let e=s.trim(),a=e.toLowerCase(),t=em.ERROR_CODE_OPTIONS.filter(e=>e.label.toLowerCase().includes(a));return""===e||em.ERROR_CODE_OPTIONS.some(a=>a.value===e)?t:[...t,{label:`Use custom code: ${e}`,value:e}]},[s]),r=(0,t.useMemo)(()=>""===e?null:em.ERROR_CODE_OPTIONS.find(a=>a.value===e)??{label:e,value:e},[e]),o=(0,t.useMemo)(()=>null===r||n.some(e=>e.value===r.value)?n:[r,...n],[n,r]);return(0,a.jsx)(z.DataTableFilterField,{label:"Error Code",children:(0,a.jsxs)(eB.Combobox,{items:o,value:r,onValueChange:e=>l(eV(e?.value??"")),onInputValueChange:i,isItemEqualToValue:(e,a)=>e.value===a.value,itemToStringLabel:e=>e.label,filter:null,children:[(0,a.jsx)(eB.ComboboxInput,{placeholder:"Select or type an error code",showClear:""!==e,className:"w-full"}),(0,a.jsxs)(eB.ComboboxContent,{children:[(0,a.jsx)(eB.ComboboxEmpty,{children:"No error codes found"}),(0,a.jsx)(eB.ComboboxList,{"data-testid":"error-code-filter-list",children:e=>(0,a.jsx)(eB.ComboboxItem,{value:e,children:e.label},e.value)})]})]})})}function eZ({get:e,set:t,teams:l,logsWindow:s}){let i=a=>{let t;return"string"==typeof(t=e(a))?t:""},n=e=>a=>t(e,a);return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(e$,{value:i(ep),onChange:n(ep),teams:l}),(0,a.jsx)(z.DataTableFilterField,{label:"Status",children:(0,a.jsxs)(P.Select,{value:""===i(eb)?"all":i(eb),onValueChange:e=>t(eb,null===e||"all"===e?void 0:e),children:[(0,a.jsx)(P.SelectTrigger,{className:"w-full",children:(0,a.jsx)(P.SelectValue,{placeholder:"All Statuses"})}),(0,a.jsxs)(P.SelectContent,{children:[(0,a.jsx)(P.SelectItem,{value:"all",children:"All Statuses"}),(0,a.jsx)(P.SelectItem,{value:"success",children:"Success"}),(0,a.jsx)(P.SelectItem,{value:"failure",children:"Failure"})]})]})}),(0,a.jsx)(eQ,{value:i(ej),onChange:n(ej),teamId:i(ep)}),(0,a.jsx)(eW,{value:i(ef),onChange:n(ef),logsWindow:s}),(0,a.jsx)(eG,{value:i(ey),onChange:n(ey)}),(0,a.jsx)(z.DataTableFilterField,{label:"Error Message",children:(0,a.jsx)(K.Input,{value:i(e_),onChange:e=>t(e_,eV(e.target.value)),placeholder:"Enter error message…"})}),(0,a.jsx)(z.DataTableFilterField,{label:"Key Hash",children:(0,a.jsx)(K.Input,{value:i(ev),onChange:e=>t(ev,eV(e.target.value)),placeholder:"Enter key hash…"})}),(0,a.jsx)(z.DataTableFilterField,{label:"Session ID",children:(0,a.jsx)(K.Input,{value:i(eS),onChange:e=>t(eS,eV(e.target.value)),placeholder:"Enter session ID…"})}),(0,a.jsx)(eJ,{value:i(eC),onChange:n(eC)}),(0,a.jsx)(z.DataTableFilterField,{label:"Public model / search tool",children:(0,a.jsx)(K.Input,{value:i(eT),onChange:e=>t(eT,eV(e.target.value)),placeholder:"Enter public model or search tool…"})})]})}var eX=e.i(581070),e0=e.i(500330),e1=e.i(916925);let e2=({size:e=12})=>(0,a.jsx)("svg",{width:e,height:e,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"shrink-0 text-gray-400",children:(0,a.jsx)("path",{d:"M12 3l1.912 5.813a2 2 0 0 0 1.275 1.275L21 12l-5.813 1.912a2 2 0 0 0-1.275 1.275L12 21l-1.912-5.813a2 2 0 0 0-1.275-1.275L3 12l5.813-1.912a2 2 0 0 0 1.275-1.275L12 3z"})}),e5=({size:e=10})=>(0,a.jsx)("svg",{width:e,height:e,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"shrink-0",children:(0,a.jsx)("path",{d:"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"})}),e4=({size:e=12})=>(0,a.jsxs)("svg",{width:e,height:e,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"shrink-0",children:[(0,a.jsx)("path",{d:"M12 8V4H8"}),(0,a.jsx)("rect",{width:"16",height:"12",x:"4",y:"8",rx:"2"}),(0,a.jsx)("path",{d:"M2 14h2"}),(0,a.jsx)("path",{d:"M20 14h2"}),(0,a.jsx)("path",{d:"M15 13v2"}),(0,a.jsx)("path",{d:"M9 13v2"})]}),e6=({count:e})=>(0,a.jsxs)("span",{className:"inline-flex items-center gap-1 px-2 py-0.5 bg-blue-50 text-blue-700 border border-blue-200 rounded-full text-[11px] font-medium whitespace-nowrap",children:[(0,a.jsx)(e2,{}),null!=e?e:"LLM"]}),e7=({count:e})=>(0,a.jsxs)("span",{className:"inline-flex items-center gap-1 px-2 py-0.5 bg-amber-50 text-amber-700 border border-amber-200 rounded-full text-[11px] font-medium whitespace-nowrap",children:[(0,a.jsx)(e5,{}),null!=e?e:"MCP"]}),e3=({count:e})=>(0,a.jsxs)("span",{className:"inline-flex items-center gap-1 px-2 py-0.5 bg-violet-50 text-violet-700 border border-violet-200 rounded-full text-[11px] font-medium whitespace-nowrap",children:[(0,a.jsx)(e4,{}),null!=e?e:"Agent"]}),e8=(e,a)=>{let t=e?.[a];return"string"==typeof t&&""!==t?t:void 0};function e9({value:e}){let t=e??"-";return(0,a.jsx)(eX.CellTooltip,{content:t,trigger:(0,a.jsx)("span",{className:"max-w-[15ch] truncate block",children:t})})}function ae({filtered:e}){return(0,a.jsxs)("div",{className:"flex flex-col items-center gap-1 py-6",children:[(0,a.jsx)("div",{className:"mb-1 flex size-10 items-center justify-center rounded-lg bg-muted",children:(0,a.jsx)(I.ScrollText,{className:"size-5 text-muted-foreground"})}),(0,a.jsx)("div",{className:"text-sm font-medium text-foreground",children:e?"No matching requests":"No requests yet"}),(0,a.jsx)("div",{className:"max-w-xs text-center text-sm text-muted-foreground",children:e?"No requests match your filters for this time range.":"Requests proxied through LiteLLM will appear here."})]})}function aa({data:e,rowCount:l,isLoading:s,isRefreshing:i,pagination:n,onPaginationChange:r,sorting:o,onSortingChange:d,columnFilters:c,onColumnFiltersChange:u,searchValue:b,onSearchChange:j,onRefresh:f,onRowClick:y,onKeyHashClick:_,onSessionClick:v,teams:S,logsWindow:C,toolbarChildren:T}){let[N,k]=(0,t.useState)(!1),D=(0,t.useMemo)(()=>(({onKeyHashClick:e,onSessionClick:t})=>[{id:"startTime",accessorKey:"startTime",header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Time",variant:"dropdown-tristate"}),size:200,enableSorting:!0,cell:({row:e})=>(0,a.jsx)(x.DateCell,{value:e.original.startTime})},{id:"type",header:"Type",size:90,enableSorting:!1,meta:{skeleton:"badge"},cell:({row:e})=>{let t=e.original,l=t.session_total_count||1,s=em.MCP_CALL_TYPES.includes(t.call_type),i=em.AGENT_CALL_TYPES.includes(t.call_type),n=t.session_llm_count??(s||i?0:l),r=t.session_agent_count??(i?l:0),o=t.session_mcp_count??(s?l:0);if(s)return(0,a.jsx)(e7,{});if(i&&l<=1)return(0,a.jsx)(e3,{});if(l<=1)return(0,a.jsx)(e6,{});let d=(0,a.jsxs)("span",{className:"inline-flex items-center gap-1 px-2 py-0.5 bg-blue-50 text-blue-700 border border-blue-200 rounded-full text-[11px] font-medium whitespace-nowrap",children:[(0,a.jsx)(e2,{}),(0,a.jsx)("span",{children:l}),r>0&&(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)("span",{className:"text-blue-300",children:"·"}),(0,a.jsx)(e4,{size:10})]}),o>0&&(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)("span",{className:"text-blue-300",children:"·"}),(0,a.jsx)(e5,{})]})]}),c=[n>0&&`${n} LLM`,r>0&&`${r} Agent`,o>0&&`${o} MCP`].filter(Boolean);return(0,a.jsx)(eX.CellTooltip,{content:c.join(" • "),trigger:d})}},{id:"status",header:"Status",size:100,enableSorting:!1,meta:{skeleton:"badge"},cell:({row:e})=>{let t="failure"!==(e8(e.original.metadata,"status")??"Success").toLowerCase();return(0,a.jsx)(A.StatusBadge,{tone:t?"success":"error",label:t?"Success":"Failure"})}},{id:"session_id",accessorKey:"session_id",header:"Session ID",size:120,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(h.IdCell,{value:e.original.session_id,onClick:t})},{id:"request_id",accessorKey:"request_id",header:"Request ID",enableSorting:!1,cell:({row:e})=>(0,a.jsx)(h.IdCell,{value:e.original.request_id,variant:"plain"})},{id:"spend",accessorKey:"spend",header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Cost",variant:"dropdown-tristate"}),size:110,enableSorting:!0,meta:{numeric:!0,skeleton:"twoLine"},cell:({row:e})=>{let t=e.original,l=t.mcp_tool_call_count||0,s=t.mcp_tool_call_spend||0,i=(t.session_total_count||1)>1,n=i&&null!=t.session_total_spend?t.session_total_spend:t.spend,r=(0,a.jsx)("span",{children:(0,a.jsx)(p.MoneyCell,{value:n,decimals:6})});return(0,a.jsxs)("div",{className:"flex flex-col items-end",children:[n?(0,a.jsx)(eX.CellTooltip,{content:`$${String(n)}`,trigger:r}):r,i&&(0,a.jsx)("span",{className:"text-[10px] text-gray-400",children:"session total"}),l>0&&s>0&&(0,a.jsxs)("span",{className:"text-[10px] text-amber-600",children:["incl. ",(0,e0.getSpendString)(s)," from ",l," MCP"]})]})}},{id:"request_duration_ms",accessorKey:"request_duration_ms",header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Duration (s)",variant:"dropdown-tristate"}),enableSorting:!0,meta:{numeric:!0},cell:({row:e})=>{let t=e.original.request_duration_ms;return null==t?(0,a.jsx)("span",{children:"-"}):(0,a.jsx)(eX.CellTooltip,{content:`${t}ms`,trigger:(0,a.jsx)("span",{className:"max-w-[15ch] truncate inline-block",children:(t/1e3).toFixed(2)})})}},{id:"ttft_ms",accessorKey:"completionStartTime",header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"TTFT (s)",variant:"dropdown-tristate"}),enableSorting:!0,meta:{numeric:!0},cell:({row:e})=>{let t=e.original,l=t.completionStartTime;if(!l||l===t.endTime)return(0,a.jsx)("span",{children:"-"});let s=new Date(l).getTime()-new Date(t.startTime).getTime();return s<=0?(0,a.jsx)("span",{children:"-"}):(0,a.jsx)(eX.CellTooltip,{content:`${s}ms`,trigger:(0,a.jsx)("span",{className:"max-w-[15ch] truncate inline-block",children:(s/1e3).toFixed(2)})})}},{id:"team_alias",header:"Team Name",size:150,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(e9,{value:e8(e.original.metadata,"user_api_key_team_alias")})},{id:"key_hash",header:"Key Hash",size:110,enableSorting:!1,cell:({row:t})=>(0,a.jsx)(h.IdCell,{value:e8(t.original.metadata,"user_api_key"),variant:"plain",onClick:e})},{id:"key_alias",header:"Key Alias",size:150,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(e9,{value:e8(e.original.metadata,"user_api_key_alias")})},{id:"model",accessorKey:"model",header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Model",variant:"dropdown-tristate"}),size:200,enableSorting:!0,cell:({row:e})=>{let t=e.original,l=t.custom_llm_provider,s=t.model??"";return(0,a.jsxs)("div",{className:"flex items-center space-x-2",children:[l&&(0,a.jsx)("img",{src:(e=>{let a=e?.mcp_tool_call_metadata;if("object"!=typeof a||null===a)return;let t=a.mcp_server_logo_url;return"string"==typeof t&&""!==t?t:void 0})(t.metadata)??(l?(0,e1.getProviderLogoAndName)(l).logo:""),alt:"",className:"w-4 h-4",onError:e=>{e.currentTarget.style.display="none"}}),(0,a.jsx)(eX.CellTooltip,{content:s,trigger:(0,a.jsx)("span",{className:"max-w-[15ch] truncate block",children:s})})]})}},{id:"total_tokens",accessorKey:"total_tokens",header:({column:e})=>(0,a.jsx)(g.DataTableSortHeader,{column:e,title:"Tokens",variant:"dropdown-tristate"}),size:140,enableSorting:!0,meta:{numeric:!0},cell:({row:e})=>{let t=e.original;return(0,a.jsxs)("span",{className:"text-sm",children:[String(t.total_tokens||"0"),(0,a.jsxs)("span",{className:"text-gray-400 text-xs ml-1",children:["(",String(t.prompt_tokens||"0"),"+",String(t.completion_tokens||"0"),")"]})]})}},{id:"user",accessorKey:"user",header:"Internal User",size:150,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(e9,{value:e.original.user})},{id:"end_user",accessorKey:"end_user",header:"End User",size:140,enableSorting:!1,cell:({row:e})=>(0,a.jsx)(e9,{value:e.original.end_user})},{id:"request_tags",accessorKey:"request_tags",header:"Tags",size:150,enableSorting:!1,meta:{skeleton:"chips"},cell:({row:e})=>{let t=e.original.request_tags;if(!t||0===Object.keys(t).length)return"-";let l=Object.entries(t),[s,i]=l[0],n=l.length-1;return(0,a.jsx)("div",{className:"flex flex-wrap gap-1",children:(0,a.jsx)(eX.CellTooltip,{content:(0,a.jsx)("div",{className:"flex flex-col gap-1",children:l.map(([e,t])=>(0,a.jsxs)("span",{children:[e,": ",String(t)]},e))}),trigger:(0,a.jsxs)("span",{className:"px-2 py-1 bg-gray-100 rounded-full text-xs",children:[s,": ",String(i),n>0&&` +${n}`]})})})}}])({onKeyHashClick:_,onSessionClick:v}),[_,v]),M=c.length>0||""!==b;return(0,a.jsx)(m.DataTable,{data:e,columns:D,getRowId:e=>e.request_id,sortingMode:"server",sorting:o,onSortingChange:d,paginationMode:"server",pagination:n,onPaginationChange:r,rowCount:l,filterMode:"server",columnFilters:c,onColumnFiltersChange:u,isLoading:s,loadingMessage:"Loading request logs…",noDataMessage:(0,a.jsx)(ae,{filtered:M}),size:"compact",onRowClick:y,toolbar:e=>(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(F.DataTableToolbar,{table:e,searchValue:b,onSearchChange:j,searchPlaceholder:"Search by Request ID",onRefresh:f,isRefreshing:i,onOpenFilters:()=>k(!0),filterLabels:ek,showViewOptions:!1,children:T}),(0,a.jsx)(z.DataTableFilterDrawer,{table:e,open:N,onOpenChange:k,title:"Filters",description:"Narrow down request logs",children:({get:e,set:t})=>(0,a.jsx)(eZ,{get:e,set:t,teams:S,logsWindow:C})})]})})}let at={value:24,unit:"hours"};function al({accessToken:e,token:l,userRole:s,userID:i,isActive:n}){let[r,o]=(0,t.useState)({pageIndex:0,pageSize:50}),[d,c]=(0,t.useState)(eM),[u,m]=(0,t.useState)([]),[g,x]=(0,t.useState)((0,ee.default)().subtract(24,"hours").format("YYYY-MM-DDTHH:mm")),[h,p]=(0,t.useState)((0,ee.default)().format("YYYY-MM-DDTHH:mm")),[b,j]=(0,t.useState)(!1),[f,y]=(0,t.useState)(at),[_,v]=(0,t.useState)(null),[S,C]=(0,t.useState)(null),[T,N]=(0,t.useState)(!1),[k,L]=(0,t.useState)(null),[I,z]=(0,t.useState)(()=>{let e=sessionStorage.getItem("isLiveTail");return null===e||JSON.parse(e)});(0,t.useEffect)(()=>{sessionStorage.setItem("isLiveTail",JSON.stringify(I))},[I]);let F=ec.internalUserRoles.includes(s),{logsQuery:K,filteredLogs:P,allTeams:O}=function({accessToken:e,token:a,userRole:t,userID:l,columnFilters:s,filterByCurrentUser:i,activeTab:n,isLiveTail:r,startTime:o,endTime:d,pagination:c,isCustomDate:u,sorting:m}){let g,x=c.pageSize||ex.defaultPageSize,h=m[0]??eM[0],p=Object.hasOwn(eh,h.id)?h.id:"startTime",b=h.desc?"desc":"asc",j={queryKey:["logs","table",c.pageIndex,x,o,d,u,s,i?l:null,p,b],queryFn:async()=>{if(!e||!a||!t||!l)return{data:[],total:0,page:1,page_size:x,total_pages:0};let n=eD(o,d,u),r=eL(s,"user_id");return await (0,w.uiSpendLogsCall)({accessToken:e,start_date:n.start_date,end_date:n.end_date,page:c.pageIndex+1,page_size:x,params:{api_key:eL(s,ev),team_id:eL(s,ep),request_id:eL(s,eN),session_id:eL(s,eS),user_id:r??(i?l??void 0:void 0),end_user:eL(s,ef),status_filter:eL(s,eb),model_id:eL(s,eC),model:eL(s,eT),key_alias:eL(s,ej),error_code:eL(s,ey),error_message:eL(s,e_),sort_by:p,sort_order:b}})},enabled:!!e&&!!a&&!!t&&!!l&&"request logs"===n,refetchInterval:(g=c.pageIndex,!!r&&0===g&&15e3),placeholderData:M.keepPreviousData,refetchIntervalInBackground:!1},f=(0,D.useQuery)(j),y=f.data??{data:[],total:0,page:1,page_size:x,total_pages:0},{data:_}=(0,D.useQuery)({queryKey:["allTeamsForLogFilters",e],queryFn:async()=>e&&await eg(e)||[],enabled:!!e});return{logsQuery:f,filteredLogs:y,allTeams:_}}({accessToken:e,token:l,userRole:s,userID:i,columnFilters:u,filterByCurrentUser:F,activeTab:n?"request logs":"inactive",isLiveTail:I,startTime:g,endTime:h,pagination:r,isCustomDate:b,sorting:d}),A=(Math.floor((K.dataUpdatedAt||Date.parse(h))/6e4)+1)*6e4,E=(0,t.useMemo)(()=>eD(g,h,b,A),[g,h,b,A]),{data:Y}=(0,D.useQuery)({queryKey:["requestLogsKeyInfo",_,e],queryFn:async()=>null===_?null:{...(await (0,w.keyInfoV1Call)(e,_)).info,token:_,api_key:_},enabled:null!==_}),H=(0,t.useMemo)(()=>{let e=P.data,a=e.reduce((e,a)=>(a.session_id&&(e[a.session_id]||(e[a.session_id]={llm:0,agent:0,mcp:0}),em.MCP_CALL_TYPES.includes(a.call_type)?e[a.session_id].mcp+=1:em.AGENT_CALL_TYPES.includes(a.call_type)?e[a.session_id].agent+=1:e[a.session_id].llm+=1),e),{}),t=new Map;for(let a of e){if(!a.session_id||1>=(a.session_total_count||1))continue;let e=em.MCP_CALL_TYPES.includes(a.call_type),l=t.get(a.session_id);l&&(!l.isMcp||e)||t.set(a.session_id,{requestId:a.request_id,isMcp:e})}return e.map(e=>{let t=e.session_id?a[e.session_id]:void 0;return{...e,session_llm_count:t?.llm??void 0,session_mcp_count:t?.mcp??void 0,session_agent_count:t?.agent??void 0}}).filter(e=>!e.session_id||1>=(e.session_total_count||1)||t.get(e.session_id)?.requestId===e.request_id)},[P.data]),q=(0,t.useMemo)(()=>{let e=u.find(e=>e.id===eN);return"string"==typeof e?.value?e.value:""},[u]),R=(0,t.useCallback)(e=>{m(a=>{let t=a.filter(e=>e.id!==eN);return""===e?t:[...t,{id:eN,value:e}]}),o(e=>({...e,pageIndex:0}))},[]),U=(0,t.useCallback)(e=>{c(e),o(e=>({...e,pageIndex:0}))},[]),B=(0,t.useCallback)(e=>{m(e),o(e=>({...e,pageIndex:0}))},[]),V=(0,t.useCallback)(()=>{o(e=>({...e,pageIndex:0}))},[]),$=(0,t.useCallback)(()=>{m([]),x((0,ee.default)().subtract(24,"hours").format("YYYY-MM-DDTHH:mm")),p((0,ee.default)().format("YYYY-MM-DDTHH:mm")),j(!1),y(at),V()},[V]),Q=(0,t.useCallback)(e=>{L(void 0!==e.session_id&&(e.session_total_count||1)>1?e.session_id??null:null),C(e),N(!0)},[]),J=(0,t.useCallback)(e=>{if(!e)return;let a=H.find(a=>a.session_id===e)??null;L(e),C(a),N(!0)},[H]),W=(0,t.useCallback)(e=>{v(e)},[]);return Y&&_&&Y.api_key===_?(0,a.jsx)(eu.default,{keyId:_,keyData:Y,teams:O??[],onClose:()=>v(null),backButtonText:"Back to Logs"}):(0,a.jsxs)(ed.AutoRouterModelGroupsProvider,{children:[(0,a.jsx)("div",{className:"flex items-center justify-between mb-4",children:(0,a.jsx)("h1",{className:"text-xl font-semibold",children:"Request Logs"})}),I&&0===r.pageIndex&&(0,a.jsx)(eO,{onStop:()=>z(!1)}),(0,a.jsx)(aa,{data:H,rowCount:P.total,isLoading:K.isLoading,isRefreshing:K.isFetching,pagination:r,onPaginationChange:o,sorting:d,onSortingChange:U,columnFilters:u,onColumnFiltersChange:B,searchValue:q,onSearchChange:R,onRefresh:()=>void K.refetch(),onRowClick:Q,onKeyHashClick:W,onSessionClick:J,teams:O??[],logsWindow:E,toolbarChildren:(0,a.jsx)(eP,{startTime:g,onStartTimeChange:x,endTime:h,onEndTimeChange:p,isCustomDate:b,onIsCustomDateChange:j,selectedTimeInterval:f,onSelectedTimeIntervalChange:y,isLiveTail:I,onIsLiveTailChange:z,onResetToFirstPage:V,onResetFilters:$})}),(0,a.jsx)(ew.LogDetailsDrawer,{open:T,onClose:()=>{N(!1),L(null)},logEntry:S,sessionId:k,accessToken:e,allLogs:H,onSelectLog:C,startTime:(0,ee.default)(g).utc().format("YYYY-MM-DD HH:mm:ss")})]})}var as=e.i(482725),ai=e.i(56456);function an({size:e,fontSize:t}){let l=(0,a.jsx)(ai.LoadingOutlined,{style:t?{fontSize:t}:void 0,spin:!0});return(0,a.jsx)(as.Spin,{indicator:l,size:e})}function ar({accessToken:e,token:o,userRole:d,userID:c,premiumUser:u}){let[m,g]=(0,t.useState)("request logs");return e&&o&&d&&c?(0,a.jsx)("div",{className:"w-full p-6 overflow-x-hidden box-border",children:(0,a.jsxs)(s.TabGroup,{defaultIndex:0,onIndexChange:e=>g(0===e?"request logs":"audit logs"),children:[(0,a.jsxs)(i.TabList,{children:[(0,a.jsx)(l.Tab,{children:"Request Logs"}),(0,a.jsx)(l.Tab,{children:"Audit Logs"}),(0,a.jsx)(l.Tab,{children:"Deleted Keys"}),(0,a.jsx)(l.Tab,{children:"Deleted Teams"})]}),(0,a.jsxs)(r.TabPanels,{children:[(0,a.jsx)(n.TabPanel,{children:(0,a.jsx)(al,{accessToken:e,token:o,userRole:d,userID:c,isActive:"request logs"===m})}),(0,a.jsx)(n.TabPanel,{children:(0,a.jsx)(eo,{userID:c,userRole:d,token:o,accessToken:e,isActive:"audit logs"===m,premiumUser:u})}),(0,a.jsx)(n.TabPanel,{children:(0,a.jsx)(_,{})}),(0,a.jsx)(n.TabPanel,{children:(0,a.jsx)(k,{})})]})]})}):(0,a.jsx)("div",{className:"flex items-center justify-center h-64",children:(0,a.jsx)(an,{size:"large"})})}e.s(["default",0,function(){let{accessToken:e,userRole:t,userId:l,token:s,premiumUser:i}=(0,c.default)();return(0,a.jsx)(ar,{userID:l,userRole:t,token:s,accessToken:e,premiumUser:i})}],799062)}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/058j9m4b8p4wx.js b/litellm/proxy/_experimental/out/_next/static/chunks/058j9m4b8p4wx.js new file mode 100644 index 00000000000..361fcf6e3e1 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/058j9m4b8p4wx.js @@ -0,0 +1 @@ +(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,677572,370359,405934,e=>{"use strict";var t,r,n,a=e.i(843476);e.s([],559657),e.i(559657),e.i(247167);var i=e.i(271645),s=e.i(951437),l=e.i(146376),o=e.i(667865),u=e.i(552245),c=e.i(53687),d=e.i(733332);let f=i.createContext(void 0);function h(){let e=i.useContext(f);if(void 0===e)throw Error((0,d.default)(64));return e}let p=((t={}).activationDirection="data-activation-direction",t.orientation="data-orientation",t),m={tabActivationDirection:e=>({[p.activationDirection]:e})};var g=e.i(675606),x=e.i(56434);let v=i.forwardRef(function(e,t){let{className:r,defaultValue:n=0,onValueChange:d,orientation:h="horizontal",render:p,value:v,style:y,..._}=e,w=void 0!==e.defaultValue,S=i.useRef([]),[C,N]=i.useState(()=>new Map),[T,A]=(0,s.useControlled)({controlled:v,default:n,name:"Tabs",state:"value"}),E=void 0!==v,[j,O]=i.useState(()=>new Map),R=i.useRef(void 0),k=i.useCallback(e=>{if(void 0===e)return null;for(let[t,r]of j.entries())if(null!=r&&e===(r.value??r.index))return t;return null},[j]),[I,M]=i.useState(()=>({previousValue:T,tabActivationDirection:"none"})),{previousValue:L,tabActivationDirection:P}=I,U=P,H=!1;L!==T&&(U=b(L,T,h,j),H=null!=L&&null!=T&&null==k(T));let D=H?L:T,z=L!==D||P!==U;(0,l.useIsoLayoutEffect)(()=>{z&&M({previousValue:D,tabActivationDirection:U})},[D,z,U]);let W=(0,o.useStableCallback)((e,t)=>{t.activationDirection=b(T,e,h,j),d?.(e,t),t.isCanceled||A(e)}),B=(0,o.useStableCallback)((e,t)=>{d?.(e,(0,g.createChangeEventDetails)(t,void 0,void 0,{activationDirection:"none"}))}),V=(0,o.useStableCallback)((e,t)=>{N(r=>{if(r.get(e)===t)return r;let n=new Map(r);return n.set(e,t),n})}),F=(0,o.useStableCallback)((e,t)=>{N(r=>{if(!r.has(e)||r.get(e)!==t)return r;let n=new Map(r);return n.delete(e),n})}),K=i.useCallback(e=>C.get(e),[C]),$=i.useCallback(e=>{for(let t of j.values())if(e===t?.value)return t?.id},[j]),Y=i.useMemo(()=>({getTabElementBySelectedValue:k,getTabIdByPanelValue:$,getTabPanelIdByValue:K,onValueChange:W,orientation:h,registerMountedTabPanel:V,setTabMap:O,unregisterMountedTabPanel:F,tabActivationDirection:U,value:T}),[k,$,K,W,h,V,O,F,U,T]),G=i.useMemo(()=>{for(let e of j.values())if(null!=e&&e.value===T)return e},[j,T]),J=i.useMemo(()=>{for(let e of j.values())if(null!=e&&!e.disabled)return e.value},[j]),X=i.useRef(!w),q=i.useRef(n),Z=i.useRef(w),Q=i.useRef(!1);(0,l.useIsoLayoutEffect)(()=>{if(E)return;function e(e,t){A(e),M(t=>t.previousValue===e&&"none"===t.tabActivationDirection?t:{previousValue:e,tabActivationDirection:"none"}),B(e,t),X.current=!1}if(0===j.size){Q.current&&null!==T&&!R.current?.isConnected&&e(null,x.REASONS.missing);return}Q.current=!0,R.current=j.keys().next().value;let t=G?.disabled,r=null==G&&null!==T;if(t||T!==q.current||(Z.current=!1),Z.current&&t&&T===q.current)return;let n=X.current;if(t||r){let r=J??null;if(T===r){X.current=!1;return}let a=x.REASONS.missing;n?a=x.REASONS.initial:t&&(a=x.REASONS.disabled),e(r,a);return}n&&null!=G&&(B(T,x.REASONS.initial),X.current=!1)},[J,E,B,G,A,j,T]);let ee={orientation:h,tabActivationDirection:U},et=(0,u.useRenderElement)("div",e,{state:ee,ref:t,props:_,stateAttributesMapping:m});return(0,a.jsx)(f.Provider,{value:Y,children:(0,a.jsx)(c.CompositeList,{elementsRef:S,children:et})})});function b(e,t,r,n){if(null==e||null==t)return"none";let a=null,i=null;for(let[r,s]of n.entries()){if(null==s)continue;let n=s.value??s.index;if(e===n&&(a=r),t===n&&(i=r),null!=a&&null!=i)break}if(null==a||null==i)return a!==i&&("number"==typeof e||"string"==typeof e)&&typeof e==typeof t?"horizontal"===r?t>e?"right":"left":t>e?"down":"up":"none";let s=a.getBoundingClientRect(),l=i.getBoundingClientRect();if("horizontal"===r){if(l.lefts.left)return"right"}else{if(l.tops.top)return"down"}return"none"}var y=e.i(108868),_=e.i(788015),w=e.i(540886);let S="data-composite-item-active";e.s(["ACTIVE_COMPOSITE_ITEM",0,S],370359);var C=e.i(395530);let N=i.createContext(void 0);function T(){let e=i.useContext(N);if(void 0===e)throw Error((0,d.default)(65));return e}var A=e.i(647554);let E=i.forwardRef(function(e,t){let{className:r,disabled:n=!1,render:a,value:s,id:o,nativeButton:c=!0,style:d,...f}=e,{value:p,getTabPanelIdByValue:v,orientation:b,tabActivationDirection:N}=h(),{activateOnFocus:E,highlightedTabIndex:j,onTabActivation:O,registerTabResizeObserverElement:R,setHighlightedTabIndex:k,tabsListElement:I}=T(),M=(0,_.useBaseUiId)(o),L=i.useMemo(()=>({disabled:n,id:M,value:s}),[n,M,s]),{compositeProps:P,compositeRef:U,index:H}=(0,C.useCompositeItem)({metadata:L}),D=s===p,z=i.useRef(!1),W=i.useRef(null);(0,l.useIsoLayoutEffect)(()=>{let e=W.current;if(e)return R(e)},[R]),(0,l.useIsoLayoutEffect)(()=>{if(z.current){z.current=!1;return}if(D&&H>-1&&j!==H){if(null!=I){let e=(0,A.activeElement)((0,y.ownerDocument)(I));if(e&&(0,A.contains)(I,e))return}n||k(H)}},[D,H,j,k,n,I]);let{getButtonProps:B,buttonRef:V}=(0,w.useButton)({disabled:n,native:c,focusableWhenDisabled:!0}),F=v(s),K=i.useRef(!1),$=i.useRef(!1);return(0,u.useRenderElement)("button",e,{state:{disabled:n,active:D,orientation:b,tabActivationDirection:N},ref:[t,V,U,W],props:[P,{role:"tab","aria-controls":F,"aria-selected":D,id:M,onClick:function(e){D||n||O(s,(0,g.createChangeEventDetails)(x.REASONS.none,e.nativeEvent,void 0,{activationDirection:"none"}))},onFocus:function(e){D||(H>-1&&!n&&k(H),!n&&E&&(!K.current||K.current&&$.current)&&O(s,(0,g.createChangeEventDetails)(x.REASONS.none,e.nativeEvent,void 0,{activationDirection:"none"})))},onPointerDown:function(e){D||n||(K.current=!0,e.button&&0!==e.button||($.current=!0,(0,y.ownerDocument)(e.currentTarget).addEventListener("pointerup",function(){K.current=!1,$.current=!1},{once:!0})))},[S]:D?"":void 0,onKeyDownCapture(){z.current=!0}},f,B],stateAttributesMapping:m})});var j=e.i(73364),O=e.i(802239),R=e.i(956789);function k(){return R.NOOP}function I(){return!1}function M(){return!0}let L=((r={}).activeTabLeft="--active-tab-left",r.activeTabRight="--active-tab-right",r.activeTabTop="--active-tab-top",r.activeTabBottom="--active-tab-bottom",r.activeTabWidth="--active-tab-width",r.activeTabHeight="--active-tab-height",r);var P=e.i(172410);let U={...m,activeTabPosition:()=>null,activeTabSize:()=>null},H=i.forwardRef(function(e,t){let{className:r,render:n,renderBeforeHydration:s=!1,style:l,...o}=e,{nonce:c}=(0,P.useCSPContext)(),{getTabElementBySelectedValue:d,orientation:f,tabActivationDirection:p,value:m}=h(),{tabsListElement:g,registerIndicatorUpdateListener:x}=T(),v=(0,O.useSyncExternalStore)(k,I,M),b=function(){let[,e]=i.useState({});return i.useCallback(()=>{e({})},[])}();i.useEffect(()=>x(b),[x,b]);let y=0,_=0,w=0,S=0,C=0,N=0,A=!1;if(null!=m&&null!=g){let e=d(m);if(null!=e){A=!0;let{width:t,height:r}=(0,j.getCssDimensions)(e),{width:n,height:a}=(0,j.getCssDimensions)(g),i=e.getBoundingClientRect(),s=g.getBoundingClientRect(),l=n>0?s.width/n:1,o=a>0?s.height/a:1;if(Math.abs(l)>Number.EPSILON&&Math.abs(o)>Number.EPSILON){let e=i.left-s.left,t=i.top-s.top;y=e/l+g.scrollLeft-g.clientLeft,w=t/o+g.scrollTop-g.clientTop}else y=e.offsetLeft,w=e.offsetTop;C=t,N=r,_=g.scrollWidth-y-C,S=g.scrollHeight-w-N}}let E=A?{left:y,right:_,top:w,bottom:S}:null,R=A?{width:C,height:N}:null,H=A?{[L.activeTabLeft]:`${y}px`,[L.activeTabRight]:`${_}px`,[L.activeTabTop]:`${w}px`,[L.activeTabBottom]:`${S}px`,[L.activeTabWidth]:`${C}px`,[L.activeTabHeight]:`${N}px`}:void 0,D=A&&C>0&&N>0,z=(0,u.useRenderElement)("span",e,{state:{orientation:f,activeTabPosition:E,activeTabSize:R,tabActivationDirection:p},ref:t,props:[{role:"presentation",style:H,hidden:!D},o,{suppressHydrationWarning:!0}],stateAttributesMapping:U});return null==m?null:(0,a.jsxs)(i.Fragment,{children:[z,v&&s&&(0,a.jsx)("script",{nonce:c,dangerouslySetInnerHTML:{__html:'!function(){const t=document.currentScript.previousElementSibling;if(!t)return;const e=t.closest(\'[role="tablist"]\');if(!e)return;const i=e.querySelector("[data-active]");if(!i)return;if(0===i.offsetWidth||0===e.offsetWidth)return;let o=0,n=0,h=0,l=0,r=0,f=0;function s(t){const e=getComputedStyle(t);let i=parseFloat(e.width)||0,o=parseFloat(e.height)||0;return(Math.round(i)!==t.offsetWidth||Math.round(o)!==t.offsetHeight)&&(i=t.offsetWidth,o=t.offsetHeight),{width:i,height:o}}if(null!=i&&null!=e){const{width:t,height:c}=s(i),{width:u,height:d}=s(e),a=i.getBoundingClientRect(),g=e.getBoundingClientRect(),p=u>0?g.width/u:1,b=d>0?g.height/d:1;if(Math.abs(p)>Number.EPSILON&&Math.abs(b)>Number.EPSILON){const t=a.left-g.left,i=a.top-g.top;o=t/p+e.scrollLeft-e.clientLeft,h=i/b+e.scrollTop-e.clientTop}else o=i.offsetLeft,h=i.offsetTop;r=t,f=c,n=e.scrollWidth-o-r,l=e.scrollHeight-h-f}function c(e,i){t.style.setProperty(`--active-tab-${e}`,`${i}px`)}c("left",o),c("right",n),c("top",h),c("bottom",l),c("width",r),c("height",f),r>0&&f>0&&t.removeAttribute("hidden")}();'},suppressHydrationWarning:!0})]})});var D=e.i(144394),z=e.i(209407),W=e.i(137584),B=e.i(223910),V=e.i(673553);let F=((n={}).index="data-index",n.activationDirection="data-activation-direction",n.orientation="data-orientation",n.hidden="data-hidden",n[n.startingStyle=z.TransitionStatusDataAttributes.startingStyle]="startingStyle",n[n.endingStyle=z.TransitionStatusDataAttributes.endingStyle]="endingStyle",n),K={...m,...z.transitionStatusMapping},$=i.forwardRef(function(e,t){let{className:r,value:n,render:a,keepMounted:s=!1,style:o,...c}=e,{value:d,getTabIdByPanelValue:f,orientation:p,tabActivationDirection:m,registerMountedTabPanel:g,unregisterMountedTabPanel:x}=h(),v=(0,_.useBaseUiId)(),b=i.useMemo(()=>({id:v,value:n}),[v,n]),{ref:y,index:w}=(0,V.useCompositeListItem)({metadata:b}),S=n===d,{mounted:C,transitionStatus:N,setMounted:T}=(0,B.useTransitionStatus)(S),A=!C,E=f(n),j=i.useRef(null),O=(0,u.useRenderElement)("div",e,{state:{hidden:A,orientation:p,tabActivationDirection:m,transitionStatus:N},ref:[t,y,j],props:[{"aria-labelledby":E,hidden:A,id:v,role:"tabpanel",tabIndex:S?0:-1,inert:(0,D.inertValue)(!S),[F.index]:w},c],stateAttributesMapping:K});return((0,W.useOpenChangeComplete)({open:S,ref:j,onComplete(){S||T(!1)}}),(0,l.useIsoLayoutEffect)(()=>{if((!A||s)&&null!=v)return g(n,v),()=>{x(n,v)}},[A,s,n,v,g,x]),s||C)?O:null});var Y=e.i(590803),G=e.i(828918),J=e.i(673327),X=e.i(621082);let q=[];var Z=e.i(838452),Q=e.i(872855);function ee(e){let{render:t,className:r,style:n,refs:s=R.EMPTY_ARRAY,props:d=R.EMPTY_ARRAY,state:f=R.EMPTY_OBJECT,stateAttributesMapping:h,highlightedIndex:p,onHighlightedIndexChange:m,orientation:g,grid:x,loopFocus:v,onLoop:b,enableHomeAndEndKeys:y,onMapChange:_,stopEventPropagation:w=!0,rootRef:C,disabledIndices:N,modifierKeys:T,highlightItemOnHover:E=!1,tag:j="div",...O}=e,{props:k,highlightedIndex:I,onHighlightedIndexChange:M,elementsRef:L,onMapChange:P,relayKeyboardEvent:U}=function(e){let{loopFocus:t=!0,orientation:r="both",grid:n,onLoop:a,direction:s,highlightedIndex:u,onHighlightedIndexChange:c,rootRef:d,enableHomeAndEndKeys:f=!1,stopEventPropagation:h=!1,disabledIndices:p,modifierKeys:m=q}=e,[g,x]=i.useState(0),v=null!=n,b=i.useRef(null),y=(0,G.useMergedRefs)(b,d),_=i.useRef([]),w=i.useRef(!1),C=u??g,N=(0,o.useStableCallback)((e,t=!1)=>{if((c??x)(e),t){let t=_.current[e];(0,J.scrollIntoViewIfNeeded)(b.current,t,s,r)}}),T=(0,o.useStableCallback)(e=>{if(0===e.size||w.current)return;w.current=!0;let t=Array.from(e.keys()),n=t.find(e=>e?.hasAttribute(S))??null,a=n?t.indexOf(n):-1;if(-1!==a)N(a);else if((0,X.isListIndexDisabled)(t,C,p)){let e=(0,X.findNonDisabledListIndex)(t,{disabledIndices:p});(0,X.isIndexOutOfListBounds)(t,e)||N(e)}(0,J.scrollIntoViewIfNeeded)(b.current,n,s,r)});(0,l.useIsoLayoutEffect)(()=>{if(null==p||null!=u||!w.current)return;let e=_.current;if((0,X.isListIndexDisabled)(e,C,p)){let t=(0,X.findNonDisabledListIndex)(e,{disabledIndices:p});(0,X.isIndexOutOfListBounds)(e,t)||N(t)}},[p,u,C,_,N]);let E=(0,o.useStableCallback)((e,t,r)=>a?a(e,t,r,_):r),j=(0,o.useStableCallback)(e=>{let i=f?J.COMPOSITE_KEYS:J.ARROW_KEYS;if(!i.has(e.key)||function(e,t){for(let r of J.MODIFIER_KEYS.values())if(!t.includes(r)&&e.getModifierState(r))return!0;return!1}(e,m)||!b.current)return;let l="rtl"===s,o=l?J.ARROW_LEFT:J.ARROW_RIGHT,u={horizontal:o,vertical:J.ARROW_DOWN,both:o}[r],c=l?J.ARROW_RIGHT:J.ARROW_LEFT,d={horizontal:c,vertical:J.ARROW_UP,both:c}[r],g=(0,A.getTarget)(e.nativeEvent);if(null!=g&&(0,J.isNativeInput)(g)&&!(0,Y.isElementDisabled)(g)){let t=g.selectionStart,r=g.selectionEnd,n=g.value??"";if(null==t||e.shiftKey||t!==r||e.key!==d&&t0)return}let x=C,y=(0,X.getMinListIndex)(_,p),w=(0,X.getMaxListIndex)(_,p);null!=n&&(x=n({disabledIndices:p,elementsRef:_,event:e,highlightedIndex:C,loopFocus:t,maxIndex:w,minIndex:y,onLoop:E,orientation:r,rtl:l}));let S={horizontal:[o],vertical:[J.ARROW_DOWN],both:[o,J.ARROW_DOWN]}[r],T={horizontal:[c],vertical:[J.ARROW_UP],both:[c,J.ARROW_UP]}[r],j=v?i:({horizontal:f?J.HORIZONTAL_KEYS_WITH_EXTRA_KEYS:J.HORIZONTAL_KEYS,vertical:f?J.VERTICAL_KEYS_WITH_EXTRA_KEYS:J.VERTICAL_KEYS,both:i})[r];f&&(e.key===J.HOME?x=y:e.key===J.END&&(x=w)),x===C&&(S.includes(e.key)||T.includes(e.key))&&(t&&x===w&&S.includes(e.key)?(x=y,a&&(x=a(e,C,x,_))):t&&x===y&&T.includes(e.key)?(x=w,a&&(x=a(e,C,x,_))):x=(0,X.findNonDisabledListIndex)(_.current,{startingIndex:x,decrement:T.includes(e.key),disabledIndices:p})),x===C||(0,X.isIndexOutOfListBounds)(_.current,x)||(h&&e.stopPropagation(),j.has(e.key)&&e.preventDefault(),N(x,!0),queueMicrotask(()=>{_.current[x]?.focus()}))});return{props:{ref:y,onFocus(e){let t=b.current,r=(0,A.getTarget)(e.nativeEvent);t&&null!=r&&(0,J.isNativeInput)(r)&&r.setSelectionRange(0,r.value.length??0)},onKeyDown:j},highlightedIndex:C,onHighlightedIndexChange:N,elementsRef:_,disabledIndices:p,onMapChange:T,relayKeyboardEvent:j}}({grid:x,loopFocus:v,onLoop:b,orientation:g,highlightedIndex:p,onHighlightedIndexChange:m,rootRef:C,stopEventPropagation:w,enableHomeAndEndKeys:y,direction:(0,Q.useDirection)(),disabledIndices:N,modifierKeys:T}),H=(0,u.useRenderElement)(j,e,{state:f,ref:s,props:[k,...d,O],stateAttributesMapping:h}),D=i.useMemo(()=>({highlightedIndex:I,onHighlightedIndexChange:M,highlightItemOnHover:E,relayKeyboardEvent:U}),[I,M,E,U]);return(0,a.jsx)(Z.CompositeRootContext.Provider,{value:D,children:(0,a.jsx)(c.CompositeList,{elementsRef:L,onMapChange:e=>{_?.(e),P(e)},children:H})})}e.s(["CompositeRoot",0,ee],405934);let et=i.forwardRef(function(e,t){let{activateOnFocus:r=!1,className:n,loopFocus:s=!0,render:u,style:c,...d}=e,{onValueChange:f,orientation:p,value:g,setTabMap:x,tabActivationDirection:v}=h(),[b,y]=i.useState(0),[_,w]=i.useState(null),S=i.useRef(new Set),C=i.useRef(new Set),T=i.useRef(null);(0,l.useIsoLayoutEffect)(()=>{if("u"{S.current.forEach(e=>{e()})});return T.current=e,_&&e.observe(_),C.current.forEach(t=>{e.observe(t)}),()=>{e.disconnect(),T.current=null}},[_]);let A=(0,o.useStableCallback)(e=>(S.current.add(e),()=>{S.current.delete(e)})),E=(0,o.useStableCallback)(e=>(C.current.add(e),T.current?.observe(e),()=>{C.current.delete(e),T.current?.unobserve(e)})),j=(0,o.useStableCallback)((e,t)=>{e!==g&&f(e,t)}),O=i.useMemo(()=>({activateOnFocus:r,highlightedTabIndex:b,registerIndicatorUpdateListener:A,registerTabResizeObserverElement:E,onTabActivation:j,setHighlightedTabIndex:y,tabsListElement:_}),[r,b,A,E,j,y,_]);return(0,a.jsx)(N.Provider,{value:O,children:(0,a.jsx)(ee,{render:u,className:n,style:c,state:{orientation:p,tabActivationDirection:v},refs:[t,w],props:[{"aria-orientation":"vertical"===p?"vertical":void 0,role:"tablist"},d],stateAttributesMapping:m,highlightedIndex:b,enableHomeAndEndKeys:!0,loopFocus:s,orientation:p,onHighlightedIndexChange:y,onMapChange:x,disabledIndices:R.EMPTY_ARRAY})})});e.s(["Indicator",0,H,"List",0,et,"Panel",0,$,"Root",0,v,"Tab",0,E],69281);var er=e.i(69281),er=er,en=e.i(115504);let ea=(0,en.cva)({base:"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-9 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",variants:{variant:{default:"bg-muted",line:"gap-1 bg-transparent"}},defaultVariants:{variant:"default"}});e.s(["Tabs",0,function({className:e,orientation:t="horizontal",...r}){return(0,a.jsx)(er.Root,{"data-slot":"tabs","data-orientation":t,className:(0,en.cn)("group/tabs flex gap-2 data-horizontal:flex-col",e),...r})},"TabsContent",0,function({className:e,...t}){return(0,a.jsx)(er.Panel,{"data-slot":"tabs-content",className:(0,en.cn)("flex-1 text-sm outline-none",e),...t})},"TabsList",0,function({className:e,variant:t="default",...r}){return(0,a.jsx)(er.List,{"data-slot":"tabs-list","data-variant":t,className:(0,en.cn)(ea({variant:t}),e),...r})},"TabsTrigger",0,function({className:e,...t}){return(0,a.jsx)(er.Tab,{"data-slot":"tabs-trigger",className:(0,en.cn)("relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4","group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent","data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground","after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",e),...t})}],677572)},531278,e=>{"use strict";let t=(0,e.i(475254).default)("loader-circle",[["path",{d:"M21 12a9 9 0 1 1-6.219-8.56",key:"13zald"}]]);e.s(["Loader2",0,t],531278)},195116,e=>{"use strict";let t=(0,e.i(475254).default)("wrench",[["path",{d:"M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z",key:"cbrjhi"}]]);e.s(["Wrench",0,t],195116)},180127,e=>{"use strict";let t=(0,e.i(475254).default)("arrow-left",[["path",{d:"m12 19-7-7 7-7",key:"1l729n"}],["path",{d:"M19 12H5",key:"x3x0zl"}]]);e.s(["default",0,t])},871689,e=>{"use strict";var t=e.i(180127);e.s(["ArrowLeft",()=>t.default])},434166,e=>{"use strict";e.s(["getSecureItem",0,function(e){try{let t=window.sessionStorage.getItem(e);if(null===t)return null;return decodeURIComponent(atob(t).split("").map(e=>"%"+e.charCodeAt(0).toString(16).padStart(2,"0")).join(""))}catch{return null}},"setSecureItem",0,function(e,t){window.sessionStorage.setItem(e,btoa(encodeURIComponent(t).replace(/%([0-9A-F]{2})/g,(e,t)=>String.fromCharCode(parseInt(t,16)))))}])},292335,122520,165615,779129,280024,e=>{"use strict";let t={NONE:"none",API_KEY:"api_key",BEARER_TOKEN:"bearer_token",TOKEN:"token",BASIC:"basic",OAUTH2:"oauth2",OAUTH2_TOKEN_EXCHANGE:"oauth2_token_exchange",AWS_SIGV4:"aws_sigv4",TRUE_PASSTHROUGH:"true_passthrough",OAUTH_DELEGATE:"oauth_delegate"},r=e=>e===t.TRUE_PASSTHROUGH||e===t.OAUTH_DELEGATE,n={INTERACTIVE:"interactive",M2M:"m2m"},a=e=>{let t=e.credentials??{};return JSON.stringify({url:"string"==typeof e.url?e.url:null,spec_path:"string"==typeof e.spec_path?e.spec_path:null,auth_type:e.auth_type??null,oauth_flow_type:e.oauth_flow_type??null,client_id:t.client_id??null,client_secret:t.client_secret??null,scopes:t.scopes??null,upstream_resource:t.upstream_resource??null,issuer:e.issuer??null,authorization_url:e.authorization_url??null,token_url:e.token_url??null,registration_url:e.registration_url??null})},i=["client_id","client_secret"],s=["upstream_resource"],l=["access_token","refresh_token","expires_in","scope"],o=(e,t)=>{if(!e)return;let r=Object.fromEntries(t.filter(t=>"string"==typeof e[t]&&""!==e[t]).map(t=>[t,e[t]]));return Object.keys(r).length>0?r:void 0},u="client_credentials",c={SSE:"sse",HTTP:"http",STDIO:"stdio",OPENAPI:"openapi"};e.s(["ADMIN_CONFIG_CREDENTIAL_KEYS",0,s,"AUTH_TYPE",0,t,"CLEARED_ON_INVALIDATION",0,["credentials"],"MCP_OAUTH2_FLOW_INTERACTIVE",0,"authorization_code","MCP_OAUTH2_FLOW_M2M",0,u,"OAUTH_FLOW",0,n,"TRANSPORT",0,c,"credentialAuthClass",0,e=>e===t.TRUE_PASSTHROUGH||e===t.OAUTH_DELEGATE?"client_forwarded":e??null,"gatewayMintsClientFor",0,e=>e.auth_type===t.TRUE_PASSTHROUGH||e.auth_type===t.OAUTH_DELEGATE&&!e.dcr_bridge,"getMcpOAuthMode",0,function(e){return e.auth_type===t.OAUTH2_TOKEN_EXCHANGE?"token_exchange":e.auth_type!==t.OAUTH2?null:e.oauth2_flow===u?"m2m":e.delegate_auth_to_upstream?"passthrough":"authorization_code"},"getOAuthAuthorizationIdentity",0,a,"handleAuth",0,e=>null==e?t.NONE:e,"handleTransport",0,(e,t)=>null==e?c.SSE:t&&e!==c.STDIO?c.OPENAPI:e,"isClientForwardedTokenMode",0,r,"isHeldOAuthTokenStale",0,(e,t)=>void 0!==t&&a(e)!==t,"isUnsupportedOnGatewayConnect",0,e=>r(e)||e===t.OAUTH2_TOKEN_EXCHANGE,"oauth2FlowToFormValue",0,function(e){return e===u?n.M2M:e?n.INTERACTIVE:void 0},"preservedAdminCredentials",0,e=>o(e,[...i,...s]),"preservedDeclaredAppCredentials",0,e=>o(e,i),"withoutMintedTokenCredentials",0,e=>{if(!e)return;let t=Object.fromEntries(Object.entries(e).filter(([e])=>!l.includes(e)));return Object.keys(t).length>0?t:void 0}],292335);var d=e.i(271645),f=e.i(602869),h=e.i(727749);function p(e){if(e instanceof Error)return e.message;if(e&&"object"==typeof e){let t=e.detail;return"string"==typeof t?t:Array.isArray(t)?t.map(e=>e&&"object"==typeof e?"string"==typeof e.msg?e.msg:JSON.stringify(e):String(e)).join("; "):t&&"object"==typeof t&&"string"==typeof t.error?t.error:"string"==typeof e.message?e.message:JSON.stringify(e)}return String(e)}e.s(["extractErrorMessage",0,p],122520);let m=e=>{let t=new Uint8Array(e),r="";return t.forEach(e=>r+=String.fromCharCode(e)),btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")},g=()=>{let e=new Uint8Array(32);return window.crypto.getRandomValues(e),m(e.buffer)},x=async e=>{let t=new TextEncoder().encode(e);return m(await window.crypto.subtle.digest("SHA-256",t))};e.s(["generateCodeChallenge",0,x,"generateCodeVerifier",0,g],165615);var v=e.i(434166);let b=()=>{{let e=window.location.pathname||"",t=e.indexOf("/ui"),r=t>=0?e.slice(0,t+3).replace(/\/+$/,""):"";return`${window.location.origin}${r}/mcp/oauth/callback`}},y=(...e)=>{e.forEach(e=>{try{window.sessionStorage.removeItem(e)}catch(e){}})};e.s(["TOOLS_OAUTH_UI_STATE_KEY",0,"litellm-mcp-oauth-tools-state","buildCallbackUrl",0,b,"clearStorage",0,y],779129);let _="litellm-user-mcp-oauth-flow-state",w="litellm-user-mcp-oauth-result",S=(e,t)=>{(0,v.setSecureItem)(e,t)},C=e=>(0,v.getSecureItem)(e);e.s(["useUserMcpOAuthFlow",0,({accessToken:e,serverId:t,serverAlias:r,scopes:n,clientId:a,onSuccess:i})=>{let[s,l]=(0,d.useState)("idle"),[o,u]=(0,d.useState)(null),c=(0,d.useRef)(!1),m=(0,d.useCallback)(async()=>{try{let i;l("authorizing"),u(null);let s=a??void 0;if(!s)try{let n=await (0,f.registerMcpOAuthClient)(e,t,{client_name:r||t,grant_types:["authorization_code","refresh_token"],response_types:["code"],token_endpoint_auth_method:"none"});s=n?.client_id,i=n?.client_secret}catch(e){}let o=g(),c=await x(o),d=crypto.randomUUID(),h=b(),p=n?.filter(e=>e.trim()).join(" "),m=(0,f.buildMcpOAuthAuthorizeUrl)({serverId:t,clientId:s,redirectUri:h,state:d,codeChallenge:c,scope:p}),v={state:d,codeVerifier:o,serverId:t,redirectUri:h,clientId:s,clientSecret:i,scopes:n};S(_,JSON.stringify(v));let y=new URL(window.location.href);y.searchParams.set("mcpOauthReturn","apps"),S("litellm-mcp-oauth-return-url",y.toString()),window.location.href=m}catch(t){let e=p(t);u(e),l("error"),h.default.error(e)}},[e,t,r,n,a]),v=(0,d.useCallback)(async()=>{if(c.current)return;let r=C(w);if(!r)return;let n=C(_);if(!n)return;try{let e=JSON.parse(n);if(e.serverId&&e.serverId!==t)return}catch(e){}c.current=!0,y(w);let a=null,s=null;try{a=JSON.parse(r);let e=C(_);s=e?JSON.parse(e):null}catch(e){u("Failed to resume OAuth flow. Please retry."),l("error"),c.current=!1,y(_);return}try{if(!s?.state||!s.codeVerifier||!s.serverId)throw Error("OAuth session state was lost. Please retry.");if(!a?.state||a.state!==s.state)throw Error("OAuth state mismatch. Please retry.");if(a.error)throw Error(a.error_description||a.error);if(!a.code)throw Error("Authorization code missing in callback.");l("exchanging");let t=await (0,f.exchangeMcpOAuthToken)({serverId:s.serverId,code:a.code,clientId:s.clientId,clientSecret:s.clientSecret,codeVerifier:s.codeVerifier,redirectUri:s.redirectUri,accessToken:e});await (0,f.storeMCPOAuthUserCredential)(e,s.serverId,{access_token:t.access_token,refresh_token:t.refresh_token,expires_in:t.expires_in,scopes:s.scopes}),l("success"),u(null),h.default.success("Connected successfully"),i()}catch(t){let e=p(t);u(e),l("error"),h.default.error(e)}finally{y(_),setTimeout(()=>{c.current=!1},1e3)}},[e,t,i]);return(0,d.useEffect)(()=>{v()},[v]),{startOAuthFlow:m,status:s,error:o}}],280024)},269638,e=>{"use strict";let t=(0,e.i(475254).default)("circle-check-big",[["path",{d:"M21.801 10A10 10 0 1 1 17 3.335",key:"yps3ct"}],["path",{d:"m9 11 3 3L22 4",key:"1pflzl"}]]);e.s(["CheckCircle",0,t],269638)},21040,e=>{"use strict";var t=e.i(843476),r=e.i(271645),n=e.i(266027),a=e.i(555436),i=e.i(871689),s=e.i(463059),l=e.i(195116),o=e.i(269638),u=e.i(531278),c=e.i(519455),d=e.i(793479),f=e.i(302747),h=e.i(677572),p=e.i(602869),m=e.i(292335),g=e.i(174553),x=e.i(888259),v=e.i(280024);let b=({server:e,accessToken:n,onConnect:a,variant:i="badge"})=>{let s=e.server_name??e.alias??e.server_id,{startOAuthFlow:l,status:o}=(0,v.useUserMcpOAuthFlow)({accessToken:n,serverId:e.server_id,serverAlias:s,onSuccess:(0,r.useCallback)(()=>a(e.server_id),[a,e.server_id])}),d="authorizing"===o||"exchanging"===o;return"button"===i?(0,t.jsxs)(c.Button,{onClick:l,disabled:d,className:"font-semibold h-[38px] min-w-[110px]",children:[d&&(0,t.jsx)(u.Loader2,{className:"h-4 w-4 animate-spin mr-1.5"}),d?"Connecting…":"Connect"]}):(0,t.jsx)("span",{onClick:e=>{e.stopPropagation(),d||l()},className:`text-[11px] font-semibold rounded-md px-2 py-0.5 shrink-0 whitespace-nowrap ${d?"text-muted-foreground bg-muted cursor-default":"text-primary-foreground bg-primary cursor-pointer hover:bg-primary/90"}`,children:d?"Connecting…":"Connect"})},y=["#1677ff","#52c41a","#fa8c16","#eb2f96","#722ed1","#13c2c2","#fa541c","#2f54eb","#a0d911","#faad14"];function _(e){let t=0;for(let r=0;r{let[S,C]=(0,r.useState)([]),[N,T]=(0,r.useState)(!0),[A,E]=(0,r.useState)(""),[j,O]=(0,r.useState)("all"),[R,k]=(0,r.useState)(new Set),[I,M]=(0,r.useState)(null),[L,P]=(0,r.useState)({}),[U,H]=(0,r.useState)(!1),[D,z]=(0,r.useState)(new Set),[W,B]=(0,r.useState)(new Set),V=(0,r.useRef)([]);(0,r.useEffect)(()=>{V.current=S},[S]);let F=(0,r.useRef)(v);(0,r.useEffect)(()=>{F.current=v},[v]);let K=(0,r.useRef)(y);(0,r.useEffect)(()=>{K.current=y},[y]);let $=e=>e.server_name??e.alias??e.server_id,Y=(0,r.useRef)(!1),G=(0,r.useCallback)(async t=>{try{let r=await (0,p.listMCPTools)(e,t.server_id);if(Y.current)return;let n=Array.isArray(r?.tools)?r.tools:[];P(e=>({...e,[$(t)]:n.length}))}catch{}},[e]),J=(0,r.useCallback)(async t=>{try{let r=await (0,p.getMCPOAuthUserCredentialStatus)(e,t.server_id);if(Y.current)return;r.has_credential&&!r.is_expired&&z(e=>new Set(e).add(t.server_id))}catch{}finally{Y.current||B(e=>{let r=new Set(e);return r.delete(t.server_id),r})}},[e]);(0,r.useEffect)(()=>(Y.current=!1,(0,p.fetchMCPServers)(e).then(async e=>{if(Y.current)return;let t=Array.isArray(e)?e:e?.data??[],r=t.filter(e=>e.auth_type===m.AUTH_TYPE.OAUTH2);for(let e of(C(t),B(new Set(r.map(e=>e.server_id))),T(!1),r.forEach(e=>J(e)),H(!0),Array.from({length:Math.ceil(t.length/5)},(e,r)=>t.slice(5*r,(r+1)*5)))){if(Y.current)return;await Promise.allSettled(e.map(e=>G(e)))}Y.current||H(!1)}).catch(()=>{Y.current||(C([]),T(!1))}),()=>{Y.current=!0}),[e,G,J]),(0,r.useEffect)(()=>{if(0===D.size)return;let e=V.current.filter(e=>D.has(e.server_id)&&!F.current.includes($(e))).map($);e.length>0&&K.current([...F.current,...e])},[D]);let X=async(t,r,n)=>{if(!r){y(v.filter(e=>e!==t)),n&&z(e=>{let t=new Set(e);return t.delete(n),t});return}k(e=>new Set(e).add(t));try{let r=n??t,a=await (0,p.listMCPTools)(e,r);if(a?.error)return void x.default.warning(`Could not load tools for ${t}`);F.current.includes(t)||y([...F.current,t])}catch{x.default.warning(`Could not load tools for ${t}`)}finally{k(e=>{let r=new Set(e);return r.delete(t),r})}},{data:q,isLoading:Z}=(0,n.useQuery)({queryKey:["mcp-apps-panel-detail-tools",I?.server_id],queryFn:()=>(0,p.listMCPTools)(e,I.server_id),enabled:!!I}),Q=Array.isArray(q?.tools)?q.tools:[],ee=S.filter(e=>{let t=$(e),r=!A.trim()||t.toLowerCase().includes(A.toLowerCase())||(e.description??"").toLowerCase().includes(A.toLowerCase()),n="all"===j||v.includes(t);return r&&n}),et=S.filter(e=>v.includes($(e))).length,er=Object.values(L).reduce((e,t)=>e+t,0);if(I){let r=$(I),n=v.includes(r),a=R.has(r),s=_(r);return(0,t.jsxs)("div",{className:"w-full",children:[(0,t.jsxs)(c.Button,{variant:"ghost",size:"sm",onClick:()=>M(null),className:"-ml-3 mb-5 gap-1.5 text-muted-foreground hover:text-foreground",children:[(0,t.jsx)(i.ArrowLeft,{className:"h-3 w-3"}),"Back"]}),(0,t.jsxs)("div",{className:"flex items-start gap-5 mb-7",children:[I.mcp_info?.logo_url?(0,t.jsx)(g.Logo,{src:I.mcp_info.logo_url,label:r,className:"w-16 h-16 rounded-2xl object-contain shrink-0 bg-muted/50"}):(0,t.jsx)("div",{className:"w-16 h-16 rounded-2xl flex items-center justify-center text-white font-bold text-[28px] shrink-0",style:{background:s},children:r.charAt(0).toUpperCase()}),(0,t.jsxs)("div",{className:"flex-1",children:[(0,t.jsx)("h2",{className:"m-0 mb-1 text-[22px] font-bold text-foreground",children:r}),(0,t.jsx)("p",{className:"m-0 text-sm text-muted-foreground",children:I.description??"MCP server"})]}),I.auth_type===m.AUTH_TYPE.OAUTH2?D.has(I.server_id)?(0,t.jsx)(c.Button,{variant:"destructive",onClick:async()=>{try{await (0,p.deleteMCPOAuthUserCredential)(e,I.server_id)}catch(e){}z(e=>{let t=new Set(e);return t.delete(I.server_id),t}),K.current(F.current.filter(e=>e!==r))},className:"font-semibold h-[38px] min-w-[110px]",children:"Disconnect"}):(0,t.jsx)(b,{server:I,accessToken:e,onConnect:e=>{z(t=>new Set(t).add(e))},variant:"button"}):(0,t.jsxs)(c.Button,{variant:n?"outline":"default",disabled:a,onClick:()=>X(r,!n,I.server_id),className:"font-semibold h-[38px] min-w-[110px]",children:[a&&(0,t.jsx)(u.Loader2,{className:"h-4 w-4 animate-spin mr-1.5"}),n?"Disconnect":"Connect"]})]}),(0,t.jsx)("h3",{className:"m-0 mb-3 text-[15px] font-semibold text-foreground",children:"Information"}),(0,t.jsx)("div",{className:"border rounded-lg overflow-hidden mb-7",children:[["Server ID",I.server_id],["Transport",(0,m.handleTransport)(I.transport,I.spec_path)],["Status",n?"Connected":"Not connected"]].filter(([,e])=>e).map(([e,r],n,a)=>(0,t.jsxs)("div",{className:`flex px-4 py-3 text-[13px] ${n(0,t.jsxs)("div",{className:"border rounded-lg px-3.5 py-2.5 bg-muted/30 flex flex-col gap-1.5",children:[(0,t.jsx)(f.Skeleton,{className:"h-3.5 w-1/3"}),(0,t.jsx)(f.Skeleton,{className:"h-3 w-2/3"})]},r))}):0===Q.length?(0,t.jsx)("div",{className:"text-muted-foreground text-[13px] py-2",children:"No tools available"}):(0,t.jsx)("div",{className:"flex flex-col gap-2",children:Q.map(e=>(0,t.jsxs)("div",{className:"border rounded-lg px-3.5 py-2.5 bg-muted/30",children:[(0,t.jsxs)("div",{className:`flex items-center gap-2 ${e.description?"mb-1":""}`,children:[(0,t.jsx)(l.Wrench,{className:"h-3.5 w-3.5 text-muted-foreground"}),(0,t.jsx)("span",{className:"text-[13px] font-semibold text-foreground font-mono",children:e.name})]}),e.description&&(0,t.jsx)("p",{className:"m-0 text-xs text-muted-foreground pl-[21px]",children:e.description})]},e.name))})]})}return(0,t.jsxs)("div",{className:"w-full",children:[(0,t.jsxs)("div",{className:"flex items-center justify-between mb-5 gap-4 flex-wrap",children:[(0,t.jsxs)("div",{children:[(0,t.jsxs)("div",{className:"flex items-center gap-2 mb-1",children:[(0,t.jsx)("h2",{className:"m-0 text-lg font-semibold text-foreground",children:"MCP Servers"}),!w&&(0,t.jsx)("span",{className:"text-[10px] font-semibold text-primary bg-primary/10 rounded px-1.5 py-0.5 uppercase tracking-wider",children:"Beta"})]}),w?(0,t.jsx)("p",{className:"m-0 text-[13px] text-muted-foreground",children:"Click a server to see its tools and connect"}):(0,t.jsxs)("div",{className:"flex items-center gap-3",children:[(0,t.jsx)("p",{className:"m-0 text-[13px] text-muted-foreground",children:"Browse tools, authenticate once, use in chat"}),U?(0,t.jsxs)("span",{className:"flex items-center gap-1.5 text-xs text-muted-foreground",children:[(0,t.jsx)(u.Loader2,{className:"h-3 w-3 animate-spin"}),"Loading tools..."]}):er>0?(0,t.jsxs)("span",{className:"flex items-center gap-1 text-xs text-muted-foreground",children:[(0,t.jsx)(l.Wrench,{className:"h-3 w-3"}),er," tool",1!==er?"s":""," available"]}):null]})]}),(0,t.jsxs)("div",{className:"relative w-[220px]",children:[(0,t.jsx)(a.Search,{className:"absolute left-3 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground"}),(0,t.jsx)(d.Input,{placeholder:"Search servers...",value:A,onChange:e=>E(e.target.value),className:"pl-9 text-[13px] h-9"})]})]}),(0,t.jsx)(h.Tabs,{value:j,onValueChange:e=>O(e),className:"mb-4",children:(0,t.jsxs)(h.TabsList,{variant:"line",className:"border-b rounded-none w-full justify-start h-auto p-0",children:[(0,t.jsx)(h.TabsTrigger,{value:"all",className:"rounded-none px-4 py-2 text-[13px]",children:"All"}),(0,t.jsxs)(h.TabsTrigger,{value:"connected",className:"rounded-none px-4 py-2 text-[13px]",children:["Connected",et>0?` (${et})`:""]})]})}),N?(0,t.jsx)("div",{className:"grid grid-cols-2 border rounded-lg overflow-hidden",children:Array.from({length:6},(e,r)=>(0,t.jsxs)("div",{className:`flex items-center gap-3 p-4 ${r%2==0?"border-r":""} ${r<4?"border-b":""}`,children:[(0,t.jsx)(f.Skeleton,{className:"w-[38px] h-[38px] rounded-xl shrink-0"}),(0,t.jsxs)("div",{className:"flex-1 min-w-0 flex flex-col gap-1.5",children:[(0,t.jsx)(f.Skeleton,{className:"h-3.5 w-2/3"}),(0,t.jsx)(f.Skeleton,{className:"h-3 w-1/2"})]})]},r))}):0===ee.length?(0,t.jsx)("div",{className:"text-center text-muted-foreground text-[13px] py-12 px-3",children:0===S.length?"No MCP servers configured. Add servers in Tools -> MCP Servers.":"connected"===j?"No servers connected yet.":"No servers match your search."}):(0,t.jsx)("div",{className:"grid grid-cols-2 border rounded-lg overflow-hidden",children:ee.map((r,n)=>{var a;let i=$(r),u=_(i),c=L[i],d=!!w&&(0,m.isUnsupportedOnGatewayConnect)(r.auth_type);return(0,t.jsxs)("div",{onClick:()=>M(r),className:`flex items-center gap-3 p-4 bg-card cursor-pointer transition-colors hover:bg-accent/30 min-w-0 ${n%2==0?"border-r":""} ${Math.floor(n/2)0?(0,t.jsxs)("span",{className:"shrink-0 flex items-center gap-1 text-muted-foreground",children:["· ",(0,t.jsx)(l.Wrench,{className:"h-2.5 w-2.5"})," ",c]}):null:U?(0,t.jsx)(f.Skeleton,{className:"w-7 h-3 shrink-0"}):null]})]}),(a=r,w&&(0,m.isUnsupportedOnGatewayConnect)(a.auth_type)?(0,t.jsx)("span",{className:"text-[11px] text-muted-foreground shrink-0 whitespace-nowrap",children:"Not supported on this connection"}):a.auth_type===m.AUTH_TYPE.OAUTH2?D.has(a.server_id)?(0,t.jsx)(o.CheckCircle,{className:"h-3.5 w-3.5 text-emerald-600 shrink-0"}):W.has(a.server_id)?(0,t.jsx)(f.Skeleton,{className:"h-6 w-16 shrink-0 rounded-md"}):(0,t.jsx)(b,{server:a,accessToken:e,onConnect:e=>z(t=>new Set(t).add(e)),variant:"badge"}):v.includes($(a))?(0,t.jsx)("span",{className:"w-[7px] h-[7px] rounded-full bg-emerald-600 dark:bg-emerald-400 shrink-0"}):null),(0,t.jsx)(s.ChevronRight,{className:"h-3 w-3 text-muted-foreground/40 shrink-0"})]},r.server_id)})})]})}])},248536,e=>{"use strict";var t=e.i(843476),r=e.i(271645),n=e.i(618566),a=e.i(405033),i=e.i(21040),s=e.i(269638),l=e.i(602869);let o=({flowHandle:e,clientOrigin:r})=>{let n=`${(0,l.getProxyBaseUrl)()}/authorize/complete`,a=r??"the application";return(0,t.jsx)("div",{className:"mb-6 rounded-lg border border-primary/30 bg-primary/5 px-5 py-4",children:(0,t.jsxs)("div",{className:"flex items-start justify-between gap-4 flex-wrap",children:[(0,t.jsxs)("div",{className:"flex items-start gap-3 min-w-0",children:[(0,t.jsx)(s.CheckCircle,{className:"h-5 w-5 text-primary shrink-0 mt-0.5"}),(0,t.jsxs)("div",{className:"min-w-0",children:[(0,t.jsxs)("p",{className:"text-sm font-semibold text-foreground",children:["Connect your MCP servers to ",a]}),(0,t.jsxs)("p",{className:"text-[13px] text-muted-foreground mt-0.5",children:["Authorize the servers you want to use below, then click Finish connecting to return to ",a,"."]})]})]}),(0,t.jsxs)("form",{method:"POST",action:n,className:"shrink-0",children:[(0,t.jsx)("input",{type:"hidden",name:"flow",value:e}),(0,t.jsx)("button",{type:"submit",className:"h-[38px] rounded-md bg-primary px-4 text-sm font-semibold text-primary-foreground hover:bg-primary/90",children:"Finish connecting"})]})]})})};function u(){let{accessToken:e,selectedMCPServers:s,setSelectedMCPServers:l}=(0,a.useChatShell)(),u=(0,n.useRouter)(),c=(0,n.useSearchParams)(),d=c.get("mcpOauthReturn"),f=c.get("connect_flow"),h=c.get("connect_client");return(0,r.useEffect)(()=>{if(d){let e=new URL(window.location.href);e.searchParams.delete("mcpOauthReturn"),u.replace(e.pathname+e.search)}},[d,u]),(0,t.jsxs)("div",{className:"flex-1 min-h-0 overflow-auto w-full py-8 px-8",children:[f&&(0,t.jsx)(o,{flowHandle:f,clientOrigin:h}),(0,t.jsx)(i.default,{accessToken:e,selectedServers:s,onChange:l,connectMode:!!f})]})}e.s(["default",0,function(){return(0,t.jsx)(r.Suspense,{children:(0,t.jsx)(u,{})})}],248536)}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/06q8aep867ss7.js b/litellm/proxy/_experimental/out/_next/static/chunks/06q8aep867ss7.js new file mode 100644 index 00000000000..251a9ba7430 --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/06q8aep867ss7.js @@ -0,0 +1 @@ +(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,515288,e=>{"use strict";var t=e.i(843476),r=e.i(271645),a=e.i(115504);let n=r.forwardRef(({className:e,size:r="default",...n},o)=>(0,t.jsx)("div",{ref:o,"data-slot":"card","data-size":r,className:(0,a.cn)("group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground shadow-xs ring-1 ring-foreground/10 [--card-spacing:--spacing(6)] has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(4)] *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",e),...n}));n.displayName="Card";let o=r.forwardRef(({className:e,...r},n)=>(0,t.jsx)("div",{ref:n,"data-slot":"card-header",className:(0,a.cn)("group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-(--card-spacing) has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-(--card-spacing)",e),...r}));o.displayName="CardHeader";let s=r.forwardRef(({className:e,...r},n)=>(0,t.jsx)("div",{ref:n,"data-slot":"card-title",className:(0,a.cn)("text-base leading-normal font-medium group-data-[size=sm]/card:text-sm",e),...r}));s.displayName="CardTitle";let i=r.forwardRef(({className:e,...r},n)=>(0,t.jsx)("div",{ref:n,"data-slot":"card-description",className:(0,a.cn)("text-sm text-muted-foreground",e),...r}));i.displayName="CardDescription";let l=r.forwardRef(({className:e,...r},n)=>(0,t.jsx)("div",{ref:n,"data-slot":"card-action",className:(0,a.cn)("col-start-2 row-span-2 row-start-1 self-start justify-self-end",e),...r}));l.displayName="CardAction";let d=r.forwardRef(({className:e,...r},n)=>(0,t.jsx)("div",{ref:n,"data-slot":"card-content",className:(0,a.cn)("px-(--card-spacing)",e),...r}));d.displayName="CardContent";let u=r.forwardRef(({className:e,...r},n)=>(0,t.jsx)("div",{ref:n,"data-slot":"card-footer",className:(0,a.cn)("flex items-center rounded-b-xl px-(--card-spacing) [.border-t]:pt-(--card-spacing)",e),...r}));u.displayName="CardFooter",e.s(["Card",0,n,"CardAction",0,l,"CardContent",0,d,"CardDescription",0,i,"CardFooter",0,u,"CardHeader",0,o,"CardTitle",0,s])},727612,e=>{"use strict";let t=(0,e.i(475254).default)("trash-2",[["path",{d:"M3 6h18",key:"d0wm0j"}],["path",{d:"M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6",key:"4alrt4"}],["path",{d:"M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2",key:"v07s0e"}],["line",{x1:"10",x2:"10",y1:"11",y2:"17",key:"1uufr5"}],["line",{x1:"14",x2:"14",y1:"11",y2:"17",key:"xtxkd"}]]);e.s(["Trash2",0,t],727612)},541071,373488,e=>{"use strict";let t=(0,e.i(475254).default)("ellipsis",[["circle",{cx:"12",cy:"12",r:"1",key:"41hilf"}],["circle",{cx:"19",cy:"12",r:"1",key:"1wjl8i"}],["circle",{cx:"5",cy:"12",r:"1",key:"1pcz8c"}]]);e.s(["default",0,t],373488),e.s(["MoreHorizontal",0,t],541071)},755146,e=>{"use strict";var t=e.i(843476),r=e.i(451512),a=e.i(115504);e.i(233565),e.i(678784),e.s(["DropdownMenu",0,function({...e}){return(0,t.jsx)(r.Menu.Root,{"data-slot":"dropdown-menu",...e})},"DropdownMenuContent",0,function({align:e="start",alignOffset:n=0,side:o="bottom",sideOffset:s=4,className:i,...l}){return(0,t.jsx)(r.Menu.Portal,{children:(0,t.jsx)(r.Menu.Positioner,{className:"isolate z-50 outline-none",align:e,alignOffset:n,side:o,sideOffset:s,children:(0,t.jsx)(r.Menu.Popup,{"data-slot":"dropdown-menu-content",className:(0,a.cn)("z-50 max-h-(--available-height) w-(--anchor-width) min-w-32 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-md bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:overflow-hidden data-closed:fade-out-0 data-closed:zoom-out-95",i),...l})})})},"DropdownMenuItem",0,function({className:e,inset:n,variant:o="default",...s}){return(0,t.jsx)(r.Menu.Item,{"data-slot":"dropdown-menu-item","data-inset":n,"data-variant":o,className:(0,a.cn)("group/dropdown-menu-item relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-inset:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 data-[variant=destructive]:*:[svg]:text-destructive",e),...s})},"DropdownMenuSeparator",0,function({className:e,...n}){return(0,t.jsx)(r.Menu.Separator,{"data-slot":"dropdown-menu-separator",className:(0,a.cn)("-mx-1 my-1 h-px bg-border",e),...n})},"DropdownMenuTrigger",0,function({...e}){return(0,t.jsx)(r.Menu.Trigger,{"data-slot":"dropdown-menu-trigger",...e})}])},788699,e=>{"use strict";let t=(0,e.i(475254).default)("pencil",[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",key:"1a8usu"}],["path",{d:"m15 5 4 4",key:"1mk7zo"}]]);e.s(["Pencil",0,t],788699)},879664,e=>{"use strict";let t=(0,e.i(475254).default)("info",[["circle",{cx:"12",cy:"12",r:"10",key:"1mglay"}],["path",{d:"M12 16v-4",key:"1dtifu"}],["path",{d:"M12 8h.01",key:"e9boi3"}]]);e.s(["default",0,t])},952571,e=>{"use strict";var t=e.i(879664);e.s(["Info",()=>t.default])},888288,e=>{"use strict";var t=e.i(271645);e.s(["default",0,(e,r)=>{let a=void 0!==r,[n,o]=(0,t.useState)(e);return[a?r:n,e=>{a||o(e)}]}])},118366,e=>{"use strict";var t=e.i(991124);e.s(["CopyIcon",()=>t.default])},768371,e=>{"use strict";let t,r;var a=e.i(247167);let n=/\{[^{}]+\}/g;function o(e,t,r){if(null==t)return"";if("object"==typeof t)throw Error("Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.");return`${e}=${r?.allowReserved===!0?t:encodeURIComponent(t)}`}function s(e,t,r){if(!t||"object"!=typeof t)return"";let a=[],n={simple:",",label:".",matrix:";"}[r.style]||"&";if("deepObject"!==r.style&&!1===r.explode){for(let e in t)a.push(e,!0===r.allowReserved?t[e]:encodeURIComponent(t[e]));let n=a.join(",");switch(r.style){case"form":return`${e}=${n}`;case"label":return`.${n}`;case"matrix":return`;${e}=${n}`;default:return n}}for(let n in t){let s="deepObject"===r.style?`${e}[${n}]`:n;a.push(o(s,t[n],r))}let s=a.join(n);return"label"===r.style||"matrix"===r.style?`${n}${s}`:s}function i(e,t,r){if(!Array.isArray(t))return"";if(!1===r.explode){let a={form:",",spaceDelimited:"%20",pipeDelimited:"|"}[r.style]||",",n=(!0===r.allowReserved?t:t.map(e=>encodeURIComponent(e))).join(a);switch(r.style){case"simple":return n;case"label":return`.${n}`;case"matrix":return`;${e}=${n}`;default:return`${e}=${n}`}}let a={simple:",",label:".",matrix:";"}[r.style]||"&",n=[];for(let a of t)"simple"===r.style||"label"===r.style?n.push(!0===r.allowReserved?a:encodeURIComponent(a)):n.push(o(e,a,r));return"label"===r.style||"matrix"===r.style?`${a}${n.join(a)}`:n.join(a)}function l(e){return function(t){let r=[];if(t&&"object"==typeof t)for(let a in t){let n=t[a];if(null!=n){if(Array.isArray(n)){if(0===n.length)continue;r.push(i(a,n,{style:"form",explode:!0,...e?.array,allowReserved:e?.allowReserved||!1}));continue}if("object"==typeof n){r.push(s(a,n,{style:"deepObject",explode:!0,...e?.object,allowReserved:e?.allowReserved||!1}));continue}r.push(o(a,n,e))}}return r.join("&")}}function d(e,t){let r=e;for(let a of e.match(n)??[]){let e=a.substring(1,a.length-1),n=!1,l="simple";if(e.endsWith("*")&&(n=!0,e=e.substring(0,e.length-1)),e.startsWith(".")?(l="label",e=e.substring(1)):e.startsWith(";")&&(l="matrix",e=e.substring(1)),!t||void 0===t[e]||null===t[e])continue;let d=t[e];if(Array.isArray(d)){r=r.replace(a,i(e,d,{style:l,explode:n}));continue}if("object"==typeof d){r=r.replace(a,s(e,d,{style:l,explode:n}));continue}if("matrix"===l){r=r.replace(a,`;${o(e,d)}`);continue}r=r.replace(a,"label"===l?`.${encodeURIComponent(d)}`:encodeURIComponent(d))}return r}function u(e,t){return e instanceof FormData?e:t&&"application/x-www-form-urlencoded"===(t.get instanceof Function?t.get("Content-Type")??t.get("content-type"):t["Content-Type"]??t["content-type"])?new URLSearchParams(e).toString():JSON.stringify(e)}function c(...e){let t=new Headers;for(let r of e)if(r&&"object"==typeof r)for(let[e,a]of r instanceof Headers?r.entries():Object.entries(r))if(null===a)t.delete(e);else if(Array.isArray(a))for(let r of a)t.append(e,r);else void 0!==a&&t.set(e,a);return t}function p(e){return e.endsWith("/")?e.substring(0,e.length-1):e}var f=e.i(954616),m=e.i(621482),h=e.i(869230),g=e.i(469637),b=e.i(254440),x=e.i(266027),v=e.i(431703),y=e.i(97198);let w=async(e,t)=>new Request(t,{method:e.method,headers:e.headers,body:e.body?await e.arrayBuffer():void 0,mode:e.mode,credentials:e.credentials,cache:e.cache,redirect:e.redirect,referrer:e.referrer,referrerPolicy:e.referrerPolicy,integrity:e.integrity,keepalive:e.keepalive,signal:e.signal}),j=function(e){let{baseUrl:t="",Request:r=globalThis.Request,fetch:n=globalThis.fetch,querySerializer:o,bodySerializer:s,pathSerializer:i,headers:f,requestInitExt:m,...h}={...e};m="object"==typeof a.default&&Number.parseInt(a.default?.versions?.node?.substring(0,2))>=18&&a.default.versions.undici?m:void 0,t=p(t);let g=[];async function b(e,a){var b,x;let v,y,w,j,C,{baseUrl:k,fetch:N=n,Request:R=r,headers:T,params:E={},parseAs:M="json",querySerializer:S,bodySerializer:z=s??u,pathSerializer:I,body:O,middleware:$=[],...A}=a||{},q=t;k&&(q=p(k)??t);let P="function"==typeof o?o:l(o);S&&(P="function"==typeof S?S:l({..."object"==typeof o?o:{},...S}));let U=I||i||d,D=void 0===O?void 0:z(O,c(f,T,E.header)),L=c(void 0===D||D instanceof FormData?{}:{"Content-Type":"application/json"},f,T,E.header),H=[...g,...$],V={redirect:"follow",...h,...A,body:D,headers:L},_=new R((b=e,x={baseUrl:q,params:E,querySerializer:P,pathSerializer:U},v=`${x.baseUrl}${b}`,x.params?.path&&(v=x.pathSerializer(v,x.params.path)),(y=x.querySerializer(x.params.query??{})).startsWith("?")&&(y=y.substring(1)),y&&(v+=`?${y}`),v),V);for(let e in A)e in _||(_[e]=A[e]);if(H.length){for(let t of(w=Math.random().toString(36).slice(2,11),j=Object.freeze({baseUrl:q,fetch:N,parseAs:M,querySerializer:P,bodySerializer:z,pathSerializer:U}),H))if(t&&"object"==typeof t&&"function"==typeof t.onRequest){let r=await t.onRequest({request:_,schemaPath:e,params:E,options:j,id:w});if(r)if(r instanceof R)_=r;else if(r instanceof Response){C=r;break}else throw Error("onRequest: must return new Request() or Response() when modifying the request")}}if(!C){try{C=await N(_,m)}catch(r){let t=r;if(H.length)for(let r=H.length-1;r>=0;r--){let a=H[r];if(a&&"object"==typeof a&&"function"==typeof a.onError){let r=await a.onError({request:_,error:t,schemaPath:e,params:E,options:j,id:w});if(r){if(r instanceof Response){t=void 0,C=r;break}if(r instanceof Error){t=r;continue}throw Error("onError: must return new Response() or instance of Error")}}}if(t)throw t}if(H.length)for(let t=H.length-1;t>=0;t--){let r=H[t];if(r&&"object"==typeof r&&"function"==typeof r.onResponse){let t=await r.onResponse({request:_,response:C,schemaPath:e,params:E,options:j,id:w});if(t){if(!(t instanceof Response))throw Error("onResponse: must return new Response() when modifying the response");C=t}}}}let B=C.headers.get("Content-Length");if(204===C.status||"HEAD"===_.method||"0"===B&&!C.headers.get("Transfer-Encoding")?.includes("chunked"))return C.ok?{data:void 0,response:C}:{error:void 0,response:C};if(C.ok){let e=async()=>{if("stream"===M)return C.body;if("json"===M&&!B){let e=await C.text();return e?JSON.parse(e):void 0}return await C[M]()};return{data:await e(),response:C}}let G=await C.text();try{G=JSON.parse(G)}catch{}return{error:G,response:C}}return{request:(e,t,r)=>b(t,{...r,method:e.toUpperCase()}),GET:(e,t)=>b(e,{...t,method:"GET"}),PUT:(e,t)=>b(e,{...t,method:"PUT"}),POST:(e,t)=>b(e,{...t,method:"POST"}),DELETE:(e,t)=>b(e,{...t,method:"DELETE"}),OPTIONS:(e,t)=>b(e,{...t,method:"OPTIONS"}),HEAD:(e,t)=>b(e,{...t,method:"HEAD"}),PATCH:(e,t)=>b(e,{...t,method:"PATCH"}),TRACE:(e,t)=>b(e,{...t,method:"TRACE"}),use(...e){for(let t of e)if(t){if("object"!=typeof t||!("onRequest"in t||"onResponse"in t||"onError"in t))throw Error("Middleware must be an object with one of `onRequest()`, `onResponse() or `onError()`");g.push(t)}},eject(...e){for(let t of e){let e=g.indexOf(t);-1!==e&&g.splice(e,1)}}}}({baseUrl:globalThis.location?.origin??""});j.use({async onRequest({request:e}){let t=(0,y.getRequestBaseUrl)(),r=t?await w(e,((e,t)=>{let{pathname:r,search:a}=new URL(e);return`${t.replace(/\/+$/,"")}${r}${a}`})(e.url,t)):e,a=(0,y.getAuthToken)();return a&&r.headers.set((0,y.getAuthHeaderName)(),`Bearer ${a}`),r},async onResponse({response:e}){let t;if(e.ok)return e;let r=await e.clone().text(),a=r;try{a=JSON.parse(r),t=(0,v.deriveErrorMessage)(a)}catch{t=r||`HTTP ${e.status}`}throw(0,y.reportError)(t),new v.ApiError(t,e.status,a)}});let C=(t=async({queryKey:[e,t,r],signal:a})=>{let n=j[e.toUpperCase()],{data:o,error:s,response:i}=await n(t,{signal:a,...r});if(s)throw s;return 204===i.status||"0"===i.headers.get("Content-Length")?o??null:o},{queryOptions:r=(e,r,...[a,n])=>({queryKey:void 0===a?[e,r]:[e,r,a],queryFn:t,...n}),useQuery:(e,t,...[a,n,o])=>(0,x.useQuery)(r(e,t,a,n),o),useSuspenseQuery:(e,t,...[a,n,o])=>{var s;return s=r(e,t,a,n),(0,g.useBaseQuery)({...s,enabled:!0,suspense:!0,throwOnError:b.defaultThrowOnError,placeholderData:void 0},h.QueryObserver,o)},useInfiniteQuery:(e,t,a,n,o)=>{let{pageParamName:s="cursor",...i}=n,{queryKey:l}=r(e,t,a);return(0,m.useInfiniteQuery)({queryKey:l,queryFn:async({queryKey:[e,t,r],pageParam:a=0,signal:n})=>{let o=j[e.toUpperCase()],i={...r,signal:n,params:{...r?.params||{},query:{...r?.params?.query,[s]:a}}},{data:l,error:d}=await o(t,i);if(d)throw d;return l},...i},o)},useMutation:(e,t,r,a)=>(0,f.useMutation)({mutationKey:[e,t],mutationFn:async r=>{let a=j[e.toUpperCase()],{data:n,error:o}=await a(t,r);if(o)throw o;return n},...r},a)});e.s(["$api",0,C,"fetchClient",0,j],768371)},738014,e=>{"use strict";var t=e.i(135214),r=e.i(602869),a=e.i(266027);let n=(0,e.i(243652).createQueryKeys)("users");e.s(["useCurrentUser",0,()=>{let{accessToken:e,userId:o}=(0,t.default)();return(0,a.useQuery)({queryKey:n.detail(o),queryFn:async()=>await (0,r.userGetInfoV2)(e),enabled:!!(e&&o)})}])},162386,e=>{"use strict";var t=e.i(843476),r=e.i(625901),a=e.i(109799),n=e.i(785242),o=e.i(738014),s=e.i(199133),i=e.i(981339),l=e.i(592968);let d={label:"All Proxy Models",value:"all-proxy-models"},u={label:"No Default Models",value:"no-default-models"},c=[d,u],p={user:({allProxyModels:e,userModels:t,options:r})=>t&&r?.includeUserModels?t:[],team:({allProxyModels:e,selectedOrganization:t,userModels:r})=>t?t.models.includes(d.value)||0===t.models.length?e:e.filter(e=>t.models.includes(e)):e??[],organization:({allProxyModels:e})=>e,global:({allProxyModels:e})=>e};e.s(["MODEL_SENTINEL_OPTIONS",0,c,"ModelSelect",0,e=>{let{teamID:f,organizationID:m,options:h,context:g,dataTestId:b,value:x=[],onChange:v,style:y}=e,{includeUserModels:w,showAllTeamModelsOption:j,showAllProxyModelsOverride:C,includeSpecialOptions:k}=h||{},{data:N,isLoading:R}=(0,r.useAllProxyModels)(),{data:T,isLoading:E}=(0,n.useTeam)(f),{data:M,isLoading:S}=(0,a.useOrganization)(m),{data:z,isLoading:I}=(0,o.useCurrentUser)(),O=e=>c.some(t=>t.value===e),$=x.some(O),A=M?.models.includes(d.value)||M?.models.length===0;if(R||E||S||I)return(0,t.jsx)(i.Skeleton.Input,{active:!0,block:!0});let{wildcard:q,regular:P}=(e=>{let t=[],r=[];for(let a of e)a.endsWith("/*")?t.push(a):r.push(a);return{wildcard:t,regular:r}})(((e,t,r)=>{let a=Array.from(new Map(e.map(e=>[e.id,e])).values()).map(e=>e.id);if(t.options?.showAllProxyModelsOverride)return a;let n=p[t.context];return n?n({allProxyModels:a,...r,options:t.options}):[]})(N?.data??[],e,{selectedTeam:T,selectedOrganization:M,userModels:z?.models}));return(0,t.jsx)(s.Select,{"data-testid":b,value:x,onChange:e=>{let t=e.filter(O);v(t.length>0?[t[t.length-1]]:e)},style:y,options:[...k?[{label:(0,t.jsx)("span",{children:"Special Options"}),title:"Special Options",options:[...C||A&&k||"global"===g?[{label:(0,t.jsx)("span",{children:"All Proxy Models"}),value:d.value,disabled:x.length>0&&x.some(e=>O(e)&&e!==d.value),key:d.value}]:[],{label:(0,t.jsx)("span",{children:"No Default Models"}),value:u.value,disabled:x.length>0&&x.some(e=>O(e)&&e!==u.value),key:u.value}]}]:[],...q.length>0?[{label:(0,t.jsx)("span",{children:"Wildcard Options"}),title:"Wildcard Options",options:q.map(e=>{let r=e.replace("/*",""),a=r.charAt(0).toUpperCase()+r.slice(1);return{label:(0,t.jsx)("span",{children:`All ${a} models`}),value:e,disabled:$}})}]:[],{label:(0,t.jsx)("span",{children:"Models"}),title:"Models",options:P.map(e=>({label:(0,t.jsx)("span",{children:e}),value:e,disabled:$}))}],mode:"multiple",placeholder:"Select Models",allowClear:!0,maxTagCount:"responsive",maxTagPlaceholder:e=>(0,t.jsx)(l.Tooltip,{styles:{root:{pointerEvents:"none"}},title:e.map(({value:e})=>e).join(", "),children:(0,t.jsxs)("span",{children:["+",e.length," more"]})})})}],162386)},655063,e=>{"use strict";var t=e.i(399029),r=e.i(271645);e.s(["useDebouncedValue",0,function(e,a,n){let[o,s,i]=(0,t.useDebouncedState)(e,a,n);return(0,r.useEffect)(()=>{s(e)},[e,s]),[o,i]}])},278587,e=>{"use strict";var t=e.i(271645);let r=t.forwardRef(function(e,r){return t.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),t.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"}))});e.s(["RefreshIcon",0,r],278587)},624687,e=>{"use strict";var t=e.i(843476),r=e.i(271645),a=e.i(115504);let n=r.forwardRef(({className:e,...r},n)=>(0,t.jsx)("textarea",{ref:n,"data-slot":"textarea",className:(0,a.cn)("flex field-sizing-content min-h-16 w-full rounded-md border border-input bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",e),...r}));n.displayName="Textarea",e.s(["Textarea",0,n])},950594,e=>{"use strict";var t=e.i(843476),r=e.i(271645),a=e.i(115504),n=e.i(519455),o=e.i(793479),s=e.i(624687);let i=(0,a.cva)({base:"flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",variants:{align:{"inline-start":"order-first pl-2 has-[>button]:-ml-1 has-[>kbd]:ml-[-0.15rem]","inline-end":"order-last pr-2 has-[>button]:-mr-1 has-[>kbd]:mr-[-0.15rem]","block-start":"order-first w-full justify-start px-2.5 pt-2 group-has-[>input]/input-group:pt-2 [.border-b]:pb-2","block-end":"order-last w-full justify-start px-2.5 pb-2 group-has-[>input]/input-group:pb-2 [.border-t]:pt-2"}},defaultVariants:{align:"inline-start"}}),l=(0,a.cva)({base:"flex items-center gap-2 text-sm shadow-none",variants:{size:{xs:"h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-1.5 [&>svg:not([class*='size-'])]:size-3.5",sm:"","icon-xs":"size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0","icon-sm":"size-8 p-0 has-[>svg]:p-0"}},defaultVariants:{size:"xs"}}),d=r.forwardRef(({className:e,type:r="button",variant:o="ghost",size:s="xs",...i},d)=>(0,t.jsx)(n.Button,{ref:d,type:r,"data-size":s,variant:o,className:(0,a.cn)(l({size:s}),e),...i}));d.displayName="InputGroupButton";let u=r.forwardRef(({className:e,...r},n)=>(0,t.jsx)(o.Input,{ref:n,"data-slot":"input-group-control",className:(0,a.cn)("flex-1 rounded-none border-0 bg-transparent shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",e),...r}));u.displayName="InputGroupInput",r.forwardRef(({className:e,...r},n)=>(0,t.jsx)(s.Textarea,{ref:n,"data-slot":"input-group-control",className:(0,a.cn)("flex-1 resize-none rounded-none border-0 bg-transparent py-2 shadow-none ring-0 focus-visible:ring-0 aria-invalid:ring-0 dark:bg-transparent",e),...r})).displayName="InputGroupTextarea",e.s(["InputGroup",0,function({className:e,...r}){return(0,t.jsx)("div",{"data-slot":"input-group",role:"group",className:(0,a.cn)("group/input-group relative flex h-9 w-full min-w-0 items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none in-data-[slot=combobox-content]:focus-within:border-inherit in-data-[slot=combobox-content]:focus-within:ring-0 has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-3 has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-3 has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>textarea]:h-auto dark:bg-input/30 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40 has-[>[data-align=block-end]]:[&>input]:pt-3 has-[>[data-align=block-start]]:[&>input]:pb-3 has-[>[data-align=inline-end]]:[&>input]:pr-1.5 has-[>[data-align=inline-start]]:[&>input]:pl-1.5",e),...r})},"InputGroupAddon",0,function({className:e,align:r="inline-start",...n}){return(0,t.jsx)("div",{role:"group","data-slot":"input-group-addon","data-align":r,className:(0,a.cn)(i({align:r}),e),onClick:e=>{e.target.closest("button")||e.currentTarget.parentElement?.querySelector("input")?.focus()},...n})},"InputGroupButton",0,d,"InputGroupInput",0,u,"InputGroupText",0,function({className:e,...r}){return(0,t.jsx)("span",{className:(0,a.cn)("flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",e),...r})}])},552546,e=>{"use strict";var t=e.i(843476),r=e.i(131792);let a=(e,t)=>{let r=t.trim().toLowerCase();return!r||e.label.toLowerCase().includes(r)||(e.sublabel?.toLowerCase().includes(r)??!1)};e.s(["SearchSelect",0,function({options:e,value:n,onValueChange:o,placeholder:s="Select…",emptyText:i="No results",disabled:l=!1,className:d}){let u=e.find(e=>e.value===n)??null;return(0,t.jsxs)(r.Combobox,{items:e,value:u,onValueChange:e=>o(e?.value??""),isItemEqualToValue:(e,t)=>e.value===t.value,itemToStringLabel:e=>e.label,filter:a,disabled:l,children:[(0,t.jsx)(r.ComboboxInput,{placeholder:s,showClear:null!=n&&""!==n,className:`w-full ${d??""}`}),(0,t.jsxs)(r.ComboboxContent,{children:[(0,t.jsx)(r.ComboboxEmpty,{children:i}),(0,t.jsx)(r.ComboboxList,{children:e=>(0,t.jsx)(r.ComboboxItem,{value:e,children:(0,t.jsxs)("span",{className:"flex min-w-0 flex-col",children:[(0,t.jsx)("span",{className:"truncate",children:e.label}),null!=e.sublabel&&""!==e.sublabel&&(0,t.jsx)("span",{className:"truncate text-xs text-muted-foreground",children:e.sublabel})]})},e.value)})]})]})}])},78085,e=>{"use strict";var t=e.i(290571),r=e.i(103471),a=e.i(888288),n=e.i(271645),o=e.i(444755),s=e.i(673706);let i=(0,s.makeClassName)("Textarea"),l=n.default.forwardRef((e,l)=>{let{value:d,defaultValue:u="",placeholder:c="Type...",error:p=!1,errorMessage:f,disabled:m=!1,className:h,onChange:g,onValueChange:b,autoHeight:x=!1}=e,v=(0,t.__rest)(e,["value","defaultValue","placeholder","error","errorMessage","disabled","className","onChange","onValueChange","autoHeight"]),[y,w]=(0,a.default)(u,d),j=(0,n.useRef)(null),C=(0,r.hasValue)(y);return(0,n.useEffect)(()=>{let e=j.current;if(x&&e){e.style.height="60px";let t=e.scrollHeight;e.style.height=t+"px"}},[x,j,y]),n.default.createElement(n.default.Fragment,null,n.default.createElement("textarea",Object.assign({ref:(0,s.mergeRefs)([j,l]),value:y,placeholder:c,disabled:m,className:(0,o.tremorTwMerge)(i("Textarea"),"w-full flex items-center outline-none rounded-tremor-default px-3 py-2 text-tremor-default focus:ring-2 transition duration-100 border","shadow-tremor-input focus:border-tremor-brand-subtle focus:ring-tremor-brand-muted","dark:shadow-dark-tremor-input focus:dark:border-dark-tremor-brand-subtle focus:dark:ring-dark-tremor-brand-muted",(0,r.getSelectButtonColors)(C,m,p),m?"placeholder:text-tremor-content-subtle dark:placeholder:text-dark-tremor-content-subtle":"placeholder:text-tremor-content dark:placeholder:text-dark-tremor-content",h),"data-testid":"text-area",onChange:e=>{null==g||g(e),w(e.target.value),null==b||b(e.target.value)}},v)),p&&f?n.default.createElement("p",{className:(0,o.tremorTwMerge)(i("errorMessage"),"text-sm text-red-500 mt-1")},f):null)});l.displayName="Textarea",e.s(["Textarea",0,l],78085)},744582,e=>{"use strict";var t=e.i(843476),r=e.i(343488),a=e.i(531278),n=e.i(271645),o=e.i(131792),s=e.i(741466);let i=new Set(["input-change","input-clear","clear-press"]);e.s(["PaginatedSearchSelect",0,function({options:e,value:l,onValueChange:d,onSearchChange:u,onLoadMore:c,hasNextPage:p=!1,isLoading:f=!1,isFetchingNextPage:m=!1,placeholder:h="Search…",emptyText:g="No results",loadingText:b="Loading…",disabled:x=!1,className:v,inputId:y,"aria-invalid":w,"aria-describedby":j}){let C=(0,n.useMemo)(()=>void 0===l||""===l?null:e.find(e=>e.value===l)??{label:l,value:l},[e,l]),k=(0,n.useMemo)(()=>null===C||e.some(e=>e.value===C.value)?e:[C,...e],[e,C]),N=(0,r.useDebouncedCallback)(u,{wait:s.DEBOUNCE_WAIT_MS});return(0,t.jsxs)(o.Combobox,{items:k,value:C,onValueChange:e=>d(e?.value??""),onInputValueChange:(e,t)=>{var r;return r=t.reason,void(i.has(r)&&N(e))},isItemEqualToValue:(e,t)=>e.value===t.value,itemToStringLabel:e=>e.label,filter:null,disabled:x,children:[(0,t.jsx)(o.ComboboxInput,{id:y,"aria-invalid":w,"aria-describedby":j,placeholder:h,showClear:void 0!==l&&""!==l,className:`w-full ${v??""}`}),(0,t.jsxs)(o.ComboboxContent,{children:[(0,t.jsx)(o.ComboboxEmpty,{children:f?b:g}),(0,t.jsx)(o.ComboboxList,{onScroll:e=>{let t=e.currentTarget;0===t.scrollHeight||(t.scrollTop+t.clientHeight)/t.scrollHeight>=.8&&p&&!m&&c()},"data-testid":"paginated-search-select-list",children:e=>(0,t.jsx)(o.ComboboxItem,{value:e,children:(0,t.jsxs)("span",{className:"flex min-w-0 flex-col",children:[(0,t.jsx)("span",{className:"truncate",children:e.label}),null!=e.sublabel&&""!==e.sublabel&&(0,t.jsx)("span",{className:"truncate text-xs text-muted-foreground",children:e.sublabel})]})},e.value)}),m&&(0,t.jsx)("div",{className:"flex justify-center py-2","data-testid":"paginated-search-select-loading-more",children:(0,t.jsx)(a.Loader2,{className:"size-4 animate-spin text-muted-foreground"})})]})]})}])}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/08691-q-pz235.js b/litellm/proxy/_experimental/out/_next/static/chunks/08691-q-pz235.js deleted file mode 100644 index 7f524f1964b..00000000000 --- a/litellm/proxy/_experimental/out/_next/static/chunks/08691-q-pz235.js +++ /dev/null @@ -1 +0,0 @@ -(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,788699,e=>{"use strict";let t=(0,e.i(475254).default)("pencil",[["path",{d:"M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",key:"1a8usu"}],["path",{d:"m15 5 4 4",key:"1mk7zo"}]]);e.s(["Pencil",0,t],788699)},332102,e=>{"use strict";let t=(0,e.i(475254).default)("inbox",[["polyline",{points:"22 12 16 12 14 15 10 15 8 12 2 12",key:"o97t9d"}],["path",{d:"M5.45 5.11 2 12v6a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2v-6l-3.45-6.89A2 2 0 0 0 16.76 4H7.24a2 2 0 0 0-1.79 1.11z",key:"oot6mr"}]]);e.s(["Inbox",0,t],332102)},677572,370359,405934,e=>{"use strict";var t,r,o,a=e.i(843476);e.s([],559657),e.i(559657),e.i(247167);var n=e.i(271645),i=e.i(951437),l=e.i(146376),s=e.i(667865),u=e.i(552245),c=e.i(53687),d=e.i(733332);let h=n.createContext(void 0);function g(){let e=n.useContext(h);if(void 0===e)throw Error((0,d.default)(64));return e}let b=((t={}).activationDirection="data-activation-direction",t.orientation="data-orientation",t),f={tabActivationDirection:e=>({[b.activationDirection]:e})};var p=e.i(675606),m=e.i(56434);let v=n.forwardRef(function(e,t){let{className:r,defaultValue:o=0,onValueChange:d,orientation:g="horizontal",render:b,value:v,style:w,...C}=e,x=void 0!==e.defaultValue,y=n.useRef([]),[R,S]=n.useState(()=>new Map),[E,M]=(0,i.useControlled)({controlled:v,default:o,name:"Tabs",state:"value"}),T=void 0!==v,[O,N]=n.useState(()=>new Map),P=n.useRef(void 0),I=n.useCallback(e=>{if(void 0===e)return null;for(let[t,r]of O.entries())if(null!=r&&e===(r.value??r.index))return t;return null},[O]),[L,j]=n.useState(()=>({previousValue:E,tabActivationDirection:"none"})),{previousValue:z,tabActivationDirection:A}=L,D=A,_=!1;z!==E&&(D=k(z,E,g,O),_=null!=z&&null!=E&&null==I(E));let H=_?z:E,W=z!==H||A!==D;(0,l.useIsoLayoutEffect)(()=>{W&&j({previousValue:H,tabActivationDirection:D})},[H,W,D]);let F=(0,s.useStableCallback)((e,t)=>{t.activationDirection=k(E,e,g,O),d?.(e,t),t.isCanceled||M(e)}),K=(0,s.useStableCallback)((e,t)=>{d?.(e,(0,p.createChangeEventDetails)(t,void 0,void 0,{activationDirection:"none"}))}),B=(0,s.useStableCallback)((e,t)=>{S(r=>{if(r.get(e)===t)return r;let o=new Map(r);return o.set(e,t),o})}),$=(0,s.useStableCallback)((e,t)=>{S(r=>{if(!r.has(e)||r.get(e)!==t)return r;let o=new Map(r);return o.delete(e),o})}),Y=n.useCallback(e=>R.get(e),[R]),V=n.useCallback(e=>{for(let t of O.values())if(e===t?.value)return t?.id},[O]),G=n.useMemo(()=>({getTabElementBySelectedValue:I,getTabIdByPanelValue:V,getTabPanelIdByValue:Y,onValueChange:F,orientation:g,registerMountedTabPanel:B,setTabMap:N,unregisterMountedTabPanel:$,tabActivationDirection:D,value:E}),[I,V,Y,F,g,B,N,$,D,E]),q=n.useMemo(()=>{for(let e of O.values())if(null!=e&&e.value===E)return e},[O,E]),U=n.useMemo(()=>{for(let e of O.values())if(null!=e&&!e.disabled)return e.value},[O]),X=n.useRef(!x),Q=n.useRef(o),Z=n.useRef(x),J=n.useRef(!1);(0,l.useIsoLayoutEffect)(()=>{if(T)return;function e(e,t){M(e),j(t=>t.previousValue===e&&"none"===t.tabActivationDirection?t:{previousValue:e,tabActivationDirection:"none"}),K(e,t),X.current=!1}if(0===O.size){J.current&&null!==E&&!P.current?.isConnected&&e(null,m.REASONS.missing);return}J.current=!0,P.current=O.keys().next().value;let t=q?.disabled,r=null==q&&null!==E;if(t||E!==Q.current||(Z.current=!1),Z.current&&t&&E===Q.current)return;let o=X.current;if(t||r){let r=U??null;if(E===r){X.current=!1;return}let a=m.REASONS.missing;o?a=m.REASONS.initial:t&&(a=m.REASONS.disabled),e(r,a);return}o&&null!=q&&(K(E,m.REASONS.initial),X.current=!1)},[U,T,K,q,M,O,E]);let ee={orientation:g,tabActivationDirection:D},et=(0,u.useRenderElement)("div",e,{state:ee,ref:t,props:C,stateAttributesMapping:f});return(0,a.jsx)(h.Provider,{value:G,children:(0,a.jsx)(c.CompositeList,{elementsRef:y,children:et})})});function k(e,t,r,o){if(null==e||null==t)return"none";let a=null,n=null;for(let[r,i]of o.entries()){if(null==i)continue;let o=i.value??i.index;if(e===o&&(a=r),t===o&&(n=r),null!=a&&null!=n)break}if(null==a||null==n)return a!==n&&("number"==typeof e||"string"==typeof e)&&typeof e==typeof t?"horizontal"===r?t>e?"right":"left":t>e?"down":"up":"none";let i=a.getBoundingClientRect(),l=n.getBoundingClientRect();if("horizontal"===r){if(l.lefti.left)return"right"}else{if(l.topi.top)return"down"}return"none"}var w=e.i(108868),C=e.i(788015),x=e.i(540886);let y="data-composite-item-active";e.s(["ACTIVE_COMPOSITE_ITEM",0,y],370359);var R=e.i(395530);let S=n.createContext(void 0);function E(){let e=n.useContext(S);if(void 0===e)throw Error((0,d.default)(65));return e}var M=e.i(647554);let T=n.forwardRef(function(e,t){let{className:r,disabled:o=!1,render:a,value:i,id:s,nativeButton:c=!0,style:d,...h}=e,{value:b,getTabPanelIdByValue:v,orientation:k,tabActivationDirection:S}=g(),{activateOnFocus:T,highlightedTabIndex:O,onTabActivation:N,registerTabResizeObserverElement:P,setHighlightedTabIndex:I,tabsListElement:L}=E(),j=(0,C.useBaseUiId)(s),z=n.useMemo(()=>({disabled:o,id:j,value:i}),[o,j,i]),{compositeProps:A,compositeRef:D,index:_}=(0,R.useCompositeItem)({metadata:z}),H=i===b,W=n.useRef(!1),F=n.useRef(null);(0,l.useIsoLayoutEffect)(()=>{let e=F.current;if(e)return P(e)},[P]),(0,l.useIsoLayoutEffect)(()=>{if(W.current){W.current=!1;return}if(H&&_>-1&&O!==_){if(null!=L){let e=(0,M.activeElement)((0,w.ownerDocument)(L));if(e&&(0,M.contains)(L,e))return}o||I(_)}},[H,_,O,I,o,L]);let{getButtonProps:K,buttonRef:B}=(0,x.useButton)({disabled:o,native:c,focusableWhenDisabled:!0}),$=v(i),Y=n.useRef(!1),V=n.useRef(!1);return(0,u.useRenderElement)("button",e,{state:{disabled:o,active:H,orientation:k,tabActivationDirection:S},ref:[t,B,D,F],props:[A,{role:"tab","aria-controls":$,"aria-selected":H,id:j,onClick:function(e){H||o||N(i,(0,p.createChangeEventDetails)(m.REASONS.none,e.nativeEvent,void 0,{activationDirection:"none"}))},onFocus:function(e){H||(_>-1&&!o&&I(_),!o&&T&&(!Y.current||Y.current&&V.current)&&N(i,(0,p.createChangeEventDetails)(m.REASONS.none,e.nativeEvent,void 0,{activationDirection:"none"})))},onPointerDown:function(e){H||o||(Y.current=!0,e.button&&0!==e.button||(V.current=!0,(0,w.ownerDocument)(e.currentTarget).addEventListener("pointerup",function(){Y.current=!1,V.current=!1},{once:!0})))},[y]:H?"":void 0,onKeyDownCapture(){W.current=!0}},h,K],stateAttributesMapping:f})});var O=e.i(73364),N=e.i(802239),P=e.i(956789);function I(){return P.NOOP}function L(){return!1}function j(){return!0}let z=((r={}).activeTabLeft="--active-tab-left",r.activeTabRight="--active-tab-right",r.activeTabTop="--active-tab-top",r.activeTabBottom="--active-tab-bottom",r.activeTabWidth="--active-tab-width",r.activeTabHeight="--active-tab-height",r);var A=e.i(172410);let D={...f,activeTabPosition:()=>null,activeTabSize:()=>null},_=n.forwardRef(function(e,t){let{className:r,render:o,renderBeforeHydration:i=!1,style:l,...s}=e,{nonce:c}=(0,A.useCSPContext)(),{getTabElementBySelectedValue:d,orientation:h,tabActivationDirection:b,value:f}=g(),{tabsListElement:p,registerIndicatorUpdateListener:m}=E(),v=(0,N.useSyncExternalStore)(I,L,j),k=function(){let[,e]=n.useState({});return n.useCallback(()=>{e({})},[])}();n.useEffect(()=>m(k),[m,k]);let w=0,C=0,x=0,y=0,R=0,S=0,M=!1;if(null!=f&&null!=p){let e=d(f);if(null!=e){M=!0;let{width:t,height:r}=(0,O.getCssDimensions)(e),{width:o,height:a}=(0,O.getCssDimensions)(p),n=e.getBoundingClientRect(),i=p.getBoundingClientRect(),l=o>0?i.width/o:1,s=a>0?i.height/a:1;if(Math.abs(l)>Number.EPSILON&&Math.abs(s)>Number.EPSILON){let e=n.left-i.left,t=n.top-i.top;w=e/l+p.scrollLeft-p.clientLeft,x=t/s+p.scrollTop-p.clientTop}else w=e.offsetLeft,x=e.offsetTop;R=t,S=r,C=p.scrollWidth-w-R,y=p.scrollHeight-x-S}}let T=M?{left:w,right:C,top:x,bottom:y}:null,P=M?{width:R,height:S}:null,_=M?{[z.activeTabLeft]:`${w}px`,[z.activeTabRight]:`${C}px`,[z.activeTabTop]:`${x}px`,[z.activeTabBottom]:`${y}px`,[z.activeTabWidth]:`${R}px`,[z.activeTabHeight]:`${S}px`}:void 0,H=M&&R>0&&S>0,W=(0,u.useRenderElement)("span",e,{state:{orientation:h,activeTabPosition:T,activeTabSize:P,tabActivationDirection:b},ref:t,props:[{role:"presentation",style:_,hidden:!H},s,{suppressHydrationWarning:!0}],stateAttributesMapping:D});return null==f?null:(0,a.jsxs)(n.Fragment,{children:[W,v&&i&&(0,a.jsx)("script",{nonce:c,dangerouslySetInnerHTML:{__html:'!function(){const t=document.currentScript.previousElementSibling;if(!t)return;const e=t.closest(\'[role="tablist"]\');if(!e)return;const i=e.querySelector("[data-active]");if(!i)return;if(0===i.offsetWidth||0===e.offsetWidth)return;let o=0,n=0,h=0,l=0,r=0,f=0;function s(t){const e=getComputedStyle(t);let i=parseFloat(e.width)||0,o=parseFloat(e.height)||0;return(Math.round(i)!==t.offsetWidth||Math.round(o)!==t.offsetHeight)&&(i=t.offsetWidth,o=t.offsetHeight),{width:i,height:o}}if(null!=i&&null!=e){const{width:t,height:c}=s(i),{width:u,height:d}=s(e),a=i.getBoundingClientRect(),g=e.getBoundingClientRect(),p=u>0?g.width/u:1,b=d>0?g.height/d:1;if(Math.abs(p)>Number.EPSILON&&Math.abs(b)>Number.EPSILON){const t=a.left-g.left,i=a.top-g.top;o=t/p+e.scrollLeft-e.clientLeft,h=i/b+e.scrollTop-e.clientTop}else o=i.offsetLeft,h=i.offsetTop;r=t,f=c,n=e.scrollWidth-o-r,l=e.scrollHeight-h-f}function c(e,i){t.style.setProperty(`--active-tab-${e}`,`${i}px`)}c("left",o),c("right",n),c("top",h),c("bottom",l),c("width",r),c("height",f),r>0&&f>0&&t.removeAttribute("hidden")}();'},suppressHydrationWarning:!0})]})});var H=e.i(144394),W=e.i(209407),F=e.i(137584),K=e.i(223910),B=e.i(673553);let $=((o={}).index="data-index",o.activationDirection="data-activation-direction",o.orientation="data-orientation",o.hidden="data-hidden",o[o.startingStyle=W.TransitionStatusDataAttributes.startingStyle]="startingStyle",o[o.endingStyle=W.TransitionStatusDataAttributes.endingStyle]="endingStyle",o),Y={...f,...W.transitionStatusMapping},V=n.forwardRef(function(e,t){let{className:r,value:o,render:a,keepMounted:i=!1,style:s,...c}=e,{value:d,getTabIdByPanelValue:h,orientation:b,tabActivationDirection:f,registerMountedTabPanel:p,unregisterMountedTabPanel:m}=g(),v=(0,C.useBaseUiId)(),k=n.useMemo(()=>({id:v,value:o}),[v,o]),{ref:w,index:x}=(0,B.useCompositeListItem)({metadata:k}),y=o===d,{mounted:R,transitionStatus:S,setMounted:E}=(0,K.useTransitionStatus)(y),M=!R,T=h(o),O=n.useRef(null),N=(0,u.useRenderElement)("div",e,{state:{hidden:M,orientation:b,tabActivationDirection:f,transitionStatus:S},ref:[t,w,O],props:[{"aria-labelledby":T,hidden:M,id:v,role:"tabpanel",tabIndex:y?0:-1,inert:(0,H.inertValue)(!y),[$.index]:x},c],stateAttributesMapping:Y});return((0,F.useOpenChangeComplete)({open:y,ref:O,onComplete(){y||E(!1)}}),(0,l.useIsoLayoutEffect)(()=>{if((!M||i)&&null!=v)return p(o,v),()=>{m(o,v)}},[M,i,o,v,p,m]),i||R)?N:null});var G=e.i(590803),q=e.i(828918),U=e.i(673327),X=e.i(621082);let Q=[];var Z=e.i(838452),J=e.i(872855);function ee(e){let{render:t,className:r,style:o,refs:i=P.EMPTY_ARRAY,props:d=P.EMPTY_ARRAY,state:h=P.EMPTY_OBJECT,stateAttributesMapping:g,highlightedIndex:b,onHighlightedIndexChange:f,orientation:p,grid:m,loopFocus:v,onLoop:k,enableHomeAndEndKeys:w,onMapChange:C,stopEventPropagation:x=!0,rootRef:R,disabledIndices:S,modifierKeys:E,highlightItemOnHover:T=!1,tag:O="div",...N}=e,{props:I,highlightedIndex:L,onHighlightedIndexChange:j,elementsRef:z,onMapChange:A,relayKeyboardEvent:D}=function(e){let{loopFocus:t=!0,orientation:r="both",grid:o,onLoop:a,direction:i,highlightedIndex:u,onHighlightedIndexChange:c,rootRef:d,enableHomeAndEndKeys:h=!1,stopEventPropagation:g=!1,disabledIndices:b,modifierKeys:f=Q}=e,[p,m]=n.useState(0),v=null!=o,k=n.useRef(null),w=(0,q.useMergedRefs)(k,d),C=n.useRef([]),x=n.useRef(!1),R=u??p,S=(0,s.useStableCallback)((e,t=!1)=>{if((c??m)(e),t){let t=C.current[e];(0,U.scrollIntoViewIfNeeded)(k.current,t,i,r)}}),E=(0,s.useStableCallback)(e=>{if(0===e.size||x.current)return;x.current=!0;let t=Array.from(e.keys()),o=t.find(e=>e?.hasAttribute(y))??null,a=o?t.indexOf(o):-1;if(-1!==a)S(a);else if((0,X.isListIndexDisabled)(t,R,b)){let e=(0,X.findNonDisabledListIndex)(t,{disabledIndices:b});(0,X.isIndexOutOfListBounds)(t,e)||S(e)}(0,U.scrollIntoViewIfNeeded)(k.current,o,i,r)});(0,l.useIsoLayoutEffect)(()=>{if(null==b||null!=u||!x.current)return;let e=C.current;if((0,X.isListIndexDisabled)(e,R,b)){let t=(0,X.findNonDisabledListIndex)(e,{disabledIndices:b});(0,X.isIndexOutOfListBounds)(e,t)||S(t)}},[b,u,R,C,S]);let T=(0,s.useStableCallback)((e,t,r)=>a?a(e,t,r,C):r),O=(0,s.useStableCallback)(e=>{let n=h?U.COMPOSITE_KEYS:U.ARROW_KEYS;if(!n.has(e.key)||function(e,t){for(let r of U.MODIFIER_KEYS.values())if(!t.includes(r)&&e.getModifierState(r))return!0;return!1}(e,f)||!k.current)return;let l="rtl"===i,s=l?U.ARROW_LEFT:U.ARROW_RIGHT,u={horizontal:s,vertical:U.ARROW_DOWN,both:s}[r],c=l?U.ARROW_RIGHT:U.ARROW_LEFT,d={horizontal:c,vertical:U.ARROW_UP,both:c}[r],p=(0,M.getTarget)(e.nativeEvent);if(null!=p&&(0,U.isNativeInput)(p)&&!(0,G.isElementDisabled)(p)){let t=p.selectionStart,r=p.selectionEnd,o=p.value??"";if(null==t||e.shiftKey||t!==r||e.key!==d&&t0)return}let m=R,w=(0,X.getMinListIndex)(C,b),x=(0,X.getMaxListIndex)(C,b);null!=o&&(m=o({disabledIndices:b,elementsRef:C,event:e,highlightedIndex:R,loopFocus:t,maxIndex:x,minIndex:w,onLoop:T,orientation:r,rtl:l}));let y={horizontal:[s],vertical:[U.ARROW_DOWN],both:[s,U.ARROW_DOWN]}[r],E={horizontal:[c],vertical:[U.ARROW_UP],both:[c,U.ARROW_UP]}[r],O=v?n:({horizontal:h?U.HORIZONTAL_KEYS_WITH_EXTRA_KEYS:U.HORIZONTAL_KEYS,vertical:h?U.VERTICAL_KEYS_WITH_EXTRA_KEYS:U.VERTICAL_KEYS,both:n})[r];h&&(e.key===U.HOME?m=w:e.key===U.END&&(m=x)),m===R&&(y.includes(e.key)||E.includes(e.key))&&(t&&m===x&&y.includes(e.key)?(m=w,a&&(m=a(e,R,m,C))):t&&m===w&&E.includes(e.key)?(m=x,a&&(m=a(e,R,m,C))):m=(0,X.findNonDisabledListIndex)(C.current,{startingIndex:m,decrement:E.includes(e.key),disabledIndices:b})),m===R||(0,X.isIndexOutOfListBounds)(C.current,m)||(g&&e.stopPropagation(),O.has(e.key)&&e.preventDefault(),S(m,!0),queueMicrotask(()=>{C.current[m]?.focus()}))});return{props:{ref:w,onFocus(e){let t=k.current,r=(0,M.getTarget)(e.nativeEvent);t&&null!=r&&(0,U.isNativeInput)(r)&&r.setSelectionRange(0,r.value.length??0)},onKeyDown:O},highlightedIndex:R,onHighlightedIndexChange:S,elementsRef:C,disabledIndices:b,onMapChange:E,relayKeyboardEvent:O}}({grid:m,loopFocus:v,onLoop:k,orientation:p,highlightedIndex:b,onHighlightedIndexChange:f,rootRef:R,stopEventPropagation:x,enableHomeAndEndKeys:w,direction:(0,J.useDirection)(),disabledIndices:S,modifierKeys:E}),_=(0,u.useRenderElement)(O,e,{state:h,ref:i,props:[I,...d,N],stateAttributesMapping:g}),H=n.useMemo(()=>({highlightedIndex:L,onHighlightedIndexChange:j,highlightItemOnHover:T,relayKeyboardEvent:D}),[L,j,T,D]);return(0,a.jsx)(Z.CompositeRootContext.Provider,{value:H,children:(0,a.jsx)(c.CompositeList,{elementsRef:z,onMapChange:e=>{C?.(e),A(e)},children:_})})}e.s(["CompositeRoot",0,ee],405934);let et=n.forwardRef(function(e,t){let{activateOnFocus:r=!1,className:o,loopFocus:i=!0,render:u,style:c,...d}=e,{onValueChange:h,orientation:b,value:p,setTabMap:m,tabActivationDirection:v}=g(),[k,w]=n.useState(0),[C,x]=n.useState(null),y=n.useRef(new Set),R=n.useRef(new Set),E=n.useRef(null);(0,l.useIsoLayoutEffect)(()=>{if("u"{y.current.forEach(e=>{e()})});return E.current=e,C&&e.observe(C),R.current.forEach(t=>{e.observe(t)}),()=>{e.disconnect(),E.current=null}},[C]);let M=(0,s.useStableCallback)(e=>(y.current.add(e),()=>{y.current.delete(e)})),T=(0,s.useStableCallback)(e=>(R.current.add(e),E.current?.observe(e),()=>{R.current.delete(e),E.current?.unobserve(e)})),O=(0,s.useStableCallback)((e,t)=>{e!==p&&h(e,t)}),N=n.useMemo(()=>({activateOnFocus:r,highlightedTabIndex:k,registerIndicatorUpdateListener:M,registerTabResizeObserverElement:T,onTabActivation:O,setHighlightedTabIndex:w,tabsListElement:C}),[r,k,M,T,O,w,C]);return(0,a.jsx)(S.Provider,{value:N,children:(0,a.jsx)(ee,{render:u,className:o,style:c,state:{orientation:b,tabActivationDirection:v},refs:[t,x],props:[{"aria-orientation":"vertical"===b?"vertical":void 0,role:"tablist"},d],stateAttributesMapping:f,highlightedIndex:k,enableHomeAndEndKeys:!0,loopFocus:i,orientation:b,onHighlightedIndexChange:w,onMapChange:m,disabledIndices:P.EMPTY_ARRAY})})});e.s(["Indicator",0,_,"List",0,et,"Panel",0,V,"Root",0,v,"Tab",0,T],69281);var er=e.i(69281),er=er,eo=e.i(115504);let ea=(0,eo.cva)({base:"group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-9 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",variants:{variant:{default:"bg-muted",line:"gap-1 bg-transparent"}},defaultVariants:{variant:"default"}});e.s(["Tabs",0,function({className:e,orientation:t="horizontal",...r}){return(0,a.jsx)(er.Root,{"data-slot":"tabs","data-orientation":t,className:(0,eo.cn)("group/tabs flex gap-2 data-horizontal:flex-col",e),...r})},"TabsContent",0,function({className:e,...t}){return(0,a.jsx)(er.Panel,{"data-slot":"tabs-content",className:(0,eo.cn)("flex-1 text-sm outline-none",e),...t})},"TabsList",0,function({className:e,variant:t="default",...r}){return(0,a.jsx)(er.List,{"data-slot":"tabs-list","data-variant":t,className:(0,eo.cn)(ea({variant:t}),e),...r})},"TabsTrigger",0,function({className:e,...t}){return(0,a.jsx)(er.Tab,{"data-slot":"tabs-trigger",className:(0,eo.cn)("relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4","group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent","data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground","after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",e),...t})}],677572)},653496,e=>{"use strict";var t=e.i(721369);e.s(["Tabs",()=>t.default])},629569,e=>{"use strict";var t=e.i(290571),r=e.i(95779),o=e.i(444755),a=e.i(673706),n=e.i(271645);let i=n.default.forwardRef((e,i)=>{let{color:l,children:s,className:u}=e,c=(0,t.__rest)(e,["color","children","className"]);return n.default.createElement("p",Object.assign({ref:i,className:(0,o.tremorTwMerge)("font-medium text-tremor-title",l?(0,a.getColorClassNames)(l,r.colorPalette.darkText).textColor:"text-tremor-content-strong dark:text-dark-tremor-content-strong",u)},c),s)});i.displayName="Title",e.s(["Title",0,i],629569)},304967,e=>{"use strict";var t=e.i(290571),r=e.i(271645),o=e.i(480731),a=e.i(95779),n=e.i(444755),i=e.i(673706);let l=(0,i.makeClassName)("Card"),s=r.default.forwardRef((e,s)=>{let{decoration:u="",decorationColor:c,children:d,className:h}=e,g=(0,t.__rest)(e,["decoration","decorationColor","children","className"]);return r.default.createElement("div",Object.assign({ref:s,className:(0,n.tremorTwMerge)(l("root"),"relative w-full text-left ring-1 rounded-tremor-default p-6","bg-tremor-background ring-tremor-ring shadow-tremor-card","dark:bg-dark-tremor-background dark:ring-dark-tremor-ring dark:shadow-dark-tremor-card",c?(0,i.getColorClassNames)(c,a.colorPalette.border).borderColor:"border-tremor-brand dark:border-dark-tremor-brand",(e=>{if(!e)return"";switch(e){case o.HorizontalPositions.Left:return"border-l-4";case o.VerticalPositions.Top:return"border-t-4";case o.HorizontalPositions.Right:return"border-r-4";case o.VerticalPositions.Bottom:return"border-b-4";default:return""}})(u),h)},g),d)});s.displayName="Card",e.s(["Card",0,s],304967)},954616,e=>{"use strict";var t=e.i(271645),r=e.i(114272),o=e.i(540143),a=e.i(915823),n=e.i(619273),i=class extends a.Subscribable{#e;#t=void 0;#r;#o;constructor(e,t){super(),this.#e=e,this.setOptions(t),this.bindMethods(),this.#a()}bindMethods(){this.mutate=this.mutate.bind(this),this.reset=this.reset.bind(this)}setOptions(e){let t=this.options;this.options=this.#e.defaultMutationOptions(e),(0,n.shallowEqualObjects)(this.options,t)||this.#e.getMutationCache().notify({type:"observerOptionsUpdated",mutation:this.#r,observer:this}),t?.mutationKey&&this.options.mutationKey&&(0,n.hashKey)(t.mutationKey)!==(0,n.hashKey)(this.options.mutationKey)?this.reset():this.#r?.state.status==="pending"&&this.#r.setOptions(this.options)}onUnsubscribe(){this.hasListeners()||this.#r?.removeObserver(this)}onMutationUpdate(e){this.#a(),this.#n(e)}getCurrentResult(){return this.#t}reset(){this.#r?.removeObserver(this),this.#r=void 0,this.#a(),this.#n()}mutate(e,t){return this.#o=t,this.#r?.removeObserver(this),this.#r=this.#e.getMutationCache().build(this.#e,this.options),this.#r.addObserver(this),this.#r.execute(e)}#a(){let e=this.#r?.state??(0,r.getDefaultState)();this.#t={...e,isPending:"pending"===e.status,isSuccess:"success"===e.status,isError:"error"===e.status,isIdle:"idle"===e.status,mutate:this.mutate,reset:this.reset}}#n(e){o.notifyManager.batch(()=>{if(this.#o&&this.hasListeners()){let t=this.#t.variables,r=this.#t.context,o={client:this.#e,meta:this.options.meta,mutationKey:this.options.mutationKey};if(e?.type==="success"){try{this.#o.onSuccess?.(e.data,t,r,o)}catch(e){Promise.reject(e)}try{this.#o.onSettled?.(e.data,null,t,r,o)}catch(e){Promise.reject(e)}}else if(e?.type==="error"){try{this.#o.onError?.(e.error,t,r,o)}catch(e){Promise.reject(e)}try{this.#o.onSettled?.(void 0,e.error,t,r,o)}catch(e){Promise.reject(e)}}}this.listeners.forEach(e=>{e(this.#t)})})}},l=e.i(912598);e.s(["useMutation",0,function(e,r){let a=(0,l.useQueryClient)(r),[s]=t.useState(()=>new i(a,e));t.useEffect(()=>{s.setOptions(e)},[s,e]);let u=t.useSyncExternalStore(t.useCallback(e=>s.subscribe(o.notifyManager.batchCalls(e)),[s]),()=>s.getCurrentResult(),()=>s.getCurrentResult()),c=t.useCallback((e,t)=>{s.mutate(e,t).catch(n.noop)},[s]);if(u.error&&(0,n.shouldThrowError)(s.options.throwOnError,[u.error]))throw u.error;return{...u,mutate:c,mutateAsync:u.mutate}}],954616)},107233,603908,e=>{"use strict";let t=(0,e.i(475254).default)("plus",[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"M12 5v14",key:"s699le"}]]);e.s(["default",0,t],603908),e.s(["Plus",0,t],107233)},646563,e=>{"use strict";var t=e.i(959013);e.s(["PlusOutlined",()=>t.default])},525720,e=>{"use strict";e.i(247167);var t=e.i(271645),r=e.i(343794),o=e.i(529681),a=e.i(908286),n=e.i(242064),i=e.i(246422),l=e.i(838378);let s=["wrap","nowrap","wrap-reverse"],u=["flex-start","flex-end","start","end","center","space-between","space-around","space-evenly","stretch","normal","left","right"],c=["center","start","end","flex-start","flex-end","self-start","self-end","baseline","normal","stretch"],d=function(e,t){let o,a,n;return(0,r.default)(Object.assign(Object.assign(Object.assign({},(o=!0===t.wrap?"wrap":t.wrap,{[`${e}-wrap-${o}`]:o&&s.includes(o)})),(a={},c.forEach(r=>{a[`${e}-align-${r}`]=t.align===r}),a[`${e}-align-stretch`]=!t.align&&!!t.vertical,a)),(n={},u.forEach(r=>{n[`${e}-justify-${r}`]=t.justify===r}),n)))},h=(0,i.genStyleHooks)("Flex",e=>{let{paddingXS:t,padding:r,paddingLG:o}=e,a=(0,l.mergeToken)(e,{flexGapSM:t,flexGap:r,flexGapLG:o});return[(e=>{let{componentCls:t}=e;return{[t]:{display:"flex",margin:0,padding:0,"&-vertical":{flexDirection:"column"},"&-rtl":{direction:"rtl"},"&:empty":{display:"none"}}}})(a),(e=>{let{componentCls:t}=e;return{[t]:{"&-gap-small":{gap:e.flexGapSM},"&-gap-middle":{gap:e.flexGap},"&-gap-large":{gap:e.flexGapLG}}}})(a),(e=>{let{componentCls:t}=e,r={};return s.forEach(e=>{r[`${t}-wrap-${e}`]={flexWrap:e}}),r})(a),(e=>{let{componentCls:t}=e,r={};return c.forEach(e=>{r[`${t}-align-${e}`]={alignItems:e}}),r})(a),(e=>{let{componentCls:t}=e,r={};return u.forEach(e=>{r[`${t}-justify-${e}`]={justifyContent:e}}),r})(a)]},()=>({}),{resetStyle:!1});var g=function(e,t){var r={};for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&0>t.indexOf(o)&&(r[o]=e[o]);if(null!=e&&"function"==typeof Object.getOwnPropertySymbols)for(var a=0,o=Object.getOwnPropertySymbols(e);at.indexOf(o[a])&&Object.prototype.propertyIsEnumerable.call(e,o[a])&&(r[o[a]]=e[o[a]]);return r};let b=t.default.forwardRef((e,i)=>{let{prefixCls:l,rootClassName:s,className:u,style:c,flex:b,gap:f,vertical:p=!1,component:m="div",children:v}=e,k=g(e,["prefixCls","rootClassName","className","style","flex","gap","vertical","component","children"]),{flex:w,direction:C,getPrefixCls:x}=t.default.useContext(n.ConfigContext),y=x("flex",l),[R,S,E]=h(y),M=null!=p?p:null==w?void 0:w.vertical,T=(0,r.default)(u,s,null==w?void 0:w.className,y,S,E,d(y,e),{[`${y}-rtl`]:"rtl"===C,[`${y}-gap-${f}`]:(0,a.isPresetSize)(f),[`${y}-vertical`]:M}),O=Object.assign(Object.assign({},null==w?void 0:w.style),c);return b&&(O.flex=b),f&&!(0,a.isPresetSize)(f)&&(O.gap=f),R(t.default.createElement(m,Object.assign({ref:i,className:T,style:O},(0,o.default)(k,["justify","wrap","align"])),v))});e.s(["Flex",0,b],525720)},928685,e=>{"use strict";var t=e.i(38953);e.s(["SearchOutlined",()=>t.default])},466828,e=>{"use strict";var t=e.i(843476),r=e.i(271645),o=e.i(678784);let a=(0,e.i(475254).default)("clipboard",[["rect",{width:"8",height:"4",x:"8",y:"2",rx:"1",ry:"1",key:"tgr4d6"}],["path",{d:"M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2",key:"116196"}]]);var n=e.i(650056);let i={'code[class*="language-"]':{background:"hsl(230, 1%, 98%)",color:"hsl(230, 8%, 24%)",fontFamily:'"Fira Code", "Fira Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace',direction:"ltr",textAlign:"left",whiteSpace:"pre",wordSpacing:"normal",wordBreak:"normal",lineHeight:"1.5",MozTabSize:"2",OTabSize:"2",tabSize:"2",WebkitHyphens:"none",MozHyphens:"none",msHyphens:"none",hyphens:"none"},'pre[class*="language-"]':{background:"hsl(230, 1%, 98%)",color:"hsl(230, 8%, 24%)",fontFamily:'"Fira Code", "Fira Mono", Menlo, Consolas, "DejaVu Sans Mono", monospace',direction:"ltr",textAlign:"left",whiteSpace:"pre",wordSpacing:"normal",wordBreak:"normal",lineHeight:"1.5",MozTabSize:"2",OTabSize:"2",tabSize:"2",WebkitHyphens:"none",MozHyphens:"none",msHyphens:"none",hyphens:"none",padding:"1em",margin:"0.5em 0",overflow:"auto",borderRadius:"0.3em"},'code[class*="language-"]::-moz-selection':{background:"hsl(230, 1%, 90%)",color:"inherit"},'code[class*="language-"] *::-moz-selection':{background:"hsl(230, 1%, 90%)",color:"inherit"},'pre[class*="language-"] *::-moz-selection':{background:"hsl(230, 1%, 90%)",color:"inherit"},'code[class*="language-"]::selection':{background:"hsl(230, 1%, 90%)",color:"inherit"},'code[class*="language-"] *::selection':{background:"hsl(230, 1%, 90%)",color:"inherit"},'pre[class*="language-"] *::selection':{background:"hsl(230, 1%, 90%)",color:"inherit"},':not(pre) > code[class*="language-"]':{padding:"0.2em 0.3em",borderRadius:"0.3em",whiteSpace:"normal"},comment:{color:"hsl(230, 4%, 64%)",fontStyle:"italic"},prolog:{color:"hsl(230, 4%, 64%)"},cdata:{color:"hsl(230, 4%, 64%)"},doctype:{color:"hsl(230, 8%, 24%)"},punctuation:{color:"hsl(230, 8%, 24%)"},entity:{color:"hsl(230, 8%, 24%)",cursor:"help"},"attr-name":{color:"hsl(35, 99%, 36%)"},"class-name":{color:"hsl(35, 99%, 36%)"},boolean:{color:"hsl(35, 99%, 36%)"},constant:{color:"hsl(35, 99%, 36%)"},number:{color:"hsl(35, 99%, 36%)"},atrule:{color:"hsl(35, 99%, 36%)"},keyword:{color:"hsl(301, 63%, 40%)"},property:{color:"hsl(5, 74%, 59%)"},tag:{color:"hsl(5, 74%, 59%)"},symbol:{color:"hsl(5, 74%, 59%)"},deleted:{color:"hsl(5, 74%, 59%)"},important:{color:"hsl(5, 74%, 59%)"},selector:{color:"hsl(119, 34%, 47%)"},string:{color:"hsl(119, 34%, 47%)"},char:{color:"hsl(119, 34%, 47%)"},builtin:{color:"hsl(119, 34%, 47%)"},inserted:{color:"hsl(119, 34%, 47%)"},regex:{color:"hsl(119, 34%, 47%)"},"attr-value":{color:"hsl(119, 34%, 47%)"},"attr-value > .token.punctuation":{color:"hsl(119, 34%, 47%)"},variable:{color:"hsl(221, 87%, 60%)"},operator:{color:"hsl(221, 87%, 60%)"},function:{color:"hsl(221, 87%, 60%)"},url:{color:"hsl(198, 99%, 37%)"},"attr-value > .token.punctuation.attr-equals":{color:"hsl(230, 8%, 24%)"},"special-attr > .token.attr-value > .token.value.css":{color:"hsl(230, 8%, 24%)"},".language-css .token.selector":{color:"hsl(5, 74%, 59%)"},".language-css .token.property":{color:"hsl(230, 8%, 24%)"},".language-css .token.function":{color:"hsl(198, 99%, 37%)"},".language-css .token.url > .token.function":{color:"hsl(198, 99%, 37%)"},".language-css .token.url > .token.string.url":{color:"hsl(119, 34%, 47%)"},".language-css .token.important":{color:"hsl(301, 63%, 40%)"},".language-css .token.atrule .token.rule":{color:"hsl(301, 63%, 40%)"},".language-javascript .token.operator":{color:"hsl(301, 63%, 40%)"},".language-javascript .token.template-string > .token.interpolation > .token.interpolation-punctuation.punctuation":{color:"hsl(344, 84%, 43%)"},".language-json .token.operator":{color:"hsl(230, 8%, 24%)"},".language-json .token.null.keyword":{color:"hsl(35, 99%, 36%)"},".language-markdown .token.url":{color:"hsl(230, 8%, 24%)"},".language-markdown .token.url > .token.operator":{color:"hsl(230, 8%, 24%)"},".language-markdown .token.url-reference.url > .token.string":{color:"hsl(230, 8%, 24%)"},".language-markdown .token.url > .token.content":{color:"hsl(221, 87%, 60%)"},".language-markdown .token.url > .token.url":{color:"hsl(198, 99%, 37%)"},".language-markdown .token.url-reference.url":{color:"hsl(198, 99%, 37%)"},".language-markdown .token.blockquote.punctuation":{color:"hsl(230, 4%, 64%)",fontStyle:"italic"},".language-markdown .token.hr.punctuation":{color:"hsl(230, 4%, 64%)",fontStyle:"italic"},".language-markdown .token.code-snippet":{color:"hsl(119, 34%, 47%)"},".language-markdown .token.bold .token.content":{color:"hsl(35, 99%, 36%)"},".language-markdown .token.italic .token.content":{color:"hsl(301, 63%, 40%)"},".language-markdown .token.strike .token.content":{color:"hsl(5, 74%, 59%)"},".language-markdown .token.strike .token.punctuation":{color:"hsl(5, 74%, 59%)"},".language-markdown .token.list.punctuation":{color:"hsl(5, 74%, 59%)"},".language-markdown .token.title.important > .token.punctuation":{color:"hsl(5, 74%, 59%)"},bold:{fontWeight:"bold"},italic:{fontStyle:"italic"},namespace:{Opacity:"0.8"},"token.tab:not(:empty):before":{color:"hsla(230, 8%, 24%, 0.2)"},"token.cr:before":{color:"hsla(230, 8%, 24%, 0.2)"},"token.lf:before":{color:"hsla(230, 8%, 24%, 0.2)"},"token.space:before":{color:"hsla(230, 8%, 24%, 0.2)"},"div.code-toolbar > .toolbar.toolbar > .toolbar-item":{marginRight:"0.4em"},"div.code-toolbar > .toolbar.toolbar > .toolbar-item > button":{background:"hsl(230, 1%, 90%)",color:"hsl(230, 6%, 44%)",padding:"0.1em 0.4em",borderRadius:"0.3em"},"div.code-toolbar > .toolbar.toolbar > .toolbar-item > a":{background:"hsl(230, 1%, 90%)",color:"hsl(230, 6%, 44%)",padding:"0.1em 0.4em",borderRadius:"0.3em"},"div.code-toolbar > .toolbar.toolbar > .toolbar-item > span":{background:"hsl(230, 1%, 90%)",color:"hsl(230, 6%, 44%)",padding:"0.1em 0.4em",borderRadius:"0.3em"},"div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:hover":{background:"hsl(230, 1%, 78%)",color:"hsl(230, 8%, 24%)"},"div.code-toolbar > .toolbar.toolbar > .toolbar-item > button:focus":{background:"hsl(230, 1%, 78%)",color:"hsl(230, 8%, 24%)"},"div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:hover":{background:"hsl(230, 1%, 78%)",color:"hsl(230, 8%, 24%)"},"div.code-toolbar > .toolbar.toolbar > .toolbar-item > a:focus":{background:"hsl(230, 1%, 78%)",color:"hsl(230, 8%, 24%)"},"div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:hover":{background:"hsl(230, 1%, 78%)",color:"hsl(230, 8%, 24%)"},"div.code-toolbar > .toolbar.toolbar > .toolbar-item > span:focus":{background:"hsl(230, 1%, 78%)",color:"hsl(230, 8%, 24%)"},".line-highlight.line-highlight":{background:"hsla(230, 8%, 24%, 0.05)"},".line-highlight.line-highlight:before":{background:"hsl(230, 1%, 90%)",color:"hsl(230, 8%, 24%)",padding:"0.1em 0.6em",borderRadius:"0.3em",boxShadow:"0 2px 0 0 rgba(0, 0, 0, 0.2)"},".line-highlight.line-highlight[data-end]:after":{background:"hsl(230, 1%, 90%)",color:"hsl(230, 8%, 24%)",padding:"0.1em 0.6em",borderRadius:"0.3em",boxShadow:"0 2px 0 0 rgba(0, 0, 0, 0.2)"},"pre[id].linkable-line-numbers.linkable-line-numbers span.line-numbers-rows > span:hover:before":{backgroundColor:"hsla(230, 8%, 24%, 0.05)"},".line-numbers.line-numbers .line-numbers-rows":{borderRightColor:"hsla(230, 8%, 24%, 0.2)"},".command-line .command-line-prompt":{borderRightColor:"hsla(230, 8%, 24%, 0.2)"},".line-numbers .line-numbers-rows > span:before":{color:"hsl(230, 1%, 62%)"},".command-line .command-line-prompt > span:before":{color:"hsl(230, 1%, 62%)"},".rainbow-braces .token.token.punctuation.brace-level-1":{color:"hsl(5, 74%, 59%)"},".rainbow-braces .token.token.punctuation.brace-level-5":{color:"hsl(5, 74%, 59%)"},".rainbow-braces .token.token.punctuation.brace-level-9":{color:"hsl(5, 74%, 59%)"},".rainbow-braces .token.token.punctuation.brace-level-2":{color:"hsl(119, 34%, 47%)"},".rainbow-braces .token.token.punctuation.brace-level-6":{color:"hsl(119, 34%, 47%)"},".rainbow-braces .token.token.punctuation.brace-level-10":{color:"hsl(119, 34%, 47%)"},".rainbow-braces .token.token.punctuation.brace-level-3":{color:"hsl(221, 87%, 60%)"},".rainbow-braces .token.token.punctuation.brace-level-7":{color:"hsl(221, 87%, 60%)"},".rainbow-braces .token.token.punctuation.brace-level-11":{color:"hsl(221, 87%, 60%)"},".rainbow-braces .token.token.punctuation.brace-level-4":{color:"hsl(301, 63%, 40%)"},".rainbow-braces .token.token.punctuation.brace-level-8":{color:"hsl(301, 63%, 40%)"},".rainbow-braces .token.token.punctuation.brace-level-12":{color:"hsl(301, 63%, 40%)"},"pre.diff-highlight > code .token.token.deleted:not(.prefix)":{backgroundColor:"hsla(353, 100%, 66%, 0.15)"},"pre > code.diff-highlight .token.token.deleted:not(.prefix)":{backgroundColor:"hsla(353, 100%, 66%, 0.15)"},"pre.diff-highlight > code .token.token.deleted:not(.prefix)::-moz-selection":{backgroundColor:"hsla(353, 95%, 66%, 0.25)"},"pre.diff-highlight > code .token.token.deleted:not(.prefix) *::-moz-selection":{backgroundColor:"hsla(353, 95%, 66%, 0.25)"},"pre > code.diff-highlight .token.token.deleted:not(.prefix)::-moz-selection":{backgroundColor:"hsla(353, 95%, 66%, 0.25)"},"pre > code.diff-highlight .token.token.deleted:not(.prefix) *::-moz-selection":{backgroundColor:"hsla(353, 95%, 66%, 0.25)"},"pre.diff-highlight > code .token.token.deleted:not(.prefix)::selection":{backgroundColor:"hsla(353, 95%, 66%, 0.25)"},"pre.diff-highlight > code .token.token.deleted:not(.prefix) *::selection":{backgroundColor:"hsla(353, 95%, 66%, 0.25)"},"pre > code.diff-highlight .token.token.deleted:not(.prefix)::selection":{backgroundColor:"hsla(353, 95%, 66%, 0.25)"},"pre > code.diff-highlight .token.token.deleted:not(.prefix) *::selection":{backgroundColor:"hsla(353, 95%, 66%, 0.25)"},"pre.diff-highlight > code .token.token.inserted:not(.prefix)":{backgroundColor:"hsla(137, 100%, 55%, 0.15)"},"pre > code.diff-highlight .token.token.inserted:not(.prefix)":{backgroundColor:"hsla(137, 100%, 55%, 0.15)"},"pre.diff-highlight > code .token.token.inserted:not(.prefix)::-moz-selection":{backgroundColor:"hsla(135, 73%, 55%, 0.25)"},"pre.diff-highlight > code .token.token.inserted:not(.prefix) *::-moz-selection":{backgroundColor:"hsla(135, 73%, 55%, 0.25)"},"pre > code.diff-highlight .token.token.inserted:not(.prefix)::-moz-selection":{backgroundColor:"hsla(135, 73%, 55%, 0.25)"},"pre > code.diff-highlight .token.token.inserted:not(.prefix) *::-moz-selection":{backgroundColor:"hsla(135, 73%, 55%, 0.25)"},"pre.diff-highlight > code .token.token.inserted:not(.prefix)::selection":{backgroundColor:"hsla(135, 73%, 55%, 0.25)"},"pre.diff-highlight > code .token.token.inserted:not(.prefix) *::selection":{backgroundColor:"hsla(135, 73%, 55%, 0.25)"},"pre > code.diff-highlight .token.token.inserted:not(.prefix)::selection":{backgroundColor:"hsla(135, 73%, 55%, 0.25)"},"pre > code.diff-highlight .token.token.inserted:not(.prefix) *::selection":{backgroundColor:"hsla(135, 73%, 55%, 0.25)"},".prism-previewer.prism-previewer:before":{borderColor:"hsl(0, 0, 95%)"},".prism-previewer-gradient.prism-previewer-gradient div":{borderColor:"hsl(0, 0, 95%)",borderRadius:"0.3em"},".prism-previewer-color.prism-previewer-color:before":{borderRadius:"0.3em"},".prism-previewer-easing.prism-previewer-easing:before":{borderRadius:"0.3em"},".prism-previewer.prism-previewer:after":{borderTopColor:"hsl(0, 0, 95%)"},".prism-previewer-flipped.prism-previewer-flipped.after":{borderBottomColor:"hsl(0, 0, 95%)"},".prism-previewer-angle.prism-previewer-angle:before":{background:"hsl(0, 0%, 100%)"},".prism-previewer-time.prism-previewer-time:before":{background:"hsl(0, 0%, 100%)"},".prism-previewer-easing.prism-previewer-easing":{background:"hsl(0, 0%, 100%)"},".prism-previewer-angle.prism-previewer-angle circle":{stroke:"hsl(230, 8%, 24%)",strokeOpacity:"1"},".prism-previewer-time.prism-previewer-time circle":{stroke:"hsl(230, 8%, 24%)",strokeOpacity:"1"},".prism-previewer-easing.prism-previewer-easing circle":{stroke:"hsl(230, 8%, 24%)",fill:"transparent"},".prism-previewer-easing.prism-previewer-easing path":{stroke:"hsl(230, 8%, 24%)"},".prism-previewer-easing.prism-previewer-easing line":{stroke:"hsl(230, 8%, 24%)"}};e.s(["default",0,({code:e,language:l})=>{let[s,u]=(0,r.useState)(!1);return(0,t.jsxs)("div",{className:"relative rounded-lg border border-gray-200 overflow-hidden",children:[(0,t.jsx)("button",{onClick:()=>{navigator.clipboard.writeText(e),u(!0),setTimeout(()=>u(!1),2e3)},className:"absolute top-3 right-3 p-2 rounded-md bg-gray-100 hover:bg-gray-200 text-gray-600 z-10","aria-label":"Copy code",children:s?(0,t.jsx)(o.CheckIcon,{size:16}):(0,t.jsx)(a,{size:16})}),(0,t.jsx)(n.Prism,{language:l,style:i,customStyle:{margin:0,padding:"1.5rem",borderRadius:"0.5rem",fontSize:"0.9rem",backgroundColor:"#fafafa"},showLineNumbers:!0,children:e})]})}],466828)},972520,e=>{"use strict";let t=(0,e.i(475254).default)("arrow-right",[["path",{d:"M5 12h14",key:"1ays0h"}],["path",{d:"m12 5 7 7-7 7",key:"xquz4c"}]]);e.s(["ArrowRight",0,t],972520)},728889,e=>{"use strict";var t=e.i(290571),r=e.i(271645),o=e.i(829087),a=e.i(480731),n=e.i(444755),i=e.i(673706),l=e.i(95779);let s={xs:{paddingX:"px-1.5",paddingY:"py-1.5"},sm:{paddingX:"px-1.5",paddingY:"py-1.5"},md:{paddingX:"px-2",paddingY:"py-2"},lg:{paddingX:"px-2",paddingY:"py-2"},xl:{paddingX:"px-2.5",paddingY:"py-2.5"}},u={xs:{height:"h-3",width:"w-3"},sm:{height:"h-5",width:"w-5"},md:{height:"h-5",width:"w-5"},lg:{height:"h-7",width:"w-7"},xl:{height:"h-9",width:"w-9"}},c={simple:{rounded:"",border:"",ring:"",shadow:""},light:{rounded:"rounded-tremor-default",border:"",ring:"",shadow:""},shadow:{rounded:"rounded-tremor-default",border:"border",ring:"",shadow:"shadow-tremor-card dark:shadow-dark-tremor-card"},solid:{rounded:"rounded-tremor-default",border:"border-2",ring:"ring-1",shadow:""},outlined:{rounded:"rounded-tremor-default",border:"border",ring:"ring-2",shadow:""}},d=(0,i.makeClassName)("Icon"),h=r.default.forwardRef((e,h)=>{let{icon:g,variant:b="simple",tooltip:f,size:p=a.Sizes.SM,color:m,className:v}=e,k=(0,t.__rest)(e,["icon","variant","tooltip","size","color","className"]),w=((e,t)=>{switch(e){case"simple":return{textColor:t?(0,i.getColorClassNames)(t,l.colorPalette.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:"",borderColor:"",ringColor:""};case"light":return{textColor:t?(0,i.getColorClassNames)(t,l.colorPalette.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,n.tremorTwMerge)((0,i.getColorClassNames)(t,l.colorPalette.background).bgColor,"bg-opacity-20"):"bg-tremor-brand-muted dark:bg-dark-tremor-brand-muted",borderColor:"",ringColor:""};case"shadow":return{textColor:t?(0,i.getColorClassNames)(t,l.colorPalette.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,n.tremorTwMerge)((0,i.getColorClassNames)(t,l.colorPalette.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:"border-tremor-border dark:border-dark-tremor-border",ringColor:""};case"solid":return{textColor:t?(0,i.getColorClassNames)(t,l.colorPalette.text).textColor:"text-tremor-brand-inverted dark:text-dark-tremor-brand-inverted",bgColor:t?(0,n.tremorTwMerge)((0,i.getColorClassNames)(t,l.colorPalette.background).bgColor,"bg-opacity-20"):"bg-tremor-brand dark:bg-dark-tremor-brand",borderColor:"border-tremor-brand-inverted dark:border-dark-tremor-brand-inverted",ringColor:"ring-tremor-ring dark:ring-dark-tremor-ring"};case"outlined":return{textColor:t?(0,i.getColorClassNames)(t,l.colorPalette.text).textColor:"text-tremor-brand dark:text-dark-tremor-brand",bgColor:t?(0,n.tremorTwMerge)((0,i.getColorClassNames)(t,l.colorPalette.background).bgColor,"bg-opacity-20"):"bg-tremor-background dark:bg-dark-tremor-background",borderColor:t?(0,i.getColorClassNames)(t,l.colorPalette.ring).borderColor:"border-tremor-brand-subtle dark:border-dark-tremor-brand-subtle",ringColor:t?(0,n.tremorTwMerge)((0,i.getColorClassNames)(t,l.colorPalette.ring).ringColor,"ring-opacity-40"):"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"}}})(b,m),{tooltipProps:C,getReferenceProps:x}=(0,o.useTooltip)();return r.default.createElement("span",Object.assign({ref:(0,i.mergeRefs)([h,C.refs.setReference]),className:(0,n.tremorTwMerge)(d("root"),"inline-flex shrink-0 items-center justify-center",w.bgColor,w.textColor,w.borderColor,w.ringColor,c[b].rounded,c[b].border,c[b].shadow,c[b].ring,s[p].paddingX,s[p].paddingY,v)},x,k),r.default.createElement(o.default,Object.assign({text:f},C)),r.default.createElement(g,{className:(0,n.tremorTwMerge)(d("icon"),"shrink-0",u[p].height,u[p].width)}))});h.displayName="Icon",e.s(["default",0,h],728889)},752978,e=>{"use strict";var t=e.i(728889);e.s(["Icon",()=>t.default])},591935,e=>{"use strict";var t=e.i(271645);let r=t.forwardRef(function(e,r){return t.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),t.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"}))});e.s(["PencilAltIcon",0,r],591935)},122577,e=>{"use strict";var t=e.i(271645);let r=t.forwardRef(function(e,r){return t.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",fill:"none",viewBox:"0 0 24 24",strokeWidth:2,stroke:"currentColor","aria-hidden":"true",ref:r},e),t.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"}),t.createElement("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M21 12a9 9 0 11-18 0 9 9 0 0118 0z"}))});e.s(["PlayIcon",0,r],122577)},91979,e=>{"use strict";e.i(247167);var t=e.i(931067),r=e.i(271645);let o={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M909.1 209.3l-56.4 44.1C775.8 155.1 656.2 92 521.9 92 290 92 102.3 279.5 102 511.5 101.7 743.7 289.8 932 521.9 932c181.3 0 335.8-115 394.6-276.1 1.5-4.2-.7-8.9-4.9-10.3l-56.7-19.5a8 8 0 00-10.1 4.8c-1.8 5-3.8 10-5.9 14.9-17.3 41-42.1 77.8-73.7 109.4A344.77 344.77 0 01655.9 829c-42.3 17.9-87.4 27-133.8 27-46.5 0-91.5-9.1-133.8-27A341.5 341.5 0 01279 755.2a342.16 342.16 0 01-73.7-109.4c-17.9-42.4-27-87.4-27-133.9s9.1-91.5 27-133.9c17.3-41 42.1-77.8 73.7-109.4 31.6-31.6 68.4-56.4 109.3-73.8 42.3-17.9 87.4-27 133.8-27 46.5 0 91.5 9.1 133.8 27a341.5 341.5 0 01109.3 73.8c9.9 9.9 19.2 20.4 27.8 31.4l-60.2 47a8 8 0 003 14.1l175.6 43c5 1.2 9.9-2.6 9.9-7.7l.8-180.9c-.1-6.6-7.8-10.3-13-6.2z"}}]},name:"reload",theme:"outlined"};var a=e.i(9583),n=r.forwardRef(function(e,n){return r.createElement(a.default,(0,t.default)({},e,{ref:n,icon:o}))});e.s(["ReloadOutlined",0,n],91979)},888288,e=>{"use strict";var t=e.i(271645);e.s(["default",0,(e,r)=>{let o=void 0!==r,[a,n]=(0,t.useState)(e);return[o?r:a,e=>{o||n(e)}]}])},793130,e=>{"use strict";var t=e.i(290571),r=e.i(783222),o=e.i(433336),a=e.i(271645),n=e.i(394487),i=e.i(503269),l=e.i(214520),s=e.i(746725),u=e.i(914189),c=e.i(144279),d=e.i(294316),h=e.i(601893),g=e.i(140721),b=e.i(942803),f=e.i(233538),p=e.i(694421),m=e.i(700020),v=e.i(35889),k=e.i(998348),w=e.i(722678);let C=(0,a.createContext)(null);C.displayName="GroupContext";let x=a.Fragment,y=Object.assign((0,m.forwardRefWithAs)(function(e,t){var x;let y=(0,a.useId)(),R=(0,b.useProvidedId)(),S=(0,h.useDisabled)(),{id:E=R||`headlessui-switch-${y}`,disabled:M=S||!1,checked:T,defaultChecked:O,onChange:N,name:P,value:I,form:L,autoFocus:j=!1,...z}=e,A=(0,a.useContext)(C),[D,_]=(0,a.useState)(null),H=(0,a.useRef)(null),W=(0,d.useSyncRefs)(H,t,null===A?null:A.setSwitch,_),F=(0,l.useDefaultValue)(O),[K,B]=(0,i.useControllable)(T,N,null!=F&&F),$=(0,s.useDisposables)(),[Y,V]=(0,a.useState)(!1),G=(0,u.useEvent)(()=>{V(!0),null==B||B(!K),$.nextFrame(()=>{V(!1)})}),q=(0,u.useEvent)(e=>{if((0,f.isDisabledReactIssue7711)(e.currentTarget))return e.preventDefault();e.preventDefault(),G()}),U=(0,u.useEvent)(e=>{e.key===k.Keys.Space?(e.preventDefault(),G()):e.key===k.Keys.Enter&&(0,p.attemptSubmit)(e.currentTarget)}),X=(0,u.useEvent)(e=>e.preventDefault()),Q=(0,w.useLabelledBy)(),Z=(0,v.useDescribedBy)(),{isFocusVisible:J,focusProps:ee}=(0,r.useFocusRing)({autoFocus:j}),{isHovered:et,hoverProps:er}=(0,o.useHover)({isDisabled:M}),{pressed:eo,pressProps:ea}=(0,n.useActivePress)({disabled:M}),en=(0,a.useMemo)(()=>({checked:K,disabled:M,hover:et,focus:J,active:eo,autofocus:j,changing:Y}),[K,et,J,eo,M,Y,j]),ei=(0,m.mergeProps)({id:E,ref:W,role:"switch",type:(0,c.useResolveButtonType)(e,D),tabIndex:-1===e.tabIndex?0:null!=(x=e.tabIndex)?x:0,"aria-checked":K,"aria-labelledby":Q,"aria-describedby":Z,disabled:M||void 0,autoFocus:j,onClick:q,onKeyUp:U,onKeyPress:X},ee,er,ea),el=(0,a.useCallback)(()=>{if(void 0!==F)return null==B?void 0:B(F)},[B,F]),es=(0,m.useRender)();return a.default.createElement(a.default.Fragment,null,null!=P&&a.default.createElement(g.FormFields,{disabled:M,data:{[P]:I||"on"},overrides:{type:"checkbox",checked:K},form:L,onReset:el}),es({ourProps:ei,theirProps:z,slot:en,defaultTag:"button",name:"Switch"}))}),{Group:function(e){var t;let[r,o]=(0,a.useState)(null),[n,i]=(0,w.useLabels)(),[l,s]=(0,v.useDescriptions)(),u=(0,a.useMemo)(()=>({switch:r,setSwitch:o}),[r,o]),c=(0,m.useRender)();return a.default.createElement(s,{name:"Switch.Description",value:l},a.default.createElement(i,{name:"Switch.Label",value:n,props:{htmlFor:null==(t=u.switch)?void 0:t.id,onClick(e){r&&(e.currentTarget instanceof HTMLLabelElement&&e.preventDefault(),r.click(),r.focus({preventScroll:!0}))}}},a.default.createElement(C.Provider,{value:u},c({ourProps:{},theirProps:e,slot:{},defaultTag:x,name:"Switch.Group"}))))},Label:w.Label,Description:v.Description});var R=e.i(888288),S=e.i(95779),E=e.i(444755),M=e.i(673706),T=e.i(829087);let O=(0,M.makeClassName)("Switch"),N=a.default.forwardRef((e,r)=>{let{checked:o,defaultChecked:n=!1,onChange:i,color:l,name:s,error:u,errorMessage:c,disabled:d,required:h,tooltip:g,id:b}=e,f=(0,t.__rest)(e,["checked","defaultChecked","onChange","color","name","error","errorMessage","disabled","required","tooltip","id"]),p={bgColor:l?(0,M.getColorClassNames)(l,S.colorPalette.background).bgColor:"bg-tremor-brand dark:bg-dark-tremor-brand",ringColor:l?(0,M.getColorClassNames)(l,S.colorPalette.ring).ringColor:"ring-tremor-brand-muted dark:ring-dark-tremor-brand-muted"},[m,v]=(0,R.default)(n,o),[k,w]=(0,a.useState)(!1),{tooltipProps:C,getReferenceProps:x}=(0,T.useTooltip)(300);return a.default.createElement("div",{className:"flex flex-row items-center justify-start"},a.default.createElement(T.default,Object.assign({text:g},C)),a.default.createElement("div",Object.assign({ref:(0,M.mergeRefs)([r,C.refs.setReference]),className:(0,E.tremorTwMerge)(O("root"),"flex flex-row relative h-5")},f,x),a.default.createElement("input",{type:"checkbox",className:(0,E.tremorTwMerge)(O("input"),"absolute w-5 h-5 cursor-pointer left-0 top-0 opacity-0"),name:s,required:h,checked:m,onChange:e=>{e.preventDefault()}}),a.default.createElement(y,{checked:m,onChange:e=>{v(e),null==i||i(e)},disabled:d,className:(0,E.tremorTwMerge)(O("switch"),"w-10 h-5 group relative inline-flex shrink-0 cursor-pointer items-center justify-center rounded-tremor-full","focus:outline-none",d?"cursor-not-allowed":""),onFocus:()=>w(!0),onBlur:()=>w(!1),id:b},a.default.createElement("span",{className:(0,E.tremorTwMerge)(O("sr-only"),"sr-only")},"Switch ",m?"on":"off"),a.default.createElement("span",{"aria-hidden":"true",className:(0,E.tremorTwMerge)(O("background"),m?p.bgColor:"bg-tremor-border dark:bg-dark-tremor-border","pointer-events-none absolute mx-auto h-3 w-9 rounded-tremor-full transition-colors duration-100 ease-in-out")}),a.default.createElement("span",{"aria-hidden":"true",className:(0,E.tremorTwMerge)(O("round"),m?(0,E.tremorTwMerge)(p.bgColor,"translate-x-5 border-tremor-background dark:border-dark-tremor-background"):"translate-x-0 bg-tremor-border dark:bg-dark-tremor-border border-tremor-background dark:border-dark-tremor-background","pointer-events-none absolute left-0 inline-block h-5 w-5 transform rounded-tremor-full border-2 shadow-tremor-input duration-100 ease-in-out transition",k?(0,E.tremorTwMerge)("ring-2",p.ringColor):"")}))),u&&c?a.default.createElement("p",{className:(0,E.tremorTwMerge)(O("errorMessage"),"text-sm text-red-500 mt-1 ")},c):null)});N.displayName="Switch",e.s(["Switch",0,N],793130)},418371,e=>{"use strict";var t=e.i(843476),r=e.i(174553);e.s(["ProviderLogo",0,({provider:e,className:o="w-4 h-4"})=>(0,t.jsx)(r.Logo,{provider:e,className:o})])},368670,e=>{"use strict";var t=e.i(602869),r=e.i(266027);let o=(0,e.i(243652).createQueryKeys)("modelCostMap");e.s(["useModelCostMap",0,()=>(0,r.useQuery)({queryKey:o.list({}),queryFn:async()=>await (0,t.modelCostMap)(),staleTime:6e4,gcTime:6e4})])},389543,e=>{"use strict";var t=e.i(843476),r=e.i(863679),o=e.i(135214);e.s(["default",0,function(){let{accessToken:e,userRole:a,userId:n}=(0,o.default)();return(0,t.jsx)(r.default,{userID:n,userRole:a,accessToken:e})}])}]); \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/_next/static/chunks/08goggic_ad66.js b/litellm/proxy/_experimental/out/_next/static/chunks/08goggic_ad66.js new file mode 100644 index 00000000000..e15232235db --- /dev/null +++ b/litellm/proxy/_experimental/out/_next/static/chunks/08goggic_ad66.js @@ -0,0 +1,2 @@ +(globalThis.TURBOPACK||(globalThis.TURBOPACK=[])).push(["object"==typeof document?document.currentScript:void 0,233538,e=>{"use strict";e.s(["isDisabledReactIssue7711",0,function(e){let t=e.parentElement,n=null;for(;t&&!(t instanceof HTMLFieldSetElement);)t instanceof HTMLLegendElement&&(n=t),t=t.parentElement;let i=(null==t?void 0:t.getAttribute("disabled"))==="";return!(i&&function(e){if(!e)return!1;let t=e.previousElementSibling;for(;null!==t;){if(t instanceof HTMLLegendElement)return!1;t=t.previousElementSibling}return!0}(n))&&i}])},83733,233137,e=>{"use strict";let t,n;var i,s,r=e.i(247167),a=e.i(271645),l=e.i(544508),o=e.i(746725),d=e.i(835696);void 0!==r.default&&"u">typeof globalThis&&"u">typeof Element&&(null==(i=null==r.default?void 0:r.default.env)?void 0:i.NODE_ENV)==="test"&&void 0===(null==(s=null==Element?void 0:Element.prototype)?void 0:s.getAnimations)&&(Element.prototype.getAnimations=function(){return console.warn(["Headless UI has polyfilled `Element.prototype.getAnimations` for your tests.","Please install a proper polyfill e.g. `jsdom-testing-mocks`, to silence these warnings.","","Example usage:","```js","import { mockAnimationsApi } from 'jsdom-testing-mocks'","mockAnimationsApi()","```"].join(` +`)),[]});var u=((t=u||{})[t.None=0]="None",t[t.Closed=1]="Closed",t[t.Enter=2]="Enter",t[t.Leave=4]="Leave",t);e.s(["transitionDataAttributes",0,function(e){let t={};for(let n in e)!0===e[n]&&(t[`data-${n}`]="");return t},"useTransition",0,function(e,t,n,i){let[s,r]=(0,a.useState)(n),{hasFlag:u,addFlag:c,removeFlag:h}=function(e=0){let[t,n]=(0,a.useState)(e),i=(0,a.useCallback)(e=>n(e),[t]),s=(0,a.useCallback)(e=>n(t=>t|e),[t]),r=(0,a.useCallback)(e=>(t&e)===e,[t]);return{flags:t,setFlag:i,addFlag:s,hasFlag:r,removeFlag:(0,a.useCallback)(e=>n(t=>t&~e),[n]),toggleFlag:(0,a.useCallback)(e=>n(t=>t^e),[n])}}(e&&s?3:0),m=(0,a.useRef)(!1),f=(0,a.useRef)(!1),p=(0,o.useDisposables)();return(0,d.useIsoMorphicEffect)(()=>{var s;if(e){if(n&&r(!0),!t){n&&c(3);return}return null==(s=null==i?void 0:i.start)||s.call(i,n),function(e,{prepare:t,run:n,done:i,inFlight:s}){let r=(0,l.disposables)();return function(e,{inFlight:t,prepare:n}){if(null!=t&&t.current)return n();let i=e.style.transition;e.style.transition="none",n(),e.offsetHeight,e.style.transition=i}(e,{prepare:t,inFlight:s}),r.nextFrame(()=>{n(),r.requestAnimationFrame(()=>{r.add(function(e,t){var n,i;let s=(0,l.disposables)();if(!e)return s.dispose;let r=!1;s.add(()=>{r=!0});let a=null!=(i=null==(n=e.getAnimations)?void 0:n.call(e).filter(e=>e instanceof CSSTransition))?i:[];return 0===a.length?t():Promise.allSettled(a.map(e=>e.finished)).then(()=>{r||t()}),s.dispose}(e,i))})}),r.dispose}(t,{inFlight:m,prepare(){f.current?f.current=!1:f.current=m.current,m.current=!0,f.current||(n?(c(3),h(4)):(c(4),h(2)))},run(){f.current?n?(h(3),c(4)):(h(4),c(3)):n?h(1):c(1)},done(){var e;f.current&&"function"==typeof t.getAnimations&&t.getAnimations().length>0||(m.current=!1,h(7),n||r(!1),null==(e=null==i?void 0:i.end)||e.call(i,n))}})}},[e,n,t,p]),e?[s,{closed:u(1),enter:u(2),leave:u(4),transition:u(2)||u(4)}]:[n,{closed:void 0,enter:void 0,leave:void 0,transition:void 0}]}],83733);let c=(0,a.createContext)(null);c.displayName="OpenClosedContext";var h=((n=h||{})[n.Open=1]="Open",n[n.Closed=2]="Closed",n[n.Closing=4]="Closing",n[n.Opening=8]="Opening",n);e.s(["OpenClosedProvider",0,function({value:e,children:t}){return a.default.createElement(c.Provider,{value:e},t)},"ResetOpenClosedProvider",0,function({children:e}){return a.default.createElement(c.Provider,{value:null},e)},"State",0,h,"useOpenClosed",0,function(){return(0,a.useContext)(c)}],233137)},677667,674175,886148,543086,e=>{"use strict";let t,n;var i,s=e.i(290571),r=e.i(783222),a=e.i(433336),l=e.i(271645),o=e.i(394487),d=e.i(914189),u=e.i(144279),c=e.i(294316),h=e.i(83733);let m=(0,l.createContext)(()=>{});function f({value:e,children:t}){return l.default.createElement(m.Provider,{value:e},t)}e.s(["CloseProvider",0,f],674175);var p=e.i(233137),g=e.i(233538),v=e.i(397701),x=e.i(402155),b=e.i(700020);let y=null!=(i=l.default.startTransition)?i:function(e){e()};var _=e.i(998348),j=((t=j||{})[t.Open=0]="Open",t[t.Closed=1]="Closed",t),E=((n=E||{})[n.ToggleDisclosure=0]="ToggleDisclosure",n[n.CloseDisclosure=1]="CloseDisclosure",n[n.SetButtonId=2]="SetButtonId",n[n.SetPanelId=3]="SetPanelId",n[n.SetButtonElement=4]="SetButtonElement",n[n.SetPanelElement=5]="SetPanelElement",n);let w={0:e=>({...e,disclosureState:(0,v.match)(e.disclosureState,{0:1,1:0})}),1:e=>1===e.disclosureState?e:{...e,disclosureState:1},2:(e,t)=>e.buttonId===t.buttonId?e:{...e,buttonId:t.buttonId},3:(e,t)=>e.panelId===t.panelId?e:{...e,panelId:t.panelId},4:(e,t)=>e.buttonElement===t.element?e:{...e,buttonElement:t.element},5:(e,t)=>e.panelElement===t.element?e:{...e,panelElement:t.element}},C=(0,l.createContext)(null);function k(e){let t=(0,l.useContext)(C);if(null===t){let t=Error(`<${e} /> is missing a parent component.`);throw Error.captureStackTrace&&Error.captureStackTrace(t,k),t}return t}C.displayName="DisclosureContext";let S=(0,l.createContext)(null);S.displayName="DisclosureAPIContext";let T=(0,l.createContext)(null);function N(e,t){return(0,v.match)(t.type,w,e,t)}T.displayName="DisclosurePanelContext";let O=l.Fragment,I=b.RenderFeatures.RenderStrategy|b.RenderFeatures.Static,R=Object.assign((0,b.forwardRefWithAs)(function(e,t){let{defaultOpen:n=!1,...i}=e,s=(0,l.useRef)(null),r=(0,c.useSyncRefs)(t,(0,c.optionalRef)(e=>{s.current=e},void 0===e.as||e.as===l.Fragment)),a=(0,l.useReducer)(N,{disclosureState:+!n,buttonElement:null,panelElement:null,buttonId:null,panelId:null}),[{disclosureState:o,buttonId:u},h]=a,m=(0,d.useEvent)(e=>{h({type:1});let t=(0,x.getOwnerDocument)(s);if(!t||!u)return;let n=e?e instanceof HTMLElement?e:e.current instanceof HTMLElement?e.current:t.getElementById(u):t.getElementById(u);null==n||n.focus()}),g=(0,l.useMemo)(()=>({close:m}),[m]),y=(0,l.useMemo)(()=>({open:0===o,close:m}),[o,m]),_=(0,b.useRender)();return l.default.createElement(C.Provider,{value:a},l.default.createElement(S.Provider,{value:g},l.default.createElement(f,{value:m},l.default.createElement(p.OpenClosedProvider,{value:(0,v.match)(o,{0:p.State.Open,1:p.State.Closed})},_({ourProps:{ref:r},theirProps:i,slot:y,defaultTag:O,name:"Disclosure"})))))}),{Button:(0,b.forwardRefWithAs)(function(e,t){let n=(0,l.useId)(),{id:i=`headlessui-disclosure-button-${n}`,disabled:s=!1,autoFocus:h=!1,...m}=e,[f,p]=k("Disclosure.Button"),v=(0,l.useContext)(T),x=null!==v&&v===f.panelId,y=(0,l.useRef)(null),j=(0,c.useSyncRefs)(y,t,(0,d.useEvent)(e=>{if(!x)return p({type:4,element:e})}));(0,l.useEffect)(()=>{if(!x)return p({type:2,buttonId:i}),()=>{p({type:2,buttonId:null})}},[i,p,x]);let E=(0,d.useEvent)(e=>{var t;if(x){if(1===f.disclosureState)return;switch(e.key){case _.Keys.Space:case _.Keys.Enter:e.preventDefault(),e.stopPropagation(),p({type:0}),null==(t=f.buttonElement)||t.focus()}}else switch(e.key){case _.Keys.Space:case _.Keys.Enter:e.preventDefault(),e.stopPropagation(),p({type:0})}}),w=(0,d.useEvent)(e=>{e.key===_.Keys.Space&&e.preventDefault()}),C=(0,d.useEvent)(e=>{var t;(0,g.isDisabledReactIssue7711)(e.currentTarget)||s||(x?(p({type:0}),null==(t=f.buttonElement)||t.focus()):p({type:0}))}),{isFocusVisible:S,focusProps:N}=(0,r.useFocusRing)({autoFocus:h}),{isHovered:O,hoverProps:I}=(0,a.useHover)({isDisabled:s}),{pressed:R,pressProps:P}=(0,o.useActivePress)({disabled:s}),L=(0,l.useMemo)(()=>({open:0===f.disclosureState,hover:O,active:R,disabled:s,focus:S,autofocus:h}),[f,O,R,S,s,h]),D=(0,u.useResolveButtonType)(e,f.buttonElement),A=x?(0,b.mergeProps)({ref:j,type:D,disabled:s||void 0,autoFocus:h,onKeyDown:E,onClick:C},N,I,P):(0,b.mergeProps)({ref:j,id:i,type:D,"aria-expanded":0===f.disclosureState,"aria-controls":f.panelElement?f.panelId:void 0,disabled:s||void 0,autoFocus:h,onKeyDown:E,onKeyUp:w,onClick:C},N,I,P);return(0,b.useRender)()({ourProps:A,theirProps:m,slot:L,defaultTag:"button",name:"Disclosure.Button"})}),Panel:(0,b.forwardRefWithAs)(function(e,t){let n=(0,l.useId)(),{id:i=`headlessui-disclosure-panel-${n}`,transition:s=!1,...r}=e,[a,o]=k("Disclosure.Panel"),{close:u}=function e(t){let n=(0,l.useContext)(S);if(null===n){let n=Error(`<${t} /> is missing a parent component.`);throw Error.captureStackTrace&&Error.captureStackTrace(n,e),n}return n}("Disclosure.Panel"),[m,f]=(0,l.useState)(null),g=(0,c.useSyncRefs)(t,(0,d.useEvent)(e=>{y(()=>o({type:5,element:e}))}),f);(0,l.useEffect)(()=>(o({type:3,panelId:i}),()=>{o({type:3,panelId:null})}),[i,o]);let v=(0,p.useOpenClosed)(),[x,_]=(0,h.useTransition)(s,m,null!==v?(v&p.State.Open)===p.State.Open:0===a.disclosureState),j=(0,l.useMemo)(()=>({open:0===a.disclosureState,close:u}),[a.disclosureState,u]),E={ref:g,id:i,...(0,h.transitionDataAttributes)(_)},w=(0,b.useRender)();return l.default.createElement(p.ResetOpenClosedProvider,null,l.default.createElement(T.Provider,{value:a.panelId},w({ourProps:E,theirProps:r,slot:j,defaultTag:"div",features:I,visible:x,name:"Disclosure.Panel"})))})});e.s(["Disclosure",0,R],886148);let P=(0,l.createContext)(void 0);var L=e.i(444755);let D=(0,e.i(673706).makeClassName)("Accordion"),A=(0,l.createContext)({isOpen:!1}),F=l.default.forwardRef((e,t)=>{var n;let{defaultOpen:i=!1,children:r,className:a}=e,o=(0,s.__rest)(e,["defaultOpen","children","className"]),d=null!=(n=(0,l.useContext)(P))?n:(0,L.tremorTwMerge)("rounded-tremor-default border");return l.default.createElement(R,Object.assign({as:"div",ref:t,className:(0,L.tremorTwMerge)(D("root"),"overflow-hidden","bg-tremor-background border-tremor-border","dark:bg-dark-tremor-background dark:border-dark-tremor-border",d,a),defaultOpen:i},o),({open:e})=>l.default.createElement(A.Provider,{value:{isOpen:e}},r))});F.displayName="Accordion",e.s(["OpenContext",0,A,"default",0,F],543086),e.s(["Accordion",0,F],677667)},130643,e=>{"use strict";var t=e.i(290571),n=e.i(271645),i=e.i(886148),s=e.i(444755);let r=(0,e.i(673706).makeClassName)("AccordionBody"),a=n.default.forwardRef((e,a)=>{let{children:l,className:o}=e,d=(0,t.__rest)(e,["children","className"]);return n.default.createElement(i.Disclosure.Panel,Object.assign({ref:a,className:(0,s.tremorTwMerge)(r("root"),"w-full text-tremor-default px-4 pb-3","text-tremor-content","dark:text-dark-tremor-content",o)},d),l)});a.displayName="AccordionBody",e.s(["AccordionBody",0,a],130643)},898667,e=>{"use strict";var t=e.i(290571),n=e.i(271645),i=e.i(886148);let s=e=>{var i=(0,t.__rest)(e,[]);return n.default.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor"},i),n.default.createElement("path",{d:"M11.9999 10.8284L7.0502 15.7782L5.63599 14.364L11.9999 8L18.3639 14.364L16.9497 15.7782L11.9999 10.8284Z"}))};var r=e.i(543086),a=e.i(444755);let l=(0,e.i(673706).makeClassName)("AccordionHeader"),o=n.default.forwardRef((e,o)=>{let{children:d,className:u}=e,c=(0,t.__rest)(e,["children","className"]),{isOpen:h}=(0,n.useContext)(r.OpenContext);return n.default.createElement(i.Disclosure.Button,Object.assign({ref:o,className:(0,a.tremorTwMerge)(l("root"),"w-full flex items-center justify-between px-4 py-3","text-tremor-content-emphasis","dark:text-dark-tremor-content-emphasis",u)},c),n.default.createElement("div",{className:(0,a.tremorTwMerge)(l("children"),"flex flex-1 text-inherit mr-4")},d),n.default.createElement("div",null,n.default.createElement(s,{className:(0,a.tremorTwMerge)(l("arrowIcon"),"h-5 w-5 -mr-1","text-tremor-content-subtle","dark:text-dark-tremor-content-subtle",h?"transition-all":"transition-all -rotate-180")})))});o.displayName="AccordionHeader",e.s(["AccordionHeader",0,o],898667)},743151,(e,t,n)=>{"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.CopyToClipboard=void 0;var i=a(e.r(844343)),s=a(e.r(271645)),r=["text","onCopy","options","children"];function a(e){return e&&e.__esModule?e:{default:e}}function l(e){return(l="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function o(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);t&&(i=i.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,i)}return n}function d(e){for(var t=1;t{"use strict";var i=e.r(743151).CopyToClipboard;i.CopyToClipboard=i,t.exports=i},285027,e=>{"use strict";e.i(247167);var t=e.i(931067),n=e.i(271645);let i={icon:{tag:"svg",attrs:{viewBox:"64 64 896 896",focusable:"false"},children:[{tag:"path",attrs:{d:"M464 720a48 48 0 1096 0 48 48 0 10-96 0zm16-304v184c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V416c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8zm475.7 440l-416-720c-6.2-10.7-16.9-16-27.7-16s-21.6 5.3-27.7 16l-416 720C56 877.4 71.4 904 96 904h832c24.6 0 40-26.6 27.7-48zm-783.5-27.9L512 239.9l339.8 588.2H172.2z"}}]},name:"warning",theme:"outlined"};var s=e.i(9583),r=n.forwardRef(function(e,r){return n.createElement(s.default,(0,t.default)({},e,{ref:r,icon:i}))});e.s(["WarningOutlined",0,r],285027)},540626,e=>{"use strict";let t;var n,i=e.i(271645);let s=(0,i.createContext)(null);function r(e,t){if(Object.is(e,t))return!0;if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;if(e instanceof Map&&t instanceof Map){if(e.size!==t.size)return!1;for(let[n,i]of e)if(!t.has(n)||!Object.is(i,t.get(n)))return!1;return!0}if(e instanceof Set&&t instanceof Set){if(e.size!==t.size)return!1;for(let n of e)if(!t.has(n))return!1;return!0}if(e instanceof Date&&t instanceof Date)return e.getTime()===t.getTime();let n=a(e);if(n.length!==a(t).length)return!1;for(let i=0;ie,n){let s=n?.compare??o,r=(0,i.useCallback)(t=>{let{unsubscribe:n}=e.subscribe(t);return n},[e]),a=(0,i.useCallback)(()=>e.get(),[e]);return(0,l.useSyncExternalStoreWithSelector)(r,a,a,t,s)}function u(e,...t){return"function"==typeof e?e(...t):e}var c=class{#e=!0;#t;#n;#i;#s;#r;#a;#l;#o=0;#d=5;#u=!1;#c=!1;#h=null;#m=()=>{this.debugLog("Connected to event bus"),this.#r=!0,this.#u=!1,this.debugLog("Emitting queued events",this.#s),this.#s.forEach(e=>this.emitEventToBus(e)),this.#s=[],this.stopConnectLoop(),this.#n().removeEventListener("tanstack-connect-success",this.#m)};#f=()=>{if(this.#o{this.#u||(this.#u=!0,this.#n().addEventListener("tanstack-connect-success",this.#m),this.#f())};constructor({pluginId:e,debug:t=!1,enabled:n=!0,reconnectEveryMs:i=300}){this.#t=e,this.#e=n,this.#n=this.getGlobalTarget,this.#i=t,this.debugLog(" Initializing event subscription for plugin",this.#t),this.#s=[],this.#r=!1,this.#c=!1,this.#a=null,this.#l=i}startConnectLoop(){null!==this.#a||this.#r||(this.debugLog(`Starting connect loop (every ${this.#l}ms)`),this.#a=setInterval(this.#f,this.#l))}stopConnectLoop(){this.#u=!1,null!==this.#a&&(clearInterval(this.#a),this.#a=null,this.#s=[],this.debugLog("Stopped connect loop"))}debugLog(...e){this.#i&&console.log(`🌴 [tanstack-devtools:${this.#t}-plugin]`,...e)}getGlobalTarget(){if("u">typeof globalThis&&globalThis.__TANSTACK_EVENT_TARGET__)return this.debugLog("Using global event target"),globalThis.__TANSTACK_EVENT_TARGET__;if("u">typeof window&&void 0!==window.addEventListener)return this.debugLog("Using window as event target"),window;let e="u">typeof EventTarget?new EventTarget:void 0;return void 0===e||void 0===e.addEventListener?(this.debugLog("No event mechanism available, running in non-web environment"),{addEventListener:()=>{},removeEventListener:()=>{},dispatchEvent:()=>!1}):(this.debugLog("Using new EventTarget as fallback"),e)}getPluginId(){return this.#t}dispatchCustomEventShim(e,t){try{let n=new Event(e,{detail:t});this.#n().dispatchEvent(n)}catch(e){this.debugLog("Failed to dispatch shim event")}}dispatchCustomEvent(e,t){try{this.#n().dispatchEvent(new CustomEvent(e,{detail:t}))}catch(n){this.dispatchCustomEventShim(e,t)}}emitEventToBus(e){this.debugLog("Emitting event to client bus",e),this.dispatchCustomEvent("tanstack-dispatch-event",e)}createEventPayload(e,t){return{type:`${this.#t}:${e}`,payload:t,pluginId:this.#t}}emit(e,t){if(!this.#e)return void this.debugLog("Event bus client is disabled, not emitting event",e,t);if(this.#h&&(this.debugLog("Emitting event to internal event target",e,t),this.#h.dispatchEvent(new CustomEvent(`${this.#t}:${e}`,{detail:this.createEventPayload(e,t)}))),this.#c)return void this.debugLog("Previously failed to connect, not emitting to bus");if(!this.#r){this.debugLog("Bus not available, will be pushed as soon as connected"),this.#s.push(this.createEventPayload(e,t)),"u">typeof CustomEvent&&!this.#u&&(this.#p(),this.startConnectLoop());return}return this.emitEventToBus(this.createEventPayload(e,t))}on(e,t,n){let i=n?.withEventTarget??!1,s=`${this.#t}:${e}`;if(i&&(this.#h||(this.#h=new EventTarget),this.#h.addEventListener(s,e=>{t(e.detail)})),!this.#e)return this.debugLog("Event bus client is disabled, not registering event",s),()=>{};let r=e=>{this.debugLog("Received event from bus",e.detail),t(e.detail)};return this.#n().addEventListener(s,r),this.debugLog("Registered event to bus",s),()=>{i&&this.#h?.removeEventListener(s,r),this.#n().removeEventListener(s,r)}}onAll(e){if(!this.#e)return this.debugLog("Event bus client is disabled, not registering event"),()=>{};let t=t=>{e(t.detail)};return this.#n().addEventListener("tanstack-devtools-global",t),()=>this.#n().removeEventListener("tanstack-devtools-global",t)}onAllPluginEvents(e){if(!this.#e)return this.debugLog("Event bus client is disabled, not registering event"),()=>{};let t=t=>{let n=t.detail;this.#t&&n.pluginId!==this.#t||e(n)};return this.#n().addEventListener("tanstack-devtools-global",t),()=>this.#n().removeEventListener("tanstack-devtools-global",t)}};let h=new Map;function m(e){if(void 0!==e)try{return JSON.parse(JSON.stringify(e))}catch{return null}}let f=new class extends c{constructor(e){super({pluginId:"pacer",debug:e?.debug,reconnectEveryMs:1e3})}},p=((n={})[n.None=0]="None",n[n.Mutable=1]="Mutable",n[n.Watching=2]="Watching",n[n.RecursedCheck=4]="RecursedCheck",n[n.Recursed=8]="Recursed",n[n.Dirty=16]="Dirty",n[n.Pending=32]="Pending",n);function g(e,t,n){let i="object"==typeof e,s=i?e:void 0;return{next:(i?e.next:e)?.bind(s),error:(i?e.error:t)?.bind(s),complete:(i?e.complete:n)?.bind(s)}}let v=[],x=0,{link:b,unlink:y,propagate:_,checkDirty:j,shallowPropagate:E}=function({update:e,notify:t,unwatched:n}){return{link:function(e,t,n){let i=t.depsTail;if(void 0!==i&&i.dep===e)return;let s=void 0!==i?i.nextDep:t.deps;if(void 0!==s&&s.dep===e){s.version=n,t.depsTail=s;return}let r=e.subsTail;if(void 0!==r&&r.version===n&&r.sub===t)return;let a=t.depsTail=e.subsTail={version:n,dep:e,sub:t,prevDep:i,nextDep:s,prevSub:r,nextSub:void 0};void 0!==s&&(s.prevDep=a),void 0!==i?i.nextDep=a:t.deps=a,void 0!==r?r.nextSub=a:e.subs=a},unlink:function(e,t=e.sub){let i=e.dep,s=e.prevDep,r=e.nextDep,a=e.nextSub,l=e.prevSub;return void 0!==r?r.prevDep=s:t.depsTail=s,void 0!==s?s.nextDep=r:t.deps=r,void 0!==a?a.prevSub=l:i.subsTail=l,void 0!==l?l.nextSub=a:void 0===(i.subs=a)&&n(i),r},propagate:function(e){let n,i=e.nextSub;e:for(;;){let s=e.sub,r=s.flags;if(r&(p.RecursedCheck|p.Recursed|p.Dirty|p.Pending)?r&(p.RecursedCheck|p.Recursed)?r&p.RecursedCheck?!(r&(p.Dirty|p.Pending))&&function(e,t){let n=t.depsTail;for(;void 0!==n;){if(n===e)return!0;n=n.prevDep}return!1}(e,s)?(s.flags=r|(p.Recursed|p.Pending),r&=p.Mutable):r=p.None:s.flags=r&~p.Recursed|p.Pending:r=p.None:s.flags=r|p.Pending,r&p.Watching&&t(s),r&p.Mutable){let t=s.subs;if(void 0!==t){let s=(e=t).nextSub;void 0!==s&&(n={value:i,prev:n},i=s);continue}}if(void 0!==(e=i)){i=e.nextSub;continue}for(;void 0!==n;)if(e=n.value,n=n.prev,void 0!==e){i=e.nextSub;continue e}break}},checkDirty:function(t,n){let s,r=0,a=!1;e:for(;;){let l=t.dep,o=l.flags;if(n.flags&p.Dirty)a=!0;else if((o&(p.Mutable|p.Dirty))==(p.Mutable|p.Dirty)){if(e(l)){let e=l.subs;void 0!==e.nextSub&&i(e),a=!0}}else if((o&(p.Mutable|p.Pending))==(p.Mutable|p.Pending)){(void 0!==t.nextSub||void 0!==t.prevSub)&&(s={value:t,prev:s}),t=l.deps,n=l,++r;continue}if(!a){let e=t.nextDep;if(void 0!==e){t=e;continue}}for(;r--;){let r=n.subs,l=void 0!==r.nextSub;if(l?(t=s.value,s=s.prev):t=r,a){if(e(n)){l&&i(r),n=t.sub;continue}a=!1}else n.flags&=~p.Pending;n=t.sub;let o=t.nextDep;if(void 0!==o){t=o;continue e}}return a}},shallowPropagate:i};function i(e){do{let n=e.sub,i=n.flags;(i&(p.Pending|p.Dirty))===p.Pending&&(n.flags=i|p.Dirty,(i&(p.Watching|p.RecursedCheck))===p.Watching&&t(n))}while(void 0!==(e=e.nextSub))}}({update:e=>e._update(),notify(e){v[C++]=e,e.flags&=~p.Watching},unwatched(e){void 0!==e.depsTail&&(e.depsTail=void 0,e.flags=p.Mutable|p.Dirty,k(e))}}),w=0,C=0;function k(e){let t=e.depsTail,n=void 0!==t?t.nextDep:e.deps;for(;void 0!==n;)n=y(n,e)}var S=class{constructor(e,n){this.atom=function(e){let n="function"==typeof e,i={_snapshot:n?void 0:e,subs:void 0,subsTail:void 0,deps:void 0,depsTail:void 0,flags:n?p.None:p.Mutable,get:()=>(void 0!==t&&b(i,t,x),i._snapshot),subscribe(e){var n;let s,r,a=g(e),l={current:!1},o=(n=()=>{i.get(),l.current?a.next?.(i._snapshot):l.current=!0},s=()=>{let e=t;t=r,++x,r.depsTail=void 0,r.flags=p.Watching|p.RecursedCheck;try{return n()}finally{t=e,r.flags&=~p.RecursedCheck,k(r)}},r={deps:void 0,depsTail:void 0,subs:void 0,subsTail:void 0,flags:p.Watching|p.RecursedCheck,notify(){let e=this.flags;e&p.Dirty||e&p.Pending&&j(this.deps,this)?s():this.flags=p.Watching},stop(){this.flags=p.None,this.depsTail=void 0,k(this)}},s(),r);return{unsubscribe:()=>{o.stop()}}},_update(s){let r=t,a=(void 0)??Object.is;if(n)t=i,++x,i.depsTail=void 0;else if(void 0===s)return!1;n&&(i.flags=p.Mutable|p.RecursedCheck);try{let t=i._snapshot,r="function"==typeof s?s(t):void 0===s&&n?e(t):s;if(void 0===t||!a(t,r))return i._snapshot=r,!0;return!1}finally{t=r,n&&(i.flags&=~p.RecursedCheck),k(i)}}};return n?(i.flags=p.Mutable|p.Dirty,i.get=function(){let e=i.flags;if(e&p.Dirty||e&p.Pending&&j(i.deps,i)){if(i._update()){let e=i.subs;void 0!==e&&E(e)}}else e&p.Pending&&(i.flags=e&~p.Pending);return void 0!==t&&b(i,t,x),i._snapshot}):i.set=function(e){if(i._update(e)){let e=i.subs;if(void 0!==e&&(_(e),E(e),1)){for(;w{this.options={...this.options,...e},this.#v()||this.cancel()},this.#x=e=>{this.store.setState(t=>{let n={...t,...e},{isPending:i}=n;return{...n,status:this.#v()?i?"pending":"idle":"disabled"}}),((e,t)=>{let n=t.key;if(n){var i,s;h.set(n,t),f.emit(e,{key:(i={...t,key:n}).key,store:{state:m("function"==typeof(s=i.store).get?s.get():s.state)},options:m(i.options)})}})("Debouncer",this)},this.#v=()=>!!u(this.options.enabled,this),this.#b=()=>u(this.options.wait,this),this.maybeExecute=(...e)=>{if(!this.#v())return;this.#x({maybeExecuteCount:this.store.state.maybeExecuteCount+1});let t=!1;this.options.leading&&this.store.state.canLeadingExecute&&(this.#x({canLeadingExecute:!1}),t=!0,this.#y(...e)),this.options.trailing&&this.#x({isPending:!0,lastArgs:e}),this.#g&&clearTimeout(this.#g),this.#g=setTimeout(()=>{this.#x({canLeadingExecute:!0}),this.options.trailing&&!t&&this.#y(...e)},this.#b())},this.#y=(...e)=>{this.#v()&&(this.fn(...e),this.#x({executionCount:this.store.state.executionCount+1,isPending:!1,lastArgs:void 0}),this.options.onExecute?.(e,this))},this.flush=()=>{this.store.state.isPending&&this.store.state.lastArgs&&(this.#_(),this.#y(...this.store.state.lastArgs))},this.#_=()=>{this.#g&&(clearTimeout(this.#g),this.#g=void 0)},this.cancel=()=>{this.#_(),this.#x({canLeadingExecute:!0,isPending:!1})},this.reset=()=>{this.#x(T())},this.key=t.key,this.options={...N,...t},this.#x(this.options.initialState??{}),this.key&&f.on("d-Debouncer",e=>{e.payload.key===this.key&&(this.#x(e.payload.store.state),this.setOptions(e.payload.options))})}#x;#v;#b;#y;#_};e.s(["useDebouncer",0,function(e,t,n=()=>({})){let a={...((0,i.useContext)(s)?.defaultOptions??{}).debouncer,...t},[l]=(0,i.useState)(()=>{let t=new O(e,a);return t.Subscribe=function(e){let n=d(t.store,e.selector,{compare:r});return"function"==typeof e.children?e.children(n):e.children},t});l.fn=e,l.setOptions(a),(0,i.useEffect)(()=>()=>{a.onUnmount?a.onUnmount(l):l.cancel()},[]);let o=d(l.store,n,{compare:r});return(0,i.useMemo)(()=>({...l,state:o}),[l,o])}],540626)},741466,e=>{"use strict";e.s(["DEBOUNCE_WAIT_MS",0,300])},399029,e=>{"use strict";var t=e.i(540626),n=e.i(271645);e.s(["useDebouncedState",0,function(e,i,s){let[r,a]=(0,n.useState)(e),l=(0,t.useDebouncer)(a,i,s);return[r,l.maybeExecute,l]}])},663435,e=>{"use strict";var t=e.i(843476),n=e.i(271645),i=e.i(199133),s=e.i(898586),r=e.i(56456),a=e.i(399029),l=e.i(785242),o=e.i(741466);let{Text:d}=s.Typography;e.s(["default",0,({value:e,onChange:s,onTeamSelect:u,disabled:c,organizationId:h,pageSize:m=20})=>{let[f,p]=(0,n.useState)(""),[g,v]=(0,a.useDebouncedState)("",{wait:o.DEBOUNCE_WAIT_MS}),{data:x,fetchNextPage:b,hasNextPage:y,isFetchingNextPage:_,isLoading:j}=(0,l.useInfiniteTeams)(m,g||void 0,h),E=(0,n.useMemo)(()=>{if(!x?.pages)return[];let e=new Set,t=[];for(let n of x.pages)for(let i of n.teams)e.has(i.team_id)||(e.add(i.team_id),t.push(i));return t},[x]);return(0,t.jsx)(i.Select,{showSearch:!0,placeholder:"Search or select a team",value:e||void 0,onChange:e=>{s?.(e??""),u&&u(e?E.find(t=>t.team_id===e)??null:null)},disabled:c,allowClear:!0,filterOption:!1,onSearch:e=>{p(e),v(e)},searchValue:f,onPopupScroll:e=>{let t=e.currentTarget;(t.scrollTop+t.clientHeight)/t.scrollHeight>=.8&&y&&!_&&b()},loading:j,notFoundContent:j?(0,t.jsx)(r.LoadingOutlined,{spin:!0}):"No teams found","data-testid":"team-dropdown",popupRender:e=>(0,t.jsxs)(t.Fragment,{children:[e,_&&(0,t.jsx)("div",{style:{textAlign:"center",padding:8},children:(0,t.jsx)(r.LoadingOutlined,{spin:!0})})]}),children:E.map(e=>(0,t.jsxs)(i.Select.Option,{value:e.team_id,children:[(0,t.jsx)("span",{className:"font-medium",children:e.team_alias})," ",(0,t.jsxs)(d,{type:"secondary",children:["(",e.team_id,")"]})]},e.team_id))})}])},737434,e=>{"use strict";var t=e.i(184163);e.s(["DownloadOutlined",()=>t.default])},59935,(e,t,n)=>{var i;let s;e.e,i=function e(){var t,n="u">typeof self?self:"u">typeof window?window:void 0!==n?n:{},i=!n.document&&!!n.postMessage,s=n.IS_PAPA_WORKER||!1,r={},a=0,l={};function o(e){this._handle=null,this._finished=!1,this._completed=!1,this._halted=!1,this._input=null,this._baseIndex=0,this._partialLine="",this._rowCount=0,this._start=0,this._nextChunk=null,this.isFirstChunk=!0,this._completeResults={data:[],errors:[],meta:{}},(function(e){var t=b(e);t.chunkSize=parseInt(t.chunkSize),e.step||e.chunk||(t.chunkSize=null),this._handle=new m(t),(this._handle.streamer=this)._config=t}).call(this,e),this.parseChunk=function(e,t){var i=parseInt(this._config.skipFirstNLines)||0;if(this.isFirstChunk&&0=this._config.preview,s)n.postMessage({results:r,workerId:l.WORKER_ID,finished:i});else if(_(this._config.chunk)&&!t){if(this._config.chunk(r,this._handle),this._handle.paused()||this._handle.aborted())return void(this._halted=!0);this._completeResults=r=void 0}return this._config.step||this._config.chunk||(this._completeResults.data=this._completeResults.data.concat(r.data),this._completeResults.errors=this._completeResults.errors.concat(r.errors),this._completeResults.meta=r.meta),this._completed||!i||!_(this._config.complete)||r&&r.meta.aborted||(this._config.complete(this._completeResults,this._input),this._completed=!0),i||r&&r.meta.paused||this._nextChunk(),r}this._halted=!0},this._sendError=function(e){_(this._config.error)?this._config.error(e):s&&this._config.error&&n.postMessage({workerId:l.WORKER_ID,error:e,finished:!1})}}function d(e){var t;(e=e||{}).chunkSize||(e.chunkSize=l.RemoteChunkSize),o.call(this,e),this._nextChunk=i?function(){this._readChunk(),this._chunkLoaded()}:function(){this._readChunk()},this.stream=function(e){this._input=e,this._nextChunk()},this._readChunk=function(){if(this._finished)this._chunkLoaded();else{if(t=new XMLHttpRequest,this._config.withCredentials&&(t.withCredentials=this._config.withCredentials),i||(t.onload=y(this._chunkLoaded,this),t.onerror=y(this._chunkError,this)),t.open(this._config.downloadRequestBody?"POST":"GET",this._input,!i),this._config.downloadRequestHeaders){var e,n,s=this._config.downloadRequestHeaders;for(n in s)t.setRequestHeader(n,s[n])}this._config.chunkSize&&(e=this._start+this._config.chunkSize-1,t.setRequestHeader("Range","bytes="+this._start+"-"+e));try{t.send(this._config.downloadRequestBody)}catch(e){this._chunkError(e.message)}i&&0===t.status&&this._chunkError()}},this._chunkLoaded=function(){let e;4===t.readyState&&(t.status<200||400<=t.status?this._chunkError():(this._start+=this._config.chunkSize||t.responseText.length,this._finished=!this._config.chunkSize||this._start>=(null!==(e=(e=t).getResponseHeader("Content-Range"))?parseInt(e.substring(e.lastIndexOf("/")+1)):-1),this.parseChunk(t.responseText)))},this._chunkError=function(e){e=t.statusText||e,this._sendError(Error(e))}}function u(e){(e=e||{}).chunkSize||(e.chunkSize=l.LocalChunkSize),o.call(this,e);var t,n,i="u">typeof FileReader;this.stream=function(e){this._input=e,n=e.slice||e.webkitSlice||e.mozSlice,i?((t=new FileReader).onload=y(this._chunkLoaded,this),t.onerror=y(this._chunkError,this)):t=new FileReaderSync,this._nextChunk()},this._nextChunk=function(){this._finished||this._config.preview&&!(this._rowCount=this._input.size,this.parseChunk(e.target.result)},this._chunkError=function(){this._sendError(t.error)}}function c(e){var t;o.call(this,e=e||{}),this.stream=function(e){return t=e,this._nextChunk()},this._nextChunk=function(){var e,n;if(!this._finished)return t=(e=this._config.chunkSize)?(n=t.substring(0,e),t.substring(e)):(n=t,""),this._finished=!t,this.parseChunk(n)}}function h(e){o.call(this,e=e||{});var t=[],n=!0,i=!1;this.pause=function(){o.prototype.pause.apply(this,arguments),this._input.pause()},this.resume=function(){o.prototype.resume.apply(this,arguments),this._input.resume()},this.stream=function(e){this._input=e,this._input.on("data",this._streamData),this._input.on("end",this._streamEnd),this._input.on("error",this._streamError)},this._checkIsFinished=function(){i&&1===t.length&&(this._finished=!0)},this._nextChunk=function(){this._checkIsFinished(),t.length?this.parseChunk(t.shift()):n=!0},this._streamData=y(function(e){try{t.push("string"==typeof e?e:e.toString(this._config.encoding)),n&&(n=!1,this._checkIsFinished(),this.parseChunk(t.shift()))}catch(e){this._streamError(e)}},this),this._streamError=y(function(e){this._streamCleanUp(),this._sendError(e)},this),this._streamEnd=y(function(){this._streamCleanUp(),i=!0,this._streamData("")},this),this._streamCleanUp=y(function(){this._input.removeListener("data",this._streamData),this._input.removeListener("end",this._streamEnd),this._input.removeListener("error",this._streamError)},this)}function m(e){var t,n,i,s,r=/^\s*-?(\d+\.?|\.\d+|\d+\.\d+)([eE][-+]?\d+)?\s*$/,a=/^((\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z)))$/,o=this,d=0,u=0,c=!1,h=!1,m=[],g={data:[],errors:[],meta:{}};function v(t){return"greedy"===e.skipEmptyLines?""===t.join("").trim():1===t.length&&0===t[0].length}function x(){if(g&&i&&(j("Delimiter","UndetectableDelimiter","Unable to auto-detect delimiting character; defaulted to '"+l.DefaultDelimiter+"'"),i=!1),e.skipEmptyLines&&(g.data=g.data.filter(function(e){return!v(e)})),y()){if(g)if(Array.isArray(g.data[0])){for(var t,n=0;y()&&n(e.dynamicTypingFunction&&void 0===e.dynamicTyping[t]&&(e.dynamicTyping[t]=e.dynamicTypingFunction(t)),!0===(e.dynamicTyping[t]||e.dynamicTyping))?"true"===n||"TRUE"===n||"false"!==n&&"FALSE"!==n&&((e=>{if(r.test(e)&&-0x20000000000000<(e=parseFloat(e))&&e<0x20000000000000)return 1})(n)?parseFloat(n):a.test(n)?new Date(n):""===n?null:n):n)(l=e.header?s>=m.length?"__parsed_extra":m[s]:l,o=e.transform?e.transform(o,l):o);"__parsed_extra"===l?(i[l]=i[l]||[],i[l].push(o)):i[l]=o}return e.header&&(s>m.length?j("FieldMismatch","TooManyFields","Too many fields: expected "+m.length+" fields but parsed "+s,u+n):se.preview?n.abort():(g.data=g.data[0],s(g,o))))}),this.parse=function(s,r,a){var o=e.quoteChar||'"',o=(e.newline||(e.newline=this.guessLineEndings(s,o)),i=!1,e.delimiter?_(e.delimiter)&&(e.delimiter=e.delimiter(s),g.meta.delimiter=e.delimiter):((o=((t,n,i,s,r)=>{var a,o,d,u;r=r||[","," ","|",";",l.RECORD_SEP,l.UNIT_SEP];for(var c=0;c=n.length/2?"\r\n":"\r"}}function f(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function p(e){var t=(e=e||{}).delimiter,n=e.newline,i=e.comments,s=e.step,r=e.preview,a=e.fastMode,o=null,d=!1,u=null==e.quoteChar?'"':e.quoteChar,c=u;if(void 0!==e.escapeChar&&(c=e.escapeChar),("string"!=typeof t||-1=r)return M(!0);break}w.push({type:"Quotes",code:"InvalidQuotes",message:"Trailing quote on quoted field is malformed",row:E.length,index:h}),R++}}else if(i&&0===C.length&&l.substring(h,h+y)===i){if(-1===O)return M();h=O+b,O=l.indexOf(n,h),N=l.indexOf(t,h)}else if(-1!==N&&(N=r)return M(!0)}return A();function L(e){E.push(e),k=h}function D(e){return -1!==e&&(e=l.substring(R+1,e))&&""===e.trim()?e.length:0}function A(e){return g||(void 0===e&&(e=l.substring(h)),C.push(e),h=v,L(C),j&&B()),M()}function F(e){h=e,L(C),C=[],O=l.indexOf(n,h)}function M(i){if(e.header&&!p&&E.length&&!d){var s=E[0],r=Object.create(null),a=new Set(s);let t=!1;for(let n=0;n{if("object"==typeof t){if("string"!=typeof t.delimiter||l.BAD_DELIMITERS.filter(function(e){return -1!==t.delimiter.indexOf(e)}).length||(s=t.delimiter),("boolean"==typeof t.quotes||"function"==typeof t.quotes||Array.isArray(t.quotes))&&(n=t.quotes),"boolean"!=typeof t.skipEmptyLines&&"string"!=typeof t.skipEmptyLines||(d=t.skipEmptyLines),"string"==typeof t.newline&&(r=t.newline),"string"==typeof t.quoteChar&&(a=t.quoteChar),"boolean"==typeof t.header&&(i=t.header),Array.isArray(t.columns)){if(0===t.columns.length)throw Error("Option columns is empty");u=t.columns}void 0!==t.escapeChar&&(o=t.escapeChar+a),t.escapeFormulae instanceof RegExp?c=t.escapeFormulae:"boolean"==typeof t.escapeFormulae&&t.escapeFormulae&&(c=/^[=+\-@\t\r].*$/)}})(),RegExp(f(a),"g"));if("string"==typeof e&&(e=JSON.parse(e)),Array.isArray(e)){if(!e.length||Array.isArray(e[0]))return m(null,e,d);if("object"==typeof e[0])return m(u||Object.keys(e[0]),e,d)}else if("object"==typeof e)return"string"==typeof e.data&&(e.data=JSON.parse(e.data)),Array.isArray(e.data)&&(e.fields||(e.fields=e.meta&&e.meta.fields||u),e.fields||(e.fields=Array.isArray(e.data[0])?e.fields:"object"==typeof e.data[0]?Object.keys(e.data[0]):[]),Array.isArray(e.data[0])||"object"==typeof e.data[0]||(e.data=[e.data])),m(e.fields||[],e.data||[],d);throw Error("Unable to serialize unrecognized input");function m(e,t,n){var a="",l=("string"==typeof e&&(e=JSON.parse(e)),"string"==typeof t&&(t=JSON.parse(t)),Array.isArray(e)&&0{for(var n=0;n{"use strict";var t=e.i(271645),n=e.i(914189);e.s(["useControllable",0,function(e,i,s){let[r,a]=(0,t.useState)(s),l=void 0!==e,o=(0,t.useRef)(l),d=(0,t.useRef)(!1),u=(0,t.useRef)(!1);return!l||o.current||d.current?l||!o.current||u.current||(u.current=!0,o.current=l,console.error("A component is changing from controlled to uncontrolled. This may be caused by the value changing from a defined value to undefined, which should not happen.")):(d.current=!0,o.current=l,console.error("A component is changing from uncontrolled to controlled. This may be caused by the value changing from undefined to a defined value, which should not happen.")),[l?e:r,(0,n.useEvent)(e=>(l||a(e),null==i?void 0:i(e)))]}],503269),e.s(["useDefaultValue",0,function(e){let[n]=(0,t.useState)(e);return n}],214520);let i=(0,t.createContext)(void 0);function s(){return(0,t.useContext)(i)}e.s(["useDisabled",0,s],601893);var r=e.i(174080),a=e.i(746725);function l(e={},t=null,n=[]){for(let[i,s]of Object.entries(e))!function e(t,n,i){if(Array.isArray(i))for(let[s,r]of i.entries())e(t,o(n,s.toString()),r);else i instanceof Date?t.push([n,i.toISOString()]):"boolean"==typeof i?t.push([n,i?"1":"0"]):"string"==typeof i?t.push([n,i]):"number"==typeof i?t.push([n,`${i}`]):null==i?t.push([n,""]):l(i,n,t)}(n,o(t,i),s);return n}function o(e,t){return e?e+"["+t+"]":t}e.s(["attemptSubmit",0,function(e){var t,n;let i=null!=(t=null==e?void 0:e.form)?t:e.closest("form");if(i){for(let t of i.elements)if(t!==e&&("INPUT"===t.tagName&&"submit"===t.type||"BUTTON"===t.tagName&&"submit"===t.type||"INPUT"===t.nodeName&&"image"===t.type))return void t.click();null==(n=i.requestSubmit)||n.call(i)}},"objectToFormEntries",0,l],694421);var d=e.i(700020),u=e.i(2788);let c=(0,t.createContext)(null);function h({children:e}){let n=(0,t.useContext)(c);if(!n)return t.default.createElement(t.default.Fragment,null,e);let{target:i}=n;return i?(0,r.createPortal)(t.default.createElement(t.default.Fragment,null,e),i):null}function m({setForm:e,formId:n}){return(0,t.useEffect)(()=>{if(n){let t=document.getElementById(n);t&&e(t)}},[e,n]),n?null:t.default.createElement(u.Hidden,{features:u.HiddenFeatures.Hidden,as:"input",type:"hidden",hidden:!0,readOnly:!0,ref:t=>{if(!t)return;let n=t.closest("form");n&&e(n)}})}e.s(["FormFields",0,function({data:e,form:n,disabled:i,onReset:s,overrides:r}){let[o,c]=(0,t.useState)(null),f=(0,a.useDisposables)();return(0,t.useEffect)(()=>{if(s&&o)return f.addEventListener(o,"reset",s)},[o,n,s]),t.default.createElement(h,null,t.default.createElement(m,{setForm:c,formId:n}),l(e).map(([e,s])=>t.default.createElement(u.Hidden,{features:u.HiddenFeatures.Hidden,...(0,d.compact)({key:e,as:"input",type:"hidden",hidden:!0,readOnly:!0,form:n,disabled:i,name:e,value:s,...r})})))}],140721);let f=(0,t.createContext)(void 0);function p(){return(0,t.useContext)(f)}e.s(["useProvidedId",0,p],942803);var g=e.i(835696),v=e.i(294316);let x=(0,t.createContext)(null);x.displayName="DescriptionContext";let b=Object.assign((0,d.forwardRefWithAs)(function(e,n){let i=(0,t.useId)(),r=s(),{id:a=`headlessui-description-${i}`,...l}=e,o=function e(){let n=(0,t.useContext)(x);if(null===n){let t=Error("You used a component, but it is not inside a relevant parent.");throw Error.captureStackTrace&&Error.captureStackTrace(t,e),t}return n}(),u=(0,v.useSyncRefs)(n);(0,g.useIsoMorphicEffect)(()=>o.register(a),[a,o.register]);let c=r||!1,h=(0,t.useMemo)(()=>({...o.slot,disabled:c}),[o.slot,c]),m={ref:u,...o.props,id:a};return(0,d.useRender)()({ourProps:m,theirProps:l,slot:h,defaultTag:"p",name:o.name||"Description"})}),{});e.s(["Description",0,b,"useDescribedBy",0,function(){var e,n;return null!=(n=null==(e=(0,t.useContext)(x))?void 0:e.value)?n:void 0},"useDescriptions",0,function(){let[e,i]=(0,t.useState)([]);return[e.length>0?e.join(" "):void 0,(0,t.useMemo)(()=>function(e){let s=(0,n.useEvent)(e=>(i(t=>[...t,e]),()=>i(t=>{let n=t.slice(),i=n.indexOf(e);return -1!==i&&n.splice(i,1),n}))),r=(0,t.useMemo)(()=>({register:s,slot:e.slot,name:e.name,props:e.props,value:e.value}),[s,e.slot,e.name,e.props,e.value]);return t.default.createElement(x.Provider,{value:r},e.children)},[i])]}],35889);let y=(0,t.createContext)(null);function _(e){var n,i,s;let r=null!=(i=null==(n=(0,t.useContext)(y))?void 0:n.value)?i:void 0;return(null!=(s=null==e?void 0:e.length)?s:0)>0?[r,...e].filter(Boolean).join(" "):r}y.displayName="LabelContext";let j=Object.assign((0,d.forwardRefWithAs)(function(e,i){var r;let a=(0,t.useId)(),l=function e(){let n=(0,t.useContext)(y);if(null===n){let t=Error("You used a