import { pageMetadata } from "@/lib/page-metadata" export const metadata = pageMetadata("docs/ag-ui") # AG-UI Integration Use json-render to support [AG-UI](https://docs.copilotkit.ai/ag-ui) (Agent User Interaction Protocol) from CopilotKit.

Concept: This page demonstrates how json-render can support AG-UI. The examples are illustrative and may require adaptation for production use.

## What is AG-UI? AG-UI is an open protocol for connecting AI agents to user interfaces. It provides a standardized way for agents to render UI components, handle user input, and manage state. The protocol uses events streamed over HTTP to update the UI in real-time. ## AG-UI Event Types AG-UI defines several event types for agent-UI communication: - `TEXT_MESSAGE_START` / `TEXT_MESSAGE_CONTENT` / `TEXT_MESSAGE_END` — Streaming text messages - `TOOL_CALL_START` / `TOOL_CALL_ARGS` / `TOOL_CALL_END` — Tool/function calls - `STATE_SNAPSHOT` / `STATE_DELTA` — State updates - `CUSTOM` — Custom events for UI rendering ### Example AG-UI Event Stream ```json {"type": "RUN_STARTED", "threadId": "thread-123", "runId": "run-456"} {"type": "TEXT_MESSAGE_START", "messageId": "msg-1", "role": "assistant"} {"type": "TEXT_MESSAGE_CONTENT", "messageId": "msg-1", "delta": "Here's a dashboard for you:"} {"type": "TEXT_MESSAGE_END", "messageId": "msg-1"} {"type": "TOOL_CALL_START", "toolCallId": "tc-1", "toolCallName": "render_ui"} {"type": "TOOL_CALL_ARGS", "toolCallId": "tc-1", "delta": "{\"component\": \"Dashboard\", \"props\": {\"title\": \"Sales\"}}"} {"type": "TOOL_CALL_END", "toolCallId": "tc-1"} {"type": "RUN_FINISHED"} ``` ## Define the AG-UI Schema Define schemas for AG-UI event types: ```typescript import { z } from 'zod'; // Base event schema const BaseEvent = z.object({ type: z.string(), timestamp: z.number().optional(), }); // Text message events const TextMessageStart = BaseEvent.extend({ type: z.literal('TEXT_MESSAGE_START'), messageId: z.string(), role: z.enum(['user', 'assistant']), }); const TextMessageContent = BaseEvent.extend({ type: z.literal('TEXT_MESSAGE_CONTENT'), messageId: z.string(), delta: z.string(), }); const TextMessageEnd = BaseEvent.extend({ type: z.literal('TEXT_MESSAGE_END'), messageId: z.string(), }); // Tool call events const ToolCallStart = BaseEvent.extend({ type: z.literal('TOOL_CALL_START'), toolCallId: z.string(), toolCallName: z.string(), parentMessageId: z.string().optional(), }); const ToolCallArgs = BaseEvent.extend({ type: z.literal('TOOL_CALL_ARGS'), toolCallId: z.string(), delta: z.string(), }); const ToolCallEnd = BaseEvent.extend({ type: z.literal('TOOL_CALL_END'), toolCallId: z.string(), }); // State events const StateSnapshot = BaseEvent.extend({ type: z.literal('STATE_SNAPSHOT'), snapshot: z.record(z.unknown()), }); const StateDelta = BaseEvent.extend({ type: z.literal('STATE_DELTA'), delta: z.array(z.object({ op: z.enum(['add', 'remove', 'replace']), path: z.string(), value: z.unknown().optional(), })), }); // Custom event for UI components const CustomEvent = BaseEvent.extend({ type: z.literal('CUSTOM'), name: z.string(), value: z.unknown(), }); // Run lifecycle events const RunStarted = BaseEvent.extend({ type: z.literal('RUN_STARTED'), threadId: z.string(), runId: z.string(), }); const RunFinished = BaseEvent.extend({ type: z.literal('RUN_FINISHED'), }); const RunError = BaseEvent.extend({ type: z.literal('RUN_ERROR'), message: z.string(), code: z.string().optional(), }); // Union of all events export const AGUIEvent = z.discriminatedUnion('type', [ TextMessageStart, TextMessageContent, TextMessageEnd, ToolCallStart, ToolCallArgs, ToolCallEnd, StateSnapshot, StateDelta, CustomEvent, RunStarted, RunFinished, RunError, ]); export type AGUIEvent = z.infer; ``` ## Define the AG-UI Catalog Create a catalog for UI components that agents can render: ```typescript import { defineCatalog } from '@json-render/core'; import { schema } from '@json-render/react/schema'; import { z } from 'zod'; export const aguiCatalog = defineCatalog(schema, { components: { Container: { description: 'A container for grouping elements', props: z.object({ direction: z.enum(['row', 'column']).optional(), gap: z.enum(['none', 'sm', 'md', 'lg']).optional(), padding: z.enum(['none', 'sm', 'md', 'lg']).optional(), }), }, Card: { description: 'A card with optional title', props: z.object({ title: z.string().optional(), description: z.string().optional(), }), }, Text: { description: 'Text content', props: z.object({ content: z.string(), variant: z.enum(['body', 'heading', 'caption', 'code']).optional(), }), }, Metric: { description: 'Displays a metric value', props: z.object({ label: z.string(), value: z.union([z.string(), z.number()]), change: z.number().optional(), format: z.enum(['number', 'currency', 'percent']).optional(), }), }, Button: { description: 'Interactive button', props: z.object({ label: z.string(), variant: z.enum(['primary', 'secondary', 'outline', 'ghost']).optional(), disabled: z.boolean().optional(), }), }, Alert: { description: 'Alert message', props: z.object({ message: z.string(), type: z.enum(['info', 'success', 'warning', 'error']).optional(), }), }, // Add more components... }, actions: { submit: { description: 'Submit form data', params: z.object({ formId: z.string() }), }, navigate: { description: 'Navigate to a URL', params: z.object({ url: z.string() }), }, callback: { description: 'Trigger a callback to the agent', params: z.object({ name: z.string(), data: z.record(z.unknown()).optional(), }), }, }, }); ``` ## Build an AG-UI Event Processor Process AG-UI events and render UI components: ```tsx 'use client'; import React, { useState, useCallback } from 'react'; import { AGUIEvent } from './schema'; interface AGUIState { messages: Array<{ id: string; role: 'user' | 'assistant'; content: string; }>; toolCalls: Map; state: Record; isRunning: boolean; } export function useAGUI() { const [aguiState, setAGUIState] = useState({ messages: [], toolCalls: new Map(), state: {}, isRunning: false, }); const processEvent = useCallback((event: AGUIEvent) => { switch (event.type) { case 'RUN_STARTED': setAGUIState(prev => ({ ...prev, isRunning: true })); break; case 'RUN_FINISHED': setAGUIState(prev => ({ ...prev, isRunning: false })); break; case 'TEXT_MESSAGE_START': setAGUIState(prev => ({ ...prev, messages: [...prev.messages, { id: event.messageId, role: event.role, content: '', }], })); break; case 'TEXT_MESSAGE_CONTENT': setAGUIState(prev => ({ ...prev, messages: prev.messages.map(msg => msg.id === event.messageId ? { ...msg, content: msg.content + event.delta } : msg ), })); break; case 'TOOL_CALL_START': setAGUIState(prev => { const toolCalls = new Map(prev.toolCalls); toolCalls.set(event.toolCallId, { name: event.toolCallName, args: '' }); return { ...prev, toolCalls }; }); break; case 'TOOL_CALL_ARGS': setAGUIState(prev => { const toolCalls = new Map(prev.toolCalls); const tc = toolCalls.get(event.toolCallId); if (tc) { toolCalls.set(event.toolCallId, { ...tc, args: tc.args + event.delta }); } return { ...prev, toolCalls }; }); break; case 'STATE_SNAPSHOT': setAGUIState(prev => ({ ...prev, state: event.snapshot })); break; } }, []); return { state: aguiState, processEvent }; } ``` ## Usage Example ```tsx 'use client'; import { useAGUI } from './use-agui'; import { renderToolCallUI } from './renderer'; export function AGUIChat() { const { state, processEvent } = useAGUI(); async function startRun(prompt: string) { const response = await fetch('/api/agent', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt }), }); const reader = response.body?.getReader(); const decoder = new TextDecoder(); while (reader) { const { done, value } = await reader.read(); if (done) break; const lines = decoder.decode(value).split('\n').filter(Boolean); for (const line of lines) { const event = JSON.parse(line); processEvent(event); } } } return (
{state.messages.map(msg => (
{msg.content}
))} {Array.from(state.toolCalls.values()).map((tc, i) => (
{renderToolCallUI(tc)}
))}
{ e.preventDefault(); const input = e.currentTarget.querySelector('input'); if (input?.value) { startRun(input.value); input.value = ''; } }}>
); } ``` ## Next Learn about [OpenAPI integration](/docs/openapi) for rendering forms from API schemas.