@mastra/inngest 0.0.0-bundle-recursion-20251030002519 → 0.0.0-bundle-studio-cloud-20251222034739

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