From 6fc1718a4a9e000a42e619df93a807b93079fd25 Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 14 Jul 2026 13:25:54 +0000 Subject: [PATCH] fix(ui): normalize persisted cache controls Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../DefaultLitellmParamsSection.test.tsx | 12 ++++++ .../DefaultLitellmParamsSection.tsx | 43 ++++++++++++++----- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/ui/litellm-dashboard/src/components/router_settings/DefaultLitellmParamsSection.test.tsx b/ui/litellm-dashboard/src/components/router_settings/DefaultLitellmParamsSection.test.tsx index 9638b81d8b1..8c640f3b91a 100644 --- a/ui/litellm-dashboard/src/components/router_settings/DefaultLitellmParamsSection.test.tsx +++ b/ui/litellm-dashboard/src/components/router_settings/DefaultLitellmParamsSection.test.tsx @@ -26,6 +26,18 @@ describe("DefaultLitellmParamsSection", () => { expect(screen.getByTestId("cache-control-location-select-0")).toBeInTheDocument(); }); + it("should normalize nullable cache control fields from persisted settings", () => { + render( + , + ); + + expect(screen.getByTestId("cache-control-location-select-0")).toBeInTheDocument(); + expect(screen.getByTestId("cache-control-index-input-0")).toHaveValue(""); + }); + it("should preserve unsupported cache control points in the JSON editor", () => { render( } | { status: "invalid"; message: string }; const CACHE_CONTROL_ROLES = ["user", "system", "assistant"] as const; +type CacheControlRole = (typeof CACHE_CONTROL_ROLES)[number]; const parseDefaultParams = (text: string): ParsedDefaultParams => { try { @@ -26,27 +27,47 @@ const parseDefaultParams = (text: string): ParsedDefaultParams => { } }; -const isCacheControlInjectionPoint = (value: unknown): value is CacheControlInjectionPoint => { +const isCacheControlRole = (value: unknown): value is CacheControlRole => + CACHE_CONTROL_ROLES.some((validRole) => validRole === value); + +const parseCacheControlInjectionPoint = (value: unknown): CacheControlInjectionPoint | undefined => { if (typeof value !== "object" || value === null) { - return false; + return undefined; } if (!("location" in value) || value.location !== "message") { - return false; + return undefined; } const role = "role" in value ? value.role : undefined; const index = "index" in value ? value.index : undefined; - const hasValidRole = role === undefined || CACHE_CONTROL_ROLES.some((validRole) => validRole === role); - const hasValidIndex = index === undefined || (typeof index === "number" && Number.isInteger(index)); - return hasValidRole && hasValidIndex; + const hasInvalidRole = role !== undefined && role !== null && !isCacheControlRole(role); + if (hasInvalidRole) { + return undefined; + } + const hasIndex = index !== undefined && index !== null; + const hasInvalidIndex = hasIndex && (typeof index !== "number" || !Number.isInteger(index)); + if (hasInvalidIndex) { + return undefined; + } + return { + location: "message", + ...(isCacheControlRole(role) ? { role } : {}), + ...(typeof index === "number" ? { index } : {}), + }; +}; + +const parseCacheControlInjectionPoints = (value: unknown): CacheControlInjectionPoint[] | undefined => { + if (!Array.isArray(value)) { + return undefined; + } + const points = value.map(parseCacheControlInjectionPoint); + return points.every((point) => point !== undefined) + ? points.filter((point): point is CacheControlInjectionPoint => point !== undefined) + : undefined; }; const DefaultLitellmParamsSection: React.FC = ({ value, onChange }) => { const cacheControlInjectionPoints = React.useMemo( - () => - Array.isArray(value.cache_control_injection_points) && - value.cache_control_injection_points.every(isCacheControlInjectionPoint) - ? value.cache_control_injection_points - : undefined, + () => parseCacheControlInjectionPoints(value.cache_control_injection_points), [value.cache_control_injection_points], ); const otherParams = React.useMemo(