"use strict";
/**
 * tool-call-analyzer.ts
 * Analyzes tool call patterns: decision vs execution, sequential vs parallel
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.ToolCallAnalyzer = void 0;
/**
 * Utility: Extract attribute with fallback between gen_ai.* and genai.* formats
 */
function getAttr(attrs, ...keys) {
    for (const key of keys) {
        if (attrs[key] !== undefined && attrs[key] !== null) {
            return attrs[key];
        }
    }
    return undefined;
}
class ToolCallAnalyzer {
    /**
     * Analyze tool call pattern in a span
     */
    analyzeToolCall(span, children) {
        const output = getAttr(span.attributes, 'gen_ai.workflow.output', 'genai.workflow.output') || '';
        const outputStr = typeof output === 'string' ? output : JSON.stringify(output);
        // Decision: Agent decides which tool to call
        const isDecision = this.isDecisionSpan(span, outputStr);
        // Execution: Actual tool execution with results
        const isExecution = this.isExecutionSpan(span, outputStr);
        // Extract decided tool name from output parser
        const decidedToolName = this.extractDecidedToolName(outputStr);
        // Extract actual executions from children
        const actualToolNames = children
            .filter(c => this.isToolExecution(c))
            .map(c => this.extractToolName(c))
            .filter(n => n !== null);
        // Determine execution pattern if multiple tools
        let executionPattern;
        if (actualToolNames.length > 1) {
            executionPattern = this.detectExecutionPattern(children.filter(c => this.isToolExecution(c)));
        }
        return {
            isDecision,
            isExecution,
            actualToolNames,
            decidedToolName,
            executionPattern
        };
    }
    /**
     * Check if span is a decision span (agent output parser)
     */
    isDecisionSpan(span, output) {
        const workflowType = getAttr(span.attributes, 'gen_ai.workflow.type', 'genai.workflow.type') || '';
        return (workflowType.includes('AgentOutputParser') ||
            workflowType.includes('OutputParser') ||
            output.includes('"tool":') ||
            output.includes('toolinput') ||
            output.includes('tool_input'));
    }
    /**
     * Check if span is an execution span
     */
    isExecutionSpan(span, output) {
        const opName = getAttr(span.attributes, 'gen_ai.operation.name', 'genai.operation.name');
        const toolName = getAttr(span.attributes, 'gen_ai.tool.name', 'genai.tool.name');
        return (span.name.startsWith('tool ') ||
            opName === 'tool' ||
            opName === 'execute_tool' ||
            toolName !== undefined ||
            this.hasToolExecutionPattern(output));
    }
    /**
     * Extract tool name from span
     */
    extractToolName(span) {
        // Priority 1: Direct attributes (try both formats)
        const directName = getAttr(span.attributes, 'gen_ai.tool.name', 'genai.tool.name');
        if (directName)
            return directName;
        // Priority 2: Parse from span name "tool <name>"
        // This is the most common pattern in LangGraph/LangChain traces
        const simpleMatch = span.name.match(/^tool\s+(.+)$/i);
        if (simpleMatch)
            return simpleMatch[1];
        // Priority 3: Other span name patterns
        const patterns = [
            /tool[\s_]+(\w+)/i,
            /^(\w+)[\s_]+tool$/i,
            /tool\.(\w+)/i
        ];
        for (const pattern of patterns) {
            const match = span.name.match(pattern);
            if (match)
                return match[1];
        }
        // Priority 4: Workflow output
        const output = getAttr(span.attributes, 'gen_ai.workflow.output', 'genai.workflow.output') || '';
        const outputStr = typeof output === 'string' ? output : JSON.stringify(output);
        return this.extractToolNameFromOutput(outputStr);
    }
    /**
     * Extract decided tool name from agent output
     */
    extractDecidedToolName(output) {
        const patterns = [
            /"tool"\s*:\s*"(\w+)"/,
            /tool[\s:]+["']?(get_\w+|calculate_\w+|search_\w+|fetch_\w+|analyze_\w+)["']?/i,
            /"name"\s*:\s*"(get_\w+|calculate_\w+|search_\w+|fetch_\w+|analyze_\w+)"/,
            /tool_input[\s\S]*?"(\w+)"/
        ];
        for (const pattern of patterns) {
            const match = output.match(pattern);
            if (match)
                return match[1];
        }
        return null;
    }
    /**
     * Extract tool name from output string
     */
    extractToolNameFromOutput(output) {
        const patterns = [
            /"name"\s*:\s*"(\w+)"/,
            /tool[\s:]+["']?(\w+)["']?/i,
            /"function"\s*:\s*{\s*[^}]*"name"\s*:\s*"(\w+)"/,
            /"tool_name"\s*:\s*"(\w+)"/
        ];
        for (const pattern of patterns) {
            const match = output.match(pattern);
            if (match)
                return match[1];
        }
        return null;
    }
    /**
     * Check if span is a tool execution
     */
    isToolExecution(span) {
        const opName = getAttr(span.attributes, 'gen_ai.operation.name', 'genai.operation.name');
        const toolName = getAttr(span.attributes, 'gen_ai.tool.name', 'genai.tool.name');
        return (span.name.startsWith('tool ') ||
            opName === 'tool' ||
            opName === 'execute_tool' ||
            toolName !== undefined);
    }
    /**
     * Check if output has tool execution patterns
     */
    hasToolExecutionPattern(output) {
        return (output.includes('tool_output') ||
            output.includes('observation') ||
            output.includes('result:') ||
            output.includes('function_result'));
    }
    /**
     * Detect if tool calls are sequential, parallel, or mixed
     */
    detectExecutionPattern(spans) {
        if (spans.length <= 1)
            return 'sequential';
        const sortedSpans = [...spans].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
        let parallelCount = 0;
        let sequentialCount = 0;
        for (let i = 1; i < sortedSpans.length; i++) {
            const prev = sortedSpans[i - 1];
            const curr = sortedSpans[i];
            const prevStart = new Date(prev.timestamp).getTime();
            const prevEnd = prevStart + (prev.duration / 1000000); // Convert nanoseconds to ms
            const currStart = new Date(curr.timestamp).getTime();
            // If current starts before previous ends, they overlap (parallel)
            if (currStart < prevEnd) {
                parallelCount++;
            }
            else {
                sequentialCount++;
            }
        }
        if (parallelCount > 0 && sequentialCount > 0)
            return 'mixed';
        if (parallelCount > 0)
            return 'parallel';
        return 'sequential';
    }
    /**
     * Extract tool input from span
     */
    extractToolInput(span) {
        return getAttr(span.attributes, 'gen_ai.tool.input', 'genai.tool.input', 'gen_ai.workflow.input', 'genai.workflow.input');
    }
    /**
     * Extract tool output from span
     */
    extractToolOutput(span) {
        return getAttr(span.attributes, 'gen_ai.tool.output', 'genai.tool.output', 'gen_ai.workflow.output', 'genai.workflow.output');
    }
}
exports.ToolCallAnalyzer = ToolCallAnalyzer;
//# sourceMappingURL=toolCallAnalyzer.js.map