"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.OtelTracerHandler = void 0;
const spanDeduplicator_1 = require("../../../utils/spanDeduplicator");
const toolArgumentFields_1 = require("../../../utils/toolArgumentFields");
const utilts_1 = require("../../../utils/utilts");
const parseLlamaIndexCallToolIo_1 = require("../parseLlamaIndexCallToolIo");
const types_1 = require("../types");
/**
 * LlamaIndex OpenTelemetry tracer (e.g. llamaindex.opentelemetry.tracer).
 * - Span names have UUID suffix: AgentWorkflow.run-19a481aa-cb99-429a-b1e9-32e74eef067e.
 * - Attributes are typically empty; type and scope inferred from name prefix only.
 */
class OtelTracerHandler {
    constructor() {
        this.kind = 'otel_tracer';
        this.deduplicator = new spanDeduplicator_1.SpanDeduplicator();
    }
    normalizedName(span) {
        return (0, types_1.normalizeLlamaIndexSpanName)(span.name, 'otel_tracer');
    }
    getWorkflowScopedSpans(spans) {
        const idSet = new Set(spans.map((s) => s.id));
        const roots = spans.filter((s) => {
            const norm = this.normalizedName(s);
            const isRunRoot = norm === 'AgentWorkflow.run';
            const noParentInSet = !s.parentSpanId || s.parentSpanId === '' || !idSet.has(s.parentSpanId);
            return isRunRoot && noParentInSet;
        });
        if (roots.length === 0) {
            const anyRun = spans.find((s) => this.normalizedName(s) === 'AgentWorkflow.run');
            if (!anyRun) {
                return spans;
            }
            roots.push(anyRun);
        }
        const parentOf = new Map();
        spans.forEach((s) => parentOf.set(s.id, s.parentSpanId ?? null));
        const isDescendantOf = (spanId, rootId) => {
            let id = spanId;
            while (id) {
                if (id === rootId) {
                    return true;
                }
                id = parentOf.get(id) ?? null;
            }
            return false;
        };
        const rootIds = new Set(roots.map((r) => r.id));
        const scoped = spans.filter((s) => rootIds.has(s.id) || rootIds.has(s.parentSpanId ?? '') || roots.some((r) => isDescendantOf(s.id, r.id)));
        return scoped.length > 0 ? scoped : spans;
    }
    getTools(spans) {
        const scope = this.getWorkflowScopedSpans(spans);
        this.deduplicator.reset();
        const tools = [];
        const seen = new Set();
        for (const span of scope) {
            if (this.deduplicator.isDuplicate(span)) {
                continue;
            }
            if (!this.isToolSpan(span) || seen.has(span.id)) {
                continue;
            }
            const tool = this.extractTool(span);
            if (tool) {
                tools.push(tool);
                seen.add(span.id);
            }
        }
        return tools;
    }
    getAgents(spans) {
        const scope = this.getWorkflowScopedSpans(spans);
        this.deduplicator.reset();
        const agents = [];
        const seen = new Set();
        const spanById = new Map(scope.map((s) => [s.id, s]));
        for (const span of scope) {
            if (this.deduplicator.isDuplicate(span)) {
                continue;
            }
            if (!this.isAgentSpan(span) || seen.has(span.id)) {
                continue;
            }
            const agent = this.extractAgent(span);
            if (agent) {
                let toolDefinitions = (0, toolArgumentFields_1.collectAgentToolDefinitions)(agent.id, scope, spanById, (s) => {
                    if (this.normalizedName(s) !== 'AgentWorkflow.call_tool') {
                        return false;
                    }
                    return !(0, parseLlamaIndexCallToolIo_1.parseLlamaIndexCallToolAttrs)((0, parseLlamaIndexCallToolIo_1.getMergedSpanAttrs)(s)).isHandoff;
                }, {
                    resolveToolDisplayName: (sp, merged) => {
                        const p = (0, parseLlamaIndexCallToolIo_1.parseLlamaIndexCallToolAttrs)((0, parseLlamaIndexCallToolIo_1.getMergedSpanAttrs)(sp));
                        const n = String(p.toolName ?? merged.name ?? '').trim();
                        return n || merged.name;
                    },
                });
                toolDefinitions = toolDefinitions.map((def) => {
                    const sp = spanById.get(def.spanId);
                    if (!sp) {
                        return def;
                    }
                    const parsed = (0, parseLlamaIndexCallToolIo_1.parseLlamaIndexCallToolAttrs)((0, parseLlamaIndexCallToolIo_1.getMergedSpanAttrs)(sp));
                    const merged = (0, toolArgumentFields_1.mergedSpanAttributes)(sp);
                    const argument = parsed.toolKwargs ?? def.argument;
                    const output = parsed.output === undefined ? def.output : parsed.output;
                    const argument_defination = (0, toolArgumentFields_1.getToolArgumentDefinitionFromAttrs)(merged) ??
                        (0, toolArgumentFields_1.inferArgumentDefinationFromInputValue)(argument) ??
                        def.argument_defination;
                    const description = (0, toolArgumentFields_1.getToolDescriptionFromAttrs)(merged) ?? def.description;
                    return {
                        ...def,
                        ...(argument !== undefined && { argument }),
                        ...(output !== undefined && { output }),
                        ...(description != null && { description }),
                        ...(argument_defination !== undefined && { argument_defination }),
                    };
                });
                if (toolDefinitions.length) {
                    agent.toolDefinitions = toolDefinitions;
                }
                agents.push(agent);
                seen.add(span.id);
            }
        }
        return agents;
    }
    getModels(spans) {
        const scope = this.getWorkflowScopedSpans(spans);
        this.deduplicator.reset();
        const models = [];
        const seen = new Set();
        for (const span of scope) {
            if (this.deduplicator.isDuplicate(span)) {
                continue;
            }
            if (this.isAsyncOnlyModelSpan(span)) {
                continue;
            }
            if (!this.isModelSpan(span) || seen.has(span.id)) {
                continue;
            }
            const model = this.extractModel(span);
            if (model) {
                models.push(model);
                seen.add(model.id);
            }
        }
        return models;
    }
    getModelsFromUnscoped(spans) {
        const models = this.getModels(spans);
        return models;
    }
    generateDag(spans) {
        const scope = this.getWorkflowScopedSpans(spans);
        this.deduplicator.reset();
        const uniqueSpans = scope.filter((s) => !this.deduplicator.isDuplicate(s));
        const parentOf = new Map(uniqueSpans.map((s) => [s.id, s.parentSpanId ?? null]));
        const nodeMap = new Map();
        for (const span of uniqueSpans) {
            if (this.isAsyncOnlyModelSpan(span)) {
                continue;
            }
            const name = this.normalizedName(span);
            nodeMap.set(span.id, {
                id: span.id,
                type: this.getNodeType(span),
                name: name || span.name,
                children: [],
                metadata: {
                    timestamp: span.timestamp,
                    duration: span.duration,
                    parentSpanId: span.parentSpanId,
                    attributes: span.attributes,
                    instrumentation: 'otel_tracer'
                }
            });
        }
        for (const span of uniqueSpans) {
            if (!nodeMap.has(span.id)) {
                continue;
            }
            let pid = span.parentSpanId ?? null;
            while (pid && !nodeMap.has(pid)) {
                pid = parentOf.get(pid) ?? null;
            }
            if (pid) {
                nodeMap.get(pid).children.push(span.id);
            }
        }
        const nodes = Array.from(nodeMap.values());
        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: {}
            });
        }
        const rootSpan = uniqueSpans.find((s) => this.normalizedName(s) === 'AgentWorkflow.run');
        const rootNode = rootSpan ? nodeMap.get(rootSpan.id) : null;
        if (rootNode) {
            return [rootNode, ...nodes.filter((n) => n.id !== rootNode.id)];
        }
        return nodes;
    }
    generateLinearFlow(spans) {
        const scope = this.getWorkflowScopedSpans(spans);
        this.deduplicator.reset();
        const childMap = this.buildChildMap(scope);
        const allowedTypes = new Set(['agent', 'tool', 'llm']);
        const sorted = scope
            .filter((s) => !this.deduplicator.isDuplicate(s))
            .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
        const candidates = sorted
            .filter((span) => allowedTypes.has(this.getNodeType(span)))
            .filter((span) => {
            const type = this.getNodeType(span);
            if (type === 'llm' && this.isAsyncOnlyModelSpan(span)) {
                return false;
            }
            return true;
        });
        const seenAgentNames = new Set();
        const filtered = candidates.filter((span) => {
            const type = this.getNodeType(span);
            if (type !== 'agent') {
                return true;
            }
            const agentName = this.extractAgent(span)?.name ?? span.id;
            const asyncOnly = this.isAsyncOnlyAgent(span, scope, childMap);
            if (asyncOnly && seenAgentNames.has(agentName)) {
                return false;
            }
            seenAgentNames.add(agentName);
            return true;
        });
        return filtered.map((span, index) => ({
            stepNumber: index + 1,
            spanId: span.id,
            type: this.getNodeType(span),
            name: this.normalizedName(span) || span.name,
            timestamp: span.timestamp,
            duration: span.duration,
            input: utilts_1.FrameworkUtils.getAttr(span.attributes, 'input.value', 'input', 'gen_ai.workflow.input', 'genai.workflow.input'),
            output: utilts_1.FrameworkUtils.getAttr(span.attributes, 'output.value', 'output', 'gen_ai.workflow.output', 'genai.workflow.output'),
            metadata: {
                parentSpanId: span.parentSpanId,
                instrumentation: 'otel_tracer'
            }
        }));
    }
    isToolSpan(span) {
        const n = this.normalizedName(span);
        return n === 'AgentWorkflow.call_tool' || n.startsWith('FunctionTool.');
    }
    /** LangChain/LangGraph-style routing and plumbing (LlamaIndex can use these when built on LangChain). */
    isInfrastructureWorkflow(span) {
        const n = this.normalizedName(span);
        const wfType = String(utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.workflow.type', 'genai.workflow.type') ?? '');
        const infra = [
            'RunnableLambda',
            'RunnableSequence',
            'RunnableParallel',
            'should_continue',
            'route_decision',
            'route_after_supervisor',
        ];
        return infra.some((p) => wfType.includes(p) || n.includes(p));
    }
    isAgentSpan(span) {
        if (this.isInfrastructureWorkflow(span)) {
            return false;
        }
        return this.normalizedName(span) === 'AgentWorkflow.run_agent_step';
    }
    isModelSpan(span) {
        const n = this.normalizedName(span);
        return /^OpenAI\.(astream_chat|acomplete|_prepare_chat)/.test(n) ||
            /^Anthropic\.|^Cohere\.|^Mistral\./.test(n) ||
            n.startsWith('chat ') ||
            n.includes('llm ');
    }
    buildChildMap(spans) {
        const map = new Map();
        for (const s of spans) {
            const pid = s.parentSpanId ?? '';
            if (!pid) {
                continue;
            }
            if (!map.has(pid)) {
                map.set(pid, []);
            }
            map.get(pid).push(s);
        }
        return map;
    }
    getDescendantIds(rootId, childMap) {
        const out = new Set();
        const queue = [rootId];
        while (queue.length) {
            const id = queue.shift();
            const children = childMap.get(id) ?? [];
            for (const c of children) {
                if (!out.has(c.id)) {
                    out.add(c.id);
                    queue.push(c.id);
                }
            }
        }
        return out;
    }
    /** Same as openInference: safe to hide; standard traces have another LLM span for the same call. */
    isAsyncOnlyModelSpan(span) {
        const n = this.normalizedName(span);
        return n === 'OpenAI._prepare_chat_with_tools' || n === 'OpenAI.astream_chat';
    }
    isAsyncOnlyAgent(agentSpan, scope, childMap) {
        const descendantIds = this.getDescendantIds(agentSpan.id, childMap);
        const descendants = scope.filter((s) => descendantIds.has(s.id));
        const modelDescendants = descendants.filter((s) => this.isModelSpan(s));
        const toolDescendants = descendants.filter((s) => this.normalizedName(s) === 'AgentWorkflow.call_tool');
        if (toolDescendants.length > 0 || modelDescendants.length === 0) {
            return false;
        }
        return modelDescendants.every((s) => this.isAsyncOnlyModelSpan(s));
    }
    extractTool(span) {
        const n = this.normalizedName(span);
        const parsed = (0, parseLlamaIndexCallToolIo_1.parseLlamaIndexCallToolAttrs)((0, parseLlamaIndexCallToolIo_1.getMergedSpanAttrs)(span));
        const name = n === 'AgentWorkflow.call_tool'
            ? String(parsed.toolName ?? 'call_tool')
            : n.startsWith('FunctionTool.')
                ? n
                : n;
        const base = (0, utilts_1.buildCanonicalTool)({
            id: span.id,
            name,
            input: parsed.toolKwargs ?? span.attributes?.input ?? span.attributes?.['input.value'],
            output: parsed.output ?? span.attributes?.output ?? span.attributes?.['output.value'],
            duration: span.duration,
            timestamp: span.timestamp,
            spanId: span.id,
            executionType: 'actual',
            metadata: { spanId: span.id, instrumentation: 'otel_tracer' },
        });
        return (0, parseLlamaIndexCallToolIo_1.enrichLlamaIndexToolFields)(base, span, parsed);
    }
    extractAgent(span) {
        const n = this.normalizedName(span);
        const merged = (0, toolArgumentFields_1.mergedSpanAttributes)(span);
        return (0, utilts_1.buildCanonicalAgent)({
            id: span.id,
            name: n === 'AgentWorkflow.run_agent_step' ? 'agent_step' : n,
            type: 'llamaindex',
            ...((0, toolArgumentFields_1.getAgentDescriptionFromAttrs)(merged) != null && {
                description: (0, toolArgumentFields_1.getAgentDescriptionFromAttrs)(merged),
            }),
            metadata: { spanId: span.id, timestamp: span.timestamp, duration: span.duration, instrumentation: 'otel_tracer' },
        });
    }
    extractModel(span) {
        const n = this.normalizedName(span);
        const modelName = utilts_1.FrameworkUtils.getAttr(span.attributes, 'llm.model_name') ||
            utilts_1.FrameworkUtils.getModelAttrResponseFirst(span.attributes) ||
            (n.match(/^OpenAI\./) ? 'gpt-4o' : null) ||
            'unknown';
        const provider = utilts_1.FrameworkUtils.getAttr(span.attributes, 'llm.provider', 'llm.system', 'gen_ai.system', 'genai.system') ??
            (n.startsWith('OpenAI.') ? 'openai' : n.startsWith('Anthropic.') ? 'anthropic' : 'unknown');
        const inputTokens = utilts_1.FrameworkUtils.safeParseInt(utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.usage.input_tokens', 'llm.usage.input_tokens')) || 0;
        const outputTokens = utilts_1.FrameworkUtils.safeParseInt(utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.usage.output_tokens', 'llm.usage.output_tokens')) || 0;
        let totalTokens = utilts_1.FrameworkUtils.safeParseInt(utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.usage.total_tokens', 'llm.usage.total_tokens')) || 0;
        if (!totalTokens && (inputTokens || outputTokens)) {
            totalTokens = inputTokens + outputTokens;
        }
        const { cacheRead, cacheWrite } = utilts_1.FrameworkUtils.getCacheTokens(span.attributes);
        return (0, utilts_1.buildCanonicalExtendedModel)({
            id: span.id,
            provider: String(provider),
            modelName: String(modelName),
            tokenUsage: {
                inputTokens,
                outputTokens,
                totalTokens,
                ...(cacheRead != null && { cacheRead }),
                ...(cacheWrite != null && { cacheWrite }),
            },
            spanId: span.id,
            timestamp: span.timestamp,
            temperature: utilts_1.FrameworkUtils.safeParseFloat(utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.request.temperature', 'genai.request.temperature')),
            cost: 0,
            ttft: utilts_1.FrameworkUtils.safeParseFloat(span.attributes['gen_ai.server.time_to_first_token']),
            serverTimePerOutputToken: utilts_1.FrameworkUtils.safeParseFloat(span.attributes['gen_ai.server.time_per_output_token']),
            prompt: utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.prompt', 'genai.prompt'),
            completion: utilts_1.FrameworkUtils.getAttr(span.attributes, 'gen_ai.completion', 'genai.completion'),
            duration: utilts_1.FrameworkUtils.safeParseFloat(span.duration ?? 0) ?? 0,
        });
    }
    getNodeType(span) {
        if (this.isModelSpan(span)) {
            return 'llm';
        }
        if (this.isToolSpan(span)) {
            return 'tool';
        }
        if (this.isAgentSpan(span)) {
            return 'agent';
        }
        return 'workflow';
    }
}
exports.OtelTracerHandler = OtelTracerHandler;
//# sourceMappingURL=otelTracerHandler.js.map