@mastra/inngest 0.0.0-vnext-inngest-20250508131921 → 0.0.0-vnext-20251119160359

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