/**
 * Telemetry Handler Module
 *
 * Handles analytics, evaluation, performance metrics, and telemetry configuration.
 * Extracted from BaseProvider to follow Single Responsibility Principle.
 *
 * Responsibilities:
 * - Analytics creation and tracking
 * - Evaluation generation
 * - Performance metrics recording
 * - Cost calculation
 * - Telemetry configuration
 *
 * @module core/modules/TelemetryHandler
 */
import { nanoid } from "nanoid";
import { logger } from "../../utils/logger.js";
import { recordProviderPerformanceFromMetrics } from "../evaluationProviders.js";
import { modelConfig } from "../modelConfiguration.js";
import { TelemetryService } from "../../telemetry/telemetryService.js";
import { calculateCost, hasPricing } from "../../utils/pricing.js";
import { getLangfuseContext } from "../../services/server/ai/observability/instrumentation.js";
/**
 * TelemetryHandler class - Handles analytics and telemetry for AI providers
 */
export class TelemetryHandler {
    providerName;
    modelName;
    neurolink;
    constructor(providerName, modelName, neurolink) {
        this.providerName = providerName;
        this.modelName = modelName;
        this.neurolink = neurolink;
    }
    /**
     * Create analytics for a generation result
     */
    async createAnalytics(result, responseTime, context) {
        const { createAnalytics } = await import("../analytics.js");
        return createAnalytics(this.providerName, this.modelName, result, responseTime, context);
    }
    /**
     * Create evaluation for a generation result
     */
    async createEvaluation(result, options) {
        const { evaluateResponse } = await import("../evaluation.js");
        const context = {
            userQuery: options.prompt || options.input?.text || "Generated response",
            aiResponse: result.content,
            context: options.context,
            primaryDomain: options.evaluationDomain,
            assistantRole: "AI assistant",
            conversationHistory: options.conversationHistory?.map((msg) => ({
                role: msg.role,
                content: msg.content,
            })),
            toolUsage: options.toolUsageContext
                ? [
                    {
                        toolName: options.toolUsageContext,
                        input: {},
                        output: {},
                        executionTime: 0,
                    },
                ]
                : undefined,
            expectedOutcome: options.expectedOutcome,
            evaluationCriteria: options.evaluationCriteria,
        };
        const evaluation = await evaluateResponse(context);
        return evaluation;
    }
    /**
     * Record performance metrics for a generation
     */
    async recordPerformanceMetrics(usage, responseTime) {
        try {
            const totalTokens = (usage?.inputTokens || 0) + (usage?.outputTokens || 0);
            const actualCost = await this.calculateActualCost(usage || { inputTokens: 0, outputTokens: 0 });
            recordProviderPerformanceFromMetrics(this.providerName, {
                responseTime,
                tokensGenerated: totalTokens,
                cost: actualCost,
                success: true,
            });
            // Wire TelemetryService metrics so OTEL counters/histograms are populated
            TelemetryService.getInstance().recordAIRequest(this.providerName, this.modelName, totalTokens, responseTime, actualCost > 0 ? actualCost : undefined);
            logger.debug(`Performance recorded for ${this.providerName}`, {
                responseTime: `${responseTime}ms`,
                tokens: totalTokens,
                estimatedCost: `$${actualCost.toFixed(6)}`,
            });
        }
        catch (perfError) {
            logger.warn("⚠️ Performance recording failed:", perfError);
        }
    }
    /**
     * Calculate actual cost based on token usage and provider configuration.
     *
     * Uses the per-model pricing table first (which has accurate rates for
     * specific models like Claude on Vertex AI), then falls back to the
     * provider-level default cost from modelConfiguration.
     *
     * Previously this only used modelConfig.getCostInfo() which returns
     * provider-level defaults (e.g. Gemini rates for the "vertex" provider),
     * causing a ~1,780x under-estimate when the actual model was Claude Sonnet
     * on Vertex AI ($0.000060 vs $0.106895 for the same request).
     */
    async calculateActualCost(usage) {
        try {
            const promptTokens = usage?.inputTokens || 0;
            const completionTokens = usage?.outputTokens || 0;
            // Try the per-model pricing table first (includes correct rates for
            // Claude on Vertex, cache token rates, etc.)
            if (hasPricing(this.providerName, this.modelName)) {
                return calculateCost(this.providerName, this.modelName, {
                    input: promptTokens,
                    output: completionTokens,
                    total: promptTokens + completionTokens,
                });
            }
            // Fall back to provider-level default cost from configuration system
            const costInfo = modelConfig.getCostInfo(this.providerName, this.modelName);
            if (!costInfo) {
                return 0; // No cost info available
            }
            // Calculate cost per 1K tokens
            const inputCost = (promptTokens / 1000) * costInfo.input;
            const outputCost = (completionTokens / 1000) * costInfo.output;
            return inputCost + outputCost;
        }
        catch (error) {
            logger.debug(`Cost calculation failed for ${this.providerName}:`, error);
            return 0; // Fallback to 0 on any error
        }
    }
    /**
     * Create telemetry configuration for Vercel AI SDK experimental_telemetry
     * This enables automatic OpenTelemetry tracing when telemetry is enabled
     */
    getTelemetryConfig(options, operationType = "stream") {
        // Check if telemetry is enabled via NeuroLink observability config
        if (!this.neurolink?.isTelemetryEnabled()) {
            return undefined;
        }
        const context = options.context;
        const langfuseContext = getLangfuseContext();
        const traceName = context?.traceName ?? langfuseContext?.traceName;
        const userId = context?.userId ?? langfuseContext?.userId;
        const functionId = traceName ? traceName : userId ? userId : "guest";
        const metadata = {
            ...(context?.metadata || {}),
            provider: this.providerName,
            model: this.modelName,
            toolsEnabled: !options.disableTools,
            neurolink: true,
            operationType,
            originalProvider: this.providerName,
        };
        // Add sessionId if available
        if ("sessionId" in options && options.sessionId) {
            const sessionId = options.sessionId;
            if (typeof sessionId === "string" ||
                typeof sessionId === "number" ||
                typeof sessionId === "boolean") {
                metadata.sessionId = sessionId;
            }
        }
        return {
            isEnabled: true,
            functionId,
            metadata,
            recordInputs: process.env.NEUROLINK_RECORD_INPUTS?.toLowerCase() !== "false",
            recordOutputs: true,
        };
    }
    /**
     * Handle tool execution storage if available
     */
    async handleToolExecutionStorage(toolCalls, toolResults, options, currentTime) {
        // Check if tools are not empty
        const hasToolData = (toolCalls && toolCalls.length > 0) ||
            (toolResults && toolResults.length > 0);
        // Check if NeuroLink instance is available and has tool execution storage
        const hasStorageAvailable = this.neurolink?.isToolExecutionStorageAvailable();
        // Early return if storage is not available or no tool data
        if (!hasStorageAvailable || !hasToolData || !this.neurolink) {
            return;
        }
        const sessionId = options.context?.sessionId ||
            options.sessionId ||
            `session-${nanoid()}`;
        const userId = options.context?.userId ||
            options.userId;
        try {
            await this.neurolink.storeToolExecutions(sessionId, userId, toolCalls, toolResults, currentTime);
        }
        catch (error) {
            logger.warn("Failed to store tool executions:", error);
        }
    }
}
//# sourceMappingURL=TelemetryHandler.js.map