"use strict";
/**
 * edge-case-handlers.ts
 * Modular edge case handlers for different tool call patterns
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.DEFAULT_HANDLERS = exports.DirectToolExecutionHandler = exports.WorkflowMessageToolHandler = exports.RunnableParallelHandler = exports.OpenAIFunctionCallHandler = exports.SequentialToolCallHandler = exports.NestedToolCallHandler = exports.EdgeCaseHandlerRegistry = void 0;
const utilts_1 = require("./utilts");
const toolCallAnalyzer_1 = require("./toolCallAnalyzer");
/**
 * 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;
}
/**
 * Utility: Safe parseInt
 */
function safeParseInt(value) {
    if (value === undefined || value === null)
        return 0;
    const parsed = parseInt(String(value), 10);
    return isNaN(parsed) ? 0 : parsed;
}
function safeParseJson(value) {
    if (typeof value !== 'string')
        return undefined;
    const s = value.trim();
    if (!s)
        return undefined;
    try {
        return JSON.parse(s);
    }
    catch {
        return undefined;
    }
}
function tokenizeName(s) {
    const cleaned = String(s ?? '').trim();
    if (!cleaned)
        return [];
    return cleaned
        .replace(/([a-z])([A-Z])/g, '$1 $2')
        .replace(/[_\-\s]+/g, ' ')
        .toLowerCase()
        .split(' ')
        .map((t) => t.trim())
        .filter(Boolean);
}
function tokenOverlapScore(a, b) {
    const A = new Set(tokenizeName(a));
    const B = new Set(tokenizeName(b));
    if (A.size === 0 || B.size === 0)
        return 0;
    let overlap = 0;
    for (const t of A)
        if (B.has(t))
            overlap++;
    return overlap;
}
function looksLikeToolMessage(name, content) {
    const n = name.toLowerCase();
    const c = content.trim();
    if (!name || n === 'user' || n === 'system')
        return false;
    if (n.includes('tool'))
        return true;
    if (n.endsWith('collector') || n.endsWith('retriever') || n.endsWith('search') || n.endsWith('fetcher')) {
        return true;
    }
    // Short “status-like” content is usually tool output, not an agent report.
    if (c.length > 0 && c.length <= 250) {
        const hasMarkdown = c.includes('###') || c.includes('####') || c.includes('```');
        if (!hasMarkdown)
            return true;
    }
    return false;
}
function parseSerializedMessage(s) {
    if (typeof s !== 'string')
        return null;
    const contentMatch = s.match(/content='([\s\S]*?)'\s+/);
    const nameMatch = s.match(/name='([^']+)'/);
    const idMatch = s.match(/id='([^']+)'/);
    if (!contentMatch && !nameMatch)
        return null;
    return { content: contentMatch?.[1], name: nameMatch?.[1], id: idMatch?.[1] };
}
/**
 * Registry for edge case handlers (Dependency Injection)
 */
class EdgeCaseHandlerRegistry {
    constructor() {
        this.handlers = new Map();
    }
    register(name, handler) {
        this.handlers.set(name, handler);
    }
    unregister(name) {
        this.handlers.delete(name);
    }
    getHandler(name) {
        return this.handlers.get(name);
    }
    getAllHandlers() {
        const handlers = Array.from(this.handlers.values());
        return handlers.sort((a, b) => (a.priority || 999) - (b.priority || 999));
    }
    clear() {
        this.handlers.clear();
    }
}
exports.EdgeCaseHandlerRegistry = EdgeCaseHandlerRegistry;
/**
 * HANDLER 1: Nested Tool Calls
 *
 * Pattern: Agent output parser (decision) with child tool executions
 * Example: Parent says "call get_similar_stocks", child actually executes "get_financial_metrics"
 */
