Skip to content

Commit 2a0d60f

Browse files
authored
Adding Thinking UX for Gemini (RooCodeInc#4137)
1 parent 9f605a1 commit 2a0d60f

File tree

7 files changed

+69
-22
lines changed

7 files changed

+69
-22
lines changed

.changeset/wet-rice-hunt.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": patch
3+
---
4+
5+
Adding Thinking UX for Gemini

package-lock.json

Lines changed: 10 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@
404404
"@bufbuild/protobuf": "^2.2.5",
405405
"@cerebras/cerebras_cloud_sdk": "^1.35.0",
406406
"@google-cloud/vertexai": "^1.9.3",
407-
"@google/genai": "^0.13.0",
407+
"@google/genai": "1.0.0",
408408
"@grpc/grpc-js": "^1.9.15",
409409
"@grpc/reflection": "^1.0.4",
410410
"@mistralai/mistralai": "^1.5.0",

src/api/providers/gemini.ts

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Anthropic } from "@anthropic-ai/sdk"
22
// Restore GenerateContentConfig import and add GenerateContentResponseUsageMetadata
33
import { GoogleGenAI, type GenerateContentConfig, type GenerateContentResponseUsageMetadata } from "@google/genai"
44
import { withRetry } from "../retry"
5+
import { Part } from "@google/genai"
56
import { ApiHandler } from "../"
67
import { ApiHandlerOptions, geminiDefaultModelId, GeminiModelId, geminiModels, ModelInfo } from "@shared/api"
78
import { convertAnthropicMessageToGemini } from "../transform/gemini-format"
@@ -96,9 +97,10 @@ export class GeminiHandler implements ApiHandler {
9697
}
9798

