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