@mastra/inngest 0.0.0-redis-cloud-transporter-20250508203756 → 0.0.0-scorers-api-v2-20250801171841

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,34 +1,50 @@
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, ToolStream } 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
+ ChunkType,
24
+ } from '@mastra/core/workflows';
25
+ import { EMITTER_SYMBOL } from '@mastra/core/workflows/_constants';
18
26
  import type { Span } from '@opentelemetry/api';
19
27
  import type { Inngest, BaseContext } from 'inngest';
20
28
  import { serve as inngestServe } from 'inngest/hono';
21
- import type { z } from 'zod';
29
+ import { z } from 'zod';
30
+
31
+ export type InngestEngineType = {
32
+ step: any;
33
+ };
22
34
 
23
35
  export function serve({ mastra, inngest }: { mastra: Mastra; inngest: Inngest }): ReturnType<typeof inngestServe> {
24
- const wfs = mastra.vnext_getWorkflows();
25
- const functions = Object.values(wfs).flatMap(wf => {
26
- if (wf instanceof InngestWorkflow) {
27
- wf.__registerMastra(mastra);
28
- return wf.getFunctions();
29
- }
30
- return [];
31
- });
36
+ const wfs = mastra.getWorkflows();
37
+ const functions = Array.from(
38
+ new Set(
39
+ Object.values(wfs).flatMap(wf => {
40
+ if (wf instanceof InngestWorkflow) {
41
+ wf.__registerMastra(mastra);
42
+ return wf.getFunctions();
43
+ }
44
+ return [];
45
+ }),
46
+ ),
47
+ );
32
48
  return inngestServe({
33
49
  client: inngest,
34
50
  functions,
@@ -36,11 +52,13 @@ export function serve({ mastra, inngest }: { mastra: Mastra; inngest: Inngest })
36
52
  }
37
53
 
38
54
  export class InngestRun<
39
- TSteps extends NewStep<string, any, any>[] = NewStep<string, any, any>[],
55
+ TEngineType = InngestEngineType,
56
+ TSteps extends Step<string, any, any>[] = Step<string, any, any>[],
40
57
  TInput extends z.ZodType<any> = z.ZodType<any>,
41
58
  TOutput extends z.ZodType<any> = z.ZodType<any>,
42
- > extends Run<TSteps, TInput, TOutput> {
59
+ > extends Run<TEngineType, TSteps, TInput, TOutput> {
43
60
  private inngest: Inngest;
61
+ serializedStepGraph: SerializedStepFlowEntry[];
44
62
  #mastra: Mastra;
45
63
 
46
64
  constructor(
@@ -49,6 +67,7 @@ export class InngestRun<
49
67
  runId: string;
50
68
  executionEngine: ExecutionEngine;
51
69
  executionGraph: ExecutionGraph;
70
+ serializedStepGraph: SerializedStepFlowEntry[];
52
71
  mastra?: Mastra;
53
72
  retryConfig?: {
54
73
  attempts?: number;
@@ -60,11 +79,12 @@ export class InngestRun<
60
79
  ) {
61
80
  super(params);
62
81
  this.inngest = inngest;
82
+ this.serializedStepGraph = params.serializedStepGraph;
63
83
  this.#mastra = params.mastra!;
64
84
  }
65
85
 
66
86
  async getRuns(eventId: string) {
67
- const response = await fetch(`${this.inngest.apiBaseUrl}/v1/events/${eventId}/runs`, {
87
+ const response = await fetch(`${this.inngest.apiBaseUrl ?? 'https://api.inngest.com'}/v1/events/${eventId}/runs`, {
68
88
  headers: {
69
89
  Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`,
70
90
  },
@@ -75,16 +95,55 @@ export class InngestRun<
75
95
 
76
96
  async getRunOutput(eventId: string) {
77
97
  let runs = await this.getRuns(eventId);
78
- while (runs?.[0]?.status !== 'Completed') {
98
+
99
+ while (runs?.[0]?.status !== 'Completed' || runs?.[0]?.event_id !== eventId) {
79
100
  await new Promise(resolve => setTimeout(resolve, 1000));
80
101
  runs = await this.getRuns(eventId);
81
- if (runs?.[0]?.status === 'Failed' || runs?.[0]?.status === 'Cancelled') {
102
+ if (runs?.[0]?.status === 'Failed') {
103
+ console.log('run', runs?.[0]);
82
104
  throw new Error(`Function run ${runs?.[0]?.status}`);
105
+ } else if (runs?.[0]?.status === 'Cancelled') {
106
+ const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
107
+ workflowName: this.workflowId,
108
+ runId: this.runId,
109
+ });
110
+ return { output: { result: { steps: snapshot?.context, status: 'canceled' } } };
83
111
  }
84
112
  }
85
113
  return runs?.[0];
86
114
  }
87
115
 
116
+ async sendEvent(event: string, data: any) {
117
+ await this.inngest.send({
118
+ name: `user-event-${event}`,
119
+ data,
120
+ });
121
+ }
122
+
123
+ async cancel() {
124
+ await this.inngest.send({
125
+ name: `cancel.workflow.${this.workflowId}`,
126
+ data: {
127
+ runId: this.runId,
128
+ },
129
+ });
130
+
131
+ const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
132
+ workflowName: this.workflowId,
133
+ runId: this.runId,
134
+ });
135
+ if (snapshot) {
136
+ await this.#mastra?.storage?.persistWorkflowSnapshot({
137
+ workflowName: this.workflowId,
138
+ runId: this.runId,
139
+ snapshot: {
140
+ ...snapshot,
141
+ status: 'canceled' as any,
142
+ },
143
+ });
144
+ }
145
+ }
146
+
88
147
  async start({
89
148
  inputData,
90
149
  }: {
@@ -96,11 +155,13 @@ export class InngestRun<
96
155
  runId: this.runId,
97
156
  snapshot: {
98
157
  runId: this.runId,
158
+ serializedStepGraph: this.serializedStepGraph,
99
159
  value: {},
100
160
  context: {} as any,
101
161
  activePaths: [],
102
162
  suspendedPaths: {},
103
163
  timestamp: Date.now(),
164
+ status: 'running',
104
165
  },
105
166
  });
106
167
 
@@ -122,7 +183,9 @@ export class InngestRun<
122
183
  result.error = new Error(result.error);
123
184
  }
124
185
 
125
- this.cleanup?.();
186
+ if (result.status !== 'suspended') {
187
+ this.cleanup?.();
188
+ }
126
189
  return result;
127
190
  }
128
191
 
@@ -134,6 +197,27 @@ export class InngestRun<
134
197
  | string
135
198
  | string[];
136
199
  runtimeContext?: RuntimeContext;
200
+ }): Promise<WorkflowResult<TOutput, TSteps>> {
201
+ const p = this._resume(params).then(result => {
202
+ if (result.status !== 'suspended') {
203
+ this.closeStreamAction?.().catch(() => {});
204
+ }
205
+
206
+ return result;
207
+ });
208
+
209
+ this.executionResults = p;
210
+ return p;
211
+ }
212
+
213
+ async _resume<TResumeSchema extends z.ZodType<any>>(params: {
214
+ resumeData?: z.infer<TResumeSchema>;
215
+ step:
216
+ | Step<string, any, any, TResumeSchema, any>
217
+ | [...Step<string, any, any, any, any>[], Step<string, any, any, TResumeSchema, any>]
218
+ | string
219
+ | string[];
220
+ runtimeContext?: RuntimeContext;
137
221
  }): Promise<WorkflowResult<TOutput, TSteps>> {
138
222
  const steps: string[] = (Array.isArray(params.step) ? params.step : [params.step]).map(step =>
139
223
  typeof step === 'string' ? step : step?.id,
@@ -148,6 +232,7 @@ export class InngestRun<
148
232
  data: {
149
233
  inputData: params.resumeData,
150
234
  runId: this.runId,
235
+ workflowId: this.workflowId,
151
236
  stepResults: snapshot?.context as any,
152
237
  resume: {
153
238
  steps,
@@ -171,43 +256,88 @@ export class InngestRun<
171
256
  return result;
172
257
  }
173
258
 
174
- watch(cb: (event: any) => void): () => void {
259
+ watch(cb: (event: WatchEvent) => void, type: 'watch' | 'watch-v2' = 'watch'): () => void {
260
+ let active = true;
175
261
  const streamPromise = subscribe(
176
262
  {
177
263
  channel: `workflow:${this.workflowId}:${this.runId}`,
178
- topics: ['watch'],
264
+ topics: [type],
179
265
  app: this.inngest,
180
266
  },
181
267
  (message: any) => {
182
- cb(message.data);
268
+ if (active) {
269
+ cb(message.data);
270
+ }
183
271
  },
184
272
  );
185
273
 
186
274
  return () => {
275
+ active = false;
187
276
  streamPromise
188
- .then((stream: any) => {
189
- stream.cancel();
277
+ .then(async (stream: Awaited<typeof streamPromise>) => {
278
+ return stream.cancel();
190
279
  })
191
280
  .catch(err => {
192
281
  console.error(err);
193
282
  });
194
283
  };
195
284
  }
285
+
286
+ stream({ inputData, runtimeContext }: { inputData?: z.infer<TInput>; runtimeContext?: RuntimeContext } = {}): {
287
+ stream: ReadableStream<StreamEvent>;
288
+ getWorkflowState: () => Promise<WorkflowResult<TOutput, TSteps>>;
289
+ } {
290
+ const { readable, writable } = new TransformStream<StreamEvent, StreamEvent>();
291
+
292
+ const writer = writable.getWriter();
293
+ const unwatch = this.watch(async event => {
294
+ try {
295
+ // watch-v2 events are data stream events, so we need to cast them to the correct type
296
+ await writer.write(event as any);
297
+ } catch {}
298
+ }, 'watch-v2');
299
+
300
+ this.closeStreamAction = async () => {
301
+ unwatch();
302
+
303
+ try {
304
+ await writer.close();
305
+ } catch (err) {
306
+ console.error('Error closing stream:', err);
307
+ } finally {
308
+ writer.releaseLock();
309
+ }
310
+ };
311
+
312
+ this.executionResults = this.start({ inputData, runtimeContext }).then(result => {
313
+ if (result.status !== 'suspended') {
314
+ this.closeStreamAction?.().catch(() => {});
315
+ }
316
+
317
+ return result;
318
+ });
319
+
320
+ return {
321
+ stream: readable as ReadableStream<StreamEvent>,
322
+ getWorkflowState: () => this.executionResults!,
323
+ };
324
+ }
196
325
  }
197
326
 
198
327
  export class InngestWorkflow<
199
- TSteps extends NewStep<string, any, any>[] = NewStep<string, any, any>[],
328
+ TEngineType = InngestEngineType,
329
+ TSteps extends Step<string, any, any>[] = Step<string, any, any>[],
200
330
  TWorkflowId extends string = string,
201
331
  TInput extends z.ZodType<any> = z.ZodType<any>,
202
332
  TOutput extends z.ZodType<any> = z.ZodType<any>,
203
333
  TPrevSchema extends z.ZodType<any> = TInput,
204
- > extends NewWorkflow<TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> {
334
+ > extends Workflow<TEngineType, TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> {
205
335
  #mastra: Mastra;
206
336
  public inngest: Inngest;
207
337
 
208
338
  private function: ReturnType<Inngest['createFunction']> | undefined;
209
339
 
210
- constructor(params: NewWorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>, inngest: Inngest) {
340
+ constructor(params: WorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>, inngest: Inngest) {
211
341
  super(params);
212
342
  this.#mastra = params.mastra!;
213
343
  this.inngest = inngest;
@@ -226,16 +356,19 @@ export class InngestWorkflow<
226
356
  return { runs: [], total: 0 };
227
357
  }
228
358
 
229
- return storage.getWorkflowRuns({ workflowName: this.id, ...(args ?? {}) });
359
+ return storage.getWorkflowRuns({ workflowName: this.id, ...(args ?? {}) }) as unknown as WorkflowRuns;
230
360
  }
231
361
 
232
362
  async getWorkflowRunById(runId: string): Promise<WorkflowRun | null> {
233
363
  const storage = this.#mastra?.getStorage();
234
364
  if (!storage) {
235
365
  this.logger.debug('Cannot get workflow runs. Mastra engine is not initialized');
236
- return null;
366
+ //returning in memory run if no storage is initialized
367
+ return this.runs.get(runId)
368
+ ? ({ ...this.runs.get(runId), workflowName: this.id } as unknown as WorkflowRun)
369
+ : null;
237
370
  }
238
- const run = await storage.getWorkflowRunById({ runId, workflowName: this.id });
371
+ const run = (await storage.getWorkflowRunById({ runId, workflowName: this.id })) as unknown as WorkflowRun;
239
372
 
240
373
  return (
241
374
  run ??
@@ -243,6 +376,32 @@ export class InngestWorkflow<
243
376
  );
244
377
  }
245
378
 
379
+ async getWorkflowRunExecutionResult(runId: string): Promise<WatchEvent['payload']['workflowState'] | null> {
380
+ const storage = this.#mastra?.getStorage();
381
+ if (!storage) {
382
+ this.logger.debug('Cannot get workflow run execution result. Mastra storage is not initialized');
383
+ return null;
384
+ }
385
+
386
+ const run = await storage.getWorkflowRunById({ runId, workflowName: this.id });
387
+
388
+ if (!run?.snapshot) {
389
+ return null;
390
+ }
391
+
392
+ if (typeof run.snapshot === 'string') {
393
+ return null;
394
+ }
395
+
396
+ return {
397
+ status: run.snapshot.status,
398
+ result: run.snapshot.result,
399
+ error: run.snapshot.error,
400
+ payload: run.snapshot.context?.input,
401
+ steps: run.snapshot.context as any,
402
+ };
403
+ }
404
+
246
405
  __registerMastra(mastra: Mastra) {
247
406
  this.#mastra = mastra;
248
407
  this.executionEngine.__registerMastra(mastra);
@@ -266,11 +425,11 @@ export class InngestWorkflow<
266
425
  }
267
426
  }
268
427
 
269
- createRun(options?: { runId?: string }): Run<TSteps, TInput, TOutput> {
428
+ createRun(options?: { runId?: string }): Run<TEngineType, TSteps, TInput, TOutput> {
270
429
  const runIdToUse = options?.runId || randomUUID();
271
430
 
272
431
  // Return a new Run instance with object parameters
273
- const run: Run<TSteps, TInput, TOutput> =
432
+ const run: Run<TEngineType, TSteps, TInput, TOutput> =
274
433
  this.runs.get(runIdToUse) ??
275
434
  new InngestRun(
276
435
  {
@@ -278,6 +437,7 @@ export class InngestWorkflow<
278
437
  runId: runIdToUse,
279
438
  executionEngine: this.executionEngine,
280
439
  executionGraph: this.executionGraph,
440
+ serializedStepGraph: this.serializedStepGraph,
281
441
  mastra: this.#mastra,
282
442
  retryConfig: this.retryConfig,
283
443
  cleanup: () => this.runs.delete(runIdToUse),
@@ -289,13 +449,64 @@ export class InngestWorkflow<
289
449
  return run;
290
450
  }
291
451
 
452
+ async createRunAsync(options?: { runId?: string }): Promise<Run<TEngineType, TSteps, TInput, TOutput>> {
453
+ const runIdToUse = options?.runId || randomUUID();
454
+
455
+ // Return a new Run instance with object parameters
456
+ const run: Run<TEngineType, TSteps, TInput, TOutput> =
457
+ this.runs.get(runIdToUse) ??
458
+ new InngestRun(
459
+ {
460
+ workflowId: this.id,
461
+ runId: runIdToUse,
462
+ executionEngine: this.executionEngine,
463
+ executionGraph: this.executionGraph,
464
+ serializedStepGraph: this.serializedStepGraph,
465
+ mastra: this.#mastra,
466
+ retryConfig: this.retryConfig,
467
+ cleanup: () => this.runs.delete(runIdToUse),
468
+ },
469
+ this.inngest,
470
+ );
471
+
472
+ this.runs.set(runIdToUse, run);
473
+
474
+ const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse);
475
+
476
+ if (!workflowSnapshotInStorage) {
477
+ await this.mastra?.getStorage()?.persistWorkflowSnapshot({
478
+ workflowName: this.id,
479
+ runId: runIdToUse,
480
+ snapshot: {
481
+ runId: runIdToUse,
482
+ status: 'pending',
483
+ value: {},
484
+ context: {},
485
+ activePaths: [],
486
+ serializedStepGraph: this.serializedStepGraph,
487
+ suspendedPaths: {},
488
+ result: undefined,
489
+ error: undefined,
490
+ // @ts-ignore
491
+ timestamp: Date.now(),
492
+ },
493
+ });
494
+ }
495
+
496
+ return run;
497
+ }
498
+
292
499
  getFunction() {
293
500
  if (this.function) {
294
501
  return this.function;
295
502
  }
296
503
  this.function = this.inngest.createFunction(
297
- // @ts-ignore
298
- { id: `workflow.${this.id}`, retries: this.retryConfig?.attempts ?? 0 },
504
+ {
505
+ id: `workflow.${this.id}`,
506
+ // @ts-ignore
507
+ retries: this.retryConfig?.attempts ?? 0,
508
+ cancelOn: [{ event: `cancel.workflow.${this.id}` }],
509
+ },
299
510
  { event: `workflow.${this.id}` },
300
511
  async ({ event, step, attempt, publish }) => {
301
512
  let { inputData, runId, resume } = event.data;
@@ -315,13 +526,22 @@ export class InngestWorkflow<
315
526
  try {
316
527
  await publish({
317
528
  channel: `workflow:${this.id}:${runId}`,
318
- topic: 'watch',
529
+ topic: event,
319
530
  data,
320
531
  });
321
532
  } catch (err: any) {
322
533
  this.logger.error('Error emitting event: ' + (err?.stack ?? err?.message ?? err));
323
534
  }
324
535
  },
536
+ on: (_event: string, _callback: (data: any) => void) => {
537
+ // no-op
538
+ },
539
+ off: (_event: string, _callback: (data: any) => void) => {
540
+ // no-op
541
+ },
542
+ once: (_event: string, _callback: (data: any) => void) => {
543
+ // no-op
544
+ },
325
545
  };
326
546
 
327
547
  const engine = new InngestExecutionEngine(this.#mastra, step, attempt);
@@ -329,11 +549,13 @@ export class InngestWorkflow<
329
549
  workflowId: this.id,
330
550
  runId,
331
551
  graph: this.executionGraph,
552
+ serializedStepGraph: this.serializedStepGraph,
332
553
  input: inputData,
333
554
  emitter,
334
555
  retryConfig: this.retryConfig,
335
556
  runtimeContext: new RuntimeContext(), // TODO
336
557
  resume,
558
+ abortController: new AbortController(),
337
559
  });
338
560
 
339
561
  return { result, runId };
@@ -362,29 +584,197 @@ export class InngestWorkflow<
362
584
  }
363
585
  }
364
586
 
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>[],
587
+ function isAgent(params: any): params is Agent<any, any, any> {
588
+ return params?.component === 'AGENT';
589
+ }
590
+
591
+ function isTool(params: any): params is Tool<any, any, any> {
592
+ return params instanceof Tool;
593
+ }
594
+
595
+ export function createStep<
596
+ TStepId extends string,
597
+ TStepInput extends z.ZodType<any>,
598
+ TStepOutput extends z.ZodType<any>,
599
+ TResumeSchema extends z.ZodType<any>,
600
+ TSuspendSchema extends z.ZodType<any>,
601
+ >(params: {
602
+ id: TStepId;
603
+ description?: string;
604
+ inputSchema: TStepInput;
605
+ outputSchema: TStepOutput;
606
+ resumeSchema?: TResumeSchema;
607
+ suspendSchema?: TSuspendSchema;
608
+ execute: ExecuteFunction<
609
+ z.infer<TStepInput>,
610
+ z.infer<TStepOutput>,
611
+ z.infer<TResumeSchema>,
612
+ z.infer<TSuspendSchema>,
613
+ InngestEngineType
614
+ >;
615
+ }): Step<TStepId, TStepInput, TStepOutput, TResumeSchema, TSuspendSchema, InngestEngineType>;
616
+
617
+ export function createStep<
618
+ TStepId extends string,
619
+ TStepInput extends z.ZodObject<{ prompt: z.ZodString }>,
620
+ TStepOutput extends z.ZodObject<{ text: z.ZodString }>,
621
+ TResumeSchema extends z.ZodType<any>,
622
+ TSuspendSchema extends z.ZodType<any>,
370
623
  >(
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
- );
624
+ agent: Agent<TStepId, any, any>,
625
+ ): Step<TStepId, TStepInput, TStepOutput, TResumeSchema, TSuspendSchema, InngestEngineType>;
626
+
627
+ export function createStep<
628
+ TSchemaIn extends z.ZodType<any>,
629
+ TSchemaOut extends z.ZodType<any>,
630
+ TContext extends ToolExecutionContext<TSchemaIn>,
631
+ >(
632
+ tool: Tool<TSchemaIn, TSchemaOut, TContext> & {
633
+ inputSchema: TSchemaIn;
634
+ outputSchema: TSchemaOut;
635
+ execute: (context: TContext) => Promise<any>;
636
+ },
637
+ ): Step<string, TSchemaIn, TSchemaOut, z.ZodType<any>, z.ZodType<any>, InngestEngineType>;
638
+ export function createStep<
639
+ TStepId extends string,
640
+ TStepInput extends z.ZodType<any>,
641
+ TStepOutput extends z.ZodType<any>,
642
+ TResumeSchema extends z.ZodType<any>,
643
+ TSuspendSchema extends z.ZodType<any>,
644
+ >(
645
+ params:
646
+ | {
647
+ id: TStepId;
648
+ description?: string;
649
+ inputSchema: TStepInput;
650
+ outputSchema: TStepOutput;
651
+ resumeSchema?: TResumeSchema;
652
+ suspendSchema?: TSuspendSchema;
653
+ execute: ExecuteFunction<
654
+ z.infer<TStepInput>,
655
+ z.infer<TStepOutput>,
656
+ z.infer<TResumeSchema>,
657
+ z.infer<TSuspendSchema>,
658
+ InngestEngineType
659
+ >;
660
+ }
661
+ | Agent<any, any, any>
662
+ | (Tool<TStepInput, TStepOutput, any> & {
663
+ inputSchema: TStepInput;
664
+ outputSchema: TStepOutput;
665
+ execute: (context: ToolExecutionContext<TStepInput>) => Promise<any>;
666
+ }),
667
+ ): Step<TStepId, TStepInput, TStepOutput, TResumeSchema, TSuspendSchema, InngestEngineType> {
668
+ if (isAgent(params)) {
669
+ return {
670
+ id: params.name,
671
+ // @ts-ignore
672
+ inputSchema: z.object({
673
+ prompt: z.string(),
674
+ // resourceId: z.string().optional(),
675
+ // threadId: z.string().optional(),
676
+ }),
677
+ // @ts-ignore
678
+ outputSchema: z.object({
679
+ text: z.string(),
680
+ }),
681
+ execute: async ({ inputData, [EMITTER_SYMBOL]: emitter, runtimeContext, abortSignal, abort }) => {
682
+ let streamPromise = {} as {
683
+ promise: Promise<string>;
684
+ resolve: (value: string) => void;
685
+ reject: (reason?: any) => void;
686
+ };
687
+
688
+ streamPromise.promise = new Promise((resolve, reject) => {
689
+ streamPromise.resolve = resolve;
690
+ streamPromise.reject = reject;
691
+ });
692
+ const toolData = {
693
+ name: params.name,
694
+ args: inputData,
695
+ };
696
+ await emitter.emit('watch-v2', {
697
+ type: 'tool-call-streaming-start',
698
+ ...toolData,
699
+ });
700
+ const { fullStream } = await params.stream(inputData.prompt, {
701
+ // resourceId: inputData.resourceId,
702
+ // threadId: inputData.threadId,
703
+ runtimeContext,
704
+ onFinish: result => {
705
+ streamPromise.resolve(result.text);
706
+ },
707
+ abortSignal,
708
+ });
709
+
710
+ if (abortSignal.aborted) {
711
+ return abort();
712
+ }
713
+
714
+ for await (const chunk of fullStream) {
715
+ switch (chunk.type) {
716
+ case 'text-delta':
717
+ await emitter.emit('watch-v2', {
718
+ type: 'tool-call-delta',
719
+ ...toolData,
720
+ argsTextDelta: chunk.textDelta,
721
+ });
722
+ break;
723
+
724
+ case 'step-start':
725
+ case 'step-finish':
726
+ case 'finish':
727
+ break;
728
+
729
+ case 'tool-call':
730
+ case 'tool-result':
731
+ case 'tool-call-streaming-start':
732
+ case 'tool-call-delta':
733
+ case 'source':
734
+ case 'file':
735
+ default:
736
+ await emitter.emit('watch-v2', chunk);
737
+ break;
738
+ }
739
+ }
740
+
741
+ return {
742
+ text: await streamPromise.promise,
743
+ };
744
+ },
745
+ };
746
+ }
747
+
748
+ if (isTool(params)) {
749
+ if (!params.inputSchema || !params.outputSchema) {
750
+ throw new Error('Tool must have input and output schemas defined');
751
+ }
384
752
 
385
- wf.setStepFlow(workflow.stepGraph);
386
- wf.commit();
387
- return wf;
753
+ return {
754
+ // TODO: tool probably should have strong id type
755
+ // @ts-ignore
756
+ id: params.id,
757
+ inputSchema: params.inputSchema,
758
+ outputSchema: params.outputSchema,
759
+ execute: async ({ inputData, mastra, runtimeContext }) => {
760
+ return params.execute({
761
+ context: inputData,
762
+ mastra,
763
+ runtimeContext,
764
+ });
765
+ },
766
+ };
767
+ }
768
+
769
+ return {
770
+ id: params.id,
771
+ description: params.description,
772
+ inputSchema: params.inputSchema,
773
+ outputSchema: params.outputSchema,
774
+ resumeSchema: params.resumeSchema,
775
+ suspendSchema: params.suspendSchema,
776
+ execute: params.execute,
777
+ };
388
778
  }
389
779
 
390
780
  export function init(inngest: Inngest) {
@@ -393,13 +783,59 @@ export function init(inngest: Inngest) {
393
783
  TWorkflowId extends string = string,
394
784
  TInput extends z.ZodType<any> = z.ZodType<any>,
395
785
  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);
786
+ TSteps extends Step<string, any, any, any, any, InngestEngineType>[] = Step<
787
+ string,
788
+ any,
789
+ any,
790
+ any,
791
+ any,
792
+ InngestEngineType
793
+ >[],
794
+ >(params: WorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>) {
795
+ return new InngestWorkflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TInput>(params, inngest);
399
796
  },
400
797
  createStep,
401
- cloneStep,
402
- cloneWorkflow,
798
+ cloneStep<TStepId extends string>(
799
+ step: Step<string, any, any, any, any, InngestEngineType>,
800
+ opts: { id: TStepId },
801
+ ): Step<TStepId, any, any, any, any, InngestEngineType> {
802
+ return {
803
+ id: opts.id,
804
+ description: step.description,
805
+ inputSchema: step.inputSchema,
806
+ outputSchema: step.outputSchema,
807
+ execute: step.execute,
808
+ };
809
+ },
810
+ cloneWorkflow<
811
+ TWorkflowId extends string = string,
812
+ TInput extends z.ZodType<any> = z.ZodType<any>,
813
+ TOutput extends z.ZodType<any> = z.ZodType<any>,
814
+ TSteps extends Step<string, any, any, any, any, InngestEngineType>[] = Step<
815
+ string,
816
+ any,
817
+ any,
818
+ any,
819
+ any,
820
+ InngestEngineType
821
+ >[],
822
+ TPrevSchema extends z.ZodType<any> = TInput,
823
+ >(
824
+ workflow: Workflow<InngestEngineType, TSteps, string, TInput, TOutput, TPrevSchema>,
825
+ opts: { id: TWorkflowId },
826
+ ): Workflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> {
827
+ const wf: Workflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> = new Workflow({
828
+ id: opts.id,
829
+ inputSchema: workflow.inputSchema,
830
+ outputSchema: workflow.outputSchema,
831
+ steps: workflow.stepDefs,
832
+ mastra: workflow.mastra,
833
+ });
834
+
835
+ wf.setStepFlow(workflow.stepGraph);
836
+ wf.commit();
837
+ return wf;
838
+ },
403
839
  };
404
840
  }
405
841
 
@@ -413,11 +849,47 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
413
849
  this.inngestAttempts = inngestAttempts;
414
850
  }
415
851
 
852
+ async execute<TInput, TOutput>(params: {
853
+ workflowId: string;
854
+ runId: string;
855
+ graph: ExecutionGraph;
856
+ serializedStepGraph: SerializedStepFlowEntry[];
857
+ input?: TInput;
858
+ resume?: {
859
+ // TODO: add execute path
860
+ steps: string[];
861
+ stepResults: Record<string, StepResult<any, any, any, any>>;
862
+ resumePayload: any;
863
+ resumePath: number[];
864
+ };
865
+ emitter: Emitter;
866
+ retryConfig?: {
867
+ attempts?: number;
868
+ delay?: number;
869
+ };
870
+ runtimeContext: RuntimeContext;
871
+ abortController: AbortController;
872
+ }): Promise<TOutput> {
873
+ await params.emitter.emit('watch-v2', {
874
+ type: 'start',
875
+ payload: { runId: params.runId },
876
+ });
877
+
878
+ const result = await super.execute<TInput, TOutput>(params);
879
+
880
+ await params.emitter.emit('watch-v2', {
881
+ type: 'finish',
882
+ payload: { runId: params.runId },
883
+ });
884
+
885
+ return result;
886
+ }
887
+
416
888
  protected async fmtReturnValue<TOutput>(
417
889
  executionSpan: Span | undefined,
418
- emitter: { emit: (event: string, data: any) => Promise<void> },
419
- stepResults: Record<string, StepResult<any>>,
420
- lastOutput: StepResult<any>,
890
+ emitter: Emitter,
891
+ stepResults: Record<string, StepResult<any, any, any, any>>,
892
+ lastOutput: StepResult<any, any, any, any>,
421
893
  error?: Error | string,
422
894
  ): Promise<TOutput> {
423
895
  const base: any = {
@@ -496,21 +968,25 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
496
968
  resume,
497
969
  prevOutput,
498
970
  emitter,
971
+ abortController,
499
972
  runtimeContext,
973
+ writableStream,
500
974
  }: {
501
975
  workflowId: string;
502
976
  runId: string;
503
977
  step: Step<string, any, any>;
504
- stepResults: Record<string, StepResult<any>>;
978
+ stepResults: Record<string, StepResult<any, any, any, any>>;
505
979
  executionContext: ExecutionContext;
506
980
  resume?: {
507
981
  steps: string[];
508
982
  resumePayload: any;
509
983
  };
510
984
  prevOutput: any;
511
- emitter: { emit: (event: string, data: any) => Promise<void> };
985
+ emitter: Emitter;
986
+ abortController: AbortController;
512
987
  runtimeContext: RuntimeContext;
513
- }): Promise<StepResult<any>> {
988
+ writableStream?: WritableStream<ChunkType>;
989
+ }): Promise<StepResult<any, any, any, any>> {
514
990
  return super.executeStep({
515
991
  workflowId,
516
992
  runId,
@@ -520,10 +996,205 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
520
996
  resume,
521
997
  prevOutput,
522
998
  emitter,
999
+ abortController,
523
1000
  runtimeContext,
1001
+ writableStream,
524
1002
  });
525
1003
  }
526
1004
 
1005
+ // async executeSleep({ id, duration }: { id: string; duration: number }): Promise<void> {
1006
+ // await this.inngestStep.sleep(id, duration);
1007
+ // }
1008
+
1009
+ async executeSleep({
1010
+ workflowId,
1011
+ runId,
1012
+ entry,
1013
+ prevOutput,
1014
+ stepResults,
1015
+ emitter,
1016
+ abortController,
1017
+ runtimeContext,
1018
+ writableStream,
1019
+ }: {
1020
+ workflowId: string;
1021
+ runId: string;
1022
+ serializedStepGraph: SerializedStepFlowEntry[];
1023
+ entry: {
1024
+ type: 'sleep';
1025
+ id: string;
1026
+ duration?: number;
1027
+ fn?: ExecuteFunction<any, any, any, any, InngestEngineType>;
1028
+ };
1029
+ prevStep: StepFlowEntry;
1030
+ prevOutput: any;
1031
+ stepResults: Record<string, StepResult<any, any, any, any>>;
1032
+ resume?: {
1033
+ steps: string[];
1034
+ stepResults: Record<string, StepResult<any, any, any, any>>;
1035
+ resumePayload: any;
1036
+ resumePath: number[];
1037
+ };
1038
+ executionContext: ExecutionContext;
1039
+ emitter: Emitter;
1040
+ abortController: AbortController;
1041
+ runtimeContext: RuntimeContext;
1042
+ writableStream?: WritableStream<ChunkType>;
1043
+ }): Promise<void> {
1044
+ let { duration, fn } = entry;
1045
+
1046
+ if (fn) {
1047
+ const stepCallId = randomUUID();
1048
+ duration = await this.inngestStep.run(`workflow.${workflowId}.sleep.${entry.id}`, async () => {
1049
+ return await fn({
1050
+ runId,
1051
+ workflowId,
1052
+ mastra: this.mastra!,
1053
+ runtimeContext,
1054
+ inputData: prevOutput,
1055
+ runCount: -1,
1056
+ getInitData: () => stepResults?.input as any,
1057
+ getStepResult: (step: any) => {
1058
+ if (!step?.id) {
1059
+ return null;
1060
+ }
1061
+
1062
+ const result = stepResults[step.id];
1063
+ if (result?.status === 'success') {
1064
+ return result.output;
1065
+ }
1066
+
1067
+ return null;
1068
+ },
1069
+
1070
+ // TODO: this function shouldn't have suspend probably?
1071
+ suspend: async (_suspendPayload: any): Promise<any> => {},
1072
+ bail: () => {},
1073
+ abort: () => {
1074
+ abortController?.abort();
1075
+ },
1076
+ [EMITTER_SYMBOL]: emitter,
1077
+ engine: { step: this.inngestStep },
1078
+ abortSignal: abortController?.signal,
1079
+ writer: new ToolStream(
1080
+ {
1081
+ prefix: 'step',
1082
+ callId: stepCallId,
1083
+ name: 'sleep',
1084
+ runId,
1085
+ },
1086
+ writableStream,
1087
+ ),
1088
+ });
1089
+ });
1090
+ }
1091
+
1092
+ await this.inngestStep.sleep(entry.id, !duration || duration < 0 ? 0 : duration);
1093
+ }
1094
+
1095
+ async executeSleepUntil({
1096
+ workflowId,
1097
+ runId,
1098
+ entry,
1099
+ prevOutput,
1100
+ stepResults,
1101
+ emitter,
1102
+ abortController,
1103
+ runtimeContext,
1104
+ writableStream,
1105
+ }: {
1106
+ workflowId: string;
1107
+ runId: string;
1108
+ serializedStepGraph: SerializedStepFlowEntry[];
1109
+ entry: {
1110
+ type: 'sleepUntil';
1111
+ id: string;
1112
+ date?: Date;
1113
+ fn?: ExecuteFunction<any, any, any, any, InngestEngineType>;
1114
+ };
1115
+ prevStep: StepFlowEntry;
1116
+ prevOutput: any;
1117
+ stepResults: Record<string, StepResult<any, any, any, any>>;
1118
+ resume?: {
1119
+ steps: string[];
1120
+ stepResults: Record<string, StepResult<any, any, any, any>>;
1121
+ resumePayload: any;
1122
+ resumePath: number[];
1123
+ };
1124
+ executionContext: ExecutionContext;
1125
+ emitter: Emitter;
1126
+ abortController: AbortController;
1127
+ runtimeContext: RuntimeContext;
1128
+ writableStream?: WritableStream<ChunkType>;
1129
+ }): Promise<void> {
1130
+ let { date, fn } = entry;
1131
+
1132
+ if (fn) {
1133
+ date = await this.inngestStep.run(`workflow.${workflowId}.sleepUntil.${entry.id}`, async () => {
1134
+ const stepCallId = randomUUID();
1135
+ return await fn({
1136
+ runId,
1137
+ workflowId,
1138
+ mastra: this.mastra!,
1139
+ runtimeContext,
1140
+ inputData: prevOutput,
1141
+ runCount: -1,
1142
+ getInitData: () => stepResults?.input as any,
1143
+ getStepResult: (step: any) => {
1144
+ if (!step?.id) {
1145
+ return null;
1146
+ }
1147
+
1148
+ const result = stepResults[step.id];
1149
+ if (result?.status === 'success') {
1150
+ return result.output;
1151
+ }
1152
+
1153
+ return null;
1154
+ },
1155
+
1156
+ // TODO: this function shouldn't have suspend probably?
1157
+ suspend: async (_suspendPayload: any): Promise<any> => {},
1158
+ bail: () => {},
1159
+ abort: () => {
1160
+ abortController?.abort();
1161
+ },
1162
+ [EMITTER_SYMBOL]: emitter,
1163
+ engine: { step: this.inngestStep },
1164
+ abortSignal: abortController?.signal,
1165
+ writer: new ToolStream(
1166
+ {
1167
+ prefix: 'step',
1168
+ callId: stepCallId,
1169
+ name: 'sleep',
1170
+ runId,
1171
+ },
1172
+ writableStream,
1173
+ ),
1174
+ });
1175
+ });
1176
+ }
1177
+
1178
+ if (!(date instanceof Date)) {
1179
+ return;
1180
+ }
1181
+
1182
+ await this.inngestStep.sleepUntil(entry.id, date);
1183
+ }
1184
+
1185
+ async executeWaitForEvent({ event, timeout }: { event: string; timeout?: number }): Promise<any> {
1186
+ const eventData = await this.inngestStep.waitForEvent(`user-event-${event}`, {
1187
+ event: `user-event-${event}`,
1188
+ timeout: timeout ?? 5e3,
1189
+ });
1190
+
1191
+ if (eventData === null) {
1192
+ throw 'Timeout waiting for event';
1193
+ }
1194
+
1195
+ return eventData?.data;
1196
+ }
1197
+
527
1198
  async executeStep({
528
1199
  step,
529
1200
  stepResults,
@@ -531,29 +1202,28 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
531
1202
  resume,
532
1203
  prevOutput,
533
1204
  emitter,
1205
+ abortController,
534
1206
  runtimeContext,
1207
+ writableStream,
535
1208
  }: {
536
1209
  step: Step<string, any, any>;
537
- stepResults: Record<string, StepResult<any>>;
538
- executionContext: {
539
- workflowId: string;
540
- runId: string;
541
- executionPath: number[];
542
- suspendedPaths: Record<string, number[]>;
543
- retryConfig: { attempts: number; delay: number };
544
- };
1210
+ stepResults: Record<string, StepResult<any, any, any, any>>;
1211
+ executionContext: ExecutionContext;
545
1212
  resume?: {
546
1213
  steps: string[];
547
1214
  resumePayload: any;
548
1215
  runId?: string;
549
1216
  };
550
1217
  prevOutput: any;
551
- emitter: { emit: (event: string, data: any) => Promise<void> };
1218
+ emitter: Emitter;
1219
+ abortController: AbortController;
552
1220
  runtimeContext: RuntimeContext;
553
- }): Promise<StepResult<any>> {
554
- await this.inngestStep.run(
1221
+ writableStream?: WritableStream<ChunkType>;
1222
+ }): Promise<StepResult<any, any, any, any>> {
1223
+ const startedAt = await this.inngestStep.run(
555
1224
  `workflow.${executionContext.workflowId}.run.${executionContext.runId}.step.${step.id}.running_ev`,
556
1225
  async () => {
1226
+ const startedAt = Date.now();
557
1227
  await emitter.emit('watch', {
558
1228
  type: 'watch',
559
1229
  payload: {
@@ -575,6 +1245,18 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
575
1245
  },
576
1246
  eventTimestamp: Date.now(),
577
1247
  });
1248
+
1249
+ await emitter.emit('watch-v2', {
1250
+ type: 'step-start',
1251
+ payload: {
1252
+ id: step.id,
1253
+ status: 'running',
1254
+ payload: prevOutput,
1255
+ startedAt,
1256
+ },
1257
+ });
1258
+
1259
+ return startedAt;
578
1260
  },
579
1261
  );
580
1262
 
@@ -641,10 +1323,20 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
641
1323
  eventTimestamp: Date.now(),
642
1324
  });
643
1325
 
1326
+ await emitter.emit('watch-v2', {
1327
+ type: 'step-result',
1328
+ payload: {
1329
+ id: step.id,
1330
+ status: 'failed',
1331
+ error: result?.error,
1332
+ payload: prevOutput,
1333
+ },
1334
+ });
1335
+
644
1336
  return { executionContext, result: { status: 'failed', error: result?.error } };
645
1337
  } else if (result.status === 'suspended') {
646
1338
  const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
647
- const stepRes: StepResult<any> = stepResult as StepResult<any>;
1339
+ const stepRes: StepResult<any, any, any, any> = stepResult as StepResult<any, any, any, any>;
648
1340
  return stepRes?.status === 'suspended';
649
1341
  });
650
1342
 
@@ -671,6 +1363,14 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
671
1363
  eventTimestamp: Date.now(),
672
1364
  });
673
1365
 
1366
+ await emitter.emit('watch-v2', {
1367
+ type: 'step-suspended',
1368
+ payload: {
1369
+ id: step.id,
1370
+ status: 'suspended',
1371
+ },
1372
+ });
1373
+
674
1374
  return {
675
1375
  executionContext,
676
1376
  result: {
@@ -727,21 +1427,42 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
727
1427
  eventTimestamp: Date.now(),
728
1428
  });
729
1429
 
1430
+ await emitter.emit('watch-v2', {
1431
+ type: 'step-result',
1432
+ payload: {
1433
+ id: step.id,
1434
+ status: 'success',
1435
+ output: result?.result,
1436
+ },
1437
+ });
1438
+
1439
+ await emitter.emit('watch-v2', {
1440
+ type: 'step-finish',
1441
+ payload: {
1442
+ id: step.id,
1443
+ metadata: {},
1444
+ },
1445
+ });
1446
+
730
1447
  return { executionContext, result: { status: 'success', output: result?.result } };
731
1448
  },
732
1449
  );
733
1450
 
734
1451
  Object.assign(executionContext, res.executionContext);
735
- return res.result as StepResult<any>;
1452
+ return res.result as StepResult<any, any, any, any>;
736
1453
  }
737
1454
 
738
1455
  const stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
739
1456
  let execResults: any;
740
1457
  let suspended: { payload: any } | undefined;
1458
+ let bailed: { payload: any } | undefined;
1459
+
741
1460
  try {
742
1461
  const result = await step.execute({
1462
+ runId: executionContext.runId,
743
1463
  mastra: this.mastra!,
744
1464
  runtimeContext,
1465
+ writableStream,
745
1466
  inputData: prevOutput,
746
1467
  resumeData: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
747
1468
  getInitData: () => stepResults?.input as any,
@@ -757,22 +1478,56 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
757
1478
  executionContext.suspendedPaths[step.id] = executionContext.executionPath;
758
1479
  suspended = { payload: suspendPayload };
759
1480
  },
1481
+ bail: (result: any) => {
1482
+ bailed = { payload: result };
1483
+ },
760
1484
  resume: {
761
1485
  steps: resume?.steps?.slice(1) || [],
762
1486
  resumePayload: resume?.resumePayload,
763
1487
  // @ts-ignore
764
1488
  runId: stepResults[step.id]?.payload?.__workflow_meta?.runId,
765
1489
  },
766
- emitter,
1490
+ [EMITTER_SYMBOL]: emitter,
1491
+ engine: {
1492
+ step: this.inngestStep,
1493
+ },
1494
+ abortSignal: abortController.signal,
767
1495
  });
768
-
769
- execResults = { status: 'success', output: result };
1496
+ const endedAt = Date.now();
1497
+
1498
+ execResults = {
1499
+ status: 'success',
1500
+ output: result,
1501
+ startedAt,
1502
+ endedAt,
1503
+ payload: prevOutput,
1504
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1505
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1506
+ };
770
1507
  } catch (e) {
771
- execResults = { status: 'failed', error: e instanceof Error ? e.message : String(e) };
1508
+ execResults = {
1509
+ status: 'failed',
1510
+ payload: prevOutput,
1511
+ error: e instanceof Error ? e.message : String(e),
1512
+ endedAt: Date.now(),
1513
+ startedAt,
1514
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1515
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1516
+ };
772
1517
  }
773
1518
 
774
1519
  if (suspended) {
775
- execResults = { status: 'suspended', payload: suspended.payload };
1520
+ execResults = {
1521
+ status: 'suspended',
1522
+ suspendedPayload: suspended.payload,
1523
+ payload: prevOutput,
1524
+ suspendedAt: Date.now(),
1525
+ startedAt,
1526
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1527
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1528
+ };
1529
+ } else if (bailed) {
1530
+ execResults = { status: 'bailed', output: bailed.payload, payload: prevOutput, endedAt: Date.now(), startedAt };
776
1531
  }
777
1532
 
778
1533
  if (execResults.status === 'failed') {
@@ -786,12 +1541,11 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
786
1541
  payload: {
787
1542
  currentStep: {
788
1543
  id: step.id,
789
- status: execResults.status,
790
- output: execResults.output,
1544
+ ...execResults,
791
1545
  },
792
1546
  workflowState: {
793
1547
  status: 'running',
794
- steps: stepResults,
1548
+ steps: { ...stepResults, [step.id]: execResults },
795
1549
  result: null,
796
1550
  error: null,
797
1551
  },
@@ -799,6 +1553,32 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
799
1553
  eventTimestamp: Date.now(),
800
1554
  });
801
1555
 
1556
+ if (execResults.status === 'suspended') {
1557
+ await emitter.emit('watch-v2', {
1558
+ type: 'step-suspended',
1559
+ payload: {
1560
+ id: step.id,
1561
+ ...execResults,
1562
+ },
1563
+ });
1564
+ } else {
1565
+ await emitter.emit('watch-v2', {
1566
+ type: 'step-result',
1567
+ payload: {
1568
+ id: step.id,
1569
+ ...execResults,
1570
+ },
1571
+ });
1572
+
1573
+ await emitter.emit('watch-v2', {
1574
+ type: 'step-finish',
1575
+ payload: {
1576
+ id: step.id,
1577
+ metadata: {},
1578
+ },
1579
+ });
1580
+ }
1581
+
802
1582
  return { result: execResults, executionContext, stepResults };
803
1583
  });
804
1584
 
@@ -816,11 +1596,20 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
816
1596
  runId,
817
1597
  stepResults,
818
1598
  executionContext,
1599
+ serializedStepGraph,
1600
+ workflowStatus,
1601
+ result,
1602
+ error,
819
1603
  }: {
820
1604
  workflowId: string;
821
1605
  runId: string;
822
- stepResults: Record<string, StepResult<any>>;
1606
+ stepResults: Record<string, StepResult<any, any, any, any>>;
1607
+ serializedStepGraph: SerializedStepFlowEntry[];
823
1608
  executionContext: ExecutionContext;
1609
+ workflowStatus: 'success' | 'failed' | 'suspended' | 'running';
1610
+ result?: Record<string, any>;
1611
+ error?: string | Error;
1612
+ runtimeContext: RuntimeContext;
824
1613
  }) {
825
1614
  await this.inngestStep.run(
826
1615
  `workflow.${workflowId}.run.${runId}.path.${JSON.stringify(executionContext.executionPath)}.stepUpdate`,
@@ -834,6 +1623,10 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
834
1623
  context: stepResults as any,
835
1624
  activePaths: [],
836
1625
  suspendedPaths: executionContext.suspendedPaths,
1626
+ serializedStepGraph,
1627
+ status: workflowStatus,
1628
+ result,
1629
+ error,
837
1630
  // @ts-ignore
838
1631
  timestamp: Date.now(),
839
1632
  },
@@ -849,27 +1642,37 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
849
1642
  prevOutput,
850
1643
  prevStep,
851
1644
  stepResults,
1645
+ serializedStepGraph,
852
1646
  resume,
853
1647
  executionContext,
854
1648
  emitter,
1649
+ abortController,
855
1650
  runtimeContext,
1651
+ writableStream,
856
1652
  }: {
857
1653
  workflowId: string;
858
1654
  runId: string;
859
- entry: { type: 'conditional'; steps: StepFlowEntry[]; conditions: ExecuteFunction<any, any, any, any>[] };
1655
+ entry: {
1656
+ type: 'conditional';
1657
+ steps: StepFlowEntry[];
1658
+ conditions: ExecuteFunction<any, any, any, any, InngestEngineType>[];
1659
+ };
860
1660
  prevStep: StepFlowEntry;
1661
+ serializedStepGraph: SerializedStepFlowEntry[];
861
1662
  prevOutput: any;
862
- stepResults: Record<string, StepResult<any>>;
1663
+ stepResults: Record<string, StepResult<any, any, any, any>>;
863
1664
  resume?: {
864
1665
  steps: string[];
865
- stepResults: Record<string, StepResult<any>>;
1666
+ stepResults: Record<string, StepResult<any, any, any, any>>;
866
1667
  resumePayload: any;
867
1668
  resumePath: number[];
868
1669
  };
869
1670
  executionContext: ExecutionContext;
870
- emitter: { emit: (event: string, data: any) => Promise<void> };
1671
+ emitter: Emitter;
1672
+ abortController: AbortController;
871
1673
  runtimeContext: RuntimeContext;
872
- }): Promise<StepResult<any>> {
1674
+ writableStream?: WritableStream<ChunkType>;
1675
+ }): Promise<StepResult<any, any, any, any>> {
873
1676
  let execResults: any;
874
1677
  const truthyIndexes = (
875
1678
  await Promise.all(
@@ -877,8 +1680,11 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
877
1680
  this.inngestStep.run(`workflow.${workflowId}.conditional.${index}`, async () => {
878
1681
  try {
879
1682
  const result = await cond({
1683
+ runId,
1684
+ workflowId,
880
1685
  mastra: this.mastra!,
881
1686
  runtimeContext,
1687
+ runCount: -1,
882
1688
  inputData: prevOutput,
883
1689
  getInitData: () => stepResults?.input as any,
884
1690
  getStepResult: (step: any) => {
@@ -896,7 +1702,24 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
896
1702
 
897
1703
  // TODO: this function shouldn't have suspend probably?
898
1704
  suspend: async (_suspendPayload: any) => {},
899
- emitter,
1705
+ bail: () => {},
1706
+ abort: () => {
1707
+ abortController.abort();
1708
+ },
1709
+ [EMITTER_SYMBOL]: emitter,
1710
+ engine: {
1711
+ step: this.inngestStep,
1712
+ },
1713
+ abortSignal: abortController.signal,
1714
+ writer: new ToolStream(
1715
+ {
1716
+ prefix: 'step',
1717
+ callId: randomUUID(),
1718
+ name: 'conditional',
1719
+ runId,
1720
+ },
1721
+ writableStream,
1722
+ ),
900
1723
  });
901
1724
  return result ? index : null;
902
1725
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -909,7 +1732,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
909
1732
  ).filter((index: any): index is number => index !== null);
910
1733
 
911
1734
  const stepsToRun = entry.steps.filter((_, index) => truthyIndexes.includes(index));
912
- const results: StepResult<any>[] = await Promise.all(
1735
+ const results: { result: StepResult<any, any, any, any> }[] = await Promise.all(
913
1736
  stepsToRun.map((step, index) =>
914
1737
  this.executeEntry({
915
1738
  workflowId,
@@ -918,6 +1741,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
918
1741
  prevStep,
919
1742
  stepResults,
920
1743
  resume,
1744
+ serializedStepGraph,
921
1745
  executionContext: {
922
1746
  workflowId,
923
1747
  runId,
@@ -927,21 +1751,25 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
927
1751
  executionSpan: executionContext.executionSpan,
928
1752
  },
929
1753
  emitter,
1754
+ abortController,
930
1755
  runtimeContext,
1756
+ writableStream,
931
1757
  }),
932
1758
  ),
933
1759
  );
934
- const hasFailed = results.find(result => result.status === 'failed');
935
- const hasSuspended = results.find(result => result.status === 'suspended');
1760
+ const hasFailed = results.find(result => result.result.status === 'failed') as {
1761
+ result: StepFailure<any, any, any>;
1762
+ };
1763
+ const hasSuspended = results.find(result => result.result.status === 'suspended');
936
1764
  if (hasFailed) {
937
- execResults = { status: 'failed', error: hasFailed.error };
1765
+ execResults = { status: 'failed', error: hasFailed.result.error };
938
1766
  } else if (hasSuspended) {
939
- execResults = { status: 'suspended', payload: hasSuspended.payload };
1767
+ execResults = { status: 'suspended', payload: hasSuspended.result.suspendPayload };
940
1768
  } else {
941
1769
  execResults = {
942
1770
  status: 'success',
943
1771
  output: results.reduce((acc: Record<string, any>, result, index) => {
944
- if (result.status === 'success') {
1772
+ if (result.result.status === 'success') {
945
1773
  // @ts-ignore
946
1774
  acc[stepsToRun[index]!.step.id] = result.output;
947
1775
  }