@mastra/inngest 0.0.0-taofeeqInngest-20250603090617 → 0.0.0-tool-call-parts-20250630193309

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/src/index.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { randomUUID } from 'crypto';
2
+ import type { ReadableStream } from 'node:stream/web';
2
3
  import { subscribe } from '@inngest/realtime';
3
- import type { Mastra, WorkflowRun, WorkflowRuns } from '@mastra/core';
4
+ import type { Agent, Mastra, ToolExecutionContext, WorkflowRun, WorkflowRuns } from '@mastra/core';
4
5
  import { RuntimeContext } from '@mastra/core/di';
5
- import { Workflow, createStep, Run, DefaultExecutionEngine, cloneStep } from '@mastra/core/workflows';
6
+ import { Tool } from '@mastra/core/tools';
7
+ import { Workflow, Run, DefaultExecutionEngine } from '@mastra/core/workflows';
6
8
  import type {
7
9
  ExecuteFunction,
8
10
  ExecutionContext,
@@ -14,12 +16,20 @@ import type {
14
16
  StepResult,
15
17
  WorkflowResult,
16
18
  SerializedStepFlowEntry,
19
+ StepFailure,
20
+ Emitter,
21
+ WatchEvent,
22
+ StreamEvent,
17
23
  } from '@mastra/core/workflows';
18
24
  import { EMITTER_SYMBOL } from '@mastra/core/workflows/_constants';
19
25
  import type { Span } from '@opentelemetry/api';
20
26
  import type { Inngest, BaseContext } from 'inngest';
21
27
  import { serve as inngestServe } from 'inngest/hono';
22
- import type { z } from 'zod';
28
+ import { z } from 'zod';
29
+
30
+ export type InngestEngineType = {
31
+ step: any;
32
+ };
23
33
 
24
34
  export function serve({ mastra, inngest }: { mastra: Mastra; inngest: Inngest }): ReturnType<typeof inngestServe> {
25
35
  const wfs = mastra.getWorkflows();
@@ -37,10 +47,11 @@ export function serve({ mastra, inngest }: { mastra: Mastra; inngest: Inngest })
37
47
  }
38
48
 
39
49
  export class InngestRun<
50
+ TEngineType = InngestEngineType,
40
51
  TSteps extends Step<string, any, any>[] = Step<string, any, any>[],
41
52
  TInput extends z.ZodType<any> = z.ZodType<any>,
42
53
  TOutput extends z.ZodType<any> = z.ZodType<any>,
43
- > extends Run<TSteps, TInput, TOutput> {
54
+ > extends Run<TEngineType, TSteps, TInput, TOutput> {
44
55
  private inngest: Inngest;
45
56
  serializedStepGraph: SerializedStepFlowEntry[];
46
57
  #mastra: Mastra;
@@ -79,7 +90,8 @@ export class InngestRun<
79
90
 
80
91
  async getRunOutput(eventId: string) {
81
92
  let runs = await this.getRuns(eventId);
82
- while (runs?.[0]?.status !== 'Completed') {
93
+
94
+ while (runs?.[0]?.status !== 'Completed' || runs?.[0]?.event_id !== eventId) {
83
95
  await new Promise(resolve => setTimeout(resolve, 1000));
84
96
  runs = await this.getRuns(eventId);
85
97
  if (runs?.[0]?.status === 'Failed' || runs?.[0]?.status === 'Cancelled') {
@@ -89,6 +101,13 @@ export class InngestRun<
89
101
  return runs?.[0];
90
102
  }
91
103
 
104
+ async sendEvent(event: string, data: any) {
105
+ await this.inngest.send({
106
+ name: `user-event-${event}`,
107
+ data,
108
+ });
109
+ }
110
+
92
111
  async start({
93
112
  inputData,
94
113
  }: {
@@ -106,6 +125,7 @@ export class InngestRun<
106
125
  activePaths: [],
107
126
  suspendedPaths: {},
108
127
  timestamp: Date.now(),
128
+ status: 'running',
109
129
  },
110
130
  });
111
131
 
@@ -127,7 +147,9 @@ export class InngestRun<
127
147
  result.error = new Error(result.error);
128
148
  }
129
149
 
130
- this.cleanup?.();
150
+ if (result.status !== 'suspended') {
151
+ this.cleanup?.();
152
+ }
131
153
  return result;
132
154
  }
133
155
 
@@ -139,6 +161,27 @@ export class InngestRun<
139
161
  | string
140
162
  | string[];
141
163
  runtimeContext?: RuntimeContext;
164
+ }): Promise<WorkflowResult<TOutput, TSteps>> {
165
+ const p = this._resume(params).then(result => {
166
+ if (result.status !== 'suspended') {
167
+ this.closeStreamAction?.().catch(() => {});
168
+ }
169
+
170
+ return result;
171
+ });
172
+
173
+ this.executionResults = p;
174
+ return p;
175
+ }
176
+
177
+ async _resume<TResumeSchema extends z.ZodType<any>>(params: {
178
+ resumeData?: z.infer<TResumeSchema>;
179
+ step:
180
+ | Step<string, any, any, TResumeSchema, any>
181
+ | [...Step<string, any, any, any, any>[], Step<string, any, any, TResumeSchema, any>]
182
+ | string
183
+ | string[];
184
+ runtimeContext?: RuntimeContext;
142
185
  }): Promise<WorkflowResult<TOutput, TSteps>> {
143
186
  const steps: string[] = (Array.isArray(params.step) ? params.step : [params.step]).map(step =>
144
187
  typeof step === 'string' ? step : step?.id,
@@ -176,37 +219,82 @@ export class InngestRun<
176
219
  return result;
177
220
  }
178
221
 
179
- watch(cb: (event: any) => void): () => void {
222
+ watch(cb: (event: WatchEvent) => void, type: 'watch' | 'watch-v2' = 'watch'): () => void {
223
+ let active = true;
180
224
  const streamPromise = subscribe(
181
225
  {
182
226
  channel: `workflow:${this.workflowId}:${this.runId}`,
183
- topics: ['watch'],
227
+ topics: [type],
184
228
  app: this.inngest,
185
229
  },
186
230
  (message: any) => {
187
- cb(message.data);
231
+ if (active) {
232
+ cb(message.data);
233
+ }
188
234
  },
189
235
  );
190
236
 
191
237
  return () => {
238
+ active = false;
192
239
  streamPromise
193
- .then((stream: any) => {
194
- stream.cancel();
240
+ .then(async (stream: Awaited<typeof streamPromise>) => {
241
+ return stream.cancel();
195
242
  })
196
243
  .catch(err => {
197
244
  console.error(err);
198
245
  });
199
246
  };
200
247
  }
248
+
249
+ stream({ inputData, runtimeContext }: { inputData?: z.infer<TInput>; runtimeContext?: RuntimeContext } = {}): {
250
+ stream: ReadableStream<StreamEvent>;
251
+ getWorkflowState: () => Promise<WorkflowResult<TOutput, TSteps>>;
252
+ } {
253
+ const { readable, writable } = new TransformStream<StreamEvent, StreamEvent>();
254
+
255
+ const writer = writable.getWriter();
256
+ const unwatch = this.watch(async event => {
257
+ try {
258
+ // watch-v2 events are data stream events, so we need to cast them to the correct type
259
+ await writer.write(event as any);
260
+ } catch {}
261
+ }, 'watch-v2');
262
+
263
+ this.closeStreamAction = async () => {
264
+ unwatch();
265
+
266
+ try {
267
+ await writer.close();
268
+ } catch (err) {
269
+ console.error('Error closing stream:', err);
270
+ } finally {
271
+ writer.releaseLock();
272
+ }
273
+ };
274
+
275
+ this.executionResults = this.start({ inputData, runtimeContext }).then(result => {
276
+ if (result.status !== 'suspended') {
277
+ this.closeStreamAction?.().catch(() => {});
278
+ }
279
+
280
+ return result;
281
+ });
282
+
283
+ return {
284
+ stream: readable as ReadableStream<StreamEvent>,
285
+ getWorkflowState: () => this.executionResults!,
286
+ };
287
+ }
201
288
  }
202
289
 
203
290
  export class InngestWorkflow<
291
+ TEngineType = InngestEngineType,
204
292
  TSteps extends Step<string, any, any>[] = Step<string, any, any>[],
205
293
  TWorkflowId extends string = string,
206
294
  TInput extends z.ZodType<any> = z.ZodType<any>,
207
295
  TOutput extends z.ZodType<any> = z.ZodType<any>,
208
296
  TPrevSchema extends z.ZodType<any> = TInput,
209
- > extends Workflow<TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> {
297
+ > extends Workflow<TEngineType, TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> {
210
298
  #mastra: Mastra;
211
299
  public inngest: Inngest;
212
300
 
@@ -238,7 +326,10 @@ export class InngestWorkflow<
238
326
  const storage = this.#mastra?.getStorage();
239
327
  if (!storage) {
240
328
  this.logger.debug('Cannot get workflow runs. Mastra engine is not initialized');
241
- return null;
329
+ //returning in memory run if no storage is initialized
330
+ return this.runs.get(runId)
331
+ ? ({ ...this.runs.get(runId), workflowName: this.id } as unknown as WorkflowRun)
332
+ : null;
242
333
  }
243
334
  const run = (await storage.getWorkflowRunById({ runId, workflowName: this.id })) as unknown as WorkflowRun;
244
335
 
@@ -248,6 +339,32 @@ export class InngestWorkflow<
248
339
  );
249
340
  }
250
341
 
342
+ async getWorkflowRunExecutionResult(runId: string): Promise<WatchEvent['payload']['workflowState'] | null> {
343
+ const storage = this.#mastra?.getStorage();
344
+ if (!storage) {
345
+ this.logger.debug('Cannot get workflow run execution result. Mastra storage is not initialized');
346
+ return null;
347
+ }
348
+
349
+ const run = await storage.getWorkflowRunById({ runId, workflowName: this.id });
350
+
351
+ if (!run?.snapshot) {
352
+ return null;
353
+ }
354
+
355
+ if (typeof run.snapshot === 'string') {
356
+ return null;
357
+ }
358
+
359
+ return {
360
+ status: run.snapshot.status,
361
+ result: run.snapshot.result,
362
+ error: run.snapshot.error,
363
+ payload: run.snapshot.context?.input,
364
+ steps: run.snapshot.context as any,
365
+ };
366
+ }
367
+
251
368
  __registerMastra(mastra: Mastra) {
252
369
  this.#mastra = mastra;
253
370
  this.executionEngine.__registerMastra(mastra);
@@ -271,11 +388,35 @@ export class InngestWorkflow<
271
388
  }
272
389
  }
273
390
 
274
- createRun(options?: { runId?: string }): Run<TSteps, TInput, TOutput> {
391
+ createRun(options?: { runId?: string }): Run<TEngineType, TSteps, TInput, TOutput> {
392
+ const runIdToUse = options?.runId || randomUUID();
393
+
394
+ // Return a new Run instance with object parameters
395
+ const run: Run<TEngineType, TSteps, TInput, TOutput> =
396
+ this.runs.get(runIdToUse) ??
397
+ new InngestRun(
398
+ {
399
+ workflowId: this.id,
400
+ runId: runIdToUse,
401
+ executionEngine: this.executionEngine,
402
+ executionGraph: this.executionGraph,
403
+ serializedStepGraph: this.serializedStepGraph,
404
+ mastra: this.#mastra,
405
+ retryConfig: this.retryConfig,
406
+ cleanup: () => this.runs.delete(runIdToUse),
407
+ },
408
+ this.inngest,
409
+ );
410
+
411
+ this.runs.set(runIdToUse, run);
412
+ return run;
413
+ }
414
+
415
+ async createRunAsync(options?: { runId?: string }): Promise<Run<TEngineType, TSteps, TInput, TOutput>> {
275
416
  const runIdToUse = options?.runId || randomUUID();
276
417
 
277
418
  // Return a new Run instance with object parameters
278
- const run: Run<TSteps, TInput, TOutput> =
419
+ const run: Run<TEngineType, TSteps, TInput, TOutput> =
279
420
  this.runs.get(runIdToUse) ??
280
421
  new InngestRun(
281
422
  {
@@ -292,6 +433,29 @@ export class InngestWorkflow<
292
433
  );
293
434
 
294
435
  this.runs.set(runIdToUse, run);
436
+
437
+ const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse);
438
+
439
+ if (!workflowSnapshotInStorage) {
440
+ await this.mastra?.getStorage()?.persistWorkflowSnapshot({
441
+ workflowName: this.id,
442
+ runId: runIdToUse,
443
+ snapshot: {
444
+ runId: runIdToUse,
445
+ status: 'pending',
446
+ value: {},
447
+ context: {},
448
+ activePaths: [],
449
+ serializedStepGraph: this.serializedStepGraph,
450
+ suspendedPaths: {},
451
+ result: undefined,
452
+ error: undefined,
453
+ // @ts-ignore
454
+ timestamp: Date.now(),
455
+ },
456
+ });
457
+ }
458
+
295
459
  return run;
296
460
  }
297
461
 
@@ -321,13 +485,22 @@ export class InngestWorkflow<
321
485
  try {
322
486
  await publish({
323
487
  channel: `workflow:${this.id}:${runId}`,
324
- topic: 'watch',
488
+ topic: event,
325
489
  data,
326
490
  });
327
491
  } catch (err: any) {
328
492
  this.logger.error('Error emitting event: ' + (err?.stack ?? err?.message ?? err));
329
493
  }
330
494
  },
495
+ on: (_event: string, _callback: (data: any) => void) => {
496
+ // no-op
497
+ },
498
+ off: (_event: string, _callback: (data: any) => void) => {
499
+ // no-op
500
+ },
501
+ once: (_event: string, _callback: (data: any) => void) => {
502
+ // no-op
503
+ },
331
504
  };
332
505
 
333
506
  const engine = new InngestExecutionEngine(this.#mastra, step, attempt);
@@ -369,29 +542,192 @@ export class InngestWorkflow<
369
542
  }
370
543
  }
371
544
 
372
- function cloneWorkflow<
373
- TWorkflowId extends string = string,
374
- TInput extends z.ZodType<any> = z.ZodType<any>,
375
- TOutput extends z.ZodType<any> = z.ZodType<any>,
376
- TSteps extends Step<string, any, any, any, any>[] = Step<string, any, any, any, any>[],
545
+ function isAgent(params: any): params is Agent<any, any, any> {
546
+ return params?.component === 'AGENT';
547
+ }
548
+
549
+ function isTool(params: any): params is Tool<any, any, any> {
550
+ return params instanceof Tool;
551
+ }
552
+
553
+ export function createStep<
554
+ TStepId extends string,
555
+ TStepInput extends z.ZodType<any>,
556
+ TStepOutput extends z.ZodType<any>,
557
+ TResumeSchema extends z.ZodType<any>,
558
+ TSuspendSchema extends z.ZodType<any>,
559
+ >(params: {
560
+ id: TStepId;
561
+ description?: string;
562
+ inputSchema: TStepInput;
563
+ outputSchema: TStepOutput;
564
+ resumeSchema?: TResumeSchema;
565
+ suspendSchema?: TSuspendSchema;
566
+ execute: ExecuteFunction<
567
+ z.infer<TStepInput>,
568
+ z.infer<TStepOutput>,
569
+ z.infer<TResumeSchema>,
570
+ z.infer<TSuspendSchema>,
571
+ InngestEngineType
572
+ >;
573
+ }): Step<TStepId, TStepInput, TStepOutput, TResumeSchema, TSuspendSchema, InngestEngineType>;
574
+
575
+ export function createStep<
576
+ TStepId extends string,
577
+ TStepInput extends z.ZodObject<{ prompt: z.ZodString }>,
578
+ TStepOutput extends z.ZodObject<{ text: z.ZodString }>,
579
+ TResumeSchema extends z.ZodType<any>,
580
+ TSuspendSchema extends z.ZodType<any>,
377
581
  >(
378
- workflow: InngestWorkflow<TSteps, string, TInput, TOutput>,
379
- opts: { id: TWorkflowId },
380
- ): InngestWorkflow<TSteps, TWorkflowId, TInput, TOutput> {
381
- const wf = new InngestWorkflow(
382
- {
383
- id: opts.id,
384
- inputSchema: workflow.inputSchema,
385
- outputSchema: workflow.outputSchema,
386
- steps: workflow.stepDefs,
387
- mastra: workflow.mastra,
388
- },
389
- workflow.inngest,
390
- );
582
+ agent: Agent<TStepId, any, any>,
583
+ ): Step<TStepId, TStepInput, TStepOutput, TResumeSchema, TSuspendSchema, InngestEngineType>;
584
+
585
+ export function createStep<
586
+ TSchemaIn extends z.ZodType<any>,
587
+ TSchemaOut extends z.ZodType<any>,
588
+ TContext extends ToolExecutionContext<TSchemaIn>,
589
+ >(
590
+ tool: Tool<TSchemaIn, TSchemaOut, TContext> & {
591
+ inputSchema: TSchemaIn;
592
+ outputSchema: TSchemaOut;
593
+ execute: (context: TContext) => Promise<any>;
594
+ },
595
+ ): Step<string, TSchemaIn, TSchemaOut, z.ZodType<any>, z.ZodType<any>, InngestEngineType>;
596
+ export function createStep<
597
+ TStepId extends string,
598
+ TStepInput extends z.ZodType<any>,
599
+ TStepOutput extends z.ZodType<any>,
600
+ TResumeSchema extends z.ZodType<any>,
601
+ TSuspendSchema extends z.ZodType<any>,
602
+ >(
603
+ params:
604
+ | {
605
+ id: TStepId;
606
+ description?: string;
607
+ inputSchema: TStepInput;
608
+ outputSchema: TStepOutput;
609
+ resumeSchema?: TResumeSchema;
610
+ suspendSchema?: TSuspendSchema;
611
+ execute: ExecuteFunction<
612
+ z.infer<TStepInput>,
613
+ z.infer<TStepOutput>,
614
+ z.infer<TResumeSchema>,
615
+ z.infer<TSuspendSchema>,
616
+ InngestEngineType
617
+ >;
618
+ }
619
+ | Agent<any, any, any>
620
+ | (Tool<TStepInput, TStepOutput, any> & {
621
+ inputSchema: TStepInput;
622
+ outputSchema: TStepOutput;
623
+ execute: (context: ToolExecutionContext<TStepInput>) => Promise<any>;
624
+ }),
625
+ ): Step<TStepId, TStepInput, TStepOutput, TResumeSchema, TSuspendSchema, InngestEngineType> {
626
+ if (isAgent(params)) {
627
+ return {
628
+ id: params.name,
629
+ // @ts-ignore
630
+ inputSchema: z.object({
631
+ prompt: z.string(),
632
+ // resourceId: z.string().optional(),
633
+ // threadId: z.string().optional(),
634
+ }),
635
+ // @ts-ignore
636
+ outputSchema: z.object({
637
+ text: z.string(),
638
+ }),
639
+ execute: async ({ inputData, [EMITTER_SYMBOL]: emitter, runtimeContext }) => {
640
+ let streamPromise = {} as {
641
+ promise: Promise<string>;
642
+ resolve: (value: string) => void;
643
+ reject: (reason?: any) => void;
644
+ };
645
+
646
+ streamPromise.promise = new Promise((resolve, reject) => {
647
+ streamPromise.resolve = resolve;
648
+ streamPromise.reject = reject;
649
+ });
650
+ const toolData = {
651
+ name: params.name,
652
+ args: inputData,
653
+ };
654
+ await emitter.emit('watch-v2', {
655
+ type: 'tool-call-streaming-start',
656
+ ...toolData,
657
+ });
658
+ const { fullStream } = await params.stream(inputData.prompt, {
659
+ // resourceId: inputData.resourceId,
660
+ // threadId: inputData.threadId,
661
+ runtimeContext,
662
+ onFinish: result => {
663
+ streamPromise.resolve(result.text);
664
+ },
665
+ });
666
+
667
+ for await (const chunk of fullStream) {
668
+ switch (chunk.type) {
669
+ case 'text-delta':
670
+ await emitter.emit('watch-v2', {
671
+ type: 'tool-call-delta',
672
+ ...toolData,
673
+ argsTextDelta: chunk.textDelta,
674
+ });
675
+ break;
676
+
677
+ case 'step-start':
678
+ case 'step-finish':
679
+ case 'finish':
680
+ break;
681
+
682
+ case 'tool-call':
683
+ case 'tool-result':
684
+ case 'tool-call-streaming-start':
685
+ case 'tool-call-delta':
686
+ case 'source':
687
+ case 'file':
688
+ default:
689
+ await emitter.emit('watch-v2', chunk);
690
+ break;
691
+ }
692
+ }
693
+
694
+ return {
695
+ text: await streamPromise.promise,
696
+ };
697
+ },
698
+ };
699
+ }
700
+
701
+ if (isTool(params)) {
702
+ if (!params.inputSchema || !params.outputSchema) {
703
+ throw new Error('Tool must have input and output schemas defined');
704
+ }
705
+
706
+ return {
707
+ // TODO: tool probably should have strong id type
708
+ // @ts-ignore
709
+ id: params.id,
710
+ inputSchema: params.inputSchema,
711
+ outputSchema: params.outputSchema,
712
+ execute: async ({ inputData, mastra, runtimeContext }) => {
713
+ return params.execute({
714
+ context: inputData,
715
+ mastra,
716
+ runtimeContext,
717
+ });
718
+ },
719
+ };
720
+ }
391
721
 
392
- wf.setStepFlow(workflow.stepGraph);
393
- wf.commit();
394
- return wf;
722
+ return {
723
+ id: params.id,
724
+ description: params.description,
725
+ inputSchema: params.inputSchema,
726
+ outputSchema: params.outputSchema,
727
+ resumeSchema: params.resumeSchema,
728
+ suspendSchema: params.suspendSchema,
729
+ execute: params.execute,
730
+ };
395
731
  }
396
732
 
397
733
  export function init(inngest: Inngest) {
@@ -400,13 +736,59 @@ export function init(inngest: Inngest) {
400
736
  TWorkflowId extends string = string,
401
737
  TInput extends z.ZodType<any> = z.ZodType<any>,
402
738
  TOutput extends z.ZodType<any> = z.ZodType<any>,
403
- TSteps extends Step<string, any, any>[] = Step<string, any, any>[],
739
+ TSteps extends Step<string, any, any, any, any, InngestEngineType>[] = Step<
740
+ string,
741
+ any,
742
+ any,
743
+ any,
744
+ any,
745
+ InngestEngineType
746
+ >[],
404
747
  >(params: WorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>) {
405
- return new InngestWorkflow(params, inngest);
748
+ return new InngestWorkflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TInput>(params, inngest);
406
749
  },
407
750
  createStep,
408
- cloneStep,
409
- cloneWorkflow,
751
+ cloneStep<TStepId extends string>(
752
+ step: Step<string, any, any, any, any, InngestEngineType>,
753
+ opts: { id: TStepId },
754
+ ): Step<TStepId, any, any, any, any, InngestEngineType> {
755
+ return {
756
+ id: opts.id,
757
+ description: step.description,
758
+ inputSchema: step.inputSchema,
759
+ outputSchema: step.outputSchema,
760
+ execute: step.execute,
761
+ };
762
+ },
763
+ cloneWorkflow<
764
+ TWorkflowId extends string = string,
765
+ TInput extends z.ZodType<any> = z.ZodType<any>,
766
+ TOutput extends z.ZodType<any> = z.ZodType<any>,
767
+ TSteps extends Step<string, any, any, any, any, InngestEngineType>[] = Step<
768
+ string,
769
+ any,
770
+ any,
771
+ any,
772
+ any,
773
+ InngestEngineType
774
+ >[],
775
+ TPrevSchema extends z.ZodType<any> = TInput,
776
+ >(
777
+ workflow: Workflow<InngestEngineType, TSteps, string, TInput, TOutput, TPrevSchema>,
778
+ opts: { id: TWorkflowId },
779
+ ): Workflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> {
780
+ const wf: Workflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> = new Workflow({
781
+ id: opts.id,
782
+ inputSchema: workflow.inputSchema,
783
+ outputSchema: workflow.outputSchema,
784
+ steps: workflow.stepDefs,
785
+ mastra: workflow.mastra,
786
+ });
787
+
788
+ wf.setStepFlow(workflow.stepGraph);
789
+ wf.commit();
790
+ return wf;
791
+ },
410
792
  };
411
793
  }
412
794
 
@@ -420,9 +802,44 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
420
802
  this.inngestAttempts = inngestAttempts;
421
803
  }
