@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#
<InstallCommand npm={["@assistant-ui/react", "@assistant-ui/react-a2a"]} />
Getting Started#
### Set up the Runtime ProviderCreate 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>
);
}
import { Thread } from "@assistant-ui/react";
import { MyRuntimeProvider } from "./MyRuntimeProvider";
export default function Page() {
return (
<MyRuntimeProvider>
<Thread />
</MyRuntimeProvider>
);
}
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 |
basePath | string | Optional path prefix for API endpoints (e.g. "/v1"). Does not affect agent card discovery |
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) |
basePath | string | Path prefix for API endpoints (e.g. "/v1"). Only used with baseUrl |
tenant | string | Tenant ID for multi-tenant servers. Only used with baseUrl |
headers | see above | Headers for the auto-created client |
extensions | string[] | Extension URIs to negotiate. Only used with baseUrl |
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 |
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 |