@mastra/core
Version:
1 lines • 250 kB
Source Map (JSON)
{"version":3,"file":"constants-CHm1eNBE.cjs","names":["z","SpanType","dbTimestamps","paginationInfoSchema","getZodTypeName","getZodInnerType","zodV4","zodV3","z","SpanType","spanContextFields","metadataField","tagsField","traceIdField","spanIdField","dbTimestamps","dateRangeSchema","sortDirectionSchema","listModeSchema","paginationArgsSchema","deltaCursorSchema","deltaLimitSchema","refineObservabilityListMode","normalizeObservabilityListArgs","paginationInfoSchema","deltaInfoSchema"],"sources":["../src/evals/types.ts","../src/storage/types.ts","../src/storage/domains/observability/tracing.ts","../src/storage/constants.ts"],"sourcesContent":["import type { CoreMessage, CoreSystemMessage } from '@internal/ai-sdk-v4';\nimport { z } from 'zod/v4';\nimport type { MastraDBMessage } from '../agent';\nimport { SpanType } from '../observability';\nimport type { ObservabilityContext } from '../observability';\nimport type { SpanRecord } from '../storage/domains/observability/tracing';\nimport { dbTimestamps, paginationInfoSchema } from '../storage/domains/shared';\nimport type { StepResult } from '../workflows/types';\n\n// ============================================================================\n// Sampling Config\n// ============================================================================\n\nexport type ScoringSamplingConfig = { type: 'none' } | { type: 'ratio'; rate: number };\n\n// ============================================================================\n// Scoring Source & Entity Type\n// ============================================================================\n\nexport const scoringSourceSchema = z.enum(['LIVE', 'TEST']);\n\nexport type ScoringSource = z.infer<typeof scoringSourceSchema>;\n\nexport const scoringEntityTypeSchema = z.enum([\n 'AGENT',\n 'WORKFLOW',\n 'TRAJECTORY',\n 'STEP',\n ...Object.values(SpanType),\n] as [string, string, ...string[]]);\n\nexport type ScoringEntityType = z.infer<typeof scoringEntityTypeSchema>;\n\n// ============================================================================\n// Scoring Prompts\n// ============================================================================\n\nexport const scoringPromptsSchema = z.object({\n description: z.string(),\n prompt: z.string(),\n});\n\nexport type ScoringPrompts = z.infer<typeof scoringPromptsSchema>;\n\n// ============================================================================\n// Shared Record Schemas\n// ============================================================================\n\n/** Reusable schema for required record fields (e.g., scorer, entity) */\nconst recordSchema = z.record(z.string(), z.unknown());\n\n/** Reusable schema for optional record fields (e.g., metadata, additionalContext) */\nconst optionalRecordSchema = recordSchema.optional();\n\n// ============================================================================\n// Base Scoring Input (used for scorer functions)\n// ============================================================================\n\nexport const scoringInputSchema = z.object({\n runId: z.string().optional(),\n input: z.unknown().optional(),\n output: z.unknown(),\n additionalContext: optionalRecordSchema,\n requestContext: optionalRecordSchema,\n // Note: observabilityContext is not serializable, so we don't include it in the schema\n // It's added at runtime when needed\n});\n\nexport type ScoringInput = z.infer<typeof scoringInputSchema> & Partial<ObservabilityContext>;\n\n// ============================================================================\n// Scoring Hook Input\n// ============================================================================\n\nexport const scoringHookInputSchema = z.object({\n runId: z.string().optional(),\n scorer: recordSchema,\n input: z.unknown(),\n output: z.unknown(),\n metadata: optionalRecordSchema,\n additionalContext: optionalRecordSchema,\n source: scoringSourceSchema,\n entity: recordSchema,\n entityType: scoringEntityTypeSchema,\n requestContext: optionalRecordSchema,\n structuredOutput: z.boolean().optional(),\n traceId: z.string().optional(),\n spanId: z.string().optional(),\n resourceId: z.string().optional(),\n threadId: z.string().optional(),\n // Tenancy: organizationId arrives via ObservabilityContext; projectId is scores-specific.\n projectId: z.string().optional(),\n // Note: observabilityContext is not serializable, so we don't include it in the schema\n});\n\nexport type ScoringHookInput = z.infer<typeof scoringHookInputSchema> & Partial<ObservabilityContext>;\n\n// ============================================================================\n// Extract Step Result\n// ============================================================================\n\nexport const scoringExtractStepResultSchema = optionalRecordSchema;\n\nexport type ScoringExtractStepResult = z.infer<typeof scoringExtractStepResultSchema>;\n\n// ============================================================================\n// Analyze Step Result (Score Result)\n// ============================================================================\n\nexport const scoringValueSchema = z.number();\n\nexport const scoreResultSchema = z.object({\n result: optionalRecordSchema,\n score: scoringValueSchema,\n prompt: z.string().optional(),\n});\n\nexport type ScoringAnalyzeStepResult = z.infer<typeof scoreResultSchema>;\n\n// ============================================================================\n// Composite Input Types (for scorer step functions)\n// ============================================================================\n\nexport const scoringInputWithExtractStepResultSchema = scoringInputSchema.extend({\n runId: z.string(), // Required in this context\n extractStepResult: optionalRecordSchema,\n extractPrompt: z.string().optional(),\n});\n\nexport type ScoringInputWithExtractStepResult<TExtract = any> = Omit<\n z.infer<typeof scoringInputWithExtractStepResultSchema>,\n 'extractStepResult'\n> & {\n extractStepResult?: TExtract;\n} & Partial<ObservabilityContext>;\n\nexport const scoringInputWithExtractStepResultAndAnalyzeStepResultSchema =\n scoringInputWithExtractStepResultSchema.extend({\n score: z.number(),\n analyzeStepResult: optionalRecordSchema,\n analyzePrompt: z.string().optional(),\n });\n\nexport type ScoringInputWithExtractStepResultAndAnalyzeStepResult<TExtract = any, TScore = any> = Omit<\n z.infer<typeof scoringInputWithExtractStepResultAndAnalyzeStepResultSchema>,\n 'extractStepResult' | 'analyzeStepResult'\n> & {\n extractStepResult?: TExtract;\n analyzeStepResult?: TScore;\n} & Partial<ObservabilityContext>;\n\nexport const scoringInputWithExtractStepResultAndScoreAndReasonSchema =\n scoringInputWithExtractStepResultAndAnalyzeStepResultSchema.extend({\n reason: z.string().optional(),\n reasonPrompt: z.string().optional(),\n });\n\nexport type ScoringInputWithExtractStepResultAndScoreAndReason = z.infer<\n typeof scoringInputWithExtractStepResultAndScoreAndReasonSchema\n> &\n Partial<ObservabilityContext>;\n\n// ============================================================================\n// Score Row Data (stored in DB)\n// ============================================================================\n\nexport const scoreRowDataSchema = z.object({\n id: z.string(),\n scorerId: z.string(),\n entityId: z.string(),\n\n // From ScoringInputWithExtractStepResultAndScoreAndReason\n runId: z.string(),\n input: z.unknown().optional(),\n output: z.unknown(),\n additionalContext: optionalRecordSchema,\n requestContext: optionalRecordSchema,\n extractStepResult: optionalRecordSchema,\n extractPrompt: z.string().optional(),\n score: z.number(),\n analyzeStepResult: optionalRecordSchema,\n analyzePrompt: z.string().optional(),\n reason: z.string().optional(),\n reasonPrompt: z.string().optional(),\n\n // From ScoringHookInput\n scorer: recordSchema,\n metadata: optionalRecordSchema,\n source: scoringSourceSchema,\n entity: recordSchema,\n entityType: scoringEntityTypeSchema.optional(),\n structuredOutput: z.boolean().optional(),\n traceId: z.string().optional(),\n spanId: z.string().optional(),\n resourceId: z.string().optional(),\n threadId: z.string().optional(),\n // Multi-tenant scope. `resourceId` is overloaded (memory end-user), so tenancy\n // uses dedicated fields: organizationId (account) + projectId (project scope).\n organizationId: z.string().nullish(),\n projectId: z.string().nullish(),\n // Batch handle shared across all per-trace scores produced by one batch scoring\n // call. `runId` stays per-execution; `batchId` groups the batch.\n batchId: z.string().nullish(),\n // Dataset provenance: links a baseline score back to the curated dataset item it\n // scored, so scores can join to ground truth without re-running the agent.\n datasetId: z.string().nullish(),\n datasetItemId: z.string().nullish(),\n\n // Additional ScoreRowData fields\n preprocessStepResult: optionalRecordSchema,\n preprocessPrompt: z.string().optional(),\n generateScorePrompt: z.string().optional(),\n generateReasonPrompt: z.string().optional(),\n\n // Timestamps\n ...dbTimestamps,\n});\n\nexport type ScoreRowData = z.infer<typeof scoreRowDataSchema>;\n\n// ============================================================================\n// Save Score Payload (for creating new scores)\n// ============================================================================\n\nexport const saveScorePayloadSchema = scoreRowDataSchema.omit({\n id: true,\n createdAt: true,\n updatedAt: true,\n});\n\nexport type SaveScorePayload = z.infer<typeof saveScorePayloadSchema>;\n\n// ============================================================================\n// List Scores Response\n// ============================================================================\n\nexport const listScoresResponseSchema = z.object({\n pagination: paginationInfoSchema,\n scores: z.array(scoreRowDataSchema),\n});\n\nexport type ListScoresResponse = z.infer<typeof listScoresResponseSchema>;\n\nexport type ExtractionStepFn = (input: ScoringInput) => Promise<Record<string, any>>;\n\nexport type AnalyzeStepFn = (input: ScoringInputWithExtractStepResult) => Promise<ScoringAnalyzeStepResult>;\n\nexport type ReasonStepFn = (\n input: ScoringInputWithExtractStepResultAndAnalyzeStepResult,\n) => Promise<{ reason: string; reasonPrompt?: string } | null>;\n\nexport type ScorerOptions = {\n name: string;\n description: string;\n extract?: ExtractionStepFn;\n analyze: AnalyzeStepFn;\n reason?: ReasonStepFn;\n metadata?: Record<string, any>;\n isLLMScorer?: boolean;\n};\n\nexport type ScorerRunInputForAgent = {\n inputMessages: MastraDBMessage[];\n rememberedMessages: MastraDBMessage[];\n systemMessages: CoreMessage[];\n taggedSystemMessages: Record<string, CoreSystemMessage[]>;\n};\n\nexport type ScorerRunOutputForAgent = MastraDBMessage[];\n\n// ============================================================================\n// Trajectory Types — Discriminated Union\n// ============================================================================\n\n/**\n * Base properties shared by all trajectory step types.\n */\nexport type TrajectoryStepBase = {\n /** Name of the tool called, model used, or step executed */\n name: string;\n /** Duration of this step in milliseconds */\n durationMs?: number;\n /** Additional metadata about this step */\n metadata?: Record<string, unknown>;\n /** Nested child steps (e.g., tool calls inside a workflow step, or steps inside an agent run) */\n children?: TrajectoryStep[];\n};\n\n// --- Individual step types ---\n\nexport type ToolCallStep = TrajectoryStepBase & {\n stepType: 'tool_call';\n /** Arguments passed to the tool */\n toolArgs?: Record<string, unknown>;\n /** Result returned by the tool */\n toolResult?: Record<string, unknown>;\n /** Whether the tool call succeeded */\n success?: boolean;\n};\n\nexport type McpToolCallStep = TrajectoryStepBase & {\n stepType: 'mcp_tool_call';\n /** Arguments passed to the MCP tool */\n toolArgs?: Record<string, unknown>;\n /** Result returned by the MCP tool */\n toolResult?: Record<string, unknown>;\n /** The MCP server that handled this tool call */\n mcpServer?: string;\n /** Whether the tool call succeeded */\n success?: boolean;\n};\n\nexport type ProviderToolCallStep = TrajectoryStepBase & {\n stepType: 'provider_tool_call';\n /** Arguments passed to the server-side tool */\n toolArgs?: Record<string, unknown>;\n /** Result returned by the server-side tool */\n toolResult?: Record<string, unknown>;\n /** Whether the tool call succeeded */\n success?: boolean;\n};\n\nexport type ModelGenerationStep = TrajectoryStepBase & {\n stepType: 'model_generation';\n /** The model ID used for generation */\n modelId?: string;\n /** Number of prompt tokens consumed */\n promptTokens?: number;\n /** Number of completion tokens generated */\n completionTokens?: number;\n /** Reason the generation finished (e.g., 'stop', 'tool-calls') */\n finishReason?: string;\n};\n\nexport type AgentRunStep = TrajectoryStepBase & {\n stepType: 'agent_run';\n /** The ID of the agent that was run */\n agentId?: string;\n};\n\nexport type WorkflowStepStep = TrajectoryStepBase & {\n stepType: 'workflow_step';\n /** The step ID within the workflow */\n stepId?: string;\n /** Status of the step (e.g., 'success', 'failed', 'suspended') */\n status?: string;\n /** Output data from the step */\n output?: Record<string, unknown>;\n};\n\nexport type WorkflowRunStep = TrajectoryStepBase & {\n stepType: 'workflow_run';\n /** The ID of the workflow that was run */\n workflowId?: string;\n /** Status of the workflow run */\n status?: string;\n};\n\nexport type WorkflowConditionalStep = TrajectoryStepBase & {\n stepType: 'workflow_conditional';\n /** Number of conditions evaluated */\n conditionCount?: number;\n /** Steps selected by the conditional */\n selectedSteps?: string[];\n};\n\nexport type WorkflowParallelStep = TrajectoryStepBase & {\n stepType: 'workflow_parallel';\n /** Number of parallel branches */\n branchCount?: number;\n /** Steps that ran in parallel */\n parallelSteps?: string[];\n};\n\nexport type WorkflowLoopStep = TrajectoryStepBase & {\n stepType: 'workflow_loop';\n /** Type of loop (e.g., 'dowhile', 'dountil') */\n loopType?: string;\n /** Total number of iterations executed */\n totalIterations?: number;\n};\n\nexport type WorkflowSleepStep = TrajectoryStepBase & {\n stepType: 'workflow_sleep';\n /** Sleep duration in milliseconds */\n sleepDurationMs?: number;\n /** Type of sleep */\n sleepType?: string;\n};\n\nexport type WorkflowWaitEventStep = TrajectoryStepBase & {\n stepType: 'workflow_wait_event';\n /** Name of the event being waited on */\n eventName?: string;\n /** Whether the event was received */\n eventReceived?: boolean;\n};\n\nexport type ProcessorRunStep = TrajectoryStepBase & {\n stepType: 'processor_run';\n /** The ID of the processor that was run */\n processorId?: string;\n};\n\n/**\n * A single step in an agent's or workflow's trajectory.\n * Discriminated union on `stepType` — each variant carries properties specific\n * to that kind of action.\n */\nexport type TrajectoryStep =\n | ToolCallStep\n | McpToolCallStep\n | ProviderToolCallStep\n | ModelGenerationStep\n | AgentRunStep\n | WorkflowStepStep\n | WorkflowRunStep\n | WorkflowConditionalStep\n | WorkflowParallelStep\n | WorkflowLoopStep\n | WorkflowSleepStep\n | WorkflowWaitEventStep\n | ProcessorRunStep;\n\n/**\n * The type of action taken in a trajectory step.\n * Derived from the discriminated union for convenience.\n */\nexport type TrajectoryStepType = TrajectoryStep['stepType'];\n\n/**\n * A complete trajectory: the ordered sequence of steps an agent or workflow took\n * to go from input to output.\n */\nexport type Trajectory = {\n /** Ordered list of steps taken */\n steps: TrajectoryStep[];\n /** Total duration of the full trajectory in milliseconds */\n totalDurationMs?: number;\n /** The raw agent output messages, preserved for scorers that need text context */\n rawOutput?: ScorerRunOutputForAgent;\n /** The raw workflow result, preserved for scorers that need workflow-specific data */\n rawWorkflowResult?: {\n stepResults: Record<string, StepResult<any, any, any, any>>;\n stepExecutionPath?: string[];\n };\n};\n\n/**\n * Configuration for trajectory comparison behavior.\n */\nexport type TrajectoryComparisonOptions = {\n /**\n * How to compare step ordering.\n * - 'strict': exact match (same steps, same order, no extras)\n * - 'relaxed': subsequence match (extra steps OK, order matters)\n * - 'unordered': just check presence (don't care about order)\n * @default 'relaxed'\n */\n ordering?: 'strict' | 'relaxed' | 'unordered';\n /**\n * Whether to allow repeated steps in the trajectory.\n * When false, repeated steps (loops) are penalized.\n * @default true\n */\n allowRepeatedSteps?: boolean;\n};\n\n/**\n * Discriminated union mirroring `TrajectoryStep` — specify a `stepType` for autocomplete\n * on that variant's fields (e.g., `toolArgs` for `tool_call`). All variant-specific fields\n * are optional; only specified fields are used for comparison.\n *\n * Omit `stepType` to match any step by name only.\n *\n * @example\n * ```ts\n * // Match any step named 'search'\n * { name: 'search' }\n *\n * // Match a tool_call with specific args (autocomplete for toolArgs, toolResult, success)\n * { name: 'search', stepType: 'tool_call', toolArgs: { query: 'weather' } }\n *\n * // Match an agent run with nested expectations for its children\n * {\n * name: 'researchAgent',\n * stepType: 'agent_run',\n * children: {\n * ordering: 'unordered',\n * steps: [\n * { name: 'search', stepType: 'tool_call' },\n * { name: 'summarize', stepType: 'tool_call' },\n * ],\n * },\n * }\n * ```\n */\n/**\n * Utility type: derive an expected-step variant from an actual TrajectoryStep variant.\n *\n * - Keeps `name` and `stepType` required (for discriminant narrowing)\n * - Makes all other variant-specific fields optional\n * - Drops `durationMs` and `metadata` (not useful for expectations)\n * - Replaces `children: TrajectoryStep[]` with `children: TrajectoryExpectation`\n */\ntype ToExpected<T extends TrajectoryStep> = Pick<T, 'name' | 'stepType'> &\n Partial<Omit<T, 'name' | 'stepType' | 'children' | 'durationMs' | 'metadata'>> & {\n /** Nested trajectory expectation for this step's children */\n children?: TrajectoryExpectation;\n };\n\n/**\n * Expected step with no specific `stepType` — matches any step by name only.\n * Use this when you don't care about the step type, just the name.\n */\ntype ExpectedGenericStep = {\n /** Step name to match (tool name, agent ID, workflow step name, etc.) */\n name: string;\n /** Must be omitted for generic matching */\n stepType?: undefined;\n /** Nested trajectory expectation for this step's children */\n children?: TrajectoryExpectation;\n};\n\n/**\n * A step expectation for trajectory evaluation.\n *\n * Discriminated union derived from `TrajectoryStep` — when you specify a `stepType`,\n * you get autocomplete for that variant's fields (e.g., `toolArgs` for `tool_call`).\n * Omit `stepType` to match any step by name only.\n *\n * @example\n * ```ts\n * // Name-only matching (any step type)\n * { name: 'search' }\n *\n * // Type-narrowed with autocomplete for toolArgs, toolResult, success\n * { name: 'search', stepType: 'tool_call', toolArgs: { query: 'weather' } }\n *\n * // Nested expectations for a sub-agent\n * {\n * name: 'research-agent',\n * stepType: 'agent_run',\n * children: {\n * ordering: 'unordered',\n * steps: [\n * { name: 'search', stepType: 'tool_call' },\n * { name: 'summarize', stepType: 'tool_call' },\n * ],\n * },\n * }\n * ```\n */\nexport type ExpectedStep =\n | ToExpected<ToolCallStep>\n | ToExpected<McpToolCallStep>\n | ToExpected<ModelGenerationStep>\n | ToExpected<AgentRunStep>\n | ToExpected<WorkflowStepStep>\n | ToExpected<WorkflowRunStep>\n | ToExpected<WorkflowConditionalStep>\n | ToExpected<WorkflowParallelStep>\n | ToExpected<WorkflowLoopStep>\n | ToExpected<WorkflowSleepStep>\n | ToExpected<WorkflowWaitEventStep>\n | ToExpected<ProcessorRunStep>\n | ExpectedGenericStep;\n\n/**\n * Full trajectory expectation config for the unified trajectory scorer.\n * Can be set as constructor defaults (agent-level) or per dataset item (prompt-specific).\n * Per-item values override constructor defaults.\n */\nexport type TrajectoryExpectation = {\n // --- Accuracy ---\n\n /** Expected steps for accuracy checking */\n steps?: ExpectedStep[];\n\n /**\n * How to compare step ordering.\n * - 'strict': exact match (same steps, same order, no extras)\n * - 'relaxed': subsequence match (extra steps OK, order matters)\n * - 'unordered': just check presence (don't care about order)\n * @default 'relaxed'\n */\n ordering?: 'strict' | 'relaxed' | 'unordered';\n\n /** Whether to allow repeated steps in accuracy evaluation. @default true */\n allowRepeatedSteps?: boolean;\n\n // --- Efficiency ---\n\n /** Maximum number of steps allowed */\n maxSteps?: number;\n\n /** Maximum total tokens across all model_generation steps */\n maxTotalTokens?: number;\n\n /** Maximum total duration in milliseconds */\n maxTotalDurationMs?: number;\n\n /** Whether to penalize redundant calls (same tool + same args consecutively). @default true */\n noRedundantCalls?: boolean;\n\n // --- Blacklist ---\n\n /** Tool names that should never appear in the trajectory */\n blacklistedTools?: string[];\n\n /** Tool name sequences that should never appear (contiguous subsequences) */\n blacklistedSequences?: string[][];\n\n // --- Tool failure tolerance ---\n\n /** Maximum acceptable retries per tool before penalizing. @default 2 */\n maxRetriesPerTool?: number;\n};\n\n// ============================================================================\n// Trajectory Extraction — Agent\n// ============================================================================\n\n/**\n * Extracts a Trajectory from agent output messages by walking through\n * tool invocations.\n *\n * This is called automatically by `runEvals` when using `AgentScorerConfig.trajectory`\n * scorers — trajectory scorers receive a pre-extracted `Trajectory` as their `output`\n * instead of raw `MastraDBMessage[]`.\n *\n * @param output - The raw agent output messages\n * @returns A Trajectory with ToolCallStep entries extracted from tool invocations\n */\nexport function extractTrajectory(output: ScorerRunOutputForAgent): Trajectory {\n const steps: ToolCallStep[] = [];\n\n for (const message of output) {\n // Prefer the legacy toolInvocations array when present; fall back to\n // V2 content.parts for messages that only store tool calls there.\n const legacy = message?.content?.toolInvocations;\n const fromParts = legacy\n ? undefined\n : message?.content?.parts\n ?.filter((p): p is Extract<typeof p, { type: 'tool-invocation' }> => p.type === 'tool-invocation')\n .map(p => p.toolInvocation);\n const toolInvocations = legacy ?? fromParts;\n if (!toolInvocations?.length) continue;\n\n for (const invocation of toolInvocations) {\n if (invocation && invocation.toolName && (invocation.state === 'result' || invocation.state === 'call')) {\n const toolArgs =\n invocation.args != null && typeof invocation.args === 'object' && !Array.isArray(invocation.args)\n ? (invocation.args as Record<string, unknown>)\n : invocation.args != null\n ? { value: invocation.args }\n : undefined;\n\n const rawResult = invocation.state === 'result' ? invocation.result : undefined;\n const toolResult =\n rawResult != null && typeof rawResult === 'object' && !Array.isArray(rawResult)\n ? (rawResult as Record<string, unknown>)\n : rawResult != null\n ? { value: rawResult }\n : undefined;\n\n steps.push({\n stepType: 'tool_call',\n name: invocation.toolName,\n toolArgs,\n toolResult,\n success: invocation.state === 'result',\n });\n }\n }\n }\n\n return { steps, rawOutput: output };\n}\n\n// ============================================================================\n// Trajectory Extraction — Workflow\n// ============================================================================\n\n/**\n * Extracts a Trajectory from workflow step results.\n *\n * Converts the `stepResults` record (and optional `stepExecutionPath` ordering)\n * into a flat list of `WorkflowStepStep` entries. Each step captures its status,\n * output, and timing.\n *\n * This is called automatically by `runEvals` when using `WorkflowScorerConfig.trajectory`\n * scorers.\n *\n * @param stepResults - The workflow step results record\n * @param stepExecutionPath - Optional ordered list of step IDs for execution ordering\n * @returns A Trajectory with WorkflowStepStep entries\n */\nexport function extractWorkflowTrajectory(\n stepResults: Record<string, StepResult<any, any, any, any>>,\n stepExecutionPath?: string[],\n): Trajectory {\n const steps: WorkflowStepStep[] = [];\n\n // Use stepExecutionPath ordering when available, fall back to stepResults keys\n const stepIds = stepExecutionPath ?? Object.keys(stepResults);\n\n let totalStartedAt: number | undefined;\n let totalEndedAt: number | undefined;\n\n for (const stepId of stepIds) {\n const result = stepResults[stepId];\n if (!result) continue;\n\n // Track overall timing\n if (result.startedAt != null) {\n if (totalStartedAt == null || result.startedAt < totalStartedAt) {\n totalStartedAt = result.startedAt;\n }\n }\n\n const endedAt = 'endedAt' in result ? (result as { endedAt?: number }).endedAt : undefined;\n if (endedAt != null) {\n if (totalEndedAt == null || endedAt > totalEndedAt) {\n totalEndedAt = endedAt;\n }\n }\n\n const durationMs = result.startedAt != null && endedAt != null ? endedAt - result.startedAt : undefined;\n\n const output =\n 'output' in result && result.output != null && typeof result.output === 'object' && !Array.isArray(result.output)\n ? (result.output as Record<string, unknown>)\n : 'output' in result && result.output != null\n ? { value: result.output }\n : undefined;\n\n steps.push({\n stepType: 'workflow_step',\n name: stepId,\n stepId,\n status: result.status,\n output,\n durationMs,\n metadata: result.metadata as Record<string, unknown> | undefined,\n });\n }\n\n const totalDurationMs = totalStartedAt != null && totalEndedAt != null ? totalEndedAt - totalStartedAt : undefined;\n\n return {\n steps,\n totalDurationMs,\n rawWorkflowResult: { stepResults, stepExecutionPath },\n };\n}\n\n// ============================================================================\n// Trajectory Extraction — From Trace (Hierarchical)\n// ============================================================================\n\n/**\n * Span types that are considered noise and should be skipped during\n * trace-to-trajectory conversion (internal implementation details, not\n * meaningful trajectory steps).\n */\nconst SKIPPED_SPAN_TYPES = new Set([\n SpanType.SCORER_RUN,\n SpanType.SCORER_STEP,\n SpanType.GENERIC,\n SpanType.MODEL_STEP,\n SpanType.MODEL_INFERENCE,\n SpanType.MODEL_CHUNK,\n SpanType.WORKFLOW_CONDITIONAL_EVAL,\n]);\n\ntype SpanTreeNode = {\n span: SpanRecord;\n children: SpanTreeNode[];\n};\n\n/**\n * Converts a `SpanTreeNode` to `TrajectoryStep` entries.\n *\n * Returns an array because a skipped span promotes its children into the\n * parent's list rather than dropping them entirely.\n */\nfunction spanToTrajectorySteps(node: SpanTreeNode): TrajectoryStep[] {\n const { span, children: childNodes } = node;\n\n if (SKIPPED_SPAN_TYPES.has(span.spanType)) {\n // Promote children of skipped spans so their subtree is preserved\n return childNodes.flatMap(spanToTrajectorySteps);\n }\n\n const durationMs =\n span.endedAt != null && span.startedAt != null ? span.endedAt.getTime() - span.startedAt.getTime() : undefined;\n\n const childSteps = childNodes.flatMap(spanToTrajectorySteps);\n\n const base: TrajectoryStepBase = {\n name: span.name,\n durationMs,\n metadata: span.metadata as Record<string, unknown> | undefined,\n ...(childSteps.length > 0 ? { children: childSteps } : {}),\n };\n\n const attrs = (span.attributes ?? {}) as Record<string, unknown>;\n\n switch (span.spanType) {\n case SpanType.TOOL_CALL: {\n const toolArgs = toRecordOrUndefined(span.input);\n const toolResult = toRecordOrUndefined(span.output);\n return [\n {\n ...base,\n stepType: 'tool_call' as const,\n toolArgs,\n toolResult,\n success: typeof attrs.success === 'boolean' ? attrs.success : undefined,\n },\n ];\n }\n\n case SpanType.MCP_TOOL_CALL: {\n const toolArgs = toRecordOrUndefined(span.input);\n const toolResult = toRecordOrUndefined(span.output);\n return [\n {\n ...base,\n stepType: 'mcp_tool_call' as const,\n toolArgs,\n toolResult,\n mcpServer: typeof attrs.mcpServer === 'string' ? attrs.mcpServer : undefined,\n success: typeof attrs.success === 'boolean' ? attrs.success : undefined,\n },\n ];\n }\n\n case SpanType.PROVIDER_TOOL_CALL: {\n const toolArgs = toRecordOrUndefined(span.input);\n const toolResult = toRecordOrUndefined(span.output);\n return [\n {\n ...base,\n stepType: 'provider_tool_call' as const,\n toolArgs,\n toolResult,\n success: typeof attrs.success === 'boolean' ? attrs.success : undefined,\n },\n ];\n }\n\n case SpanType.MODEL_GENERATION: {\n const usage = attrs.usage as { inputTokens?: number; outputTokens?: number } | undefined;\n return [\n {\n ...base,\n stepType: 'model_generation' as const,\n modelId: typeof attrs.model === 'string' ? attrs.model : undefined,\n promptTokens: usage?.inputTokens,\n completionTokens: usage?.outputTokens,\n finishReason: typeof attrs.finishReason === 'string' ? attrs.finishReason : undefined,\n },\n ];\n }\n\n case SpanType.AGENT_RUN:\n return [{ ...base, stepType: 'agent_run' as const, agentId: span.entityId ?? undefined }];\n\n case SpanType.WORKFLOW_RUN:\n return [{ ...base, stepType: 'workflow_run' as const, workflowId: span.entityId ?? undefined }];\n\n case SpanType.WORKFLOW_STEP: {\n const output = toRecordOrUndefined(span.output);\n return [{ ...base, stepType: 'workflow_step' as const, stepId: span.name, output }];\n }\n\n case SpanType.WORKFLOW_CONDITIONAL:\n return [{ ...base, stepType: 'workflow_conditional' as const }];\n\n case SpanType.WORKFLOW_PARALLEL:\n return [{ ...base, stepType: 'workflow_parallel' as const }];\n\n case SpanType.WORKFLOW_LOOP:\n return [{ ...base, stepType: 'workflow_loop' as const }];\n\n case SpanType.WORKFLOW_SLEEP:\n return [{ ...base, stepType: 'workflow_sleep' as const }];\n\n case SpanType.WORKFLOW_WAIT_EVENT:\n return [{ ...base, stepType: 'workflow_wait_event' as const }];\n\n case SpanType.PROCESSOR_RUN:\n return [{ ...base, stepType: 'processor_run' as const }];\n\n default:\n // Unknown span type — promote children if any\n return childSteps;\n }\n}\n\n/**\n * Safely converts a value to `Record<string, unknown>` or returns undefined.\n */\nfunction toRecordOrUndefined(value: unknown): Record<string, unknown> | undefined {\n if (value == null) return undefined;\n if (typeof value === 'object' && !Array.isArray(value)) {\n return value as Record<string, unknown>;\n }\n return { value };\n}\n\n/**\n * Extracts a hierarchical Trajectory from trace spans (as returned by the\n * observability store's `getTrace()`).\n *\n * Builds a parent-child tree from `parentSpanId` references, then recursively\n * converts each span to the appropriate `TrajectoryStep` discriminated union\n * type with nested `children`.\n *\n * Noise spans (`generic`, `model_step`, `model_chunk`, `workflow_conditional_eval`)\n * are automatically skipped.\n *\n * This is used by `runEvals` when storage is available to produce richer,\n * hierarchical trajectories that include nested agent runs, tool calls, and\n * model generations inside workflow or agent steps.\n *\n * @param spans - Flat array of span records from `getTrace().spans`\n * @param rootSpanId - Optional span ID to use as root. If omitted, spans with\n * no parent are used as roots.\n * @returns A Trajectory with hierarchical TrajectoryStep entries\n *\n * @example\n * ```ts\n * const trace = await observabilityStore.getTrace({ traceId });\n * const trajectory = extractTrajectoryFromTrace(trace.spans, workflowSpanId);\n * ```\n */\nexport function extractTrajectoryFromTrace(spans: SpanRecord[], rootSpanId?: string): Trajectory {\n if (spans.length === 0) {\n return { steps: [] };\n }\n\n // Build lookup map\n const nodeMap = new Map<string, SpanTreeNode>();\n for (const span of spans) {\n nodeMap.set(span.spanId, { span, children: [] });\n }\n\n // Attach children to parents\n const roots: SpanTreeNode[] = [];\n for (const span of spans) {\n const node = nodeMap.get(span.spanId)!;\n if (span.parentSpanId && nodeMap.has(span.parentSpanId)) {\n nodeMap.get(span.parentSpanId)!.children.push(node);\n } else {\n roots.push(node);\n }\n }\n\n // Sort children by start time\n for (const node of nodeMap.values()) {\n node.children.sort((a, b) => a.span.startedAt.getTime() - b.span.startedAt.getTime());\n }\n\n // Find the root to start from\n let targetRoots: SpanTreeNode[];\n if (rootSpanId) {\n const rootNode = nodeMap.get(rootSpanId);\n targetRoots = rootNode ? [rootNode] : roots;\n } else {\n targetRoots = roots;\n }\n\n // If the target is a single root span (e.g., a workflow_run or agent_run),\n // convert its children directly as the trajectory steps (the root itself\n // is the \"container\", not a step in the trajectory)\n let stepsToConvert: SpanTreeNode[];\n if (targetRoots.length === 1) {\n const root = targetRoots[0]!;\n // If root is a container span type, use its children as trajectory steps\n const containerTypes = new Set([SpanType.WORKFLOW_RUN, SpanType.AGENT_RUN]);\n if (containerTypes.has(root.span.spanType)) {\n stepsToConvert = root.children;\n } else {\n stepsToConvert = targetRoots;\n }\n } else {\n stepsToConvert = targetRoots;\n }\n\n const steps = stepsToConvert.flatMap(spanToTrajectorySteps);\n\n // Calculate total duration from the root span(s)\n let totalDurationMs: number | undefined;\n if (targetRoots.length === 1) {\n const root = targetRoots[0]!.span;\n if (root.endedAt && root.startedAt) {\n totalDurationMs = root.endedAt.getTime() - root.startedAt.getTime();\n }\n }\n\n return { steps, totalDurationMs };\n}\n","import type { z } from 'zod/v4';\nimport type { AgentExecutionOptionsBase } from '../agent/agent.types';\nimport type { SerializedError } from '../error';\nimport type { ScoringSamplingConfig, ScoringSource } from '../evals/types';\nimport type { MastraDBMessage, StorageThreadType, SerializedMemoryConfig } from '../memory/types';\nimport type { ProcessorPhase } from '../processor-provider';\nimport { getZodInnerType, getZodTypeName } from '../utils/zod-utils';\nimport type { StepResult, WorkflowRunState, WorkflowRunStatus } from '../workflows';\n\nexport type StoragePagination = {\n page: number;\n perPage: number | false;\n};\n\nexport type StorageColumnType = 'text' | 'timestamp' | 'uuid' | 'jsonb' | 'integer' | 'float' | 'bigint' | 'boolean';\n\nexport interface StorageColumn {\n type: StorageColumnType;\n primaryKey?: boolean;\n nullable?: boolean;\n references?: {\n table: string;\n column: string;\n };\n}\n\nexport interface StorageTableConfig {\n columns: Record<string, StorageColumn>;\n compositePrimaryKey?: string[];\n}\nexport interface WorkflowRuns {\n runs: WorkflowRun[];\n total: number;\n}\n\nexport interface StorageWorkflowRun {\n workflow_name: string;\n run_id: string;\n resourceId?: string;\n snapshot: WorkflowRunState | string;\n createdAt: Date;\n updatedAt: Date;\n}\nexport interface WorkflowRun {\n workflowName: string;\n runId: string;\n snapshot: WorkflowRunState | string;\n createdAt: Date;\n updatedAt: Date;\n resourceId?: string;\n}\n\nexport type PaginationInfo = {\n total: number;\n page: number;\n /**\n * Number of items per page, or `false` to fetch all records without pagination limit.\n * When `false`, all matching records are returned in a single response.\n */\n perPage: number | false;\n hasMore: boolean;\n};\n\nexport type MastraMessageFormat = 'v1' | 'v2';\n\nexport type StorageMetadataFilterValue = string | number | boolean | null;\n\nexport type StorageMetadataFilter = Record<string, StorageMetadataFilterValue>;\n\n/**\n * Common options for listing messages (pagination, filtering, ordering)\n */\ntype StorageListMessagesOptions = {\n include?: {\n id: string;\n threadId?: string;\n withPreviousMessages?: number;\n withNextMessages?: number;\n }[];\n /**\n * Number of items per page, or `false` to fetch all records without pagination limit.\n * Defaults to 40 if not specified.\n */\n perPage?: number | false;\n /**\n * Zero-indexed page number for pagination.\n * Defaults to 0 if not specified.\n */\n page?: number;\n filter?: {\n dateRange?: {\n start?: Date;\n end?: Date;\n /**\n * When true, excludes the start date from results (uses > instead of >=).\n * Useful for cursor-based pagination to avoid duplicates.\n * @default false\n */\n startExclusive?: boolean;\n /**\n * When true, excludes the end date from results (uses < instead of <=).\n * Useful for cursor-based pagination to avoid duplicates.\n * @default false\n */\n endExclusive?: boolean;\n };\n /**\n * Filter messages by shallow scalar metadata key-value pairs from message content metadata.\n * All specified key-value pairs must match with exact type equality (AND logic).\n * Keys must start with a letter or underscore, contain only letters, numbers, and underscores,\n * be at most 128 characters, and cannot be `__proto__`, `prototype`, or `constructor`.\n */\n metadata?: StorageMetadataFilter;\n };\n orderBy?: StorageOrderBy<'createdAt'>;\n};\n\n/**\n * Input for listing messages by thread ID.\n * The resource ID can be optionally provided to filter messages within the thread.\n */\nexport type StorageListMessagesInput = StorageListMessagesOptions & {\n /**\n * Thread ID(s) to query messages from.\n */\n threadId: string | string[];\n /**\n * Optional resource ID to further filter messages within the thread(s).\n */\n resourceId?: string;\n};\n\nexport type StorageListMessagesOutput = PaginationInfo & {\n messages: MastraDBMessage[];\n};\n\n/**\n * Input for listing messages by resource ID only (across all threads).\n * Used by Observational Memory and LongMemEval for resource-scoped queries.\n */\nexport type StorageListMessagesByResourceIdInput = StorageListMessagesOptions & {\n /**\n * Resource ID to query ALL messages for the resource across all threads.\n */\n resourceId: string;\n};\n\nexport type StorageListWorkflowRunsInput = {\n workflowName?: string;\n fromDate?: Date;\n toDate?: Date;\n /**\n * Number of items per page, or `false` to fetch all records without pagination limit.\n * When undefined, returns all workflow runs without pagination.\n * When both perPage and page are provided, pagination is applied.\n */\n perPage?: number | false;\n /**\n * Zero-indexed page number for pagination.\n * When both perPage and page are provided, pagination is applied.\n * When either is undefined, all results are returned.\n */\n page?: number;\n resourceId?: string;\n status?: WorkflowRunStatus;\n};\n\nexport type StorageListThreadsInput = {\n /**\n * Number of items per page, or `false` to fetch all records without pagination limit.\n * Defaults to 100 if not specified.\n */\n perPage?: number | false;\n /**\n * Zero-indexed page number for pagination.\n * Defaults to 0 if not specified.\n */\n page?: number;\n orderBy?: StorageOrderBy;\n /**\n * Filter options for querying threads.\n */\n filter?: {\n /**\n * Filter threads by resource ID.\n */\n resourceId?: string;\n /**\n * Filter threads by metadata key-value pairs.\n * All specified key-value pairs must match (AND logic).\n */\n metadata?: Record<string, unknown>;\n };\n};\n\nexport type StorageListThreadsOutput = PaginationInfo & {\n threads: StorageThreadType[];\n};\n\n/**\n * Metadata stored on cloned threads to track their origin\n */\nexport type ThreadCloneMetadata = {\n /** ID of the thread this was cloned from */\n sourceThreadId: string;\n /** Timestamp when the clone was created */\n clonedAt: Date;\n /** ID of the last message included in the clone (if messages were copied) */\n lastMessageId?: string;\n};\n\n/**\n * Input options for cloning a thread\n */\nexport type StorageCloneThreadInput = {\n /** ID of the thread to clone */\n sourceThreadId: string;\n /** ID for the new cloned thread (if not provided, a random UUID will be generated) */\n newThreadId?: string;\n /** Resource ID for the new thread (defaults to source thread's resourceId) */\n resourceId?: string;\n /** Title for the new cloned thread */\n title?: string;\n /** Additional metadata to merge with clone metadata */\n metadata?: Record<string, unknown>;\n /** Options for filtering which messages to include */\n options?: {\n /** Maximum number of messages to copy (from most recent) */\n messageLimit?: number;\n /** Filter messages by date range or specific IDs */\n messageFilter?: {\n /** Only include messages created on or after this date */\n startDate?: Date;\n /** Only include messages created on or before this date */\n endDate?: Date;\n /** Only include messages with these specific IDs */\n messageIds?: string[];\n };\n };\n};\n\n/**\n * Output from cloning a thread\n */\nexport type StorageCloneThreadOutput = {\n /** The newly created cloned thread */\n thread: StorageThreadType;\n /** The messages that were copied to the new thread */\n clonedMessages: MastraDBMessage[];\n /** Map from source message IDs to cloned message IDs (used for OM remapping) */\n messageIdMap?: Record<string, string>;\n};\n\nexport type StorageResourceType = {\n id: string;\n workingMemory?: string;\n metadata?: Record<string, unknown>;\n createdAt: Date;\n updatedAt: Date;\n};\n\nexport type StorageMessageType = {\n id: string;\n thread_id: string;\n content: string;\n role: string;\n type: string;\n createdAt: Date;\n resourceId: string | null;\n};\n\nexport interface StorageOrderBy<TField extends ThreadOrderBy = ThreadOrderBy> {\n field?: TField;\n direction?: ThreadSortDirection;\n}\n\nexport interface ThreadSortOptions {\n orderBy?: ThreadOrderBy;\n sortDirection?: ThreadSortDirection;\n}\n\nexport type ThreadOrderBy = 'createdAt' | 'updatedAt';\n\nexport type ThreadSortDirection = 'ASC' | 'DESC';\n\n// Agent Storage Types\n\n/**\n * Per-tool configuration stored in agent snapshots.\n * Allows overriding the tool description for this specific agent.\n */\nexport interface StorageToolConfig {\n /** Custom description override for this tool in this agent context */\n description?: string;\n /** Conditional rules for when this tool should be available */\n rules?: RuleGroup;\n}\n\n/**\n * Per-MCP-client tool configuration stored in agent snapshots.\n * Specifies which tools from an MCP client are enabled and their overrides.\n * When `tools` is omitted, all tools from the MCP client/server are included.\n */\nexport interface StorageMCPClientToolsConfig {\n /** When omitted, all tools from the source are included. */\n tools?: Record<string, StorageToolConfig>;\n}\n\n/**\n * One pinned connection on a tool provider config (per-agent snapshot).\n * Adapter-native `connectionId` is the join key into the\n * `mastra_tool_provider_connections` storage table.\n */\nexport interface StorageToolProviderConfigConnection {\n kind: 'author' | 'invoker' | 'platform';\n connectionId: string;\n toolkit: string;\n label?: string;\n scope?: StorageToolProviderConnectionScope;\n}\n\n/**\n * Per-tool metadata (toolkit + optional description override) for a tool\n * provider's selected tools.\n */\nexport interface StorageToolProviderToolMeta {\n toolkit?: string;\n description?: string;\n}\n\n/**\n * Stored shape for one tool provider's configuration on one agent.\n * Keyed by tool slug for `tools` and by toolkit slug for `connections`.\n */\nexport interface StorageToolProviderConfig {\n tools: Record<string, StorageToolProviderToolMeta>;\n connections: Record<string, StorageToolProviderConfigConnection[]>;\n}\n\n/**\n * Scorer reference with optional sampling configuration\n */\nexport interface StorageScorerConfig {\n /** Custom description override for this scorer in this agent context */\n description?: string;\n /** Sampling configuration for this scorer */\n sampling?: ScoringSamplingConfig;\n /** Conditional rules for when this scorer should be active */\n rules?: RuleGroup;\n}\n\n/**\n * Model configuration stored in agent snapshots.\n */\nexport interface StorageModelConfig {\n /** Model provider (e.g., 'openai', 'anthropic') */\n provider: string;\n /** Model name (e.g., 'gpt-4o', 'claude-3-opus') */\n name: string;\n /** Temperature for generation */\n temperature?: number;\n /** Top-p sampling parameter */\n topP?: number;\n /** Frequency penalty */\n frequencyPenalty?: number;\n /** Presence penalty */\n presencePenalty?: number;\n /** Maximum completion tokens */\n maxCompletionTokens?: number;\n /** Additional provider-specific options */\n [key: string]: unknown;\n}\n\n/**\n * Default options stored in agent snapshots.\n * Based on AgentExecutionOptionsBase but omitting non-serializable properties.\n *\n * Non-serializable properties that are omitted:\n * - Callbacks (onStepFinish, onFinish, onChunk, onError, onAbort, prepareStep)\n * - Runtime objects (requestContext, abortSignal, tracingContext)\n * - Functions and processor instances (inputProcessors, outputProcessors, clientTools, scorers)\n * - Tools/toolsets (contain functions, stored separately as references)\n * - Complex types (context, memory, instructions, system, stopWhen)\n */\nexport type StorageDefaultOptions = Omit<\n AgentExecutionOptionsBase<any>,\n // Callback functions\n | 'onStepFinish'\n | 'onFinish'\n | 'onChunk'\n | 'onError'\n | 'onAbort'\n | 'prepareStep'\n // Runtime objects\n | 'abortSignal'\n | 'requestContext'\n | 'tracingContext'\n // Functions and processor instances\n | 'inputProcessors'\n | 'outputProcessors'\n | 'clientTools'\n | 'scorers'\n | 'toolsets'\n // Complex types\n | 'context' // ModelMessage includes complex content types (images, files)\n | 'memory' // AgentMemoryOption might contain runtime memory instances\n | 'instructions' // SystemMessage can be arrays or complex message objects\n | 'system' // SystemMessage can be arrays or complex message objects\n | 'stopWhen' // StopCondition is a complex union type from AI SDK\n | 'providerOptions' // ProviderOptions includes provider-specific types from external packages\n | 'requireToolApproval' // can be a function at runtime; stored options must be serializable\n> & {\n /**\n * Stored agents only support a boolean here. Function-based approval policies are runtime-only\n * and cannot be serialized, so they are intentionally excluded from stored default options.\n */\n requireToolApproval?: boolean;\n};\n\n/**\n * A conditional variant: a value paired with an optional RuleGroup.\n * When rules are present, the value is only used if rules evaluate to true against the request context.\n * When rules are absent, the variant acts as the default/fallback.\n */\nexport interface StorageConditionalVariant<T> {\n value: T;\n rules?: RuleGroup;\n}\n\n/**\n * A field that can be either a static value or an array of conditional variants.\n * When an array of variants, all matching variants accumulate:\n * arrays are concatenated and objects are shallow-merged.\n * A variant with no rules always matches (acts as the default/base).\n */\nexport type StorageConditionalField<T> = T | StorageConditionalVariant<T>[];\n\n/**\n * Agent version snapshot type containing ALL agent configuration fields.\n * These fields live exclusively in version snapshot rows, not on the agent record.\n */\nexport interface StorageAgentSnapshotType {\n /** Display name of the agent */\n name: string;\n /** Purpose description */\n description?: string;\n /** System instructions/prompt — plain string for backward compatibility, or array of instruction blocks */\n instructions: string | AgentInstructionBlock[];\n /** Model configuration (provider, name, etc.) — static or conditional on request context */\n model: StorageConditionalFie