A2A Protocol

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-a2a

Getting Started

Set up the Runtime Provider

Create a runtime provider component that connects to your A2A server.

app/MyRuntimeProvider.tsx
"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

app/page.tsx
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
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)
headerssee aboveHeaders for the auto-created client
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