fix(provider): generalize Claude adaptive thinking (#38757)

This commit is contained in:
Aiden Cline
2026-07-24 16:44:00 -05:00
committed by GitHub
parent 7840562d1b
commit 2b2aacc939
2 changed files with 145 additions and 20 deletions
+36 -17
View File
@@ -635,24 +635,23 @@ function openaiCompatibleReasoningEfforts(id: string) {
return gpt5CodexReasoningEfforts(apiId) ?? versionedGpt5ReasoningEfforts(apiId) ?? OPENAI_EFFORTS
}
function anthropicOpus47OrLater(apiId: string) {
// Matches "opus-4.7" (Anthropic/Bedrock/Vertex) and "claude-4.7-opus" (SAP AI Core inverted).
// Greedy \d+ correctly extends to multi-digit majors (e.g. "claude-10.0-opus") for forward compatibility.
const version = /opus-(\d+)[.-](\d+)(?:[.@-]|$)|claude-(\d+)[.-](\d+)-opus(?:[.@-]|$)/i.exec(apiId)
if (!version) return false
const major = Number(version[1] ?? version[3])
const minor = Number(version[2] ?? version[4])
function anthropicUsesModernAdaptiveThinking(apiId: string) {
if (!apiId.toLowerCase().includes("claude-")) return false
// Covers family-first IDs such as claude-opus-4.7 and version-first IDs such as claude-4.7-opus.
// Limit minors to two digits so release dates in IDs such as claude-opus-4-20250514 are not versions.
const version = /claude-(?:[a-z]+-)?(\d+)(?:[.-](\d{1,2}))?(?:[.@-]|$)/i.exec(apiId)
if (!version) return true
const major = Number(version[1])
const minor = Number(version[2] ?? 0)
return major > 4 || (major === 4 && minor >= 7)
}
function anthropicSonnet5OrLater(apiId: string) {
const version = /sonnet-(\d+)(?:[.@-]|$)|claude-(\d+)-sonnet(?:[.@-]|$)/i.exec(apiId)
if (!version) return false
return Number(version[1] ?? version[2]) >= 5
function anthropicOpus45(apiId: string) {
return ["opus-4-5", "opus-4.5"].some((value) => apiId.includes(value))
}
function anthropicAdaptiveEfforts(apiId: string): string[] | null {
if (anthropicOpus47OrLater(apiId) || anthropicSonnet5OrLater(apiId) || apiId.includes("fable-5")) {
if (anthropicUsesModernAdaptiveThinking(apiId)) {
return ["low", "medium", "high", "xhigh", "max"]
}
if (
@@ -666,7 +665,7 @@ function anthropicAdaptiveEfforts(apiId: string): string[] | null {
}
function anthropicOmitsThinking(apiId: string) {
return anthropicOpus47OrLater(apiId) || anthropicSonnet5OrLater(apiId) || apiId.includes("fable-5")
return anthropicUsesModernAdaptiveThinking(apiId)
}
function googleThinkingLevelEfforts(apiId: string) {
@@ -990,8 +989,10 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
)
}
if (["opus-4-5", "opus-4.5"].some((v) => model.api.id.includes(v))) {
return Object.fromEntries(WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, { effort }]))
if (anthropicOpus45(model.api.id)) {
return Object.fromEntries(
WIDELY_SUPPORTED_EFFORTS.map((effort) => [effort, anthropicOpus45Effort(model, effort)]),
)
}
return {
@@ -1103,7 +1104,7 @@ export function variants(model: Provider.Model): Record<string, Record<string, a
if (id.includes("anthropic")) {
if (adaptiveEfforts) {
// Bedrock adaptive splits `effort` out into `output_config` (vs Anthropic
// native which inlines it). Opus 4.7+ flipped `display` default to "omitted".
// native which inlines it). Claude 4.7+ defaults `display` to "omitted".
return wrapInSapModelParams(
Object.fromEntries(
adaptiveEfforts.map((effort) => [
@@ -1711,6 +1712,14 @@ function reasoningEffort(model: Provider.Model, effort: string) {
...(anthropicOmitsThinking(model.api.id) ? { display: "summarized" } : {}),
},
}
if (anthropicOpus45(model.api.id))
return {
reasoningConfig: {
type: "enabled",
budgetTokens: Math.min(16_000, Math.floor(model.limit.output / 2 - 1)),
maxReasoningEffort: effort,
},
}
if (model.api.id.includes("anthropic")) return
return { reasoningConfig: { type: "enabled", maxReasoningEffort: effort } }
case "@ai-sdk/gateway":
@@ -1751,7 +1760,7 @@ function reasoningEffort(model: Provider.Model, effort: string) {
}
function anthropicEffort(model: Provider.Model, effort: string) {
if (["opus-4-5", "opus-4.5"].some((value) => model.api.id.includes(value))) return { effort }
if (anthropicOpus45(model.api.id)) return anthropicOpus45Effort(model, effort)
// Kimi defaults to omitting adaptive thinking text unless summarized display is requested.
if (isKimiFamily(model)) return { thinking: { type: "adaptive", display: "summarized" }, effort }
if (!anthropicAdaptiveEfforts(model.api.id)) return
@@ -1764,6 +1773,16 @@ function anthropicEffort(model: Provider.Model, effort: string) {
}
}
function anthropicOpus45Effort(model: Provider.Model, effort: string) {
return {
thinking: {
type: "enabled",
budgetTokens: Math.min(16_000, Math.floor(model.limit.output / 2 - 1)),
},
effort,
}
}
function reasoningBudget(model: Provider.Model, budget: number) {
switch (model.api.npm) {
case "@openrouter/ai-sdk-provider":