import { i as logger_default } from "./logger-BudIH0qK.js";
import { t as getDirectory } from "./esm-C5zIToiC.js";
import "./types-Cg21S-St.js";
import "./tables-4jScbad_.js";
import { n as getTraceStore } from "./store-D0ZCRr6q.js";
import path from "path";
import express from "express";
import protobuf from "protobufjs";

//#region src/tracing/protobuf.ts
/**
* OTLP Protobuf decoder for trace data
*
* Uses protobufjs to decode binary OTLP trace requests sent with
* Content-Type: application/x-protobuf
*/
let protoRoot = null;
let ExportTraceServiceRequest = null;
/**
* Get the path to the proto files directory.
* This works correctly in both development (tsx) and production (bundled) environments.
*/
function getProtoDir() {
	return path.join(getDirectory(), "tracing", "proto");
}
/**
* Load and cache the OTLP proto definitions
*/
async function loadProtoDefinitions() {
	if (protoRoot) return protoRoot;
	logger_default.debug("[Protobuf] Loading OTLP proto definitions");
	const protoDir = getProtoDir();
	logger_default.debug(`[Protobuf] Proto directory: ${protoDir}`);
	try {
		const root = new protobuf.Root();
		root.resolvePath = (_origin, target) => {
			return path.join(protoDir, target);
		};
		await root.load("opentelemetry/proto/collector/trace/v1/trace_service.proto");
		protoRoot = root;
		logger_default.debug("[Protobuf] Successfully loaded OTLP proto definitions");
		return protoRoot;
	} catch (error) {
		logger_default.error(`[Protobuf] Failed to load proto definitions: ${error}`);
		throw error;
	}
}
/**
* Get the ExportTraceServiceRequest message type
*/
async function getExportTraceServiceRequestType() {
	if (ExportTraceServiceRequest) return ExportTraceServiceRequest;
	ExportTraceServiceRequest = (await loadProtoDefinitions()).lookupType("opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest");
	return ExportTraceServiceRequest;
}
/**
* Convert a Uint8Array to a hex string
*/
function bytesToHex(bytes, expectedLength) {
	if (!bytes || bytes.length === 0) return "0".repeat(expectedLength);
	return Buffer.from(bytes).toString("hex").padStart(expectedLength, "0");
}
/**
* Decode a binary OTLP ExportTraceServiceRequest
*
* @param data - The binary protobuf data
* @returns The decoded request object
*/
async function decodeExportTraceServiceRequest(data) {
	const messageType = await getExportTraceServiceRequestType();
	try {
		const message = messageType.decode(data instanceof Buffer ? new Uint8Array(data) : data);
		const decoded = messageType.toObject(message, {
			longs: Number,
			bytes: Uint8Array,
			defaults: true,
			arrays: true
		});
		logger_default.debug(`[Protobuf] Decoded ExportTraceServiceRequest with ${decoded.resourceSpans?.length || 0} resource spans`);
		return decoded;
	} catch (error) {
		logger_default.error(`[Protobuf] Failed to decode ExportTraceServiceRequest: ${error}`);
		throw new Error(`Invalid protobuf data: ${error instanceof Error ? error.message : error}`);
	}
}

