UNPKG

@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
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,