diff --git a/ui/litellm-dashboard/src/components/networking.test.ts b/ui/litellm-dashboard/src/components/networking.test.ts index 9e402b85c23..8fad64e094e 100644 --- a/ui/litellm-dashboard/src/components/networking.test.ts +++ b/ui/litellm-dashboard/src/components/networking.test.ts @@ -132,3 +132,188 @@ describe("daily activity helpers", () => { expect(urlWithTeams.searchParams.get("exclude_team_ids")).toBe("litellm-dashboard"); }); }); + +describe("UI config and public endpoints", () => { + const originalFetch = global.fetch; + + const setupMockFetch = (responses: Array<{ url: string; data: any }>) => { + const mockFetch = vi.fn().mockImplementation((url: string) => { + const response = responses.find((r) => url.includes(r.url)); + if (response) { + return Promise.resolve({ + ok: true, + json: vi.fn().mockResolvedValue(response.data), + } as any); + } + return Promise.resolve({ + ok: true, + json: vi.fn().mockResolvedValue({}), + } as any); + }); + global.fetch = mockFetch as any; + return mockFetch; + }; + + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + global.fetch = originalFetch; + }); + + it("should use proxyBaseURL and server_root_path for /public/providers/fields when server_root_path is defined", async () => { + const uiConfig = { + server_root_path: "/api/v1", + proxy_base_url: "https://example.com", + }; + + const mockFetch = setupMockFetch([ + { url: "/litellm/.well-known/litellm-ui-config", data: uiConfig }, + { url: "/public/providers/fields", data: [] }, + ]); + + // First call getUiConfig to set up proxyBaseUrl + await Networking.getUiConfig(); + + // Then call the public endpoint + await Networking.getProviderCreateMetadata(); + + expect(mockFetch).toHaveBeenCalledTimes(2); + const publicEndpointCall = mockFetch.mock.calls.find((call) => + (call[0] as string).includes("/public/providers/fields"), + ); + expect(publicEndpointCall).toBeDefined(); + const calledUrl = publicEndpointCall![0] as string; + expect(calledUrl).toBe("https://example.com/api/v1/public/providers/fields"); + }); + + it("should use proxyBaseURL and server_root_path for /public/model_hub/info when server_root_path is defined", async () => { + const uiConfig = { + server_root_path: "/api/v1", + proxy_base_url: "https://example.com", + }; + + const mockFetch = setupMockFetch([ + { url: "/litellm/.well-known/litellm-ui-config", data: uiConfig }, + { url: "/public/model_hub/info", data: {} }, + ]); + + await Networking.getUiConfig(); + await Networking.getPublicModelHubInfo(); + + expect(mockFetch).toHaveBeenCalledTimes(2); + const publicEndpointCall = mockFetch.mock.calls.find((call) => + (call[0] as string).includes("/public/model_hub/info"), + ); + expect(publicEndpointCall).toBeDefined(); + const calledUrl = publicEndpointCall![0] as string; + expect(calledUrl).toBe("https://example.com/api/v1/public/model_hub/info"); + }); + + it("should use proxyBaseURL and server_root_path for /public/model_hub when server_root_path is defined", async () => { + const uiConfig = { + server_root_path: "/api/v1", + proxy_base_url: "https://example.com", + }; + + const mockFetch = setupMockFetch([ + { url: "/litellm/.well-known/litellm-ui-config", data: uiConfig }, + { url: "/public/model_hub", data: [] }, + ]); + + await Networking.getUiConfig(); + await Networking.modelHubPublicModelsCall(); + + expect(mockFetch).toHaveBeenCalledTimes(2); + const publicEndpointCall = mockFetch.mock.calls.find( + (call) => (call[0] as string).includes("/public/model_hub") && !(call[0] as string).includes("/info"), + ); + expect(publicEndpointCall).toBeDefined(); + const calledUrl = publicEndpointCall![0] as string; + expect(calledUrl).toBe("https://example.com/api/v1/public/model_hub"); + }); + + it("should use proxyBaseURL and server_root_path for /public/agent_hub when server_root_path is defined", async () => { + const uiConfig = { + server_root_path: "/api/v1", + proxy_base_url: "https://example.com", + }; + + const mockFetch = setupMockFetch([ + { url: "/litellm/.well-known/litellm-ui-config", data: uiConfig }, + { url: "/public/agent_hub", data: [] }, + ]); + + await Networking.getUiConfig(); + await Networking.agentHubPublicModelsCall(); + + expect(mockFetch).toHaveBeenCalledTimes(2); + const publicEndpointCall = mockFetch.mock.calls.find((call) => (call[0] as string).includes("/public/agent_hub")); + expect(publicEndpointCall).toBeDefined(); + const calledUrl = publicEndpointCall![0] as string; + expect(calledUrl).toBe("https://example.com/api/v1/public/agent_hub"); + }); + + it("should use proxyBaseURL and server_root_path for /public/mcp_hub when server_root_path is defined", async () => { + const uiConfig = { + server_root_path: "/api/v1", + proxy_base_url: "https://example.com", + }; + + const mockFetch = setupMockFetch([ + { url: "/litellm/.well-known/litellm-ui-config", data: uiConfig }, + { url: "/public/mcp_hub", data: [] }, + ]); + + await Networking.getUiConfig(); + await Networking.mcpHubPublicServersCall(); + + expect(mockFetch).toHaveBeenCalledTimes(2); + const publicEndpointCall = mockFetch.mock.calls.find((call) => (call[0] as string).includes("/public/mcp_hub")); + expect(publicEndpointCall).toBeDefined(); + const calledUrl = publicEndpointCall![0] as string; + expect(calledUrl).toBe("https://example.com/api/v1/public/mcp_hub"); + }); + + it("should not include server_root_path when it is root path", async () => { + const uiConfig = { + server_root_path: "/", + proxy_base_url: "https://example.com", + }; + + const mockFetch = setupMockFetch([ + { url: "/litellm/.well-known/litellm-ui-config", data: uiConfig }, + { url: "/public/providers/fields", data: [] }, + ]); + + await Networking.getUiConfig(); + await Networking.getProviderCreateMetadata(); + + expect(mockFetch).toHaveBeenCalledTimes(2); + const publicEndpointCall = mockFetch.mock.calls.find((call) => + (call[0] as string).includes("/public/providers/fields"), + ); + expect(publicEndpointCall).toBeDefined(); + const calledUrl = publicEndpointCall![0] as string; + expect(calledUrl).toBe("https://example.com/public/providers/fields"); + }); + + it("should return UI config from getUiConfig", async () => { + const uiConfig = { + server_root_path: "/api/v1", + proxy_base_url: "https://example.com", + }; + + const mockFetch = setupMockFetch([{ url: "/litellm/.well-known/litellm-ui-config", data: uiConfig }]); + + const result = await Networking.getUiConfig(); + + expect(mockFetch).toHaveBeenCalledOnce(); + expect(result).toEqual(uiConfig); + const configCall = mockFetch.mock.calls.find((call) => + (call[0] as string).includes("/litellm/.well-known/litellm-ui-config"), + ); + expect(configCall).toBeDefined(); + }); +}); diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index b3a9c9dc50d..f4eaeb9dcec 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -217,7 +217,7 @@ const handleError = async (errorData: string | any) => { if (currentTime - lastErrorTime > 60000) { // 60000 milliseconds = 60 seconds // Convert errorData to string if it isn't already - const errorString = typeof errorData === 'string' ? errorData : JSON.stringify(errorData); + const errorString = typeof errorData === "string" ? errorData : JSON.stringify(errorData); if (errorString.includes("Authentication Error - Expired Key")) { NotificationsManager.info("UI Session Expired. Logging out."); lastErrorTime = currentTime; @@ -238,7 +238,7 @@ export const getProviderCreateMetadata = async (): Promise * Fetch provider credential field metadata from the proxy's public endpoint. * This is used by the UI to dynamically render provider-specific credential fields. */ - const url = defaultProxyBaseUrl ? `${defaultProxyBaseUrl}/public/providers/fields` : `/public/providers/fields`; + const url = proxyBaseUrl ? `${proxyBaseUrl}/public/providers/fields` : `/public/providers/fields`; const response = await fetch(url, { method: "GET", }); @@ -295,7 +295,7 @@ export const getUiConfig = async () => { }; export const getPublicModelHubInfo = async () => { - const url = defaultProxyBaseUrl ? `${defaultProxyBaseUrl}/public/model_hub/info` : `/public/model_hub/info`; + const url = proxyBaseUrl ? `${proxyBaseUrl}/public/model_hub/info` : `/public/model_hub/info`; const response = await fetch(url); const jsonData: PublicModelHubInfo = await response.json(); return jsonData; @@ -6749,7 +6749,6 @@ export const getGuardrailProviderSpecificParams = async (accessToken: string) => } }; - export const getAgentsList = async (accessToken: string) => { try { const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/agents` : `/v1/agents`; @@ -6867,7 +6866,6 @@ export const patchAgentCall = async ( } }; - export const updateGuardrailCall = async ( accessToken: string, guardrailId: string,