UNPKG

@mastra/core

Version:
1,308 lines 152 kB
const require_rolldown_runtime = require("./rolldown-runtime-uwYp4b74.cjs"); const require_base = require("./base-B6soWsYg.cjs"); const require_error = require("./error-B-e62x-A.cjs"); const require_observability = require("./observability-BzV5axz0.cjs"); const require_utils = require("./utils-CNiGU0Uf.cjs"); require("./tracing-BUrUJwCM.cjs"); const require_llm = require("./llm-CmflaXHA.cjs"); const require_signals = require("./signals-D2CulJo3.cjs"); const require_message_list = require("./message-list-BM7m-E-v.cjs"); const require_dist = require("./dist-BGdEcgoh.cjs"); const require_types = require("./types-ftn4_F2h.cjs"); let crypto = require("crypto"); let events = require("events"); let _mastra_schema_compat = require("@mastra/schema-compat"); let stream_web = require("stream/web"); let _mastra_schema_compat_schema = require("@mastra/schema-compat/schema"); //#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 require_dist.JSONParseError.isInstance(error) || require_dist.NoObjectGeneratedError.isInstance(error) || require_dist.TypeValidationError.isInstance(error) || error instanceof require_error.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 require_error.MastraError({ id: "STRUCTURED_OUTPUT_OPTIONS_REQUIRED", domain: require_error.ErrorDomain.AGENT, category: require_error.ErrorCategory.USER, text: "structuredOutput is required to use tryGenerateWithJsonFallback" }); try { const result = await agent.generate(prompt, options); if (result.object === void 0) throw new require_error.MastraError({ id: "STRUCTURED_OUTPUT_OBJECT_UNDEFINED", domain: require_error.ErrorDomain.AGENT, category: require_error.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 require_error.MastraError({ id: "STRUCTURED_OUTPUT_OBJECT_UNDEFINED", domain: require_error.ErrorDomain.AGENT, category: require_error.ErrorCategory.USER, text: "structuredOutput object is undefined" }); return result; } } async function tryStreamWithJsonFallback(agent, prompt, options) { if (!options.structuredOutput?.schema) throw new require_error.MastraError({ id: "STRUCTURED_OUTPUT_OPTIONS_REQUIRED", domain: require_error.ErrorDomain.AGENT, category: require_error.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 require_error.MastraError({ id: "STRUCTURED_OUTPUT_OBJECT_UNDEFINED", domain: require_error.ErrorDomain.AGENT, category: require_error.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 require_error.MastraError({ id: "STRUCTURED_OUTPUT_OBJECT_UNDEFINED", domain: require_error.ErrorDomain.AGENT, category: require_error.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 = require_signals.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: (0, crypto.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__ */ require_rolldown_runtime.__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: require_utils.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) => require_message_list.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 = require_types.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 = require_types.getStateSignalsMetadata(thread.metadata)[stateId]; const { activeStateSignals, contextWindow, lastSnapshot, deltasSinceSnapshot } = await require_types.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 require_types.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 require_types.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 require_error.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 require_error.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: require_utils.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, ...require_observability.createObservabilityContext({ currentSpan: processorSpan }), requestContext, retryCount, writer, sendSignal: createProcessorSendSignal({ messageList, writer }) }); const mutations = messageList.stopRecording(); if (processResult instanceof require_message_list.MessageList) { if (processResult !== messageList) throw new require_error.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); }, ...require_observability.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: require_utils.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, ...require_observability.createObservabilityContext({ currentSpan: processorSpan }), messageList, requestContext, retryCount, sendSignal: createProcessorSendSignal({ messageList }) }); let mutations; if (result instanceof require_message_list.MessageList) { if (result !== messageList) throw new require_error.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 = require_observability.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: require_utils.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 } :