@mastra/inngest 0.0.0-remove-unused-model-providers-api-20251030210744 → 0.0.0-remove-ai-peer-dep-from-evals-20260105220639

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,1693 +1,1601 @@
1
1
  'use strict';
2
2
 
3
- var crypto = require('crypto');
4
- var web = require('stream/web');
5
- var realtime = require('@inngest/realtime');
6
- var aiTracing = require('@mastra/core/ai-tracing');
7
- var di = require('@mastra/core/di');
8
- var stream = require('@mastra/core/stream');
3
+ var agent = require('@mastra/core/agent');
9
4
  var tools = require('@mastra/core/tools');
10
5
  var workflows = require('@mastra/core/workflows');
11
6
  var _constants = require('@mastra/core/workflows/_constants');
7
+ var zod = require('zod');
8
+ var crypto$1 = require('crypto');
9
+ var di = require('@mastra/core/di');
12
10
  var inngest = require('inngest');
11
+ var error = require('@mastra/core/error');
12
+ var realtime = require('@inngest/realtime');
13
+ var events = require('@mastra/core/events');
14
+ var web = require('stream/web');
15
+ var stream = require('@mastra/core/stream');
13
16
  var hono = require('inngest/hono');
14
- var zod = require('zod');
15
17
 
16
18
  // src/index.ts
17
- function serve({
18
- mastra,
19
- inngest,
20
- functions: userFunctions = [],
21
- registerOptions
22
- }) {
23
- const wfs = mastra.listWorkflows();
24
- const workflowFunctions = Array.from(
25
- new Set(
26
- Object.values(wfs).flatMap((wf) => {
27
- if (wf instanceof InngestWorkflow) {
28
- wf.__registerMastra(mastra);
29
- return wf.getFunctions();
30
- }
31
- return [];
32
- })
33
- )
34
- );
35
- return hono.serve({
36
- ...registerOptions,
37
- client: inngest,
38
- functions: [...workflowFunctions, ...userFunctions]
39
- });
40
- }
41
- var InngestRun = class extends workflows.Run {
42
- inngest;
43
- serializedStepGraph;
44
- #mastra;
45
- constructor(params, inngest) {
46
- super(params);
47
- this.inngest = inngest;
48
- this.serializedStepGraph = params.serializedStepGraph;
49
- this.#mastra = params.mastra;
19
+ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
20
+ inngestStep;
21
+ inngestAttempts;
22
+ constructor(mastra, inngestStep, inngestAttempts = 0, options) {
23
+ super({ mastra, options });
24
+ this.inngestStep = inngestStep;
25
+ this.inngestAttempts = inngestAttempts;
50
26
  }
51
- async getRuns(eventId) {
52
- const response = await fetch(`${this.inngest.apiBaseUrl ?? "https://api.inngest.com"}/v1/events/${eventId}/runs`, {
53
- headers: {
54
- Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`
55
- }
27
+ // =============================================================================
28
+ // Hook Overrides
29
+ // =============================================================================
30
+ /**
31
+ * Format errors while preserving Error instances and their custom properties.
32
+ * Uses getErrorFromUnknown to ensure all error properties are preserved.
33
+ */
34
+ formatResultError(error$1, lastOutput) {
35
+ const outputError = lastOutput?.error;
36
+ const errorSource = error$1 || outputError;
37
+ const errorInstance = error.getErrorFromUnknown(errorSource, {
38
+ serializeStack: true,
39
+ // Include stack in JSON for better debugging in Inngest
40
+ fallbackMessage: "Unknown workflow error"
56
41
  });
57
- const json = await response.json();
58
- return json.data;
42
+ return errorInstance.toJSON();
59
43
  }
60
- async getRunOutput(eventId) {
61
- let runs = await this.getRuns(eventId);
62
- while (runs?.[0]?.status !== "Completed" || runs?.[0]?.event_id !== eventId) {
63
- await new Promise((resolve) => setTimeout(resolve, 1e3));
64
- runs = await this.getRuns(eventId);
65
- if (runs?.[0]?.status === "Failed") {
66
- const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
67
- workflowName: this.workflowId,
68
- runId: this.runId
69
- });
70
- return {
71
- output: { result: { steps: snapshot?.context, status: "failed", error: runs?.[0]?.output?.message } }
72
- };
73
- }
74
- if (runs?.[0]?.status === "Cancelled") {
75
- const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
76
- workflowName: this.workflowId,
77
- runId: this.runId
78
- });
79
- return { output: { result: { steps: snapshot?.context, status: "canceled" } } };
80
- }
81
- }
82
- return runs?.[0];
44
+ /**
45
+ * Detect InngestWorkflow instances for special nested workflow handling
46
+ */
47
+ isNestedWorkflowStep(step) {
48
+ return step instanceof InngestWorkflow;
83
49
  }
84
- async sendEvent(event, data) {
85
- await this.inngest.send({
86
- name: `user-event-${event}`,
87
- data
88
- });
50
+ /**
51
+ * Inngest requires requestContext serialization for memoization.
52
+ * When steps are replayed, the original function doesn't re-execute,
53
+ * so requestContext modifications must be captured and restored.
54
+ */
55
+ requiresDurableContextSerialization() {
56
+ return true;
89
57
  }
90
- async cancel() {
91
- await this.inngest.send({
92
- name: `cancel.workflow.${this.workflowId}`,
93
- data: {
94
- runId: this.runId
58
+ /**
59
+ * Execute a step with retry logic for Inngest.
60
+ * Retries are handled via step-level retry (RetryAfterError thrown INSIDE step.run()).
61
+ * After retries exhausted, error propagates here and we return a failed result.
62
+ */
63
+ async executeStepWithRetry(stepId, runStep, params) {
64
+ for (let i = 0; i < params.retries + 1; i++) {
65
+ if (i > 0 && params.delay) {
66
+ await new Promise((resolve) => setTimeout(resolve, params.delay));
95
67
  }
96
- });
97
- const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
98
- workflowName: this.workflowId,
99
- runId: this.runId
100
- });
101
- if (snapshot) {
102
- await this.#mastra?.storage?.persistWorkflowSnapshot({
103
- workflowName: this.workflowId,
104
- runId: this.runId,
105
- resourceId: this.resourceId,
106
- snapshot: {
107
- ...snapshot,
108
- status: "canceled"
68
+ try {
69
+ const result = await this.wrapDurableOperation(stepId, runStep);
70
+ return { ok: true, result };
71
+ } catch (e) {
72
+ if (i === params.retries) {
73
+ const cause = e?.cause;
74
+ if (cause?.status === "failed") {
75
+ params.stepSpan?.error({
76
+ error: e,
77
+ attributes: { status: "failed" }
78
+ });
79
+ if (cause.error && !(cause.error instanceof Error)) {
80
+ cause.error = error.getErrorFromUnknown(cause.error, { serializeStack: false });
81
+ }
82
+ return { ok: false, error: cause };
83
+ }
84
+ const errorInstance = error.getErrorFromUnknown(e, {
85
+ serializeStack: false,
86
+ fallbackMessage: "Unknown step execution error"
87
+ });
88
+ params.stepSpan?.error({
89
+ error: errorInstance,
90
+ attributes: { status: "failed" }
91
+ });
92
+ return {
93
+ ok: false,
94
+ error: {
95
+ status: "failed",
96
+ error: errorInstance,
97
+ endedAt: Date.now()
98
+ }
99
+ };
109
100
  }
110
- });
101
+ }
111
102
  }
103
+ return { ok: false, error: { status: "failed", error: new Error("Unknown error"), endedAt: Date.now() } };
112
104
  }
113
- async start(params) {
114
- return this._start(params);
105
+ /**
106
+ * Use Inngest's sleep primitive for durability
107
+ */
108
+ async executeSleepDuration(duration, sleepId, workflowId) {
109
+ await this.inngestStep.sleep(`workflow.${workflowId}.sleep.${sleepId}`, duration < 0 ? 0 : duration);
115
110
  }
116
- async _start({
117
- inputData,
118
- initialState,
119
- outputOptions,
120
- tracingOptions,
121
- format
122
- }) {
123
- await this.#mastra.getStorage()?.persistWorkflowSnapshot({
124
- workflowName: this.workflowId,
125
- runId: this.runId,
126
- resourceId: this.resourceId,
127
- snapshot: {
128
- runId: this.runId,
129
- serializedStepGraph: this.serializedStepGraph,
130
- value: {},
131
- context: {},
132
- activePaths: [],
133
- suspendedPaths: {},
134
- resumeLabels: {},
135
- waitingPaths: {},
136
- timestamp: Date.now(),
137
- status: "running"
138
- }
139
- });
140
- const inputDataToUse = await this._validateInput(inputData);
141
- const initialStateToUse = await this._validateInitialState(initialState ?? {});
142
- const eventOutput = await this.inngest.send({
143
- name: `workflow.${this.workflowId}`,
144
- data: {
145
- inputData: inputDataToUse,
146
- initialState: initialStateToUse,
147
- runId: this.runId,
148
- resourceId: this.resourceId,
149
- outputOptions,
150
- tracingOptions,
151
- format
152
- }
153
- });
154
- const eventId = eventOutput.ids[0];
155
- if (!eventId) {
156
- throw new Error("Event ID is not set");
157
- }
158
- const runOutput = await this.getRunOutput(eventId);
159
- const result = runOutput?.output?.result;
160
- if (result.status === "failed") {
161
- result.error = new Error(result.error);
162
- }
163
- if (result.status !== "suspended") {
164
- this.cleanup?.();
165
- }
166
- return result;
111
+ /**
112
+ * Use Inngest's sleepUntil primitive for durability
113
+ */
114
+ async executeSleepUntilDate(date, sleepUntilId, workflowId) {
115
+ await this.inngestStep.sleepUntil(`workflow.${workflowId}.sleepUntil.${sleepUntilId}`, date);
167
116
  }
168
- async resume(params) {
169
- const p = this._resume(params).then((result) => {
170
- if (result.status !== "suspended") {
171
- this.closeStreamAction?.().catch(() => {
172
- });
117
+ /**
118
+ * Wrap durable operations in Inngest step.run() for durability.
119
+ * If retryConfig is provided, throws RetryAfterError INSIDE step.run() to trigger
120
+ * Inngest's step-level retry mechanism (not function-level retry).
121
+ */
122
+ async wrapDurableOperation(operationId, operationFn) {
123
+ return this.inngestStep.run(operationId, async () => {
124
+ try {
125
+ return await operationFn();
126
+ } catch (e) {
127
+ throw e;
173
128
  }
174
- return result;
175
129
  });
176
- this.executionResults = p;
177
- return p;
178
130
  }
179
- async _resume(params) {
180
- const steps = (Array.isArray(params.step) ? params.step : [params.step]).map(
181
- (step) => typeof step === "string" ? step : step?.id
182
- );
183
- const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
184
- workflowName: this.workflowId,
185
- runId: this.runId
186
- });
187
- const suspendedStep = this.workflowSteps[steps?.[0] ?? ""];
188
- const resumeDataToUse = await this._validateResumeData(params.resumeData, suspendedStep);
189
- const eventOutput = await this.inngest.send({
190
- name: `workflow.${this.workflowId}`,
191
- data: {
192
- inputData: resumeDataToUse,
193
- initialState: snapshot?.value ?? {},
194
- runId: this.runId,
195
- workflowId: this.workflowId,
196
- stepResults: snapshot?.context,
197
- resume: {
198
- steps,
199
- stepResults: snapshot?.context,
200
- resumePayload: resumeDataToUse,
201
- // @ts-ignore
202
- resumePath: snapshot?.suspendedPaths?.[steps?.[0]]
203
- }
204
- }
205
- });
206
- const eventId = eventOutput.ids[0];
207
- if (!eventId) {
208
- throw new Error("Event ID is not set");
209
- }
210
- const runOutput = await this.getRunOutput(eventId);
211
- const result = runOutput?.output?.result;
212
- if (result.status === "failed") {
213
- result.error = new Error(result.error);
214
- }
215
- return result;
131
+ /**
132
+ * Provide Inngest step primitive in engine context
133
+ */
134
+ getEngineContext() {
135
+ return { step: this.inngestStep };
216
136
  }
217
- watch(cb, type = "watch") {
218
- let active = true;
219
- const streamPromise = realtime.subscribe(
220
- {
221
- channel: `workflow:${this.workflowId}:${this.runId}`,
222
- topics: [type],
223
- app: this.inngest
224
- },
225
- (message) => {
226
- if (active) {
227
- cb(message.data);
228
- }
229
- }
230
- );
231
- return () => {
232
- active = false;
233
- streamPromise.then(async (stream) => {
234
- return stream.cancel();
235
- }).catch((err) => {
236
- console.error(err);
237
- });
238
- };
137
+ /**
138
+ * For Inngest, lifecycle callbacks are invoked in the workflow's finalize step
139
+ * (wrapped in step.run for durability), not in execute(). Override to skip.
140
+ */
141
+ async invokeLifecycleCallbacks(_result) {
239
142
  }
240
- streamLegacy({ inputData, requestContext } = {}) {
241
- const { readable, writable } = new TransformStream();
242
- const writer = writable.getWriter();
243
- const unwatch = this.watch(async (event) => {
244
- try {
245
- await writer.write({
246
- // @ts-ignore
247
- type: "start",
248
- // @ts-ignore
249
- payload: { runId: this.runId }
143
+ /**
144
+ * Actually invoke the lifecycle callbacks. Called from workflow.ts finalize step.
145
+ */
146
+ async invokeLifecycleCallbacksInternal(result) {
147
+ return super.invokeLifecycleCallbacks(result);
148
+ }
149
+ /**
150
+ * Execute nested InngestWorkflow using inngestStep.invoke() for durability.
151
+ * This MUST be called directly (not inside step.run()) due to Inngest constraints.
152
+ */
153
+ async executeWorkflowStep(params) {
154
+ if (!(params.step instanceof InngestWorkflow)) {
155
+ return null;
156
+ }
157
+ const {
158
+ step,
159
+ stepResults,
160
+ executionContext,
161
+ resume,
162
+ timeTravel,
163
+ prevOutput,
164
+ inputData,
165
+ pubsub,
166
+ startedAt,
167
+ perStep
168
+ } = params;
169
+ const isResume = !!resume?.steps?.length;
170
+ let result;
171
+ let runId;
172
+ const isTimeTravel = !!(timeTravel && timeTravel.steps?.length > 1 && timeTravel.steps[0] === step.id);
173
+ try {
174
+ if (isResume) {
175
+ runId = stepResults[resume?.steps?.[0] ?? ""]?.suspendPayload?.__workflow_meta?.runId ?? crypto$1.randomUUID();
176
+ const workflowsStore = await this.mastra?.getStorage()?.getStore("workflows");
177
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
178
+ workflowName: step.id,
179
+ runId
250
180
  });
251
- const e = {
252
- ...event,
253
- type: event.type.replace("workflow-", "")
181
+ const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
182
+ function: step.getFunction(),
183
+ data: {
184
+ inputData,
185
+ initialState: executionContext.state ?? snapshot?.value ?? {},
186
+ runId,
187
+ resume: {
188
+ runId,
189
+ steps: resume.steps.slice(1),
190
+ stepResults: snapshot?.context,
191
+ resumePayload: resume.resumePayload,
192
+ resumePath: resume.steps?.[1] ? snapshot?.suspendedPaths?.[resume.steps?.[1]] : void 0
193
+ },
194
+ outputOptions: { includeState: true },
195
+ perStep
196
+ }
197
+ });
198
+ result = invokeResp.result;
199
+ runId = invokeResp.runId;
200
+ executionContext.state = invokeResp.result.state;
201
+ } else if (isTimeTravel) {
202
+ const workflowsStoreForTimeTravel = await this.mastra?.getStorage()?.getStore("workflows");
203
+ const snapshot = await workflowsStoreForTimeTravel?.loadWorkflowSnapshot({
204
+ workflowName: step.id,
205
+ runId: executionContext.runId
206
+ }) ?? { context: {} };
207
+ const timeTravelParams = workflows.createTimeTravelExecutionParams({
208
+ steps: timeTravel.steps.slice(1),
209
+ inputData: timeTravel.inputData,
210
+ resumeData: timeTravel.resumeData,
211
+ context: timeTravel.nestedStepResults?.[step.id] ?? {},
212
+ nestedStepsContext: timeTravel.nestedStepResults ?? {},
213
+ snapshot,
214
+ graph: step.buildExecutionGraph()
215
+ });
216
+ const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
217
+ function: step.getFunction(),
218
+ data: {
219
+ timeTravel: timeTravelParams,
220
+ initialState: executionContext.state ?? {},
221
+ runId: executionContext.runId,
222
+ outputOptions: { includeState: true },
223
+ perStep
224
+ }
225
+ });
226
+ result = invokeResp.result;
227
+ runId = invokeResp.runId;
228
+ executionContext.state = invokeResp.result.state;
229
+ } else {
230
+ const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
231
+ function: step.getFunction(),
232
+ data: {
233
+ inputData,
234
+ initialState: executionContext.state ?? {},
235
+ outputOptions: { includeState: true },
236
+ perStep
237
+ }
238
+ });
239
+ result = invokeResp.result;
240
+ runId = invokeResp.runId;
241
+ executionContext.state = invokeResp.result.state;
242
+ }
243
+ } catch (e) {
244
+ const errorCause = e?.cause;
245
+ if (errorCause && typeof errorCause === "object") {
246
+ result = errorCause;
247
+ runId = errorCause.runId || crypto$1.randomUUID();
248
+ } else {
249
+ runId = crypto$1.randomUUID();
250
+ result = {
251
+ status: "failed",
252
+ error: e instanceof Error ? e : new Error(String(e)),
253
+ steps: {},
254
+ input: inputData
254
255
  };
255
- if (e.type === "step-output") {
256
- e.type = e.payload.output.type;
257
- e.payload = e.payload.output.payload;
258
- }
259
- await writer.write(e);
260
- } catch {
261
- }
262
- }, "watch-v2");
263
- this.closeStreamAction = async () => {
264
- await writer.write({
265
- type: "finish",
266
- // @ts-ignore
267
- payload: { runId: this.runId }
268
- });
269
- unwatch();
270
- try {
271
- await writer.close();
272
- } catch (err) {
273
- console.error("Error closing stream:", err);
274
- } finally {
275
- writer.releaseLock();
276
- }
277
- };
278
- this.executionResults = this._start({ inputData, requestContext, format: "legacy" }).then((result) => {
279
- if (result.status !== "suspended") {
280
- this.closeStreamAction?.().catch(() => {
281
- });
282
256
  }
283
- return result;
284
- });
285
- return {
286
- stream: readable,
287
- getWorkflowState: () => this.executionResults
288
- };
289
- }
290
- stream({
291
- inputData,
292
- requestContext,
293
- tracingOptions,
294
- closeOnSuspend = true,
295
- initialState,
296
- outputOptions
297
- } = {}) {
298
- if (this.closeStreamAction && this.streamOutput) {
299
- return this.streamOutput;
300
257
  }
301
- this.closeStreamAction = async () => {
302
- };
303
- const self = this;
304
- const stream$1 = new web.ReadableStream({
305
- async start(controller) {
306
- const unwatch = self.watch(async ({ type, from = stream.ChunkFrom.WORKFLOW, payload }) => {
307
- controller.enqueue({
308
- type,
309
- runId: self.runId,
310
- from,
311
- payload: {
312
- stepName: payload?.id,
313
- ...payload
258
+ const res = await this.inngestStep.run(
259
+ `workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
260
+ async () => {
261
+ if (result.status === "failed") {
262
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
263
+ type: "watch",
264
+ runId: executionContext.runId,
265
+ data: {
266
+ type: "workflow-step-result",
267
+ payload: {
268
+ id: step.id,
269
+ status: "failed",
270
+ error: result?.error,
271
+ payload: prevOutput
272
+ }
314
273
  }
315
274
  });
316
- }, "watch-v2");
317
- self.closeStreamAction = async () => {
318
- unwatch();
319
- try {
320
- await controller.close();
321
- } catch (err) {
322
- console.error("Error closing stream:", err);
323
- }
324
- };
325
- const executionResultsPromise = self._start({
326
- inputData,
327
- requestContext,
328
- // tracingContext, // We are not able to pass a reference to a span here, what to do?
329
- initialState,
330
- tracingOptions,
331
- outputOptions,
332
- format: "vnext"
333
- });
334
- let executionResults;
335
- try {
336
- executionResults = await executionResultsPromise;
337
- if (closeOnSuspend) {
338
- self.closeStreamAction?.().catch(() => {
339
- });
340
- } else if (executionResults.status !== "suspended") {
341
- self.closeStreamAction?.().catch(() => {
275
+ return { executionContext, result: { status: "failed", error: result?.error, endedAt: Date.now() } };
276
+ } else if (result.status === "suspended") {
277
+ const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
278
+ const stepRes = stepResult;
279
+ return stepRes?.status === "suspended";
280
+ });
281
+ for (const [stepName, stepResult] of suspendedSteps) {
282
+ const suspendPath = [stepName, ...stepResult?.suspendPayload?.__workflow_meta?.path ?? []];
283
+ executionContext.suspendedPaths[step.id] = executionContext.executionPath;
284
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
285
+ type: "watch",
286
+ runId: executionContext.runId,
287
+ data: {
288
+ type: "workflow-step-suspended",
289
+ payload: {
290
+ id: step.id,
291
+ status: "suspended"
292
+ }
293
+ }
342
294
  });
295
+ return {
296
+ executionContext,
297
+ result: {
298
+ status: "suspended",
299
+ suspendedAt: Date.now(),
300
+ payload: stepResult.payload,
301
+ suspendPayload: {
302
+ ...stepResult?.suspendPayload,
303
+ __workflow_meta: { runId, path: suspendPath }
304
+ }
305
+ }
306
+ };
343
307
  }
344
- if (self.streamOutput) {
345
- self.streamOutput.updateResults(
346
- executionResults
347
- );
348
- }
349
- } catch (err) {
350
- self.streamOutput?.rejectResults(err);
351
- self.closeStreamAction?.().catch(() => {
308
+ return {
309
+ executionContext,
310
+ result: {
311
+ status: "suspended",
312
+ suspendedAt: Date.now(),
313
+ payload: {}
314
+ }
315
+ };
316
+ } else if (result.status === "tripwire") {
317
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
318
+ type: "watch",
319
+ runId: executionContext.runId,
320
+ data: {
321
+ type: "workflow-step-result",
322
+ payload: {
323
+ id: step.id,
324
+ status: "tripwire",
325
+ error: result?.tripwire?.reason,
326
+ payload: prevOutput
327
+ }
328
+ }
329
+ });
330
+ return {
331
+ executionContext,
332
+ result: {
333
+ status: "tripwire",
334
+ tripwire: result?.tripwire,
335
+ endedAt: Date.now()
336
+ }
337
+ };
338
+ } else if (perStep || result.status === "paused") {
339
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
340
+ type: "watch",
341
+ runId: executionContext.runId,
342
+ data: {
343
+ type: "workflow-step-result",
344
+ payload: {
345
+ id: step.id,
346
+ status: "paused"
347
+ }
348
+ }
349
+ });
350
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
351
+ type: "watch",
352
+ runId: executionContext.runId,
353
+ data: {
354
+ type: "workflow-step-finish",
355
+ payload: {
356
+ id: step.id,
357
+ metadata: {}
358
+ }
359
+ }
352
360
  });
