"use strict";
/**
 * Thinking Budget Validator
 *
 * Validates user-provided thinking values against model capabilities.
 * - Clamps out-of-range budgets
 * - Maps invalid levels to closest valid
 * - Warns when model doesn't support thinking
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateThinking = exports.isThinkingOffValue = exports.THINKING_AUTO_VALUE = exports.THINKING_OFF_VALUES = exports.capLevelAtMax = exports.VALID_THINKING_TIERS = exports.VALID_THINKING_LEVELS = exports.THINKING_LEVEL_RANK = exports.THINKING_LEVEL_BUDGETS = exports.THINKING_BUDGET_DEFAULT_MIN = exports.THINKING_BUDGET_MAX = exports.THINKING_BUDGET_MIN = void 0;
const model_catalog_1 = require("./model-catalog");
/**
 * Thinking budget bounds (used for validation across API and CLI)
 */
exports.THINKING_BUDGET_MIN = 0;
exports.THINKING_BUDGET_MAX = 100000;
exports.THINKING_BUDGET_DEFAULT_MIN = 512;
/**
 * Named thinking level mappings to budget values (when converting level→budget)
 *
 * `max` sits above `xhigh` to represent unconstrained thinking (Claude Opus 4.7,
 * Mythos). The numeric value is a CCS-internal mapping, not an Anthropic wire
 * value: Opus 4.7 uses adaptive thinking with an effort string, and other
 * max-capable models already treat the level as a qualitative cap.
 */
exports.THINKING_LEVEL_BUDGETS = {
    minimal: 512,
    low: 1024,
    medium: 8192,
    high: 24576,
    xhigh: 32768,
    max: 65536,
};
/**
 * Level rank for comparison (higher = more intensive)
 */
exports.THINKING_LEVEL_RANK = {
    minimal: 1,
    low: 2,
    medium: 3,
    high: 4,
    xhigh: 5,
    max: 6,
};
/**
 * Valid thinking level names
 */
exports.VALID_THINKING_LEVELS = [
    'minimal',
    'low',
    'medium',
    'high',
    'xhigh',
    'max',
    'auto',
];
/**
 * Valid tier names for tier_defaults configuration
 */
exports.VALID_THINKING_TIERS = ['opus', 'sonnet', 'haiku'];
/**
 * Cap a level at the model's maximum supported level.
 * Returns the capped level and whether capping occurred.
 */
function capLevelAtMax(level, maxLevel) {
    if (!maxLevel)
        return { level, capped: false };
    const levelRank = exports.THINKING_LEVEL_RANK[level] ?? 0;
    const maxRank = exports.THINKING_LEVEL_RANK[maxLevel] ?? 5;
    if (levelRank > maxRank) {
        return { level: maxLevel, capped: true };
    }
    return { level, capped: false };
}
exports.capLevelAtMax = capLevelAtMax;
/**
 * Special thinking values
 */
exports.THINKING_OFF_VALUES = ['off', 'none', 'disabled', '0'];
exports.THINKING_AUTO_VALUE = 'auto';
/**
 * Check whether a value should disable thinking.
 * Accepts common string aliases and numeric 0.
 */
function isThinkingOffValue(value) {
    if (typeof value === 'number') {
        return value === 0;
    }
    if (typeof value === 'string') {
        const normalized = value.toLowerCase().trim();
        return exports.THINKING_OFF_VALUES.includes(normalized);
    }
    return false;
}
exports.isThinkingOffValue = isThinkingOffValue;
/**
 * Find closest valid level using simple string matching
 * Returns undefined if no close match found
 */
function findClosestLevel(input, validLevels) {
    const normalized = input.toLowerCase().trim();
    // Exact match first
    if (validLevels.includes(normalized)) {
        return normalized;
    }
    // Prefix matching (e.g., 'med' → 'medium', 'hi' → 'high')
    for (const level of validLevels) {
        if (level.startsWith(normalized)) {
            return level;
        }
    }
    // Common aliases.
    //
    // `max: 'xhigh'` is a graceful fallback for models whose levels list does not
    // include `max` (e.g. Codex `gpt-5.4`). Exact match above takes priority, so
    // Opus 4.7 (which has `max` in its validLevels) returns `max` directly while
    // Codex still maps `max` → `xhigh`.
    const aliases = {
        min: 'minimal',
        lo: 'low',
        med: 'medium',
        mid: 'medium',
        hi: 'high',
        max: 'xhigh',
        ultra: 'xhigh',
        extreme: 'xhigh',
    };
    if (aliases[normalized] && validLevels.includes(aliases[normalized])) {
        return aliases[normalized];
    }
    return undefined;
}
/**
 * Validate a thinking value against model capabilities
 *
 * @param provider - The CLI proxy provider
 * @param modelId - The model identifier
 * @param value - User-provided thinking value (level name or budget number)
 * @returns Validation result with possibly clamped/mapped value and warnings
 */