422
804
 
805
+ async execute<TInput, TOutput>(params: {
806
+ workflowId: string;
807
+ runId: string;
808
+ graph: ExecutionGraph;
809
+ serializedStepGraph: SerializedStepFlowEntry[];
810
+ input?: TInput;
811
+ resume?: {
812
+ // TODO: add execute path
813
+ steps: string[];
814
+ stepResults: Record<string, StepResult<any, any, any, any>>;
815
+ resumePayload: any;
816
+ resumePath: number[];
817
+ };
818
+ emitter: Emitter;
819
+ retryConfig?: {
820
+ attempts?: number;
821
+ delay?: number;
822
+ };
823
+ runtimeContext: RuntimeContext;
824
+ }): Promise<TOutput> {
825
+ await params.emitter.emit('watch-v2', {
826
+ type: 'start',
827
+ payload: { runId: params.runId },
828
+ });
829
+
830
+ const result = await super.execute<TInput, TOutput>(params);
831
+
832
+ await params.emitter.emit('watch-v2', {
833
+ type: 'finish',
834
+ payload: { runId: params.runId },
835
+ });
836
+
837
+ return result;
838
+ }
839
+
423
840
  protected async fmtReturnValue<TOutput>(
424
841
  executionSpan: Span | undefined,
425
- emitter: { emit: (event: string, data: any) => Promise<void> },
842
+ emitter: Emitter,
426
843
  stepResults: Record<string, StepResult<any, any, any, any>>,
427
844
  lastOutput: StepResult<any, any, any, any>,
428
845
  error?: Error | string,
@@ -515,7 +932,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
515
932
  resumePayload: any;
516
933
  };
