fix(thinking): disable thinking for unsupported Ollama models (#1376)

* fix(thinking): disable thinking for unsupported Ollama models

Fixes #1371

- Adds central `shouldUseThinkingForModel` gate that checks the actual route and model descriptor.
- Disables thinking parameters for the Ollama route when the model is unknown or unsupported.
- Updates API requests to evaluate the actual retry model against the capability gate instead of the initial request model.
- Adds targeted tests for Ollama logic and shim payloads.

* test(thinking): cover Ollama thinking gate
This commit is contained in:
chioarub
2026-05-27 07:55:25 +08:00
committed by GitHub
parent 670744fc70
commit 8513178934
3 changed files with 42 additions and 10 deletions
+5 -7
View File
@@ -181,7 +181,7 @@ import { calculateUSDCost } from 'src/utils/modelCost.js'
import { endQueryProfile, queryCheckpoint } from 'src/utils/queryProfiler.js'
import {
modelSupportsAdaptiveThinking,
modelSupportsThinking,
shouldUseThinkingForModel,
type ThinkingConfig,
} from 'src/utils/thinking.js'
import {
@@ -1604,18 +1604,16 @@ async function* queryModel(
options.maxOutputTokensOverride ||
getMaxOutputTokensForModel(options.model)
const hasThinking =
thinkingConfig.type !== 'disabled' &&
!isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_THINKING)
const hasThinking = shouldUseThinkingForModel(retryContext.model, thinkingConfig)
let thinking: BetaMessageStreamParams['thinking'] | undefined = undefined
// IMPORTANT: Do not change the adaptive-vs-budget thinking selection below
// without notifying the model launch DRI and research. This is a sensitive
// setting that can greatly affect model quality and bashing.
if (hasThinking && modelSupportsThinking(options.model)) {
if (hasThinking) {
if (
!isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING) &&
modelSupportsAdaptiveThinking(options.model)
modelSupportsAdaptiveThinking(retryContext.model)
) {
// For models that support adaptive thinking, always use adaptive
// thinking without a budget.
@@ -1625,7 +1623,7 @@ async function* queryModel(
} else {
// For models that do not support adaptive thinking, use the default
// thinking budget unless explicitly specified.
let thinkingBudget = getMaxThinkingTokensForModel(options.model)
let thinkingBudget = getMaxThinkingTokensForModel(retryContext.model)
if (
thinkingConfig.type === 'enabled' &&
thinkingConfig.budgetTokens !== undefined
+21 -3
View File
@@ -25,6 +25,7 @@ const ENV_KEYS = [
'ANTHROPIC_DEFAULT_SONNET_MODEL_SUPPORTED_CAPABILITIES',
'ANTHROPIC_DEFAULT_HAIKU_MODEL',
'ANTHROPIC_DEFAULT_HAIKU_MODEL_SUPPORTED_CAPABILITIES',
'CLAUDE_CODE_DISABLE_THINKING',
'USER_TYPE',
]
@@ -57,9 +58,13 @@ afterEach(() => {
async function importFreshThinkingModule() {
mock.restore()
mock.module('./model/providers.js', () => ({
getAPIProvider: () => 'openai',
}))
const originalProviders = await import('./model/providers.js')
mock.module('./model/providers.js', () => {
return {
...originalProviders,
getAPIProvider: () => 'openai',
}
})
const nonce = `${Date.now()}-${Math.random()}`
return import(`./thinking.js?ts=${nonce}`)
}
@@ -109,3 +114,16 @@ describe('modelSupportsThinking — Z.AI GLM', () => {
expect(modelSupportsThinking('GLM-5.1')).toBe(true)
})
})
describe('shouldUseThinkingForModel — Ollama', () => {
test('does not use thinking for Ollama models when app-level thinking is enabled', async () => {
process.env.CLAUDE_CODE_USE_OPENAI = '1'
process.env.OPENAI_BASE_URL = 'http://localhost:11434/v1'
const { shouldUseThinkingForModel } = await importFreshThinkingModule()
const enabledThinking = { type: 'enabled' as const, budgetTokens: 1024 }
expect(shouldUseThinkingForModel('llama3.1:8b', enabledThinking)).toBe(false)
// Covers catalog-missing local names that would otherwise match Claude 4 heuristics.
expect(shouldUseThinkingForModel('claude-sonnet-4-local', enabledThinking)).toBe(false)
})
})
+16
View File
@@ -12,6 +12,7 @@ import { resolveAntModel } from './model/antModels.js'
import { get3PModelCapabilityOverride } from './model/modelSupportOverrides.js'
import { getAPIProvider } from './model/providers.js'
import { getSettingsWithErrors } from './settings/settings.js'
import { isEnvTruthy } from './envUtils.js'
export type ThinkingConfig =
| { type: 'adaptive' }
@@ -143,6 +144,10 @@ export function modelSupportsThinking(model: string): boolean {
if (descriptorSupportsThinking !== undefined) {
return descriptorSupportsThinking
}
const routeId = resolveActiveRouteIdFromEnv(process.env)
if (routeId === 'ollama') {
return false
}
}
// 3P (Bedrock/Vertex): only Opus 4+ and Sonnet 4+
return canonical.includes('sonnet-4') || canonical.includes('opus-4')
@@ -199,3 +204,14 @@ export function shouldEnableThinkingByDefault(): boolean {
// Enable thinking by default unless explicitly disabled.
return true
}
export function shouldUseThinkingForModel(
model: string,
thinkingConfig: ThinkingConfig,
): boolean {
return (
thinkingConfig.type !== 'disabled' &&
!isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_THINKING) &&
modelSupportsThinking(model)
)
}