Merge branch 'litellm_internal_staging' into litellm_dead_locals_5_8

This commit is contained in:
Yuneng Jiang 2026-08-05 18:07:23 -07:00
commit 6e434c926b
No known key found for this signature in database
33 changed files with 25 additions and 249 deletions

View file

@ -1,6 +1,6 @@
import React from "react";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { screen, within } from "@testing-library/react";
import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { renderWithProviders } from "../../../../../../tests/test-utils";
import MultiCostResults from "./multi_cost_results";

View file

@ -1,5 +1,5 @@
import React from "react";
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { screen, fireEvent } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { renderWithProviders } from "../../../../../../tests/test-utils";

View file

@ -1,6 +1,6 @@
import React from "react";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { screen, within } from "@testing-library/react";
import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { renderWithProviders } from "../../../../../tests/test-utils";
import ProviderDiscountTable from "./provider_discount_table";

View file

@ -229,7 +229,7 @@ describe("Guardrail Info", () => {
vi.mocked(networking.getGuardrailProviderSpecificParams).mockResolvedValue({});
vi.mocked(networking.updateGuardrailCall).mockResolvedValue({ status: "success" });
const { getByText, getByRole, getAllByRole, getByLabelText } = render(
const { getByText, getByLabelText } = render(
<GuardrailInfoView guardrailId="123" onClose={() => {}} accessToken="123" isAdmin={true} />,
);

View file

@ -1,5 +1,5 @@
import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import { render, screen } from "@testing-library/react";
import { describe, it, expect, vi, beforeEach } from "vitest";
import userEvent from "@testing-library/user-event";
import MCPLogoSelector from "./MCPLogoSelector";

View file

@ -60,7 +60,7 @@ describe("ChatUI", () => {
});
it("should show the voice selector when the endpoint type is audio_speech", async () => {
const { getByText, container } = render(
const { getByText } = render(
<ChatUI
accessToken="1234567890"
token="1234567890"
@ -110,7 +110,7 @@ describe("ChatUI", () => {
});
it("should allow the user to select a model", async () => {
const { getByText, container } = render(
const { getByText } = render(
<ChatUI
accessToken="1234567890"
token="1234567890"
@ -148,7 +148,7 @@ describe("ChatUI", () => {
{ model_group: "ResponsesModel", mode: "responses" },
]);
const { getByText, baseElement } = render(
const { getByText } = render(
<ChatUI
accessToken="1234567890"
token="1234567890"

View file

@ -15,7 +15,7 @@ interface UIThemeSettingsProps {
}
const UIThemeSettings: React.FC<UIThemeSettingsProps> = ({ userID, userRole, accessToken }) => {
const { logoUrl, setLogoUrl, faviconUrl, setFaviconUrl } = useTheme();
const { setLogoUrl, setFaviconUrl } = useTheme();
const [logoUrlInput, setLogoUrlInput] = useState<string>("");
const [faviconUrlInput, setFaviconUrlInput] = useState<string>("");
const [loading, setLoading] = useState(false);

View file

@ -15,7 +15,7 @@ import {
Tooltip,
Typography,
} from "antd";
import React, { useEffect, useMemo, useState } from "react";
import React, { useEffect, useState } from "react";
import BulkCreateUsers from "./bulk_create_users_button";
import TeamDropdown from "./common_components/team_dropdown";
import { getModelDisplayName } from "./key_team_helpers/fetch_available_models_team_key";
@ -29,7 +29,7 @@ import {
} from "./networking";
import OnboardingModal, { InvitationLink } from "./onboarding_link";
const { Option } = Select;
const { Text, Link, Title } = Typography;
const { Text, Link } = Typography;
// Helper function to generate UUID compatible across all environments
const generateUUID = (): string => {
if (typeof crypto !== "undefined" && crypto.randomUUID) {
@ -80,11 +80,6 @@ export const CreateUserButton: React.FC<CreateuserProps> = ({
const { data: organizations = [] } = useOrganizations();
// Derive teams from the user's organizations, falling back to the teams prop
const availableTeams = useMemo(() => {
const orgTeams = organizations.flatMap((org) => org.teams || []);
if (orgTeams.length > 0) return orgTeams;
return teams || [];
}, [organizations, teams]);
useEffect(() => {
const fetchData = async () => {

View file

@ -1,5 +1,5 @@
import React from "react";
import { describe, it, expect, vi } from "vitest";
import { describe, it, expect } from "vitest";
import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { renderWithProviders } from "../../tests/test-utils";

View file

@ -2,7 +2,7 @@ import { ProxyModel, useAllProxyModels } from "@/app/(dashboard)/hooks/models/us
import { useOrganization } from "@/app/(dashboard)/hooks/organizations/useOrganizations";
import { useTeam } from "@/app/(dashboard)/hooks/teams/useTeams";
import { useCurrentUser } from "@/app/(dashboard)/hooks/users/useCurrentUser";
import { Select, Skeleton, Tooltip, type SelectProps } from "antd";
import { Select, Skeleton, Tooltip } from "antd";
import { Organization, Team } from "../networking";
import { splitWildcardModels } from "./modelUtils";
@ -93,8 +93,7 @@ const filterModels = (
export const ModelSelect = (props: ModelSelectProps) => {
const { teamID, organizationID, options, context, dataTestId, value = [], onChange, style } = props;
const { includeUserModels, showAllTeamModelsOption, showAllProxyModelsOverride, includeSpecialOptions } =
options || {};
const { showAllProxyModelsOverride, includeSpecialOptions } = options || {};
const { data: allProxyModels, isLoading: isLoadingAllProxyModels } = useAllProxyModels();
const { data: team, isLoading: isLoadingTeam } = useTeam(teamID);
const { data: organization, isLoading: isLoadingOrganization } = useOrganization(organizationID);
@ -113,10 +112,6 @@ export const ModelSelect = (props: ModelSelectProps) => {
return <Skeleton.Input active block />;
}
const optionRender: NonNullable<SelectProps["optionRender"]> = (option) => {
return <span>{option.label}</span>;
};
const handleChange = (values: string[]) => {
const specialValues = values.filter(isSpecialOption);

View file

@ -20,7 +20,7 @@ interface TopKeyViewProps {
}
const TopKeyView: React.FC<TopKeyViewProps> = ({ topKeys, teams, showTags = false, topKeysLimit, setTopKeysLimit }) => {
const { accessToken, userRole, userId: userID, premiumUser } = useAuthorized();
const { accessToken } = useAuthorized();
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
const [selectedKey, setSelectedKey] = useState<string | null>(null);
const [keyData, setKeyData] = useState<any | undefined>(undefined);

View file

@ -66,7 +66,7 @@ const AddModelForm: React.FC<AddModelFormProps> = ({
} = useProviderFields();
const { data: guardrailsData } = useGuardrails();
const guardrailsList = guardrailsData?.guardrails.map((g) => g.guardrail_name);
const { data: tagsList, isLoading: isTagsLoading, error: tagsError } = useTags();
const { data: tagsList } = useTags();
const handleTestConnection = async () => {
setIsTestingConnection(true);

View file

@ -126,13 +126,6 @@ const RouterConfigBuilder: React.FC<RouterConfigBuilderProps> = ({ modelInfo, va
};
// Handle utterances change (convert textarea string to array)
const handleUtterancesChange = (routeId: string, utterancesText: string) => {
const utterancesArray = utterancesText
.split("\n")
.map((line) => line.trim()) // Only trims leading/trailing whitespace, preserves internal spaces
.filter((line) => line.length > 0);
updateRoute(routeId, "utterances", utterancesArray);
};
// Prepare model options for dropdowns
const modelOptions = modelInfo.map((model) => ({

View file

@ -191,11 +191,6 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({
const isAdmin = all_admin_roles.includes(userRole);
const modelGroupOptions = Array.from(new Set(modelInfo.map((option) => option.model_group))).map((model_group) => ({
value: model_group,
label: model_group,
}));
const availability = React.useMemo(
() =>
buildModelAvailability(

View file

@ -37,7 +37,6 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
const [form] = Form.useForm();
const [isModalVisible, setIsModalVisible] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [selectedModel, setSelectedModel] = useState("");
const [pathValue, setPathValue] = useState("");
const [targetValue, setTargetValue] = useState("");
const [includeSubpath, setIncludeSubpath] = useState(true);
@ -107,11 +106,6 @@ const AddPassThroughEndpoint: React.FC<AddFallbacksProps> = ({
}
};
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
NotificationsManager.success("Copied to clipboard!");
};
return (
<div>
<Button className="mx-auto mb-4 mt-4" onClick={() => setIsModalVisible(true)}>

View file

@ -1,5 +1,4 @@
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi, beforeEach } from "vitest";
// Mock networking module

View file

@ -80,24 +80,6 @@ const BulkCreateUsersButton: React.FC<BulkCreateUsersProps> = ({
setBaseUrl(base.toString());
}, [accessToken]);
const downloadTemplate = () => {
const template = [
["user_email", "user_role", "teams", "max_budget", "budget_duration", "models"],
["user@example.com", "internal_user", "team-id-1,team-id-2", "100", "30d", "gpt-3.5-turbo,gpt-4"],
];
const csv = Papa.unparse(template);
const blob = new Blob([csv], { type: "text/csv" });
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "bulk_users_template.csv";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
};
const handleFileUpload = (file: File) => {
// Reset all error states
setParseError(null);

View file

@ -43,9 +43,6 @@ export const generateCodeSnippet = (params: GenerateCodeParams): string => {
selectedVectorStores,
selectedGuardrails,
selectedPolicies,
selectedMCPServers,
mcpServers,
mcpServerToolRestrictions,
selectedVoice,
endpointType,
selectedModel,

View file

@ -1,8 +1,7 @@
import React from "react";
import { Typography, Collapse } from "antd";
import { Collapse } from "antd";
import type { MCPEvent } from "@/components/mcp_tools/types";
const { Text } = Typography;
const { Panel } = Collapse;
interface MCPEventsDisplayProps {

View file

@ -32,7 +32,7 @@ export default function DeleteResourceModal({
confirmLoading,
requiredConfirmation,
}: DeleteResourceModalProps) {
const { Title, Text } = Typography;
const { Text } = Typography;
const { token } = theme.useToken();
const [requiredConfirmationInput, setRequiredConfirmationInput] = useState("");

View file

@ -2,7 +2,7 @@ import React from "react";
import { Form, Button, Tooltip, Typography, Modal } from "antd";
import { TextInput } from "@tremor/react";
import { CredentialItem } from "../networking";
const { Title, Link } = Typography;
const { Link } = Typography;
interface ReuseCredentialsModalProps {
isVisible: boolean;

View file

@ -121,13 +121,6 @@ const ModelFilters: React.FC<ModelFiltersProps> = ({
};
// Expose filter values and reset function
const filterValues = {
searchTerm,
selectedProvider,
selectedMode,
selectedFeature,
resetFilters,
};
const filtersContent = (
<div className="flex flex-wrap gap-4 items-center">

View file

@ -1,5 +1,5 @@
import userEvent from "@testing-library/user-event";
import React, { useState } from "react";
import React from "react";
import { describe, expect, it, vi } from "vitest";
import { renderWithProviders, screen, waitFor } from "../../tests/test-utils";
import Navbar from "./navbar";

View file

@ -390,7 +390,6 @@ export const getAgentCreateMetadata = async (): Promise<AgentCreateInfo[]> => {
// Global variable for the header name
let globalLitellmHeaderName: string = "Authorization";
const MCP_AUTH_HEADER: string = "x-mcp-auth";
// Function to set the global header name
export function setGlobalLitellmHeaderName(headerName: string = "Authorization") {

View file

@ -58,7 +58,7 @@ export default function OnboardingModal({
invitationLinkData,
modalType = "invitation",
}: OnboardingProps) {
const { Title, Paragraph } = Typography;
const { Paragraph } = Typography;
const handleInvitationOk = () => {
setIsInvitationLinkModalVisible(false);
};

View file

@ -72,7 +72,7 @@ const PassThroughInfoView: React.FC<PassThroughInfoProps> = ({
onEndpointUpdated,
}) => {
const [endpointData, setEndpointData] = useState<PassThroughEndpoint | null>(initialEndpointData);
const [loading, setLoading] = useState(false);
const [loading] = useState(false);
const [isEditing, setIsEditing] = useState(false);
const [authEnabled, setAuthEnabled] = useState(initialEndpointData?.auth || false);
const [selectedMethods, setSelectedMethods] = useState<string[]>(initialEndpointData?.methods || []);

View file

@ -20,13 +20,11 @@ import {
} from "@tremor/react";
import React, { useEffect, useState } from "react";
import { Button as Button2, Form, Input, Modal, Select, Typography } from "antd";
import { Button as Button2, Form, Input, Modal, Select } from "antd";
import EmailSettings from "./email_settings";
import { Logo } from "@/components/molecules/logo/Logo";
import NotificationsManager from "./molecules/notifications_manager";
const { Title, Paragraph } = Typography;
import FormItem from "antd/es/form/FormItem";
import AlertingSettings from "./alerting/alerting_settings";
import CloudZeroCostTracking from "./CloudZeroCostTracking/CloudZeroCostTracking";
@ -48,12 +46,6 @@ interface SettingsPageProps {
premiumUser: boolean;
}
interface genericCallbackParams {
litellm_callback_name: string; // what to send in request
ui_callback_name: string; // what to show on UI
litellm_callback_params: string[] | null; // known required params for this callback
}
const assetsLogoFolder = "/ui/assets/logos/";
export const backendCallbackLogoSrc = (logo: string | null | undefined): string | undefined => {
@ -214,7 +206,6 @@ const Settings: React.FC<SettingsPageProps> = ({ accessToken, userRole, userID,
const [callbacks, setCallbacks] = useState<AlertingObject[]>([]);
const [isLoadingCallbacks, setIsLoadingCallbacks] = useState(true);
const [alerts, setAlerts] = useState<any[]>([]);
const [isModalVisible, setIsModalVisible] = useState(false);
const [addForm] = Form.useForm();
const [editForm] = Form.useForm();
const [selectedCallback, setSelectedCallback] = useState<string | null>(null);
@ -418,119 +409,6 @@ const Settings: React.FC<SettingsPageProps> = ({ accessToken, userRole, userID,
}
NotificationsManager.success("Alerts updated successfully");
};
const handleSaveChanges = (callback: any) => {
if (!accessToken) {
return;
}
const updatedVariables = Object.fromEntries(
Object.entries(callback.variables).map(([key, value]) => [
key,
(document.querySelector(`input[name="${key}"]`) as HTMLInputElement)?.value || value,
]),
);
const payload = {
environment_variables: updatedVariables,
litellm_settings: {
success_callback: [callback.name],
},
};
try {
setCallbacksCall(accessToken, payload);
} catch (error) {
NotificationsManager.fromBackend(error);
}
NotificationsManager.success("Callback updated successfully");
};
const handleOk = () => {
if (!accessToken) {
return;
}
// Handle form submission
addForm.validateFields().then((values) => {
// Call API to add the callback
let payload;
if (values.callback === "langfuse" || values.callback === "langfuse_otel") {
payload = {
environment_variables: {
LANGFUSE_PUBLIC_KEY: values.langfusePublicKey,
LANGFUSE_SECRET_KEY: values.langfusePrivateKey,
},
litellm_settings: {
success_callback: [values.callback],
},
};
setCallbacksCall(accessToken, payload);
let newCallback: AlertingObject = {
name: values.callback,
variables: {
SLACK_WEBHOOK_URL: null,
LANGFUSE_HOST: null,
LANGFUSE_PUBLIC_KEY: values.langfusePublicKey,
LANGFUSE_SECRET_KEY: values.langfusePrivateKey,
OPENMETER_API_KEY: null,
},
};
// add langfuse to callbacks
setCallbacks(callbacks ? [...callbacks, newCallback] : [newCallback]);
} else if (values.callback === "slack") {
payload = {
general_settings: {
alerting: ["slack"],
alerting_threshold: 300,
},
environment_variables: {
SLACK_WEBHOOK_URL: values.slackWebhookUrl,
},
};
setCallbacksCall(accessToken, payload);
let newCallback: AlertingObject = {
name: values.callback,
variables: {
SLACK_WEBHOOK_URL: values.slackWebhookUrl,
LANGFUSE_HOST: null,
LANGFUSE_PUBLIC_KEY: null,
LANGFUSE_SECRET_KEY: null,
OPENMETER_API_KEY: null,
},
};
setCallbacks(callbacks ? [...callbacks, newCallback] : [newCallback]);
} else if (values.callback == "openmeter") {
payload = {
environment_variables: {
OPENMETER_API_KEY: values.openMeterApiKey,
},
litellm_settings: {
success_callback: [values.callback],
},
};
setCallbacksCall(accessToken, payload);
let newCallback: AlertingObject = {
name: values.callback,
variables: {
SLACK_WEBHOOK_URL: null,
LANGFUSE_HOST: null,
LANGFUSE_PUBLIC_KEY: null,
LANGFUSE_SECRET_KEY: null,
OPENMETER_API_KEY: values.openMeterAPIKey,
},
};
// add langfuse to callbacks
setCallbacks(callbacks ? [...callbacks, newCallback] : [newCallback]);
} else {
payload = {
error: "Invalid callback value",
};
}
setIsModalVisible(false);
addForm.resetFields();
setSelectedCallback(null);
});
};
const handleDeleteCallback = (callback: any) => {
setCallbackToDelete(callback);

View file

@ -50,22 +50,6 @@ interface KeyEditViewProps {
}
// Add this helper function
const getAvailableModelsForKey = (keyData: KeyResponse, teams: any[] | null): string[] => {
// If no teams data is available, return empty array
if (!teams || !keyData.team_id) {
return [];
}
// Find the team that matches the key's team_id
const keyTeam = teams.find((team) => team.team_id === keyData.team_id);
// If team found and has models, return those models
if (keyTeam?.models) {
return keyTeam.models;
}
return [];
};
// Helper function to determine key_type display value from allowed_routes
const getKeyTypeFromRoutes = (allowedRoutes: string[] | null | undefined): string => {

View file

@ -1,11 +1,9 @@
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
import { useProjects } from "@/app/(dashboard)/hooks/projects/useProjects";
import useTeams from "@/app/(dashboard)/hooks/useTeams";
import { renderWithProviders } from "../../../tests/test-utils";
import { screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { useResetKeySpend } from "@/app/(dashboard)/hooks/keys/useResetKeySpend";
import { KeyResponse, Team } from "../key_team_helpers/key_list";
import { keyDeleteCall, keyUpdateCall } from "../networking";
import { QueryClient } from "@tanstack/react-query";

View file

@ -38,10 +38,6 @@ interface DistinctTagResponse {
tag: string;
}
interface DistinctTagsResponse {
results: DistinctTagResponse[];
}
interface UserAgentActivityProps {
accessToken: string | null;
userRole: string | null;
@ -59,7 +55,7 @@ const UserAgentActivity: React.FC<UserAgentActivityProps> = ({ accessToken, user
const [mauData, setMauData] = useState<ActiveUsersAnalyticsResponse>({ results: [] });
const [summaryData, setSummaryData] = useState<TagSummaryResponse>({ results: [] });
const [userAgentFilter, setUserAgentFilter] = useState<string>("");
const [userAgentFilter] = useState<string>("");
// Tag filtering state
const [availableTags, setAvailableTags] = useState<string[]>([]);

View file

@ -235,18 +235,6 @@ const DownloadIcon = () => (
</svg>
);
const ExternalLinkIcon = () => (
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" className="inline ml-1">
<path
d="M6 2H3a1 1 0 00-1 1v8a1 1 0 001 1h8a1 1 0 001-1V8M8 2h4m0 0v4m0-4L6.5 7.5"
stroke="currentColor"
strokeWidth="1.2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
// ── Sub-components ──────────────────────────────────────────────────────────
const MatchDetailsTable = ({ matchDetails }: { matchDetails: MatchDetail[] }) => {
@ -647,10 +635,6 @@ const GuardrailViewer = ({ data, accessToken, logEntry }: GuardrailViewerProps)
return Math.round(guardrailEntries.reduce((sum, e) => sum + (e.duration ?? 0), 0) * 1000);
}, [guardrailEntries]);
const policyTemplates = useMemo(() => {
return Array.from(new Set(guardrailEntries.map((e) => e.policy_template).filter(Boolean)));
}, [guardrailEntries]);
if (guardrailEntries.length === 0) {
return null;
}

View file

@ -1,5 +1,5 @@
import React from "react";
import { render, screen, fireEvent, act, waitFor } from "@testing-library/react";
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { RealtimePrettyView, isRealtimeResponse } from "./RealtimePrettyView";

View file

@ -5,11 +5,6 @@ import { formatNumberWithCommas } from "@/utils/dataUtils";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";
// Define the props type
interface UserSpendData {
spend: number; // Adjust the type accordingly based on your data
max_budget?: number | null; // Optional property with a default of null
// Add other properties if needed
}
interface ViewUserSpendProps {
userSpend: number | null;
userMaxBudget: number | null;