A2UICore.Processor (A2UICore v0.1.1)

Copy Markdown View Source

Applies agent→renderer messages to a set of surfaces.

A processor is plain data: process/2 takes a processor and a raw payload (a message, a list of messages, a {"messages": [...]} wrapper) and returns the updated processor together with the effects a host may react to. Nothing is delivered by callback and no process is involved; the host owns the processor value.

catalog = A2UICore.Catalog.Basic.catalog("v0.9")
processor = A2UICore.Processor.new([catalog])

{:ok, processor, effects} =
  A2UICore.Processor.process(processor, %{
    "version" => "v0.9",
    "createSurface" => %{"surfaceId" => "main", "catalogId" => catalog.catalog_id}
  })

Semantics

Messages are decoded (A2UICore.Message.decode/2) and their operations applied in order. There is no rollback across operations: when one fails, the operations before it stay applied and process/2 returns {:error, error, processor} with that partially updated processor. Within one updateComponents the update is atomic — a failing batch leaves the surface untouched.

  • createSurface — the surface's default catalog is the one named by catalogId. A v1.0 surface may name none and then has no default catalog; before v1.0 the processor's first catalog is the fallback. The message's version must be compatible with the catalog (A2UICore.Version) and a theme is validated against it. The surface can use every catalog of its protocol version. Inline dataModel and components are applied right after the surface is created.
  • updateComponents — each component takes the catalog named by its catalogId, else the one it already has (when its type is unchanged), else the surface's default; from v1.0 a component that names none on a surface without a default catalog is an error, and so is a function call nested in it whose catalog does not resolve. With a validation configuration the batch is validated as a whole before it is committed (A2UICore.Validation.ComponentsUpdate).
  • updateDataModel — writes into the surface's data model.
  • deleteSurface — removes the surface; an unknown id is ignored.
  • callRendererFunction — runs the function (see A2UICore.RPC) against the data of the surface its surfaceId names, or of no surface when it names none, and comes back as an {:rpc_response, message} effect: the rendererFunctionResponse to send to the agent. Calls that require user activation need the :user_activated option.
  • agentFunctionResponse — settles the pending agent call it answers and comes back as an {:rpc_settled, call_id, outcome} effect. Neither changes surface state.

Without a validation configuration (:validation option) component batches are committed unchecked.

Effects

  • {:surface_created, surface_id}
  • {:surface_deleted, surface_id}
  • {:components_updated, surface_id, component_ids}
  • {:data_model_updated, surface_id, path}
  • {:rpc_response, message}
  • {:rpc_settled, call_id, {:ok, value} | {:error, error}}

Calling the agent

call_agent_function/4 registers a call and returns the callAgentFunction message. The call is settled by an agentFunctionResponse message, by timeout_agent_call/2 when the host's timer for it fires, by cancel_agent_call/3, or by dispose/1; the settlements come back as {:rpc_settled, …} effects.

Summary

Functions

The timeout, in milliseconds, of a pending agent call: the host starts a timer for it and reports the expiry with timeout_agent_call/2. nil for a call that is not pending or has no timeout.

Applies already-decoded operations in order, as process/2 does.

Registers a call to an agent function on surface_id and returns the callAgentFunction message to send; see A2UICore.RPC.call_agent_function/4 for the call and its options.

Rejects a pending agent call the host abandoned, with CANCELLED.

Rejects every pending agent call with CANCELLED and stops answering function calls. Surfaces are left as they are.

Creates a processor over catalogs. The first catalog is the default for surfaces that name none.

Decodes payload and applies its operations in order.

The renderer capabilities for the processor's catalogs, keyed by protocol version.

The data models of the surfaces that asked for them (sendDataModel), as %{"version" => …, "surfaces" => %{id => data}}, or nil when no surface did.

Writes value at path of a surface's data model on the renderer's behalf — user input reaching a two-way binding. It is the same write as an updateDataModel message, but not one the agent sent, so the validation configuration's allowed_messages does not apply.

The surface with id, or nil.

All surfaces, sorted by id.

Rejects a pending agent call whose timer fired, with TIMEOUT.

Types

effect()

(since 0.1.0)
@type effect() ::
  {:surface_created, String.t()}
  | {:surface_deleted, String.t()}
  | {:components_updated, String.t(), [String.t()]}
  | {:data_model_updated, String.t(), String.t()}
  | {:rpc_response, map()}
  | {:rpc_settled, String.t(), {:ok, term()} | {:error, A2UICore.Error.t()}}

t()

