Skip to content
AdaptersAdaptersMenu

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.

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.

  1. Leaf components take plain props. Nothing in components/ui/chatcn imports ai, @ag-ui/*, @langchain/* or @assistant-ui/*.
  2. One message contract. chatcn-types mirrors AI SDK 7 UIMessage parts: text, reasoning, tool calls with seven states, sources, files and data-* parts. A type test pins it to the installed AI SDK on every release.
  3. 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.
The same block on three runtimes
// 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

RuntimeInstallWhat you get
AI SDK 7use-chatcn-chatuseChat messages already fit the contract. The hook adds ChatcnActions and resumes approvals on its own
AG-UIchatcn-ag-ui, use-chatcn-ag-uiA pure event reducer and a hook around any AG-UI agent: LangGraph, CrewAI, Mastra, Pydantic AI, ADK and more
LangGraphchatcn-langgraph, use-chatcn-langgraphMessage mapping and a stream reducer for the LangGraph SDK, with human-in-the-loop interrupts as approvals
assistant-uichatcn-assistant-uiKeep your assistant-ui runtime; render its parts with chatcn through MessagePrimitive.Parts

How they map

chatcnAG-UILangGraphassistant-ui
text streamingTextMessageContentAIMessageChunk contenttext part, status running
reasoningReasoningMessage*thinking and reasoning blocksreasoning part
input-streamingToolCallArgs (partial JSON)tool_call_chunksargsText not yet complete
input-availableToolCallEndtool_callsargs complete, no result
approval-requestedRunFinished with outcome: interrupt__interrupt__ (HITL middleware)approval or interrupt on the part
output-availableToolCallResultToolMessageresult
output-errorRunError, tool errorToolMessage with status: errorisError

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.

tsx
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.