From e458d6391db3932cce903248bb856fdf0fc5730b Mon Sep 17 00:00:00 2001 From: claude-code-best Date: Tue, 14 Apr 2026 09:56:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20compact=20=E6=A8=A1=E5=9E=8B=E9=99=8D?= =?UTF-8?q?=E7=BA=A7=E4=B8=BA=20-1=20=E6=A8=A1=E5=BC=8F=EF=BC=88Opus?= =?UTF-8?q?=E2=86=92Sonnet,=20Sonnet=E2=86=92Haiku=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- src/services/compact/compact.ts | 5 +++-- src/utils/model/model.ts | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) 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 ||