@mastra/inngest 0.0.0-add-runtime-context-to-openai-realtime-20250516201052 → 0.0.0-ai-v5-20250625014956

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,27 +1,37 @@
1
1
  import { randomUUID } from 'crypto';
2
2
  import { subscribe } from '@inngest/realtime';
3
- import type { Mastra, VNextWorkflowRun, VNextWorkflowRuns } from '@mastra/core';
3
+ import type { Agent, Mastra, ToolExecutionContext, WorkflowRun, WorkflowRuns } from '@mastra/core';
4
4
  import { RuntimeContext } from '@mastra/core/di';
5
- import { NewWorkflow, createStep, Run, DefaultExecutionEngine, cloneStep } from '@mastra/core/workflows/vNext';
5
+ import { Tool } from '@mastra/core/tools';
6
+ import { Workflow, Run, DefaultExecutionEngine } from '@mastra/core/workflows';
6
7
  import type {
7
8
  ExecuteFunction,
8
9
  ExecutionContext,
9
10
  ExecutionEngine,
10
11
  ExecutionGraph,
11
- NewStep,
12
- NewStep as Step,
13
- NewWorkflowConfig,
12
+ Step,
13
+ WorkflowConfig,
14
14
  StepFlowEntry,
15
15
  StepResult,
16
16
  WorkflowResult,
17
- } from '@mastra/core/workflows/vNext';
17
+ SerializedStepFlowEntry,
18
+ StepFailure,
19
+ Emitter,
20
+ WatchEvent,
21
+ StreamEvent,
22
+ } from '@mastra/core/workflows';
23
+ import { EMITTER_SYMBOL } from '@mastra/core/workflows/_constants';
18
24
  import type { Span } from '@opentelemetry/api';
19
25
  import type { Inngest, BaseContext } from 'inngest';
20
26
  import { serve as inngestServe } from 'inngest/hono';
21
- import type { z } from 'zod';
27
+ import { z } from 'zod';
28
+
29
+ export type InngestEngineType = {
30
+ step: any;
31
+ };
22
32
 
