/** * Table-first catalog read chokepoint — `resolveCatalogEntry()` (E8 · T11737). * * ## The single read chokepoint * * The provider+model catalog now has THREE physical surfaces, in strict priority: * * 1. **`models_catalog` DB table** (cleo-global · T11733) — the SSoT. Seeded * from the shipped offline `curated-catalog.json` (T11734). * 2. **disk JSON cache** (`llm-catalog/` · T9314 · `catalog-cache.ts`) — the * offline/degraded fallback MIRROR (the most-recent models.dev snapshot). * 3. **shipped seed** (`curated-catalog.json`) — the last-resort floor that * always ships in the package, so a fresh/offline install always resolves. * * Every catalog reader (the resolver default — T11944; future capability lookups) * funnels through {@link resolveCatalogEntry} so the table and the cache can NEVER * silently diverge: one chokepoint, table-or-fallback, in that order. * * ## Offline-first (degrade) — NO network at read time * * This module NEVER fetches. The network refresh op (`cleo llm refresh-catalog`) * is a SEPARATE leaf. `resolveCatalogEntry()` reads the DB table first; when the * table is empty/unseeded it falls back to the disk-cache snapshot, then to the * shipped seed. A fresh install with no DB rows and no disk cache still resolves * from the bundled `curated-catalog.json`. * * @module llm/catalog-resolver * @task T11737 * @epic T11694 (E8-CATALOG-CURATION) */ import type { CuratedCatalog } from '@cleocode/contracts'; import { type CleoGlobalDb } from '../store/dual-scope-db.js'; /** * A normalized catalog entry returned by {@link resolveCatalogEntry}. * * The shape is the union of the fields the resolver-default path (T11944) and the * capability lookups actually consume — it is provenance-agnostic so callers do * NOT care whether the entry came from the table, the disk cache, or the seed. */ export interface ResolvedCatalogEntry { /** Model id (e.g. `claude-haiku-4-5-20251001`). */ readonly id: string; /** Provider key (models.dev id, e.g. `anthropic`). */ readonly providerId: string; /** ISO release date `YYYY-MM-DD`. The SSoT sort key. */ readonly releaseDate: string; /** Max context window (tokens), when known. */ readonly contextLimit: number | null; /** Where this entry was resolved from (provenance). */ readonly source: CatalogResolutionSource; } /** Provenance of a {@link ResolvedCatalogEntry} (table-first, then fallbacks). */ export type CatalogResolutionSource = 'table' | 'disk-cache' | 'shipped-seed'; /** Injectable seam for {@link resolveCatalogEntry} (tests pass a temp-DB handle). */ export interface CatalogResolverDeps { /** * An already-open global Drizzle handle. When omitted the resolver opens via * {@link openDualScopeDb}`('global')`. Tests pass a temp-DB handle (opened via * {@link openCatalogAtPath}) to stay off `.cleo/*.db`. */ readonly db?: CleoGlobalDb; /** * Disk-cache directory override (tests). Defaults to {@link getCatalogDir}. */ readonly cacheDir?: string; } /** * Open the global catalog handle at an EXPLICIT path (test seam). * * Production callers MUST use {@link openDualScopeDb}`('global')`. This path-aware * variant exists so tests open a temp-dir `cleo.db` — never `.cleo/*.db`. * * @param dbPath - Absolute path to the temp `cleo.db`. * @task T11737 */ export declare function openCatalogAtPath(dbPath: string): Promise; /** * Load the shipped seed (`curated-catalog.json`) from disk — no network. * * Read once and cached for the process lifetime. Returns `null` only if the * bundled file is missing/unparseable (never expected — it ships in the package). * * @internal */ export declare function loadShippedSeed(): CuratedCatalog | null; /** * Resolve the latest-released catalog entry for a provider — the SINGLE table-first * read chokepoint (T11737). * * Resolution order (offline-first, no network): * 1. `models_catalog` DB table (SSoT) — newest `release_date` row for the provider. * 2. disk JSON cache (`llm-catalog/`) — newest entry when the table has no rows. * 3. shipped `curated-catalog.json` seed — last-resort floor (always present). * * Returns `null` only when NONE of the three surfaces carry a dated entry for the * provider — callers then degrade to their own static literal floor (the resolver * default keeps `IMPLICIT_FALLBACK_MODEL` for exactly this case — T11944). * * @param providerCatalogKey - The models.dev provider key (e.g. `anthropic`). * @param deps - Optional injected DB handle / cache dir (tests). * @returns The newest-released entry for the provider, or `null` when unresolved. * * @task T11737 */ export declare function resolveCatalogEntry(providerCatalogKey: string, deps?: CatalogResolverDeps): Promise; /** * Reset the in-memory shipped-seed cache. * * @internal — for use in unit tests only. */ export declare function _resetCatalogResolverCache(): void; //# sourceMappingURL=catalog-resolver.d.ts.map