"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createArgusAgentNode = createArgusAgentNode;
const prebuilt_1 = require("@langchain/langgraph/prebuilt");
const messages_1 = require("@langchain/core/messages");
const types_1 = require("../../types");
const toolRegistry_1 = require("../../tools/toolRegistry");
const argusWebFetchTool_1 = require("../../tools/argusWebFetchTool");
const securityConfigArgusTools_1 = require("../../tools/securityConfigArgusTools");
const logger_1 = __importDefault(require("../../../../../logger"));
const systemPrompt_1 = require("../../utils/systemPrompt");
const langChainFromDefaultProvider_1 = require("../../utils/langChainFromDefaultProvider");
const nodeHelperFuncitons_1 = require("../../utils/nodeHelperFuncitons");
const argusScopeNotices_1 = require("../../utils/argusScopeNotices");
/**
 * This is a single agent that:
 * 1. Understands query intent
 * 2. Calls appropriate MCP tools (APIs)
 * 3. Analyzes collected data
 * 4. Generates summarized response
 */
function createArgusAgentNode(domain, mcpClient) {
    return async (state) => {
        try {
            const llm = (0, langChainFromDefaultProvider_1.getLangChainFromDefaultProvider)();
            logger_1.default.info('Argus LLM initialized from default grading provider');
            // Log available filters for infoging
            if (state.filters && Object.keys(state.filters).length > 0) {
                logger_1.default.info(`Argus agent state filters: ${JSON.stringify(state.filters)}`);
            }
            // Track API calls for observability
            const apiCalls = [];
            const isSecurityConfigDomain = state.domain === types_1.ArgusDomain.SECURITY_CONFIG;
            // Domain tools (MCP or direct REST / security draft) plus optional read-only public URL fetch.
            const domainTools = isSecurityConfigDomain
                ? (0, securityConfigArgusTools_1.getSecurityConfigArgusTools)()
                : await (0, toolRegistry_1.getMCPToolsForDomain)(state.domain, mcpClient);
            const webTools = (0, argusWebFetchTool_1.getArgusWebFetchTools)();
            const toolsForAgent = [...domainTools, ...webTools];
            if (toolsForAgent.length === 0) {
                throw new Error(`No tools available for domain: ${state.domain}`);
            }
            // Convert tools to LangChain tools (handle both MCP and direct tools)
            // Pass state.filters to tools so they can extract applicationId, sessionId, traceId, etc.
            const langChainToolsFromRegistry = toolsForAgent.map((toolItem) => {
                // Check if it's an MCP tool
                if (mcpClient && 'inputSchema' in toolItem && !('execute' in toolItem)) {
                    return (0, nodeHelperFuncitons_1.convertMCPToolToLangChain)(toolItem, mcpClient, state.projectId, state.orgId, state.filters, apiCalls);
                }
                // Otherwise it's a direct REST tool
                return (0, nodeHelperFuncitons_1.convertToolToLangChain)(toolItem, state.projectId, state.orgId, state.filters, apiCalls);
            });
            let langChainTools;
            if (isSecurityConfigDomain) {
                langChainTools = [...langChainToolsFromRegistry];
            }
            else {
                const clickhouseTextToSqlToolLC = (0, nodeHelperFuncitons_1.createClickhouseTextToSqlLangChainTool)(state, apiCalls);
                const getEvaluationAudioToolLC = (0, nodeHelperFuncitons_1.createGetEvaluationAudioLangChainTool)(state, apiCalls);
                langChainTools = [clickhouseTextToSqlToolLC, getEvaluationAudioToolLC];
                if (process.env.ARGUS_USE_TELEMETRY_TOOLS === 'true') {
                    langChainTools.push(...langChainToolsFromRegistry);
                }
                else {
                    const webToolNames = new Set(webTools.map((w) => w.name));
                    const webOnlyLangChain = toolsForAgent
                        .filter((t) => webToolNames.has(t.name))
                        .map((t) => (0, nodeHelperFuncitons_1.convertToolToLangChain)(t, state.projectId, state.orgId, state.filters, apiCalls));
                    langChainTools.push(...webOnlyLangChain);
                }
            }
            // Extract context IDs from state filters for system message
            const contextInfo = [];
            if (state.filters?.traceId) {
                contextInfo.push(`traceId: ${state.filters.traceId}`);
            }
            if (state.filters?.sessionId) {
                contextInfo.push(`sessionId: ${state.filters.sessionId}`);
            }
            if (state.filters?.applicationId) {
                contextInfo.push(`applicationId: ${state.filters.applicationId}`);
            }
            if (state.filters?.spanId) {
                contextInfo.push(`spanId: ${state.filters.spanId}`);
            }
            if (state.filters?.evaluationId) {
                contextInfo.push(`evaluationId: ${state.filters.evaluationId}`);
            }
            const contextInfoText = contextInfo.length > 0
                ? `\n\nContext Available: ${contextInfo.join(', ')}. These values are automatically included in internal data lookups via filters.`
                : '';
            // Security-config drafting: include current config snapshot (bounded) so the agent can propose
            // updates against the current persisted JSON rather than drafting from scratch.
            let securityConfigContextText = '';
            if (isSecurityConfigDomain) {
                const currentConfig = state.filters?.currentConfig;
                if (currentConfig && typeof currentConfig === 'object') {
                    try {
                        const raw = JSON.stringify(currentConfig);
                        const bounded = raw.length > 8000 ? `${raw.slice(0, 8000)}... [truncated]` : raw;
                        const checklistSnap = (0, securityConfigArgusTools_1.getSecurityConfigArgusChecklistSnapshotFromCurrentConfig)(currentConfig);
                        securityConfigContextText = `\n\nCurrent security config (JSON; do not change target):\n${bounded}\n\nChecklist snapshot for this draft (same rules as the UI checklist and \`draft_security_config\`; use \`checklistMissingKeys\` to pick the next \`argus_ui\` follow-up—**if applicationDefinition rows are satisfied and only \`vulnerableCategories\` and/or \`attackStrategies\` remain, you must ask those next**):\n${JSON.stringify(checklistSnap)}`;
                    }
                    catch {
                        // ignore
                    }
                }
            }
            // Create system message (optimized for token efficiency)
            const domainIntro = state.domain === types_1.ArgusDomain.SECURITY_CONFIG
                ? 'You are Argus, helping users draft red team **security configurations** (same JSON the product stores on a security config row).'
                : `You are Argus, an assistant for ${state.domain} data analysis.`;
            let systemMessage = `${domainIntro} ${(0, systemPrompt_1.getSystemPromptForDomain)(state.domain, toolsForAgent, `${contextInfoText}${securityConfigContextText}`)}`;
            // When responseFormat is "graph" or "both", append chart JSON contract so the UI can render charts.
            if (state.responseFormat === 'graph' || state.responseFormat === 'both') {
                systemMessage += systemPrompt_1.graphSystemPrompt;
            }
            // Create the agent using LangGraph's createReactAgent
            // Note: recursion limit is set in the invoke call below (default is 25, we set it to 50)
            const agent = (0, prebuilt_1.createReactAgent)({
                llm,
                tools: langChainTools,
            });
            const MAX_HISTORY_MESSAGES = 20; // keep slightly richer context for follow-ups
            const MAX_HISTORY_MESSAGE_CHARS = 6000;
            const rawHistoryItems = Array.isArray(state.filters?.chatHistory)
                ? state.filters?.chatHistory.slice(-MAX_HISTORY_MESSAGES)
                : [];
            const historyItems = (rawHistoryItems || []).map((item) => {
                if (item?.role === 'assistant' &&
                    typeof item.content === 'string' &&
                    item.content.length > MAX_HISTORY_MESSAGE_CHARS) {
                    return {
                        ...item,
                        content: item.content.substring(0, MAX_HISTORY_MESSAGE_CHARS),
                    };
                }
                return item;
            });
            const historyMessages = (historyItems ?? [])
                .map((item) => {
                const content = typeof item?.content === 'string' ? item.content : '';
                if (!content) {
                    return null;
                }
                return item.role === 'assistant' ? new messages_1.AIMessage(content) : new messages_1.HumanMessage(content);
            })
                .filter(Boolean);
            let agentInput = {
                messages: [
                    new messages_1.SystemMessage(systemMessage),
                    ...historyMessages,
                    new messages_1.HumanMessage(state.query),
                ],
            };
            let estimatedTokens = (0, nodeHelperFuncitons_1.estimateTokens)(agentInput.messages);
            const MAX_SAFE_TOKENS = 100000; // Leave buffer below 128k limit
            // If estimated tokens exceed safe limit, aggressively reduce history
            if (estimatedTokens > MAX_SAFE_TOKENS) {
                logger_1.default.warn(`Estimated tokens (${estimatedTokens.toFixed(0)}) exceed safe limit (${MAX_SAFE_TOKENS}), reducing chat history`);
                const singleHistoryItem = historyItems && historyItems.length > 0 ? [historyItems[historyItems.length - 1]] : [];
                const singleHistoryMessage = singleHistoryItem.map((item) => {
                    let content = item.content ?? '';
                    if (typeof content === 'string' && content.length > 2000) { // 500 tokens max
                        content = content.substring(0, 2000) + '... [truncated]';
                    }
                    return item.role === 'assistant' ? new messages_1.AIMessage(content) : new messages_1.HumanMessage(content);
                });
                agentInput = {
                    messages: [
                        new messages_1.SystemMessage(systemMessage),
                        ...singleHistoryMessage,
                        new messages_1.HumanMessage(state.query),
                    ],
                };
                estimatedTokens = (0, nodeHelperFuncitons_1.estimateTokens)(agentInput.messages);
                // If still too large, skip history entirely
                if (estimatedTokens > MAX_SAFE_TOKENS) {
                    logger_1.default.warn(`Estimated tokens (${estimatedTokens.toFixed(0)}) still exceed safe limit, skipping chat history entirely`);
                    agentInput = {
                        messages: [new messages_1.SystemMessage(systemMessage), new messages_1.HumanMessage(state.query)],
                    };
                    estimatedTokens = (0, nodeHelperFuncitons_1.estimateTokens)(agentInput.messages);
                }
            }
            // Execute the agent
            logger_1.default.info(`Executing Argus agent for query: ${state.query}`);
            logger_1.default.info(`Agent input messages count: ${agentInput.messages.length}, estimated tokens (pre-check only): ${estimatedTokens.toFixed(0)}`);
            let agentResult;
            try {
                const tokenUsageCallback = (0, nodeHelperFuncitons_1.createTokenUsageCallbackForAgent)(state);
                const timeoutMs = 30000;
                // Add timeout protection (configurable).
                agentResult = await Promise.race([
                    agent.invoke(agentInput, {
                        configurable: { recursionLimit: 10 },
                        callbacks: [tokenUsageCallback],
                    }),
                    new Promise((_, reject) => setTimeout(() => reject(new Error(`Agent invoke timeout after ${Math.round(timeoutMs / 1000)} seconds`)), timeoutMs)),
                ]);
                logger_1.default.info('Agent invoke completed successfully');
            }
            catch (invokeError) {
                // Log detailed error information
                logger_1.default.error(`Agent invoke error: ${invokeError}`);
                logger_1.default.error(`Error type: ${invokeError?.constructor?.name}`);
                logger_1.default.error(`Error message: ${invokeError?.message}`);
                logger_1.default.error(`Error stack: ${invokeError?.stack}`);
                if (invokeError?.response) {
                    logger_1.default.error(`Error response status: ${invokeError?.response?.status}`);
                    logger_1.default.error(`Error response data: ${JSON.stringify(invokeError?.response?.data)}`);
                }
                if (invokeError?.status) {
                    logger_1.default.error(`Error status: ${invokeError?.status}`);
                }
                // Handle recursion limit error specifically
                if (invokeError?.message?.includes('Recursion limit') || invokeError?.name === 'GraphRecursionError') {
                    logger_1.default.warn('Agent hit recursion limit - this may indicate the agent is stuck in a loop');
                    // Try to extract any partial response from the error
                    if (invokeError?.messages && invokeError.messages.length > 0) {
                        const lastMessage = invokeError.messages[invokeError.messages.length - 1];
                        if (lastMessage && 'content' in lastMessage) {
                            const content = lastMessage.content;
                            if (typeof content === 'string' && content.trim().length > 0) {
                                // Use the partial response if available
                                agentResult = { messages: invokeError.messages };
                                logger_1.default.info('Using partial response from recursion limit error');
                            }
                        }
                    }
                    // If we couldn't extract a response, try to get messages from the error state
                    if (!agentResult && invokeError?.state?.messages) {
                        const errorMessages = invokeError.state.messages;
                        if (Array.isArray(errorMessages) && errorMessages.length > 0) {
                            const lastMsg = errorMessages[errorMessages.length - 1];
                            if (lastMsg && 'content' in lastMsg && typeof lastMsg.content === 'string' && lastMsg.content.trim().length > 0) {
                                agentResult = { messages: errorMessages };
                                logger_1.default.info('Using partial response from recursion limit error state');
                            }
                        }
                    }
                    // If we still couldn't extract a response, throw a more helpful error
                    if (!agentResult) {
                        // Throw a sentinel error so downstream can return a user-friendly message
                        // without exposing internal recursion details.
                        throw new Error('ARGUS_REPHRASE_NEEDED');
                    }
                }
                else if (invokeError?.status === 500 || invokeError?.response?.status === 500) {
                    // Handle 500 errors from OpenAI API
                    const errorMsg = invokeError?.response?.data?.error?.message || invokeError?.message || 'Internal server error from AI service';
                    logger_1.default.error(`OpenAI API 500 error: ${errorMsg}`);
                    throw new Error(`The AI service encountered an error while processing your query. Please try again in a moment. If the problem persists, try rephrasing your query.`);
                }
                else if (invokeError?.status === 401 || invokeError?.response?.status === 401) {
                    // Handle 401/unauthorized errors from OpenAI API with a user-safe message
                    const rawErrorMsg = invokeError?.response?.data?.error?.message || invokeError?.message || 'Unauthorized';
                    logger_1.default.error(`OpenAI API 401/unauthorized error: ${rawErrorMsg}`);
                    throw new Error('The AI service is currently unavailable due to a configuration issue. Please contact your administrator.');
                }
                else {
                    throw invokeError;
                }
            }
            // Extract the final answer from agent messages
            let answer = '';
            let dataPoints = 0;
            if (agentResult.messages && agentResult.messages.length > 0) {
                // Get the last assistant message
                const lastMessage = agentResult.messages[agentResult.messages.length - 1];
                if (lastMessage) {
                    // Handle different message types
                    if ('content' in lastMessage) {
                        const content = lastMessage.content;
                        if (typeof content === 'string') {
                            answer = content;
                        }
                        else if (Array.isArray(content)) {
                            // Handle array of content blocks
                            answer = content
                                .map((block) => (typeof block === 'string' ? block : block.text || JSON.stringify(block)))
                                .join('\n');
                        }
                        else {
                            answer = JSON.stringify(content);
                        }
                    }
                    else {
                        // Fallback: try to get text from message
                        answer = String(lastMessage);
                    }
                }
            }
            // Extract tool call info and data points from messages
            if (agentResult.messages) {
                for (const message of agentResult.messages) {
                    // Track tool calls reported by LangGraph (may be MCP or HTTP tools)
                    if ('tool_calls' in message && message.tool_calls && Array.isArray(message.tool_calls)) {
                        for (const toolCall of message.tool_calls) {
                            apiCalls.push({
                                tool: toolCall.name || 'unknown',
                                timestamp: new Date(),
                                success: true,
                                sql: toolCall.args?.sql || '',
                            });
                        }
                    }
                    // Try to extract data points from tool results
                    if ('content' in message) {
                        const content = message.content;
                        if (typeof content === 'string') {
                            try {
                                const parsed = JSON.parse(content);
                                if (parsed.count !== undefined) {
                                    dataPoints = parsed.count;
                                }
                                else if (typeof parsed.rowCount === 'number') {
                                    dataPoints = parsed.rowCount;
                                }
                                else if (parsed.traces && Array.isArray(parsed.traces)) {
                                    dataPoints = parsed.traces.length;
                                }
                                else if (parsed.spans && Array.isArray(parsed.spans)) {
                                    dataPoints = parsed.spans.length;
                                }
                                else if (parsed.sessions && Array.isArray(parsed.sessions)) {
                                    dataPoints = parsed.sessions.length;
                                }
                                else if (Array.isArray(parsed.rows)) {
                                    dataPoints = parsed.rows.length;
                                }
                            }
                            catch {
                                // Not JSON, continue
                            }
                        }
                    }
                }
            }
            if (!answer) {
                answer = 'I could not process your query. Please try rephrasing it.';
            }
            // Clean up any technical truncation markers that might leak into the final answer
            // from internal history truncation logic. Users should never see these.
            if (typeof answer === 'string') {
                answer = answer.replace(/\s*\.\.\.\s*\[truncated[^\]]*\]/gi, '');
            }
            // Guardrail: if the agent never called any tools, avoid returning potentially hallucinated,
            // off-topic answers. Argus is only allowed to answer about Argus telemetry/evaluation data.
            const totalApiCalls = apiCalls.length;
            if (totalApiCalls === 0 && process.env.ARGUS_RESTRIC_RANDOM_ANSWERS === 'true') {
                answer = (0, nodeHelperFuncitons_1.buildNoToolCallsAnswer)(state);
            }
            answer = await (0, argusScopeNotices_1.applyArgusScopeNoticesToAnswer)(answer, apiCalls, state.filters, state.responseFormat, state.projectId);
            let apiToolsSummary;
            let apiTools;
            // For graph responses we must not append debug text, otherwise the JSON graph
            // spec would be invalid. For text responses we keep the existing debug suffix.
            if (state.responseFormat !== 'graph') {
                const debugResult = (0, nodeHelperFuncitons_1.appendDebugToolsAndFilters)(answer, apiCalls);
                answer = debugResult.answer;
                apiToolsSummary = debugResult.apiToolsSummary;
                apiTools = debugResult.apiTools;
            }
            const response = {
                answer,
                metadata: {
                    domain: state.domain,
                    dataPoints,
                    //confidence: 0.9,
                    apiCalls,
                    ...(apiToolsSummary && {
                        apiToolsSummary,
                        apiTools,
                    }),
                },
            };
            return {
                response,
                metadata: {
                    startTime: state.metadata?.startTime || new Date(),
                    executionPath: [...(state.metadata?.executionPath || []), 'argus_agent'],
                    endTime: new Date(),
                    ...(state.metadata?.tokenUsage && { tokenUsage: state.metadata.tokenUsage }),
                    apiCalls: [...(state.metadata?.apiCalls || []), ...apiCalls],
                },
            };
        }
        catch (error) {
            const rawErrorMessage = error instanceof Error ? error.message : String(error);
            // Sanitize internal/credential-related errors before surfacing to the user
            let safeErrorMessage = rawErrorMessage;
            if (typeof rawErrorMessage === 'string') {
                const lower = rawErrorMessage.toLowerCase();
                if (lower.includes('401') ||
                    lower.includes('unauthorized') ||
                    lower.includes('invalid api key') ||
                    lower.includes('incorrect api key') ||
                    lower.includes('api key is not set')) {
                    safeErrorMessage =
                        'The AI service is currently unavailable due to a configuration issue. Please contact your administrator.';
                }
            }
            logger_1.default.error(`Argus agent failed: ${rawErrorMessage}`);
            return {
                error: `Argus agent failed: ${safeErrorMessage}`,
                metadata: {
                    startTime: state.metadata?.startTime || new Date(),
                    executionPath: [...(state.metadata?.executionPath || []), 'argus_agent_error'],
                    endTime: new Date(),
                    ...(state.metadata?.tokenUsage && { tokenUsage: state.metadata.tokenUsage }),
                    ...(state.metadata?.apiCalls && { apiCalls: state.metadata.apiCalls }),
                },
            };
        }
    };
}
//# sourceMappingURL=argusAgentNode.js.map