@mastra/inngest 0.0.0-remove-unused-model-providers-api-20251030210744 → 0.0.0-remove-ai-peer-dep-from-evals-20260105220639

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