Managoat.ACP.Blocks (managoat_acp v0.1.0)

Copy Markdown View Source

Translate ACP messages into the block maps a transcript renders.

This is the module that replaces a hand-written dialect parser, and the reason it lives beside the peer rather than in a view is the point of Fountain's ADR 0014: four proprietary output formats used to be parsed in the render path, where a vendor's point release becomes a rendering bug. The ACP path parses once, in a module with its own tests, and every client reads one parse.

The block shapes are a wire contract, not a choice made here: they are what the transcript renderers already draw, so a client can render an ACP transcript and a legacy one with one code path. The README lists them.

ACP session/updateblock
agent_message_chunk%{kind: :text}
agent_thought_chunk%{kind: :thinking}
tool_call%{kind: :tool_use}
tool_call_update%{kind: :tool_result}
user_message_chunkdropped — we already render the prompt
plan, available_commands_updatedropped — no equivalent yet

Chunks are not messages

agent_message_chunk is a chunk: a turn produces many, and each carries a fragment of text. We emit one :text block per chunk and let the existing renderer concatenate adjacent ones, which is what it already does for the legacy stream. Buffering them here would mean holding a turn's whole assistant message in the peer's memory to produce output that renders identically.

Tool calls thread on one id

A renderer's tool-pairing pass exists because three of the four legacy dialects emit a tool call and its result as unrelated top-level events. ACP threads them on toolCallId by construction, so the pairing pass gets the ids it wants for free — we map tool_call:tool_use with :id and tool_call_update:tool_result with :tool_id, and the existing pass collapses them.

A tool_call_update only becomes a :tool_result once it reports a terminal status. In-flight updates (pending, in_progress) carry progress, not an outcome, and turning each one into a result block would render a completed tool card several times per call.

Summary

Functions

Translate one stored ndjson line into blocks.

Translate the params of one session/update notification.

Functions

from_line(line)

@spec from_line(String.t()) :: [map()]

Translate one stored ndjson line into blocks.

This is the entry point a render path uses: an owner stores the raw protocol line the peer reported, so what is on disk is what the adapter actually said.

from_update(update)

@spec from_update(map()) :: [map()]

Translate the params of one session/update notification.

The update variant lives under sessionUpdate; everything else in the map is variant-specific.