Copied to Clipboard
Lint:
xbill@penguin:~/currency-agent-agui$ make lint
Running linting checks (ruff check + format)...
uv run ruff check .
All checks passed!
uv run ruff format --check .
21 files already formatted
Test:
xbill@penguin:~/currency-agent-agui$ make test
Running tests...
make stop
make[1]: Entering directory '/home/xbill/currency-agent-agui'
Stopping servers...
make[1]: *** [Makefile:68: stop] Killed
make[1]: Leaving directory '/home/xbill/currency-agent-agui'
make: [Makefile:194: test] Error 2 (ignored)
make start
make[1]: Entering directory '/home/xbill/currency-agent-agui'
Starting MCP Server in background...
Waiting for MCP Server to initialize...
Starting A2A Agent Server in background...
Starting AG-UI React Frontend UI in background...
Starting AG-UI React Frontend Agent in background...
Services started. Logs: mcp.log, agent.log, react-ui.log, react-agent.log
make[1]: Leaving directory '/home/xbill/currency-agent-agui'
uv run pytest
================================================ test session starts =================================================
platform linux -- Python 3.13.13, pytest-9.0.3, pluggy-1.6.0
rootdir: /home/xbill/currency-agent-agui
configfile: pyproject.toml
plugins: asyncio-1.3.0, anyio-4.10.0
asyncio: mode=Mode.STRICT, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function
collected 17 items
Time to Start some Currency Arbitrage!
The servers are started:
xbill@penguin:~/currency-agent-agui$ make start
Starting MCP Server in background...
Waiting for MCP Server to initialize...
Starting A2A Agent Server in background...
Starting AG-UI React Frontend UI in background...
Starting AG-UI React Frontend Agent in background...
Services started. Logs: mcp.log, agent.log, react-ui.log, react-agent.log
xbill@penguin:~/currency-agent-agui$
xbill@penguin:~/currency-agent-agui$ make status
Checking status of background services...
MCP Server: Running (PID 5398 5505 )
A2A Agent Server: Running (PID 5508 5665 )
Frontend Server: Stopped
AG-UI Agent: Running (PID 5769 )
AG-UI React UI: Running (PID 5608 )
and checked end-to-end:
xbill@penguin:~/currency-agent-agui$ make react-test
Running React UI & Agent E2E tests...
make stop
make[1]: Entering directory '/home/xbill/currency-agent-agui'
Stopping servers...
make[1]: *** [Makefile:68: stop] Killed
make[1]: Leaving directory '/home/xbill/currency-agent-agui'
make: [Makefile:226: react-test] Error 2 (ignored)
make start
make[1]: Entering directory '/home/xbill/currency-agent-agui'
Starting MCP Server in background...
Waiting for MCP Server to initialize...
Starting A2A Agent Server in background...
Starting AG-UI React Frontend UI in background...
Starting AG-UI React Frontend Agent in background...
Services started. Logs: mcp.log, agent.log, react-ui.log, react-agent.log
make[1]: Leaving directory '/home/xbill/currency-agent-agui'
uv run python3 frontend-react/scripts/test_react_e2e.py
==================================================
🚀 Running React UI & Agent End-to-End Test
==================================================
=== Test Case 1: Checking Servers Health ====
✓ React Agent on port 8008 is healthy.
✓ React UI Server on port 3000 is healthy and reachable.
=== Test Case 2: Streaming Query to Currency Agent via Next.js ====
i Sending POST request to Next.js API route: [http://localhost:3000/api/copilotkit/agent/currency\_agent/run](http://localhost:3000/api/copilotkit/agent/currency_agent/run)
✓ Connected to SSE stream. Parsing events...
i Event: RUN_STARTED
i Event: TOOL_CALL_START -> get_exchange_rate
✓ SSE Stream completed successfully.
✓ The agent successfully invoked a tool during execution.
i Agent response:
<a2ui-json>
{
"version": "v0.9",
"createSurface": {
"surfaceId": "currency_conversion_surface",
"catalogId": "https://a2ui.org/specification/v0_9/basic_catalog.json",
"theme": {
"agentDisplayName": "Currency Agent"
}
}
}
</a2ui-json>
<a2ui-json>
{
"version": "v0.9",
"updateComponents": {
"surfaceId": "currency_conversion_surface",
"components": [
{
"id": "root",
"component": "Card",
"child": "conversion_text"
},
{
"id": "conversion_text",
"component": "Text",
"text": "100 USD is equal to 85.91 EUR."
}
]
}
}
</a2ui-json>
✓ Agent streamed content back to UI successfully.
==================================================
🎉 ALL REACT E2E TEST CHECKS PASSED SUCCESSFULLY!
xbill@penguin:~/currency-agent-agui$ make start
Starting MCP Server in background...
Waiting for MCP Server to initialize...
Starting A2A Agent Server in background...
Starting AG-UI React Frontend UI in background...
Starting AG-UI React Frontend Agent in background...
Services started. Logs: mcp.log, agent.log, react-ui.log, react-agent.log
xbill@penguin:~/currency-agent-agui$ make test-agui
Running AG-UI CopilotKit Validator...
uv run python3 frontend-react/scripts/validate_agui.py
i Both port 3000 (Next.js) and 8008 (React Agent) are open. Defaulting validation to 'nextjs'.
==================================================
🚀 Running AG-UI CopilotKit Validator on target: NEXTJS
==================================================
=== Test Case 1: Health & Reachability ====
✓ Next.js server is reachable at [http://localhost:3000/api/copilotkit](http://localhost:3000/api/copilotkit) (HTTP 405)
=== Test Case 2: Capabilities Discovery ====
i Skipping capabilities GET check for Next.js (queried internally by CopilotKit runtime)
=== Test Case 3: Streamed Currency Conversion & A2UI Output ====
i Sending conversion request and reading SSE stream...
i Tool execution started: get_exchange_rate
✓ SSE Stream completed with valid flow transitions.
✓ A2UI JSON payload extracted and successfully parsed as valid JSON.
Extracted Components: [{"id": "root", "component": "Card", "child": "conversion_details"}, {"id": "conversion_details", "component": "Column", "children": ["conversion_titl...
=== Test Case 4: Agent Guardrails & Domain Enforcement ====
i Sending off-topic request and reading SSE stream...
✓ Agent correctly refused to answer off-topic prompt (Matched: ['only assist', 'currency'])
Response text: I can only assist with currency-related queries.
=== Test Case 5: Thread State Retrieval ====
i Skipping thread state retrieval check for Next.js (not exposed on API route)
==================================================
🎉 ALL INTEGRATION VALIDATION CHECKS PASSED SUCCESSFULLY!
Start the Currency Session
Once all the agents have been started — the react interface on port 3000 is connected with a standard web browser:
The new AG-UI tools are presented:
And in plain text:
Exchange Rate Details
Date: 6/5/2026
💵
From
USD
➔
To
JPY
Fetching...
1 USD = ? JPY
1 USD is equal to 159.86 JPY (as of 2026年06月05日).
The Co-Pilot chat-style box allows for more complex queries:
and the results are presented:
USD Exchange Rates (as of 2026年06月05日)
Currency
Rate (1 USD = X)
CAD
1.3882
THB
32.635
GBP
0.74255
JPY
159.86
So how does AG-UI actually work?
Antigravity was used for a detailed breakdown:
> give some examples of how the ag-ui code works in the current currency project focus on the react interface
●くろまる Read(/home/xbill/currency-agent-agui/frontend-react/src/components/CopilotProvider.tsx) (ctrl+o to expand)
▸ Thought Process
Analyzing A2UI Provider
In the React frontend ( frontend-react/ ), the AG-UI / A2UI framework is structured around three main parts:
catalog registration, message streaming interception, and surface rendering.
Here is how each layer works in the React interface:
──────
### 1. Catalog Registration ( A2UIProvider )
In CopilotProvider.tsx, the React application is wrapped in <A2UIProvider> :
import { A2UIProvider } from "@copilotkit/a2ui-renderer";
import { customCatalog } from "./A2UICustomCatalog";
export default function CopilotProvider({ children }) {
return (
<CopilotKit runtimeUrl="/api/copilotkit" agent="currency_agent">
<A2UIProvider catalog={customCatalog}>
{children}
</A2UIProvider> </CopilotKit> );
}
• customCatalog : Defined in A2UICustomCatalog.tsx, this is built using createCatalog() . It specifies the
component
props (using Zod schemas) and associates them with custom React elements (like Card , Table , BarChart , and
LineChart ).
──────
### 2. Message Streaming Interception
In page.tsx, a custom markdown renderer intercepts the assistant's stream:
const CustomMarkdownRenderer = ({ content, ...props }: any) => {
const { cleanText, parsed, surfaceId } = parseA2UIContent(content);
return (
<div className="flex flex-col gap-2 w-full">
<CopilotChatAssistantMessage.MarkdownRenderer
{...props}
content={cleanText}
/>
{parsed && surfaceId && (
<A2UIContainer parsedJson={parsed} surfaceId={surfaceId} />
)}
</div>
);
};
• parseA2UIContent : Strips the <a2ui-json> block from the text content (so JSON code never shows up in the
chatbot bubbles) and parses it into a valid JavaScript object.
• cleanText : Rendered as standard markdown message bubbles.
• A2UIContainer : Mounted only when valid A2UI JSON payload exists in the response.
──────
### 3. Surface Rendering & State Synchronization
In page.tsx, the container handles component injection and rendering:
const A2UIContainer = ({ parsedJson, surfaceId }: { parsedJson: any; surfaceId: string }) => {
const { processMessages, getSurface } = useA2UI();
useEffect(() => {
if (parsedJson) {
const messagesToProcess = [];
// 1. If the surface isn't created yet, synthesize a createSurface message
if (parsedJson.updateComponents && surfaceId && !getSurface(surfaceId)) {
messagesToProcess.push({
version: "0.9",
createSurface: {
surfaceId: surfaceId,
catalogId: "https://a2ui.org/specification/v0_9/basic_catalog.json"
}
});
}
// 2. Queue the updateComponents instructions
messagesToProcess.push(parsedJson);
processMessages(messagesToProcess);
}
}, [parsedJson, surfaceId, processMessages, getSurface]);
// 3. Render the synchronized surface UI natively
return (
<div className="mt-3 w-full">
<A2UIRenderer surfaceId={surfaceId} />
</div>
);
};
• processMessages : Dynamically updates the A2UI state machine on the client side with components sent from the
agent.
• <A2UIRenderer surfaceId={surfaceId} /> : Renders the specific component tree for that surface by pulling
matched components from the configured catalog.
Summary
The goal of the demo/article was to enable AG-UI integration on a functional agent supporting common protocols like A2A and MCP.
The currency agent was chosen as a starting point and new AG-UI presentation objects were added to the user interface with Antigravity CLI.
This interface was tested with a frontend local React server.
Finally- the updated project integration was summarized, and code reviewed with Antigravity CLI.