/**
 * Model registry - manages built-in and custom models, provides API key resolution.
 */
import { resetApiProviders } from "@earendil-works/pi-ai/compat";
import { resetOAuthProviders } from "@earendil-works/pi-ai/oauth";
import { dirname } from "node:path";
import { getAgentConfigPaths } from "../config.js";
import { normalizePath } from "../utils/paths.js";
import { copilotCatalogCachePath, copilotTokenFromEnvironment, seedActiveCopilotModelCatalogFromCache } from "./copilot-model-catalog.js";
import { getModelRequestAuth, getApiKeyForProviderFromConfig, getProviderAuthStatusFromConfig } from "./model-registry-auth.js";
import { applyProviderConfigToModels, migrateLegacyRegisterProviderConfigValues, validateProviderConfig } from "./model-registry-dynamic.js";
import { loadModelRegistryModels } from "./model-registry-loader.js";
import { BUILT_IN_PROVIDER_DISPLAY_NAMES } from "./provider-display-names.js";
import { clearConfigValueCache, isConfigValueConfigured } from "./resolve-config-value.js";
const REMOTE_CATALOG_PROVIDERS = new Set(["cursor", "github-copilot", "openrouter", "vercel-ai-gateway"]);
const OPENAI_COMPATIBLE_APIS = new Set(["openai-completions", "openai-responses"]);
/** Clear the config value command cache. Exported for testing. */
export const clearApiKeyCache = clearConfigValueCache;
/**
 * Model registry - loads and manages models, resolves API keys via AuthStorage.
 */
