Real-time AI agent chat with WebSocket streaming, markdown rendering, tool call display, and session management.
!Chat Interface
A chat message travels through 5 layers before reaching the browser:
| File | Layer | Responsibility |
|------|-------|----------------|
| aiui/plugins/views/chat.js | Frontend | WS connection, DOM rendering, session history |
aiui/plugins/views/chat.js
| src/praisonaiui/features/chat.py | Backend | ChatManager, _run_and_broadcast, history API |
src/praisonaiui/features/chat.py
_run_and_broadcast
| src/praisonaiui/server.py | Backend | SSE streaming path (run_agent endpoint) |
src/praisonaiui/server.py
run_agent
| src/praisonaiui/providers/__init__.py | Provider | SDK → RunEvent bridge, _run_direct_mode |
src/praisonaiui/providers/__init__.py
_run_direct_mode
| src/praisonaiui/datastore.py | Storage | MemoryDataStore / JSONFileDataStore |
src/praisonaiui/datastore.py
MemoryDataStore
JSONFileDataStore
| src/praisonaiui/provider.py | Protocol | RunEventType enum definition |
src/praisonaiui/provider.py
RunEventType
Agents created via YAML config, CRUD API, jobs, or channel bots automatically get tools resolved through the ToolResolver. Tool names in config are resolved to callable Python functions from 4 sources:
ToolResolver
1. Local tools.py file (backward compatibility)
tools.py
2. praisonaiagents.tools.TOOL_MAPPINGS (built-in tools)
praisonaiagents.tools.TOOL_MAPPINGS
3. praisonai_tools package (community tools)
praisonai_tools
4. Tool registry (programmatically registered tools)
Agents created via POST /api/agents also support tools:
POST /api/agents
| Component | Tool Resolution |
|-----------|----------------|
| Gateway _create_agents_from_config() | ✅ ToolResolver |
_create_agents_from_config()
| Integration create_gateway_from_yaml() | ✅ ToolResolver |
create_gateway_from_yaml()
| Provider _get_or_create_agent() | ✅ Default tools |
_get_or_create_agent()
| Channel bot _start_channel_bot() | ✅ Default tools |
_start_channel_bot()
| Jobs _execute_job() fallback | ✅ ToolResolver |
_execute_job()
| CRUD agents _run() fallback | ✅ ToolResolver |
_run()
| CRUD agents _sync_to_gateway() | ✅ ToolResolver |
_sync_to_gateway()
Assistant messages are rendered with full markdown support:
code
!Tool Call Streaming
When agents use tools, each call is displayed as a collapsible card:
Tool calls go through 4 stages: emit → enrich → dedup → persist.
1. Emit — The SDK fires TOOL_CALL_START / DELTA_TOOL_CALL / TOOL_CALL_END events via StreamEventEmitter. The provider maps these to RunEvent types (TOOL_CALL_STARTED, TOOL_CALL_COMPLETED).
TOOL_CALL_START
DELTA_TOOL_CALL
TOOL_CALL_END
StreamEventEmitter
RunEvent
TOOL_CALL_STARTED
TOOL_CALL_COMPLETED
2. Enrich — _run_and_broadcast() in chat.py enriches each tool call event with display-friendly fields via _enrich_tool_payload():
_run_and_broadcast()
chat.py
_enrich_tool_payload()
| Field | Source | Example |
|-------|--------|---------|
| icon | Mapped from tool name | 🔍, 📝, 💾 |
icon
| description | Generated from name + args | "🔍 Searching for 'Django latest version'" |
description
| step_number | Auto-incrementing counter per run | 1, 2, 3 |
step_number
| formatted_result | Truncated result string | "✓ Done" |
formatted_result
| tool_call_id | SDK-assigned or UUID fallback | "call_abc123" |
tool_call_id
3. Dedup — Both DELTA_TOOL_CALL (stream) and hook callbacks fire for the same tool call. The handler uses _seen_tool_started / _seen_tool_completed sets keyed by tool_call_id and name to suppress duplicates. A re-broadcast with has_complete_args=True is allowed to update the description with keyword-rich text.
_seen_tool_started
_seen_tool_completed
name
has_complete_args=True
4. Persist — After the run completes, all enriched tool calls are merged by tool_call_id into collected_tool_calls and saved alongside the assistant message:
collected_tool_calls
Tool calls are persisted alongside assistant messages in the datastore. When a session is reloaded from history, tool call steps are reconstructed from the toolCalls array on each assistant message.
toolCalls
Assistant message schema in the datastore:
chat_abort
Upload files to include with your chat messages — see Attachments for details.
Chat history is persisted via the BaseDataStore interface (datastore.py). Two implementations are available:
BaseDataStore
datastore.py
| DataStore | Persistence | Default |
|-----------|-------------|--------|
| MemoryDataStore | Volatile (in-memory only) | Yes (fallback) |
| JSONFileDataStore | Disk (~/.praisonaiui/sessions/) | Yes (when data_dir configured) |
~/.praisonaiui/sessions/
data_dir
Each session is a JSON file containing:
Messages are appended via add_message() and retrieved via get_messages(). Both methods are async to support future database backends.
add_message()
get_messages()
async
The content field on assistant messages stores the SDK's final response from agent.chat() — the same text displayed by finalizeDelta() in the live view. This ensures the reloaded view matches what the user saw during streaming.
content
agent.chat()
finalizeDelta()
> [!IMPORTANT]
> During streaming, text tokens are accumulated in full_response. However, when RUN_COMPLETED arrives with event.content (the SDK's authoritative return value), it replaces the accumulated tokens for storage. This prevents intermediate narrative text from being duplicated on reload.
full_response
RUN_COMPLETED
event.content
All events include session_id and run_id.
session_id
run_id
| Endpoint | Method | Description |
|----------|--------|-------------|
| /api/chat/send | POST | Send a message (non-streaming) |
/api/chat/send
| /api/chat/history/{session_id} | GET | Get message history for a session |
/api/chat/history/{session_id}
| /api/chat/abort | POST | Abort an active run |
/api/chat/abort
| /api/chat/ws | WebSocket | Real-time chat streaming |
/api/chat/ws
Response includes toolCalls on assistant messages: