From 98e813d1d3086a59d0ad85437fc5a0461d9dd745 Mon Sep 17 00:00:00 2001 From: "Ton Hoang Nguyen (Bill)" <32552798+HahaBill@users.noreply.github.com> Date: Fri, 27 Jun 2025 00:13:14 +0100 Subject: [PATCH] feat: max value of maxOutputTokens is model's maxTokens + adding more tests --- .../__tests__/sliding-window.spec.ts | 24 ++++++ .../components/settings/providers/Gemini.tsx | 7 +- .../providers/__tests__/Gemini.spec.tsx | 85 +++++++++++++++++++ 3 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 webview-ui/src/components/settings/providers/__tests__/Gemini.spec.tsx diff --git a/src/core/sliding-window/__tests__/sliding-window.spec.ts b/src/core/sliding-window/__tests__/sliding-window.spec.ts index 3bda5351d4..d3837c7a23 100644 --- a/src/core/sliding-window/__tests__/sliding-window.spec.ts +++ b/src/core/sliding-window/__tests__/sliding-window.spec.ts @@ -250,6 +250,30 @@ describe("Sliding Window", () => { { role: "assistant", content: "Fourth message" }, { role: "user", content: "Fifth message" }, ] + it("should use contextLimit as contextWindow when apiProvider is gemini", async () => { + const contextLimit = 2 + const messages: ApiMessage[] = [ + { role: "user", content: "First message" }, + { role: "assistant", content: "Second message" }, + { role: "user", content: "Third message" }, + { role: "assistant", content: "Fourth message" }, + { role: "user", content: "" }, + ] + const result = await truncateConversationIfNeeded({ + messages, + totalTokens: 2, + contextWindow: contextLimit, + maxTokens: null, + apiHandler: mockApiHandler, + autoCondenseContext: false, + autoCondenseContextPercent: 100, + systemPrompt: "", + taskId, + profileThresholds: {}, + currentProfileId: "default", + }) + expect(result.messages).toEqual([messages[0], messages[3], messages[4]]) + }) it("should not truncate if tokens are below max tokens threshold", async () => { const modelInfo = createModelInfo(100000, 30000) diff --git a/webview-ui/src/components/settings/providers/Gemini.tsx b/webview-ui/src/components/settings/providers/Gemini.tsx index 8f3f2b536b..54df49c392 100644 --- a/webview-ui/src/components/settings/providers/Gemini.tsx +++ b/webview-ui/src/components/settings/providers/Gemini.tsx @@ -198,7 +198,7 @@ export const Gemini = ({
setApiConfigurationField("maxOutputTokens", values[0])} @@ -208,7 +208,10 @@ export const Gemini = ({ value={(apiConfiguration.maxOutputTokens ?? 0).toString()} type="text" inputMode="numeric" - onInput={handleInputChange("maxOutputTokens", (e) => parseInt((e as any).target.value, 10))} + onInput={handleInputChange("maxOutputTokens", (e) => { + const val = parseInt((e as any).target.value, 10) + return Number.isNaN(val) ? 0 : Math.min(val, modelInfo.maxTokens) + })} className="w-16" />
diff --git a/webview-ui/src/components/settings/providers/__tests__/Gemini.spec.tsx b/webview-ui/src/components/settings/providers/__tests__/Gemini.spec.tsx new file mode 100644 index 0000000000..7b69538ff9 --- /dev/null +++ b/webview-ui/src/components/settings/providers/__tests__/Gemini.spec.tsx @@ -0,0 +1,85 @@ +import React from "react" +import { render, screen, fireEvent } from "@testing-library/react" +import { Gemini } from "../Gemini" +import type { ProviderSettings } from "@roo-code/types" +import { geminiModels, geminiDefaultModelId, type GeminiModelId } from "@roo-code/types" + +vi.mock("@vscode/webview-ui-toolkit/react", () => ({ + VSCodeTextField: ({ children, value, onInput, type }: any) => ( +
+ {children} + onInput(e)} /> +
+ ), +})) + +vi.mock("vscrui", () => ({ + Checkbox: ({ children, checked, onChange }: any) => ( + + ), +})) + +vi.mock("@src/components/ui", () => ({ + Slider: ({ min, max, step, value, onValueChange }: any) => ( + onValueChange([Number(e.target.value)])} + /> + ), +})) + +vi.mock("@src/i18n/TranslationContext", () => ({ + useAppTranslation: () => ({ t: (key: string) => key }), +})) + +vi.mock("@src/components/common/VSCodeButtonLink", () => ({ + VSCodeButtonLink: ({ children, href }: any) => {children}, +})) + +const defaultModelId: GeminiModelId = geminiDefaultModelId +const defaultContextWindow = geminiModels[defaultModelId].contextWindow + +describe("Gemini provider settings", () => { + it("does not render context limit slider when custom context limit is not enabled", () => { + const setApiField = vi.fn() + const config: ProviderSettings = {} + render( + , + ) + expect(screen.queryByTestId("slider")).toBeNull() + }) + + it("enables custom context limit on checkbox toggle and shows slider with default value", () => { + const setApiField = vi.fn() + const config: ProviderSettings = {} + render( + , + ) + const checkbox = screen.getByTestId("checkbox-custom-context-limit") + fireEvent.click(checkbox) + expect(setApiField).toHaveBeenCalledWith("contextLimit", defaultContextWindow) + const slider = screen.getByTestId("slider") + expect(slider).toHaveValue(defaultContextWindow.toString()) + }) + + it("renders slider when contextLimit already set and updates on slider change", () => { + const setApiField = vi.fn() + const initialLimit = 100000 + const config: ProviderSettings = { contextLimit: initialLimit } + render( + , + ) + const slider = screen.getByTestId("slider") + expect(slider).toHaveValue(initialLimit.toString()) + fireEvent.change(slider, { target: { value: "50000" } }) + expect(setApiField).toHaveBeenCalledWith("contextLimit", 50000) + }) +})