@mastra/inngest 0.0.0-vnext-inngest-20250508131921 → 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,40 +226,175 @@ 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
  }
149
- async getWorkflowRuns(args) {
385
+ async listWorkflowRuns(args) {
150
386
  const storage = this.#mastra?.getStorage();
151
387
  if (!storage) {
152
388
  this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
153
389
  return { runs: [], total: 0 };
154
390
  }
155
- return storage.getWorkflowRuns({ workflowName: this.id, ...args ?? {} });
391
+ return storage.listWorkflowRuns({ workflowName: this.id, ...args ?? {} });
156
392
  }
157
393
  async getWorkflowRunById(runId) {
158
394
  const storage = this.#mastra?.getStorage();
159
395
  if (!storage) {
160
396
  this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
161
- return null;
397
+ return this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null;
162
398
  }
163
399
  const run = await storage.getWorkflowRunById({ runId, workflowName: this.id });
164
400
  return run ?? (this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null);
@@ -181,21 +417,51 @@ var InngestWorkflow = class _InngestWorkflow extends vNext.NewWorkflow {
181
417
  }
182
418
  }
183
419
  }
184
- createRun(options) {
420
+ async createRun(options) {
185
421
  const runIdToUse = options?.runId || crypto.randomUUID();
186
422
  const run = this.runs.get(runIdToUse) ?? new InngestRun(
187
423
  {
188
424
  workflowId: this.id,
189
425
  runId: runIdToUse,
426
+ resourceId: options?.resourceId,
190
427
  executionEngine: this.executionEngine,
191
428
  executionGraph: this.executionGraph,
429
+ serializedStepGraph: this.serializedStepGraph,
192
430
  mastra: this.#mastra,
193
431
  retryConfig: this.retryConfig,
194
- cleanup: () => this.runs.delete(runIdToUse)
432
+ cleanup: () => this.runs.delete(runIdToUse),
433
+ workflowSteps: this.steps
195
434
  },
196
435
  this.inngest
197
436
  );
198
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
+ }
199
465
  return run;
200
466
  }
201
467
  getFunction() {
@@ -203,11 +469,17 @@ var InngestWorkflow = class _InngestWorkflow extends vNext.NewWorkflow {
203
469
  return this.function;
204
470
  }
205
471
  this.function = this.inngest.createFunction(
206
- // @ts-ignore
207
- { 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
+ },
208
480
  { event: `workflow.${this.id}` },
209
481
  async ({ event, step, attempt, publish }) => {
210
- let { inputData, runId, resume } = event.data;
482
+ let { inputData, initialState, runId, resourceId, resume, outputOptions, format } = event.data;
211
483
  if (!runId) {
212
484
  runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
213
485
  return crypto.randomUUID();
@@ -221,25 +493,52 @@ var InngestWorkflow = class _InngestWorkflow extends vNext.NewWorkflow {
221
493
  try {
222
494
  await publish({
223
495
  channel: `workflow:${this.id}:${runId}`,
224
- topic: "watch",
496
+ topic: event2,
225
497
  data
226
498
  });
227
499
  } catch (err) {
228
500
  this.logger.error("Error emitting event: " + (err?.stack ?? err?.message ?? err));
229
501
  }
502
+ },
503
+ on: (_event, _callback) => {
504
+ },
505
+ off: (_event, _callback) => {
506
+ },
507
+ once: (_event, _callback) => {
230
508
  }
231
509
  };
232
- const engine = new InngestExecutionEngine(this.#mastra, step, attempt);
510
+ const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
233
511
  const result = await engine.execute({
234
512
  workflowId: this.id,
235
513
  runId,
514
+ resourceId,
236
515
  graph: this.executionGraph,
516
+ serializedStepGraph: this.serializedStepGraph,
237
517
  input: inputData,
518
+ initialState,
238
519
  emitter,
239
520
  retryConfig: this.retryConfig,
240
- runtimeContext: new di.RuntimeContext(),
521
+ requestContext: new di.RequestContext(),
241
522
  // TODO
242
- 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;
243
542
  });
244
543
  return { result, runId };
245
544
  }
@@ -263,118 +562,403 @@ var InngestWorkflow = class _InngestWorkflow extends vNext.NewWorkflow {
263
562
  return [this.getFunction(), ...this.getNestedFunctions(this.executionGraph.steps)];
264
563
  }
265
564
  };
266
- function cloneWorkflow(workflow, opts) {
267
- const wf = new InngestWorkflow(
268
- {
269
- id: opts.id,
270
- inputSchema: workflow.inputSchema,
271
- outputSchema: workflow.outputSchema,
272
- steps: workflow.stepDefs,
273
- mastra: workflow.mastra
274
- },
275
- workflow.inngest
276
- );
277
- wf.setStepFlow(workflow.stepGraph);
278
- wf.commit();
279
- 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
+ };
280
699
  }
281
700
  function init(inngest) {
282
701
  return {
283
702
  createWorkflow(params) {
284
- return new InngestWorkflow(params, inngest);
703
+ return new InngestWorkflow(
704
+ params,
705
+ inngest
706
+ );
707
+ },
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
+ };
285
721
  },
286
- createStep: vNext.createStep,
287
- cloneStep: vNext.cloneStep,
288
- cloneWorkflow
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
+ }
289
734
  };
