Skip to content

Commit df7b458

Browse files
Sonnet, Give Me a Reason (RooCodeInc#1961)
* wip * added slider for setting reasoning budget tokens * refactor out generic slider component; improve styling; add debounce * added setting validation * styling and adding reasoning level * changeset * make change to trigger test rerun * revert useless comment
1 parent 560cefe commit df7b458

File tree

10 files changed

+407
-1
lines changed

10 files changed

+407
-1
lines changed

.changeset/slimy-keys-add.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": minor
3+
---
4+
5+
Added thinking tokens budget slider for Sonnet 3.7 with Anthropic

package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,11 @@
211211
"type": "boolean",
212212
"default": true,
213213
"description": "Controls whether the MCP Marketplace is enabled."
214+
},
215+
"cline.modelSettings.anthropic.thinkingBudgetTokens": {
216+
"type": "number",
217+
"default": 0,
218+
"description": "Controls the token budget for Claude's thinking capability. Set to 0 to disable thinking. When enabled, must be ≥1024 and less than the model's max token output."
214219
}
215220
}
216221
}

src/api/providers/anthropic.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ export class AnthropicHandler implements ApiHandler {
1919

2020
@withRetry()
2121
async *createMessage(systemPrompt: string, messages: Anthropic.Messages.MessageParam[]): ApiStream {
22+
let budget_tokens = this.options.thinkingBudgetTokens || 0
23+
const reasoningOn = budget_tokens !== 0 ? true : false
2224
const model = this.getModel()
2325
let stream: AnthropicStream<Anthropic.RawMessageStreamEvent>
2426
const modelId = model.id
@@ -41,8 +43,11 @@ export class AnthropicHandler implements ApiHandler {
4143
stream = await this.client.messages.create(
4244
{
4345
model: modelId,
46+
thinking: reasoningOn ? { type: "enabled", budget_tokens: budget_tokens } : undefined,
4447
max_tokens: model.info.maxTokens || 8192,
45-
temperature: 0,
48+
// "Thinking isn’t compatible with temperature, top_p, or top_k modifications as well as forced tool use."
49+
// (https://docs.anthropic.com/en/docs/build-with-claude/extended-thinking#important-considerations-when-using-extended-thinking)
50+
temperature: reasoningOn ? 1 : 0,
4651
system: [
4752
{
4853
text: systemPrompt,
@@ -148,6 +153,20 @@ export class AnthropicHandler implements ApiHandler {
148153
break
149154
case "content_block_start":
150155
switch (chunk.content_block.type) {
156+
case "thinking":
157+
yield {
158+
type: "reasoning",
159+
reasoning: chunk.content_block.thinking || "",
160+
}
161+
break
162+
case "redacted_thinking":
163+
// Handle redacted thinking blocks - we still mark it as reasoning
164+
// but note that the content is encrypted
165+
yield {
166+
type: "reasoning",
167+
reasoning: "[Redacted thinking block]",
168+
}
169+
break
151170
case "text":
152171
// we may receive multiple text blocks, in which case just insert a line break between them
153172
if (chunk.index > 0) {
@@ -165,12 +184,22 @@ export class AnthropicHandler implements ApiHandler {
165184
break
166185
case "content_block_delta":
167186
switch (chunk.delta.type) {
187+
case "thinking_delta":
188+
yield {
189+
type: "reasoning",
190+
reasoning: chunk.delta.thinking,
191+
}
192+
break
168193
case "text_delta":
169194
yield {
170195
type: "text",
171196
text: chunk.delta.text,
172197
}
173198
break
199+
case "signature_delta":
200+
// We don't need to do anything with the signature in the client
201+
// It's used when sending the thinking block back to the API
202+
break
174203
}
175204
break
176205
case "content_block_stop":

src/core/webview/ClineProvider.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { getNonce } from "./getNonce"
3535
import { getUri } from "./getUri"
3636
import { telemetryService } from "../../services/telemetry/TelemetryService"
3737
import { TelemetrySetting } from "../../shared/TelemetrySetting"
38+
import { validateThinkingBudget } from "../../utils/validation"
3839

3940
/*
4041
https://github.com/microsoft/vscode-webview-ui-toolkit-samples/blob/main/default/weather-webview/src/providers/WeatherViewProvider.ts
@@ -261,6 +262,19 @@ export class ClineProvider implements vscode.WebviewViewProvider {
261262
// Update state when marketplace tab setting changes
262263
await this.postStateToWebview()
263264
}
265+
if (e && e.affectsConfiguration("cline.modelSettings.anthropic.thinkingBudgetTokens")) {
266+
const config = vscode.workspace.getConfiguration("cline.modelSettings.anthropic")
267+
const thinkingBudget = config.get<number>("thinkingBudgetTokens", 0)
268+
269+
const validatedValue = validateThinkingBudget(thinkingBudget)
270+
271+
// Only update if the value changed
272+
if (validatedValue !== thinkingBudget) {
273+
await config.update("thinkingBudgetTokens", validatedValue, true)
274+
}
275+
276+
await this.postStateToWebview()
277+
}
264278
},
265279
null,
266280
this.disposables,
@@ -976,6 +990,15 @@ export class ClineProvider implements vscode.WebviewViewProvider {
976990
}
977991
break
978992
}
993+
case "updateThinkingBudgetTokens": {
994+
if (message.number !== undefined) {
995+
const validatedValue = validateThinkingBudget(message.number)
996+
997+
const config = vscode.workspace.getConfiguration("cline.modelSettings.anthropic")
998+
await config.update("thinkingBudgetTokens", validatedValue, true)
999+
}
1000+
break
1001+
}
9791002
case "openExtensionSettings": {
9801003
const settingsFilter = message.text || ""
9811004
await vscode.commands.executeCommand(
@@ -2042,6 +2065,10 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
20422065
.getConfiguration("cline.modelSettings.o3Mini")
20432066
.get("reasoningEffort", "medium")
20442067

2068+
const thinkingBudgetTokens = vscode.workspace
2069+
.getConfiguration("cline.modelSettings.anthropic")
2070+
.get("thinkingBudgetTokens", 0)
2071+
20452072
const mcpMarketplaceEnabled = vscode.workspace.getConfiguration("cline").get<boolean>("mcpMarketplace.enabled", true)
20462073

20472074
return {
@@ -2084,6 +2111,7 @@ Here is the project's README to help you get started:\n\n${mcpDetails.readmeCont
20842111
openRouterModelInfo,
20852112
vsCodeLmModelSelector,
20862113
o3MiniReasoningEffort,
2114+
thinkingBudgetTokens,
20872115
liteLlmBaseUrl,
20882116
liteLlmModelId,
20892117
liteLlmApiKey,

src/shared/WebviewMessage.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export interface WebviewMessage {
5757
| "updateMcpTimeout"
5858
| "fetchOpenGraphData"
5959
| "checkIsImageUrl"
60+
| "updateThinkingBudgetTokens"
6061
// | "relaunchChromeDebugMode"
6162
text?: string
6263
disabled?: boolean

src/shared/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ export interface ApiHandlerOptions {
5959
o3MiniReasoningEffort?: string
6060
qwenApiLine?: string
6161
xaiApiKey?: string
62+
thinkingBudgetTokens?: number
6263
}
6364

6465
export type ApiConfiguration = ApiHandlerOptions & {

src/utils/validation.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { anthropicModels } from "../shared/api"
2+
3+
/**
4+
* Validates the thinking budget token value according to the specified rules:
5+
* - If disabled (0), return as is
6+
* - If enabled but less than minimum (1024), set to minimum
7+
* - If greater than or equal to max tokens, set to max tokens - 1
8+
* - Otherwise, return the original value
9+
*
10+
* @param value The thinking budget token value to validate
11+
* @param maxTokens The maximum tokens for the current model
12+
* @returns The validated thinking budget token value
13+
*/
14+
export function validateThinkingBudget(
15+
value: number,
16+
maxTokens: number = anthropicModels["claude-3-7-sonnet-20250219"].maxTokens,
17+
): number {
18+
// If disabled (0), return as is
19+
if (value === 0) {
20+
return 0
21+
}
22+
23+
// If enabled but less than minimum, set to minimum
24+
if (value > 0 && value < 1024) {
25+
return 1024
26+
}
27+
28+
// If greater than or equal to max allowed tokens (80% of max tokens), cap at that value
29+
const maxAllowedTokens = Math.floor(maxTokens * 0.8)
30+
if (value >= maxAllowedTokens) {
31+
return maxAllowedTokens
32+
}
33+
34+
// Otherwise, return the original value
35+
return value
36+
}

0 commit comments

Comments
 (0)