From c36d0f667bb9cdce0e61b61fbc52f1c10fcfb648 Mon Sep 17 00:00:00 2001 From: John Tong <45779362+jtong99@users.noreply.github.com> Date: Sat, 21 Jun 2025 06:20:37 +0930 Subject: [PATCH] Feat/add delete callback (#11654) * feat(delete callback): add api and ui for deleting logging callbacks * hotfix(delete callback): hotfix lint error * move test case to test_litellm folder * fix test case to use mock prisma client --- litellm/proxy/_types.py | 3 + litellm/proxy/proxy_server.py | 89 ++ .../test_delete_callbacks_endpoint.py | 176 ++++ .../src/components/networking.tsx | 894 +++++++++++------- .../src/components/settings.tsx | 59 +- 5 files changed, 862 insertions(+), 359 deletions(-) create mode 100644 tests/test_litellm/proxy/management_endpoints/test_delete_callbacks_endpoint.py diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index e214da35d8b..a040d7fe6fe 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -1463,6 +1463,9 @@ class ConfigFieldDelete(LiteLLMPydanticObjectBase): config_type: Literal["general_settings"] field_name: str +class CallbackDelete(LiteLLMPydanticObjectBase): + callback_name: str + class FieldDetail(BaseModel): field_name: str diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index ef765d89b47..3be280ad778 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -8010,6 +8010,95 @@ async def delete_config_general_settings( return response +@router.post( + "/config/callback/delete", + tags=["config.yaml"], + dependencies=[Depends(user_api_key_auth)], + include_in_schema=False, +) +async def delete_callback( + data: CallbackDelete, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """ + Delete specific logging callback from configuration. + """ + global prisma_client, proxy_config + + if prisma_client is None: + raise HTTPException( + status_code=400, + detail={"error": CommonProxyErrors.db_not_connected_error.value}, + ) + + if user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN: + raise HTTPException( + status_code=400, + detail={ + "error": "{}, your role={}".format( + CommonProxyErrors.not_allowed_access.value, + user_api_key_dict.user_role, + ) + }, + ) + + if store_model_in_db is not True: + raise HTTPException( + status_code=500, + detail={ + "error": "Set `'STORE_MODEL_IN_DB='True'` in your env to enable this feature." + }, + ) + + try: + # Get current configuration + config = await proxy_config.get_config() + callback_name = data.callback_name.lower() + + # Check if callback exists in current configuration + litellm_settings = config.get("litellm_settings", {}) + success_callbacks = litellm_settings.get("success_callback", []) + + if callback_name not in success_callbacks: + raise HTTPException( + status_code=404, + detail={"error": f"Callback '{callback_name}' not found in active configuration"}, + ) + + # Remove callback from success_callback list + success_callbacks.remove(callback_name) + config.setdefault("litellm_settings", {})["success_callback"] = success_callbacks + + # Save the updated configuration + await proxy_config.save_config(new_config=config) + + # Restart the proxy to apply changes + await proxy_config.add_deployment( + prisma_client=prisma_client, proxy_logging_obj=proxy_logging_obj + ) + + return { + "message": f"Successfully deleted callback: {callback_name}", + "removed_callback": callback_name, + "remaining_callbacks": success_callbacks, + "deleted_at": datetime.now().isoformat(), + } + + except HTTPException: + raise + except Exception as e: + verbose_proxy_logger.error( + f"litellm.proxy.proxy_server.delete_callback(): Exception occurred - {str(e)}" + ) + verbose_proxy_logger.debug(traceback.format_exc()) + raise ProxyException( + message="Error deleting callback: " + str(e), + type=ProxyErrorTypes.internal_server_error, + param="callback_name", + code=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + + @router.get( "/get/config/callbacks", tags=["config.yaml"], diff --git a/tests/test_litellm/proxy/management_endpoints/test_delete_callbacks_endpoint.py b/tests/test_litellm/proxy/management_endpoints/test_delete_callbacks_endpoint.py new file mode 100644 index 00000000000..4d6a81b481a --- /dev/null +++ b/tests/test_litellm/proxy/management_endpoints/test_delete_callbacks_endpoint.py @@ -0,0 +1,176 @@ +import os +import sys +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest +from fastapi.testclient import TestClient + +sys.path.insert( + 0, os.path.abspath("../../../..") +) + +from litellm.proxy._types import CallbackDelete, ConfigYAML, LitellmUserRoles, UserAPIKeyAuth +from litellm.proxy.proxy_server import app + +client = TestClient(app) + + +class MockPrismaClient: + def __init__(self): + self.db = MagicMock() + self.config_data = { + "litellm_settings": {"success_callback": ["langfuse"]}, + "environment_variables": { + "LANGFUSE_PUBLIC_KEY": "any-public-key", + "LANGFUSE_SECRET_KEY": "any-secret-key", + "LANGFUSE_HOST": "https://exampleopenaiendpoint-production-c715.up.railway.app", + }, + } + + # Mock the config update/upsert + self.db.litellm_config.upsert = AsyncMock() + + # Mock config retrieval for get_config/callbacks + self.db.litellm_config.find_first = AsyncMock( + side_effect=self._mock_find_first + ) + + # Mock for get_generic_data + self.get_generic_data = AsyncMock(side_effect=self._mock_get_generic_data) + + # Mock insert_data method (required by delete_callback endpoint) + self.insert_data = AsyncMock(return_value=MagicMock()) + + # Mock jsonify_object method (required by config endpoints) + self.jsonify_object = lambda obj: obj + + async def _mock_find_first(self, where=None): + """Mock find_first to return config data based on param_name""" + if where and "param_name" in where: + param_name = where["param_name"] + if param_name == "litellm_settings": + return MagicMock( + param_name="litellm_settings", + param_value=self.config_data["litellm_settings"] + ) + elif param_name == "environment_variables": + return MagicMock( + param_name="environment_variables", + param_value=self.config_data["environment_variables"] + ) + return None + + async def _mock_get_generic_data(self, key=None, value=None, table_name=None): + """Mock get_generic_data for _update_config_from_db""" + if key == "param_name" and table_name == "config": + if value == "litellm_settings": + return MagicMock( + param_name="litellm_settings", + param_value=self.config_data["litellm_settings"] + ) + elif value == "environment_variables": + return MagicMock( + param_name="environment_variables", + param_value=self.config_data["environment_variables"] + ) + elif value in ["general_settings", "router_settings"]: + return None + return None + + def remove_callback_from_config(self, callback_name): + """Remove callback from the mock config""" + if "success_callback" in self.config_data["litellm_settings"]: + callbacks = self.config_data["litellm_settings"]["success_callback"] + if callback_name in callbacks: + callbacks.remove(callback_name) + + +@pytest.fixture +def mock_auth(): + """Mock admin user authentication""" + return UserAPIKeyAuth( + user_id="test_admin", + user_role=LitellmUserRoles.PROXY_ADMIN, + api_key="sk-1234" + ) + + +@pytest.fixture +def mock_prisma(): + """Mock prisma client""" + return MockPrismaClient() + + +def mock_encrypt_value_helper(value): + """Mock encryption - just return the value as-is for testing""" + return value + +def mock_decrypt_value_helper(value): + """Mock decryption - just return the value as-is for testing""" + return value + + +@pytest.mark.asyncio +async def test_delete_callbacks_in_db(mock_prisma, mock_auth): + + with patch("litellm.proxy.proxy_server.prisma_client", mock_prisma), \ + patch("litellm.proxy.proxy_server.store_model_in_db", True), \ + patch("litellm.proxy.proxy_server.encrypt_value_helper", side_effect=mock_encrypt_value_helper), \ + patch("litellm.proxy.proxy_server.decrypt_value_helper", side_effect=mock_decrypt_value_helper): + + # Override auth dependency + app.dependency_overrides[ + lambda: __import__("litellm.proxy.proxy_server", fromlist=["user_api_key_auth"]).user_api_key_auth + ] = lambda: mock_auth + + # Add langfuse callback to DB via /config/update + config_data = { + "litellm_settings": {"success_callback": ["langfuse"]}, + "environment_variables": { + "LANGFUSE_PUBLIC_KEY": "any-public-key", + "LANGFUSE_SECRET_KEY": "any-secret-key", + "LANGFUSE_HOST": "https://exampleopenaiendpoint-production-c715.up.railway.app", + }, + } + + config_response = client.post( + "/config/update", + json=config_data, + headers={"Authorization": "Bearer sk-1234"} + ) + assert config_response.status_code == 200 + + # Delete the langfuse callback + delete_data = {"callback_name": "langfuse"} + delete_response = client.post( + "/config/callback/delete", + json=delete_data, + headers={"Authorization": "Bearer sk-1234"} + ) + + assert delete_response.status_code == 200 + delete_result = delete_response.json() + + # Verify delete response + assert "message" in delete_result + assert "langfuse" in delete_result.get("removed_callback", "") + assert "langfuse" not in delete_result.get("remaining_callbacks", []) + + # Update mock to reflect deletion for get_config test + mock_prisma.remove_callback_from_config("langfuse") + + # Get config and verify callback is deleted + config_response = client.get( + "/get/config/callbacks", + headers={"Authorization": "Bearer sk-1234"} + ) + + assert config_response.status_code == 200 + config_data = config_response.json() + + # Verify callback is removed from the config + callback_names = [callback["name"] for callback in config_data.get("callbacks", [])] + assert "langfuse" not in callback_names + + # Clean up + app.dependency_overrides.clear() \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index ec377bf3b4c..a7190d49a3b 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -3,10 +3,20 @@ */ import { all_admin_roles } from "@/utils/roles"; import { message } from "antd"; -import { TagNewRequest, TagUpdateRequest, TagDeleteRequest, TagInfoRequest, TagListResponse, TagInfoResponse } from "./tag_management/types"; +import { + TagNewRequest, + TagUpdateRequest, + TagDeleteRequest, + TagInfoRequest, + TagListResponse, + TagInfoResponse, +} from "./tag_management/types"; import { Team } from "./key_team_helpers/key_list"; import { UserInfo } from "./view_users/types"; -import { EmailEventSettingsResponse, EmailEventSettingsUpdateRequest } from "./email_events/types"; +import { + EmailEventSettingsResponse, + EmailEventSettingsUpdateRequest, +} from "./email_events/types"; const isLocal = process.env.NODE_ENV === "development"; export const defaultProxyBaseUrl = isLocal ? "http://localhost:4000" : null; @@ -14,18 +24,27 @@ const defaultServerRootPath = "/"; export let serverRootPath = defaultServerRootPath; export let proxyBaseUrl = defaultProxyBaseUrl; if (isLocal != true) { - console.log = function() {}; + console.log = function () {}; } -const updateProxyBaseUrl = (serverRootPath: string, receivedProxyBaseUrl: string | null = null) => { +const updateProxyBaseUrl = ( + serverRootPath: string, + receivedProxyBaseUrl: string | null = null +) => { /** * Special function for updating the proxy base url. Should only be called by getUiConfig. */ - const defaultProxyBaseUrl = isLocal ? "http://localhost:4000" : window.location.origin; + const defaultProxyBaseUrl = isLocal + ? "http://localhost:4000" + : window.location.origin; let initialProxyBaseUrl = receivedProxyBaseUrl || defaultProxyBaseUrl; console.log("proxyBaseUrl:", proxyBaseUrl); console.log("serverRootPath:", serverRootPath); - if (serverRootPath.length > 0 && !initialProxyBaseUrl.endsWith(serverRootPath) && serverRootPath != "/") { + if ( + serverRootPath.length > 0 && + !initialProxyBaseUrl.endsWith(serverRootPath) && + serverRootPath != "/" + ) { initialProxyBaseUrl += serverRootPath; proxyBaseUrl = initialProxyBaseUrl; } @@ -37,7 +56,7 @@ const updateServerRootPath = (receivedServerRootPath: string) => { }; export const getProxyBaseUrl = (): string => { - return proxyBaseUrl ? proxyBaseUrl : window.location.origin; + return proxyBaseUrl ? proxyBaseUrl : window.location.origin; }; const HTTP_REQUEST = { @@ -55,7 +74,6 @@ export interface Model { model_info: Object | null; } - export interface Organization { organization_id: string | null; organization_alias: string; @@ -68,7 +86,7 @@ export interface Organization { created_by: string; updated_at: string; updated_by: string; - litellm_budget_table: any; // Simplified to any since we don't need the detailed structure + litellm_budget_table: any; // Simplified to any since we don't need the detailed structure teams: any[] | null; users: any[] | null; members: any[] | null; @@ -100,14 +118,15 @@ export interface CredentialsResponse { let lastErrorTime = 0; - const handleError = async (errorData: string) => { const currentTime = Date.now(); - if (currentTime - lastErrorTime > 60000) { // 60000 milliseconds = 60 seconds + if (currentTime - lastErrorTime > 60000) { + // 60000 milliseconds = 60 seconds if (errorData.includes("Authentication Error - Expired Key")) { message.info("UI Session Expired. Logging out."); lastErrorTime = currentTime; - document.cookie = "token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; + document.cookie = + "token=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;"; window.location.href = window.location.pathname; } lastErrorTime = currentTime; @@ -116,12 +135,13 @@ const handleError = async (errorData: string) => { } }; - // Global variable for the header name -let globalLitellmHeaderName: string = "Authorization"; +let globalLitellmHeaderName: string = "Authorization"; // Function to set the global header name -export function setGlobalLitellmHeaderName(headerName: string = "Authorization") { +export function setGlobalLitellmHeaderName( + headerName: string = "Authorization" +) { console.log(`setGlobalLitellmHeaderName: ${headerName}`); globalLitellmHeaderName = headerName; } @@ -129,7 +149,9 @@ export function setGlobalLitellmHeaderName(headerName: string = "Authorization") export const getUiConfig = async () => { console.log("Getting UI config"); /**Special route to get the proxy base url and server root path */ - const url = defaultProxyBaseUrl ? `${defaultProxyBaseUrl}/litellm/.well-known/litellm-ui-config` : `/litellm/.well-known/litellm-ui-config`; + const url = defaultProxyBaseUrl + ? `${defaultProxyBaseUrl}/litellm/.well-known/litellm-ui-config` + : `/litellm/.well-known/litellm-ui-config`; const response = await fetch(url); const jsonData: LiteLLMWellKnownUiConfig = await response.json(); /** @@ -138,29 +160,27 @@ export const getUiConfig = async () => { console.log("jsonData in getUiConfig:", jsonData); updateProxyBaseUrl(jsonData.server_root_path, jsonData.proxy_base_url); return jsonData; -} +}; export const getOpenAPISchema = async () => { const url = proxyBaseUrl ? `${proxyBaseUrl}/openapi.json` : `/openapi.json`; const response = await fetch(url); const jsonData = await response.json(); return jsonData; -} +}; -export const modelCostMap = async ( - accessToken: string, -) => { +export const modelCostMap = async (accessToken: string) => { try { - const url = proxyBaseUrl ? `${proxyBaseUrl}/get/litellm_model_cost_map` : `/get/litellm_model_cost_map`; - const response = await fetch( - url, { - method: "GET", - headers: { - [globalLitellmHeaderName]: `Bearer ${accessToken}`, - "Content-Type": "application/json", - }, - } - ); + const url = proxyBaseUrl + ? `${proxyBaseUrl}/get/litellm_model_cost_map` + : `/get/litellm_model_cost_map`; + const response = await fetch(url, { + method: "GET", + headers: { + [globalLitellmHeaderName]: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + }); const jsonData = await response.json(); console.log(`received litellm model cost data: ${jsonData}`); return jsonData; @@ -188,22 +208,20 @@ export const modelCreateCall = async ( if (!response.ok) { const errorData = await response.text(); - const errorMsg = - errorData|| - "Network response was not ok"; + const errorMsg = errorData || "Network response was not ok"; message.error(errorMsg); throw new Error(errorMsg); } const data = await response.json(); console.log("API Response:", data); - + // Close any existing messages before showing new ones message.destroy(); - + // Sequential success messages message.success(`Model ${formValues.model_name} created successfully`, 2); - + return data; } catch (error) { console.error("Failed to create key:", error); @@ -363,7 +381,9 @@ export const budgetUpdateCall = async ( console.log("Form Values in budgetUpdateCall:", formValues); // Log the form values before making the API call console.log("Form Values after check:", formValues); - const url = proxyBaseUrl ? `${proxyBaseUrl}/budget/update` : `/budget/update`; + const url = proxyBaseUrl + ? `${proxyBaseUrl}/budget/update` + : `/budget/update`; const response = await fetch(url, { method: "POST", headers: { @@ -660,7 +680,10 @@ export const keyDeleteCall = async (accessToken: String, user_key: String) => { } }; -export const userDeleteCall = async (accessToken: string, userIds: string[]) => { +export const userDeleteCall = async ( + accessToken: string, + userIds: string[] +) => { try { const url = proxyBaseUrl ? `${proxyBaseUrl}/user/delete` : `/user/delete`; console.log("in userDeleteCall:", userIds); @@ -692,7 +715,6 @@ export const userDeleteCall = async (accessToken: string, userIds: string[]) => } }; - export const teamDeleteCall = async (accessToken: String, teamID: String) => { try { const url = proxyBaseUrl ? `${proxyBaseUrl}/team/delete` : `/team/delete`; @@ -724,15 +746,15 @@ export const teamDeleteCall = async (accessToken: String, teamID: String) => { }; export type UserListResponse = { - page: number, - page_size: number, - total: number, - total_pages: number, - users: UserInfo[] -} + page: number; + page_size: number; + total: number; + total_pages: number; + users: UserInfo[]; +}; export const userListCall = async ( - accessToken: String, + accessToken: String, userIDs: string[] | null = null, page: number | null = null, page_size: number | null = null, @@ -741,7 +763,7 @@ export const userListCall = async ( team: string | null = null, sso_user_id: string | null = null, sortBy: string | null = null, - sortOrder: 'asc' | 'desc' | null = null, + sortOrder: "asc" | "desc" | null = null ) => { /** * Get all available teams on proxy @@ -750,45 +772,45 @@ export const userListCall = async ( let url = proxyBaseUrl ? `${proxyBaseUrl}/user/list` : `/user/list`; console.log("in userListCall"); const queryParams = new URLSearchParams(); - + if (userIDs && userIDs.length > 0) { // Convert array to comma-separated string - const userIDsString = userIDs.join(','); - queryParams.append('user_ids', userIDsString); + const userIDsString = userIDs.join(","); + queryParams.append("user_ids", userIDsString); } - + if (page) { - queryParams.append('page', page.toString()); + queryParams.append("page", page.toString()); } - + if (page_size) { - queryParams.append('page_size', page_size.toString()); + queryParams.append("page_size", page_size.toString()); } if (userEmail) { - queryParams.append('user_email', userEmail); + queryParams.append("user_email", userEmail); } if (userRole) { - queryParams.append('role', userRole); + queryParams.append("role", userRole); } if (team) { - queryParams.append('team', team); + queryParams.append("team", team); } if (sso_user_id) { - queryParams.append('sso_user_ids', sso_user_id); + queryParams.append("sso_user_ids", sso_user_id); } if (sortBy) { - queryParams.append('sort_by', sortBy); + queryParams.append("sort_by", sortBy); } if (sortOrder) { - queryParams.append('sort_order', sortOrder); + queryParams.append("sort_order", sortOrder); } - + const queryString = queryParams.toString(); if (queryString) { url += `?${queryString}`; @@ -808,7 +830,7 @@ export const userListCall = async ( throw new Error("Network response was not ok"); } - const data = await response.json() as UserListResponse; + const data = (await response.json()) as UserListResponse; console.log("/user/list API Response:", data); return data; // Handle success - you might want to update some state or UI based on the created key @@ -818,8 +840,6 @@ export const userListCall = async ( } }; - - export const userInfoCall = async ( accessToken: String, userID: String | null, @@ -829,22 +849,28 @@ export const userInfoCall = async ( page_size: number | null, lookup_user_id: boolean = false ) => { - console.log(`userInfoCall: ${userID}, ${userRole}, ${viewAll}, ${page}, ${page_size}, ${lookup_user_id}`) + console.log( + `userInfoCall: ${userID}, ${userRole}, ${viewAll}, ${page}, ${page_size}, ${lookup_user_id}` + ); try { let url: string; - + if (viewAll) { // Use /user/list endpoint when viewAll is true url = proxyBaseUrl ? `${proxyBaseUrl}/user/list` : `/user/list`; const queryParams = new URLSearchParams(); - if (page != null) queryParams.append('page', page.toString()); - if (page_size != null) queryParams.append('page_size', page_size.toString()); + if (page != null) queryParams.append("page", page.toString()); + if (page_size != null) + queryParams.append("page_size", page_size.toString()); url += `?${queryParams.toString()}`; } else { // Use /user/info endpoint for individual user info url = proxyBaseUrl ? `${proxyBaseUrl}/user/info` : `/user/info`; - if ((userRole === "Admin" || userRole === "Admin Viewer") && !lookup_user_id) { - // do nothing + if ( + (userRole === "Admin" || userRole === "Admin Viewer") && + !lookup_user_id + ) { + // do nothing } else if (userID) { url += `?user_id=${userID}`; } @@ -917,7 +943,7 @@ type TeamListResponse = { }; export const v2TeamListCall = async ( - accessToken: String, + accessToken: String, organizationID: string | null, userID: String | null = null, teamID: string | null = null, @@ -925,7 +951,7 @@ export const v2TeamListCall = async ( page: number = 1, page_size: number = 10, sort_by: string | null = null, - sort_order: 'asc' | 'desc' | null = null, + sort_order: "asc" | "desc" | null = null ): Promise => { /** * Get list of teams with filtering and sorting options @@ -934,23 +960,23 @@ export const v2TeamListCall = async ( let url = proxyBaseUrl ? `${proxyBaseUrl}/v2/team/list` : `/v2/team/list`; console.log("in teamInfoCall"); const queryParams = new URLSearchParams(); - + if (userID) { - queryParams.append('user_id', userID.toString()); + queryParams.append("user_id", userID.toString()); } - + if (organizationID) { - queryParams.append('organization_id', organizationID.toString()); + queryParams.append("organization_id", organizationID.toString()); } if (teamID) { - queryParams.append('team_id', teamID.toString()); + queryParams.append("team_id", teamID.toString()); } if (team_alias) { - queryParams.append('team_alias', team_alias.toString()); + queryParams.append("team_alias", team_alias.toString()); } - + const queryString = queryParams.toString(); if (queryString) { url += `?${queryString}`; @@ -980,13 +1006,12 @@ export const v2TeamListCall = async ( } }; - export const teamListCall = async ( - accessToken: String, + accessToken: String, organizationID: string | null, userID: String | null = null, teamID: string | null = null, - team_alias: string | null = null, + team_alias: string | null = null ) => { /** * Get all available teams on proxy @@ -995,23 +1020,23 @@ export const teamListCall = async ( let url = proxyBaseUrl ? `${proxyBaseUrl}/team/list` : `/team/list`; console.log("in teamInfoCall"); const queryParams = new URLSearchParams(); - + if (userID) { - queryParams.append('user_id', userID.toString()); + queryParams.append("user_id", userID.toString()); } - + if (organizationID) { - queryParams.append('organization_id', organizationID.toString()); + queryParams.append("organization_id", organizationID.toString()); } if (teamID) { - queryParams.append('team_id', teamID.toString()); + queryParams.append("team_id", teamID.toString()); } if (team_alias) { - queryParams.append('team_alias', team_alias.toString()); + queryParams.append("team_alias", team_alias.toString()); } - + const queryString = queryParams.toString(); if (queryString) { url += `?${queryString}`; @@ -1041,15 +1066,14 @@ export const teamListCall = async ( } }; - -export const availableTeamListCall = async ( - accessToken: String, -) => { +export const availableTeamListCall = async (accessToken: String) => { /** * Get all available teams on proxy */ try { - let url = proxyBaseUrl ? `${proxyBaseUrl}/team/available` : `/team/available`; + let url = proxyBaseUrl + ? `${proxyBaseUrl}/team/available` + : `/team/available`; console.log("in availableTeamListCall"); const response = await fetch(url, { method: "GET", @@ -1078,7 +1102,9 @@ export const organizationListCall = async (accessToken: String) => { * Get all organizations on proxy */ try { - let url = proxyBaseUrl ? `${proxyBaseUrl}/organization/list` : `/organization/list`; + let url = proxyBaseUrl + ? `${proxyBaseUrl}/organization/list` + : `/organization/list`; const response = await fetch(url, { method: "GET", headers: { @@ -1106,7 +1132,9 @@ export const organizationInfoCall = async ( organizationID: String ) => { try { - let url = proxyBaseUrl ? `${proxyBaseUrl}/organization/info` : `/organization/info`; + let url = proxyBaseUrl + ? `${proxyBaseUrl}/organization/info` + : `/organization/info`; if (organizationID) { url = `${url}?organization_id=${organizationID}`; } @@ -1153,7 +1181,9 @@ export const organizationCreateCall = async ( } } - const url = proxyBaseUrl ? `${proxyBaseUrl}/organization/new` : `/organization/new`; + const url = proxyBaseUrl + ? `${proxyBaseUrl}/organization/new` + : `/organization/new`; const response = await fetch(url, { method: "POST", headers: { @@ -1189,7 +1219,9 @@ export const organizationUpdateCall = async ( try { console.log("Form Values in organizationUpdateCall:", formValues); // Log the form values before making the API call - const url = proxyBaseUrl ? `${proxyBaseUrl}/organization/update` : `/organization/update`; + const url = proxyBaseUrl + ? `${proxyBaseUrl}/organization/update` + : `/organization/update`; const response = await fetch(url, { method: "PATCH", headers: { @@ -1217,13 +1249,14 @@ export const organizationUpdateCall = async ( } }; - export const organizationDeleteCall = async ( accessToken: string, organizationID: string ) => { try { - const url = proxyBaseUrl ? `${proxyBaseUrl}/organization/delete` : `/organization/delete`; + const url = proxyBaseUrl + ? `${proxyBaseUrl}/organization/delete` + : `/organization/delete`; const response = await fetch(url, { method: "DELETE", headers: { @@ -1231,7 +1264,7 @@ export const organizationDeleteCall = async ( "Content-Type": "application/json", }, body: JSON.stringify({ - organization_ids: [organizationID] + organization_ids: [organizationID], }), }); @@ -1249,13 +1282,18 @@ export const organizationDeleteCall = async ( } }; -export const transformRequestCall = async (accessToken: String, request: object) => { +export const transformRequestCall = async ( + accessToken: String, + request: object +) => { /** * Transform request */ try { - let url = proxyBaseUrl ? `${proxyBaseUrl}/utils/transform_request` : `/utils/transform_request`; + let url = proxyBaseUrl + ? `${proxyBaseUrl}/utils/transform_request` + : `/utils/transform_request`; const response = await fetch(url, { method: "POST", @@ -1278,21 +1316,26 @@ export const transformRequestCall = async (accessToken: String, request: object) console.error("Failed to create key:", error); throw error; } - - -} +}; -export const userDailyActivityCall = async (accessToken: String, startTime: Date, endTime: Date, page: number = 1) => { +export const userDailyActivityCall = async ( + accessToken: String, + startTime: Date, + endTime: Date, + page: number = 1 +) => { /** * Get daily user activity on proxy */ try { - let url = proxyBaseUrl ? `${proxyBaseUrl}/user/daily/activity` : `/user/daily/activity`; + let url = proxyBaseUrl + ? `${proxyBaseUrl}/user/daily/activity` + : `/user/daily/activity`; const queryParams = new URLSearchParams(); - queryParams.append('start_date', startTime.toISOString()); - queryParams.append('end_date', endTime.toISOString()); - queryParams.append('page_size', '1000'); - queryParams.append('page', page.toString()); + queryParams.append("start_date", startTime.toISOString()); + queryParams.append("end_date", endTime.toISOString()); + queryParams.append("page_size", "1000"); + queryParams.append("page", page.toString()); const queryString = queryParams.toString(); if (queryString) { url += `?${queryString}`; @@ -1320,19 +1363,27 @@ export const userDailyActivityCall = async (accessToken: String, startTime: Date } }; -export const tagDailyActivityCall = async (accessToken: String, startTime: Date, endTime: Date, page: number = 1, tags: string[] | null = null) => { +export const tagDailyActivityCall = async ( + accessToken: String, + startTime: Date, + endTime: Date, + page: number = 1, + tags: string[] | null = null +) => { /** * Get daily user activity on proxy */ try { - let url = proxyBaseUrl ? `${proxyBaseUrl}/tag/daily/activity` : `/tag/daily/activity`; + let url = proxyBaseUrl + ? `${proxyBaseUrl}/tag/daily/activity` + : `/tag/daily/activity`; const queryParams = new URLSearchParams(); - queryParams.append('start_date', startTime.toISOString()); - queryParams.append('end_date', endTime.toISOString()); - queryParams.append('page_size', '1000'); - queryParams.append('page', page.toString()); + queryParams.append("start_date", startTime.toISOString()); + queryParams.append("end_date", endTime.toISOString()); + queryParams.append("page_size", "1000"); + queryParams.append("page", page.toString()); if (tags) { - queryParams.append('tags', tags.join(',')); + queryParams.append("tags", tags.join(",")); } const queryString = queryParams.toString(); if (queryString) { @@ -1361,21 +1412,29 @@ export const tagDailyActivityCall = async (accessToken: String, startTime: Date, } }; -export const teamDailyActivityCall = async (accessToken: String, startTime: Date, endTime: Date, page: number = 1, teamIds: string[] | null = null) => { +export const teamDailyActivityCall = async ( + accessToken: String, + startTime: Date, + endTime: Date, + page: number = 1, + teamIds: string[] | null = null +) => { /** * Get daily user activity on proxy */ try { - let url = proxyBaseUrl ? `${proxyBaseUrl}/team/daily/activity` : `/team/daily/activity`; + let url = proxyBaseUrl + ? `${proxyBaseUrl}/team/daily/activity` + : `/team/daily/activity`; const queryParams = new URLSearchParams(); - queryParams.append('start_date', startTime.toISOString()); - queryParams.append('end_date', endTime.toISOString()); - queryParams.append('page_size', '1000'); - queryParams.append('page', page.toString()); + queryParams.append("start_date", startTime.toISOString()); + queryParams.append("end_date", endTime.toISOString()); + queryParams.append("page_size", "1000"); + queryParams.append("page", page.toString()); if (teamIds) { - queryParams.append('team_ids', teamIds.join(',')); + queryParams.append("team_ids", teamIds.join(",")); } - queryParams.append('exclude_team_ids', 'litellm-dashboard'); + queryParams.append("exclude_team_ids", "litellm-dashboard"); const queryString = queryParams.toString(); if (queryString) { url += `?${queryString}`; @@ -1504,7 +1563,11 @@ export const claimOnboardingToken = async ( } }; -export const regenerateKeyCall = async (accessToken: string, keyToRegenerate: string, formData: any) => { +export const regenerateKeyCall = async ( + accessToken: string, + keyToRegenerate: string, + formData: any +) => { try { const url = proxyBaseUrl ? `${proxyBaseUrl}/key/${keyToRegenerate}/regenerate` @@ -1549,7 +1612,8 @@ export const modelInfoCall = async ( console.log("modelInfoCall:", accessToken, userID, userRole); let url = proxyBaseUrl ? `${proxyBaseUrl}/v2/model/info` : `/v2/model/info`; - if (!all_admin_roles.includes(userRole as string)) { // only show users models they've added + if (!all_admin_roles.includes(userRole as string)) { + // only show users models they've added url += `?user_models_only=true`; } //message.info("Requesting model data"); @@ -1563,14 +1627,14 @@ export const modelInfoCall = async ( if (!response.ok) { let errorData = await response.text(); - errorData += `error shown=${ModelListerrorShown}` + errorData += `error shown=${ModelListerrorShown}`; if (!ModelListerrorShown) { if (errorData.includes("No model list passed")) { errorData = "No Models Exist. Click Add Model to get started."; } message.info(errorData, 10); ModelListerrorShown = true; - + if (errorTimer) clearTimeout(errorTimer); errorTimer = setTimeout(() => { ModelListerrorShown = false; @@ -1621,7 +1685,6 @@ export const modelInfoV1Call = async (accessToken: String, modelId: String) => { } }; - export const modelHubCall = async (accessToken: String) => { /** * Get all models on proxy @@ -1927,19 +1990,22 @@ export const modelAvailableCall = async ( /** * Get all the models user has access to */ - console.log("in /models calls, globalLitellmHeaderName", globalLitellmHeaderName) + console.log( + "in /models calls, globalLitellmHeaderName", + globalLitellmHeaderName + ); try { let url = proxyBaseUrl ? `${proxyBaseUrl}/models` : `/models`; const params = new URLSearchParams(); - params.append('include_model_access_groups', 'True'); + params.append("include_model_access_groups", "True"); if (return_wildcard_routes === true) { - params.append('return_wildcard_routes', 'True'); + params.append("return_wildcard_routes", "True"); } if (only_model_access_groups === true) { - params.append('only_model_access_groups', 'True'); + params.append("only_model_access_groups", "True"); } if (teamID) { - params.append('team_id', teamID.toString()); + params.append("team_id", teamID.toString()); } if (params.toString()) { url += `?${params.toString()}`; @@ -2124,9 +2190,14 @@ export const allEndUsersCall = async (accessToken: String) => { } }; -export const userFilterUICall = async (accessToken: String, params: URLSearchParams) => { +export const userFilterUICall = async ( + accessToken: String, + params: URLSearchParams +) => { try { - let url = proxyBaseUrl ? `${proxyBaseUrl}/user/filter/ui` : `/user/filter/ui`; + let url = proxyBaseUrl + ? `${proxyBaseUrl}/user/filter/ui` + : `/user/filter/ui`; if (params.get("user_email")) { url += `?user_email=${params.get("user_email")}`; @@ -2147,12 +2218,12 @@ export const userFilterUICall = async (accessToken: String, params: URLSearchPar handleError(errorData); throw new Error("Network response was not ok"); } - return await response.json(); + return await response.json(); } catch (error) { console.error("Failed to create key:", error); throw error; } -} +}; export const userSpendLogsCall = async ( accessToken: String, @@ -2196,7 +2267,7 @@ export const userSpendLogsCall = async ( export const uiSpendLogsCall = async ( accessToken: String, - api_key?: string, + api_key?: string, team_id?: string, request_id?: string, start_date?: string, @@ -2213,16 +2284,16 @@ export const uiSpendLogsCall = async ( // Add query parameters if they exist const queryParams = new URLSearchParams(); - if (api_key) queryParams.append('api_key', api_key); - if (team_id) queryParams.append('team_id', team_id); - if (request_id) queryParams.append('request_id', request_id); - if (start_date) queryParams.append('start_date', start_date); - if (end_date) queryParams.append('end_date', end_date); - if (page) queryParams.append('page', page.toString()); - if (page_size) queryParams.append('page_size', page_size.toString()); - if (user_id) queryParams.append('user_id', user_id); - if (status_filter) queryParams.append('status_filter', status_filter); - if (model) queryParams.append('model', model); + if (api_key) queryParams.append("api_key", api_key); + if (team_id) queryParams.append("team_id", team_id); + if (request_id) queryParams.append("request_id", request_id); + if (start_date) queryParams.append("start_date", start_date); + if (end_date) queryParams.append("end_date", end_date); + if (page) queryParams.append("page", page.toString()); + if (page_size) queryParams.append("page_size", page_size.toString()); + if (user_id) queryParams.append("user_id", user_id); + if (status_filter) queryParams.append("status_filter", status_filter); + if (model) queryParams.append("model", model); // Append query parameters to URL if any exist const queryString = queryParams.toString(); if (queryString) { @@ -2252,7 +2323,6 @@ export const uiSpendLogsCall = async ( } }; - export const adminSpendLogsCall = async (accessToken: String) => { try { let url = proxyBaseUrl @@ -2347,7 +2417,6 @@ export const adminTopEndUsersCall = async ( body: body, }; - const response = await fetch(url, requestOptions); if (!response.ok) { const errorData = await response.text(); @@ -2661,42 +2730,46 @@ export const keyInfoCall = async (accessToken: String, keys: String[]) => { } }; - export const testConnectionRequest = async ( accessToken: string, litellm_params: Record, - mode: string, + mode: string ) => { try { - console.log("Sending model connection test request:", JSON.stringify(litellm_params)); - + console.log( + "Sending model connection test request:", + JSON.stringify(litellm_params) + ); + // Construct the URL based on environment - const url = proxyBaseUrl ? `${proxyBaseUrl}/health/test_connection` : `/health/test_connection`; - + const url = proxyBaseUrl + ? `${proxyBaseUrl}/health/test_connection` + : `/health/test_connection`; + const response = await fetch(url, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', - [globalLitellmHeaderName]: `Bearer ${accessToken}` + "Content-Type": "application/json", + [globalLitellmHeaderName]: `Bearer ${accessToken}`, }, - body: JSON.stringify( - { - litellm_params: litellm_params, - mode: mode, - } - ) + body: JSON.stringify({ + litellm_params: litellm_params, + mode: mode, + }), }); // Check for non-JSON responses first - const contentType = response.headers.get('content-type'); - if (!contentType || !contentType.includes('application/json')) { + const contentType = response.headers.get("content-type"); + if (!contentType || !contentType.includes("application/json")) { const text = await response.text(); console.error("Received non-JSON response:", text); - throw new Error(`Received non-JSON response (${response.status}: ${response.statusText}). Check network tab for details.`); + throw new Error( + `Received non-JSON response (${response.status}: ${response.statusText}). Check network tab for details.` + ); } const data = await response.json(); - + if (!response.ok || data.status === "error") { // Return the error response instead of throwing an error // This allows the caller to handle the error format properly @@ -2705,7 +2778,9 @@ export const testConnectionRequest = async ( } else { return { status: "error", - message: data.error?.message || `Connection test failed: ${response.status} ${response.statusText}` + message: + data.error?.message || + `Connection test failed: ${response.status} ${response.statusText}`, }; } } @@ -2751,10 +2826,8 @@ export const keyInfoV1Call = async (accessToken: string, key: string) => { } }; - - export const keyListCall = async ( - accessToken: String, + accessToken: String, organizationID: string | null, teamID: string | null, selectedKeyAlias: string | null, @@ -2763,7 +2836,7 @@ export const keyListCall = async ( page: number, pageSize: number, sortBy: string | null = null, - sortOrder: string | null = null, + sortOrder: string | null = null ) => { /** * Get all available teams on proxy @@ -2772,45 +2845,45 @@ export const keyListCall = async ( let url = proxyBaseUrl ? `${proxyBaseUrl}/key/list` : `/key/list`; console.log("in keyListCall"); const queryParams = new URLSearchParams(); - + if (teamID) { - queryParams.append('team_id', teamID.toString()); + queryParams.append("team_id", teamID.toString()); } - + if (organizationID) { - queryParams.append('organization_id', organizationID.toString()); + queryParams.append("organization_id", organizationID.toString()); } if (selectedKeyAlias) { - queryParams.append('key_alias', selectedKeyAlias) + queryParams.append("key_alias", selectedKeyAlias); } if (keyHash) { - queryParams.append('key_hash', keyHash); + queryParams.append("key_hash", keyHash); } if (userID) { - queryParams.append('user_id', userID.toString()); + queryParams.append("user_id", userID.toString()); } if (page) { - queryParams.append('page', page.toString()); + queryParams.append("page", page.toString()); } if (pageSize) { - queryParams.append('size', pageSize.toString()); + queryParams.append("size", pageSize.toString()); } if (sortBy) { - queryParams.append('sort_by', sortBy); + queryParams.append("sort_by", sortBy); } if (sortOrder) { - queryParams.append('sort_order', sortOrder); + queryParams.append("sort_order", sortOrder); } - queryParams.append('return_full_object', 'true'); - queryParams.append('include_team_keys', 'true'); - + queryParams.append("return_full_object", "true"); + queryParams.append("include_team_keys", "true"); + const queryString = queryParams.toString(); if (queryString) { url += `?${queryString}`; @@ -2992,7 +3065,10 @@ export const getPossibleUserRoles = async (accessToken: String) => { const errorData = await response.text(); throw new Error("Network response was not ok"); } - const data = await response.json() as Record>; + const data = (await response.json()) as Record< + string, + Record + >; console.log("response from user/available_role", data); return data; // Handle success - you might want to update some state or UI based on the created key @@ -3091,9 +3167,7 @@ export const credentialCreateCall = async ( } }; -export const credentialListCall = async ( - accessToken: String, -) => { +export const credentialListCall = async (accessToken: String) => { /** * Get all available teams on proxy */ @@ -3125,7 +3199,11 @@ export const credentialListCall = async ( } }; -export const credentialGetCall = async (accessToken: String, credentialName: String | null, modelId: String | null) => { +export const credentialGetCall = async ( + accessToken: String, + credentialName: String | null, + modelId: String | null +) => { try { let url = proxyBaseUrl ? `${proxyBaseUrl}/credentials` : `/credentials`; @@ -3161,9 +3239,14 @@ export const credentialGetCall = async (accessToken: String, credentialName: Str } }; -export const credentialDeleteCall = async (accessToken: String, credentialName: String) => { +export const credentialDeleteCall = async ( + accessToken: String, + credentialName: String +) => { try { - const url = proxyBaseUrl ? `${proxyBaseUrl}/credentials/${credentialName}` : `/credentials/${credentialName}`; + const url = proxyBaseUrl + ? `${proxyBaseUrl}/credentials/${credentialName}` + : `/credentials/${credentialName}`; console.log("in credentialDeleteCall:", credentialName); const response = await fetch(url, { method: "DELETE", @@ -3205,7 +3288,9 @@ export const credentialUpdateCall = async ( } } - const url = proxyBaseUrl ? `${proxyBaseUrl}/credentials/${credentialName}` : `/credentials/${credentialName}`; + const url = proxyBaseUrl + ? `${proxyBaseUrl}/credentials/${credentialName}` + : `/credentials/${credentialName}`; const response = await fetch(url, { method: "PATCH", headers: { @@ -3314,7 +3399,7 @@ export const teamUpdateCall = async ( message.error("Failed to update team settings: " + errorData); throw new Error(errorData); } - const data = await response.json() as { data: Team, team_id: string }; + const data = (await response.json()) as { data: Team; team_id: string }; console.log("Update Team Response:", data); return data; // Handle success - you might want to update some state or UI based on the updated team @@ -3326,10 +3411,10 @@ export const teamUpdateCall = async ( /** * Patch update a model - * - * @param accessToken - * @param formValues - * @returns + * + * @param accessToken + * @param formValues + * @returns */ export const modelPatchUpdateCall = async ( accessToken: string, @@ -3339,7 +3424,9 @@ export const modelPatchUpdateCall = async ( try { console.log("Form Values in modelUpateCall:", formValues); // Log the form values before making the API call - const url = proxyBaseUrl ? `${proxyBaseUrl}/model/${modelId}/update` : `/model/${modelId}/update`; + const url = proxyBaseUrl + ? `${proxyBaseUrl}/model/${modelId}/update` + : `/model/${modelId}/update`; const response = await fetch(url, { method: "PATCH", headers: { @@ -3443,7 +3530,8 @@ export const teamMemberAddCall = async ( console.warn("Failed to parse error body as JSON:", errorText); } - const rawMessage = parsedError?.detail?.error || "Failed to add team member"; + const rawMessage = + parsedError?.detail?.error || "Failed to add team member"; const err = new Error(rawMessage); (err as any).raw = parsedError; throw err; @@ -3477,7 +3565,7 @@ export const teamMemberUpdateCall = async ( }, body: JSON.stringify({ team_id: teamId, - role: formValues.role, + role: formValues.role, user_id: formValues.user_id, }), }); @@ -3493,7 +3581,8 @@ export const teamMemberUpdateCall = async ( console.warn("Failed to parse error body as JSON:", errorText); } - const rawMessage = parsedError?.detail?.error || "Failed to add team member"; + const rawMessage = + parsedError?.detail?.error || "Failed to add team member"; const err = new Error(rawMessage); (err as any).raw = parsedError; throw err; @@ -3527,8 +3616,12 @@ export const teamMemberDeleteCall = async ( }, body: JSON.stringify({ team_id: teamId, - ...(formValues.user_email !== undefined && { user_email: formValues.user_email }), - ...(formValues.user_id !== undefined && { user_id: formValues.user_id }) + ...(formValues.user_email !== undefined && { + user_email: formValues.user_email, + }), + ...(formValues.user_id !== undefined && { + user_id: formValues.user_id, + }), }), }); @@ -3547,7 +3640,7 @@ export const teamMemberDeleteCall = async ( console.error("Failed to create key:", error); throw error; } -} +}; export const organizationMemberAddCall = async ( accessToken: string, @@ -3609,7 +3702,7 @@ export const organizationMemberDeleteCall = async ( }, body: JSON.stringify({ organization_id: organizationId, - user_id: userId + user_id: userId, }), }); @@ -3698,7 +3791,7 @@ export const userUpdateUserCall = async ( throw new Error("Network response was not ok"); } - const data = await response.json() as { + const data = (await response.json()) as { user_id: string; data: UserInfo; }; @@ -3959,7 +4052,6 @@ export const getGeneralSettingsCall = async (accessToken: String) => { } }; - export const getPassThroughEndpointsCall = async (accessToken: String) => { try { let url = proxyBaseUrl @@ -4072,7 +4164,9 @@ export const createPassThroughEndpoint = async ( * Set callbacks on proxy */ try { - let url = proxyBaseUrl ? `${proxyBaseUrl}/config/pass_through_endpoint` : `/config/pass_through_endpoint`; + let url = proxyBaseUrl + ? `${proxyBaseUrl}/config/pass_through_endpoint` + : `/config/pass_through_endpoint`; //message.info("Requesting model data"); const response = await fetch(url, { @@ -4183,7 +4277,10 @@ export const deleteConfigFieldSetting = async ( } }; -export const deletePassThroughEndpointsCall = async (accessToken: String, endpointId: string) => { +export const deletePassThroughEndpointsCall = async ( + accessToken: String, + endpointId: string +) => { try { let url = proxyBaseUrl ? `${proxyBaseUrl}/config/pass_through_endpoint?endpoint_id=${endpointId}` @@ -4284,12 +4381,17 @@ export const healthCheckCall = async (accessToken: String) => { } }; -export const individualModelHealthCheckCall = async (accessToken: String, modelName: string) => { +export const individualModelHealthCheckCall = async ( + accessToken: String, + modelName: string +) => { /** * Run health check for a specific model using model name */ try { - let url = proxyBaseUrl ? `${proxyBaseUrl}/health?model=${encodeURIComponent(modelName)}` : `/health?model=${encodeURIComponent(modelName)}`; + let url = proxyBaseUrl + ? `${proxyBaseUrl}/health?model=${encodeURIComponent(modelName)}` + : `/health?model=${encodeURIComponent(modelName)}`; const response = await fetch(url, { method: "GET", @@ -4355,14 +4457,16 @@ export const healthCheckHistoryCall = async ( * Get health check history for models */ try { - let url = proxyBaseUrl ? `${proxyBaseUrl}/health/history` : `/health/history`; - + let url = proxyBaseUrl + ? `${proxyBaseUrl}/health/history` + : `/health/history`; + const params = new URLSearchParams(); - if (model) params.append('model', model); - if (statusFilter) params.append('status_filter', statusFilter); - params.append('limit', limit.toString()); - params.append('offset', offset.toString()); - + if (model) params.append("model", model); + if (statusFilter) params.append("status_filter", statusFilter); + params.append("limit", limit.toString()); + params.append("offset", offset.toString()); + if (params.toString()) { url += `?${params.toString()}`; } @@ -4418,9 +4522,7 @@ export const latestHealthChecksCall = async (accessToken: String) => { } }; -export const getProxyUISettings = async ( - accessToken: String, -) => { +export const getProxyUISettings = async (accessToken: String) => { /** * Get all the models user has access to */ @@ -4455,11 +4557,11 @@ export const getProxyUISettings = async ( } }; - - export const getGuardrailsList = async (accessToken: String) => { try { - const url = proxyBaseUrl ? `${proxyBaseUrl}/v2/guardrails/list` : `/v2/guardrails/list`; + const url = proxyBaseUrl + ? `${proxyBaseUrl}/v2/guardrails/list` + : `/v2/guardrails/list`; const response = await fetch(url, { method: "GET", headers: { @@ -4482,10 +4584,13 @@ export const getGuardrailsList = async (accessToken: String) => { } }; -export const createGuardrailCall = async (accessToken: string, guardrailData: any) => { +export const createGuardrailCall = async ( + accessToken: string, + guardrailData: any +) => { try { const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails` : `/guardrails`; - + const response = await fetch(url, { method: "POST", headers: { @@ -4493,7 +4598,7 @@ export const createGuardrailCall = async (accessToken: string, guardrailData: an "Content-Type": "application/json", }, body: JSON.stringify({ - guardrail: guardrailData + guardrail: guardrailData, }), }); @@ -4519,12 +4624,12 @@ export const uiSpendLogDetailsCall = async ( ) => { try { // Construct base URL - let url = proxyBaseUrl + let url = proxyBaseUrl ? `${proxyBaseUrl}/spend/logs/ui/${logId}?start_date=${encodeURIComponent(start_date)}` : `/spend/logs/ui/${logId}?start_date=${encodeURIComponent(start_date)}`; console.log("Fetching log details from:", url); - + const response = await fetch(url, { method: "GET", headers: { @@ -4544,19 +4649,19 @@ export const uiSpendLogDetailsCall = async ( return data; } catch (error) { console.error("Failed to fetch log details:", error); - throw error; + throw error; } }; export const getInternalUserSettings = async (accessToken: string) => { try { // Construct base URL - let url = proxyBaseUrl + let url = proxyBaseUrl ? `${proxyBaseUrl}/get/internal_user_settings` : `/get/internal_user_settings`; console.log("Fetching SSO settings from:", url); - + const response = await fetch(url, { method: "GET", headers: { @@ -4580,16 +4685,18 @@ export const getInternalUserSettings = async (accessToken: string) => { } }; - -export const updateInternalUserSettings = async (accessToken: string, settings: Record) => { +export const updateInternalUserSettings = async ( + accessToken: string, + settings: Record +) => { try { // Construct base URL - let url = proxyBaseUrl + let url = proxyBaseUrl ? `${proxyBaseUrl}/update/internal_user_settings` : `/update/internal_user_settings`; console.log("Updating internal user settings:", settings); - + const response = await fetch(url, { method: "PATCH", headers: { @@ -4618,7 +4725,9 @@ export const updateInternalUserSettings = async (accessToken: string, settings: export const fetchMCPServers = async (accessToken: string) => { try { // Construct base URL - const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/mcp/server` : `/v1/mcp/server`; + const url = proxyBaseUrl + ? `${proxyBaseUrl}/v1/mcp/server` + : `/v1/mcp/server`; console.log("Fetching MCP servers from:", url); @@ -4651,9 +4760,11 @@ export const createMCPServer = async ( ) => { try { console.log("Form Values in createMCPServer:", formValues); // Log the form values before making the API call - - const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/mcp/server` : `/v1/mcp/server`; - + + const url = proxyBaseUrl + ? `${proxyBaseUrl}/v1/mcp/server` + : `/v1/mcp/server`; + const response = await fetch(url, { method: "POST", headers: { @@ -4687,7 +4798,9 @@ export const updateMCPServer = async ( formValues: Record ) => { try { - const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/mcp/server` : `/v1/mcp/server`; + const url = proxyBaseUrl + ? `${proxyBaseUrl}/v1/mcp/server` + : `/v1/mcp/server`; const response = await fetch(url, { method: "PUT", headers: { @@ -4739,12 +4852,12 @@ export const deleteMCPServer = async ( export const listMCPTools = async (accessToken: string, serverId: string) => { try { // Construct base URL - let url = proxyBaseUrl + let url = proxyBaseUrl ? `${proxyBaseUrl}/mcp-rest/tools/list?server_id=${serverId}` : `/mcp-rest/tools/list?server_id=${serverId}`; console.log("Fetching MCP tools from:", url); - + const response = await fetch(url, { method: "GET", headers: { @@ -4768,16 +4881,24 @@ export const listMCPTools = async (accessToken: string, serverId: string) => { } }; - -export const callMCPTool = async (accessToken: string, toolName: string, toolArguments: Record) => { +export const callMCPTool = async ( + accessToken: string, + toolName: string, + toolArguments: Record +) => { try { // Construct base URL - let url = proxyBaseUrl + let url = proxyBaseUrl ? `${proxyBaseUrl}/mcp-rest/tools/call` : `/mcp-rest/tools/call`; - console.log("Calling MCP tool:", toolName, "with arguments:", toolArguments); - + console.log( + "Calling MCP tool:", + toolName, + "with arguments:", + toolArguments + ); + const response = await fetch(url, { method: "POST", headers: { @@ -4805,15 +4926,12 @@ export const callMCPTool = async (accessToken: string, toolName: string, toolArg } }; - export const tagCreateCall = async ( accessToken: string, formValues: TagNewRequest ): Promise => { try { - let url = proxyBaseUrl - ? `${proxyBaseUrl}/tag/new` - : `/tag/new`; + let url = proxyBaseUrl ? `${proxyBaseUrl}/tag/new` : `/tag/new`; const response = await fetch(url, { method: "POST", @@ -4842,9 +4960,7 @@ export const tagUpdateCall = async ( formValues: TagUpdateRequest ): Promise => { try { - let url = proxyBaseUrl - ? `${proxyBaseUrl}/tag/update` - : `/tag/update`; + let url = proxyBaseUrl ? `${proxyBaseUrl}/tag/update` : `/tag/update`; const response = await fetch(url, { method: "POST", @@ -4873,9 +4989,7 @@ export const tagInfoCall = async ( tagNames: string[] ): Promise => { try { - let url = proxyBaseUrl - ? `${proxyBaseUrl}/tag/info` - : `/tag/info`; + let url = proxyBaseUrl ? `${proxyBaseUrl}/tag/info` : `/tag/info`; const response = await fetch(url, { method: "POST", @@ -4900,11 +5014,11 @@ export const tagInfoCall = async ( } }; -export const tagListCall = async (accessToken: string): Promise => { +export const tagListCall = async ( + accessToken: string +): Promise => { try { - let url = proxyBaseUrl - ? `${proxyBaseUrl}/tag/list` - : `/tag/list`; + let url = proxyBaseUrl ? `${proxyBaseUrl}/tag/list` : `/tag/list`; const response = await fetch(url, { method: "GET", @@ -4932,9 +5046,7 @@ export const tagDeleteCall = async ( tagName: string ): Promise => { try { - let url = proxyBaseUrl - ? `${proxyBaseUrl}/tag/delete` - : `/tag/delete`; + let url = proxyBaseUrl ? `${proxyBaseUrl}/tag/delete` : `/tag/delete`; const response = await fetch(url, { method: "POST", @@ -4961,12 +5073,12 @@ export const tagDeleteCall = async ( export const getDefaultTeamSettings = async (accessToken: string) => { try { // Construct base URL - let url = proxyBaseUrl + let url = proxyBaseUrl ? `${proxyBaseUrl}/get/default_team_settings` : `/get/default_team_settings`; console.log("Fetching default team settings from:", url); - + const response = await fetch(url, { method: "GET", headers: { @@ -4990,16 +5102,18 @@ export const getDefaultTeamSettings = async (accessToken: string) => { } }; - -export const updateDefaultTeamSettings = async (accessToken: string, settings: Record) => { +export const updateDefaultTeamSettings = async ( + accessToken: string, + settings: Record +) => { try { // Construct base URL - let url = proxyBaseUrl + let url = proxyBaseUrl ? `${proxyBaseUrl}/update/default_team_settings` : `/update/default_team_settings`; console.log("Updating default team settings:", settings); - + const response = await fetch(url, { method: "PATCH", headers: { @@ -5025,14 +5139,12 @@ export const updateDefaultTeamSettings = async (accessToken: string, settings: R } }; - - export const getTeamPermissionsCall = async ( accessToken: string, teamId: string ) => { try { - let url = proxyBaseUrl + let url = proxyBaseUrl ? `${proxyBaseUrl}/team/permissions_list?team_id=${teamId}` : `/team/permissions_list?team_id=${teamId}`; @@ -5059,19 +5171,16 @@ export const getTeamPermissionsCall = async ( } }; - - export const teamPermissionsUpdateCall = async ( accessToken: string, teamId: string, permissions: string[] ) => { try { - let url = proxyBaseUrl + let url = proxyBaseUrl ? `${proxyBaseUrl}/team/permissions_update` : `/team/permissions_update`; - const response = await fetch(url, { method: "POST", headers: { @@ -5090,7 +5199,6 @@ export const teamPermissionsUpdateCall = async ( throw new Error("Network response was not ok"); } - const data = await response.json(); console.log("Team permissions response:", data); return data; @@ -5144,22 +5252,22 @@ export const vectorStoreCreateCall = async ( : `/vector_store/new`; const response = await fetch(url, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}` + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, }, - body: JSON.stringify(formValues) + body: JSON.stringify(formValues), }); - + if (!response.ok) { const error = await response.json(); - throw new Error(error.detail || 'Failed to create vector store'); + throw new Error(error.detail || "Failed to create vector store"); } - + return await response.json(); } catch (error) { - console.error('Error creating vector store:', error); + console.error("Error creating vector store:", error); throw error; } }; @@ -5174,23 +5282,22 @@ export const vectorStoreListCall = async ( ? `${proxyBaseUrl}/vector_store/list` : `/vector_store/list`; - const response = await fetch(url, { - method: 'GET', + method: "GET", headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}` - } + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, + }, }); - + if (!response.ok) { const error = await response.json(); - throw new Error(error.detail || 'Failed to list vector stores'); + throw new Error(error.detail || "Failed to list vector stores"); } - + return await response.json(); } catch (error) { - console.error('Error listing vector stores:', error); + console.error("Error listing vector stores:", error); throw error; } }; @@ -5205,22 +5312,22 @@ export const vectorStoreDeleteCall = async ( : `/vector_store/delete`; const response = await fetch(url, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}` + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, }, - body: JSON.stringify({ vector_store_id: vectorStoreId }) + body: JSON.stringify({ vector_store_id: vectorStoreId }), }); - + if (!response.ok) { const error = await response.json(); - throw new Error(error.detail || 'Failed to delete vector store'); + throw new Error(error.detail || "Failed to delete vector store"); } - + return await response.json(); } catch (error) { - console.error('Error deleting vector store:', error); + console.error("Error deleting vector store:", error); throw error; } }; @@ -5235,31 +5342,34 @@ export const vectorStoreInfoCall = async ( : `/vector_store/info`; const response = await fetch(url, { - method: 'POST', + method: "POST", headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${accessToken}` + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, }, - body: JSON.stringify({ vector_store_id: vectorStoreId }) + body: JSON.stringify({ vector_store_id: vectorStoreId }), }); - + if (!response.ok) { const error = await response.json(); - throw new Error(error.detail || 'Failed to get vector store info'); + throw new Error(error.detail || "Failed to get vector store info"); } - + return await response.json(); } catch (error) { - console.error('Error getting vector store info:', error); + console.error("Error getting vector store info:", error); throw error; } }; - -export const getEmailEventSettings = async (accessToken: string): Promise => { +export const getEmailEventSettings = async ( + accessToken: string +): Promise => { try { - const url = proxyBaseUrl ? `${proxyBaseUrl}/email/event_settings` : `/email/event_settings`; - + const url = proxyBaseUrl + ? `${proxyBaseUrl}/email/event_settings` + : `/email/event_settings`; + const response = await fetch(url, { method: "GET", headers: { @@ -5288,8 +5398,10 @@ export const updateEmailEventSettings = async ( settings: EmailEventSettingsUpdateRequest ) => { try { - const url = proxyBaseUrl ? `${proxyBaseUrl}/email/event_settings` : `/email/event_settings`; - + const url = proxyBaseUrl + ? `${proxyBaseUrl}/email/event_settings` + : `/email/event_settings`; + const response = await fetch(url, { method: "PATCH", headers: { @@ -5316,14 +5428,16 @@ export const updateEmailEventSettings = async ( export const resetEmailEventSettings = async (accessToken: string) => { try { - const url = proxyBaseUrl ? `${proxyBaseUrl}/email/event_settings/reset` : `/email/event_settings/reset`; - + const url = proxyBaseUrl + ? `${proxyBaseUrl}/email/event_settings/reset` + : `/email/event_settings/reset`; + const response = await fetch(url, { method: "POST", headers: { [globalLitellmHeaderName]: `Bearer ${accessToken}`, "Content-Type": "application/json", - } + }, }); if (!response.ok) { @@ -5344,10 +5458,15 @@ export const resetEmailEventSettings = async (accessToken: string) => { export { type UserInfo } from "./view_users/types"; // Re-export UserInfo export { type Team } from "./key_team_helpers/key_list"; // Re-export Team -export const deleteGuardrailCall = async (accessToken: string, guardrailId: string) => { +export const deleteGuardrailCall = async ( + accessToken: string, + guardrailId: string +) => { try { - const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/${guardrailId}` : `/guardrails/${guardrailId}`; - + const url = proxyBaseUrl + ? `${proxyBaseUrl}/guardrails/${guardrailId}` + : `/guardrails/${guardrailId}`; + const response = await fetch(url, { method: "DELETE", headers: { @@ -5373,8 +5492,10 @@ export const deleteGuardrailCall = async (accessToken: string, guardrailId: stri export const getGuardrailUISettings = async (accessToken: string) => { try { - const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/ui/add_guardrail_settings` : `/guardrails/ui/add_guardrail_settings`; - + const url = proxyBaseUrl + ? `${proxyBaseUrl}/guardrails/ui/add_guardrail_settings` + : `/guardrails/ui/add_guardrail_settings`; + const response = await fetch(url, { method: "GET", headers: { @@ -5398,10 +5519,14 @@ export const getGuardrailUISettings = async (accessToken: string) => { } }; -export const getGuardrailProviderSpecificParams = async (accessToken: string) => { +export const getGuardrailProviderSpecificParams = async ( + accessToken: string +) => { try { - const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/ui/provider_specific_params` : `/guardrails/ui/provider_specific_params`; - + const url = proxyBaseUrl + ? `${proxyBaseUrl}/guardrails/ui/provider_specific_params` + : `/guardrails/ui/provider_specific_params`; + const response = await fetch(url, { method: "GET", headers: { @@ -5420,15 +5545,23 @@ export const getGuardrailProviderSpecificParams = async (accessToken: string) => console.log("Guardrail provider specific params response:", data); return data; } catch (error) { - console.error("Failed to get guardrail provider specific parameters:", error); + console.error( + "Failed to get guardrail provider specific parameters:", + error + ); throw error; } }; -export const getGuardrailInfo = async (accessToken: string, guardrailId: string) => { +export const getGuardrailInfo = async ( + accessToken: string, + guardrailId: string +) => { try { - const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/${guardrailId}/info` : `/guardrails/${guardrailId}/info`; - + const url = proxyBaseUrl + ? `${proxyBaseUrl}/guardrails/${guardrailId}/info` + : `/guardrails/${guardrailId}/info`; + const response = await fetch(url, { method: "GET", headers: { @@ -5462,8 +5595,10 @@ export const updateGuardrailCall = async ( } ) => { try { - const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/${guardrailId}` : `/guardrails/${guardrailId}`; - + const url = proxyBaseUrl + ? `${proxyBaseUrl}/guardrails/${guardrailId}` + : `/guardrails/${guardrailId}`; + const response = await fetch(url, { method: "PATCH", headers: { @@ -5491,12 +5626,12 @@ export const updateGuardrailCall = async ( export const getSSOSettings = async (accessToken: string) => { try { // Construct base URL - let url = proxyBaseUrl + let url = proxyBaseUrl ? `${proxyBaseUrl}/get/sso_settings` : `/get/sso_settings`; console.log("Fetching SSO configuration from:", url); - + const response = await fetch(url, { method: "GET", headers: { @@ -5520,16 +5655,18 @@ export const getSSOSettings = async (accessToken: string) => { } }; - -export const updateSSOSettings = async (accessToken: string, settings: Record) => { +export const updateSSOSettings = async ( + accessToken: string, + settings: Record +) => { try { // Construct base URL - let url = proxyBaseUrl + let url = proxyBaseUrl ? `${proxyBaseUrl}/update/sso_settings` : `/update/sso_settings`; console.log("Updating SSO configuration:", settings); - + const response = await fetch(url, { method: "PATCH", headers: { @@ -5559,7 +5696,7 @@ export const uiAuditLogsCall = async ( start_date?: string, end_date?: string, page?: number, - page_size?: number, + page_size?: number ) => { try { // Construct base URL @@ -5569,8 +5706,8 @@ export const uiAuditLogsCall = async ( const queryParams = new URLSearchParams(); // if (start_date) queryParams.append('start_date', start_date); // if (end_date) queryParams.append('end_date', end_date); - if (page) queryParams.append('page', page.toString()); - if (page_size) queryParams.append('page_size', page_size.toString()); + if (page) queryParams.append("page", page.toString()); + if (page_size) queryParams.append("page_size", page_size.toString()); // Append query parameters to URL if any exist const queryString = queryParams.toString(); @@ -5600,13 +5737,17 @@ export const uiAuditLogsCall = async ( } }; -export const getRemainingUsers = async (accessToken: string): Promise<{ +export const getRemainingUsers = async ( + accessToken: string +): Promise<{ total_users: number; total_users_used: number; total_users_remaining: number; } | null> => { try { - const url = proxyBaseUrl ? `${proxyBaseUrl}/user/available_users` : `/user/available_users`; + const url = proxyBaseUrl + ? `${proxyBaseUrl}/user/available_users` + : `/user/available_users`; const response = await fetch(url, { method: "GET", @@ -5692,14 +5833,51 @@ export const getPassThroughEndpointInfo = async ( const data = await response.json(); const endpoints = data["endpoints"]; - + if (!endpoints || endpoints.length === 0) { throw new Error("Pass through endpoint not found"); } - + return endpoints[0]; // Return the first (and should be only) endpoint } catch (error) { console.error("Failed to get pass through endpoint info:", error); throw error; } -}; \ No newline at end of file +}; + +export const deleteCallback = async ( + accessToken: String, + callbackName: string +) => { + /** + * Delete specific callback from proxy using the /config/callback/delete API + */ + try { + let url = proxyBaseUrl + ? `${proxyBaseUrl}/config/callback/delete` + : `/config/callback/delete`; + + const response = await fetch(url, { + method: "POST", + headers: { + [globalLitellmHeaderName]: `Bearer ${accessToken}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ + callback_name: callbackName, + }), + }); + + if (!response.ok) { + const errorData = await response.text(); + handleError(errorData); + throw new Error("Network response was not ok"); + } + + const data = await response.json(); + return data; + } catch (error) { + console.error("Failed to delete specific callback:", error); + throw error; + } +}; diff --git a/ui/litellm-dashboard/src/components/settings.tsx b/ui/litellm-dashboard/src/components/settings.tsx index 50831adfdd6..eef5014ccfd 100644 --- a/ui/litellm-dashboard/src/components/settings.tsx +++ b/ui/litellm-dashboard/src/components/settings.tsx @@ -28,7 +28,8 @@ import { } from "@tremor/react"; import { - PencilAltIcon + PencilAltIcon, + TrashIcon } from "@heroicons/react/outline"; import { Modal, Typography, Form, Input, Select, Button as Button2, message } from "antd"; @@ -40,6 +41,7 @@ import { getCallbacksCall, setCallbacksCall, serviceHealthCheck, + deleteCallback } from "./networking"; import AlertingSettings from "./alerting/alerting_settings"; import FormItem from "antd/es/form/FormItem"; @@ -132,6 +134,8 @@ const Settings: React.FC = ({ const [showEditCallback, setShowEditCallback] = useState(false); const [selectedEditCallback, setSelectedEditCallback] = useState(null); + const [showDeleteConfirmModal, setShowDeleteConfirmModal] = useState(false); + const [callbackToDelete, setCallbackToDelete] = useState(null); const handleSwitchChange = (alertName: string) => { if (activeAlerts.includes(alertName)) { @@ -447,6 +451,33 @@ const Settings: React.FC = ({ const handleCallbackChange = (value: string) => { setSelectedCallback(value); }; + const handleDeleteCallback = (callbackName: string) => { + setCallbackToDelete(callbackName); + setShowDeleteConfirmModal(true); + }; + + const confirmDeleteCallback = async () => { + if (!callbackToDelete || !accessToken) { + return; + } + + try { + await deleteCallback(accessToken, callbackToDelete); + message.success(`Callback ${callbackToDelete} deleted successfully`); + + // Refresh the callbacks list + if (userID && userRole) { + const data = await getCallbacksCall(accessToken, userID, userRole); + setCallbacks(data.callbacks); + } + + setShowDeleteConfirmModal(false); + setCallbackToDelete(null); + } catch (error) { + console.error("Failed to delete callback:", error); + message.error(`Failed to delete callback: ${error}`); + } + }; if (!accessToken) { return null; @@ -498,6 +529,14 @@ const Settings: React.FC = ({ setShowEditCallback(true); }} /> + + handleDeleteCallback(callback.name) + } + className="text-red-500 hover:text-red-700 cursor-pointer" + />