@mastra/inngest 0.10.5 → 0.10.6-alpha.1

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,8 +1,8 @@
1
1
  import { randomUUID } from 'crypto';
2
2
  import { subscribe } from '@inngest/realtime';
3
- import { Agent, Tool } from '@mastra/core';
4
- import type { Mastra, ToolExecutionContext, WorkflowRun, WorkflowRuns } from '@mastra/core';
3
+ import type { Agent, Mastra, ToolExecutionContext, WorkflowRun, WorkflowRuns } from '@mastra/core';
5
4
  import { RuntimeContext } from '@mastra/core/di';
5
+ import { Tool } from '@mastra/core/tools';
6
6
  import { Workflow, Run, DefaultExecutionEngine } from '@mastra/core/workflows';
7
7
  import type {
8
8
  ExecuteFunction,
@@ -16,7 +16,9 @@ import type {
16
16
  WorkflowResult,
17
17
  SerializedStepFlowEntry,
18
18
  StepFailure,
19
+ Emitter,
19
20
  WatchEvent,
21
+ StreamEvent,
20
22
  } from '@mastra/core/workflows';
21
23
  import { EMITTER_SYMBOL } from '@mastra/core/workflows/_constants';
22
24
  import type { Span } from '@opentelemetry/api';
@@ -87,7 +89,8 @@ export class InngestRun<
87
89
 
88
90
  async getRunOutput(eventId: string) {
89
91
  let runs = await this.getRuns(eventId);
90
- while (runs?.[0]?.status !== 'Completed') {
92
+
93
+ while (runs?.[0]?.status !== 'Completed' || runs?.[0]?.event_id !== eventId) {
91
94
  await new Promise(resolve => setTimeout(resolve, 1000));
92
95
  runs = await this.getRuns(eventId);
93
96
  if (runs?.[0]?.status === 'Failed' || runs?.[0]?.status === 'Cancelled') {
@@ -97,6 +100,13 @@ export class InngestRun<
97
100
  return runs?.[0];
98
101
  }
99
102
 
103
+ async sendEvent(event: string, data: any) {
104
+ await this.inngest.send({
105
+ name: `user-event-${event}`,
106
+ data,
107
+ });
108
+ }
109
+
100
110
  async start({
101
111
  inputData,
102
112
  }: {
@@ -148,6 +158,27 @@ export class InngestRun<
148
158
  | string
149
159
  | string[];
150
160
  runtimeContext?: RuntimeContext;
161
+ }): Promise<WorkflowResult<TOutput, TSteps>> {
162
+ const p = this._resume(params).then(result => {
163
+ if (result.status !== 'suspended') {
164
+ this.closeStreamAction?.().catch(() => {});
165
+ }
166
+
167
+ return result;
168
+ });
169
+
170
+ this.executionResults = p;
171
+ return p;
172
+ }
173
+
174
+ async _resume<TResumeSchema extends z.ZodType<any>>(params: {
175
+ resumeData?: z.infer<TResumeSchema>;
176
+ step:
177
+ | Step<string, any, any, TResumeSchema, any>
178
+ | [...Step<string, any, any, any, any>[], Step<string, any, any, TResumeSchema, any>]
179
+ | string
180
+ | string[];
181
+ runtimeContext?: RuntimeContext;
151
182
  }): Promise<WorkflowResult<TOutput, TSteps>> {
152
183
  const steps: string[] = (Array.isArray(params.step) ? params.step : [params.step]).map(step =>
153
184
  typeof step === 'string' ? step : step?.id,
@@ -185,28 +216,72 @@ export class InngestRun<
185
216
  return result;
186
217
  }
187
218
 
188
- watch(cb: (event: any) => void): () => void {
219
+ watch(cb: (event: WatchEvent) => void, type: 'watch' | 'watch-v2' = 'watch'): () => void {
220
+ let active = true;
189
221
  const streamPromise = subscribe(
190
222
  {
191
223
  channel: `workflow:${this.workflowId}:${this.runId}`,
192
- topics: ['watch'],
224
+ topics: [type],
193
225
  app: this.inngest,
194
226
  },
195
227
  (message: any) => {
196
- cb(message.data);
228
+ if (active) {
229
+ cb(message.data);
230
+ }
197
231
  },
198
232
  );
199
233
 
200
234
  return () => {
235
+ active = false;
201
236
  streamPromise
202
- .then((stream: any) => {
203
- stream.cancel();
237
+ .then(async (stream: Awaited<typeof streamPromise>) => {
238
+ return stream.cancel();
204
239
  })
205
240
  .catch(err => {
206
241
  console.error(err);
207
242
  });
208
243
  };
209
244
  }
245
+
246
+ stream({ inputData, runtimeContext }: { inputData?: z.infer<TInput>; runtimeContext?: RuntimeContext } = {}): {
247
+ stream: ReadableStream<StreamEvent>;
248
+ getWorkflowState: () => Promise<WorkflowResult<TOutput, TSteps>>;
249
+ } {
250
+ const { readable, writable } = new TransformStream<StreamEvent, StreamEvent>();
251
+
252
+ const writer = writable.getWriter();
253
+ const unwatch = this.watch(async event => {
254
+ try {
255
+ // watch-v2 events are data stream events, so we need to cast them to the correct type
256
+ await writer.write(event as any);
257
+ } catch {}
258
+ }, 'watch-v2');
259
+
260
+ this.closeStreamAction = async () => {
261
+ unwatch();
262
+
263
+ try {
264
+ await writer.close();
265
+ } catch (err) {
266
+ console.error('Error closing stream:', err);
267
+ } finally {
268
+ writer.releaseLock();
269
+ }
270
+ };
271
+
272
+ this.executionResults = this.start({ inputData, runtimeContext }).then(result => {
273
+ if (result.status !== 'suspended') {
274
+ this.closeStreamAction?.().catch(() => {});
275
+ }
276
+
277
+ return result;
278
+ });
279
+
280
+ return {
281
+ stream: readable,
282
+ getWorkflowState: () => this.executionResults!,
283
+ };
284
+ }
210
285
  }
211
286
 
212
287
  export class InngestWorkflow<
@@ -360,13 +435,22 @@ export class InngestWorkflow<
360
435
  try {
361
436
  await publish({
362
437
  channel: `workflow:${this.id}:${runId}`,
363
- topic: 'watch',
438
+ topic: event,
364
439
  data,
365
440
  });
366
441
  } catch (err: any) {
367
442
  this.logger.error('Error emitting event: ' + (err?.stack ?? err?.message ?? err));
368
443
  }
369
444
  },
445
+ on: (_event: string, _callback: (data: any) => void) => {
446
+ // no-op
447
+ },
448
+ off: (_event: string, _callback: (data: any) => void) => {
449
+ // no-op
450
+ },
451
+ once: (_event: string, _callback: (data: any) => void) => {
452
+ // no-op
453
+ },
370
454
  };
371
455
 
372
456
  const engine = new InngestExecutionEngine(this.#mastra, step, attempt);
@@ -408,6 +492,14 @@ export class InngestWorkflow<
408
492
  }
409
493
  }
410
494
 
495
+ function isAgent(params: any): params is Agent<any, any, any> {
496
+ return params?.component === 'AGENT';
497
+ }
498
+
499
+ function isTool(params: any): params is Tool<any, any, any> {
500
+ return params instanceof Tool;
501
+ }
502
+
411
503
  export function createStep<
412
504
  TStepId extends string,
413
505
  TStepInput extends z.ZodType<any>,
@@ -451,7 +543,6 @@ export function createStep<
451
543
  execute: (context: TContext) => Promise<any>;
452
544
  },
453
545
  ): Step<string, TSchemaIn, TSchemaOut, z.ZodType<any>, z.ZodType<any>, InngestEngineType>;
454
-
455
546
  export function createStep<
456
547
  TStepId extends string,
457
548
  TStepInput extends z.ZodType<any>,
@@ -482,7 +573,7 @@ export function createStep<
482
573
  execute: (context: ToolExecutionContext<TStepInput>) => Promise<any>;
483
574
  }),
