@mastra/inngest 0.0.0-sidebar-window-undefined-fix-20251029233656 → 0.0.0-standard-schema-20260123120255

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,1634 +1,2257 @@
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 aiTracing = require('@mastra/core/ai-tracing');
7
- var di = require('@mastra/core/di');
8
- var stream = require('@mastra/core/stream');
3
+ var agent = require('@mastra/core/agent');
4
+ var error = require('@mastra/core/error');
5
+ var observability = require('@mastra/core/observability');
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.getWorkflows();
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();
30
- }
31
- return [];
32
- })
33
- )
34
- );
35
- return hono.serve({
36
- ...registerOptions,
37
- client: inngest,
38
- functions: [...workflowFunctions, ...userFunctions]
39
- });
40
- }
41
- var InngestRun = class extends workflows.Run {
42
- inngest;
43
- serializedStepGraph;
44
- #mastra;
45
- constructor(params, inngest) {
46
- super(params);
47
- this.inngest = inngest;
48
- this.serializedStepGraph = params.serializedStepGraph;
49
- this.#mastra = params.mastra;
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;
50
29
  }
51
- 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}`
55
- }
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"
56
44
  });
57
- const json = await response.json();
58
- return json.data;
45
+ return errorInstance.toJSON();
59
46
  }
60
- async getRunOutput(eventId) {
61
- let runs = await this.getRuns(eventId);
62
- while (runs?.[0]?.status !== "Completed" || runs?.[0]?.event_id !== eventId) {
63
- await new Promise((resolve) => setTimeout(resolve, 1e3));
64
- runs = await this.getRuns(eventId);
65
- if (runs?.[0]?.status === "Failed") {
66
- const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
67
- workflowName: this.workflowId,
68
- runId: this.runId
69
- });
70
- return {
71
- output: { result: { steps: snapshot?.context, status: "failed", error: runs?.[0]?.output?.message } }
72
- };
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));
73
70
  }
74
- if (runs?.[0]?.status === "Cancelled") {
75
- const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
76
- workflowName: this.workflowId,
77
- runId: this.runId
78
- });
79
- return { output: { result: { steps: snapshot?.context, status: "canceled" } } };
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
+ };
103
+ }
80
104
  }
81
105
  }
82
- return runs?.[0];
106
+ return { ok: false, error: { status: "failed", error: new Error("Unknown error"), endedAt: Date.now() } };
83
107
  }
84
- async sendEvent(event, data) {
85
- await this.inngest.send({
86
- name: `user-event-${event}`,
87
- data
88
- });
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);
89
113
  }
90
- async cancel() {
91
- await this.inngest.send({
92
- name: `cancel.workflow.${this.workflowId}`,
93
- data: {
94
- runId: this.runId
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
+ });
95
148
  }
96
149
  });
97
- const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
98
- workflowName: this.workflowId,
99
- runId: this.runId
100
- });
101
- if (snapshot) {
102
- await this.#mastra?.storage?.persistWorkflowSnapshot({
103
- workflowName: this.workflowId,
104
- runId: this.runId,
105
- resourceId: this.resourceId,
106
- snapshot: {
107
- ...snapshot,
108
- status: "canceled"
109
- }
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
110
187
  });
188
+ return span?.exportSpan();
189
+ });
190
+ if (exportedSpan) {
191
+ const observability = this.mastra?.observability?.getSelectedInstance({});
192
+ return observability?.rebuildSpan(exportedSpan);
111
193
  }
194
+ return void 0;
112
195
  }
113
- async start({
114
- inputData,
115
- initialState
116
- }) {
117
- await this.#mastra.getStorage()?.persistWorkflowSnapshot({
118
- workflowName: this.workflowId,
119
- runId: this.runId,
120
- resourceId: this.resourceId,
121
- snapshot: {
122
- runId: this.runId,
123
- serializedStepGraph: this.serializedStepGraph,
124
- value: {},
125
- context: {},
126
- activePaths: [],
127
- suspendedPaths: {},
128
- resumeLabels: {},
129
- waitingPaths: {},
130
- timestamp: Date.now(),
131
- status: "running"
132
- }
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);
133
204
  });
134
- const inputDataToUse = await this._validateInput(inputData);
135
- const initialStateToUse = await this._validateInitialState(initialState ?? {});
136
- const eventOutput = await this.inngest.send({
137
- name: `workflow.${this.workflowId}`,
138
- data: {
139
- inputData: inputDataToUse,
140
- initialState: initialStateToUse,
141
- runId: this.runId,
142
- resourceId: this.resourceId
143
- }
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);
144
214
  });
145
- const eventId = eventOutput.ids[0];
146
- if (!eventId) {
147
- throw new Error("Event ID is not set");
148
- }
149
- const runOutput = await this.getRunOutput(eventId);
150
- const result = runOutput?.output?.result;
151
- if (result.status === "failed") {
152
- result.error = new Error(result.error);
153
- }
154
- if (result.status !== "suspended") {
155
- this.cleanup?.();
156
- }
157
- return result;
158
215
  }
159
- async resume(params) {
160
- const p = this._resume(params).then((result) => {
161
- if (result.status !== "suspended") {
162
- this.closeStreamAction?.().catch(() => {
163
- });
164
- }
165
- return result;
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();
166
232
  });
167
- this.executionResults = p;
168
- return p;
233
+ if (exportedSpan) {
234
+ const observability = this.mastra?.observability?.getSelectedInstance({});
235
+ return observability?.rebuildSpan(exportedSpan);
236
+ }
237
+ return void 0;
169
238
  }
170
- async _resume(params) {
171
- const steps = (Array.isArray(params.step) ? params.step : [params.step]).map(
172
- (step) => typeof step === "string" ? step : step?.id
173
- );
174
- const snapshot = await this.#mastra?.storage?.loadWorkflowSnapshot({
175
- workflowName: this.workflowId,
176
- runId: this.runId
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);
177
247
  });
178
- const suspendedStep = this.workflowSteps[steps?.[0] ?? ""];
179
- const resumeDataToUse = await this._validateResumeData(params.resumeData, suspendedStep);
180
- const eventOutput = await this.inngest.send({
181
- name: `workflow.${this.workflowId}`,
182
- data: {
183
- inputData: resumeDataToUse,
184
- initialState: snapshot?.value ?? {},
185
- runId: this.runId,
186
- workflowId: this.workflowId,
187
- stepResults: snapshot?.context,
188
- resume: {
189
- steps,
190
- stepResults: snapshot?.context,
191
- resumePayload: resumeDataToUse,
192
- // @ts-ignore
193
- resumePath: snapshot?.suspendedPaths?.[steps?.[0]]
194
- }
195
- }
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);
196
257
  });
197
- const eventId = eventOutput.ids[0];
198
- if (!eventId) {
199
- throw new Error("Event ID is not set");
200
- }
201
- const runOutput = await this.getRunOutput(eventId);
202
- const result = runOutput?.output?.result;
203
- if (result.status === "failed") {
204
- result.error = new Error(result.error);
205
- }
206
- return result;
207
258
  }
208
- watch(cb, type = "watch") {
209
- let active = true;
210
- const streamPromise = realtime.subscribe(
211
- {
212
- channel: `workflow:${this.workflowId}:${this.runId}`,
213
- topics: [type],
214
- app: this.inngest
215
- },
216
- (message) => {
217
- if (active) {
218
- cb(message.data);
219
- }
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;
220
360
  }
221
- );
222
- return () => {
223
- active = false;
224
- streamPromise.then(async (stream) => {
225
- return stream.cancel();
226
- }).catch((err) => {
227
- console.error(err);
228
- });
229
- };
230
- }
231
- streamLegacy({ inputData, runtimeContext } = {}) {
232
- const { readable, writable } = new TransformStream();
233
- const writer = writable.getWriter();
234
- const unwatch = this.watch(async (event) => {
235
- try {
236
- const e = {
237
- ...event,
238
- type: event.type.replace("workflow-", "")
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
239
373
  };
240
- await writer.write(e);
241
- } catch {
242
374
  }
243
- }, "watch-v2");
244
- this.closeStreamAction = async () => {
245
- unwatch();
246
- try {
247
- await writer.close();
248
- } catch (err) {
249
- console.error("Error closing stream:", err);
250
- } finally {
251
- writer.releaseLock();
252
- }
253
- };
254
- this.executionResults = this.start({ inputData, runtimeContext }).then((result) => {
255
- if (result.status !== "suspended") {
256
- this.closeStreamAction?.().catch(() => {
257
- });
258
- }
259
- return result;
260
- });
261
- return {
262
- stream: readable,
263
- getWorkflowState: () => this.executionResults
264
- };
265
- }
266
- stream({
267
- inputData,
268
- runtimeContext,
269
- closeOnSuspend = true
270
- } = {}) {
271
- const self = this;
272
- let streamOutput;
273
- const stream$1 = new web.ReadableStream({
274
- async start(controller) {
275
- const unwatch = self.watch(async ({ type, from = stream.ChunkFrom.WORKFLOW, payload }) => {
276
- controller.enqueue({
277
- type,
278
- runId: self.runId,
279
- from,
280
- payload: {
281
- stepName: payload.id,
282
- ...payload
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
+ }
283
391
  }
284
392
  });
285
- }, "watch-v2");
286
- self.closeStreamAction = async () => {
287
- unwatch();
288
- try {
289
- await controller.close();
290
- } catch (err) {
291
- console.error("Error closing stream:", err);
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
+ };
292
425
  }
293
- };
294
- const executionResultsPromise = self.start({
295
- inputData,
296
- runtimeContext
297
- });
298
- const executionResults = await executionResultsPromise;
299
- if (closeOnSuspend) {
300
- self.closeStreamAction?.().catch(() => {
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
+ }
301
447
  });
302
- } else if (executionResults.status !== "suspended") {
303
- self.closeStreamAction?.().catch(() => {
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
+ }
304
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" } };
305
480
  }
306
- if (streamOutput) {
307
- streamOutput.updateResults(executionResults);
308
- }
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() } };
309
505
  }
310
- });
311
- streamOutput = new stream.WorkflowRunOutput({
312
- runId: this.runId,
313
- workflowId: this.workflowId,
314
- stream: stream$1
315
- });
316
- return streamOutput;
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
+ };
317
515
  }
318
516
  };
319
- var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
320
- #mastra;
517
+ var InngestPubSub = class extends events.PubSub {
321
518
  inngest;
322
- function;
323
- flowControlConfig;
324
- constructor(params, inngest) {
325
- const { concurrency, rateLimit, throttle, debounce, priority, ...workflowParams } = params;
326
- super(workflowParams);
327
- const flowControlEntries = Object.entries({ concurrency, rateLimit, throttle, debounce, priority }).filter(
328
- ([_, value]) => value !== void 0
329
- );
330
- this.flowControlConfig = flowControlEntries.length > 0 ? Object.fromEntries(flowControlEntries) : void 0;
331
- this.#mastra = params.mastra;
519
+ workflowId;
520
+ publishFn;
521
+ subscriptions = /* @__PURE__ */ new Map();
522
+ constructor(inngest, workflowId, publishFn) {
523
+ super();
332
524
  this.inngest = inngest;
525
+ this.workflowId = workflowId;
526
+ this.publishFn = publishFn;
333
527
  }
334
- async getWorkflowRuns(args) {
335
- const storage = this.#mastra?.getStorage();
336
- if (!storage) {
337
- this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
338
- return { runs: [], total: 0 };
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;
339
537
  }
340
- return storage.getWorkflowRuns({ workflowName: this.id, ...args ?? {} });
341
- }
342
- async getWorkflowRunById(runId) {
343
- const storage = this.#mastra?.getStorage();
344
- if (!storage) {
345
- this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
346
- return this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null;
538
+ const match = topic.match(/^workflow\.events\.v2\.(.+)$/);
539
+ if (!match) {
540
+ return;
347
541
  }
348
- const run = await storage.getWorkflowRunById({ runId, workflowName: this.id });
349
- return run ?? (this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null);
350
- }
351
- __registerMastra(mastra) {
352
- this.#mastra = mastra;
353
- this.executionEngine.__registerMastra(mastra);
354
- const updateNested = (step) => {
355
- if ((step.type === "step" || step.type === "loop" || step.type === "foreach") && step.step instanceof _InngestWorkflow) {
356
- step.step.__registerMastra(mastra);
357
- } else if (step.type === "parallel" || step.type === "conditional") {
358
- for (const subStep of step.steps) {
359
- updateNested(subStep);
360
- }
361
- }
362
- };
363
- if (this.executionGraph.steps.length) {
364
- for (const step of this.executionGraph.steps) {
365
- updateNested(step);
366
- }
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);
367
551
  }
368
552
  }
369
553
  /**
370
- * @deprecated Use createRunAsync() instead.
371
- * @throws {Error} Always throws an error directing users to use createRunAsync()
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}"
372
558
  */
373
- createRun(_options) {
374
- throw new Error(
375
- "createRun() has been deprecated. Please use createRunAsync() instead.\n\nMigration guide:\n Before: const run = workflow.createRun();\n After: const run = await workflow.createRunAsync();\n\nNote: createRunAsync() is an async method, so make sure your calling function is async."
376
- );
377
- }
378
- async createRunAsync(options) {
379
- const runIdToUse = options?.runId || crypto.randomUUID();
380
- const run = this.runs.get(runIdToUse) ?? new InngestRun(
381
- {
382
- workflowId: this.id,
383
- runId: runIdToUse,
384
- resourceId: options?.resourceId,
385
- executionEngine: this.executionEngine,
386
- executionGraph: this.executionGraph,
387
- serializedStepGraph: this.serializedStepGraph,
388
- mastra: this.#mastra,
389
- retryConfig: this.retryConfig,
390
- cleanup: () => this.runs.delete(runIdToUse),
391
- workflowSteps: this.steps
392
- },
393
- this.inngest
394
- );
395
- this.runs.set(runIdToUse, run);
396
- const shouldPersistSnapshot = this.options.shouldPersistSnapshot({
397
- workflowStatus: run.workflowRunStatus,
398
- stepResults: {}
399
- });
400
- const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse, false);
401
- if (!workflowSnapshotInStorage && shouldPersistSnapshot) {
402
- await this.mastra?.getStorage()?.persistWorkflowSnapshot({
403
- workflowName: this.id,
404
- runId: runIdToUse,
405
- resourceId: options?.resourceId,
406
- snapshot: {
407
- runId: runIdToUse,
408
- status: "pending",
409
- value: {},
410
- context: {},
411
- activePaths: [],
412
- waitingPaths: {},
413
- serializedStepGraph: this.serializedStepGraph,
414
- suspendedPaths: {},
415
- resumeLabels: {},
416
- result: void 0,
417
- error: void 0,
418
- // @ts-ignore
419
- timestamp: Date.now()
420
- }
421
- });
559
+ async subscribe(topic, cb) {
560
+ const match = topic.match(/^workflow\.events\.v2\.(.+)$/);
561
+ if (!match || !match[1]) {
562
+ return;
422
563
  }
423
- return run;
424
- }
425
- getFunction() {
426
- if (this.function) {
427
- return this.function;
564
+ const runId = match[1];
565
+ if (this.subscriptions.has(topic)) {
566
+ this.subscriptions.get(topic).callbacks.add(cb);
567
+ return;
428
568
  }
429
- this.function = this.inngest.createFunction(
569
+ const callbacks = /* @__PURE__ */ new Set([cb]);
570
+ const channel = `workflow:${this.workflowId}:${runId}`;
571
+ const streamPromise = realtime.subscribe(
430
572
  {
431
- id: `workflow.${this.id}`,
432
- // @ts-ignore
433
- retries: this.retryConfig?.attempts ?? 0,
434
- cancelOn: [{ event: `cancel.workflow.${this.id}` }],
435
- // Spread flow control configuration
436
- ...this.flowControlConfig
573
+ channel,
574
+ topics: ["watch"],
575
+ app: this.inngest
437
576
  },
438
- { event: `workflow.${this.id}` },
439
- async ({ event, step, attempt, publish }) => {
440
- let { inputData, initialState, runId, resourceId, resume, outputOptions } = event.data;
441
- if (!runId) {
442
- runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
443
- return crypto.randomUUID();
444
- });
445
- }
446
- const emitter = {
447
- emit: async (event2, data) => {
448
- if (!publish) {
449
- return;
450
- }
451
- try {
452
- await publish({
453
- channel: `workflow:${this.id}:${runId}`,
454
- topic: event2,
455
- data
456
- });
457
- } catch (err) {
458
- this.logger.error("Error emitting event: " + (err?.stack ?? err?.message ?? err));
459
- }
460
- },
461
- on: (_event, _callback) => {
462
- },
463
- off: (_event, _callback) => {
464
- },
465
- once: (_event, _callback) => {
466
- }
467
- };
468
- const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
469
- const result = await engine.execute({
470
- workflowId: this.id,
577
+ (message) => {
578
+ const event = {
579
+ id: crypto.randomUUID(),
580
+ type: "watch",
471
581
  runId,
472
- resourceId,
473
- graph: this.executionGraph,
474
- serializedStepGraph: this.serializedStepGraph,
475
- input: inputData,
476
- initialState,
477
- emitter,
478
- retryConfig: this.retryConfig,
479
- runtimeContext: new di.RuntimeContext(),
480
- // TODO
481
- resume,
482
- abortController: new AbortController(),
483
- currentSpan: void 0,
484
- // TODO: Pass actual parent AI span from workflow execution context
485
- outputOptions
486
- });
487
- await step.run(`workflow.${this.id}.finalize`, async () => {
488
- if (result.status === "failed") {
489
- throw new inngest.NonRetriableError(`Workflow failed`, {
490
- cause: result
491
- });
492
- }
493
- return result;
494
- });
495
- return { result, runId };
582
+ data: message.data,
583
+ createdAt: /* @__PURE__ */ new Date()
584
+ };
585
+ for (const callback of callbacks) {
586
+ callback(event);
587
+ }
496
588
  }
497
589
  );
498
- return this.function;
499
- }
500
- getNestedFunctions(steps) {
501
- return steps.flatMap((step) => {
502
- if (step.type === "step" || step.type === "loop" || step.type === "foreach") {
503
- if (step.step instanceof _InngestWorkflow) {
504
- return [step.step.getFunction(), ...step.step.getNestedFunctions(step.step.executionGraph.steps)];
505
- }
506
- return [];
507
- } else if (step.type === "parallel" || step.type === "conditional") {
508
- return this.getNestedFunctions(step.steps);
509
- }
510
- return [];
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
511
597
  });
512
598
  }
513
- getFunctions() {
514
- return [this.getFunction(), ...this.getNestedFunctions(this.executionGraph.steps)];
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();
515
627
  }
516
628
  };
517
- function isAgent(params) {
518
- return params?.component === "AGENT";
519
- }
520
- function isTool(params) {
521
- return params instanceof tools.Tool;
522
- }
523
- function createStep(params) {
524
- if (isAgent(params)) {
525
- return {
526
- id: params.name,
527
- description: params.getDescription(),
528
- // @ts-ignore
529
- inputSchema: zod.z.object({
530
- prompt: zod.z.string()
531
- }),
532
- // @ts-ignore
533
- outputSchema: zod.z.object({
534
- text: zod.z.string()
535
- }),
536
- execute: async ({ inputData, [_constants.EMITTER_SYMBOL]: emitter, runtimeContext, abortSignal, abort, tracingContext }) => {
537
- let streamPromise = {};
538
- streamPromise.promise = new Promise((resolve, reject) => {
539
- streamPromise.resolve = resolve;
540
- streamPromise.reject = reject;
541
- });
542
- const toolData = {
543
- name: params.name,
544
- args: inputData
545
- };
546
- if ((await params.getLLM()).getModel().specificationVersion === `v2`) {
547
- const { fullStream } = await params.stream(inputData.prompt, {
548
- runtimeContext,
549
- tracingContext,
550
- onFinish: (result) => {
551
- streamPromise.resolve(result.text);
552
- },
553
- abortSignal
554
- });
555
- if (abortSignal.aborted) {
556
- return abort();
557
- }
558
- await emitter.emit("watch-v2", {
559
- type: "tool-call-streaming-start",
560
- ...toolData ?? {}
561
- });
562
- for await (const chunk of fullStream) {
563
- if (chunk.type === "text-delta") {
564
- await emitter.emit("watch-v2", {
565
- type: "tool-call-delta",
566
- ...toolData ?? {},
567
- argsTextDelta: chunk.payload.text
568
- });
569
- }
570
- }
571
- } else {
572
- const { fullStream } = await params.streamLegacy(inputData.prompt, {
573
- runtimeContext,
574
- tracingContext,
575
- onFinish: (result) => {
576
- streamPromise.resolve(result.text);
577
- },
578
- abortSignal
579
- });
580
- if (abortSignal.aborted) {
581
- return abort();
582
- }
583
- await emitter.emit("watch-v2", {
584
- type: "tool-call-streaming-start",
585
- ...toolData ?? {}
586
- });
587
- for await (const chunk of fullStream) {
588
- if (chunk.type === "text-delta") {
589
- await emitter.emit("watch-v2", {
590
- type: "tool-call-delta",
591
- ...toolData ?? {},
592
- argsTextDelta: chunk.textDelta
593
- });
629
+ var InngestRun = class extends workflows.Run {
630
+ inngest;
631
+ serializedStepGraph;
632
+ #mastra;
633
+ constructor(params, inngest) {
634
+ super(params);
635
+ this.inngest = inngest;
636
+ this.serializedStepGraph = params.serializedStepGraph;
637
+ this.#mastra = params.mastra;
638
+ }
639
+ async getRuns(eventId) {
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}`
594
649
  }