517
934
  prevOutput: any;
518
- emitter: { emit: (event: string, data: any) => Promise<void> };
935
+ emitter: Emitter;
519
936
  runtimeContext: RuntimeContext;
520
937
  }): Promise<StepResult<any, any, any, any>> {
521
938
  return super.executeStep({
@@ -531,6 +948,23 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
531
948
  });
532
949
  }
533
950
 
951
+ async executeSleep({ id, duration }: { id: string; duration: number }): Promise<void> {
952
+ await this.inngestStep.sleep(id, duration);
953
+ }
954
+
955
+ async executeWaitForEvent({ event, timeout }: { event: string; timeout?: number }): Promise<any> {
956
+ const eventData = await this.inngestStep.waitForEvent(`user-event-${event}`, {
957
+ event: `user-event-${event}`,
958
+ timeout: timeout ?? 5e3,
959
+ });
960
+
961
+ if (eventData === null) {
962
+ throw 'Timeout waiting for event';
963
+ }
964
+
965
+ return eventData?.data;
966
+ }
967
+
534
968
  async executeStep({
535
969
  step,
536
970
  stepResults,
@@ -555,12 +989,13 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
555
989
  runId?: string;
556
990
  };
557
991
  prevOutput: any;
558
- emitter: { emit: (event: string, data: any) => Promise<void> };
992
+ emitter: Emitter;
559
993
  runtimeContext: RuntimeContext;
560
994
  }): Promise<StepResult<any, any, any, any>> {
561
- await this.inngestStep.run(
995
+ const startedAt = await this.inngestStep.run(
562
996
  `workflow.${executionContext.workflowId}.run.${executionContext.runId}.step.${step.id}.running_ev`,
563
997
  async () => {
998
+ const startedAt = Date.now();
564
999
  await emitter.emit('watch', {
565
1000
  type: 'watch',
566
1001
  payload: {
@@ -582,6 +1017,16 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
582
1017
  },
583
1018
  eventTimestamp: Date.now(),
584
1019
  });
1020
+
1021
+ await emitter.emit('watch-v2', {
1022
+ type: 'step-start',
1023
+ payload: {
1024
+ id: step.id,
1025
+ status: 'running',
1026
+ },
1027
+ });
1028
+
1029
+ return startedAt;
585
1030
  },
586
1031
  );
587
1032
 
@@ -648,6 +1093,16 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
648
1093
  eventTimestamp: Date.now(),
649
1094
  });
