index
Type
External
Status
Published
Created
Mar 17, 2026
Updated
Apr 1, 2026
Updated by
Dosu Bot

@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 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#

OptionTypeDescription
baseUrlstringBase URL of the A2A server
basePathstringOptional path prefix for API endpoints (e.g. "/v1"). Does not affect agent card discovery
headersRecord<string, string> or () => Record<string, string>Static or dynamic headers (e.g. for auth tokens)
tenantstringTenant ID for multi-tenant servers (prepended to URL paths)
extensionsstring[]Extension URIs to negotiate via A2A-Extensions header

Client Methods#

MethodDescription
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#

OptionTypeDescription
clientA2AClientPre-built A2A client instance (provide this OR baseUrl)
baseUrlstringA2A server URL (creates a client automatically)
basePathstringPath prefix for API endpoints (e.g. "/v1"). Only used with baseUrl
tenantstringTenant ID for multi-tenant servers. Only used with baseUrl
headerssee aboveHeaders for the auto-created client
extensionsstring[]Extension URIs to negotiate. Only used with baseUrl
contextIdstringInitial context ID for the conversation
configurationA2ASendMessageConfigurationDefault send message configuration
onError(error: Error) => voidError callback
onCancel() => voidCancellation callback
adapters.attachmentsAttachmentAdapterCustom attachment handling
adapters.speechSpeechSynthesisAdapterText-to-speech
adapters.feedbackFeedbackAdapterFeedback collection
adapters.historyThreadHistoryAdapterMessage persistence
adapters.threadListUseA2AThreadListAdapterThread 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 StateDescriptionMessage Status
unspecifiedUnknown/default staterunning
submittedTask acknowledgedrunning
workingTask in progressrunning
completedTask finishedcomplete
failedTask erroredincomplete (error)
canceledTask cancelledincomplete (cancelled)
rejectedAgent declined taskincomplete (error)
input_requiredAgent needs user inputrequires-action
auth_requiredAuthentication neededrequires-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 append mode
  • Artifact completion notification via onArtifactComplete callback
  • 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 uses POST /message:stream with SSE
  • If capabilities.streaming: false, the runtime falls back to POST /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#

FeatureSupported
Streaming (SSE)Yes
Non-streaming fallbackYes
All 9 task statesYes
Artifacts (text, data, file)Yes
Agent card discoveryYes
Multi-tenancyYes
Structured errorsYes
Push notifications CRUDYes
Extension negotiationYes
Task cancellationYes
Message editingYes
Message reloadYes
History persistenceYes
Thread list managementYes