@mastra/core
Version:
446 lines (445 loc) • 14.7 kB
JavaScript
const require_request_context = require("./request-context-ByoZMp-j.cjs");
const require_background_tasks = require("./background-tasks-lifNqs9M.cjs");
const require_workflow_event_processor = require("./workflow-event-processor-CkjVcesJ.cjs");
const require_scheduler = require("./scheduler-D9Mqp8B5.cjs");
const require_pull_transport = require("./pull-transport-BlEVfOcc.cjs");
//#region src/worker/strategies/http-remote-strategy.ts
/**
* Executes workflow steps by calling a remote server endpoint over HTTP.
* Used in standalone worker deployments where the worker runs orchestration
* logic but delegates actual step execution to the server.
*
* Authentication piggy-backs on Mastra's existing auth pipeline: the route
* is marked `requiresAuth: true` and the deployer's `authenticateToken`
* provider validates the credential we send here. There is no separate
* "worker secret" — whatever auth scheme the rest of the server uses is
* what the worker uses too.
*/
var HttpRemoteStrategy = class HttpRemoteStrategy {
#baseUrl;
#auth;
#timeoutMs;
constructor({ serverUrl, auth, timeoutMs }) {
const normalized = serverUrl.endsWith("/") ? serverUrl : `${serverUrl}/`;
this.#baseUrl = new URL(normalized);
this.#auth = auth ?? HttpRemoteStrategy.#authFromEnv();
this.#timeoutMs = timeoutMs ?? 3e4;
}
/**
* Default credential resolution: when `MASTRA_WORKER_AUTH_TOKEN` is set,
* send it as a bearer token. The server's auth provider decides whether
* to accept it.
*/
static #authFromEnv() {
const token = process.env.MASTRA_WORKER_AUTH_TOKEN;
if (!token) return void 0;
return {
type: "bearer",
token
};
}
async executeStep(params) {
const url = new URL(`workflows/${encodeURIComponent(params.workflowId)}/runs/${encodeURIComponent(params.runId)}/steps/execute`, this.#baseUrl);
const body = this.#buildBody(params);
const signal = this.#combineSignals(params.abortSignal);
const res = await fetch(url, {
method: "POST",
headers: {
"content-type": "application/json",
...this.#buildAuthHeaders()
},
body,
signal
});
if (!res.ok) {
const text = await res.text();
throw new StepExecutionError(res.status, text);
}
return res.json();
}
/**
* Build a JSON-serializable request body. The `params.requestContext` is
* a plain object; if a caller stuffed a non-serializable value into it we
* surface a clear error instead of silently dropping fields.
*
* `abortSignal` is consumed via fetch's `signal` argument — it must not
* be in the body.
*/
#buildBody(params) {
const { abortSignal: _abortSignal, requestContext, ...rest } = params;
let safeRequestContext;
try {
safeRequestContext = JSON.parse(JSON.stringify(requestContext ?? {}));
} catch (err) {
throw new Error(`HttpRemoteStrategy: requestContext is not JSON-serializable. ${err instanceof Error ? err.message : String(err)}`);
}
return JSON.stringify({
...rest,
requestContext: safeRequestContext
});
}
#combineSignals(externalSignal) {
const timeoutSignal = AbortSignal.timeout(this.#timeoutMs);
if (!externalSignal) return timeoutSignal;
if (typeof AbortSignal.any === "function") return AbortSignal.any([timeoutSignal, externalSignal]);
const controller = new AbortController();
const onAbort = (reason) => controller.abort(reason);
if (externalSignal.aborted) onAbort(externalSignal.reason);
else externalSignal.addEventListener("abort", () => onAbort(externalSignal.reason), { once: true });
if (timeoutSignal.aborted) onAbort(timeoutSignal.reason);
else timeoutSignal.addEventListener("abort", () => onAbort(timeoutSignal.reason), { once: true });
return controller.signal;
}
#buildAuthHeaders() {
if (!this.#auth) return {};
if (this.#auth.type === "api-key") return { "x-worker-api-key": this.#auth.key };
if (this.#auth.type === "header") return { [this.#auth.name]: this.#auth.value };
return { authorization: `Bearer ${this.#auth.token}` };
}
};
var StepExecutionError = class extends Error {
status;
body;
constructor(status, body) {
super(`Step execution failed with status ${status}: ${body}`);
this.name = "StepExecutionError";
this.status = status;
this.body = body;
}
};
//#endregion
//#region src/worker/workers/orchestration-worker.ts
const DEFAULT_GROUP = "mastra-orchestration";
/**
* Processes workflow events (step.run, step.end, start, cancel, etc.)
* by delegating to the WorkflowEventProcessor.
*
* Subscribes to the PubSub "workflows" topic and routes events to WEP.
*
* When MASTRA_STEP_EXECUTION_URL is set, injects HttpRemoteStrategy into
* WEP so step execution happens over HTTP to the server. Otherwise WEP
* executes steps directly in-process.
*/
var OrchestrationWorker = class extends require_pull_transport.MastraWorker {
name = "orchestration";
#config;
#transport;
#processor;
#strategy;
#running = false;
constructor(config = {}) {
super();
this.#config = config;
}
async init(deps) {
await super.init(deps);
if (!deps.mastra) throw new Error("OrchestrationWorker requires Mastra instance");
const modes = deps.pubsub.supportedModes ?? ["pull"];
if (!modes.includes("pull")) throw new Error(`OrchestrationWorker requires a pull-capable PubSub, but the configured pubsub only supports: ${modes.join(", ")}. Either remove OrchestrationWorker from the workers list or use a pull-capable PubSub (e.g. Redis Streams).`);
const remoteUrl = process.env.MASTRA_STEP_EXECUTION_URL;
if (remoteUrl) this.#strategy = new HttpRemoteStrategy({ serverUrl: remoteUrl });
this.#processor = new require_workflow_event_processor.WorkflowEventProcessor({
mastra: deps.mastra,
stepExecutionStrategy: this.#strategy
});
}
async start() {
if (this.#running) return;
if (!this.deps) throw new Error("OrchestrationWorker: call init() before start()");
const group = this.#config.group ?? DEFAULT_GROUP;
this.#transport = new require_pull_transport.PullTransport({
pubsub: this.deps.pubsub,
group,
logger: this.deps.logger
});
await this.#transport.start({ route: (event, ack, nack) => this.#processEvent(event, ack, nack) });
this.#running = true;
}
async stop() {
if (!this.#running) return;
try {
if (this.#transport) {
await this.#transport.stop();
this.#transport = void 0;
}
} finally {
this.#running = false;
}
}
get isRunning() {
return this.#running;
}
async #processEvent(event, ack, nack) {
if (!this.#processor) throw new Error("OrchestrationWorker not initialized");
const result = await this.#processor.handle(event);
if (result.ok) {
try {
await ack?.();
} catch (e) {
this.deps?.logger?.error("OrchestrationWorker: error acking event", { error: e });
}
return;
}
this.deps?.logger?.error("OrchestrationWorker: error processing event", {
type: event.type,
runId: event.runId,
retry: result.retry
});
if (result.retry) {
if (nack) try {
await nack();
} catch (e) {
this.deps?.logger?.error("OrchestrationWorker: error nacking event", { error: e });
}
return;
}
if (ack) try {
await ack();
} catch (e) {
this.deps?.logger?.error("OrchestrationWorker: error acking terminal event", { error: e });
}
}
};
//#endregion
//#region src/worker/workers/scheduler-worker.ts
/**
* Drives cron-based workflow schedules. On each tick it polls storage
* for due schedules, computes next fire times, and publishes
* workflow.start events. Does not consume events — only produces them.
*
* This is the **single** scheduler code path. The Mastra constructor
* adds the worker to the default workers list (guarded by
* `#shouldEnableScheduler()`), and `startWorkers()` initializes it.
*/
var SchedulerWorker = class extends require_pull_transport.MastraWorker {
name = "scheduler";
#scheduler;
#config;
#running = false;
constructor(config = {}) {
super();
this.#config = config;
}
async init(deps) {
await super.init(deps);
if (!deps.storage) {
deps.logger.warn("SchedulerWorker: no storage configured, scheduler will not run");
return;
}
const schedulesStore = await deps.storage.getStore("schedules");
if (!schedulesStore) {
deps.logger.warn("SchedulerWorker: no schedules store available, scheduler will not run");
return;
}
const mastra = this.mastra;
const isTargetReady = mastra ? (target) => {
try {
if (target.type === "workflow") {
mastra.getWorkflowById(target.workflowId);
return true;
}
if (target.type === "agent") {
mastra.getAgentById(target.agentId);
return true;
}
return false;
} catch {
return false;
}
} : void 0;
this.#scheduler = new require_scheduler.Scheduler({
schedulesStore,
pubsub: deps.pubsub,
config: {
...this.#config,
isTargetReady
}
});
this.#scheduler.__setLogger(deps.logger);
if (this.mastra) try {
await this.mastra.registerDeclarativeSchedules(schedulesStore);
} catch (err) {
deps.logger.error?.("SchedulerWorker: failed to register declarative schedules", { error: err });
}
}
async start() {
if (this.#running) return;
if (this.#scheduler) await this.#scheduler.start();
this.#running = true;
}
async stop() {
if (!this.#running) return;
if (this.#scheduler) await this.#scheduler.stop();
this.#running = false;
}
get isRunning() {
return this.#running;
}
/** Expose the underlying scheduler for direct API access (e.g., schedule management). */
get scheduler() {
return this.#scheduler;
}
};
//#endregion
//#region src/worker/workers/background-task-worker.ts
/**
* Manages background tool execution for agents. Handles task queuing,
* concurrency limits, and lifecycle. Subscribes to PubSub internally
* via BackgroundTaskManager's own subscription mechanism.
*/
var BackgroundTaskWorker = class extends require_pull_transport.MastraWorker {
name = "backgroundTasks";
#manager;
#ownsManager = false;
#config;
#running = false;
constructor(config = {}) {
super();
this.#config = config;
}
async init(deps) {
await super.init(deps);
const existing = deps.mastra?.backgroundTaskManager;
if (existing) {
this.#manager = existing;
this.#ownsManager = false;
return;
}
this.#manager = new require_background_tasks.BackgroundTaskManager({
enabled: true,
mode: "worker",
globalConcurrency: this.#config.globalConcurrency,
perAgentConcurrency: this.#config.perAgentConcurrency,
backpressure: this.#config.backpressure,
defaultTimeoutMs: this.#config.defaultTimeoutMs
});
this.#ownsManager = true;
if (deps.mastra) {
this.#manager.__registerMastra(deps.mastra);
this.#wireStaticTools(deps.mastra);
}
}
/**
* Populate the manager's static executor registry from tools registered
* on `Mastra`, so that cross-process dispatches can be resolved by tool
* name on this worker. Mirrors the wiring Mastra does for its own
* managed background-task manager — the worker owns a separate manager
* instance, so it has to populate its own registry.
*/
#wireStaticTools(mastra) {
const tools = mastra.listTools?.call(mastra);
if (!tools || !this.#manager) return;
for (const [name, tool] of Object.entries(tools)) {
if (!tool || typeof tool.execute !== "function") continue;
const execute = tool.execute.bind(tool);
this.#manager.registerStaticExecutor(name, { execute: async (args, options) => {
return execute(args, {
toolCallId: "",
messages: [],
abortSignal: options?.abortSignal
});
} });
}
}
async start() {
if (this.#running) return;
if (!this.#manager || !this.deps) throw new Error("BackgroundTaskWorker: call init() before start()");
if (this.#ownsManager) await this.#manager.init(this.deps.pubsub);
this.#running = true;
}
async stop() {
if (!this.#running) return;
if (this.#manager && this.#ownsManager) await this.#manager.shutdown();
this.#running = false;
}
get isRunning() {
return this.#running;
}
/** Expose the underlying manager for direct API access. */
get manager() {
return this.#manager;
}
};
//#endregion
//#region src/worker/strategies/in-process-strategy.ts
/**
* Executes workflow steps in the same process by delegating to StepExecutor.
* This is the default strategy used when the worker runs co-located with the server.
*/
var InProcessStrategy = class {
#mastra;
constructor({ mastra } = {}) {
this.#mastra = mastra;
}
__registerMastra(mastra) {
this.#mastra = mastra;
}
async executeStep(params) {
if (!this.#mastra) throw new Error("InProcessStrategy requires Mastra instance. Call __registerMastra() first.");
const entry = require_workflow_event_processor.getStepEntry(this.#mastra.getWorkflowById(params.workflowId), params.executionPath);
if (!entry) throw new Error(`InProcessStrategy: could not resolve step "${params.stepId}" at executionPath [${params.executionPath.join(",")}] in workflow "${params.workflowId}"`);
const rc = new require_request_context.RequestContext(Object.entries(params.requestContext ?? {}));
let abortController;
if (params.abortSignal) {
abortController = new AbortController();
if (params.abortSignal.aborted) abortController.abort(params.abortSignal.reason);
else params.abortSignal.addEventListener("abort", () => {
abortController.abort(params.abortSignal.reason);
}, { once: true });
}
return new require_workflow_event_processor.StepExecutor({ mastra: this.#mastra }).execute({
workflowId: params.workflowId,
entry,
runId: params.runId,
stepResults: params.stepResults,
state: params.state,
requestContext: rc,
input: params.input,
resumeData: params.resumeData,
retryCount: params.retryCount,
foreachIdx: params.foreachIdx,
validateInputs: params.validateInputs,
abortController,
format: params.format,
perStep: params.perStep
});
}
};
//#endregion
Object.defineProperty(exports, "BackgroundTaskWorker", {
enumerable: true,
get: function() {
return BackgroundTaskWorker;
}
});
Object.defineProperty(exports, "HttpRemoteStrategy", {
enumerable: true,
get: function() {
return HttpRemoteStrategy;
}
});
Object.defineProperty(exports, "InProcessStrategy", {
enumerable: true,
get: function() {
return InProcessStrategy;
}
});
Object.defineProperty(exports, "OrchestrationWorker", {
enumerable: true,
get: function() {
return OrchestrationWorker;
}
});
Object.defineProperty(exports, "SchedulerWorker", {
enumerable: true,
get: function() {
return SchedulerWorker;
}
});
Object.defineProperty(exports, "StepExecutionError", {
enumerable: true,
get: function() {
return StepExecutionError;
}
});
//# sourceMappingURL=worker-H14yjTvH.cjs.map