mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
8164 lines
235 KiB
TypeScript
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);
|
|
}
|
|
};
|