#!/usr/bin/env node
import { b as getEnvBool, o as logger_default } from "./logger-Bzi5o47S.js";
import { a as BLOB_MAX_SIZE, i as storeBlob, o as BLOB_MIN_SIZE, r as recordBlobReference } from "./blobs-eYQ1PJ-z.js";
import { createHash } from "crypto";

//#region src/util/createHash.ts
function sha256(str) {
	return createHash("sha256").update(str).digest("hex");
}
function randomSequence(length = 3) {
	const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
	let result = "";
	for (let i = 0; i < length; i++) result += characters.charAt(Math.floor(Math.random() * 62));
	return result;
}

//#endregion
//#region src/blobs/extractor.ts
const BLOB_URI_REGEX = /^promptfoo:\/\/blob\/([a-f0-9]{64})$/i;
const BLOB_HASH_REGEX = /^[a-f0-9]{64}$/i;
function isDataUrl(value) {
	return /^data:(audio|image)\/[^;]+;base64,/.test(value);
}
function extractBase64(value) {
	const match = value.match(/^data:([^;]+);base64,(.+)$/);
	if (!match) return null;
	const mimeType = match[1];
	try {
		return {
			buffer: Buffer.from(match[2], "base64"),
			mimeType
		};
	} catch (error) {
		logger_default.warn("[BlobExtractor] Failed to parse base64 data URL", { error });
		return null;
	}
}
function shouldExternalize(buffer) {
	const size = buffer.length;
	return size >= BLOB_MIN_SIZE && size <= BLOB_MAX_SIZE;
}
function getKindFromMimeType(mimeType) {
	return mimeType.startsWith("audio/") ? "audio" : "image";
}
/**
* Normalize audio format to proper MIME type.
* Some providers return just 'wav' instead of 'audio/wav'.
* @internal Exported for testing
*/
function normalizeAudioMimeType(format) {
	if (!format) return "audio/wav";
	const trimmedFormat = format.trim();
	if (/^audio\/[a-z0-9_+-]+$/i.test(trimmedFormat)) return trimmedFormat;
	const formatLower = trimmedFormat.toLowerCase();
	const mimeMap = {
		wav: "audio/wav",
		mp3: "audio/mpeg",
		ogg: "audio/ogg",
		flac: "audio/flac",
		aac: "audio/aac",
		m4a: "audio/mp4",
		webm: "audio/webm"
	};
	if (mimeMap[formatLower]) return mimeMap[formatLower];
	if (!/^[a-z0-9_-]+$/i.test(formatLower)) {
		logger_default.warn("[BlobExtractor] Invalid audio format, using default", { format });
		return "audio/wav";
	}
	return `audio/${formatLower}`;
}
function parseBinary(base64OrDataUrl, defaultMimeType) {
	if (isDataUrl(base64OrDataUrl)) {
		const parsed = extractBase64(base64OrDataUrl);
		if (!parsed) return null;
		return parsed;
	}
	try {
		return {
			buffer: Buffer.from(base64OrDataUrl, "base64"),
			mimeType: defaultMimeType
		};
	} catch (error) {
		logger_default.warn("[BlobExtractor] Failed to parse base64 data", { error });
		return null;
	}
}
async function maybeStore(base64OrDataUrl, defaultMimeType, context, location, kind) {
	const parsed = parseBinary(base64OrDataUrl, defaultMimeType);
	if (!parsed || !shouldExternalize(parsed.buffer)) return null;
	if (!isBlobStorageEnabled()) return null;
	const { ref } = await storeBlob(parsed.buffer, parsed.mimeType || "application/octet-stream", {
		...context,
		location,
		kind
	});
	return ref;
}
async function externalizeDataUrls(value, context, location) {
	if (typeof value === "string") {
		if (!isDataUrl(value)) return {
			value,
			mutated: false
		};
		const parsed = extractBase64(value);
		if (!parsed || !shouldExternalize(parsed.buffer)) return {
			value,
			mutated: false
		};
		const storedRef = await maybeStore(parsed.buffer.toString("base64"), parsed.mimeType, context, location, getKindFromMimeType(parsed.mimeType)) || null;
		if (!storedRef) return {
			value,
			mutated: false
		};
		return {
			value: storedRef.uri,
			mutated: true
		};
	}
	if (Array.isArray(value)) {
		let mutated = false;
		const nextValues = await Promise.all(value.map(async (item, idx) => {
			const { value: nextValue, mutated: childMutated } = await externalizeDataUrls(item, context, `${location}[${idx}]`);
			mutated ||= childMutated;
			return nextValue;
		}));
		return mutated ? {
			value: nextValues,
			mutated
		} : {
			value,
			mutated: false
		};
	}
	if (value && typeof value === "object") {
		let mutated = false;
		const nextObject = { ...value };
		for (const [key, child] of Object.entries(value)) {
			const { value: nextValue, mutated: childMutated } = await externalizeDataUrls(child, context, location ? `${location}.${key}` : key);
			if (childMutated) {
				nextObject[key] = nextValue;
				mutated = true;
			}
		}
		return mutated ? {
			value: nextObject,
			mutated: true
		} : {
			value,
			mutated: false
		};
	}
	return {
		value,
		mutated: false
	};
}
/**
* Best-effort extraction of binary data from provider responses.
* Currently focuses on audio.data fields and data URL outputs.
*/
async function extractAndStoreBinaryData(response, context) {
	if (!response) return response;
	let mutated = false;
	const next = { ...response };
	const blobContext = context || {};
	if (response.audio?.data && typeof response.audio.data === "string") {
		const stored = await maybeStore(response.audio.data, normalizeAudioMimeType(response.audio.format), blobContext, "response.audio.data", "audio");
		if (stored) {
			next.audio = {
				...response.audio,
				data: void 0,
				blobRef: stored
			};
			mutated = true;
			logger_default.debug("[BlobExtractor] Stored audio blob", {
				...context,
				hash: stored.hash
			});
		}
	}
	const turns = response.turns;
	if (Array.isArray(turns)) next.turns = await Promise.all(turns.map(async (turn, idx) => {
		if (turn?.audio?.data && typeof turn.audio.data === "string") {
			const stored = await maybeStore(turn.audio.data, normalizeAudioMimeType(turn.audio.format), blobContext, `response.turns[${idx}].audio.data`, "audio");
			if (stored) {
				mutated = true;
				return {
					...turn,
					audio: {
						...turn.audio,
						data: void 0,
						blobRef: stored
					}
				};
			}
		}
		return turn;
	}));
	if (typeof response.output === "string" && isDataUrl(response.output)) {
		const parsed = extractBase64(response.output);
		if (parsed && shouldExternalize(parsed.buffer)) {
			const stored = await maybeStore(parsed.buffer.toString("base64"), parsed.mimeType, blobContext, "response.output", getKindFromMimeType(parsed.mimeType));
			if (stored) {
				next.output = stored.uri;
				mutated = true;
				logger_default.debug("[BlobExtractor] Stored output blob", {
					...context,
					hash: stored.hash
				});
			}
		}
	}
	if (typeof response.output === "string" && response.output.trim().startsWith("{") && (response.isBase64 && response.format === "json" || response.output.includes("\"b64_json\"") || response.output.includes("b64_json"))) try {
		const parsed = JSON.parse(response.output);
		if (Array.isArray(parsed.data)) {
			let jsonMutated = false;
			const storedUris = [];
			for (const item of parsed.data) if (item?.b64_json && typeof item.b64_json === "string") {
				const stored = await maybeStore(item.b64_json, "image/png", blobContext, "response.output.data[].b64_json", "image");
				if (stored) {
					item.b64_json = stored.uri;
					storedUris.push(stored.uri);
					jsonMutated = true;
					mutated = true;
					logger_default.debug("[BlobExtractor] Stored image blob from b64_json", {
						...context,
						hash: stored.hash
					});
				}
			}
			if (jsonMutated) {
				if (storedUris.length === 1) next.output = storedUris[0];
				else if (storedUris.length > 1) next.output = JSON.stringify(storedUris);
				else next.output = JSON.stringify(parsed);
				next.metadata = {
					...response.metadata || {},
					blobUris: storedUris,
					originalFormat: response.format
				};
			}
		}
	} catch (err) {
		logger_default.debug("[BlobExtractor] Failed to parse base64 JSON output", {
			error: err instanceof Error ? err.message : String(err),
			location: "response.output"
		});
	}
	if (response.metadata) {
		const { value, mutated: metadataMutated } = await externalizeDataUrls(response.metadata, blobContext, "response.metadata");
		if (metadataMutated) {
			next.metadata = value;
			mutated = true;
		}
	}
	const finalResponse = mutated ? next : response;
	if (blobContext.evalId) await recordExistingBlobReferences(finalResponse, blobContext, "response");
	return finalResponse;
}
function isBlobStorageEnabled() {
	return !getEnvBool("PROMPTFOO_INLINE_MEDIA", false);
}
function parseBlobHashFromValue(value) {
	if (!value) return null;
	if (typeof value === "string") {
		const match = value.match(BLOB_URI_REGEX);
		return match ? match[1] : null;
	}
	if (typeof value === "object") {
		const candidate = value;
		if (candidate.hash && BLOB_HASH_REGEX.test(candidate.hash)) return candidate.hash;
		if (candidate.uri && typeof candidate.uri === "string") {
			const match = candidate.uri.match(BLOB_URI_REGEX);
			if (match) return match[1];
		}
	}
	return null;
}
async function recordExistingBlobReferences(value, context, location) {
	const hash = parseBlobHashFromValue(value);
	if (hash) {
		await recordBlobReference(hash, {
			...context,
			location
		});
		return;
	}
	if (Array.isArray(value)) {
		await Promise.all(value.map((child, idx) => recordExistingBlobReferences(child, context, `${location}[${idx}]`)));
		return;
	}
	if (value && typeof value === "object") for (const [key, child] of Object.entries(value)) await recordExistingBlobReferences(child, context, location ? `${location}.${key}` : key);
}

//#endregion
export { sha256 as i, isBlobStorageEnabled as n, randomSequence as r, extractAndStoreBinaryData as t };
//# sourceMappingURL=extractor-BEtCUN8X.js.map