@mastra/core
Version:
1 lines • 316 kB
Source Map (JSON)
{"version":3,"file":"trip-wire-dxd_uCHj.cjs","names":["JSONParseError","NoObjectGeneratedError","TypeValidationError","MastraError","ErrorDomain","ErrorCategory","createSignal","EntityType","messagesAreEqual","parseMemoryRequestContext","getStateSignalsMetadata","resolveStateSignalHistory","applyStateSignal","MastraError","createObservabilityContext","MessageList","resolveObservabilityContext","resolveModelConfig","MastraBase","readModelStreamTransport","AnthropicSchemaCompatLayer","MastraError","ErrorDomain","ErrorCategory","parsePartialJson","isDeepEqualData","TransformStream","convertMessages","coreContentToString","MastraBase","EventEmitter","#options","#transportRef","#returnScorerData","getRootExportSpan","#model","#structuredOutputMode","TransformStream","resolveObservabilityContext","#baseStream","#status","#wasSuspended","#delayedPromises","#finishCallbackSent","#createSuspendedOnFinishPayload","#createAbortedOnFinishPayload","#closeTransportIfNeeded","#bufferedObject","#bufferedSources","#bufferedByStep","#bufferedText","#bufferedTextChunks","#toolCallDeltaIdNameMap","#toolCallStreamingMeta","#toolCallArgsDeltas","#toolCalls","#emitChunk","#bufferedFiles","#bufferedReasoningDetails","#bufferedReasoning","#toolResults","#warnings","#request","#bufferedSteps","#truncateAtNextStepFinish","#truncateRunBuffers","#tripwire","#finishReason","#streamFinished","#usageCount","#emitter","#stepProviderMetadata","#error","getErrorFromUnknown","#getTotalUsage","MastraError","ErrorDomain","ErrorCategory","#transportClosed","#consumptionStarted","#getDelayedPromise","#createEventedStream","#consumeStreamPromise","#consumeStreamErrored","#consumeStreamError","#bufferedChunks","ReadableStream","resolveObservabilityContext","ReadableStream"],"sources":["../src/processors/is-processor-workflow.ts","../src/stream/aisdk/v5/compat/delayed-promise.ts","../src/stream/aisdk/v5/compat/consume-stream.ts","../src/agent/utils.ts","../src/processors/send-signal.ts","../src/processors/span-payload.ts","../src/processors/stream-reprocess.ts","../src/processors/trailing-assistant-guard.ts","../src/processors/runner.ts","../src/stream/base/input.ts","../src/stream/base/schema.ts","../src/stream/base/output-format-handlers.ts","../src/stream/base/output.ts","../src/agent/trip-wire.ts"],"sourcesContent":["import type { ProcessorWorkflow } from './index';\n\n/**\n * Type guard to check if an object is a Workflow that can be used as a processor.\n *\n * Extracted to its own module so that `runner.ts` (and by extension\n * `stream/base/output.ts`) can use it without loading the full processors\n * barrel — which re-exports every built-in processor, many of which import\n * from the agent barrel and create ESM init-time cycles.\n */\nexport function isProcessorWorkflow(obj: unknown): obj is ProcessorWorkflow {\n return (\n obj !== null &&\n typeof obj === 'object' &&\n 'id' in obj &&\n typeof (obj as Record<string, unknown>).id === 'string' &&\n 'inputSchema' in obj &&\n 'outputSchema' in obj &&\n 'execute' in obj &&\n typeof (obj as Record<string, unknown>).execute === 'function' &&\n !('processInput' in obj) &&\n !('processInputStep' in obj) &&\n !('processOutputStream' in obj) &&\n !('processOutputResult' in obj) &&\n !('processOutputStep' in obj) &&\n !('processToolResult' in obj) &&\n !('processLLMRequest' in obj) &&\n !('processAPIError' in obj)\n );\n}\n","/**\n * Delayed promise. It is only constructed once the value is accessed.\n * This is useful to avoid unhandled promise rejections when the promise is created\n * but not accessed.\n */\nexport class DelayedPromise<T> {\n public status: { type: 'pending' } | { type: 'resolved'; value: T } | { type: 'rejected'; error: unknown } = {\n type: 'pending',\n };\n private _promise: Promise<T> | undefined;\n private _resolve: undefined | ((value: T) => void) = undefined;\n private _reject: undefined | ((error: unknown) => void) = undefined;\n\n get promise(): Promise<T> {\n if (this._promise) {\n return this._promise;\n }\n\n this._promise = new Promise<T>((resolve, reject) => {\n if (this.status.type === 'resolved') {\n resolve(this.status.value);\n } else if (this.status.type === 'rejected') {\n reject(this.status.error);\n }\n\n this._resolve = resolve;\n this._reject = reject;\n });\n\n return this._promise;\n }\n\n resolve(value: T): void {\n this.status = { type: 'resolved', value };\n\n if (this._promise) {\n this._resolve?.(value);\n }\n }\n\n reject(error: unknown): void {\n this.status = { type: 'rejected', error };\n\n if (this._promise) {\n this._reject?.(error);\n }\n }\n}\n","import type { IMastraLogger } from '../../../../logger';\n\nexport type ConsumeStreamOptions = {\n onError?: (error: unknown) => void;\n logger?: IMastraLogger;\n};\n\nexport async function consumeStream({\n stream,\n onError,\n logger,\n}: {\n stream: ReadableStream;\n onError?: (error: unknown) => void;\n logger?: IMastraLogger;\n}): Promise<void> {\n const reader = stream.getReader();\n try {\n while (true) {\n const { done } = await reader.read();\n if (done) break;\n }\n } catch (error) {\n logger?.error('consumeStream error', error);\n onError?.(error);\n } finally {\n reader.releaseLock();\n }\n}\n","import { JSONParseError, NoObjectGeneratedError, TypeValidationError } from '@internal/ai-sdk-v5';\n\nimport { ErrorCategory, ErrorDomain, MastraError } from '../error';\nimport type { MastraLegacyLanguageModel, MastraLanguageModel } from '../llm/model/shared.types';\nimport type { StorageThreadType } from '../memory';\nimport type { StandardSchemaWithJSON, InferStandardSchemaOutput } from '../schema';\nimport type { FullOutput } from '../stream/base/output';\nimport type { Agent } from './agent';\nimport type { AgentExecutionOptions, AgentExecutionOptionsBase } from './agent.types';\nimport type { MessageListInput } from './message-list';\nimport type { StructuredOutputOptions } from './types';\n\nexport const supportedLanguageModelSpecifications = ['v2', 'v3', 'v4'];\nexport const isSupportedLanguageModel = (\n model: MastraLanguageModel | MastraLegacyLanguageModel,\n): model is MastraLanguageModel => {\n return supportedLanguageModelSpecifications.includes(model.specificationVersion);\n};\n\nfunction isStructuredOutputFormatError(error: unknown): boolean {\n return (\n JSONParseError.isInstance(error) ||\n NoObjectGeneratedError.isInstance(error) ||\n TypeValidationError.isInstance(error) ||\n (error instanceof MastraError &&\n (error.id === 'STRUCTURED_OUTPUT_OBJECT_UNDEFINED' || error.id === 'STRUCTURED_OUTPUT_SCHEMA_VALIDATION_FAILED'))\n );\n}\n\nexport async function tryGenerateWithJsonFallback<\n SCHEMA extends StandardSchemaWithJSON,\n OUTPUT extends InferStandardSchemaOutput<SCHEMA>,\n>(agent: Agent, prompt: MessageListInput, options: AgentExecutionOptions<OUTPUT>): Promise<FullOutput<OUTPUT>>;\nexport async function tryGenerateWithJsonFallback<OUTPUT extends {}>(\n agent: Agent,\n prompt: MessageListInput,\n options: AgentExecutionOptions<OUTPUT>,\n): Promise<FullOutput<OUTPUT>>;\nexport async function tryGenerateWithJsonFallback<OUTPUT>(\n agent: Agent,\n prompt: MessageListInput,\n options: AgentExecutionOptions<OUTPUT>,\n): Promise<FullOutput<OUTPUT>> {\n if (!options.structuredOutput?.schema) {\n throw new MastraError({\n id: 'STRUCTURED_OUTPUT_OPTIONS_REQUIRED',\n domain: ErrorDomain.AGENT,\n category: ErrorCategory.USER,\n text: 'structuredOutput is required to use tryGenerateWithJsonFallback',\n });\n }\n\n try {\n const result = await agent.generate(prompt, options);\n // Some models resolve without throwing but produce no parseable structured\n // object (empty/malformed JSON). Treat that the same as a thrown error so the\n // caller still gets the json-prompt-injection retry instead of a downstream\n // crash when it reads `result.object`. Mirrors tryStreamWithJsonFallback.\n if (result.object === undefined) {\n throw new MastraError({\n id: 'STRUCTURED_OUTPUT_OBJECT_UNDEFINED',\n domain: ErrorDomain.AGENT,\n category: ErrorCategory.USER,\n text: 'structuredOutput object is undefined',\n });\n }\n return result;\n } catch (error) {\n if (!isStructuredOutputFormatError(error)) throw error;\n\n console.warn('Error in tryGenerateWithJsonFallback. Attempting fallback.', error);\n const result = await agent.generate(prompt, {\n ...options,\n structuredOutput: {\n ...options.structuredOutput,\n jsonPromptInjection:\n options.structuredOutput.jsonPromptInjection === 'inline' ||\n options.structuredOutput.jsonPromptInjection === 'system'\n ? options.structuredOutput.jsonPromptInjection\n : true,\n },\n });\n if (result.object === undefined) {\n throw new MastraError({\n id: 'STRUCTURED_OUTPUT_OBJECT_UNDEFINED',\n domain: ErrorDomain.AGENT,\n category: ErrorCategory.USER,\n text: 'structuredOutput object is undefined',\n });\n }\n return result;\n }\n}\n\nexport async function tryStreamWithJsonFallback<OUTPUT extends {}>(\n agent: Agent,\n prompt: MessageListInput,\n options: AgentExecutionOptionsBase<OUTPUT> & {\n structuredOutput: StructuredOutputOptions<OUTPUT>;\n onStream?: (stream: Awaited<ReturnType<Agent['stream']>>) => void | Promise<void>;\n /** Called immediately before each primary or fallback stream invocation. */\n onStreamAttempt?: () => void | Promise<void>;\n /** Called after each stream invocation is consumed, including a failed structured-output attempt. */\n onStreamFinish?: (stream: Awaited<ReturnType<Agent['stream']>>) => void | Promise<void>;\n },\n) {\n if (!options.structuredOutput?.schema) {\n throw new MastraError({\n id: 'STRUCTURED_OUTPUT_OPTIONS_REQUIRED',\n domain: ErrorDomain.AGENT,\n category: ErrorCategory.USER,\n text: 'structuredOutput is required to use tryStreamWithJsonFallback',\n });\n }\n\n const { onStream, onStreamAttempt, onStreamFinish, ...streamOptions } = options;\n\n try {\n await onStreamAttempt?.();\n const result = await agent.stream(prompt, streamOptions);\n void onStream?.(result as unknown as Awaited<ReturnType<Agent['stream']>>);\n try {\n const object = await result.object;\n if (!object) {\n throw new MastraError({\n id: 'STRUCTURED_OUTPUT_OBJECT_UNDEFINED',\n domain: ErrorDomain.AGENT,\n category: ErrorCategory.USER,\n text: 'structuredOutput object is undefined',\n });\n }\n return result;\n } finally {\n await onStreamFinish?.(result as unknown as Awaited<ReturnType<Agent['stream']>>);\n }\n } catch (error) {\n if (!isStructuredOutputFormatError(error)) throw error;\n\n console.warn('Error in tryStreamWithJsonFallback. Attempting fallback.', error);\n await onStreamAttempt?.();\n const result = await agent.stream(prompt, {\n ...streamOptions,\n structuredOutput: {\n ...streamOptions.structuredOutput,\n jsonPromptInjection:\n streamOptions.structuredOutput.jsonPromptInjection === 'inline' ||\n streamOptions.structuredOutput.jsonPromptInjection === 'system'\n ? streamOptions.structuredOutput.jsonPromptInjection\n : true,\n },\n });\n void onStream?.(result as unknown as Awaited<ReturnType<Agent['stream']>>);\n try {\n const object = await result.object;\n if (object === undefined) {\n throw new MastraError({\n id: 'STRUCTURED_OUTPUT_OBJECT_UNDEFINED',\n domain: ErrorDomain.AGENT,\n category: ErrorCategory.USER,\n text: 'structuredOutput object is undefined',\n });\n }\n return result;\n } finally {\n await onStreamFinish?.(result as unknown as Awaited<ReturnType<Agent['stream']>>);\n }\n }\n}\n\nexport function resolveThreadIdFromArgs(args: {\n memory?: { thread?: string | { id: string } };\n threadId?: string;\n overrideId?: string;\n}): (Partial<StorageThreadType> & { id: string }) | undefined {\n let resolved: (Partial<StorageThreadType> & { id: string }) | undefined;\n\n if (args?.memory?.thread) {\n if (typeof args.memory.thread === 'string') {\n resolved = { id: args.memory.thread };\n } else if (typeof args.memory.thread === 'object' && args.memory.thread.id) {\n resolved = args.memory.thread as Partial<StorageThreadType> & { id: string };\n }\n }\n if (!resolved && args?.threadId) {\n resolved = { id: args.threadId };\n }\n\n if (args.overrideId) {\n return { ...(resolved || {}), id: args.overrideId };\n }\n\n return resolved;\n}\n","import type { MessageList } from '../agent/message-list';\nimport { createSignal } from '../agent/signals';\nimport type { AgentSignalInput, CreatedAgentSignal } from '../agent/signals';\nimport type { ProcessorStreamWriter } from './index';\n\nexport function createProcessorSendSignal(args: {\n messageList: MessageList;\n writer?: ProcessorStreamWriter;\n rotateResponseMessageId?: () => string;\n}): (signalInput: AgentSignalInput) => Promise<CreatedAgentSignal> {\n return async signalInput => {\n const signal = createSignal(signalInput);\n args.messageList.markResponseMessageBoundary();\n args.rotateResponseMessageId?.();\n const signalForTranscript = args.messageList.addSignal(signal);\n await args.writer?.custom(signalForTranscript.toDataPart());\n return signalForTranscript;\n };\n}\n","function isPlainObject(value: unknown): value is Record<string, unknown> {\n if (value === null || typeof value !== 'object' || Array.isArray(value)) {\n return false;\n }\n\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\nfunction readString(value: unknown): string | undefined {\n return typeof value === 'string' ? value : undefined;\n}\n\ntype ProcessorToolSummary = {\n id: string;\n name: string;\n description?: string;\n};\n\ntype ActiveToolSummary = {\n id: string;\n name: string;\n};\n\nfunction summarizeProcessorToolEntry(key: string, value: unknown): ProcessorToolSummary {\n if (!isPlainObject(value)) {\n return {\n id: key,\n name: key,\n };\n }\n\n const id = readString(value.id) ?? key;\n const name = readString(value.name) ?? id;\n const description = readString(value.description);\n\n return {\n id,\n name,\n ...(description !== undefined ? { description } : {}),\n };\n}\n\nexport function summarizeProcessorModelForSpan(value: unknown):\n | {\n modelId?: string;\n provider?: string;\n specificationVersion?: string;\n }\n | undefined {\n if (!isPlainObject(value)) {\n return undefined;\n }\n\n const modelId = readString(value.modelId) ?? readString(value.id);\n const provider = readString(value.provider);\n const specificationVersion = readString(value.specificationVersion);\n\n if (modelId === undefined && provider === undefined && specificationVersion === undefined) {\n return undefined;\n }\n\n return {\n ...(modelId !== undefined ? { modelId } : {}),\n ...(provider !== undefined ? { provider } : {}),\n ...(specificationVersion !== undefined ? { specificationVersion } : {}),\n };\n}\n\nexport function summarizeProcessorToolsForSpan(tools: unknown): ProcessorToolSummary[] | undefined {\n if (!isPlainObject(tools)) {\n return undefined;\n }\n\n return Object.entries(tools).map(([key, value]) => summarizeProcessorToolEntry(key, value));\n}\n\nfunction summarizeProcessorToolRegistry(\n tools: unknown,\n): Array<{ registryKey: string; summary: ProcessorToolSummary }> | undefined {\n if (!isPlainObject(tools)) {\n return undefined;\n }\n\n const summaries = summarizeProcessorToolsForSpan(tools);\n if (!summaries) {\n return undefined;\n }\n\n return Object.keys(tools).map((registryKey, index) => ({\n registryKey,\n summary: summaries[index]!,\n }));\n}\n\nfunction resolveToolInRegistry(\n toolRegistry: Array<{ registryKey: string; summary: ProcessorToolSummary }>,\n toolKey: string,\n) {\n return toolRegistry.find(\n candidate =>\n candidate.summary.id === toolKey || candidate.summary.name === toolKey || candidate.registryKey === toolKey,\n );\n}\n\nexport function summarizeActiveToolsForSpan(activeTools: unknown, tools?: unknown): ActiveToolSummary[] | undefined {\n if (!Array.isArray(activeTools)) {\n return undefined;\n }\n\n const toolRegistry = summarizeProcessorToolRegistry(tools) ?? [];\n const summaries = activeTools\n .map(tool => {\n const toolKey = readString(tool);\n if (toolKey === undefined) {\n return undefined;\n }\n\n const match = resolveToolInRegistry(toolRegistry, toolKey);\n return {\n id: match?.summary.id ?? toolKey,\n name: match?.summary.name ?? toolKey,\n };\n })\n .filter((tool): tool is ActiveToolSummary => tool !== undefined);\n\n return summaries;\n}\n\nexport function summarizeToolChoiceForSpan(\n toolChoice: unknown,\n tools?: unknown,\n):\n | {\n type: string;\n tool?: ActiveToolSummary;\n }\n | undefined {\n if (typeof toolChoice === 'string') {\n return { type: toolChoice };\n }\n\n if (!isPlainObject(toolChoice)) {\n return undefined;\n }\n\n const type = readString(toolChoice.type);\n if (type === undefined) {\n return undefined;\n }\n\n if (type !== 'tool') {\n return { type };\n }\n\n const toolKey = readString(toolChoice.toolName) ?? readString(toolChoice.toolId);\n if (toolKey === undefined) {\n return { type };\n }\n\n const toolRegistry = summarizeProcessorToolRegistry(tools) ?? [];\n const match = resolveToolInRegistry(toolRegistry, toolKey);\n const fallbackToolId = readString(toolChoice.toolId) ?? toolKey;\n const fallbackToolName = readString(toolChoice.toolName) ?? toolKey;\n\n return {\n type,\n tool: match\n ? {\n id: match.summary.id,\n name: match.summary.name,\n }\n : {\n id: fallbackToolId,\n name: fallbackToolName,\n },\n };\n}\n\nexport function summarizeProcessorResultForSpan(value: unknown): Record<string, unknown> | undefined {\n if (!isPlainObject(value)) {\n return undefined;\n }\n\n const projected: Record<string, unknown> = {};\n for (const key of [\n 'text',\n 'object',\n 'finishReason',\n 'toolCalls',\n 'toolResults',\n 'warnings',\n 'files',\n 'sources',\n 'reasoning',\n 'reasoningText',\n 'tripwire',\n ] as const) {\n if (value[key] !== undefined) {\n projected[key] = value[key];\n }\n }\n\n if (Array.isArray(value.steps)) {\n projected.stepCount = value.steps.length;\n }\n\n return Object.keys(projected).length > 0 ? projected : undefined;\n}\n","/**\n * Well-known key a stream output processor can set on its `state` to ask the\n * ProcessorRunner to re-drive an additional part through the full output\n * processor chain after the current part has been emitted.\n *\n * `processOutputStream` can only return a single part, but some processors\n * (e.g. `BatchPartsProcessor`) need to emit two parts for one input: a flushed\n * batch of buffered text plus the non-text part that triggered the flush. The\n * processor returns the flushed batch (so it flows through downstream\n * processors normally) and stashes the non-text part under this key. The runner\n * then re-feeds the stashed part through the whole chain so it also receives\n * downstream processing and is emitted in order — instead of being deferred to\n * a \"next\" call that may never happen (which dropped the part when a `stopWhen`\n * condition stopped the agent on that part — issue #17094).\n */\nexport const REPROCESS_PART_KEY = '__mastraReprocessPart';\n","import { randomUUID } from 'node:crypto';\n\nimport type { Processor, ProcessInputStepArgs, ProcessInputStepResult } from './index';\n\nconst CLAUDE_46_PATTERN = /[^0-9]4[.-]6/;\n\n/**\n * Checks whether a model config could be Claude 4.6.\n *\n * Handles raw model configs (strings like `'anthropic/claude-opus-4-6'`),\n * language model objects (with `provider` and `modelId`), dynamic functions\n * (returns `true` as a safe default), and model fallback arrays.\n */\nexport function isMaybeClaude46(\n model:\n | string\n | { provider?: string; modelId?: string }\n | ((...args: any[]) => any)\n | { model: any; enabled?: boolean }[]\n | unknown,\n): boolean {\n if (typeof model === 'function') return true;\n\n if (Array.isArray(model)) {\n return model.some(m => isMaybeClaude46(m.model ?? m));\n }\n\n if (typeof model === 'string') {\n return model.startsWith('anthropic') && CLAUDE_46_PATTERN.test(model);\n }\n\n if (model && typeof model === 'object' && 'provider' in model && 'modelId' in model) {\n const { provider, modelId } = model as { provider: string; modelId: string };\n return provider.startsWith('anthropic') && CLAUDE_46_PATTERN.test(modelId);\n }\n\n return true;\n}\n\n/**\n * Guards against trailing assistant messages when using native structured output\n * with Anthropic Claude 4.6.\n *\n * Claude 4.6 rejects requests where the last message is an assistant message when\n * using output format (structured output), interpreting it as pre-filling the response.\n * This processor appends a user message to prevent that error.\n *\n * This processor should only be added when the agent uses a Claude 4.6 model.\n * Use {@link isMaybeClaude46} to check before adding.\n *\n * @see https://github.com/mastra-ai/mastra/issues/12800\n */\nexport class TrailingAssistantGuard implements Processor<'trailing-assistant-guard'> {\n readonly id = 'trailing-assistant-guard' as const;\n readonly name = 'Trailing Assistant Guard';\n\n processInputStep({ messages, structuredOutput }: ProcessInputStepArgs): ProcessInputStepResult | undefined {\n const willUseResponseFormat =\n structuredOutput?.schema && !structuredOutput?.model && !structuredOutput?.jsonPromptInjection;\n\n if (!willUseResponseFormat) return;\n\n const lastMessage = messages[messages.length - 1];\n if (!lastMessage || lastMessage.role !== 'assistant') return;\n\n return {\n messages: [\n ...messages,\n {\n id: randomUUID(),\n role: 'user' as const,\n content: {\n format: 2 as const,\n parts: [{ type: 'text' as const, text: 'Generate the structured response.' }],\n },\n createdAt: new Date(),\n },\n ],\n };\n }\n}\n","import type { LanguageModelV2Prompt, LanguageModelV2CallWarning } from '@ai-sdk/provider-v5';\nimport type { StepResult } from '@internal/ai-sdk-v5';\nimport type { Agent } from '../agent';\nimport type { MastraDBMessage, MessageInput } from '../agent/message-list';\nimport { MessageList, messagesAreEqual } from '../agent/message-list';\nimport type { AgentStateSignalInput } from '../agent/signals';\nimport { applyStateSignal, getStateSignalsMetadata, resolveStateSignalHistory } from '../agent/state-signals';\nimport { TripWire } from '../agent/trip-wire';\nimport type { TripWireOptions } from '../agent/trip-wire';\nimport { isSupportedLanguageModel, supportedLanguageModelSpecifications } from '../agent/utils';\nimport { MastraError } from '../error';\nimport { resolveModelConfig } from '../llm';\nimport type { IMastraLogger } from '../logger';\nimport type { MastraMemory } from '../memory/memory';\nimport { parseMemoryRequestContext } from '../memory/types';\nimport { EntityType, SpanType, createObservabilityContext, resolveObservabilityContext } from '../observability';\nimport type { ObservabilityContext, Span } from '../observability';\nimport type { TracingContext } from '../observability/types';\nimport type { RequestContext } from '../request-context';\nimport type { ChunkType } from '../stream';\nimport type { MastraModelOutput } from '../stream/base/output';\nimport type { LanguageModelUsage, ProviderMetadata } from '../stream/types';\nimport { isProcessorWorkflow } from './is-processor-workflow';\nimport { createProcessorSendSignal } from './send-signal';\nimport {\n summarizeActiveToolsForSpan,\n summarizeProcessorModelForSpan,\n summarizeProcessorResultForSpan,\n summarizeProcessorToolsForSpan,\n summarizeToolChoiceForSpan,\n} from './span-payload';\nimport type { ProcessorStepOutput } from './step-schema';\nimport { REPROCESS_PART_KEY } from './stream-reprocess';\nimport { isMaybeClaude46, TrailingAssistantGuard } from './trailing-assistant-guard';\nimport type {\n CachedLLMStepChunk,\n CachedLLMStepResponse,\n ComputeStateSignalResult,\n ErrorProcessorOrWorkflow,\n OutputResult,\n ProcessInputStepResult,\n Processor,\n ProcessorMessageResult,\n ProcessorStreamWriter,\n ProcessorViolation,\n ProcessorWorkflow,\n RunProcessInputStepArgs,\n RunProcessInputStepResult,\n ToolCallInfo,\n} from './index';\n\n/**\n * Safely invoke a processor's onViolation callback when a TripWire is caught.\n * Errors from the callback are silently caught.\n */\nasync function invokeOnViolation(processor: Processor, error: TripWire): Promise<void> {\n if (!processor.onViolation) return;\n try {\n const violation: ProcessorViolation = {\n processorId: error.processorId ?? processor.id,\n message: error.message,\n detail: error.options?.metadata,\n };\n await processor.onViolation(violation);\n } catch {\n // onViolation errors are silently caught\n }\n}\n\n/**\n * Implementation of processor state management\n */\n/**\n * Tracks state for stream processing across chunks.\n * Used by both legacy processors and workflow processors.\n */\nexport class ProcessorState<OUTPUT = undefined> {\n private inputAccumulatedText = '';\n private outputAccumulatedText = '';\n private outputChunkCount = 0;\n public customState: Record<string, unknown> = {};\n public streamParts: ChunkType<OUTPUT>[] = [];\n public span?: Span<SpanType.PROCESSOR_RUN>;\n\n constructor(\n options?: {\n processorName?: string;\n processorIndex?: number;\n createSpan?: boolean;\n } & Partial<ObservabilityContext>,\n ) {\n // Only create span if explicitly requested (legacy processors)\n // Workflow processors handle span creation in workflow.ts\n if (!options?.createSpan || !options.processorName) {\n return;\n }\n\n const currentSpan = options.tracingContext?.currentSpan;\n const parentSpan = currentSpan?.findParent(SpanType.AGENT_RUN) || currentSpan?.parent || currentSpan;\n this.span = parentSpan?.createChildSpan({\n type: SpanType.PROCESSOR_RUN,\n name: `output stream processor: ${options.processorName}`,\n entityType: EntityType.OUTPUT_PROCESSOR,\n entityName: options.processorName,\n attributes: {\n processorExecutor: 'legacy',\n processorIndex: options.processorIndex ?? 0,\n },\n input: {\n totalChunks: 0,\n },\n });\n }\n\n /** Track incoming chunk (before processor transformation) */\n addInputPart(part: ChunkType<OUTPUT>): void {\n // Extract text from text-delta chunks for accumulated text\n if (part.type === 'text-delta') {\n this.inputAccumulatedText += part.payload.text;\n }\n this.streamParts.push(part);\n\n if (this.span) {\n this.span.input = {\n totalChunks: this.streamParts.length,\n accumulatedText: this.inputAccumulatedText,\n };\n }\n }\n\n /** Track outgoing chunk (after processor transformation) */\n addOutputPart(part: ChunkType<OUTPUT> | null | undefined): void {\n if (!part) return;\n this.outputChunkCount++;\n // Extract text from text-delta chunks for accumulated text\n if (part.type === 'text-delta') {\n this.outputAccumulatedText += part.payload.text;\n }\n }\n\n /** Get final output for span */\n getFinalOutput(): { totalChunks: number; accumulatedText: string } {\n return {\n totalChunks: this.outputChunkCount,\n accumulatedText: this.outputAccumulatedText,\n };\n }\n}\n\n/**\n * Union type for processor or workflow that can be used as a processor\n */\ntype ProcessorOrWorkflow = Processor | ProcessorWorkflow;\n\nfunction areProcessorMessageArraysEqual(before: unknown[] | undefined, after: unknown[] | undefined): boolean {\n if (before === after) {\n return true;\n }\n\n if (!before || !after) {\n return before === after;\n }\n\n return (\n before.length === after.length &&\n before.every((message, index) => messagesAreEqual(message as MessageInput, after[index] as MessageInput))\n );\n}\n\nfunction buildProcessInputStepSpanInput(args: {\n messages: MastraDBMessage[];\n systemMessages: unknown[];\n stepNumber: number;\n messageId?: string;\n retryCount: number;\n model: unknown;\n tools?: unknown;\n toolChoice?: unknown;\n activeTools?: unknown;\n}) {\n const summarizedModel = summarizeProcessorModelForSpan(args.model);\n const summarizedTools = summarizeProcessorToolsForSpan(args.tools);\n const summarizedToolChoice = summarizeToolChoiceForSpan(args.toolChoice, args.tools);\n const summarizedActiveTools = summarizeActiveToolsForSpan(args.activeTools, args.tools);\n\n return {\n messages: args.messages,\n systemMessages: args.systemMessages,\n stepNumber: args.stepNumber,\n ...(args.messageId ? { messageId: args.messageId } : {}),\n retryCount: args.retryCount,\n ...(summarizedModel ? { model: summarizedModel } : {}),\n ...(summarizedTools ? { tools: summarizedTools } : {}),\n ...(summarizedToolChoice ? { toolChoice: summarizedToolChoice } : {}),\n ...(summarizedActiveTools ? { activeTools: summarizedActiveTools } : {}),\n };\n}\n\nfunction buildProcessInputStepSpanOutput(args: {\n result: RunProcessInputStepResult;\n beforeStepInput: Pick<RunProcessInputStepResult, 'messageId' | 'model' | 'tools' | 'toolChoice' | 'activeTools'>;\n afterStepInput: RunProcessInputStepResult;\n beforeMessages: MastraDBMessage[];\n beforeSystemMessages: unknown[];\n messages: MastraDBMessage[];\n systemMessages: unknown[];\n}) {\n const output: Record<string, unknown> = {};\n\n if (!areProcessorMessageArraysEqual(args.beforeMessages, args.messages)) {\n output.messages = args.messages;\n }\n\n if (!areProcessorMessageArraysEqual(args.beforeSystemMessages, args.systemMessages)) {\n output.systemMessages = args.systemMessages;\n }\n\n if (args.afterStepInput.messageId !== args.beforeStepInput.messageId) {\n output.messageId = args.afterStepInput.messageId;\n }\n\n if (args.result.model !== undefined || args.afterStepInput.model !== args.beforeStepInput.model) {\n const model = summarizeProcessorModelForSpan(args.afterStepInput.model);\n if (model) {\n output.model = model;\n }\n }\n\n if (args.result.tools !== undefined || args.afterStepInput.tools !== args.beforeStepInput.tools) {\n const tools = summarizeProcessorToolsForSpan(args.afterStepInput.tools);\n if (tools) {\n output.tools = tools;\n }\n }\n\n if (\n args.result.toolChoice !== undefined ||\n args.afterStepInput.toolChoice !== args.beforeStepInput.toolChoice ||\n args.afterStepInput.tools !== args.beforeStepInput.tools\n ) {\n const toolChoice = summarizeToolChoiceForSpan(args.afterStepInput.toolChoice, args.afterStepInput.tools);\n if (toolChoice) {\n output.toolChoice = toolChoice;\n }\n }\n\n if (\n args.result.activeTools !== undefined ||\n args.afterStepInput.activeTools !== args.beforeStepInput.activeTools ||\n args.afterStepInput.tools !== args.beforeStepInput.tools\n ) {\n const activeTools = summarizeActiveToolsForSpan(args.afterStepInput.activeTools, args.afterStepInput.tools);\n if (activeTools) {\n output.activeTools = activeTools;\n }\n }\n\n if (args.result.retryCount !== undefined) {\n output.retryCount = args.result.retryCount;\n }\n\n return output;\n}\n\nexport class ProcessorRunner {\n public readonly inputProcessors: ProcessorOrWorkflow[];\n public readonly outputProcessors: ProcessorOrWorkflow[];\n public readonly errorProcessors: ErrorProcessorOrWorkflow[];\n private readonly logger: IMastraLogger;\n private readonly agentName: string;\n private readonly agent?: Agent<any, any, any, any>;\n /**\n * Shared processor state that persists across loop iterations.\n * Used by all processor methods (input and output) to share state.\n * Keyed by processor ID.\n */\n private readonly processorStates: Map<string, ProcessorState>;\n\n constructor({\n inputProcessors,\n outputProcessors,\n errorProcessors,\n logger,\n agentName,\n agent,\n processorStates,\n }: {\n inputProcessors?: ProcessorOrWorkflow[];\n outputProcessors?: ProcessorOrWorkflow[];\n errorProcessors?: ErrorProcessorOrWorkflow[];\n logger: IMastraLogger;\n agentName: string;\n agent?: Agent<any, any, any, any>;\n processorStates?: Map<string, ProcessorState>;\n }) {\n this.inputProcessors = inputProcessors ?? [];\n this.outputProcessors = outputProcessors ?? [];\n this.errorProcessors = errorProcessors ?? [];\n this.logger = logger;\n this.agentName = agentName;\n this.agent = agent;\n this.processorStates = processorStates ?? new Map();\n }\n\n /**\n * Get or create ProcessorState for the given processor ID.\n * This state persists across loop iterations and is shared between\n * all processor methods (input and output).\n */\n private getProcessorState(processorId: string): ProcessorState {\n let state = this.processorStates.get(processorId);\n if (!state) {\n state = new ProcessorState();\n this.processorStates.set(processorId, state);\n }\n return state;\n }\n\n private async runComputeStateSignal({\n processor,\n messageList,\n stepNumber,\n steps,\n requestContext,\n writer,\n abort,\n processorState,\n memory,\n resourceId,\n threadId,\n abortSignal,\n retryCount,\n rotateResponseMessageId,\n }: {\n processor: Processor;\n messageList: MessageList;\n stepNumber: number;\n steps: Array<StepResult<any>>;\n requestContext?: RequestContext;\n writer?: ProcessorStreamWriter;\n abort: (reason?: string, options?: TripWireOptions) => never;\n processorState: ProcessorState;\n memory?: MastraMemory;\n resourceId?: string;\n threadId?: string;\n abortSignal?: AbortSignal;\n retryCount: number;\n rotateResponseMessageId?: () => string;\n }): Promise<void> {\n const computeStateSignal = processor.computeStateSignal?.bind(processor);\n if (!computeStateSignal) return;\n\n const memoryContext = parseMemoryRequestContext(requestContext);\n const resolvedMemory = memory;\n const resolvedThreadId = threadId ?? memoryContext?.thread?.id;\n const resolvedResourceId = resourceId ?? memoryContext?.resourceId;\n\n if (!resolvedMemory) {\n throw new Error(\n `[Processor:${processor.id}] computeStateSignal requires Mastra memory with an active resourceId and threadId`,\n );\n }\n\n // Memory is configured but this invocation has no thread/resource identity\n // (e.g. an ephemeral workflow agent step). State signals are thread-scoped,\n // so skip rather than throw.\n if (!resolvedThreadId || !resolvedResourceId) {\n this.logger.debug(\n `[Processor:${processor.id}] computeStateSignal skipped — no threadId/resourceId resolved for this invocation`,\n );\n return;\n }\n\n const loadedThread = (await resolvedMemory.getThreadById({ threadId: resolvedThreadId })) ?? memoryContext?.thread;\n if (!loadedThread) {\n throw new Error(`[Processor:${processor.id}] computeStateSignal could not load thread ${resolvedThreadId}`);\n }\n let thread = {\n ...loadedThread,\n id: resolvedThreadId,\n resourceId: loadedThread.resourceId ?? resolvedResourceId,\n createdAt: loadedThread.createdAt ?? new Date(),\n updatedAt: loadedThread.updatedAt ?? new Date(),\n metadata: loadedThread.metadata,\n };\n\n const stateId = processor.stateId ?? processor.id;\n const beforeAddStateSignal = rotateResponseMessageId\n ? () => {\n messageList.markResponseMessageBoundary();\n rotateResponseMessageId();\n }\n : undefined;\n const trackingById = getStateSignalsMetadata(thread.metadata);\n const tracking = trackingById[stateId];\n const { activeStateSignals, contextWindow, lastSnapshot, deltasSinceSnapshot } = await resolveStateSignalHistory({\n messageList,\n memory: resolvedMemory,\n threadId: resolvedThreadId,\n stateId,\n tracking,\n });\n const result = (await computeStateSignal({\n messages: messageList.get.all.db(),\n messageList,\n stepNumber,\n steps,\n state: processorState.customState,\n requestContext,\n writer,\n abortSignal,\n abort,\n retryCount,\n resourceId: resolvedResourceId,\n threadId: resolvedThreadId,\n activeStateSignals,\n contextWindow,\n lastSnapshot,\n deltasSinceSnapshot,\n tracking,\n sendStateSignal: async stateSignal => {\n const sendResult = await applyStateSignal({\n input: stateSignal,\n memory: resolvedMemory,\n thread,\n resourceId: resolvedResourceId,\n threadId: resolvedThreadId,\n memoryConfig: memoryContext?.memoryConfig,\n messageList,\n defaultId: stateId,\n beforeAddSignal: beforeAddStateSignal,\n writeSignal: signal => writer?.custom(signal.toDataPart()),\n });\n if (!sendResult.skipped) {\n const updated = await resolvedMemory.getThreadById({ threadId: resolvedThreadId });\n if (updated) thread = { ...thread, metadata: updated.metadata };\n }\n return sendResult.skipped ? sendResult : sendResult.signal;\n },\n })) as ComputeStateSignalResult;\n\n if (!result) return;\n\n await applyStateSignal({\n input: result,\n memory: resolvedMemory,\n thread,\n resourceId: resolvedResourceId,\n threadId: resolvedThreadId,\n memoryConfig: memoryContext?.memoryConfig,\n messageList,\n defaultId: stateId,\n beforeAddSignal: beforeAddStateSignal,\n writeSignal: signal => writer?.custom(signal.toDataPart()),\n });\n }\n\n private async runWorkflowComputeStateSignals({\n workflow,\n messageList,\n stepNumber,\n steps,\n requestContext,\n writer,\n memory,\n resourceId,\n threadId,\n abortSignal,\n retryCount,\n rotateResponseMessageId,\n }: {\n workflow: ProcessorWorkflow;\n messageList: MessageList;\n stepNumber: number;\n steps: Array<StepResult<any>>;\n requestContext?: RequestContext;\n writer?: ProcessorStreamWriter;\n memory?: MastraMemory;\n resourceId?: string;\n threadId?: string;\n abortSignal?: AbortSignal;\n retryCount: number;\n rotateResponseMessageId?: () => string;\n }): Promise<void> {\n for (const processor of workflow.__stateSignalProcessors ?? []) {\n const abort = <TMetadata = unknown>(reason?: string, options?: TripWireOptions<TMetadata>): never => {\n throw new TripWire(reason || `Tripwire triggered by ${processor.id}`, options, processor.id);\n };\n\n await this.runComputeStateSignal({\n processor,\n messageList,\n stepNumber,\n steps,\n requestContext,\n writer,\n abort,\n processorState: this.getProcessorState(processor.id),\n memory,\n resourceId,\n threadId,\n abortSignal,\n retryCount,\n rotateResponseMessageId,\n });\n }\n }\n\n /**\n * Execute a workflow as a processor and handle the result.\n * Returns the processed messages and any tripwire information.\n */\n private async executeWorkflowAsProcessor(\n workflow: ProcessorWorkflow,\n input: ProcessorStepOutput,\n observabilityContext?: ObservabilityContext,\n requestContext?: RequestContext,\n writer?: ProcessorStreamWriter,\n abortSignal?: AbortSignal,\n ): Promise<ProcessorStepOutput> {\n // Create a run and start the workflow\n const run = await workflow.createRun();\n const result = await run.start({\n // Cast to allow processorStates/abortSignal - passed through to workflow processor steps\n // but not part of the official ProcessorStepOutput schema\n inputData: {\n ...input,\n // Pass the processorStates map so workflow processor steps can access their state\n processorStates: this.processorStates,\n // Pass abortSignal so processors can cancel in-flight work\n abortSignal,\n agent: this.agent,\n } as ProcessorStepOutput,\n ...observabilityContext,\n requestContext,\n outputWriter: writer ? chunk => writer.custom(chunk) : undefined,\n });\n\n // Check for tripwire status - this means a processor in the workflow called abort()\n if (result.status === 'tripwire') {\n const tripwireData = (\n result as { tripwire?: { reason?: string; retry?: boolean; metadata?: unknown; processorId?: string } }\n ).tripwire;\n // Re-throw as TripWire so the agent handles it properly\n throw new TripWire(\n tripwireData?.reason || `Tripwire triggered in workflow ${workflow.id}`,\n {\n retry: tripwireData?.retry,\n metadata: tripwireData?.metadata,\n },\n tripwireData?.processorId || workflow.id,\n );\n }\n\n // Check for execution failure\n if (result.status !== 'success') {\n // Collect error details from the workflow result and failed steps\n const details: string[] = [];\n if (result.status === 'failed') {\n if (result.error) {\n details.push(result.error.message || JSON.stringify(result.error));\n }\n for (const [stepId, step] of Object.entries(result.steps)) {\n if (step.status === 'failed' && step.error?.message) {\n details.push(`step ${stepId}: ${step.error.message}`);\n }\n }\n }\n const detailStr = details.length > 0 ? ` — ${details.join('; ')}` : '';\n throw new MastraError({\n category: 'USER',\n domain: 'AGENT',\n id: 'PROCESSOR_WORKFLOW_FAILED',\n text: `Processor workflow ${workflow.id} failed with status: ${result.status}${detailStr}`,\n });\n }\n\n // Extract and validate the output from the workflow result\n const output = result.result;\n\n if (!output || typeof output !== 'object') {\n // No output means no changes - return input unchanged\n return input;\n }\n\n // Validate it has the expected ProcessorStepOutput shape\n if (!('phase' in output) || !('messages' in output || 'part' in output || 'messageList' in output)) {\n throw new MastraError({\n category: 'USER',\n domain: 'AGENT',\n id: 'PROCESSOR_WORKFLOW_INVALID_OUTPUT',\n text: `Processor workflow ${workflow.id} returned invalid output format. Expected ProcessorStepOutput.`,\n });\n }\n\n return output as ProcessorStepOutput;\n }\n\n async runOutputProcessors(\n messageList: MessageList,\n observabilityContext?: ObservabilityContext,\n requestContext?: RequestContext,\n retryCount: number = 0,\n writer?: ProcessorStreamWriter,\n result?: OutputResult,\n ): Promise<MessageList> {\n for (const [index, processorOrWorkflow] of this.outputProcessors.entries()) {\n const allNewMessages = messageList.get.response.db();\n let processableMessages: MastraDBMessage[] = [...allNewMessages];\n const idsBeforeProcessing = processableMessages.map((m: MastraDBMessage) => m.id);\n const check = messageList.makeMessageSourceChecker();\n\n // Handle workflow as processor\n if (isProcessorWorkflow(processorOrWorkflow)) {\n await this.executeWorkflowAsProcessor(\n processorOrWorkflow,\n {\n phase: 'outputResult',\n messages: processableMessages,\n messageList,\n retryCount,\n result,\n },\n observabilityContext,\n requestContext,\n writer,\n );\n continue;\n }\n\n // Handle regular processor\n const processor = processorOrWorkflow;\n const abort = <TMetadata = unknown>(reason?: string, options?: TripWireOptions<TMetadata>): never => {\n throw new TripWire(reason || `Tripwire triggered by ${processor.id}`, options, processor.id);\n };\n\n // Use the processOutputResult method if available\n const processMethod = processor.processOutputResult?.bind(processor);\n\n if (!processMethod) {\n // Skip processors that don't implement processOutputResult\n continue;\n }\n\n const outputMessagesBefore = processableMessages;\n const outputSystemMessagesBefore = messageList.getAllSystemMessages();\n const defaultResult: OutputResult = {\n text: '',\n usage: { inputTokens: 0, outputTokens: 0, totalTokens: 0 },\n finishReason: 'unknown',\n steps: [],\n };\n const summarizedResult = result ? summarizeProcessorResultForSpan(result) : undefined;\n const currentSpan = observabilityContext?.tracingContext?.currentSpan;\n const parentSpan = currentSpan?.findParent(SpanType.AGENT_RUN) || currentSpan?.parent || currentSpan;\n const processorSpan = parentSpan?.createChildSpan({\n type: SpanType.PROCESSOR_RUN,\n name: `output processor: ${processor.id}`,\n entityType: EntityType.OUTPUT_PROCESSOR,\n entityId: processor.id,\n entityName: processor.name,\n attributes: {\n processorExecutor: 'legacy',\n processorIndex: index,\n },\n input: {\n messages: processableMessages,\n ...(summarizedResult ? { result: summarizedResult } : {}),\n retryCount,\n },\n });\n\n // Start recording MessageList mutations for this processor\n messageList.startRecording();\n\n try {\n // Get per-processor state that persists across all method calls within this request\n const processorState = this.getProcessorState(processor.id);\n\n const processResult = await processMethod({\n messages: processableMessages,\n messageList,\n state: processorState.customState,\n result: result ?? defaultResult,\n abort,\n agent: this.agent,\n ...createObservabilityContext({ currentSpan: processorSpan }),\n requestContext,\n retryCount,\n writer,\n sendSignal: createProcessorSendSignal({ messageList, writer }),\n });\n\n // Stop recording and get mutations for this processor\n const mutations = messageList.stopRecording();\n\n // Handle the new return type - MessageList or MastraDBMessage[]\n if (processResult instanceof MessageList) {\n if (processResult !== messageList) {\n throw new MastraError({\n category: 'USER',\n domain: 'AGENT',\n id: 'PROCESSOR_RETURNED_EXTERNAL_MESSAGE_LIST',\n 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.`,\n });\n }\n if (mutations.length > 0) {\n processableMessages = processResult.get.response.db();\n }\n } else {\n if (processResult) {\n const deletedIds = idsBeforeProcessing.filter(\n (i: string) => !processResult.some((m: MastraDBMessage) => m.id === i),\n );\n if (deletedIds.length) {\n messageList.removeByIds(deletedIds);\n }\n processableMessages = processResult || [];\n for (const message of processResult) {\n messageList.removeByIds([message.id]);\n messageList.add(message, check.getSource(message) || 'response', { merge: false });\n }\n }\n }\n\n processorSpan?.end({\n output: {\n ...(!areProcessorMessageArraysEqual(outputMessagesBefore, processableMessages)\n ? { messages: processableMessages }\n : {}),\n ...(!areProcessorMessageArraysEqual(outputSystemMessagesBefore, messageList.getAllSystemMessages())\n ? { systemMessages: messageList.getAllSystemMessages() }\n : {}),\n },\n attributes: mutations.length > 0 ? { messageListMutations: mutations } : undefined,\n });\n } catch (error) {\n // Stop recording on error\n messageList.stopRecording();\n\n if (error instanceof TripWire) {\n processorSpan?.error({\n error,\n endSpan: true,\n attributes: {\n tripwireAbort: {\n reason: error.message,\n retry: error.options?.retry,\n metadata: error.options?.metadata,\n },\n },\n });\n await invokeOnViolation(processor, error);\n throw error;\n }\n processorSpan?.error({ error: error as Error, endSpan: true });\n throw error;\n }\n }\n\n retur