assistant-ui adapter
Keep your assistant-ui runtime and render its messages with chatcn text, reasoning and tool components, approvals included.
On this page
An assistant-ui runtime with chatcn text, reasoning and tool parts, approvals wired.
Installation
Usage
"use client"
import { MessagePrimitive } from "@assistant-ui/react"
import { toolBashRenderers } from "@/components/ui/chatcn/tool-bash"
import { toolFileEditRenderers } from "@/components/ui/chatcn/tool-file-edit"
import { createChatcnPartComponents } from "@/lib/chatcn-assistant-ui"
// Module scope: new component identities would remount every part.
const components = createChatcnPartComponents({
renderers: { ...toolBashRenderers, ...toolFileEditRenderers },
})
export function AssistantMessage() {
return (
<MessagePrimitive.Root className="flex flex-col gap-3">
<MessagePrimitive.Parts components={components} />
</MessagePrimitive.Root>
)
}Your runtime, composer, thread list and tool UIs stay as they are. Tools with their own makeAssistantToolUI keep it; every other tool renders through chatcn (tools.Fallback).
What each part becomes
| assistant-ui part | chatcn |
|---|---|
text | Markdown, streaming while the part status is running |
reasoning | Reasoning, redacted when it finishes empty |
tool-call | The renderer for its name from renderers, or the generic tool renderer |
approval on a tool call | Allow and Deny, answered with respondToApproval. Always allow picks the request's allow-always option when it has one |
interrupt (human input) | Allow and Deny, answered with resume({ approved, reason }) |
status incomplete, reason cancelled | Unfinished calls render as cancelled |
Actions for blocks
import { useChatcnAssistantUiActions } from "@/lib/chatcn-assistant-ui"
// Inside AssistantRuntimeProvider
const actions = useChatcnAssistantUiActions()
actions.send?.("Run the tests")
actions.approve?.(approvalId)| ChatcnActions | assistant-ui |
|---|---|
send(text, { files }) | aui.thread.append with text, image and file parts |
stop() | aui.thread.cancelRun() |
regenerate(messageId?) | aui.thread.message({ id }).reload(), the last assistant message by default |
approve(id), deny(id, reason) | part.respondToToolApproval, or part.resumeToolCall for a human interrupt |
submitToolOutput(toolCallId, output) | part.addToolResult(output) |
editMessage(id, text) | aui.thread.append with the edited message as sourceId, which branches |
Converting messages
import { useAuiState } from "@assistant-ui/react"
import { MessageParts } from "@/components/ui/chatcn/message-parts"
import { toChatcnMessage } from "@/lib/chatcn-assistant-ui"
// chatcn's own part renderer, grouping and thinking indicator included.
const message = useAuiState((s) => s.message)
<MessageParts message={toChatcnMessage(message)} streaming={message.status?.type === "running"} />toChatcnMessage maps text, reasoning, tool calls, sources, files, images, data parts and attachments. toChatcnToolPart maps a single tool call, for your own tool UI.
Notes
- Verified against
@assistant-ui/react0.15.30. - Leaf components never import this adapter; only your app does.