From 374540fc2ba85c47b2b1c3dbdbf514644c73ad53 Mon Sep 17 00:00:00 2001 From: James Tancock Date: Fri, 12 Jun 2026 20:54:16 +0100 Subject: [PATCH] llm/ant: detect adaptive thinking for qualified model names useAdaptiveThinking only handled bare names and dated snapshots ("claude-opus-4-8-20260115"), missing provider-qualified names like "us.anthropic.claude-opus-4-8-v1:0". Normalize '.' to '-' and match on '-'-delimited tokens so qualified names work without false positives like "claude-opus-4-80". --- llm/ant/ant.go | 14 +++++++++++--- llm/ant/ant_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/llm/ant/ant.go b/llm/ant/ant.go index 7c58289d..04bcdc72 100644 --- a/llm/ant/ant.go +++ b/llm/ant/ant.go @@ -278,10 +278,18 @@ type systemContent struct { // (thinking: {type: "adaptive"} + output_config: {effort: "..."}) instead of // the legacy manual thinking (thinking: {type: "enabled", budget_tokens: N}). // Claude Opus 4.7 and later require adaptive thinking. +// Matching is done on '-'/'.'-delimited tokens so it covers dated snapshots +// ("claude-opus-4-8-20260115") and provider-qualified names +// ("us.anthropic.claude-opus-4-8-v1:0") without false positives like +// "claude-opus-4-80". func useAdaptiveThinking(model string) bool { - return model == ClaudeFable5 || strings.HasPrefix(model, "claude-fable-5-") || - model == Claude48Opus || strings.HasPrefix(model, "claude-opus-4-8-") || - model == Claude47Opus || strings.HasPrefix(model, "claude-opus-4-7-") + model = "-" + strings.ReplaceAll(model, ".", "-") + "-" + for _, m := range []string{ClaudeFable5, Claude48Opus, Claude47Opus} { + if strings.Contains(model, "-"+m+"-") { + return true + } + } + return false } // request represents the request payload for creating a message. diff --git a/llm/ant/ant_test.go b/llm/ant/ant_test.go index fb2127ae..88bf9ea0 100644 --- a/llm/ant/ant_test.go +++ b/llm/ant/ant_test.go @@ -2697,6 +2697,31 @@ func TestWebSearchContentSurvivesJSONRoundTrip(t *testing.T) { } } +func TestUseAdaptiveThinking(t *testing.T) { + tests := []struct { + model string + want bool + }{ + {Claude48Opus, true}, + {Claude47Opus, true}, + {ClaudeFable5, true}, + {"claude-opus-4-8-20260115", true}, + {"anthropic.claude-opus-4-8", true}, + {"us.anthropic.claude-opus-4-8-v1:0", true}, + {"anthropic.claude-fable-5-20260301-v1:0", true}, + {Claude46Opus, false}, + {Claude46Sonnet, false}, + {"anthropic.claude-sonnet-4-5-20250929-v1:0", false}, + {"claude-opus-4-80", false}, + {"anthropic.claude-opus-4-85-v1:0", false}, + } + for _, tt := range tests { + if got := useAdaptiveThinking(tt.model); got != tt.want { + t.Errorf("useAdaptiveThinking(%q) = %v, want %v", tt.model, got, tt.want) + } + } +} + // TestFromLLMRequestThinkingLevels exercises the per-request ThinkingLevel // override across both adaptive and budget-style Claude models. func TestFromLLMRequestThinkingLevels(t *testing.T) { @@ -2713,6 +2738,7 @@ func TestFromLLMRequestThinkingLevels(t *testing.T) { {name: "adaptive req xhigh", model: Claude47Opus, svcLevel: llm.ThinkingLevelMedium, reqLevel: llm.ThinkingLevelXHigh, wantType: "adaptive", wantEffort: "xhigh"}, {name: "adaptive opus48 xhigh", model: Claude48Opus, svcLevel: llm.ThinkingLevelMedium, reqLevel: llm.ThinkingLevelXHigh, wantType: "adaptive", wantEffort: "xhigh"}, {name: "adaptive req off", model: Claude47Opus, svcLevel: llm.ThinkingLevelMedium, reqLevel: llm.ThinkingLevelOff, wantType: ""}, + {name: "adaptive provider-qualified opus48", model: "us.anthropic.claude-opus-4-8-v1:0", svcLevel: llm.ThinkingLevelMedium, wantType: "adaptive", wantEffort: "medium"}, {name: "budget default medium", model: Claude45Sonnet, svcLevel: llm.ThinkingLevelMedium, wantType: "enabled", wantBudgetGreat: 8192}, {name: "budget req low", model: Claude45Sonnet, svcLevel: llm.ThinkingLevelMedium, reqLevel: llm.ThinkingLevelLow, wantType: "enabled", wantBudgetGreat: 2048}, {name: "budget req off", model: Claude45Sonnet, svcLevel: llm.ThinkingLevelMedium, reqLevel: llm.ThinkingLevelOff, wantType: ""},