import { a as normalizeLowercaseStringOrEmpty } from "./string-coerce-Bje8XVt9.js";
import { a as formatErrorMessage } from "./errors-D58q7G6U.js";
import { l as assertNoWindowsNetworkPath, p as safeFileURLToPath } from "./fs-safe-DiVKX-F2.js";
import { p as resolveUserPath } from "./utils-Ba75iQOz.js";
import { n as sanitizeImageBlocks } from "./tool-images-DTm5ukju.js";
import { t as log } from "./logger-mRy_ehd-.js";
import "./local-file-access-DTa1MOlf.js";
import { t as loadWebMedia } from "./web-media-CyjYvJrM.js";
import { l as resolveMediaBufferPath } from "./store-BMDm-hWh.js";
import { n as resolveSandboxedBridgeMediaPath, t as createSandboxBridgeReadFile } from "./sandbox-media-paths-Bdphxvj6.js";
import path from "node:path";
//#region src/agents/pi-embedded-runner/run/images.ts
/**
* Common image file extensions for detection.
*/
const IMAGE_EXTENSION_NAMES = [
	"png",
	"jpg",
	"jpeg",
	"gif",
	"webp",
	"bmp",
	"tiff",
	"tif",
	"heic",
	"heif"
];
const IMAGE_EXTENSIONS = new Set(IMAGE_EXTENSION_NAMES.map((ext) => `.${ext}`));
const IMAGE_EXTENSION_PATTERN = IMAGE_EXTENSION_NAMES.join("|");
const MEDIA_ATTACHED_PATH_REGEX_SOURCE = "^\\s*(.+?\\.(?:" + IMAGE_EXTENSION_PATTERN + "))\\s*(?:\\(|$|\\|)";
const MESSAGE_IMAGE_REGEX_SOURCE = "\\[Image:\\s*source:\\s*([^\\]]+\\.(?:" + IMAGE_EXTENSION_PATTERN + "))\\]";
const FILE_URL_REGEX_SOURCE = "file://[^\\s<>\"'`\\]]+\\.(?:" + IMAGE_EXTENSION_PATTERN + ")";
const PATH_REGEX_SOURCE = "(?:^|\\s|[\"'`(])((\\.\\.?/|[~/])[^\\s\"'`()\\[\\]]*\\.(?:" + IMAGE_EXTENSION_PATTERN + "))";
const MEDIA_ATTACHED_PATTERN = /\[media attached(?:\s+\d+\/\d+)?:\s*([^\]]+)\]/gi;
const MEDIA_ATTACHED_PATH_PATTERN = new RegExp(MEDIA_ATTACHED_PATH_REGEX_SOURCE, "i");
const MESSAGE_IMAGE_PATTERN = new RegExp(MESSAGE_IMAGE_REGEX_SOURCE, "gi");
const FILE_URL_PATTERN = new RegExp(FILE_URL_REGEX_SOURCE, "gi");
const PATH_PATTERN = new RegExp(PATH_REGEX_SOURCE, "gi");
/**
* Matches the opaque media URI written by the Gateway's claim-check offload:
*   media://inbound/<uuid-or-id>
*
* Uses an exclusion-based character class rather than a whitelist so that
* Unicode filenames (e.g. Chinese characters) preserved by sanitizeFilename
* in store.ts are matched correctly.
*
* Explicitly excluded from the ID segment:
*   ]      — closes the surrounding [media attached: ...] bracket
*   \s     — any whitespace (space, newline, tab) — terminates the token
*   /      — forward slash path separator (traversal prevention)
*   \      — back slash path separator (traversal prevention)
*   \x00   — null byte (path injection prevention)
*
* resolveMediaBufferPath applies its own guards against these characters, but
* excluding them here provides defence-in-depth at the parsing layer.
*
* Example valid IDs:
*   "1c77ce17-20b9-4546-be64-6e36a9adcb2c.png"
*   "photo---1c77ce17-20b9-4546-be64-6e36a9adcb2c.png"
*   "图片---1c77ce17-20b9-4546-be64-6e36a9adcb2c.png"
*/
const MEDIA_URI_REGEX = /\bmedia:\/\/inbound\/([^\]\s/\\\x00]+)/;
/**
* Checks if a file extension indicates an image file.
*/
function isImageExtension(filePath) {
	const ext = normalizeLowercaseStringOrEmpty(path.extname(filePath));
	return IMAGE_EXTENSIONS.has(ext);
}
function normalizeRefForDedupe(raw) {
	return process.platform === "win32" ? normalizeLowercaseStringOrEmpty(raw) : raw;
}
function mergePromptAttachmentImages(params) {
	const promptImages = [];
	const existingImages = params.existingImages ?? [];
	const offloadedImages = params.offloadedImages ?? [];
	if (params.imageOrder && params.imageOrder.length > 0) {
		let inlineIndex = 0;
		let offloadedIndex = 0;
		for (const entry of params.imageOrder) {
			if (entry === "inline") {
				const image = existingImages[inlineIndex++];
				if (image) promptImages.push(image);
				continue;
			}
			const image = offloadedImages[offloadedIndex++];
			if (image) promptImages.push(image);
		}
		while (inlineIndex < existingImages.length) promptImages.push(existingImages[inlineIndex++]);
		while (offloadedIndex < offloadedImages.length) {
			const image = offloadedImages[offloadedIndex++];
			if (image) promptImages.push(image);
		}
	} else {
		promptImages.push(...existingImages);
		for (const image of offloadedImages) if (image) promptImages.push(image);
	}
	promptImages.push(...params.promptRefImages ?? []);
	return promptImages;
}
function extractTrailingAttachmentMediaUris(prompt, count) {
	if (count <= 0) return [];
	const lines = prompt.split(/\r?\n/);
	const uris = [];
	for (let index = lines.length - 1; index >= 0 && uris.length < count; index--) {
		const line = lines[index]?.trim();
		if (!line || line.includes("\0")) break;
		const match = line.match(/^\[media attached:\s*(media:\/\/inbound\/[^\]\s/\\]+)\]$/);
		if (!match?.[1]) break;
		uris.unshift(match[1]);
	}
	return uris;
}
function splitPromptAndAttachmentRefs(params) {
	const offloadedCount = params.imageOrder?.filter((entry) => entry === "offloaded").length ?? 0;
	if (offloadedCount === 0) return {
		promptRefs: params.refs,
		attachmentRefs: []
	};
	const attachmentUris = new Set(extractTrailingAttachmentMediaUris(params.prompt, offloadedCount));
	if (attachmentUris.size === 0) return {
		promptRefs: params.refs,
		attachmentRefs: []
	};
	const promptRefs = [];
	const attachmentRefs = [];
	for (const ref of params.refs) {
		if (ref.type === "media-uri" && attachmentUris.has(ref.resolved)) {
			attachmentRefs.push(ref);
			continue;
		}
		promptRefs.push(ref);
	}
	return {
		promptRefs,
		attachmentRefs
	};
}
async function sanitizeImagesWithLog(images, label, imageSanitization) {
	const { images: sanitized, dropped } = await sanitizeImageBlocks(images, label, imageSanitization);
	if (dropped > 0) log.warn(`Native image: dropped ${dropped} image(s) after sanitization (${label}).`);
	return sanitized;
}
/**
* Detects image references in a user prompt.
*
* Patterns detected:
* - Absolute paths: /path/to/image.png
* - Relative paths: ./image.png, ../images/photo.jpg
* - Home paths: ~/Pictures/screenshot.png
* - file:// URLs: file:///path/to/image.png
* - Message attachments: [Image: source: /path/to/image.jpg]
* - Gateway claim-check URIs: [media attached: media://inbound/<id>]
*
* @param prompt The user prompt text to scan
* @returns Array of detected image references
*/
function detectImageReferences(prompt) {
	const refs = [];
	const seen = /* @__PURE__ */ new Set();
	const addPathRef = (raw) => {
		const trimmed = raw.trim();
		const dedupeKey = normalizeRefForDedupe(trimmed);
		if (!trimmed || seen.has(dedupeKey)) return;
		if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return;
		if (!isImageExtension(trimmed)) return;
		try {
			assertNoWindowsNetworkPath(trimmed, "Image path");
		} catch {
			return;
		}
		seen.add(dedupeKey);
		const resolved = trimmed.startsWith("~") ? resolveUserPath(trimmed) : trimmed;
		refs.push({
			raw: trimmed,
			type: "path",
			resolved
		});
	};
	MEDIA_ATTACHED_PATTERN.lastIndex = 0;
	MESSAGE_IMAGE_PATTERN.lastIndex = 0;
	FILE_URL_PATTERN.lastIndex = 0;
	PATH_PATTERN.lastIndex = 0;
	let match;
	while ((match = MEDIA_ATTACHED_PATTERN.exec(prompt)) !== null) {
		const content = match[1];
		if (/^\d+\s+files?$/i.test(content.trim())) continue;
		const mediaUriMatch = content.match(MEDIA_URI_REGEX);
		if (mediaUriMatch) {
			const uri = `media://inbound/${mediaUriMatch[1]}`;
			const dedupeKey = normalizeRefForDedupe(uri);
			if (!seen.has(dedupeKey)) {
				seen.add(dedupeKey);
				refs.push({
					raw: uri,
					type: "media-uri",
					resolved: uri
				});
			}
			continue;
		}
		const pathMatch = content.match(MEDIA_ATTACHED_PATH_PATTERN);
		if (pathMatch?.[1]) addPathRef(pathMatch[1].trim());
	}
	while ((match = MESSAGE_IMAGE_PATTERN.exec(prompt)) !== null) {
		const raw = match[1]?.trim();
		if (raw) addPathRef(raw);
	}
	while ((match = FILE_URL_PATTERN.exec(prompt)) !== null) {
		const raw = match[0];
		const dedupeKey = normalizeRefForDedupe(raw);
		if (seen.has(dedupeKey)) continue;
		seen.add(dedupeKey);
		try {
			const resolved = safeFileURLToPath(raw);
			refs.push({
				raw,
				type: "path",
				resolved
			});
		} catch {}
	}
	while ((match = PATH_PATTERN.exec(prompt)) !== null) if (match[1]) addPathRef(match[1]);
	return refs;
}
/**
* Loads an image from a file path and returns it as ImageContent.
*
* @param ref The detected image reference
* @param workspaceDir The current workspace directory for resolving relative paths
* @param options Optional settings for sandbox and size limits
* @returns The loaded image content, or null if loading failed
*/
async function loadImageFromRef(ref, workspaceDir, options) {
	try {
		let targetPath = ref.resolved;
		if (ref.type === "media-uri") {
			const idMatch = MEDIA_URI_REGEX.exec(ref.resolved);
			if (!idMatch?.[1]) {
				log.debug(`Native image: media-uri ref has no id: ${ref.resolved}`);
				return null;
			}
			try {
				targetPath = await resolveMediaBufferPath(idMatch[1], "inbound");
			} catch (err) {
				log.debug(`Native image: failed to resolve ${ref.resolved}: ${formatErrorMessage(err)}`);
				return null;
			}
		}
		if (options?.sandbox) try {
			targetPath = (await resolveSandboxedBridgeMediaPath({
				sandbox: {
					root: options.sandbox.root,
					bridge: options.sandbox.bridge,
					workspaceOnly: options.workspaceOnly
				},
				mediaPath: targetPath
			})).resolved;
		} catch (err) {
			log.debug(`Native image: sandbox validation failed for ${ref.resolved}: ${formatErrorMessage(err)}`);
			return null;
		}
		else if (!path.isAbsolute(targetPath)) targetPath = path.resolve(workspaceDir, targetPath);
		const media = options?.sandbox ? await loadWebMedia(targetPath, {
			maxBytes: options.maxBytes,
			sandboxValidated: true,
			readFile: createSandboxBridgeReadFile({ sandbox: options.sandbox })
		}) : await loadWebMedia(targetPath, options?.workspaceOnly ? {
			maxBytes: options.maxBytes,
			localRoots: [workspaceDir]
		} : options?.maxBytes);
		if (media.kind !== "image") {
			log.debug(`Native image: not an image file: ${targetPath} (got ${media.kind})`);
			return null;
		}
		const mimeType = media.contentType ?? "image/jpeg";
		return {
			type: "image",
			data: media.buffer.toString("base64"),
			mimeType
		};
	} catch (err) {
		log.debug(`Native image: failed to load ${ref.resolved}: ${formatErrorMessage(err)}`);
		return null;
	}
}
/**
* Checks if a model supports image input based on its input capabilities.
*
* @param model The model object with input capability array
* @returns True if the model supports image input
*/
function modelSupportsImages(model) {
	return model.input?.includes("image") ?? false;
}
/**
* Detects and loads images referenced in a prompt for models with vision capability.
*
* This function scans the prompt for image references (file paths and URLs),
* loads them, and returns them as ImageContent array ready to be passed to
* the model's prompt method.
*
* @param params Configuration for image detection and loading
* @returns Object with loaded images for current prompt only
*/
async function detectAndLoadPromptImages(params) {
	if (!modelSupportsImages(params.model)) return {
		images: [],
		detectedRefs: [],
		loadedCount: 0,
		skippedCount: 0
	};
	const allRefs = detectImageReferences(params.prompt);
	if (allRefs.length === 0) return {
		images: params.existingImages ?? [],
		detectedRefs: [],
		loadedCount: 0,
		skippedCount: 0
	};
	log.debug(`Native image: detected ${allRefs.length} image refs in prompt`);
	const { promptRefs, attachmentRefs } = splitPromptAndAttachmentRefs({
		prompt: params.prompt,
		refs: allRefs,
		imageOrder: params.imageOrder
	});
	const promptRefImages = [];
	const offloadedImages = [];
	let loadedCount = 0;
	let skippedCount = 0;
	for (const ref of promptRefs) {
		const image = await loadImageFromRef(ref, params.workspaceDir, {
			maxBytes: params.maxBytes,
			workspaceOnly: params.workspaceOnly,
			sandbox: params.sandbox
		});
		if (image) {
			promptRefImages.push(image);
			loadedCount++;
			log.debug(`Native image: loaded ${ref.type} ${ref.resolved}`);
		} else skippedCount++;
	}
	for (const ref of attachmentRefs) {
		const image = await loadImageFromRef(ref, params.workspaceDir, {
			maxBytes: params.maxBytes,
			workspaceOnly: params.workspaceOnly,
			sandbox: params.sandbox
		});
		offloadedImages.push(image);
		if (image) {
			loadedCount++;
			log.debug(`Native image: loaded ${ref.type} ${ref.resolved}`);
		} else skippedCount++;
	}
	return {
		images: await sanitizeImagesWithLog(mergePromptAttachmentImages({
			imageOrder: params.imageOrder,
			existingImages: params.existingImages,
			offloadedImages,
			promptRefImages
		}), "prompt:images", { maxDimensionPx: params.maxDimensionPx }),
		detectedRefs: allRefs,
		loadedCount,
		skippedCount
	};
}
//#endregion
export { detectImageReferences as n, loadImageFromRef as r, detectAndLoadPromptImages as t };
