From f04433dff1a84f6d22661657b15ed20b91e04c79 Mon Sep 17 00:00:00 2001 From: Hannes Rudolph Date: Tue, 9 Dec 2025 22:15:30 -0700 Subject: [PATCH 1/4] feat(gemini): add minimal and medium reasoning effort levels - Add GeminiThinkingLevel type with minimal, low, medium, high values - Update getGeminiReasoning() to accept all four thinking levels - Add comprehensive tests for new reasoning levels --- src/api/transform/__tests__/reasoning.spec.ts | 196 ++++++++++++++++++ src/api/transform/reasoning.ts | 15 +- 2 files changed, 206 insertions(+), 5 deletions(-) diff --git a/src/api/transform/__tests__/reasoning.spec.ts b/src/api/transform/__tests__/reasoning.spec.ts index c1e6c6d5ce4..352aac8e7bb 100644 --- a/src/api/transform/__tests__/reasoning.spec.ts +++ b/src/api/transform/__tests__/reasoning.spec.ts @@ -14,6 +14,7 @@ import { OpenAiReasoningParams, RooReasoningParams, GeminiReasoningParams, + GeminiThinkingLevel, } from "../reasoning" describe("reasoning.ts", () => { @@ -642,6 +643,201 @@ describe("reasoning.ts", () => { const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined expect(result).toEqual({ thinkingLevel: "high", includeThoughts: true }) }) + + it("should return thinkingLevel for minimal effort", () => { + const geminiModel: ModelInfo = { + ...baseModel, + supportsReasoningEffort: ["minimal", "low", "medium", "high"] as ModelInfo["supportsReasoningEffort"], + reasoningEffort: "high", + } + + const settings: ProviderSettings = { + apiProvider: "gemini", + reasoningEffort: "minimal", + } + + const options: GetModelReasoningOptions = { + model: geminiModel, + reasoningBudget: undefined, + reasoningEffort: "minimal", + settings, + } + + const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined + expect(result).toEqual({ thinkingLevel: "minimal", includeThoughts: true }) + }) + + it("should return thinkingLevel for medium effort", () => { + const geminiModel: ModelInfo = { + ...baseModel, + supportsReasoningEffort: ["minimal", "low", "medium", "high"] as ModelInfo["supportsReasoningEffort"], + reasoningEffort: "low", + } + + const settings: ProviderSettings = { + apiProvider: "gemini", + reasoningEffort: "medium", + } + + const options: GetModelReasoningOptions = { + model: geminiModel, + reasoningBudget: undefined, + reasoningEffort: "medium", + settings, + } + + const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined + expect(result).toEqual({ thinkingLevel: "medium", includeThoughts: true }) + }) + + it("should handle all four Gemini thinking levels", () => { + const levels: GeminiThinkingLevel[] = ["minimal", "low", "medium", "high"] + + levels.forEach((level) => { + const geminiModel: ModelInfo = { + ...baseModel, + supportsReasoningEffort: [ + "minimal", + "low", + "medium", + "high", + ] as ModelInfo["supportsReasoningEffort"], + reasoningEffort: "low", + } + + const settings: ProviderSettings = { + apiProvider: "gemini", + reasoningEffort: level, + } + + const options: GetModelReasoningOptions = { + model: geminiModel, + reasoningBudget: undefined, + reasoningEffort: level, + settings, + } + + const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined + expect(result).toEqual({ thinkingLevel: level, includeThoughts: true }) + }) + }) + + it("should return undefined for disable effort", () => { + const geminiModel: ModelInfo = { + ...baseModel, + supportsReasoningEffort: ["minimal", "low", "medium", "high"] as ModelInfo["supportsReasoningEffort"], + reasoningEffort: "low", + } + + const settings: ProviderSettings = { + apiProvider: "gemini", + reasoningEffort: "disable", + } + + const options: GetModelReasoningOptions = { + model: geminiModel, + reasoningBudget: undefined, + reasoningEffort: "disable", + settings, + } + + const result = getGeminiReasoning(options) + expect(result).toBeUndefined() + }) + + it("should return undefined for none effort (invalid for Gemini)", () => { + const geminiModel: ModelInfo = { + ...baseModel, + supportsReasoningEffort: ["minimal", "low", "medium", "high"] as ModelInfo["supportsReasoningEffort"], + reasoningEffort: "low", + } + + const settings: ProviderSettings = { + apiProvider: "gemini", + reasoningEffort: "none", + } + + const options: GetModelReasoningOptions = { + model: geminiModel, + reasoningBudget: undefined, + reasoningEffort: "none", + settings, + } + + const result = getGeminiReasoning(options) + expect(result).toBeUndefined() + }) + + it("should use thinkingBudget for budget-based models", () => { + const geminiModel: ModelInfo = { + ...baseModel, + supportsReasoningBudget: true, + requiredReasoningBudget: true, + } + + const settings: ProviderSettings = { + apiProvider: "gemini", + enableReasoningEffort: true, + } + + const options: GetModelReasoningOptions = { + model: geminiModel, + reasoningBudget: 4096, + reasoningEffort: "high", + settings, + } + + const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined + expect(result).toEqual({ thinkingBudget: 4096, includeThoughts: true }) + }) + + it("should prioritize budget over effort when model has requiredReasoningBudget", () => { + const geminiModel: ModelInfo = { + ...baseModel, + supportsReasoningBudget: true, + requiredReasoningBudget: true, + supportsReasoningEffort: ["minimal", "low", "medium", "high"] as ModelInfo["supportsReasoningEffort"], + } + + const settings: ProviderSettings = { + apiProvider: "gemini", + enableReasoningEffort: true, + reasoningEffort: "high", + } + + const options: GetModelReasoningOptions = { + model: geminiModel, + reasoningBudget: 8192, + reasoningEffort: "high", + settings, + } + + const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined + // Budget should take precedence + expect(result).toEqual({ thinkingBudget: 8192, includeThoughts: true }) + }) + + it("should fall back to model default effort when settings.reasoningEffort is undefined", () => { + const geminiModel: ModelInfo = { + ...baseModel, + supportsReasoningEffort: ["minimal", "low", "medium", "high"] as ModelInfo["supportsReasoningEffort"], + reasoningEffort: "medium", + } + + const settings: ProviderSettings = { + apiProvider: "gemini", + } + + const options: GetModelReasoningOptions = { + model: geminiModel, + reasoningBudget: undefined, + reasoningEffort: undefined, + settings, + } + + const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined + expect(result).toEqual({ thinkingLevel: "medium", includeThoughts: true }) + }) }) describe("Integration scenarios", () => { diff --git a/src/api/transform/reasoning.ts b/src/api/transform/reasoning.ts index f4940625696..027ade46d3a 100644 --- a/src/api/transform/reasoning.ts +++ b/src/api/transform/reasoning.ts @@ -21,8 +21,10 @@ export type AnthropicReasoningParams = BetaThinkingConfigParam export type OpenAiReasoningParams = { reasoning_effort: OpenAI.Chat.ChatCompletionCreateParams["reasoning_effort"] } +export type GeminiThinkingLevel = "minimal" | "low" | "medium" | "high" + export type GeminiReasoningParams = GenerateContentConfig["thinkingConfig"] & { - thinkingLevel?: "low" | "high" + thinkingLevel?: GeminiThinkingLevel } export type GetModelReasoningOptions = { @@ -116,6 +118,9 @@ export const getOpenAiReasoning = ({ } } +// Valid Gemini thinking levels for effort-based reasoning +const GEMINI_THINKING_LEVELS: readonly GeminiThinkingLevel[] = ["minimal", "low", "medium", "high"] as const + export const getGeminiReasoning = ({ model, reasoningBudget, @@ -136,15 +141,15 @@ export const getGeminiReasoning = ({ | "disable" | undefined - // Respect “off” / unset semantics from the effort selector itself. + // Respect "off" / unset semantics from the effort selector itself. if (!selectedEffort || selectedEffort === "disable") { return undefined } - // Effort-based models on Google GenAI currently support only explicit low/high levels. - if (selectedEffort !== "low" && selectedEffort !== "high") { + // Effort-based models on Google GenAI support minimal/low/medium/high levels. + if (!GEMINI_THINKING_LEVELS.includes(selectedEffort as GeminiThinkingLevel)) { return undefined } - return { thinkingLevel: selectedEffort, includeThoughts: true } + return { thinkingLevel: selectedEffort as GeminiThinkingLevel, includeThoughts: true } } From 5772e893c6025e7e93524395907f2f737158617b Mon Sep 17 00:00:00 2001 From: Roo Code Date: Wed, 10 Dec 2025 05:23:45 +0000 Subject: [PATCH 2/4] refactor: derive GeminiThinkingLevel type from const array --- src/api/transform/reasoning.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/api/transform/reasoning.ts b/src/api/transform/reasoning.ts index 027ade46d3a..63f43fa59eb 100644 --- a/src/api/transform/reasoning.ts +++ b/src/api/transform/reasoning.ts @@ -21,7 +21,10 @@ export type AnthropicReasoningParams = BetaThinkingConfigParam export type OpenAiReasoningParams = { reasoning_effort: OpenAI.Chat.ChatCompletionCreateParams["reasoning_effort"] } -export type GeminiThinkingLevel = "minimal" | "low" | "medium" | "high" +// Valid Gemini thinking levels for effort-based reasoning +const GEMINI_THINKING_LEVELS = ["minimal", "low", "medium", "high"] as const + +export type GeminiThinkingLevel = (typeof GEMINI_THINKING_LEVELS)[number] export type GeminiReasoningParams = GenerateContentConfig["thinkingConfig"] & { thinkingLevel?: GeminiThinkingLevel @@ -118,9 +121,6 @@ export const getOpenAiReasoning = ({ } } -// Valid Gemini thinking levels for effort-based reasoning -const GEMINI_THINKING_LEVELS: readonly GeminiThinkingLevel[] = ["minimal", "low", "medium", "high"] as const - export const getGeminiReasoning = ({ model, reasoningBudget, From 9838370e42f5e5263f3fc635ae34e798bf760507 Mon Sep 17 00:00:00 2001 From: cte Date: Tue, 9 Dec 2025 21:32:38 -0800 Subject: [PATCH 3/4] Add changeset --- .changeset/silly-cycles-enjoy.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/silly-cycles-enjoy.md diff --git a/.changeset/silly-cycles-enjoy.md b/.changeset/silly-cycles-enjoy.md new file mode 100644 index 00000000000..571b5253f6a --- /dev/null +++ b/.changeset/silly-cycles-enjoy.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Adds support for minimal and medium reasoning effort levels in the Gemini provider implementation From 173814d595705a65e08802dfe3704db888ea9428 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Wed, 10 Dec 2025 05:39:59 +0000 Subject: [PATCH 4/4] refactor(gemini): add isGeminiThinkingLevel type guard function --- src/api/transform/reasoning.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/api/transform/reasoning.ts b/src/api/transform/reasoning.ts index 63f43fa59eb..e726ce32234 100644 --- a/src/api/transform/reasoning.ts +++ b/src/api/transform/reasoning.ts @@ -26,6 +26,10 @@ const GEMINI_THINKING_LEVELS = ["minimal", "low", "medium", "high"] as const export type GeminiThinkingLevel = (typeof GEMINI_THINKING_LEVELS)[number] +export function isGeminiThinkingLevel(value: unknown): value is GeminiThinkingLevel { + return typeof value === "string" && GEMINI_THINKING_LEVELS.includes(value as GeminiThinkingLevel) +} + export type GeminiReasoningParams = GenerateContentConfig["thinkingConfig"] & { thinkingLevel?: GeminiThinkingLevel } @@ -147,9 +151,9 @@ export const getGeminiReasoning = ({ } // Effort-based models on Google GenAI support minimal/low/medium/high levels. - if (!GEMINI_THINKING_LEVELS.includes(selectedEffort as GeminiThinkingLevel)) { + if (!isGeminiThinkingLevel(selectedEffort)) { return undefined } - return { thinkingLevel: selectedEffort as GeminiThinkingLevel, includeThoughts: true } + return { thinkingLevel: selectedEffort, includeThoughts: true } }