Skip to content

Messaging

Messages are the medium for user-AI interaction. Users send messages and the AI streams responses, with support for Markdown rendering, code highlighting, thinking process display, and tool call cards — the entire process is visible in real time.

TypeDescriptionRendering
💬 User MessageContent entered by the userPlain text
📝 AI ReplyText generated by AIMarkdown + code highlighting
🧠 Thinking ProcessAI’s reasoning processCollapsible, Markdown rendered
⚙️ Tool CallTool requested by AI for executionInput/output cards
📄 SummaryCompressed summary after context compaction (summary)Markdown rendered
📃 Plain TextUnformatted text content (text)Plain text
🔧 Tool MessageIndependent tool role message (tool role)Input/output cards
📩 User MessageUser input with embedded scenarios for injection (user_message)Plain text + scenario tags
📜 System PromptPersisted system-level instructions (prompt)Not directly rendered
ℹ️ System MessageSystem notificationsPlain text

MessageRole has 4 types: user, assistant, system, tool ContentType has 9 types: text, markdown, summary, thinking, toolcall_input, toolcall_output, user_message, error, prompt

Messages use a local-first strategy: stored locally first for immediate display, then synced asynchronously in the background. After sending a message, the user sees it appear in the UI with zero wait.

Sync StateUser-Visible Behavior
pendingMessage shows sending animation
syncedMessage displays normally
failedMessage shows failure marker + 🔄 retry button

💡 Retries use the same client_id, and the server idempotently deduplicates, so no duplicate messages are produced.

Each AI processing cycle constitutes a Turn, which has a well-defined lifecycle:

StateMeaningUser-Visible Behavior
PendingWaiting to executeMessage shows “Processing”
RunningCurrently executingAI streams output, tool cards flash
InterruptedWaiting for tool executionTool card shows “Executing”
CompletedNormal completionAI reply fully displayed
FailedExecution failedError message shown
CancelledUser cancelledStopped marker shown
MergedMerged into another turnNot displayed separately

AI replies arrive token by token in a stream, so users don’t have to wait for the complete response:

Streaming messages are transmitted via dual channels:

ChannelPurposeCharacteristics
Topic ChannelFirst chunk + final complete messagePersistent, supports offline recovery
Live ChannelIntermediate chunksNon-persistent, low latency, tolerant to loss

💡 The final complete message is pushed via the Topic channel, so even if intermediate chunks are lost, the complete content is eventually received.

Users can stop a running turn at any time:

The AI’s reasoning process is collapsed by default; users can expand it to view:

FeatureDescription
Collapsed by defaultSaves UI space
Click to expandView full reasoning process
Streaming animationPulse animation effect during generation
Markdown renderingSupports formatted display

Each RTC tool call is displayed as a card with input and output sections:

FeatureDescription
Sectioned DisplayInput parameters and output results shown independently
JSON FormattingParameters and results auto-pretty-printed
Status IndicatorRunning (🟠 orange pulse) / Completed (🟢 green)
Copy ButtonOne-click copy of input/output content

Tool Call Card UI

FeatureDescription
📊 SortingAscending by send time (oldest first)
📑 PaginationCursor-based pagination, loads history from server
📜 Auto-scrollAutomatically scrolls to bottom when new messages arrive
🔔 New Message AlertShows ”↓ New messages” button when user scrolls away from bottom
ScenarioHandling
Send failureMarked as failed, retry button displayed
IdempotencyUses client_id for deduplication; retries produce no duplicates
RTC failureExponential backoff retry (1s → 2s → 4s → … → 30s cap)

user_message is the 7th ContentType, supporting scenario injection — embedding structured context within user messages that the server parses and injects as system prompts into the LLM.

{
"type": "user_message",
"data": {
"text": "Please help me create a task",
"scenarios": [
{
"filepath": "/scenarios/create-task.md",
"title": "Create and Complete a Task",
"file_content": "# Create Task\n\n..."
}
],
"files": []
}
}
FieldTypeDescription
textstringMessage text content
scenariosScenarioRef[]Scenario list (optional), containing full file content
filesFileAttachment[]File attachment list (optional)
FieldTypeDescription
filepathstringScenario file path
titlestringScenario title
file_contentstringFull scenario file content (Markdown format)
FieldTypeDescription
fileidstringFile unique identifier (content identifier)
mimetypestringMIME type (e.g., image/png, application/pdf)
extraobjectExtension fields (e.g., name, size)

File ID Format Specification:

File ID is the unique identifier of file content, in the format {md5}.{ext}:

  • {md5}: 32 hexadecimal characters (lowercase a-f or digits 0-9) — the MD5 hash of the file content
  • {ext}: 1-10 alphanumeric characters for the file extension (e.g., txt, jpg, pdf)