361
+ return { executionContext, result: { status: "paused" } };
353
362
  }
363
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
364
+ type: "watch",
365
+ runId: executionContext.runId,
366
+ data: {
367
+ type: "workflow-step-result",
368
+ payload: {
369
+ id: step.id,
370
+ status: "success",
371
+ output: result?.result
372
+ }
373
+ }
374
+ });
375
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
376
+ type: "watch",
377
+ runId: executionContext.runId,
378
+ data: {
379
+ type: "workflow-step-finish",
380
+ payload: {
381
+ id: step.id,
382
+ metadata: {}
383
+ }
384
+ }
385
+ });
386
+ return { executionContext, result: { status: "success", output: result?.result, endedAt: Date.now() } };
354
387
  }
355
- });
356
- this.streamOutput = new stream.WorkflowRunOutput({
357
- runId: this.runId,
358
- workflowId: this.workflowId,
359
- stream: stream$1
360
- });
361
- return this.streamOutput;
362
- }
363
- streamVNext(args = {}) {
364
- return this.stream(args);
388
+ );
389
+ Object.assign(executionContext, res.executionContext);
390
+ return {
391
+ ...res.result,
392
+ startedAt,
393
+ payload: inputData,
394
+ resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
395
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
396
+ };
365
397
  }
366
398
  };
