/** * imageReader — resolves an image `source_uri` (file:// or http(s)://) into a * bounded base64 data URI for the VLM tier (tier V, Qwen3-VL). * * The whole point of the bridge: raw image bytes go LOCAL (bridge → oMLX over * HTTP) and NEVER traverse the frontier. This resolver does the fetch + cap + * downscale so the MCP tool handlers only ever hand a bounded data URI to the * vision backend. See docs/scope-memos/v0.8.0-multimodal-2026-06-16.md §4. * * Security: * - file:// reads are UNRESTRICTED — same trust model as the text * `sourceReader` (the bridge runs with the user's own FS access; the host * app's trust boundary covers this). This intentionally deviates from the * scope memo's F4 "confine file:// to workspace": confining only images * while text stays unrestricted would be inconsistent, and the original * "host trust boundary covers this" rationale applies equally to images. * - http(s):// reuses sourceReader's SSRF host policy (`assertHostAllowed`) * + timeout + byte cap. * - MIME allowlist: png / jpeg / webp. PDF is rejected with a "deferred" * message (multimodal v0.8.0 is image-only; PDF gets its own memo). * * Downscaling: macOS `sips -Z ` (zero new dependency — the bridge is * Apple-Silicon-only). Bounds prefill tokens + base64 size, keeps calls under * the 60 s MCP wall and the oMLX prefill memory guard (which rejects an image * needing > ~2.3 GB peak — see §0.6). `sips -Z` only shrinks (never upscales), * so it is a no-op on already-small images. If `sips` is unavailable or fails, * the original bytes are used (logged) rather than failing the call. * * Environment variables: * OMCP_IMAGE_MAX_EDGE — downscale target for the longest edge (default 1568) * (input byte cap + timeout reuse OMCP_URL_MAX_BYTES / OMCP_URL_TIMEOUT_MS) */ import { type ReadSourceOptions } from './sourceReader.js'; export interface ImageSourceResult { /** `data:image/;base64,<…>` ready to drop into a chat `image_url`. */ dataUri: string; /** Resolved MIME type: image/png, image/jpeg, or image/webp. */ mimeType: string; /** Final byte size after any downscale. */ bytes: number; /** Byte size before downscale (== bytes when no downscale happened). */ originalBytes: number; /** True when `sips` actually shrank the image. */ downscaled: boolean; } /** Resolve the longest-edge target from env, with the documented default. */ export declare function imageMaxEdgeFromEnv(): number; /** Magic-byte MIME sniff for the supported image types. Returns null if none match. */ export declare function sniffImageMime(buf: Uint8Array): string | null; /** * Classify raw bytes as a supported image, or throw a descriptive error. * `headerCt` (the http content-type, when available) is advisory only — the * magic-byte sniff is authoritative. */ export declare function resolveImageMime(buf: Uint8Array): string; /** * Fetch + cap + downscale an image source_uri into a base64 data URI. * Throws a descriptive Error on any failure (bad scheme, SSRF blocked, size * exceeded, unsupported format, PDF); the caller surfaces it as isError: true. */ export declare function readImageSource(uri: string, opts: ReadSourceOptions, maxEdge?: number): Promise; //# sourceMappingURL=imageReader.d.ts.map