23
33
  export function serve({ mastra, inngest }: { mastra: Mastra; inngest: Inngest }): ReturnType<typeof inngestServe> {
24
- const wfs = mastra.vnext_getWorkflows();
34
+ const wfs = mastra.getWorkflows();
25
35
  const functions = Object.values(wfs).flatMap(wf => {
26
36
  if (wf instanceof InngestWorkflow) {
27
37
  wf.__registerMastra(mastra);
@@ -36,11 +46,13 @@ export function serve({ mastra, inngest }: { mastra: Mastra; inngest: Inngest })
36
46
  }
37
47
 
38
48
  export class InngestRun<
39
- TSteps extends NewStep<string, any, any>[] = NewStep<string, any, any>[],
49
+ TEngineType = InngestEngineType,
50
+ TSteps extends Step<string, any, any>[] = Step<string, any, any>[],
40
51
  TInput extends z.ZodType<any> = z.ZodType<any>,
41
52
  TOutput extends z.ZodType<any> = z.ZodType<any>,
42
- > extends Run<TSteps, TInput, TOutput> {
53
+ > extends Run<TEngineType, TSteps, TInput, TOutput> {
43
54
  private inngest: Inngest;
55
+ serializedStepGraph: SerializedStepFlowEntry[];
44
56
  #mastra: Mastra;
45
57
 
46
58
  constructor(
@@ -49,6 +61,7 @@ export class InngestRun<
49
61
  runId: string;
50
62
  executionEngine: ExecutionEngine;
51
63
  executionGraph: ExecutionGraph;
64
+ serializedStepGraph: SerializedStepFlowEntry[];
52
65
  mastra?: Mastra;
53
66
  retryConfig?: {
54
67
  attempts?: number;
@@ -60,11 +73,12 @@ export class InngestRun<
60
73
  ) {
61
74
  super(params);
62
75
  this.inngest = inngest;
76
+ this.serializedStepGraph = params.serializedStepGraph;
63
77
  this.#mastra = params.mastra!;
64
78
  }
65
79
 
66
80
  async getRuns(eventId: string) {
67
- const response = await fetch(`${this.inngest.apiBaseUrl}/v1/events/${eventId}/runs`, {
81
+ const response = await fetch(`${this.inngest.apiBaseUrl ?? 'https://api.inngest.com'}/v1/events/${eventId}/runs`, {
68
82
  headers: {
69
83
  Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`,
70
84
  },
@@ -75,7 +89,8 @@ export class InngestRun<
75
89
 
76
90
  async getRunOutput(eventId: string) {
77
91
  let runs = await this.getRuns(eventId);
78
- while (runs?.[0]?.status !== 'Completed') {
92
+
93
+ while (runs?.[0]?.status !== 'Completed' || runs?.[0]?.event_id !== eventId) {
79
94
  await new Promise(resolve => setTimeout(resolve, 1000));
80
95
  runs = await this.getRuns(eventId);
81
96
  if (runs?.[0]?.status === 'Failed' || runs?.[0]?.status === 'Cancelled') {
@@ -85,6 +100,13 @@ export class InngestRun<
85
100
  return runs?.[0];
86
101
  }
87
102
 
103
+ async sendEvent(event: string, data: any) {
104
+ await this.inngest.send({
105
+ name: `user-event-${event}`,
106
+ data,
107
+ });
108
+ }
109
+
88
110
  async start({
89
111
  inputData,
90
112
  }: {
@@ -96,11 +118,13 @@ export class InngestRun<
96
118
  runId: this.runId,
97
119
  snapshot: {
98
120
  runId: this.runId,
121
+ serializedStepGraph: this.serializedStepGraph,
99
122
  value: {},
100
123
  context: {} as any,
101
124
  activePaths: [],
102
125
  suspendedPaths: {},
103
126
  timestamp: Date.now(),
127
+ status: 'running',
104
128
  },
105
129
  });
106
130
 
@@ -134,6 +158,27 @@ export class InngestRun<
134
158
  | string
135
159
  | string[];
136
160
  runtimeContext?: RuntimeContext;
161
+ }): Promise<WorkflowResult<TOutput, TSteps>> {
162
+ const p = this._resume(params).then(result => {
163
+ if (result.status !== 'suspended') {
164
+ this.closeStreamAction?.().catch(() => {});
165
+ }
166
+
167
+ return result;
168
+ });
169
+
170
+ this.executionResults = p;
171
+ return p;
172
+ }
173
+
174
+ async _resume<TResumeSchema extends z.ZodType<any>>(params: {
175
+ resumeData?: z.infer<TResumeSchema>;
176
+ step:
177
+ | Step<string, any, any, TResumeSchema, any>
178
+ | [...Step<string, any, any, any, any>[], Step<string, any, any, TResumeSchema, any>]
179
+ | string
180
+ | string[];
181
+ runtimeContext?: RuntimeContext;
137
182
  }): Promise<WorkflowResult<TOutput, TSteps>> {
138
183
  const steps: string[] = (Array.isArray(params.step) ? params.step : [params.step]).map(step =>
139
184
  typeof step === 'string' ? step : step?.id,
@@ -171,43 +216,88 @@ export class InngestRun<
171
216
  return result;
172
217
  }
173
218
 
174
- watch(cb: (event: any) => void): () => void {
219
+ watch(cb: (event: WatchEvent) => void, type: 'watch' | 'watch-v2' = 'watch'): () => void {
220
+ let active = true;
175
221
  const streamPromise = subscribe(
176
222
  {
177
223
  channel: `workflow:${this.workflowId}:${this.runId}`,
178
- topics: ['watch'],
224
+ topics: [type],
179
225
  app: this.inngest,
180
226
  },
181
227
  (message: any) => {
182
- cb(message.data);
228
+ if (active) {
229
+ cb(message.data);
230
+ }
183
231
  },
184
232
  );
185
233
 
186
234
  return () => {
235
+ active = false;
187
236
  streamPromise
188
- .then((stream: any) => {
189
- stream.cancel();
237
+ .then(async (stream: Awaited<typeof streamPromise>) => {
238
+ return stream.cancel();
190
239
  })
191
240
  .catch(err => {
192
241
  console.error(err);
193
242
  });
194
243
  };
195
244
  }
245
+
246
+ stream({ inputData, runtimeContext }: { inputData?: z.infer<TInput>; runtimeContext?: RuntimeContext } = {}): {
247
+ stream: ReadableStream<StreamEvent>;
248
+ getWorkflowState: () => Promise<WorkflowResult<TOutput, TSteps>>;
249
+ } {
250
+ const { readable, writable } = new TransformStream<StreamEvent, StreamEvent>();
251
+
252
+ const writer = writable.getWriter();
253
+ const unwatch = this.watch(async event => {
254
+ try {
255
+ // watch-v2 events are data stream events, so we need to cast them to the correct type
256
+ await writer.write(event as any);
257
+ } catch {}
258
+ }, 'watch-v2');
259
+
260
+ this.closeStreamAction = async () => {
261
+ unwatch();
262
+
263
+ try {
264
+ await writer.close();
265
+ } catch (err) {
266
+ console.error('Error closing stream:', err);
267
+ } finally {
268
+ writer.releaseLock();
269
+ }
270
+ };
271
+
272
+ this.executionResults = this.start({ inputData, runtimeContext }).then(result => {
273
+ if (result.status !== 'suspended') {
274
+ this.closeStreamAction?.().catch(() => {});
275
+ }
276
+
277
+ return result;
278
+ });
279
+
280
+ return {
281
+ stream: readable,
282
+ getWorkflowState: () => this.executionResults!,
283
+ };
284
+ }
196
285
  }
197
286
 
198
287
  export class InngestWorkflow<
199
- TSteps extends NewStep<string, any, any>[] = NewStep<string, any, any>[],
288
+ TEngineType = InngestEngineType,
289
+ TSteps extends Step<string, any, any>[] = Step<string, any, any>[],
200
290
  TWorkflowId extends string = string,
201
291
  TInput extends z.ZodType<any> = z.ZodType<any>,
202
292
  TOutput extends z.ZodType<any> = z.ZodType<any>,
203
293
  TPrevSchema extends z.ZodType<any> = TInput,
204
- > extends NewWorkflow<TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> {
294
+ > extends Workflow<TEngineType, TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> {
205
295
  #mastra: Mastra;
206
296
  public inngest: Inngest;
207
297
 
208
298
  private function: ReturnType<Inngest['createFunction']> | undefined;
209
299
 
210
- constructor(params: NewWorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>, inngest: Inngest) {
300
+ constructor(params: WorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>, inngest: Inngest) {
211
301
  super(params);
212
302
  this.#mastra = params.mastra!;
213
303
  this.inngest = inngest;
@@ -226,25 +316,52 @@ export class InngestWorkflow<
226
316
  return { runs: [], total: 0 };
227
317
  }
228
318
 
229
- return storage.getWorkflowRuns({ workflowName: this.id, ...(args ?? {}) }) as unknown as VNextWorkflowRuns;
319
+ return storage.getWorkflowRuns({ workflowName: this.id, ...(args ?? {}) }) as unknown as WorkflowRuns;
230
320
  }
231
321
 
232
- async getWorkflowRunById(runId: string): Promise<VNextWorkflowRun | null> {
322
+ async getWorkflowRunById(runId: string): Promise<WorkflowRun | null> {
233
323
  const storage = this.#mastra?.getStorage();
234
324
  if (!storage) {
235
325
  this.logger.debug('Cannot get workflow runs. Mastra engine is not initialized');
236
- return null;
326
+ //returning in memory run if no storage is initialized
327
+ return this.runs.get(runId)
328
+ ? ({ ...this.runs.get(runId), workflowName: this.id } as unknown as WorkflowRun)
329
+ : null;
237
330
  }
238
- const run = (await storage.getWorkflowRunById({ runId, workflowName: this.id })) as unknown as VNextWorkflowRun;
331
+ const run = (await storage.getWorkflowRunById({ runId, workflowName: this.id })) as unknown as WorkflowRun;
239
332
 
240
333
  return (
241
334
  run ??
242
- (this.runs.get(runId)
243
- ? ({ ...this.runs.get(runId), workflowName: this.id } as unknown as VNextWorkflowRun)
244
- : null)
335
+ (this.runs.get(runId) ? ({ ...this.runs.get(runId), workflowName: this.id } as unknown as WorkflowRun) : null)
245
336
  );
246
337
  }
247
338
 
339
+ async getWorkflowRunExecutionResult(runId: string): Promise<WatchEvent['payload']['workflowState'] | null> {
340
+ const storage = this.#mastra?.getStorage();
341
+ if (!storage) {
342
+ this.logger.debug('Cannot get workflow run execution result. Mastra storage is not initialized');
343
+ return null;
344
+ }
345
+
346
+ const run = await storage.getWorkflowRunById({ runId, workflowName: this.id });
347
+
348
+ if (!run?.snapshot) {
349
+ return null;
350
+ }
351
+
352
+ if (typeof run.snapshot === 'string') {
353
+ return null;
354
+ }
355
+
356
+ return {
357
+ status: run.snapshot.status,
358
+ result: run.snapshot.result,
359
+ error: run.snapshot.error,
360
+ payload: run.snapshot.context?.input,
361
+ steps: run.snapshot.context as any,
362
+ };
363
+ }
364
+
248
365
  __registerMastra(mastra: Mastra) {
249
366
  this.#mastra = mastra;
250
367
  this.executionEngine.__registerMastra(mastra);
@@ -268,11 +385,11 @@ export class InngestWorkflow<
268
385
  }
269
386
  }
270
387
 
271
- createRun(options?: { runId?: string }): Run<TSteps, TInput, TOutput> {
388
+ createRun(options?: { runId?: string }): Run<TEngineType, TSteps, TInput, TOutput> {
272
389
  const runIdToUse = options?.runId || randomUUID();
273
390
 
274
391
  // Return a new Run instance with object parameters
275
- const run: Run<TSteps, TInput, TOutput> =
392
+ const run: Run<TEngineType, TSteps, TInput, TOutput> =
276
393
  this.runs.get(runIdToUse) ??
277
394
  new InngestRun(
278
395
  {
@@ -280,6 +397,7 @@ export class InngestWorkflow<
280
397
  runId: runIdToUse,
281
398
  executionEngine: this.executionEngine,
282
399
  executionGraph: this.executionGraph,
400
+ serializedStepGraph: this.serializedStepGraph,
283
401
  mastra: this.#mastra,
284
402
  retryConfig: this.retryConfig,
285
403
  cleanup: () => this.runs.delete(runIdToUse),
@@ -317,13 +435,22 @@ export class InngestWorkflow<
317
435
  try {
318
436
  await publish({
319
437
  channel: `workflow:${this.id}:${runId}`,
320
- topic: 'watch',
438
+ topic: event,
321
439
  data,
322
440
  });
323
441
  } catch (err: any) {
324
442
  this.logger.error('Error emitting event: ' + (err?.stack ?? err?.message ?? err));
325
443
  }
326
444
  },
445
+ on: (_event: string, _callback: (data: any) => void) => {
446
+ // no-op
447
+ },
448
+ off: (_event: string, _callback: (data: any) => void) => {
449
+ // no-op
450
+ },
451
+ once: (_event: string, _callback: (data: any) => void) => {
452
+ // no-op
453
+ },
327
454
  };
328
455
 
329
456
  const engine = new InngestExecutionEngine(this.#mastra, step, attempt);
@@ -331,6 +458,7 @@ export class InngestWorkflow<
331
458
  workflowId: this.id,
332
459
  runId,
333
460
  graph: this.executionGraph,
461
+ serializedStepGraph: this.serializedStepGraph,
334
462
  input: inputData,
335
463
  emitter,
336
464
  retryConfig: this.retryConfig,
@@ -364,29 +492,192 @@ export class InngestWorkflow<
364
492
  }
365
493
  }
366
494
 
367
- function cloneWorkflow<
368
- TWorkflowId extends string = string,
369
- TInput extends z.ZodType<any> = z.ZodType<any>,
370
- TOutput extends z.ZodType<any> = z.ZodType<any>,
371
- TSteps extends Step<string, any, any, any, any>[] = Step<string, any, any, any, any>[],
495
+ function isAgent(params: any): params is Agent<any, any, any> {
496
+ return params?.component === 'AGENT';
497
+ }
498
+
499
+ function isTool(params: any): params is Tool<any, any, any> {
500
+ return params instanceof Tool;
501
+ }
502
+
503
+ export function createStep<
504
+ TStepId extends string,
505
+ TStepInput extends z.ZodType<any>,
506
+ TStepOutput extends z.ZodType<any>,
507
+ TResumeSchema extends z.ZodType<any>,
508
+ TSuspendSchema extends z.ZodType<any>,
509
+ >(params: {
510
+ id: TStepId;
511
+ description?: string;
512
+ inputSchema: TStepInput;
513
+ outputSchema: TStepOutput;
514
+ resumeSchema?: TResumeSchema;
515
+ suspendSchema?: TSuspendSchema;
516
+ execute: ExecuteFunction<
517
+ z.infer<TStepInput>,
518
+ z.infer<TStepOutput>,
519
+ z.infer<TResumeSchema>,
520
+ z.infer<TSuspendSchema>,
521
+ InngestEngineType
522
+ >;
523
+ }): Step<TStepId, TStepInput, TStepOutput, TResumeSchema, TSuspendSchema, InngestEngineType>;
524
+
525
+ export function createStep<
526
+ TStepId extends string,
527
+ TStepInput extends z.ZodObject<{ prompt: z.ZodString }>,
528
+ TStepOutput extends z.ZodObject<{ text: z.ZodString }>,
529
+ TResumeSchema extends z.ZodType<any>,
530
+ TSuspendSchema extends z.ZodType<any>,
372
531
  >(
373
- workflow: InngestWorkflow<TSteps, string, TInput, TOutput>,
374
- opts: { id: TWorkflowId },
375
- ): InngestWorkflow<TSteps, TWorkflowId, TInput, TOutput> {
376
- const wf = new InngestWorkflow(
377
- {
378
- id: opts.id,
379
- inputSchema: workflow.inputSchema,
380
- outputSchema: workflow.outputSchema,
381
- steps: workflow.stepDefs,
382
- mastra: workflow.mastra,
383
- },
384
- workflow.inngest,
385
- );
532
+ agent: Agent<TStepId, any, any>,
533
+ ): Step<TStepId, TStepInput, TStepOutput, TResumeSchema, TSuspendSchema, InngestEngineType>;
534
+
535
+ export function createStep<
536
+ TSchemaIn extends z.ZodType<any>,
537
+ TSchemaOut extends z.ZodType<any>,
538
+ TContext extends ToolExecutionContext<TSchemaIn>,
539
+ >(
540
+ tool: Tool<TSchemaIn, TSchemaOut, TContext> & {
541
+ inputSchema: TSchemaIn;
542
+ outputSchema: TSchemaOut;
543
+ execute: (context: TContext) => Promise<any>;
544
+ },
545
+ ): Step<string, TSchemaIn, TSchemaOut, z.ZodType<any>, z.ZodType<any>, InngestEngineType>;
546
+ export function createStep<
547
+ TStepId extends string,
548
+ TStepInput extends z.ZodType<any>,
549
+ TStepOutput extends z.ZodType<any>,
550
+ TResumeSchema extends z.ZodType<any>,
551
+ TSuspendSchema extends z.ZodType<any>,
552
+ >(
553
+ params:
554
+ | {
555
+ id: TStepId;
556
+ description?: string;
557
+ inputSchema: TStepInput;
558
+ outputSchema: TStepOutput;
559
+ resumeSchema?: TResumeSchema;
560
+ suspendSchema?: TSuspendSchema;
561
+ execute: ExecuteFunction<
562
+ z.infer<TStepInput>,
563
+ z.infer<TStepOutput>,
564
+ z.infer<TResumeSchema>,
565
+ z.infer<TSuspendSchema>,
566
+ InngestEngineType
567
+ >;
568
+ }
569
+ | Agent<any, any, any>
570
+ | (Tool<TStepInput, TStepOutput, any> & {
571
+ inputSchema: TStepInput;
572
+ outputSchema: TStepOutput;
573
+ execute: (context: ToolExecutionContext<TStepInput>) => Promise<any>;
574
+ }),
575
+ ): Step<TStepId, TStepInput, TStepOutput, TResumeSchema, TSuspendSchema, InngestEngineType> {
576
+ if (isAgent(params)) {
577
+ return {
578
+ id: params.name,
579
+ // @ts-ignore
580
+ inputSchema: z.object({
581
+ prompt: z.string(),
582
+ // resourceId: z.string().optional(),
583
+ // threadId: z.string().optional(),
584
+ }),
585
+ // @ts-ignore
586
+ outputSchema: z.object({
587
+ text: z.string(),
588
+ }),
589
+ execute: async ({ inputData, [EMITTER_SYMBOL]: emitter, runtimeContext }) => {
590
+ let streamPromise = {} as {
591
+ promise: Promise<string>;
592
+ resolve: (value: string) => void;
593
+ reject: (reason?: any) => void;
594
+ };
386
595
 
387
- wf.setStepFlow(workflow.stepGraph);
388
- wf.commit();
389
- return wf;
596
+ streamPromise.promise = new Promise((resolve, reject) => {
597
+ streamPromise.resolve = resolve;
598
+ streamPromise.reject = reject;
599
+ });
600
+ const toolData = {
601
+ name: params.name,
602
+ args: inputData,
603
+ };
604
+ await emitter.emit('watch-v2', {
605
+ type: 'tool-call-streaming-start',
606
+ ...toolData,
607
+ });
608
+ const { fullStream } = await params.stream(inputData.prompt, {
609
+ // resourceId: inputData.resourceId,
610
+ // threadId: inputData.threadId,
611
+ runtimeContext,
612
+ onFinish: result => {
613
+ streamPromise.resolve(result.text);
614
+ },
615
+ });
616
+
617
+ for await (const chunk of fullStream) {
618
+ switch (chunk.type) {
619
+ case 'text':
620
+ await emitter.emit('watch-v2', {
621
+ type: 'tool-call-delta',
622
+ ...toolData,
623
+ argsTextDelta: chunk.text,
624
+ });
625
+ break;
626
+
627
+ case 'start-step':
628
+ case 'finish-step':
629
+ case 'finish':
630
+ break;
631
+
632
+ case 'tool-call':
633
+ case 'tool-result':
634
+ case 'tool-input-start':
635
+ case 'tool-input-delta':
636
+ case 'source':
637
+ case 'file':
638
+ default:
639
+ await emitter.emit('watch-v2', chunk);
640
+ break;
641
+ }
642
+ }
643
+
644
+ return {
645
+ text: await streamPromise.promise,
646
+ };
647
+ },
648
+ };
649
+ }
650
+
651
+ if (isTool(params)) {
652
+ if (!params.inputSchema || !params.outputSchema) {
653
+ throw new Error('Tool must have input and output schemas defined');
654
+ }
655
+
656
+ return {
657
+ // TODO: tool probably should have strong id type
658
+ // @ts-ignore
659
+ id: params.id,
660
+ inputSchema: params.inputSchema,
661
+ outputSchema: params.outputSchema,
662
+ execute: async ({ inputData, mastra, runtimeContext }) => {
663
+ return params.execute({
664
+ context: inputData,
665
+ mastra,
666
+ runtimeContext,
667
+ });
668
+ },
669
+ };
670
+ }
671
+
672
+ return {
673
+ id: params.id,
674
+ description: params.description,
675
+ inputSchema: params.inputSchema,
676
+ outputSchema: params.outputSchema,
677
+ resumeSchema: params.resumeSchema,
678
+ suspendSchema: params.suspendSchema,
679
+ execute: params.execute,
680
+ };
390
681
  }
391
682
 
392
683
  export function init(inngest: Inngest) {
@@ -395,13 +686,59 @@ export function init(inngest: Inngest) {
395
686
  TWorkflowId extends string = string,
396
687
  TInput extends z.ZodType<any> = z.ZodType<any>,
397
688
  TOutput extends z.ZodType<any> = z.ZodType<any>,
398
- TSteps extends Step<string, any, any>[] = Step<string, any, any>[],
399
- >(params: NewWorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>) {
400
- return new InngestWorkflow(params, inngest);
689
+ TSteps extends Step<string, any, any, any, any, InngestEngineType>[] = Step<
690
+ string,
691
+ any,
692
+ any,
693
+ any,
694
+ any,
695
+ InngestEngineType
696
+ >[],
697
+ >(params: WorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>) {
698
+ return new InngestWorkflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TInput>(params, inngest);
401
699
  },
402
700
  createStep,
403
- cloneStep,
404
- cloneWorkflow,
701
+ cloneStep<TStepId extends string>(
702
+ step: Step<string, any, any, any, any, InngestEngineType>,
703
+ opts: { id: TStepId },
704
+ ): Step<TStepId, any, any, any, any, InngestEngineType> {
705
+ return {
706
+ id: opts.id,
707
+ description: step.description,
708
+ inputSchema: step.inputSchema,
709
+ outputSchema: step.outputSchema,
710
+ execute: step.execute,
711
+ };
712
+ },
713
+ cloneWorkflow<
714
+ TWorkflowId extends string = string,
715
+ TInput extends z.ZodType<any> = z.ZodType<any>,
716
+ TOutput extends z.ZodType<any> = z.ZodType<any>,
717
+ TSteps extends Step<string, any, any, any, any, InngestEngineType>[] = Step<
718
+ string,
719
+ any,
720
+ any,
721
+ any,
722
+ any,
723
+ InngestEngineType
724
+ >[],
725
+ TPrevSchema extends z.ZodType<any> = TInput,
726
+ >(
727
+ workflow: Workflow<InngestEngineType, TSteps, string, TInput, TOutput, TPrevSchema>,
728
+ opts: { id: TWorkflowId },
729
+ ): Workflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> {
730
+ const wf: Workflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> = new Workflow({
731
+ id: opts.id,
732
+ inputSchema: workflow.inputSchema,
733
+ outputSchema: workflow.outputSchema,
734
+ steps: workflow.stepDefs,
735
+ mastra: workflow.mastra,
736
+ });
737
+
738
+ wf.setStepFlow(workflow.stepGraph);
739
+ wf.commit();
740
+ return wf;
741
+ },
405
742
  };
406
743
  }
407
744
 
@@ -415,11 +752,46 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
415
752
  this.inngestAttempts = inngestAttempts;
416
753
  }
417
754
 
755
+ async execute<TInput, TOutput>(params: {
756
+ workflowId: string;
757
+ runId: string;
758
+ graph: ExecutionGraph;
759
+ serializedStepGraph: SerializedStepFlowEntry[];
760
+ input?: TInput;
761
+ resume?: {
762
+ // TODO: add execute path
763
+ steps: string[];
764
+ stepResults: Record<string, StepResult<any, any, any, any>>;
765
+ resumePayload: any;
766
+ resumePath: number[];
767
+ };
768
+ emitter: Emitter;
769
+ retryConfig?: {
770
+ attempts?: number;
771
+ delay?: number;
772
+ };
773
+ runtimeContext: RuntimeContext;
774
+ }): Promise<TOutput> {
775
+ await params.emitter.emit('watch-v2', {
776
+ type: 'start',
777
+ payload: { runId: params.runId },
778
+ });
779
+
780
+ const result = await super.execute<TInput, TOutput>(params);
781
+
782
+ await params.emitter.emit('watch-v2', {
783
+ type: 'finish',
784
+ payload: { runId: params.runId },
785
+ });
786
+
787
+ return result;
788
+ }
789
+
418
790
  protected async fmtReturnValue<TOutput>(
419
791
  executionSpan: Span | undefined,
420
- emitter: { emit: (event: string, data: any) => Promise<void> },
421
- stepResults: Record<string, StepResult<any>>,
422
- lastOutput: StepResult<any>,
792
+ emitter: Emitter,
793
+ stepResults: Record<string, StepResult<any, any, any, any>>,
794
+ lastOutput: StepResult<any, any, any, any>,
423
795
  error?: Error | string,
424
796
  ): Promise<TOutput> {
425
797
  const base: any = {
@@ -503,16 +875,16 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
503
875
  workflowId: string;
504
876
  runId: string;
505
877
  step: Step<string, any, any>;
506
- stepResults: Record<string, StepResult<any>>;
878
+ stepResults: Record<string, StepResult<any, any, any, any>>;
507
879
  executionContext: ExecutionContext;
508
880
  resume?: {
509
881
  steps: string[];
510
882
  resumePayload: any;
511
883
  };
512
884
  prevOutput: any;
513
- emitter: { emit: (event: string, data: any) => Promise<void> };
885
+ emitter: Emitter;
514
886
  runtimeContext: RuntimeContext;
515
- }): Promise<StepResult<any>> {
887
+ }): Promise<StepResult<any, any, any, any>> {
516
888
  return super.executeStep({
517
889
  workflowId,
518
890
  runId,
@@ -526,6 +898,23 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
526
898
  });
527
899
  }
528
900
 
901
+ async executeSleep({ id, duration }: { id: string; duration: number }): Promise<void> {
902
+ await this.inngestStep.sleep(id, duration);
903
+ }
904
+
905
+ async executeWaitForEvent({ event, timeout }: { event: string; timeout?: number }): Promise<any> {
906
+ const eventData = await this.inngestStep.waitForEvent(`user-event-${event}`, {
907
+ event: `user-event-${event}`,
908
+ timeout: timeout ?? 5e3,
909
+ });
910
+
911
+ if (eventData === null) {
912
+ throw 'Timeout waiting for event';
913
+ }
914
+
915
+ return eventData?.data;
916
+ }
917
+
529
918
  async executeStep({
530
919
  step,
531
920
  stepResults,
@@ -536,7 +925,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
536
925
  runtimeContext,
537
926
  }: {
538
927
  step: Step<string, any, any>;
539
- stepResults: Record<string, StepResult<any>>;
928
+ stepResults: Record<string, StepResult<any, any, any, any>>;
540
929
  executionContext: {
541
930
  workflowId: string;
542
931
  runId: string;
@@ -550,12 +939,13 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
550
939
  runId?: string;
551
940
  };
552
941
  prevOutput: any;
553
- emitter: { emit: (event: string, data: any) => Promise<void> };
942
+ emitter: Emitter;
554
943
  runtimeContext: RuntimeContext;
555
- }): Promise<StepResult<any>> {
556
- await this.inngestStep.run(
944
+ }): Promise<StepResult<any, any, any, any>> {
945
+ const startedAt = await this.inngestStep.run(
557
946
  `workflow.${executionContext.workflowId}.run.${executionContext.runId}.step.${step.id}.running_ev`,
558
947
  async () => {
948
+ const startedAt = Date.now();
559
949
  await emitter.emit('watch', {
560
950
  type: 'watch',
561
951
  payload: {
@@ -577,6 +967,15 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
577
967
  },
578
968
  eventTimestamp: Date.now(),
579
969
  });
970
+
971
+ await emitter.emit('watch-v2', {
972
+ type: 'step-start',
973
+ payload: {
974
+ id: step.id,
975
+ },
976
+ });
977
+
978
+ return startedAt;
580
979
  },
581
980
  );
582
981
 
@@ -643,10 +1042,18 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
643
1042
  eventTimestamp: Date.now(),
644
1043
  });
645
1044
 
1045
+ await emitter.emit('watch-v2', {
1046
+ type: 'step-result',
1047
+ payload: {
1048
+ id: step.id,
1049
+ status: 'failed',
1050
+ },
1051
+ });
1052
+
646
1053
  return { executionContext, result: { status: 'failed', error: result?.error } };
647
1054
  } else if (result.status === 'suspended') {
648
1055
  const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
649
- const stepRes: StepResult<any> = stepResult as StepResult<any>;
1056
+ const stepRes: StepResult<any, any, any, any> = stepResult as StepResult<any, any, any, any>;
650
1057
  return stepRes?.status === 'suspended';
651
1058
  });
652
1059
 
@@ -673,6 +1080,13 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
673
1080
  eventTimestamp: Date.now(),
674
1081
  });
675
1082
 
1083
+ await emitter.emit('watch-v2', {
1084
+ type: 'step-suspended',
1085
+ payload: {
1086
+ id: step.id,
1087
+ },
1088
+ });
1089
+
676
1090
  return {
677
1091
  executionContext,
678
1092
  result: {
@@ -729,19 +1143,29 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
729
1143
  eventTimestamp: Date.now(),
730
1144
  });
731
1145
 
1146
+ await emitter.emit('watch-v2', {
1147
+ type: 'step-finish',
1148
+ payload: {
1149
+ id: step.id,
1150
+ metadata: {},
1151
+ },
1152
+ });
1153
+
732
1154
  return { executionContext, result: { status: 'success', output: result?.result } };
733
1155
  },
734
1156
  );
735
1157
 
736
1158
  Object.assign(executionContext, res.executionContext);
737
- return res.result as StepResult<any>;
1159
+ return res.result as StepResult<any, any, any, any>;
738
1160
  }
739
1161
 
740
1162
  const stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
741
1163
  let execResults: any;
742
1164
  let suspended: { payload: any } | undefined;
1165
+
743
1166
  try {
744
1167
  const result = await step.execute({
1168
+ runId: executionContext.runId,
745
1169
  mastra: this.mastra!,
746
1170
  runtimeContext,
747
1171
  inputData: prevOutput,
@@ -765,16 +1189,44 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
765
1189
  // @ts-ignore
766
1190
  runId: stepResults[step.id]?.payload?.__workflow_meta?.runId,
767
1191
  },
768
- emitter,
1192
+ [EMITTER_SYMBOL]: emitter,
1193
+ engine: {
1194
+ step: this.inngestStep,
1195
+ },
769
1196
  });
770
-
771
- execResults = { status: 'success', output: result };
1197
+ const endedAt = Date.now();
1198
+
1199
+ execResults = {
1200
+ status: 'success',
1201
+ output: result,
1202
+ startedAt,
1203
+ endedAt,
1204
+ payload: prevOutput,
1205
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1206
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1207
+ };
772
1208
  } catch (e) {
773
- execResults = { status: 'failed', error: e instanceof Error ? e.message : String(e) };
1209
+ execResults = {
1210
+ status: 'failed',
1211
+ payload: prevOutput,
1212
+ error: e instanceof Error ? e.message : String(e),
1213
+ endedAt: Date.now(),
1214
+ startedAt,
1215
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1216
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1217
+ };
774
1218
  }
775
1219
 
776
1220
  if (suspended) {
777
- execResults = { status: 'suspended', payload: suspended.payload };
1221
+ execResults = {
1222
+ status: 'suspended',
1223
+ suspendedPayload: suspended.payload,
1224
+ payload: prevOutput,
1225
+ suspendedAt: Date.now(),
1226
+ startedAt,
1227
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1228
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1229
+ };
778
1230
  }
779
1231
 
780
1232
  if (execResults.status === 'failed') {
@@ -788,12 +1240,11 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
788
1240
  payload: {
789
1241
  currentStep: {
790
1242
  id: step.id,
791
- status: execResults.status,
792
- output: execResults.output,
1243
+ ...execResults,
793
1244
  },
794
1245
  workflowState: {
795
1246
  status: 'running',
796
- steps: stepResults,
1247
+ steps: { ...stepResults, [step.id]: execResults },
797
1248
  result: null,
798
1249
  error: null,
799
1250
  },
@@ -801,6 +1252,34 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
801
1252
  eventTimestamp: Date.now(),
802
1253
  });
803
1254
 
1255
+ if (execResults.status === 'suspended') {
1256
+ await emitter.emit('watch-v2', {
1257
+ type: 'step-suspended',
1258
+ payload: {
1259
+ id: step.id,
1260
+ status: execResults.status,
1261
+ output: execResults.status === 'success' ? execResults?.output : undefined,
1262
+ },
1263
+ });
1264
+ } else {
1265
+ await emitter.emit('watch-v2', {
1266
+ type: 'step-result',
1267
+ payload: {
1268
+ id: step.id,
1269
+ status: execResults.status,
1270
+ output: execResults.status === 'success' ? execResults?.output : undefined,
1271
+ },
1272
+ });
1273
+
1274
+ await emitter.emit('watch-v2', {
1275
+ type: 'step-finish',
1276
+ payload: {
1277
+ id: step.id,
1278
+ metadata: {},
1279
+ },
1280
+ });
1281
+ }
1282
+
804
1283
  return { result: execResults, executionContext, stepResults };
805
1284
  });
806
1285
 
@@ -818,11 +1297,19 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
818
1297
  runId,
819
1298
  stepResults,
820
1299
  executionContext,
1300
+ serializedStepGraph,
1301
+ workflowStatus,
1302
+ result,
1303
+ error,
821
1304
  }: {
822
1305
  workflowId: string;
823
1306
  runId: string;
824
- stepResults: Record<string, StepResult<any>>;
1307
+ stepResults: Record<string, StepResult<any, any, any, any>>;
1308
+ serializedStepGraph: SerializedStepFlowEntry[];
825
1309
  executionContext: ExecutionContext;
1310
+ workflowStatus: 'success' | 'failed' | 'suspended' | 'running';
1311
+ result?: Record<string, any>;
1312
+ error?: string | Error;
826
1313
  }) {
827
1314
  await this.inngestStep.run(
828
1315
  `workflow.${workflowId}.run.${runId}.path.${JSON.stringify(executionContext.executionPath)}.stepUpdate`,
@@ -836,6 +1323,10 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
836
1323
  context: stepResults as any,
837
1324
  activePaths: [],
838
1325
  suspendedPaths: executionContext.suspendedPaths,
1326
+ serializedStepGraph,
1327
+ status: workflowStatus,
1328
+ result,
1329
+ error,
839
1330
  // @ts-ignore
840
1331
  timestamp: Date.now(),
841
1332
  },
@@ -851,6 +1342,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
851
1342
  prevOutput,
852
1343
  prevStep,
853
1344
  stepResults,
1345
+ serializedStepGraph,
854
1346
  resume,
855
1347
  executionContext,
856
1348
  emitter,
@@ -858,20 +1350,25 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
858
1350
  }: {
859
1351
  workflowId: string;
860
1352
  runId: string;
861
- entry: { type: 'conditional'; steps: StepFlowEntry[]; conditions: ExecuteFunction<any, any, any, any>[] };
1353
+ entry: {
1354
+ type: 'conditional';
1355
+ steps: StepFlowEntry[];
1356
+ conditions: ExecuteFunction<any, any, any, any, InngestEngineType>[];
1357
+ };
862
1358
  prevStep: StepFlowEntry;
1359
+ serializedStepGraph: SerializedStepFlowEntry[];
863
1360
  prevOutput: any;
864
- stepResults: Record<string, StepResult<any>>;
1361
+ stepResults: Record<string, StepResult<any, any, any, any>>;
865
1362
  resume?: {
866
1363
  steps: string[];
867
- stepResults: Record<string, StepResult<any>>;
1364
+ stepResults: Record<string, StepResult<any, any, any, any>>;
868
1365
  resumePayload: any;
869
1366
  resumePath: number[];
870
1367
  };
871
1368
  executionContext: ExecutionContext;
872
- emitter: { emit: (event: string, data: any) => Promise<void> };
1369
+ emitter: Emitter;
873
1370
  runtimeContext: RuntimeContext;
874
- }): Promise<StepResult<any>> {
1371
+ }): Promise<StepResult<any, any, any, any>> {
875
1372
  let execResults: any;
876
1373
  const truthyIndexes = (
877
1374
  await Promise.all(
@@ -879,6 +1376,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
879
1376
  this.inngestStep.run(`workflow.${workflowId}.conditional.${index}`, async () => {
880
1377
  try {
881
1378
  const result = await cond({
1379
+ runId,
882
1380
  mastra: this.mastra!,
883
1381
  runtimeContext,
884
1382
  inputData: prevOutput,
@@ -898,7 +1396,10 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
898
1396
 
899
1397
  // TODO: this function shouldn't have suspend probably?
900
1398
  suspend: async (_suspendPayload: any) => {},
901
- emitter,
1399
+ [EMITTER_SYMBOL]: emitter,
1400
+ engine: {
1401
+ step: this.inngestStep,
1402
+ },
902
1403
  });
903
1404
  return result ? index : null;
904
1405
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -911,7 +1412,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
911
1412
  ).filter((index: any): index is number => index !== null);
912
1413
 
913
1414
  const stepsToRun = entry.steps.filter((_, index) => truthyIndexes.includes(index));
914
- const results: StepResult<any>[] = await Promise.all(
1415
+ const results: { result: StepResult<any, any, any, any> }[] = await Promise.all(
915
1416
  stepsToRun.map((step, index) =>
916
1417
  this.executeEntry({
917
1418
  workflowId,
@@ -920,6 +1421,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
920
1421
  prevStep,
921
1422
  stepResults,
922
1423
  resume,
1424
+ serializedStepGraph,
923
1425
  executionContext: {
924
1426
  workflowId,
925
1427
  runId,
@@ -933,17 +1435,19 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
933
1435
  }),
934
1436
  ),
935
1437
  );
936
- const hasFailed = results.find(result => result.status === 'failed');
937
- const hasSuspended = results.find(result => result.status === 'suspended');
1438
+ const hasFailed = results.find(result => result.result.status === 'failed') as {
1439
+ result: StepFailure<any, any, any>;
1440
+ };
1441
+ const hasSuspended = results.find(result => result.result.status === 'suspended');
938
1442
  if (hasFailed) {
939
- execResults = { status: 'failed', error: hasFailed.error };
1443
+ execResults = { status: 'failed', error: hasFailed.result.error };
940
1444
  } else if (hasSuspended) {
941
- execResults = { status: 'suspended', payload: hasSuspended.payload };
1445
+ execResults = { status: 'suspended', payload: hasSuspended.result.suspendPayload };
942
1446
  } else {
943
1447
  execResults = {
944
1448
  status: 'success',
945
1449
  output: results.reduce((acc: Record<string, any>, result, index) => {
946
- if (result.status === 'success') {
1450
+ if (result.result.status === 'success') {
947
1451
  // @ts-ignore
948
1452
  acc[stepsToRun[index]!.step.id] = result.output;
949
1453
  }