litellm/ui/litellm-dashboard/src/components/networking.tsx
moe-berri e20cfbeaeb feat(auto-router): add JEV classifier alongside LLM classifier
Backport of #41886 to stable/1.102.x.
Cherry-picked from a83773cfa5 (main).
2026-09-22 22:49:10 +00:00

8164 lines
235 KiB
TypeScript

// Shared date formatter for daily activity endpoints
export const formatDate = (date: Date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
export const getCallbackConfigsCall = async (accessToken: string) => {
/**
* Get callback configuration metadata (logos, params, etc.)
*/
try {
return await apiClient.get(`/callbacks/configs`, { accessToken });
} catch (error) {
console.error("Failed to get callbacks:", error);
throw error;
}
};
export const getAutoRouterClassifierDefaultPromptCall = async (
accessToken: string,
contextWindowSize: number,
tierLabels?: Record<string, string>,
classificationRubric?: string,
): Promise<string> => {
/**
* Get the built-in system prompt an auto-router's LLM classifier uses when none is configured,
* so the prompt editor prefills what the proxy actually sends rather than a frontend copy.
*
* tierLabels names the rubric's tier bullets, so a router that renamed its tiers prefills the
* rubric it sends rather than one using the canonical names. rubric selects which calibration
* examples it carries, for the same reason.
*/
try {
const response = await apiClient.get<{ system_prompt: string }>(`/auto_router/classifier/default_prompt`, {
accessToken,
query: {
context_window_size: contextWindowSize,
...(tierLabels && Object.keys(tierLabels).length > 0 ? { tier_labels: JSON.stringify(tierLabels) } : {}),
...(classificationRubric ? { classification_rubric: classificationRubric } : {}),
},
});
return response.system_prompt;
} catch (error) {
console.error("Failed to get the default classifier prompt:", error);
throw error;
}
};
export type AssembledPromptTierSource =
| { tierDefinitions: { name: string; description?: string }[] }
| { tierLabels?: Record<string, string>; classificationRubric?: string };
export const getAutoRouterAssembledPromptCall = async (
accessToken: string,
contextWindowSize: number,
source: AssembledPromptTierSource,
sections: { classificationPrompt?: string; classificationExamples?: string } = {},
): Promise<string> => {
const { classificationPrompt, classificationExamples } = sections;
/**
* Assembled by the proxy, because tier criteria live only in the backend: a built-in tier name
* with no description inherits them, and the built-in rubric derives its bullets from them.
* POSTed so the operator's prompt does not reach access logs through a URL.
*/
const response = await apiClient.post<{ system_prompt: string }>(`/auto_router/classifier/default_prompt`, {
accessToken,
body: {
context_window_size: contextWindowSize,
...("tierDefinitions" in source
? { tier_definitions: source.tierDefinitions }
: {
...(source.tierLabels && Object.keys(source.tierLabels).length > 0
? { tier_labels: source.tierLabels }
: {}),
...(source.classificationRubric ? { classification_rubric: source.classificationRubric } : {}),
}),
...(classificationPrompt?.trim() ? { classification_prompt: classificationPrompt } : {}),
...(classificationExamples?.trim() ? { classification_examples: classificationExamples } : {}),
},
});
return response.system_prompt;
};
/**
* Helper file for calls being made to proxy
*/
import { toast } from "@/lib/toast";
import { clearTokenCookies, getCookie, storeLoginToken } from "@/utils/cookieUtils";
import { decodeToken } from "@/utils/jwtUtils";
import { TagNewRequest, TagUpdateRequest, TagListResponse, TagInfoResponse } from "./tag_management/types";
import { Team } from "./key_team_helpers/key_list";
import { EmailEventSettingsResponse, EmailEventSettingsUpdateRequest } from "./email_events/types";
import type { SkillRegisterRequest } from "./claude_code_plugins/types";
import type { ModelBudgetUsage, ModelMaxBudget } from "./key_team_helpers/ModelMaxBudgetEditor";
import type { ObjectPermission } from "./object_permission_types";
import type { components } from "@/lib/http/schema";
import { jsonFields } from "./common_components/check_openapi_schema";
import type { MCPUserEnvVarsStatus } from "./mcp_tools/types";
import type {
CoordinationRedisSettings,
CoordinationRedisSettingsResponse,
CoordinationRedisTestResponse,
} from "@/app/(dashboard)/caching/_components/coordination_redis_settings/types";
import { MCP_TOOLS_PREVIEW_FORBIDDEN_MESSAGE } from "./mcp_tools/constants";
import type { ComplexityRouterConfigPayload } from "./add_model/build_complexity_router_config";
import type { AutoRouterPresetsResponse } from "@/lib/autorouter_presets";
import type { VectorStoreIndex } from "@/app/(dashboard)/vector-stores/_components/IndexesTab";
import type { RoutingDecision } from "./view_logs/LogDetailsDrawer/RoutingDecisionCard";
import {
createApiClient,
deriveErrorMessage,
extractProxyErrorMessage,
unwrapProxyErrorMessage,
} from "@/lib/http/client";
import { resolveApiBase } from "@/lib/http/resolveApiBase";
import {
registerAuthHeaderNameGetter,
registerAuthTokenGetter,
registerBaseUrlGetter,
registerErrorHandler,
} from "@/lib/http/runtime";
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
// to the backend — use relative URLs (null) so rewrites can intercept them.
const resolveDefaultBase = (fallback: string | null): string | null =>
process.env.NEXT_PUBLIC_BASE_URL
? process.env.NEXT_PUBLIC_BASE_URL
: isLocal && process.env.NEXT_PUBLIC_USE_REWRITES !== "true"
? "http://localhost:4000"
: fallback;
const defaultProxyBaseUrl = resolveDefaultBase(null);
const WORKER_URL_KEY = "litellm_worker_url";
// If a worker URL is in localStorage, use it as the initial proxyBaseUrl.
// This survives page navigation.
const _rawWorkerUrl = typeof window !== "undefined" ? window.localStorage.getItem(WORKER_URL_KEY) : null;
// Validate stored worker URL — reject non-HTTP schemes to prevent exfiltration
const _initialWorkerUrl = (() => {
if (!_rawWorkerUrl) return null;
try {
const parsed = new URL(_rawWorkerUrl);
if (parsed.protocol === "http:" || parsed.protocol === "https:") return _rawWorkerUrl;
} catch {
/* invalid URL */
}
// Invalid URL in storage — clear it
if (typeof window !== "undefined") window.localStorage.removeItem(WORKER_URL_KEY);
return null;
})();
export let proxyBaseUrl: string | null = _initialWorkerUrl ?? defaultProxyBaseUrl;
if (isLocal != true) {
console.log = function () {};
}
const getWindowLocation = () => {
if (typeof window === "undefined") {
return null;
}
return window.location;
};
const updateProxyBaseUrl = (serverRootPath: string, receivedProxyBaseUrl: string | null = null) => {
/**
* Special function for updating the proxy base url. Should only be called by getUiConfig.
*/
// If a worker URL is in localStorage, don't let getUiConfig overwrite it
if (typeof window !== "undefined" && window.localStorage.getItem(WORKER_URL_KEY)) {
return;
}
proxyBaseUrl = resolveApiBase({
explicitBase: receivedProxyBaseUrl || resolveDefaultBase(getWindowLocation()?.origin ?? null),
serverRootPath,
});
};
const updateServerRootPath = (receivedServerRootPath: string) => {
setServerRootPath(receivedServerRootPath);
};
export const getProxyBaseUrl = (): string => {
if (proxyBaseUrl) {
return proxyBaseUrl;
}
const browserLocation = getWindowLocation();
return browserLocation?.origin ?? "";
};
/**
* Switch API calls to point at a worker (or back to the control plane).
* Persists to localStorage so it survives page navigation. Also updates the
* module-level proxyBaseUrl so in-flight code in this JS execution sees the
* new value immediately.
*/
function isValidHttpUrl(url: string): boolean {
try {
const parsed = new URL(url);
return parsed.protocol === "http:" || parsed.protocol === "https:";
} catch {
return false;
}
}
export function switchToWorkerUrl(workerUrl: string | null): void {
if (workerUrl && !isValidHttpUrl(workerUrl)) {
return;
}
if (typeof window !== "undefined") {
if (workerUrl) {
window.localStorage.setItem(WORKER_URL_KEY, workerUrl);
} else {
window.localStorage.removeItem(WORKER_URL_KEY);
}
}
proxyBaseUrl = workerUrl ?? defaultProxyBaseUrl;
}
const HTTP_REQUEST = {
GET: "GET",
POST: "POST",
PUT: "PUT",
DELETE: "DELETE",
};
export interface Model {
model_name: string;
litellm_params: object;
model_info: object | null;
}
interface PromptInfo {
prompt_type: string;
environment?: string;
}
export interface PromptSpec {
prompt_id: string;
litellm_params: object;
prompt_info: PromptInfo;
created_at?: string;
updated_at?: string;
version?: number; // Explicit version number for version history
environment?: string;
created_by?: string;
}
export interface PromptTemplateBase {
litellm_prompt_id: string;
content: string;
metadata?: Record<string, unknown> | null;
}
interface PromptInfoResponse {
prompt_spec: PromptSpec;
raw_prompt_template: PromptTemplateBase | null;
environments?: string[];
}
export interface ListPromptsResponse {
prompts: PromptSpec[];
}
export interface Organization {
organization_id: string;
organization_alias: string;
budget_id: string;
metadata: Record<string, any>;
models: string[];
spend: number;
model_spend: Record<string, number>;
created_at: string;
created_by: string;
updated_at: string;
updated_by: string;
litellm_budget_table: any; // Simplified to any since we don't need the detailed structure
teams: any[] | null;
users: any[] | null;
members: any[] | null;
object_permission?: ObjectPermission | null;
}
export interface CredentialItem {
credential_name: string;
credential_values: any;
credential_info: {
custom_llm_provider?: string;
description?: string;
required?: boolean;
};
}
export interface ProviderCredentialFieldMetadata {
key: string;
label: string;
placeholder?: string | null;
tooltip?: string | null;
required?: boolean;
field_type?: "text" | "password" | "select" | "upload" | "textarea";
options?: string[] | null;
default_value?: string | null;
}
export interface ProviderCreateInfo {
provider: string;
provider_display_name: string;
litellm_provider: string;
default_model_placeholder?: string | null;
credential_fields: ProviderCredentialFieldMetadata[];
}
export interface AgentCredentialFieldMetadata {
key: string;
label: string;
placeholder?: string | null;
tooltip?: string | null;
required?: boolean;
field_type?: "text" | "password" | "select" | "upload" | "textarea";
options?: string[] | null;
default_value?: string | null;
include_in_litellm_params?: boolean;
validation_pattern?: string | null;
validation_message?: string | null;
}
export interface AgentCreateInfo {
agent_type: string;
agent_type_display_name: string;
description?: string | null;
logo_url?: string | null;
credential_fields: AgentCredentialFieldMetadata[];
litellm_params_template?: Record<string, string> | null;
model_template?: string | null;
use_a2a_form_fields?: boolean;
}
interface PublicModelHubInfo {
docs_title: string;
custom_docs_description: string | null;
litellm_version: string;
// Supports both old format (Record<string, string>) and new format (Record<string, {url: string, index: number}>)
useful_links: Record<string, string | { url: string; index: number }>;
}
export interface WorkerInfo {
worker_id: string;
name: string;
url: string;
}
export interface LiteLLMWellKnownUiConfig {
server_root_path: string;
proxy_base_url: string | null;
auto_redirect_to_sso: boolean;
admin_ui_disabled: boolean;
sso_configured: boolean;
hide_default_credentials_hint?: boolean;
is_control_plane?: boolean;
workers?: WorkerInfo[];
}
export interface CredentialsResponse {
credentials: CredentialItem[];
}
let lastErrorTime = 0;
export const handleError = async (errorData: string | any) => {
const currentTime = Date.now();
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);
if (errorString.includes("Authentication Error - Expired Key")) {
toast.info("UI Session Expired. Logging out.");
lastErrorTime = currentTime;
clearTokenCookies();
const browserLocation = getWindowLocation();
if (browserLocation) {
window.location.href = browserLocation.pathname;
}
}
lastErrorTime = currentTime;
}
};
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 = proxyBaseUrl ? `${proxyBaseUrl}/public/providers/fields` : `/public/providers/fields`;
const response = await fetch(url, {
method: "GET",
});
if (!response.ok) {
const errorText = await response.text();
console.error("Failed to fetch provider create metadata:", response.status, errorText);
throw new Error("Failed to load provider configuration");
}
const jsonData: ProviderCreateInfo[] = await response.json();
return jsonData;
};
export interface ComplexityScorerDefaults {
tier_boundaries: Record<string, number>;
token_thresholds: Record<string, number>;
dimension_weights: Record<string, number>;
}
export const getComplexityScorerDefaults = async (): Promise<ComplexityScorerDefaults> => {
/**
* Fetch the complexity router's shipped heuristic scorer defaults from the proxy's public endpoint.
* The Advanced scoring controls prefill from these rather than from a copy in the dashboard, so a
* recalibration of the defaults cannot leave the form reporting numbers the router no longer uses.
*/
return await apiClient.get(`/public/complexity_router/scorer_defaults`);
};
export const getAutoRouterPresets = async (): Promise<AutoRouterPresetsResponse> => {
/**
* Fetch the auto-router preset catalog from the proxy's public endpoint. The template picker
* renders from this rather than from a copy in the dashboard, so a catalog update propagates
* without a dashboard release.
*/
return await apiClient.get(`/public/autorouter_presets`);
};
export const getAgentCreateMetadata = async (): Promise<AgentCreateInfo[]> => {
/**
* Fetch agent type metadata from the proxy's public endpoint.
* This is used by the UI to dynamically render agent-specific credential fields.
*/
const url = proxyBaseUrl ? `${proxyBaseUrl}/public/agents/fields` : `/public/agents/fields`;
const response = await fetch(url, {
method: "GET",
});
if (!response.ok) {
const errorText = await response.text();
console.error("Failed to fetch agent create metadata:", response.status, errorText);
throw new Error("Failed to load agent configuration");
}
const jsonData: AgentCreateInfo[] = await response.json();
return jsonData;
};
// Global variable for the header name
let globalLitellmHeaderName: string = "Authorization";
// Function to set the global header name
export function setGlobalLitellmHeaderName(headerName: string = "Authorization") {
globalLitellmHeaderName = headerName;
}
// Function to get the global header name
export function getGlobalLitellmHeaderName(): string {
return globalLitellmHeaderName;
}
export const apiClient = createApiClient({
getBaseUrl: getProxyBaseUrl,
getAuthHeaderName: getGlobalLitellmHeaderName,
onError: handleError,
});
registerBaseUrlGetter(getProxyBaseUrl);
registerAuthHeaderNameGetter(getGlobalLitellmHeaderName);
registerAuthTokenGetter(() => decodeToken(getCookie("token"))?.key ?? null);
registerErrorHandler(handleError);
export const makeModelGroupPublic = async (accessToken: string, modelGroups: string[]) => {
const url = proxyBaseUrl ? `${proxyBaseUrl}/model_group/make_public` : `/model_group/make_public`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model_groups: modelGroups,
}),
});
return response.json();
};
export const getUiConfig = async () => {
/**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 response = await fetch(url);
const jsonData: LiteLLMWellKnownUiConfig = await response.json();
/**
* Update the proxy base url and server root path
*/
updateServerRootPath(jsonData.server_root_path);
updateProxyBaseUrl(jsonData.server_root_path, jsonData.proxy_base_url);
return jsonData;
};
export const getPublicModelHubInfo = async () => {
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;
};
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 () => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/public/litellm_model_cost_map` : `/public/litellm_model_cost_map`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const jsonData = await response.json();
return jsonData;
} catch (error) {
console.error("Failed to get model cost map:", error);
throw error;
}
};
export const reloadModelCostMap = async (accessToken: string) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/reload/model_cost_map` : `/reload/model_cost_map`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
const jsonData = await response.json();
return jsonData;
} catch (error) {
console.error("Failed to reload model cost map:", error);
throw error;
}
};
export const scheduleModelCostMapReload = async (accessToken: string, hours: number) => {
try {
const url = proxyBaseUrl
? `${proxyBaseUrl}/schedule/model_cost_map_reload?hours=${hours}`
: `/schedule/model_cost_map_reload?hours=${hours}`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
const jsonData = await response.json();
return jsonData;
} catch (error) {
console.error("Failed to schedule model cost map reload:", error);
throw error;
}
};
export const cancelModelCostMapReload = async (accessToken: string) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/schedule/model_cost_map_reload` : `/schedule/model_cost_map_reload`;
const response = await fetch(url, {
method: "DELETE",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
const jsonData = await response.json();
return jsonData;
} catch (error) {
console.error("Failed to cancel model cost map reload:", error);
throw error;
}
};
export const getModelCostMapSource = async (accessToken: string) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/model/cost_map/source` : `/model/cost_map/source`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`HTTP ${response.status}: ${errorText}`);
}
const jsonData = await response.json();
return jsonData;
} catch (error) {
console.error("Failed to get model cost map source info:", error);
throw error;
}
};
export const getModelCostMapReloadStatus = async (accessToken: string) => {
try {
const url = proxyBaseUrl
? `${proxyBaseUrl}/schedule/model_cost_map_reload/status`
: `/schedule/model_cost_map_reload/status`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
console.error(`Status request failed with status: ${response.status}`);
const errorText = await response.text();
console.error("Error response:", errorText);
throw new Error(`HTTP ${response.status}: ${errorText}`);
}
const jsonData = await response.json();
return jsonData;
} catch (error) {
console.error("Failed to get model cost map reload status:", error);
throw error;
}
};
export const modelCreateCall = async (accessToken: string, formValues: Model) => {
try {
const data = await apiClient.post(`/model/new`, {
accessToken,
body: {
...formValues,
},
});
// Close any existing messages before showing new ones
toast.dismiss();
// Sequential success messages
toast.success(`Model ${formValues.model_name} created successfully`);
return data;
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
export const modelDeleteCall = async (accessToken: string, model_id: string) => {
try {
const data = await apiClient.post(`/model/delete`, {
accessToken,
body: {
id: model_id,
},
});
return data;
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
export const budgetDeleteCall = async (accessToken: string | null, budget_id: string) => {
if (accessToken == null) {
return;
}
try {
const data = await apiClient.post(`/budget/delete`, {
accessToken,
body: {
id: budget_id,
},
});
return data;
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
export const budgetCreateCall = async (
accessToken: string,
formValues: Record<string, any>, // Assuming formValues is an object
) => {
try {
const data = await apiClient.post(`/budget/new`, {
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 budgetUpdateCall = async (
accessToken: string,
formValues: Record<string, any>, // Assuming formValues is an object
) => {
try {
const data = await apiClient.post(`/budget/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 invitationCreateCall = async (
accessToken: string,
userID: string, // Assuming formValues is an object
) => {
try {
const data = await apiClient.post(`/invitation/new`, {
accessToken,
body: {
user_id: userID, // 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 alertingSettingsCall = async (accessToken: string) => {
/**
* Get all configurable params for setting a model
*/
try {
return await apiClient.get(`/alerting/settings`, { accessToken });
} catch (error) {
console.error("Failed to get callbacks:", error);
throw error;
}
};
export const keyCreateServiceAccountCall = async (
accessToken: string,
formValues: Record<string, any>, // Assuming formValues is an object
) => {
try {
// check if formValues.description is not undefined, make it a string and add it to formValues.metadata
if (formValues.description) {
// add to formValues.metadata
if (!formValues.metadata) {
formValues.metadata = {};
}
// value needs to be in "", valid JSON
formValues.metadata.description = formValues.description;
// remove descrption from formValues
delete formValues.description;
formValues.metadata = JSON.stringify(formValues.metadata);
}
// Parse JSON fields if they exist
for (const field of jsonFields) {
if (formValues[field]) {
// if there's an exception JSON.parse, show it in the message
try {
formValues[field] = JSON.parse(formValues[field]);
} catch (error) {
throw new Error(`Failed to parse ${field}: ` + error);
}
}
}
const url = proxyBaseUrl ? `${proxyBaseUrl}/key/service-account/generate` : `/key/service-account/generate`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
...formValues, // Include formValues in the request body
}),
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
console.error("Error response from the server:", errorData);
throw new Error(errorData);
}
const data = await response.json();
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 keyCreateCall = async (
accessToken: string,
userID: string,
formValues: Record<string, any>, // Assuming formValues is an object
) => {
try {
// check if formValues.description is not undefined, make it a string and add it to formValues.metadata
if (formValues.description) {
// add to formValues.metadat
if (!formValues.metadata) {
formValues.metadata = {};
}
// value needs to be in "", valid JSON
formValues.metadata.description = formValues.description;
// remove descrption from formValues
delete formValues.description;
formValues.metadata = JSON.stringify(formValues.metadata);
}
// Parse JSON fields if they exist
for (const field of jsonFields) {
if (formValues[field]) {
// if there's an exception JSON.parse, show it in the message
try {
formValues[field] = JSON.parse(formValues[field]);
} catch (error) {
throw new Error(`Failed to parse ${field}: ` + error);
}
}
}
const url = proxyBaseUrl ? `${proxyBaseUrl}/key/generate` : `/key/generate`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: userID,
...formValues, // Include formValues in the request body
}),
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
console.error("Error response from the server:", errorData);
throw new Error(errorData);
}
const data = await response.json();
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 keyCreateForAgentCall = async (
accessToken: string,
agentId: string,
keyAlias: string,
models: string[],
metadata?: Record<string, any>,
teamId?: string | null,
) => {
const url = proxyBaseUrl ? `${proxyBaseUrl}/key/generate` : `/key/generate`;
const body: Record<string, any> = {
agent_id: agentId,
key_alias: keyAlias,
models: models.length > 0 ? models : [],
};
if (teamId) {
body.team_id = teamId;
}
if (metadata && Object.keys(metadata).length > 0) {
body.metadata = metadata;
}
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error("Failed to create key for agent");
}
return response.json();
};
export const userCreateCall = async (
accessToken: string,
userID: string | null,
formValues: Record<string, any>, // Assuming formValues is an object
) => {
try {
// check if formValues.description is not undefined, make it a string and add it to formValues.metadata
if (formValues.description) {
// add to formValues.metadata
if (!formValues.metadata) {
formValues.metadata = {};
}
// value needs to be in "", valid JSON
formValues.metadata.description = formValues.description;
// remove descrption from formValues
delete formValues.description;
formValues.metadata = JSON.stringify(formValues.metadata);
}
formValues.auto_create_key = false;
// if formValues.metadata is not undefined, make it a valid dict
if (formValues.metadata) {
// if there's an exception JSON.parse, show it in the message
try {
formValues.metadata = JSON.parse(formValues.metadata);
} catch (error) {
throw new Error("Failed to parse metadata: " + error);
}
}
const url = proxyBaseUrl ? `${proxyBaseUrl}/user/new` : `/user/new`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
user_id: userID,
...formValues, // Include formValues in the request body
}),
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
console.error("Error response from the server:", errorData);
throw new Error(errorData);
}
const data = await response.json();
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 keyDeleteCall = async (accessToken: string, user_key: string) => {
try {
return await apiClient.post(`/key/delete`, { accessToken, body: { keys: [user_key] } });
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
export const userDeleteCall = async (accessToken: string, userIds: string[]) => {
try {
return await apiClient.post(`/user/delete`, { accessToken, body: { user_ids: userIds } });
} catch (error) {
console.error("Failed to delete user(s):", error);
throw error;
}
};
export const teamDeleteCall = async (accessToken: string, teamID: string) => {
try {
return await apiClient.post(`/team/delete`, { accessToken, body: { team_ids: [teamID] } });
} catch (error) {
console.error("Failed to delete key:", error);
throw error;
}
};
export interface UserInfo {
user_id: string;
user_email: string;
user_alias: string | null;
user_role: string;
spend: number;
max_budget: number | null;
models: string[];
key_count: number;
created_at: string;
updated_at: string;
sso_user_id: string | null;
budget_duration: string | null;
metadata?: Record<string, unknown> | null;
}
export type UserListResponse = {
page: number;
page_size: number;
total: number;
total_pages: number;
users: UserInfo[];
};
export const userListCall = async (
accessToken: string,
userIDs: string[] | null = null,
page: number | null = null,
page_size: number | null = null,
userEmail: string | null = null,
userRole: string | null = null,
team: string | null = null,
sso_user_id: string | null = null,
sortBy: string | null = null,
sortOrder: "asc" | "desc" | null = null,
organizationIds: string[] | null = null,
search: string | null = null,
) => {
/**
* Get all available teams on proxy
*/
try {
const data = (await apiClient.get(`/user/list`, {
accessToken,
query: {
user_ids: userIDs && userIDs.length > 0 ? userIDs.join(",") : undefined,
page: page || undefined,
page_size: page_size || undefined,
user_email: userEmail || undefined,
role: userRole || undefined,
team: team || undefined,
sso_user_ids: sso_user_id || undefined,
sort_by: sortBy || undefined,
sort_order: sortOrder || undefined,
organization_ids: organizationIds && organizationIds.length > 0 ? organizationIds.join(",") : undefined,
search: search || undefined,
},
})) as UserListResponse;
return data;
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
/**
* Response type for /v2/user/info — lightweight endpoint that returns only the user object.
*/
export interface UserInfoV2Response {
user_id: string;
user_email: string | null;
user_alias: string | null;
user_role: string | null;
spend: number;
max_budget: number | null;
models: string[];
budget_duration: string | null;
budget_reset_at: string | null;
metadata: Record<string, any> | null;
created_at: string | null;
updated_at: string | null;
sso_user_id: string | null;
teams: string[];
object_permission?: ObjectPermission | null;
model_max_budget?: ModelMaxBudget | null;
model_max_budget_usage?: Record<string, ModelBudgetUsage> | null;
}
/**
* Lightweight user info fetch from /v2/user/info.
* Returns only the user object — no keys, no teams objects.
*
* @param accessToken - Bearer token for auth
* @param userId - Optional user ID to look up. If omitted, returns the caller's own info.
*/
export const userGetInfoV2 = async (accessToken: string, userId?: string): Promise<UserInfoV2Response> => {
try {
return await apiClient.get(`/v2/user/info`, { accessToken, query: { user_id: userId || undefined } });
} catch (error) {
console.error("Failed to fetch user info v2:", error);
throw error;
}
};
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 } });
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
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,
userID: string | null = null,
teamID: string | null = null,
team_alias: string | null = null,
) => {
/**
* Get all available teams on proxy
*/
try {
return await apiClient.get(`/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 availableTeamListCall = async (accessToken: string) => {
/**
* Get all available teams on proxy
*/
try {
const data = await apiClient.get(`/team/available`, { accessToken });
return data;
} catch (error) {
throw error;
}
};
export const organizationListCall = async (
accessToken: string,
org_id: string | null = null,
org_alias: string | null = null,
) => {
/**
* Get all organizations on proxy
*/
try {
return await apiClient.get(`/organization/list`, {
accessToken,
query: {
org_id: org_id || undefined,
org_alias: org_alias || undefined,
},
});
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
export const organizationInfoCall = async (accessToken: string, organizationID: string) => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/organization/info` : `/organization/info`;
if (organizationID) {
url = `${url}?organization_id=${organizationID}`;
}
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
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 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`;
const response = await fetch(url, {
method: "DELETE",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
organization_ids: [organizationID],
}),
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error(`Error deleting organization: ${errorData}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to delete organization:", error);
throw error;
}
};
export const transformRequestCall = async (accessToken: string, request: object) => {
/**
* Transform request
*/
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/utils/transform_request` : `/utils/transform_request`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(request),
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
type DailyActivityQueryValue = string | number | string[] | null | undefined;
const DEFAULT_DAILY_ACTIVITY_PAGE_SIZE = "1000";
const appendDailyActivityQueryParam = (params: URLSearchParams, key: string, value: DailyActivityQueryValue) => {
if (value === null || value === undefined) {
return;
}
if (Array.isArray(value)) {
if (value.length > 0) {
params.append(key, value.join(","));
}
return;
}
params.append(key, `${value}`);
};
const buildDailyActivityUrl = (
endpoint: string,
startTime: Date,
endTime: Date,
page: number,
extraQueryParams?: Record<string, DailyActivityQueryValue>,
) => {
const resolvedEndpoint = endpoint.startsWith("/") ? endpoint : `/${endpoint}`;
const baseUrl = proxyBaseUrl ? `${proxyBaseUrl}${resolvedEndpoint}` : resolvedEndpoint;
const params = new URLSearchParams();
params.append("start_date", formatDate(startTime));
params.append("end_date", formatDate(endTime));
params.append("page_size", DEFAULT_DAILY_ACTIVITY_PAGE_SIZE);
params.append("page", page.toString());
// Send timezone offset so backend can adjust date range for UTC storage
params.append("timezone", new Date().getTimezoneOffset().toString());
if (extraQueryParams) {
Object.entries(extraQueryParams).forEach(([key, value]) => {
appendDailyActivityQueryParam(params, key, value);
});
}
const queryString = params.toString();
return queryString ? `${baseUrl}?${queryString}` : baseUrl;
};
type DailyActivityCallOptions = {
accessToken: string;
endpoint: string;
startTime: Date;
endTime: Date;
page?: number;
extraQueryParams?: Record<string, DailyActivityQueryValue>;
};
const fetchDailyActivity = async ({
accessToken,
endpoint,
startTime,
endTime,
page = 1,
extraQueryParams,
}: DailyActivityCallOptions) => {
try {
const url = buildDailyActivityUrl(endpoint, startTime, endTime, page, extraQueryParams);
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error(`Failed to fetch daily activity (${endpoint}):`, error);
throw error;
}
};
export const userDailyActivityCall = async (
accessToken: string,
startTime: Date,
endTime: Date,
page: number = 1,
userId: string | null = null,
includeCurrentUtcDay: boolean = false,
apiKey: string | null = null,
) => {
/**
* Get daily user activity on proxy
*/
return fetchDailyActivity({
accessToken,
endpoint: "/user/daily/activity",
startTime,
endTime,
page,
extraQueryParams: {
user_id: userId,
include_current_utc_day: includeCurrentUtcDay ? "true" : undefined,
api_key: apiKey,
},
});
};
export const tagDailyActivityCall = async (
accessToken: string,
startTime: Date,
endTime: Date,
page: number = 1,
tags: string[] | null = null,
) => {
/**
* Get daily user activity on proxy
*/
return fetchDailyActivity({
accessToken,
endpoint: "/tag/daily/activity",
startTime,
endTime,
page,
extraQueryParams: {
tags,
},
});
};
export const teamDailyActivityCall = async (
accessToken: string,
startTime: Date,
endTime: Date,
page: number = 1,
teamIds: string[] | null = null,
) => {
/**
* Get daily user activity on proxy
*/
return fetchDailyActivity({
accessToken,
endpoint: "/team/daily/activity",
startTime,
endTime,
page,
extraQueryParams: {
team_ids: teamIds,
exclude_team_ids: "litellm-dashboard",
},
});
};
export const teamDailyActivityAggregatedCall = async (
accessToken: string,
startTime: Date,
endTime: Date,
teamIds: string[] | null = null,
) => {
/**
* Get aggregated daily team activity with per-team breakdown (no pagination)
*/
try {
return await apiClient.get(`/team/daily/activity/aggregated`, {
accessToken,
query: {
start_date: formatDate(startTime),
end_date: formatDate(endTime),
timezone: new Date().getTimezoneOffset().toString(),
team_ids: teamIds && teamIds.length > 0 ? teamIds.join(",") : undefined,
exclude_team_ids: "litellm-dashboard",
},
});
} catch (error) {
console.error("Failed to fetch aggregated team daily activity:", error);
throw error;
}
};
export type TeamUserSpendResponse = components["schemas"]["TeamUserSpendResponse"];
export const teamSpendByUserCall = async (
accessToken: string,
startTime: Date,
endTime: Date,
teamIds: string[],
): Promise<TeamUserSpendResponse> =>
apiClient.get<TeamUserSpendResponse>(`/team/spend/by_user`, {
accessToken,
query: {
start_date: formatDate(startTime),
end_date: formatDate(endTime),
team_ids: teamIds.join(","),
},
});
export const organizationDailyActivityCall = async (
accessToken: string,
startTime: Date,
endTime: Date,
page: number = 1,
organizationIds: string[] | null = null,
) => {
return fetchDailyActivity({
accessToken,
endpoint: "/organization/daily/activity",
startTime,
endTime,
page,
extraQueryParams: {
organization_ids: organizationIds,
},
});
};
export const customerDailyActivityCall = async (
accessToken: string,
startTime: Date,
endTime: Date,
page: number = 1,
customerIds: string[] | null = null,
) => {
return fetchDailyActivity({
accessToken,
endpoint: "/customer/daily/activity",
startTime,
endTime,
page,
extraQueryParams: {
end_user_ids: customerIds,
},
});
};
export const agentDailyActivityCall = async (
accessToken: string,
startTime: Date,
endTime: Date,
page: number = 1,
agentIds: string[] | null = null,
) => {
return fetchDailyActivity({
accessToken,
endpoint: "/agent/daily/activity",
startTime,
endTime,
page,
extraQueryParams: {
agent_ids: agentIds,
},
});
};
export const getOnboardingCredentials = async (inviteUUID: string) => {
/**
* Get all models on proxy
*/
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/onboarding/get_token` : `/onboarding/get_token`;
url += `?invite_link=${inviteUUID}`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
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 claimOnboardingToken = async (
accessToken: string,
inviteUUID: string,
userID: string,
password: string,
) => {
try {
const data = await apiClient.post(`/onboarding/claim_token`, {
accessToken,
body: {
invitation_link: inviteUUID,
user_id: userID,
password: password,
},
});
return data;
// Handle success - you might want to update some state or UI based on the created key
} catch (error) {
console.error("Failed to delete key:", error);
throw error;
}
};
export const regenerateKeyCall = async (accessToken: string, keyToRegenerate: string, formData: any) => {
try {
const url = proxyBaseUrl
? `${proxyBaseUrl}/key/${keyToRegenerate}/regenerate`
: `/key/${keyToRegenerate}/regenerate`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(formData),
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to regenerate key:", error);
throw error;
}
};
let ModelListerrorShown = false;
let errorTimer: NodeJS.Timeout | null = null;
export const modelInfoCall = async (
accessToken: string,
userID: string,
userRole: string,
page: number = 1,
size: number = 50,
search?: string,
modelId?: string,
teamId?: string,
sortBy?: string,
sortOrder?: string,
excludeAutoRouters?: boolean,
modelName?: string,
accessGroup?: string,
wildcardOnly?: boolean,
) => {
/**
* Get all models on proxy
*/
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/v2/model/info` : `/v2/model/info`;
const params = new URLSearchParams();
params.append("include_team_models", "true");
params.append("page", page.toString());
params.append("size", size.toString());
if (search && search.trim()) {
params.append("search", search.trim());
}
if (modelName && modelName.trim()) {
params.append("model", modelName.trim());
}
if (modelId && modelId.trim()) {
params.append("modelId", modelId.trim());
}
if (teamId && teamId.trim()) {
params.append("teamId", teamId.trim());
}
if (sortBy && sortBy.trim()) {
params.append("sortBy", sortBy.trim());
}
if (sortOrder && sortOrder.trim()) {
params.append("sortOrder", sortOrder.trim());
}
if (excludeAutoRouters) {
params.append("exclude_auto_routers", "true");
}
if (accessGroup && accessGroup.trim()) {
params.append("access_group", accessGroup.trim());
}
if (wildcardOnly) {
params.append("wildcard_only", "true");
}
if (params.toString()) {
url += `?${params.toString()}`;
}
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
let errorData = await response.text();
errorData += `error shown=${ModelListerrorShown}`;
if (!ModelListerrorShown) {
if (errorData.includes("No model list passed")) {
errorData = "No Models Exist. Click Add Model to get started.";
}
toast.info(errorData);
ModelListerrorShown = true;
if (errorTimer) clearTimeout(errorTimer);
errorTimer = setTimeout(() => {
ModelListerrorShown = false;
}, 10000);
}
throw new Error("Network response was not ok");
}
const data = await response.json();
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 modelInfoV1Call = async (accessToken: string, modelId: string) => {
/**
* Get all models on proxy
*/
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/v1/model/info` : `/v1/model/info`;
url += `?litellm_model_id=${modelId}`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
export const modelHubPublicModelsCall = async () => {
const url = proxyBaseUrl ? `${proxyBaseUrl}/public/model_hub` : `/public/model_hub`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
console.error(`modelHubPublicModelsCall failed with status ${response.status}`);
return [];
}
return response.json();
};
export const agentHubPublicModelsCall = async () => {
const url = proxyBaseUrl ? `${proxyBaseUrl}/public/agent_hub` : `/public/agent_hub`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
console.error(`agentHubPublicModelsCall failed with status ${response.status}`);
return [];
}
return response.json();
};
export const mcpHubPublicServersCall = async () => {
const url = proxyBaseUrl ? `${proxyBaseUrl}/public/mcp_hub` : `/public/mcp_hub`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
console.error(`mcpHubPublicServersCall failed with status ${response.status}`);
return [];
}
return response.json();
};
export const skillHubPublicCall = async () => {
const url = proxyBaseUrl ? `${proxyBaseUrl}/public/skill_hub` : `/public/skill_hub`;
const response = await fetch(url, {
method: "GET",
headers: { "Content-Type": "application/json" },
});
if (!response.ok) {
console.error(`skillHubPublicCall failed with status ${response.status}`);
return { plugins: [] };
}
return response.json();
};
export const modelHubCall = async (accessToken: string) => {
/**
* Get all models on proxy
*/
try {
const data = await apiClient.get(`/model_group/info`, { 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 create key:", error);
throw error;
}
};
// Function to get allowed IPs
export const getAllowedIPs = async (accessToken: string) => {
try {
const data = await apiClient.get(`/get/allowed_ips`, { accessToken });
return data.data; // Assuming the API returns { data: [...] }
} catch (error) {
console.error("Failed to get allowed IPs:", error);
throw error;
}
};
// Function to add an allowed IP
export const addAllowedIP = async (accessToken: string, ip: string) => {
try {
const data = await apiClient.post(`/add/allowed_ip`, { accessToken, body: { ip: ip } });
return data;
} catch (error) {
console.error("Failed to add allowed IP:", error);
throw error;
}
};
// Function to delete an allowed IP
export const deleteAllowedIP = async (accessToken: string, ip: string) => {
try {
const data = await apiClient.post(`/delete/allowed_ip`, { accessToken, body: { ip: ip } });
return data;
} catch (error) {
console.error("Failed to delete allowed IP:", error);
throw error;
}
};
export const updateUsefulLinksCall = async (
accessToken: string,
useful_links: Record<string, string | { url: string; index: number }>,
) => {
try {
return await apiClient.post(`/model_hub/update_useful_links`, {
accessToken,
body: { useful_links: useful_links },
});
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
export const modelAvailableCall = async (
accessToken: string,
userID: string,
userRole: string,
return_wildcard_routes: boolean = false,
teamID: string | null = null,
include_model_access_groups: boolean = false,
only_model_access_groups: boolean = false,
scope?: string,
) => {
/**
* Get all the models user has access to
*/
try {
return await apiClient.get(`/models`, {
accessToken,
query: {
include_model_access_groups: "True",
return_wildcard_routes: return_wildcard_routes === true ? "True" : undefined,
only_model_access_groups: only_model_access_groups === true ? "True" : undefined,
team_id: teamID || undefined,
scope: scope || undefined,
},
});
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
export const teamSpendLogsCall = async (accessToken: string) => {
try {
const data = await apiClient.get(`/global/spend/teams`, { accessToken });
return data;
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
export const tagsSpendLogsCall = async (
accessToken: string,
startTime: string | undefined,
endTime: string | undefined,
tags: string[] | undefined,
) => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/global/spend/tags` : `/global/spend/tags`;
if (startTime && endTime) {
url = `${url}?start_date=${startTime}&end_date=${endTime}`;
}
// if tags, convert the list to a comma separated string
if (tags) {
url += `&tags=${tags.join(",")}`;
}
const response = await fetch(`${url}`, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
export const allTagNamesCall = async (accessToken: string) => {
try {
const data = await apiClient.get(`/global/spend/all_tag_names`, { accessToken });
return data;
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
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`, {
accessToken,
query: {
user_email: params.get("user_email") || undefined,
user_id: params.get("user_id") || undefined,
team_id: params.get("team_id") || undefined,
},
});
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
/**
* Optional query params for /spend/logs/ui - matches backend spend_management_endpoints.py
*/
interface UiSpendLogsParams {
api_key?: string;
team_id?: string;
request_id?: string;
session_id?: string;
user_id?: string;
end_user?: string;
status_filter?: string;
cache_hit_filter?: string;
/** Filter by model name (e.g. "gpt-4") */
model?: string;
/** Filter by model ID (litellm model deployment id) */
model_id?: string;
key_alias?: string;
error_code?: string;
error_message?: string;
sort_by?: string;
sort_order?: "asc" | "desc";
min_spend?: number;
max_spend?: number;
exclude_internal_health_checks?: boolean;
group_by_session?: boolean;
session_cursor?: string;
search?: string;
}
interface UiSpendLogsCallOptions {
accessToken: string;
start_date: string;
end_date: string;
page?: number;
page_size?: number;
params?: UiSpendLogsParams;
}
export const uiSpendLogsCall = async ({
accessToken,
start_date,
end_date,
page = 1,
page_size = 50,
params = {},
}: UiSpendLogsCallOptions) => {
try {
// Construct base URL
let url = proxyBaseUrl ? `${proxyBaseUrl}/spend/logs/ui` : `/spend/logs/ui`;
const queryParams = new URLSearchParams();
queryParams.append("start_date", start_date);
queryParams.append("end_date", end_date);
queryParams.append("page", page.toString());
queryParams.append("page_size", page_size.toString());
// Add optional params only when explicitly provided
for (const [key, value] of Object.entries(params)) {
if (value == null) continue;
if (key === "min_spend" || key === "max_spend") {
queryParams.append(key, value.toString());
} else if (typeof value === "boolean") {
if (value) queryParams.append(key, "true");
} else if (typeof value === "string" && value !== "") {
queryParams.append(key, String(value));
}
}
const queryString = queryParams.toString();
if (queryString) {
url += `?${queryString}`;
}
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to fetch spend logs:", error);
throw error;
}
};
export const adminSpendLogsCall = async (accessToken: string) => {
try {
const data = await apiClient.get(`/global/spend/logs`, { accessToken });
return data;
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
export const adminTopKeysCall = async (accessToken: string) => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/global/spend/keys?limit=5` : `/global/spend/keys?limit=5`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
export const adminTopEndUsersCall = async (
accessToken: string,
keyToken: string | null,
startTime: string | undefined,
endTime: string | undefined,
) => {
try {
const body = keyToken
? { api_key: keyToken, startTime: startTime, endTime: endTime }
: { startTime: startTime, endTime: endTime };
const data = await apiClient.post(`/global/spend/end_users`, { accessToken, body });
return data;
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
export const adminspendByProvider = async (
accessToken: string,
startTime: string | undefined,
endTime: string | undefined,
) => {
try {
const data = await apiClient.get(`/global/spend/provider`, {
accessToken,
query: {
...(startTime && endTime ? { start_date: startTime, end_date: endTime } : {}),
},
});
return data;
} catch (error) {
console.error("Failed to fetch spend data:", error);
throw error;
}
};
export const adminGlobalActivity = async (
accessToken: string,
startTime: string | undefined,
endTime: string | undefined,
) => {
try {
const data = await apiClient.get(`/global/activity`, {
accessToken,
query: startTime && endTime ? { start_date: startTime, end_date: endTime } : undefined,
});
return data;
} catch (error) {
console.error("Failed to fetch spend data:", error);
throw error;
}
};
export const adminGlobalActivityPerModel = async (
accessToken: string,
startTime: string | undefined,
endTime: string | undefined,
) => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/global/activity/model` : `/global/activity/model`;
if (startTime && endTime) {
url += `?start_date=${startTime}&end_date=${endTime}`;
}
const requestOptions = {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
},
};
const response = await fetch(url, requestOptions);
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to fetch spend data:", error);
throw error;
}
};
export const adminTopModelsCall = async (accessToken: string) => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/global/spend/models?limit=5` : `/global/spend/models?limit=5`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
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>,
model_info: Record<string, any>,
mode: string,
) => {
try {
// Construct the URL based on environment
const url = proxyBaseUrl ? `${proxyBaseUrl}/health/test_connection` : `/health/test_connection`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
},
body: JSON.stringify({
litellm_params: litellm_params,
model_info: model_info,
mode: mode,
}),
});
// Check for non-JSON responses first
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.`,
);
}
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
if (data.status === "error") {
return data; // Return the full error response
} else {
return {
status: "error",
message: data.error?.message || `Connection test failed: ${response.status} ${response.statusText}`,
};
}
}
return data;
} catch (error) {
console.error("Model connection test error:", error);
// For network errors or other exceptions, still throw
throw error;
}
};
export type ModelGroupConnectionResult = { status: "success" } | { status: "error"; error: string };
/**
* Test an existing model group by routing a minimal request through the proxy
* exactly as production would (by public model_group name). Unlike
* /health/test_connection, this needs no litellm_params resolution: the router
* resolves the group, credentials, and provider. Used by the auto-router Test
* Connection to probe each tier's model group and the embedding model.
*/
/**
* Build the minimal request that probes a model group by public name. No
* max_tokens: reasoning models (o1/o3/...) reject a tiny cap with "max_tokens
* reached" because reasoning tokens count against it, which would show a false
* failure for a reachable tier.
*/
export const buildModelGroupTestRequest = (
modelGroup: string,
mode: "chat" | "embedding",
requestParams: Record<string, unknown> = {},
): { path: string; body: Record<string, unknown> } =>
mode === "embedding"
? { path: "/v1/embeddings", body: { model: modelGroup, input: "test from litellm" } }
: {
path: "/v1/chat/completions",
body: { ...requestParams, model: modelGroup, messages: [{ role: "user", content: "test from litellm" }] },
};
export const testModelGroupConnection = async (
accessToken: string,
modelGroup: string,
mode: "chat" | "embedding",
requestParams?: Record<string, unknown>,
): Promise<ModelGroupConnectionResult> => {
const { path, body } = buildModelGroupTestRequest(modelGroup, mode, requestParams);
try {
await apiClient.post(path, { accessToken, body });
return { status: "success" };
} catch (error) {
return { status: "error", error: error instanceof Error ? error.message : String(error) };
}
};
export interface AutoRouterRoutingTestRequest {
prompt: string;
complexity_router_config: ComplexityRouterConfigPayload | Record<string, unknown>;
saved_model_id?: string;
default_model?: string;
router_name?: string;
team_id?: string;
}
export interface AutoRouterRoutingTestResult {
routed_model: string;
routed_model_configured: boolean;
routing_decision: RoutingDecision;
}
export type AutoRouterRoutingTestResponse =
| { status: "success"; result: AutoRouterRoutingTestResult }
| { status: "error"; error: string };
export const testAutoRouterRouting = async (
accessToken: string,
request: AutoRouterRoutingTestRequest,
): Promise<AutoRouterRoutingTestResponse> => {
try {
const result = await apiClient.post<AutoRouterRoutingTestResult>("/auto_router/test_routing", {
accessToken,
body: request,
});
return { status: "success", result };
} catch (error) {
return { status: "error", error: extractProxyErrorMessage(error) };
}
};
export interface ComplexityRouterConfigValidation {
valid: boolean;
error?: string | null;
}
// Dry-runs the same write gate /model/new and /model/update apply, so a save that would come back
// as a raw 400 shows the backend's own message inline first. Transport failures fail open: the
// write gate stays authoritative.
export const validateAutoRouterConfig = async (
accessToken: string,
complexityRouterConfig: Record<string, unknown>,
teamId?: string,
): Promise<ComplexityRouterConfigValidation> => {
try {
return await apiClient.post<ComplexityRouterConfigValidation>("/auto_router/validate_complexity_router_config", {
accessToken,
body: { complexity_router_config: complexityRouterConfig, ...(teamId && { team_id: teamId }) },
});
} catch (error) {
console.warn("Could not dry-run the complexity router config; the save will be validated server side", error);
return { valid: true };
}
};
// ... existing code ...
export const keyInfoV1Call = async (accessToken: string, key: string) => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/key/info` : `/key/info`;
url = `${url}?key=${key}`; // Add key as query parameter
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
// Remove body since this is a GET request
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
toast.fromError("Failed to fetch key info - " + errorData);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to fetch key info:", error);
throw error;
}
};
export const keyListCall = async (
accessToken: string,
organizationID: string | null,
teamID: string | null,
selectedKeyAlias: string | null,
userID: string | null,
keyHash: string | null,
page: number,
pageSize: number,
sortBy: string | null = null,
sortOrder: string | null = null,
expand: string | null = null,
status: string | null = null,
) => {
/**
* Get all available teams on proxy
*/
try {
return await apiClient.get(`/key/list`, {
accessToken,
query: {
team_id: teamID || undefined,
organization_id: organizationID || undefined,
key_alias: selectedKeyAlias || undefined,
key_hash: keyHash || undefined,
user_id: userID || undefined,
page: page ? page.toString() : undefined,
size: pageSize ? pageSize.toString() : undefined,
sort_by: sortBy || undefined,
sort_order: sortOrder || undefined,
expand: expand || undefined,
status: status || undefined,
return_full_object: "true",
include_team_keys: "true",
include_created_by_keys: "true",
// /key/list is exact by default; opt in so the key-list search box keeps
// matching partial user_id/key_alias.
substring_matching: "true",
},
});
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
export interface PaginatedKeyAliasResponse {
aliases: string[];
total_count: number;
current_page: number;
total_pages: number;
size: number;
}
export const keyAliasesCall = async (
accessToken: string,
page: number = 1,
size: number = 50,
search?: string,
team_id?: string,
): Promise<PaginatedKeyAliasResponse> => {
/**
* Get key aliases from proxy with pagination and optional search
*/
try {
return await apiClient.get(`/key/aliases`, {
accessToken,
query: {
page: String(page),
size: String(size),
search: search || undefined,
team_id: team_id || undefined,
},
});
} catch (error) {
console.error("Failed to fetch key aliases:", error);
throw error;
}
};
export const userDailyActivityAggregatedCall = async (
accessToken: string,
startTime: Date,
endTime: Date,
...options: [userId?: string | null, includeCurrentUtcDay?: boolean, apiKey?: string | null]
) => {
/**
* Get aggregated daily user activity (no pagination)
*/
const [userId = null, includeCurrentUtcDay = false, apiKey = null] = options;
try {
const formatDate = (date: Date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
return await apiClient.get(`/user/daily/activity/aggregated`, {
accessToken,
query: {
start_date: formatDate(startTime),
end_date: formatDate(endTime),
timezone: new Date().getTimezoneOffset().toString(),
// Passed raw, matching the paginated caller: both serializers drop null and undefined,
// and both keep "". An empty filter must not vanish, or a request scoped to one user or
// key would silently widen into an unscoped, proxy-wide read.
user_id: userId,
include_current_utc_day: includeCurrentUtcDay ? "true" : undefined,
api_key: apiKey,
},
});
} catch (error) {
console.error("Failed to fetch aggregated user daily activity:", error);
throw error;
}
};
export const gatewayDailyActivityCall = async (accessToken: string, startTime: Date, endTime: Date) => {
/**
* Get gateway request counts (SGR) recorded by the proxy middleware.
* Deployment-wide and admin-only; carries no per-key or per-user dimension.
*/
try {
const formatDate = (date: Date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
return await apiClient.get(`/gateway/daily/activity`, {
accessToken,
query: {
start_date: formatDate(startTime),
end_date: formatDate(endTime),
},
});
} catch (error) {
console.error("Failed to fetch gateway daily activity:", error);
throw error;
}
};
export const getPossibleUserRoles = async (accessToken: string) => {
try {
const data = (await apiClient.get(`/user/available_roles`, { accessToken })) as Record<
string,
Record<string, string>
>;
return data;
// Handle success - you might want to update some state or UI based on the created key
} catch (error) {
throw error;
}
};
export const teamCreateCall = async (
accessToken: string,
formValues: Record<string, any>, // Assuming formValues is an object
) => {
try {
if (formValues.metadata) {
// if there's an exception JSON.parse, show it in the message
try {
formValues.metadata = JSON.parse(formValues.metadata);
} catch (error) {
throw new Error("Failed to parse metadata: " + error);
}
}
const data = await apiClient.post(`/team/new`, {
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 credentialCreateCall = async (
accessToken: string,
formValues: Record<string, any>, // Assuming formValues is an object
) => {
try {
if (formValues.metadata) {
// if there's an exception JSON.parse, show it in the message
try {
formValues.metadata = JSON.parse(formValues.metadata);
} catch (error) {
throw new Error("Failed to parse metadata: " + error);
}
}
const data = await apiClient.post(`/credentials`, {
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 credentialListCall = async (accessToken: string) => {
/**
* Get all available teams on proxy
*/
try {
const data = await apiClient.get(`/credentials`, { 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 create key:", error);
throw error;
}
};
export const credentialGetCall = async (accessToken: string, credentialName: string | null, modelId: string | null) => {
try {
let path = `/credentials`;
if (credentialName) {
path += `/by_name/${credentialName}`;
} else if (modelId) {
path += `/by_model/${modelId}`;
}
const data = await apiClient.get(path, { 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 create key:", error);
throw error;
}
};
export const credentialDeleteCall = async (accessToken: string, credentialName: string) => {
try {
const data = await apiClient.delete(`/credentials/${credentialName}`, { 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 delete key:", error);
throw error;
}
};
export const credentialUpdateCall = async (
accessToken: string,
credentialName: string,
formValues: Record<string, any>, // Assuming formValues is an object
) => {
try {
if (formValues.metadata) {
// if there's an exception JSON.parse, show it in the message
try {
formValues.metadata = JSON.parse(formValues.metadata);
} catch (error) {
throw new Error("Failed to parse metadata: " + error);
}
}
const data = await apiClient.patch(`/credentials/${credentialName}`, {
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 keyUpdateCall = async (
accessToken: string,
formValues: Record<string, any>, // Assuming formValues is an object
) => {
try {
if (formValues.model_tpm_limit) {
// if there's an exception JSON.parse, show it in the message
try {
formValues.model_tpm_limit = JSON.parse(formValues.model_tpm_limit);
} catch (error) {
throw new Error("Failed to parse model_tpm_limit: " + error);
}
}
if (formValues.model_rpm_limit) {
// if there's an exception JSON.parse, show it in the message
try {
formValues.model_rpm_limit = JSON.parse(formValues.model_rpm_limit);
} catch (error) {
throw new Error("Failed to parse model_rpm_limit: " + error);
}
}
const url = proxyBaseUrl ? `${proxyBaseUrl}/key/update` : `/key/update`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
...formValues, // Include formValues in the request body
}),
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
console.error("Error response from the server:", errorData);
throw new Error(errorData);
}
const data = await response.json();
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 teamUpdateCall = async (
accessToken: string,
formValues: Record<string, any>, // Assuming formValues is an object
) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/team/update` : `/team/update`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
...formValues, // Include formValues in the request body
}),
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
console.error("Error response from the server:", errorData);
toast.fromError("Failed to update team settings: " + unwrapProxyErrorMessage(errorData));
throw new Error(errorData);
}
const data = (await response.json()) as { data: Team; team_id: string };
return data;
// Handle success - you might want to update some state or UI based on the updated team
} catch (error) {
console.error("Failed to update team:", error);
throw error;
}
};
/**
* Patch update a model
*
* @param accessToken
* @param formValues
* @returns
*/
export const modelPatchUpdateCall = async (
accessToken: string,
formValues: Record<string, any>, // Assuming formValues is an object
modelId: string,
) => {
try {
// Intentionally not logging the payload: it can contain freshly-entered
// provider secrets (api_key, vertex_credentials, AWS creds).
const url = proxyBaseUrl ? `${proxyBaseUrl}/model/${modelId}/update` : `/model/${modelId}/update`;
const response = await fetch(url, {
method: "PATCH",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
...formValues, // Include formValues in the request body
}),
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
console.error("Error update from the server:", errorData);
throw new Error("Network response was not ok");
}
const data = await response.json();
return data;
// Handle success - you might want to update some state or UI based on the created key
} catch (error) {
console.error("Failed to update model:", error);
throw error;
}
};
export interface Member {
role: string;
user_id: string | null;
user_email?: string | null;
user_alias?: string | null;
max_budget_in_team?: number | null;
tpm_limit?: number | null;
rpm_limit?: number | null;
budget_duration?: string | null;
allowed_models?: string[] | null;
}
export const teamMemberAddCall = async (accessToken: string, teamId: string, formValues: Member) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/team/member_add` : `/team/member_add`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
team_id: teamId,
member: formValues,
}),
});
if (!response.ok) {
// Read and parse JSON error body
const errorText = await response.text();
let parsedError: any = {};
try {
parsedError = JSON.parse(errorText);
} catch (e) {
console.warn("Failed to parse error body as JSON:", errorText);
}
const rawMessage = parsedError?.detail?.error || "Failed to add team member";
const err = new Error(rawMessage);
(err as any).raw = parsedError;
throw err;
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to create key:", error);
throw error;
}
};
export const teamBulkMemberAddCall = async (
accessToken: string,
teamId: string,
members: Member[] | null,
maxBudgetInTeam?: number,
allUsers?: boolean,
) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/team/bulk_member_add` : `/team/bulk_member_add`;
let requestBody: any = {
team_id: teamId,
};
if (allUsers) {
requestBody.all_users = true;
} else {
requestBody.members = members;
}
if (maxBudgetInTeam !== undefined && maxBudgetInTeam !== null) {
requestBody.max_budget_in_team = maxBudgetInTeam;
}
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(requestBody),
});
if (!response.ok) {
// Read and parse JSON error body
const errorText = await response.text();
let parsedError: any = {};
try {
parsedError = JSON.parse(errorText);
} catch (e) {
console.warn("Failed to parse error body as JSON:", errorText);
}
const rawMessage = parsedError?.detail?.error || "Failed to bulk add team members";
const err = new Error(rawMessage);
(err as any).raw = parsedError;
throw err;
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to bulk add team members:", error);
throw error;
}
};
export const teamMemberUpdateCall = async (
accessToken: string,
teamId: string,
formValues: Member, // Assuming formValues is an object
) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/team/member_update` : `/team/member_update`;
const requestBody: any = {
team_id: teamId,
role: formValues.role,
user_id: formValues.user_id,
};
const orNull = (value: unknown) => (value === undefined || value === null || value === "" ? null : value);
if (formValues.user_email !== undefined) {
requestBody.user_email = formValues.user_email;
}
if ("max_budget_in_team" in formValues) {
requestBody.max_budget_in_team = orNull(formValues.max_budget_in_team);
}
if ("tpm_limit" in formValues) {
requestBody.tpm_limit = orNull(formValues.tpm_limit);
}
if ("rpm_limit" in formValues) {
requestBody.rpm_limit = orNull(formValues.rpm_limit);
}
if ("budget_duration" in formValues) {
requestBody.budget_duration = orNull(formValues.budget_duration);
}
if (formValues.allowed_models !== undefined) {
requestBody.allowed_models = formValues.allowed_models;
}
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(requestBody),
});
if (!response.ok) {
// Read and parse JSON error body
const errorText = await response.text();
let parsedError: any = {};
try {
parsedError = JSON.parse(errorText);
} catch (e) {
console.warn("Failed to parse error body as JSON:", errorText);
}
const rawMessage = parsedError?.detail?.error || "Failed to add team member";
const err = new Error(rawMessage);
(err as any).raw = parsedError;
throw err;
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to update team member:", error);
throw error;
}
};
export const teamMemberDeleteCall = async (
accessToken: string,
teamId: string,
formValues: Member, // Assuming formValues is an object
) => {
try {
const data = await apiClient.post(`/team/member_delete`, {
accessToken,
body: {
team_id: teamId,
...(formValues.user_email !== undefined && {
user_email: formValues.user_email,
}),
...(formValues.user_id !== undefined && {
user_id: formValues.user_id,
}),
},
});
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 organizationMemberAddCall = async (
accessToken: string,
organizationId: string,
formValues: Member, // Assuming formValues is an object
) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/organization/member_add` : `/organization/member_add`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
organization_id: organizationId,
member: formValues, // Include formValues in the request body
}),
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
console.error("Error response from the server:", errorData);
throw new Error(errorData);
}
const data = await response.json();
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 organization member:", error);
throw error;
}
};
export const organizationMemberDeleteCall = async (accessToken: string, organizationId: string, userId: string) => {
try {
const data = await apiClient.delete(`/organization/member_delete`, {
accessToken,
body: {
organization_id: organizationId,
user_id: userId,
},
});
return data;
} catch (error) {
console.error("Failed to delete organization member:", error);
throw error;
}
};
export const organizationMemberUpdateCall = async (
accessToken: string,
organizationId: string,
formValues: Member, // Assuming formValues is an object
) => {
try {
const data = await apiClient.patch(`/organization/member_update`, {
accessToken,
body: {
organization_id: organizationId,
...formValues, // Include formValues in the request body
},
});
return data;
} catch (error) {
console.error("Failed to update organization member:", error);
throw error;
}
};
export const userUpdateUserCall = async (
accessToken: string,
formValues: any, // Assuming formValues is an object
userRole: string | null,
) => {
try {
const response_body = { ...formValues };
if (userRole !== null) {
response_body["user_role"] = userRole;
}
const data = (await apiClient.post(`/user/update`, { accessToken, body: response_body })) as {
user_id: string;
data: UserInfo;
};
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 userBulkUpdateUserCall = async (
accessToken: string,
formValues: any, // Assuming formValues is an object
userIds?: string[], // Optional - if not provided, will update all users
allUsers: boolean = false, // Flag to update all users
) => {
try {
let request_body: Record<string, any>;
if (allUsers) {
// Update all users mode
request_body = {
all_users: true,
user_updates: formValues,
};
} else if (userIds && userIds.length > 0) {
// Update specific users mode
let users = [];
for (const user_id of userIds) {
users.push({
user_id: user_id,
...formValues,
});
}
request_body = {
users: users,
};
} else {
throw new Error("Must provide either userIds or set allUsers=true");
}
const data = (await apiClient.post(`/user/bulk_update`, { accessToken, body: request_body })) as {
results: Array<{
user_id?: string;
user_email?: string;
success: boolean;
error?: string;
updated_user?: any;
}>;
total_requested: number;
successful_updates: number;
failed_updates: number;
};
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 serviceHealthCheck = async (accessToken: string, service: string) => {
try {
let url = proxyBaseUrl
? `${proxyBaseUrl}/health/services?service=${service}`
: `/health/services?service=${service}`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
// throw error with message
throw new Error(errorData);
}
const data = await response.json();
// You can add additional logic here based on the response if needed
return data;
} catch (error) {
console.error("Failed to perform health check:", error);
throw error;
}
};
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
*/
try {
const data = await apiClient.get(`/get/config/callbacks`, { 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 getGeneralSettingsCall = async (accessToken: string) => {
try {
let url = proxyBaseUrl
? `${proxyBaseUrl}/config/list?config_type=general_settings`
: `/config/list?config_type=general_settings`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
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 getRouterSettingsCall = async (accessToken: string) => {
try {
const data = await apiClient.get(`/router/settings`, { accessToken });
return data;
} catch (error) {
console.error("Failed to get router settings:", error);
throw error;
}
};
export const getCacheSettingsCall = async (accessToken: string) => {
try {
const data = await apiClient.get(`/cache/settings`, { accessToken });
return data;
} catch (error) {
console.error("Failed to get cache settings:", error);
throw error;
}
};
export const testCacheConnectionCall = async (accessToken: string, cacheSettings: Record<string, any>) => {
try {
const data = await apiClient.post(`/cache/settings/test`, {
accessToken,
body: {
cache_settings: cacheSettings,
},
});
return data;
} catch (error) {
console.error("Failed to test cache connection:", error);
throw error;
}
};
export const updateCacheSettingsCall = async (accessToken: string, cacheSettings: Record<string, any>) => {
try {
const data = await apiClient.post(`/cache/settings`, {
accessToken,
body: {
cache_settings: cacheSettings,
},
});
return data;
} catch (error) {
console.error("Failed to update cache settings:", error);
throw error;
}
};
export const getCoordinationRedisSettingsCall = async (
accessToken: string,
): Promise<CoordinationRedisSettingsResponse> => {
try {
return await apiClient.get<CoordinationRedisSettingsResponse>(`/coordination_redis/settings`, { accessToken });
} catch (error) {
console.error("Failed to get coordination redis settings:", error);
throw error;
}
};
export const testCoordinationRedisConnectionCall = async (
accessToken: string,
settings: CoordinationRedisSettings,
): Promise<CoordinationRedisTestResponse> => {
try {
return await apiClient.post<CoordinationRedisTestResponse>(`/coordination_redis/settings/test`, {
accessToken,
body: { settings },
});
} catch (error) {
console.error("Failed to test coordination redis connection:", error);
throw error;
}
};
export const updateCoordinationRedisSettingsCall = async (
accessToken: string,
settings: CoordinationRedisSettings,
): Promise<void> => {
try {
await apiClient.post(`/coordination_redis/settings`, {
accessToken,
body: { settings },
});
} catch (error) {
console.error("Failed to update coordination redis settings:", error);
throw error;
}
};
export const getPassThroughEndpointsCall = async (accessToken: string, teamId?: string | null) => {
try {
let path = `/config/pass_through_endpoint`;
if (teamId) {
path += `/team/${teamId}`;
}
const data = await apiClient.get(path, { 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 getConfigFieldSetting = async (accessToken: string, fieldName: string) => {
try {
let url = proxyBaseUrl
? `${proxyBaseUrl}/config/field/info?field_name=${fieldName}`
: `/config/field/info?field_name=${fieldName}`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
// Handle success - you might want to update some state or UI based on the created key
} catch (error) {
console.error("Failed to set callbacks:", error);
throw error;
}
};
export const createPassThroughEndpoint = async (accessToken: string, formValues: Record<string, any>) => {
/**
* Set callbacks on proxy
*/
try {
const data = await apiClient.post(`/config/pass_through_endpoint`, {
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 set callbacks:", error);
throw error;
}
};
export const updateConfigFieldSetting = async (accessToken: string, fieldName: string, fieldValue: any) => {
try {
let formData = {
field_name: fieldName,
field_value: fieldValue,
config_type: "general_settings",
};
const data = await apiClient.post(`/config/field/update`, { accessToken, body: formData });
toast.success("Successfully updated value!");
return data;
// Handle success - you might want to update some state or UI based on the created key
} catch (error) {
console.error("Failed to set callbacks:", error);
throw error;
}
};
export const deleteConfigFieldSetting = async (accessToken: string, fieldName: string) => {
try {
let formData = {
field_name: fieldName,
config_type: "general_settings",
};
const data = await apiClient.post(`/config/field/delete`, { accessToken, body: formData });
toast.success("Field reset on proxy");
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 deletePassThroughEndpointsCall = async (accessToken: string, endpointId: string) => {
try {
let url = proxyBaseUrl
? `${proxyBaseUrl}/config/pass_through_endpoint?endpoint_id=${endpointId}`
: `/config/pass_through_endpoint?endpoint_id=${endpointId}`;
const response = await fetch(url, {
method: "DELETE",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
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 setCallbacksCall = async (accessToken: string, formValues: Record<string, any>) => {
/**
* Set callbacks on proxy
*/
try {
const data = await apiClient.post(`/config/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 set callbacks:", error);
throw error;
}
};
export const individualModelHealthCheckCall = async (accessToken: string, modelId: string) => {
/**
* Run health check for a specific model using model ID (so each deployment is checked separately).
*/
try {
let url = proxyBaseUrl
? `${proxyBaseUrl}/health?model_id=${encodeURIComponent(modelId)}`
: `/health?model_id=${encodeURIComponent(modelId)}`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error(`Failed to call /health for model id ${modelId}:`, error);
throw error;
}
};
export const cachingHealthCheckCall = async (accessToken: string) => {
/**
* Get all the models user has access to
*/
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/cache/ping` : `/cache/ping`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error(errorData);
}
const data = await response.json();
return data;
// Handle success - you might want to update some state or UI based on the created key
} catch (error) {
console.error("Failed to call /cache/ping:", error);
throw error;
}
};
export const latestHealthChecksCall = async (accessToken: string) => {
/**
* Get the latest health check status for all models
*/
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/health/latest` : `/health/latest`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error(errorData);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to call /health/latest:", error);
throw error;
}
};
export const getProxyUISettings = async (accessToken: string) => {
/**
* Get all the models user has access to
*/
try {
const data = await apiClient.get(`/sso/get/ui_settings`, { 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 getUISettings = async (accessToken: string) => {
/**
* Get UI-specific configuration flags from the database
*/
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/get/ui_settings` : `/get/ui_settings`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
console.error("Failed to get UI settings:", errorMessage);
return null;
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to get UI settings:", error);
return null;
}
};
export const getMCPSemanticFilterSettings = async (accessToken: string) => {
/**
* Get MCP semantic filter configuration
*/
try {
const data = await apiClient.get(`/get/mcp_semantic_filter_settings`, { accessToken });
return data;
} catch (error) {
console.error("Failed to get MCP semantic filter settings:", error);
throw error;
}
};
export const updateMCPSemanticFilterSettings = async (accessToken: string, settings: Record<string, any>) => {
/**
* Update MCP semantic filter settings
* Settings will be applied across all pods within 10 seconds
*/
try {
const url = proxyBaseUrl
? `${proxyBaseUrl}/update/mcp_semantic_filter_settings`
: `/update/mcp_semantic_filter_settings`;
const response = await fetch(url, {
method: "PATCH",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(settings),
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to update MCP semantic filter settings:", error);
throw error;
}
};
export const testMCPSemanticFilter = async (accessToken: string, model: string, query: string) => {
/**
* Test MCP semantic filter by making a responses API call
* Returns both the response data and headers containing filter information
*/
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/responses` : `/v1/responses`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: model,
input: [
{
role: "user",
content: query,
type: "message",
},
],
tools: [
{
type: "mcp",
server_url: "litellm_proxy",
require_approval: "never",
},
],
tool_choice: "required",
}),
});
// Extract headers before checking response status
const filterHeader = response.headers.get("x-litellm-semantic-filter");
const toolsHeader = response.headers.get("x-litellm-semantic-filter-tools");
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
// Return both data and headers
return {
data,
headers: {
filter: filterHeader,
tools: toolsHeader,
},
};
} catch (error) {
console.error("Failed to test MCP semantic filter:", error);
throw error;
}
};
export const getGuardrailsList = async (accessToken: string) => {
try {
const v2Url = proxyBaseUrl ? `${proxyBaseUrl}/v2/guardrails/list` : `/v2/guardrails/list`;
const response = await fetch(v2Url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
throw new Error(`v2 guardrails/list returned ${response.status}`);
}
const data = await response.json();
return data;
} catch (error) {
try {
const v1Url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/list` : `/guardrails/list`;
const fallbackResponse = await fetch(v1Url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!fallbackResponse.ok) {
const errorData = await fallbackResponse.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
return await fallbackResponse.json();
} catch (fallbackError) {
console.error("Failed to get guardrails list:", fallbackError);
throw fallbackError;
}
}
};
// Team guardrail submissions (admin)
export interface GuardrailSubmissionItem {
guardrail_id: string;
guardrail_name: string;
status: string; // "pending_review" | "active" | "rejected"
team_id?: string | null;
team_guardrail?: boolean; // true when submitted via team (team_id set)
litellm_params?: Record<string, unknown> | null;
guardrail_info?: Record<string, unknown> | null;
submitted_by_user_id?: string | null;
submitted_by_email?: string | null;
submitted_at?: string | null;
reviewed_at?: string | null;
created_at?: string | null;
updated_at?: string | null;
}
interface GuardrailSubmissionSummary {
total: number;
pending_review: number;
active: number;
rejected: number;
}
interface ListGuardrailSubmissionsResponse {
submissions: GuardrailSubmissionItem[];
summary: GuardrailSubmissionSummary;
}
export const listGuardrailSubmissions = async (
accessToken: string,
params?: { status?: string; team_id?: string; team_guardrail?: boolean; search?: string },
): Promise<ListGuardrailSubmissionsResponse> => {
return apiClient.get<ListGuardrailSubmissionsResponse>(`/guardrails/submissions`, {
accessToken,
query: {
...(params?.status ? { status: params.status } : {}),
...(params?.team_id ? { team_id: params.team_id } : {}),
...(params?.team_guardrail !== undefined ? { team_guardrail: params.team_guardrail } : {}),
...(params?.search ? { search: params.search } : {}),
},
});
};
export const approveGuardrailSubmission = async (
accessToken: string,
guardrailId: string,
): Promise<{ guardrail_id: string; status: string; message: string }> => {
return apiClient.post<{ guardrail_id: string; status: string; message: string }>(
`/guardrails/submissions/${encodeURIComponent(guardrailId)}/approve`,
{ accessToken },
);
};
export const rejectGuardrailSubmission = async (
accessToken: string,
guardrailId: string,
): Promise<{ guardrail_id: string; status: string; message: string }> => {
return apiClient.post<{ guardrail_id: string; status: string; message: string }>(
`/guardrails/submissions/${encodeURIComponent(guardrailId)}/reject`,
{ accessToken },
);
};
// Guardrails / Policies usage (dashboard)
export const getGuardrailsUsageLogs = async (
accessToken: string,
options: {
guardrailId?: string;
policyId?: string;
page?: number;
pageSize?: number;
action?: string;
startDate?: string;
endDate?: string;
},
) => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/usage/logs` : `/guardrails/usage/logs`;
const params = new URLSearchParams();
if (options.guardrailId) params.append("guardrail_id", options.guardrailId);
if (options.policyId) params.append("policy_id", options.policyId);
if (options.page != null) params.append("page", String(options.page));
if (options.pageSize != null) params.append("page_size", String(options.pageSize));
if (options.action) params.append("action", options.action);
if (options.startDate) params.append("start_date", options.startDate);
if (options.endDate) params.append("end_date", options.endDate);
if (params.toString()) url += `?${params.toString()}`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(deriveErrorMessage(errorData));
}
return response.json();
} catch (error) {
console.error("Failed to get guardrails usage logs:", error);
throw error;
}
};
// ─────────────────────────────────────────────────────────────────────────────
// Policy CRUD API Calls
// ─────────────────────────────────────────────────────────────────────────────
export const getPoliciesList = async (accessToken: string) => {
try {
const data = await apiClient.get(`/policies/list`, { accessToken });
return data;
} catch (error) {
console.error("Failed to get policies list:", error);
throw error;
}
};
interface GuardrailInputs {
texts?: string[];
images?: string[];
[key: string]: unknown;
}
interface TestPoliciesAndGuardrailsRequest {
policy_names?: string[] | null;
guardrail_names?: string[] | null;
/** Single input (legacy). Use inputs_list for per-input batch processing. */
inputs?: GuardrailInputs | null;
/** List of inputs; each processed separately for batch compliance testing. */
inputs_list?: GuardrailInputs[] | null;
request_data?: Record<string, unknown>;
input_type?: "request" | "response";
/** When set, backend runs chat completion with this model/agent per input and includes agent_response in each result. */
agent_id?: string | null;
}
interface GuardrailErrorEntry {
guardrail_name: string;
message: string;
}
interface TestPoliciesAndGuardrailsResultItem {
inputs: Record<string, unknown>;
guardrail_errors: GuardrailErrorEntry[];
/** Present when request included agent_id; serialized chat completion response. */
agent_response?: Record<string, unknown>;
}
interface TestPoliciesAndGuardrailsResponse {
inputs?: Record<string, unknown>;
guardrail_errors?: GuardrailErrorEntry[];
/** Present when inputs_list was used; one result per input. */
results?: TestPoliciesAndGuardrailsResultItem[];
}
export const testPoliciesAndGuardrails = async (
accessToken: string,
body: TestPoliciesAndGuardrailsRequest,
signal?: AbortSignal,
): Promise<TestPoliciesAndGuardrailsResponse> => {
try {
const url = proxyBaseUrl
? `${proxyBaseUrl}/utils/test_policies_and_guardrails`
: `/utils/test_policies_and_guardrails`;
const response = await fetch(url, {
method: "POST",
signal,
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
policy_names: body.policy_names ?? null,
guardrail_names: body.guardrail_names ?? null,
inputs: body.inputs ?? null,
inputs_list: body.inputs_list ?? null,
request_data: body.request_data ?? {},
input_type: body.input_type ?? "request",
agent_id: body.agent_id ?? null,
}),
});
if (!response.ok) {
const errorData = await response.text();
let errorMessage = "Failed to test policies and guardrails";
try {
const errorJson = JSON.parse(errorData);
if (errorJson.detail)
errorMessage = typeof errorJson.detail === "string" ? errorJson.detail : JSON.stringify(errorJson.detail);
else if (errorJson.message) errorMessage = errorJson.message;
} catch {
errorMessage = errorData || errorMessage;
}
handleError(errorMessage);
throw new Error(errorMessage);
}
return await response.json();
} catch (error) {
console.error("Failed to test policies and guardrails:", error);
throw error;
}
};
export const getPolicyInfoWithGuardrails = async (accessToken: string, policyName: string) => {
try {
const data = await apiClient.get(`/policy/info/${policyName}`, { accessToken });
return data;
} catch (error) {
console.error(`Failed to get policy info for ${policyName}:`, error);
throw error;
}
};
export const getPolicyTemplates = async (accessToken: string) => {
try {
const data = await apiClient.get(`/policy/templates`, { accessToken });
return data;
} catch (error) {
console.error("Failed to get policy templates:", error);
throw error;
}
};
export const enrichPolicyTemplate = async (
accessToken: string,
templateId: string,
parameters: Record<string, string>,
model?: string,
competitors?: string[],
) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/policy/templates/enrich` : `/policy/templates/enrich`;
const body: any = { template_id: templateId, parameters };
if (model) body.model = model;
if (competitors) body.competitors = competitors;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to enrich policy template:", error);
throw error;
}
};
export const suggestPolicyTemplates = async (
accessToken: string,
attackExamples: string[],
description: string,
model: string,
) => {
try {
return await apiClient.post(`/policy/templates/suggest`, {
accessToken,
body: {
attack_examples: attackExamples.filter((e) => e.trim()),
description,
model,
},
});
} catch (error) {
console.error("Failed to suggest policy templates:", error);
throw error;
}
};
export const testPolicyTemplate = async (accessToken: string, guardrailDefinitions: any[], text: string) => {
try {
return await apiClient.post(`/policy/templates/test`, {
accessToken,
body: {
guardrail_definitions: guardrailDefinitions,
text,
},
});
} catch (error) {
console.error("Failed to test policy template:", error);
throw error;
}
};
export const enrichPolicyTemplateStream = async (
accessToken: string,
templateId: string,
parameters: Record<string, string>,
model: string,
onCompetitor: (name: string) => void,
onDone: (result: {
competitors: string[];
competitor_variations: Record<string, string[]>;
guardrailDefinitions: any[];
}) => void,
onError?: (error: string) => void,
options?: { instruction?: string; existingCompetitors?: string[] },
onStatus?: (message: string) => void,
) => {
const url = proxyBaseUrl ? `${proxyBaseUrl}/policy/templates/enrich/stream` : `/policy/templates/enrich/stream`;
const body: any = { template_id: templateId, parameters, model };
if (options?.instruction) body.instruction = options.instruction;
if (options?.existingCompetitors) body.competitors = options.existingCompetitors;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const reader = response.body?.getReader();
if (!reader) throw new Error("No response body");
const decoder = new TextDecoder();
let buffer = "";
// eslint-disable-next-line no-constant-condition -- stream read loop
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() || "";
for (const line of lines) {
if (!line.startsWith("data: ")) continue;
try {
const event = JSON.parse(line.slice(6));
if (event.type === "competitor") {
onCompetitor(event.name);
} else if (event.type === "status") {
onStatus?.(event.message);
} else if (event.type === "done") {
onDone(event);
} else if (event.type === "error") {
onError?.(event.message);
}
} catch {
// skip malformed events
}
}
}
};
export interface UsageAiToolCallEvent {
tool_name: string;
tool_label: string;
arguments: Record<string, string>;
status: "running" | "complete" | "error";
error?: string;
}
export const usageAiChatStream = async (
accessToken: string,
messages: { role: string; content: string }[],
model: string,
onChunk: (content: string) => void,
onDone: () => void,
onError?: (error: string) => void,
onStatus?: (message: string) => void,
onToolCall?: (event: UsageAiToolCallEvent) => void,
signal?: AbortSignal,
) => {
const url = proxyBaseUrl ? `${proxyBaseUrl}/usage/ai/chat` : `/usage/ai/chat`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ messages, model }),
signal,
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const reader = response.body?.getReader();
if (!reader) throw new Error("No response body");
const decoder = new TextDecoder();
let buffer = "";
// eslint-disable-next-line no-constant-condition -- stream read loop
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\n");
buffer = lines.pop() || "";
for (const line of lines) {
if (!line.startsWith("data: ")) continue;
try {
const event = JSON.parse(line.slice(6));
if (event.type === "chunk") {
onChunk(event.content);
} else if (event.type === "status") {
onStatus?.(event.message);
} else if (event.type === "tool_call") {
onToolCall?.(event as UsageAiToolCallEvent);
} else if (event.type === "done") {
onDone();
} else if (event.type === "error") {
onError?.(event.message);
}
} catch {
// skip malformed events
}
}
}
};
export const createPolicyCall = async (accessToken: string, policyData: any) => {
try {
const data = await apiClient.post(`/policies`, { accessToken, body: policyData });
return data;
} catch (error) {
console.error("Failed to create policy:", error);
throw error;
}
};
export const updatePolicyCall = async (accessToken: string, policyId: string, policyData: any) => {
try {
const data = await apiClient.put(`/policies/${policyId}`, { accessToken, body: policyData });
return data;
} catch (error) {
console.error("Failed to update policy:", error);
throw error;
}
};
export const listPolicyVersions = async (
accessToken: string,
policyName: string,
): Promise<{ policy_name: string; versions: any[]; total_count: number }> => {
try {
const encodedName = encodeURIComponent(policyName);
const url = proxyBaseUrl
? `${proxyBaseUrl}/policies/name/${encodedName}/versions`
: `/policies/name/${encodedName}/versions`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
return await response.json();
} catch (error) {
console.error("Failed to list policy versions:", error);
throw error;
}
};
export const createPolicyVersion = async (
accessToken: string,
policyName: string,
sourcePolicyId?: string | null,
): Promise<any> => {
try {
const encodedName = encodeURIComponent(policyName);
const url = proxyBaseUrl
? `${proxyBaseUrl}/policies/name/${encodedName}/versions`
: `/policies/name/${encodedName}/versions`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ source_policy_id: sourcePolicyId ?? undefined }),
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
return await response.json();
} catch (error) {
console.error("Failed to create policy version:", error);
throw error;
}
};
export const updatePolicyVersionStatus = async (
accessToken: string,
policyId: string,
versionStatus: "published" | "production",
): Promise<any> => {
try {
return await apiClient.put(`/policies/${policyId}/status`, {
accessToken,
body: { version_status: versionStatus },
});
} catch (error) {
console.error("Failed to update policy version status:", error);
throw error;
}
};
export const deletePolicyCall = async (accessToken: string, policyId: string) => {
try {
const data = await apiClient.delete(`/policies/${policyId}`, { accessToken });
return data;
} catch (error) {
console.error("Failed to delete policy:", error);
throw error;
}
};
export const getPolicyInfo = async (accessToken: string, policyId: string) => {
try {
const data = await apiClient.get(`/policies/${policyId}`, { accessToken });
return data;
} catch (error) {
console.error("Failed to get policy info:", error);
throw error;
}
};
// Policy Attachments API Calls
export const getPolicyAttachmentsList = async (accessToken: string) => {
try {
const data = await apiClient.get(`/policies/attachments/list`, { accessToken });
return data;
} catch (error) {
console.error("Failed to get policy attachments list:", error);
throw error;
}
};
export const createPolicyAttachmentCall = async (accessToken: string, attachmentData: any) => {
try {
const data = await apiClient.post(`/policies/attachments`, { accessToken, body: attachmentData });
return data;
} catch (error) {
console.error("Failed to create policy attachment:", error);
throw error;
}
};
export const deletePolicyAttachmentCall = async (accessToken: string, attachmentId: string) => {
try {
const url = proxyBaseUrl
? `${proxyBaseUrl}/policies/attachments/${attachmentId}`
: `/policies/attachments/${attachmentId}`;
const response = await fetch(url, {
method: "DELETE",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to delete policy attachment:", error);
throw error;
}
};
export const testPipelineCall = async (
accessToken: string,
pipeline: any,
testMessages: Array<{ role: string; content: string }>,
) => {
try {
const data = await apiClient.post(`/policies/test-pipeline`, {
accessToken,
body: { pipeline, test_messages: testMessages },
});
return data;
} catch (error) {
console.error("Failed to test pipeline:", error);
throw error;
}
};
export const getResolvedGuardrails = async (accessToken: string, policyId: string) => {
try {
const url = proxyBaseUrl
? `${proxyBaseUrl}/policies/${policyId}/resolved-guardrails`
: `/policies/${policyId}/resolved-guardrails`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to get resolved guardrails:", error);
throw error;
}
};
export const resolvePoliciesCall = async (
accessToken: string,
context: { team_alias?: string; key_alias?: string; model?: string; tags?: string[] },
) => {
try {
return await apiClient.post(`/policies/resolve`, { accessToken, body: context });
} catch (error) {
console.error("Failed to resolve policies:", error);
throw error;
}
};
export const estimateAttachmentImpactCall = async (accessToken: string, attachmentData: any) => {
try {
const url = proxyBaseUrl
? `${proxyBaseUrl}/policies/attachments/estimate-impact`
: `/policies/attachments/estimate-impact`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(attachmentData),
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
return await response.json();
} catch (error) {
console.error("Failed to estimate attachment impact:", error);
throw error;
}
};
export const getPromptsList = async (accessToken: string, environment?: string): Promise<ListPromptsResponse> => {
try {
return await apiClient.get(`/prompts/list`, { accessToken, query: { environment: environment || undefined } });
} catch (error) {
console.error("Failed to get prompts list:", error);
throw error;
}
};
export const getPromptInfo = async (
accessToken: string,
promptId: string,
environment?: string,
): Promise<PromptInfoResponse> => {
try {
return await apiClient.get(`/prompts/${promptId}/info`, {
accessToken,
query: { environment: environment || undefined },
});
} catch (error) {
console.error("Failed to get prompt info:", error);
throw error;
}
};
export const getPromptVersions = async (
accessToken: string,
promptId: string,
environment?: string,
): Promise<ListPromptsResponse> => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/prompts/${promptId}/versions` : `/prompts/${promptId}/versions`;
if (environment) {
url += `?environment=${encodeURIComponent(environment)}`;
}
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
// Don't throw global error for 404 (no versions found) as we might want to handle it gracefully
if (response.status !== 404) {
handleError(errorMessage);
}
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to get prompt versions:", error);
throw error;
}
};
export const createPromptCall = async (accessToken: string, promptData: any) => {
try {
const data = await apiClient.post(`/prompts`, { accessToken, body: promptData });
return data;
} catch (error) {
console.error("Failed to create prompt:", error);
throw error;
}
};
export const updatePromptCall = async (accessToken: string, promptId: string, promptData: any) => {
try {
const data = await apiClient.put(`/prompts/${promptId}`, { accessToken, body: promptData });
return data;
} catch (error) {
console.error("Failed to update prompt:", error);
throw error;
}
};
export const deletePromptCall = async (accessToken: string, promptId: string, environment?: string) => {
try {
const data = await apiClient.delete(`/prompts/${promptId}`, {
accessToken,
query: { environment: environment || undefined },
});
return data;
} catch (error) {
console.error("Failed to delete prompt:", error);
throw error;
}
};
export const convertPromptFileToJson = async (
accessToken: string,
file: File,
): Promise<{ prompt_id: string; json_data: any }> => {
try {
const formData = new FormData();
formData.append("file", file);
const url = proxyBaseUrl ? `${proxyBaseUrl}/utils/dotprompt_json_converter` : `/utils/dotprompt_json_converter`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
},
body: formData,
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
return await response.json();
} catch (error) {
console.error("Failed to convert prompt file:", error);
throw error;
}
};
export const createAgentCall = async (accessToken: string, agentData: any) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/agents` : `/v1/agents`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
...agentData,
}),
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error(errorData);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to create agent:", error);
throw error;
}
};
export interface DiscoveredAgentCard {
protocolVersion?: string;
name?: string;
description?: string;
version?: string;
url?: string;
iconUrl?: string;
documentationUrl?: string;
defaultInputModes?: string[];
defaultOutputModes?: string[];
capabilities?: Record<string, any>;
skills?: Array<{
id?: string;
name?: string;
description?: string;
tags?: string[];
examples?: string[];
[key: string]: any;
}>;
provider?: { organization?: string; url?: string };
[key: string]: any;
}
export interface DiscoverAgentCardResponse {
url: string;
agent_card: DiscoveredAgentCard;
}
/**
* How the backend should locate the upstream agent card.
*
* - ``well_known_fallback`` (default): pure A2A — try the three standard
* well-known paths under the base URL.
* - ``langgraph_platform``: LangGraph Platform — hits the canonical
* well-known path with an ``assistant_id`` query parameter, because
* LangGraph mounts one shared card endpoint per deployment.
*/
export type DiscoveryMode = "well_known_fallback" | "langgraph_platform";
export interface DiscoverAgentCardOptions {
discovery_mode?: DiscoveryMode;
/** Mode-specific params. ``langgraph_platform`` requires ``assistant_id``. */
params?: Record<string, any>;
}
export const discoverAgentCardCall = async (
accessToken: string,
url: string,
options?: DiscoverAgentCardOptions,
): Promise<DiscoverAgentCardResponse> => {
const endpoint = proxyBaseUrl ? `${proxyBaseUrl}/v1/a2a/discover` : `/v1/a2a/discover`;
const body: Record<string, any> = { url };
if (options?.discovery_mode) body.discovery_mode = options.discovery_mode;
if (options?.params) body.params = options.params;
const response = await fetch(endpoint, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error(errorData);
}
return (await response.json()) as DiscoverAgentCardResponse;
};
export const createGuardrailCall = async (accessToken: string, guardrailData: any) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails` : `/guardrails`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
guardrail: guardrailData,
}),
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error(errorData);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to create guardrail:", error);
throw error;
}
};
export const uiSpendLogDetailsCall = async (accessToken: string, logId: string, start_date: string) => {
try {
// Construct base URL
let url = proxyBaseUrl
? `${proxyBaseUrl}/spend/logs/ui/${logId}?start_date=${encodeURIComponent(start_date)}`
: `/spend/logs/ui/${logId}?start_date=${encodeURIComponent(start_date)}`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to fetch log details:", error);
throw error;
}
};
export const fetchOpenAPIRegistry = async (accessToken: string) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/mcp/openapi-registry` : `/v1/mcp/openapi-registry`;
const response = await fetch(url, {
method: HTTP_REQUEST.GET,
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(deriveErrorMessage(errorData));
}
return await response.json();
} catch (error) {
console.error("Failed to fetch OpenAPI registry:", error);
throw error;
}
};
export const fetchDiscoverableMCPServers = async (accessToken: string) => {
try {
return await apiClient.get(`/v1/mcp/discover`, { accessToken });
} catch (error) {
console.error("Failed to fetch discoverable MCP servers:", error);
throw error;
}
};
export interface ConnectFlowStatus {
state: "unscoped" | "interactive" | "m2m" | "stale";
client_origin: string;
server_id: string | null;
server_name: string | null;
connected: boolean | null;
}
/**
* What the gateway says about one in-flight connect flow, read from the sealed HttpOnly
* flow cookie rather than from the address bar (LIT-7075): the client asking, the server
* the flow is scoped to, and whether that server's vendor OAuth is already done. Sent with
* no access token; the flow and session cookies are the credential, exactly as they are for
* the finish form this page posts.
*/
export const fetchConnectFlow = async (flowHandle: string): Promise<ConnectFlowStatus> =>
apiClient.get(`/authorize/flow`, { query: { flow: flowHandle }, credentials: "include" });
export const fetchMCPServers = async (accessToken: string, teamId?: string | null, connectedAppView?: boolean) => {
try {
return await apiClient.get(`/v1/mcp/server`, {
accessToken,
query: { team_id: teamId || undefined, connected_app_view: connectedAppView || undefined },
});
} catch (error) {
console.error("Failed to fetch MCP servers:", error);
throw error;
}
};
export const fetchMCPServerHealth = async (accessToken: string, serverIds?: string[]) => {
try {
return await apiClient.get(`/v1/mcp/server/health`, {
accessToken,
query: {
server_ids: serverIds && serverIds.length > 0 ? serverIds : undefined,
},
});
} catch (error) {
console.error("Failed to fetch MCP server health:", error);
throw error;
}
};
export const fetchMCPAccessGroups = async (accessToken: string) => {
try {
const data = await apiClient.get(`/v1/mcp/access_groups`, { accessToken });
return data.access_groups || [];
} catch (error) {
console.error("Failed to fetch MCP access groups:", error);
throw error;
}
};
export const fetchMCPClientIp = async (accessToken: string): Promise<string | null> => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/mcp/network/client-ip` : `/v1/mcp/network/client-ip`;
const response = await fetch(url, {
method: HTTP_REQUEST.GET,
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
},
});
if (!response.ok) {
return null;
}
const data = await response.json();
return data.ip || null;
} catch {
return null;
}
};
export const createMCPServer = async (
accessToken: string,
formValues: Record<string, any>, // Assuming formValues is an object
) => {
try {
const data = await apiClient.post(`/v1/mcp/server`, {
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 importMCPServers = async (accessToken: string, payload: Record<string, unknown>) => {
try {
return await apiClient.post(`/v1/mcp/server/import`, { accessToken, body: payload });
} catch (error) {
console.error("Failed to import MCP servers:", error);
throw error;
}
};
export const updateMCPServer = async (accessToken: string, formValues: Record<string, any>) => {
try {
return await apiClient.put(`/v1/mcp/server`, { accessToken, body: formValues });
} catch (error) {
console.error("Failed to update MCP server:", error);
throw error;
}
};
export const deleteMCPServer = async (accessToken: string, serverId: string) => {
try {
await apiClient.delete(`/v1/mcp/server/${serverId}`, { accessToken });
} catch (error) {
console.error("Failed to delete key:", error);
throw error;
}
};
export const fetchMCPToolsets = async (accessToken: string): Promise<any[]> => {
try {
return await apiClient.get(`/v1/mcp/toolset`, { accessToken });
} catch (error) {
console.error("Failed to fetch MCP toolsets:", error);
throw error;
}
};
export const createMCPToolset = async (accessToken: string, formValues: Record<string, any>) => {
try {
return await apiClient.post(`/v1/mcp/toolset`, { accessToken, body: formValues });
} catch (error) {
console.error("Failed to create MCP toolset:", error);
throw error;
}
};
export const updateMCPToolset = async (accessToken: string, formValues: Record<string, any>) => {
try {
return await apiClient.put(`/v1/mcp/toolset`, { accessToken, body: formValues });
} catch (error) {
console.error("Failed to update MCP toolset:", error);
throw error;
}
};
export const deleteMCPToolset = async (accessToken: string, toolsetId: string) => {
try {
await apiClient.delete(`/v1/mcp/toolset/${toolsetId}`, { accessToken });
} catch (error) {
console.error("Failed to delete MCP toolset:", error);
throw error;
}
};
export const registerMCPServer = async (accessToken: string, formValues: Record<string, any>) => {
try {
return await apiClient.post(`/v1/mcp/server/register`, { accessToken, body: formValues });
} catch (error) {
console.error("Failed to register MCP server:", error);
throw error;
}
};
export const fetchMCPSubmissions = async (accessToken: string) => {
try {
const url = (proxyBaseUrl ? `${proxyBaseUrl}` : "") + `/v1/mcp/server/submissions`;
const response = await fetch(url, {
method: HTTP_REQUEST.GET,
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
return response.json();
} catch (error) {
console.error("Failed to fetch MCP submissions:", error);
throw error;
}
};
export const approveMCPServer = async (accessToken: string, serverId: string) => {
try {
const url = (proxyBaseUrl ? `${proxyBaseUrl}` : "") + `/v1/mcp/server/${encodeURIComponent(serverId)}/approve`;
const response = await fetch(url, {
method: HTTP_REQUEST.PUT,
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
},
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
return response.json();
} catch (error) {
console.error("Failed to approve MCP server:", error);
throw error;
}
};
export const rejectMCPServer = async (accessToken: string, serverId: string, reviewNotes?: string) => {
try {
const url = (proxyBaseUrl ? `${proxyBaseUrl}` : "") + `/v1/mcp/server/${encodeURIComponent(serverId)}/reject`;
const response = await fetch(url, {
method: HTTP_REQUEST.PUT,
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ review_notes: reviewNotes ?? null }),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
return response.json();
} catch (error) {
console.error("Failed to reject MCP server:", error);
throw error;
}
};
// Search Tools API calls
export const fetchSearchTools = async (accessToken: string) => {
try {
const data = await apiClient.get(`/search_tools/list`, { accessToken });
return data;
} catch (error) {
console.error("Failed to fetch search tools:", error);
throw error;
}
};
export const createSearchTool = async (accessToken: string, formValues: Record<string, any>) => {
try {
const data = await apiClient.post(`/search_tools`, {
accessToken,
body: {
search_tool: formValues,
},
});
return data;
} catch (error) {
console.error("Failed to create search tool:", error);
throw error;
}
};
export const updateSearchTool = async (accessToken: string, searchToolId: string, formValues: Record<string, any>) => {
try {
const data = await apiClient.put(`/search_tools/${searchToolId}`, {
accessToken,
body: {
search_tool: formValues,
},
});
return data;
} catch (error) {
console.error("Failed to update search tool:", error);
throw error;
}
};
export const deleteSearchTool = async (accessToken: string, searchToolId: string) => {
try {
const data = await apiClient.delete(`/search_tools/${searchToolId}`, { accessToken });
return data;
} catch (error) {
console.error("Failed to delete search tool:", error);
throw error;
}
};
export const fetchAvailableSearchProviders = async (accessToken: string) => {
try {
const url = proxyBaseUrl
? `${proxyBaseUrl}/search_tools/ui/available_providers`
: `/search_tools/ui/available_providers`;
const response = await fetch(url, {
method: HTTP_REQUEST.GET,
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to fetch available search providers:", error);
throw error;
}
};
export const testSearchToolConnection = async (accessToken: string, litellmParams: Record<string, any>) => {
try {
const data = await apiClient.post(`/search_tools/test_connection`, {
accessToken,
body: {
litellm_params: litellmParams,
},
});
return data;
} catch (error) {
console.error("Failed to test search tool connection:", error);
throw error;
}
};
export const listMCPTools = async (
accessToken: string,
serverId: string,
customHeaders?: Record<string, string>,
includeDisabledTools?: boolean,
) => {
// Construct base URL. include_disabled_tools returns the full server catalog
// (admin-only, backend-enforced) so the settings UI can configure the allowlist.
const query = `server_id=${serverId}${includeDisabledTools ? "&include_disabled_tools=true" : ""}`;
let url = proxyBaseUrl ? `${proxyBaseUrl}/mcp-rest/tools/list?${query}` : `/mcp-rest/tools/list?${query}`;
const headers: Record<string, string> = {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
...customHeaders, // Merge custom headers for passthrough auth
};
let response: Response;
try {
response = await fetch(url, {
method: "GET",
headers,
});
} catch (error) {
// Network-level failure (no HTTP response). Preserve legacy shape so the
// caller can render a generic error message without crashing.
console.error("Failed to fetch MCP tools (network error):", error);
return {
tools: [],
error: "network_error",
message: error instanceof Error ? error.message : "Failed to fetch MCP tools",
stack_trace: null,
};
}
let data: any = null;
try {
data = await response.json();
} catch (parseError) {
console.error("Failed to parse MCP tools response:", parseError);
return {
tools: [],
error: "parse_error",
message: "Failed to parse MCP tools response",
status: response.status,
statusText: response.statusText,
stack_trace: null,
};
}
if (!response.ok) {
// Preserve the legacy "never throws" contract so existing callers
// (e.g. MCPToolPermissions, MCPAppsPanel, MCPConnectPicker) can continue
// to inspect `result.error` / `result.message`. Attach `status` so
// callers that need to react to auth failures (e.g. the useQuery in
// mcp_tools.tsx) can still detect 401s from the returned object.
const errorMessage = (data && (data.message || data.error)) || "Failed to fetch MCP tools";
return {
tools: [],
error: (data && data.error) || `http_${response.status}`,
message: errorMessage,
status: response.status,
statusText: response.statusText,
details: data,
stack_trace: null,
};
}
// Return the full response object which includes tools, error, message, and stack_trace
return data;
};
interface CallMCPToolOptions {
guardrails?: string[];
customHeaders?: Record<string, string>;
}
export const callMCPTool = async (
accessToken: string,
serverId: string,
toolName: string,
toolArguments: Record<string, any>,
options?: CallMCPToolOptions,
) => {
try {
// Construct base URL
let url = proxyBaseUrl ? `${proxyBaseUrl}/mcp-rest/tools/call` : `/mcp-rest/tools/call`;
const headers: Record<string, string> = {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
...(options?.customHeaders || {}), // Merge custom headers for passthrough auth
};
const body: Record<string, any> = {
server_id: serverId,
name: toolName,
arguments: toolArguments,
};
if (options?.guardrails && options.guardrails.length > 0) {
body.litellm_metadata = { guardrails: options.guardrails };
}
const response = await fetch(url, {
method: "POST",
headers,
body: JSON.stringify(body),
});
if (!response.ok) {
let errorMessage = "Network response was not ok";
let errorDetails = null;
// First, try to get the response as text to see what we're dealing with
const responseText = await response.text();
try {
// Try to parse as JSON
const errorData = JSON.parse(responseText);
if (errorData.detail) {
if (typeof errorData.detail === "string") {
errorMessage = errorData.detail;
} else if (typeof errorData.detail === "object") {
errorMessage = errorData.detail.message || errorData.detail.error || "An error occurred";
errorDetails = errorData.detail;
}
} else {
errorMessage = errorData.message || errorData.error || errorMessage;
}
} catch (parseError) {
console.error("Failed to parse JSON error response:", parseError);
// If JSON parsing fails, use the raw text
if (responseText) {
errorMessage = responseText;
}
}
// Create a more informative error object
const enhancedError = new Error(errorMessage);
(enhancedError as any).status = response.status;
(enhancedError as any).statusText = response.statusText;
(enhancedError as any).details = errorDetails;
handleError(errorMessage);
throw enhancedError;
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to call MCP tool:", error);
console.error("Error type:", typeof error);
if (error instanceof Error) {
console.error("Error message:", error.message);
console.error("Error stack:", error.stack);
}
throw error;
}
};
export const tagCreateCall = async (accessToken: string, formValues: TagNewRequest): Promise<void> => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/tag/new` : `/tag/new`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
},
body: JSON.stringify(formValues),
});
if (!response.ok) {
const errorData = await response.text();
await handleError(errorData);
return;
}
return await response.json();
} catch (error) {
console.error("Error creating tag:", error);
throw error;
}
};
export const tagUpdateCall = async (accessToken: string, formValues: TagUpdateRequest): Promise<void> => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/tag/update` : `/tag/update`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
},
body: JSON.stringify(formValues),
});
if (!response.ok) {
const errorData = await response.text();
await handleError(errorData);
return;
}
return await response.json();
} catch (error) {
console.error("Error updating tag:", error);
throw error;
}
};
export const tagInfoCall = async (accessToken: string, tagNames: string[]): Promise<TagInfoResponse> => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/tag/info` : `/tag/info`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
},
body: JSON.stringify({ names: tagNames }),
});
if (!response.ok) {
const errorData = await response.text();
await handleError(errorData);
return {};
}
const data = await response.json();
return data as TagInfoResponse;
} catch (error) {
console.error("Error getting tag info:", error);
throw error;
}
};
const formatYmd = (value: Date): string => {
const year = value.getFullYear();
const month = String(value.getMonth() + 1).padStart(2, "0");
const day = String(value.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
export const tagListCall = async (
accessToken: string,
startTime?: Date | null,
endTime?: Date | null,
): Promise<TagListResponse> => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/tag/list` : `/tag/list`;
if (startTime && endTime) {
const params = new URLSearchParams({
start_date: formatYmd(startTime),
end_date: formatYmd(endTime),
});
url = `${url}?${params.toString()}`;
}
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
},
});
if (!response.ok) {
const errorData = await response.text();
await handleError(errorData);
return {};
}
const data = await response.json();
return data as TagListResponse;
} catch (error) {
console.error("Error listing tags:", error);
throw error;
}
};
export const tagDeleteCall = async (accessToken: string, tagName: string): Promise<void> => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/tag/delete` : `/tag/delete`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
},
body: JSON.stringify({ name: tagName }),
});
if (!response.ok) {
const errorData = await response.text();
await handleError(errorData);
return;
}
return await response.json();
} catch (error) {
console.error("Error deleting tag:", error);
throw error;
}
};
export const getDefaultTeamSettings = async (accessToken: string) => {
try {
const data = await apiClient.get(`/get/default_team_settings`, { accessToken });
return data;
} catch (error) {
console.error("Failed to fetch default team settings:", error);
throw error;
}
};
export const updateDefaultTeamSettings = async (accessToken: string, settings: Record<string, any>) => {
try {
const data = await apiClient.patch(`/update/default_team_settings`, { accessToken, body: settings });
return data;
} catch (error) {
console.error("Failed to update default team settings:", error);
throw error;
}
};
export const getTeamPermissionsCall = async (accessToken: string, teamId: string) => {
try {
let url = proxyBaseUrl
? `${proxyBaseUrl}/team/permissions_list?team_id=${teamId}`
: `/team/permissions_list?team_id=${teamId}`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
console.error("Available permissions fetch failed:", errorMessage);
return { all_available_permissions: [], team_member_permissions: [] };
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to get team permissions:", error);
throw error;
}
};
export const teamPermissionsUpdateCall = async (accessToken: string, teamId: string, permissions: string[]) => {
try {
const data = await apiClient.post(`/team/permissions_update`, {
accessToken,
body: {
team_id: teamId,
team_member_permissions: permissions,
},
});
return data;
} catch (error) {
console.error("Failed to update team permissions:", error);
throw error;
}
};
/**
* Get a page of spend logs for a particular session.
*
* The backend paginates this endpoint (page / page_size, returning
* { data, total, page, page_size, total_pages }). Callers that need the whole
* session should page through total_pages and accumulate the results.
*/
export const sessionSpendLogsCall = async (
accessToken: string,
session_id: string,
page: number = 1,
page_size: number = 100,
) => {
try {
const params = new URLSearchParams({
session_id,
page: String(page),
page_size: String(page_size),
});
let url = proxyBaseUrl
? `${proxyBaseUrl}/spend/logs/session/ui?${params.toString()}`
: `/spend/logs/session/ui?${params.toString()}`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to fetch session logs:", error);
throw error;
}
};
export const vectorStoreCreateCall = async (accessToken: string, formValues: Record<string, any>): Promise<void> => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/vector_store/new` : `/vector_store/new`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
},
body: JSON.stringify(formValues),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || "Failed to create vector store");
}
return await response.json();
} catch (error) {
console.error("Error creating vector store:", error);
throw error;
}
};
export const vectorStoreListCall = async (
accessToken: string,
page: number = 1,
page_size: number = 100,
): Promise<any> => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/vector_store/list` : `/vector_store/list`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
},
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || "Failed to list vector stores");
}
return await response.json();
} catch (error) {
console.error("Error listing vector stores:", error);
throw error;
}
};
export interface IndexesListResponse {
object: string;
data: VectorStoreIndex[];
}
export const indexesListCall = async (accessToken: string): Promise<IndexesListResponse> => {
try {
return await apiClient.get<IndexesListResponse>(`/v1/indexes`, { accessToken });
} catch (error) {
console.error("Error listing indexes:", error);
throw error;
}
};
export const vectorStoreDeleteCall = async (accessToken: string, vectorStoreId: string): Promise<void> => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/vector_store/delete` : `/vector_store/delete`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
},
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");
}
return await response.json();
} catch (error) {
console.error("Error deleting vector store:", error);
throw error;
}
};
export const vectorStoreInfoCall = async (accessToken: string, vectorStoreId: string): Promise<any> => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/vector_store/info` : `/vector_store/info`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
},
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");
}
return await response.json();
} catch (error) {
console.error("Error getting vector store info:", error);
throw error;
}
};
export const vectorStoreUpdateCall = async (accessToken: string, formValues: Record<string, any>): Promise<any> => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/vector_store/update` : `/vector_store/update`;
const response = await fetch(url, {
method: "POST",
headers: {
"Content-Type": "application/json",
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
},
body: JSON.stringify(formValues),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || "Failed to update vector store");
}
return await response.json();
} catch (error) {
console.error("Error updating vector store:", error);
throw error;
}
};
export const ragIngestCall = async (
accessToken: string,
file: File,
customLlmProvider: string,
vectorStoreId?: string,
vectorStoreName?: string,
vectorStoreDescription?: string,
providerSpecificParams?: Record<string, any>,
): Promise<any> => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/rag/ingest` : `/rag/ingest`;
const formData = new FormData();
formData.append("file", file);
const ingestOptions: any = {
ingest_options: {
vector_store: {
custom_llm_provider: customLlmProvider,
...(vectorStoreId && { vector_store_id: vectorStoreId }),
...(providerSpecificParams && providerSpecificParams),
},
},
};
// Add litellm_vector_store_params if name or description provided
if (vectorStoreName || vectorStoreDescription) {
ingestOptions.ingest_options.litellm_vector_store_params = {};
if (vectorStoreName) {
ingestOptions.ingest_options.litellm_vector_store_params.vector_store_name = vectorStoreName;
}
if (vectorStoreDescription) {
ingestOptions.ingest_options.litellm_vector_store_params.vector_store_description = vectorStoreDescription;
}
}
formData.append("request", JSON.stringify(ingestOptions));
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
},
body: formData,
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.error?.message || error.detail || "Failed to ingest document");
}
return await response.json();
} catch (error) {
console.error("Error ingesting document:", error);
throw error;
}
};
export const getEmailEventSettings = async (accessToken: string): Promise<EmailEventSettingsResponse> => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/email/event_settings` : `/email/event_settings`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error("Failed to get email event settings");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to get email event settings:", error);
throw error;
}
};
export const updateEmailEventSettings = async (accessToken: string, settings: EmailEventSettingsUpdateRequest) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/email/event_settings` : `/email/event_settings`;
const response = await fetch(url, {
method: "PATCH",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(settings),
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error("Failed to update email event settings");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to update email event settings:", error);
throw error;
}
};
export const resetEmailEventSettings = async (accessToken: string) => {
try {
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) {
const errorData = await response.text();
handleError(errorData);
throw new Error("Failed to reset email event settings");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to reset email event settings:", error);
throw error;
}
};
export { type Team } from "./key_team_helpers/key_list"; // Re-export Team
export const deleteAgentCall = async (accessToken: string, agentId: string) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/agents/${agentId}` : `/v1/agents/${agentId}`;
const response = await fetch(url, {
method: "DELETE",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error(errorData);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to delete agent:", error);
throw error;
}
};
export const makeAgentsPublicCall = async (accessToken: string, agentIds: string[]) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/agents/make_public` : `/v1/agents/make_public`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
agent_ids: agentIds,
}),
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error(errorData);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to make agents public:", error);
throw error;
}
};
export const makeMCPPublicCall = async (accessToken: string, mcpServerIds: string[]) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/mcp/make_public` : `/v1/mcp/make_public`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
mcp_server_ids: mcpServerIds,
}),
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error(errorData);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to make agents public:", error);
throw error;
}
};
export const deleteGuardrailCall = async (accessToken: string, guardrailId: string) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/${guardrailId}` : `/guardrails/${guardrailId}`;
const response = await fetch(url, {
method: "DELETE",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error(errorData);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to delete guardrail:", error);
throw error;
}
};
export const getGuardrailUISettings = async (accessToken: string) => {
try {
const url = proxyBaseUrl
? `${proxyBaseUrl}/guardrails/ui/add_guardrail_settings`
: `/guardrails/ui/add_guardrail_settings`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error("Failed to get guardrail UI settings");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to get guardrail UI settings:", error);
throw error;
}
};
export const getGuardrailProviderSpecificParams = async (accessToken: string) => {
try {
const url = proxyBaseUrl
? `${proxyBaseUrl}/guardrails/ui/provider_specific_params`
: `/guardrails/ui/provider_specific_params`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error("Failed to get guardrail provider specific parameters");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to get guardrail provider specific parameters:", error);
throw error;
}
};
export const getCategoryYaml = async (accessToken: string, categoryName: string) => {
try {
// URL encode the category name to handle special characters
const encodedCategoryName = encodeURIComponent(categoryName);
const url = proxyBaseUrl
? `${proxyBaseUrl}/guardrails/ui/category_yaml/${encodedCategoryName}`
: `/guardrails/ui/category_yaml/${encodedCategoryName}`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
console.error(`Failed to get category YAML. Status: ${response.status}, Error:`, errorData);
handleError(errorData);
throw new Error(`Failed to get category YAML: ${response.status} ${errorData}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to get category YAML:", error);
throw error;
}
};
export const getMajorAirlines = async (accessToken: string) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/ui/major_airlines` : `/guardrails/ui/major_airlines`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
console.error(`Failed to get major airlines. Status: ${response.status}, Error:`, errorData);
handleError(errorData);
throw new Error(`Failed to get major airlines: ${response.status} ${errorData}`);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to get major airlines:", error);
throw error;
}
};
export const getAgentsList = async (accessToken: string, healthCheck: boolean = false) => {
try {
const params = healthCheck ? "?health_check=true" : "";
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/agents${params}` : `/v1/agents${params}`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error("Failed to get agents list");
}
const data = await response.json();
return { agents: data };
} catch (error) {
console.error("Failed to get agents list:", error);
throw error;
}
};
export const getAgentInfo = async (accessToken: string, agentId: string) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/agents/${agentId}` : `/v1/agents/${agentId}`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error("Failed to get agent info");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to get agent info:", error);
throw error;
}
};
export const getGuardrailInfo = async (accessToken: string, guardrailId: string) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/${guardrailId}/info` : `/guardrails/${guardrailId}/info`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error("Failed to get guardrail info");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to get guardrail info:", error);
throw error;
}
};
export const patchAgentCall = async (
accessToken: string,
agentId: string,
updateData: {
agent_name?: string;
litellm_params?: Record<string, any>;
agent_card_params?: Record<string, any>;
object_permission?: Record<string, any>;
tpm_limit?: number | null;
rpm_limit?: number | null;
session_tpm_limit?: number | null;
session_rpm_limit?: number | null;
},
) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/agents/${agentId}` : `/v1/agents/${agentId}`;
const response = await fetch(url, {
method: "PATCH",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(updateData),
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error("Failed to patch agent");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to update guardrail:", error);
throw error;
}
};
export const updateGuardrailCall = async (
accessToken: string,
guardrailId: string,
updateData: {
guardrail_name?: string;
default_on?: boolean;
guardrail_info?: Record<string, any>;
litellm_params?: Record<string, any>;
},
) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/${guardrailId}` : `/guardrails/${guardrailId}`;
const response = await fetch(url, {
method: "PATCH",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(updateData),
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error("Failed to update guardrail");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to update guardrail:", error);
throw error;
}
};
export const applyGuardrail = async (
accessToken: string,
guardrailName: string,
text: string,
language?: string | null,
entities?: string[] | null,
metadata?: Record<string, unknown> | null,
) => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/apply_guardrail` : `/guardrails/apply_guardrail`;
const requestBody: Record<string, any> = {
guardrail_name: guardrailName,
text: text,
};
if (language) {
requestBody.language = language;
}
if (entities && entities.length > 0) {
requestBody.entities = entities;
}
if (metadata != null) {
requestBody.metadata = metadata;
}
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(requestBody),
});
if (!response.ok) {
const errorData = await response.text();
let errorMessage = "Failed to apply guardrail";
try {
const errorJson = JSON.parse(errorData);
if (errorJson.error?.message) {
errorMessage = errorJson.error.message;
} else if (errorJson.detail) {
errorMessage = errorJson.detail;
} else if (errorJson.message) {
errorMessage = errorJson.message;
}
} catch (e) {
errorMessage = errorData || errorMessage;
}
handleError(errorData);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to apply guardrail:", error);
throw error;
}
};
interface TestCustomCodeGuardrailRequest {
custom_code: string;
test_input: {
texts: string[];
images?: string[];
tools?: Record<string, any>[];
tool_calls?: Record<string, any>[];
structured_messages?: Record<string, any>[];
model?: string;
};
input_type?: "request" | "response";
request_data?: {
model?: string;
user_id?: string;
team_id?: string;
end_user_id?: string;
metadata?: Record<string, any>;
};
}
interface TestCustomCodeGuardrailResponse {
success: boolean;
result?: {
action: "allow" | "block" | "modify";
reason?: string;
texts?: string[];
images?: string[];
tool_calls?: Record<string, any>[];
detection_info?: Record<string, any>;
warning?: string;
};
error?: string;
error_type?: "compilation" | "execution";
}
export const testCustomCodeGuardrail = async (
accessToken: string,
request: TestCustomCodeGuardrailRequest,
): Promise<TestCustomCodeGuardrailResponse> => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/guardrails/test_custom_code` : `/guardrails/test_custom_code`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(request),
});
if (!response.ok) {
const errorData = await response.text();
let errorMessage = "Failed to test custom code guardrail";
try {
const errorJson = JSON.parse(errorData);
if (errorJson.error?.message) {
errorMessage = errorJson.error.message;
} else if (errorJson.detail) {
errorMessage = errorJson.detail;
} else if (errorJson.message) {
errorMessage = errorJson.message;
}
} catch (e) {
errorMessage = errorData || errorMessage;
}
handleError(errorData);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to test custom code guardrail:", error);
throw error;
}
};
export const validateBlockedWordsFile = async (accessToken: string, fileContent: string) => {
try {
const url = proxyBaseUrl
? `${proxyBaseUrl}/guardrails/validate_blocked_words_file`
: `/guardrails/validate_blocked_words_file`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ file_content: fileContent }),
});
if (!response.ok) {
const errorData = await response.text();
handleError(errorData);
throw new Error("Failed to validate blocked words file");
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to validate blocked words file:", error);
throw error;
}
};
export const getSSOSettings = async (accessToken: string) => {
try {
const data = await apiClient.get(`/get/sso_settings`, { accessToken });
return data;
} catch (error) {
console.error("Failed to fetch SSO configuration:", error);
throw error;
}
};
export const updateSSOSettings = async (accessToken: string, settings: Record<string, any>) => {
try {
// Construct base URL
let url = proxyBaseUrl ? `${proxyBaseUrl}/update/sso_settings` : `/update/sso_settings`;
const response = await fetch(url, {
method: "PATCH",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(settings),
});
if (!response.ok) {
const errorData = await response.json();
const detailMessage =
typeof errorData?.detail === "object"
? errorData.detail?.error || errorData.detail?.message
: errorData?.detail;
const errorMessage =
typeof detailMessage === "string" && detailMessage.length > 0 ? detailMessage : deriveErrorMessage(errorData);
handleError(errorMessage);
const enhancedError = new Error(errorMessage);
if (errorData?.detail !== undefined) {
(enhancedError as any).detail = errorData.detail;
}
(enhancedError as any).rawError = errorData;
throw enhancedError;
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to update SSO configuration:", error);
throw error;
}
};
interface UiAuditLogsParams {
action?: string;
table_name?: string;
object_id?: string;
changed_by?: string;
changed_by_api_key?: string;
object_team_id?: string;
object_key_hash?: string;
search?: string | null;
sort_by?: string;
sort_order?: "asc" | "desc";
}
interface UiAuditLogsCallOptions {
accessToken: string;
page?: number;
page_size?: number;
params?: UiAuditLogsParams;
}
export const uiAuditLogsCall = async ({
accessToken,
page = 1,
page_size = 50,
params = {},
}: UiAuditLogsCallOptions) => {
try {
let url = proxyBaseUrl ? `${proxyBaseUrl}/audit` : `/audit`;
const queryParams = new URLSearchParams();
queryParams.append("page", page.toString());
queryParams.append("page_size", page_size.toString());
for (const [key, value] of Object.entries(params)) {
if (value != null && value !== "") {
queryParams.append(key, String(value));
}
}
url += `?${queryParams.toString()}`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
return await response.json();
} catch (error) {
console.error("Failed to fetch audit logs:", error);
throw error;
}
};
export const getRemainingUsers = async (
accessToken: string,
): Promise<{
total_users: number | null;
total_users_used: number;
total_users_remaining: number | null;
total_teams: number | null;
total_teams_used: number;
total_teams_remaining: number | null;
} | null> => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/user/available_users` : `/user/available_users`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
},
});
if (!response.ok) {
// if 404 - return None
if (response.status === 404) {
return null;
}
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 fetch remaining users:", error);
throw error;
}
};
export interface LicenseInfo {
has_license: boolean;
license_type: string | null;
expiration_date: string | null;
allowed_features: string[];
limits: {
max_users: number | null;
max_teams: number | null;
};
}
export const getLicenseInfo = async (accessToken: string): Promise<LicenseInfo | null> => {
try {
const url = proxyBaseUrl ? `${proxyBaseUrl}/health/license` : `/health/license`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
},
});
if (!response.ok) {
// if 404 - return null (endpoint not available)
if (response.status === 404) {
return null;
}
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 fetch license info:", error);
throw error;
}
};
export const updatePassThroughEndpoint = async (
accessToken: string,
endpointPath: string,
formValues: Record<string, any>,
) => {
try {
let url = proxyBaseUrl
? `${proxyBaseUrl}/config/pass_through_endpoint/${encodeURIComponent(endpointPath)}`
: `/config/pass_through_endpoint/${encodeURIComponent(endpointPath)}`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(formValues),
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
toast.success("Pass through endpoint updated successfully");
return data;
} catch (error) {
console.error("Failed to update pass through endpoint:", error);
throw error;
}
};
export const deleteCallback = async (accessToken: string, callbackName: string) => {
/**
* Delete specific callback from proxy using the /config/callback/delete API
*/
try {
const data = await apiClient.post(`/config/callback/delete`, {
accessToken,
body: {
callback_name: callbackName,
},
});
return data;
} catch (error) {
console.error("Failed to delete specific callback:", error);
throw error;
}
};
export const testMCPToolsListRequest = async (
accessToken: string | null,
mcpServerConfig: Record<string, any>,
oauthAccessToken?: string | null,
) => {
try {
// Construct the URL for POST request
const url = proxyBaseUrl ? `${proxyBaseUrl}/mcp-rest/test/tools/list` : `/mcp-rest/test/tools/list`;
const headers: Record<string, string> = {
"Content-Type": "application/json",
};
if (accessToken) {
headers["x-litellm-api-key"] = accessToken;
if (globalLitellmHeaderName.toLowerCase() !== "authorization") {
headers[globalLitellmHeaderName] = `Bearer ${accessToken}`;
}
}
if (oauthAccessToken) {
headers["Authorization"] = `Bearer ${oauthAccessToken}`;
} else if (accessToken) {
headers[globalLitellmHeaderName] = `Bearer ${accessToken}`;
}
const response = await fetch(url, {
method: "POST",
headers,
body: JSON.stringify(mcpServerConfig),
});
// Check for non-JSON responses first
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.`,
);
}
const data = await response.json();
if (!response.ok || data.error) {
if (response.status === 403) {
return {
tools: [],
error: true,
status: 403,
message: MCP_TOOLS_PREVIEW_FORBIDDEN_MESSAGE,
};
}
// Return the error response instead of throwing an error
// This allows the caller to handle the error format properly
if (data.error) {
return { ...data, status: response.status };
}
return {
tools: [],
error: "request_failed",
status: response.status,
message: data.message || `MCP tools list failed: ${response.status} ${response.statusText}`,
};
}
return data;
} catch (error) {
console.error("MCP tools list test error:", error);
// For network errors or other exceptions, still throw
throw error;
}
};
export const cacheTemporaryMcpServer = async (accessToken: string, payload: Record<string, any>) => {
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/mcp/server/oauth/session` : `/v1/mcp/server/oauth/session`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
const data = await response.json();
if (!response.ok) {
const errorMessage = deriveErrorMessage(data) || data?.error || "Failed to cache MCP server";
throw new Error(errorMessage);
}
return data;
};
interface RegisterMcpOAuthClientPayload {
client_name?: string;
grant_types?: string[];
response_types?: string[];
token_endpoint_auth_method?: string;
redirect_uris?: string[];
}
export const registerMcpOAuthClient = async (
accessToken: string,
serverId: string,
payload: RegisterMcpOAuthClientPayload,
) => {
const base = getProxyBaseUrl();
const normalizedServerId = encodeURIComponent(serverId.trim());
const url = `${base}/v1/mcp/server/oauth/${normalizedServerId}/register`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
Accept: "application/json, text/event-stream",
},
body: JSON.stringify(payload),
});
const data = await response.json();
if (!response.ok) {
const errorMessage = deriveErrorMessage(data) || data?.detail || "Failed to register OAuth client";
throw new Error(errorMessage);
}
return data;
};
interface BuildOAuthAuthorizeURLParams {
serverId: string;
clientId?: string;
redirectUri: string;
state: string;
codeChallenge: string;
scope?: string;
}
export const buildMcpOAuthAuthorizeUrl = ({
serverId,
clientId,
redirectUri,
state,
codeChallenge,
scope,
}: BuildOAuthAuthorizeURLParams): string => {
const base = getProxyBaseUrl();
const normalizedServerId = encodeURIComponent(serverId.trim());
const url = `${base}/v1/mcp/server/oauth/${normalizedServerId}/authorize`;
const authorizeParams = {
redirect_uri: redirectUri,
state,
response_type: "code",
code_challenge: codeChallenge,
code_challenge_method: "S256",
};
const params = new URLSearchParams(authorizeParams);
if (clientId && clientId.trim().length > 0) {
params.set("client_id", clientId);
}
if (scope && scope.trim().length > 0) {
params.set("scope", scope);
}
return `${url}?${params.toString()}`;
};
interface ExchangeMcpOAuthTokenParams {
serverId: string;
code: string;
clientId?: string;
clientSecret?: string;
codeVerifier: string;
redirectUri: string;
accessToken?: string | null;
}
export const exchangeMcpOAuthToken = async ({
serverId,
code,
clientId,
clientSecret,
codeVerifier,
redirectUri,
accessToken,
}: ExchangeMcpOAuthTokenParams) => {
const base = getProxyBaseUrl();
const normalizedServerId = encodeURIComponent(serverId.trim());
const url = `${base}/v1/mcp/server/oauth/${normalizedServerId}/token`;
const body = new URLSearchParams();
body.set("grant_type", "authorization_code");
body.set("code", code);
if (clientId && clientId.trim().length > 0) {
body.set("client_id", clientId);
}
if (clientSecret && clientSecret.trim().length > 0) {
body.set("client_secret", clientSecret);
}
body.set("code_verifier", codeVerifier);
body.set("redirect_uri", redirectUri);
const headers: Record<string, string> = {
"Content-Type": "application/x-www-form-urlencoded",
};
if (accessToken) {
headers["Authorization"] = `Bearer ${accessToken}`;
}
const response = await fetch(url, {
method: "POST",
headers,
body: body.toString(),
});
const data = await response.json();
if (!response.ok) {
const oauthErrorMessage =
typeof data?.error === "string" && typeof data?.error_description === "string"
? `${data.error}: ${data.error_description}`
: undefined;
const errorMessage = oauthErrorMessage || deriveErrorMessage(data) || data?.detail || "OAuth token exchange failed";
throw new Error(errorMessage);
}
return data;
};
export const vectorStoreSearchCall = async (
accessToken: string,
vectorStoreId: string,
query: string,
): Promise<any> => {
try {
const url = `${getProxyBaseUrl()}/v1/vector_stores/${vectorStoreId}/search`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: query,
}),
});
if (!response.ok) {
const errorData = await response.text();
await handleError(errorData);
throw new Error(errorData);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Error testing vector store search:", error);
throw error;
}
};
export const searchToolQueryCall = async (
accessToken: string,
searchToolName: string,
query: string,
maxResults?: number,
): Promise<any> => {
try {
const url = `${getProxyBaseUrl()}/v1/search/${searchToolName}`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
query: query,
max_results: maxResults || 5,
}),
});
if (!response.ok) {
const errorData = await response.text();
await handleError(errorData);
return null;
}
const data = await response.json();
return data;
} catch (error) {
console.error("Error querying search tool:", error);
throw error;
}
};
// New endpoint functions for DAU, WAU, MAU
export const tagDauCall = async (accessToken: string, endDate: Date, tagFilter?: string, tagFilters?: string[]) => {
/**
* Get Daily Active Users (DAU) for last 7 days ending on endDate
*/
try {
const formatDate = (date: Date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
const hasTagFilters = tagFilters && tagFilters.length > 0;
return await apiClient.get(`/tag/dau`, {
accessToken,
query: {
end_date: formatDate(endDate),
tag_filters: hasTagFilters ? tagFilters : undefined,
tag_filter: !hasTagFilters && tagFilter ? tagFilter : undefined,
},
});
} catch (error) {
console.error("Failed to fetch DAU:", error);
throw error;
}
};
export const tagWauCall = async (accessToken: string, endDate: Date, tagFilter?: string, tagFilters?: string[]) => {
/**
* Get Weekly Active Users (WAU) for last 7 weeks ending on endDate
*/
try {
const formatDate = (date: Date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
const hasTagFilters = tagFilters && tagFilters.length > 0;
return await apiClient.get(`/tag/wau`, {
accessToken,
query: {
end_date: formatDate(endDate),
tag_filters: hasTagFilters ? tagFilters : undefined,
tag_filter: !hasTagFilters && tagFilter ? tagFilter : undefined,
},
});
} catch (error) {
console.error("Failed to fetch WAU:", error);
throw error;
}
};
export const tagMauCall = async (accessToken: string, endDate: Date, tagFilter?: string, tagFilters?: string[]) => {
/**
* Get Monthly Active Users (MAU) for last 7 months ending on endDate
*/
try {
const formatDate = (date: Date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
const hasTagFilters = tagFilters && tagFilters.length > 0;
return await apiClient.get(`/tag/mau`, {
accessToken,
query: {
end_date: formatDate(endDate),
tag_filters: hasTagFilters ? tagFilters : undefined,
tag_filter: !hasTagFilters && tagFilter ? tagFilter : undefined,
},
});
} catch (error) {
console.error("Failed to fetch MAU:", error);
throw error;
}
};
export const tagDistinctCall = async (accessToken: string) => {
/**
* Get all distinct user agent tags (up to 250)
*/
try {
const data = await apiClient.get(`/tag/distinct`, { accessToken });
return data;
} catch (error) {
console.error("Failed to fetch distinct tags:", error);
throw error;
}
};
export const userAgentSummaryCall = async (
accessToken: string,
startTime: Date,
endTime: Date,
tagFilters?: string[],
) => {
/**
* Get user agent summary statistics
*/
try {
const formatDate = (date: Date) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
return `${year}-${month}-${day}`;
};
return await apiClient.get(`/tag/summary`, {
accessToken,
query: {
start_date: formatDate(startTime),
end_date: formatDate(endTime),
tag_filters: tagFilters && tagFilters.length > 0 ? tagFilters : undefined,
},
});
} catch (error) {
console.error("Failed to fetch user agent summary:", error);
throw error;
}
};
export const perUserAnalyticsCall = async (
accessToken: string,
page: number = 1,
pageSize: number = 50,
tagFilters?: string[],
) => {
/**
* Get per-user analytics data for the last 30 days
*/
try {
return await apiClient.get(`/tag/user-agent/per-user-analytics`, {
accessToken,
query: {
page: page.toString(),
page_size: pageSize.toString(),
tag_filters: tagFilters && tagFilters.length > 0 ? tagFilters : undefined,
},
});
} catch (error) {
console.error("Failed to fetch per-user analytics:", error);
throw error;
}
};
export interface LoginRequest {
username: string;
password: string;
useV3?: boolean;
}
interface LoginResponse {
redirect_url: string;
token?: string;
code?: string;
expires_in?: number;
}
export const loginCall = async (username: string, password: string, useV3?: boolean): Promise<LoginResponse> => {
const proxyBaseUrl = getProxyBaseUrl();
const loginPath = useV3 ? "/v3/login" : "/v2/login";
const loginUrl = proxyBaseUrl ? `${proxyBaseUrl}${loginPath}` : loginPath;
const body = JSON.stringify({
username,
password,
});
const response = await fetch(loginUrl, {
method: "POST",
body,
credentials: "include",
headers: {
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
throw new Error(errorMessage);
}
const data: LoginResponse = await response.json();
// v3 returns an opaque code — exchange it for the real JWT
if (useV3 && data.code) {
const exchangeUrl = proxyBaseUrl ? `${proxyBaseUrl}/v3/login/exchange` : "/v3/login/exchange";
const exchangeResponse = await fetch(exchangeUrl, {
method: "POST",
body: JSON.stringify({ code: data.code }),
credentials: "include",
headers: { "Content-Type": "application/json" },
});
if (!exchangeResponse.ok) {
const errorData = await exchangeResponse.json();
throw new Error(deriveErrorMessage(errorData));
}
const exchangeData: LoginResponse = await exchangeResponse.json();
if (exchangeData.token) {
storeLoginToken(exchangeData.token);
}
return exchangeData;
}
// Backwards compatibility: v2 or old v3 returns token directly
if (data.token) {
storeLoginToken(data.token);
}
return data;
};
/**
* Exchange a single-use login code for a JWT token.
* Used by the SSO callback when the worker redirects back with ?code=.
*/
export const exchangeLoginCode = async (code: string, workerBaseUrl?: string | null): Promise<string> => {
const base = workerBaseUrl || getProxyBaseUrl();
const response = await fetch(`${base}/v3/login/exchange`, {
method: "POST",
body: JSON.stringify({ code }),
headers: { "Content-Type": "application/json" },
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(deriveErrorMessage(errorData));
}
const data = await response.json();
if (data.token) {
document.cookie = `token=${data.token}; path=/; SameSite=Lax`;
}
return data.token;
};
export const getUiSettings = async () => {
const proxyBaseUrl = getProxyBaseUrl();
const url = proxyBaseUrl ? `${proxyBaseUrl}/get/ui_settings` : `/get/ui_settings`;
const response = await fetch(url, {
method: "GET",
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
};
export const updateUiSettings = async (accessToken: string, settings: Record<string, any>) => {
const proxyBaseUrl = getProxyBaseUrl();
const url = proxyBaseUrl ? `${proxyBaseUrl}/update/ui_settings` : `/update/ui_settings`;
const response = await fetch(url, {
method: "PATCH",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(settings),
});
if (!response.ok) {
const errorData = await response.json();
const errorMessage = deriveErrorMessage(errorData);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
};
export type UserBannerSeverity = "info" | "warning" | "error";
export interface UserBanner {
enabled: boolean;
message: string;
severity: UserBannerSeverity;
revision: string;
}
export type UserBannerUpdate = Omit<UserBanner, "revision">;
export const getUserBanner = async (accessToken: string): Promise<UserBanner> => {
return await apiClient.get<UserBanner>("/get/user_banner", { accessToken });
};
export const updateUserBanner = async (accessToken: string, banner: UserBannerUpdate): Promise<UserBanner> => {
const data = await apiClient.patch<{ message: string; banner: UserBanner }>("/update/user_banner", {
accessToken,
body: banner,
});
return data.banner;
};
// 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
* @param enabledOnly - If true, only return enabled plugins (default: false)
*/
export const getClaudeCodePluginsList = async (accessToken: string, enabledOnly: boolean = false) => {
try {
const proxyBaseUrl = getProxyBaseUrl();
const url = proxyBaseUrl
? `${proxyBaseUrl}/claude-code/plugins?enabled_only=${enabledOnly}`
: `/claude-code/plugins?enabled_only=${enabledOnly}`;
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 Claude Code plugins list:", error);
throw error;
}
};
/**
* 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.
* @param accessToken - Admin access token
* @param pluginData - Plugin registration data
*/
export const registerClaudeCodePlugin = async (accessToken: string, pluginData: SkillRegisterRequest) => {
try {
const proxyBaseUrl = getProxyBaseUrl();
const url = proxyBaseUrl ? `${proxyBaseUrl}/claude-code/plugins` : `/claude-code/plugins`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(pluginData),
});
if (!response.ok) {
const errorBody = await response.text();
let errorMessage: string;
try {
errorMessage = deriveErrorMessage(JSON.parse(errorBody));
} catch {
errorMessage = errorBody || `Request failed with status ${response.status}`;
}
handleError(errorMessage);
throw new Error(errorMessage);
}
const data = await response.json();
return data;
} catch (error) {
console.error("Failed to register Claude Code plugin:", error);
throw error;
}
};
/**
* Enable a Claude Code plugin (admin only)
* @param accessToken - Admin access token
* @param pluginName - Name of the plugin to enable
*/
export const enableClaudeCodePlugin = async (accessToken: string, pluginName: string) => {
try {
const proxyBaseUrl = getProxyBaseUrl();
const url = proxyBaseUrl
? `${proxyBaseUrl}/claude-code/plugins/${pluginName}/enable`
: `/claude-code/plugins/${pluginName}/enable`;
const response = await fetch(url, {
method: "POST",
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 enable plugin "${pluginName}":`, error);
throw error;
}
};
/**
* Disable a Claude Code plugin (admin only)
* @param accessToken - Admin access token
* @param pluginName - Name of the plugin to disable
*/
export const disableClaudeCodePlugin = async (accessToken: string, pluginName: string) => {
try {
const proxyBaseUrl = getProxyBaseUrl();
const url = proxyBaseUrl
? `${proxyBaseUrl}/claude-code/plugins/${pluginName}/disable`
: `/claude-code/plugins/${pluginName}/disable`;
const response = await fetch(url, {
method: "POST",
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 disable plugin "${pluginName}":`, error);
throw error;
}
};
/**
* Delete a Claude Code plugin (admin only)
* @param accessToken - Admin access token
* @param pluginName - Name of the plugin to delete
*/
export const deleteClaudeCodePlugin = 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: "DELETE",
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 delete plugin "${pluginName}":`, error);
throw error;
}
};
// Compliance check types and functions
export interface ComplianceCheckResult {
check_name: string;
article: string;
passed: boolean;
detail: string;
}
export interface ComplianceResponse {
compliant: boolean;
regulation: string;
checks: ComplianceCheckResult[];
}
export interface ComplianceCheckRequest {
request_id: string;
user_id?: string;
model?: string;
timestamp?: string;
guardrail_information?: Record<string, any>[];
}
export const checkEuAiActCompliance = async (
accessToken: string,
payload: ComplianceCheckRequest,
): Promise<ComplianceResponse> => {
const url = proxyBaseUrl ? `${proxyBaseUrl}/compliance/eu-ai-act` : `/compliance/eu-ai-act`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
if (!response.ok) {
const errorData = await response.text();
throw new Error(errorData);
}
return response.json();
};
export const checkGdprCompliance = async (
accessToken: string,
payload: ComplianceCheckRequest,
): Promise<ComplianceResponse> => {
const url = proxyBaseUrl ? `${proxyBaseUrl}/compliance/gdpr` : `/compliance/gdpr`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
if (!response.ok) {
const errorData = await response.text();
throw new Error(errorData);
}
return response.json();
};
export interface ToolRow {
tool_id: string;
tool_name: string;
origin?: string;
input_policy: string;
output_policy: string;
call_count?: number;
assignments?: Record<string, any>;
key_hash?: string;
team_id?: string;
key_alias?: string;
created_at?: string;
updated_at?: string;
created_by?: string;
updated_by?: string;
user_agent?: string;
last_used_at?: string;
}
export interface ToolPolicyOption {
value: string;
label: string;
description: string;
}
export interface ToolPolicyOptionsResponse {
input_policies: ToolPolicyOption[];
output_policies: ToolPolicyOption[];
}
export const fetchToolPolicyOptions = async (accessToken: string): Promise<ToolPolicyOptionsResponse> => {
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/tool/policy/options` : `/v1/tool/policy/options`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
throw new Error(errorData);
}
return response.json();
};
export const fetchToolsList = async (accessToken: string): Promise<ToolRow[]> => {
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/tool/list` : `/v1/tool/list`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
throw new Error(errorData);
}
const data = await response.json();
return data.tools ?? [];
};
export interface ToolSpendEntry {
tool_name: string;
spend: number;
call_count: number;
total_tokens: number;
}
export interface ToolSpendDailyEntry {
date: string;
tool_name: string;
spend: number;
call_count: number;
}
export interface ToolSpendResponse {
by_tool: ToolSpendEntry[];
daily: ToolSpendDailyEntry[];
start_date: string | null;
end_date: string | null;
}
export const getToolSpend = async (
accessToken: string,
startDate?: string,
endDate?: string,
): Promise<ToolSpendResponse> =>
apiClient.get<ToolSpendResponse>(`/v1/tool/spend`, {
accessToken,
query: { start_date: startDate, end_date: endDate },
});
export interface ToolPolicyOverrideRow {
override_id: string;
tool_name: string;
team_id?: string | null;
key_hash?: string | null;
input_policy: string;
key_alias?: string | null;
created_at?: string;
updated_at?: string;
}
export interface ToolDetailResponse {
tool: ToolRow;
overrides: ToolPolicyOverrideRow[];
}
export interface ToolUsageLogEntry {
id: string;
timestamp: string;
model?: string | null;
spend?: number | null;
total_tokens?: number | null;
input_snippet?: string | null;
}
export interface ToolUsageLogsResponse {
logs: ToolUsageLogEntry[];
total: number;
page: number;
page_size: number;
}
export const getToolUsageLogs = async (
accessToken: string,
toolName: string,
options: { page?: number; pageSize?: number; startDate?: string; endDate?: string },
): Promise<ToolUsageLogsResponse> => {
const encoded = encodeURIComponent(toolName);
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/tool/${encoded}/logs` : `/v1/tool/${encoded}/logs`;
const params = new URLSearchParams();
if (options.page != null) params.append("page", String(options.page));
if (options.pageSize != null) params.append("page_size", String(options.pageSize));
if (options.startDate) params.append("start_date", options.startDate);
if (options.endDate) params.append("end_date", options.endDate);
const fullUrl = params.toString() ? `${url}?${params.toString()}` : url;
const response = await fetch(fullUrl, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(deriveErrorMessage(errorData));
}
return response.json();
};
export const fetchToolDetail = async (accessToken: string, toolName: string): Promise<ToolDetailResponse> => {
const encoded = encodeURIComponent(toolName);
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/tool/${encoded}/detail` : `/v1/tool/${encoded}/detail`;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
throw new Error(errorData);
}
return response.json();
};
export const updateToolPolicy = async (
accessToken: string,
toolName: string,
policies: { input_policy?: string; output_policy?: string },
options?: { team_id?: string | null; key_hash?: string | null; key_alias?: string | null },
): Promise<ToolRow> => {
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/tool/policy` : `/v1/tool/policy`;
const body: Record<string, string | undefined | null> = {
tool_name: toolName,
};
if (policies.input_policy != null) body.input_policy = policies.input_policy;
if (policies.output_policy != null) body.output_policy = policies.output_policy;
if (options?.team_id != null) body.team_id = options.team_id || undefined;
if (options?.key_hash != null) body.key_hash = options.key_hash || undefined;
if (options?.key_alias != null) body.key_alias = options.key_alias || undefined;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!response.ok) {
const errorData = await response.text();
throw new Error(errorData);
}
return response.json();
};
export const deleteToolPolicyOverride = async (
accessToken: string,
toolName: string,
params: { team_id?: string | null; key_hash?: string | null },
): Promise<{ deleted: boolean; tool_name: string }> => {
const encoded = encodeURIComponent(toolName);
const q = new URLSearchParams();
if (params.team_id != null && params.team_id !== "") q.set("team_id", params.team_id);
if (params.key_hash != null && params.key_hash !== "") q.set("key_hash", params.key_hash);
const query = q.toString();
const url = proxyBaseUrl
? `${proxyBaseUrl}/v1/tool/${encoded}/overrides${query ? `?${query}` : ""}`
: `/v1/tool/${encoded}/overrides${query ? `?${query}` : ""}`;
const response = await fetch(url, {
method: "DELETE",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
},
});
if (!response.ok) {
const errorData = await response.text();
throw new Error(errorData);
}
return response.json();
};
// ── MCP OAuth user-credential helpers ────────────────────────────────────────
export interface MCPOAuthUserCredentialStatus {
server_id: string;
has_credential: boolean;
expires_at?: string | null;
is_expired: boolean;
connected_at?: string | null;
}
export interface MCPUserCredentialListItem {
server_id: string;
server_name?: string | null;
alias?: string | null;
credential_type: string;
has_credential: boolean;
expires_at?: string | null;
connected_at?: string | null;
}
export const storeMCPOAuthUserCredential = async (
accessToken: string,
serverId: string,
tokenResponse: { access_token: string; refresh_token?: string; expires_in?: number; scopes?: string[] },
): Promise<MCPOAuthUserCredentialStatus> => {
const url = proxyBaseUrl
? `${proxyBaseUrl}/v1/mcp/server/${serverId}/oauth-user-credential`
: `/v1/mcp/server/${serverId}/oauth-user-credential`;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(tokenResponse),
});
if (!response.ok) {
const err = await response.json().catch(() => ({}));
const errObj = err as { detail?: unknown };
const detail = errObj?.detail;
const detailMsg = Array.isArray(detail)
? detail
.map((d: unknown) =>
d && typeof d === "object" ? (d as Record<string, unknown>).msg ?? JSON.stringify(d) : String(d),
)
.join("; ")
: typeof detail === "string"
? detail
: detail && typeof (detail as Record<string, unknown>).error === "string"
? ((detail as Record<string, unknown>).error as string)
: undefined;
throw new Error(detailMsg || "Failed to store OAuth credential");
}
return response.json();
};
export const deleteMCPOAuthUserCredential = async (
accessToken: string,
serverId: string,
): Promise<MCPOAuthUserCredentialStatus> => {
const url = proxyBaseUrl
? `${proxyBaseUrl}/v1/mcp/server/${serverId}/oauth-user-credential`
: `/v1/mcp/server/${serverId}/oauth-user-credential`;
const response = await fetch(url, {
method: "DELETE",
headers: { [globalLitellmHeaderName]: `Bearer ${accessToken}` },
});
if (!response.ok) {
const err = await response.json().catch(() => ({}));
const errObj = err as { detail?: unknown };
const detail = errObj?.detail;
const detailMsg = Array.isArray(detail)
? detail
.map((d: unknown) =>
d && typeof d === "object" ? (d as Record<string, unknown>).msg ?? JSON.stringify(d) : String(d),
)
.join("; ")
: typeof detail === "string"
? detail
: detail && typeof (detail as Record<string, unknown>).error === "string"
? ((detail as Record<string, unknown>).error as string)
: undefined;
throw new Error(detailMsg || "Failed to revoke OAuth credential");
}
return response.json();
};
export const getMCPOAuthUserCredentialStatus = async (
accessToken: string,
serverId: string,
): Promise<MCPOAuthUserCredentialStatus> => {
const url = proxyBaseUrl
? `${proxyBaseUrl}/v1/mcp/server/${serverId}/oauth-user-credential/status`
: `/v1/mcp/server/${serverId}/oauth-user-credential/status`;
const response = await fetch(url, {
method: "GET",
headers: { [globalLitellmHeaderName]: `Bearer ${accessToken}` },
});
if (!response.ok) {
return { server_id: serverId, has_credential: false, is_expired: false };
}
return response.json();
};
export const listMCPUserCredentials = async (accessToken: string): Promise<MCPUserCredentialListItem[]> => {
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/mcp/user-credentials` : `/v1/mcp/user-credentials`;
const response = await fetch(url, {
method: "GET",
headers: { [globalLitellmHeaderName]: `Bearer ${accessToken}` },
});
if (!response.ok) return [];
return response.json();
};
// ============================================================
// MCP per-user env vars (/v1/mcp/server/{id}/user-env-vars)
// ============================================================
export const getMCPUserEnvVars = async (accessToken: string, serverId: string): Promise<MCPUserEnvVarsStatus> => {
return apiClient.get<MCPUserEnvVarsStatus>(`/v1/mcp/server/${serverId}/user-env-vars`, { accessToken });
};
export const storeMCPUserEnvVars = async (
accessToken: string,
serverId: string,
values: Record<string, string>,
): Promise<MCPUserEnvVarsStatus> => {
return apiClient.post<MCPUserEnvVarsStatus>(`/v1/mcp/server/${serverId}/user-env-vars`, {
accessToken,
body: { values },
});
};
export const listMCPUserEnvVarStatus = async (accessToken: string): Promise<MCPUserEnvVarsStatus[]> => {
// Best-effort status badges: a failure here must not break the page, so fall
// back to an empty list rather than surfacing the error to the caller.
try {
return await apiClient.get<MCPUserEnvVarsStatus[]>("/v1/mcp/user-env-vars/status", { accessToken });
} catch {
return [];
}
};
// ============================================================
// Memory management (/v1/memory)
// ============================================================
/**
* Encode a memory key for use in a URL path segment.
*
* The backend route is declared as `/v1/memory/{key:path}`, which supports
* slashes in the key (e.g. `user/123/notes`). Plain `encodeURIComponent`
* encodes `/` as `%2F`, and some proxies/middlewares (nginx default,
* CloudFlare, AWS ALB) either reject or silently re-decode `%2F`, which
* can break the request before FastAPI ever sees it.
*
* We keep slashes literal as path delimiters while still encoding every
* other potentially-unsafe character (spaces, `?`, `#`, `%`, etc.) per
* path segment.
*/
const encodeMemoryKeyForPath = (key: string): string => key.split("/").map(encodeURIComponent).join("/");
export interface MemoryRow {
memory_id: string;
key: string;
value: string;
metadata?: unknown;
user_id?: string | null;
team_id?: string | null;
created_at?: string;
created_by?: string | null;
updated_at?: string;
updated_by?: string | null;
}
export interface MemoryListResponse {
memories: MemoryRow[];
total: number;
}
export const fetchMemoryList = async (
accessToken: string,
options: {
key?: string;
keyPrefix?: string;
search?: string;
page?: number;
pageSize?: number;
} = {},
): Promise<MemoryListResponse> => {
const base = proxyBaseUrl ? `${proxyBaseUrl}/v1/memory` : `/v1/memory`;
const params = new URLSearchParams();
// Backend precedence is search > key_prefix > key; only the winner is sent
// to keep the URL clean and intent obvious.
if (options.search) {
params.append("search", options.search);
} else if (options.keyPrefix) {
params.append("key_prefix", options.keyPrefix);
} else if (options.key) {
params.append("key", options.key);
}
if (options.page != null) params.append("page", String(options.page));
if (options.pageSize != null) params.append("page_size", String(options.pageSize));
const url = params.toString() ? `${base}?${params.toString()}` : base;
const response = await fetch(url, {
method: "GET",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
throw new Error(errorData);
}
return response.json();
};
export const createMemory = async (
accessToken: string,
payload: { key: string; value: string; metadata?: unknown },
): Promise<MemoryRow> => {
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/memory` : `/v1/memory`;
const body: Record<string, unknown> = {
key: payload.key,
value: payload.value,
};
if (payload.metadata !== undefined) body.metadata = payload.metadata;
const response = await fetch(url, {
method: "POST",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!response.ok) {
const errorData = await response.text();
throw new Error(errorData);
}
return response.json();
};
export const updateMemory = async (
accessToken: string,
key: string,
payload: { value?: string; metadata?: unknown },
): Promise<MemoryRow> => {
const encoded = encodeMemoryKeyForPath(key);
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/memory/${encoded}` : `/v1/memory/${encoded}`;
const response = await fetch(url, {
method: "PUT",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
});
if (!response.ok) {
const errorData = await response.text();
throw new Error(errorData);
}
return response.json();
};
export const deleteMemory = async (accessToken: string, key: string): Promise<void> => {
const encoded = encodeMemoryKeyForPath(key);
const url = proxyBaseUrl ? `${proxyBaseUrl}/v1/memory/${encoded}` : `/v1/memory/${encoded}`;
const response = await fetch(url, {
method: "DELETE",
headers: {
[globalLitellmHeaderName]: `Bearer ${accessToken}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
const errorData = await response.text();
throw new Error(errorData);
}
};