claude-flow-multilang
Version:
Revolutionary multilingual AI orchestration framework with cultural awareness and DDD architecture
1,452 lines (1,217 loc) • 39.2 kB
text/typescript
/**
* Advanced messaging and communication layer for swarm coordination
*/
import { EventEmitter } from 'node:events';
import type { ILogger } from '../core/logger.js';
import type { IEventBus } from '../core/event-bus.js';
import type { SwarmEvent, EventType, AgentId, CommunicationStrategy } from '../swarm/types.js';
import { generateId } from '../utils/helpers.js';
export interface MessageBusConfig {
strategy: CommunicationStrategy;
enablePersistence: boolean;
enableReliability: boolean;
enableOrdering: boolean;
enableFiltering: boolean;
maxMessageSize: number;
maxQueueSize: number;
messageRetention: number;
acknowledgmentTimeout: number;
retryAttempts: number;
backoffMultiplier: number;
compressionEnabled: boolean;
encryptionEnabled: boolean;
metricsEnabled: boolean;
debugMode: boolean;
}
export interface Message {
id: string;
type: string;
sender: AgentId;
receivers: AgentId[];
content: any;
metadata: MessageMetadata;
timestamp: Date;
expiresAt?: Date;
priority: MessagePriority;
reliability: ReliabilityLevel;
}
export interface MessageMetadata {
correlationId?: string;
causationId?: string;
replyTo?: string;
ttl?: number;
compressed: boolean;
encrypted: boolean;
size: number;
contentType: string;
encoding: string;
checksum?: string;
route?: string[];
deadLetterReason?: string;
deadLetterTimestamp?: Date;
}
export interface MessageChannel {
id: string;
name: string;
type: ChannelType;
participants: AgentId[];
config: ChannelConfig;
statistics: ChannelStatistics;
filters: MessageFilter[];
middleware: ChannelMiddleware[];
}
export interface ChannelConfig {
persistent: boolean;
ordered: boolean;
reliable: boolean;
maxParticipants: number;
maxMessageSize: number;
maxQueueDepth: number;
retentionPeriod: number;
accessControl: AccessControlConfig;
}
export interface AccessControlConfig {
readPermission: 'public' | 'participants' | 'restricted';
writePermission: 'public' | 'participants' | 'restricted';
adminPermission: 'creator' | 'administrators' | 'system';
allowedSenders: AgentId[];
allowedReceivers: AgentId[];
bannedAgents: AgentId[];
}
export interface ChannelStatistics {
messagesTotal: number;
messagesDelivered: number;
messagesFailed: number;
bytesTransferred: number;
averageLatency: number;
throughput: number;
errorRate: number;
participantCount: number;
lastActivity: Date;
}
export interface MessageFilter {
id: string;
name: string;
enabled: boolean;
conditions: FilterCondition[];
action: 'allow' | 'deny' | 'modify' | 'route';
priority: number;
}
export interface FilterCondition {
field: string;
operator: 'eq' | 'ne' | 'gt' | 'lt' | 'contains' | 'matches' | 'in';
value: any;
caseSensitive?: boolean;
}
export interface ChannelMiddleware {
id: string;
name: string;
enabled: boolean;
order: number;
process: (message: Message, context: MiddlewareContext) => Promise<Message | null>;
}
export interface MiddlewareContext {
channel: MessageChannel;
direction: 'inbound' | 'outbound';
agent: AgentId;
metadata: Record<string, any>;
}
export interface MessageQueue {
id: string;
name: string;
type: QueueType;
messages: Message[];
config: QueueConfig;
subscribers: QueueSubscriber[];
statistics: QueueStatistics;
}
export interface QueueConfig {
maxSize: number;
persistent: boolean;
ordered: boolean;
durability: 'memory' | 'disk' | 'distributed';
deliveryMode: 'at-most-once' | 'at-least-once' | 'exactly-once';
deadLetterQueue?: string;
retryPolicy: RetryPolicy;
}
export interface QueueSubscriber {
id: string;
agent: AgentId;
filter?: MessageFilter;
ackMode: 'auto' | 'manual';
prefetchCount: number;
lastActivity: Date;
}
export interface QueueStatistics {
depth: number;
enqueueRate: number;
dequeueRate: number;
throughput: number;
averageWaitTime: number;
subscriberCount: number;
deadLetterCount: number;
}
export interface RetryPolicy {
maxAttempts: number;
initialDelay: number;
maxDelay: number;
backoffMultiplier: number;
jitter: boolean;
}
export interface TopicSubscription {
id: string;
topic: string;
subscriber: AgentId;
filter?: MessageFilter;
ackRequired: boolean;
qos: QualityOfService;
createdAt: Date;
lastMessage?: Date;
}
export interface RoutingRule {
id: string;
name: string;
enabled: boolean;
priority: number;
conditions: FilterCondition[];
actions: RoutingAction[];
}
export interface RoutingAction {
type: 'forward' | 'duplicate' | 'transform' | 'aggregate' | 'delay';
target?: string;
config: Record<string, any>;
}
export type MessagePriority = 'low' | 'normal' | 'high' | 'critical';
export type ReliabilityLevel = 'best-effort' | 'at-least-once' | 'exactly-once';
export type ChannelType = 'direct' | 'broadcast' | 'multicast' | 'topic' | 'queue';
export type QueueType = 'fifo' | 'lifo' | 'priority' | 'delay' | 'round-robin';
export type QualityOfService = 0 | 1 | 2; // MQTT-style QoS levels
/**
* Advanced message bus with support for multiple communication patterns
*/
export class MessageBus extends EventEmitter {
private logger: ILogger;
private eventBus: IEventBus;
private config: MessageBusConfig;
// Core messaging components
private channels = new Map<string, MessageChannel>();
private queues = new Map<string, MessageQueue>();
private subscriptions = new Map<string, TopicSubscription>();
private routingRules = new Map<string, RoutingRule>();
// Message tracking
private messageStore = new Map<string, Message>();
private deliveryReceipts = new Map<string, DeliveryReceipt>();
private acknowledgments = new Map<string, MessageAcknowledgment>();
// Routing and delivery
private router: MessageRouter;
private deliveryManager: DeliveryManager;
private retryManager: RetryManager;
// Performance monitoring
private metrics: MessageBusMetrics;
private metricsInterval?: NodeJS.Timeout;
constructor(config: Partial<MessageBusConfig>, logger: ILogger, eventBus: IEventBus) {
super();
this.logger = logger;
this.eventBus = eventBus;
this.config = {
strategy: 'event-driven',
enablePersistence: true,
enableReliability: true,
enableOrdering: false,
enableFiltering: true,
maxMessageSize: 1024 * 1024, // 1MB
maxQueueSize: 10000,
messageRetention: 86400000, // 24 hours
acknowledgmentTimeout: 30000,
retryAttempts: 3,
backoffMultiplier: 2,
compressionEnabled: false,
encryptionEnabled: false,
metricsEnabled: true,
debugMode: false,
...config,
};
this.router = new MessageRouter(this.config, this.logger);
this.deliveryManager = new DeliveryManager(this.config, this.logger);
this.retryManager = new RetryManager(this.config, this.logger);
this.metrics = new MessageBusMetrics();
this.setupEventHandlers();
}
private setupEventHandlers(): void {
this.eventBus.on('agent:connected', (data) => {
if (hasAgentId(data)) {
this.handleAgentConnected(data.agentId);
}
});
this.eventBus.on('agent:disconnected', (data) => {
if (hasAgentId(data)) {
this.handleAgentDisconnected(data.agentId);
}
});
this.deliveryManager.on('delivery:success', (data) => {
this.handleDeliverySuccess(data);
});
this.deliveryManager.on('delivery:failure', (data) => {
this.handleDeliveryFailure(data);
});
this.retryManager.on('retry:exhausted', (data) => {
this.handleRetryExhausted(data);
});
}
async initialize(): Promise<void> {
this.logger.info('Initializing message bus', {
strategy: this.config.strategy,
persistence: this.config.enablePersistence,
reliability: this.config.enableReliability,
});
// Initialize components
await this.router.initialize();
await this.deliveryManager.initialize();
await this.retryManager.initialize();
// Create default channels
await this.createDefaultChannels();
// Start metrics collection
if (this.config.metricsEnabled) {
this.startMetricsCollection();
}
this.emit('messagebus:initialized');
}
async shutdown(): Promise<void> {
this.logger.info('Shutting down message bus');
// Stop metrics collection
if (this.metricsInterval) {
clearInterval(this.metricsInterval);
}
// Shutdown components
await this.retryManager.shutdown();
await this.deliveryManager.shutdown();
await this.router.shutdown();
// Persist any remaining messages if enabled
if (this.config.enablePersistence) {
await this.persistMessages();
}
this.emit('messagebus:shutdown');
}
// === MESSAGE OPERATIONS ===
async sendMessage(
type: string,
content: any,
sender: AgentId,
receivers: AgentId | AgentId[],
options: {
priority?: MessagePriority;
reliability?: ReliabilityLevel;
ttl?: number;
correlationId?: string;
replyTo?: string;
channel?: string;
} = {},
): Promise<string> {
const messageId = generateId('msg');
const now = new Date();
const receiversArray = Array.isArray(receivers) ? receivers : [receivers];
const message: Message = {
id: messageId,
type,
sender,
receivers: receiversArray,
content: await this.processContent(content),
metadata: {
correlationId: options.correlationId,
replyTo: options.replyTo,
ttl: options.ttl,
compressed: this.config.compressionEnabled,
encrypted: this.config.encryptionEnabled,
size: this.calculateSize(content),
contentType: this.detectContentType(content),
encoding: 'utf-8',
route: [sender.id],
},
timestamp: now,
expiresAt: options.ttl ? new Date(now.getTime() + options.ttl) : undefined,
priority: options.priority || 'normal',
reliability: options.reliability || 'best-effort',
};
// Validate message
this.validateMessage(message);
// Store message if persistence is enabled
if (this.config.enablePersistence) {
this.messageStore.set(messageId, message);
}
// Route and deliver message
await this.routeMessage(message, options.channel);
this.metrics.recordMessageSent(message);
this.logger.debug('Message sent', {
messageId,
type,
sender: sender.id,
receivers: receiversArray.map((r) => r.id),
size: message.metadata.size,
});
this.emit('message:sent', { message });
return messageId;
}
async broadcastMessage(
type: string,
content: any,
sender: AgentId,
options: {
channel?: string;
filter?: MessageFilter;
priority?: MessagePriority;
ttl?: number;
} = {},
): Promise<string> {
const channel = options.channel
? this.channels.get(options.channel)
: this.getDefaultBroadcastChannel();
if (!channel) {
throw new Error('No broadcast channel available');
}
// Get all participants as receivers
let receivers = channel.participants.filter((p) => p.id !== sender.id);
// Apply filter if provided
if (options.filter) {
receivers = await this.filterReceivers(receivers, options.filter, { type, content });
}
return this.sendMessage(type, content, sender, receivers, {
priority: options.priority,
ttl: options.ttl,
channel: channel.id,
});
}
async subscribeToTopic(
topic: string,
subscriber: AgentId,
options: {
filter?: MessageFilter;
qos?: QualityOfService;
ackRequired?: boolean;
} = {},
): Promise<string> {
const subscriptionId = generateId('sub');
const subscription: TopicSubscription = {
id: subscriptionId,
topic,
subscriber,
filter: options.filter,
ackRequired: options.ackRequired || false,
qos: options.qos || 0,
createdAt: new Date(),
};
this.subscriptions.set(subscriptionId, subscription);
this.logger.info('Topic subscription created', {
subscriptionId,
topic,
subscriber: subscriber.id,
qos: subscription.qos,
});
this.emit('subscription:created', { subscription });
return subscriptionId;
}
async unsubscribeFromTopic(subscriptionId: string): Promise<void> {
const subscription = this.subscriptions.get(subscriptionId);
if (!subscription) {
throw new Error(`Subscription ${subscriptionId} not found`);
}
this.subscriptions.delete(subscriptionId);
this.logger.info('Topic subscription removed', {
subscriptionId,
topic: subscription.topic,
subscriber: subscription.subscriber.id,
});
this.emit('subscription:removed', { subscription });
}
async acknowledgeMessage(messageId: string, agentId: AgentId): Promise<void> {
const message = this.messageStore.get(messageId);
if (!message) {
throw new Error(`Message ${messageId} not found`);
}
const ack: MessageAcknowledgment = {
messageId,
agentId,
timestamp: new Date(),
status: 'acknowledged',
};
this.acknowledgments.set(`${messageId}:${agentId.id}`, ack);
this.logger.debug('Message acknowledged', {
messageId,
agentId: agentId.id,
});
this.emit('message:acknowledged', { messageId, agentId });
// Check if all receivers have acknowledged
this.checkAllAcknowledgments(message);
}
// === CHANNEL MANAGEMENT ===
async createChannel(
name: string,
type: ChannelType,
config: Partial<ChannelConfig> = {},
): Promise<string> {
const channelId = generateId('channel');
const channel: MessageChannel = {
id: channelId,
name,
type,
participants: [],
config: {
persistent: true,
ordered: false,
reliable: true,
maxParticipants: 1000,
maxMessageSize: this.config.maxMessageSize,
maxQueueDepth: this.config.maxQueueSize,
retentionPeriod: this.config.messageRetention,
accessControl: {
readPermission: 'participants',
writePermission: 'participants',
adminPermission: 'creator',
allowedSenders: [],
allowedReceivers: [],
bannedAgents: [],
},
...config,
},
statistics: this.createChannelStatistics(),
filters: [],
middleware: [],
};
this.channels.set(channelId, channel);
this.logger.info('Channel created', {
channelId,
name,
type,
config: channel.config,
});
this.emit('channel:created', { channel });
return channelId;
}
async joinChannel(channelId: string, agentId: AgentId): Promise<void> {
const channel = this.channels.get(channelId);
if (!channel) {
throw new Error(`Channel ${channelId} not found`);
}
// Check access permissions
if (!this.canJoinChannel(channel, agentId)) {
throw new Error(`Agent ${agentId.id} not allowed to join channel ${channelId}`);
}
// Check capacity
if (channel.participants.length >= channel.config.maxParticipants) {
throw new Error(`Channel ${channelId} is at capacity`);
}
// Add participant if not already present
if (!channel.participants.some((p) => p.id === agentId.id)) {
channel.participants.push(agentId);
channel.statistics.participantCount = channel.participants.length;
}
this.logger.info('Agent joined channel', {
channelId,
agentId: agentId.id,
participantCount: channel.participants.length,
});
this.emit('channel:joined', { channelId, agentId });
}
async leaveChannel(channelId: string, agentId: AgentId): Promise<void> {
const channel = this.channels.get(channelId);
if (!channel) {
throw new Error(`Channel ${channelId} not found`);
}
// Remove participant
channel.participants = channel.participants.filter((p) => p.id !== agentId.id);
channel.statistics.participantCount = channel.participants.length;
this.logger.info('Agent left channel', {
channelId,
agentId: agentId.id,
participantCount: channel.participants.length,
});
this.emit('channel:left', { channelId, agentId });
}
// === QUEUE MANAGEMENT ===
async createQueue(
name: string,
type: QueueType,
config: Partial<QueueConfig> = {},
): Promise<string> {
const queueId = generateId('queue');
const queue: MessageQueue = {
id: queueId,
name,
type,
messages: [],
config: {
maxSize: this.config.maxQueueSize,
persistent: this.config.enablePersistence,
ordered: this.config.enableOrdering,
durability: 'memory',
deliveryMode: 'at-least-once',
retryPolicy: {
maxAttempts: this.config.retryAttempts,
initialDelay: 1000,
maxDelay: 30000,
backoffMultiplier: this.config.backoffMultiplier,
jitter: true,
},
...config,
},
subscribers: [],
statistics: this.createQueueStatistics(),
};
this.queues.set(queueId, queue);
this.logger.info('Queue created', {
queueId,
name,
type,
config: queue.config,
});
this.emit('queue:created', { queue });
return queueId;
}
async enqueueMessage(queueId: string, message: Message): Promise<void> {
const queue = this.queues.get(queueId);
if (!queue) {
throw new Error(`Queue ${queueId} not found`);
}
// Check queue capacity
if (queue.messages.length >= queue.config.maxSize) {
if (queue.config.deadLetterQueue) {
await this.sendToDeadLetterQueue(queue.config.deadLetterQueue, message, 'queue_full');
return;
} else {
throw new Error(`Queue ${queueId} is full`);
}
}
// Insert message based on queue type
this.insertMessageInQueue(queue, message);
queue.statistics.depth = queue.messages.length;
queue.statistics.enqueueRate++;
this.logger.debug('Message enqueued', {
queueId,
messageId: message.id,
queueDepth: queue.messages.length,
});
this.emit('message:enqueued', { queueId, message });
// Process queue for delivery
await this.processQueue(queue);
}
async dequeueMessage(queueId: string, subscriberId: string): Promise<Message | null> {
const queue = this.queues.get(queueId);
if (!queue) {
throw new Error(`Queue ${queueId} not found`);
}
const subscriber = queue.subscribers.find((s) => s.id === subscriberId);
if (!subscriber) {
throw new Error(`Subscriber ${subscriberId} not found in queue ${queueId}`);
}
// Find next eligible message
let message: Message | null = null;
let messageIndex = -1;
for (let i = 0; i < queue.messages.length; i++) {
const msg = queue.messages[i];
// Check if message matches subscriber filter
if (subscriber.filter && !this.matchesFilter(msg, subscriber.filter)) {
continue;
}
message = msg;
messageIndex = i;
break;
}
if (!message) {
return null;
}
// Remove message from queue (for at-least-once, remove after ack)
if (queue.config.deliveryMode === 'at-most-once') {
queue.messages.splice(messageIndex, 1);
}
queue.statistics.depth = queue.messages.length;
queue.statistics.dequeueRate++;
subscriber.lastActivity = new Date();
this.logger.debug('Message dequeued', {
queueId,
messageId: message.id,
subscriberId,
queueDepth: queue.messages.length,
});
this.emit('message:dequeued', { queueId, message, subscriberId });
return message;
}
// === ROUTING AND DELIVERY ===
private async routeMessage(message: Message, preferredChannel?: string): Promise<void> {
// Apply routing rules
const route = await this.router.calculateRoute(message, preferredChannel);
// Update message route
message.metadata.route = [...(message.metadata.route || []), ...route.hops];
// Deliver to targets
for (const target of route.targets) {
await this.deliverMessage(message, target);
}
}
private async deliverMessage(message: Message, target: DeliveryTarget): Promise<void> {
try {
await this.deliveryManager.deliver(message, target);
this.metrics.recordDeliverySuccess(message);
} catch (error) {
this.metrics.recordDeliveryFailure(message);
// Handle delivery failure based on reliability level
if (message.reliability !== 'best-effort') {
await this.retryManager.scheduleRetry(message, target, error);
}
}
}
// === UTILITY METHODS ===
private validateMessage(message: Message): void {
if (message.metadata.size > this.config.maxMessageSize) {
throw new Error(
`Message size ${message.metadata.size} exceeds limit ${this.config.maxMessageSize}`,
);
}
if (message.expiresAt && message.expiresAt <= new Date()) {
throw new Error('Message has already expired');
}
if (message.receivers.length === 0) {
throw new Error('Message must have at least one receiver');
}
}
private async processContent(content: any): Promise<any> {
let processed = content;
// Compress if enabled
if (this.config.compressionEnabled) {
processed = await this.compress(processed);
}
// Encrypt if enabled
if (this.config.encryptionEnabled) {
processed = await this.encrypt(processed);
}
return processed;
}
private calculateSize(content: any): number {
return JSON.stringify(content).length;
}
private detectContentType(content: any): string {
if (typeof content === 'string') return 'text/plain';
if (typeof content === 'object') return 'application/json';
if (Buffer.isBuffer(content)) return 'application/octet-stream';
return 'application/unknown';
}
private async filterReceivers(
receivers: AgentId[],
filter: MessageFilter,
context: any,
): Promise<AgentId[]> {
// Placeholder for receiver filtering logic
return receivers;
}
private canJoinChannel(channel: MessageChannel, agentId: AgentId): boolean {
const acl = channel.config.accessControl;
// Check banned list
if (acl.bannedAgents.some((banned) => banned.id === agentId.id)) {
return false;
}
// Check allowed list (if specified)
if (acl.allowedSenders.length > 0) {
return acl.allowedSenders.some((allowed) => allowed.id === agentId.id);
}
return true;
}
private matchesFilter(message: Message, filter: MessageFilter): boolean {
return filter.conditions.every((condition) => {
const fieldValue = this.getFieldValue(message, condition.field);
return this.evaluateCondition(fieldValue, condition.operator, condition.value);
});
}
private getFieldValue(message: Message, field: string): any {
const parts = field.split('.');
let value: any = message;
for (const part of parts) {
value = value?.[part];
}
return value;
}
private evaluateCondition(fieldValue: any, operator: string, compareValue: any): boolean {
switch (operator) {
case 'eq':
return fieldValue === compareValue;
case 'ne':
return fieldValue !== compareValue;
case 'gt':
return fieldValue > compareValue;
case 'lt':
return fieldValue < compareValue;
case 'contains':
return String(fieldValue).includes(String(compareValue));
case 'matches':
return new RegExp(compareValue).test(String(fieldValue));
case 'in':
return Array.isArray(compareValue) && compareValue.includes(fieldValue);
default:
return false;
}
}
private insertMessageInQueue(queue: MessageQueue, message: Message): void {
switch (queue.type) {
case 'fifo':
queue.messages.push(message);
break;
case 'lifo':
queue.messages.unshift(message);
break;
case 'priority':
this.insertByPriority(queue.messages, message);
break;
case 'delay':
this.insertByTimestamp(queue.messages, message);
break;
default:
queue.messages.push(message);
}
}
private insertByPriority(messages: Message[], message: Message): void {
const priorityOrder = { critical: 0, high: 1, normal: 2, low: 3 };
const messagePriority = priorityOrder[message.priority];
let insertIndex = messages.length;
for (let i = 0; i < messages.length; i++) {
const currentPriority = priorityOrder[messages[i].priority];
if (messagePriority < currentPriority) {
insertIndex = i;
break;
}
}
messages.splice(insertIndex, 0, message);
}
private insertByTimestamp(messages: Message[], message: Message): void {
const targetTime = message.expiresAt || message.timestamp;
let insertIndex = messages.length;
for (let i = 0; i < messages.length; i++) {
const currentTime = messages[i].expiresAt || messages[i].timestamp;
if (targetTime <= currentTime) {
insertIndex = i;
break;
}
}
messages.splice(insertIndex, 0, message);
}
private async processQueue(queue: MessageQueue): Promise<void> {
// Process messages for subscribers
for (const subscriber of queue.subscribers) {
if (subscriber.prefetchCount > 0) {
// Deliver up to prefetch count
for (let i = 0; i < subscriber.prefetchCount; i++) {
const message = await this.dequeueMessage(queue.id, subscriber.id);
if (!message) break;
await this.deliverMessageToSubscriber(message, subscriber);
}
}
}
}
private async deliverMessageToSubscriber(
message: Message,
subscriber: QueueSubscriber,
): Promise<void> {
try {
// Deliver message to subscriber
this.emit('message:delivered', {
message,
subscriber: subscriber.agent,
});
// Handle acknowledgment if required
if (subscriber.ackMode === 'auto') {
await this.acknowledgeMessage(message.id, subscriber.agent);
}
} catch (error) {
this.logger.error('Failed to deliver message to subscriber', {
messageId: message.id,
subscriberId: subscriber.id,
error,
});
}
}
private checkAllAcknowledgments(message: Message): void {
const requiredAcks = message.receivers.length;
const receivedAcks = message.receivers.filter((receiver) =>
this.acknowledgments.has(`${message.id}:${receiver.id}`),
).length;
if (receivedAcks === requiredAcks) {
this.emit('message:fully-acknowledged', { message });
// Clean up acknowledgments
message.receivers.forEach((receiver) => {
this.acknowledgments.delete(`${message.id}:${receiver.id}`);
});
}
}
private async createDefaultChannels(): Promise<void> {
// System broadcast channel
await this.createChannel('system-broadcast', 'broadcast', {
persistent: true,
reliable: true,
maxParticipants: 10000,
});
// Agent coordination channel
await this.createChannel('agent-coordination', 'multicast', {
persistent: true,
reliable: true,
ordered: true,
});
// Task distribution channel
await this.createChannel('task-distribution', 'topic', {
persistent: true,
reliable: false,
});
}
private getDefaultBroadcastChannel(): MessageChannel | undefined {
return Array.from(this.channels.values()).find((channel) => channel.type === 'broadcast');
}
private createChannelStatistics(): ChannelStatistics {
return {
messagesTotal: 0,
messagesDelivered: 0,
messagesFailed: 0,
bytesTransferred: 0,
averageLatency: 0,
throughput: 0,
errorRate: 0,
participantCount: 0,
lastActivity: new Date(),
};
}
private createQueueStatistics(): QueueStatistics {
return {
depth: 0,
enqueueRate: 0,
dequeueRate: 0,
throughput: 0,
averageWaitTime: 0,
subscriberCount: 0,
deadLetterCount: 0,
};
}
private startMetricsCollection(): void {
this.metricsInterval = setInterval(() => {
this.updateMetrics();
}, 10000); // Every 10 seconds
}
private updateMetrics(): void {
// Update channel statistics
for (const channel of this.channels.values()) {
// Calculate throughput, latency, etc.
this.updateChannelStatistics(channel);
}
// Update queue statistics
for (const queue of this.queues.values()) {
this.updateQueueStatistics(queue);
}
// Emit metrics event
this.emit('metrics:updated', { metrics: this.getMetrics() });
}
private updateChannelStatistics(channel: MessageChannel): void {
// Placeholder for channel statistics calculation
channel.statistics.lastActivity = new Date();
}
private updateQueueStatistics(queue: MessageQueue): void {
// Placeholder for queue statistics calculation
queue.statistics.depth = queue.messages.length;
}
private handleAgentConnected(agentId: AgentId): void {
this.logger.info('Agent connected to message bus', { agentId: agentId.id });
this.emit('agent:connected', { agentId });
}
private handleAgentDisconnected(agentId: AgentId): void {
this.logger.info('Agent disconnected from message bus', { agentId: agentId.id });
// Remove from all channels
for (const channel of this.channels.values()) {
channel.participants = channel.participants.filter((p) => p.id !== agentId.id);
}
// Remove subscriptions
for (const [subId, subscription] of this.subscriptions) {
if (subscription.subscriber.id === agentId.id) {
this.subscriptions.delete(subId);
}
}
this.emit('agent:disconnected', { agentId });
}
private handleDeliverySuccess(data: any): void {
this.metrics.recordDeliverySuccess(data.message);
}
private handleDeliveryFailure(data: any): void {
this.metrics.recordDeliveryFailure(data.message);
}
private handleRetryExhausted(data: any): void {
this.logger.error('Message delivery retry exhausted', {
messageId: data.message.id,
target: data.target,
});
// Send to dead letter queue if configured
this.sendToDeadLetterQueue('system-dlq', data.message, 'retry_exhausted');
}
private async sendToDeadLetterQueue(
queueId: string,
message: Message,
reason: string,
): Promise<void> {
try {
message.metadata.deadLetterReason = reason;
message.metadata.deadLetterTimestamp = new Date();
await this.enqueueMessage(queueId, message);
} catch (error) {
this.logger.error('Failed to send message to dead letter queue', {
messageId: message.id,
queueId,
reason,
error,
});
}
}
private async compress(content: any): Promise<any> {
// Placeholder for compression
return content;
}
private async encrypt(content: any): Promise<any> {
// Placeholder for encryption
return content;
}
private async persistMessages(): Promise<void> {
// Placeholder for message persistence
this.logger.info('Persisting messages', { count: this.messageStore.size });
}
// === PUBLIC API ===
getChannel(channelId: string): MessageChannel | undefined {
return this.channels.get(channelId);
}
getAllChannels(): MessageChannel[] {
return Array.from(this.channels.values());
}
getQueue(queueId: string): MessageQueue | undefined {
return this.queues.get(queueId);
}
getAllQueues(): MessageQueue[] {
return Array.from(this.queues.values());
}
getSubscription(subscriptionId: string): TopicSubscription | undefined {
return this.subscriptions.get(subscriptionId);
}
getAllSubscriptions(): TopicSubscription[] {
return Array.from(this.subscriptions.values());
}
getMetrics(): any {
return {
channels: this.channels.size,
queues: this.queues.size,
subscriptions: this.subscriptions.size,
storedMessages: this.messageStore.size,
deliveryReceipts: this.deliveryReceipts.size,
acknowledgments: this.acknowledgments.size,
busMetrics: this.metrics.getMetrics(),
};
}
getMessage(messageId: string): Message | undefined {
return this.messageStore.get(messageId);
}
async addChannelFilter(channelId: string, filter: MessageFilter): Promise<void> {
const channel = this.channels.get(channelId);
if (!channel) {
throw new Error(`Channel ${channelId} not found`);
}
channel.filters.push(filter);
channel.filters.sort((a, b) => a.priority - b.priority);
}
async addChannelMiddleware(channelId: string, middleware: ChannelMiddleware): Promise<void> {
const channel = this.channels.get(channelId);
if (!channel) {
throw new Error(`Channel ${channelId} not found`);
}
channel.middleware.push(middleware);
channel.middleware.sort((a, b) => a.order - b.order);
}
}
// === HELPER CLASSES ===
interface DeliveryReceipt {
messageId: string;
target: string;
status: 'delivered' | 'failed' | 'pending';
timestamp: Date;
attempts: number;
error?: string;
}
interface MessageAcknowledgment {
messageId: string;
agentId: AgentId;
timestamp: Date;
status: 'acknowledged' | 'rejected';
}
interface DeliveryTarget {
type: 'agent' | 'channel' | 'queue' | 'topic';
id: string;
address?: string;
}
interface RouteResult {
targets: DeliveryTarget[];
hops: string[];
cost: number;
}
class MessageRouter {
constructor(
private config: MessageBusConfig,
private logger: ILogger,
) {}
async initialize(): Promise<void> {
this.logger.debug('Message router initialized');
}
async shutdown(): Promise<void> {
this.logger.debug('Message router shutdown');
}
async calculateRoute(message: Message, preferredChannel?: string): Promise<RouteResult> {
const targets: DeliveryTarget[] = [];
const hops: string[] = [];
// Simple routing - direct to receivers
for (const receiver of message.receivers) {
targets.push({
type: 'agent',
id: receiver.id,
});
hops.push(receiver.id);
}
return {
targets,
hops,
cost: targets.length,
};
}
}
class DeliveryManager extends EventEmitter {
constructor(
private config: MessageBusConfig,
private logger: ILogger,
) {
super();
}
async initialize(): Promise<void> {
this.logger.debug('Delivery manager initialized');
}
async shutdown(): Promise<void> {
this.logger.debug('Delivery manager shutdown');
}
async deliver(message: Message, target: DeliveryTarget): Promise<void> {
// Simulate delivery
this.logger.debug('Delivering message', {
messageId: message.id,
target: target.id,
type: target.type,
});
// Emit delivery success
this.emit('delivery:success', { message, target });
}
}
class RetryManager extends EventEmitter {
private retryQueue: Array<{ message: Message; target: DeliveryTarget; attempts: number }> = [];
private retryInterval?: NodeJS.Timeout;
constructor(
private config: MessageBusConfig,
private logger: ILogger,
) {
super();
}
async initialize(): Promise<void> {
this.startRetryProcessor();
this.logger.debug('Retry manager initialized');
}
async shutdown(): Promise<void> {
if (this.retryInterval) {
clearInterval(this.retryInterval);
}
this.logger.debug('Retry manager shutdown');
}
async scheduleRetry(message: Message, target: DeliveryTarget, error: any): Promise<void> {
const existingEntry = this.retryQueue.find(
(entry) => entry.message.id === message.id && entry.target.id === target.id,
);
if (existingEntry) {
existingEntry.attempts++;
} else {
this.retryQueue.push({ message, target, attempts: 1 });
}
this.logger.debug('Retry scheduled', {
messageId: message.id,
target: target.id,
error: error instanceof Error ? error.message : String(error),
});
}
private startRetryProcessor(): void {
this.retryInterval = setInterval(() => {
this.processRetries();
}, 5000); // Process retries every 5 seconds
}
private async processRetries(): Promise<void> {
const now = Date.now();
const toRetry = this.retryQueue.filter((entry) => {
const delay = this.calculateDelay(entry.attempts);
return now >= entry.message.timestamp.getTime() + delay;
});
for (const entry of toRetry) {
if (entry.attempts >= this.config.retryAttempts) {
// Remove from retry queue and emit exhausted event
this.retryQueue = this.retryQueue.filter((r) => r !== entry);
this.emit('retry:exhausted', entry);
} else {
// Retry delivery
try {
// Simulate retry delivery
this.logger.debug('Retrying message delivery', {
messageId: entry.message.id,
attempt: entry.attempts,
});
// Remove from retry queue on success
this.retryQueue = this.retryQueue.filter((r) => r !== entry);
} catch (error) {
// Keep in retry queue for next attempt
this.logger.warn('Retry attempt failed', {
messageId: entry.message.id,
attempt: entry.attempts,
error: error instanceof Error ? error.message : String(error),
});
}
}
}
}
private calculateDelay(attempts: number): number {
const baseDelay = 1000; // 1 second
return Math.min(
baseDelay * Math.pow(this.config.backoffMultiplier, attempts - 1),
30000, // Max 30 seconds
);
}
}
class MessageBusMetrics {
private messagesSent = 0;
private messagesDelivered = 0;
private messagesFailed = 0;
private bytesTransferred = 0;
private deliveryLatencies: number[] = [];
recordMessageSent(message: Message): void {
this.messagesSent++;
this.bytesTransferred += message.metadata.size;
}
recordDeliverySuccess(message: Message): void {
this.messagesDelivered++;
const latency = Date.now() - message.timestamp.getTime();
this.deliveryLatencies.push(latency);
// Keep only last 1000 latencies
if (this.deliveryLatencies.length > 1000) {
this.deliveryLatencies.shift();
}
}
recordDeliveryFailure(message: Message): void {
this.messagesFailed++;
}
getMetrics(): any {
const avgLatency =
this.deliveryLatencies.length > 0
? this.deliveryLatencies.reduce((sum, lat) => sum + lat, 0) / this.deliveryLatencies.length
: 0;
return {
messagesSent: this.messagesSent,
messagesDelivered: this.messagesDelivered,
messagesFailed: this.messagesFailed,
bytesTransferred: this.bytesTransferred,
averageLatency: avgLatency,
successRate: this.messagesSent > 0 ? (this.messagesDelivered / this.messagesSent) * 100 : 100,
};
}
}