openclaw/src/media/parse.ts

65 lines
1.8 KiB
TypeScript

// Shared helpers for parsing MEDIA tokens from command/stdout text.
export const MEDIA_LINE_RE = /\bMEDIA:/i;
// Allow optional wrapping backticks and punctuation after the token; capture the core token.
export const MEDIA_TOKEN_RE = /\bMEDIA:\s*`?([^\s`]+)`?/i;
export function normalizeMediaSource(src: string) {
if (src.startsWith("file://")) return src.replace("file://", "");
return src;
}
export function splitMediaFromOutput(raw: string): {
text: string;
mediaUrl?: string;
} {
const trimmedRaw = raw.trim();
let text = trimmedRaw;
let mediaUrl: string | undefined;
let mediaLine = trimmedRaw.split("\n").find((line) => MEDIA_LINE_RE.test(line));
let mediaMatch = mediaLine?.match(MEDIA_TOKEN_RE) ?? trimmedRaw.match(MEDIA_TOKEN_RE);
if (!mediaMatch) {
return { text: trimmedRaw };
}
if (!mediaLine && mediaMatch) {
mediaLine = mediaMatch[0];
}
let isValidMedia = false;
if (mediaMatch?.[1]) {
const cleaned = mediaMatch[1]
.replace(/^[`"'[{(]+/, "")
.replace(/[`"'\\})\],]+$/, "");
const candidate = normalizeMediaSource(cleaned);
const looksLikeUrl = /^https?:\/\//i.test(candidate);
const looksLikePath = candidate.startsWith("/") || candidate.startsWith("./");
const hasWhitespace = /\s/.test(candidate);
isValidMedia =
!hasWhitespace && candidate.length <= 1024 && (looksLikeUrl || looksLikePath);
if (isValidMedia) {
mediaUrl = candidate;
}
}
if (isValidMedia && mediaMatch?.[0]) {
text = trimmedRaw
.replace(mediaMatch[0], "")
.replace(/[ \t]{2,}/g, " ")
.replace(/[ \t]+\n/g, "\n")
.replace(/\n{2,}/g, "\n")
.trim();
} else {
text = trimmedRaw
.split("\n")
.filter((line) => line !== mediaLine)
.join("\n")
.replace(/[ \t]{2,}/g, " ")
.replace(/[ \t]+\n/g, "\n")
.replace(/\n{2,}/g, "\n")
.trim();
}
return { text, mediaUrl };
}