Skip to main content

Overview

The A2A JavaScript SDK provides a JavaScript/TypeScript client library for building agents that interact with A2A agents following the Agent2Agent (A2A) Protocol. Use this SDK to build agents that:
  • Discover and connect to StackOne’s A2A agents
  • Send messages and receive responses
  • Manage multi-turn conversations with context

Installation

Install the A2A SDK using npm:
npm install @a2a-js/sdk

Quick Start

Get an Agent Card, send a Message, and print the response.
import { A2AClient, SendMessageSuccessResponse } from '@a2a-js/sdk/client';
import { Message, MessageSendParams } from '@a2a-js/sdk';
import { v4 as uuidv4 } from 'uuid';

const STACKONE_API_KEY = '<stackone_api_key>';
const STACKONE_ACCOUNT_ID = '<account_id>';

// Base64 encode the API key (with trailing colon for Basic auth)
const base64EncodedApiKey = Buffer.from(`${STACKONE_API_KEY}:`).toString('base64');

async function main() {
  // Create a custom fetch that adds authentication headers
  const authenticatedFetch: typeof fetch = async (url, init) => {
    const headers = new Headers(init?.headers);
    headers.set('Authorization', `Basic ${base64EncodedApiKey}`);
    headers.set('x-account-id', STACKONE_ACCOUNT_ID);
    return fetch(url, { ...init, headers });
  };

  // Create a client pointing to the StackOne A2A Agent Card URL
  const client = await A2AClient.fromCardUrl(
    'https://a2a.stackone.com/.well-known/agent-card.json',
    { fetchImpl: authenticatedFetch }
  );

  // Send a message
  const sendParams: MessageSendParams = {
    message: {
      messageId: uuidv4(),
      role: 'user',
      parts: [{ kind: 'text', text: 'List the first 5 employees' }],
      kind: 'message',
    },
  };

  const response = await client.sendMessage(sendParams);

  if ('error' in response) {
    console.error('Error:', response.error.message);
  } else {
    const result = (response as SendMessageSuccessResponse).result;
    console.log('Agent response:', JSON.stringify(result, null, 2));
  }
}

main();
See Authentication Guide for details on obtaining your API key and account ID.

Getting Agent Cards

During agent initialisation, fetch an Agent Card for every A2A agent you want to send messages to. This is typically a setup step as opposed to a tool.
Ensure to include the agent information in the agent’s context (e.g. system message).
import { A2AClient } from '@a2a-js/sdk/client';
import { AgentCard } from '@a2a-js/sdk';

const STACKONE_API_KEY = '<stackone_api_key>';
const base64EncodedApiKey = Buffer.from(`${STACKONE_API_KEY}:`).toString('base64');

interface AgentCardWithAccountId {
  accountId: string;
  agentCard: AgentCard;
}

/**
 * Fetch Agent Cards from StackOne A2A agents for multiple accounts.
 *
 * The Agent URL is fixed to https://a2a.stackone.com and dynamically loads
 * an Agent Card based on the account ID.
 */
async function fetchAgentCards(accountIds: string[]): Promise<Map<string, AgentCardWithAccountId>> {
  const agentCards = new Map<string, AgentCardWithAccountId>();

  for (const accountId of accountIds) {
    try {
      // Create authenticated fetch for this account
      const authenticatedFetch: typeof fetch = async (url, init) => {
        const headers = new Headers(init?.headers);
        headers.set('Authorization', `Basic ${base64EncodedApiKey}`);
        headers.set('x-account-id', accountId);
        return fetch(url, { ...init, headers });
      };

      const client = await A2AClient.fromCardUrl(
        'https://a2a.stackone.com/.well-known/agent-card.json',
        { fetchImpl: authenticatedFetch }
      );

      const agentCard = client.agentCard;
      agentCards.set(agentCard.name, { accountId, agentCard });
    } catch (error) {
      console.error(`Failed to fetch agent card for account ${accountId}:`, error);
    }
  }

  return agentCards;
}

/**
 * Get A2A agent information for agent context.
 */
