Generative UI¶
CopilotKit draws UI from what an agent streams. kaboo-react passes its
generative UI settings through to <CopilotKit> and labels what sub-agents
render, so every tier works from any agent:
| Tier | What it is | Where you configure it |
|---|---|---|
| Your components | A renderer per tool name, drawn as the call streams. | useRenderTool, or renderToolCalls (Custom tool cards) |
| A2UI | Declarative surfaces built from a catalog of your components. | a2ui, plus an activity renderer |
| Open Generative UI | A one-off visual in a sandboxed iframe. | openGenerativeUI |
| MCP Apps | An app an MCP server ships, rendered in a sandbox. | Nothing: CopilotKit's built-in renderer draws it. |
Wiring¶
```tsx no-verify import { createA2UIMessageRenderer } from "@copilotkit/react-core/v2"; import { KabooProvider } from "@pgege/kaboo-react"; import { KabooChat, withSubagentLabel } from "@pgege/kaboo-react/copilotkit"; import { catalog, theme } from "./a2ui"; import { openGenerativeUI } from "./ogui";
const a2uiRenderer = createA2UIMessageRenderer({ theme, catalog, onAction: (action) => (action.name === "add_to_dashboard" ? null : undefined), }); const ACTIVITY_RENDERERS = [withSubagentLabel(a2uiRenderer)]; const A2UI = { catalog };
export function Page({ threadId }: { threadId: string }) {
return (
a2uisends your catalog to the agent as context and turns A2UI on in the runtime. The agent callsrender_a2uiwhen its workflow allows it, and the surface paints as the call streams.renderActivityMessagestakes precedence over CopilotKit's built-in renderers. Pass your own A2UI renderer to handle actions in the browser: anonActionthat returnsnullhandles the action; one that returnsundefinedsends it to the agent, which reads it as the person's turn.withSubagentLabelwraps an activity renderer so a surface a sub-agent rendered shows the agent's name above it, as its questions do. Surfaces from the agent answering the chat stay unlabelled.openGenerativeUIturns on sandboxed visuals. Give the sandbox read-only functions that fetch data by id, and a design skill so the visual matches your app.
Keep the arrays and objects you pass stable (module-level, or memoized): CopilotKit expects stable renderer lists.
Widgets by reference¶
A catalog component should take an id — a saved query's — and fetch its own rows, rather than receive the data in the surface. The surface stays small, its figures stay live, and the same widget can move onto a dashboard unchanged.
Lab scenarios: a2ui-inline, a2ui-subagent, a2ui-action, ogui-visual, mcp-partner and viewer-parity.