@mastra/inngest 0.18.5 → 0.18.6

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
@@ -5,7 +5,7 @@ import { wrapMastra, AISpanType } from '@mastra/core/ai-tracing';
5
5
  import { RuntimeContext } from '@mastra/core/di';
6
6
  import { ChunkFrom, WorkflowRunOutput } from '@mastra/core/stream';
7
7
  import { ToolStream, Tool } from '@mastra/core/tools';
8
- import { Run, Workflow, DefaultExecutionEngine, getStepResult, validateStepInput } from '@mastra/core/workflows';
8
+ import { Run, createTimeTravelExecutionParams, Workflow, DefaultExecutionEngine, getStepResult, validateStepInput, validateStepResumeData } from '@mastra/core/workflows';
9
9
  import { EMITTER_SYMBOL, STREAM_FORMAT_SYMBOL } from '@mastra/core/workflows/_constants';
10
10
  import { NonRetriableError, RetryAfterError } from 'inngest';
11
11
  import { serve as serve$1 } from 'inngest/hono';
@@ -103,7 +103,8 @@ var InngestRun = class extends Run {
103
103
  resourceId: this.resourceId,
104
104
  snapshot: {
105
105
  ...snapshot,
106
- status: "canceled"
106
+ status: "canceled",
107
+ value: snapshot.value
107
108
  }
108
109
  });
109
110
  }
@@ -119,14 +120,15 @@ var InngestRun = class extends Run {
119
120
  snapshot: {
120
121
  runId: this.runId,
121
122
  serializedStepGraph: this.serializedStepGraph,
123
+ status: "running",
122
124
  value: {},
123
125
  context: {},
124
126
  activePaths: [],
125
127
  suspendedPaths: {},
128
+ activeStepsPath: {},
126
129
  resumeLabels: {},
127
130
  waitingPaths: {},
128
- timestamp: Date.now(),
129
- status: "running"
131
+ timestamp: Date.now()
130
132
  }
131
133
  });
132
134
  const inputDataToUse = await this._validateInput(inputData);
@@ -166,10 +168,16 @@ var InngestRun = class extends Run {
166
168
  return p;
167
169
  }
168
170
  async _resume(params) {
169
- const steps = (Array.isArray(params.step) ? params.step : [params.step]).map(
170
- (step) => typeof step === "string" ? step : step?.id
171
- );
172
- 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({
173
181
  workflowName: this.workflowId,
174
182
  runId: this.runId
175
183
  });
@@ -226,6 +234,97 @@ var InngestRun = class extends Run {
226
234
  });
227
235
  };
228
236
  }
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
+ }
229
328
  streamLegacy({ inputData, runtimeContext } = {}) {
230
329
  const { readable, writable } = new TransformStream();
231
330
  const writer = writable.getWriter();
@@ -313,6 +412,74 @@ var InngestRun = class extends Run {
313
412
  });
314
413
  return streamOutput;
315
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
+ }
316
483
  };
317
484
  var InngestWorkflow = class _InngestWorkflow extends Workflow {
318
485
  #mastra;
@@ -322,6 +489,7 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
322
489
  constructor(params, inngest) {
323
490
  const { concurrency, rateLimit, throttle, debounce, priority, ...workflowParams } = params;
324
491
  super(workflowParams);
492
+ this.engineType = "inngest";
325
493
  const flowControlEntries = Object.entries({ concurrency, rateLimit, throttle, debounce, priority }).filter(
326
494
  ([_, value]) => value !== void 0
327
495
  );
@@ -386,7 +554,9 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
386
554
  mastra: this.#mastra,
387
555
  retryConfig: this.retryConfig,
388
556
  cleanup: () => this.runs.delete(runIdToUse),
389
- workflowSteps: this.steps
557
+ workflowSteps: this.steps,
558
+ workflowEngineType: this.engineType,
559
+ validateInputs: this.options.validateInputs
390
560
  },
391
561
  this.inngest
392
562
  );
@@ -407,6 +577,7 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
407
577
  value: {},
408
578
  context: {},
409
579
  activePaths: [],
580
+ activeStepsPath: {},
410
581
  waitingPaths: {},
411
582
  serializedStepGraph: this.serializedStepGraph,
412
583
  suspendedPaths: {},
@@ -435,7 +606,7 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
435
606
  },
436
607
  { event: `workflow.${this.id}` },
