import { OpenAiCompatibleModelName } from './openai-compatible-model.js';
/**
 * Discovers profile-owned models without shelling out. Local providers use
 * their HTTP endpoints, and hosted providers use their OpenAI-compatible
 * `/models` endpoint when the required credentials are configured.
 */
export class OpenAiCompatibleModelDiscoveryService {
    static async listModels(options) {
        if (options.profile.modelDiscovery.mode === 'ollama-tags') {
            return await OpenAiCompatibleModelDiscoveryService.listOllamaTags(options);
        }
        return await OpenAiCompatibleModelDiscoveryService.listOpenAiModels(options);
    }
    static async listOllamaTags(options) {
        const response = await (options.fetchImpl ?? fetch)(`${OpenAiCompatibleModelDiscoveryService.trimTrailingSlash(options.nativeBaseUrl ?? OpenAiCompatibleModelDiscoveryService.openAiToNativeBaseUrl(options.baseUrl))}/api/tags`, {
            method: 'GET',
            signal: options.signal,
        });
        const payload = await OpenAiCompatibleModelDiscoveryService.readJsonResponse(response, options.profile);
        return (payload.models ?? [])
            .flatMap((model) => OpenAiCompatibleModelDiscoveryService.toOllamaDiscoveredModel(options.profile, model) ?? [])
            .sort((a, b) => a.name.localeCompare(b.name));
    }
    static async listOpenAiModels(options) {
        const response = await (options.fetchImpl ?? fetch)(`${OpenAiCompatibleModelDiscoveryService.trimTrailingSlash(options.baseUrl)}/models`, {
            method: 'GET',
            signal: options.signal,
            headers: OpenAiCompatibleModelDiscoveryService.headers(options.apiKey),
        });
        const payload = await OpenAiCompatibleModelDiscoveryService.readJsonResponse(response, options.profile);
        return (payload.data ?? [])
            .flatMap((model) => OpenAiCompatibleModelDiscoveryService.toOpenAiDiscoveredModel(options.profile, model) ?? [])
            .sort((a, b) => a.name.localeCompare(b.name));
    }
    static toOllamaDiscoveredModel(profile, model) {
        if (typeof model.name !== 'string' || !model.name.trim()) {
            return undefined;
        }
        if (!OpenAiCompatibleModelDiscoveryService.isChatCapable(model.capabilities)) {
            return undefined;
        }
        return {
            id: OpenAiCompatibleModelName.toHeddleModel(profile, model.name),
            name: model.name.trim(),
            sizeBytes: typeof model.size === 'number' ? model.size : undefined,
            modifiedAt: typeof model.modified_at === 'string' ? model.modified_at : undefined,
        };
    }
    static toOpenAiDiscoveredModel(profile, model) {
        if (typeof model.id !== 'string' || !model.id.trim() || OpenAiCompatibleModelDiscoveryService.isEmbeddingModel(model.id)) {
            return undefined;
        }
        const name = model.id.trim();
        return {
            id: OpenAiCompatibleModelName.toHeddleModel(profile, name),
            name,
        };
    }
    static isChatCapable(capabilities) {
        if (!Array.isArray(capabilities)) {
            return true;
        }
        return capabilities.some((capability) => capability === 'completion' || capability === 'chat');
    }
    static isEmbeddingModel(name) {
        const normalized = name.toLowerCase();
        return ['embed', 'embedding', 'nomic-embed', 'bge', 'clip'].some((part) => normalized.includes(part));
    }
    static async readJsonResponse(response, profile) {
        const text = await response.text();
        if (!response.ok) {
            throw new Error(`${profile.label} model discovery request failed: ${response.status} ${response.statusText}${text ? `: ${text}` : ''}`);
        }
        const parsed = text.trim() ? JSON.parse(text) : {};
        return typeof parsed === 'object' && parsed !== null ? parsed : {};
    }
    static headers(apiKey) {
        return apiKey ? { authorization: `Bearer ${apiKey}` } : undefined;
    }
    static trimTrailingSlash(value) {
        return value.replace(/\/+$/, '');
    }
    static openAiToNativeBaseUrl(value) {
        return OpenAiCompatibleModelDiscoveryService.trimTrailingSlash(value).replace(/\/v1$/i, '');
    }
}
//# sourceMappingURL=openai-compatible-model-discovery.js.map