Generative UI
Stream interactive React components as first-class typed events, and round-trip user input back to the agent.
Beyond text and tool calls, an agent can emit components — a confirm card, a choice list, a chart, a form — as first-class typed events. The client renders them as React components and sends the user's response back to the agent.
Define a component
defineAgentComponent<Kind, Data, Action>(kind) declares a component's event kind, the type of its render payload, and (optionally) the type of the interaction it sends back. It's type-only (zero runtime weight) — it makes the custom branch of AgentEvent fully typed end to end:
import { defineAgentComponent } from '@agentoria/core';
export const confirmCard = defineAgentComponent<'confirm', { title: string; body: string }, { confirmed: boolean }>('confirm');Emit from the server
Inside a runAgent hook (e.g. onToolResult), emit the component as a custom event — the payload is checked against Data:
createAgentHandler({
provider, tools,
onToolResult: (exec, emit) => {
if (exec.name === 'draft_post') emit.custom('confirm', { title: 'Publish?', body: exec.result.preview });
},
});Render on the client
Bind a React renderer with agentComponent — it returns a one-entry map (kind → renderer). The renderer gets the typed data plus two response channels: act (a typed action) and send (a plain user message):
import { agentComponent } from '@agentoria/ui';
const Confirm = agentComponent(confirmCard, ({ data, act }) => (
<div>
<b>{data.title}</b>
<p>{data.body}</p>
<button onClick={() => act({ confirmed: true })}>Publish</button>
<button onClick={() => act({ confirmed: false })}>Cancel</button>
</div>
));
// agentComponents is a map — spread several with { ...A, ...B }
<ChatPanel endpoint="/api/agent" agentComponents={Confirm} />;act(action)round-trips a typed action to the agent (as the next turn'saction), so the conversation continues from the user's choice.send(message)sends a plain user message — for input-only widgets (quick-reply chips, pickers) whose choice just continues the chat:
const quickReplies = defineAgentComponent<'quick_replies', { options: { label: string; value: string }[] }>('quick_replies');
const QuickReplies = agentComponent(quickReplies, ({ data, send }) => (
<>{data.options.map((o) => <button key={o.value} onClick={() => send(o.value)}>{o.label}</button>)}</>
));Built-ins
@agentoria/ui ships ready-made components — ConfirmCard, ChoiceList, Citations — pre-bound as the builtinAgentComponents map:
import { builtinAgentComponents } from '@agentoria/ui';
<ChatPanel endpoint="/api/agent" agentComponents={builtinAgentComponents} />;