UNPKG

@mastra/core

Version:

Mastra is a framework for building AI-powered applications and agents with a modern TypeScript stack.

1,487 lines (1,486 loc) 101 kB
import { OrchestrationWorker, BackgroundTaskWorker, SchedulerWorker } from './chunk-YX7UTM3D.js'; import { computeNextFireAt, WorkflowEventProcessor } from './chunk-NNKKOMEN.js'; import { isToolLoopAgentLike, toolLoopAgentToMastraAgent } from './chunk-L4BINS4U.js'; import { initContextStorage } from './chunk-7F7LYRRK.js'; import { DatasetsManager } from './chunk-PAAKN4O7.js'; import { createOnScorerHook } from './chunk-RTJNKJR7.js'; import { agentThreadStreamRuntime, augmentWithInit, AgentChannels, isDurableAgentLike } from './chunk-AM3IOVFX.js'; import { DualLogger, noopLogger } from './chunk-CFNCK3E2.js'; import { EventEmitterPubSub } from './chunk-SQDHPWBX.js'; import { registerHook } from './chunk-L54GIUCB.js'; import { defaultGateways } from './chunk-7SS36SRG.js'; import { InMemoryServerCache } from './chunk-JZ7Q75IW.js'; import { NoOpObservability, noOpLoggerContext, noOpMetricsContext } from './chunk-4ZCIE3Q5.js'; import { BackgroundTaskManager } from './chunk-QOKJTCIS.js'; import { ConsoleLogger, LogLevel } from './chunk-DBBWTK24.js'; import { normalizeToolPayloadTransformPolicy } from './chunk-GNP47JBD.js'; import { MastraError } from './chunk-FJEVLHJT.js'; import { randomUUID } from 'crypto'; function createUndefinedPrimitiveError(type, value, key) { const typeLabel = type === "mcp-server" ? "MCP server" : type; const errorId = `MASTRA_ADD_${type.toUpperCase().replace("-", "_")}_UNDEFINED`; return new MastraError({ id: errorId, domain: "MASTRA" /* MASTRA */, category: "USER" /* USER */, text: `Cannot add ${typeLabel}: ${typeLabel} is ${value === null ? "null" : "undefined"}. This may occur if config was spread ({ ...config }) and the original object had getters or non-enumerable properties.`, details: { status: 400, ...key && { key } } }); } function targetsEqual(a, b) { if (a === b) return true; if (!a) return false; return JSON.stringify(a) === JSON.stringify(b); } function collectWorkflowScheduleConfigs(workflow) { const w = workflow; if (typeof w.getScheduleConfigs === "function") { return w.getScheduleConfigs() ?? []; } if (typeof w.getScheduleConfig === "function") { const cfg = w.getScheduleConfig(); if (!cfg) return []; return Array.isArray(cfg) ? cfg : [cfg]; } return []; } function declarativeScheduleRowId(workflowId, scheduleId) { const encodedWorkflow = encodeURIComponent(workflowId); if (scheduleId === void 0) return `wf_${encodedWorkflow}`; return `wf_${encodedWorkflow}__${encodeURIComponent(scheduleId)}`; } function ownerWorkflowIdForRow(rowId, byWorkflow) { for (const workflowId of byWorkflow.keys()) { const prefix = `wf_${encodeURIComponent(workflowId)}`; if (rowId === prefix || rowId.startsWith(`${prefix}__`)) { return workflowId; } } return void 0; } function ownerWorkflowIdFromRowId(rowId) { if (!rowId.startsWith("wf_")) return void 0; const rest = rowId.slice("wf_".length); const sep = rest.indexOf("__"); const encoded = sep === -1 ? rest : rest.slice(0, sep); if (!encoded) return void 0; try { return decodeURIComponent(encoded); } catch { return void 0; } } function metadataEqual(a, b) { const aNorm = a ?? void 0; const bNorm = b ?? void 0; if (aNorm === bNorm) return true; if (!aNorm || !bNorm) return false; return JSON.stringify(aNorm) === JSON.stringify(bNorm); } var Mastra = class { #vectors; #agents; #logger; #workflows; #observability; #tts; #deployer; #serverMiddleware = []; #storage; #scorers; #tools; #processors; #processorConfigurations = /* @__PURE__ */ new Map(); #memory; #workspace; #workspaces = {}; #server; #serverAdapter; #mcpServers; #bundler; #idGenerator; #pubsub; #backgroundTaskConfig; #backgroundTaskManager; #schedulerConfig; /** * Tracks whether any registered workflow has declared a `schedule` config. * Used as a fast short-circuit so users without scheduled workflows pay * zero cost beyond a boolean check. */ #hasScheduledWorkflow = false; #gateways; #channels; #environment; #toolPayloadTransform; #workers = []; #workerFilter; // Lazily-constructed processor used by handleWorkflowEvent(). Shared between // pull-mode workers (OrchestrationWorker) and push-mode entry points // (in-process EventEmitter listener, the /api/workers/events HTTP route). #workflowEventProcessor; // Callback registered against the pubsub when running in push mode so we can // unsubscribe it cleanly during stopWorkers(). #pushSubscription; // Tracks (topic, listener) pairs registered against the pubsub on behalf of // user-defined event listeners during startWorkers(). Used to make // startWorkers()/stopWorkers() idempotent — a second startWorkers() call // must not double-subscribe the same listener. #userEventSubscriptions = []; #events = {}; #internalMastraWorkflows = {}; // Server cache for temporary persistence and durable agent resumable streams #serverCache; // Cache for stored agents to allow in-memory modifications (like model changes) to persist across requests #storedAgentsCache = /* @__PURE__ */ new Map(); // Cache for stored scorers to allow in-memory modifications to persist across requests #storedScorersCache = /* @__PURE__ */ new Map(); // Registry for prompt blocks (stored or code-defined) #promptBlocks = {}; // Editor instance for handling agent instantiation and configuration #editor; #datasets; // Global version overrides for primitives (agents, etc.) #versions; get pubsub() { return this.#pubsub; } get agentThreadStreamRuntime() { return agentThreadStreamRuntime; } get workers() { return this.#workers; } getWorker(name) { return this.#workers.find((w) => w.name === name); } get backgroundTaskManager() { return this.#backgroundTaskManager; } /** * Returns the workflow scheduler owned by the SchedulerWorker, * or undefined if the scheduler is not enabled / not yet started. * * The scheduler is created when `startWorkers()` initializes the * SchedulerWorker (guarded by `#shouldEnableScheduler()`). Use it * to create, pause, resume, or delete schedules imperatively. */ get scheduler() { return this.#findSchedulerWorker()?.scheduler; } get datasets() { if (!this.#datasets) { this.#datasets = new DatasetsManager(this); } return this.#datasets; } /** * Gets the currently configured ID generator function. * * @example * ```typescript * const mastra = new Mastra({ * idGenerator: context => * context?.idType === 'message' && context.threadId * ? `msg-${context.threadId}-${Date.now()}` * : `custom-${Date.now()}` * }); * const generator = mastra.getIdGenerator(); * console.log(generator?.({ idType: 'message', threadId: 'thread-123' })); // \"msg-thread-123-1234567890\" * ``` */ getIdGenerator() { return this.#idGenerator; } /** * Gets the currently configured editor instance. * The editor is responsible for handling agent instantiation and configuration. * * @example * ```typescript * const mastra = new Mastra({ * editor: new MastraEditor({ logger }) * }); * const editor = mastra.getEditor(); * ``` */ getEditor() { return this.#editor; } /** * Gets a registered channel provider by its key. * * @example * ```typescript * import { SlackProvider } from '@mastra/slack'; * const slack = mastra.getChannelProvider<SlackProvider>('slack'); * ``` */ getChannelProvider(key) { return this.#channels?.[key]; } /** * Gets all registered channel providers. */ getChannelProviders() { return this.#channels; } /** * Shorthand getter for platform channels. * Usage: `mastra.channels.slack.connect(agentId)` */ get channels() { return this.#channels ?? {}; } /** * Returns the global version overrides configured on this Mastra instance. * These are used as defaults when resolving sub-agent versions during delegation. */ getVersionOverrides() { return this.#versions; } /** * Returns the deployment environment name configured on this Mastra instance, * falling back to `process.env.NODE_ENV` when unset, or `undefined` if neither * is provided. * * Observability automatically reads this and attaches it to all signals so * consumers can filter by environment without passing * `tracingOptions.metadata.environment` on each call. */ getEnvironment() { return this.#environment; } getToolPayloadTransform() { return this.#toolPayloadTransform; } /** * Gets the stored agents cache * @internal */ getStoredAgentCache() { return this.#storedAgentsCache; } /** * Gets the stored scorers cache * @internal */ getStoredScorerCache() { return this.#storedScorersCache; } /** * Generates a unique identifier using the configured generator or defaults to `crypto.randomUUID()`. * * This method is used internally by Mastra for creating unique IDs for various entities * like workflow runs, agent conversations, and other resources that need unique identification. * * @param context - Optional context information about what type of ID is being generated * and where it's being requested from. This allows custom ID generators * to create deterministic IDs based on context. * * @throws {MastraError} When the custom ID generator returns an empty string * * @example * ```typescript * const mastra = new Mastra(); * const id = mastra.generateId(); * console.log(id); // "550e8400-e29b-41d4-a716-446655440000" * * // With context for deterministic IDs * const messageId = mastra.generateId({ * idType: 'message', * source: 'agent', * threadId: 'thread-123' * }); * ``` */ generateId(context) { if (this.#idGenerator) { const id = this.#idGenerator(context); if (!id) { const error = new MastraError({ id: "MASTRA_ID_GENERATOR_RETURNED_EMPTY_STRING", domain: "MASTRA" /* MASTRA */, category: "USER" /* USER */, text: "ID generator returned an empty string, which is not allowed" }); this.#logger?.trackException(error); throw error; } return id; } return randomUUID(); } /** * Sets a custom ID generator function for creating unique identifiers. * * The ID generator function will be used by `generateId()` instead of the default * `crypto.randomUUID()`. This is useful for creating application-specific ID formats * or integrating with existing ID generation systems. The function receives * optional context about what is requesting the ID. * * @example * ```typescript * const mastra = new Mastra(); * mastra.setIdGenerator(context => * context?.idType === 'run' && context.entityId * ? `run-${context.entityId}-${Date.now()}` * : `custom-${Date.now()}` * ); * const id = mastra.generateId({ idType: 'run', entityId: 'agent-123' }); * console.log(id); // "run-agent-123-1234567890" * ``` */ setIdGenerator(idGenerator) { this.#idGenerator = idGenerator; } /** * Sets the server configuration for this Mastra instance. * * @param server - The server configuration object * * @example * ```typescript * mastra.setServer({ ...mastra.getServer(), auth: new MastraAuthWorkos() }); * ``` */ setServer(server) { this.#server = server; } /** * Registers an exporter on the default observability instance. * * If the current observability is a no-op (user didn't configure any), it is * first replaced with the provided entrypoint and the instance is registered * as default. If a real observability entrypoint already exists, the exporter * is added directly to the existing default instance. * * @param exporter - The exporter to register (e.g. a MastraPlatformExporter) * @param instance - An ObservabilityInstance pre-configured with the exporter, used as default when bootstrapping * @param entrypoint - A real ObservabilityEntrypoint to bootstrap if the current one is a no-op */ registerExporter(exporter, instance, entrypoint) { if (this.#observability instanceof NoOpObservability) { this.#observability = entrypoint; this.#observability.setLogger({ logger: this.#logger }); this.#observability.setMastraContext({ mastra: this }); this.#observability.registerInstance("default", instance, true); } const defaultInstance = this.#observability.getDefaultInstance(); if (defaultInstance?.registerExporter) { defaultInstance.registerExporter(exporter); } } /** * Creates a new Mastra instance with the provided configuration. * * The constructor initializes all the components specified in the config, sets up * internal systems like logging and observability, and registers components with each other. * * @example * ```typescript * const mastra = new Mastra({ * agents: { * assistant: new Agent({ * id: 'assistant', * name: 'Assistant', * instructions: 'You are a helpful assistant', * model: 'openai/gpt-5' * }) * }, * storage: new PostgresStore({ * connectionString: process.env.DATABASE_URL * }), * logger: new PinoLogger({ name: 'MyApp' }), * observability: new Observability({ * configs: { default: { serviceName: 'mastra', exporters: [new MastraStorageExporter()] } }, * }), * }); * ``` */ constructor(config) { initContextStorage(); this.#serverCache = config?.cache ?? new InMemoryServerCache(); this.#editor = config?.editor; if (this.#editor && typeof this.#editor.registerWithMastra === "function") { this.#editor.registerWithMastra(this); } this.#versions = config?.versions; this.#environment = config?.environment ?? process.env.NODE_ENV; this.#toolPayloadTransform = normalizeToolPayloadTransformPolicy( config?.transform ?? config?.toolPayloadProjection ); if (config?.pubsub) { this.#pubsub = config.pubsub; } else { this.#pubsub = new EventEmitterPubSub(); } this.#events = {}; for (const topic in config?.events ?? {}) { if (!Array.isArray(config?.events?.[topic])) { this.#events[topic] = [config?.events?.[topic]]; } else { this.#events[topic] = config?.events?.[topic] ?? []; } } const rawWorkersEnv = process.env.MASTRA_WORKERS; let workersOption; if (rawWorkersEnv === "false") { workersOption = false; } else { workersOption = config?.workers; if (rawWorkersEnv && rawWorkersEnv !== "false") { const names = rawWorkersEnv.split(",").map((s) => s.trim()).filter(Boolean); if (names.length > 0) { this.#workerFilter = new Set(names); } } } if (workersOption === false) ; else if (Array.isArray(workersOption)) { this.#workers = workersOption; for (const w of this.#workers) { w.__registerMastra(this); } } else { const pubsubModes = this.#pubsub.supportedModes ?? ["pull"]; const defaultWorkers = []; if (pubsubModes.includes("pull")) { defaultWorkers.push(new OrchestrationWorker()); } if (config?.backgroundTasks?.enabled) { defaultWorkers.push(new BackgroundTaskWorker(config.backgroundTasks)); } this.#workers = defaultWorkers; for (const w of this.#workers) { w.__registerMastra(this); } } let logger; if (config?.logger === false) { logger = noopLogger; } else { if (config?.logger) { logger = config.logger; } else { const levelOnEnv = process.env.NODE_ENV === "production" && process.env.MASTRA_DEV !== "true" ? LogLevel.WARN : LogLevel.INFO; logger = new ConsoleLogger({ name: "Mastra", level: levelOnEnv }); } } this.#logger = logger; this.#idGenerator = config?.idGenerator; let storage = config?.storage; if (storage) { storage = augmentWithInit(storage); } if (config?.observability) { if (typeof config.observability.getDefaultInstance === "function") { this.#observability = config.observability; this.#observability.setLogger({ logger: this.#logger }); } else { this.#logger?.warn( 'Observability configuration error: Expected an Observability instance, but received a config object. Import and instantiate: import { Observability, MastraStorageExporter } from "@mastra/observability"; then pass: observability: new Observability({ configs: { default: { serviceName: "mastra", exporters: [new MastraStorageExporter()] } } }). Observability has been disabled.' ); this.#observability = new NoOpObservability(); } } else { this.#observability = new NoOpObservability(); } const dualLogger = new DualLogger(this.#logger, () => this.loggerVNext); this.#logger = dualLogger; this.#storage = storage; this.#backgroundTaskConfig = config?.backgroundTasks; if (workersOption !== false) { this.#ensureBackgroundTaskManager(); } this.#schedulerConfig = config?.scheduler; this.#vectors = {}; this.#mcpServers = {}; this.#tts = {}; this.#agents = {}; this.#scorers = {}; this.#tools = {}; this.#processors = {}; this.#memory = {}; this.#workflows = {}; this.#gateways = {}; if (config?.tools) { Object.entries(config.tools).forEach(([key, tool]) => { if (tool != null) { this.addTool(tool, key); } }); } if (config?.processors) { Object.entries(config.processors).forEach(([key, processor]) => { if (processor != null) { this.addProcessor(processor, key); } }); } if (config?.memory) { Object.entries(config.memory).forEach(([key, memory]) => { if (memory != null) { this.addMemory(memory, key); } }); } if (config?.vectors) { Object.entries(config.vectors).forEach(([key, vector]) => { if (vector != null) { this.addVector(vector, key); } }); } if (config?.workspace) { this.#workspace = config.workspace; this.addWorkspace(config.workspace, void 0, { source: "mastra" }); } if (config?.scorers) { Object.entries(config.scorers).forEach(([key, scorer]) => { if (scorer != null) { this.addScorer(scorer, key, { source: "code" }); } }); } if (config?.workflows) { Object.entries(config.workflows).forEach(([key, workflow]) => { if (workflow != null) { this.addWorkflow(workflow, key); } }); } if (config?.gateways) { Object.entries(config.gateways).forEach(([key, gateway]) => { if (gateway != null) { this.addGateway(gateway, key); } }); } for (const gateway of defaultGateways) { const key = gateway.getId(); if (!this.#gateways[key]) { this.#gateways[key] = gateway; } } if (config?.mcpServers) { Object.entries(config.mcpServers).forEach(([key, server]) => { if (server != null) { this.addMCPServer(server, key); } }); } if (config?.tts) { Object.entries(config.tts).forEach(([key, tts]) => { if (tts != null) { this.#tts[key] = tts; } }); } if (config?.server) { this.#server = config.server; } if (config?.channels) { this.#channels = config.channels; const channelRoutes = []; for (const [, channel] of Object.entries(config.channels)) { if (channel == null) continue; if (channel.__attach) { channel.__attach(this); } const routes = channel.getRoutes(); channelRoutes.push(...routes); } if (channelRoutes.length > 0) { const existingRoutes = this.#server?.apiRoutes ?? []; this.#server = { ...this.#server, apiRoutes: [...existingRoutes, ...channelRoutes] }; } } if (config?.agents) { Object.entries(config.agents).forEach(([key, agent]) => { if (agent != null) { this.addAgent(agent, key); } }); } registerHook("onScorerRun" /* ON_SCORER_RUN */, createOnScorerHook(this)); this.#observability.setMastraContext({ mastra: this }); this.setLogger({ logger }); if (this.#channels) { void Promise.resolve().then(async () => { for (const [key, channel] of Object.entries(this.#channels ?? {})) { if (channel.initialize) { try { await channel.initialize(); } catch (err) { console.error(`[Mastra] Failed to initialize channel "${key}":`, err); } } } }); } } #ensureBackgroundTaskManager() { if (!this.#backgroundTaskConfig?.enabled || !this.#storage || this.#backgroundTaskManager) { return; } const bgManager = new BackgroundTaskManager(this.#backgroundTaskConfig); bgManager.__registerMastra(this); this.#backgroundTaskManager = bgManager; const tools = this.#tools; if (tools) { for (const [name, tool] of Object.entries(tools)) { this.#registerToolWithBackgroundManager(name, tool); } } void bgManager.init(this.#pubsub).catch((error) => { this.#logger?.error("Failed to initialize background task manager", error); }); } /** * Build a `ToolExecutor` adapter for a Mastra-registered tool and stash it * on the background task manager's static registry. Skipped if the tool has * no `execute` (declarative-only tools, e.g. MCP descriptors). */ #registerToolWithBackgroundManager(name, tool) { if (!this.#backgroundTaskManager) return; if (typeof tool.execute !== "function") return; const execute = tool.execute.bind(tool); this.#backgroundTaskManager.registerStaticExecutor(name, { execute: async (args, options) => { return execute( args, { toolCallId: "", messages: [], abortSignal: options?.abortSignal } ); } }); } /** * Returns the flat list of declarative schedules sourced from currently * registered workflows. Single-schedule workflows yield one entry keyed by * `wf_<encoded(workflowId)>`. Array-form workflows yield one entry per array * entry keyed by `wf_<encoded(workflowId)>__<encoded(scheduleId)>` so the * prefix uniquely identifies "all rows owned by this workflow's declarative * config" even when ids contain `__` or other delimiter-like characters. */ #collectDeclarativeSchedules() { const out = []; const workflows = this.#workflows; for (const workflow of Object.values(workflows ?? {})) { const configs = collectWorkflowScheduleConfigs(workflow); if (configs.length === 0) continue; const isArrayForm = configs.length > 1 || configs.length === 1 && configs[0].id !== void 0; for (const cfg of configs) { const scheduleId = isArrayForm ? declarativeScheduleRowId(workflow.id, cfg.id) : declarativeScheduleRowId(workflow.id); out.push({ scheduleId, workflowId: workflow.id, cfg }); } } return out; } #shouldEnableScheduler() { if (this.#schedulerConfig?.enabled === false) return false; if (this.#schedulerConfig?.enabled === true) return true; return this.#hasScheduledWorkflow; } /** * Find the SchedulerWorker from the workers list (if present). */ #findSchedulerWorker() { return this.#workers.find((w) => w.name === "scheduler"); } /** * Sync code-declared schedule configs to the database. Called by * SchedulerWorker during init and by addWorkflow() for late registrations. * * @internal — public so SchedulerWorker can call it, not part of the user API. */ async registerDeclarativeSchedules(schedulesStore) { const declared = this.#collectDeclarativeSchedules(); const declaredIds = new Set(declared.map((d) => d.scheduleId)); const declaredIdsByWorkflow = /* @__PURE__ */ new Map(); const workflows = this.#workflows; for (const workflow of Object.values(workflows ?? {})) { declaredIdsByWorkflow.set(workflow.id, /* @__PURE__ */ new Set()); } for (const { workflowId, scheduleId } of declared) { if (!declaredIdsByWorkflow.has(workflowId)) declaredIdsByWorkflow.set(workflowId, /* @__PURE__ */ new Set()); declaredIdsByWorkflow.get(workflowId).add(scheduleId); } for (const { scheduleId, workflowId, cfg } of declared) { try { const existing = await schedulesStore.getSchedule(scheduleId); const now = Date.now(); const target = { type: "workflow", workflowId, inputData: cfg.inputData, initialState: cfg.initialState, requestContext: cfg.requestContext }; if (!existing) { await schedulesStore.createSchedule({ id: scheduleId, target, cron: cfg.cron, timezone: cfg.timezone, status: "active", nextFireAt: computeNextFireAt(cfg.cron, { timezone: cfg.timezone, after: now }), createdAt: now, updatedAt: now, metadata: cfg.metadata }); continue; } const patch = {}; const cronChanged = existing.cron !== cfg.cron; const timezoneChanged = (existing.timezone ?? void 0) !== (cfg.timezone ?? void 0); if (cronChanged) patch.cron = cfg.cron; if (timezoneChanged) patch.timezone = cfg.timezone; if (!targetsEqual(existing.target, target)) patch.target = target; if (!metadataEqual(existing.metadata, cfg.metadata)) patch.metadata = cfg.metadata; if (cronChanged || timezoneChanged) { patch.nextFireAt = computeNextFireAt(cfg.cron, { timezone: cfg.timezone, after: now }); } if (Object.keys(patch).length > 0) { await schedulesStore.updateSchedule(scheduleId, patch); } } catch (error) { this.#logger?.error("Failed to register declarative schedule", { scheduleId, workflowId, error }); } } const allRows = await schedulesStore.listSchedules(); for (const row of allRows) { if (declaredIds.has(row.id)) continue; if (!row.id.startsWith("wf_")) continue; const ownerWorkflowId = ownerWorkflowIdForRow(row.id, declaredIdsByWorkflow) ?? ownerWorkflowIdFromRowId(row.id); if (!ownerWorkflowId) continue; try { await schedulesStore.deleteSchedule(row.id); } catch (error) { this.#logger?.error("Failed to delete orphaned declarative schedule", { scheduleId: row.id, workflowId: ownerWorkflowId, error }); } } } /** * Auto-enables the background task manager when an agent with sub-agents is * registered. Sub-agent delegation runs in the background by default so the * parent stream stays responsive; that requires the manager to be available. * No-op when the user explicitly opted out via `backgroundTasks.enabled: false`. * * Eligible agents: any agent whose `agents` field is either a static record * with at least one entry OR a dynamic (function-based) resolver. Function * resolvers are evaluated per request, so we can't inspect their contents * here — but if the caller bothered to wire one up, we enable defensively * so those resolved sub-agents also dispatch in the background. */ #maybeEnableBackgroundTasksForAgent(agent) { if (this.#backgroundTaskManager) return; if (this.#backgroundTaskConfig?.enabled === false) return; if (!agent.__hasSubAgentsConfigured?.()) return; this.#backgroundTaskConfig = { ...this.#backgroundTaskConfig ?? {}, enabled: true }; this.#ensureBackgroundTaskManager(); } getAgent(name, version) { const agent = this.#agents?.[name]; if (!agent) { const error = new MastraError({ id: "MASTRA_GET_AGENT_BY_NAME_NOT_FOUND", domain: "MASTRA" /* MASTRA */, category: "USER" /* USER */, text: `Agent with name ${String(name)} not found`, details: { status: 404, agentName: String(name), agents: Object.keys(this.#agents ?? {}).join(", ") } }); this.#logger?.trackException(error); throw error; } if (!version) { return this.#agents[name]; } return this.resolveVersionedAgent(agent, version); } /** * Returns the `AgentChannels` instances for all registered agents. * Keys are agent IDs. */ getChannels() { const result = {}; for (const [agentKey, agent] of Object.entries(this.#agents ?? {})) { const agentChannels = agent.getChannels(); if (agentChannels instanceof AgentChannels) { result[agentKey] = agentChannels; } } return result; } getAgentById(id, version) { let agent = Object.values(this.#agents).find((a) => a.id === id); if (!agent) { try { agent = this.getAgent(id); } catch { } } if (!agent) { const error = new MastraError({ id: "MASTRA_GET_AGENT_BY_AGENT_ID_NOT_FOUND", domain: "MASTRA" /* MASTRA */, category: "USER" /* USER */, text: `Agent with id ${String(id)} not found`, details: { status: 404, agentId: String(id), agents: Object.keys(this.#agents ?? {}).join(", ") } }); this.#logger?.trackException(error); throw error; } if (!version) { return agent; } return this.resolveVersionedAgent(agent, version); } /** * Resolve a versioned variant of an agent by applying stored overrides from the editor. * * Requires the editor package to be configured — throws * `MASTRA_EDITOR_REQUIRED_FOR_VERSIONED_AGENT_LOOKUP` if it is not. * * @param agent - The code-defined agent to resolve a version for. * @param version - Selects a version by ID or publication status. * @returns A forked agent instance with the stored overrides applied. */ async resolveVersionedAgent(agent, version) { const editor = this.getEditor(); if (!editor) { const error = new MastraError({ id: "MASTRA_EDITOR_REQUIRED_FOR_VERSIONED_AGENT_LOOKUP", domain: "MASTRA" /* MASTRA */, category: "USER" /* USER */, text: "Versioned agent lookup requires the editor package to be configured", details: { status: 400, agentId: agent.id, ...version && "versionId" in version ? { versionId: version.versionId } : {}, ...version && "status" in version && version.status ? { versionStatus: version.status } : {} } }); this.#logger?.trackException(error); throw error; } return editor.agent.applyStoredOverrides( agent, "versionId" in version ? version : { status: version.status ?? "published" } ); } /** * Returns all registered agents as a record keyed by their names. * * This method provides access to the complete registry of agents, allowing you to * iterate over them, check what agents are available, or perform bulk operations. * * @example * ```typescript * const mastra = new Mastra({ * agents: { * weatherAgent: new Agent({ id: 'weather-agent', name: 'weather', model: 'openai/gpt-4o' }), * supportAgent: new Agent({ id: 'support-agent', name: 'support', model: 'openai/gpt-4o' }) * } * }); * * const allAgents = mastra.listAgents(); * console.log(Object.keys(allAgents)); // ['weatherAgent', 'supportAgent'] * ``` */ listAgents() { return this.#agents; } /** * Adds a new agent to the Mastra instance. * * This method allows dynamic registration of agents after the Mastra instance * has been created. The agent will be initialized with the current logger. * * @throws {MastraError} When an agent with the same key already exists * * @example * ```typescript * const mastra = new Mastra(); * const newAgent = new Agent({ * id: 'chat-agent', * name: 'Chat Assistant', * model: 'openai/gpt-4o' * }); * mastra.addAgent(newAgent); // Uses agent.id as key * // or * mastra.addAgent(newAgent, 'customKey'); // Uses custom key * * // Durable agents (e.g., InngestAgent) are also supported: * const durableAgent = createInngestAgent({ agent: newAgent, inngest }); * mastra.addAgent(durableAgent); // Auto-registers required workflows * ``` */ addAgent(agent, key, options) { if (!agent) { throw createUndefinedPrimitiveError("agent", agent, key); } if (isDurableAgentLike(agent)) { const durableAgent = agent; const underlyingAgent = durableAgent.agent; const agentKey2 = key || durableAgent.id; const agents2 = this.#agents; if (agents2[agentKey2]) { const logger = this.getLogger(); logger.debug(`Agent with key ${agentKey2} already exists. Skipping addition.`); return; } durableAgent.__setMastra?.(this); underlyingAgent.__setLogger(this.#logger); underlyingAgent.__registerMastra(this); underlyingAgent.__registerPrimitives({ logger: this.getLogger(), storage: this.getStorage(), agents: agents2, tts: this.#tts, vectors: this.#vectors }); agents2[agentKey2] = durableAgent; const durableWorkflows = durableAgent.getDurableWorkflows?.() ?? []; for (const workflow of durableWorkflows) { this.addWorkflow(workflow, workflow.id); } return; } let mastraAgent; if (isToolLoopAgentLike(agent)) { mastraAgent = toolLoopAgentToMastraAgent(agent, { fallbackName: key }); } else { mastraAgent = agent; } const agentKey = key || mastraAgent.id; const agents = this.#agents; if (agents[agentKey]) { return; } mastraAgent.__setLogger(this.#logger); mastraAgent.__registerMastra(this); mastraAgent.__registerPrimitives({ logger: this.getLogger(), storage: this.getStorage(), agents, tts: this.#tts, vectors: this.#vectors }); if (options?.source) { mastraAgent.source = options.source; } agents[agentKey] = mastraAgent; mastraAgent.getConfiguredProcessorWorkflows().then((processorWorkflows) => { for (const workflow of processorWorkflows) { this.addWorkflow(workflow, workflow.id); } }).catch((err) => { this.#logger?.debug(`Failed to register processor workflows for agent ${agentKey}:`, err); }); if (mastraAgent.hasOwnWorkspace?.()) { Promise.resolve(mastraAgent.getWorkspace?.()).then((workspace) => { if (workspace) { this.addWorkspace(workspace, void 0, { source: "agent", agentId: mastraAgent.id ?? agentKey, agentName: mastraAgent.name }); } }).catch((err) => { this.#logger?.debug(`Failed to register workspace for agent ${agentKey}:`, err); }); } mastraAgent.listScorers().then((scorers) => { for (const [, entry] of Object.entries(scorers || {})) { this.addScorer(entry.scorer, void 0, { source: "code" }); } }).catch((err) => { this.#logger?.debug(`Failed to register scorers from agent ${agentKey}:`, err); }); const agentChannelsInstance = mastraAgent.getChannels(); if (agentChannelsInstance) { agentChannelsInstance.__setLogger(this.#logger); const channelRoutes = agentChannelsInstance.getWebhookRoutes(); if (channelRoutes.length > 0) { this.#server = { ...this.#server, apiRoutes: [...this.#server?.apiRoutes ?? [], ...channelRoutes] }; } void agentChannelsInstance.initialize(this); } } /** * Removes an agent from the Mastra instance by its key or ID. * Used when stored agents are updated/deleted to allow fresh data to be loaded. * * @param keyOrId - The agent key or ID to remove * @returns true if an agent was removed, false if no agent was found * * @example * ```typescript * // Remove by key * mastra.removeAgent('myAgent'); * * // Remove by ID * mastra.removeAgent('agent-123'); * ``` */ removeAgent(keyOrId) { const agents = this.#agents; if (agents[keyOrId]) { const agentId = agents[keyOrId]?.id; delete agents[keyOrId]; if (agentId) { this.#storedAgentsCache.delete(agentId); } return true; } const key = Object.keys(agents).find((k) => agents[k]?.id === keyOrId); if (key) { const agentId = agents[key]?.id; delete agents[key]; if (agentId) { this.#storedAgentsCache.delete(agentId); } return true; } return false; } /** * Retrieves a registered vector store by its name. * * @template TVectorName - The specific vector store name type from the registered vectors * @throws {MastraError} When the vector store with the specified name is not found * * @example Using a vector store for semantic search * ```typescript * import { PineconeVector } from '@mastra/pinecone'; * import { OpenAIEmbedder } from '@mastra/embedders'; * * const mastra = new Mastra({ * vectors: { * knowledge: new PineconeVector({ * apiKey: process.env.PINECONE_API_KEY, * indexName: 'knowledge-base', * embedder: new OpenAIEmbedder({ * apiKey: process.env.OPENAI_API_KEY, * model: 'text-embedding-3-small' * }) * }), * products: new PineconeVector({ * apiKey: process.env.PINECONE_API_KEY, * indexName: 'product-catalog' * }) * } * }); * * // Get a vector store and perform semantic search * const knowledgeBase = mastra.getVector('knowledge'); * const results = await knowledgeBase.query({ * query: 'How to reset password?', * topK: 5 * }); * * console.log('Relevant documents:', results); * ``` */ getVector(name) { const vector = this.#vectors?.[name]; if (!vector) { const error = new MastraError({ id: "MASTRA_GET_VECTOR_BY_NAME_NOT_FOUND", domain: "MASTRA" /* MASTRA */, category: "USER" /* USER */, text: `Vector with name ${String(name)} not found`, details: { status: 404, vectorName: String(name), vectors: Object.keys(this.#vectors ?? {}).join(", ") } }); this.#logger?.trackException(error); throw error; } return vector; } /** * Retrieves a specific vector store instance by its ID. * * This method searches for a vector store by its internal ID property. * If not found by ID, it falls back to searching by registration key. * * @throws {MastraError} When the specified vector store is not found * * @example * ```typescript * const mastra = new Mastra({ * vectors: { * embeddings: chromaVector * } * }); * * const vectorStore = mastra.getVectorById('chroma-123'); * ``` */ getVectorById(id) { const allVectors = this.#vectors ?? {}; for (const vector of Object.values(allVectors)) { if (vector.id === id) { return vector; } } const vectorByKey = allVectors[id]; if (vectorByKey) { return vectorByKey; } const error = new MastraError({ id: "MASTRA_GET_VECTOR_BY_ID_NOT_FOUND", domain: "MASTRA" /* MASTRA */, category: "USER" /* USER */, text: `Vector store with id ${id} not found`, details: { status: 404, vectorId: String(id), vectors: Object.keys(allVectors).join(", ") } }); this.#logger?.trackException(error); throw error; } /** * Returns all registered vector stores as a record keyed by their names. * * @example Listing all vector stores * ```typescript * const mastra = new Mastra({ * vectors: { * documents: new PineconeVector({ indexName: 'docs' }), * images: new PineconeVector({ indexName: 'images' }), * products: new ChromaVector({ collectionName: 'products' }) * } * }); * * const allVectors = mastra.getVectors(); * console.log(Object.keys(allVectors)); // ['documents', 'images', 'products'] * * // Check vector store types and configurations * for (const [name, vectorStore] of Object.entries(allVectors)) { * console.log(`Vector store ${name}:`, vectorStore.constructor.name); * } * ``` */ listVectors() { return this.#vectors; } /** * Adds a new vector store to the Mastra instance. * * This method allows dynamic registration of vector stores after the Mastra instance * has been created. The vector store will be initialized with the current logger. * * @throws {MastraError} When a vector store with the same key already exists * * @example * ```typescript * const mastra = new Mastra(); * const newVector = new ChromaVector({ id: 'chroma-embeddings' }); * mastra.addVector(newVector); // Uses vector.id as key * // or * mastra.addVector(newVector, 'customKey'); // Uses custom key * ``` */ addVector(vector, key) { if (!vector) { throw createUndefinedPrimitiveError("vector", vector, key); } const vectorKey = key || vector.id; const vectors = this.#vectors; if (vectors[vectorKey]) { return; } vector.__setLogger(this.#logger || this.getLogger()); vectors[vectorKey] = vector; } /** * @deprecated Use listVectors() instead */ getVectors() { console.warn("getVectors() is deprecated. Use listVectors() instead."); return this.listVectors(); } /** * Gets the currently configured deployment provider. * * @example * ```typescript * const mastra = new Mastra({ * deployer: new VercelDeployer({ * token: process.env.VERCEL_TOKEN, * projectId: process.env.VERCEL_PROJECT_ID * }) * }); * * const deployer = mastra.getDeployer(); * if (deployer) { * await deployer.deploy({ * name: 'my-mastra-app', * environment: 'production' * }); * } * ``` */ getDeployer() { return this.#deployer; } /** * Gets the global workspace instance. * Workspace provides file storage, skills, and code execution capabilities. * Agents inherit this workspace unless they have their own configured. * * @example * ```typescript * const workspace = mastra.getWorkspace(); * if (workspace?.skills) { * const skills = await workspace.skills.list(); * } * ``` */ getWorkspace() { return this.#workspace; } /** * Retrieves a registered workspace by its ID. * * @throws {MastraError} When the workspace with the specified ID is not found * * @example * ```typescript * const workspace = mastra.getWorkspaceById('workspace-123'); * const files = await workspace.filesystem.readdir('/'); * ``` */ getWorkspaceById(id) { const entry = this.#workspaces[id]; if (!entry) { const error = new MastraError({ id: "MASTRA_GET_WORKSPACE_BY_ID_NOT_FOUND", domain: "MASTRA" /* MASTRA */, category: "USER" /* USER */, text: `Workspace with id ${id} not found`, details: { status: 404, workspaceId: id, availableIds: Object.keys(this.#workspaces).join(", ") } }); this.#logger?.trackException(error); throw error; } return entry.workspace; } /** * Returns all registered workspaces as a record keyed by their IDs. * * @example * ```typescript * const workspaces = mastra.listWorkspaces(); * for (const [id, entry] of Object.entries(workspaces)) { * console.log(`Workspace ${id}: ${entry.workspace.name} (source: ${entry.source})`); * } * ``` */ listWorkspaces() { return { ...this.#workspaces }; } /** * Adds a new workspace to the Mastra instance. * * This method allows dynamic registration of workspaces after the Mastra instance * has been created. Workspaces are keyed by their ID. * * @example * ```typescript * const workspace = new Workspace({ * id: 'project-workspace', * name: 'Project Workspace', * filesystem: new LocalFilesystem({ rootPath: './workspace' }) * }); * mastra.addWorkspace(workspace); * ``` */ addWorkspace(workspace, key, metadata) { if (!workspace) { throw createUndefinedPrimitiveError("workspace", workspace, key); } const source = metadata?.source ?? (metadata?.agentId || metadata?.agentName ? "agent" : "mastra"); if (source === "agent" && (!metadata?.agentId || !metadata?.agentName)) { throw new MastraError({ id: "MASTRA_ADD_WORKSPACE_MISSING_AGENT_METADATA", domain: "MASTRA" /* MASTRA */, category: "USER" /* USER */, text: "Agent workspaces must include agentId and agentName.", details: { status: 400, workspaceId: key || workspace.id } }); } const workspaceKey = key || workspace.id; if (this.#workspaces[workspaceKey]) { return; } this.#workspaces[workspaceKey] = { workspace, source, ...metadata?.agentId ? { agentId: metadata.agentId } : {}, ...metadata?.agentName ? { agentName: metadata.agentName } : {} }; } /** * Retrieves a registered workflow by its ID. * * @template TWorkflowId - The specific workflow ID type from the registered workflows * @throws {MastraError} When the workflow with the specified ID is not found * * @example Getting and executing a workflow * ```typescript * import { createWorkflow, createStep } from '@mastra/core/workflows'; * import { z } from 'zod/v4'; * * const processDataWorkflow = createWorkflow({ * name: 'process-data', * triggerSchema: z.object({ input: z.string() }) * }) * .then(validateStep) * .then(transformStep) * .then(saveStep) * .commit(); * * const mastra = new Mastra({ * workflows: { * dataProcessor: processDataWorkflow * } * }); * ``` */ getWorkflow(id, { serialized } = {}) { const workflow = this.#workflows?.[id]; if (!workflow) { const error = new MastraError({ id: "MASTRA_GET_WORKFLOW_BY_ID_NOT_FOUND", domain: "MASTRA" /* MASTRA */, category: "USER" /* USER */, text: `Workflow with ID ${String(id)} not found`, details: { status: 404, workflowId: String(id), workflows: Object.keys(this.#workflows ?? {}).join(", ") } }); this.#logger?.trackException(error); throw error; } if (serialized) { return { name: workflow.name }; } return workflow; } __registerInternalWorkflow(workflow) { workflow.__registerMastra(this); workflow.__registerPrimitives({ logger: this.getLogger() }); this.#internalMastraWorkflows[workflow.id] = workflow; } __hasInternalWorkflow(id) { return Object.values(this.#internalMastraWorkflows).some((workflow) => workflow.id === id); } __getInternalWorkflow(id) { const workflow = Object.values(this.#internalMastraWorkflows).find((a) => a.id === id); if (!workflow) { throw new MastraError({ id: "MASTRA_GET_INTERNAL_WORKFLOW_BY_ID_NOT_FOUND", domain: "MASTRA" /* MASTRA */, category: "SYSTEM" /* SYSTEM */, text: `Workflow with id ${String(id)} not found`, details: { status: 404, workflowId: String(id) } }); } return workflow; } /** * Retrieves a registered workflow by its unique ID. * * This method searches for a workflow using its internal ID property. If no workflow * is found with the given ID, it also attempts to find a workflow using the ID as * a name. * * @throws {MastraError} When no workflow is found with the specified ID * * @example Finding a workflow by ID * ```typescript * const mastra = new Mastra({ * workflows: { * dataProcessor: createWorkflow({ * name: 'process-data', * triggerSchema: z.object({ input: z.string() }) * }).commit() * } * }); * * // Get the workflow's ID * const workflow = mastra.getWorkflow('dataProcessor'); * const workflowId = workflow.id; * * // Later, retrieve the workflow by ID * const sameWorkflow = mastra.getWorkflowById(workflowId); * console.log(sameWorkflow.name); // "process-data" * ``` */ getWorkflowById(id) { let workflow = Object.values(this.#workflows).find((a) => a.id === id); if (!workflow) { try { workflow = this.getWorkflow(id); } catch { } } if (!workflow) { const error = new MastraError({ id: "MASTRA_GET_WORKFLOW_BY_ID_NOT_FOUND", domain: "MASTRA" /* MASTRA */, category: "USER" /* USER */, text: `Workflow with id ${String(id)} not found`, details: { status: 404, workflowId: String(id), workflows: Object.keys(this.#workflows ?? {}).join(", ") } }); this.#logger?.trackException(error); throw error; } return workflow; } async listActiveWorkflowRuns() { const storage