function validateThinking(provider, modelId, value) {
    const thinking = (0, model_catalog_1.getModelThinkingSupport)(provider, modelId);
    // Handle string length limit to prevent performance issues
    if (typeof value === 'string' && value.length > 100) {
        return {
            valid: false,
            value: 'off',
            warning: 'Thinking value too long (max 100 chars). Using "off".',
        };
    }
    // Handle empty string explicitly
    if (typeof value === 'string' && value.trim() === '') {
        return {
            valid: false,
            value: 'off',
            warning: 'Empty thinking value not allowed. Using "off".',
        };
    }
    // Handle off/none/disabled values
    if (isThinkingOffValue(value)) {
        return { valid: true, value: 'off' };
    }
    // If model has no thinking support info, pass through
    if (!thinking) {
        return {
            valid: true,
            value,
            warning: `Model ${modelId} has unknown thinking support. Value passed through unchanged.`,
        };
    }
    // Model doesn't support thinking at all
    if (thinking.type === 'none') {
        return {
            valid: false,
            value: 'off',
            warning: `Model ${modelId} does not support extended thinking. Thinking disabled.`,
        };
    }
    // Handle 'auto' value
    if (typeof value === 'string' && value.toLowerCase().trim() === exports.THINKING_AUTO_VALUE) {
        if (!thinking.dynamicAllowed) {
            return {
                valid: false,
                value: thinking.type === 'budget' ? (thinking.min ?? 1024) : (thinking.levels?.[0] ?? 'low'),
                warning: `Model ${modelId} does not support dynamic/auto thinking. Using minimum value.`,
            };
        }
        return { valid: true, value: 'auto' };
    }
    // Budget-type models
    if (thinking.type === 'budget') {
        return validateBudgetThinking(thinking, value, modelId);
    }
    // Level-type models
    if (thinking.type === 'levels') {
        return validateLevelThinking(thinking, value, modelId);
    }
    // Fallback
    return { valid: true, value };
}
exports.validateThinking = validateThinking;
/**
 * Validate budget-type thinking value
 */
function validateBudgetThinking(thinking, value, modelId) {
    const min = thinking.min ?? exports.THINKING_BUDGET_MIN;
    const max = thinking.max ?? exports.THINKING_BUDGET_MAX;
    // Convert level name to budget if needed
    let budget;
    if (typeof value === 'string') {
        const normalizedLevel = value.toLowerCase().trim();
        if (normalizedLevel in exports.THINKING_LEVEL_BUDGETS) {
            budget = exports.THINKING_LEVEL_BUDGETS[normalizedLevel];
        }
        else {
            // Strict number parsing: reject partial parses like "123abc"
            const parsed = Number(value);
            const trimmed = value.trim();
            // Accept trailing .0 for whole numbers (e.g., "8192.0" → 8192)
            const isWholeWithDecimal = Number.isInteger(parsed) && trimmed === `${parsed}.0`;
            if (isNaN(parsed) ||
                !Number.isFinite(parsed) ||
                (trimmed !== String(parsed) && !isWholeWithDecimal)) {
                // Not a valid number, try to find closest level
                const closest = findClosestLevel(value, Object.keys(exports.THINKING_LEVEL_BUDGETS));
                if (closest) {
                    budget = exports.THINKING_LEVEL_BUDGETS[closest];
                }
                else {
                    return {
                        valid: false,
                        value: min || exports.THINKING_BUDGET_DEFAULT_MIN,
                        warning: `Invalid thinking value "${value}" for ${modelId}. Using minimum budget ${min || exports.THINKING_BUDGET_DEFAULT_MIN}.`,
                    };
                }
            }
            else {
                budget = parsed;
            }
        }
    }
    else {
        budget = value;
    }
    // Reject NaN/Infinity budgets
    if (!Number.isFinite(budget)) {
        return {
            valid: false,
            value: min || exports.THINKING_BUDGET_DEFAULT_MIN,
            warning: `Budget must be a finite number. Using minimum ${min || exports.THINKING_BUDGET_DEFAULT_MIN}.`,
        };
    }
    // Reject negative budgets
    if (budget < exports.THINKING_BUDGET_MIN) {
        return {
            valid: false,
            value: min || exports.THINKING_BUDGET_DEFAULT_MIN,
            warning: `Negative thinking budget not allowed. Using minimum ${min || exports.THINKING_BUDGET_DEFAULT_MIN}.`,
        };
    }
    // Check zero budget
    if (budget === 0 && !thinking.zeroAllowed) {
        return {
            valid: false,
            value: min || exports.THINKING_BUDGET_DEFAULT_MIN,
            warning: `Model ${modelId} does not support zero thinking budget. Using minimum ${min || exports.THINKING_BUDGET_DEFAULT_MIN}.`,
        };
    }
    // Clamp to valid range
    if (budget < min) {
        return {
            valid: true,
            value: min,
            warning: `Thinking budget ${budget} below minimum for ${modelId}. Clamped to ${min}.`,
        };
    }
    if (budget > max) {
        return {
            valid: true,
            value: max,
            warning: `Thinking budget ${budget} exceeds maximum for ${modelId}. Clamped to ${max}.`,
        };
    }
    return { valid: true, value: budget };
}
/**
 * Validate level-type thinking value
 */