(since 0.1.0)
@type t() :: %A2UICore.Processor{
  catalogs: [A2UICore.Catalog.t()],
  group: A2UICore.State.SurfaceGroup.t(),
  rpc: A2UICore.RPC.t(),
  validation: A2UICore.Validation.Config.t() | nil
}

Functions

agent_call_timeout(processor, call_id)

(since 0.1.0)
@spec agent_call_timeout(t(), String.t()) :: number() | nil

The timeout, in milliseconds, of a pending agent call: the host starts a timer for it and reports the expiry with timeout_agent_call/2. nil for a call that is not pending or has no timeout.

apply_operation(processor, operation, options \\ [])

(since 0.1.0)
@spec apply_operation(t(), A2UICore.Message.Operation.t(), keyword()) ::
  {:ok, t(), [effect()]} | {:error, A2UICore.Error.t(), t()}

Applies one operation.

apply_operations(processor, operations, options \\ [])

(since 0.1.0)
@spec apply_operations(t(), [A2UICore.Message.Operation.t()], keyword()) ::
  {:ok, t(), [effect()]} | {:error, A2UICore.Error.t(), t()}

Applies already-decoded operations in order, as process/2 does.

call_agent_function(processor, surface_id, call, options \\ [])

(since 0.1.0)
@spec call_agent_function(t(), String.t(), map(), keyword()) ::
  {:ok, t(), map()} | {:error, A2UICore.Error.t()}

Registers a call to an agent function on surface_id and returns the callAgentFunction message to send; see A2UICore.RPC.call_agent_function/4 for the call and its options.

cancel_agent_call(processor, call_id, reason \\ "The call was cancelled.")

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

Rejects a pending agent call the host abandoned, with CANCELLED.

dispose(processor)

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

Rejects every pending agent call with CANCELLED and stops answering function calls. Surfaces are left as they are.

new(catalogs, options \\ [])

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

Creates a processor over catalogs. The first catalog is the default for surfaces that name none.

Options: :validation — an A2UICore.Validation.Config applied to component batches (none by default); :default_timeout_ms — the timeout of agent calls that name none (30 000).

Raises ArgumentError when catalogs is empty.

process(processor, payload, options \\ [])

(since 0.1.0)
@spec process(t(), term(), keyword()) ::
  {:ok, t(), [effect()]} | {:error, A2UICore.Error.t(), t()}

Decodes payload and applies its operations in order.

Returns {:ok, processor, effects}, or {:error, error, processor} where processor holds whatever was applied before the failure.

Options: :user_activated — the messages are processed inside a user gesture (functions that require activation run only then).

renderer_capabilities(processor, options)

(since 0.1.0)
@spec renderer_capabilities(t(), keyword()) ::
  {:ok, map()} | {:error, A2UICore.Error.t()}

The renderer capabilities for the processor's catalogs, keyed by protocol version.

Options:

  • :versions — the protocol versions to describe (required);
  • :include_inline_catalogs — add each catalog's definition under "inlineCatalogs": its document (A2UICore.Catalog.to_json/2) from v1.0 on, else a legacy inline catalog whose components are allOf envelopes over :component_envelope_ref (default common_types.json#/$defs/ComponentCommon), built from the resolved schemas.

Returns {:error, error} when no version is given.

renderer_data_model(processor, version \\ nil)

(since 0.1.0)
@spec renderer_data_model(t(), String.t() | nil) ::
  {:ok, map() | nil} | {:error, A2UICore.Error.t()}

The data models of the surfaces that asked for them (sendDataModel), as %{"version" => …, "surfaces" => %{id => data}}, or nil when no surface did.

With version, only surfaces whose catalog is compatible with it are included. Without, the version is the one all such surfaces share ("v1.0" when none declares one); surfaces on different versions are an error.

set_data(processor, surface_id, path, value)

(since 0.1.0)
@spec set_data(t(), String.t(), String.t(), term()) ::
  {:ok, t(), [effect()]} | {:error, A2UICore.Error.t()}

Writes value at path of a surface's data model on the renderer's behalf — user input reaching a two-way binding. It is the same write as an updateDataModel message, but not one the agent sent, so the validation configuration's allowed_messages does not apply.

Returns {:ok, processor, [{:data_model_updated, surface_id, path}]}, or {:error, error} for an unknown surface or an invalid path.

surface(processor, id)

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

The surface with id, or nil.

surfaces(processor)

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

All surfaces, sorted by id.

timeout_agent_call(processor, call_id)

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

Rejects a pending agent call whose timer fired, with TIMEOUT.