437
608
  async ({ event, step, attempt, publish }) => {
438
- let { inputData, initialState, runId, resourceId, resume, outputOptions } = event.data;
609
+ let { inputData, initialState, runId, resourceId, resume, outputOptions, timeTravel } = event.data;
439
610
  if (!runId) {
440
611
  runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
441
612
  return randomUUID();
@@ -477,6 +648,7 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
477
648
  runtimeContext: new RuntimeContext(),
478
649
  // TODO
479
650
  resume,
651
+ timeTravel,
480
652
  abortController: new AbortController(),
481
653
  currentSpan: void 0,
482
654
  // TODO: Pass actual parent AI span from workflow execution context
@@ -937,6 +1109,7 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
937
1109
  stepResults,
938
1110
  executionContext,
939
1111
  resume,
1112
+ timeTravel,
940
1113
  prevOutput,
941
1114
  emitter,
942
1115
  abortController,
@@ -1000,6 +1173,7 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1000
1173
  const isResume = !!resume?.steps?.length;
1001
1174
  let result;
1002
1175
  let runId;
1176
+ const isTimeTravel = !!(timeTravel && timeTravel.steps?.length > 1 && timeTravel.steps[0] === step.id);
1003
1177
  try {
1004
1178
  if (isResume) {
1005
1179
  runId = stepResults[resume?.steps?.[0]]?.suspendPayload?.__workflow_meta?.runId ?? randomUUID();
@@ -1027,6 +1201,32 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1027
1201
  result = invokeResp.result;
1028
1202
  runId = invokeResp.runId;
1029
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;
1030
1230
  } else {
1031
1231
  const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
1032
1232
  function: step.getFunction(),
@@ -1087,12 +1287,12 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1087
1287
  });
1088
1288
  return { executionContext, result: { status: "failed", error: result?.error } };
1089
1289
  } else if (result.status === "suspended") {
1090
- const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
1091
- const stepRes2 = stepResult;
1290
+ const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult2]) => {
1291
+ const stepRes2 = stepResult2;
1092
1292
  return stepRes2?.status === "suspended";
1093
1293
  });
1094
- for (const [stepName, stepResult] of suspendedSteps) {
1095
- const suspendPath = [stepName, ...stepResult?.suspendPayload?.__workflow_meta?.path ?? []];
1294
+ for (const [stepName, stepResult2] of suspendedSteps) {
1295
+ const suspendPath = [stepName, ...stepResult2?.suspendPayload?.__workflow_meta?.path ?? []];
1096
1296
  executionContext.suspendedPaths[step.id] = executionContext.executionPath;
1097
1297
  await emitter.emit("watch", {
1098
1298
  type: "watch",
@@ -1100,9 +1300,9 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1100
1300
  currentStep: {
1101
1301
  id: step.id,
1102
1302
  status: "suspended",
1103
- payload: stepResult.payload,
1303
+ payload: stepResult2.payload,
1104
1304
  suspendPayload: {
1105
- ...stepResult?.suspendPayload,
1305
+ ...stepResult2?.suspendPayload,
1106
1306
  __workflow_meta: { runId, path: suspendPath }
1107
1307
  }
1108
1308
  },
@@ -1126,9 +1326,9 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1126
1326
  executionContext,
1127
1327
  result: {
1128
1328
  status: "suspended",
1129
- payload: stepResult.payload,
1329
+ payload: stepResult2.payload,
1130
1330
  suspendPayload: {
1131
- ...stepResult?.suspendPayload,
1331
+ ...stepResult2?.suspendPayload,
1132
1332
  __workflow_meta: { runId, path: suspendPath }
1133
1333
  }
1134
1334
  }
@@ -1195,7 +1395,7 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1195
1395
  }
1196
1396
  );
1197
1397
  Object.assign(executionContext, res.executionContext);
1198
- return {
1398
+ const stepResult = {
1199
1399
  ...res.result,
1200
1400
  startedAt,
1201
1401
  endedAt: Date.now(),
@@ -1203,6 +1403,7 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1203
1403
  resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1204
1404
  resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1205
1405
  };
1406
+ return { result: stepResult, executionContextState: executionContext.state };
1206
1407
  }
1207
1408
  let stepRes;
1208
1409
  try {
@@ -1210,6 +1411,21 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1210
1411
  let execResults;
1211
1412
  let suspended;
1212
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;
1428
+ }
1213
1429
  try {
1214
1430
  if (validationError) {
1215
1431
  throw validationError;
@@ -1224,7 +1440,7 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1224
1440
  executionContext.state = state;
1225
1441
  },
1226
1442
  inputData,
1227
- resumeData: resume?.steps[0] === step.id ? resume?.resumePayload : void 0,
1443
+ resumeData: resumeDataToUse,
1228
1444
  tracingContext: {
1229
1445
  currentSpan: stepAISpan
1230
1446
  },
@@ -1265,8 +1481,8 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1265
1481
  startedAt,
1266
1482
  endedAt,
1267
1483
  payload: inputData,
1268
- resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1269
- resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1484
+ resumedAt: resumeDataToUse ? startedAt : void 0,
1485
+ resumePayload: resumeDataToUse
1270
1486
  };
1271
1487
  } catch (e) {
1272
1488
  const stepFailure = {
@@ -1275,8 +1491,8 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1275
1491
  error: e instanceof Error ? e.stack ?? e.message : String(e),
1276
1492
  endedAt: Date.now(),
1277
1493
  startedAt,
1278
- resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1279
- resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1494
+ resumedAt: resumeDataToUse ? startedAt : void 0,
1495
+ resumePayload: resumeDataToUse
1280
1496
  };
1281
1497
  execResults = stepFailure;
1282
1498
  const fallbackErrorMessage = `Step ${step.id} failed`;
@@ -1292,8 +1508,8 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1292
1508
  payload: inputData,
1293
1509
  suspendedAt: Date.now(),
1294
1510
  startedAt,
1295
- resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1296
- resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1511
+ resumedAt: resumeDataToUse ? startedAt : void 0,
1512
+ resumePayload: resumeDataToUse
1297
1513
  };
1298
1514
  } else if (bailed) {
1299
1515
  execResults = {
@@ -1396,9 +1612,11 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1396
1612
  });
