Skip to content

Commit 3b45d86

Browse files
daniel-lxsshariqriazzcte
authored
Add gemini pro 06 05 (RooCodeInc#4386)
* feat: add gemini-2.5-pro-preview-06-05 to Gemini and Vertex providers and UI, identical to 05-06 * feat: add thinking variant for gemini-2.5-pro-preview-06-05 * feat: add gemini-2.5-pro-preview-06-05 support to OpenRouter * fix: update gemini-2.5-pro-preview model references in OpenRouter * fix: tests * feat: enhance reasoning handling in Gemini and Vertex handlers * feat: add google/gemini-2.5-pro-preview to required reasoning budget models * fix: refactor thinkingConfig assignment for consistency in Gemini and Vertex handlers * Fix Gemini reasoning * Fix tsc error * Fix tsc error * Hack to exclude thinking tokens by default * feat: add global region to VERTEX_REGIONS --------- Co-authored-by: Shariq Riaz <[email protected]> Co-authored-by: cte <[email protected]>
1 parent cb5b9c3 commit 3b45d86

File tree

10 files changed

+148
-71
lines changed

10 files changed

+148
-71
lines changed

packages/types/src/providers/gemini.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,32 @@ export const geminiModels = {
104104
},
105105
],
106106
},
107+
"gemini-2.5-pro-preview-06-05": {
108+
maxTokens: 65_535,
109+
contextWindow: 1_048_576,
110+
supportsImages: true,
111+
supportsPromptCache: true,
112+
inputPrice: 2.5, // This is the pricing for prompts above 200k tokens.
113+
outputPrice: 15,
114+
cacheReadsPrice: 0.625,
115+
cacheWritesPrice: 4.5,
116+
maxThinkingTokens: 32_768,
117+
supportsReasoningBudget: true,
118+
tiers: [
119+
{
120+
contextWindow: 200_000,
121+
inputPrice: 1.25,
122+
outputPrice: 10,
123+
cacheReadsPrice: 0.31,
124+
},
125+
{
126+
contextWindow: Infinity,
127+
inputPrice: 2.5,
128+
outputPrice: 15,
129+
cacheReadsPrice: 0.625,
130+
},
131+
],
132+
},
107133
"gemini-2.0-flash-001": {
108134
maxTokens: 8192,
109135
contextWindow: 1_048_576,

packages/types/src/providers/openrouter.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,26 @@ export const OPEN_ROUTER_COMPUTER_USE_MODELS = new Set([
6060
"anthropic/claude-opus-4",
6161
])
6262

63+
// When we first launched these models we didn't have support for
64+
// enabling/disabling the reasoning budget for hybrid models. Now that we
65+
// do support this we should give users the option to enable/disable it
66+
// whenever possible. However these particular (virtual) model ids with the
67+
// `:thinking` suffix always require the reasoning budget to be enabled, so
68+
// for backwards compatibility we should still require it.
69+
// We should *not* be adding new models to this set.
70+
export const OPEN_ROUTER_REQUIRED_REASONING_BUDGET_MODELS = new Set([
71+
"anthropic/claude-3.7-sonnet:thinking",
72+
"google/gemini-2.5-flash-preview-05-20:thinking",
73+
])
74+
6375
export const OPEN_ROUTER_REASONING_BUDGET_MODELS = new Set([
6476
"anthropic/claude-3.7-sonnet:beta",
65-
"anthropic/claude-3.7-sonnet:thinking",
6677
"anthropic/claude-opus-4",
6778
"anthropic/claude-sonnet-4",
79+
"google/gemini-2.5-pro-preview",
6880
"google/gemini-2.5-flash-preview-05-20",
69-
"google/gemini-2.5-flash-preview-05-20:thinking",
70-
])
71-
72-
export const OPEN_ROUTER_REQUIRED_REASONING_BUDGET_MODELS = new Set([
81+
// Also include the models that require the reasoning budget to be enabled
82+
// even though `OPEN_ROUTER_REQUIRED_REASONING_BUDGET_MODELS` takes precedence.
7383
"anthropic/claude-3.7-sonnet:thinking",
7484
"google/gemini-2.5-flash-preview-05-20:thinking",
7585
])

packages/types/src/providers/vertex.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@ export const vertexModels = {
6060
inputPrice: 2.5,
6161
outputPrice: 15,
6262
},
63+
"gemini-2.5-pro-preview-06-05": {
64+
maxTokens: 65_535,
65+
contextWindow: 1_048_576,
66+
supportsImages: true,
67+
supportsPromptCache: true,
68+
inputPrice: 2.5,
69+
outputPrice: 15,
70+
maxThinkingTokens: 32_768,
71+
supportsReasoningBudget: true,
72+
},
6373
"gemini-2.5-pro-exp-03-25": {
6474
maxTokens: 65_535,
6575
contextWindow: 1_048_576,
@@ -217,6 +227,7 @@ export const vertexModels = {
217227
} as const satisfies Record<string, ModelInfo>
218228

219229
export const VERTEX_REGIONS = [
230+
{ value: "global", label: "global" },
220231
{ value: "us-east5", label: "us-east5" },
221232
{ value: "us-central1", label: "us-central1" },
222233
{ value: "europe-west1", label: "europe-west1" },

src/api/providers/fetchers/__tests__/openrouter.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,10 +185,11 @@ describe("OpenRouter API", () => {
185185

186186
expect(endpoints).toEqual({
187187
Google: {
188-
maxTokens: 0,
188+
maxTokens: 65535,
189189
contextWindow: 1048576,
190190
supportsImages: true,
191191
supportsPromptCache: true,
192+
supportsReasoningBudget: true,
192193
inputPrice: 1.25,
193194
outputPrice: 10,
194195
cacheWritesPrice: 1.625,
@@ -198,10 +199,11 @@ describe("OpenRouter API", () => {
198199
supportedParameters: undefined,
199200
},
200201
"Google AI Studio": {
201-
maxTokens: 0,
202+
maxTokens: 65536,
202203
contextWindow: 1048576,
203204
supportsImages: true,
204205
supportsPromptCache: true,
206+
supportsReasoningBudget: true,
205207
inputPrice: 1.25,
206208
outputPrice: 10,
207209
cacheWritesPrice: 1.625,

src/api/providers/gemini.ts

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { safeJsonParse } from "../../shared/safeJsonParse"
1414

1515
import { convertAnthropicContentToGemini, convertAnthropicMessageToGemini } from "../transform/gemini-format"
1616
import type { ApiStream } from "../transform/stream"
17+
import { getModelParams } from "../transform/model-params"
1718

1819
import type { SingleCompletionHandler, ApiHandlerCreateMessageMetadata } from "../index"
1920
import { BaseProvider } from "./base-provider"
@@ -62,15 +63,15 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl
6263
messages: Anthropic.Messages.MessageParam[],
6364
metadata?: ApiHandlerCreateMessageMetadata,
6465
): ApiStream {
65-
const { id: model, thinkingConfig, maxOutputTokens, info } = this.getModel()
66+
const { id: model, info, reasoning: thinkingConfig, maxTokens } = this.getModel()
6667

6768
const contents = messages.map(convertAnthropicMessageToGemini)
6869

6970
const config: GenerateContentConfig = {
7071
systemInstruction,
7172
httpOptions: this.options.googleGeminiBaseUrl ? { baseUrl: this.options.googleGeminiBaseUrl } : undefined,
7273
thinkingConfig,
73-
maxOutputTokens,
74+
maxOutputTokens: this.options.modelMaxTokens ?? maxTokens ?? undefined,
7475
temperature: this.options.modelTemperature ?? 0,
7576
}
7677

@@ -81,7 +82,28 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl
8182
let lastUsageMetadata: GenerateContentResponseUsageMetadata | undefined
8283

8384
for await (const chunk of result) {
84-
if (chunk.text) {
85+
// Process candidates and their parts to separate thoughts from content
86+
if (chunk.candidates && chunk.candidates.length > 0) {
87+
const candidate = chunk.candidates[0]
88+
if (candidate.content && candidate.content.parts) {
89+
for (const part of candidate.content.parts) {
90+
if (part.thought) {
91+
// This is a thinking/reasoning part
92+
if (part.text) {
93+
yield { type: "reasoning", text: part.text }
94+
}
95+
} else {
96+
// This is regular content
97+
if (part.text) {
98+
yield { type: "text", text: part.text }
99+
}
100+
}
101+
}
102+
}
103+
}
104+
105+
// Fallback to the original text property if no candidates structure
106+
else if (chunk.text) {
85107
yield { type: "text", text: chunk.text }
86108
}
87109

@@ -108,32 +130,16 @@ export class GeminiHandler extends BaseProvider implements SingleCompletionHandl
108130
}
109131

110132
override getModel() {
111-
let id = this.options.apiModelId ?? geminiDefaultModelId
112-
let info: ModelInfo = geminiModels[id as GeminiModelId]
113-
114-
if (id?.endsWith(":thinking")) {
115-
id = id.slice(0, -":thinking".length)
116-
117-
if (geminiModels[id as GeminiModelId]) {
118-
info = geminiModels[id as GeminiModelId]
119-
120-
return {
121-
id,
122-
info,
123-
thinkingConfig: this.options.modelMaxThinkingTokens
124-
? { thinkingBudget: this.options.modelMaxThinkingTokens }
125-
: undefined,
126-
maxOutputTokens: this.options.modelMaxTokens ?? info.maxTokens ?? undefined,
127-
}
128-
}
129-
}
130-
131-
if (!info) {
132-
id = geminiDefaultModelId
133-
info = geminiModels[geminiDefaultModelId]
134-
}
135-
136-
return { id, info }
133+
const modelId = this.options.apiModelId
134+
let id = modelId && modelId in geminiModels ? (modelId as GeminiModelId) : geminiDefaultModelId
135+
const info: ModelInfo = geminiModels[id]
136+
const params = getModelParams({ format: "gemini", modelId: id, model: info, settings: this.options })
137+
138+
// The `:thinking` suffix indicates that the model is a "Hybrid"
139+
// reasoning model and that reasoning is required to be enabled.
140+
// The actual model ID honored by Gemini's API does not have this
141+
// suffix.
142+
return { id: id.endsWith(":thinking") ? id.replace(":thinking", "") : id, info, ...params }
137143
}
138144

139145
async completePrompt(prompt: string): Promise<string> {

src/api/providers/openrouter.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
7474

7575
let { id: modelId, maxTokens, temperature, topP, reasoning } = model
7676

77+
// OpenRouter sends reasoning tokens by default for Gemini 2.5 Pro
78+
// Preview even if you don't request them. This is not the default for
79+
// other providers (including Gemini), so we need to explicitly disable
80+
// i We should generalize this using the logic in `getModelParams`, but
81+
// this is easier for now.
82+
if (modelId === "google/gemini-2.5-pro-preview" && typeof reasoning === "undefined") {
83+
reasoning = { exclude: true }
84+
}
85+
7786
// Convert Anthropic messages to OpenAI format.
7887
let openAiMessages: OpenAI.Chat.ChatCompletionMessageParam[] = [
7988
{ role: "system", content: systemPrompt },

src/api/providers/vertex.ts

Lines changed: 12 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { type ModelInfo, type VertexModelId, vertexDefaultModelId, vertexModels
22

33
import type { ApiHandlerOptions } from "../../shared/api"
44

5+
import { getModelParams } from "../transform/model-params"
6+
57
import { GeminiHandler } from "./gemini"
68
import { SingleCompletionHandler } from "../index"
79

@@ -11,31 +13,15 @@ export class VertexHandler extends GeminiHandler implements SingleCompletionHand
1113
}
1214

1315
override getModel() {
14-
let id = this.options.apiModelId ?? vertexDefaultModelId
15-
let info: ModelInfo = vertexModels[id as VertexModelId]
16-
17-
if (id?.endsWith(":thinking")) {
18-
id = id.slice(0, -":thinking".length) as VertexModelId
19-
20-
if (vertexModels[id as VertexModelId]) {
21-
info = vertexModels[id as VertexModelId]
22-
23-
return {
24-
id,
25-
info,
26-
thinkingConfig: this.options.modelMaxThinkingTokens
27-
? { thinkingBudget: this.options.modelMaxThinkingTokens }
28-
: undefined,
29-
maxOutputTokens: this.options.modelMaxTokens ?? info.maxTokens ?? undefined,
30-
}
31-
}
32-
}
33-
34-
if (!info) {
35-
id = vertexDefaultModelId
36-
info = vertexModels[vertexDefaultModelId]
37-
}
38-
39-
return { id, info }
16+
const modelId = this.options.apiModelId
17+
let id = modelId && modelId in vertexModels ? (modelId as VertexModelId) : vertexDefaultModelId
18+
const info: ModelInfo = vertexModels[id]
19+
const params = getModelParams({ format: "gemini", modelId: id, model: info, settings: this.options })
20+
21+
// The `:thinking` suffix indicates that the model is a "Hybrid"
22+
// reasoning model and that reasoning is required to be enabled.
23+
// The actual model ID honored by Gemini's API does not have this
24+
// suffix.
25+
return { id: id.endsWith(":thinking") ? id.replace(":thinking", "") : id, info, ...params }
4026
}
4127
}

src/api/transform/model-params.ts

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@ import { shouldUseReasoningBudget, shouldUseReasoningEffort } from "../../shared
55
import {
66
type AnthropicReasoningParams,
77
type OpenAiReasoningParams,
8+
type GeminiReasoningParams,
89
type OpenRouterReasoningParams,
910
getAnthropicReasoning,
1011
getOpenAiReasoning,
12+
getGeminiReasoning,
1113
getOpenRouterReasoning,
1214
} from "./reasoning"
1315

14-
type GetModelParamsOptions<T extends "openai" | "anthropic" | "openrouter"> = {
16+
type Format = "anthropic" | "openai" | "gemini" | "openrouter"
17+
18+
type GetModelParamsOptions<T extends Format> = {
1519
format: T
1620
modelId: string
1721
model: ModelInfo
@@ -26,34 +30,40 @@ type BaseModelParams = {
2630
reasoningBudget: number | undefined
2731
}
2832

33+
type AnthropicModelParams = {
34+
format: "anthropic"
35+
reasoning: AnthropicReasoningParams | undefined
36+
} & BaseModelParams
37+
2938
type OpenAiModelParams = {
3039
format: "openai"
3140
reasoning: OpenAiReasoningParams | undefined
3241
} & BaseModelParams
3342

34-
type AnthropicModelParams = {
35-
format: "anthropic"
36-
reasoning: AnthropicReasoningParams | undefined
43+
type GeminiModelParams = {
44+
format: "gemini"
45+
reasoning: GeminiReasoningParams | undefined
3746
} & BaseModelParams
3847

3948
type OpenRouterModelParams = {
4049
format: "openrouter"
4150
reasoning: OpenRouterReasoningParams | undefined
4251
} & BaseModelParams
4352

44-
export type ModelParams = OpenAiModelParams | AnthropicModelParams | OpenRouterModelParams
53+
export type ModelParams = AnthropicModelParams | OpenAiModelParams | GeminiModelParams | OpenRouterModelParams
4554

4655
// Function overloads for specific return types
47-
export function getModelParams(options: GetModelParamsOptions<"openai">): OpenAiModelParams
4856
export function getModelParams(options: GetModelParamsOptions<"anthropic">): AnthropicModelParams
57+
export function getModelParams(options: GetModelParamsOptions<"openai">): OpenAiModelParams
58+
export function getModelParams(options: GetModelParamsOptions<"gemini">): GeminiModelParams
4959
export function getModelParams(options: GetModelParamsOptions<"openrouter">): OpenRouterModelParams
5060
export function getModelParams({
5161
format,
5262
modelId,
5363
model,
5464
settings,
5565
defaultTemperature = 0,
56-
}: GetModelParamsOptions<"openai" | "anthropic" | "openrouter">): ModelParams {
66+
}: GetModelParamsOptions<Format>): ModelParams {
5767
const {
5868
modelMaxTokens: customMaxTokens,
5969
modelMaxThinkingTokens: customMaxThinkingTokens,
@@ -121,6 +131,12 @@ export function getModelParams({
121131
...params,
122132
reasoning: getOpenAiReasoning({ model, reasoningBudget, reasoningEffort, settings }),
123133
}
134+
} else if (format === "gemini") {
135+
return {
136+
format,
137+
...params,
138+
reasoning: getGeminiReasoning({ model, reasoningBudget, reasoningEffort, settings }),
139+
}
124140
} else {
125141
// Special case for o1-pro, which doesn't support temperature.
126142
// Note that OpenRouter's `supported_parameters` field includes

src/api/transform/reasoning.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { BetaThinkingConfigParam } from "@anthropic-ai/sdk/resources/beta"
22
import OpenAI from "openai"
3+
import type { GenerateContentConfig } from "@google/genai"
34

45
import type { ModelInfo, ProviderSettings } from "@roo-code/types"
56

@@ -17,6 +18,8 @@ export type AnthropicReasoningParams = BetaThinkingConfigParam
1718

1819
export type OpenAiReasoningParams = { reasoning_effort: OpenAI.Chat.ChatCompletionCreateParams["reasoning_effort"] }
1920

21+
export type GeminiReasoningParams = GenerateContentConfig["thinkingConfig"]
22+
2023
export type GetModelReasoningOptions = {
2124
model: ModelInfo
2225
reasoningBudget: number | undefined
@@ -49,3 +52,12 @@ export const getOpenAiReasoning = ({
4952
settings,
5053
}: GetModelReasoningOptions): OpenAiReasoningParams | undefined =>
5154
shouldUseReasoningEffort({ model, settings }) ? { reasoning_effort: reasoningEffort } : undefined
55+
56+
export const getGeminiReasoning = ({
57+
model,
58+
reasoningBudget,
59+
settings,
60+
}: GetModelReasoningOptions): GeminiReasoningParams | undefined =>
61+
shouldUseReasoningBudget({ model, settings })
62+
? { thinkingBudget: reasoningBudget!, includeThoughts: true }
63+
: undefined

webview-ui/src/components/settings/ModelInfoView.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,7 @@ export const ModelInfoView = ({
7373
),
7474
apiProvider === "gemini" && (
7575
<span className="italic">
76-
{selectedModelId === "gemini-2.5-pro-preview-03-25" ||
77-
selectedModelId === "gemini-2.5-pro-preview-05-06"
76+
{selectedModelId.includes("pro-preview")
7877
? t("settings:modelInfo.gemini.billingEstimate")
7978
: t("settings:modelInfo.gemini.freeRequests", {
8079
count: selectedModelId && selectedModelId.includes("flash") ? 15 : 2,

0 commit comments

Comments
 (0)