@mastra/inngest 0.0.0-vector-query-sources-20250516172905 → 0.0.0-vector-query-tool-provider-options-20250828222356

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,55 @@
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, VNextWorkflowRun, VNextWorkflowRuns } from '@mastra/core';
4
+ import type { Agent } from '@mastra/core/agent';
5
+ import { AISpanType, wrapMastra } from '@mastra/core/ai-tracing';
6
+ import type { TracingContext, AnyAISpan } from '@mastra/core/ai-tracing';
4
7
  import { RuntimeContext } from '@mastra/core/di';
5
- import { NewWorkflow, createStep, Run, DefaultExecutionEngine, cloneStep } from '@mastra/core/workflows/vNext';
8
+ import type { Mastra } from '@mastra/core/mastra';
9
+ import type { WorkflowRun, WorkflowRuns } from '@mastra/core/storage';
10
+ import type { ToolExecutionContext } from '@mastra/core/tools';
11
+ import { Tool, ToolStream } from '@mastra/core/tools';
12
+ import { Workflow, Run, DefaultExecutionEngine } from '@mastra/core/workflows';
6
13
  import type {
7
14
  ExecuteFunction,
8
15
  ExecutionContext,
9
16
  ExecutionEngine,
10
17
  ExecutionGraph,
11
- NewStep,
12
- NewStep as Step,
13
- NewWorkflowConfig,
18
+ Step,
19
+ WorkflowConfig,
14
20
  StepFlowEntry,
15
21
  StepResult,
16
22
  WorkflowResult,
17
- } from '@mastra/core/workflows/vNext';
23
+ SerializedStepFlowEntry,
24
+ StepFailure,
25
+ Emitter,
26
+ WatchEvent,
27
+ StreamEvent,
28
+ ChunkType,
29
+ } from '@mastra/core/workflows';
30
+ import { EMITTER_SYMBOL } from '@mastra/core/workflows/_constants';
18
31
  import type { Span } from '@opentelemetry/api';
19
32
  import type { Inngest, BaseContext } from 'inngest';
20
33
  import { serve as inngestServe } from 'inngest/hono';
21
- import type { z } from 'zod';
34
+ import { z } from 'zod';
35
+
36
+ export type InngestEngineType = {
37
+ step: any;
38
+ };
22
39
 
23
40
  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
