/**
* Model Manager — CLI model selection and cost tracking
*
* Extracted from agent-loop.ts for single-responsibility.
* All consumers should import from agent-loop.ts (re-export facade).
*/
import { MODEL_MAP } from "../../shared/constants.js";
import { estimateCostUsd as coreEstimateCostUsd } from "../../shared/agent-core.js";
import { loadConfig, updateConfig } from "./config-store.js";
/** CLI-only: current model ID for the active session */
let activeModel = "claude-opus-4-6";
// Load persisted default model on import
try {
const cfg = loadConfig();
if (cfg.default_model) {
const id = MODEL_MAP[cfg.default_model.toLowerCase()];
if (id) activeModel = id;
}
} catch { /* use default */ }
/** Reverse map: full model ID → short key */
const REVERSE_MAP: Record<string, string> = {};
for (const [key, id] of Object.entries(MODEL_MAP)) {
REVERSE_MAP[id] = key;
}
export function setModel(name: string, persist = true): { success: boolean; model: string; error?: string } {
// Try direct key match first (e.g. "opus", "bedrock-sonnet", "gemini-pro")
const directId = MODEL_MAP[name.toLowerCase()];
if (directId) {
activeModel = directId;
if (persist) try { updateConfig({ default_model: name.toLowerCase() }); } catch { /* best effort */ }
return { success: true, model: activeModel };
}
// Legacy: strip "claude-" prefix for backward compat (e.g. "claude-opus" → "opus")
const legacy = name.toLowerCase().replace(/^claude-?/, "").replace(/-.*/, "");
const legacyId = MODEL_MAP[legacy];
if (legacyId) {
activeModel = legacyId;
if (persist) try { updateConfig({ default_model: legacy }); } catch { /* best effort */ }
return { success: true, model: activeModel };
}
return { success: false, model: activeModel, error: `Unknown model: "${name}". Valid: ${Object.keys(MODEL_MAP).join(", ")}` };
}
/** Internal: set model by full ID (used for restoring after fallback) */
export function setModelById(modelId: string): void {
activeModel = modelId;
}
export function getModel(): string {
return activeModel;
}
export function getModelShortName(): string {
if (activeModel === "auto") return "auto";
return REVERSE_MAP[activeModel] || "sonnet";
}
/** Check if the current model is set to auto-routing */
export function isAutoRouting(): boolean {
return activeModel === "auto";
}
export function estimateCostUsd(inputTokens: number, outputTokens: number, model?: string, thinkingTokens = 0, cacheReadTokens = 0, cacheCreationTokens = 0): number {
return coreEstimateCostUsd(inputTokens, outputTokens, model || activeModel, thinkingTokens, cacheReadTokens, cacheCreationTokens);
}