@mastra/inngest 0.0.0-dynamic-model-router-20251010230835 → 0.0.0-elated-armadillo-be37a1-2-20251219213629

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