UNPKG

@mastra/core

Version:
288 lines (287 loc) 9.95 kB
require("./tracing-BUrUJwCM.cjs"); const require_background_tasks = require("./background-tasks-lifNqs9M.cjs"); const require_agent = require("./agent-DCD4MApC.cjs"); let zod = require("zod"); //#region src/background-tasks/workflow.ts const inputSchema = zod.z.object({ taskId: zod.z.string() }); const attemptOutcomeSchema = zod.z.enum([ "success", "retry", "failed", "cancelled", "timed_out" ]); const attemptOutputSchema = zod.z.object({ taskId: zod.z.string(), outcome: attemptOutcomeSchema, result: zod.z.unknown().optional(), error: zod.z.any().optional() }); const bodyIOSchema = zod.z.object({ taskId: zod.z.string(), done: zod.z.boolean().optional(), result: zod.z.unknown().optional() }); const bodyOutputSchema = zod.z.object({ taskId: zod.z.string(), done: zod.z.boolean(), result: zod.z.unknown().optional() }); const WORKFLOW_STATUS_TO_PERSIST = [ "suspended", "pending", "paused", "waiting" ]; /** * Builds the per-task workflow that owns executor + retries. * * Uses the standard (default) execution engine so the workflow runs entirely * in-process on whatever host calls `run.start()`. This is critical for * distributed deployments where the background-task worker must * execute tools locally — routing through the evented pipeline would send * step execution to the orchestration worker / API, which don't have the * internal workflow or task contexts registered. * * Shape: outer workflow runs an inner `[run-attempt, classify-outcome]` * workflow inside a `dountil` loop. `run-attempt` invokes the executor and * categorises the outcome; `classify-outcome` persists final state, advances * retry bookkeeping, and decides whether the loop is done. The dountil * predicate exits on `done === true`. * * Step bodies close over `manager` directly — the bg-tasks layer is the only * consumer of the `@internal` private fields. */ function buildBackgroundTaskWorkflow(manager) { const runAttemptStep = require_agent.createStep$1({ 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 executor = manager.taskContexts.get(taskId)?.executor ?? (task.agentId ? manager.getStaticExecutor(`${task.agentId}:${task.toolName}`) : void 0) ?? 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(/* @__PURE__ */ new Error("Task cancelled")); if (workflowAbortSignal.aborted) abortController.abort(/* @__PURE__ */ new Error("Task cancelled")); else workflowAbortSignal.addEventListener("abort", onWorkflowAbort, { once: true }); const timeoutHandle = setTimeout(() => { abortController.abort(/* @__PURE__ */ 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, 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" }; if (error?.name === "FGADeniedError") return { taskId, outcome: "failed", error: { name: error.name, message: error?.message ?? "Authorization denied", stack: error?.stack } }; 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 = require_agent.createStep$1({ 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 (outcome === "retry" && 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.name) thrown.name = errorInfo.name; if (errorInfo.stack) thrown.stack = errorInfo.stack; throw thrown; } }); const attemptBodyWorkflow = require_agent.createWorkflow({ id: `${require_background_tasks.BACKGROUND_TASK_WORKFLOW_ID}__attempt`, inputSchema: bodyIOSchema, outputSchema: bodyOutputSchema, steps: [runAttemptStep, classifyOutcomeStep], options: { validateInputs: false, shouldPersistSnapshot: ({ workflowStatus }) => WORKFLOW_STATUS_TO_PERSIST.includes(workflowStatus), tracingPolicy: { internal: 1 } } }).then(runAttemptStep).then(classifyOutcomeStep).commit(); return require_agent.createWorkflow({ id: require_background_tasks.BACKGROUND_TASK_WORKFLOW_ID, inputSchema, outputSchema: bodyOutputSchema, steps: [attemptBodyWorkflow], options: { shouldPersistSnapshot: ({ workflowStatus }) => WORKFLOW_STATUS_TO_PERSIST.includes(workflowStatus), tracingPolicy: { internal: 1 } } }).dountil(attemptBodyWorkflow, async ({ inputData }) => inputData?.done === true).commit(); } //#endregion exports.buildBackgroundTaskWorkflow = buildBackgroundTaskWorkflow; //# sourceMappingURL=workflow-BHmP62a4.cjs.map