mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-20 00:11:50 +00:00
Merge pull request #41656 from BerriAI/litellm_remove_dead_networking_and_marketplace_helpers
refactor(ui): remove dead networking exports and orphaned Claude Code marketplace helpers
This commit is contained in:
commit
8ff4991583
8 changed files with 5 additions and 459 deletions
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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: [] })),
|
||||
}));
|
||||
|
||||
|
|
|
|||
|
|
@ -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: {} }),
|
||||
|
|
|
|||
|
|
@ -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" });
|
||||
|
|
|
|||
|
|
@ -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<string>();
|
||||
|
||||
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)
|
||||
*/
|
||||
|
|
@ -474,14 +384,3 @@ export const parseKeywords = (keywordsString: string): string[] => {
|
|||
.map((kw) => kw.trim())
|
||||
.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(", ");
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<TeamListResponse> => {
|
||||
/**
|
||||
* 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<string, any>, // 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<string, any>,
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ vi.mock("../networking", () => {
|
|||
organizationMemberAddCall: vi.fn(),
|
||||
organizationMemberUpdateCall: vi.fn(),
|
||||
organizationMemberDeleteCall: vi.fn(),
|
||||
organizationUpdateCall: vi.fn(),
|
||||
serverRootPath: "",
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ vi.mock("../networking", async (importOriginal) => {
|
|||
...actual,
|
||||
uiSpendLogsCall: vi.fn(),
|
||||
keyInfoV1Call: vi.fn().mockResolvedValue({ info: {} }),
|
||||
allEndUsersCall: vi.fn().mockResolvedValue([]),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue