@mastra/inngest 0.0.0-fix-generate-title-20250616171351 → 0.0.0-fix-fetch-workflow-runs-20250624231457

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,9 @@
1
1
  import { randomUUID } from 'crypto';
2
+ import type { ReadableStream } from 'node:stream/web';
2
3
  import { subscribe } from '@inngest/realtime';
3
- import { Agent, Tool } from '@mastra/core';
4
- import type { Mastra, ToolExecutionContext, WorkflowRun, WorkflowRuns } from '@mastra/core';
4
+ import type { Agent, Mastra, ToolExecutionContext, WorkflowRun, WorkflowRuns } from '@mastra/core';
5
5
  import { RuntimeContext } from '@mastra/core/di';
6
+ import { Tool } from '@mastra/core/tools';
6
7
  import { Workflow, Run, DefaultExecutionEngine } from '@mastra/core/workflows';
7
8
  import type {
8
9
  ExecuteFunction,
@@ -16,7 +17,9 @@ import type {
16
17
  WorkflowResult,
17
18
  SerializedStepFlowEntry,
18
19
  StepFailure,
20
+ Emitter,
19
21
  WatchEvent,
22
+ StreamEvent,
20
23
  } from '@mastra/core/workflows';
21
24
  import { EMITTER_SYMBOL } from '@mastra/core/workflows/_constants';
22
25
  import type { Span } from '@opentelemetry/api';
@@ -87,7 +90,8 @@ export class InngestRun<
87
90
 
88
91
  async getRunOutput(eventId: string) {
89
92
  let runs = await this.getRuns(eventId);
90
- while (runs?.[0]?.status !== 'Completed') {
93
+
94
+ while (runs?.[0]?.status !== 'Completed' || runs?.[0]?.event_id !== eventId) {
91
95
  await new Promise(resolve => setTimeout(resolve, 1000));
92
96
  runs = await this.getRuns(eventId);
93
97
  if (runs?.[0]?.status === 'Failed' || runs?.[0]?.status === 'Cancelled') {
@@ -97,6 +101,13 @@ export class InngestRun<
97
101
  return runs?.[0];
98
102
  }
99
103
 
104
+ async sendEvent(event: string, data: any) {
105
+ await this.inngest.send({
106
+ name: `user-event-${event}`,
107
+ data,
108
+ });
109
+ }
110
+
100
111
  async start({
101
112
  inputData,
102
113
  }: {
@@ -148,6 +159,27 @@ export class InngestRun<
148
159
  | string
149
160
  | string[];
150
161
  runtimeContext?: RuntimeContext;
162
+ }): Promise<WorkflowResult<TOutput, TSteps>> {
163
+ const p = this._resume(params).then(result => {
164
+ if (result.status !== 'suspended') {
165
+ this.closeStreamAction?.().catch(() => {});
166
+ }
167
+
168
+ return result;
169
+ });
170
+
171
+ this.executionResults = p;
172
+ return p;
173
+ }
174
+
175
+ async _resume<TResumeSchema extends z.ZodType<any>>(params: {
176
+ resumeData?: z.infer<TResumeSchema>;
177
+ step:
178
+ | Step<string, any, any, TResumeSchema, any>
179
+ | [...Step<string, any, any, any, any>[], Step<string, any, any, TResumeSchema, any>]
180
+ | string
181
+ | string[];
182
+ runtimeContext?: RuntimeContext;
151
183
  }): Promise<WorkflowResult<TOutput, TSteps>> {
152
184
  const steps: string[] = (Array.isArray(params.step) ? params.step : [params.step]).map(step =>
153
185
  typeof step === 'string' ? step : step?.id,
@@ -185,28 +217,72 @@ export class InngestRun<
185
217
  return result;
186
218
  }
187
219
 
188
- watch(cb: (event: any) => void): () => void {
220
+ watch(cb: (event: WatchEvent) => void, type: 'watch' | 'watch-v2' = 'watch'): () => void {
221
+ let active = true;
189
222
  const streamPromise = subscribe(
190
223
  {
191
224
  channel: `workflow:${this.workflowId}:${this.runId}`,
192
- topics: ['watch'],
225
+ topics: [type],
193
226
  app: this.inngest,
194
227
  },
195
228
  (message: any) => {
196
- cb(message.data);
229
+ if (active) {
230
+ cb(message.data);
231
+ }
197
232
  },
198
233
  );
199
234
 
200
235
  return () => {
236
+ active = false;
201
237
  streamPromise
202
- .then((stream: any) => {
203
- stream.cancel();
238
+ .then(async (stream: Awaited<typeof streamPromise>) => {
239
+ return stream.cancel();
204
240
  })
205
241
  .catch(err => {
206
242
  console.error(err);
207
243
  });
208
244
  };
209
245
  }
246
+
247
+ stream({ inputData, runtimeContext }: { inputData?: z.infer<TInput>; runtimeContext?: RuntimeContext } = {}): {
248
+ stream: ReadableStream<StreamEvent>;
249
+ getWorkflowState: () => Promise<WorkflowResult<TOutput, TSteps>>;
250
+ } {
251
+ const { readable, writable } = new TransformStream<StreamEvent, StreamEvent>();
252
+
253
+ const writer = writable.getWriter();
254
+ const unwatch = this.watch(async event => {
255
+ try {
256
+ // watch-v2 events are data stream events, so we need to cast them to the correct type
257
+ await writer.write(event as any);
258
+ } catch {}
259
+ }, 'watch-v2');
260
+
261
+ this.closeStreamAction = async () => {
262
+ unwatch();
263
+
264
+ try {
265
+ await writer.close();
266
+ } catch (err) {
267
+ console.error('Error closing stream:', err);
268
+ } finally {
269
+ writer.releaseLock();
270
+ }
271
+ };
272
+
273
+ this.executionResults = this.start({ inputData, runtimeContext }).then(result => {
274
+ if (result.status !== 'suspended') {
275
+ this.closeStreamAction?.().catch(() => {});
276
+ }
277
+
278
+ return result;
279
+ });
280
+
281
+ return {
282
+ stream: readable as ReadableStream<StreamEvent>,
283
+ getWorkflowState: () => this.executionResults!,
284
+ };
285
+ }
210
286
  }
211
287
 
212
288
  export class InngestWorkflow<
@@ -334,6 +410,49 @@ export class InngestWorkflow<
334
410
  return run;
335
411
  }
336
412
 
413
+ async createRunAsync(options?: { runId?: string }): Promise<Run<TEngineType, TSteps, TInput, TOutput>> {
414
+ const runIdToUse = options?.runId || randomUUID();
415
+
416
+ // Return a new Run instance with object parameters
417
+ const run: Run<TEngineType, TSteps, TInput, TOutput> =
418
+ this.runs.get(runIdToUse) ??
419
+ new InngestRun(
420
+ {
421
+ workflowId: this.id,
422
+ runId: runIdToUse,
423
+ executionEngine: this.executionEngine,
424
+ executionGraph: this.executionGraph,
425
+ serializedStepGraph: this.serializedStepGraph,
426
+ mastra: this.#mastra,
427
+ retryConfig: this.retryConfig,
428
+ cleanup: () => this.runs.delete(runIdToUse),
429
+ },
430
+ this.inngest,
431
+ );
432
+
433
+ this.runs.set(runIdToUse, run);
434
+
435
+ await this.mastra?.getStorage()?.persistWorkflowSnapshot({
436
+ workflowName: this.id,
437
+ runId: runIdToUse,
438
+ snapshot: {
439
+ runId: runIdToUse,
440
+ status: 'pending',
441
+ value: {},
442
+ context: {},
443
+ activePaths: [],
444
+ serializedStepGraph: this.serializedStepGraph,
445
+ suspendedPaths: {},
446
+ result: undefined,
447
+ error: undefined,
448
+ // @ts-ignore
449
+ timestamp: Date.now(),
450
+ },
451
+ });
452
+
453
+ return run;
454
+ }
455
+
337
456
  getFunction() {
338
457
  if (this.function) {
339
458
  return this.function;
@@ -360,13 +479,22 @@ export class InngestWorkflow<
360
479
  try {
361
480
  await publish({
362
481
  channel: `workflow:${this.id}:${runId}`,
363
- topic: 'watch',
482
+ topic: event,
364
483
  data,
365
484
  });
366
485
  } catch (err: any) {
367
486
  this.logger.error('Error emitting event: ' + (err?.stack ?? err?.message ?? err));
368
487
  }
369
488
  },
489
+ on: (_event: string, _callback: (data: any) => void) => {
490
+ // no-op
491
+ },
492
+ off: (_event: string, _callback: (data: any) => void) => {
493
+ // no-op
494
+ },
495
+ once: (_event: string, _callback: (data: any) => void) => {
496
+ // no-op
497
+ },
370
498
  };
371
499
 
372
500
  const engine = new InngestExecutionEngine(this.#mastra, step, attempt);
@@ -408,6 +536,14 @@ export class InngestWorkflow<
408
536
  }
409
537
  }
410
538
 
539
+ function isAgent(params: any): params is Agent<any, any, any> {
540
+ return params?.component === 'AGENT';
541
+ }
542
+
543
+ function isTool(params: any): params is Tool<any, any, any> {
544
+ return params instanceof Tool;
545
+ }
546
+
411
547
  export function createStep<
412
548
  TStepId extends string,
413
549
  TStepInput extends z.ZodType<any>,
@@ -451,7 +587,6 @@ export function createStep<
451
587
  execute: (context: TContext) => Promise<any>;
452
588
  },
453
589
  ): Step<string, TSchemaIn, TSchemaOut, z.ZodType<any>, z.ZodType<any>, InngestEngineType>;
454
-
455
590
  export function createStep<
456
591
  TStepId extends string,
457
592
  TStepInput extends z.ZodType<any>,
@@ -482,7 +617,7 @@ export function createStep<
482
617
  execute: (context: ToolExecutionContext<TStepInput>) => Promise<any>;
483
618
  }),
484
619
  ): Step<TStepId, TStepInput, TStepOutput, TResumeSchema, TSuspendSchema, InngestEngineType> {
485
- if (params instanceof Agent) {
620
+ if (isAgent(params)) {
486
621
  return {
487
622
  id: params.name,
488
623
  // @ts-ignore
@@ -557,7 +692,7 @@ export function createStep<
557
692
  };
558
693
  }
559
694
 
560
- if (params instanceof Tool) {
695
+ if (isTool(params)) {
561
696
  if (!params.inputSchema || !params.outputSchema) {
562
697
  throw new Error('Tool must have input and output schemas defined');
563
698
  }
@@ -631,11 +766,12 @@ export function init(inngest: Inngest) {
631
766
  any,
632
767
  InngestEngineType
633
768
  >[],
769
+ TPrevSchema extends z.ZodType<any> = TInput,
634
770
  >(
635
- workflow: Workflow<InngestEngineType, TSteps, string, TInput, TOutput, TInput>,
771
+ workflow: Workflow<InngestEngineType, TSteps, string, TInput, TOutput, TPrevSchema>,
636
772
  opts: { id: TWorkflowId },
637
- ): Workflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TInput> {
638
- const wf = new Workflow({
773
+ ): Workflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> {
774
+ const wf: Workflow<InngestEngineType, TSteps, TWorkflowId, TInput, TOutput, TPrevSchema> = new Workflow({
639
775
  id: opts.id,
640
776
  inputSchema: workflow.inputSchema,
641
777
  outputSchema: workflow.outputSchema,
@@ -660,9 +796,44 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
660
796
  this.inngestAttempts = inngestAttempts;
661
797
  }
662
798
 
799
+ async execute<TInput, TOutput>(params: {
800
+ workflowId: string;
801
+ runId: string;
802
+ graph: ExecutionGraph;
803
+ serializedStepGraph: SerializedStepFlowEntry[];
804
+ input?: TInput;
805
+ resume?: {
806
+ // TODO: add execute path
807
+ steps: string[];
808
+ stepResults: Record<string, StepResult<any, any, any, any>>;
809
+ resumePayload: any;
810
+ resumePath: number[];
811
+ };
812
+ emitter: Emitter;
813
+ retryConfig?: {
814
+ attempts?: number;
815
+ delay?: number;
816
+ };
817
+ runtimeContext: RuntimeContext;
818
+ }): Promise<TOutput> {
819
+ await params.emitter.emit('watch-v2', {
820
+ type: 'start',
821
+ payload: { runId: params.runId },
822
+ });
823
+
824
+ const result = await super.execute<TInput, TOutput>(params);
825
+
826
+ await params.emitter.emit('watch-v2', {
827
+ type: 'finish',
828
+ payload: { runId: params.runId },
829
+ });
830
+
831
+ return result;
832
+ }
833
+
663
834
  protected async fmtReturnValue<TOutput>(
664
835
  executionSpan: Span | undefined,
665
- emitter: { emit: (event: string, data: any) => Promise<void> },
836
+ emitter: Emitter,
666
837
  stepResults: Record<string, StepResult<any, any, any, any>>,
667
838
  lastOutput: StepResult<any, any, any, any>,
668
839
  error?: Error | string,
@@ -755,7 +926,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
755
926
  resumePayload: any;
756
927
  };
757
928
  prevOutput: any;
758
- emitter: { emit: (event: string, data: any) => Promise<void> };
929
+ emitter: Emitter;
759
930
  runtimeContext: RuntimeContext;
760
931
  }): Promise<StepResult<any, any, any, any>> {
761
932
  return super.executeStep({
@@ -775,6 +946,19 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
775
946
  await this.inngestStep.sleep(id, duration);
776
947
  }
777
948
 
949
+ async executeWaitForEvent({ event, timeout }: { event: string; timeout?: number }): Promise<any> {
950
+ const eventData = await this.inngestStep.waitForEvent(`user-event-${event}`, {
951
+ event: `user-event-${event}`,
952
+ timeout: timeout ?? 5e3,
953
+ });
954
+
955
+ if (eventData === null) {
956
+ throw 'Timeout waiting for event';
957
+ }
958
+
959
+ return eventData?.data;
960
+ }
961
+
778
962
  async executeStep({
779
963
  step,
780
964
  stepResults,
@@ -799,12 +983,13 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
799
983
  runId?: string;
800
984
  };
801
985
  prevOutput: any;
802
- emitter: { emit: (event: string, data: any) => Promise<void> };
986
+ emitter: Emitter;
803
987
  runtimeContext: RuntimeContext;
804
988
  }): Promise<StepResult<any, any, any, any>> {
805
- await this.inngestStep.run(
989
+ const startedAt = await this.inngestStep.run(
806
990
  `workflow.${executionContext.workflowId}.run.${executionContext.runId}.step.${step.id}.running_ev`,
807
991
  async () => {
992
+ const startedAt = Date.now();
808
993
  await emitter.emit('watch', {
809
994
  type: 'watch',
810
995
  payload: {
@@ -826,6 +1011,15 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
826
1011
  },
827
1012
  eventTimestamp: Date.now(),
828
1013
  });
1014
+
1015
+ await emitter.emit('watch-v2', {
1016
+ type: 'step-start',
1017
+ payload: {
1018
+ id: step.id,
1019
+ },
1020
+ });
1021
+
1022
+ return startedAt;
829
1023
  },
830
1024
  );
831
1025
 
@@ -892,6 +1086,14 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
892
1086
  eventTimestamp: Date.now(),
893
1087
  });
894
1088
 
1089
+ await emitter.emit('watch-v2', {
1090
+ type: 'step-result',
1091
+ payload: {
1092
+ id: step.id,
1093
+ status: 'failed',
1094
+ },
1095
+ });
1096
+
895
1097
  return { executionContext, result: { status: 'failed', error: result?.error } };
896
1098
  } else if (result.status === 'suspended') {
897
1099
  const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
@@ -922,6 +1124,13 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
922
1124
  eventTimestamp: Date.now(),
923
1125
  });
924
1126
 
1127
+ await emitter.emit('watch-v2', {
1128
+ type: 'step-suspended',
1129
+ payload: {
1130
+ id: step.id,
1131
+ },
1132
+ });
1133
+
925
1134
  return {
926
1135
  executionContext,
927
1136
  result: {
@@ -978,6 +1187,14 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
978
1187
  eventTimestamp: Date.now(),
979
1188
  });
980
1189
 
1190
+ await emitter.emit('watch-v2', {
1191
+ type: 'step-finish',
1192
+ payload: {
1193
+ id: step.id,
1194
+ metadata: {},
1195
+ },
1196
+ });
1197
+
981
1198
  return { executionContext, result: { status: 'success', output: result?.result } };
982
1199
  },
983
1200
  );
@@ -989,6 +1206,8 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
989
1206
  const stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
990
1207
  let execResults: any;
991
1208
  let suspended: { payload: any } | undefined;
1209
+ let bailed: { payload: any } | undefined;
1210
+
992
1211
  try {
993
1212
  const result = await step.execute({
994
1213
  runId: executionContext.runId,
@@ -1009,6 +1228,9 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1009
1228
  executionContext.suspendedPaths[step.id] = executionContext.executionPath;
1010
1229
  suspended = { payload: suspendPayload };
1011
1230
  },
1231
+ bail: (result: any) => {
1232
+ bailed = { payload: result };
1233
+ },
1012
1234
  resume: {
1013
1235
  steps: resume?.steps?.slice(1) || [],
1014
1236
  resumePayload: resume?.resumePayload,
@@ -1020,14 +1242,41 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1020
1242
  step: this.inngestStep,
1021
1243
  },
1022
1244
  });
1023
-
1024
- execResults = { status: 'success', output: result };
1245
+ const endedAt = Date.now();
1246
+
1247
+ execResults = {
1248
+ status: 'success',
1249
+ output: result,
1250
+ startedAt,
1251
+ endedAt,
1252
+ payload: prevOutput,
1253
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1254
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1255
+ };
1025
1256
  } catch (e) {
1026
- execResults = { status: 'failed', error: e instanceof Error ? e.message : String(e) };
1257
+ execResults = {
1258
+ status: 'failed',
1259
+ payload: prevOutput,
1260
+ error: e instanceof Error ? e.message : String(e),
1261
+ endedAt: Date.now(),
1262
+ startedAt,
1263
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1264
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1265
+ };
1027
1266
  }
1028
1267
 
1029
1268
  if (suspended) {
1030
- execResults = { status: 'suspended', payload: suspended.payload };
1269
+ execResults = {
1270
+ status: 'suspended',
1271
+ suspendedPayload: suspended.payload,
1272
+ payload: prevOutput,
1273
+ suspendedAt: Date.now(),
1274
+ startedAt,
1275
+ resumedAt: resume?.steps[0] === step.id ? startedAt : undefined,
1276
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : undefined,
1277
+ };
1278
+ } else if (bailed) {
1279
+ execResults = { status: 'bailed', output: bailed.payload, payload: prevOutput, endedAt: Date.now(), startedAt };
1031
1280
  }
1032
1281
 
1033
1282
  if (execResults.status === 'failed') {
@@ -1041,12 +1290,11 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1041
1290
  payload: {
1042
1291
  currentStep: {
1043
1292
  id: step.id,
1044
- status: execResults.status,
1045
- output: execResults.output,
1293
+ ...execResults,
1046
1294
  },
1047
1295
  workflowState: {
1048
1296
  status: 'running',
1049
- steps: stepResults,
1297
+ steps: { ...stepResults, [step.id]: execResults },
1050
1298
  result: null,
1051
1299
  error: null,
1052
1300
  },
@@ -1054,6 +1302,34 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1054
1302
  eventTimestamp: Date.now(),
1055
1303
  });
1056
1304
 
1305
+ if (execResults.status === 'suspended') {
1306
+ await emitter.emit('watch-v2', {
1307
+ type: 'step-suspended',
1308
+ payload: {
1309
+ id: step.id,
1310
+ status: execResults.status,
1311
+ output: execResults.status === 'success' ? execResults?.output : undefined,
1312
+ },
1313
+ });
1314
+ } else {
1315
+ await emitter.emit('watch-v2', {
1316
+ type: 'step-result',
1317
+ payload: {
1318
+ id: step.id,
1319
+ status: execResults.status,
1320
+ output: execResults.status === 'success' ? execResults?.output : undefined,
1321
+ },
1322
+ });
1323
+
1324
+ await emitter.emit('watch-v2', {
1325
+ type: 'step-finish',
1326
+ payload: {
1327
+ id: step.id,
1328
+ metadata: {},
1329
+ },
1330
+ });
1331
+ }
1332
+
1057
1333
  return { result: execResults, executionContext, stepResults };
1058
1334
  });
1059
1335
 
@@ -1140,7 +1416,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1140
1416
  resumePath: number[];
1141
1417
  };
1142
1418
  executionContext: ExecutionContext;
1143
- emitter: { emit: (event: string, data: any) => Promise<void> };
1419
+ emitter: Emitter;
1144
1420
  runtimeContext: RuntimeContext;
1145
1421
  }): Promise<StepResult<any, any, any, any>> {
1146
1422
  let execResults: any;
@@ -1170,6 +1446,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1170
1446
 
1171
1447
  // TODO: this function shouldn't have suspend probably?
1172
1448
  suspend: async (_suspendPayload: any) => {},
1449
+ bail: () => {},
1173
1450
  [EMITTER_SYMBOL]: emitter,
1174
1451
  engine: {
1175
1452
  step: this.inngestStep,
@@ -1216,7 +1493,7 @@ export class InngestExecutionEngine extends DefaultExecutionEngine {
1216
1493
  if (hasFailed) {
1217
1494
  execResults = { status: 'failed', error: hasFailed.result.error };
1218
1495
  } else if (hasSuspended) {
1219
- execResults = { status: 'suspended', payload: hasSuspended.result.payload };
1496
+ execResults = { status: 'suspended', payload: hasSuspended.result.suspendPayload };
1220
1497
  } else {
1221
1498
  execResults = {
1222
1499
  status: 'success',