@mastra/core
Version:
Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.
1,534 lines (1,525 loc) • 122 kB
JavaScript
'use strict';
var chunkVEYVZLLD_cjs = require('../../chunk-VEYVZLLD.cjs');
var chunkACQ5CVFF_cjs = require('../../chunk-ACQ5CVFF.cjs');
var chunkZCBG4ZQT_cjs = require('../../chunk-ZCBG4ZQT.cjs');
var chunkCVF4W47C_cjs = require('../../chunk-CVF4W47C.cjs');
var chunk6BHHFTLL_cjs = require('../../chunk-6BHHFTLL.cjs');
var chunkBRVYVULM_cjs = require('../../chunk-BRVYVULM.cjs');
var chunk7JE7FOGJ_cjs = require('../../chunk-7JE7FOGJ.cjs');
var chunk2E7FPUYL_cjs = require('../../chunk-2E7FPUYL.cjs');
var chunkD3BJ5NBH_cjs = require('../../chunk-D3BJ5NBH.cjs');
var chunkM5AKMHS2_cjs = require('../../chunk-M5AKMHS2.cjs');
var chunkRQ4MB5TM_cjs = require('../../chunk-RQ4MB5TM.cjs');
var chunkCAVARKYS_cjs = require('../../chunk-CAVARKYS.cjs');
var ttlcache = require('@isaacs/ttlcache');
var web = require('stream/web');
var zod = require('zod');
// src/agent/durable/constants.ts
var AGENT_STREAM_TOPIC = (runId) => `agent.stream.${runId}`;
var AgentStreamEventTypes = {
/** Chunk of streaming data (text, tool call, etc.) */
CHUNK: "chunk",
/** Start of a new step in the agentic loop */
STEP_START: "step-start",
/** End of a step in the agentic loop */
STEP_FINISH: "step-finish",
/** Agent execution completed successfully */
FINISH: "finish",
/** Error occurred during execution */
ERROR: "error",
/** Workflow suspended (e.g., for tool approval) */
SUSPENDED: "suspended"
};
var DurableAgentDefaults = {
/** Default maximum number of agentic loop iterations */
MAX_STEPS: 5,
/**
* Default tool call concurrency.
* NOTE: Currently unused — durable workflows run tool calls sequentially
* (concurrency: 1) because tool approval and suspension require sequential
* execution. The serialized toolCallConcurrency option is preserved in
* workflow input for future use when dynamic foreach concurrency is supported.
*/
TOOL_CALL_CONCURRENCY: 10
};
var DurableStepIds = {
/** LLM execution step */
LLM_EXECUTION: "durable-llm-execution",
/** Tool call step */
TOOL_CALL: "durable-tool-call",
/** LLM mapping step (combines results) */
LLM_MAPPING: "durable-llm-mapping",
/** Agentic execution workflow (one iteration) */
AGENTIC_EXECUTION: "durable-agentic-execution",
/** Full agentic loop workflow */
AGENTIC_LOOP: "durable-agentic-loop",
/** Scorer execution step */
SCORER_EXECUTION: "durable-scorer-execution"
};
// src/agent/durable/durable-stream-until-idle.ts
var TERMINAL_BG_CHUNKS = /* @__PURE__ */ new Set([
"background-task-completed",
"background-task-failed",
"background-task-cancelled",
// Suspended is non-terminal for the bg task itself (it can be resumed
// later via `manager.resume`), but it IS terminal-for-this-iteration of
// the streamUntilIdle wrapper: the agent should react to the suspend in
// a follow-up turn so the user is told the task is parked. Without
// this, the wrapper waits indefinitely for completed/failed/cancelled
// and the stream times out.
"background-task-suspended"
]);
async function resolveScope(agent, mergedOptions) {
const requestContext = mergedOptions?.requestContext ?? new chunkCAVARKYS_cjs.RequestContext();
const memory = await agent.getMemory();
if (!memory) return null;
const threadIdFromContext = requestContext.get(chunkCAVARKYS_cjs.MASTRA_THREAD_ID_KEY);
const resourceIdFromContext = requestContext.get(chunkCAVARKYS_cjs.MASTRA_RESOURCE_ID_KEY);
const threadIdFromArgs = typeof mergedOptions?.memory?.thread === "string" ? mergedOptions.memory.thread : mergedOptions?.memory?.thread?.id;
const threadId = threadIdFromContext ?? threadIdFromArgs;
const resourceId = resourceIdFromContext ?? mergedOptions?.memory?.resource;
const scopeKey = threadId || resourceId ? `${threadId ?? ""}|${resourceId ?? ""}` : null;
return { threadId, resourceId, scopeKey };
}
function buildContinuationDirective(batch) {
const entries = batch.map((chunk) => {
const payload = chunk.payload ?? {};
return {
toolCallId: payload.toolCallId,
toolName: payload.toolName,
isSuspended: !!payload.suspendedAt
};
}).filter((e) => !!e.toolCallId);
const idList = entries.filter((e) => !e.isSuspended).map((e) => e.toolName ? `${e.toolCallId} (${e.toolName})` : e.toolCallId).join(", ");
const suspendedIdList = entries.filter((e) => e.isSuspended).map((e) => `${e.toolCallId} (${e.toolName})`).join(", ");
return `Background task(s) you previously dispatched have completed. Process ONLY these tool-call IDs (their results are now in the conversation): ${idList}. IMPORTANT: Do NOT process any tool-call IDs that were not in the list, and do NOT call the same tool again \u2014 the result is already available. Use these result(s) to answer the user's original question.IMPORTANT: The following tool-call IDs are suspended: ${suspendedIdList}. Do not attempt to resume them; let the user know they are waiting for explicit resume input.`;
}
function buildContinuationOpts(baseContinuationOpts, callerContext, batch) {
const directive = buildContinuationDirective(batch);
return {
...baseContinuationOpts,
context: [...callerContext ?? [], { role: "user", content: directive }]
};
}
function acquireStreamSlot(activeStreams, scopeKey, closer) {
if (!scopeKey) return;
const priorClose = activeStreams.get(scopeKey);
priorClose?.();
activeStreams.set(scopeKey, closer);
}
function releaseStreamSlot(activeStreams, scopeKey, closer) {
if (!scopeKey) return;
if (activeStreams.get(scopeKey) === closer) {
activeStreams.delete(scopeKey);
}
}
async function runDurableStreamUntilIdle(agent, messages, streamOptions, deps) {
const { maxIdleMs: _maxIdleMs, ...restStreamOptions } = streamOptions ?? {};
const defaultOptions = await agent.getDefaultOptions({
requestContext: streamOptions?.requestContext
});
const mergedOptions = chunk7JE7FOGJ_cjs.deepMerge(
defaultOptions,
restStreamOptions ?? {}
);
const scope = await resolveScope(agent, mergedOptions);
if (!deps.bgManager || !scope) {
return agent.stream(messages, restStreamOptions);
}
const { threadId, resourceId, scopeKey } = scope;
const maxIdleMs = _maxIdleMs ?? 5 * 6e4;
const baseContinuationOpts = {
...restStreamOptions ?? {},
onFinish: void 0,
_skipBgTaskWait: true
};
const initialStreamOpts = {
...restStreamOptions ?? {},
_skipBgTaskWait: true
};
const runningTaskIds = /* @__PURE__ */ new Set();
const pendingCompletions = [];
const processedTerminalKeys = /* @__PURE__ */ new Set();
const innerCleanups = [];
let isProcessing = false;
let closed = false;
let idleTimer;
let outerController;
const outerAbort = new AbortController();
let firstRunId;
const forceClose = () => {
if (closed) return;
closed = true;
if (idleTimer) {
clearTimeout(idleTimer);
idleTimer = void 0;
}
outerAbort.abort();
try {
outerController.close();
} catch {
}
for (const fn of innerCleanups) {
try {
fn();
} catch {
}
}
releaseStreamSlot(deps.activeStreams, scopeKey, forceClose);
};
const tryClose = () => {
if (closed) return;
if (isProcessing) return;
if (runningTaskIds.size > 0) return;
if (pendingCompletions.length > 0) return;
forceClose();
};
const clearIdleTimer = () => {
if (idleTimer) {
clearTimeout(idleTimer);
idleTimer = void 0;
}
};
const updateIdleTimer = () => {
if (closed) return;
clearIdleTimer();
if (isProcessing) return;
if (runningTaskIds.size === 0) return;
if (pendingCompletions.length > 0) return;
idleTimer = setTimeout(forceClose, maxIdleMs);
};
const pipeInner = async (inner) => {
const reader = inner.getReader();
try {
while (true) {
if (outerAbort.signal.aborted) break;
const { done, value } = await reader.read();
if (done) break;
clearIdleTimer();
try {
outerController.enqueue(value);
} catch {
break;
}
if (value && typeof value === "object" && value.type === "background-task-started") {
const taskId = value.payload?.taskId;
if (taskId) runningTaskIds.add(taskId);
}
}
} finally {
reader.releaseLock();
}
};
const processIfIdle = async () => {
if (isProcessing || closed || pendingCompletions.length === 0) return;
isProcessing = true;
try {
const batch = pendingCompletions.splice(0, pendingCompletions.length);
for (const chunk of batch) {
const tid = chunk.payload?.taskId;
const ctype = chunk.type;
if (tid && ctype) processedTerminalKeys.add(`${tid}:${ctype}`);
}
const continuationOpts = buildContinuationOpts(baseContinuationOpts, restStreamOptions?.context, batch);
const inner = await agent.stream([], continuationOpts);
innerCleanups.push(inner.cleanup);
await pipeInner(inner.fullStream);
} catch (err) {
try {
outerController.error(err);
} catch {
}
forceClose();
return;
} finally {
isProcessing = false;
if (pendingCompletions.length > 0) {
void processIfIdle();
} else {
tryClose();
updateIdleTimer();
}
}
};
acquireStreamSlot(deps.activeStreams, scopeKey, forceClose);
streamOptions?.abortSignal?.addEventListener("abort", forceClose);
const combinedStream = new ReadableStream({
start(controller) {
outerController = controller;
},
cancel() {
closed = true;
outerAbort.abort();
clearIdleTimer();
}
});
const bgStream = deps.bgManager.stream({
agentId: agent.id,
threadId,
resourceId,
abortSignal: outerAbort.signal
});
const bgReader = bgStream.getReader();
void (async () => {
try {
while (true) {
if (outerAbort.signal.aborted) break;
const { done, value } = await bgReader.read();
if (done) break;
const chunk = value;
if (!chunk || typeof chunk !== "object" || typeof chunk.type !== "string") continue;
const taskId = chunk.payload?.taskId;
const terminalKey = taskId && TERMINAL_BG_CHUNKS.has(chunk.type) ? `${taskId}:${chunk.type}` : void 0;
if (terminalKey && processedTerminalKeys.has(terminalKey)) {
continue;
}
updateIdleTimer();
try {
outerController.enqueue(chunk);
} catch {
break;
}
if (!taskId) continue;
if (chunk.type === "background-task-running") {
runningTaskIds.add(taskId);
} else if (TERMINAL_BG_CHUNKS.has(chunk.type)) {
runningTaskIds.delete(taskId);
pendingCompletions.push(chunk);
void processIfIdle();
}
}
} catch {
} finally {
bgReader.releaseLock();
}
})();
isProcessing = true;
clearIdleTimer();
let first;
try {
first = await agent.stream(messages, initialStreamOpts);
} catch (err) {
forceClose();
throw err;
}
firstRunId = first.runId;
innerCleanups.push(first.cleanup);
void (async () => {
try {
await pipeInner(first.fullStream);
} catch (err) {
try {
outerController.error(err);
} catch {
}
}
isProcessing = false;
if (pendingCompletions.length > 0) {
void processIfIdle();
} else {
tryClose();
updateIdleTimer();
}
})();
return {
output: new Proxy(first.output, {
get(target, prop) {
if (prop === "fullStream") return combinedStream;
const value = Reflect.get(target, prop, target);
return typeof value === "function" ? value.bind(target) : value;
}
}),
get fullStream() {
return combinedStream;
},
runId: firstRunId,
threadId,
resourceId,
cleanup: forceClose
};
}
// src/agent/durable/utils/serialize-state.ts
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
};
}
function serializeToolsMetadata(tools) {
return Object.entries(tools).map(([name, tool]) => serializeToolMetadata(name, tool));
}
function serializeModelConfig(model) {
return {
provider: model.provider,
modelId: model.modelId,
specificationVersion: model.specificationVersion,
// Store the original config string for runtime resolution (e.g., 'openai/gpt-4o')
originalConfig: `${model.provider}/${model.modelId}`
// Note: We don't serialize model settings here - they come from execution options
};
}
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
};
}
function serializeModelList(models) {
return models.filter((m) => m.enabled !== false).map(serializeModelListEntry);
}
function serializeScorersConfig(scorers) {
const result = {};
for (const [key, entry] of Object.entries(scorers)) {
const scorerName = typeof entry.scorer === "string" ? entry.scorer : entry.scorer.name;
const scorerEntry = {
scorerName
};
if (entry.sampling) {
scorerEntry.sampling = entry.sampling;
}
result[key] = scorerEntry;
}
return result;
}
function serializeDurableState(params) {
return {
memoryConfig: params.memoryConfig,
threadId: params.threadId,
resourceId: params.resourceId,
threadExists: params.threadExists,
savePerStep: params.savePerStep,
observationalMemory: params.observationalMemory
};
}
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,
temperature: options.temperature,
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
};
}
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
};
}
function serializeError(error) {
if (error instanceof Error) {
return {
name: error.name,
message: error.message,
stack: error.stack
};
}
return {
name: "Error",
message: String(error)
};
}
// src/agent/durable/preparation.ts
async function prepareForDurableExecution(options) {
const {
agent,
messages,
options: execOptions,
runId: providedRunId,
requestContext: providedRequestContext,
logger,
mastra
} = options;
const typedAgent = agent;
const runId = providedRunId ?? crypto.randomUUID();
const messageId = crypto.randomUUID();
const requestContext = providedRequestContext ?? new chunkCAVARKYS_cjs.RequestContext();
const requestVersions = requestContext.get(chunkCAVARKYS_cjs.MASTRA_VERSIONS_KEY);
let mergedVersions = chunkCAVARKYS_cjs.mergeVersionOverrides(mastra?.getVersionOverrides?.(), requestVersions);
if (execOptions?.versions) {
mergedVersions = chunkCAVARKYS_cjs.mergeVersionOverrides(mergedVersions, execOptions.versions);
}
if (mergedVersions) {
requestContext.set(chunkCAVARKYS_cjs.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 chunkRQ4MB5TM_cjs.MessageList({
threadId,
resourceId
});
const 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?.filesystem || workspace?.sandbox) {
const wsInstructions = workspace.getInstructions({ requestContext });
if (wsInstructions) {
messageList.addSystem({ role: "system", content: wsInstructions });
}
}
if (execOptions?.context) {
messageList.add(execOptions.context, "context");
}
messageList.add(messages, "input");
const processorStates = /* @__PURE__ */ new Map();
let inputProcessors = [];
let outputProcessors = [];
let errorProcessors = [];
try {
inputProcessors = await typedAgent.listInputProcessors(requestContext);
outputProcessors = await typedAgent.listOutputProcessors(requestContext);
errorProcessors = await typedAgent.listErrorProcessors(requestContext);
} catch (error) {
logger?.warn?.(`[DurableAgent] Error resolving processors: ${error}`);
}
if (inputProcessors.length > 0) {
try {
const memory2 = await typedAgent.getMemory({ requestContext });
const memoryConfig2 = execOptions?.memory?.options;
if (memory2 && threadId && resourceId) {
const existingThread = await memory2.getThreadById({ threadId });
threadObject = existingThread ?? await memory2.createThread({
threadId,
metadata: thread?.metadata,
title: thread?.title,
memoryConfig: memoryConfig2,
resourceId,
saveThread: true
});
threadExists = true;
requestContext.set("MastraMemory", { thread: threadObject, resourceId, memoryConfig: memoryConfig2 });
}
const { ProcessorRunner: ProcessorRunner2 } = await import('../../runner-LMVIIRXF.cjs');
const runner = new ProcessorRunner2({
inputProcessors,
outputProcessors,
errorProcessors,
logger,
agentName: agent.name,
processorStates
});
await runner.runInputProcessors(messageList, {}, requestContext, 0);
} catch (error) {
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
});
} 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 memory = await typedAgent.getMemory({ requestContext });
const memoryConfig = execOptions?.memory?.options;
const saveQueueManager = memory ? new chunkACQ5CVFF_cjs.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 = mastra?.backgroundTaskManager;
const savePerStep = execOptions?.savePerStep;
const observationalMemory = !!memoryConfig?.observationalMemory;
const workflowInput = createWorkflowInput({
runId,
agentId: agent.id,
agentName: agent.name,
messageList,
tools,
model,
modelList: modelList ?? void 0,
scorers,
options: {
maxSteps: execOptions?.maxSteps,
toolChoice: execOptions?.toolChoice,
activeTools: execOptions?.activeTools,
temperature: execOptions?.modelSettings?.temperature,
requireToolApproval: 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
},
state: {
memoryConfig,
threadId,
resourceId,
threadExists,
savePerStep,
observationalMemory
},
messageId
});
const registryEntry = {
tools,
saveQueueManager,
memory,
model,
modelList: modelList ? modelList.map((entry) => ({
id: entry.id,
model: entry.model,
maxRetries: entry.maxRetries ?? 0,
enabled: entry.enabled ?? true
})) : void 0,
workspace,
requestContext,
inputProcessors,
outputProcessors,
errorProcessors,
processorStates,
backgroundTaskManager,
backgroundTasksConfig,
cleanup: () => {
}
};
return {
runId,
messageId,
workflowInput,
registryEntry,
messageList,
threadId,
resourceId
};
}
var globalRunRegistry = new ttlcache.TTLCache({
max: 1e3,
ttl: 10 * 60 * 1e3,
updateAgeOnGet: true,
dispose: (entry) => {
entry.cleanup?.();
},
noDisposeOnSet: true
});
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);
}
}
};
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();
}
};
function createDurableAgentStream(options) {
const {
pubsub,
runId,
messageId,
model,
threadId,
resourceId,
offset,
onChunk,
onStepFinish,
onFinish,
onError,
onSuspended,
logger
} = options;
const logError = (message, error) => {
if (logger) {
logger.error(message, error);
} else {
console.error(message, error);
}
};
const messageList = new chunkRQ4MB5TM_cjs.MessageList({
threadId,
resourceId
});
let isSubscribed = false;
let cancelled = false;
let controller = null;
let resolveReady;
let rejectReady;
const ready = new Promise((resolve, reject) => {
resolveReady = resolve;
rejectReady = reject;
});
const handleEvent = async (event) => {
if (!controller) return;
const streamEvent = event;
try {
switch (streamEvent.type) {
case AgentStreamEventTypes.CHUNK: {
const chunk = streamEvent.data;
chunkACQ5CVFF_cjs.safeEnqueue(controller, chunk);
await onChunk?.(chunk);
break;
}
case AgentStreamEventTypes.STEP_START: {
const chunk = streamEvent.data;
if (chunk && "type" in chunk) {
chunkACQ5CVFF_cjs.safeEnqueue(controller, chunk);
}
break;
}
case AgentStreamEventTypes.STEP_FINISH: {
const data = streamEvent.data;
await onStepFinish?.(data);
break;
}
case AgentStreamEventTypes.FINISH: {
const data = streamEvent.data;
const finishChunk = {
type: "finish",
payload: {
output: data.output,
stepResult: data.stepResult
}
};
chunkACQ5CVFF_cjs.safeEnqueue(controller, finishChunk);
chunkACQ5CVFF_cjs.safeClose(controller);
try {
await onFinish?.(data);
} catch (callbackError) {
logError(`[DurableAgentStream] onFinish callback error:`, callbackError);
}
break;
}
case 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;
}
try {
controller.error(error);
} catch {
}
try {
await onError?.(error);
} catch (callbackError) {
logError(`[DurableAgentStream] onError callback error:`, callbackError);
}
break;
}
case AgentStreamEventTypes.SUSPENDED: {
const data = streamEvent.data;
await onSuspended?.(data);
break;
}
}
} catch (error) {
logError(`[DurableAgentStream] Error handling event ${streamEvent.type}:`, error);
}
};
const stream = new web.ReadableStream({
start(ctrl) {
controller = ctrl;
const topic = AGENT_STREAM_TOPIC(runId);
const subscribePromise = offset !== void 0 ? pubsub.subscribeFromOffset(topic, offset, handleEvent) : pubsub.subscribeWithReplay(topic, handleEvent);
subscribePromise.then(() => {
if (cancelled) {
void pubsub.unsubscribe(topic, handleEvent).catch((error) => {
logError(`[DurableAgentStream] Failed to unsubscribe from ${topic}:`, error);
});
resolveReady();
return;
}
isSubscribed = true;
resolveReady();
}).catch((error) => {
logError(`[DurableAgentStream] Failed to subscribe to ${topic}:`, error);
rejectReady(error);
ctrl.error(error);
});
},
cancel() {
cleanup();
}
});
const cleanup = () => {
cancelled = true;
if (isSubscribed) {
isSubscribed = false;
const topic = AGENT_STREAM_TOPIC(runId);
void pubsub.unsubscribe(topic, handleEvent).catch((error) => {
logError(`[DurableAgentStream] Failed to unsubscribe from ${topic}:`, error);
});
}
controller = null;
};
const output = new chunkACQ5CVFF_cjs.MastraModelOutput({
model,
stream,
messageList,
messageId,
options: {
runId
}
});
return {
output,
cleanup,
ready
};
}
async function emitChunkEvent(pubsub, runId, chunk) {
const topic = AGENT_STREAM_TOPIC(runId);
await pubsub.publish(topic, {
type: AgentStreamEventTypes.CHUNK,
runId,
data: chunk
});
}
async function emitStepStartEvent(pubsub, runId, data) {
await pubsub.publish(AGENT_STREAM_TOPIC(runId), {
type: AgentStreamEventTypes.STEP_START,
runId,
data
});
}
async function emitStepFinishEvent(pubsub, runId, data) {
await pubsub.publish(AGENT_STREAM_TOPIC(runId), {
type: AgentStreamEventTypes.STEP_FINISH,
runId,
data
});
}
async function emitFinishEvent(pubsub, runId, data) {
await pubsub.publish(AGENT_STREAM_TOPIC(runId), {
type: AgentStreamEventTypes.FINISH,
runId,
data
});
}
async function emitErrorEvent(pubsub, runId, error) {
await pubsub.publish(AGENT_STREAM_TOPIC(runId), {
type: AgentStreamEventTypes.ERROR,
runId,
data: {
error: {
name: error.name,
message: error.message
// stack intentionally omitted — avoid leaking internals through external pubsub
}
}
});
}
async function emitSuspendedEvent(pubsub, runId, data) {
await pubsub.publish(AGENT_STREAM_TOPIC(runId), {
type: AgentStreamEventTypes.SUSPENDED,
runId,
data
});
}
// src/agent/durable/workflows/shared/execute-tool-calls.ts
async function executeDurableToolCalls(ctx) {
const toolResults = [];
for (const toolCall of ctx.toolCalls) {
if (toolCall.providerExecuted && toolCall.output !== void 0) {
toolResults.push({
...toolCall,
result: toolCall.output
});
continue;
}
const tool = ctx.tools[toolCall.toolName];
if (!tool) {
const error = {
name: "ToolNotFoundError",
message: `Tool ${toolCall.toolName} not found`
};
await ctx.onToolError?.(toolCall, error);
toolResults.push({
...toolCall,
error
});
continue;
}
await ctx.onToolStart?.(toolCall);
try {
if (tool.execute) {
const result = await tool.execute(toolCall.args, {
toolCallId: toolCall.toolCallId,
messages: [],
workspace: ctx.workspace,
requestContext: ctx.requestContext
});
await ctx.onToolResult?.(toolCall, result);
toolResults.push({
...toolCall,
result
});
} else {
await ctx.onToolResult?.(toolCall, void 0);
toolResults.push({
...toolCall,
result: void 0
});
}
} catch (error) {
const toolError = {
name: "ToolExecutionError",
message: error instanceof Error ? error.message : String(error),
stack: error instanceof Error ? error.stack : void 0
};
await ctx.onToolError?.(toolCall, toolError);
toolResults.push({
...toolCall,
error: toolError
});
}
}
return toolResults;
}
var 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()
});
var 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()
});
var accumulatedUsageSchema = zod.z.object({
inputTokens: zod.z.number(),
outputTokens: zod.z.number(),
totalTokens: zod.z.number()
});
var 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()
});
var 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()
});
var baseIterationStateSchema = zod.z.object({
// Original input fields
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(),
// Iteration tracking
iterationCount: zod.z.number(),
accumulatedSteps: zod.z.array(zod.z.any()),
accumulatedUsage: accumulatedUsageSchema,
// Last step result for continuation check
lastStepResult: zod.z.any().optional(),
// Background task tracking
backgroundTaskPending: zod.z.boolean().optional()
});
// src/agent/durable/workflows/shared/iteration-state.ts
function calculateAccumulatedUsage(currentUsage, executionUsage) {
return {
inputTokens: currentUsage.inputTokens + (executionUsage?.inputTokens || 0),
outputTokens: currentUsage.outputTokens + (executionUsage?.outputTokens || 0),
totalTokens: currentUsage.totalTokens + (executionUsage?.totalTokens || 0)
};
}
function buildStepRecord(executionOutput) {
return {
text: executionOutput.output.text,
toolCalls: executionOutput.output.toolCalls,
toolResults: executionOutput.toolResults,
usage: executionOutput.output.usage,
finishReason: executionOutput.stepResult.reason
};
}
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
};
}
var BG_CHECK_STEP_ID = `${DurableStepIds.AGENTIC_EXECUTION}-bg-task-check`;
var bgCheckInputSchema = zod.z.any();
var bgCheckOutputSchema = zod.z.any();
function createDurableBackgroundTaskCheckStep() {
return chunkACQ5CVFF_cjs.createStep({
id: BG_CHECK_STEP_ID,
inputSchema: bgCheckInputSchema,
outputSchema: bgCheckOutputSchema,
execute: async (params) => {
const { inputData, retryCount, getInitData } = params;
const pubsub = params[chunkZCBG4ZQT_cjs.PUBSUB_SYMBOL];
const typedInput = inputData;
const initData = getInitData();
const { runId, agentId } = initData;
const registryEntry = globalRunRegistry.get(runId);
const bgManager = registryEntry?.backgroundTaskManager;
if (!bgManager) {
return typedInput;
}
const runningResult = await bgManager.listTasks({
agentId,
status: "running",
threadId: initData.state?.threadId,
resourceId: initData.state?.resourceId
});
const runningTasks = runningResult?.tasks;
if (!runningTasks || runningTasks.length === 0) {
return typedInput;
}
if (initData.options?.skipBgTaskWait) {
return { ...typedInput, backgroundTaskPending: true };
}
const taskIds = runningTasks.map((task) => task.id);
const bgConfig = registryEntry?.backgroundTasksConfig;
const managerConfig = bgManager.config;
const waitTimeoutMs = bgConfig?.waitTimeoutMs ?? managerConfig?.waitTimeoutMs;
if (retryCount === 0 || !waitTimeoutMs) {
return { ...typedInput, backgroundTaskPending: true };
}
if (pubsub) {
try {
await emitChunkEvent(pubsub, runId, {
type: "background-task-progress",
runId,
from: "AGENT" /* AGENT */,
payload: { taskIds, runningCount: runningTasks.length, elapsedMs: 0 }
});
} catch {
}
}
try {
await bgManager.waitForNextTask(taskIds, {
timeoutMs: waitTimeoutMs,
onProgress: (elapsedMs) => {
if (!pubsub) return;
void emitChunkEvent(pubsub, runId, {
type: "background-task-progress",
runId,
from: "AGENT" /* AGENT */,
payload: { taskIds, runningCount: runningTasks.length, elapsedMs }
}).catch(() => {
});
},
progressIntervalMs: 3e3
});
} catch {
return typedInput;
}
if (typedInput.stepResult) {
return {
...typedInput,
backgroundTaskPending: true,
stepResult: { ...typedInput.stepResult, isContinued: true }
};
}
return { ...typedInput, backgroundTaskPending: true };
}
});
}
// src/agent/durable/utils/resolve-runtime.ts
async function resolveRuntimeDependencies(options) {
const { mastra, runId, agentId, input, logger } = options;
const messageList = new chunkRQ4MB5TM_cjs.MessageList({
threadId: input.state.threadId,
resourceId: input.state.resourceId
});
messageList.deserialize(input.messageListState);
const globalEntry = globalRunRegistry.get(runId);
let tools = globalEntry?.tools ?? {};
let model = globalEntry?.model;
let modelList = globalEntry?.modelList;
let workspace = globalEntry?.workspace;
let memory;
if (globalEntry) {
logger?.debug?.(`[DurableAgent:${agentId}] Using model and tools from global registry for run ${runId}`);
} else if (mastra) {
try {
const agent = mastra.getAgentById(agentId);
const resolveRequestContext = new chunkCAVARKYS_cjs.RequestContext();
tools = await agent.getToolsForExecution({
runId,
threadId: input.state.threadId,
resourceId: input.state.resourceId,
requestContext: resolveRequestContext,
memoryConfig: input.state.memoryConfig,
autoResumeSuspendedTools: input.options?.autoResumeSuspendedTools
});
model = await agent.getModel?.({ requestContext: resolveRequestContext }) ?? resolveModel(input.modelConfig);
const rawModelList = await agent.getModelList?.(resolveRequestContext);
if (rawModelList && Array.isArray(rawModelList)) {
modelList = rawModelList.map((entry) => ({
id: entry.id,
model: entry.model,
maxRetries: entry.maxRetries ?? 0,
enabled: entry.enabled ?? true
}));
}
memory = await agent.getMemory?.({ requestContext: resolveRequestContext });
workspace = await agent.getWorkspace?.({ requestContext: resolveRequestContext });
} catch (error) {
logger?.debug?.(`[DurableAgent:${agentId}] Failed to get agent from Mastra: ${error}`);
model = resolveModel(input.modelConfig);
}
} else {
logger?.debug?.(`[DurableAgent:${agentId}] No Mastra instance available, using fallback model`);
model = resolveModel(input.modelConfig);
}
if (Object.keys(tools).length === 0) {
logger?.debug?.(`[DurableAgent:${agentId}] No tools resolved for run ${runId}`);
}
let saveQueueManager;
if (memory) {
saveQueueManager = new chunkACQ5CVFF_cjs.SaveQueueManager({
logger: mastra?.getLogger?.(),
memory
});
}
const _internal = resolveInternalState({
state: input.state,
memory,
saveQueueManager,
tools
});
return {
_internal,
tools,
model,
modelList,
messageList,
memory,
saveQueueManager,
workspace
};
}
function resolveModel(config, _mastra) {
const metadataError = () => {
throw new Error(
`Model ${config.provider}/${config.modelId} is a metadata-only stub. The actual model instance should be resolved from the run registry.`
);
};
return {
provider: config.provider,
modelId: config.modelId,
specificationVersion: config.specificationVersion ?? "v2",
supportedUrls: {},
doGenerate: metadataError,
doStream: metadataError,
__metadataOnly: true
};
}
function resolveInternalState(options) {
const { state, memory, saveQueueManager, tools } = options;
return {
// Functions - create fresh
now: () => Date.now(),
generateId: () => crypto.randomUUID(),
currentDate: () => /* @__PURE__ */ new Date(),
// Class instances - from resolved state
saveQueueManager,
memory,
// Serializable state
memoryConfig: state.memoryConfig,
threadId: state.threadId,
resourceId: state.resourceId,
threadExists: state.threadExists,
// Tools if provided - cast to ToolSet for compatibility
// CoreTool and ToolSet are structurally compatible at runtime
stepTools: tools
};
}
function resolveTool(toolName, mastra) {
try {
return mastra?.getTool?.(toolName);
} catch {
return void 0;
}
}
async function toolRequiresApproval(tool, globalRequireApproval, args) {
let requires = !!(globalRequireApproval || tool.requireApproval);
if (tool.needsApprovalFn) {
try {
requires = await tool.needsApprovalFn(args ?? {});
} catch {
requires = true;
}
}
return requires;
}
async function resolveModelFromConfig(config, mastra) {
const requestContext = new chunkCAVARKYS_cjs.RequestContext();
const modelConfigString = config.originalConfig ?? `${config.provider}/${config.modelId}`;
if (typeof modelConfigString === "string") {
return await chunk6BHHFTLL_cjs.resolveModelConfig(modelConfigString, requestContext, mastra);
}
return await chunk6BHHFTLL_cjs.resolveModelConfig(
modelConfigString,
requestContext,
mastra
);
}
async function resolveModelFromListEntry(entry, mastra) {
return resolveModelFromConfig(entry.config, mastra);
}
// src/agent/durable/workflows/steps/llm-execution.ts
var durableLLMInputSchema = zod.z.object({
runId: zod.z.string(),
agentId: zod.z.string(),
agentName: zod.z.string().optional(),
messageListState: zod.z.any(),
// SerializedMessageListState
toolsMetadata: zod.z.array(zod.z.any()),
modelConfig: 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(),
settings: zod.z.record(zod.z.string(), zod.z.any()).optional(),
providerOptions: zod.z.record(zod.z.string(), zod.z.any()).optional()
}),
// Model list for fallback support (when agent configured with array of models)
modelList: zod.z.array(
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()
})
).optional(),
options: zod.z.any(),
state: zod.z.any(),
messageId: zod.z.string(),
// Agent span data for model span parenting
agentSpanData: zod.z.any().optional(),
// Model span data (ONE span for entire agent run, created before workflow)
modelSpanData: zod.z.any().optional(),
// Step index for continuation (step: 0, 1, 2, ...)
stepIndex: zod.z.number().optional()
});
var durableLLMOutputSchema = zod.z.object({
messageListState: zod.z.any(),
toolCalls: zod.z.array(
zod.z.object({
toolCallId: zod.z.string(),
toolName: zod.z.string(),
args: zod.z.record(zod.z.string(), zod.z.any()),
providerMetadata: zod.z.record(zod.z.string(), zod.z.any()).optional(),
activeTools: zod.z.array(zod.z.string()).nullable().optional()
})
),
stepResult: zod.z.object({
reason: zod.z.string(),
warnings: zod.z.array(zod.z.any()),
isContinued: zod.z.boolean(),
totalUsage: zod.z.any().optional()
}),
metadata: zod.z.any(),
processorRetryCount: zod.z.number().optional(),
processorRetryFeedback: zod.z.string().optional(),
state: zod.z.any(),
// Step index used in this execution (for tracking)
stepIndex: zod.z.number().optional()
});
function createDurableLLMExecutionStep(_options) {
return chunkACQ5CVFF_cjs.createStep({
id: DurableStepIds.LLM_EXECUTION,
inputSchema: durableLLMInputSchema,
outputSchema: durableLLMOutputSchema,
execute: async (params) => {
const { inputData, mastra, tracingContext, requestContext, abortSignal } = params;
const pubsub = params[chunkZCBG4ZQT_cjs.PUBSUB_SYMBOL];
const typedInput = inputData;
const { agentId, messageId, options: execOptions } = typedInput;
const runId = typedInput.runId;
const logger = mastra?.getLogger?.();
const resolved = await resolveRuntimeDependencies({
mastra,
runId,
agentId,
input: typedInput,
logger
});
const { messageList, tools, model: resolvedModel, modelList: resolvedModelList } = resolved;
const hasModelList = typedInput.modelList && typedInput.modelList.length > 0;
const modelList = hasModelList ? typedInput.modelList.filter((m) => m.enabled) : [
{
id: `${typedInput.modelConfig.provider}/${typedInput.modelConfig.modelId}`,
config: typedInput.modelConfig,
maxRetries: 0,
enabled: true
}
];
if (modelList.length === 0) {
throw new Error("No enabled models available for execution");
}
let lastError;
for (let mo