650
1095
 
1096
+ await emitter.emit('watch-v2', {
1097
+ type: 'step-result',
1098
+ payload: {
1099
+ id: step.id,
1100
+ status: 'failed',
1101
+ error: result?.error,
1102
+ payload: prevOutput,
1103
+ },
1104
+ });
1105
+
651
1106
  return { executionContext, result: { status: 'failed', error: result?.error } };
652
1107
  } else if (result.status === 'suspended') {
653
1108
  const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
@@ -678,6 +1133,14 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
678
1133
  eventTimestamp: Date.now(),
679
1134
  });
680
1135
 
1136
+ await emitter.emit('watch-v2', {
1137
+ type: 'step-suspended',
1138
+ payload: {
1139
+ id: step.id,
1140
+ status: 'suspended',
1141
+ },
1142
+ });
1143
+
681
1144
  return {
682
1145
  executionContext,
683
1146
  result: {
@@ -734,6 +1197,23 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
734
1197
  eventTimestamp: Date.now(),
735
1198
  });
736
1199
 
1200
+ await emitter.emit('watch-v2', {
1201
+ type: 'step-result',
1202
+ payload: {
1203
+ id: step.id,
1204
+ status: 'success',
1205
+ output: result?.result,
1206
+ },
1207
+ });
1208
+
1209
+ await emitter.emit('watch-v2', {
1210
+ type: 'step-finish',
1211
+ payload: {
1212
+ id: step.id,
1213
+ metadata: {},
1214
+ },
1215
+ });
1216
+
737
1217
  return { executionContext, result: { status: 'success', output: result?.result } };
