# `SuperPi.Event.Renderer.AGUI`
[🔗](https://github.com/oleksandr/super-pi-beam/blob/v0.1.0/lib/super_pi/event/renderer/agui.ex#L1)

Renders events as [AG-UI](https://docs.ag-ui.com) events, for `SuperPi.Gateway.AGUI`.

Each output item is a map with a `"type"` (`RUN_STARTED`, `TEXT_MESSAGE_CONTENT`, ...) and the
fields of that event in camelCase, ready for `Jason.encode!/1` and an SSE `data:` line.

## Mapping

| Event | AG-UI |
|---|---|
| `turn_started` | `RUN_STARTED` |
| `text_delta` | `TEXT_MESSAGE_START` (once per message), `TEXT_MESSAGE_CONTENT` |
| `thinking_delta` | `REASONING_START`, `REASONING_MESSAGE_START`, `REASONING_MESSAGE_CONTENT` |
| `message_completed` | `TEXT_MESSAGE_END` (and the reasoning end events) |
| `toolcall_delta` | `TOOL_CALL_START` (once per call), `TOOL_CALL_ARGS` |
| `tool_started` | `TOOL_CALL_END` - AG-UI means *arguments complete* by it, not *tool complete* |
| `tool_completed`, `tool_blocked` | `TOOL_CALL_RESULT` |
| `turn_completed` | `RUN_FINISHED` |
| `turn_error` | `RUN_ERROR` |
| `turn_cancelled` | `RUN_ERROR` with `code` `"cancelled"` (AG-UI has no cancel event) |
| `compaction_completed` | `MESSAGES_SNAPSHOT`, because the context really changed |
| `state_changed`, `mode_changed`, `usage_updated`, `plan_updated`, `permission_request`, `ui_resolved` | `STATE_DELTA` |
| `permission_request`, `ui_request`, `ui_resolved`, `notice`, `stream_reset`, `subagent_*` | `CUSTOM` |

A bare run of deltas gets its `TEXT_MESSAGE_START` and `TEXT_MESSAGE_END` here, because the machine
only emits deltas. A tool call that a provider did not stream gets `TOOL_CALL_START`,
`TOOL_CALL_ARGS` and `TOOL_CALL_END` together when `tool_started` arrives.

## State

The renderer keeps a small projection of the conversation (`status`, `mode`, `model`, `usage`,
`plan`, `pendingPermission`). `snapshot/1` gives `STATE_SNAPSHOT` for an opening or a reconnect.
After a state event it recomputes the projection and emits `STATE_DELTA` with
`Jsonpatch.diff(previous, next)`. The durable document layer offers no patch stream for this: only
`pi.agent` keeps ops, `pi.live` and `pi.usage` do not, and no write broadcasts. Doing it here is
correct by construction and needs no change to document retention.

## Options

- `:thread_id`, `:run_id` - ids of the run. `run_id` defaults to the turn id.
- `:projection` - initial projection (`initial_projection/1` builds one from the documents).
- `:messages_fun` - zero-arity function that returns the messages for `MESSAGES_SNAPSHOT`.

## Coalescing

`coalesce/1` merges consecutive `TEXT_MESSAGE_CONTENT` and `TOOL_CALL_ARGS` items of the same
message or call. The transport calls it on the batch of events that arrived within about 16 ms.
There is no ack window as in the RPC server: SSE is one-way and TCP is the backpressure.

# `coalesce`

```elixir
@spec coalesce([map()]) :: [map()]
```

Merges consecutive content items of the same message or tool call into one.

# `initial_projection`

```elixir
@spec initial_projection(integer()) :: map()
```

The projection of a conversation, read from its documents.

# `snapshot`

```elixir
@spec snapshot(%SuperPi.Event.Renderer.AGUI{
  call_order: term(),
  calls: term(),
  message: term(),
  messages_fun: term(),
  projection: term(),
  reasoning: term(),
  run_id: term(),
  sent: term(),
  seq: term(),
  thread_id: term()
}) ::
  {[map()],
   %SuperPi.Event.Renderer.AGUI{
     call_order: term(),
     calls: term(),
     message: term(),
     messages_fun: term(),
     projection: term(),
     reasoning: term(),
     run_id: term(),
     sent: term(),
     seq: term(),
     thread_id: term()
   }}
```

`STATE_SNAPSHOT` of the current projection. Marks it as sent, so the next delta is relative to it.

---

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