From cd39df154c517ceb3d83d28771c626ae1ee551c8 Mon Sep 17 00:00:00 2001 From: Girish Kanjiyani <88148445+girish-kanjiyani7@users.noreply.github.com> Date: Wed, 22 Jul 2026 12:17:11 -0400 Subject: [PATCH] fix(suggest-compact): recognize large-window model families without a [1m] marker (#2468) * fix(suggest-compact): recognize large-window model families without a [1m] marker resolveContextWindowTokens() only detected a 1M window via the env override, the [1m] model-id marker, or observed tokens already above 200k. Large-window models whose ids carry none of these (e.g. claude-fable-5) were misclassified as 200k windows, overstating context usage ~5x in the compact suggestion. Add a known-model-family substring table (claude-fable-5, claude-mythos-5) checked after the env override and [1m] marker and before the token-count heuristic. Env overrides still win, and unknown model ids still fall back to the 200k default. Closes #2461 * fix(suggest-compact): anchor known-model-family match at a token boundary Unanchored substring matching would misclassify a hypothetical smaller tier sharing a known family prefix (e.g. claude-fable-5-mini) as a 1M window. Require the family id to end at a token boundary: end of id, a delimiter, or a dated/versioned suffix (-20260115). Alphanumeric continuations and letter suffixes no longer match. Addresses CodeRabbit/Greptile review on #2468 --- scripts/lib/transcript-context.js | 42 ++++++++++++++++++++++++++-- tests/lib/transcript-context.test.js | 30 ++++++++++++++++++++ 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/scripts/lib/transcript-context.js b/scripts/lib/transcript-context.js index c499523f7..d8df7411a 100644 --- a/scripts/lib/transcript-context.js +++ b/scripts/lib/transcript-context.js @@ -28,6 +28,32 @@ const DEFAULT_TRANSCRIPT_TAIL_BYTES = 256 * 1024; const MAX_TOKEN_SETTING = 10000000; const LARGE_WINDOW_MODEL_MARKER = '[1m]'; +// Known large-window model families whose ids carry no `[1m]` marker (#2461). +// Matched boundary-aware against the model id — covers dated/region-prefixed +// variants (e.g. `us.anthropic.claude-fable-5-20260115-v1:0`) without matching +// hypothetical smaller tiers sharing the prefix (e.g. `claude-fable-5-mini`). +// Checked in order, first match wins. Best-effort and expected to lag new +// releases; the env override remains the escape hatch for unlisted models. +const KNOWN_MODEL_WINDOW_TOKENS = [ + ['claude-fable-5', LARGE_CONTEXT_WINDOW_TOKENS], + ['claude-mythos-5', LARGE_CONTEXT_WINDOW_TOKENS] +]; + +/** + * True when `model` contains `familyId` ending at a token boundary: end of id, + * a delimiter (`[`, `:`, `.`), or a dated/versioned suffix (`-20260115`). + * Alphanumeric continuations and letter suffixes (`-mini`) are different + * models, possibly with smaller windows, and must not match. + */ +function isKnownModelFamilyMatch(model, familyId) { + const start = model.indexOf(familyId); + if (start === -1) { + return false; + } + const rest = model.slice(start + familyId.length); + return !/^[A-Za-z0-9]/.test(rest) && !/^-[A-Za-z]/.test(rest); +} + /** * Read the trailing `tailBytes` of a file as UTF-8. * Returns null when the file is missing or unreadable. @@ -132,9 +158,10 @@ function readLatestContextTokens(transcriptPath, options = {}) { /** * Detect the context window size for a turn. - * 1M when the model id carries the `[1m]` marker, or when the observed token - * count already exceeds the standard 200k window (covers logs that drop the - * suffix); otherwise the standard 200k window. + * 1M when the model id carries the `[1m]` marker, matches a known large-window + * model family, or when the observed token count already exceeds the standard + * 200k window (covers logs that drop the suffix); otherwise the standard 200k + * window. */ function resolveContextWindowTokens(tokens, model) { // Explicit window override wins: 400k models (e.g. Opus 4.x) match neither the @@ -150,6 +177,15 @@ function resolveContextWindowTokens(tokens, model) { return LARGE_CONTEXT_WINDOW_TOKENS; } + // Large-window model families without a [1m] marker fall through the checks + // above and would be misreported against the 200k default (#2461). + if (typeof model === 'string') { + const known = KNOWN_MODEL_WINDOW_TOKENS.find(([familyId]) => isKnownModelFamilyMatch(model, familyId)); + if (known) { + return known[1]; + } + } + if (Number.isFinite(tokens) && tokens > STANDARD_CONTEXT_WINDOW_TOKENS) { return LARGE_CONTEXT_WINDOW_TOKENS; } diff --git a/tests/lib/transcript-context.test.js b/tests/lib/transcript-context.test.js index 96615fe44..4f95c62b4 100644 --- a/tests/lib/transcript-context.test.js +++ b/tests/lib/transcript-context.test.js @@ -180,6 +180,36 @@ test('detects a 1M window when observed tokens exceed 200k (marker dropped)', () assert.strictEqual(resolveContextWindowTokens(220000, 'claude-opus-4-5'), LARGE_CONTEXT_WINDOW_TOKENS); }); +test('recognizes claude-fable-5 as a 1M window without a [1m] marker or 200k+ tokens (#2461)', () => { + assert.strictEqual(resolveContextWindowTokens(187000, 'claude-fable-5'), LARGE_CONTEXT_WINDOW_TOKENS); +}); + +test('recognizes claude-mythos-5 as a 1M window from the known-model table (#2461)', () => { + assert.strictEqual(resolveContextWindowTokens(50000, 'claude-mythos-5'), LARGE_CONTEXT_WINDOW_TOKENS); +}); + +test('recognizes dated/prefixed variants of known large-window model ids (#2461)', () => { + assert.strictEqual(resolveContextWindowTokens(50000, 'us.anthropic.claude-fable-5-20260115-v1:0'), LARGE_CONTEXT_WINDOW_TOKENS); +}); + +test('env window override still wins over the known-model table (#2461)', () => { + process.env.ECC_CONTEXT_WINDOW_TOKENS = '400000'; + try { + assert.strictEqual(resolveContextWindowTokens(50000, 'claude-fable-5'), 400000); + } finally { + delete process.env.ECC_CONTEXT_WINDOW_TOKENS; + } +}); + +test('does not match hypothetical smaller tiers sharing a known-family prefix (#2461)', () => { + assert.strictEqual(resolveContextWindowTokens(50000, 'claude-fable-5-mini'), STANDARD_CONTEXT_WINDOW_TOKENS); + assert.strictEqual(resolveContextWindowTokens(50000, 'claude-mythos-5-haiku-20260201'), STANDARD_CONTEXT_WINDOW_TOKENS); +}); + +test('keeps the 200k default for unknown model ids at low token counts (no false positives, #2461)', () => { + assert.strictEqual(resolveContextWindowTokens(187000, 'claude-haiku-4-5-20251001'), STANDARD_CONTEXT_WINDOW_TOKENS); +}); + test('treats an empty model id as standard window', () => { assert.strictEqual(resolveContextWindowTokens(100000, ''), STANDARD_CONTEXT_WINDOW_TOKENS); });