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

The typed turn event: one schema that every interface renders.

A conversation publishes events on the PubSub topic `"conversation:<id>"` as
`{:conversation_event, conversation_id, wire_map}`. Before this module each producer built an
untyped map and each consumer (Gateway, RPC server, TUI) read it again in its own way. Now a
producer builds an event with one function per type (the only sanctioned construction site, each
with a `@spec`), and a consumer reads it through a renderer (`SuperPi.Event.Renderer`).

There is one struct, not one per type. The wire form is a plain map anyway, and a renderer
matches on `:type`.

## Wire form

`to_wire/1` gives the flat map that consumers have always read, plus `"v"` (schema version, `1`)
and `"turn_id"` (the id of the turn task, or `nil` outside a turn). Payload keys stay at the top
level, not under `"data"`, so a consumer that matches `%{"type" => "text_delta", "delta" => d}`
keeps working. Both additions are purely additive.

## Types

| Type | Payload keys |
|---|---|
| `turn_started` | `seq` |
| `turn_completed` | `stop_reason`, `result`, `seq` |
| `turn_error` | `error`, `seq` |
| `turn_cancelled` | `seq` |
| `text_delta` | `index`, `delta` |
| `thinking_delta` | `index`, `delta` |
| `toolcall_delta` | `index`, `delta`, `partial_args`, and `id` and `name` when the provider gave them |
| `message_completed` | `role`, `content`, `usage` |
| `tool_started` | `tool_call_id`, `name`, `arguments`, `overwritten_keys` |
| `tool_blocked` | `tool_call_id`, `name`, `reason` |
| `tool_completed` | `tool_call_id`, `name`, `is_error`, `output` |
| `permission_request` | `request_id`, `tool_call_id`, `tool_name`, `arguments`, `reason`, `risk`, `options` |
| `ui_request` | `request_id` and the request map (for example `kind`, `questions`) |
| `ui_resolved` | `request_id`, `outcome` |
| `state_changed` | `status` |
| `mode_changed` | `mode`, `max_mode` |
| `stream_reset` | `retry_in_ms` |
| `compaction_completed` | `head_entry_id`, `summary` |
| `usage_updated` | `input_tokens`, `output_tokens`, `total_input_tokens`, `total_output_tokens` |
| `plan_updated` | `entries` (todo items with `id`, `text`, `completed`) |
| `notice` | `level`, `message` |
| `watch_started` | `seq` |
| `subagent_started` | `child_conversation_id`, `role`, `task` |
| `subagent_finished` | `child_conversation_id`, `status`, `summary` |

`stop_reason` of `turn_completed` is `"end_turn"`. `permission_request` options carry ACP
`PermissionOptionKind` values in `kind`: `allow_once`, `allow_always`, `reject_once`, `reject_always`.

# `t`

```elixir
@type t() :: %SuperPi.Event{
  conversation_id: integer() | nil,
  data: map(),
  timestamp_ms: integer(),
  turn_id: integer() | nil,
  type: String.t(),
  v: pos_integer()
}
```

# `compaction_completed`

```elixir
@spec compaction_completed(integer(), term(), String.t() | nil) :: t()
```

# `from_extension`

```elixir
@spec from_extension(integer(), integer() | nil, map()) :: t()
```

Wraps a map that an extension passed to `ctx.emit_event` into an event. The map must have a
`"type"`. Keys that belong to the envelope are taken from the arguments, not the map.

# `from_wire`

```elixir
@spec from_wire(map()) :: t()
```

Reads a wire map back into an event. A map from an older producer (no `"v"`, no `"turn_id"`)
reads as version 1 with no turn.

# `message_completed`

```elixir
@spec message_completed(integer(), integer() | nil, String.t(), [map()], map()) :: t()
```

# `mode_changed`

```elixir
@spec mode_changed(integer(), String.t(), String.t() | nil) :: t()
```

# `notice`

```elixir
@spec notice(integer(), integer() | nil, String.t(), String.t()) :: t()
```

# `permission_options`

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

The four permission options, with ACP `PermissionOptionKind` in `kind`.

# `permission_request`

```elixir
@spec permission_request(integer(), integer() | nil, map()) :: t()
```

A tool call waits for a human decision. `request_id` answers through `SuperPi.UI.Broker.respond/2`
with the `option_id` of one of `options`.

# `plan_updated`

```elixir
@spec plan_updated(integer(), [map()]) :: t()
```

# `publish`

```elixir
@spec publish(t()) :: :ok
```

Publishes an event on the conversation topic.

# `state_changed`

```elixir
@spec state_changed(integer(), integer() | nil, String.t()) :: t()
```

# `stream_reset`

```elixir
@spec stream_reset(integer(), integer() | nil, integer()) :: t()
```

# `subagent_finished`

```elixir
@spec subagent_finished(
  integer(),
  integer() | nil,
  integer(),
  String.t(),
  String.t() | nil
) :: t()
```

# `subagent_started`

```elixir
@spec subagent_started(integer(), integer() | nil, integer(), String.t(), String.t()) ::
  t()
```

# `text_delta`

```elixir
@spec text_delta(integer(), integer() | nil, integer(), String.t()) :: t()
```

# `thinking_delta`

```elixir
@spec thinking_delta(integer(), integer() | nil, integer(), String.t()) :: t()
```

# `to_wire`

```elixir
@spec to_wire(t()) :: map()
```

The wire map of an event: its payload at the top level, plus the envelope keys.

# `tool_blocked`

```elixir
@spec tool_blocked(integer(), integer() | nil, String.t(), String.t(), String.t()) ::
  t()
```

# `tool_completed`

```elixir
@spec tool_completed(
  integer(),
  integer() | nil,
  String.t(),
  String.t(),
  boolean(),
  term()
) :: t()
```

# `tool_started`

```elixir
@spec tool_started(integer(), integer() | nil, String.t(), String.t(), map(), [
  String.t()
]) :: t()
```

# `toolcall_delta`

```elixir
@spec toolcall_delta(
  integer(),
  integer() | nil,
  integer(),
  String.t() | nil,
  term(),
  keyword()
) :: t()
```

# `topic`

```elixir
@spec topic(integer()) :: String.t()
```

PubSub topic of a conversation.

# `turn_cancelled`

```elixir
@spec turn_cancelled(integer(), integer() | nil, integer()) :: t()
```

# `turn_completed`

# `turn_error`

```elixir
@spec turn_error(integer(), integer() | nil, term(), integer()) :: t()
```

# `turn_started`

```elixir
@spec turn_started(integer(), integer() | nil, integer()) :: t()
```

# `types`

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

The event types.

# `ui_request`

```elixir
@spec ui_request(integer(), String.t(), map()) :: t()
```

# `ui_resolved`

```elixir
@spec ui_resolved(integer(), String.t(), term()) :: t()
```

# `usage_updated`

```elixir
@spec usage_updated(integer(), integer() | nil, map()) :: t()
```

# `watch_started`

```elixir
@spec watch_started(integer(), integer()) :: t()
```

---

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