@mastra/inngest 0.0.0-course-20250527170450 → 0.0.0-createToolOptions-20250926094418

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.js CHANGED
@@ -1,35 +1,50 @@
1
1
  import { randomUUID } from 'crypto';
2
2
  import { subscribe } from '@inngest/realtime';
3
+ import { wrapMastra, AISpanType } from '@mastra/core/ai-tracing';
3
4
  import { RuntimeContext } from '@mastra/core/di';
4
- import { Run, Workflow, cloneStep, createStep, DefaultExecutionEngine } from '@mastra/core/workflows';
5
- import { EMITTER_SYMBOL } from '@mastra/core/workflows/_constants';
5
+ import { ToolStream, Tool } from '@mastra/core/tools';
6
+ import { Run, Workflow, DefaultExecutionEngine, getStepResult, validateStepInput } from '@mastra/core/workflows';
7
+ import { EMITTER_SYMBOL, STREAM_FORMAT_SYMBOL } from '@mastra/core/workflows/_constants';
6
8
  import { serve as serve$1 } from 'inngest/hono';
9
+ import { z } from 'zod';
7
10
 
8
11
  // src/index.ts
9
- function serve({ mastra, inngest }) {
12
+ function serve({
13
+ mastra,
14
+ inngest,
15
+ functions: userFunctions = [],
16
+ registerOptions
17
+ }) {
10
18
  const wfs = mastra.getWorkflows();
11
- const functions = Object.values(wfs).flatMap((wf) => {
12
- if (wf instanceof InngestWorkflow) {
13
- wf.__registerMastra(mastra);
14
- return wf.getFunctions();
15
- }
16
- return [];
17
- });
19
+ const workflowFunctions = Array.from(
20
+ new Set(
21
+ Object.values(wfs).flatMap((wf) => {
22
+ if (wf instanceof InngestWorkflow) {
23
+ wf.__registerMastra(mastra);
24
+ return wf.getFunctions();
25
+ }
26
+ return [];
27
+ })
28
+ )
29
+ );
18
30
  return serve$1({
31
+ ...registerOptions,
19
32
  client: inngest,
20
- functions
33
+ functions: [...workflowFunctions, ...userFunctions]
21
34
  });
22
35
  }
23
36
  var InngestRun = class extends Run {
24
37
  inngest;
38
+ serializedStepGraph;
25
39
  #mastra;
26
40
  constructor(params, inngest) {
27
41
  super(params);
28
42
  this.inngest = inngest;
43
+ this.serializedStepGraph = params.serializedStepGraph;
29
44
  this.#mastra = params.mastra;
30
45
  }
31
46
  async getRuns(eventId) {
32
- const response = await fetch(`${this.inngest.apiBaseUrl}/v1/events/${eventId}/runs`, {
47
+ const response = await fetch(`${this.inngest.apiBaseUrl ?? "https://api.inngest.com"}/v1/events/${eventId}/runs`, {
33
48
  headers: {
34
49
  Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`
35
50
  }
@@ -39,35 +54,76 @@ var InngestRun = class extends Run {
39
54
  }
40
55
  async getRunOutput(eventId) {
41
56
  let runs = await this.getRuns(eventId);
42
- while (runs?.[0]?.status !== "Completed") {
57
+ while (runs?.[0]?.status !== "Completed" || runs?.[0]?.event_id !== eventId) {
43
58
  await new Promise((resolve) => setTimeout(resolve, 1e3));
44
59
  runs = await this.getRuns(eventId);
45
- if (runs?.[0]?.status === "Failed" || runs?.[0]?.status === "Cancelled") {
60
+ if (runs?.[0]?.status === "Failed") {
46
61
  throw new Error(`Function run ${runs?.[0]?.status}`);
62
+ } else if (runs?.[0]?.status === "Cancelled") {
63
+ const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
64
+ workflowName: this.workflowId,
65
+ runId: this.runId
66
+ });
67
+ return { output: { result: { steps: snapshot?.context, status: "canceled" } } };
47
68
  }
48
69
  }
49
70
  return runs?.[0];
50
71
  }
72
+ async sendEvent(event, data) {
73
+ await this.inngest.send({
74
+ name: `user-event-${event}`,
75
+ data
76
+ });
77
+ }
78
+ async cancel() {
79
+ await this.inngest.send({
80
+ name: `cancel.workflow.${this.workflowId}`,
81
+ data: {
82
+ runId: this.runId
83
+ }
84
+ });
85
+ const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
86
+ workflowName: this.workflowId,
87
+ runId: this.runId
88
+ });
89
+ if (snapshot) {
90
+ await this.#mastra?.storage?.persistWorkflowSnapshot({
91
+ workflowName: this.workflowId,
92
+ runId: this.runId,
93
+ resourceId: this.resourceId,
94
+ snapshot: {
95
+ ...snapshot,
96
+ status: "canceled"
97
+ }
98
+ });
99
+ }
100
+ }
51
101
  async start({
52
102
  inputData
53
103
  }) {
54
104
  await this.#mastra.getStorage()?.persistWorkflowSnapshot({
55
105
  workflowName: this.workflowId,
56
106
  runId: this.runId,
107
+ resourceId: this.resourceId,
57
108
  snapshot: {
58
109
  runId: this.runId,
110
+ serializedStepGraph: this.serializedStepGraph,
59
111
  value: {},
60
112
  context: {},
61
113
  activePaths: [],
62
114
  suspendedPaths: {},
63
- timestamp: Date.now()
115
+ waitingPaths: {},
116
+ timestamp: Date.now(),
117
+ status: "running"
64
118
  }
65
119
  });
120
+ const inputDataToUse = await this._validateInput(inputData);
66
121
  const eventOutput = await this.inngest.send({
67
122
  name: `workflow.${this.workflowId}`,
68
123
  data: {
69
- inputData,
70
- runId: this.runId
124
+ inputData: inputDataToUse,
125
+ runId: this.runId,
126
+ resourceId: this.resourceId
71
127
  }
72
128
  });
73
129
  const eventId = eventOutput.ids[0];
@@ -79,10 +135,23 @@ var InngestRun = class extends Run {
79
135
  if (result.status === "failed") {
80
136
  result.error = new Error(result.error);
81
137
  }
82
- this.cleanup?.();
138
+ if (result.status !== "suspended") {
139
+ this.cleanup?.();
140
+ }
83
141
  return result;
84
142
  }
85
143
  async resume(params) {
144
+ const p = this._resume(params).then((result) => {
145
+ if (result.status !== "suspended") {
146
+ this.closeStreamAction?.().catch(() => {
147
+ });
148
+ }
149
+ return result;
150
+ });
151
+ this.executionResults = p;
152
+ return p;
153
+ }
154
+ async _resume(params) {
86
155
  const steps = (Array.isArray(params.step) ? params.step : [params.step]).map(
87
156
  (step) => typeof step === "string" ? step : step?.id
88
157
  );
@@ -90,16 +159,19 @@ var InngestRun = class extends Run {
90
159
  workflowName: this.workflowId,
91
160
  runId: this.runId
92
161
  });
162
+ const suspendedStep = this.workflowSteps[steps?.[0] ?? ""];
163
+ const resumeDataToUse = await this._validateResumeData(params.resumeData, suspendedStep);
93
164
  const eventOutput = await this.inngest.send({
94
165
  name: `workflow.${this.workflowId}`,
95
166
  data: {
96
- inputData: params.resumeData,
167
+ inputData: resumeDataToUse,
97
168
  runId: this.runId,
169
+ workflowId: this.workflowId,
98
170
  stepResults: snapshot?.context,
99
171
  resume: {
100
172
  steps,
101
173
  stepResults: snapshot?.context,
102
- resumePayload: params.resumeData,
174
+ resumePayload: resumeDataToUse,
103
175
  // @ts-ignore
104
176
  resumePath: snapshot?.suspendedPaths?.[steps?.[0]]
105
177
  }
@@ -116,32 +188,77 @@ var InngestRun = class extends Run {
116
188
  }
117
189
  return result;
118
190
  }
119
- watch(cb) {
191
+ watch(cb, type = "watch") {
192
+ let active = true;
120
193
  const streamPromise = subscribe(
121
194
  {
122
195
  channel: `workflow:${this.workflowId}:${this.runId}`,
123
- topics: ["watch"],
196
+ topics: [type],
124
197
  app: this.inngest
125
198
  },
126
199
  (message) => {
127
- cb(message.data);
200
+ if (active) {
201
+ cb(message.data);
202
+ }
128
203
  }
129
204
  );
130
205
  return () => {
131
- streamPromise.then((stream) => {
132
- stream.cancel();
206
+ active = false;
207
+ streamPromise.then(async (stream) => {
208
+ return stream.cancel();
133
209
  }).catch((err) => {
134
210
  console.error(err);
135
211
  });
136
212
  };
137
213
  }
214
+ stream({ inputData, runtimeContext } = {}) {
215
+ const { readable, writable } = new TransformStream();
216
+ const writer = writable.getWriter();
217
+ const unwatch = this.watch(async (event) => {
218
+ try {
219
+ const e = {
220
+ ...event,
221
+ type: event.type.replace("workflow-", "")
222
+ };
223
+ await writer.write(e);
224
+ } catch {
225
+ }
226
+ }, "watch-v2");
227
+ this.closeStreamAction = async () => {
228
+ unwatch();
229
+ try {
230
+ await writer.close();
231
+ } catch (err) {
232
+ console.error("Error closing stream:", err);
233
+ } finally {
234
+ writer.releaseLock();
235
+ }
236
+ };
237
+ this.executionResults = this.start({ inputData, runtimeContext }).then((result) => {
238
+ if (result.status !== "suspended") {
239
+ this.closeStreamAction?.().catch(() => {
240
+ });
241
+ }
242
+ return result;
243
+ });
244
+ return {
245
+ stream: readable,
246
+ getWorkflowState: () => this.executionResults
247
+ };
248
+ }
138
249
  };
139
250
  var InngestWorkflow = class _InngestWorkflow extends Workflow {
140
251
  #mastra;
141
252
  inngest;
142
253
  function;
254
+ flowControlConfig;
143
255
  constructor(params, inngest) {
144
- super(params);
256
+ const { concurrency, rateLimit, throttle, debounce, priority, ...workflowParams } = params;
257
+ super(workflowParams);
258
+ const flowControlEntries = Object.entries({ concurrency, rateLimit, throttle, debounce, priority }).filter(
259
+ ([_, value]) => value !== void 0
260
+ );
261
+ this.flowControlConfig = flowControlEntries.length > 0 ? Object.fromEntries(flowControlEntries) : void 0;
145
262
  this.#mastra = params.mastra;
146
263
  this.inngest = inngest;
147
264
  }
@@ -157,7 +274,7 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
157
274
  const storage = this.#mastra?.getStorage();
158
275
  if (!storage) {
159
276
  this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
160
- return null;
277
+ return this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null;
161
278
  }
162
279
  const run = await storage.getWorkflowRunById({ runId, workflowName: this.id });
163
280
  return run ?? (this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null);
@@ -180,21 +297,55 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
180
297
  }
181
298
  }
182
299
  }
183
- createRun(options) {
300
+ /**
301
+ * @deprecated Use createRunAsync() instead.
302
+ * @throws {Error} Always throws an error directing users to use createRunAsync()
303
+ */
304
+ createRun(_options) {
305
+ throw new Error(
306
+ "createRun() has been deprecated. Please use createRunAsync() instead.\n\nMigration guide:\n Before: const run = workflow.createRun();\n After: const run = await workflow.createRunAsync();\n\nNote: createRunAsync() is an async method, so make sure your calling function is async."
307
+ );
308
+ }
309
+ async createRunAsync(options) {
184
310
  const runIdToUse = options?.runId || randomUUID();
185
311
  const run = this.runs.get(runIdToUse) ?? new InngestRun(
186
312
  {
187
313
  workflowId: this.id,
188
314
  runId: runIdToUse,
315
+ resourceId: options?.resourceId,
189
316
  executionEngine: this.executionEngine,
190
317
  executionGraph: this.executionGraph,
318
+ serializedStepGraph: this.serializedStepGraph,
191
319
  mastra: this.#mastra,
192
320
  retryConfig: this.retryConfig,
193
- cleanup: () => this.runs.delete(runIdToUse)
321
+ cleanup: () => this.runs.delete(runIdToUse),
322
+ workflowSteps: this.steps
194
323
  },
195
324
  this.inngest
196
325
  );
197
326
  this.runs.set(runIdToUse, run);
327
+ const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse, false);
328
+ if (!workflowSnapshotInStorage) {
329
+ await this.mastra?.getStorage()?.persistWorkflowSnapshot({
330
+ workflowName: this.id,
331
+ runId: runIdToUse,
332
+ resourceId: options?.resourceId,
333
+ snapshot: {
334
+ runId: runIdToUse,
335
+ status: "pending",
336
+ value: {},
337
+ context: {},
338
+ activePaths: [],
339
+ waitingPaths: {},
340
+ serializedStepGraph: this.serializedStepGraph,
341
+ suspendedPaths: {},
342
+ result: void 0,
343
+ error: void 0,
344
+ // @ts-ignore
345
+ timestamp: Date.now()
346
+ }
347
+ });
348
+ }
198
349
  return run;
199
350
  }
200
351
  getFunction() {
@@ -202,11 +353,17 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
202
353
  return this.function;
203
354
  }
204
355
  this.function = this.inngest.createFunction(
205
- // @ts-ignore
206
- { id: `workflow.${this.id}`, retries: this.retryConfig?.attempts ?? 0 },
356
+ {
357
+ id: `workflow.${this.id}`,
358
+ // @ts-ignore
359
+ retries: this.retryConfig?.attempts ?? 0,
360
+ cancelOn: [{ event: `cancel.workflow.${this.id}` }],
361
+ // Spread flow control configuration
362
+ ...this.flowControlConfig
363
+ },
207
364
  { event: `workflow.${this.id}` },
208
365
  async ({ event, step, attempt, publish }) => {
209
- let { inputData, runId, resume } = event.data;
366
+ let { inputData, runId, resourceId, resume } = event.data;
210
367
  if (!runId) {
211
368
  runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
212
369
  return randomUUID();
@@ -220,25 +377,36 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
220
377
  try {
221
378
  await publish({
222
379
  channel: `workflow:${this.id}:${runId}`,
223
- topic: "watch",
380
+ topic: event2,
224
381
  data
225
382
  });
226
383
  } catch (err) {
227
384
  this.logger.error("Error emitting event: " + (err?.stack ?? err?.message ?? err));
228
385
  }
386
+ },
387
+ on: (_event, _callback) => {
388
+ },
389
+ off: (_event, _callback) => {
390
+ },
391
+ once: (_event, _callback) => {
229
392
  }
230
393
  };
231
394
  const engine = new InngestExecutionEngine(this.#mastra, step, attempt);
232
395
  const result = await engine.execute({
233
396
  workflowId: this.id,
234
397
  runId,
398
+ resourceId,
235
399
  graph: this.executionGraph,
400
+ serializedStepGraph: this.serializedStepGraph,
236
401
  input: inputData,
237
402
  emitter,
238
403
  retryConfig: this.retryConfig,
239
404
  runtimeContext: new RuntimeContext(),
240
405
  // TODO
241
- resume
406
+ resume,
407
+ abortController: new AbortController(),
408
+ currentSpan: void 0
409
+ // TODO: Pass actual parent AI span from workflow execution context
242
410
  });
243
411
  return { result, runId };
244
412
  }
@@ -262,20 +430,103 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
262
430
  return [this.getFunction(), ...this.getNestedFunctions(this.executionGraph.steps)];
263
431
  }
264
432
  };
265
- function cloneWorkflow(workflow, opts) {
266
- const wf = new InngestWorkflow(
267
- {
268
- id: opts.id,
269
- inputSchema: workflow.inputSchema,
270
- outputSchema: workflow.outputSchema,
271
- steps: workflow.stepDefs,
272
- mastra: workflow.mastra
273
- },
274
- workflow.inngest
275
- );
276
- wf.setStepFlow(workflow.stepGraph);
277
- wf.commit();
278
- return wf;
433
+ function isAgent(params) {
434
+ return params?.component === "AGENT";
435
+ }
436
+ function isTool(params) {
437
+ return params instanceof Tool;
438
+ }
439
+ function createStep(params) {
440
+ if (isAgent(params)) {
441
+ return {
442
+ id: params.name,
443
+ description: params.getDescription(),
444
+ // @ts-ignore
445
+ inputSchema: z.object({
446
+ prompt: z.string()
447
+ }),
448
+ // @ts-ignore
449
+ outputSchema: z.object({
450
+ text: z.string()
451
+ }),
452
+ execute: async ({ inputData, [EMITTER_SYMBOL]: emitter, runtimeContext, abortSignal, abort, tracingContext }) => {
453
+ let streamPromise = {};
454
+ streamPromise.promise = new Promise((resolve, reject) => {
455
+ streamPromise.resolve = resolve;
456
+ streamPromise.reject = reject;
457
+ });
458
+ const toolData = {
459
+ name: params.name,
460
+ args: inputData
461
+ };
462
+ const { fullStream } = await params.stream(inputData.prompt, {
463
+ runtimeContext,
464
+ tracingContext,
465
+ onFinish: (result) => {
466
+ streamPromise.resolve(result.text);
467
+ },
468
+ abortSignal
469
+ });
470
+ if (abortSignal.aborted) {
471
+ return abort();
472
+ }
473
+ await emitter.emit("watch-v2", {
474
+ type: "tool-call-streaming-start",
475
+ ...toolData ?? {}
476
+ });
477
+ for await (const chunk of fullStream) {
478
+ if (chunk.type === "text-delta") {
479
+ await emitter.emit("watch-v2", {
480
+ type: "tool-call-delta",
481
+ ...toolData ?? {},
482
+ argsTextDelta: chunk.textDelta
483
+ });
484
+ }
485
+ }
486
+ await emitter.emit("watch-v2", {
487
+ type: "tool-call-streaming-finish",
488
+ ...toolData ?? {}
489
+ });
490
+ return {
491
+ text: await streamPromise.promise
492
+ };
493
+ },
494
+ component: params.component
495
+ };
496
+ }
497
+ if (isTool(params)) {
498
+ if (!params.inputSchema || !params.outputSchema) {
499
+ throw new Error("Tool must have input and output schemas defined");
500
+ }
501
+ return {
502
+ // TODO: tool probably should have strong id type
503
+ // @ts-ignore
504
+ id: params.id,
505
+ description: params.description,
506
+ inputSchema: params.inputSchema,
507
+ outputSchema: params.outputSchema,
508
+ execute: async ({ inputData, mastra, runtimeContext, tracingContext, suspend, resumeData }) => {
509
+ return params.execute({
510
+ context: inputData,
511
+ mastra: wrapMastra(mastra, tracingContext),
512
+ runtimeContext,
513
+ tracingContext,
514
+ suspend,
515
+ resumeData
516
+ });
517
+ },
518
+ component: "TOOL"
519
+ };
520
+ }
521
+ return {
522
+ id: params.id,
523
+ description: params.description,
524
+ inputSchema: params.inputSchema,
525
+ outputSchema: params.outputSchema,
526
+ resumeSchema: params.resumeSchema,
527
+ suspendSchema: params.suspendSchema,
528
+ execute: params.execute
529
+ };
279
530
  }
280
531
  function init(inngest) {
281
532
  return {
@@ -283,18 +534,50 @@ function init(inngest) {
283
534
  return new InngestWorkflow(params, inngest);
284
535
  },
285
536
  createStep,
286
- cloneStep,
287
- cloneWorkflow
537
+ cloneStep(step, opts) {
538
+ return {
539
+ id: opts.id,
540
+ description: step.description,
541
+ inputSchema: step.inputSchema,
542
+ outputSchema: step.outputSchema,
543
+ execute: step.execute,
544
+ component: step.component
545
+ };
546
+ },
547
+ cloneWorkflow(workflow, opts) {
548
+ const wf = new Workflow({
549
+ id: opts.id,
550
+ inputSchema: workflow.inputSchema,
551
+ outputSchema: workflow.outputSchema,
552
+ steps: workflow.stepDefs,
553
+ mastra: workflow.mastra
554
+ });
555
+ wf.setStepFlow(workflow.stepGraph);
556
+ wf.commit();
557
+ return wf;
558
+ }
288
559
  };
289
560
  }
290
561
  var InngestExecutionEngine = class extends DefaultExecutionEngine {
291
562
  inngestStep;
292
563
  inngestAttempts;
293
- constructor(mastra, inngestStep, inngestAttempts = 0) {
294
- super({ mastra });
564
+ constructor(mastra, inngestStep, inngestAttempts = 0, options) {
565
+ super({ mastra, options });
295
566
  this.inngestStep = inngestStep;
296
567
  this.inngestAttempts = inngestAttempts;
297
568
  }
569
+ async execute(params) {
570
+ await params.emitter.emit("watch-v2", {
571
+ type: "workflow-start",
572
+ payload: { runId: params.runId }
573
+ });
574
+ const result = await super.execute(params);
575
+ await params.emitter.emit("watch-v2", {
576
+ type: "workflow-finish",
577
+ payload: { runId: params.runId }
578
+ });
579
+ return result;
580
+ }
298
581
  async fmtReturnValue(executionSpan, emitter, stepResults, lastOutput, error) {
299
582
  const base = {
300
583
  status: lastOutput.status,
@@ -352,28 +635,179 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
352
635
  executionSpan?.end();
353
636
  return base;
354
637
  }
355
- async superExecuteStep({
638
+ // async executeSleep({ id, duration }: { id: string; duration: number }): Promise<void> {
639
+ // await this.inngestStep.sleep(id, duration);
640
+ // }
641
+ async executeSleep({
356
642
  workflowId,
357
643
  runId,
358
- step,
644
+ entry,
645
+ prevOutput,
359
646
  stepResults,
647
+ emitter,
648
+ abortController,
649
+ runtimeContext,
360
650
  executionContext,
361
- resume,
651
+ writableStream,
652
+ tracingContext
653
+ }) {
654
+ let { duration, fn } = entry;
655
+ const sleepSpan = tracingContext?.currentSpan?.createChildSpan({
656
+ type: AISpanType.WORKFLOW_SLEEP,
657
+ name: `sleep: ${duration ? `${duration}ms` : "dynamic"}`,
658
+ attributes: {
659
+ durationMs: duration,
660
+ sleepType: fn ? "dynamic" : "fixed"
661
+ },
662
+ tracingPolicy: this.options?.tracingPolicy
663
+ });
664
+ if (fn) {
665
+ const stepCallId = randomUUID();
666
+ duration = await this.inngestStep.run(`workflow.${workflowId}.sleep.${entry.id}`, async () => {
667
+ return await fn({
668
+ runId,
669
+ workflowId,
670
+ mastra: this.mastra,
671
+ runtimeContext,
672
+ inputData: prevOutput,
673
+ runCount: -1,
674
+ tracingContext: {
675
+ currentSpan: sleepSpan
676
+ },
677
+ getInitData: () => stepResults?.input,
678
+ getStepResult: getStepResult.bind(this, stepResults),
679
+ // TODO: this function shouldn't have suspend probably?
680
+ suspend: async (_suspendPayload) => {
681
+ },
682
+ bail: () => {
683
+ },
684
+ abort: () => {
685
+ abortController?.abort();
686
+ },
687
+ [EMITTER_SYMBOL]: emitter,
688
+ // TODO: add streamVNext support
689
+ [STREAM_FORMAT_SYMBOL]: executionContext.format,
690
+ engine: { step: this.inngestStep },
691
+ abortSignal: abortController?.signal,
692
+ writer: new ToolStream(
693
+ {
694
+ prefix: "workflow-step",
695
+ callId: stepCallId,
696
+ name: "sleep",
697
+ runId
698
+ },
699
+ writableStream
700
+ )
701
+ });
702
+ });
703
+ sleepSpan?.update({
704
+ attributes: {
705
+ durationMs: duration
706
+ }
707
+ });
708
+ }
709
+ try {
710
+ await this.inngestStep.sleep(entry.id, !duration || duration < 0 ? 0 : duration);
711
+ sleepSpan?.end();
712
+ } catch (e) {
713
+ sleepSpan?.error({ error: e });
714
+ throw e;
715
+ }
716
+ }
717
+ async executeSleepUntil({
718
+ workflowId,
719
+ runId,
720
+ entry,
362
721
  prevOutput,
722
+ stepResults,
363
723
  emitter,
364
- runtimeContext
724
+ abortController,
725
+ runtimeContext,
726
+ executionContext,
727
+ writableStream,
728
+ tracingContext
365
729
  }) {
366
- return super.executeStep({
367
- workflowId,
368
- runId,
369
- step,
370
- stepResults,
371
- executionContext,
372
- resume,
373
- prevOutput,
374
- emitter,
375
- runtimeContext
730
+ let { date, fn } = entry;
731
+ const sleepUntilSpan = tracingContext?.currentSpan?.createChildSpan({
732
+ type: AISpanType.WORKFLOW_SLEEP,
733
+ name: `sleepUntil: ${date ? date.toISOString() : "dynamic"}`,
734
+ attributes: {
735
+ untilDate: date,
736
+ durationMs: date ? Math.max(0, date.getTime() - Date.now()) : void 0,
737
+ sleepType: fn ? "dynamic" : "fixed"
738
+ },
739
+ tracingPolicy: this.options?.tracingPolicy
740
+ });
741
+ if (fn) {
742
+ date = await this.inngestStep.run(`workflow.${workflowId}.sleepUntil.${entry.id}`, async () => {
743
+ const stepCallId = randomUUID();
744
+ return await fn({
745
+ runId,
746
+ workflowId,
747
+ mastra: this.mastra,
748
+ runtimeContext,
749
+ inputData: prevOutput,
750
+ runCount: -1,
751
+ tracingContext: {
752
+ currentSpan: sleepUntilSpan
753
+ },
754
+ getInitData: () => stepResults?.input,
755
+ getStepResult: getStepResult.bind(this, stepResults),
756
+ // TODO: this function shouldn't have suspend probably?
757
+ suspend: async (_suspendPayload) => {
758
+ },
759
+ bail: () => {
760
+ },
761
+ abort: () => {
762
+ abortController?.abort();
763
+ },
764
+ [EMITTER_SYMBOL]: emitter,
765
+ [STREAM_FORMAT_SYMBOL]: executionContext.format,
766
+ // TODO: add streamVNext support
767
+ engine: { step: this.inngestStep },
768
+ abortSignal: abortController?.signal,
769
+ writer: new ToolStream(
770
+ {
771
+ prefix: "workflow-step",
772
+ callId: stepCallId,
773
+ name: "sleep",
774
+ runId
775
+ },
776
+ writableStream
777
+ )
778
+ });
779
+ });
780
+ if (date && !(date instanceof Date)) {
781
+ date = new Date(date);
782
+ }
783
+ const time = !date ? 0 : date.getTime() - Date.now();
784
+ sleepUntilSpan?.update({
785
+ attributes: {
786
+ durationMs: Math.max(0, time)
787
+ }
788
+ });
789
+ }
790
+ if (!(date instanceof Date)) {
791
+ sleepUntilSpan?.end();
792
+ return;
793
+ }
794
+ try {
795
+ await this.inngestStep.sleepUntil(entry.id, date);
796
+ sleepUntilSpan?.end();
797
+ } catch (e) {
798
+ sleepUntilSpan?.error({ error: e });
799
+ throw e;
800
+ }
801
+ }
802
+ async executeWaitForEvent({ event, timeout }) {
803
+ const eventData = await this.inngestStep.waitForEvent(`user-event-${event}`, {
804
+ event: `user-event-${event}`,
805
+ timeout: timeout ?? 5e3
376
806
  });
807
+ if (eventData === null) {
808
+ throw "Timeout waiting for event";
809
+ }
810
+ return eventData?.data;
377
811
  }
378
812
  async executeStep({
379
813
  step,
@@ -382,11 +816,30 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
382
816
  resume,
383
817
  prevOutput,
384
818
  emitter,
385
- runtimeContext
819
+ abortController,
820
+ runtimeContext,
821
+ tracingContext,
822
+ writableStream,
823
+ disableScorers
386
824
  }) {
387
- await this.inngestStep.run(
825
+ const stepAISpan = tracingContext?.currentSpan?.createChildSpan({
826
+ name: `workflow step: '${step.id}'`,
827
+ type: AISpanType.WORKFLOW_STEP,
828
+ input: prevOutput,
829
+ attributes: {
830
+ stepId: step.id
831
+ },
832
+ tracingPolicy: this.options?.tracingPolicy
833
+ });
834
+ const { inputData, validationError } = await validateStepInput({
835
+ prevOutput,
836
+ step,
837
+ validateInputs: this.options?.validateInputs ?? false
838
+ });
839
+ const startedAt = await this.inngestStep.run(
388
840
  `workflow.${executionContext.workflowId}.run.${executionContext.runId}.step.${step.id}.running_ev`,
389
841
  async () => {
842
+ const startedAt2 = Date.now();
390
843
  await emitter.emit("watch", {
391
844
  type: "watch",
392
845
  payload: {
@@ -408,6 +861,16 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
408
861
  },
409
862
  eventTimestamp: Date.now()
410
863
  });
864
+ await emitter.emit("watch-v2", {
865
+ type: "workflow-step-start",
866
+ payload: {
867
+ id: step.id,
868
+ status: "running",
869
+ payload: inputData,
870
+ startedAt: startedAt2
871
+ }
872
+ });
873
+ return startedAt2;
411
874
  }
412
875
  );
413
876
  if (step instanceof InngestWorkflow) {
@@ -423,7 +886,7 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
423
886
  const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
424
887
  function: step.getFunction(),
425
888
  data: {
426
- inputData: prevOutput,
889
+ inputData,
427
890
  runId,
428
891
  resume: {
429
892
  runId,
@@ -441,7 +904,7 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
441
904
  const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
442
905
  function: step.getFunction(),
443
906
  data: {
444
- inputData: prevOutput
907
+ inputData
445
908
  }
446
909
  });
447
910
  result = invokeResp.result;
@@ -468,6 +931,15 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
468
931
  },
469
932
  eventTimestamp: Date.now()
470
933
  });
934
+ await emitter.emit("watch-v2", {
935
+ type: "workflow-step-result",
936
+ payload: {
937
+ id: step.id,
938
+ status: "failed",
939
+ error: result?.error,
940
+ payload: prevOutput
941
+ }
942
+ });
471
943
  return { executionContext, result: { status: "failed", error: result?.error } };
472
944
  } else if (result.status === "suspended") {
473
945
  const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
@@ -494,6 +966,13 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
494
966
  },
495
967
  eventTimestamp: Date.now()
496
968
  });
969
+ await emitter.emit("watch-v2", {
970
+ type: "workflow-step-suspended",
971
+ payload: {
972
+ id: step.id,
973
+ status: "suspended"
974
+ }
975
+ });
497
976
  return {
498
977
  executionContext,
499
978
  result: {
@@ -544,6 +1023,21 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
544
1023
  },
545
1024
  eventTimestamp: Date.now()
546
1025
  });
1026
+ await emitter.emit("watch-v2", {
1027
+ type: "workflow-step-result",
1028
+ payload: {
1029
+ id: step.id,
1030
+ status: "success",
1031
+ output: result?.result
1032
+ }
1033
+ });
1034
+ await emitter.emit("watch-v2", {
1035
+ type: "workflow-step-finish",
1036
+ payload: {
1037
+ id: step.id,
1038
+ metadata: {}
1039
+ }
1040
+ });
547
1041
  return { executionContext, result: { status: "success", output: result?.result } };
548
1042
  }
549
1043
  );
@@ -553,42 +1047,81 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
553
1047
  const stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
554
1048
  let execResults;
555
1049
  let suspended;
1050
+ let bailed;
556
1051
  try {
1052
+ if (validationError) {
1053
+ throw validationError;
1054
+ }
557
1055
  const result = await step.execute({
1056
+ runId: executionContext.runId,
558
1057
  mastra: this.mastra,
559
1058
  runtimeContext,
560
- inputData: prevOutput,
1059
+ writableStream,
1060
+ inputData,
561
1061
  resumeData: resume?.steps[0] === step.id ? resume?.resumePayload : void 0,
562
- getInitData: () => stepResults?.input,
563
- getStepResult: (step2) => {
564
- const result2 = stepResults[step2.id];
565
- if (result2?.status === "success") {
566
- return result2.output;
567
- }
568
- return null;
1062
+ tracingContext: {
1063
+ currentSpan: stepAISpan
569
1064
  },
1065
+ getInitData: () => stepResults?.input,
1066
+ getStepResult: getStepResult.bind(this, stepResults),
570
1067
  suspend: async (suspendPayload) => {
571
1068
  executionContext.suspendedPaths[step.id] = executionContext.executionPath;
572
1069
  suspended = { payload: suspendPayload };
573
1070
  },
1071
+ bail: (result2) => {
1072
+ bailed = { payload: result2 };
1073
+ },
574
1074
  resume: {
575
1075
  steps: resume?.steps?.slice(1) || [],
576
1076
  resumePayload: resume?.resumePayload,
577
1077
  // @ts-ignore
578
1078
  runId: stepResults[step.id]?.payload?.__workflow_meta?.runId
579
1079
  },
580
- emitter
1080
+ [EMITTER_SYMBOL]: emitter,
1081
+ engine: {
1082
+ step: this.inngestStep
1083
+ },
1084
+ abortSignal: abortController.signal
581
1085
  });
582
- execResults = { status: "success", output: result };
1086
+ const endedAt = Date.now();
1087
+ execResults = {
1088
+ status: "success",
1089
+ output: result,
1090
+ startedAt,
1091
+ endedAt,
1092
+ payload: inputData,
1093
+ resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1094
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1095
+ };
583
1096
  } catch (e) {
584
- execResults = { status: "failed", error: e instanceof Error ? e.message : String(e) };
1097
+ execResults = {
1098
+ status: "failed",
1099
+ payload: inputData,
1100
+ error: e instanceof Error ? e.message : String(e),
1101
+ endedAt: Date.now(),
1102
+ startedAt,
1103
+ resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1104
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1105
+ };
585
1106
  }
586
1107
  if (suspended) {
587
- execResults = { status: "suspended", payload: suspended.payload };
1108
+ execResults = {
1109
+ status: "suspended",
1110
+ suspendedPayload: suspended.payload,
1111
+ payload: inputData,
1112
+ suspendedAt: Date.now(),
1113
+ startedAt,
1114
+ resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1115
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1116
+ };
1117
+ } else if (bailed) {
1118
+ execResults = { status: "bailed", output: bailed.payload, payload: inputData, endedAt: Date.now(), startedAt };
588
1119
  }
589
1120
  if (execResults.status === "failed") {
590
1121
  if (executionContext.retryConfig.attempts > 0 && this.inngestAttempts < executionContext.retryConfig.attempts) {
591
- throw execResults.error;
1122
+ const error = new Error(execResults.error);
1123
+ stepAISpan?.error({ error });
1124
+ throw error;
592
1125
  }
593
1126
  }
594
1127
  await emitter.emit("watch", {
@@ -596,20 +1129,61 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
596
1129
  payload: {
597
1130
  currentStep: {
598
1131
  id: step.id,
599
- status: execResults.status,
600
- output: execResults.output
1132
+ ...execResults
601
1133
  },
602
1134
  workflowState: {
603
1135
  status: "running",
604
- steps: stepResults,
1136
+ steps: { ...stepResults, [step.id]: execResults },
605
1137
  result: null,
606
1138
  error: null
607
1139
  }
608
1140
  },
609
1141
  eventTimestamp: Date.now()
610
1142
  });
1143
+ if (execResults.status === "suspended") {
1144
+ await emitter.emit("watch-v2", {
1145
+ type: "workflow-step-suspended",
1146
+ payload: {
1147
+ id: step.id,
1148
+ ...execResults
1149
+ }
1150
+ });
1151
+ } else {
1152
+ await emitter.emit("watch-v2", {
1153
+ type: "workflow-step-result",
1154
+ payload: {
1155
+ id: step.id,
1156
+ ...execResults
1157
+ }
1158
+ });
1159
+ await emitter.emit("watch-v2", {
1160
+ type: "workflow-step-finish",
1161
+ payload: {
1162
+ id: step.id,
1163
+ metadata: {}
1164
+ }
1165
+ });
1166
+ }
1167
+ stepAISpan?.end({ output: execResults });
611
1168
  return { result: execResults, executionContext, stepResults };
612
1169
  });
1170
+ if (disableScorers !== false) {
1171
+ await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}.score`, async () => {
1172
+ if (step.scorers) {
1173
+ await this.runScorers({
1174
+ scorers: step.scorers,
1175
+ runId: executionContext.runId,
1176
+ input: inputData,
1177
+ output: stepRes.result,
1178
+ workflowId: executionContext.workflowId,
1179
+ stepId: step.id,
1180
+ runtimeContext,
1181
+ disableScorers,
1182
+ tracingContext: { currentSpan: stepAISpan }
1183
+ });
1184
+ }
1185
+ });
1186
+ }
613
1187
  Object.assign(executionContext.suspendedPaths, stepRes.executionContext.suspendedPaths);
614
1188
  Object.assign(stepResults, stepRes.stepResults);
615
1189
  return stepRes.result;
@@ -618,7 +1192,12 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
618
1192
  workflowId,
619
1193
  runId,
620
1194
  stepResults,
621
- executionContext
1195
+ resourceId,
1196
+ executionContext,
1197
+ serializedStepGraph,
1198
+ workflowStatus,
1199
+ result,
1200
+ error
622
1201
  }) {
623
1202
  await this.inngestStep.run(
624
1203
  `workflow.${workflowId}.run.${runId}.path.${JSON.stringify(executionContext.executionPath)}.stepUpdate`,
@@ -626,12 +1205,18 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
626
1205
  await this.mastra?.getStorage()?.persistWorkflowSnapshot({
627
1206
  workflowName: workflowId,
628
1207
  runId,
1208
+ resourceId,
629
1209
  snapshot: {
630
1210
  runId,
631
1211
  value: {},
632
1212
  context: stepResults,
633
1213
  activePaths: [],
634
1214
  suspendedPaths: executionContext.suspendedPaths,
1215
+ waitingPaths: {},
1216
+ serializedStepGraph,
1217
+ status: workflowStatus,
1218
+ result,
1219
+ error,
635
1220
  // @ts-ignore
636
1221
  timestamp: Date.now()
637
1222
  }
@@ -646,50 +1231,109 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
646
1231
  prevOutput,
647
1232
  prevStep,
648
1233
  stepResults,
1234
+ serializedStepGraph,
649
1235
  resume,
650
1236
  executionContext,
651
1237
  emitter,
652
- runtimeContext
1238
+ abortController,
1239
+ runtimeContext,
1240
+ writableStream,
1241
+ disableScorers,
1242
+ tracingContext
653
1243
  }) {
1244
+ const conditionalSpan = tracingContext?.currentSpan?.createChildSpan({
1245
+ type: AISpanType.WORKFLOW_CONDITIONAL,
1246
+ name: `conditional: '${entry.conditions.length} conditions'`,
1247
+ input: prevOutput,
1248
+ attributes: {
1249
+ conditionCount: entry.conditions.length
1250
+ },
1251
+ tracingPolicy: this.options?.tracingPolicy
1252
+ });
654
1253
  let execResults;
655
1254
  const truthyIndexes = (await Promise.all(
656
1255
  entry.conditions.map(
657
1256
  (cond, index) => this.inngestStep.run(`workflow.${workflowId}.conditional.${index}`, async () => {
1257
+ const evalSpan = conditionalSpan?.createChildSpan({
1258
+ type: AISpanType.WORKFLOW_CONDITIONAL_EVAL,
1259
+ name: `condition: '${index}'`,
1260
+ input: prevOutput,
1261
+ attributes: {
1262
+ conditionIndex: index
1263
+ },
1264
+ tracingPolicy: this.options?.tracingPolicy
1265
+ });
658
1266
  try {
659
1267
  const result = await cond({
1268
+ runId,
1269
+ workflowId,
660
1270
  mastra: this.mastra,
661
1271
  runtimeContext,
1272
+ runCount: -1,
662
1273
  inputData: prevOutput,
663
- getInitData: () => stepResults?.input,
664
- getStepResult: (step) => {
665
- if (!step?.id) {
666
- return null;
667
- }
668
- const result2 = stepResults[step.id];
669
- if (result2?.status === "success") {
670
- return result2.output;
671
- }
672
- return null;
1274
+ tracingContext: {
1275
+ currentSpan: evalSpan
673
1276
  },
1277
+ getInitData: () => stepResults?.input,
1278
+ getStepResult: getStepResult.bind(this, stepResults),
674
1279
  // TODO: this function shouldn't have suspend probably?
675
1280
  suspend: async (_suspendPayload) => {
676
1281
  },
677
- [EMITTER_SYMBOL]: emitter
1282
+ bail: () => {
1283
+ },
1284
+ abort: () => {
1285
+ abortController.abort();
1286
+ },
1287
+ [EMITTER_SYMBOL]: emitter,
1288
+ [STREAM_FORMAT_SYMBOL]: executionContext.format,
1289
+ // TODO: add streamVNext support
1290
+ engine: {
1291
+ step: this.inngestStep
1292
+ },
1293
+ abortSignal: abortController.signal,
1294
+ writer: new ToolStream(
1295
+ {
1296
+ prefix: "workflow-step",
1297
+ callId: randomUUID(),
1298
+ name: "conditional",
1299
+ runId
1300
+ },
1301
+ writableStream
1302
+ )
1303
+ });
1304
+ evalSpan?.end({
1305
+ output: result,
1306
+ attributes: {
1307
+ result: !!result
1308
+ }
678
1309
  });
679
1310
  return result ? index : null;
680
1311
  } catch (e) {
1312
+ evalSpan?.error({
1313
+ error: e instanceof Error ? e : new Error(String(e)),
1314
+ attributes: {
1315
+ result: false
1316
+ }
1317
+ });
681
1318
  return null;
682
1319
  }
683
1320
  })
684
1321
  )
685
1322
  )).filter((index) => index !== null);
686
1323
  const stepsToRun = entry.steps.filter((_, index) => truthyIndexes.includes(index));
1324
+ conditionalSpan?.update({
1325
+ attributes: {
1326
+ truthyIndexes,
1327
+ selectedSteps: stepsToRun.map((s) => s.type === "step" ? s.step.id : `control-${s.type}`)
1328
+ }
1329
+ });
687
1330
  const results = await Promise.all(
688
1331
  stepsToRun.map(
689
1332
  (step, index) => this.executeEntry({
690
1333
  workflowId,
691
1334
  runId,
692
1335
  entry: step,
1336
+ serializedStepGraph,
693
1337
  prevStep,
694
1338
  stepResults,
695
1339
  resume,
@@ -702,29 +1346,46 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
702
1346
  executionSpan: executionContext.executionSpan
703
1347
  },
704
1348
  emitter,
705
- runtimeContext
1349
+ abortController,
1350
+ runtimeContext,
1351
+ writableStream,
1352
+ disableScorers,
1353
+ tracingContext: {
1354
+ currentSpan: conditionalSpan
1355
+ }
706
1356
  })
707
1357
  )
708
1358
  );
709
- const hasFailed = results.find((result) => result.status === "failed");
710
- const hasSuspended = results.find((result) => result.status === "suspended");
1359
+ const hasFailed = results.find((result) => result.result.status === "failed");
1360
+ const hasSuspended = results.find((result) => result.result.status === "suspended");
711
1361
  if (hasFailed) {
712
- execResults = { status: "failed", error: hasFailed.error };
1362
+ execResults = { status: "failed", error: hasFailed.result.error };
713
1363
  } else if (hasSuspended) {
714
- execResults = { status: "suspended", payload: hasSuspended.payload };
1364
+ execResults = { status: "suspended", payload: hasSuspended.result.suspendPayload };
715
1365
  } else {
716
1366
  execResults = {
717
1367
  status: "success",
718
1368
  output: results.reduce((acc, result, index) => {
719
- if (result.status === "success") {
1369
+ if (result.result.status === "success") {
720
1370
  acc[stepsToRun[index].step.id] = result.output;
721
1371
  }
722
1372
  return acc;
723
1373
  }, {})
724
1374
  };
725
1375
  }
1376
+ if (execResults.status === "failed") {
1377
+ conditionalSpan?.error({
1378
+ error: new Error(execResults.error)
1379
+ });
1380
+ } else {
1381
+ conditionalSpan?.end({
1382
+ output: execResults.output || execResults
1383
+ });
1384
+ }
726
1385
  return execResults;
727
1386
  }
728
1387
  };
729
1388
 
730
- export { InngestExecutionEngine, InngestRun, InngestWorkflow, init, serve };
1389
+ export { InngestExecutionEngine, InngestRun, InngestWorkflow, createStep, init, serve };
1390
+ //# sourceMappingURL=index.js.map
1391
+ //# sourceMappingURL=index.js.map