diff --git a/src/services/compact/compact.ts b/src/services/compact/compact.ts index f46194ffb..a0d5903d1 100644 --- a/src/services/compact/compact.ts +++ b/src/services/compact/compact.ts @@ -103,6 +103,7 @@ import { getMaxOutputTokensForModel, queryModelWithStreaming, } from '../api/claude.js' +import { getCompactModel } from '../../utils/model/model.js' import { getPromptTooLongTokenGap, PROMPT_TOO_LONG_ERROR_MESSAGE, @@ -1314,13 +1315,13 @@ async function streamCompactSummary({ const appState = context.getAppState() return appState.toolPermissionContext }, - model: context.options.mainLoopModel, + model: getCompactModel(context.options.mainLoopModel), toolChoice: undefined, isNonInteractiveSession: context.options.isNonInteractiveSession, hasAppendSystemPrompt: !!context.options.appendSystemPrompt, maxOutputTokensOverride: Math.min( COMPACT_MAX_OUTPUT_TOKENS, - getMaxOutputTokensForModel(context.options.mainLoopModel), + getMaxOutputTokensForModel(getCompactModel(context.options.mainLoopModel)), ), querySource: 'compact', agents: context.options.agentDefinitions.activeAgents, diff --git a/src/utils/model/model.ts b/src/utils/model/model.ts index 791daeb6b..1aa3c79a6 100644 --- a/src/utils/model/model.ts +++ b/src/utils/model/model.ts @@ -47,6 +47,28 @@ export function getSmallFastModel(): ModelName { return process.env.ANTHROPIC_SMALL_FAST_MODEL || getDefaultHaikuModel() } +/** + * Get the model to use for compaction, one tier below the current model. + * Opus → Sonnet, Sonnet → Haiku, Haiku → Haiku (already lowest). + * Preserves [1m] suffix only if the target family supports it (sonnet/opus). + */ +export function getCompactModel(model: ModelName): ModelName { + const has1m = model.endsWith('[1m]') + const baseModel = has1m ? model.slice(0, -4) : model + const canonical = getCanonicalName(baseModel) + + // Opus family → Sonnet + if (canonical.includes('opus')) { + return getDefaultSonnetModel() + } + // Sonnet family → Haiku + if (canonical.includes('sonnet')) { + return getDefaultHaikuModel() + } + // Haiku or unknown → Haiku (lowest tier) + return getDefaultHaikuModel() +} + export function isNonCustomOpusModel(model: ModelName): boolean { return ( model === getModelStrings().opus40 ||