738
1218
  },
739
1219
  );
@@ -745,6 +1225,8 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
745
1225
  const stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
746
1226
  let execResults: any;
747
1227
  let suspended: { payload: any } | undefined;
1228
+ let bailed: { payload: any } | undefined;
1229
+
748
1230
  try {
749
1231
  const result = await step.execute({
750
1232
  runId: executionContext.runId,
@@ -765,22 +1247,55 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
765
1247
  executionContext.suspendedPaths[step.id] = executionContext.executionPath;
766
1248
  suspended = { payload: suspendPayload };
767
1249
  },
1250
+ bail: (result: any) => {
1251
+ bailed = { payload: result };
1252
+ },
768
1253
  resume: {
769
1254
  steps: resume?.steps?.slice(1) || [],
770
1255
  resumePayload: resume?.resumePayload,
771
1256
  // @ts-ignore
772
1257
  runId: stepResults[step.id]?.payload?.__workflow_meta?.runId,
773
1258
  },
774
- emitter,
1259
+ [EMITTER_SYMBOL]: emitter,
1260
+ engine: {
1261
+ step: this.inngestStep,
1262
+ },
775
1263
  });
776
-
777
- execResults = { status: 'success', output: result };
1264
+ const endedAt = Date.now();
1265
+
1266
+ execResults = {
1267
+ status: 'success',
1268
+ output: result,
1269
+ startedAt,
1270
+ endedAt,
1271
+ payload: prevOutput,
1272
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1273
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1274
+ };
778
1275
  } catch (e) {
779
- execResults = { status: 'failed', error: e instanceof Error ? e.message : String(e) };
1276
+ execResults = {
1277
+ status: 'failed',
1278
+ payload: prevOutput,
1279
+ error: e instanceof Error ? e.message : String(e),
1280
+ endedAt: Date.now(),
1281
+ startedAt,
1282
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1283
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1284
+ };
780
1285
  }
