"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.OpenAICodexSDKProvider = void 0;
const crypto_1 = __importDefault(require("crypto"));
const fs_1 = __importDefault(require("fs"));
const path_1 = __importDefault(require("path"));
const dedent_1 = __importDefault(require("dedent"));
const cliState_1 = __importDefault(require("../../cliState"));
const envars_1 = require("../../envars");
const esm_1 = require("../../esm");
const logger_1 = __importDefault(require("../../logger"));
/**
 * Helper to load the OpenAI Codex SDK ESM module
 * Uses importModule utility which handles ESM loading in CommonJS environments
 */
async function loadCodexSDK() {
    try {
        // Resolve the package path, then use importModule to load it
        // The SDK is ESM-only, so we need the importModule utility which uses dynamic-import.cjs
        const basePath = cliState_1.default.basePath && path_1.default.isAbsolute(cliState_1.default.basePath) ? cliState_1.default.basePath : process.cwd();
        // The package only exports ESM, so we construct the direct path
        // The SDK is installed in node_modules/@openai/codex-sdk/dist/index.js
        const modulePath = path_1.default.join(basePath, 'node_modules', '@openai', 'codex-sdk', 'dist', 'index.js');
        return await (0, esm_1.importModule)(modulePath);
    }
    catch (err) {
        logger_1.default.error(`Failed to load OpenAI Codex SDK: ${err}`);
        if (err.stack) {
            logger_1.default.error(err.stack);
        }
        throw new Error((0, dedent_1.default) `The @openai/codex-sdk package is required but not installed.

      This package may have a proprietary license and is not installed by default.

      To use the OpenAI Codex SDK provider, install it with:
        npm install @openai/codex-sdk

      Requires Node.js 18+.

      For more information, see: https://www.promptfoo.dev/docs/providers/openai-codex-sdk/`);
    }
}
// Pricing per 1M tokens (as of November 2025)
// See: https://openai.com/pricing
const CODEX_MODEL_PRICING = {
    'gpt-5.1-codex': { input: 2.0, output: 8.0 },
    'gpt-5.1-codex-max': { input: 3.0, output: 12.0 },
    'gpt-5.1-codex-mini': { input: 0.5, output: 2.0 },
    'gpt-5-codex': { input: 2.0, output: 8.0 },
    'gpt-5-codex-mini': { input: 0.5, output: 2.0 },
    'gpt-5': { input: 2.0, output: 8.0 },
    'gpt-4o': { input: 2.5, output: 10.0 },
    'gpt-4o-mini': { input: 0.15, output: 0.6 },
    'gpt-4-turbo': { input: 10.0, output: 30.0 },
    'gpt-4': { input: 30.0, output: 60.0 },
    'o3-mini': { input: 1.1, output: 4.4 },
    o1: { input: 15.0, output: 60.0 },
    'o1-mini': { input: 3.0, output: 12.0 },
};
class OpenAICodexSDKProvider {
    constructor(options = {}) {
        this.providerId = 'openai:codex-sdk';
        this.threads = new Map();
        const { config, env, id } = options;
        this.config = config ?? {};
        this.env = env;
        this.apiKey = this.getApiKey();
        this.providerId = id ?? this.providerId;
        if (this.config.model && !OpenAICodexSDKProvider.OPENAI_MODELS.includes(this.config.model)) {
            logger_1.default.warn(`Using unknown model for OpenAI Codex SDK: ${this.config.model}`);
        }
        if (this.config.fallback_model &&
            !OpenAICodexSDKProvider.OPENAI_MODELS.includes(this.config.fallback_model)) {
            logger_1.default.warn(`Using unknown model for OpenAI Codex SDK fallback: ${this.config.fallback_model}`);
        }
    }
    id() {
        return this.providerId;
    }
    getApiKey() {
        return (this.config?.apiKey ||
            this.env?.OPENAI_API_KEY ||
            this.env?.CODEX_API_KEY ||
            (0, envars_1.getEnvString)('OPENAI_API_KEY') ||
            (0, envars_1.getEnvString)('CODEX_API_KEY'));
    }
    toString() {
        return '[OpenAI Codex SDK Provider]';
    }
    async cleanup() {
        this.threads.clear();
    }
    prepareEnvironment(config) {
        const env = config.cli_env
            ? { ...config.cli_env }
            : { ...process.env };
        // Sort keys for stable cache key generation
        const sortedEnv = {};
        for (const key of Object.keys(env).sort()) {
            if (env[key] !== undefined) {
                sortedEnv[key] = env[key];
            }
        }
        // Inject API key
        if (this.apiKey) {
            sortedEnv.OPENAI_API_KEY = this.apiKey;
            sortedEnv.CODEX_API_KEY = this.apiKey;
        }
        // Inject env overrides
        if (this.env) {
            for (const key of Object.keys(this.env).sort()) {
                const value = this.env[key];
                if (value !== undefined) {
                    sortedEnv[key] = value;
                }
            }
        }
        return sortedEnv;
    }
    validateWorkingDirectory(workingDir, skipGitCheck = false) {
        let stats;
        try {
            stats = fs_1.default.statSync(workingDir);
        }
        catch (err) {
            throw new Error(`Working directory ${workingDir} does not exist or isn't accessible: ${err.message}`);
        }
        if (!stats.isDirectory()) {
            throw new Error(`Working directory ${workingDir} is not a directory`);
        }
        if (!skipGitCheck) {
            const gitDir = path_1.default.join(workingDir, '.git');
            if (!fs_1.default.existsSync(gitDir)) {
                throw new Error((0, dedent_1.default) `Working directory ${workingDir} is not a Git repository.

          Codex requires a Git repository by default to prevent unrecoverable errors.

          To bypass this check, set skip_git_repo_check: true in your provider config.`);
            }
        }
    }
    async getOrCreateThread(config, cacheKey) {
        // Resume specific thread
        if (config.thread_id) {
            const cached = this.threads.get(config.thread_id);
            if (cached) {
                return cached;
            }
            const thread = this.codexInstance.resumeThread(config.thread_id);
            if (config.persist_threads) {
                this.threads.set(config.thread_id, thread);
            }
            return thread;
        }
        // Use pooled thread
        if (config.persist_threads && cacheKey) {
            const cached = this.threads.get(cacheKey);
            if (cached) {
                return cached;
            }
            // Enforce pool size limit
            const poolSize = config.thread_pool_size ?? 1;
            if (this.threads.size >= poolSize) {
                const oldestKey = this.threads.keys().next().value;
                if (oldestKey) {
                    this.threads.delete(oldestKey);
                }
            }
        }
        // Create new thread
        const thread = this.codexInstance.startThread({
            workingDirectory: config.working_dir,
            skipGitRepoCheck: config.skip_git_repo_check ?? false,
            ...(config.model ? { model: config.model } : {}),
            ...(config.additional_directories?.length
                ? { additionalDirectories: config.additional_directories }
                : {}),
        });
        if (config.persist_threads && cacheKey) {
            this.threads.set(cacheKey, thread);
        }
        return thread;
    }
    async runStreaming(thread, prompt, runOptions, callOptions) {
        const { events } = await thread.runStreamed(prompt, runOptions);
        const items = [];
        let usage = undefined;
        for await (const event of events) {
            // Check abort signal
            if (callOptions?.abortSignal?.aborted) {
                throw new Error('AbortError');
            }
            switch (event.type) {
                case 'item.completed':
                    items.push(event.item);
                    logger_1.default.debug(`Codex item completed: ${JSON.stringify(event.item)}`);
                    break;
                case 'turn.completed':
                    usage = event.usage;
                    logger_1.default.debug(`Codex turn completed: ${JSON.stringify(usage)}`);
                    break;
            }
        }
        // Extract text from agent_message items for final response
        const agentMessages = items.filter((i) => i.type === 'agent_message');
        const finalResponse = agentMessages.length > 0 ? agentMessages.map((i) => i.text).join('\n') : '';
        return {
            finalResponse,
            items,
            usage,
        };
    }
    generateCacheKey(config, prompt) {
        const keyData = {
            working_dir: config.working_dir,
            additional_directories: config.additional_directories,
            model: config.model,
            output_schema: config.output_schema,
            tool_output_token_limit: config.tool_output_token_limit,
            prompt,
        };
        const hash = crypto_1.default.createHash('sha256').update(JSON.stringify(keyData)).digest('hex');
        return `openai:codex-sdk:${hash}`;
    }
    async callApi(prompt, context, callOptions) {
        // Merge configs (prompt config takes precedence)
        const config = {
            ...this.config,
            ...context?.prompt?.config,
        };
        // Prepare environment
        const env = this.prepareEnvironment(config);
        if (!this.apiKey && !env.OPENAI_API_KEY && !env.CODEX_API_KEY) {
            throw new Error('OpenAI API key is not set. Set OPENAI_API_KEY or CODEX_API_KEY environment variable or add "apiKey" to provider config.');
        }
        // Validate working directory
        if (config.working_dir) {
            this.validateWorkingDirectory(config.working_dir, config.skip_git_repo_check);
        }
        // Check abort signal
        if (callOptions?.abortSignal?.aborted) {
            return { error: 'OpenAI Codex SDK call aborted before it started' };
        }
        // Load SDK module (lazy)
        if (!this.codexModule) {
            this.codexModule = await loadCodexSDK();
        }
        // Initialize Codex instance (lazy)
        if (!this.codexInstance) {
            this.codexInstance = new this.codexModule.Codex({
                env,
                ...(config.codex_path_override ? { codexPathOverride: config.codex_path_override } : {}),
            });
        }
        // Get or create thread
        const cacheKey = this.generateCacheKey(config, prompt);
        const thread = await this.getOrCreateThread(config, cacheKey);
        // Prepare run options
        const runOptions = {};
        if (config.output_schema) {
            runOptions.outputSchema = config.output_schema;
        }
        if (config.tool_output_token_limit !== undefined) {
            runOptions.toolOutputTokenLimit = config.tool_output_token_limit;
        }
        // Execute turn
        try {
            const turn = config.enable_streaming
                ? await this.runStreaming(thread, prompt, runOptions, callOptions)
                : await thread.run(prompt, runOptions);
            // Extract response
            const output = turn.finalResponse || '';
            const raw = JSON.stringify(turn);
            const tokenUsage = turn.usage
                ? {
                    prompt: turn.usage.input_tokens + (turn.usage.cached_input_tokens || 0),
                    completion: turn.usage.output_tokens,
                    total: turn.usage.input_tokens +
                        (turn.usage.cached_input_tokens || 0) +
                        turn.usage.output_tokens,
                }
                : undefined;
            // Calculate cost from usage
            let cost = 0;
            if (tokenUsage && config.model) {
                const pricing = CODEX_MODEL_PRICING[config.model];
                if (pricing) {
                    // Pricing is per 1M tokens
                    const inputCost = (tokenUsage.prompt || 0) * (pricing.input / 1000000);
                    const outputCost = (tokenUsage.completion || 0) * (pricing.output / 1000000);
                    cost = inputCost + outputCost;
                }
            }
            logger_1.default.debug(`OpenAI Codex SDK response: ${output} ${JSON.stringify(turn.usage)}`);
            return {
                output,
                tokenUsage,
                cost,
                raw,
                sessionId: thread.id || 'unknown',
            };
        }
        catch (error) {
            const isAbort = error?.name === 'AbortError' || callOptions?.abortSignal?.aborted;
            if (isAbort) {
                logger_1.default.warn('OpenAI Codex SDK call aborted');
                return { error: 'OpenAI Codex SDK call aborted' };
            }
            logger_1.default.error(`Error calling OpenAI Codex SDK: ${error.message}`);
            return {
                error: `Error calling OpenAI Codex SDK: ${error.message}`,
            };
        }
        finally {
            // Clean up ephemeral threads
            if (!config.persist_threads && !config.thread_id && cacheKey) {
                this.threads.delete(cacheKey);
            }
        }
    }
}
exports.OpenAICodexSDKProvider = OpenAICodexSDKProvider;
OpenAICodexSDKProvider.OPENAI_MODELS = [
    // GPT-5.1 Codex models (recommended for code tasks)
    'gpt-5.1-codex',
    'gpt-5.1-codex-max',
    'gpt-5.1-codex-mini',
    // GPT-5 Codex models
    'gpt-5-codex',
    'gpt-5-codex-mini',
    // GPT-5 base
    'gpt-5',
    // GPT-4 models
    'gpt-4',
    'gpt-4-turbo',
    'gpt-4o',
    'gpt-4o-mini',
    // Reasoning models
    'o1',
    'o1-mini',
    'o3-mini',
];
//# sourceMappingURL=codex-sdk.js.map