# `A2UIAgent.DirectJson.Parser`
[🔗](https://github.com/a2ui-elixir/a2ui/blob/v0.1.1/umbrella/apps/a2ui_agent/lib/a2ui_agent/direct_json/parser.ex#L1)

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.

# `part`
*since 0.1.0* 

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

A part of a parsed response.

# `raw_part`
*since 0.1.0* 

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

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

# `compile`
*since 0.1.0* 

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

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

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

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

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

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

```elixir
@spec unwrap(String.t()) :: [raw_part()]
```

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

# `wrap`
*since 0.1.0* 

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

```elixir
@spec wrap_decompiled_blocks([String.t()]) :: String.t()
```

Encloses decompiled blocks in one pair of tags.

---

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