DocumentationClient Guide

Client Guide

Learn how to build powerful A2A clients using the A2A Node SDK. This guide covers advanced patterns, best practices, and real-world usage scenarios.

Overview

The A2A Node SDK provides three main client classes for interacting with A2A servers:

  • AgentClient - For agent discovery and management
  • MessageClient - For sending and receiving messages
  • TaskClient - For task management and monitoring

AgentClient

The AgentClient is used to discover and interact with A2A agents.

Basic Usage

import { AgentClient } from '@dexwox-labs/a2a-node';
 
const agentClient = new AgentClient({
  baseUrl: 'https://your-a2a-server.com',
  timeout: 30000, // 30 seconds
  retries: 3
});
 
// Discover all available agents
const agents = await agentClient.resolveAgents();
console.log('Available agents:', agents);
 
// Get a specific agent by ID
const agent = await agentClient.getAgentCard('weather-agent');
console.log('Agent capabilities:', agent.capabilities);

Advanced Configuration

const agentClient = new AgentClient({
  baseUrl: 'https://your-a2a-server.com',
  timeout: 30000,
  retries: 3,
  headers: {
    'Authorization': 'Bearer your-token',
    'User-Agent': 'MyApp/1.0.0'
  },
  // Circuit breaker configuration
  circuitBreaker: {
    failureThreshold: 5,
    resetTimeout: 60000,
    monitoringPeriod: 10000
  }
});

Error Handling

try {
  const agents = await agentClient.resolveAgents();
} catch (error) {
  if (error instanceof A2AError) {
    console.error('A2A Error:', error.code, error.message);
  } else {
    console.error('Network Error:', error.message);
  }
}

MessageClient

The MessageClient handles message sending and streaming communication.

Sending Messages

import { MessageClient } from '@dexwox-labs/a2a-node';
 
const messageClient = new MessageClient({
  baseUrl: 'https://your-a2a-server.com'
});
 
// Send a simple text message
const messageId = await messageClient.sendMessage([
  { type: 'text', content: 'Hello, agent!' }
], 'target-agent-id');
 
// Send a message with multiple parts
const messageId = await messageClient.sendMessage([
  { type: 'text', content: 'Please analyze this data:' },
  { 
    type: 'file', 
    content: {
      name: 'data.csv',
      mimeType: 'text/csv',
      data: csvData
    }
  }
], 'data-analysis-agent');

Message Streaming

// Stream messages in real-time
const stream = messageClient.streamMessages('conversation-id', {
  includeHistory: true,
  maxMessages: 100
});
 
for await (const message of stream) {
  console.log('New message:', message);
  
  // Process message parts
  for (const part of message.parts) {
    if (part.type === 'text') {
      console.log('Text:', part.content);
    } else if (part.type === 'file') {
      console.log('File:', part.content.name);
    }
  }
}

Message Configuration

const messageId = await messageClient.sendMessage(
  [{ type: 'text', content: 'Hello!' }],
  'agent-id',
  {
    // Message configuration
    priority: 'high',
    timeout: 60000,
    retryPolicy: {
      maxRetries: 3,
      backoffMultiplier: 2,
      initialDelay: 1000
    },
    // Push notification settings
    pushNotification: {
      title: 'New Message',
      body: 'You have received a new message',
      icon: 'https://example.com/icon.png'
    }
  }
);

TaskClient

The TaskClient manages task lifecycle and monitoring.

Creating and Managing Tasks

import { TaskClient } from '@dexwox-labs/a2a-node';
 
const taskClient = new TaskClient({
  baseUrl: 'https://your-a2a-server.com'
});
 
// Create a new task
const task = await taskClient.createTask({
  name: 'analyze-data',
  description: 'Analyze the provided dataset',
  agentId: 'data-agent',
  input: {
    dataset: 'sales-data-2024.csv',
    analysisType: 'trend-analysis'
  }
});
 
console.log('Task created:', task.id);

Monitoring Task Progress

// Get task status
const taskStatus = await taskClient.getTask(task.id);
console.log('Task status:', taskStatus.status);
 
// Stream task updates
const taskStream = taskClient.streamTaskUpdates(task.id);
 
for await (const update of taskStream) {
  console.log('Task update:', update);
  
  if (update.status === 'completed') {
    console.log('Task completed!');
    console.log('Result:', update.result);
    break;
  } else if (update.status === 'failed') {
    console.error('Task failed:', update.error);
    break;
  }
}

Task Cancellation

// Cancel a running task
try {
  await taskClient.cancelTask(task.id);
  console.log('Task cancelled successfully');
} catch (error) {
  if (error.code === 'TASK_ALREADY_COMPLETED') {
    console.log('Task was already completed');
  } else {
    console.error('Failed to cancel task:', error.message);
  }
}

Advanced Patterns

Connection Pooling

import { AgentClient, MessageClient, TaskClient } from '@dexwox-labs/a2a-node';
 