595
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
+ }
672
+ }
673
+ }
674
+ throw new inngest.NonRetriableError(`Failed to get runs after ${maxRetries} attempts: ${lastError?.message}`);
675
+ }
676
+ async getRunOutput(eventId, maxWaitMs = 3e5) {
677
+ const startTime = Date.now();
678
+ const storage = this.#mastra?.getStorage();
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;
596
687
  }
597
- await emitter.emit("watch-v2", {
598
- type: "tool-call-streaming-finish",
599
- ...toolData ?? {}
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
+ }
695
+ if (runs?.[0]?.status === "Failed") {
696
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
697
+ workflowName: this.workflowId,
698
+ runId: this.runId
600
699
  });
700
+ if (snapshot?.context) {
701
+ snapshot.context = workflows.hydrateSerializedStepErrors(snapshot.context);
702
+ }
601
703
  return {
602
- text: await streamPromise.promise
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
+ }
603
712
  };
604
- },
605
- component: params.component
606
- };
607
- }
608
- if (isTool(params)) {
609
- if (!params.inputSchema || !params.outputSchema) {
610
- throw new Error("Tool must have input and output schemas defined");
611
- }
612
- return {
613
- // TODO: tool probably should have strong id type
614
- // @ts-ignore
615
- id: params.id,
616
- description: params.description,
617
- inputSchema: params.inputSchema,
618
- outputSchema: params.outputSchema,
619
- execute: async ({ inputData, mastra, runtimeContext, tracingContext, suspend, resumeData }) => {
620
- return params.execute({
621
- context: inputData,
622
- mastra: aiTracing.wrapMastra(mastra, tracingContext),
623
- runtimeContext,
624
- tracingContext,
625
- suspend,
626
- resumeData
713
+ }
714
+ if (runs?.[0]?.status === "Cancelled") {
715
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
716
+ workflowName: this.workflowId,
717
+ runId: this.runId
627
718
  });
628
- },
629
- component: "TOOL"
630
- };
631
- }
632
- return {
633
- id: params.id,
634
- description: params.description,
635
- inputSchema: params.inputSchema,
636
- outputSchema: params.outputSchema,
637
- resumeSchema: params.resumeSchema,
638
- suspendSchema: params.suspendSchema,
639
- execute: params.execute
640
- };
641
- }
642
- function init(inngest) {
643
- return {
644
- createWorkflow(params) {
645
- return new InngestWorkflow(
646
- params,
647
- inngest
648
- );
649
- },
650
- createStep,
651
- cloneStep(step, opts) {
652
- return {
653
- id: opts.id,
654
- description: step.description,
655
- inputSchema: step.inputSchema,
656
- outputSchema: step.outputSchema,
657
- resumeSchema: step.resumeSchema,
658
- suspendSchema: step.suspendSchema,
659
- stateSchema: step.stateSchema,
660
- execute: step.execute,
661
- component: step.component
662
- };
663
- },
664
- cloneWorkflow(workflow, opts) {
665
- const wf = new workflows.Workflow({
666
- id: opts.id,
667
- inputSchema: workflow.inputSchema,
668
- outputSchema: workflow.outputSchema,
669
- steps: workflow.stepDefs,
670
- mastra: workflow.mastra
671
- });
672
- wf.setStepFlow(workflow.stepGraph);
673
- wf.commit();
674
- return wf;
719
+ return { output: { result: { steps: snapshot?.context, status: "canceled" } } };
720
+ }
721
+ await new Promise((resolve) => setTimeout(resolve, 1e3 + Math.random() * 1e3));
675
722
  }
676
- };
677
- }
678
- var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
679
- inngestStep;
680
- inngestAttempts;
681
- constructor(mastra, inngestStep, inngestAttempts = 0, options) {
682
- super({ mastra, options });
683
- this.inngestStep = inngestStep;
684
- this.inngestAttempts = inngestAttempts;
723
+ throw new inngest.NonRetriableError(`Workflow did not complete within ${maxWaitMs}ms`);
685
724
  }