function getA2AAgentInfo(agentCards: Map<string, AgentCardWithAccountId>): string {
  const a2aAgentInfo: Array<{ name: string; description: string; skills: string[] }> = [];

  for (const { agentCard } of agentCards.values()) {
    a2aAgentInfo.push({
      name: agentCard.name,
      description: agentCard.description,
      skills: (agentCard.skills || []).map((skill) => skill.name),
    });
  }

  return ['<agents>', JSON.stringify(a2aAgentInfo, null, 4), '</agents>'].join('\n');
}

async function main() {
  // Fetch agent cards from multiple StackOne accounts
  const accountIds = ['<account_id_1>', '<account_id_2>', '<account_id_3>'];
  const agentCards = await fetchAgentCards(accountIds);

  // Get agent information for LLM context (e.g. system message)
  const a2aAgentInfo = getA2AAgentInfo(agentCards);
  console.log(a2aAgentInfo);
}

main();

Sending Messages

To send messages to A2A agents, give your agent access to a tool like below.
The tool below gives the agent relatively high autonomy. You might want to abstract context IDs and task IDs outside of the tool.
import { A2AClient, SendMessageSuccessResponse } from '@a2a-js/sdk/client';
import { AgentCard, Message, MessageSendParams, Task } from '@a2a-js/sdk';
import { v4 as uuidv4 } from 'uuid';

const STACKONE_API_KEY = '<stackone_api_key>';
const base64EncodedApiKey = Buffer.from(`${STACKONE_API_KEY}:`).toString('base64');

interface AgentCardWithAccountId {
  accountId: string;
  agentCard: AgentCard;
}

// Map of agent names to their cards and account IDs
const agentCards = new Map<string, AgentCardWithAccountId>();

interface SendMessageResult {
  Message?: {
    messageId: string;
    role: string;
    parts: Array<{ kind: string; text?: string }>;
    contextId?: string;
  };
  Task?: {
    status: { state: string; timestamp: string };
    artifacts: Array<{ artifactId: string; name?: string; parts: Array<{ kind: string; text?: string }> }>;
    contextId: string;
    taskId: string;
  };
  error?: { code: number; message: string };
}

/**
 * Send a message to an agent and receive a response.
 * The response can be a Task, Message, or Error.
 *
 * To start a new conversation, set `contextId` and `taskId` to undefined.
 *
 * To continue a conversation, set `contextId` to the `contextId` of the previous conversation.
 *   - If the previous Task has not terminated (input_required, auth_required),
 *     set `taskId` to the `taskId` of the previous Task.
 *   - If the previous Task has terminated (completed, canceled, rejected, or failed),
 *     set `taskId` to undefined.
 */
async function sendMessageToAgent(
  agentName: string,
  message: string,
  contextId?: string,
  taskId?: string
): Promise<SendMessageResult> {
  const agentEntry = agentCards.get(agentName);

  if (!agentEntry) {
    throw new Error(`Agent ${agentName} not found`);
  }

  const { accountId, agentCard } = agentEntry;

  // Create authenticated fetch for this account
  const authenticatedFetch: typeof fetch = async (url, init) => {
    const headers = new Headers(init?.headers);
    headers.set('Authorization', `Basic ${base64EncodedApiKey}`);
    headers.set('x-account-id', accountId);
    return fetch(url, { ...init, headers });
  };

  const client = await A2AClient.fromCardUrl(agentCard.url, {
    fetchImpl: authenticatedFetch,
  });

  const sendParams: MessageSendParams = {
    message: {
      messageId: uuidv4(),
      role: 'user',
      parts: [{ kind: 'text', text: message }],
      kind: 'message',
    },
    contextId,
    taskId,
  };

  const response = await client.sendMessage(sendParams);

  // Handle error
  if ('error' in response) {
    return { error: response.error };
  }

  const result = (response as SendMessageSuccessResponse).result;

  if (result.kind === 'message') {
    // Agent responded with a Message (i.e. didn't start a Task)
    const msg = result as Message;
    return {
      Message: {
        messageId: msg.messageId,
        role: msg.role,
        parts: msg.parts,
        contextId: msg.contextId,
      },
    };
  } else {
    // Agent responded with a Task (i.e. started a Task)
    // Tasks contain the whole message history. We typically want the
    // Task's status, and Artifacts (if the Task has completed).
    const task = result as Task;
    return {
      Task: {
        status: task.status,
        artifacts: task.artifacts || [],
        contextId: task.contextId,
        taskId: task.id,
      },
    };
  }
}

Next Steps