ALLM.Image (allm v0.3.0)

Copy Markdown View Source

A serializable image value used by image generation, editing, and vision inputs. See spec §35.2.1.

Layer A — pure data. The :source is one of four tagged tuples and is the only required field. All other fields are nilable to support both inputs (vision: only :source/:mime_type) and outputs (generated: :prompt/:revised_prompt/:width/:height populated by the adapter).

Source variants

  • {:binary, bytes} — raw image bytes; an explicit :mime_type MUST be supplied via from_binary/2.
  • {:base64, encoded} — standard base64-encoded bytes (with padding) plus explicit :mime_type. Constructed via from_base64/2 — pure data, no decode validation. Callers passing URL-safe (-/_) or unpadded base64 will round-trip cleanly through ETF / JSON but to_data_uri/1's fast-path (which forwards the encoded string verbatim) may emit a data: URI a consumer rejects.
  • {:url, url} — public HTTP/HTTPS URL. from_url/1 does NOT inspect or fetch the URL; :mime_type is nil and the adapter resolves it. URLs are NEVER fetched in Layer A — see to_binary/1 and to_data_uri/1 for the :remote_source error.
  • {:file, path} — local filesystem path. from_file/1 does NOT call File.read/1; only the path is stored. MIME type is inferred from the extension (lowercase) and may be nil when the extension is missing or unknown. Filesystem I/O happens only in to_binary/1/to_data_uri/1.

Serializability

ETF round-trip via :erlang.term_to_binary/1 preserves every legal source shape verbatim. JSON round-trip via ALLM.Serializer represents :source as %{"type" => "<kind>", "value" => <value>}; the {:binary, _} variant Base64-encodes its bytes on the wire so JSON stays text-safe. Decoding dispatches on data["source"]["type"] against the closed set ~w[binary base64 url file]; an unknown type falls through to the {:_unknown, :atom_decode_failed} field-error path. A "binary" source whose "value" is not valid base64 surfaces as {[:source], :invalid_base64} (raised pre-emptively as a ValidationError so the field-error survives Serializer.from_json/1's ArgumentError rescue).

Summary

Functions

Build an %Image{} from a base64-encoded string plus an explicit MIME type.

Build an %Image{} from raw bytes plus an explicit MIME type.

Build an %Image{} from a local filesystem path.

Build an %Image{} from a URL string.

Resolve the image's :source to raw bytes.

Resolve the image to a data:<mime>;base64,<...> URI.

Types

source()

@type source() ::
  {:binary, binary()}
  | {:base64, String.t()}
  | {:url, String.t()}
  | {:file, Path.t()}

t()

@type t() :: %ALLM.Image{
  height: non_neg_integer() | nil,
  metadata: map(),
  mime_type: String.t() | nil,
  prompt: String.t() | nil,
  revised_prompt: String.t() | nil,
  source: source(),
  width: non_neg_integer() | nil
}

Functions

from_base64(encoded, mime_type)

@spec from_base64(String.t(), String.t()) :: t()

Build an %Image{} from a base64-encoded string plus an explicit MIME type.

Pure data — does NOT validate that encoded is well-formed base64. Standard base64 with padding is the documented contract; URL-safe base64 (-/_) or unpadded variants may produce a struct whose to_data_uri/1 fast-path emits a data: URI a downstream consumer rejects. mime_type: nil raises FunctionClauseError.

Examples

iex> img = ALLM.Image.from_base64("aGk=", "image/png")
iex> img.source
{:base64, "aGk="}
iex> img.mime_type
"image/png"

from_binary(bytes, mime_type)

@spec from_binary(binary(), String.t()) :: t()

Build an %Image{} from raw bytes plus an explicit MIME type.

Both arguments are required and must be binaries — mime_type: nil raises FunctionClauseError (the is_binary(mime_type) runtime guard backs the @spec so callers don't get silently-nil-mime structs).

Examples

iex> img = ALLM.Image.from_binary(<<137, 80>>, "image/png")
iex> img.source
{:binary, <<137, 80>>}
iex> img.mime_type
"image/png"

from_file(path)

@spec from_file(Path.t()) :: t()

Build an %Image{} from a local filesystem path.

Pure data — does NOT call File.read/1. The path is stored verbatim and the filesystem is only touched at to_binary/1 / to_data_uri/1 time. The :mime_type is inferred from the lowercased extension against the closed set [".png", ".jpg", ".jpeg", ".webp", ".gif"]; unknown or missing extensions leave :mime_type as nil.

Examples

iex> img = ALLM.Image.from_file("/tmp/cat.png")
iex> img.source
{:file, "/tmp/cat.png"}
iex> img.mime_type
"image/png"

iex> ALLM.Image.from_file("/tmp/noext").mime_type
nil

from_url(url)

@spec from_url(String.t()) :: t()

Build an %Image{} from a URL string.

Pure data — does NOT inspect or fetch the URL. The :mime_type is nil; the adapter that consumes the image resolves the type at request-build time. URL fetching is banned in Layer A (Decision #2 / phasing principle #8).

Examples

iex> img = ALLM.Image.from_url("https://example.com/x.png")
iex> img.source
{:url, "https://example.com/x.png"}
iex> img.mime_type
nil

to_binary(image)

@spec to_binary(t()) ::
  {:ok, binary()} | {:error, :remote_source | :invalid_base64 | File.posix()}

Resolve the image's :source to raw bytes.

  • {:binary, b} returns {:ok, b} verbatim.
  • {:base64, s} decodes via Base.decode64/1; invalid base64 returns {:error, :invalid_base64}.
  • {:url, _} returns {:error, :remote_source} — Layer A NEVER fetches URLs (Decision #2). Adapters fetch at request-build time.
  • {:file, path} reads the file via File.read/1 and returns its {:ok, _} | {:error, posix()} shape directly (:enoent on missing, etc.).

Examples

iex> ALLM.Image.to_binary(ALLM.Image.from_binary(<<1, 2>>, "image/png"))
{:ok, <<1, 2>>}

iex> ALLM.Image.to_binary(ALLM.Image.from_base64("aGVsbG8=", "image/png"))
{:ok, "hello"}

iex> ALLM.Image.to_binary(ALLM.Image.from_url("https://example.com/x.png"))
{:error, :remote_source}

to_data_uri(image)

@spec to_data_uri(t()) ::
  {:ok, String.t()}
  | {:error,
     :remote_source | :missing_mime_type | :invalid_base64 | File.posix()}

Resolve the image to a data:<mime>;base64,<...> URI.

  • {:binary, b} with a :mime_type Base64-encodes the bytes.
  • {:base64, s} with a :mime_type forwards s verbatim — fast path, no decode-then-re-encode.
  • {:file, path} with a :mime_type reads + Base64-encodes.
  • {:url, _} returns {:error, :remote_source} (Decision #6) — a data: URI and an https: URI are different addressing schemes; passing the URL through verbatim would surprise.
  • Missing :mime_type returns {:error, :missing_mime_type} — there is no default like application/octet-stream.

Examples

iex> ALLM.Image.to_data_uri(ALLM.Image.from_binary("hi", "image/png"))
{:ok, "data:image/png;base64,aGk="}

iex> ALLM.Image.to_data_uri(ALLM.Image.from_base64("aGk=", "image/png"))
{:ok, "data:image/png;base64,aGk="}

iex> ALLM.Image.to_data_uri(ALLM.Image.from_url("https://example.com/x.png"))
{:error, :remote_source}