/** * kosha-discovery — Thin registry façade. * * I keep `ModelRegistry` as the stable public API while the heavy lifting * lives in focused helper modules that stay under the file-size policy. * @module */ import { type RankedRoute, type RouteHealth, type RouteStrategy } from "./registry-routing.js"; import { type EnrichOnlyResult } from "./registry-runtime.js"; import type { DiscoveryBindingHintsV1, DiscoveryBindingQuery, DiscoveryCheapestResultV1, DiscoveryDeltaV1, DiscoverySnapshotV1 } from "./discovery-contract.js"; import type { ProviderHealth } from "./resilience.js"; import type { CapabilitySummary, CheapestModelOptions, CheapestModelResult, DiscoveryError, DiscoveryOptions, KoshaConfig, LatestDiscoveryOptions, LatestDiscoveryResult, ModelCard, ModelMode, ModelRouteInfo, ProviderCredentialPrompt, ProviderInfo, ProviderRoleInfo, RoleQueryOptions } from "./types.js"; /** * Public registry API for provider discovery and routing-oriented queries. */ export declare class ModelRegistry { private readonly state; /** * FIFO tail used to serialize in-process discovery passes. * * `registryDiscover` mutates shared state (providerMap, deltaHistory, * currentCursor, discoveryRevision, lastSnapshotCache) with no internal * lock. Without serialization, two concurrent `discover()` calls — a * POST /api/refresh racing boot, or two refreshes — both capture the same * stale beforeSnapshot, both push a delta, and both rewrite the cursor, * corrupting the v1 delta stream that daemon consumers sync on. Each * `discover()` chains onto this tail so passes run strictly one after * another; the stored tail always settles (it swallows rejection) so one * failed pass can never dead-end the chain, while each caller still * awaits its own promise and observes its own rejection. The cross- * process manifest file lock is unchanged. */ private discoverChain; constructor(config?: KoshaConfig); /** Compatibility accessor retained for existing tests and debug hooks. */ private get providerMap(); /** Compatibility accessor retained for existing tests and debug hooks. */ private get aliasResolver(); /** Compatibility accessor retained for existing tests and debug hooks. */ private get discoveredAt(); private set discoveredAt(value); /** Compatibility accessor retained for existing tests and debug hooks. */ private get healthTracker(); private get currentCursor(); private set currentCursor(value); private get lastSnapshotCache(); private set lastSnapshotCache(value); /** * Run discovery across all or selected providers. * * Serialized per-instance: concurrent calls queue FIFO on * {@link discoverChain} so the v1 delta stream stays consistent. Each * caller awaits its own turn and observes its own result or rejection — * a failed pass never blocks the next one. */ discover(options?: DiscoveryOptions): Promise; /** * Force a fresh discovery pass, bypassing cache for the targeted scope. */ refresh(providerId?: string): Promise; /** * Force a live discovery fetch and return a summary payload. * * This always bypasses cache, so callers can ask for "latest now" * without relying on TTL expiry. */ fetchLatestDetails(options?: LatestDiscoveryOptions): Promise; /** * Re-run LiteLLM enrichment on cached models without re-discovering providers. * * This is the lightweight alternative to `refresh()` — no provider API calls, * just a fetch from the litellm community catalogue. Returns `null` when * no cached data is available (user should run `discover()` first). */ enrichOnly(): Promise; /** Return all known models with optional provider/origin/mode filters. */ models(filter?: { provider?: string; originProvider?: string; mode?: ModelMode; capability?: string; }): ModelCard[]; /** Return the provider -> model -> roles matrix used by routing clients. */ providerRoles(filter?: RoleQueryOptions): ProviderRoleInfo[]; /** Return prompts for discovered providers missing required credentials. */ missingCredentialPrompts(providerIds?: string[]): ProviderCredentialPrompt[]; /** Return the cheapest ranked legacy candidates for the requested query. */ cheapestModels(options?: CheapestModelOptions): CheapestModelResult; /** * Rank candidate routes for a query by a selection {@link RouteStrategy} * — `cheapest` (price), `fastest` (observed p95 latency), `reliable` * (circuit-breaker + timeout health), or `balanced` (a weighted blend). * * The candidate set is the same price-filtered pool as * {@link cheapestModels}; the strategy only changes the ordering and folds * in the runtime health kosha already tracks. Open-breaker providers always * sort last, which is what makes this usable as a failover order. */ rankedRoutes(options?: CheapestModelOptions, strategy?: RouteStrategy): RankedRoute[]; /** Read-only runtime health for one provider (breaker state, latency, reliability). */ providerRouteHealth(providerId: string): RouteHealth; /** Return every provider route for a normalized model identifier. */ modelRoutes(modelId: string): ModelCard[]; /** Return enriched route metadata for a normalized model identifier. */ modelRouteInfo(modelId: string): ModelRouteInfo[]; /** Resolve a model by canonical ID or configured alias. */ model(idOrAlias: string): ModelCard | undefined; /** Return a single provider by canonical or alias provider ID. */ provider(id: string): ProviderInfo | undefined; /** Return all currently known providers. */ providers_list(): ProviderInfo[]; /** Return errors captured during the most recent discovery pass. */ discoveryErrors(): DiscoveryError[]; /** Return raw circuit-breaker health details for monitoring/debugging. */ providerHealth(): ProviderHealth[]; /** Build the stable v1 discovery snapshot for daemon consumers. */ discoverySnapshot(): DiscoverySnapshotV1; /** Return delta batches since the provided cursor. */ discoveryDelta(options?: { sinceCursor?: string | null; }): DiscoveryDeltaV1; /** Stream live discovery deltas through an async iterator. */ watchDiscovery(options?: { sinceCursor?: string | null; }): AsyncGenerator; /** * Subscribe to discovery deltas with a callback API. Easier to wire up * from a long-running daemon than the async-iterator form. Returns an * unsubscribe function so the caller can tear down on shutdown. * * Errors thrown by the handler are caught and forwarded to an optional * `onError` callback so one bad subscriber can't crash the emitter for * everyone else. */ onChange(handler: (delta: DiscoveryDeltaV1) => void | Promise, onError?: (err: unknown) => void): () => void; /** Return cheapest candidates using the trusted v1 capability taxonomy. */ cheapestCandidates(query?: DiscoveryBindingQuery): DiscoveryCheapestResultV1; /** Return query-scoped binding hints without taking routing authority. */ executionBindingHints(query?: DiscoveryBindingQuery): DiscoveryBindingHintsV1; /** Reset one provider breaker or the full health tracker. */ resetHealth(providerId?: string): void; /** * Feed a proxy request outcome back into routing health. * * The proxy layer calls this after every forwarded request so that * `kosha:reliable` / `fastest` / `balanced` ranking reflects real serving * traffic, not just discovery-ping latency. On success the provider's * circuit breaker is notified (closing a half-open probe); on failure the * breaker failure counter advances (opening after the configured * threshold) and the normalized error class is recorded. The latency * sample is appended to the same rolling observation store that discovery * writes into, so all strategies see one unified health signal. * * Additive only — discovery continues to record its own observations; this * just lets proxy traffic contribute to the same store. * * @param providerId - Serving-layer provider the request was forwarded to. * @param outcome - Result of the proxied request. */ recordProxyOutcome(providerId: string, outcome: { ok: boolean; status?: number; latencyMs: number; errorType?: string; }): void; /** Aggregate capability statistics across the current model set. */ capabilities(filter?: { provider?: string; }): CapabilitySummary[]; /** Normalize a role or capability token used by legacy queries. */ normalizeRoleToken(value: string): string; /** Return the deduplicated role list for a model. */ modelRoles(model: ModelCard): string[]; /** Check whether a model satisfies a role or capability query. */ modelSupportsRole(model: ModelCard, roleOrCapability: string): boolean; /** Resolve a configured alias to its canonical model ID. */ resolve(alias: string): string; /** Add a custom alias mapping. */ alias(short: string, modelId: string): void; /** Serialize the registry into a plain JSON-compatible snapshot. */ toJSON(): { providers: ProviderInfo[]; aliases: Record; discoveredAt: number; }; /** Restore a registry instance from a serialized JSON payload. */ static fromJSON(data: { providers: ProviderInfo[]; aliases: Record; discoveredAt: number; }): ModelRegistry; /** Load config from global/project files and merge explicit overrides last. */ static loadConfigFile(overrides?: KoshaConfig): Promise; private dependencies; /** Compatibility wrapper retained for tests that probe internal mutation APIs. */ private snapshotForDelta; /** Compatibility wrapper retained for tests that probe internal mutation APIs. */ private recordDiscoveryMutation; /** Compatibility wrapper retained for tests that probe internal mutation APIs. */ private recordObservation; /** Compatibility wrapper retained for internal error classification hooks. */ private classifyError; private static readJsonFile; } //# sourceMappingURL=registry.d.ts.map