# `A2UIPhoenix.Session`
[🔗](https://github.com/a2ui-elixir/a2ui/blob/v0.1.1/umbrella/apps/a2ui_phoenix/lib/a2ui_phoenix/session.ex#L1)

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)

# `effect`
*since 0.1.0* 

```elixir
@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* 

```elixir
@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* 

```elixir
@type t() :: %A2UIPhoenix.Session{
  agent_calls: %{optional(String.t()) =&gt; {String.t(), term()}},
  clock: (-&gt; DateTime.t()) | nil,
  mcp_client: A2UICore.MCP.Client.t() | nil,
  processor: A2UICore.Processor.t(),
  resolvers: %{optional(String.t()) =&gt; A2UICore.Resolution.NodeResolver.t()},
  version: String.t()
}
```

# `client_data_model`
*since 0.1.0* 

```elixir
@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`
*since 0.1.0* 

```elixir
@spec dispose(t()) :: {t(), [effect()]}
```

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

# `handle_message`
*since 0.1.0* 

```elixir
@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`
*since 0.1.0* 

```elixir
@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`
*since 0.1.0* 

```elixir
@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`
*since 0.1.0* 

```elixir
@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`
*since 0.1.0* 

```elixir
@spec resolver(t(), String.t()) :: A2UICore.Resolution.NodeResolver.t() | nil
```

The resolved tree of surface `id`, or `nil`.

# `surface`
*since 0.1.0* 

```elixir
@spec surface(t(), String.t()) :: A2UICore.State.Surface.t() | nil
```

The surface with `id`, or `nil`.

# `surfaces`
*since 0.1.0* 

```elixir
@spec surfaces(t()) :: [A2UICore.State.Surface.t()]
```

All surfaces, sorted by id.

# `timeout_agent_call`
*since 0.1.0* 

```elixir
@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`
*since 0.1.0* 

```elixir
@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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
