@mastra/core
Version:
The core foundation of the Mastra framework, providing essential components and interfaces for building AI-powered applications.
1,741 lines (1,738 loc) • 82.1 kB
JavaScript
import { EMITTER_SYMBOL } from './chunk-GK5V7YTQ.js';
import { Agent, runScorer } from './chunk-ZK54NTZC.js';
import { ToolStream } from './chunk-YW7UILPE.js';
import { Tool } from './chunk-QU6RMCGM.js';
import { MastraError } from './chunk-E2ZYWKI7.js';
import { MastraBase } from './chunk-FQ4W6KBT.js';
import { RegisteredLogger } from './chunk-R3SQUADS.js';
import { RuntimeContext } from './chunk-ZET2LV2K.js';
import { randomUUID } from 'crypto';
import { context, trace } from '@opentelemetry/api';
import EventEmitter from 'events';
import { TransformStream, ReadableStream } from 'stream/web';
import { z } from 'zod';
// src/workflows/execution-engine.ts
var ExecutionEngine = class extends MastraBase {
mastra;
constructor({ mastra }) {
super({ name: "ExecutionEngine", component: RegisteredLogger.WORKFLOW });
this.mastra = mastra;
}
__registerMastra(mastra) {
this.mastra = mastra;
}
};
var DefaultExecutionEngine = class extends ExecutionEngine {
/**
* The runCounts map is used to keep track of the run count for each step.
* The step id is used as the key and the run count is the value.
*/
runCounts = /* @__PURE__ */ new Map();
/**
* Get or generate the run count for a step.
* If the step id is not in the map, it will be added and the run count will be 0.
* If the step id is in the map, it will return the run count.
*
* @param stepId - The id of the step.
* @returns The run count for the step.
*/
getOrGenerateRunCount(stepId) {
if (this.runCounts.has(stepId)) {
const currentRunCount = this.runCounts.get(stepId);
const nextRunCount = currentRunCount + 1;
this.runCounts.set(stepId, nextRunCount);
return nextRunCount;
}
const runCount = 0;
this.runCounts.set(stepId, runCount);
return runCount;
}
async fmtReturnValue(executionSpan, emitter, stepResults, lastOutput, error) {
const base = {
status: lastOutput.status,
steps: stepResults
};
if (lastOutput.status === "success") {
await emitter.emit("watch", {
type: "watch",
payload: {
workflowState: {
status: lastOutput.status,
steps: stepResults,
result: lastOutput.output
}
},
eventTimestamp: Date.now()
});
base.result = lastOutput.output;
} else if (lastOutput.status === "failed") {
await emitter.emit("watch", {
type: "watch",
payload: {
workflowState: {
status: lastOutput.status,
steps: stepResults,
result: null,
error: lastOutput.error
}
},
eventTimestamp: Date.now()
});
base.error = error instanceof Error ? error?.stack ?? error : lastOutput.error ?? (typeof error === "string" ? error : new Error("Unknown error: " + error)?.stack ?? new Error("Unknown error: " + error));
} else if (lastOutput.status === "suspended") {
const suspendedStepIds = Object.entries(stepResults).flatMap(([stepId, stepResult]) => {
if (stepResult?.status === "suspended") {
const nestedPath = stepResult?.suspendPayload?.__workflow_meta?.path;
return nestedPath ? [[stepId, ...nestedPath]] : [[stepId]];
}
return [];
});
base.suspended = suspendedStepIds;
await emitter.emit("watch", {
type: "watch",
payload: {
workflowState: {
status: lastOutput.status,
steps: stepResults,
result: null,
error: null
}
},
eventTimestamp: Date.now()
});
}
executionSpan?.end();
return base;
}
/**
* Executes a workflow run with the provided execution graph and input
* @param graph The execution graph to execute
* @param input The input data for the workflow
* @returns A promise that resolves to the workflow output
*/
async execute(params) {
const { workflowId, runId, graph, input, resume, retryConfig } = params;
const { attempts = 0, delay = 0 } = retryConfig ?? {};
const steps = graph.steps;
this.runCounts.clear();
if (steps.length === 0) {
throw new MastraError({
id: "WORKFLOW_EXECUTE_EMPTY_GRAPH",
text: "Workflow must have at least one step",
domain: "MASTRA_WORKFLOW" /* MASTRA_WORKFLOW */,
category: "USER" /* USER */
});
}
const executionSpan = this.mastra?.getTelemetry()?.tracer.startSpan(`workflow.${workflowId}.execute`, {
attributes: { componentName: workflowId, runId }
});
let startIdx = 0;
if (resume?.resumePath) {
startIdx = resume.resumePath[0];
resume.resumePath.shift();
}
const stepResults = resume?.stepResults || { input };
let lastOutput;
for (let i = startIdx; i < steps.length; i++) {
const entry = steps[i];
try {
lastOutput = await this.executeEntry({
workflowId,
runId,
entry,
serializedStepGraph: params.serializedStepGraph,
prevStep: steps[i - 1],
stepResults,
resume,
executionContext: {
workflowId,
runId,
executionPath: [i],
suspendedPaths: {},
retryConfig: { attempts, delay },
executionSpan
},
abortController: params.abortController,
emitter: params.emitter,
runtimeContext: params.runtimeContext,
writableStream: params.writableStream
});
if (lastOutput.result.status !== "success") {
if (lastOutput.result.status === "bailed") {
lastOutput.result.status = "success";
}
const result2 = await this.fmtReturnValue(
executionSpan,
params.emitter,
stepResults,
lastOutput.result
);
await this.persistStepUpdate({
workflowId,
runId,
stepResults: lastOutput.stepResults,
serializedStepGraph: params.serializedStepGraph,
executionContext: lastOutput.executionContext,
workflowStatus: result2.status,
result: result2.result,
error: result2.error,
runtimeContext: params.runtimeContext
});
return result2;
}
} catch (e) {
const error = e instanceof MastraError ? e : new MastraError(
{
id: "WORKFLOW_ENGINE_STEP_EXECUTION_FAILED",
domain: "MASTRA_WORKFLOW" /* MASTRA_WORKFLOW */,
category: "USER" /* USER */,
details: { workflowId, runId }
},
e
);
this.logger?.trackException(error);
this.logger?.error(`Error executing step: ${error?.stack}`);
const result2 = await this.fmtReturnValue(
executionSpan,
params.emitter,
stepResults,
lastOutput.result,
e
);
await this.persistStepUpdate({
workflowId,
runId,
stepResults: lastOutput.stepResults,
serializedStepGraph: params.serializedStepGraph,
executionContext: lastOutput.executionContext,
workflowStatus: result2.status,
result: result2.result,
error: result2.error,
runtimeContext: params.runtimeContext
});
return result2;
}
}
const result = await this.fmtReturnValue(executionSpan, params.emitter, stepResults, lastOutput.result);
await this.persistStepUpdate({
workflowId,
runId,
stepResults: lastOutput.stepResults,
serializedStepGraph: params.serializedStepGraph,
executionContext: lastOutput.executionContext,
workflowStatus: result.status,
result: result.result,
error: result.error,
runtimeContext: params.runtimeContext
});
return result;
}
getStepOutput(stepResults, step) {
if (!step) {
return stepResults.input;
} else if (step.type === "step" || step.type === "waitForEvent") {
return stepResults[step.step.id]?.output;
} else if (step.type === "sleep" || step.type === "sleepUntil") {
return stepResults[step.id]?.output;
} else if (step.type === "parallel" || step.type === "conditional") {
return step.steps.reduce(
(acc, entry) => {
if (entry.type === "step" || entry.type === "waitForEvent") {
acc[entry.step.id] = stepResults[entry.step.id]?.output;
} else if (entry.type === "parallel" || entry.type === "conditional") {
const parallelResult = this.getStepOutput(stepResults, entry)?.output;
acc = { ...acc, ...parallelResult };
} else if (entry.type === "loop") {
acc[entry.step.id] = stepResults[entry.step.id]?.output;
} else if (entry.type === "foreach") {
acc[entry.step.id] = stepResults[entry.step.id]?.output;
} else if (entry.type === "sleep" || entry.type === "sleepUntil") {
acc[entry.id] = stepResults[entry.id]?.output;
}
return acc;
},
{}
);
} else if (step.type === "loop") {
return stepResults[step.step.id]?.output;
} else if (step.type === "foreach") {
return stepResults[step.step.id]?.output;
}
}
async executeSleep({
workflowId,
runId,
entry,
prevOutput,
stepResults,
emitter,
abortController,
runtimeContext,
writableStream
}) {
let { duration, fn } = entry;
if (fn) {
const stepCallId = randomUUID();
duration = await fn({
runId,
workflowId,
mastra: this.mastra,
runtimeContext,
inputData: prevOutput,
runCount: -1,
getInitData: () => stepResults?.input,
getStepResult: (step) => {
if (!step?.id) {
return null;
}
const result = stepResults[step.id];
if (result?.status === "success") {
return result.output;
}
return null;
},
// TODO: this function shouldn't have suspend probably?
suspend: async (_suspendPayload) => {
},
bail: () => {
},
abort: () => {
abortController?.abort();
},
[EMITTER_SYMBOL]: emitter,
engine: {},
abortSignal: abortController?.signal,
writer: new ToolStream(
{
prefix: "step",
callId: stepCallId,
name: "sleep",
runId
},
writableStream
)
});
}
await new Promise((resolve) => setTimeout(resolve, !duration || duration < 0 ? 0 : duration));
}
async executeSleepUntil({
workflowId,
runId,
entry,
prevOutput,
stepResults,
emitter,
abortController,
runtimeContext,
writableStream
}) {
let { date, fn } = entry;
if (fn) {
const stepCallId = randomUUID();
date = await fn({
runId,
workflowId,
mastra: this.mastra,
runtimeContext,
inputData: prevOutput,
runCount: -1,
getInitData: () => stepResults?.input,
getStepResult: (step) => {
if (!step?.id) {
return null;
}
const result = stepResults[step.id];
if (result?.status === "success") {
return result.output;
}
return null;
},
// TODO: this function shouldn't have suspend probably?
suspend: async (_suspendPayload) => {
},
bail: () => {
},
abort: () => {
abortController?.abort();
},
[EMITTER_SYMBOL]: emitter,
engine: {},
abortSignal: abortController?.signal,
writer: new ToolStream(
{
prefix: "step",
callId: stepCallId,
name: "sleepUntil",
runId
},
writableStream
)
});
}
const time = !date ? 0 : date?.getTime() - Date.now();
await new Promise((resolve) => setTimeout(resolve, time < 0 ? 0 : time));
}
async executeWaitForEvent({
event,
emitter,
timeout
}) {
return new Promise((resolve, reject) => {
const cb = (eventData) => {
resolve(eventData);
};
if (timeout) {
setTimeout(() => {
emitter.off(`user-event-${event}`, cb);
reject(new Error("Timeout waiting for event"));
}, timeout);
}
emitter.once(`user-event-${event}`, cb);
});
}
async executeStep({
workflowId,
runId,
step,
stepResults,
executionContext,
resume,
prevOutput,
emitter,
abortController,
runtimeContext,
skipEmits = false,
writableStream
}) {
const startTime = resume?.steps[0] === step.id ? void 0 : Date.now();
const resumeTime = resume?.steps[0] === step.id ? Date.now() : void 0;
const stepCallId = randomUUID();
const stepInfo = {
...stepResults[step.id],
...resume?.steps[0] === step.id ? { resumePayload: resume?.resumePayload } : { payload: prevOutput },
...startTime ? { startedAt: startTime } : {},
...resumeTime ? { resumedAt: resumeTime } : {}
};
if (!skipEmits) {
await emitter.emit("watch", {
type: "watch",
payload: {
currentStep: {
id: step.id,
status: "running",
...stepInfo
},
workflowState: {
status: "running",
steps: {
...stepResults,
[step.id]: {
status: "running",
...stepInfo
}
},
result: null,
error: null
}
},
eventTimestamp: Date.now()
});
await emitter.emit("watch-v2", {
type: "step-start",
payload: {
id: step.id,
stepCallId,
...stepInfo,
status: "running"
}
});
}
const _runStep = (step2, spanName, attributes) => {
return async (data) => {
const telemetry = this.mastra?.getTelemetry();
const span = executionContext.executionSpan;
if (!telemetry || !span) {
return step2.execute(data);
}
return context.with(trace.setSpan(context.active(), span), async () => {
return telemetry.traceMethod(step2.execute.bind(step2), {
spanName,
attributes
})(data);
});
};
};
const runStep = _runStep(step, `workflow.${workflowId}.step.${step.id}`, {
componentName: workflowId,
runId
});
let execResults;
const retries = step.retries ?? executionContext.retryConfig.attempts ?? 0;
const delay = executionContext.retryConfig.delay ?? 0;
for (let i = 0; i < retries + 1; i++) {
if (i > 0 && delay) {
await new Promise((resolve) => setTimeout(resolve, delay));
}
try {
let suspended;
let bailed;
const result = await runStep({
runId,
workflowId,
mastra: this.mastra,
runtimeContext,
inputData: prevOutput,
runCount: this.getOrGenerateRunCount(step.id),
resumeData: resume?.steps[0] === step.id ? resume?.resumePayload : void 0,
getInitData: () => stepResults?.input,
getStepResult: (step2) => {
if (!step2?.id) {
return null;
}
const result2 = stepResults[step2.id];
if (result2?.status === "success") {
return result2.output;
}
return null;
},
suspend: async (suspendPayload) => {
executionContext.suspendedPaths[step.id] = executionContext.executionPath;
suspended = { payload: suspendPayload };
},
bail: (result2) => {
bailed = { payload: result2 };
},
abort: () => {
abortController?.abort();
},
// Only pass resume data if this step was actually suspended before
// This prevents pending nested workflows from trying to resume instead of start
resume: stepResults[step.id]?.status === "suspended" ? {
steps: resume?.steps?.slice(1) || [],
resumePayload: resume?.resumePayload,
// @ts-ignore
runId: stepResults[step.id]?.suspendPayload?.__workflow_meta?.runId
} : void 0,
[EMITTER_SYMBOL]: emitter,
engine: {},
abortSignal: abortController?.signal,
writer: new ToolStream(
{
prefix: "step",
callId: stepCallId,
name: step.id,
runId
},
writableStream
)
});
if (suspended) {
execResults = { status: "suspended", suspendPayload: suspended.payload, suspendedAt: Date.now() };
} else if (bailed) {
execResults = { status: "bailed", output: bailed.payload, endedAt: Date.now() };
} else {
execResults = { status: "success", output: result, endedAt: Date.now() };
}
break;
} catch (e) {
const error = e instanceof MastraError ? e : new MastraError(
{
id: "WORKFLOW_STEP_INVOKE_FAILED",
domain: "MASTRA_WORKFLOW" /* MASTRA_WORKFLOW */,
category: "USER" /* USER */,
details: { workflowId, runId, stepId: step.id }
},
e
);
this.logger.trackException(error);
this.logger.error(`Error executing step ${step.id}: ` + error?.stack);
execResults = {
status: "failed",
error: error?.stack,
endedAt: Date.now()
};
}
}
if (!skipEmits) {
await emitter.emit("watch", {
type: "watch",
payload: {
currentStep: {
id: step.id,
...stepInfo,
...execResults
},
workflowState: {
status: "running",
steps: {
...stepResults,
[step.id]: {
...stepInfo,
...execResults
}
},
result: null,
error: null
}
},
eventTimestamp: Date.now()
});
if (execResults.status === "suspended") {
await emitter.emit("watch-v2", {
type: "step-suspended",
payload: {
id: step.id,
stepCallId,
...execResults
}
});
} else {
await emitter.emit("watch-v2", {
type: "step-result",
payload: {
id: step.id,
stepCallId,
...execResults
}
});
await emitter.emit("watch-v2", {
type: "step-finish",
payload: {
id: step.id,
stepCallId,
metadata: {}
}
});
}
}
return { ...stepInfo, ...execResults };
}
async executeParallel({
workflowId,
runId,
entry,
prevStep,
serializedStepGraph,
stepResults,
resume,
executionContext,
emitter,
abortController,
runtimeContext,
writableStream
}) {
let execResults;
const results = await Promise.all(
entry.steps.map(
(step, i) => this.executeEntry({
workflowId,
runId,
entry: step,
prevStep,
stepResults,
serializedStepGraph,
resume,
executionContext: {
workflowId,
runId,
executionPath: [...executionContext.executionPath, i],
suspendedPaths: executionContext.suspendedPaths,
retryConfig: executionContext.retryConfig,
executionSpan: executionContext.executionSpan
},
emitter,
abortController,
runtimeContext,
writableStream
})
)
);
const hasFailed = results.find((result) => result.result.status === "failed");
const hasSuspended = results.find((result) => result.result.status === "suspended");
if (hasFailed) {
execResults = { status: "failed", error: hasFailed.result.error };
} else if (hasSuspended) {
execResults = { status: "suspended", payload: hasSuspended.result.suspendPayload };
} else if (abortController?.signal?.aborted) {
execResults = { status: "canceled" };
} else {
execResults = {
status: "success",
output: results.reduce((acc, result, index) => {
if (result.result.status === "success") {
acc[entry.steps[index].step.id] = result.result.output;
}
return acc;
}, {})
};
}
return execResults;
}
async executeConditional({
workflowId,
runId,
entry,
prevOutput,
prevStep,
serializedStepGraph,
stepResults,
resume,
executionContext,
emitter,
abortController,
runtimeContext,
writableStream
}) {
let execResults;
const truthyIndexes = (await Promise.all(
entry.conditions.map(async (cond, index) => {
try {
const result = await cond({
runId,
workflowId,
mastra: this.mastra,
runtimeContext,
inputData: prevOutput,
runCount: -1,
getInitData: () => stepResults?.input,
getStepResult: (step) => {
if (!step?.id) {
return null;
}
const result2 = stepResults[step.id];
if (result2?.status === "success") {
return result2.output;
}
return null;
},
// TODO: this function shouldn't have suspend probably?
suspend: async (_suspendPayload) => {
},
bail: () => {
},
abort: () => {
abortController?.abort();
},
[EMITTER_SYMBOL]: emitter,
engine: {},
abortSignal: abortController?.signal,
writer: new ToolStream(
{
prefix: "step",
callId: randomUUID(),
name: "conditional",
runId
},
writableStream
)
});
return result ? index : null;
} catch (e) {
const error = e instanceof MastraError ? e : new MastraError(
{
id: "WORKFLOW_CONDITION_EVALUATION_FAILED",
domain: "MASTRA_WORKFLOW" /* MASTRA_WORKFLOW */,
category: "USER" /* USER */,
details: { workflowId, runId }
},
e
);
this.logger.trackException(error);
this.logger.error("Error evaluating condition: " + error?.stack);
return null;
}
})
)).filter((index) => index !== null);
const stepsToRun = entry.steps.filter((_, index) => truthyIndexes.includes(index));
const results = await Promise.all(
stepsToRun.map(
(step, index) => this.executeEntry({
workflowId,
runId,
entry: step,
prevStep,
stepResults,
serializedStepGraph,
resume,
executionContext: {
workflowId,
runId,
executionPath: [...executionContext.executionPath, index],
suspendedPaths: executionContext.suspendedPaths,
retryConfig: executionContext.retryConfig,
executionSpan: executionContext.executionSpan
},
emitter,
abortController,
runtimeContext,
writableStream
})
)
);
const hasFailed = results.find((result) => result.result.status === "failed");
const hasSuspended = results.find((result) => result.result.status === "suspended");
if (hasFailed) {
execResults = { status: "failed", error: hasFailed.result.error };
} else if (hasSuspended) {
execResults = { status: "suspended", payload: hasSuspended.result.suspendPayload };
} else if (abortController?.signal?.aborted) {
execResults = { status: "canceled" };
} else {
execResults = {
status: "success",
output: results.reduce((acc, result, index) => {
if (result.result.status === "success") {
acc[stepsToRun[index].step.id] = result.result.output;
}
return acc;
}, {})
};
}
return execResults;
}
async executeLoop({
workflowId,
runId,
entry,
prevOutput,
stepResults,
resume,
executionContext,
emitter,
abortController,
runtimeContext,
writableStream
}) {
const { step, condition } = entry;
let isTrue = true;
let result = { status: "success", output: prevOutput };
let currentResume = resume;
do {
result = await this.executeStep({
workflowId,
runId,
step,
stepResults,
executionContext,
resume: currentResume,
prevOutput: result.output,
emitter,
abortController,
runtimeContext,
writableStream
});
if (currentResume && result.status !== "suspended") {
currentResume = void 0;
}
if (result.status !== "success") {
return result;
}
isTrue = await condition({
workflowId,
runId,
mastra: this.mastra,
runtimeContext,
inputData: result.output,
runCount: -1,
getInitData: () => stepResults?.input,
getStepResult: (step2) => {
if (!step2?.id) {
return null;
}
const result2 = stepResults[step2.id];
return result2?.status === "success" ? result2.output : null;
},
suspend: async (_suspendPayload) => {
},
bail: () => {
},
abort: () => {
abortController?.abort();
},
[EMITTER_SYMBOL]: emitter,
engine: {},
abortSignal: abortController?.signal,
writer: new ToolStream(
{
prefix: "step",
callId: randomUUID(),
name: "loop",
runId
},
writableStream
)
});
} while (entry.loopType === "dowhile" ? isTrue : !isTrue);
return result;
}
async executeForeach({
workflowId,
runId,
entry,
prevOutput,
stepResults,
resume,
executionContext,
emitter,
abortController,
runtimeContext,
writableStream
}) {
const { step, opts } = entry;
const results = [];
const concurrency = opts.concurrency;
const startTime = resume?.steps[0] === step.id ? void 0 : Date.now();
const resumeTime = resume?.steps[0] === step.id ? Date.now() : void 0;
const stepInfo = {
...stepResults[step.id],
...resume?.steps[0] === step.id ? { resumePayload: resume?.resumePayload } : { payload: prevOutput },
...startTime ? { startedAt: startTime } : {},
...resumeTime ? { resumedAt: resumeTime } : {}
};
await emitter.emit("watch", {
type: "watch",
payload: {
currentStep: {
id: step.id,
status: "running",
...stepInfo
},
workflowState: {
status: "running",
steps: {
...stepResults,
[step.id]: {
status: "running",
...stepInfo
}
},
result: null,
error: null
}
},
eventTimestamp: Date.now()
});
await emitter.emit("watch-v2", {
type: "step-start",
payload: {
id: step.id,
...stepInfo,
status: "running"
}
});
for (let i = 0; i < prevOutput.length; i += concurrency) {
const items = prevOutput.slice(i, i + concurrency);
const itemsResults = await Promise.all(
items.map((item) => {
return this.executeStep({
workflowId,
runId,
step,
stepResults,
executionContext,
resume,
prevOutput: item,
emitter,
abortController,
runtimeContext,
skipEmits: true,
writableStream
});
})
);
for (const result of itemsResults) {
if (result.status !== "success") {
const { status, error, suspendPayload, suspendedAt, endedAt, output } = result;
const execResults = { status, error, suspendPayload, suspendedAt, endedAt, output };
await emitter.emit("watch", {
type: "watch",
payload: {
currentStep: {
id: step.id,
...stepInfo,
...execResults
},
workflowState: {
status: "running",
steps: {
...stepResults,
[step.id]: {
...stepInfo,
...execResults
}
},
result: null,
error: null
}
},
eventTimestamp: Date.now()
});
if (execResults.status === "suspended") {
await emitter.emit("watch-v2", {
type: "step-suspended",
payload: {
id: step.id,
...execResults
}
});
} else {
await emitter.emit("watch-v2", {
type: "step-result",
payload: {
id: step.id,
...execResults
}
});
await emitter.emit("watch-v2", {
type: "step-finish",
payload: {
id: step.id,
metadata: {}
}
});
}
return result;
}
results.push(result?.output);
}
}
await emitter.emit("watch", {
type: "watch",
payload: {
currentStep: {
id: step.id,
...stepInfo,
status: "success",
output: results,
endedAt: Date.now()
},
workflowState: {
status: "running",
steps: {
...stepResults,
[step.id]: {
...stepInfo,
status: "success",
output: results,
endedAt: Date.now()
}
},
result: null,
error: null
}
},
eventTimestamp: Date.now()
});
await emitter.emit("watch-v2", {
type: "step-result",
payload: {
id: step.id,
status: "success",
output: results,
endedAt: Date.now()
}
});
await emitter.emit("watch-v2", {
type: "step-finish",
payload: {
id: step.id,
metadata: {}
}
});
return {
...stepInfo,
status: "success",
output: results,
//@ts-ignore
endedAt: Date.now()
};
}
async persistStepUpdate({
workflowId,
runId,
stepResults,
serializedStepGraph,
executionContext,
workflowStatus,
result,
error,
runtimeContext
}) {
const runtimeContextObj = {};
runtimeContext.forEach((value, key) => {
runtimeContextObj[key] = value;
});
await this.mastra?.getStorage()?.persistWorkflowSnapshot({
workflowName: workflowId,
runId,
snapshot: {
runId,
status: workflowStatus,
value: {},
context: stepResults,
activePaths: [],
serializedStepGraph,
suspendedPaths: executionContext.suspendedPaths,
result,
error,
runtimeContext: runtimeContextObj,
// @ts-ignore
timestamp: Date.now()
}
});
}
async executeEntry({
workflowId,
runId,
entry,
prevStep,
serializedStepGraph,
stepResults,
resume,
executionContext,
emitter,
abortController,
runtimeContext,
writableStream
}) {
const prevOutput = this.getStepOutput(stepResults, prevStep);
let execResults;
if (entry.type === "step") {
const { step } = entry;
execResults = await this.executeStep({
workflowId,
runId,
step,
stepResults,
executionContext,
resume,
prevOutput,
emitter,
abortController,
runtimeContext,
writableStream
});
} else if (resume?.resumePath?.length && entry.type === "parallel") {
const idx = resume.resumePath.shift();
return this.executeEntry({
workflowId,
runId,
entry: entry.steps[idx],
prevStep,
serializedStepGraph,
stepResults,
resume,
executionContext: {
workflowId,
runId,
executionPath: [...executionContext.executionPath, idx],
suspendedPaths: executionContext.suspendedPaths,
retryConfig: executionContext.retryConfig,
executionSpan: executionContext.executionSpan
},
emitter,
abortController,
runtimeContext,
writableStream
});
} else if (entry.type === "parallel") {
execResults = await this.executeParallel({
workflowId,
runId,
entry,
prevStep,
stepResults,
serializedStepGraph,
resume,
executionContext,
emitter,
abortController,
runtimeContext,
writableStream
});
} else if (entry.type === "conditional") {
execResults = await this.executeConditional({
workflowId,
runId,
entry,
prevStep,
prevOutput,
stepResults,
serializedStepGraph,
resume,
executionContext,
emitter,
abortController,
runtimeContext,
writableStream
});
} else if (entry.type === "loop") {
execResults = await this.executeLoop({
workflowId,
runId,
entry,
prevStep,
prevOutput,
stepResults,
resume,
executionContext,
emitter,
abortController,
runtimeContext,
writableStream
});
} else if (entry.type === "foreach") {
execResults = await this.executeForeach({
workflowId,
runId,
entry,
prevStep,
prevOutput,
stepResults,
resume,
executionContext,
emitter,
abortController,
runtimeContext,
writableStream
});
} else if (entry.type === "sleep") {
const startedAt = Date.now();
await emitter.emit("watch", {
type: "watch",
payload: {
currentStep: {
id: entry.id,
status: "waiting",
payload: prevOutput,
startedAt
},
workflowState: {
status: "waiting",
steps: {
...stepResults,
[entry.id]: {
status: "waiting",
payload: prevOutput,
startedAt
}
},
result: null,
error: null
}
},
eventTimestamp: Date.now()
});
await emitter.emit("watch-v2", {
type: "step-waiting",
payload: {
id: entry.id,
payload: prevOutput,
startedAt,
status: "waiting"
}
});
await this.persistStepUpdate({
workflowId,
runId,
serializedStepGraph,
stepResults,
executionContext,
workflowStatus: "waiting",
runtimeContext
});
await this.executeSleep({
workflowId,
runId,
entry,
prevStep,
prevOutput,
stepResults,
serializedStepGraph,
resume,
executionContext,
emitter,
abortController,
runtimeContext,
writableStream
});
await this.persistStepUpdate({
workflowId,
runId,
serializedStepGraph,
stepResults,
executionContext,
workflowStatus: "running",
runtimeContext
});
const endedAt = Date.now();
const stepInfo = {
payload: prevOutput,
startedAt,
endedAt
};
execResults = { ...stepInfo, status: "success", output: prevOutput };
stepResults[entry.id] = { ...stepInfo, status: "success", output: prevOutput };
await emitter.emit("watch", {
type: "watch",
payload: {
currentStep: {
id: entry.id,
...execResults
},
workflowState: {
status: "running",
steps: {
...stepResults,
[entry.id]: {
...execResults
}
},
result: null,
error: null
}
},
eventTimestamp: Date.now()
});
await emitter.emit("watch-v2", {
type: "step-result",
payload: {
id: entry.id,
endedAt,
status: "success",
output: prevOutput
}
});
await emitter.emit("watch-v2", {
type: "step-finish",
payload: {
id: entry.id,
metadata: {}
}
});
} else if (entry.type === "sleepUntil") {
const startedAt = Date.now();
await emitter.emit("watch", {
type: "watch",
payload: {
currentStep: {
id: entry.id,
status: "waiting",
payload: prevOutput,
startedAt
},
workflowState: {
status: "waiting",
steps: {
...stepResults,
[entry.id]: {
status: "waiting",
payload: prevOutput,
startedAt
}
},
result: null,
error: null
}
},
eventTimestamp: Date.now()
});
await emitter.emit("watch-v2", {
type: "step-waiting",
payload: {
id: entry.id,
payload: prevOutput,
startedAt,
status: "waiting"
}
});
await this.persistStepUpdate({
workflowId,
runId,
serializedStepGraph,
stepResults,
executionContext,
workflowStatus: "waiting",
runtimeContext
});
await this.executeSleepUntil({
workflowId,
runId,
entry,
prevStep,
prevOutput,
stepResults,
serializedStepGraph,
resume,
executionContext,
emitter,
abortController,
runtimeContext,
writableStream
});
await this.persistStepUpdate({
workflowId,
runId,
serializedStepGraph,
stepResults,
executionContext,
workflowStatus: "running",
runtimeContext
});
const endedAt = Date.now();
const stepInfo = {
payload: prevOutput,
startedAt,
endedAt
};
execResults = { ...stepInfo, status: "success", output: prevOutput };
stepResults[entry.id] = { ...stepInfo, status: "success", output: prevOutput };
await emitter.emit("watch", {
type: "watch",
payload: {
currentStep: {
id: entry.id,
...execResults
},
workflowState: {
status: "running",
steps: {
...stepResults,
[entry.id]: {
...execResults
}
},
result: null,
error: null
}
},
eventTimestamp: Date.now()
});
await emitter.emit("watch-v2", {
type: "step-result",
payload: {
id: entry.id,
endedAt,
status: "success",
output: prevOutput
}
});
await emitter.emit("watch-v2", {
type: "step-finish",
payload: {
id: entry.id,
metadata: {}
}
});
} else if (entry.type === "waitForEvent") {
const startedAt = Date.now();
let eventData;
await emitter.emit("watch", {
type: "watch",
payload: {
currentStep: {
id: entry.step.id,
status: "waiting",
payload: prevOutput,
startedAt
},
workflowState: {
status: "waiting",
steps: {
...stepResults,
[entry.step.id]: {
status: "waiting",
payload: prevOutput,
startedAt
}
},
result: null,
error: null
}
},
eventTimestamp: Date.now()
});
await emitter.emit("watch-v2", {
type: "step-waiting",
payload: {
id: entry.step.id,
payload: prevOutput,
startedAt,
status: "waiting"
}
});
await this.persistStepUpdate({
workflowId,
runId,
serializedStepGraph,
stepResults,
executionContext,
workflowStatus: "waiting",
runtimeContext
});
try {
eventData = await this.executeWaitForEvent({ event: entry.event, emitter, timeout: entry.timeout });
await this.persistStepUpdate({
workflowId,
runId,
serializedStepGraph,
stepResults,
executionContext,
workflowStatus: "running",
runtimeContext
});
const { step } = entry;
execResults = await this.executeStep({
workflowId,
runId,
step,
stepResults,
executionContext,
resume: {
resumePayload: eventData,
steps: [entry.step.id]
},
prevOutput,
emitter,
abortController,
runtimeContext,
writableStream
});
} catch (error) {
execResults = {
status: "failed",
error
};
}
const endedAt = Date.now();
const stepInfo = {
payload: prevOutput,
startedAt,
endedAt
};
execResults = { ...execResults, ...stepInfo };
}
if (entry.type === "step" || entry.type === "waitForEvent" || entry.type === "loop" || entry.type === "foreach") {
stepResults[entry.step.id] = execResults;
}
if (abortController?.signal?.aborted) {
execResults = { ...execResults, status: "canceled" };
}
await this.persistStepUpdate({
workflowId,
runId,
serializedStepGraph,
stepResults,
executionContext,
workflowStatus: execResults.status === "success" ? "running" : execResults.status,
runtimeContext
});
return { result: execResults, stepResults, executionContext };
}
};
var MastraWorkflowStream = class extends ReadableStream {
#usageCount = {
promptTokens: 0,
completionTokens: 0,
totalTokens: 0
};
#streamPromise;
#run;
constructor({
createStream,
run
}) {
const deferredPromise = {
promise: null,
resolve: null,
reject: null
};
deferredPromise.promise = new Promise((resolve, reject) => {
deferredPromise.resolve = resolve;
deferredPromise.reject = reject;
});
const updateUsageCount = (usage) => {
this.#usageCount.promptTokens += parseInt(usage.promptTokens?.toString() ?? "0", 10);
this.#usageCount.completionTokens += parseInt(usage.completionTokens?.toString() ?? "0", 10);
this.#usageCount.totalTokens += parseInt(usage.totalTokens?.toString() ?? "0", 10);
};
super({
start: async (controller) => {
const writer = new WritableStream({
write: (chunk) => {
if (chunk.type === "step-output" && chunk.payload?.output?.from === "AGENT" && chunk.payload?.output?.type === "finish" || chunk.type === "step-output" && chunk.payload?.output?.from === "WORKFLOW" && chunk.payload?.output?.type === "finish") {
const finishPayload = chunk.payload?.output.payload;
updateUsageCount(finishPayload.usage);
}
controller.enqueue(chunk);
}
});
controller.enqueue({
type: "start",
runId: run.runId,
from: "WORKFLOW",
payload: {}
});
const stream = await createStream(writer);
for await (const chunk of stream) {
if (chunk.type === "step-output" && chunk.payload?.output?.from === "AGENT" && chunk.payload?.output?.type === "finish" || chunk.type === "step-output" && chunk.payload?.output?.from === "WORKFLOW" && chunk.payload?.output?.type === "finish") {
const finishPayload = chunk.payload?.output.payload;
updateUsageCount(finishPayload.usage);
}
controller.enqueue(chunk);
}
controller.enqueue({
type: "finish",
runId: run.runId,
from: "WORKFLOW",
payload: {
totalUsage: this.#usageCount
}
});
controller.close();
deferredPromise.resolve();
}
});
this.#run = run;
this.#streamPromise = deferredPromise;
}
get status() {
return this.#streamPromise.promise.then(() => this.#run._getExecutionResults()).then((res) => res.status);
}
get result() {
return this.#streamPromise.promise.then(() => this.#run._getExecutionResults());
}
get usage() {
return this.#streamPromise.promise.then(() => this.#usageCount);
}
};
// src/workflows/workflow.ts
function mapVariable(config) {
return config;
}
function createStep(params) {
const wrapExecute = (execute) => {
return async (executeParams) => {
const executeResult = await execute(executeParams);
if (params instanceof Agent || params instanceof Tool) {
return executeResult;
}
let scorersToUse = params.scorers;
if (typeof scorersToUse === "function") {
scorersToUse = await scorersToUse({
runtimeContext: executeParams.runtimeContext
});
}
if (scorersToUse && Object.keys(scorersToUse || {}).length > 0) {
for (const [id, scorerObject] of Object.entries(scorersToUse || {})) {
runScorer({
scorerId: id,
scorerObject,
runId: executeParams.runId,
input: [executeParams.inputData],
output: executeResult,
runtimeContext: executeParams.runtimeContext,
entity: {
id: executeParams.workflowId,
stepId: params.id
},
structuredOutput: true,
source: "LIVE",
entityType: "WORKFLOW"
});
}
}
return executeResult;
};
};
if (params instanceof Agent) {
return {
id: params.name,
// @ts-ignore
inputSchema: z.object({
prompt: z.string()
// resourceId: z.string().optional(),
// threadId: z.string().optional(),
}),
// @ts-ignore
outputSchema: z.object({
text: z.string()
}),
execute: wrapExecute(async ({ inputData, [EMITTER_SYMBOL]: emitter, runtimeContext, abortSignal, abort }) => {
let streamPromise = {};
streamPromise.promise = new Promise((resolve, reject) => {
streamPromise.resolve = resolve;
streamPromise.reject = reject;
});
const toolData = {
name: params.name,
args: inputData
};
await emitter.emit("watch-v2", {
type: "tool-call-streaming-start",
...toolData
});
const { fullStream } = await params.stream(inputData.prompt, {
// resourceId: inputData.resourceId,
// threadId: inputData.threadId,
runtimeContext,
onFinish: (result) => {
streamPromise.resolve(result.text);
},
abortSignal
});
if (abortSignal.aborted) {
return abort();
}
for await (const chunk of fullStream) {
switch (chunk.type) {
case "text-delta":
await emitter.emit("watch-v2", {
type: "tool-call-delta",
...toolData,
argsTextDelta: chunk.textDelta
});
break;
case "step-start":
case "step-finish":
case "finish":
break;
case "tool-call":
case "tool-result":
case "tool-call-streaming-start":
case "tool-call-delta":
case "source":
case "file":
default:
await emitter.emit("watch-v2", chunk);
break;
}
}
return {
text: await streamPromise.promise
};
})
};
}
if (params instanceof Tool) {
if (!params.inputSchema || !params.outputSchema) {
throw new Error("Tool must have input and output schemas defined");
}
return {
// TODO: tool probably should have strong id type
// @ts-ignore
id: params.id,
inputSchema: params.inputSchema,
outputSchema: params.outputSchema,
execute: wrapExecute(async ({ inputData, mastra, runtimeContext }) => {
return params.execute({
context: inputData,
mastra,
runtimeContext
});
})
};
}
return {
id: params.id,
description: params.description,