openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
588 lines (587 loc) • 23.7 kB
JavaScript
import { u as asPositiveFiniteNumber } from "./number-coercion-CLj0HTDM.js";
import { l as normalizeOptionalString } from "./string-coerce-CIXf7egm.js";
import { i as shouldLogVerbose, r as logVerbose } from "./globals-CTaGxEqj.js";
import { d as normalizeMimeType, u as mimeTypeFromFilePath } from "./mime-CVpcq9ju.js";
import { r as classifyMediaReferenceSource } from "./media-reference-ZG0ZXjCX.js";
import { a as wrapExternalContent } from "./external-content-CpqslxXH.js";
import { t as finalizeInboundContext } from "./inbound-context-BJXKd4x8.js";
import { t as resolveConcurrency } from "./resolve-DyQF7rKc.js";
import { t as attachmentClassFromMime } from "./attachment-classify-DzYPUMr_.js";
import { t as renderFileContextBlock } from "./file-context-qb0kehru.js";
import { i as extractFileContentFromBuffer, l as resolveInputFileLimits } from "./input-files-CWWFaJLH.js";
import { o as resolveAttachmentKind } from "./attachments.normalize-BdfpE9Qn.js";
import { d as selectAttachments } from "./runner.entries-B6FlBFiO.js";
import { a as createMediaAttachmentCache, i as runCapability, o as normalizeMediaAttachments, r as resolveMediaAttachmentLocalRoots, t as buildProviderRegistry } from "./runner-3MXvZszK.js";
import { n as sendTranscriptEcho } from "./echo-transcript-CeJJVghR.js";
import pMap from "p-map";
//#region packages/media-understanding-common/src/format.ts
const sectionByKind = {
"audio.transcription": {
title: "Audio",
label: "Transcript"
},
"image.description": {
title: "Image",
label: "Description"
},
"video.description": {
title: "Video",
label: "Description"
}
};
function formatSection(title, label, text, userText) {
const lines = [`[${title}]`];
if (userText) lines.push(`User text:\n${userText}`);
lines.push(`${label}:\n${text}`);
return lines.join("\n");
}
/** Formats media-understanding outputs into the chat body sent back to the model. */
function formatMediaUnderstandingBody(params) {
const outputs = params.outputs.filter((output) => output.text.trim());
if (outputs.length === 0) return params.body ?? "";
const userText = params.body?.trim() || void 0;
const sections = [];
if (userText && outputs.length > 1) sections.push(`User text:\n${userText}`);
const counts = /* @__PURE__ */ new Map();
for (const output of outputs) counts.set(output.kind, (counts.get(output.kind) ?? 0) + 1);
const seen = /* @__PURE__ */ new Map();
for (const output of outputs) {
const count = counts.get(output.kind) ?? 1;
const next = (seen.get(output.kind) ?? 0) + 1;
seen.set(output.kind, next);
const suffix = count > 1 ? ` ${next}/${count}` : "";
const section = sectionByKind[output.kind];
sections.push(formatSection(`${section.title}${suffix}`, section.label, output.text, outputs.length === 1 ? userText : void 0));
}
return sections.join("\n\n").trim();
}
/** Formats one or more audio transcript outputs for legacy transcript-only callers. */
function formatAudioTranscripts(outputs) {
if (outputs.length === 1) {
const [output] = outputs;
if (output) return output.text;
throw new Error("expected single audio transcript to be defined");
}
return outputs.map((output, index) => `Audio ${index + 1}:\n${output.text}`).join("\n\n");
}
//#endregion
//#region src/media-understanding/apply-capability.ts
async function runMediaCapability(params) {
try {
return await runCapability(params);
} catch (err) {
if (shouldLogVerbose()) logVerbose(`Media understanding task failed: ${String(err)}`);
const selection = selectAttachments({
capability: params.capability,
attachments: params.media,
policy: params.config?.attachments
});
return {
outputs: [],
decision: {
capability: params.capability,
outcome: "failed",
attachments: [],
attachmentDispositions: Object.fromEntries([...selection.selected.map(({ index }) => [index, { kind: "failed" }]), ...selection.droppedAttachmentIndexes.map((index) => [index, { kind: "not-selected" }])]),
...params.capability === "image" ? { nativeVisionActive: false } : {}
}
};
}
}
//#endregion
//#region src/media-understanding/file-attachment-outcomes.ts
const MIME_TYPE = String.raw`([a-z0-9!#$&^_.+-]+/[a-z0-9!#$&^_.+-]+)`;
const HTTP_TOKEN = String.raw`[a-z0-9!#$%&'*+.^_\x60|~-]+`;
const HTTP_QUOTED_STRING = String.raw`"(?:[\t !#-\[\]-~]|\\[\t -~])*"`;
const MIME_PARAMETER = String.raw`[ \t]*;[ \t]*${HTTP_TOKEN}=(?:${HTTP_TOKEN}|${HTTP_QUOTED_STRING})`;
const MIME_TYPE_WITH_OPTIONAL_PARAMS = new RegExp(String.raw`^${MIME_TYPE}(?:${MIME_PARAMETER})*$`, "i");
const MARKER_MIME_MAX_CHARS = 100;
function sanitizeMimeType(value) {
const trimmed = normalizeOptionalString(value);
if (!trimmed) return;
return trimmed.match(MIME_TYPE_WITH_OPTIONAL_PARAMS)?.[1]?.toLowerCase();
}
function markerSafeMime(value) {
const mime = sanitizeMimeType(value);
return mime && mime.length <= MARKER_MIME_MAX_CHARS ? mime : void 0;
}
function wrapUntrustedAttachmentContent(content) {
return wrapExternalContent(content, {
source: "unknown",
includeWarning: false
});
}
const MARKER_LOCAL_PATH_MAX_CHARS = 300;
const POSIX_ABSOLUTE_PATH = /^\//;
const WINDOWS_ABSOLUTE_PATH = /^[A-Za-z]:\\/;
const MARKER_PATH_SAFE = /^[\p{L}\p{M}\p{N} /\\:._-]+$/u;
function markerSafeLocalPath(value, allowWorkspaceRelative = false) {
if (!value || value.length > MARKER_LOCAL_PATH_MAX_CHARS) return;
if (!(POSIX_ABSOLUTE_PATH.test(value) || WINDOWS_ABSOLUTE_PATH.test(value)) && (!allowWorkspaceRelative || value.includes("\\") || value.split("/").some((segment) => !segment || segment === "." || segment === ".."))) return;
return MARKER_PATH_SAFE.test(value) ? value : void 0;
}
const SKIPPED_FILE_OUTCOME_KINDS = /* @__PURE__ */ new Set([
"unsupported-format",
"policy-rejected",
"read-failure",
"url-sources-disabled"
]);
function isSkippedFileOutcome(outcome) {
return SKIPPED_FILE_OUTCOME_KINDS.has(outcome.kind);
}
function renderFileAttachmentOutcome(outcome, options) {
switch (outcome.kind) {
case "extracted": return wrapUntrustedAttachmentContent(outcome.text);
case "rendered-to-images": return "[PDF content rendered to images]";
case "no-extractable-text": return "[No extractable text]";
case "unsupported-format": {
const mime = markerSafeMime(outcome.mime);
const formatClause = mime ? `Unsupported document format: ${mime}.` : "Unsupported document format.";
const localPath = markerSafeLocalPath(options?.selfServeLocalPath === false ? void 0 : options?.selfServeLocalPath ?? outcome.localPath, typeof options?.selfServeLocalPath === "string");
const formatHint = outcome.mime?.startsWith("application/vnd.openxmlformats-officedocument") ? " (this Office file is a zip archive containing XML)" : "";
return localPath ? [`[${formatClause} The approved local file path follows as external attachment metadata. Its text is not extracted automatically. Read the file yourself with your tools before answering${formatHint}; do not ask the user to paste the contents.]`, wrapUntrustedAttachmentContent(localPath)].join("") : `[${formatClause} PDF and plain-text attachments can be read.]`;
}
case "policy-rejected": {
const mime = markerSafeMime(outcome.mime);
return mime ? `[Attachment type not allowed: ${mime}]` : "[Attachment type not allowed]";
}
case "read-failure": return "[Attachment could not be read]";
case "url-sources-disabled": return "[Attachment skipped: URL file sources are disabled]";
case "claimed-elsewhere": return null;
default: return outcome;
}
}
//#endregion
//#region src/media-understanding/file-extraction-limits.ts
const INBOUND_FILE_EXTRACTION_DEFAULT_MAX_MB = 20;
const INBOUND_FILE_EXTRACTION_MAX_BYTES_CAP = 26214400;
const INBOUND_FILE_EXTRACTION_DEFAULT_MAX_PAGES = 20;
const INBOUND_FILE_EXTRACTION_MAX_PAGES_CAP = 150;
function resolveInboundFileExtractionMaxBytes(defaults) {
const maxMb = asPositiveFiniteNumber(defaults?.mediaMaxMb) ?? INBOUND_FILE_EXTRACTION_DEFAULT_MAX_MB;
return Math.min(Math.floor(maxMb * 1024 * 1024), INBOUND_FILE_EXTRACTION_MAX_BYTES_CAP);
}
function resolveInboundFileExtractionMaxPages(defaults) {
const pages = asPositiveFiniteNumber(defaults?.pdfMaxPages) ?? INBOUND_FILE_EXTRACTION_DEFAULT_MAX_PAGES;
return Math.min(Math.trunc(pages), INBOUND_FILE_EXTRACTION_MAX_PAGES_CAP);
}
/** Builds inbound attachment extraction limits, sized to the agent's media/PDF config. */
function resolveFileExtractionLimits(cfg) {
const files = cfg.gateway?.http?.endpoints?.responses?.files;
const allowedMimesConfigured = Boolean(files?.allowedMimes?.length);
const defaults = cfg.agents?.defaults;
const inboundFiles = {
...files,
maxBytes: files?.maxBytes ?? resolveInboundFileExtractionMaxBytes(defaults),
pdf: {
...files?.pdf,
maxPages: files?.pdf?.maxPages ?? resolveInboundFileExtractionMaxPages(defaults)
}
};
return {
...resolveInputFileLimits(inboundFiles),
allowedMimesConfigured
};
}
//#endregion
//#region src/media-understanding/media-attachment-outcomes.ts
function renderSkippedFileOverflowSummary(count) {
return `[${count} more attachment${count === 1 ? "" : "s"} skipped]`;
}
function renderMediaAttachmentDisposition(capability, disposition) {
const label = `${capability[0]?.toUpperCase()}${capability.slice(1)}`;
switch (disposition.kind) {
case "handled":
case "handed-to-native-vision": return null;
case "not-selected": return `[${label} attachment not processed: attachment limit reached]`;
case "capability-disabled": return `[${label} attachment not analyzed: ${capability} understanding is disabled]`;
case "no-model": return `[${label} attachment not analyzed: no ${capability}-understanding model is configured]`;
case "scope-denied": return `[${label} attachment not analyzed in this chat]`;
case "failed": return `[${label} attachment could not be analyzed]`;
default: return disposition;
}
}
//#endregion
//#region src/media-understanding/apply.ts
const CAPABILITY_ORDER = [
"image",
"audio",
"video"
];
const AUDIO_ONLY_CAPABILITY_ORDER = ["audio"];
const EMPTY_VOICE_NOTE_PLACEHOLDER = "[Voice note could not be transcribed because the audio attachment was too small]";
function appendFileBlocks(body, blocks) {
if (!blocks || blocks.length === 0) return body ?? "";
const base = typeof body === "string" ? body.trim() : "";
const suffix = blocks.join("\n\n").trim();
if (!base) return suffix;
return `${base}\n\n${suffix}`.trim();
}
function attachmentUrlDisplayName(url) {
try {
return new URL(url).pathname.split("/").findLast((segment) => segment.length > 0) || void 0;
} catch {
return;
}
}
async function classifyFileAttachment(params) {
const { attachment, cache, cfg, limits, skipAttachmentIndexes } = params;
const attachmentFilename = attachment.path ?? (attachment.url ? attachmentUrlDisplayName(attachment.url) : void 0);
const displayFilename = attachment.fileName ?? attachmentFilename;
if (skipAttachmentIndexes?.has(attachment.index)) return { outcome: { kind: "claimed-elsewhere" } };
const extensionMime = mimeTypeFromFilePath(attachmentFilename);
const forcedTextMime = attachmentClassFromMime(extensionMime) === "text" ? extensionMime : void 0;
const kind = forcedTextMime ? "document" : resolveAttachmentKind(attachment);
if (!forcedTextMime && (kind === "image" || kind === "video" || kind === "audio")) return { outcome: { kind: "claimed-elsewhere" } };
if (!limits.allowUrl && attachment.url && !attachment.path && !classifyMediaReferenceSource(attachment.url).isMediaStoreUrl) {
if (shouldLogVerbose()) logVerbose(`media: file attachment skipped (url disabled) index=${attachment.index}`);
return {
outcome: { kind: "url-sources-disabled" },
filename: displayFilename
};
}
let bufferResult;
try {
bufferResult = await cache.getBuffer({
attachmentIndex: attachment.index,
maxBytes: limits.maxBytes,
timeoutMs: limits.timeoutMs
});
} catch (err) {
if (shouldLogVerbose()) logVerbose(`media: file attachment skipped (buffer): ${String(err)}`);
return {
outcome: { kind: "read-failure" },
filename: displayFilename
};
}
const filename = attachment.fileName ?? bufferResult?.fileName;
const classification = bufferResult.classification;
const classifiedMime = sanitizeMimeType(classification.mime);
const binaryMime = sanitizeMimeType(normalizeMimeType(attachment.mime)) ?? classifiedMime;
const selfServeLocalPath = bufferResult.localPath;
if (classification.class !== "text" && !(classification.class === "document" && classification.mime === "application/pdf")) {
if (limits.allowedMimesConfigured && !(classifiedMime && limits.allowedMimes.has(classifiedMime))) return {
outcome: {
kind: "policy-rejected",
mime: classifiedMime ?? binaryMime
},
filename,
mimeType: classifiedMime ?? binaryMime
};
return {
outcome: {
kind: "unsupported-format",
mime: binaryMime,
...selfServeLocalPath ? { localPath: selfServeLocalPath } : {}
},
filename,
mimeType: binaryMime
};
}
const mimeType = sanitizeMimeType(classification.mime);
if (classification.class === "text" && attachment.mime && normalizeMimeType(attachment.mime) !== classification.mime) logVerbose(`media: MIME override from "${attachment.mime}" to "${classification.mime}" for index=${attachment.index}`);
if (!mimeType) {
if (shouldLogVerbose()) logVerbose(`media: file attachment skipped (unknown mime) index=${attachment.index}`);
return {
outcome: { kind: "unsupported-format" },
filename
};
}
const allowedMimes = new Set(limits.allowedMimes);
if (!limits.allowedMimesConfigured && classification.class === "text") allowedMimes.add(mimeType);
if (!allowedMimes.has(mimeType)) {
if (shouldLogVerbose()) logVerbose(`media: file attachment skipped (unsupported mime ${mimeType}) index=${attachment.index}`);
return {
outcome: limits.allowedMimesConfigured ? {
kind: "policy-rejected",
mime: mimeType
} : {
kind: "unsupported-format",
mime: mimeType,
...selfServeLocalPath ? { localPath: selfServeLocalPath } : {}
},
filename,
mimeType
};
}
let extracted;
try {
const { allowedMimesConfigured: _allowedMimesConfigured, ...baseLimits } = limits;
extracted = await extractFileContentFromBuffer({
buffer: Buffer.from(bufferResult.buffer),
filename: bufferResult.fileName,
limits: {
...baseLimits,
allowedMimes
},
config: cfg,
classification
});
} catch (err) {
if (shouldLogVerbose()) logVerbose(`media: file attachment skipped (extract): ${String(err)}`);
return {
outcome: { kind: "read-failure" },
filename,
mimeType
};
}
const text = extracted?.text?.trim() ?? "";
const extractedImages = extracted?.images ?? [];
if (text) return {
outcome: {
kind: "extracted",
text,
images: extractedImages
},
filename,
mimeType
};
if (extractedImages.length > 0) return {
outcome: {
kind: "rendered-to-images",
images: extractedImages
},
filename,
mimeType
};
return {
outcome: { kind: "no-extractable-text" },
filename,
mimeType
};
}
async function extractFileContext(params) {
const { attachments, cache, cfg, limits, skipAttachmentIndexes } = params;
if (!attachments || attachments.length === 0) return {
blocks: [],
images: [],
localPathSelfServeUpgrades: []
};
const blocks = [];
const images = [];
const localPathSelfServeUpgrades = [];
for (const attachment of attachments) {
if (!attachment) continue;
const { outcome, filename, mimeType } = await classifyFileAttachment({
attachment,
cache,
cfg,
limits,
skipAttachmentIndexes
});
if (outcome.kind === "extracted" || outcome.kind === "rendered-to-images") images.push(...outcome.images.map((image) => ({
...image,
attachmentIndex: attachment.index
})));
const blockText = renderFileAttachmentOutcome(outcome, { selfServeLocalPath: params.selfServePathsEnabled ? void 0 : false });
if (blockText === null) continue;
const renderBlock = (content) => renderFileContextBlock({
filename,
fallbackName: `file-${attachment.index + 1}`,
mimeType,
content
});
const text = renderBlock(blockText);
blocks.push({
text,
consumesMarkerBudget: isSkippedFileOutcome(outcome)
});
if (outcome.kind === "unsupported-format" && outcome.localPath) {
const fallback = renderFileAttachmentOutcome(outcome, { selfServeLocalPath: false });
const selfServe = renderFileAttachmentOutcome(outcome);
if (fallback && selfServe) localPathSelfServeUpgrades.push({
attachmentIndex: attachment.index,
fallback: renderBlock(fallback),
render: (path) => {
const rendered = renderFileAttachmentOutcome(outcome, path ? { selfServeLocalPath: path } : void 0);
return rendered ? renderBlock(rendered) : void 0;
}
});
}
}
return {
blocks,
images,
localPathSelfServeUpgrades
};
}
const SELF_SERVE_CONTEXT_FIELDS = [
"Body",
"BodyForAgent",
"agentText"
];
function enableLocalPathSelfServe(upgrades, contexts, stagedPaths) {
for (const context of contexts) for (const upgrade of upgrades) {
const stagedPath = stagedPaths?.get(upgrade.attachmentIndex);
if (stagedPaths && !stagedPath) continue;
const selfServe = upgrade.render(stagedPath);
if (!selfServe) continue;
for (const field of SELF_SERVE_CONTEXT_FIELDS) {
const value = context[field];
if (typeof value === "string") context[field] = value.replace(upgrade.fallback, selfServe);
}
}
}
function renderMediaAttachmentMarkers(params) {
const handledIndexes = new Set(params.outputs.map((output) => output.attachmentIndex));
const decisions = new Map(params.decisions.map((decision) => [decision.capability, decision]));
return params.attachments.flatMap((attachment) => {
const capability = resolveAttachmentKind(attachment);
if (capability !== "image" && capability !== "audio" && capability !== "video") return [];
if (capability === "image" && params.deliveredImageIndexes?.has(attachment.index)) return [];
const decision = decisions.get(capability);
if (!decision || handledIndexes.has(attachment.index)) return [];
const disposition = decision.attachmentDispositions?.[attachment.index];
if (capability === "image" && decision.nativeVisionActive !== false && disposition?.kind !== "failed") return [];
const text = disposition ? renderMediaAttachmentDisposition(capability, disposition) : null;
return text ? [{
text,
consumesMarkerBudget: true
}] : [];
});
}
function applyAttachmentMarkerBudget(blocks) {
const rendered = [];
let markers = 0;
let overflow = 0;
for (const block of blocks) {
if (block.consumesMarkerBudget && markers >= 5) {
overflow += 1;
continue;
}
markers += Number(block.consumesMarkerBudget);
rendered.push(block.text);
}
return overflow > 0 ? [...rendered, renderSkippedFileOverflowSummary(overflow)] : rendered;
}
async function applyMediaUnderstanding(params) {
const { ctx, cfg } = params;
const originalUserText = [
ctx.CommandBody,
ctx.RawBody,
ctx.Body
].map((value) => normalizeOptionalString(value)).find((value) => value && value.trim()) ?? void 0;
const attachments = normalizeMediaAttachments(ctx);
const providerRegistry = buildProviderRegistry(params.providers, cfg);
const cache = createMediaAttachmentCache(attachments, {
localPathRoots: resolveMediaAttachmentLocalRoots({
cfg,
ctx,
workspaceDir: params.workspaceDir
}),
ssrfPolicy: cfg.tools?.web?.fetch?.ssrfPolicy,
workspaceDir: params.workspaceDir
});
try {
const results = await pMap(params.processingMode === "audio-only" ? AUDIO_ONLY_CAPABILITY_ORDER : CAPABILITY_ORDER, async (capability) => await runMediaCapability({
capability,
cfg,
ctx,
attachments: cache,
media: attachments,
agentId: params.agentId,
agentDir: params.agentDir,
workspaceDir: params.workspaceDir,
providerRegistry,
config: cfg.tools?.media?.[capability],
activeModel: params.activeModel
}), {
concurrency: resolveConcurrency(cfg),
stopOnError: false
});
const outputs = [];
const decisions = [];
const audioAttachmentIndexes = /* @__PURE__ */ new Set();
for (const entry of results) {
decisions.push(entry.decision);
if (entry.decision.capability !== "audio") {
for (const output of entry.outputs) outputs.push(output);
continue;
}
const audioOutputsByAttachmentIndex = /* @__PURE__ */ new Map();
for (const output of entry.outputs) {
audioOutputsByAttachmentIndex.set(output.attachmentIndex, output);
audioAttachmentIndexes.add(output.attachmentIndex);
}
for (const attachment of entry.decision.attachments) {
const output = audioOutputsByAttachmentIndex.get(attachment.attachmentIndex);
if (output) outputs.push(output);
else if (attachment.attempts.some((attempt) => attempt.reason?.trim().startsWith("tooSmall"))) outputs.push({
kind: "audio.transcription",
attachmentIndex: attachment.attachmentIndex,
text: EMPTY_VOICE_NOTE_PLACEHOLDER,
provider: "openclaw",
model: "synthetic-empty-audio"
});
}
}
if (decisions.length > 0) ctx.MediaUnderstandingDecisions = [...ctx.MediaUnderstandingDecisions ?? [], ...decisions];
if (outputs.length > 0) {
const audioOutputs = outputs.filter((output) => output.kind === "audio.transcription");
if (audioOutputs.length > 0) {
const transcript = formatAudioTranscripts(audioOutputs);
ctx.Transcript = transcript;
if (originalUserText) {
ctx.CommandBody = originalUserText;
ctx.RawBody = originalUserText;
} else {
ctx.CommandBody = transcript;
ctx.RawBody = transcript;
}
const audioCfg = cfg.tools?.media?.audio;
if (audioCfg?.echoTranscript && transcript) await sendTranscriptEcho({
ctx,
cfg,
transcript,
format: audioCfg.echoFormat ?? "📝 \"{transcript}\""
});
} else if (originalUserText) {
ctx.CommandBody = originalUserText;
ctx.RawBody = originalUserText;
}
ctx.MediaUnderstanding = [...ctx.MediaUnderstanding ?? [], ...outputs];
}
const fileContext = params.processingMode === "audio-only" ? {
blocks: [],
images: [],
localPathSelfServeUpgrades: []
} : await extractFileContext({
attachments,
cache,
cfg,
limits: resolveFileExtractionLimits(cfg),
skipAttachmentIndexes: audioAttachmentIndexes.size > 0 ? audioAttachmentIndexes : void 0,
selfServePathsEnabled: params.selfServeLocalPaths === true
});
const mediaMarkers = renderMediaAttachmentMarkers({
attachments,
decisions,
outputs,
deliveredImageIndexes: params.deliveredImageIndexes
});
const contextBlocks = applyAttachmentMarkerBudget([...fileContext.blocks, ...mediaMarkers]);
if (outputs.length > 0 || contextBlocks.length > 0) {
const enrich = (body) => appendFileBlocks(formatMediaUnderstandingBody({
body,
outputs
}), contextBlocks);
ctx.agentText = enrich(ctx.agentText ?? ctx.BodyForAgent ?? ctx.Body);
ctx.Body = enrich(ctx.Body);
finalizeInboundContext(ctx, { forceBodyForCommands: true });
}
return {
outputs,
decisions,
extractedFileImages: fileContext.images,
appliedImage: outputs.some((output) => output.kind === "image.description"),
appliedAudio: outputs.some((output) => output.kind === "audio.transcription"),
appliedVideo: outputs.some((output) => output.kind === "video.description"),
appliedFile: fileContext.blocks.length > 0,
...fileContext.localPathSelfServeUpgrades.length > 0 ? { enableLocalPathSelfServe: (contexts, stagedPaths) => enableLocalPathSelfServe(fileContext.localPathSelfServeUpgrades, contexts, stagedPaths) } : {}
};
} finally {
await cache.cleanup();
}
}
//#endregion
export { applyMediaUnderstanding as t };