/** * Context-window constants — single source of truth. * * Several Claude Code budgets scale with the model's context window: * - the skill listing is allotted ~2% of context (CC 2.1.32, changelog L2860); * - the "CLAUDE.md is too long" warning threshold scales with it (CC 2.1.169). * * We cannot observe the user's actual context window, so scanners anchor on a * conservative 200k window (the smallest common size — it fires earliest, the * safe default when the window is unknown) and disclose the relaxed 1M figure. * * Zero external dependencies. */ // Conservative anchor: the smallest common context window. Budgets anchored // here fire earliest, which is the safe default when the window is unknown. export const CONTEXT_WINDOW_ANCHOR = 200_000; // Large context window (Opus/Sonnet 1M tier). Used to disclose how a budget // relaxes on large-context models. export const LARGE_CONTEXT_WINDOW = 1_000_000; // How much larger the 1M window is than the 200k anchor (= 5). A budget that // scales linearly with the window relaxes by this factor at 1M. export const LARGE_CONTEXT_SCALE = LARGE_CONTEXT_WINDOW / CONTEXT_WINDOW_ANCHOR; // Dependency-free thousands separator (repo invariant: zero external deps). export const withCommas = (n) => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ','); // Model families whose context window is the large (1M) tier. Verified June 2026 // (platform.claude.com models overview): Fable 5, Opus 4.8/4.7/4.6 and Sonnet 4.6 // all run a 1M context window. Matched by substring so dated IDs // (claude-opus-4-8-20260528) and provider-prefixed IDs // (us.anthropic.claude-opus-4-8) resolve too. Models we cannot confirm (e.g. // Haiku, older 200k-era IDs) are deliberately left out: the caller then keeps the // conservative anchor rather than guess a relaxed budget. export const LARGE_CONTEXT_MODEL_IDS = [ 'claude-fable-5', 'claude-opus-4-8', 'claude-opus-4-7', 'claude-opus-4-6', 'claude-sonnet-4-6', ]; // Short aliases Claude Code accepts in the `model` setting / ANTHROPIC_MODEL that // currently resolve to a 1M-tier model (`opusplan` plans on an Opus-tier model). export const LARGE_CONTEXT_MODEL_ALIASES = new Set(['opus', 'sonnet', 'fable', 'opusplan']); /** * Map a configured model id/alias to its context window, or null when we cannot * confirm it. Pure: no IO. Used by the `--context-window auto` probe (B8b) so * known 1M-tier models calibrate budgets instead of falling back to the * conservative advisory anchor. * * @param {string} modelId - e.g. "claude-opus-4-8[1m]", "claude-sonnet-4-6", "opus" * @returns {number|null} the context window, or null if unrecognized */ export function modelToContextWindow(modelId) { if (typeof modelId !== 'string') return null; const id = modelId.trim().toLowerCase(); if (!id) return null; // Explicit tier tag wins — the running session model surfaces as e.g. // "claude-opus-4-8[1m]". This is the strongest, most future-proof signal. if (id.includes('[1m]')) return LARGE_CONTEXT_WINDOW; // Known 1M-tier families (substring → tolerant of date/provider-prefix variants). for (const fam of LARGE_CONTEXT_MODEL_IDS) { if (id.includes(fam)) return LARGE_CONTEXT_WINDOW; } // Short aliases. if (LARGE_CONTEXT_MODEL_ALIASES.has(id)) return LARGE_CONTEXT_WINDOW; // Unknown: cannot confirm the window — keep the conservative anchor (null). return null; } /** * @typedef {object} ResolvedContextWindow * @property {number} window - the context window budgets calibrate against * @property {boolean} advisory - true when the window is unknown: keep the anchor * but downgrade budget findings to info instead of * firing them as a breach * @property {'default'|'explicit'|'auto-probed'|'auto-unresolved'} source */ /** * Resolve the raw `--context-window` CLI value into a window + advisory flag. * * Design (B8): the DEFAULT (no flag) is byte-identical to the pre-B8 behavior — * the conservative 200k anchor at full severity. Only an explicit value changes * calibration. `auto` asks the tool to figure out the window. * * B8b: `auto` now probes the configured model (`opts.model`, resolved from the * settings cascade / ANTHROPIC_MODEL by the orchestrator). A recognized 1M-tier * model calibrates to its window (source `auto-probed`, not advisory). When the * model is unknown or unpinned, it keeps the conservative anchor but marks the * result advisory (source `auto-unresolved`) so SKL/CML downgrade their budget * findings to info rather than "crying wolf" on a window we cannot confirm. * * @param {string|number|null|undefined} arg * @param {{ model?: string|null }} [opts] - probe input for `auto` (ignored on the * default/explicit paths, which stay byte-stable). * @returns {ResolvedContextWindow} */ export function resolveContextWindow(arg, opts = {}) { if (arg == null) { return { window: CONTEXT_WINDOW_ANCHOR, advisory: false, source: 'default' }; } if (String(arg).trim().toLowerCase() === 'auto') { const probed = modelToContextWindow(opts.model); if (probed) { return { window: probed, advisory: false, source: 'auto-probed' }; } return { window: CONTEXT_WINDOW_ANCHOR, advisory: true, source: 'auto-unresolved' }; } const n = typeof arg === 'number' ? arg : parseInt(String(arg).trim(), 10); if (Number.isFinite(n) && n > 0) { return { window: n, advisory: false, source: 'explicit' }; } // Unparseable / non-positive: fall back to the conservative default (no advisory). return { window: CONTEXT_WINDOW_ANCHOR, advisory: false, source: 'default' }; } /** * Scale a 200k-anchored budget to a given context window. Linear in the window, * so it is the identity at the anchor (keeps the default byte-stable). * * @param {number} anchorValue - the budget/threshold defined at the 200k anchor * @param {number} window - the target context window * @returns {number} */ export function scaleForWindow(anchorValue, window) { return Math.round(anchorValue * (window / CONTEXT_WINDOW_ANCHOR)); }