{"version":3,"file":"model-categories.d.ts","sourceRoot":"","sources":["../../src/core/model-categories.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEtD,iCAAiC;AACjC,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,UAAU,GAAG,SAAS,CAAC;AAE5D,kDAAkD;AAClD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,aAAa,CAErE;AAiBD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,4BAA4B,CAC3C,eAAe,EAAE,SAAS,KAAK,CAAC,GAAG,CAAC,EAAE,EACtC,QAAQ,CAAC,EAAE,QAAQ,GACjB;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CA8BxD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,oBAAoB,CACnC,QAAQ,EAAE,aAAa,EACvB,QAAQ,CAAC,EAAE,QAAQ,EACnB,eAAe,CAAC,EAAE,SAAS,KAAK,CAAC,GAAG,CAAC,EAAE,GACrC,MAAM,GAAG,SAAS,CAOpB;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACpC,KAAK,EAAE,MAAM,EACb,QAAQ,CAAC,EAAE,QAAQ,EACnB,eAAe,CAAC,EAAE,SAAS,KAAK,CAAC,GAAG,CAAC,EAAE,GACrC,MAAM,GAAG,SAAS,CAKpB","sourcesContent":["/**\n * Model categories for subagent model selection.\n *\n * A category (`fast` | `standard` | `capable`) is a provider-neutral indirection\n * that maps to an explicit model id. Precedence:\n *\n *   1. An explicit `settings.modelCategories[category]` always wins.\n *   2. Otherwise, when a set of available models is supplied, the category\n *      resolves to a default *derived* from those models (see\n *      `deriveDefaultModelCategories`) — never a hardcoded provider/model id.\n *   3. Otherwise it resolves to `undefined`, which callers treat as \"no override\"\n *      and fall back to the agent's or parent's default model.\n *\n * No concrete model names are baked in here, so the feature never assumes a\n * particular provider.\n */\n\nimport type { Api, Model } from \"@kolisachint/hoocode-ai\";\nimport type { Settings } from \"./settings-manager.js\";\n\n/** Valid model category names */\nexport type ModelCategory = \"fast\" | \"standard\" | \"capable\";\n\n/** Check if a string is a valid model category */\nexport function isModelCategory(value: string): value is ModelCategory {\n\treturn value === \"fast\" || value === \"standard\" || value === \"capable\";\n}\n\n/** A category maps to a concrete model reference in `<provider>/<id>` form. */\nfunction modelRef(model: Model<Api>): string {\n\treturn `${model.provider}/${model.id}`;\n}\n\n/** Combined per-token price (input + output), used as a capability/cost proxy. */\nfunction combinedPrice(model: Model<Api>): number {\n\treturn model.cost.input + model.cost.output;\n}\n\n/** Deterministic tie-break so identical available sets always yield the same pick. */\nfunction compareById(a: Model<Api>, b: Model<Api>): number {\n\treturn a.id < b.id ? -1 : a.id > b.id ? 1 : 0;\n}\n\n/**\n * Derive a default model for each tier from the user's available models, used\n * only when a tier is not explicitly configured in `settings.modelCategories`.\n *\n * The rule is deliberately transparent (config, not magic) and provider-neutral:\n * nothing is hardcoded, everything is derived from what the user actually has.\n *\n *   1. `capable` = the user's PRIMARY model: the configured default\n *      (`settings.defaultProvider`/`defaultModel`) when it is in the available\n *      set, otherwise the most capable available model, using combined token\n *      price (input + output cost) as a stand-in for capability.\n *   2. `fast` and `standard` are the cheapest and the upper-median of every\n *      available model priced at or below `capable`, ordered cheapest-first.\n *      Clamping to `capable`'s price keeps the tiers monotonic\n *      (`fast` <= `standard` <= `capable`), and drawing from the whole available\n *      set — not just `capable`'s own provider — still yields a genuinely cheap\n *      `fast` when the primary model's provider has nothing cheaper (a strict\n *      same-provider rule collapses every tier onto a single-model provider).\n *\n * Every ordering breaks ties on a fixed key (context window, then id) so the same\n * available set always yields the same mapping. An empty available set yields an\n * empty map (every tier resolves to `undefined`, i.e. inherit the parent model).\n */\nexport function deriveDefaultModelCategories(\n\tavailableModels: readonly Model<Api>[],\n\tsettings?: Settings,\n): { fast?: string; standard?: string; capable?: string } {\n\tif (availableModels.length === 0) return {};\n\n\t// capable = primary model.\n\tconst configuredDefault =\n\t\tsettings?.defaultProvider && settings?.defaultModel\n\t\t\t? availableModels.find((m) => m.provider === settings.defaultProvider && m.id === settings.defaultModel)\n\t\t\t: undefined;\n\t// Most capable = highest combined price; ties -> larger context window, then id.\n\tconst capable =\n\t\tconfiguredDefault ??\n\t\t[...availableModels].sort(\n\t\t\t(a, b) => combinedPrice(b) - combinedPrice(a) || b.contextWindow - a.contextWindow || compareById(a, b),\n\t\t)[0];\n\n\t// fast/standard: every model priced at or below capable, cheapest-first.\n\t// `capable` is always in this set (its price <= its own price), so it never empties.\n\tconst capablePrice = combinedPrice(capable);\n\tconst candidates = availableModels\n\t\t.filter((m) => combinedPrice(m) <= capablePrice)\n\t\t.sort((a, b) => combinedPrice(a) - combinedPrice(b) || a.contextWindow - b.contextWindow || compareById(a, b));\n\n\tconst fast = candidates[0] ?? capable;\n\tconst standard = candidates[Math.floor(candidates.length / 2)] ?? capable;\n\n\treturn {\n\t\tfast: modelRef(fast),\n\t\tstandard: modelRef(standard),\n\t\tcapable: modelRef(capable),\n\t};\n}\n\n/**\n * Resolve a model category to a model id. An explicit\n * `settings.modelCategories[category]` wins; otherwise a default is derived from\n * `availableModels` (provider-neutral, see `deriveDefaultModelCategories`); when\n * neither applies the category resolves to `undefined` (a no-op, so the caller\n * keeps its existing model).\n *\n * @param category - The model category (fast, standard, capable)\n * @param settings - The current settings (may contain modelCategories config)\n * @param availableModels - The user's available/configured models to derive from\n */\nexport function resolveModelCategory(\n\tcategory: ModelCategory,\n\tsettings?: Settings,\n\tavailableModels?: readonly Model<Api>[],\n): string | undefined {\n\tconst explicit = settings?.modelCategories?.[category];\n\tif (explicit) return explicit;\n\tif (availableModels && availableModels.length > 0) {\n\t\treturn deriveDefaultModelCategories(availableModels, settings)[category];\n\t}\n\treturn undefined;\n}\n\n/**\n * Resolve a model string that might be a category reference. A category resolves\n * to its configured or derived model id (or `undefined` when neither applies);\n * any other string is already a concrete model id or alias and is returned as-is.\n *\n * @param model - The model string (could be a category, alias, or full model ID)\n * @param settings - The current settings (may contain modelCategories config)\n * @param availableModels - The user's available/configured models to derive from\n */\nexport function resolveModelReference(\n\tmodel: string,\n\tsettings?: Settings,\n\tavailableModels?: readonly Model<Api>[],\n): string | undefined {\n\tif (isModelCategory(model)) {\n\t\treturn resolveModelCategory(model, settings, availableModels);\n\t}\n\treturn model;\n}\n"]}