@mastra/core
Version:
Mastra is the Typescript framework for building AI agents and assistants. It’s used by some of the largest companies in the world to build internal AI automation tooling and customer-facing agents.
1,484 lines (1,473 loc) • 79.7 kB
JavaScript
import { ToolStream } from './chunk-A3QHQYMC.js';
import { isVercelTool, validateToolInput } from './chunk-RAQ4VAQ4.js';
import { RuntimeContext } from './chunk-HLRWYUFN.js';
import { MastraError } from './chunk-MCOVMKIS.js';
import { MastraBase } from './chunk-BMVFEBPE.js';
import { RegisteredLogger, ConsoleLogger, LogLevel } from './chunk-BN2M4UK5.js';
import { createHash } from 'crypto';
import jsonSchemaToZod from 'json-schema-to-zod';
import { z } from 'zod';
import { convertZodSchemaToAISDKSchema, OpenAIReasoningSchemaCompatLayer, OpenAISchemaCompatLayer, GoogleSchemaCompatLayer, AnthropicSchemaCompatLayer, DeepSeekSchemaCompatLayer, MetaSchemaCompatLayer, applyCompatLayer } from '@mastra/schema-compat';
// src/ai-tracing/types.ts
var AISpanType = /* @__PURE__ */ ((AISpanType2) => {
AISpanType2["AGENT_RUN"] = "agent_run";
AISpanType2["GENERIC"] = "generic";
AISpanType2["LLM_GENERATION"] = "llm_generation";
AISpanType2["LLM_CHUNK"] = "llm_chunk";
AISpanType2["MCP_TOOL_CALL"] = "mcp_tool_call";
AISpanType2["TOOL_CALL"] = "tool_call";
AISpanType2["WORKFLOW_RUN"] = "workflow_run";
AISpanType2["WORKFLOW_STEP"] = "workflow_step";
AISpanType2["WORKFLOW_CONDITIONAL"] = "workflow_conditional";
AISpanType2["WORKFLOW_CONDITIONAL_EVAL"] = "workflow_conditional_eval";
AISpanType2["WORKFLOW_PARALLEL"] = "workflow_parallel";
AISpanType2["WORKFLOW_LOOP"] = "workflow_loop";
AISpanType2["WORKFLOW_SLEEP"] = "workflow_sleep";
AISpanType2["WORKFLOW_WAIT_EVENT"] = "workflow_wait_event";
return AISpanType2;
})(AISpanType || {});
var InternalSpans = /* @__PURE__ */ ((InternalSpans2) => {
InternalSpans2[InternalSpans2["NONE"] = 0] = "NONE";
InternalSpans2[InternalSpans2["WORKFLOW"] = 1] = "WORKFLOW";
InternalSpans2[InternalSpans2["AGENT"] = 2] = "AGENT";
InternalSpans2[InternalSpans2["TOOL"] = 4] = "TOOL";
InternalSpans2[InternalSpans2["LLM"] = 8] = "LLM";
InternalSpans2[InternalSpans2["ALL"] = 15] = "ALL";
return InternalSpans2;
})(InternalSpans || {});
var SamplingStrategyType = /* @__PURE__ */ ((SamplingStrategyType2) => {
SamplingStrategyType2["ALWAYS"] = "always";
SamplingStrategyType2["NEVER"] = "never";
SamplingStrategyType2["RATIO"] = "ratio";
SamplingStrategyType2["CUSTOM"] = "custom";
return SamplingStrategyType2;
})(SamplingStrategyType || {});
var AITracingEventType = /* @__PURE__ */ ((AITracingEventType2) => {
AITracingEventType2["SPAN_STARTED"] = "span_started";
AITracingEventType2["SPAN_UPDATED"] = "span_updated";
AITracingEventType2["SPAN_ENDED"] = "span_ended";
return AITracingEventType2;
})(AITracingEventType || {});
// src/ai-tracing/spans/base.ts
function isSpanInternal(spanType, flags) {
if (flags === void 0 || flags === 0 /* NONE */) {
return false;
}
switch (spanType) {
// Workflow-related spans
case "workflow_run" /* WORKFLOW_RUN */:
case "workflow_step" /* WORKFLOW_STEP */:
case "workflow_conditional" /* WORKFLOW_CONDITIONAL */:
case "workflow_conditional_eval" /* WORKFLOW_CONDITIONAL_EVAL */:
case "workflow_parallel" /* WORKFLOW_PARALLEL */:
case "workflow_loop" /* WORKFLOW_LOOP */:
case "workflow_sleep" /* WORKFLOW_SLEEP */:
case "workflow_wait_event" /* WORKFLOW_WAIT_EVENT */:
return (flags & 1 /* WORKFLOW */) !== 0;
// Agent-related spans
case "agent_run" /* AGENT_RUN */:
return (flags & 2 /* AGENT */) !== 0;
// Tool-related spans
case "tool_call" /* TOOL_CALL */:
case "mcp_tool_call" /* MCP_TOOL_CALL */:
return (flags & 4 /* TOOL */) !== 0;
// LLM-related spans
case "llm_generation" /* LLM_GENERATION */:
case "llm_chunk" /* LLM_CHUNK */:
return (flags & 8 /* LLM */) !== 0;
// Default: never internal
default:
return false;
}
}
var BaseAISpan = class {
name;
type;
attributes;
parent;
startTime;
endTime;
isEvent;
isInternal;
aiTracing;
input;
output;
errorInfo;
metadata;
constructor(options, aiTracing) {
this.name = options.name;
this.type = options.type;
this.attributes = deepClean(options.attributes) || {};
this.metadata = deepClean(options.metadata);
this.parent = options.parent;
this.startTime = /* @__PURE__ */ new Date();
this.aiTracing = aiTracing;
this.isEvent = options.isEvent ?? false;
this.isInternal = isSpanInternal(this.type, options.tracingPolicy?.internal);
if (this.isEvent) {
this.output = deepClean(options.output);
} else {
this.input = deepClean(options.input);
}
}
createChildSpan(options) {
return this.aiTracing.startSpan({ ...options, parent: this, isEvent: false });
}
createEventSpan(options) {
return this.aiTracing.startSpan({ ...options, parent: this, isEvent: true });
}
/** Returns `TRUE` if the span is the root span of a trace */
get isRootSpan() {
return !this.parent;
}
/** Get the closest parent spanId that isn't an internal span */
getParentSpanId(includeInternalSpans) {
if (!this.parent) return void 0;
if (includeInternalSpans) return this.parent.id;
if (this.parent.isInternal) return this.parent.getParentSpanId(includeInternalSpans);
return this.parent.id;
}
/** Returns a lightweight span ready for export */
exportSpan(includeInternalSpans) {
return {
id: this.id,
traceId: this.traceId,
name: this.name,
type: this.type,
attributes: this.attributes,
metadata: this.metadata,
startTime: this.startTime,
endTime: this.endTime,
input: this.input,
output: this.output,
errorInfo: this.errorInfo,
isEvent: this.isEvent,
isRootSpan: this.isRootSpan,
parentSpanId: this.getParentSpanId(includeInternalSpans)
};
}
};
var DEFAULT_KEYS_TO_STRIP = /* @__PURE__ */ new Set([
"logger",
"experimental_providerMetadata",
"providerMetadata",
"steps",
"tracingContext"
]);
function deepClean(value, options = {}, _seen = /* @__PURE__ */ new WeakSet(), _depth = 0) {
const { keysToStrip = DEFAULT_KEYS_TO_STRIP, maxDepth = 10 } = options;
if (_depth > maxDepth) {
return "[MaxDepth]";
}
if (value === null || typeof value !== "object") {
try {
JSON.stringify(value);
return value;
} catch (error) {
return `[${error instanceof Error ? error.message : String(error)}]`;
}
}
if (_seen.has(value)) {
return "[Circular]";
}
_seen.add(value);
if (Array.isArray(value)) {
return value.map((item) => deepClean(item, options, _seen, _depth + 1));
}
const cleaned = {};
for (const [key, val] of Object.entries(value)) {
if (keysToStrip.has(key)) {
continue;
}
try {
cleaned[key] = deepClean(val, options, _seen, _depth + 1);
} catch (error) {
cleaned[key] = `[${error instanceof Error ? error.message : String(error)}]`;
}
}
return cleaned;
}
// src/ai-tracing/spans/default.ts
var DefaultAISpan = class extends BaseAISpan {
id;
traceId;
constructor(options, aiTracing) {
super(options, aiTracing);
this.id = generateSpanId();
if (!options.parent) {
this.traceId = generateTraceId();
} else {
this.traceId = options.parent.traceId;
}
}
end(options) {
if (this.isEvent) {
return;
}
this.endTime = /* @__PURE__ */ new Date();
if (options?.output !== void 0) {
this.output = deepClean(options.output);
}
if (options?.attributes) {
this.attributes = { ...this.attributes, ...deepClean(options.attributes) };
}
if (options?.metadata) {
this.metadata = { ...this.metadata, ...deepClean(options.metadata) };
}
}
error(options) {
if (this.isEvent) {
return;
}
const { error, endSpan = true, attributes, metadata } = options;
this.errorInfo = error instanceof MastraError ? {
id: error.id,
details: error.details,
category: error.category,
domain: error.domain,
message: error.message
} : {
message: error.message
};
if (attributes) {
this.attributes = { ...this.attributes, ...deepClean(attributes) };
}
if (metadata) {
this.metadata = { ...this.metadata, ...deepClean(metadata) };
}
if (endSpan) {
this.end();
} else {
this.update({});
}
}
update(options) {
if (this.isEvent) {
return;
}
if (options.input !== void 0) {
this.input = deepClean(options.input);
}
if (options.output !== void 0) {
this.output = deepClean(options.output);
}
if (options.attributes) {
this.attributes = { ...this.attributes, ...deepClean(options.attributes) };
}
if (options.metadata) {
this.metadata = { ...this.metadata, ...deepClean(options.metadata) };
}
}
get isValid() {
return true;
}
async export() {
return JSON.stringify({
spanId: this.id,
traceId: this.traceId,
startTime: this.startTime,
endTime: this.endTime,
attributes: this.attributes,
metadata: this.metadata
});
}
};
function generateSpanId() {
const bytes = new Uint8Array(8);
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
crypto.getRandomValues(bytes);
} else {
for (let i = 0; i < 8; i++) {
bytes[i] = Math.floor(Math.random() * 256);
}
}
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
}
function generateTraceId() {
const bytes = new Uint8Array(16);
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
crypto.getRandomValues(bytes);
} else {
for (let i = 0; i < 16; i++) {
bytes[i] = Math.floor(Math.random() * 256);
}
}
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
}
// src/ai-tracing/spans/no-op.ts
var NoOpAISpan = class extends BaseAISpan {
id;
traceId;
constructor(options, aiTracing) {
super(options, aiTracing);
this.id = "no-op";
this.traceId = "no-op-trace";
}
end(_options) {
}
error(_options) {
}
update(_options) {
}
get isValid() {
return false;
}
};
// src/ai-tracing/tracers/base.ts
var BaseAITracing = class extends MastraBase {
config;
constructor(config) {
super({ component: RegisteredLogger.AI_TRACING, name: config.serviceName });
this.config = {
serviceName: config.serviceName,
name: config.name,
sampling: config.sampling ?? { type: "always" /* ALWAYS */ },
exporters: config.exporters ?? [],
processors: config.processors ?? [],
includeInternalSpans: config.includeInternalSpans ?? false
};
}
/**
* Override setLogger to add AI tracing specific initialization log
*/
__setLogger(logger) {
super.__setLogger(logger);
this.logger.debug(
`[AI Tracing] Initialized [service=${this.config.serviceName}] [instance=${this.config.name}] [sampling=${this.config.sampling.type}]`
);
}
// ============================================================================
// Protected getters for clean config access
// ============================================================================
get exporters() {
return this.config.exporters || [];
}
get processors() {
return this.config.processors || [];
}
// ============================================================================
// Public API - Single type-safe span creation method
// ============================================================================
/**
* Start a new span of a specific AISpanType
*/
startSpan(options) {
const { customSamplerOptions, ...createSpanOptions } = options;
if (!this.shouldSample(customSamplerOptions)) {
return new NoOpAISpan(createSpanOptions, this);
}
const span = this.createSpan(createSpanOptions);
if (span.isEvent) {
this.emitSpanEnded(span);
} else {
this.wireSpanLifecycle(span);
this.emitSpanStarted(span);
}
return span;
}
// ============================================================================
// Configuration Management
// ============================================================================
/**
* Get current configuration
*/
getConfig() {
return { ...this.config };
}
// ============================================================================
// Plugin Access
// ============================================================================
/**
* Get all exporters
*/
getExporters() {
return [...this.exporters];
}
/**
* Get all processors
*/
getProcessors() {
return [...this.processors];
}
/**
* Get the logger instance (for exporters and other components)
*/
getLogger() {
return this.logger;
}
// ============================================================================
// Span Lifecycle Management
// ============================================================================
/**
* Automatically wires up AI tracing lifecycle events for any span
* This ensures all spans emit events regardless of implementation
*/
wireSpanLifecycle(span) {
if (!this.config.includeInternalSpans && span.isInternal) {
return;
}
const originalEnd = span.end.bind(span);
const originalUpdate = span.update.bind(span);
span.end = (options) => {
if (span.isEvent) {
this.logger.warn(`End event is not available on event spans`);
return;
}
originalEnd(options);
this.emitSpanEnded(span);
};
span.update = (options) => {
if (span.isEvent) {
this.logger.warn(`Update() is not available on event spans`);
return;
}
originalUpdate(options);
this.emitSpanUpdated(span);
};
}
// ============================================================================
// Utility Methods
// ============================================================================
/**
* Check if an AI trace should be sampled
*/
shouldSample(options) {
const { sampling } = this.config;
switch (sampling.type) {
case "always" /* ALWAYS */:
return true;
case "never" /* NEVER */:
return false;
case "ratio" /* RATIO */:
if (sampling.probability === void 0 || sampling.probability < 0 || sampling.probability > 1) {
this.logger.warn(
`Invalid sampling probability: ${sampling.probability}. Expected value between 0 and 1. Defaulting to no sampling.`
);
return false;
}
return Math.random() < sampling.probability;
case "custom" /* CUSTOM */:
return sampling.sampler(options);
default:
throw new Error(`Sampling strategy type not implemented: ${sampling.type}`);
}
}
/**
* Process a span through all processors
*/
processSpan(span) {
for (const processor of this.processors) {
if (!span) {
break;
}
try {
span = processor.process(span);
} catch (error) {
this.logger.error(`[AI Tracing] Processor error [name=${processor.name}]`, error);
}
}
return span;
}
// ============================================================================
// Event-driven Export Methods
// ============================================================================
getSpanForExport(span) {
if (!span.isValid) return void 0;
if (span.isInternal && !this.config.includeInternalSpans) return void 0;
const processedSpan = this.processSpan(span);
return processedSpan?.exportSpan(this.config.includeInternalSpans);
}
/**
* Emit a span started event
*/
emitSpanStarted(span) {
const exportedSpan = this.getSpanForExport(span);
if (exportedSpan) {
this.exportEvent({ type: "span_started" /* SPAN_STARTED */, exportedSpan }).catch((error) => {
this.logger.error("[AI Tracing] Failed to export span_started event", error);
});
}
}
/**
* Emit a span ended event (called automatically when spans end)
*/
emitSpanEnded(span) {
const exportedSpan = this.getSpanForExport(span);
if (exportedSpan) {
this.exportEvent({ type: "span_ended" /* SPAN_ENDED */, exportedSpan }).catch((error) => {
this.logger.error("[AI Tracing] Failed to export span_ended event", error);
});
}
}
/**
* Emit a span updated event
*/
emitSpanUpdated(span) {
const exportedSpan = this.getSpanForExport(span);
if (exportedSpan) {
this.exportEvent({ type: "span_updated" /* SPAN_UPDATED */, exportedSpan }).catch((error) => {
this.logger.error("[AI Tracing] Failed to export span_updated event", error);
});
}
}
/**
* Export tracing event through all exporters (realtime mode)
*/
async exportEvent(event) {
const exportPromises = this.exporters.map(async (exporter) => {
try {
if (exporter.exportEvent) {
await exporter.exportEvent(event);
this.logger.debug(`[AI Tracing] Event exported [exporter=${exporter.name}] [type=${event.type}]`);
}
} catch (error) {
this.logger.error(`[AI Tracing] Export error [exporter=${exporter.name}]`, error);
}
});
await Promise.allSettled(exportPromises);
}
// ============================================================================
// Lifecycle Management
// ============================================================================
/**
* Initialize AI tracing (called by Mastra during component registration)
*/
init() {
this.logger.debug(`[AI Tracing] Initialization started [name=${this.name}]`);
this.logger.info(`[AI Tracing] Initialized successfully [name=${this.name}]`);
}
/**
* Shutdown AI tracing and clean up resources
*/
async shutdown() {
this.logger.debug(`[AI Tracing] Shutdown started [name=${this.name}]`);
const shutdownPromises = [...this.exporters.map((e) => e.shutdown()), ...this.processors.map((p) => p.shutdown())];
await Promise.allSettled(shutdownPromises);
this.logger.info(`[AI Tracing] Shutdown completed [name=${this.name}]`);
}
};
// src/ai-tracing/tracers/default.ts
var DefaultAITracing = class extends BaseAITracing {
constructor(config) {
super(config);
}
createSpan(options) {
return new DefaultAISpan(options, this);
}
};
var CoreToolBuilder = class extends MastraBase {
originalTool;
options;
logType;
constructor(input) {
super({ name: "CoreToolBuilder" });
this.originalTool = input.originalTool;
this.options = input.options;
this.logType = input.logType;
}
// Helper to get parameters based on tool type
getParameters = () => {
if (isVercelTool(this.originalTool)) {
return this.originalTool.parameters ?? z.object({});
}
return this.originalTool.inputSchema ?? z.object({});
};
getOutputSchema = () => {
if ("outputSchema" in this.originalTool) return this.originalTool.outputSchema;
return null;
};
// For provider-defined tools, we need to include all required properties
buildProviderTool(tool) {
if ("type" in tool && tool.type === "provider-defined" && "id" in tool && typeof tool.id === "string" && tool.id.includes(".")) {
const parameters = this.getParameters();
const outputSchema = this.getOutputSchema();
return {
type: "provider-defined",
id: tool.id,
args: "args" in this.originalTool ? this.originalTool.args : {},
description: tool.description,
parameters: convertZodSchemaToAISDKSchema(parameters),
...outputSchema ? { outputSchema: convertZodSchemaToAISDKSchema(outputSchema) } : {},
execute: this.originalTool.execute ? this.createExecute(
this.originalTool,
{ ...this.options, description: this.originalTool.description },
this.logType
) : void 0
};
}
return void 0;
}
createLogMessageOptions({ agentName, toolName, type }) {
if (!agentName) {
return {
start: `Executing tool ${toolName}`,
error: `Failed tool execution`
};
}
const prefix = `[Agent:${agentName}]`;
const toolType = type === "toolset" ? "toolset" : "tool";
return {
start: `${prefix} - Executing ${toolType} ${toolName}`,
error: `${prefix} - Failed ${toolType} execution`
};
}
createExecute(tool, options, logType) {
const { logger, mastra: _mastra, memory: _memory, runtimeContext, ...rest } = options;
const { start, error } = this.createLogMessageOptions({
agentName: options.agentName,
toolName: options.name,
type: logType
});
const execFunction = async (args, execOptions) => {
const toolSpan = options.tracingContext?.currentSpan?.createChildSpan({
type: "tool_call" /* TOOL_CALL */,
name: `tool: '${options.name}'`,
input: args,
attributes: {
toolId: options.name,
toolDescription: options.description,
toolType: logType || "tool"
},
tracingPolicy: options.tracingPolicy
});
try {
let result;
if (isVercelTool(tool)) {
result = await tool?.execute?.(args, execOptions);
} else {
const wrappedMastra = options.mastra ? wrapMastra(options.mastra, { currentSpan: toolSpan }) : options.mastra;
result = await tool?.execute?.(
{
context: args,
threadId: options.threadId,
resourceId: options.resourceId,
mastra: wrappedMastra,
memory: options.memory,
runId: options.runId,
runtimeContext: options.runtimeContext ?? new RuntimeContext(),
writer: new ToolStream(
{
prefix: "tool",
callId: execOptions.toolCallId,
name: options.name,
runId: options.runId
},
options.writableStream || execOptions.writableStream
),
tracingContext: { currentSpan: toolSpan }
},
execOptions
);
}
toolSpan?.end({ output: result });
return result ?? void 0;
} catch (error2) {
toolSpan?.error({ error: error2 });
throw error2;
}
};
return async (args, execOptions) => {
let logger2 = options.logger || this.logger;
try {
logger2.debug(start, { ...rest, args });
const parameters = this.getParameters();
const { data, error: error2 } = validateToolInput(parameters, args, options.name);
if (error2) {
logger2.warn(`Tool input validation failed for '${options.name}'`, {
toolName: options.name,
errors: error2.validationErrors,
args
});
return error2;
}
args = data;
return await new Promise((resolve, reject) => {
setImmediate(async () => {
try {
const result = await execFunction(args, execOptions);
resolve(result);
} catch (err) {
reject(err);
}
});
});
} catch (err) {
const mastraError = new MastraError(
{
id: "TOOL_EXECUTION_FAILED",
domain: "TOOL" /* TOOL */,
category: "USER" /* USER */,
details: {
errorMessage: String(error),
argsJson: JSON.stringify(args),
model: rest.model?.modelId ?? ""
}
},
err
);
logger2.trackException(mastraError);
logger2.error(error, { ...rest, error: mastraError, args });
return mastraError;
}
};
}
buildV5() {
const builtTool = this.build();
if (!builtTool.parameters) {
throw new Error("Tool parameters are required");
}
return {
...builtTool,
inputSchema: builtTool.parameters,
onInputStart: "onInputStart" in this.originalTool ? this.originalTool.onInputStart : void 0,
onInputDelta: "onInputDelta" in this.originalTool ? this.originalTool.onInputDelta : void 0,
onInputAvailable: "onInputAvailable" in this.originalTool ? this.originalTool.onInputAvailable : void 0
};
}
build() {
const providerTool = this.buildProviderTool(this.originalTool);
if (providerTool) {
return providerTool;
}
const definition = {
type: "function",
description: this.originalTool.description,
parameters: this.getParameters(),
outputSchema: this.getOutputSchema(),
execute: this.originalTool.execute ? this.createExecute(
this.originalTool,
{ ...this.options, description: this.originalTool.description },
this.logType
) : void 0
};
const model = this.options.model;
const schemaCompatLayers = [];
if (model) {
const supportsStructuredOutputs = model.specificationVersion !== "v2" ? model.supportsStructuredOutputs ?? false : false;
const modelInfo = {
modelId: model.modelId,
supportsStructuredOutputs,
provider: model.provider
};
schemaCompatLayers.push(
new OpenAIReasoningSchemaCompatLayer(modelInfo),
new OpenAISchemaCompatLayer(modelInfo),
new GoogleSchemaCompatLayer(modelInfo),
new AnthropicSchemaCompatLayer(modelInfo),
new DeepSeekSchemaCompatLayer(modelInfo),
new MetaSchemaCompatLayer(modelInfo)
);
}
const processedSchema = applyCompatLayer({
schema: this.getParameters(),
compatLayers: schemaCompatLayers,
mode: "aiSdkSchema"
});
let processedOutputSchema;
if (this.getOutputSchema()) {
processedOutputSchema = applyCompatLayer({
schema: this.getOutputSchema(),
compatLayers: schemaCompatLayers,
mode: "aiSdkSchema"
});
}
return {
...definition,
id: "id" in this.originalTool ? this.originalTool.id : void 0,
parameters: processedSchema,
outputSchema: processedOutputSchema
};
}
};
// src/utils.ts
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
function deepMerge(target, source) {
const output = { ...target };
if (!source) return output;
Object.keys(source).forEach((key) => {
const targetValue = output[key];
const sourceValue = source[key];
if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
output[key] = sourceValue;
} else if (sourceValue instanceof Object && targetValue instanceof Object && !Array.isArray(sourceValue) && !Array.isArray(targetValue)) {
output[key] = deepMerge(targetValue, sourceValue);
} else if (sourceValue !== void 0) {
output[key] = sourceValue;
}
});
return output;
}
function generateEmptyFromSchema(schema) {
try {
const parsedSchema = JSON.parse(schema);
if (!parsedSchema || parsedSchema.type !== "object" || !parsedSchema.properties) return {};
const obj = {};
const TYPE_DEFAULTS = {
string: "",
array: [],
object: {},
number: 0,
integer: 0,
boolean: false
};
for (const [key, prop] of Object.entries(parsedSchema.properties)) {
obj[key] = TYPE_DEFAULTS[prop.type] ?? null;
}
return obj;
} catch {
return {};
}
}
async function* maskStreamTags(stream, tag, options = {}) {
const { onStart, onEnd, onMask } = options;
const openTag = `<${tag}>`;
const closeTag = `</${tag}>`;
let buffer = "";
let fullContent = "";
let isMasking = false;
let isBuffering = false;
const trimOutsideDelimiter = (text, delimiter, trim) => {
if (!text.includes(delimiter)) {
return text;
}
const parts = text.split(delimiter);
if (trim === `before-start`) {
return `${delimiter}${parts[1]}`;
}
return `${parts[0]}${delimiter}`;
};
const startsWith = (text, pattern) => {
if (pattern.includes(openTag.substring(0, 3))) {
pattern = trimOutsideDelimiter(pattern, `<`, `before-start`);
}
return text.trim().startsWith(pattern.trim());
};
for await (const chunk of stream) {
fullContent += chunk;
if (isBuffering) buffer += chunk;
const chunkHasTag = startsWith(chunk, openTag);
const bufferHasTag = !chunkHasTag && isBuffering && startsWith(openTag, buffer);
let toYieldBeforeMaskedStartTag = ``;
if (!isMasking && (chunkHasTag || bufferHasTag)) {
isMasking = true;
isBuffering = false;
const taggedTextToMask = trimOutsideDelimiter(buffer, `<`, `before-start`);
if (taggedTextToMask !== buffer.trim()) {
toYieldBeforeMaskedStartTag = buffer.replace(taggedTextToMask, ``);
}
buffer = "";
onStart?.();
}
if (!isMasking && !isBuffering && startsWith(openTag, chunk) && chunk.trim() !== "") {
isBuffering = true;
buffer += chunk;
continue;
}
if (isBuffering && buffer && !startsWith(openTag, buffer)) {
yield buffer;
buffer = "";
isBuffering = false;
continue;
}
if (isMasking && fullContent.includes(closeTag)) {
onMask?.(chunk);
onEnd?.();
isMasking = false;
const lastFullContent = fullContent;
fullContent = ``;
const textUntilEndTag = trimOutsideDelimiter(lastFullContent, closeTag, "after-end");
if (textUntilEndTag !== lastFullContent) {
yield lastFullContent.replace(textUntilEndTag, ``);
}
continue;
}
if (isMasking) {
onMask?.(chunk);
if (toYieldBeforeMaskedStartTag) {
yield toYieldBeforeMaskedStartTag;
}
continue;
}
yield chunk;
}
}
function resolveSerializedZodOutput(schema) {
return Function("z", `"use strict";return (${schema});`)(z);
}
function isZodType(value) {
return typeof value === "object" && value !== null && "_def" in value && "parse" in value && typeof value.parse === "function" && "safeParse" in value && typeof value.safeParse === "function";
}
function createDeterministicId(input) {
return createHash("sha256").update(input).digest("hex").slice(0, 8);
}
function setVercelToolProperties(tool) {
const inputSchema = convertVercelToolParameters(tool);
const toolId = !("id" in tool) ? tool.description ? `tool-${createDeterministicId(tool.description)}` : `tool-${Math.random().toString(36).substring(2, 9)}` : tool.id;
return {
...tool,
id: toolId,
inputSchema
};
}
function ensureToolProperties(tools) {
const toolsWithProperties = Object.keys(tools).reduce((acc, key) => {
const tool = tools?.[key];
if (tool) {
if (isVercelTool(tool)) {
acc[key] = setVercelToolProperties(tool);
} else {
acc[key] = tool;
}
}
return acc;
}, {});
return toolsWithProperties;
}
function convertVercelToolParameters(tool) {
const schema = tool.parameters ?? z.object({});
return isZodType(schema) ? schema : resolveSerializedZodOutput(jsonSchemaToZod(schema));
}
function makeCoreTool(originalTool, options, logType) {
return new CoreToolBuilder({ originalTool, options, logType }).build();
}
function makeCoreToolV5(originalTool, options, logType) {
return new CoreToolBuilder({ originalTool, options, logType }).buildV5();
}
function createMastraProxy({ mastra, logger }) {
return new Proxy(mastra, {
get(target, prop) {
const hasProp = Reflect.has(target, prop);
if (hasProp) {
const value = Reflect.get(target, prop);
const isFunction = typeof value === "function";
if (isFunction) {
return value.bind(target);
}
return value;
}
if (prop === "logger") {
logger.warn(`Please use 'getLogger' instead, logger is deprecated`);
return Reflect.apply(target.getLogger, target, []);
}
if (prop === "telemetry") {
logger.warn(`Please use 'getTelemetry' instead, telemetry is deprecated`);
return Reflect.apply(target.getTelemetry, target, []);
}
if (prop === "storage") {
logger.warn(`Please use 'getStorage' instead, storage is deprecated`);
return Reflect.get(target, "storage");
}
if (prop === "agents") {
logger.warn(`Please use 'getAgents' instead, agents is deprecated`);
return Reflect.apply(target.getAgents, target, []);
}
if (prop === "tts") {
logger.warn(`Please use 'getTTS' instead, tts is deprecated`);
return Reflect.apply(target.getTTS, target, []);
}
if (prop === "vectors") {
logger.warn(`Please use 'getVectors' instead, vectors is deprecated`);
return Reflect.apply(target.getVectors, target, []);
}
if (prop === "memory") {
logger.warn(`Please use 'getMemory' instead, memory is deprecated`);
return Reflect.get(target, "memory");
}
return Reflect.get(target, prop);
}
});
}
function checkEvalStorageFields(traceObject, logger) {
const missingFields = [];
if (!traceObject.input) missingFields.push("input");
if (!traceObject.output) missingFields.push("output");
if (!traceObject.agentName) missingFields.push("agent_name");
if (!traceObject.metricName) missingFields.push("metric_name");
if (!traceObject.instructions) missingFields.push("instructions");
if (!traceObject.globalRunId) missingFields.push("global_run_id");
if (!traceObject.runId) missingFields.push("run_id");
if (missingFields.length > 0) {
if (logger) {
logger.warn("Skipping evaluation storage due to missing required fields", {
missingFields,
runId: traceObject.runId,
agentName: traceObject.agentName
});
} else {
console.warn("Skipping evaluation storage due to missing required fields", {
missingFields,
runId: traceObject.runId,
agentName: traceObject.agentName
});
}
return false;
}
return true;
}
function detectSingleMessageCharacteristics(message) {
if (typeof message === "object" && message !== null && (message.role === "function" || // UI-only role
message.role === "data" || // UI-only role
"toolInvocations" in message || // UI-specific field
"parts" in message || // UI-specific field
"experimental_attachments" in message)) {
return "has-ui-specific-parts";
} else if (typeof message === "object" && message !== null && "content" in message && (Array.isArray(message.content) || // Core messages can have array content
"experimental_providerMetadata" in message || "providerOptions" in message)) {
return "has-core-specific-parts";
} else if (typeof message === "object" && message !== null && "role" in message && "content" in message && typeof message.content === "string" && ["system", "user", "assistant", "tool"].includes(message.role)) {
return "message";
} else {
return "other";
}
}
function isUiMessage(message) {
return detectSingleMessageCharacteristics(message) === `has-ui-specific-parts`;
}
function isCoreMessage(message) {
return [`has-core-specific-parts`, `message`].includes(detectSingleMessageCharacteristics(message));
}
var SQL_IDENTIFIER_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
function parseSqlIdentifier(name, kind = "identifier") {
if (!SQL_IDENTIFIER_PATTERN.test(name) || name.length > 63) {
throw new Error(
`Invalid ${kind}: ${name}. Must start with a letter or underscore, contain only letters, numbers, or underscores, and be at most 63 characters long.`
);
}
return name;
}
function parseFieldKey(key) {
if (!key) throw new Error("Field key cannot be empty");
const segments = key.split(".");
for (const segment of segments) {
if (!SQL_IDENTIFIER_PATTERN.test(segment) || segment.length > 63) {
throw new Error(`Invalid field key segment: ${segment} in ${key}`);
}
}
return key;
}
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
let retryCount = 0;
let lastError = null;
while (retryCount < maxRetries) {
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`Request failed with status: ${response.status} ${response.statusText}`);
}
return response;
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
retryCount++;
if (retryCount >= maxRetries) {
break;
}
const delay2 = Math.min(1e3 * Math.pow(2, retryCount) * 1e3, 1e4);
await new Promise((resolve) => setTimeout(resolve, delay2));
}
}
throw lastError || new Error("Request failed after multiple retry attempts");
}
// src/ai-tracing/exporters/cloud.ts
var CloudExporter = class {
name = "mastra-cloud-ai-tracing-exporter";
config;
buffer;
flushTimer = null;
logger;
isDisabled = false;
constructor(config = {}) {
this.logger = config.logger ?? new ConsoleLogger({ level: LogLevel.INFO });
const accessToken = config.accessToken ?? process.env.MASTRA_CLOUD_ACCESS_TOKEN;
if (!accessToken) {
this.logger.warn(
"CloudExporter disabled: MASTRA_CLOUD_ACCESS_TOKEN environment variable not set. \u{1F680} Sign up for Mastra Cloud at https://cloud.mastra.ai to see your AI traces online and obtain your access token."
);
this.isDisabled = true;
}
const endpoint = config.endpoint ?? process.env.MASTRA_CLOUD_AI_TRACES_ENDPOINT ?? "https://api.mastra.ai/ai/spans/publish";
this.config = {
maxBatchSize: config.maxBatchSize ?? 1e3,
maxBatchWaitMs: config.maxBatchWaitMs ?? 5e3,
maxRetries: config.maxRetries ?? 3,
accessToken: accessToken || "",
// Empty string if no token
endpoint,
logger: this.logger
};
this.buffer = {
spans: [],
totalSize: 0
};
}
async exportEvent(event) {
if (this.isDisabled) {
return;
}
if (event.type !== "span_ended" /* SPAN_ENDED */) {
return;
}
this.addToBuffer(event);
if (this.shouldFlush()) {
this.flush().catch((error) => {
this.logger.error("Batch flush failed", {
error: error instanceof Error ? error.message : String(error)
});
});
} else if (this.buffer.totalSize === 1) {
this.scheduleFlush();
}
}
addToBuffer(event) {
if (this.buffer.totalSize === 0) {
this.buffer.firstEventTime = /* @__PURE__ */ new Date();
}
const spanRecord = this.formatSpan(event.exportedSpan);
this.buffer.spans.push(spanRecord);
this.buffer.totalSize++;
}
formatSpan(span) {
const spanRecord = {
traceId: span.traceId,
spanId: span.id,
parentSpanId: span.parentSpanId ?? null,
name: span.name,
spanType: span.type,
attributes: span.attributes ?? null,
metadata: span.metadata ?? null,
startedAt: span.startTime,
endedAt: span.endTime ?? null,
input: span.input ?? null,
output: span.output ?? null,
error: span.errorInfo,
isEvent: span.isEvent,
createdAt: /* @__PURE__ */ new Date(),
updatedAt: null
};
return spanRecord;
}
shouldFlush() {
if (this.buffer.totalSize >= this.config.maxBatchSize) {
return true;
}
if (this.buffer.firstEventTime && this.buffer.totalSize > 0) {
const elapsed = Date.now() - this.buffer.firstEventTime.getTime();
if (elapsed >= this.config.maxBatchWaitMs) {
return true;
}
}
return false;
}
scheduleFlush() {
if (this.flushTimer) {
clearTimeout(this.flushTimer);
}
this.flushTimer = setTimeout(() => {
this.flush().catch((error) => {
const mastraError = new MastraError(
{
id: `CLOUD_AI_TRACING_FAILED_TO_SCHEDULE_FLUSH`,
domain: "MASTRA_OBSERVABILITY" /* MASTRA_OBSERVABILITY */,
category: "USER" /* USER */
},
error
);
this.logger.trackException(mastraError);
this.logger.error("Scheduled flush failed", mastraError);
});
}, this.config.maxBatchWaitMs);
}
async flush() {
if (this.flushTimer) {
clearTimeout(this.flushTimer);
this.flushTimer = null;
}
if (this.buffer.totalSize === 0) {
return;
}
const startTime = Date.now();
const spansCopy = [...this.buffer.spans];
const flushReason = this.buffer.totalSize >= this.config.maxBatchSize ? "size" : "time";
this.resetBuffer();
try {
await this.batchUpload(spansCopy);
const elapsed = Date.now() - startTime;
this.logger.debug("Batch flushed successfully", {
batchSize: spansCopy.length,
flushReason,
durationMs: elapsed
});
} catch (error) {
const mastraError = new MastraError(
{
id: `CLOUD_AI_TRACING_FAILED_TO_BATCH_UPLOAD`,
domain: "MASTRA_OBSERVABILITY" /* MASTRA_OBSERVABILITY */,
category: "USER" /* USER */,
details: {
droppedBatchSize: spansCopy.length
}
},
error
);
this.logger.trackException(mastraError);
this.logger.error("Batch upload failed after all retries, dropping batch", mastraError);
}
}
/**
* Uploads spans to cloud API using fetchWithRetry for all retry logic
*/
async batchUpload(spans) {
const url = `${this.config.endpoint}`;
const headers = {
Authorization: `Bearer ${this.config.accessToken}`,
"Content-Type": "application/json"
};
const options = {
method: "POST",
headers,
body: JSON.stringify({ spans })
};
await fetchWithRetry(url, options, this.config.maxRetries);
}
resetBuffer() {
this.buffer.spans = [];
this.buffer.firstEventTime = void 0;
this.buffer.totalSize = 0;
}
async shutdown() {
if (this.isDisabled) {
return;
}
if (this.flushTimer) {
clearTimeout(this.flushTimer);
this.flushTimer = null;
}
if (this.buffer.totalSize > 0) {
this.logger.info("Flushing remaining events on shutdown", {
remainingEvents: this.buffer.totalSize
});
try {
await this.flush();
} catch (error) {
const mastraError = new MastraError(
{
id: `CLOUD_AI_TRACING_FAILED_TO_FLUSH_REMAINING_EVENTS_DURING_SHUTDOWN`,
domain: "MASTRA_OBSERVABILITY" /* MASTRA_OBSERVABILITY */,
category: "USER" /* USER */,
details: {
remainingEvents: this.buffer.totalSize
}
},
error
);
this.logger.trackException(mastraError);
this.logger.error("Failed to flush remaining events during shutdown", mastraError);
}
}
this.logger.info("CloudExporter shutdown complete");
}
};
// src/ai-tracing/exporters/console.ts
var ConsoleExporter = class {
name = "tracing-console-exporter";
logger;
constructor(logger) {
if (logger) {
this.logger = logger;
} else {
this.logger = new ConsoleLogger({ level: LogLevel.INFO });
}
}
async exportEvent(event) {
const span = event.exportedSpan;
const formatAttributes = (attributes) => {
try {
return JSON.stringify(attributes, null, 2);
} catch (error) {
const errMsg = error instanceof Error ? error.message : "Unknown formatting error";
return `[Unable to serialize attributes: ${errMsg}]`;
}
};
const formatDuration = (startTime, endTime) => {
if (!endTime) return "N/A";
const duration = endTime.getTime() - startTime.getTime();
return `${duration}ms`;
};
switch (event.type) {
case "span_started" /* SPAN_STARTED */:
this.logger.info(`\u{1F680} SPAN_STARTED`);
this.logger.info(` Type: ${span.type}`);
this.logger.info(` Name: ${span.name}`);
this.logger.info(` ID: ${span.id}`);
this.logger.info(` Trace ID: ${span.traceId}`);
if (span.input !== void 0) {
this.logger.info(` Input: ${formatAttributes(span.input)}`);
}
this.logger.info(` Attributes: ${formatAttributes(span.attributes)}`);
this.logger.info("\u2500".repeat(80));
break;
case "span_ended" /* SPAN_ENDED */:
const duration = formatDuration(span.startTime, span.endTime);
this.logger.info(`\u2705 SPAN_ENDED`);
this.logger.info(` Type: ${span.type}`);
this.logger.info(` Name: ${span.name}`);
this.logger.info(` ID: ${span.id}`);
this.logger.info(` Duration: ${duration}`);
this.logger.info(` Trace ID: ${span.traceId}`);
if (span.input !== void 0) {
this.logger.info(` Input: ${formatAttributes(span.input)}`);
}
if (span.output !== void 0) {
this.logger.info(` Output: ${formatAttributes(span.output)}`);
}
if (span.errorInfo) {
this.logger.info(` Error: ${formatAttributes(span.errorInfo)}`);
}
this.logger.info(` Attributes: ${formatAttributes(span.attributes)}`);
this.logger.info("\u2500".repeat(80));
break;
case "span_updated" /* SPAN_UPDATED */:
this.logger.info(`\u{1F4DD} SPAN_UPDATED`);
this.logger.info(` Type: ${span.type}`);
this.logger.info(` Name: ${span.name}`);
this.logger.info(` ID: ${span.id}`);
this.logger.info(` Trace ID: ${span.traceId}`);
if (span.input !== void 0) {
this.logger.info(` Input: ${formatAttributes(span.input)}`);
}
if (span.output !== void 0) {
this.logger.info(` Output: ${formatAttributes(span.output)}`);
}
if (span.errorInfo) {
this.logger.info(` Error: ${formatAttributes(span.errorInfo)}`);
}
this.logger.info(` Updated Attributes: ${formatAttributes(span.attributes)}`);
this.logger.info("\u2500".repeat(80));
break;
default:
this.logger.warn(`Tracing event type not implemented: ${event.type}`);
}
}
async shutdown() {
this.logger.info("ConsoleExporter shutdown");
}
};
// src/ai-tracing/exporters/default.ts
function resolveStrategy(userConfig, storage, logger) {
if (userConfig.strategy && userConfig.strategy !== "auto") {
const hints = storage.aiTracingStrategy;
if (hints.supported.includes(userConfig.strategy)) {
return userConfig.strategy;
}
logger.warn("User-specified AI tracing strategy not supported by storage adapter, falling back to auto-selection", {
userStrategy: userConfig.strategy,
storageAdapter: storage.constructor.name,
supportedStrategies: hints.supported,
fallbackStrategy: hints.preferred
});
}
return storage.aiTracingStrategy.preferred;
}
var DefaultExporter = class {
name = "tracing-default-exporter";
logger;
mastra = null;
config;
resolvedStrategy;
buffer;
flushTimer = null;
// Track all spans that have been created, persists across flushes
allCreatedSpans = /* @__PURE__ */ new Set();
constructor(config = {}, logger) {
if (logger) {
this.logger = logger;
} else {
this.logger = new ConsoleLogger({ level: LogLevel.INFO });
}
this.config = {
maxBatchSize: config.maxBatchSize ?? 1e3,
maxBufferSize: config.maxBufferSize ?? 1e4,
maxBatchWaitMs: config.maxBatchWaitMs ?? 5e3,
maxRetries: config.maxRetries ?? 4,
retryDelayMs: config.retryDelayMs ?? 500,
strategy: config.strategy ?? "auto"
};
this.buffer = {
creates: [],
updates: [],
insertOnly: [],
seenSpans: /* @__PURE__ */ new Set(),
spanSequences: /* @__PURE__ */ new Map(),
completedSpans: /* @__PURE__ */ new Set(),
outOfOrderCount: 0,
totalSize: 0
};
this.resolvedStrategy = "batch-with-updates";
}
strategyInitialized = false;
/**
* Register the Mastra instance (called after Mastra construction is complete)
*/
__registerMastra(mastra) {
this.mastra = mastra;
}
/**
* Initialize the exporter (called after all dependencies are ready)
*/
init() {
if (!this.mastra) {
throw new Error("DefaultExporter: init() called before __registerMastra()");
}
const storage = this.mastra.getStorage();
if (!storage) {
this.logger.warn("DefaultExporter disabled: Storage not available. Traces will not be persisted.");
return;
}
this.initializeStrategy(storage);
}
/**
* Initialize the resolved strategy once storage is available
*/
initializeStrategy(storage) {
if (this.strategyInitialized) return;
this.resolvedStrategy = resolveStrategy(this.config, storage, this.logger);
this.strategyInitialized = true;
this.logger.info("AI tracing exporter initialized", {
strategy: this.resolvedStrategy,
source: this.config.strategy !== "auto" ? "user" : "auto",
storageAdapter: storage.constructor.name,
maxBatchSize: this.config.maxBatchSize,
maxBatchWaitMs: this.config.maxBatchWaitMs
});
}
/**
* Builds a unique span key for tracking
*/
buildSpanKey(traceId, spanId) {
return `${traceId}:${spanId}`;
}
/**
* Gets the next sequence number for a span
*/
getNextSequence(spanKey) {
const current = this.buffer.spanSequences.get(spanKey) || 0;
const next = current + 1;
this.buffer.spanSequences.set(spanKey, next);
return next;
}
/**
* Handles out-of-order span updates by logging and skipping
*/
handleOutOfOrderUpdate(event) {
this.logger.warn("Out-of-order span update detected - skipping event", {
spanId: event.exportedSpan.id,
traceId: event.exportedSpan.traceId,
spanName: event.exportedSpan.name,
eventType: event.type
});
}
/**
* Adds an event to the appropriate buffer based on strategy
*/
addToBuffer(event) {
const spanKey = this.buildSpanKey(event.exportedSpan.traceId, event.exportedSpan.id);
if (this.buffer.totalSize === 0) {