The renderer side of one A2UI conversation: the surfaces an agent built
(an A2UICore.Processor) and, for each surface, its resolved component
tree (an A2UICore.Resolution.NodeResolver).
A session is plain data owned by the host — usually a LiveView assign
(see A2UIPhoenix.Live). Every operation returns the new session
together with the effects the host acts on:
{:send, message}— a renderer→agent message (action,error,rendererFunctionResponse) to deliver to the agent;{:open_url, url}— anopenUrlaction asks the browser to openurl(the scheme was checked by the catalog function);{:surface_created, id},{:surface_deleted, id},{:components_updated, id, component_ids},{:data_model_updated, id, path}— as the processor reports them;{:rpc_settled, call_id, outcome}— a pending agent call settled;{:agent_call_sent, call_id, timeout_ms}— acallAgentFunctionmessage went out in a{:send, message}effect; the host starts a timer for it and callstimeout_agent_call/2when it fires (timeout_msisnilfor a call without a timeout);{:diagnostic, surface_id, :error | :warning, payload}— what the resolver found while resolving or invoking (an unknown component type, a cyclic reference, a binding that does not resolve), and agent function calls that failed.
Agent messages go through handle_message/3; user input goes through
write/5 (a two-way binding) and invoke/4 (an action); the requests
of an embedded MCP App (the MCP catalog's McpApp) go through
mcp_app_request/5.
Agent functions
In protocol v1.0 a function call that no catalog of the surface can
run locally is sent to the agent as a callAgentFunction message: a
call the catalog it resolves to does not declare, or declares without
an implementation. A property that uses such a call is nil, and its
node busy, until the agent's agentFunctionResponse arrives through
handle_message/3; the surface is then resolved again with the value.
A call that fails (an error response, a timeout) leaves the property
nil and is reported as a diagnostic. The call a function-call action
makes is sent each time the action runs.
session = A2UIPhoenix.Session.new([A2UICore.Catalog.Basic.catalog("v1.0")])
{:ok, session, _effects} = A2UIPhoenix.Session.handle_message(session, message)
Summary
Types
The reply to an MCP App's request: a JSON-RPC result, a JSON-RPC error (code and message), or none for a notification.
Functions
The data models of the surfaces created with sendDataModel, for the
transport to attach to outbound messages; see
A2UICore.Processor.renderer_data_model/2.
Rejects the pending agent calls and drops every resolved tree; the surfaces stay readable.
Applies an agent payload (a message, a list, or a {"messages": …}
wrapper) and re-resolves the surfaces it touched.
Invokes the action in property of node ref on surface surface_id.
Acts on a request or notification an embedded MCP App sent from the
McpApp node ref on surface surface_id, and returns the reply for
the app with the effects.
Creates a session over catalogs (the first one is the default for
surfaces that name none).
The resolved tree of surface id, or nil.
The surface with id, or nil.
All surfaces, sorted by id.
Rejects the pending agent call call_id with TIMEOUT, for the timer
the host started on {:agent_call_sent, call_id, timeout_ms}. A call
that has settled in the meantime is left alone.
Writes value through property of node ref on surface surface_id
— a two-way binding receiving user input — and re-resolves the surface.
Types
The reply to an MCP App's request: a JSON-RPC result, a JSON-RPC error (code and message), or none for a notification.
@type t() :: %A2UIPhoenix.Session{ agent_calls: %{optional(String.t()) => {String.t(), term()}}, clock: (-> DateTime.t()) | nil, mcp_client: A2UICore.MCP.Client.t() | nil, processor: A2UICore.Processor.t(), resolvers: %{optional(String.t()) => A2UICore.Resolution.NodeResolver.t()}, version: String.t() }
Functions
@spec client_data_model(t(), String.t() | nil) :: {:ok, map() | nil} | {:error, A2UICore.Error.t()}
The data models of the surfaces created with sendDataModel, for the
transport to attach to outbound messages; see
A2UICore.Processor.renderer_data_model/2.
Rejects the pending agent calls and drops every resolved tree; the surfaces stay readable.
@spec handle_message(t(), term(), keyword()) :: {:ok, t(), [effect()]} | {:error, A2UICore.Error.t(), t(), [effect()]}
Applies an agent payload (a message, a list, or a {"messages": …}
wrapper) and re-resolves the surfaces it touched.
On failure the operations before the failing one stay applied (see
A2UICore.Processor.process/3); the effects then include the error
message to report to the agent when the error names a surface or a
function call. Options are passed to the processor (:user_activated,
:surface_id).
@spec invoke(t(), String.t(), pos_integer(), String.t()) :: {:ok, t(), [effect()]} | {:error, A2UICore.Error.t()}
Invokes the action in property of node ref on surface surface_id.
An event action produces {:send, action_message}; an openUrl
function call produces {:open_url, url}; any other function call runs
in the renderer, and what it asks for is applied in order: data model
writes, and A2UI messages (as handle_message/3 would, except that
messages read from a tool's UI resource do not recreate a surface that
exists). A component whose checks fail (isValid is false) does not
act.
@spec mcp_app_request(t(), String.t(), pos_integer(), String.t(), term()) :: {:ok, t(), [effect()], mcp_reply()} | {:error, A2UICore.Error.t()}
Acts on a request or notification an embedded MCP App sent from the
McpApp node ref on surface surface_id, and returns the reply for
the app with the effects.
tools/call— a tool the component'sallowedToolslists is dispatched to the agent as an action named after it, with its arguments as context; the reply is the tool's result when the session has an:mcp_client, else an empty result.ui/requests/function-call— a function the component'sallowedFunctionslists, whose arguments pass the schema given there, runs in the surface's catalogs; the reply is%{"status" => "success", "result" => value}.ui/notifications/data-model-change— the value is written to the path the component binds to the key (data.paths), extended bysubpath, unless it is already there.ui/update-model-context—structuredContent(elsecontent) is written to the path bound tomodelContext.ui/message— dispatched as an action namedui/messagewithroleandcontent.
A payload that fails A2UIPhoenix.MCPApp.check_payload/1, a tool or
function that is not allowed, and an unknown method get a JSON-RPC
error. Returns {:error, error} when the node is gone or is not an
McpApp.
@spec new([A2UICore.Catalog.t()], keyword()) :: t()
Creates a session over catalogs (the first one is the default for
surfaces that name none).
Options:
:validationand:default_timeout_ms— passed toA2UICore.Processor.new/2;:version— the protocol version of outbound messages when the surface does not decide it ("v1.0");:clock— a zero-arity function returning the currentDateTime, used for action timestamps (DateTime.utc_now/0);:mcp_client— anA2UICore.MCP.Client({module, state}) that runs the tools an embedded MCP App calls, so that the app gets the tool's result; without one the app gets an empty result (the call still reaches the agent as an action).
@spec resolver(t(), String.t()) :: A2UICore.Resolution.NodeResolver.t() | nil
The resolved tree of surface id, or nil.
@spec surface(t(), String.t()) :: A2UICore.State.Surface.t() | nil
The surface with id, or nil.
@spec surfaces(t()) :: [A2UICore.State.Surface.t()]
All surfaces, sorted by id.
Rejects the pending agent call call_id with TIMEOUT, for the timer
the host started on {:agent_call_sent, call_id, timeout_ms}. A call
that has settled in the meantime is left alone.
@spec write(t(), String.t(), pos_integer(), String.t(), term()) :: {:ok, t(), [effect()]} | {:error, A2UICore.Error.t()}
Writes value through property of node ref on surface surface_id
— a two-way binding receiving user input — and re-resolves the surface.
Returns an error when the node is gone or the property is not bound to a data path.