781
1286
 
782
1287
  if (suspended) {
783
- execResults = { status: 'suspended', payload: suspended.payload };
1288
+ execResults = {
1289
+ status: 'suspended',
1290
+ suspendedPayload: suspended.payload,
1291
+ payload: prevOutput,
1292
+ suspendedAt: Date.now(),
1293
+ startedAt,
1294
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1295
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1296
+ };
1297
+ } else if (bailed) {
1298
+ execResults = { status: 'bailed', output: bailed.payload, payload: prevOutput, endedAt: Date.now(), startedAt };
784
1299
  }
785
1300
 
786
1301
  if (execResults.status === 'failed') {
@@ -794,12 +1309,11 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
794
1309
  payload: {
795
1310
  currentStep: {
796
1311
  id: step.id,
797
- status: execResults.status,
798
- output: execResults.output,
1312
+ ...execResults,
799
1313
  },
800
1314
  workflowState: {
801
1315
  status: 'running',
802
- steps: stepResults,
1316
+ steps: { ...stepResults, [step.id]: execResults },
803
1317
  result: null,
804
1318
  error: null,
805
1319
  },
@@ -807,6 +1321,32 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
807
1321
  eventTimestamp: Date.now(),
808
1322
  });
809
1323
 
1324
+ if (execResults.status === 'suspended') {
1325
+ await emitter.emit('watch-v2', {
1326
+ type: 'step-suspended',
1327
+ payload: {
1328
+ id: step.id,
1329
+ ...execResults,
1330
+ },
1331
+ });
1332
+ } else {
1333
+ await emitter.emit('watch-v2', {
1334
+ type: 'step-result',
1335
+ payload: {
1336
+ id: step.id,
1337
+ ...execResults,
1338
+ },
1339
+ });
1340
+
1341
+ await emitter.emit('watch-v2', {
1342
+ type: 'step-finish',
1343
+ payload: {
1344
+ id: step.id,
1345
+ metadata: {},
1346
+ },
1347
+ });
1348
+ }
1349
+
810
1350
  return { result: execResults, executionContext, stepResults };
