Chat UI SDK
@openbench/chat-ui is a standalone React component library for rendering interactive chat interfaces powered by A2UI v0.10 JSONL (Google's declarative JSON streaming UI protocol).
A2UI v0.10 Key Points
- 4 message types:
createSurface,updateComponents,updateDataModel,deleteSurface - Every message has
"version": "v0.10" - Components are flat objects:
{id, component, ...properties}(NOT nested) - Root component must have
id: "root" - Data binding via JSON Pointer (RFC 6901):
{"path": "/user/name"} - Actions:
{event: {name, context}}or{functionCall: {call, args}} - 14 standard functions for validation and formatting
checkssystem for input validation
AG-UI Streaming Events
The frontend receives AG-UI events via SSE, including progressive text streaming:
| Event | Purpose |
|---|---|
TEXT_MESSAGE_START |
Begin accumulating message content |
TEXT_MESSAGE_CONTENT |
Append text delta to message buffer (token-by-token) |
TEXT_MESSAGE_END |
Finalize text message |
STEP_STARTED / STEP_FINISHED |
Track processing steps |
CUSTOM(name="a2ui") |
A2UI rendering messages (surfaces, components) |
RUN_STARTED / RUN_FINISHED |
Run lifecycle |
The useChat hook handles all events automatically. Text appears progressively via TEXT_MESSAGE_CONTENT deltas, then rich content (charts, files) renders via A2UI CUSTOM events.
Package Location
packages/chat-ui/
├── src/
│ ├── index.ts # Public API exports
│ ├── types.ts # All TypeScript interfaces
│ ├── core/ # No React dependency
│ │ ├── transport.ts # SSE + REST client
│ │ ├── message-processor.ts # A2UI v0.10 JSONL parser
│ │ ├── chat-store.ts # Zustand store
│ │ └── utils.ts # Helpers
│ ├── a2ui/ # A2UI rendering
│ │ ├── surface-renderer.tsx
│ │ ├── catalog.ts # Component registry
│ │ ├── data-binding.ts # JSON Pointer resolver + function evaluator
│ │ ├── standard/ # 18 standard A2UI components
│ │ └── custom/ # 6 OpenBench extensions
│ ├── components/ # Pre-built chat UI
│ └── hooks/ # React hooks
├── styles/
│ └── chat-ui.css # Default styles
└── tests/
Usage Patterns
Drop-in (full chat page)
import { ChatProvider, ChatPanel, SessionSidebar } from '@openbench/chat-ui';
import '@openbench/chat-ui/styles/chat-ui.css';
function ChatPage() {
return (
<ChatProvider config={{ streamUrl: '/awp' }}>
<div className="flex h-screen">
<SessionSidebar />
<ChatPanel className="flex-1" />
</div>
</ChatProvider>
);
}
Custom UI with hooks
import { useChat } from '@openbench/chat-ui';
function MyChat() {
const { messages, sendMessage, isStreaming } = useChat({
streamUrl: '/awp',
});
return (
<div>
{messages.map(m => <div key={m.id}>{m.content}</div>)}
<button => sendMessage('Hello!')}>Send</button>
</div>
);
}
Extend A2UI catalog
import { registerCustomComponent } from '@openbench/chat-ui';
registerCustomComponent('MyWidget', MyWidgetComponent);
A2UI Component Catalog
Standard (18 from A2UI v0.10)
Text, Image, Icon, Video, AudioPlayer, Row, Column, List, Card, Tabs, Modal, Divider, Button, TextField, CheckBox, ChoicePicker, Slider, DateTimeInput
Custom (6 OpenBench extensions)
| Component | Library | Purpose |
|---|---|---|
ObChart |
Recharts | Bar, line, pie, scatter, area charts |
ObFileCard |
Custom | File preview card with download |
ObCodeBlock |
Shiki | Syntax-highlighted code blocks |
ObMarkdown |
react-markdown | Rich markdown rendering |
ObTable |
Custom | Structured tabular data display |
ObCallout |
Custom + react-markdown | Styled callout boxes (info, success, warning) |
Note: AudioPlayer and Video are standard A2UI components -- no custom wrappers needed.
Standard Functions (14)
Validation: required, regex, length, numeric, email
Formatting: formatString, formatNumber, formatCurrency, formatDate, pluralize
Navigation: openUrl
Logic: and, or, not
SurfaceRenderer
Converts A2UI v0.10 adjacency list to React component tree:
import { SurfaceRenderer } from '@openbench/chat-ui';
<SurfaceRenderer
surface={a2uiSurface}
=> transport.send(action)}
/>
Processing:
- Index components by ID in Map
- Find component with
id: "root" - Look up
componenttype in catalog -> React component - Resolve Dynamic* properties (literal | DataBinding | FunctionCall)
- Resolve ChildList (array of IDs or template with data path)
- Recursively render children
- Wire action handlers (resolve context, dispatch A2UIAction)
- Handle two-way binding for input components
Design System
Notion-inspired, monochrome, icon-driven. No emojis.
- Colors: Carbon (#1a1a1a) on White (#ffffff), Gray scale for hierarchy
- Icons: Lucide React (clean, consistent, 1px stroke)
- Typography: System font stack (Inter where available)
- Borders: 1px solid rgba(0,0,0,0.08) -- subtle, not heavy shadows
- Spacing: 4px base unit, consistent padding/margin multiples
See docs/DESIGN_SYSTEM.md for full design tokens.
Build Commands
cd packages/chat-ui
pnpm install # Install dependencies
pnpm dev # Dev server
pnpm build # Build library (ESM + .d.ts)
pnpm tsc --noEmit # Type check
pnpm vitest # Run tests
Anti-Patterns
DO NOT:
- Import from internal paths -- use public API from
@openbench/chat-ui - Use emojis in the UI -- use Lucide icons exclusively
- Add heavy shadows or gradients -- keep Notion-like flat, subtle aesthetic
- Skip data-binding resolution -- always resolve JSON Pointers before rendering
- Create A2UI components without registering in catalog -- use
registerCustomComponent() - Mix CSS-in-JS with plain CSS -- use plain CSS with CSS custom properties
- Use
"type"on components -- the correct A2UI field is"component" - Nest properties inside a
"properties"dict -- A2UI properties are flat - Use invented message types like
surfaceUpdateorbeginRendering - Forget to implement standard functions in the data-binding evaluator
Cross-References
- Chat Layer: Python backend that produces A2UI v0.10 JSONL -> see
chat-layerskill - Composing Workflows: ChatLayer L2 composition -> see
composing-workflowsskill - Testing: Component and hook tests -> see
testing-openbenchskill
For architecture details, see docs/CHAT_UI_ARCHITECTURE.md
For design tokens, see docs/DESIGN_SYSTEM.md
Converted and distributed by TomeVault — claim your Tome and manage your conversions.