A2UIAgent.DirectJson.Parser (A2UIAgent v0.1.1)

Copy Markdown View Source

Reads model output written in the direct JSON format: conversational text with A2UI message lists between <a2ui-json> and </a2ui-json> tags.

{:ok, parser} = A2UIAgent.DirectJson.Parser.new(catalogs)

{:ok, parts} =
  A2UIAgent.DirectJson.Parser.parse_response(parser, """
  Here is your surface.
  <a2ui-json>[{"version": "v1.0", "deleteSurface": {"surfaceId": "s1"}}]</a2ui-json>
  """)

parts
#=> [{:text, "Here is your surface."},
#    {:a2ui, [%{"version" => "v1.0", "deleteSurface" => %{"surfaceId" => "s1"}}]}]

A block is read leniently (A2UIAgent.DirectJson.JsonReader: trailing commas, raw line breaks in strings, curly quotes), a Markdown code fence around it is dropped, and a single message is read as a list of one. The messages are then checked with A2UIAgent.PayloadValidator.

Parts are {:text, text} and {:a2ui, messages}. unwrap/1 returns raw parts, {:text, text} and {:a2ui_raw, block, final?}, where final? is false for a block whose closing tag has not arrived.

Summary

Types

A part of a parsed response.

A part of an unwrapped response: text or an uncompiled block.

t()

Functions

Compiles one block into validated messages.

Writes messages back out as the pretty-printed JSON a model would have emitted, each message's keys in the protocol's order (A2UICore.KeyOrder.message/1). Text in that order streams: parse_chunk/3 can pass a message on as soon as its surfaceId has arrived, and a component list one component at a time.

Whether content holds an <a2ui-json> block; with complete: true, a block whose closing tag has arrived.

Creates a parser for catalogs (default catalog first). Option: :progressive_keys — string properties the streaming reader may emit before their closing quote.

Reads the next chunk of a streamed response and returns the parts it completes or changes.

Parses a complete model response into parts.

Splits model output into text and raw blocks, without compiling them.

Writes parts out as model output: text as it is, each block between tags, joined by newlines.

Encloses decompiled blocks in one pair of tags.

Types

part()

(since 0.1.0)
@type part() :: {:text, String.t()} | {:a2ui, [map()]}

A part of a parsed response.

raw_part()

(since 0.1.0)
@type raw_part() :: {:text, String.t()} | {:a2ui_raw, String.t(), boolean()}

A part of an unwrapped response: text or an uncompiled block.

t()

(since 0.1.0)
@type t() :: %A2UIAgent.DirectJson.Parser{
  block: String.t(),
  catalogs: [A2UICore.Catalog.t()],
  emitted: [map()],
  held: String.t(),
  held_space: String.t(),
  in_block: boolean(),
  progressive_keys: MapSet.t(String.t()),
  run_started: boolean(),
  wrapped: boolean() | nil
}

Functions

compile(parser, block)

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

Compiles one block into validated messages.

An empty block or one that does not read as JSON is a :parse error; messages a renderer holding the catalogs would reject are a :validation error.

decompile(messages)

(since 0.1.0)
@spec decompile([map()] | map()) :: String.t()

Writes messages back out as the pretty-printed JSON a model would have emitted, each message's keys in the protocol's order (A2UICore.KeyOrder.message/1). Text in that order streams: parse_chunk/3 can pass a message on as soon as its surfaceId has arrived, and a component list one component at a time.

has_format_content?(content, options \\ [])

(since 0.1.0)
@spec has_format_content?(String.t(), keyword()) :: boolean()

Whether content holds an <a2ui-json> block; with complete: true, a block whose closing tag has arrived.

new(catalogs, options \\ [])

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

Creates a parser for catalogs (default catalog first). Option: :progressive_keys — string properties the streaming reader may emit before their closing quote.

The catalogs must be non-empty, have distinct ids and share a protocol version.

parse_chunk(parser, chunk, options \\ [])

(since 0.1.0)
@spec parse_chunk(t(), String.t(), keyword()) ::
  {:ok, t(), [part()]} | {:error, A2UICore.Error.t()}

Reads the next chunk of a streamed response and returns the parts it completes or changes.

Text is emitted as it arrives, trimmed at the edges of each run of text; a possible start of an <a2ui-json> tag is held back until the next chunk settles it. Inside a block, each message is emitted as soon as it reads (A2UIAgent.DirectJson.JsonReader.read_partial_items/3) and validates, and again whenever it grows; a message that has not changed is not emitted twice. A createSurface waits until its object closes, and so do the messages after it. When the block closes it is compiled in full, so a block that does not validate is an error then.

From v1.0 a component is held back until its object closes, so an updateComponents message holds only closed components. Later chunks cannot make such a message valid, so one that does not validate is an error on the chunk that closes the offending component.

With wrapped: false the whole response is one block without tags. A response is either wrapped or not: every chunk must say the same.

parse_response(parser, content, options \\ [])

(since 0.1.0)
@spec parse_response(t(), String.t(), keyword()) ::
  {:ok, [part()]} | {:error, A2UICore.Error.t()}

Parses a complete model response into parts.

With wrapped: false the whole response is one block. Otherwise each block is compiled in turn and the first failure is returned.

unwrap(content)

(since 0.1.0)
@spec unwrap(String.t()) :: [raw_part()]

Splits model output into text and raw blocks, without compiling them.

wrap(parts)

(since 0.1.0)
@spec wrap([raw_part() | {:a2ui_raw, String.t()}]) :: String.t()

Writes parts out as model output: text as it is, each block between tags, joined by newlines.

wrap_decompiled_blocks(blocks)

(since 0.1.0)
@spec wrap_decompiled_blocks([String.t()]) :: String.t()

Encloses decompiled blocks in one pair of tags.