@mastra/core
Version:
1,220 lines • 1.33 MB
JavaScript
import { i as __toESM } from "./rolldown-runtime-DP3BCW9_.js";
import { a as RegisteredLogger, t as ConsoleLogger } 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 { t as EventEmitterPubSub } from "./event-emitter-C12mi0dL.js";
import { a as resolveObservabilityContext, i as createObservabilityContext, r as wrapMastra } from "./observability-Cz-X7NF_.js";
import { a as getOrCreateSpan, f as EntityType, n as executeWithContextSync, s as getStepAvailableToolNames, t as executeWithContext } from "./utils-DxsDNzD2.js";
import "./tracing-Bm0k4FBA.js";
import { a as RequestContext, i as MASTRA_VERSIONS_KEY, n as MASTRA_RESOURCE_ID_KEY, r as MASTRA_THREAD_ID_KEY } from "./request-context-p_Tq-4EM.js";
import { a as ModelRouterLanguageModel, d as readModelStreamTransport, i as resolveModelConfig, n as ModelRouterEmbeddingModel } from "./llm-DntEbB3j.js";
import { isStandardSchemaWithJSON, standardSchemaToJSONSchema, toStandardSchema } from "./schema/index.js";
import { n as Tool, r as createTool } from "./tool-qGw4ZhYO.js";
import { i as MastraFGAPermissions } from "./ee-DXvSoTl7-BqTsKmQd.js";
import { a as createBackgroundTask, i as resolveBackgroundConfig, t as generateBackgroundTaskSystemPrompt } from "./background-tasks-6lJjk3_t.js";
import { a as isProviderTool, i as isProviderDefinedTool, n as getProviderToolName, r as isMastraTool, t as getNeedsApprovalFn } from "./toolchecks-BWgiThPN.js";
import { n as ToolStream, t as noopObserve } from "./types-C59tsW89.js";
import { a as ensureToolProperties, b as selectFields, d as makeCoreTool, i as delay, n as createMastraProxy, o as generateEmptyFromSchema, r as deepMerge$1 } from "./utils-CCbB2dG1.js";
import { t as deepEqual$2 } from "./deep-equal-D_JPy4kj.js";
import { ensureSerializable, safeStringify } from "./utils/safe-stringify.js";
import { C as tryGenerateWithJsonFallback, E as isProcessorWorkflow, S as supportedLanguageModelSpecifications, _ as summarizeProcessorToolsForSpan, a as escapeUnescapedControlCharsInJsonStrings, b as isSupportedLanguageModel, c as safeClose, d as ProcessorState, g as summarizeProcessorResultForSpan, h as summarizeProcessorModelForSpan, i as createDestructurableOutput, l as safeEnqueue, m as summarizeActiveToolsForSpan, n as getModelOutputForTripwire, o as getResponseFormat, p as REPROCESS_PART_KEY, r as MastraModelOutput, s as MastraModelInput, t as TripWire, u as ProcessorRunner, v as summarizeToolChoiceForSpan, w as tryStreamWithJsonFallback, x as resolveThreadIdFromArgs, y as createProcessorSendSignal } from "./trip-wire-csEv7lz7.js";
import { a as withToolPayloadTransformMetadata, i as transformToolPayloadForTargets, n as hasTransformedToolPayload, o as withToolPayloadTransformProviderMetadata, r as normalizeToolPayloadTransformPolicy, t as getTransformedToolPayload } from "./payload-transform-C4k4-WlM.js";
import { a as isMastraSignalMessage, f as signalToXmlMarkup, o as isTransientSignalMessage, s as mastraDBMessageToSignal } from "./signals-DTzJ08gd.js";
import { n as MessageList, o as messagesAreEqual, p as sanitizeToolName, u as stableStringify } from "./message-list-mC29laJJ.js";
import { c as stepCountIs, h as tool, m as generateId, p as asSchema, s as parsePartialJson, u as APICallError } from "./dist-DIIEuFGB.js";
import { a as convertFullStreamChunkToMastra, c as WorkflowRunOutput, s as MastraAgentNetworkStream } from "./stream-CRtv-q6z.js";
import { n as isObservationalMemoryEnabled$1, r as parseMemoryRequestContext } from "./types-LyOfO-TK.js";
import { s as modelSupportsStructuredOutput } from "./provider-registry-DOB2blrv.js";
import { t as MastraLLMV1 } from "./model-Bj_6H9X0.js";
import { t as GoalSignalProvider } from "./signal-provider-Dtdpbuf1.js";
import { n as validateCron } from "./cron-B2j814dd.js";
import { D as resolveEffectiveGoalSettings, E as readObjective, O as resolveGoalStore, _ as clearCachedGoalObjective, b as GOAL_SCORER_ID, g as cacheGoalObjective, k as writeObjective, v as DEFAULT_GOAL_JUDGE_PROMPT, w as clearObjective, x as GOAL_SCORE_WAITING } from "./task-state-processor-C9agcUfw.js";
import { _n as saveScorePayloadSchema, hn as extractWorkflowTrajectory, mn as extractTrajectoryFromTrace, pn as extractTrajectory } from "./constants-BfpAlX25.js";
import { noopLogger } from "./logger/index.js";
import { PUBSUB_SYMBOL, STREAM_FORMAT_SYMBOL } from "./workflows/constants.js";
import { A as getEntryId, C as runCountDeprecationMessage, D as validateStepStateData, E as validateStepResumeData, M as getStepResult, N as forwardAgentStreamChunk, O as validateStepSuspendData, S as resolveForeachConcurrency, T as validateStepRequestContext, _ as getResumeLabelsByStepId, a as isTripwireChunk, b as hydrateSerializedStepErrors, c as runMappingEntry, d as runToolEntry, f as runAgentEntry, g as createTimeTravelExecutionParams, h as createRestartExecutionParams, i as getTextDeltaFromChunk, k as waitForSuspendedSnapshot, m as createDeprecationProxy, p as cleanStepResult, r as createTripWireFromChunk, s as getStepId, t as WorkflowEventProcessor, u as validateTemplate, v as getSingleStepEntryId, w as validateStepInput, x as isSingleStepEntry } from "./workflow-event-processor-BbED1LMn.js";
import { i as executeHook } from "./hooks-s8qUTtbg.js";
import { G as LocalFilesystem, O as BM25Index, S as createSkillTools, k as LocalSandbox, t as createWorkspaceTools, x as Workspace } from "./workspace-CvZ9jmZN.js";
import { i as normalizeWebSearchProvider, n as createWebSearchProviderTool, r as isWebSearchTool } from "./tools-DdVMYter.js";
import { M as require_fast_deep_equal, s as InMemoryStore } from "./storage-BS3ic0Sd.js";
import { a as createNotificationSummarySignal, h as agentThreadStreamRuntime, i as createNotificationSignal, m as resolveNotificationDeliveryDecision, p as resolveDeliveryFailureUpdate, u as summarizeNotifications } from "./storage-B1u4gxRl.js";
import { t as DefaultStepResult } from "./output-helpers-CBpE9R3e.js";
import { n as resolveAgentSkills, t as mergeWorkspaceSkills } from "./agent-skills-resolver-O3RWfuIN.js";
import { n as DefaultVoice } from "./voice-BWZgAbg7.js";
import * as crypto$1 from "crypto";
import { createHash, randomUUID } from "crypto";
import { z } from "zod/v4";
import { prepareJsonSchemaForOpenAIStrictMode, wrapSchemaWithNullTransform } from "@mastra/schema-compat";
import { ReadableStream as ReadableStream$1, TransformStream } from "stream/web";
import { z as z$1 } from "zod";
import { injectJsonInstructionIntoMessages, isAbortError } from "@ai-sdk/provider-utils-v5";
import { existsSync, readFileSync, statSync } from "fs";
import { basename, dirname, isAbsolute, join, normalize, resolve } from "path";
import fastq from "fastq";
import { estimateTokenCount, sliceByTokens } from "tokenx";
import xxhash from "xxhash-wasm";
import { LRUCache } from "lru-cache";
//#region src/processors/processors/unicode-normalizer.ts
var UnicodeNormalizer = class {
id = "unicode-normalizer";
name = "Unicode Normalizer";
options;
constructor(options = {}) {
this.options = {
stripControlChars: options.stripControlChars ?? false,
preserveEmojis: options.preserveEmojis ?? true,
collapseWhitespace: options.collapseWhitespace ?? true,
trim: options.trim ?? true
};
}
processInput(args) {
try {
return args.messages.map((message) => ({
...message,
content: {
...message.content,
parts: message.content.parts?.map((part) => {
if (part.type === "text" && "text" in part && typeof part.text === "string") return {
...part,
text: this.normalizeText(part.text)
};
return part;
}),
content: typeof message.content.content === "string" ? this.normalizeText(message.content.content) : message.content.content
}
}));
} catch {
return args.messages;
}
}
normalizeText(text) {
let normalized = text;
normalized = normalized.normalize("NFKC");
if (this.options.stripControlChars) if (this.options.preserveEmojis) normalized = normalized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F]/g, "");
else normalized = normalized.replace(/[^\x09\x0A\x0D\x20-\x7E\u00A0-\uFFFF]/g, "");
if (this.options.collapseWhitespace) {
normalized = normalized.replace(/\r\n/g, "\n");
normalized = normalized.replace(/\r/g, "\n");
normalized = normalized.replace(/\n+/g, "\n");
normalized = normalized.replace(/[ \t]+/g, " ");
}
if (this.options.trim) normalized = normalized.trim();
return normalized;
}
};
//#endregion
//#region src/stream/aisdk/v5/compat/prepare-tools.ts
/**
* Recursively fixes JSON Schema properties that lack a 'type' key.
* Zod v4's toJSONSchema serializes z.any() to just { description: "..." } with no 'type',
* which providers like OpenAI reject. This converts such schemas to a permissive type union.
*/
function fixTypelessProperties(schema) {
if (typeof schema !== "object" || schema === null) return schema;
const result = { ...schema };
if (result.properties && typeof result.properties === "object" && !Array.isArray(result.properties)) result.properties = Object.fromEntries(Object.entries(result.properties).map(([key, value]) => {
if (typeof value !== "object" || value === null || Array.isArray(value)) return [key, value];
const propSchema = value;
const hasType = "type" in propSchema;
const hasRef = "$ref" in propSchema;
const hasAnyOf = "anyOf" in propSchema;
const hasOneOf = "oneOf" in propSchema;
const hasAllOf = "allOf" in propSchema;
if (!hasType && !hasRef && !hasAnyOf && !hasOneOf && !hasAllOf) {
const { items: _items, ...rest } = propSchema;
return [key, {
...rest,
type: [
"string",
"number",
"integer",
"boolean",
"object",
"null"
]
}];
}
return [key, fixTypelessProperties(propSchema)];
}));
if (result.items) {
if (Array.isArray(result.items)) result.items = result.items.map((item) => fixTypelessProperties(item));
else if (typeof result.items === "object") result.items = fixTypelessProperties(result.items);
}
return result;
}
function prepareToolsAndToolChoice({ tools, toolChoice, activeTools, targetVersion = "v2" }) {
if (toolChoice === "none") return {
tools: void 0,
toolChoice: { type: "none" }
};
if (Object.keys(tools || {}).length === 0) return {
tools: void 0,
toolChoice: void 0
};
const filteredTools = activeTools != null ? Object.entries(tools || {}).filter(([name]) => activeTools.includes(name)) : Object.entries(tools || {});
const providerToolType = targetVersion === "v2" ? "provider-defined" : "provider";
return {
tools: filteredTools.map(([name, tool$1]) => {
try {
if (isProviderDefinedTool(tool$1)) {
const toolName = tool$1.name ?? name;
return {
type: providerToolType,
name: toolName,
id: tool$1.id,
args: tool$1.args ?? {}
};
}
let inputSchema;
if ("inputSchema" in tool$1) inputSchema = tool$1.inputSchema;
else if ("parameters" in tool$1) inputSchema = tool$1.parameters;
const sdkTool = tool({
type: "function",
...tool$1,
inputSchema
});
const strict = "strict" in tool$1 ? tool$1.strict : void 0;
const toolType = sdkTool?.type ?? "function";
switch (toolType) {
case void 0:
case "dynamic":
case "function":
let parameters;
if (sdkTool.inputSchema) {
if ("$schema" in sdkTool.inputSchema && typeof sdkTool.inputSchema.$schema === "string" && sdkTool.inputSchema.$schema.startsWith("http://json-schema.org/")) parameters = sdkTool.inputSchema;
else if (isStandardSchemaWithJSON(sdkTool.inputSchema)) parameters = standardSchemaToJSONSchema(sdkTool.inputSchema, {
io: "input",
target: "draft-07"
});
else parameters = asSchema(sdkTool.inputSchema).jsonSchema;
if (parameters && typeof parameters === "object" && "$schema" in parameters && parameters.$schema !== "http://json-schema.org/draft-07/schema#") parameters.$schema = "http://json-schema.org/draft-07/schema#";
} else parameters = {
type: "object",
properties: {},
additionalProperties: false
};
return {
type: "function",
name,
description: sdkTool.description,
inputSchema: fixTypelessProperties(parameters),
...strict != null ? { strict } : {},
providerOptions: sdkTool.providerOptions
};
case "provider-defined": {
const providerId = sdkTool.id;
const providerName = sdkTool.name ?? name;
return {
type: providerToolType,
name: providerName,
id: providerId,
args: sdkTool.args
};
}
default: throw new Error(`Unsupported tool type: ${toolType}`);
}
} catch (e) {
console.error("Error preparing tool", e);
return null;
}
}).filter((tool) => tool !== null),
toolChoice: toolChoice == null ? { type: "auto" } : typeof toolChoice === "string" ? { type: toolChoice } : {
type: "tool",
toolName: toolChoice.toolName
}
};
}
/**
* Serialize a tool set into `ModelToolDefinition[]` for the `tools` attribute
* on MODEL_GENERATION spans, reusing the same conversion the provider request
* goes through so exporters see the schemas the model actually received.
*
* Never throws — tracing must not break model execution. Returns undefined
* when there are no tools or serialization fails.
*/
function getToolDefinitionsForTracing({ tools, toolChoice, activeTools }) {
try {
const { tools: prepared } = prepareToolsAndToolChoice({
tools,
toolChoice,
activeTools
});
if (!prepared?.length) return void 0;
return prepared.map((tool) => tool.type === "function" ? {
type: "function",
name: tool.name,
...tool.description !== void 0 ? { description: tool.description } : {},
parameters: tool.inputSchema
} : {
type: tool.type,
name: tool.name,
id: tool.id
});
} catch {
return;
}
}
//#endregion
//#region src/agent/types.ts
/**
* Type guard to check if an object is a DurableAgentLike wrapper.
*/
function isDurableAgentLike(obj) {
if (!obj) return false;
return typeof obj.id === "string" && typeof obj.name === "string" && "agent" in obj && obj.agent !== null && typeof obj.agent === "object" && typeof obj.agent.id === "string" && typeof obj.stream === "function" && typeof obj.recover === "function" && typeof obj.recoverActiveRuns === "function";
}
//#endregion
//#region src/agent/goal/activity.ts
const activeSegments = /* @__PURE__ */ new Map();
const checkpointedDurations = /* @__PURE__ */ new Map();
const writeQueues = /* @__PURE__ */ new WeakMap();
function objectiveScopeKey(agentId, threadId) {
return `${agentId}:${threadId}`;
}
function segmentKey(agentId, runId) {
return `${agentId}:${runId}`;
}
function normalizeDuration(value) {
return value !== void 0 && Number.isFinite(value) && value >= 0 ? value : 0;
}
function debugFailure(mastra, message, context) {
try {
mastra?.getLogger()?.debug(message, context);
} catch {}
}
function enqueueThreadWrite(store, threadId, write) {
let storeQueues = writeQueues.get(store);
if (!storeQueues) {
storeQueues = /* @__PURE__ */ new Map();
writeQueues.set(store, storeQueues);
}
const next = (storeQueues.get(threadId) ?? Promise.resolve()).catch(() => {}).then(write);
storeQueues.set(threadId, next);
return next.finally(() => {
if (storeQueues.get(threadId) === next) storeQueues.delete(threadId);
});
}
/** Begin an in-process active-pursuit segment for an active thread objective. */
async function beginGoalActivity({ mastra, agentId, threadId, runId, requestContext, now = Date.now }) {
if (!threadId) return;
const key = segmentKey(agentId, runId);
if (activeSegments.has(key)) return;
clearCachedGoalObjective(requestContext);
let store;
let objective;
try {
store = await resolveGoalStore(mastra);
objective = await readObjective(store, threadId);
cacheGoalObjective(requestContext, threadId, objective);
} catch (error) {
debugFailure(mastra, "Failed to begin goal activity tracking", {
error,
agentId,
threadId,
runId
});
return;
}
if (!store || objective?.status !== "active") return;
const objectiveId = objective.id ?? objective.objective;
checkpointedDurations.set(objectiveScopeKey(agentId, threadId), {
objectiveId,
durationMs: normalizeDuration(objective.activeDurationMs)
});
activeSegments.set(key, {
mastra,
agentId,
threadId,
objectiveId,
startedAt: now(),
store
});
}
/**
* Stop and durably checkpoint an active-pursuit segment. Calling this for an
* already-stopped run is a no-op.
*/
async function stopGoalActivity({ agentId, runId, now = Date.now }) {
const key = segmentKey(agentId, runId);
const segment = activeSegments.get(key);
if (!segment) return;
activeSegments.delete(key);
const stoppedAt = now();
const elapsedMs = Math.max(0, stoppedAt - segment.startedAt);
try {
await enqueueThreadWrite(segment.store, segment.threadId, async () => {
const objective = await readObjective(segment.store, segment.threadId);
if (!objective || (objective.id ?? objective.objective) !== segment.objectiveId) return;
const activeDurationMs = normalizeDuration(objective.activeDurationMs) + elapsedMs;
const updated = {
...objective,
activeDurationMs,
updatedAt: Math.max(objective.updatedAt, stoppedAt)
};
await writeObjective(segment.store, segment.threadId, updated);
checkpointedDurations.set(objectiveScopeKey(segment.agentId, segment.threadId), {
objectiveId: segment.objectiveId,
durationMs: activeDurationMs
});
});
} catch (error) {
debugFailure(segment.mastra, "Failed to persist goal activity duration", {
error,
agentId: segment.agentId,
threadId: segment.threadId,
runId
});
}
}
/** Read the persisted duration plus all live core-owned segments for display. */
function getGoalActivityDurationMs({ agentId, threadId, objectiveId, activeDurationMs, now = Date.now }) {
let durationMs = normalizeDuration(activeDurationMs);
if (!threadId || !objectiveId) return durationMs;
const checkpoint = checkpointedDurations.get(objectiveScopeKey(agentId, threadId));
if (checkpoint?.objectiveId === objectiveId) durationMs = Math.max(durationMs, checkpoint.durationMs);
for (const segment of activeSegments.values()) if (segment.agentId === agentId && segment.threadId === threadId && segment.objectiveId === objectiveId) durationMs += Math.max(0, now() - segment.startedAt);
return durationMs;
}
//#endregion
//#region src/agent/subagent.ts
function isAgentCompatible(input) {
if (typeof input !== "object" || input === null) return false;
const candidate = input;
return typeof candidate.id === "string" && candidate.id.length > 0 && typeof candidate.generate === "function" && typeof candidate.stream === "function" && typeof candidate.getDescription === "function" && typeof candidate.getModel === "function" && typeof candidate.hasOwnMemory === "function" && typeof candidate.__setMemory === "function" && typeof candidate.getMemory === "function" && typeof candidate.getInstructions === "function" && typeof candidate.resumeGenerate === "function" && typeof candidate.resumeStream === "function";
}
//#endregion
//#region src/processors/step-schema.ts
/**
* Text part in a message
*/
const TextPartSchema = z.object({
type: z.literal("text"),
text: z.string()
}).passthrough();
/**
* Image part in a message
*/
const ImagePartSchema = z.object({
type: z.literal("image"),
image: z.union([
z.string(),
z.instanceof(URL),
z.instanceof(Uint8Array)
]),
mimeType: z.string().optional()
}).passthrough();
/**
* File part in a message
*/
const FilePartSchema = z.object({
type: z.literal("file"),
data: z.union([
z.string(),
z.instanceof(URL),
z.instanceof(Uint8Array)
]),
mimeType: z.string()
}).passthrough();
/**
* Tool invocation part in a message (covers tool-call states)
*/
const ToolInvocationPartSchema = z.object({
type: z.literal("tool-invocation"),
toolInvocation: z.object({
toolCallId: z.string(),
toolName: z.string(),
args: z.unknown(),
state: z.enum([
"partial-call",
"call",
"result"
]),
result: z.unknown().optional()
})
}).passthrough();
/**
* Reasoning part in a message (for models that support reasoning)
*/
const ReasoningPartSchema = z.object({
type: z.literal("reasoning"),
reasoning: z.string(),
details: z.array(z.object({
type: z.enum(["text", "redacted"]),
text: z.string().optional(),
data: z.string().optional()
}))
}).passthrough();
/**
* Source part in a message (for citations/references)
*/
const SourcePartSchema = z.object({
type: z.literal("source"),
source: z.object({
sourceType: z.string(),
id: z.string(),
url: z.string().optional(),
title: z.string().optional()
})
}).passthrough();
/**
* Step start part (marks the beginning of a step in multi-step responses)
*/
const StepStartPartSchema = z.object({ type: z.literal("step-start") }).passthrough();
/**
* Custom data part (for data-* custom parts from AI SDK writer.custom())
* This uses a regex to match any type starting with "data-"
*/
const DataPartSchema = z.object({
type: z.string().refine((t) => t.startsWith("data-"), { message: "Type must start with \"data-\"" }),
id: z.string().optional(),
data: z.unknown().optional()
}).passthrough();
/**
* Union of all message part types.
* Uses passthrough to allow additional fields from the AI SDK.
* Note: We can't use discriminatedUnion here because DataPartSchema uses a regex pattern.
*/
const MessagePartSchema = z.union([
TextPartSchema,
ImagePartSchema,
FilePartSchema,
ToolInvocationPartSchema,
ReasoningPartSchema,
SourcePartSchema,
StepStartPartSchema,
DataPartSchema
]);
/**
* Message content structure (MastraMessageContentV2 format)
* This is a documentation-friendly schema with properly typed parts.
*/
const MessageContentSchema = z.object({
/** Format version - 2 corresponds to AI SDK v4 UIMessage format */
format: z.literal(2),
/** Array of message parts (text, images, tool calls, etc.) */
parts: z.array(MessagePartSchema),
/** Legacy content field for backwards compatibility */
content: z.string().optional(),
/** Additional metadata */
metadata: z.record(z.string(), z.unknown()).optional(),
/** Provider-specific metadata */
providerMetadata: z.record(z.string(), z.unknown()).optional()
});
/**
* Schema for message content in processor workflows.
* Uses the MessagePartSchema discriminated union for proper UI rendering.
*/
const ProcessorMessageContentSchema = z.object({
/** Format version - 2 corresponds to AI SDK v4 UIMessage format */
format: z.literal(2),
/** Array of message parts (text, images, tool calls, etc.) */
parts: z.array(MessagePartSchema),
/** Legacy content field for backwards compatibility */
content: z.string().optional(),
/** Additional metadata */
metadata: z.record(z.string(), z.unknown()).optional(),
/** Provider-specific metadata */
providerMetadata: z.record(z.string(), z.unknown()).optional()
}).passthrough();
/**
* Schema for a message in the processor workflow.
* This represents MastraDBMessage with properly typed fields for UI usage.
*
* Key fields:
* - id: string - Unique message identifier
* - role: 'user' | 'assistant' | 'system' - Message role
* - createdAt: Date - When the message was created
* - threadId?: string - Thread identifier for conversation grouping
* - resourceId?: string - Resource identifier
* - type?: string - Message type
* - content: Message content with parts array
*/
const ProcessorMessageSchema = z.object({
/** Unique message identifier */
id: z.string(),
/** Message role */
role: z.enum([
"user",
"assistant",
"system",
"tool",
"signal"
]),
/** When the message was created */
createdAt: z.coerce.date(),
/** Thread identifier for conversation grouping */
threadId: z.string().optional(),
/** Resource identifier */
resourceId: z.string().optional(),
/** Message type */
type: z.string().optional(),
/** Message content with parts */
content: ProcessorMessageContentSchema
}).passthrough();
/**
* MessageList instance for managing message sources.
* Required for processors that need to mutate the message list.
*/
const messageListSchema = z.custom().describe("MessageList instance for managing message sources");
/**
* The messages to be processed.
* Format is MastraDBMessage[] - use ProcessorMessage type for TypeScript.
*/
const messagesSchema = z.array(ProcessorMessageSchema);
/**
* Schema for system message content parts (CoreSystemMessage format)
* System messages can have text parts or experimental provider extensions
*/
const SystemMessageTextPartSchema = z.object({
type: z.literal("text"),
text: z.string()
}).passthrough();
z.object({
role: z.literal("system"),
content: z.union([z.string(), z.array(SystemMessageTextPartSchema)]),
/** Optional experimental provider-specific extensions */
experimental_providerMetadata: z.record(z.string(), z.unknown()).optional()
}).passthrough();
/**
* Schema for CoreMessage (any message type from AI SDK)
* This is a more permissive schema for runtime flexibility.
*/
const CoreMessageSchema = z.object({
role: z.enum([
"system",
"user",
"assistant",
"tool"
]),
content: z.unknown()
}).passthrough();
/**
* System messages for context.
* These are CoreMessage types from the AI SDK, typically system messages
* but may include other message types in some contexts.
*/
const systemMessagesSchema = z.array(CoreMessageSchema);
/**
* Tool call schema for processOutputStep
*/
const toolCallSchema = z.object({
toolName: z.string(),
toolCallId: z.string(),
args: z.unknown()
});
/**
* Number of times processors have triggered retry for this generation.
*/
const retryCountSchema = z.number().optional();
/**
* Schema for 'input' phase - processInput
* Processes input messages before they are sent to the LLM (once at the start)
*/
const ProcessorInputPhaseSchema = z.object({
phase: z.literal("input"),
messages: messagesSchema,
messageList: messageListSchema,
systemMessages: systemMessagesSchema.optional(),
retryCount: retryCountSchema
});
/**
* Schema for 'inputStep' phase - processInputStep
* Processes input messages at each step of the agentic loop.
* Includes model/tools configuration that can be modified per-step.
*/
const ProcessorInputStepPhaseSchema = z.object({
phase: z.literal("inputStep"),
messages: messagesSchema,
messageList: messageListSchema,
stepNumber: z.number().describe("The current step number (0-indexed)"),
systemMessages: systemMessagesSchema.optional(),
retryCount: retryCountSchema,
messageId: z.string().optional().describe("The active assistant response message ID for this step"),
rotateResponseMessageId: z.custom().optional().describe("Rotate the active assistant response message ID when supported by the caller"),
model: z.custom().optional().describe("Current model for this step"),
tools: z.custom().optional().describe("Current tools available for this step"),
toolChoice: z.custom().optional().describe("Current tool choice setting"),
activeTools: z.array(z.string()).optional().describe("Currently active tools"),
providerOptions: z.custom().optional().describe("Provider-specific options"),
modelSettings: z.custom().optional().describe("Model settings (temperature, etc.)"),
structuredOutput: z.custom().optional().describe("Structured output configuration"),
steps: z.custom().optional().describe("Results from previous steps")
});
/**
* Schema for 'outputStream' phase - processOutputStream
* Processes output stream chunks with built-in state management
*/
const ProcessorOutputStreamPhaseSchema = z.object({
phase: z.literal("outputStream"),
part: z.unknown().nullable().describe("The current chunk being processed. Can be null to skip."),
streamParts: z.array(z.unknown()).describe("All chunks seen so far"),
state: z.record(z.string(), z.unknown()).describe("Mutable state object that persists across chunks"),
messageList: messageListSchema.optional(),
retryCount: retryCountSchema
});
/**
* Schema for 'outputResult' phase - processOutputResult
* Processes the complete output result after streaming/generate is finished
*/
const outputResultSchema = z.object({
text: z.string().describe("The accumulated text from all steps"),
usage: z.record(z.string(), z.unknown()).describe("Token usage (cumulative across all steps)"),
finishReason: z.string().describe("Why the generation finished"),
steps: z.array(z.unknown()).describe("All LLM step results")
});
const ProcessorOutputResultPhaseSchema = z.object({
phase: z.literal("outputResult"),
messages: messagesSchema,
messageList: messageListSchema,
retryCount: retryCountSchema,
result: outputResultSchema.optional()
});
/**
* Schema for 'outputStep' phase - processOutputStep
* Processes output after each LLM response in the agentic loop, before tool execution
*/
const ProcessorOutputStepPhaseSchema = z.object({
phase: z.literal("outputStep"),
messages: messagesSchema,
messageList: messageListSchema,
stepNumber: z.number().describe("The current step number (0-indexed)"),
finishReason: z.string().optional().describe("The finish reason from the LLM (stop, tool-use, length, etc.)"),
providerMetadata: z.record(z.string(), z.unknown()).optional().describe("Provider-specific metadata for the step (e.g. Bedrock guardrail trace under bedrock.trace.guardrail)"),
toolCalls: z.array(toolCallSchema).optional().describe("Tool calls made in this step (if any)"),
text: z.string().optional().describe("Generated text from this step"),
usage: z.record(z.string(), z.unknown()).optional().describe("Token usage for the current step (inputTokens, outputTokens, totalTokens, etc.)"),
systemMessages: systemMessagesSchema.optional(),
retryCount: retryCountSchema
});
/**
* Schema for 'toolResult' phase - processToolResult
* Processes a tool's result after tool.execute() returns successfully and
* before the result is added to the message list / fed to the next LLM call.
*/
const ProcessorToolResultPhaseSchema = z.object({
phase: z.literal("toolResult"),
messages: messagesSchema,
messageList: messageListSchema,
stepNumber: z.number().describe("The current step number (0-indexed)"),
toolName: z.string().describe("Name of the tool that was executed"),
toolCallId: z.string().describe("Unique identifier for this specific tool call"),
args: z.unknown().optional().describe("Arguments the LLM passed to the tool"),
result: z.unknown().optional().describe("Raw value returned by tool.execute() (already serialized)"),
providerExecuted: z.boolean().optional().describe("Whether this result came from a provider-executed tool (e.g. Anthropic web_search)"),
systemMessages: systemMessagesSchema.optional(),
steps: z.custom().optional().describe("Results from previous steps"),
retryCount: retryCountSchema
});
/**
* Discriminated union schema for processor step input in workflows.
*
* This schema uses a discriminated union based on the `phase` field,
* which determines what other fields are required/available.
* This makes it much clearer what data is needed for each phase
* and provides better UX in the playground UI.
*
* Phases:
* - 'input': Process input messages before LLM (once at start)
* - 'inputStep': Process input messages at each agentic loop step
* - 'outputStream': Process streaming chunks
* - 'outputResult': Process complete output after streaming
* - 'outputStep': Process output after each LLM response (before tools)
* - 'toolResult': Process a tool's result after tool.execute() (before next LLM call)
*/
const ProcessorStepInputSchema = z.discriminatedUnion("phase", [
ProcessorInputPhaseSchema,
ProcessorInputStepPhaseSchema,
ProcessorOutputStreamPhaseSchema,
ProcessorOutputResultPhaseSchema,
ProcessorOutputStepPhaseSchema,
ProcessorToolResultPhaseSchema
]);
/**
* Output schema for processor step data in workflows.
*
* This is a more flexible schema that allows all fields to be optional
* since the output from one phase may need to be passed to another.
* The workflow engine handles the type narrowing internally.
*/
const ProcessorStepOutputSchema = z.object({
phase: z.enum([
"input",
"inputStep",
"outputStream",
"outputResult",
"outputStep",
"toolResult"
]),
messages: messagesSchema.optional(),
messageList: messageListSchema.optional(),
systemMessages: systemMessagesSchema.optional(),
stepNumber: z.number().optional(),
part: z.unknown().nullable().optional(),
streamParts: z.array(z.unknown()).optional(),
state: z.record(z.string(), z.unknown()).optional(),
result: outputResultSchema.optional(),
finishReason: z.string().optional(),
toolCalls: z.array(toolCallSchema).optional(),
text: z.string().optional(),
usage: z.record(z.string(), z.unknown()).optional(),
toolName: z.string().optional(),
toolCallId: z.string().optional(),
args: z.unknown().optional(),
toolResultValue: z.unknown().optional(),
providerExecuted: z.boolean().optional(),
retryCount: z.number().optional(),
model: z.custom().optional(),
tools: z.custom().optional(),
toolChoice: z.custom().optional(),
activeTools: z.array(z.string()).optional(),
providerOptions: z.custom().optional(),
modelSettings: z.custom().optional(),
structuredOutput: z.custom().optional(),
steps: z.custom().optional(),
messageId: z.string().optional(),
rotateResponseMessageId: z.custom().optional()
});
/**
* Combined schema that works for both input and output.
* Uses the discriminated union for better type inference.
*/
const ProcessorStepSchema = ProcessorStepInputSchema;
//#endregion
//#region src/workflows/execution-engine.ts
/**
* Execution engine abstract class for building and executing workflow graphs
* Providers will implement this class to provide their own execution logic
*/
var ExecutionEngine = class extends MastraBase {
mastra;
options;
constructor({ mastra, options }) {
super({
name: "ExecutionEngine",
component: RegisteredLogger.WORKFLOW
});
this.mastra = mastra;
this.options = options;
}
__registerMastra(mastra) {
this.mastra = mastra;
const logger = mastra?.getLogger();
if (logger) this.__setLogger(logger);
}
getLogger() {
return this.logger;
}
/**
* Invokes the onFinish and onError lifecycle callbacks if they are defined.
* Errors in callbacks are caught and logged, not propagated.
* @param result The workflow result containing status, result, error, steps, tripwire info, and context
*/
async invokeLifecycleCallbacks(result) {
const { onFinish, onError } = this.options;
const commonContext = {
runId: result.runId,
workflowId: result.workflowId,
resourceId: result.resourceId,
getInitData: () => result.input,
mastra: this.mastra,
requestContext: result.requestContext,
logger: this.logger,
state: result.state,
stepExecutionPath: result.stepExecutionPath
};
if (onFinish) try {
await Promise.resolve(onFinish({
status: result.status,
result: result.result,
error: result.error,
steps: result.steps,
tripwire: result.tripwire,
...commonContext
}));
} catch (err) {
this.logger.error("Error in onFinish callback", { error: err });
}
if (onError && (result.status === "failed" || result.status === "tripwire")) try {
await Promise.resolve(onError({
status: result.status,
error: result.error,
steps: result.steps,
tripwire: result.tripwire,
...commonContext
}));
} catch (err) {
this.logger.error("Error in onError callback", { error: err });
}
}
};
//#endregion
//#region src/workflows/handlers/control-flow.ts
/**
* Runs one child of a parallel/conditional block by dispatching on its step type
* to the matching engine execute method - the same per-type dispatch the engine
* uses for top-level entries.
*/
function executeChildEntry(engine, child, params) {
switch (child.type) {
case "step": return engine.executeStep({
...params,
step: child.step
});
case "agent": return engine.executeAgent({
...params,
entry: child
});
case "tool": return engine.executeTool({
...params,
entry: child
});
case "mapping": return engine.executeMapping({
...params,
entry: child
});
}
}
async function executeParallel(engine, params) {
const { workflowId, runId, resourceId, entry, prevStep, serializedStepGraph, stepResults, resume, restart, timeTravel, executionContext, pubsub, abortController, requestContext, actor, outputWriter, disableScorers, perStep, ...rest } = params;
const observabilityContext = resolveObservabilityContext(rest);
const steps = entry.steps;
const parallelSpan = await engine.createChildSpan({
parentSpan: observabilityContext.tracingContext.currentSpan,
operationId: `workflow.${workflowId}.run.${runId}.parallel.${executionContext.executionPath.join("-")}.span.start`,
options: {
type: "workflow_parallel",
name: `parallel: '${steps.length} branches'`,
input: engine.getStepOutput(stepResults, prevStep),
attributes: {
branchCount: steps.length,
parallelSteps: steps.map((s) => getSingleStepEntryId(s))
},
tracingPolicy: engine.options?.tracingPolicy
},
executionContext
});
const prevOutput = engine.getStepOutput(stepResults, prevStep);
for (const [stepIndex, step] of steps.entries()) {
const stepId = getSingleStepEntryId(step);
let makeStepRunning = true;
if (restart) makeStepRunning = !!restart.activeStepsPath[stepId];
if (timeTravel && timeTravel.executionPath.length > 0) makeStepRunning = timeTravel.steps[0] === stepId;
if (!makeStepRunning) break;
const startTime = resume?.steps[0] === stepId ? void 0 : Date.now();
const resumeTime = resume?.steps[0] === stepId ? Date.now() : void 0;
stepResults[stepId] = {
...stepResults[stepId],
status: "running",
...resumeTime ? { resumePayload: resume?.resumePayload } : { payload: prevOutput },
...startTime ? { startedAt: startTime } : {},
...resumeTime ? { resumedAt: resumeTime } : {}
};
executionContext.activeStepsPath[stepId] = [...executionContext.executionPath, stepIndex];
if (perStep) break;
}
if (timeTravel && timeTravel.executionPath.length > 0) timeTravel.executionPath.shift();
let execResults;
const results = await Promise.all(steps.map(async (step, i) => {
const stepId = getSingleStepEntryId(step);
const currStepResult = stepResults[stepId];
if (currStepResult && currStepResult.status !== "running") return currStepResult;
if (!currStepResult && (perStep || timeTravel)) return {};
const stepExecResult = await executeChildEntry(engine, step, {
workflowId,
runId,
resourceId,
prevOutput,
stepResults,
serializedStepGraph,
restart,
timeTravel,
resume,
executionContext: {
activeStepsPath: executionContext.activeStepsPath,
workflowId,
runId,
executionPath: [...executionContext.executionPath, i],
stepExecutionPath: executionContext.stepExecutionPath,
suspendedPaths: executionContext.suspendedPaths,
resumeLabels: executionContext.resumeLabels,
retryConfig: executionContext.retryConfig,
state: executionContext.state,
tracingIds: executionContext.tracingIds
},
...createObservabilityContext({ currentSpan: parallelSpan }),
pubsub,
abortController,
requestContext,
actor,
outputWriter,
disableScorers,
perStep
});
engine.applyMutableContext(executionContext, stepExecResult.mutableContext);
Object.assign(stepResults, stepExecResult.stepResults);
return stepExecResult.result;
}));
const hasFailed = results.find((result) => result.status === "failed");
const hasSuspended = results.find((result) => result.status === "suspended");
if (hasFailed) execResults = {
status: "failed",
error: hasFailed.error,
tripwire: hasFailed.tripwire
};
else if (hasSuspended) execResults = {
status: "suspended",
suspendPayload: hasSuspended.suspendPayload,
...hasSuspended.suspendOutput ? { suspendOutput: hasSuspended.suspendOutput } : {}
};
else if (abortController?.signal?.aborted) execResults = { status: "canceled" };
else execResults = {
status: "success",
output: results.reduce((acc, result, index) => {
if (result.status === "success") acc[getSingleStepEntryId(steps[index])] = result.output;
return acc;
}, {})
};
if (execResults.status === "failed") await engine.errorChildSpan({
span: parallelSpan,
operationId: `workflow.${workflowId}.run.${runId}.parallel.${executionContext.executionPath.join("-")}.span.error`,
errorOptions: { error: execResults.error }
});
else await engine.endChildSpan({
span: parallelSpan,
operationId: `workflow.${workflowId}.run.${runId}.parallel.${executionContext.executionPath.join("-")}.span.end`,
endOptions: { output: execResults.output || execResults }
});
return execResults;
}
async function executeConditional(engine, params) {
const { workflowId, runId, resourceId, entry, prevOutput, serializedStepGraph, stepResults, resume, restart, timeTravel, executionContext, pubsub, abortController, requestContext, actor, outputWriter, disableScorers, perStep, ...rest } = params;
const observabilityContext = resolveObservabilityContext(rest);
const steps = entry.steps;
const conditionalSpan = await engine.createChildSpan({
parentSpan: observabilityContext.tracingContext.currentSpan,
operationId: `workflow.${workflowId}.run.${runId}.conditional.${executionContext.executionPath.join("-")}.span.start`,
options: {
type: "workflow_conditional",
name: `conditional: '${entry.conditions.length} conditions'`,
input: prevOutput,
attributes: { conditionCount: entry.conditions.length },
tracingPolicy: engine.options?.tracingPolicy
},
executionContext
});
let execResults;
const truthyIndexes = (await Promise.all(entry.conditions.map(async (cond, index) => {
const evalSpan = await engine.createChildSpan({
parentSpan: conditionalSpan,
operationId: `workflow.${workflowId}.run.${runId}.conditional.${executionContext.executionPath.join("-")}.eval.${index}.span.start`,
options: {
type: "workflow_conditional_eval",
name: `condition '${index}'`,
input: prevOutput,
attributes: { conditionIndex: index },
tracingPolicy: engine.options?.tracingPolicy
},
executionContext
});
const operationId = `workflow.${workflowId}.conditional.${index}`;
const context = createDeprecationProxy({
runId,
workflowId,
mastra: engine.mastra,
requestContext,
actor,
inputData: prevOutput,
state: executionContext.state,
retryCount: -1,
...createObservabilityContext({ currentSpan: evalSpan }),
getInitData: () => stepResults?.input,
getStepResult: getStepResult.bind(null, stepResults),
bail: (() => {}),
abort: () => {
abortController?.abort();
},
[PUBSUB_SYMBOL]: pubsub,
[STREAM_FORMAT_SYMBOL]: executionContext.format,
engine: engine.getEngineContext(),
abortSignal: abortController?.signal,
writer: new ToolStream({
prefix: "workflow-step",
callId: randomUUID(),
name: "conditional",
runId
}, outputWriter)
}, {
paramName: "runCount",
deprecationMessage: runCountDeprecationMessage,
logger: engine.getLogger()
});
try {
const result = await engine.evaluateCondition(cond, index, context, operationId);
await engine.endChildSpan({
span: evalSpan,
operationId: `workflow.${workflowId}.run.${runId}.conditional.${executionContext.executionPath.join("-")}.eval.${index}.span.end`,
endOptions: {
output: result !== null,
attributes: { result: result !== null }
}
});
return result;
} catch (e) {
const errorInstance = getErrorFromUnknown(e, { serializeStack: false });
const mastraError = new MastraError({
id: "WORKFLOW_CONDITION_EVALUATION_FAILED",
domain: ErrorDomain.MASTRA_WORKFLOW,
category: ErrorCategory.USER,
details: {
workflowId,
runId
}
}, errorInstance);
engine.getLogger()?.trackException(mastraError);
engine.getLogger()?.error("Error evaluating condition: " + errorInstance.stack);
await engine.errorChildSpan({
span: evalSpan,
operationId: `workflow.${workflowId}.run.${runId}.conditional.${executionContext.executionPath.join("-")}.eval.${index}.span.error`,
errorOptions: {
error: mastraError,
attributes: { result: false }
}
});
return null;
}
}))).filter((index) => index !== null);
let stepsToRun = steps.filter((_, index) => truthyIndexes.includes(index));
if (perStep || timeTravel && timeTravel.executionPath.length > 0) {
const possibleStepToRun = stepsToRun.filter((s) => {
const currStepResult = stepResults[getSingleStepEntryId(s)];
if (timeTravel && timeTravel.executionPath.length > 0) return timeTravel.steps[0] === getSingleStepEntryId(s);
return !currStepResult;
})?.[0];
stepsToRun = possibleStepToRun ? [possibleStepToRun] : stepsToRun;
}
if (timeTravel && timeTravel.executionPath.length > 0) entry.steps.forEach((armEntry, index) => {
if (truthyIndexes.includes(index)) return;
const armId = getSingleStepEntryId(armEntry);
const existing = stepResults[armId];
if (existing?.status !== "running") return;
stepResults[armId] = {
status: "skipped",
payload: existing.payload ?? {},
startedAt: existing.startedAt ?? Date.now(),
endedAt: Date.now()
};
});
conditionalSpan?.update({ attributes: {
truthyIndexes,
selectedSteps: stepsToRun.map((s) => getSingleStepEntryId(s))
} });
const results = await Promise.all(stepsToRun.map(async (step) => {
const stepId = getSingleStepEntryId(step);
const currStepResult = stepResults[stepId];
const isRestartStep = restart ? !!restart.activeStepsPath[stepId] : void 0;
if (currStepResult && timeTravel && timeTravel.executionPath.length > 0) {
if (timeTravel.steps[0] !== stepId) return currStepResult;
}
if (currStepResult && ["success", "failed"].includes(currStepResult.status) && isRestartStep === void 0) return currStepResult;
const stepExecResult = await executeChildEntry(engine, step, {
workflowId,
runId,
resourceId,
prevOutput,
stepResults,
serializedStepGraph,
resume,
restart,
timeTravel,
executionContext: {
workflowId,
runId,
executionPath: [...executionContext.executionPath, steps.indexOf(step)],
stepExecutionPath: executionContext.stepExecutionPath,
activeStepsPath: executionContext.activeStepsPath,
suspendedPaths: executionContext.suspendedPaths,
resumeLabels: executionContext.resumeLabels,
retryConfig: executionContext.retryConfig,
state: executionContext.state,
tracingIds: executionContext.tracingIds
},
...createObservabilityContext({ currentSpan: conditionalSpan }),
pubsub,
abortController,
requestContext,
actor,
outputWriter,
disableScorers,
perStep
});
engine.applyMutableContext(executionContext, stepExecResult.mutableContext);
Object.assign(stepResults, stepExecResult.stepResults);
return stepExecResult.result;
}));
const hasFailed = results.find((result) => result.status === "failed");
const hasSuspended = results.find((result) => result.status === "suspended");
if (hasFailed) execResults = {
status: "failed",
error: hasFailed.error,
tripwire: hasFailed.tripwire
};
else if (hasSuspended) execResults = {
status: "suspended",
suspendPayload: hasSuspended.suspendPayload,
...hasSuspended.suspendOutput ? { suspendOutput: hasSuspended.suspendOutput } : {},
suspendedAt: hasSuspended.suspendedAt
};
else if (abortController?.signal?.aborted) execResults = { status: "canceled" };
else execResults = {
status: "success",
output: results.reduce((acc, result, index) => {
if (result.status === "success") acc[getSingleStepEntryId(stepsToRun[index])] = result.output;
return acc;
}, {})
};
if (execResults.status === "failed") await engine.errorChildSpan({
span: conditionalSpan,
operationId: `workflow.${workflowId}.run.${runId}.conditional.${executionContext.executionPath.join("-")}.span.error`,
errorOptions: { error: execResults.error }
});
else await engine.endChildSpan({
span: conditionalSpan,
operationId: `workflow.${workflowId}.run.${runId}.conditional.${executionContext.executionPath.join("-")}.span.end`,
endOptions: { output: execResults.output || execResults }
});
return execResults;
}
async function executeLoop(engine, params) {
const { workflowId, runId, resourceId, entry, prevOutput, stepResults, resume, restart, timeTravel, executionContext, pubsub, abortController, requestContext, actor, outputWriter, disableScorers, serializedStepGraph, perStep, ...rest } = params;
const observabilityContext = resolveObservabilityContext(rest);
const { step, condition } = entry;
const stepId = getEntryId(step);
const loopSpan = await engine.createChildSpan({
parentSpan: observabilityContext.tracingContext.currentSpan,
operationId: `workflow.${workflowId}.run.${runId}.loop.${executionContext.executionPath.join("-")}.span.start`,
options: {
type: "workflow_loop",
name: `loop: '${entry.loopType}'`,
input: prevOutput,
attributes: { loopType: entry.loopType },
tracingPolicy: engine.options?.tracingPolicy
},
executionContext
});
let isTrue = true;
const prevIterationCount = stepResults[stepId]?.metadata?.iterationCount;
let iteration = prevIterationCount ? prevIterationCount - 1 : 0;
const prevStepResult = stepResults[stepId];
let result = {
status: "success",
output: prevStepResult && Object.prototype.hasOwnProperty.call(prevStepResult, "payload") ? prevStepResult.payload : prevOutput
};
let currentResume = resume;
let currentRestart = restart;
let currentTimeTravel = timeTravel;
do {
if (abortController?.signal?.aborted) {
await engine.endChildSpan({
span: loopSpan,
operationId: `workflow.${workflowId}.run.${runId}.loop.${executionContext.executionPath.join("-")}.span.end.early`,
endOptions: { attributes: { totalIterations: