UNPKG

@dexwox-labs/a2a-server

Version:

TypeScript server implementation for Google's Agent-to-Agent (A2A) protocol - includes Express/WebSocket handlers, request validation and queue management

255 lines (243 loc) 7.77 kB
/** * @module EventQueue * @description Event queue system for task events in the A2A protocol * * This module provides an event queue implementation for managing and distributing * task-related events in the A2A protocol server. It supports event publishing, * subscription, history tracking, and hierarchical event propagation. */ import { EventEmitter } from 'node:events'; import type { Task, Artifact } from '@dexwox-labs/a2a-core'; /** * Type definition for events handled by the EventEmitter * @internal */ interface EventQueueEvents { event: [TaskEvent]; } /** * Represents a task-related event in the A2A protocol * * This type defines the structure of events that flow through the event queue, * including the event type, associated task, timestamp, and optional metadata. */ type TaskEvent = { /** The type of task event */ type: 'taskCreated' | 'taskUpdated' | 'taskCompleted' | 'taskFailed' | 'taskCanceled' | 'taskInputRequired' | 'artifactAdded' | 'taskStatusUpdate' | 'taskArtifactUpdate'; /** The task associated with this event */ task: Task; /** Timestamp when the event occurred (milliseconds since epoch) */ timestamp: number; /** Optional previous state of the task (for state transition events) */ previousState?: string; /** Optional artifact associated with the event (for artifact events) */ artifact?: Artifact; }; /** * Event queue for task-related events * * The EventQueue provides a publish-subscribe system for task-related events * in the A2A protocol server. It maintains a history of recent events, supports * hierarchical event propagation through child queues, and provides methods for * publishing and subscribing to events. * * @example * ```typescript * // Create an event queue * const eventQueue = new EventQueue(); * * // Subscribe to events * const unsubscribe = eventQueue.subscribe(event => { * console.log(`Event: ${event.type} for task ${event.task.id}`); * * if (event.type === 'taskCompleted') { * console.log('Task completed successfully!'); * } * }); * * // Publish an event * eventQueue.publish({ * type: 'taskCreated', * task: someTask, * timestamp: Date.now() * }); * * // Later, unsubscribe * unsubscribe(); * * // Create a child queue that receives all events from the parent * const childQueue = eventQueue.tap(); * ``` */ export class EventQueue { /** Event emitter for handling event subscriptions */ private readonly emitter = new EventEmitter<EventQueueEvents>(); /** History of recent events */ private readonly history: TaskEvent[] = []; /** Maximum number of events to keep in history */ private readonly maxHistory = 1000; /** Child queues that receive events from this queue */ private readonly children: EventQueue[] = []; /** Whether this queue has been shut down */ private isShutdown = false; /** * Publishes an event to the queue * * This method adds an event to the queue's history, emits it to all subscribers, * and propagates it to any child queues. If the queue has been shut down, * an error is thrown. * * @param event - The event to publish * @throws Error if the queue has been shut down * * @example * ```typescript * eventQueue.publish({ * type: 'taskUpdated', * task: updatedTask, * timestamp: Date.now(), * previousState: 'submitted' * }); * ``` */ publish(event: TaskEvent): void { if (this.isShutdown) { throw new Error('EventQueue has been shutdown'); } // Add to history and enforce size limit this.history.push(event); if (this.history.length > this.maxHistory) { this.history.shift(); } this.emitter.emit('event', event); // Propagate to child queues this.children.forEach(child => child.publish(event)); } /** * Subscribes to events from the queue * * This method registers a callback to be called for each event in the queue. * It immediately calls the callback with all historical events, then registers * it for future events. It returns a function that can be called to unsubscribe. * * @param callback - Function to call for each event * @returns Function to call to unsubscribe * * @example * ```typescript * const unsubscribe = eventQueue.subscribe(event => { * if (event.type === 'taskCompleted') { * console.log(`Task ${event.task.id} completed`); * } * }); * * // Later, when no longer interested in events * unsubscribe(); * ``` */ subscribe(callback: (event: TaskEvent) => void): () => void { // Send historical events first this.history.forEach(event => callback(event)); // Subscribe to new events this.emitter.on('event', callback); return () => this.emitter.off('event', callback); } /** * Creates a child queue that receives all events from this queue * * This method creates a new EventQueue and registers it as a child of this queue. * All events published to this queue will also be published to the child queue. * This is useful for creating specialized event handlers or for filtering events. * * @returns A new EventQueue that receives all events from this queue * * @example * ```typescript * // Create a main event queue * const mainQueue = new EventQueue(); * * // Create a specialized queue for completed tasks * const completedTasksQueue = mainQueue.tap(); * completedTasksQueue.subscribe(event => { * if (event.type === 'taskCompleted') { * // Handle completed tasks * } * }); * ``` */ tap(): EventQueue { const child = new EventQueue(); this.children.push(child); return child; } /** * Shuts down the event queue * * This method marks the queue as shut down, closes all child queues, * and removes all event listeners. After calling this method, no more * events can be published to the queue. * * @example * ```typescript * // When done with the event queue * eventQueue.close(); * ``` */ close(): void { this.isShutdown = true; this.children.forEach(child => child.close()); this.emitter.removeAllListeners(); } /** * Gets a copy of the event history * * This method returns a copy of the queue's event history. The returned array * is a new instance, so modifications to it will not affect the queue's history. * * @returns A copy of the event history * * @example * ```typescript * const history = eventQueue.getHistory(); * console.log(`Queue has ${history.length} events in history`); * ``` */ getHistory(): TaskEvent[] { return [...this.history]; } /** * Waits for and returns the next event from the queue * * This method returns a promise that resolves with the next event published * to the queue. If the queue is shut down, it resolves with undefined. * This is useful for implementing event-driven workflows. * * @returns Promise that resolves with the next event, or undefined if the queue is shut down * * @example * ```typescript * // Process events one at a time * async function processEvents() { * while (true) { * const event = await eventQueue.dequeue(); * if (!event) { * console.log('Queue has been shut down'); * break; * } * console.log(`Processing event: ${event.type}`); * } * } * ``` */ async dequeue(): Promise<TaskEvent | undefined> { if (this.isShutdown) { return undefined; } return new Promise(resolve => { const unsubscribe = this.subscribe(event => { unsubscribe(); resolve(event); }); }); } }