Adapters
chatcn components take plain props. Adapters turn AI SDK, AG-UI, LangGraph and assistant-ui runtimes into those props, so you keep your backend and swap only the UI.
On this page
Recorded AG-UI events folded by the reducer into chatcn messages, approval included.
Props first
Every chatcn component renders from plain data and callbacks. ToolCall takes a state, an input, an output and onApprove; it has no idea which framework produced them. That is what lets one set of components work with any agent runtime, and with static fixtures in tests and Storybook.
- Leaf components take plain props. Nothing in
components/ui/chatcnimportsai,@ag-ui/*,@langchain/*or@assistant-ui/*. - One message contract.
chatcn-typesmirrors AI SDK 7UIMessageparts: text, reasoning, tool calls with seven states, sources, files anddata-*parts. A type test pins it to the installed AI SDK on every release. - Adapters at the edge. Each runtime gets a small file that maps its messages or events to that contract and its controls to
ChatcnActions(send,stop,regenerate,approve,deny,submitToolOutput,editMessage). Blocks hide the controls whose action is missing.
// AI SDK 7
const { chatcnMessages, actions } = useChatcnChat({ transport })
<Chatbot messages={chatcnMessages} actions={actions} />
// AG-UI
const { messages, actions } = useChatcnAgUi({ agent: new HttpAgent({ url: "/api/agent" }) })
<Chatbot messages={messages} actions={actions} />
// LangGraph
const { messages, actions } = useChatcnLangGraph({ client: new Client({ apiUrl }), assistantId: "agent" })
<Chatbot messages={messages} actions={actions} />Pick an adapter
| Runtime | Install | What you get |
|---|---|---|
| AI SDK 7 | use-chatcn-chat | useChat messages already fit the contract. The hook adds ChatcnActions and resumes approvals on its own |
| AG-UI | chatcn-ag-ui, use-chatcn-ag-ui | A pure event reducer and a hook around any AG-UI agent: LangGraph, CrewAI, Mastra, Pydantic AI, ADK and more |
| LangGraph | chatcn-langgraph, use-chatcn-langgraph | Message mapping and a stream reducer for the LangGraph SDK, with human-in-the-loop interrupts as approvals |
| assistant-ui | chatcn-assistant-ui | Keep your assistant-ui runtime; render its parts with chatcn through MessagePrimitive.Parts |
How they map
| chatcn | AG-UI | LangGraph | assistant-ui |
|---|---|---|---|
text streaming | TextMessageContent | AIMessageChunk content | text part, status running |
reasoning | ReasoningMessage* | thinking and reasoning blocks | reasoning part |
input-streaming | ToolCallArgs (partial JSON) | tool_call_chunks | argsText not yet complete |
input-available | ToolCallEnd | tool_calls | args complete, no result |
approval-requested | RunFinished with outcome: interrupt | __interrupt__ (HITL middleware) | approval or interrupt on the part |
output-available | ToolCallResult | ToolMessage | result |
output-error | RunError, tool error | ToolMessage with status: error | isError |
Write your own
Any backend works if you can produce ChatcnMessage[]. Build messages from your events, keep tool calls in one assistant message per turn, and pass callbacks for the actions you support.
import type { ChatcnActions, ChatcnMessage } from "@/lib/chatcn-types"
const messages: ChatcnMessage[] = myEvents.reduce(applyMyEvent, [])
const actions: ChatcnActions = {
send: (text) => api.send(text),
stop: () => api.cancel(),
approve: (id) => api.answer(id, true),
deny: (id, reason) => api.answer(id, false, reason),
}
<Chatbot messages={messages} actions={actions} status={busy ? "streaming" : "ready"} />Notes
- Each adapter is its own registry item with its runtime as the only dependency, so you install and bundle only the one you use.
- The reducers are pure and synchronous: fold a recorded stream in a test, on the server, or in a Storybook story.
- Unknown events and part types never throw. Events are skipped, parts render the quiet fallback.