Change /public fields to honor server root path (#16930)

This commit is contained in:
yuneng-jiang 2025-11-21 13:59:16 -08:00 • committed by GitHub
parent 6ae22908b7
commit 5dd2ee0bff
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 188 additions and 5 deletions

View file

@ -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();
});
});

View file

@ -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<ProviderCreateInfo[]>
* 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,