@mastra/core
Version:
1,298 lines • 139 kB
JavaScript
import { a as RegisteredLogger } from "./logger-B_aQzjbm.js";
import { t as MastraBase } from "./base-BeUQ6mLP.js";
import { a as MastraNonRetryableError, i as MastraError, n as ErrorDomain, o as getErrorFromUnknown, t as ErrorCategory } from "./error-MjDSls8S.js";
import { a as resolveObservabilityContext, i as createObservabilityContext } from "./observability-Cz-X7NF_.js";
import { f as EntityType, t as executeWithContext } from "./utils-DxsDNzD2.js";
import "./tracing-Bm0k4FBA.js";
import { a as RequestContext } from "./request-context-p_Tq-4EM.js";
import { toStandardSchema } from "./schema/index.js";
import { n as ToolStream } from "./types-C59tsW89.js";
import { v as removeUndefinedValues } from "./utils-CCbB2dG1.js";
import { t as TripWire } from "./trip-wire-csEv7lz7.js";
import { PUBSUB_SYMBOL, STREAM_FORMAT_SYMBOL } from "./workflows/constants.js";
import { randomUUID } from "crypto";
import EventEmitter$1 from "events";
import { z } from "zod";
//#region src/workflows/stream-utils.ts
async function forwardAgentStreamChunk({ writer, chunk }) {
if (!writer) return;
await writer.write(chunk);
}
//#endregion
//#region src/workflows/step.ts
const getStepResult = (stepResults, step) => {
let result;
if (typeof step === "string") result = stepResults[step];
else {
if (!step?.id) return null;
result = stepResults[step.id];
}
return result?.status === "success" ? result.output : null;
};
//#endregion
//#region src/workflows/step-entry.ts
/**
* Accessors for the {@link SingleStepEntry} union.
*
* This module is the single place allowed to pattern-match the union's shape.
* Everything else (both engines, handlers, utils) should go through these
* helpers so that adding a new variant means changing exactly one file.
*
* Union *shape* questions (id, retries, schemas, …) live here; how each
* declarative kind is *interpreted* at run time lives in `./entry-executors`.
*/
/**
* The id of a single step-like entry. Plain `step` entries key off the wrapped
* step's id; declarative variants (agent / tool / mapping) carry their own `id`.
*/
function getEntryId(entry) {
return entry.type === "step" ? entry.step.id : entry.id;
}
/**
* The effective retry count for an entry, falling back to the provided
* workflow-level default when the entry doesn't declare its own.
*
* - `step` — the step's own `retries`
* - `agent` / `tool` — the declarative `options.retries`
* - `mapping` — never declares retries; always the fallback
*/
function getEntryRetries(entry, fallback) {
switch (entry.type) {
case "step": return entry.step.retries ?? fallback;
case "agent":
case "tool": return entry.options?.retries ?? fallback;
case "mapping": return fallback;
}
}
/**
* The `component` discriminator of the entry, if any. Only plain `step`
* entries can carry one (notably `'WORKFLOW'` for nested workflows);
* declarative variants have none.
*/
function getEntryComponent(entry) {
return entry.type === "step" ? entry.step.component : void 0;
}
/**
* Probes an entry for a nested workflow. Only the `type: 'step'` variant can
* wrap a live `Workflow` (identified by its `component === 'WORKFLOW'`
* discriminator from MastraBase); declarative variants never nest one.
*/
function getEntryWorkflow(entry) {
if (entry.type !== "step") return null;
const step = entry.step;
if (step && typeof step === "object" && step.component === "WORKFLOW") return entry.step;
return null;
}
/**
* The validation schemas of an entry, used by the engines to validate step
* input / suspend / resume data without materializing a live Step.
*
* - `step` — the step's own schemas
* - `agent` — the fixed `{ prompt: string }` input contract (mirrors `createStepFromAgent`)
* - `tool` — the resolved tool's schemas
* - `mapping` — none (mappings accept and return anything)
*
* Never throws: when a tool can't be resolved the schemas are simply empty and
* the run path surfaces the actionable not-found error.
*/
function getEntrySchemas(entry, mastra) {
switch (entry.type) {
case "step": return {
inputSchema: entry.step.inputSchema,
resumeSchema: entry.step.resumeSchema,
suspendSchema: entry.step.suspendSchema
};
case "agent": return { inputSchema: toStandardSchema(z.object({ prompt: z.string() })) };
case "tool": {
let tool;
try {
tool = entry.tool ?? mastra?.getTool(entry.toolId);
} catch {
tool = void 0;
}
return tool ? {
inputSchema: tool.inputSchema,
resumeSchema: tool.resumeSchema,
suspendSchema: tool.suspendSchema
} : {};
}
case "mapping": return {};
}
}
//#endregion
//#region src/workflows/utils.ts
/**
* Validates data against a StandardSchema and returns the result.
* Works with both sync and async schemas.
*/
async function validateWithStandardSchema(schema, data) {
const result = schema["~standard"].validate(data);
const resolvedResult = result instanceof Promise ? await result : result;
if ("issues" in resolvedResult && resolvedResult.issues) return {
success: false,
issues: resolvedResult.issues.map((issue) => ({
path: issue.path?.map((p) => typeof p === "object" && "key" in p ? p.key : p),
message: issue.message
}))
};
return {
success: true,
data: resolvedResult.value
};
}
async function validateStepInput({ prevOutput, step, validateInputs }) {
let inputData = prevOutput;
let validationError;
const inputSchema = step.inputSchema;
if (validateInputs && inputSchema) {
const validatedInput = await validateWithStandardSchema(inputSchema, prevOutput);
if (!validatedInput.success) {
const errorMessages = validatedInput.issues.map((e) => `- ${e.path?.join(".")}: ${e.message}`).join("\n");
validationError = new MastraError({
id: "WORKFLOW_STEP_INPUT_VALIDATION_FAILED",
domain: ErrorDomain.MASTRA_WORKFLOW,
category: ErrorCategory.USER,
text: "Step input validation failed: \n" + errorMessages
}, { issues: validatedInput.issues });
} else inputData = validatedInput.data !== null && typeof validatedInput.data === "object" && !Array.isArray(validatedInput.data) && Object.keys(validatedInput.data).length === 0 ? prevOutput : validatedInput.data;
}
return {
inputData,
validationError
};
}
async function validateStepResumeData({ resumeData, step }) {
if (!resumeData) return {
resumeData: void 0,
validationError: void 0
};
let validationError;
const resumeSchema = step.resumeSchema;
if (resumeSchema) {
const validatedResumeData = await validateWithStandardSchema(resumeSchema, resumeData);
if (!validatedResumeData.success) {
const errorMessages = validatedResumeData.issues.map((e) => `- ${e.path?.join(".")}: ${e.message}`).join("\n");
validationError = new MastraError({
id: "WORKFLOW_STEP_RESUME_DATA_VALIDATION_FAILED",
domain: ErrorDomain.MASTRA_WORKFLOW,
category: ErrorCategory.USER,
text: "Step resume data validation failed: \n" + errorMessages
});
} else resumeData = validatedResumeData.data;
}
return {
resumeData,
validationError
};
}
async function validateStepSuspendData({ suspendData, step, validateInputs }) {
if (!suspendData) return {
suspendData: void 0,
validationError: void 0
};
let validationError;
const suspendSchema = step.suspendSchema;
if (suspendSchema && validateInputs) {
const validatedSuspendData = await validateWithStandardSchema(suspendSchema, suspendData);
if (!validatedSuspendData.success) {
const errorMessages = validatedSuspendData.issues.map((e) => `- ${e.path?.join(".")}: ${e.message}`).join("\n");
validationError = new MastraError({
id: "WORKFLOW_STEP_SUSPEND_DATA_VALIDATION_FAILED",
domain: ErrorDomain.MASTRA_WORKFLOW,
category: ErrorCategory.USER,
text: "Step suspend data validation failed: \n" + errorMessages
});
} else suspendData = validatedSuspendData.data;
}
return {
suspendData,
validationError
};
}
async function validateStepStateData({ stateData, step, validateInputs }) {
if (!stateData) return {
stateData: void 0,
validationError: void 0
};
let validationError;
const stateSchema = step.stateSchema;
if (stateSchema && validateInputs) {
const validatedStateData = await validateWithStandardSchema(stateSchema, stateData);
if (!validatedStateData.success) {
const errorMessages = validatedStateData.issues.map((e) => `- ${e.path?.join(".")}: ${e.message}`).join("\n");
validationError = /* @__PURE__ */ new Error("Step state data validation failed: \n" + errorMessages);
} else stateData = validatedStateData.data;
}
return {
stateData,
validationError
};
}
async function validateStepRequestContext({ requestContext, step, validateInputs }) {
let validationError;
const requestContextSchema = step.requestContextSchema;
if (requestContextSchema && validateInputs) {
const validatedRequestContext = await validateWithStandardSchema(requestContextSchema, requestContext?.all ?? {});
if (!validatedRequestContext.success) {
const errorMessages = validatedRequestContext.issues.map((e) => `- ${e.path?.join(".")}: ${e.message}`).join("\n");
validationError = new MastraError({
id: "WORKFLOW_STEP_REQUEST_CONTEXT_VALIDATION_FAILED",
domain: ErrorDomain.MASTRA_WORKFLOW,
category: ErrorCategory.USER,
text: `Step request context validation failed for step '${step.id}': \n` + errorMessages
});
}
}
return { validationError };
}
function getResumeLabelsByStepId(resumeLabels, stepId) {
return Object.entries(resumeLabels).filter(([_, value]) => value.stepId === stepId).reduce((acc, [key, value]) => {
acc[key] = value;
return acc;
}, {});
}
const runCountDeprecationMessage = "Warning: 'runCount' is deprecated and will be removed on November 4th, 2025. Please use 'retryCount' instead.";
/**
* Track which deprecation warnings have been shown globally to avoid spam
*/
const shownWarnings = /* @__PURE__ */ new Set();
/**
* Creates a Proxy that wraps execute function parameters to show deprecation warnings
* when accessing deprecated properties.
*
* Currently handles:
* - `runCount`: Deprecated in favor of `retryCount`, will be removed on November 4th, 2025
*/
function createDeprecationProxy(params, { paramName, deprecationMessage, logger }) {
return new Proxy(params, { get(target, prop, receiver) {
if (prop === paramName && !shownWarnings.has(paramName)) {
shownWarnings.add(paramName);
if (logger) logger.warn("\x1B[33m%s\x1B[0m", deprecationMessage);
else console.warn("\x1B[33m%s\x1B[0m", deprecationMessage);
}
return Reflect.get(target, prop, receiver);
} });
}
const SINGLE_STEP_TYPES = [
"step",
"agent",
"tool",
"mapping"
];
/**
* Whether an entry is a "single step-like" entry: a plain user step or one of the
* declarative variants (agent / tool / mapping) that resolve to exactly one step.
*/
function isSingleStepEntry(entry) {
return SINGLE_STEP_TYPES.includes(entry.type);
}
/**
* The id of a single step-like entry. Plain `step` entries key off the wrapped
* step's id; declarative variants (agent / tool / mapping) carry their own `id`.
*
* Public alias of {@link getEntryId} from `./step-entry`.
*/
const getSingleStepEntryId = getEntryId;
const getStepIds = (entry) => {
if (isSingleStepEntry(entry)) return [getSingleStepEntryId(entry)];
if (entry.type === "foreach" || entry.type === "loop") return [getSingleStepEntryId(entry.step)];
if (entry.type === "parallel" || entry.type === "conditional") return entry.steps.map((s) => getSingleStepEntryId(s));
if (entry.type === "sleep" || entry.type === "sleepUntil") return [entry.id];
return [];
};
const createTimeTravelExecutionParams = (params) => {
const { steps, inputData, resumeData, context, nestedStepsContext, snapshot, initialState, graph, perStep } = params;
const firstStepId = steps[0];
let executionPath = [];
const stepResults = {};
const snapshotContext = snapshot.context;
for (const [index, entry] of graph.steps.entries()) {
const currentExecPathLength = executionPath.length;
if (currentExecPathLength > 0 && !resumeData) break;
const stepIds = getStepIds(entry);
const isTargetEntry = stepIds.includes(firstStepId);
if (isTargetEntry) executionPath = [index, ...stepIds?.length > 1 ? [stepIds?.findIndex((s) => s === firstStepId)] : []];
const prevStep = graph.steps[index - 1];
let stepPayload = void 0;
if (prevStep) {
const prevStepIds = getStepIds(prevStep);
if (prevStepIds.length > 0) if (prevStepIds.length === 1) stepPayload = (stepResults?.[prevStepIds[0]])?.output ?? {};
else stepPayload = prevStepIds.reduce((acc, stepId) => {
acc[stepId] = (stepResults?.[stepId])?.output ?? {};
return acc;
}, {});
}
if (index === 0 && stepIds.includes(firstStepId)) stepResults.input = context?.[firstStepId]?.payload ?? inputData ?? snapshotContext?.input;
else if (index === 0) stepResults.input = stepIds?.reduce((acc, stepId) => {
if (acc) return acc;
return context?.[stepId]?.payload ?? snapshotContext?.[stepId]?.payload;
}, null) ?? snapshotContext?.input ?? {};
let stepOutput = void 0;
const nextStep = graph.steps[index + 1];
if (nextStep) {
const nextStepIds = getStepIds(nextStep);
if (nextStepIds.length > 0 && inputData && nextStepIds.includes(firstStepId) && steps.length === 1) stepOutput = inputData;
}
stepIds.forEach((stepId) => {
let result;
const stepContext = context?.[stepId] ?? snapshotContext[stepId];
const isUnselectedConditionalSibling = isTargetEntry && entry.type === "conditional" && !steps?.includes(stepId);
const defaultStepStatus = steps?.includes(stepId) ? "running" : isUnselectedConditionalSibling ? "skipped" : "success";
const status = ["failed", "canceled"].includes(stepContext?.status) ? defaultStepStatus : stepContext?.status ?? defaultStepStatus;
const isCompleteStatus = [
"success",
"failed",
"canceled"
].includes(status);
result = {
status,
payload: context?.[stepId]?.payload ?? stepPayload ?? snapshotContext[stepId]?.payload ?? {},
output: isCompleteStatus ? context?.[stepId]?.output ?? stepOutput ?? snapshotContext[stepId]?.output ?? {} : void 0,
resumePayload: stepContext?.resumePayload,
suspendPayload: stepContext?.suspendPayload,
suspendOutput: stepContext?.suspendOutput,
startedAt: stepContext?.startedAt ?? Date.now(),
endedAt: isCompleteStatus ? stepContext?.endedAt ?? Date.now() : void 0,
suspendedAt: stepContext?.suspendedAt,
resumedAt: stepContext?.resumedAt
};
if ((perStep ? executionPath.length : currentExecPathLength) > 0 && !steps?.includes(stepId) && !context?.[stepId] && (!snapshotContext[stepId] || snapshotContext[stepId] && snapshotContext[stepId].status !== "suspended")) result = void 0;
if (result) {
const formattedResult = removeUndefinedValues(result);
stepResults[stepId] = formattedResult;
}
});
}
if (!executionPath.length) throw new Error(`Time travel target step not found in execution graph: '${steps?.join(".")}'. Verify the step id/path.`);
return {
inputData,
executionPath,
steps,
stepResults,
nestedStepResults: nestedStepsContext,
state: initialState ?? snapshot.value ?? {},
resumeData,
stepExecutionPath: snapshot?.stepExecutionPath
};
};
const createRestartExecutionParams = ({ snapshot, graph }) => {
let nestedWorkflowPending = false;
if (snapshot.status !== "running" && snapshot.status !== "waiting") if (snapshot.status === "pending" && snapshot.context && Object.prototype.hasOwnProperty.call(snapshot.context, "input")) nestedWorkflowPending = true;
else throw new Error("This workflow run was not active");
let nestedWorkflowActiveStepsPath = {};
const firstEntry = graph.steps[0];
if (isSingleStepEntry(firstEntry)) nestedWorkflowActiveStepsPath = { [getSingleStepEntryId(firstEntry)]: [0] };
else if (firstEntry.type === "foreach" || firstEntry.type === "loop") nestedWorkflowActiveStepsPath = { [getSingleStepEntryId(firstEntry.step)]: [0] };
else if (firstEntry.type === "sleep" || firstEntry.type === "sleepUntil") nestedWorkflowActiveStepsPath = { [firstEntry.id]: [0] };
else if (firstEntry.type === "conditional" || firstEntry.type === "parallel") nestedWorkflowActiveStepsPath = firstEntry.steps.reduce((acc, step) => {
acc[getSingleStepEntryId(step)] = [0];
return acc;
}, {});
return {
activePaths: nestedWorkflowPending ? [0] : snapshot.activePaths,
activeStepsPath: nestedWorkflowPending ? nestedWorkflowActiveStepsPath : snapshot.activeStepsPath,
stepResults: snapshot.context,
state: snapshot.value,
stepExecutionPath: snapshot?.stepExecutionPath
};
};
/**
* Re-hydrates serialized errors in step results back into proper Error instances.
* This is useful when errors have been serialized through an event system (e.g., evented engine, Inngest)
* and need to be converted back to Error instances with their custom properties preserved.
*
* @param steps - The workflow step results (context) that may contain serialized errors
* @returns The same steps object with errors hydrated as Error instances
*/
function hydrateSerializedStepErrors(steps) {
if (steps) {
for (const step of Object.values(steps)) if (step.status === "failed" && "error" in step && step.error) step.error = getErrorFromUnknown(step.error, { serializeStack: false });
}
return steps;
}
/**
* Cleans a single step result object by removing internal properties.
* This is a helper for cleanStepResult that handles one level of cleaning.
*/
function cleanSingleResult(result) {
const { __state: _state, metadata, ...rest } = result;
if (metadata && typeof metadata === "object" && !Array.isArray(metadata)) {
const { nestedRunId: _nestedRunId, ...userMetadata } = metadata;
if (Object.keys(userMetadata).length > 0) return {
...rest,
metadata: userMetadata
};
}
return rest;
}
/**
* Cleans step result data by removing internal properties at known structural levels.
*
* Removes:
* - `__state` properties (internal workflow state for state propagation)
* - `nestedRunId` from `metadata` objects (internal tracking for nested workflow retrieval)
*
* ## Why targeted cleaning instead of recursive?
*
* Internal properties only appear at specific, known locations:
*
* 1. **`__state`** - Added by step-executor.ts to every step result. For forEach,
* suspended iterations store the full result (including __state) while completed
* iterations only store the output value. See workflow-event-processor/index.ts:1227-1230.
*
* 2. **`metadata.nestedRunId`** - Added when nested workflows complete, stored at the
* step result level. For forEach with nested workflows, each iteration result can
* have this. See workflow-event-processor/index.ts:1449-1453.
*
* By only cleaning at the step result level and forEach iteration level, we avoid
* accidentally stripping user data that happens to use `__state` as a property name
* in their actual output values.
*
* @param stepResult - A step result object, or an array of iteration results (forEach)
* @returns The cleaned step result with internal properties removed
*/
function cleanStepResult(stepResult) {
if (stepResult === null || stepResult === void 0) return stepResult;
if (typeof stepResult !== "object") return stepResult;
if (Array.isArray(stepResult)) return stepResult.map((item) => {
if (item && typeof item === "object" && !Array.isArray(item)) return cleanSingleResult(item);
return item;
});
const cleaned = cleanSingleResult(stepResult);
if (Array.isArray(cleaned.output)) cleaned.output = cleaned.output.map((item) => {
if (item && typeof item === "object" && !Array.isArray(item)) return cleanSingleResult(item);
return item;
});
return cleaned;
}
/**
* Resolves the effective concurrency for a foreach entry at execution time.
*
* Supports both a static number and a {@link ForeachConcurrencyResolver}
* function that derives concurrency from the run's input. Invalid or
* non-positive values fall back to 1 (sequential).
*/
function resolveForeachConcurrency(opts, context) {
const configured = opts?.concurrency ?? 1;
const resolved = typeof configured === "function" ? configured(context) : configured;
if (typeof resolved !== "number" || !Number.isFinite(resolved) || resolved < 1) return 1;
return Math.floor(resolved);
}
const RESUME_SNAPSHOT_POLL_INTERVAL_MS = 25;
const RESUME_SNAPSHOT_POLL_TIMEOUT_MS = 2e3;
async function waitForSuspendedSnapshot(workflowsStore, workflowName, runId) {
if (!workflowsStore) return null;
const deadline = Date.now() + RESUME_SNAPSHOT_POLL_TIMEOUT_MS;
let snapshot = await workflowsStore.loadWorkflowSnapshot({
workflowName,
runId
}) ?? null;
while ((!snapshot || snapshot.status !== "suspended") && Date.now() < deadline) {
await new Promise((resolve) => setTimeout(resolve, RESUME_SNAPSHOT_POLL_INTERVAL_MS));
snapshot = await workflowsStore.loadWorkflowSnapshot({
workflowName,
runId
}) ?? null;
}
return snapshot;
}
//#endregion
//#region src/workflows/entry-executors/run-agent-entry.ts
/**
* Runs a declarative `agent` entry: resolves the agent (inline handle, else the
* Mastra registry), streams the prompt through it, forwards stream chunks, and
* returns either the structured output or `{ text }`.
*
* `ctx` is the step execute context (the same object a plain step's `execute`
* receives). `mastra` defaults to `ctx.mastra` when omitted.
*/
async function runAgentEntry(entry, ctx, mastra) {
const registry = mastra ?? ctx?.mastra;
const agent = entry.agent ?? registry?.getAgentById(entry.agentId);
if (!agent) throw new Error(`Agent '${entry.agentId}' not found for workflow step '${entry.id}'. Register the agent on the Mastra instance or pass the agent instance directly.`);
const { retries: _retries, scorers: _scorers, metadata: _metadata, ...agentOptions } = entry.options ?? {};
const { inputData, runId, [PUBSUB_SYMBOL]: pubsub, [STREAM_FORMAT_SYMBOL]: streamFormat, requestContext, abortSignal, abort, writer, ...rest } = ctx;
const observabilityContext = resolveObservabilityContext(rest);
let streamPromise = {};
streamPromise.promise = new Promise((resolve, reject) => {
streamPromise.resolve = resolve;
streamPromise.reject = reject;
});
streamPromise.promise.catch(() => {});
let structuredResult = null;
const toolData = {
name: agent.name,
args: inputData
};
let stream;
const handleFinish = (result) => {
const resultWithObject = result;
if (agentOptions?.structuredOutput?.schema && resultWithObject.object) structuredResult = resultWithObject.object;
streamPromise.resolve(result.text);
agentOptions?.onFinish?.(result);
};
if ((await agent.getModel({ requestContext })).specificationVersion === "v1" && typeof agent.streamLegacy === "function") {
const { fullStream } = await agent.streamLegacy(inputData.prompt, {
...agentOptions,
requestContext,
...observabilityContext,
onFinish: handleFinish,
abortSignal
});
stream = fullStream;
} else {
const modelOutput = await agent.stream(inputData.prompt, {
...agentOptions,
requestContext,
...observabilityContext,
onFinish: handleFinish,
abortSignal
});
modelOutput.text.then(() => {}, (err) => streamPromise.reject(err));
stream = modelOutput.fullStream;
}
const tripwireChunk = streamFormat === "legacy" ? await bridgeLegacyWatchEvents({
stream,
pubsub,
runId,
toolData
}) : await consumeStreamForTripwire(stream, writer);
if (tripwireChunk) throw new TripWire(tripwireChunk.payload?.reason || "Agent tripwire triggered", {
retry: tripwireChunk.payload?.retry,
metadata: tripwireChunk.payload?.metadata
}, tripwireChunk.payload?.processorId);
if (abortSignal.aborted) return abort();
if (structuredResult !== null) return structuredResult;
return { text: await streamPromise.promise };
}
/**
* Legacy-format watch-event bridge: instead of forwarding chunks to the step
* writer, mirrors the agent stream onto the run's pubsub watch channel as
* `tool-call-streaming-*` / `tool-call-delta` events (the shape v1 watchers
* expect). Returns the tripwire chunk if one was seen, else `null`.
*/
async function bridgeLegacyWatchEvents({ stream, pubsub, runId, toolData }) {
let tripwireChunk = null;
await pubsub.publish(`workflow.events.v2.${runId}`, {
type: "watch",
runId,
data: {
type: "tool-call-streaming-start",
...toolData ?? {}
}
});
try {
for await (const chunk of stream) {
if (chunk.type === "tripwire") {
tripwireChunk = chunk;
break;
}
if (chunk.type === "text-delta") await pubsub.publish(`workflow.events.v2.${runId}`, {
type: "watch",
runId,
data: {
type: "tool-call-delta",
...toolData ?? {},
argsTextDelta: chunk.textDelta
}
});
}
} finally {
await pubsub.publish(`workflow.events.v2.${runId}`, {
type: "watch",
runId,
data: {
type: "tool-call-streaming-finish",
...toolData ?? {}
}
}).catch(() => {});
}
return tripwireChunk;
}
/**
* Forwards every chunk to the step writer, stopping early when a tripwire
* chunk appears. Returns the tripwire chunk if one was seen, else `null`.
*/
async function consumeStreamForTripwire(stream, writer) {
for await (const chunk of stream) {
await forwardAgentStreamChunk({
writer,
chunk
});
if (chunk.type === "tripwire") return chunk;
}
return null;
}
//#endregion
//#region src/workflows/entry-executors/run-tool-entry.ts
/**
* Runs a declarative `tool` entry: resolves the tool (inline handle, else the
* Mastra registry) and executes it with the step context mapped into the tool
* execution context.
*/
async function runToolEntry(entry, ctx, mastra) {
const registry = mastra ?? ctx?.mastra;
const tool = entry.tool ?? registry?.getTool(entry.toolId);
if (!tool) throw new Error(`Tool '${entry.toolId}' not found for workflow step '${entry.id}'. Pass the tool instance directly.`);
const { inputData, mastra: ctxMastra, requestContext, suspend, resumeData, runId, workflowId, state, setState, abortSignal, ...rest } = ctx;
const toolContext = {
mastra: ctxMastra,
requestContext,
...resolveObservabilityContext(rest),
abortSignal,
resumeData,
workflow: {
runId,
suspend,
resumeData,
workflowId,
state,
setState
}
};
return tool.execute(inputData, toolContext);
}
//#endregion
//#region src/workflows/mapping-template.ts
/**
* The `${scope.path}` mapping-template DSL used by `.map()` template sources.
*
* Definition-time syntax checks live in {@link validateTemplate}; run-time
* resolution (path lookup + value coercion) lives in {@link resolveTemplate}.
* This module has no knowledge of the step-entry union — it is a pure
* string-DSL interpreter over a step's execute context.
*/
/** Walks a dotted path on an object. `''` or `'.'` returns the root unchanged. */
function traverseMappingPath(root, path, errorLabel) {
if (path === "" || path === ".") return root;
const parts = path.split(".");
let value = root;
for (const part of parts) if (typeof value === "object" && value !== null) value = value[part];
else throw new Error(`Invalid path ${path} in ${errorLabel}`);
return value;
}
const TEMPLATE_PLACEHOLDER = /\$\{([^}]*)\}/g;
const TEMPLATE_NAMESPACES = [
"inputData",
"initData",
"state",
"requestContext",
"stepResults"
];
/** Common error-message prefix so every template diagnostic points at the exact placeholder. */
function describeBadPlaceholder(template, idx, rawExpr) {
return `Template placeholder #${idx} (\${${rawExpr}}) in '${template}'`;
}
/** Split a placeholder body `scope.path.with.dots` into its leading scope and the dotted remainder. */
function parseTemplatePlaceholder(rawExpr) {
const dot = rawExpr.indexOf(".");
return {
scope: dot === -1 ? rawExpr : rawExpr.slice(0, dot),
rest: dot === -1 ? "" : rawExpr.slice(dot + 1)
};
}
/**
* Validates a `{ template }` source's syntax at workflow-definition time.
* Throws if any placeholder is empty, whitespace-padded, references an unknown
* namespace, or is a malformed `stepResults.<stepId>` / `stepResults.<stepId>.<path>` shape.
*
* Run-time concerns (does the step actually exist, does the path resolve, is
* the value a primitive) stay in {@link resolveTemplate}.
*/
function validateTemplate(template) {
let idx = 0;
for (const match of template.matchAll(TEMPLATE_PLACEHOLDER)) {
idx++;
const rawExpr = match[1] ?? "";
if (rawExpr.length === 0 || rawExpr !== rawExpr.trim()) throw new Error(`${describeBadPlaceholder(template, idx, rawExpr)} has empty or whitespace-padded contents. Use \${<scope>.<path>} with no surrounding whitespace.`);
const { scope, rest } = parseTemplatePlaceholder(rawExpr);
if (scope === "stepResults") {
const innerDot = rest.indexOf(".");
if (!(innerDot === -1 ? rest : rest.slice(0, innerDot))) throw new Error(`${describeBadPlaceholder(template, idx, rawExpr)} must be of the form \${stepResults.<stepId>} or \${stepResults.<stepId>.<path>}.`);
continue;
}
if (scope === "requestContext") {
if (!rest) throw new Error(`${describeBadPlaceholder(template, idx, rawExpr)} requires a request-context key — use \${requestContext.<key>}.`);
continue;
}
if (TEMPLATE_NAMESPACES.includes(scope)) continue;
throw new Error(`${describeBadPlaceholder(template, idx, rawExpr)} references unknown namespace "${scope}". Use one of: ${TEMPLATE_NAMESPACES.join(", ")}.`);
}
}
/**
* Collects the step ids referenced by `${stepResults.<stepId>}` /
* `${stepResults.<stepId>.<path>}` placeholders in a template. Assumes the
* template already passed {@link validateTemplate}; malformed placeholders are
* skipped. Used by validation to scope-check template references against the
* preceding workflow-local steps.
*/
function collectTemplateStepIds(template) {
const ids = [];
for (const match of template.matchAll(TEMPLATE_PLACEHOLDER)) {
const { scope, rest } = parseTemplatePlaceholder(match[1] ?? "");
if (scope !== "stepResults") continue;
const innerDot = rest.indexOf(".");
const stepId = innerDot === -1 ? rest : rest.slice(0, innerDot);
if (stepId) ids.push(stepId);
}
return ids;
}
/**
* Coerces a resolved placeholder value to a string. Primitives are stringified
* the normal way; objects and arrays are JSON-encoded so downstream agents can
* consume complex step outputs (e.g. `foreach(agent)` returns `{ text }[]`)
* directly in a template. `null`/`undefined` render as empty. If JSON encoding
* fails (circular references, BigInt, etc.), throws with a hint pointing at
* the offending placeholder.
*/
function stringifyTemplateValue(v, template, idx, rawExpr) {
if (v === null || v === void 0) return "";
if (typeof v === "object") try {
return JSON.stringify(v);
} catch (err) {
throw new Error(`${describeBadPlaceholder(template, idx, rawExpr)} resolved to a value that could not be JSON-stringified (${err.message}). Drill into a primitive path (e.g. \${${rawExpr}.someField}) or reshape the value in a preceding step.`);
}
return String(v);
}
/**
* Resolves `${<scope>.<path>}` placeholders against the implicit namespaces
* available in a step's execute context. See the `.map()` overload signature
* for the full list of accepted scopes (`inputData`, `initData`, `state`,
* `requestContext`, `stepResults.<stepId>`).
*/
function resolveTemplate(template, ctx) {
let idx = 0;
return template.replace(TEMPLATE_PLACEHOLDER, (_match, rawExpr) => {
idx++;
return resolveTemplatePlaceholder(rawExpr, template, idx, ctx);
});
}
function resolveTemplatePlaceholder(rawExpr, template, idx, ctx) {
const { scope, rest } = parseTemplatePlaceholder(rawExpr);
const label = describeBadPlaceholder(template, idx, rawExpr);
switch (scope) {
case "inputData": return stringifyTemplateValue(traverseMappingPath(ctx.inputData, rest, label), template, idx, rawExpr);
case "initData": return stringifyTemplateValue(traverseMappingPath(ctx.getInitData(), rest, label), template, idx, rawExpr);
case "state": return stringifyTemplateValue(traverseMappingPath(ctx.state, rest, label), template, idx, rawExpr);
case "requestContext": return stringifyTemplateValue(ctx.requestContext.get(rest), template, idx, rawExpr);
case "stepResults": {
const innerDot = rest.indexOf(".");
const stepId = innerDot === -1 ? rest : rest.slice(0, innerDot);
const subPath = innerDot === -1 ? "" : rest.slice(innerDot + 1);
const stepResult = ctx.getStepResult(stepId);
if (stepResult == null) throw new Error(`${label} references stepResults.${stepId} but step "${stepId}" has no successful output (not run yet, not registered, failed, or produced no output).`);
return stringifyTemplateValue(traverseMappingPath(stepResult, subPath, label), template, idx, rawExpr);
}
default: throw new Error(`${label} references unknown namespace "${scope}". Use one of: ${TEMPLATE_NAMESPACES.join(", ")}.`);
}
}
//#endregion
//#region src/workflows/entry-executors/run-mapping-entry.ts
/**
* Runs a declarative `mapping` entry. Function configs are invoked directly;
* object configs are interpreted key-by-key (`value` / `fn` / `template` /
* `requestContextPath` / `step`+`path` / `initData`+`path`).
*/
async function runMappingEntry(entry, ctx) {
const { mapConfig } = entry;
if (typeof mapConfig === "function") return mapConfig(ctx);
const { getStepResult, getInitData, requestContext } = ctx;
const result = {};
for (const [key, mapping] of Object.entries(mapConfig)) {
const m = mapping;
if (m.value !== void 0) {
result[key] = m.value;
continue;
}
if (m.fn !== void 0) {
result[key] = await m.fn(ctx);
continue;
}
if (typeof m.template === "string") {
result[key] = resolveTemplate(m.template, ctx);
continue;
}
if (m.requestContextPath) {
result[key] = requestContext.get(m.requestContextPath);
continue;
}
result[key] = traverseMappingPath(m.initData ? getInitData() : getStepResult(Array.isArray(m.step) ? m.step.find((s) => {
const stepRes = getStepResult(s);
if (typeof stepRes === "object" && stepRes !== null) return Object.keys(stepRes).length > 0;
return stepRes;
}) : m.step), m.path, describeMappingSource(m));
}
return result;
}
/** Human-readable source label for path-traversal errors. */
function describeMappingSource(m) {
if (m.initData) return "initData";
const stepLabel = (s) => typeof s === "string" ? s : s?.id ?? "unknown";
if (Array.isArray(m.step)) return `step ${m.step.map(stepLabel).join("|")}`;
return `step ${stepLabel(m.step)}`;
}
//#endregion
//#region src/workflows/evented/workflow-event-processor/utils.ts
function getNestedWorkflow(mastra, { workflowId, executionPath, parentWorkflow, runId }) {
let workflow = null;
if (parentWorkflow) {
const nestedWorkflow = getNestedWorkflow(mastra, parentWorkflow);
if (!nestedWorkflow) return null;
workflow = nestedWorkflow;
}
workflow = workflow ?? (mastra.__hasInternalWorkflow(workflowId, runId) ? mastra.__getInternalWorkflow(workflowId, runId) : mastra.getWorkflow(workflowId));
let parentStep = workflow.stepGraph[executionPath[0]];
if (parentStep?.type === "parallel" || parentStep?.type === "conditional") parentStep = parentStep.steps[executionPath[1]];
if (parentStep?.type === "loop" || parentStep?.type === "foreach") return getEntryWorkflow(parentStep.step);
if (parentStep && isSingleStepEntry(parentStep)) return getEntryWorkflow(parentStep);
return null;
}
/**
* Resolves the single-step entry addressed by an execution path, or null when
* the path doesn't land on a single-step-like entry. For `loop` / `foreach`
* the body entry is returned.
*/
function getStepEntry(workflow, executionPath) {
let parentStep = workflow.stepGraph[executionPath[0]];
if (parentStep?.type === "parallel" || parentStep?.type === "conditional") parentStep = parentStep.steps[executionPath[1]];
if (parentStep?.type === "loop" || parentStep?.type === "foreach") return parentStep.step;
if (parentStep && isSingleStepEntry(parentStep)) return parentStep;
return null;
}
/**
* Resolves the id of the entry addressed by an execution path, or null when the
* path doesn't land on a single-step-like entry. For `loop` / `foreach` the id
* of the body entry is returned.
*/
function getStepId(workflow, executionPath) {
const entry = getStepEntry(workflow, executionPath);
return entry ? getEntryId(entry) : null;
}
function isExecutableStep(step) {
return isSingleStepEntry(step) || step.type === "loop" || step.type === "foreach";
}
//#endregion
//#region src/workflows/evented/helpers.ts
/**
* Helper functions for evented workflow execution.
*/
/**
* Type guard to check if a chunk is a tripwire chunk.
* @param chunk - The chunk to check
* @returns True if the chunk is a TripwireChunk
*/
function isTripwireChunk(chunk) {
return chunk !== null && typeof chunk === "object" && "type" in chunk && chunk.type === "tripwire" && "payload" in chunk;
}
/**
* Creates a TripWire error from a tripwire chunk.
* @param chunk - The tripwire chunk from the stream
* @returns A TripWire error instance
*/
function createTripWireFromChunk(chunk) {
const { payload } = chunk;
return new TripWire(payload.reason || "Agent tripwire triggered", {
retry: payload.retry,
metadata: payload.metadata
}, payload.processorId);
}
/**
* Extracts text delta from a stream chunk, handling V1 vs V2 differences.
*
* V1 (AI SDK v4): Uses `chunk.textDelta` for raw text
* V2 (AI SDK v5): Uses `chunk.payload.text` for normalized text
*
* @param chunk - The stream chunk
* @param isV2Model - Whether this is a V2 model (uses normalized payload)
* @returns The text delta string, or undefined if not a text-delta chunk
*/
function getTextDeltaFromChunk(chunk, isV2Model) {
if (chunk.type !== "text-delta") return;
return isV2Model ? chunk.payload?.text : chunk.textDelta;
}
/**
* Resolves the current workflow state from multiple potential sources.
* Priority order: stepResult.__state > stepResults.__state > state > empty object
*
* @param params - The state sources to check
* @returns The resolved state object
*/
function resolveCurrentState(params) {
const { stepResult, stepResults, state } = params;
return stepResult?.__state ?? stepResults?.__state ?? state ?? {};
}
//#endregion
//#region src/events/processor.ts
var EventProcessor = class {
mastra;
__registerMastra(mastra) {
this.mastra = mastra;
}
constructor({ mastra }) {
this.mastra = mastra;
}
};
//#endregion
//#region src/workflows/evented/step-executor.ts
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 { entry, stepResults, runId, requestContext, retryCount = 0, perStep } = params;
const stepId = getEntryId(entry);
const schemas = getEntrySchemas(entry, this.mastra);
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: schemas,
validateInputs: params.validateInputs ?? true
});
let stepInfo = {
...stepResults[stepId],
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[stepId]?.status === "suspended" ? params.stepResults[stepId]?.suspendPayload : void 0;
if (suspendDataToUse && typeof params.foreachIdx === "number") {
const iterationResult = suspendDataToUse.__workflow_meta?.foreachOutput?.[params.foreachIdx];
if (iterationResult?.status === "suspended" && iterationResult.suspendPayload) suspendDataToUse = iterationResult.suspendPayload;
}
if (suspendDataToUse && "__workflow_meta" in suspendDataToUse) {
const { __workflow_meta, ...userSuspendData } = suspendDataToUse;
suspendDataToUse = userSuspendData;
}
let stateUpdate;
const workflowStepSpan = params.tracingContext?.currentSpan?.createChildSpan({
type: "workflow_step",
name: `workflow step: '${stepId}'`,
entityType: EntityType.WORKFLOW_STEP,
entityId: stepId,
input: inputData,
tracingPolicy: params.tracingPolicy,
requestContext
});
const stepTracingContext = workflowStepSpan ? { currentSpan: workflowStepSpan } : params.tracingContext ?? {};
try {
if (validationError) throw validationError;
const callId = randomUUID();
const outputWriter = this.createOutputWriter(runId);
const stepOutput = await executeWithContext({
span: stepTracingContext.currentSpan,
fn: () => {
const executionContext = 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 } = await validateStepSuspendData({
suspendData: suspendPayload,
step: schemas,
validateInputs: params.validateInputs ?? true
});
if (validationError) throw validationError;
const resumeLabels = {};
if (suspendOptions?.resumeLabel) {
const labels = Array.isArray(suspendOptions.resumeLabel) ? suspendOptions.resumeLabel : [suspendOptions.resumeLabel];
for (const label of labels) resumeLabels[label] = {
stepId,
foreachIndex: params.foreachIdx
};
}
suspended = { payload: {
...suspendData,
__workflow_meta: {
runId,
path: [stepId],
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: stepId,
runId
}, outputWriter),
abort: () => {
abortController?.abort();
},
[PUBSUB_SYMBOL]: this.mastra.pubsub,
[STREAM_FORMAT_SYMBOL]: params.format,
engine: {},
abortSignal: abortController?.signal,
...createObservabilityContext(stepTracingContext)
}, {
paramName: "runCount",
deprecationMessage: runCountDeprecationMessage,
logger: this.logger
});
switch (entry.type) {
case "step": return entry.step.execute(executionContext);
case "agent": return runAgentEntry(entry, executionContext, this.mastra);
case "tool": return runToolEntry(entry, executionContext, this.mastra);
case "mapping": return runMappingEntry(entry, executionContext);
}
}
});
const nestedWflowStepPaused = getEntryComponent(entry) === "WORKFLOW" && 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,
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
};
if (finalResult.status === "success") workflowStepSpan?.end({
output: stepOutput,
attributes: { status: "success" }
});
else workflowStepSpan?.end({ attributes: { status: finalResult.status } });
return finalResult;
} catch (error) {
const endedAt = Date.now();
const errorInstance = getErrorFromUnknown(error, {
serializeStack: false,
fallbackMessage: "Unknown step execution error"
});
workflowStepSpan?.error({ error: errorInstance });
const mastraError = new MastraError({
id: "WORKFLOW_STEP_INVOKE_FAILED",
domain: ErrorDomain.MASTRA_WORKFLOW,
category: ErrorCategory.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,
...error instanceof MastraNonRetryableError && { nonRetryable: true },
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();
return (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;
}
}))).reduce((acc, result, idx) => {
if (result) acc.push(idx);
return acc;
}, []);
}
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,
engine: {},
abortSignal: abortController?.signal,
...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,
engine: {},
abortSignal: abortController?.signal,
...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);
return (await step.fn(createDeprecationProxy({
workflowId: param