A local React + Node workbench for testing an Agentic UI stack from a real frontend surface.
The app posts a run request to a local agent endpoint, receives Server-Sent Event frames, and renders those frames into:
- a live chat transcript
- an event timeline
- tool call cards
- shared state from
STATE_SNAPSHOTandSTATE_DELTAJSON Patch updates - generated UI surfaces from A2UI-style declarative component trees
- interrupt-driven approval flows that pause and resume a run
npm install
npm run devOpen:
http://127.0.0.1:5173/
The Vite dev server proxies /api/* to the local agent server at:
http://127.0.0.1:4317
npm run build
npm run lintBackend health check:
Invoke-RestMethod -Uri http://127.0.0.1:4317/api/healthSmoke-test the stream:
@'
const res = await fetch('http://127.0.0.1:4317/api/agent/run', {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({ scenario: 'research', prompt: 'smoke test' })
});
const text = await res.text();
console.log(text.split('\n\n').filter(Boolean).length);
'@ | node --input-type=moduleExpected result for the research scenario: 17 SSE frames, beginning with RUN_STARTED and ending with RUN_FINISHED.
The demo emits event names from @ag-ui/core:
RUN_STARTEDSTATE_SNAPSHOTMESSAGES_SNAPSHOTTEXT_MESSAGE_STARTTEXT_MESSAGE_CONTENTTEXT_MESSAGE_ENDTOOL_CALL_STARTTOOL_CALL_ARGSTOOL_CALL_ENDTOOL_CALL_RESULTSTATE_DELTARUN_FINISHEDCUSTOMevents withA2UI_SURFACE_CREATED,INTERRUPT_REQUESTED, andINTERRUPT_RESUMEDnames
STATE_DELTA uses JSON Patch-shaped operations and the frontend applies add, replace, and remove.
The Generated UI scenario emits a declarative surface shaped like A2UI:
{
"surfaceId": "surface-dashboard",
"components": [
{ "id": "root", "type": "card", "children": ["intro", "progress"] },
{ "id": "intro", "type": "text", "text": "/project/summary" },
{ "id": "progress", "type": "progress-bar", "value": "/project/progress" }
],
"dataModel": {
"project": {
"summary": "Generated from a natural-language request.",
"progress": 0.68
}
}
}The frontend renders the surface with a local React widget registry in src/a2ui.tsx. The agent sends data, not executable UI code.
The Approval interrupt scenario:
- Emits a payment confirmation surface.
- Emits
CUSTOMeventINTERRUPT_REQUESTED. - Keeps the SSE run open while waiting.
- The frontend posts the user's decision to:
POST /api/agent/resume
- The backend emits
INTERRUPT_RESUMED, patches shared state, and finishes the run.
server/index.mjs local AG-UI-style SSE agent endpoint
src/App.tsx React workbench UI, stream parser, event reducer
src/a2ui.tsx A2UI-style safe React widget registry
src/App.css app layout and responsive UI
src/index.css global tokens and base styles
vite.config.ts Vite proxy to the local agent server
- The backend is a deterministic simulator, not a live LLM agent.
- Generated UI is A2UI-style and intentionally local; it is not using the official A2UI SDK yet.
- The stream uses AG-UI event types and SSE framing, but it does not yet use an AG-UI client transport abstraction.
- There is no persistence between runs.
- Interrupt resume state is in memory only. Restarting the Node server clears pending approvals.