diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/WebSearchInterceptionSettings/WebSearchInterceptionSettings.test.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/WebSearchInterceptionSettings/WebSearchInterceptionSettings.test.tsx index f28891a5da5..ae981aa9767 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/WebSearchInterceptionSettings/WebSearchInterceptionSettings.test.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/WebSearchInterceptionSettings/WebSearchInterceptionSettings.test.tsx @@ -127,6 +127,29 @@ describe("WebSearchInterceptionSettings", () => { expect(mockMutate.mock.calls[0][0]).toEqual(ENABLED_PAYLOAD); }); + it("ignores stored values whose types do not match the field", async () => { + vi.mocked(useWebSearchInterceptionSettings).mockReturnValue({ + data: { + ...storedSettings, + values: { + enabled: "yes", + enabled_providers: "bedrock", + search_tool_name: 7, + max_agentic_loops: "3", + }, + }, + isLoading: false, + isError: false, + error: null, + } as any); + + await renderSettings(); + + expect(screen.getByRole("switch")).not.toBeChecked(); + expect(screen.getByLabelText(/max agentic loops/i)).toHaveValue(null); + expect(screen.queryByText("bedrock")).not.toBeInTheDocument(); + }); + it("reseeds the form when the stored settings change underneath it", async () => { vi.mocked(useWebSearchInterceptionSettings).mockReturnValue({ data: { ...storedSettings, values: { ...storedSettings.values, max_agentic_loops: 3 } }, diff --git a/ui/litellm-dashboard/src/components/Settings/AdminSettings/WebSearchInterceptionSettings/WebSearchInterceptionSettings.tsx b/ui/litellm-dashboard/src/components/Settings/AdminSettings/WebSearchInterceptionSettings/WebSearchInterceptionSettings.tsx index ce2141253ba..3e9e04e720b 100644 --- a/ui/litellm-dashboard/src/components/Settings/AdminSettings/WebSearchInterceptionSettings/WebSearchInterceptionSettings.tsx +++ b/ui/litellm-dashboard/src/components/Settings/AdminSettings/WebSearchInterceptionSettings/WebSearchInterceptionSettings.tsx @@ -45,7 +45,7 @@ interface WebSearchInterceptionFormValues { max_agentic_loops: number | null; } -const NO_STORED_VALUES: WebSearchInterceptionStoredValues = {}; +const NO_STORED_VALUES: Readonly> = {}; const MAX_AGENTIC_LOOPS_MIN = 1; @@ -69,6 +69,16 @@ const labelWithHint = (label: string, hint: string): React.ReactNode => ( const parseLoops = (raw: string, rawAsNumber: number): number | null => raw === "" || Number.isNaN(rawAsNumber) ? null : rawAsNumber; +const isStringArray = (value: unknown): value is string[] => + Array.isArray(value) && value.every((entry) => typeof entry === "string"); + +const toStoredValues = (raw: Readonly>): WebSearchInterceptionStoredValues => ({ + enabled: typeof raw.enabled === "boolean" ? raw.enabled : undefined, + enabled_providers: isStringArray(raw.enabled_providers) ? raw.enabled_providers : undefined, + search_tool_name: typeof raw.search_tool_name === "string" ? raw.search_tool_name : null, + max_agentic_loops: typeof raw.max_agentic_loops === "number" ? raw.max_agentic_loops : null, +}); + const toFormValues = (values: WebSearchInterceptionStoredValues): WebSearchInterceptionFormValues => ({ enabled: values.enabled ?? false, enabled_providers: values.enabled_providers ?? [], @@ -282,7 +292,7 @@ export default function WebSearchInterceptionSettings() { ); } - const values: WebSearchInterceptionStoredValues = data?.values ?? NO_STORED_VALUES; + const values: WebSearchInterceptionStoredValues = toStoredValues(data?.values ?? NO_STORED_VALUES); return (