function validateLevelThinking(thinking, value, modelId) {
    const validLevels = thinking.levels ?? [];
    const maxLevel = thinking.maxLevel;
    // Helper to apply maxLevel cap and build result
    const applyMaxCap = (level, baseWarning) => {
        const { level: cappedLevel, capped } = capLevelAtMax(level, maxLevel);
        const warnings = [];
        if (baseWarning)
            warnings.push(baseWarning);
        if (capped) {
            warnings.push(`Level "${level}" exceeds max "${maxLevel}" for ${modelId}. Capped to "${cappedLevel}".`);
        }
        return {
            valid: true,
            value: cappedLevel,
            warning: warnings.length > 0 ? warnings.join(' ') : undefined,
        };
    };
    // If numeric, try to map to closest level by budget
    if (typeof value === 'number') {
        // Find closest level by comparing to level budgets
        let closestLevel = validLevels[0] ?? 'low';
        let closestDiff = Infinity;
        for (const level of validLevels) {
            const levelBudget = exports.THINKING_LEVEL_BUDGETS[level] ?? 8192;
            const diff = Math.abs(levelBudget - value);
            if (diff < closestDiff) {
                closestDiff = diff;
                closestLevel = level;
            }
        }
        return applyMaxCap(closestLevel, `Model ${modelId} uses named levels. Mapped budget ${value} to "${closestLevel}".`);
    }
    // String level
    const normalizedLevel = value.toLowerCase().trim();
    // Check if it's a valid level for this model
    if (validLevels.includes(normalizedLevel)) {
        return applyMaxCap(normalizedLevel);
    }
    // Try to find closest match
    const closest = findClosestLevel(normalizedLevel, validLevels);
    if (closest) {
        return applyMaxCap(closest, `Level "${value}" not valid for ${modelId}. Mapped to "${closest}".`);
    }
    // If the input is a standard named level but this model only supports a subset,
    // choose the highest supported level that does not exceed the requested intensity.
    const requestedRank = exports.THINKING_LEVEL_RANK[normalizedLevel];
    if (requestedRank && validLevels.length > 0) {
        const rankedValidLevels = validLevels
            .filter((level) => exports.THINKING_LEVEL_RANK[level] !== undefined)
            .sort((a, b) => (exports.THINKING_LEVEL_RANK[a] ?? 0) - (exports.THINKING_LEVEL_RANK[b] ?? 0));
        if (rankedValidLevels.length > 0) {
            let mappedLevel = rankedValidLevels[0];
            for (const level of rankedValidLevels) {
                const levelRank = exports.THINKING_LEVEL_RANK[level] ?? 0;
                if (levelRank <= requestedRank) {
                    mappedLevel = level;
                    continue;
                }
                break;
            }
            return applyMaxCap(mappedLevel, `Level "${value}" mapped to "${mappedLevel}" for ${modelId} (available: ${validLevels.join(', ')}).`);
        }
    }
    // Try to map from standard level names to model's levels
    const standardToModelLevel = {};
    const levelOrder = ['minimal', 'low', 'medium', 'high', 'xhigh'];
    // Build mapping based on position
    for (let i = 0; i < validLevels.length; i++) {
        // Map first half of standard levels to lower model levels, second half to higher
        const standardIdx = Math.floor((i / validLevels.length) * levelOrder.length);
        for (let j = standardIdx; j < levelOrder.length; j++) {
            if (!standardToModelLevel[levelOrder[j]]) {
                standardToModelLevel[levelOrder[j]] = validLevels[Math.min(i, validLevels.length - 1)];
            }
        }
    }
    const mapped = standardToModelLevel[normalizedLevel];
    if (mapped) {
        return applyMaxCap(mapped, `Level "${value}" mapped to "${mapped}" for ${modelId} (available: ${validLevels.join(', ')}).`);
    }
    // Default to first level
    const defaultLevel = validLevels[0] ?? 'low';
    return {
        valid: false,
        value: defaultLevel,
        warning: `Invalid level "${value}" for ${modelId}. Using "${defaultLevel}" (available: ${validLevels.join(', ')}).`,
    };
}
//# sourceMappingURL=thinking-validator.js.map