811
1351
  });
812
1352
 
@@ -825,12 +1365,18 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
825
1365
  stepResults,
826
1366
  executionContext,
827
1367
  serializedStepGraph,
1368
+ workflowStatus,
1369
+ result,
1370
+ error,
828
1371
  }: {
829
1372
  workflowId: string;
830
1373
  runId: string;
831
1374
  stepResults: Record<string, StepResult<any, any, any, any>>;
832
1375
  serializedStepGraph: SerializedStepFlowEntry[];
833
1376
  executionContext: ExecutionContext;
1377
+ workflowStatus: 'success' | 'failed' | 'suspended' | 'running';
1378
+ result?: Record<string, any>;
1379
+ error?: string | Error;
834
1380
  }) {
835
1381
  await this.inngestStep.run(
836
1382
  `workflow.${workflowId}.run.${runId}.path.${JSON.stringify(executionContext.executionPath)}.stepUpdate`,
@@ -845,6 +1391,9 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
845
1391
  activePaths: [],
846
1392
  suspendedPaths: executionContext.suspendedPaths,
847
1393
  serializedStepGraph,
1394
+ status: workflowStatus,
1395
+ result,
1396
+ error,
848
1397
  // @ts-ignore
849
1398
  timestamp: Date.now(),
850
1399
  },
@@ -868,7 +1417,11 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
868
1417
  }: {
869
1418
  workflowId: string;
870
1419
  runId: string;
871
- entry: { type: 'conditional'; steps: StepFlowEntry[]; conditions: ExecuteFunction<any, any, any, any>[] };
1420
+ entry: {
1421
+ type: 'conditional';
1422
+ steps: StepFlowEntry[];
1423
+ conditions: ExecuteFunction<any, any, any, any, InngestEngineType>[];
1424
+ };
872
1425
  prevStep: StepFlowEntry;
873
1426
  serializedStepGraph: SerializedStepFlowEntry[];
874
1427
  prevOutput: any;
@@ -880,7 +1433,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
880
1433
  resumePath: number[];
881
1434
  };
