@mastra/inngest 0.0.0-safe-stringify-telemetry-20251205024938 → 0.0.0-salesman-20260127182805

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