@mastra/inngest 0.0.0-roamin-openaivoice-speak-options-passing-20250926163614 → 0.0.0-safe-stringify-telemetry-20251205024938

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.js CHANGED
@@ -1,10 +1,13 @@
1
1
  import { randomUUID } from 'crypto';
2
+ import { ReadableStream } from 'stream/web';
2
3
  import { subscribe } from '@inngest/realtime';
3
4
  import { wrapMastra, AISpanType } from '@mastra/core/ai-tracing';
4
5
  import { RuntimeContext } from '@mastra/core/di';
6
+ import { ChunkFrom, WorkflowRunOutput } from '@mastra/core/stream';
5
7
  import { ToolStream, Tool } from '@mastra/core/tools';
6
- import { Run, Workflow, DefaultExecutionEngine, getStepResult, validateStepInput } from '@mastra/core/workflows';
8
+ import { Run, createTimeTravelExecutionParams, Workflow, DefaultExecutionEngine, getStepResult, validateStepInput, validateStepResumeData } from '@mastra/core/workflows';
7
9
  import { EMITTER_SYMBOL, STREAM_FORMAT_SYMBOL } from '@mastra/core/workflows/_constants';
10
+ import { NonRetriableError, RetryAfterError } from 'inngest';
8
11
  import { serve as serve$1 } from 'inngest/hono';
9
12
  import { z } from 'zod';
10
13
 
@@ -58,8 +61,15 @@ var InngestRun = class extends Run {
58
61
  await new Promise((resolve) => setTimeout(resolve, 1e3));
59
62
  runs = await this.getRuns(eventId);
60
63
  if (runs?.[0]?.status === "Failed") {
61
- throw new Error(`Function run ${runs?.[0]?.status}`);
62
- } else if (runs?.[0]?.status === "Cancelled") {
64
+ const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
65
+ workflowName: this.workflowId,
66
+ runId: this.runId
67
+ });
68
+ return {
69
+ output: { result: { steps: snapshot?.context, status: "failed", error: runs?.[0]?.output?.message } }
70
+ };
71
+ }
72
+ if (runs?.[0]?.status === "Cancelled") {
63
73
  const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
64
74
  workflowName: this.workflowId,
65
75
  runId: this.runId
@@ -93,13 +103,15 @@ var InngestRun = class extends Run {
93
103
  resourceId: this.resourceId,
94
104
  snapshot: {
95
105
  ...snapshot,
96
- status: "canceled"
106
+ status: "canceled",
107
+ value: snapshot.value
97
108
  }
98
109
  });
99
110
  }
100
111
  }
101
112
  async start({
102
- inputData
113
+ inputData,
114
+ initialState
103
115
  }) {
104
116
  await this.#mastra.getStorage()?.persistWorkflowSnapshot({
105
117
  workflowName: this.workflowId,
@@ -108,20 +120,24 @@ var InngestRun = class extends Run {
108
120
  snapshot: {
109
121
  runId: this.runId,
110
122
  serializedStepGraph: this.serializedStepGraph,
123
+ status: "running",
111
124
  value: {},
112
125
  context: {},
113
126
  activePaths: [],
114
127
  suspendedPaths: {},
128
+ activeStepsPath: {},
129
+ resumeLabels: {},
115
130
  waitingPaths: {},
116
- timestamp: Date.now(),
117
- status: "running"
131
+ timestamp: Date.now()
118
132
  }
119
133
  });
120
134
  const inputDataToUse = await this._validateInput(inputData);