882
1435
  executionContext: ExecutionContext;
883
- emitter: { emit: (event: string, data: any) => Promise<void> };
1436
+ emitter: Emitter;
884
1437
  runtimeContext: RuntimeContext;
885
1438
  }): Promise<StepResult<any, any, any, any>> {
886
1439
  let execResults: any;
@@ -893,6 +1446,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
893
1446
  runId,
894
1447
  mastra: this.mastra!,
895
1448
  runtimeContext,
1449
+ runCount: -1,
896
1450
  inputData: prevOutput,
897
1451
  getInitData: () => stepResults?.input as any,
898
1452
  getStepResult: (step: any) => {
@@ -910,7 +1464,11 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
910
1464
 
911
1465
  // TODO: this function shouldn't have suspend probably?
912
1466
  suspend: async (_suspendPayload: any) => {},
1467
+ bail: () => {},
913
1468
  [EMITTER_SYMBOL]: emitter,
1469
+ engine: {
1470
+ step: this.inngestStep,
1471
+ },
914
1472
  });
915
1473
  return result ? index : null;
916
1474
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -923,7 +1481,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
923
1481
  ).filter((index: any): index is number => index !== null);
924
1482
 
925
1483
  const stepsToRun = entry.steps.filter((_, index) => truthyIndexes.includes(index));
926
- const results: StepResult<any, any, any, any>[] = await Promise.all(
1484
+ const results: { result: StepResult<any, any, any, any> }[] = await Promise.all(
927
1485
  stepsToRun.map((step, index) =>
928
1486
  this.executeEntry({
929
1487
  workflowId,
@@ -946,17 +1504,19 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
946
1504
  }),
947
1505
  ),
948
1506
  );
949
- const hasFailed = results.find(result => result.status === 'failed');
950
- const hasSuspended = results.find(result => result.status === 'suspended');
1507
+ const hasFailed = results.find(result => result.result.status === 'failed') as {
1508
+ result: StepFailure<any, any, any>;
1509
+ };
1510
+ const hasSuspended = results.find(result => result.result.status === 'suspended');
951
1511
  if (hasFailed) {
952
- execResults = { status: 'failed', error: hasFailed.error };
1512
+ execResults = { status: 'failed', error: hasFailed.result.error };
953
1513
  } else if (hasSuspended) {
954
- execResults = { status: 'suspended', payload: hasSuspended.payload };
1514
+ execResults = { status: 'suspended', payload: hasSuspended.result.suspendPayload };
955
1515
  } else {
956
1516
  execResults = {
957
1517
  status: 'success',
958
1518
  output: results.reduce((acc: Record<string, any>, result, index) => {
959
- if (result.status === 'success') {
1519
+ if (result.result.status === 'success') {
960
1520
  // @ts-ignore
961
1521
  acc[stepsToRun[index]!.step.id] = result.output;
962
1522
  }