Skip to content

Feature: Add support for registering custom message types with their own dedicated renderers or components #74

@ankushchhabradelta4infotech-ai

Description

Summary

Add support for registering custom message types with their own dedicated renderers, beyond the current closed union of "user" | "assistant" | "system" | "tool".

Problem / Use Case

The ChatMessage type currently enforces a closed literal union for role:

type ChatMessage = {
  role: "user" | "assistant" | "system" | "tool"
  // ...
}

There is no mechanism to register a new message type (e.g. "plan", "approval", "error") with its own renderer. This is a blocker for building specialized conversation flows where certain messages need distinct UI treatment that doesn't fit into the existing four roles.

For example:

  • A planning agent wants to emit a "plan" message that renders a structured <PlanApproval /> card
  • An error handler wants to emit an "error" message that renders a styled error UI
  • A workflow agent wants to emit a "form" message that renders an interactive input form

None of these map cleanly to the existing roles. The only workaround today is to parse special marker tags out of assistant text content inside renderMessage and manually branch on them — which is fragile and forces all custom rendering logic into a single centralized callback.

Proposed Solution

Allow developers to register custom message types with their own renderers, either via a prop on Chat / CopilotChat or via a hook:

// Option A: via prop
<CopilotChat
  customMessageRenderers={{
    plan: ({ message }) => <PlanApproval plan={JSON.parse(message.content)} />,
    error: ({ message }) => <ErrorCard message={message.content} />,
  }}
/>
// Option B: via hook (co-located with the component)
useCopilotMessageRenderer({
  type: "plan",
  render: ({ message }) => <PlanApproval plan={JSON.parse(message.content)} />
})

The SDK would automatically render the matching component when a message with that custom type appears in the thread, without requiring manual branching inside renderMessage.

Alternatives Considered

  • renderMessage callback — already available but forces all custom rendering into a single callback with manual if/switch branching. Not scalable and not co-located with the component that handles each type.
  • toolRenderers — only applies to tool execution messages. Not suitable for messages that are not tool calls but still need custom rendering.

Additional Context

The role field being a closed literal union makes the type system actively work against extensibility. Even if the server sends a message with a custom role, the SDK has no way to handle it — it either falls through to a default renderer or breaks. Opening this up with a registration mechanism would make the SDK much more composable for complex agent UIs.


Before submitting:

  • I've searched existing issues to make sure this isn't a duplicate
  • I've read the documentation

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions