Connect to A2A (Agent-to-Agent) v1.0 protocol servers.
@assistant-ui/react-a2a provides a runtime adapter for the A2A (Agent-to-Agent) v1.0 protocol, enabling your assistant-ui frontend to communicate with any A2A-compliant agent server.
Requirements
- An A2A v1.0 compatible agent server
- React 18 or 19
Installation
npm install @assistant-ui/react @assistant-ui/react-a2aGetting Started
Set up the Runtime Provider
Create a runtime provider component that connects to your A2A server.
"use client";
import { AssistantRuntimeProvider } from "@assistant-ui/react";
import { useA2ARuntime } from "@assistant-ui/react-a2a";
export function MyRuntimeProvider({
children,
}: {
children: React.ReactNode;
}) {
const runtime = useA2ARuntime({
baseUrl: "http://localhost:9999",
});
return (
<AssistantRuntimeProvider runtime={runtime}>
{children}
</AssistantRuntimeProvider>
);
}Add the Thread component
import { Thread } from "@assistant-ui/react";
import { MyRuntimeProvider } from "./MyRuntimeProvider";
export default function Page() {
return (
<MyRuntimeProvider>
<Thread />
</MyRuntimeProvider>
);
}Setup UI Components
Follow the UI Setup guide to setup the UI components.
A2AClient
The built-in A2AClient handles all communication with the A2A server, including JSON serialization, SSE streaming, ProtoJSON enum normalization, and structured error handling.
import { A2AClient } from "@assistant-ui/react-a2a";
const client = new A2AClient({
baseUrl: "https://my-agent.example.com",
headers: { Authorization: "Bearer <token>" },
tenant: "my-org", // optional, for multi-tenant servers
extensions: ["urn:a2a:ext:my-extension"], // optional
});You can pass a pre-built client to useA2ARuntime:
const runtime = useA2ARuntime({ client });Client Options
| Option | Type | Description |
|---|---|---|
baseUrl | string | Base URL of the A2A server |
headers | Record<string, string> or () => Record<string, string> | Static or dynamic headers (e.g. for auth tokens) |
tenant | string | Tenant ID for multi-tenant servers (prepended to URL paths) |
extensions | string[] | Extension URIs to negotiate via A2A-Extensions header |
Client Methods
| Method | Description |
|---|---|
sendMessage(message, configuration?, metadata?) | Send a message (non-streaming) |
streamMessage(message, configuration?, metadata?) | Send a message with SSE streaming |
getTask(taskId, historyLength?) | Get a task by ID |
listTasks(request?) | List tasks with filtering and pagination |
cancelTask(taskId, metadata?) | Cancel an in-progress task |
subscribeToTask(taskId) | Subscribe to SSE updates for a task |
getAgentCard() | Fetch the agent card from /.well-known/agent-card.json |
getExtendedAgentCard() | Fetch the extended (authenticated) agent card |
createTaskPushNotificationConfig(config) | Create a push notification config |
getTaskPushNotificationConfig(taskId, configId) | Get a push notification config |
listTaskPushNotificationConfigs(taskId) | List push notification configs |
deleteTaskPushNotificationConfig(taskId, configId) | Delete a push notification config |
useA2ARuntime Options
| Option | Type | Description |
|---|---|---|
client | A2AClient | Pre-built A2A client instance (provide this OR baseUrl) |
baseUrl | string | A2A server URL (creates a client automatically) |
headers | see above | Headers for the auto-created client |
contextId | string | Initial context ID for the conversation |
configuration | A2ASendMessageConfiguration | Default send message configuration |
onError | (error: Error) => void | Error callback |
onCancel | () => void | Cancellation callback |
adapters.attachments | AttachmentAdapter | Custom attachment handling |
adapters.speech | SpeechSynthesisAdapter | Text-to-speech |
adapters.feedback | FeedbackAdapter | Feedback collection |
adapters.history | ThreadHistoryAdapter | Message persistence |
adapters.threadList | UseA2AThreadListAdapter | Thread switching |
Hooks
useA2ATask
Returns the current A2A task object, including task state and status message.
import { useA2ATask } from "@assistant-ui/react-a2a";
function TaskStatus() {
const task = useA2ATask();
if (!task) return null;
return <div>Task {task.id}: {task.status.state}</div>;
}useA2AArtifacts
Returns the artifacts generated by the current task.
import { useA2AArtifacts } from "@assistant-ui/react-a2a";
function ArtifactList() {
const artifacts = useA2AArtifacts();
return (
<ul>
{artifacts.map((artifact) => (
<li key={artifact.artifactId}>
{artifact.name}: {artifact.parts.length} parts
</li>
))}
</ul>
);
}useA2AAgentCard
Returns the agent card fetched from the server on initialization.
import { useA2AAgentCard } from "@assistant-ui/react-a2a";
function AgentInfo() {
const card = useA2AAgentCard();
if (!card) return null;
return (
<div>
<h3>{card.name}</h3>
<p>{card.description}</p>
<div>Skills: {card.skills.map((s) => s.name).join(", ")}</div>
</div>
);
}Task States
The A2A protocol defines 9 task states. The runtime maps them to assistant-ui message statuses:
| A2A Task State | Description | Message Status |
|---|---|---|
unspecified | Unknown/default state | running |
submitted | Task acknowledged | running |
working | Task in progress | running |
completed | Task finished | complete |
failed | Task errored | incomplete (error) |
canceled | Task cancelled | incomplete (cancelled) |
rejected | Agent declined task | incomplete (error) |
input_required | Agent needs user input | requires-action |
auth_required | Authentication needed | requires-action |
When a task enters input_required, the user can continue the conversation normally. The runtime will send the next message with the same taskId to resume the task.
Artifacts
A2A agents can produce artifacts (files, code, data) alongside their responses. Artifacts are accumulated during streaming and accessible via the useA2AArtifacts hook.
The runtime supports:
- Incremental artifact streaming via
appendmode - Artifact completion notification via
onArtifactCompletecallback - Automatic reset of artifacts on each new run
const runtime = useA2ARuntime({
baseUrl: "http://localhost:9999",
onArtifactComplete: (artifact) => {
console.log("Artifact ready:", artifact.name);
},
});Streaming vs Non-Streaming
The runtime automatically selects the communication mode based on the agent's capabilities:
- If the agent card indicates
capabilities.streaming: true(or unset), the runtime usesPOST /message:streamwith SSE - If
capabilities.streaming: false, the runtime falls back toPOST /message:send
Error Handling
The client throws A2AError instances with structured error information following the google.rpc.Status format:
import { A2AError } from "@assistant-ui/react-a2a";
const runtime = useA2ARuntime({
baseUrl: "http://localhost:9999",
onError: (error) => {
if (error instanceof A2AError) {
console.log(error.code); // HTTP status code
console.log(error.status); // e.g. "NOT_FOUND"
console.log(error.details); // google.rpc.ErrorInfo details
}
},
});Multi-Tenancy
For multi-tenant A2A servers, pass a tenant option to the client:
const client = new A2AClient({
baseUrl: "https://agent.example.com",
tenant: "my-org",
});This prepends /{tenant} to all API paths (e.g. /my-org/message:send).
Features
| Feature | Supported |
|---|---|
| Streaming (SSE) | Yes |
| Non-streaming fallback | Yes |
| All 9 task states | Yes |
| Artifacts (text, data, file) | Yes |
| Agent card discovery | Yes |
| Multi-tenancy | Yes |
| Structured errors | Yes |
| Push notifications CRUD | Yes |
| Extension negotiation | Yes |
| Task cancellation | Yes |
| Message editing | Yes |
| Message reload | Yes |
| History persistence | Yes |
| Thread list management | Yes |