openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
264 lines (263 loc) • 11.7 kB
JavaScript
import { r as truncateUtf16Safe } from "./utf16-slice-D_ngcYKd.js";
import { L as sanitizeUntrustedFileName, t as FsSafeError } from "./fs-safe-B6pvPGnf.js";
import { _ as resolveSessionAgentId } from "./agent-scope-DbtJyKUL.js";
import { r as logVerbose } from "./globals-CTaGxEqj.js";
import { o as mediaKindFromMime } from "./constants-DUxuqQz8.js";
import { t as basenameFromAnyPath } from "./file-name-CKnWacTs.js";
import { u as mimeTypeFromFilePath } from "./mime-CVpcq9ju.js";
import { a as getReplyPayloadMetadata, i as copyReplyPayloadMetadata, n as appendReplyMediaFailures, y as setReplyPayloadMetadata } from "./reply-payload-DahKk-7n.js";
import { t as isPassThroughRemoteMediaSource } from "./media-source-url-BL9SUd7E.js";
import { r as resolveAllowedManagedMediaPath, s as resolveSandboxedMediaSource, t as assertMediaNotDataUrl } from "./sandbox-paths-hdOwYXJD.js";
import { i as toRelativeWorkspacePath, n as resolvePathFromInput } from "./path-policy-s_BvJZSk.js";
import "./reply-payload-Bfsi665c.js";
import { a as resolveSendableOutboundReplyParts } from "./reply-payload-parts-BsFA4sDv.js";
import { n as LocalMediaAccessError, t as HostReadMediaTypeError } from "./local-media-access-DXGJo-Ud.js";
import { t as resolveAgentScopedOutboundMediaAccess } from "./read-capability-Ba0RMAnC.js";
import { t as ensureSandboxWorkspaceForSession } from "./context-B_khSODz.js";
import "./sandbox-A2KOznlu.js";
import { r as resolveOutboundMediaMaxBytes } from "./configured-max-bytes-CTSUbFDK.js";
import { n as resolveOutboundAttachmentFromUrl } from "./outbound-attachment-BK-r1SfK.js";
import path from "node:path";
//#region src/auto-reply/reply/reply-media-paths.ts
const FILE_URL_RE = /^file:/i;
const WINDOWS_DRIVE_RE = /^[a-zA-Z]:[\\/]/;
const SCHEME_RE = /^[a-zA-Z][a-zA-Z0-9+.-]*:/;
const HAS_FILE_EXT_RE = /\.\w{1,10}$/;
const MAX_FAILURE_LABEL_LENGTH = 180;
function resolveReplyMediaFailureLabel(media, index) {
const trimmed = media.trim();
let source = trimmed;
if (SCHEME_RE.test(trimmed)) try {
source = new URL(trimmed).pathname;
} catch {}
const basename = basenameFromAnyPath(source).trim();
const fallback = `Attachment ${index + 1}`;
return truncateUtf16Safe(sanitizeUntrustedFileName(basename, fallback) || fallback, MAX_FAILURE_LABEL_LENGTH);
}
function resolveReplyMediaFailureKind(media) {
const kind = mediaKindFromMime(mimeTypeFromFilePath(media));
return kind === "image" || kind === "audio" || kind === "video" ? kind : "document";
}
function resolveReplyMediaFailureCode(error) {
let current = error;
for (let depth = 0; current instanceof Error && depth < 4; depth += 1) {
if (current instanceof LocalMediaAccessError && current.code === "not-found" || current instanceof FsSafeError && current.code === "not-found") return "file-not-found";
if (current instanceof HostReadMediaTypeError || current instanceof LocalMediaAccessError && current.code === "unsupported-media-type") return "unsupported-format";
current = current.cause;
}
return "delivery-failed";
}
function createReplyMediaFailure(media, index, error) {
const mimeType = mimeTypeFromFilePath(media);
return {
code: resolveReplyMediaFailureCode(error),
kind: resolveReplyMediaFailureKind(media),
label: resolveReplyMediaFailureLabel(media, index),
...mimeType ? { mimeType } : {}
};
}
function isLikelyLocalMediaSource(media) {
return FILE_URL_RE.test(media) || media.startsWith("/") || media.startsWith("./") || media.startsWith("../") || media.startsWith("~") || WINDOWS_DRIVE_RE.test(media) || media.startsWith("\\\\") || !SCHEME_RE.test(media) && (media.includes("/") || media.includes("\\") || HAS_FILE_EXT_RE.test(media));
}
function getPayloadMediaList(payload) {
return resolveSendableOutboundReplyParts(payload).mediaUrls;
}
function createReplyMediaPathNormalizer(params) {
const agentId = params.agentId ?? (params.sessionKey ? resolveSessionAgentId({
sessionKey: params.sessionKey,
config: params.cfg
}) : void 0);
const maxBytes = resolveOutboundMediaMaxBytes({
cfg: params.cfg,
channel: params.messageProvider,
accountId: params.accountId
});
const explicitSandboxRoot = params.sandboxRoot?.trim();
let sandboxWorkspacePromise = explicitSandboxRoot ? Promise.resolve({
root: explicitSandboxRoot,
containerWorkdir: params.sandboxContainerWorkdir
}) : void 0;
const persistedMediaBySource = /* @__PURE__ */ new Map();
const resolveSandboxWorkspace = async () => {
if (!sandboxWorkspacePromise) sandboxWorkspacePromise = ensureSandboxWorkspaceForSession({
config: params.cfg,
agentId,
sessionKey: params.sessionKey,
workspaceDir: params.workspaceDir
}).then((sandbox) => sandbox ? {
root: sandbox.workspaceDir,
containerWorkdir: sandbox.containerWorkdir
} : void 0);
return await sandboxWorkspacePromise;
};
const resolveMediaAccessForSource = (media) => resolveAgentScopedOutboundMediaAccess({
cfg: params.cfg,
agentId,
workspaceDir: params.workspaceDir,
mediaSources: [media],
mediaAccess: params.mediaAccess,
workspaceMediaAccess: params.workspaceMediaAccess,
sessionKey: params.sessionKey,
messageProvider: params.sessionKey ? void 0 : params.messageProvider,
accountId: params.accountId,
requesterSenderId: params.requesterSenderId,
requesterSenderName: params.requesterSenderName,
requesterSenderUsername: params.requesterSenderUsername,
requesterSenderE164: params.requesterSenderE164,
groupId: params.groupId,
groupChannel: params.groupChannel,
groupSpace: params.groupSpace
});
const persistLocalReplyMedia = async (media) => {
if (!isLikelyLocalMediaSource(media)) return { path: media };
const managedMediaPath = await resolveAllowedManagedMediaPath(media);
if (managedMediaPath) return {
path: managedMediaPath,
contentType: mimeTypeFromFilePath(managedMediaPath)
};
const cached = persistedMediaBySource.get(media);
if (cached) return await cached;
const persistPromise = resolveOutboundAttachmentFromUrl(media, maxBytes, { mediaAccess: resolveMediaAccessForSource(media) }).then((saved) => ({
...saved,
contentType: saved.contentType ?? mimeTypeFromFilePath(media) ?? "application/octet-stream"
})).catch((err) => {
persistedMediaBySource.delete(media);
throw err;
});
persistedMediaBySource.set(media, persistPromise);
return await persistPromise;
};
const resolveWorkspaceRelativeMedia = (media) => {
const relativeWorkspacePath = toRelativeWorkspacePath(params.workspaceDir, media, { cwd: params.workspaceDir });
return resolvePathFromInput(relativeWorkspacePath, params.workspaceDir);
};
const resolveAbsoluteWorkspaceMedia = (media) => {
if (FILE_URL_RE.test(media) || !path.isAbsolute(media) && !WINDOWS_DRIVE_RE.test(media)) return;
try {
return resolveWorkspaceRelativeMedia(media);
} catch {
return;
}
};
const normalizeMediaSource = async (raw) => {
const media = raw.trim();
if (!media) return {
mediaUrl: media,
trustedLocalMedia: false
};
assertMediaNotDataUrl(media);
if (isPassThroughRemoteMediaSource(media)) return {
mediaUrl: media,
trustedLocalMedia: false
};
const absoluteWorkspaceMedia = resolveAbsoluteWorkspaceMedia(media);
if (absoluteWorkspaceMedia) {
const persisted = await persistLocalReplyMedia(absoluteWorkspaceMedia);
return {
mediaUrl: persisted.path,
trustedLocalMedia: true,
fileName: path.basename(absoluteWorkspaceMedia),
...persisted.contentType ? { mimeType: persisted.contentType } : {}
};
}
const isRelativeLocalMedia = isLikelyLocalMediaSource(media) && !FILE_URL_RE.test(media) && !media.startsWith("~") && !path.isAbsolute(media) && !WINDOWS_DRIVE_RE.test(media);
const sandboxWorkspace = await resolveSandboxWorkspace();
if (sandboxWorkspace) {
let sandboxResolvedMedia;
try {
sandboxResolvedMedia = await resolveSandboxedMediaSource({
media,
sandboxRoot: sandboxWorkspace.root,
containerWorkdir: sandboxWorkspace.containerWorkdir
});
} catch (err) {
if (FILE_URL_RE.test(media)) throw new Error("Host-local MEDIA file URLs are blocked in normal replies. Use a safe path or the message tool.", { cause: err });
throw err;
}
const persisted = await persistLocalReplyMedia(sandboxResolvedMedia);
return {
mediaUrl: persisted.path,
trustedLocalMedia: true,
fileName: path.basename(sandboxResolvedMedia),
...persisted.contentType ? { mimeType: persisted.contentType } : {}
};
}
if (isRelativeLocalMedia) {
const workspaceMedia = resolveWorkspaceRelativeMedia(media);
const persisted = await persistLocalReplyMedia(workspaceMedia);
return {
mediaUrl: persisted.path,
trustedLocalMedia: true,
fileName: path.basename(workspaceMedia),
...persisted.contentType ? { mimeType: persisted.contentType } : {}
};
}
if (!isLikelyLocalMediaSource(media)) return {
mediaUrl: media,
trustedLocalMedia: false
};
if (FILE_URL_RE.test(media)) throw new Error("Host-local MEDIA file URLs are blocked in normal replies. Use a safe path or the message tool.");
const persisted = await persistLocalReplyMedia(media);
return {
mediaUrl: persisted.path,
trustedLocalMedia: true,
fileName: path.basename(media),
...persisted.contentType ? { mimeType: persisted.contentType } : {}
};
};
return async (payload) => {
const mediaList = getPayloadMediaList(payload);
if (mediaList.length === 0) return payload;
const normalizedMedia = [];
const normalizedAttachments = [];
const seen = /* @__PURE__ */ new Set();
let hasTrustedLocalMedia = payload.trustedLocalMedia === true;
const mediaFailures = [];
for (const [mediaIndex, media] of mediaList.entries()) {
let normalized;
try {
normalized = await normalizeMediaSource(media);
} catch (err) {
mediaFailures.push(createReplyMediaFailure(media, mediaIndex, err));
logVerbose(`dropping blocked reply media ${media}: ${String(err)}`);
continue;
}
if (!normalized.mediaUrl || seen.has(normalized.mediaUrl)) continue;
seen.add(normalized.mediaUrl);
normalizedMedia.push(normalized.mediaUrl);
hasTrustedLocalMedia ||= normalized.trustedLocalMedia;
const existingAttachment = payload.attachments?.[mediaIndex] ?? {};
normalizedAttachments.push({
...existingAttachment,
...normalized.fileName && !existingAttachment.name ? { name: normalized.fileName } : {},
...normalized.mimeType && !existingAttachment.mimeType ? { mimeType: normalized.mimeType } : {},
...normalized.trustedLocalMedia ? { trustedLocalMedia: true } : {}
});
}
const text = appendReplyMediaFailures(payload.text, mediaFailures);
const assistantMediaFailures = [...getReplyPayloadMetadata(payload)?.assistantMediaFailures ?? [], ...mediaFailures];
if (normalizedMedia.length === 0) {
const normalized = copyReplyPayloadMetadata(payload, {
...payload,
text,
mediaUrl: void 0,
mediaUrls: void 0
});
return mediaFailures.length === 0 ? normalized : setReplyPayloadMetadata(normalized, { assistantMediaFailures });
}
const normalized = copyReplyPayloadMetadata(payload, {
...payload,
text,
mediaUrl: normalizedMedia[0],
mediaUrls: normalizedMedia,
...normalizedAttachments.some((attachment) => Object.keys(attachment).length > 0) ? { attachments: normalizedAttachments } : {},
...hasTrustedLocalMedia ? { trustedLocalMedia: true } : {}
});
return mediaFailures.length === 0 ? normalized : setReplyPayloadMetadata(normalized, { assistantMediaFailures });
};
}
function createReplyMediaContext(params) {
return { normalizePayload: createReplyMediaPathNormalizer(params) };
}
//#endregion
export { createReplyMediaPathNormalizer as n, createReplyMediaContext as t };