// modules/core/model-profiles.js — per-model-class tool budgets. // class is picked by an ordered rule list; the FIRST match wins, so keep specific ids above size rules. const MODEL_CLASSES = { tiny: { ctx: 8192, readPageChars: 4000, toolResultChars: 3000, toolContextBudgetChars: 12000, fetchChars: 3000 }, small: { ctx: 16384, readPageChars: 8000, toolResultChars: 4000, toolContextBudgetChars: 16000, fetchChars: 4000 }, mid: { ctx: 32768, readPageChars: 16000, toolResultChars: 6000, toolContextBudgetChars: 24000, fetchChars: 6000 }, large: { ctx: 65536, readPageChars: 32000, toolResultChars: 12000, toolContextBudgetChars: 48000, fetchChars: 12000 }, }; // Ordered: first match wins. Only ids we know; everything else falls through to the size parse. const MODEL_IDS = { 'gemma4:31b-cloud': 'large', 'qwen3:0.6b': 'tiny', 'gemma2:2b': 'tiny', 'llama3.1:8b': 'small', 'gemma4:31b': 'mid', }; // "235b" -> large, "31b" -> mid, "8b" -> small, "0.6b"/"2b" -> tiny function classFor(modelId) { const id = String(modelId || '').toLowerCase(); if (!id) return 'mid'; if (MODEL_IDS[id]) return MODEL_IDS[id]; if (id.endsWith('-cloud') || id.includes(':cloud')) return 'large'; const m = id.match(/(\d+(?:\.\d+)?)\s*b\b/); if (!m) return 'mid'; const p = parseFloat(m[1]); if (p <= 3) return 'tiny'; if (p <= 9) return 'small'; if (p <= 32) return 'mid'; return 'large'; } function profileFor(modelId) { return MODEL_CLASSES[classFor(modelId)] || MODEL_CLASSES.mid; } // The bundle concatenates this file FIRST, so this binding exists before any module method runs. // module-aiassist/ollama-manager resolve budgets through it: a require() there resolved relative // to whoever called them and always returned null (found by the live ai-config probe 2026-11-02). const MODEL_PROFILES = { MODEL_CLASSES, classFor, profileFor }; if (typeof module !== 'undefined' && module.exports) module.exports = { MODEL_CLASSES, classFor, profileFor, MODEL_PROFILES };