"use strict";
/**
 * Cursor Model Catalog
 *
 * Manages available models from Cursor IDE.
 */
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getDefaultModel = exports.resolveCursorRequestModel = exports.getAvailableModels = exports.fetchModelsFromDaemon = exports.getModelsForDaemon = exports.fetchModelsFromCursorApi = exports.clearCursorModelsCache = exports.formatModelName = exports.detectProvider = exports.DEFAULT_CURSOR_MODELS = exports.DEFAULT_CURSOR_MODEL = exports.DEFAULT_CURSOR_PORT = void 0;
const http = __importStar(require("http"));
const cursor_daemon_1 = require("./cursor-daemon");
const cursor_client_policy_1 = require("./cursor-client-policy");
const cursor_default_models_1 = require("./cursor-default-models");
Object.defineProperty(exports, "DEFAULT_CURSOR_MODEL", { enumerable: true, get: function () { return cursor_default_models_1.DEFAULT_CURSOR_MODEL; } });
Object.defineProperty(exports, "DEFAULT_CURSOR_MODELS", { enumerable: true, get: function () { return cursor_default_models_1.DEFAULT_CURSOR_MODELS; } });
Object.defineProperty(exports, "detectProvider", { enumerable: true, get: function () { return cursor_default_models_1.detectProvider; } });
Object.defineProperty(exports, "formatModelName", { enumerable: true, get: function () { return cursor_default_models_1.formatModelName; } });
/** Default daemon port */
exports.DEFAULT_CURSOR_PORT = 20129;
const CURSOR_MODELS_API_ENDPOINT = 'https://api2.cursor.sh/v1/models';
const CURSOR_MODELS_CACHE_TTL_MS = 5 * 60 * 1000;
const CURSOR_MODELS_MAX_BODY_SIZE = 1024 * 1024; // 1MB limit
const CURSOR_DAEMON_MODELS_TIMEOUT_MS = 5000;
let liveModelsCache = null;
function debugLog(message, error) {
    if (!process.env.CCS_DEBUG)
        return;
    if (error) {
        console.error(`[cursor] ${message}`, error);
        return;
    }
    console.error(`[cursor] ${message}`);
}
function normalizeModelRecords(records) {
    const models = [];
    for (const record of records) {
        if (!record || typeof record !== 'object')
            continue;
        if (typeof record.id !== 'string' || !record.id)
            continue;
        const modelId = record.id;
        const modelName = typeof record.name === 'string' && record.name ? record.name : modelId;
        const provider = typeof record.provider === 'string' && record.provider
            ? record.provider
            : (0, cursor_default_models_1.detectProvider)(modelId);
        models.push({
            id: modelId,
            name: modelName,
            provider,
            isDefault: modelId === cursor_default_models_1.DEFAULT_CURSOR_MODEL,
        });
    }
    return models;
}
function parseApiModelsResponse(payload) {
    if (!payload || typeof payload !== 'object')
        return null;
    const response = payload;
    const records = Array.isArray(response.data)
        ? response.data
        : Array.isArray(response.models)
            ? response.models
            : null;
    if (!records)
        return null;
    const models = normalizeModelRecords(records);
    return models.length > 0 ? models : null;
}
function parseContentLength(value) {
    const rawValue = Array.isArray(value) ? value[0] : value;
    if (!rawValue)
        return null;
    const parsed = Number.parseInt(rawValue, 10);
    return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
}
function getCachedLiveModels(nowMs = Date.now()) {
    if (!liveModelsCache)
        return null;
    if (liveModelsCache.expiresAtMs <= nowMs) {
        liveModelsCache = null;
        return null;
    }
    return liveModelsCache.models;
}
function setCachedLiveModels(models, nowMs = Date.now()) {
    liveModelsCache = {
        models,
        expiresAtMs: nowMs + CURSOR_MODELS_CACHE_TTL_MS,
    };
}
function clearCursorModelsCache() {
    liveModelsCache = null;
}
exports.clearCursorModelsCache = clearCursorModelsCache;
async function fetchModelsFromCursorApi(credentials, options = {}) {
    if (!credentials.accessToken || !credentials.machineId) {
        return null;
    }
    const endpoint = options.endpoint || CURSOR_MODELS_API_ENDPOINT;
    const timeoutMs = options.timeoutMs ?? 5000;
    const abortController = new AbortController();
    const timeout = setTimeout(() => abortController.abort(), timeoutMs);
    try {
        const response = await fetch(endpoint, {
            method: 'GET',
            headers: (0, cursor_client_policy_1.buildCursorModelsHeaders)(credentials),
            signal: abortController.signal,
        });
        if (!response.ok) {
            if (response.status === 401 || response.status === 403) {
                clearCursorModelsCache();
            }
            debugLog(`Cursor models API returned ${response.status} (${endpoint})`);
            return null;
        }
        const payload = (await response.json());
        const parsed = parseApiModelsResponse(payload);
        if (!parsed) {
            debugLog(`Cursor models API payload shape invalid (${endpoint})`);
        }
        return parsed;
    }
    catch (error) {
        debugLog(`Cursor models API fetch failed (${endpoint})`, error);
        return null;
    }
    finally {
        clearTimeout(timeout);
    }
}
exports.fetchModelsFromCursorApi = fetchModelsFromCursorApi;
async function getModelsForDaemon(options = {}) {
    const cached = getCachedLiveModels();
    if (cached) {
        return cached;
    }
    const credentials = options.credentials;
    if (credentials?.accessToken && credentials.machineId) {
        const liveModels = await fetchModelsFromCursorApi(credentials, {
            endpoint: options.endpoint,
            timeoutMs: options.timeoutMs,
        });
        if (liveModels && liveModels.length > 0) {
            setCachedLiveModels(liveModels);
            return liveModels;
        }
    }
    return cursor_default_models_1.DEFAULT_CURSOR_MODELS;
}
exports.getModelsForDaemon = getModelsForDaemon;
/**
 * Fetch available models from running cursor daemon.
 *
 * @param port The port cursor daemon is running on
 * @returns List of available models
 */