686
- async execute(params) {
687
- await params.emitter.emit("watch-v2", {
688
- type: "workflow-start",
689
- payload: { runId: params.runId }
725
+ async cancel() {
726
+ const storage = this.#mastra?.getStorage();
727
+ await this.inngest.send({
728
+ name: `cancel.workflow.${this.workflowId}`,
729
+ data: {
730
+ runId: this.runId
731
+ }
690
732
  });
691
- const result = await super.execute(params);
692
- await params.emitter.emit("watch-v2", {
693
- type: "workflow-finish",
694
- payload: { runId: params.runId }
733
+ const workflowsStore = await storage?.getStore("workflows");
734
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
735
+ workflowName: this.workflowId,
736
+ runId: this.runId
695
737
  });
696
- return result;
697
- }
698
- async fmtReturnValue(emitter, stepResults, lastOutput, error) {
699
- const base = {
700
- status: lastOutput.status,
701
- steps: stepResults
702
- };
703
- if (lastOutput.status === "success") {
704
- await emitter.emit("watch", {
705
- type: "watch",
706
- payload: {
707
- workflowState: {
708
- status: lastOutput.status,
709
- steps: stepResults,
710
- result: lastOutput.output
711
- }
712
- },
713
- eventTimestamp: Date.now()
714
- });
715
- base.result = lastOutput.output;
716
- } else if (lastOutput.status === "failed") {
717
- base.error = error instanceof Error ? error?.stack ?? error.message : lastOutput?.error instanceof Error ? lastOutput.error.message : lastOutput.error ?? error ?? "Unknown error";
718
- await emitter.emit("watch", {
719
- type: "watch",
720
- payload: {
721
- workflowState: {
722
- status: lastOutput.status,
723
- steps: stepResults,
724
- result: null,
725
- error: base.error
726
- }
727
- },
728
- eventTimestamp: Date.now()
729
- });
730
- } else if (lastOutput.status === "suspended") {
731
- await emitter.emit("watch", {
732
- type: "watch",
733
- payload: {
734
- workflowState: {
735
- status: lastOutput.status,
736
- steps: stepResults,
737
- result: null,
738
- error: null
739
- }
740
- },
741
- eventTimestamp: Date.now()
742
- });
743
- const suspendedStepIds = Object.entries(stepResults).flatMap(([stepId, stepResult]) => {
744
- if (stepResult?.status === "suspended") {
745
- const nestedPath = stepResult?.suspendPayload?.__workflow_meta?.path;
746
- return nestedPath ? [[stepId, ...nestedPath]] : [[stepId]];
738
+ if (snapshot) {
739
+ await workflowsStore?.persistWorkflowSnapshot({
740
+ workflowName: this.workflowId,
741
+ runId: this.runId,
742
+ resourceId: this.resourceId,
743
+ snapshot: {
744
+ ...snapshot,
745
+ status: "canceled",
746
+ value: snapshot.value
747
747
  }
748
- return [];
749
748
  });
750
- base.suspended = suspendedStepIds;
751
749
  }
752
- return base;
753
750
  }
754
- // async executeSleep({ id, duration }: { id: string; duration: number }): Promise<void> {
755
- // await this.inngestStep.sleep(id, duration);
756
- // }
757
- async executeSleep({
758
- workflowId,
759
- runId,
760
- entry,
761
- prevOutput,
762
- stepResults,
763
- emitter,
764
- abortController,
765
- runtimeContext,
766
- executionContext,
767
- writableStream,
768
- tracingContext
769
- }) {
770
- let { duration, fn } = entry;
771
- const sleepSpan = tracingContext?.currentSpan?.createChildSpan({
772
- type: aiTracing.AISpanType.WORKFLOW_SLEEP,
773
- name: `sleep: ${duration ? `${duration}ms` : "dynamic"}`,
774
- attributes: {
775
- durationMs: duration,
776
- sleepType: fn ? "dynamic" : "fixed"
777
- },
778
- tracingPolicy: this.options?.tracingPolicy
779
- });
780
- if (fn) {
781
- const stepCallId = crypto.randomUUID();
782
- duration = await this.inngestStep.run(`workflow.${workflowId}.sleep.${entry.id}`, async () => {
783
- return await fn(
784
- workflows.createDeprecationProxy(
785
- {
786
- runId,
787
- workflowId,
788
- mastra: this.mastra,
789
- runtimeContext,
790
- inputData: prevOutput,
791
- state: executionContext.state,
792
- setState: (state) => {
793
- executionContext.state = state;
794
- },
795
- runCount: -1,
796
- retryCount: -1,
797
- tracingContext: {
798
- currentSpan: sleepSpan
799
- },
800
- getInitData: () => stepResults?.input,
801
- getStepResult: workflows.getStepResult.bind(this, stepResults),
802
- // TODO: this function shouldn't have suspend probably?
803
- suspend: async (_suspendPayload) => {
804
- },
805
- bail: () => {
806
- },
807
- abort: () => {
808
- abortController?.abort();
809
- },
810
- [_constants.EMITTER_SYMBOL]: emitter,
811
- // TODO: add streamVNext support
812
- [_constants.STREAM_FORMAT_SYMBOL]: executionContext.format,
813
- engine: { step: this.inngestStep },
814
- abortSignal: abortController?.signal,
815
- writer: new tools.ToolStream(
816
- {
817
- prefix: "workflow-step",
818
- callId: stepCallId,
819
- name: "sleep",
820
- runId
821
- },
822
- writableStream
823
- )
824
- },
825
- {
826
- paramName: "runCount",
827
- deprecationMessage: workflows.runCountDeprecationMessage,
828
- logger: this.logger
829
- }
830
- )
831
- );
832
- });
833
- sleepSpan?.update({
834
- attributes: {
835
- durationMs: duration
836
- }
837
- });
838
- }
839
- try {
840
- await this.inngestStep.sleep(entry.id, !duration || duration < 0 ? 0 : duration);
841
- sleepSpan?.end();
842
- } catch (e) {
843
- sleepSpan?.error({ error: e });
844
- throw e;
845
- }
751
+ async start(args) {
752
+ return this._start(args);
846
753
  }
847
- async executeSleepUntil({
848
- workflowId,
849
- runId,
850
- entry,
851
- prevOutput,
852
- stepResults,
853
- emitter,
854
- abortController,
855
- runtimeContext,
856
- executionContext,
857
- writableStream,
858
- tracingContext
859
- }) {
860
- let { date, fn } = entry;
861
- const sleepUntilSpan = tracingContext?.currentSpan?.createChildSpan({
862
- type: aiTracing.AISpanType.WORKFLOW_SLEEP,
863
- name: `sleepUntil: ${date ? date.toISOString() : "dynamic"}`,
864
- attributes: {
865
- untilDate: date,
866
- durationMs: date ? Math.max(0, date.getTime() - Date.now()) : void 0,
867
- sleepType: fn ? "dynamic" : "fixed"
868
- },
869
- tracingPolicy: this.options?.tracingPolicy
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({
763
+ workflowName: this.workflowId,
764
+ runId: this.runId,
765
+ resourceId: this.resourceId,
766
+ snapshot: {
767
+ runId: this.runId,
768
+ serializedStepGraph: this.serializedStepGraph,
769
+ status: "running",
770
+ value: {},
771
+ context: {},
772
+ activePaths: [],
773
+ suspendedPaths: {},
774
+ activeStepsPath: {},
775
+ resumeLabels: {},
776
+ waitingPaths: {},
777
+ timestamp: Date.now()
778
+ }
870
779
  });
871
- if (fn) {
872
- date = await this.inngestStep.run(`workflow.${workflowId}.sleepUntil.${entry.id}`, async () => {
873
- const stepCallId = crypto.randomUUID();
874
- return await fn(
875
- workflows.createDeprecationProxy(
876
- {
877
- runId,
878
- workflowId,
879
- mastra: this.mastra,
880
- runtimeContext,
881
- inputData: prevOutput,
882
- state: executionContext.state,
883
- setState: (state) => {
884
- executionContext.state = state;
885
- },
886
- runCount: -1,
887
- retryCount: -1,
888
- tracingContext: {
889
- currentSpan: sleepUntilSpan
890
- },
891
- getInitData: () => stepResults?.input,
892
- getStepResult: workflows.getStepResult.bind(this, stepResults),
893
- // TODO: this function shouldn't have suspend probably?
894
- suspend: async (_suspendPayload) => {
895
- },
896
- bail: () => {
897
- },
898
- abort: () => {
899
- abortController?.abort();
900
- },
901
- [_constants.EMITTER_SYMBOL]: emitter,
902
- [_constants.STREAM_FORMAT_SYMBOL]: executionContext.format,
903
- // TODO: add streamVNext support
904
- engine: { step: this.inngestStep },
905
- abortSignal: abortController?.signal,
906
- writer: new tools.ToolStream(
907
- {
908
- prefix: "workflow-step",
909
- callId: stepCallId,
910
- name: "sleep",
911
- runId
912
- },
913
- writableStream
914
- )
915
- },
916
- {
917
- paramName: "runCount",
918
- deprecationMessage: workflows.runCountDeprecationMessage,
919
- logger: this.logger
920
- }
921
- )
922
- );
923
- });
924
- if (date && !(date instanceof Date)) {
925
- date = new Date(date);
780
+ const inputDataToUse = await this._validateInput(args.inputData);
781
+ const initialStateToUse = await this._validateInitialState(args.initialState ?? {});
782
+ const eventOutput = await this.inngest.send({
783
+ name: `workflow.${this.workflowId}`,
784
+ data: {
785
+ inputData: inputDataToUse,
786
+ initialState: initialStateToUse,
787
+ runId: this.runId,
788
+ resourceId: this.resourceId,
789
+ outputOptions: args.outputOptions,
790
+ tracingOptions: args.tracingOptions,
791
+ requestContext: args.requestContext ? Object.fromEntries(args.requestContext.entries()) : {},
792
+ perStep: args.perStep
926
793
  }
927
- const time = !date ? 0 : date.getTime() - Date.now();
928
- sleepUntilSpan?.update({
929
- attributes: {
930
- durationMs: Math.max(0, time)
931
- }
932
- });
933
- }
934
- if (!(date instanceof Date)) {
935
- sleepUntilSpan?.end();
936
- return;
937
- }
938
- try {
939
- await this.inngestStep.sleepUntil(entry.id, date);
940
- sleepUntilSpan?.end();
941
- } catch (e) {
942
- sleepUntilSpan?.error({ error: e });
943
- throw e;
944
- }
945
- }
946
- async executeWaitForEvent({ event, timeout }) {
947
- const eventData = await this.inngestStep.waitForEvent(`user-event-${event}`, {
948
- event: `user-event-${event}`,
949
- timeout: timeout ?? 5e3
950
794
  });
951
- if (eventData === null) {
952
- throw "Timeout waiting for event";
795
+ const eventId = eventOutput.ids[0];
796
+ if (!eventId) {
797
+ throw new Error("Event ID is not set");
953
798
  }
954
- return eventData?.data;
799
+ return { runId: this.runId };
955
800
  }
956
- async executeStep({
957
- step,
958
- stepResults,
959
- executionContext,
960
- resume,
961
- prevOutput,
962
- emitter,
963
- abortController,
964
- runtimeContext,
965
- tracingContext,
966
- writableStream,
967
- disableScorers
801
+ async _start({
802
+ inputData,
803
+ initialState,
804
+ outputOptions,
805
+ tracingOptions,
806
+ format,
807
+ requestContext,
808
+ perStep
968
809
  }) {
969
- const stepAISpan = tracingContext?.currentSpan?.createChildSpan({
970
- name: `workflow step: '${step.id}'`,
971
- type: aiTracing.AISpanType.WORKFLOW_STEP,
972
- input: prevOutput,
973
- attributes: {
974
- stepId: step.id
975
- },
976
- tracingPolicy: this.options?.tracingPolicy
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
+ }
977
828
  });
978
- const { inputData, validationError } = await workflows.validateStepInput({
979
- prevOutput,
980
- step,
981
- validateInputs: this.options?.validateInputs ?? false
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
+ }
982
844
  });
983
- const startedAt = await this.inngestStep.run(
984
- `workflow.${executionContext.workflowId}.run.${executionContext.runId}.step.${step.id}.running_ev`,
985
- async () => {
986
- const startedAt2 = Date.now();
987
- await emitter.emit("watch", {
988
- type: "watch",
989
- payload: {
990
- currentStep: {
991
- id: step.id,
992
- status: "running"
993
- },
994
- workflowState: {
995
- status: "running",
996
- steps: {
997
- ...stepResults,
998
- [step.id]: {
999
- status: "running"
1000
- }
1001
- },
1002
- result: null,
1003
- error: null
1004
- }
1005
- },
1006
- eventTimestamp: Date.now()
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;
856
+ }
857
+ async resume(params) {
858
+ const p = this._resume(params).then((result) => {
859
+ if (result.status !== "suspended") {
860
+ this.closeStreamAction?.().catch(() => {
1007
861
  });
1008
- await emitter.emit("watch-v2", {
1009
- type: "workflow-step-start",
1010
- payload: {
1011
- id: step.id,
1012
- status: "running",
1013
- payload: inputData,
1014
- startedAt: startedAt2
1015
- }
862
+ }
863
+ return result;
864
+ });
865
+ this.executionResults = p;
866
+ return p;
867
+ }
868
+ async _resume(params) {
869
+ const storage = this.#mastra?.getStorage();
870
+ let steps = [];
871
+ if (typeof params.step === "string") {
872
+ steps = params.step.split(".");
873
+ } else {
874
+ steps = (Array.isArray(params.step) ? params.step : [params.step]).map(
875
+ (step) => typeof step === "string" ? step : step?.id
876
+ );
877
+ }
878
+ const workflowsStore = await storage?.getStore("workflows");
879
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
880
+ workflowName: this.workflowId,
881
+ runId: this.runId
882
+ });
883
+ const suspendedStep = this.workflowSteps[steps?.[0] ?? ""];
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 };
888
+ const eventOutput = await this.inngest.send({
889
+ name: `workflow.${this.workflowId}`,
890
+ data: {
891
+ inputData: resumeDataToUse,
892
+ initialState: snapshot?.value ?? {},
893
+ runId: this.runId,
894
+ workflowId: this.workflowId,
895
+ stepResults: snapshot?.context,
896
+ resume: {
897
+ steps,
898
+ stepResults: snapshot?.context,
899
+ resumePayload: resumeDataToUse,
900
+ resumePath: steps?.[0] ? snapshot?.suspendedPaths?.[steps?.[0]] : void 0
901
+ },
902
+ requestContext: mergedRequestContext,
903
+ perStep: params.perStep
904
+ }
905
+ });
906
+ const eventId = eventOutput.ids[0];
907
+ if (!eventId) {
908
+ throw new Error("Event ID is not set");
909
+ }
910
+ const runOutput = await this.getRunOutput(eventId);
911
+ const result = runOutput?.output?.result;
912
+ this.hydrateFailedResult(result);
913
+ return result;
914
+ }
915
+ async timeTravel(params) {
916
+ const p = this._timeTravel(params).then((result) => {
917
+ if (result.status !== "suspended") {
918
+ this.closeStreamAction?.().catch(() => {
1016
919
  });
1017
- return startedAt2;
920
+ }
921
+ return result;
922
+ });
923
+ this.executionResults = p;
924
+ return p;
925
+ }
926
+ async _timeTravel(params) {
927
+ if (!params.step || Array.isArray(params.step) && params.step?.length === 0) {
928
+ throw new Error("Step is required and must be a valid step or array of steps");
929
+ }
930
+ let steps = [];
931
+ if (typeof params.step === "string") {
932
+ steps = params.step.split(".");
933
+ } else {
934
+ steps = (Array.isArray(params.step) ? params.step : [params.step]).map(
935
+ (step) => typeof step === "string" ? step : step?.id
936
+ );
937
+ }
938
+ if (steps.length === 0) {
939
+ throw new Error("No steps provided to timeTravel");
940
+ }
941
+ const storage = this.#mastra?.getStorage();
942
+ const workflowsStore = await storage?.getStore("workflows");
943
+ const snapshot = await workflowsStore?.loadWorkflowSnapshot({
944
+ workflowName: this.workflowId,
945
+ runId: this.runId
946
+ });
947
+ if (!snapshot) {
948
+ await workflowsStore?.persistWorkflowSnapshot({
949
+ workflowName: this.workflowId,
950
+ runId: this.runId,
951
+ resourceId: this.resourceId,
952
+ snapshot: {
953
+ runId: this.runId,
954
+ serializedStepGraph: this.serializedStepGraph,
955
+ status: "pending",
956
+ value: {},
957
+ context: {},
958
+ activePaths: [],
959
+ suspendedPaths: {},
960
+ activeStepsPath: {},
961
+ resumeLabels: {},
962
+ waitingPaths: {},
963
+ timestamp: Date.now()
964
+ }
965
+ });
966
+ }
967
+ if (snapshot?.status === "running") {
968
+ throw new Error("This workflow run is still running, cannot time travel");
969
+ }
970
+ let inputDataToUse = params.inputData;
971
+ if (inputDataToUse && steps.length === 1) {
972
+ inputDataToUse = await this._validateTimetravelInputData(params.inputData, this.workflowSteps[steps[0]]);
973
+ }
974
+ const timeTravelData = workflows.createTimeTravelExecutionParams({
975
+ steps,
976
+ inputData: inputDataToUse,
977
+ resumeData: params.resumeData,
978
+ context: params.context,
979
+ nestedStepsContext: params.nestedStepsContext,
980
+ snapshot: snapshot ?? { context: {} },
981
+ graph: this.executionGraph,
982
+ initialState: params.initialState,
983
+ perStep: params.perStep
984
+ });
985
+ const eventOutput = await this.inngest.send({
986
+ name: `workflow.${this.workflowId}`,
987
+ data: {
988
+ initialState: timeTravelData.state,
989
+ runId: this.runId,
990
+ workflowId: this.workflowId,
991
+ stepResults: timeTravelData.stepResults,
992
+ timeTravel: timeTravelData,
993
+ tracingOptions: params.tracingOptions,
994
+ outputOptions: params.outputOptions,
995
+ requestContext: params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {},
996
+ perStep: params.perStep
997
+ }
998
+ });
999
+ const eventId = eventOutput.ids[0];
1000
+ if (!eventId) {
1001
+ throw new Error("Event ID is not set");
1002
+ }
1003
+ const runOutput = await this.getRunOutput(eventId);
1004
+ const result = runOutput?.output?.result;
1005
+ this.hydrateFailedResult(result);
1006
+ return result;
1007
+ }
1008
+ watch(cb) {
1009
+ let active = true;
1010
+ const streamPromise = realtime.subscribe(
1011
+ {
1012
+ channel: `workflow:${this.workflowId}:${this.runId}`,
1013
+ topics: ["watch"],
1014
+ app: this.inngest
1015
+ },
1016
+ (message) => {
1017
+ if (active) {
1018
+ cb(message.data);
1019
+ }
1018
1020
  }
1019
1021
  );
1020
- if (step instanceof InngestWorkflow) {
1021
- const isResume = !!resume?.steps?.length;
1022
- let result;
1023
- let runId;
1022
+ return () => {
1023
+ active = false;
1024
+ streamPromise.then(async (stream) => {
1025
+ return stream.cancel();
1026
+ }).catch((err) => {
1027
+ console.error(err);
1028
+ });
1029
+ };
1030
+ }
1031
+ streamLegacy({ inputData, requestContext } = {}) {
1032
+ const { readable, writable } = new TransformStream();
1033
+ const writer = writable.getWriter();
1034
+ void writer.write({
1035
+ // @ts-expect-error
1036
+ type: "start",
1037
+ payload: { runId: this.runId }
1038
+ });
1039
+ const unwatch = this.watch(async (event) => {
1024
1040
  try {
1025
- if (isResume) {
1026
- runId = stepResults[resume?.steps?.[0]]?.suspendPayload?.__workflow_meta?.runId ?? crypto.randomUUID();
1027
- const snapshot = await this.mastra?.getStorage()?.loadWorkflowSnapshot({
1028
- workflowName: step.id,
1029
- runId
1030
- });
1031
- const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
1032
- function: step.getFunction(),
1033
- data: {
1034
- inputData,
1035
- initialState: executionContext.state ?? snapshot?.value ?? {},
1036
- runId,
1037
- resume: {
1038
- runId,
1039
- steps: resume.steps.slice(1),
1040
- stepResults: snapshot?.context,
1041
- resumePayload: resume.resumePayload,
1042
- // @ts-ignore
1043
- resumePath: snapshot?.suspendedPaths?.[resume.steps?.[1]]
1044
- },
1045
- outputOptions: { includeState: true }
1046
- }
1047
- });
1048
- result = invokeResp.result;
1049
- runId = invokeResp.runId;
1050
- executionContext.state = invokeResp.result.state;
1051
- } else {
1052
- const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
1053
- function: step.getFunction(),
1054
- data: {
1055
- inputData,
1056
- initialState: executionContext.state ?? {},
1057
- outputOptions: { includeState: true }
1058
- }
1059
- });
1060
- result = invokeResp.result;
1061
- runId = invokeResp.runId;
1062
- executionContext.state = invokeResp.result.state;
1063
- }
1064
- } catch (e) {
1065
- const errorCause = e?.cause;
1066
- if (errorCause && typeof errorCause === "object") {
1067
- result = errorCause;
1068
- runId = errorCause.runId || crypto.randomUUID();
1069
- } else {
1070
- runId = crypto.randomUUID();
1071
- result = {
1072
- status: "failed",
1073
- error: e instanceof Error ? e : new Error(String(e)),
1074
- steps: {},
1075
- input: inputData
1076
- };
1041
+ const e = {
1042
+ ...event,
1043
+ type: event.type.replace("workflow-", "")
1044
+ };
1045
+ if (e.type === "step-output") {
1046
+ e.type = e.payload.output.type;
1047
+ e.payload = e.payload.output.payload;
1077
1048
  }
1049
+ await writer.write(e);
1050
+ } catch {
1078
1051
  }
1079
- const res = await this.inngestStep.run(
1080
- `workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
1081
- async () => {
1082
- if (result.status === "failed") {
1083
- await emitter.emit("watch", {
1084
- type: "watch",
1085
- payload: {
1086
- currentStep: {
1087
- id: step.id,
1088
- status: "failed",
1089
- error: result?.error
1090
- },
1091
- workflowState: {
1092
- status: "running",
1093
- steps: stepResults,
1094
- result: null,
1095
- error: null
1096
- }
1097
- },
1098
- eventTimestamp: Date.now()
1099
- });
1100
- await emitter.emit("watch-v2", {
1101
- type: "workflow-step-result",
1102
- payload: {
1103
- id: step.id,
1104
- status: "failed",
1105
- error: result?.error,
1106
- payload: prevOutput
1107
- }
1108
- });
1109
- return { executionContext, result: { status: "failed", error: result?.error } };
1110
- } else if (result.status === "suspended") {
1111
- const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
1112
- const stepRes2 = stepResult;
1113
- return stepRes2?.status === "suspended";
1114
- });
1115
- for (const [stepName, stepResult] of suspendedSteps) {
1116
- const suspendPath = [stepName, ...stepResult?.suspendPayload?.__workflow_meta?.path ?? []];
1117
- executionContext.suspendedPaths[step.id] = executionContext.executionPath;
1118
- await emitter.emit("watch", {
1119
- type: "watch",
1120
- payload: {
1121
- currentStep: {
1122
- id: step.id,
1123
- status: "suspended",
1124
- payload: stepResult.payload,
1125
- suspendPayload: {
1126
- ...stepResult?.suspendPayload,
1127
- __workflow_meta: { runId, path: suspendPath }
1128
- }
1129
- },
1130
- workflowState: {
1131
- status: "running",
1132
- steps: stepResults,
1133
- result: null,
1134
- error: null
1135
- }
1136
- },
1137
- eventTimestamp: Date.now()
1138
- });
1139
- await emitter.emit("watch-v2", {
1140
- type: "workflow-step-suspended",
1141
- payload: {
1142
- id: step.id,
1143
- status: "suspended"
1144
- }
1145
- });
1146
- return {
1147
- executionContext,
1148
- result: {
1149
- status: "suspended",
1150
- payload: stepResult.payload,
1151
- suspendPayload: {
1152
- ...stepResult?.suspendPayload,
1153
- __workflow_meta: { runId, path: suspendPath }
1154
- }
1155
- }
1156
- };
1157
- }
1158
- await emitter.emit("watch", {
1159
- type: "watch",
1160
- payload: {
1161
- currentStep: {
1162
- id: step.id,
1163
- status: "suspended",
1164
- payload: {}
1165
- },
1166
- workflowState: {
1167
- status: "running",
1168
- steps: stepResults,
1169
- result: null,
1170
- error: null
1171
- }
1172
- },
1173
- eventTimestamp: Date.now()
1174
- });
1175
- return {
1176
- executionContext,
1177
- result: {
1178
- status: "suspended",
1179
- payload: {}
1180
- }
1181
- };
1182
- }
1183
- await emitter.emit("watch", {
1184
- type: "watch",
1185
- payload: {
1186
- currentStep: {
1187
- id: step.id,
1188
- status: "success",
1189
- output: result?.result
1190
- },
1191
- workflowState: {
1192
- status: "running",
1193
- steps: stepResults,
1194
- result: null,
1195
- error: null
1196
- }
1197
- },
1198
- eventTimestamp: Date.now()
1199
- });
1200
- await emitter.emit("watch-v2", {
1201
- type: "workflow-step-result",
1202
- payload: {
1203
- id: step.id,
1204
- status: "success",
1205
- output: result?.result
1206
- }
1207
- });
1208
- await emitter.emit("watch-v2", {
1209
- type: "workflow-step-finish",
1052
+ });
1053
+ this.closeStreamAction = async () => {
1054
+ await writer.write({
1055
+ type: "finish",
1056
+ // @ts-expect-error
1057
+ payload: { runId: this.runId }
1058
+ });
1059
+ unwatch();
1060
+ try {
1061
+ await writer.close();
1062
+ } catch (err) {
1063
+ console.error("Error closing stream:", err);
1064
+ } finally {
1065
+ writer.releaseLock();
1066
+ }
1067
+ };
1068
+ this.executionResults = this._start({ inputData, requestContext, format: "legacy" }).then((result) => {
1069
+ if (result.status !== "suspended") {
1070
+ this.closeStreamAction?.().catch(() => {
1071
+ });
1072
+ }
1073
+ return result;
1074
+ });
1075
+ return {
1076
+ stream: readable,
1077
+ getWorkflowState: () => this.executionResults
1078
+ };
1079
+ }
1080
+ stream({
1081
+ inputData,
1082
+ requestContext,
1083
+ tracingOptions,
1084
+ closeOnSuspend = true,
1085
+ initialState,
1086
+ outputOptions,
1087
+ perStep
1088
+ } = {}) {
1089
+ if (this.closeStreamAction && this.streamOutput) {
1090
+ return this.streamOutput;
1091
+ }
1092
+ this.closeStreamAction = async () => {
1093
+ };
1094
+ const self = this;
1095
+ const stream$1 = new web.ReadableStream({
1096
+ async start(controller) {
1097
+ const unwatch = self.watch(async ({ type, from = stream.ChunkFrom.WORKFLOW, payload }) => {
1098
+ controller.enqueue({
1099
+ type,
1100
+ runId: self.runId,
1101
+ from,
1210
1102
  payload: {
1211
- id: step.id,
1212
- metadata: {}
1103
+ stepName: payload?.id,
1104
+ ...payload
1213
1105
  }
1214
1106
  });
1215
- return { executionContext, result: { status: "success", output: result?.result } };
1216
- }
1217
- );
1218
- Object.assign(executionContext, res.executionContext);
1219
- return {
1220
- ...res.result,
1221
- startedAt,
1222
- endedAt: Date.now(),
1223
- payload: inputData,
1224
- resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1225
- resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1226
- };
1227
- }
1228
- let stepRes;
1229
- try {
1230
- stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
1231
- let execResults;
1232
- let suspended;
1233
- let bailed;
1107
+ });
1108
+ self.closeStreamAction = async () => {
1109
+ unwatch();
1110
+ try {
1111
+ await controller.close();
1112
+ } catch (err) {
1113
+ console.error("Error closing stream:", err);
1114
+ }
1115
+ };
1116
+ const executionResultsPromise = self._start({
1117
+ inputData,
1118
+ requestContext,
1119
+ // tracingContext, // We are not able to pass a reference to a span here, what to do?
1120
+ initialState,
1121
+ tracingOptions,
1122
+ outputOptions,
1123
+ format: "vnext",
1124
+ perStep
1125
+ });
1126
+ let executionResults;
1234
1127
  try {
1235
- if (validationError) {
1236
- throw validationError;
1128
+ executionResults = await executionResultsPromise;
1129
+ if (closeOnSuspend) {
1130
+ self.closeStreamAction?.().catch(() => {
1131
+ });
1132
+ } else if (executionResults.status !== "suspended") {
1133
+ self.closeStreamAction?.().catch(() => {
1134
+ });
1237
1135
  }
1238
- const result = await step.execute({
1239
- runId: executionContext.runId,
1240
- mastra: this.mastra,
1241
- runtimeContext,
1242
- writableStream,
1243
- state: executionContext?.state ?? {},
1244
- setState: (state) => {
1245
- executionContext.state = state;
1246
- },
1247
- inputData,
1248
- resumeData: resume?.steps[0] === step.id ? resume?.resumePayload : void 0,
1249
- tracingContext: {
1250
- currentSpan: stepAISpan
1251
- },
1252
- getInitData: () => stepResults?.input,
1253
- getStepResult: workflows.getStepResult.bind(this, stepResults),
1254
- suspend: async (suspendPayload, suspendOptions) => {
1255
- executionContext.suspendedPaths[step.id] = executionContext.executionPath;
1256
- if (suspendOptions?.resumeLabel) {
1257
- const resumeLabel = Array.isArray(suspendOptions.resumeLabel) ? suspendOptions.resumeLabel : [suspendOptions.resumeLabel];
1258
- for (const label of resumeLabel) {
1259
- executionContext.resumeLabels[label] = {
1260
- stepId: step.id,
1261
- foreachIndex: executionContext.foreachIndex
1262
- };
1263
- }
1264
- }
1265
- suspended = { payload: suspendPayload };
1266
- },
1267
- bail: (result2) => {
1268
- bailed = { payload: result2 };
1269
- },
1270
- resume: {
1271
- steps: resume?.steps?.slice(1) || [],
1272
- resumePayload: resume?.resumePayload,
1273
- // @ts-ignore
1274
- runId: stepResults[step.id]?.suspendPayload?.__workflow_meta?.runId
1275
- },
1276
- [_constants.EMITTER_SYMBOL]: emitter,
1277
- engine: {
1278
- step: this.inngestStep
1279
- },
1280
- abortSignal: abortController.signal
1281
- });
1282
- const endedAt = Date.now();
1283
- execResults = {
1284
- status: "success",
1285
- output: result,
1286
- startedAt,
1287
- endedAt,
1288
- payload: inputData,
1289
- resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1290
- resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1291
- };
1292
- } catch (e) {
1293
- const stepFailure = {
1294
- status: "failed",
1295
- payload: inputData,
1296
- error: e instanceof Error ? e.message : String(e),
1297
- endedAt: Date.now(),
1298
- startedAt,
1299
- resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1300
- resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1301
- };
1302
- execResults = stepFailure;
1303
- const fallbackErrorMessage = `Step ${step.id} failed`;
1304
- stepAISpan?.error({ error: new Error(execResults.error ?? fallbackErrorMessage) });
1305
- throw new inngest.RetryAfterError(execResults.error ?? fallbackErrorMessage, executionContext.retryConfig.delay, {
1306
- cause: execResults
1136
+ if (self.streamOutput) {
1137
+ self.streamOutput.updateResults(
1138
+ executionResults
1139
+ );
1140
+ }
1141
+ } catch (err) {
1142
+ self.streamOutput?.rejectResults(err);
1143
+ self.closeStreamAction?.().catch(() => {
1307
1144
  });
1308
1145
  }
1309
- if (suspended) {
1310
- execResults = {
1311
- status: "suspended",
1312
- suspendPayload: suspended.payload,
1313
- payload: inputData,
1314
- suspendedAt: Date.now(),
1315
- startedAt,
1316
- resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
1317
- resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
1318
- };
1319
- } else if (bailed) {
1320
- execResults = {
1321
- status: "bailed",
1322
- output: bailed.payload,
1323
- payload: inputData,
1324
- endedAt: Date.now(),
1325
- startedAt
1326
- };
1327
- }
1328
- await emitter.emit("watch", {
1329
- type: "watch",
1330
- payload: {
1331
- currentStep: {
1332
- id: step.id,
1333
- ...execResults
1334
- },
1335
- workflowState: {
1336
- status: "running",
1337
- steps: { ...stepResults, [step.id]: execResults },
1338
- result: null,
1339
- error: null
1340
- }
1341
- },
1342
- eventTimestamp: Date.now()
1343
- });
1344
- if (execResults.status === "suspended") {
1345
- await emitter.emit("watch-v2", {
1346
- type: "workflow-step-suspended",
1146
+ }
1147
+ });
1148
+ this.streamOutput = new stream.WorkflowRunOutput({
1149
+ runId: this.runId,
1150
+ workflowId: this.workflowId,
1151
+ stream: stream$1
1152
+ });
1153
+ return this.streamOutput;
1154
+ }
1155
+ timeTravelStream({
1156
+ inputData,
1157
+ resumeData,
1158
+ initialState,
1159
+ step,
1160
+ context,
1161
+ nestedStepsContext,
1162
+ requestContext,
1163
+ // tracingContext,
1164
+ tracingOptions,
1165
+ outputOptions,
1166
+ perStep
1167
+ }) {
1168
+ this.closeStreamAction = async () => {
1169
+ };
1170
+ const self = this;
1171
+ const stream$1 = new web.ReadableStream({
1172
+ async start(controller) {
1173
+ const unwatch = self.watch(async ({ type, from = stream.ChunkFrom.WORKFLOW, payload }) => {
1174
+ controller.enqueue({
1175
+ type,
1176
+ runId: self.runId,
1177
+ from,
1347
1178
  payload: {
1348
- id: step.id,
1349
- ...execResults
1179
+ stepName: payload?.id,
1180
+ ...payload
1350
1181
  }
1351
1182
  });
1352
- } else {
1353
- await emitter.emit("watch-v2", {
1354
- type: "workflow-step-result",
1355
- payload: {
1356
- id: step.id,
1357
- ...execResults
1358
- }
1183
+ });
1184
+ self.closeStreamAction = async () => {
1185
+ unwatch();
1186
+ try {
1187
+ controller.close();
1188
+ } catch (err) {
1189
+ console.error("Error closing stream:", err);
1190
+ }
1191
+ };
1192
+ const executionResultsPromise = self._timeTravel({
1193
+ inputData,
1194
+ step,
1195
+ context,
1196
+ nestedStepsContext,
1197
+ resumeData,
1198
+ initialState,
1199
+ requestContext,
1200
+ tracingOptions,
1201
+ outputOptions,
1202
+ perStep
1203
+ });
1204
+ self.executionResults = executionResultsPromise;
1205
+ let executionResults;
1206
+ try {
1207
+ executionResults = await executionResultsPromise;
1208
+ self.closeStreamAction?.().catch(() => {
1359
1209
  });
1360
- await emitter.emit("watch-v2", {
1361
- type: "workflow-step-finish",
1362
- payload: {
1363
- id: step.id,
1364
- metadata: {}
1365
- }
1210
+ if (self.streamOutput) {
1211
+ self.streamOutput.updateResults(executionResults);
1212
+ }
1213
+ } catch (err) {
1214
+ self.streamOutput?.rejectResults(err);
1215
+ self.closeStreamAction?.().catch(() => {
1366
1216
  });
1367
1217
  }
1368
- stepAISpan?.end({ output: execResults });
1369
- return { result: execResults, executionContext, stepResults };
1370
- });
1371
- } catch (e) {
1372
- const stepFailure = e instanceof Error ? e?.cause : {
1373
- status: "failed",
1374
- error: e instanceof Error ? e.message : String(e),
1375
- payload: inputData,
1376
- startedAt,
1377
- endedAt: Date.now()
1378
- };
1379
- stepRes = {
1380
- result: stepFailure,
1381
- executionContext,
1382
- stepResults: {
1383
- ...stepResults,
1384
- [step.id]: stepFailure
1218
+ }
1219
+ });
1220
+ this.streamOutput = new stream.WorkflowRunOutput({
1221
+ runId: this.runId,
1222
+ workflowId: this.workflowId,
1223
+ stream: stream$1
1224
+ });
1225
+ return this.streamOutput;
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
+ }
1239
+ };
1240
+
1241
+ // src/workflow.ts
1242
+ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
1243
+ #mastra;
1244
+ inngest;
1245
+ function;
1246
+ cronFunction;
1247
+ flowControlConfig;
1248
+ cronConfig;
1249
+ constructor(params, inngest) {
1250
+ const { concurrency, rateLimit, throttle, debounce, priority, cron, inputData, initialState, ...workflowParams } = params;
1251
+ super(workflowParams);
1252
+ this.engineType = "inngest";
1253
+ const flowControlEntries = Object.entries({ concurrency, rateLimit, throttle, debounce, priority }).filter(
1254
+ ([_, value]) => value !== void 0
1255
+ );
1256
+ this.flowControlConfig = flowControlEntries.length > 0 ? Object.fromEntries(flowControlEntries) : void 0;
1257
+ this.#mastra = params.mastra;
1258
+ this.inngest = inngest;
1259
+ if (cron) {
1260
+ this.cronConfig = { cron, inputData, initialState };
1261
+ }
1262
+ }
1263
+ async listWorkflowRuns(args) {
1264
+ const storage = this.#mastra?.getStorage();
1265
+ if (!storage) {
1266
+ this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
1267
+ return { runs: [], total: 0 };
1268
+ }
1269
+ const workflowsStore = await storage.getStore("workflows");
1270
+ if (!workflowsStore) {
1271
+ return { runs: [], total: 0 };
1272
+ }
1273
+ return workflowsStore.listWorkflowRuns({ workflowName: this.id, ...args ?? {} });
1274
+ }
1275
+ __registerMastra(mastra) {
1276
+ super.__registerMastra(mastra);
1277
+ this.#mastra = mastra;
1278
+ this.executionEngine.__registerMastra(mastra);
1279
+ const updateNested = (step) => {
1280
+ if ((step.type === "step" || step.type === "loop" || step.type === "foreach") && step.step instanceof _InngestWorkflow) {
1281
+ step.step.__registerMastra(mastra);
1282
+ } else if (step.type === "parallel" || step.type === "conditional") {
1283
+ for (const subStep of step.steps) {
1284
+ updateNested(subStep);
1385
1285
  }
1386
- };
1286
+ }
1287
+ };
1288
+ if (this.executionGraph.steps.length) {
1289
+ for (const step of this.executionGraph.steps) {
1290
+ updateNested(step);
1291
+ }
1387
1292
  }
1388
- if (disableScorers !== false && stepRes.result.status === "success") {
1389
- await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}.score`, async () => {
1390
- if (step.scorers) {
1391
- await this.runScorers({
1392
- scorers: step.scorers,
1393
- runId: executionContext.runId,
1394
- input: inputData,
1395
- output: stepRes.result,
1396
- workflowId: executionContext.workflowId,
1397
- stepId: step.id,
1398
- runtimeContext,
1399
- disableScorers,
1400
- tracingContext: { currentSpan: stepAISpan }
1401
- });
1293
+ }
1294
+ async createRun(options) {
1295
+ const runIdToUse = options?.runId || crypto$1.randomUUID();
1296
+ const existingInMemoryRun = this.runs.get(runIdToUse);
1297
+ const newRun = new InngestRun(
1298
+ {
1299
+ workflowId: this.id,
1300
+ runId: runIdToUse,
1301
+ resourceId: options?.resourceId,
1302
+ executionEngine: this.executionEngine,
1303
+ executionGraph: this.executionGraph,
1304
+ serializedStepGraph: this.serializedStepGraph,
1305
+ mastra: this.#mastra,
1306
+ retryConfig: this.retryConfig,
1307
+ cleanup: () => this.runs.delete(runIdToUse),
1308
+ workflowSteps: this.steps,
1309
+ workflowEngineType: this.engineType,
1310
+ validateInputs: this.options.validateInputs
1311
+ },
1312
+ this.inngest
1313
+ );
1314
+ const run = existingInMemoryRun ?? newRun;
1315
+ this.runs.set(runIdToUse, run);
1316
+ const shouldPersistSnapshot = this.options.shouldPersistSnapshot({
1317
+ workflowStatus: run.workflowRunStatus,
1318
+ stepResults: {}
1319
+ });
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({
1327
+ workflowName: this.id,
1328
+ runId: runIdToUse,
1329
+ resourceId: options?.resourceId,
1330
+ snapshot: {
1331
+ runId: runIdToUse,
1332
+ status: "pending",
1333
+ value: {},
1334
+ context: {},
1335
+ activePaths: [],
1336
+ activeStepsPath: {},
1337
+ waitingPaths: {},
1338
+ serializedStepGraph: this.serializedStepGraph,
1339
+ suspendedPaths: {},
1340
+ resumeLabels: {},
1341
+ result: void 0,
1342
+ error: void 0,
1343
+ timestamp: Date.now()
1402
1344
  }
1403
1345
  });
1404
1346
  }
1405
- Object.assign(executionContext.suspendedPaths, stepRes.executionContext.suspendedPaths);
1406
- Object.assign(stepResults, stepRes.stepResults);
1407
- executionContext.state = stepRes.executionContext.state;
1408
- return stepRes.result;
1347
+ return run;
1409
1348
  }
1410
- async persistStepUpdate({
1411
- workflowId,
1412
- runId,
1413
- stepResults,
1414
- resourceId,
1415
- executionContext,
1416
- serializedStepGraph,
1417
- workflowStatus,
1418
- result,
1419
- error
1420
- }) {
1421
- await this.inngestStep.run(
1422
- `workflow.${workflowId}.run.${runId}.path.${JSON.stringify(executionContext.executionPath)}.stepUpdate`,
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 ?? "" },
1423
1362
  async () => {
1424
- const shouldPersistSnapshot = this.options.shouldPersistSnapshot({ stepResults, workflowStatus });
1425
- if (!shouldPersistSnapshot) {
1426
- return;
1427
- }
1428
- await this.mastra?.getStorage()?.persistWorkflowSnapshot({
1429
- workflowName: workflowId,
1430
- runId,
1431
- resourceId,
1432
- snapshot: {
1433
- runId,
1434
- value: executionContext.state,
1435
- context: stepResults,
1436
- activePaths: [],
1437
- suspendedPaths: executionContext.suspendedPaths,
1438
- resumeLabels: executionContext.resumeLabels,
1439
- waitingPaths: {},
1440
- serializedStepGraph,
1441
- status: workflowStatus,
1442
- result,
1443
- error,
1444
- // @ts-ignore
1445
- timestamp: Date.now()
1446
- }
1363
+ const run = await this.createRun();
1364
+ const result = await run.start({
1365
+ inputData: this.cronConfig?.inputData,
1366
+ initialState: this.cronConfig?.initialState
1447
1367
  });
1368
+ return { result, runId: run.runId };
1448
1369
  }
1449
1370
  );
1371
+ return this.cronFunction;
1450
1372
  }
1451
- async executeConditional({
1452
- workflowId,
1453
- runId,
1454
- entry,
1455
- prevOutput,
1456
- stepResults,
1457
- resume,
1458
- executionContext,
1459
- emitter,
1460
- abortController,
1461
- runtimeContext,
1462
- writableStream,
1463
- disableScorers,
1464
- tracingContext
1465
- }) {
1466
- const conditionalSpan = tracingContext?.currentSpan?.createChildSpan({
1467
- type: aiTracing.AISpanType.WORKFLOW_CONDITIONAL,
1468
- name: `conditional: '${entry.conditions.length} conditions'`,
1469
- input: prevOutput,
1470
- attributes: {
1471
- conditionCount: entry.conditions.length
1373
+ getFunction() {
1374
+ if (this.function) {
1375
+ return this.function;
1376
+ }
1377
+ this.function = this.inngest.createFunction(
1378
+ {
1379
+ id: `workflow.${this.id}`,
1380
+ retries: 0,
1381
+ cancelOn: [{ event: `cancel.workflow.${this.id}` }],
1382
+ // Spread flow control configuration
1383
+ ...this.flowControlConfig
1472
1384
  },
1473
- tracingPolicy: this.options?.tracingPolicy
1474
- });
1475
- let execResults;
1476
- const truthyIndexes = (await Promise.all(
1477
- entry.conditions.map(
1478
- (cond, index) => this.inngestStep.run(`workflow.${workflowId}.conditional.${index}`, async () => {
1479
- const evalSpan = conditionalSpan?.createChildSpan({
1480
- type: aiTracing.AISpanType.WORKFLOW_CONDITIONAL_EVAL,
1481
- name: `condition: '${index}'`,
1482
- input: prevOutput,
1483
- attributes: {
1484
- conditionIndex: index
1385
+ { event: `workflow.${this.id}` },
1386
+ async ({ event, step, attempt, publish }) => {
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;
1399
+ if (!runId) {
1400
+ runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
1401
+ return crypto$1.randomUUID();
1402
+ });
1403
+ }
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
1485
1420
  },
1486
- tracingPolicy: this.options?.tracingPolicy
1421
+ tracingPolicy,
1422
+ tracingOptions,
1423
+ requestContext
1487
1424
  });
1488
- try {
1489
- const result = await cond(
1490
- workflows.createDeprecationProxy(
1491
- {
1425
+ return span?.exportSpan();
1426
+ });
1427
+ const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
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",
1492
1458
  runId,
1493
- workflowId,
1494
- mastra: this.mastra,
1495
- runtimeContext,
1496
- runCount: -1,
1497
- retryCount: -1,
1498
- inputData: prevOutput,
1499
- state: executionContext.state,
1500
- setState: (state) => {
1501
- executionContext.state = state;
1502
- },
1503
- tracingContext: {
1504
- currentSpan: evalSpan
1505
- },
1506
- getInitData: () => stepResults?.input,
1507
- getStepResult: workflows.getStepResult.bind(this, stepResults),
1508
- // TODO: this function shouldn't have suspend probably?
1509
- suspend: async (_suspendPayload) => {
1510
- },
1511
- bail: () => {
1512
- },
1513
- abort: () => {
1514
- abortController.abort();
1515
- },
1516
- [_constants.EMITTER_SYMBOL]: emitter,
1517
- [_constants.STREAM_FORMAT_SYMBOL]: executionContext.format,
1518
- // TODO: add streamVNext support
1519
- engine: {
1520
- step: this.inngestStep
1521
- },
1522
- abortSignal: abortController.signal,
1523
- writer: new tools.ToolStream(
1524
- {
1525
- prefix: "workflow-step",
1526
- callId: crypto.randomUUID(),
1527
- name: "conditional",
1528
- runId
1529
- },
1530
- writableStream
1531
- )
1532
- },
1533
- {
1534
- paramName: "runCount",
1535
- deprecationMessage: workflows.runCountDeprecationMessage,
1536
- logger: this.logger
1537
- }
1538
- )
1539
- );
1540
- evalSpan?.end({
1541
- output: result,
1542
- attributes: {
1543
- result: !!result
1459
+ data: chunk
1460
+ });
1461
+ } catch (err) {
1462
+ this.logger.debug?.("Failed to publish watch event:", err);
1544
1463
  }
1464
+ }
1465
+ });
1466
+ } catch (error) {
1467
+ throw error;
1468
+ }
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 ?? {}
1545
1483
  });
1546
- return result ? index : null;
1547
- } catch (e) {
1548
- evalSpan?.error({
1549
- error: e instanceof Error ? e : new Error(String(e)),
1550
- attributes: {
1551
- result: false
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
+ });
1552
1499
  }
1500
+ }
1501
+ }
1502
+ if (result.status === "failed") {
1503
+ throw new inngest.NonRetriableError(`Workflow failed`, {
1504
+ cause: result
1553
1505
  });
1554
- return null;
1555
1506
  }
1556
- })
1557
- )
1558
- )).filter((index) => index !== null);
1559
- const stepsToRun = entry.steps.filter((_, index) => truthyIndexes.includes(index));
1560
- conditionalSpan?.update({
1561
- attributes: {
1562
- truthyIndexes,
1563
- selectedSteps: stepsToRun.map((s) => s.type === "step" ? s.step.id : `control-${s.type}`)
1507
+ return result;
1508
+ });
1509
+ return { result, runId };
1510
+ }
1511
+ );
1512
+ return this.function;
1513
+ }
1514
+ getNestedFunctions(steps) {
1515
+ return steps.flatMap((step) => {
1516
+ if (step.type === "step" || step.type === "loop" || step.type === "foreach") {
1517
+ if (step.step instanceof _InngestWorkflow) {
1518
+ return [step.step.getFunction(), ...step.step.getNestedFunctions(step.step.executionGraph.steps)];
1519
+ }
1520
+ return [];
1521
+ } else if (step.type === "parallel" || step.type === "conditional") {
1522
+ return this.getNestedFunctions(step.steps);
1564
1523
  }
1524
+ return [];
1565
1525
  });
1566
- const results = await Promise.all(
1567
- stepsToRun.map(async (step, index) => {
1568
- const currStepResult = stepResults[step.step.id];
1569
- if (currStepResult && currStepResult.status === "success") {
1570
- return currStepResult;
1526
+ }
1527
+ getFunctions() {
1528
+ return [
1529
+ this.getFunction(),
1530
+ ...this.cronConfig?.cron ? [this.createCronFunction()] : [],
1531
+ ...this.getNestedFunctions(this.executionGraph.steps)
1532
+ ];
1533
+ }
1534
+ };
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();
1571
1543
  }
1572
- const result = await this.executeStep({
1573
- step: step.step,
1574
- prevOutput,
1575
- stepResults,
1576
- resume,
1577
- executionContext: {
1578
- workflowId,
1579
- runId,
1580
- executionPath: [...executionContext.executionPath, index],
1581
- suspendedPaths: executionContext.suspendedPaths,
1582
- resumeLabels: executionContext.resumeLabels,
1583
- retryConfig: executionContext.retryConfig,
1584
- state: executionContext.state
1544
+ return [];
1545
+ })
1546
+ )
1547
+ );
1548
+ return {
1549
+ ...registerOptions,
1550
+ client: inngest,
1551
+ functions: [...workflowFunctions, ...userFunctions]
1552
+ };
1553
+ }
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);
1577
+ }
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
+ }
1585
+ if (isAgent(params)) {
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({
1622
+ prompt: zod.z.string()
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);
1585
1662
  },
1586
- emitter,
1587
- abortController,
1588
- runtimeContext,
1589
- writableStream,
1590
- disableScorers,
1591
- tracingContext: {
1592
- currentSpan: conditionalSpan
1593
- }
1663
+ abortSignal
1594
1664
  });
1595
- stepResults[step.step.id] = result;
1596
- return result;
1597
- })
1598
- );
1599
- const hasFailed = results.find((result) => result.status === "failed");
1600
- const hasSuspended = results.find((result) => result.status === "suspended");
1601
- if (hasFailed) {
1602
- execResults = { status: "failed", error: hasFailed.error };
1603
- } else if (hasSuspended) {
1604
- execResults = { status: "suspended", suspendPayload: hasSuspended.suspendPayload };
1605
- } else {
1606
- execResults = {
1607
- status: "success",
1608
- output: results.reduce((acc, result, index) => {
1609
- if (result.status === "success") {
1610
- acc[stepsToRun[index].step.id] = result.output;
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;
1675
+ }
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
+ });
1611
1696
  }
1612
- return acc;
1613
- }, {})
1697
+ }
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);
1706
+ }
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");
1725
+ }
1726
+ return {
1727
+ id: params.id,
1728
+ description: params.description,
1729
+ inputSchema: params.inputSchema,
1730
+ outputSchema: params.outputSchema,
1731
+ resumeSchema: params.resumeSchema,
1732
+ suspendSchema: params.suspendSchema,
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
+ }
1614
1759
  };