// Shared configuration for connection pooling
const baseConfig = {
  baseUrl: 'https://your-a2a-server.com',
  keepAlive: true,
  maxSockets: 10,
  timeout: 30000
};
 
const agentClient = new AgentClient(baseConfig);
const messageClient = new MessageClient(baseConfig);
const taskClient = new TaskClient(baseConfig);

Retry Logic with Exponential Backoff

const messageClient = new MessageClient({
  baseUrl: 'https://your-a2a-server.com',
  retryPolicy: {
    maxRetries: 5,
    initialDelay: 1000,
    maxDelay: 30000,
    backoffMultiplier: 2,
    jitter: true
  }
});

Custom Error Handling

import { A2AError, TaskFailedError, MessageErrorCode } from '@dexwox-labs/a2a-node';
 
try {
  await messageClient.sendMessage(parts, agentId);
} catch (error) {
  if (error instanceof TaskFailedError) {
    console.error('Task execution failed:', error.taskId);
  } else if (error instanceof A2AError) {
    switch (error.code) {
      case MessageErrorCode.AGENT_NOT_FOUND:
        console.error('Agent not found');
        break;
      case MessageErrorCode.INVALID_MESSAGE_FORMAT:
        console.error('Invalid message format');
        break;
      default:
        console.error('A2A Error:', error.message);
    }
  } else {
    console.error('Unexpected error:', error);
  }
}

Batch Operations

// Send multiple messages concurrently
const messages = [
  { parts: [{ type: 'text', content: 'Message 1' }], agentId: 'agent-1' },
  { parts: [{ type: 'text', content: 'Message 2' }], agentId: 'agent-2' },
  { parts: [{ type: 'text', content: 'Message 3' }], agentId: 'agent-3' }
];
 
const messagePromises = messages.map(({ parts, agentId }) =>
  messageClient.sendMessage(parts, agentId)
);
 
const messageIds = await Promise.all(messagePromises);
console.log('All messages sent:', messageIds);

Health Monitoring

// Monitor client health
const agentClient = new AgentClient({
  baseUrl: 'https://your-a2a-server.com',
  healthCheck: {
    enabled: true,
    interval: 30000, // 30 seconds
    timeout: 5000,   // 5 seconds
    onHealthChange: (isHealthy) => {
      console.log('Client health:', isHealthy ? 'OK' : 'UNHEALTHY');
    }
  }
});

Best Practices

1. Connection Management

Always reuse client instances when possible to benefit from connection pooling and reduce overhead.

// ✅ Good: Reuse clients
const agentClient = new AgentClient(config);
const messageClient = new MessageClient(config);
 
// Use these clients throughout your application
 
// ❌ Bad: Creating new clients for each operation
async function sendMessage() {
  const client = new MessageClient(config); // Creates new connection
  return client.sendMessage(parts, agentId);
}

2. Error Handling

// ✅ Good: Comprehensive error handling
try {
  const result = await taskClient.createTask(taskData);
  return result;
} catch (error) {
  if (error instanceof A2AError) {
    // Handle A2A-specific errors
    logger.error('A2A Error:', { code: error.code, message: error.message });
  } else {
    // Handle network/system errors
    logger.error('System Error:', error.message);
  }
  throw error;
}

3. Resource Cleanup

// ✅ Good: Proper cleanup
class A2AService {
  private clients: Array<AgentClient | MessageClient | TaskClient> = [];
 
  constructor() {
    this.agentClient = new AgentClient(config);
    this.messageClient = new MessageClient(config);
    this.taskClient = new TaskClient(config);
    
    this.clients.push(this.agentClient, this.messageClient, this.taskClient);
  }
 
  async cleanup() {
    await Promise.all(this.clients.map(client => client.close()));
  }
}

4. Configuration Management

// ✅ Good: Environment-based configuration
const config = {
  baseUrl: process.env.A2A_SERVER_URL || 'http://localhost:3000',
  timeout: parseInt(process.env.A2A_TIMEOUT || '30000'),
  retries: parseInt(process.env.A2A_RETRIES || '3'),
  headers: {
    'Authorization': `Bearer ${process.env.A2A_TOKEN}`,
    'User-Agent': `${process.env.APP_NAME}/${process.env.APP_VERSION}`
  }
};

Troubleshooting

Common Issues

Connection Timeouts

// Increase timeout for slow networks
const client = new MessageClient({
  baseUrl: 'https://your-a2a-server.com',
  timeout: 60000, // 60 seconds
  retries: 5
});

Rate Limiting

// Implement rate limiting
import { RateLimiter } from 'limiter';
 
const limiter = new RateLimiter(10, 'second'); // 10 requests per second
 
async function sendMessageWithRateLimit(parts, agentId) {
  await new Promise(resolve => limiter.removeTokens(1, resolve));
  return messageClient.sendMessage(parts, agentId);
}

Memory Leaks

// Properly handle streams
const stream = messageClient.streamMessages('conversation-id');
 
try {
  for await (const message of stream) {
    // Process message
  }
} finally {
  // Ensure stream is closed
  await stream.close();
}

Next Steps