290
735
  }
291
- var InngestExecutionEngine = class extends vNext.DefaultExecutionEngine {
736
+ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
292
737
  inngestStep;
293
738
  inngestAttempts;
294
- constructor(mastra, inngestStep, inngestAttempts = 0) {
295
- super({ mastra });
739
+ constructor(mastra, inngestStep, inngestAttempts = 0, options) {
740
+ super({ mastra, options });
296
741
  this.inngestStep = inngestStep;
297
742
  this.inngestAttempts = inngestAttempts;
298
743
  }
299
- async fmtReturnValue(executionSpan, emitter, stepResults, lastOutput, error) {
744
+ async fmtReturnValue(emitter, stepResults, lastOutput, error) {
300
745
  const base = {
301
746
  status: lastOutput.status,
302
747
  steps: stepResults
303
748
  };
304
749
  if (lastOutput.status === "success") {
305
- await emitter.emit("watch", {
306
- type: "watch",
307
- payload: {
308
- workflowState: {
309
- status: lastOutput.status,
310
- steps: stepResults,
311
- result: lastOutput.output
312
- }
313
- },
314
- eventTimestamp: Date.now()
315
- });
316
750
  base.result = lastOutput.output;
317
751
  } else if (lastOutput.status === "failed") {
318
752
  base.error = error instanceof Error ? error?.stack ?? error.message : lastOutput?.error instanceof Error ? lastOutput.error.message : lastOutput.error ?? error ?? "Unknown error";
319
- await emitter.emit("watch", {
320
- type: "watch",
321
- payload: {
322
- workflowState: {
323
- status: lastOutput.status,
324
- steps: stepResults,
325
- result: null,
326
- error: base.error
327
- }
328
- },
329
- eventTimestamp: Date.now()
330
- });
331
753
  } else if (lastOutput.status === "suspended") {
332
- await emitter.emit("watch", {
333
- type: "watch",
334
- payload: {
335
- workflowState: {
336
- status: lastOutput.status,
337
- steps: stepResults,
338
- result: null,
339
- error: null
340
- }
341
- },
342
- eventTimestamp: Date.now()
343
- });
344
754
  const suspendedStepIds = Object.entries(stepResults).flatMap(([stepId, stepResult]) => {
345
755
  if (stepResult?.status === "suspended") {
346
- const nestedPath = stepResult?.payload?.__workflow_meta?.path;
756
+ const nestedPath = stepResult?.suspendPayload?.__workflow_meta?.path;
347
757
  return nestedPath ? [[stepId, ...nestedPath]] : [[stepId]];
348
758
  }
349
759
  return [];
350
760
  });
351
761
  base.suspended = suspendedStepIds;
352
762
  }
353
- executionSpan?.end();
354
763
  return base;
355
764
  }
356
- async superExecuteStep({
765
+ // async executeSleep({ id, duration }: { id: string; duration: number }): Promise<void> {
766
+ // await this.inngestStep.sleep(id, duration);
767
+ // }
768
+ async executeSleep({
357
769
  workflowId,
358
770
  runId,
359
- step,
771
+ entry,
772
+ prevOutput,
360
773
  stepResults,
774
+ emitter,
775
+ abortController,
776
+ requestContext,
361
777
  executionContext,
362
- 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,
363
860
  prevOutput,
861
+ stepResults,
364
862
  emitter,
365
- runtimeContext
863
+ abortController,
864
+ requestContext,
865
+ executionContext,
866
+ writableStream,
867
+ tracingContext
366
868
  }) {
367
- return super.executeStep({
368
- workflowId,
369
- runId,
370
- step,
371
- stepResults,
372
- executionContext,
373
- resume,
374
- prevOutput,
375
- emitter,
376
- 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
377
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;
378
962
  }
379
963
  async executeStep({
380
964
  step,
@@ -383,91 +967,113 @@ var InngestExecutionEngine = class extends vNext.DefaultExecutionEngine {
383
967
  resume,
384
968
  prevOutput,
385
969
  emitter,
386
- runtimeContext
970
+ abortController,
971
+ requestContext,
972
+ tracingContext,
973
+ writableStream,
974
+ disableScorers
387
975
  }) {
388
- 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(
389
991
  `workflow.${executionContext.workflowId}.run.${executionContext.runId}.step.${step.id}.running_ev`,
390
992
  async () => {
993
+ const startedAt2 = Date.now();
391
994
  await emitter.emit("watch", {
392
- type: "watch",
995
+ type: "workflow-step-start",
393
996
  payload: {
394
- currentStep: {
395
- id: step.id,
396
- status: "running"
397
- },
398
- workflowState: {
399
- status: "running",
400
- steps: {
401
- ...stepResults,
402
- [step.id]: {
403
- status: "running"
404
- }
405
- },
406
- result: null,
407
- error: null
408
- }
409
- },
410
- eventTimestamp: Date.now()
997
+ id: step.id,
998
+ status: "running",
999
+ payload: inputData,
1000
+ startedAt: startedAt2
1001
+ }
411
1002
  });
1003
+ return startedAt2;
412
1004
  }
413
1005
  );
414
1006
  if (step instanceof InngestWorkflow) {
415
1007
  const isResume = !!resume?.steps?.length;
416
1008
  let result;
417
1009
  let runId;
418
- if (isResume) {
419
- runId = stepResults[resume?.steps?.[0]]?.payload?.__workflow_meta?.runId ?? crypto.randomUUID();
420
- const snapshot = await this.mastra?.getStorage()?.loadWorkflowSnapshot({
421
- workflowName: step.id,
422
- runId
423
- });
424
- const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
425
- function: step.getFunction(),
426
- data: {
427
- inputData: prevOutput,
428
- runId,
429
- 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 ?? {},
430
1022
  runId,
431
- steps: resume.steps.slice(1),
432
- stepResults: snapshot?.context,
433
- resumePayload: resume.resumePayload,
434
- // @ts-ignore
435
- 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 }
436
1032
  }
437
- }
438
- });
439
- result = invokeResp.result;
440
- runId = invokeResp.runId;
441
- } else {
442
- const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
443
- function: step.getFunction(),
444
- data: {
445
- inputData: prevOutput
446
- }
447
- });
448
- result = invokeResp.result;
449
- 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
+ }
450
1064
  }
451
1065
  const res = await this.inngestStep.run(
452
1066
  `workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
453
1067
  async () => {
454
1068
  if (result.status === "failed") {
455
1069
  await emitter.emit("watch", {
456
- type: "watch",
1070
+ type: "workflow-step-result",
457
1071
  payload: {
458
- currentStep: {
459
- id: step.id,
460
- status: "failed",
461
- error: result?.error
462
- },
463
- workflowState: {
464
- status: "running",
465
- steps: stepResults,
466
- result: null,
467
- error: null
468
- }
469
- },
470
- eventTimestamp: Date.now()
1072
+ id: step.id,
1073
+ status: "failed",
1074
+ error: result?.error,
1075
+ payload: prevOutput
1076
+ }
471
1077
  });
472
1078
  return { executionContext, result: { status: "failed", error: result?.error } };
473
1079
  } else if (result.status === "suspended") {
@@ -476,50 +1082,27 @@ var InngestExecutionEngine = class extends vNext.DefaultExecutionEngine {
476
1082
  return stepRes2?.status === "suspended";
477
1083
  });
478
1084
  for (const [stepName, stepResult] of suspendedSteps) {
479
- const suspendPath = [stepName, ...stepResult?.payload?.__workflow_meta?.path ?? []];
1085
+ const suspendPath = [stepName, ...stepResult?.suspendPayload?.__workflow_meta?.path ?? []];
480
1086
  executionContext.suspendedPaths[step.id] = executionContext.executionPath;
481
1087
  await emitter.emit("watch", {
482
- type: "watch",
1088
+ type: "workflow-step-suspended",
483
1089
  payload: {
484
- currentStep: {
485
- id: step.id,
486
- status: "suspended",
487
- payload: { ...stepResult?.payload, __workflow_meta: { runId, path: suspendPath } }
488
- },
489
- workflowState: {
490
- status: "running",
491
- steps: stepResults,
492
- result: null,
493
- error: null
494
- }
495
- },
496
- eventTimestamp: Date.now()
1090
+ id: step.id,
1091
+ status: "suspended"
1092
+ }
497
1093
  });
498
1094
  return {
499
1095
  executionContext,
500
1096
  result: {
501
1097
  status: "suspended",
502
- payload: { ...stepResult?.payload, __workflow_meta: { runId, path: suspendPath } }
1098
+ payload: stepResult.payload,
1099
+ suspendPayload: {
1100
+ ...stepResult?.suspendPayload,
1101
+ __workflow_meta: { runId, path: suspendPath }
1102
+ }
503
1103
  }
504
1104
  };
505
1105
  }
506
- await emitter.emit("watch", {
507
- type: "watch",
508
- payload: {
509
- currentStep: {
510
- id: step.id,
511
- status: "suspended",
512
- payload: {}
513
- },
514
- workflowState: {
515
- status: "running",
516
- steps: stepResults,
517
- result: null,
518
- error: null
519
- }
520
- },
521
- eventTimestamp: Date.now()
522
- });
523
1106
  return {
524
1107
  executionContext,
525
1108
  result: {
@@ -529,110 +1112,243 @@ var InngestExecutionEngine = class extends vNext.DefaultExecutionEngine {
529
1112
  };
530
1113
  }
531
1114
  await emitter.emit("watch", {
532
- type: "watch",
1115
+ type: "workflow-step-result",
533
1116
  payload: {
534
- currentStep: {
535
- id: step.id,
536
- status: "success",
537
- output: result?.result
538
- },
539
- workflowState: {
540
- status: "running",
541
- steps: stepResults,
542
- result: null,
543
- error: null
544
- }
545
- },
546
- 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
+ }
547
1128
  });
548
1129
  return { executionContext, result: { status: "success", output: result?.result } };
549
1130
  }
550
1131
  );
551
1132
  Object.assign(executionContext, res.executionContext);
552
- 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
+ };
553
1141
  }
554
- const stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
555
- let execResults;
556
- let suspended;
557
- try {
558
- const result = await step.execute({
559
- mastra: this.mastra,
560
- runtimeContext,
561
- inputData: prevOutput,
562
- resumeData: resume?.steps[0] === step.id ? resume?.resumePayload : void 0,
563
- getInitData: () => stepResults?.input,
564
- getStepResult: (step2) => {
565
- const result2 = stepResults[step2.id];
566
- if (result2?.status === "success") {
567
- 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
568
1258
  }
569
- return null;
570
- },
571
- suspend: async (suspendPayload) => {
572
- executionContext.suspendedPaths[step.id] = executionContext.executionPath;
573
- suspended = { payload: suspendPayload };
574
- },
575
- resume: {
576
- steps: resume?.steps?.slice(1) || [],
577
- resumePayload: resume?.resumePayload,
578
- // @ts-ignore
579
- runId: stepResults[step.id]?.payload?.__workflow_meta?.runId
580
- },
581
- emitter
582
- });
583
- execResults = { status: "success", output: result };
584
- } catch (e) {
585
- execResults = { status: "failed", error: e instanceof Error ? e.message : String(e) };
586
- }
587
- if (suspended) {
588
- execResults = { status: "suspended", payload: suspended.payload };
589
- }
590
- if (execResults.status === "failed") {
591
- if (executionContext.retryConfig.attempts > 0 && this.inngestAttempts < executionContext.retryConfig.attempts) {
592
- 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
+ });
593
1275
  }
594
- }
595
- await emitter.emit("watch", {
596
- type: "watch",
597
- payload: {
598
- currentStep: {
599
- id: step.id,
600
- status: execResults.status,
601
- output: execResults.output
602
- },
603
- workflowState: {
604
- status: "running",
605
- steps: stepResults,
606
- result: null,
607
- error: null
608
- }
609
- },
610
- eventTimestamp: Date.now()
1276
+ stepAISpan?.end({ output: execResults });
1277
+ return { result: execResults, executionContext, stepResults };
611
1278
  });
612
- return { result: execResults, executionContext, stepResults };
613
- });
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
+ }
614
1313
  Object.assign(executionContext.suspendedPaths, stepRes.executionContext.suspendedPaths);
615
1314
  Object.assign(stepResults, stepRes.stepResults);
1315
+ executionContext.state = stepRes.executionContext.state;
616
1316
  return stepRes.result;
617
1317
  }
618
1318
  async persistStepUpdate({
619
1319
  workflowId,
620
1320
  runId,
621
1321
  stepResults,
622
- executionContext
1322
+ resourceId,
1323
+ executionContext,
1324
+ serializedStepGraph,
1325
+ workflowStatus,
1326
+ result,
1327
+ error
623
1328
  }) {
624
1329
  await this.inngestStep.run(
625
1330
  `workflow.${workflowId}.run.${runId}.path.${JSON.stringify(executionContext.executionPath)}.stepUpdate`,
626
1331
  async () => {
1332
+ const shouldPersistSnapshot = this.options.shouldPersistSnapshot({ stepResults, workflowStatus });
1333
+ if (!shouldPersistSnapshot) {
1334
+ return;
1335
+ }
627
1336
  await this.mastra?.getStorage()?.persistWorkflowSnapshot({
628
1337
  workflowName: workflowId,
629
1338
  runId,
1339
+ resourceId,
630
1340
  snapshot: {
631
1341
  runId,
632
- value: {},
1342
+ value: executionContext.state,
633
1343
  context: stepResults,
634
1344
  activePaths: [],
635
1345
  suspendedPaths: executionContext.suspendedPaths,
1346
+ resumeLabels: executionContext.resumeLabels,
1347
+ waitingPaths: {},
1348
+ serializedStepGraph,
1349
+ status: workflowStatus,
1350
+ result,
1351
+ error,
636
1352
  // @ts-ignore
637
1353
  timestamp: Date.now()
638
1354
  }
@@ -645,53 +1361,123 @@ var InngestExecutionEngine = class extends vNext.DefaultExecutionEngine {
645
1361
  runId,
646
1362
  entry,
647
1363
  prevOutput,
648
- prevStep,
649
1364
  stepResults,
650
1365
  resume,
651
1366
  executionContext,
652
1367
  emitter,
653
- runtimeContext
1368
+ abortController,
1369
+ requestContext,
1370
+ writableStream,
1371
+ disableScorers,
1372
+ tracingContext
654
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
+ });
655
1383
  let execResults;
656
1384
  const truthyIndexes = (await Promise.all(
657
1385
  entry.conditions.map(
658
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
+ });
659
1396
  try {
660
- const result = await cond({
661
- mastra: this.mastra,
662
- runtimeContext,
663
- inputData: prevOutput,
664
- getInitData: () => stepResults?.input,
665
- getStepResult: (step) => {
666
- if (!step?.id) {
667
- return null;
668
- }
669
- const result2 = stepResults[step.id];
670
- if (result2?.status === "success") {
671
- 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
672
1443
  }
673
- return null;
674
- },
675
- // TODO: this function shouldn't have suspend probably?
676
- suspend: async (_suspendPayload) => {
677
- },
678
- emitter
1444
+ )
1445
+ );
1446
+ evalSpan?.end({
1447
+ output: result,
1448
+ attributes: {
1449
+ result: !!result
1450
+ }
679
1451
  });
680
1452
  return result ? index : null;
681
1453
  } catch (e) {
1454
+ evalSpan?.error({
1455
+ error: e instanceof Error ? e : new Error(String(e)),
1456
+ attributes: {
1457
+ result: false
1458
+ }
1459
+ });
682
1460
  return null;
683
1461
  }
684
1462
  })
685
1463
  )
686
1464
  )).filter((index) => index !== null);
687
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
+ });
688
1472
  const results = await Promise.all(
689
- stepsToRun.map(
690
- (step, index) => this.executeEntry({
691
- workflowId,
692
- runId,
693
- entry: step,
694
- 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,
695
1481
  stepResults,
696
1482
  resume,
697
1483
  executionContext: {
@@ -699,20 +1485,29 @@ var InngestExecutionEngine = class extends vNext.DefaultExecutionEngine {
699
1485
  runId,
700
1486
  executionPath: [...executionContext.executionPath, index],
701
1487
  suspendedPaths: executionContext.suspendedPaths,
1488
+ resumeLabels: executionContext.resumeLabels,
702
1489
  retryConfig: executionContext.retryConfig,
703
- executionSpan: executionContext.executionSpan
1490
+ state: executionContext.state
704
1491
  },
705
1492
  emitter,
706
- runtimeContext
707
- })
708
- )
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
+ })
709
1504
  );
710
1505
  const hasFailed = results.find((result) => result.status === "failed");
711
1506
  const hasSuspended = results.find((result) => result.status === "suspended");
712
1507
  if (hasFailed) {
713
1508
  execResults = { status: "failed", error: hasFailed.error };
714
1509
  } else if (hasSuspended) {
715
- execResults = { status: "suspended", payload: hasSuspended.payload };
1510
+ execResults = { status: "suspended", suspendPayload: hasSuspended.suspendPayload };
716
1511
  } else {
717
1512
  execResults = {
718
1513
  status: "success",
@@ -724,6 +1519,15 @@ var InngestExecutionEngine = class extends vNext.DefaultExecutionEngine {
724
1519
  }, {})
725
1520
  };
726
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
+ }
727
1531
  return execResults;
728
1532
  }
729
1533
  };
@@ -731,5 +1535,8 @@ var InngestExecutionEngine = class extends vNext.DefaultExecutionEngine {
731
1535
  exports.InngestExecutionEngine = InngestExecutionEngine;
732
1536
  exports.InngestRun = InngestRun;
733
1537
  exports.InngestWorkflow = InngestWorkflow;
1538
+ exports.createStep = createStep;
734
1539
  exports.init = init;
735
1540
  exports.serve = serve;
1541
+ //# sourceMappingURL=index.cjs.map
1542
+ //# sourceMappingURL=index.cjs.map