class NestedToolCallHandler {
    constructor() {
        this.name = 'nested_tool_call';
        this.priority = 1;
        this.toolAnalyzer = new toolCallAnalyzer_1.ToolCallAnalyzer();
    }
    canHandle(span, _context) {
        const output = getAttr(span.attributes, 'gen_ai.workflow.output', 'genai.workflow.output') || '';
        const outputStr = typeof output === 'string' ? output : JSON.stringify(output);
        const workflowType = getAttr(span.attributes, 'gen_ai.workflow.type', 'genai.workflow.type') || '';
        return ((workflowType.includes('AgentOutputParser') ||
            workflowType.includes('OutputParser')) &&
            outputStr.includes('tool'));
    }
    handle(span, context) {
        const children = context.spans.filter(s => s.parentSpanId === span.id);
        const analysis = this.toolAnalyzer.analyzeToolCall(span, children);
        console.log(`[NestedTool] Span ${span.id}: decided="${analysis.decidedToolName}", actual=${JSON.stringify(analysis.actualToolNames)}`);
        // Process actual tool executions from children
        analysis.actualToolNames.forEach(toolName => {
            const childSpan = children.find(c => this.toolAnalyzer.extractToolName(c) === toolName);
            if (childSpan && !context.seenSpans.has(childSpan.id)) {
                context.seenSpans.add(childSpan.id);
                const tool = {
                    id: childSpan.id,
                    name: toolName,
                    input: this.toolAnalyzer.extractToolInput(childSpan),
                    output: this.toolAnalyzer.extractToolOutput(childSpan),
                    duration: childSpan.duration,
                    timestamp: childSpan.timestamp,
                    spanId: childSpan.id,
                    executionType: 'actual',
                    tokenUsage: this.extractTokenUsage(childSpan),
                    metadata: {
                        decisionSpanId: span.id,
                        decidedTool: analysis.decidedToolName,
                        actualTool: toolName,
                        mismatch: analysis.decidedToolName !== toolName
                    }
                };
                context.tools.push(tool);
            }
        });
        // Mark parent as seen
        context.seenSpans.add(span.id);
    }
    extractTokenUsage(span) {
        const attrs = span.attributes;
        // Try both attribute formats
        const inputTokens = safeParseInt(getAttr(attrs, 'gen_ai.usage.input_tokens', 'genai.usage.inputtokens'));
        const outputTokens = safeParseInt(getAttr(attrs, 'gen_ai.usage.output_tokens', 'genai.usage.outputtokens'));
        let totalTokens = safeParseInt(getAttr(attrs, 'gen_ai.usage.total_tokens', 'genai.usage.totaltokens'));
        // Fallback to client token usage
        if (!totalTokens) {
            totalTokens = safeParseInt(attrs['gen_ai.client.token.usage']);
        }
        // Calculate total if not provided
        if (!totalTokens && (inputTokens || outputTokens)) {
            totalTokens = inputTokens + outputTokens;
        }
        if (inputTokens || outputTokens || totalTokens) {
            const { cacheRead, cacheWrite } = utilts_1.FrameworkUtils.getCacheTokens(attrs);
            return {
                inputTokens,
                outputTokens,
                totalTokens,
                ...(cacheRead != null && { cacheRead }),
                ...(cacheWrite != null && { cacheWrite }),
            };
        }
        return undefined;
    }
}
exports.NestedToolCallHandler = NestedToolCallHandler;
/**
 * HANDLER 2: Sequential Tool Calls
 *
 * Pattern: Multiple tools executed one after another
 */
class SequentialToolCallHandler {
    constructor() {
        this.name = 'sequential_tool_calls';
        this.priority = 2;
        this.toolAnalyzer = new toolCallAnalyzer_1.ToolCallAnalyzer();
    }
    canHandle(span, context) {
        const children = context.spans.filter(s => s.parentSpanId === span.id);
        const toolChildren = children.filter(c => c.name.startsWith('tool ') ||
            getAttr(c.attributes, 'gen_ai.operation.name', 'genai.operation.name') === 'tool' ||
            getAttr(c.attributes, 'gen_ai.operation.name', 'genai.operation.name') === 'execute_tool' ||
            getAttr(c.attributes, 'gen_ai.tool.name', 'genai.tool.name'));
        return toolChildren.length > 1;
    }
    handle(span, context) {
        const children = context.spans.filter(s => s.parentSpanId === span.id);
        const toolChildren = children
            .filter(c => c.name.startsWith('tool ') ||
            getAttr(c.attributes, 'gen_ai.operation.name', 'genai.operation.name') === 'tool' ||
            getAttr(c.attributes, 'gen_ai.operation.name', 'genai.operation.name') === 'execute_tool' ||
            getAttr(c.attributes, 'gen_ai.tool.name', 'genai.tool.name'))
            .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
        const pattern = this.toolAnalyzer.detectExecutionPattern(toolChildren);
        console.log(`[Sequential] Span ${span.id}: ${toolChildren.length} tools, pattern=${pattern}`);
        toolChildren.forEach((toolSpan, index) => {
            if (context.seenSpans.has(toolSpan.id))
                return;
            context.seenSpans.add(toolSpan.id);
            const toolName = this.toolAnalyzer.extractToolName(toolSpan);
            if (!toolName)
                return;
            const tool = {
                id: toolSpan.id,
                name: toolName,
                input: this.toolAnalyzer.extractToolInput(toolSpan),
                output: this.toolAnalyzer.extractToolOutput(toolSpan),
                duration: toolSpan.duration,
                timestamp: toolSpan.timestamp,
                spanId: toolSpan.id,
                executionType: 'actual',
                metadata: {
                    executionPattern: pattern,
                    sequenceNumber: index + 1,
                    totalInSequence: toolChildren.length,
                    parentSpanId: span.id
                }
            };
            context.tools.push(tool);
        });
    }
}
exports.SequentialToolCallHandler = SequentialToolCallHandler;
/**
 * HANDLER 3: OpenAI Function Calling
 *
 * Pattern: function_call with arguments in output
 */
