Lossless serialisation of Omni structs to and from JSON-safe maps.
Use this when persisting messages, content blocks, or usage records to a
storage layer that speaks JSON (Ecto :map columns, document stores, the
wire). encode/1 produces a plain map (or list of maps) with string keys
and only JSON-compatible values; decode/1 reverses it, returning the
original structs.
The encoded shape is self-describing — every encoded value carries a
"__type" discriminator so decode/1 can dispatch without external
context.
Supported types
Omni.MessageOmni.Content.Text,Omni.Content.Thinking,Omni.Content.Attachment,Omni.Content.ToolUse,Omni.Content.ToolResultOmni.Usage
Lists of any combination of the above are also supported.
Opaque fields
Message.private and Attachment.meta can hold arbitrary terms (atoms,
tuples, structs from provider-specific data) that have no portable JSON
representation. These are encoded as opaque base64-encoded ETF blobs
(%{"__etf" => "..."}) and decoded with the :safe option, which refuses
to create new atoms or load resources.
Encoding arbitrary terms
encode_term/1 and decode_term/1 expose the same opaque-blob mechanism
for any Erlang term. Use them in persistence layers that need to stash
values outside the Omni struct family while keeping the same JSON-safe
shape.
Examples
iex> message = Omni.Message.new("hello")
iex> encoded = Omni.Codec.encode(message)
iex> {:ok, ^message} = Omni.Codec.decode(encoded)
Summary
Functions
Decodes a previously-encoded map (or list of maps) back into Omni structs.
Decodes a wrapper produced by encode_term/1 back into the original term.
Encodes an Omni struct (or a list of structs) to a JSON-safe map.
Encodes an arbitrary Erlang term as an opaque JSON-safe wrapper.
Types
@type encodable() :: Omni.Message.t() | Omni.Content.Text.t() | Omni.Content.Thinking.t() | Omni.Content.Attachment.t() | Omni.Content.ToolUse.t() | Omni.Content.ToolResult.t() | Omni.Usage.t()
Any value the codec can encode.
@type error() :: :invalid_input | {:unknown_type, String.t()} | {:invalid_role, term()} | {:missing_field, atom()} | {:invalid_source, term()} | {:invalid_timestamp, term()} | {:invalid_etf, term()}
Decode error reasons.
Functions
Decodes a previously-encoded map (or list of maps) back into Omni structs.
Returns {:ok, struct} or {:ok, [struct, ...]} on success. Returns
{:error, reason} if the input is malformed, has an unknown "__type",
or fails field-level validation. For lists, decoding stops on the first
failing element.
Decodes a wrapper produced by encode_term/1 back into the original term.
Uses :erlang.binary_to_term/2 with the :safe option, so unknown atoms
are not created and code is not loaded from the binary.
Encodes an Omni struct (or a list of structs) to a JSON-safe map.
Always succeeds for valid input types. Lists are encoded element-wise and returned as a bare list (no envelope).
Encodes an arbitrary Erlang term as an opaque JSON-safe wrapper.
The term is serialised via :erlang.term_to_binary/1 and base64-encoded
inside a %{"__etf" => "..."} map. Use this for values that have no
portable JSON representation (atom-keyed maps, tuples, structs) when you
need to stash them in a JSON-typed storage column.
iex> wrapper = Omni.Codec.encode_term({:ok, %{a: 1}})
iex> {:ok, {:ok, %{a: 1}}} = Omni.Codec.decode_term(wrapper)