UNPKG

@mastra/core

Version:

Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.

219 lines (217 loc) 9.48 kB
import { createStep, createWorkflow } from './chunk-NNKKOMEN.js'; import { BACKGROUND_TASK_WORKFLOW_ID } from './chunk-QOKJTCIS.js'; export { BACKGROUND_TASK_WORKFLOW_ID } from './chunk-QOKJTCIS.js'; import { z } from 'zod'; var inputSchema = z.object({ taskId: z.string() }); var attemptOutcomeSchema = z.enum(["success", "retry", "cancelled", "timed_out"]); var attemptOutputSchema = z.object({ taskId: z.string(), outcome: attemptOutcomeSchema, result: z.unknown().optional(), error: z.any().optional() }); var bodyIOSchema = z.object({ taskId: z.string(), done: z.boolean().optional(), result: z.unknown().optional() }); var bodyOutputSchema = z.object({ taskId: z.string(), done: z.boolean(), result: z.unknown().optional() }); var WORKFLOW_STATUS_TO_PERSIST = ["suspended", "pending", "paused", "waiting"]; function buildBackgroundTaskWorkflow(manager) { const runAttemptStep = createStep({ id: "run-attempt", inputSchema: bodyIOSchema, outputSchema: attemptOutputSchema, execute: async ({ inputData, abortSignal: workflowAbortSignal, suspend, resumeData }) => { const { taskId } = inputData; const storage = await manager.getStorage(); const task = await storage.getTask(taskId); if (!task || task.status === "cancelled") { manager.deregisterTaskContext(taskId); return { taskId, outcome: "cancelled" }; } const ctx = manager.taskContexts.get(taskId); const executor = ctx?.executor ?? manager.getStaticExecutor(task.toolName); if (!executor) { const errorInfo = { message: `No executor registered for tool "${task.toolName}". Register the tool on Mastra (so workers can resolve it cross-process) or run the task in the same process as the producer.` }; await storage.updateTask(taskId, { status: "failed", error: errorInfo, completedAt: /* @__PURE__ */ new Date() }); const failedTask = await storage.getTask(taskId); if (failedTask) { await manager.runLocalCompletionHooks(failedTask, "failed", { error: errorInfo }); await manager.publishLifecycleEvent("task.failed", failedTask); } manager.deregisterTaskContext(taskId); throw new Error(errorInfo.message); } const progressThrottleMs = manager.config.progressThrottleMs; const shouldThrottleProgress = typeof progressThrottleMs === "number" && Number.isFinite(progressThrottleMs) && progressThrottleMs > 0; let lastProgressEmitMs; const onProgress = async (chunk) => { if (shouldThrottleProgress) { const now = Date.now(); if (lastProgressEmitMs !== void 0 && now - lastProgressEmitMs < progressThrottleMs) return; lastProgressEmitMs = now; } await manager.publishLifecycleEvent("task.output", { ...task, chunk }); }; const abortController = new AbortController(); manager.activeAbortControllers.set(taskId, abortController); const onWorkflowAbort = () => abortController.abort(new Error("Task cancelled")); if (workflowAbortSignal.aborted) { abortController.abort(new Error("Task cancelled")); } else { workflowAbortSignal.addEventListener("abort", onWorkflowAbort, { once: true }); } const timeoutHandle = setTimeout(() => { abortController.abort(new Error(`Task timed out after ${task.timeoutMs}ms`)); }, task.timeoutMs); let pendingSuspend; const wrappedSuspend = async (data, suspendOptions) => { await storage.updateTask(taskId, { status: "suspended", suspendPayload: data, suspendedAt: /* @__PURE__ */ new Date() }); const suspendedTask = await storage.getTask(taskId); if (suspendedTask) { await manager.runLocalSuspendHooks(suspendedTask); await manager.publishLifecycleEvent("task.suspended", suspendedTask); } pendingSuspend = { data, suspendOptions }; }; try { const result = await executor.execute(task.args, { abortSignal: abortController.signal, onProgress, suspend: wrappedSuspend, // On resume the runtime populates `resumeData`; undefined on // the initial run. resumeData }); if (pendingSuspend) { return suspend(pendingSuspend.data, pendingSuspend.suspendOptions); } return { taskId, outcome: "success", result }; } catch (error) { const currentTask = await storage.getTask(taskId); if (!currentTask || currentTask.status === "cancelled") { manager.deregisterTaskContext(taskId); return { taskId, outcome: "cancelled" }; } if (abortController.signal.aborted || error?.name === "AbortError" || error?.message === "Task cancelled" || error?.message?.startsWith("Task timed out after ")) { return { taskId, outcome: "timed_out" }; } return { taskId, outcome: "retry", error: { message: error?.message ?? "Unknown error", stack: error?.stack } }; } finally { clearTimeout(timeoutHandle); workflowAbortSignal.removeEventListener("abort", onWorkflowAbort); manager.activeAbortControllers.delete(taskId); } } }); const classifyOutcomeStep = createStep({ id: "classify-outcome", inputSchema: attemptOutputSchema, outputSchema: bodyOutputSchema, execute: async ({ inputData }) => { const { taskId, outcome, result, error } = inputData; const storage = await manager.getStorage(); const task = await storage.getTask(taskId); if (!task) return { taskId, done: true }; if (outcome === "cancelled") { manager.deregisterTaskContext(taskId); return { taskId, done: true }; } if (outcome === "timed_out") { const status = task.status; if (status !== "timed_out" && status !== "cancelled") { await storage.updateTask(taskId, { status: "timed_out", error: { message: `Task timed out after ${task.timeoutMs}ms` }, completedAt: /* @__PURE__ */ new Date() }); const timedOutTask = await storage.getTask(taskId); if (timedOutTask) await manager.publishLifecycleEvent("task.failed", timedOutTask); } return { taskId, done: true }; } if (outcome === "success") { if (task.status === "cancelled") { manager.deregisterTaskContext(taskId); return { taskId, done: true }; } await storage.updateTask(taskId, { status: "completed", result, completedAt: /* @__PURE__ */ new Date() }); const completedTask = await storage.getTask(taskId); if (completedTask) { await manager.runLocalCompletionHooks(completedTask, "completed", { result }); await manager.publishLifecycleEvent("task.completed", completedTask); } return { taskId, done: true, result }; } if (task.retryCount < task.maxRetries) { await storage.updateTask(taskId, { retryCount: task.retryCount + 1, error: void 0, startedAt: /* @__PURE__ */ new Date() }); return { taskId, done: false }; } const errorInfo = error ?? { message: "Unknown error" }; await storage.updateTask(taskId, { status: "failed", error: errorInfo, completedAt: /* @__PURE__ */ new Date() }); const failedTask = await storage.getTask(taskId); if (failedTask) { await manager.runLocalCompletionHooks(failedTask, "failed", { error: errorInfo }); await manager.publishLifecycleEvent("task.failed", failedTask); } const thrown = new Error(errorInfo.message); if (errorInfo.stack) thrown.stack = errorInfo.stack; throw thrown; } }); const attemptBodyWorkflow = createWorkflow({ id: `${BACKGROUND_TASK_WORKFLOW_ID}__attempt`, inputSchema: bodyIOSchema, outputSchema: bodyOutputSchema, steps: [runAttemptStep, classifyOutcomeStep], options: { // `dountil` feeds the prior iteration's output back in as input. The // body's actual entry point only needs `taskId`, but the loop's // feedback shape includes `done`/`result`/etc. Skip validation rather // than widen every step's input schema. validateInputs: false, shouldPersistSnapshot: ({ workflowStatus }) => WORKFLOW_STATUS_TO_PERSIST.includes(workflowStatus), // Internal scheduler plumbing — hide workflow spans from exported // traces. The task body itself runs as user code and keeps its own // spans. tracingPolicy: { internal: 1 /* WORKFLOW */ } } }).then(runAttemptStep).then(classifyOutcomeStep).commit(); return createWorkflow({ id: BACKGROUND_TASK_WORKFLOW_ID, inputSchema, outputSchema: bodyOutputSchema, steps: [attemptBodyWorkflow], options: { shouldPersistSnapshot: ({ workflowStatus }) => WORKFLOW_STATUS_TO_PERSIST.includes(workflowStatus), // Internal scheduler plumbing — see the inner workflow comment. tracingPolicy: { internal: 1 /* WORKFLOW */ } } }).dountil(attemptBodyWorkflow, async ({ inputData }) => inputData?.done === true).commit(); } export { buildBackgroundTaskWorkflow }; //# sourceMappingURL=workflow-EJC4LNUU.js.map //# sourceMappingURL=workflow-EJC4LNUU.js.map