@mastra/inngest 0.0.0-cloud-transporter-20250513033346 → 0.0.0-custom-instrumentation-20250626084921

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,38 @@
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 } from '@mastra/core';
4
+ import type { Agent, Mastra, ToolExecutionContext, WorkflowRun, WorkflowRuns } from '@mastra/core';
4
5
  import { RuntimeContext } from '@mastra/core/di';
5
- import { NewWorkflow, createStep, Run, DefaultExecutionEngine, cloneStep } from '@mastra/core/workflows/vNext';
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,
9
11
  ExecutionEngine,
10
12
  ExecutionGraph,
11
- NewStep,
12
- NewStep as Step,
13
- NewWorkflowConfig,
13
+ Step,
14
+ WorkflowConfig,
14
15
  StepFlowEntry,
15
16
  StepResult,
16
17
  WorkflowResult,
17
- } from '@mastra/core/workflows/vNext';
18
+ SerializedStepFlowEntry,
19
+ StepFailure,
20
+ Emitter,
21
+ WatchEvent,
22
+ StreamEvent,
23
+ } from '@mastra/core/workflows';
24
+ import { EMITTER_SYMBOL } from '@mastra/core/workflows/_constants';
18
25
  import type { Span } from '@opentelemetry/api';
19
26
  import type { Inngest, BaseContext } from 'inngest';
20
27
  import { serve as inngestServe } from 'inngest/hono';
21
- import type { z } from 'zod';
28
+ import { z } from 'zod';
29
+
30
+ export type InngestEngineType = {
31
+ step: any;
32
+ };
22
33
 
23
34
  export function serve({ mastra, inngest }: { mastra: Mastra; inngest: Inngest }): ReturnType<typeof inngestServe> {
24
- const wfs = mastra.vnext_getWorkflows();
35
+ const wfs = mastra.getWorkflows();
25
36
  const functions = Object.values(wfs).flatMap(wf => {
26
37
  if (wf instanceof InngestWorkflow) {
27
38
  wf.__registerMastra(mastra);
@@ -36,11 +47,13 @@ export function serve({ mastra, inngest }: { mastra: Mastra; inngest: Inngest })
36
47
  }
37
48
 
38
49
  export class InngestRun<
39
- TSteps extends NewStep<string, any, any>[] = NewStep<string, any, any>[],
50
+ TEngineType = InngestEngineType,
51
+ TSteps extends Step<string, any, any>[] = Step<string, any, any>[],
40
52
  TInput extends z.ZodType<any> = z.ZodType<any>,
41
53
  TOutput extends z.ZodType<any> = z.ZodType<any>,
42
- > extends Run<TSteps, TInput, TOutput> {
54
+ > extends Run<TEngineType, TSteps, TInput, TOutput> {
43
55
  private inngest: Inngest;
56
+ serializedStepGraph: SerializedStepFlowEntry[];
44
57
  #mastra: Mastra;
45
58
 
46
59
  constructor(
@@ -49,6 +62,7 @@ export class InngestRun<
49
62
  runId: string;
50
63
  executionEngine: ExecutionEngine;
51
64
  executionGraph: ExecutionGraph;
65
+ serializedStepGraph: SerializedStepFlowEntry[];
52
66
  mastra?: Mastra;
53
67
  retryConfig?: {
54
68
  attempts?: number;
@@ -60,11 +74,12 @@ export class InngestRun<
60
74
  ) {
61
75
  super(params);
62
76
  this.inngest = inngest;
77
+ this.serializedStepGraph = params.serializedStepGraph;
63
78
  this.#mastra = params.mastra!;
64
79
  }
65
80
 
66
81
  async getRuns(eventId: string) {
67
- const response = await fetch(`${this.inngest.apiBaseUrl}/v1/events/${eventId}/runs`, {
82
+ const response = await fetch(`${this.inngest.apiBaseUrl ?? 'https://api.inngest.com'}/v1/events/${eventId}/runs`, {
68
83
  headers: {
69
84
  Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`,
70
85
  },
@@ -75,7 +90,8 @@ export class InngestRun<
75
90
 
76
91
  async getRunOutput(eventId: string) {
77
92
  let runs = await this.getRuns(eventId);
78
- while (runs?.[0]?.status !== 'Completed') {
93
+
94
+ while (runs?.[0]?.status !== 'Completed' || runs?.[0]?.event_id !== eventId) {
79
95
  await new Promise(resolve => setTimeout(resolve, 1000));
80
96
  runs = await this.getRuns(eventId);
81
97
  if (runs?.[0]?.status === 'Failed' || runs?.[0]?.status === 'Cancelled') {
@@ -85,6 +101,13 @@ export class InngestRun<
85
101
  return runs?.[0];
86
102
  }
87
103
 
104
+ async sendEvent(event: string, data: any) {
105
+ await this.inngest.send({
106
+ name: `user-event-${event}`,
107
+ data,
108
+ });
109
+ }
110
+
88
111
  async start({
89
112
  inputData,
90
113
  }: {
@@ -96,11 +119,13 @@ export class InngestRun<
96
119
  runId: this.runId,
97
120
  snapshot: {
98
121
  runId: this.runId,
122
+ serializedStepGraph: this.serializedStepGraph,
99
123
  value: {},
100
124
  context: {} as any,
101
125
  activePaths: [],
102
126
  suspendedPaths: {},
103
127
  timestamp: Date.now(),
128
+ status: 'running',
104
129
  },
105
130
  });
106
131
 
@@ -134,6 +159,27 @@ export class InngestRun<
134
159
  | string
135
160
  | string[];
136
161
  runtimeContext?: RuntimeContext;
162
+ }): Promise<WorkflowResult<TOutput, TSteps>> {
163
+ const p = this._resume(params).then(result => {
164
+ if (result.status !== 'suspended') {
165
+ this.closeStreamAction?.().catch(() => {});
166
+ }
167
+
168
+ return result;
169
+ });
170
+
171
+ this.executionResults = p;
172
+ return p;
173
+ }
174
+
175
+ async _resume<TResumeSchema extends z.ZodType<any>>(params: {
176
+ resumeData?: z.infer<TResumeSchema>;
177
+ step:
178
+ | Step<string, any, any, TResumeSchema, any>
179
+ | [...Step<string, any, any, any, any>[], Step<string, any, any, TResumeSchema, any>]
180
+ | string
181
+ | string[];
182
+ runtimeContext?: RuntimeContext;
137
183
  }): Promise<WorkflowResult<TOutput, TSteps>> {
138
184
  const steps: string[] = (Array.isArray(params.step) ? params.step : [params.step]).map(step =>
139
185
  typeof step === 'string' ? step : step?.id,
@@ -171,43 +217,88 @@ export class InngestRun<
171
217
  return result;
172
218
  }
173
219
 
174
- watch(cb: (event: any) => void): () => void {
220
+ watch(cb: (event: WatchEvent) => void, type: 'watch' | 'watch-v2' = 'watch'): () => void {
221
+ let active = true;
175
222
  const streamPromise = subscribe(
176
223
  {
177
224
  channel: `workflow:${this.workflowId}:${this.runId}`,
178
- topics: ['watch'],
225
+ topics: [type],
179
226
  app: this.inngest,
180
227
  },
181
228
  (message: any) => {
182
- cb(message.data);
229
+ if (active) {
230
+ cb(message.data);
231
+ }
183
232
  },
184
233
  );
185
234
 
186
235
  return () => {
236
+ active = false;
187
237
  streamPromise
188
- .then((stream: any) => {
189
- stream.cancel();
238
+ .then(async (stream: Awaited<typeof streamPromise>) => {
239
+ return stream.cancel();
190
240
  })
191
241
  .catch(err => {
192
242
  console.error(err);
193
243
  });
194
244
  };
195
245
  }
246
+
247
+ stream({ inputData, runtimeContext }: { inputData?: z.infer<TInput>; runtimeContext?: RuntimeContext } = {}): {
248
+ stream: ReadableStream<StreamEvent>;
249
+ getWorkflowState: () => Promise<WorkflowResult<TOutput, TSteps>>;
250
+ } {
251
+ const { readable, writable } = new TransformStream<StreamEvent, StreamEvent>();
252
+
253
+ const writer = writable.getWriter();
254
+ const unwatch = this.watch(async event => {
255
+ try {
256
+ // watch-v2 events are data stream events, so we need to cast them to the correct type
257
+ await writer.write(event as any);
258
+ } catch {}
259
+ }, 'watch-v2');
260
+
261
+ this.closeStreamAction = async () => {
262
+ unwatch();
263
+
264
+ try {
265
+ await writer.close();
266
+ } catch (err) {
267
+ console.error('Error closing stream:', err);
268
+ } finally {
269
+ writer.releaseLock();
270
+ }
271
+ };
272
+
273
+ this.executionResults = this.start({ inputData, runtimeContext }).then(result => {
274
+ if (result.status !== 'suspended') {
275
+ this.closeStreamAction?.().catch(() => {});
276
+ }
277
+
278
+ return result;
279
+ });
280
+
281
+ return {
282
+ stream: readable as ReadableStream<StreamEvent>,
283
+ getWorkflowState: () => this.executionResults!,
284
+ };
285
+ }
196
286
  }
197
287
 
198
288
  export class InngestWorkflow<
199
- TSteps extends NewStep<string, any, any>[] = NewStep<string, any, any>[],
289
+ TEngineType = InngestEngineType,
290
+ TSteps extends Step<string, any, any>[] = Step<string, any, any>[],
200
291
  TWorkflowId extends string = string,
201
292
  TInput extends z.ZodType<any> = z.ZodType<any>,
202
293
  TOutput extends z.ZodType<any> = z.ZodType<any>,
203
294
  TPrevSchema extends z.ZodType<any> = TInput,
204
- > extends NewWorkflow<TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> {
295
+ > extends Workflow<TEngineType, TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> {
205
296
  #mastra: Mastra;
206
297
  public inngest: Inngest;
207
298
 
208
299
  private function: ReturnType<Inngest['createFunction']> | undefined;
209
300
 
210
- constructor(params: NewWorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>, inngest: Inngest) {
301
+ constructor(params: WorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>, inngest: Inngest) {
211
302
  super(params);
212
303
  this.#mastra = params.mastra!;
213
304
  this.inngest = inngest;
@@ -226,16 +317,19 @@ export class InngestWorkflow<
226
317
  return { runs: [], total: 0 };
227
318
  }
228
319
 
229
- return storage.getWorkflowRuns({ workflowName: this.id, ...(args ?? {}) });
320
+ return storage.getWorkflowRuns({ workflowName: this.id, ...(args ?? {}) }) as unknown as WorkflowRuns;
230
321
  }
231
322
 
232
323
  async getWorkflowRunById(runId: string): Promise<WorkflowRun | null> {
233
324
  const storage = this.#mastra?.getStorage();
234
325
  if (!storage) {
235
326
  this.logger.debug('Cannot get workflow runs. Mastra engine is not initialized');
236
- return null;
327
+ //returning in memory run if no storage is initialized
328
+ return this.runs.get(runId)
329
+ ? ({ ...this.runs.get(runId), workflowName: this.id } as unknown as WorkflowRun)
330
+ : null;
237
331
  }
238
- const run = await storage.getWorkflowRunById({ runId, workflowName: this.id });
332
+ const run = (await storage.getWorkflowRunById({ runId, workflowName: this.id })) as unknown as WorkflowRun;
239
333
 
240
334
  return (
241
335
  run ??
@@ -243,6 +337,32 @@ export class InngestWorkflow<
243
337
  );
244
338
  }
245
339
 
340
+ async getWorkflowRunExecutionResult(runId: string): Promise<WatchEvent['payload']['workflowState'] | null> {
341
+ const storage = this.#mastra?.getStorage();
342
+ if (!storage) {
343
+ this.logger.debug('Cannot get workflow run execution result. Mastra storage is not initialized');
344
+ return null;
345
+ }
346
+
347
+ const run = await storage.getWorkflowRunById({ runId, workflowName: this.id });
348
+
349
+ if (!run?.snapshot) {
350
+ return null;
351
+ }
352
+
353
+ if (typeof run.snapshot === 'string') {
354
+ return null;
355
+ }
356
+
357
+ return {
358
+ status: run.snapshot.status,
359
+ result: run.snapshot.result,
360
+ error: run.snapshot.error,
361
+ payload: run.snapshot.context?.input,
362
+ steps: run.snapshot.context as any,
363
+ };
364
+ }
365
+
246
366
  __registerMastra(mastra: Mastra) {
247
367
  this.#mastra = mastra;
248
368
  this.executionEngine.__registerMastra(mastra);
@@ -266,11 +386,35 @@ export class InngestWorkflow<
266
386
  }
267
387
  }
268
388
 
269
- createRun(options?: { runId?: string }): Run<TSteps, TInput, TOutput> {
389
+ createRun(options?: { runId?: string }): Run<TEngineType, TSteps, TInput, TOutput> {
390
+ const runIdToUse = options?.runId || randomUUID();
391
+
392
+ // Return a new Run instance with object parameters
393
+ const run: Run<TEngineType, TSteps, TInput, TOutput> =
394
+ this.runs.get(runIdToUse) ??
395
+ new InngestRun(
396
+ {
397
+ workflowId: this.id,
398
+ runId: runIdToUse,
399
+ executionEngine: this.executionEngine,
400
+ executionGraph: this.executionGraph,
401
+ serializedStepGraph: this.serializedStepGraph,
402
+ mastra: this.#mastra,
403
+ retryConfig: this.retryConfig,
404
+ cleanup: () => this.runs.delete(runIdToUse),
405
+ },
406
+ this.inngest,
407
+ );
408
+
409
+ this.runs.set(runIdToUse, run);
410
+ return run;
411
+ }
412
+
413
+ async createRunAsync(options?: { runId?: string }): Promise<Run<TEngineType, TSteps, TInput, TOutput>> {
270
414
  const runIdToUse = options?.runId || randomUUID();
271
415
 
272
416
  // Return a new Run instance with object parameters
273
- const run: Run<TSteps, TInput, TOutput> =
417
+ const run: Run<TEngineType, TSteps, TInput, TOutput> =
274
418
  this.runs.get(runIdToUse) ??
275
419
  new InngestRun(
276
420
  {
@@ -278,6 +422,7 @@ export class InngestWorkflow<
278
422
  runId: runIdToUse,
279
423
  executionEngine: this.executionEngine,
280
424
  executionGraph: this.executionGraph,
425
+ serializedStepGraph: this.serializedStepGraph,
281
426
  mastra: this.#mastra,
282
427
  retryConfig: this.retryConfig,
283
428
  cleanup: () => this.runs.delete(runIdToUse),
@@ -286,6 +431,25 @@ export class InngestWorkflow<
286
431
  );
287
432
 
288
433
  this.runs.set(runIdToUse, run);
434
+
435
+ await this.mastra?.getStorage()?.persistWorkflowSnapshot({
436
+ workflowName: this.id,
437
+ runId: runIdToUse,
438
+ snapshot: {
439
+ runId: runIdToUse,
440
+ status: 'pending',
441
+ value: {},
442
+ context: {},
443
+ activePaths: [],
444
+ serializedStepGraph: this.serializedStepGraph,
445
+ suspendedPaths: {},
446
+ result: undefined,
447
+ error: undefined,
448
+ // @ts-ignore
449
+ timestamp: Date.now(),
450
+ },
451
+ });
452
+
289
453
  return run;
290
454
  }
291
455
 
@@ -315,13 +479,22 @@ export class InngestWorkflow<
315
479
  try {
316
480
  await publish({
317
481
  channel: `workflow:${this.id}:${runId}`,
318
- topic: 'watch',
482
+ topic: event,
319
483
  data,
320
484
  });
321
485
  } catch (err: any) {
322
486
  this.logger.error('Error emitting event: ' + (err?.stack ?? err?.message ?? err));
323
487
  }
324
488
  },
489
+ on: (_event: string, _callback: (data: any) => void) => {
490
+ // no-op
491
+ },
492
+ off: (_event: string, _callback: (data: any) => void) => {
493
+ // no-op
494
+ },
495
+ once: (_event: string, _callback: (data: any) => void) => {
496
+ // no-op
497
+ },
325
498
  };
326
499
 
327
500
  const engine = new InngestExecutionEngine(this.#mastra, step, attempt);
@@ -329,6 +502,7 @@ export class InngestWorkflow<
329
502
  workflowId: this.id,
330
503
  runId,
331
504
  graph: this.executionGraph,
505
+ serializedStepGraph: this.serializedStepGraph,
332
506
  input: inputData,
333
507
  emitter,
334
508
  retryConfig: this.retryConfig,
@@ -362,29 +536,192 @@ export class InngestWorkflow<
362
536
  }
363
537
  }
364
538
 
365
- function cloneWorkflow<
366
- TWorkflowId extends string = string,
367
- TInput extends z.ZodType<any> = z.ZodType<any>,
368
- TOutput extends z.ZodType<any> = z.ZodType<any>,
369
- TSteps extends Step<string, any, any, any, any>[] = Step<string, any, any, any, any>[],
539
+ function isAgent(params: any): params is Agent<any, any, any> {
540
+ return params?.component === 'AGENT';
541
+ }
542
+
543
+ function isTool(params: any): params is Tool<any, any, any> {
544
+ return params instanceof Tool;
545
+ }
546
+
547
+ export function createStep<
548
+ TStepId extends string,
549
+ TStepInput extends z.ZodType<any>,
550
+ TStepOutput extends z.ZodType<any>,
551
+ TResumeSchema extends z.ZodType<any>,
552
+ TSuspendSchema extends z.ZodType<any>,
553
+ >(params: {
554
+ id: TStepId;
555
+ description?: string;
556
+ inputSchema: TStepInput;
557
+ outputSchema: TStepOutput;
558
+ resumeSchema?: TResumeSchema;
559
+ suspendSchema?: TSuspendSchema;
560
+ execute: ExecuteFunction<
561
+ z.infer<TStepInput>,
562
+ z.infer<TStepOutput>,
563
+ z.infer<TResumeSchema>,
564
+ z.infer<TSuspendSchema>,
565
+ InngestEngineType
566
+ >;
567
+ }): Step<TStepId, TStepInput, TStepOutput, TResumeSchema, TSuspendSchema, InngestEngineType>;
568
+
569
+ export function createStep<
570
+ TStepId extends string,
571
+ TStepInput extends z.ZodObject<{ prompt: z.ZodString }>,
572
+ TStepOutput extends z.ZodObject<{ text: z.ZodString }>,
573
+ TResumeSchema extends z.ZodType<any>,
574
+ TSuspendSchema extends z.ZodType<any>,
370
575
  >(
371
- workflow: InngestWorkflow<TSteps, string, TInput, TOutput>,
372
- opts: { id: TWorkflowId },
373
- ): InngestWorkflow<TSteps, TWorkflowId, TInput, TOutput> {
374
- const wf = new InngestWorkflow(
375
- {
376
- id: opts.id,
377
- inputSchema: workflow.inputSchema,
378
- outputSchema: workflow.outputSchema,
379
- steps: workflow.stepDefs,
380
- mastra: workflow.mastra,
381
- },
382
- workflow.inngest,
383
- );
576
+ agent: Agent<TStepId, any, any>,
577
+ ): Step<TStepId, TStepInput, TStepOutput, TResumeSchema, TSuspendSchema, InngestEngineType>;
384
578
 
385
- wf.setStepFlow(workflow.stepGraph);
386
- wf.commit();
387
- return wf;
579
+ export function createStep<
580
+ TSchemaIn extends z.ZodType<any>,
581
+ TSchemaOut extends z.ZodType<any>,
582
+ TContext extends ToolExecutionContext<TSchemaIn>,
583
+ >(
584
+ tool: Tool<TSchemaIn, TSchemaOut, TContext> & {
585
+ inputSchema: TSchemaIn;
586
+ outputSchema: TSchemaOut;
587
+ execute: (context: TContext) => Promise<any>;
588
+ },
589
+ ): Step<string, TSchemaIn, TSchemaOut, z.ZodType<any>, z.ZodType<any>, InngestEngineType>;
590
+ export function createStep<
591
+ TStepId extends string,
592
+ TStepInput extends z.ZodType<any>,
593
+ TStepOutput extends z.ZodType<any>,
594
+ TResumeSchema extends z.ZodType<any>,
595
+ TSuspendSchema extends z.ZodType<any>,
596
+ >(
597
+ params:
598
+ | {
599
+ id: TStepId;
600
+ description?: string;
601
+ inputSchema: TStepInput;
602
+ outputSchema: TStepOutput;
603
+ resumeSchema?: TResumeSchema;
604
+ suspendSchema?: TSuspendSchema;
605
+ execute: ExecuteFunction<
606
+ z.infer<TStepInput>,
607
+ z.infer<TStepOutput>,
608
+ z.infer<TResumeSchema>,
609
+ z.infer<TSuspendSchema>,
610
+ InngestEngineType
611
+ >;
612
+ }
613
+ | Agent<any, any, any>
614
+ | (Tool<TStepInput, TStepOutput, any> & {
615
+ inputSchema: TStepInput;
616
+ outputSchema: TStepOutput;
617
+ execute: (context: ToolExecutionContext<TStepInput>) => Promise<any>;
618
+ }),
619
+ ): Step<TStepId, TStepInput, TStepOutput, TResumeSchema, TSuspendSchema, InngestEngineType> {
620
+ if (isAgent(params)) {
621
+ return {
622
+ id: params.name,
623
+ // @ts-ignore
624
+ inputSchema: z.object({
625
+ prompt: z.string(),
626
+ // resourceId: z.string().optional(),
627
+ // threadId: z.string().optional(),
628
+ }),
629
+ // @ts-ignore
630
+ outputSchema: z.object({
631
+ text: z.string(),
632
+ }),
633
+ execute: async ({ inputData, [EMITTER_SYMBOL]: emitter, runtimeContext }) => {
634
+ let streamPromise = {} as {
635
+ promise: Promise<string>;
636
+ resolve: (value: string) => void;
637
+ reject: (reason?: any) => void;
638
+ };
639
+
640
+ streamPromise.promise = new Promise((resolve, reject) => {
641
+ streamPromise.resolve = resolve;
642
+ streamPromise.reject = reject;
643
+ });
644
+ const toolData = {
645
+ name: params.name,
646
+ args: inputData,
647
+ };
648
+ await emitter.emit('watch-v2', {
649
+ type: 'tool-call-streaming-start',
650
+ ...toolData,
651
+ });
652
+ const { fullStream } = await params.stream(inputData.prompt, {
653
+ // resourceId: inputData.resourceId,
654
+ // threadId: inputData.threadId,
655
+ runtimeContext,
656
+ onFinish: result => {
657
+ streamPromise.resolve(result.text);
658
+ },
659
+ });
660
+
661
+ for await (const chunk of fullStream) {
662
+ switch (chunk.type) {
663
+ case 'text-delta':
664
+ await emitter.emit('watch-v2', {
665
+ type: 'tool-call-delta',
666
+ ...toolData,
667
+ argsTextDelta: chunk.textDelta,
668
+ });
669
+ break;
670
+
671
+ case 'step-start':
672
+ case 'step-finish':
673
+ case 'finish':
674
+ break;
675
+
676
+ case 'tool-call':
677
+ case 'tool-result':
678
+ case 'tool-call-streaming-start':
679
+ case 'tool-call-delta':
680
+ case 'source':
681
+ case 'file':
682
+ default:
683
+ await emitter.emit('watch-v2', chunk);
684
+ break;
685
+ }
686
+ }
687
+
688
+ return {
689
+ text: await streamPromise.promise,
690
+ };
691
+ },
692
+ };
693
+ }
694
+
695
+ if (isTool(params)) {
696
+ if (!params.inputSchema || !params.outputSchema) {
697
+ throw new Error('Tool must have input and output schemas defined');
698
+ }
699
+
700
+ return {
701
+ // TODO: tool probably should have strong id type
702
+ // @ts-ignore
703
+ id: params.id,
704
+ inputSchema: params.inputSchema,
705
+ outputSchema: params.outputSchema,
706
+ execute: async ({ inputData, mastra, runtimeContext }) => {
707
+ return params.execute({
708
+ context: inputData,
709
+ mastra,
710
+ runtimeContext,
711
+ });
712
+ },
713
+ };
714
+ }
715
+
716
+ return {
717
+ id: params.id,
718
+ description: params.description,
719
+ inputSchema: params.inputSchema,
720
+ outputSchema: params.outputSchema,
721
+ resumeSchema: params.resumeSchema,
722
+ suspendSchema: params.suspendSchema,
723
+ execute: params.execute,
724
+ };
388
725
  }
389
726
 
390
727
  export function init(inngest: Inngest) {
@@ -393,13 +730,59 @@ export function init(inngest: Inngest) {
393
730
  TWorkflowId extends string = string,
394
731
  TInput extends z.ZodType<any> = z.ZodType<any>,
395
732
  TOutput extends z.ZodType<any> = z.ZodType<any>,
396
- TSteps extends Step<string, any, any>[] = Step<string, any, any>[],
397
- >(params: NewWorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>) {
398
- return new InngestWorkflow(params, inngest);
733
+ TSteps extends Step<string, any, any, any, any, InngestEngineType>[] = Step<
734
+ string,
735
+ any,
736
+ any,
737
+ any,
738
+ any,
739
+ InngestEngineType
740
+ >[],
741
+ >(params: WorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>) {
742
+ return new InngestWorkflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TInput>(params, inngest);
399
743
  },
400
744
  createStep,
401
- cloneStep,
402
- cloneWorkflow,
745
+ cloneStep<TStepId extends string>(
746
+ step: Step<string, any, any, any, any, InngestEngineType>,
747
+ opts: { id: TStepId },
748
+ ): Step<TStepId, any, any, any, any, InngestEngineType> {
749
+ return {
750
+ id: opts.id,
751
+ description: step.description,
752
+ inputSchema: step.inputSchema,
753
+ outputSchema: step.outputSchema,
754
+ execute: step.execute,
755
+ };
756
+ },
757
+ cloneWorkflow<
758
+ TWorkflowId extends string = string,
759
+ TInput extends z.ZodType<any> = z.ZodType<any>,
760
+ TOutput extends z.ZodType<any> = z.ZodType<any>,
761
+ TSteps extends Step<string, any, any, any, any, InngestEngineType>[] = Step<
762
+ string,
763
+ any,
764
+ any,
765
+ any,
766
+ any,
767
+ InngestEngineType
768
+ >[],
769
+ TPrevSchema extends z.ZodType<any> = TInput,
770
+ >(
771
+ workflow: Workflow<InngestEngineType, TSteps, string, TInput, TOutput, TPrevSchema>,
772
+ opts: { id: TWorkflowId },
773
+ ): Workflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> {
774
+ const wf: Workflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> = new Workflow({
775
+ id: opts.id,
776
+ inputSchema: workflow.inputSchema,
777
+ outputSchema: workflow.outputSchema,
778
+ steps: workflow.stepDefs,
779
+ mastra: workflow.mastra,
780
+ });
781
+
782
+ wf.setStepFlow(workflow.stepGraph);
783
+ wf.commit();
784
+ return wf;
785
+ },
403
786
  };
404
787
  }
405
788
 
@@ -413,11 +796,46 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
413
796
  this.inngestAttempts = inngestAttempts;
414
797
  }
415
798
 
799
+ async execute<TInput, TOutput>(params: {
800
+ workflowId: string;
801
+ runId: string;
802
+ graph: ExecutionGraph;
803
+ serializedStepGraph: SerializedStepFlowEntry[];
804
+ input?: TInput;
805
+ resume?: {
806
+ // TODO: add execute path
807
+ steps: string[];
808
+ stepResults: Record<string, StepResult<any, any, any, any>>;
809
+ resumePayload: any;
810
+ resumePath: number[];
811
+ };
812
+ emitter: Emitter;
813
+ retryConfig?: {
814
+ attempts?: number;
815
+ delay?: number;
816
+ };
817
+ runtimeContext: RuntimeContext;
818
+ }): Promise<TOutput> {
819
+ await params.emitter.emit('watch-v2', {
820
+ type: 'start',
821
+ payload: { runId: params.runId },
822
+ });
823
+
824
+ const result = await super.execute<TInput, TOutput>(params);
825
+
826
+ await params.emitter.emit('watch-v2', {
827
+ type: 'finish',
828
+ payload: { runId: params.runId },
829
+ });
830
+
831
+ return result;
832
+ }
833
+
416
834
  protected async fmtReturnValue<TOutput>(
417
835
  executionSpan: Span | undefined,
418
- emitter: { emit: (event: string, data: any) => Promise<void> },
419
- stepResults: Record<string, StepResult<any>>,
420
- lastOutput: StepResult<any>,
836
+ emitter: Emitter,
837
+ stepResults: Record<string, StepResult<any, any, any, any>>,
838
+ lastOutput: StepResult<any, any, any, any>,
421
839
  error?: Error | string,
422
840
  ): Promise<TOutput> {
423
841
  const base: any = {
@@ -501,16 +919,16 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
501
919
  workflowId: string;
502
920
  runId: string;
503
921
  step: Step<string, any, any>;
504
- stepResults: Record<string, StepResult<any>>;
922
+ stepResults: Record<string, StepResult<any, any, any, any>>;
505
923
  executionContext: ExecutionContext;
506
924
  resume?: {
507
925
  steps: string[];
508
926
  resumePayload: any;
509
927
  };
510
928
  prevOutput: any;
511
- emitter: { emit: (event: string, data: any) => Promise<void> };
929
+ emitter: Emitter;
512
930
  runtimeContext: RuntimeContext;
513
- }): Promise<StepResult<any>> {
931
+ }): Promise<StepResult<any, any, any, any>> {
514
932
  return super.executeStep({
515
933
  workflowId,
516
934
  runId,
@@ -524,6 +942,23 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
524
942
  });
525
943
  }
526
944
 
945
+ async executeSleep({ id, duration }: { id: string; duration: number }): Promise<void> {
946
+ await this.inngestStep.sleep(id, duration);
947
+ }
948
+
949
+ async executeWaitForEvent({ event, timeout }: { event: string; timeout?: number }): Promise<any> {
950
+ const eventData = await this.inngestStep.waitForEvent(`user-event-${event}`, {
951
+ event: `user-event-${event}`,
952
+ timeout: timeout ?? 5e3,
953
+ });
954
+
955
+ if (eventData === null) {
956
+ throw 'Timeout waiting for event';
957
+ }
958
+
959
+ return eventData?.data;
960
+ }
961
+
527
962
  async executeStep({
528
963
  step,
529
964
  stepResults,
@@ -534,7 +969,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
534
969
  runtimeContext,
535
970
  }: {
536
971
  step: Step<string, any, any>;
537
- stepResults: Record<string, StepResult<any>>;
972
+ stepResults: Record<string, StepResult<any, any, any, any>>;
538
973
  executionContext: {
539
974
  workflowId: string;
540
975
  runId: string;
@@ -548,12 +983,13 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
548
983
  runId?: string;
549
984
  };
550
985
  prevOutput: any;
551
- emitter: { emit: (event: string, data: any) => Promise<void> };
986
+ emitter: Emitter;
552
987
  runtimeContext: RuntimeContext;
553
- }): Promise<StepResult<any>> {
554
- await this.inngestStep.run(
988
+ }): Promise<StepResult<any, any, any, any>> {
989
+ const startedAt = await this.inngestStep.run(
555
990
  `workflow.${executionContext.workflowId}.run.${executionContext.runId}.step.${step.id}.running_ev`,
556
991
  async () => {
992
+ const startedAt = Date.now();
557
993
  await emitter.emit('watch', {
558
994
  type: 'watch',
559
995
  payload: {
@@ -575,6 +1011,15 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
575
1011
  },
576
1012
  eventTimestamp: Date.now(),
577
1013
  });
1014
+
1015
+ await emitter.emit('watch-v2', {
1016
+ type: 'step-start',
1017
+ payload: {
1018
+ id: step.id,
1019
+ },
1020
+ });
1021
+
1022
+ return startedAt;
578
1023
  },
579
1024
  );
580
1025
 
@@ -641,10 +1086,18 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
641
1086
  eventTimestamp: Date.now(),
642
1087
  });
643
1088
 
1089
+ await emitter.emit('watch-v2', {
1090
+ type: 'step-result',
1091
+ payload: {
1092
+ id: step.id,
1093
+ status: 'failed',
1094
+ },
1095
+ });
1096
+
644
1097
  return { executionContext, result: { status: 'failed', error: result?.error } };
645
1098
  } else if (result.status === 'suspended') {
646
1099
  const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
647
- const stepRes: StepResult<any> = stepResult as StepResult<any>;
1100
+ const stepRes: StepResult<any, any, any, any> = stepResult as StepResult<any, any, any, any>;
648
1101
  return stepRes?.status === 'suspended';
649
1102
  });
650
1103
 
@@ -671,6 +1124,13 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
671
1124
  eventTimestamp: Date.now(),
672
1125
  });
673
1126
 
1127
+ await emitter.emit('watch-v2', {
1128
+ type: 'step-suspended',
1129
+ payload: {
1130
+ id: step.id,
1131
+ },
1132
+ });
1133
+
674
1134
  return {
675
1135
  executionContext,
676
1136
  result: {
@@ -727,19 +1187,30 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
727
1187
  eventTimestamp: Date.now(),
728
1188
  });
729
1189
 
1190
+ await emitter.emit('watch-v2', {
1191
+ type: 'step-finish',
1192
+ payload: {
1193
+ id: step.id,
1194
+ metadata: {},
1195
+ },
1196
+ });
1197
+
730
1198
  return { executionContext, result: { status: 'success', output: result?.result } };
731
1199
  },
732
1200
  );
733
1201
 
734
1202
  Object.assign(executionContext, res.executionContext);
735
- return res.result as StepResult<any>;
1203
+ return res.result as StepResult<any, any, any, any>;
736
1204
  }
737
1205
 
738
1206
  const stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
739
1207
  let execResults: any;
740
1208
  let suspended: { payload: any } | undefined;
1209
+ let bailed: { payload: any } | undefined;
1210
+
741
1211
  try {
742
1212
  const result = await step.execute({
1213
+ runId: executionContext.runId,
743
1214
  mastra: this.mastra!,
744
1215
  runtimeContext,
745
1216
  inputData: prevOutput,
@@ -757,22 +1228,55 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
757
1228
  executionContext.suspendedPaths[step.id] = executionContext.executionPath;
758
1229
  suspended = { payload: suspendPayload };
759
1230
  },
1231
+ bail: (result: any) => {
1232
+ bailed = { payload: result };
1233
+ },
760
1234
  resume: {
761
1235
  steps: resume?.steps?.slice(1) || [],
762
1236
  resumePayload: resume?.resumePayload,
763
1237
  // @ts-ignore
764
1238
  runId: stepResults[step.id]?.payload?.__workflow_meta?.runId,
765
1239
  },
766
- emitter,
1240
+ [EMITTER_SYMBOL]: emitter,
1241
+ engine: {
1242
+ step: this.inngestStep,
1243
+ },
767
1244
  });
768
-
769
- execResults = { status: 'success', output: result };
1245
+ const endedAt = Date.now();
1246
+
1247
+ execResults = {
1248
+ status: 'success',
1249
+ output: result,
1250
+ startedAt,
1251
+ endedAt,
1252
+ payload: prevOutput,
1253
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1254
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1255
+ };
770
1256
  } catch (e) {
771
- execResults = { status: 'failed', error: e instanceof Error ? e.message : String(e) };
1257
+ execResults = {
1258
+ status: 'failed',
1259
+ payload: prevOutput,
1260
+ error: e instanceof Error ? e.message : String(e),
1261
+ endedAt: Date.now(),
1262
+ startedAt,
1263
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1264
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1265
+ };
772
1266
  }
773
1267
 
774
1268
  if (suspended) {
775
- execResults = { status: 'suspended', payload: suspended.payload };
1269
+ execResults = {
1270
+ status: 'suspended',
1271
+ suspendedPayload: suspended.payload,
1272
+ payload: prevOutput,
1273
+ suspendedAt: Date.now(),
1274
+ startedAt,
1275
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1276
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1277
+ };
1278
+ } else if (bailed) {
1279
+ execResults = { status: 'bailed', output: bailed.payload, payload: prevOutput, endedAt: Date.now(), startedAt };
776
1280
  }
777
1281
 
778
1282
  if (execResults.status === 'failed') {
@@ -786,12 +1290,11 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
786
1290
  payload: {
787
1291
  currentStep: {
788
1292
  id: step.id,
789
- status: execResults.status,
790
- output: execResults.output,
1293
+ ...execResults,
791
1294
  },
792
1295
  workflowState: {
793
1296
  status: 'running',
794
- steps: stepResults,
1297
+ steps: { ...stepResults, [step.id]: execResults },
795
1298
  result: null,
796
1299
  error: null,
797
1300
  },
@@ -799,6 +1302,34 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
799
1302
  eventTimestamp: Date.now(),
800
1303
  });
801
1304
 
1305
+ if (execResults.status === 'suspended') {
1306
+ await emitter.emit('watch-v2', {
1307
+ type: 'step-suspended',
1308
+ payload: {
1309
+ id: step.id,
1310
+ status: execResults.status,
1311
+ output: execResults.status === 'success' ? execResults?.output : undefined,
1312
+ },
1313
+ });
1314
+ } else {
1315
+ await emitter.emit('watch-v2', {
1316
+ type: 'step-result',
1317
+ payload: {
1318
+ id: step.id,
1319
+ status: execResults.status,
1320
+ output: execResults.status === 'success' ? execResults?.output : undefined,
1321
+ },
1322
+ });
1323
+
1324
+ await emitter.emit('watch-v2', {
1325
+ type: 'step-finish',
1326
+ payload: {
1327
+ id: step.id,
1328
+ metadata: {},
1329
+ },
1330
+ });
1331
+ }
1332
+
802
1333
  return { result: execResults, executionContext, stepResults };
803
1334
  });
804
1335
 
@@ -816,11 +1347,19 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
816
1347
  runId,
817
1348
  stepResults,
818
1349
  executionContext,
1350
+ serializedStepGraph,
1351
+ workflowStatus,
1352
+ result,
1353
+ error,
819
1354
  }: {
820
1355
  workflowId: string;
821
1356
  runId: string;
822
- stepResults: Record<string, StepResult<any>>;
1357
+ stepResults: Record<string, StepResult<any, any, any, any>>;
1358
+ serializedStepGraph: SerializedStepFlowEntry[];
823
1359
  executionContext: ExecutionContext;
1360
+ workflowStatus: 'success' | 'failed' | 'suspended' | 'running';
1361
+ result?: Record<string, any>;
1362
+ error?: string | Error;
824
1363
  }) {
825
1364
  await this.inngestStep.run(
826
1365
  `workflow.${workflowId}.run.${runId}.path.${JSON.stringify(executionContext.executionPath)}.stepUpdate`,
@@ -834,6 +1373,10 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
834
1373
  context: stepResults as any,
835
1374
  activePaths: [],
836
1375
  suspendedPaths: executionContext.suspendedPaths,
1376
+ serializedStepGraph,
1377
+ status: workflowStatus,
1378
+ result,
1379
+ error,
837
1380
  // @ts-ignore
838
1381
  timestamp: Date.now(),
839
1382
  },
@@ -849,6 +1392,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
849
1392
  prevOutput,
850
1393
  prevStep,
851
1394
  stepResults,
1395
+ serializedStepGraph,
852
1396
  resume,
853
1397
  executionContext,
854
1398
  emitter,
@@ -856,20 +1400,25 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
856
1400
  }: {
857
1401
  workflowId: string;
858
1402
  runId: string;
859
- entry: { type: 'conditional'; steps: StepFlowEntry[]; conditions: ExecuteFunction<any, any, any, any>[] };
1403
+ entry: {
1404
+ type: 'conditional';
1405
+ steps: StepFlowEntry[];
1406
+ conditions: ExecuteFunction<any, any, any, any, InngestEngineType>[];
1407
+ };
860
1408
  prevStep: StepFlowEntry;
1409
+ serializedStepGraph: SerializedStepFlowEntry[];
861
1410
  prevOutput: any;
862
- stepResults: Record<string, StepResult<any>>;
1411
+ stepResults: Record<string, StepResult<any, any, any, any>>;
863
1412
  resume?: {
864
1413
  steps: string[];
865
- stepResults: Record<string, StepResult<any>>;
1414
+ stepResults: Record<string, StepResult<any, any, any, any>>;
866
1415
  resumePayload: any;
867
1416
  resumePath: number[];
868
1417
  };
869
1418
  executionContext: ExecutionContext;
870
- emitter: { emit: (event: string, data: any) => Promise<void> };
1419
+ emitter: Emitter;
871
1420
  runtimeContext: RuntimeContext;
872
- }): Promise<StepResult<any>> {
1421
+ }): Promise<StepResult<any, any, any, any>> {
873
1422
  let execResults: any;
874
1423
  const truthyIndexes = (
875
1424
  await Promise.all(
@@ -877,6 +1426,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
877
1426
  this.inngestStep.run(`workflow.${workflowId}.conditional.${index}`, async () => {
878
1427
  try {
879
1428
  const result = await cond({
1429
+ runId,
880
1430
  mastra: this.mastra!,
881
1431
  runtimeContext,
882
1432
  inputData: prevOutput,
@@ -896,7 +1446,11 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
896
1446
 
897
1447
  // TODO: this function shouldn't have suspend probably?
898
1448
  suspend: async (_suspendPayload: any) => {},
899
- emitter,
1449
+ bail: () => {},
1450
+ [EMITTER_SYMBOL]: emitter,
1451
+ engine: {
1452
+ step: this.inngestStep,
1453
+ },
900
1454
  });
901
1455
  return result ? index : null;
902
1456
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -909,7 +1463,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
909
1463
  ).filter((index: any): index is number => index !== null);
910
1464
 
911
1465
  const stepsToRun = entry.steps.filter((_, index) => truthyIndexes.includes(index));
912
- const results: StepResult<any>[] = await Promise.all(
1466
+ const results: { result: StepResult<any, any, any, any> }[] = await Promise.all(
913
1467
  stepsToRun.map((step, index) =>
914
1468
  this.executeEntry({
915
1469
  workflowId,
@@ -918,6 +1472,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
918
1472
  prevStep,
919
1473
  stepResults,
920
1474
  resume,
1475
+ serializedStepGraph,
921
1476
  executionContext: {
922
1477
  workflowId,
923
1478
  runId,
@@ -931,17 +1486,19 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
931
1486
  }),
932
1487
  ),
933
1488
  );
934
- const hasFailed = results.find(result => result.status === 'failed');
935
- const hasSuspended = results.find(result => result.status === 'suspended');
1489
+ const hasFailed = results.find(result => result.result.status === 'failed') as {
1490
+ result: StepFailure<any, any, any>;
1491
+ };
1492
+ const hasSuspended = results.find(result => result.result.status === 'suspended');
936
1493
  if (hasFailed) {
937
- execResults = { status: 'failed', error: hasFailed.error };
1494
+ execResults = { status: 'failed', error: hasFailed.result.error };
938
1495
  } else if (hasSuspended) {
939
- execResults = { status: 'suspended', payload: hasSuspended.payload };
1496
+ execResults = { status: 'suspended', payload: hasSuspended.result.suspendPayload };
940
1497
  } else {
941
1498
  execResults = {
942
1499
  status: 'success',
943
1500
  output: results.reduce((acc: Record<string, any>, result, index) => {
944
- if (result.status === 'success') {
1501
+ if (result.result.status === 'success') {
945
1502
  // @ts-ignore
946
1503
  acc[stepsToRun[index]!.step.id] = result.output;
947
1504
  }