"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.detectBlockedSqlKeyword = detectBlockedSqlKeyword;
exports.sqlAgentAgentExecute = sqlAgentAgentExecute;
const tools_1 = require("@langchain/core/tools");
const sql_1 = require("@langchain/classic/agents/toolkits/sql");
const sql_db_1 = require("@langchain/classic/sql_db");
const n8n_workflow_1 = require("n8n-workflow");
const helpers_1 = require("../../../../../utils/helpers");
const tracing_1 = require("../../../../../utils/tracing");
const mysql_1 = require("./other/handlers/mysql");
const postgres_1 = require("./other/handlers/postgres");
const sqlite_1 = require("./other/handlers/sqlite");
const prompts_1 = require("./other/prompts");
const parseTablesString = (tablesString) => tablesString
    .split(',')
    .map((table) => table.trim())
    .filter((table) => table.length > 0);
const BLOCKED_SQL_OPERATIONS = new Set([
    'INSERT',
    'UPDATE',
    'DELETE',
    'DROP',
    'TRUNCATE',
    'ALTER',
    'CREATE',
    'REPLACE',
    'MERGE',
]);
function detectBlockedSqlKeyword(sql) {
    const stripped = sql
        .replace(/--[^\n]*/g, '')
        .replace(/\/\*[\s\S]*?\*\//g, '')
        .replace(/'(?:[^'\\]|\\.)*'/g, "''")
        .replace(/"(?:[^"\\]|\\.)*"/g, '""');
    const pattern = new RegExp(`\\b(${[...BLOCKED_SQL_OPERATIONS].join('|')})\\b`, 'i');
    const match = stripped.match(pattern);
    return match ? match[1].toUpperCase() : undefined;
}
async function sqlAgentAgentExecute() {
    this.logger.debug('Executing SQL Agent');
    const model = (await this.getInputConnectionData(n8n_workflow_1.NodeConnectionTypes.AiLanguageModel, 0));
    const items = this.getInputData();
    const returnData = [];
    for (let i = 0; i < items.length; i++) {
        try {
            const item = items[i];
            let input;
            if (this.getNode().typeVersion <= 1.2) {
                input = this.getNodeParameter('input', i);
            }
            else {
                input = (0, helpers_1.getPromptInputByType)({
                    ctx: this,
                    i,
                    inputKey: 'text',
                    promptTypeKey: 'promptType',
                });
            }
            if (input === undefined) {
                throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'The ‘prompt’ parameter is empty.');
            }
            const options = this.getNodeParameter('options', i, {});
            const selectedDataSource = this.getNodeParameter('dataSource', i, 'sqlite');
            const includedSampleRows = options.includedSampleRows;
            const includedTablesArray = parseTablesString(options.includedTables ?? '');
            const ignoredTablesArray = parseTablesString(options.ignoredTables ?? '');
            let dataSource = null;
            if (selectedDataSource === 'sqlite') {
                if (!item.binary) {
                    throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No binary data found, please connect a binary to the input if you want to use SQLite as data source');
                }
                const binaryPropertyName = this.getNodeParameter('binaryPropertyName', i, 'data');
                dataSource = await sqlite_1.getSqliteDataSource.call(this, item.binary, binaryPropertyName);
            }
            if (selectedDataSource === 'postgres') {
                dataSource = await postgres_1.getPostgresDataSource.call(this);
            }
            if (selectedDataSource === 'mysql') {
                dataSource = await mysql_1.getMysqlDataSource.call(this);
            }
            if (!dataSource) {
                throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'No data source found, please configure data source');
            }
            const agentOptions = {
                topK: options.topK ?? 10,
                prefix: options.prefixPrompt ?? prompts_1.SQL_PREFIX,
                suffix: options.suffixPrompt ?? prompts_1.SQL_SUFFIX,
                inputVariables: ['chatHistory', 'input', 'agent_scratchpad'],
            };
            const dbInstance = await sql_db_1.SqlDatabase.fromDataSourceParams({
                appDataSource: dataSource,
                includesTables: includedTablesArray.length > 0 ? includedTablesArray : undefined,
                ignoreTables: ignoredTablesArray.length > 0 ? ignoredTablesArray : undefined,
                sampleRowsInTableInfo: includedSampleRows ?? 3,
            });
            const toolkit = new sql_1.SqlToolkit(dbInstance, model);
            const agentExecutor = (0, sql_1.createSqlAgent)(model, toolkit, agentOptions);
            agentExecutor.tools = agentExecutor.tools.map((tool) => {
                if (tool.name !== 'query-sql')
                    return tool;
                return new tools_1.DynamicTool({
                    name: tool.name,
                    description: tool.description,
                    func: async (sqlInput) => {
                        const blockedKeyword = detectBlockedSqlKeyword(sqlInput);
                        if (blockedKeyword) {
                            throw new n8n_workflow_1.NodeOperationError(this.getNode(), `SQL operation "${blockedKeyword}" is not allowed`, { itemIndex: i });
                        }
                        return String(await tool.invoke(sqlInput));
                    },
                });
            });
            const memory = (await this.getInputConnectionData(n8n_workflow_1.NodeConnectionTypes.AiMemory, 0));
            agentExecutor.memory = memory;
            let chatHistory = '';
            if (memory) {
                const messages = await memory.chatHistory.getMessages();
                chatHistory = (0, helpers_1.serializeChatHistory)(messages);
            }
            let response;
            const additionalMetadata = (0, tracing_1.buildTracingMetadata)(options.tracingMetadata?.values, this.logger);
            if (Object.keys(additionalMetadata).length > 0) {
                this.logger.debug('Tracing metadata', { additionalMetadata });
            }
            const tracingConfig = (0, tracing_1.getTracingConfig)(this, { additionalMetadata });
            try {
                response = await agentExecutor.withConfig(tracingConfig).invoke({
                    input,
                    signal: this.getExecutionCancelSignal(),
                    chatHistory,
                });
            }
            catch (error) {
                if (error.message?.output) {
                    response = error.message;
                }
                else {
                    throw new n8n_workflow_1.NodeOperationError(this.getNode(), error.message, { itemIndex: i });
                }
            }
            returnData.push({ json: response });
        }
        catch (error) {
            if (this.continueOnFail()) {
                returnData.push({ json: { error: error.message }, pairedItem: { item: i } });
                continue;
            }
            throw error;
        }
    }
    return [returnData];
}
//# sourceMappingURL=execute.js.map