484
575
  ): Step<TStepId, TStepInput, TStepOutput, TResumeSchema, TSuspendSchema, InngestEngineType> {
485
- if (params instanceof Agent) {
576
+ if (isAgent(params)) {
486
577
  return {
487
578
  id: params.name,
488
579
  // @ts-ignore
@@ -557,7 +648,7 @@ export function createStep<
557
648
  };
558
649
  }
559
650
 
560
- if (params instanceof Tool) {
651
+ if (isTool(params)) {
561
652
  if (!params.inputSchema || !params.outputSchema) {
562
653
  throw new Error('Tool must have input and output schemas defined');
563
654
  }
@@ -631,11 +722,12 @@ export function init(inngest: Inngest) {
631
722
  any,
632
723
  InngestEngineType
633
724
  >[],
725
+ TPrevSchema extends z.ZodType<any> = TInput,
634
726
  >(
635
- workflow: Workflow<InngestEngineType, TSteps, string, TInput, TOutput, TInput>,
727
+ workflow: Workflow<InngestEngineType, TSteps, string, TInput, TOutput, TPrevSchema>,
636
728
  opts: { id: TWorkflowId },
637
- ): Workflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TInput> {
638
- const wf = new Workflow({
729
+ ): Workflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> {
730
+ const wf: Workflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> = new Workflow({
639
731
  id: opts.id,
640
732
  inputSchema: workflow.inputSchema,
641
733
  outputSchema: workflow.outputSchema,
@@ -660,9 +752,44 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
660
752
  this.inngestAttempts = inngestAttempts;
661
753
  }
662
754
 
755
+ async execute<TInput, TOutput>(params: {
756
+ workflowId: string;
757
+ runId: string;
758
+ graph: ExecutionGraph;
759
+ serializedStepGraph: SerializedStepFlowEntry[];
760
+ input?: TInput;
761
+ resume?: {
762
+ // TODO: add execute path
763
+ steps: string[];
764
+ stepResults: Record<string, StepResult<any, any, any, any>>;
765
+ resumePayload: any;
766
+ resumePath: number[];
767
+ };
768
+ emitter: Emitter;
769
+ retryConfig?: {
770
+ attempts?: number;
771
+ delay?: number;
772
+ };
773
+ runtimeContext: RuntimeContext;
774
+ }): Promise<TOutput> {
775
+ await params.emitter.emit('watch-v2', {
776
+ type: 'start',
777
+ payload: { runId: params.runId },
778
+ });
779
+
780
+ const result = await super.execute<TInput, TOutput>(params);
781
+
782
+ await params.emitter.emit('watch-v2', {
783
+ type: 'finish',
784
+ payload: { runId: params.runId },
785
+ });
786
+
787
+ return result;
788
+ }
789
+
663
790
  protected async fmtReturnValue<TOutput>(
664
791
  executionSpan: Span | undefined,
665
- emitter: { emit: (event: string, data: any) => Promise<void> },
792
+ emitter: Emitter,
666
793
  stepResults: Record<string, StepResult<any, any, any, any>>,
667
794
  lastOutput: StepResult<any, any, any, any>,
668
795
  error?: Error | string,
@@ -755,7 +882,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
755
882
  resumePayload: any;
756
883
  };
757
884
  prevOutput: any;
758
- emitter: { emit: (event: string, data: any) => Promise<void> };
885
+ emitter: Emitter;
759
886
  runtimeContext: RuntimeContext;
760
887
  }): Promise<StepResult<any, any, any, any>> {
761
888
  return super.executeStep({
@@ -775,6 +902,19 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
775
902
  await this.inngestStep.sleep(id, duration);
776
903
  }
777
904
 
905
+ async executeWaitForEvent({ event, timeout }: { event: string; timeout?: number }): Promise<any> {
906
+ const eventData = await this.inngestStep.waitForEvent(`user-event-${event}`, {
907
+ event: `user-event-${event}`,
908
+ timeout: timeout ?? 5e3,
909
+ });
910
+
911
+ if (eventData === null) {
912
+ throw 'Timeout waiting for event';
913
+ }
914
+
915
+ return eventData?.data;
916
+ }
917
+
778
918
  async executeStep({
779
919
  step,
780
920
  stepResults,
@@ -799,12 +939,13 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
799
939
  runId?: string;
800
940
  };
801
941
  prevOutput: any;
802
- emitter: { emit: (event: string, data: any) => Promise<void> };
942
+ emitter: Emitter;
803
943
  runtimeContext: RuntimeContext;
804
944
  }): Promise<StepResult<any, any, any, any>> {
805
- await this.inngestStep.run(
945
+ const startedAt = await this.inngestStep.run(
806
946
  `workflow.${executionContext.workflowId}.run.${executionContext.runId}.step.${step.id}.running_ev`,
807
947
  async () => {
948
+ const startedAt = Date.now();
808
949
  await emitter.emit('watch', {
809
950
  type: 'watch',
810
951
  payload: {
@@ -826,6 +967,15 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
826
967
  },
827
968
  eventTimestamp: Date.now(),
828
969
  });
970
+
971
+ await emitter.emit('watch-v2', {
972
+ type: 'step-start',
973
+ payload: {
974
+ id: step.id,
975
+ },
976
+ });
977
+
978
+ return startedAt;
829
979
  },
830
980
  );
831
981
 
@@ -892,6 +1042,14 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
892
1042
  eventTimestamp: Date.now(),
893
1043
  });
894
1044
 
1045
+ await emitter.emit('watch-v2', {
1046
+ type: 'step-result',
1047
+ payload: {
1048
+ id: step.id,
1049
+ status: 'failed',
1050
+ },
1051
+ });
1052
+
895
1053
  return { executionContext, result: { status: 'failed', error: result?.error } };
896
1054
  } else if (result.status === 'suspended') {
897
1055
  const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
@@ -922,6 +1080,13 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
922
1080
  eventTimestamp: Date.now(),
923
1081
  });
924
1082
 
1083
+ await emitter.emit('watch-v2', {
1084
+ type: 'step-suspended',
1085
+ payload: {
1086
+ id: step.id,
1087
+ },
1088
+ });
1089
+
925
1090
  return {
926
1091
  executionContext,
927
1092
  result: {
@@ -978,6 +1143,14 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
978
1143
  eventTimestamp: Date.now(),
979
1144
  });
980
1145
 
1146
+ await emitter.emit('watch-v2', {
1147
+ type: 'step-finish',
1148
+ payload: {
1149
+ id: step.id,
1150
+ metadata: {},
1151
+ },
1152
+ });
1153
+
981
1154
  return { executionContext, result: { status: 'success', output: result?.result } };
982
1155
  },
983
1156
  );
@@ -989,6 +1162,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
989
1162
  const stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
990
1163
  let execResults: any;
991
1164
  let suspended: { payload: any } | undefined;
1165
+
992
1166
  try {
993
1167
  const result = await step.execute({
994
1168
  runId: executionContext.runId,
@@ -1020,14 +1194,39 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1020
1194
  step: this.inngestStep,
1021
1195
  },
1022
1196
  });
1023
-
1024
- execResults = { status: 'success', output: result };
1197
+ const endedAt = Date.now();
1198
+
1199
+ execResults = {
1200
+ status: 'success',
1201
+ output: result,
1202
+ startedAt,
1203
+ endedAt,
1204
+ payload: prevOutput,
1205
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1206
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1207
+ };
1025
1208
  } catch (e) {
1026
- execResults = { status: 'failed', error: e instanceof Error ? e.message : String(e) };
1209
+ execResults = {
1210
+ status: 'failed',
1211
+ payload: prevOutput,
1212
+ error: e instanceof Error ? e.message : String(e),
1213
+ endedAt: Date.now(),
1214
+ startedAt,
1215
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1216
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1217
+ };
1027
1218
  }
1028
1219
 
1029
1220
  if (suspended) {
1030
- execResults = { status: 'suspended', payload: suspended.payload };
1221
+ execResults = {
1222
+ status: 'suspended',
1223
+ suspendedPayload: suspended.payload,
1224
+ payload: prevOutput,
1225
+ suspendedAt: Date.now(),
1226
+ startedAt,
1227
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1228
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1229
+ };
1031
1230
  }
1032
1231
 
1033
1232
  if (execResults.status === 'failed') {
@@ -1041,12 +1240,11 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1041
1240
  payload: {
1042
1241
  currentStep: {
1043
1242
  id: step.id,
1044
- status: execResults.status,
1045
- output: execResults.output,
1243
+ ...execResults,
1046
1244
  },
1047
1245
  workflowState: {
1048
1246
  status: 'running',
1049
- steps: stepResults,
1247
+ steps: { ...stepResults, [step.id]: execResults },
1050
1248
  result: null,
1051
1249
  error: null,
1052
1250
  },
@@ -1054,6 +1252,34 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1054
1252
  eventTimestamp: Date.now(),
1055
1253
  });
1056
1254
 
1255
+ if (execResults.status === 'suspended') {
1256
+ await emitter.emit('watch-v2', {
1257
+ type: 'step-suspended',
1258
+ payload: {
1259
+ id: step.id,
1260
+ status: execResults.status,
1261
+ output: execResults.status === 'success' ? execResults?.output : undefined,
1262
+ },
1263
+ });
1264
+ } else {
1265
+ await emitter.emit('watch-v2', {
1266
+ type: 'step-result',
1267
+ payload: {
1268
+ id: step.id,
1269
+ status: execResults.status,
1270
+ output: execResults.status === 'success' ? execResults?.output : undefined,
1271
+ },
1272
+ });
1273
+
1274
+ await emitter.emit('watch-v2', {
1275
+ type: 'step-finish',
1276
+ payload: {
1277
+ id: step.id,
1278
+ metadata: {},
1279
+ },
1280
+ });
1281
+ }
1282
+
1057
1283
  return { result: execResults, executionContext, stepResults };
1058
1284
  });
1059
1285
 
@@ -1140,7 +1366,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1140
1366
  resumePath: number[];
1141
1367
  };
1142
1368
  executionContext: ExecutionContext;
1143
- emitter: { emit: (event: string, data: any) => Promise<void> };
1369
+ emitter: Emitter;
1144
1370
  runtimeContext: RuntimeContext;
1145
1371
  }): Promise<StepResult<any, any, any, any>> {
1146
1372
  let execResults: any;
@@ -1216,7 +1442,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1216
1442
  if (hasFailed) {
1217
1443
  execResults = { status: 'failed', error: hasFailed.result.error };
1218
1444
  } else if (hasSuspended) {
1219
- execResults = { status: 'suspended', payload: hasSuspended.result.payload };
1445
+ execResults = { status: 'suspended', payload: hasSuspended.result.suspendPayload };
1220
1446
  } else {
1221
1447
  execResults = {
1222
1448
  status: 'success',