/** * Active-model resolution for the `--context-window auto` probe (B8b). * * Reads the configured model the way Claude Code itself resolves it, so the * window probe (context-window.mjs `modelToContextWindow`) sees the real model: * 1. the shell `ANTHROPIC_MODEL` override (applies to the launched session); * 2. otherwise the settings cascade `model` field — user `~/.claude`, then * project `.claude`, then project-local `.claude` (local > project > user). * * Reads the cascade files directly (like isBundledSkillsDisabled) rather than via * config-discovery classification, and takes an injectable `env` so it is * deterministic and hermetic under the test HOME. Returns null when no model is * pinned anywhere — the honest signal that `auto` must fall back to advisory. * * Zero external dependencies (repo invariant). */ import { join } from 'node:path'; import { readTextFile } from './file-discovery.mjs'; import { parseJson } from './yaml-parser.mjs'; /** * @param {string|null|undefined} projectPath - project root, to also read project + local settings * @param {{ env?: Record }} [opts] * @returns {Promise} the resolved model id/alias, or null if unset */ export async function resolveActiveModel(projectPath, { env = process.env } = {}) { // 1. Shell ANTHROPIC_MODEL overrides settings (CC: applies to the session). const envModel = typeof env?.ANTHROPIC_MODEL === 'string' ? env.ANTHROPIC_MODEL.trim() : ''; if (envModel) return envModel; // 2. Settings cascade: user -> project -> project-local, later wins. const home = (env && (env.HOME || env.USERPROFILE)) || ''; const candidates = []; if (home) candidates.push(join(home, '.claude', 'settings.json')); if (projectPath) { candidates.push(join(projectPath, '.claude', 'settings.json')); candidates.push(join(projectPath, '.claude', 'settings.local.json')); } let model = null; for (const p of candidates) { const content = await readTextFile(p); if (!content) continue; const parsed = parseJson(content); if (parsed && typeof parsed.model === 'string' && parsed.model.trim()) { model = parsed.model.trim(); } } return model; }