@clduab11/gemini-flow
Version:
Revolutionary AI agent swarm coordination platform with Google Services integration, multimedia processing, and production-ready monitoring. Features 8 Google AI services, quantum computing capabilities, and enterprise-grade security.
875 lines (758 loc) • 23.6 kB
text/typescript
/**
* A2A Protocol Manager
*
* Core component for Agent-to-Agent communication using JSON-RPC 2.0 protocol.
* Provides comprehensive message handling, routing, security, and error management.
*/
import { EventEmitter } from "events";
import {
A2AMessage,
A2AResponse,
A2ANotification,
A2AError,
A2AErrorType,
A2AProtocolConfig,
AgentCard,
AgentId,
JsonRpcRequest,
JsonRpcResponse,
JsonRpcError,
MessagePriority,
RetryPolicy,
} from "../../../types/a2a.js";
import { Logger } from "../../../utils/logger.js";
/**
* Message handler function type
*/
export type MessageHandler = (message: A2AMessage) => Promise<any>;
/**
* Protocol manager metrics
*/
export interface ProtocolManagerMetrics {
messagesProcessed: number;
messagesSucceeded: number;
messagesFailed: number;
avgResponseTime: number;
p95ResponseTime: number;
p99ResponseTime: number;
successRate: number;
errorRate: number;
throughput: number;
concurrentConnections: number;
activeHandlers: number;
errorsByType: { [key in A2AErrorType]?: number };
}
/**
* Message queue entry for priority handling
*/
interface QueuedMessage {
message: A2AMessage;
priority: MessagePriority;
timestamp: number;
resolve: (value: A2AResponse) => void;
reject: (error: A2AError) => void;
retryCount: number;
}
/**
* A2A Protocol Manager - Core JSON-RPC 2.0 implementation
*/
export class A2AProtocolManager extends EventEmitter {
private config: A2AProtocolConfig;
private logger: Logger;
private isInitialized: boolean = false;
private isShuttingDown: boolean = false;
// Message handling
private messageHandlers: Map<string, MessageHandler> = new Map();
private messageQueue: QueuedMessage[] = [];
private processingQueue: boolean = false;
private activeMessages: Map<string, QueuedMessage> = new Map();
// Performance tracking
private metrics: {
messagesProcessed: number;
messagesSucceeded: number;
messagesFailed: number;
responseTimes: number[];
startTime: number;
errorsByType: Map<A2AErrorType, number>;
} = {
messagesProcessed: 0,
messagesSucceeded: 0,
messagesFailed: 0,
responseTimes: [],
startTime: Date.now(),
errorsByType: new Map(),
};
// Security and validation
private trustedAgents: Set<AgentId>;
private messageValidationEnabled: boolean;
constructor(config: A2AProtocolConfig) {
super();
this.config = config;
this.logger = new Logger("A2AProtocolManager");
this.trustedAgents = new Set(config.trustedAgents || []);
this.messageValidationEnabled = config.securityEnabled ?? true;
// Set up error handling
this.on("error", (error) => {
this.logger.error("Protocol manager error:", error);
});
}
/**
* Initialize the protocol manager
*/
async initialize(): Promise<void> {
try {
this.logger.info("Initializing A2A Protocol Manager", {
agentId: this.config.agentId,
transports: this.config.transports.length,
securityEnabled: this.config.securityEnabled,
});
// Validate configuration
this.validateConfiguration();
// Initialize transport layers (would be injected in real implementation)
await this.initializeTransports();
// Start message processing queue
this.startMessageProcessing();
// Register default handlers
this.registerDefaultHandlers();
this.isInitialized = true;
this.metrics.startTime = Date.now();
this.logger.info("A2A Protocol Manager initialized successfully");
this.emit("initialized");
} catch (error) {
this.logger.error("Failed to initialize A2A Protocol Manager:", error);
throw error;
}
}
/**
* Gracefully shutdown the protocol manager
*/
async shutdown(): Promise<void> {
if (this.isShuttingDown) {
return;
}
this.isShuttingDown = true;
this.logger.info("Shutting down A2A Protocol Manager");
try {
// Stop processing new messages
this.processingQueue = false;
// Wait for active messages to complete (with timeout)
await this.waitForActiveMessages(5000);
// Clear message queue
this.messageQueue.forEach((queuedMsg) => {
queuedMsg.reject(
this.createError(
"protocol_error",
"Protocol manager is shutting down",
-32000,
),
);
});
this.messageQueue.length = 0;
// Clear handlers
this.messageHandlers.clear();
// Shutdown transports (would be implemented with actual transport layer)
await this.shutdownTransports();
this.isInitialized = false;
this.logger.info("A2A Protocol Manager shutdown complete");
this.emit("shutdown");
} catch (error) {
this.logger.error("Error during shutdown:", error);
throw error;
}
}
/**
* Send a message and wait for response
*/
async sendMessage(message: A2AMessage): Promise<A2AResponse> {
if (!this.isInitialized) {
throw this.createError(
"protocol_error",
"Protocol manager not initialized",
-32000,
);
}
if (this.isShuttingDown) {
throw this.createError(
"protocol_error",
"Protocol manager is shutting down",
-32000,
);
}
try {
// Validate message format
this.validateMessage(message);
// Check security constraints
if (this.messageValidationEnabled) {
await this.validateSecurity(message);
}
// Add to processing queue
return await this.queueMessage(message);
} catch (error) {
this.trackError("validation_error");
throw error;
}
}
/**
* Send a notification (no response expected)
*/
async sendNotification(notification: A2ANotification): Promise<void> {
if (!this.isInitialized) {
throw this.createError(
"protocol_error",
"Protocol manager not initialized",
-32000,
);
}
try {
// Validate notification format
this.validateNotification(notification);
// Check security constraints
if (this.messageValidationEnabled) {
await this.validateSecurity(notification);
}
// Send notification (implementation would use transport layer)
await this.processNotification(notification);
this.logger.debug("Notification sent successfully", {
method: notification.method,
to: notification.to,
});
} catch (error) {
this.logger.error("Failed to send notification:", error);
this.trackError("protocol_error");
throw error;
}
}
/**
* Register a message handler for a specific method
*/
async registerMessageHandler(
method: string,
handler: MessageHandler,
): Promise<void> {
if (this.messageHandlers.has(method)) {
throw new Error(`Handler already registered for method: ${method}`);
}
this.messageHandlers.set(method, handler);
this.logger.debug(`Message handler registered for method: ${method}`);
}
/**
* Unregister a message handler
*/
async unregisterMessageHandler(method: string): Promise<void> {
if (!this.messageHandlers.has(method)) {
throw new Error(`No handler registered for method: ${method}`);
}
this.messageHandlers.delete(method);
this.logger.debug(`Message handler unregistered for method: ${method}`);
}
/**
* Get the agent card for this protocol manager
*/
getAgentCard(): AgentCard {
return this.config.agentCard;
}
/**
* Get performance metrics
*/
getMetrics(): ProtocolManagerMetrics {
const now = Date.now();
const uptime = now - this.metrics.startTime;
const throughput = this.metrics.messagesProcessed / (uptime / 1000);
// Calculate percentiles
const sortedTimes = [...this.metrics.responseTimes].sort((a, b) => a - b);
const p95Index = Math.floor(sortedTimes.length * 0.95);
const p99Index = Math.floor(sortedTimes.length * 0.99);
const errorsByType: { [key in A2AErrorType]?: number } = {};
this.metrics.errorsByType.forEach((count, type) => {
errorsByType[type] = count;
});
return {
messagesProcessed: this.metrics.messagesProcessed,
messagesSucceeded: this.metrics.messagesSucceeded,
messagesFailed: this.metrics.messagesFailed,
avgResponseTime:
this.metrics.responseTimes.length > 0
? this.metrics.responseTimes.reduce((a, b) => a + b, 0) /
this.metrics.responseTimes.length
: 0,
p95ResponseTime: sortedTimes[p95Index] || 0,
p99ResponseTime: sortedTimes[p99Index] || 0,
successRate:
this.metrics.messagesProcessed > 0
? this.metrics.messagesSucceeded / this.metrics.messagesProcessed
: 0,
errorRate:
this.metrics.messagesProcessed > 0
? this.metrics.messagesFailed / this.metrics.messagesProcessed
: 0,
throughput,
concurrentConnections: this.activeMessages.size,
activeHandlers: this.messageHandlers.size,
errorsByType,
};
}
/**
* Validate configuration
*/
private validateConfiguration(): void {
if (!this.config.agentId || this.config.agentId.trim() === "") {
throw new Error("Invalid agent ID");
}
if (!this.config.agentCard) {
throw new Error("Agent card is required");
}
if (!this.config.transports || this.config.transports.length === 0) {
throw new Error("At least one transport configuration is required");
}
if (!this.config.defaultTransport) {
throw new Error("Default transport must be specified");
}
if (
!this.config.transports.find(
(t) => t.protocol === this.config.defaultTransport,
)
) {
throw new Error(
"Default transport not found in transport configurations",
);
}
}
/**
* Validate JSON-RPC 2.0 message format
*/
private validateMessage(message: A2AMessage): void {
if (message.jsonrpc !== "2.0") {
throw this.createError("protocol_error", "Invalid Request", -32600);
}
if (!message.method || typeof message.method !== "string") {
throw this.createError("protocol_error", "Invalid Request", -32600);
}
if (!message.from || !message.to) {
throw this.createError(
"protocol_error",
"Missing required routing information",
-32600,
);
}
if (!message.timestamp || typeof message.timestamp !== "number") {
throw this.createError("protocol_error", "Invalid timestamp", -32600);
}
if (!message.messageType) {
throw this.createError("protocol_error", "Missing message type", -32600);
}
}
/**
* Validate notification format
*/
private validateNotification(notification: A2ANotification): void {
if (notification.jsonrpc !== "2.0") {
throw this.createError("protocol_error", "Invalid Request", -32600);
}
if (!notification.method || typeof notification.method !== "string") {
throw this.createError("protocol_error", "Invalid Request", -32600);
}
if (!notification.from || !notification.to) {
throw this.createError(
"protocol_error",
"Missing required routing information",
-32600,
);
}
}
/**
* Validate security constraints
*/
private async validateSecurity(
message: A2AMessage | A2ANotification,
): Promise<void> {
if (!this.config.securityEnabled) {
return;
}
// Check if agent is trusted
if (this.trustedAgents.size > 0 && !this.trustedAgents.has(message.from)) {
throw this.createError(
"authorization_error",
`Agent not trusted: ${message.from}`,
-32003,
);
}
// Validate signature if present
if ("signature" in message && message.signature) {
const isValidSignature = await this.validateSignature(message);
if (!isValidSignature) {
throw this.createError(
"authentication_error",
"Authentication failed",
-32002,
);
}
}
// Check message timestamp (prevent replay attacks)
const now = Date.now();
const messageAge = now - message.timestamp;
const maxAge = this.config.messageTimeout || 300000; // 5 minutes default
if (messageAge > maxAge) {
throw this.createError(
"authentication_error",
"Message timestamp too old",
-32002,
);
}
}
/**
* Validate message signature (mock implementation)
*/
private async validateSignature(message: A2AMessage): Promise<boolean> {
// In a real implementation, this would verify the cryptographic signature
// using the agent's public key
return message.signature !== "invalid-signature";
}
/**
* Queue message for processing with priority handling
*/
private async queueMessage(message: A2AMessage): Promise<A2AResponse> {
return new Promise((resolve, reject) => {
const queuedMessage: QueuedMessage = {
message,
priority: message.priority || "normal",
timestamp: Date.now(),
resolve,
reject,
retryCount: 0,
};
// Insert based on priority
this.insertByPriority(queuedMessage);
// Apply timeout
const timeout = message.context?.timeout || this.config.messageTimeout;
if (timeout) {
setTimeout(() => {
this.removeFromQueue(queuedMessage);
reject(
this.createError(
"timeout_error",
`Message timeout after ${timeout}ms`,
-32000,
),
);
}, timeout);
}
});
}
/**
* Insert message into queue based on priority
*/
private insertByPriority(queuedMessage: QueuedMessage): void {
const priorityOrder = { critical: 0, high: 1, normal: 2, low: 3 };
const messagePriority = priorityOrder[queuedMessage.priority];
let insertIndex = this.messageQueue.length;
for (let i = 0; i < this.messageQueue.length; i++) {
const existingPriority = priorityOrder[this.messageQueue[i].priority];
if (messagePriority < existingPriority) {
insertIndex = i;
break;
}
}
this.messageQueue.splice(insertIndex, 0, queuedMessage);
}
/**
* Remove message from queue
*/
private removeFromQueue(queuedMessage: QueuedMessage): void {
const index = this.messageQueue.indexOf(queuedMessage);
if (index !== -1) {
this.messageQueue.splice(index, 1);
}
}
/**
* Start message processing loop
*/
private startMessageProcessing(): void {
this.processingQueue = true;
this.processMessageQueue();
}
/**
* Process message queue
*/
private async processMessageQueue(): Promise<void> {
while (this.processingQueue) {
try {
if (this.messageQueue.length === 0) {
await new Promise((resolve) => setTimeout(resolve, 10)); // Small delay
continue;
}
// Check concurrent message limit
if (this.activeMessages.size >= this.config.maxConcurrentMessages) {
await new Promise((resolve) => setTimeout(resolve, 10));
continue;
}
const queuedMessage = this.messageQueue.shift();
if (!queuedMessage) continue;
// Process message
this.processMessage(queuedMessage);
} catch (error) {
this.logger.error("Error in message processing queue:", error);
}
}
}
/**
* Process individual message
*/
private async processMessage(queuedMessage: QueuedMessage): Promise<void> {
const startTime = Date.now();
const messageId = String(
queuedMessage.message.id ||
`msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
);
this.activeMessages.set(messageId, queuedMessage);
try {
this.metrics.messagesProcessed++;
// Find and execute handler
const handler = this.messageHandlers.get(queuedMessage.message.method);
if (!handler) {
throw this.createError(
"capability_not_found",
`No handler found for method: ${queuedMessage.message.method}`,
-32601,
);
}
// Execute handler
const result = await handler(queuedMessage.message);
// Create response
const response: A2AResponse = {
jsonrpc: "2.0",
result,
id: queuedMessage.message.id || null,
from: this.config.agentId,
to: queuedMessage.message.from,
timestamp: Date.now(),
messageType: "response",
};
// Track success
const responseTime = Date.now() - startTime;
this.metrics.responseTimes.push(responseTime);
if (this.metrics.responseTimes.length > 1000) {
this.metrics.responseTimes.splice(0, 100); // Keep last 1000 entries
}
this.metrics.messagesSucceeded++;
queuedMessage.resolve(response);
this.logger.debug("Message processed successfully", {
method: queuedMessage.message.method,
responseTime,
from: queuedMessage.message.from,
});
} catch (error: any) {
await this.handleMessageError(queuedMessage, error, startTime);
} finally {
this.activeMessages.delete(messageId);
}
}
/**
* Handle message processing errors with retry logic
*/
private async handleMessageError(
queuedMessage: QueuedMessage,
error: any,
startTime: number,
): Promise<void> {
const responseTime = Date.now() - startTime;
this.metrics.responseTimes.push(responseTime);
// Check if error is retryable and retry policy exists
const retryPolicy =
queuedMessage.message.context?.retryPolicy || this.config.retryPolicy;
const isRetryable = this.isRetryableError(error);
if (
isRetryable &&
retryPolicy &&
queuedMessage.retryCount < retryPolicy.maxAttempts
) {
queuedMessage.retryCount++;
// Calculate backoff delay
const delay = this.calculateBackoffDelay(
retryPolicy,
queuedMessage.retryCount,
);
this.logger.debug("Retrying message after error", {
method: queuedMessage.message.method,
retryCount: queuedMessage.retryCount,
delay,
error: error.message,
});
// Re-queue with delay
setTimeout(() => {
this.insertByPriority(queuedMessage);
}, delay);
return;
}
// No more retries or not retryable - fail the message
this.metrics.messagesFailed++;
const errorType = this.getErrorType(error);
this.trackError(errorType);
const a2aError =
error instanceof Error &&
"type" in error &&
"source" in error &&
"code" in error
? (error as A2AError)
: this.createError(
"internal_error",
error.message || "Unknown error",
-32603,
);
queuedMessage.reject(a2aError);
this.logger.error("Message processing failed", {
method: queuedMessage.message.method,
error: error.message,
retryCount: queuedMessage.retryCount,
});
}
/**
* Check if error is retryable
*/
private isRetryableError(error: any): boolean {
if (!error || typeof error !== "object") return false;
const retryableTypes: A2AErrorType[] = [
"timeout_error",
"agent_unavailable",
"resource_exhausted",
"routing_error",
];
return (
retryableTypes.includes(error.type) ||
error.retryable === true ||
error.code === -32000
); // Server error (potentially temporary)
}
/**
* Calculate backoff delay for retries
*/
private calculateBackoffDelay(
retryPolicy: RetryPolicy,
attempt: number,
): number {
let delay: number;
switch (retryPolicy.backoffStrategy) {
case "linear":
delay = retryPolicy.baseDelay * attempt;
break;
case "exponential":
delay = retryPolicy.baseDelay * Math.pow(2, attempt - 1);
break;
case "fixed":
default:
delay = retryPolicy.baseDelay;
break;
}
// Apply maximum delay
delay = Math.min(delay, retryPolicy.maxDelay);
// Apply jitter if enabled
if (retryPolicy.jitter) {
const jitterAmount = delay * 0.1; // 10% jitter
delay += (Math.random() - 0.5) * 2 * jitterAmount;
}
return Math.max(0, Math.floor(delay));
}
/**
* Get error type from error object
*/
private getErrorType(error: any): A2AErrorType {
if (error && typeof error === "object" && "type" in error) {
return error.type as A2AErrorType;
}
return "internal_error";
}
/**
* Track error statistics
*/
private trackError(errorType: A2AErrorType): void {
const currentCount = this.metrics.errorsByType.get(errorType) || 0;
this.metrics.errorsByType.set(errorType, currentCount + 1);
}
/**
* Create A2A error object
*/
private createError(
type: A2AErrorType,
message: string,
code: number,
): A2AError {
return {
code,
message,
type,
source: this.config.agentId,
retryable: this.isRetryableError({ type }),
} as A2AError;
}
/**
* Process notification (mock implementation)
*/
private async processNotification(
notification: A2ANotification,
): Promise<void> {
// In real implementation, this would use the transport layer to send the notification
this.logger.debug("Processing notification", {
method: notification.method,
to: notification.to,
});
// Simulate network delay
await new Promise((resolve) => setTimeout(resolve, 10));
}
/**
* Initialize transport layers (mock implementation)
*/
private async initializeTransports(): Promise<void> {
// In real implementation, this would initialize the actual transport layers
this.logger.debug("Initializing transport layers", {
transports: this.config.transports.map((t) => t.protocol),
});
}
/**
* Shutdown transport layers (mock implementation)
*/
private async shutdownTransports(): Promise<void> {
// In real implementation, this would shutdown the actual transport layers
this.logger.debug("Shutting down transport layers");
}
/**
* Register default message handlers
*/
private registerDefaultHandlers(): void {
// System ping handler
this.messageHandlers.set("system.ping", async (message: A2AMessage) => {
return {
pong: true,
timestamp: Date.now(),
agentId: this.config.agentId,
};
});
// Agent info handler
this.messageHandlers.set("agent.info", async (message: A2AMessage) => {
return {
agentCard: this.config.agentCard,
uptime: Date.now() - this.metrics.startTime,
metrics: this.getMetrics(),
};
});
// Echo handler for testing
this.messageHandlers.set("system.echo", async (message: A2AMessage) => {
return {
echo: message.params,
timestamp: Date.now(),
};
});
}
/**
* Wait for active messages to complete
*/
private async waitForActiveMessages(timeoutMs: number): Promise<void> {
const startTime = Date.now();
while (this.activeMessages.size > 0 && Date.now() - startTime < timeoutMs) {
await new Promise((resolve) => setTimeout(resolve, 100));
}
if (this.activeMessages.size > 0) {
this.logger.warn(
`${this.activeMessages.size} messages still active after timeout`,
);
}
}
}