@mastra/core
Version:
Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.
1,647 lines (1,640 loc) • 172 kB
JavaScript
import { __registerEventedCreateWorkflow, createTimeTravelExecutionParams, createRestartExecutionParams, validateStepResumeData, ExecutionEngine, cleanStepResult, Workflow, validateStepInput, createDeprecationProxy, validateStepSuspendData, getStepResult, runCountDeprecationMessage, TripWire, hydrateSerializedStepErrors, Run, WorkflowRunOutput, isAgentCompatible, isSupportedLanguageModel, ProcessorState, ProcessorStepOutputSchema, ProcessorStepSchema, Agent, summarizeProcessorResultForSpan, summarizeProcessorModelForSpan, summarizeProcessorToolsForSpan, summarizeToolChoiceForSpan, summarizeActiveToolsForSpan, ProcessorRunner, forwardAgentStreamChunk } from './chunk-AM3IOVFX.js';
import { STREAM_FORMAT_SYMBOL, PUBSUB_SYMBOL } from './chunk-2QXNHEDL.js';
import { createPendingMarker } from './chunk-XW7PRUR5.js';
import { ToolStream } from './chunk-DD2VNRQM.js';
import { Tool } from './chunk-6FFXBNBE.js';
import { toStandardSchema } from './chunk-6SRTDZ7S.js';
import { createObservabilityContext, resolveObservabilityContext } from './chunk-4ZCIE3Q5.js';
import { executeWithContext } from './chunk-MJEXAXIO.js';
import { EntityType } from './chunk-QAXRURAT.js';
import { MastraBase } from './chunk-WENZPAHS.js';
import { RegisteredLogger } from './chunk-DBBWTK24.js';
import { MessageList, messagesAreEqual } from './chunk-NRWJGKOK.js';
import { getErrorFromUnknown, MastraError } from './chunk-FJEVLHJT.js';
import { RequestContext } from './chunk-BBVL3KAA.js';
import { Cron } from 'croner';
import { randomUUID } from 'crypto';
import { ReadableStream } from 'stream/web';
import { z } from 'zod/v4';
import EventEmitter from 'events';
function validateCron(cron, timezone) {
const job = new Cron(cron, { timezone });
job.nextRun();
}
function computeNextFireAt(cron, options) {
const job = new Cron(cron, { timezone: options?.timezone });
const reference = options?.after !== void 0 ? new Date(options.after) : /* @__PURE__ */ new Date();
const next = job.nextRun(reference);
if (!next) {
throw new Error(`Cron expression "${cron}" has no future occurrence after ${reference.toISOString()}`);
}
return next.getTime();
}
// src/workflows/evented/helpers.ts
function isTripwireChunk(chunk) {
return chunk !== null && typeof chunk === "object" && "type" in chunk && chunk.type === "tripwire" && "payload" in chunk;
}
function createTripWireFromChunk(chunk) {
const { payload } = chunk;
return new TripWire(
payload.reason || "Agent tripwire triggered",
{
retry: payload.retry,
metadata: payload.metadata
},
payload.processorId
);
}
function getTextDeltaFromChunk(chunk, isV2Model) {
if (chunk.type !== "text-delta") {
return void 0;
}
return isV2Model ? chunk.payload?.text : chunk.textDelta;
}
function resolveCurrentState(params) {
const { stepResult, stepResults, state } = params;
return stepResult?.__state ?? stepResults?.__state ?? state ?? {};
}
// src/events/processor.ts
var EventProcessor = class {
mastra;
__registerMastra(mastra) {
this.mastra = mastra;
}
constructor({ mastra }) {
this.mastra = mastra;
}
};
var StepExecutor = class extends MastraBase {
mastra;
constructor({ mastra }) {
super({ name: "StepExecutor", component: RegisteredLogger.WORKFLOW });
this.mastra = mastra;
}
__registerMastra(mastra) {
this.mastra = mastra;
const logger = mastra?.getLogger();
if (logger) {
this.__setLogger(logger);
}
}
/**
* Creates an output writer function that publishes chunks to the workflow event stream.
* @param runId - The workflow run ID
* @returns An async function that writes chunks to the pubsub
*/
createOutputWriter(runId) {
return async (chunk) => {
try {
if (this.mastra?.pubsub) {
await this.mastra.pubsub.publish(`workflow.events.v2.${runId}`, {
type: "watch",
runId,
data: chunk
});
}
} catch (err) {
this.logger.debug("Failed to publish workflow watch event", { runId, error: err });
}
};
}
async execute(params) {
const { step, stepResults, runId, requestContext, retryCount = 0, perStep } = params;
const abortController = params.abortController ?? new AbortController();
let suspended;
let bailed;
const startedAt = Date.now();
const { inputData, validationError } = await validateStepInput({
prevOutput: typeof params.foreachIdx === "number" ? params.input?.[params.foreachIdx] : params.input,
step,
validateInputs: params.validateInputs ?? true
});
let stepInfo = {
...stepResults[step.id],
startedAt,
payload: (typeof params.foreachIdx === "number" ? params.input : inputData) ?? {}
};
if (params.resumeData) {
stepInfo.resumePayload = params.resumeData;
stepInfo.resumedAt = Date.now();
if (stepInfo.suspendPayload && "__workflow_meta" in stepInfo.suspendPayload) {
const { __workflow_meta, ...userSuspendPayload } = stepInfo.suspendPayload;
stepInfo.suspendPayload = userSuspendPayload;
}
}
let suspendDataToUse = params.stepResults[step.id]?.status === "suspended" ? params.stepResults[step.id]?.suspendPayload : void 0;
if (suspendDataToUse && "__workflow_meta" in suspendDataToUse) {
const { __workflow_meta, ...userSuspendData } = suspendDataToUse;
suspendDataToUse = userSuspendData;
}
let stateUpdate;
try {
if (validationError) {
throw validationError;
}
const callId = randomUUID();
const outputWriter = this.createOutputWriter(runId);
const stepOutput = await executeWithContext({
span: params.tracingContext?.currentSpan,
fn: () => step.execute(
createDeprecationProxy(
{
workflowId: params.workflowId,
runId,
mastra: this.mastra,
requestContext,
inputData,
state: params.state,
setState: async (newState) => {
stateUpdate = { ...stateUpdate ?? params.state, ...newState };
},
retryCount,
resumeData: params.resumeData,
suspendData: suspendDataToUse,
getInitData: () => stepResults?.input,
getStepResult: getStepResult.bind(this, stepResults),
suspend: async (suspendPayload, suspendOptions) => {
const { suspendData, validationError: validationError2 } = await validateStepSuspendData({
suspendData: suspendPayload,
step,
validateInputs: params.validateInputs ?? true
});
if (validationError2) {
throw validationError2;
}
const resumeLabels = {};
if (suspendOptions?.resumeLabel) {
const labels = Array.isArray(suspendOptions.resumeLabel) ? suspendOptions.resumeLabel : [suspendOptions.resumeLabel];
for (const label of labels) {
resumeLabels[label] = {
stepId: step.id,
foreachIndex: params.foreachIdx
};
}
}
suspended = {
payload: {
...suspendData,
__workflow_meta: {
runId,
path: [step.id],
foreachIndex: params.foreachIdx,
resumeLabels: Object.keys(resumeLabels).length > 0 ? resumeLabels : void 0
}
}
};
},
bail: (result) => {
bailed = { payload: result };
},
writer: new ToolStream(
{
prefix: "workflow-step",
callId,
name: step.id,
runId
},
outputWriter
),
abort: () => {
abortController?.abort();
},
[PUBSUB_SYMBOL]: this.mastra.pubsub,
[STREAM_FORMAT_SYMBOL]: params.format,
engine: {},
abortSignal: abortController?.signal,
...createObservabilityContext(params.tracingContext)
},
{
paramName: "runCount",
deprecationMessage: runCountDeprecationMessage,
logger: this.logger
}
)
)
});
const isNestedWorkflowStep = step.component === "WORKFLOW";
const nestedWflowStepPaused = isNestedWorkflowStep && perStep;
const endedAt = Date.now();
const finalState = stateUpdate ?? params.state;
let finalResult;
if (suspended) {
finalResult = {
...stepInfo,
status: "suspended",
suspendedAt: endedAt,
...stepOutput ? { suspendOutput: stepOutput } : {},
__state: finalState
};
if (suspended.payload) {
finalResult.suspendPayload = suspended.payload;
}
} else if (bailed) {
finalResult = {
...stepInfo,
// @ts-expect-error - bailed status not in type
status: "bailed",
endedAt,
output: bailed.payload,
__state: finalState
};
} else if (nestedWflowStepPaused) {
finalResult = {
...stepInfo,
status: "paused",
__state: finalState
};
} else {
finalResult = {
...stepInfo,
status: "success",
endedAt,
output: stepOutput,
__state: finalState
};
}
return finalResult;
} catch (error) {
const endedAt = Date.now();
const errorInstance = getErrorFromUnknown(error, {
serializeStack: false,
fallbackMessage: "Unknown step execution error"
});
const stepId = params.step.id;
const mastraError = new MastraError(
{
id: "WORKFLOW_STEP_INVOKE_FAILED",
domain: "MASTRA_WORKFLOW" /* MASTRA_WORKFLOW */,
category: "USER" /* USER */,
details: { workflowId: params.workflowId, runId: params.runId, stepId }
},
errorInstance
);
this.logger?.trackException(mastraError);
this.logger?.error(`Error executing step ${stepId}: ` + errorInstance?.stack);
return {
...stepInfo,
status: "failed",
endedAt,
error: errorInstance,
// Preserve TripWire data as plain object for proper serialization
// Important: Check `error` not `errorInstance` because getErrorFromUnknown
// converts the error and loses the prototype chain
tripwire: error instanceof TripWire ? {
reason: error.message,
retry: error.options?.retry,
metadata: error.options?.metadata,
processorId: error.processorId
} : void 0
};
}
}
async evaluateConditions(params) {
const { step, stepResults, runId, requestContext, retryCount = 0 } = params;
const abortController = params.abortController ?? new AbortController();
const results = await Promise.all(
step.conditions.map((condition) => {
try {
return this.evaluateCondition({
workflowId: params.workflowId,
condition,
runId,
requestContext,
inputData: params.input,
state: params.state,
retryCount,
resumeData: params.resumeData,
abortController,
stepResults,
iterationCount: 0
});
} catch (e) {
this.mastra?.getLogger()?.error("error evaluating condition", e);
return false;
}
})
);
const idxs = results.reduce((acc, result, idx) => {
if (result) {
acc.push(idx);
}
return acc;
}, []);
return idxs;
}
async evaluateCondition({
workflowId,
condition,
runId,
inputData,
resumeData,
stepResults,
state,
requestContext,
abortController,
retryCount = 0,
iterationCount
}) {
const callId = randomUUID();
const outputWriter = this.createOutputWriter(runId);
return condition(
createDeprecationProxy(
{
workflowId,
runId,
mastra: this.mastra,
requestContext,
inputData,
state,
retryCount,
resumeData,
getInitData: () => stepResults?.input,
getStepResult: getStepResult.bind(this, stepResults),
bail: (_result) => {
throw new Error("Not implemented");
},
writer: new ToolStream(
{
prefix: "workflow-step",
callId,
name: "condition",
runId
},
outputWriter
),
abort: () => {
abortController?.abort();
},
[PUBSUB_SYMBOL]: this.mastra.pubsub,
[STREAM_FORMAT_SYMBOL]: void 0,
// TODO
engine: {},
abortSignal: abortController?.signal,
// TODO
...createObservabilityContext(),
iterationCount
},
{
paramName: "runCount",
deprecationMessage: runCountDeprecationMessage,
logger: this.logger
}
)
);
}
async resolveSleep(params) {
const { step, stepResults, runId, requestContext, retryCount = 0 } = params;
const currentState = params.state ?? stepResults?.__state ?? {};
const abortController = params.abortController ?? new AbortController();
if (step.duration) {
return step.duration;
}
if (!step.fn) {
return 0;
}
try {
const callId = randomUUID();
const outputWriter = this.createOutputWriter(runId);
return await step.fn(
createDeprecationProxy(
{
workflowId: params.workflowId,
runId,
mastra: this.mastra,
requestContext,
inputData: params.input,
state: currentState,
setState: async (newState) => {
Object.assign(currentState, newState);
},
retryCount,
resumeData: params.resumeData,
getInitData: () => stepResults?.input,
getStepResult: getStepResult.bind(this, stepResults),
suspend: async (_suspendPayload) => {
throw new Error("Not implemented");
},
bail: (_result) => {
throw new Error("Not implemented");
},
abort: () => {
abortController?.abort();
},
writer: new ToolStream(
{
prefix: "workflow-step",
callId,
name: step.id,
runId
},
outputWriter
),
[PUBSUB_SYMBOL]: this.mastra.pubsub,
[STREAM_FORMAT_SYMBOL]: void 0,
// TODO
engine: {},
abortSignal: abortController?.signal,
// TODO
...createObservabilityContext()
},
{
paramName: "runCount",
deprecationMessage: runCountDeprecationMessage,
logger: this.logger
}
)
);
} catch (e) {
this.mastra?.getLogger()?.error("error evaluating condition", e);
return 0;
}
}
async resolveSleepUntil(params) {
const { step, stepResults, runId, requestContext, retryCount = 0 } = params;
const currentState = params.state ?? stepResults?.__state ?? {};
const abortController = params.abortController ?? new AbortController();
if (step.date) {
return step.date.getTime() - Date.now();
}
if (!step.fn) {
return 0;
}
try {
const callId = randomUUID();
const outputWriter = this.createOutputWriter(runId);
const result = await step.fn(
createDeprecationProxy(
{
workflowId: params.workflowId,
runId,
mastra: this.mastra,
requestContext,
inputData: params.input,
state: currentState,
setState: async (newState) => {
Object.assign(currentState, newState);
},
retryCount,
resumeData: params.resumeData,
getInitData: () => stepResults?.input,
getStepResult: getStepResult.bind(this, stepResults),
suspend: async (_suspendPayload) => {
throw new Error("Not implemented");
},
bail: (_result) => {
throw new Error("Not implemented");
},
abort: () => {
abortController?.abort();
},
writer: new ToolStream(
{
prefix: "workflow-step",
callId,
name: step.id,
runId
},
outputWriter
),
[PUBSUB_SYMBOL]: this.mastra.pubsub,
[STREAM_FORMAT_SYMBOL]: void 0,
// TODO
engine: {},
abortSignal: abortController?.signal,
// TODO
...createObservabilityContext()
},
{
paramName: "runCount",
deprecationMessage: runCountDeprecationMessage,
logger: this.logger
}
)
);
return result.getTime() - Date.now();
} catch (e) {
this.mastra?.getLogger()?.error("error evaluating condition", e);
return 0;
}
}
};
// src/workflows/evented/workflow-event-processor/loop.ts
async function processWorkflowLoop({
workflowId,
prevResult,
runId,
executionPath,
stepResults,
activeStepsPath,
resumeSteps,
resumeData,
parentWorkflow,
requestContext,
retryCount = 0,
perStep,
state,
outputOptions
}, {
pubsub,
stepExecutor,
step,
stepResult
}) {
const currentState = resolveCurrentState({ stepResult, stepResults, state });
const reqContext = new RequestContext(Object.entries(requestContext ?? {}));
const prevIterationCount = stepResults[step.step?.id]?.metadata?.iterationCount ?? 0;
const iterationCount = prevIterationCount + 1;
const loopCondition = await stepExecutor.evaluateCondition({
workflowId,
condition: step.condition,
runId,
stepResults,
state: currentState,
requestContext: reqContext,
inputData: prevResult?.status === "success" ? prevResult.output : void 0,
resumeData,
abortController: new AbortController(),
retryCount,
iterationCount
});
const loopAgainData = {
parentWorkflow,
workflowId,
runId,
executionPath,
resumeSteps: [],
stepResults,
prevResult: stepResult,
resumeData: void 0,
activeStepsPath,
requestContext,
retryCount,
perStep,
state: currentState,
outputOptions
};
const loopEndData = {
parentWorkflow,
workflowId,
runId,
executionPath,
resumeSteps,
stepResults,
prevResult: stepResult,
resumeData,
activeStepsPath,
requestContext,
perStep,
state: currentState,
outputOptions
};
if (step.loopType === "dountil") {
if (loopCondition) {
await pubsub.publish("workflows", { type: "workflow.step.end", runId, data: loopEndData });
} else {
await pubsub.publish("workflows", { type: "workflow.step.run", runId, data: loopAgainData });
}
} else {
if (loopCondition) {
await pubsub.publish("workflows", { type: "workflow.step.run", runId, data: loopAgainData });
} else {
await pubsub.publish("workflows", { type: "workflow.step.end", runId, data: loopEndData });
}
}
}
async function processWorkflowForEach({
workflowId,
prevResult,
runId,
executionPath,
stepResults,
activeStepsPath,
resumeSteps,
timeTravel,
restart,
resumeData,
parentWorkflow,
requestContext,
perStep,
state,
outputOptions,
forEachIndex
}, {
pubsub,
mastra,
step
}) {
const currentState = resolveCurrentState({ stepResults, state });
const currentResult = stepResults[step.step.id];
const idx = currentResult?.output?.length ?? 0;
const targetLen = prevResult?.output?.length ?? 0;
if (forEachIndex !== void 0 && resumeSteps?.length > 0 && idx > 0) {
const outputArray = currentResult?.output;
const outputLength = Array.isArray(outputArray) ? outputArray.length : 0;
if (!Array.isArray(outputArray) || forEachIndex < 0 || forEachIndex >= outputLength) {
const error = new Error(
`Invalid forEachIndex ${forEachIndex} for forEach resume: expected index in range [0, ${outputLength - 1}] but output array has length ${outputLength}`
);
await pubsub.publish("workflows", {
type: "workflow.fail",
runId,
data: {
parentWorkflow,
workflowId,
runId,
executionPath,
resumeSteps,
stepResults,
prevResult: { status: "failed", error },
activeStepsPath,
requestContext,
state: currentState,
outputOptions
}
});
return;
}
const iterationResult = currentResult?.output?.[forEachIndex];
if (iterationResult?.status === "suspended" || iterationResult === null) {
const isNestedWorkflow2 = step.step.component === "WORKFLOW";
const targetArray2 = prevResult?.output;
const iterationPrevResult2 = isNestedWorkflow2 && prevResult.status === "success" && Array.isArray(targetArray2) ? { status: "success", output: targetArray2[forEachIndex] } : prevResult;
await pubsub.publish("workflows", {
type: "workflow.step.run",
runId,
data: {
parentWorkflow,
workflowId,
runId,
executionPath: [executionPath[0], forEachIndex],
resumeSteps,
timeTravel,
restart,
stepResults,
prevResult: iterationPrevResult2,
resumeData,
activeStepsPath,
requestContext,
perStep,
state: currentState,
outputOptions
}
});
return;
}
const pendingIterations = currentResult.output.filter((r) => r === null || r?.status === "suspended");
if (pendingIterations.length > 0) {
const collectedResumeLabels = {};
for (let i = 0; i < currentResult.output.length; i++) {
const iterResult = currentResult.output[i];
if (iterResult?.status === "suspended" && iterResult.suspendPayload?.__workflow_meta?.resumeLabels) {
Object.assign(collectedResumeLabels, iterResult.suspendPayload.__workflow_meta.resumeLabels);
}
}
const suspendMeta = {
foreachIndex: forEachIndex
};
if (Object.keys(collectedResumeLabels).length > 0) {
suspendMeta.resumeLabels = collectedResumeLabels;
}
await pubsub.publish("workflows", {
type: "workflow.step.end",
runId,
data: {
parentWorkflow,
workflowId,
runId,
executionPath,
resumeSteps,
stepResults: {
...stepResults,
[step.step.id]: {
...currentResult,
status: "suspended",
suspendedAt: Date.now(),
suspendPayload: { __workflow_meta: suspendMeta }
}
},
prevResult: {
status: "suspended",
output: currentResult.output,
suspendPayload: { __workflow_meta: suspendMeta },
payload: currentResult.payload,
startedAt: currentResult.startedAt,
suspendedAt: Date.now()
},
activeStepsPath,
requestContext,
state: currentState,
outputOptions
}
});
return;
}
return;
}
if (resumeData !== void 0 && forEachIndex === void 0 && currentResult?.output?.length > 0) {
const suspendedIndices = [];
for (let i = 0; i < currentResult.output.length; i++) {
const iterResult = currentResult.output[i];
if (iterResult && typeof iterResult === "object" && iterResult.status === "suspended") {
suspendedIndices.push(i);
}
}
if (suspendedIndices.length > 0) {
const concurrency = step.opts.concurrency ?? 1;
const indicesToResume = suspendedIndices.slice(0, concurrency);
const workflowsStore2 = await mastra.getStorage()?.getStore("workflows");
const updatedOutput = [...currentResult.output];
for (const suspIdx of indicesToResume) {
updatedOutput[suspIdx] = createPendingMarker();
}
await workflowsStore2?.updateWorkflowResults({
workflowName: workflowId,
runId,
stepId: step.step.id,
result: {
...currentResult,
output: updatedOutput
},
requestContext
});
const isNestedWorkflow2 = step.step.component === "WORKFLOW";
for (const suspIdx of indicesToResume) {
const targetArray2 = prevResult?.output;
const iterationPrevResult2 = isNestedWorkflow2 && prevResult.status === "success" && Array.isArray(targetArray2) ? { status: "success", output: targetArray2[suspIdx] } : prevResult;
try {
await pubsub.publish("workflows", {
type: "workflow.step.run",
runId,
data: {
parentWorkflow,
workflowId,
runId,
executionPath: [executionPath[0], suspIdx],
resumeSteps,
timeTravel,
restart,
stepResults,
prevResult: iterationPrevResult2,
resumeData,
activeStepsPath,
requestContext,
perStep,
state: currentState,
outputOptions
}
});
} catch {
}
}
return;
}
}
const workflowsStore = await mastra.getStorage()?.getStore("workflows");
if (idx >= targetLen && currentResult?.output?.filter((r) => r !== null)?.length >= targetLen || prevResult?.output?.length === 0) {
let result = currentResult;
if (prevResult?.output?.length === 0) {
result = {
status: "success",
output: [],
startedAt: Date.now(),
endedAt: Date.now(),
payload: prevResult?.output
};
await workflowsStore?.updateWorkflowResults({
workflowName: workflowId,
runId,
stepId: step.step.id,
result,
requestContext
});
stepResults[step.step.id] = result;
}
await pubsub.publish("workflows", {
type: "workflow.step.run",
runId,
data: {
parentWorkflow,
workflowId,
runId,
executionPath: executionPath.slice(0, -1).concat([executionPath[executionPath.length - 1] + 1]),
resumeSteps,
stepResults,
timeTravel,
restart,
prevResult: result,
resumeData: void 0,
// No resumeData when advancing past foreach
activeStepsPath,
requestContext,
perStep,
state: currentState,
outputOptions
}
});
return;
} else if (idx >= targetLen) {
return;
}
if (executionPath.length === 1 && idx === 0) {
const concurrency = Math.min(step.opts.concurrency ?? 1, targetLen);
const dummyResult = Array.from({ length: concurrency }, () => null);
await workflowsStore?.updateWorkflowResults({
workflowName: workflowId,
runId,
stepId: step.step.id,
result: {
status: "success",
output: dummyResult,
startedAt: Date.now(),
payload: prevResult?.output
},
requestContext
});
const isNestedWorkflow2 = step.step.component === "WORKFLOW";
for (let i = 0; i < concurrency; i++) {
const targetArray2 = prevResult?.output;
const iterationPrevResult2 = isNestedWorkflow2 && prevResult.status === "success" && Array.isArray(targetArray2) ? { status: "success", output: targetArray2[i] } : prevResult;
await pubsub.publish("workflows", {
type: "workflow.step.run",
runId,
data: {
parentWorkflow,
workflowId,
runId,
executionPath: [executionPath[0], i],
resumeSteps,
stepResults,
timeTravel,
restart,
prevResult: iterationPrevResult2,
resumeData,
activeStepsPath,
requestContext,
perStep,
state: currentState,
outputOptions
}
});
}
return;
}
currentResult.output.push(null);
await workflowsStore?.updateWorkflowResults({
workflowName: workflowId,
runId,
stepId: step.step.id,
result: {
status: "success",
output: currentResult.output,
startedAt: Date.now(),
payload: prevResult?.output
},
requestContext
});
const isNestedWorkflow = step.step.component === "WORKFLOW";
const targetArray = prevResult?.output;
const iterationPrevResult = isNestedWorkflow && prevResult.status === "success" && Array.isArray(targetArray) ? { status: "success", output: targetArray[idx] } : prevResult;
await pubsub.publish("workflows", {
type: "workflow.step.run",
runId,
data: {
parentWorkflow,
workflowId,
runId,
executionPath: [executionPath[0], idx],
resumeSteps,
timeTravel,
restart,
stepResults,
prevResult: iterationPrevResult,
resumeData,
activeStepsPath,
requestContext,
perStep,
state: currentState,
outputOptions
}
});
}
// src/workflows/evented/workflow-event-processor/parallel.ts
async function processWorkflowParallel({
workflowId,
runId,
executionPath,
stepResults,
activeStepsPath,
resumeSteps,
timeTravel,
restart,
prevResult,
resumeData,
parentWorkflow,
requestContext,
perStep,
state,
outputOptions
}, {
pubsub,
step
}) {
const pathsToRun = {};
const currentState = resolveCurrentState({ stepResults, state });
for (let i = 0; i < step.steps.length; i++) {
const nestedStep = step.steps[i];
if (nestedStep?.type === "step") {
if (restart) {
pathsToRun[nestedStep.step.id] = !!restart.activeStepsPath[nestedStep.step.id];
} else {
pathsToRun[nestedStep.step.id] = true;
}
if (perStep) {
break;
}
}
}
await Promise.all(
step.steps?.filter((step2) => pathsToRun[step2.step.id]).map(async (_step, idx) => {
return pubsub.publish("workflows", {
type: "workflow.step.run",
runId,
data: {
workflowId,
runId,
executionPath: restart ? executionPath.slice(0, -1).concat([idx]) : executionPath.concat([idx]),
resumeSteps,
stepResults,
prevResult,
resumeData,
timeTravel,
restart: restart ? { ...restart, isParallelOrConditionalRestarted: true } : void 0,
parentWorkflow,
activeStepsPath,
requestContext,
perStep,
state: currentState,
outputOptions
}
});
})
);
}
async function processWorkflowConditional({
workflowId,
runId,
executionPath,
stepResults,
activeStepsPath,
resumeSteps,
timeTravel,
restart,
prevResult,
resumeData,
parentWorkflow,
requestContext,
perStep,
state,
outputOptions
}, {
pubsub,
stepExecutor,
step
}) {
const currentState = resolveCurrentState({ stepResults, state });
const reqContext = new RequestContext(Object.entries(requestContext ?? {}));
const idxs = await stepExecutor.evaluateConditions({
workflowId,
step,
runId,
stepResults,
state: currentState,
requestContext: reqContext,
input: prevResult?.status === "success" ? prevResult.output : void 0,
resumeData
});
const truthyIdxs = {};
for (let i = 0; i < idxs.length; i++) {
truthyIdxs[idxs[i]] = true;
}
let onlyStepToRun;
if (perStep) {
const stepsToRun = step.steps.filter((_, idx) => truthyIdxs[idx]);
onlyStepToRun = stepsToRun[0];
}
if (onlyStepToRun) {
const stepIndex = step.steps.findIndex((step2) => step2.step.id === onlyStepToRun.step.id);
activeStepsPath[onlyStepToRun.step.id] = executionPath.concat([stepIndex]);
await pubsub.publish("workflows", {
type: "workflow.step.run",
runId,
data: {
workflowId,
runId,
executionPath: executionPath.concat([stepIndex]),
resumeSteps,
stepResults,
timeTravel,
restart,
prevResult,
resumeData,
parentWorkflow,
activeStepsPath,
requestContext,
perStep,
state: currentState,
outputOptions
}
});
} else {
await Promise.all(
step.steps.map(async (step2, idx) => {
if (truthyIdxs[idx]) {
if (step2?.type === "step") {
activeStepsPath[step2.step.id] = executionPath.concat([idx]);
}
return pubsub.publish("workflows", {
type: "workflow.step.run",
runId,
data: {
workflowId,
runId,
executionPath: executionPath.concat([idx]),
resumeSteps,
stepResults,
timeTravel,
restart: restart ? { ...restart, isParallelOrConditionalRestarted: true } : void 0,
prevResult,
resumeData,
parentWorkflow,
activeStepsPath,
requestContext,
perStep,
state: currentState,
outputOptions
}
});
} else {
return pubsub.publish("workflows", {
type: "workflow.step.end",
runId,
data: {
workflowId,
runId,
executionPath: executionPath.concat([idx]),
resumeSteps,
stepResults,
prevResult: { status: "skipped" },
resumeData,
parentWorkflow,
activeStepsPath,
requestContext,
perStep,
state: currentState,
outputOptions
}
});
}
})
);
}
}
// src/workflows/evented/workflow-event-processor/sleep.ts
async function processWorkflowWaitForEvent(workflowData, {
pubsub,
eventName,
currentState
}) {
const executionPath = currentState?.waitingPaths[eventName];
if (!executionPath) {
return;
}
const currentStep = getStep(workflowData.workflow, executionPath);
const prevResult = {
status: "success",
output: currentState?.context[currentStep?.id ?? "input"]?.payload
};
await pubsub.publish("workflows", {
type: "workflow.step.run",
runId: workflowData.runId,
data: {
workflowId: workflowData.workflowId,
runId: workflowData.runId,
executionPath,
resumeSteps: [],
resumeData: workflowData.resumeData,
parentWorkflow: workflowData.parentWorkflow,
stepResults: currentState?.context,
prevResult,
activeStepsPath: {},
requestContext: currentState?.requestContext,
perStep: workflowData.perStep
}
});
}
async function processWorkflowSleep({
workflowId,
runId,
executionPath,
stepResults,
activeStepsPath,
resumeSteps,
timeTravel,
restart,
prevResult,
resumeData,
parentWorkflow,
requestContext,
perStep
}, {
pubsub,
stepExecutor,
step
}) {
const startedAt = Date.now();
await pubsub.publish(`workflow.events.v2.${runId}`, {
type: "watch",
runId,
data: {
type: "workflow-step-waiting",
payload: {
id: step.id,
status: "waiting",
payload: prevResult.status === "success" ? prevResult.output : void 0,
startedAt
}
}
});
const reqContext = new RequestContext(Object.entries(requestContext ?? {}));
const duration = await stepExecutor.resolveSleep({
workflowId,
step,
runId,
stepResults,
requestContext: reqContext,
input: prevResult?.status === "success" ? prevResult.output : void 0,
resumeData
});
setTimeout(
async () => {
await pubsub.publish(`workflow.events.v2.${runId}`, {
type: "watch",
runId,
data: {
type: "workflow-step-result",
payload: {
id: step.id,
status: "success",
payload: prevResult.status === "success" ? prevResult.output : void 0,
output: prevResult.status === "success" ? prevResult.output : void 0,
startedAt,
endedAt: Date.now()
}
}
});
await pubsub.publish(`workflow.events.v2.${runId}`, {
type: "watch",
runId,
data: {
type: "workflow-step-finish",
payload: {
id: step.id,
metadata: {}
}
}
});
await pubsub.publish("workflows", {
type: "workflow.step.run",
runId,
data: {
workflowId,
runId,
executionPath: executionPath.slice(0, -1).concat([executionPath[executionPath.length - 1] + 1]),
resumeSteps,
timeTravel,
restart,
stepResults,
prevResult,
resumeData,
parentWorkflow,
activeStepsPath,
requestContext,
perStep
}
});
},
duration < 0 ? 0 : duration
);
}
async function processWorkflowSleepUntil({
workflowId,
runId,
executionPath,
stepResults,
activeStepsPath,
resumeSteps,
timeTravel,
restart,
prevResult,
resumeData,
parentWorkflow,
requestContext,
perStep
}, {
pubsub,
stepExecutor,
step
}) {
const startedAt = Date.now();
const reqContext = new RequestContext(Object.entries(requestContext ?? {}));
const duration = await stepExecutor.resolveSleepUntil({
workflowId,
step,
runId,
stepResults,
requestContext: reqContext,
input: prevResult?.status === "success" ? prevResult.output : void 0,
resumeData
});
await pubsub.publish(`workflow.events.v2.${runId}`, {
type: "watch",
runId,
data: {
type: "workflow-step-waiting",
payload: {
id: step.id,
status: "waiting",
payload: prevResult.status === "success" ? prevResult.output : void 0,
startedAt
}
}
});
setTimeout(
async () => {
await pubsub.publish(`workflow.events.v2.${runId}`, {
type: "watch",
runId,
data: {
type: "workflow-step-result",
payload: {
id: step.id,
status: "success",
payload: prevResult.status === "success" ? prevResult.output : void 0,
output: prevResult.status === "success" ? prevResult.output : void 0,
startedAt,
endedAt: Date.now()
}
}
});
await pubsub.publish(`workflow.events.v2.${runId}`, {
type: "watch",
runId,
data: {
type: "workflow-step-finish",
payload: {
id: step.id,
metadata: {}
}
}
});
await pubsub.publish("workflows", {
type: "workflow.step.run",
runId,
data: {
workflowId,
runId,
executionPath: executionPath.slice(0, -1).concat([executionPath[executionPath.length - 1] + 1]),
resumeSteps,
timeTravel,
restart,
stepResults,
prevResult,
resumeData,
parentWorkflow,
activeStepsPath,
requestContext,
perStep
}
});
},
duration < 0 ? 0 : duration
);
}
// src/workflows/evented/workflow-event-processor/index.ts
var WorkflowEventProcessor = class extends EventProcessor {
stepExecutor;
stepExecutionStrategy;
// Map of runId -> AbortController for active workflow runs
abortControllers = /* @__PURE__ */ new Map();
// Map of child runId -> parent runId for tracking nested workflows
parentChildRelationships = /* @__PURE__ */ new Map();
runFormats = /* @__PURE__ */ new Map();
constructor({ mastra, stepExecutionStrategy }) {
super({ mastra });
this.stepExecutor = new StepExecutor({ mastra });
this.stepExecutionStrategy = stepExecutionStrategy;
}
/**
* Get or create an AbortController for a workflow run
*/
getOrCreateAbortController(runId) {
let controller = this.abortControllers.get(runId);
if (!controller) {
controller = new AbortController();
this.abortControllers.set(runId, controller);
}
return controller;
}
/**
* Cancel a workflow run and all its nested child workflows
*/
cancelRunAndChildren(runId) {
const controller = this.abortControllers.get(runId);
if (controller) {
controller.abort();
}
for (const [childRunId, parentRunId] of this.parentChildRelationships.entries()) {
if (parentRunId === runId) {
this.cancelRunAndChildren(childRunId);
}
}
}
/**
* Clean up abort controller and relationships when a workflow completes.
* Also cleans up any orphaned child entries that reference this run as parent.
*/
cleanupRun(runId) {
this.abortControllers.delete(runId);
this.parentChildRelationships.delete(runId);
this.runFormats.delete(runId);
for (const [childRunId, parentRunId] of this.parentChildRelationships.entries()) {
if (parentRunId === runId) {
this.parentChildRelationships.delete(childRunId);
}
}
}
__registerMastra(mastra) {
super.__registerMastra(mastra);
this.stepExecutor.__registerMastra(mastra);
}
/**
* Resolves a workflow by id without throwing. Searches first by the
* workflow's `.id` (the value that ends up on event payloads) and then
* falls back to the registration key in `Mastra.workflows`. Returns
* `undefined` if neither lookup succeeds — callers decide how to handle
* the missing case (e.g. terminal failure vs. cleanup pass-through) so
* we don't throw inside `#dispatch` and trigger infinite event retries.
*/
#tryResolveWorkflow(workflowId) {
try {
return this.mastra.getWorkflowById(workflowId);
} catch {
return void 0;
}
}
async errorWorkflow({
parentWorkflow,
workflowId,
runId,
resumeSteps,
stepResults,
resumeData,
requestContext
}, e) {
await this.mastra.pubsub.publish("workflows", {
type: "workflow.fail",
runId,
data: {
workflowId,
runId,
executionPath: [],
resumeSteps,
stepResults,
prevResult: { status: "failed", error: getErrorFromUnknown(e).toJSON() },
requestContext,
resumeData,
activeStepsPath: {},
parentWorkflow
}
});
}
async processWorkflowCancel({ workflowId, runId, prevResult, ...args }) {
this.cancelRunAndChildren(runId);
const workflowsStore = await this.mastra.getStorage()?.getStore("workflows");
const currentState = await workflowsStore?.loadWorkflowSnapshot({
workflowName: workflowId,
runId
});
if (!currentState) {
this.mastra.getLogger()?.warn("Canceling workflow without loaded state", { workflowId, runId });
}
await this.endWorkflow(
{
workflowId,
runId,
prevResult,
...args
},
"canceled"
);
}
async processWorkflowStart({
workflow,
parentWorkflow,
workflowId,
runId,
resumeSteps,
prevResult,
resumeData,
timeTravel,
restart,
executionPath,
stepResults,
requestContext,
perStep,
format,
state,
outputOptions,
forEachIndex
}) {
const initialState = arguments[0].initialState ?? state ?? {};
const resolvedFormat = format ?? this.runFormats.get(runId);
this.runFormats.set(runId, resolvedFormat);
this.getOrCreateAbortController(runId);
if (parentWorkflow?.runId) {
this.parentChildRelationships.set(runId, parentWorkflow.runId);
}
const workflowsStore = await this.mastra.getStorage()?.getStore("workflows");
const existingRun = await workflowsStore?.getWorkflowRunById({ runId, workflowName: workflow.id });
const resourceId = existingRun?.resourceId;
const shouldPersist = workflow?.options?.shouldPersistSnapshot?.({
stepResults: stepResults ?? {},
workflowStatus: "running"
}) ?? true;
if (shouldPersist) {
await workflowsStore?.persistWorkflowSnapshot({
workflowName: workflow.id,
runId,
resourceId,
snapshot: {
activePaths: [],
suspendedPaths: {},
resumeLabels: {},
waitingPaths: {},
activeStepsPath: {},
serializedStepGraph: workflow.serializedStepGraph,
timestamp: Date.now(),
runId,
context: {
...stepResults ?? {
input: prevResult?.status === "success" ? prevResult.output : void 0
},
__state: initialState
},
status: "running",
value: initialState
}
});
if (parentWorkflow) {
const parentSnap = await workflowsStore?.loadWorkflowSnapshot({
workflowName: parentWorkflow.workflowId,
runId: parentWorkflow.runId
});
const existing = parentSnap?.context?.[workflowId];
await workflowsStore?.updateWorkflowResults({
workflowName: parentWorkflow.workflowId,
runId: parentWorkflow.runId,
stepId: workflowId,
result: {
startedAt: existing?.startedAt ?? Date.now(),
status: "running",
payload: existing?.payload ?? parentWorkflow.input?.output ?? {},
...existing ?? {},
// preserve anything else (suspendPayload, etc.)
metadata: { ...existing?.metadata ?? {}, nestedRunId: runId }
},
requestContext
});
}
}
await this.mastra.pubsub.publish("workflows", {
type: "workflow.step.run",
runId,
data: {
parentWorkflow,
workflowId,
runId,
executionPath: executionPath ?? [0],
resumeSteps,
stepResults: {
...stepResults ?? {
input: prevResult?.status === "success" ? prevResult.output : void 0
},
__state: initialState
},
prevResult,
timeTravel,
restart,
requestContext,
resumeData,
activeStepsPath: {},
perStep,
state: initialState,
outputOptions,
forEachIndex
}
});
}
async endWorkflow(args, status = "success") {
const { workflowId, runId, prevResult, perStep, workflow, stepResults, activeStepsPath, executionPath } = args;
const workflowsStore = await this.mastra.getStorage()?.getStore("workflows");
const finalStatus = perStep && status === "success" ? "paused" : status;
const shouldPersist = workflow?.options?.shouldPersistSnapshot?.({
stepResults: stepResults ?? {},
workflowStatus: finalStatus
}) ?? true;
if (shouldPersist) {
await workflowsStore?.updateWorkflowState({
workflowName: workflowId,
runId,
opts: {
status: finalStatus,
result: prevResult,
activePaths: executionPath,
activeStepsPath
}
});
}
if (perStep) {
await this.mastra.pubsub.publish(`workflow.events.v2.${runId}`, {
type: "watch",
runId,
data: {
type: "workflow-paused",
payload: {}
}
});
}
await this.mastra.pubsub.publish(`workflow.events.v2.${runId}`, {
type: "watch",
runId,
data: {
type: "workflow-finish",
payload: {
runId
}
}
});
await this.mastra.pubsub.publish("workflows", {
type: "workflow.end",
runId,
data: { ...args, workflow: void 0 }
});
}
async processWorkflowEnd(args) {
const {
resumeSteps,
prevResult,
resumeData,
parentWorkflow,
activeStepsPath,
requestContext,
runId,
timeTravel,
perStep,
stepResults,
state} = args;
const finalState = resolveCurrentState({ stepResults, state });
this.cleanupRun(runId);
if (parentWorkflow) {
const step = parentWorkflow.stepGraph[parentWorkflow.executionPath[0]];
if (step?.type === "loop") {
await processWorkflowLoop(
{
workflowId: parentWorkflow.workflowId,
prevResult,
runId: parentWorkflow.runId,
executionPath: parentWorkflow.executionPath,
stepResults: parentWorkflow.stepResults,
activeStepsPath: parentWorkflow.activeStepsPath,
resumeSteps: parentWorkflow.resumeSteps,
resumeData: parentWorkflow.resumeData,
parentWorkflow: parentWorkflow.parentWorkflow,
requestContext,
retryCount: 0
},
{
pubsub: this.mastra.pubsub,
stepExecutor: this.stepExecutor,
step,
stepResult: prevResult
}
);
} else {
await this.mastra.pubsub.publish("workflows", {
type: "workflow.step.end",
runId: parentWorkflow.runId,
// Use parent's runId for event routing
data: {
workflowId: parentWorkflow.workflowId,
runId: parentWorkflow.runI