@mastra/inngest 0.0.0-span-scorring-test-20251124132129 → 0.0.0-standard-schema-20260123120255

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,41 +1,629 @@
1
+ import { MessageList, Agent, TripWire } from '@mastra/core/agent';
2
+ import { getErrorFromUnknown, MastraError, ErrorDomain, ErrorCategory } from '@mastra/core/error';
3
+ import { EntityType, SpanType } from '@mastra/core/observability';
4
+ import { ProcessorStepOutputSchema, ProcessorStepSchema, ProcessorRunner } from '@mastra/core/processors';
5
+ import { toStandardSchema } from '@mastra/core/schema';
6
+ import { Tool } from '@mastra/core/tools';
7
+ import { DefaultExecutionEngine, createTimeTravelExecutionParams, Run, hydrateSerializedStepErrors, Workflow } from '@mastra/core/workflows';
8
+ import { PUBSUB_SYMBOL, STREAM_FORMAT_SYMBOL } from '@mastra/core/workflows/_constants';
9
+ import { z } from 'zod';
1
10
  import { randomUUID } from 'crypto';
2
- import { ReadableStream, WritableStream } from 'stream/web';
3
- import { subscribe } from '@inngest/realtime';
4
11
  import { RequestContext } from '@mastra/core/di';
5
- import { SpanType } from '@mastra/core/observability';
12
+ import { NonRetriableError } from 'inngest';
13
+ import { subscribe } from '@inngest/realtime';
14
+ import { PubSub } from '@mastra/core/events';
15
+ import { ReadableStream } from 'stream/web';
6
16
  import { ChunkFrom, WorkflowRunOutput } from '@mastra/core/stream';
7
- import { ToolStream, Tool } from '@mastra/core/tools';
8
- import { Run, createTimeTravelExecutionParams, Workflow, DefaultExecutionEngine, createDeprecationProxy, getStepResult, runCountDeprecationMessage, validateStepInput, validateStepResumeData } from '@mastra/core/workflows';
9
- import { EMITTER_SYMBOL, STREAM_FORMAT_SYMBOL } from '@mastra/core/workflows/_constants';
10
- import { NonRetriableError, RetryAfterError } from 'inngest';
11
17
  import { serve as serve$1 } from 'inngest/hono';
12
- import { z } from 'zod';
13
18
 
14
19
  // src/index.ts