1397
1613
  }
1398
1614
  Object.assign(executionContext.suspendedPaths, stepRes.executionContext.suspendedPaths);
1399
- Object.assign(stepResults, stepRes.stepResults);
1400
1615
  executionContext.state = stepRes.executionContext.state;
1401
- return stepRes.result;
1616
+ return {
1617
+ result: stepRes.result,
1618
+ executionContextState: stepRes.executionContext.state
1619
+ };
1402
1620
  }
1403
1621
  async persistStepUpdate({
1404
1622
  workflowId,
@@ -1424,14 +1642,15 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1424
1642
  resourceId,
1425
1643
  snapshot: {
1426
1644
  runId,
1645
+ status: workflowStatus,
1427
1646
  value: executionContext.state,
1428
1647
  context: stepResults,
1429
- activePaths: [],
1648
+ activePaths: executionContext.executionPath,
1649
+ activeStepsPath: executionContext.activeStepsPath,
1430
1650
  suspendedPaths: executionContext.suspendedPaths,
1431
1651
  resumeLabels: executionContext.resumeLabels,
1432
1652
  waitingPaths: {},
1433
1653
  serializedStepGraph,
1434
- status: workflowStatus,
1435
1654
  result,
1436
1655
  error,
1437
1656
  // @ts-ignore
@@ -1447,6 +1666,7 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1447
1666
  entry,
1448
1667
  prevOutput,
1449
1668
  stepResults,
1669
+ timeTravel,
1450
1670
  resume,
1451
1671
  executionContext,
1452
1672
  emitter,
@@ -1557,10 +1777,12 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1557
1777
  prevOutput,
1558
1778
  stepResults,
1559
1779
  resume,
1780
+ timeTravel,
1560
1781
  executionContext: {
1561
1782
  workflowId,
1562
1783
  runId,
1563
1784
  executionPath: [...executionContext.executionPath, index],
1785
+ activeStepsPath: executionContext.activeStepsPath,
1564
1786
  suspendedPaths: executionContext.suspendedPaths,
1565
1787
  resumeLabels: executionContext.resumeLabels,
1566
1788
  retryConfig: executionContext.retryConfig,
@@ -1576,8 +1798,9 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
1576
1798
  currentSpan: conditionalSpan
1577
1799
  }
1578
1800
  });
1579
- stepResults[step.step.id] = result;
1580
- return result;
1801
+ stepResults[step.step.id] = result.result;
1802
+ executionContext.state = result.executionContextState;
1803
+ return result.result;
1581
1804
  })
1582
1805
  );
1583
1806
  const hasFailed = results.find((result) => result.status === "failed");