/** * [WHO]: ModelRegistry class, model definitions, API key resolution * [FROM]: Depends on ai, typebox, config modules * [TO]: Consumed by index.ts, main.ts, catui-defaults.ts, core/runtime/sdk.ts, core/runtime/agent-session.ts, core/extensions-host/runner.ts, core/extensions-host/types.ts, cli/list-models.ts, modes/interactive/components/model-selector.ts, and test files * [HERE]: core/model-registry.ts - model catalog and credential management */ import type { Api, Context, Model, SimpleStreamOptions } from "@catui/ai/types"; import type { AssistantMessageEventStream } from "@catui/ai/events"; import { type OAuthProviderInterface } from "@catui/ai/oauth"; import type { AuthStorage } from "./platform/config/auth-storage.js"; import { clearConfigValueCache } from "./platform/config/resolve-config-value.js"; import { type DiscoveryResult } from "./model/discovery.js"; type AgentLoopFramework = "standard" | "weak-model-compatible"; type AgentLoopFrameworkInput = AgentLoopFramework | "high-intelligence" | "low-intelligence" | "structured-adaptive"; /** Clear the config value command cache. Exported for testing. */ export declare const clearApiKeyCache: typeof clearConfigValueCache; /** * Model registry - loads and manages models, resolves API keys via AuthStorage. */ export interface ModelRegistryOptions { /** * When true, only load models from models.json (no full built-in catalog). Used by Catui. * Exception: a small OpenRouter built-in set (`openrouter/auto`, `openrouter/free`) so `/login` and * `/model` work without pasting ids; add any other OpenRouter model id in models.json. */ useOnlyCustomModels?: boolean; /** Provider id(s) for which apiKey is optional in models.json (key stored in auth.json later). Used by Catui. */ allowOptionalApiKeyForProvider?: string | string[]; } export declare class ModelRegistry { readonly authStorage: AuthStorage; private modelsJsonPath; private models; private customProviderApiKeys; private registeredProviders; private loadError; private useOnlyCustomModels; private allowOptionalApiKeyForProvider; private discoveryCache; private discoveryProviders; private discoveryRefreshing; constructor(authStorage: AuthStorage, modelsJsonPath?: string | undefined, options?: ModelRegistryOptions); /** * Reload models from disk (built-in + custom from models.json). */ refresh(): void; /** * Get any error from loading models.json (undefined if no error). */ getError(): string | undefined; private loadModels; /** Load built-in models and apply provider/model overrides */ private loadBuiltInModels; /** Merge custom models into built-in list by provider+id (custom wins on conflicts). */ private mergeCustomModels; /** * Merge discovered models into the existing model list. * Hand-configured models (by provider+id) are NOT overwritten — they take priority. * Discovered models use known-metadata defaults for fields the /models endpoint doesn't provide. */ private mergeDiscoveredModels; private loadCustomModels; private validateConfig; private parseModels; /** * Get all models (built-in + custom). * If models.json had errors, returns only built-in models. */ getAll(): Model[]; /** * Get only models that have auth configured. * This is a fast check that doesn't refresh OAuth tokens. */ getAvailable(): Model[]; /** * Get models with valid API keys (async, validates OAuth tokens). * This checks and refreshes OAuth tokens, filtering out expired ones. */ getAvailableAsync(): Promise[]>; /** * Fetch models from remote /models endpoints for all discovery-enabled providers. * * This is an async, non-blocking operation: * - Fetches all providers in parallel (Promise.allSettled) * - Updates the discovery cache with fresh results * - Re-runs loadModels() to merge fresh data * * Use this for: * - Fire-and-forget on startup * - Manual refresh from /model selector (Ctrl+R) * - CLI --list-models --refresh */ refreshWithDiscovery(): Promise<{ discovered: number; errors: string[]; }>; /** * Discover models for a single provider. * Fetches from remote, updates cache, returns result. */ discoverProvider(providerName: string): Promise; /** * Get discovery status for a provider. * Useful for debugging and UI indicators. */ getDiscoveryStatus(providerName: string): { enabled: boolean; cached: boolean; lastFetched?: number; modelCount: number; }; /** * Check if a provider has discovery enabled. */ isDiscoveryEnabled(providerName: string): boolean; /** * Clear all discovery cache data. */ clearDiscoveryCache(): void; /** * Find a model by provider and ID. */ find(provider: string, modelId: string): Model | undefined; /** * Get API key for a model. */ getApiKey(model: Model): Promise; /** * Get API key for a provider. */ getApiKeyForProvider(provider: string): Promise; /** * Check if a model is using OAuth credentials (subscription). */ isUsingOAuth(model: Model): boolean; /** * Register a provider dynamically (from extensions). * * If provider has models: replaces all existing models for this provider. * If provider has only baseUrl/headers: overrides existing models' URLs. * If provider has oauth: registers OAuth provider for /login support. */ registerProvider(providerName: string, config: ProviderConfigInput): void; private applyProviderConfig; private static readonly OPENROUTER_JSON_BASE; private static readonly OPENROUTER_JSON_API; /** * Append an OpenRouter model to models.json by id (same string as on openrouter.ai, e.g. x-ai/grok-4.20). * API key is not written; use /login openrouter or OPENROUTER_API_KEY. */ appendOpenRouterModel(modelId: string, options?: { name?: string; }): void; } /** * Input type for registerProvider API. */ export interface ProviderConfigInput { baseUrl?: string; apiKey?: string; api?: Api; streamSimple?: (model: Model, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream; headers?: Record; authHeader?: boolean; /** OAuth provider for /login support */ oauth?: Omit; models?: Array<{ id: string; name: string; api?: Api; reasoning: boolean; input: ("text" | "image")[]; cost: { input: number; output: number; cacheRead: number; cacheWrite: number; }; contextWindow: number; maxTokens: number; headers?: Record; agentLoopFramework?: AgentLoopFrameworkInput; compat?: Model["compat"]; }>; } export {};