Agentoria

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's action), 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} />;

On this page