Packages

Elixir implementation of the Agent-to-Agent (A2A) protocol. Exposes ADK agents as A2A-compatible HTTP endpoints and consumes remote A2A agents as local ADK agents.

Current section

Files

Jump to
a2a_elixir_sdk lib a2a_ex part.ex
Raw

lib/a2a_ex/part.ex

defmodule A2AEx.FileBytes do
@moduledoc """
File content provided as a base64-encoded string.
"""
@type t :: %__MODULE__{
bytes: String.t(),
name: String.t() | nil,
mime_type: String.t() | nil
}
@enforce_keys [:bytes]
defstruct [:bytes, :name, :mime_type]
end
defmodule A2AEx.FileURI do
@moduledoc """
File content located at a URI.
"""
@type t :: %__MODULE__{
uri: String.t(),
name: String.t() | nil,
mime_type: String.t() | nil
}
@enforce_keys [:uri]
defstruct [:uri, :name, :mime_type]
end
defmodule A2AEx.TextPart do
@moduledoc """
A text segment within a message or artifact.
"""
@type t :: %__MODULE__{
text: String.t(),
metadata: map() | nil
}
@enforce_keys [:text]
defstruct [:text, :metadata]
end
defmodule A2AEx.FilePart do
@moduledoc """
A file segment within a message or artifact.
The file content can be provided either as base64 bytes or as a URI.
"""
@type t :: %__MODULE__{
file: A2AEx.FileBytes.t() | A2AEx.FileURI.t(),
metadata: map() | nil
}
@enforce_keys [:file]
defstruct [:file, :metadata]
end
defmodule A2AEx.DataPart do
@moduledoc """
A structured data segment (e.g., JSON) within a message or artifact.
"""
@type t :: %__MODULE__{
data: map(),
metadata: map() | nil
}
@enforce_keys [:data]
defstruct [:data, :metadata]
end
defmodule A2AEx.Part do
@moduledoc """
Union type for message/artifact content parts.
A part can be one of:
- `A2AEx.TextPart` — text content
- `A2AEx.FilePart` — file content (bytes or URI)
- `A2AEx.DataPart` — structured data
"""
@type t :: A2AEx.TextPart.t() | A2AEx.FilePart.t() | A2AEx.DataPart.t()
@doc """
Decode a part from a JSON-decoded map (string keys, camelCase).
Dispatches on the `"kind"` field.
"""
@spec from_map(map()) :: {:ok, t()} | {:error, String.t()}
def from_map(%{"kind" => "text", "text" => text} = map) do
{:ok, %A2AEx.TextPart{text: text, metadata: map["metadata"]}}
end
def from_map(%{"kind" => "file", "file" => file_map} = map) do
case decode_file_content(file_map) do
{:ok, file} -> {:ok, %A2AEx.FilePart{file: file, metadata: map["metadata"]}}
error -> error
end
end
def from_map(%{"kind" => "data", "data" => data} = map) when is_map(data) do
{:ok, %A2AEx.DataPart{data: data, metadata: map["metadata"]}}
end
def from_map(%{"kind" => kind}) do
{:error, "unknown part kind: #{kind}"}
end
def from_map(_) do
{:error, "missing or invalid part kind"}
end
@doc """
Decode a list of parts from JSON-decoded maps.
"""
@spec from_map_list([map()]) :: {:ok, [t()]} | {:error, String.t()}
def from_map_list(parts) when is_list(parts) do
parts
|> Enum.reduce_while({:ok, []}, fn part_map, {:ok, acc} ->
case from_map(part_map) do
{:ok, part} -> {:cont, {:ok, [part | acc]}}
{:error, _} = err -> {:halt, err}
end
end)
|> case do
{:ok, parts} -> {:ok, Enum.reverse(parts)}
error -> error
end
end
@doc """
Encode a part to a JSON-ready map (string keys, camelCase).
"""
@spec to_map(t()) :: map()
def to_map(%A2AEx.TextPart{} = part) do
%{"kind" => "text", "text" => part.text}
|> maybe_put("metadata", part.metadata)
end
def to_map(%A2AEx.FilePart{} = part) do
%{"kind" => "file", "file" => encode_file_content(part.file)}
|> maybe_put("metadata", part.metadata)
end
def to_map(%A2AEx.DataPart{} = part) do
%{"kind" => "data", "data" => part.data}
|> maybe_put("metadata", part.metadata)
end
defp decode_file_content(%{"bytes" => bytes} = map) do
{:ok,
%A2AEx.FileBytes{
bytes: bytes,
name: map["name"],
mime_type: map["mimeType"]
}}
end
defp decode_file_content(%{"uri" => uri} = map) do
{:ok,
%A2AEx.FileURI{
uri: uri,
name: map["name"],
mime_type: map["mimeType"]
}}
end
defp decode_file_content(_) do
{:error, "invalid file part: either bytes or uri must be set"}
end
defp encode_file_content(%A2AEx.FileBytes{} = f) do
%{"bytes" => f.bytes}
|> maybe_put("name", f.name)
|> maybe_put("mimeType", f.mime_type)
end
defp encode_file_content(%A2AEx.FileURI{} = f) do
%{"uri" => f.uri}
|> maybe_put("name", f.name)
|> maybe_put("mimeType", f.mime_type)
end
defp maybe_put(map, _key, nil), do: map
defp maybe_put(map, key, value), do: Map.put(map, key, value)
end
# Jason.Encoder implementations
defimpl Jason.Encoder, for: A2AEx.TextPart do
def encode(part, opts) do
A2AEx.Part.to_map(part) |> Jason.Encode.map(opts)
end
end
defimpl Jason.Encoder, for: A2AEx.FilePart do
def encode(part, opts) do
A2AEx.Part.to_map(part) |> Jason.Encode.map(opts)
end
end
defimpl Jason.Encoder, for: A2AEx.DataPart do
def encode(part, opts) do
A2AEx.Part.to_map(part) |> Jason.Encode.map(opts)
end
end