@mastra/inngest 1.0.0-beta.1 → 1.0.0-beta.11

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,43 +1,513 @@
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 di = require('@mastra/core/di');
7
- var observability = require('@mastra/core/observability');
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();
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;
26
+ }
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"
41
+ });
42
+ return errorInstance.toJSON();
43
+ }
44
+ /**
45
+ * Detect InngestWorkflow instances for special nested workflow handling
46
+ */
47
+ isNestedWorkflowStep(step) {
48
+ return step instanceof InngestWorkflow;
49
+ }
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;
57
+ }
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));
67
+ }
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
+ };
30
100
  }
31
- return [];
32
- })
33
- )
34
- );
35
- return hono.serve({
36
- ...registerOptions,
37
- client: inngest,
38
- functions: [...workflowFunctions, ...userFunctions]
39
- });
40
- }
101
+ }
102
+ }
103
+ return { ok: false, error: { status: "failed", error: new Error("Unknown error"), endedAt: Date.now() } };
104
+ }
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);
110
+ }
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);
116
+ }
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;
128
+ }
129
+ });
130
+ }
131
+ /**
132
+ * Provide Inngest step primitive in engine context
133
+ */
134
+ getEngineContext() {
135
+ return { step: this.inngestStep };
136
+ }
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) {
142
+ }
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
180
+ });
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
255
+ };
256
+ }
257
+ }
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
+ }
273
+ }
274
+ });
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
+ }
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
+ };
307
+ }
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
+ }
360
+ });
361
+ return { executionContext, result: { status: "paused" } };
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() } };
387
+ }
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
+ };
397
+ }
398
+ };
399
+ var InngestPubSub = class extends events.PubSub {
400
+ inngest;
401
+ workflowId;
402
+ publishFn;
403
+ subscriptions = /* @__PURE__ */ new Map();
404
+ constructor(inngest, workflowId, publishFn) {
405
+ super();
406
+ this.inngest = inngest;
407
+ this.workflowId = workflowId;
408
+ this.publishFn = publishFn;
409
+ }
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;
419
+ }
420
+ const match = topic.match(/^workflow\.events\.v2\.(.+)$/);
421
+ if (!match) {
422
+ return;
423
+ }
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);
433
+ }
434
+ }
435
+ /**
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}"
440
+ */
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(
454
+ {
455
+ channel,
456
+ topics: ["watch"],
457
+ app: this.inngest
458
+ },
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
+ }
471
+ );
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
479
+ });
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
+ };
41
511
  var InngestRun = class extends workflows.Run {
42
512
  inngest;
43
513
  serializedStepGraph;
@@ -49,38 +519,90 @@ var InngestRun = class extends workflows.Run {
49
519
  this.#mastra = params.mastra;
50
520
  }
51
521
  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}`
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
+ }
55
554
  }
56
- });
57
- const json = await response.json();
58
- return json.data;
555
+ }
556
+ throw new inngest.NonRetriableError(`Failed to get runs after ${maxRetries} attempts: ${lastError?.message}`);
59
557
  }
60
- async getRunOutput(eventId) {
61
- let runs = await this.getRuns(eventId);
558
+ async getRunOutput(eventId, maxWaitMs = 3e5) {
559
+ const startTime = Date.now();
62
560
  const storage = this.#mastra?.getStorage();
63
- while (runs?.[0]?.status !== "Completed" || runs?.[0]?.event_id !== eventId) {
64
- await new Promise((resolve) => setTimeout(resolve, 1e3));
65
- runs = await this.getRuns(eventId);
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
+ }
66
577
  if (runs?.[0]?.status === "Failed") {
67
- const snapshot = await storage?.loadWorkflowSnapshot({
578
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
68
579
  workflowName: this.workflowId,
69
580
  runId: this.runId
70
581
  });
582
+ if (snapshot?.context) {
583
+ snapshot.context = workflows.hydrateSerializedStepErrors(snapshot.context);
584
+ }
71
585
  return {
72
- output: { result: { steps: snapshot?.context, status: "failed", error: runs?.[0]?.output?.message } }
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
+ }
73
594
  };
74
595
  }
75
596
  if (runs?.[0]?.status === "Cancelled") {
76
- const snapshot = await storage?.loadWorkflowSnapshot({
597
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
77
598
  workflowName: this.workflowId,
78
599
  runId: this.runId
79
600
  });
80
601
  return { output: { result: { steps: snapshot?.context, status: "canceled" } } };
81
602
  }
603
+ await new Promise((resolve) => setTimeout(resolve, 1e3 + Math.random() * 1e3));
82
604
  }
83
- return runs?.[0];
605
+ throw new inngest.NonRetriableError(`Workflow did not complete within ${maxWaitMs}ms`);
84
606
  }
85
607
  async cancel() {
86
608
  const storage = this.#mastra?.getStorage();
@@ -90,18 +612,20 @@ var InngestRun = class extends workflows.Run {
90
612
  runId: this.runId
91
613
  }
92
614
  });
93
- const snapshot = await storage?.loadWorkflowSnapshot({
615
+ const workflowsStore = await storage?.getStore("workflows");
616
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
94
617
  workflowName: this.workflowId,
95
618
  runId: this.runId
96
619
  });
97
620
  if (snapshot) {
98
- await storage?.persistWorkflowSnapshot({
621
+ await workflowsStore?.persistWorkflowSnapshot({
99
622
  workflowName: this.workflowId,
100
623
  runId: this.runId,
101
624
  resourceId: this.resourceId,
102
625
  snapshot: {
103
626
  ...snapshot,
104
- status: "canceled"
627
+ status: "canceled",
628
+ value: snapshot.value
105
629
  }
106
630
  });
107
631
  }
@@ -109,28 +633,79 @@ var InngestRun = class extends workflows.Run {
109
633
  async start(params) {
110
634
  return this._start(params);
111
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
+ }
112
683
  async _start({
113
684
  inputData,
114
685
  initialState,
115
686
  outputOptions,
116
687
  tracingOptions,
117
- format
688
+ format,
689
+ requestContext,
690
+ perStep
118
691
  }) {
119
- await this.#mastra.getStorage()?.persistWorkflowSnapshot({
692
+ const workflowsStore = await this.#mastra.getStorage()?.getStore("workflows");
693
+ await workflowsStore?.persistWorkflowSnapshot({
120
694
  workflowName: this.workflowId,
121
695
  runId: this.runId,
122
696
  resourceId: this.resourceId,
123
697
  snapshot: {
124
698
  runId: this.runId,
125
699
  serializedStepGraph: this.serializedStepGraph,
700
+ status: "running",
126
701
  value: {},
127
702
  context: {},
128
703
  activePaths: [],
129
704
  suspendedPaths: {},
705
+ activeStepsPath: {},
130
706
  resumeLabels: {},
131
707
  waitingPaths: {},
132
- timestamp: Date.now(),
133
- status: "running"
708
+ timestamp: Date.now()
134
709
  }
135
710
  });
136
711
  const inputDataToUse = await this._validateInput(inputData);
@@ -144,7 +719,9 @@ var InngestRun = class extends workflows.Run {
144
719
  resourceId: this.resourceId,
145
720
  outputOptions,
146
721
  tracingOptions,
147
- format
722
+ format,
723
+ requestContext: requestContext ? Object.fromEntries(requestContext.entries()) : {},
724
+ perStep
148
725
  }
149
726
  });
150
727
  const eventId = eventOutput.ids[0];
@@ -153,9 +730,7 @@ var InngestRun = class extends workflows.Run {
153
730
  }
154
731
  const runOutput = await this.getRunOutput(eventId);
155
732
  const result = runOutput?.output?.result;
156
- if (result.status === "failed") {
157
- result.error = new Error(result.error);
158
- }
733
+ this.hydrateFailedResult(result);
159
734
  if (result.status !== "suspended") {
160
735
  this.cleanup?.();
161
736
  }
@@ -174,15 +749,24 @@ var InngestRun = class extends workflows.Run {
174
749
  }
175
750
  async _resume(params) {
176
751
  const storage = this.#mastra?.getStorage();
177
- const steps = (Array.isArray(params.step) ? params.step : [params.step]).map(
178
- (step) => typeof step === "string" ? step : step?.id
179
- );
180
- const snapshot = await storage?.loadWorkflowSnapshot({
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({
181
762
  workflowName: this.workflowId,
182
763
  runId: this.runId
183
764
  });
184
765
  const suspendedStep = this.workflowSteps[steps?.[0] ?? ""];
185
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 };
186
770
  const eventOutput = await this.inngest.send({
187
771
  name: `workflow.${this.workflowId}`,
188
772
  data: {
@@ -195,9 +779,10 @@ var InngestRun = class extends workflows.Run {
195
779
  steps,
196
780
  stepResults: snapshot?.context,
197
781
  resumePayload: resumeDataToUse,
198
- // @ts-ignore
199
- resumePath: snapshot?.suspendedPaths?.[steps?.[0]]
200
- }
782
+ resumePath: steps?.[0] ? snapshot?.suspendedPaths?.[steps?.[0]] : void 0
783
+ },
784
+ requestContext: mergedRequestContext,
785
+ perStep: params.perStep
201
786
  }
202
787
  });
203
788
  const eventId = eventOutput.ids[0];
@@ -206,9 +791,100 @@ var InngestRun = class extends workflows.Run {
206
791
  }
207
792
  const runOutput = await this.getRunOutput(eventId);
208
793
  const result = runOutput?.output?.result;
209
- if (result.status === "failed") {
210
- result.error = new Error(result.error);
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");
211
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,
836
+ serializedStepGraph: this.serializedStepGraph,
837
+ status: "pending",
838
+ value: {},
839
+ context: {},
840
+ activePaths: [],
841
+ suspendedPaths: {},
842
+ activeStepsPath: {},
843
+ resumeLabels: {},
844
+ waitingPaths: {},
845
+ timestamp: Date.now()
846
+ }
847
+ });
848
+ }
849
+ if (snapshot?.status === "running") {
850
+ throw new Error("This workflow run is still running, cannot time travel");
851
+ }
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);
212
888
  return result;
213
889
  }
214
890
  watch(cb) {
@@ -237,14 +913,14 @@ var InngestRun = class extends workflows.Run {
237
913
  streamLegacy({ inputData, requestContext } = {}) {
238
914
  const { readable, writable } = new TransformStream();
239
915
  const writer = writable.getWriter();
916
+ void writer.write({
917
+ // @ts-ignore
918
+ type: "start",
919
+ // @ts-ignore
920
+ payload: { runId: this.runId }
921
+ });
240
922
  const unwatch = this.watch(async (event) => {
241
923
  try {
242
- await writer.write({
243
- // @ts-ignore
244
- type: "start",
245
- // @ts-ignore
246
- payload: { runId: this.runId }
247
- });
248
924
  const e = {
249
925
  ...event,
250
926
  type: event.type.replace("workflow-", "")
@@ -290,7 +966,8 @@ var InngestRun = class extends workflows.Run {
290
966
  tracingOptions,
291
967
  closeOnSuspend = true,
292
968
  initialState,
293
- outputOptions
969
+ outputOptions,
970
+ perStep
294
971
  } = {}) {
295
972
  if (this.closeStreamAction && this.streamOutput) {
296
973
  return this.streamOutput;
@@ -326,7 +1003,8 @@ var InngestRun = class extends workflows.Run {
326
1003
  initialState,
327
1004
  tracingOptions,
328
1005
  outputOptions,
329
- format: "vnext"
1006
+ format: "vnext",
1007
+ perStep
330
1008
  });
331
1009
  let executionResults;
332
1010
  try {
@@ -357,24 +1035,112 @@ var InngestRun = class extends workflows.Run {
357
1035
  });
358
1036
  return this.streamOutput;
359
1037
  }
360
- streamVNext(args = {}) {
361
- return this.stream(args);
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
+ });
1065
+ });
1066
+ self.closeStreamAction = async () => {
1067
+ unwatch();
1068
+ try {
1069
+ controller.close();
1070
+ } catch (err) {
1071
+ console.error("Error closing stream:", err);
1072
+ }
1073
+ };
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(() => {
1091
+ });
1092
+ if (self.streamOutput) {
1093
+ self.streamOutput.updateResults(executionResults);
1094
+ }
1095
+ } catch (err) {
1096
+ self.streamOutput?.rejectResults(err);
1097
+ self.closeStreamAction?.().catch(() => {
1098
+ });
1099
+ }
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;
1108
+ }
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
+ }
1119
+ }
362
1120
  }
363
1121
  };
1122
+
1123
+ // src/workflow.ts
364
1124
  var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
365
1125
  #mastra;
366
1126
  inngest;
367
1127
  function;
1128
+ cronFunction;
368
1129
  flowControlConfig;
1130
+ cronConfig;
369
1131
  constructor(params, inngest) {
370
- const { concurrency, rateLimit, throttle, debounce, priority, ...workflowParams } = params;
1132
+ const { concurrency, rateLimit, throttle, debounce, priority, cron, inputData, initialState, ...workflowParams } = params;
371
1133
  super(workflowParams);
1134
+ this.engineType = "inngest";
372
1135
  const flowControlEntries = Object.entries({ concurrency, rateLimit, throttle, debounce, priority }).filter(
373
1136
  ([_, value]) => value !== void 0
374
1137
  );
375
1138
  this.flowControlConfig = flowControlEntries.length > 0 ? Object.fromEntries(flowControlEntries) : void 0;
376
1139
  this.#mastra = params.mastra;
377
1140
  this.inngest = inngest;
1141
+ if (cron) {
1142
+ this.cronConfig = { cron, inputData, initialState };
1143
+ }
378
1144
  }
379
1145
  async listWorkflowRuns(args) {
380
1146
  const storage = this.#mastra?.getStorage();
@@ -382,18 +1148,14 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
382
1148
  this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
383
1149
  return { runs: [], total: 0 };
384
1150
  }
385
- return storage.listWorkflowRuns({ workflowName: this.id, ...args ?? {} });
386
- }
387
- async getWorkflowRunById(runId) {
388
- const storage = this.#mastra?.getStorage();
389
- if (!storage) {
390
- this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
391
- return this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null;
1151
+ const workflowsStore = await storage.getStore("workflows");
1152
+ if (!workflowsStore) {
1153
+ return { runs: [], total: 0 };
392
1154
  }
393
- const run = await storage.getWorkflowRunById({ runId, workflowName: this.id });
394
- return run ?? (this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null);
1155
+ return workflowsStore.listWorkflowRuns({ workflowName: this.id, ...args ?? {} });
395
1156
  }
396
1157
  __registerMastra(mastra) {
1158
+ super.__registerMastra(mastra);
397
1159
  this.#mastra = mastra;
398
1160
  this.executionEngine.__registerMastra(mastra);
399
1161
  const updateNested = (step) => {
@@ -412,7 +1174,7 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
412
1174
  }
413
1175
  }
414
1176
  async createRun(options) {
415
- const runIdToUse = options?.runId || crypto.randomUUID();
1177
+ const runIdToUse = options?.runId || crypto$1.randomUUID();
416
1178
  const run = this.runs.get(runIdToUse) ?? new InngestRun(
417
1179
  {
418
1180
  workflowId: this.id,
@@ -424,7 +1186,9 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
424
1186
  mastra: this.#mastra,
425
1187
  retryConfig: this.retryConfig,
426
1188
  cleanup: () => this.runs.delete(runIdToUse),
427
- workflowSteps: this.steps
1189
+ workflowSteps: this.steps,
1190
+ workflowEngineType: this.engineType,
1191
+ validateInputs: this.options.validateInputs
428
1192
  },
429
1193
  this.inngest
430
1194
  );
@@ -433,9 +1197,13 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
433
1197
  workflowStatus: run.workflowRunStatus,
434
1198
  stepResults: {}
435
1199
  });
436
- const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse, false);
437
- if (!workflowSnapshotInStorage && shouldPersistSnapshot) {
438
- await this.mastra?.getStorage()?.persistWorkflowSnapshot({
1200
+ const existingRun = await this.getWorkflowRunById(runIdToUse, {
1201
+ withNestedWorkflows: false
1202
+ });
1203
+ const existsInStorage = existingRun && !existingRun.isFromInMemory;
1204
+ if (!existsInStorage && shouldPersistSnapshot) {
1205
+ const workflowsStore = await this.mastra?.getStorage()?.getStore("workflows");
1206
+ await workflowsStore?.persistWorkflowSnapshot({
439
1207
  workflowName: this.id,
440
1208
  runId: runIdToUse,
441
1209
  resourceId: options?.resourceId,
@@ -445,19 +1213,43 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
445
1213
  value: {},
446
1214
  context: {},
447
1215
  activePaths: [],
1216
+ activeStepsPath: {},
448
1217
  waitingPaths: {},
449
1218
  serializedStepGraph: this.serializedStepGraph,
450
1219
  suspendedPaths: {},
451
1220
  resumeLabels: {},
452
1221
  result: void 0,
453
1222
  error: void 0,
454
- // @ts-ignore
455
1223
  timestamp: Date.now()
456
1224
  }
457
1225
  });
458
1226
  }
459
1227
  return run;
460
1228
  }
1229
+ //createCronFunction is only called if cronConfig.cron is defined.
1230
+ createCronFunction() {
1231
+ if (this.cronFunction) {
1232
+ return this.cronFunction;
1233
+ }
1234
+ this.cronFunction = this.inngest.createFunction(
1235
+ {
1236
+ id: `workflow.${this.id}.cron`,
1237
+ retries: 0,
1238
+ cancelOn: [{ event: `cancel.workflow.${this.id}` }],
1239
+ ...this.flowControlConfig
1240
+ },
1241
+ { cron: this.cronConfig?.cron ?? "" },
1242
+ async () => {
1243
+ const run = await this.createRun();
1244
+ const result = await run.start({
1245
+ inputData: this.cronConfig?.inputData,
1246
+ initialState: this.cronConfig?.initialState
1247
+ });
1248
+ return { result, runId: run.runId };
1249
+ }
1250
+ );
1251
+ return this.cronFunction;
1252
+ }
461
1253
  getFunction() {
462
1254
  if (this.function) {
463
1255
  return this.function;
@@ -465,42 +1257,20 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
465
1257
  this.function = this.inngest.createFunction(
466
1258
  {
467
1259
  id: `workflow.${this.id}`,
468
- // @ts-ignore
469
- retries: this.retryConfig?.attempts ?? 0,
1260
+ retries: 0,
470
1261
  cancelOn: [{ event: `cancel.workflow.${this.id}` }],
471
1262
  // Spread flow control configuration
472
1263
  ...this.flowControlConfig
473
1264
  },
474
1265
  { event: `workflow.${this.id}` },
475
1266
  async ({ event, step, attempt, publish }) => {
476
- let { inputData, initialState, runId, resourceId, resume, outputOptions, format } = event.data;
1267
+ let { inputData, initialState, runId, resourceId, resume, outputOptions, format, timeTravel, perStep } = event.data;
477
1268
  if (!runId) {
478
1269
  runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
479
- return crypto.randomUUID();
1270
+ return crypto$1.randomUUID();
480
1271
  });
481
1272
  }
482
- const emitter = {
483
- emit: async (event2, data) => {
484
- if (!publish) {
485
- return;
486
- }
487
- try {
488
- await publish({
489
- channel: `workflow:${this.id}:${runId}`,
490
- topic: event2,
491
- data
492
- });
493
- } catch (err) {
494
- this.logger.error("Error emitting event: " + (err?.stack ?? err?.message ?? err));
495
- }
496
- },
497
- on: (_event, _callback) => {
498
- },
499
- off: (_event, _callback) => {
500
- },
501
- once: (_event, _callback) => {
502
- }
503
- };
1273
+ const pubsub = new InngestPubSub(this.inngest, this.id, publish);
504
1274
  const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
505
1275
  const result = await engine.execute({
506
1276
  workflowId: this.id,
@@ -510,23 +1280,32 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
510
1280
  serializedStepGraph: this.serializedStepGraph,
511
1281
  input: inputData,
512
1282
  initialState,
513
- emitter,
1283
+ pubsub,
514
1284
  retryConfig: this.retryConfig,
515
- requestContext: new di.RequestContext(),
516
- // TODO
1285
+ requestContext: new di.RequestContext(Object.entries(event.data.requestContext ?? {})),
517
1286
  resume,
1287
+ timeTravel,
1288
+ perStep,
518
1289
  format,
519
1290
  abortController: new AbortController(),
520
1291
  // currentSpan: undefined, // TODO: Pass actual parent Span from workflow execution context
521
1292
  outputOptions,
522
- writableStream: new WritableStream({
523
- write(chunk) {
524
- void emitter.emit("watch", chunk).catch(() => {
1293
+ outputWriter: async (chunk) => {
1294
+ try {
1295
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1296
+ type: "watch",
1297
+ runId,
1298
+ data: chunk
525
1299
  });
1300
+ } catch (err) {
1301
+ this.logger.debug?.("Failed to publish watch event:", err);
526
1302
  }
527
- })
1303
+ }
528
1304
  });
529
1305
  await step.run(`workflow.${this.id}.finalize`, async () => {
1306
+ if (result.status !== "paused") {
1307
+ await engine.invokeLifecycleCallbacksInternal(result);
1308
+ }
530
1309
  if (result.status === "failed") {
531
1310
  throw new inngest.NonRetriableError(`Workflow failed`, {
532
1311
  cause: result
@@ -553,33 +1332,65 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
553
1332
  });
554
1333
  }
555
1334
  getFunctions() {
556
- return [this.getFunction(), ...this.getNestedFunctions(this.executionGraph.steps)];
1335
+ return [
1336
+ this.getFunction(),
1337
+ ...this.cronConfig?.cron ? [this.createCronFunction()] : [],
1338
+ ...this.getNestedFunctions(this.executionGraph.steps)
1339
+ ];
557
1340
  }
558
1341
  };
559
- function isAgent(params) {
560
- return params?.component === "AGENT";
561
- }
562
- function isTool(params) {
563
- return params instanceof tools.Tool;
1342
+ function serve({
1343
+ mastra,
1344
+ inngest,
1345
+ functions: userFunctions = [],
1346
+ registerOptions
1347
+ }) {
1348
+ const wfs = mastra.listWorkflows();
1349
+ const workflowFunctions = Array.from(
1350
+ new Set(
1351
+ Object.values(wfs).flatMap((wf) => {
1352
+ if (wf instanceof InngestWorkflow) {
1353
+ wf.__registerMastra(mastra);
1354
+ return wf.getFunctions();
1355
+ }
1356
+ return [];
1357
+ })
1358
+ )
1359
+ );
1360
+ return hono.serve({
1361
+ ...registerOptions,
1362
+ client: inngest,
1363
+ functions: [...workflowFunctions, ...userFunctions]
1364
+ });
564
1365
  }
565
- function createStep(params, agentOptions) {
566
- if (isAgent(params)) {
1366
+
1367
+ // src/types.ts
1368
+ var _compatibilityCheck = true;
1369
+
1370
+ // src/index.ts
1371
+ function createStep(params, agentOrToolOptions) {
1372
+ if (params instanceof InngestWorkflow) {
1373
+ return params;
1374
+ }
1375
+ if (params instanceof agent.Agent) {
1376
+ const options = agentOrToolOptions;
1377
+ const outputSchema = options?.structuredOutput?.schema ?? zod.z.object({ text: zod.z.string() });
1378
+ const { retries, scorers, ...agentOptions } = options ?? {};
567
1379
  return {
568
1380
  id: params.name,
569
1381
  description: params.getDescription(),
570
- // @ts-ignore
571
1382
  inputSchema: zod.z.object({
572
1383
  prompt: zod.z.string()
573
1384
  // resourceId: z.string().optional(),
574
1385
  // threadId: z.string().optional(),
575
1386
  }),
576
- // @ts-ignore
577
- outputSchema: zod.z.object({
578
- text: zod.z.string()
579
- }),
1387
+ outputSchema,
1388
+ retries,
1389
+ scorers,
580
1390
  execute: async ({
581
1391
  inputData,
582
- [_constants.EMITTER_SYMBOL]: emitter,
1392
+ runId,
1393
+ [_constants.PUBSUB_SYMBOL]: pubsub,
583
1394
  [_constants.STREAM_FORMAT_SYMBOL]: streamFormat,
584
1395
  requestContext,
585
1396
  tracingContext,
@@ -592,6 +1403,7 @@ function createStep(params, agentOptions) {
592
1403
  streamPromise.resolve = resolve;
593
1404
  streamPromise.reject = reject;
594
1405
  });
1406
+ let structuredResult = null;
595
1407
  const toolData = {
596
1408
  name: params.name,
597
1409
  args: inputData
@@ -605,6 +1417,10 @@ function createStep(params, agentOptions) {
605
1417
  requestContext,
606
1418
  tracingContext,
607
1419
  onFinish: (result) => {
1420
+ const resultWithObject = result;
1421
+ if (agentOptions?.structuredOutput?.schema && resultWithObject.object) {
1422
+ structuredResult = resultWithObject.object;
1423
+ }
608
1424
  streamPromise.resolve(result.text);
609
1425
  void agentOptions?.onFinish?.(result);
610
1426
  },
@@ -617,6 +1433,10 @@ function createStep(params, agentOptions) {
617
1433
  requestContext,
618
1434
  tracingContext,
619
1435
  onFinish: (result) => {
1436
+ const resultWithObject = result;
1437
+ if (agentOptions?.structuredOutput?.schema && resultWithObject.object) {
1438
+ structuredResult = resultWithObject.object;
1439
+ }
620
1440
  streamPromise.resolve(result.text);
621
1441
  void agentOptions?.onFinish?.(result);
622
1442
  },
@@ -625,22 +1445,24 @@ function createStep(params, agentOptions) {
625
1445
  stream = modelOutput.fullStream;
626
1446
  }
627
1447
  if (streamFormat === "legacy") {
628
- await emitter.emit("watch", {
629
- type: "tool-call-streaming-start",
630
- ...toolData ?? {}
1448
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1449
+ type: "watch",
1450
+ runId,
1451
+ data: { type: "tool-call-streaming-start", ...toolData ?? {} }
631
1452
  });
632
1453
  for await (const chunk of stream) {
633
1454
  if (chunk.type === "text-delta") {
634
- await emitter.emit("watch", {
635
- type: "tool-call-delta",
636
- ...toolData ?? {},
637
- argsTextDelta: chunk.textDelta
1455
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1456
+ type: "watch",
1457
+ runId,
1458
+ data: { type: "tool-call-delta", ...toolData ?? {}, argsTextDelta: chunk.textDelta }
638
1459
  });
639
1460
  }
640
1461
  }
641
- await emitter.emit("watch", {
642
- type: "tool-call-streaming-finish",
643
- ...toolData ?? {}
1462
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1463
+ type: "watch",
1464
+ runId,
1465
+ data: { type: "tool-call-streaming-finish", ...toolData ?? {} }
644
1466
  });
645
1467
  } else {
646
1468
  for await (const chunk of stream) {
@@ -650,6 +1472,9 @@ function createStep(params, agentOptions) {
650
1472
  if (abortSignal.aborted) {
651
1473
  return abort();
652
1474
  }
1475
+ if (structuredResult !== null) {
1476
+ return structuredResult;
1477
+ }
653
1478
  return {
654
1479
  text: await streamPromise.promise
655
1480
  };
@@ -657,26 +1482,47 @@ function createStep(params, agentOptions) {
657
1482
  component: params.component
658
1483
  };
659
1484
  }
660
- if (isTool(params)) {
1485
+ if (params instanceof tools.Tool) {
1486
+ const toolOpts = agentOrToolOptions;
661
1487
  if (!params.inputSchema || !params.outputSchema) {
662
1488
  throw new Error("Tool must have input and output schemas defined");
663
1489
  }
664
1490
  return {
665
1491
  // TODO: tool probably should have strong id type
666
- // @ts-ignore
667
1492
  id: params.id,
668
1493
  description: params.description,
669
1494
  inputSchema: params.inputSchema,
670
1495
  outputSchema: params.outputSchema,
671
- execute: async ({ inputData, mastra, requestContext, tracingContext, suspend, resumeData }) => {
672
- return params.execute({
673
- context: inputData,
674
- mastra: observability.wrapMastra(mastra, tracingContext),
1496
+ suspendSchema: params.suspendSchema,
1497
+ resumeSchema: params.resumeSchema,
1498
+ retries: toolOpts?.retries,
1499
+ scorers: toolOpts?.scorers,
1500
+ execute: async ({
1501
+ inputData,
1502
+ mastra,
1503
+ requestContext,
1504
+ tracingContext,
1505
+ suspend,
1506
+ resumeData,
1507
+ runId,
1508
+ workflowId,
1509
+ state,
1510
+ setState
1511
+ }) => {
1512
+ const toolContext = {
1513
+ mastra,
675
1514
  requestContext,
676
1515
  tracingContext,
677
- suspend,
678
- resumeData
679
- });
1516
+ workflow: {
1517
+ runId,
1518
+ resumeData,
1519
+ suspend,
1520
+ workflowId,
1521
+ state,
1522
+ setState
1523
+ }
1524
+ };
1525
+ return params.execute(inputData, toolContext);
680
1526
  },
681
1527
  component: "TOOL"
682
1528
  };
@@ -688,6 +1534,8 @@ function createStep(params, agentOptions) {
688
1534
  outputSchema: params.outputSchema,
689
1535
  resumeSchema: params.resumeSchema,
690
1536
  suspendSchema: params.suspendSchema,
1537
+ retries: params.retries,
1538
+ scorers: params.scorers,
691
1539
  execute: params.execute
692
1540
  };
693
1541
  }
@@ -710,6 +1558,8 @@ function init(inngest) {
710
1558
  suspendSchema: step.suspendSchema,
711
1559
  stateSchema: step.stateSchema,
712
1560
  execute: step.execute,
1561
+ retries: step.retries,
1562
+ scorers: step.scorers,
713
1563
  component: step.component
714
1564
  };
715
1565
  },
@@ -719,7 +1569,8 @@ function init(inngest) {
719
1569
  inputSchema: workflow.inputSchema,
720
1570
  outputSchema: workflow.outputSchema,
721
1571
  steps: workflow.stepDefs,
722
- mastra: workflow.mastra
1572
+ mastra: workflow.mastra,
1573
+ options: workflow.options
723
1574
  });
724
1575
  wf.setStepFlow(workflow.stepGraph);
725
1576
  wf.commit();
@@ -727,803 +1578,12 @@ function init(inngest) {
727
1578
  }
728
1579
  };
729
1580
  }
730
- var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
731
- inngestStep;
732
- inngestAttempts;
733
- constructor(mastra, inngestStep, inngestAttempts = 0, options) {
734
- super({ mastra, options });
735
- this.inngestStep = inngestStep;
736
- this.inngestAttempts = inngestAttempts;
737
- }
738
- async fmtReturnValue(emitter, stepResults, lastOutput, error) {
739
- const base = {
740
- status: lastOutput.status,
741
- steps: stepResults
742
- };
743
- if (lastOutput.status === "success") {
744
- base.result = lastOutput.output;
745
- } else if (lastOutput.status === "failed") {
746
- base.error = error instanceof Error ? error?.stack ?? error.message : lastOutput?.error instanceof Error ? lastOutput.error.message : lastOutput.error ?? error ?? "Unknown error";
747
- } else if (lastOutput.status === "suspended") {
748
- const suspendedStepIds = Object.entries(stepResults).flatMap(([stepId, stepResult]) => {
749
- if (stepResult?.status === "suspended") {
750
- const nestedPath = stepResult?.suspendPayload?.__workflow_meta?.path;
751
- return nestedPath ? [[stepId, ...nestedPath]] : [[stepId]];
752
- }
753
- return [];
754
- });
755
- base.suspended = suspendedStepIds;
756
- }
757
- return base;
758
- }
759
- // async executeSleep({ id, duration }: { id: string; duration: number }): Promise<void> {
760
- // await this.inngestStep.sleep(id, duration);
761
- // }
762
- async executeSleep({
763
- workflowId,
764
- runId,
765
- entry,
766
- prevOutput,
767
- stepResults,
768
- emitter,
769
- abortController,
770
- requestContext,
771
- executionContext,
772
- writableStream,
773
- tracingContext
774
- }) {
775
- let { duration, fn } = entry;
776
- const sleepSpan = tracingContext?.currentSpan?.createChildSpan({
777
- type: observability.SpanType.WORKFLOW_SLEEP,
778
- name: `sleep: ${duration ? `${duration}ms` : "dynamic"}`,
779
- attributes: {
780
- durationMs: duration,
781
- sleepType: fn ? "dynamic" : "fixed"
782
- },
783
- tracingPolicy: this.options?.tracingPolicy
784
- });
785
- if (fn) {
786
- const stepCallId = crypto.randomUUID();
787
- duration = await this.inngestStep.run(`workflow.${workflowId}.sleep.${entry.id}`, async () => {
788
- return await fn(
789
- workflows.createDeprecationProxy(
790
- {
791
- runId,
792
- workflowId,
793
- mastra: this.mastra,
794
- requestContext,
795
- inputData: prevOutput,
796
- state: executionContext.state,
797
- setState: (state) => {
798
- executionContext.state = state;
799
- },
800
- retryCount: -1,
801
- tracingContext: {
802
- currentSpan: sleepSpan
803
- },
804
- getInitData: () => stepResults?.input,
805
- getStepResult: workflows.getStepResult.bind(this, stepResults),
806
- // TODO: this function shouldn't have suspend probably?
807
- suspend: async (_suspendPayload) => {
808
- },
809
- bail: () => {
810
- },
811
- abort: () => {
812
- abortController?.abort();
813
- },
814
- [_constants.EMITTER_SYMBOL]: emitter,
815
- [_constants.STREAM_FORMAT_SYMBOL]: executionContext.format,
816
- engine: { step: this.inngestStep },
817
- abortSignal: abortController?.signal,
818
- writer: new tools.ToolStream(
819
- {
820
- prefix: "workflow-step",
821
- callId: stepCallId,
822
- name: "sleep",
823
- runId
824
- },
825
- writableStream
826
- )
827
- },
828
- {
829
- paramName: "runCount",
830
- deprecationMessage: workflows.runCountDeprecationMessage,
831
- logger: this.logger
832
- }
833
- )
834
- );
835
- });
836
- sleepSpan?.update({
837
- attributes: {
838
- durationMs: duration
839
- }
840
- });
841
- }
842
- try {
843
- await this.inngestStep.sleep(entry.id, !duration || duration < 0 ? 0 : duration);
844
- sleepSpan?.end();
845
- } catch (e) {
846
- sleepSpan?.error({ error: e });
847
- throw e;
848
- }
849
- }
850
- async executeSleepUntil({
851
- workflowId,
852
- runId,
853
- entry,
854
- prevOutput,
855
- stepResults,
856
- emitter,
857
- abortController,
858
- requestContext,
859
- executionContext,
860
- writableStream,
861
- tracingContext
862
- }) {
863
- let { date, fn } = entry;
864
- const sleepUntilSpan = tracingContext?.currentSpan?.createChildSpan({
865
- type: observability.SpanType.WORKFLOW_SLEEP,
866
- name: `sleepUntil: ${date ? date.toISOString() : "dynamic"}`,
867
- attributes: {
868
- untilDate: date,
869
- durationMs: date ? Math.max(0, date.getTime() - Date.now()) : void 0,
870
- sleepType: fn ? "dynamic" : "fixed"
871
- },
872
- tracingPolicy: this.options?.tracingPolicy
873
- });
874
- if (fn) {
875
- date = await this.inngestStep.run(`workflow.${workflowId}.sleepUntil.${entry.id}`, async () => {
876
- const stepCallId = crypto.randomUUID();
877
- return await fn(
878
- workflows.createDeprecationProxy(
879
- {
880
- runId,
881
- workflowId,
882
- mastra: this.mastra,
883
- requestContext,
884
- inputData: prevOutput,
885
- state: executionContext.state,
886
- setState: (state) => {
887
- executionContext.state = state;
888
- },
889
- retryCount: -1,
890
- tracingContext: {
891
- currentSpan: sleepUntilSpan
892
- },
893
- getInitData: () => stepResults?.input,
894
- getStepResult: workflows.getStepResult.bind(this, stepResults),
895
- // TODO: this function shouldn't have suspend probably?
896
- suspend: async (_suspendPayload) => {
897
- },
898
- bail: () => {
899
- },
900
- abort: () => {
901
- abortController?.abort();
902
- },
903
- [_constants.EMITTER_SYMBOL]: emitter,
904
- [_constants.STREAM_FORMAT_SYMBOL]: executionContext.format,
905
- engine: { step: this.inngestStep },
906
- abortSignal: abortController?.signal,
907
- writer: new tools.ToolStream(
908
- {
909
- prefix: "workflow-step",
910
- callId: stepCallId,
911
- name: "sleep",
912
- runId
913
- },
914
- writableStream
915
- )
916
- },
917
- {
918
- paramName: "runCount",
919
- deprecationMessage: workflows.runCountDeprecationMessage,
920
- logger: this.logger
921
- }
922
- )
923
- );
924
- });
925
- if (date && !(date instanceof Date)) {
926
- date = new Date(date);
927
- }
928
- const time = !date ? 0 : date.getTime() - Date.now();
929
- sleepUntilSpan?.update({
930
- attributes: {
931
- durationMs: Math.max(0, time)
932
- }
933
- });
934
- }
935
- if (!(date instanceof Date)) {
936
- sleepUntilSpan?.end();
937
- return;
938
- }
939
- try {
940
- await this.inngestStep.sleepUntil(entry.id, date);
941
- sleepUntilSpan?.end();
942
- } catch (e) {
943
- sleepUntilSpan?.error({ error: e });
944
- throw e;
945
- }
946
- }
947
- async executeStep({
948
- step,
949
- stepResults,
950
- executionContext,
951
- resume,
952
- prevOutput,
953
- emitter,
954
- abortController,
955
- requestContext,
956
- tracingContext,
957
- writableStream,
958
- disableScorers
959
- }) {
960
- const stepSpan = tracingContext?.currentSpan?.createChildSpan({
961
- name: `workflow step: '${step.id}'`,
962
- type: observability.SpanType.WORKFLOW_STEP,
963
- input: prevOutput,
964
- attributes: {
965
- stepId: step.id
966
- },
967
- tracingPolicy: this.options?.tracingPolicy
968
- });
969
- const { inputData, validationError } = await workflows.validateStepInput({
970
- prevOutput,
971
- step,
972
- validateInputs: this.options?.validateInputs ?? false
973
- });
974
- const startedAt = await this.inngestStep.run(
975
- `workflow.${executionContext.workflowId}.run.${executionContext.runId}.step.${step.id}.running_ev`,
976
- async () => {
977
- const startedAt2 = Date.now();
978
- await emitter.emit("watch", {
979
- type: "workflow-step-start",
980
- payload: {
981
- id: step.id,
982
- status: "running",
983
- payload: inputData,
984
- startedAt: startedAt2
985
- }
986
- });
987
- return startedAt2;
988
- }
989
- );
990
- if (step instanceof InngestWorkflow) {
991
- const isResume = !!resume?.steps?.length;
992
- let result;
993
- let runId;
994
- try {
995
- if (isResume) {
996
- runId = stepResults[resume?.steps?.[0]]?.suspendPayload?.__workflow_meta?.runId ?? crypto.randomUUID();
997
- const snapshot = await this.mastra?.getStorage()?.loadWorkflowSnapshot({
998
- workflowName: step.id,
999
- runId
1000
- });
1001
- const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
1002
- function: step.getFunction(),
1003
- data: {
1004
- inputData,
1005
- initialState: executionContext.state ?? snapshot?.value ?? {},
1006
- runId,
1007
- resume: {
1008
- runId,
1009
- steps: resume.steps.slice(1),
1010
- stepResults: snapshot?.context,
1011
- resumePayload: resume.resumePayload,
1012
- // @ts-ignore
1013
- resumePath: snapshot?.suspendedPaths?.[resume.steps?.[1]]
1014
- },
1015
- outputOptions: { includeState: true }
1016
- }
1017
- });
1018
- result = invokeResp.result;
1019
- runId = invokeResp.runId;
1020
- executionContext.state = invokeResp.result.state;
1021
- } else {
1022
- const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
1023
- function: step.getFunction(),
1024
- data: {
1025
- inputData,
1026
- initialState: executionContext.state ?? {},
1027
- outputOptions: { includeState: true }
1028
- }
1029
- });
1030
- result = invokeResp.result;
1031
- runId = invokeResp.runId;
1032
- executionContext.state = invokeResp.result.state;
1033
- }
1034
- } catch (e) {
1035
- const errorCause = e?.cause;
1036
- if (errorCause && typeof errorCause === "object") {
1037
- result = errorCause;
1038
- runId = errorCause.runId || crypto.randomUUID();
1039
- } else {
1040
- runId = crypto.randomUUID();
1041
- result = {
1042
- status: "failed",
1043
- error: e instanceof Error ? e : new Error(String(e)),
1044
- steps: {},
1045
- input: inputData
1046
- };
1047
- }
1048
- }
1049
- const res = await this.inngestStep.run(
1050
- `workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
1051
- async () => {
1052
- if (result.status === "failed") {
1053
- await emitter.emit("watch", {
1054
- type: "workflow-step-result",
1055
- payload: {
1056
- id: step.id,
1057
- status: "failed",
1058
- error: result?.error,
1059
- payload: prevOutput
1060
- }
1061
- });
1062
- return { executionContext, result: { status: "failed", error: result?.error } };
1063
- } else if (result.status === "suspended") {
1064
- const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
1065
- const stepRes2 = stepResult;
1066
- return stepRes2?.status === "suspended";
1067
- });
1068
- for (const [stepName, stepResult] of suspendedSteps) {
1069
- const suspendPath = [stepName, ...stepResult?.suspendPayload?.__workflow_meta?.path ?? []];
1070
- executionContext.suspendedPaths[step.id] = executionContext.executionPath;
1071
- await emitter.emit("watch", {
1072
- type: "workflow-step-suspended",
1073
- payload: {
1074
- id: step.id,
1075
- status: "suspended"
1076
- }
1077
- });
1078
- return {
1079
- executionContext,
1080
- result: {
1081
- status: "suspended",
1082
- payload: stepResult.payload,
1083
- suspendPayload: {
1084
- ...stepResult?.suspendPayload,
1085
- __workflow_meta: { runId, path: suspendPath }
1086
- }
1087
- }
1088
- };
1089
- }
1090
- return {
1091
- executionContext,
1092
- result: {
1093
- status: "suspended",
1094
- payload: {}
1095
- }
1096
- };
1097
- }
1098
- await emitter.emit("watch", {
1099
- type: "workflow-step-result",
1100
- payload: {
1101
- id: step.id,
1102
- status: "success",
1103
- output: result?.result
1104
- }
1105
- });
1106
- await emitter.emit("watch", {
1107
- type: "workflow-step-finish",
1108
- payload: {
1109
- id: step.id,
1110
- metadata: {}
1111
- }
1112
- });
1113
- return { executionContext, result: { status: "success", output: result?.result } };
1114
- }
1115
- );
1116
- Object.assign(executionContext, res.executionContext);
1117
- return {
1118
- ...res.result,
1119
- startedAt,
1120
- endedAt: Date.now(),
1121
- payload: inputData,
1122
- resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1123
- resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1124
- };
1125
- }
1126
- const stepCallId = crypto.randomUUID();
1127
- let stepRes;
1128
- try {
1129
- stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
1130
- let execResults;
1131
- let suspended;
1132
- let bailed;
1133
- try {
1134
- if (validationError) {
1135
- throw validationError;
1136
- }
1137
- const result = await step.execute({
1138
- runId: executionContext.runId,
1139
- mastra: this.mastra,
1140
- requestContext,
1141
- writer: new tools.ToolStream(
1142
- {
1143
- prefix: "workflow-step",
1144
- callId: stepCallId,
1145
- name: step.id,
1146
- runId: executionContext.runId
1147
- },
1148
- writableStream
1149
- ),
1150
- state: executionContext?.state ?? {},
1151
- setState: (state) => {
1152
- executionContext.state = state;
1153
- },
1154
- inputData,
1155
- resumeData: resume?.steps[0] === step.id ? resume?.resumePayload : void 0,
1156
- tracingContext: {
1157
- currentSpan: stepSpan
1158
- },
1159
- getInitData: () => stepResults?.input,
1160
- getStepResult: workflows.getStepResult.bind(this, stepResults),
1161
- suspend: async (suspendPayload, suspendOptions) => {
1162
- executionContext.suspendedPaths[step.id] = executionContext.executionPath;
1163
- if (suspendOptions?.resumeLabel) {
1164
- const resumeLabel = Array.isArray(suspendOptions.resumeLabel) ? suspendOptions.resumeLabel : [suspendOptions.resumeLabel];
1165
- for (const label of resumeLabel) {
1166
- executionContext.resumeLabels[label] = {
1167
- stepId: step.id,
1168
- foreachIndex: executionContext.foreachIndex
1169
- };
1170
- }
1171
- }
1172
- suspended = { payload: suspendPayload };
1173
- },
1174
- bail: (result2) => {
1175
- bailed = { payload: result2 };
1176
- },
1177
- resume: {
1178
- steps: resume?.steps?.slice(1) || [],
1179
- resumePayload: resume?.resumePayload,
1180
- // @ts-ignore
1181
- runId: stepResults[step.id]?.suspendPayload?.__workflow_meta?.runId
1182
- },
1183
- [_constants.EMITTER_SYMBOL]: emitter,
1184
- [_constants.STREAM_FORMAT_SYMBOL]: executionContext.format,
1185
- engine: {
1186
- step: this.inngestStep
1187
- },
1188
- abortSignal: abortController.signal
1189
- });
1190
- const endedAt = Date.now();
1191
- execResults = {
1192
- status: "success",
1193
- output: result,
1194
- startedAt,
1195
- endedAt,
1196
- payload: inputData,
1197
- resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1198
- resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1199
- };
1200
- } catch (e) {
1201
- const stepFailure = {
1202
- status: "failed",
1203
- payload: inputData,
1204
- error: e instanceof Error ? e.message : String(e),
1205
- endedAt: Date.now(),
1206
- startedAt,
1207
- resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1208
- resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1209
- };
1210
- execResults = stepFailure;
1211
- const fallbackErrorMessage = `Step ${step.id} failed`;
1212
- stepSpan?.error({ error: new Error(execResults.error ?? fallbackErrorMessage) });
1213
- throw new inngest.RetryAfterError(execResults.error ?? fallbackErrorMessage, executionContext.retryConfig.delay, {
1214
- cause: execResults
1215
- });
1216
- }
1217
- if (suspended) {
1218
- execResults = {
1219
- status: "suspended",
1220
- suspendPayload: suspended.payload,
1221
- ...execResults.output ? { suspendOutput: execResults.output } : {},
1222
- payload: inputData,
1223
- suspendedAt: Date.now(),
1224
- startedAt,
1225
- resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1226
- resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1227
- };
1228
- } else if (bailed) {
1229
- execResults = {
1230
- status: "bailed",
1231
- output: bailed.payload,
1232
- payload: inputData,
1233
- endedAt: Date.now(),
1234
- startedAt
1235
- };
1236
- }
1237
- if (execResults.status === "suspended") {
1238
- await emitter.emit("watch", {
1239
- type: "workflow-step-suspended",
1240
- payload: {
1241
- id: step.id,
1242
- ...execResults
1243
- }
1244
- });
1245
- } else {
1246
- await emitter.emit("watch", {
1247
- type: "workflow-step-result",
1248
- payload: {
1249
- id: step.id,
1250
- ...execResults
1251
- }
1252
- });
1253
- await emitter.emit("watch", {
1254
- type: "workflow-step-finish",
1255
- payload: {
1256
- id: step.id,
1257
- metadata: {}
1258
- }
1259
- });
1260
- }
1261
- stepSpan?.end({ output: execResults });
1262
- return { result: execResults, executionContext, stepResults };
1263
- });
1264
- } catch (e) {
1265
- const stepFailure = e instanceof Error ? e?.cause : {
1266
- status: "failed",
1267
- error: e instanceof Error ? e.message : String(e),
1268
- payload: inputData,
1269
- startedAt,
1270
- endedAt: Date.now()
1271
- };
1272
- stepRes = {
1273
- result: stepFailure,
1274
- executionContext,
1275
- stepResults: {
1276
- ...stepResults,
1277
- [step.id]: stepFailure
1278
- }
1279
- };
1280
- }
1281
- if (disableScorers !== false && stepRes.result.status === "success") {
1282
- await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}.score`, async () => {
1283
- if (step.scorers) {
1284
- await this.runScorers({
1285
- scorers: step.scorers,
1286
- runId: executionContext.runId,
1287
- input: inputData,
1288
- output: stepRes.result,
1289
- workflowId: executionContext.workflowId,
1290
- stepId: step.id,
1291
- requestContext,
1292
- disableScorers,
1293
- tracingContext: { currentSpan: stepSpan }
1294
- });
1295
- }
1296
- });
1297
- }
1298
- Object.assign(executionContext.suspendedPaths, stepRes.executionContext.suspendedPaths);
1299
- Object.assign(stepResults, stepRes.stepResults);
1300
- executionContext.state = stepRes.executionContext.state;
1301
- return stepRes.result;
1302
- }
1303
- async persistStepUpdate({
1304
- workflowId,
1305
- runId,
1306
- stepResults,
1307
- resourceId,
1308
- executionContext,
1309
- serializedStepGraph,
1310
- workflowStatus,
1311
- result,
1312
- error
1313
- }) {
1314
- await this.inngestStep.run(
1315
- `workflow.${workflowId}.run.${runId}.path.${JSON.stringify(executionContext.executionPath)}.stepUpdate`,
1316
- async () => {
1317
- const shouldPersistSnapshot = this.options.shouldPersistSnapshot({ stepResults, workflowStatus });
1318
- if (!shouldPersistSnapshot) {
1319
- return;
1320
- }
1321
- await this.mastra?.getStorage()?.persistWorkflowSnapshot({
1322
- workflowName: workflowId,
1323
- runId,
1324
- resourceId,
1325
- snapshot: {
1326
- runId,
1327
- value: executionContext.state,
1328
- context: stepResults,
1329
- activePaths: [],
1330
- suspendedPaths: executionContext.suspendedPaths,
1331
- resumeLabels: executionContext.resumeLabels,
1332
- waitingPaths: {},
1333
- serializedStepGraph,
1334
- status: workflowStatus,
1335
- result,
1336
- error,
1337
- // @ts-ignore
1338
- timestamp: Date.now()
1339
- }
1340
- });
1341
- }
1342
- );
1343
- }
1344
- async executeConditional({
1345
- workflowId,
1346
- runId,
1347
- entry,
1348
- prevOutput,
1349
- stepResults,
1350
- resume,
1351
- executionContext,
1352
- emitter,
1353
- abortController,
1354
- requestContext,
1355
- writableStream,
1356
- disableScorers,
1357
- tracingContext
1358
- }) {
1359
- const conditionalSpan = tracingContext?.currentSpan?.createChildSpan({
1360
- type: observability.SpanType.WORKFLOW_CONDITIONAL,
1361
- name: `conditional: '${entry.conditions.length} conditions'`,
1362
- input: prevOutput,
1363
- attributes: {
1364
- conditionCount: entry.conditions.length
1365
- },
1366
- tracingPolicy: this.options?.tracingPolicy
1367
- });
1368
- let execResults;
1369
- const truthyIndexes = (await Promise.all(
1370
- entry.conditions.map(
1371
- (cond, index) => this.inngestStep.run(`workflow.${workflowId}.conditional.${index}`, async () => {
1372
- const evalSpan = conditionalSpan?.createChildSpan({
1373
- type: observability.SpanType.WORKFLOW_CONDITIONAL_EVAL,
1374
- name: `condition: '${index}'`,
1375
- input: prevOutput,
1376
- attributes: {
1377
- conditionIndex: index
1378
- },
1379
- tracingPolicy: this.options?.tracingPolicy
1380
- });
1381
- try {
1382
- const result = await cond(
1383
- workflows.createDeprecationProxy(
1384
- {
1385
- runId,
1386
- workflowId,
1387
- mastra: this.mastra,
1388
- requestContext,
1389
- retryCount: -1,
1390
- inputData: prevOutput,
1391
- state: executionContext.state,
1392
- setState: (state) => {
1393
- executionContext.state = state;
1394
- },
1395
- tracingContext: {
1396
- currentSpan: evalSpan
1397
- },
1398
- getInitData: () => stepResults?.input,
1399
- getStepResult: workflows.getStepResult.bind(this, stepResults),
1400
- // TODO: this function shouldn't have suspend probably?
1401
- suspend: async (_suspendPayload) => {
1402
- },
1403
- bail: () => {
1404
- },
1405
- abort: () => {
1406
- abortController.abort();
1407
- },
1408
- [_constants.EMITTER_SYMBOL]: emitter,
1409
- [_constants.STREAM_FORMAT_SYMBOL]: executionContext.format,
1410
- engine: {
1411
- step: this.inngestStep
1412
- },
1413
- abortSignal: abortController.signal,
1414
- writer: new tools.ToolStream(
1415
- {
1416
- prefix: "workflow-step",
1417
- callId: crypto.randomUUID(),
1418
- name: "conditional",
1419
- runId
1420
- },
1421
- writableStream
1422
- )
1423
- },
1424
- {
1425
- paramName: "runCount",
1426
- deprecationMessage: workflows.runCountDeprecationMessage,
1427
- logger: this.logger
1428
- }
1429
- )
1430
- );
1431
- evalSpan?.end({
1432
- output: result,
1433
- attributes: {
1434
- result: !!result
1435
- }
1436
- });
1437
- return result ? index : null;
1438
- } catch (e) {
1439
- evalSpan?.error({
1440
- error: e instanceof Error ? e : new Error(String(e)),
1441
- attributes: {
1442
- result: false
1443
- }
1444
- });
1445
- return null;
1446
- }
1447
- })
1448
- )
1449
- )).filter((index) => index !== null);
1450
- const stepsToRun = entry.steps.filter((_, index) => truthyIndexes.includes(index));
1451
- conditionalSpan?.update({
1452
- attributes: {
1453
- truthyIndexes,
1454
- selectedSteps: stepsToRun.map((s) => s.type === "step" ? s.step.id : `control-${s.type}`)
1455
- }
1456
- });
1457
- const results = await Promise.all(
1458
- stepsToRun.map(async (step, index) => {
1459
- const currStepResult = stepResults[step.step.id];
1460
- if (currStepResult && currStepResult.status === "success") {
1461
- return currStepResult;
1462
- }
1463
- const result = await this.executeStep({
1464
- step: step.step,
1465
- prevOutput,
1466
- stepResults,
1467
- resume,
1468
- executionContext: {
1469
- workflowId,
1470
- runId,
1471
- executionPath: [...executionContext.executionPath, index],
1472
- suspendedPaths: executionContext.suspendedPaths,
1473
- resumeLabels: executionContext.resumeLabels,
1474
- retryConfig: executionContext.retryConfig,
1475
- state: executionContext.state
1476
- },
1477
- emitter,
1478
- abortController,
1479
- requestContext,
1480
- writableStream,
1481
- disableScorers,
1482
- tracingContext: {
1483
- currentSpan: conditionalSpan
1484
- }
1485
- });
1486
- stepResults[step.step.id] = result;
1487
- return result;
1488
- })
1489
- );
1490
- const hasFailed = results.find((result) => result.status === "failed");
1491
- const hasSuspended = results.find((result) => result.status === "suspended");
1492
- if (hasFailed) {
1493
- execResults = { status: "failed", error: hasFailed.error };
1494
- } else if (hasSuspended) {
1495
- execResults = {
1496
- status: "suspended",
1497
- suspendPayload: hasSuspended.suspendPayload,
1498
- ...hasSuspended.suspendOutput ? { suspendOutput: hasSuspended.suspendOutput } : {}
1499
- };
1500
- } else {
1501
- execResults = {
1502
- status: "success",
1503
- output: results.reduce((acc, result, index) => {
1504
- if (result.status === "success") {
1505
- acc[stepsToRun[index].step.id] = result.output;
1506
- }
1507
- return acc;
1508
- }, {})
1509
- };
1510
- }
1511
- if (execResults.status === "failed") {
1512
- conditionalSpan?.error({
1513
- error: new Error(execResults.error)
1514
- });
1515
- } else {
1516
- conditionalSpan?.end({
1517
- output: execResults.output || execResults
1518
- });
1519
- }
1520
- return execResults;
1521
- }
1522
- };
1523
1581
 
1524
1582
  exports.InngestExecutionEngine = InngestExecutionEngine;
1583
+ exports.InngestPubSub = InngestPubSub;
1525
1584
  exports.InngestRun = InngestRun;
1526
1585
  exports.InngestWorkflow = InngestWorkflow;
1586
+ exports._compatibilityCheck = _compatibilityCheck;
1527
1587
  exports.createStep = createStep;
1528
1588
  exports.init = init;
1529
1589
  exports.serve = serve;