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

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