UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

264 lines (263 loc) 11.7 kB
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 };