@mastra/inngest 0.0.0-break-rename-vnext-legacy-20251002212351 → 0.0.0-bundle-studio-cloud-20251222034739

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