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

426 lines 18 kB
"use strict"; /** * @module RequestHandler * @description Core request handling and routing for the A2A server * * This module provides the main request handler implementation for the A2A server, * handling all incoming requests, routing them to the appropriate handlers, and * managing the lifecycle of tasks and messages. */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); } var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) { if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); var g = generator.apply(thisArg, _arguments || []), i, q = []; return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i; function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; } function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } } function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } function fulfill(value) { resume("next", value); } function reject(value) { resume("throw", value); } function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DefaultRequestHandler = void 0; const express_1 = require("express"); const default_jsonrpc_handler_1 = require("./request-handlers/default-jsonrpc-handler"); const crypto_1 = require("crypto"); const agent_executor_1 = require("./agent-execution/agent-executor"); const task_manager_1 = require("./tasks/task-manager"); const in_memory_task_store_1 = require("./tasks/in-memory-task-store"); const push_service_1 = require("./push-notifications/push-service"); const event_queue_1 = require("./agent-execution/event-queue"); const task_event_manager_1 = require("./agent-execution/task-event-manager"); const in_memory_queue_manager_1 = require("./queue-system/in-memory-queue-manager"); const a2a_core_1 = require("@dexwox-labs/a2a-core"); const request_context_1 = require("./agent-execution/request-context"); /** * Default implementation of the RequestHandler interface * * This class provides the standard implementation of the RequestHandler interface, * handling all A2A protocol requests including message sending, task management, * push notifications, and agent discovery. * * @example * ```typescript * // Create a request handler with available agents * const agents: AgentCard[] = [ * { * id: 'assistant-agent', * name: 'Assistant', * description: 'A helpful assistant', * capabilities: ['chat', 'answer-questions'] * } * ]; * * const requestHandler = new DefaultRequestHandler(agents); * * // Use in an Express app * app.use('/a2a', requestHandler.router); * ``` */ class DefaultRequestHandler extends default_jsonrpc_handler_1.DefaultJsonRpcRequestHandler { /** * Creates a new DefaultRequestHandler * * @param agents - Array of available agent cards */ constructor(agents = []) { super(); /** Manager for request queues */ this.queueManager = new in_memory_queue_manager_1.InMemoryQueueManager(); /** Manager for tasks */ this.taskManager = new task_manager_1.TaskManager(new in_memory_task_store_1.InMemoryTaskStore()); /** Service for push notifications */ this.pushService = new push_service_1.PushNotificationService(); this.router = (0, express_1.Router)(); this.eventQueue = new event_queue_1.EventQueue(); this.taskEventManager = new task_event_manager_1.TaskEventManager(this.eventQueue); this.agentExecutor = new agent_executor_1.DefaultAgentExecutor(new task_manager_1.TaskManager(new in_memory_task_store_1.InMemoryTaskStore()), this.taskEventManager); this.agents = agents; this.setupRoutes(); } /** * Sets up the Express routes for handling A2A protocol requests * * @private */ setupRoutes() { this.router.post('/sendMessage', this.handleJsonRpcSendMessage.bind(this)); this.router.post('/streamMessage', this.handleJsonRpcStreamMessage.bind(this)); this.router.get('/tasks/:taskId', this.handleJsonRpcGetTaskStatus.bind(this)); this.router.post('/tasks/:taskId/cancel', this.handleJsonRpcCancelTask.bind(this)); this.router.get('/agents', this.handleJsonRpcDiscoverAgents.bind(this)); } handleSendMessage(parts, agentId) { return __awaiter(this, void 0, void 0, function* () { const task = yield this.taskManager.createTask({ name: 'MessageTask', agentId, parts: parts || [], expectedParts: parts.length, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }); try { yield this.agentExecutor.execute((0, request_context_1.createRequestContext)(task, agentId), this.eventQueue); } catch (error) { yield this.taskManager.updateTaskStatus(task.id, 'failed'); this.taskEventManager.taskFailed(task, this.normalizeError(error)); throw error; } this.taskEventManager.taskCreated(task); for (const part of parts) { if (part.type === 'file') { this.taskEventManager.artifactAdded(task, { id: (0, crypto_1.randomUUID)(), type: 'file', content: { data: part.content }, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }); } else if (part.type === 'data') { this.taskEventManager.artifactAdded(task, { id: (0, crypto_1.randomUUID)(), type: 'data', content: part.content, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }); } } return task.id; }); } createArtifactEvent(task, part) { if (part.type === 'file') { return { id: (0, crypto_1.randomUUID)(), type: part.type, content: { data: part.content }, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), metadata: { name: part.name, size: part.size, mimeType: part.mimeType } }; } else if (part.type === 'data') { return { id: (0, crypto_1.randomUUID)(), type: part.type, content: part.content, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), metadata: { schema: ('schema' in part) ? part.schema : 'json' } }; } throw new Error(`Cannot create artifact from part type: ${part.type}`); } createHeartbeat() { return { type: 'heartbeat', content: new Date().toISOString(), format: 'plain' }; } handleStreamMessage(parts, agentId) { return __asyncGenerator(this, arguments, function* handleStreamMessage_1() { const task = yield __await(this.taskManager.createTask({ name: 'StreamTask', agentId, parts, expectedParts: parts.length, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() })); const aggregator = this.taskManager.getAggregator(task.id); this.taskEventManager.taskCreated(task); for (const part of parts) { yield yield __await(part); if (aggregator) { aggregator.addPart(part); if (part.type === 'file' || part.type === 'data') { try { this.taskEventManager.artifactAdded(task, this.createArtifactEvent(task, part)); } catch (err) { console.error('Failed to create artifact:', err); } } } // Send heartbeat every 15 seconds if (Math.random() < 0.066) { // ~1/15 chance per second const heartbeat = this.createHeartbeat(); yield yield __await(heartbeat); if (aggregator) { aggregator.addPart(heartbeat); } } yield __await(new Promise(resolve => setTimeout(resolve, 1000))); } aggregator === null || aggregator === void 0 ? void 0 : aggregator.complete(); }); } /** * Gets the status of a task * * Retrieves the current status and details of a task by its ID. * * @param taskId - ID of the task to retrieve * @returns Promise resolving to the task object * @throws {A2AError} If the task is not found * * @example * ```typescript * try { * const task = await requestHandler.handleGetTaskStatus('task-123'); * console.log('Task status:', task.status); * console.log('Task result:', task.result); * } catch (error) { * console.error('Failed to get task:', error); * } * ``` */ handleGetTaskStatus(taskId) { return __awaiter(this, void 0, void 0, function* () { const task = yield this.taskManager.getTask(taskId); if (!task) { throw this.normalizeError({ code: -32004, message: 'Task not found' }); } return task; }); } /** * Cancels a running task * * Attempts to cancel a task that is currently in progress. This will notify * the agent to stop processing and update the task status to 'canceled'. * * @param taskId - ID of the task to cancel * @returns Promise resolving when the task is canceled * @throws {A2AError} If the task is not found or has no agent ID * * @example * ```typescript * try { * await requestHandler.handleCancelTask('task-123'); * console.log('Task canceled successfully'); * } catch (error) { * console.error('Failed to cancel task:', error); * } * ``` */ handleCancelTask(taskId) { return __awaiter(this, void 0, void 0, function* () { const task = yield this.taskManager.getTask(taskId); if (!task.agentId) { throw this.normalizeError({ code: -32000, message: 'Task has no agentId' }); } yield this.agentExecutor.cancel((0, request_context_1.createRequestContext)(task, task.agentId), this.eventQueue); yield this.taskManager.cancelTask(taskId); this.taskEventManager.taskUpdated(task); }); } /** * Discovers available agents * * Returns a list of available agents, optionally filtered by capability. * * @param capability - Optional capability to filter agents by * @returns Promise resolving to an array of agent cards * * @example * ```typescript * // Get all agents * const allAgents = await requestHandler.handleDiscoverAgents(); * console.log('All agents:', allAgents); * * // Get agents with a specific capability * const chatAgents = await requestHandler.handleDiscoverAgents('chat'); * console.log('Chat agents:', chatAgents); * ``` */ handleDiscoverAgents(capability) { return __awaiter(this, void 0, void 0, function* () { return capability ? this.agents.filter(agent => agent.capabilities.includes(capability)) : this.agents; }); } handleTaskResubscription(taskId) { return __asyncGenerator(this, arguments, function* handleTaskResubscription_1() { var _a; const task = yield __await(this.taskManager.getTask(taskId)); if (!task) { throw this.normalizeError({ code: -32004, message: 'Task not found' }); } // Yield existing task parts first const parts = (_a = task.parts) !== null && _a !== void 0 ? _a : []; for (const part of parts) { yield yield __await(part); } // Track last activity time let lastActivity = Date.now(); // Then continue streaming new updates (mock implementation) while (task.status === 'working') { yield __await(new Promise(resolve => setTimeout(resolve, 1000))); const update = { type: 'text', content: `Task ${taskId} update at ${new Date().toISOString()}`, format: 'plain' }; yield yield __await(update); lastActivity = Date.now(); // Check if we need to send a heartbeat if (Date.now() - lastActivity > 15000) { const heartbeat = { type: 'heartbeat', content: new Date().toISOString(), format: 'plain' }; yield yield __await(heartbeat); lastActivity = Date.now(); } } }); } /** * Sets push notification configuration for a task * * Configures push notifications for a specific task, including the endpoint * to send notifications to and which events to notify about. * * @param taskId - ID of the task * @param config - Push notification configuration * @returns Promise resolving when the configuration is set * @throws {A2AError} If the task is not found * * @example * ```typescript * await requestHandler.handleSetPushConfig('task-123', { * enabled: true, * endpoint: 'https://webhook.example.com/notifications', * authToken: 'your-auth-token', * events: ['taskCompleted', 'taskFailed'] * }); * ``` */ handleSetPushConfig(taskId, config) { return __awaiter(this, void 0, void 0, function* () { yield this.taskManager.getTask(taskId); // Verify task exists yield this.pushService.setConfig(taskId, config); }); } /** * Gets push notification configuration for a task * * Retrieves the current push notification configuration for a specific task. * * @param taskId - ID of the task * @returns Promise resolving to the push notification configuration * @throws {A2AError} If the task is not found * * @example * ```typescript * const config = await requestHandler.handleGetPushConfig('task-123'); * console.log('Push notification config:', config); * console.log('Enabled:', config.enabled); * console.log('Events:', config.events); * ``` */ handleGetPushConfig(taskId) { return __awaiter(this, void 0, void 0, function* () { yield this.taskManager.getTask(taskId); // Verify task exists return this.pushService.getConfig(taskId); }); } /** * Normalizes errors to A2AError format * * Converts various error types to the standardized A2AError format used * throughout the A2A protocol. * * @param err - Error to normalize * @returns Normalized A2AError * * @example * ```typescript * try { * // Some operation that might fail * throw new Error('Something went wrong'); * } catch (error) { * // Normalize the error to A2AError format * const normalizedError = requestHandler.normalizeError(error); * console.error('Normalized error:', normalizedError); * console.error('Error code:', normalizedError.code); * } * ``` */ normalizeError(err) { if (err instanceof a2a_core_1.A2AError) { return err; } if (err instanceof Error) { return new a2a_core_1.A2AError(err.message, -32000, { stack: err.stack }); } return new a2a_core_1.A2AError('Unknown error occurred', -32000, { originalError: err }); } } exports.DefaultRequestHandler = DefaultRequestHandler; //# sourceMappingURL=request-handler.js.map