Chat Events (chat:*)¶
The chat:* namespace handles all communication between the Python ChatManager and the chat frontend. Events flow in both directions: user messages travel JS → Python, while assistant responses, artifacts, and state updates travel Python → JS.
Availability
Chat events are only active when content is rendered via app.show_chat() or the ChatManager component. They require the chat frontend assets.
User Messages (JS → Python)¶
| Event | Payload | Description |
|---|---|---|
chat:user-message |
{messageId, text, threadId, timestamp, attachments?} |
User sends a message. messageId (e.g. "msg_abc12345") is generated by the frontend and stored on both sides so the same message can be addressed later for editing or resending. |
chat:stop-generation |
{threadId, messageId} |
User clicks stop button to cancel in-progress generation. Sets cooperative cancel event. |
chat:edit-message |
{messageId, threadId, text} |
User submits edited text for a prior user message. Backend replaces the message, drops everything after it, asks the provider to forget its own state for that thread, and re-runs generation under the same messageId. |
chat:resend-from |
{messageId, threadId} |
User re-runs generation starting at a specific user message. The target user message and everything after it are dropped, then regeneration runs. |
chat:slash-command |
{command, args, threadId} |
User submits a /command from the input bar (e.g., /clear, /export). |
chat:input-response |
{text, requestId, threadId} |
User responds to a free-form chat:input-required prompt mid-stream (text / buttons / radio). Permission decisions use chat:permission-response instead. |
chat:request-state |
{} |
Frontend requests full state snapshot on initialization. |
chat:request-history |
{threadId, limit} |
Frontend requests message history for a thread. |
chat:user-message attachment structure:
{
"type": "file" | "widget",
"name": str,
"path": str, # Desktop only (filesystem path)
"content": str, # Browser/inline (file content)
"widgetId": str, # For widget attachments
"componentId": str
}
Message identifiers¶
Every message the chat manager stores carries an id field alongside
role and text:
{"id": "msg_abc12345", "role": "user", "text": "..."}
{"id": "msg_a1b2c3d4", "role": "assistant", "text": "..."}
User-message ids are generated on the frontend and passed through
chat:user-message; assistant-message ids are generated by the manager
and used as messageId on every stream chunk. chat:edit-message and
chat:resend-from address messages by this id — the UI's edit/resend
buttons send the id from the data-msg-id attribute on the bubble.
Thread Management (JS → Python)¶
| Event | Payload | Description |
|---|---|---|
chat:thread-create |
{title} |
Create a new conversation thread. |
chat:thread-switch |
{threadId} |
Switch active thread and replay its message history. |
chat:thread-delete |
{threadId} |
Delete a thread and switch to the next available one. |
chat:thread-rename |
{threadId, title} |
Rename a thread. |
Settings & Todos (JS → Python)¶
| Event | Payload | Description |
|---|---|---|
chat:settings-change |
{key, value} |
User changed a settings menu item (e.g., temperature slider, model select). |
chat:todo-clear |
{} |
User dismissed the todo list above the input bar. |
chat:permission-response |
{toolCallId, optionId, threadId} |
User clicked one of the options offered by a chat:permission-request. Resumes the paused generation with the chosen option id. |
Assistant Responses (Python → JS)¶
| Event | Payload | Description |
|---|---|---|
chat:assistant-message |
{messageId, text, threadId, role?, stopped?} |
Complete (non-streamed) assistant message. Also used to replay history on thread switch. |
chat:stream-chunk |
{messageId, chunk, done, stopped?} |
Incremental text chunk during streaming. Flushed every 30 ms or 300 characters. |
chat:typing-indicator |
{typing, threadId?} |
Show or hide the typing indicator before/after streaming. |
chat:generation-stopped |
{messageId, partialContent} |
Generation was cancelled or stopped by the user or system. |
chat:messages-deleted |
{threadId, messageIds: [str], editedMessageId?, editedText?} |
The backend truncated the thread — either because the user hit Edit or Resend on a prior message. The frontend drops every bubble whose id is in messageIds, removes any thinking/tool blocks tied to those message ids, and — if editedMessageId/editedText are present — re-renders the edited user message in place with the new text. |
Reasoning & Status (Python → JS)¶
| Event | Payload | Description |
|---|---|---|
chat:thinking-chunk |
{messageId, text, threadId} |
Incremental reasoning/thinking text (rendered in a collapsible block). |
chat:thinking-done |
{messageId, threadId} |
Thinking stream complete — collapses the thinking block and shows character count. |
chat:status-update |
{messageId, text, threadId} |
Transient status message (e.g., "Searching..."). Shown inline, not stored in history. The manager emits this with text: "" at the end of every stream to clear any persistent banner left behind by provider updates (e.g. LangGraph's "Thinking..." status). |
Tool Use (Python → JS)¶
| Event | Payload | Description |
|---|---|---|
chat:tool-call |
{messageId, toolId, toolCallId, title, name, kind, status, threadId} |
Announces a tool invocation. Rendered as a collapsible <details> card with a spinner. Emitted when ToolCallUpdate(status="in_progress") is yielded. |
chat:tool-result |
{messageId, toolId, toolCallId, name, kind, status, result, isError, threadId} |
Final result of the invocation. Frontend looks up the existing tool-call card by toolId (data-tool-id attribute), removes the spinner, updates the status icon, and appends the result text inside the card. Emitted when ToolCallUpdate(status="completed") or ToolCallUpdate(status="failed") is yielded — the manager routes by status so a second "tool-call" event is never created for the same run. |
The toolId and toolCallId fields carry the same value; both are
emitted so handlers written against older PyWry releases (which used
toolCallId only) keep working while the newer data-tool-id DOM
attribute also gets populated.
Interactive Input (Python → JS)¶
| Event | Payload | Description |
|---|---|---|
chat:input-required |
{messageId, threadId, requestId, prompt, placeholder?, inputType?, options?} |
Pause streaming to request free-form user input (inputType: "text" / "buttons" / "radio"). Typically emitted by callback / custom providers. Response arrives on chat:input-response. |
chat:permission-request |
{toolCallId, title, options: [{id, label, kind?}], requestId, threadId} |
Pause streaming to ask the user to approve / deny a tool invocation. Payload mirrors the ACP PermissionRequestUpdate fields (no prompt / placeholder / description). Response arrives on chat:permission-response. |
Handler pattern for permission requests:
from pywry.chat.updates import PermissionRequestUpdate
from pywry.chat.session import PermissionOption
def my_handler(messages, ctx):
yield PermissionRequestUpdate(
toolCallId="call_1",
title="Execute deployment script",
options=[
PermissionOption(id="allow_once", label="Allow"),
PermissionOption(id="reject_once", label="Reject"),
],
)
Rich Content (Python → JS)¶
| Event | Payload | Description |
|---|---|---|
chat:artifact |
{messageId, artifactType, title, threadId, ...} |
Rich content artifact (code, chart, table, image, etc.). |
chat:citation |
{messageId, url, title, snippet, threadId} |
Source citation/reference link. |
chat:todo-update |
{items} |
Push a todo list above the input bar. Not stored in history. |
chat:plan-update |
{entries: [{content, priority, status}]} |
Push a structured plan (to-do card) above the input bar. Emitted by agent providers that expose an explicit planning phase (e.g. Deep Agents write_todos). |
Artifact types and type-specific fields:
artifactType |
Additional Fields |
|---|---|
code |
content, language |
markdown |
content |
html |
content |
table |
rowData, columns, columnTypes, columnDefs?, gridOptions?, height |
plotly |
figure, height |
image |
url, alt |
json |
data |
Todo item structure:
State & Configuration (Python → JS)¶
| Event | Payload | Description |
|---|---|---|
chat:state-response |
{threads, activeThreadId, messages, settingsItems, contextSources} |
Full state snapshot in response to chat:request-state. |
chat:clear |
{threadId?} |
Clear all messages from the chat display. |
chat:update-thread-list |
{threads} |
Refresh the sidebar thread list after create/delete/rename. |
chat:switch-thread |
{threadId} |
Tell the frontend to switch the active thread. |
chat:load-assets |
{scripts, styles} |
Lazy-inject AG Grid or Plotly libraries on first artifact of that type. |
chat:register-command |
{name, description} |
Register a slash command in the input autocomplete palette. |
chat:register-settings-item |
{id, label, type, value, options?, min?, max?, step?} |
Register a settings menu item in the gear dropdown. |
chat:context-sources |
{sources} |
List of dashboard components available as @-mentionable context sources. |
chat:update-settings |
{key: value, ...} |
Push updated settings values to the frontend menu. |
chat:config-update |
{options: [{id, label, description?}]} |
Update the registered command palette when a provider adjusts its slash-command surface mid-session. |
chat:mode-update |
{currentModeId, availableModes: [ModeInfo]} |
Broadcast a change in the active agent mode (e.g. planner / builder / chat). The mode picker in the chat header rerenders from this payload. |
chat:commands-update |
{commands: [{name, description}]} |
Replace the full slash-command palette. Used when the provider resets or switches its command set. |
Settings item types: action, toggle, select, range, separator