UNPKG

@mastra/core

Version:
1,220 lines 1.33 MB
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: