feat(gemini): add minimal and medium reasoning effort levels (#9973)

Co-authored-by: Roo Code <roomote@roocode.com>
Co-authored-by: cte <cestreich@gmail.com>
This commit is contained in:
Hannes Rudolph 2025-12-09 22:50:14 -07:00 committed by GitHub
parent 0cf5b28569
commit 048e7f3502
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 214 additions and 4 deletions

View file

@ -0,0 +1,5 @@
---
"roo-cline": patch
---
Adds support for minimal and medium reasoning effort levels in the Gemini provider implementation

View file

@ -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", () => {

View file

@ -21,8 +21,17 @@ export type AnthropicReasoningParams = BetaThinkingConfigParam
export type OpenAiReasoningParams = { reasoning_effort: OpenAI.Chat.ChatCompletionCreateParams["reasoning_effort"] }
// 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 function isGeminiThinkingLevel(value: unknown): value is GeminiThinkingLevel {
return typeof value === "string" && GEMINI_THINKING_LEVELS.includes(value as GeminiThinkingLevel)
}
export type GeminiReasoningParams = GenerateContentConfig["thinkingConfig"] & {
thinkingLevel?: "low" | "high"
thinkingLevel?: GeminiThinkingLevel
}
export type GetModelReasoningOptions = {
@ -136,13 +145,13 @@ 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 (!isGeminiThinkingLevel(selectedEffort)) {
return undefined
}