import type { TaskState, PendingQuestion } from '../task/task-state.js'; import { TaskStatus } from '../task/task-state.js'; import type { WireState } from '../task/wire-state-mapper.js'; import { mapToWireState } from '../task/wire-state-mapper.js'; // --------------------------------------------------------------------------- // Response interfaces — SEP-1686 wire-compatible shapes // --------------------------------------------------------------------------- /** * Response shape for `tasks/get` — a single task's status snapshot. */ export interface TasksGetResponse { taskId: string; status: WireState; pollFrequency: number; keepAlive?: number; metadata?: { pendingQuestion?: PendingQuestion }; error?: string; } /** * List item shape for `tasks/list` — lightweight summary per task. */ export interface TasksListItem { taskId: string; status: WireState; pollFrequency: number; createdAt: string; lastUpdatedAt: string; } // --------------------------------------------------------------------------- // Poll frequency computation // --------------------------------------------------------------------------- /** * Returns the recommended poll interval in milliseconds based on task status. * * - WAITING_ANSWER → 2 000 ms (client should check frequently for input-required) * - PENDING, RUNNING → 5 000 ms * - All others (WAITING, RATE_LIMITED, terminal) → 30 000 ms */ export function computePollFrequency(task: TaskState): number { switch (task.status) { case TaskStatus.WAITING_ANSWER: return 2000; case TaskStatus.PENDING: case TaskStatus.RUNNING: return 5000; default: return 30000; } } // --------------------------------------------------------------------------- // tasks/get response builder // --------------------------------------------------------------------------- /** * Builds a SEP-1686 `tasks/get` response from internal TaskState. * * - Maps internal status to wire state via `mapToWireState` * - Computes poll frequency via `computePollFrequency` * - Includes `keepAlive` from task if present * - Includes first pending question in metadata when status is * WAITING_ANSWER and the queue is non-empty * - Includes error string if present */ export function buildTasksGetResponse(task: TaskState): TasksGetResponse { const status = mapToWireState(task.status); const pollFrequency = computePollFrequency(task); const hasPendingQuestion = task.status === TaskStatus.WAITING_ANSWER && task.pendingQuestions.length > 0; const response: TasksGetResponse = { taskId: task.id, status, pollFrequency, }; if (task.keepAlive !== undefined) { response.keepAlive = task.keepAlive; } if (hasPendingQuestion) { const firstQuestion = task.pendingQuestions[0]; if (firstQuestion !== undefined) { response.metadata = { pendingQuestion: firstQuestion }; } } if (task.error !== undefined) { response.error = task.error; } return response; } // --------------------------------------------------------------------------- // tasks/list response builder // --------------------------------------------------------------------------- const PAGE_SIZE = 50; /** * Builds a SEP-1686 `tasks/list` response. * * - Filters by wire status if `statusFilter` is provided * - Sorts by `createdAt` descending (newest first) * - Paginates with page size 50; cursor is a string-encoded start index * - Returns `nextCursor` if more pages exist */ export function buildTasksListResponse( tasks: TaskState[], cursor?: string, statusFilter?: WireState, ): { tasks: TasksListItem[]; nextCursor?: string } { // 1. Filter by wire status if requested const filtered = statusFilter ? tasks.filter(t => mapToWireState(t.status) === statusFilter) : [...tasks]; // 2. Sort by createdAt descending filtered.sort( (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), ); // 3. Paginate const start = cursor ? parseInt(cursor, 10) : 0; const page = filtered.slice(start, start + PAGE_SIZE); const nextStart = start + PAGE_SIZE; // 4. Map to list items const items: TasksListItem[] = page.map(t => ({ taskId: t.id, status: mapToWireState(t.status), pollFrequency: computePollFrequency(t), createdAt: t.createdAt, lastUpdatedAt: t.updatedAt, })); const result: { tasks: TasksListItem[]; nextCursor?: string } = { tasks: items }; if (nextStart < filtered.length) { result.nextCursor = String(nextStart); } return result; }