367
- var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
368
- #mastra;
399
+ var InngestPubSub = class extends events.PubSub {
369
400
  inngest;
370
- function;
371
- flowControlConfig;
372
- constructor(params, inngest) {
373
- const { concurrency, rateLimit, throttle, debounce, priority, ...workflowParams } = params;
374
- super(workflowParams);
375
- const flowControlEntries = Object.entries({ concurrency, rateLimit, throttle, debounce, priority }).filter(
376
- ([_, value]) => value !== void 0
377
- );
378
- this.flowControlConfig = flowControlEntries.length > 0 ? Object.fromEntries(flowControlEntries) : void 0;
379
- this.#mastra = params.mastra;
401
+ workflowId;
402
+ publishFn;
403
+ subscriptions = /* @__PURE__ */ new Map();
404
+ constructor(inngest, workflowId, publishFn) {
405
+ super();
380
406
  this.inngest = inngest;
407
+ this.workflowId = workflowId;
408
+ this.publishFn = publishFn;
381
409
  }
382
- async listWorkflowRuns(args) {
383
- const storage = this.#mastra?.getStorage();
384
- if (!storage) {
385
- this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
386
- return { runs: [], total: 0 };
410
+ /**
411
+ * Publish an event to Inngest's realtime system.
412
+ *
413
+ * Topic format: "workflow.events.v2.{runId}"
414
+ * Maps to Inngest channel: "workflow:{workflowId}:{runId}"
415
+ */
416
+ async publish(topic, event) {
417
+ if (!this.publishFn) {
418
+ return;
387
419
  }
388
- return storage.listWorkflowRuns({ workflowName: this.id, ...args ?? {} });
389
- }
390
- async getWorkflowRunById(runId) {
391
- const storage = this.#mastra?.getStorage();
392
- if (!storage) {
393
- this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
394
- return this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null;
420
+ const match = topic.match(/^workflow\.events\.v2\.(.+)$/);
421
+ if (!match) {
422
+ return;
395
423
  }
396
- const run = await storage.getWorkflowRunById({ runId, workflowName: this.id });
397
- return run ?? (this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null);
398
- }
399
- __registerMastra(mastra) {
400
- this.#mastra = mastra;
401
- this.executionEngine.__registerMastra(mastra);
402
- const updateNested = (step) => {
403
- if ((step.type === "step" || step.type === "loop" || step.type === "foreach") && step.step instanceof _InngestWorkflow) {
404
- step.step.__registerMastra(mastra);
405
- } else if (step.type === "parallel" || step.type === "conditional") {
406
- for (const subStep of step.steps) {
407
- updateNested(subStep);
408
- }
409
- }
410
- };
411
- if (this.executionGraph.steps.length) {
412
- for (const step of this.executionGraph.steps) {
413
- updateNested(step);
414
- }
424
+ const runId = match[1];
425
+ try {
426
+ await this.publishFn({
427
+ channel: `workflow:${this.workflowId}:${runId}`,
428
+ topic: "watch",
429
+ data: event.data
430
+ });
431
+ } catch (err) {
432
+ console.error("InngestPubSub publish error:", err?.message ?? err);
415
433
  }
416
434
  }
417
435
  /**
418
- * @deprecated Use createRunAsync() instead.
419
- * @throws {Error} Always throws an error directing users to use createRunAsync()
436
+ * Subscribe to events from Inngest's realtime system.
437
+ *
438
+ * Topic format: "workflow.events.v2.{runId}"
439
+ * Maps to Inngest channel: "workflow:{workflowId}:{runId}"
420
440
  */
421
- createRun(_options) {
422
- throw new Error(
423
- "createRun() has been deprecated. Please use createRunAsync() instead.\n\nMigration guide:\n Before: const run = workflow.createRun();\n After: const run = await workflow.createRunAsync();\n\nNote: createRunAsync() is an async method, so make sure your calling function is async."
424
- );
425
- }
426
- async createRunAsync(options) {
427
- const runIdToUse = options?.runId || crypto.randomUUID();
428
- const run = this.runs.get(runIdToUse) ?? new InngestRun(
441
+ async subscribe(topic, cb) {
442
+ const match = topic.match(/^workflow\.events\.v2\.(.+)$/);
443
+ if (!match || !match[1]) {
444
+ return;
445
+ }
446
+ const runId = match[1];
447
+ if (this.subscriptions.has(topic)) {
448
+ this.subscriptions.get(topic).callbacks.add(cb);
449
+ return;
450
+ }
451
+ const callbacks = /* @__PURE__ */ new Set([cb]);
452
+ const channel = `workflow:${this.workflowId}:${runId}`;
453
+ const streamPromise = realtime.subscribe(
429
454
  {
430
- workflowId: this.id,
431
- runId: runIdToUse,
432
- resourceId: options?.resourceId,
433
- executionEngine: this.executionEngine,
434
- executionGraph: this.executionGraph,
435
- serializedStepGraph: this.serializedStepGraph,
436
- mastra: this.#mastra,
437
- retryConfig: this.retryConfig,
438
- cleanup: () => this.runs.delete(runIdToUse),
439
- workflowSteps: this.steps
455
+ channel,
456
+ topics: ["watch"],
457
+ app: this.inngest
440
458
  },
441
- this.inngest
459
+ (message) => {
460
+ const event = {
461
+ id: crypto.randomUUID(),
462
+ type: "watch",
463
+ runId,
464
+ data: message.data,
465
+ createdAt: /* @__PURE__ */ new Date()
466
+ };
467
+ for (const callback of callbacks) {
468
+ callback(event);
469
+ }
470
+ }
442
471
  );
443
- this.runs.set(runIdToUse, run);
444
- const shouldPersistSnapshot = this.options.shouldPersistSnapshot({
445
- workflowStatus: run.workflowRunStatus,
446
- stepResults: {}
472
+ this.subscriptions.set(topic, {
473
+ unsubscribe: () => {
474
+ streamPromise.then((stream) => stream.cancel()).catch((err) => {
475
+ console.error("InngestPubSub unsubscribe error:", err);
476
+ });
477
+ },
478
+ callbacks
447
479
  });
448
- const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse, false);
449
- if (!workflowSnapshotInStorage && shouldPersistSnapshot) {
450
- await this.mastra?.getStorage()?.persistWorkflowSnapshot({
451
- workflowName: this.id,
452
- runId: runIdToUse,
453
- resourceId: options?.resourceId,
454
- snapshot: {
455
- runId: runIdToUse,
456
- status: "pending",
457
- value: {},
458
- context: {},
459
- activePaths: [],
460
- waitingPaths: {},
480
+ }
481
+ /**
482
+ * Unsubscribe a callback from a topic.
483
+ * If no callbacks remain, the underlying Inngest subscription is cancelled.
484
+ */
485
+ async unsubscribe(topic, cb) {
486
+ const sub = this.subscriptions.get(topic);
487
+ if (!sub) {
488
+ return;
489
+ }
490
+ sub.callbacks.delete(cb);
491
+ if (sub.callbacks.size === 0) {
492
+ sub.unsubscribe();
493
+ this.subscriptions.delete(topic);
494
+ }
495
+ }
496
+ /**
497
+ * Flush any pending operations. No-op for Inngest.
498
+ */
499
+ async flush() {
500
+ }
501
+ /**
502
+ * Clean up all subscriptions during graceful shutdown.
503
+ */
504
+ async close() {
505
+ for (const [, sub] of this.subscriptions) {
506
+ sub.unsubscribe();
507
+ }
508
+ this.subscriptions.clear();
509
+ }
510
+ };
511
+ var InngestRun = class extends workflows.Run {
512
+ inngest;
513
+ serializedStepGraph;
514
+ #mastra;
515
+ constructor(params, inngest) {
516
+ super(params);
517
+ this.inngest = inngest;
518
+ this.serializedStepGraph = params.serializedStepGraph;
519
+ this.#mastra = params.mastra;
520
+ }
521
+ async getRuns(eventId) {
522
+ const maxRetries = 3;
523
+ let lastError = null;
524
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
525
+ try {
526
+ const response = await fetch(
527
+ `${this.inngest.apiBaseUrl ?? "https://api.inngest.com"}/v1/events/${eventId}/runs`,
528
+ {
529
+ headers: {
530
+ Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`
531
+ }
532
+ }
533
+ );
534
+ if (response.status === 429) {
535
+ const retryAfter = parseInt(response.headers.get("retry-after") || "2", 10);
536
+ await new Promise((resolve) => setTimeout(resolve, retryAfter * 1e3));
537
+ continue;
538
+ }
539
+ if (!response.ok) {
540
+ throw new Error(`Inngest API error: ${response.status} ${response.statusText}`);
541
+ }
542
+ const text = await response.text();
543
+ if (!text) {
544
+ await new Promise((resolve) => setTimeout(resolve, 1e3 * (attempt + 1)));
545
+ continue;
546
+ }
547
+ const json = JSON.parse(text);
548
+ return json.data;
549
+ } catch (error) {
550
+ lastError = error;
551
+ if (attempt < maxRetries - 1) {
552
+ await new Promise((resolve) => setTimeout(resolve, 1e3 * Math.pow(2, attempt)));
553
+ }
554
+ }
555
+ }
556
+ throw new inngest.NonRetriableError(`Failed to get runs after ${maxRetries} attempts: ${lastError?.message}`);
557
+ }
558
+ async getRunOutput(eventId, maxWaitMs = 3e5) {
559
+ const startTime = Date.now();
560
+ const storage = this.#mastra?.getStorage();
561
+ const workflowsStore = await storage?.getStore("workflows");
562
+ while (Date.now() - startTime < maxWaitMs) {
563
+ let runs;
564
+ try {
565
+ runs = await this.getRuns(eventId);
566
+ } catch (error) {
567
+ if (error instanceof inngest.NonRetriableError) {
568
+ throw error;
569
+ }
570
+ throw new inngest.NonRetriableError(
571
+ `Failed to poll workflow status: ${error instanceof Error ? error.message : String(error)}`
572
+ );
573
+ }
574
+ if (runs?.[0]?.status === "Completed" && runs?.[0]?.event_id === eventId) {
575
+ return runs[0];
576
+ }
577
+ if (runs?.[0]?.status === "Failed") {
578
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
579
+ workflowName: this.workflowId,
580
+ runId: this.runId
581
+ });
582
+ if (snapshot?.context) {
583
+ snapshot.context = workflows.hydrateSerializedStepErrors(snapshot.context);
584
+ }
585
+ return {
586
+ output: {
587
+ result: {
588
+ steps: snapshot?.context,
589
+ status: "failed",
590
+ // Get the original error from NonRetriableError's cause (which contains the workflow result)
591
+ error: error.getErrorFromUnknown(runs?.[0]?.output?.cause?.error, { serializeStack: false })
592
+ }
593
+ }
594
+ };
595
+ }
596
+ if (runs?.[0]?.status === "Cancelled") {
597
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
598
+ workflowName: this.workflowId,
599
+ runId: this.runId
600
+ });
601
+ return { output: { result: { steps: snapshot?.context, status: "canceled" } } };
602
+ }
603
+ await new Promise((resolve) => setTimeout(resolve, 1e3 + Math.random() * 1e3));
604
+ }
605
+ throw new inngest.NonRetriableError(`Workflow did not complete within ${maxWaitMs}ms`);
606
+ }
607
+ async cancel() {
608
+ const storage = this.#mastra?.getStorage();
609
+ await this.inngest.send({
610
+ name: `cancel.workflow.${this.workflowId}`,
611
+ data: {
612
+ runId: this.runId
613
+ }
614
+ });
615
+ const workflowsStore = await storage?.getStore("workflows");
616
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
617
+ workflowName: this.workflowId,
618
+ runId: this.runId
619
+ });
620
+ if (snapshot) {
621
+ await workflowsStore?.persistWorkflowSnapshot({
622
+ workflowName: this.workflowId,
623
+ runId: this.runId,
624
+ resourceId: this.resourceId,
625
+ snapshot: {
626
+ ...snapshot,
627
+ status: "canceled",
628
+ value: snapshot.value
629
+ }
630
+ });
631
+ }
632
+ }
633
+ async start(params) {
634
+ return this._start(params);
635
+ }
636
+ /**
637
+ * Starts the workflow execution without waiting for completion (fire-and-forget).
638
+ * Returns immediately with the runId after sending the event to Inngest.
639
+ * The workflow executes independently in Inngest.
640
+ * Use this when you don't need to wait for the result or want to avoid polling failures.
641
+ */
642
+ async startAsync(params) {
643
+ const workflowsStore = await this.#mastra.getStorage()?.getStore("workflows");
644
+ await workflowsStore?.persistWorkflowSnapshot({
645
+ workflowName: this.workflowId,
646
+ runId: this.runId,
647
+ resourceId: this.resourceId,
648
+ snapshot: {
649
+ runId: this.runId,
650
+ serializedStepGraph: this.serializedStepGraph,
651
+ status: "running",
652
+ value: {},
653
+ context: {},
654
+ activePaths: [],
655
+ suspendedPaths: {},
656
+ activeStepsPath: {},
657
+ resumeLabels: {},
658
+ waitingPaths: {},
659
+ timestamp: Date.now()
660
+ }
661
+ });
662
+ const inputDataToUse = await this._validateInput(params.inputData);
663
+ const initialStateToUse = await this._validateInitialState(params.initialState ?? {});
664
+ const eventOutput = await this.inngest.send({
665
+ name: `workflow.${this.workflowId}`,
666
+ data: {
667
+ inputData: inputDataToUse,
668
+ initialState: initialStateToUse,
669
+ runId: this.runId,
670
+ resourceId: this.resourceId,
671
+ outputOptions: params.outputOptions,
672
+ tracingOptions: params.tracingOptions,
673
+ requestContext: params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {},
674
+ perStep: params.perStep
675
+ }
676
+ });
677
+ const eventId = eventOutput.ids[0];
678
+ if (!eventId) {
679
+ throw new Error("Event ID is not set");
680
+ }
681
+ return { runId: this.runId };
682
+ }
683
+ async _start({
684
+ inputData,
685
+ initialState,
686
+ outputOptions,
687
+ tracingOptions,
688
+ format,
689
+ requestContext,
690
+ perStep
691
+ }) {
692
+ const workflowsStore = await this.#mastra.getStorage()?.getStore("workflows");
693
+ await workflowsStore?.persistWorkflowSnapshot({
694
+ workflowName: this.workflowId,
695
+ runId: this.runId,
696
+ resourceId: this.resourceId,
697
+ snapshot: {
698
+ runId: this.runId,
699
+ serializedStepGraph: this.serializedStepGraph,
700
+ status: "running",
701
+ value: {},
702
+ context: {},
703
+ activePaths: [],
704
+ suspendedPaths: {},
705
+ activeStepsPath: {},
706
+ resumeLabels: {},
707
+ waitingPaths: {},
708
+ timestamp: Date.now()
709
+ }
710
+ });
711
+ const inputDataToUse = await this._validateInput(inputData);
712
+ const initialStateToUse = await this._validateInitialState(initialState ?? {});
713
+ const eventOutput = await this.inngest.send({
714
+ name: `workflow.${this.workflowId}`,
715
+ data: {
716
+ inputData: inputDataToUse,
717
+ initialState: initialStateToUse,
718
+ runId: this.runId,
719
+ resourceId: this.resourceId,
720
+ outputOptions,
721
+ tracingOptions,
722
+ format,
723
+ requestContext: requestContext ? Object.fromEntries(requestContext.entries()) : {},
724
+ perStep
725
+ }
726
+ });
727
+ const eventId = eventOutput.ids[0];
728
+ if (!eventId) {
729
+ throw new Error("Event ID is not set");
730
+ }
731
+ const runOutput = await this.getRunOutput(eventId);
732
+ const result = runOutput?.output?.result;
733
+ this.hydrateFailedResult(result);
734
+ if (result.status !== "suspended") {
735
+ this.cleanup?.();
736
+ }
737
+ return result;
738
+ }
739
+ async resume(params) {
740
+ const p = this._resume(params).then((result) => {
741
+ if (result.status !== "suspended") {
742
+ this.closeStreamAction?.().catch(() => {
743
+ });
744
+ }
745
+ return result;
746
+ });
747
+ this.executionResults = p;
748
+ return p;
749
+ }
750
+ async _resume(params) {
751
+ const storage = this.#mastra?.getStorage();
752
+ let steps = [];
753
+ if (typeof params.step === "string") {
754
+ steps = params.step.split(".");
755
+ } else {
756
+ steps = (Array.isArray(params.step) ? params.step : [params.step]).map(
757
+ (step) => typeof step === "string" ? step : step?.id
758
+ );
759
+ }
760
+ const workflowsStore = await storage?.getStore("workflows");
761
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
762
+ workflowName: this.workflowId,
763
+ runId: this.runId
764
+ });
765
+ const suspendedStep = this.workflowSteps[steps?.[0] ?? ""];
766
+ const resumeDataToUse = await this._validateResumeData(params.resumeData, suspendedStep);
767
+ const persistedRequestContext = snapshot?.requestContext ?? {};
768
+ const newRequestContext = params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {};
769
+ const mergedRequestContext = { ...persistedRequestContext, ...newRequestContext };
770
+ const eventOutput = await this.inngest.send({
771
+ name: `workflow.${this.workflowId}`,
772
+ data: {
773
+ inputData: resumeDataToUse,
774
+ initialState: snapshot?.value ?? {},
775
+ runId: this.runId,
776
+ workflowId: this.workflowId,
777
+ stepResults: snapshot?.context,
778
+ resume: {
779
+ steps,
780
+ stepResults: snapshot?.context,
781
+ resumePayload: resumeDataToUse,
782
+ resumePath: steps?.[0] ? snapshot?.suspendedPaths?.[steps?.[0]] : void 0
783
+ },
784
+ requestContext: mergedRequestContext,
785
+ perStep: params.perStep
786
+ }
787
+ });
788
+ const eventId = eventOutput.ids[0];
789
+ if (!eventId) {
790
+ throw new Error("Event ID is not set");
791
+ }
792
+ const runOutput = await this.getRunOutput(eventId);
793
+ const result = runOutput?.output?.result;
794
+ this.hydrateFailedResult(result);
795
+ return result;
796
+ }
797
+ async timeTravel(params) {
798
+ const p = this._timeTravel(params).then((result) => {
799
+ if (result.status !== "suspended") {
800
+ this.closeStreamAction?.().catch(() => {
801
+ });
802
+ }
803
+ return result;
804
+ });
805
+ this.executionResults = p;
806
+ return p;
807
+ }
808
+ async _timeTravel(params) {
809
+ if (!params.step || Array.isArray(params.step) && params.step?.length === 0) {
810
+ throw new Error("Step is required and must be a valid step or array of steps");
811
+ }
812
+ let steps = [];
813
+ if (typeof params.step === "string") {
814
+ steps = params.step.split(".");
815
+ } else {
816
+ steps = (Array.isArray(params.step) ? params.step : [params.step]).map(
817
+ (step) => typeof step === "string" ? step : step?.id
818
+ );
819
+ }
820
+ if (steps.length === 0) {
821
+ throw new Error("No steps provided to timeTravel");
822
+ }
823
+ const storage = this.#mastra?.getStorage();
824
+ const workflowsStore = await storage?.getStore("workflows");
825
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
826
+ workflowName: this.workflowId,
827
+ runId: this.runId
828
+ });
829
+ if (!snapshot) {
830
+ await workflowsStore?.persistWorkflowSnapshot({
831
+ workflowName: this.workflowId,
832
+ runId: this.runId,
833
+ resourceId: this.resourceId,
834
+ snapshot: {
835
+ runId: this.runId,
461
836
  serializedStepGraph: this.serializedStepGraph,
837
+ status: "pending",
838
+ value: {},
839
+ context: {},
840
+ activePaths: [],
462
841
  suspendedPaths: {},
842
+ activeStepsPath: {},
463
843
  resumeLabels: {},
464
- result: void 0,
465
- error: void 0,
466
- // @ts-ignore
844
+ waitingPaths: {},
467
845
  timestamp: Date.now()
468
846
  }
469
847
  });
470
848
  }
471
- return run;
472
- }
473
- getFunction() {
474
- if (this.function) {
475
- return this.function;
849
+ if (snapshot?.status === "running") {
850
+ throw new Error("This workflow run is still running, cannot time travel");
476
851
  }
477
- this.function = this.inngest.createFunction(
852
+ let inputDataToUse = params.inputData;
853
+ if (inputDataToUse && steps.length === 1) {
854
+ inputDataToUse = await this._validateTimetravelInputData(params.inputData, this.workflowSteps[steps[0]]);
855
+ }
856
+ const timeTravelData = workflows.createTimeTravelExecutionParams({
857
+ steps,
858
+ inputData: inputDataToUse,
859
+ resumeData: params.resumeData,
860
+ context: params.context,
861
+ nestedStepsContext: params.nestedStepsContext,
862
+ snapshot: snapshot ?? { context: {} },
863
+ graph: this.executionGraph,
864
+ initialState: params.initialState,
865
+ perStep: params.perStep
866
+ });
867
+ const eventOutput = await this.inngest.send({
868
+ name: `workflow.${this.workflowId}`,
869
+ data: {
870
+ initialState: timeTravelData.state,
871
+ runId: this.runId,
872
+ workflowId: this.workflowId,
873
+ stepResults: timeTravelData.stepResults,
874
+ timeTravel: timeTravelData,
875
+ tracingOptions: params.tracingOptions,
876
+ outputOptions: params.outputOptions,
877
+ requestContext: params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {},
878
+ perStep: params.perStep
879
+ }
880
+ });
881
+ const eventId = eventOutput.ids[0];
882
+ if (!eventId) {
883
+ throw new Error("Event ID is not set");
884
+ }
885
+ const runOutput = await this.getRunOutput(eventId);
886
+ const result = runOutput?.output?.result;
887
+ this.hydrateFailedResult(result);
888
+ return result;
889
+ }
890
+ watch(cb) {
891
+ let active = true;
892
+ const streamPromise = realtime.subscribe(
478
893
  {
479
- id: `workflow.${this.id}`,
480
- // @ts-ignore
481
- retries: this.retryConfig?.attempts ?? 0,
482
- cancelOn: [{ event: `cancel.workflow.${this.id}` }],
483
- // Spread flow control configuration
484
- ...this.flowControlConfig
894
+ channel: `workflow:${this.workflowId}:${this.runId}`,
895
+ topics: ["watch"],
896
+ app: this.inngest
485
897
  },
486
- { event: `workflow.${this.id}` },
487
- async ({ event, step, attempt, publish }) => {
488
- let { inputData, initialState, runId, resourceId, resume, outputOptions, format } = event.data;
489
- if (!runId) {
490
- runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
491
- return crypto.randomUUID();
492
- });
898
+ (message) => {
899
+ if (active) {
900
+ cb(message.data);
493
901
  }
494
- const emitter = {
495
- emit: async (event2, data) => {
496
- if (!publish) {
497
- return;
498
- }
499
- try {
500
- await publish({
501
- channel: `workflow:${this.id}:${runId}`,
502
- topic: event2,
503
- data
504
- });
505
- } catch (err) {
506
- this.logger.error("Error emitting event: " + (err?.stack ?? err?.message ?? err));
902
+ }
903
+ );
904
+ return () => {
905
+ active = false;
906
+ streamPromise.then(async (stream) => {
907
+ return stream.cancel();
908
+ }).catch((err) => {
909
+ console.error(err);
910
+ });
911
+ };
912
+ }
913
+ streamLegacy({ inputData, requestContext } = {}) {
914
+ const { readable, writable } = new TransformStream();
915
+ const writer = writable.getWriter();
916
+ void writer.write({
917
+ // @ts-ignore
918
+ type: "start",
919
+ // @ts-ignore
920
+ payload: { runId: this.runId }
921
+ });
922
+ const unwatch = this.watch(async (event) => {
923
+ try {
924
+ const e = {
925
+ ...event,
926
+ type: event.type.replace("workflow-", "")
927
+ };
928
+ if (e.type === "step-output") {
929
+ e.type = e.payload.output.type;
930
+ e.payload = e.payload.output.payload;
931
+ }
932
+ await writer.write(e);
933
+ } catch {
934
+ }
935
+ });
936
+ this.closeStreamAction = async () => {
937
+ await writer.write({
938
+ type: "finish",
939
+ // @ts-ignore
940
+ payload: { runId: this.runId }
941
+ });
942
+ unwatch();
943
+ try {
944
+ await writer.close();
945
+ } catch (err) {
946
+ console.error("Error closing stream:", err);
947
+ } finally {
948
+ writer.releaseLock();
949
+ }
950
+ };
951
+ this.executionResults = this._start({ inputData, requestContext, format: "legacy" }).then((result) => {
952
+ if (result.status !== "suspended") {
953
+ this.closeStreamAction?.().catch(() => {
954
+ });
955
+ }
956
+ return result;
957
+ });
958
+ return {
959
+ stream: readable,
960
+ getWorkflowState: () => this.executionResults
961
+ };
962
+ }
963
+ stream({
964
+ inputData,
965
+ requestContext,
966
+ tracingOptions,
967
+ closeOnSuspend = true,
968
+ initialState,
969
+ outputOptions,
970
+ perStep
971
+ } = {}) {
972
+ if (this.closeStreamAction && this.streamOutput) {
973
+ return this.streamOutput;
974
+ }
975
+ this.closeStreamAction = async () => {
976
+ };
977
+ const self = this;
978
+ const stream$1 = new web.ReadableStream({
979
+ async start(controller) {
980
+ const unwatch = self.watch(async ({ type, from = stream.ChunkFrom.WORKFLOW, payload }) => {
981
+ controller.enqueue({
982
+ type,
983
+ runId: self.runId,
984
+ from,
985
+ payload: {
986
+ stepName: payload?.id,
987
+ ...payload
507
988
  }
508
- },
509
- on: (_event, _callback) => {
510
- },
511
- off: (_event, _callback) => {
512
- },
513
- once: (_event, _callback) => {
989
+ });
990
+ });
991
+ self.closeStreamAction = async () => {
992
+ unwatch();
993
+ try {
994
+ await controller.close();
995
+ } catch (err) {
996
+ console.error("Error closing stream:", err);
514
997
  }
515
998
  };
516
- const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
517
- const result = await engine.execute({
518
- workflowId: this.id,
519
- runId,
520
- resourceId,
521
- graph: this.executionGraph,
522
- serializedStepGraph: this.serializedStepGraph,
523
- input: inputData,
524
- initialState,
525
- emitter,
526
- retryConfig: this.retryConfig,
527
- requestContext: new di.RequestContext(),
528
- // TODO
529
- resume,
530
- format,
531
- abortController: new AbortController(),
532
- // currentSpan: undefined, // TODO: Pass actual parent AI span from workflow execution context
999
+ const executionResultsPromise = self._start({
1000
+ inputData,
1001
+ requestContext,
1002
+ // tracingContext, // We are not able to pass a reference to a span here, what to do?
1003
+ initialState,
1004
+ tracingOptions,
533
1005
  outputOptions,
534
- writableStream: new WritableStream({
535
- write(chunk) {
536
- void emitter.emit("watch-v2", chunk).catch(() => {
537
- });
538
- }
539
- })
1006
+ format: "vnext",
1007
+ perStep
540
1008
  });
541
- await step.run(`workflow.${this.id}.finalize`, async () => {
542
- if (result.status === "failed") {
543
- throw new inngest.NonRetriableError(`Workflow failed`, {
544
- cause: result
1009
+ let executionResults;
1010
+ try {
1011
+ executionResults = await executionResultsPromise;
1012
+ if (closeOnSuspend) {
1013
+ self.closeStreamAction?.().catch(() => {
1014
+ });
1015
+ } else if (executionResults.status !== "suspended") {
1016
+ self.closeStreamAction?.().catch(() => {
545
1017
  });
546
1018
  }
547
- return result;
548
- });
549
- return { result, runId };
550
- }
551
- );
552
- return this.function;
553
- }
554
- getNestedFunctions(steps) {
555
- return steps.flatMap((step) => {
556
- if (step.type === "step" || step.type === "loop" || step.type === "foreach") {
557
- if (step.step instanceof _InngestWorkflow) {
558
- return [step.step.getFunction(), ...step.step.getNestedFunctions(step.step.executionGraph.steps)];
1019
+ if (self.streamOutput) {
1020
+ self.streamOutput.updateResults(
1021
+ executionResults
1022
+ );
1023
+ }
1024
+ } catch (err) {
1025
+ self.streamOutput?.rejectResults(err);
1026
+ self.closeStreamAction?.().catch(() => {
1027
+ });
559
1028
  }
560
- return [];
561
- } else if (step.type === "parallel" || step.type === "conditional") {
562
- return this.getNestedFunctions(step.steps);
563
1029
  }
564
- return [];
565
1030
  });
1031
+ this.streamOutput = new stream.WorkflowRunOutput({
1032
+ runId: this.runId,
1033
+ workflowId: this.workflowId,
1034
+ stream: stream$1
1035
+ });
1036
+ return this.streamOutput;
566
1037
  }
567
- getFunctions() {
568
- return [this.getFunction(), ...this.getNestedFunctions(this.executionGraph.steps)];
569
- }
570
- };
571
- function isAgent(params) {
572
- return params?.component === "AGENT";
573
- }
574
- function isTool(params) {
575
- return params instanceof tools.Tool;
576
- }
577
- function createStep(params, agentOptions) {
578
- if (isAgent(params)) {
579
- return {
580
- id: params.name,
581
- description: params.getDescription(),
582
- // @ts-ignore
583
- inputSchema: zod.z.object({
584
- prompt: zod.z.string()
585
- // resourceId: z.string().optional(),
586
- // threadId: z.string().optional(),
587
- }),
588
- // @ts-ignore
589
- outputSchema: zod.z.object({
590
- text: zod.z.string()
591
- }),
592
- execute: async ({
593
- inputData,
594
- [_constants.EMITTER_SYMBOL]: emitter,
595
- [_constants.STREAM_FORMAT_SYMBOL]: streamFormat,
596
- requestContext,
597
- tracingContext,
598
- abortSignal,
599
- abort,
600
- writer
601
- }) => {
602
- let streamPromise = {};
603
- streamPromise.promise = new Promise((resolve, reject) => {
604
- streamPromise.resolve = resolve;
605
- streamPromise.reject = reject;
1038
+ timeTravelStream({
1039
+ inputData,
1040
+ resumeData,
1041
+ initialState,
1042
+ step,
1043
+ context,
1044
+ nestedStepsContext,
1045
+ requestContext,
1046
+ tracingOptions,
1047
+ outputOptions,
1048
+ perStep
1049
+ }) {
1050
+ this.closeStreamAction = async () => {
1051
+ };
1052
+ const self = this;
1053
+ const stream$1 = new web.ReadableStream({
1054
+ async start(controller) {
1055
+ const unwatch = self.watch(async ({ type, from = stream.ChunkFrom.WORKFLOW, payload }) => {
1056
+ controller.enqueue({
1057
+ type,
1058
+ runId: self.runId,
1059
+ from,
1060
+ payload: {
1061
+ stepName: payload?.id,
1062
+ ...payload
1063
+ }
1064
+ });
606
1065
  });
607
- const toolData = {
608
- name: params.name,
609
- args: inputData
1066
+ self.closeStreamAction = async () => {
1067
+ unwatch();
1068
+ try {
1069
+ controller.close();
1070
+ } catch (err) {
1071
+ console.error("Error closing stream:", err);
1072
+ }
610
1073
  };
611
- let stream;
612
- if ((await params.getModel()).specificationVersion === "v1") {
613
- const { fullStream } = await params.streamLegacy(inputData.prompt, {
614
- ...agentOptions ?? {},
615
- // resourceId: inputData.resourceId,
616
- // threadId: inputData.threadId,
617
- requestContext,
618
- tracingContext,
619
- onFinish: (result) => {
620
- streamPromise.resolve(result.text);
621
- void agentOptions?.onFinish?.(result);
622
- },
623
- abortSignal
624
- });
625
- stream = fullStream;
626
- } else {
627
- const modelOutput = await params.stream(inputData.prompt, {
628
- ...agentOptions ?? {},
629
- requestContext,
630
- tracingContext,
631
- onFinish: (result) => {
632
- streamPromise.resolve(result.text);
633
- void agentOptions?.onFinish?.(result);
634
- },
635
- abortSignal
636
- });
637
- stream = modelOutput.fullStream;
638
- }
639
- if (streamFormat === "legacy") {
640
- await emitter.emit("watch-v2", {
641
- type: "tool-call-streaming-start",
642
- ...toolData ?? {}
1074
+ const executionResultsPromise = self._timeTravel({
1075
+ inputData,
1076
+ step,
1077
+ context,
1078
+ nestedStepsContext,
1079
+ resumeData,
1080
+ initialState,
1081
+ requestContext,
1082
+ tracingOptions,
1083
+ outputOptions,
1084
+ perStep
1085
+ });
1086
+ self.executionResults = executionResultsPromise;
1087
+ let executionResults;
1088
+ try {
1089
+ executionResults = await executionResultsPromise;
1090
+ self.closeStreamAction?.().catch(() => {
643
1091
  });
644
- for await (const chunk of stream) {
645
- if (chunk.type === "text-delta") {
646
- await emitter.emit("watch-v2", {
647
- type: "tool-call-delta",
648
- ...toolData ?? {},
649
- argsTextDelta: chunk.textDelta
650
- });
651
- }
1092
+ if (self.streamOutput) {
1093
+ self.streamOutput.updateResults(executionResults);
652
1094
  }
653
- await emitter.emit("watch-v2", {
654
- type: "tool-call-streaming-finish",
655
- ...toolData ?? {}
1095
+ } catch (err) {
1096
+ self.streamOutput?.rejectResults(err);
1097
+ self.closeStreamAction?.().catch(() => {
656
1098
  });
657
- } else {
658
- for await (const chunk of stream) {
659
- await writer.write(chunk);
660
- }
661
1099
  }
662
- if (abortSignal.aborted) {
663
- return abort();
664
- }
665
- return {
666
- text: await streamPromise.promise
667
- };
668
- },
669
- component: params.component
670
- };
1100
+ }
1101
+ });
1102
+ this.streamOutput = new stream.WorkflowRunOutput({
1103
+ runId: this.runId,
1104
+ workflowId: this.workflowId,
1105
+ stream: stream$1
1106
+ });
1107
+ return this.streamOutput;
671
1108
  }
672
- if (isTool(params)) {
673
- if (!params.inputSchema || !params.outputSchema) {
674
- throw new Error("Tool must have input and output schemas defined");
1109
+ /**
1110
+ * Hydrates errors in a failed workflow result back to proper Error instances.
1111
+ * This ensures error.cause chains and custom properties are preserved.
1112
+ */
1113
+ hydrateFailedResult(result) {
1114
+ if (result.status === "failed") {
1115
+ result.error = error.getErrorFromUnknown(result.error, { serializeStack: false });
1116
+ if (result.steps) {
1117
+ workflows.hydrateSerializedStepErrors(result.steps);
1118
+ }
675
1119
  }
676
- return {
677
- // TODO: tool probably should have strong id type
678
- // @ts-ignore
679
- id: params.id,
680
- description: params.description,
681
- inputSchema: params.inputSchema,
682
- outputSchema: params.outputSchema,
683
- execute: async ({ inputData, mastra, requestContext, tracingContext, suspend, resumeData }) => {
684
- return params.execute({
685
- context: inputData,
686
- mastra: aiTracing.wrapMastra(mastra, tracingContext),
687
- requestContext,
688
- tracingContext,
689
- suspend,
690
- resumeData
691
- });
692
- },
693
- component: "TOOL"
694
- };
695
1120
  }
696
- return {
697
- id: params.id,
698
- description: params.description,
699
- inputSchema: params.inputSchema,
700
- outputSchema: params.outputSchema,
701
- resumeSchema: params.resumeSchema,
702
- suspendSchema: params.suspendSchema,
703
- execute: params.execute
704
- };
705
- }
706
- function init(inngest) {
707
- return {
708
- createWorkflow(params) {
709
- return new InngestWorkflow(
710
- params,
711
- inngest
712
- );
713
- },
714
- createStep,
715
- cloneStep(step, opts) {
716
- return {
717
- id: opts.id,
718
- description: step.description,
719
- inputSchema: step.inputSchema,
720
- outputSchema: step.outputSchema,
721
- resumeSchema: step.resumeSchema,
722
- suspendSchema: step.suspendSchema,
723
- stateSchema: step.stateSchema,
724
- execute: step.execute,
725
- component: step.component
726
- };
727
- },
728
- cloneWorkflow(workflow, opts) {
729
- const wf = new workflows.Workflow({
730
- id: opts.id,
731
- inputSchema: workflow.inputSchema,
732
- outputSchema: workflow.outputSchema,
733
- steps: workflow.stepDefs,
734
- mastra: workflow.mastra
735
- });
736
- wf.setStepFlow(workflow.stepGraph);
737
- wf.commit();
738
- return wf;
1121
+ };
1122
+
1123
+ // src/workflow.ts
1124
+ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
1125
+ #mastra;
1126
+ inngest;
1127
+ function;
1128
+ cronFunction;
1129
+ flowControlConfig;
1130
+ cronConfig;
1131
+ constructor(params, inngest) {
1132
+ const { concurrency, rateLimit, throttle, debounce, priority, cron, inputData, initialState, ...workflowParams } = params;
1133
+ super(workflowParams);
1134
+ this.engineType = "inngest";
1135
+ const flowControlEntries = Object.entries({ concurrency, rateLimit, throttle, debounce, priority }).filter(
1136
+ ([_, value]) => value !== void 0
1137
+ );
1138
+ this.flowControlConfig = flowControlEntries.length > 0 ? Object.fromEntries(flowControlEntries) : void 0;
1139
+ this.#mastra = params.mastra;
1140
+ this.inngest = inngest;
1141
+ if (cron) {
1142
+ this.cronConfig = { cron, inputData, initialState };
1143
+ }
1144
+ }
1145
+ async listWorkflowRuns(args) {
1146
+ const storage = this.#mastra?.getStorage();
1147
+ if (!storage) {
1148
+ this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
1149
+ return { runs: [], total: 0 };
739
1150
  }
740
- };
741
- }
742
- var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
743
- inngestStep;
744
- inngestAttempts;
745
- constructor(mastra, inngestStep, inngestAttempts = 0, options) {
746
- super({ mastra, options });
747
- this.inngestStep = inngestStep;
748
- this.inngestAttempts = inngestAttempts;
1151
+ const workflowsStore = await storage.getStore("workflows");
1152
+ if (!workflowsStore) {
1153
+ return { runs: [], total: 0 };
1154
+ }
1155
+ return workflowsStore.listWorkflowRuns({ workflowName: this.id, ...args ?? {} });
749
1156
  }
750
- async fmtReturnValue(emitter, stepResults, lastOutput, error) {
751
- const base = {
752
- status: lastOutput.status,
753
- steps: stepResults
754
- };
755
- if (lastOutput.status === "success") {
756
- await emitter.emit("watch", {
757
- type: "watch",
758
- payload: {
759
- workflowState: {
760
- status: lastOutput.status,
761
- steps: stepResults,
762
- result: lastOutput.output
763
- }
764
- },
765
- eventTimestamp: Date.now()
766
- });
767
- base.result = lastOutput.output;
768
- } else if (lastOutput.status === "failed") {
769
- base.error = error instanceof Error ? error?.stack ?? error.message : lastOutput?.error instanceof Error ? lastOutput.error.message : lastOutput.error ?? error ?? "Unknown error";
770
- await emitter.emit("watch", {
771
- type: "watch",
772
- payload: {
773
- workflowState: {
774
- status: lastOutput.status,
775
- steps: stepResults,
776
- result: null,
777
- error: base.error
778
- }
779
- },
780
- eventTimestamp: Date.now()
781
- });
782
- } else if (lastOutput.status === "suspended") {
783
- await emitter.emit("watch", {
784
- type: "watch",
785
- payload: {
786
- workflowState: {
787
- status: lastOutput.status,
788
- steps: stepResults,
789
- result: null,
790
- error: null
791
- }
792
- },
793
- eventTimestamp: Date.now()
794
- });
795
- const suspendedStepIds = Object.entries(stepResults).flatMap(([stepId, stepResult]) => {
796
- if (stepResult?.status === "suspended") {
797
- const nestedPath = stepResult?.suspendPayload?.__workflow_meta?.path;
798
- return nestedPath ? [[stepId, ...nestedPath]] : [[stepId]];
799
- }
800
- return [];
801
- });
802
- base.suspended = suspendedStepIds;
1157
+ async getWorkflowRunById(runId) {
1158
+ const storage = this.#mastra?.getStorage();
1159
+ if (!storage) {
1160
+ this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
1161
+ return this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null;
1162
+ }
1163
+ const workflowsStore = await storage.getStore("workflows");
1164
+ if (!workflowsStore) {
1165
+ return this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null;
803
1166
  }
804
- return base;
1167
+ const run = await workflowsStore.getWorkflowRunById({ runId, workflowName: this.id });
1168
+ return run ?? (this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null);
805
1169
  }
806
- // async executeSleep({ id, duration }: { id: string; duration: number }): Promise<void> {
807
- // await this.inngestStep.sleep(id, duration);
808
- // }
809
- async executeSleep({
810
- workflowId,
811
- runId,
812
- entry,
813
- prevOutput,
814
- stepResults,
815
- emitter,
816
- abortController,
817
- requestContext,
818
- executionContext,
819
- writableStream,
820
- tracingContext
821
- }) {
822
- let { duration, fn } = entry;
823
- const sleepSpan = tracingContext?.currentSpan?.createChildSpan({
824
- type: aiTracing.AISpanType.WORKFLOW_SLEEP,
825
- name: `sleep: ${duration ? `${duration}ms` : "dynamic"}`,
826
- attributes: {
827
- durationMs: duration,
828
- sleepType: fn ? "dynamic" : "fixed"
829
- },
830
- tracingPolicy: this.options?.tracingPolicy
831
- });
832
- if (fn) {
833
- const stepCallId = crypto.randomUUID();
834
- duration = await this.inngestStep.run(`workflow.${workflowId}.sleep.${entry.id}`, async () => {
835
- return await fn(
836
- workflows.createDeprecationProxy(
837
- {
838
- runId,
839
- workflowId,
840
- mastra: this.mastra,
841
- requestContext,
842
- inputData: prevOutput,
843
- state: executionContext.state,
844
- setState: (state) => {
845
- executionContext.state = state;
846
- },
847
- runCount: -1,
848
- retryCount: -1,
849
- tracingContext: {
850
- currentSpan: sleepSpan
851
- },
852
- getInitData: () => stepResults?.input,
853
- getStepResult: workflows.getStepResult.bind(this, stepResults),
854
- // TODO: this function shouldn't have suspend probably?
855
- suspend: async (_suspendPayload) => {
856
- },
857
- bail: () => {
858
- },
859
- abort: () => {
860
- abortController?.abort();
861
- },
862
- [_constants.EMITTER_SYMBOL]: emitter,
863
- [_constants.STREAM_FORMAT_SYMBOL]: executionContext.format,
864
- engine: { step: this.inngestStep },
865
- abortSignal: abortController?.signal,
866
- writer: new tools.ToolStream(
867
- {
868
- prefix: "workflow-step",
869
- callId: stepCallId,
870
- name: "sleep",
871
- runId
872
- },
873
- writableStream
874
- )
875
- },
876
- {
877
- paramName: "runCount",
878
- deprecationMessage: workflows.runCountDeprecationMessage,
879
- logger: this.logger
880
- }
881
- )
882
- );
883
- });
884
- sleepSpan?.update({
885
- attributes: {
886
- durationMs: duration
1170
+ __registerMastra(mastra) {
1171
+ super.__registerMastra(mastra);
1172
+ this.#mastra = mastra;
1173
+ this.executionEngine.__registerMastra(mastra);
1174
+ const updateNested = (step) => {
1175
+ if ((step.type === "step" || step.type === "loop" || step.type === "foreach") && step.step instanceof _InngestWorkflow) {
1176
+ step.step.__registerMastra(mastra);
1177
+ } else if (step.type === "parallel" || step.type === "conditional") {
1178
+ for (const subStep of step.steps) {
1179
+ updateNested(subStep);
887
1180
  }
888
- });
889
- }
890
- try {
891
- await this.inngestStep.sleep(entry.id, !duration || duration < 0 ? 0 : duration);
892
- sleepSpan?.end();
893
- } catch (e) {
894
- sleepSpan?.error({ error: e });
895
- throw e;
1181
+ }
1182
+ };
1183
+ if (this.executionGraph.steps.length) {
1184
+ for (const step of this.executionGraph.steps) {
1185
+ updateNested(step);
1186
+ }
896
1187
  }
897
1188
  }
898
- async executeSleepUntil({
899
- workflowId,
900
- runId,
901
- entry,
902
- prevOutput,
903
- stepResults,
904
- emitter,
905
- abortController,
906
- requestContext,
907
- executionContext,
908
- writableStream,
909
- tracingContext
910
- }) {
911
- let { date, fn } = entry;
912
- const sleepUntilSpan = tracingContext?.currentSpan?.createChildSpan({
913
- type: aiTracing.AISpanType.WORKFLOW_SLEEP,
914
- name: `sleepUntil: ${date ? date.toISOString() : "dynamic"}`,
915
- attributes: {
916
- untilDate: date,
917
- durationMs: date ? Math.max(0, date.getTime() - Date.now()) : void 0,
918
- sleepType: fn ? "dynamic" : "fixed"
1189
+ async createRun(options) {
1190
+ const runIdToUse = options?.runId || crypto$1.randomUUID();
1191
+ const run = this.runs.get(runIdToUse) ?? new InngestRun(
1192
+ {
1193
+ workflowId: this.id,
1194
+ runId: runIdToUse,
1195
+ resourceId: options?.resourceId,
1196
+ executionEngine: this.executionEngine,
1197
+ executionGraph: this.executionGraph,
1198
+ serializedStepGraph: this.serializedStepGraph,
1199
+ mastra: this.#mastra,
1200
+ retryConfig: this.retryConfig,
1201
+ cleanup: () => this.runs.delete(runIdToUse),
1202
+ workflowSteps: this.steps,
1203
+ workflowEngineType: this.engineType,
1204
+ validateInputs: this.options.validateInputs
919
1205
  },
920
- tracingPolicy: this.options?.tracingPolicy
1206
+ this.inngest
1207
+ );
1208
+ this.runs.set(runIdToUse, run);
1209
+ const shouldPersistSnapshot = this.options.shouldPersistSnapshot({
1210
+ workflowStatus: run.workflowRunStatus,
1211
+ stepResults: {}
921
1212
  });
922
- if (fn) {
923
- date = await this.inngestStep.run(`workflow.${workflowId}.sleepUntil.${entry.id}`, async () => {
924
- const stepCallId = crypto.randomUUID();
925
- return await fn(
926
- workflows.createDeprecationProxy(
927
- {
928
- runId,
929
- workflowId,
930
- mastra: this.mastra,
931
- requestContext,
932
- inputData: prevOutput,
933
- state: executionContext.state,
934
- setState: (state) => {
935
- executionContext.state = state;
936
- },
937
- runCount: -1,
938
- retryCount: -1,
939
- tracingContext: {
940
- currentSpan: sleepUntilSpan
941
- },
942
- getInitData: () => stepResults?.input,
943
- getStepResult: workflows.getStepResult.bind(this, stepResults),
944
- // TODO: this function shouldn't have suspend probably?
945
- suspend: async (_suspendPayload) => {
946
- },
947
- bail: () => {
948
- },
949
- abort: () => {
950
- abortController?.abort();
951
- },
952
- [_constants.EMITTER_SYMBOL]: emitter,
953
- [_constants.STREAM_FORMAT_SYMBOL]: executionContext.format,
954
- engine: { step: this.inngestStep },
955
- abortSignal: abortController?.signal,
956
- writer: new tools.ToolStream(
957
- {
958
- prefix: "workflow-step",
959
- callId: stepCallId,
960
- name: "sleep",
961
- runId
962
- },
963
- writableStream
964
- )
965
- },
966
- {
967
- paramName: "runCount",
968
- deprecationMessage: workflows.runCountDeprecationMessage,
969
- logger: this.logger
970
- }
971
- )
972
- );
973
- });
974
- if (date && !(date instanceof Date)) {
975
- date = new Date(date);
976
- }
977
- const time = !date ? 0 : date.getTime() - Date.now();
978
- sleepUntilSpan?.update({
979
- attributes: {
980
- durationMs: Math.max(0, time)
1213
+ const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse, {
1214
+ withNestedWorkflows: false
1215
+ });
1216
+ if (!workflowSnapshotInStorage && shouldPersistSnapshot) {
1217
+ const workflowsStore = await this.mastra?.getStorage()?.getStore("workflows");
1218
+ await workflowsStore?.persistWorkflowSnapshot({
1219
+ workflowName: this.id,
1220
+ runId: runIdToUse,
1221
+ resourceId: options?.resourceId,
1222
+ snapshot: {
1223
+ runId: runIdToUse,
1224
+ status: "pending",
1225
+ value: {},
1226
+ context: {},
1227
+ activePaths: [],
1228
+ activeStepsPath: {},
1229
+ waitingPaths: {},
1230
+ serializedStepGraph: this.serializedStepGraph,
1231
+ suspendedPaths: {},
1232
+ resumeLabels: {},
1233
+ result: void 0,
1234
+ error: void 0,
1235
+ timestamp: Date.now()
981
1236
  }
982
1237
  });
983
1238
  }
984
- if (!(date instanceof Date)) {
985
- sleepUntilSpan?.end();
986
- return;
987
- }
988
- try {
989
- await this.inngestStep.sleepUntil(entry.id, date);
990
- sleepUntilSpan?.end();
991
- } catch (e) {
992
- sleepUntilSpan?.error({ error: e });
993
- throw e;
994
- }
1239
+ return run;
995
1240
  }
996
- async executeWaitForEvent({ event, timeout }) {
997
- const eventData = await this.inngestStep.waitForEvent(`user-event-${event}`, {
998
- event: `user-event-${event}`,
999
- timeout: timeout ?? 5e3
1000
- });
1001
- if (eventData === null) {
1002
- throw "Timeout waiting for event";
1241
+ //createCronFunction is only called if cronConfig.cron is defined.
1242
+ createCronFunction() {
1243
+ if (this.cronFunction) {
1244
+ return this.cronFunction;
1003
1245
  }
1004
- return eventData?.data;
1005
- }
1006
- async executeStep({
1007
- step,
1008
- stepResults,
1009
- executionContext,
1010
- resume,
1011
- prevOutput,
1012
- emitter,
1013
- abortController,
1014
- requestContext,
1015
- tracingContext,
1016
- writableStream,
1017
- disableScorers
1018
- }) {
1019
- const stepAISpan = tracingContext?.currentSpan?.createChildSpan({
1020
- name: `workflow step: '${step.id}'`,
1021
- type: aiTracing.AISpanType.WORKFLOW_STEP,
1022
- input: prevOutput,
1023
- attributes: {
1024
- stepId: step.id
1246
+ this.cronFunction = this.inngest.createFunction(
1247
+ {
1248
+ id: `workflow.${this.id}.cron`,
1249
+ retries: 0,
1250
+ cancelOn: [{ event: `cancel.workflow.${this.id}` }],
1251
+ ...this.flowControlConfig
1025
1252
  },
1026
- tracingPolicy: this.options?.tracingPolicy
1027
- });
1028
- const { inputData, validationError } = await workflows.validateStepInput({
1029
- prevOutput,
1030
- step,
1031
- validateInputs: this.options?.validateInputs ?? false
1032
- });
1033
- const startedAt = await this.inngestStep.run(
1034
- `workflow.${executionContext.workflowId}.run.${executionContext.runId}.step.${step.id}.running_ev`,
1253
+ { cron: this.cronConfig?.cron ?? "" },
1035
1254
  async () => {
1036
- const startedAt2 = Date.now();
1037
- await emitter.emit("watch", {
1038
- type: "watch",
1039
- payload: {
1040
- currentStep: {
1041
- id: step.id,
1042
- status: "running"
1043
- },
1044
- workflowState: {
1045
- status: "running",
1046
- steps: {
1047
- ...stepResults,
1048
- [step.id]: {
1049
- status: "running"
1050
- }
1051
- },
1052
- result: null,
1053
- error: null
1054
- }
1055
- },
1056
- eventTimestamp: Date.now()
1057
- });
1058
- await emitter.emit("watch-v2", {
1059
- type: "workflow-step-start",
1060
- payload: {
1061
- id: step.id,
1062
- status: "running",
1063
- payload: inputData,
1064
- startedAt: startedAt2
1065
- }
1255
+ const run = await this.createRun();
1256
+ const result = await run.start({
1257
+ inputData: this.cronConfig?.inputData,
1258
+ initialState: this.cronConfig?.initialState
1066
1259
  });
1067
- return startedAt2;
1260
+ return { result, runId: run.runId };
1068
1261
  }
1069
1262
  );
1070
- if (step instanceof InngestWorkflow) {
1071
- const isResume = !!resume?.steps?.length;
1072
- let result;
1073
- let runId;
1074
- try {
1075
- if (isResume) {
1076
- runId = stepResults[resume?.steps?.[0]]?.suspendPayload?.__workflow_meta?.runId ?? crypto.randomUUID();
1077
- const snapshot = await this.mastra?.getStorage()?.loadWorkflowSnapshot({
1078
- workflowName: step.id,
1079
- runId
1080
- });
1081
- const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
1082
- function: step.getFunction(),
1083
- data: {
1084
- inputData,
1085
- initialState: executionContext.state ?? snapshot?.value ?? {},
1086
- runId,
1087
- resume: {
1088
- runId,
1089
- steps: resume.steps.slice(1),
1090
- stepResults: snapshot?.context,
1091
- resumePayload: resume.resumePayload,
1092
- // @ts-ignore
1093
- resumePath: snapshot?.suspendedPaths?.[resume.steps?.[1]]
1094
- },
1095
- outputOptions: { includeState: true }
1096
- }
1097
- });
1098
- result = invokeResp.result;
1099
- runId = invokeResp.runId;
1100
- executionContext.state = invokeResp.result.state;
1101
- } else {
1102
- const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
1103
- function: step.getFunction(),
1104
- data: {
1105
- inputData,
1106
- initialState: executionContext.state ?? {},
1107
- outputOptions: { includeState: true }
1108
- }
1263
+ return this.cronFunction;
1264
+ }
1265
+ getFunction() {
1266
+ if (this.function) {
1267
+ return this.function;
1268
+ }
1269
+ this.function = this.inngest.createFunction(
1270
+ {
1271
+ id: `workflow.${this.id}`,
1272
+ retries: 0,
1273
+ cancelOn: [{ event: `cancel.workflow.${this.id}` }],
1274
+ // Spread flow control configuration
1275
+ ...this.flowControlConfig
1276
+ },
1277
+ { event: `workflow.${this.id}` },
1278
+ async ({ event, step, attempt, publish }) => {
1279
+ let { inputData, initialState, runId, resourceId, resume, outputOptions, format, timeTravel, perStep } = event.data;
1280
+ if (!runId) {
1281
+ runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
1282
+ return crypto$1.randomUUID();
1109
1283
  });
1110
- result = invokeResp.result;
1111
- runId = invokeResp.runId;
1112
- executionContext.state = invokeResp.result.state;
1113
- }
1114
- } catch (e) {
1115
- const errorCause = e?.cause;
1116
- if (errorCause && typeof errorCause === "object") {
1117
- result = errorCause;
1118
- runId = errorCause.runId || crypto.randomUUID();
1119
- } else {
1120
- runId = crypto.randomUUID();
1121
- result = {
1122
- status: "failed",
1123
- error: e instanceof Error ? e : new Error(String(e)),
1124
- steps: {},
1125
- input: inputData
1126
- };
1127
1284
  }
1128
- }
1129
- const res = await this.inngestStep.run(
1130
- `workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
1131
- async () => {
1132
- if (result.status === "failed") {
1133
- await emitter.emit("watch", {
1134
- type: "watch",
1135
- payload: {
1136
- currentStep: {
1137
- id: step.id,
1138
- status: "failed",
1139
- error: result?.error
1140
- },
1141
- workflowState: {
1142
- status: "running",
1143
- steps: stepResults,
1144
- result: null,
1145
- error: null
1146
- }
1147
- },
1148
- eventTimestamp: Date.now()
1149
- });
1150
- await emitter.emit("watch-v2", {
1151
- type: "workflow-step-result",
1152
- payload: {
1153
- id: step.id,
1154
- status: "failed",
1155
- error: result?.error,
1156
- payload: prevOutput
1157
- }
1158
- });
1159
- return { executionContext, result: { status: "failed", error: result?.error } };
1160
- } else if (result.status === "suspended") {
1161
- const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
1162
- const stepRes2 = stepResult;
1163
- return stepRes2?.status === "suspended";
1164
- });
1165
- for (const [stepName, stepResult] of suspendedSteps) {
1166
- const suspendPath = [stepName, ...stepResult?.suspendPayload?.__workflow_meta?.path ?? []];
1167
- executionContext.suspendedPaths[step.id] = executionContext.executionPath;
1168
- await emitter.emit("watch", {
1285
+ const pubsub = new InngestPubSub(this.inngest, this.id, publish);
1286
+ const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
1287
+ const result = await engine.execute({
1288
+ workflowId: this.id,
1289
+ runId,
1290
+ resourceId,
1291
+ graph: this.executionGraph,
1292
+ serializedStepGraph: this.serializedStepGraph,
1293
+ input: inputData,
1294
+ initialState,
1295
+ pubsub,
1296
+ retryConfig: this.retryConfig,
1297
+ requestContext: new di.RequestContext(Object.entries(event.data.requestContext ?? {})),
1298
+ resume,
1299
+ timeTravel,
1300
+ perStep,
1301
+ format,
1302
+ abortController: new AbortController(),
1303
+ // currentSpan: undefined, // TODO: Pass actual parent Span from workflow execution context
1304
+ outputOptions,
1305
+ outputWriter: async (chunk) => {
1306
+ try {
1307
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1169
1308
  type: "watch",
1170
- payload: {
1171
- currentStep: {
1172
- id: step.id,
1173
- status: "suspended",
1174
- payload: stepResult.payload,
1175
- suspendPayload: {
1176
- ...stepResult?.suspendPayload,
1177
- __workflow_meta: { runId, path: suspendPath }
1178
- }
1179
- },
1180
- workflowState: {
1181
- status: "running",
1182
- steps: stepResults,
1183
- result: null,
1184
- error: null
1185
- }
1186
- },
1187
- eventTimestamp: Date.now()
1188
- });
1189
- await emitter.emit("watch-v2", {
1190
- type: "workflow-step-suspended",
1191
- payload: {
1192
- id: step.id,
1193
- status: "suspended"
1194
- }
1309
+ runId,
1310
+ data: chunk
1195
1311
  });
1196
- return {
1197
- executionContext,
1198
- result: {
1199
- status: "suspended",
1200
- payload: stepResult.payload,
1201
- suspendPayload: {
1202
- ...stepResult?.suspendPayload,
1203
- __workflow_meta: { runId, path: suspendPath }
1204
- }
1205
- }
1206
- };
1312
+ } catch (err) {
1313
+ this.logger.debug?.("Failed to publish watch event:", err);
1207
1314
  }
1208
- await emitter.emit("watch", {
1209
- type: "watch",
1210
- payload: {
1211
- currentStep: {
1212
- id: step.id,
1213
- status: "suspended",
1214
- payload: {}
1215
- },
1216
- workflowState: {
1217
- status: "running",
1218
- steps: stepResults,
1219
- result: null,
1220
- error: null
1221
- }
1222
- },
1223
- eventTimestamp: Date.now()
1315
+ }
1316
+ });
1317
+ await step.run(`workflow.${this.id}.finalize`, async () => {
1318
+ if (result.status !== "paused") {
1319
+ await engine.invokeLifecycleCallbacksInternal(result);
1320
+ }
1321
+ if (result.status === "failed") {
1322
+ throw new inngest.NonRetriableError(`Workflow failed`, {
1323
+ cause: result
1224
1324
  });
1225
- return {
1226
- executionContext,
1227
- result: {
1228
- status: "suspended",
1229
- payload: {}
1230
- }
1231
- };
1232
1325
  }
1233
- await emitter.emit("watch", {
1234
- type: "watch",
1235
- payload: {
1236
- currentStep: {
1237
- id: step.id,
1238
- status: "success",
1239
- output: result?.result
1240
- },
1241
- workflowState: {
1242
- status: "running",
1243
- steps: stepResults,
1244
- result: null,
1245
- error: null
1326
+ return result;
1327
+ });
1328
+ return { result, runId };
1329
+ }
1330
+ );
1331
+ return this.function;
1332
+ }
1333
+ getNestedFunctions(steps) {
1334
+ return steps.flatMap((step) => {
1335
+ if (step.type === "step" || step.type === "loop" || step.type === "foreach") {
1336
+ if (step.step instanceof _InngestWorkflow) {
1337
+ return [step.step.getFunction(), ...step.step.getNestedFunctions(step.step.executionGraph.steps)];
1338
+ }
1339
+ return [];
1340
+ } else if (step.type === "parallel" || step.type === "conditional") {
1341
+ return this.getNestedFunctions(step.steps);
1342
+ }
1343
+ return [];
1344
+ });
1345
+ }
1346
+ getFunctions() {
1347
+ return [
1348
+ this.getFunction(),
1349
+ ...this.cronConfig?.cron ? [this.createCronFunction()] : [],
1350
+ ...this.getNestedFunctions(this.executionGraph.steps)
1351
+ ];
1352
+ }
1353
+ };
1354
+ function serve({
1355
+ mastra,
1356
+ inngest,
1357
+ functions: userFunctions = [],
1358
+ registerOptions
1359
+ }) {
1360
+ const wfs = mastra.listWorkflows();
1361
+ const workflowFunctions = Array.from(
1362
+ new Set(
1363
+ Object.values(wfs).flatMap((wf) => {
1364
+ if (wf instanceof InngestWorkflow) {
1365
+ wf.__registerMastra(mastra);
1366
+ return wf.getFunctions();
1367
+ }
1368
+ return [];
1369
+ })
1370
+ )
1371
+ );
1372
+ return hono.serve({
1373
+ ...registerOptions,
1374
+ client: inngest,
1375
+ functions: [...workflowFunctions, ...userFunctions]
1376
+ });
1377
+ }
1378
+
1379
+ // src/types.ts
1380
+ var _compatibilityCheck = true;
1381
+
1382
+ // src/index.ts
1383
+ function createStep(params, agentOrToolOptions) {
1384
+ if (params instanceof InngestWorkflow) {
1385
+ return params;
1386
+ }
1387
+ if (params instanceof agent.Agent) {
1388
+ const options = agentOrToolOptions;
1389
+ const outputSchema = options?.structuredOutput?.schema ?? zod.z.object({ text: zod.z.string() });
1390
+ const { retries, scorers, ...agentOptions } = options ?? {};
1391
+ return {
1392
+ id: params.name,
1393
+ description: params.getDescription(),
1394
+ inputSchema: zod.z.object({
1395
+ prompt: zod.z.string()
1396
+ // resourceId: z.string().optional(),
1397
+ // threadId: z.string().optional(),
1398
+ }),
1399
+ outputSchema,
1400
+ retries,
1401
+ scorers,
1402
+ execute: async ({
1403
+ inputData,
1404
+ runId,
1405
+ [_constants.PUBSUB_SYMBOL]: pubsub,
1406
+ [_constants.STREAM_FORMAT_SYMBOL]: streamFormat,
1407
+ requestContext,
1408
+ tracingContext,
1409
+ abortSignal,
1410
+ abort,
1411
+ writer
1412
+ }) => {
1413
+ let streamPromise = {};
1414
+ streamPromise.promise = new Promise((resolve, reject) => {
1415
+ streamPromise.resolve = resolve;
1416
+ streamPromise.reject = reject;
1417
+ });
1418
+ let structuredResult = null;
1419
+ const toolData = {
1420
+ name: params.name,
1421
+ args: inputData
1422
+ };
1423
+ let stream;
1424
+ if ((await params.getModel()).specificationVersion === "v1") {
1425
+ const { fullStream } = await params.streamLegacy(inputData.prompt, {
1426
+ ...agentOptions ?? {},
1427
+ // resourceId: inputData.resourceId,
1428
+ // threadId: inputData.threadId,
1429
+ requestContext,
1430
+ tracingContext,
1431
+ onFinish: (result) => {
1432
+ const resultWithObject = result;
1433
+ if (agentOptions?.structuredOutput?.schema && resultWithObject.object) {
1434
+ structuredResult = resultWithObject.object;
1246
1435
  }
1436
+ streamPromise.resolve(result.text);
1437
+ void agentOptions?.onFinish?.(result);
1247
1438
  },
1248
- eventTimestamp: Date.now()
1249
- });
1250
- await emitter.emit("watch-v2", {
1251
- type: "workflow-step-result",
1252
- payload: {
1253
- id: step.id,
1254
- status: "success",
1255
- output: result?.result
1256
- }
1257
- });
1258
- await emitter.emit("watch-v2", {
1259
- type: "workflow-step-finish",
1260
- payload: {
1261
- id: step.id,
1262
- metadata: {}
1263
- }
1439
+ abortSignal
1264
1440
  });
1265
- return { executionContext, result: { status: "success", output: result?.result } };
1266
- }
1267
- );
1268
- Object.assign(executionContext, res.executionContext);
1269
- return {
1270
- ...res.result,
1271
- startedAt,
1272
- endedAt: Date.now(),
1273
- payload: inputData,
1274
- resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1275
- resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1276
- };
1277
- }
1278
- const stepCallId = crypto.randomUUID();
1279
- let stepRes;
1280
- try {
1281
- stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
1282
- let execResults;
1283
- let suspended;
1284
- let bailed;
1285
- try {
1286
- if (validationError) {
1287
- throw validationError;
1288
- }
1289
- const result = await step.execute({
1290
- runId: executionContext.runId,
1291
- mastra: this.mastra,
1441
+ stream = fullStream;
1442
+ } else {
1443
+ const modelOutput = await params.stream(inputData.prompt, {
1444
+ ...agentOptions ?? {},
1292
1445
  requestContext,
1293
- writer: new tools.ToolStream(
1294
- {
1295
- prefix: "workflow-step",
1296
- callId: stepCallId,
1297
- name: step.id,
1298
- runId: executionContext.runId
1299
- },
1300
- writableStream
1301
- ),
1302
- state: executionContext?.state ?? {},
1303
- setState: (state) => {
1304
- executionContext.state = state;
1305
- },
1306
- inputData,
1307
- resumeData: resume?.steps[0] === step.id ? resume?.resumePayload : void 0,
1308
- tracingContext: {
1309
- currentSpan: stepAISpan
1310
- },
1311
- getInitData: () => stepResults?.input,
1312
- getStepResult: workflows.getStepResult.bind(this, stepResults),
1313
- suspend: async (suspendPayload, suspendOptions) => {
1314
- executionContext.suspendedPaths[step.id] = executionContext.executionPath;
1315
- if (suspendOptions?.resumeLabel) {
1316
- const resumeLabel = Array.isArray(suspendOptions.resumeLabel) ? suspendOptions.resumeLabel : [suspendOptions.resumeLabel];
1317
- for (const label of resumeLabel) {
1318
- executionContext.resumeLabels[label] = {
1319
- stepId: step.id,
1320
- foreachIndex: executionContext.foreachIndex
1321
- };
1322
- }
1446
+ tracingContext,
1447
+ onFinish: (result) => {
1448
+ const resultWithObject = result;
1449
+ if (agentOptions?.structuredOutput?.schema && resultWithObject.object) {
1450
+ structuredResult = resultWithObject.object;
1323
1451
  }
1324
- suspended = { payload: suspendPayload };
1325
- },
1326
- bail: (result2) => {
1327
- bailed = { payload: result2 };
1328
- },
1329
- resume: {
1330
- steps: resume?.steps?.slice(1) || [],
1331
- resumePayload: resume?.resumePayload,
1332
- // @ts-ignore
1333
- runId: stepResults[step.id]?.suspendPayload?.__workflow_meta?.runId
1334
- },
1335
- [_constants.EMITTER_SYMBOL]: emitter,
1336
- [_constants.STREAM_FORMAT_SYMBOL]: executionContext.format,
1337
- engine: {
1338
- step: this.inngestStep
1452
+ streamPromise.resolve(result.text);
1453
+ void agentOptions?.onFinish?.(result);
1339
1454
  },
1340
- abortSignal: abortController.signal
1341
- });
1342
- const endedAt = Date.now();
1343
- execResults = {
1344
- status: "success",
1345
- output: result,
1346
- startedAt,
1347
- endedAt,
1348
- payload: inputData,
1349
- resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1350
- resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1351
- };
1352
- } catch (e) {
1353
- const stepFailure = {
1354
- status: "failed",
1355
- payload: inputData,
1356
- error: e instanceof Error ? e.message : String(e),
1357
- endedAt: Date.now(),
1358
- startedAt,
1359
- resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1360
- resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1361
- };
1362
- execResults = stepFailure;
1363
- const fallbackErrorMessage = `Step ${step.id} failed`;
1364
- stepAISpan?.error({ error: new Error(execResults.error ?? fallbackErrorMessage) });
1365
- throw new inngest.RetryAfterError(execResults.error ?? fallbackErrorMessage, executionContext.retryConfig.delay, {
1366
- cause: execResults
1455
+ abortSignal
1367
1456
  });
1457
+ stream = modelOutput.fullStream;
1368
1458
  }
1369
- if (suspended) {
1370
- execResults = {
1371
- status: "suspended",
1372
- suspendPayload: suspended.payload,
1373
- payload: inputData,
1374
- suspendedAt: Date.now(),
1375
- startedAt,
1376
- resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1377
- resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1378
- };
1379
- } else if (bailed) {
1380
- execResults = {
1381
- status: "bailed",
1382
- output: bailed.payload,
1383
- payload: inputData,
1384
- endedAt: Date.now(),
1385
- startedAt
1386
- };
1387
- }
1388
- await emitter.emit("watch", {
1389
- type: "watch",
1390
- payload: {
1391
- currentStep: {
1392
- id: step.id,
1393
- ...execResults
1394
- },
1395
- workflowState: {
1396
- status: "running",
1397
- steps: { ...stepResults, [step.id]: execResults },
1398
- result: null,
1399
- error: null
1400
- }
1401
- },
1402
- eventTimestamp: Date.now()
1403
- });
1404
- if (execResults.status === "suspended") {
1405
- await emitter.emit("watch-v2", {
1406
- type: "workflow-step-suspended",
1407
- payload: {
1408
- id: step.id,
1409
- ...execResults
1410
- }
1411
- });
1412
- } else {
1413
- await emitter.emit("watch-v2", {
1414
- type: "workflow-step-result",
1415
- payload: {
1416
- id: step.id,
1417
- ...execResults
1418
- }
1459
+ if (streamFormat === "legacy") {
1460
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1461
+ type: "watch",
1462
+ runId,
1463
+ data: { type: "tool-call-streaming-start", ...toolData ?? {} }
1419
1464
  });
1420
- await emitter.emit("watch-v2", {
1421
- type: "workflow-step-finish",
1422
- payload: {
1423
- id: step.id,
1424
- metadata: {}
1465
+ for await (const chunk of stream) {
1466
+ if (chunk.type === "text-delta") {
1467
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1468
+ type: "watch",
1469
+ runId,
1470
+ data: { type: "tool-call-delta", ...toolData ?? {}, argsTextDelta: chunk.textDelta }
1471
+ });
1425
1472
  }
1473
+ }
1474
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1475
+ type: "watch",
1476
+ runId,
1477
+ data: { type: "tool-call-streaming-finish", ...toolData ?? {} }
1426
1478
  });
1479
+ } else {
1480
+ for await (const chunk of stream) {
1481
+ await writer.write(chunk);
1482
+ }
1427
1483
  }
1428
- stepAISpan?.end({ output: execResults });
1429
- return { result: execResults, executionContext, stepResults };
1430
- });
1431
- } catch (e) {
1432
- const stepFailure = e instanceof Error ? e?.cause : {
1433
- status: "failed",
1434
- error: e instanceof Error ? e.message : String(e),
1435
- payload: inputData,
1436
- startedAt,
1437
- endedAt: Date.now()
1438
- };
1439
- stepRes = {
1440
- result: stepFailure,
1441
- executionContext,
1442
- stepResults: {
1443
- ...stepResults,
1444
- [step.id]: stepFailure
1484
+ if (abortSignal.aborted) {
1485
+ return abort();
1445
1486
  }
1446
- };
1447
- }
1448
- if (disableScorers !== false && stepRes.result.status === "success") {
1449
- await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}.score`, async () => {
1450
- if (step.scorers) {
1451
- await this.runScorers({
1452
- scorers: step.scorers,
1453
- runId: executionContext.runId,
1454
- input: inputData,
1455
- output: stepRes.result,
1456
- workflowId: executionContext.workflowId,
1457
- stepId: step.id,
1458
- requestContext,
1459
- disableScorers,
1460
- tracingContext: { currentSpan: stepAISpan }
1461
- });
1487
+ if (structuredResult !== null) {
1488
+ return structuredResult;
1462
1489
  }
1463
- });
1464
- }
1465
- Object.assign(executionContext.suspendedPaths, stepRes.executionContext.suspendedPaths);
1466
- Object.assign(stepResults, stepRes.stepResults);
1467
- executionContext.state = stepRes.executionContext.state;
1468
- return stepRes.result;
1490
+ return {
1491
+ text: await streamPromise.promise
1492
+ };
1493
+ },
1494
+ component: params.component
1495
+ };
1469
1496
  }
1470
- async persistStepUpdate({
1471
- workflowId,
1472
- runId,
1473
- stepResults,
1474
- resourceId,
1475
- executionContext,
1476
- serializedStepGraph,
1477
- workflowStatus,
1478
- result,
1479
- error
1480
- }) {
1481
- await this.inngestStep.run(
1482
- `workflow.${workflowId}.run.${runId}.path.${JSON.stringify(executionContext.executionPath)}.stepUpdate`,
1483
- async () => {
1484
- const shouldPersistSnapshot = this.options.shouldPersistSnapshot({ stepResults, workflowStatus });
1485
- if (!shouldPersistSnapshot) {
1486
- return;
1487
- }
1488
- await this.mastra?.getStorage()?.persistWorkflowSnapshot({
1489
- workflowName: workflowId,
1490
- runId,
1491
- resourceId,
1492
- snapshot: {
1497
+ if (params instanceof tools.Tool) {
1498
+ const toolOpts = agentOrToolOptions;
1499
+ if (!params.inputSchema || !params.outputSchema) {
1500
+ throw new Error("Tool must have input and output schemas defined");
1501
+ }
1502
+ return {
1503
+ // TODO: tool probably should have strong id type
1504
+ id: params.id,
1505
+ description: params.description,
1506
+ inputSchema: params.inputSchema,
1507
+ outputSchema: params.outputSchema,
1508
+ suspendSchema: params.suspendSchema,
1509
+ resumeSchema: params.resumeSchema,
1510
+ retries: toolOpts?.retries,
1511
+ scorers: toolOpts?.scorers,
1512
+ execute: async ({
1513
+ inputData,
1514
+ mastra,
1515
+ requestContext,
1516
+ tracingContext,
1517
+ suspend,
1518
+ resumeData,
1519
+ runId,
1520
+ workflowId,
1521
+ state,
1522
+ setState
1523
+ }) => {
1524
+ const toolContext = {
1525
+ mastra,
1526
+ requestContext,
1527
+ tracingContext,
1528
+ workflow: {
1493
1529
  runId,
1494
- value: executionContext.state,
1495
- context: stepResults,
1496
- activePaths: [],
1497
- suspendedPaths: executionContext.suspendedPaths,
1498
- resumeLabels: executionContext.resumeLabels,
1499
- waitingPaths: {},
1500
- serializedStepGraph,
1501
- status: workflowStatus,
1502
- result,
1503
- error,
1504
- // @ts-ignore
1505
- timestamp: Date.now()
1506
- }
1507
- });
1508
- }
1509
- );
1510
- }
1511
- async executeConditional({
1512
- workflowId,
1513
- runId,
1514
- entry,
1515
- prevOutput,
1516
- stepResults,
1517
- resume,
1518
- executionContext,
1519
- emitter,
1520
- abortController,
1521
- requestContext,
1522
- writableStream,
1523
- disableScorers,
1524
- tracingContext
1525
- }) {
1526
- const conditionalSpan = tracingContext?.currentSpan?.createChildSpan({
1527
- type: aiTracing.AISpanType.WORKFLOW_CONDITIONAL,
1528
- name: `conditional: '${entry.conditions.length} conditions'`,
1529
- input: prevOutput,
1530
- attributes: {
1531
- conditionCount: entry.conditions.length
1532
- },
1533
- tracingPolicy: this.options?.tracingPolicy
1534
- });
1535
- let execResults;
1536
- const truthyIndexes = (await Promise.all(
1537
- entry.conditions.map(
1538
- (cond, index) => this.inngestStep.run(`workflow.${workflowId}.conditional.${index}`, async () => {
1539
- const evalSpan = conditionalSpan?.createChildSpan({
1540
- type: aiTracing.AISpanType.WORKFLOW_CONDITIONAL_EVAL,
1541
- name: `condition: '${index}'`,
1542
- input: prevOutput,
1543
- attributes: {
1544
- conditionIndex: index
1545
- },
1546
- tracingPolicy: this.options?.tracingPolicy
1547
- });
1548
- try {
1549
- const result = await cond(
1550
- workflows.createDeprecationProxy(
1551
- {
1552
- runId,
1553
- workflowId,
1554
- mastra: this.mastra,
1555
- requestContext,
1556
- runCount: -1,
1557
- retryCount: -1,
1558
- inputData: prevOutput,
1559
- state: executionContext.state,
1560
- setState: (state) => {
1561
- executionContext.state = state;
1562
- },
1563
- tracingContext: {
1564
- currentSpan: evalSpan
1565
- },
1566
- getInitData: () => stepResults?.input,
1567
- getStepResult: workflows.getStepResult.bind(this, stepResults),
1568
- // TODO: this function shouldn't have suspend probably?
1569
- suspend: async (_suspendPayload) => {
1570
- },
1571
- bail: () => {
1572
- },
1573
- abort: () => {
1574
- abortController.abort();
1575
- },
1576
- [_constants.EMITTER_SYMBOL]: emitter,
1577
- [_constants.STREAM_FORMAT_SYMBOL]: executionContext.format,
1578
- engine: {
1579
- step: this.inngestStep
1580
- },
1581
- abortSignal: abortController.signal,
1582
- writer: new tools.ToolStream(
1583
- {
1584
- prefix: "workflow-step",
1585
- callId: crypto.randomUUID(),
1586
- name: "conditional",
1587
- runId
1588
- },
1589
- writableStream
1590
- )
1591
- },
1592
- {
1593
- paramName: "runCount",
1594
- deprecationMessage: workflows.runCountDeprecationMessage,
1595
- logger: this.logger
1596
- }
1597
- )
1598
- );
1599
- evalSpan?.end({
1600
- output: result,
1601
- attributes: {
1602
- result: !!result
1603
- }
1604
- });
1605
- return result ? index : null;
1606
- } catch (e) {
1607
- evalSpan?.error({
1608
- error: e instanceof Error ? e : new Error(String(e)),
1609
- attributes: {
1610
- result: false
1611
- }
1612
- });
1613
- return null;
1614
- }
1615
- })
1616
- )
1617
- )).filter((index) => index !== null);
1618
- const stepsToRun = entry.steps.filter((_, index) => truthyIndexes.includes(index));
1619
- conditionalSpan?.update({
1620
- attributes: {
1621
- truthyIndexes,
1622
- selectedSteps: stepsToRun.map((s) => s.type === "step" ? s.step.id : `control-${s.type}`)
1623
- }
1624
- });
1625
- const results = await Promise.all(
1626
- stepsToRun.map(async (step, index) => {
1627
- const currStepResult = stepResults[step.step.id];
1628
- if (currStepResult && currStepResult.status === "success") {
1629
- return currStepResult;
1630
- }
1631
- const result = await this.executeStep({
1632
- step: step.step,
1633
- prevOutput,
1634
- stepResults,
1635
- resume,
1636
- executionContext: {
1530
+ resumeData,
1531
+ suspend,
1637
1532
  workflowId,
1638
- runId,
1639
- executionPath: [...executionContext.executionPath, index],
1640
- suspendedPaths: executionContext.suspendedPaths,
1641
- resumeLabels: executionContext.resumeLabels,
1642
- retryConfig: executionContext.retryConfig,
1643
- state: executionContext.state
1644
- },
1645
- emitter,
1646
- abortController,
1647
- requestContext,
1648
- writableStream,
1649
- disableScorers,
1650
- tracingContext: {
1651
- currentSpan: conditionalSpan
1652
- }
1653
- });
1654
- stepResults[step.step.id] = result;
1655
- return result;
1656
- })
1657
- );
1658
- const hasFailed = results.find((result) => result.status === "failed");
1659
- const hasSuspended = results.find((result) => result.status === "suspended");
1660
- if (hasFailed) {
1661
- execResults = { status: "failed", error: hasFailed.error };
1662
- } else if (hasSuspended) {
1663
- execResults = { status: "suspended", suspendPayload: hasSuspended.suspendPayload };
1664
- } else {
1665
- execResults = {
1666
- status: "success",
1667
- output: results.reduce((acc, result, index) => {
1668
- if (result.status === "success") {
1669
- acc[stepsToRun[index].step.id] = result.output;
1533
+ state,
1534
+ setState
1670
1535
  }
1671
- return acc;
1672
- }, {})
1536
+ };
1537
+ return params.execute(inputData, toolContext);
1538
+ },
1539
+ component: "TOOL"
1540
+ };
1541
+ }
1542
+ return {
1543
+ id: params.id,
1544
+ description: params.description,
1545
+ inputSchema: params.inputSchema,
1546
+ outputSchema: params.outputSchema,
1547
+ resumeSchema: params.resumeSchema,
1548
+ suspendSchema: params.suspendSchema,
1549
+ retries: params.retries,
1550
+ scorers: params.scorers,
1551
+ execute: params.execute
1552
+ };
1553
+ }
1554
+ function init(inngest) {
1555
+ return {
1556
+ createWorkflow(params) {
1557
+ return new InngestWorkflow(
1558
+ params,
1559
+ inngest
1560
+ );
1561
+ },
1562
+ createStep,
1563
+ cloneStep(step, opts) {
1564
+ return {
1565
+ id: opts.id,
1566
+ description: step.description,
1567
+ inputSchema: step.inputSchema,
1568
+ outputSchema: step.outputSchema,
1569
+ resumeSchema: step.resumeSchema,
1570
+ suspendSchema: step.suspendSchema,
1571
+ stateSchema: step.stateSchema,
1572
+ execute: step.execute,
1573
+ retries: step.retries,
1574
+ scorers: step.scorers,
1575
+ component: step.component
1673
1576
  };
1674
- }
1675
- if (execResults.status === "failed") {
1676
- conditionalSpan?.error({
1677
- error: new Error(execResults.error)
1678
- });
1679
- } else {
1680
- conditionalSpan?.end({
1681
- output: execResults.output || execResults
1577
+ },
1578
+ cloneWorkflow(workflow, opts) {
1579
+ const wf = new workflows.Workflow({
1580
+ id: opts.id,
1581
+ inputSchema: workflow.inputSchema,
1582
+ outputSchema: workflow.outputSchema,
1583
+ steps: workflow.stepDefs,
1584
+ mastra: workflow.mastra,
1585
+ options: workflow.options
1682
1586
  });
1587
+ wf.setStepFlow(workflow.stepGraph);
1588
+ wf.commit();
1589
+ return wf;
1683
1590
  }
1684
- return execResults;
1685
- }
1686
- };
1591
+ };
1592
+ }
1687
1593
 
1688
1594
  exports.InngestExecutionEngine = InngestExecutionEngine;
1595
+ exports.InngestPubSub = InngestPubSub;
1689
1596
  exports.InngestRun = InngestRun;
1690
1597
  exports.InngestWorkflow = InngestWorkflow;
1598
+ exports._compatibilityCheck = _compatibilityCheck;
1691
1599
  exports.createStep = createStep;
1692
1600
  exports.init = init;
1693
1601
  exports.serve = serve;