From d7da44618380c52d1661f52ed8bc2bb9320477ef Mon Sep 17 00:00:00 2001 From: Tin Chi Lo Date: Mon, 31 Aug 2026 19:40:48 -0700 Subject: [PATCH] fix(ui): clearing the context-window buffer removes it from the payload Both review bots converged on the same defect: an emptied buffer field early-returned in commitBuffer, the draft was discarded on blur, and the stale number reappeared and stayed in the saved config, contradicting the copy that an empty field tracks the backend default. An empty commit now removes the key, which the managed-keys projection propagates as a real deletion on edit. Also trims the narrative comments the review flagged as restating behavior --- .../ContextWindowEscalationConfig.tsx | 9 ++++--- .../add_model/add_auto_router_tab.test.tsx | 25 +++++++++++++++++-- .../build_complexity_router_config.test.ts | 2 -- .../src/lib/autorouter_presets.test.ts | 2 -- 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/ui/litellm-dashboard/src/components/add_model/ContextWindowEscalationConfig.tsx b/ui/litellm-dashboard/src/components/add_model/ContextWindowEscalationConfig.tsx index 7271750aadc..c0a65076d20 100644 --- a/ui/litellm-dashboard/src/components/add_model/ContextWindowEscalationConfig.tsx +++ b/ui/litellm-dashboard/src/components/add_model/ContextWindowEscalationConfig.tsx @@ -8,13 +8,16 @@ const ContextWindowEscalationConfig: React.FC<{ onChange: (value: ComplexityRouterConfigValue) => void; }> = ({ value, onChange }) => { const enabled = value.enable_context_window_escalation ?? true; + // A number input renders Number("0.") as "0", so a decimal cannot be typed without a local draft. const [bufferDraft, setBufferDraft] = React.useState(null); - // min/max are inert on a text input, and a plain number input renders Number("0.") as "0" so a - // decimal cannot be typed. Hence the local draft plus an explicit clamp on commit. const commitBuffer = (raw: string) => { setBufferDraft(null); + if (raw.trim() === "") { + onChange({ ...value, context_window_escalation_buffer: undefined }); + return; + } const parsed = Number(raw); - if (raw.trim() === "" || !Number.isFinite(parsed)) return; + if (!Number.isFinite(parsed)) return; onChange({ ...value, context_window_escalation_buffer: Math.min(1, Math.max(0.01, parsed)) }); }; return ( diff --git a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx index e08952d5889..71b454dbb06 100644 --- a/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx +++ b/ui/litellm-dashboard/src/components/add_model/add_auto_router_tab.test.tsx @@ -394,8 +394,6 @@ describe("AddAutoRouterTab", () => { }); }); - // A plain number input renders Number("1.5") fine but the clamp is ours: values above 1 must commit as 1, - // and an untouched buffer must stay out of the payload so the router tracks the backend default. it("clamps the context-window buffer to 1 and keeps an untouched buffer out of the payload", async () => { const user = userEvent.setup(); vi.mocked(getMissingTiersError).mockReturnValue(null); @@ -417,6 +415,29 @@ describe("AddAutoRouterTab", () => { expect(config).not.toHaveProperty("enable_context_window_escalation"); }); + it("clearing the buffer removes it from the payload so the router tracks the backend default", async () => { + const user = userEvent.setup(); + vi.mocked(getMissingTiersError).mockReturnValue(null); + + renderWithProviders(); + + await user.type(screen.getByPlaceholderText(/smart_router/i), "ctx-clear-router"); + expandDetailedConfiguration(); + await user.click(screen.getByText("Advanced: Context Window Escalation")); + const buffer = await screen.findByLabelText("Window fit buffer"); + fireEvent.change(buffer, { target: { value: "0.8" } }); + fireEvent.blur(buffer, { target: { value: "0.8" } }); + fireEvent.change(buffer, { target: { value: "" } }); + fireEvent.blur(buffer, { target: { value: "" } }); + + await user.click(screen.getByRole("button", { name: /add auto router/i })); + + await waitFor(() => expect(handleAddAutoRouterSubmit).toHaveBeenCalled()); + expect(vi.mocked(handleAddAutoRouterSubmit).mock.calls.at(-1)?.[0].complexity_router_config).not.toHaveProperty( + "context_window_escalation_buffer", + ); + }); + // The scalar floor is the one scorer knob with no group dict behind it, so its wiring into the create // payload is only proven end to end. 0 is the case a truthy check would silently drop. it("carries a reasoning override floor of 0 through to the create payload", async () => { diff --git a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts index 159fdf957e4..feddaaa0eac 100644 --- a/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts +++ b/ui/litellm-dashboard/src/components/add_model/build_complexity_router_config.test.ts @@ -59,8 +59,6 @@ describe("buildComplexityRouterConfig", () => { expect(config).toEqual(expected); }); - // False is the value a truthy guard would silently drop, and it is the whole point of the toggle: - // an untouched form tracks the backend default (enabled), an explicit false is a real opt-out. it("carries an explicit context-window escalation opt-out and buffer, false included", () => { const config = buildComplexityRouterConfig({ ...baseParams, diff --git a/ui/litellm-dashboard/src/lib/autorouter_presets.test.ts b/ui/litellm-dashboard/src/lib/autorouter_presets.test.ts index 506411bb7da..f14d6279e32 100644 --- a/ui/litellm-dashboard/src/lib/autorouter_presets.test.ts +++ b/ui/litellm-dashboard/src/lib/autorouter_presets.test.ts @@ -573,8 +573,6 @@ describe("autorouter_presets", () => { expect(prefill.escalationKeywords).toEqual([]); }); - // The prefill mapping is a hand-written field list, so a new config key is silently dropped - // unless mapped; false is the value a `||` default would erase (the #38453 class). it("carries a preset's context-window escalation opt-out and buffer through the prefill", () => { const prefill = buildPresetPrefill( {