UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,078 lines 50.3 kB
import { I as resolveTimestampMsToIsoString, l as asNonNegativeFiniteNumber, o as asDateTimestampMs } from "./number-coercion-CLj0HTDM.js";
import "./src-vebZIeLe.js";
import { t as expectDefined } from "./expect-CyE8FADM.js";
import { a as asOptionalRecord } from "./record-coerce-DItp3I4t.js";
import { n as getRuntimeConfig } from "./io.runtime-B9iJRs3w.js";
import { L as sanitizeUntrustedFileName, h as readLocalFileSafely, u as openLocalFileSafely } from "./fs-safe-B6pvPGnf.js";
import { n as normalizeAgentId } from "./agent-id-CeT3w4ap.js";
import { w as resolveStateDir } from "./paths-D2sRr1a_.js";
import { O as parseAgentSessionKey } from "./session-key-BnWWjqNc.js";
import "./config-Cs0XXL3x.js";
import { t as safeEqualSecret } from "./secret-equal-DRsL8lKD.js";
import { n as authorizeOperatorScopesForMethod } from "./method-scopes-K6J_UQGL.js";
import { a as maxBytesForKind, o as mediaKindFromMime } from "./constants-DUxuqQz8.js";
import { d as normalizeMimeType, u as mimeTypeFromFilePath } from "./mime-CVpcq9ju.js";
import { f as saveMediaBuffer, p as saveMediaSource, s as getMediaDir, t as MEDIA_MAX_BYTES } from "./store-Ph57PZ9P.js";
import { c as getImageMetadata, s as createImageProcessor } from "./image-ops-DU1SIRgh.js";
import { Yt as loadExactSessionEntryReadOnlyResult } from "./session-accessor-YsytfDtG.js";
import { _ as resolveSessionEntry } from "./session-accessor.sqlite-entry-CWk3jL7s.js";
import { n as probePlaybackMediaFileDescriptor } from "./media-probe-Dbs998-L.js";
import "./media-services-By-i0Drw.js";
import { r as readAssistantDisplayContent } from "./assistant-display-content-BZljc_tT.js";
import { a as resolveLocalMediaPath } from "./local-roots-BphuGw-a.js";
import { o as resolveLocalMediaRoots, r as assertLocalMediaAllowed } from "./local-media-access-DXGJo-Ud.js";
import { l as readSessionMessagesWithSourceAsync, s as readSessionMessagesMatchingIdAsync } from "./session-transcript-readers-CYDRQsH5.js";
import { i as tryResolveSessionCompatibilityOwnerAgentId } from "./session-request-agent-CCRSEGCB.js";
import { i as loadGatewaySessionEntryReadOnly } from "./session-utils-store-CInT2loy.js";
import "./session-utils-Cai0_C6U.js";
import { m as loadPendingSessionDeliveries } from "./session-delivery-queue-storage-ClcsFC9e.js";
import { a as authorizeGatewayHttpRequestOrReply, g as resolveOpenAiCompatibleHttpSenderIsOwner, h as resolveOpenAiCompatibleHttpOperatorScopes } from "./http-auth-utils-C3lb4QXY.js";
import { c as sendMethodNotAllowed, l as sendMissingScopeForbidden, s as sendJson } from "./http-common-BaZaosnr.js";
import "./http-utils-BHgXp7Zb.js";
import { t as resolveExistingAgentSessionStoreTargetsReadOnlyResult } from "./targets-read-availability-lKmQE2tg.js";
import { a as buildAssistantMediaContentDisposition, c as resolvePlaybackTranscode, i as writeByteHeaders, n as createImmutableFileValidators, o as replacePlaybackFileExtension, r as resolveByteResponse, s as resolvePlaybackModeForSource, t as createGatewayByteStream } from "./http-byte-range-Clvh5mwC.js";
import { a as insertManagedImageRecord, i as deleteClaimedManagedImageRecord, n as attachManagedImageRecordToMessage, o as listManagedImageRecordEntries, r as claimManagedImageRecordCleanupIfCurrent, t as MANAGED_OUTGOING_ORIGINALS_SUBDIR, u as readManagedImageRecord } from "./managed-image-record-store-DnURBN77.js";
import path from "node:path";
import fs from "node:fs/promises";
import { createHmac, randomBytes, randomUUID } from "node:crypto";
import pLimit from "p-limit";
//#region src/gateway/managed-image-attachments.ts
const OUTGOING_IMAGE_ROUTE_PREFIX = "/api/chat/media/outgoing";
const DEFAULT_TRANSIENT_OUTGOING_IMAGE_TTL_MS = 9e5;
const MANAGED_OUTGOING_IMAGE_TICKET_SCOPE = "managed-outgoing-image";
const MANAGED_OUTGOING_IMAGE_TICKET_TTL_MS = 3e5;
const MANAGED_OUTGOING_IMAGE_ARTIFACT_ID_PREFIX = "artifact_managed_image_";
const MANAGED_OUTGOING_MEDIA_ARTIFACT_ID_PREFIX = "artifact_managed_media_";
const MANAGED_IMAGE_THUMBNAIL_MAX_SIDE = 300;
const MANAGED_IMAGE_THUMBNAIL_CACHE_MAX_ENTRIES = 128;
const MANAGED_IMAGE_THUMBNAIL_CACHE_MAX_BYTES = 16777216;
const MANAGED_IMAGE_THUMBNAIL_MAX_PENDING = 128;
const MANAGED_OUTGOING_ATTACHMENT_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
const managedOutgoingImageTicketSecret = randomBytes(32);
const managedImageThumbnailCache = /* @__PURE__ */ new Map();
const managedImageThumbnailJobs = /* @__PURE__ */ new Map();
const limitManagedImageThumbnails = pLimit(4);
let managedImageThumbnailCacheBytes = 0;
const DEFAULT_MANAGED_IMAGE_ATTACHMENT_LIMITS = {
	maxBytes: 12582912,
	maxWidth: 4096,
	maxHeight: 4096,
	maxPixels: 2e7
};
const MANAGED_DOCUMENT_MIME_TYPES = /* @__PURE__ */ new Set([
	"application/json",
	"application/msword",
	"application/pdf",
	"application/vnd.ms-excel",
	"application/vnd.ms-powerpoint",
	"application/vnd.openxmlformats-officedocument.presentationml.presentation",
	"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
	"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
	"application/x-cfb",
	"application/yaml",
	"application/zip",
	"text/csv",
	"text/html",
	"text/markdown",
	"text/plain"
]);
function resolveManagedMediaKind(contentType) {
	const normalized = normalizeMimeType(contentType);
	if (normalized === "image/svg+xml") return null;
	const kind = mediaKindFromMime(normalized);
	if (kind === "image" || kind === "audio" || kind === "video") return kind;
	return normalized && MANAGED_DOCUMENT_MIME_TYPES.has(normalized) ? "document" : null;
}
function readManagedImageThumbnail(cacheKey) {
	const thumbnail = managedImageThumbnailCache.get(cacheKey);
	if (!thumbnail) return;
	managedImageThumbnailCache.delete(cacheKey);
	managedImageThumbnailCache.set(cacheKey, thumbnail);
	return thumbnail;
}
function cacheManagedImageThumbnail(cacheKey, thumbnail) {
	const previous = managedImageThumbnailCache.get(cacheKey);
	if (previous) {
		managedImageThumbnailCache.delete(cacheKey);
		managedImageThumbnailCacheBytes -= previous.byteLength;
	}
	managedImageThumbnailCache.set(cacheKey, thumbnail);
	managedImageThumbnailCacheBytes += thumbnail.byteLength;
	while (managedImageThumbnailCache.size > MANAGED_IMAGE_THUMBNAIL_CACHE_MAX_ENTRIES || managedImageThumbnailCacheBytes > MANAGED_IMAGE_THUMBNAIL_CACHE_MAX_BYTES) {
		const oldest = managedImageThumbnailCache.entries().next().value;
		if (!oldest) break;
		managedImageThumbnailCache.delete(oldest[0]);
		managedImageThumbnailCacheBytes -= oldest[1].byteLength;
	}
}
async function resolveManagedImageThumbnail(cacheKey, create) {
	const cached = readManagedImageThumbnail(cacheKey);
	if (cached) return cached;
	const active = managedImageThumbnailJobs.get(cacheKey);
	if (active) return await active;
	if (limitManagedImageThumbnails.pendingCount >= MANAGED_IMAGE_THUMBNAIL_MAX_PENDING) throw new Error("managed image thumbnail queue is full");
	const pending = limitManagedImageThumbnails(create).then((thumbnail) => {
		cacheManagedImageThumbnail(cacheKey, thumbnail);
		return thumbnail;
	}).finally(() => {
		managedImageThumbnailJobs.delete(cacheKey);
	});
	managedImageThumbnailJobs.set(cacheKey, pending);
	return await pending;
}
function buildSessionManagedOutgoingAttachmentIndexCacheKey(sessionKey, agentId) {
	return sessionKey === "global" && agentId ? `agent:${agentId}:global` : sessionKey;
}
function resolveManagedImageAttachmentLimits(config) {
	return {
		maxBytes: config?.maxBytes ?? DEFAULT_MANAGED_IMAGE_ATTACHMENT_LIMITS.maxBytes,
		maxWidth: config?.maxWidth ?? DEFAULT_MANAGED_IMAGE_ATTACHMENT_LIMITS.maxWidth,
		maxHeight: config?.maxHeight ?? DEFAULT_MANAGED_IMAGE_ATTACHMENT_LIMITS.maxHeight,
		maxPixels: config?.maxPixels ?? DEFAULT_MANAGED_IMAGE_ATTACHMENT_LIMITS.maxPixels
	};
}
function formatLimitMiB(bytes) {
	if (bytes < 1048576) return `${bytes} bytes`;
	return Number.isInteger(bytes / 1048576) ? `${bytes / 1048576} MiB` : `${(bytes / 1048576).toFixed(1)} MiB`;
}
function createManagedImageAttachmentError(message) {
	const error = new Error(message);
	error.name = "ManagedImageAttachmentError";
	return error;
}
function isManagedImageAttachmentSafeError(error) {
	if (!(error instanceof Error)) return false;
	if (error.name === "ManagedImageAttachmentError") return true;
	return error.message.startsWith("Managed image attachment ") || error.message.startsWith("Invalid image data URL");
}
function getSanitizedManagedImageAttachmentError(error, label, kind) {
	if (isManagedImageAttachmentSafeError(error)) return error;
	return createManagedImageAttachmentError(`Managed ${kind} attachment ${JSON.stringify(label)} could not be prepared`);
}
function buildManagedMediaFailureBlock(params) {
	return {
		type: "attachment_error",
		attachment: {
			code: params.code,
			kind: params.kind === "media" ? "document" : params.kind,
			label: params.label,
			...params.mimeType ? { mimeType: params.mimeType } : {}
		}
	};
}
function validateManagedImageBuffer(buffer, alt, limits) {
	if (buffer.byteLength > limits.maxBytes) throw createManagedImageAttachmentError(`Managed image attachment ${JSON.stringify(alt)} exceeds the ${formatLimitMiB(limits.maxBytes)} byte limit`);
}
function maxBytesForManagedMediaKind(kind, imageLimits) {
	return kind === "image" ? imageLimits.maxBytes : maxBytesForKind(kind);
}
function createManagedMediaByteLimitError(params) {
	return createManagedImageAttachmentError(`Managed ${params.kind} attachment ${JSON.stringify(params.label)} exceeds the ${formatLimitMiB(params.maxBytes)} byte limit`);
}
function estimateBase64DecodedByteLength(base64) {
	const normalized = base64.replace(/\s+/g, "");
	const paddingMatch = /=+$/u.exec(normalized);
	const padding = Math.min(paddingMatch?.[0].length ?? 0, 2);
	return Math.floor(normalized.length * 3 / 4) - padding;
}
function getManagedImageMetadataLimitError(metadata, alt, limits) {
	if (!metadata) return `Managed image attachment ${JSON.stringify(alt)} is missing readable dimensions`;
	if (metadata.width > limits.maxWidth) return `Managed image attachment ${JSON.stringify(alt)} exceeds the ${limits.maxWidth}px width limit`;
	if (metadata.height > limits.maxHeight) return `Managed image attachment ${JSON.stringify(alt)} exceeds the ${limits.maxHeight}px height limit`;
	if (metadata.width * metadata.height > limits.maxPixels) return `Managed image attachment ${JSON.stringify(alt)} exceeds the ${limits.maxPixels.toLocaleString("en-US")} pixel limit`;
	return null;
}
async function resizeManagedImageBufferToLimits(params) {
	const resized = await createImageProcessor().encode(params.buffer, {
		format: "auto",
		limits: {
			maxWidth: params.limits.maxWidth,
			maxHeight: params.limits.maxHeight,
			maxPixels: params.limits.maxPixels
		},
		opaque: {
			format: "jpeg",
			quality: 92
		},
		transparent: {
			format: "png",
			compressionLevel: 9
		},
		transparency: "auto"
	});
	return {
		buffer: resized.data,
		contentType: resized.mimeType,
		width: resized.width,
		height: resized.height
	};
}
function resolveManagedImageOriginalPath(record) {
	if (!path.isAbsolute(record.original.mediaRoot) || record.original.mediaSubdir !== "outgoing/originals" || !record.original.mediaId || record.original.mediaId.includes("/") || record.original.mediaId.includes("\\") || record.original.mediaId.includes("\0")) throw new Error("Managed image record has an unsafe media identity");
	return path.join(record.original.mediaRoot, record.original.mediaSubdir, record.original.mediaId);
}
function resolveManagedImageOriginalsDir(stateDir) {
	const runtimeMediaRoot = path.resolve(stateDir) === path.resolve(resolveStateDir()) ? getMediaDir() : path.join(stateDir, "media");
	return path.join(runtimeMediaRoot, MANAGED_OUTGOING_ORIGINALS_SUBDIR);
}
async function hasUnmigratedManagedImageMetadata(stateDir) {
	try {
		return (await fs.readdir(path.join(stateDir, "media", "outgoing", "records"))).some((name) => name.endsWith(".json") || name.includes(".json.doctor-importing-"));
	} catch (error) {
		return error.code !== "ENOENT";
	}
}
async function deleteAgedOrphanManagedImageFiles(params) {
	if (await hasUnmigratedManagedImageMetadata(params.stateDir)) return 0;
	const referencedMediaIds = new Set(listManagedImageRecordEntries({ stateDir: params.stateDir }).map(({ record }) => record.original.mediaId));
	const originalsDir = resolveManagedImageOriginalsDir(params.stateDir);
	let names;
	try {
		names = await fs.readdir(originalsDir);
	} catch {
		return 0;
	}
	let deletedCount = 0;
	for (const name of names) {
		if (referencedMediaIds.has(name)) continue;
		const filePath = path.join(originalsDir, name);
		try {
			const stat = await fs.lstat(filePath);
			if (!stat.isFile() || stat.isSymbolicLink() || params.nowMs - stat.mtimeMs < params.minAgeMs) continue;
			await fs.rm(filePath, { force: true });
			deletedCount += 1;
		} catch {}
	}
	return deletedCount;
}
function buildOutgoingVariantUrl(sessionKey, attachmentId, variant) {
	return `${OUTGOING_IMAGE_ROUTE_PREFIX}/${encodeURIComponent(sessionKey)}/${attachmentId}/${variant}`;
}
function buildManagedOutgoingArtifactId(attachmentId, kind) {
	return `${kind === "image" ? MANAGED_OUTGOING_IMAGE_ARTIFACT_ID_PREFIX : MANAGED_OUTGOING_MEDIA_ARTIFACT_ID_PREFIX}${attachmentId}`;
}
function parseManagedOutgoingArtifactId(value) {
	const family = value.startsWith("artifact_managed_image_") ? "image" : value.startsWith("artifact_managed_media_") ? "media" : null;
	if (!family) return null;
	const prefix = family === "image" ? MANAGED_OUTGOING_IMAGE_ARTIFACT_ID_PREFIX : MANAGED_OUTGOING_MEDIA_ARTIFACT_ID_PREFIX;
	const attachmentId = value.slice(prefix.length);
	return MANAGED_OUTGOING_ATTACHMENT_ID_RE.test(attachmentId) ? {
		attachmentId,
		family
	} : null;
}
function signManagedOutgoingImageTicketPayload(encodedPayload) {
	return createHmac("sha256", managedOutgoingImageTicketSecret).update(encodedPayload).digest("base64url");
}
function createManagedOutgoingImageTicket(params) {
	const now = asDateTimestampMs(params.nowMs ?? Date.now());
	if (now === void 0) return null;
	const exp = asDateTimestampMs(now + MANAGED_OUTGOING_IMAGE_TICKET_TTL_MS);
	if (exp === void 0) return null;
	const payload = {
		scope: MANAGED_OUTGOING_IMAGE_TICKET_SCOPE,
		sessionKey: params.sessionKey,
		attachmentId: params.attachmentId,
		variant: "full",
		exp
	};
	const encodedPayload = Buffer.from(JSON.stringify(payload), "utf8").toString("base64url");
	return {
		ticket: `v1.${encodedPayload}.${signManagedOutgoingImageTicketPayload(encodedPayload)}`,
		expiresAt: resolveTimestampMsToIsoString(exp)
	};
}
function verifyManagedOutgoingImageTicket(params) {
	const now = asDateTimestampMs(params.nowMs ?? Date.now());
	if (now === void 0) return false;
	const parts = params.ticket?.split(".");
	if (!parts || parts.length !== 3 || parts[0] !== "v1") return false;
	const [, encodedPayload, signature] = parts;
	if (!encodedPayload || !signature) return false;
	if (!safeEqualSecret(signature, signManagedOutgoingImageTicketPayload(encodedPayload))) return false;
	try {
		const payload = JSON.parse(Buffer.from(encodedPayload, "base64url").toString("utf8"));
		return payload.scope === MANAGED_OUTGOING_IMAGE_TICKET_SCOPE && payload.sessionKey === params.sessionKey && payload.attachmentId === params.attachmentId && payload.variant === "full" && typeof payload.exp === "number" && Number.isFinite(payload.exp) && payload.exp >= now;
	} catch {
		return false;
	}
}
function deriveAltText(source, index) {
	const fallback = `Generated image ${index + 1}`;
	try {
		if (/^https?:\/\//i.test(source)) {
			const parsed = new URL(source);
			return path.basename(parsed.pathname || "").trim() || fallback;
		}
	} catch {}
	return path.basename(source).trim() || fallback;
}
function parseMediaDataUrl(source, label, imageLimits) {
	const trimmed = source.trim();
	if (!trimmed.startsWith("data:")) return { kind: "not-data-url" };
	const afterPrefix = trimmed.slice(5);
	const commaIdx = afterPrefix.indexOf(",");
	const mimeAndParams = commaIdx < 0 ? "" : afterPrefix.slice(0, commaIdx);
	if (mimeAndParams.slice(-7).toLowerCase() !== ";base64") throw new Error("Invalid image data URL");
	const semicolonIdx = mimeAndParams.indexOf(";");
	const contentType = (semicolonIdx < 0 ? mimeAndParams : mimeAndParams.slice(0, semicolonIdx)).trim().toLowerCase();
	if (!contentType) throw new Error("Invalid image data URL");
	const base64Part = afterPrefix.slice(commaIdx + 1);
	if (!/^[A-Za-z0-9+/=\s]+$/.test(base64Part)) throw new Error("Invalid image data URL");
	const mediaKind = mediaKindFromMime(contentType);
	if (mediaKind !== "image" && mediaKind !== "audio" && mediaKind !== "video") return { kind: "unsupported-data-url" };
	const maxBytes = maxBytesForManagedMediaKind(mediaKind, imageLimits);
	if (estimateBase64DecodedByteLength(base64Part) > maxBytes) throw createManagedMediaByteLimitError({
		kind: mediaKind,
		label,
		maxBytes
	});
	return {
		kind: "media-data-url",
		buffer: Buffer.from(base64Part.replace(/\s+/g, ""), "base64"),
		contentType,
		mediaKind
	};
}
async function getVariantStats(params) {
	const loaded = params.buffer ? {
		buffer: params.buffer,
		sizeBytes: params.sizeBytes ?? params.buffer.byteLength
	} : await (async () => {
		const { buffer, stat } = await readLocalFileSafely({ filePath: params.filePath });
		return {
			buffer,
			sizeBytes: stat.size
		};
	})();
	const metadataBuffer = loaded.buffer;
	const metadata = await getImageMetadata(metadataBuffer).catch(() => null) ?? {
		width: null,
		height: null
	};
	return {
		width: metadata.width ?? null,
		height: metadata.height ?? null,
		sizeBytes: Number.isFinite(loaded.sizeBytes) ? loaded.sizeBytes : null
	};
}
async function deleteManagedImageRecordArtifacts(record, stateDir = resolveStateDir(), alreadyClaimed = false) {
	if (!alreadyClaimed && !claimManagedImageRecordCleanupIfCurrent(record, stateDir)) return {
		deletedRecord: false,
		deletedFileCount: 0
	};
	try {
		await fs.rm(resolveManagedImageOriginalPath(record), { force: true });
	} catch {
		return {
			deletedRecord: false,
			deletedFileCount: 0
		};
	}
	return {
		deletedRecord: deleteClaimedManagedImageRecord(record, stateDir),
		deletedFileCount: 1
	};
}
async function cleanupManagedOutgoingMediaRecords(params) {
	const stateDir = params?.stateDir ?? resolveStateDir();
	const nowMs = params?.nowMs ?? Date.now();
	const transientMaxAgeMs = params?.transientMaxAgeMs ?? DEFAULT_TRANSIENT_OUTGOING_IMAGE_TTL_MS;
	const sessionKeyFilter = params?.sessionKey ?? null;
	const agentIdFilter = params?.agentId?.trim() ? normalizeAgentId(params.agentId) : void 0;
	const globalCompatibilityOwnerAgentId = sessionKeyFilter === "global" && agentIdFilter ? tryResolveSessionCompatibilityOwnerAgentId(getRuntimeConfig(), "global") : void 0;
	const forceDeleteSessionRecords = params?.forceDeleteSessionRecords === true;
	const entries = listManagedImageRecordEntries({ stateDir });
	let pendingPreparedAttachmentIds;
	let deletedRecordCount = 0;
	let deletedFileCount = 0;
	let retainedCount = 0;
	const transcriptAttachmentIndexCache = /* @__PURE__ */ new Map();
	const sessionStoreAvailabilityCache = /* @__PURE__ */ new Map();
	const sessionStoreTargetsReadCache = /* @__PURE__ */ new Map();
	for (const entry of entries) {
		const { record } = entry;
		if (sessionKeyFilter && record.sessionKey !== sessionKeyFilter) {
			retainedCount += 1;
			continue;
		}
		if (sessionKeyFilter === "global" && record.sessionKey === "global" && (!agentIdFilter || resolveManagedSessionOwnerAgentId(record.sessionKey, record.agentId, globalCompatibilityOwnerAgentId) !== agentIdFilter)) {
			retainedCount += 1;
			continue;
		}
		let shouldDelete = entry.cleanupPending;
		if (!entry.cleanupPending && forceDeleteSessionRecords && (!sessionKeyFilter || record.sessionKey === sessionKeyFilter)) shouldDelete = true;
		else if (!entry.cleanupPending && record.messageId) shouldDelete = await recordMatchesTranscriptMessage(record, transcriptAttachmentIndexCache, sessionStoreAvailabilityCache, sessionStoreTargetsReadCache, stateDir) === "missing";
		else if (!entry.cleanupPending) {
			const createdAtMs = Date.parse(record.createdAt);
			if (Number.isFinite(createdAtMs) && nowMs - createdAtMs >= transientMaxAgeMs && params?.hasActiveSessionRun?.(record.sessionKey, record.agentId?.trim() || void 0) !== true) {
				if (pendingPreparedAttachmentIds === void 0) pendingPreparedAttachmentIds = await loadPendingPreparedAttachmentIds(stateDir);
				shouldDelete = pendingPreparedAttachmentIds !== null && !pendingPreparedAttachmentIds.has(record.attachmentId);
			}
		}
		if (shouldDelete) {
			const deleted = await deleteManagedImageRecordArtifacts(record, stateDir, entry.cleanupPending);
			if (deleted.deletedRecord) {
				deletedRecordCount += 1;
				deletedFileCount += deleted.deletedFileCount;
			} else retainedCount += 1;
		} else retainedCount += 1;
	}
	deletedFileCount += await deleteAgedOrphanManagedImageFiles({
		stateDir,
		nowMs,
		minAgeMs: Math.max(transientMaxAgeMs, DEFAULT_TRANSIENT_OUTGOING_IMAGE_TTL_MS)
	});
	return {
		deletedRecordCount,
		deletedFileCount,
		retainedCount
	};
}
async function removeManagedOutgoingMediaBlocks(params) {
	const stateDir = params.stateDir ?? resolveStateDir();
	await Promise.all(collectManagedOutgoingAttachmentRefs(params.blocks).map(async ({ attachmentId }) => {
		const record = readManagedImageRecord(attachmentId, stateDir);
		if (record?.messageId === params.messageId) await deleteManagedImageRecordArtifacts(record, stateDir);
	}));
}
function resolveManagedSessionOwnerAgentId(sessionKey, explicitAgentId, compatibilityAgentId) {
	const ownerAgentId = explicitAgentId?.trim() || parseAgentSessionKey(sessionKey)?.agentId || compatibilityAgentId?.trim();
	return ownerAgentId ? normalizeAgentId(ownerAgentId) : void 0;
}
function resolveManagedRecordKind(record) {
	return resolveManagedMediaKind(record.original.contentType);
}
function buildManagedMediaBlock(record, playback) {
	const kind = resolveManagedRecordKind(record);
	if (!kind) throw new Error("Managed media record has an unsupported content type");
	const fullUrl = buildOutgoingVariantUrl(record.sessionKey, record.attachmentId, "full");
	const artifactId = buildManagedOutgoingArtifactId(record.attachmentId, kind);
	if (kind === "document") return {
		type: "attachment",
		attachment: {
			artifactId,
			url: fullUrl,
			kind,
			label: record.original.filename ?? record.alt,
			mimeType: record.original.contentType,
			sizeBytes: record.original.sizeBytes
		}
	};
	return {
		type: kind,
		artifactId,
		url: fullUrl,
		openUrl: fullUrl,
		...kind === "image" ? { alt: record.alt } : { fileName: record.original.filename },
		mimeType: record.original.contentType,
		...playback ? { playback } : {},
		...kind === "image" ? {
			width: record.original.width,
			height: record.original.height
		} : {},
		sizeBytes: record.original.sizeBytes
	};
}
function buildManagedOutgoingAttachmentRefKey(messageId, attachmentId) {
	return `${messageId}::${attachmentId}`;
}
function buildManagedImageResizeWarningBlock(params) {
	return {
		type: "text",
		text: `[Image warning] ${params.alt} exceeded gateway dimension/pixel limits and was resized from ${params.originalWidth}×${params.originalHeight} to ${params.resizedWidth}×${params.resizedHeight}.`
	};
}
function toRecordFilename(filePath, attachmentName, fallbackName, contentType) {
	const fallback = fallbackName ?? path.basename(filePath).trim();
	if (!attachmentName?.trim()) return fallback || null;
	const safeName = sanitizeUntrustedFileName(attachmentName, fallback);
	const extension = contentType === "application/octet-stream" || mimeTypeFromFilePath(safeName) === contentType ? path.extname(safeName) : path.extname(filePath);
	return `${path.parse(safeName).name}${extension}`;
}
function collectReplyMediaEntries(payload) {
	const attachmentByReference = /* @__PURE__ */ new Map();
	for (const attachment of payload.attachments ?? []) {
		const reference = (attachment.path ?? attachment.url ?? attachment.mediaUrl ?? attachment.filePath)?.trim();
		if (reference && !attachmentByReference.has(reference)) attachmentByReference.set(reference, attachment);
	}
	const mediaUrlCount = payload.mediaUrls?.length ?? 0;
	return [...(payload.mediaUrls ?? []).map((url, index) => ({
		url,
		attachment: attachmentByReference.get(url.trim()) ?? payload.attachments?.[index]
	})), ...typeof payload.mediaUrl === "string" ? [{
		url: payload.mediaUrl,
		attachment: attachmentByReference.get(payload.mediaUrl.trim()) ?? payload.attachments?.[mediaUrlCount]
	}] : []];
}
function prepareOutgoingMediaFromReplyPayload(payload, metadataSource = payload) {
	const metadataByUrl = /* @__PURE__ */ new Map();
	for (const entry of collectReplyMediaEntries(metadataSource)) {
		const key = entry.url.trim();
		if (key && entry.attachment && !metadataByUrl.has(key)) metadataByUrl.set(key, entry.attachment);
	}
	const seen = /* @__PURE__ */ new Set();
	return collectReplyMediaEntries(payload).flatMap((entry) => {
		const key = entry.url.trim();
		if (!key || seen.has(key)) return [];
		seen.add(key);
		const attachment = metadataByUrl.get(key) ?? entry.attachment;
		return [{
			url: entry.url,
			...attachment?.name ? { filename: attachment.name } : {},
			...attachment?.mimeType ? { mimeType: attachment.mimeType } : {},
			trustedLocal: attachment?.trustedLocalMedia ?? payload.trustedLocalMedia === true,
			...attachment?.durationMs !== void 0 ? { durationMs: attachment.durationMs } : {},
			...attachment?.width !== void 0 ? { width: attachment.width } : {},
			...attachment?.height !== void 0 ? { height: attachment.height } : {}
		}];
	});
}
function parseManagedOutgoingRoute(value) {
	try {
		const match = new URL(value, "http://localhost").pathname.match(/^\/api\/chat\/media\/outgoing\/([^/]+)\/([^/]+)\/full$/);
		if (!match) return null;
		if (!MANAGED_OUTGOING_ATTACHMENT_ID_RE.test(expectDefined(match[2], "managed image attachments regex capture 2"))) return null;
		return {
			sessionKey: decodeURIComponent(expectDefined(match[1], "managed image attachments regex capture 1")),
			attachmentId: expectDefined(match[2], "managed image attachments regex capture 2")
		};
	} catch {
		return null;
	}
}
function collectManagedOutgoingAttachmentRefs(blocks, expectedSessionKey) {
	const refs = /* @__PURE__ */ new Map();
	for (const block of blocks ?? []) {
		const attachment = block?.type === "attachment" ? asOptionalRecord(block.attachment) : void 0;
		if (block?.type !== "image" && block?.type !== "audio" && block?.type !== "video" && !attachment) continue;
		for (const candidate of [
			block.url,
			block.openUrl,
			attachment?.url
		]) {
			if (typeof candidate !== "string") continue;
			const parsed = parseManagedOutgoingRoute(candidate);
			if (!parsed) continue;
			if (expectedSessionKey && parsed.sessionKey !== expectedSessionKey) continue;
			const attachmentId = expectDefined(parsed.attachmentId, "managed image attachment id");
			refs.set(attachmentId, {
				attachmentId,
				sessionKey: parsed.sessionKey
			});
		}
	}
	return [...refs.values()];
}
async function loadPendingPreparedAttachmentIds(stateDir) {
	try {
		const attachmentIds = /* @__PURE__ */ new Set();
		for (const entry of await loadPendingSessionDeliveries(stateDir)) {
			if (entry.kind !== "agentTurn") continue;
			for (const blocks of Object.values(entry.preparedMediaBlocks ?? {})) for (const ref of collectManagedOutgoingAttachmentRefs(blocks, entry.sessionKey)) attachmentIds.add(ref.attachmentId);
		}
		return attachmentIds;
	} catch {
		return null;
	}
}
async function recordMatchesTranscriptMessage(record, cache, storeAvailabilityCache, storeTargetsReadCache, stateDir) {
	if (!record.messageId) return "missing";
	const { sessionKey, agentId, messageId: requestedMessageId } = record;
	const refKey = buildManagedOutgoingAttachmentRefKey(requestedMessageId, record.attachmentId);
	const cacheKey = buildSessionManagedOutgoingAttachmentIndexCacheKey(sessionKey, agentId);
	if (cache?.has(cacheKey)) return cache.get(cacheKey)?.has(refKey) ? "match" : "missing";
	const cfg = getRuntimeConfig();
	const ownerAgentId = resolveManagedSessionOwnerAgentId(sessionKey, agentId) ?? tryResolveSessionCompatibilityOwnerAgentId(cfg, sessionKey);
	if (!ownerAgentId) return "unavailable";
	const discovery = storeAvailabilityCache?.get(ownerAgentId) ?? resolveExistingAgentSessionStoreTargetsReadOnlyResult(cfg, ownerAgentId, {
		cache: storeTargetsReadCache,
		...stateDir ? { env: {
			...process.env,
			OPENCLAW_STATE_DIR: stateDir
		} } : {}
	});
	storeAvailabilityCache?.set(ownerAgentId, discovery);
	if (!discovery.available) return "unavailable";
	const usesRuntimeState = !stateDir || path.resolve(stateDir) === path.resolve(resolveStateDir());
	const env = stateDir ? {
		...process.env,
		OPENCLAW_STATE_DIR: stateDir
	} : process.env;
	let matched;
	for (const target of discovery.targets) {
		const exact = loadExactSessionEntryReadOnlyResult({
			agentId: ownerAgentId,
			clone: false,
			env,
			sessionKey,
			storePath: target.storePath
		});
		if (!exact.found) return "unavailable";
		let targetEntry = exact.value?.entry;
		if (!targetEntry) try {
			targetEntry = resolveSessionEntry({
				agentId: ownerAgentId,
				clone: false,
				env,
				sessionKey,
				storePath: target.storePath
			}, { readOnly: true }).existing;
		} catch {
			return "unavailable";
		}
		if (targetEntry) {
			if (matched) return "unavailable";
			matched = {
				entry: targetEntry,
				storePath: target.storePath
			};
		}
	}
	let entry = matched?.entry;
	let storePath = matched?.storePath ?? discovery.targets[0]?.storePath ?? "";
	if (!entry && usesRuntimeState) {
		const loaded = loadGatewaySessionEntryReadOnly(sessionKey, { agentId: ownerAgentId });
		const exact = loadExactSessionEntryReadOnlyResult({
			agentId: ownerAgentId,
			clone: false,
			sessionKey,
			storePath: loaded.storePath
		});
		if (!exact.found) return "unavailable";
		entry = exact.value?.entry ?? loaded.entry;
		storePath = loaded.storePath;
	}
	const sessionId = entry?.sessionId;
	if (!sessionId) {
		cache?.set(cacheKey, null);
		return "missing";
	}
	const scope = {
		agentId,
		sessionEntry: entry,
		sessionId,
		sessionKey,
		storePath
	};
	const messages = cache ? (await readSessionMessagesWithSourceAsync(scope, {
		mode: "full",
		reason: "managed outgoing attachment index",
		allowResetArchiveFallback: true
	})).messages : await readSessionMessagesMatchingIdAsync(scope, requestedMessageId);
	const index = /* @__PURE__ */ new Set();
	for (const message of messages) {
		const messageId = (message?.["__openclaw"])?.id;
		if (typeof messageId !== "string" || !messageId) continue;
		for (const ref of collectManagedOutgoingAttachmentRefs(readAssistantDisplayContent(message), sessionKey)) index.add(buildManagedOutgoingAttachmentRefKey(messageId, ref.attachmentId));
	}
	cache?.set(cacheKey, index);
	return index.has(refKey) ? "match" : "missing";
}
async function resolveManagedOutgoingMediaArtifactDownloadForRecord(record, stateDir) {
	if (await recordMatchesTranscriptMessage(record, void 0, void 0, void 0, stateDir) !== "match") return null;
	const kind = resolveManagedRecordKind(record);
	if (!kind) return null;
	const ticket = createManagedOutgoingImageTicket({
		sessionKey: record.sessionKey,
		attachmentId: record.attachmentId
	});
	if (!ticket) return null;
	try {
		if (!(await fs.stat(resolveManagedImageOriginalPath(record))).isFile()) return null;
	} catch {
		return null;
	}
	const canonicalUrl = buildOutgoingVariantUrl(record.sessionKey, record.attachmentId, "full");
	const params = new URLSearchParams({ mediaTicket: ticket.ticket });
	return {
		artifactId: buildManagedOutgoingArtifactId(record.attachmentId, kind),
		sessionKey: record.sessionKey,
		type: kind === "document" ? "file" : kind,
		title: kind === "image" ? record.alt : record.original.filename ?? record.alt,
		...record.original.contentType ? { mimeType: record.original.contentType } : {},
		...record.original.sizeBytes != null ? { sizeBytes: record.original.sizeBytes } : {},
		url: `${canonicalUrl}?${params.toString()}`,
		expiresAt: ticket.expiresAt
	};
}
/** Resolve one transcript-backed media artifact to a short-lived HTTP capability. */
async function resolveManagedOutgoingMediaArtifactDownload(params) {
	const parsed = parseManagedOutgoingArtifactId(params.artifactId);
	if (!parsed) return null;
	const record = readManagedImageRecord(parsed.attachmentId, params.stateDir);
	if (!record || record.sessionKey !== params.sessionKey) return null;
	const requestedAgentId = params.agentId ? normalizeAgentId(params.agentId) : void 0;
	const recordAgentId = resolveManagedSessionOwnerAgentId(record.sessionKey, record.agentId, params.defaultAgentId);
	if (requestedAgentId && recordAgentId !== requestedAgentId) return null;
	const kind = resolveManagedRecordKind(record);
	if (!kind || parsed.family === "image" !== (kind === "image")) return null;
	return await resolveManagedOutgoingMediaArtifactDownloadForRecord(record, params.stateDir);
}
/** Upgrade legacy managed-image URLs that predate stable artifact ids. */
async function resolveManagedOutgoingMediaUrlDownload(params) {
	const parsed = parseManagedOutgoingRoute(params.url);
	if (!parsed || parsed.sessionKey !== params.sessionKey) return null;
	const record = readManagedImageRecord(parsed.attachmentId, params.stateDir);
	if (!record || record.sessionKey !== params.sessionKey) return null;
	return await resolveManagedOutgoingMediaArtifactDownloadForRecord(record, params.stateDir);
}
function attachManagedOutgoingMediaToMessage(params) {
	const messageId = params.messageId.trim();
	if (!messageId) return false;
	const refs = collectManagedOutgoingAttachmentRefs(params.blocks);
	if (refs.length === 0) return false;
	return refs.map(({ attachmentId, sessionKey }) => attachManagedImageRecordToMessage({
		attachmentId,
		sessionKey,
		messageId,
		updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
		stateDir: params.stateDir
	})).every(Boolean);
}
async function createManagedOutgoingMediaBlocks(params) {
	const sessionKey = params.sessionKey.trim();
	if (!sessionKey) return [];
	const items = params.items ?? [];
	if (items.length === 0) return [];
	const stateDir = params.stateDir ?? resolveStateDir();
	const limits = resolveManagedImageAttachmentLimits(params.limits);
	const blocks = [];
	let resolvedLocalRoots;
	for (const [index, item] of items.entries()) {
		const mediaUrl = item.url;
		const trimmedMediaUrl = mediaUrl.trim();
		const dataUrlKind = /^data:(image|audio|video)\//iu.exec(trimmedMediaUrl)?.[1];
		const fallbackLabel = `Generated ${dataUrlKind ?? "media"} ${index + 1}`;
		const isDataUrl = trimmedMediaUrl.startsWith("data:");
		const localMediaPath = isDataUrl ? void 0 : resolveLocalMediaPath(mediaUrl);
		const label = isDataUrl ? fallbackLabel : item.filename?.trim() || deriveAltText(localMediaPath ?? mediaUrl, index);
		const inferredKind = resolveManagedMediaKind(item.mimeType ?? mimeTypeFromFilePath(localMediaPath ?? mediaUrl));
		const hintedKind = dataUrlKind === "image" || dataUrlKind === "audio" || dataUrlKind === "video" ? dataUrlKind : inferredKind === "image" || inferredKind === "audio" || inferredKind === "video" || inferredKind === "document" ? inferredKind : "media";
		let savedOriginalPath = null;
		try {
			const parsedDataUrl = parseMediaDataUrl(mediaUrl, fallbackLabel, limits);
			if (parsedDataUrl.kind === "unsupported-data-url") throw new Error("Managed media attachment has an unsupported data URL content type");
			if (localMediaPath && (hintedKind === "audio" || hintedKind === "video") && !item.trustedLocal) throw new Error("Local audio/video media requires an explicitly trusted reply payload");
			let resizeWarning = null;
			let savedOriginal = parsedDataUrl.kind === "media-data-url" ? await saveMediaBuffer(parsedDataUrl.buffer, parsedDataUrl.contentType, "outgoing/originals", maxBytesForManagedMediaKind(parsedDataUrl.mediaKind, limits), `generated-${parsedDataUrl.mediaKind}-${index + 1}`) : await (async () => {
				if (localMediaPath) {
					const localRoots = params.localRoots;
					await assertLocalMediaAllowed(localMediaPath, localRoots, localRoots === "any" ? void 0 : { resolveRoots: async () => {
						resolvedLocalRoots ??= await resolveLocalMediaRoots(localRoots);
						return resolvedLocalRoots;
					} });
				}
				return await saveMediaSource(localMediaPath ?? mediaUrl, void 0, "outgoing/originals", Math.max(limits.maxBytes, maxBytesForKind("audio"), maxBytesForKind("video"), maxBytesForKind("document"), MEDIA_MAX_BYTES));
			})();
			savedOriginalPath = savedOriginal.path;
			let savedOriginalContentType = savedOriginal.contentType ?? item.mimeType;
			if (!savedOriginalContentType) throw new Error("Managed media attachment has no detectable content type");
			const mediaKind = resolveManagedMediaKind(savedOriginalContentType);
			if (!mediaKind) throw new Error("Managed media attachment has an unsupported content type");
			if (localMediaPath && mediaKind !== "image" && !item.trustedLocal) throw new Error("Local audio/video media requires an explicitly trusted reply payload");
			const maxBytes = maxBytesForManagedMediaKind(mediaKind, limits);
			if (savedOriginal.size > maxBytes) throw createManagedMediaByteLimitError({
				kind: mediaKind,
				label,
				maxBytes
			});
			let originalStats = {
				width: null,
				height: null,
				sizeBytes: savedOriginal.size
			};
			if (mediaKind === "image") {
				let originalBuffer = parsedDataUrl.kind === "media-data-url" ? parsedDataUrl.buffer : (await readLocalFileSafely({ filePath: savedOriginal.path })).buffer;
				validateManagedImageBuffer(originalBuffer, label, limits);
				originalStats = await getVariantStats({
					filePath: savedOriginal.path,
					buffer: originalBuffer,
					sizeBytes: savedOriginal.size
				});
				if (originalStats.sizeBytes != null && originalStats.sizeBytes > maxBytes) throw createManagedMediaByteLimitError({
					kind: mediaKind,
					label,
					maxBytes
				});
				const originalDisplayMetadata = originalStats.width != null && originalStats.height != null ? {
					width: originalStats.width,
					height: originalStats.height
				} : await getImageMetadata(originalBuffer);
				let effectiveMetadata = originalDisplayMetadata;
				let metadataLimitError = getManagedImageMetadataLimitError(effectiveMetadata, label, limits);
				for (let resizeAttempt = 0; metadataLimitError; resizeAttempt += 1) {
					if (!effectiveMetadata || resizeAttempt >= 3) throw createManagedImageAttachmentError(metadataLimitError);
					const resized = await resizeManagedImageBufferToLimits({
						buffer: originalBuffer,
						limits
					});
					validateManagedImageBuffer(resized.buffer, label, limits);
					const replacement = await saveMediaBuffer(resized.buffer, resized.contentType, "outgoing/originals", limits.maxBytes, toRecordFilename(savedOriginal.path) ?? `generated-image-${index + 1}`);
					await fs.rm(savedOriginal.path, { force: true }).catch(() => {});
					savedOriginal = replacement;
					savedOriginalContentType = replacement.contentType ?? resized.contentType;
					savedOriginalPath = savedOriginal.path;
					originalBuffer = resized.buffer;
					originalStats = await getVariantStats({
						filePath: savedOriginal.path,
						buffer: originalBuffer,
						sizeBytes: savedOriginal.size
					});
					effectiveMetadata = originalStats.width != null && originalStats.height != null ? {
						width: originalStats.width,
						height: originalStats.height
					} : await getImageMetadata(originalBuffer);
					metadataLimitError = getManagedImageMetadataLimitError(effectiveMetadata, label, limits);
					if (!metadataLimitError) resizeWarning = buildManagedImageResizeWarningBlock({
						alt: label,
						originalWidth: originalDisplayMetadata?.width ?? effectiveMetadata?.width ?? resized.width,
						originalHeight: originalDisplayMetadata?.height ?? effectiveMetadata?.height ?? resized.height,
						resizedWidth: effectiveMetadata?.width ?? resized.width,
						resizedHeight: effectiveMetadata?.height ?? resized.height
					});
				}
			}
			const record = {
				attachmentId: randomUUID(),
				sessionKey,
				...sessionKey === "global" && params.agentId?.trim() ? { agentId: params.agentId.trim() } : {},
				messageId: params.messageId ?? null,
				createdAt: (/* @__PURE__ */ new Date()).toISOString(),
				retentionClass: params.messageId ? "history" : "transient",
				alt: label,
				original: {
					mediaRoot: path.dirname(path.dirname(path.dirname(path.resolve(savedOriginal.path)))),
					mediaId: savedOriginal.id,
					mediaSubdir: MANAGED_OUTGOING_ORIGINALS_SUBDIR,
					contentType: savedOriginalContentType,
					width: originalStats.width,
					height: originalStats.height,
					sizeBytes: originalStats.sizeBytes,
					filename: toRecordFilename(savedOriginal.path, item.filename, mediaKind === "image" ? void 0 : label, savedOriginalContentType)
				}
			};
			let playback;
			if (mediaKind === "audio" || mediaKind === "video") {
				const opened = await openLocalFileSafely({ filePath: savedOriginal.path });
				try {
					const probe = await probePlaybackMediaFileDescriptor(opened.handle.fd, mediaKind);
					playback = await resolvePlaybackModeForSource({
						sourcePath: opened.realPath,
						sourceStat: opened.stat,
						mimeType: savedOriginalContentType,
						kind: mediaKind,
						probe
					});
				} finally {
					await opened.handle.close().catch(() => {});
				}
			}
			const block = buildManagedMediaBlock(record, playback);
			insertManagedImageRecord(record, stateDir);
			const durationMs = asNonNegativeFiniteNumber(item.durationMs);
			const width = asNonNegativeFiniteNumber(item.width);
			const height = asNonNegativeFiniteNumber(item.height);
			blocks.push({
				...block,
				...durationMs !== void 0 ? { durationMs } : {},
				...mediaKind === "video" && width !== void 0 ? { width } : {},
				...mediaKind === "video" && height !== void 0 ? { height } : {}
			});
			if (resizeWarning) blocks.push(resizeWarning);
		} catch (error) {
			if (savedOriginalPath) await fs.rm(savedOriginalPath, { force: true }).catch(() => {});
			const sanitizedError = getSanitizedManagedImageAttachmentError(error, label, hintedKind);
			if (params.continueOnPrepareError) {
				blocks.push(buildManagedMediaFailureBlock({
					code: "delivery-failed",
					kind: hintedKind,
					label,
					mimeType: item.mimeType ?? mimeTypeFromFilePath(localMediaPath ?? mediaUrl)
				}));
				params.onPrepareError?.(sanitizedError);
				continue;
			}
			await removeManagedOutgoingMediaBlocks({
				blocks,
				messageId: params.messageId ?? null,
				stateDir
			});
			throw sanitizedError;
		}
	}
	return blocks;
}
function sendStatus(res, statusCode, body) {
	if (res.writableEnded) return;
	res.statusCode = statusCode;
	res.setHeader("content-type", "text/plain; charset=utf-8");
	res.end(body);
}
function buildManagedMediaContentDisposition(value, contentType) {
	const fallback = contentType.startsWith("image/") ? "generated-image" : "generated-media";
	const filename = (value ?? fallback).replace(/[\r\n"\\]/g, "_").trim() || fallback;
	if (mediaKindFromMime(contentType) === "document") return buildAssistantMediaContentDisposition(filename, contentType);
	return /^[\x20-\x7e]+$/u.test(filename) ? `inline; filename="${filename}"` : buildAssistantMediaContentDisposition(filename, contentType);
}
async function handleManagedOutgoingMediaHttpRequest(req, res, opts) {
	const requestUrl = new URL(req.url ?? "/", "http://localhost");
	const match = (opts.basePath && requestUrl.pathname.startsWith(`${opts.basePath}/`) ? requestUrl.pathname.slice(opts.basePath.length) : requestUrl.pathname).match(/^\/api\/chat\/media\/outgoing\/([^/]+)\/([^/]+)\/(full|thumbnail)$/);
	if (!match) return false;
	if (req.method !== "GET" && req.method !== "HEAD") {
		sendMethodNotAllowed(res, "GET, HEAD");
		return true;
	}
	const encodedSessionKey = match[1];
	const attachmentId = match[2];
	const variant = match[3];
	if (!encodedSessionKey || !attachmentId || variant !== "full" && variant !== "thumbnail") return false;
	if (!MANAGED_OUTGOING_ATTACHMENT_ID_RE.test(attachmentId)) {
		sendStatus(res, 404, "not found");
		return true;
	}
	let sessionKey;
	try {
		sessionKey = decodeURIComponent(encodedSessionKey);
	} catch {
		sendStatus(res, 404, "not found");
		return true;
	}
	const hasValidMediaTicket = verifyManagedOutgoingImageTicket({
		ticket: requestUrl.searchParams.get("mediaTicket"),
		sessionKey,
		attachmentId
	});
	if (!hasValidMediaTicket) {
		const requestAuth = await authorizeGatewayHttpRequestOrReply({
			req,
			res,
			auth: opts.auth,
			trustedProxies: opts.trustedProxies,
			allowRealIpFallback: opts.allowRealIpFallback,
			rateLimiter: opts.rateLimiter
		});
		if (!requestAuth) return true;
		const requestedScopes = resolveOpenAiCompatibleHttpOperatorScopes(req, requestAuth);
		const scopeAuth = authorizeOperatorScopesForMethod("chat.history", requestedScopes);
		if (!scopeAuth.allowed) {
			sendMissingScopeForbidden(res, scopeAuth.missingScope);
			return true;
		}
		if (!resolveOpenAiCompatibleHttpSenderIsOwner(req, requestAuth)) {
			sendJson(res, 403, {
				ok: false,
				error: {
					type: "forbidden",
					message: "owner access required"
				}
			});
			return true;
		}
	}
	const stateDir = opts.stateDir ?? resolveStateDir();
	const record = readManagedImageRecord(attachmentId, stateDir);
	if (!record || record.sessionKey !== sessionKey) {
		sendStatus(res, 404, "not found");
		return true;
	}
	if (await recordMatchesTranscriptMessage(record, void 0, void 0, void 0, stateDir) !== "match") {
		sendStatus(res, 404, "not found");
		return true;
	}
	const mediaKind = resolveManagedRecordKind(record);
	if (!mediaKind) {
		sendStatus(res, 404, "not found");
		return true;
	}
	let opened;
	try {
		opened = await openLocalFileSafely({ filePath: resolveManagedImageOriginalPath(record) });
	} catch {
		sendStatus(res, 404, "not found");
		return true;
	}
	const respondNotFound = () => sendStatus(res, 404, "not found");
	let responseContentType = record.original.contentType || "application/octet-stream";
	let responseFilename = record.original.filename;
	if (variant === "thumbnail") {
		if (mediaKind !== "image") {
			await opened.handle.close();
			sendStatus(res, 404, "not found");
			return true;
		}
		try {
			const thumbnail = await resolveManagedImageThumbnail(`${opened.realPath}\0${opened.stat.mtimeMs}\0${opened.stat.size}`, async () => {
				const source = await opened.handle.readFile();
				return (await createImageProcessor().encode(source, {
					format: "png",
					resize: {
						maxSide: MANAGED_IMAGE_THUMBNAIL_MAX_SIDE,
						enlarge: false
					},
					compressionLevel: 8
				})).data;
			});
			await opened.handle.close();
			const sourceName = path.parse(responseFilename ?? "generated-image").name;
			res.statusCode = 200;
			res.setHeader("content-type", "image/png");
			res.setHeader("content-length", String(thumbnail.byteLength));
			res.setHeader("x-content-type-options", "nosniff");
			res.setHeader("referrer-policy", "no-referrer");
			res.setHeader("cache-control", hasValidMediaTicket ? `private, max-age=${MANAGED_OUTGOING_IMAGE_TICKET_TTL_MS / 1e3}, immutable` : "private, max-age=31536000, immutable");
			res.setHeader("content-disposition", buildManagedMediaContentDisposition(`${sourceName}-thumbnail.png`, "image/png"));
			res.end(req.method === "HEAD" ? void 0 : thumbnail);
			return true;
		} catch {
			await opened.handle.close().catch(() => {});
			sendStatus(res, 404, "not found");
			return true;
		}
	}
	let byteStream = createGatewayByteStream(res, opened.handle, respondNotFound);
	const isPlayback = requestUrl.searchParams.get("playback") === "1" && (mediaKind === "audio" || mediaKind === "video");
	if (isPlayback) {
		const playback = await resolvePlaybackTranscode({
			sourcePath: opened.realPath,
			sourceStat: opened.stat,
			mimeType: responseContentType,
			kind: mediaKind
		}).catch(async (error) => {
			await byteStream.close();
			throw error;
		});
		if (playback.kind === "preparing") {
			await byteStream.close();
			sendJson(res, 202, { status: "preparing" });
			return true;
		}
		if (playback.kind === "transcoded") {
			const transcoded = await openLocalFileSafely({ filePath: playback.path }).catch(() => null);
			if (transcoded) {
				await byteStream.close();
				opened = transcoded;
				byteStream = createGatewayByteStream(res, opened.handle, respondNotFound);
				responseContentType = playback.contentType;
				responseFilename = replacePlaybackFileExtension(responseFilename ?? "generated-media", playback.extension);
			}
		}
	}
	res.setHeader("content-type", responseContentType);
	res.setHeader("x-content-type-options", "nosniff");
	res.setHeader("referrer-policy", "no-referrer");
	res.setHeader("cache-control", isPlayback ? "private, no-cache" : hasValidMediaTicket ? `private, max-age=${MANAGED_OUTGOING_IMAGE_TICKET_TTL_MS / 1e3}, immutable` : "private, max-age=31536000, immutable");
	res.setHeader("content-disposition", buildManagedMediaContentDisposition(responseFilename, responseContentType));
	const byteResponse = resolveByteResponse({
		file: opened.stat,
		validators: isPlayback ? void 0 : createImmutableFileValidators(opened.stat),
		method: req.method,
		request: req
	});
	writeByteHeaders(res, byteResponse);
	await byteStream.pipe(byteResponse, req.method);
	return true;
}
//#endregion
export { buildManagedMediaFailureBlock as a, handleManagedOutgoingMediaHttpRequest as c, removeManagedOutgoingMediaBlocks as d, resolveManagedImageAttachmentLimits as f, attachManagedOutgoingMediaToMessage as i, parseManagedOutgoingArtifactId as l, resolveManagedOutgoingMediaUrlDownload as m, MANAGED_OUTGOING_IMAGE_ARTIFACT_ID_PREFIX as n, cleanupManagedOutgoingMediaRecords as o, resolveManagedOutgoingMediaArtifactDownload as p, MANAGED_OUTGOING_MEDIA_ARTIFACT_ID_PREFIX as r, createManagedOutgoingMediaBlocks as s, DEFAULT_MANAGED_IMAGE_ATTACHMENT_LIMITS as t, prepareOutgoingMediaFromReplyPayload as u };