async function fetchModelsFromDaemon(port) {
    return new Promise((resolve) => {
        let resolved = false;
        const safeResolve = (models) => {
            if (resolved)
                return;
            resolved = true;
            resolve(models);
        };
        const req = http.request({
            // Use 127.0.0.1 instead of localhost for more reliable local connections
            hostname: '127.0.0.1',
            port,
            path: '/v1/models',
            method: 'GET',
            timeout: CURSOR_DAEMON_MODELS_TIMEOUT_MS,
        }, (res) => {
            const contentLength = parseContentLength(res.headers['content-length']);
            if (contentLength !== null && contentLength > CURSOR_MODELS_MAX_BODY_SIZE) {
                debugLog('Cursor daemon /v1/models content-length exceeded 1MB; falling back to defaults');
                res.destroy();
                req.destroy();
                safeResolve(cursor_default_models_1.DEFAULT_CURSOR_MODELS);
                return;
            }
            let data = '';
            let bodySize = 0;
            res.on('data', (chunk) => {
                if (resolved)
                    return;
                bodySize += typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.length;
                if (bodySize > CURSOR_MODELS_MAX_BODY_SIZE) {
                    debugLog('Cursor daemon /v1/models body exceeded 1MB; falling back to defaults');
                    res.destroy();
                    req.destroy();
                    safeResolve(cursor_default_models_1.DEFAULT_CURSOR_MODELS);
                    return;
                }
                data += chunk;
            });
            res.on('end', () => {
                if (resolved)
                    return;
                try {
                    const response = JSON.parse(data);
                    if (Array.isArray(response.data)) {
                        const models = response.data
                            .filter((m) => m && typeof m.id === 'string' && m.id.length > 0)
                            .map((m) => ({
                            id: m.id,
                            name: (0, cursor_default_models_1.formatModelName)(m.id),
                            provider: (0, cursor_default_models_1.detectProvider)(m.id),
                            isDefault: m.id === cursor_default_models_1.DEFAULT_CURSOR_MODEL,
                        }));
                        safeResolve(models.length > 0 ? models : cursor_default_models_1.DEFAULT_CURSOR_MODELS);
                    }
                    else {
                        debugLog('Cursor daemon /v1/models payload missing data[]; falling back to defaults');
                        safeResolve(cursor_default_models_1.DEFAULT_CURSOR_MODELS);
                    }
                }
                catch (error) {
                    debugLog('Cursor daemon /v1/models returned invalid JSON; falling back to defaults', error);
                    safeResolve(cursor_default_models_1.DEFAULT_CURSOR_MODELS);
                }
            });
        });
        req.on('error', (error) => {
            debugLog('Cursor daemon /v1/models request failed; falling back to defaults', error);
            safeResolve(cursor_default_models_1.DEFAULT_CURSOR_MODELS);
        });
        req.on('timeout', () => {
            debugLog('Cursor daemon /v1/models request timed out; falling back to defaults');
            req.destroy();
            safeResolve(cursor_default_models_1.DEFAULT_CURSOR_MODELS);
        });
        req.end();
    });
}
exports.fetchModelsFromDaemon = fetchModelsFromDaemon;
/**
 * Get available models (from daemon or defaults).
 * Checks daemon health first to avoid 5s timeout when daemon is not running.
 */