- });
41
+ const wfs = mastra.getWorkflows();
42
+ const functions = Array.from(
43
+ new Set(
44
+ Object.values(wfs).flatMap(wf => {
45
+ if (wf instanceof InngestWorkflow) {
46
+ wf.__registerMastra(mastra);
47
+ return wf.getFunctions();
48
+ }
49
+ return [];
50
+ }),
51
+ ),
52
+ );
32
53
  return inngestServe({
33
54
  client: inngest,
34
55
  functions,
@@ -36,11 +57,13 @@ export function serve({ mastra, inngest }: { mastra: Mastra; inngest: Inngest })
36
57
  }
37
58
 
38
59
  export class InngestRun<
39
- TSteps extends NewStep<string, any, any>[] = NewStep<string, any, any>[],
60
+ TEngineType = InngestEngineType,
61
+ TSteps extends Step<string, any, any>[] = Step<string, any, any>[],
40
62
  TInput extends z.ZodType<any> = z.ZodType<any>,
41
63
  TOutput extends z.ZodType<any> = z.ZodType<any>,
42
- > extends Run<TSteps, TInput, TOutput> {
64
+ > extends Run<TEngineType, TSteps, TInput, TOutput> {
43
65
  private inngest: Inngest;
66
+ serializedStepGraph: SerializedStepFlowEntry[];
44
67
  #mastra: Mastra;
45
68
 
46
69
  constructor(
@@ -49,6 +72,7 @@ export class InngestRun<
49
72
  runId: string;
50
73
  executionEngine: ExecutionEngine;
51
74
  executionGraph: ExecutionGraph;
75
+ serializedStepGraph: SerializedStepFlowEntry[];
52
76
  mastra?: Mastra;
53
77
  retryConfig?: {
54
78
  attempts?: number;
@@ -60,11 +84,12 @@ export class InngestRun<
60
84
  ) {
61
85
  super(params);
62
86
  this.inngest = inngest;
87
+ this.serializedStepGraph = params.serializedStepGraph;
63
88
  this.#mastra = params.mastra!;
64
89
  }
65
90
 
66
91
  async getRuns(eventId: string) {
67
- const response = await fetch(`${this.inngest.apiBaseUrl}/v1/events/${eventId}/runs`, {
92
+ const response = await fetch(`${this.inngest.apiBaseUrl ?? 'https://api.inngest.com'}/v1/events/${eventId}/runs`, {
68
93
  headers: {
69
94
  Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`,
70
95
  },
@@ -75,16 +100,55 @@ export class InngestRun<
75
100
 
76
101
  async getRunOutput(eventId: string) {
77
102
  let runs = await this.getRuns(eventId);
78
- while (runs?.[0]?.status !== 'Completed') {
103
+
104
+ while (runs?.[0]?.status !== 'Completed' || runs?.[0]?.event_id !== eventId) {
79
105
  await new Promise(resolve => setTimeout(resolve, 1000));
80
106
  runs = await this.getRuns(eventId);
81
- if (runs?.[0]?.status === 'Failed' || runs?.[0]?.status === 'Cancelled') {
107
+ if (runs?.[0]?.status === 'Failed') {
108
+ console.log('run', runs?.[0]);
82
109
  throw new Error(`Function run ${runs?.[0]?.status}`);
110
+ } else if (runs?.[0]?.status === 'Cancelled') {
111
+ const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
112
+ workflowName: this.workflowId,
113
+ runId: this.runId,
114
+ });
115
+ return { output: { result: { steps: snapshot?.context, status: 'canceled' } } };
83
116
  }
84
117
  }
85
118
  return runs?.[0];
86
119
  }
87
120
 
121
+ async sendEvent(event: string, data: any) {
122
+ await this.inngest.send({
123
+ name: `user-event-${event}`,
124
+ data,
125
+ });
126
+ }
127
+
128
+ async cancel() {
129
+ await this.inngest.send({
130
+ name: `cancel.workflow.${this.workflowId}`,
131
+ data: {
132
+ runId: this.runId,
133
+ },
134
+ });
135
+
136
+ const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
137
+ workflowName: this.workflowId,
138
+ runId: this.runId,
139
+ });
140
+ if (snapshot) {
141
+ await this.#mastra?.storage?.persistWorkflowSnapshot({
142
+ workflowName: this.workflowId,
143
+ runId: this.runId,
144
+ snapshot: {
145
+ ...snapshot,
146
+ status: 'canceled' as any,
147
+ },
148
+ });
149
+ }
150
+ }
151
+
88
152
  async start({
89
153
  inputData,
90
154
  }: {
@@ -96,11 +160,14 @@ export class InngestRun<
96
160
  runId: this.runId,
97
161
  snapshot: {
98
162
  runId: this.runId,
163
+ serializedStepGraph: this.serializedStepGraph,
99
164
  value: {},
100
165
  context: {} as any,
101
166
  activePaths: [],
102
167
  suspendedPaths: {},
168
+ waitingPaths: {},
103
169
  timestamp: Date.now(),
170
+ status: 'running',
104
171
  },
105
172
  });
106
173
 
@@ -122,7 +189,9 @@ export class InngestRun<
122
189
  result.error = new Error(result.error);
123
190
  }
124
191
 
125
- this.cleanup?.();
192
+ if (result.status !== 'suspended') {
193
+ this.cleanup?.();
194
+ }
126
195
  return result;
127
196
  }
128
197
 
@@ -134,6 +203,27 @@ export class InngestRun<
134
203
  | string
135
204
  | string[];
136
205
  runtimeContext?: RuntimeContext;
206
+ }): Promise<WorkflowResult<TOutput, TSteps>> {
207
+ const p = this._resume(params).then(result => {
208
+ if (result.status !== 'suspended') {
209
+ this.closeStreamAction?.().catch(() => {});
210
+ }
211
+
212
+ return result;
213
+ });
214
+
215
+ this.executionResults = p;
216
+ return p;
217
+ }
218
+
219
+ async _resume<TResumeSchema extends z.ZodType<any>>(params: {
220
+ resumeData?: z.infer<TResumeSchema>;
221
+ step:
222
+ | Step<string, any, any, TResumeSchema, any>
223
+ | [...Step<string, any, any, any, any>[], Step<string, any, any, TResumeSchema, any>]
224
+ | string
225
+ | string[];
226
+ runtimeContext?: RuntimeContext;
137
227
  }): Promise<WorkflowResult<TOutput, TSteps>> {
138
228
  const steps: string[] = (Array.isArray(params.step) ? params.step : [params.step]).map(step =>
139
229
  typeof step === 'string' ? step : step?.id,
@@ -148,6 +238,7 @@ export class InngestRun<
148
238
  data: {
149
239
  inputData: params.resumeData,
150
240
  runId: this.runId,
241
+ workflowId: this.workflowId,
151
242
  stepResults: snapshot?.context as any,
152
243
  resume: {
153
244
  steps,
@@ -171,43 +262,88 @@ export class InngestRun<
171
262
  return result;
172
263
  }
173
264
 
174
- watch(cb: (event: any) => void): () => void {
265
+ watch(cb: (event: WatchEvent) => void, type: 'watch' | 'watch-v2' = 'watch'): () => void {
266
+ let active = true;
175
267
  const streamPromise = subscribe(
176
268
  {
177
269
  channel: `workflow:${this.workflowId}:${this.runId}`,
178
- topics: ['watch'],
270
+ topics: [type],
179
271
  app: this.inngest,
180
272
  },
181
273
  (message: any) => {
182
- cb(message.data);
274
+ if (active) {
275
+ cb(message.data);
276
+ }
183
277
  },
184
278
  );
185
279
 
186
280
  return () => {
281
+ active = false;
187
282
  streamPromise
188
- .then((stream: any) => {
189
- stream.cancel();
283
+ .then(async (stream: Awaited<typeof streamPromise>) => {
284
+ return stream.cancel();
190
285
  })
191
286
  .catch(err => {
192
287
  console.error(err);
193
288
  });
194
289
  };
195
290
  }
291
+
292
+ stream({ inputData, runtimeContext }: { inputData?: z.infer<TInput>; runtimeContext?: RuntimeContext } = {}): {
293
+ stream: ReadableStream<StreamEvent>;
294
+ getWorkflowState: () => Promise<WorkflowResult<TOutput, TSteps>>;
295
+ } {
296
+ const { readable, writable } = new TransformStream<StreamEvent, StreamEvent>();
297
+
298
+ const writer = writable.getWriter();
299
+ const unwatch = this.watch(async event => {
300
+ try {
301
+ // watch-v2 events are data stream events, so we need to cast them to the correct type
302
+ await writer.write(event as any);
303
+ } catch {}
304
+ }, 'watch-v2');
305
+
306
+ this.closeStreamAction = async () => {
307
+ unwatch();
308
+
309
+ try {
310
+ await writer.close();
311
+ } catch (err) {
312
+ console.error('Error closing stream:', err);
313
+ } finally {
314
+ writer.releaseLock();
315
+ }
316
+ };
317
+
318
+ this.executionResults = this.start({ inputData, runtimeContext }).then(result => {
319
+ if (result.status !== 'suspended') {
320
+ this.closeStreamAction?.().catch(() => {});
321
+ }
322
+
323
+ return result;
324
+ });
325
+
326
+ return {
327
+ stream: readable as ReadableStream<StreamEvent>,
328
+ getWorkflowState: () => this.executionResults!,
329
+ };
330
+ }
196
331
  }
197
332
 
198
333
  export class InngestWorkflow<
199
- TSteps extends NewStep<string, any, any>[] = NewStep<string, any, any>[],
334
+ TEngineType = InngestEngineType,
335
+ TSteps extends Step<string, any, any>[] = Step<string, any, any>[],
200
336
  TWorkflowId extends string = string,
201
337
  TInput extends z.ZodType<any> = z.ZodType<any>,
202
338
  TOutput extends z.ZodType<any> = z.ZodType<any>,
203
339
  TPrevSchema extends z.ZodType<any> = TInput,
204
- > extends NewWorkflow<TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> {
340
+ > extends Workflow<TEngineType, TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> {
205
341
  #mastra: Mastra;
206
342
  public inngest: Inngest;
207
343
 
208
344
  private function: ReturnType<Inngest['createFunction']> | undefined;
209
345
 
210
- constructor(params: NewWorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>, inngest: Inngest) {
346
+ constructor(params: WorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>, inngest: Inngest) {
211
347
  super(params);
212
348
  this.#mastra = params.mastra!;
213
349
  this.inngest = inngest;
@@ -226,25 +362,52 @@ export class InngestWorkflow<
226
362
  return { runs: [], total: 0 };
227
363
  }
228
364
 
229
- return storage.getWorkflowRuns({ workflowName: this.id, ...(args ?? {}) }) as unknown as VNextWorkflowRuns;
365
+ return storage.getWorkflowRuns({ workflowName: this.id, ...(args ?? {}) }) as unknown as WorkflowRuns;
230
366
  }
231
367
 
232
- async getWorkflowRunById(runId: string): Promise<VNextWorkflowRun | null> {
368
+ async getWorkflowRunById(runId: string): Promise<WorkflowRun | null> {
233
369
  const storage = this.#mastra?.getStorage();
234
370
  if (!storage) {
235
371
  this.logger.debug('Cannot get workflow runs. Mastra engine is not initialized');
236
- return null;
372
+ //returning in memory run if no storage is initialized
373
+ return this.runs.get(runId)
374
+ ? ({ ...this.runs.get(runId), workflowName: this.id } as unknown as WorkflowRun)
375
+ : null;
237
376
  }
238
- const run = (await storage.getWorkflowRunById({ runId, workflowName: this.id })) as unknown as VNextWorkflowRun;
377
+ const run = (await storage.getWorkflowRunById({ runId, workflowName: this.id })) as unknown as WorkflowRun;
239
378
 
240
379
  return (
241
380
  run ??
242
- (this.runs.get(runId)
243
- ? ({ ...this.runs.get(runId), workflowName: this.id } as unknown as VNextWorkflowRun)
244
- : null)
381
+ (this.runs.get(runId) ? ({ ...this.runs.get(runId), workflowName: this.id } as unknown as WorkflowRun) : null)
245
382
  );
246
383
  }
247
384
 
385
+ async getWorkflowRunExecutionResult(runId: string): Promise<WatchEvent['payload']['workflowState'] | null> {
386
+ const storage = this.#mastra?.getStorage();
387
+ if (!storage) {
388
+ this.logger.debug('Cannot get workflow run execution result. Mastra storage is not initialized');
389
+ return null;
390
+ }
391
+
392
+ const run = await storage.getWorkflowRunById({ runId, workflowName: this.id });
393
+
394
+ if (!run?.snapshot) {
395
+ return null;
396
+ }
397
+
398
+ if (typeof run.snapshot === 'string') {
399
+ return null;
400
+ }
401
+
402
+ return {
403
+ status: run.snapshot.status,
404
+ result: run.snapshot.result,
405
+ error: run.snapshot.error,
406
+ payload: run.snapshot.context?.input,
407
+ steps: run.snapshot.context as any,
408
+ };
409
+ }
410
+
248
411
  __registerMastra(mastra: Mastra) {
249
412
  this.#mastra = mastra;
250
413
  this.executionEngine.__registerMastra(mastra);
@@ -268,11 +431,35 @@ export class InngestWorkflow<
268
431
  }
269
432
  }
270
433
 
271
- createRun(options?: { runId?: string }): Run<TSteps, TInput, TOutput> {
434
+ createRun(options?: { runId?: string }): Run<TEngineType, TSteps, TInput, TOutput> {
435
+ const runIdToUse = options?.runId || randomUUID();
436
+
437
+ // Return a new Run instance with object parameters
438
+ const run: Run<TEngineType, TSteps, TInput, TOutput> =
439
+ this.runs.get(runIdToUse) ??
440
+ new InngestRun(
441
+ {
442
+ workflowId: this.id,
443
+ runId: runIdToUse,
444
+ executionEngine: this.executionEngine,
445
+ executionGraph: this.executionGraph,
446
+ serializedStepGraph: this.serializedStepGraph,
447
+ mastra: this.#mastra,
448
+ retryConfig: this.retryConfig,
449
+ cleanup: () => this.runs.delete(runIdToUse),
450
+ },
451
+ this.inngest,
452
+ );
453
+
454
+ this.runs.set(runIdToUse, run);
455
+ return run;
456
+ }
457
+
458
+ async createRunAsync(options?: { runId?: string }): Promise<Run<TEngineType, TSteps, TInput, TOutput>> {
272
459
  const runIdToUse = options?.runId || randomUUID();
273
460
 
274
461
  // Return a new Run instance with object parameters
275
- const run: Run<TSteps, TInput, TOutput> =
462
+ const run: Run<TEngineType, TSteps, TInput, TOutput> =
276
463
  this.runs.get(runIdToUse) ??
277
464
  new InngestRun(
278
465
  {
@@ -280,6 +467,7 @@ export class InngestWorkflow<
280
467
  runId: runIdToUse,
281
468
  executionEngine: this.executionEngine,
282
469
  executionGraph: this.executionGraph,
470
+ serializedStepGraph: this.serializedStepGraph,
283
471
  mastra: this.#mastra,
284
472
  retryConfig: this.retryConfig,
285
473
  cleanup: () => this.runs.delete(runIdToUse),
@@ -288,6 +476,30 @@ export class InngestWorkflow<
288
476
  );
289
477
 
290
478
  this.runs.set(runIdToUse, run);
479
+
480
+ const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse);
481
+
482
+ if (!workflowSnapshotInStorage) {
483
+ await this.mastra?.getStorage()?.persistWorkflowSnapshot({
484
+ workflowName: this.id,
485
+ runId: runIdToUse,
486
+ snapshot: {
487
+ runId: runIdToUse,
488
+ status: 'pending',
489
+ value: {},
490
+ context: {},
491
+ activePaths: [],
492
+ waitingPaths: {},
493
+ serializedStepGraph: this.serializedStepGraph,
494
+ suspendedPaths: {},
495
+ result: undefined,
496
+ error: undefined,
497
+ // @ts-ignore
498
+ timestamp: Date.now(),
499
+ },
500
+ });
501
+ }
502
+
291
503
  return run;
292
504
  }
293
505
 
@@ -296,8 +508,12 @@ export class InngestWorkflow<
296
508
  return this.function;
297
509
  }
298
510
  this.function = this.inngest.createFunction(
299
- // @ts-ignore
300
- { id: `workflow.${this.id}`, retries: this.retryConfig?.attempts ?? 0 },
511
+ {
512
+ id: `workflow.${this.id}`,
513
+ // @ts-ignore
514
+ retries: this.retryConfig?.attempts ?? 0,
515
+ cancelOn: [{ event: `cancel.workflow.${this.id}` }],
516
+ },
301
517
  { event: `workflow.${this.id}` },
302
518
  async ({ event, step, attempt, publish }) => {
303
519
  let { inputData, runId, resume } = event.data;
@@ -317,13 +533,22 @@ export class InngestWorkflow<
317
533
  try {
318
534
  await publish({
319
535
  channel: `workflow:${this.id}:${runId}`,
320
- topic: 'watch',
536
+ topic: event,
321
537
  data,
322
538
  });
323
539
  } catch (err: any) {
324
540
  this.logger.error('Error emitting event: ' + (err?.stack ?? err?.message ?? err));
325
541
  }
326
542
  },
543
+ on: (_event: string, _callback: (data: any) => void) => {
544
+ // no-op
545
+ },
546
+ off: (_event: string, _callback: (data: any) => void) => {
547
+ // no-op
548
+ },
549
+ once: (_event: string, _callback: (data: any) => void) => {
550
+ // no-op
551
+ },
327
552
  };
328
553
 
329
554
  const engine = new InngestExecutionEngine(this.#mastra, step, attempt);
@@ -331,11 +556,14 @@ export class InngestWorkflow<
331
556
  workflowId: this.id,
332
557
  runId,
333
558
  graph: this.executionGraph,
559
+ serializedStepGraph: this.serializedStepGraph,
334
560
  input: inputData,
335
561
  emitter,
336
562
  retryConfig: this.retryConfig,
337
563
  runtimeContext: new RuntimeContext(), // TODO
338
564
  resume,
565
+ abortController: new AbortController(),
566
+ currentSpan: undefined, // TODO: Pass actual parent AI span from workflow execution context
339
567
  });
340
568
 
341
569
  return { result, runId };
@@ -364,29 +592,199 @@ export class InngestWorkflow<
364
592
  }
365
593
  }
366
594
 
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>[],
595
+ function isAgent(params: any): params is Agent<any, any, any> {
596
+ return params?.component === 'AGENT';
597
+ }
598
+
599
+ function isTool(params: any): params is Tool<any, any, any> {
600
+ return params instanceof Tool;
601
+ }
602
+
603
+ export function createStep<
604
+ TStepId extends string,
605
+ TStepInput extends z.ZodType<any>,
606
+ TStepOutput extends z.ZodType<any>,
607
+ TResumeSchema extends z.ZodType<any>,
608
+ TSuspendSchema extends z.ZodType<any>,
609
+ >(params: {
610
+ id: TStepId;
611
+ description?: string;
612
+ inputSchema: TStepInput;
613
+ outputSchema: TStepOutput;
614
+ resumeSchema?: TResumeSchema;
615
+ suspendSchema?: TSuspendSchema;
616
+ execute: ExecuteFunction<
617
+ z.infer<TStepInput>,
618
+ z.infer<TStepOutput>,
619
+ z.infer<TResumeSchema>,
620
+ z.infer<TSuspendSchema>,
621
+ InngestEngineType
622
+ >;
623
+ }): Step<TStepId, TStepInput, TStepOutput, TResumeSchema, TSuspendSchema, InngestEngineType>;
624
+
625
+ export function createStep<
626
+ TStepId extends string,
627
+ TStepInput extends z.ZodObject<{ prompt: z.ZodString }>,
628
+ TStepOutput extends z.ZodObject<{ text: z.ZodString }>,
629
+ TResumeSchema extends z.ZodType<any>,
630
+ TSuspendSchema extends z.ZodType<any>,
372
631
  >(
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
- );
632
+ agent: Agent<TStepId, any, any>,
633
+ ): Step<TStepId, TStepInput, TStepOutput, TResumeSchema, TSuspendSchema, InngestEngineType>;
634
+
635
+ export function createStep<
636
+ TSchemaIn extends z.ZodType<any>,
637
+ TSchemaOut extends z.ZodType<any>,
638
+ TContext extends ToolExecutionContext<TSchemaIn>,
639
+ >(
640
+ tool: Tool<TSchemaIn, TSchemaOut, TContext> & {
641
+ inputSchema: TSchemaIn;
642
+ outputSchema: TSchemaOut;
643
+ execute: (context: TContext) => Promise<any>;
644
+ },
645
+ ): Step<string, TSchemaIn, TSchemaOut, z.ZodType<any>, z.ZodType<any>, InngestEngineType>;
646
+ export function createStep<
647
+ TStepId extends string,
648
+ TStepInput extends z.ZodType<any>,
649
+ TStepOutput extends z.ZodType<any>,
650
+ TResumeSchema extends z.ZodType<any>,
651
+ TSuspendSchema extends z.ZodType<any>,
652
+ >(
653
+ params:
654
+ | {
655
+ id: TStepId;
656
+ description?: string;
657
+ inputSchema: TStepInput;
658
+ outputSchema: TStepOutput;
659
+ resumeSchema?: TResumeSchema;
660
+ suspendSchema?: TSuspendSchema;
661
+ execute: ExecuteFunction<
662
+ z.infer<TStepInput>,
663
+ z.infer<TStepOutput>,
664
+ z.infer<TResumeSchema>,
665
+ z.infer<TSuspendSchema>,
666
+ InngestEngineType
667
+ >;
668
+ }
669
+ | Agent<any, any, any>
670
+ | (Tool<TStepInput, TStepOutput, any> & {
671
+ inputSchema: TStepInput;
672
+ outputSchema: TStepOutput;
673
+ execute: (context: ToolExecutionContext<TStepInput>) => Promise<any>;
674
+ }),
675
+ ): Step<TStepId, TStepInput, TStepOutput, TResumeSchema, TSuspendSchema, InngestEngineType> {
676
+ if (isAgent(params)) {
677
+ return {
678
+ id: params.name,
679
+ // @ts-ignore
680
+ inputSchema: z.object({
681
+ prompt: z.string(),
682
+ // resourceId: z.string().optional(),
683
+ // threadId: z.string().optional(),
684
+ }),
685
+ // @ts-ignore
686
+ outputSchema: z.object({
687
+ text: z.string(),
688
+ }),
689
+ execute: async ({ inputData, [EMITTER_SYMBOL]: emitter, runtimeContext, abortSignal, abort, tracingContext }) => {
690
+ let streamPromise = {} as {
691
+ promise: Promise<string>;
692
+ resolve: (value: string) => void;
693
+ reject: (reason?: any) => void;
694
+ };
695
+
696
+ streamPromise.promise = new Promise((resolve, reject) => {
697
+ streamPromise.resolve = resolve;
698
+ streamPromise.reject = reject;
699
+ });
700
+ const toolData = {
701
+ name: params.name,
702
+ args: inputData,
703
+ };
704
+ await emitter.emit('watch-v2', {
705
+ type: 'tool-call-streaming-start',
706
+ ...toolData,
707
+ });
708
+ const { fullStream } = await params.stream(inputData.prompt, {
709
+ // resourceId: inputData.resourceId,
710
+ // threadId: inputData.threadId,
711
+ runtimeContext,
712
+ tracingContext,
713
+ onFinish: result => {
714
+ streamPromise.resolve(result.text);
715
+ },
716
+ abortSignal,
717
+ });
718
+
719
+ if (abortSignal.aborted) {
720
+ return abort();
721
+ }
722
+
723
+ for await (const chunk of fullStream) {
724
+ switch (chunk.type) {
725
+ case 'text-delta':
726
+ await emitter.emit('watch-v2', {
727
+ type: 'tool-call-delta',
728
+ ...toolData,
729
+ argsTextDelta: chunk.textDelta,
730
+ });
731
+ break;
732
+
733
+ case 'step-start':
734
+ case 'step-finish':
735
+ case 'finish':
736
+ break;
737
+
738
+ case 'tool-call':
739
+ case 'tool-result':
740
+ case 'tool-call-streaming-start':
741
+ case 'tool-call-delta':
742
+ case 'source':
743
+ case 'file':
744
+ default:
745
+ await emitter.emit('watch-v2', chunk);
746
+ break;
747
+ }
748
+ }
749
+
750
+ return {
751
+ text: await streamPromise.promise,
752
+ };
753
+ },
754
+ };
755
+ }
756
+
757
+ if (isTool(params)) {
758
+ if (!params.inputSchema || !params.outputSchema) {
759
+ throw new Error('Tool must have input and output schemas defined');
760
+ }
761
+
762
+ return {
763
+ // TODO: tool probably should have strong id type
764
+ // @ts-ignore
765
+ id: params.id,
766
+ inputSchema: params.inputSchema,
767
+ outputSchema: params.outputSchema,
768
+ execute: async ({ inputData, mastra, runtimeContext, tracingContext }) => {
769
+ return params.execute({
770
+ context: inputData,
771
+ mastra: wrapMastra(mastra, tracingContext),
772
+ runtimeContext,
773
+ tracingContext,
774
+ });
775
+ },
776
+ };
777
+ }
386
778
 
387
- wf.setStepFlow(workflow.stepGraph);
388
- wf.commit();
389
- return wf;
779
+ return {
780
+ id: params.id,
781
+ description: params.description,
782
+ inputSchema: params.inputSchema,
783
+ outputSchema: params.outputSchema,
784
+ resumeSchema: params.resumeSchema,
785
+ suspendSchema: params.suspendSchema,
786
+ execute: params.execute,
787
+ };
390
788
  }
391
789
 
392
790
  export function init(inngest: Inngest) {
@@ -395,13 +793,59 @@ export function init(inngest: Inngest) {
395
793
  TWorkflowId extends string = string,
396
794
  TInput extends z.ZodType<any> = z.ZodType<any>,
397
795
  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);
796
+ TSteps extends Step<string, any, any, any, any, InngestEngineType>[] = Step<
797
+ string,
798
+ any,
799
+ any,
800
+ any,
801
+ any,
802
+ InngestEngineType
803
+ >[],
804
+ >(params: WorkflowConfig<TWorkflowId, TInput, TOutput, TSteps>) {
805
+ return new InngestWorkflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TInput>(params, inngest);
401
806
  },
402
807
  createStep,
403
- cloneStep,
404
- cloneWorkflow,
808
+ cloneStep<TStepId extends string>(
809
+ step: Step<string, any, any, any, any, InngestEngineType>,
810
+ opts: { id: TStepId },
811
+ ): Step<TStepId, any, any, any, any, InngestEngineType> {
812
+ return {
813
+ id: opts.id,
814
+ description: step.description,
815
+ inputSchema: step.inputSchema,
816
+ outputSchema: step.outputSchema,
817
+ execute: step.execute,
818
+ };
819
+ },
820
+ cloneWorkflow<
821
+ TWorkflowId extends string = string,
822
+ TInput extends z.ZodType<any> = z.ZodType<any>,
823
+ TOutput extends z.ZodType<any> = z.ZodType<any>,
824
+ TSteps extends Step<string, any, any, any, any, InngestEngineType>[] = Step<
825
+ string,
826
+ any,
827
+ any,
828
+ any,
829
+ any,
830
+ InngestEngineType
831
+ >[],
832
+ TPrevSchema extends z.ZodType<any> = TInput,
833
+ >(
834
+ workflow: Workflow<InngestEngineType, TSteps, string, TInput, TOutput, TPrevSchema>,
835
+ opts: { id: TWorkflowId },
836
+ ): Workflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> {
837
+ const wf: Workflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> = new Workflow({
838
+ id: opts.id,
839
+ inputSchema: workflow.inputSchema,
840
+ outputSchema: workflow.outputSchema,
841
+ steps: workflow.stepDefs,
842
+ mastra: workflow.mastra,
843
+ });
844
+
845
+ wf.setStepFlow(workflow.stepGraph);
846
+ wf.commit();
847
+ return wf;
848
+ },
405
849
  };
406
850
  }
407
851
 
@@ -415,11 +859,48 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
415
859
  this.inngestAttempts = inngestAttempts;
416
860
  }
417
861
 
862
+ async execute<TInput, TOutput>(params: {
863
+ workflowId: string;
864
+ runId: string;
865
+ graph: ExecutionGraph;
866
+ serializedStepGraph: SerializedStepFlowEntry[];
867
+ input?: TInput;
868
+ resume?: {
869
+ // TODO: add execute path
870
+ steps: string[];
871
+ stepResults: Record<string, StepResult<any, any, any, any>>;
872
+ resumePayload: any;
873
+ resumePath: number[];
874
+ };
875
+ emitter: Emitter;
876
+ retryConfig?: {
877
+ attempts?: number;
878
+ delay?: number;
879
+ };
880
+ runtimeContext: RuntimeContext;
881
+ abortController: AbortController;
882
+ currentSpan?: AnyAISpan;
883
+ }): Promise<TOutput> {
884
+ await params.emitter.emit('watch-v2', {
885
+ type: 'start',
886
+ payload: { runId: params.runId },
887
+ });
888
+
889
+ const result = await super.execute<TInput, TOutput>(params);
890
+
891
+ await params.emitter.emit('watch-v2', {
892
+ type: 'finish',
893
+ payload: { runId: params.runId },
894
+ });
895
+
896
+ return result;
897
+ }
898
+
418
899
  protected async fmtReturnValue<TOutput>(
419
900
  executionSpan: Span | undefined,
420
- emitter: { emit: (event: string, data: any) => Promise<void> },
421
- stepResults: Record<string, StepResult<any>>,
422
- lastOutput: StepResult<any>,
901
+ emitter: Emitter,
902
+ stepResults: Record<string, StepResult<any, any, any, any>>,
903
+ lastOutput: StepResult<any, any, any, any>,
423
904
  error?: Error | string,
424
905
  ): Promise<TOutput> {
425
906
  const base: any = {
@@ -489,41 +970,254 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
489
970
  return base as TOutput;
490
971
  }
491
972
 
492
- async superExecuteStep({
973
+ // async executeSleep({ id, duration }: { id: string; duration: number }): Promise<void> {
974
+ // await this.inngestStep.sleep(id, duration);
975
+ // }
976
+
977
+ async executeSleep({
493
978
  workflowId,
494
979
  runId,
495
- step,
496
- stepResults,
497
- executionContext,
498
- resume,
980
+ entry,
499
981
  prevOutput,
982
+ stepResults,
500
983
  emitter,
984
+ abortController,
501
985
  runtimeContext,
986
+ writableStream,
987
+ tracingContext,
502
988
  }: {
503
989
  workflowId: string;
504
990
  runId: string;
505
- step: Step<string, any, any>;
506
- stepResults: Record<string, StepResult<any>>;
507
- executionContext: ExecutionContext;
991
+ serializedStepGraph: SerializedStepFlowEntry[];
992
+ entry: {
993
+ type: 'sleep';
994
+ id: string;
995
+ duration?: number;
996
+ fn?: ExecuteFunction<any, any, any, any, InngestEngineType>;
997
+ };
998
+ prevStep: StepFlowEntry;
999
+ prevOutput: any;
1000
+ stepResults: Record<string, StepResult<any, any, any, any>>;
508
1001
  resume?: {
509
1002
  steps: string[];
1003
+ stepResults: Record<string, StepResult<any, any, any, any>>;
510
1004
  resumePayload: any;
1005
+ resumePath: number[];
511
1006
  };
1007
+ executionContext: ExecutionContext;
1008
+ emitter: Emitter;
1009
+ abortController: AbortController;
1010
+ runtimeContext: RuntimeContext;
1011
+ writableStream?: WritableStream<ChunkType>;
1012
+ tracingContext?: TracingContext;
1013
+ }): Promise<void> {
1014
+ let { duration, fn } = entry;
1015
+
1016
+ const sleepSpan = tracingContext?.currentSpan?.createChildSpan({
1017
+ type: AISpanType.WORKFLOW_SLEEP,
1018
+ name: `sleep: ${duration ? `${duration}ms` : 'dynamic'}`,
1019
+ attributes: {
1020
+ durationMs: duration,
1021
+ sleepType: fn ? 'dynamic' : 'fixed',
1022
+ },
1023
+ });
1024
+
1025
+ if (fn) {
1026
+ const stepCallId = randomUUID();
1027
+ duration = await this.inngestStep.run(`workflow.${workflowId}.sleep.${entry.id}`, async () => {
1028
+ return await fn({
1029
+ runId,
1030
+ workflowId,
1031
+ mastra: this.mastra!,
1032
+ runtimeContext,
1033
+ inputData: prevOutput,
1034
+ runCount: -1,
1035
+ tracingContext: {
1036
+ currentSpan: sleepSpan,
1037
+ },
1038
+ getInitData: () => stepResults?.input as any,
1039
+ getStepResult: (step: any) => {
1040
+ if (!step?.id) {
1041
+ return null;
1042
+ }
1043
+
1044
+ const result = stepResults[step.id];
1045
+ if (result?.status === 'success') {
1046
+ return result.output;
1047
+ }
1048
+
1049
+ return null;
1050
+ },
1051
+
1052
+ // TODO: this function shouldn't have suspend probably?
1053
+ suspend: async (_suspendPayload: any): Promise<any> => {},
1054
+ bail: () => {},
1055
+ abort: () => {
1056
+ abortController?.abort();
1057
+ },
1058
+ [EMITTER_SYMBOL]: emitter,
1059
+ engine: { step: this.inngestStep },
1060
+ abortSignal: abortController?.signal,
1061
+ writer: new ToolStream(
1062
+ {
1063
+ prefix: 'step',
1064
+ callId: stepCallId,
1065
+ name: 'sleep',
1066
+ runId,
1067
+ },
1068
+ writableStream,
1069
+ ),
1070
+ });
1071
+ });
1072
+
1073
+ // Update sleep span with dynamic duration
1074
+ sleepSpan?.update({
1075
+ attributes: {
1076
+ durationMs: duration,
1077
+ },
1078
+ });
1079
+ }
1080
+
1081
+ try {
1082
+ await this.inngestStep.sleep(entry.id, !duration || duration < 0 ? 0 : duration);
1083
+ sleepSpan?.end();
1084
+ } catch (e) {
1085
+ sleepSpan?.error({ error: e as Error });
1086
+ throw e;
1087
+ }
1088
+ }
1089
+
1090
+ async executeSleepUntil({
1091
+ workflowId,
1092
+ runId,
1093
+ entry,
1094
+ prevOutput,
1095
+ stepResults,
1096
+ emitter,
1097
+ abortController,
1098
+ runtimeContext,
1099
+ writableStream,
1100
+ tracingContext,
1101
+ }: {
1102
+ workflowId: string;
1103
+ runId: string;
1104
+ serializedStepGraph: SerializedStepFlowEntry[];
1105
+ entry: {
1106
+ type: 'sleepUntil';
1107
+ id: string;
1108
+ date?: Date;
1109
+ fn?: ExecuteFunction<any, any, any, any, InngestEngineType>;
1110
+ };
1111
+ prevStep: StepFlowEntry;
512
1112
  prevOutput: any;
513
- emitter: { emit: (event: string, data: any) => Promise<void> };
1113
+ stepResults: Record<string, StepResult<any, any, any, any>>;
1114
+ resume?: {
1115
+ steps: string[];
1116
+ stepResults: Record<string, StepResult<any, any, any, any>>;
1117
+ resumePayload: any;
1118
+ resumePath: number[];
1119
+ };
1120
+ executionContext: ExecutionContext;
1121
+ emitter: Emitter;
1122
+ abortController: AbortController;
514
1123
  runtimeContext: RuntimeContext;
515
- }): Promise<StepResult<any>> {
516
- return super.executeStep({
517
- workflowId,
518
- runId,
519
- step,
520
- stepResults,
521
- executionContext,
522
- resume,
523
- prevOutput,
524
- emitter,
525
- runtimeContext,
1124
+ writableStream?: WritableStream<ChunkType>;
1125
+ tracingContext?: TracingContext;
1126
+ }): Promise<void> {
1127
+ let { date, fn } = entry;
1128
+
1129
+ const sleepUntilSpan = tracingContext?.currentSpan?.createChildSpan({
1130
+ type: AISpanType.WORKFLOW_SLEEP,
1131
+ name: `sleepUntil: ${date ? date.toISOString() : 'dynamic'}`,
1132
+ attributes: {
1133
+ untilDate: date,
1134
+ durationMs: date ? Math.max(0, date.getTime() - Date.now()) : undefined,
1135
+ sleepType: fn ? 'dynamic' : 'fixed',
1136
+ },
1137
+ });
1138
+
1139
+ if (fn) {
1140
+ date = await this.inngestStep.run(`workflow.${workflowId}.sleepUntil.${entry.id}`, async () => {
1141
+ const stepCallId = randomUUID();
1142
+ return await fn({
1143
+ runId,
1144
+ workflowId,
1145
+ mastra: this.mastra!,
1146
+ runtimeContext,
1147
+ inputData: prevOutput,
1148
+ runCount: -1,
1149
+ tracingContext: {
1150
+ currentSpan: sleepUntilSpan,
1151
+ },
1152
+ getInitData: () => stepResults?.input as any,
1153
+ getStepResult: (step: any) => {
1154
+ if (!step?.id) {
1155
+ return null;
1156
+ }
1157
+
1158
+ const result = stepResults[step.id];
1159
+ if (result?.status === 'success') {
1160
+ return result.output;
1161
+ }
1162
+
1163
+ return null;
1164
+ },
1165
+
1166
+ // TODO: this function shouldn't have suspend probably?
1167
+ suspend: async (_suspendPayload: any): Promise<any> => {},
1168
+ bail: () => {},
1169
+ abort: () => {
1170
+ abortController?.abort();
1171
+ },
1172
+ [EMITTER_SYMBOL]: emitter,
1173
+ engine: { step: this.inngestStep },
1174
+ abortSignal: abortController?.signal,
1175
+ writer: new ToolStream(
1176
+ {
1177
+ prefix: 'step',
1178
+ callId: stepCallId,
1179
+ name: 'sleep',
1180
+ runId,
1181
+ },
1182
+ writableStream,
1183
+ ),
1184
+ });
1185
+ });
1186
+
1187
+ // Update sleep until span with dynamic duration
1188
+ const time = !date ? 0 : date.getTime() - Date.now();
1189
+ sleepUntilSpan?.update({
1190
+ attributes: {
1191
+ durationMs: Math.max(0, time),
1192
+ },
1193
+ });
1194
+ }
1195
+
1196
+ if (!(date instanceof Date)) {
1197
+ sleepUntilSpan?.end();
1198
+ return;
1199
+ }
1200
+
1201
+ try {
1202
+ await this.inngestStep.sleepUntil(entry.id, date);
1203
+ sleepUntilSpan?.end();
1204
+ } catch (e) {
1205
+ sleepUntilSpan?.error({ error: e as Error });
1206
+ throw e;
1207
+ }
1208
+ }
1209
+
1210
+ async executeWaitForEvent({ event, timeout }: { event: string; timeout?: number }): Promise<any> {
1211
+ const eventData = await this.inngestStep.waitForEvent(`user-event-${event}`, {
1212
+ event: `user-event-${event}`,
1213
+ timeout: timeout ?? 5e3,
526
1214
  });
1215
+
1216
+ if (eventData === null) {
1217
+ throw 'Timeout waiting for event';
1218
+ }
1219
+
1220
+ return eventData?.data;
527
1221
  }
528
1222
 
529
1223
  async executeStep({
@@ -533,29 +1227,41 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
533
1227
  resume,
534
1228
  prevOutput,
535
1229
  emitter,
1230
+ abortController,
536
1231
  runtimeContext,
1232
+ writableStream,
1233
+ disableScorers,
1234
+ tracingContext,
537
1235
  }: {
538
1236
  step: Step<string, any, any>;
539
- stepResults: Record<string, StepResult<any>>;
540
- executionContext: {
541
- workflowId: string;
542
- runId: string;
543
- executionPath: number[];
544
- suspendedPaths: Record<string, number[]>;
545
- retryConfig: { attempts: number; delay: number };
546
- };
1237
+ stepResults: Record<string, StepResult<any, any, any, any>>;
1238
+ executionContext: ExecutionContext;
547
1239
  resume?: {
548
1240
  steps: string[];
549
1241
  resumePayload: any;
550
1242
  runId?: string;
551
1243
  };
552
1244
  prevOutput: any;
553
- emitter: { emit: (event: string, data: any) => Promise<void> };
1245
+ emitter: Emitter;
1246
+ abortController: AbortController;
554
1247
  runtimeContext: RuntimeContext;
555
- }): Promise<StepResult<any>> {
556
- await this.inngestStep.run(
1248
+ writableStream?: WritableStream<ChunkType>;
1249
+ disableScorers?: boolean;
1250
+ tracingContext?: TracingContext;
1251
+ }): Promise<StepResult<any, any, any, any>> {
1252
+ const stepAISpan = tracingContext?.currentSpan?.createChildSpan({
1253
+ name: `workflow step: '${step.id}'`,
1254
+ type: AISpanType.WORKFLOW_STEP,
1255
+ input: prevOutput,
1256
+ attributes: {
1257
+ stepId: step.id,
1258
+ },
1259
+ });
1260
+
1261
+ const startedAt = await this.inngestStep.run(
557
1262
  `workflow.${executionContext.workflowId}.run.${executionContext.runId}.step.${step.id}.running_ev`,
558
1263
  async () => {
1264
+ const startedAt = Date.now();
559
1265
  await emitter.emit('watch', {
560
1266
  type: 'watch',
561
1267
  payload: {
@@ -577,6 +1283,18 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
577
1283
  },
578
1284
  eventTimestamp: Date.now(),
579
1285
  });
1286
+
1287
+ await emitter.emit('watch-v2', {
1288
+ type: 'step-start',
1289
+ payload: {
1290
+ id: step.id,
1291
+ status: 'running',
1292
+ payload: prevOutput,
1293
+ startedAt,
1294
+ },
1295
+ });
1296
+
1297
+ return startedAt;
580
1298
  },
581
1299
  );
582
1300
 
@@ -643,10 +1361,20 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
643
1361
  eventTimestamp: Date.now(),
644
1362
  });
645
1363
 
1364
+ await emitter.emit('watch-v2', {
1365
+ type: 'step-result',
1366
+ payload: {
1367
+ id: step.id,
1368
+ status: 'failed',
1369
+ error: result?.error,
1370
+ payload: prevOutput,
1371
+ },
1372
+ });
1373
+
646
1374
  return { executionContext, result: { status: 'failed', error: result?.error } };
647
1375
  } else if (result.status === 'suspended') {
648
1376
  const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
649
- const stepRes: StepResult<any> = stepResult as StepResult<any>;
1377
+ const stepRes: StepResult<any, any, any, any> = stepResult as StepResult<any, any, any, any>;
650
1378
  return stepRes?.status === 'suspended';
651
1379
  });
652
1380
 
@@ -673,6 +1401,14 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
673
1401
  eventTimestamp: Date.now(),
674
1402
  });
675
1403
 
1404
+ await emitter.emit('watch-v2', {
1405
+ type: 'step-suspended',
1406
+ payload: {
1407
+ id: step.id,
1408
+ status: 'suspended',
1409
+ },
1410
+ });
1411
+
676
1412
  return {
677
1413
  executionContext,
678
1414
  result: {
@@ -729,23 +1465,58 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
729
1465
  eventTimestamp: Date.now(),
730
1466
  });
731
1467
 
1468
+ await emitter.emit('watch-v2', {
1469
+ type: 'step-result',
1470
+ payload: {
1471
+ id: step.id,
1472
+ status: 'success',
1473
+ output: result?.result,
1474
+ },
1475
+ });
1476
+
1477
+ await emitter.emit('watch-v2', {
1478
+ type: 'step-finish',
1479
+ payload: {
1480
+ id: step.id,
1481
+ metadata: {},
1482
+ },
1483
+ });
1484
+
732
1485
  return { executionContext, result: { status: 'success', output: result?.result } };
733
1486
  },
734
1487
  );
735
1488
 
736
1489
  Object.assign(executionContext, res.executionContext);
737
- return res.result as StepResult<any>;
1490
+ return res.result as StepResult<any, any, any, any>;
738
1491
  }
739
1492
 
740
1493
  const stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
741
- let execResults: any;
1494
+ let execResults: {
1495
+ status: 'success' | 'failed' | 'suspended' | 'bailed';
1496
+ output?: any;
1497
+ startedAt: number;
1498
+ endedAt?: number;
1499
+ payload: any;
1500
+ error?: string;
1501
+ resumedAt?: number;
1502
+ resumePayload?: any;
1503
+ suspendedPayload?: any;
1504
+ suspendedAt?: number;
1505
+ };
742
1506
  let suspended: { payload: any } | undefined;
1507
+ let bailed: { payload: any } | undefined;
1508
+
743
1509
  try {
744
1510
  const result = await step.execute({
1511
+ runId: executionContext.runId,
745
1512
  mastra: this.mastra!,
746
1513
  runtimeContext,
1514
+ writableStream,
747
1515
  inputData: prevOutput,
748
1516
  resumeData: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1517
+ tracingContext: {
1518
+ currentSpan: stepAISpan,
1519
+ },
749
1520
  getInitData: () => stepResults?.input as any,
750
1521
  getStepResult: (step: any) => {
751
1522
  const result = stepResults[step.id];
@@ -759,27 +1530,63 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
759
1530
  executionContext.suspendedPaths[step.id] = executionContext.executionPath;
760
1531
  suspended = { payload: suspendPayload };
761
1532
  },
1533
+ bail: (result: any) => {
1534
+ bailed = { payload: result };
1535
+ },
762
1536
  resume: {
763
1537
  steps: resume?.steps?.slice(1) || [],
764
1538
  resumePayload: resume?.resumePayload,
765
1539
  // @ts-ignore
766
1540
  runId: stepResults[step.id]?.payload?.__workflow_meta?.runId,
767
1541
  },
768
- emitter,
1542
+ [EMITTER_SYMBOL]: emitter,
1543
+ engine: {
1544
+ step: this.inngestStep,
1545
+ },
1546
+ abortSignal: abortController.signal,
769
1547
  });
770
-
771
- execResults = { status: 'success', output: result };
1548
+ const endedAt = Date.now();
1549
+
1550
+ execResults = {
1551
+ status: 'success',
1552
+ output: result,
1553
+ startedAt,
1554
+ endedAt,
1555
+ payload: prevOutput,
1556
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1557
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1558
+ };
772
1559
  } catch (e) {
773
- execResults = { status: 'failed', error: e instanceof Error ? e.message : String(e) };
1560
+ execResults = {
1561
+ status: 'failed',
1562
+ payload: prevOutput,
1563
+ error: e instanceof Error ? e.message : String(e),
1564
+ endedAt: Date.now(),
1565
+ startedAt,
1566
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1567
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1568
+ };
774
1569
  }
775
1570
 
776
1571
  if (suspended) {
777
- execResults = { status: 'suspended', payload: suspended.payload };
1572
+ execResults = {
1573
+ status: 'suspended',
1574
+ suspendedPayload: suspended.payload,
1575
+ payload: prevOutput,
1576
+ suspendedAt: Date.now(),
1577
+ startedAt,
1578
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1579
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1580
+ };
1581
+ } else if (bailed) {
1582
+ execResults = { status: 'bailed', output: bailed.payload, payload: prevOutput, endedAt: Date.now(), startedAt };
778
1583
  }
779
1584
 
780
1585
  if (execResults.status === 'failed') {
781
1586
  if (executionContext.retryConfig.attempts > 0 && this.inngestAttempts < executionContext.retryConfig.attempts) {
782
- throw execResults.error;
1587
+ const error = new Error(execResults.error);
1588
+ stepAISpan?.error({ error });
1589
+ throw error;
783
1590
  }
784
1591
  }
785
1592
 
@@ -788,12 +1595,11 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
788
1595
  payload: {
789
1596
  currentStep: {
790
1597
  id: step.id,
791
- status: execResults.status,
792
- output: execResults.output,
1598
+ ...execResults,
793
1599
  },
794
1600
  workflowState: {
795
1601
  status: 'running',
796
- steps: stepResults,
1602
+ steps: { ...stepResults, [step.id]: execResults },
797
1603
  result: null,
798
1604
  error: null,
799
1605
  },
@@ -801,9 +1607,54 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
801
1607
  eventTimestamp: Date.now(),
802
1608
  });
803
1609
 
1610
+ if (execResults.status === 'suspended') {
1611
+ await emitter.emit('watch-v2', {
1612
+ type: 'step-suspended',
1613
+ payload: {
1614
+ id: step.id,
1615
+ ...execResults,
1616
+ },
1617
+ });
1618
+ } else {
1619
+ await emitter.emit('watch-v2', {
1620
+ type: 'step-result',
1621
+ payload: {
1622
+ id: step.id,
1623
+ ...execResults,
1624
+ },
1625
+ });
1626
+
1627
+ await emitter.emit('watch-v2', {
1628
+ type: 'step-finish',
1629
+ payload: {
1630
+ id: step.id,
1631
+ metadata: {},
1632
+ },
1633
+ });
1634
+ }
1635
+
1636
+ stepAISpan?.end({ output: execResults });
1637
+
804
1638
  return { result: execResults, executionContext, stepResults };
805
1639
  });
806
1640
 
1641
+ if (disableScorers !== false) {
1642
+ await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}.score`, async () => {
1643
+ if (step.scorers) {
1644
+ await this.runScorers({
1645
+ scorers: step.scorers,
1646
+ runId: executionContext.runId,
1647
+ input: prevOutput,
1648
+ output: stepRes.result,
1649
+ workflowId: executionContext.workflowId,
1650
+ stepId: step.id,
1651
+ runtimeContext,
1652
+ disableScorers,
1653
+ });
1654
+ }
1655
+ });
1656
+ }
1657
+
807
1658
  // @ts-ignore
808
1659
  Object.assign(executionContext.suspendedPaths, stepRes.executionContext.suspendedPaths);
809
1660
  // @ts-ignore
@@ -818,11 +1669,20 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
818
1669
  runId,
819
1670
  stepResults,
820
1671
  executionContext,
1672
+ serializedStepGraph,
1673
+ workflowStatus,
1674
+ result,
1675
+ error,
821
1676
  }: {
822
1677
  workflowId: string;
823
1678
  runId: string;
824
- stepResults: Record<string, StepResult<any>>;
1679
+ stepResults: Record<string, StepResult<any, any, any, any>>;
1680
+ serializedStepGraph: SerializedStepFlowEntry[];
825
1681
  executionContext: ExecutionContext;
1682
+ workflowStatus: 'success' | 'failed' | 'suspended' | 'running';
1683
+ result?: Record<string, any>;
1684
+ error?: string | Error;
1685
+ runtimeContext: RuntimeContext;
826
1686
  }) {
827
1687
  await this.inngestStep.run(
828
1688
  `workflow.${workflowId}.run.${runId}.path.${JSON.stringify(executionContext.executionPath)}.stepUpdate`,
@@ -836,6 +1696,11 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
836
1696
  context: stepResults as any,
837
1697
  activePaths: [],
838
1698
  suspendedPaths: executionContext.suspendedPaths,
1699
+ waitingPaths: {},
1700
+ serializedStepGraph,
1701
+ status: workflowStatus,
1702
+ result,
1703
+ error,
839
1704
  // @ts-ignore
840
1705
  timestamp: Date.now(),
841
1706
  },
@@ -851,37 +1716,75 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
851
1716
  prevOutput,
852
1717
  prevStep,
853
1718
  stepResults,
1719
+ serializedStepGraph,
854
1720
  resume,
855
1721
  executionContext,
856
1722
  emitter,
1723
+ abortController,
857
1724
  runtimeContext,
1725
+ writableStream,
1726
+ disableScorers,
1727
+ tracingContext,
858
1728
  }: {
859
1729
  workflowId: string;
860
1730
  runId: string;
861
- entry: { type: 'conditional'; steps: StepFlowEntry[]; conditions: ExecuteFunction<any, any, any, any>[] };
1731
+ entry: {
1732
+ type: 'conditional';
1733
+ steps: StepFlowEntry[];
1734
+ conditions: ExecuteFunction<any, any, any, any, InngestEngineType>[];
1735
+ };
862
1736
  prevStep: StepFlowEntry;
1737
+ serializedStepGraph: SerializedStepFlowEntry[];
863
1738
  prevOutput: any;
864
- stepResults: Record<string, StepResult<any>>;
1739
+ stepResults: Record<string, StepResult<any, any, any, any>>;
865
1740
  resume?: {
866
1741
  steps: string[];
867
- stepResults: Record<string, StepResult<any>>;
1742
+ stepResults: Record<string, StepResult<any, any, any, any>>;
868
1743
  resumePayload: any;
869
1744
  resumePath: number[];
870
1745
  };
871
1746
  executionContext: ExecutionContext;
872
- emitter: { emit: (event: string, data: any) => Promise<void> };
1747
+ emitter: Emitter;
1748
+ abortController: AbortController;
873
1749
  runtimeContext: RuntimeContext;
874
- }): Promise<StepResult<any>> {
1750
+ writableStream?: WritableStream<ChunkType>;
1751
+ disableScorers?: boolean;
1752
+ tracingContext?: TracingContext;
1753
+ }): Promise<StepResult<any, any, any, any>> {
1754
+ const conditionalSpan = tracingContext?.currentSpan?.createChildSpan({
1755
+ type: AISpanType.WORKFLOW_CONDITIONAL,
1756
+ name: `conditional: ${entry.conditions.length} conditions`,
1757
+ input: prevOutput,
1758
+ attributes: {
1759
+ conditionCount: entry.conditions.length,
1760
+ },
1761
+ });
1762
+
875
1763
  let execResults: any;
876
1764
  const truthyIndexes = (
877
1765
  await Promise.all(
878
1766
  entry.conditions.map((cond, index) =>
879
1767
  this.inngestStep.run(`workflow.${workflowId}.conditional.${index}`, async () => {
1768
+ const evalSpan = conditionalSpan?.createChildSpan({
1769
+ type: AISpanType.WORKFLOW_CONDITIONAL_EVAL,
1770
+ name: `condition ${index}`,
1771
+ input: prevOutput,
1772
+ attributes: {
1773
+ conditionIndex: index,
1774
+ },
1775
+ });
1776
+
880
1777
  try {
881
1778
  const result = await cond({
1779
+ runId,
1780
+ workflowId,
882
1781
  mastra: this.mastra!,
883
1782
  runtimeContext,
1783
+ runCount: -1,
884
1784
  inputData: prevOutput,
1785
+ tracingContext: {
1786
+ currentSpan: evalSpan,
1787
+ },
885
1788
  getInitData: () => stepResults?.input as any,
886
1789
  getStepResult: (step: any) => {
887
1790
  if (!step?.id) {
@@ -898,11 +1801,42 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
898
1801
 
899
1802
  // TODO: this function shouldn't have suspend probably?
900
1803
  suspend: async (_suspendPayload: any) => {},
901
- emitter,
1804
+ bail: () => {},
1805
+ abort: () => {
1806
+ abortController.abort();
1807
+ },
1808
+ [EMITTER_SYMBOL]: emitter,
1809
+ engine: {
1810
+ step: this.inngestStep,
1811
+ },
1812
+ abortSignal: abortController.signal,
1813
+ writer: new ToolStream(
1814
+ {
1815
+ prefix: 'step',
1816
+ callId: randomUUID(),
1817
+ name: 'conditional',
1818
+ runId,
1819
+ },
1820
+ writableStream,
1821
+ ),
902
1822
  });
1823
+
1824
+ evalSpan?.end({
1825
+ output: result,
1826
+ attributes: {
1827
+ result: !!result,
1828
+ },
1829
+ });
1830
+
903
1831
  return result ? index : null;
904
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
905
1832
  } catch (e: unknown) {
1833
+ evalSpan?.error({
1834
+ error: e instanceof Error ? e : new Error(String(e)),
1835
+ attributes: {
1836
+ result: false,
1837
+ },
1838
+ });
1839
+
906
1840
  return null;
907
1841
  }
908
1842
  }),
@@ -911,12 +1845,22 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
911
1845
  ).filter((index: any): index is number => index !== null);
912
1846
 
913
1847
  const stepsToRun = entry.steps.filter((_, index) => truthyIndexes.includes(index));
914
- const results: StepResult<any>[] = await Promise.all(
1848
+
1849
+ // Update conditional span with evaluation results
1850
+ conditionalSpan?.update({
1851
+ attributes: {
1852
+ truthyIndexes,
1853
+ selectedSteps: stepsToRun.map(s => (s.type === 'step' ? s.step.id : `control-${s.type}`)),
1854
+ },
1855
+ });
1856
+
1857
+ const results: { result: StepResult<any, any, any, any> }[] = await Promise.all(
915
1858
  stepsToRun.map((step, index) =>
916
1859
  this.executeEntry({
917
1860
  workflowId,
918
1861
  runId,
919
1862
  entry: step,
1863
+ serializedStepGraph,
920
1864
  prevStep,
921
1865
  stepResults,
922
1866
  resume,
@@ -929,21 +1873,29 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
929
1873
  executionSpan: executionContext.executionSpan,
930
1874
  },
931
1875
  emitter,
1876
+ abortController,
932
1877
  runtimeContext,
1878
+ writableStream,
1879
+ disableScorers,
1880
+ tracingContext: {
1881
+ currentSpan: conditionalSpan,
1882
+ },
933
1883
  }),
934
1884
  ),
935
1885
  );
936
- const hasFailed = results.find(result => result.status === 'failed');
937
- const hasSuspended = results.find(result => result.status === 'suspended');
1886
+ const hasFailed = results.find(result => result.result.status === 'failed') as {
1887
+ result: StepFailure<any, any, any>;
1888
+ };
1889
+ const hasSuspended = results.find(result => result.result.status === 'suspended');
938
1890
  if (hasFailed) {
939
- execResults = { status: 'failed', error: hasFailed.error };
1891
+ execResults = { status: 'failed', error: hasFailed.result.error };
940
1892
  } else if (hasSuspended) {
941
- execResults = { status: 'suspended', payload: hasSuspended.payload };
1893
+ execResults = { status: 'suspended', payload: hasSuspended.result.suspendPayload };
942
1894
  } else {
943
1895
  execResults = {
944
1896
  status: 'success',
945
1897
  output: results.reduce((acc: Record<string, any>, result, index) => {
946
- if (result.status === 'success') {
1898
+ if (result.result.status === 'success') {
947
1899
  // @ts-ignore
948
1900
  acc[stepsToRun[index]!.step.id] = result.output;
949
1901
  }
@@ -953,6 +1905,16 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
953
1905
  };
954
1906
  }
955
1907
 
1908
+ if (execResults.status === 'failed') {
1909
+ conditionalSpan?.error({
1910
+ error: new Error(execResults.error),
1911
+ });
1912
+ } else {
1913
+ conditionalSpan?.end({
1914
+ output: execResults.output || execResults,
1915
+ });
1916
+ }
1917
+
956
1918
  return execResults;
957
1919
  }
958
1920
  }