@mastra/inngest 0.12.3-alpha.1 → 0.12.3-alpha.3

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