Skip to main content
Cedar allows you to completely customize how messages are displayed in your chat interface. This is used for use cases like Generative UI, or any custom logic you want for the way that messages are rendered to the user. This system mirrors the architecture of Custom Response Processing.

Base Message Type

All messages in Cedar extend the BaseMessage interface from @messages/types:

Requirements for Custom Messages

To use the custom message rendering system, your message objects must have a type field. This field determines which renderer will be used to display the message.

Message Renderer Interface

Cedar uses a strongly typed MessageRenderer interface that ensures type safety throughout the rendering pipeline:
Key typing features:
  • The render function receives your narrowly typed custom message (e.g., AlertMessage)
  • The validateMessage function receives the broadly typed Message for runtime validation

Custom Message Type Definition

Cedar exports a CustomMessage<T, P> type that allows you to create type-safe custom messages:

Message Renderer Factory Function

Cedar exports a createMessageRenderer factory function to create type-safe renderers:

How Message Rendering Works Internally

The chat system uses the following process to render messages:
  1. Message Reception: When a message is added to the chat, the system examines its type field
  2. Renderer Lookup: The system searches for a registered renderer that matches the message type
  3. Validation: If a renderer is found and has a validation function, it checks if the message structure is valid
  4. Rendering: If validation passes (or no validation exists), the renderer’s render function is called
  5. Fallback: If no matching renderer is found or validation fails, the default text renderer is used

Full Implementation Examples

Here’s an example of creating a custom message renderer for an AlertMessage.

Default Message Types and Renderers

Cedar automatically handles these message types with default behavior. Each type has a corresponding default renderer:

"message" Type

  • Behavior: Standard text messages with role-based styling
  • Properties: content: string, role: 'user' | 'assistant' | 'bot'
  • Renderer: Built-in text message renderer with role-based styling
  • Warning: Overriding this type will disable default text message rendering

"setState" Type

  • Behavior: Executes state actions and displays completion status
  • Properties: stateKey: string, setterKey: string, args?: unknown
  • Default Renderer: SetStateRenderer shows setState completion with shimmer effects
SetState Factory Functions: Cedar provides special factory functions for setState messages:

"frontendTool" Type

  • Behavior: Displays frontend tool execution results with status indicators
  • Properties: toolName: string, args?: unknown, result?: unknown, error?: string, status: 'success' | 'error'
  • Default Renderer: FrontendToolRenderer shows tool execution status with result display

"progress_update" Type

  • Behavior: Shows progress indicators with shimmer effects
  • Properties: content: string, metadata.state?: 'in_progress' | 'complete' | 'error'
  • Default Renderer: ProgressUpdateRenderer with animated shimmer text

Additional Built-in Renderers

Cedar also includes renderers for streaming events:

Mastra Event Renderer

Cedar automatically receives and renders all Mastra streamed events. Any of these can be customized or overridden with your own renderers: Supported Mastra Event Types:
  • 'start' - Agent run started
  • 'step-start' - Agent step started
  • 'tool-call' - Tool being called
  • 'tool-result' - Tool call result
  • 'step-finish' - Agent step completed
  • 'tool-output' - Tool output received
  • 'step-result' - Step result available
  • 'step-output' - Step output received
  • 'finish' - Agent run completed
Customization: You can override any specific Mastra event type by creating a custom renderer with the same type. For example, to customize just the 'tool-call' event:
⚠️ Important: If you override any of these default types, you lose the built-in behavior. Ensure your custom implementation covers all cases you want to support. Cedar encourages organizing custom renderers in dedicated files for easier maintenance and debugging:
This structure keeps all Cedar-related logic organized under a cedar-os directory, making it easier to maintain, debug, and ensure consistency across your application.