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