fix(ai): enable adaptive thinking for sonnet 4.6 and clamp xhigh effort (#1548)

* fix(ai): enable adaptive thinking for sonnet 4.6 and clamp xhigh effort

* chore(ai): drop changelog edit from contribution

---------

Co-authored-by: tctev <224793535+tctev@users.noreply.github.com>
This commit is contained in:
tctev 2026-02-26 00:34:06 +01:00 committed by GitHub
parent cf656c169c
commit e9d0074fa6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 32 additions and 18 deletions

View file

@ -371,13 +371,21 @@ function handleContentBlockStop(
} }
/** /**
* Check if the model supports adaptive thinking (Opus 4.6+). * Check if the model supports adaptive thinking (Opus 4.6 and Sonnet 4.6).
*/ */
function supportsAdaptiveThinking(modelId: string): boolean { function supportsAdaptiveThinking(modelId: string): boolean {
return modelId.includes("opus-4-6") || modelId.includes("opus-4.6"); return (
modelId.includes("opus-4-6") ||
modelId.includes("opus-4.6") ||
modelId.includes("sonnet-4-6") ||
modelId.includes("sonnet-4.6")
);
} }
function mapThinkingLevelToEffort(level: SimpleStreamOptions["reasoning"]): "low" | "medium" | "high" | "max" { function mapThinkingLevelToEffort(
level: SimpleStreamOptions["reasoning"],
modelId: string,
): "low" | "medium" | "high" | "max" {
switch (level) { switch (level) {
case "minimal": case "minimal":
case "low": case "low":
@ -387,7 +395,7 @@ function mapThinkingLevelToEffort(level: SimpleStreamOptions["reasoning"]): "low
case "high": case "high":
return "high"; return "high";
case "xhigh": case "xhigh":
return "max"; return modelId.includes("opus-4-6") || modelId.includes("opus-4.6") ? "max" : "high";
default: default:
return "high"; return "high";
} }
@ -668,7 +676,7 @@ function buildAdditionalModelRequestFields(
const result: Record<string, any> = supportsAdaptiveThinking(model.id) const result: Record<string, any> = supportsAdaptiveThinking(model.id)
? { ? {
thinking: { type: "adaptive" }, thinking: { type: "adaptive" },
output_config: { effort: mapThinkingLevelToEffort(options.reasoning) }, output_config: { effort: mapThinkingLevelToEffort(options.reasoning, model.id) },
} }
: (() => { : (() => {
const defaultBudgets: Record<ThinkingLevel, number> = { const defaultBudgets: Record<ThinkingLevel, number> = {

View file

@ -157,19 +157,19 @@ export type AnthropicEffort = "low" | "medium" | "high" | "max";
export interface AnthropicOptions extends StreamOptions { export interface AnthropicOptions extends StreamOptions {
/** /**
* Enable extended thinking. * Enable extended thinking.
* For Opus 4.6+: uses adaptive thinking (Claude decides when/how much to think). * For Opus 4.6 and Sonnet 4.6: uses adaptive thinking (model decides when/how much to think).
* For older models: uses budget-based thinking with thinkingBudgetTokens. * For older models: uses budget-based thinking with thinkingBudgetTokens.
*/ */
thinkingEnabled?: boolean; thinkingEnabled?: boolean;
/** /**
* Token budget for extended thinking (older models only). * Token budget for extended thinking (older models only).
* Ignored for Opus 4.6+ which uses adaptive thinking. * Ignored for Opus 4.6 and Sonnet 4.6, which use adaptive thinking.
*/ */
thinkingBudgetTokens?: number; thinkingBudgetTokens?: number;
/** /**
* Effort level for adaptive thinking (Opus 4.6+ only). * Effort level for adaptive thinking (Opus 4.6 and Sonnet 4.6).
* Controls how much thinking Claude allocates: * Controls how much thinking Claude allocates:
* - "max": Always thinks with no constraints * - "max": Always thinks with no constraints (Opus 4.6 only)
* - "high": Always thinks, deep reasoning (default) * - "high": Always thinks, deep reasoning (default)
* - "medium": Moderate thinking, may skip for simple queries * - "medium": Moderate thinking, may skip for simple queries
* - "low": Minimal thinking, skips for simple tasks * - "low": Minimal thinking, skips for simple tasks
@ -411,17 +411,23 @@ export const streamAnthropic: StreamFunction<"anthropic-messages", AnthropicOpti
}; };
/** /**
* Check if a model supports adaptive thinking (Opus 4.6+) * Check if a model supports adaptive thinking (Opus 4.6 and Sonnet 4.6)
*/ */
function supportsAdaptiveThinking(modelId: string): boolean { function supportsAdaptiveThinking(modelId: string): boolean {
// Opus 4.6 model IDs (with or without date suffix) // Opus 4.6 and Sonnet 4.6 model IDs (with or without date suffix)
return modelId.includes("opus-4-6") || modelId.includes("opus-4.6"); return (
modelId.includes("opus-4-6") ||
modelId.includes("opus-4.6") ||
modelId.includes("sonnet-4-6") ||
modelId.includes("sonnet-4.6")
);
} }
/** /**
* Map ThinkingLevel to Anthropic effort levels for adaptive thinking * Map ThinkingLevel to Anthropic effort levels for adaptive thinking.
* Note: effort "max" is only valid on Opus 4.6.
*/ */
function mapThinkingLevelToEffort(level: SimpleStreamOptions["reasoning"]): AnthropicEffort { function mapThinkingLevelToEffort(level: SimpleStreamOptions["reasoning"], modelId: string): AnthropicEffort {
switch (level) { switch (level) {
case "minimal": case "minimal":
return "low"; return "low";
@ -432,7 +438,7 @@ function mapThinkingLevelToEffort(level: SimpleStreamOptions["reasoning"]): Anth
case "high": case "high":
return "high"; return "high";
case "xhigh": case "xhigh":
return "max"; return modelId.includes("opus-4-6") || modelId.includes("opus-4.6") ? "max" : "high";
default: default:
return "high"; return "high";
} }
@ -453,10 +459,10 @@ export const streamSimpleAnthropic: StreamFunction<"anthropic-messages", SimpleS
return streamAnthropic(model, context, { ...base, thinkingEnabled: false } satisfies AnthropicOptions); return streamAnthropic(model, context, { ...base, thinkingEnabled: false } satisfies AnthropicOptions);
} }
// For Opus 4.6+: use adaptive thinking with effort level // For Opus 4.6 and Sonnet 4.6: use adaptive thinking with effort level
// For older models: use budget-based thinking // For older models: use budget-based thinking
if (supportsAdaptiveThinking(model.id)) { if (supportsAdaptiveThinking(model.id)) {
const effort = mapThinkingLevelToEffort(options.reasoning); const effort = mapThinkingLevelToEffort(options.reasoning, model.id);
return streamAnthropic(model, context, { return streamAnthropic(model, context, {
...base, ...base,
thinkingEnabled: true, thinkingEnabled: true,
@ -613,7 +619,7 @@ function buildParams(
params.tools = convertTools(context.tools, isOAuthToken); params.tools = convertTools(context.tools, isOAuthToken);
} }
// Configure thinking mode: adaptive (Opus 4.6+) or budget-based (older models) // Configure thinking mode: adaptive (Opus 4.6 and Sonnet 4.6) or budget-based (older models)
if (options?.thinkingEnabled && model.reasoning) { if (options?.thinkingEnabled && model.reasoning) {
if (supportsAdaptiveThinking(model.id)) { if (supportsAdaptiveThinking(model.id)) {
// Adaptive thinking: Claude decides when and how much to think // Adaptive thinking: Claude decides when and how much to think