"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LangChainParser = void 0;
const spanDeduplicator_1 = require("../utils/spanDeduplicator");
const toolArgumentFields_1 = require("../utils/toolArgumentFields");
const edgeCaseHandlers_1 = require("../utils/edgeCaseHandlers");
const utilts_1 = require("../utils/utilts");
/**
 * LangChain Parser
 */
class LangChainParser {
    constructor() {
        this.name = 'langchain';
        this.deduplicator = new spanDeduplicator_1.SpanDeduplicator();
        this.edgeCaseRegistry = new edgeCaseHandlers_1.EdgeCaseHandlerRegistry();
        // Register default edge case handlers
        edgeCaseHandlers_1.DEFAULT_HANDLERS.forEach(handler => {
            this.edgeCaseRegistry.register(handler.name, handler);
        });
    }
    canParse(spans) {
        return spans.some(s => {
            const system = utilts_1.FrameworkUtils.getAttr(s.attributes, 'gen_ai.system', 'genai.system');
            const sdkVersion = utilts_1.FrameworkUtils.getAttr(s.attributes, 'gen_ai.sdk.version', 'genai.sdk.version');
            return (system === 'langchain' ||
                sdkVersion?.includes('langchain') ||
                s.resourceAttributes?.['service.name']?.includes('langchain'));
        });
    }
    getTools(spans) {
        this.deduplicator.reset();
        const context = this.createContext(spans);
        for (const span of spans) {
            // Skip duplicates
            if (this.deduplicator.isDuplicate(span)) {
                continue;
            }
            // Skip already processed
            if (context.seenSpans.has(span.id)) {
                continue;
            }
            // Try each edge case handler in priority order
            for (const handler of this.edgeCaseRegistry.getAllHandlers()) {
                if (handler.canHandle(span, context)) {
                    handler.handle(span, context);
                    break;
                }
            }
        }
        return this.enrichToolsArgumentMetadata(context.tools, spans);
    }
    /**
     * Add `argument` / `argument_defination` to tools produced by edge-case handlers (OpenLit-style parity with LangGraph).
     */
    enrichToolsArgumentMetadata(tools, spans) {
        const spanById = new Map(spans.map((s) => [s.id, s]));
        return tools.map((t) => {
            const span = spanById.get(t.spanId);
            const attrs = span?.attributes ?? {};
            const enriched = (0, toolArgumentFields_1.enrichToolArgumentFieldsForLangChainFromSpan)(t.input, attrs);
            return {
                ...t,
                ...enriched,
                ...((0, toolArgumentFields_1.getToolDescriptionFromAttrs)(attrs) != null && {
                    description: (0, toolArgumentFields_1.getToolDescriptionFromAttrs)(attrs),
                }),
            };
        });
    }
    /** Align {@link collectAgentToolDefinitions} output with {@link enrichToolsArgumentMetadata} (same span-level rules). */
    enrichAgentToolDefinitions(defs, spanById) {
        return defs.map((d) => {
            const span = spanById.get(d.spanId);
            const attrs = span?.attributes ?? {};
            const enriched = (0, toolArgumentFields_1.enrichToolArgumentFieldsForLangChainFromSpan)(d.argument, attrs);
            return { ...d, ...enriched };
        });
    }
    getAgents(spans) {
        this.deduplicator.reset();
        const agents = [];
        const seenAgents = new Set();
        // IMPORTANT: content-based span deduplication can accidentally drop "parent" workflow spans
        // (e.g. AgentExecutor) when multiple spans share similar content. If we drop the parent,
        // tools/models can no longer nest correctly in the entity hierarchy.
        const parentSpanIds = new Set();
        for (const s of spans) {
            if (s.parentSpanId)
                parentSpanIds.add(s.parentSpanId);
        }
        const spanById = new Map(spans.map((s) => [s.id, s]));
        for (const span of spans) {
            // Keep parent spans even if they look duplicate by content.
            if (this.deduplicator.isDuplicate(span) && !parentSpanIds.has(span.id))
                continue;
            if (this.isAgentSpan(span)) {
                const agent = this.extractAgent(span);
                if (agent && !seenAgents.has(agent.id)) {
                    const toolDefinitions = this.enrichAgentToolDefinitions((0, toolArgumentFields_1.collectAgentToolDefinitions)(agent.id, spans, spanById, (sp) => this.isToolEntitySpan(sp)), spanById);
                    agents.push(toolDefinitions.length > 0 ? { ...agent, toolDefinitions } : agent);
                    seenAgents.add(agent.id);
                }
            }
        }
        return agents;
    }
    getModels(spans) {
        this.deduplicator.reset();
        const models = [];
        const seenModels = new Set();
        for (const span of spans) {
            if (this.deduplicator.isDuplicate(span))
                continue;
            if (this.isModelSpan(span)) {
                const model = this.extractModel(span);
                if (model && !seenModels.has(model.id)) {
                    models.push(model);
                    seenModels.add(model.id);
                }
            }
        }
        return models;
    }
    generateDag(spans) {
        this.deduplicator.reset();
        const uniqueSpans = spans.filter(s => !this.deduplicator.isDuplicate(s));
        const nodes = [];
        const nodeMap = new Map();
        // Create nodes
        for (const span of uniqueSpans) {
            const node = {
                id: span.id,
                type: this.getNodeType(span),
                name: span.name,
                children: [],
                metadata: {
                    timestamp: span.timestamp,
                    duration: span.duration,
                    parentSpanId: span.parentSpanId,
                    attributes: span.attributes
                }
            };
            nodes.push(node);
            nodeMap.set(span.id, node);
        }
        // Build relationships
        for (const span of uniqueSpans) {
            if (span.parentSpanId) {
                const parentNode = nodeMap.get(span.parentSpanId);
                const childNode = nodeMap.get(span.id);
                if (parentNode && childNode && !parentNode.children.includes(childNode.id)) {
                    parentNode.children.push(childNode.id);
                }
            }
        }
        const byTime = [...uniqueSpans].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
        const firstSpanId = byTime.length > 0 && nodeMap.has(byTime[0].id) ? byTime[0].id : undefined;
        const lastSpanId = byTime.length > 0 && nodeMap.has(byTime[byTime.length - 1].id)
            ? byTime[byTime.length - 1].id
            : undefined;
        if (firstSpanId != null) {
            nodes.push({
                id: 'prompt',
                type: 'prompt',
                name: 'Prompt',
                children: [firstSpanId],
                metadata: {}
            });
        }
        if (lastSpanId != null) {
            const lastNode = nodeMap.get(lastSpanId);
            if (lastNode)
                lastNode.children = [...lastNode.children, 'response'];
            nodes.push({
                id: 'response',
                type: 'response',
                name: 'Response',
                children: [],
                metadata: {}
            });
        }
        return nodes;
    }
    generateLinearFlow(spans) {
        this.deduplicator.reset();
        const allowedTypes = new Set(['agent', 'tool', 'llm']);
        const uniqueSpans = spans
            .filter(s => !this.deduplicator.isDuplicate(s))
            .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
        const filtered = uniqueSpans.filter((span) => allowedTypes.has(this.getNodeType(span)));
        const steps = [];
        filtered.forEach((span, index) => {
            steps.push({
                stepNumber: index + 1,
                spanId: span.id,
                type: this.getNodeType(span),
                name: span.name,
                timestamp: span.timestamp,
                duration: span.duration,
                input: utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.workflow.input', 'genai.workflow.input', 'gen_ai.prompt', 'genai.prompt', 'gen_ai.tool.input'),
                output: utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.workflow.output', 'genai.workflow.output', 'gen_ai.completion', 'genai.completion', 'gen_ai.tool.output'),
                metadata: {
                    parentSpanId: span.parentSpanId,
                    operationName: utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.operation.name', 'genai.operation.name'),
                    workflowType: utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.workflow.type', 'genai.workflow.type')
                }
            });
        });
        return steps;
    }
    // Helper methods
    createContext(spans) {
        const spanMap = new Map(spans.map(s => [s.id, s]));
        return {
            spans,
            spanMap,
            seenSpans: new Set(),
            tools: [],
            agents: [],
            models: [],
            dagNodes: [],
            linearSteps: []
        };
    }
    isAgentSpan(span) {
        const opName = utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.operation.name', 'genai.operation.name');
        const workflowType = utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.workflow.type', 'genai.workflow.type') || '';
        // Exclude infrastructure workflows (LangChain "runnable graph" plumbing)
        // IMPORTANT: Some infrastructure components include the substring "Agent" (e.g. *AgentOutputParser),
        // which can otherwise be misclassified as a semantic agent.
        if (this.isInfrastructureWorkflow(span))
            return false;
        // Agent patterns
        return (span.name.includes('agent ') ||
            opName === 'invokeagent' ||
            opName?.includes('agent') ||
            workflowType.includes('Agent'));
    }
    /**
     * Identify LangChain infrastructure workflows that should NOT be classified as agents.
     *
     * Examples:
     * - OpenAIFunctionsAgentOutputParser / ToolsAgentOutputParser: output parsing helpers (not agents)
     * - RunnableAssign<agent_scratchpad>: scratchpad wiring (not agents)
     */
    isInfrastructureWorkflow(span) {
        const workflowType = String(utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.workflow.type', 'genai.workflow.type') ?? '');
        const spanName = String(span.name ?? '');
        const combined = (workflowType + ' ' + spanName).toLowerCase();
        // Common runnable/plumbing patterns
        const infrastructurePatterns = [
            'runnablelambda',
            'runnablesequence',
            'runnableparallel',
            'chatprompttemplate',
            'stroutputparser',
            'should_continue',
            'route_decision',
            'route_after_supervisor',
        ];
        if (infrastructurePatterns.some((p) => combined.includes(p)))
            return true;
        // Agent scratchpad wiring (shows up across model providers)
        if (combined.includes('agent_scratchpad'))
            return true;
        // Output parsers (OpenAI/Anthropic/Mistral/Llama tool-calling agents all use output parsing helpers)
        // e.g. OpenAIFunctionsAgentOutputParser, ToolsAgentOutputParser, OpenAIToolsAgentOutputParser, etc.
        if (combined.includes('outputparser') || combined.includes('output_parser'))
            return true;
        // More runnable plumbing often seen around agents (model-agnostic)
        const extraPlumbing = [
            'runnableassign',
            'runnablepassthrough',
            'runnablebranch',
            'runnablemap',
            'runnablebinding',
            'runnablewithmessagehistory',
            'messagesplaceholder',
            'prompttemplate',
            'fewshotprompttemplate',
        ];
        if (extraPlumbing.some((p) => combined.includes(p)))
            return true;
        return false;
    }
    /** Matches tool classification in getNodeType (explicit tool spans + execute_tool / gen_ai.tool.name). */
    isToolEntitySpan(span) {
        if (typeof span.name === 'string' && span.name.startsWith('tool ')) {
            return true;
        }
        if (utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.tool.name', 'genai.tool.name')) {
            return true;
        }
        const opName = utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.operation.name', 'genai.operation.name');
        return opName === 'execute_tool' || opName === 'tool';
    }
    isModelSpan(span) {
        // Pattern 1: "chat <model>" in span name
        if (span.name.startsWith('chat '))
            return true;
        if (span.name.includes('llm '))
            return true;
        // Pattern 2: operation name is chat
        const opName = utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.operation.name', 'genai.operation.name');
        if (opName === 'chat')
            return true;
        // Pattern 3: has response/request model attribute (ignore gen_ai.request.model = "unknown")
        if (utilts_1.FrameworkUtils.getModelAttr(span.attributes, 'gen_ai.request.model', 'genai.request.model'))
            return true;
        // if (FrameworkUtils.getModelAttr(span.attributes, 'gen_ai.response.model', 'genai.response.model')) return true;
        return false;
    }
    extractAgent(span) {
        const workflowType = utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.workflow.type', 'genai.workflow.type');
        const agentType = workflowType || span.name;
        let name = workflowType;
        if (!name || name.includes('Runnable')) {
            const match = span.name.match(/^workflow\s+(.+)$/i);
            name = match ? match[1] : span.name;
        }
        return (0, utilts_1.buildCanonicalAgent)({
            id: span.id,
            name,
            type: agentType,
            ...((0, toolArgumentFields_1.getAgentDescriptionFromAttrs)(span.attributes) != null && {
                description: (0, toolArgumentFields_1.getAgentDescriptionFromAttrs)(span.attributes),
            }),
            role: utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.agent.role', 'genai.agent.role'),
            goal: utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.agent.goal', 'genai.agent.goal'),
            metadata: {
                spanId: span.id,
                timestamp: span.timestamp,
                duration: span.duration,
                operationDuration: utilts_1.FrameworkUtils.safeParseFloat(span.attributes['gen_ai.client.operation.duration']),
            },
        });
    }
    extractModel(span) {
        const attrs = span.attributes;
        // Get model name - prefer response model, fall back to request when missing or copied
        const modelName = utilts_1.FrameworkUtils.getModelAttrResponseFirst(attrs);
        if (!modelName) {
            // Try parsing from span name "chat <model>"
            const match = span.name.match(/^chat\s+(.+)$/i);
            if (!match)
                return null;
        }
        const finalModelName = modelName || span.name.replace(/^chat\s+/i, '');
        return (0, utilts_1.buildCanonicalExtendedModel)({
            id: span.id,
            provider: utilts_1.FrameworkUtils.getAttr(attrs, 'gen_ai.system', 'genai.system') || 'openai',
            modelName: finalModelName,
            tokenUsage: this.extractTokenUsage(span),
            spanId: span.id,
            timestamp: span.timestamp,
            temperature: utilts_1.FrameworkUtils.safeParseFloat(utilts_1.FrameworkUtils.getAttr(attrs, 'gen_ai.request.temperature', 'genai.request.temperature')),
            cost: utilts_1.FrameworkUtils.safeParseFloat(utilts_1.FrameworkUtils.getAttr(attrs, 'gen_ai.usage.cost', 'genai.usage.cost')) || 0,
            ttft: this.extractTTFT(span),
            serverTimePerOutputToken: utilts_1.FrameworkUtils.safeParseFloat(attrs['gen_ai.server.time_per_output_token']),
            prompt: utilts_1.FrameworkUtils.getAttr(attrs, 'gen_ai.prompt', 'genai.prompt'),
            completion: utilts_1.FrameworkUtils.getAttr(attrs, 'gen_ai.completion', 'genai.completion'),
            duration: utilts_1.FrameworkUtils.safeParseFloat(span.duration ?? 0) ?? 0,
        });
    }
    extractTokenUsage(span) {
        const attrs = span.attributes;
        // Try underscore format first (gen_ai.usage.input_tokens - OpenLit format)
        let inputTokens = utilts_1.FrameworkUtils.safeParseInt(utilts_1.FrameworkUtils.getAttr(attrs, 'gen_ai.usage.input_tokens', 'genai.usage.inputtokens'));
        let outputTokens = utilts_1.FrameworkUtils.safeParseInt(utilts_1.FrameworkUtils.getAttr(attrs, 'gen_ai.usage.output_tokens', 'genai.usage.outputtokens'));
        let totalTokens = utilts_1.FrameworkUtils.safeParseInt(utilts_1.FrameworkUtils.getAttr(attrs, 'gen_ai.usage.total_tokens', 'genai.usage.totaltokens'));
        // Fallback to client token usage (total only)
        if (!totalTokens) {
            totalTokens = utilts_1.FrameworkUtils.safeParseInt(attrs['gen_ai.client.token.usage']);
        }
        // Calculate total if not provided
        if (!totalTokens && (inputTokens || outputTokens)) {
            totalTokens = inputTokens + outputTokens;
        }
        const { cacheRead, cacheWrite } = utilts_1.FrameworkUtils.getCacheTokens(attrs);
        return {
            inputTokens,
            outputTokens,
            totalTokens,
            ...(cacheRead != null && { cacheRead }),
            ...(cacheWrite != null && { cacheWrite }),
        };
    }
    /**
     * Extract Time to First Token (TTFT)
     */
    extractTTFT(span) {
        return utilts_1.FrameworkUtils.safeParseFloat(span.attributes['gen_ai.server.time_to_first_token']);
    }
    getNodeType(span) {
        if (this.isModelSpan(span))
            return 'llm';
        if (span.name.startsWith('tool ') ||
            utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.tool.name', 'genai.tool.name') ||
            utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.operation.name', 'genai.operation.name') === 'execute_tool') {
            return 'tool';
        }
        if (this.isAgentSpan(span))
            return 'agent';
        return 'workflow';
    }
}
exports.LangChainParser = LangChainParser;
//# sourceMappingURL=langchain.js.map