Skip to content

Commit c32f32a

Browse files
cursoragentmsukkari
andcommitted
fix: add adaptive thinking support for Claude Opus 4.7+
Claude Opus 4.7 only supports adaptive thinking mode (thinking.type: 'adaptive') and rejects the legacy budget-based thinking (thinking.type: 'enabled'). This change: - Adds model detection to automatically use adaptive thinking for Opus 4.7+ - Uses adaptive thinking by default for Opus 4.6 and Sonnet 4.6 (recommended) - Falls back to budget-based thinking for older models (Sonnet 4.5, etc.) - Adds thinking support for google-vertex-anthropic and amazon-bedrock providers - Adds ANTHROPIC_EFFORT env var to control thinking depth (low/medium/high/max) Fixes #SOU-918 Co-authored-by: Michael Sukkarieh <msukkari@users.noreply.github.com>
1 parent ed05fc4 commit c32f32a

2 files changed

Lines changed: 79 additions & 6 deletions

File tree

packages/shared/src/env.server.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,13 @@ const options = {
203203
ANTHROPIC_API_KEY: z.string().optional(),
204204
ANTHROPIC_AUTH_TOKEN: z.string().optional(),
205205
ANTHROPIC_THINKING_BUDGET_TOKENS: numberSchema.default(12000),
206+
/**
207+
* The effort level for Anthropic models using adaptive thinking.
208+
* Controls how much thinking Claude uses when responding.
209+
* Valid values: 'low', 'medium', 'high' (default), 'max'
210+
* @see https://docs.anthropic.com/en/docs/build-with-claude/effort
211+
*/
212+
ANTHROPIC_EFFORT: z.enum(['low', 'medium', 'high', 'max']).default('high'),
206213

207214
AZURE_API_KEY: z.string().optional(),
208215
AZURE_RESOURCE_NAME: z.string().optional(),

packages/web/src/features/chat/utils.server.ts

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,66 @@ import fs from 'fs';
2626
import path from 'path';
2727
import { LanguageModelInfo, SBChatMessage } from './types';
2828

29+
type AnthropicThinkingMode = 'adaptive' | 'budget' | 'disabled';
30+
31+
/**
32+
* Determines the appropriate thinking configuration for an Anthropic model.
33+
*
34+
* - Claude Opus 4.7+: Only supports adaptive thinking (budget mode is rejected)
35+
* - Claude Opus 4.6/Sonnet 4.6+: Supports both but adaptive is recommended
36+
* - Older models (Sonnet 4.5, Opus 4.5, etc.): Only support budget-based thinking
37+
*/
38+
const getAnthropicThinkingMode = (modelId: string): AnthropicThinkingMode => {
39+
const lowerModelId = modelId.toLowerCase();
40+
41+
// Claude Opus 4.7 and later versions ONLY support adaptive thinking
42+
// Model IDs: claude-opus-4-7, claude-opus-4-7-*, claude-opus-4-8, etc.
43+
if (lowerModelId.includes('opus-4-7') ||
44+
lowerModelId.includes('opus-4-8') ||
45+
lowerModelId.includes('opus-4-9') ||
46+
lowerModelId.includes('opus-5') ||
47+
lowerModelId.includes('mythos')) {
48+
return 'adaptive';
49+
}
50+
51+
// Claude Opus 4.6 and Sonnet 4.6+ support both, but adaptive is recommended
52+
// Model IDs: claude-opus-4-6, claude-sonnet-4-6, etc.
53+
if (lowerModelId.includes('opus-4-6') || lowerModelId.includes('sonnet-4-6')) {
54+
return 'adaptive';
55+
}
56+
57+
// Older models (Sonnet 4.5, Opus 4.5, Sonnet 3.7, etc.) only support budget-based thinking
58+
return 'budget';
59+
};
60+
61+
/**
62+
* Builds the Anthropic thinking provider options based on the model and environment configuration.
63+
*/
64+
const getAnthropicThinkingOptions = (modelId: string): AnthropicProviderOptions => {
65+
const thinkingMode = getAnthropicThinkingMode(modelId);
66+
67+
if (thinkingMode === 'disabled') {
68+
return {};
69+
}
70+
71+
if (thinkingMode === 'adaptive') {
72+
return {
73+
thinking: {
74+
type: 'adaptive',
75+
},
76+
effort: env.ANTHROPIC_EFFORT,
77+
};
78+
}
79+
80+
// Budget-based thinking for older models
81+
return {
82+
thinking: {
83+
type: 'enabled',
84+
budgetTokens: env.ANTHROPIC_THINKING_BUDGET_TOKENS,
85+
},
86+
};
87+
};
88+
2989
/**
3090
* Checks if the current user (authenticated or anonymous) is the owner of a chat.
3191
*/
@@ -192,8 +252,16 @@ export const getAISDKLanguageModelAndOptions = async (config: LanguageModel): Pr
192252
: undefined,
193253
});
194254

255+
// Add thinking options for Anthropic models on Bedrock
256+
const isAnthropicModel = modelId.toLowerCase().includes('anthropic') ||
257+
modelId.toLowerCase().includes('claude');
258+
const providerOptions = isAnthropicModel
259+
? { anthropic: getAnthropicThinkingOptions(modelId) }
260+
: undefined;
261+
195262
return {
196263
model: aws(modelId),
264+
providerOptions,
197265
};
198266
}
199267
case 'anthropic': {
@@ -213,12 +281,7 @@ export const getAISDKLanguageModelAndOptions = async (config: LanguageModel): Pr
213281
return {
214282
model: anthropic(modelId),
215283
providerOptions: {
216-
anthropic: {
217-
thinking: {
218-
type: "enabled",
219-
budgetTokens: env.ANTHROPIC_THINKING_BUDGET_TOKENS,
220-
}
221-
} satisfies AnthropicProviderOptions,
284+
anthropic: getAnthropicThinkingOptions(modelId),
222285
},
223286
};
224287
}
@@ -326,6 +389,9 @@ export const getAISDKLanguageModelAndOptions = async (config: LanguageModel): Pr
326389

327390
return {
328391
model: vertexAnthropic(modelId),
392+
providerOptions: {
393+
anthropic: getAnthropicThinkingOptions(modelId),
394+
},
329395
};
330396
}
331397
case 'mistral': {

0 commit comments

Comments
 (0)