import path from "node:path"; import type { ImageContent } from "@mariozechner/pi-ai"; import { formatErrorMessage } from "../../../infra/errors.js"; import { assertNoWindowsNetworkPath, safeFileURLToPath } from "../../../infra/local-file-access.js"; import type { PromptImageOrderEntry } from "../../../media/prompt-image-order.js"; import { loadWebMedia } from "../../../media/web-media.js"; import { normalizeLowercaseStringOrEmpty } from "../../../shared/string-coerce.js"; import { resolveUserPath } from "../../../utils.js"; import type { ImageSanitizationLimits } from "../../image-sanitization.js"; import {
createSandboxBridgeReadFile,
resolveSandboxedBridgeMediaPath,
} from "../../sandbox-media-paths.js"; import type { SandboxFsBridge } from "../../sandbox/fs-bridge.js"; import { sanitizeImageBlocks } from "../../tool-images.js"; import { log } from "../logger.js";
/** *Resultofdetectinganimagereferenceintext.
*/
export interface DetectedImageRef { /** The raw matched string from the prompt */
raw: string; /** The type of reference */
type: "path" | "media-uri"; /** The resolved/normalized path, or the raw media URI for media-uri type */
resolved: string;
}
// Pattern for [media attached: path (type) | url] or [media attached N/M: path (type) | url] format // Each bracket = ONE file. The | separates path from URL, not multiple files. // Multi-file format uses separate brackets on separate lines.
MEDIA_ATTACHED_PATTERN.lastIndex = 0;
MESSAGE_IMAGE_PATTERN.lastIndex = 0;
FILE_URL_PATTERN.lastIndex = 0;
PATH_PATTERN.lastIndex = 0;
let match: RegExpExecArray | null; while ((match = MEDIA_ATTACHED_PATTERN.exec(prompt)) !== null) { const content = match[1];
// Skip "[media attached: N files]" header lines if (/^\d+\s+files?$/i.test(content.trim())) { continue;
}
// Check for a Gateway claim-check URI first (media://inbound/<id>). // This must be tested before the extension-based path regex because the // URI has no file extension suffix in its base form. const mediaUriMatch = content.match(MEDIA_URI_REGEX); if (mediaUriMatch) { const uri = `media://inbound/${mediaUriMatch[1]}`; const dedupeKey = normalizeRefForDedupe(uri); if (!seen.has(dedupeKey)) {
seen.add(dedupeKey);
refs.push({ raw: uri, type: "media-uri", resolved: uri });
} continue;
}
// Extract path before the (mime/type) or | delimiter // Format is: path (type) | url OR just: path (type) // Path may contain spaces (e.g., "ChatGPT Image Apr 21.png") // Use non-greedy .+? to stop at first image extension const pathMatch = content.match(MEDIA_ATTACHED_PATH_PATTERN); if (pathMatch?.[1]) {
addPathRef(pathMatch[1].trim());
}
}
// Pattern for [Image: source: /path/...] format from messaging systems while ((match = MESSAGE_IMAGE_PATTERN.exec(prompt)) !== null) { const raw = match[1]?.trim(); if (raw) {
addPathRef(raw);
}
}
// Remote HTTP(S) URLs are intentionally ignored. Native image injection is local-only.
// Pattern for file:// URLs - treat as paths since loadWebMedia handles them while ((match = FILE_URL_PATTERN.exec(prompt)) !== null) { const raw = match[0]; const dedupeKey = normalizeRefForDedupe(raw); if (seen.has(dedupeKey)) { continue;
}
seen.add(dedupeKey); // Use fileURLToPath for proper handling (e.g., file://localhost/path) try { const resolved = safeFileURLToPath(raw);
refs.push({ raw, type: "path", resolved });
} catch { // Skip malformed file:// URLs
}
}
// Pattern for file paths (absolute, relative, or home) // Matches: // - /absolute/path/to/file.ext (including paths with special chars like Messages/Attachments) // - ./relative/path.ext // - ../parent/path.ext // - ~/home/path.ext while ((match = PATH_PATTERN.exec(prompt)) !== null) { // Use capture group 1 (the path without delimiter prefix); skip if undefined if (match[1]) {
addPathRef(match[1]);
}
}
if (media.kind !== "image") {
log.debug(`Native image: not an image file: ${targetPath} (got ${media.kind})`); returnnull;
}
// EXIF orientation is already normalized by loadWebMedia -> resizeToJpeg // Default to JPEG since optimization converts images to JPEG format const mimeType = media.contentType ?? "image/jpeg"; const data = media.buffer.toString("base64");
return { type: "image", data, mimeType };
} catch (err) { // Log the actual error for debugging (size limits, network failures, etc.)
log.debug(`Native image: failed to load ${ref.resolved}: ${formatErrorMessage(err)}`); returnnull;
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.