UNPKG

@mastra/core

Version:
1,264 lines (1,263 loc) 1.38 MB
const require_rolldown_runtime = require("./rolldown-runtime-uwYp4b74.cjs"); const require_logger = require("./logger-BPclhj7J.cjs"); const require_base = require("./base-B6soWsYg.cjs"); const require_error = require("./error-B-e62x-A.cjs"); const require_event_emitter = require("./event-emitter-80LrFIBS.cjs"); const require_observability = require("./observability-BzV5axz0.cjs"); const require_utils = require("./utils-CNiGU0Uf.cjs"); require("./tracing-BUrUJwCM.cjs"); const require_request_context = require("./request-context-ByoZMp-j.cjs"); const require_llm = require("./llm-CmflaXHA.cjs"); const require_tool = require("./tool-d85xHVkl.cjs"); const require_ee_DXvSoTl7 = require("./ee-DXvSoTl7-C7miIhtq.cjs"); const require_background_tasks = require("./background-tasks-lifNqs9M.cjs"); const require_toolchecks = require("./toolchecks-Rfz17G5p.cjs"); const require_types = require("./types-CepZ83u8.cjs"); const require_utils$1 = require("./utils-Bw7FoAI3.cjs"); const require_deep_equal = require("./deep-equal-BvQBG8wE.cjs"); const require_utils_safe_stringify = require("./utils/safe-stringify.cjs"); const require_trip_wire = require("./trip-wire-dxd_uCHj.cjs"); const require_payload_transform = require("./payload-transform-DJzDdfpE.cjs"); const require_signals = require("./signals-D2CulJo3.cjs"); const require_message_list = require("./message-list-BM7m-E-v.cjs"); const require_dist = require("./dist-BGdEcgoh.cjs"); const require_stream = require("./stream-CGwJ76we.cjs"); const require_types$1 = require("./types-ftn4_F2h.cjs"); const require_provider_registry = require("./provider-registry-Bv8eMxuW.cjs"); const require_model = require("./model-PhgPlg24.cjs"); const require_signal_provider = require("./signal-provider-xZOwL9Eb.cjs"); const require_cron = require("./cron-CrmzJKFJ.cjs"); const require_task_state_processor = require("./task-state-processor-BB7omHO3.cjs"); const require_constants = require("./constants-CHm1eNBE.cjs"); const require_logger_index = require("./logger/index.cjs"); const require_workflows_constants = require("./workflows/constants.cjs"); const require_workflow_event_processor = require("./workflow-event-processor-CkjVcesJ.cjs"); const require_hooks = require("./hooks-JAto1d0E.cjs"); const require_workspace = require("./workspace-SsEU6Si6.cjs"); const require_tools = require("./tools-B-JXNZhs.cjs"); const require_storage = require("./storage-DBpVHkrA.cjs"); const require_storage$1 = require("./storage-C4FD5U8Z.cjs"); const require_output_helpers = require("./output-helpers-BxsqfJ0U.cjs"); const require_agent_skills_resolver = require("./agent-skills-resolver-BfwRVTvX.cjs"); const require_voice = require("./voice-BxFaQWuy.cjs"); let crypto$1 = require("crypto"); crypto$1 = require_rolldown_runtime.__toESM(crypto$1, 1); let zod_v4 = require("zod/v4"); let _mastra_schema_compat = require("@mastra/schema-compat"); let stream_web = require("stream/web"); let zod = require("zod"); let _ai_sdk_provider_utils_v5 = require("@ai-sdk/provider-utils-v5"); let fs = require("fs"); let path = require("path"); let fastq = require("fastq"); fastq = require_rolldown_runtime.__toESM(fastq, 1); let tokenx = require("tokenx"); let xxhash_wasm = require("xxhash-wasm"); xxhash_wasm = require_rolldown_runtime.__toESM(xxhash_wasm, 1); let lru_cache = require("lru-cache"); let _mastra_schema_compat_schema = require("@mastra/schema-compat/schema"); //#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 (require_toolchecks.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 = require_dist.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 ((0, _mastra_schema_compat_schema.isStandardSchemaWithJSON)(sdkTool.inputSchema)) parameters = (0, _mastra_schema_compat_schema.standardSchemaToJSONSchema)(sdkTool.inputSchema, { io: "input", target: "draft-07" }); else parameters = require_dist.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; require_task_state_processor.clearCachedGoalObjective(requestContext); let store; let objective; try { store = await require_task_state_processor.resolveGoalStore(mastra); objective = await require_task_state_processor.readObjective(store, threadId); require_task_state_processor.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 require_task_state_processor.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 require_task_state_processor.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 = zod_v4.z.object({ type: zod_v4.z.literal("text"), text: zod_v4.z.string() }).passthrough(); /** * Image part in a message */ const ImagePartSchema = zod_v4.z.object({ type: zod_v4.z.literal("image"), image: zod_v4.z.union([ zod_v4.z.string(), zod_v4.z.instanceof(URL), zod_v4.z.instanceof(Uint8Array) ]), mimeType: zod_v4.z.string().optional() }).passthrough(); /** * File part in a message */ const FilePartSchema = zod_v4.z.object({ type: zod_v4.z.literal("file"), data: zod_v4.z.union([ zod_v4.z.string(), zod_v4.z.instanceof(URL), zod_v4.z.instanceof(Uint8Array) ]), mimeType: zod_v4.z.string() }).passthrough(); /** * Tool invocation part in a message (covers tool-call states) */ const ToolInvocationPartSchema = zod_v4.z.object({ type: zod_v4.z.literal("tool-invocation"), toolInvocation: zod_v4.z.object({ toolCallId: zod_v4.z.string(), toolName: zod_v4.z.string(), args: zod_v4.z.unknown(), state: zod_v4.z.enum([ "partial-call", "call", "result" ]), result: zod_v4.z.unknown().optional() }) }).passthrough(); /** * Reasoning part in a message (for models that support reasoning) */ const ReasoningPartSchema = zod_v4.z.object({ type: zod_v4.z.literal("reasoning"), reasoning: zod_v4.z.string(), details: zod_v4.z.array(zod_v4.z.object({ type: zod_v4.z.enum(["text", "redacted"]), text: zod_v4.z.string().optional(), data: zod_v4.z.string().optional() })) }).passthrough(); /** * Source part in a message (for citations/references) */ const SourcePartSchema = zod_v4.z.object({ type: zod_v4.z.literal("source"), source: zod_v4.z.object({ sourceType: zod_v4.z.string(), id: zod_v4.z.string(), url: zod_v4.z.string().optional(), title: zod_v4.z.string().optional() }) }).passthrough(); /** * Step start part (marks the beginning of a step in multi-step responses) */ const StepStartPartSchema = zod_v4.z.object({ type: zod_v4.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 = zod_v4.z.object({ type: zod_v4.z.string().refine((t) => t.startsWith("data-"), { message: "Type must start with \"data-\"" }), id: zod_v4.z.string().optional(), data: zod_v4.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 = zod_v4.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 = zod_v4.z.object({ /** Format version - 2 corresponds to AI SDK v4 UIMessage format */ format: zod_v4.z.literal(2), /** Array of message parts (text, images, tool calls, etc.) */ parts: zod_v4.z.array(MessagePartSchema), /** Legacy content field for backwards compatibility */ content: zod_v4.z.string().optional(), /** Additional metadata */ metadata: zod_v4.z.record(zod_v4.z.string(), zod_v4.z.unknown()).optional(), /** Provider-specific metadata */ providerMetadata: zod_v4.z.record(zod_v4.z.string(), zod_v4.z.unknown()).optional() }); /** * Schema for message content in processor workflows. * Uses the MessagePartSchema discriminated union for proper UI rendering. */ const ProcessorMessageContentSchema = zod_v4.z.object({ /** Format version - 2 corresponds to AI SDK v4 UIMessage format */ format: zod_v4.z.literal(2), /** Array of message parts (text, images, tool calls, etc.) */ parts: zod_v4.z.array(MessagePartSchema), /** Legacy content field for backwards compatibility */ content: zod_v4.z.string().optional(), /** Additional metadata */ metadata: zod_v4.z.record(zod_v4.z.string(), zod_v4.z.unknown()).optional(), /** Provider-specific metadata */ providerMetadata: zod_v4.z.record(zod_v4.z.string(), zod_v4.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 = zod_v4.z.object({ /** Unique message identifier */ id: zod_v4.z.string(), /** Message role */ role: zod_v4.z.enum([ "user", "assistant", "system", "tool", "signal" ]), /** When the message was created */ createdAt: zod_v4.z.coerce.date(), /** Thread identifier for conversation grouping */ threadId: zod_v4.z.string().optional(), /** Resource identifier */ resourceId: zod_v4.z.string().optional(), /** Message type */ type: zod_v4.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 = zod_v4.z.custom().describe("MessageList instance for managing message sources"); /** * The messages to be processed. * Format is MastraDBMessage[] - use ProcessorMessage type for TypeScript. */ const messagesSchema = zod_v4.z.array(ProcessorMessageSchema); /** * Schema for system message content parts (CoreSystemMessage format) * System messages can have text parts or experimental provider extensions */ const SystemMessageTextPartSchema = zod_v4.z.object({ type: zod_v4.z.literal("text"), text: zod_v4.z.string() }).passthrough(); zod_v4.z.object({ role: zod_v4.z.literal("system"), content: zod_v4.z.union([zod_v4.z.string(), zod_v4.z.array(SystemMessageTextPartSchema)]), /** Optional experimental provider-specific extensions */ experimental_providerMetadata: zod_v4.z.record(zod_v4.z.string(), zod_v4.z.unknown()).optional() }).passthrough(); /** * Schema for CoreMessage (any message type from AI SDK) * This is a more permissive schema for runtime flexibility. */ const CoreMessageSchema = zod_v4.z.object({ role: zod_v4.z.enum([ "system", "user", "assistant", "tool" ]), content: zod_v4.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 = zod_v4.z.array(CoreMessageSchema); /** * Tool call schema for processOutputStep */ const toolCallSchema = zod_v4.z.object({ toolName: zod_v4.z.string(), toolCallId: zod_v4.z.string(), args: zod_v4.z.unknown() }); /** * Number of times processors have triggered retry for this generation. */ const retryCountSchema = zod_v4.z.number().optional(); /** * Schema for 'input' phase - processInput * Processes input messages before they are sent to the LLM (once at the start) */ const ProcessorInputPhaseSchema = zod_v4.z.object({ phase: zod_v4.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 = zod_v4.z.object({ phase: zod_v4.z.literal("inputStep"), messages: messagesSchema, messageList: messageListSchema, stepNumber: zod_v4.z.number().describe("The current step number (0-indexed)"), systemMessages: systemMessagesSchema.optional(), retryCount: retryCountSchema, messageId: zod_v4.z.string().optional().describe("The active assistant response message ID for this step"), rotateResponseMessageId: zod_v4.z.custom().optional().describe("Rotate the active assistant response message ID when supported by the caller"), model: zod_v4.z.custom().optional().describe("Current model for this step"), tools: zod_v4.z.custom().optional().describe("Current tools available for this step"), toolChoice: zod_v4.z.custom().optional().describe("Current tool choice setting"), activeTools: zod_v4.z.array(zod_v4.z.string()).optional().describe("Currently active tools"), providerOptions: zod_v4.z.custom().optional().describe("Provider-specific options"), modelSettings: zod_v4.z.custom().optional().describe("Model settings (temperature, etc.)"), structuredOutput: zod_v4.z.custom().optional().describe("Structured output configuration"), steps: zod_v4.z.custom().optional().describe("Results from previous steps") }); /** * Schema for 'outputStream' phase - processOutputStream * Processes output stream chunks with built-in state management */ const ProcessorOutputStreamPhaseSchema = zod_v4.z.object({ phase: zod_v4.z.literal("outputStream"), part: zod_v4.z.unknown().nullable().describe("The current chunk being processed. Can be null to skip."), streamParts: zod_v4.z.array(zod_v4.z.unknown()).describe("All chunks seen so far"), state: zod_v4.z.record(zod_v4.z.string(), zod_v4.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 = zod_v4.z.object({ text: zod_v4.z.string().describe("The accumulated text from all steps"), usage: zod_v4.z.record(zod_v4.z.string(), zod_v4.z.unknown()).describe("Token usage (cumulative across all steps)"), finishReason: zod_v4.z.string().describe("Why the generation finished"), steps: zod_v4.z.array(zod_v4.z.unknown()).describe("All LLM step results") }); const ProcessorOutputResultPhaseSchema = zod_v4.z.object({ phase: zod_v4.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 = zod_v4.z.object({ phase: zod_v4.z.literal("outputStep"), messages: messagesSchema, messageList: messageListSchema, stepNumber: zod_v4.z.number().describe("The current step number (0-indexed)"), finishReason: zod_v4.z.string().optional().describe("The finish reason from the LLM (stop, tool-use, length, etc.)"), providerMetadata: zod_v4.z.record(zod_v4.z.string(), zod_v4.z.unknown()).optional().describe("Provider-specific metadata for the step (e.g. Bedrock guardrail trace under bedrock.trace.guardrail)"), toolCalls: zod_v4.z.array(toolCallSchema).optional().describe("Tool calls made in this step (if any)"), text: zod_v4.z.string().optional().describe("Generated text from this step"), usage: zod_v4.z.record(zod_v4.z.string(), zod_v4.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 = zod_v4.z.object({ phase: zod_v4.z.literal("toolResult"), messages: messagesSchema, messageList: messageListSchema, stepNumber: zod_v4.z.number().describe("The current step number (0-indexed)"), toolName: zod_v4.z.string().describe("Name of the tool that was executed"), toolCallId: zod_v4.z.string().describe("Unique identifier for this specific tool call"), args: zod_v4.z.unknown().optional().describe("Arguments the LLM passed to the tool"), result: zod_v4.z.unknown().optional().describe("Raw value returned by tool.execute() (already serialized)"), providerExecuted: zod_v4.z.boolean().optional().describe("Whether this result came from a provider-executed tool (e.g. Anthropic web_search)"), systemMessages: systemMessagesSchema.optional(), steps: zod_v4.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 = zod_v4.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 = zod_v4.z.object({ phase: zod_v4.z.enum([ "input", "inputStep", "outputStream", "outputResult", "outputStep", "toolResult" ]), messages: messagesSchema.optional(), messageList: messageListSchema.optional(), systemMessages: systemMessagesSchema.optional(), stepNumber: zod_v4.z.number().optional(), part: zod_v4.z.unknown().nullable().optional(), streamParts: zod_v4.z.array(zod_v4.z.unknown()).optional(), state: zod_v4.z.record(zod_v4.z.string(), zod_v4.z.unknown()).optional(), result: outputResultSchema.optional(), finishReason: zod_v4.z.string().optional(), toolCalls: zod_v4.z.array(toolCallSchema).optional(), text: zod_v4.z.string().optional(), usage: zod_v4.z.record(zod_v4.z.string(), zod_v4.z.unknown()).optional(), toolName: zod_v4.z.string().optional(), toolCallId: zod_v4.z.string().optional(), args: zod_v4.z.unknown().optional(), toolResultValue: zod_v4.z.unknown().optional(), providerExecuted: zod_v4.z.boolean().optional(), retryCount: zod_v4.z.number().optional(), model: zod_v4.z.custom().optional(), tools: zod_v4.z.custom().optional(), toolChoice: zod_v4.z.custom().optional(), activeTools: zod_v4.z.array(zod_v4.z.string()).optional(), providerOptions: zod_v4.z.custom().optional(), modelSettings: zod_v4.z.custom().optional(), structuredOutput: zod_v4.z.custom().optional(), steps: zod_v4.z.custom().optional(), messageId: zod_v4.z.string().optional(), rotateResponseMessageId: zod_v4.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 require_base.MastraBase { mastra; options; constructor({ mastra, options }) { super({ name: "ExecutionEngine", component: require_logger.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 = require_observability.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) => require_workflow_event_processor.getSingleStepEntryId(s)) }, tracingPolicy: engine.options?.tracingPolicy }, executionContext }); const prevOutput = engine.getStepOutput(stepResults, prevStep); for (const [stepIndex, step] of steps.entries()) { const stepId = require_workflow_event_processor.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 = require_workflow_event_processor.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 }, ...require_observability.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[require_workflow_event_processor.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 = require_observability.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 = require_workflow_event_processor.createDeprecationProxy({ runId, workflowId, mastra: engine.mastra, requestContext, actor, inputData: prevOutput, state: executionContext.state, retryCount: -1, ...require_observability.createObservabilityContext({ currentSpan: evalSpan }), getInitData: () => stepResults?.input, getStepResult: require_workflow_event_processor.getStepResult.bind(null, stepResults), bail: (() => {}), abort: () => { abortController?.abort(); }, [require_workflows_constants.PUBSUB_SYMBOL]: pubsub, [require_workflows_constants.STREAM_FORMAT_SYMBOL]: executionContext.format, engine: engine.getEngineContext(), abortSignal: abortController?.signal, writer: new require_types.ToolStream({ prefix: "workflow-step", callId: (0, crypto$1.randomUUID)(), name: "conditional", runId }, outputWriter) }, { paramName: "runCount", deprecationMessage: require_workflow_event_processor.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 = require_error.getErrorFromUnknown(e, { serializeStack: false }); const mastraError = new require_error.MastraError({ id: "WORKFLOW_CONDITION_EVALUATION_FAILED", domain: require_error.ErrorDomain.MASTRA_WORKFLOW, category: require_error.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[require_workflow_event_processor.getSingleStepEntryId(s)]; if (timeTravel && timeTravel.executionPath.length > 0) return timeTravel.steps[0] === require_workflow_event_processor.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 = require_workflow_event_processor.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) => require_workflow_event_processor.getSingleStepEntryId(s)) } }); const results = await Promise.all(stepsToRun.map(async (step) => { const stepId = require_workflow_event_processor.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 }, ...require_observability.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[require_workflow_event_processor.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 = require_observability.resolveObservabilityContext(rest); const { step, condition } = entry; const stepId = require_workflow_event_processor.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: iteration } } }); return { status: "canceled" }; } const stepExecResult = await executeChildEntry(engine, step, { workflowId, runId, resourceId, stepResults, executionContext, restart: currentRestart, resume: currentResume, timeTravel: currentTimeTravel, prevOutput: result.output, ...require_observability.createObservabilityContext({ currentSpan: loopSpan }), pubsub, abortController, requestContext, actor, outputWriter, disableScorers, serializedStepGraph, iterationCount: iteration + 1, perStep }); engine.applyMutableContext(executionContext, stepExecResult.mutableContext); Object.assign(stepResults, stepExecResult.stepResults); result = stepExecResult.result; currentRestart = void 0; currentTimeTravel = void 0; if (currentResume && result.status !== "suspended") currentResume = void 0; if (result.status !== "success") { await engine.endChildSpan({ span: loopSpan, operationId: `workflow.${workflowId}.run.${runId}.loop.${executionContext.executionPath.join("-")}.span.end.early`, endOptions: { attributes: { totalIterations: iteration } } }); return result; } if (abortController?.signal?.aborted) { await engine.endChildSpan({ span: loopSpan,