"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAgentExecutor = createAgentExecutor;
exports.toolsAgentExecute = toolsAgentExecute;
const agents_1 = require("@langchain/classic/agents");
const base_1 = require("@langchain/core/callbacks/base");
const runnables_1 = require("@langchain/core/runnables");
const openai_1 = require("@langchain/openai");
const agent_execution_1 = require("../../../../../../utils/agent-execution");
const helpers_1 = require("../../../../../../utils/helpers");
const N8nOutputParser_1 = require("../../../../../../utils/output_parsers/N8nOutputParser");
const tracing_1 = require("../../../../../../utils/tracing");
const omit_1 = __importDefault(require("lodash/omit"));
const n8n_workflow_1 = require("n8n-workflow");
const node_assert_1 = __importDefault(require("node:assert"));
const utils_1 = require("../../utils");
const common_1 = require("../common");
const prompt_1 = require("../prompt");
function createAgentExecutor(model, tools, prompt, options, outputParser, memory, fallbackModel) {
    const agent = (0, agents_1.createToolCallingAgent)({
        llm: model,
        tools,
        prompt,
        streamRunnable: false,
    });
    let fallbackAgent;
    if (fallbackModel) {
        fallbackAgent = (0, agents_1.createToolCallingAgent)({
            llm: fallbackModel,
            tools,
            prompt,
            streamRunnable: false,
        });
    }
    const runnableAgent = runnables_1.RunnableSequence.from([
        fallbackAgent ? agent.withFallbacks([fallbackAgent]) : agent,
        (0, common_1.getAgentStepsParser)(outputParser, memory),
        common_1.fixEmptyContentMessage,
    ]);
    runnableAgent.singleAction = false;
    runnableAgent.streamRunnable = false;
    return agents_1.AgentExecutor.fromAgentAndTools({
        agent: runnableAgent,
        memory,
        tools,
        returnIntermediateSteps: options.returnIntermediateSteps === true,
        maxIterations: options.maxIterations ?? 10,
    });
}
class ToolCallCounterCallback extends base_1.BaseCallbackHandler {
    constructor() {
        super(...arguments);
        this.name = 'ToolCallCounterCallback';
        this.count = 0;
    }
    handleToolEnd() {
        this.count++;
    }
}
function applyAgentTracingMetadata(context, stats) {
    if (!(0, utils_1.isExecuteFunctions)(context))
        return;
    const tracing = {
        'ai.agent.version': 'v2',
        'ai.agent.streaming.enabled': stats.streamingEnabled,
        'ai.agent.items.total': stats.totalItems,
        'ai.agent.items.failed': stats.failedItems,
        'ai.agent.tool_calls.total': stats.toolCalls,
        'ai.agent.memory.loads': stats.memoryLoads ?? 0,
        'ai.agent.memory.saves': 0,
        'ai.agent.execution.succeeded': stats.failureType === undefined,
    };
    if (stats.failureType !== undefined) {
        tracing['ai.agent.failure.type'] = stats.failureType;
    }
    context.setMetadata({ tracing });
}
async function processEventStream(ctx, eventStream, itemIndex, returnIntermediateSteps = false) {
    const agentResult = {
        output: '',
    };
    if (returnIntermediateSteps) {
        agentResult.intermediateSteps = [];
    }
    ctx.sendChunk('begin', itemIndex);
    for await (const event of eventStream) {
        switch (event.event) {
            case 'on_chat_model_stream':
                const chunk = event.data?.chunk;
                if (chunk?.content) {
                    const chunkContent = chunk.content;
                    let chunkText = '';
                    if (Array.isArray(chunkContent)) {
                        for (const message of chunkContent) {
                            if (message?.type === 'text') {
                                chunkText += message?.text;
                            }
                        }
                    }
                    else if (typeof chunkContent === 'string') {
                        chunkText = chunkContent;
                    }
                    ctx.sendChunk('item', itemIndex, chunkText);
                    agentResult.output += chunkText;
                }
                break;
            case 'on_chat_model_end':
                if (returnIntermediateSteps && event.data) {
                    const chatModelData = event.data;
                    const output = chatModelData.output;
                    if (output?.tool_calls && output.tool_calls.length > 0) {
                        for (const toolCall of output.tool_calls) {
                            agentResult.intermediateSteps.push({
                                action: {
                                    tool: toolCall.name,
                                    toolInput: toolCall.args,
                                    log: output.content ||
                                        `Calling ${toolCall.name} with input: ${JSON.stringify(toolCall.args)}`,
                                    messageLog: [output],
                                    toolCallId: toolCall.id,
                                    type: toolCall.type,
                                },
                            });
                        }
                    }
                }
                break;
            case 'on_tool_end':
                if (returnIntermediateSteps && event.data && agentResult.intermediateSteps.length > 0) {
                    const toolData = event.data;
                    const matchingStep = agentResult.intermediateSteps.find((step) => !step.observation && step.action.tool === event.name);
                    if (matchingStep) {
                        matchingStep.observation = toolData.output;
                    }
                }
                break;
            default:
                break;
        }
    }
    ctx.sendChunk('end', itemIndex);
    return agentResult;
}
function checkIsResponsesApi(model) {
    try {
        const isUsingResponsesApi = !!model && model instanceof openai_1.ChatOpenAI && 'useResponsesApi' in model && model.useResponsesApi;
        return isUsingResponsesApi;
    }
    catch (error) {
        return false;
    }
}
async function toolsAgentExecute() {
    let toolCalls = 0;
    let failedItems = 0;
    let totalItems = 0;
    let enableStreaming = true;
    let failureType;
    let memoryLoads = 0;
    try {
        const version = this.getNode().typeVersion;
        this.logger.debug('Executing Tools Agent V2');
        const returnData = [];
        const items = this.getInputData();
        totalItems = items.length;
        const batchSize = this.getNodeParameter('options.batching.batchSize', 0, 1);
        const delayBetweenBatches = this.getNodeParameter('options.batching.delayBetweenBatches', 0, 0);
        const needsFallback = this.getNodeParameter('needsFallback', 0, false);
        const memory = await (0, common_1.getOptionalMemory)(this);
        const model = await (0, common_1.getChatModel)(this, 0);
        (0, node_assert_1.default)(model, 'Please connect a model to the Chat Model input');
        const fallbackModel = needsFallback ? await (0, common_1.getChatModel)(this, 1) : null;
        if (checkIsResponsesApi(model)) {
            throw new n8n_workflow_1.NodeOperationError(this.getNode(), `This model is not supported in ${version} version of the Agent node. Please upgrade the Agent node to the latest version.`);
        }
        if (checkIsResponsesApi(fallbackModel)) {
            throw new n8n_workflow_1.NodeOperationError(this.getNode(), `This fallback model is not supported in ${version} version of the Agent node. Please upgrade the Agent node to the latest version.`);
        }
        if (needsFallback && !fallbackModel) {
            throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Please connect a model to the Fallback Model input or disable the fallback option');
        }
        enableStreaming = this.getNodeParameter('options.enableStreaming', 0, true);
        for (let i = 0; i < items.length; i += batchSize) {
            const batch = items.slice(i, i + batchSize);
            const batchPromises = batch.map(async (_item, batchItemIndex) => {
                const itemIndex = i + batchItemIndex;
                const input = (0, helpers_1.getPromptInputByType)({
                    ctx: this,
                    i: itemIndex,
                    inputKey: 'text',
                    promptTypeKey: 'promptType',
                });
                if (input === undefined) {
                    throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'The "text" parameter is empty.');
                }
                const outputParser = await (0, N8nOutputParser_1.getOptionalOutputParser)(this, itemIndex);
                const tools = await (0, common_1.getTools)(this, outputParser);
                const options = this.getNodeParameter('options', itemIndex, {});
                const messages = await (0, common_1.prepareMessages)(this, itemIndex, {
                    systemMessage: options.systemMessage,
                    passthroughBinaryImages: options.passthroughBinaryImages ?? true,
                    outputParser,
                });
                const prompt = (0, common_1.preparePrompt)(messages);
                const executor = createAgentExecutor(model, tools, prompt, options, outputParser, memory, fallbackModel);
                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, utils_1.isExecuteFunctions)(this)
                    ? (0, tracing_1.getTracingConfig)(this, { additionalMetadata })
                    : undefined;
                const executorWithTracing = tracingConfig ? executor.withConfig(tracingConfig) : executor;
                const invokeParams = {
                    input,
                    system_message: options.systemMessage ?? prompt_1.SYSTEM_MESSAGE,
                    formatting_instructions: 'IMPORTANT: For your response to user, you MUST use the `format_final_json_response` tool with your complete answer formatted according to the required schema. Do not attempt to format the JSON manually - always use this tool. Your response will be rejected if it is not properly formatted through this tool. Only use this tool once you are ready to provide your final answer.',
                };
                const toolCounter = new ToolCallCounterCallback();
                const executeOptions = {
                    signal: this.getExecutionCancelSignal(),
                    callbacks: [toolCounter],
                };
                const isStreamingAvailable = 'isStreaming' in this ? this.isStreaming?.() : undefined;
                if ('isStreaming' in this &&
                    enableStreaming &&
                    isStreamingAvailable &&
                    this.getNode().typeVersion >= 2.1) {
                    const chatHistory = memory ? await (0, agent_execution_1.loadMemory)(memory, model) : undefined;
                    if (memory) {
                        memoryLoads++;
                    }
                    const eventStream = executorWithTracing.streamEvents({
                        ...invokeParams,
                        chat_history: chatHistory ?? undefined,
                    }, {
                        version: 'v2',
                        ...executeOptions,
                    });
                    const response = await processEventStream(this, eventStream, itemIndex, options.returnIntermediateSteps);
                    return { response, toolCallsCounted: toolCounter.count };
                }
                else {
                    const response = await executorWithTracing.invoke(invokeParams, executeOptions);
                    return { response, toolCallsCounted: toolCounter.count };
                }
            });
            const batchResults = await Promise.allSettled(batchPromises);
            const outputParser = await (0, N8nOutputParser_1.getOptionalOutputParser)(this, 0);
            batchResults.forEach((result, index) => {
                const itemIndex = i + index;
                if (result.status === 'rejected') {
                    const error = result.reason;
                    failedItems++;
                    if (this.continueOnFail()) {
                        returnData.push({
                            json: { error: error.message },
                            pairedItem: { item: itemIndex },
                        });
                        return;
                    }
                    else {
                        throw new n8n_workflow_1.NodeOperationError(this.getNode(), error);
                    }
                }
                const { response, toolCallsCounted } = result.value;
                toolCalls += toolCallsCounted;
                if (memory && outputParser) {
                    const parsedOutput = (0, n8n_workflow_1.jsonParse)(response.output);
                    response.output = parsedOutput?.output ?? parsedOutput;
                }
                const itemResult = {
                    json: (0, omit_1.default)(response, 'system_message', 'formatting_instructions', 'input', 'chat_history', 'agent_scratchpad'),
                    pairedItem: { item: itemIndex },
                };
                returnData.push(itemResult);
            });
            if (i + batchSize < items.length && delayBetweenBatches > 0) {
                await (0, n8n_workflow_1.sleep)(delayBetweenBatches);
            }
        }
        return [returnData];
    }
    catch (error) {
        failureType =
            error instanceof Error ? error.name || error.constructor.name || 'Error' : typeof error;
        throw error;
    }
    finally {
        applyAgentTracingMetadata(this, {
            toolCalls,
            failedItems,
            totalItems,
            streamingEnabled: enableStreaming,
            failureType,
            memoryLoads,
        });
    }
}
//# sourceMappingURL=execute.js.map