import { ROOT_CONTEXT, SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";

//#region src/tracing/genaiTracer.ts
const TRACER_NAME = "promptfoo.providers";
const TRACER_VERSION = "1.0.0";
const GenAIAttributes = {
	SYSTEM: "gen_ai.system",
	OPERATION_NAME: "gen_ai.operation.name",
	REQUEST_MODEL: "gen_ai.request.model",
	REQUEST_MAX_TOKENS: "gen_ai.request.max_tokens",
	REQUEST_TEMPERATURE: "gen_ai.request.temperature",
	REQUEST_TOP_P: "gen_ai.request.top_p",
	REQUEST_TOP_K: "gen_ai.request.top_k",
	REQUEST_STOP_SEQUENCES: "gen_ai.request.stop_sequences",
	REQUEST_FREQUENCY_PENALTY: "gen_ai.request.frequency_penalty",
	REQUEST_PRESENCE_PENALTY: "gen_ai.request.presence_penalty",
	RESPONSE_MODEL: "gen_ai.response.model",
	RESPONSE_ID: "gen_ai.response.id",
	RESPONSE_FINISH_REASONS: "gen_ai.response.finish_reasons",
	USAGE_INPUT_TOKENS: "gen_ai.usage.input_tokens",
	USAGE_OUTPUT_TOKENS: "gen_ai.usage.output_tokens",
	USAGE_TOTAL_TOKENS: "gen_ai.usage.total_tokens",
	USAGE_CACHED_TOKENS: "gen_ai.usage.cached_tokens",
	USAGE_REASONING_TOKENS: "gen_ai.usage.reasoning_tokens",
	USAGE_ACCEPTED_PREDICTION_TOKENS: "gen_ai.usage.accepted_prediction_tokens",
	USAGE_REJECTED_PREDICTION_TOKENS: "gen_ai.usage.rejected_prediction_tokens"
};
const PromptfooAttributes = {
	PROVIDER_ID: "promptfoo.provider.id",
	EVAL_ID: "promptfoo.eval.id",
	TEST_INDEX: "promptfoo.test.index",
	PROMPT_LABEL: "promptfoo.prompt.label",
	CACHE_HIT: "promptfoo.cache_hit",
	REQUEST_BODY: "promptfoo.request.body",
	RESPONSE_BODY: "promptfoo.response.body"
};
/** Maximum length for request/response body attributes (characters) */
const MAX_BODY_LENGTH = 4096;
/**
* Patterns to redact from request/response bodies for security.
* These patterns match common API key and secret formats.
*/
const SENSITIVE_PATTERNS = [
	{
		pattern: /\b(sk-[a-zA-Z0-9_-]{20,})/g,
		replacement: "<REDACTED_API_KEY>"
	},
	{
		pattern: /\b(pk-[a-zA-Z0-9_-]{20,})/g,
		replacement: "<REDACTED_API_KEY>"
	},
	{
		pattern: /\b(api[_-]?key["']?\s*[:=]\s*["']?)([a-zA-Z0-9_-]{16,})/gi,
		replacement: "$1<REDACTED>"
	},
	{
		pattern: /\b(secret["']?\s*[:=]\s*["']?)([a-zA-Z0-9_-]{16,})/gi,
		replacement: "$1<REDACTED>"
	},
	{
		pattern: /\b(token["']?\s*[:=]\s*["']?)([a-zA-Z0-9_-]{16,})/gi,
		replacement: "$1<REDACTED>"
	},
	{
		pattern: /\b(password["']?\s*[:=]\s*["']?)([^\s"',}{]+)/gi,
		replacement: "$1<REDACTED>"
	},
	{
		pattern: /(Authorization["']?\s*[:=]\s*["']?)(Bearer\s+)?([a-zA-Z0-9_.-]{16,})/gi,
		replacement: "$1$2<REDACTED>"
	},
	{
		pattern: /\b(AKIA[A-Z0-9]{16})/g,
		replacement: "<REDACTED_AWS_KEY>"
	},
	{
		pattern: /\b([a-zA-Z0-9/+=]{40})/g,
		replacement: (match) => {
			if (/^[A-Za-z0-9+/=]{40}$/.test(match) && match.includes("/")) return "<REDACTED_SECRET>";
			return match;
		}
	},
	{
		pattern: /\b[a-f0-9]{64,}\b/gi,
		replacement: "<REDACTED_HASH>"
	}
];
/**
* Get the tracer instance for GenAI operations.
*/
function getGenAITracer() {
	return trace.getTracer(TRACER_NAME, TRACER_VERSION);
}
/**
* Execute a function within a GenAI span.
*
* This wrapper:
* 1. Creates a span with GenAI semantic conventions
* 2. Sets request attributes before execution
* 3. Executes the provided function
* 4. Sets response attributes (including token usage) after execution
* 5. Handles errors and sets appropriate span status
*
* @param ctx - GenAI span context with request information
* @param fn - The async function to execute (typically the API call)
* @param resultExtractor - Optional function to extract result data from the return value
* @returns The return value from fn
*
* @example
* ```typescript
* const response = await withGenAISpan(
*   {
*     system: 'openai',
*     operationName: 'chat',
*     model: 'gpt-4',
*     providerId: 'openai:gpt-4',
*   },
*   async (span) => {
*     return await openai.chat.completions.create({...});
*   },
*   (response) => ({
*     tokenUsage: {
*       prompt: response.usage?.prompt_tokens,
*       completion: response.usage?.completion_tokens,
*     },
*     responseId: response.id,
*   })
* );
* ```
*/
async function withGenAISpan(ctx, fn, resultExtractor) {
	const tracer = getGenAITracer();
	const spanName = `${ctx.operationName} ${ctx.model}`;
	let parentContext = context.active();
	if (ctx.traceparent) {
		const carrier = { traceparent: ctx.traceparent };
		parentContext = propagation.extract(ROOT_CONTEXT, carrier);
	}
	const spanCallback = async (span) => {
		try {
			const value = await fn(span);
			if (resultExtractor) setGenAIResponseAttributes(span, resultExtractor(value), ctx.sanitizeBodies);
			const valueAsRecord = value;
			if (valueAsRecord && typeof valueAsRecord.error === "string" && valueAsRecord.error) span.setStatus({
				code: SpanStatusCode.ERROR,
				message: valueAsRecord.error
			});
			else span.setStatus({ code: SpanStatusCode.OK });
			return value;
		} catch (error) {
			span.setStatus({
				code: SpanStatusCode.ERROR,
				message: error instanceof Error ? error.message : String(error)
			});
			if (error instanceof Error) span.recordException(error);
			throw error;
		} finally {
			span.end();
		}
	};
	return tracer.startActiveSpan(spanName, {
		kind: SpanKind.CLIENT,
		attributes: buildRequestAttributes(ctx)
	}, parentContext, spanCallback);
}
/**
* Build request attributes for a GenAI span.
*/
function buildRequestAttributes(ctx) {
	const attrs = {
		[GenAIAttributes.SYSTEM]: ctx.system,
		[GenAIAttributes.OPERATION_NAME]: ctx.operationName,
		[GenAIAttributes.REQUEST_MODEL]: ctx.model,
		[PromptfooAttributes.PROVIDER_ID]: ctx.providerId
	};
	if (ctx.maxTokens !== void 0) attrs[GenAIAttributes.REQUEST_MAX_TOKENS] = ctx.maxTokens;
	if (ctx.temperature !== void 0) attrs[GenAIAttributes.REQUEST_TEMPERATURE] = ctx.temperature;
	if (ctx.topP !== void 0) attrs[GenAIAttributes.REQUEST_TOP_P] = ctx.topP;
	if (ctx.topK !== void 0) attrs[GenAIAttributes.REQUEST_TOP_K] = ctx.topK;
	if (ctx.stopSequences && ctx.stopSequences.length > 0) attrs[GenAIAttributes.REQUEST_STOP_SEQUENCES] = ctx.stopSequences;
	if (ctx.frequencyPenalty !== void 0) attrs[GenAIAttributes.REQUEST_FREQUENCY_PENALTY] = ctx.frequencyPenalty;
	if (ctx.presencePenalty !== void 0) attrs[GenAIAttributes.REQUEST_PRESENCE_PENALTY] = ctx.presencePenalty;
	if (ctx.evalId) attrs[PromptfooAttributes.EVAL_ID] = ctx.evalId;
	if (ctx.testIndex !== void 0) attrs[PromptfooAttributes.TEST_INDEX] = ctx.testIndex;
	if (ctx.promptLabel) attrs[PromptfooAttributes.PROMPT_LABEL] = ctx.promptLabel;
	if (ctx.requestBody) attrs[PromptfooAttributes.REQUEST_BODY] = truncateBody(ctx.requestBody, ctx.sanitizeBodies);
	return attrs;
}
/**
* Sanitize sensitive data from a body string.
* Redacts API keys, secrets, tokens, and other sensitive patterns.
*/
function sanitizeBody(body) {
	let sanitized = body;
	for (const { pattern, replacement } of SENSITIVE_PATTERNS) if (typeof replacement === "function") sanitized = sanitized.replace(pattern, replacement);
	else sanitized = sanitized.replace(pattern, replacement);
	return sanitized;
}
/**
* Truncate a body string to MAX_BODY_LENGTH.
* Optionally sanitizes sensitive data first if sanitize=true.
*
* @param body - The body string to process
* @param sanitize - Whether to sanitize sensitive data (defaults to true)
*/
function truncateBody(body, sanitize = true) {
	const processed = sanitize ? sanitizeBody(body) : body;
	if (processed.length <= MAX_BODY_LENGTH) return processed;
	return processed.slice(0, MAX_BODY_LENGTH - 15) + "... [truncated]";
}
/**
* Set response attributes on a span after the API call completes.
*
* @param span - The span to update
* @param result - The result data containing token usage and response metadata
* @param sanitize - Whether to sanitize sensitive data from response body (defaults to true)
*/
function setGenAIResponseAttributes(span, result, sanitize = true) {
	if (result.tokenUsage) {
		const usage = result.tokenUsage;
		if (usage.prompt !== void 0) span.setAttribute(GenAIAttributes.USAGE_INPUT_TOKENS, usage.prompt);
		if (usage.completion !== void 0) span.setAttribute(GenAIAttributes.USAGE_OUTPUT_TOKENS, usage.completion);
		if (usage.total !== void 0) span.setAttribute(GenAIAttributes.USAGE_TOTAL_TOKENS, usage.total);
		if (usage.cached !== void 0) span.setAttribute(GenAIAttributes.USAGE_CACHED_TOKENS, usage.cached);
		if (usage.completionDetails) {
			if (usage.completionDetails.reasoning !== void 0) span.setAttribute(GenAIAttributes.USAGE_REASONING_TOKENS, usage.completionDetails.reasoning);
			if (usage.completionDetails.acceptedPrediction !== void 0) span.setAttribute(GenAIAttributes.USAGE_ACCEPTED_PREDICTION_TOKENS, usage.completionDetails.acceptedPrediction);
			if (usage.completionDetails.rejectedPrediction !== void 0) span.setAttribute(GenAIAttributes.USAGE_REJECTED_PREDICTION_TOKENS, usage.completionDetails.rejectedPrediction);
		}
	}
	if (result.responseModel) span.setAttribute(GenAIAttributes.RESPONSE_MODEL, result.responseModel);
	if (result.responseId) span.setAttribute(GenAIAttributes.RESPONSE_ID, result.responseId);
	if (result.finishReasons && result.finishReasons.length > 0) span.setAttribute(GenAIAttributes.RESPONSE_FINISH_REASONS, result.finishReasons);
	if (result.cacheHit !== void 0) span.setAttribute(PromptfooAttributes.CACHE_HIT, result.cacheHit);
	if (result.responseBody) span.setAttribute(PromptfooAttributes.RESPONSE_BODY, truncateBody(result.responseBody, sanitize));
	if (result.additionalAttributes) {
		for (const [key, value] of Object.entries(result.additionalAttributes)) if (value !== void 0 && value !== null) if (typeof value === "string") span.setAttribute(key, truncateBody(value, sanitize));
		else span.setAttribute(key, value);
	}
}
/**
* Get the W3C traceparent header value from the current active span.
* Returns undefined if there is no active span.
*
* This can be used to propagate trace context to downstream services.
*/
function getTraceparent() {
	const activeSpan = trace.getActiveSpan();
	if (!activeSpan) return;
	const ctx = activeSpan.spanContext();
	const traceFlags = ctx.traceFlags.toString(16).padStart(2, "0");
	return `00-${ctx.traceId}-${ctx.spanId}-${traceFlags}`;
}

//#endregion
export { withGenAISpan as n, getTraceparent as t };
//# sourceMappingURL=genaiTracer-C6LxD3Ij.js.map