semnet-snap-protocol
Version:
TypeScript reference implementation of the SNAP Protocol v1.1 - Agent Internet Edition
317 lines • 8.3 kB
JavaScript
import { nanoid } from 'nanoid';
import { TaskSchema, TaskCreateRequestSchema, TaskStatus } from './types.js';
/**
* Generate a unique task ID
*/
export function generateTaskId() {
return `task_${nanoid()}`;
}
/**
* Task builder for creating task requests
*/
export class TaskBuilder {
request;
constructor(message) {
this.request = {
message,
priority: 'normal'
};
}
/**
* Set callback URL for status updates
*/
callback(url) {
this.request.callback = url;
return this;
}
/**
* Set task timeout in seconds
*/
timeout(seconds) {
this.request.timeout = seconds;
return this;
}
/**
* Set task priority
*/
priority(priority) {
this.request.priority = priority;
return this;
}
/**
* Add metadata
*/
metadata(metadata) {
this.request.metadata = { ...this.request.metadata, ...metadata };
return this;
}
/**
* Build the task creation request
*/
build() {
return TaskCreateRequestSchema.parse(this.request);
}
/**
* Build as JSON-RPC request
*/
buildRPC(id) {
return {
jsonrpc: '2.0',
method: 'task/create',
params: this.build(),
id: id || nanoid()
};
}
}
/**
* Task manager for creating and updating tasks
*/
export class TaskManager {
/**
* Create a new task
*/
static create(message) {
return new TaskBuilder(message);
}
/**
* Create a task instance
*/
static createTask(request, agentId) {
const now = new Date().toISOString();
return {
id: generateTaskId(),
status: TaskStatus.QUEUED,
createdAt: now,
updatedAt: now,
metadata: {
...request.metadata,
agentId: agentId.id,
priority: request.priority,
...(request.timeout && { timeout: request.timeout }),
...(request.callback && { callback: request.callback })
}
};
}
/**
* Update task status
*/
static updateTask(task, update) {
const updatedTask = {
...task,
...update,
updatedAt: new Date().toISOString()
};
return TaskSchema.parse(updatedTask);
}
/**
* Mark task as processing
*/
static startProcessing(task, message, estimatedCompletion) {
return this.updateTask(task, {
status: TaskStatus.PROCESSING,
progress: 0,
message,
estimatedCompletion
});
}
/**
* Update task progress
*/
static updateProgress(task, progress, message) {
return this.updateTask(task, {
progress: Math.max(0, Math.min(1, progress)),
message
});
}
/**
* Complete task with result
*/
static complete(task, result) {
return this.updateTask(task, {
status: TaskStatus.COMPLETED,
progress: 1,
result,
message: 'Task completed successfully'
});
}
/**
* Fail task with error
*/
static fail(task, error) {
return this.updateTask(task, {
status: TaskStatus.FAILED,
error,
message: 'Task failed'
});
}
/**
* Cancel task
*/
static cancel(task, reason) {
return this.updateTask(task, {
status: TaskStatus.CANCELLED,
message: reason || 'Task cancelled',
error: reason
});
}
/**
* Check if task is active (can be updated)
*/
static isActive(task) {
return task.status === TaskStatus.QUEUED || task.status === TaskStatus.PROCESSING;
}
/**
* Check if task is completed (successfully or not)
*/
static isCompleted(task) {
return [TaskStatus.COMPLETED, TaskStatus.FAILED, TaskStatus.CANCELLED].includes(task.status);
}
/**
* Check if task is expired based on timeout
*/
static isExpired(task) {
const timeout = task.metadata?.timeout;
if (!timeout)
return false;
const createdAt = new Date(task.createdAt).getTime();
const now = Date.now();
const elapsedSeconds = (now - createdAt) / 1000;
return elapsedSeconds > timeout;
}
/**
* Get task duration in seconds
*/
static getDuration(task) {
const createdAt = new Date(task.createdAt).getTime();
const endTime = this.isCompleted(task)
? new Date(task.updatedAt).getTime()
: Date.now();
return (endTime - createdAt) / 1000;
}
/**
* Estimate completion time based on progress
*/
static estimateCompletion(task) {
if (!task.progress || task.progress === 0)
return null;
const duration = this.getDuration(task);
const estimatedTotal = duration / task.progress;
const remainingTime = estimatedTotal - duration;
const completionTime = new Date(Date.now() + remainingTime * 1000);
return completionTime.toISOString();
}
}
/**
* Task utilities
*/
export class TaskUtils {
/**
* Create JSON-RPC request to get task status
*/
static getStatusRequest(taskId, rpcId) {
return {
jsonrpc: '2.0',
method: 'task/status',
params: { taskId },
id: rpcId || nanoid()
};
}
/**
* Create JSON-RPC request to cancel task
*/
static cancelRequest(taskId, reason, rpcId) {
return {
jsonrpc: '2.0',
method: 'task/cancel',
params: {
taskId,
...(reason && { reason })
},
id: rpcId || nanoid()
};
}
/**
* Create JSON-RPC request to list tasks
*/
static listRequest(filters, rpcId) {
return {
jsonrpc: '2.0',
method: 'task/list',
params: filters || {},
id: rpcId || nanoid()
};
}
/**
* Create task status update notification
*/
static createStatusUpdate(task) {
return {
jsonrpc: '2.0',
method: 'task/update',
params: {
taskId: task.id,
status: task.status,
progress: task.progress,
message: task.message,
updatedAt: task.updatedAt,
...(task.result && { result: task.result }),
...(task.error && { error: task.error })
},
id: nanoid()
};
}
/**
* Validate task object
*/
static validate(task) {
return TaskSchema.parse(task);
}
/**
* Filter tasks by status
*/
static filterByStatus(tasks, status) {
return tasks.filter(task => task.status === status);
}
/**
* Sort tasks by creation date
*/
static sortByCreatedAt(tasks, descending = true) {
return tasks.sort((a, b) => {
const aTime = new Date(a.createdAt).getTime();
const bTime = new Date(b.createdAt).getTime();
return descending ? bTime - aTime : aTime - bTime;
});
}
/**
* Get task summary statistics
*/
static getStats(tasks) {
const stats = {
total: tasks.length,
queued: 0,
processing: 0,
completed: 0,
failed: 0,
cancelled: 0,
averageDuration: 0
};
let totalDuration = 0;
let completedCount = 0;
for (const task of tasks) {
stats[task.status]++;
if (TaskManager.isCompleted(task)) {
totalDuration += TaskManager.getDuration(task);
completedCount++;
}
}
stats.averageDuration = completedCount > 0 ? totalDuration / completedCount : 0;
return stats;
}
}
/**
* Convenience function to create a task
*/
export function createTask(message) {
return TaskManager.create(message);
}
//# sourceMappingURL=task.js.map