1760
+ return params.execute(inputData, toolContext);
1761
+ },
1762
+ component: "TOOL"
1763
+ };
1764
+ }
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";
1615
1795
  }
1616
- if (execResults.status === "failed") {
1617
- conditionalSpan?.error({
1618
- error: new Error(execResults.error)
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
+ };
1813
+ return {
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);
1845
+ };
1846
+ if (!hasPhaseMethod(phase)) {
1847
+ return input;
1848
+ }
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 },
1858
+ attributes: {
1859
+ processorExecutor: "workflow",
1860
+ // Read processorIndex from processor (set in combineProcessorsIntoWorkflow)
1861
+ processorIndex: processor.processorIndex
1862
+ }
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 });
1904
+ }
1905
+ throw error;
1906
+ }
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
+ });
1919
+ }
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 ?? []
1927
+ });
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
+ });
1936
+ }
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
+ };
1966
+ }
1967
+ return { ...passThrough, messages };
1968
+ }
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 };
2016
+ }
2017
+ return { ...passThrough, messages };
2018
+ }
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 });
2067
+ }
2068
+ delete mutableState[spanKey];
2069
+ throw error;
2070
+ }
2071
+ return { ...passThrough, state: mutableState, part: result };
2072
+ }
2073
+ return { ...passThrough, part };
2074
+ }
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
+ });
2084
+ }
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
+ };
2130
+ }
2131
+ return { ...passThrough, messages };
2132
+ }
2133
+ return { ...passThrough, messages };
2134
+ }
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 };
2198
+ }
2199
+ return { ...passThrough, messages };
2200
+ }
2201
+ default:
2202
+ return { ...passThrough, messages };
2203
+ }
1619
2204
  });
1620
- } else {
1621
- conditionalSpan?.end({
1622
- 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
1623
2241
  });
2242
+ wf.setStepFlow(workflow.stepGraph);
2243
+ wf.commit();
2244
+ return wf;
1624
2245
  }
1625
- return execResults;
1626
- }
1627
- };
2246
+ };
2247
+ }
1628
2248
 
1629
2249
  exports.InngestExecutionEngine = InngestExecutionEngine;
2250
+ exports.InngestPubSub = InngestPubSub;
1630
2251
  exports.InngestRun = InngestRun;
1631
2252
  exports.InngestWorkflow = InngestWorkflow;
2253
+ exports._compatibilityCheck = _compatibilityCheck;
2254
+ exports.createServe = createServe;
1632
2255
  exports.createStep = createStep;
1633
2256
  exports.init = init;
1634
2257
  exports.serve = serve;