9899
// Add thinking config if the model supports it
99-
if (info.thinkingConfig?.outputPrice !== undefined && maxBudget > 0) {
100+
if (thinkingBudget > 0) {
100101
requestConfig.thinkingConfig = {
101102
thinkingBudget: thinkingBudget,
103+
includeThoughts: true,
102104
}
103105
}
104106

@@ -111,6 +113,7 @@ export class GeminiHandler implements ApiHandler {
111113
let promptTokens = 0
112114
let outputTokens = 0
113115
let cacheReadTokens = 0
116+
let thoughtsTokenCount = 0 // Initialize thought token counts
114117
let lastUsageMetadata: GenerateContentResponseUsageMetadata | undefined
115118

116119
try {
@@ -130,6 +133,31 @@ export class GeminiHandler implements ApiHandler {
130133
isFirstSdkChunk = false
131134
}
132135

136+
// Handle thinking content from Gemini's response
137+
const candidateForThoughts = chunk?.candidates?.[0]
138+
const partsForThoughts = candidateForThoughts?.content?.parts
139+
let thoughts = "" // Initialize as empty string
140+
141+
if (partsForThoughts) {
142+
// This ensures partsForThoughts is a Part[] array
143+
for (const part of partsForThoughts) {
144+
const { thought, text } = part as Part
145+
if (thought && text) {
146+
// Ensure part.text exists
147+
// Handle the thought part
148+
thoughts += text + "\n" // Append thought and a newline
149+
}
150+
}
151+
}
152+
153+
if (thoughts.trim() !== "") {
154+
yield {
155+
type: "reasoning",
156+
reasoning: thoughts.trim(),
157+
}
158+
thoughts = "" // Reset thoughts after yielding
159+
}
160+
133161
if (chunk.text) {
134162
yield {
135163
type: "text",
@@ -141,6 +169,7 @@ export class GeminiHandler implements ApiHandler {
141169
lastUsageMetadata = chunk.usageMetadata
142170
promptTokens = lastUsageMetadata.promptTokenCount ?? promptTokens
143171
outputTokens = lastUsageMetadata.candidatesTokenCount ?? outputTokens
172+
thoughtsTokenCount = lastUsageMetadata.thoughtsTokenCount ?? thoughtsTokenCount
144173
cacheReadTokens = lastUsageMetadata.cachedContentTokenCount ?? cacheReadTokens
145174
}
146175
}
@@ -151,12 +180,14 @@ export class GeminiHandler implements ApiHandler {
151180
info,
152181
inputTokens: promptTokens,
153182
outputTokens,
183+
thoughtsTokenCount,
154184
cacheReadTokens,
155185
})
156186
yield {
157187
type: "usage",
158188
inputTokens: promptTokens,
159189
outputTokens,
190+
thoughtsTokenCount,
160191
cacheReadTokens,
161192
cacheWriteTokens: 0,
162193
totalCost,
@@ -239,11 +270,13 @@ export class GeminiHandler implements ApiHandler {
239270
info,
240271
inputTokens,
241272
outputTokens,
273+
thoughtsTokenCount = 0,
242274
cacheReadTokens = 0,
243275
}: {
244276
info: ModelInfo
245277
inputTokens: number
246278
outputTokens: number
279+
thoughtsTokenCount: number
247280
cacheReadTokens?: number
248281
}) {
249282
// Exit early if any required pricing information is missing
@@ -275,18 +308,18 @@ export class GeminiHandler implements ApiHandler {
275308
const inputTokensCost = inputPrice * (uncachedInputTokens / 1_000_000)
276309

277310
// 2. Output token costs
278-
const outputTokensCost = outputPrice * (outputTokens / 1_000_000)
311+
const responseTokensCost = outputPrice * ((outputTokens + thoughtsTokenCount) / 1_000_000)
279312

280313
// 3. Cache read costs (immediate)
281314
const cacheReadCost = (cacheReadTokens ?? 0) > 0 ? cacheReadsPrice * ((cacheReadTokens ?? 0) / 1_000_000) : 0
282315

283316
// Calculate total immediate cost (excluding cache write/storage costs)
284-
const totalCost = inputTokensCost + outputTokensCost + cacheReadCost
317+
const totalCost = inputTokensCost + responseTokensCost + cacheReadCost
285318

286319
// Create the trace object for debugging
287320
const trace: Record<string, { price: number; tokens: number; cost: number }> = {
288321
input: { price: inputPrice, tokens: uncachedInputTokens, cost: inputTokensCost },
289-
output: { price: outputPrice, tokens: outputTokens, cost: outputTokensCost },
322+
output: { price: outputPrice, tokens: outputTokens, cost: responseTokensCost },
290323
}
291324

292325
// Only include cache read costs in the trace (cache write costs are tracked separately)

src/api/transform/stream.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ export interface ApiStreamUsageChunk {
1717
outputTokens: number
1818
cacheWriteTokens?: number
1919
cacheReadTokens?: number
20+
thoughtsTokenCount?: number // openrouter
2021
totalCost?: number // openrouter
2122
}

src/shared/api.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,9 @@ export const vertexModels = {
598598
cacheReadsPrice: 0.625,
599599
},
600600
],
601+
thinkingConfig: {
602+
maxBudget: 32768,
603+
},
601604
},
602605
"gemini-2.5-flash-preview-04-17": {
603606
maxTokens: 65536,
@@ -766,6 +769,9 @@ export const geminiModels = {
766769
cacheReadsPrice: 0.625,
767770
},
768771
],
772+
thinkingConfig: {
773+
maxBudget: 32768,
774+
},
769775
},
770776
"gemini-2.5-flash-preview-05-20": {
771777
maxTokens: 65536,

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

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,14 @@ const OpenRouterBalanceDisplay = ({ apiKey }: { apiKey: string }) => {
119119

120120
const SUPPORTED_THINKING_MODELS: Record<string, string[]> = {
121121
anthropic: ["claude-3-7-sonnet-20250219", "claude-sonnet-4-20250514", "claude-opus-4-20250514"],
122-
vertex: ["claude-3-7-sonnet@20250219", "claude-sonnet-4@20250514", "claude-opus-4@20250514"],
122+
vertex: [
123+
"claude-3-7-sonnet@20250219",
124+
"claude-sonnet-4@20250514",
125+
"claude-opus-4@20250514",
126+
"gemini-2.5-flash-preview-05-20",
127+
"gemini-2.5-flash-preview-04-17",
128+
"gemini-2.5-pro-preview-06-05",
129+
],
123130
qwen: [
124131
"qwen3-235b-a22b",
125132
"qwen3-32b",
@@ -132,6 +139,7 @@ const SUPPORTED_THINKING_MODELS: Record<string, string[]> = {
132139
"qwen-plus-latest",
133140
"qwen-turbo-latest",
134141
],
142+
gemini: ["gemini-2.5-flash-preview-05-20", "gemini-2.5-flash-preview-04-17", "gemini-2.5-pro-preview-06-05"],
135143
}
136144

137145
// This is necessary to ensure dropdown opens downward, important for when this is used in popup
@@ -1057,15 +1065,6 @@ const ApiOptions = ({
10571065
</VSCodeLink>
10581066
)}
10591067
</p>
1060-
1061-
{/* Add Thinking Budget Slider specifically for gemini-2.5-flash-preview-04-17 */}
1062-
{selectedProvider === "gemini" && selectedModelId === "gemini-2.5-flash-preview-04-17" && (
1063-
<ThinkingBudgetSlider
1064-
apiConfiguration={apiConfiguration}
1065-
setApiConfiguration={setApiConfiguration}
1066-
maxBudget={selectedModelInfo.thinkingConfig?.maxBudget}
1067-
/>
1068-
)}
10691068
</div>
10701069
)}
10711070

0 commit comments

Comments
 (0)