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 usingnpm:
Copy
Ask AI
npm install @a2a-js/sdk
Quick Start
Get an Agent Card, send a Message, and print the response.Copy
Ask AI
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();
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).
Copy
Ask AI
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.
Copy
Ask AI
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,
},
};
}
}