"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.description = void 0;
exports.execute = execute;
const agents_1 = require("@langchain/classic/agents");
const openai_assistant_1 = require("@langchain/classic/experimental/openai_assistant");
const omit_1 = __importDefault(require("lodash/omit"));
const n8n_workflow_1 = require("n8n-workflow");
const openai_1 = require("openai");
const descriptions_1 = require("../../../../../../utils/descriptions");
const helpers_1 = require("../../../../../../utils/helpers");
const tracing_1 = require("../../../../../../utils/tracing");
const utils_1 = require("../../../helpers/utils");
const descriptions_2 = require("../descriptions");
const ai_utilities_1 = require("@n8n/ai-utilities");
const di_1 = require("@n8n/di");
const config_1 = require("@n8n/config");
const properties = [
    descriptions_2.assistantRLC,
    {
        ...descriptions_1.promptTypeOptionsDeprecated,
        name: 'prompt',
    },
    {
        displayName: 'Prompt (User Message)',
        name: 'text',
        type: 'string',
        default: '',
        placeholder: 'e.g. Hello, how can you help me?',
        typeOptions: {
            rows: 2,
        },
        displayOptions: {
            show: {
                prompt: ['define'],
            },
        },
    },
    {
        displayName: 'Memory',
        name: 'memory',
        type: 'options',
        options: [
            {
                name: 'Use memory connector',
                value: 'connector',
                description: 'Connect one of the supported memory nodes',
            },
            {
                name: 'Use thread ID',
                value: 'threadId',
                description: 'Specify the ID of the thread to continue',
            },
        ],
        displayOptions: {
            show: {
                '@version': [{ _cnd: { gte: 1.6 } }],
            },
        },
        default: 'connector',
    },
    {
        displayName: 'Thread ID',
        name: 'threadId',
        type: 'string',
        default: '',
        placeholder: '',
        description: 'The ID of the thread to continue, a new thread will be created if not specified',
        hint: 'If the thread ID is empty or undefined a new thread will be created and included in the response',
        displayOptions: {
            show: {
                '@version': [{ _cnd: { gte: 1.6 } }],
                memory: ['threadId'],
            },
        },
    },
    {
        displayName: 'Connect your own custom n8n tools to this node on the canvas',
        name: 'noticeTools',
        type: 'notice',
        default: '',
    },
    {
        displayName: 'Options',
        name: 'options',
        placeholder: 'Add Option',
        description: 'Additional options to add',
        type: 'collection',
        default: {},
        options: [
            {
                displayName: 'Base URL',
                name: 'baseURL',
                default: 'https://api.openai.com/v1',
                description: 'Override the default base URL for the API',
                type: 'string',
                displayOptions: {
                    hide: {
                        '@version': [{ _cnd: { gte: 1.8 } }],
                    },
                },
            },
            {
                displayName: 'Max Retries',
                name: 'maxRetries',
                default: 2,
                description: 'Maximum number of retries to attempt',
                type: 'number',
            },
            {
                displayName: 'Timeout',
                name: 'timeout',
                default: 10000,
                description: 'Maximum amount of time a request is allowed to take in milliseconds',
                type: 'number',
            },
            {
                displayName: 'Preserve Original Tools',
                name: 'preserveOriginalTools',
                type: 'boolean',
                default: true,
                description: 'Whether to preserve the original tools of the assistant after the execution of this node, otherwise the tools will be replaced with the connected tools, if any, default is true',
                displayOptions: {
                    show: {
                        '@version': [{ _cnd: { gte: 1.3 } }],
                    },
                },
            },
        ],
    },
];
const displayOptions = {
    show: {
        operation: ['message'],
        resource: ['assistant'],
    },
};
exports.description = (0, n8n_workflow_1.updateDisplayOptions)(displayOptions, properties);
const mapChatMessageToThreadMessage = (message) => ({
    role: message._getType() === 'ai' ? 'assistant' : 'user',
    content: message.content.toString(),
});
async function execute(i) {
    const credentials = await this.getCredentials('openAiApi');
    const nodeVersion = this.getNode().typeVersion;
    const input = (0, helpers_1.getPromptInputByType)({
        ctx: this,
        i,
        inputKey: 'text',
        promptTypeKey: 'prompt',
    });
    const assistantId = this.getNodeParameter('assistantId', i, '', { extractValue: true });
    const options = this.getNodeParameter('options', i, {});
    if (options.baseURL) {
        (0, n8n_workflow_1.assertCredentialAllowsUrl)({
            node: this.getNode(),
            credentialData: credentials,
            url: options.baseURL,
            pinnedUrl: typeof credentials.url === 'string' ? credentials.url : undefined,
            surface: 'OpenAI',
        });
    }
    const baseURL = (options.baseURL ?? credentials.url);
    const { openAiDefaultHeaders } = di_1.Container.get(config_1.AiConfig);
    const defaultHeaders = (0, helpers_1.mergeCustomHeaders)(credentials, openAiDefaultHeaders ?? {});
    const timeout = options.timeout;
    const client = new openai_1.OpenAI({
        apiKey: credentials.apiKey,
        maxRetries: options.maxRetries ?? 2,
        timeout: timeout ?? 10000,
        baseURL,
        fetchOptions: {
            dispatcher: (0, ai_utilities_1.getProxyAgent)(baseURL, {
                headersTimeout: timeout,
                bodyTimeout: timeout,
            }),
        },
        defaultHeaders,
    });
    const agent = new openai_assistant_1.OpenAIAssistantRunnable({ assistantId, client, asAgent: true });
    const tools = await (0, helpers_1.getConnectedTools)(this, nodeVersion > 1, false);
    let assistantTools;
    if (tools.length) {
        const transformedConnectedTools = tools?.map(utils_1.formatToOpenAIAssistantTool) ?? [];
        const nativeToolsParsed = [];
        assistantTools = (await client.beta.assistants.retrieve(assistantId)).tools;
        const useCodeInterpreter = assistantTools.some((tool) => tool.type === 'code_interpreter');
        if (useCodeInterpreter) {
            nativeToolsParsed.push({
                type: 'code_interpreter',
            });
        }
        const useRetrieval = assistantTools.some((tool) => tool.type === 'file_search');
        if (useRetrieval) {
            nativeToolsParsed.push({
                type: 'file_search',
            });
        }
        await client.beta.assistants.update(assistantId, {
            tools: [...nativeToolsParsed, ...transformedConnectedTools],
        });
    }
    const agentExecutor = agents_1.AgentExecutor.fromAgentAndTools({
        agent,
        tools: tools ?? [],
    });
    const useMemoryConnector = nodeVersion >= 1.6 && this.getNodeParameter('memory', i) === 'connector';
    const memory = useMemoryConnector || nodeVersion < 1.6
        ? (await this.getInputConnectionData(n8n_workflow_1.NodeConnectionTypes.AiMemory, 0))
        : undefined;
    const threadId = nodeVersion >= 1.6 && !useMemoryConnector
        ? this.getNodeParameter('threadId', i)
        : undefined;
    const chainValues = {
        content: input,
        signal: this.getExecutionCancelSignal(),
        timeout: options.timeout ?? 10000,
    };
    let thread;
    if (memory) {
        const chatMessages = await (0, utils_1.getChatMessages)(memory);
        if (chatMessages.length) {
            const first32Messages = chatMessages.slice(0, 32);
            const mappedMessages = first32Messages.map(mapChatMessageToThreadMessage);
            thread = await client.beta.threads.create({ messages: mappedMessages });
            const overLimitMessages = chatMessages.slice(32).map(mapChatMessageToThreadMessage);
            for (const message of overLimitMessages) {
                await client.beta.threads.messages.create(thread.id, message);
            }
            chainValues.threadId = thread.id;
        }
    }
    else if (threadId) {
        chainValues.threadId = threadId;
    }
    let filteredResponse = {};
    try {
        const response = await agentExecutor.withConfig((0, tracing_1.getTracingConfig)(this)).invoke(chainValues);
        if (memory) {
            await memory.saveContext({ input }, { output: response.output });
            if (response.threadId && response.runId) {
                const threadRun = await client.beta.threads.runs.retrieve(response.runId, {
                    thread_id: response.threadId,
                });
                response.usage = threadRun.usage;
            }
        }
        if (options.preserveOriginalTools !== false &&
            nodeVersion >= 1.3 &&
            (assistantTools ?? [])?.length) {
            await client.beta.assistants.update(assistantId, {
                tools: assistantTools,
            });
        }
        filteredResponse = (0, omit_1.default)(response, ['signal', 'timeout', 'content', 'runId']);
    }
    catch (error) {
        if (!(error instanceof n8n_workflow_1.ApplicationError) && !(error instanceof n8n_workflow_1.BaseError)) {
            throw new n8n_workflow_1.NodeOperationError(this.getNode(), error.message, { itemIndex: i });
        }
    }
    return [{ json: filteredResponse, pairedItem: { item: i } }];
}
//# sourceMappingURL=message.operation.js.map