Merge pull request #19009 from Dima-Mediator/fix-image-tokens-spend-logging

Fix image tokens spend logging for /images/generations
This commit is contained in:
Sameer Kankute
2026-01-13 15:03:37 +05:30
committed by GitHub
230 changed files with 2938 additions and 488 deletions
+17 -1
View File
@@ -1959,6 +1959,18 @@ jobs:
command: |
kind create cluster --name litellm-test
- run:
name: Build Docker image for helm tests
command: |
IMAGE_TAG=${CIRCLE_SHA1:-ci}
docker build -t litellm-ci:${IMAGE_TAG} -f docker/Dockerfile.database .
- run:
name: Load Docker image into Kind
command: |
IMAGE_TAG=${CIRCLE_SHA1:-ci}
kind load docker-image litellm-ci:${IMAGE_TAG} --name litellm-test
# Run helm lint
- run:
name: Run helm lint
@@ -1969,7 +1981,11 @@ jobs:
- run:
name: Run helm tests
command: |
helm install litellm ./deploy/charts/litellm-helm -f ./deploy/charts/litellm-helm/ci/test-values.yaml
IMAGE_TAG=${CIRCLE_SHA1:-ci}
helm install litellm ./deploy/charts/litellm-helm -f ./deploy/charts/litellm-helm/ci/test-values.yaml \
--set image.repository=litellm-ci \
--set image.tag=${IMAGE_TAG} \
--set image.pullPolicy=Never
# Wait for pod to be ready
echo "Waiting 30 seconds for pod to be ready..."
sleep 30
+3
View File
@@ -0,0 +1,3 @@
ignore:
- vulnerability: CVE-2019-1010022
reason: no fixed glibc package is available yet in the Wolfi repositories, so this is ignored temporarily until an upstream release exists
+7 -2
View File
@@ -101,12 +101,12 @@ run_grype_scans() {
# Build and scan Dockerfile.database
echo "Building and scanning Dockerfile.database..."
docker build --no-cache -t litellm-database:latest -f ./docker/Dockerfile.database .
grype litellm-database:latest --fail-on critical
grype litellm-database:latest --config ci_cd/.grype.yaml --fail-on critical
# Build and scan main Dockerfile
echo "Building and scanning main Dockerfile..."
docker build --no-cache -t litellm:latest .
grype litellm:latest --fail-on critical
grype litellm:latest --config ci_cd/.grype.yaml --fail-on critical
# Restore original .dockerignore
echo "Restoring original .dockerignore..."
@@ -129,6 +129,11 @@ run_grype_scans() {
"CVE-2025-13836" # Python 3.13 HTTP response reading OOM/DoS - no fix available in base image
"CVE-2025-12084" # Python 3.13 xml.dom.minidom quadratic algorithm - no fix available in base image
"CVE-2025-60876" # BusyBox wget HTTP request splitting - no fix available in Chainguard Wolfi base image
"CVE-2010-4756" # glibc glob DoS - awaiting patched Wolfi glibc build
"CVE-2019-1010022" # glibc stack guard bypass - awaiting patched Wolfi glibc build
"CVE-2019-1010023" # glibc ldd remap issue - awaiting patched Wolfi glibc build
"CVE-2019-1010024" # glibc ASLR mitigation bypass - awaiting patched Wolfi glibc build
"CVE-2019-1010025" # glibc pthread heap address leak - awaiting patched Wolfi glibc build
)
# Build JSON array of allowlisted CVE IDs for jq
+2
View File
@@ -110,6 +110,8 @@ COPY --from=builder /app/requirements.txt /app/requirements.txt
COPY --from=builder /app/docker/entrypoint.sh /app/docker/prod_entrypoint.sh /app/docker/
COPY --from=builder /app/docker/supervisord.conf /etc/supervisord.conf
COPY --from=builder /app/schema.prisma /app/
# Copy prisma_migration.py for Helm migrations job compatibility
COPY --from=builder /app/litellm/proxy/prisma_migration.py /app/litellm/proxy/prisma_migration.py
COPY --from=builder /wheels/ /wheels/
COPY --from=builder /var/lib/litellm/ui /var/lib/litellm/ui
COPY --from=builder /var/lib/litellm/assets /var/lib/litellm/assets
+1 -1
View File
@@ -10,7 +10,7 @@ This policy outlines the requirements and controls/procedures LiteLLM Cloud has
For Customers
1. Active Accounts
- Customer data is retained for as long as the customers account is in active status. This includes data such as prompts, generated content, logs, and usage metrics.
- Customer data is retained for as long as the customers account is in active status. This includes data such as prompts, generated content, logs, and usage metrics. By default, we do not store the message / response content of your API requests or responses. Cloud users need to explicitly opt in to store the message / response content of your API requests or responses.
2. Voluntary Account Closure
+50 -1
View File
@@ -73,8 +73,21 @@ GOOGLE_CLIENT_SECRET=
```shell
MICROSOFT_CLIENT_ID="84583a4d-"
MICROSOFT_CLIENT_SECRET="nbk8Q~"
MICROSOFT_TENANT="5a39737
MICROSOFT_TENANT="5a39737"
```
**Optional: Custom Microsoft SSO Endpoints**
If you need to use custom Microsoft SSO endpoints (e.g., for a custom identity provider, sovereign cloud, or proxy), you can override the default endpoints:
```shell
MICROSOFT_AUTHORIZATION_ENDPOINT="https://your-custom-url.com/oauth2/v2.0/authorize"
MICROSOFT_TOKEN_ENDPOINT="https://your-custom-url.com/oauth2/v2.0/token"
MICROSOFT_USERINFO_ENDPOINT="https://your-custom-graph-api.com/v1.0/me"
```
If these are not set, the default Microsoft endpoints are used based on your tenant.
- Set Redirect URI on your App Registration on https://portal.azure.com/
- Set a redirect url = `<your proxy base url>/sso/callback`
```shell
@@ -98,6 +111,42 @@ To set up app roles:
4. Assign users to these roles in your Enterprise Application
5. When users sign in via SSO, LiteLLM will automatically assign them the corresponding role
**Advanced: Custom User Attribute Mapping**
For certain Microsoft Entra ID configurations, you may need to override the default user attribute field names. This is useful when your organization uses custom claims or non-standard attribute names in the SSO response.
**Step 1: Debug SSO Response**
First, inspect the JWT fields returned by your Microsoft SSO provider using the [SSO Debug Route](#debugging-sso-jwt-fields).
1. Add `/sso/debug/callback` as a redirect URL in your Azure App Registration
2. Navigate to `https://<proxy_base_url>/sso/debug/login`
3. Complete the SSO flow to see the returned user attributes
**Step 2: Identify Field Attribute Names**
From the debug response, identify the field names used for email, display name, user ID, first name, and last name.
**Step 3: Set Environment Variables**
Override the default attribute names by setting these environment variables:
| Environment Variable | Description | Default Value |
|---------------------|-------------|---------------|
| `MICROSOFT_USER_EMAIL_ATTRIBUTE` | Field name for user email | `userPrincipalName` |
| `MICROSOFT_USER_DISPLAY_NAME_ATTRIBUTE` | Field name for display name | `displayName` |
| `MICROSOFT_USER_ID_ATTRIBUTE` | Field name for user ID | `id` |
| `MICROSOFT_USER_FIRST_NAME_ATTRIBUTE` | Field name for first name | `givenName` |
| `MICROSOFT_USER_LAST_NAME_ATTRIBUTE` | Field name for last name | `surname` |
**Step 4: Restart the Proxy**
After setting the environment variables, restart the proxy:
```bash
litellm --config /path/to/config.yaml
```
</TabItem>
<TabItem value="Generic" label="Generic SSO Provider">
@@ -771,10 +771,18 @@ router_settings:
| MINIMUM_PROMPT_CACHE_TOKEN_COUNT | Minimum token count for caching a prompt. Default is 1024
| MISTRAL_API_BASE | Base URL for Mistral API. Default is https://api.mistral.ai
| MISTRAL_API_KEY | API key for Mistral API
| MICROSOFT_AUTHORIZATION_ENDPOINT | Custom authorization endpoint URL for Microsoft SSO (overrides default Microsoft OAuth authorization endpoint)
| MICROSOFT_CLIENT_ID | Client ID for Microsoft services
| MICROSOFT_CLIENT_SECRET | Client secret for Microsoft services
| MICROSOFT_TENANT | Tenant ID for Microsoft Azure
| MICROSOFT_SERVICE_PRINCIPAL_ID | Service Principal ID for Microsoft Enterprise Application. (This is an advanced feature if you want litellm to auto-assign members to Litellm Teams based on their Microsoft Entra ID Groups)
| MICROSOFT_TENANT | Tenant ID for Microsoft Azure
| MICROSOFT_TOKEN_ENDPOINT | Custom token endpoint URL for Microsoft SSO (overrides default Microsoft OAuth token endpoint)
| MICROSOFT_USER_DISPLAY_NAME_ATTRIBUTE | Field name for user display name in Microsoft SSO response. Default is `displayName`
| MICROSOFT_USER_EMAIL_ATTRIBUTE | Field name for user email in Microsoft SSO response. Default is `userPrincipalName`
| MICROSOFT_USER_FIRST_NAME_ATTRIBUTE | Field name for user first name in Microsoft SSO response. Default is `givenName`
| MICROSOFT_USER_ID_ATTRIBUTE | Field name for user ID in Microsoft SSO response. Default is `id`
| MICROSOFT_USER_LAST_NAME_ATTRIBUTE | Field name for user last name in Microsoft SSO response. Default is `surname`
| MICROSOFT_USERINFO_ENDPOINT | Custom userinfo endpoint URL for Microsoft SSO (overrides default Microsoft Graph userinfo endpoint)
| NO_DOCS | Flag to disable Swagger UI documentation
| NO_REDOC | Flag to disable Redoc documentation
| NO_PROXY | List of addresses to bypass proxy
@@ -1,5 +1,5 @@
---
title: "v1.80.11 - Google Interactions API"
title: "v1.80.11-stable - Google Interactions API"
slug: "v1-80-11"
date: 2025-12-20T10:00:00
authors:
@@ -1,5 +1,5 @@
---
title: "v1.80.15 - Manus API Support"
title: "[Preview] v1.80.15.rc.1 - Manus API Support"
slug: "v1-80-15"
date: 2026-01-10T10:00:00
authors:
@@ -8,6 +8,7 @@ from typing import TYPE_CHECKING, Any, Dict, List, Literal, Optional, Union, cas
from fastapi import HTTPException
import litellm
from litellm import Router, verbose_logger
from litellm._uuid import uuid
from litellm.caching.caching import DualCache
@@ -836,15 +837,36 @@ class _PROXY_LiteLLMManagedFiles(CustomLogger, BaseFileEndpoints):
return response
async def afile_retrieve(
self, file_id: str, litellm_parent_otel_span: Optional[Span]
self, file_id: str, litellm_parent_otel_span: Optional[Span], llm_router=None
) -> OpenAIFileObject:
stored_file_object = await self.get_unified_file_id(
file_id, litellm_parent_otel_span
)
if stored_file_object:
return stored_file_object.file_object
else:
# Case 1 : This is not a managed file
if not stored_file_object:
raise Exception(f"LiteLLM Managed File object with id={file_id} not found")
# Case 2: Managed file and the file object exists in the database
if stored_file_object and stored_file_object.file_object:
return stored_file_object.file_object
# Case 3: Managed file exists in the database but not the file object (for. e.g the batch task might not have run)
# So we fetch the file object from the provider. We deliberately do not store the result to avoid interfering with batch cost tracking code.
if not llm_router:
raise Exception(
f"LiteLLM Managed File object with id={file_id} has no file_object "
f"and llm_router is required to fetch from provider"
)
try:
model_id, model_file_id = next(iter(stored_file_object.model_mappings.items()))
credentials = llm_router.get_deployment_credentials_with_provider(model_id) or {}
response = await litellm.afile_retrieve(file_id=model_file_id, **credentials)
response.id = file_id # Replace with unified ID
return response
except Exception as e:
raise Exception(f"Failed to retrieve file {file_id} from provider: {str(e)}") from e
async def afile_list(
self,
@@ -868,10 +890,11 @@ class _PROXY_LiteLLMManagedFiles(CustomLogger, BaseFileEndpoints):
[file_id], litellm_parent_otel_span
)
delete_response = None
specific_model_file_id_mapping = model_file_id_mapping.get(file_id)
if specific_model_file_id_mapping:
for model_id, model_file_id in specific_model_file_id_mapping.items():
await llm_router.afile_delete(model=model_id, file_id=model_file_id, **data) # type: ignore
delete_response = await llm_router.afile_delete(model=model_id, file_id=model_file_id, **data) # type: ignore
stored_file_object = await self.delete_unified_file_id(
file_id, litellm_parent_otel_span
@@ -879,6 +902,9 @@ class _PROXY_LiteLLMManagedFiles(CustomLogger, BaseFileEndpoints):
if stored_file_object:
return stored_file_object
elif delete_response:
delete_response.id = file_id
return delete_response
else:
raise Exception(f"LiteLLM Managed File object with id={file_id} not found")
+17
View File
@@ -1285,3 +1285,20 @@ COROUTINE_CHECKER_MAX_SIZE_IN_MEMORY = int(
########################### RAG Text Splitter Constants ###########################
DEFAULT_CHUNK_SIZE = int(os.getenv("DEFAULT_CHUNK_SIZE", 1000))
DEFAULT_CHUNK_OVERLAP = int(os.getenv("DEFAULT_CHUNK_OVERLAP", 200))
########################### Microsoft SSO Constants ###########################
MICROSOFT_USER_EMAIL_ATTRIBUTE = str(
os.getenv("MICROSOFT_USER_EMAIL_ATTRIBUTE", "userPrincipalName")
)
MICROSOFT_USER_DISPLAY_NAME_ATTRIBUTE = str(
os.getenv("MICROSOFT_USER_DISPLAY_NAME_ATTRIBUTE", "displayName")
)
MICROSOFT_USER_ID_ATTRIBUTE = str(
os.getenv("MICROSOFT_USER_ID_ATTRIBUTE", "id")
)
MICROSOFT_USER_FIRST_NAME_ATTRIBUTE = str(
os.getenv("MICROSOFT_USER_FIRST_NAME_ATTRIBUTE", "givenName")
)
MICROSOFT_USER_LAST_NAME_ATTRIBUTE = str(
os.getenv("MICROSOFT_USER_LAST_NAME_ATTRIBUTE", "surname")
)
+34 -26
View File
@@ -719,32 +719,40 @@ class ModelResponseIterator:
content_block_start=content_block_start,
provider_specific_fields=provider_specific_fields,
)
elif (
content_block_start["content_block"]["type"]
== "web_search_tool_result"
):
# Capture web_search_tool_result for multi-turn reconstruction
# The full content comes in content_block_start, not in deltas
# See: https://github.com/BerriAI/litellm/issues/17737
self.web_search_results.append(
content_block_start["content_block"]
)
provider_specific_fields["web_search_results"] = (
self.web_search_results
)
elif (
content_block_start["content_block"]["type"]
== "web_fetch_tool_result"
):
# Capture web_fetch_tool_result for multi-turn reconstruction
# The full content comes in content_block_start, not in deltas
# Fixes: https://github.com/BerriAI/litellm/issues/18137
self.web_search_results.append(
content_block_start["content_block"]
)
provider_specific_fields["web_search_results"] = (
self.web_search_results
)
elif content_block_start["content_block"]["type"].endswith("_tool_result"):
# Handle all tool result types (web_search, bash_code_execution, text_editor, etc.)
content_type = content_block_start["content_block"]["type"]
# Special handling for web_search_tool_result for backwards compatibility
if content_type == "web_search_tool_result":
# Capture web_search_tool_result for multi-turn reconstruction
# The full content comes in content_block_start, not in deltas
# See: https://github.com/BerriAI/litellm/issues/17737
self.web_search_results.append(
content_block_start["content_block"]
)
provider_specific_fields["web_search_results"] = (
self.web_search_results
)
elif content_type == "web_fetch_tool_result":
# Capture web_fetch_tool_result for multi-turn reconstruction
# The full content comes in content_block_start, not in deltas
# Fixes: https://github.com/BerriAI/litellm/issues/18137
self.web_search_results.append(
content_block_start["content_block"]
)
provider_specific_fields["web_search_results"] = (
self.web_search_results
)
elif content_type != "tool_search_tool_result":
# Handle other tool results (code execution, etc.)
# Skip tool_search_tool_result as it's internal metadata
if not hasattr(self, "tool_results"):
self.tool_results = []
self.tool_results.append(content_block_start["content_block"])
provider_specific_fields["tool_results"] = self.tool_results
elif type_chunk == "content_block_stop":
ContentBlockStop(**chunk) # type: ignore
# check if tool call content block - only for tool_use and server_tool_use blocks
+27 -17
View File
@@ -1138,6 +1138,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
Optional[str],
List[ChatCompletionToolCallChunk],
Optional[List[Any]],
Optional[List[Any]],
]:
text_content = ""
citations: Optional[List[Any]] = None
@@ -1149,6 +1150,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
reasoning_content: Optional[str] = None
tool_calls: List[ChatCompletionToolCallChunk] = []
web_search_results: Optional[List[Any]] = None
tool_results: Optional[List[Any]] = None
for idx, content in enumerate(completion_response["content"]):
if content["type"] == "text":
text_content += content["text"]
@@ -1159,22 +1161,27 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
index=idx,
)
tool_calls.append(tool_call)
## TOOL SEARCH TOOL RESULT (skip - this is metadata about tool discovery)
elif content["type"] == "tool_search_tool_result":
# This block contains tool_references that were discovered
# We don't need to include this in the response as it's internal metadata
pass
## WEB SEARCH TOOL RESULT - preserve web search results for multi-turn conversations
elif content["type"] == "web_search_tool_result":
if web_search_results is None:
web_search_results = []
web_search_results.append(content)
## WEB FETCH TOOL RESULT - preserve web fetch results for multi-turn conversations
## Fixes: https://github.com/BerriAI/litellm/issues/18137
elif content["type"] == "web_fetch_tool_result":
if web_search_results is None:
web_search_results = []
web_search_results.append(content)
## TOOL RESULTS - handle all tool result types (code execution, etc.)
elif content["type"].endswith("_tool_result"):
# Skip tool_search_tool_result as it's internal metadata
if content["type"] == "tool_search_tool_result":
continue
# Handle web_search_tool_result separately for backwards compatibility
if content["type"] == "web_search_tool_result":
if web_search_results is None:
web_search_results = []
web_search_results.append(content)
elif content["type"] == "web_fetch_tool_result":
if web_search_results is None:
web_search_results = []
web_search_results.append(content)
else:
# All other tool results (bash_code_execution_tool_result, text_editor_code_execution_tool_result, etc.)
if tool_results is None:
tool_results = []
tool_results.append(content)
elif content.get("thinking", None) is not None:
if thinking_blocks is None:
thinking_blocks = []
@@ -1206,7 +1213,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
if thinking_content is not None:
reasoning_content += thinking_content
return text_content, citations, thinking_blocks, reasoning_content, tool_calls, web_search_results
return text_content, citations, thinking_blocks, reasoning_content, tool_calls, web_search_results, tool_results
def calculate_usage(
self,
@@ -1348,6 +1355,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
reasoning_content,
tool_calls,
web_search_results,
tool_results,
) = self.extract_response_content(completion_response=completion_response)
if (
@@ -1371,6 +1379,8 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig):
provider_specific_fields["context_management"] = context_management
if web_search_results is not None:
provider_specific_fields["web_search_results"] = web_search_results
if tool_results is not None:
provider_specific_fields["tool_results"] = tool_results
if container is not None:
provider_specific_fields["container"] = container
@@ -227,6 +227,7 @@ class BaseFileEndpoints(ABC):
self,
file_id: str,
litellm_parent_otel_span: Optional[Span],
llm_router: Optional[Router] = None,
) -> OpenAIFileObject:
pass
+7 -7
View File
@@ -4587,8 +4587,8 @@ def embedding( # noqa: PLR0915
or get_secret_str("OPENAI_API_KEY")
)
if extra_headers is not None:
optional_params["extra_headers"] = extra_headers
if headers is not None and headers != {}:
optional_params["extra_headers"] = headers
if encoding_format is not None:
optional_params["encoding_format"] = encoding_format
@@ -4656,8 +4656,8 @@ def embedding( # noqa: PLR0915
or get_secret_str("OPENAI_LIKE_API_KEY")
)
if extra_headers is not None:
optional_params["extra_headers"] = extra_headers
if headers is not None and headers != {}:
optional_params["extra_headers"] = headers
## EMBEDDING CALL
response = openai_like_embedding.embedding(
@@ -4681,9 +4681,9 @@ def embedding( # noqa: PLR0915
or litellm.api_key
)
if extra_headers is not None and isinstance(extra_headers, dict):
headers = extra_headers
else:
# Use the merged headers variable (already merged at the top of the function)
# Don't overwrite it with just extra_headers
if headers is None:
headers = {}
response = base_llm_http_handler.embedding(
@@ -1825,9 +1825,17 @@ class MCPServerManager:
if mcp_server.extra_headers and raw_headers:
if extra_headers is None:
extra_headers = {}
normalized_raw_headers = {
str(k).lower(): v for k, v in raw_headers.items() if isinstance(k, str)
}
for header in mcp_server.extra_headers:
if isinstance(header, str) and header in raw_headers:
extra_headers[header] = raw_headers[header]
if not isinstance(header, str):
continue
header_value = normalized_raw_headers.get(header.lower())
if header_value is None:
continue
extra_headers[header] = header_value
if mcp_server.static_headers:
if extra_headers is None:
@@ -715,9 +715,18 @@ if MCP_AVAILABLE:
if server.extra_headers and raw_headers:
if extra_headers is None:
extra_headers = {}
normalized_raw_headers = {
str(k).lower(): v for k, v in raw_headers.items() if isinstance(k, str)
}
for header in server.extra_headers:
if header in raw_headers:
extra_headers[header] = raw_headers[header]
if not isinstance(header, str):
continue
header_value = normalized_raw_headers.get(header.lower())
if header_value is None:
continue
extra_headers[header] = header_value
if server_auth_header is None:
server_auth_header = mcp_auth_header
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1979],{15143:function(e,n,r){Promise.resolve().then(r.bind(r,37492))},25512:function(e,n,r){"use strict";r.d(n,{P:function(){return l.Z},Q:function(){return t.Z}});var l=r(27281),t=r(43227)},37492:function(e,n,r){"use strict";r.r(n);var l=r(57437),t=r(66600),u=r(39760);n.default=()=>{let{token:e,accessToken:n,userRole:r,userId:i,premiumUser:o}=(0,u.Z)();return(0,l.jsx)(t.Z,{accessToken:n,token:e,userRole:r,userID:i,premiumUser:o})}},90246:function(e,n,r){"use strict";function l(e){let n=[e];return{all:n,lists:()=>[...n,"list"],list:e=>[...n,"list",{params:e}],details:()=>[...n,"detail"],detail:e=>[...n,"detail",e]}}r.d(n,{n:function(){return l}})},76191:function(e,n,r){"use strict";r.d(n,{p:function(){return i}});var l=r(19250),t=r(11713);let u=(0,r(90246).n)("uiConfig"),i=()=>(0,t.a)({queryKey:u.list({}),queryFn:async()=>await (0,l.getUiConfig)(),staleTime:864e5,gcTime:864e5})},39760:function(e,n,r){"use strict";var l=r(19250),t=r(3914),u=r(14474),i=r(99376),o=r(2265),a=r(76191);n.Z=()=>{var e,n,r,s,d,c;let m=(0,i.useRouter)(),{data:_,isLoading:p}=(0,a.p)(),f="undefined"!=typeof document?(0,t.e)("token"):null;(0,o.useEffect)(()=>{!p&&(!f||(null==_?void 0:_.admin_ui_disabled))&&m.replace("".concat((0,l.getProxyBaseUrl)(),"/ui/login"))},[f,m,p,_]);let v=(0,o.useMemo)(()=>{if(!f)return null;try{return(0,u.o)(f)}catch(e){return(0,t.b)(),m.replace("".concat((0,l.getProxyBaseUrl)(),"/ui/login")),null}},[f,m]);return{token:f,accessToken:null!==(e=null==v?void 0:v.key)&&void 0!==e?e:null,userId:null!==(n=null==v?void 0:v.user_id)&&void 0!==n?n:null,userEmail:null!==(r=null==v?void 0:v.user_email)&&void 0!==r?r:null,userRole:function(e){if(!e)return"Undefined Role";switch(e.toLowerCase()){case"app_owner":case"demo_app_owner":return"App Owner";case"app_admin":case"proxy_admin":return"Admin";case"proxy_admin_viewer":return"Admin Viewer";case"org_admin":return"Org Admin";case"internal_user":return"Internal User";case"internal_user_viewer":case"internal_viewer":return"Internal Viewer";case"app_user":return"App User";default:return"Unknown Role"}}(null!==(s=null==v?void 0:v.user_role)&&void 0!==s?s:null),premiumUser:null!==(d=null==v?void 0:v.premium_user)&&void 0!==d?d:null,disabledPersonalKeyCreation:null!==(c=null==v?void 0:v.disabled_non_admin_personal_key_creation)&&void 0!==c?c:null,showSSOBanner:(null==v?void 0:v.login_method)==="username_password"}}},10703:function(e,n,r){"use strict";r.d(n,{p:function(){return t}});var l=r(19250);let t=async e=>{try{let n=await (0,l.modelHubCall)(e);if(console.log("model_info:",n),(null==n?void 0:n.data.length)>0){let e=n.data.map(e=>({model_group:e.model_group,mode:null==e?void 0:e.mode}));return e.sort((e,n)=>e.model_group.localeCompare(n.model_group)),e}return[]}catch(e){throw console.error("Error fetching model info:",e),e}}},24199:function(e,n,r){"use strict";r.d(n,{Z:function(){return u}});var l=r(57437);r(2265);var t=r(30150),u=e=>{let{step:n=.01,style:r={width:"100%"},placeholder:u="Enter a numerical value",min:i,max:o,onChange:a,...s}=e;return(0,l.jsx)(t.Z,{onWheel:e=>e.currentTarget.blur(),step:n,style:r,placeholder:u,min:i,max:o,onChange:a,...s})}}},function(e){e.O(0,[1047,9028,9409,1713,4865,1442,2926,5333,1108,5733,9051,8049,6600,2971,2117,1744],function(){return e(e.s=15143)}),_N_E=e.O()}]);
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[1979],{90286:function(e,n,r){Promise.resolve().then(r.bind(r,37492))},25512:function(e,n,r){"use strict";r.d(n,{P:function(){return l.Z},Q:function(){return t.Z}});var l=r(27281),t=r(57365)},37492:function(e,n,r){"use strict";r.r(n);var l=r(57437),t=r(66600),u=r(39760);n.default=()=>{let{token:e,accessToken:n,userRole:r,userId:i,premiumUser:o}=(0,u.Z)();return(0,l.jsx)(t.Z,{accessToken:n,token:e,userRole:r,userID:i,premiumUser:o})}},90246:function(e,n,r){"use strict";function l(e){let n=[e];return{all:n,lists:()=>[...n,"list"],list:e=>[...n,"list",{params:e}],details:()=>[...n,"detail"],detail:e=>[...n,"detail",e]}}r.d(n,{n:function(){return l}})},76191:function(e,n,r){"use strict";r.d(n,{p:function(){return i}});var l=r(19250),t=r(11713);let u=(0,r(90246).n)("uiConfig"),i=()=>(0,t.a)({queryKey:u.list({}),queryFn:async()=>await (0,l.getUiConfig)(),staleTime:864e5,gcTime:864e5})},39760:function(e,n,r){"use strict";var l=r(19250),t=r(3914),u=r(14474),i=r(99376),o=r(2265),a=r(76191);n.Z=()=>{var e,n,r,s,d,c;let m=(0,i.useRouter)(),{data:_,isLoading:p}=(0,a.p)(),f="undefined"!=typeof document?(0,t.e)("token"):null;(0,o.useEffect)(()=>{!p&&(!f||(null==_?void 0:_.admin_ui_disabled))&&m.replace("".concat((0,l.getProxyBaseUrl)(),"/ui/login"))},[f,m,p,_]);let v=(0,o.useMemo)(()=>{if(!f)return null;try{return(0,u.o)(f)}catch(e){return(0,t.b)(),m.replace("".concat((0,l.getProxyBaseUrl)(),"/ui/login")),null}},[f,m]);return{token:f,accessToken:null!==(e=null==v?void 0:v.key)&&void 0!==e?e:null,userId:null!==(n=null==v?void 0:v.user_id)&&void 0!==n?n:null,userEmail:null!==(r=null==v?void 0:v.user_email)&&void 0!==r?r:null,userRole:function(e){if(!e)return"Undefined Role";switch(e.toLowerCase()){case"app_owner":case"demo_app_owner":return"App Owner";case"app_admin":case"proxy_admin":return"Admin";case"proxy_admin_viewer":return"Admin Viewer";case"org_admin":return"Org Admin";case"internal_user":return"Internal User";case"internal_user_viewer":case"internal_viewer":return"Internal Viewer";case"app_user":return"App User";default:return"Unknown Role"}}(null!==(s=null==v?void 0:v.user_role)&&void 0!==s?s:null),premiumUser:null!==(d=null==v?void 0:v.premium_user)&&void 0!==d?d:null,disabledPersonalKeyCreation:null!==(c=null==v?void 0:v.disabled_non_admin_personal_key_creation)&&void 0!==c?c:null,showSSOBanner:(null==v?void 0:v.login_method)==="username_password"}}},10703:function(e,n,r){"use strict";r.d(n,{p:function(){return t}});var l=r(19250);let t=async e=>{try{let n=await (0,l.modelHubCall)(e);if(console.log("model_info:",n),(null==n?void 0:n.data.length)>0){let e=n.data.map(e=>({model_group:e.model_group,mode:null==e?void 0:e.mode}));return e.sort((e,n)=>e.model_group.localeCompare(n.model_group)),e}return[]}catch(e){throw console.error("Error fetching model info:",e),e}}},24199:function(e,n,r){"use strict";r.d(n,{Z:function(){return u}});var l=r(57437);r(2265);var t=r(30150),u=e=>{let{step:n=.01,style:r={width:"100%"},placeholder:u="Enter a numerical value",min:i,max:o,onChange:a,...s}=e;return(0,l.jsx)(t.Z,{onWheel:e=>e.currentTarget.blur(),step:n,style:r,placeholder:u,min:i,max:o,onChange:a,...s})}}},function(e){e.O(0,[1047,9028,9409,1713,4865,1442,2926,5333,1108,5733,9051,8049,6600,2971,2117,1744],function(){return e(e.s=90286)}),_N_E=e.O()}]);
@@ -1 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[6061],{38997:function(n,t,u){Promise.resolve().then(u.bind(u,21933))},9513:function(n,t,u){"use strict";u.d(t,{Ct:function(){return r.Z},JO:function(){return c.Z},RM:function(){return o.Z},SC:function(){return a.Z},iA:function(){return i.Z},pj:function(){return f.Z},ss:function(){return s.Z},xs:function(){return Z.Z},xv:function(){return l.Z},zx:function(){return e.Z}});var r=u(41649),e=u(78489),c=u(47323),i=u(21626),o=u(97214),f=u(28241),s=u(58834),Z=u(69552),a=u(71876),l=u(84264)},45822:function(n,t,u){"use strict";u.d(t,{JO:function(){return i.Z},JX:function(){return e.Z},rj:function(){return c.Z},xv:function(){return o.Z},zx:function(){return r.Z}});var r=u(78489),e=u(49804),c=u(67101),i=u(47323),o=u(84264)},21933:function(n,t,u){"use strict";u.r(t);var r=u(57437),e=u(39145),c=u(39760);t.default=()=>{let{accessToken:n,userId:t,userRole:u}=(0,c.Z)();return(0,r.jsx)(e.Z,{accessToken:n,userID:t,userRole:u})}}},function(n){n.O(0,[1047,9028,9409,1713,4865,337,8135,1442,2409,3367,3709,353,1994,7138,2068,8565,5319,5333,8582,6609,4242,789,8049,5144,2202,9640,9145,2971,2117,1744],function(){return n(n.s=38997)}),_N_E=n.O()}]);
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[6061],{86947:function(n,t,u){Promise.resolve().then(u.bind(u,21933))},9513:function(n,t,u){"use strict";u.d(t,{Ct:function(){return r.Z},JO:function(){return c.Z},RM:function(){return o.Z},SC:function(){return a.Z},iA:function(){return i.Z},pj:function(){return f.Z},ss:function(){return s.Z},xs:function(){return Z.Z},xv:function(){return l.Z},zx:function(){return e.Z}});var r=u(41649),e=u(78489),c=u(47323),i=u(21626),o=u(97214),f=u(28241),s=u(58834),Z=u(69552),a=u(71876),l=u(84264)},45822:function(n,t,u){"use strict";u.d(t,{JO:function(){return i.Z},JX:function(){return e.Z},rj:function(){return c.Z},xv:function(){return o.Z},zx:function(){return r.Z}});var r=u(78489),e=u(49804),c=u(67101),i=u(47323),o=u(84264)},21933:function(n,t,u){"use strict";u.r(t);var r=u(57437),e=u(39145),c=u(39760);t.default=()=>{let{accessToken:n,userId:t,userRole:u}=(0,c.Z)();return(0,r.jsx)(e.Z,{accessToken:n,userID:t,userRole:u})}}},function(n){n.O(0,[1047,9028,9409,1713,4865,337,8135,1442,2409,3367,3709,353,1994,7138,2068,8565,5319,5333,8582,6609,4242,789,8049,5144,2202,9640,9145,2971,2117,1744],function(){return n(n.s=86947)}),_N_E=n.O()}]);
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More