class OpenAIFunctionCallHandler {
    constructor() {
        this.name = 'openai_function_call';
        this.priority = 3;
        this.toolAnalyzer = new toolCallAnalyzer_1.ToolCallAnalyzer();
    }
    canHandle(span, _context) {
        const output = getAttr(span.attributes, 'gen_ai.workflow.output', 'genai.workflow.output', 'gen_ai.completion', 'genai.completion') || '';
        const outputStr = typeof output === 'string' ? output : JSON.stringify(output);
        return (outputStr.includes('function_call') ||
            (outputStr.includes('additionalkwargs') && outputStr.includes('functioncall')));
    }
    handle(span, context) {
        const output = getAttr(span.attributes, 'gen_ai.workflow.output', 'genai.workflow.output', 'gen_ai.completion', 'genai.completion');
        const outputStr = typeof output === 'string' ? output : JSON.stringify(output);
        // Extract function call information
        const functionNameMatch = outputStr.match(/"name"\s*:\s*"(\w+)"/);
        const argumentsMatch = outputStr.match(/"arguments"\s*:\s*"?([^,"}\]]+)"?/);
        if (functionNameMatch) {
            const functionName = functionNameMatch[1];
            const children = context.spans.filter(s => s.parentSpanId === span.id);
            console.log(`[FunctionCall] Span ${span.id}: function=${functionName}`);
            // Find actual execution in children
            const executionSpan = children.find(c => c.name.includes(functionName) ||
                (getAttr(c.attributes, 'gen_ai.tool.name', 'genai.tool.name') || '').includes(functionName));
            if (executionSpan && !context.seenSpans.has(executionSpan.id)) {
                context.seenSpans.add(executionSpan.id);
                const tool = {
                    id: executionSpan.id,
                    name: functionName,
                    input: argumentsMatch ? argumentsMatch[1] : this.toolAnalyzer.extractToolInput(executionSpan),
                    output: this.toolAnalyzer.extractToolOutput(executionSpan),
                    duration: executionSpan.duration,
                    timestamp: executionSpan.timestamp,
                    spanId: executionSpan.id,
                    executionType: 'actual',
                    metadata: {
                        functionCallSpanId: span.id,
                        callingPattern: 'openai_function'
                    }
                };
                context.tools.push(tool);
            }
        }
        context.seenSpans.add(span.id);
    }
}
exports.OpenAIFunctionCallHandler = OpenAIFunctionCallHandler;
/**
 * HANDLER 4: RunnableParallel Pattern
 *
 * Pattern: Multiple tools run in parallel (RunnableParallel)
 */
class RunnableParallelHandler {
    constructor() {
        this.name = 'runnable_parallel';
        this.priority = 4;
        this.toolAnalyzer = new toolCallAnalyzer_1.ToolCallAnalyzer();
    }
    canHandle(span, _context) {
        const workflowType = getAttr(span.attributes, 'gen_ai.workflow.type', 'genai.workflow.type') || '';
        return (workflowType.includes('RunnableParallel') ||
            span.name.includes('RunnableParallel') ||
            span.name.includes('parallel'));
    }
    handle(span, context) {
        const children = context.spans.filter(s => s.parentSpanId === span.id);
        console.log(`[Parallel] Span ${span.id}: ${children.length} parallel children`);
        children.forEach((child, index) => {
            if (context.seenSpans.has(child.id))
                return;
            context.seenSpans.add(child.id);
            const toolName = this.toolAnalyzer.extractToolName(child);
            if (toolName) {
                const tool = {
                    id: child.id,
                    name: toolName,
                    input: this.toolAnalyzer.extractToolInput(child),
                    output: this.toolAnalyzer.extractToolOutput(child),
                    duration: child.duration,
                    timestamp: child.timestamp,
                    spanId: child.id,
                    executionType: 'actual',
                    metadata: {
                        executionPattern: 'parallel',
                        parallelIndex: index,
                        totalParallel: children.length,
                        parentSpanId: span.id
                    }
                };
                context.tools.push(tool);
            }
        });
    }
}
exports.RunnableParallelHandler = RunnableParallelHandler;
/**
 * HANDLER 5: Tool-like Messages Embedded in LangGraph workflow.output
 *
 * Pattern: gen_ai.workflow.type = LangGraph with workflow.output.messages including entries like:
 *   "content='Data collected for TCS.' ... name='DataCollector' ..."
 *
 * LangGraph/OpenLit sometimes does not emit explicit "tool ..." spans for these, so we create a Tool
 * attached to the best-matching child workflow span.
 */
