import { type Api, type Model, type ModelSpec, type Provider } from "./types.js"; /** * Controls when dynamic endpoint models should be fetched. */ export type ModelRefreshStrategy = "online" | "offline" | "online-if-uncached"; /** * Hook for loading and mapping stencil.so fallback data into canonical model objects. */ export interface ModelsDevFallback { /** Fetches raw fallback payload (for example from stencil.so). */ fetch(): Promise; /** Maps payload into provider models. */ map(payload: TPayload, providerId: Provider): readonly ModelSpec[]; /** When true, mapped rows can add model ids but cannot replace static metadata. */ additiveOnly?: boolean; } /** * Configuration for provider model resolution. */ export interface ModelManagerOptions { /** Provider id used for static lookup and cache namespacing. */ providerId: Provider; /** * Optional static override. Raw ModelSpec rows are materialized with * `buildModel`; explicitly prebuilt Model rows are consumed as trusted * materializations. When omitted, generator-materialized models.json rows * are consumed directly. */ staticModels?: readonly (ModelSpec | Model)[]; /** Optional override for the cache database path. Default: /models.db. */ cacheDbPath?: string; /** Optional provider id override for cache namespacing. Defaults to providerId. */ cacheProviderId?: string; /** Maximum cache age in milliseconds before considered stale. Default: 2h (`DEFAULT_CACHE_TTL_MS`). */ cacheTtlMs?: number; /** When true, a successful dynamic fetch is the complete provider catalog and prunes static-only models. */ dynamicModelsAuthoritative?: boolean; /** * When true, a provider's dynamic rows are the deployment's whole truth * for input modality: a live text-only row strips the bundled reference's * image support instead of OR-merging it back. Declared by providers whose * capability flags are authoritative and whose endpoints reject * unsupported content (`github-copilot`, `deepinfra`, `coralbricks`). */ dynamicInputAuthoritative?: boolean; /** When true, live reasoning capability replaces the fallback, including an explicit false. */ dynamicReasoningAuthoritative?: boolean; /** * When true, a live per-million price replaces the fallback even when it is * an explicit `0` (a free model or a dropped cache-write charge). Without it, * a zero discovery price is treated as unknown and the bundled rate is kept. */ dynamicCostAuthoritative?: boolean; /** * When true, a static row's display name wins over the cached or live row * for the same id, so a model keeps one name before and after discovery. * Set from the provider's `keep-bundled-names` catalog entry. */ keepStaticNames?: boolean; /** * When true, a fresh cache never satisfies an online-eligible refresh: the * dynamic fetch always runs (an explicit `"offline"` strategy is still * honored), and the cache serves only as the fetch-failure fallback. For * providers whose dynamic result encodes fast-changing live state that a * TTL cache would replay wrongly — e.g. Factory discovery includes live * organization model policy and upstream blocks. Cached eligibility is * an offline snapshot, not current entitlement. */ alwaysRefetchDynamicModels?: boolean; /** Cached model ids whose presence forces refresh when the static or migration-policy fingerprint changes. */ dropCachedModelIdsOnStaticMismatch?: readonly string[]; /** * Trusted, provider-wide request headers that can be safely re-derived at * cache-read time. The cache never persists their values. Models whose live * headers matched this record at write time can survive offline reads without * a bundled static source, including configured discovery providers whose * bearer header comes from the current local config. */ restorableHeaderFallback?: Record; /** * Reconstruct omitted request headers from authoritative local configuration * when no trusted static source remains. Return undefined to keep the row * unavailable. This hook must not perform I/O; attach `resolveHeaders` for * command-backed credentials that must wait until an actual request. */ restoreCachedHeaders?: (model: Readonly) => Pick | undefined; /** Optional dynamic endpoint fetcher. */ fetchDynamicModels?: () => Promise[] | null>; /** Optional stencil.so fallback hook. */ modelsDev?: ModelsDevFallback; /** Clock override for deterministic tests. */ now?: () => number; } /** * Catalog source that most recently refreshed the resolved provider snapshot. */ export type ModelResolutionSource = "bundled" | "cache" | "models.dev" | "provider"; /** * Resolution result. * * `stale` is false when the resolved catalog is authoritative for the selected provider: * - a provider endpoint fetch succeeded in this call (an empty catalog is still * authoritative for the cycle, so downstream pruning of removed models runs), * - a models.dev fetch succeeded for a provider without endpoint discovery, * - a still-fresh authoritative cache was reused in `online-if-uncached` mode, or * - the provider has no remote fetcher configured. */ export interface ModelResolutionResult { models: Model[]; stale: boolean; source: ModelResolutionSource; updatedAt?: number; /** Why the provider's endpoint fetcher threw this call; the models then come from cache, models.dev or bundled data. */ fetchError?: string; } /** * Stateful facade over provider model resolution. */ export interface ModelManager { refresh(strategy?: ModelRefreshStrategy): Promise>; } /** * Creates a reusable provider model manager. */ export declare function createModelManager(options: ModelManagerOptions): ModelManager; /** * Resolves provider models with source precedence: * static -> cached fallback -> stencil.so -> dynamic. * * Later sources override earlier ones by model id. Cached rows participate only * when at least one configured remote source did not refresh successfully. */ export declare function resolveProviderModels(options: ModelManagerOptions, strategy?: ModelRefreshStrategy): Promise>; /** * Merge policy that is part of a cache's identity in * {@link fingerprintStaticModels}: `authoritative` when dynamic discovery owns * the catalog, `additive` when the shared catalog may only add ids. The flags * compose; snapshots written under different policies never match. */ export interface StaticFingerprintPolicy { authoritative?: boolean; additive?: boolean; } /** * Return the versioned content identity for a static provider slice. Callers * may mutate or freeze their arrays, so every call observes current content * and never attaches a stale sidecar fingerprint to caller-owned data. */ export declare function fingerprintStaticModels(models: readonly (ModelSpec | Model)[], policy?: StaticFingerprintPolicy): string;