A2UIPhoenix.Session (A2UIPhoenix v0.1.1)

Copy Markdown View Source

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} — an openUrl action asks the browser to open url (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} — a callAgentFunction message went out in a {:send, message} effect; the host starts a timer for it and calls timeout_agent_call/2 when it fires (timeout_ms is nil for 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.

t()

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

effect()

(since 0.1.0)
@type effect() ::
  {:send, map()}
  | {:open_url, String.t()}
  | {:agent_call_sent, String.t(), number() | nil}
  | {:diagnostic, String.t(), :error | :warning, map()}
  | A2UICore.Processor.effect()

mcp_reply()

(since 0.1.0)
@type mcp_reply() :: {:result, term()} | {:error, integer(), String.t()} | :none

The reply to an MCP App's request: a JSON-RPC result, a JSON-RPC error (code and message), or none for a notification.

t()

(since 0.1.0)
@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

client_data_model(session, version \\ nil)

(since 0.1.0)
@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.

dispose(session)

(since 0.1.0)
@spec dispose(t()) :: {t(), [effect()]}

Rejects the pending agent calls and drops every resolved tree; the surfaces stay readable.

handle_message(session, payload, options \\ [])

(since 0.1.0)
@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).

invoke(session, surface_id, ref, property)

(since 0.1.0)
@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.

mcp_app_request(session, surface_id, ref, method, params)

(since 0.1.0)
@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's allowedTools lists 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's allowedFunctions lists, 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 by subpath, unless it is already there.
  • ui/update-model-context — structuredContent (else content) is written to the path bound to modelContext.
  • ui/message — dispatched as an action named ui/message with role and content.

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.

new(catalogs, options \\ [])

(since 0.1.0)
@spec new([A2UICore.Catalog.t()], keyword()) :: t()

Creates a session over catalogs (the first one is the default for surfaces that name none).

Options:

  • :validation and :default_timeout_ms — passed to A2UICore.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 current DateTime, used for action timestamps (DateTime.utc_now/0);
  • :mcp_client — an A2UICore.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).

resolver(session, id)

(since 0.1.0)
@spec resolver(t(), String.t()) :: A2UICore.Resolution.NodeResolver.t() | nil

The resolved tree of surface id, or nil.

surface(session, id)

(since 0.1.0)
@spec surface(t(), String.t()) :: A2UICore.State.Surface.t() | nil

The surface with id, or nil.

surfaces(session)

(since 0.1.0)
@spec surfaces(t()) :: [A2UICore.State.Surface.t()]

All surfaces, sorted by id.

timeout_agent_call(session, call_id)

(since 0.1.0)
@spec timeout_agent_call(t(), String.t()) :: {:ok, t(), [effect()]}

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.

write(session, surface_id, ref, property, value)

(since 0.1.0)
@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.