class WorkflowMessageToolHandler {
    constructor() {
        this.name = 'workflow_message_tool';
        this.priority = 5;
    }
    canHandle(span, _context) {
        const opName = getAttr(span.attributes, 'gen_ai.operation.name', 'genai.operation.name');
        const wfType = getAttr(span.attributes, 'gen_ai.workflow.type', 'genai.workflow.type');
        if (opName !== 'workflow')
            return false;
        if (wfType !== 'LangGraph' && !String(span.name ?? '').includes('workflow LangGraph'))
            return false;
        const output = getAttr(span.attributes, 'gen_ai.workflow.output', 'genai.workflow.output');
        if (typeof output !== 'string')
            return false;
        return output.includes('"messages"') && output.includes("name='");
    }
    handle(span, context) {
        const rawOutput = getAttr(span.attributes, 'gen_ai.workflow.output', 'genai.workflow.output');
        const parsed = safeParseJson(rawOutput);
        const messages = Array.isArray(parsed?.messages) ? parsed.messages : [];
        if (messages.length === 0)
            return;
        const toolCandidates = messages
            .map(parseSerializedMessage)
            .filter((m) => Boolean(m))
            .map((m) => ({
            name: String(m.name ?? '').trim(),
            content: String(m.content ?? '').trim(),
            msgId: m.id,
        }))
            .filter((m) => m.name && m.content)
            .filter((m) => looksLikeToolMessage(m.name, m.content));
        if (toolCandidates.length === 0)
            return;
        const children = context.spans.filter((s) => s.parentSpanId === span.id);
        const existingToolSpanIds = new Set(context.tools.map((t) => t.spanId));
        for (const cand of toolCandidates) {
            let bestChild;
            let bestScore = 0;
            for (const ch of children) {
                const chOp = getAttr(ch.attributes, 'gen_ai.operation.name', 'genai.operation.name');
                if (chOp !== 'workflow')
                    continue;
                const chType = String(getAttr(ch.attributes, 'gen_ai.workflow.type', 'genai.workflow.type') ?? '');
                const score = Math.max(tokenOverlapScore(cand.name, chType), tokenOverlapScore(cand.name, ch.name ?? ''));
                if (score > bestScore) {
                    bestScore = score;
                    bestChild = ch;
                }
            }
            const attachSpan = bestChild && bestScore > 0 ? bestChild : span;
            if (existingToolSpanIds.has(attachSpan.id))
                continue;
            const tool = {
                id: attachSpan.id,
                name: cand.name,
                input: getAttr(attachSpan.attributes, 'gen_ai.workflow.input', 'genai.workflow.input'),
                output: cand.content,
                duration: attachSpan.duration,
                timestamp: attachSpan.timestamp,
                spanId: attachSpan.id,
                executionType: 'actual',
                metadata: {
                    source: 'workflow_messages',
                    messageId: cand.msgId,
                    parentWorkflowSpanId: span.id,
                    attachedToSpanId: attachSpan.id,
                },
            };
            context.tools.push(tool);
        }
    }
}
exports.WorkflowMessageToolHandler = WorkflowMessageToolHandler;
/**
 * HANDLER 5: Direct Tool Execution
 *
 * Pattern: Direct tool execution without special patterns
 */
class DirectToolExecutionHandler {
    constructor() {
        this.name = 'direct_tool_execution';
        this.priority = 10; // Low priority - fallback
        this.toolAnalyzer = new toolCallAnalyzer_1.ToolCallAnalyzer();
    }
    canHandle(span, _context) {
        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);
    }
    handle(span, context) {
        if (context.seenSpans.has(span.id))
            return;
        context.seenSpans.add(span.id);
        const toolName = this.toolAnalyzer.extractToolName(span);
        if (!toolName)
            return;
        console.log(`[DirectTool] Span ${span.id}: tool=${toolName}`);
        const tool = {
            id: span.id,
            name: toolName,
            input: this.toolAnalyzer.extractToolInput(span),
            output: this.toolAnalyzer.extractToolOutput(span),
            duration: span.duration,
            timestamp: span.timestamp,
            spanId: span.id,
            executionType: 'actual',
            metadata: {
                executionPattern: 'direct'
            }
        };
        context.tools.push(tool);
    }
}
exports.DirectToolExecutionHandler = DirectToolExecutionHandler;
/**
 * Export all handlers
 */
exports.DEFAULT_HANDLERS = [
    new NestedToolCallHandler(),
    new SequentialToolCallHandler(),
    new OpenAIFunctionCallHandler(),
    new RunnableParallelHandler(),
    new WorkflowMessageToolHandler(),
    new DirectToolExecutionHandler()
];
//# sourceMappingURL=edgeCaseHandlers.js.map