User-message image attachments.
Dropping a file onto the terminal pastes its PATH into the input (the terminal's drop behavior — same mechanism pi relies on). At turn start the engine scans the user message for path-shaped tokens that resolve to real image files, reads them, and attaches them to the initial user message as multimodal content blocks. Channel-neutral: TUI, web, and Telegram all get the same behavior because the scan runs in the engine, not the channel.
Only files the model can genuinely consume are attached: the MIME type
is sniffed from magic bytes (pi-parity: jpeg / non-animated png / gif /
webp / bmp), never trusted from the extension alone. Files over
max-image-bytes are skipped with a reason the prompt assembler
surfaces to the model (providers downscale server-side; vis does no
client-side resizing — AWT/ImageIO is unavailable under GraalVM
native-image on macOS).
User-message image attachments. Dropping a file onto the terminal pastes its PATH into the input (the terminal's drop behavior — same mechanism pi relies on). At turn start the engine scans the user message for path-shaped tokens that resolve to real image files, reads them, and attaches them to the initial user message as multimodal content blocks. Channel-neutral: TUI, web, and Telegram all get the same behavior because the scan runs in the engine, not the channel. Only files the model can genuinely consume are attached: the MIME type is sniffed from magic bytes (pi-parity: jpeg / non-animated png / gif / webp / bmp), never trusted from the extension alone. Files over `max-image-bytes` are skipped with a reason the prompt assembler surfaces to the model (providers downscale server-side; vis does no client-side resizing — AWT/ImageIO is unavailable under GraalVM native-image on macOS).
(collect-user-images text)(collect-user-images text
{:keys [workspace-root max-bytes max-images]
:or {max-bytes max-image-bytes
max-images max-image-count}})Scan text (one user message) for paths of readable image files and
load them as attachments.
Options:
:workspace-root - base for relative candidates (default: cwd).
:max-bytes - per-image cap (default max-image-bytes).
:max-images - attachment count cap (default max-image-count).
Returns {:attached [{:path :media-type :base64 :size :size-label}] :skipped [{:path :reason}]} — :skipped names sniffed
image files that were rejected (size cap / count cap) so the prompt
assembler can tell the model WHY an image it can see referenced is
absent. Non-image or non-existent candidates are silently ignored.
Never throws — a failure to read one file skips that file.
Scan `text` (one user message) for paths of readable image files and
load them as attachments.
Options:
:workspace-root - base for relative candidates (default: cwd).
:max-bytes - per-image cap (default [[max-image-bytes]]).
:max-images - attachment count cap (default [[max-image-count]]).
Returns `{:attached [{:path :media-type :base64 :size :size-label}]
:skipped [{:path :reason}]}` — `:skipped` names sniffed
image files that were rejected (size cap / count cap) so the prompt
assembler can tell the model WHY an image it can see referenced is
absent. Non-image or non-existent candidates are silently ignored.
Never throws — a failure to read one file skips that file.(detect-image-mime b)Sniff a supported image MIME type from the leading bytes of a file. Returns "image/png" | "image/jpeg" | "image/gif" | "image/webp" | "image/bmp", or nil when the bytes are not a supported still image. Animated PNGs and JPEG-LS return nil (provider-rejected shapes).
Sniff a supported image MIME type from the leading bytes of a file. Returns "image/png" | "image/jpeg" | "image/gif" | "image/webp" | "image/bmp", or nil when the bytes are not a supported still image. Animated PNGs and JPEG-LS return nil (provider-rejected shapes).
Per-image byte cap. Anthropic's API limit is 5MB/image; OpenAI allows more, but the smallest common bound keeps one attachment valid on every wire.
Per-image byte cap. Anthropic's API limit is 5MB/image; OpenAI allows more, but the smallest common bound keeps one attachment valid on every wire.
Attachment count cap per user message. Guards against a pathological message (e.g. a pasted directory listing) ballooning the request.
Attachment count cap per user message. Guards against a pathological message (e.g. a pasted directory listing) ballooning the request.
(prepare-inline-attachments attachments)(prepare-inline-attachments attachments
{:keys [max-bytes max-images]
:or {max-bytes max-image-bytes
max-images max-image-count}})Validate already-encoded image attachments delivered INLINE (web/API upload)
rather than as filesystem paths. Each entry is {:base64 :filename :media-type?};
the base64 may be a bare payload or a data:...;base64, URL. Decodes each,
sniffs the MIME from magic bytes (the declared :media-type is NEVER trusted),
enforces the same per-image and count caps as collect-user-images, and
returns the same {:attached [...] :skipped [...]} shape so the assemble seam
treats disk-scanned and inline images uniformly. Never throws.
Validate already-encoded image attachments delivered INLINE (web/API upload)
rather than as filesystem paths. Each entry is `{:base64 :filename :media-type?}`;
the base64 may be a bare payload or a `data:...;base64,` URL. Decodes each,
sniffs the MIME from magic bytes (the declared `:media-type` is NEVER trusted),
enforces the same per-image and count caps as [[collect-user-images]], and
returns the same `{:attached [...] :skipped [...]}` shape so the assemble seam
treats disk-scanned and inline images uniformly. Never throws.(scan-image-descriptors text)(scan-image-descriptors text {:keys [workspace-root]})Resolve every image the user text points at, WITHOUT loading pixel bytes.
Returns [{:path :media-type :size :size-label :filename}] for files whose
magic bytes sniff to a supported still image — ordered, de-duped. Cheap
enough to run on every paste (only a small file-head read per candidate).
Never throws.
Resolve every image the user text points at, WITHOUT loading pixel bytes.
Returns `[{:path :media-type :size :size-label :filename}]` for files whose
magic bytes sniff to a supported still image — ordered, de-duped. Cheap
enough to run on every paste (only a small file-head read per candidate).
Never throws.cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |