@mastra/inngest 0.10.5-alpha.1 → 0.10.6-alpha.0

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
  }
@@ -660,9 +751,44 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
660
751
  this.inngestAttempts = inngestAttempts;
661
752
  }
662
753
 
754
+ async execute<TInput, TOutput>(params: {
755
+ workflowId: string;
756
+ runId: string;
757
+ graph: ExecutionGraph;
758
+ serializedStepGraph: SerializedStepFlowEntry[];
759
+ input?: TInput;
760
+ resume?: {
761
+ // TODO: add execute path
762
+ steps: string[];
763
+ stepResults: Record<string, StepResult<any, any, any, any>>;
764
+ resumePayload: any;
765
+ resumePath: number[];
766
+ };
767
+ emitter: Emitter;
768
+ retryConfig?: {
769
+ attempts?: number;
770
+ delay?: number;
771
+ };
772
+ runtimeContext: RuntimeContext;
773
+ }): Promise<TOutput> {
774
+ await params.emitter.emit('watch-v2', {
775
+ type: 'start',
776
+ payload: { runId: params.runId },
777
+ });
778
+
779
+ const result = await super.execute<TInput, TOutput>(params);
780
+
781
+ await params.emitter.emit('watch-v2', {
782
+ type: 'finish',
783
+ payload: { runId: params.runId },
784
+ });
785
+
786
+ return result;
787
+ }
788
+
663
789
  protected async fmtReturnValue<TOutput>(
664
790
  executionSpan: Span | undefined,
665
- emitter: { emit: (event: string, data: any) => Promise<void> },
791
+ emitter: Emitter,
666
792
  stepResults: Record<string, StepResult<any, any, any, any>>,
667
793
  lastOutput: StepResult<any, any, any, any>,
668
794
  error?: Error | string,
@@ -755,7 +881,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
755
881
  resumePayload: any;
756
882
  };
757
883
  prevOutput: any;
758
- emitter: { emit: (event: string, data: any) => Promise<void> };
884
+ emitter: Emitter;
759
885
  runtimeContext: RuntimeContext;
760
886
  }): Promise<StepResult<any, any, any, any>> {
761
887
  return super.executeStep({
@@ -775,6 +901,19 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
775
901
  await this.inngestStep.sleep(id, duration);
776
902
  }
777
903
 
904
+ async executeWaitForEvent({ event, timeout }: { event: string; timeout?: number }): Promise<any> {
905
+ const eventData = await this.inngestStep.waitForEvent(`user-event-${event}`, {
906
+ event: `user-event-${event}`,
907
+ timeout: timeout ?? 5e3,
908
+ });
909
+
910
+ if (eventData === null) {
911
+ throw 'Timeout waiting for event';
912
+ }
913
+
914
+ return eventData?.data;
915
+ }
916
+
778
917
  async executeStep({
779
918
  step,
780
919
  stepResults,
@@ -799,12 +938,13 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
799
938
  runId?: string;
800
939
  };
801
940
  prevOutput: any;
802
- emitter: { emit: (event: string, data: any) => Promise<void> };
941
+ emitter: Emitter;
803
942
  runtimeContext: RuntimeContext;
804
943
  }): Promise<StepResult<any, any, any, any>> {
805
- await this.inngestStep.run(
944
+ const startedAt = await this.inngestStep.run(
806
945
  `workflow.${executionContext.workflowId}.run.${executionContext.runId}.step.${step.id}.running_ev`,
807
946
  async () => {
947
+ const startedAt = Date.now();
808
948
  await emitter.emit('watch', {
809
949
  type: 'watch',
810
950
  payload: {
@@ -826,6 +966,15 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
826
966
  },
827
967
  eventTimestamp: Date.now(),
828
968
  });
969
+
970
+ await emitter.emit('watch-v2', {
971
+ type: 'step-start',
972
+ payload: {
973
+ id: step.id,
974
+ },
975
+ });
976
+
977
+ return startedAt;
829
978
  },
830
979
  );
831
980
 
@@ -892,6 +1041,14 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
892
1041
  eventTimestamp: Date.now(),
893
1042
  });
894
1043
 
1044
+ await emitter.emit('watch-v2', {
1045
+ type: 'step-result',
1046
+ payload: {
1047
+ id: step.id,
1048
+ status: 'failed',
1049
+ },
1050
+ });
1051
+
895
1052
  return { executionContext, result: { status: 'failed', error: result?.error } };
896
1053
  } else if (result.status === 'suspended') {
897
1054
  const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
@@ -922,6 +1079,13 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
922
1079
  eventTimestamp: Date.now(),
923
1080
  });
924
1081
 
1082
+ await emitter.emit('watch-v2', {
1083
+ type: 'step-suspended',
1084
+ payload: {
1085
+ id: step.id,
1086
+ },
1087
+ });
1088
+
925
1089
  return {
926
1090
  executionContext,
927
1091
  result: {
@@ -978,6 +1142,14 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
978
1142
  eventTimestamp: Date.now(),
979
1143
  });
980
1144
 
1145
+ await emitter.emit('watch-v2', {
1146
+ type: 'step-finish',
1147
+ payload: {
1148
+ id: step.id,
1149
+ metadata: {},
1150
+ },
1151
+ });
1152
+
981
1153
  return { executionContext, result: { status: 'success', output: result?.result } };
982
1154
  },
983
1155
  );
@@ -989,6 +1161,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
989
1161
  const stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
990
1162
  let execResults: any;
991
1163
  let suspended: { payload: any } | undefined;
1164
+
992
1165
  try {
993
1166
  const result = await step.execute({
994
1167
  runId: executionContext.runId,
@@ -1020,14 +1193,39 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1020
1193
  step: this.inngestStep,
1021
1194
  },
1022
1195
  });
1023
-
1024
- execResults = { status: 'success', output: result };
1196
+ const endedAt = Date.now();
1197
+
1198
+ execResults = {
1199
+ status: 'success',
1200
+ output: result,
1201
+ startedAt,
1202
+ endedAt,
1203
+ payload: prevOutput,
1204
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1205
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1206
+ };
1025
1207
  } catch (e) {
1026
- execResults = { status: 'failed', error: e instanceof Error ? e.message : String(e) };
1208
+ execResults = {
1209
+ status: 'failed',
1210
+ payload: prevOutput,
1211
+ error: e instanceof Error ? e.message : String(e),
1212
+ endedAt: Date.now(),
1213
+ startedAt,
1214
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1215
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1216
+ };
1027
1217
  }
1028
1218
 
1029
1219
  if (suspended) {
1030
- execResults = { status: 'suspended', payload: suspended.payload };
1220
+ execResults = {
1221
+ status: 'suspended',
1222
+ suspendedPayload: suspended.payload,
1223
+ payload: prevOutput,
1224
+ suspendedAt: Date.now(),
1225
+ startedAt,
1226
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1227
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1228
+ };
1031
1229
  }
1032
1230
 
1033
1231
  if (execResults.status === 'failed') {
@@ -1041,12 +1239,11 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1041
1239
  payload: {
1042
1240
  currentStep: {
1043
1241
  id: step.id,
1044
- status: execResults.status,
1045
- output: execResults.output,
1242
+ ...execResults,
1046
1243
  },
1047
1244
  workflowState: {
1048
1245
  status: 'running',
1049
- steps: stepResults,
1246
+ steps: { ...stepResults, [step.id]: execResults },
1050
1247
  result: null,
1051
1248
  error: null,
1052
1249
  },
@@ -1054,6 +1251,34 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1054
1251
  eventTimestamp: Date.now(),
1055
1252
  });
1056
1253
 
1254
+ if (execResults.status === 'suspended') {
1255
+ await emitter.emit('watch-v2', {
1256
+ type: 'step-suspended',
1257
+ payload: {
1258
+ id: step.id,
1259
+ status: execResults.status,
1260
+ output: execResults.status === 'success' ? execResults?.output : undefined,
1261
+ },
1262
+ });
1263
+ } else {
1264
+ await emitter.emit('watch-v2', {
1265
+ type: 'step-result',
1266
+ payload: {
1267
+ id: step.id,
1268
+ status: execResults.status,
1269
+ output: execResults.status === 'success' ? execResults?.output : undefined,
1270
+ },
1271
+ });
1272
+
1273
+ await emitter.emit('watch-v2', {
1274
+ type: 'step-finish',
1275
+ payload: {
1276
+ id: step.id,
1277
+ metadata: {},
1278
+ },
1279
+ });
1280
+ }
1281
+
1057
1282
  return { result: execResults, executionContext, stepResults };
1058
1283
  });
1059
1284
 
@@ -1140,7 +1365,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1140
1365
  resumePath: number[];
1141
1366
  };
1142
1367
  executionContext: ExecutionContext;
1143
- emitter: { emit: (event: string, data: any) => Promise<void> };
1368
+ emitter: Emitter;
1144
1369
  runtimeContext: RuntimeContext;
1145
1370
  }): Promise<StepResult<any, any, any, any>> {
1146
1371
  let execResults: any;
@@ -1216,7 +1441,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1216
1441
  if (hasFailed) {
1217
1442
  execResults = { status: 'failed', error: hasFailed.result.error };
1218
1443
  } else if (hasSuspended) {
1219
- execResults = { status: 'suspended', payload: hasSuspended.result.payload };
1444
+ execResults = { status: 'suspended', payload: hasSuspended.result.suspendPayload };
1220
1445
  } else {
1221
1446
  execResults = {
1222
1447
  status: 'success',