async function getAvailableModels(port) {
    if (!(await (0, cursor_daemon_1.isDaemonRunning)(port))) {
        return cursor_default_models_1.DEFAULT_CURSOR_MODELS;
    }
    return fetchModelsFromDaemon(port);
}
exports.getAvailableModels = getAvailableModels;
function getCatalogDefaultModelId(availableModels) {
    if (availableModels.some((model) => model.id === cursor_default_models_1.DEFAULT_CURSOR_MODEL)) {
        return cursor_default_models_1.DEFAULT_CURSOR_MODEL;
    }
    const explicitDefault = availableModels.find((model) => model.isDefault)?.id;
    if (explicitDefault) {
        return explicitDefault;
    }
    const firstAvailable = availableModels.find((model) => typeof model.id === 'string' && model.id.trim().length > 0)?.id;
    return firstAvailable || cursor_default_models_1.DEFAULT_CURSOR_MODEL;
}
function addLookupCandidate(candidates, value) {
    const normalized = value.trim().toLowerCase();
    if (normalized) {
        candidates.add(normalized);
    }
}
function buildCursorAnthropicModelLookupCandidates(requestedModel) {
    const candidates = new Set();
    const raw = requestedModel.trim().toLowerCase();
    addLookupCandidate(candidates, raw);
    let normalized = raw.replace(/^[a-z0-9_-]+\//, '');
    addLookupCandidate(candidates, normalized);
    while (true) {
        const stripped = normalized
            .replace(/\(\d+\)$/i, '')
            .replace(/\[1m\]$/i, '')
            .replace(/-thinking$/i, '')
            .replace(/-\d{8}$/i, '');
        if (stripped === normalized) {
            break;
        }
        normalized = stripped;
        addLookupCandidate(candidates, normalized);
    }
    const anthropicAliasMatch = normalized.match(/^claude-(opus|sonnet|haiku)-(\d+)(?:[.-](\d+))?(?:-(1m|fast-mode))?$/i);
    if (anthropicAliasMatch) {
        const [, family, major, minor, variant] = anthropicAliasMatch;
        const cursorModelId = `claude-${major}${minor ? `.${minor}` : ''}-${family.toLowerCase()}${variant ? `-${variant.toLowerCase()}` : ''}`;
        addLookupCandidate(candidates, cursorModelId);
    }
    return [...candidates];
}
function resolveCursorRequestModel(requestedModel, availableModels) {
    const fallbackModel = getCatalogDefaultModelId(availableModels);
    const normalizedRequested = typeof requestedModel === 'string' ? requestedModel.trim() : '';
    if (!normalizedRequested) {
        return fallbackModel;
    }
    const lookupCandidates = new Set(buildCursorAnthropicModelLookupCandidates(normalizedRequested));
    const matchedModel = availableModels.find((model) => lookupCandidates.has(model.id.toLowerCase()));
    if (matchedModel) {
        return matchedModel.id;
    }
    return fallbackModel;
}
exports.resolveCursorRequestModel = resolveCursorRequestModel;
/**
 * Get the default model.
 * Uses GPT-5.3 Codex as default.
 */
function getDefaultModel() {
    return getCatalogDefaultModelId(cursor_default_models_1.DEFAULT_CURSOR_MODELS);
}
exports.getDefaultModel = getDefaultModel;
//# sourceMappingURL=cursor-models.js.map