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:
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,
},
};
}
}
See a complete working example in the StackOne A2A Agents Orchestrator Demo, which shows how to build an orchestrator agent that routes requests to multiple StackOne A2A agents, manages multi-turn conversations, and serves as an A2A server itself.
Next Steps