@mastra/inngest 0.0.0-vnext-inngest-20250508122351 → 0.0.0-vnext-20251104230439

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/dist/index.cjs CHANGED
@@ -1,36 +1,55 @@
1
1
  'use strict';
2
2
 
3
3
  var crypto = require('crypto');
4
+ var web = require('stream/web');
4
5
  var realtime = require('@inngest/realtime');
5
6
  var di = require('@mastra/core/di');
6
- var vNext = require('@mastra/core/workflows/vNext');
7
+ var observability = require('@mastra/core/observability');
8
+ var stream = require('@mastra/core/stream');
9
+ var tools = require('@mastra/core/tools');
10
+ var workflows = require('@mastra/core/workflows');
11
+ var _constants = require('@mastra/core/workflows/_constants');
12
+ var inngest = require('inngest');
7
13
  var hono = require('inngest/hono');
14
+ var zod = require('zod');
8
15
 
9
16
  // src/index.ts
10
- function serve({ mastra, inngest }) {
11
- const wfs = mastra.vnext_getWorkflows();
12
- const functions = Object.values(wfs).flatMap((wf) => {
13
- if (wf instanceof InngestWorkflow) {
14
- wf.__registerMastra(mastra);
15
- return wf.getFunctions();
16
- }
17
- return [];
18
- });
17
+ function serve({
18
+ mastra,
19
+ inngest,
20
+ functions: userFunctions = [],
21
+ registerOptions
22
+ }) {
23
+ const wfs = mastra.listWorkflows();
24
+ const workflowFunctions = Array.from(
25
+ new Set(
26
+ Object.values(wfs).flatMap((wf) => {
27
+ if (wf instanceof InngestWorkflow) {
28
+ wf.__registerMastra(mastra);
29
+ return wf.getFunctions();
30
+ }
31
+ return [];
32
+ })
33
+ )
34
+ );
19
35
  return hono.serve({
36
+ ...registerOptions,
20
37
  client: inngest,
21
- functions
38
+ functions: [...workflowFunctions, ...userFunctions]
22
39
  });
23
40
  }
24
- var InngestRun = class extends vNext.Run {
41
+ var InngestRun = class extends workflows.Run {
25
42
  inngest;
43
+ serializedStepGraph;
26
44
  #mastra;
27
45
  constructor(params, inngest) {
28
46
  super(params);
29
47
  this.inngest = inngest;
48
+ this.serializedStepGraph = params.serializedStepGraph;
30
49
  this.#mastra = params.mastra;
31
50
  }
32
51
  async getRuns(eventId) {
33
- const response = await fetch(`${this.inngest.apiBaseUrl}/v1/events/${eventId}/runs`, {
52
+ const response = await fetch(`${this.inngest.apiBaseUrl ?? "https://api.inngest.com"}/v1/events/${eventId}/runs`, {
34
53
  headers: {
35
54
  Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`
36
55
  }
@@ -40,35 +59,98 @@ var InngestRun = class extends vNext.Run {
40
59
  }
41
60
  async getRunOutput(eventId) {
42
61
  let runs = await this.getRuns(eventId);
43
- while (runs?.[0]?.status !== "Completed") {
62
+ const storage = this.#mastra?.getStorage();
63
+ while (runs?.[0]?.status !== "Completed" || runs?.[0]?.event_id !== eventId) {
44
64
  await new Promise((resolve) => setTimeout(resolve, 1e3));
45
65
  runs = await this.getRuns(eventId);
46
- if (runs?.[0]?.status === "Failed" || runs?.[0]?.status === "Cancelled") {
47
- throw new Error(`Function run ${runs?.[0]?.status}`);
66
+ if (runs?.[0]?.status === "Failed") {
67
+ const snapshot = await storage?.loadWorkflowSnapshot({
68
+ workflowName: this.workflowId,
69
+ runId: this.runId
70
+ });
71
+ return {
72
+ output: { result: { steps: snapshot?.context, status: "failed", error: runs?.[0]?.output?.message } }
73
+ };
74
+ }
75
+ if (runs?.[0]?.status === "Cancelled") {
76
+ const snapshot = await storage?.loadWorkflowSnapshot({
77
+ workflowName: this.workflowId,
78
+ runId: this.runId
79
+ });
80
+ return { output: { result: { steps: snapshot?.context, status: "canceled" } } };
48
81
  }
49
82
  }
50
83
  return runs?.[0];
51
84
  }
52
- async start({
53
- inputData
85
+ async sendEvent(event, data) {
86
+ await this.inngest.send({
87
+ name: `user-event-${event}`,
88
+ data
89
+ });
90
+ }
91
+ async cancel() {
92
+ const storage = this.#mastra?.getStorage();
93
+ await this.inngest.send({
94
+ name: `cancel.workflow.${this.workflowId}`,
95
+ data: {
96
+ runId: this.runId
97
+ }
98
+ });
99
+ const snapshot = await storage?.loadWorkflowSnapshot({
100
+ workflowName: this.workflowId,
101
+ runId: this.runId
102
+ });
103
+ if (snapshot) {
104
+ await storage?.persistWorkflowSnapshot({
105
+ workflowName: this.workflowId,
106
+ runId: this.runId,
107
+ resourceId: this.resourceId,
108
+ snapshot: {
109
+ ...snapshot,
110
+ status: "canceled"
111
+ }
112
+ });
113
+ }
114
+ }
115
+ async start(params) {
116
+ return this._start(params);
117
+ }
118
+ async _start({
119
+ inputData,
120
+ initialState,
121
+ outputOptions,
122
+ tracingOptions,
123
+ format
54
124
  }) {
55
125
  await this.#mastra.getStorage()?.persistWorkflowSnapshot({
56
126
  workflowName: this.workflowId,
57
127
  runId: this.runId,
128
+ resourceId: this.resourceId,
58
129
  snapshot: {
59
130
  runId: this.runId,
131
+ serializedStepGraph: this.serializedStepGraph,
60
132
  value: {},
61
133
  context: {},
62
134
  activePaths: [],
63
135
  suspendedPaths: {},
64
- timestamp: Date.now()
136
+ resumeLabels: {},
137
+ waitingPaths: {},
138
+ timestamp: Date.now(),
139
+ status: "running"
65
140
  }
66
141
  });
142
+ const inputDataToUse = await this._validateInput(inputData);
143
+ const initialStateToUse = await this._validateInitialState(initialState ?? {});
67
144
  const eventOutput = await this.inngest.send({
68
145
  name: `workflow.${this.workflowId}`,
69
146
  data: {
70
- inputData,
71
- runId: this.runId
147
+ inputData: inputDataToUse,
148
+ initialState: initialStateToUse,
149
+ runId: this.runId,
150
+ resourceId: this.resourceId,
151
+ outputOptions,
152
+ tracingOptions,
153
+ format
72
154
  }
73
155
  });
74
156
  const eventId = eventOutput.ids[0];
@@ -80,27 +162,45 @@ var InngestRun = class extends vNext.Run {
80
162
  if (result.status === "failed") {
81
163
  result.error = new Error(result.error);
82
164
  }
83
- this.cleanup?.();
165
+ if (result.status !== "suspended") {
166
+ this.cleanup?.();
167
+ }
84
168
  return result;
85
169
  }
86
170
  async resume(params) {
171
+ const p = this._resume(params).then((result) => {
172
+ if (result.status !== "suspended") {
173
+ this.closeStreamAction?.().catch(() => {
174
+ });
175
+ }
176
+ return result;
177
+ });
178
+ this.executionResults = p;
179
+ return p;
180
+ }
181
+ async _resume(params) {
182
+ const storage = this.#mastra?.getStorage();
87
183
  const steps = (Array.isArray(params.step) ? params.step : [params.step]).map(
88
184
  (step) => typeof step === "string" ? step : step?.id
89
185
  );
90
- const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
186
+ const snapshot = await storage?.loadWorkflowSnapshot({
91
187
  workflowName: this.workflowId,
92
188
  runId: this.runId
93
189
  });
190
+ const suspendedStep = this.workflowSteps[steps?.[0] ?? ""];
191
+ const resumeDataToUse = await this._validateResumeData(params.resumeData, suspendedStep);
94
192
  const eventOutput = await this.inngest.send({
95
193
  name: `workflow.${this.workflowId}`,
96
194
  data: {
97
- inputData: params.resumeData,
195
+ inputData: resumeDataToUse,
196
+ initialState: snapshot?.value ?? {},
98
197
  runId: this.runId,
198
+ workflowId: this.workflowId,
99
199
  stepResults: snapshot?.context,
100
200
  resume: {
101
201
  steps,
102
202
  stepResults: snapshot?.context,
103
- resumePayload: params.resumeData,
203
+ resumePayload: resumeDataToUse,
104
204
  // @ts-ignore
105
205
  resumePath: snapshot?.suspendedPaths?.[steps?.[0]]
106
206
  }
@@ -118,6 +218,7 @@ var InngestRun = class extends vNext.Run {
118
218
  return result;
119
219
  }
120
220
  watch(cb) {
221
+ let active = true;
121
222
  const streamPromise = realtime.subscribe(
122
223
  {
123
224
  channel: `workflow:${this.workflowId}:${this.runId}`,
@@ -125,27 +226,179 @@ var InngestRun = class extends vNext.Run {
125
226
  app: this.inngest
126
227
  },
127
228
  (message) => {
128
- cb(message.data);
229
+ if (active) {
230
+ cb(message.data);
231
+ }
129
232
  }
130
233
  );
131
234
  return () => {
132
- streamPromise.then((stream) => {
133
- stream.cancel();
235
+ active = false;
236
+ streamPromise.then(async (stream) => {
237
+ return stream.cancel();
134
238
  }).catch((err) => {
135
239
  console.error(err);
136
240
  });
137
241
  };
138
242
  }
243
+ streamLegacy({ inputData, requestContext } = {}) {
244
+ const { readable, writable } = new TransformStream();
245
+ const writer = writable.getWriter();
246
+ const unwatch = this.watch(async (event) => {
247
+ try {
248
+ await writer.write({
249
+ // @ts-ignore
250
+ type: "start",
251
+ // @ts-ignore
252
+ payload: { runId: this.runId }
253
+ });
254
+ const e = {
255
+ ...event,
256
+ type: event.type.replace("workflow-", "")
257
+ };
258
+ if (e.type === "step-output") {
259
+ e.type = e.payload.output.type;
260
+ e.payload = e.payload.output.payload;
261
+ }
262
+ await writer.write(e);
263
+ } catch {
264
+ }
265
+ });
266
+ this.closeStreamAction = async () => {
267
+ await writer.write({
268
+ type: "finish",
269
+ // @ts-ignore
270
+ payload: { runId: this.runId }
271
+ });
272
+ unwatch();
273
+ try {
274
+ await writer.close();
275
+ } catch (err) {
276
+ console.error("Error closing stream:", err);
277
+ } finally {
278
+ writer.releaseLock();
279
+ }
280
+ };
281
+ this.executionResults = this._start({ inputData, requestContext, format: "legacy" }).then((result) => {
282
+ if (result.status !== "suspended") {
283
+ this.closeStreamAction?.().catch(() => {
284
+ });
285
+ }
286
+ return result;
287
+ });
288
+ return {
289
+ stream: readable,
290
+ getWorkflowState: () => this.executionResults
291
+ };
292
+ }
293
+ stream({
294
+ inputData,
295
+ requestContext,
296
+ tracingOptions,
297
+ closeOnSuspend = true,
298
+ initialState,
299
+ outputOptions
300
+ } = {}) {
301
+ if (this.closeStreamAction && this.streamOutput) {
302
+ return this.streamOutput;
303
+ }
304
+ this.closeStreamAction = async () => {
305
+ };
306
+ const self = this;
307
+ const stream$1 = new web.ReadableStream({
308
+ async start(controller) {
309
+ const unwatch = self.watch(async ({ type, from = stream.ChunkFrom.WORKFLOW, payload }) => {
310
+ controller.enqueue({
311
+ type,
312
+ runId: self.runId,
313
+ from,
314
+ payload: {
315
+ stepName: payload?.id,
316
+ ...payload
317
+ }
318
+ });
319
+ });
320
+ self.closeStreamAction = async () => {
321
+ unwatch();
322
+ try {
323
+ await controller.close();
324
+ } catch (err) {
325
+ console.error("Error closing stream:", err);
326
+ }
327
+ };
328
+ const executionResultsPromise = self._start({
329
+ inputData,
330
+ requestContext,
331
+ // tracingContext, // We are not able to pass a reference to a span here, what to do?
332
+ initialState,
333
+ tracingOptions,
334
+ outputOptions,
335
+ format: "vnext"
336
+ });
337
+ let executionResults;
338
+ try {
339
+ executionResults = await executionResultsPromise;
340
+ if (closeOnSuspend) {
341
+ self.closeStreamAction?.().catch(() => {
342
+ });
343
+ } else if (executionResults.status !== "suspended") {
344
+ self.closeStreamAction?.().catch(() => {
345
+ });
346
+ }
347
+ if (self.streamOutput) {
348
+ self.streamOutput.updateResults(
349
+ executionResults
350
+ );
351
+ }
352
+ } catch (err) {
353
+ self.streamOutput?.rejectResults(err);
354
+ self.closeStreamAction?.().catch(() => {
355
+ });
356
+ }
357
+ }
358
+ });
359
+ this.streamOutput = new stream.WorkflowRunOutput({
360
+ runId: this.runId,
361
+ workflowId: this.workflowId,
362
+ stream: stream$1
363
+ });
364
+ return this.streamOutput;
365
+ }
366
+ streamVNext(args = {}) {
367
+ return this.stream(args);
368
+ }
139
369
  };
140
- var InngestWorkflow = class _InngestWorkflow extends vNext.NewWorkflow {
370
+ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
141
371
  #mastra;
142
372
  inngest;
143
373
  function;
374
+ flowControlConfig;
144
375
  constructor(params, inngest) {
145
- super(params);
376
+ const { concurrency, rateLimit, throttle, debounce, priority, ...workflowParams } = params;
377
+ super(workflowParams);
378
+ const flowControlEntries = Object.entries({ concurrency, rateLimit, throttle, debounce, priority }).filter(
379
+ ([_, value]) => value !== void 0
380
+ );
381
+ this.flowControlConfig = flowControlEntries.length > 0 ? Object.fromEntries(flowControlEntries) : void 0;
146
382
  this.#mastra = params.mastra;
147
383
  this.inngest = inngest;
148
384
  }
385
+ async listWorkflowRuns(args) {
386
+ const storage = this.#mastra?.getStorage();
387
+ if (!storage) {
388
+ this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
389
+ return { runs: [], total: 0 };
390
+ }
391
+ return storage.listWorkflowRuns({ workflowName: this.id, ...args ?? {} });
392
+ }
393
+ async getWorkflowRunById(runId) {
394
+ const storage = this.#mastra?.getStorage();
395
+ if (!storage) {
396
+ this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
397
+ return this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null;
398
+ }
399
+ const run = await storage.getWorkflowRunById({ runId, workflowName: this.id });
400
+ return run ?? (this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null);
401
+ }
149
402
  __registerMastra(mastra) {
150
403
  this.#mastra = mastra;
151
404
  this.executionEngine.__registerMastra(mastra);
@@ -164,21 +417,51 @@ var InngestWorkflow = class _InngestWorkflow extends vNext.NewWorkflow {
164
417
  }
165
418
  }
166
419
  }
167
- createRun(options) {
420
+ async createRun(options) {
168
421
  const runIdToUse = options?.runId || crypto.randomUUID();
169
422
  const run = this.runs.get(runIdToUse) ?? new InngestRun(
170
423
  {
171
424
  workflowId: this.id,
172
425
  runId: runIdToUse,
426
+ resourceId: options?.resourceId,
173
427
  executionEngine: this.executionEngine,
174
428
  executionGraph: this.executionGraph,
429
+ serializedStepGraph: this.serializedStepGraph,
175
430
  mastra: this.#mastra,
176
431
  retryConfig: this.retryConfig,
177
- cleanup: () => this.runs.delete(runIdToUse)
432
+ cleanup: () => this.runs.delete(runIdToUse),
433
+ workflowSteps: this.steps
178
434
  },
179
435
  this.inngest
180
436
  );
181
437
  this.runs.set(runIdToUse, run);
438
+ const shouldPersistSnapshot = this.options.shouldPersistSnapshot({
439
+ workflowStatus: run.workflowRunStatus,
440
+ stepResults: {}
441
+ });
442
+ const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse, false);
443
+ if (!workflowSnapshotInStorage && shouldPersistSnapshot) {
444
+ await this.mastra?.getStorage()?.persistWorkflowSnapshot({
445
+ workflowName: this.id,
446
+ runId: runIdToUse,
447
+ resourceId: options?.resourceId,
448
+ snapshot: {
449
+ runId: runIdToUse,
450
+ status: "pending",
451
+ value: {},
452
+ context: {},
453
+ activePaths: [],
454
+ waitingPaths: {},
455
+ serializedStepGraph: this.serializedStepGraph,
456
+ suspendedPaths: {},
457
+ resumeLabels: {},
458
+ result: void 0,
459
+ error: void 0,
460
+ // @ts-ignore
461
+ timestamp: Date.now()
462
+ }
463
+ });
464
+ }
182
465
  return run;
183
466
  }
184
467
  getFunction() {
@@ -186,11 +469,17 @@ var InngestWorkflow = class _InngestWorkflow extends vNext.NewWorkflow {
186
469
  return this.function;
187
470
  }
188
471
  this.function = this.inngest.createFunction(
189
- // @ts-ignore
190
- { id: `workflow.${this.id}`, retries: this.retryConfig?.attempts ?? 0 },
472
+ {
473
+ id: `workflow.${this.id}`,
474
+ // @ts-ignore
475
+ retries: this.retryConfig?.attempts ?? 0,
476
+ cancelOn: [{ event: `cancel.workflow.${this.id}` }],
477
+ // Spread flow control configuration
478
+ ...this.flowControlConfig
479
+ },
191
480
  { event: `workflow.${this.id}` },
192
481
  async ({ event, step, attempt, publish }) => {
193
- let { inputData, runId, resume } = event.data;
482
+ let { inputData, initialState, runId, resourceId, resume, outputOptions, format } = event.data;
194
483
  if (!runId) {
195
484
  runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
196
485
  return crypto.randomUUID();
@@ -204,25 +493,52 @@ var InngestWorkflow = class _InngestWorkflow extends vNext.NewWorkflow {
204
493
  try {
205
494
  await publish({
206
495
  channel: `workflow:${this.id}:${runId}`,
207
- topic: "watch",
496
+ topic: event2,
208
497
  data
209
498
  });
210
499
  } catch (err) {
211
500
  this.logger.error("Error emitting event: " + (err?.stack ?? err?.message ?? err));
212
501
  }
502
+ },
503
+ on: (_event, _callback) => {
504
+ },
505
+ off: (_event, _callback) => {
506
+ },
507
+ once: (_event, _callback) => {
213
508
  }
214
509
  };
215
- const engine = new InngestExecutionEngine(this.#mastra, step, attempt);
510
+ const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
216
511
  const result = await engine.execute({
217
512
  workflowId: this.id,
218
513
  runId,
514
+ resourceId,
219
515
  graph: this.executionGraph,
516
+ serializedStepGraph: this.serializedStepGraph,
220
517
  input: inputData,
518
+ initialState,
221
519
  emitter,
222
520
  retryConfig: this.retryConfig,
223
- runtimeContext: new di.RuntimeContext(),
521
+ requestContext: new di.RequestContext(),
224
522
  // TODO
225
- resume
523
+ resume,
524
+ format,
525
+ abortController: new AbortController(),
526
+ // currentSpan: undefined, // TODO: Pass actual parent AI span from workflow execution context
527
+ outputOptions,
528
+ writableStream: new WritableStream({
529
+ write(chunk) {
530
+ void emitter.emit("watch", chunk).catch(() => {
531
+ });
532
+ }
533
+ })
534
+ });
535
+ await step.run(`workflow.${this.id}.finalize`, async () => {
536
+ if (result.status === "failed") {
537
+ throw new inngest.NonRetriableError(`Workflow failed`, {
538
+ cause: result
539
+ });
540
+ }
541
+ return result;
226
542
  });
227
543
  return { result, runId };
228
544
  }
@@ -246,118 +562,403 @@ var InngestWorkflow = class _InngestWorkflow extends vNext.NewWorkflow {
246
562
  return [this.getFunction(), ...this.getNestedFunctions(this.executionGraph.steps)];
247
563
  }
248
564
  };
249
- function cloneWorkflow(workflow, opts) {
250
- const wf = new InngestWorkflow(
251
- {
252
- id: opts.id,
253
- inputSchema: workflow.inputSchema,
254
- outputSchema: workflow.outputSchema,
255
- steps: workflow.stepDefs,
256
- mastra: workflow.mastra
257
- },
258
- workflow.inngest
259
- );
260
- wf.setStepFlow(workflow.stepGraph);
261
- wf.commit();
262
- return wf;
565
+ function isAgent(params) {
566
+ return params?.component === "AGENT";
567
+ }
568
+ function isTool(params) {
569
+ return params instanceof tools.Tool;
570
+ }
571
+ function createStep(params, agentOptions) {
572
+ if (isAgent(params)) {
573
+ return {
574
+ id: params.name,
575
+ description: params.getDescription(),
576
+ // @ts-ignore
577
+ inputSchema: zod.z.object({
578
+ prompt: zod.z.string()
579
+ // resourceId: z.string().optional(),
580
+ // threadId: z.string().optional(),
581
+ }),
582
+ // @ts-ignore
583
+ outputSchema: zod.z.object({
584
+ text: zod.z.string()
585
+ }),
586
+ execute: async ({
587
+ inputData,
588
+ [_constants.EMITTER_SYMBOL]: emitter,
589
+ [_constants.STREAM_FORMAT_SYMBOL]: streamFormat,
590
+ requestContext,
591
+ tracingContext,
592
+ abortSignal,
593
+ abort,
594
+ writer
595
+ }) => {
596
+ let streamPromise = {};
597
+ streamPromise.promise = new Promise((resolve, reject) => {
598
+ streamPromise.resolve = resolve;
599
+ streamPromise.reject = reject;
600
+ });
601
+ const toolData = {
602
+ name: params.name,
603
+ args: inputData
604
+ };
605
+ let stream;
606
+ if ((await params.getModel()).specificationVersion === "v1") {
607
+ const { fullStream } = await params.streamLegacy(inputData.prompt, {
608
+ ...agentOptions ?? {},
609
+ // resourceId: inputData.resourceId,
610
+ // threadId: inputData.threadId,
611
+ requestContext,
612
+ tracingContext,
613
+ onFinish: (result) => {
614
+ streamPromise.resolve(result.text);
615
+ void agentOptions?.onFinish?.(result);
616
+ },
617
+ abortSignal
618
+ });
619
+ stream = fullStream;
620
+ } else {
621
+ const modelOutput = await params.stream(inputData.prompt, {
622
+ ...agentOptions ?? {},
623
+ requestContext,
624
+ tracingContext,
625
+ onFinish: (result) => {
626
+ streamPromise.resolve(result.text);
627
+ void agentOptions?.onFinish?.(result);
628
+ },
629
+ abortSignal
630
+ });
631
+ stream = modelOutput.fullStream;
632
+ }
633
+ if (streamFormat === "legacy") {
634
+ await emitter.emit("watch", {
635
+ type: "tool-call-streaming-start",
636
+ ...toolData ?? {}
637
+ });
638
+ for await (const chunk of stream) {
639
+ if (chunk.type === "text-delta") {
640
+ await emitter.emit("watch", {
641
+ type: "tool-call-delta",
642
+ ...toolData ?? {},
643
+ argsTextDelta: chunk.textDelta
644
+ });
645
+ }
646
+ }
647
+ await emitter.emit("watch", {
648
+ type: "tool-call-streaming-finish",
649
+ ...toolData ?? {}
650
+ });
651
+ } else {
652
+ for await (const chunk of stream) {
653
+ await writer.write(chunk);
654
+ }
655
+ }
656
+ if (abortSignal.aborted) {
657
+ return abort();
658
+ }
659
+ return {
660
+ text: await streamPromise.promise
661
+ };
662
+ },
663
+ component: params.component
664
+ };
665
+ }
666
+ if (isTool(params)) {
667
+ if (!params.inputSchema || !params.outputSchema) {
668
+ throw new Error("Tool must have input and output schemas defined");
669
+ }
670
+ return {
671
+ // TODO: tool probably should have strong id type
672
+ // @ts-ignore
673
+ id: params.id,
674
+ description: params.description,
675
+ inputSchema: params.inputSchema,
676
+ outputSchema: params.outputSchema,
677
+ execute: async ({ inputData, mastra, requestContext, tracingContext, suspend, resumeData }) => {
678
+ return params.execute({
679
+ context: inputData,
680
+ mastra: observability.wrapMastra(mastra, tracingContext),
681
+ requestContext,
682
+ tracingContext,
683
+ suspend,
684
+ resumeData
685
+ });
686
+ },
687
+ component: "TOOL"
688
+ };
689
+ }
690
+ return {
691
+ id: params.id,
692
+ description: params.description,
693
+ inputSchema: params.inputSchema,
694
+ outputSchema: params.outputSchema,
695
+ resumeSchema: params.resumeSchema,
696
+ suspendSchema: params.suspendSchema,
697
+ execute: params.execute
698
+ };
263
699
  }
264
700
  function init(inngest) {
265
701
  return {
266
702
  createWorkflow(params) {
267
- return new InngestWorkflow(params, inngest);
703
+ return new InngestWorkflow(
704
+ params,
705
+ inngest
706
+ );
268
707
  },
269
- createStep: vNext.createStep,
270
- cloneStep: vNext.cloneStep,
271
- cloneWorkflow
708
+ createStep,
709
+ cloneStep(step, opts) {
710
+ return {
711
+ id: opts.id,
712
+ description: step.description,
713
+ inputSchema: step.inputSchema,
714
+ outputSchema: step.outputSchema,
715
+ resumeSchema: step.resumeSchema,
716
+ suspendSchema: step.suspendSchema,
717
+ stateSchema: step.stateSchema,
718
+ execute: step.execute,
719
+ component: step.component
720
+ };
721
+ },
722
+ cloneWorkflow(workflow, opts) {
723
+ const wf = new workflows.Workflow({
724
+ id: opts.id,
725
+ inputSchema: workflow.inputSchema,
726
+ outputSchema: workflow.outputSchema,
727
+ steps: workflow.stepDefs,
728
+ mastra: workflow.mastra
729
+ });
730
+ wf.setStepFlow(workflow.stepGraph);
731
+ wf.commit();
732
+ return wf;
733
+ }
272
734
  };
273
735
  }
274
- var InngestExecutionEngine = class extends vNext.DefaultExecutionEngine {
736
+ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
275
737
  inngestStep;
276
738
  inngestAttempts;
277
- constructor(mastra, inngestStep, inngestAttempts = 0) {
278
- super({ mastra });
739
+ constructor(mastra, inngestStep, inngestAttempts = 0, options) {
740
+ super({ mastra, options });
279
741
  this.inngestStep = inngestStep;
280
742
  this.inngestAttempts = inngestAttempts;
281
743
  }
282
- async fmtReturnValue(executionSpan, emitter, stepResults, lastOutput, error) {
744
+ async fmtReturnValue(emitter, stepResults, lastOutput, error) {
283
745
  const base = {
284
746
  status: lastOutput.status,
285
747
  steps: stepResults
286
748
  };
287
749
  if (lastOutput.status === "success") {
288
- await emitter.emit("watch", {
289
- type: "watch",
290
- payload: {
291
- workflowState: {
292
- status: lastOutput.status,
293
- steps: stepResults,
294
- result: lastOutput.output
295
- }
296
- },
297
- eventTimestamp: Date.now()
298
- });
299
750
  base.result = lastOutput.output;
300
751
  } else if (lastOutput.status === "failed") {
301
752
  base.error = error instanceof Error ? error?.stack ?? error.message : lastOutput?.error instanceof Error ? lastOutput.error.message : lastOutput.error ?? error ?? "Unknown error";
302
- await emitter.emit("watch", {
303
- type: "watch",
304
- payload: {
305
- workflowState: {
306
- status: lastOutput.status,
307
- steps: stepResults,
308
- result: null,
309
- error: base.error
310
- }
311
- },
312
- eventTimestamp: Date.now()
313
- });
314
753
  } else if (lastOutput.status === "suspended") {
315
- await emitter.emit("watch", {
316
- type: "watch",
317
- payload: {
318
- workflowState: {
319
- status: lastOutput.status,
320
- steps: stepResults,
321
- result: null,
322
- error: null
323
- }
324
- },
325
- eventTimestamp: Date.now()
326
- });
327
754
  const suspendedStepIds = Object.entries(stepResults).flatMap(([stepId, stepResult]) => {
328
755
  if (stepResult?.status === "suspended") {
329
- const nestedPath = stepResult?.payload?.__workflow_meta?.path;
756
+ const nestedPath = stepResult?.suspendPayload?.__workflow_meta?.path;
330
757
  return nestedPath ? [[stepId, ...nestedPath]] : [[stepId]];
331
758
  }
332
759
  return [];
333
760
  });
334
761
  base.suspended = suspendedStepIds;
335
762
  }
336
- executionSpan?.end();
337
763
  return base;
338
764
  }
339
- async superExecuteStep({
765
+ // async executeSleep({ id, duration }: { id: string; duration: number }): Promise<void> {
766
+ // await this.inngestStep.sleep(id, duration);
767
+ // }
768
+ async executeSleep({
340
769
  workflowId,
341
770
  runId,
342
- step,
771
+ entry,
772
+ prevOutput,
343
773
  stepResults,
774
+ emitter,
775
+ abortController,
776
+ requestContext,
344
777
  executionContext,
345
- resume,
778
+ writableStream,
779
+ tracingContext
780
+ }) {
781
+ let { duration, fn } = entry;
782
+ const sleepSpan = tracingContext?.currentSpan?.createChildSpan({
783
+ type: observability.AISpanType.WORKFLOW_SLEEP,
784
+ name: `sleep: ${duration ? `${duration}ms` : "dynamic"}`,
785
+ attributes: {
786
+ durationMs: duration,
787
+ sleepType: fn ? "dynamic" : "fixed"
788
+ },
789
+ tracingPolicy: this.options?.tracingPolicy
790
+ });
791
+ if (fn) {
792
+ const stepCallId = crypto.randomUUID();
793
+ duration = await this.inngestStep.run(`workflow.${workflowId}.sleep.${entry.id}`, async () => {
794
+ return await fn(
795
+ workflows.createDeprecationProxy(
796
+ {
797
+ runId,
798
+ workflowId,
799
+ mastra: this.mastra,
800
+ requestContext,
801
+ inputData: prevOutput,
802
+ state: executionContext.state,
803
+ setState: (state) => {
804
+ executionContext.state = state;
805
+ },
806
+ retryCount: -1,
807
+ tracingContext: {
808
+ currentSpan: sleepSpan
809
+ },
810
+ getInitData: () => stepResults?.input,
811
+ getStepResult: workflows.getStepResult.bind(this, stepResults),
812
+ // TODO: this function shouldn't have suspend probably?
813
+ suspend: async (_suspendPayload) => {
814
+ },
815
+ bail: () => {
816
+ },
817
+ abort: () => {
818
+ abortController?.abort();
819
+ },
820
+ [_constants.EMITTER_SYMBOL]: emitter,
821
+ [_constants.STREAM_FORMAT_SYMBOL]: executionContext.format,
822
+ engine: { step: this.inngestStep },
823
+ abortSignal: abortController?.signal,
824
+ writer: new tools.ToolStream(
825
+ {
826
+ prefix: "workflow-step",
827
+ callId: stepCallId,
828
+ name: "sleep",
829
+ runId
830
+ },
831
+ writableStream
832
+ )
833
+ },
834
+ {
835
+ paramName: "runCount",
836
+ deprecationMessage: workflows.runCountDeprecationMessage,
837
+ logger: this.logger
838
+ }
839
+ )
840
+ );
841
+ });
842
+ sleepSpan?.update({
843
+ attributes: {
844
+ durationMs: duration
845
+ }
846
+ });
847
+ }
848
+ try {
849
+ await this.inngestStep.sleep(entry.id, !duration || duration < 0 ? 0 : duration);
850
+ sleepSpan?.end();
851
+ } catch (e) {
852
+ sleepSpan?.error({ error: e });
853
+ throw e;
854
+ }
855
+ }
856
+ async executeSleepUntil({
857
+ workflowId,
858
+ runId,
859
+ entry,
346
860
  prevOutput,
861
+ stepResults,
347
862
  emitter,
348
- runtimeContext
863
+ abortController,
864
+ requestContext,
865
+ executionContext,
866
+ writableStream,
867
+ tracingContext
349
868
  }) {
350
- return super.executeStep({
351
- workflowId,
352
- runId,
353
- step,
354
- stepResults,
355
- executionContext,
356
- resume,
357
- prevOutput,
358
- emitter,
359
- runtimeContext
869
+ let { date, fn } = entry;
870
+ const sleepUntilSpan = tracingContext?.currentSpan?.createChildSpan({
871
+ type: observability.AISpanType.WORKFLOW_SLEEP,
872
+ name: `sleepUntil: ${date ? date.toISOString() : "dynamic"}`,
873
+ attributes: {
874
+ untilDate: date,
875
+ durationMs: date ? Math.max(0, date.getTime() - Date.now()) : void 0,
876
+ sleepType: fn ? "dynamic" : "fixed"
877
+ },
878
+ tracingPolicy: this.options?.tracingPolicy
360
879
  });
880
+ if (fn) {
881
+ date = await this.inngestStep.run(`workflow.${workflowId}.sleepUntil.${entry.id}`, async () => {
882
+ const stepCallId = crypto.randomUUID();
883
+ return await fn(
884
+ workflows.createDeprecationProxy(
885
+ {
886
+ runId,
887
+ workflowId,
888
+ mastra: this.mastra,
889
+ requestContext,
890
+ inputData: prevOutput,
891
+ state: executionContext.state,
892
+ setState: (state) => {
893
+ executionContext.state = state;
894
+ },
895
+ retryCount: -1,
896
+ tracingContext: {
897
+ currentSpan: sleepUntilSpan
898
+ },
899
+ getInitData: () => stepResults?.input,
900
+ getStepResult: workflows.getStepResult.bind(this, stepResults),
901
+ // TODO: this function shouldn't have suspend probably?
902
+ suspend: async (_suspendPayload) => {
903
+ },
904
+ bail: () => {
905
+ },
906
+ abort: () => {
907
+ abortController?.abort();
908
+ },
909
+ [_constants.EMITTER_SYMBOL]: emitter,
910
+ [_constants.STREAM_FORMAT_SYMBOL]: executionContext.format,
911
+ engine: { step: this.inngestStep },
912
+ abortSignal: abortController?.signal,
913
+ writer: new tools.ToolStream(
914
+ {
915
+ prefix: "workflow-step",
916
+ callId: stepCallId,
917
+ name: "sleep",
918
+ runId
919
+ },
920
+ writableStream
921
+ )
922
+ },
923
+ {
924
+ paramName: "runCount",
925
+ deprecationMessage: workflows.runCountDeprecationMessage,
926
+ logger: this.logger
927
+ }
928
+ )
929
+ );
930
+ });
931
+ if (date && !(date instanceof Date)) {
932
+ date = new Date(date);
933
+ }
934
+ const time = !date ? 0 : date.getTime() - Date.now();
935
+ sleepUntilSpan?.update({
936
+ attributes: {
937
+ durationMs: Math.max(0, time)
938
+ }
939
+ });
940
+ }
941
+ if (!(date instanceof Date)) {
942
+ sleepUntilSpan?.end();
943
+ return;
944
+ }
945
+ try {
946
+ await this.inngestStep.sleepUntil(entry.id, date);
947
+ sleepUntilSpan?.end();
948
+ } catch (e) {
949
+ sleepUntilSpan?.error({ error: e });
950
+ throw e;
951
+ }
952
+ }
953
+ async executeWaitForEvent({ event, timeout }) {
954
+ const eventData = await this.inngestStep.waitForEvent(`user-event-${event}`, {
955
+ event: `user-event-${event}`,
956
+ timeout: timeout ?? 5e3
957
+ });
958
+ if (eventData === null) {
959
+ throw "Timeout waiting for event";
960
+ }
961
+ return eventData?.data;
361
962
  }
362
963
  async executeStep({
363
964
  step,
@@ -366,91 +967,113 @@ var InngestExecutionEngine = class extends vNext.DefaultExecutionEngine {
366
967
  resume,
367
968
  prevOutput,
368
969
  emitter,
369
- runtimeContext
970
+ abortController,
971
+ requestContext,
972
+ tracingContext,
973
+ writableStream,
974
+ disableScorers
370
975
  }) {
371
- await this.inngestStep.run(
976
+ const stepAISpan = tracingContext?.currentSpan?.createChildSpan({
977
+ name: `workflow step: '${step.id}'`,
978
+ type: observability.AISpanType.WORKFLOW_STEP,
979
+ input: prevOutput,
980
+ attributes: {
981
+ stepId: step.id
982
+ },
983
+ tracingPolicy: this.options?.tracingPolicy
984
+ });
985
+ const { inputData, validationError } = await workflows.validateStepInput({
986
+ prevOutput,
987
+ step,
988
+ validateInputs: this.options?.validateInputs ?? false
989
+ });
990
+ const startedAt = await this.inngestStep.run(
372
991
  `workflow.${executionContext.workflowId}.run.${executionContext.runId}.step.${step.id}.running_ev`,
373
992
  async () => {
993
+ const startedAt2 = Date.now();
374
994
  await emitter.emit("watch", {
375
- type: "watch",
995
+ type: "workflow-step-start",
376
996
  payload: {
377
- currentStep: {
378
- id: step.id,
379
- status: "running"
380
- },
381
- workflowState: {
382
- status: "running",
383
- steps: {
384
- ...stepResults,
385
- [step.id]: {
386
- status: "running"
387
- }
388
- },
389
- result: null,
390
- error: null
391
- }
392
- },
393
- eventTimestamp: Date.now()
997
+ id: step.id,
998
+ status: "running",
999
+ payload: inputData,
1000
+ startedAt: startedAt2
1001
+ }
394
1002
  });
1003
+ return startedAt2;
395
1004
  }
396
1005
  );
397
1006
  if (step instanceof InngestWorkflow) {
398
1007
  const isResume = !!resume?.steps?.length;
399
1008
  let result;
400
1009
  let runId;
401
- if (isResume) {
402
- runId = stepResults[resume?.steps?.[0]]?.payload?.__workflow_meta?.runId ?? crypto.randomUUID();
403
- const snapshot = await this.mastra?.getStorage()?.loadWorkflowSnapshot({
404
- workflowName: step.id,
405
- runId
406
- });
407
- const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
408
- function: step.getFunction(),
409
- data: {
410
- inputData: prevOutput,
411
- runId,
412
- resume: {
1010
+ try {
1011
+ if (isResume) {
1012
+ runId = stepResults[resume?.steps?.[0]]?.suspendPayload?.__workflow_meta?.runId ?? crypto.randomUUID();
1013
+ const snapshot = await this.mastra?.getStorage()?.loadWorkflowSnapshot({
1014
+ workflowName: step.id,
1015
+ runId
1016
+ });
1017
+ const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
1018
+ function: step.getFunction(),
1019
+ data: {
1020
+ inputData,
1021
+ initialState: executionContext.state ?? snapshot?.value ?? {},
413
1022
  runId,
414
- steps: resume.steps.slice(1),
415
- stepResults: snapshot?.context,
416
- resumePayload: resume.resumePayload,
417
- // @ts-ignore
418
- resumePath: snapshot?.suspendedPaths?.[resume.steps?.[1]]
1023
+ resume: {
1024
+ runId,
1025
+ steps: resume.steps.slice(1),
1026
+ stepResults: snapshot?.context,
1027
+ resumePayload: resume.resumePayload,
1028
+ // @ts-ignore
1029
+ resumePath: snapshot?.suspendedPaths?.[resume.steps?.[1]]
1030
+ },
1031
+ outputOptions: { includeState: true }
419
1032
  }
420
- }
421
- });
422
- result = invokeResp.result;
423
- runId = invokeResp.runId;
424
- } else {
425
- const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
426
- function: step.getFunction(),
427
- data: {
428
- inputData: prevOutput
429
- }
430
- });
431
- result = invokeResp.result;
432
- runId = invokeResp.runId;
1033
+ });
1034
+ result = invokeResp.result;
1035
+ runId = invokeResp.runId;
1036
+ executionContext.state = invokeResp.result.state;
1037
+ } else {
1038
+ const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
1039
+ function: step.getFunction(),
1040
+ data: {
1041
+ inputData,
1042
+ initialState: executionContext.state ?? {},
1043
+ outputOptions: { includeState: true }
1044
+ }
1045
+ });
1046
+ result = invokeResp.result;
1047
+ runId = invokeResp.runId;
1048
+ executionContext.state = invokeResp.result.state;
1049
+ }
1050
+ } catch (e) {
1051
+ const errorCause = e?.cause;
1052
+ if (errorCause && typeof errorCause === "object") {
1053
+ result = errorCause;
1054
+ runId = errorCause.runId || crypto.randomUUID();
1055
+ } else {
1056
+ runId = crypto.randomUUID();
1057
+ result = {
1058
+ status: "failed",
1059
+ error: e instanceof Error ? e : new Error(String(e)),
1060
+ steps: {},
1061
+ input: inputData
1062
+ };
1063
+ }
433
1064
  }
434
1065
  const res = await this.inngestStep.run(
435
1066
  `workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
436
1067
  async () => {
437
1068
  if (result.status === "failed") {
438
1069
  await emitter.emit("watch", {
439
- type: "watch",
1070
+ type: "workflow-step-result",
440
1071
  payload: {
441
- currentStep: {
442
- id: step.id,
443
- status: "failed",
444
- error: result?.error
445
- },
446
- workflowState: {
447
- status: "running",
448
- steps: stepResults,
449
- result: null,
450
- error: null
451
- }
452
- },
453
- eventTimestamp: Date.now()
1072
+ id: step.id,
1073
+ status: "failed",
1074
+ error: result?.error,
1075
+ payload: prevOutput
1076
+ }
454
1077
  });
455
1078
  return { executionContext, result: { status: "failed", error: result?.error } };
456
1079
  } else if (result.status === "suspended") {
@@ -459,50 +1082,27 @@ var InngestExecutionEngine = class extends vNext.DefaultExecutionEngine {
459
1082
  return stepRes2?.status === "suspended";
460
1083
  });
461
1084
  for (const [stepName, stepResult] of suspendedSteps) {
462
- const suspendPath = [stepName, ...stepResult?.payload?.__workflow_meta?.path ?? []];
1085
+ const suspendPath = [stepName, ...stepResult?.suspendPayload?.__workflow_meta?.path ?? []];
463
1086
  executionContext.suspendedPaths[step.id] = executionContext.executionPath;
464
1087
  await emitter.emit("watch", {
465
- type: "watch",
1088
+ type: "workflow-step-suspended",
466
1089
  payload: {
467
- currentStep: {
468
- id: step.id,
469
- status: "suspended",
470
- payload: { ...stepResult?.payload, __workflow_meta: { runId, path: suspendPath } }
471
- },
472
- workflowState: {
473
- status: "running",
474
- steps: stepResults,
475
- result: null,
476
- error: null
477
- }
478
- },
479
- eventTimestamp: Date.now()
1090
+ id: step.id,
1091
+ status: "suspended"
1092
+ }
480
1093
  });
481
1094
  return {
482
1095
  executionContext,
483
1096
  result: {
484
1097
  status: "suspended",
485
- payload: { ...stepResult?.payload, __workflow_meta: { runId, path: suspendPath } }
1098
+ payload: stepResult.payload,
1099
+ suspendPayload: {
1100
+ ...stepResult?.suspendPayload,
1101
+ __workflow_meta: { runId, path: suspendPath }
1102
+ }
486
1103
  }
487
1104
  };
488
1105
  }
489
- await emitter.emit("watch", {
490
- type: "watch",
491
- payload: {
492
- currentStep: {
493
- id: step.id,
494
- status: "suspended",
495
- payload: {}
496
- },
497
- workflowState: {
498
- status: "running",
499
- steps: stepResults,
500
- result: null,
501
- error: null
502
- }
503
- },
504
- eventTimestamp: Date.now()
505
- });
506
1106
  return {
507
1107
  executionContext,
508
1108
  result: {
@@ -512,110 +1112,243 @@ var InngestExecutionEngine = class extends vNext.DefaultExecutionEngine {
512
1112
  };
513
1113
  }
514
1114
  await emitter.emit("watch", {
515
- type: "watch",
1115
+ type: "workflow-step-result",
516
1116
  payload: {
517
- currentStep: {
518
- id: step.id,
519
- status: "success",
520
- output: result?.result
521
- },
522
- workflowState: {
523
- status: "running",
524
- steps: stepResults,
525
- result: null,
526
- error: null
527
- }
528
- },
529
- eventTimestamp: Date.now()
1117
+ id: step.id,
1118
+ status: "success",
1119
+ output: result?.result
1120
+ }
1121
+ });
1122
+ await emitter.emit("watch", {
1123
+ type: "workflow-step-finish",
1124
+ payload: {
1125
+ id: step.id,
1126
+ metadata: {}
1127
+ }
530
1128
  });
531
1129
  return { executionContext, result: { status: "success", output: result?.result } };
532
1130
  }
533
1131
  );
534
1132
  Object.assign(executionContext, res.executionContext);
535
- return res.result;
1133
+ return {
1134
+ ...res.result,
1135
+ startedAt,
1136
+ endedAt: Date.now(),
1137
+ payload: inputData,
1138
+ resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1139
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1140
+ };
536
1141
  }
537
- const stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
538
- let execResults;
539
- let suspended;
540
- try {
541
- const result = await step.execute({
542
- mastra: this.mastra,
543
- runtimeContext,
544
- inputData: prevOutput,
545
- resumeData: resume?.steps[0] === step.id ? resume?.resumePayload : void 0,
546
- getInitData: () => stepResults?.input,
547
- getStepResult: (step2) => {
548
- const result2 = stepResults[step2.id];
549
- if (result2?.status === "success") {
550
- return result2.output;
1142
+ const stepCallId = crypto.randomUUID();
1143
+ let stepRes;
1144
+ try {
1145
+ stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
1146
+ let execResults;
1147
+ let suspended;
1148
+ let bailed;
1149
+ try {
1150
+ if (validationError) {
1151
+ throw validationError;
1152
+ }
1153
+ const result = await step.execute({
1154
+ runId: executionContext.runId,
1155
+ mastra: this.mastra,
1156
+ requestContext,
1157
+ writer: new tools.ToolStream(
1158
+ {
1159
+ prefix: "workflow-step",
1160
+ callId: stepCallId,
1161
+ name: step.id,
1162
+ runId: executionContext.runId
1163
+ },
1164
+ writableStream
1165
+ ),
1166
+ state: executionContext?.state ?? {},
1167
+ setState: (state) => {
1168
+ executionContext.state = state;
1169
+ },
1170
+ inputData,
1171
+ resumeData: resume?.steps[0] === step.id ? resume?.resumePayload : void 0,
1172
+ tracingContext: {
1173
+ currentSpan: stepAISpan
1174
+ },
1175
+ getInitData: () => stepResults?.input,
1176
+ getStepResult: workflows.getStepResult.bind(this, stepResults),
1177
+ suspend: async (suspendPayload, suspendOptions) => {
1178
+ executionContext.suspendedPaths[step.id] = executionContext.executionPath;
1179
+ if (suspendOptions?.resumeLabel) {
1180
+ const resumeLabel = Array.isArray(suspendOptions.resumeLabel) ? suspendOptions.resumeLabel : [suspendOptions.resumeLabel];
1181
+ for (const label of resumeLabel) {
1182
+ executionContext.resumeLabels[label] = {
1183
+ stepId: step.id,
1184
+ foreachIndex: executionContext.foreachIndex
1185
+ };
1186
+ }
1187
+ }
1188
+ suspended = { payload: suspendPayload };
1189
+ },
1190
+ bail: (result2) => {
1191
+ bailed = { payload: result2 };
1192
+ },
1193
+ resume: {
1194
+ steps: resume?.steps?.slice(1) || [],
1195
+ resumePayload: resume?.resumePayload,
1196
+ // @ts-ignore
1197
+ runId: stepResults[step.id]?.suspendPayload?.__workflow_meta?.runId
1198
+ },
1199
+ [_constants.EMITTER_SYMBOL]: emitter,
1200
+ [_constants.STREAM_FORMAT_SYMBOL]: executionContext.format,
1201
+ engine: {
1202
+ step: this.inngestStep
1203
+ },
1204
+ abortSignal: abortController.signal
1205
+ });
1206
+ const endedAt = Date.now();
1207
+ execResults = {
1208
+ status: "success",
1209
+ output: result,
1210
+ startedAt,
1211
+ endedAt,
1212
+ payload: inputData,
1213
+ resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1214
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1215
+ };
1216
+ } catch (e) {
1217
+ const stepFailure = {
1218
+ status: "failed",
1219
+ payload: inputData,
1220
+ error: e instanceof Error ? e.message : String(e),
1221
+ endedAt: Date.now(),
1222
+ startedAt,
1223
+ resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1224
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1225
+ };
1226
+ execResults = stepFailure;
1227
+ const fallbackErrorMessage = `Step ${step.id} failed`;
1228
+ stepAISpan?.error({ error: new Error(execResults.error ?? fallbackErrorMessage) });
1229
+ throw new inngest.RetryAfterError(execResults.error ?? fallbackErrorMessage, executionContext.retryConfig.delay, {
1230
+ cause: execResults
1231
+ });
1232
+ }
1233
+ if (suspended) {
1234
+ execResults = {
1235
+ status: "suspended",
1236
+ suspendPayload: suspended.payload,
1237
+ payload: inputData,
1238
+ suspendedAt: Date.now(),
1239
+ startedAt,
1240
+ resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1241
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1242
+ };
1243
+ } else if (bailed) {
1244
+ execResults = {
1245
+ status: "bailed",
1246
+ output: bailed.payload,
1247
+ payload: inputData,
1248
+ endedAt: Date.now(),
1249
+ startedAt
1250
+ };
1251
+ }
1252
+ if (execResults.status === "suspended") {
1253
+ await emitter.emit("watch", {
1254
+ type: "workflow-step-suspended",
1255
+ payload: {
1256
+ id: step.id,
1257
+ ...execResults
551
1258
  }
552
- return null;
553
- },
554
- suspend: async (suspendPayload) => {
555
- executionContext.suspendedPaths[step.id] = executionContext.executionPath;
556
- suspended = { payload: suspendPayload };
557
- },
558
- resume: {
559
- steps: resume?.steps?.slice(1) || [],
560
- resumePayload: resume?.resumePayload,
561
- // @ts-ignore
562
- runId: stepResults[step.id]?.payload?.__workflow_meta?.runId
563
- },
564
- emitter
565
- });
566
- execResults = { status: "success", output: result };
567
- } catch (e) {
568
- execResults = { status: "failed", error: e instanceof Error ? e.message : String(e) };
569
- }
570
- if (suspended) {
571
- execResults = { status: "suspended", payload: suspended.payload };
572
- }
573
- if (execResults.status === "failed") {
574
- if (executionContext.retryConfig.attempts > 0 && this.inngestAttempts < executionContext.retryConfig.attempts) {
575
- throw execResults.error;
1259
+ });
1260
+ } else {
1261
+ await emitter.emit("watch", {
1262
+ type: "workflow-step-result",
1263
+ payload: {
1264
+ id: step.id,
1265
+ ...execResults
1266
+ }
1267
+ });
1268
+ await emitter.emit("watch", {
1269
+ type: "workflow-step-finish",
1270
+ payload: {
1271
+ id: step.id,
1272
+ metadata: {}
1273
+ }
1274
+ });
576
1275
  }
577
- }
578
- await emitter.emit("watch", {
579
- type: "watch",
580
- payload: {
581
- currentStep: {
582
- id: step.id,
583
- status: execResults.status,
584
- output: execResults.output
585
- },
586
- workflowState: {
587
- status: "running",
588
- steps: stepResults,
589
- result: null,
590
- error: null
591
- }
592
- },
593
- eventTimestamp: Date.now()
1276
+ stepAISpan?.end({ output: execResults });
1277
+ return { result: execResults, executionContext, stepResults };
594
1278
  });
595
- return { result: execResults, executionContext, stepResults };
596
- });
1279
+ } catch (e) {
1280
+ const stepFailure = e instanceof Error ? e?.cause : {
1281
+ status: "failed",
1282
+ error: e instanceof Error ? e.message : String(e),
1283
+ payload: inputData,
1284
+ startedAt,
1285
+ endedAt: Date.now()
1286
+ };
1287
+ stepRes = {
1288
+ result: stepFailure,
1289
+ executionContext,
1290
+ stepResults: {
1291
+ ...stepResults,
1292
+ [step.id]: stepFailure
1293
+ }
1294
+ };
1295
+ }
1296
+ if (disableScorers !== false && stepRes.result.status === "success") {
1297
+ await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}.score`, async () => {
1298
+ if (step.scorers) {
1299
+ await this.runScorers({
1300
+ scorers: step.scorers,
1301
+ runId: executionContext.runId,
1302
+ input: inputData,
1303
+ output: stepRes.result,
1304
+ workflowId: executionContext.workflowId,
1305
+ stepId: step.id,
1306
+ requestContext,
1307
+ disableScorers,
1308
+ tracingContext: { currentSpan: stepAISpan }
1309
+ });
1310
+ }
1311
+ });
1312
+ }
597
1313
  Object.assign(executionContext.suspendedPaths, stepRes.executionContext.suspendedPaths);
598
1314
  Object.assign(stepResults, stepRes.stepResults);
1315
+ executionContext.state = stepRes.executionContext.state;
599
1316
  return stepRes.result;
600
1317
  }
601
1318
  async persistStepUpdate({
602
1319
  workflowId,
603
1320
  runId,
604
1321
  stepResults,
605
- executionContext
1322
+ resourceId,
1323
+ executionContext,
1324
+ serializedStepGraph,
1325
+ workflowStatus,
1326
+ result,
1327
+ error
606
1328
  }) {
607
1329
  await this.inngestStep.run(
608
1330
  `workflow.${workflowId}.run.${runId}.path.${JSON.stringify(executionContext.executionPath)}.stepUpdate`,
609
1331
  async () => {
1332
+ const shouldPersistSnapshot = this.options.shouldPersistSnapshot({ stepResults, workflowStatus });
1333
+ if (!shouldPersistSnapshot) {
1334
+ return;
1335
+ }
610
1336
  await this.mastra?.getStorage()?.persistWorkflowSnapshot({
611
1337
  workflowName: workflowId,
612
1338
  runId,
1339
+ resourceId,
613
1340
  snapshot: {
614
1341
  runId,
615
- value: {},
1342
+ value: executionContext.state,
616
1343
  context: stepResults,
617
1344
  activePaths: [],
618
1345
  suspendedPaths: executionContext.suspendedPaths,
1346
+ resumeLabels: executionContext.resumeLabels,
1347
+ waitingPaths: {},
1348
+ serializedStepGraph,
1349
+ status: workflowStatus,
1350
+ result,
1351
+ error,
619
1352
  // @ts-ignore
620
1353
  timestamp: Date.now()
621
1354
  }
@@ -628,53 +1361,123 @@ var InngestExecutionEngine = class extends vNext.DefaultExecutionEngine {
628
1361
  runId,
629
1362
  entry,
630
1363
  prevOutput,
631
- prevStep,
632
1364
  stepResults,
633
1365
  resume,
634
1366
  executionContext,
635
1367
  emitter,
636
- runtimeContext
1368
+ abortController,
1369
+ requestContext,
1370
+ writableStream,
1371
+ disableScorers,
1372
+ tracingContext
637
1373
  }) {
1374
+ const conditionalSpan = tracingContext?.currentSpan?.createChildSpan({
1375
+ type: observability.AISpanType.WORKFLOW_CONDITIONAL,
1376
+ name: `conditional: '${entry.conditions.length} conditions'`,
1377
+ input: prevOutput,
1378
+ attributes: {
1379
+ conditionCount: entry.conditions.length
1380
+ },
1381
+ tracingPolicy: this.options?.tracingPolicy
1382
+ });
638
1383
  let execResults;
639
1384
  const truthyIndexes = (await Promise.all(
640
1385
  entry.conditions.map(
641
1386
  (cond, index) => this.inngestStep.run(`workflow.${workflowId}.conditional.${index}`, async () => {
1387
+ const evalSpan = conditionalSpan?.createChildSpan({
1388
+ type: observability.AISpanType.WORKFLOW_CONDITIONAL_EVAL,
1389
+ name: `condition: '${index}'`,
1390
+ input: prevOutput,
1391
+ attributes: {
1392
+ conditionIndex: index
1393
+ },
1394
+ tracingPolicy: this.options?.tracingPolicy
1395
+ });
642
1396
  try {
643
- const result = await cond({
644
- mastra: this.mastra,
645
- runtimeContext,
646
- inputData: prevOutput,
647
- getInitData: () => stepResults?.input,
648
- getStepResult: (step) => {
649
- if (!step?.id) {
650
- return null;
651
- }
652
- const result2 = stepResults[step.id];
653
- if (result2?.status === "success") {
654
- return result2.output;
1397
+ const result = await cond(
1398
+ workflows.createDeprecationProxy(
1399
+ {
1400
+ runId,
1401
+ workflowId,
1402
+ mastra: this.mastra,
1403
+ requestContext,
1404
+ retryCount: -1,
1405
+ inputData: prevOutput,
1406
+ state: executionContext.state,
1407
+ setState: (state) => {
1408
+ executionContext.state = state;
1409
+ },
1410
+ tracingContext: {
1411
+ currentSpan: evalSpan
1412
+ },
1413
+ getInitData: () => stepResults?.input,
1414
+ getStepResult: workflows.getStepResult.bind(this, stepResults),
1415
+ // TODO: this function shouldn't have suspend probably?
1416
+ suspend: async (_suspendPayload) => {
1417
+ },
1418
+ bail: () => {
1419
+ },
1420
+ abort: () => {
1421
+ abortController.abort();
1422
+ },
1423
+ [_constants.EMITTER_SYMBOL]: emitter,
1424
+ [_constants.STREAM_FORMAT_SYMBOL]: executionContext.format,
1425
+ engine: {
1426
+ step: this.inngestStep
1427
+ },
1428
+ abortSignal: abortController.signal,
1429
+ writer: new tools.ToolStream(
1430
+ {
1431
+ prefix: "workflow-step",
1432
+ callId: crypto.randomUUID(),
1433
+ name: "conditional",
1434
+ runId
1435
+ },
1436
+ writableStream
1437
+ )
1438
+ },
1439
+ {
1440
+ paramName: "runCount",
1441
+ deprecationMessage: workflows.runCountDeprecationMessage,
1442
+ logger: this.logger
655
1443
  }
656
- return null;
657
- },
658
- // TODO: this function shouldn't have suspend probably?
659
- suspend: async (_suspendPayload) => {
660
- },
661
- emitter
1444
+ )
1445
+ );
1446
+ evalSpan?.end({
1447
+ output: result,
1448
+ attributes: {
1449
+ result: !!result
1450
+ }
662
1451
  });
663
1452
  return result ? index : null;
664
1453
  } catch (e) {
1454
+ evalSpan?.error({
1455
+ error: e instanceof Error ? e : new Error(String(e)),
1456
+ attributes: {
1457
+ result: false
1458
+ }
1459
+ });
665
1460
  return null;
666
1461
  }
667
1462
  })
668
1463
  )
669
1464
  )).filter((index) => index !== null);
670
1465
  const stepsToRun = entry.steps.filter((_, index) => truthyIndexes.includes(index));
1466
+ conditionalSpan?.update({
1467
+ attributes: {
1468
+ truthyIndexes,
1469
+ selectedSteps: stepsToRun.map((s) => s.type === "step" ? s.step.id : `control-${s.type}`)
1470
+ }
1471
+ });
671
1472
  const results = await Promise.all(
672
- stepsToRun.map(
673
- (step, index) => this.executeEntry({
674
- workflowId,
675
- runId,
676
- entry: step,
677
- prevStep,
1473
+ stepsToRun.map(async (step, index) => {
1474
+ const currStepResult = stepResults[step.step.id];
1475
+ if (currStepResult && currStepResult.status === "success") {
1476
+ return currStepResult;
1477
+ }
1478
+ const result = await this.executeStep({
1479
+ step: step.step,
1480
+ prevOutput,
678
1481
  stepResults,
679
1482
  resume,
680
1483
  executionContext: {
@@ -682,20 +1485,29 @@ var InngestExecutionEngine = class extends vNext.DefaultExecutionEngine {
682
1485
  runId,
683
1486
  executionPath: [...executionContext.executionPath, index],
684
1487
  suspendedPaths: executionContext.suspendedPaths,
1488
+ resumeLabels: executionContext.resumeLabels,
685
1489
  retryConfig: executionContext.retryConfig,
686
- executionSpan: executionContext.executionSpan
1490
+ state: executionContext.state
687
1491
  },
688
1492
  emitter,
689
- runtimeContext
690
- })
691
- )
1493
+ abortController,
1494
+ requestContext,
1495
+ writableStream,
1496
+ disableScorers,
1497
+ tracingContext: {
1498
+ currentSpan: conditionalSpan
1499
+ }
1500
+ });
1501
+ stepResults[step.step.id] = result;
1502
+ return result;
1503
+ })
692
1504
  );
693
1505
  const hasFailed = results.find((result) => result.status === "failed");
694
1506
  const hasSuspended = results.find((result) => result.status === "suspended");
695
1507
  if (hasFailed) {
696
1508
  execResults = { status: "failed", error: hasFailed.error };
697
1509
  } else if (hasSuspended) {
698
- execResults = { status: "suspended", payload: hasSuspended.payload };
1510
+ execResults = { status: "suspended", suspendPayload: hasSuspended.suspendPayload };
699
1511
  } else {
700
1512
  execResults = {
701
1513
  status: "success",
@@ -707,6 +1519,15 @@ var InngestExecutionEngine = class extends vNext.DefaultExecutionEngine {
707
1519
  }, {})
708
1520
  };
709
1521
  }
1522
+ if (execResults.status === "failed") {
1523
+ conditionalSpan?.error({
1524
+ error: new Error(execResults.error)
1525
+ });
1526
+ } else {
1527
+ conditionalSpan?.end({
1528
+ output: execResults.output || execResults
1529
+ });
1530
+ }
710
1531
  return execResults;
711
1532
  }
712
1533
  };
@@ -714,5 +1535,8 @@ var InngestExecutionEngine = class extends vNext.DefaultExecutionEngine {
714
1535
  exports.InngestExecutionEngine = InngestExecutionEngine;
715
1536
  exports.InngestRun = InngestRun;
716
1537
  exports.InngestWorkflow = InngestWorkflow;
1538
+ exports.createStep = createStep;
717
1539
  exports.init = init;
718
1540
  exports.serve = serve;
1541
+ //# sourceMappingURL=index.cjs.map
1542
+ //# sourceMappingURL=index.cjs.map