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

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,517 @@
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');
9
3
  var tools = require('@mastra/core/tools');
10
4
  var workflows = require('@mastra/core/workflows');
11
5
  var _constants = require('@mastra/core/workflows/_constants');
6
+ var zod = require('zod');
7
+ var crypto$1 = require('crypto');
8
+ var di = require('@mastra/core/di');
12
9
  var inngest = require('inngest');
10
+ var error = require('@mastra/core/error');
11
+ var realtime = require('@inngest/realtime');
12
+ var events = require('@mastra/core/events');
13
+ var web = require('stream/web');
14
+ var stream = require('@mastra/core/stream');
13
15
  var hono = require('inngest/hono');
14
- var zod = require('zod');
15
16
 
16
17
  // 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();
18
+ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
19
+ inngestStep;
20
+ inngestAttempts;
21
+ constructor(mastra, inngestStep, inngestAttempts = 0, options) {
22
+ super({ mastra, options });
23
+ this.inngestStep = inngestStep;
24
+ this.inngestAttempts = inngestAttempts;
25
+ }
26
+ // =============================================================================
27
+ // Hook Overrides
28
+ // =============================================================================
29
+ /**
30
+ * Format errors while preserving Error instances and their custom properties.
31
+ * Uses getErrorFromUnknown to ensure all error properties are preserved.
32
+ */
33
+ formatResultError(error$1, lastOutput) {
34
+ const outputError = lastOutput?.error;
35
+ const errorSource = error$1 || outputError;
36
+ const errorInstance = error.getErrorFromUnknown(errorSource, {
37
+ serializeStack: true,
38
+ // Include stack in JSON for better debugging in Inngest
39
+ fallbackMessage: "Unknown workflow error"
40
+ });
41
+ return errorInstance.toJSON();
42
+ }
43
+ /**
44
+ * Detect InngestWorkflow instances for special nested workflow handling
45
+ */
46
+ isNestedWorkflowStep(step) {
47
+ return step instanceof InngestWorkflow;
48
+ }
49
+ /**
50
+ * Inngest requires requestContext serialization for memoization.
51
+ * When steps are replayed, the original function doesn't re-execute,
52
+ * so requestContext modifications must be captured and restored.
53
+ */
54
+ requiresDurableContextSerialization() {
55
+ return true;
56
+ }
57
+ /**
58
+ * Execute a step with retry logic for Inngest.
59
+ * Retries are handled via step-level retry (RetryAfterError thrown INSIDE step.run()).
60
+ * After retries exhausted, error propagates here and we return a failed result.
61
+ */
62
+ async executeStepWithRetry(stepId, runStep, params) {
63
+ try {
64
+ const result = await this.wrapDurableOperation(stepId, runStep, { delay: params.delay });
65
+ return { ok: true, result };
66
+ } catch (e) {
67
+ const cause = e?.cause;
68
+ if (cause?.status === "failed") {
69
+ params.stepSpan?.error({
70
+ error: e,
71
+ attributes: { status: "failed" }
72
+ });
73
+ if (cause.error && !(cause.error instanceof Error)) {
74
+ cause.error = error.getErrorFromUnknown(cause.error, { serializeStack: false });
30
75
  }
31
- return [];
32
- })
33
- )
34
- );
35
- return hono.serve({
36
- ...registerOptions,
37
- client: inngest,
38
- functions: [...workflowFunctions, ...userFunctions]
39
- });
40
- }
76
+ return { ok: false, error: cause };
77
+ }
78
+ const errorInstance = error.getErrorFromUnknown(e, {
79
+ serializeStack: false,
80
+ fallbackMessage: "Unknown step execution error"
81
+ });
82
+ params.stepSpan?.error({
83
+ error: errorInstance,
84
+ attributes: { status: "failed" }
85
+ });
86
+ return {
87
+ ok: false,
88
+ error: {
89
+ status: "failed",
90
+ error: errorInstance,
91
+ endedAt: Date.now()
92
+ }
93
+ };
94
+ }
95
+ }
96
+ /**
97
+ * Use Inngest's sleep primitive for durability
98
+ */
99
+ async executeSleepDuration(duration, sleepId, workflowId) {
100
+ await this.inngestStep.sleep(`workflow.${workflowId}.sleep.${sleepId}`, duration < 0 ? 0 : duration);
101
+ }
102
+ /**
103
+ * Use Inngest's sleepUntil primitive for durability
104
+ */
105
+ async executeSleepUntilDate(date, sleepUntilId, workflowId) {
106
+ await this.inngestStep.sleepUntil(`workflow.${workflowId}.sleepUntil.${sleepUntilId}`, date);
107
+ }
108
+ /**
109
+ * Wrap durable operations in Inngest step.run() for durability.
110
+ * If retryConfig is provided, throws RetryAfterError INSIDE step.run() to trigger
111
+ * Inngest's step-level retry mechanism (not function-level retry).
112
+ */
113
+ async wrapDurableOperation(operationId, operationFn, retryConfig) {
114
+ return this.inngestStep.run(operationId, async () => {
115
+ try {
116
+ return await operationFn();
117
+ } catch (e) {
118
+ if (retryConfig) {
119
+ const errorInstance = error.getErrorFromUnknown(e, {
120
+ serializeStack: false,
121
+ fallbackMessage: "Unknown step execution error"
122
+ });
123
+ throw new inngest.RetryAfterError(errorInstance.message, retryConfig.delay, {
124
+ cause: {
125
+ status: "failed",
126
+ error: errorInstance,
127
+ endedAt: Date.now()
128
+ }
129
+ });
130
+ }
131
+ throw e;
132
+ }
133
+ });
134
+ }
135
+ /**
136
+ * Provide Inngest step primitive in engine context
137
+ */
138
+ getEngineContext() {
139
+ return { step: this.inngestStep };
140
+ }
141
+ /**
142
+ * For Inngest, lifecycle callbacks are invoked in the workflow's finalize step
143
+ * (wrapped in step.run for durability), not in execute(). Override to skip.
144
+ */
145
+ async invokeLifecycleCallbacks(_result) {
146
+ }
147
+ /**
148
+ * Actually invoke the lifecycle callbacks. Called from workflow.ts finalize step.
149
+ */
150
+ async invokeLifecycleCallbacksInternal(result) {
151
+ return super.invokeLifecycleCallbacks(result);
152
+ }
153
+ /**
154
+ * Execute nested InngestWorkflow using inngestStep.invoke() for durability.
155
+ * This MUST be called directly (not inside step.run()) due to Inngest constraints.
156
+ */
157
+ async executeWorkflowStep(params) {
158
+ if (!(params.step instanceof InngestWorkflow)) {
159
+ return null;
160
+ }
161
+ const {
162
+ step,
163
+ stepResults,
164
+ executionContext,
165
+ resume,
166
+ timeTravel,
167
+ prevOutput,
168
+ inputData,
169
+ pubsub,
170
+ startedAt,
171
+ perStep
172
+ } = params;
173
+ const isResume = !!resume?.steps?.length;
174
+ let result;
175
+ let runId;
176
+ const isTimeTravel = !!(timeTravel && timeTravel.steps?.length > 1 && timeTravel.steps[0] === step.id);
177
+ try {
178
+ if (isResume) {
179
+ runId = stepResults[resume?.steps?.[0] ?? ""]?.suspendPayload?.__workflow_meta?.runId ?? crypto$1.randomUUID();
180
+ const workflowsStore = await this.mastra?.getStorage()?.getStore("workflows");
181
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
182
+ workflowName: step.id,
183
+ runId
184
+ });
185
+ const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
186
+ function: step.getFunction(),
187
+ data: {
188
+ inputData,
189
+ initialState: executionContext.state ?? snapshot?.value ?? {},
190
+ runId,
191
+ resume: {
192
+ runId,
193
+ steps: resume.steps.slice(1),
194
+ stepResults: snapshot?.context,
195
+ resumePayload: resume.resumePayload,
196
+ resumePath: resume.steps?.[1] ? snapshot?.suspendedPaths?.[resume.steps?.[1]] : void 0
197
+ },
198
+ outputOptions: { includeState: true },
199
+ perStep
200
+ }
201
+ });
202
+ result = invokeResp.result;
203
+ runId = invokeResp.runId;
204
+ executionContext.state = invokeResp.result.state;
205
+ } else if (isTimeTravel) {
206
+ const workflowsStoreForTimeTravel = await this.mastra?.getStorage()?.getStore("workflows");
207
+ const snapshot = await workflowsStoreForTimeTravel?.loadWorkflowSnapshot({
208
+ workflowName: step.id,
209
+ runId: executionContext.runId
210
+ }) ?? { context: {} };
211
+ const timeTravelParams = workflows.createTimeTravelExecutionParams({
212
+ steps: timeTravel.steps.slice(1),
213
+ inputData: timeTravel.inputData,
214
+ resumeData: timeTravel.resumeData,
215
+ context: timeTravel.nestedStepResults?.[step.id] ?? {},
216
+ nestedStepsContext: timeTravel.nestedStepResults ?? {},
217
+ snapshot,
218
+ graph: step.buildExecutionGraph()
219
+ });
220
+ const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
221
+ function: step.getFunction(),
222
+ data: {
223
+ timeTravel: timeTravelParams,
224
+ initialState: executionContext.state ?? {},
225
+ runId: executionContext.runId,
226
+ outputOptions: { includeState: true },
227
+ perStep
228
+ }
229
+ });
230
+ result = invokeResp.result;
231
+ runId = invokeResp.runId;
232
+ executionContext.state = invokeResp.result.state;
233
+ } else {
234
+ const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
235
+ function: step.getFunction(),
236
+ data: {
237
+ inputData,
238
+ initialState: executionContext.state ?? {},
239
+ outputOptions: { includeState: true },
240
+ perStep
241
+ }
242
+ });
243
+ result = invokeResp.result;
244
+ runId = invokeResp.runId;
245
+ executionContext.state = invokeResp.result.state;
246
+ }
247
+ } catch (e) {
248
+ const errorCause = e?.cause;
249
+ if (errorCause && typeof errorCause === "object") {
250
+ result = errorCause;
251
+ runId = errorCause.runId || crypto$1.randomUUID();
252
+ } else {
253
+ runId = crypto$1.randomUUID();
254
+ result = {
255
+ status: "failed",
256
+ error: e instanceof Error ? e : new Error(String(e)),
257
+ steps: {},
258
+ input: inputData
259
+ };
260
+ }
261
+ }
262
+ const res = await this.inngestStep.run(
263
+ `workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
264
+ async () => {
265
+ if (result.status === "failed") {
266
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
267
+ type: "watch",
268
+ runId: executionContext.runId,
269
+ data: {
270
+ type: "workflow-step-result",
271
+ payload: {
272
+ id: step.id,
273
+ status: "failed",
274
+ error: result?.error,
275
+ payload: prevOutput
276
+ }
277
+ }
278
+ });
279
+ return { executionContext, result: { status: "failed", error: result?.error, endedAt: Date.now() } };
280
+ } else if (result.status === "suspended") {
281
+ const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
282
+ const stepRes = stepResult;
283
+ return stepRes?.status === "suspended";
284
+ });
285
+ for (const [stepName, stepResult] of suspendedSteps) {
286
+ const suspendPath = [stepName, ...stepResult?.suspendPayload?.__workflow_meta?.path ?? []];
287
+ executionContext.suspendedPaths[step.id] = executionContext.executionPath;
288
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
289
+ type: "watch",
290
+ runId: executionContext.runId,
291
+ data: {
292
+ type: "workflow-step-suspended",
293
+ payload: {
294
+ id: step.id,
295
+ status: "suspended"
296
+ }
297
+ }
298
+ });
299
+ return {
300
+ executionContext,
301
+ result: {
302
+ status: "suspended",
303
+ suspendedAt: Date.now(),
304
+ payload: stepResult.payload,
305
+ suspendPayload: {
306
+ ...stepResult?.suspendPayload,
307
+ __workflow_meta: { runId, path: suspendPath }
308
+ }
309
+ }
310
+ };
311
+ }
312
+ return {
313
+ executionContext,
314
+ result: {
315
+ status: "suspended",
316
+ suspendedAt: Date.now(),
317
+ payload: {}
318
+ }
319
+ };
320
+ } else if (result.status === "tripwire") {
321
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
322
+ type: "watch",
323
+ runId: executionContext.runId,
324
+ data: {
325
+ type: "workflow-step-result",
326
+ payload: {
327
+ id: step.id,
328
+ status: "tripwire",
329
+ error: result?.tripwire?.reason,
330
+ payload: prevOutput
331
+ }
332
+ }
333
+ });
334
+ return {
335
+ executionContext,
336
+ result: {
337
+ status: "tripwire",
338
+ tripwire: result?.tripwire,
339
+ endedAt: Date.now()
340
+ }
341
+ };
342
+ } else if (perStep || result.status === "paused") {
343
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
344
+ type: "watch",
345
+ runId: executionContext.runId,
346
+ data: {
347
+ type: "workflow-step-result",
348
+ payload: {
349
+ id: step.id,
350
+ status: "paused"
351
+ }
352
+ }
353
+ });
354
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
355
+ type: "watch",
356
+ runId: executionContext.runId,
357
+ data: {
358
+ type: "workflow-step-finish",
359
+ payload: {
360
+ id: step.id,
361
+ metadata: {}
362
+ }
363
+ }
364
+ });
365
+ return { executionContext, result: { status: "paused" } };
366
+ }
367
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
368
+ type: "watch",
369
+ runId: executionContext.runId,
370
+ data: {
371
+ type: "workflow-step-result",
372
+ payload: {
373
+ id: step.id,
374
+ status: "success",
375
+ output: result?.result
376
+ }
377
+ }
378
+ });
379
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
380
+ type: "watch",
381
+ runId: executionContext.runId,
382
+ data: {
383
+ type: "workflow-step-finish",
384
+ payload: {
385
+ id: step.id,
386
+ metadata: {}
387
+ }
388
+ }
389
+ });
390
+ return { executionContext, result: { status: "success", output: result?.result, endedAt: Date.now() } };
391
+ }
392
+ );
393
+ Object.assign(executionContext, res.executionContext);
394
+ return {
395
+ ...res.result,
396
+ startedAt,
397
+ payload: inputData,
398
+ resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
399
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
400
+ };
401
+ }
402
+ };
403
+ var InngestPubSub = class extends events.PubSub {
404
+ inngest;
405
+ workflowId;
406
+ publishFn;
407
+ subscriptions = /* @__PURE__ */ new Map();
408
+ constructor(inngest, workflowId, publishFn) {
409
+ super();
410
+ this.inngest = inngest;
411
+ this.workflowId = workflowId;
412
+ this.publishFn = publishFn;
413
+ }
414
+ /**
415
+ * Publish an event to Inngest's realtime system.
416
+ *
417
+ * Topic format: "workflow.events.v2.{runId}"
418
+ * Maps to Inngest channel: "workflow:{workflowId}:{runId}"
419
+ */
420
+ async publish(topic, event) {
421
+ if (!this.publishFn) {
422
+ return;
423
+ }
424
+ const match = topic.match(/^workflow\.events\.v2\.(.+)$/);
425
+ if (!match) {
426
+ return;
427
+ }
428
+ const runId = match[1];
429
+ try {
430
+ await this.publishFn({
431
+ channel: `workflow:${this.workflowId}:${runId}`,
432
+ topic: "watch",
433
+ data: event.data
434
+ });
435
+ } catch (err) {
436
+ console.error("InngestPubSub publish error:", err?.message ?? err);
437
+ }
438
+ }
439
+ /**
440
+ * Subscribe to events from Inngest's realtime system.
441
+ *
442
+ * Topic format: "workflow.events.v2.{runId}"
443
+ * Maps to Inngest channel: "workflow:{workflowId}:{runId}"
444
+ */
445
+ async subscribe(topic, cb) {
446
+ const match = topic.match(/^workflow\.events\.v2\.(.+)$/);
447
+ if (!match || !match[1]) {
448
+ return;
449
+ }
450
+ const runId = match[1];
451
+ if (this.subscriptions.has(topic)) {
452
+ this.subscriptions.get(topic).callbacks.add(cb);
453
+ return;
454
+ }
455
+ const callbacks = /* @__PURE__ */ new Set([cb]);
456
+ const channel = `workflow:${this.workflowId}:${runId}`;
457
+ const streamPromise = realtime.subscribe(
458
+ {
459
+ channel,
460
+ topics: ["watch"],
461
+ app: this.inngest
462
+ },
463
+ (message) => {
464
+ const event = {
465
+ id: crypto.randomUUID(),
466
+ type: "watch",
467
+ runId,
468
+ data: message.data,
469
+ createdAt: /* @__PURE__ */ new Date()
470
+ };
471
+ for (const callback of callbacks) {
472
+ callback(event);
473
+ }
474
+ }
475
+ );
476
+ this.subscriptions.set(topic, {
477
+ unsubscribe: () => {
478
+ streamPromise.then((stream) => stream.cancel()).catch((err) => {
479
+ console.error("InngestPubSub unsubscribe error:", err);
480
+ });
481
+ },
482
+ callbacks
483
+ });
484
+ }
485
+ /**
486
+ * Unsubscribe a callback from a topic.
487
+ * If no callbacks remain, the underlying Inngest subscription is cancelled.
488
+ */
489
+ async unsubscribe(topic, cb) {
490
+ const sub = this.subscriptions.get(topic);
491
+ if (!sub) {
492
+ return;
493
+ }
494
+ sub.callbacks.delete(cb);
495
+ if (sub.callbacks.size === 0) {
496
+ sub.unsubscribe();
497
+ this.subscriptions.delete(topic);
498
+ }
499
+ }
500
+ /**
501
+ * Flush any pending operations. No-op for Inngest.
502
+ */
503
+ async flush() {
504
+ }
505
+ /**
506
+ * Clean up all subscriptions during graceful shutdown.
507
+ */
508
+ async close() {
509
+ for (const [, sub] of this.subscriptions) {
510
+ sub.unsubscribe();
511
+ }
512
+ this.subscriptions.clear();
513
+ }
514
+ };
41
515
  var InngestRun = class extends workflows.Run {
42
516
  inngest;
43
517
  serializedStepGraph;
@@ -49,38 +523,90 @@ var InngestRun = class extends workflows.Run {
49
523
  this.#mastra = params.mastra;
50
524
  }
51
525
  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}`
526
+ const maxRetries = 3;
527
+ let lastError = null;
528
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
529
+ try {
530
+ const response = await fetch(
531
+ `${this.inngest.apiBaseUrl ?? "https://api.inngest.com"}/v1/events/${eventId}/runs`,
532
+ {
533
+ headers: {
534
+ Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`
535
+ }
536
+ }
537
+ );
538
+ if (response.status === 429) {
539
+ const retryAfter = parseInt(response.headers.get("retry-after") || "2", 10);
540
+ await new Promise((resolve) => setTimeout(resolve, retryAfter * 1e3));
541
+ continue;
542
+ }
543
+ if (!response.ok) {
544
+ throw new Error(`Inngest API error: ${response.status} ${response.statusText}`);
545
+ }
546
+ const text = await response.text();
547
+ if (!text) {
548
+ await new Promise((resolve) => setTimeout(resolve, 1e3 * (attempt + 1)));
549
+ continue;
550
+ }
551
+ const json = JSON.parse(text);
552
+ return json.data;
553
+ } catch (error) {
554
+ lastError = error;
555
+ if (attempt < maxRetries - 1) {
556
+ await new Promise((resolve) => setTimeout(resolve, 1e3 * Math.pow(2, attempt)));
557
+ }
55
558
  }
56
- });
57
- const json = await response.json();
58
- return json.data;
559
+ }
560
+ throw new inngest.NonRetriableError(`Failed to get runs after ${maxRetries} attempts: ${lastError?.message}`);
59
561
  }
60
- async getRunOutput(eventId) {
61
- let runs = await this.getRuns(eventId);
562
+ async getRunOutput(eventId, maxWaitMs = 3e5) {
563
+ const startTime = Date.now();
62
564
  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);
565
+ const workflowsStore = await storage?.getStore("workflows");
566
+ while (Date.now() - startTime < maxWaitMs) {
567
+ let runs;
568
+ try {
569
+ runs = await this.getRuns(eventId);
570
+ } catch (error) {
571
+ if (error instanceof inngest.NonRetriableError) {
572
+ throw error;
573
+ }
574
+ throw new inngest.NonRetriableError(
575
+ `Failed to poll workflow status: ${error instanceof Error ? error.message : String(error)}`
576
+ );
577
+ }
578
+ if (runs?.[0]?.status === "Completed" && runs?.[0]?.event_id === eventId) {
579
+ return runs[0];
580
+ }
66
581
  if (runs?.[0]?.status === "Failed") {
67
- const snapshot = await storage?.loadWorkflowSnapshot({
582
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
68
583
  workflowName: this.workflowId,
69
584
  runId: this.runId
70
585
  });
586
+ if (snapshot?.context) {
587
+ snapshot.context = workflows.hydrateSerializedStepErrors(snapshot.context);
588
+ }
71
589
  return {
72
- output: { result: { steps: snapshot?.context, status: "failed", error: runs?.[0]?.output?.message } }
590
+ output: {
591
+ result: {
592
+ steps: snapshot?.context,
593
+ status: "failed",
594
+ // Get the original error from NonRetriableError's cause (which contains the workflow result)
595
+ error: error.getErrorFromUnknown(runs?.[0]?.output?.cause?.error, { serializeStack: false })
596
+ }
597
+ }
73
598
  };
74
599
  }
75
600
  if (runs?.[0]?.status === "Cancelled") {
76
- const snapshot = await storage?.loadWorkflowSnapshot({
601
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
77
602
  workflowName: this.workflowId,
78
603
  runId: this.runId
79
604
  });
80
605
  return { output: { result: { steps: snapshot?.context, status: "canceled" } } };
81
606
  }
607
+ await new Promise((resolve) => setTimeout(resolve, 1e3 + Math.random() * 1e3));
82
608
  }
83
- return runs?.[0];
609
+ throw new inngest.NonRetriableError(`Workflow did not complete within ${maxWaitMs}ms`);
84
610
  }
85
611
  async cancel() {
86
612
  const storage = this.#mastra?.getStorage();
@@ -90,18 +616,20 @@ var InngestRun = class extends workflows.Run {
90
616
  runId: this.runId
91
617
  }
92
618
  });
93
- const snapshot = await storage?.loadWorkflowSnapshot({
619
+ const workflowsStore = await storage?.getStore("workflows");
620
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
94
621
  workflowName: this.workflowId,
95
622
  runId: this.runId
96
623
  });
97
624
  if (snapshot) {
98
- await storage?.persistWorkflowSnapshot({
625
+ await workflowsStore?.persistWorkflowSnapshot({
99
626
  workflowName: this.workflowId,
100
627
  runId: this.runId,
101
628
  resourceId: this.resourceId,
102
629
  snapshot: {
103
630
  ...snapshot,
104
- status: "canceled"
631
+ status: "canceled",
632
+ value: snapshot.value
105
633
  }
106
634
  });
107
635
  }
@@ -109,28 +637,79 @@ var InngestRun = class extends workflows.Run {
109
637
  async start(params) {
110
638
  return this._start(params);
111
639
  }
640
+ /**
641
+ * Starts the workflow execution without waiting for completion (fire-and-forget).
642
+ * Returns immediately with the runId after sending the event to Inngest.
643
+ * The workflow executes independently in Inngest.
644
+ * Use this when you don't need to wait for the result or want to avoid polling failures.
645
+ */
646
+ async startAsync(params) {
647
+ const workflowsStore = await this.#mastra.getStorage()?.getStore("workflows");
648
+ await workflowsStore?.persistWorkflowSnapshot({
649
+ workflowName: this.workflowId,
650
+ runId: this.runId,
651
+ resourceId: this.resourceId,
652
+ snapshot: {
653
+ runId: this.runId,
654
+ serializedStepGraph: this.serializedStepGraph,
655
+ status: "running",
656
+ value: {},
657
+ context: {},
658
+ activePaths: [],
659
+ suspendedPaths: {},
660
+ activeStepsPath: {},
661
+ resumeLabels: {},
662
+ waitingPaths: {},
663
+ timestamp: Date.now()
664
+ }
665
+ });
666
+ const inputDataToUse = await this._validateInput(params.inputData);
667
+ const initialStateToUse = await this._validateInitialState(params.initialState ?? {});
668
+ const eventOutput = await this.inngest.send({
669
+ name: `workflow.${this.workflowId}`,
670
+ data: {
671
+ inputData: inputDataToUse,
672
+ initialState: initialStateToUse,
673
+ runId: this.runId,
674
+ resourceId: this.resourceId,
675
+ outputOptions: params.outputOptions,
676
+ tracingOptions: params.tracingOptions,
677
+ requestContext: params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {},
678
+ perStep: params.perStep
679
+ }
680
+ });
681
+ const eventId = eventOutput.ids[0];
682
+ if (!eventId) {
683
+ throw new Error("Event ID is not set");
684
+ }
685
+ return { runId: this.runId };
686
+ }
112
687
  async _start({
113
688
  inputData,
114
689
  initialState,
115
690
  outputOptions,
116
691
  tracingOptions,
117
- format
692
+ format,
693
+ requestContext,
694
+ perStep
118
695
  }) {
119
- await this.#mastra.getStorage()?.persistWorkflowSnapshot({
696
+ const workflowsStore = await this.#mastra.getStorage()?.getStore("workflows");
697
+ await workflowsStore?.persistWorkflowSnapshot({
120
698
  workflowName: this.workflowId,
121
699
  runId: this.runId,
122
700
  resourceId: this.resourceId,
123
701
  snapshot: {
124
702
  runId: this.runId,
125
703
  serializedStepGraph: this.serializedStepGraph,
704
+ status: "running",
126
705
  value: {},
127
706
  context: {},
128
707
  activePaths: [],
129
708
  suspendedPaths: {},
709
+ activeStepsPath: {},
130
710
  resumeLabels: {},
131
711
  waitingPaths: {},
132
- timestamp: Date.now(),
133
- status: "running"
712
+ timestamp: Date.now()
134
713
  }
135
714
  });
136
715
  const inputDataToUse = await this._validateInput(inputData);
@@ -144,7 +723,9 @@ var InngestRun = class extends workflows.Run {
144
723
  resourceId: this.resourceId,
145
724
  outputOptions,
146
725
  tracingOptions,
147
- format
726
+ format,
727
+ requestContext: requestContext ? Object.fromEntries(requestContext.entries()) : {},
728
+ perStep
148
729
  }
149
730
  });
150
731
  const eventId = eventOutput.ids[0];
@@ -153,9 +734,7 @@ var InngestRun = class extends workflows.Run {
153
734
  }
154
735
  const runOutput = await this.getRunOutput(eventId);
155
736
  const result = runOutput?.output?.result;
156
- if (result.status === "failed") {
157
- result.error = new Error(result.error);
158
- }
737
+ this.hydrateFailedResult(result);
159
738
  if (result.status !== "suspended") {
160
739
  this.cleanup?.();
161
740
  }
@@ -174,15 +753,24 @@ var InngestRun = class extends workflows.Run {
174
753
  }
175
754
  async _resume(params) {
176
755
  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({
756
+ let steps = [];
757
+ if (typeof params.step === "string") {
758
+ steps = params.step.split(".");
759
+ } else {
760
+ steps = (Array.isArray(params.step) ? params.step : [params.step]).map(
761
+ (step) => typeof step === "string" ? step : step?.id
762
+ );
763
+ }
764
+ const workflowsStore = await storage?.getStore("workflows");
765
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
181
766
  workflowName: this.workflowId,
182
767
  runId: this.runId
183
768
  });
184
769
  const suspendedStep = this.workflowSteps[steps?.[0] ?? ""];
185
770
  const resumeDataToUse = await this._validateResumeData(params.resumeData, suspendedStep);
771
+ const persistedRequestContext = snapshot?.requestContext ?? {};
772
+ const newRequestContext = params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {};
773
+ const mergedRequestContext = { ...persistedRequestContext, ...newRequestContext };
186
774
  const eventOutput = await this.inngest.send({
187
775
  name: `workflow.${this.workflowId}`,
188
776
  data: {
@@ -195,9 +783,10 @@ var InngestRun = class extends workflows.Run {
195
783
  steps,
196
784
  stepResults: snapshot?.context,
197
785
  resumePayload: resumeDataToUse,
198
- // @ts-ignore
199
- resumePath: snapshot?.suspendedPaths?.[steps?.[0]]
200
- }
786
+ resumePath: steps?.[0] ? snapshot?.suspendedPaths?.[steps?.[0]] : void 0
787
+ },
788
+ requestContext: mergedRequestContext,
789
+ perStep: params.perStep
201
790
  }
202
791
  });
203
792
  const eventId = eventOutput.ids[0];
@@ -206,9 +795,100 @@ var InngestRun = class extends workflows.Run {
206
795
  }
207
796
  const runOutput = await this.getRunOutput(eventId);
208
797
  const result = runOutput?.output?.result;
209
- if (result.status === "failed") {
210
- result.error = new Error(result.error);
798
+ this.hydrateFailedResult(result);
799
+ return result;
800
+ }
801
+ async timeTravel(params) {
802
+ const p = this._timeTravel(params).then((result) => {
803
+ if (result.status !== "suspended") {
804
+ this.closeStreamAction?.().catch(() => {
805
+ });
806
+ }
807
+ return result;
808
+ });
809
+ this.executionResults = p;
810
+ return p;
811
+ }
812
+ async _timeTravel(params) {
813
+ if (!params.step || Array.isArray(params.step) && params.step?.length === 0) {
814
+ throw new Error("Step is required and must be a valid step or array of steps");
815
+ }
816
+ let steps = [];
817
+ if (typeof params.step === "string") {
818
+ steps = params.step.split(".");
819
+ } else {
820
+ steps = (Array.isArray(params.step) ? params.step : [params.step]).map(
821
+ (step) => typeof step === "string" ? step : step?.id
822
+ );
823
+ }
824
+ if (steps.length === 0) {
825
+ throw new Error("No steps provided to timeTravel");
826
+ }
827
+ const storage = this.#mastra?.getStorage();
828
+ const workflowsStore = await storage?.getStore("workflows");
829
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
830
+ workflowName: this.workflowId,
831
+ runId: this.runId
832
+ });
833
+ if (!snapshot) {
834
+ await workflowsStore?.persistWorkflowSnapshot({
835
+ workflowName: this.workflowId,
836
+ runId: this.runId,
837
+ resourceId: this.resourceId,
838
+ snapshot: {
839
+ runId: this.runId,
840
+ serializedStepGraph: this.serializedStepGraph,
841
+ status: "pending",
842
+ value: {},
843
+ context: {},
844
+ activePaths: [],
845
+ suspendedPaths: {},
846
+ activeStepsPath: {},
847
+ resumeLabels: {},
848
+ waitingPaths: {},
849
+ timestamp: Date.now()
850
+ }
851
+ });
852
+ }
853
+ if (snapshot?.status === "running") {
854
+ throw new Error("This workflow run is still running, cannot time travel");
855
+ }
856
+ let inputDataToUse = params.inputData;
857
+ if (inputDataToUse && steps.length === 1) {
858
+ inputDataToUse = await this._validateTimetravelInputData(params.inputData, this.workflowSteps[steps[0]]);
859
+ }
860
+ const timeTravelData = workflows.createTimeTravelExecutionParams({
861
+ steps,
862
+ inputData: inputDataToUse,
863
+ resumeData: params.resumeData,
864
+ context: params.context,
865
+ nestedStepsContext: params.nestedStepsContext,
866
+ snapshot: snapshot ?? { context: {} },
867
+ graph: this.executionGraph,
868
+ initialState: params.initialState,
869
+ perStep: params.perStep
870
+ });
871
+ const eventOutput = await this.inngest.send({
872
+ name: `workflow.${this.workflowId}`,
873
+ data: {
874
+ initialState: timeTravelData.state,
875
+ runId: this.runId,
876
+ workflowId: this.workflowId,
877
+ stepResults: timeTravelData.stepResults,
878
+ timeTravel: timeTravelData,
879
+ tracingOptions: params.tracingOptions,
880
+ outputOptions: params.outputOptions,
881
+ requestContext: params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {},
882
+ perStep: params.perStep
883
+ }
884
+ });
885
+ const eventId = eventOutput.ids[0];
886
+ if (!eventId) {
887
+ throw new Error("Event ID is not set");
211
888
  }
889
+ const runOutput = await this.getRunOutput(eventId);
890
+ const result = runOutput?.output?.result;
891
+ this.hydrateFailedResult(result);
212
892
  return result;
213
893
  }
214
894
  watch(cb) {
@@ -237,14 +917,14 @@ var InngestRun = class extends workflows.Run {
237
917
  streamLegacy({ inputData, requestContext } = {}) {
238
918
  const { readable, writable } = new TransformStream();
239
919
  const writer = writable.getWriter();
920
+ void writer.write({
921
+ // @ts-ignore
922
+ type: "start",
923
+ // @ts-ignore
924
+ payload: { runId: this.runId }
925
+ });
240
926
  const unwatch = this.watch(async (event) => {
241
927
  try {
242
- await writer.write({
243
- // @ts-ignore
244
- type: "start",
245
- // @ts-ignore
246
- payload: { runId: this.runId }
247
- });
248
928
  const e = {
249
929
  ...event,
250
930
  type: event.type.replace("workflow-", "")
@@ -290,7 +970,8 @@ var InngestRun = class extends workflows.Run {
290
970
  tracingOptions,
291
971
  closeOnSuspend = true,
292
972
  initialState,
293
- outputOptions
973
+ outputOptions,
974
+ perStep
294
975
  } = {}) {
295
976
  if (this.closeStreamAction && this.streamOutput) {
296
977
  return this.streamOutput;
@@ -326,7 +1007,8 @@ var InngestRun = class extends workflows.Run {
326
1007
  initialState,
327
1008
  tracingOptions,
328
1009
  outputOptions,
329
- format: "vnext"
1010
+ format: "vnext",
1011
+ perStep
330
1012
  });
331
1013
  let executionResults;
332
1014
  try {
@@ -360,7 +1042,92 @@ var InngestRun = class extends workflows.Run {
360
1042
  streamVNext(args = {}) {
361
1043
  return this.stream(args);
362
1044
  }
1045
+ timeTravelStream({
1046
+ inputData,
1047
+ resumeData,
1048
+ initialState,
1049
+ step,
1050
+ context,
1051
+ nestedStepsContext,
1052
+ requestContext,
1053
+ tracingOptions,
1054
+ outputOptions,
1055
+ perStep
1056
+ }) {
1057
+ this.closeStreamAction = async () => {
1058
+ };
1059
+ const self = this;
1060
+ const stream$1 = new web.ReadableStream({
1061
+ async start(controller) {
1062
+ const unwatch = self.watch(async ({ type, from = stream.ChunkFrom.WORKFLOW, payload }) => {
1063
+ controller.enqueue({
1064
+ type,
1065
+ runId: self.runId,
1066
+ from,
1067
+ payload: {
1068
+ stepName: payload?.id,
1069
+ ...payload
1070
+ }
1071
+ });
1072
+ });
1073
+ self.closeStreamAction = async () => {
1074
+ unwatch();
1075
+ try {
1076
+ controller.close();
1077
+ } catch (err) {
1078
+ console.error("Error closing stream:", err);
1079
+ }
1080
+ };
1081
+ const executionResultsPromise = self._timeTravel({
1082
+ inputData,
1083
+ step,
1084
+ context,
1085
+ nestedStepsContext,
1086
+ resumeData,
1087
+ initialState,
1088
+ requestContext,
1089
+ tracingOptions,
1090
+ outputOptions,
1091
+ perStep
1092
+ });
1093
+ self.executionResults = executionResultsPromise;
1094
+ let executionResults;
1095
+ try {
1096
+ executionResults = await executionResultsPromise;
1097
+ self.closeStreamAction?.().catch(() => {
1098
+ });
1099
+ if (self.streamOutput) {
1100
+ self.streamOutput.updateResults(executionResults);
1101
+ }
1102
+ } catch (err) {
1103
+ self.streamOutput?.rejectResults(err);
1104
+ self.closeStreamAction?.().catch(() => {
1105
+ });
1106
+ }
1107
+ }
1108
+ });
1109
+ this.streamOutput = new stream.WorkflowRunOutput({
1110
+ runId: this.runId,
1111
+ workflowId: this.workflowId,
1112
+ stream: stream$1
1113
+ });
1114
+ return this.streamOutput;
1115
+ }
1116
+ /**
1117
+ * Hydrates errors in a failed workflow result back to proper Error instances.
1118
+ * This ensures error.cause chains and custom properties are preserved.
1119
+ */
1120
+ hydrateFailedResult(result) {
1121
+ if (result.status === "failed") {
1122
+ result.error = error.getErrorFromUnknown(result.error, { serializeStack: false });
1123
+ if (result.steps) {
1124
+ workflows.hydrateSerializedStepErrors(result.steps);
1125
+ }
1126
+ }
1127
+ }
363
1128
  };
1129
+
1130
+ // src/workflow.ts
364
1131
  var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
365
1132
  #mastra;
366
1133
  inngest;
@@ -369,6 +1136,7 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
369
1136
  constructor(params, inngest) {
370
1137
  const { concurrency, rateLimit, throttle, debounce, priority, ...workflowParams } = params;
371
1138
  super(workflowParams);
1139
+ this.engineType = "inngest";
372
1140
  const flowControlEntries = Object.entries({ concurrency, rateLimit, throttle, debounce, priority }).filter(
373
1141
  ([_, value]) => value !== void 0
374
1142
  );
@@ -382,7 +1150,11 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
382
1150
  this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
383
1151
  return { runs: [], total: 0 };
384
1152
  }
385
- return storage.listWorkflowRuns({ workflowName: this.id, ...args ?? {} });
1153
+ const workflowsStore = await storage.getStore("workflows");
1154
+ if (!workflowsStore) {
1155
+ return { runs: [], total: 0 };
1156
+ }
1157
+ return workflowsStore.listWorkflowRuns({ workflowName: this.id, ...args ?? {} });
386
1158
  }
387
1159
  async getWorkflowRunById(runId) {
388
1160
  const storage = this.#mastra?.getStorage();
@@ -390,10 +1162,15 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
390
1162
  this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
391
1163
  return this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null;
392
1164
  }
393
- const run = await storage.getWorkflowRunById({ runId, workflowName: this.id });
1165
+ const workflowsStore = await storage.getStore("workflows");
1166
+ if (!workflowsStore) {
1167
+ return this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null;
1168
+ }
1169
+ const run = await workflowsStore.getWorkflowRunById({ runId, workflowName: this.id });
394
1170
  return run ?? (this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null);
395
1171
  }
396
1172
  __registerMastra(mastra) {
1173
+ super.__registerMastra(mastra);
397
1174
  this.#mastra = mastra;
398
1175
  this.executionEngine.__registerMastra(mastra);
399
1176
  const updateNested = (step) => {
@@ -412,7 +1189,7 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
412
1189
  }
413
1190
  }
414
1191
  async createRun(options) {
415
- const runIdToUse = options?.runId || crypto.randomUUID();
1192
+ const runIdToUse = options?.runId || crypto$1.randomUUID();
416
1193
  const run = this.runs.get(runIdToUse) ?? new InngestRun(
417
1194
  {
418
1195
  workflowId: this.id,
@@ -424,7 +1201,9 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
424
1201
  mastra: this.#mastra,
425
1202
  retryConfig: this.retryConfig,
426
1203
  cleanup: () => this.runs.delete(runIdToUse),
427
- workflowSteps: this.steps
1204
+ workflowSteps: this.steps,
1205
+ workflowEngineType: this.engineType,
1206
+ validateInputs: this.options.validateInputs
428
1207
  },
429
1208
  this.inngest
430
1209
  );
@@ -433,9 +1212,12 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
433
1212
  workflowStatus: run.workflowRunStatus,
434
1213
  stepResults: {}
435
1214
  });
436
- const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse, false);
1215
+ const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse, {
1216
+ withNestedWorkflows: false
1217
+ });
437
1218
  if (!workflowSnapshotInStorage && shouldPersistSnapshot) {
438
- await this.mastra?.getStorage()?.persistWorkflowSnapshot({
1219
+ const workflowsStore = await this.mastra?.getStorage()?.getStore("workflows");
1220
+ await workflowsStore?.persistWorkflowSnapshot({
439
1221
  workflowName: this.id,
440
1222
  runId: runIdToUse,
441
1223
  resourceId: options?.resourceId,
@@ -445,13 +1227,13 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
445
1227
  value: {},
446
1228
  context: {},
447
1229
  activePaths: [],
1230
+ activeStepsPath: {},
448
1231
  waitingPaths: {},
449
1232
  serializedStepGraph: this.serializedStepGraph,
450
1233
  suspendedPaths: {},
451
1234
  resumeLabels: {},
452
1235
  result: void 0,
453
1236
  error: void 0,
454
- // @ts-ignore
455
1237
  timestamp: Date.now()
456
1238
  }
457
1239
  });
@@ -465,42 +1247,20 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
465
1247
  this.function = this.inngest.createFunction(
466
1248
  {
467
1249
  id: `workflow.${this.id}`,
468
- // @ts-ignore
469
- retries: this.retryConfig?.attempts ?? 0,
1250
+ retries: Math.min(this.retryConfig?.attempts ?? 0, 20),
470
1251
  cancelOn: [{ event: `cancel.workflow.${this.id}` }],
471
1252
  // Spread flow control configuration
472
1253
  ...this.flowControlConfig
473
1254
  },
474
1255
  { event: `workflow.${this.id}` },
475
1256
  async ({ event, step, attempt, publish }) => {
476
- let { inputData, initialState, runId, resourceId, resume, outputOptions, format } = event.data;
1257
+ let { inputData, initialState, runId, resourceId, resume, outputOptions, format, timeTravel, perStep } = event.data;
477
1258
  if (!runId) {
478
1259
  runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
479
- return crypto.randomUUID();
1260
+ return crypto$1.randomUUID();
480
1261
  });
481
1262
  }
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
- };
1263
+ const pubsub = new InngestPubSub(this.inngest, this.id, publish);
504
1264
  const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
505
1265
  const result = await engine.execute({
506
1266
  workflowId: this.id,
@@ -510,23 +1270,32 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
510
1270
  serializedStepGraph: this.serializedStepGraph,
511
1271
  input: inputData,
512
1272
  initialState,
513
- emitter,
1273
+ pubsub,
514
1274
  retryConfig: this.retryConfig,
515
- requestContext: new di.RequestContext(),
516
- // TODO
1275
+ requestContext: new di.RequestContext(Object.entries(event.data.requestContext ?? {})),
517
1276
  resume,
1277
+ timeTravel,
1278
+ perStep,
518
1279
  format,
519
1280
  abortController: new AbortController(),
520
1281
  // currentSpan: undefined, // TODO: Pass actual parent Span from workflow execution context
521
1282
  outputOptions,
522
- writableStream: new WritableStream({
523
- write(chunk) {
524
- void emitter.emit("watch", chunk).catch(() => {
1283
+ outputWriter: async (chunk) => {
1284
+ try {
1285
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1286
+ type: "watch",
1287
+ runId,
1288
+ data: chunk
525
1289
  });
1290
+ } catch (err) {
1291
+ this.logger.debug?.("Failed to publish watch event:", err);
526
1292
  }
527
- })
1293
+ }
528
1294
  });
529
1295
  await step.run(`workflow.${this.id}.finalize`, async () => {
1296
+ if (result.status !== "paused") {
1297
+ await engine.invokeLifecycleCallbacksInternal(result);
1298
+ }
530
1299
  if (result.status === "failed") {
531
1300
  throw new inngest.NonRetriableError(`Workflow failed`, {
532
1301
  cause: result
@@ -556,30 +1325,63 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
556
1325
  return [this.getFunction(), ...this.getNestedFunctions(this.executionGraph.steps)];
557
1326
  }
558
1327
  };
1328
+ function serve({
1329
+ mastra,
1330
+ inngest,
1331
+ functions: userFunctions = [],
1332
+ registerOptions
1333
+ }) {
1334
+ const wfs = mastra.listWorkflows();
1335
+ const workflowFunctions = Array.from(
1336
+ new Set(
1337
+ Object.values(wfs).flatMap((wf) => {
1338
+ if (wf instanceof InngestWorkflow) {
1339
+ wf.__registerMastra(mastra);
1340
+ return wf.getFunctions();
1341
+ }
1342
+ return [];
1343
+ })
1344
+ )
1345
+ );
1346
+ return hono.serve({
1347
+ ...registerOptions,
1348
+ client: inngest,
1349
+ functions: [...workflowFunctions, ...userFunctions]
1350
+ });
1351
+ }
1352
+
1353
+ // src/types.ts
1354
+ var _compatibilityCheck = true;
1355
+
1356
+ // src/index.ts
559
1357
  function isAgent(params) {
560
1358
  return params?.component === "AGENT";
561
1359
  }
562
1360
  function isTool(params) {
563
1361
  return params instanceof tools.Tool;
564
1362
  }
1363
+ function isInngestWorkflow(params) {
1364
+ return params instanceof InngestWorkflow;
1365
+ }
565
1366
  function createStep(params, agentOptions) {
1367
+ if (isInngestWorkflow(params)) {
1368
+ return params;
1369
+ }
566
1370
  if (isAgent(params)) {
1371
+ const outputSchema = agentOptions?.structuredOutput?.schema ?? zod.z.object({ text: zod.z.string() });
567
1372
  return {
568
1373
  id: params.name,
569
1374
  description: params.getDescription(),
570
- // @ts-ignore
571
1375
  inputSchema: zod.z.object({
572
1376
  prompt: zod.z.string()
573
1377
  // resourceId: z.string().optional(),
574
1378
  // threadId: z.string().optional(),
575
1379
  }),
576
- // @ts-ignore
577
- outputSchema: zod.z.object({
578
- text: zod.z.string()
579
- }),
1380
+ outputSchema,
580
1381
  execute: async ({
581
1382
  inputData,
582
- [_constants.EMITTER_SYMBOL]: emitter,
1383
+ runId,
1384
+ [_constants.PUBSUB_SYMBOL]: pubsub,
583
1385
  [_constants.STREAM_FORMAT_SYMBOL]: streamFormat,
584
1386
  requestContext,
585
1387
  tracingContext,
@@ -592,6 +1394,7 @@ function createStep(params, agentOptions) {
592
1394
  streamPromise.resolve = resolve;
593
1395
  streamPromise.reject = reject;
594
1396
  });
1397
+ let structuredResult = null;
595
1398
  const toolData = {
596
1399
  name: params.name,
597
1400
  args: inputData
@@ -605,6 +1408,10 @@ function createStep(params, agentOptions) {
605
1408
  requestContext,
606
1409
  tracingContext,
607
1410
  onFinish: (result) => {
1411
+ const resultWithObject = result;
1412
+ if (agentOptions?.structuredOutput?.schema && resultWithObject.object) {
1413
+ structuredResult = resultWithObject.object;
1414
+ }
608
1415
  streamPromise.resolve(result.text);
609
1416
  void agentOptions?.onFinish?.(result);
610
1417
  },
@@ -617,6 +1424,10 @@ function createStep(params, agentOptions) {
617
1424
  requestContext,
618
1425
  tracingContext,
619
1426
  onFinish: (result) => {
1427
+ const resultWithObject = result;
1428
+ if (agentOptions?.structuredOutput?.schema && resultWithObject.object) {
1429
+ structuredResult = resultWithObject.object;
1430
+ }
620
1431
  streamPromise.resolve(result.text);
621
1432
  void agentOptions?.onFinish?.(result);
622
1433
  },
@@ -625,22 +1436,24 @@ function createStep(params, agentOptions) {
625
1436
  stream = modelOutput.fullStream;
626
1437
  }
627
1438
  if (streamFormat === "legacy") {
628
- await emitter.emit("watch", {
629
- type: "tool-call-streaming-start",
630
- ...toolData ?? {}
1439
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1440
+ type: "watch",
1441
+ runId,
1442
+ data: { type: "tool-call-streaming-start", ...toolData ?? {} }
631
1443
  });
632
1444
  for await (const chunk of stream) {
633
1445
  if (chunk.type === "text-delta") {
634
- await emitter.emit("watch", {
635
- type: "tool-call-delta",
636
- ...toolData ?? {},
637
- argsTextDelta: chunk.textDelta
1446
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1447
+ type: "watch",
1448
+ runId,
1449
+ data: { type: "tool-call-delta", ...toolData ?? {}, argsTextDelta: chunk.textDelta }
638
1450
  });
639
1451
  }
640
1452
  }
641
- await emitter.emit("watch", {
642
- type: "tool-call-streaming-finish",
643
- ...toolData ?? {}
1453
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1454
+ type: "watch",
1455
+ runId,
1456
+ data: { type: "tool-call-streaming-finish", ...toolData ?? {} }
644
1457
  });
645
1458
  } else {
646
1459
  for await (const chunk of stream) {
@@ -650,6 +1463,9 @@ function createStep(params, agentOptions) {
650
1463
  if (abortSignal.aborted) {
651
1464
  return abort();
652
1465
  }
1466
+ if (structuredResult !== null) {
1467
+ return structuredResult;
1468
+ }
653
1469
  return {
654
1470
  text: await streamPromise.promise
655
1471
  };
@@ -663,20 +1479,38 @@ function createStep(params, agentOptions) {
663
1479
  }
664
1480
  return {
665
1481
  // TODO: tool probably should have strong id type
666
- // @ts-ignore
667
1482
  id: params.id,
668
1483
  description: params.description,
669
1484
  inputSchema: params.inputSchema,
670
1485
  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),
1486
+ suspendSchema: params.suspendSchema,
1487
+ resumeSchema: params.resumeSchema,
1488
+ execute: async ({
1489
+ inputData,
1490
+ mastra,
1491
+ requestContext,
1492
+ tracingContext,
1493
+ suspend,
1494
+ resumeData,
1495
+ runId,
1496
+ workflowId,
1497
+ state,
1498
+ setState
1499
+ }) => {
1500
+ const toolContext = {
1501
+ mastra,
675
1502
  requestContext,
676
1503
  tracingContext,
677
- suspend,
678
- resumeData
679
- });
1504
+ workflow: {
1505
+ runId,
1506
+ resumeData,
1507
+ suspend,
1508
+ workflowId,
1509
+ state,
1510
+ setState
1511
+ }
1512
+ };
1513
+ return params.execute(inputData, toolContext);
680
1514
  },
681
1515
  component: "TOOL"
682
1516
  };
@@ -710,6 +1544,8 @@ function init(inngest) {
710
1544
  suspendSchema: step.suspendSchema,
711
1545
  stateSchema: step.stateSchema,
712
1546
  execute: step.execute,
1547
+ retries: step.retries,
1548
+ scorers: step.scorers,
713
1549
  component: step.component
714
1550
  };
715
1551
  },
@@ -719,7 +1555,8 @@ function init(inngest) {
719
1555
  inputSchema: workflow.inputSchema,
720
1556
  outputSchema: workflow.outputSchema,
721
1557
  steps: workflow.stepDefs,
722
- mastra: workflow.mastra
1558
+ mastra: workflow.mastra,
1559
+ options: workflow.options
723
1560
  });
724
1561
  wf.setStepFlow(workflow.stepGraph);
725
1562
  wf.commit();
@@ -727,803 +1564,12 @@ function init(inngest) {
727
1564
  }
728
1565
  };
729
1566
  }
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
1567
 
1524
1568
  exports.InngestExecutionEngine = InngestExecutionEngine;
1569
+ exports.InngestPubSub = InngestPubSub;
1525
1570
  exports.InngestRun = InngestRun;
1526
1571
  exports.InngestWorkflow = InngestWorkflow;
1572
+ exports._compatibilityCheck = _compatibilityCheck;
1527
1573
  exports.createStep = createStep;
1528
1574
  exports.init = init;
1529
1575
  exports.serve = serve;