135
+ const initialStateToUse = await this._validateInitialState(initialState ?? {});
121
136
  const eventOutput = await this.inngest.send({
122
137
  name: `workflow.${this.workflowId}`,
123
138
  data: {
124
139
  inputData: inputDataToUse,
140
+ initialState: initialStateToUse,
125
141
  runId: this.runId,
126
142
  resourceId: this.resourceId
127
143
  }
@@ -152,10 +168,16 @@ var InngestRun = class extends Run {
152
168
  return p;
153
169
  }
154
170
  async _resume(params) {
155
- const steps = (Array.isArray(params.step) ? params.step : [params.step]).map(
156
- (step) => typeof step === "string" ? step : step?.id
157
- );
158
- const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
171
+ const storage = this.#mastra?.getStorage();
172
+ let steps = [];
173
+ if (typeof params.step === "string") {
174
+ steps = params.step.split(".");
175
+ } else {
176
+ steps = (Array.isArray(params.step) ? params.step : [params.step]).map(
177
+ (step) => typeof step === "string" ? step : step?.id
178
+ );
179
+ }
180
+ const snapshot = await storage?.loadWorkflowSnapshot({
159
181
  workflowName: this.workflowId,
160
182
  runId: this.runId
161
183
  });
@@ -165,6 +187,7 @@ var InngestRun = class extends Run {
165
187
  name: `workflow.${this.workflowId}`,
166
188
  data: {
167
189
  inputData: resumeDataToUse,
190
+ initialState: snapshot?.value ?? {},
168
191
  runId: this.runId,
169
192
  workflowId: this.workflowId,
170
193
  stepResults: snapshot?.context,
@@ -211,7 +234,98 @@ var InngestRun = class extends Run {
211
234
  });
212
235
  };
213
236
  }
214
- stream({ inputData, runtimeContext } = {}) {
237
+ async timeTravel(params) {
238
+ const p = this._timeTravel(params).then((result) => {
239
+ if (result.status !== "suspended") {
240
+ this.closeStreamAction?.().catch(() => {
241
+ });
242
+ }
243
+ return result;
244
+ });
245
+ this.executionResults = p;
246
+ return p;
247
+ }
248
+ async _timeTravel(params) {
249
+ if (!params.step || Array.isArray(params.step) && params.step?.length === 0) {
250
+ throw new Error("Step is required and must be a valid step or array of steps");
251
+ }
252
+ let steps = [];
253
+ if (typeof params.step === "string") {
254
+ steps = params.step.split(".");
255
+ } else {
256
+ steps = (Array.isArray(params.step) ? params.step : [params.step]).map(
257
+ (step) => typeof step === "string" ? step : step?.id
258
+ );
259
+ }
260
+ if (steps.length === 0) {
261
+ throw new Error("No steps provided to timeTravel");
262
+ }
263
+ const storage = this.#mastra?.getStorage();
264
+ const snapshot = await storage?.loadWorkflowSnapshot({
265
+ workflowName: this.workflowId,
266
+ runId: this.runId
267
+ });
268
+ if (!snapshot) {
269
+ await storage?.persistWorkflowSnapshot({
270
+ workflowName: this.workflowId,
271
+ runId: this.runId,
272
+ resourceId: this.resourceId,
273
+ snapshot: {
274
+ runId: this.runId,
275
+ serializedStepGraph: this.serializedStepGraph,
276
+ status: "pending",
277
+ value: {},
278
+ context: {},
279
+ activePaths: [],
280
+ suspendedPaths: {},
281
+ activeStepsPath: {},
282
+ resumeLabels: {},
283
+ waitingPaths: {},
284
+ timestamp: Date.now()
285
+ }
286
+ });
287
+ }
288
+ if (snapshot?.status === "running") {
289
+ throw new Error("This workflow run is still running, cannot time travel");
290
+ }
291
+ let inputDataToUse = params.inputData;
292
+ if (inputDataToUse && steps.length === 1) {
293
+ inputDataToUse = await this._validateTimetravelInputData(params.inputData, this.workflowSteps[steps[0]]);
294
+ }
295
+ const timeTravelData = createTimeTravelExecutionParams({
296
+ steps,
297
+ inputData: inputDataToUse,
298
+ resumeData: params.resumeData,
299
+ context: params.context,
300
+ nestedStepsContext: params.nestedStepsContext,
301
+ snapshot: snapshot ?? { context: {} },
302
+ graph: this.executionGraph,
303
+ initialState: params.initialState
304
+ });
305
+ const eventOutput = await this.inngest.send({
306
+ name: `workflow.${this.workflowId}`,
307
+ data: {
308
+ initialState: timeTravelData.state,
309
+ runId: this.runId,
310
+ workflowId: this.workflowId,
311
+ stepResults: timeTravelData.stepResults,
312
+ timeTravel: timeTravelData,
313
+ tracingOptions: params.tracingOptions,
314
+ outputOptions: params.outputOptions
315
+ }
316
+ });
317
+ const eventId = eventOutput.ids[0];
318
+ if (!eventId) {
319
+ throw new Error("Event ID is not set");
320
+ }
321
+ const runOutput = await this.getRunOutput(eventId);
322
+ const result = runOutput?.output?.result;
323
+ if (result.status === "failed") {
324
+ result.error = new Error(result.error);
325
+ }
326
+ return result;
327
+ }
328
+ streamLegacy({ inputData, runtimeContext } = {}) {
215
329
  const { readable, writable } = new TransformStream();
216
330
  const writer = writable.getWriter();
217
331
  const unwatch = this.watch(async (event) => {
@@ -246,6 +360,126 @@ var InngestRun = class extends Run {
246
360
  getWorkflowState: () => this.executionResults
247
361
  };
248
362
  }
363
+ stream({
364
+ inputData,
365
+ runtimeContext,
366
+ closeOnSuspend = true
367
+ } = {}) {
368
+ const self = this;
369
+ let streamOutput;
370
+ const stream = new ReadableStream({
371
+ async start(controller) {
372
+ const unwatch = self.watch(async ({ type, from = ChunkFrom.WORKFLOW, payload }) => {
373
+ controller.enqueue({
374
+ type,
375
+ runId: self.runId,
376
+ from,
377
+ payload: {
378
+ stepName: payload.id,
379
+ ...payload
380
+ }
381
+ });
382
+ }, "watch-v2");
383
+ self.closeStreamAction = async () => {
384
+ unwatch();
385
+ try {
386
+ await controller.close();
387
+ } catch (err) {
388
+ console.error("Error closing stream:", err);
389
+ }
390
+ };
391
+ const executionResultsPromise = self.start({
392
+ inputData,
393
+ runtimeContext
394
+ });
395
+ const executionResults = await executionResultsPromise;
396
+ if (closeOnSuspend) {
397
+ self.closeStreamAction?.().catch(() => {
398
+ });
399
+ } else if (executionResults.status !== "suspended") {
400
+ self.closeStreamAction?.().catch(() => {
401
+ });
402
+ }
403
+ if (streamOutput) {
404
+ streamOutput.updateResults(executionResults);
405
+ }
406
+ }
407
+ });
408
+ streamOutput = new WorkflowRunOutput({
409
+ runId: this.runId,
410
+ workflowId: this.workflowId,
411
+ stream
412
+ });
413
+ return streamOutput;
414
+ }
415
+ timeTravelStream({
416
+ inputData,
417
+ resumeData,
418
+ initialState,
419
+ step,
420
+ context,
421
+ nestedStepsContext,
422
+ runtimeContext,
423
+ tracingOptions,
424
+ outputOptions
425
+ }) {
426
+ const self = this;
427
+ let streamOutput;
428
+ const stream = new ReadableStream({
429
+ async start(controller) {
430
+ const unwatch = self.watch(async ({ type, from = ChunkFrom.WORKFLOW, payload }) => {
431
+ controller.enqueue({
432
+ type,
433
+ runId: self.runId,
434
+ from,
435
+ payload: {
436
+ stepName: payload?.id,
437
+ ...payload
438
+ }
439
+ });
440
+ }, "watch-v2");
441
+ self.closeStreamAction = async () => {
442
+ unwatch();
443
+ try {
444
+ await controller.close();
445
+ } catch (err) {
446
+ console.error("Error closing stream:", err);
447
+ }
448
+ };
449
+ const executionResultsPromise = self._timeTravel({
450
+ inputData,
451
+ step,
452
+ context,
453
+ nestedStepsContext,
454
+ resumeData,
455
+ initialState,
456
+ runtimeContext,
457
+ tracingOptions,
458
+ outputOptions
459
+ });
460
+ self.executionResults = executionResultsPromise;
461
+ let executionResults;
462
+ try {
463
+ executionResults = await executionResultsPromise;
464
+ self.closeStreamAction?.().catch(() => {
465
+ });
466
+ if (streamOutput) {
467
+ streamOutput.updateResults(executionResults);
468
+ }
469
+ } catch (err) {
470
+ streamOutput?.rejectResults(err);
471
+ self.closeStreamAction?.().catch(() => {
472
+ });
473
+ }
474
+ }
475
+ });
476
+ streamOutput = new WorkflowRunOutput({
477
+ runId: this.runId,
478
+ workflowId: this.workflowId,
479
+ stream
480
+ });
481
+ return streamOutput;
482
+ }
249
483
  };
250
484
  var InngestWorkflow = class _InngestWorkflow extends Workflow {
251
485
  #mastra;
@@ -255,6 +489,7 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
255
489
  constructor(params, inngest) {
256
490
  const { concurrency, rateLimit, throttle, debounce, priority, ...workflowParams } = params;
257
491
  super(workflowParams);
492
+ this.engineType = "inngest";
258
493
  const flowControlEntries = Object.entries({ concurrency, rateLimit, throttle, debounce, priority }).filter(
259
494
  ([_, value]) => value !== void 0
260
495
  );
@@ -319,13 +554,19 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
319
554
  mastra: this.#mastra,
320
555
  retryConfig: this.retryConfig,
321
556
  cleanup: () => this.runs.delete(runIdToUse),
322
- workflowSteps: this.steps
557
+ workflowSteps: this.steps,
558
+ workflowEngineType: this.engineType,
559
+ validateInputs: this.options.validateInputs
323
560
  },
324
561
  this.inngest
325
562
  );
326
563
  this.runs.set(runIdToUse, run);
564
+ const shouldPersistSnapshot = this.options.shouldPersistSnapshot({
565
+ workflowStatus: run.workflowRunStatus,
566
+ stepResults: {}
567
+ });
327
568
  const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse, false);
328
- if (!workflowSnapshotInStorage) {
569
+ if (!workflowSnapshotInStorage && shouldPersistSnapshot) {
329
570
  await this.mastra?.getStorage()?.persistWorkflowSnapshot({
330
571
  workflowName: this.id,
331
572
  runId: runIdToUse,
@@ -336,9 +577,11 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
336
577
  value: {},
337
578
  context: {},
338
579
  activePaths: [],
580
+ activeStepsPath: {},
339
581
  waitingPaths: {},
340
582
  serializedStepGraph: this.serializedStepGraph,
341
583
  suspendedPaths: {},
584
+ resumeLabels: {},
342
585
  result: void 0,
343
586
  error: void 0,
344
587
  // @ts-ignore
@@ -363,7 +606,7 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
363
606
  },
364
607
  { event: `workflow.${this.id}` },
365
608
  async ({ event, step, attempt, publish }) => {
366
- let { inputData, runId, resourceId, resume } = event.data;
609
+ let { inputData, initialState, runId, resourceId, resume, outputOptions, timeTravel } = event.data;
367
610
  if (!runId) {
368
611
  runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
369
612
  return randomUUID();
@@ -391,7 +634,7 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
391
634
  once: (_event, _callback) => {
392
635
  }
393
636
  };
394
- const engine = new InngestExecutionEngine(this.#mastra, step, attempt);
637
+ const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
395
638
  const result = await engine.execute({
396
639
  workflowId: this.id,
397
640
  runId,
@@ -399,14 +642,25 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
399
642
  graph: this.executionGraph,
400
643
  serializedStepGraph: this.serializedStepGraph,
401
644
  input: inputData,
645
+ initialState,
402
646
  emitter,
403
647
  retryConfig: this.retryConfig,
404
648
  runtimeContext: new RuntimeContext(),
405
649
  // TODO
406
650
  resume,
651
+ timeTravel,
407
652
  abortController: new AbortController(),
408
- currentSpan: void 0
653
+ currentSpan: void 0,
409
654
  // TODO: Pass actual parent AI span from workflow execution context
655
+ outputOptions
656
+ });
657
+ await step.run(`workflow.${this.id}.finalize`, async () => {
658
+ if (result.status === "failed") {
659
+ throw new NonRetriableError(`Workflow failed`, {
660
+ cause: result
661
+ });
662
+ }
663
+ return result;
410
664
  });
411
665
  return { result, runId };
412
666
  }
@@ -459,28 +713,55 @@ function createStep(params) {
459
713
  name: params.name,
460
714
  args: inputData
461
715
  };
462
- const { fullStream } = await params.stream(inputData.prompt, {
463
- runtimeContext,
464
- tracingContext,
465
- onFinish: (result) => {
466
- streamPromise.resolve(result.text);
467
- },
468
- abortSignal
469
- });
470
- if (abortSignal.aborted) {
471
- return abort();
472
- }
473
- await emitter.emit("watch-v2", {
474
- type: "tool-call-streaming-start",
475
- ...toolData ?? {}
476
- });
477
- for await (const chunk of fullStream) {
478
- if (chunk.type === "text-delta") {
479
- await emitter.emit("watch-v2", {
480
- type: "tool-call-delta",
481
- ...toolData ?? {},
482
- argsTextDelta: chunk.textDelta
483
- });
716
+ if ((await params.getLLM()).getModel().specificationVersion === `v2`) {
717
+ const { fullStream } = await params.stream(inputData.prompt, {
718
+ runtimeContext,
719
+ tracingContext,
720
+ onFinish: (result) => {
721
+ streamPromise.resolve(result.text);
722
+ },
723
+ abortSignal
724
+ });
725
+ if (abortSignal.aborted) {
726
+ return abort();
727
+ }
728
+ await emitter.emit("watch-v2", {
729
+ type: "tool-call-streaming-start",
730
+ ...toolData ?? {}
731
+ });
732
+ for await (const chunk of fullStream) {
733
+ if (chunk.type === "text-delta") {
734
+ await emitter.emit("watch-v2", {
735
+ type: "tool-call-delta",
736
+ ...toolData ?? {},
737
+ argsTextDelta: chunk.payload.text
738
+ });
739
+ }
740
+ }
741
+ } else {
742
+ const { fullStream } = await params.streamLegacy(inputData.prompt, {
743
+ runtimeContext,
744
+ tracingContext,
745
+ onFinish: (result) => {
746
+ streamPromise.resolve(result.text);
747
+ },
748
+ abortSignal
749
+ });
750
+ if (abortSignal.aborted) {
751
+ return abort();
752
+ }
753
+ await emitter.emit("watch-v2", {
754
+ type: "tool-call-streaming-start",
755
+ ...toolData ?? {}
756
+ });
757
+ for await (const chunk of fullStream) {
758
+ if (chunk.type === "text-delta") {
759
+ await emitter.emit("watch-v2", {
760
+ type: "tool-call-delta",
761
+ ...toolData ?? {},
762
+ argsTextDelta: chunk.textDelta
763
+ });
764
+ }
484
765
  }
485
766
  }
486
767
  await emitter.emit("watch-v2", {
@@ -531,7 +812,10 @@ function createStep(params) {
531
812
  function init(inngest) {
532
813
  return {
533
814
  createWorkflow(params) {
534
- return new InngestWorkflow(params, inngest);
815
+ return new InngestWorkflow(
816
+ params,
817
+ inngest
818
+ );
535
819
  },
536
820
  createStep,
537
821
  cloneStep(step, opts) {
@@ -540,6 +824,9 @@ function init(inngest) {
540
824
  description: step.description,
541
825
  inputSchema: step.inputSchema,
542
826
  outputSchema: step.outputSchema,
827
+ resumeSchema: step.resumeSchema,
828
+ suspendSchema: step.suspendSchema,
829
+ stateSchema: step.stateSchema,
543
830
  execute: step.execute,
544
831
  component: step.component
545
832
  };
@@ -625,7 +912,7 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
625
912
  });
626
913
  const suspendedStepIds = Object.entries(stepResults).flatMap(([stepId, stepResult]) => {
627
914
  if (stepResult?.status === "suspended") {
628
- const nestedPath = stepResult?.payload?.__workflow_meta?.path;
915
+ const nestedPath = stepResult?.suspendPayload?.__workflow_meta?.path;
629
916
  return nestedPath ? [[stepId, ...nestedPath]] : [[stepId]];
630
917
  }
631
918
  return [];
@@ -670,6 +957,10 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
670
957
  mastra: this.mastra,
671
958
  runtimeContext,
672
959
  inputData: prevOutput,
960
+ state: executionContext.state,
961
+ setState: (state) => {
962
+ executionContext.state = state;
963
+ },
673
964
  runCount: -1,
674
965
  tracingContext: {
675
966
  currentSpan: sleepSpan
@@ -747,6 +1038,10 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
747
1038
  mastra: this.mastra,
748
1039
  runtimeContext,
749
1040
  inputData: prevOutput,
1041
+ state: executionContext.state,
1042
+ setState: (state) => {
1043
+ executionContext.state = state;
1044
+ },
750
1045
  runCount: -1,
751
1046
  tracingContext: {
752
1047
  currentSpan: sleepUntilSpan
@@ -814,6 +1109,7 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
814
1109
  stepResults,
815
1110
  executionContext,
816
1111
  resume,
1112
+ timeTravel,
817
1113
  prevOutput,
818
1114
  emitter,
819
1115
  abortController,
@@ -877,38 +1173,87 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
877
1173
  const isResume = !!resume?.steps?.length;
878
1174
  let result;
879
1175
  let runId;
880
- if (isResume) {
881
- runId = stepResults[resume?.steps?.[0]]?.payload?.__workflow_meta?.runId ?? randomUUID();
882
- const snapshot = await this.mastra?.getStorage()?.loadWorkflowSnapshot({
883
- workflowName: step.id,
884
- runId
885
- });
886
- const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
887
- function: step.getFunction(),
888
- data: {
889
- inputData,
890
- runId,
891
- resume: {
1176
+ const isTimeTravel = !!(timeTravel && timeTravel.steps?.length > 1 && timeTravel.steps[0] === step.id);
1177
+ try {
1178
+ if (isResume) {
1179
+ runId = stepResults[resume?.steps?.[0]]?.suspendPayload?.__workflow_meta?.runId ?? randomUUID();
1180
+ const snapshot = await this.mastra?.getStorage()?.loadWorkflowSnapshot({
1181
+ workflowName: step.id,
1182
+ runId
1183
+ });
1184
+ const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
1185
+ function: step.getFunction(),
1186
+ data: {
1187
+ inputData,
1188
+ initialState: executionContext.state ?? snapshot?.value ?? {},
892
1189
  runId,
893
- steps: resume.steps.slice(1),
894
- stepResults: snapshot?.context,
895
- resumePayload: resume.resumePayload,
896
- // @ts-ignore
897
- resumePath: snapshot?.suspendedPaths?.[resume.steps?.[1]]
1190
+ resume: {
1191
+ runId,
1192
+ steps: resume.steps.slice(1),
1193
+ stepResults: snapshot?.context,
1194
+ resumePayload: resume.resumePayload,
1195
+ // @ts-ignore
1196
+ resumePath: snapshot?.suspendedPaths?.[resume.steps?.[1]]
1197
+ },
1198
+ outputOptions: { includeState: true }
898
1199
  }
899
- }
900
- });
901
- result = invokeResp.result;
902
- runId = invokeResp.runId;
903
- } else {
904
- const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
905
- function: step.getFunction(),
906
- data: {
907
- inputData
908
- }
909
- });
910
- result = invokeResp.result;
911
- runId = invokeResp.runId;
1200
+ });
1201
+ result = invokeResp.result;
1202
+ runId = invokeResp.runId;
1203
+ executionContext.state = invokeResp.result.state;
1204
+ } else if (isTimeTravel) {
1205
+ const snapshot = await this.mastra?.getStorage()?.loadWorkflowSnapshot({
1206
+ workflowName: step.id,
1207
+ runId: executionContext.runId
1208
+ }) ?? { context: {} };
1209
+ const timeTravelParams = createTimeTravelExecutionParams({
1210
+ steps: timeTravel.steps.slice(1),
1211
+ inputData: timeTravel.inputData,
1212
+ resumeData: timeTravel.resumeData,
1213
+ context: timeTravel.nestedStepResults?.[step.id] ?? {},
1214
+ nestedStepsContext: timeTravel.nestedStepResults ?? {},
1215
+ snapshot,
1216
+ graph: step.buildExecutionGraph()
1217
+ });
1218
+ const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
1219
+ function: step.getFunction(),
1220
+ data: {
1221
+ timeTravel: timeTravelParams,
1222
+ initialState: executionContext.state ?? {},
1223
+ runId: executionContext.runId,
1224
+ outputOptions: { includeState: true }
1225
+ }
1226
+ });
1227
+ result = invokeResp.result;
1228
+ runId = invokeResp.runId;
1229
+ executionContext.state = invokeResp.result.state;
1230
+ } else {
1231
+ const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
1232
+ function: step.getFunction(),
1233
+ data: {
1234
+ inputData,
1235
+ initialState: executionContext.state ?? {},
1236
+ outputOptions: { includeState: true }
1237
+ }
1238
+ });
1239
+ result = invokeResp.result;
1240
+ runId = invokeResp.runId;
1241
+ executionContext.state = invokeResp.result.state;
1242
+ }
1243
+ } catch (e) {
1244
+ const errorCause = e?.cause;
1245
+ if (errorCause && typeof errorCause === "object") {
1246
+ result = errorCause;
1247
+ runId = errorCause.runId || randomUUID();
1248
+ } else {
1249
+ runId = randomUUID();
1250
+ result = {
1251
+ status: "failed",
1252
+ error: e instanceof Error ? e : new Error(String(e)),
1253
+ steps: {},
1254
+ input: inputData
1255
+ };
1256
+ }
912
1257
  }
913
1258
  const res = await this.inngestStep.run(
914
1259
  `workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
@@ -942,12 +1287,12 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
942
1287
  });
943
1288
  return { executionContext, result: { status: "failed", error: result?.error } };
944
1289
  } else if (result.status === "suspended") {
945
- const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
946
- const stepRes2 = stepResult;
1290
+ const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult2]) => {
1291
+ const stepRes2 = stepResult2;
947
1292
  return stepRes2?.status === "suspended";
948
1293
  });
949
- for (const [stepName, stepResult] of suspendedSteps) {
950
- const suspendPath = [stepName, ...stepResult?.payload?.__workflow_meta?.path ?? []];
1294
+ for (const [stepName, stepResult2] of suspendedSteps) {
1295
+ const suspendPath = [stepName, ...stepResult2?.suspendPayload?.__workflow_meta?.path ?? []];
951
1296
  executionContext.suspendedPaths[step.id] = executionContext.executionPath;
952
1297
  await emitter.emit("watch", {
953
1298
  type: "watch",
@@ -955,7 +1300,11 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
955
1300
  currentStep: {
956
1301
  id: step.id,
957
1302
  status: "suspended",
958
- payload: { ...stepResult?.payload, __workflow_meta: { runId, path: suspendPath } }
1303
+ payload: stepResult2.payload,
1304
+ suspendPayload: {
1305
+ ...stepResult2?.suspendPayload,
1306
+ __workflow_meta: { runId, path: suspendPath }
1307
+ }
959
1308
  },
960
1309
  workflowState: {
961
1310
  status: "running",
@@ -977,7 +1326,11 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
977
1326
  executionContext,
978
1327
  result: {
979
1328
  status: "suspended",
980
- payload: { ...stepResult?.payload, __workflow_meta: { runId, path: suspendPath } }
1329
+ payload: stepResult2.payload,
1330
+ suspendPayload: {
1331
+ ...stepResult2?.suspendPayload,
1332
+ __workflow_meta: { runId, path: suspendPath }
1333
+ }
981
1334
  }
982
1335
  };
983
1336
  }
@@ -1042,132 +1395,206 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1042
1395
  }
1043
1396
  );
1044
1397
  Object.assign(executionContext, res.executionContext);
1045
- return res.result;
1398
+ const stepResult = {
1399
+ ...res.result,
1400
+ startedAt,
1401
+ endedAt: Date.now(),
1402
+ payload: inputData,
1403
+ resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1404
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1405
+ };
1406
+ return { result: stepResult, executionContextState: executionContext.state };
1046
1407
  }
1047
- const stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
1048
- let execResults;
1049
- let suspended;
1050
- let bailed;
1051
- try {
1052
- if (validationError) {
1053
- throw validationError;
1408
+ let stepRes;
1409
+ try {
1410
+ stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
1411
+ let execResults;
1412
+ let suspended;
1413
+ let bailed;
1414
+ const { resumeData: timeTravelResumeData, validationError: timeTravelResumeValidationError } = await validateStepResumeData({
1415
+ resumeData: timeTravel?.stepResults[step.id]?.status === "suspended" ? timeTravel?.resumeData : void 0,
1416
+ step
1417
+ });
1418
+ let resumeDataToUse;
1419
+ if (timeTravelResumeData && !timeTravelResumeValidationError) {
1420
+ resumeDataToUse = timeTravelResumeData;
1421
+ } else if (timeTravelResumeData && timeTravelResumeValidationError) {
1422
+ this.logger.warn("Time travel resume data validation failed", {
1423
+ stepId: step.id,
1424
+ error: timeTravelResumeValidationError.message
1425
+ });
1426
+ } else if (resume?.steps[0] === step.id) {
1427
+ resumeDataToUse = resume?.resumePayload;
1054
1428
  }
1055
- const result = await step.execute({
1056
- runId: executionContext.runId,
1057
- mastra: this.mastra,
1058
- runtimeContext,
1059
- writableStream,
1060
- inputData,
1061
- resumeData: resume?.steps[0] === step.id ? resume?.resumePayload : void 0,
1062
- tracingContext: {
1063
- currentSpan: stepAISpan
1064
- },
1065
- getInitData: () => stepResults?.input,
1066
- getStepResult: getStepResult.bind(this, stepResults),
1067
- suspend: async (suspendPayload) => {
1068
- executionContext.suspendedPaths[step.id] = executionContext.executionPath;
1069
- suspended = { payload: suspendPayload };
1070
- },
1071
- bail: (result2) => {
1072
- bailed = { payload: result2 };
1073
- },
1074
- resume: {
1075
- steps: resume?.steps?.slice(1) || [],
1076
- resumePayload: resume?.resumePayload,
1077
- // @ts-ignore
1078
- runId: stepResults[step.id]?.payload?.__workflow_meta?.runId
1079
- },
1080
- [EMITTER_SYMBOL]: emitter,
1081
- engine: {
1082
- step: this.inngestStep
1429
+ try {
1430
+ if (validationError) {
1431
+ throw validationError;
1432
+ }
1433
+ const result = await step.execute({
1434
+ runId: executionContext.runId,
1435
+ mastra: this.mastra,
1436
+ runtimeContext,
1437
+ writableStream,
1438
+ state: executionContext?.state ?? {},
1439
+ setState: (state) => {
1440
+ executionContext.state = state;
1441
+ },
1442
+ inputData,
1443
+ resumeData: resumeDataToUse,
1444
+ tracingContext: {
1445
+ currentSpan: stepAISpan
1446
+ },
1447
+ getInitData: () => stepResults?.input,
1448
+ getStepResult: getStepResult.bind(this, stepResults),
1449
+ suspend: async (suspendPayload, suspendOptions) => {
1450
+ executionContext.suspendedPaths[step.id] = executionContext.executionPath;
1451
+ if (suspendOptions?.resumeLabel) {
1452
+ const resumeLabel = Array.isArray(suspendOptions.resumeLabel) ? suspendOptions.resumeLabel : [suspendOptions.resumeLabel];
1453
+ for (const label of resumeLabel) {
1454
+ executionContext.resumeLabels[label] = {
1455
+ stepId: step.id,
1456
+ foreachIndex: executionContext.foreachIndex
1457
+ };
1458
+ }
1459
+ }
1460
+ suspended = { payload: suspendPayload };
1461
+ },
1462
+ bail: (result2) => {
1463
+ bailed = { payload: result2 };
1464
+ },
1465
+ resume: {
1466
+ steps: resume?.steps?.slice(1) || [],
1467
+ resumePayload: resume?.resumePayload,
1468
+ // @ts-ignore
1469
+ runId: stepResults[step.id]?.suspendPayload?.__workflow_meta?.runId
1470
+ },
1471
+ [EMITTER_SYMBOL]: emitter,
1472
+ engine: {
1473
+ step: this.inngestStep
1474
+ },
1475
+ abortSignal: abortController.signal
1476
+ });
1477
+ const endedAt = Date.now();
1478
+ execResults = {
1479
+ status: "success",
1480
+ output: result,
1481
+ startedAt,
1482
+ endedAt,
1483
+ payload: inputData,
1484
+ resumedAt: resumeDataToUse ? startedAt : void 0,
1485
+ resumePayload: resumeDataToUse
1486
+ };
1487
+ } catch (e) {
1488
+ const stepFailure = {
1489
+ status: "failed",
1490
+ payload: inputData,
1491
+ error: e instanceof Error ? e.stack ?? e.message : String(e),
1492
+ endedAt: Date.now(),
1493
+ startedAt,
1494
+ resumedAt: resumeDataToUse ? startedAt : void 0,
1495
+ resumePayload: resumeDataToUse
1496
+ };
1497
+ execResults = stepFailure;
1498
+ const fallbackErrorMessage = `Step ${step.id} failed`;
1499
+ stepAISpan?.error({ error: new Error(execResults.error ?? fallbackErrorMessage) });
1500
+ throw new RetryAfterError(execResults.error ?? fallbackErrorMessage, executionContext.retryConfig.delay, {
1501
+ cause: execResults
1502
+ });
1503
+ }
1504
+ if (suspended) {
1505
+ execResults = {
1506
+ status: "suspended",
1507
+ suspendPayload: suspended.payload,
1508
+ payload: inputData,
1509
+ suspendedAt: Date.now(),
1510
+ startedAt,
1511
+ resumedAt: resumeDataToUse ? startedAt : void 0,
1512
+ resumePayload: resumeDataToUse
1513
+ };
1514
+ } else if (bailed) {
1515
+ execResults = {
1516
+ status: "bailed",
1517
+ output: bailed.payload,
1518
+ payload: inputData,
1519
+ endedAt: Date.now(),
1520
+ startedAt
1521
+ };
1522
+ }
1523
+ await emitter.emit("watch", {
1524
+ type: "watch",
1525
+ payload: {
1526
+ currentStep: {
1527
+ id: step.id,
1528
+ ...execResults
1529
+ },
1530
+ workflowState: {
1531
+ status: "running",
1532
+ steps: { ...stepResults, [step.id]: execResults },
1533
+ result: null,
1534
+ error: null
1535
+ }
1083
1536
  },
1084
- abortSignal: abortController.signal
1537
+ eventTimestamp: Date.now()
1085
1538
  });
1086
- const endedAt = Date.now();
1087
- execResults = {
1088
- status: "success",
1089
- output: result,
1090
- startedAt,
1091
- endedAt,
1092
- payload: inputData,
1093
- resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1094
- resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1095
- };
1096
- } catch (e) {
1097
- execResults = {
1098
- status: "failed",
1099
- payload: inputData,
1100
- error: e instanceof Error ? e.message : String(e),
1101
- endedAt: Date.now(),
1102
- startedAt,
1103
- resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1104
- resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1105
- };
1106
- }
1107
- if (suspended) {
1108
- execResults = {
1109
- status: "suspended",
1110
- suspendedPayload: suspended.payload,
1111
- payload: inputData,
1112
- suspendedAt: Date.now(),
1113
- startedAt,
1114
- resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1115
- resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1116
- };
1117
- } else if (bailed) {
1118
- execResults = { status: "bailed", output: bailed.payload, payload: inputData, endedAt: Date.now(), startedAt };
1119
- }
1120
- if (execResults.status === "failed") {
1121
- if (executionContext.retryConfig.attempts > 0 && this.inngestAttempts < executionContext.retryConfig.attempts) {
1122
- const error = new Error(execResults.error);
1123
- stepAISpan?.error({ error });
1124
- throw error;
1539
+ if (execResults.status === "suspended") {
1540
+ await emitter.emit("watch-v2", {
1541
+ type: "workflow-step-suspended",
1542
+ payload: {
1543
+ id: step.id,
1544
+ ...execResults
1545
+ }
1546
+ });
1547
+ } else {
1548
+ await emitter.emit("watch-v2", {
1549
+ type: "workflow-step-result",
1550
+ payload: {
1551
+ id: step.id,
1552
+ ...execResults
1553
+ }
1554
+ });
1555
+ await emitter.emit("watch-v2", {
1556
+ type: "workflow-step-finish",
1557
+ payload: {
1558
+ id: step.id,
1559
+ metadata: {}
1560
+ }
1561
+ });
1125
1562
  }
1126
- }
1127
- await emitter.emit("watch", {
1128
- type: "watch",
1563
+ stepAISpan?.end({ output: execResults });
1564
+ return { result: execResults, executionContext, stepResults };
1565
+ });
1566
+ } catch (e) {
1567
+ const stepFailure = e instanceof Error ? e?.cause : {
1568
+ status: "failed",
1569
+ error: e instanceof Error ? e.stack ?? e.message : String(e),
1570
+ payload: inputData,
1571
+ startedAt,
1572
+ endedAt: Date.now()
1573
+ };
1574
+ await emitter.emit("watch-v2", {
1575
+ type: "workflow-step-result",
1129
1576
  payload: {
1130
- currentStep: {
1131
- id: step.id,
1132
- ...execResults
1133
- },
1134
- workflowState: {
1135
- status: "running",
1136
- steps: { ...stepResults, [step.id]: execResults },
1137
- result: null,
1138
- error: null
1139
- }
1140
- },
1141
- eventTimestamp: Date.now()
1577
+ id: step.id,
1578
+ ...stepFailure
1579
+ }
1142
1580
  });
1143
- if (execResults.status === "suspended") {
1144
- await emitter.emit("watch-v2", {
1145
- type: "workflow-step-suspended",
1146
- payload: {
1147
- id: step.id,
1148
- ...execResults
1149
- }
1150
- });
1151
- } else {
1152
- await emitter.emit("watch-v2", {
1153
- type: "workflow-step-result",
1154
- payload: {
1155
- id: step.id,
1156
- ...execResults
1157
- }
1158
- });
1159
- await emitter.emit("watch-v2", {
1160
- type: "workflow-step-finish",
1161
- payload: {
1162
- id: step.id,
1163
- metadata: {}
1164
- }
1165
- });
1166
- }
1167
- stepAISpan?.end({ output: execResults });
1168
- return { result: execResults, executionContext, stepResults };
1169
- });
1170
- if (disableScorers !== false) {
1581
+ await emitter.emit("watch-v2", {
1582
+ type: "workflow-step-finish",
1583
+ payload: {
1584
+ id: step.id,
1585
+ metadata: {}
1586
+ }
1587
+ });
1588
+ stepRes = {
1589
+ result: stepFailure,
1590
+ executionContext,
1591
+ stepResults: {
1592
+ ...stepResults,
1593
+ [step.id]: stepFailure
1594
+ }
1595
+ };
1596
+ }
1597
+ if (disableScorers !== false && stepRes.result.status === "success") {
1171
1598
  await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}.score`, async () => {
1172
1599
  if (step.scorers) {
1173
1600
  await this.runScorers({
@@ -1185,8 +1612,11 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1185
1612
  });
1186
1613
  }
1187
1614
  Object.assign(executionContext.suspendedPaths, stepRes.executionContext.suspendedPaths);
1188
- Object.assign(stepResults, stepRes.stepResults);
1189
- return stepRes.result;
1615
+ executionContext.state = stepRes.executionContext.state;
1616
+ return {
1617
+ result: stepRes.result,
1618
+ executionContextState: stepRes.executionContext.state
1619
+ };
1190
1620
  }
1191
1621
  async persistStepUpdate({
1192
1622
  workflowId,
@@ -1202,19 +1632,25 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1202
1632
  await this.inngestStep.run(
1203
1633
  `workflow.${workflowId}.run.${runId}.path.${JSON.stringify(executionContext.executionPath)}.stepUpdate`,
1204
1634
  async () => {
1635
+ const shouldPersistSnapshot = this.options.shouldPersistSnapshot({ stepResults, workflowStatus });
1636
+ if (!shouldPersistSnapshot) {
1637
+ return;
1638
+ }
1205
1639
  await this.mastra?.getStorage()?.persistWorkflowSnapshot({
1206
1640
  workflowName: workflowId,
1207
1641
  runId,
1208
1642
  resourceId,
1209
1643
  snapshot: {
1210
1644
  runId,
1211
- value: {},
1645
+ status: workflowStatus,
1646
+ value: executionContext.state,
1212
1647
  context: stepResults,
1213
- activePaths: [],
1648
+ activePaths: executionContext.executionPath,
1649
+ activeStepsPath: executionContext.activeStepsPath,
1214
1650
  suspendedPaths: executionContext.suspendedPaths,
1651
+ resumeLabels: executionContext.resumeLabels,
1215
1652
  waitingPaths: {},
1216
1653
  serializedStepGraph,
1217
- status: workflowStatus,
1218
1654
  result,
1219
1655
  error,
1220
1656
  // @ts-ignore
@@ -1229,9 +1665,8 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1229
1665
  runId,
1230
1666
  entry,
1231
1667
  prevOutput,
1232
- prevStep,
1233
1668
  stepResults,
1234
- serializedStepGraph,
1669
+ timeTravel,
1235
1670
  resume,
1236
1671
  executionContext,
1237
1672
  emitter,
@@ -1271,6 +1706,10 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1271
1706
  runtimeContext,
1272
1707
  runCount: -1,
1273
1708
  inputData: prevOutput,
1709
+ state: executionContext.state,
1710
+ setState: (state) => {
1711
+ executionContext.state = state;
1712
+ },
1274
1713
  tracingContext: {
1275
1714
  currentSpan: evalSpan
1276
1715
  },
@@ -1328,22 +1767,27 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1328
1767
  }
1329
1768
  });
1330
1769
  const results = await Promise.all(
1331
- stepsToRun.map(
1332
- (step, index) => this.executeEntry({
1333
- workflowId,
1334
- runId,
1335
- entry: step,
1336
- serializedStepGraph,
1337
- prevStep,
1770
+ stepsToRun.map(async (step, index) => {
1771
+ const currStepResult = stepResults[step.step.id];
1772
+ if (currStepResult && currStepResult.status === "success") {
1773
+ return currStepResult;
1774
+ }
1775
+ const result = await this.executeStep({
1776
+ step: step.step,
1777
+ prevOutput,
1338
1778
  stepResults,
1339
1779
  resume,
1780
+ timeTravel,
1340
1781
  executionContext: {
1341
1782
  workflowId,
1342
1783
  runId,
1343
1784
  executionPath: [...executionContext.executionPath, index],
1785
+ activeStepsPath: executionContext.activeStepsPath,
1344
1786
  suspendedPaths: executionContext.suspendedPaths,
1787
+ resumeLabels: executionContext.resumeLabels,
1345
1788
  retryConfig: executionContext.retryConfig,
1346
- executionSpan: executionContext.executionSpan
1789
+ executionSpan: executionContext.executionSpan,
1790
+ state: executionContext.state
1347
1791
  },
1348
1792
  emitter,
1349
1793
  abortController,
@@ -1353,20 +1797,23 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1353
1797
  tracingContext: {
1354
1798
  currentSpan: conditionalSpan
1355
1799
  }
1356
- })
1357
- )
1800
+ });
1801
+ stepResults[step.step.id] = result.result;
1802
+ executionContext.state = result.executionContextState;
1803
+ return result.result;
1804
+ })
1358
1805
  );
1359
- const hasFailed = results.find((result) => result.result.status === "failed");
1360
- const hasSuspended = results.find((result) => result.result.status === "suspended");
1806
+ const hasFailed = results.find((result) => result.status === "failed");
1807
+ const hasSuspended = results.find((result) => result.status === "suspended");
1361
1808
  if (hasFailed) {
1362
- execResults = { status: "failed", error: hasFailed.result.error };
1809
+ execResults = { status: "failed", error: hasFailed.error };
1363
1810
  } else if (hasSuspended) {
1364
- execResults = { status: "suspended", payload: hasSuspended.result.suspendPayload };
1811
+ execResults = { status: "suspended", suspendPayload: hasSuspended.suspendPayload };
1365
1812
  } else {
1366
1813
  execResults = {
1367
1814
  status: "success",
1368
1815
  output: results.reduce((acc, result, index) => {
1369
- if (result.result.status === "success") {
1816
+ if (result.status === "success") {
1370
1817
  acc[stepsToRun[index].step.id] = result.output;
1371
1818
  }
1372
1819
  return acc;