Semantic agent chat model.
Describes the agent conversation view: visibility, runtime status, the active
model name and thinking level, the prompt buffer plus its cell-grid metadata
(cursor, vim mode, line counts), an optional prompt completion popup, an
optional help overlay, and the resident conversation messages with their stable
BEAM-assigned IDs.
This is pure data with domain fields and core types only. It does not
reference any product module; the editor builder pre-resolves every agent
struct into the core views defined here before putting them on the model. The
GUI adapters split the model across two commands:
Minga.Frontend.Adapter.GUI.AgentChatEncoder owns gui_agent_chat (0x78)
chrome encoding and chrome fingerprinting,
Minga.Frontend.Adapter.GUI.AgentTranscriptEncoder owns
gui_agent_transcript (0x86) resident framing, and
Minga.Frontend.Adapter.GUI.AgentChatMessageCodec owns each transcript
message body. A hidden panel is just %AgentChat{visible?: false}; the chrome
encoder derives its change-detection fingerprint with :erlang.phash2/1, so
no sentinel is needed.
Messages
resident_messages is a list of {id, body} tuples where id is a stable
uint32 the GUI uses as a persistent identity. body is one of the resolved
core message forms produced by the editor builder:
{:user, text}/{:user, text, attachments}{:assistant, text}{:styled_assistant, styled_lines}{:assistant_markdown, markdown_blocks}{:thinking, text, collapsed?}{:tool_call, %AgentChat.ToolCallView{}}{:styled_tool_call, %AgentChat.ToolCallView{}, styled_lines}{:approval_tool_call, %AgentChat.ApprovalView{}}{:system, text, level}{:usage, %AgentChat.Usage{}}
Bare body tuples (without an id wrapper) are also accepted and encode with
ID 0, matching the historical wire behaviour.
Resident transcript
resident_messages rides the dedicated gui_agent_transcript (0x86) stream
so the frontend can scroll from local data (#2654). The editor builder applies
:agent_transcript_resident_max_bytes as a contiguous most-recent suffix and
sets resident_truncated? when it omits older complete messages. The adapter
serializes that already-selected model exactly. transcript_epoch is an opaque change token that flips on structural change
(session switch, display-start/compaction), driving the resident stream's
full-replace-vs-append decision. Both default empty/zero so hidden or
non-transcript surfaces produce no resident frame.
Summary
Types
A message with its stable GUI identity, or a bare body that encodes with ID 0.
A conversation message body.
A line of styled runs.
A styled text run: {text, fg_rgb, bg_rgb, flags} or with a trailing url.
Functions
Selects a contiguous newest resident suffix without shortening any message.
Types
@type message() :: {pos_integer(), message_body()} | message_body()
A message with its stable GUI identity, or a bare body that encodes with ID 0.
@type message_body() :: {:user, String.t()} | {:user, String.t(), term()} | {:assistant, String.t()} | {:styled_assistant, [styled_line()]} | {:assistant_markdown, [Minga.RenderModel.UI.AgentChat.MarkdownBlock.t()]} | {:thinking, String.t(), boolean()} | {:tool_call, Minga.RenderModel.UI.AgentChat.ToolCallView.t()} | {:styled_tool_call, Minga.RenderModel.UI.AgentChat.ToolCallView.t(), [styled_line()]} | {:approval_tool_call, Minga.RenderModel.UI.AgentChat.ApprovalView.t()} | {:system, String.t(), atom()} | {:usage, Minga.RenderModel.UI.AgentChat.Usage.t()}
A conversation message body.
@type styled_line() :: [styled_run()]
A line of styled runs.
@type styled_run() :: {String.t(), non_neg_integer(), non_neg_integer(), non_neg_integer()} | {String.t(), non_neg_integer(), non_neg_integer(), non_neg_integer(), String.t()}
A styled text run: {text, fg_rgb, bg_rgb, flags} or with a trailing url.
@type t() :: %Minga.RenderModel.UI.AgentChat{ help_groups: [{String.t(), [{String.t(), String.t()}]}], help_visible?: boolean(), input_focused: boolean(), model_name: String.t() | nil, prompt: String.t() | nil, prompt_completion: Minga.RenderModel.UI.AgentChat.PromptCompletion.t() | nil, prompt_cursor_col: non_neg_integer() | nil, prompt_cursor_line: non_neg_integer() | nil, prompt_line_count: non_neg_integer() | nil, prompt_vim_mode: atom() | nil, prompt_visible_rows: non_neg_integer() | nil, resident_messages: [message()], resident_truncated?: boolean(), status: atom(), thinking_level: String.t() | nil, transcript_epoch: non_neg_integer(), visible?: boolean() }
Functions
@spec resident_suffix([message()], pos_integer(), (message() -> pos_integer())) :: {[message()], boolean()}
Selects a contiguous newest resident suffix without shortening any message.