UNPKG

@mastra/core

Version:
1 lines 623 kB
{"version":3,"file":"create-durable-agent-CB4JGA_3.cjs","names":["runIdleLoop","boundedStringify","mastraDBMessageToSignal","RequestContext","deepMerge","MASTRA_VERSIONS_KEY","mergeVersionOverrides","MessageList","getOrCreateSpan","EntityType","createObservabilityContext","TripWire","SaveQueueManager","normalizeToolPayloadTransformPolicy","TTLCache","#entries","#messageLists","#memoryInfo","MessageList","AgentStreamEventTypes","ReadableStream","AGENT_STREAM_TOPIC","MastraModelOutput","z","DurableAgentDefaults","DurableStepIds","z","createStep","PUBSUB_SYMBOL","transformToolPayloadForTargets","withToolPayloadTransformMetadata","SaveQueueManager","RequestContext","MessageList","getNeedsApprovalFn","resolveModelConfig","z","createStep","DurableStepIds","PUBSUB_SYMBOL","isSupportedLanguageModel","mergeProviderOptions","PrepareStepProcessor","ProcessorRunner","composeStepInput","ConsoleLogger","isMastraTool","makeCoreTool","createMastraProxy","TripWire","buildLlmPromptArgs","applyAutoResumeSystemMessage","injectBackgroundTaskPrompt","findProviderToolByName","inferProviderExecuted","EntityType","getStepAvailableToolNames","execute","mergeLlmCallHeaders","buildMemoryHeaders","MastraModelOutput","MessageList","buildMessagesFromChunks","z","ProcessorRunner","createStep","DurableStepIds","PUBSUB_SYMBOL","findProviderToolByName","stopGoalActivity","resolveBackgroundConfig","createBackgroundTask","z","createStep","DurableStepIds","MessageList","EntityType","PUBSUB_SYMBOL","DurableAgentDefaults","createStep","DurableStepIds","z","PUBSUB_SYMBOL","MessageList","runStreamCompletionScorers","formatStreamCompletionFeedback","createStep","z","PUBSUB_SYMBOL","RequestContext","resolveGoalStore","readObjective","resolveEffectiveGoalSettings","resolveModelConfig","createGoalScorer","MessageList","runStreamCompletionScorers","GOAL_SCORER_ID","writeObjective","createProcessorSendSignal","DurableStepIds","createStep","z","MessageList","PUBSUB_SYMBOL","z","DurableAgentDefaults","createWorkflow","DurableStepIds","pruneAgentLoopSnapshot","input","PUBSUB_SYMBOL","MessageList","createObservabilityContext","RequestContext","Agent","#wrappedAgent","#runRegistry","#maxSteps","#hasCustomPubsub","#cleanupTimeoutMs","#innerPubsub","EventEmitterPubSub","#cacheConfig","#ensurePubsubInitialized","#resolvedCache","#cachingPubsub","CachingPubSub","#mastra","InMemoryServerCache","#resolveExecutionOptions","deepMerge","createObservabilityContext","DurableStepIds","#activeStreamUntilIdle","#clearPubsubTopic","beginGoalActivity","stopGoalActivity","agentThreadStreamRuntime","MastraError","ErrorDomain","ErrorCategory","RequestContext","#getPubsubOffset","getOrCreateSpan","EntityType","MessageList","SaveQueueManager","AGENT_STREAM_TOPIC","#workflow"],"sources":["../src/agent/durable/durable-stream-until-idle.ts","../src/agent/durable/utils/serialize-state.ts","../src/agent/durable/preparation.ts","../src/agent/durable/run-registry.ts","../src/agent/durable/stream-adapter.ts","../src/agent/durable/workflows/shared/schemas.ts","../src/agent/durable/workflows/shared/iteration-state.ts","../src/agent/durable/workflows/shared/tool-call-concurrency.ts","../src/agent/durable/workflows/steps/background-task-check.ts","../src/agent/durable/utils/apply-tool-payload-transform.ts","../src/agent/durable/utils/resolve-runtime.ts","../src/agent/durable/workflows/steps/llm-execution.ts","../src/agent/durable/workflows/steps/tool-call.ts","../src/agent/durable/workflows/steps/llm-mapping.ts","../src/agent/durable/workflows/steps/is-task-complete.ts","../src/agent/durable/workflows/steps/goal.ts","../src/agent/durable/workflows/steps/signal-drain.ts","../src/agent/durable/workflows/create-durable-agentic-workflow.ts","../src/agent/durable/durable-agent.ts","../src/agent/durable/create-durable-agent.ts"],"sourcesContent":["/**\n * Implementation of `DurableAgent.streamUntilIdle` and\n * `DurableAgent.resume(..., { untilIdle })`. Mirrors the regular agent's\n * `stream-until-idle.ts` but adapted for durable execution:\n * - `DurableAgent.stream()` returns `DurableAgentStreamResult` (not `MastraModelOutput`)\n * - Each continuation starts a new durable workflow (new runId)\n * - Cleanup functions from each inner stream are tracked and called on close\n * - Inner `abort()` handles are fanned out so the outer `result.abort()`\n * cancels every active durable run\n *\n * Uses the shared `runIdleLoop` helper from `loop/shared/stream-until-idle-helpers`\n * with durable-specific hooks for cleanup/abort tracking.\n */\nimport type { BackgroundTaskManager } from '../../background-tasks/manager';\nimport { runIdleLoop } from '../../loop/shared/stream-until-idle-helpers';\nimport type { MessageListInput } from '../message-list';\n\nimport type { DurableAgent, DurableAgentStreamOptions, DurableAgentStreamResult } from './durable-agent';\n\nexport interface DurableStreamUntilIdleDeps {\n activeStreams: Map<string, () => void>;\n bgManager: BackgroundTaskManager | undefined;\n}\n\n/**\n * Run `DurableAgent.streamUntilIdle` (or `DurableAgent.stream({ untilIdle })`).\n * Initial turn invokes `agent.stream(messages, ...)`; continuations triggered\n * by background-task completions run as fresh `agent.stream([], ...)` calls\n * against the same memory thread.\n */\nexport async function runDurableStreamUntilIdle<OUTPUT = undefined>(\n agent: DurableAgent<any, any, OUTPUT>,\n messages: MessageListInput,\n streamOptions: (DurableAgentStreamOptions<OUTPUT> & { maxIdleMs?: number }) | undefined,\n deps: DurableStreamUntilIdleDeps,\n): Promise<DurableAgentStreamResult<OUTPUT>> {\n // Durable-specific: track cleanup/abort handles from each inner stream\n const innerCleanups: Array<() => void> = [];\n const innerAborts: Array<(reason?: unknown) => void> = [];\n\n return runIdleLoop<typeof agent, DurableAgentStreamResult<OUTPUT>, DurableAgentStreamResult<OUTPUT>>(\n agent,\n streamOptions,\n deps,\n opts => (agent as any).stream(messages, opts) as Promise<DurableAgentStreamResult<OUTPUT>>,\n opts => (agent as any).stream([], opts) as Promise<{ fullStream: ReadableStream<any> }>,\n (first, ctx) => {\n // No ctx means no bgManager / no memory — fall through without wrapping.\n if (!ctx) return first;\n\n return {\n output: new Proxy(first.output, {\n get(target, prop) {\n if (prop === 'fullStream') return ctx.combinedStream;\n const value = Reflect.get(target, prop, target);\n return typeof value === 'function' ? value.bind(target) : value;\n },\n }) as any,\n get fullStream() {\n return ctx.combinedStream;\n },\n runId: first.runId,\n threadId: ctx.threadId,\n resourceId: ctx.resourceId,\n cleanup: ctx.forceClose,\n abort: (reason?: unknown) => {\n // Fan the abort out to every inner DurableAgent.stream() that has been\n // spawned by the idle loop so far. `forceClose` then unwinds the outer\n // stream + idle timer.\n for (const innerAbort of innerAborts) {\n try {\n innerAbort(reason);\n } catch {\n // ignore — best-effort abort across siblings\n }\n }\n ctx.forceClose();\n },\n };\n },\n {\n onInnerResult: (inner: any) => {\n if (typeof inner.cleanup === 'function') innerCleanups.push(inner.cleanup);\n if (typeof inner.abort === 'function') innerAborts.push(inner.abort);\n },\n onForceClose: () => {\n for (const fn of innerCleanups) {\n try {\n fn();\n } catch {\n // ignore\n }\n }\n },\n },\n );\n}\n\n/**\n * Run `DurableAgent.resume(..., { untilIdle })`. Same idle-loop semantics as\n * `runDurableStreamUntilIdle` — initial turn calls `agent.resume(runId,\n * resumeData, ...)` against the existing run snapshot, and subsequent\n * continuations triggered by background-task completions use\n * `agent.stream([], continuationOpts)` (a normal multi-turn agent stream)\n * since the resume completes and we're back in regular conversation flow.\n */\nexport async function runResumeDurableStreamUntilIdle<OUTPUT = undefined>(\n agent: DurableAgent<any, any, OUTPUT>,\n runId: string,\n resumeData: unknown,\n streamOptions: (DurableAgentStreamOptions<OUTPUT> & { maxIdleMs?: number }) | undefined,\n deps: DurableStreamUntilIdleDeps,\n): Promise<DurableAgentStreamResult<OUTPUT>> {\n const innerCleanups: Array<() => void> = [];\n const innerAborts: Array<(reason?: unknown) => void> = [];\n\n return runIdleLoop<typeof agent, DurableAgentStreamResult<OUTPUT>, DurableAgentStreamResult<OUTPUT>>(\n agent,\n streamOptions,\n deps,\n opts => (agent as any).resume(runId, resumeData, opts) as Promise<DurableAgentStreamResult<OUTPUT>>,\n opts => (agent as any).stream([], opts) as Promise<{ fullStream: ReadableStream<any> }>,\n (first, ctx) => {\n if (!ctx) return first;\n\n return {\n output: new Proxy(first.output, {\n get(target, prop) {\n if (prop === 'fullStream') return ctx.combinedStream;\n const value = Reflect.get(target, prop, target);\n return typeof value === 'function' ? value.bind(target) : value;\n },\n }) as any,\n get fullStream() {\n return ctx.combinedStream;\n },\n runId: first.runId,\n threadId: ctx.threadId,\n resourceId: ctx.resourceId,\n cleanup: ctx.forceClose,\n abort: (reason?: unknown) => {\n for (const innerAbort of innerAborts) {\n try {\n innerAbort(reason);\n } catch {\n // ignore\n }\n }\n ctx.forceClose();\n },\n };\n },\n {\n onInnerResult: (inner: any) => {\n if (typeof inner.cleanup === 'function') innerCleanups.push(inner.cleanup);\n if (typeof inner.abort === 'function') innerAborts.push(inner.abort);\n },\n onForceClose: () => {\n for (const fn of innerCleanups) {\n try {\n fn();\n } catch {\n // ignore\n }\n }\n },\n },\n );\n}\n","import type { JSONSchema7 } from 'json-schema';\nimport type { MastraLanguageModel } from '../../../llm/model/shared.types';\nimport type { MemoryConfig } from '../../../memory/types';\nimport type { CoreTool } from '../../../tools/types';\nimport type { MessageList } from '../../message-list';\nimport type { AgentModelManagerConfig } from '../../types';\nimport type {\n SerializableToolMetadata,\n SerializableModelConfig,\n SerializableModelListEntry,\n SerializableDurableState,\n SerializableDurableOptions,\n SerializableModelSettings,\n SerializableScorersConfig,\n SerializableScorerEntry,\n DurableAgenticWorkflowInput,\n} from '../types';\n\n/**\n * Extract serializable metadata from a CoreTool\n * This strips out the execute function and converts the schema to JSON Schema\n */\nexport function serializeToolMetadata(name: string, tool: CoreTool): SerializableToolMetadata {\n // Extract JSON Schema from the parameters\n let inputSchema: JSONSchema7 = { type: 'object' };\n\n if (tool.parameters) {\n // If it's already a JSON Schema object\n if ('type' in tool.parameters && typeof tool.parameters.type === 'string') {\n inputSchema = tool.parameters as JSONSchema7;\n }\n // If it has a jsonSchema property (zod schema converted)\n else if ('jsonSchema' in tool.parameters) {\n inputSchema = (tool.parameters as any).jsonSchema as JSONSchema7;\n }\n // If it's a Zod schema with _def (try to extract)\n else if ('_def' in tool.parameters) {\n // We'll need to use zodToJsonSchema at runtime if available\n // For now, use a basic object schema\n inputSchema = { type: 'object' };\n }\n }\n\n return {\n id: 'id' in tool && typeof tool.id === 'string' ? tool.id : name,\n name,\n description: tool.description,\n inputSchema,\n requireApproval: (tool as any).requireApproval,\n hasSuspendSchema: (tool as any).hasSuspendSchema,\n };\n}\n\n/**\n * Extract serializable metadata from all tools\n */\nexport function serializeToolsMetadata(tools: Record<string, CoreTool>): SerializableToolMetadata[] {\n return Object.entries(tools).map(([name, tool]) => serializeToolMetadata(name, tool));\n}\n\n/**\n * Extract serializable model configuration\n */\nexport function serializeModelConfig(model: MastraLanguageModel): SerializableModelConfig {\n return {\n provider: model.provider,\n modelId: model.modelId,\n specificationVersion: model.specificationVersion,\n // Store the original config string for runtime resolution (e.g., 'openai/gpt-4o')\n originalConfig: `${model.provider}/${model.modelId}`,\n // Note: We don't serialize model settings here - they come from execution options\n };\n}\n\n/**\n * Extract serializable model list entry from AgentModelManagerConfig\n */\nexport function serializeModelListEntry(entry: AgentModelManagerConfig): SerializableModelListEntry {\n const model = entry.model;\n return {\n id: entry.id,\n config: {\n provider: model.provider,\n modelId: model.modelId,\n specificationVersion: model.specificationVersion,\n originalConfig: `${model.provider}/${model.modelId}`,\n providerOptions: entry.providerOptions,\n },\n maxRetries: entry.maxRetries,\n enabled: entry.enabled,\n };\n}\n\n/**\n * Serialize an array of model configs into a model list.\n * Filters out disabled models since they shouldn't be included in durable execution.\n */\nexport function serializeModelList(models: AgentModelManagerConfig[]): SerializableModelListEntry[] {\n return models.filter(m => m.enabled !== false).map(serializeModelListEntry);\n}\n\n/**\n * Serialize scorers configuration for durable execution.\n *\n * This extracts the scorer name (for resolution at runtime) and sampling config.\n * The actual scorer objects are resolved from Mastra at step execution time.\n *\n * @param scorers The agent's scorers configuration (from agent.scorers or options.scorers)\n * @returns Serializable scorer configuration\n */\nexport function serializeScorersConfig(\n scorers: Record<\n string,\n { scorer: { name: string } | string; sampling?: { type: 'none' } | { type: 'ratio'; rate: number } }\n >,\n): SerializableScorersConfig {\n const result: SerializableScorersConfig = {};\n\n for (const [key, entry] of Object.entries(scorers)) {\n // Get the scorer name - can be a string directly or from scorer.name\n const scorerName = typeof entry.scorer === 'string' ? entry.scorer : entry.scorer.name;\n\n const scorerEntry: SerializableScorerEntry = {\n scorerName,\n };\n\n // Include sampling if provided\n if (entry.sampling) {\n scorerEntry.sampling = entry.sampling;\n }\n\n result[key] = scorerEntry;\n }\n\n return result;\n}\n\n/**\n * Extract serializable state from _internal-like objects\n */\nexport function serializeDurableState(params: {\n memoryConfig?: MemoryConfig;\n threadId?: string;\n resourceId?: string;\n threadExists?: boolean;\n savePerStep?: boolean;\n observationalMemory?: boolean;\n}): SerializableDurableState {\n return {\n memoryConfig: params.memoryConfig,\n threadId: params.threadId,\n resourceId: params.resourceId,\n threadExists: params.threadExists,\n savePerStep: params.savePerStep,\n observationalMemory: params.observationalMemory,\n };\n}\n\n/**\n * Pick the JSON-safe call settings out of an arbitrary `modelSettings` input.\n * Drops any field that is not a primitive value of the expected type so that\n * non-serializable fields (functions, AbortSignal, etc.) never reach the\n * workflow input.\n */\nexport function serializeModelSettings(\n settings: SerializableModelSettings | Record<string, unknown> | undefined,\n): SerializableModelSettings | undefined {\n if (!settings || typeof settings !== 'object') return undefined;\n\n const source = settings as Record<string, unknown>;\n const out: SerializableModelSettings = {};\n const pickNumber = (key: keyof SerializableModelSettings) => {\n const value = source[key as string];\n if (typeof value === 'number' && Number.isFinite(value)) {\n (out as Record<string, unknown>)[key as string] = value;\n }\n };\n\n pickNumber('maxOutputTokens');\n pickNumber('temperature');\n pickNumber('topP');\n pickNumber('topK');\n pickNumber('presencePenalty');\n pickNumber('frequencyPenalty');\n pickNumber('seed');\n pickNumber('maxRetries');\n\n if (Array.isArray(source.stopSequences) && source.stopSequences.every(v => typeof v === 'string')) {\n out.stopSequences = source.stopSequences as string[];\n }\n\n // Headers are never serialized into the workflow input. They are stored\n // exclusively on the in-process RunRegistryEntry so they never reach\n // durable storage. The durable llm-execution step merges them back from\n // the registry at call time.\n // (Previously we had a denylist of \"sensitive\" header names, but any\n // header could carry credentials — the safest approach is to keep them\n // all off the wire.)\n\n return Object.keys(out).length > 0 ? out : undefined;\n}\n\n/**\n * Extract serializable options from agent execution options\n */\nexport function serializeDurableOptions(options: {\n maxSteps?: number;\n toolChoice?: any;\n activeTools?: string[];\n modelSettings?: SerializableModelSettings | Record<string, unknown>;\n requireToolApproval?: boolean;\n toolCallConcurrency?: number;\n autoResumeSuspendedTools?: boolean;\n maxProcessorRetries?: number;\n includeRawChunks?: boolean;\n returnScorerData?: boolean;\n hasErrorProcessors?: boolean;\n providerOptions?: SerializableDurableOptions['providerOptions'];\n structuredOutput?: SerializableDurableOptions['structuredOutput'];\n skipBgTaskWait?: boolean;\n disableBackgroundTasks?: boolean;\n tracingOptions?: SerializableDurableOptions['tracingOptions'];\n actor?: SerializableDurableOptions['actor'];\n instructionsOverride?: SerializableDurableOptions['instructionsOverride'];\n systemMessage?: SerializableDurableOptions['systemMessage'];\n transform?: SerializableDurableOptions['transform'];\n isTaskComplete?: SerializableDurableOptions['isTaskComplete'];\n}): SerializableDurableOptions {\n // Normalize toolChoice to serializable form\n let serializedToolChoice: SerializableDurableOptions['toolChoice'];\n if (options.toolChoice) {\n if (typeof options.toolChoice === 'string') {\n serializedToolChoice = options.toolChoice as 'auto' | 'none' | 'required';\n } else if (typeof options.toolChoice === 'object' && 'type' in options.toolChoice) {\n if (options.toolChoice.type === 'tool' && 'toolName' in options.toolChoice) {\n serializedToolChoice = {\n type: 'tool',\n toolName: options.toolChoice.toolName as string,\n };\n }\n }\n }\n\n return {\n maxSteps: options.maxSteps,\n toolChoice: serializedToolChoice,\n activeTools: options.activeTools,\n modelSettings: serializeModelSettings(options.modelSettings),\n requireToolApproval: options.requireToolApproval,\n toolCallConcurrency: options.toolCallConcurrency,\n autoResumeSuspendedTools: options.autoResumeSuspendedTools,\n maxProcessorRetries: options.maxProcessorRetries,\n includeRawChunks: options.includeRawChunks,\n returnScorerData: options.returnScorerData,\n hasErrorProcessors: options.hasErrorProcessors,\n providerOptions: options.providerOptions,\n structuredOutput: options.structuredOutput,\n skipBgTaskWait: options.skipBgTaskWait,\n disableBackgroundTasks: options.disableBackgroundTasks,\n tracingOptions: options.tracingOptions,\n actor: options.actor,\n instructionsOverride: options.instructionsOverride,\n systemMessage: options.systemMessage,\n transform: options.transform,\n isTaskComplete: options.isTaskComplete,\n };\n}\n\n/**\n * Create the full workflow input from all components\n */\nexport function createWorkflowInput(params: {\n runId: string;\n agentId: string;\n agentName?: string;\n messageList: MessageList;\n tools: Record<string, CoreTool>;\n model: MastraLanguageModel;\n modelList?: AgentModelManagerConfig[];\n scorers?: Parameters<typeof serializeScorersConfig>[0];\n options: Parameters<typeof serializeDurableOptions>[0];\n state: Parameters<typeof serializeDurableState>[0];\n messageId: string;\n agentSpanData?: unknown;\n modelSpanData?: unknown;\n requestContextEntries?: Record<string, unknown>;\n}): DurableAgenticWorkflowInput {\n return {\n __workflowKind: 'durable-agent',\n runId: params.runId,\n agentId: params.agentId,\n agentName: params.agentName,\n messageListState: params.messageList.serialize(),\n toolsMetadata: serializeToolsMetadata(params.tools),\n modelConfig: serializeModelConfig(params.model),\n modelList: params.modelList ? serializeModelList(params.modelList) : undefined,\n scorers: params.scorers ? serializeScorersConfig(params.scorers) : undefined,\n options: serializeDurableOptions(params.options),\n state: serializeDurableState(params.state),\n messageId: params.messageId,\n agentSpanData: params.agentSpanData,\n modelSpanData: params.modelSpanData,\n requestContextEntries: params.requestContextEntries,\n };\n}\n\n/**\n * Serialize an error for workflow state\n */\nexport function serializeError(error: unknown): { name: string; message: string; stack?: string } {\n if (error instanceof Error) {\n return {\n name: error.name,\n message: error.message,\n stack: error.stack,\n };\n }\n return {\n name: 'Error',\n message: String(error),\n };\n}\n\n/**\n * Serialize a Date to ISO string for workflow state\n */\nexport function serializeDate(date: Date | undefined): string | undefined {\n return date?.toISOString();\n}\n\n/**\n * Deserialize an ISO string back to Date\n */\nexport function deserializeDate(isoString: string | undefined): Date | undefined {\n return isoString ? new Date(isoString) : undefined;\n}\n","import type { AgentBackgroundConfig } from '../../background-tasks/types';\nimport type { MastraLanguageModel } from '../../llm/model/shared.types';\nimport type { IMastraLogger } from '../../logger';\nimport type { Mastra } from '../../mastra';\nimport type { MastraMemory } from '../../memory/memory';\nimport type { MemoryConfig, MemoryConfig as _MemoryConfig, StorageThreadType } from '../../memory/types';\nimport { EntityType, SpanType, createObservabilityContext, getOrCreateSpan } from '../../observability';\nimport type { InputProcessorOrWorkflow, OutputProcessorOrWorkflow, ErrorProcessorOrWorkflow } from '../../processors';\nimport type { ProcessorState } from '../../processors/runner';\nimport { RequestContext, MASTRA_VERSIONS_KEY, mergeVersionOverrides } from '../../request-context';\nimport type { VersionOverrides } from '../../request-context';\nimport { toStandardSchema } from '../../schema';\nimport { normalizeToolPayloadTransformPolicy } from '../../tools/payload-transform';\nimport type { CoreTool, ToolHooks, ToolPayloadTransformPolicy } from '../../tools/types';\nimport { boundedStringify, deepMerge } from '../../utils';\nimport type { Workspace } from '../../workspace';\nimport type { Agent } from '../agent';\nimport type { AgentExecutionOptions, DelegationConfig } from '../agent.types';\nimport { MessageList } from '../message-list';\nimport type { MessageListInput } from '../message-list';\nimport { SaveQueueManager } from '../save-queue';\nimport type { CreatedAgentSignal } from '../signals';\nimport { mastraDBMessageToSignal } from '../signals';\nimport { TripWire } from '../trip-wire';\nimport type {\n AgentInstructions,\n AgentMethodType,\n AgentModelManagerConfig,\n GoalConfig,\n ToolsetsInput,\n ToolsInput,\n} from '../types';\nimport type { DurableAgenticWorkflowInput, RunRegistryEntry, SerializableStructuredOutput } from './types';\nimport { createWorkflowInput } from './utils/serialize-state';\n\n/**\n * JSON-safe snapshot of `requestContext.entries()` so durable steps (e.g.\n * is-task-complete scorers) can see the same `customContext` the non-durable\n * path passes. Best-effort: entries that fail a JSON round-trip are skipped\n * so a single non-serializable value can't break the workflow input.\n */\nfunction snapshotRequestContextEntries(\n requestContext: RequestContext | undefined,\n): Record<string, unknown> | undefined {\n if (!requestContext) return undefined;\n const out: Record<string, unknown> = {};\n let any = false;\n for (const [key, value] of requestContext.entries()) {\n // Serialize each entry exactly once with a bounded pass: a shared-reference\n // graph would otherwise make JSON.stringify expand exponentially and wedge\n // the event loop on every durable step, and reading the value twice (probe\n // then clone) could disagree if a getter/toJSON is stateful. Entries that\n // produce no JSON (non-serializable, or too large to serialize within\n // budget) are skipped — they wouldn't survive the wire on cross-process\n // engines anyway.\n const json = boundedStringify(value);\n if (json === undefined) continue;\n out[key as string] = JSON.parse(json);\n any = true;\n }\n return any ? out : undefined;\n}\n\n/**\n * Mirror of Agent#convertInstructionsToString — used for the AGENT_RUN span\n * `attributes.instructions` field so durable runs publish the same shape as\n * non-durable runs. Kept local to avoid promoting the private method.\n */\nfunction convertInstructionsToString(instructions: AgentInstructions | undefined): string {\n if (!instructions) return '';\n if (typeof instructions === 'string') return instructions;\n if (Array.isArray(instructions)) {\n return instructions\n .map(msg => (typeof msg === 'string' ? msg : typeof msg.content === 'string' ? msg.content : ''))\n .filter(Boolean)\n .join('\\n\\n');\n }\n return typeof instructions.content === 'string' ? instructions.content : '';\n}\n\n/**\n * Extract signal messages already present in the messageList at run start\n * (from persisted history) so they can be echoed as data-signal stream parts\n * on the first LLM step. Mirrors `prepare-memory-step.ts#getInitialSignalEchoes`.\n */\nfunction getInitialSignalEchoes(messageList: MessageList): CreatedAgentSignal[] {\n const inputMessageIds = messageList.makeMessageSourceChecker().input;\n return messageList.get.all\n .db()\n .filter(message => message.role === 'signal' && inputMessageIds.has(message.id))\n .map(mastraDBMessageToSignal);\n}\n\n/**\n * Interface for the Agent methods needed during durable preparation.\n * This provides proper typing for the public Agent methods we call.\n */\ninterface DurablePreparationAgent {\n id: string;\n name?: string;\n getDefaultOptions(opts: { requestContext: RequestContext }): AgentExecutionOptions | Promise<AgentExecutionOptions>;\n getInstructions(opts: { requestContext: RequestContext }): AgentInstructions | Promise<AgentInstructions>;\n getModel(opts: { requestContext: RequestContext }): MastraLanguageModel | Promise<MastraLanguageModel>;\n getModelList(requestContext: RequestContext): Promise<AgentModelManagerConfig[] | null>;\n getMemory(opts: { requestContext: RequestContext }): Promise<MastraMemory | undefined>;\n getWorkspace(opts: { requestContext: RequestContext }): Promise<Workspace | undefined>;\n listScorers(opts: {\n requestContext: RequestContext;\n }): Promise<Record<string, { scorer: unknown; sampling?: unknown }> | undefined>;\n getToolsForExecution(opts: {\n toolsets?: ToolsetsInput;\n clientTools?: ToolsInput;\n threadId?: string;\n resourceId?: string;\n runId?: string;\n requestContext?: RequestContext;\n memoryConfig?: MemoryConfig;\n autoResumeSuspendedTools?: boolean;\n hooks?: ToolHooks;\n delegation?: DelegationConfig;\n methodType?: AgentMethodType;\n }): Promise<Record<string, CoreTool>>;\n listInputProcessors(requestContext?: RequestContext): Promise<InputProcessorOrWorkflow[]>;\n listOutputProcessors(requestContext?: RequestContext): Promise<OutputProcessorOrWorkflow[]>;\n listErrorProcessors(requestContext?: RequestContext): Promise<ErrorProcessorOrWorkflow[]>;\n getBackgroundTasksConfig(): AgentBackgroundConfig | undefined;\n getToolPayloadTransform?(): ToolPayloadTransformPolicy | undefined;\n __getDrainPendingSignals(): (runId: string, scope?: 'pending' | 'pre-run') => CreatedAgentSignal[];\n __getGoalConfig(): GoalConfig | undefined;\n __listLLMRequestProcessors(requestContext?: RequestContext): Promise<InputProcessorOrWorkflow[]>;\n}\n\n/**\n * Result from the preparation phase\n */\nexport interface PreparationResult<_OUTPUT = undefined> {\n /** Unique run identifier */\n runId: string;\n /** Message ID for this generation */\n messageId: string;\n /** Serialized workflow input */\n workflowInput: DurableAgenticWorkflowInput;\n /** Non-serializable state for the run registry */\n registryEntry: RunRegistryEntry;\n /** MessageList for callback access */\n messageList: MessageList;\n /** Thread ID if using memory */\n threadId?: string;\n /** Resource ID if using memory */\n resourceId?: string;\n}\n\n/**\n * Options for preparation phase\n */\nexport interface PreparationOptions<OUTPUT = undefined> {\n /** The agent instance (wrapped agent — used for config resolution: tools, model, instructions, memory) */\n agent: Agent<string, any, OUTPUT>;\n /** User messages to process */\n messages: MessageListInput;\n /** Execution options */\n options?: AgentExecutionOptions<OUTPUT>;\n /** Whether execution options already include the agent defaults. */\n optionsAreResolved?: boolean;\n /** Run ID (will be generated if not provided) */\n runId?: string;\n /** Request context */\n requestContext?: RequestContext;\n /** Logger */\n logger?: IMastraLogger;\n /** Mastra instance (for version overrides, background tasks, etc.) */\n mastra?: Mastra;\n /** Method type */\n methodType?: AgentMethodType;\n /**\n * The public-facing agent ID (the DurableAgent wrapper's ID).\n * Used for spans, background tasks, scorers, and all identification visible to Studio.\n * Falls back to `agent.id` if not provided.\n */\n durableAgentId?: string;\n /**\n * The public-facing agent name (the DurableAgent wrapper's name).\n * Used for spans, background tasks, scorers, and all identification visible to Studio.\n * Falls back to `agent.name` if not provided.\n */\n durableAgentName?: string;\n}\n\n/**\n * Prepare for durable agent execution.\n *\n * This function performs the non-durable preparation phase:\n * 1. Generates run ID and message ID\n * 2. Resolves thread/memory context\n * 3. Creates MessageList with instructions and messages\n * 4. Converts tools to CoreTool format\n * 5. Gets the model configuration\n * 6. Creates serialized workflow input\n * 7. Creates run registry entry for non-serializable state\n *\n * The result includes both the serialized workflow input (for the durable\n * workflow) and the run registry entry (for non-serializable state).\n */\nexport async function prepareForDurableExecution<OUTPUT = undefined>(\n options: PreparationOptions<OUTPUT>,\n): Promise<PreparationResult<OUTPUT>> {\n const {\n agent,\n messages,\n options: rawExecOptions,\n optionsAreResolved = false,\n runId: providedRunId,\n requestContext: providedRequestContext,\n logger,\n mastra,\n methodType = 'stream',\n durableAgentId,\n durableAgentName,\n } = options;\n\n // Public-facing identity: use the durable wrapper's ID/name for all\n // external-facing identification (spans, background tasks, scorers, Studio).\n // Fall back to the wrapped agent's ID/name when called outside the durable wrapper.\n const publicAgentId = durableAgentId ?? agent.id;\n const publicAgentName = durableAgentName ?? agent.name ?? agent.id;\n\n const typedAgent = agent as unknown as DurablePreparationAgent;\n\n // 1. Generate IDs\n const runId = providedRunId ?? crypto.randomUUID();\n const messageId = crypto.randomUUID();\n\n // 2. Get request context\n const requestContext = providedRequestContext ?? new RequestContext();\n\n // 2a. Snapshot caller-provided RequestContext entries *before* preparation\n // mutates the context (version overrides at step 3, MastraMemory at step 4).\n // The persisted `customContext` should reflect only what the caller passed in,\n // not internal-key state added during prep.\n const requestContextEntriesSnapshot = snapshotRequestContextEntries(requestContext);\n\n // 2b. Merge the wrapped agent's defaultOptions under the per-request options,\n // mirroring the non-durable Agent.stream()/generate() paths. Without this the\n // agent's configured defaults (maxSteps, providerOptions, etc.) are silently\n // dropped and durable runs fall back to DurableAgentDefaults.MAX_STEPS.\n const execOptions: AgentExecutionOptions<OUTPUT> = optionsAreResolved\n ? (rawExecOptions ?? ({} as AgentExecutionOptions<OUTPUT>))\n : (deepMerge(\n ((await typedAgent.getDefaultOptions({ requestContext })) ?? {}) as Record<string, unknown>,\n (rawExecOptions ?? {}) as Record<string, unknown>,\n ) as AgentExecutionOptions<OUTPUT>);\n\n // 3. Merge version overrides (Mastra defaults < requestContext < call-site)\n const requestVersions = requestContext.get(MASTRA_VERSIONS_KEY) as VersionOverrides | undefined;\n let mergedVersions = mergeVersionOverrides(mastra?.getVersionOverrides?.(), requestVersions);\n if ((execOptions as any)?.versions) {\n mergedVersions = mergeVersionOverrides(mergedVersions, (execOptions as any).versions);\n }\n if (mergedVersions) {\n requestContext.set(MASTRA_VERSIONS_KEY, mergedVersions);\n }\n\n // 4. Resolve thread/memory context\n const thread =\n typeof execOptions?.memory?.thread === 'string' ? { id: execOptions.memory.thread } : execOptions?.memory?.thread;\n const threadId = thread?.id;\n const resourceId = execOptions?.memory?.resource;\n let threadObject: StorageThreadType | undefined;\n let threadExists = false;\n\n // 5. Create MessageList\n const messageList = new MessageList({\n threadId,\n resourceId,\n });\n\n // Add agent instructions. Per-call `options.instructions` overrides the\n // agent's default instructions to mirror non-durable Agent.stream() behavior.\n const instructions = execOptions?.instructions || (await typedAgent.getInstructions({ requestContext }));\n if (instructions) {\n if (typeof instructions === 'string') {\n messageList.addSystem(instructions);\n } else if (Array.isArray(instructions)) {\n for (const inst of instructions) {\n messageList.addSystem(inst);\n }\n } else {\n messageList.addSystem(instructions);\n }\n }\n const workspace = await typedAgent.getWorkspace({ requestContext });\n\n // Durable preparation runs processInput processors below, but workspace\n // instructions are a processInputStep concern in the non-durable path.\n // Add them here once so durable runs get the same workspace context.\n if (workspace) {\n const hasFs =\n typeof workspace.hasFilesystemConfig === 'function' ? workspace.hasFilesystemConfig() : !!workspace.filesystem;\n const hasSb = typeof workspace.hasSandboxConfig === 'function' ? workspace.hasSandboxConfig() : !!workspace.sandbox;\n if (hasFs || hasSb) {\n const wsInstructions =\n typeof workspace.getInstructionsAsync === 'function'\n ? await workspace.getInstructionsAsync({ requestContext })\n : workspace.getInstructions({ requestContext });\n if (wsInstructions) {\n messageList.addSystem({ role: 'system', content: wsInstructions });\n }\n }\n }\n\n // Add context messages if provided\n if (execOptions?.context) {\n messageList.add(execOptions.context, 'context');\n }\n\n // Per-call `options.system` is appended as an additional system message after\n // context. Mirrors the non-durable Agent.stream() prepare-memory-step path.\n if (execOptions?.system) {\n const sys = execOptions.system;\n if (typeof sys === 'string') {\n messageList.addSystem(sys);\n } else if (Array.isArray(sys)) {\n for (const s of sys) {\n messageList.addSystem(s);\n }\n } else {\n messageList.addSystem(sys);\n }\n }\n\n // Add user messages\n messageList.add(messages, 'input');\n\n // 6. Establish the memory/thread context BEFORE resolving input processors.\n //\n // Memory.getInputProcessors() decides whether to add the working-memory\n // injector by reading requestContext.get('MastraMemory')?.memoryConfig. When\n // working memory is disabled in the constructor and enabled per-request (the\n // documented setup), that runtime config is the only signal that turns the\n // injector on. If we resolve processors before setting MastraMemory, the\n // per-request config is invisible, the chain falls back to the constructor\n // config, and the injector is silently omitted — so stored working memory is\n // saved by the update-working-memory tool but never read back into the prompt.\n // Setting the context first keeps read (inject) and write (tool) in sync.\n const memory = await typedAgent.getMemory({ requestContext });\n const memoryConfig = execOptions?.memory?.options;\n if (memory && threadId && resourceId) {\n const existingThread = await memory.getThreadById({ threadId });\n threadObject =\n existingThread ??\n (await memory.createThread({\n threadId,\n metadata: thread?.metadata,\n title: thread?.title,\n memoryConfig,\n resourceId,\n saveThread: true,\n }));\n threadExists = true;\n requestContext.set('MastraMemory', { thread: threadObject, resourceId, memoryConfig });\n } else {\n // This run has no complete per-request memory context. Clear any\n // MastraMemory inherited from a caller-provided requestContext (e.g. a\n // parent agent's context during sub-agent delegation) so processor\n // resolution can't pick up the working-memory injector from stale/parent\n // memory — that would both leak prior resource memory into this prompt and\n // break the \"no per-request memory options means no injection\" gate.\n requestContext.delete('MastraMemory');\n }\n\n // Resolve input processors now that the memory context is in place.\n const processorStates = new Map<string, ProcessorState>();\n let inputProcessors: InputProcessorOrWorkflow[] = [];\n let llmRequestInputProcessors: InputProcessorOrWorkflow[] = [];\n let outputProcessors: OutputProcessorOrWorkflow[] = [];\n let errorProcessors: ErrorProcessorOrWorkflow[] = [];\n\n try {\n inputProcessors = await typedAgent.listInputProcessors(requestContext);\n // Uncombined processors for processLLMRequest — combined (workflow-wrapped)\n // processors are skipped by ProcessorRunner.runProcessLLMRequest.\n llmRequestInputProcessors = await typedAgent.__listLLMRequestProcessors(requestContext);\n // Call-time outputProcessors replace constructor-level ones (parity with\n // Agent.listResolvedOutputProcessors which uses overrides-first semantics).\n outputProcessors = execOptions?.outputProcessors\n ? execOptions.outputProcessors\n : await typedAgent.listOutputProcessors(requestContext);\n errorProcessors = await typedAgent.listErrorProcessors(requestContext);\n } catch (error) {\n logger?.warn?.(`[DurableAgent] Error resolving processors: ${error}`);\n }\n\n // Open AGENT_RUN here so processor_run spans (and their MEMORY_OPERATION\n // children) parent to it. MODEL_GENERATION is opened later under it.\n //\n // Mirrors non-durable Agent.stream(): forward attributes (conversationId,\n // resolved instructions string, resolvedVersionId), metadata (entityVersionId),\n // and the agent-level tracingPolicy so durable runs land in the same span\n // shape as in-process runs.\n const rawConfig = typeof (agent as any).toRawConfig === 'function' ? (agent as any).toRawConfig() : undefined;\n const resolvedVersionId = rawConfig?.resolvedVersionId as string | undefined;\n const agentTracingPolicy =\n typeof (agent as any).getTracingPolicy === 'function' ? (agent as any).getTracingPolicy() : undefined;\n const agentSpan = getOrCreateSpan({\n type: SpanType.AGENT_RUN,\n name: `agent run: '${publicAgentId}'`,\n entityType: EntityType.AGENT,\n entityId: publicAgentId,\n entityName: publicAgentName,\n input: messages,\n attributes: {\n conversationId: threadId,\n instructions: convertInstructionsToString(instructions),\n // @deprecated — use entityVersionId (top-level span context field) instead.\n // Kept for backward compatibility during migration.\n ...(resolvedVersionId ? { resolvedVersionId } : {}),\n },\n metadata: {\n runId,\n resourceId,\n threadId,\n ...(resolvedVersionId ? { entityVersionId: resolvedVersionId } : {}),\n },\n tracingPolicy: agentTracingPolicy,\n tracingContext: execOptions?.tracingContext,\n tracingOptions: execOptions?.tracingOptions,\n requestContext,\n mastra,\n });\n // Run processInput (once, before execution) if we have any processors.\n // The MastraMemory context (thread + memoryConfig) was already established\n // above, before processor resolution, so processors that need it (working\n // memory, OM, message history) can access it here.\n let tripwireData: RunRegistryEntry['tripwire'];\n if (inputProcessors.length > 0) {\n try {\n const { ProcessorRunner } = await import('../../processors/runner');\n const runner = new ProcessorRunner({\n inputProcessors,\n outputProcessors,\n errorProcessors,\n logger: logger as any,\n agentName: publicAgentName,\n processorStates,\n });\n await runner.runInputProcessors(\n messageList,\n createObservabilityContext({ currentSpan: agentSpan }),\n requestContext,\n 0,\n );\n } catch (error) {\n if (error instanceof TripWire) {\n tripwireData = {\n reason: error.message,\n retry: error.options?.retry,\n metadata: error.options?.metadata,\n processorId: error.processorId,\n };\n logger?.warn?.('Input processor tripwire triggered', {\n agent: publicAgentName,\n reason: error.message,\n processorId: error.processorId,\n retry: error.options?.retry,\n });\n } else {\n logger?.warn?.(`[DurableAgent] Error running input processors: ${error}`);\n }\n }\n }\n\n // 7. Convert tools to CoreTool format for execution\n let tools: Record<string, CoreTool> = {};\n try {\n tools = await typedAgent.getToolsForExecution({\n toolsets: execOptions?.toolsets,\n clientTools: execOptions?.clientTools,\n threadId,\n resourceId,\n runId,\n requestContext,\n memoryConfig: execOptions?.memory?.options,\n autoResumeSuspendedTools: execOptions?.autoResumeSuspendedTools,\n hooks: execOptions?.hooks,\n delegation: execOptions?.delegation,\n methodType,\n });\n } catch (error) {\n logger?.warn?.(`[DurableAgent] Error converting tools: ${error}`);\n }\n\n // 8. Get model (and model list if configured)\n const model = await typedAgent.getModel({ requestContext });\n if (!model) {\n throw new Error('Agent model not available');\n }\n\n const modelList = await typedAgent.getModelList(requestContext);\n\n // 8b. Get scorers configuration\n const overrideScorers = (execOptions as any)?.scorers;\n let scorers: Record<string, { scorer: any; sampling?: any }> | undefined;\n\n if (overrideScorers) {\n scorers = overrideScorers;\n } else {\n try {\n const agentScorers = await typedAgent.listScorers({ requestContext });\n if (agentScorers && Object.keys(agentScorers).length > 0) {\n scorers = agentScorers;\n }\n } catch (error) {\n logger?.debug?.(`[DurableAgent] Error getting scorers: ${error}`);\n }\n }\n\n // 9. Create SaveQueueManager (memory + memoryConfig were resolved in step 6)\n const saveQueueManager = memory\n ? new SaveQueueManager({\n logger,\n memory,\n })\n : undefined;\n\n // 10. Serialize structured output if provided\n let serializedStructuredOutput: SerializableStructuredOutput | undefined;\n if (execOptions?.structuredOutput) {\n const so = execOptions.structuredOutput as any;\n if (so.schema) {\n serializedStructuredOutput = {\n jsonPromptInjection: so.jsonPromptInjection,\n useAgent: so.useAgent,\n };\n // Convert Zod schema to JSON Schema if possible\n if (typeof so.schema === 'object' && 'type' in so.schema) {\n serializedStructuredOutput.schema = so.schema;\n } else if (typeof so.schema === 'object' && 'jsonSchema' in so.schema) {\n serializedStructuredOutput.schema = so.schema.jsonSchema;\n }\n }\n }\n\n // 11. Get background task config. When the caller opts out with\n // `disableBackgroundTasks: true`, drop the manager so the registry entry\n // signals \"no background tasks for this run\" to the check step.\n const backgroundTasksConfig = typedAgent.getBackgroundTasksConfig?.();\n const backgroundTaskManager = execOptions?.disableBackgroundTasks ? undefined : mastra?.backgroundTaskManager;\n\n // Resolve tool payload transform policy with the same precedence the\n // non-durable Agent uses: per-call > agent-level > mastra-level. The\n // resolved policy carries a closure, so it lives on the run registry; the\n // JSON-safe `targets` shadow is serialized into workflow input below.\n const toolPayloadTransform =\n normalizeToolPayloadTransformPolicy(execOptions?.transform) ??\n typedAgent.getToolPayloadTransform?.() ??\n normalizeToolPayloadTransformPolicy(\n mastra?.getToolPayloadTransform?.() ?? (mastra as any)?.getToolPayloadProjection?.(),\n );\n\n // 12. Resolve memory persistence flags\n const savePerStep = execOptions?.savePerStep;\n const observationalMemory = !!memoryConfig?.observationalMemory;\n\n // 12b. Open MODEL_GENERATION under the AGENT_RUN opened in step 6, and export both\n // into the workflow input so each durable step can rebuild them. No-ops when\n // observability is off.\n const modelSpan = agentSpan?.createChildSpan({\n type: SpanType.MODEL_GENERATION,\n name: `llm: '${model.modelId}'`,\n attributes: {\n model: model.modelId,\n provider: model.provider,\n streaming: true,\n },\n metadata: {\n runId,\n threadId,\n resourceId,\n },\n requestContext,\n });\n\n // 13. Create serialized workflow input\n const workflowInput = createWorkflowInput({\n runId,\n agentId: publicAgentId,\n agentName: publicAgentName,\n messageList,\n tools,\n model,\n modelList: modelList ?? undefined,\n scorers,\n options: {\n maxSteps: execOptions?.maxSteps,\n toolChoice: execOptions?.toolChoice as any,\n activeTools: execOptions?.activeTools,\n modelSettings: execOptions?.modelSettings as any,\n // Function-form approval policies are closures that can't ride on the\n // serialized workflow input — the live closure is parked on the run\n // registry below. This boolean shadow is the cross-process fallback:\n // function policies degrade to \"require approval for every tool call\"\n // when the registry slot is unavailable (e.g. Inngest after a worker\n // restart), which is the safe default.\n requireToolApproval:\n typeof execOptions?.requireToolApproval === 'function' ? true : execOptions?.requireToolApproval,\n toolCallConcurrency: execOptions?.toolCallConcurrency,\n autoResumeSuspendedTools: execOptions?.autoResumeSuspendedTools,\n maxProcessorRetries: execOptions?.maxProcessorRetries,\n includeRawChunks: execOptions?.includeRawChunks,\n returnScorerData: (execOptions as any)?.returnScorerData,\n hasErrorProcessors: errorProcessors.length > 0,\n providerOptions: execOptions?.providerOptions,\n structuredOutput: serializedStructuredOutput,\n skipBgTaskWait: (execOptions as any)?._skipBgTaskWait,\n disableBackgroundTasks: execOptions?.disableBackgroundTasks,\n tracingOptions: execOptions?.tracingOptions,\n actor: execOptions?.actor,\n instructionsOverride: execOptions?.instructions,\n systemMessage: execOptions?.system,\n transform: toolPayloadTransform?.targets ? { targets: toolPayloadTransform.targets } : undefined,\n isTaskComplete: execOptions?.isTaskComplete\n ? {\n scorerNames: execOptions.isTaskComplete.scorers?.map(s => s.name).filter((n): n is string => !!n),\n strategy: execOptions.isTaskComplete.strategy,\n timeout: execOptions.isTaskComplete.timeout,\n parallel: execOptions.isTaskComplete.parallel,\n suppressFeedback: execOptions.isTaskComplete.suppressFeedback,\n }\n : undefined,\n },\n state: {\n memoryConfig,\n threadId,\n resourceId,\n threadExists,\n savePerStep,\n observationalMemory,\n },\n messageId,\n agentSpanData: agentSpan?.exportSpan(),\n modelSpanData: modelSpan?.exportSpan(),\n requestContextEntries: requestContextEntriesSnapshot,\n });\n\n // 14. Create registry entry for non-serializable state\n const registryEntry: RunRegistryEntry =