diff --git a/.changeset/fix-gemini-3-thinking-level.md b/.changeset/fix-gemini-3-thinking-level.md new file mode 100644 index 00000000000..bb51845df3e --- /dev/null +++ b/.changeset/fix-gemini-3-thinking-level.md @@ -0,0 +1,12 @@ +--- +"roo-cline": patch +--- + +fix(gemini): upgrade @google/genai to support thinkingLevel for Gemini 3 models + +The previous SDK version (1.29.1) did not include the `thinkingLevel` property in +`ThinkingConfig`, causing INVALID_ARGUMENT errors when using Gemini 3 Pro and Flash +models which require effort-based reasoning configuration via `thinkingLevel`. + +Updated to @google/genai ^1.30.0 which adds support for `thinkingLevel` with values: +MINIMAL, LOW, MEDIUM, HIGH. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5589df1b424..220bf5266a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -668,8 +668,8 @@ importers: specifier: ^3.922.0 version: 3.922.0 '@google/genai': - specifier: ^1.29.1 - version: 1.29.1(@modelcontextprotocol/sdk@1.12.0) + specifier: ^1.30.0 + version: 1.34.0(@modelcontextprotocol/sdk@1.12.0) '@lmstudio/sdk': specifier: ^1.1.1 version: 1.2.0 @@ -1973,11 +1973,11 @@ packages: '@floating-ui/utils@0.2.9': resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==} - '@google/genai@1.29.1': - resolution: {integrity: sha512-Buywpq0A6xf9cOdhiWCi5KUiDBbZkjCH5xbl+xxNQRItoYQgd31p0OKyn5cUnT0YNzC/pAmszqXoOc7kncqfFQ==} + '@google/genai@1.34.0': + resolution: {integrity: sha512-vu53UMPvjmb7PGzlYu6Tzxso8Dfhn+a7eQFaS2uNemVtDZKwzSpJ5+ikqBbXplF7RGB1STcVDqCkPvquiwb2sw==} engines: {node: '>=20.0.0'} peerDependencies: - '@modelcontextprotocol/sdk': ^1.20.1 + '@modelcontextprotocol/sdk': ^1.24.0 peerDependenciesMeta: '@modelcontextprotocol/sdk': optional: true @@ -11594,7 +11594,7 @@ snapshots: '@floating-ui/utils@0.2.9': {} - '@google/genai@1.29.1(@modelcontextprotocol/sdk@1.12.0)': + '@google/genai@1.34.0(@modelcontextprotocol/sdk@1.12.0)': dependencies: google-auth-library: 10.5.0 ws: 8.18.3 @@ -11854,7 +11854,7 @@ snapshots: '@lmstudio/lms-isomorphic@0.4.5': dependencies: - ws: 8.18.2 + ws: 8.18.3 transitivePeerDependencies: - bufferutil - utf-8-validate @@ -18954,7 +18954,7 @@ snapshots: debug: 4.4.1(supports-color@8.1.1) devtools-protocol: 0.0.1452169 typed-query-selector: 2.12.0 - ws: 8.18.2 + ws: 8.18.3 transitivePeerDependencies: - bare-buffer - bufferutil diff --git a/src/api/transform/__tests__/reasoning.spec.ts b/src/api/transform/__tests__/reasoning.spec.ts index 352aac8e7bb..ef4e380edfc 100644 --- a/src/api/transform/__tests__/reasoning.spec.ts +++ b/src/api/transform/__tests__/reasoning.spec.ts @@ -1,6 +1,7 @@ // npx vitest run src/api/transform/__tests__/reasoning.spec.ts import type { ModelInfo, ProviderSettings, ReasoningEffortWithMinimal } from "@roo-code/types" +import { ThinkingLevel } from "@google/genai" import { getOpenRouterReasoning, @@ -615,7 +616,7 @@ describe("reasoning.ts", () => { const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined // Budget should not be used for effort-only models - expect(result).toEqual({ thinkingLevel: "high", includeThoughts: true }) + expect(result).toEqual({ thinkingLevel: ThinkingLevel.HIGH, includeThoughts: true }) }) it("should still return thinkingLevel when enableReasoningEffort is false but effort is explicitly set", () => { @@ -641,7 +642,7 @@ describe("reasoning.ts", () => { } const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined - expect(result).toEqual({ thinkingLevel: "high", includeThoughts: true }) + expect(result).toEqual({ thinkingLevel: ThinkingLevel.HIGH, includeThoughts: true }) }) it("should return thinkingLevel for minimal effort", () => { @@ -664,7 +665,7 @@ describe("reasoning.ts", () => { } const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined - expect(result).toEqual({ thinkingLevel: "minimal", includeThoughts: true }) + expect(result).toEqual({ thinkingLevel: ThinkingLevel.MINIMAL, includeThoughts: true }) }) it("should return thinkingLevel for medium effort", () => { @@ -687,11 +688,17 @@ describe("reasoning.ts", () => { } const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined - expect(result).toEqual({ thinkingLevel: "medium", includeThoughts: true }) + expect(result).toEqual({ thinkingLevel: ThinkingLevel.MEDIUM, includeThoughts: true }) }) it("should handle all four Gemini thinking levels", () => { const levels: GeminiThinkingLevel[] = ["minimal", "low", "medium", "high"] + const expectedThinkingLevels: Record = { + minimal: ThinkingLevel.MINIMAL, + low: ThinkingLevel.LOW, + medium: ThinkingLevel.MEDIUM, + high: ThinkingLevel.HIGH, + } levels.forEach((level) => { const geminiModel: ModelInfo = { @@ -718,7 +725,7 @@ describe("reasoning.ts", () => { } const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined - expect(result).toEqual({ thinkingLevel: level, includeThoughts: true }) + expect(result).toEqual({ thinkingLevel: expectedThinkingLevels[level], includeThoughts: true }) }) }) @@ -836,7 +843,7 @@ describe("reasoning.ts", () => { } const result = getGeminiReasoning(options) as GeminiReasoningParams | undefined - expect(result).toEqual({ thinkingLevel: "medium", includeThoughts: true }) + expect(result).toEqual({ thinkingLevel: ThinkingLevel.MEDIUM, includeThoughts: true }) }) }) diff --git a/src/api/transform/reasoning.ts b/src/api/transform/reasoning.ts index e726ce32234..079234da398 100644 --- a/src/api/transform/reasoning.ts +++ b/src/api/transform/reasoning.ts @@ -1,6 +1,6 @@ import { BetaThinkingConfigParam } from "@anthropic-ai/sdk/resources/beta" import OpenAI from "openai" -import type { GenerateContentConfig } from "@google/genai" +import { ThinkingLevel, type GenerateContentConfig } from "@google/genai" import type { ModelInfo, ProviderSettings, ReasoningEffortExtended } from "@roo-code/types" @@ -21,7 +21,7 @@ export type AnthropicReasoningParams = BetaThinkingConfigParam export type OpenAiReasoningParams = { reasoning_effort: OpenAI.Chat.ChatCompletionCreateParams["reasoning_effort"] } -// Valid Gemini thinking levels for effort-based reasoning +// Valid Gemini thinking levels for effort-based reasoning (lowercase for internal use) const GEMINI_THINKING_LEVELS = ["minimal", "low", "medium", "high"] as const export type GeminiThinkingLevel = (typeof GEMINI_THINKING_LEVELS)[number] @@ -30,10 +30,16 @@ export function isGeminiThinkingLevel(value: unknown): value is GeminiThinkingLe return typeof value === "string" && GEMINI_THINKING_LEVELS.includes(value as GeminiThinkingLevel) } -export type GeminiReasoningParams = GenerateContentConfig["thinkingConfig"] & { - thinkingLevel?: GeminiThinkingLevel +// Map lowercase thinking levels to SDK's ThinkingLevel enum +const THINKING_LEVEL_MAP: Record = { + minimal: ThinkingLevel.MINIMAL, + low: ThinkingLevel.LOW, + medium: ThinkingLevel.MEDIUM, + high: ThinkingLevel.HIGH, } +export type GeminiReasoningParams = GenerateContentConfig["thinkingConfig"] + export type GetModelReasoningOptions = { model: ModelInfo reasoningBudget: number | undefined @@ -155,5 +161,6 @@ export const getGeminiReasoning = ({ return undefined } - return { thinkingLevel: selectedEffort, includeThoughts: true } + // Map lowercase effort to SDK's ThinkingLevel enum (uppercase values) + return { thinkingLevel: THINKING_LEVEL_MAP[selectedEffort], includeThoughts: true } } diff --git a/src/package.json b/src/package.json index df1a6954064..155240dd6df 100644 --- a/src/package.json +++ b/src/package.json @@ -435,7 +435,7 @@ "@anthropic-ai/vertex-sdk": "^0.7.0", "@aws-sdk/client-bedrock-runtime": "^3.922.0", "@aws-sdk/credential-providers": "^3.922.0", - "@google/genai": "^1.29.1", + "@google/genai": "^1.30.0", "@lmstudio/sdk": "^1.1.1", "@mistralai/mistralai": "^1.9.18", "@modelcontextprotocol/sdk": "1.12.0",