"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultModerationProvider = exports.DefaultSuggestionsProvider = exports.DefaultGradingJsonProvider = exports.DefaultGradingProvider = exports.DefaultEmbeddingProvider = exports.OpenAiModerationProvider = exports.OpenAiImageProvider = exports.OpenAiAssistantProvider = exports.OpenAiChatCompletionProvider = exports.OpenAiCompletionProvider = exports.OpenAiEmbeddingProvider = exports.OpenAiGenericProvider = void 0;
const openai_1 = __importDefault(require("openai"));
const logger_1 = __importDefault(require("../logger"));
const cache_1 = require("../cache");
const util_1 = require("../util");
const shared_1 = require("./shared");
const util_2 = require("../util");
function failApiCall(err) {
    if (err instanceof openai_1.default.APIError) {
        return {
            error: `API error: ${err.type} ${err.message}`,
        };
    }
    return {
        error: `API error: ${String(err)}`,
    };
}
function getTokenUsage(data, cached) {
    if (data.usage) {
        if (cached) {
            return { cached: data.usage.total_tokens, total: data.usage.total_tokens };
        }
        else {
            return {
                total: data.usage.total_tokens,
                prompt: data.usage.prompt_tokens || 0,
                completion: data.usage.completion_tokens || 0,
            };
        }
    }
    return {};
}
class OpenAiGenericProvider {
    constructor(modelName, options = {}) {
        const { config, id, env } = options;
        this.env = env;
        this.modelName = modelName;
        this.config = config || {};
        this.id = id ? () => id : this.id;
    }
    id() {
        return this.config.apiHost || this.config.apiBaseUrl
            ? this.modelName
            : `openai:${this.modelName}`;
    }
    toString() {
        return `[OpenAI Provider ${this.modelName}]`;
    }
    getOrganization() {
        return (this.config.organization || this.env?.OPENAI_ORGANIZATION || process.env.OPENAI_ORGANIZATION);
    }
    getApiUrlDefault() {
        return 'https://api.openai.com/v1';
    }
    getApiUrl() {
        const apiHost = this.config.apiHost || this.env?.OPENAI_API_HOST || process.env.OPENAI_API_HOST;
        if (apiHost) {
            return `https://${apiHost}/v1`;
        }
        return (this.config.apiBaseUrl ||
            this.env?.OPENAI_API_BASE_URL ||
            this.env?.OPENAI_BASE_URL ||
            process.env.OPENAI_API_BASE_URL ||
            process.env.OPENAI_BASE_URL ||
            this.getApiUrlDefault());
    }
    getApiKey() {
        return (this.config.apiKey ||
            (this.config?.apiKeyEnvar
                ? process.env[this.config.apiKeyEnvar] ||
                    this.env?.[this.config.apiKeyEnvar]
                : undefined) ||
            this.env?.OPENAI_API_KEY ||
            process.env.OPENAI_API_KEY);
    }
    // @ts-ignore: Params are not used in this implementation
    async callApi(prompt, context, callApiOptions) {
        throw new Error('Not implemented');
    }
}
exports.OpenAiGenericProvider = OpenAiGenericProvider;
class OpenAiEmbeddingProvider extends OpenAiGenericProvider {
    async callEmbeddingApi(text) {
        if (!this.getApiKey()) {
            throw new Error('OpenAI API key must be set for similarity comparison');
        }
        const body = {
            input: text,
            model: this.modelName,
        };
        let data, cached = false;
        try {
            ({ data, cached } = (await (0, cache_1.fetchWithCache)(`${this.getApiUrl()}/embeddings`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    Authorization: `Bearer ${this.getApiKey()}`,
                    ...(this.getOrganization() ? { 'OpenAI-Organization': this.getOrganization() } : {}),
                    ...this.config.headers,
                },
                body: JSON.stringify(body),
            }, shared_1.REQUEST_TIMEOUT_MS)));
        }
        catch (err) {
            logger_1.default.error(`API call error: ${err}`);
            throw err;
        }
        logger_1.default.debug(`\tOpenAI embeddings API response: ${JSON.stringify(data)}`);
        try {
            const embedding = data?.data?.[0]?.embedding;
            if (!embedding) {
                throw new Error('No embedding found in OpenAI embeddings API response');
            }
            return {
                embedding,
                tokenUsage: getTokenUsage(data, cached),
            };
        }
        catch (err) {
            logger_1.default.error(data.error.message);
            throw err;
        }
    }
}
exports.OpenAiEmbeddingProvider = OpenAiEmbeddingProvider;
class OpenAiCompletionProvider extends OpenAiGenericProvider {
    constructor(modelName, options = {}) {
        super(modelName, options);
        this.config = options.config || {};
        if (!OpenAiCompletionProvider.OPENAI_COMPLETION_MODEL_NAMES.includes(modelName) &&
            this.getApiUrl() === this.getApiUrlDefault()) {
            logger_1.default.warn(`FYI: Using unknown OpenAI completion model: ${modelName}`);
        }
    }
    async callApi(prompt, context, callApiOptions) {
        if (!this.getApiKey()) {
            throw new Error('OpenAI API key is not set. Set the OPENAI_API_KEY environment variable or add `apiKey` to the provider config.');
        }
        let stop;
        try {
            stop = process.env.OPENAI_STOP
                ? JSON.parse(process.env.OPENAI_STOP)
                : this.config?.stop || ['<|im_end|>', '<|endoftext|>'];
        }
        catch (err) {
            throw new Error(`OPENAI_STOP is not a valid JSON string: ${err}`);
        }
        const body = {
            model: this.modelName,
            prompt,
            seed: this.config.seed || 0,
            max_tokens: this.config.max_tokens ?? parseInt(process.env.OPENAI_MAX_TOKENS || '1024'),
            temperature: this.config.temperature ?? parseFloat(process.env.OPENAI_TEMPERATURE || '0'),
            top_p: this.config.top_p ?? parseFloat(process.env.OPENAI_TOP_P || '1'),
            presence_penalty: this.config.presence_penalty ?? parseFloat(process.env.OPENAI_PRESENCE_PENALTY || '0'),
            frequency_penalty: this.config.frequency_penalty ?? parseFloat(process.env.OPENAI_FREQUENCY_PENALTY || '0'),
            best_of: this.config.best_of ?? parseInt(process.env.OPENAI_BEST_OF || '1'),
            ...(callApiOptions?.includeLogProbs ? { logprobs: callApiOptions.includeLogProbs } : {}),
            ...(stop ? { stop } : {}),
            ...(this.config.passthrough || {}),
        };
        logger_1.default.debug(`Calling OpenAI API: ${JSON.stringify(body)}`);
        let data, cached = false;
        try {
            ({ data, cached } = (await (0, cache_1.fetchWithCache)(`${this.getApiUrl()}/completions`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    Authorization: `Bearer ${this.getApiKey()}`,
                    ...(this.getOrganization() ? { 'OpenAI-Organization': this.getOrganization() } : {}),
                    ...this.config.headers,
                },
                body: JSON.stringify(body),
            }, shared_1.REQUEST_TIMEOUT_MS)));
        }
        catch (err) {
            return {
                error: `API call error: ${String(err)}`,
            };
        }
        logger_1.default.debug(`\tOpenAI completions API response: ${JSON.stringify(data)}`);
        if (data.error) {
            return {
                error: formatOpenAiError(data),
            };
        }
        try {
            return {
                output: data.choices[0].text,
                tokenUsage: getTokenUsage(data, cached),
                cached,
                cost: calculateCost(this.modelName, this.config, data.usage?.prompt_tokens, data.usage?.completion_tokens),
            };
        }
        catch (err) {
            return {
                error: `API error: ${String(err)}: ${JSON.stringify(data)}`,
            };
        }
    }
}
exports.OpenAiCompletionProvider = OpenAiCompletionProvider;
OpenAiCompletionProvider.OPENAI_COMPLETION_MODELS = [
    {
        id: 'gpt-3.5-turbo-instruct',
        cost: {
            input: 0.0015 / 1000,
            output: 0.002 / 1000,
        },
    },
    {
        id: 'gpt-3.5-turbo-instruct-0914',
        cost: {
            input: 0.0015 / 1000,
            output: 0.002 / 1000,
        },
    },
    {
        id: 'text-davinci-003',
    },
    {
        id: 'text-davinci-002',
    },
    {
        id: 'text-curie-001',
    },
    {
        id: 'text-babbage-001',
    },
    {
        id: 'text-ada-001',
    },
];
OpenAiCompletionProvider.OPENAI_COMPLETION_MODEL_NAMES = OpenAiCompletionProvider.OPENAI_COMPLETION_MODELS.map((model) => model.id);
class OpenAiChatCompletionProvider extends OpenAiGenericProvider {
    constructor(modelName, options = {}) {
        if (!OpenAiChatCompletionProvider.OPENAI_CHAT_MODEL_NAMES.includes(modelName)) {
            logger_1.default.debug(`Using unknown OpenAI chat model: ${modelName}`);
        }
        super(modelName, options);
        this.config = options.config || {};
    }
    async callApi(prompt, context, callApiOptions) {
        if (!this.getApiKey()) {
            throw new Error('OpenAI API key is not set. Set the OPENAI_API_KEY environment variable or add `apiKey` to the provider config.');
        }
        const messages = (0, shared_1.parseChatPrompt)(prompt, [{ role: 'user', content: prompt }]);
        const body = {
            model: this.modelName,
            messages: messages,
            seed: this.config.seed || 0,
            max_tokens: this.config.max_tokens ?? parseInt(process.env.OPENAI_MAX_TOKENS || '1024'),
            temperature: this.config.temperature ?? parseFloat(process.env.OPENAI_TEMPERATURE || '0'),
            top_p: this.config.top_p ?? parseFloat(process.env.OPENAI_TOP_P || '1'),
            presence_penalty: this.config.presence_penalty ?? parseFloat(process.env.OPENAI_PRESENCE_PENALTY || '0'),
            frequency_penalty: this.config.frequency_penalty ?? parseFloat(process.env.OPENAI_FREQUENCY_PENALTY || '0'),
            ...(this.config.functions
                ? { functions: (0, util_1.renderVarsInObject)(this.config.functions, context?.vars) }
                : {}),
            ...(this.config.function_call ? { function_call: this.config.function_call } : {}),
            ...(this.config.tools ? { tools: (0, util_1.renderVarsInObject)(this.config.tools, context?.vars) } : {}),
            ...(this.config.tool_choice ? { tool_choice: this.config.tool_choice } : {}),
            ...(this.config.response_format ? { response_format: this.config.response_format } : {}),
            ...(callApiOptions?.includeLogProbs ? { logprobs: callApiOptions.includeLogProbs } : {}),
            ...(this.config.stop ? { stop: this.config.stop } : {}),
            ...(this.config.passthrough || {}),
        };
        logger_1.default.debug(`Calling OpenAI API: ${JSON.stringify(body)}`);
        let data, cached = false;
        try {
            ({ data, cached } = (await (0, cache_1.fetchWithCache)(`${this.getApiUrl()}/chat/completions`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    Authorization: `Bearer ${this.getApiKey()}`,
                    ...(this.getOrganization() ? { 'OpenAI-Organization': this.getOrganization() } : {}),
                    ...this.config.headers,
                },
                body: JSON.stringify(body),
            }, shared_1.REQUEST_TIMEOUT_MS)));
        }
        catch (err) {
            return {
                error: `API call error: ${String(err)}`,
            };
        }
        logger_1.default.debug(`\tOpenAI chat completions API response: ${JSON.stringify(data)}`);
        if (data.error) {
            return {
                error: formatOpenAiError(data),
            };
        }
        try {
            const message = data.choices[0].message;
            let output = '';
            if (message.content && (message.function_call || message.tool_calls)) {
                output = message;
            }
            else if (message.content === null) {
                output = message.function_call || message.tool_calls;
            }
            else {
                output = message.content;
            }
            const logProbs = data.choices[0].logprobs?.content?.map((logProbObj) => logProbObj.logprob);
            // Handle function tool callbacks
            const functionCalls = message.function_call ? [message.function_call] : message.tool_calls;
            if (functionCalls && this.config.functionToolCallbacks) {
                for (const functionCall of functionCalls) {
                    const functionName = functionCall.name;
                    if (this.config.functionToolCallbacks[functionName]) {
                        const functionResult = await this.config.functionToolCallbacks[functionName](message.function_call.arguments);
                        return {
                            output: functionResult,
                            tokenUsage: getTokenUsage(data, cached),
                            cached,
                            logProbs,
                            cost: calculateCost(this.modelName, this.config, data.usage?.prompt_tokens, data.usage?.completion_tokens),
                        };
                    }
                }
            }
            return {
                output,
                tokenUsage: getTokenUsage(data, cached),
                cached,
                logProbs,
                cost: calculateCost(this.modelName, this.config, data.usage?.prompt_tokens, data.usage?.completion_tokens),
            };
        }
        catch (err) {
            return {
                error: `API error: ${String(err)}: ${JSON.stringify(data)}`,
            };
        }
    }
}
exports.OpenAiChatCompletionProvider = OpenAiChatCompletionProvider;
OpenAiChatCompletionProvider.OPENAI_CHAT_MODELS = [
    ...['gpt-4', 'gpt-4-0314', 'gpt-4-0613'].map((model) => ({
        id: model,
        cost: {
            input: 0.03 / 1000,
            output: 0.06 / 1000,
        },
    })),
    ...[
        'gpt-4-1106-preview',
        'gpt-4-1106-vision-preview',
        'gpt-4-0125-preview',
        'gpt-4-turbo-preview',
        'gpt-4-turbo',
    ].map((model) => ({
        id: model,
        cost: {
            input: 0.01 / 1000,
            output: 0.03 / 1000,
        },
    })),
    ...['gpt-4-32k', 'gpt-4-32k-0314'].map((model) => ({
        id: model,
        cost: {
            input: 0.06 / 1000,
            output: 0.12 / 1000,
        },
    })),
    ...['gpt-4o', 'gpt-4o-2024-05-13'].map((model) => ({
        id: model,
        cost: {
            input: 0.005 / 1000,
            output: 0.015 / 1000,
        },
    })),
    ...[
        'gpt-3.5-turbo',
        'gpt-3.5-turbo-0301',
        'gpt-3.5-turbo-0613',
        'gpt-3.5-turbo-1106',
        'gpt-3.5-turbo-0125',
        'gpt-3.5-turbo-16k',
        'gpt-3.5-turbo-16k-0613',
    ].map((model) => ({
        id: model,
        cost: {
            input: 0.0005 / 1000,
            output: 0.0015 / 1000,
        },
    })),
];
OpenAiChatCompletionProvider.OPENAI_CHAT_MODEL_NAMES = OpenAiChatCompletionProvider.OPENAI_CHAT_MODELS.map((model) => model.id);
function formatOpenAiError(data) {
    let errorMessage = `API error: ${data.error.message}`;
    if (data.error.type) {
        errorMessage += `, Type: ${data.error.type}`;
    }
    if (data.error.code) {
        errorMessage += `, Code: ${data.error.code}`;
    }
    errorMessage += '\n\n' + (0, util_2.safeJsonStringify)(data, true /* prettyPrint */);
    return errorMessage;
}
function calculateCost(modelName, config, promptTokens, completionTokens) {
    if (!promptTokens || !completionTokens) {
        return undefined;
    }
    const model = [
        ...OpenAiChatCompletionProvider.OPENAI_CHAT_MODELS,
        ...OpenAiCompletionProvider.OPENAI_COMPLETION_MODELS,
    ].find((m) => m.id === modelName);
    if (!model || !model.cost) {
        return undefined;
    }
    const inputCost = config.cost ?? model.cost.input;
    const outputCost = config.cost ?? model.cost.output;
    return inputCost * promptTokens + outputCost * completionTokens || undefined;
}
class OpenAiAssistantProvider extends OpenAiGenericProvider {
    constructor(assistantId, options = {}) {
        super(assistantId, options);
        this.assistantConfig = options.config || {};
        this.assistantId = assistantId;
    }
    async callApi(prompt, context, callApiOptions) {
        if (!this.getApiKey()) {
            throw new Error('OpenAI API key is not set. Set the OPENAI_API_KEY environment variable or add `apiKey` to the provider config.');
        }
        const openai = new openai_1.default({
            apiKey: this.getApiKey(),
            organization: this.getOrganization(),
            baseURL: this.getApiUrl(),
            maxRetries: 3,
            timeout: shared_1.REQUEST_TIMEOUT_MS,
            defaultHeaders: this.assistantConfig.headers,
        });
        const messages = (0, shared_1.parseChatPrompt)(prompt, [
            { role: 'user', content: prompt },
        ]);
        const body = {
            assistant_id: this.assistantId,
            model: this.assistantConfig.modelName || undefined,
            instructions: this.assistantConfig.instructions || undefined,
            tools: (0, util_1.renderVarsInObject)(this.assistantConfig.tools, context?.vars) || undefined,
            metadata: this.assistantConfig.metadata || undefined,
            temperature: this.assistantConfig.temperature || undefined,
            tool_choice: this.assistantConfig.toolChoice || undefined,
            thread: {
                messages,
            },
        };
        logger_1.default.debug(`Calling OpenAI API, creating thread run: ${JSON.stringify(body)}`);
        let run;
        try {
            run = await openai.beta.threads.createAndRun(body);
        }
        catch (err) {
            return failApiCall(err);
        }
        logger_1.default.debug(`\tOpenAI thread run API response: ${JSON.stringify(run)}`);
        while (run.status === 'in_progress' ||
            run.status === 'queued' ||
            run.status === 'requires_action') {
            if (run.status === 'requires_action') {
                const requiredAction = run.required_action;
                if (requiredAction === null || requiredAction.type !== 'submit_tool_outputs') {
                    break;
                }
                const functionCallsWithCallbacks = requiredAction.submit_tool_outputs.tool_calls.filter((toolCall) => {
                    return (toolCall.type === 'function' &&
                        toolCall.function.name in (this.assistantConfig.functionToolCallbacks ?? {}));
                });
                if (functionCallsWithCallbacks.length === 0) {
                    break;
                }
                logger_1.default.debug(`Calling functionToolCallbacks for functions: ${functionCallsWithCallbacks.map(({ function: { name } }) => name)}`);
                const toolOutputs = await Promise.all(functionCallsWithCallbacks.map(async (toolCall) => {
                    logger_1.default.debug(`Calling functionToolCallbacks[${toolCall.function.name}]('${toolCall.function.arguments}')`);
                    const result = await this.assistantConfig.functionToolCallbacks[toolCall.function.name](toolCall.function.arguments);
                    return {
                        tool_call_id: toolCall.id,
                        output: result,
                    };
                }));
                logger_1.default.debug(`Calling OpenAI API, submitting tool outputs for ${run.thread_id}: ${JSON.stringify(toolOutputs)}`);
                try {
                    run = await openai.beta.threads.runs.submitToolOutputs(run.thread_id, run.id, {
                        tool_outputs: toolOutputs,
                    });
                }
                catch (err) {
                    return failApiCall(err);
                }
                continue;
            }
            await new Promise((resolve) => setTimeout(resolve, 1000));
            logger_1.default.debug(`Calling OpenAI API, getting thread run ${run.id} status`);
            try {
                run = await openai.beta.threads.runs.retrieve(run.thread_id, run.id);
            }
            catch (err) {
                return failApiCall(err);
            }
            logger_1.default.debug(`\tOpenAI thread run API response: ${JSON.stringify(run)}`);
        }
        if (run.status !== 'completed' && run.status !== 'requires_action') {
            if (run.last_error) {
                return {
                    error: `Thread run failed: ${run.last_error.message}`,
                };
            }
            return {
                error: `Thread run failed: ${run.status}`,
            };
        }
        // Get run steps
        logger_1.default.debug(`Calling OpenAI API, getting thread run steps for ${run.thread_id}`);
        let steps;
        try {
            steps = await openai.beta.threads.runs.steps.list(run.thread_id, run.id, {
                order: 'asc',
            });
        }
        catch (err) {
            return failApiCall(err);
        }
        logger_1.default.debug(`\tOpenAI thread run steps API response: ${JSON.stringify(steps)}`);
        const outputBlocks = [];
        for (const step of steps.data) {
            if (step.step_details.type === 'message_creation') {
                logger_1.default.debug(`Calling OpenAI API, getting message ${step.id}`);
                let message;
                try {
                    message = await openai.beta.threads.messages.retrieve(run.thread_id, step.step_details.message_creation.message_id);
                }
                catch (err) {
                    return failApiCall(err);
                }
                logger_1.default.debug(`\tOpenAI thread run step message API response: ${JSON.stringify(message)}`);
                const content = message.content
                    .map((content) => content.type === 'text' ? content.text.value : `<${content.type} output>`)
                    .join('\n');
                outputBlocks.push(`[${(0, shared_1.toTitleCase)(message.role)}] ${content}`);
            }
            else if (step.step_details.type === 'tool_calls') {
                for (const toolCall of step.step_details.tool_calls) {
                    if (toolCall.type === 'function') {
                        outputBlocks.push(`[Call function ${toolCall.function.name} with arguments ${toolCall.function.arguments}]`);
                        outputBlocks.push(`[Function output: ${toolCall.function.output}]`);
                    }
                    else if (toolCall.type === 'file_search') {
                        outputBlocks.push(`[Ran file search]`);
                    }
                    else if (toolCall.type === 'code_interpreter') {
                        const output = toolCall.code_interpreter.outputs
                            .map((output) => (output.type === 'logs' ? output.logs : `<${output.type} output>`))
                            .join('\n');
                        outputBlocks.push(`[Code interpreter input]`);
                        outputBlocks.push(toolCall.code_interpreter.input);
                        outputBlocks.push(`[Code interpreter output]`);
                        outputBlocks.push(output);
                    }
                    else {
                        outputBlocks.push(`[Unknown tool call type: ${toolCall.type}]`);
                    }
                }
            }
            else {
                outputBlocks.push(`[Unknown step type: ${step.step_details.type}]`);
            }
        }
        return {
            output: outputBlocks.join('\n\n').trim(),
            /*
            tokenUsage: {
              total: data.usage.total_tokens,
              prompt: data.usage.prompt_tokens,
              completion: data.usage.completion_tokens,
            },
            */
        };
    }
}
exports.OpenAiAssistantProvider = OpenAiAssistantProvider;
class OpenAiImageProvider extends OpenAiGenericProvider {
    constructor(modelName, options = {}) {
        super(modelName, options);
        this.config = options.config || {};
    }
    async callApi(prompt, context, callApiOptions) {
        const cache = (0, cache_1.getCache)();
        const cacheKey = `openai:image:${(0, util_2.safeJsonStringify)({ context, prompt })}`;
        if (!this.getApiKey()) {
            throw new Error('OpenAI API key is not set. Set the OPENAI_API_KEY environment variable or add `apiKey` to the provider config.');
        }
        const openai = new openai_1.default({
            apiKey: this.getApiKey(),
            organization: this.getOrganization(),
            baseURL: this.getApiUrl(),
            maxRetries: 3,
            timeout: shared_1.REQUEST_TIMEOUT_MS,
            defaultHeaders: this.config.headers,
        });
        let response;
        let cached = false;
        if ((0, cache_1.isCacheEnabled)()) {
            // Try to get the cached response
            const cachedResponse = await cache.get(cacheKey);
            if (cachedResponse) {
                logger_1.default.debug(`Retrieved cached response for ${prompt}: ${cachedResponse}`);
                response = JSON.parse(cachedResponse);
                cached = true;
            }
        }
        if (!response) {
            response = await openai.images.generate({
                model: this.modelName,
                prompt,
                n: 1,
                size: (this.config.size || process.env.OPENAI_IMAGE_SIZE) || '1024x1024',
            });
        }
        const url = response.data[0].url;
        if (!url) {
            return {
                error: `No image URL found in response: ${JSON.stringify(response)}`,
            };
        }
        if (!cached && (0, cache_1.isCacheEnabled)()) {
            try {
                await cache.set(cacheKey, JSON.stringify(response));
            }
            catch (err) {
                logger_1.default.error(`Failed to cache response: ${String(err)}`);
            }
        }
        const sanitizedPrompt = prompt
            .replace(/\r?\n|\r/g, ' ')
            .replace(/\[/g, '(')
            .replace(/\]/g, ')');
        const ellipsizedPrompt = sanitizedPrompt.length > 50 ? `${sanitizedPrompt.substring(0, 47)}...` : sanitizedPrompt;
        return {
            output: `![${ellipsizedPrompt}](${url})`,
            cached,
        };
    }
}
exports.OpenAiImageProvider = OpenAiImageProvider;
class OpenAiModerationProvider extends OpenAiGenericProvider {
    async callModerationApi(userPrompt, // userPrompt is not supported by OpenAI moderation API
    assistantResponse) {
        if (!this.getApiKey()) {
            throw new Error('OpenAI API key is not set. Set the OPENAI_API_KEY environment variable or add `apiKey` to the provider config.');
        }
        const openai = new openai_1.default({
            apiKey: this.getApiKey(),
            organization: this.getOrganization(),
            baseURL: this.getApiUrl(),
            maxRetries: 3,
            timeout: shared_1.REQUEST_TIMEOUT_MS,
        });
        let cache;
        let cacheKey;
        if ((0, cache_1.isCacheEnabled)()) {
            cache = await (0, cache_1.getCache)();
            cacheKey = `openai:${this.modelName}:${JSON.stringify(this.config)}:${userPrompt}:${assistantResponse}`;
            // Try to get the cached response
            const cachedResponse = await cache.get(cacheKey);
            if (cachedResponse) {
                logger_1.default.debug(`Returning cached response for ${userPrompt}: ${cachedResponse}`);
                return JSON.parse(cachedResponse);
            }
        }
        logger_1.default.debug(`Calling OpenAI moderation API: prompt [${userPrompt}] assistant [${assistantResponse}]`);
        let moderation;
        try {
            moderation = await openai.moderations.create({
                model: this.modelName,
                input: assistantResponse,
            });
        }
        catch (err) {
            return {
                error: `API call error: ${String(err)}`,
            };
        }
        logger_1.default.debug(`\tOpenAI moderation API response: ${JSON.stringify(moderation)}`);
        try {
            const { results } = moderation;
            const flags = [];
            if (!results) {
                throw new Error('API response error: no results');
            }
            if (cache && cacheKey) {
                await cache.set(cacheKey, JSON.stringify(moderation));
            }
            if (results.length === 0) {
                return { flags };
            }
            for (const result of results) {
                if (result.flagged) {
                    for (const [category, flagged] of Object.entries(result.categories)) {
                        if (flagged) {
                            flags.push({
                                code: category,
                                description: category,
                                confidence: result.category_scores[category],
                            });
                        }
                    }
                }
            }
            return { flags };
        }
        catch (err) {
            return {
                error: `API response error: ${String(err)}: ${JSON.stringify(moderation)}`,
            };
        }
    }
}
exports.OpenAiModerationProvider = OpenAiModerationProvider;
exports.DefaultEmbeddingProvider = new OpenAiEmbeddingProvider('text-embedding-3-large');
exports.DefaultGradingProvider = new OpenAiChatCompletionProvider('gpt-4o');
exports.DefaultGradingJsonProvider = new OpenAiChatCompletionProvider('gpt-4o', {
    config: {
        response_format: { type: 'json_object' },
    },
});
exports.DefaultSuggestionsProvider = new OpenAiChatCompletionProvider('gpt-4o');
exports.DefaultModerationProvider = new OpenAiModerationProvider('text-moderation-latest');
//# sourceMappingURL=openai.js.map