@mastra/core
Version:
1,437 lines • 263 kB
JavaScript
const require_rolldown_runtime = require("./rolldown-runtime-uwYp4b74.cjs");
const require_logger = require("./logger-BPclhj7J.cjs");
const require_inmemory = require("./inmemory-CVHnncRp.cjs");
const require_error = require("./error-B-e62x-A.cjs");
const require_event_emitter = require("./event-emitter-80LrFIBS.cjs");
const require_caching_pubsub = require("./caching-pubsub-DBB7kAgu.cjs");
const require_observability = require("./observability-BzV5axz0.cjs");
const require_utils = require("./utils-CNiGU0Uf.cjs");
require("./tracing-BUrUJwCM.cjs");
const require_request_context = require("./request-context-ByoZMp-j.cjs");
const require_llm = require("./llm-CmflaXHA.cjs");
const require_background_tasks = require("./background-tasks-lifNqs9M.cjs");
const require_toolchecks = require("./toolchecks-Rfz17G5p.cjs");
const require_utils$1 = require("./utils-Bw7FoAI3.cjs");
const require_utils_safe_stringify = require("./utils/safe-stringify.cjs");
const require_trip_wire = require("./trip-wire-dxd_uCHj.cjs");
const require_agent = require("./agent-DCD4MApC.cjs");
const require_payload_transform = require("./payload-transform-DJzDdfpE.cjs");
const require_signals = require("./signals-D2CulJo3.cjs");
const require_message_list = require("./message-list-BM7m-E-v.cjs");
const require_task_state_processor = require("./task-state-processor-BB7omHO3.cjs");
const require_workflows_constants = require("./workflows/constants.cjs");
const require_storage = require("./storage-C4FD5U8Z.cjs");
let _isaacs_ttlcache = require("@isaacs/ttlcache");
let stream_web = require("stream/web");
let zod = require("zod");
let _mastra_schema_compat_schema = require("@mastra/schema-compat/schema");
//#region src/agent/durable/durable-stream-until-idle.ts
/**
* Run `DurableAgent.streamUntilIdle` (or `DurableAgent.stream({ untilIdle })`).
* Initial turn invokes `agent.stream(messages, ...)`; continuations triggered
* by background-task completions run as fresh `agent.stream([], ...)` calls
* against the same memory thread.
*/
async function runDurableStreamUntilIdle(agent, messages, streamOptions, deps) {
const innerCleanups = [];
const innerAborts = [];
return require_agent.runIdleLoop(agent, streamOptions, deps, (opts) => agent.stream(messages, opts), (opts) => agent.stream([], opts), (first, ctx) => {
if (!ctx) return first;
return {
output: new Proxy(first.output, { get(target, prop) {
if (prop === "fullStream") return ctx.combinedStream;
const value = Reflect.get(target, prop, target);
return typeof value === "function" ? value.bind(target) : value;
} }),
get fullStream() {
return ctx.combinedStream;
},
runId: first.runId,
threadId: ctx.threadId,
resourceId: ctx.resourceId,
cleanup: ctx.forceClose,
abort: (reason) => {
for (const innerAbort of innerAborts) try {
innerAbort(reason);
} catch {}
ctx.forceClose();
}
};
}, {
onInnerResult: (inner) => {
if (typeof inner.cleanup === "function") innerCleanups.push(inner.cleanup);
if (typeof inner.abort === "function") innerAborts.push(inner.abort);
},
onForceClose: () => {
for (const fn of innerCleanups) try {
fn();
} catch {}
}
});
}
/**
* Run `DurableAgent.resume(..., { untilIdle })`. Same idle-loop semantics as
* `runDurableStreamUntilIdle` — initial turn calls `agent.resume(runId,
* resumeData, ...)` against the existing run snapshot, and subsequent
* continuations triggered by background-task completions use
* `agent.stream([], continuationOpts)` (a normal multi-turn agent stream)
* since the resume completes and we're back in regular conversation flow.
*/
async function runResumeDurableStreamUntilIdle(agent, runId, resumeData, streamOptions, deps) {
const innerCleanups = [];
const innerAborts = [];
return require_agent.runIdleLoop(agent, streamOptions, deps, (opts) => agent.resume(runId, resumeData, opts), (opts) => agent.stream([], opts), (first, ctx) => {
if (!ctx) return first;
return {
output: new Proxy(first.output, { get(target, prop) {
if (prop === "fullStream") return ctx.combinedStream;
const value = Reflect.get(target, prop, target);
return typeof value === "function" ? value.bind(target) : value;
} }),
get fullStream() {
return ctx.combinedStream;
},
runId: first.runId,
threadId: ctx.threadId,
resourceId: ctx.resourceId,
cleanup: ctx.forceClose,
abort: (reason) => {
for (const innerAbort of innerAborts) try {
innerAbort(reason);
} catch {}
ctx.forceClose();
}
};
}, {
onInnerResult: (inner) => {
if (typeof inner.cleanup === "function") innerCleanups.push(inner.cleanup);
if (typeof inner.abort === "function") innerAborts.push(inner.abort);
},
onForceClose: () => {
for (const fn of innerCleanups) try {
fn();
} catch {}
}
});
}
//#endregion
//#region src/agent/durable/utils/serialize-state.ts
/**
* Extract serializable metadata from a CoreTool
* This strips out the execute function and converts the schema to JSON Schema
*/
function serializeToolMetadata(name, tool) {
let inputSchema = { type: "object" };
if (tool.parameters) {
if ("type" in tool.parameters && typeof tool.parameters.type === "string") inputSchema = tool.parameters;
else if ("jsonSchema" in tool.parameters) inputSchema = tool.parameters.jsonSchema;
else if ("_def" in tool.parameters) inputSchema = { type: "object" };
}
return {
id: "id" in tool && typeof tool.id === "string" ? tool.id : name,
name,
description: tool.description,
inputSchema,
requireApproval: tool.requireApproval,
hasSuspendSchema: tool.hasSuspendSchema
};
}
/**
* Extract serializable metadata from all tools
*/
function serializeToolsMetadata(tools) {
return Object.entries(tools).map(([name, tool]) => serializeToolMetadata(name, tool));
}
/**
* Extract serializable model configuration
*/
function serializeModelConfig(model) {
return {
provider: model.provider,
modelId: model.modelId,
specificationVersion: model.specificationVersion,
originalConfig: `${model.provider}/${model.modelId}`
};
}
/**
* Extract serializable model list entry from AgentModelManagerConfig
*/
function serializeModelListEntry(entry) {
const model = entry.model;
return {
id: entry.id,
config: {
provider: model.provider,
modelId: model.modelId,
specificationVersion: model.specificationVersion,
originalConfig: `${model.provider}/${model.modelId}`,
providerOptions: entry.providerOptions
},
maxRetries: entry.maxRetries,
enabled: entry.enabled
};
}
/**
* Serialize an array of model configs into a model list.
* Filters out disabled models since they shouldn't be included in durable execution.
*/
function serializeModelList(models) {
return models.filter((m) => m.enabled !== false).map(serializeModelListEntry);
}
/**
* Serialize scorers configuration for durable execution.
*
* This extracts the scorer name (for resolution at runtime) and sampling config.
* The actual scorer objects are resolved from Mastra at step execution time.
*
* @param scorers The agent's scorers configuration (from agent.scorers or options.scorers)
* @returns Serializable scorer configuration
*/
function serializeScorersConfig(scorers) {
const result = {};
for (const [key, entry] of Object.entries(scorers)) {
const scorerEntry = { scorerName: typeof entry.scorer === "string" ? entry.scorer : entry.scorer.name };
if (entry.sampling) scorerEntry.sampling = entry.sampling;
result[key] = scorerEntry;
}
return result;
}
/**
* Extract serializable state from _internal-like objects
*/
function serializeDurableState(params) {
return {
memoryConfig: params.memoryConfig,
threadId: params.threadId,
resourceId: params.resourceId,
threadExists: params.threadExists,
savePerStep: params.savePerStep,
observationalMemory: params.observationalMemory
};
}
/**
* Pick the JSON-safe call settings out of an arbitrary `modelSettings` input.
* Drops any field that is not a primitive value of the expected type so that
* non-serializable fields (functions, AbortSignal, etc.) never reach the
* workflow input.
*/
function serializeModelSettings(settings) {
if (!settings || typeof settings !== "object") return void 0;
const source = settings;
const out = {};
const pickNumber = (key) => {
const value = source[key];
if (typeof value === "number" && Number.isFinite(value)) out[key] = value;
};
pickNumber("maxOutputTokens");
pickNumber("temperature");
pickNumber("topP");
pickNumber("topK");
pickNumber("presencePenalty");
pickNumber("frequencyPenalty");
pickNumber("seed");
pickNumber("maxRetries");
if (Array.isArray(source.stopSequences) && source.stopSequences.every((v) => typeof v === "string")) out.stopSequences = source.stopSequences;
return Object.keys(out).length > 0 ? out : void 0;
}
/**
* Extract serializable options from agent execution options
*/
function serializeDurableOptions(options) {
let serializedToolChoice;
if (options.toolChoice) {
if (typeof options.toolChoice === "string") serializedToolChoice = options.toolChoice;
else if (typeof options.toolChoice === "object" && "type" in options.toolChoice) {
if (options.toolChoice.type === "tool" && "toolName" in options.toolChoice) serializedToolChoice = {
type: "tool",
toolName: options.toolChoice.toolName
};
}
}
return {
maxSteps: options.maxSteps,
toolChoice: serializedToolChoice,
activeTools: options.activeTools,
modelSettings: serializeModelSettings(options.modelSettings),
requireToolApproval: options.requireToolApproval,
toolCallConcurrency: options.toolCallConcurrency,
autoResumeSuspendedTools: options.autoResumeSuspendedTools,
maxProcessorRetries: options.maxProcessorRetries,
includeRawChunks: options.includeRawChunks,
returnScorerData: options.returnScorerData,
hasErrorProcessors: options.hasErrorProcessors,
providerOptions: options.providerOptions,
structuredOutput: options.structuredOutput,
skipBgTaskWait: options.skipBgTaskWait,
disableBackgroundTasks: options.disableBackgroundTasks,
tracingOptions: options.tracingOptions,
actor: options.actor,
instructionsOverride: options.instructionsOverride,
systemMessage: options.systemMessage,
transform: options.transform,
isTaskComplete: options.isTaskComplete
};
}
/**
* Create the full workflow input from all components
*/
function createWorkflowInput(params) {
return {
__workflowKind: "durable-agent",
runId: params.runId,
agentId: params.agentId,
agentName: params.agentName,
messageListState: params.messageList.serialize(),
toolsMetadata: serializeToolsMetadata(params.tools),
modelConfig: serializeModelConfig(params.model),
modelList: params.modelList ? serializeModelList(params.modelList) : void 0,
scorers: params.scorers ? serializeScorersConfig(params.scorers) : void 0,
options: serializeDurableOptions(params.options),
state: serializeDurableState(params.state),
messageId: params.messageId,
agentSpanData: params.agentSpanData,
modelSpanData: params.modelSpanData,
requestContextEntries: params.requestContextEntries
};
}
/**
* Serialize an error for workflow state
*/
function serializeError(error) {
if (error instanceof Error) return {
name: error.name,
message: error.message,
stack: error.stack
};
return {
name: "Error",
message: String(error)
};
}
//#endregion
//#region src/agent/durable/preparation.ts
/**
* JSON-safe snapshot of `requestContext.entries()` so durable steps (e.g.
* is-task-complete scorers) can see the same `customContext` the non-durable
* path passes. Best-effort: entries that fail a JSON round-trip are skipped
* so a single non-serializable value can't break the workflow input.
*/
function snapshotRequestContextEntries(requestContext) {
if (!requestContext) return void 0;
const out = {};
let any = false;
for (const [key, value] of requestContext.entries()) {
const json = require_utils_safe_stringify.boundedStringify(value);
if (json === void 0) continue;
out[key] = JSON.parse(json);
any = true;
}
return any ? out : void 0;
}
/**
* Mirror of Agent#convertInstructionsToString — used for the AGENT_RUN span
* `attributes.instructions` field so durable runs publish the same shape as
* non-durable runs. Kept local to avoid promoting the private method.
*/
function convertInstructionsToString(instructions) {
if (!instructions) return "";
if (typeof instructions === "string") return instructions;
if (Array.isArray(instructions)) return instructions.map((msg) => typeof msg === "string" ? msg : typeof msg.content === "string" ? msg.content : "").filter(Boolean).join("\n\n");
return typeof instructions.content === "string" ? instructions.content : "";
}
/**
* Extract signal messages already present in the messageList at run start
* (from persisted history) so they can be echoed as data-signal stream parts
* on the first LLM step. Mirrors `prepare-memory-step.ts#getInitialSignalEchoes`.
*/
function getInitialSignalEchoes(messageList) {
const inputMessageIds = messageList.makeMessageSourceChecker().input;
return messageList.get.all.db().filter((message) => message.role === "signal" && inputMessageIds.has(message.id)).map(require_signals.mastraDBMessageToSignal);
}
/**
* Prepare for durable agent execution.
*
* This function performs the non-durable preparation phase:
* 1. Generates run ID and message ID
* 2. Resolves thread/memory context
* 3. Creates MessageList with instructions and messages
* 4. Converts tools to CoreTool format
* 5. Gets the model configuration
* 6. Creates serialized workflow input
* 7. Creates run registry entry for non-serializable state
*
* The result includes both the serialized workflow input (for the durable
* workflow) and the run registry entry (for non-serializable state).
*/
async function prepareForDurableExecution(options) {
const { agent, messages, options: rawExecOptions, optionsAreResolved = false, runId: providedRunId, requestContext: providedRequestContext, logger, mastra, methodType = "stream", durableAgentId, durableAgentName } = options;
const publicAgentId = durableAgentId ?? agent.id;
const publicAgentName = durableAgentName ?? agent.name ?? agent.id;
const typedAgent = agent;
const runId = providedRunId ?? crypto.randomUUID();
const messageId = crypto.randomUUID();
const requestContext = providedRequestContext ?? new require_request_context.RequestContext();
const requestContextEntriesSnapshot = snapshotRequestContextEntries(requestContext);
const execOptions = optionsAreResolved ? rawExecOptions ?? {} : require_utils$1.deepMerge(await typedAgent.getDefaultOptions({ requestContext }) ?? {}, rawExecOptions ?? {});
const requestVersions = requestContext.get(require_request_context.MASTRA_VERSIONS_KEY);
let mergedVersions = require_request_context.mergeVersionOverrides(mastra?.getVersionOverrides?.(), requestVersions);
if (execOptions?.versions) mergedVersions = require_request_context.mergeVersionOverrides(mergedVersions, execOptions.versions);
if (mergedVersions) requestContext.set(require_request_context.MASTRA_VERSIONS_KEY, mergedVersions);
const thread = typeof execOptions?.memory?.thread === "string" ? { id: execOptions.memory.thread } : execOptions?.memory?.thread;
const threadId = thread?.id;
const resourceId = execOptions?.memory?.resource;
let threadObject;
let threadExists = false;
const messageList = new require_message_list.MessageList({
threadId,
resourceId
});
const instructions = execOptions?.instructions || await typedAgent.getInstructions({ requestContext });
if (instructions) if (typeof instructions === "string") messageList.addSystem(instructions);
else if (Array.isArray(instructions)) for (const inst of instructions) messageList.addSystem(inst);
else messageList.addSystem(instructions);
const workspace = await typedAgent.getWorkspace({ requestContext });
if (workspace) {
const hasFs = typeof workspace.hasFilesystemConfig === "function" ? workspace.hasFilesystemConfig() : !!workspace.filesystem;
const hasSb = typeof workspace.hasSandboxConfig === "function" ? workspace.hasSandboxConfig() : !!workspace.sandbox;
if (hasFs || hasSb) {
const wsInstructions = typeof workspace.getInstructionsAsync === "function" ? await workspace.getInstructionsAsync({ requestContext }) : workspace.getInstructions({ requestContext });
if (wsInstructions) messageList.addSystem({
role: "system",
content: wsInstructions
});
}
}
if (execOptions?.context) messageList.add(execOptions.context, "context");
if (execOptions?.system) {
const sys = execOptions.system;
if (typeof sys === "string") messageList.addSystem(sys);
else if (Array.isArray(sys)) for (const s of sys) messageList.addSystem(s);
else messageList.addSystem(sys);
}
messageList.add(messages, "input");
const memory = await typedAgent.getMemory({ requestContext });
const memoryConfig = execOptions?.memory?.options;
if (memory && threadId && resourceId) {
threadObject = await memory.getThreadById({ threadId }) ?? await memory.createThread({
threadId,
metadata: thread?.metadata,
title: thread?.title,
memoryConfig,
resourceId,
saveThread: true
});
threadExists = true;
requestContext.set("MastraMemory", {
thread: threadObject,
resourceId,
memoryConfig
});
} else requestContext.delete("MastraMemory");
const processorStates = /* @__PURE__ */ new Map();
let inputProcessors = [];
let llmRequestInputProcessors = [];
let outputProcessors = [];
let errorProcessors = [];
try {
inputProcessors = await typedAgent.listInputProcessors(requestContext);
llmRequestInputProcessors = await typedAgent.__listLLMRequestProcessors(requestContext);
outputProcessors = execOptions?.outputProcessors ? execOptions.outputProcessors : await typedAgent.listOutputProcessors(requestContext);
errorProcessors = await typedAgent.listErrorProcessors(requestContext);
} catch (error) {
logger?.warn?.(`[DurableAgent] Error resolving processors: ${error}`);
}
const resolvedVersionId = (typeof agent.toRawConfig === "function" ? agent.toRawConfig() : void 0)?.resolvedVersionId;
const agentTracingPolicy = typeof agent.getTracingPolicy === "function" ? agent.getTracingPolicy() : void 0;
const agentSpan = require_utils.getOrCreateSpan({
type: "agent_run",
name: `agent run: '${publicAgentId}'`,
entityType: require_utils.EntityType.AGENT,
entityId: publicAgentId,
entityName: publicAgentName,
input: messages,
attributes: {
conversationId: threadId,
instructions: convertInstructionsToString(instructions),
...resolvedVersionId ? { resolvedVersionId } : {}
},
metadata: {
runId,
resourceId,
threadId,
...resolvedVersionId ? { entityVersionId: resolvedVersionId } : {}
},
tracingPolicy: agentTracingPolicy,
tracingContext: execOptions?.tracingContext,
tracingOptions: execOptions?.tracingOptions,
requestContext,
mastra
});
let tripwireData;
if (inputProcessors.length > 0) try {
const { ProcessorRunner } = await Promise.resolve().then(() => require("./trip-wire-dxd_uCHj.cjs")).then((n) => n.runner_exports);
await new ProcessorRunner({
inputProcessors,
outputProcessors,
errorProcessors,
logger,
agentName: publicAgentName,
processorStates
}).runInputProcessors(messageList, require_observability.createObservabilityContext({ currentSpan: agentSpan }), requestContext, 0);
} catch (error) {
if (error instanceof require_trip_wire.TripWire) {
tripwireData = {
reason: error.message,
retry: error.options?.retry,
metadata: error.options?.metadata,
processorId: error.processorId
};
logger?.warn?.("Input processor tripwire triggered", {
agent: publicAgentName,
reason: error.message,
processorId: error.processorId,
retry: error.options?.retry
});
} else logger?.warn?.(`[DurableAgent] Error running input processors: ${error}`);
}
let tools = {};
try {
tools = await typedAgent.getToolsForExecution({
toolsets: execOptions?.toolsets,
clientTools: execOptions?.clientTools,
threadId,
resourceId,
runId,
requestContext,
memoryConfig: execOptions?.memory?.options,
autoResumeSuspendedTools: execOptions?.autoResumeSuspendedTools,
hooks: execOptions?.hooks,
delegation: execOptions?.delegation,
methodType
});
} catch (error) {
logger?.warn?.(`[DurableAgent] Error converting tools: ${error}`);
}
const model = await typedAgent.getModel({ requestContext });
if (!model) throw new Error("Agent model not available");
const modelList = await typedAgent.getModelList(requestContext);
const overrideScorers = execOptions?.scorers;
let scorers;
if (overrideScorers) scorers = overrideScorers;
else try {
const agentScorers = await typedAgent.listScorers({ requestContext });
if (agentScorers && Object.keys(agentScorers).length > 0) scorers = agentScorers;
} catch (error) {
logger?.debug?.(`[DurableAgent] Error getting scorers: ${error}`);
}
const saveQueueManager = memory ? new require_agent.SaveQueueManager({
logger,
memory
}) : void 0;
let serializedStructuredOutput;
if (execOptions?.structuredOutput) {
const so = execOptions.structuredOutput;
if (so.schema) {
serializedStructuredOutput = {
jsonPromptInjection: so.jsonPromptInjection,
useAgent: so.useAgent
};
if (typeof so.schema === "object" && "type" in so.schema) serializedStructuredOutput.schema = so.schema;
else if (typeof so.schema === "object" && "jsonSchema" in so.schema) serializedStructuredOutput.schema = so.schema.jsonSchema;
}
}
const backgroundTasksConfig = typedAgent.getBackgroundTasksConfig?.();
const backgroundTaskManager = execOptions?.disableBackgroundTasks ? void 0 : mastra?.backgroundTaskManager;
const toolPayloadTransform = require_payload_transform.normalizeToolPayloadTransformPolicy(execOptions?.transform) ?? typedAgent.getToolPayloadTransform?.() ?? require_payload_transform.normalizeToolPayloadTransformPolicy(mastra?.getToolPayloadTransform?.() ?? mastra?.getToolPayloadProjection?.());
const savePerStep = execOptions?.savePerStep;
const observationalMemory = !!memoryConfig?.observationalMemory;
const modelSpan = agentSpan?.createChildSpan({
type: "model_generation",
name: `llm: '${model.modelId}'`,
attributes: {
model: model.modelId,
provider: model.provider,
streaming: true
},
metadata: {
runId,
threadId,
resourceId
},
requestContext
});
return {
runId,
messageId,
workflowInput: createWorkflowInput({
runId,
agentId: publicAgentId,
agentName: publicAgentName,
messageList,
tools,
model,
modelList: modelList ?? void 0,
scorers,
options: {
maxSteps: execOptions?.maxSteps,
toolChoice: execOptions?.toolChoice,
activeTools: execOptions?.activeTools,
modelSettings: execOptions?.modelSettings,
requireToolApproval: typeof execOptions?.requireToolApproval === "function" ? true : execOptions?.requireToolApproval,
toolCallConcurrency: execOptions?.toolCallConcurrency,
autoResumeSuspendedTools: execOptions?.autoResumeSuspendedTools,
maxProcessorRetries: execOptions?.maxProcessorRetries,
includeRawChunks: execOptions?.includeRawChunks,
returnScorerData: execOptions?.returnScorerData,
hasErrorProcessors: errorProcessors.length > 0,
providerOptions: execOptions?.providerOptions,
structuredOutput: serializedStructuredOutput,
skipBgTaskWait: execOptions?._skipBgTaskWait,
disableBackgroundTasks: execOptions?.disableBackgroundTasks,
tracingOptions: execOptions?.tracingOptions,
actor: execOptions?.actor,
instructionsOverride: execOptions?.instructions,
systemMessage: execOptions?.system,
transform: toolPayloadTransform?.targets ? { targets: toolPayloadTransform.targets } : void 0,
isTaskComplete: execOptions?.isTaskComplete ? {
scorerNames: execOptions.isTaskComplete.scorers?.map((s) => s.name).filter((n) => !!n),
strategy: execOptions.isTaskComplete.strategy,
timeout: execOptions.isTaskComplete.timeout,
parallel: execOptions.isTaskComplete.parallel,
suppressFeedback: execOptions.isTaskComplete.suppressFeedback
} : void 0
},
state: {
memoryConfig,
threadId,
resourceId,
threadExists,
savePerStep,
observationalMemory
},
messageId,
agentSpanData: agentSpan?.exportSpan(),
modelSpanData: modelSpan?.exportSpan(),
requestContextEntries: requestContextEntriesSnapshot
}),
registryEntry: {
tools,
saveQueueManager,
memory,
model,
modelList: modelList ? modelList.map((entry) => ({
id: entry.id,
model: entry.model,
maxRetries: entry.maxRetries ?? 0,
enabled: entry.enabled ?? true,
headers: entry.headers
})) : void 0,
workspace,
requestContext,
inputProcessors,
llmRequestInputProcessors,
outputProcessors,
errorProcessors,
processorStates,
backgroundTaskManager,
backgroundTasksConfig,
agentSpan,
modelSpan,
stopWhen: execOptions?.stopWhen,
onIterationComplete: execOptions?.onIterationComplete,
prepareStep: execOptions?.prepareStep,
toolPayloadTransform,
isTaskComplete: execOptions?.isTaskComplete,
requireToolApproval: execOptions?.requireToolApproval,
drainPendingSignals: (scope) => typedAgent.__getDrainPendingSignals()(runId, scope),
generateThreadTitle: memory ? async ({ threadId, resourceId, memoryConfig, messageListState, requestContext: rc, tracingContext }) => {
const thread = await memory.getThreadById?.({ threadId });
const mergedConfig = memory.getMergedThreadConfig?.(memoryConfig);
const { shouldGenerate, model, instructions, minMessages } = agent.resolveTitleGenerationConfig(mergedConfig?.generateTitle);
if (!shouldGenerate || thread?.title) return;
const titleMessageList = new require_message_list.MessageList().deserialize(messageListState);
const uiMessages = agent.filterUiMessagesByThread(titleMessageList, threadId, titleMessageList.get.all.ui());
if (uiMessages.length < (minMessages ?? 1)) return;
const userMessage = agent.getMostRecentUserMessage(uiMessages);
if (!userMessage) return;
const title = await agent.genTitle(userMessage, rc ?? new require_request_context.RequestContext(), require_observability.createObservabilityContext(tracingContext), model, instructions, uiMessages);
if (!title) return;
if (thread) await memory.updateThread({
id: threadId,
title,
metadata: thread.metadata ?? {},
memoryConfig
});
else await memory.createThread({
threadId,
resourceId,
memoryConfig,
title
});
} : void 0,
initialSignalEchoes: getInitialSignalEchoes(messageList),
goal: agent.__getGoalConfig(),
tripwire: tripwireData,
callTimeHeaders: extractCallTimeHeaders(execOptions?.modelSettings),
structuredOutput: execOptions?.structuredOutput?.schema ? {
...execOptions.structuredOutput,
schema: (0, _mastra_schema_compat_schema.toStandardSchema)(execOptions.structuredOutput.schema)
} : void 0,
cleanup: () => {}
},
messageList,
threadId,
resourceId
};
}
/**
* Extract string-valued headers from `modelSettings.headers` for storage on the
* in-process `RunRegistryEntry`. Returns `undefined` when no valid headers are
* present so the registry slot stays empty rather than carrying an empty object.
*/
function extractCallTimeHeaders(modelSettings) {
const raw = modelSettings?.headers;
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
const headers = {};
for (const [key, value] of Object.entries(raw)) if (typeof value === "string") headers[key] = value;
return Object.keys(headers).length > 0 ? headers : void 0;
}
//#endregion
//#region src/agent/durable/run-registry.ts
/**
* Global registry for accessing run entries from workflow steps.
* This is necessary because workflow steps don't have direct access to
* the DurableAgent instance's registry.
*
* Entries are keyed by runId (which are unique UUIDs).
*
* Uses TTLCache to prevent unbounded memory growth: entries auto-expire
* after 10 minutes (refreshed on access) and the registry is hard-capped
* at 1000 concurrent entries.
*/
const globalRunRegistry = new _isaacs_ttlcache.TTLCache({
max: 1e3,
ttl: 600 * 1e3,
updateAgeOnGet: true,
dispose: (entry) => {
entry?.cleanup?.();
},
noDisposeOnSet: true
});
/**
* End a run's root spans (MODEL_GENERATION then AGENT_RUN) with an error so the trace
* still exports — stores persist only span-end events. After a resume the fresh resume
* spans are the active root, so prefer them. Ending an already-ended span is a no-op,
* so the duplicate error paths (workflow failure + emitError) are safe. Never throws.
*/
function endRunSpansWithError(runId, error) {
try {
const entry = globalRunRegistry.get(runId);
(entry?.resumeModelSpan ?? entry?.modelSpan)?.error({
error,
endSpan: true
});
(entry?.resumeAgentSpan ?? entry?.agentSpan)?.error({
error,
endSpan: true
});
} catch {}
}
/**
* Registry for per-run non-serializable state.
*
* During durable execution, the DurableAgent needs to store non-serializable
* objects (tools with execute functions, SaveQueueManager, etc.) that can't
* flow through workflow state. This registry provides a way to store and
* retrieve these objects keyed by runId.
*
* The registry is scoped to a single DurableAgent instance and entries are
* cleaned up when a run completes.
*/
var RunRegistry = class {
#entries = /* @__PURE__ */ new Map();
/**
* Register non-serializable state for a run
* @param runId - The unique run identifier
* @param entry - The registry entry containing tools, saveQueueManager, etc.
*/
register(runId, entry) {
this.cleanup(runId);
this.#entries.set(runId, entry);
}
/**
* Get the registry entry for a run
* @param runId - The unique run identifier
* @returns The registry entry or undefined if not found
*/
get(runId) {
return this.#entries.get(runId);
}
/**
* Get tools for a specific run
* @param runId - The unique run identifier
* @returns The tools record or an empty object if not found
*/
getTools(runId) {
return this.#entries.get(runId)?.tools ?? {};
}
/**
* Get SaveQueueManager for a specific run
* @param runId - The unique run identifier
* @returns The SaveQueueManager or undefined if not found
*/
getSaveQueueManager(runId) {
return this.#entries.get(runId)?.saveQueueManager;
}
/**
* Get the language model for a specific run
* @param runId - The unique run identifier
* @returns The MastraLanguageModel or undefined if not found
*/
getModel(runId) {
return this.#entries.get(runId)?.model;
}
/**
* Check if a run is registered
* @param runId - The unique run identifier
* @returns True if the run is registered
*/
has(runId) {
return this.#entries.has(runId);
}
/**
* Cleanup and remove a run's entry from the registry
* @param runId - The unique run identifier
*/
cleanup(runId) {
const entry = this.#entries.get(runId);
if (entry) {
entry.cleanup?.();
this.#entries.delete(runId);
}
}
/**
* Get the number of active runs in the registry
*/
get size() {
return this.#entries.size;
}
/**
* Get all active run IDs
*/
get runIds() {
return Array.from(this.#entries.keys());
}
/**
* Clear all entries from the registry
* Calls cleanup on each entry before removing
*/
clear() {
for (const runId of this.#entries.keys()) this.cleanup(runId);
}
};
/**
* Extended run registry that also stores MessageList references and memory info
*/
var ExtendedRunRegistry = class extends RunRegistry {
#messageLists = /* @__PURE__ */ new Map();
#memoryInfo = /* @__PURE__ */ new Map();
/**
* Register non-serializable state for a run including MessageList
*/
registerWithMessageList(runId, entry, messageList, memoryInfo) {
this.register(runId, entry);
this.#messageLists.set(runId, messageList);
if (memoryInfo) this.#memoryInfo.set(runId, memoryInfo);
}
/**
* Get MessageList for a specific run
*/
getMessageList(runId) {
return this.#messageLists.get(runId);
}
/**
* Get memory info for a specific run
*/
getMemoryInfo(runId) {
return this.#memoryInfo.get(runId);
}
/**
* Override cleanup to also remove MessageList and memory info
*/
cleanup(runId) {
super.cleanup(runId);
this.#messageLists.delete(runId);
this.#memoryInfo.delete(runId);
}
/**
* Override clear to also clear MessageLists and memory info
*/
clear() {
super.clear();
this.#messageLists.clear();
this.#memoryInfo.clear();
}
};
//#endregion
//#region src/agent/durable/stream-adapter.ts
/**
* Map workflow usage (which may use legacy promptTokens/completionTokens) to
* the canonical LanguageModelUsage shape (inputTokens/outputTokens).
*/
function normalizeUsage(raw) {
if (!raw) return {
inputTokens: 0,
outputTokens: 0,
totalTokens: 0
};
const inputTokens = raw.inputTokens ?? raw.promptTokens ?? 0;
const outputTokens = raw.outputTokens ?? raw.completionTokens ?? 0;
return {
inputTokens,
outputTokens,
totalTokens: raw.totalTokens ?? inputTokens + outputTokens
};
}
/**
* Create a MastraModelOutput that streams from pubsub events.
*
* This adapter subscribes to the agent stream pubsub channel and converts
* pubsub events into a ReadableStream that MastraModelOutput can consume.
* Callbacks are invoked as events arrive.
*/
function createDurableAgentStream(options) {
const { pubsub, runId, messageId, model, threadId, resourceId, offset, idleTimeoutMs, isAlive, onChunk, onStepFinish, onFinish, onStreamFinished, onError, onSuspended, onAbort, onIterationComplete, logger, closeOnSuspend = false, structuredOutput, outputProcessors, experimentalTransform, messageList: externalMessageList } = options;
const logError = (message, error) => {
if (logger) logger.error(message, error);
else console.error(message, error);
};
const messageList = externalMessageList ?? new require_message_list.MessageList({
threadId,
resourceId
});
let isSubscribed = false;
let cancelled = false;
let terminated = false;
let controller = null;
let resolveReady;
let rejectReady;
const ready = new Promise((resolve, reject) => {
resolveReady = resolve;
rejectReady = reject;
});
let lastErrorMessage;
let idleTimer;
let idleGeneration = 0;
const clearIdleTimer = () => {
idleGeneration += 1;
if (idleTimer) {
clearTimeout(idleTimer);
idleTimer = void 0;
}
};
const markTerminated = () => {
terminated = true;
clearIdleTimer();
};
const onIdleTimeout = async (generation) => {
idleTimer = void 0;
if (cancelled || !controller || generation !== idleGeneration) return;
if (isAlive) {
let alive = true;
try {
alive = await isAlive();
} catch {
alive = true;
}
if (cancelled || !controller || generation !== idleGeneration) return;
if (alive) {
armIdleTimer();
return;
}
}
const error = /* @__PURE__ */ new Error(`Durable agent stream idle for ${idleTimeoutMs}ms with no live producer`);
require_trip_wire.safeEnqueue(controller, {
type: "error",
payload: { error }
});
require_trip_wire.safeClose(controller);
markTerminated();
try {
await onError?.({ error });
} catch (callbackError) {
logError(`[DurableAgentStream] onError callback error:`, callbackError);
} finally {
cleanup();
}
};
const armIdleTimer = () => {
if (idleTimeoutMs === void 0 || idleTimeoutMs <= 0 || cancelled || terminated || !isSubscribed || !controller) return;
clearIdleTimer();
const generation = idleGeneration;
idleTimer = setTimeout(() => {
onIdleTimeout(generation);
}, idleTimeoutMs);
};
const handleEvent = async (event) => {
if (!controller) return;
armIdleTimer();
const streamEvent = event;
try {
switch (streamEvent.type) {
case require_agent.AgentStreamEventTypes.CHUNK: {
const chunk = streamEvent.data;
if (chunk.type === "error") {
const errPayload = chunk.payload;
lastErrorMessage = errPayload?.error?.message || errPayload?.message || "LLM execution error";
}
require_trip_wire.safeEnqueue(controller, chunk);
await onChunk?.(chunk);
break;
}
case require_agent.AgentStreamEventTypes.STEP_START: {
const chunk = streamEvent.data;
if (chunk && "type" in chunk) require_trip_wire.safeEnqueue(controller, chunk);
break;
}
case require_agent.AgentStreamEventTypes.STEP_FINISH: {
const data = streamEvent.data;
await onStepFinish?.(data);
break;
}
case require_agent.AgentStreamEventTypes.FINISH: {
const data = streamEvent.data;
const finishChunk = {
type: "finish",
payload: {
output: data.output,
stepResult: data.stepResult
}
};
require_trip_wire.safeEnqueue(controller, finishChunk);
require_trip_wire.safeClose(controller);
markTerminated();
if (onFinish) try {
const steps = data.output?.steps ?? [];
const allToolResults = steps.flatMap((s) => s?.toolResults ?? []);
const allToolCalls = steps.flatMap((s) => s?.toolCalls ?? []);
await onFinish({
text: data.output?.text ?? "",
steps,
toolResults: allToolResults,
toolCalls: allToolCalls,
dynamicToolCalls: [],
dynamicToolResults: [],
staticToolCalls: [],
staticToolResults: [],
files: [],
sources: [],
reasoning: [],
content: [],
finishReason: data.stepResult?.reason ?? "stop",
usage: normalizeUsage(data.output?.usage),
totalUsage: normalizeUsage(data.output?.usage),
warnings: data.stepResult?.warnings ?? [],
request: { body: void 0 },
response: {},
reasoningText: void 0,
providerMetadata: void 0
});
} catch (callbackError) {
logError(`[DurableAgentStream] onFinish callback error:`, callbackError);
}
if (onAbort && data.stepResult?.reason === "abort") try {
await onAbort({ steps: data.output?.steps ?? [] });
} catch (callbackError) {
logError(`[DurableAgentStream] onAbort (from FINISH) callback error:`, callbackError);
}
if (onError && data.stepResult?.reason === "error") try {
await onError({ error: new Error(lastErrorMessage || "LLM execution error") });
} catch (callbackError) {
logError(`[DurableAgentStream] onError (from FINISH) callback error:`, callbackError);
}
try {
await onStreamFinished?.();
} catch (callbackError) {
logError(`[DurableAgentStream] onStreamFinished callback error:`, callbackError);
}
break;
}
case require_agent.AgentStreamEventTypes.ERROR: {
const data = streamEvent.data;
const error = new Error(data.error.message);
error.name = data.error.name;
if (data.error.stack) error.stack = data.error.stack;
require_trip_wire.safeEnqueue(controller, {
type: "error",
payload: { error }
});
require_trip_wire.safeClose(controller);
markTerminated();
try {
await onError?.({ error });
} catch (callbackError) {
logError(`[DurableAgentStream] onError callback error:`, callbackError);
}
break;
}
case require_agent.AgentStreamEventTypes.SUSPENDED: {
const data = streamEvent.data;
if (closeOnSuspend) {
markTerminated();
try {
await onSuspended?.(data);
} finally {
require_trip_wire.safeClose(controller);
}
} else await onSuspended?.(data);
break;
}
case require_agent.AgentStreamEventTypes.ABORT: {
const data = streamEvent.data;
markTerminated();
try {
await onAbort?.(data);
} catch (callbackError) {
logError(`[DurableAgentStream] onAbort callback error:`, callbackError);
}
require_trip_wire.safeClose(controller);
break;
}
case require_agent.AgentStreamEventTypes.ITERATION_COMPLETE: {
const data = streamEvent.data;
try {
await onIterationComplete?.(data);
} catch (callbackError) {
logError(`[DurableAgentStream] onIterationComplete callback error:`, callbackError);
}
break;
}
default: break;
}
} catch (error) {
logError(`[DurableAgentStream] Error handling event ${streamEvent.type}:`, error);
}
};
const stream = new stream_web.ReadableStream({
start(ctrl) {
controller = ctrl;
const topic = require_agent.AGENT_STREAM_TOPIC(runId);
(offset !== void 0 ? pubsub.subscribeFromOffset(topic, offset, handleEvent) : pubsub.subscribeWithReplay(topic, handleEvent)).then(() => {
if (cancelled) {
pubsub.unsubscribe(topic, handleEvent).catch((error) => {
logError(`[DurableAgentStream] Failed to unsubscribe from ${topic}:`, error);
});
resolveReady();
return;
}
isSubscribed = true;
armIdleTimer();
resolveReady();
}).catch((error) => {
logError(`[DurableAgentStream] Failed to subscribe to ${topic}:`, error);
rejectReady(error);
ctrl.error(error);
});
},
cancel() {
cleanup();
}
});
const cleanup = () => {
markTerminated();
cancelled = true;
if (isSubscribed) {
isSubscribed = false;
const topic = require_agent.AGENT_STREAM_TOPIC(runId);
pubsub.unsubscribe(topic, handleEvent).catch((error) => {
logError(`[DurableAgentStream] Failed to unsubscribe from ${topic}:`, error);
});
}
controller = null;
};
return {
output: new require_trip_wire.MastraModelOutput({
model,
stream,
messageList,
messageId,
options: {
runId,
onStepFinish,
structuredOutput,
isLLMExecutionStep: true,
resolveFinalPromises: true,
outputProcessors,
experimentalTransform
}
}),
cleanup,
ready
};
}
/**
* Helper to emit a chunk event to pubsub
*/
async function emitChunkEvent(pubsub, runId, chunk) {
const topic = require_agent.AGENT_STREAM_TOPIC(runId);
await pubsub.publish(topic, {
type: require_agent.AgentStreamEventTypes.CHUNK,
runId,
data: chunk
});
}
/**
* Helper to emit a step start event to pubsub.
* The `data` payload must include `type: 'step-start'` so the stream-adapter
* consumer recognises it as a `ChunkType` and enqueues it onto the client stream.
*/
async function emitStepStartEvent(pubsub, runId, data) {
await pubsub.publish(require_agent.AGENT_STREAM_TOPIC(runId), {
type: require_agent.AgentStreamEventTypes.STEP_START,
runId,
data: {
type: "step-start",
...data
}
});
}
/**
* Helper to emit a step finish event to pubsub
*/
async function emitStepFinishEvent(pubsub, runId, data) {
await pubsub.publish(require_agent.AGENT_STREAM_TOPIC(runId), {
type: require_agent.AgentStreamEventTypes.STEP_FINISH,
runId,
data
});
}
/**
* Helper to emit a finish event to pubsub
*/
async function emitFinishEvent(pubsub, runId, data) {
await pubsub.publish(require_agent.AGENT_STREAM_TOPIC(runId), {
type: require_agent.AgentStreamEventTypes.FINISH,
runId,
data
});
}
/**
* Helper to emit an error event to pubsub
*/
async function emitErrorEvent(pubsub, runId, error) {
await pubsub.publish(require_agent.AGENT_STREAM_TOPIC(runId), {
type: require_agent.AgentStreamEventTypes.ERROR,
runId,
data: { error: {
name: error.name,
message: error.message
} }
});
}
/**
* Helper to emit a suspended event to pubsub
*/
async function emitSuspendedEvent(pubsub, runId, data) {
await pubsub.publish(require_agent.AGENT_STREAM_TOPIC(runId), {
type: require_agent.AgentStreamEventTypes.SUSPENDED,
runId,
data
});
}
/**
* Helper to emit an iteration-complete event to pubsub
*/
async function emitIterationCompleteEvent(pubsub, runId, data) {
await pubsub.publish(require_agent.AGENT_STREAM_TOPIC(runId), {
type: require_agent.AgentStreamEventTypes.ITERATION_COMPLETE,
runId,
data
});
}
//#endregion
//#region src/agent/durable/workflows/shared/schemas.ts
/**
* Shared Zod schemas for durable agentic workflows.
*
* These schemas are used by:
* - Core DurableAgent workflow
* - Inngest durable agent workflow
* - Evented durable agent workflow (future)
*/
/**
* Schema for model configuration
*/
const modelConfigSchema = zod.z.object({
provider: zod.z.string(),
modelId: zod.z.string(),
specificationVersion: zod.z.string().optional(),
settings: zod.z.record(zod.z.string(), zod.z.any()).optional(),
providerOptions: zod.z.record(zod.z.string(), zod.z.any()).optional()
});
/**
* Schema for model list entry (fallback support)
*/
const modelListEntrySchema = zod.z.object({
id: zod.z.string(),
config: zod.z.object({
provider: zod.z.string(),
modelId: zod.z.string(),
specificationVersion: zod.z.string().optional(),
originalConfig: zod.z.union([zod.z.string(), zod.z.record(zod.z.string(), zod.z.any())]).optional(),
providerOptions: zod.z.record(zod.z.string(), zod.z.any()).optional()
}),
maxRetries: zod.z.number(),
enabled: zod.z.boolean()
});
/**
* Schema for accumulated usage across iterations
*/
const accumulatedUsageSchema = zod.z.object({
inputTokens: zod.z.number(),
outputTokens: zod.z.number(),
totalTokens: zod.z.number()
});
/**
* Schema for output from the durable agentic workflow
*/
const durableAgenticOutputSchema = zod.z.object({
messageListState: zod.z.any(),
messageId: zod.z.string(),
stepResult: zod.z.any(),
output: zod.z.object({
text: zod.z.string().optional(),
usage: zod.z.any(),
steps: zod.z.array(zod.z.any())
}),
state: zod.z.any()
});
/**
* Base schema for durable agentic workflow input.
* Implementations can extend this with additional fields.
*/
const baseDurableAgenticInputSchema = zod.z.object({
runId: zod.z.string(),
agentId: zod.z.string(),
agentName: zod.z.string().optional(),
messageListState: zod.z.any(),
toolsMetadata: zod.z.array(zod.z.any()),
modelConfig: modelConfigSchema,
options: zod.z.any(),
state: zod.z.any(),
messageId: zod.z.string()
});
/**
* Base schema for iteration state.
* Implementations can extend this with additional fields.
*/
const baseIterationStateSchema = zod.z.object({
runId: zod.z.string(),
agentId: zod.z.string(),
agentName: zod.z.string().optional(),
messageListState: zod.z.any(),
toolsMetadata: zod.z.array(zod.z.any()),
modelConfig: zod.z.any(),
options: zod.z.any(),
state: zod.z.any(),
messageId: zod.z.string(),
iterationCount: zod.z.number(),
accumulatedSteps: zod.z.array(zod.z.any()),
accumulatedUsage: accumulatedUsageSchema,
lastStepResult: zod.z.any().optional(),
backgroundTaskPending: zod.z.boolean().optional(),
delegationBailed: zod.z.boolean().optional(),
pendingFeedbackStop: zod.z.boolean().optional(),
agentSpanData: zod.z.any().optional(),
modelSpanData: zod.z.any().optional()
});
//#endregion
//#region src/agent/durable/workflows/shared/iteration-state.ts
/**
* Calculate accumulated usage from current state and new execution output.
*/
function calculateAccumulatedUsage(currentUsage, executionUsage) {
return {
inputTokens: currentUsage.inputTokens + (executionUsage?.inputTokens || 0),
outputTokens: currentUsage.outputTokens + (executionUsage?.outputTokens || 0),
totalTokens: currentUsage.totalTokens + (executionUsage?.totalTokens || 0)
};
}
/**
* Build a step record from execution output.
*/
function buildStepRecord(executionOutput) {
return {
text: executionOutput.output.text,
toolCalls: executionOutput.output.toolCalls,
toolResults: executionOutput.toolResults,
usage: executionOutput.output.usage,
finishReason: executionOutput.stepResult.reason
};
}
/**
* Create the base iteration state update.
*
* This returns the common fields for iteration state updates.
* Implementations can extend this with their specific fields.
*
* @example
* ```typescript
* const baseUpdate = createBaseIterationStateUpdate({
* currentState: initData,
* executionOutput,
* });
*
* // Core extends with modelList
* const coreState = { ...baseUpdate, modelList: initData.modelList };
*
* // Inngest extends with observability
* const inngestState = {
* ...baseUpdate,
* agentSpanData: initData.agentSpanData,
* modelSpanData: initData.modelSpanData,
* stepIndex: initData.stepIndex + 1,
* };
* ```
*/
function createBaseIterationStateUpdate(input) {
const { currentState, executionOutput } = input;
const newUsage = calculateAccumulatedUsage(currentState.accumulatedUsage, executionOutput.output.usage);
const stepRecord = buildStepRecord(executionOutput);
return {
runId: currentState.runId,
agentId: currentState.agentId,
agentName: currentState.agentName,
messageListState: executionOutput.messageListState,
toolsMetadata: currentState.toolsMetadata,
modelConfig: currentState.modelConfig,
options: currentState.options,
state: executionOutput.state,
messageId: executionOutput.messageId,
iterationCount: currentState.iterationCount + 1,
accumulatedSteps: [...currentState.accumulatedSteps, stepRecord],
accumulatedUsage: newUsage,
lastStepResult: executionOutput.stepResult,
backgroundTaskPending: executionOutput.backgroundTaskPending,
delegationBailed: executionOutput.delegationBailed,
pendingFeedbackStop: currentState.pendingFeedbackStop,
agentSpanData: currentState.agentSpanData,
modelSpanData: currentState.modelSpanData
};
}
//#endregion
//#region src/agent/durable/workflows/shared/tool-call-concurrency.ts
/**
* Resolves the effective tool-call foreach concurrency for a durable agentic
* workflow from the serialized workflow input (iteration state) and the
* step's tool calls.
*
* Mirrors @mastra/core's non-durable loop semantics
* (loop/workflows/agentic-execution/tool-call-concurrency.ts):
* - Global `requireToolApproval` forces sequential execution. The serialized
* boolean shadow is `true` for function-form policies, so those degrade
* safely to sequential as well.
* - Any tool in the step's *effective active tool set* with `requireApproval`
* or `hasSuspendSchema` forces sequential execution so approval/suspension
* flows never race with concurrent tool calls. The check is against the
* active tool set, NOT the tools the m