@mastra/core
Version:
1,310 lines • 148 kB
JavaScript
import { n as __exportAll } from "./rolldown-runtime-DP3BCW9_.js";
import { t as MastraBase } from "./base-BeUQ6mLP.js";
import { i as MastraError, n as ErrorDomain, o as getErrorFromUnknown, t as ErrorCategory } from "./error-MjDSls8S.js";
import { a as resolveObservabilityContext, i as createObservabilityContext } from "./observability-Cz-X7NF_.js";
import { f as EntityType, o as getRootExportSpan } from "./utils-DxsDNzD2.js";
import "./tracing-Bm0k4FBA.js";
import { d as readModelStreamTransport, i as resolveModelConfig, u as attachModelStreamTransport } from "./llm-DntEbB3j.js";
import { isStandardSchemaWithJSON, standardSchemaToJSONSchema, toStandardSchema } from "./schema/index.js";
import { n as createSignal } from "./signals-DTzJ08gd.js";
import { a as coreContentToString, n as MessageList, o as messagesAreEqual, t as convertMessages } from "./message-list-mC29laJJ.js";
import { a as isDeepEqualData, d as JSONParseError, f as TypeValidationError, s as parsePartialJson, t as NoObjectGeneratedError } from "./dist-DIIEuFGB.js";
import { a as applyStateSignal, o as getStateSignalsMetadata, r as parseMemoryRequestContext, s as resolveStateSignalHistory } from "./types-LyOfO-TK.js";
import { randomUUID } from "crypto";
import { EventEmitter } from "events";
import { AnthropicSchemaCompatLayer, applyCompatLayer, isZodType } from "@mastra/schema-compat";
import { ReadableStream as ReadableStream$1, TransformStream } from "stream/web";
//#region src/processors/is-processor-workflow.ts
/**
* Type guard to check if an object is a Workflow that can be used as a processor.
*
* Extracted to its own module so that `runner.ts` (and by extension
* `stream/base/output.ts`) can use it without loading the full processors
* barrel — which re-exports every built-in processor, many of which import
* from the agent barrel and create ESM init-time cycles.
*/
function isProcessorWorkflow(obj) {
return obj !== null && typeof obj === "object" && "id" in obj && typeof obj.id === "string" && "inputSchema" in obj && "outputSchema" in obj && "execute" in obj && typeof obj.execute === "function" && !("processInput" in obj) && !("processInputStep" in obj) && !("processOutputStream" in obj) && !("processOutputResult" in obj) && !("processOutputStep" in obj) && !("processToolResult" in obj) && !("processLLMRequest" in obj) && !("processAPIError" in obj);
}
//#endregion
//#region src/stream/aisdk/v5/compat/delayed-promise.ts
/**
* Delayed promise. It is only constructed once the value is accessed.
* This is useful to avoid unhandled promise rejections when the promise is created
* but not accessed.
*/
var DelayedPromise = class {
status = { type: "pending" };
_promise;
_resolve = void 0;
_reject = void 0;
get promise() {
if (this._promise) return this._promise;
this._promise = new Promise((resolve, reject) => {
if (this.status.type === "resolved") resolve(this.status.value);
else if (this.status.type === "rejected") reject(this.status.error);
this._resolve = resolve;
this._reject = reject;
});
return this._promise;
}
resolve(value) {
this.status = {
type: "resolved",
value
};
if (this._promise) this._resolve?.(value);
}
reject(error) {
this.status = {
type: "rejected",
error
};
if (this._promise) this._reject?.(error);
}
};
//#endregion
//#region src/stream/aisdk/v5/compat/consume-stream.ts
async function consumeStream({ stream, onError, logger }) {
const reader = stream.getReader();
try {
while (true) {
const { done } = await reader.read();
if (done) break;
}
} catch (error) {
logger?.error("consumeStream error", error);
onError?.(error);
} finally {
reader.releaseLock();
}
}
//#endregion
//#region src/agent/utils.ts
const supportedLanguageModelSpecifications = [
"v2",
"v3",
"v4"
];
const isSupportedLanguageModel = (model) => {
return supportedLanguageModelSpecifications.includes(model.specificationVersion);
};
function isStructuredOutputFormatError(error) {
return JSONParseError.isInstance(error) || NoObjectGeneratedError.isInstance(error) || TypeValidationError.isInstance(error) || error instanceof MastraError && (error.id === "STRUCTURED_OUTPUT_OBJECT_UNDEFINED" || error.id === "STRUCTURED_OUTPUT_SCHEMA_VALIDATION_FAILED");
}
async function tryGenerateWithJsonFallback(agent, prompt, options) {
if (!options.structuredOutput?.schema) throw new MastraError({
id: "STRUCTURED_OUTPUT_OPTIONS_REQUIRED",
domain: ErrorDomain.AGENT,
category: ErrorCategory.USER,
text: "structuredOutput is required to use tryGenerateWithJsonFallback"
});
try {
const result = await agent.generate(prompt, options);
if (result.object === void 0) throw new MastraError({
id: "STRUCTURED_OUTPUT_OBJECT_UNDEFINED",
domain: ErrorDomain.AGENT,
category: ErrorCategory.USER,
text: "structuredOutput object is undefined"
});
return result;
} catch (error) {
if (!isStructuredOutputFormatError(error)) throw error;
console.warn("Error in tryGenerateWithJsonFallback. Attempting fallback.", error);
const result = await agent.generate(prompt, {
...options,
structuredOutput: {
...options.structuredOutput,
jsonPromptInjection: options.structuredOutput.jsonPromptInjection === "inline" || options.structuredOutput.jsonPromptInjection === "system" ? options.structuredOutput.jsonPromptInjection : true
}
});
if (result.object === void 0) throw new MastraError({
id: "STRUCTURED_OUTPUT_OBJECT_UNDEFINED",
domain: ErrorDomain.AGENT,
category: ErrorCategory.USER,
text: "structuredOutput object is undefined"
});
return result;
}
}
async function tryStreamWithJsonFallback(agent, prompt, options) {
if (!options.structuredOutput?.schema) throw new MastraError({
id: "STRUCTURED_OUTPUT_OPTIONS_REQUIRED",
domain: ErrorDomain.AGENT,
category: ErrorCategory.USER,
text: "structuredOutput is required to use tryStreamWithJsonFallback"
});
const { onStream, onStreamAttempt, onStreamFinish, ...streamOptions } = options;
try {
await onStreamAttempt?.();
const result = await agent.stream(prompt, streamOptions);
onStream?.(result);
try {
if (!await result.object) throw new MastraError({
id: "STRUCTURED_OUTPUT_OBJECT_UNDEFINED",
domain: ErrorDomain.AGENT,
category: ErrorCategory.USER,
text: "structuredOutput object is undefined"
});
return result;
} finally {
await onStreamFinish?.(result);
}
} catch (error) {
if (!isStructuredOutputFormatError(error)) throw error;
console.warn("Error in tryStreamWithJsonFallback. Attempting fallback.", error);
await onStreamAttempt?.();
const result = await agent.stream(prompt, {
...streamOptions,
structuredOutput: {
...streamOptions.structuredOutput,
jsonPromptInjection: streamOptions.structuredOutput.jsonPromptInjection === "inline" || streamOptions.structuredOutput.jsonPromptInjection === "system" ? streamOptions.structuredOutput.jsonPromptInjection : true
}
});
onStream?.(result);
try {
if (await result.object === void 0) throw new MastraError({
id: "STRUCTURED_OUTPUT_OBJECT_UNDEFINED",
domain: ErrorDomain.AGENT,
category: ErrorCategory.USER,
text: "structuredOutput object is undefined"
});
return result;
} finally {
await onStreamFinish?.(result);
}
}
}
function resolveThreadIdFromArgs(args) {
let resolved;
if (args?.memory?.thread) {
if (typeof args.memory.thread === "string") resolved = { id: args.memory.thread };
else if (typeof args.memory.thread === "object" && args.memory.thread.id) resolved = args.memory.thread;
}
if (!resolved && args?.threadId) resolved = { id: args.threadId };
if (args.overrideId) return {
...resolved || {},
id: args.overrideId
};
return resolved;
}
//#endregion
//#region src/processors/send-signal.ts
function createProcessorSendSignal(args) {
return async (signalInput) => {
const signal = createSignal(signalInput);
args.messageList.markResponseMessageBoundary();
args.rotateResponseMessageId?.();
const signalForTranscript = args.messageList.addSignal(signal);
await args.writer?.custom(signalForTranscript.toDataPart());
return signalForTranscript;
};
}
//#endregion
//#region src/processors/span-payload.ts
function isPlainObject(value) {
if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
const prototype = Object.getPrototypeOf(value);
return prototype === Object.prototype || prototype === null;
}
function readString(value) {
return typeof value === "string" ? value : void 0;
}
function summarizeProcessorToolEntry(key, value) {
if (!isPlainObject(value)) return {
id: key,
name: key
};
const id = readString(value.id) ?? key;
const name = readString(value.name) ?? id;
const description = readString(value.description);
return {
id,
name,
...description !== void 0 ? { description } : {}
};
}
function summarizeProcessorModelForSpan(value) {
if (!isPlainObject(value)) return;
const modelId = readString(value.modelId) ?? readString(value.id);
const provider = readString(value.provider);
const specificationVersion = readString(value.specificationVersion);
if (modelId === void 0 && provider === void 0 && specificationVersion === void 0) return;
return {
...modelId !== void 0 ? { modelId } : {},
...provider !== void 0 ? { provider } : {},
...specificationVersion !== void 0 ? { specificationVersion } : {}
};
}
function summarizeProcessorToolsForSpan(tools) {
if (!isPlainObject(tools)) return;
return Object.entries(tools).map(([key, value]) => summarizeProcessorToolEntry(key, value));
}
function summarizeProcessorToolRegistry(tools) {
if (!isPlainObject(tools)) return;
const summaries = summarizeProcessorToolsForSpan(tools);
if (!summaries) return;
return Object.keys(tools).map((registryKey, index) => ({
registryKey,
summary: summaries[index]
}));
}
function resolveToolInRegistry(toolRegistry, toolKey) {
return toolRegistry.find((candidate) => candidate.summary.id === toolKey || candidate.summary.name === toolKey || candidate.registryKey === toolKey);
}
function summarizeActiveToolsForSpan(activeTools, tools) {
if (!Array.isArray(activeTools)) return;
const toolRegistry = summarizeProcessorToolRegistry(tools) ?? [];
return activeTools.map((tool) => {
const toolKey = readString(tool);
if (toolKey === void 0) return;
const match = resolveToolInRegistry(toolRegistry, toolKey);
return {
id: match?.summary.id ?? toolKey,
name: match?.summary.name ?? toolKey
};
}).filter((tool) => tool !== void 0);
}
function summarizeToolChoiceForSpan(toolChoice, tools) {
if (typeof toolChoice === "string") return { type: toolChoice };
if (!isPlainObject(toolChoice)) return;
const type = readString(toolChoice.type);
if (type === void 0) return;
if (type !== "tool") return { type };
const toolKey = readString(toolChoice.toolName) ?? readString(toolChoice.toolId);
if (toolKey === void 0) return { type };
const match = resolveToolInRegistry(summarizeProcessorToolRegistry(tools) ?? [], toolKey);
const fallbackToolId = readString(toolChoice.toolId) ?? toolKey;
const fallbackToolName = readString(toolChoice.toolName) ?? toolKey;
return {
type,
tool: match ? {
id: match.summary.id,
name: match.summary.name
} : {
id: fallbackToolId,
name: fallbackToolName
}
};
}
function summarizeProcessorResultForSpan(value) {
if (!isPlainObject(value)) return;
const projected = {};
for (const key of [
"text",
"object",
"finishReason",
"toolCalls",
"toolResults",
"warnings",
"files",
"sources",
"reasoning",
"reasoningText",
"tripwire"
]) if (value[key] !== void 0) projected[key] = value[key];
if (Array.isArray(value.steps)) projected.stepCount = value.steps.length;
return Object.keys(projected).length > 0 ? projected : void 0;
}
//#endregion
//#region src/processors/stream-reprocess.ts
/**
* Well-known key a stream output processor can set on its `state` to ask the
* ProcessorRunner to re-drive an additional part through the full output
* processor chain after the current part has been emitted.
*
* `processOutputStream` can only return a single part, but some processors
* (e.g. `BatchPartsProcessor`) need to emit two parts for one input: a flushed
* batch of buffered text plus the non-text part that triggered the flush. The
* processor returns the flushed batch (so it flows through downstream
* processors normally) and stashes the non-text part under this key. The runner
* then re-feeds the stashed part through the whole chain so it also receives
* downstream processing and is emitted in order — instead of being deferred to
* a "next" call that may never happen (which dropped the part when a `stopWhen`
* condition stopped the agent on that part — issue #17094).
*/
const REPROCESS_PART_KEY = "__mastraReprocessPart";
//#endregion
//#region src/processors/trailing-assistant-guard.ts
const CLAUDE_46_PATTERN = /[^0-9]4[.-]6/;
/**
* Checks whether a model config could be Claude 4.6.
*
* Handles raw model configs (strings like `'anthropic/claude-opus-4-6'`),
* language model objects (with `provider` and `modelId`), dynamic functions
* (returns `true` as a safe default), and model fallback arrays.
*/
function isMaybeClaude46(model) {
if (typeof model === "function") return true;
if (Array.isArray(model)) return model.some((m) => isMaybeClaude46(m.model ?? m));
if (typeof model === "string") return model.startsWith("anthropic") && CLAUDE_46_PATTERN.test(model);
if (model && typeof model === "object" && "provider" in model && "modelId" in model) {
const { provider, modelId } = model;
return provider.startsWith("anthropic") && CLAUDE_46_PATTERN.test(modelId);
}
return true;
}
/**
* Guards against trailing assistant messages when using native structured output
* with Anthropic Claude 4.6.
*
* Claude 4.6 rejects requests where the last message is an assistant message when
* using output format (structured output), interpreting it as pre-filling the response.
* This processor appends a user message to prevent that error.
*
* This processor should only be added when the agent uses a Claude 4.6 model.
* Use {@link isMaybeClaude46} to check before adding.
*
* @see https://github.com/mastra-ai/mastra/issues/12800
*/
var TrailingAssistantGuard = class {
id = "trailing-assistant-guard";
name = "Trailing Assistant Guard";
processInputStep({ messages, structuredOutput }) {
if (!(structuredOutput?.schema && !structuredOutput?.model && !structuredOutput?.jsonPromptInjection)) return;
const lastMessage = messages[messages.length - 1];
if (!lastMessage || lastMessage.role !== "assistant") return;
return { messages: [...messages, {
id: randomUUID(),
role: "user",
content: {
format: 2,
parts: [{
type: "text",
text: "Generate the structured response."
}]
},
createdAt: /* @__PURE__ */ new Date()
}] };
}
};
//#endregion
//#region src/processors/runner.ts
var runner_exports = /* @__PURE__ */ __exportAll({
ProcessorRunner: () => ProcessorRunner,
ProcessorState: () => ProcessorState
});
/**
* Safely invoke a processor's onViolation callback when a TripWire is caught.
* Errors from the callback are silently caught.
*/
async function invokeOnViolation(processor, error) {
if (!processor.onViolation) return;
try {
const violation = {
processorId: error.processorId ?? processor.id,
message: error.message,
detail: error.options?.metadata
};
await processor.onViolation(violation);
} catch {}
}
/**
* Implementation of processor state management
*/
/**
* Tracks state for stream processing across chunks.
* Used by both legacy processors and workflow processors.
*/
var ProcessorState = class {
inputAccumulatedText = "";
outputAccumulatedText = "";
outputChunkCount = 0;
customState = {};
streamParts = [];
span;
constructor(options) {
if (!options?.createSpan || !options.processorName) return;
const currentSpan = options.tracingContext?.currentSpan;
const parentSpan = currentSpan?.findParent("agent_run") || currentSpan?.parent || currentSpan;
this.span = parentSpan?.createChildSpan({
type: "processor_run",
name: `output stream processor: ${options.processorName}`,
entityType: EntityType.OUTPUT_PROCESSOR,
entityName: options.processorName,
attributes: {
processorExecutor: "legacy",
processorIndex: options.processorIndex ?? 0
},
input: { totalChunks: 0 }
});
}
/** Track incoming chunk (before processor transformation) */
addInputPart(part) {
if (part.type === "text-delta") this.inputAccumulatedText += part.payload.text;
this.streamParts.push(part);
if (this.span) this.span.input = {
totalChunks: this.streamParts.length,
accumulatedText: this.inputAccumulatedText
};
}
/** Track outgoing chunk (after processor transformation) */
addOutputPart(part) {
if (!part) return;
this.outputChunkCount++;
if (part.type === "text-delta") this.outputAccumulatedText += part.payload.text;
}
/** Get final output for span */
getFinalOutput() {
return {
totalChunks: this.outputChunkCount,
accumulatedText: this.outputAccumulatedText
};
}
};
function areProcessorMessageArraysEqual(before, after) {
if (before === after) return true;
if (!before || !after) return before === after;
return before.length === after.length && before.every((message, index) => messagesAreEqual(message, after[index]));
}
function buildProcessInputStepSpanInput(args) {
const summarizedModel = summarizeProcessorModelForSpan(args.model);
const summarizedTools = summarizeProcessorToolsForSpan(args.tools);
const summarizedToolChoice = summarizeToolChoiceForSpan(args.toolChoice, args.tools);
const summarizedActiveTools = summarizeActiveToolsForSpan(args.activeTools, args.tools);
return {
messages: args.messages,
systemMessages: args.systemMessages,
stepNumber: args.stepNumber,
...args.messageId ? { messageId: args.messageId } : {},
retryCount: args.retryCount,
...summarizedModel ? { model: summarizedModel } : {},
...summarizedTools ? { tools: summarizedTools } : {},
...summarizedToolChoice ? { toolChoice: summarizedToolChoice } : {},
...summarizedActiveTools ? { activeTools: summarizedActiveTools } : {}
};
}
function buildProcessInputStepSpanOutput(args) {
const output = {};
if (!areProcessorMessageArraysEqual(args.beforeMessages, args.messages)) output.messages = args.messages;
if (!areProcessorMessageArraysEqual(args.beforeSystemMessages, args.systemMessages)) output.systemMessages = args.systemMessages;
if (args.afterStepInput.messageId !== args.beforeStepInput.messageId) output.messageId = args.afterStepInput.messageId;
if (args.result.model !== void 0 || args.afterStepInput.model !== args.beforeStepInput.model) {
const model = summarizeProcessorModelForSpan(args.afterStepInput.model);
if (model) output.model = model;
}
if (args.result.tools !== void 0 || args.afterStepInput.tools !== args.beforeStepInput.tools) {
const tools = summarizeProcessorToolsForSpan(args.afterStepInput.tools);
if (tools) output.tools = tools;
}
if (args.result.toolChoice !== void 0 || args.afterStepInput.toolChoice !== args.beforeStepInput.toolChoice || args.afterStepInput.tools !== args.beforeStepInput.tools) {
const toolChoice = summarizeToolChoiceForSpan(args.afterStepInput.toolChoice, args.afterStepInput.tools);
if (toolChoice) output.toolChoice = toolChoice;
}
if (args.result.activeTools !== void 0 || args.afterStepInput.activeTools !== args.beforeStepInput.activeTools || args.afterStepInput.tools !== args.beforeStepInput.tools) {
const activeTools = summarizeActiveToolsForSpan(args.afterStepInput.activeTools, args.afterStepInput.tools);
if (activeTools) output.activeTools = activeTools;
}
if (args.result.retryCount !== void 0) output.retryCount = args.result.retryCount;
return output;
}
var ProcessorRunner = class ProcessorRunner {
inputProcessors;
outputProcessors;
errorProcessors;
logger;
agentName;
agent;
/**
* Shared processor state that persists across loop iterations.
* Used by all processor methods (input and output) to share state.
* Keyed by processor ID.
*/
processorStates;
constructor({ inputProcessors, outputProcessors, errorProcessors, logger, agentName, agent, processorStates }) {
this.inputProcessors = inputProcessors ?? [];
this.outputProcessors = outputProcessors ?? [];
this.errorProcessors = errorProcessors ?? [];
this.logger = logger;
this.agentName = agentName;
this.agent = agent;
this.processorStates = processorStates ?? /* @__PURE__ */ new Map();
}
/**
* Get or create ProcessorState for the given processor ID.
* This state persists across loop iterations and is shared between
* all processor methods (input and output).
*/
getProcessorState(processorId) {
let state = this.processorStates.get(processorId);
if (!state) {
state = new ProcessorState();
this.processorStates.set(processorId, state);
}
return state;
}
async runComputeStateSignal({ processor, messageList, stepNumber, steps, requestContext, writer, abort, processorState, memory, resourceId, threadId, abortSignal, retryCount, rotateResponseMessageId }) {
const computeStateSignal = processor.computeStateSignal?.bind(processor);
if (!computeStateSignal) return;
const memoryContext = parseMemoryRequestContext(requestContext);
const resolvedMemory = memory;
const resolvedThreadId = threadId ?? memoryContext?.thread?.id;
const resolvedResourceId = resourceId ?? memoryContext?.resourceId;
if (!resolvedMemory) throw new Error(`[Processor:${processor.id}] computeStateSignal requires Mastra memory with an active resourceId and threadId`);
if (!resolvedThreadId || !resolvedResourceId) {
this.logger.debug(`[Processor:${processor.id}] computeStateSignal skipped — no threadId/resourceId resolved for this invocation`);
return;
}
const loadedThread = await resolvedMemory.getThreadById({ threadId: resolvedThreadId }) ?? memoryContext?.thread;
if (!loadedThread) throw new Error(`[Processor:${processor.id}] computeStateSignal could not load thread ${resolvedThreadId}`);
let thread = {
...loadedThread,
id: resolvedThreadId,
resourceId: loadedThread.resourceId ?? resolvedResourceId,
createdAt: loadedThread.createdAt ?? /* @__PURE__ */ new Date(),
updatedAt: loadedThread.updatedAt ?? /* @__PURE__ */ new Date(),
metadata: loadedThread.metadata
};
const stateId = processor.stateId ?? processor.id;
const beforeAddStateSignal = rotateResponseMessageId ? () => {
messageList.markResponseMessageBoundary();
rotateResponseMessageId();
} : void 0;
const tracking = getStateSignalsMetadata(thread.metadata)[stateId];
const { activeStateSignals, contextWindow, lastSnapshot, deltasSinceSnapshot } = await resolveStateSignalHistory({
messageList,
memory: resolvedMemory,
threadId: resolvedThreadId,
stateId,
tracking
});
const result = await computeStateSignal({
messages: messageList.get.all.db(),
messageList,
stepNumber,
steps,
state: processorState.customState,
requestContext,
writer,
abortSignal,
abort,
retryCount,
resourceId: resolvedResourceId,
threadId: resolvedThreadId,
activeStateSignals,
contextWindow,
lastSnapshot,
deltasSinceSnapshot,
tracking,
sendStateSignal: async (stateSignal) => {
const sendResult = await applyStateSignal({
input: stateSignal,
memory: resolvedMemory,
thread,
resourceId: resolvedResourceId,
threadId: resolvedThreadId,
memoryConfig: memoryContext?.memoryConfig,
messageList,
defaultId: stateId,
beforeAddSignal: beforeAddStateSignal,
writeSignal: (signal) => writer?.custom(signal.toDataPart())
});
if (!sendResult.skipped) {
const updated = await resolvedMemory.getThreadById({ threadId: resolvedThreadId });
if (updated) thread = {
...thread,
metadata: updated.metadata
};
}
return sendResult.skipped ? sendResult : sendResult.signal;
}
});
if (!result) return;
await applyStateSignal({
input: result,
memory: resolvedMemory,
thread,
resourceId: resolvedResourceId,
threadId: resolvedThreadId,
memoryConfig: memoryContext?.memoryConfig,
messageList,
defaultId: stateId,
beforeAddSignal: beforeAddStateSignal,
writeSignal: (signal) => writer?.custom(signal.toDataPart())
});
}
async runWorkflowComputeStateSignals({ workflow, messageList, stepNumber, steps, requestContext, writer, memory, resourceId, threadId, abortSignal, retryCount, rotateResponseMessageId }) {
for (const processor of workflow.__stateSignalProcessors ?? []) {
const abort = (reason, options) => {
throw new TripWire(reason || `Tripwire triggered by ${processor.id}`, options, processor.id);
};
await this.runComputeStateSignal({
processor,
messageList,
stepNumber,
steps,
requestContext,
writer,
abort,
processorState: this.getProcessorState(processor.id),
memory,
resourceId,
threadId,
abortSignal,
retryCount,
rotateResponseMessageId
});
}
}
/**
* Execute a workflow as a processor and handle the result.
* Returns the processed messages and any tripwire information.
*/
async executeWorkflowAsProcessor(workflow, input, observabilityContext, requestContext, writer, abortSignal) {
const result = await (await workflow.createRun()).start({
inputData: {
...input,
processorStates: this.processorStates,
abortSignal,
agent: this.agent
},
...observabilityContext,
requestContext,
outputWriter: writer ? (chunk) => writer.custom(chunk) : void 0
});
if (result.status === "tripwire") {
const tripwireData = result.tripwire;
throw new TripWire(tripwireData?.reason || `Tripwire triggered in workflow ${workflow.id}`, {
retry: tripwireData?.retry,
metadata: tripwireData?.metadata
}, tripwireData?.processorId || workflow.id);
}
if (result.status !== "success") {
const details = [];
if (result.status === "failed") {
if (result.error) details.push(result.error.message || JSON.stringify(result.error));
for (const [stepId, step] of Object.entries(result.steps)) if (step.status === "failed" && step.error?.message) details.push(`step ${stepId}: ${step.error.message}`);
}
const detailStr = details.length > 0 ? ` — ${details.join("; ")}` : "";
throw new MastraError({
category: "USER",
domain: "AGENT",
id: "PROCESSOR_WORKFLOW_FAILED",
text: `Processor workflow ${workflow.id} failed with status: ${result.status}${detailStr}`
});
}
const output = result.result;
if (!output || typeof output !== "object") return input;
if (!("phase" in output) || !("messages" in output || "part" in output || "messageList" in output)) throw new MastraError({
category: "USER",
domain: "AGENT",
id: "PROCESSOR_WORKFLOW_INVALID_OUTPUT",
text: `Processor workflow ${workflow.id} returned invalid output format. Expected ProcessorStepOutput.`
});
return output;
}
async runOutputProcessors(messageList, observabilityContext, requestContext, retryCount = 0, writer, result) {
for (const [index, processorOrWorkflow] of this.outputProcessors.entries()) {
let processableMessages = [...messageList.get.response.db()];
const idsBeforeProcessing = processableMessages.map((m) => m.id);
const check = messageList.makeMessageSourceChecker();
if (isProcessorWorkflow(processorOrWorkflow)) {
await this.executeWorkflowAsProcessor(processorOrWorkflow, {
phase: "outputResult",
messages: processableMessages,
messageList,
retryCount,
result
}, observabilityContext, requestContext, writer);
continue;
}
const processor = processorOrWorkflow;
const abort = (reason, options) => {
throw new TripWire(reason || `Tripwire triggered by ${processor.id}`, options, processor.id);
};
const processMethod = processor.processOutputResult?.bind(processor);
if (!processMethod) continue;
const outputMessagesBefore = processableMessages;
const outputSystemMessagesBefore = messageList.getAllSystemMessages();
const defaultResult = {
text: "",
usage: {
inputTokens: 0,
outputTokens: 0,
totalTokens: 0
},
finishReason: "unknown",
steps: []
};
const summarizedResult = result ? summarizeProcessorResultForSpan(result) : void 0;
const currentSpan = observabilityContext?.tracingContext?.currentSpan;
const processorSpan = (currentSpan?.findParent("agent_run") || currentSpan?.parent || currentSpan)?.createChildSpan({
type: "processor_run",
name: `output processor: ${processor.id}`,
entityType: EntityType.OUTPUT_PROCESSOR,
entityId: processor.id,
entityName: processor.name,
attributes: {
processorExecutor: "legacy",
processorIndex: index
},
input: {
messages: processableMessages,
...summarizedResult ? { result: summarizedResult } : {},
retryCount
}
});
messageList.startRecording();
try {
const processorState = this.getProcessorState(processor.id);
const processResult = await processMethod({
messages: processableMessages,
messageList,
state: processorState.customState,
result: result ?? defaultResult,
abort,
agent: this.agent,
...createObservabilityContext({ currentSpan: processorSpan }),
requestContext,
retryCount,
writer,
sendSignal: createProcessorSendSignal({
messageList,
writer
})
});
const mutations = messageList.stopRecording();
if (processResult instanceof MessageList) {
if (processResult !== messageList) throw new MastraError({
category: "USER",
domain: "AGENT",
id: "PROCESSOR_RETURNED_EXTERNAL_MESSAGE_LIST",
text: `Processor ${processor.id} returned a MessageList instance other than the one that was passed in as an argument. New external message list instances are not supported. Use the messageList argument instead.`
});
if (mutations.length > 0) processableMessages = processResult.get.response.db();
} else if (processResult) {
const deletedIds = idsBeforeProcessing.filter((i) => !processResult.some((m) => m.id === i));
if (deletedIds.length) messageList.removeByIds(deletedIds);
processableMessages = processResult || [];
for (const message of processResult) {
messageList.removeByIds([message.id]);
messageList.add(message, check.getSource(message) || "response", { merge: false });
}
}
processorSpan?.end({
output: {
...!areProcessorMessageArraysEqual(outputMessagesBefore, processableMessages) ? { messages: processableMessages } : {},
...!areProcessorMessageArraysEqual(outputSystemMessagesBefore, messageList.getAllSystemMessages()) ? { systemMessages: messageList.getAllSystemMessages() } : {}
},
attributes: mutations.length > 0 ? { messageListMutations: mutations } : void 0
});
} catch (error) {
messageList.stopRecording();
if (error instanceof TripWire) {
processorSpan?.error({
error,
endSpan: true,
attributes: { tripwireAbort: {
reason: error.message,
retry: error.options?.retry,
metadata: error.options?.metadata
} }
});
await invokeOnViolation(processor, error);
throw error;
}
processorSpan?.error({
error,
endSpan: true
});
throw error;
}
}
return messageList;
}
/**
* Process a stream part through all output processors with state management
*/
async processPart(part, processorStates, observabilityContext, requestContext, messageList, retryCount = 0, writer) {
if (!this.outputProcessors.length) return {
part,
blocked: false
};
try {
let processedPart = part;
const isFinishChunk = part.type === "finish";
for (const [index, processorOrWorkflow] of this.outputProcessors.entries()) {
if (isProcessorWorkflow(processorOrWorkflow)) {
if (!processedPart) continue;
const workflowId = processorOrWorkflow.id;
let state = processorStates.get(workflowId);
if (!state) {
state = new ProcessorState();
processorStates.set(workflowId, state);
}
state.addInputPart(processedPart);
try {
const result = await this.executeWorkflowAsProcessor(processorOrWorkflow, {
phase: "outputStream",
part: processedPart,
streamParts: state.streamParts,
state: state.customState,
messageList,
retryCount
}, observabilityContext, requestContext, writer);
if ("part" in result) processedPart = result.part;
state.addOutputPart(processedPart);
} catch (error) {
if (error instanceof TripWire) return {
part: null,
blocked: true,
reason: error.message,
tripwireOptions: error.options,
processorId: error.processorId || workflowId
};
this.logger.error("Output processor workflow failed", {
agent: this.agentName,
workflowId,
error
});
}
continue;
}
const processor = processorOrWorkflow;
try {
if (processor.processOutputStream && processedPart) {
let state = processorStates.get(processor.id);
if (!state) {
state = new ProcessorState({
processorName: processor.name ?? processor.id,
...observabilityContext,
processorIndex: index,
createSpan: true
});
processorStates.set(processor.id, state);
}
state.addInputPart(processedPart);
processedPart = await processor.processOutputStream({
part: processedPart,
streamParts: state.streamParts,
state: state.customState,
agent: this.agent,
abort: (reason, options) => {
throw new TripWire(reason || `Stream part blocked by ${processor.id}`, options, processor.id);
},
...createObservabilityContext({ currentSpan: state.span }),
requestContext,
messageList,
retryCount,
writer
});
state.addOutputPart(processedPart);
}
} catch (error) {
if (error instanceof TripWire) {
processorStates.get(processor.id)?.span?.error({
error,
endSpan: true,
attributes: { tripwireAbort: {
reason: error.message,
retry: error.options?.retry,
metadata: error.options?.metadata
} }
});
await invokeOnViolation(processor, error);
return {
part: null,
blocked: true,
reason: error.message,
tripwireOptions: error.options,
processorId: processor.id
};
}
processorStates.get(processor.id)?.span?.error({
error,
endSpan: true
});
this.logger.error("Output processor failed", {
agent: this.agentName,
processorId: processor.id,
error
});
}
}
if (isFinishChunk) {
for (const state of processorStates.values()) if (state.span) state.span.end({ output: state.getFinalOutput() });
}
return {
part: processedPart,
blocked: false
};
} catch (error) {
this.logger.error("Stream part processing failed", {
agent: this.agentName,
error
});
for (const state of processorStates.values()) state.span?.error({
error,
endSpan: true
});
return {
part,
blocked: false
};
}
}
/**
* Re-drive any parts that stream processors stashed for reprocessing through
* the full output processor chain.
*
* A stream processor can only return one part from `processOutputStream`, but
* some processors (e.g. `BatchPartsProcessor`) need to emit a second part for
* one input — it returns the first part and stashes the second under
* `REPROCESS_PART_KEY` on its state. After the primary part has been emitted,
* callers invoke this to push each stashed part back through the whole chain
* (so it receives downstream processing) and emit the results in order.
*
* Returns the processed results in emission order. Reprocessing can itself
* stash more parts, so this drains until none remain.
*/
async drainReprocessParts(processorStates, observabilityContext, requestContext, messageList, retryCount = 0, writer) {
const results = [];
const takeNext = () => {
for (const state of processorStates.values()) {
const custom = state.customState;
const stashed = custom[REPROCESS_PART_KEY];
if (stashed) {
delete custom[REPROCESS_PART_KEY];
return stashed;
}
}
};
let guard = 0;
let next = takeNext();
while (next && guard++ < 1e3) {
const result = await this.processPart(next, processorStates, observabilityContext, requestContext, messageList, retryCount, writer);
results.push(result);
if (result.blocked) break;
next = takeNext();
}
return results;
}
async runOutputProcessorsForStream(streamResult, observabilityContext, writer) {
return new ReadableStream({ start: async (controller) => {
const reader = streamResult.fullStream.getReader();
const processorStates = /* @__PURE__ */ new Map();
const streamWriter = writer ?? { custom: async (data) => controller.enqueue(data) };
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
controller.close();
break;
}
const { part: processedPart, blocked, reason, tripwireOptions, processorId } = await this.processPart(value, processorStates, observabilityContext, void 0, void 0, 0, streamWriter);
const enqueueTripwire = (r, opts, pid) => {
this.logger.debug("Stream part blocked by output processor", {
agent: this.agentName,
reason: r,
originalPart: value
});
controller.enqueue({
type: "tripwire",
payload: {
reason: r || "Output processor blocked content",
retry: opts?.retry,
metadata: opts?.metadata,
processorId: pid
}
});
};
if (blocked) {
enqueueTripwire(reason, tripwireOptions, processorId);
controller.close();
break;
} else if (processedPart != null) controller.enqueue(processedPart);
const reprocessed = await this.drainReprocessParts(processorStates, observabilityContext, void 0, void 0, 0, streamWriter);
let aborted = false;
for (const r of reprocessed) {
if (r.blocked) {
enqueueTripwire(r.reason, r.tripwireOptions, r.processorId);
controller.close();
aborted = true;
break;
}
if (r.part != null) controller.enqueue(r.part);
}
if (aborted) break;
}
} catch (error) {
controller.error(error);
}
} });
}
async runInputProcessors(messageList, observabilityContext, requestContext, retryCount = 0) {
for (const [index, processorOrWorkflow] of this.inputProcessors.entries()) {
let processableMessages = messageList.get.input.db();
const inputIds = processableMessages.map((m) => m.id);
const check = messageList.makeMessageSourceChecker();
if (isProcessorWorkflow(processorOrWorkflow)) {
const currentSystemMessages = messageList.getSystemMessages();
await this.executeWorkflowAsProcessor(processorOrWorkflow, {
phase: "input",
messages: processableMessages,
messageList,
systemMessages: currentSystemMessages,
retryCount
}, observabilityContext, requestContext);
continue;
}
const processor = processorOrWorkflow;
const abort = (reason, options) => {
throw new TripWire(reason || `Tripwire triggered by ${processor.id}`, options, processor.id);
};
const processMethod = processor.processInput?.bind(processor);
if (!processMethod) continue;
const currentSystemMessages = messageList.getSystemMessages();
const inputMessagesBefore = processableMessages;
const inputSystemMessagesBefore = currentSystemMessages;
const currentSpan = observabilityContext?.tracingContext?.currentSpan;
const processorSpan = (currentSpan?.findParent("agent_run") || currentSpan?.parent || currentSpan)?.createChildSpan({
type: "processor_run",
name: `input processor: ${processor.id}`,
entityType: EntityType.INPUT_PROCESSOR,
entityId: processor.id,
entityName: processor.name,
attributes: {
processorExecutor: "legacy",
processorIndex: index
},
input: {
messages: processableMessages,
systemMessages: currentSystemMessages
}
});
messageList.startRecording();
try {
const processorState = this.getProcessorState(processor.id);
const result = await processMethod({
messages: processableMessages,
systemMessages: currentSystemMessages,
state: processorState.customState,
abort,
agent: this.agent,
...createObservabilityContext({ currentSpan: processorSpan }),
messageList,
requestContext,
retryCount,
sendSignal: createProcessorSendSignal({ messageList })
});
let mutations;
if (result instanceof MessageList) {
if (result !== messageList) throw new MastraError({
category: "USER",
domain: "AGENT",
id: "PROCESSOR_RETURNED_EXTERNAL_MESSAGE_LIST",
text: `Processor ${processor.id} returned a MessageList instance other than the one that was passed in as an argument. New external message list instances are not supported. Use the messageList argument instead.`
});
mutations = messageList.stopRecording();
if (mutations.length > 0) processableMessages = messageList.get.input.db();
} else if (this.isProcessInputResultWithSystemMessages(result)) {
mutations = messageList.stopRecording();
messageList.replaceAllSystemMessages(result.systemMessages);
const regularMessages = result.messages;
if (regularMessages) {
const deletedIds = inputIds.filter((i) => !regularMessages.some((m) => m.id === i));
if (deletedIds.length) messageList.removeByIds(deletedIds);
const newSystemMessages = regularMessages.filter((m) => m.role === "system");
const nonSystemMessages = regularMessages.filter((m) => m.role !== "system");
for (const sysMsg of newSystemMessages) {
const systemText = sysMsg.content.content ?? sysMsg.content.parts?.map((p) => p.type === "text" ? p.text : "").join("\n") ?? "";
messageList.addSystem(systemText);
}
if (nonSystemMessages.length > 0) for (const message of nonSystemMessages) {
messageList.removeByIds([message.id]);
messageList.add(message, check.getSource(message) || "input", { merge: false });
}
}
processableMessages = messageList.get.input.db();
} else {
mutations = messageList.stopRecording();
if (result) {
const deletedIds = inputIds.filter((i) => !result.some((m) => m.id === i));
if (deletedIds.length) messageList.removeByIds(deletedIds);
const systemMessages = result.filter((m) => m.role === "system");
const nonSystemMessages = result.filter((m) => m.role !== "system");
for (const sysMsg of systemMessages) {
const systemText = sysMsg.content.content ?? sysMsg.content.parts?.map((p) => p.type === "text" ? p.text : "").join("\n") ?? "";
messageList.addSystem(systemText);
}
if (nonSystemMessages.length > 0) for (const message of nonSystemMessages) {
messageList.removeByIds([message.id]);
messageList.add(message, check.getSource(message) || "input", { merge: false });
}
processableMessages = messageList.get.input.db();
}
}
processorSpan?.end({
output: {
...!areProcessorMessageArraysEqual(inputMessagesBefore, processableMessages) ? { messages: processableMessages } : {},
...!areProcessorMessageArraysEqual(inputSystemMessagesBefore, messageList.getSystemMessages()) ? { systemMessages: messageList.getSystemMessages() } : {}
},
attributes: mutations.length > 0 ? { messageListMutations: mutations } : void 0
});
} catch (error) {
messageList.stopRecording();
if (error instanceof TripWire) {
processorSpan?.error({
error,
endSpan: true,
attributes: { tripwireAbort: {
reason: error.message,
retry: error.options?.retry,
metadata: error.options?.metadata
} }
});
await invokeOnViolation(processor, error);
throw error;
}
processorSpan?.error({
error,
endSpan: true
});
throw error;
}
}
return messageList;
}
/**
* Run processInputStep for all processors that implement it.
* Called at each step of the agentic loop, before the LLM is invoked.
*
* Unlike processInput which runs once at the start, this runs at every step
* (including tool call continuations). This is useful for:
* - Transforming message types between steps (e.g., AI SDK 'reasoning' -> Anthropic 'thinking')
* - Modifying messages based on step context
* - Implementing per-step message transformations
*
* @param args.messages - The current messages to be sent to the LLM (MastraDBMessage format)
* @param args.messageList - MessageList instance for managing message sources
* @param args.stepNumber - The current step number (0-indexed)
* @param args.tracingContext - Optional tracing context for observability
* @param args.requestContext - Optional runtime context with execution metadata
*
* @returns The processed MessageList
*/
async runProcessInputStep(args) {
const { messageList, stepNumber, steps, requestContext, writer } = args;
const observabilityContext = resolveObservabilityContext(args);
const stepInput = {
messageId: args.messageId,
tools: args.tools,
toolChoice: args.toolChoice,
model: args.model,
activeTools: args.activeTools,
providerOptions: args.providerOptions,
modelSettings: args.modelSettings,
structuredOutput: args.structuredOutput,
retryCount: args.retryCount ?? 0
};
const processors = stepInput.model && isMaybeClaude46(stepInput.model) ? [...this.inputProcessors, new TrailingAssistantGuard()] : this.inputProcessors;
for (const [index, processorOrWorkflow] of processors.entries()) {
const processableMessages = messageList.get.all.db();
const idsBeforeProcessing = processableMessages.map((m) => m.id);
const check = messageList.makeMessageSourceChecker();
if (isProcessorWorkflow(processorOrWorkflow)) {
const currentSystemMessages = messageList.getSystemMessages();
const result = await this.executeWorkflowAsProcessor(processorOrWorkflow, {
phase: "inputStep",
messages: processableMessages,
messageList,
stepNumber,
steps,
systemMessages: currentSystemMessages,
rotateResponseMessageId: args.rotateResponseMessageId ? () => {
const nextMessageId = args.rotateResponseMessageId();
stepInput.messageId = nextMessageId;
return nextMessageId;
} : void 0,
...stepInput
}, observabilityContext, requestContext, writer, args.abortSignal);
Object.assign(stepInput, result);
await this.runWorkflowComputeStateSignals({
workflow: processorOrWorkflow,
messageList,
stepNumber,
steps,
requestContext,
writer,
memory: args.memory,
resourceId: args.resourceId,
threadId: args.threadId,
abortSignal: args.abortSignal,
retryCount: args.retryCount ?? 0,
rotateResponseMessageId: args.rotateResponseMessageId ? () => {
const nextMessageId = args.rotateResponseMessageId();
stepInput.messageId = nextMessageId;
return nextMessageId;
} : void 0
});
continue;
}
const processor = processorOrWorkflow;
const processMethod = processor.processInputStep?.bind(processor);
const computeStateSignal = processor.computeStateSignal?.bind(processor);
if (!processMethod && !computeStateSignal) continue;
const abort = (reason, options) => {
throw new TripWire(reason || `Tripwire triggered by ${processor.id}`, options, processor.id);
};
const currentSystemMessages = messageList.getSystemMessages();
const inputData = {
messages: processableMessages,
stepNumber,
steps,
messageId: stepInput.messageId,
systemMessages: currentSystemMessages,
tools: stepInput.tools,
toolChoice: stepInput.toolChoice,
model: stepInput.model,
activeTools: stepInput.activeTools,
providerOptions: stepInput.providerOptions,
modelSettings: stepInput.modelSettings,
structuredOutput: stepInput.structuredOutput,
requestContext,
agent: this.agent
};
const processorSpan = (observabilityContext.tracingContext?.currentSpan)?.createChildSpan({
type: "processor_run",
name: `input step processor: ${processor.id}`,
entityType: EntityType.INPUT_STEP_PROCESSOR,
entityId: processor.id,
entityName: processor.name,
attributes: {
processorExecutor: "legacy",
processorIndex: index
},
input: buildProcessInputStepSpanInput({
messages: inputData.messages,
systemMessages: inputData.systemMessages,
stepNumber: inputData.stepNumber,
messageId: inputData.messageId,
retryCount: args.retryCount ?? 0,
model: inputData.model,
tools: inputData.tools,
toolChoice: inputData.toolChoice,
activeTools: inputData.activeTools
})
});
messageList.startRecording();
try {
const processorState = this.getProcessorState(processor.id);
const beforeStepInput = {
messageId: inputData.messageId,
model: inputData.model,
tools: inputData.tools,
toolChoice: inputData.toolChoice,
activeTools: inputData.activeTools
};
const rotateResponseMessageId = args.rotateResponseMessageId ? () => {
const nextMessageId = args.rotateResponseMessageId();
stepInput.messageId = nextMessageId;
return nextMessageId;
} : void 0;
const processMethodArgs = {
messageList,
...inputData,
state: processorState.customState,
abort,
...rotateResponseMessageId ? { rotateResponseMessageId } : {},
...createObservabilityContext({ currentSpan: processorSpan }),
retryCount: args.retryCount ??