From b19332319b809c96ab0d6542b84f03ac2f7f9b64 Mon Sep 17 00:00:00 2001 From: mateo Date: Thu, 17 Sep 2026 20:05:38 +0000 Subject: [PATCH 1/2] refactor(ui): remove dead networking exports and orphaned Claude Code marketplace helpers Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../components/AIHub/ModelHubTable.test.tsx | 1 - .../src/components/Teams.test.tsx | 1 - .../claude_code_plugins/helpers.test.ts | 139 ------------ .../components/claude_code_plugins/helpers.ts | 101 +-------- .../src/components/networking.tsx | 210 ------------------ .../organization/organization_view.test.tsx | 1 - .../view_logs/RequestLogsPanel.test.tsx | 1 - 7 files changed, 1 insertion(+), 453 deletions(-) diff --git a/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.test.tsx b/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.test.tsx index 906565750b1..27fe2330acd 100644 --- a/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.test.tsx +++ b/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.test.tsx @@ -19,7 +19,6 @@ vi.mock("@/components/networking", () => ({ getAgentsList: vi.fn(), fetchMCPServers: vi.fn(), getUiSettings: vi.fn(), - getClaudeCodeMarketplace: vi.fn(), getClaudeCodePluginsList: vi.fn(() => Promise.resolve({ plugins: [] })), })); diff --git a/ui/litellm-dashboard/src/components/Teams.test.tsx b/ui/litellm-dashboard/src/components/Teams.test.tsx index f2d7cff2ec4..b6c6bbbcdd9 100644 --- a/ui/litellm-dashboard/src/components/Teams.test.tsx +++ b/ui/litellm-dashboard/src/components/Teams.test.tsx @@ -64,7 +64,6 @@ vi.mock("./networking", () => ({ teamCreateCall: vi.fn(), teamDeleteCall: vi.fn(), fetchMCPAccessGroups: vi.fn(), - v2TeamListCall: vi.fn(), getGuardrailsList: vi.fn().mockResolvedValue({ guardrails: [] }), getPoliciesList: vi.fn().mockResolvedValue({ policies: [] }), getDefaultTeamSettings: vi.fn().mockResolvedValue({ values: {} }), diff --git a/ui/litellm-dashboard/src/components/claude_code_plugins/helpers.test.ts b/ui/litellm-dashboard/src/components/claude_code_plugins/helpers.test.ts index e40e0e0c783..5a4b8840c3e 100644 --- a/ui/litellm-dashboard/src/components/claude_code_plugins/helpers.test.ts +++ b/ui/litellm-dashboard/src/components/claude_code_plugins/helpers.test.ts @@ -1,26 +1,19 @@ import { describe, expect, it } from "vitest"; import { formatInstallCommand, - extractCategories, validatePluginName, getSourceDisplayText, getSourceLink, getCategoryBadgeColor, - formatDateString, - truncateText, - filterPluginsBySearch, - filterPluginsByCategory, isValidSemanticVersion, isValidEmail, isValidUrl, parseKeywords, - formatKeywords, parseSkillSource, isValidSubPath, isValidSha256, buildMarketplaceSettingsSnippet, } from "./helpers"; -import { MarketplacePluginEntry } from "./types"; describe("buildMarketplaceSettingsSnippet", () => { it("nests the url under a source object so Claude Code accepts the marketplace", () => { @@ -47,27 +40,6 @@ describe("formatInstallCommand", () => { }); }); -describe("extractCategories", () => { - it("returns All and Other for empty list", () => { - expect(extractCategories([])).toEqual(["All", "Other"]); - }); - - it("extracts and sorts unique categories", () => { - const plugins = [{ category: "Development" }, { category: "Analytics" }, { category: "Development" }]; - expect(extractCategories(plugins)).toEqual(["All", "Analytics", "Development", "Other"]); - }); - - it("ignores empty/whitespace categories", () => { - const plugins = [{ category: "" }, { category: " " }, { category: "Tools" }]; - expect(extractCategories(plugins)).toEqual(["All", "Tools", "Other"]); - }); - - it("handles undefined category", () => { - const plugins = [{ category: undefined }, { category: "Security" }]; - expect(extractCategories(plugins)).toEqual(["All", "Security", "Other"]); - }); -}); - describe("validatePluginName", () => { it("accepts valid kebab-case names", () => { expect(validatePluginName("my-plugin")).toBe(true); @@ -209,106 +181,6 @@ describe("getCategoryBadgeColor", () => { }); }); -describe("formatDateString", () => { - it("formats valid date strings", () => { - const result = formatDateString("2024-01-15T12:00:00Z"); - expect(result).toContain("2024"); - expect(result).toContain("Jan"); - expect(result).toContain("15"); - }); - - it("returns N/A for undefined", () => { - expect(formatDateString(undefined)).toBe("N/A"); - }); - - it("returns N/A for empty string", () => { - expect(formatDateString("")).toBe("N/A"); - }); -}); - -describe("truncateText", () => { - it("returns text unchanged if shorter than max", () => { - expect(truncateText("hello", 10)).toBe("hello"); - }); - - it("truncates and adds ellipsis", () => { - expect(truncateText("hello world", 5)).toBe("hello..."); - }); - - it("handles exact length", () => { - expect(truncateText("hello", 5)).toBe("hello"); - }); - - it("handles empty text", () => { - expect(truncateText("", 5)).toBe(""); - }); -}); - -describe("filterPluginsBySearch", () => { - const plugins: MarketplacePluginEntry[] = [ - { - name: "code-formatter", - source: { source: "github", repo: "org/formatter" }, - description: "Formats code nicely", - keywords: ["format", "lint"], - }, - { - name: "data-viewer", - source: { source: "github", repo: "org/viewer" }, - description: "View data", - keywords: ["analytics"], - }, - ]; - - it("returns all plugins for empty search", () => { - expect(filterPluginsBySearch(plugins, "")).toEqual(plugins); - expect(filterPluginsBySearch(plugins, " ")).toEqual(plugins); - }); - - it("matches by name", () => { - expect(filterPluginsBySearch(plugins, "formatter")).toHaveLength(1); - expect(filterPluginsBySearch(plugins, "formatter")[0].name).toBe("code-formatter"); - }); - - it("matches by description", () => { - expect(filterPluginsBySearch(plugins, "nicely")).toHaveLength(1); - }); - - it("matches by keyword", () => { - expect(filterPluginsBySearch(plugins, "analytics")).toHaveLength(1); - expect(filterPluginsBySearch(plugins, "analytics")[0].name).toBe("data-viewer"); - }); - - it("is case insensitive", () => { - expect(filterPluginsBySearch(plugins, "FORMATTER")).toHaveLength(1); - }); -}); - -describe("filterPluginsByCategory", () => { - const plugins: MarketplacePluginEntry[] = [ - { name: "a", source: { source: "github" }, category: "Dev" }, - { name: "b", source: { source: "github" }, category: "Security" }, - { name: "c", source: { source: "github" }, category: "" }, - { name: "d", source: { source: "github" } }, - ]; - - it("returns all plugins for 'All'", () => { - expect(filterPluginsByCategory(plugins, "All")).toEqual(plugins); - }); - - it("returns uncategorized plugins for 'Other'", () => { - const result = filterPluginsByCategory(plugins, "Other"); - expect(result).toHaveLength(2); - expect(result.map((p) => p.name)).toEqual(["c", "d"]); - }); - - it("filters by specific category", () => { - const result = filterPluginsByCategory(plugins, "Dev"); - expect(result).toHaveLength(1); - expect(result[0].name).toBe("a"); - }); -}); - describe("isValidSemanticVersion", () => { it("accepts valid semver", () => { expect(isValidSemanticVersion("1.0.0")).toBe(true); @@ -389,17 +261,6 @@ describe("parseKeywords", () => { }); }); -describe("formatKeywords", () => { - it("joins keywords with comma and space", () => { - expect(formatKeywords(["a", "b", "c"])).toBe("a, b, c"); - }); - - it("returns empty string for empty/undefined array", () => { - expect(formatKeywords([])).toBe(""); - expect(formatKeywords(undefined)).toBe(""); - }); -}); - describe("parseSkillSource", () => { it("parses a plain github repo", () => { expect(parseSkillSource("github.com/org/repo")?.parsed).toEqual({ source: "github", repo: "org/repo" }); diff --git a/ui/litellm-dashboard/src/components/claude_code_plugins/helpers.ts b/ui/litellm-dashboard/src/components/claude_code_plugins/helpers.ts index 8cf620d9077..3c7e84ade46 100644 --- a/ui/litellm-dashboard/src/components/claude_code_plugins/helpers.ts +++ b/ui/litellm-dashboard/src/components/claude_code_plugins/helpers.ts @@ -2,7 +2,7 @@ * Helper utilities for Claude Code Marketplace */ -import { PluginSource, MarketplacePluginEntry } from "./types"; +import { PluginSource } from "./types"; export interface SkillSourcePreview { parsed: PluginSource; @@ -262,25 +262,6 @@ export const buildMarketplaceSettingsSnippet = (proxyOrigin: string): string => */ export const formatInstallCommand = (plugin: { name: string }): string => `/plugin install ${plugin.name}@litellm`; -/** - * Extract unique categories from plugins list - * Returns array with "All" first, then sorted categories, then "Other" - */ -export const extractCategories = (plugins: Array<{ category?: string }>): string[] => { - const categories = new Set(); - - plugins.forEach((p) => { - if (p.category && p.category.trim() !== "") { - categories.add(p.category); - } - }); - - const sortedCategories = Array.from(categories).sort(); - - // Return: All, sorted categories, Other - return ["All", ...sortedCategories, "Other"]; -}; - /** * Validate plugin name format (kebab-case) * Must be lowercase letters, numbers, and hyphens only @@ -349,77 +330,6 @@ export const getCategoryBadgeColor = ( return "gray"; }; -/** - * Format date to readable string - */ -export const formatDateString = (dateString?: string): string => { - if (!dateString) { - return "N/A"; - } - - try { - const date = new Date(dateString); - return date.toLocaleDateString("en-US", { - year: "numeric", - month: "short", - day: "numeric", - }); - } catch (error) { - return "Invalid date"; - } -}; - -/** - * Truncate text with ellipsis - */ -export const truncateText = (text: string, maxLength: number): string => { - if (!text || text.length <= maxLength) { - return text; - } - return text.substring(0, maxLength) + "..."; -}; - -/** - * Filter plugins by search term - * Searches in: name, description, keywords - */ -export const filterPluginsBySearch = ( - plugins: MarketplacePluginEntry[], - searchTerm: string, -): MarketplacePluginEntry[] => { - if (!searchTerm || searchTerm.trim() === "") { - return plugins; - } - - const term = searchTerm.toLowerCase().trim(); - - return plugins.filter((plugin) => { - const nameMatch = plugin.name.toLowerCase().includes(term); - const descriptionMatch = plugin.description?.toLowerCase().includes(term) || false; - const keywordsMatch = plugin.keywords?.some((keyword) => keyword.toLowerCase().includes(term)) || false; - - return nameMatch || descriptionMatch || keywordsMatch; - }); -}; - -/** - * Filter plugins by category - */ -export const filterPluginsByCategory = ( - plugins: MarketplacePluginEntry[], - category: string, -): MarketplacePluginEntry[] => { - if (category === "All") { - return plugins; - } - - if (category === "Other") { - return plugins.filter((p) => !p.category || p.category.trim() === ""); - } - - return plugins.filter((p) => p.category === category); -}; - /** * Validate semantic version format (basic check) */ @@ -475,13 +385,4 @@ export const parseKeywords = (keywordsString: string): string[] => { .filter((kw) => kw !== ""); }; -/** - * Format keywords array to comma-separated string - */ -export const formatKeywords = (keywords?: string[]): string => { - if (!keywords || keywords.length === 0) { - return ""; - } - return keywords.join(", "); -}; diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index e77c8ba7e41..bb2e4d5a0cf 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -126,7 +126,6 @@ import { serverRootPath, setServerRootPath } from "@/lib/serverRootPath"; export { serverRootPath }; export { deriveErrorMessage }; -export { ApiError } from "@/lib/http/client"; const isLocal = process.env.NODE_ENV === "development"; // In dev, if NEXT_PUBLIC_USE_REWRITES=true the Next.js dev server proxies API calls @@ -1115,37 +1114,6 @@ export const userGetInfoV2 = async (accessToken: string, userId?: string): Promi } }; -export const userInfoCall = async ( - accessToken: string, - userID: string | null, - userRole: string, - viewAll: boolean = false, - page: number | null, - page_size: number | null, - lookup_user_id: boolean = false, -) => { - try { - if (viewAll) { - return await apiClient.get(`/user/list`, { - accessToken, - query: { - page: page != null ? page.toString() : undefined, - page_size: page_size != null ? page_size.toString() : undefined, - }, - }); - } - - const includeUserID = !((userRole === "Admin" || userRole === "Admin Viewer") && !lookup_user_id) && userID; - return await apiClient.get(`/user/info`, { - accessToken, - query: { user_id: includeUserID ? userID : undefined }, - }); - } catch (error) { - console.error("Failed to fetch user data:", error); - throw error; - } -}; - export const teamInfoCall = async (accessToken: string, teamID: string | null) => { try { return await apiClient.get(`/team/info`, { accessToken, query: { team_id: teamID || undefined } }); @@ -1155,44 +1123,6 @@ export const teamInfoCall = async (accessToken: string, teamID: string | null) = } }; -type TeamListResponse = { - teams: Team[]; - total: number; - page: number; - page_size: number; - total_pages: number; -}; - -export const v2TeamListCall = async ( - accessToken: string, - organizationID: string | null, - userID: string | null = null, - teamID: string | null = null, - team_alias: string | null = null, - page: number = 1, - page_size: number = 10, - sort_by: string | null = null, - sort_order: "asc" | "desc" | null = null, -): Promise => { - /** - * Get list of teams with filtering and sorting options - */ - try { - return await apiClient.get(`/v2/team/list`, { - accessToken, - query: { - user_id: userID || undefined, - organization_id: organizationID || undefined, - team_id: teamID || undefined, - team_alias: team_alias || undefined, - }, - }); - } catch (error) { - console.error("Failed to create key:", error); - throw error; - } -}; - export const teamListCall = async ( accessToken: string, organizationID: string | null, @@ -1283,25 +1213,6 @@ export const organizationInfoCall = async (accessToken: string, organizationID: } }; -export const organizationUpdateCall = async ( - accessToken: string, - formValues: Record, // Assuming formValues is an object -) => { - try { - const data = await apiClient.patch(`/organization/update`, { - accessToken, - body: { - ...formValues, // Include formValues in the request body - }, - }); - return data; - // Handle success - you might want to update some state or UI based on the created key - } catch (error) { - console.error("Failed to create key:", error); - throw error; - } -}; - export const organizationDeleteCall = async (accessToken: string, organizationID: string) => { try { const url = proxyBaseUrl ? `${proxyBaseUrl}/organization/delete` : `/organization/delete`; @@ -2043,16 +1954,6 @@ export const allTagNamesCall = async (accessToken: string) => { } }; -export const allEndUsersCall = async (accessToken: string) => { - try { - const data = await apiClient.get(`/customer/list`, { accessToken }); - return data; - } catch (error) { - console.error("Failed to fetch end users:", error); - throw error; - } -}; - export const userFilterUICall = async (accessToken: string, params: URLSearchParams) => { try { return await apiClient.get(`/user/filter/ui`, { @@ -2318,38 +2219,6 @@ export const adminTopModelsCall = async (accessToken: string) => { } }; -export const keyInfoCall = async (accessToken: string, keys: string[]) => { - try { - let url = proxyBaseUrl ? `${proxyBaseUrl}/v2/key/info` : `/v2/key/info`; - - const response = await fetch(url, { - method: "POST", - headers: { - [globalLitellmHeaderName]: `Bearer ${accessToken}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ - keys: keys, - }), - }); - - if (!response.ok) { - const errorData = await response.text(); - if (errorData.includes("Invalid proxy server token passed")) { - throw new Error("Invalid proxy server token passed"); - } - handleError(errorData); - throw new Error("Network response was not ok"); - } - - const data = await response.json(); - return data; - } catch (error) { - console.error("Failed to create key:", error); - throw error; - } -}; - export const testConnectionRequest = async ( accessToken: string, litellm_params: Record, @@ -3320,19 +3189,6 @@ export const serviceHealthCheck = async (accessToken: string, service: string) = } }; -export const getBudgetList = async (accessToken: string) => { - /** - * Get all configurable params for setting a budget - */ - try { - const data = await apiClient.get(`/budget/list`, { accessToken }); - return data; - // Handle success - you might want to update some state or UI based on the created key - } catch (error) { - console.error("Failed to get callbacks:", error); - throw error; - } -}; export const getCallbacksCall = async (accessToken: string, userID: string, userRole: string) => { /** * Get all the models user has access to @@ -7346,37 +7202,6 @@ export const updateUserBanner = async (accessToken: string, banner: UserBannerUp // Claude Code Marketplace Networking Functions -/** - * Get public marketplace catalog (no authentication required) - * Returns marketplace.json for Claude Code CLI discovery - */ -export const getClaudeCodeMarketplace = async () => { - try { - const proxyBaseUrl = getProxyBaseUrl(); - const url = proxyBaseUrl ? `${proxyBaseUrl}/claude-code/marketplace.json` : `/claude-code/marketplace.json`; - - const response = await fetch(url, { - method: "GET", - headers: { - "Content-Type": "application/json", - }, - }); - - if (!response.ok) { - const errorData = await response.text(); - const errorMessage = deriveErrorMessage(JSON.parse(errorData)); - handleError(errorMessage); - throw new Error(errorMessage); - } - - const data = await response.json(); - return data; - } catch (error) { - console.error("Failed to fetch Claude Code marketplace:", error); - throw error; - } -}; - /** * List all Claude Code plugins (admin only) * @param accessToken - Admin access token @@ -7412,41 +7237,6 @@ export const getClaudeCodePluginsList = async (accessToken: string, enabledOnly: } }; -/** - * Get details for a specific Claude Code plugin (admin only) - * @param accessToken - Admin access token - * @param pluginName - Name of the plugin - */ -export const getClaudeCodePluginDetails = async (accessToken: string, pluginName: string) => { - try { - const proxyBaseUrl = getProxyBaseUrl(); - const url = proxyBaseUrl - ? `${proxyBaseUrl}/claude-code/plugins/${pluginName}` - : `/claude-code/plugins/${pluginName}`; - - const response = await fetch(url, { - method: "GET", - headers: { - [globalLitellmHeaderName]: `Bearer ${accessToken}`, - "Content-Type": "application/json", - }, - }); - - if (!response.ok) { - const errorData = await response.text(); - const errorMessage = deriveErrorMessage(JSON.parse(errorData)); - handleError(errorMessage); - throw new Error(errorMessage); - } - - const data = await response.json(); - return data; - } catch (error) { - console.error(`Failed to fetch plugin "${pluginName}":`, error); - throw error; - } -}; - /** * Register a new Claude Code plugin (admin only). Create-only: the proxy returns * 409 if a plugin with the same name already exists. diff --git a/ui/litellm-dashboard/src/components/organization/organization_view.test.tsx b/ui/litellm-dashboard/src/components/organization/organization_view.test.tsx index d609b657717..ca792084f2a 100644 --- a/ui/litellm-dashboard/src/components/organization/organization_view.test.tsx +++ b/ui/litellm-dashboard/src/components/organization/organization_view.test.tsx @@ -21,7 +21,6 @@ vi.mock("../networking", () => { organizationMemberAddCall: vi.fn(), organizationMemberUpdateCall: vi.fn(), organizationMemberDeleteCall: vi.fn(), - organizationUpdateCall: vi.fn(), serverRootPath: "", }; }); diff --git a/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.test.tsx b/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.test.tsx index fda3b08b30b..8a0e529e2e8 100644 --- a/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.test.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/RequestLogsPanel.test.tsx @@ -15,7 +15,6 @@ vi.mock("../networking", async (importOriginal) => { ...actual, uiSpendLogsCall: vi.fn(), keyInfoV1Call: vi.fn().mockResolvedValue({ info: {} }), - allEndUsersCall: vi.fn().mockResolvedValue([]), }; }); From 67b2bdb7357107ef5b7c290ec5595d53456eadaa Mon Sep 17 00:00:00 2001 From: mateo Date: Thu, 17 Sep 2026 20:07:19 +0000 Subject: [PATCH 2/2] chore(ui): format helpers.ts and prune stale eslint suppressions Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- ui/litellm-dashboard/eslint-suppressions.json | 8 ++++---- .../src/components/claude_code_plugins/helpers.ts | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index 51a7a196d0a..493b2089c68 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -1066,7 +1066,7 @@ "count": 1 }, "prefer-const": { - "count": 2 + "count": 1 } }, "src/app/(dashboard)/search-tools/_components/SearchTools.tsx": { @@ -1804,16 +1804,16 @@ "count": 1 }, "max-params": { - "count": 23 + "count": 21 }, "no-nested-ternary": { "count": 5 }, "no-restricted-syntax": { - "count": 150 + "count": 147 }, "prefer-const": { - "count": 32 + "count": 31 } }, "src/components/object_permissions_view.tsx": { diff --git a/ui/litellm-dashboard/src/components/claude_code_plugins/helpers.ts b/ui/litellm-dashboard/src/components/claude_code_plugins/helpers.ts index 3c7e84ade46..57ac2dc9545 100644 --- a/ui/litellm-dashboard/src/components/claude_code_plugins/helpers.ts +++ b/ui/litellm-dashboard/src/components/claude_code_plugins/helpers.ts @@ -384,5 +384,3 @@ export const parseKeywords = (keywordsString: string): string[] => { .map((kw) => kw.trim()) .filter((kw) => kw !== ""); }; - -