Examples:

  • ✅ Correct: a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6.txt
  • ✅ Correct: b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7.jpg
  • ❌ Incorrect: user-550e8400.../a1b2c3d4...txt (should not include user prefix)
  • ❌ Incorrect: myfile.txt (MD5 format error)

💡 File ID vs File Key: File ID is the content identifier used by the client ({md5}.{ext}). The server constructs the full S3 Key when storing: user-{userId}/{fileid}. The client only needs to pass the File ID; the server automatically adds the user prefix for validation and storage.

💡 Injection mechanism: The server extracts scenarios from UserMessageContent.Scenarios and injects them as system messages with <scenarios> XML tags. The final message order is: [system] Attachments → [system] Scenarios → [system] Command prompts → [conversation history].

Users can attach images and text files to messages. The Agent can recognize image content (visual understanding) and read text content, enabling reasoning and responses based on attachment information.

OperationDescription
Attach buttonClick the attach icon in the input area toolbar to open the file picker (accepts image/*, text/*)
Paste uploadUse Ctrl+V / Cmd+V to paste images or files from the clipboard
File previewClick on an attached file thumbnail to open a full-screen preview modal (images support zoom, text files show content)
Upload retryWhen file upload fails, click the retry icon to manually re-upload (files are kept in memory, no need to re-select)

Upload flow: Files are first cached locally (generating a File ID), with thumbnail previews displayed immediately, then uploaded to object storage in the background. If upload fails, an error state and retry button are shown; users can manually retry or continue sending the message (sending with failed attachments is allowed).

CategoryMIME TypeMax SizeProcessing
Images (supported)JPEG, PNG, BMP, TIFF20MB raw / 5MB base64Auto-resize to 2000x2000, multi-level JPEG compression
Images (unsupported)WebP, GIF-Currently unsupported; please convert to JPEG or PNG
Texttext/*256KBUTF-8 validation, truncation if too long
  1. Read: Read raw data from object storage (limited to 20MB)
  2. Detect: Detect actual MIME type via magic bytes
  3. Decode: Auto-apply EXIF orientation correction
  4. Fast path: Return as-is if already within size limits
  5. Resize: Scale to fit within 2000x2000 bounding box using Lanczos algorithm
  6. Compress: Try JPEG quality 80→60→40→20 until base64-encoded size ≤ 5MB
  7. Fallback: If still over limit, downscale to 400x400 at lowest quality
  8. Inject: Embed as base64 in the LLM request’s image content part
  1. Read: Read text data from object storage (limited to 256KB)
  2. Validate: Verify the file content is valid UTF-8 encoding
  3. Truncate: If over 256KB, safely truncate at UTF-8 character boundary with truncation notice appended
  4. Wrap: Wrap with XML tags: <file_content name="filename" type="MIME type">content</file_content>
  5. Append: Append the wrapped text to the user message’s Content field
ConditionDescription
All files uploadedSend button is disabled when any file is still uploading (loading) or has failed (error)
Upload failure handlingFailed files can be retried, or deleted and re-uploaded

💡 File attachments are submitted with the user_message when sending. The server loads all files in parallel to improve response speed in multi-file scenarios. When users send multiple consecutive messages with images, the server merges the MultiContent (image content parts) of adjacent same-role messages to ensure all images are recognized by the LLM.

The frontend uses the rtc-token-usage Web Component to display real-time token consumption and cost information in the input area toolbar.

Display ItemData SourceDescription
Circular Progresscompression_progressCurrent context as percentage of compression threshold
Total Tokenstotal_tokensCumulative total token count
Total Costtotal_cost_usdCumulative cost in USD
Input/Outputtotal_input_tokens / total_output_tokensCategorized token statistics
Cache Hit Ratecached_read / inputCache reads as percentage of total input (capped at 99%)
Estimated Next Roundestimated_next_round_tokensEWMA-based prediction for next round
Compression Countdownrounds_until_compressionRounds remaining until auto-compression

Snake_case fields from the backend session.updated event are mapped to frontend camelCase properties:

Protocol Field (snake_case)Frontend Property (camelCase)Description
current_context_tokenscurrentContextTokensCurrent context token count
estimated_next_round_tokensestimatedNextRoundTokensEstimated next round tokens (circle progress numerator)
compression_thresholdcompressionThresholdCompression trigger threshold (circle progress denominator)
compression_progresscompressionProgressCompression progress (0-100)
rounds_until_compressionroundsUntilCompressionRounds until compression
total_tokenstotalTokensCumulative total tokens
total_cost_usdtotalCostUsdCumulative cost

Circle progress formula: estimatedNext / compressionThreshold * 100%

Progress RangeColorMeaning
< 50%🔵 BlueContext is plentiful
50% - 70%🟡 YellowApproaching compression threshold
> 70%🔴 RedCompression imminent

💡 When roundsUntilCompression is within 0-3 rounds, the tooltip footer shows “X rounds until compression (Y%)”; at 1 or fewer rounds, it escalates to warning color.