UNPKG

@mastra/core

Version:
518 lines (516 loc) • 14.8 kB
import { MastraWorker, PullTransport } from './chunk-2JTOKKYG.js'; import { RequestContext } from './chunk-W4DLMY73.js'; // src/schedules/worker.ts var TOPIC_AGENT_SCHEDULES = "agent-schedules"; var DEFAULT_GROUP = "mastra-agent-schedules"; var AgentScheduleWorker = class extends MastraWorker { name = "agent-schedule"; #config; #transport; #pushCb; #running = false; constructor(config = {}) { super(); this.#config = config; } async init(deps) { await super.init(deps); if (!deps.mastra) { throw new Error("AgentScheduleWorker requires Mastra instance"); } } async start() { if (this.#running) return; if (!this.deps) throw new Error("AgentScheduleWorker: call init() before start()"); const modes = this.deps.pubsub.supportedModes ?? ["pull"]; if (!modes.includes("pull")) { const cb = (event, ack, nack) => { void this.#handleEvent(event, ack, nack); }; this.#pushCb = cb; await this.deps.pubsub.subscribe(TOPIC_AGENT_SCHEDULES, cb); this.#running = true; return; } const group = this.#config.group ?? DEFAULT_GROUP; this.#transport = new PullTransport({ pubsub: this.deps.pubsub, group, topic: TOPIC_AGENT_SCHEDULES, logger: this.deps.logger }); await this.#transport.start({ route: (event, ack, nack) => this.#handleEvent(event, ack, nack) }); this.#running = true; } async stop() { if (!this.#running) return; try { if (this.#transport) { await this.#transport.stop(); this.#transport = void 0; } if (this.#pushCb && this.deps) { await this.deps.pubsub.unsubscribe(TOPIC_AGENT_SCHEDULES, this.#pushCb); this.#pushCb = void 0; } } finally { this.#running = false; } } get isRunning() { return this.#running; } async #handleEvent(event, ack, nack) { if (event.type !== "agent-schedule.fire") { await ack?.(); return; } const mastra = this.mastra; const payload = event.data; try { await this.#dispatch(mastra, payload); await ack?.(); } catch (err) { this.deps?.logger?.error("AgentScheduleWorker: error processing agent-schedule.fire", { scheduleId: payload?.scheduleId, claimId: payload?.claimId, error: err }); await nack?.(); } } async #dispatch(mastra, data) { const { scheduleId, claimId, scheduledFireAt, target } = data; const actualFireAt = Date.now(); const result = await executeAgentSchedule(mastra, scheduleId, target, { triggerKind: data.triggerKind ?? "schedule-fire", firedAt: new Date(actualFireAt), logger: this.deps?.logger }); await this.#recordTrigger({ scheduleId, claimId, scheduledFireAt, actualFireAt, outcome: result.outcome, runId: result.runId, error: result.reason, triggerKind: data.triggerKind ?? "schedule-fire" }); } async #recordTrigger(args) { const store = await this.deps?.storage.getStore("schedules"); if (!store) return; try { await store.recordTrigger({ scheduleId: args.scheduleId, runId: args.runId ?? args.claimId, scheduledFireAt: args.scheduledFireAt, actualFireAt: args.actualFireAt, outcome: args.outcome, error: args.error, triggerKind: args.triggerKind }); } catch (err) { this.deps?.logger?.error("AgentScheduleWorker: failed to record trigger", { scheduleId: args.scheduleId, claimId: args.claimId, error: err }); } } }; async function selfClean(mastra, scheduleId) { try { const store = await mastra.getStorage()?.getStore("schedules"); if (!store) return; await store.deleteSchedule(scheduleId); } catch (error) { mastra.getLogger?.()?.debug?.("agent-schedule self-clean failed", { scheduleId, error }); } } async function executeAgentSchedule(mastra, scheduleId, target, ctx = {}) { const { agentId } = target; const trigger = { kind: ctx.triggerKind === "manual" ? "manual" : "cron", firedAt: ctx.firedAt ?? /* @__PURE__ */ new Date() }; const log = ctx.logger ?? mastra.getLogger?.(); const agent = (() => { try { return mastra.getAgentById(agentId); } catch { return null; } })(); if (!agent) { await selfClean(mastra, scheduleId); return { status: "agent-missing", outcome: "failed", reason: `agent "${agentId}" no longer registered` }; } const hooks = mastra.__getScheduleHooks?.() ?? void 0; const scheduleRef = hooks ? await loadScheduleRef(mastra, scheduleId, target, log) : scheduleRefFromTarget(scheduleId, target); const rowDefaults = buildEffectiveFromTarget(target); let prepared; if (hooks?.prepare) { try { const prepareCtx = { mastra, agentId, schedule: scheduleRef, trigger }; prepared = await hooks.prepare(prepareCtx); } catch (err) { await safeHookCall( log, () => hooks.onError?.({ mastra, agentId, schedule: scheduleRef, trigger, phase: "prepare", error: err instanceof Error ? err : new Error(String(err)), effective: rowDefaults }) ); return { status: "invalid-input", outcome: "failed", reason: err instanceof Error ? err.message : String(err) }; } } if (prepared === null) { await safeHookCall( log, () => hooks?.onFinish?.({ mastra, agentId, schedule: scheduleRef, trigger, outcome: "skipped", effective: rowDefaults }) ); return { status: "fired", outcome: "skipped" }; } const effective = mergeEffective(rowDefaults, prepared); const scheduleRunMeta = { scheduleId, ...effective.threadId ? { threadId: effective.threadId } : {} }; if (effective.threadId) { if (!effective.resourceId) { const reason = "resourceId required when threadId is set"; await safeHookCall( log, () => hooks?.onError?.({ mastra, agentId, schedule: scheduleRef, trigger, phase: "run", error: new Error(reason), effective }) ); return { status: "invalid-input", outcome: "failed", reason }; } const memory = await agent.getMemory(); if (memory) { const thread = await memory.getThreadById({ threadId: effective.threadId }); if (!thread) { await selfClean(mastra, scheduleId); const reason = `thread "${effective.threadId}" not found`; await safeHookCall( log, () => hooks?.onError?.({ mastra, agentId, schedule: scheduleRef, trigger, phase: "run", error: new Error(reason), effective }) ); return { status: "thread-missing", outcome: "failed", reason }; } } let signalResult; try { signalResult = agent.sendSignal( { type: effective.signalType ?? "notification", tagName: effective.tagName ?? "schedule", contents: effective.prompt, ...effective.attributes ? { attributes: effective.attributes } : {}, providerOptions: mergeProviderOptions(effective.providerOptions, scheduleRunMeta) }, { resourceId: effective.resourceId, threadId: effective.threadId, ...effective.ifActive ? { ifActive: effective.ifActive } : {}, ...effective.ifIdle ? { ifIdle: buildIfIdleOptions(effective.ifIdle) } : {} } ); } catch (err) { const error = err instanceof Error ? err : new Error(String(err)); await safeHookCall( log, () => hooks?.onError?.({ mastra, agentId, schedule: scheduleRef, trigger, phase: "run", error, effective }) ); return { status: "invalid-input", outcome: "failed", reason: error.message }; } let settled; try { settled = await signalResult.accepted; } catch (err) { const error = err instanceof Error ? err : new Error(String(err)); await safeHookCall( log, () => hooks?.onError?.({ mastra, agentId, schedule: scheduleRef, trigger, phase: "run", error, effective }) ); return { status: "invalid-input", outcome: "failed", reason: error.message }; } const action = settled.action; const runId = "runId" in settled ? settled.runId : void 0; if (action === "deliver") { await safeHookCall( log, () => hooks?.onFinish?.({ mastra, agentId, schedule: scheduleRef, trigger, outcome: "delivered", runId, joinedExistingRun: true, effective }) ); return { status: "signal-accepted", outcome: "delivered", runId }; } if (action === "persist") { if (signalResult.persisted) { try { await signalResult.persisted; } catch { } } await safeHookCall( log, () => hooks?.onFinish?.({ mastra, agentId, schedule: scheduleRef, trigger, outcome: "persisted", runId, effective }) ); return { status: "signal-accepted", outcome: "persisted", runId }; } if (action === "discard") { await safeHookCall( log, () => hooks?.onFinish?.({ mastra, agentId, schedule: scheduleRef, trigger, outcome: "discarded", runId, effective }) ); return { status: "signal-accepted", outcome: "discarded", runId }; } if (action === "blocked") { await safeHookCall( log, () => hooks?.onFinish?.({ mastra, agentId, schedule: scheduleRef, trigger, outcome: "skipped", runId, effective }) ); return { status: "skipped-thread-blocked", outcome: "skipped", runId }; } await safeHookCall( log, () => hooks?.onFinish?.({ mastra, agentId, schedule: scheduleRef, trigger, outcome: "succeeded", runId, effective }) ); return { status: "signal-accepted", outcome: "succeeded", runId }; } try { const result = await agent.generate(effective.prompt, { providerOptions: mergeProviderOptions(effective.providerOptions, scheduleRunMeta) }); const runId = extractRunId(result); await safeHookCall( log, () => hooks?.onFinish?.({ mastra, agentId, schedule: scheduleRef, trigger, outcome: "succeeded", runId, result: extractRunSnapshot(result), effective }) ); return { status: "fired", outcome: "succeeded", runId }; } catch (err) { const error = err instanceof Error ? err : new Error(String(err)); if (isAbortError(error)) { await safeHookCall( log, () => hooks?.onAbort?.({ mastra, agentId, schedule: scheduleRef, trigger, runId: extractRunId(error) ?? scheduleId, effective }) ); return { status: "fired", outcome: "aborted" }; } await safeHookCall( log, () => hooks?.onError?.({ mastra, agentId, schedule: scheduleRef, trigger, phase: "run", error, effective }) ); return { status: "invalid-input", outcome: "failed", reason: error.message }; } } function buildEffectiveFromTarget(target) { return { threadId: target.threadId, resourceId: target.resourceId, prompt: target.prompt, signalType: target.signalType, tagName: target.tagName, attributes: target.attributes, providerOptions: target.providerOptions, ifActive: target.ifActive, ifIdle: target.ifIdle }; } function buildIfIdleOptions(ifIdle) { const requestContext = ifIdle.streamOptions?.requestContext; return { ...ifIdle.behavior ? { behavior: ifIdle.behavior } : {}, ...ifIdle.attributes ? { attributes: ifIdle.attributes } : {}, ...requestContext ? { streamOptions: { requestContext: new RequestContext(Object.entries(requestContext)) } } : {} }; } function mergeEffective(base, overrides) { if (!overrides) return base; return { ...base, ...overrides }; } function mergeProviderOptions(fromHook, scheduleRunMeta) { const base = fromHook ?? {}; const baseMastra = base.mastra ?? {}; return { ...base, mastra: { ...baseMastra, schedule: scheduleRunMeta } }; } async function loadScheduleRef(mastra, scheduleId, target, logger) { try { const schedule = await mastra.schedules.get(scheduleId); if (schedule && schedule.agentId !== void 0) return { ...schedule, agentId: schedule.agentId }; } catch (err) { logger?.debug?.("AgentScheduleWorker: failed to load schedule row for hook context", { scheduleId, error: err }); } return scheduleRefFromTarget(scheduleId, target); } function scheduleRefFromTarget(scheduleId, target) { return { id: scheduleId, agentId: target.agentId, ...target.name !== void 0 ? { name: target.name } : {} }; } async function safeHookCall(logger, fn) { try { await fn(); } catch (err) { logger?.error?.("AgentScheduleWorker: hook threw, ignoring", { error: err }); } } function isAbortError(err) { if (!err || typeof err !== "object") return false; const name = err.name; return name === "AbortError"; } function extractRunId(value) { if (value && typeof value === "object" && "runId" in value) { const runId = value.runId; if (typeof runId === "string") return runId; } return void 0; } function extractRunSnapshot(value) { if (!value || typeof value !== "object") return void 0; const v = value; const snapshot = {}; if (typeof v.text === "string") snapshot.text = v.text; if (v.usage && typeof v.usage === "object") snapshot.usage = v.usage; if (typeof v.finishReason === "string") snapshot.finishReason = v.finishReason; return Object.keys(snapshot).length > 0 ? snapshot : void 0; } export { AgentScheduleWorker, TOPIC_AGENT_SCHEDULES, executeAgentSchedule }; //# sourceMappingURL=worker-RIJ2XRDF.js.map //# sourceMappingURL=worker-RIJ2XRDF.js.map