15
- function serve({
16
- mastra,
17
- inngest,
18
- functions: userFunctions = [],
19
- registerOptions
20
- }) {
21
- const wfs = mastra.listWorkflows();
22
- const workflowFunctions = Array.from(
23
- new Set(
24
- Object.values(wfs).flatMap((wf) => {
25
- if (wf instanceof InngestWorkflow) {
26
- wf.__registerMastra(mastra);
27
- return wf.getFunctions();
20
+ var InngestExecutionEngine = class extends DefaultExecutionEngine {
21
+ inngestStep;
22
+ inngestAttempts;
23
+ constructor(mastra, inngestStep, inngestAttempts = 0, options) {
24
+ super({ mastra, options });
25
+ this.inngestStep = inngestStep;
26
+ this.inngestAttempts = inngestAttempts;
27
+ }
28
+ // =============================================================================
29
+ // Hook Overrides
30
+ // =============================================================================
31
+ /**
32
+ * Format errors while preserving Error instances and their custom properties.
33
+ * Uses getErrorFromUnknown to ensure all error properties are preserved.
34
+ */
35
+ formatResultError(error, lastOutput) {
36
+ const outputError = lastOutput?.error;
37
+ const errorSource = error || outputError;
38
+ const errorInstance = getErrorFromUnknown(errorSource, {
39
+ serializeStack: true,
40
+ // Include stack in JSON for better debugging in Inngest
41
+ fallbackMessage: "Unknown workflow error"
42
+ });
43
+ return errorInstance.toJSON();
44
+ }
45
+ /**
46
+ * Detect InngestWorkflow instances for special nested workflow handling
47
+ */
48
+ isNestedWorkflowStep(step) {
49
+ return step instanceof InngestWorkflow;
50
+ }
51
+ /**
52
+ * Inngest requires requestContext serialization for memoization.
53
+ * When steps are replayed, the original function doesn't re-execute,
54
+ * so requestContext modifications must be captured and restored.
55
+ */
56
+ requiresDurableContextSerialization() {
57
+ return true;
58
+ }
59
+ /**
60
+ * Execute a step with retry logic for Inngest.
61
+ * Retries are handled via step-level retry (RetryAfterError thrown INSIDE step.run()).
62
+ * After retries exhausted, error propagates here and we return a failed result.
63
+ */
64
+ async executeStepWithRetry(stepId, runStep, params) {
65
+ for (let i = 0; i < params.retries + 1; i++) {
66
+ if (i > 0 && params.delay) {
67
+ await new Promise((resolve) => setTimeout(resolve, params.delay));
68
+ }
69
+ try {
70
+ const result = await this.wrapDurableOperation(stepId, runStep);
71
+ return { ok: true, result };
72
+ } catch (e) {
73
+ if (i === params.retries) {
74
+ const cause = e?.cause;
75
+ if (cause?.status === "failed") {
76
+ params.stepSpan?.error({
77
+ error: e,
78
+ attributes: { status: "failed" }
79
+ });
80
+ if (cause.error && !(cause.error instanceof Error)) {
81
+ cause.error = getErrorFromUnknown(cause.error, { serializeStack: false });
82
+ }
83
+ return { ok: false, error: cause };
84
+ }
85
+ const errorInstance = getErrorFromUnknown(e, {
86
+ serializeStack: false,
87
+ fallbackMessage: "Unknown step execution error"
88
+ });
89
+ params.stepSpan?.error({
90
+ error: errorInstance,
91
+ attributes: { status: "failed" }
92
+ });
93
+ return {
94
+ ok: false,
95
+ error: {
96
+ status: "failed",
97
+ error: errorInstance,
98
+ endedAt: Date.now()
99
+ }
100
+ };
28
101
  }
29
- return [];
30
- })
31
- )
32
- );
33
- return serve$1({
34
- ...registerOptions,
35
- client: inngest,
36
- functions: [...workflowFunctions, ...userFunctions]
37
- });
38
- }
102
+ }
103
+ }
104
+ return { ok: false, error: { status: "failed", error: new Error("Unknown error"), endedAt: Date.now() } };
105
+ }
106
+ /**
107
+ * Use Inngest's sleep primitive for durability
108
+ */
109
+ async executeSleepDuration(duration, sleepId, workflowId) {
110
+ await this.inngestStep.sleep(`workflow.${workflowId}.sleep.${sleepId}`, duration < 0 ? 0 : duration);
111
+ }
112
+ /**
113
+ * Use Inngest's sleepUntil primitive for durability
114
+ */
115
+ async executeSleepUntilDate(date, sleepUntilId, workflowId) {
116
+ await this.inngestStep.sleepUntil(`workflow.${workflowId}.sleepUntil.${sleepUntilId}`, date);
117
+ }
118
+ /**
119
+ * Wrap durable operations in Inngest step.run() for durability.
120
+ *
121
+ * IMPORTANT: Errors are wrapped with a cause structure before throwing.
122
+ * This is necessary because Inngest's error serialization (serialize-error-cjs)
123
+ * only captures standard Error properties (message, name, stack, code, cause).
124
+ * Custom properties like statusCode, responseHeaders from AI SDK errors would
125
+ * be lost. By putting our serialized error (via getErrorFromUnknown with toJSON())
126
+ * in the cause property, we ensure custom properties survive serialization.
127
+ * The cause property is in serialize-error-cjs's allowlist, and when the cause
128
+ * object is finally JSON.stringify'd, our error's toJSON() is called.
129
+ */
130
+ async wrapDurableOperation(operationId, operationFn) {
131
+ return this.inngestStep.run(operationId, async () => {
132
+ try {
133
+ return await operationFn();
134
+ } catch (e) {
135
+ const errorInstance = getErrorFromUnknown(e, {
136
+ serializeStack: false,
137
+ fallbackMessage: "Unknown step execution error"
138
+ });
139
+ throw new Error(errorInstance.message, {
140
+ cause: {
141
+ status: "failed",
142
+ error: errorInstance,
143
+ endedAt: Date.now()
144
+ }
145
+ });
146
+ }
147
+ });
148
+ }
149
+ /**
150
+ * Provide Inngest step primitive in engine context
151
+ */
152
+ getEngineContext() {
153
+ return { step: this.inngestStep };
154
+ }
155
+ /**
156
+ * For Inngest, lifecycle callbacks are invoked in the workflow's finalize step
157
+ * (wrapped in step.run for durability), not in execute(). Override to skip.
158
+ */
159
+ async invokeLifecycleCallbacks(_result) {
160
+ }
161
+ /**
162
+ * Actually invoke the lifecycle callbacks. Called from workflow.ts finalize step.
163
+ */
164
+ async invokeLifecycleCallbacksInternal(result) {
165
+ return super.invokeLifecycleCallbacks(result);
166
+ }
167
+ // =============================================================================
168
+ // Durable Span Lifecycle Hooks
169
+ // =============================================================================
170
+ /**
171
+ * Create a step span durably - on first execution, creates and exports span.
172
+ * On replay, returns cached span data without re-creating.
173
+ */
174
+ async createStepSpan(params) {
175
+ const { executionContext, operationId, options, parentSpan } = params;
176
+ const parentSpanId = parentSpan?.id ?? executionContext.tracingIds?.workflowSpanId;
177
+ const exportedSpan = await this.wrapDurableOperation(operationId, async () => {
178
+ const observability = this.mastra?.observability?.getSelectedInstance({});
179
+ if (!observability) return void 0;
180
+ const span = observability.startSpan({
181
+ ...options,
182
+ entityType: options.entityType,
183
+ traceId: executionContext.tracingIds?.traceId,
184
+ parentSpanId
185
+ });
186
+ return span?.exportSpan();
187
+ });
188
+ if (exportedSpan) {
189
+ const observability = this.mastra?.observability?.getSelectedInstance({});
190
+ return observability?.rebuildSpan(exportedSpan);
191
+ }
192
+ return void 0;
193
+ }
194
+ /**
195
+ * End a step span durably.
196
+ */
197
+ async endStepSpan(params) {
198
+ const { span, operationId, endOptions } = params;
199
+ if (!span) return;
200
+ await this.wrapDurableOperation(operationId, async () => {
201
+ span.end(endOptions);
202
+ });
203
+ }
204
+ /**
205
+ * Record error on step span durably.
206
+ */
207
+ async errorStepSpan(params) {
208
+ const { span, operationId, errorOptions } = params;
209
+ if (!span) return;
210
+ await this.wrapDurableOperation(operationId, async () => {
211
+ span.error(errorOptions);
212
+ });
213
+ }
214
+ /**
215
+ * Create a generic child span durably (for control-flow operations).
216
+ * On first execution, creates and exports span. On replay, returns cached span data.
217
+ */
218
+ async createChildSpan(params) {
219
+ const { executionContext, operationId, options, parentSpan } = params;
220
+ const parentSpanId = parentSpan?.id ?? executionContext.tracingIds?.workflowSpanId;
221
+ const exportedSpan = await this.wrapDurableOperation(operationId, async () => {
222
+ const observability = this.mastra?.observability?.getSelectedInstance({});
223
+ if (!observability) return void 0;
224
+ const span = observability.startSpan({
225
+ ...options,
226
+ traceId: executionContext.tracingIds?.traceId,
227
+ parentSpanId
228
+ });
229
+ return span?.exportSpan();
230
+ });
231
+ if (exportedSpan) {
232
+ const observability = this.mastra?.observability?.getSelectedInstance({});
233
+ return observability?.rebuildSpan(exportedSpan);
234
+ }
235
+ return void 0;
236
+ }
237
+ /**
238
+ * End a generic child span durably (for control-flow operations).
239
+ */
240
+ async endChildSpan(params) {
241
+ const { span, operationId, endOptions } = params;
242
+ if (!span) return;
243
+ await this.wrapDurableOperation(operationId, async () => {
244
+ span.end(endOptions);
245
+ });
246
+ }
247
+ /**
248
+ * Record error on a generic child span durably (for control-flow operations).
249
+ */
250
+ async errorChildSpan(params) {
251
+ const { span, operationId, errorOptions } = params;
252
+ if (!span) return;
253
+ await this.wrapDurableOperation(operationId, async () => {
254
+ span.error(errorOptions);
255
+ });
256
+ }
257
+ /**
258
+ * Execute nested InngestWorkflow using inngestStep.invoke() for durability.
259
+ * This MUST be called directly (not inside step.run()) due to Inngest constraints.
260
+ */
261
+ async executeWorkflowStep(params) {
262
+ if (!(params.step instanceof InngestWorkflow)) {
263
+ return null;
264
+ }
265
+ const {
266
+ step,
267
+ stepResults,
268
+ executionContext,
269
+ resume,
270
+ timeTravel,
271
+ prevOutput,
272
+ inputData,
273
+ pubsub,
274
+ startedAt,
275
+ perStep,
276
+ stepSpan
277
+ } = params;
278
+ const nestedTracingContext = executionContext.tracingIds?.traceId ? {
279
+ traceId: executionContext.tracingIds.traceId,
280
+ parentSpanId: stepSpan?.id
281
+ } : void 0;
282
+ const isResume = !!resume?.steps?.length;
283
+ let result;
284
+ let runId;
285
+ const isTimeTravel = !!(timeTravel && timeTravel.steps?.length > 1 && timeTravel.steps[0] === step.id);
286
+ try {
287
+ if (isResume) {
288
+ runId = stepResults[resume?.steps?.[0] ?? ""]?.suspendPayload?.__workflow_meta?.runId ?? randomUUID();
289
+ const workflowsStore = await this.mastra?.getStorage()?.getStore("workflows");
290
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
291
+ workflowName: step.id,
292
+ runId
293
+ });
294
+ const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
295
+ function: step.getFunction(),
296
+ data: {
297
+ inputData,
298
+ initialState: executionContext.state ?? snapshot?.value ?? {},
299
+ runId,
300
+ resume: {
301
+ runId,
302
+ steps: resume.steps.slice(1),
303
+ stepResults: snapshot?.context,
304
+ resumePayload: resume.resumePayload,
305
+ resumePath: resume.steps?.[1] ? snapshot?.suspendedPaths?.[resume.steps?.[1]] : void 0
306
+ },
307
+ outputOptions: { includeState: true },
308
+ perStep,
309
+ tracingOptions: nestedTracingContext
310
+ }
311
+ });
312
+ result = invokeResp.result;
313
+ runId = invokeResp.runId;
314
+ executionContext.state = invokeResp.result.state;
315
+ } else if (isTimeTravel) {
316
+ const workflowsStoreForTimeTravel = await this.mastra?.getStorage()?.getStore("workflows");
317
+ const snapshot = await workflowsStoreForTimeTravel?.loadWorkflowSnapshot({
318
+ workflowName: step.id,
319
+ runId: executionContext.runId
320
+ }) ?? { context: {} };
321
+ const timeTravelParams = createTimeTravelExecutionParams({
322
+ steps: timeTravel.steps.slice(1),
323
+ inputData: timeTravel.inputData,
324
+ resumeData: timeTravel.resumeData,
325
+ context: timeTravel.nestedStepResults?.[step.id] ?? {},
326
+ nestedStepsContext: timeTravel.nestedStepResults ?? {},
327
+ snapshot,
328
+ graph: step.buildExecutionGraph()
329
+ });
330
+ const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
331
+ function: step.getFunction(),
332
+ data: {
333
+ timeTravel: timeTravelParams,
334
+ initialState: executionContext.state ?? {},
335
+ runId: executionContext.runId,
336
+ outputOptions: { includeState: true },
337
+ perStep,
338
+ tracingOptions: nestedTracingContext
339
+ }
340
+ });
341
+ result = invokeResp.result;
342
+ runId = invokeResp.runId;
343
+ executionContext.state = invokeResp.result.state;
344
+ } else {
345
+ const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
346
+ function: step.getFunction(),
347
+ data: {
348
+ inputData,
349
+ initialState: executionContext.state ?? {},
350
+ outputOptions: { includeState: true },
351
+ perStep,
352
+ tracingOptions: nestedTracingContext
353
+ }
354
+ });
355
+ result = invokeResp.result;
356
+ runId = invokeResp.runId;
357
+ executionContext.state = invokeResp.result.state;
358
+ }
359
+ } catch (e) {
360
+ const errorCause = e?.cause;
361
+ if (errorCause && typeof errorCause === "object") {
362
+ result = errorCause;
363
+ runId = errorCause.runId || randomUUID();
364
+ } else {
365
+ runId = randomUUID();
366
+ result = {
367
+ status: "failed",
368
+ error: e instanceof Error ? e : new Error(String(e)),
369
+ steps: {},
370
+ input: inputData
371
+ };
372
+ }
373
+ }
374
+ const res = await this.inngestStep.run(
375
+ `workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
376
+ async () => {
377
+ if (result.status === "failed") {
378
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
379
+ type: "watch",
380
+ runId: executionContext.runId,
381
+ data: {
382
+ type: "workflow-step-result",
383
+ payload: {
384
+ id: step.id,
385
+ status: "failed",
386
+ error: result?.error,
387
+ payload: prevOutput
388
+ }
389
+ }
390
+ });
391
+ return { executionContext, result: { status: "failed", error: result?.error, endedAt: Date.now() } };
392
+ } else if (result.status === "suspended") {
393
+ const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
394
+ const stepRes = stepResult;
395
+ return stepRes?.status === "suspended";
396
+ });
397
+ for (const [stepName, stepResult] of suspendedSteps) {
398
+ const suspendPath = [stepName, ...stepResult?.suspendPayload?.__workflow_meta?.path ?? []];
399
+ executionContext.suspendedPaths[step.id] = executionContext.executionPath;
400
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
401
+ type: "watch",
402
+ runId: executionContext.runId,
403
+ data: {
404
+ type: "workflow-step-suspended",
405
+ payload: {
406
+ id: step.id,
407
+ status: "suspended"
408
+ }
409
+ }
410
+ });
411
+ return {
412
+ executionContext,
413
+ result: {
414
+ status: "suspended",
415
+ suspendedAt: Date.now(),
416
+ payload: stepResult.payload,
417
+ suspendPayload: {
418
+ ...stepResult?.suspendPayload,
419
+ __workflow_meta: { runId, path: suspendPath }
420
+ }
421
+ }
422
+ };
423
+ }
424
+ return {
425
+ executionContext,
426
+ result: {
427
+ status: "suspended",
428
+ suspendedAt: Date.now(),
429
+ payload: {}
430
+ }
431
+ };
432
+ } else if (result.status === "tripwire") {
433
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
434
+ type: "watch",
435
+ runId: executionContext.runId,
436
+ data: {
437
+ type: "workflow-step-result",
438
+ payload: {
439
+ id: step.id,
440
+ status: "tripwire",
441
+ error: result?.tripwire?.reason,
442
+ payload: prevOutput
443
+ }
444
+ }
445
+ });
446
+ return {
447
+ executionContext,
448
+ result: {
449
+ status: "tripwire",
450
+ tripwire: result?.tripwire,
451
+ endedAt: Date.now()
452
+ }
453
+ };
454
+ } else if (perStep || result.status === "paused") {
455
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
456
+ type: "watch",
457
+ runId: executionContext.runId,
458
+ data: {
459
+ type: "workflow-step-result",
460
+ payload: {
461
+ id: step.id,
462
+ status: "paused"
463
+ }
464
+ }
465
+ });
466
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
467
+ type: "watch",
468
+ runId: executionContext.runId,
469
+ data: {
470
+ type: "workflow-step-finish",
471
+ payload: {
472
+ id: step.id,
473
+ metadata: {}
474
+ }
475
+ }
476
+ });
477
+ return { executionContext, result: { status: "paused" } };
478
+ }
479
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
480
+ type: "watch",
481
+ runId: executionContext.runId,
482
+ data: {
483
+ type: "workflow-step-result",
484
+ payload: {
485
+ id: step.id,
486
+ status: "success",
487
+ output: result?.result
488
+ }
489
+ }
490
+ });
491
+ await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
492
+ type: "watch",
493
+ runId: executionContext.runId,
494
+ data: {
495
+ type: "workflow-step-finish",
496
+ payload: {
497
+ id: step.id,
498
+ metadata: {}
499
+ }
500
+ }
501
+ });
502
+ return { executionContext, result: { status: "success", output: result?.result, endedAt: Date.now() } };
503
+ }
504
+ );
505
+ Object.assign(executionContext, res.executionContext);
506
+ return {
507
+ ...res.result,
508
+ startedAt,
509
+ payload: inputData,
510
+ resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
511
+ resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
512
+ };
513
+ }
514
+ };
515
+ var InngestPubSub = class extends PubSub {
516
+ inngest;
517
+ workflowId;
518
+ publishFn;
519
+ subscriptions = /* @__PURE__ */ new Map();
520
+ constructor(inngest, workflowId, publishFn) {
521
+ super();
522
+ this.inngest = inngest;
523
+ this.workflowId = workflowId;
524
+ this.publishFn = publishFn;
525
+ }
526
+ /**
527
+ * Publish an event to Inngest's realtime system.
528
+ *
529
+ * Topic format: "workflow.events.v2.{runId}"
530
+ * Maps to Inngest channel: "workflow:{workflowId}:{runId}"
531
+ */
532
+ async publish(topic, event) {
533
+ if (!this.publishFn) {
534
+ return;
535
+ }
536
+ const match = topic.match(/^workflow\.events\.v2\.(.+)$/);
537
+ if (!match) {
538
+ return;
539
+ }
540
+ const runId = match[1];
541
+ try {
542
+ await this.publishFn({
543
+ channel: `workflow:${this.workflowId}:${runId}`,
544
+ topic: "watch",
545
+ data: event.data
546
+ });
547
+ } catch (err) {
548
+ console.error("InngestPubSub publish error:", err?.message ?? err);
549
+ }
550
+ }
551
+ /**
552
+ * Subscribe to events from Inngest's realtime system.
553
+ *
554
+ * Topic format: "workflow.events.v2.{runId}"
555
+ * Maps to Inngest channel: "workflow:{workflowId}:{runId}"
556
+ */
557
+ async subscribe(topic, cb) {
558
+ const match = topic.match(/^workflow\.events\.v2\.(.+)$/);
559
+ if (!match || !match[1]) {
560
+ return;
561
+ }
562
+ const runId = match[1];
563
+ if (this.subscriptions.has(topic)) {
564
+ this.subscriptions.get(topic).callbacks.add(cb);
565
+ return;
566
+ }
567
+ const callbacks = /* @__PURE__ */ new Set([cb]);
568
+ const channel = `workflow:${this.workflowId}:${runId}`;
569
+ const streamPromise = subscribe(
570
+ {
571
+ channel,
572
+ topics: ["watch"],
573
+ app: this.inngest
574
+ },
575
+ (message) => {
576
+ const event = {
577
+ id: crypto.randomUUID(),
578
+ type: "watch",
579
+ runId,
580
+ data: message.data,
581
+ createdAt: /* @__PURE__ */ new Date()
582
+ };
583
+ for (const callback of callbacks) {
584
+ callback(event);
585
+ }
586
+ }
587
+ );
588
+ this.subscriptions.set(topic, {
589
+ unsubscribe: () => {
590
+ streamPromise.then((stream) => stream.cancel()).catch((err) => {
591
+ console.error("InngestPubSub unsubscribe error:", err);
592
+ });
593
+ },
594
+ callbacks
595
+ });
596
+ }
597
+ /**
598
+ * Unsubscribe a callback from a topic.
599
+ * If no callbacks remain, the underlying Inngest subscription is cancelled.
600
+ */
601
+ async unsubscribe(topic, cb) {
602
+ const sub = this.subscriptions.get(topic);
603
+ if (!sub) {
604
+ return;
605
+ }
606
+ sub.callbacks.delete(cb);
607
+ if (sub.callbacks.size === 0) {
608
+ sub.unsubscribe();
609
+ this.subscriptions.delete(topic);
610
+ }
611
+ }
612
+ /**
613
+ * Flush any pending operations. No-op for Inngest.
614
+ */
615
+ async flush() {
616
+ }
617
+ /**
618
+ * Clean up all subscriptions during graceful shutdown.
619
+ */
620
+ async close() {
621
+ for (const [, sub] of this.subscriptions) {
622
+ sub.unsubscribe();
623
+ }
624
+ this.subscriptions.clear();
625
+ }
626
+ };
39
627
  var InngestRun = class extends Run {
40
628
  inngest;
41
629
  serializedStepGraph;
@@ -47,38 +635,90 @@ var InngestRun = class extends Run {
47
635
  this.#mastra = params.mastra;
48
636
  }
49
637
  async getRuns(eventId) {
50
- const response = await fetch(`${this.inngest.apiBaseUrl ?? "https://api.inngest.com"}/v1/events/${eventId}/runs`, {
51
- headers: {
52
- Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`
638
+ const maxRetries = 3;
639
+ let lastError = null;
640
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
641
+ try {
642
+ const response = await fetch(
643
+ `${this.inngest.apiBaseUrl ?? "https://api.inngest.com"}/v1/events/${eventId}/runs`,
644
+ {
645
+ headers: {
646
+ Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`
647
+ }
648
+ }
649
+ );
650
+ if (response.status === 429) {
651
+ const retryAfter = parseInt(response.headers.get("retry-after") || "2", 10);
652
+ await new Promise((resolve) => setTimeout(resolve, retryAfter * 1e3));
653
+ continue;
654
+ }
655
+ if (!response.ok) {
656
+ throw new Error(`Inngest API error: ${response.status} ${response.statusText}`);
657
+ }
658
+ const text = await response.text();
659
+ if (!text) {
660
+ await new Promise((resolve) => setTimeout(resolve, 1e3 * (attempt + 1)));
661
+ continue;
662
+ }
663
+ const json = JSON.parse(text);
664
+ return json.data;
665
+ } catch (error) {
666
+ lastError = error;
667
+ if (attempt < maxRetries - 1) {
668
+ await new Promise((resolve) => setTimeout(resolve, 1e3 * Math.pow(2, attempt)));
669
+ }
53
670
  }
54
- });
55
- const json = await response.json();
56
- return json.data;
671
+ }
672
+ throw new NonRetriableError(`Failed to get runs after ${maxRetries} attempts: ${lastError?.message}`);
57
673
  }
58
- async getRunOutput(eventId) {
59
- let runs = await this.getRuns(eventId);
674
+ async getRunOutput(eventId, maxWaitMs = 3e5) {
675
+ const startTime = Date.now();
60
676
  const storage = this.#mastra?.getStorage();
61
- while (runs?.[0]?.status !== "Completed" || runs?.[0]?.event_id !== eventId) {
62
- await new Promise((resolve) => setTimeout(resolve, 1e3));
63
- runs = await this.getRuns(eventId);
677
+ const workflowsStore = await storage?.getStore("workflows");
678
+ while (Date.now() - startTime < maxWaitMs) {
679
+ let runs;
680
+ try {
681
+ runs = await this.getRuns(eventId);
682
+ } catch (error) {
683
+ if (error instanceof NonRetriableError) {
684
+ throw error;
685
+ }
686
+ throw new NonRetriableError(
687
+ `Failed to poll workflow status: ${error instanceof Error ? error.message : String(error)}`
688
+ );
689
+ }
690
+ if (runs?.[0]?.status === "Completed" && runs?.[0]?.event_id === eventId) {
691
+ return runs[0];
692
+ }
64
693
  if (runs?.[0]?.status === "Failed") {
65
- const snapshot = await storage?.loadWorkflowSnapshot({
694
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
66
695
  workflowName: this.workflowId,
67
696
  runId: this.runId
68
697
  });
698
+ if (snapshot?.context) {
699
+ snapshot.context = hydrateSerializedStepErrors(snapshot.context);
700
+ }
69
701
  return {
70
- output: { result: { steps: snapshot?.context, status: "failed", error: runs?.[0]?.output?.message } }
702
+ output: {
703
+ result: {
704
+ steps: snapshot?.context,
705
+ status: "failed",
706
+ // Get the original error from NonRetriableError's cause (which contains the workflow result)
707
+ error: getErrorFromUnknown(runs?.[0]?.output?.cause?.error, { serializeStack: false })
708
+ }
709
+ }
71
710
  };
72
711
  }
73
712
  if (runs?.[0]?.status === "Cancelled") {
74
- const snapshot = await storage?.loadWorkflowSnapshot({
713
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
75
714
  workflowName: this.workflowId,
76
715
  runId: this.runId
77
716
  });
78
717
  return { output: { result: { steps: snapshot?.context, status: "canceled" } } };
79
718
  }
719
+ await new Promise((resolve) => setTimeout(resolve, 1e3 + Math.random() * 1e3));
80
720
  }
81
- return runs?.[0];
721
+ throw new NonRetriableError(`Workflow did not complete within ${maxWaitMs}ms`);
82
722
  }
83
723
  async cancel() {
84
724
  const storage = this.#mastra?.getStorage();
@@ -88,12 +728,13 @@ var InngestRun = class extends Run {
88
728
  runId: this.runId
89
729
  }
90
730
  });
91
- const snapshot = await storage?.loadWorkflowSnapshot({
731
+ const workflowsStore = await storage?.getStore("workflows");
732
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
92
733
  workflowName: this.workflowId,
93
734
  runId: this.runId
94
735
  });
95
736
  if (snapshot) {
96
- await storage?.persistWorkflowSnapshot({
737
+ await workflowsStore?.persistWorkflowSnapshot({
97
738
  workflowName: this.workflowId,
98
739
  runId: this.runId,
99
740
  resourceId: this.resourceId,
@@ -105,17 +746,18 @@ var InngestRun = class extends Run {
105
746
  });
106
747
  }
107
748
  }
108
- async start(params) {
109
- return this._start(params);
749
+ async start(args) {
750
+ return this._start(args);
110
751
  }
111
- async _start({
112
- inputData,
113
- initialState,
114
- outputOptions,
115
- tracingOptions,
116
- format
117
- }) {
118
- await this.#mastra.getStorage()?.persistWorkflowSnapshot({
752
+ /**
753
+ * Starts the workflow execution without waiting for completion (fire-and-forget).
754
+ * Returns immediately with the runId after sending the event to Inngest.
755
+ * The workflow executes independently in Inngest.
756
+ * Use this when you don't need to wait for the result or want to avoid polling failures.
757
+ */
758
+ async startAsync(args) {
759
+ const workflowsStore = await this.#mastra.getStorage()?.getStore("workflows");
760
+ await workflowsStore?.persistWorkflowSnapshot({
119
761
  workflowName: this.workflowId,
120
762
  runId: this.runId,
121
763
  resourceId: this.resourceId,
@@ -133,8 +775,8 @@ var InngestRun = class extends Run {
133
775
  timestamp: Date.now()
134
776
  }
135
777
  });
136
- const inputDataToUse = await this._validateInput(inputData);
137
- const initialStateToUse = await this._validateInitialState(initialState ?? {});
778
+ const inputDataToUse = await this._validateInput(args.inputData);
779
+ const initialStateToUse = await this._validateInitialState(args.initialState ?? {});
138
780
  const eventOutput = await this.inngest.send({
139
781
  name: `workflow.${this.workflowId}`,
140
782
  data: {
@@ -142,31 +784,80 @@ var InngestRun = class extends Run {
142
784
  initialState: initialStateToUse,
143
785
  runId: this.runId,
144
786
  resourceId: this.resourceId,
145
- outputOptions,
146
- tracingOptions,
147
- format
787
+ outputOptions: args.outputOptions,
788
+ tracingOptions: args.tracingOptions,
789
+ requestContext: args.requestContext ? Object.fromEntries(args.requestContext.entries()) : {},
790
+ perStep: args.perStep
148
791
  }
149
792
  });
150
793
  const eventId = eventOutput.ids[0];
151
794
  if (!eventId) {
152
795
  throw new Error("Event ID is not set");
153
796
  }
154
- const runOutput = await this.getRunOutput(eventId);
155
- const result = runOutput?.output?.result;
156
- if (result.status === "failed") {
157
- result.error = new Error(result.error);
158
- }
159
- if (result.status !== "suspended") {
160
- this.cleanup?.();
161
- }
162
- return result;
797
+ return { runId: this.runId };
163
798
  }
164
- async resume(params) {
165
- const p = this._resume(params).then((result) => {
166
- if (result.status !== "suspended") {
167
- this.closeStreamAction?.().catch(() => {
168
- });
169
- }
799
+ async _start({
800
+ inputData,
801
+ initialState,
802
+ outputOptions,
803
+ tracingOptions,
804
+ format,
805
+ requestContext,
806
+ perStep
807
+ }) {
808
+ const workflowsStore = await this.#mastra.getStorage()?.getStore("workflows");
809
+ await workflowsStore?.persistWorkflowSnapshot({
810
+ workflowName: this.workflowId,
811
+ runId: this.runId,
812
+ resourceId: this.resourceId,
813
+ snapshot: {
814
+ runId: this.runId,
815
+ serializedStepGraph: this.serializedStepGraph,
816
+ status: "running",
817
+ value: {},
818
+ context: {},
819
+ activePaths: [],
820
+ suspendedPaths: {},
821
+ activeStepsPath: {},
822
+ resumeLabels: {},
823
+ waitingPaths: {},
824
+ timestamp: Date.now()
825
+ }
826
+ });
827
+ const inputDataToUse = await this._validateInput(inputData);
828
+ const initialStateToUse = await this._validateInitialState(initialState ?? {});
829
+ const eventOutput = await this.inngest.send({
830
+ name: `workflow.${this.workflowId}`,
831
+ data: {
832
+ inputData: inputDataToUse,
833
+ initialState: initialStateToUse,
834
+ runId: this.runId,
835
+ resourceId: this.resourceId,
836
+ outputOptions,
837
+ tracingOptions,
838
+ format,
839
+ requestContext: requestContext ? Object.fromEntries(requestContext.entries()) : {},
840
+ perStep
841
+ }
842
+ });
843
+ const eventId = eventOutput.ids[0];
844
+ if (!eventId) {
845
+ throw new Error("Event ID is not set");
846
+ }
847
+ const runOutput = await this.getRunOutput(eventId);
848
+ const result = runOutput?.output?.result;
849
+ this.hydrateFailedResult(result);
850
+ if (result.status !== "suspended") {
851
+ this.cleanup?.();
852
+ }
853
+ return result;
854
+ }
855
+ async resume(params) {
856
+ const p = this._resume(params).then((result) => {
857
+ if (result.status !== "suspended") {
858
+ this.closeStreamAction?.().catch(() => {
859
+ });
860
+ }
170
861
  return result;
171
862
  });
172
863
  this.executionResults = p;
@@ -182,12 +873,16 @@ var InngestRun = class extends Run {
182
873
  (step) => typeof step === "string" ? step : step?.id
183
874
  );
184
875
  }
185
- const snapshot = await storage?.loadWorkflowSnapshot({
876
+ const workflowsStore = await storage?.getStore("workflows");
877
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
186
878
  workflowName: this.workflowId,
187
879
  runId: this.runId
188
880
  });
189
881
  const suspendedStep = this.workflowSteps[steps?.[0] ?? ""];
190
882
  const resumeDataToUse = await this._validateResumeData(params.resumeData, suspendedStep);
883
+ const persistedRequestContext = snapshot?.requestContext ?? {};
884
+ const newRequestContext = params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {};
885
+ const mergedRequestContext = { ...persistedRequestContext, ...newRequestContext };
191
886
  const eventOutput = await this.inngest.send({
192
887
  name: `workflow.${this.workflowId}`,
193
888
  data: {
@@ -201,7 +896,9 @@ var InngestRun = class extends Run {
201
896
  stepResults: snapshot?.context,
202
897
  resumePayload: resumeDataToUse,
203
898
  resumePath: steps?.[0] ? snapshot?.suspendedPaths?.[steps?.[0]] : void 0
204
- }
899
+ },
900
+ requestContext: mergedRequestContext,
901
+ perStep: params.perStep
205
902
  }
206
903
  });
207
904
  const eventId = eventOutput.ids[0];
@@ -210,9 +907,7 @@ var InngestRun = class extends Run {
210
907
  }
211
908
  const runOutput = await this.getRunOutput(eventId);
212
909
  const result = runOutput?.output?.result;
213
- if (result.status === "failed") {
214
- result.error = new Error(result.error);
215
- }
910
+ this.hydrateFailedResult(result);
216
911
  return result;
217
912
  }
218
913
  async timeTravel(params) {
@@ -242,12 +937,13 @@ var InngestRun = class extends Run {
242
937
  throw new Error("No steps provided to timeTravel");
243
938
  }
244
939
  const storage = this.#mastra?.getStorage();
245
- const snapshot = await storage?.loadWorkflowSnapshot({
940
+ const workflowsStore = await storage?.getStore("workflows");
941
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
246
942
  workflowName: this.workflowId,
247
943
  runId: this.runId
248
944
  });
249
945
  if (!snapshot) {
250
- await storage?.persistWorkflowSnapshot({
946
+ await workflowsStore?.persistWorkflowSnapshot({
251
947
  workflowName: this.workflowId,
252
948
  runId: this.runId,
253
949
  resourceId: this.resourceId,
@@ -281,7 +977,8 @@ var InngestRun = class extends Run {
281
977
  nestedStepsContext: params.nestedStepsContext,
282
978
  snapshot: snapshot ?? { context: {} },
283
979
  graph: this.executionGraph,
284
- initialState: params.initialState
980
+ initialState: params.initialState,
981
+ perStep: params.perStep
285
982
  });
286
983
  const eventOutput = await this.inngest.send({
287
984
  name: `workflow.${this.workflowId}`,
@@ -292,7 +989,9 @@ var InngestRun = class extends Run {
292
989
  stepResults: timeTravelData.stepResults,
293
990
  timeTravel: timeTravelData,
294
991
  tracingOptions: params.tracingOptions,
295
- outputOptions: params.outputOptions
992
+ outputOptions: params.outputOptions,
993
+ requestContext: params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {},
994
+ perStep: params.perStep
296
995
  }
297
996
  });
298
997
  const eventId = eventOutput.ids[0];
@@ -301,9 +1000,7 @@ var InngestRun = class extends Run {
301
1000
  }
302
1001
  const runOutput = await this.getRunOutput(eventId);
303
1002
  const result = runOutput?.output?.result;
304
- if (result.status === "failed") {
305
- result.error = new Error(result.error);
306
- }
1003
+ this.hydrateFailedResult(result);
307
1004
  return result;
308
1005
  }
309
1006
  watch(cb) {
@@ -332,14 +1029,13 @@ var InngestRun = class extends Run {
332
1029
  streamLegacy({ inputData, requestContext } = {}) {
333
1030
  const { readable, writable } = new TransformStream();
334
1031
  const writer = writable.getWriter();
1032
+ void writer.write({
1033
+ // @ts-expect-error
1034
+ type: "start",
1035
+ payload: { runId: this.runId }
1036
+ });
335
1037
  const unwatch = this.watch(async (event) => {
336
1038
  try {
337
- await writer.write({
338
- // @ts-ignore
339
- type: "start",
340
- // @ts-ignore
341
- payload: { runId: this.runId }
342
- });
343
1039
  const e = {
344
1040
  ...event,
345
1041
  type: event.type.replace("workflow-", "")
@@ -355,7 +1051,7 @@ var InngestRun = class extends Run {
355
1051
  this.closeStreamAction = async () => {
356
1052
  await writer.write({
357
1053
  type: "finish",
358
- // @ts-ignore
1054
+ // @ts-expect-error
359
1055
  payload: { runId: this.runId }
360
1056
  });
361
1057
  unwatch();
@@ -385,7 +1081,8 @@ var InngestRun = class extends Run {
385
1081
  tracingOptions,
386
1082
  closeOnSuspend = true,
387
1083
  initialState,
388
- outputOptions
1084
+ outputOptions,
1085
+ perStep
389
1086
  } = {}) {
390
1087
  if (this.closeStreamAction && this.streamOutput) {
391
1088
  return this.streamOutput;
@@ -421,7 +1118,8 @@ var InngestRun = class extends Run {
421
1118
  initialState,
422
1119
  tracingOptions,
423
1120
  outputOptions,
424
- format: "vnext"
1121
+ format: "vnext",
1122
+ perStep
425
1123
  });
426
1124
  let executionResults;
427
1125
  try {
@@ -452,9 +1150,6 @@ var InngestRun = class extends Run {
452
1150
  });
453
1151
  return this.streamOutput;
454
1152
  }
455
- streamVNext(args = {}) {
456
- return this.stream(args);
457
- }
458
1153
  timeTravelStream({
459
1154
  inputData,
460
1155
  resumeData,
@@ -463,8 +1158,10 @@ var InngestRun = class extends Run {
463
1158
  context,
464
1159
  nestedStepsContext,
465
1160
  requestContext,
1161
+ // tracingContext,
466
1162
  tracingOptions,
467
- outputOptions
1163
+ outputOptions,
1164
+ perStep
468
1165
  }) {
469
1166
  this.closeStreamAction = async () => {
470
1167
  };
@@ -485,7 +1182,7 @@ var InngestRun = class extends Run {
485
1182
  self.closeStreamAction = async () => {
486
1183
  unwatch();
487
1184
  try {
488
- await controller.close();
1185
+ controller.close();
489
1186
  } catch (err) {
490
1187
  console.error("Error closing stream:", err);
491
1188
  }
@@ -499,7 +1196,8 @@ var InngestRun = class extends Run {
499
1196
  initialState,
500
1197
  requestContext,
501
1198
  tracingOptions,
502
- outputOptions
1199
+ outputOptions,
1200
+ perStep
503
1201
  });
504
1202
  self.executionResults = executionResultsPromise;
505
1203
  let executionResults;
@@ -524,14 +1222,30 @@ var InngestRun = class extends Run {
524
1222
  });
525
1223
  return this.streamOutput;
526
1224
  }
1225
+ /**
1226
+ * Hydrates errors in a failed workflow result back to proper Error instances.
1227
+ * This ensures error.cause chains and custom properties are preserved.
1228
+ */
1229
+ hydrateFailedResult(result) {
1230
+ if (result.status === "failed") {
1231
+ result.error = getErrorFromUnknown(result.error, { serializeStack: false });
1232
+ if (result.steps) {
1233
+ hydrateSerializedStepErrors(result.steps);
1234
+ }
1235
+ }
1236
+ }
527
1237
  };
1238
+
1239
+ // src/workflow.ts
528
1240
  var InngestWorkflow = class _InngestWorkflow extends Workflow {
529
1241
  #mastra;
530
1242
  inngest;
531
1243
  function;
1244
+ cronFunction;
532
1245
  flowControlConfig;
1246
+ cronConfig;
533
1247
  constructor(params, inngest) {
534
- const { concurrency, rateLimit, throttle, debounce, priority, ...workflowParams } = params;
1248
+ const { concurrency, rateLimit, throttle, debounce, priority, cron, inputData, initialState, ...workflowParams } = params;
535
1249
  super(workflowParams);
536
1250
  this.engineType = "inngest";
537
1251
  const flowControlEntries = Object.entries({ concurrency, rateLimit, throttle, debounce, priority }).filter(
@@ -540,6 +1254,9 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
540
1254
  this.flowControlConfig = flowControlEntries.length > 0 ? Object.fromEntries(flowControlEntries) : void 0;
541
1255
  this.#mastra = params.mastra;
542
1256
  this.inngest = inngest;
1257
+ if (cron) {
1258
+ this.cronConfig = { cron, inputData, initialState };
1259
+ }
543
1260
  }
544
1261
  async listWorkflowRuns(args) {
545
1262
  const storage = this.#mastra?.getStorage();
@@ -547,18 +1264,14 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
547
1264
  this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
548
1265
  return { runs: [], total: 0 };
549
1266
  }
550
- return storage.listWorkflowRuns({ workflowName: this.id, ...args ?? {} });
551
- }
552
- async getWorkflowRunById(runId) {
553
- const storage = this.#mastra?.getStorage();
554
- if (!storage) {
555
- this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
556
- return this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null;
1267
+ const workflowsStore = await storage.getStore("workflows");
1268
+ if (!workflowsStore) {
1269
+ return { runs: [], total: 0 };
557
1270
  }
558
- const run = await storage.getWorkflowRunById({ runId, workflowName: this.id });
559
- return run ?? (this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null);
1271
+ return workflowsStore.listWorkflowRuns({ workflowName: this.id, ...args ?? {} });
560
1272
  }
561
1273
  __registerMastra(mastra) {
1274
+ super.__registerMastra(mastra);
562
1275
  this.#mastra = mastra;
563
1276
  this.executionEngine.__registerMastra(mastra);
564
1277
  const updateNested = (step) => {
@@ -578,7 +1291,8 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
578
1291
  }
579
1292
  async createRun(options) {
580
1293
  const runIdToUse = options?.runId || randomUUID();
581
- const run = this.runs.get(runIdToUse) ?? new InngestRun(
1294
+ const existingInMemoryRun = this.runs.get(runIdToUse);
1295
+ const newRun = new InngestRun(
582
1296
  {
583
1297
  workflowId: this.id,
584
1298
  runId: runIdToUse,
@@ -595,14 +1309,19 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
595
1309
  },
596
1310
  this.inngest
597
1311
  );
1312
+ const run = existingInMemoryRun ?? newRun;
598
1313
  this.runs.set(runIdToUse, run);
599
1314
  const shouldPersistSnapshot = this.options.shouldPersistSnapshot({
600
1315
  workflowStatus: run.workflowRunStatus,
601
1316
  stepResults: {}
602
1317
  });
603
- const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse, false);
604
- if (!workflowSnapshotInStorage && shouldPersistSnapshot) {
605
- await this.mastra?.getStorage()?.persistWorkflowSnapshot({
1318
+ const existingStoredRun = await this.getWorkflowRunById(runIdToUse, {
1319
+ withNestedWorkflows: false
1320
+ });
1321
+ const existsInStorage = existingStoredRun && !existingStoredRun.isFromInMemory;
1322
+ if (!existsInStorage && shouldPersistSnapshot) {
1323
+ const workflowsStore = await this.mastra?.getStorage()?.getStore("workflows");
1324
+ await workflowsStore?.persistWorkflowSnapshot({
606
1325
  workflowName: this.id,
607
1326
  runId: runIdToUse,
608
1327
  resourceId: options?.resourceId,
@@ -625,6 +1344,30 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
625
1344
  }
626
1345
  return run;
627
1346
  }
1347
+ //createCronFunction is only called if cronConfig.cron is defined.
1348
+ createCronFunction() {
1349
+ if (this.cronFunction) {
1350
+ return this.cronFunction;
1351
+ }
1352
+ this.cronFunction = this.inngest.createFunction(
1353
+ {
1354
+ id: `workflow.${this.id}.cron`,
1355
+ retries: 0,
1356
+ cancelOn: [{ event: `cancel.workflow.${this.id}` }],
1357
+ ...this.flowControlConfig
1358
+ },
1359
+ { cron: this.cronConfig?.cron ?? "" },
1360
+ async () => {
1361
+ const run = await this.createRun();
1362
+ const result = await run.start({
1363
+ inputData: this.cronConfig?.inputData,
1364
+ initialState: this.cronConfig?.initialState
1365
+ });
1366
+ return { result, runId: run.runId };
1367
+ }
1368
+ );
1369
+ return this.cronFunction;
1370
+ }
628
1371
  getFunction() {
629
1372
  if (this.function) {
630
1373
  return this.function;
@@ -632,68 +1375,128 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
632
1375
  this.function = this.inngest.createFunction(
633
1376
  {
634
1377
  id: `workflow.${this.id}`,
635
- retries: Math.min(this.retryConfig?.attempts ?? 0, 20),
1378
+ retries: 0,
636
1379
  cancelOn: [{ event: `cancel.workflow.${this.id}` }],
637
1380
  // Spread flow control configuration
638
1381
  ...this.flowControlConfig
639
1382
  },
640
1383
  { event: `workflow.${this.id}` },
641
1384
  async ({ event, step, attempt, publish }) => {
642
- let { inputData, initialState, runId, resourceId, resume, outputOptions, format, timeTravel } = event.data;
1385
+ let {
1386
+ inputData,
1387
+ initialState,
1388
+ runId,
1389
+ resourceId,
1390
+ resume,
1391
+ outputOptions,
1392
+ format,
1393
+ timeTravel,
1394
+ perStep,
1395
+ tracingOptions
1396
+ } = event.data;
643
1397
  if (!runId) {
644
1398
  runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
645
1399
  return randomUUID();
646
1400
  });
647
1401
  }
648
- const emitter = {
649
- emit: async (event2, data) => {
650
- if (!publish) {
651
- return;
652
- }
653
- try {
654
- await publish({
655
- channel: `workflow:${this.id}:${runId}`,
656
- topic: event2,
657
- data
658
- });
659
- } catch (err) {
660
- this.logger.error("Error emitting event: " + (err?.stack ?? err?.message ?? err));
661
- }
662
- },
663
- on: (_event, _callback) => {
664
- },
665
- off: (_event, _callback) => {
666
- },
667
- once: (_event, _callback) => {
668
- }
669
- };
1402
+ const pubsub = new InngestPubSub(this.inngest, this.id, publish);
1403
+ const requestContext = new RequestContext(Object.entries(event.data.requestContext ?? {}));
1404
+ const mastra = this.#mastra;
1405
+ const tracingPolicy = this.options.tracingPolicy;
1406
+ const workflowSpanData = await step.run(`workflow.${this.id}.span.start`, async () => {
1407
+ const observability = mastra?.observability?.getSelectedInstance({ requestContext });
1408
+ if (!observability) return void 0;
1409
+ const span = observability.startSpan({
1410
+ type: SpanType.WORKFLOW_RUN,
1411
+ name: `workflow run: '${this.id}'`,
1412
+ entityType: EntityType.WORKFLOW_RUN,
1413
+ entityId: this.id,
1414
+ input: inputData,
1415
+ metadata: {
1416
+ resourceId,
1417
+ runId
1418
+ },
1419
+ tracingPolicy,
1420
+ tracingOptions,
1421
+ requestContext
1422
+ });
1423
+ return span?.exportSpan();
1424
+ });
670
1425
  const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
671
- const result = await engine.execute({
672
- workflowId: this.id,
673
- runId,
674
- resourceId,
675
- graph: this.executionGraph,
676
- serializedStepGraph: this.serializedStepGraph,
677
- input: inputData,
678
- initialState,
679
- emitter,
680
- retryConfig: this.retryConfig,
681
- requestContext: new RequestContext(),
682
- // TODO
683
- resume,
684
- timeTravel,
685
- format,
686
- abortController: new AbortController(),
687
- // currentSpan: undefined, // TODO: Pass actual parent Span from workflow execution context
688
- outputOptions,
689
- writableStream: new WritableStream({
690
- write(chunk) {
691
- void emitter.emit("watch", chunk).catch(() => {
692
- });
1426
+ let result;
1427
+ try {
1428
+ result = await engine.execute({
1429
+ workflowId: this.id,
1430
+ runId,
1431
+ resourceId,
1432
+ graph: this.executionGraph,
1433
+ serializedStepGraph: this.serializedStepGraph,
1434
+ input: inputData,
1435
+ initialState,
1436
+ pubsub,
1437
+ retryConfig: this.retryConfig,
1438
+ requestContext,
1439
+ resume,
1440
+ timeTravel,
1441
+ perStep,
1442
+ format,
1443
+ abortController: new AbortController(),
1444
+ // For Inngest, we don't pass workflowSpan - step spans use tracingIds instead
1445
+ workflowSpan: void 0,
1446
+ // Pass tracing IDs for durable span operations
1447
+ tracingIds: workflowSpanData ? {
1448
+ traceId: workflowSpanData.traceId,
1449
+ workflowSpanId: workflowSpanData.id
1450
+ } : void 0,
1451
+ outputOptions,
1452
+ outputWriter: async (chunk) => {
1453
+ try {
1454
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1455
+ type: "watch",
1456
+ runId,
1457
+ data: chunk
1458
+ });
1459
+ } catch (err) {
1460
+ this.logger.debug?.("Failed to publish watch event:", err);
1461
+ }
693
1462
  }
694
- })
695
- });
1463
+ });
1464
+ } catch (error) {
1465
+ throw error;
1466
+ }
696
1467
  await step.run(`workflow.${this.id}.finalize`, async () => {
1468
+ if (result.status !== "paused") {
1469
+ await engine.invokeLifecycleCallbacksInternal({
1470
+ status: result.status,
1471
+ result: "result" in result ? result.result : void 0,
1472
+ error: "error" in result ? result.error : void 0,
1473
+ steps: result.steps,
1474
+ tripwire: "tripwire" in result ? result.tripwire : void 0,
1475
+ runId,
1476
+ workflowId: this.id,
1477
+ resourceId,
1478
+ input: inputData,
1479
+ requestContext,
1480
+ state: result.state ?? initialState ?? {}
1481
+ });
1482
+ }
1483
+ if (workflowSpanData) {
1484
+ const observability = mastra?.observability?.getSelectedInstance({ requestContext });
1485
+ if (observability) {
1486
+ const workflowSpan = observability.rebuildSpan(workflowSpanData);
1487
+ if (result.status === "failed") {
1488
+ workflowSpan.error({
1489
+ error: result.error instanceof Error ? result.error : new Error(String(result.error)),
1490
+ attributes: { status: "failed" }
1491
+ });
1492
+ } else {
1493
+ workflowSpan.end({
1494
+ output: result.status === "success" ? result.result : void 0,
1495
+ attributes: { status: result.status }
1496
+ });
1497
+ }
1498
+ }
1499
+ }
697
1500
  if (result.status === "failed") {
698
1501
  throw new NonRetriableError(`Workflow failed`, {
699
1502
  cause: result
@@ -720,147 +1523,203 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
720
1523
  });
721
1524
  }
722
1525
  getFunctions() {
723
- return [this.getFunction(), ...this.getNestedFunctions(this.executionGraph.steps)];
1526
+ return [
1527
+ this.getFunction(),
1528
+ ...this.cronConfig?.cron ? [this.createCronFunction()] : [],
1529
+ ...this.getNestedFunctions(this.executionGraph.steps)
1530
+ ];
724
1531
  }
725
1532
  };
726
- function isAgent(params) {
727
- return params?.component === "AGENT";
1533
+ function prepareServeOptions({ mastra, inngest, functions: userFunctions = [], registerOptions }) {
1534
+ const wfs = mastra.listWorkflows();
1535
+ const workflowFunctions = Array.from(
1536
+ new Set(
1537
+ Object.values(wfs).flatMap((wf) => {
1538
+ if (wf instanceof InngestWorkflow) {
1539
+ wf.__registerMastra(mastra);
1540
+ return wf.getFunctions();
1541
+ }
1542
+ return [];
1543
+ })
1544
+ )
1545
+ );
1546
+ return {
1547
+ ...registerOptions,
1548
+ client: inngest,
1549
+ functions: [...workflowFunctions, ...userFunctions]
1550
+ };
1551
+ }
1552
+ function createServe(adapter) {
1553
+ return (options) => {
1554
+ const serveOptions = prepareServeOptions(options);
1555
+ return adapter(serveOptions);
1556
+ };
728
1557
  }
729
- function isTool(params) {
730
- return params instanceof Tool;
1558
+ var serve = createServe(serve$1);
1559
+
1560
+ // src/types.ts
1561
+ var _compatibilityCheck = true;
1562
+
1563
+ // src/index.ts
1564
+ function isInngestWorkflow(input) {
1565
+ return input instanceof InngestWorkflow;
1566
+ }
1567
+ function isAgent(input) {
1568
+ return input instanceof Agent;
731
1569
  }
732
- function createStep(params, agentOptions) {
1570
+ function isToolStep(input) {
1571
+ return input instanceof Tool;
1572
+ }
1573
+ function isStepParams(input) {
1574
+ return input !== null && typeof input === "object" && "id" in input && "execute" in input && !(input instanceof Agent) && !(input instanceof Tool) && !(input instanceof InngestWorkflow);
1575
+ }
1576
+ function isProcessor(obj) {
1577
+ return obj !== null && typeof obj === "object" && "id" in obj && typeof obj.id === "string" && !(obj instanceof Agent) && !(obj instanceof Tool) && !(obj instanceof InngestWorkflow) && (typeof obj.processInput === "function" || typeof obj.processInputStep === "function" || typeof obj.processOutputStream === "function" || typeof obj.processOutputResult === "function" || typeof obj.processOutputStep === "function");
1578
+ }
1579
+ function createStep(params, agentOrToolOptions) {
1580
+ if (isInngestWorkflow(params)) {
1581
+ return params;
1582
+ }
733
1583
  if (isAgent(params)) {
734
- return {
735
- id: params.name,
736
- description: params.getDescription(),
737
- inputSchema: z.object({
1584
+ return createStepFromAgent(params, agentOrToolOptions);
1585
+ }
1586
+ if (isToolStep(params)) {
1587
+ return createStepFromTool(params, agentOrToolOptions);
1588
+ }
1589
+ if (isStepParams(params)) {
1590
+ return createStepFromParams(params);
1591
+ }
1592
+ if (isProcessor(params)) {
1593
+ return createStepFromProcessor(params);
1594
+ }
1595
+ throw new Error("Invalid input: expected StepParams, Agent, ToolStep, Processor, or InngestWorkflow");
1596
+ }
1597
+ function createStepFromParams(params) {
1598
+ return {
1599
+ id: params.id,
1600
+ description: params.description,
1601
+ inputSchema: toStandardSchema(params.inputSchema),
1602
+ stateSchema: params.stateSchema ? toStandardSchema(params.stateSchema) : void 0,
1603
+ outputSchema: toStandardSchema(params.outputSchema),
1604
+ resumeSchema: params.resumeSchema ? toStandardSchema(params.resumeSchema) : void 0,
1605
+ suspendSchema: params.suspendSchema ? toStandardSchema(params.suspendSchema) : void 0,
1606
+ scorers: params.scorers,
1607
+ retries: params.retries,
1608
+ execute: params.execute.bind(params)
1609
+ };
1610
+ }
1611
+ function createStepFromAgent(params, agentOrToolOptions) {
1612
+ const options = agentOrToolOptions ?? {};
1613
+ const outputSchema = options?.structuredOutput?.schema ?? z.object({ text: z.string() });
1614
+ const { retries, scorers, ...agentOptions } = options ?? {};
1615
+ return {
1616
+ id: params.name,
1617
+ description: params.getDescription(),
1618
+ inputSchema: toStandardSchema(
1619
+ z.object({
738
1620
  prompt: z.string()
739
- // resourceId: z.string().optional(),
740
- // threadId: z.string().optional(),
741
- }),
742
- outputSchema: z.object({
743
- text: z.string()
744
- }),
745
- execute: async ({
746
- inputData,
747
- [EMITTER_SYMBOL]: emitter,
748
- [STREAM_FORMAT_SYMBOL]: streamFormat,
749
- requestContext,
750
- tracingContext,
751
- abortSignal,
752
- abort,
753
- writer
754
- }) => {
755
- let streamPromise = {};
756
- streamPromise.promise = new Promise((resolve, reject) => {
757
- streamPromise.resolve = resolve;
758
- streamPromise.reject = reject;
1621
+ })
1622
+ ),
1623
+ outputSchema: toStandardSchema(outputSchema),
1624
+ retries,
1625
+ scorers,
1626
+ execute: async ({
1627
+ inputData,
1628
+ runId,
1629
+ [PUBSUB_SYMBOL]: pubsub,
1630
+ [STREAM_FORMAT_SYMBOL]: streamFormat,
1631
+ requestContext,
1632
+ tracingContext,
1633
+ abortSignal,
1634
+ abort,
1635
+ writer
1636
+ }) => {
1637
+ let streamPromise = {};
1638
+ streamPromise.promise = new Promise((resolve, reject) => {
1639
+ streamPromise.resolve = resolve;
1640
+ streamPromise.reject = reject;
1641
+ });
1642
+ let structuredResult = null;
1643
+ const toolData = {
1644
+ name: params.name,
1645
+ args: inputData
1646
+ };
1647
+ let stream;
1648
+ if ((await params.getModel()).specificationVersion === "v1") {
1649
+ const { fullStream } = await params.streamLegacy(inputData.prompt, {
1650
+ ...agentOptions ?? {},
1651
+ requestContext,
1652
+ tracingContext,
1653
+ onFinish: (result) => {
1654
+ const resultWithObject = result;
1655
+ if (agentOptions?.structuredOutput?.schema && resultWithObject.object) {
1656
+ structuredResult = resultWithObject.object;
1657
+ }
1658
+ streamPromise.resolve(result.text);
1659
+ void agentOptions?.onFinish?.(result);
1660
+ },
1661
+ abortSignal
759
1662
  });
760
- const toolData = {
761
- name: params.name,
762
- args: inputData
763
- };
764
- let stream;
765
- if ((await params.getModel()).specificationVersion === "v1") {
766
- const { fullStream } = await params.streamLegacy(inputData.prompt, {
767
- ...agentOptions ?? {},
768
- // resourceId: inputData.resourceId,
769
- // threadId: inputData.threadId,
770
- requestContext,
771
- tracingContext,
772
- onFinish: (result) => {
773
- streamPromise.resolve(result.text);
774
- void agentOptions?.onFinish?.(result);
775
- },
776
- abortSignal
777
- });
778
- stream = fullStream;
779
- } else {
780
- const modelOutput = await params.stream(inputData.prompt, {
781
- ...agentOptions ?? {},
782
- requestContext,
783
- tracingContext,
784
- onFinish: (result) => {
785
- streamPromise.resolve(result.text);
786
- void agentOptions?.onFinish?.(result);
787
- },
788
- abortSignal
789
- });
790
- stream = modelOutput.fullStream;
791
- }
792
- if (streamFormat === "legacy") {
793
- await emitter.emit("watch", {
794
- type: "tool-call-streaming-start",
795
- ...toolData ?? {}
796
- });
797
- for await (const chunk of stream) {
798
- if (chunk.type === "text-delta") {
799
- await emitter.emit("watch", {
800
- type: "tool-call-delta",
801
- ...toolData ?? {},
802
- argsTextDelta: chunk.textDelta
803
- });
1663
+ stream = fullStream;
1664
+ } else {
1665
+ const modelOutput = await params.stream(inputData.prompt, {
1666
+ ...agentOptions ?? {},
1667
+ requestContext,
1668
+ tracingContext,
1669
+ onFinish: (result) => {
1670
+ const resultWithObject = result;
1671
+ if (agentOptions?.structuredOutput?.schema && resultWithObject.object) {
1672
+ structuredResult = resultWithObject.object;
804
1673
  }
805
- }
806
- await emitter.emit("watch", {
807
- type: "tool-call-streaming-finish",
808
- ...toolData ?? {}
809
- });
810
- } else {
811
- for await (const chunk of stream) {
812
- await writer.write(chunk);
1674
+ streamPromise.resolve(result.text);
1675
+ void agentOptions?.onFinish?.(result);
1676
+ },
1677
+ abortSignal
1678
+ });
1679
+ stream = modelOutput.fullStream;
1680
+ }
1681
+ if (streamFormat === "legacy") {
1682
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1683
+ type: "watch",
1684
+ runId,
1685
+ data: { type: "tool-call-streaming-start", ...toolData ?? {} }
1686
+ });
1687
+ for await (const chunk of stream) {
1688
+ if (chunk.type === "text-delta") {
1689
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1690
+ type: "watch",
1691
+ runId,
1692
+ data: { type: "tool-call-delta", ...toolData ?? {}, argsTextDelta: chunk.textDelta }
1693
+ });
813
1694
  }
814
1695
  }
815
- if (abortSignal.aborted) {
816
- return abort();
1696
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1697
+ type: "watch",
1698
+ runId,
1699
+ data: { type: "tool-call-streaming-finish", ...toolData ?? {} }
1700
+ });
1701
+ } else {
1702
+ for await (const chunk of stream) {
1703
+ await writer.write(chunk);
817
1704
  }
818
- return {
819
- text: await streamPromise.promise
820
- };
821
- },
822
- component: params.component
823
- };
824
- }
825
- if (isTool(params)) {
826
- if (!params.inputSchema || !params.outputSchema) {
827
- throw new Error("Tool must have input and output schemas defined");
828
- }
829
- return {
830
- // TODO: tool probably should have strong id type
831
- id: params.id,
832
- description: params.description,
833
- inputSchema: params.inputSchema,
834
- outputSchema: params.outputSchema,
835
- execute: async ({
836
- inputData,
837
- mastra,
838
- requestContext,
839
- tracingContext,
840
- suspend,
841
- resumeData,
842
- runId,
843
- workflowId,
844
- state,
845
- setState
846
- }) => {
847
- const toolContext = {
848
- mastra,
849
- requestContext,
850
- tracingContext,
851
- resumeData,
852
- workflow: {
853
- runId,
854
- suspend,
855
- workflowId,
856
- state,
857
- setState
858
- }
859
- };
860
- return params.execute(inputData, toolContext);
861
- },
862
- component: "TOOL"
863
- };
1705
+ }
1706
+ if (abortSignal.aborted) {
1707
+ return abort();
1708
+ }
1709
+ if (structuredResult !== null) {
1710
+ return structuredResult;
1711
+ }
1712
+ return {
1713
+ text: await streamPromise.promise
1714
+ };
1715
+ },
1716
+ component: params.component
1717
+ };
1718
+ }
1719
+ function createStepFromTool(params, agentOrToolOptions) {
1720
+ const toolOpts = agentOrToolOptions;
1721
+ if (!params.inputSchema || !params.outputSchema) {
1722
+ throw new Error("Tool must have input and output schemas defined");
864
1723
  }
865
1724
  return {
866
1725
  id: params.id,
@@ -869,20 +1728,493 @@ function createStep(params, agentOptions) {
869
1728
  outputSchema: params.outputSchema,
870
1729
  resumeSchema: params.resumeSchema,
871
1730
  suspendSchema: params.suspendSchema,
872
- execute: params.execute
1731
+ retries: toolOpts?.retries,
1732
+ scorers: toolOpts?.scorers,
1733
+ execute: async ({
1734
+ inputData,
1735
+ mastra,
1736
+ requestContext,
1737
+ tracingContext,
1738
+ suspend,
1739
+ resumeData,
1740
+ runId,
1741
+ workflowId,
1742
+ state,
1743
+ setState
1744
+ }) => {
1745
+ const toolContext = {
1746
+ mastra,
1747
+ requestContext,
1748
+ tracingContext,
1749
+ workflow: {
1750
+ runId,
1751
+ resumeData,
1752
+ suspend,
1753
+ workflowId,
1754
+ state,
1755
+ setState
1756
+ }
1757
+ };
1758
+ return params.execute(inputData, toolContext);
1759
+ },
1760
+ component: "TOOL"
873
1761
  };
874
1762
  }
875
- function init(inngest) {
1763
+ function createStepFromProcessor(processor) {
1764
+ const getProcessorEntityType = (phase) => {
1765
+ switch (phase) {
1766
+ case "input":
1767
+ return EntityType.INPUT_PROCESSOR;
1768
+ case "inputStep":
1769
+ return EntityType.INPUT_STEP_PROCESSOR;
1770
+ case "outputStream":
1771
+ case "outputResult":
1772
+ return EntityType.OUTPUT_PROCESSOR;
1773
+ case "outputStep":
1774
+ return EntityType.OUTPUT_STEP_PROCESSOR;
1775
+ default:
1776
+ return EntityType.OUTPUT_PROCESSOR;
1777
+ }
1778
+ };
1779
+ const getSpanNamePrefix = (phase) => {
1780
+ switch (phase) {
1781
+ case "input":
1782
+ return "input processor";
1783
+ case "inputStep":
1784
+ return "input step processor";
1785
+ case "outputStream":
1786
+ return "output stream processor";
1787
+ case "outputResult":
1788
+ return "output processor";
1789
+ case "outputStep":
1790
+ return "output step processor";
1791
+ default:
1792
+ return "processor";
1793
+ }
1794
+ };
1795
+ const hasPhaseMethod = (phase) => {
1796
+ switch (phase) {
1797
+ case "input":
1798
+ return !!processor.processInput;
1799
+ case "inputStep":
1800
+ return !!processor.processInputStep;
1801
+ case "outputStream":
1802
+ return !!processor.processOutputStream;
1803
+ case "outputResult":
1804
+ return !!processor.processOutputResult;
1805
+ case "outputStep":
1806
+ return !!processor.processOutputStep;
1807
+ default:
1808
+ return false;
1809
+ }
1810
+ };
876
1811
  return {
877
- createWorkflow(params) {
878
- return new InngestWorkflow(
879
- params,
880
- inngest
881
- );
882
- },
883
- createStep,
884
- cloneStep(step, opts) {
885
- return {
1812
+ id: `processor:${processor.id}`,
1813
+ description: processor.name ?? `Processor ${processor.id}`,
1814
+ inputSchema: ProcessorStepSchema,
1815
+ outputSchema: ProcessorStepOutputSchema,
1816
+ execute: async ({ inputData, requestContext, tracingContext }) => {
1817
+ const input = inputData;
1818
+ const {
1819
+ phase,
1820
+ messages,
1821
+ messageList,
1822
+ stepNumber,
1823
+ systemMessages,
1824
+ part,
1825
+ streamParts,
1826
+ state,
1827
+ finishReason,
1828
+ toolCalls,
1829
+ text,
1830
+ retryCount,
1831
+ // inputStep phase fields for model/tools configuration
1832
+ model,
1833
+ tools,
1834
+ toolChoice,
1835
+ activeTools,
1836
+ providerOptions,
1837
+ modelSettings,
1838
+ structuredOutput,
1839
+ steps
1840
+ } = input;
1841
+ const abort = (reason, options) => {
1842
+ throw new TripWire(reason || `Tripwire triggered by ${processor.id}`, options, processor.id);
1843
+ };
1844
+ if (!hasPhaseMethod(phase)) {
1845
+ return input;
1846
+ }
1847
+ const currentSpan = tracingContext?.currentSpan;
1848
+ const parentSpan = phase === "inputStep" || phase === "outputStep" ? currentSpan?.findParent(SpanType.MODEL_STEP) || currentSpan : currentSpan?.findParent(SpanType.AGENT_RUN) || currentSpan;
1849
+ const processorSpan = phase !== "outputStream" ? parentSpan?.createChildSpan({
1850
+ type: SpanType.PROCESSOR_RUN,
1851
+ name: `${getSpanNamePrefix(phase)}: ${processor.id}`,
1852
+ entityType: getProcessorEntityType(phase),
1853
+ entityId: processor.id,
1854
+ entityName: processor.name ?? processor.id,
1855
+ input: { phase, messageCount: messages?.length },
1856
+ attributes: {
1857
+ processorExecutor: "workflow",
1858
+ // Read processorIndex from processor (set in combineProcessorsIntoWorkflow)
1859
+ processorIndex: processor.processorIndex
1860
+ }
1861
+ }) : void 0;
1862
+ const processorTracingContext = processorSpan ? { currentSpan: processorSpan } : tracingContext;
1863
+ const baseContext = {
1864
+ abort,
1865
+ retryCount: retryCount ?? 0,
1866
+ requestContext,
1867
+ tracingContext: processorTracingContext
1868
+ };
1869
+ const passThrough = {
1870
+ phase,
1871
+ // Auto-create MessageList from messages if not provided
1872
+ // This enables running processor workflows from the UI where messageList can't be serialized
1873
+ messageList: messageList ?? (Array.isArray(messages) ? new MessageList().add(messages, "input").addSystem(systemMessages ?? []) : void 0),
1874
+ stepNumber,
1875
+ systemMessages,
1876
+ streamParts,
1877
+ state,
1878
+ finishReason,
1879
+ toolCalls,
1880
+ text,
1881
+ retryCount,
1882
+ // inputStep phase fields for model/tools configuration
1883
+ model,
1884
+ tools,
1885
+ toolChoice,
1886
+ activeTools,
1887
+ providerOptions,
1888
+ modelSettings,
1889
+ structuredOutput,
1890
+ steps
1891
+ };
1892
+ const executePhaseWithSpan = async (fn) => {
1893
+ try {
1894
+ const result = await fn();
1895
+ processorSpan?.end({ output: result });
1896
+ return result;
1897
+ } catch (error) {
1898
+ if (error instanceof TripWire) {
1899
+ processorSpan?.end({ output: { tripwire: error.message } });
1900
+ } else {
1901
+ processorSpan?.error({ error, endSpan: true });
1902
+ }
1903
+ throw error;
1904
+ }
1905
+ };
1906
+ return executePhaseWithSpan(async () => {
1907
+ switch (phase) {
1908
+ case "input": {
1909
+ if (processor.processInput) {
1910
+ if (!passThrough.messageList) {
1911
+ throw new MastraError({
1912
+ category: ErrorCategory.USER,
1913
+ domain: ErrorDomain.MASTRA_WORKFLOW,
1914
+ id: "PROCESSOR_MISSING_MESSAGE_LIST",
1915
+ text: `Processor ${processor.id} requires messageList or messages for processInput phase`
1916
+ });
1917
+ }
1918
+ const idsBeforeProcessing = messages.map((m) => m.id);
1919
+ const check = passThrough.messageList.makeMessageSourceChecker();
1920
+ const result = await processor.processInput({
1921
+ ...baseContext,
1922
+ messages,
1923
+ messageList: passThrough.messageList,
1924
+ systemMessages: systemMessages ?? []
1925
+ });
1926
+ if (result instanceof MessageList) {
1927
+ if (result !== passThrough.messageList) {
1928
+ throw new MastraError({
1929
+ category: ErrorCategory.USER,
1930
+ domain: ErrorDomain.MASTRA_WORKFLOW,
1931
+ id: "PROCESSOR_RETURNED_EXTERNAL_MESSAGE_LIST",
1932
+ text: `Processor ${processor.id} returned a MessageList instance other than the one passed in. Use the messageList argument instead.`
1933
+ });
1934
+ }
1935
+ return {
1936
+ ...passThrough,
1937
+ messages: result.get.all.db(),
1938
+ systemMessages: result.getAllSystemMessages()
1939
+ };
1940
+ } else if (Array.isArray(result)) {
1941
+ ProcessorRunner.applyMessagesToMessageList(
1942
+ result,
1943
+ passThrough.messageList,
1944
+ idsBeforeProcessing,
1945
+ check,
1946
+ "input"
1947
+ );
1948
+ return { ...passThrough, messages: result };
1949
+ } else if (result && "messages" in result && "systemMessages" in result) {
1950
+ const typedResult = result;
1951
+ ProcessorRunner.applyMessagesToMessageList(
1952
+ typedResult.messages,
1953
+ passThrough.messageList,
1954
+ idsBeforeProcessing,
1955
+ check,
1956
+ "input"
1957
+ );
1958
+ passThrough.messageList.replaceAllSystemMessages(typedResult.systemMessages);
1959
+ return {
1960
+ ...passThrough,
1961
+ messages: typedResult.messages,
1962
+ systemMessages: typedResult.systemMessages
1963
+ };
1964
+ }
1965
+ return { ...passThrough, messages };
1966
+ }
1967
+ return { ...passThrough, messages };
1968
+ }
1969
+ case "inputStep": {
1970
+ if (processor.processInputStep) {
1971
+ if (!passThrough.messageList) {
1972
+ throw new MastraError({
1973
+ category: ErrorCategory.USER,
1974
+ domain: ErrorDomain.MASTRA_WORKFLOW,
1975
+ id: "PROCESSOR_MISSING_MESSAGE_LIST",
1976
+ text: `Processor ${processor.id} requires messageList or messages for processInputStep phase`
1977
+ });
1978
+ }
1979
+ const idsBeforeProcessing = messages.map((m) => m.id);
1980
+ const check = passThrough.messageList.makeMessageSourceChecker();
1981
+ const result = await processor.processInputStep({
1982
+ ...baseContext,
1983
+ messages,
1984
+ messageList: passThrough.messageList,
1985
+ stepNumber: stepNumber ?? 0,
1986
+ systemMessages: systemMessages ?? [],
1987
+ // Pass model/tools configuration fields - types match ProcessInputStepArgs
1988
+ model,
1989
+ tools,
1990
+ toolChoice,
1991
+ activeTools,
1992
+ providerOptions,
1993
+ modelSettings,
1994
+ structuredOutput,
1995
+ steps: steps ?? []
1996
+ });
1997
+ const validatedResult = await ProcessorRunner.validateAndFormatProcessInputStepResult(result, {
1998
+ messageList: passThrough.messageList,
1999
+ processor,
2000
+ stepNumber: stepNumber ?? 0
2001
+ });
2002
+ if (validatedResult.messages) {
2003
+ ProcessorRunner.applyMessagesToMessageList(
2004
+ validatedResult.messages,
2005
+ passThrough.messageList,
2006
+ idsBeforeProcessing,
2007
+ check
2008
+ );
2009
+ }
2010
+ if (validatedResult.systemMessages) {
2011
+ passThrough.messageList.replaceAllSystemMessages(validatedResult.systemMessages);
2012
+ }
2013
+ return { ...passThrough, messages, ...validatedResult };
2014
+ }
2015
+ return { ...passThrough, messages };
2016
+ }
2017
+ case "outputStream": {
2018
+ if (processor.processOutputStream) {
2019
+ const spanKey = `__outputStreamSpan_${processor.id}`;
2020
+ const mutableState = state ?? {};
2021
+ let processorSpan2 = mutableState[spanKey];
2022
+ if (!processorSpan2 && parentSpan) {
2023
+ processorSpan2 = parentSpan.createChildSpan({
2024
+ type: SpanType.PROCESSOR_RUN,
2025
+ name: `output stream processor: ${processor.id}`,
2026
+ entityType: EntityType.OUTPUT_PROCESSOR,
2027
+ entityId: processor.id,
2028
+ entityName: processor.name ?? processor.id,
2029
+ input: { phase, streamParts: [] },
2030
+ attributes: {
2031
+ processorExecutor: "workflow",
2032
+ processorIndex: processor.processorIndex
2033
+ }
2034
+ });
2035
+ mutableState[spanKey] = processorSpan2;
2036
+ }
2037
+ if (processorSpan2) {
2038
+ processorSpan2.input = {
2039
+ phase,
2040
+ streamParts: streamParts ?? [],
2041
+ totalChunks: (streamParts ?? []).length
2042
+ };
2043
+ }
2044
+ const processorTracingContext2 = processorSpan2 ? { currentSpan: processorSpan2 } : baseContext.tracingContext;
2045
+ let result;
2046
+ try {
2047
+ result = await processor.processOutputStream({
2048
+ ...baseContext,
2049
+ tracingContext: processorTracingContext2,
2050
+ part,
2051
+ streamParts: streamParts ?? [],
2052
+ state: mutableState,
2053
+ messageList: passThrough.messageList
2054
+ // Optional for stream processing
2055
+ });
2056
+ if (part && part.type === "finish") {
2057
+ processorSpan2?.end({ output: result });
2058
+ delete mutableState[spanKey];
2059
+ }
2060
+ } catch (error) {
2061
+ if (error instanceof TripWire) {
2062
+ processorSpan2?.end({ output: { tripwire: error.message } });
2063
+ } else {
2064
+ processorSpan2?.error({ error, endSpan: true });
2065
+ }
2066
+ delete mutableState[spanKey];
2067
+ throw error;
2068
+ }
2069
+ return { ...passThrough, state: mutableState, part: result };
2070
+ }
2071
+ return { ...passThrough, part };
2072
+ }
2073
+ case "outputResult": {
2074
+ if (processor.processOutputResult) {
2075
+ if (!passThrough.messageList) {
2076
+ throw new MastraError({
2077
+ category: ErrorCategory.USER,
2078
+ domain: ErrorDomain.MASTRA_WORKFLOW,
2079
+ id: "PROCESSOR_MISSING_MESSAGE_LIST",
2080
+ text: `Processor ${processor.id} requires messageList or messages for processOutputResult phase`
2081
+ });
2082
+ }
2083
+ const idsBeforeProcessing = messages.map((m) => m.id);
2084
+ const check = passThrough.messageList.makeMessageSourceChecker();
2085
+ const result = await processor.processOutputResult({
2086
+ ...baseContext,
2087
+ messages,
2088
+ messageList: passThrough.messageList
2089
+ });
2090
+ if (result instanceof MessageList) {
2091
+ if (result !== passThrough.messageList) {
2092
+ throw new MastraError({
2093
+ category: ErrorCategory.USER,
2094
+ domain: ErrorDomain.MASTRA_WORKFLOW,
2095
+ id: "PROCESSOR_RETURNED_EXTERNAL_MESSAGE_LIST",
2096
+ text: `Processor ${processor.id} returned a MessageList instance other than the one passed in. Use the messageList argument instead.`
2097
+ });
2098
+ }
2099
+ return {
2100
+ ...passThrough,
2101
+ messages: result.get.all.db(),
2102
+ systemMessages: result.getAllSystemMessages()
2103
+ };
2104
+ } else if (Array.isArray(result)) {
2105
+ ProcessorRunner.applyMessagesToMessageList(
2106
+ result,
2107
+ passThrough.messageList,
2108
+ idsBeforeProcessing,
2109
+ check,
2110
+ "response"
2111
+ );
2112
+ return { ...passThrough, messages: result };
2113
+ } else if (result && "messages" in result && "systemMessages" in result) {
2114
+ const typedResult = result;
2115
+ ProcessorRunner.applyMessagesToMessageList(
2116
+ typedResult.messages,
2117
+ passThrough.messageList,
2118
+ idsBeforeProcessing,
2119
+ check,
2120
+ "response"
2121
+ );
2122
+ passThrough.messageList.replaceAllSystemMessages(typedResult.systemMessages);
2123
+ return {
2124
+ ...passThrough,
2125
+ messages: typedResult.messages,
2126
+ systemMessages: typedResult.systemMessages
2127
+ };
2128
+ }
2129
+ return { ...passThrough, messages };
2130
+ }
2131
+ return { ...passThrough, messages };
2132
+ }
2133
+ case "outputStep": {
2134
+ if (processor.processOutputStep) {
2135
+ if (!passThrough.messageList) {
2136
+ throw new MastraError({
2137
+ category: ErrorCategory.USER,
2138
+ domain: ErrorDomain.MASTRA_WORKFLOW,
2139
+ id: "PROCESSOR_MISSING_MESSAGE_LIST",
2140
+ text: `Processor ${processor.id} requires messageList or messages for processOutputStep phase`
2141
+ });
2142
+ }
2143
+ const idsBeforeProcessing = messages.map((m) => m.id);
2144
+ const check = passThrough.messageList.makeMessageSourceChecker();
2145
+ const result = await processor.processOutputStep({
2146
+ ...baseContext,
2147
+ messages,
2148
+ messageList: passThrough.messageList,
2149
+ stepNumber: stepNumber ?? 0,
2150
+ finishReason,
2151
+ toolCalls,
2152
+ text,
2153
+ systemMessages: systemMessages ?? [],
2154
+ steps: steps ?? []
2155
+ });
2156
+ if (result instanceof MessageList) {
2157
+ if (result !== passThrough.messageList) {
2158
+ throw new MastraError({
2159
+ category: ErrorCategory.USER,
2160
+ domain: ErrorDomain.MASTRA_WORKFLOW,
2161
+ id: "PROCESSOR_RETURNED_EXTERNAL_MESSAGE_LIST",
2162
+ text: `Processor ${processor.id} returned a MessageList instance other than the one passed in. Use the messageList argument instead.`
2163
+ });
2164
+ }
2165
+ return {
2166
+ ...passThrough,
2167
+ messages: result.get.all.db(),
2168
+ systemMessages: result.getAllSystemMessages()
2169
+ };
2170
+ } else if (Array.isArray(result)) {
2171
+ ProcessorRunner.applyMessagesToMessageList(
2172
+ result,
2173
+ passThrough.messageList,
2174
+ idsBeforeProcessing,
2175
+ check,
2176
+ "response"
2177
+ );
2178
+ return { ...passThrough, messages: result };
2179
+ } else if (result && "messages" in result && "systemMessages" in result) {
2180
+ const typedResult = result;
2181
+ ProcessorRunner.applyMessagesToMessageList(
2182
+ typedResult.messages,
2183
+ passThrough.messageList,
2184
+ idsBeforeProcessing,
2185
+ check,
2186
+ "response"
2187
+ );
2188
+ passThrough.messageList.replaceAllSystemMessages(typedResult.systemMessages);
2189
+ return {
2190
+ ...passThrough,
2191
+ messages: typedResult.messages,
2192
+ systemMessages: typedResult.systemMessages
2193
+ };
2194
+ }
2195
+ return { ...passThrough, messages };
2196
+ }
2197
+ return { ...passThrough, messages };
2198
+ }
2199
+ default:
2200
+ return { ...passThrough, messages };
2201
+ }
2202
+ });
2203
+ },
2204
+ component: "PROCESSOR"
2205
+ };
2206
+ }
2207
+ function init(inngest) {
2208
+ return {
2209
+ createWorkflow(params) {
2210
+ return new InngestWorkflow(
2211
+ params,
2212
+ inngest
2213
+ );
2214
+ },
2215
+ createStep,
2216
+ cloneStep(step, opts) {
2217
+ return {
886
2218
  id: opts.id,
887
2219
  description: step.description,
888
2220
  inputSchema: step.inputSchema,
@@ -902,7 +2234,8 @@ function init(inngest) {
902
2234
  inputSchema: workflow.inputSchema,
903
2235
  outputSchema: workflow.outputSchema,
904
2236
  steps: workflow.stepDefs,
905
- mastra: workflow.mastra
2237
+ mastra: workflow.mastra,
2238
+ options: workflow.options
906
2239
  });
907
2240
  wf.setStepFlow(workflow.stepGraph);
908
2241
  wf.commit();
@@ -910,846 +2243,7 @@ function init(inngest) {
910
2243
  }
911
2244
  };
912
2245
  }
913
- var InngestExecutionEngine = class extends DefaultExecutionEngine {
914
- inngestStep;
915
- inngestAttempts;
916
- constructor(mastra, inngestStep, inngestAttempts = 0, options) {
917
- super({ mastra, options });
918
- this.inngestStep = inngestStep;
919
- this.inngestAttempts = inngestAttempts;
920
- }
921
- async fmtReturnValue(emitter, stepResults, lastOutput, error) {
922
- const base = {
923
- status: lastOutput.status,
924
- steps: stepResults
925
- };
926
- if (lastOutput.status === "success") {
927
- base.result = lastOutput.output;
928
- } else if (lastOutput.status === "failed") {
929
- base.error = error instanceof Error ? error?.stack ?? error.message : lastOutput?.error instanceof Error ? lastOutput.error.message : lastOutput.error ?? error ?? "Unknown error";
930
- } else if (lastOutput.status === "suspended") {
931
- const suspendedStepIds = Object.entries(stepResults).flatMap(([stepId, stepResult]) => {
932
- if (stepResult?.status === "suspended") {
933
- const nestedPath = stepResult?.suspendPayload?.__workflow_meta?.path;
934
- return nestedPath ? [[stepId, ...nestedPath]] : [[stepId]];
935
- }
936
- return [];
937
- });
938
- base.suspended = suspendedStepIds;
939
- }
940
- return base;
941
- }
942
- // async executeSleep({ id, duration }: { id: string; duration: number }): Promise<void> {
943
- // await this.inngestStep.sleep(id, duration);
944
- // }
945
- async executeSleep({
946
- workflowId,
947
- runId,
948
- entry,
949
- prevOutput,
950
- stepResults,
951
- emitter,
952
- abortController,
953
- requestContext,
954
- executionContext,
955
- writableStream,
956
- tracingContext
957
- }) {
958
- let { duration, fn } = entry;
959
- const sleepSpan = tracingContext?.currentSpan?.createChildSpan({
960
- type: SpanType.WORKFLOW_SLEEP,
961
- name: `sleep: ${duration ? `${duration}ms` : "dynamic"}`,
962
- attributes: {
963
- durationMs: duration,
964
- sleepType: fn ? "dynamic" : "fixed"
965
- },
966
- tracingPolicy: this.options?.tracingPolicy
967
- });
968
- if (fn) {
969
- const stepCallId = randomUUID();
970
- duration = await this.inngestStep.run(`workflow.${workflowId}.sleep.${entry.id}`, async () => {
971
- return await fn(
972
- createDeprecationProxy(
973
- {
974
- runId,
975
- workflowId,
976
- mastra: this.mastra,
977
- requestContext,
978
- inputData: prevOutput,
979
- state: executionContext.state,
980
- setState: (state) => {
981
- executionContext.state = state;
982
- },
983
- retryCount: -1,
984
- tracingContext: {
985
- currentSpan: sleepSpan
986
- },
987
- getInitData: () => stepResults?.input,
988
- getStepResult: getStepResult.bind(this, stepResults),
989
- // TODO: this function shouldn't have suspend probably?
990
- suspend: async (_suspendPayload) => {
991
- },
992
- bail: () => {
993
- },
994
- abort: () => {
995
- abortController?.abort();
996
- },
997
- [EMITTER_SYMBOL]: emitter,
998
- [STREAM_FORMAT_SYMBOL]: executionContext.format,
999
- engine: { step: this.inngestStep },
1000
- abortSignal: abortController?.signal,
1001
- writer: new ToolStream(
1002
- {
1003
- prefix: "workflow-step",
1004
- callId: stepCallId,
1005
- name: "sleep",
1006
- runId
1007
- },
1008
- writableStream
1009
- )
1010
- },
1011
- {
1012
- paramName: "runCount",
1013
- deprecationMessage: runCountDeprecationMessage,
1014
- logger: this.logger
1015
- }
1016
- )
1017
- );
1018
- });
1019
- sleepSpan?.update({
1020
- attributes: {
1021
- durationMs: duration
1022
- }
1023
- });
1024
- }
1025
- try {
1026
- await this.inngestStep.sleep(entry.id, !duration || duration < 0 ? 0 : duration);
1027
- sleepSpan?.end();
1028
- } catch (e) {
1029
- sleepSpan?.error({ error: e });
1030
- throw e;
1031
- }
1032
- }
1033
- async executeSleepUntil({
1034
- workflowId,
1035
- runId,
1036
- entry,
1037
- prevOutput,
1038
- stepResults,
1039
- emitter,
1040
- abortController,
1041
- requestContext,
1042
- executionContext,
1043
- writableStream,
1044
- tracingContext
1045
- }) {
1046
- let { date, fn } = entry;
1047
- const sleepUntilSpan = tracingContext?.currentSpan?.createChildSpan({
1048
- type: SpanType.WORKFLOW_SLEEP,
1049
- name: `sleepUntil: ${date ? date.toISOString() : "dynamic"}`,
1050
- attributes: {
1051
- untilDate: date,
1052
- durationMs: date ? Math.max(0, date.getTime() - Date.now()) : void 0,
1053
- sleepType: fn ? "dynamic" : "fixed"
1054
- },
1055
- tracingPolicy: this.options?.tracingPolicy
1056
- });
1057
- if (fn) {
1058
- date = await this.inngestStep.run(`workflow.${workflowId}.sleepUntil.${entry.id}`, async () => {
1059
- const stepCallId = randomUUID();
1060
- return await fn(
1061
- createDeprecationProxy(
1062
- {
1063
- runId,
1064
- workflowId,
1065
- mastra: this.mastra,
1066
- requestContext,
1067
- inputData: prevOutput,
1068
- state: executionContext.state,
1069
- setState: (state) => {
1070
- executionContext.state = state;
1071
- },
1072
- retryCount: -1,
1073
- tracingContext: {
1074
- currentSpan: sleepUntilSpan
1075
- },
1076
- getInitData: () => stepResults?.input,
1077
- getStepResult: getStepResult.bind(this, stepResults),
1078
- // TODO: this function shouldn't have suspend probably?
1079
- suspend: async (_suspendPayload) => {
1080
- },
1081
- bail: () => {
1082
- },
1083
- abort: () => {
1084
- abortController?.abort();
1085
- },
1086
- [EMITTER_SYMBOL]: emitter,
1087
- [STREAM_FORMAT_SYMBOL]: executionContext.format,
1088
- engine: { step: this.inngestStep },
1089
- abortSignal: abortController?.signal,
1090
- writer: new ToolStream(
1091
- {
1092
- prefix: "workflow-step",
1093
- callId: stepCallId,
1094
- name: "sleep",
1095
- runId
1096
- },
1097
- writableStream
1098
- )
1099
- },
1100
- {
1101
- paramName: "runCount",
1102
- deprecationMessage: runCountDeprecationMessage,
1103
- logger: this.logger
1104
- }
1105
- )
1106
- );
1107
- });
1108
- if (date && !(date instanceof Date)) {
1109
- date = new Date(date);
1110
- }
1111
- const time = !date ? 0 : date.getTime() - Date.now();
1112
- sleepUntilSpan?.update({
1113
- attributes: {
1114
- durationMs: Math.max(0, time)
1115
- }
1116
- });
1117
- }
1118
- if (!(date instanceof Date)) {
1119
- sleepUntilSpan?.end();
1120
- return;
1121
- }
1122
- try {
1123
- await this.inngestStep.sleepUntil(entry.id, date);
1124
- sleepUntilSpan?.end();
1125
- } catch (e) {
1126
- sleepUntilSpan?.error({ error: e });
1127
- throw e;
1128
- }
1129
- }
1130
- async executeStep({
1131
- step,
1132
- stepResults,
1133
- executionContext,
1134
- resume,
1135
- timeTravel,
1136
- prevOutput,
1137
- emitter,
1138
- abortController,
1139
- requestContext,
1140
- tracingContext,
1141
- writableStream,
1142
- disableScorers
1143
- }) {
1144
- const stepSpan = tracingContext?.currentSpan?.createChildSpan({
1145
- name: `workflow step: '${step.id}'`,
1146
- type: SpanType.WORKFLOW_STEP,
1147
- input: prevOutput,
1148
- attributes: {
1149
- stepId: step.id
1150
- },
1151
- tracingPolicy: this.options?.tracingPolicy
1152
- });
1153
- const { inputData, validationError } = await validateStepInput({
1154
- prevOutput,
1155
- step,
1156
- validateInputs: this.options?.validateInputs ?? true
1157
- });
1158
- const startedAt = await this.inngestStep.run(
1159
- `workflow.${executionContext.workflowId}.run.${executionContext.runId}.step.${step.id}.running_ev`,
1160
- async () => {
1161
- const startedAt2 = Date.now();
1162
- await emitter.emit("watch", {
1163
- type: "workflow-step-start",
1164
- payload: {
1165
- id: step.id,
1166
- status: "running",
1167
- payload: inputData,
1168
- startedAt: startedAt2
1169
- }
1170
- });
1171
- return startedAt2;
1172
- }
1173
- );
1174
- if (step instanceof InngestWorkflow) {
1175
- const isResume = !!resume?.steps?.length;
1176
- let result;
1177
- let runId;
1178
- const isTimeTravel = !!(timeTravel && timeTravel.steps?.length > 1 && timeTravel.steps[0] === step.id);
1179
- try {
1180
- if (isResume) {
1181
- runId = stepResults[resume?.steps?.[0] ?? ""]?.suspendPayload?.__workflow_meta?.runId ?? randomUUID();
1182
- const snapshot = await this.mastra?.getStorage()?.loadWorkflowSnapshot({
1183
- workflowName: step.id,
1184
- runId
1185
- });
1186
- const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
1187
- function: step.getFunction(),
1188
- data: {
1189
- inputData,
1190
- initialState: executionContext.state ?? snapshot?.value ?? {},
1191
- runId,
1192
- resume: {
1193
- runId,
1194
- steps: resume.steps.slice(1),
1195
- stepResults: snapshot?.context,
1196
- resumePayload: resume.resumePayload,
1197
- resumePath: resume.steps?.[1] ? snapshot?.suspendedPaths?.[resume.steps?.[1]] : void 0
1198
- },
1199
- outputOptions: { includeState: true }
1200
- }
1201
- });
1202
- result = invokeResp.result;
1203
- runId = invokeResp.runId;
1204
- executionContext.state = invokeResp.result.state;
1205
- } else if (isTimeTravel) {
1206
- const snapshot = await this.mastra?.getStorage()?.loadWorkflowSnapshot({
1207
- workflowName: step.id,
1208
- runId: executionContext.runId
1209
- }) ?? { context: {} };
1210
- const timeTravelParams = createTimeTravelExecutionParams({
1211
- steps: timeTravel.steps.slice(1),
1212
- inputData: timeTravel.inputData,
1213
- resumeData: timeTravel.resumeData,
1214
- context: timeTravel.nestedStepResults?.[step.id] ?? {},
1215
- nestedStepsContext: timeTravel.nestedStepResults ?? {},
1216
- snapshot,
1217
- graph: step.buildExecutionGraph()
1218
- });
1219
- const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
1220
- function: step.getFunction(),
1221
- data: {
1222
- timeTravel: timeTravelParams,
1223
- initialState: executionContext.state ?? {},
1224
- runId: executionContext.runId,
1225
- outputOptions: { includeState: true }
1226
- }
1227
- });
1228
- result = invokeResp.result;
1229
- runId = invokeResp.runId;
1230
- executionContext.state = invokeResp.result.state;
1231
- } else {
1232
- const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
1233
- function: step.getFunction(),
1234
- data: {
1235
- inputData,
1236
- initialState: executionContext.state ?? {},
1237
- outputOptions: { includeState: true }
1238
- }
1239
- });
1240
- result = invokeResp.result;
1241
- runId = invokeResp.runId;
1242
- executionContext.state = invokeResp.result.state;
1243
- }
1244
- } catch (e) {
1245
- const errorCause = e?.cause;
1246
- if (errorCause && typeof errorCause === "object") {
1247
- result = errorCause;
1248
- runId = errorCause.runId || randomUUID();
1249
- } else {
1250
- runId = randomUUID();
1251
- result = {
1252
- status: "failed",
1253
- error: e instanceof Error ? e : new Error(String(e)),
1254
- steps: {},
1255
- input: inputData
1256
- };
1257
- }
1258
- }
1259
- const res = await this.inngestStep.run(
1260
- `workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
1261
- async () => {
1262
- if (result.status === "failed") {
1263
- await emitter.emit("watch", {
1264
- type: "workflow-step-result",
1265
- payload: {
1266
- id: step.id,
1267
- status: "failed",
1268
- error: result?.error,
1269
- payload: prevOutput
1270
- }
1271
- });
1272
- return { executionContext, result: { status: "failed", error: result?.error } };
1273
- } else if (result.status === "suspended") {
1274
- const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
1275
- const stepRes2 = stepResult;
1276
- return stepRes2?.status === "suspended";
1277
- });
1278
- for (const [stepName, stepResult] of suspendedSteps) {
1279
- const suspendPath = [stepName, ...stepResult?.suspendPayload?.__workflow_meta?.path ?? []];
1280
- executionContext.suspendedPaths[step.id] = executionContext.executionPath;
1281
- await emitter.emit("watch", {
1282
- type: "workflow-step-suspended",
1283
- payload: {
1284
- id: step.id,
1285
- status: "suspended"
1286
- }
1287
- });
1288
- return {
1289
- executionContext,
1290
- result: {
1291
- status: "suspended",
1292
- payload: stepResult.payload,
1293
- suspendPayload: {
1294
- ...stepResult?.suspendPayload,
1295
- __workflow_meta: { runId, path: suspendPath }
1296
- }
1297
- }
1298
- };
1299
- }
1300
- return {
1301
- executionContext,
1302
- result: {
1303
- status: "suspended",
1304
- payload: {}
1305
- }
1306
- };
1307
- }
1308
- await emitter.emit("watch", {
1309
- type: "workflow-step-result",
1310
- payload: {
1311
- id: step.id,
1312
- status: "success",
1313
- output: result?.result
1314
- }
1315
- });
1316
- await emitter.emit("watch", {
1317
- type: "workflow-step-finish",
1318
- payload: {
1319
- id: step.id,
1320
- metadata: {}
1321
- }
1322
- });
1323
- return { executionContext, result: { status: "success", output: result?.result } };
1324
- }
1325
- );
1326
- Object.assign(executionContext, res.executionContext);
1327
- return {
1328
- ...res.result,
1329
- startedAt,
1330
- endedAt: Date.now(),
1331
- payload: inputData,
1332
- resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1333
- resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1334
- };
1335
- }
1336
- const stepCallId = randomUUID();
1337
- let stepRes;
1338
- try {
1339
- stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
1340
- let execResults;
1341
- let suspended;
1342
- let bailed;
1343
- const { resumeData: timeTravelResumeData, validationError: timeTravelResumeValidationError } = await validateStepResumeData({
1344
- resumeData: timeTravel?.stepResults[step.id]?.status === "suspended" ? timeTravel?.resumeData : void 0,
1345
- step
1346
- });
1347
- let resumeDataToUse;
1348
- if (timeTravelResumeData && !timeTravelResumeValidationError) {
1349
- resumeDataToUse = timeTravelResumeData;
1350
- } else if (timeTravelResumeData && timeTravelResumeValidationError) {
1351
- this.logger.warn("Time travel resume data validation failed", {
1352
- stepId: step.id,
1353
- error: timeTravelResumeValidationError.message
1354
- });
1355
- } else if (resume?.steps[0] === step.id) {
1356
- resumeDataToUse = resume?.resumePayload;
1357
- }
1358
- try {
1359
- if (validationError) {
1360
- throw validationError;
1361
- }
1362
- const retryCount = this.getOrGenerateRetryCount(step.id);
1363
- const result = await step.execute({
1364
- runId: executionContext.runId,
1365
- workflowId: executionContext.workflowId,
1366
- mastra: this.mastra,
1367
- requestContext,
1368
- retryCount,
1369
- writer: new ToolStream(
1370
- {
1371
- prefix: "workflow-step",
1372
- callId: stepCallId,
1373
- name: step.id,
1374
- runId: executionContext.runId
1375
- },
1376
- writableStream
1377
- ),
1378
- state: executionContext?.state ?? {},
1379
- setState: (state) => {
1380
- executionContext.state = state;
1381
- },
1382
- inputData,
1383
- resumeData: resumeDataToUse,
1384
- tracingContext: {
1385
- currentSpan: stepSpan
1386
- },
1387
- getInitData: () => stepResults?.input,
1388
- getStepResult: getStepResult.bind(this, stepResults),
1389
- suspend: async (suspendPayload, suspendOptions) => {
1390
- executionContext.suspendedPaths[step.id] = executionContext.executionPath;
1391
- if (suspendOptions?.resumeLabel) {
1392
- const resumeLabel = Array.isArray(suspendOptions.resumeLabel) ? suspendOptions.resumeLabel : [suspendOptions.resumeLabel];
1393
- for (const label of resumeLabel) {
1394
- executionContext.resumeLabels[label] = {
1395
- stepId: step.id,
1396
- foreachIndex: executionContext.foreachIndex
1397
- };
1398
- }
1399
- }
1400
- suspended = { payload: suspendPayload };
1401
- },
1402
- bail: (result2) => {
1403
- bailed = { payload: result2 };
1404
- },
1405
- abort: () => {
1406
- abortController?.abort();
1407
- },
1408
- [EMITTER_SYMBOL]: emitter,
1409
- [STREAM_FORMAT_SYMBOL]: executionContext.format,
1410
- engine: {
1411
- step: this.inngestStep
1412
- },
1413
- abortSignal: abortController.signal
1414
- });
1415
- const endedAt = Date.now();
1416
- execResults = {
1417
- status: "success",
1418
- output: result,
1419
- startedAt,
1420
- endedAt,
1421
- payload: inputData,
1422
- resumedAt: resumeDataToUse ? startedAt : void 0,
1423
- resumePayload: resumeDataToUse
1424
- };
1425
- } catch (e) {
1426
- const stepFailure = {
1427
- status: "failed",
1428
- payload: inputData,
1429
- error: e instanceof Error ? e.message : String(e),
1430
- endedAt: Date.now(),
1431
- startedAt,
1432
- resumedAt: resumeDataToUse ? startedAt : void 0,
1433
- resumePayload: resumeDataToUse
1434
- };
1435
- execResults = stepFailure;
1436
- const fallbackErrorMessage = `Step ${step.id} failed`;
1437
- stepSpan?.error({ error: new Error(execResults.error ?? fallbackErrorMessage) });
1438
- throw new RetryAfterError(execResults.error ?? fallbackErrorMessage, executionContext.retryConfig.delay, {
1439
- cause: execResults
1440
- });
1441
- }
1442
- if (suspended) {
1443
- execResults = {
1444
- status: "suspended",
1445
- suspendPayload: suspended.payload,
1446
- ...execResults.output ? { suspendOutput: execResults.output } : {},
1447
- payload: inputData,
1448
- suspendedAt: Date.now(),
1449
- startedAt,
1450
- resumedAt: resumeDataToUse ? startedAt : void 0,
1451
- resumePayload: resumeDataToUse
1452
- };
1453
- } else if (bailed) {
1454
- execResults = {
1455
- status: "bailed",
1456
- output: bailed.payload,
1457
- payload: inputData,
1458
- endedAt: Date.now(),
1459
- startedAt
1460
- };
1461
- }
1462
- if (execResults.status === "suspended") {
1463
- await emitter.emit("watch", {
1464
- type: "workflow-step-suspended",
1465
- payload: {
1466
- id: step.id,
1467
- ...execResults
1468
- }
1469
- });
1470
- } else {
1471
- await emitter.emit("watch", {
1472
- type: "workflow-step-result",
1473
- payload: {
1474
- id: step.id,
1475
- ...execResults
1476
- }
1477
- });
1478
- await emitter.emit("watch", {
1479
- type: "workflow-step-finish",
1480
- payload: {
1481
- id: step.id,
1482
- metadata: {}
1483
- }
1484
- });
1485
- }
1486
- stepSpan?.end({ output: execResults });
1487
- return { result: execResults, executionContext, stepResults };
1488
- });
1489
- } catch (e) {
1490
- const stepFailure = e instanceof Error ? e?.cause : {
1491
- status: "failed",
1492
- error: e instanceof Error ? e.message : String(e),
1493
- payload: inputData,
1494
- startedAt,
1495
- endedAt: Date.now()
1496
- };
1497
- stepRes = {
1498
- result: stepFailure,
1499
- executionContext,
1500
- stepResults: {
1501
- ...stepResults,
1502
- [step.id]: stepFailure
1503
- }
1504
- };
1505
- }
1506
- if (disableScorers !== false && stepRes.result.status === "success") {
1507
- await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}.score`, async () => {
1508
- if (step.scorers) {
1509
- await this.runScorers({
1510
- scorers: step.scorers,
1511
- runId: executionContext.runId,
1512
- input: inputData,
1513
- output: stepRes.result,
1514
- workflowId: executionContext.workflowId,
1515
- stepId: step.id,
1516
- requestContext,
1517
- disableScorers,
1518
- tracingContext: { currentSpan: stepSpan }
1519
- });
1520
- }
1521
- });
1522
- }
1523
- Object.assign(executionContext.suspendedPaths, stepRes.executionContext.suspendedPaths);
1524
- executionContext.state = stepRes.executionContext.state;
1525
- return stepRes.result;
1526
- }
1527
- async persistStepUpdate({
1528
- workflowId,
1529
- runId,
1530
- stepResults,
1531
- resourceId,
1532
- executionContext,
1533
- serializedStepGraph,
1534
- workflowStatus,
1535
- result,
1536
- error
1537
- }) {
1538
- await this.inngestStep.run(
1539
- `workflow.${workflowId}.run.${runId}.path.${JSON.stringify(executionContext.executionPath)}.stepUpdate`,
1540
- async () => {
1541
- const shouldPersistSnapshot = this.options.shouldPersistSnapshot({ stepResults, workflowStatus });
1542
- if (!shouldPersistSnapshot) {
1543
- return;
1544
- }
1545
- await this.mastra?.getStorage()?.persistWorkflowSnapshot({
1546
- workflowName: workflowId,
1547
- runId,
1548
- resourceId,
1549
- snapshot: {
1550
- runId,
1551
- status: workflowStatus,
1552
- value: executionContext.state,
1553
- context: stepResults,
1554
- activePaths: executionContext.executionPath,
1555
- activeStepsPath: executionContext.activeStepsPath,
1556
- suspendedPaths: executionContext.suspendedPaths,
1557
- resumeLabels: executionContext.resumeLabels,
1558
- waitingPaths: {},
1559
- serializedStepGraph,
1560
- result,
1561
- error,
1562
- timestamp: Date.now()
1563
- }
1564
- });
1565
- }
1566
- );
1567
- }
1568
- async executeConditional({
1569
- workflowId,
1570
- runId,
1571
- entry,
1572
- prevOutput,
1573
- stepResults,
1574
- timeTravel,
1575
- resume,
1576
- executionContext,
1577
- emitter,
1578
- abortController,
1579
- requestContext,
1580
- writableStream,
1581
- disableScorers,
1582
- tracingContext
1583
- }) {
1584
- const conditionalSpan = tracingContext?.currentSpan?.createChildSpan({
1585
- type: SpanType.WORKFLOW_CONDITIONAL,
1586
- name: `conditional: '${entry.conditions.length} conditions'`,
1587
- input: prevOutput,
1588
- attributes: {
1589
- conditionCount: entry.conditions.length
1590
- },
1591
- tracingPolicy: this.options?.tracingPolicy
1592
- });
1593
- let execResults;
1594
- const truthyIndexes = (await Promise.all(
1595
- entry.conditions.map(
1596
- (cond, index) => this.inngestStep.run(`workflow.${workflowId}.conditional.${index}`, async () => {
1597
- const evalSpan = conditionalSpan?.createChildSpan({
1598
- type: SpanType.WORKFLOW_CONDITIONAL_EVAL,
1599
- name: `condition: '${index}'`,
1600
- input: prevOutput,
1601
- attributes: {
1602
- conditionIndex: index
1603
- },
1604
- tracingPolicy: this.options?.tracingPolicy
1605
- });
1606
- try {
1607
- const result = await cond(
1608
- createDeprecationProxy(
1609
- {
1610
- runId,
1611
- workflowId,
1612
- mastra: this.mastra,
1613
- requestContext,
1614
- retryCount: -1,
1615
- inputData: prevOutput,
1616
- state: executionContext.state,
1617
- setState: (state) => {
1618
- executionContext.state = state;
1619
- },
1620
- tracingContext: {
1621
- currentSpan: evalSpan
1622
- },
1623
- getInitData: () => stepResults?.input,
1624
- getStepResult: getStepResult.bind(this, stepResults),
1625
- // TODO: this function shouldn't have suspend probably?
1626
- suspend: async (_suspendPayload) => {
1627
- },
1628
- bail: () => {
1629
- },
1630
- abort: () => {
1631
- abortController.abort();
1632
- },
1633
- [EMITTER_SYMBOL]: emitter,
1634
- [STREAM_FORMAT_SYMBOL]: executionContext.format,
1635
- engine: {
1636
- step: this.inngestStep
1637
- },
1638
- abortSignal: abortController.signal,
1639
- writer: new ToolStream(
1640
- {
1641
- prefix: "workflow-step",
1642
- callId: randomUUID(),
1643
- name: "conditional",
1644
- runId
1645
- },
1646
- writableStream
1647
- )
1648
- },
1649
- {
1650
- paramName: "runCount",
1651
- deprecationMessage: runCountDeprecationMessage,
1652
- logger: this.logger
1653
- }
1654
- )
1655
- );
1656
- evalSpan?.end({
1657
- output: result,
1658
- attributes: {
1659
- result: !!result
1660
- }
1661
- });
1662
- return result ? index : null;
1663
- } catch (e) {
1664
- evalSpan?.error({
1665
- error: e instanceof Error ? e : new Error(String(e)),
1666
- attributes: {
1667
- result: false
1668
- }
1669
- });
1670
- return null;
1671
- }
1672
- })
1673
- )
1674
- )).filter((index) => index !== null);
1675
- const stepsToRun = entry.steps.filter((_, index) => truthyIndexes.includes(index));
1676
- conditionalSpan?.update({
1677
- attributes: {
1678
- truthyIndexes,
1679
- selectedSteps: stepsToRun.map((s) => s.type === "step" ? s.step.id : `control-${s.type}`)
1680
- }
1681
- });
1682
- const results = await Promise.all(
1683
- stepsToRun.map(async (step, index) => {
1684
- const currStepResult = stepResults[step.step.id];
1685
- if (currStepResult && currStepResult.status === "success") {
1686
- return currStepResult;
1687
- }
1688
- const result = await this.executeStep({
1689
- step: step.step,
1690
- prevOutput,
1691
- stepResults,
1692
- resume,
1693
- timeTravel,
1694
- executionContext: {
1695
- workflowId,
1696
- runId,
1697
- executionPath: [...executionContext.executionPath, index],
1698
- activeStepsPath: executionContext.activeStepsPath,
1699
- suspendedPaths: executionContext.suspendedPaths,
1700
- resumeLabels: executionContext.resumeLabels,
1701
- retryConfig: executionContext.retryConfig,
1702
- state: executionContext.state
1703
- },
1704
- emitter,
1705
- abortController,
1706
- requestContext,
1707
- writableStream,
1708
- disableScorers,
1709
- tracingContext: {
1710
- currentSpan: conditionalSpan
1711
- }
1712
- });
1713
- stepResults[step.step.id] = result;
1714
- return result;
1715
- })
1716
- );
1717
- const hasFailed = results.find((result) => result.status === "failed");
1718
- const hasSuspended = results.find((result) => result.status === "suspended");
1719
- if (hasFailed) {
1720
- execResults = { status: "failed", error: hasFailed.error };
1721
- } else if (hasSuspended) {
1722
- execResults = {
1723
- status: "suspended",
1724
- suspendPayload: hasSuspended.suspendPayload,
1725
- ...hasSuspended.suspendOutput ? { suspendOutput: hasSuspended.suspendOutput } : {}
1726
- };
1727
- } else {
1728
- execResults = {
1729
- status: "success",
1730
- output: results.reduce((acc, result, index) => {
1731
- if (result.status === "success") {
1732
- if ("step" in stepsToRun[index]) {
1733
- acc[stepsToRun[index].step.id] = result.output;
1734
- }
1735
- }
1736
- return acc;
1737
- }, {})
1738
- };
1739
- }
1740
- if (execResults.status === "failed") {
1741
- conditionalSpan?.error({
1742
- error: new Error(execResults.error)
1743
- });
1744
- } else {
1745
- conditionalSpan?.end({
1746
- output: execResults.output || execResults
1747
- });
1748
- }
1749
- return execResults;
1750
- }
1751
- };
1752
2246
 
1753
- export { InngestExecutionEngine, InngestRun, InngestWorkflow, createStep, init, serve };
2247
+ export { InngestExecutionEngine, InngestPubSub, InngestRun, InngestWorkflow, _compatibilityCheck, createServe, createStep, init, serve };
1754
2248
  //# sourceMappingURL=index.js.map
1755
2249
  //# sourceMappingURL=index.js.map