@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
361 lines • 13.7 kB
JavaScript
"use strict";
/**
* @module TaskManager
* @description Task lifecycle and state management for the A2A server
*
* This module provides a task manager implementation that handles creating,
* updating, and managing tasks throughout their lifecycle. It also provides
* support for task artifacts and push notifications.
*/
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 __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TaskManager = void 0;
const a2a_core_1 = require("@dexwox-labs/a2a-core");
const result_aggregator_1 = require("../agent-execution/result-aggregator");
const logger_1 = __importDefault(require("../utils/logger"));
/**
* Manages task lifecycle and state
*
* The TaskManager is responsible for creating, updating, and managing tasks
* throughout their lifecycle. It provides methods for task creation, status
* updates, cancellation, and artifact management.
*
* @example
* ```typescript
* // Create a task manager with in-memory storage
* const taskStore = new InMemoryTaskStore();
* const taskManager = new TaskManager(taskStore);
*
* // Create a new task
* const task = await taskManager.createTask({
* name: 'ProcessData',
* agentId: 'data-processor',
* parts: [{ type: 'text', content: 'Process this data', format: 'plain' }],
* expectedParts: 1,
* createdAt: new Date().toISOString(),
* updatedAt: new Date().toISOString()
* });
*
* console.log('Created task:', task.id);
* ```
*/
class TaskManager {
/**
* Creates a new TaskManager
*
* @param taskStore - Storage backend for tasks
* @param pushService - Optional service for push notifications
*/
constructor(taskStore, pushService) {
this.taskStore = taskStore;
this.pushService = pushService;
/** Map of task aggregators for collecting and processing task results */
this.aggregators = new Map();
logger_1.default.info('TaskManager initialized', { hasPushService: !!pushService });
}
/**
* Creates a new task
*
* Creates a task with the provided parameters and initializes a result
* aggregator if expectedParts is specified.
*
* @param task - Task parameters (without id and status)
* @returns Promise resolving to the created task
*
* @example
* ```typescript
* const task = await taskManager.createTask({
* name: 'ProcessImage',
* agentId: 'image-processor',
* parts: [{
* type: 'file',
* content: 'base64-encoded-image-data',
* mimeType: 'image/jpeg',
* name: 'image.jpg'
* }],
* expectedParts: 2,
* createdAt: new Date().toISOString(),
* updatedAt: new Date().toISOString()
* });
* ```
*/
createTask(task) {
return __awaiter(this, void 0, void 0, function* () {
var _a;
logger_1.default.debug('Creating new task', { taskParams: task });
const createdTask = yield this.taskStore.createTask(Object.assign(Object.assign({}, task), { status: 'submitted', parts: (_a = task.parts) !== null && _a !== void 0 ? _a : [] // Ensure parts array exists
}));
logger_1.default.info('Successfully created task', { taskId: createdTask.id });
// Initialize result aggregation if expectedParts is specified
if (task.expectedParts) {
logger_1.default.debug('Initializing result aggregator', { taskId: createdTask.id });
this.aggregators.set(createdTask.id, new result_aggregator_1.ResultAggregator(createdTask));
}
return createdTask;
});
}
/**
* Gets the result aggregator for a task
*
* Result aggregators collect and process task results, including
* handling streaming responses and combining multiple parts.
*
* @param taskId - ID of the task
* @returns The result aggregator for the task, or undefined if none exists
*/
getAggregator(taskId) {
return this.aggregators.get(taskId);
}
/**
* Gets a task by ID
*
* Retrieves a task from the task store by its ID.
*
* @param taskId - ID of the task to retrieve
* @returns Promise resolving to the task
* @throws {TaskNotFoundError} If the task is not found
*
* @example
* ```typescript
* try {
* const task = await taskManager.getTask('task-123');
* console.log('Task status:', task.status);
* } catch (error) {
* if (error instanceof TaskNotFoundError) {
* console.error('Task not found');
* } else {
* console.error('Error retrieving task:', error);
* }
* }
* ```
*/
getTask(taskId) {
return __awaiter(this, void 0, void 0, function* () {
logger_1.default.debug('Getting task', { taskId });
const task = yield this.taskStore.getTask(taskId);
if (!task) {
logger_1.default.error('Task not found', { taskId });
throw new a2a_core_1.TaskNotFoundError(taskId);
}
logger_1.default.debug('Successfully retrieved task', { taskId });
return task;
});
}
/**
* Updates a task with new data
*
* Updates a task with the provided partial task data and sets
* the updatedAt timestamp to the current time.
*
* @param taskId - ID of the task to update
* @param updates - Partial task data to update
* @returns Promise resolving to the updated task
* @throws {TaskNotFoundError} If the task is not found
*
* @example
* ```typescript
* const updatedTask = await taskManager.updateTask('task-123', {
* name: 'Updated Task Name',
* metadata: { priority: 'high' }
* });
* ```
*/
updateTask(taskId, updates) {
return __awaiter(this, void 0, void 0, function* () {
const currentTask = yield this.getTask(taskId);
return this.taskStore.updateTask(taskId, Object.assign(Object.assign(Object.assign({}, currentTask), updates), { updatedAt: new Date().toISOString() }));
});
}
/**
* Updates a task's status
*
* Updates the status of a task and sets the updatedAt timestamp
* to the current time.
*
* @param taskId - ID of the task to update
* @param status - New status for the task
* @returns Promise resolving to the updated task
* @throws {TaskNotFoundError} If the task is not found
*
* @example
* ```typescript
* // Update task status to 'completed'
* const task = await taskManager.updateTaskStatus('task-123', 'completed');
* console.log('Task status updated:', task.status);
* ```
*/
updateTaskStatus(taskId, status) {
return __awaiter(this, void 0, void 0, function* () {
logger_1.default.info('Updating task status', { taskId, newStatus: status });
const updatedTask = yield this.updateTask(taskId, { status });
logger_1.default.info('Successfully updated task status', { taskId });
return updatedTask;
});
}
/**
* Cancels a task
*
* Cancels a task that is not already in a terminal state (completed, failed, or canceled).
* If a push service is configured, it will send a push notification about the cancellation.
*
* @param taskId - ID of the task to cancel
* @returns Promise resolving when the task is canceled
* @throws {TaskNotFoundError} If the task is not found
* @throws {InvalidTaskStateError} If the task is already in a terminal state
*
* @example
* ```typescript
* try {
* await taskManager.cancelTask('task-123');
* console.log('Task canceled successfully');
* } catch (error) {
* if (error instanceof InvalidTaskStateError) {
* console.error('Cannot cancel task in terminal state');
* } else {
* console.error('Error canceling task:', error);
* }
* }
* ```
*/
cancelTask(taskId) {
return __awaiter(this, void 0, void 0, function* () {
logger_1.default.info('Attempting to cancel task', { taskId });
const task = yield this.getTask(taskId);
if (['completed', 'failed', 'canceled'].includes(task.status)) {
logger_1.default.error('Cannot cancel task in terminal state', { taskId, currentStatus: task.status });
throw new a2a_core_1.InvalidTaskStateError(`Task ${taskId} is already in terminal state: ${task.status}`);
}
yield this.taskStore.cancelTask(taskId);
logger_1.default.info('Successfully canceled task', { taskId });
if (this.pushService) {
logger_1.default.debug('Sending push notification for canceled task', { taskId });
yield this.pushService.notifyStatusChange(taskId, 'canceled');
}
});
}
/**
* Lists all active tasks
*
* Retrieves all tasks that are not in a terminal state (completed, failed, or canceled).
* Active tasks include those with status 'submitted', 'working', or 'input_required'.
*
* @returns Promise resolving to an array of active tasks
*
* @example
* ```typescript
* const activeTasks = await taskManager.listActiveTasks();
* console.log(`Found ${activeTasks.length} active tasks`);
*
* // Process each active task
* for (const task of activeTasks) {
* console.log(`Task ${task.id} is ${task.status}`);
* }
* ```
*/
listActiveTasks() {
return __awaiter(this, void 0, void 0, function* () {
logger_1.default.debug('Listing active tasks');
const tasks = yield this.taskStore.getTasksByStatus([
'submitted', 'working', 'input_required'
]);
logger_1.default.debug('Found active tasks', { count: tasks.length });
return tasks;
});
}
/**
* Adds an artifact to a task
*
* Artifacts represent files, data, or other content associated with a task.
* This method adds an artifact to a specific task.
*
* @param taskId - ID of the task
* @param artifact - Artifact to add
* @returns Promise resolving when the artifact is added
* @throws {TaskNotFoundError} If the task is not found
*
* @example
* ```typescript
* await taskManager.addArtifact('task-123', {
* id: 'artifact-456',
* type: 'file',
* content: 'base64-encoded-file-data',
* createdAt: new Date().toISOString(),
* updatedAt: new Date().toISOString(),
* metadata: {
* filename: 'report.pdf',
* mimeType: 'application/pdf'
* }
* });
* ```
*/
addArtifact(taskId, artifact) {
return __awaiter(this, void 0, void 0, function* () {
yield this.taskStore.addArtifact(taskId, artifact);
});
}
/**
* Gets all artifacts for a task
*
* Retrieves all artifacts associated with a specific task.
*
* @param taskId - ID of the task
* @returns Promise resolving to an array of artifacts
* @throws {TaskNotFoundError} If the task is not found
*
* @example
* ```typescript
* const artifacts = await taskManager.getArtifacts('task-123');
* console.log(`Task has ${artifacts.length} artifacts`);
*
* // Process each artifact
* for (const artifact of artifacts) {
* console.log(`Artifact ${artifact.id} of type ${artifact.type}`);
* }
* ```
*/
getArtifacts(taskId) {
return __awaiter(this, void 0, void 0, function* () {
return this.taskStore.getArtifacts(taskId);
});
}
/**
* Gets a specific artifact for a task
*
* Retrieves a specific artifact by ID from a specific task.
*
* @param taskId - ID of the task
* @param artifactId - ID of the artifact
* @returns Promise resolving to the artifact, or null if not found
* @throws {TaskNotFoundError} If the task is not found
*
* @example
* ```typescript
* const artifact = await taskManager.getArtifact('task-123', 'artifact-456');
* if (artifact) {
* console.log('Found artifact:', artifact.id);
* console.log('Type:', artifact.type);
* console.log('Created at:', artifact.createdAt);
* } else {
* console.log('Artifact not found');
* }
* ```
*/
getArtifact(taskId, artifactId) {
return __awaiter(this, void 0, void 0, function* () {
return this.taskStore.getArtifact(taskId, artifactId);
});
}
}
exports.TaskManager = TaskManager;
//# sourceMappingURL=task-manager.js.map