//#endregion
//#region src/tracing/otlpReceiver.ts
const SPAN_KIND_MAP = {
	0: "unspecified",
	1: "internal",
	2: "server",
	3: "client",
	4: "producer",
	5: "consumer"
};
var OTLPReceiver = class {
	app;
	traceStore;
	port;
	server;
	constructor() {
		this.app = express();
		this.traceStore = getTraceStore();
		logger_default.debug("[OtlpReceiver] Initializing OTLP receiver");
		this.setupMiddleware();
		this.setupRoutes();
	}
	setupMiddleware() {
		this.app.use(express.json({
			limit: "10mb",
			type: "application/json"
		}));
		this.app.use(express.raw({
			type: "application/x-protobuf",
			limit: "10mb"
		}));
		logger_default.debug("[OtlpReceiver] Middleware configured for JSON and protobuf");
	}
	setupRoutes() {
		this.app.post("/v1/traces", async (req, res) => {
			const contentType = req.headers["content-type"] || "unknown";
			const bodySize = req.body ? JSON.stringify(req.body).length : 0;
			logger_default.debug(`[OtlpReceiver] Received trace request: ${req.headers["content-type"]} with ${bodySize} bytes`);
			logger_default.debug("[OtlpReceiver] Starting to process traces");
			const isJson = contentType === "application/json";
			const isProtobuf = contentType === "application/x-protobuf";
			if (!isJson && !isProtobuf) {
				res.status(415).json({ error: "Unsupported content type" });
				return;
			}
			try {
				let traces = [];
				if (isJson) {
					logger_default.debug("[OtlpReceiver] Parsing OTLP JSON request");
					logger_default.debug(`[OtlpReceiver] Request body: ${JSON.stringify(req.body).substring(0, 500)}...`);
					traces = this.parseOTLPJSONRequest(req.body);
				} else if (isProtobuf) {
					logger_default.debug("[OtlpReceiver] Parsing OTLP protobuf request");
					logger_default.debug(`[OtlpReceiver] Request body size: ${req.body?.length || 0} bytes`);
					traces = await this.parseOTLPProtobufRequest(req.body);
				}
				logger_default.debug(`[OtlpReceiver] Parsed ${traces.length} traces from request`);
				const spansByTrace = /* @__PURE__ */ new Map();
				const traceInfoById = /* @__PURE__ */ new Map();
				for (const trace of traces) {
					if (!spansByTrace.has(trace.traceId)) {
						spansByTrace.set(trace.traceId, []);
						const evaluationId = trace.span.attributes?.["evaluation.id"];
						const testCaseId = trace.span.attributes?.["test.case.id"];
						const info = traceInfoById.get(trace.traceId) ?? {};
						if (evaluationId) info.evaluationId = evaluationId;
						if (testCaseId) info.testCaseId = testCaseId;
						traceInfoById.set(trace.traceId, info);
					}
					spansByTrace.get(trace.traceId).push(trace.span);
				}
				logger_default.debug(`[OtlpReceiver] Grouped spans into ${spansByTrace.size} traces`);
				for (const [traceId, info] of traceInfoById) try {
					logger_default.debug(`[OtlpReceiver] Creating trace record for ${traceId}`);
					await this.traceStore.createTrace({
						traceId,
						evaluationId: info.evaluationId || "",
						testCaseId: info.testCaseId || ""
					});
				} catch (error) {
					logger_default.debug(`[OtlpReceiver] Trace ${traceId} may already exist: ${error}`);
				}
				for (const [traceId, spans] of spansByTrace) {
					logger_default.debug(`[OtlpReceiver] Storing ${spans.length} spans for trace ${traceId}`);
					await this.traceStore.addSpans(traceId, spans, { skipTraceCheck: true });
				}
				res.status(200).json({ partialSuccess: {} });
				logger_default.debug("[OtlpReceiver] Successfully processed traces");
			} catch (error) {
				logger_default.error(`[OtlpReceiver] Failed to process OTLP traces: ${error}`);
				logger_default.error(`[OtlpReceiver] Error stack: ${error instanceof Error ? error.stack : "No stack"}`);
				const errorMessage = error instanceof Error ? error.message : String(error);
				if (errorMessage.toLowerCase().includes("invalid protobuf")) {
					res.status(400).json({ error: errorMessage });
					return;
				}
				res.status(500).json({ error: "Internal server error" });
			}
		});
		this.app.get("/health", (_req, res) => {
			logger_default.debug("[OtlpReceiver] Health check requested");
			res.status(200).json({ status: "ok" });
		});
		this.app.get("/v1/traces", (_req, res) => {
			res.status(200).json({
				service: "promptfoo-otlp-receiver",
				version: "1.0.0",
				supported_formats: ["json", "protobuf"]
			});
		});
		this.app.get("/debug/status", async (_req, res) => {
			res.status(200).json({
				status: "running",
				uptime: process.uptime(),
				timestamp: (/* @__PURE__ */ new Date()).toISOString(),
				port: this.port || 4318
			});
		});
		this.app.use((error, _req, res, _next) => {
			logger_default.error(`[OtlpReceiver] Global error handler: ${error}`);
			logger_default.error(`[OtlpReceiver] Error stack: ${error.stack}`);
			if (error instanceof SyntaxError && "body" in error) {
				res.status(400).json({ error: "Invalid JSON" });
				return;
			}
			res.status(500).json({ error: "Internal server error" });
		});
	}
	parseOTLPJSONRequest(body) {
		const traces = [];
		logger_default.debug(`[OtlpReceiver] Parsing request with ${body.resourceSpans?.length || 0} resource spans`);
		for (const resourceSpan of body.resourceSpans) {
			const resourceAttributes = this.parseAttributes(resourceSpan.resource?.attributes);
			logger_default.debug(`[OtlpReceiver] Parsed ${Object.keys(resourceAttributes).length} resource attributes`);
			for (const scopeSpan of resourceSpan.scopeSpans) for (const span of scopeSpan.spans) {
				const traceId = this.convertId(span.traceId, 32);
				const spanId = this.convertId(span.spanId, 16);
				const parentSpanId = span.parentSpanId ? this.convertId(span.parentSpanId, 16) : void 0;
				logger_default.debug(`[OtlpReceiver] Processing span: ${span.name} (${spanId}) in trace ${traceId}`);
				const spanKindName = SPAN_KIND_MAP[span.kind] ?? "unspecified";
				const attributes = {
					...resourceAttributes,
					...this.parseAttributes(span.attributes),
					"otel.scope.name": scopeSpan.scope?.name,
					"otel.scope.version": scopeSpan.scope?.version,
					"otel.span.kind": spanKindName,
					"otel.span.kind_code": span.kind
				};
				traces.push({
					traceId,
					span: {
						spanId,
						parentSpanId,
						name: span.name,
						startTime: Number(span.startTimeUnixNano) / 1e6,
						endTime: span.endTimeUnixNano ? Number(span.endTimeUnixNano) / 1e6 : void 0,
						attributes,
						statusCode: span.status?.code,
						statusMessage: span.status?.message
					}
				});
			}
		}
		return traces;
	}
	async parseOTLPProtobufRequest(body) {
		const traces = [];
		const decoded = await decodeExportTraceServiceRequest(body);
		logger_default.debug(`[OtlpReceiver] Parsing protobuf request with ${decoded.resourceSpans?.length || 0} resource spans`);
		for (const resourceSpan of decoded.resourceSpans || []) {
			const resourceAttributes = this.parseDecodedAttributes(resourceSpan.resource?.attributes);
			logger_default.debug(`[OtlpReceiver] Parsed ${Object.keys(resourceAttributes).length} resource attributes from protobuf`);
			for (const scopeSpan of resourceSpan.scopeSpans || []) for (const span of scopeSpan.spans || []) {
				const traceId = bytesToHex(span.traceId, 32);
				const spanId = bytesToHex(span.spanId, 16);
				const parentSpanId = span.parentSpanId?.length ? bytesToHex(span.parentSpanId, 16) : void 0;
				logger_default.debug(`[OtlpReceiver] Processing protobuf span: ${span.name} (${spanId}) in trace ${traceId}`);
				const spanKindName = SPAN_KIND_MAP[span.kind ?? 0] ?? "unspecified";
				const attributes = {
					...resourceAttributes,
					...this.parseDecodedAttributes(span.attributes),
					"otel.scope.name": scopeSpan.scope?.name,
					"otel.scope.version": scopeSpan.scope?.version,
					"otel.span.kind": spanKindName,
					"otel.span.kind_code": span.kind ?? 0
				};
				const startTimeNano = typeof span.startTimeUnixNano === "number" ? span.startTimeUnixNano : Number(span.startTimeUnixNano);
				const endTimeNano = span.endTimeUnixNano ? typeof span.endTimeUnixNano === "number" ? span.endTimeUnixNano : Number(span.endTimeUnixNano) : void 0;
				traces.push({
					traceId,
					span: {
						spanId,
						parentSpanId,
						name: span.name,
						startTime: startTimeNano / 1e6,
						endTime: endTimeNano ? endTimeNano / 1e6 : void 0,
						attributes,
						statusCode: span.status?.code,
						statusMessage: span.status?.message
					}
				});
			}
		}
		return traces;
	}
	parseDecodedAttributes(attributes) {
		if (!attributes) return {};
		const result = {};
		for (const attr of attributes) {
			const value = this.parseDecodedAttributeValue(attr.value);
			if (value !== void 0) result[attr.key] = value;
		}
		return result;
	}
	parseDecodedAttributeValue(value) {
		if (!value) return;
		if (value.stringValue !== void 0) return value.stringValue;
		if (value.intValue !== void 0) return typeof value.intValue === "number" ? value.intValue : Number(value.intValue);
		if (value.doubleValue !== void 0) return value.doubleValue;
		if (value.boolValue !== void 0) return value.boolValue;
		if (value.bytesValue !== void 0) return Buffer.from(value.bytesValue).toString("base64");
		if (value.arrayValue?.values) return value.arrayValue.values.map((v) => this.parseDecodedAttributeValue(v));
		if (value.kvlistValue?.values) {
			const kvMap = {};
			for (const kv of value.kvlistValue.values) kvMap[kv.key] = this.parseDecodedAttributeValue(kv.value);
			return kvMap;
		}
	}
	parseAttributes(attributes) {
		if (!attributes) return {};
		const result = {};
		for (const attr of attributes) {
			const value = this.parseAttributeValue(attr.value);
			if (value !== void 0) result[attr.key] = value;
		}
		return result;
	}
	parseAttributeValue(value) {
		if (value.stringValue !== void 0) return value.stringValue;
		if (value.intValue !== void 0) return Number(value.intValue);
		if (value.doubleValue !== void 0) return value.doubleValue;
		if (value.boolValue !== void 0) return value.boolValue;
		if (value.arrayValue?.values) return value.arrayValue.values.map((v) => this.parseAttributeValue(v));
		if (value.kvlistValue?.values) {
			const kvMap = {};
			for (const kv of value.kvlistValue.values) kvMap[kv.key] = this.parseAttributeValue(kv.value);
			return kvMap;
		}
	}
	convertId(id, expectedHexLength) {
		logger_default.debug(`[OtlpReceiver] Converting ID: ${id} (length: ${id.length}, expected hex length: ${expectedHexLength})`);
		if (id.length === expectedHexLength && /^[0-9a-f]+$/i.test(id)) {
			logger_default.debug(`[OtlpReceiver] ID is already hex format`);
			return id.toLowerCase();
		}
		try {
			const buffer = Buffer.from(id, "base64");
			const hex = buffer.toString("hex");
			logger_default.debug(`[OtlpReceiver] Base64 decoded: ${id} -> ${hex} (${buffer.length} bytes)`);
			const utf8String = buffer.toString("utf8");
			if (utf8String.length === expectedHexLength && /^[0-9a-f]+$/i.test(utf8String)) {
				logger_default.debug(`[OtlpReceiver] Detected hex string encoded as UTF-8: ${utf8String}`);
				return utf8String.toLowerCase();
			}
			if (hex.length === expectedHexLength) return hex;
			logger_default.warn(`[OtlpReceiver] Unexpected ID format: ${id} -> ${hex} (expected ${expectedHexLength} hex chars)`);
			return id.toLowerCase();
		} catch (error) {
			logger_default.error(`[OtlpReceiver] Failed to convert ID: ${error}`);
			return id.toLowerCase();
		}
	}
	listen(port = 4318, host = "127.0.0.1") {
		this.port = port;
		logger_default.debug(`[OtlpReceiver] Starting receiver on ${host}:${port}`);
		return new Promise((resolve, reject) => {
			this.server = this.app.listen(port, host, () => {
				logger_default.info(`[OtlpReceiver] Listening on http://${host}:${port}`);
				logger_default.debug("[OtlpReceiver] Receiver fully initialized and ready to accept traces");
				resolve();
			});
			this.server.on("error", (error) => {
				logger_default.error(`[OtlpReceiver] Failed to start: ${error}`);
				reject(error);
			});
		});
	}
	stop() {
		logger_default.debug("[OtlpReceiver] Stopping receiver");
		return new Promise((resolve) => {
			if (this.server) this.server.close(() => {
				logger_default.info("[OtlpReceiver] Server stopped");
				this.server = void 0;
				resolve();
			});
			else {
				logger_default.debug("[OtlpReceiver] No server to stop");
				resolve();
			}
		});
	}
	getApp() {
		return this.app;
	}
};
let otlpReceiver = null;
function getOTLPReceiver() {
	if (!otlpReceiver) otlpReceiver = new OTLPReceiver();
	return otlpReceiver;
}
async function startOTLPReceiver(port, host) {
	logger_default.debug("[OtlpReceiver] Starting receiver through startOTLPReceiver function");
	await getOTLPReceiver().listen(port, host);
}
async function stopOTLPReceiver() {
	logger_default.debug("[OtlpReceiver] Stopping receiver through stopOTLPReceiver function");
	if (otlpReceiver) {
		await otlpReceiver.stop();
		otlpReceiver = null;
	}
}

//#endregion
export { startOTLPReceiver, stopOTLPReceiver };
//# sourceMappingURL=otlpReceiver-JjWpoHkr.js.map