export class ModelRegistry {
    constructor(authStorage, modelsJsonPaths) {
        this.models = [];
        this.modelOverrides = new Map();
        this.providerRequestConfigs = new Map();
        this.modelRequestHeaders = new Map();
        this.registeredProviders = new Map();
        this.builtInProviders = new Set();
        this.customOpenAICompatibleProviders = new Set();
        this.loadError = undefined;
        this.authStorage = authStorage;
        this.modelsJsonPaths = modelsJsonPaths.map((path) => normalizePath(path));
        this.seedCopilotModelCatalogFromCache();
        this.loadModels();
    }
    seedCopilotModelCatalogFromCache() {
        if (this.modelsJsonPaths.length === 0)
            return;
        const cred = this.authStorage.get("github-copilot");
        const token = cred?.type === "oauth" && typeof cred.access === "string" ? cred.access : copilotTokenFromEnvironment();
        if (!token)
            return;
        seedActiveCopilotModelCatalogFromCache(token, copilotCatalogCachePath(dirname(this.modelsJsonPaths[0])));
    }
    static create(authStorage, modelsJsonPath = getAgentConfigPaths("models.json")) {
        return new ModelRegistry(authStorage, Array.isArray(modelsJsonPath) ? modelsJsonPath : [modelsJsonPath]);
    }
    static inMemory(authStorage) {
        return new ModelRegistry(authStorage, []);
    }
    /**
     * Reload models from disk (built-in + custom from models.json).
     */
    refresh() {
        this.providerRequestConfigs.clear();
        this.modelRequestHeaders.clear();
        this.loadError = undefined;
        resetApiProviders();
        resetOAuthProviders();
        this.loadModels();
        for (const [providerName, config] of this.registeredProviders.entries()) {
            this.applyProviderConfig(providerName, config);
        }
    }
    /**
     * Get any error from loading models.json (undefined if no error).
     */
    getError() {
        return this.loadError;
    }
    loadModels() {
        const loaded = loadModelRegistryModels(this.authStorage, this.modelsJsonPaths);
        this.modelOverrides = loaded.modelOverrides;
        this.models = loaded.models;
        this.providerRequestConfigs = loaded.providerRequestConfigs;
        this.modelRequestHeaders = loaded.modelRequestHeaders;
        this.builtInProviders = loaded.builtInProviders;
        this.customOpenAICompatibleProviders = loaded.customOpenAICompatibleProviders;
        this.loadError = loaded.loadError;
    }
    /**
     * Get all models (built-in + custom).
     * If models.json had errors, returns only built-in models.
     */
    getAll() {
        return this.models;
    }
    /**
     * Get only models that have auth configured.
     * This is a fast check that doesn't refresh OAuth tokens.
     */
    getAvailable() {
        return this.models.filter((m) => this.hasConfiguredAuth(m));
    }
    /**
     * Find a model by provider and ID.
     */
    find(provider, modelId) {
        return this.models.find((m) => m.provider === provider && m.id === modelId);
    }
    /** Whether an authenticated provider may reconstruct an absent saved model ID. */
    canRestoreUnknownModel(provider) {
        if (REMOTE_CATALOG_PROVIDERS.has(provider))
            return true;
        if (this.customOpenAICompatibleProviders.has(provider))
            return true;
        if (this.builtInProviders.has(provider))
            return false;
        const config = this.registeredProviders.get(provider);
        return (config?.models?.some((model) => {
            const api = model.api ?? config.api;
            return api !== undefined && OPENAI_COMPATIBLE_APIS.has(api);
        }) === true);
    }
    /**
     * Get API key for a model.
     */
    hasConfiguredAuth(model) {
        const providerApiKey = this.providerRequestConfigs.get(model.provider)?.apiKey;
        return (this.authStorage.hasAuth(model.provider) ||
            (providerApiKey !== undefined && isConfigValueConfigured(providerApiKey)));
    }
    getModelRequestKey(provider, modelId) {
        return `${provider}:${modelId}`;
    }
    storeProviderRequestConfig(providerName, config) {
        if (!config.apiKey && !config.headers && !config.authHeader) {
            return;
        }
        this.providerRequestConfigs.set(providerName, {
            apiKey: config.apiKey,
            headers: config.headers,
            authHeader: config.authHeader,
        });
    }
    storeModelHeaders(providerName, modelId, headers) {
        const key = this.getModelRequestKey(providerName, modelId);
        if (!headers || Object.keys(headers).length === 0) {
            this.modelRequestHeaders.delete(key);
            return;
        }
        this.modelRequestHeaders.set(key, headers);
    }
    /**
     * Get API key and request headers for a model.
     */
    async getApiKeyAndHeaders(model) {
        return getModelRequestAuth(model, this.authStorage, this.providerRequestConfigs, this.modelRequestHeaders);
    }
    /**
     * Return auth status for a provider, including request auth configured in models.json.
     * This intentionally does not execute command-backed config values.
     */
    getProviderAuthStatus(provider) {
        return getProviderAuthStatusFromConfig(provider, this.authStorage, this.providerRequestConfigs);
    }
    /**
     * Get display name for a provider.
     */
    getProviderDisplayName(provider) {
        const registeredProvider = this.registeredProviders.get(provider);
        const oauthProvider = this.authStorage.getOAuthProviders().find((p) => p.id === provider);
        return (registeredProvider?.name ??
            registeredProvider?.oauth?.name ??
            oauthProvider?.name ??
            BUILT_IN_PROVIDER_DISPLAY_NAMES[provider] ??
            provider);
    }
    /**
     * Get API key for a provider.
     */
    async getApiKeyForProvider(provider) {
        return getApiKeyForProviderFromConfig(provider, this.authStorage, this.providerRequestConfigs);
    }
    /**
     * Check if a model is using OAuth credentials (subscription).
     */
    isUsingOAuth(model) {
        const cred = this.authStorage.get(model.provider);
        return cred?.type === "oauth";
    }
    /**
     * Register a provider dynamically (from extensions).
     */
    registerProvider(providerName, config) {
        const migratedConfig = migrateLegacyRegisterProviderConfigValues(providerName, config);
        validateProviderConfig(providerName, migratedConfig);
        this.applyProviderConfig(providerName, migratedConfig);
        this.upsertRegisteredProvider(providerName, migratedConfig);
    }
    /**
     * Check whether extensions have registered custom streamSimple dispatch for an API.
     */
    hasRegisteredStreamSimpleForApi(api) {
        for (const config of this.registeredProviders.values()) {
            if (config.api === api && config.streamSimple) {
                return true;
            }
        }
        return false;
    }
    /**
     * Unregister a previously registered provider.
     */
    unregisterProvider(providerName) {
        if (!this.registeredProviders.has(providerName))
            return;
        this.registeredProviders.delete(providerName);
        this.refresh();
    }
    upsertRegisteredProvider(providerName, config) {
        const existing = this.registeredProviders.get(providerName);
        if (!existing) {
            this.registeredProviders.set(providerName, config);
            return;
        }
        for (const k of Object.keys(config)) {
            if (config[k] !== undefined) {
                existing[k] = config[k];
            }
        }
    }
    applyProviderConfig(providerName, config) {
        this.models = applyProviderConfigToModels({
            providerName,
            config,
            models: this.models,
            modelOverrides: this.modelOverrides,
            authStorage: this.authStorage,
            storeProviderRequestConfig: (name, requestConfig) => this.storeProviderRequestConfig(name, requestConfig),
            storeModelHeaders: (name, modelId, headers) => this.storeModelHeaders(name, modelId, headers),
        });
    }
}
//# sourceMappingURL=model-registry.js.map