@cchevli/inngest 1.8.1-walton.0
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/CHANGELOG.md +3981 -0
- package/dist/__tests__/adapters/_utils.d.ts +18 -0
- package/dist/__tests__/adapters/_utils.d.ts.map +1 -0
- package/dist/__tests__/durable-agent.test.utils.d.ts +43 -0
- package/dist/__tests__/durable-agent.test.utils.d.ts.map +1 -0
- package/dist/chunk-MICZJLEE.cjs +1977 -0
- package/dist/chunk-MICZJLEE.cjs.map +1 -0
- package/dist/chunk-SE7QPBOX.js +1970 -0
- package/dist/chunk-SE7QPBOX.js.map +1 -0
- package/dist/connect.cjs +12 -0
- package/dist/connect.cjs.map +1 -0
- package/dist/connect.d.ts +43 -0
- package/dist/connect.d.ts.map +1 -0
- package/dist/connect.js +3 -0
- package/dist/connect.js.map +1 -0
- package/dist/durable-agent/create-inngest-agent.d.ts +425 -0
- package/dist/durable-agent/create-inngest-agent.d.ts.map +1 -0
- package/dist/durable-agent/create-inngest-agentic-workflow.d.ts +56 -0
- package/dist/durable-agent/create-inngest-agentic-workflow.d.ts.map +1 -0
- package/dist/durable-agent/index.d.ts +48 -0
- package/dist/durable-agent/index.d.ts.map +1 -0
- package/dist/execution-engine.d.ts +209 -0
- package/dist/execution-engine.d.ts.map +1 -0
- package/dist/functions.d.ts +7 -0
- package/dist/functions.d.ts.map +1 -0
- package/dist/index.cjs +1576 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +117 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1548 -0
- package/dist/index.js.map +1 -0
- package/dist/nested-workflow-output.d.ts +21 -0
- package/dist/nested-workflow-output.d.ts.map +1 -0
- package/dist/pubsub.d.ts +60 -0
- package/dist/pubsub.d.ts.map +1 -0
- package/dist/run.d.ts +234 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/serve.d.ts +77 -0
- package/dist/serve.d.ts.map +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/workflow.d.ts +58 -0
- package/dist/workflow.d.ts.map +1 -0
- package/package.json +98 -0
|
@@ -0,0 +1,1977 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var crypto$1 = require('crypto');
|
|
4
|
+
var durable = require('@mastra/core/agent/durable');
|
|
5
|
+
var di = require('@mastra/core/di');
|
|
6
|
+
var observability = require('@mastra/core/observability');
|
|
7
|
+
var workflows = require('@mastra/core/workflows');
|
|
8
|
+
var inngest = require('inngest');
|
|
9
|
+
var error = require('@mastra/core/error');
|
|
10
|
+
var events = require('@mastra/core/events');
|
|
11
|
+
var realtime = require('inngest/realtime');
|
|
12
|
+
var web = require('stream/web');
|
|
13
|
+
var stream = require('@mastra/core/stream');
|
|
14
|
+
|
|
15
|
+
// src/workflow.ts
|
|
16
|
+
|
|
17
|
+
// src/nested-workflow-output.ts
|
|
18
|
+
var NESTED_WORKFLOW_OUTPUT_MODE = {
|
|
19
|
+
DEFAULT: "default",
|
|
20
|
+
COMPACT: "compact"
|
|
21
|
+
};
|
|
22
|
+
function resolveNestedWorkflowOutputMode(mode = NESTED_WORKFLOW_OUTPUT_MODE.DEFAULT) {
|
|
23
|
+
return mode === NESTED_WORKFLOW_OUTPUT_MODE.COMPACT ? NESTED_WORKFLOW_OUTPUT_MODE.COMPACT : NESTED_WORKFLOW_OUTPUT_MODE.DEFAULT;
|
|
24
|
+
}
|
|
25
|
+
function compactNestedWorkflowResult(result) {
|
|
26
|
+
switch (result.status) {
|
|
27
|
+
case "success":
|
|
28
|
+
return { status: result.status, result: result.result, state: result.state };
|
|
29
|
+
case "failed":
|
|
30
|
+
return { status: result.status, error: result.error, state: result.state };
|
|
31
|
+
case "tripwire":
|
|
32
|
+
return { status: result.status, tripwire: result.tripwire, state: result.state };
|
|
33
|
+
case "suspended":
|
|
34
|
+
return { status: result.status, steps: result.steps, state: result.state };
|
|
35
|
+
case "paused":
|
|
36
|
+
return { status: result.status, state: result.state };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// src/execution-engine.ts
|
|
41
|
+
var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
|
|
42
|
+
inngestStep;
|
|
43
|
+
inngestAttempts;
|
|
44
|
+
constructor(mastra, inngestStep, inngestAttempts = 0, options) {
|
|
45
|
+
super({ mastra, options });
|
|
46
|
+
this.inngestStep = inngestStep;
|
|
47
|
+
this.inngestAttempts = inngestAttempts;
|
|
48
|
+
}
|
|
49
|
+
// =============================================================================
|
|
50
|
+
// Hook Overrides
|
|
51
|
+
// =============================================================================
|
|
52
|
+
/**
|
|
53
|
+
* Format errors while preserving Error instances and their custom properties.
|
|
54
|
+
* Uses getErrorFromUnknown to ensure all error properties are preserved.
|
|
55
|
+
*/
|
|
56
|
+
formatResultError(error$1, lastOutput) {
|
|
57
|
+
const outputError = lastOutput?.error;
|
|
58
|
+
const errorSource = error$1 || outputError;
|
|
59
|
+
const errorInstance = error.getErrorFromUnknown(errorSource, {
|
|
60
|
+
serializeStack: true,
|
|
61
|
+
// Include stack in JSON for better debugging in Inngest
|
|
62
|
+
fallbackMessage: "Unknown workflow error"
|
|
63
|
+
});
|
|
64
|
+
return errorInstance.toJSON();
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Detect InngestWorkflow instances for special nested workflow handling
|
|
68
|
+
*/
|
|
69
|
+
isNestedWorkflowStep(step) {
|
|
70
|
+
return step instanceof InngestWorkflow;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Inngest requires requestContext serialization for memoization.
|
|
74
|
+
* When steps are replayed, the original function doesn't re-execute,
|
|
75
|
+
* so requestContext modifications must be captured and restored.
|
|
76
|
+
*/
|
|
77
|
+
requiresDurableContextSerialization() {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Execute a step with retry logic for Inngest.
|
|
82
|
+
* Retries are handled via step-level retry (RetryAfterError thrown INSIDE step.run()).
|
|
83
|
+
* After retries exhausted, error propagates here and we return a failed result.
|
|
84
|
+
*/
|
|
85
|
+
async executeStepWithRetry(stepId, runStep, params) {
|
|
86
|
+
for (let i = 0; i < params.retries + 1; i++) {
|
|
87
|
+
if (i > 0 && params.delay) {
|
|
88
|
+
await new Promise((resolve) => setTimeout(resolve, params.delay));
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const result = await this.wrapDurableOperation(stepId, runStep);
|
|
92
|
+
return { ok: true, result };
|
|
93
|
+
} catch (e) {
|
|
94
|
+
if (i === params.retries) {
|
|
95
|
+
const cause = e?.cause;
|
|
96
|
+
if (cause?.status === "failed") {
|
|
97
|
+
params.stepSpan?.error({
|
|
98
|
+
error: e,
|
|
99
|
+
attributes: { status: "failed" }
|
|
100
|
+
});
|
|
101
|
+
if (cause.error && !(cause.error instanceof Error)) {
|
|
102
|
+
cause.error = error.getErrorFromUnknown(cause.error, { serializeStack: false });
|
|
103
|
+
}
|
|
104
|
+
return { ok: false, error: cause };
|
|
105
|
+
}
|
|
106
|
+
const errorInstance = error.getErrorFromUnknown(e, {
|
|
107
|
+
serializeStack: false,
|
|
108
|
+
fallbackMessage: "Unknown step execution error"
|
|
109
|
+
});
|
|
110
|
+
params.stepSpan?.error({
|
|
111
|
+
error: errorInstance,
|
|
112
|
+
attributes: { status: "failed" }
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
error: {
|
|
117
|
+
status: "failed",
|
|
118
|
+
error: errorInstance,
|
|
119
|
+
endedAt: Date.now()
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { ok: false, error: { status: "failed", error: new Error("Unknown error"), endedAt: Date.now() } };
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Use Inngest's sleep primitive for durability
|
|
129
|
+
*/
|
|
130
|
+
async executeSleepDuration(duration, sleepId, workflowId) {
|
|
131
|
+
await this.inngestStep.sleep(`workflow.${workflowId}.sleep.${sleepId}`, duration < 0 ? 0 : duration);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Use Inngest's sleepUntil primitive for durability
|
|
135
|
+
*/
|
|
136
|
+
async executeSleepUntilDate(date, sleepUntilId, workflowId) {
|
|
137
|
+
await this.inngestStep.sleepUntil(`workflow.${workflowId}.sleepUntil.${sleepUntilId}`, date);
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Wrap durable operations in Inngest step.run() for durability.
|
|
141
|
+
*
|
|
142
|
+
* IMPORTANT: Errors are wrapped with a cause structure before throwing.
|
|
143
|
+
* This is necessary because Inngest's error serialization (serialize-error-cjs)
|
|
144
|
+
* only captures standard Error properties (message, name, stack, code, cause).
|
|
145
|
+
* Custom properties like statusCode, responseHeaders from AI SDK errors would
|
|
146
|
+
* be lost. By putting our serialized error (via getErrorFromUnknown with toJSON())
|
|
147
|
+
* in the cause property, we ensure custom properties survive serialization.
|
|
148
|
+
* The cause property is in serialize-error-cjs's allowlist, and when the cause
|
|
149
|
+
* object is finally JSON.stringify'd, our error's toJSON() is called.
|
|
150
|
+
*/
|
|
151
|
+
async wrapDurableOperation(operationId, operationFn) {
|
|
152
|
+
const result = await this.inngestStep.run(operationId, async () => {
|
|
153
|
+
try {
|
|
154
|
+
const fnResult = await operationFn();
|
|
155
|
+
return fnResult;
|
|
156
|
+
} catch (e) {
|
|
157
|
+
const errorInstance = error.getErrorFromUnknown(e, {
|
|
158
|
+
serializeStack: false,
|
|
159
|
+
fallbackMessage: "Unknown step execution error"
|
|
160
|
+
});
|
|
161
|
+
throw new Error(errorInstance.message, {
|
|
162
|
+
cause: {
|
|
163
|
+
status: "failed",
|
|
164
|
+
error: errorInstance,
|
|
165
|
+
endedAt: Date.now()
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Provide Inngest step primitive in engine context
|
|
174
|
+
*/
|
|
175
|
+
getEngineContext() {
|
|
176
|
+
return { step: this.inngestStep };
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* For Inngest, lifecycle callbacks are invoked in the workflow's finalize step
|
|
180
|
+
* (wrapped in step.run for durability), not in execute(). Override to skip.
|
|
181
|
+
*/
|
|
182
|
+
async invokeLifecycleCallbacks(_result) {
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Actually invoke the lifecycle callbacks. Called from workflow.ts finalize step.
|
|
186
|
+
*/
|
|
187
|
+
async invokeLifecycleCallbacksInternal(result) {
|
|
188
|
+
return super.invokeLifecycleCallbacks(result);
|
|
189
|
+
}
|
|
190
|
+
// =============================================================================
|
|
191
|
+
// Durable Span Lifecycle Hooks
|
|
192
|
+
// =============================================================================
|
|
193
|
+
/**
|
|
194
|
+
* Create a step span durably - on first execution, creates and exports span.
|
|
195
|
+
* On replay, returns cached span data without re-creating.
|
|
196
|
+
*/
|
|
197
|
+
async createStepSpan(params) {
|
|
198
|
+
const { executionContext, operationId, options, parentSpan } = params;
|
|
199
|
+
const parentSpanId = parentSpan?.id ?? executionContext.tracingIds?.workflowSpanId;
|
|
200
|
+
const exportedSpan = await this.wrapDurableOperation(operationId, async () => {
|
|
201
|
+
const observability = this.mastra?.observability?.getSelectedInstance({});
|
|
202
|
+
if (!observability) return void 0;
|
|
203
|
+
const span = observability.startSpan({
|
|
204
|
+
...options,
|
|
205
|
+
entityType: options.entityType,
|
|
206
|
+
traceId: executionContext.tracingIds?.traceId,
|
|
207
|
+
parentSpanId
|
|
208
|
+
});
|
|
209
|
+
return span?.exportSpan();
|
|
210
|
+
});
|
|
211
|
+
if (exportedSpan) {
|
|
212
|
+
const observability = this.mastra?.observability?.getSelectedInstance({});
|
|
213
|
+
return observability?.rebuildSpan(exportedSpan);
|
|
214
|
+
}
|
|
215
|
+
return void 0;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* End a step span durably.
|
|
219
|
+
*/
|
|
220
|
+
async endStepSpan(params) {
|
|
221
|
+
const { span, operationId, endOptions } = params;
|
|
222
|
+
if (!span) return;
|
|
223
|
+
await this.wrapDurableOperation(operationId, async () => {
|
|
224
|
+
span.end(endOptions);
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Record error on step span durably.
|
|
229
|
+
*/
|
|
230
|
+
async errorStepSpan(params) {
|
|
231
|
+
const { span, operationId, errorOptions } = params;
|
|
232
|
+
if (!span) return;
|
|
233
|
+
await this.wrapDurableOperation(operationId, async () => {
|
|
234
|
+
span.error(errorOptions);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Create a generic child span durably (for control-flow operations).
|
|
239
|
+
* On first execution, creates and exports span. On replay, returns cached span data.
|
|
240
|
+
*/
|
|
241
|
+
async createChildSpan(params) {
|
|
242
|
+
const { executionContext, operationId, options, parentSpan } = params;
|
|
243
|
+
const parentSpanId = parentSpan?.id ?? executionContext.tracingIds?.workflowSpanId;
|
|
244
|
+
const exportedSpan = await this.wrapDurableOperation(operationId, async () => {
|
|
245
|
+
const observability = this.mastra?.observability?.getSelectedInstance({});
|
|
246
|
+
if (!observability) return void 0;
|
|
247
|
+
const span = observability.startSpan({
|
|
248
|
+
...options,
|
|
249
|
+
traceId: executionContext.tracingIds?.traceId,
|
|
250
|
+
parentSpanId,
|
|
251
|
+
tracingPolicy: this.options?.tracingPolicy
|
|
252
|
+
});
|
|
253
|
+
return span?.exportSpan();
|
|
254
|
+
});
|
|
255
|
+
if (exportedSpan) {
|
|
256
|
+
const observability = this.mastra?.observability?.getSelectedInstance({});
|
|
257
|
+
return observability?.rebuildSpan(exportedSpan);
|
|
258
|
+
}
|
|
259
|
+
return void 0;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* End a generic child span durably (for control-flow operations).
|
|
263
|
+
*/
|
|
264
|
+
async endChildSpan(params) {
|
|
265
|
+
const { span, operationId, endOptions } = params;
|
|
266
|
+
if (!span) return;
|
|
267
|
+
await this.wrapDurableOperation(operationId, async () => {
|
|
268
|
+
span.end(endOptions);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Record error on a generic child span durably (for control-flow operations).
|
|
273
|
+
*/
|
|
274
|
+
async errorChildSpan(params) {
|
|
275
|
+
const { span, operationId, errorOptions } = params;
|
|
276
|
+
if (!span) return;
|
|
277
|
+
await this.wrapDurableOperation(operationId, async () => {
|
|
278
|
+
span.error(errorOptions);
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Execute nested InngestWorkflow using inngestStep.invoke() for durability.
|
|
283
|
+
* This MUST be called directly (not inside step.run()) due to Inngest constraints.
|
|
284
|
+
*/
|
|
285
|
+
async executeWorkflowStep(params) {
|
|
286
|
+
if (!(params.step instanceof InngestWorkflow)) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
const {
|
|
290
|
+
step,
|
|
291
|
+
stepResults,
|
|
292
|
+
executionContext,
|
|
293
|
+
resume,
|
|
294
|
+
timeTravel,
|
|
295
|
+
prevOutput,
|
|
296
|
+
inputData,
|
|
297
|
+
pubsub,
|
|
298
|
+
startedAt,
|
|
299
|
+
perStep,
|
|
300
|
+
stepSpan,
|
|
301
|
+
actor,
|
|
302
|
+
requestContext: parentRequestContext
|
|
303
|
+
} = params;
|
|
304
|
+
const forwardedRequestContext = parentRequestContext ? this.serializeRequestContext(parentRequestContext) : inputData?.requestContextEntries ?? {};
|
|
305
|
+
const nestedTracingContext = executionContext.tracingIds?.traceId ? {
|
|
306
|
+
traceId: executionContext.tracingIds.traceId,
|
|
307
|
+
parentSpanId: stepSpan?.id
|
|
308
|
+
} : void 0;
|
|
309
|
+
const isResume = !!resume?.steps?.length;
|
|
310
|
+
let result;
|
|
311
|
+
let runId;
|
|
312
|
+
const isTimeTravel = !!(timeTravel && timeTravel.steps?.length > 1 && timeTravel.steps[0] === step.id);
|
|
313
|
+
try {
|
|
314
|
+
if (isResume) {
|
|
315
|
+
runId = stepResults[resume?.steps?.[0] ?? ""]?.suspendPayload?.__workflow_meta?.runId ?? crypto$1.randomUUID();
|
|
316
|
+
const workflowsStore = await this.mastra?.getStorage()?.getStore("workflows");
|
|
317
|
+
const snapshot = await workflowsStore?.loadWorkflowSnapshot({
|
|
318
|
+
workflowName: step.id,
|
|
319
|
+
runId
|
|
320
|
+
});
|
|
321
|
+
const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
|
|
322
|
+
function: step.getFunction(),
|
|
323
|
+
data: {
|
|
324
|
+
inputData,
|
|
325
|
+
initialState: executionContext.state ?? snapshot?.value ?? {},
|
|
326
|
+
requestContext: forwardedRequestContext,
|
|
327
|
+
runId,
|
|
328
|
+
resume: {
|
|
329
|
+
runId,
|
|
330
|
+
steps: resume.steps.slice(1),
|
|
331
|
+
stepResults: snapshot?.context,
|
|
332
|
+
resumePayload: resume.resumePayload,
|
|
333
|
+
resumePath: resume.steps?.[1] ? snapshot?.suspendedPaths?.[resume.steps?.[1]] : void 0
|
|
334
|
+
},
|
|
335
|
+
outputOptions: { includeState: true },
|
|
336
|
+
nestedWorkflowOutputMode: NESTED_WORKFLOW_OUTPUT_MODE.COMPACT,
|
|
337
|
+
perStep,
|
|
338
|
+
tracingOptions: nestedTracingContext,
|
|
339
|
+
actor
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
result = invokeResp.result;
|
|
343
|
+
runId = invokeResp.runId;
|
|
344
|
+
executionContext.state = invokeResp.result.state;
|
|
345
|
+
} else if (isTimeTravel) {
|
|
346
|
+
const workflowsStoreForTimeTravel = await this.mastra?.getStorage()?.getStore("workflows");
|
|
347
|
+
const snapshot = await workflowsStoreForTimeTravel?.loadWorkflowSnapshot({
|
|
348
|
+
workflowName: step.id,
|
|
349
|
+
runId: executionContext.runId
|
|
350
|
+
}) ?? { context: {} };
|
|
351
|
+
const timeTravelParams = workflows.createTimeTravelExecutionParams({
|
|
352
|
+
steps: timeTravel.steps.slice(1),
|
|
353
|
+
inputData: timeTravel.inputData,
|
|
354
|
+
resumeData: timeTravel.resumeData,
|
|
355
|
+
context: timeTravel.nestedStepResults?.[step.id] ?? {},
|
|
356
|
+
nestedStepsContext: timeTravel.nestedStepResults ?? {},
|
|
357
|
+
snapshot,
|
|
358
|
+
graph: step.buildExecutionGraph()
|
|
359
|
+
});
|
|
360
|
+
const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
|
|
361
|
+
function: step.getFunction(),
|
|
362
|
+
data: {
|
|
363
|
+
timeTravel: timeTravelParams,
|
|
364
|
+
initialState: executionContext.state ?? {},
|
|
365
|
+
requestContext: forwardedRequestContext,
|
|
366
|
+
runId: executionContext.runId,
|
|
367
|
+
outputOptions: { includeState: true },
|
|
368
|
+
nestedWorkflowOutputMode: NESTED_WORKFLOW_OUTPUT_MODE.COMPACT,
|
|
369
|
+
perStep,
|
|
370
|
+
tracingOptions: nestedTracingContext,
|
|
371
|
+
actor
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
result = invokeResp.result;
|
|
375
|
+
runId = invokeResp.runId;
|
|
376
|
+
executionContext.state = invokeResp.result.state;
|
|
377
|
+
} else {
|
|
378
|
+
const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
|
|
379
|
+
function: step.getFunction(),
|
|
380
|
+
data: {
|
|
381
|
+
inputData,
|
|
382
|
+
initialState: executionContext.state ?? {},
|
|
383
|
+
requestContext: forwardedRequestContext,
|
|
384
|
+
outputOptions: { includeState: true },
|
|
385
|
+
nestedWorkflowOutputMode: NESTED_WORKFLOW_OUTPUT_MODE.COMPACT,
|
|
386
|
+
perStep,
|
|
387
|
+
tracingOptions: nestedTracingContext,
|
|
388
|
+
actor
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
result = invokeResp.result;
|
|
392
|
+
runId = invokeResp.runId;
|
|
393
|
+
executionContext.state = invokeResp.result.state;
|
|
394
|
+
}
|
|
395
|
+
} catch (e) {
|
|
396
|
+
const errorCause = e && typeof e === "object" && "cause" in e ? e.cause : void 0;
|
|
397
|
+
if (errorCause && typeof errorCause === "object" && "status" in errorCause && errorCause.status === "failed") {
|
|
398
|
+
result = errorCause;
|
|
399
|
+
runId = "runId" in errorCause && typeof errorCause.runId === "string" ? errorCause.runId : crypto$1.randomUUID();
|
|
400
|
+
} else {
|
|
401
|
+
runId = crypto$1.randomUUID();
|
|
402
|
+
result = {
|
|
403
|
+
status: "failed",
|
|
404
|
+
error: e instanceof Error ? e : new Error(String(e))
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
const res = await this.inngestStep.run(
|
|
409
|
+
`workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
|
|
410
|
+
async () => {
|
|
411
|
+
if (result.status === "failed") {
|
|
412
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
413
|
+
type: "watch",
|
|
414
|
+
runId: executionContext.runId,
|
|
415
|
+
data: {
|
|
416
|
+
type: "workflow-step-result",
|
|
417
|
+
payload: {
|
|
418
|
+
id: step.id,
|
|
419
|
+
status: "failed",
|
|
420
|
+
error: result?.error,
|
|
421
|
+
payload: prevOutput
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
return { executionContext, result: { status: "failed", error: result?.error, endedAt: Date.now() } };
|
|
426
|
+
} else if (result.status === "suspended") {
|
|
427
|
+
const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
|
|
428
|
+
const stepRes = stepResult;
|
|
429
|
+
return stepRes?.status === "suspended";
|
|
430
|
+
});
|
|
431
|
+
for (const [stepName, stepResult] of suspendedSteps) {
|
|
432
|
+
const suspendPath = [stepName, ...stepResult?.suspendPayload?.__workflow_meta?.path ?? []];
|
|
433
|
+
executionContext.suspendedPaths[step.id] = executionContext.executionPath;
|
|
434
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
435
|
+
type: "watch",
|
|
436
|
+
runId: executionContext.runId,
|
|
437
|
+
data: {
|
|
438
|
+
type: "workflow-step-suspended",
|
|
439
|
+
payload: {
|
|
440
|
+
id: step.id,
|
|
441
|
+
status: "suspended"
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
return {
|
|
446
|
+
executionContext,
|
|
447
|
+
result: {
|
|
448
|
+
status: "suspended",
|
|
449
|
+
suspendedAt: Date.now(),
|
|
450
|
+
payload: stepResult.payload,
|
|
451
|
+
suspendPayload: {
|
|
452
|
+
...stepResult?.suspendPayload,
|
|
453
|
+
__workflow_meta: { runId, path: suspendPath }
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
return {
|
|
459
|
+
executionContext,
|
|
460
|
+
result: {
|
|
461
|
+
status: "suspended",
|
|
462
|
+
suspendedAt: Date.now(),
|
|
463
|
+
payload: {}
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
} else if (result.status === "tripwire") {
|
|
467
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
468
|
+
type: "watch",
|
|
469
|
+
runId: executionContext.runId,
|
|
470
|
+
data: {
|
|
471
|
+
type: "workflow-step-result",
|
|
472
|
+
payload: {
|
|
473
|
+
id: step.id,
|
|
474
|
+
status: "tripwire",
|
|
475
|
+
error: result?.tripwire?.reason,
|
|
476
|
+
payload: prevOutput
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
return {
|
|
481
|
+
executionContext,
|
|
482
|
+
result: {
|
|
483
|
+
status: "tripwire",
|
|
484
|
+
tripwire: result?.tripwire,
|
|
485
|
+
endedAt: Date.now()
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
} else if (perStep || result.status === "paused") {
|
|
489
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
490
|
+
type: "watch",
|
|
491
|
+
runId: executionContext.runId,
|
|
492
|
+
data: {
|
|
493
|
+
type: "workflow-step-result",
|
|
494
|
+
payload: {
|
|
495
|
+
id: step.id,
|
|
496
|
+
status: "paused"
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
501
|
+
type: "watch",
|
|
502
|
+
runId: executionContext.runId,
|
|
503
|
+
data: {
|
|
504
|
+
type: "workflow-step-finish",
|
|
505
|
+
payload: {
|
|
506
|
+
id: step.id,
|
|
507
|
+
metadata: {}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
});
|
|
511
|
+
return { executionContext, result: { status: "paused" } };
|
|
512
|
+
}
|
|
513
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
514
|
+
type: "watch",
|
|
515
|
+
runId: executionContext.runId,
|
|
516
|
+
data: {
|
|
517
|
+
type: "workflow-step-result",
|
|
518
|
+
payload: {
|
|
519
|
+
id: step.id,
|
|
520
|
+
status: "success",
|
|
521
|
+
output: result?.result
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
526
|
+
type: "watch",
|
|
527
|
+
runId: executionContext.runId,
|
|
528
|
+
data: {
|
|
529
|
+
type: "workflow-step-finish",
|
|
530
|
+
payload: {
|
|
531
|
+
id: step.id,
|
|
532
|
+
metadata: {}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
return { executionContext, result: { status: "success", output: result?.result, endedAt: Date.now() } };
|
|
537
|
+
}
|
|
538
|
+
);
|
|
539
|
+
Object.assign(executionContext, res.executionContext);
|
|
540
|
+
return {
|
|
541
|
+
...res.result,
|
|
542
|
+
startedAt,
|
|
543
|
+
payload: inputData,
|
|
544
|
+
resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
|
|
545
|
+
resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
function buildTopicRef(channel, topic) {
|
|
550
|
+
return { channel, topic, config: {} };
|
|
551
|
+
}
|
|
552
|
+
function parseTopic(topic) {
|
|
553
|
+
const workflowMatch = topic.match(/^workflow\.events\.v2\.(.+)$/);
|
|
554
|
+
if (workflowMatch && workflowMatch[1]) {
|
|
555
|
+
return { runId: workflowMatch[1], topicType: "workflow" };
|
|
556
|
+
}
|
|
557
|
+
const agentMatch = topic.match(/^agent\.stream\.(.+)$/);
|
|
558
|
+
if (agentMatch && agentMatch[1]) {
|
|
559
|
+
return { runId: agentMatch[1], topicType: "agent" };
|
|
560
|
+
}
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
var InngestPubSub = class extends events.PubSub {
|
|
564
|
+
inngest;
|
|
565
|
+
workflowId;
|
|
566
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
567
|
+
constructor(inngest, workflowId) {
|
|
568
|
+
super();
|
|
569
|
+
this.inngest = inngest;
|
|
570
|
+
this.workflowId = workflowId;
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Publish an event to Inngest's realtime system.
|
|
574
|
+
*
|
|
575
|
+
* Supported topic formats:
|
|
576
|
+
* - "workflow.events.v2.{runId}" - workflow events
|
|
577
|
+
* -> channel: "workflow:{workflowId}:{runId}", topic: "watch"
|
|
578
|
+
* - "agent.stream.{runId}" - agent stream events
|
|
579
|
+
* -> channel: "agent:{runId}", topic: "agent-stream"
|
|
580
|
+
* (Note: agent stream uses runId-only channel so nested workflows can publish to same channel)
|
|
581
|
+
*/
|
|
582
|
+
async publish(topic, event) {
|
|
583
|
+
const parsed = parseTopic(topic);
|
|
584
|
+
if (!parsed) {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
const { runId, topicType } = parsed;
|
|
588
|
+
const inngestTopic = topicType === "agent" ? "agent-stream" : "watch";
|
|
589
|
+
const channel = topicType === "agent" ? `agent:${runId}` : `workflow:${this.workflowId}:${runId}`;
|
|
590
|
+
try {
|
|
591
|
+
const dataToSend = topicType === "agent" ? event : event.data;
|
|
592
|
+
await this.inngest.realtime.publish(buildTopicRef(channel, inngestTopic), dataToSend);
|
|
593
|
+
} catch (err) {
|
|
594
|
+
if (topicType === "agent" && (event.type === "finish" || event.type === "error")) {
|
|
595
|
+
throw err;
|
|
596
|
+
}
|
|
597
|
+
console.error("InngestPubSub publish error:", err?.message ?? err);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Subscribe to events from Inngest's realtime system.
|
|
602
|
+
*
|
|
603
|
+
* Supported topic formats:
|
|
604
|
+
* - "workflow.events.v2.{runId}" - workflow events
|
|
605
|
+
* -> channel: "workflow:{workflowId}:{runId}", topic: "watch"
|
|
606
|
+
* - "agent.stream.{runId}" - agent stream events
|
|
607
|
+
* -> channel: "agent:{runId}", topic: "agent-stream"
|
|
608
|
+
* (Note: agent stream uses runId-only channel so nested workflows can publish to same channel)
|
|
609
|
+
*/
|
|
610
|
+
async subscribe(topic, cb) {
|
|
611
|
+
const parsed = parseTopic(topic);
|
|
612
|
+
if (!parsed) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
const { runId, topicType } = parsed;
|
|
616
|
+
if (this.subscriptions.has(topic)) {
|
|
617
|
+
this.subscriptions.get(topic).callbacks.add(cb);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
const callbacks = /* @__PURE__ */ new Set([cb]);
|
|
621
|
+
const inngestTopic = topicType === "agent" ? "agent-stream" : "watch";
|
|
622
|
+
const channel = topicType === "agent" ? `agent:${runId}` : `workflow:${this.workflowId}:${runId}`;
|
|
623
|
+
const stream = await realtime.subscribe(
|
|
624
|
+
{
|
|
625
|
+
channel,
|
|
626
|
+
topics: [inngestTopic],
|
|
627
|
+
app: this.inngest
|
|
628
|
+
},
|
|
629
|
+
(message) => {
|
|
630
|
+
let event;
|
|
631
|
+
if (topicType === "agent" && message.data?.type && message.data?.runId) {
|
|
632
|
+
event = {
|
|
633
|
+
id: crypto.randomUUID(),
|
|
634
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
635
|
+
...message.data
|
|
636
|
+
};
|
|
637
|
+
} else {
|
|
638
|
+
event = {
|
|
639
|
+
id: crypto.randomUUID(),
|
|
640
|
+
type: inngestTopic,
|
|
641
|
+
runId,
|
|
642
|
+
data: message.data,
|
|
643
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
for (const callback of callbacks) {
|
|
647
|
+
callback(event);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
);
|
|
651
|
+
this.subscriptions.set(topic, {
|
|
652
|
+
unsubscribe: () => {
|
|
653
|
+
try {
|
|
654
|
+
void stream.cancel();
|
|
655
|
+
} catch (err) {
|
|
656
|
+
console.error("InngestPubSub unsubscribe error:", err);
|
|
657
|
+
}
|
|
658
|
+
},
|
|
659
|
+
callbacks
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Unsubscribe a callback from a topic.
|
|
664
|
+
* If no callbacks remain, the underlying Inngest subscription is cancelled.
|
|
665
|
+
*/
|
|
666
|
+
async unsubscribe(topic, cb) {
|
|
667
|
+
const sub = this.subscriptions.get(topic);
|
|
668
|
+
if (!sub) {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
sub.callbacks.delete(cb);
|
|
672
|
+
if (sub.callbacks.size === 0) {
|
|
673
|
+
sub.unsubscribe();
|
|
674
|
+
this.subscriptions.delete(topic);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
/**
|
|
678
|
+
* Flush any pending operations. No-op for Inngest.
|
|
679
|
+
*/
|
|
680
|
+
async flush() {
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* Clean up all subscriptions during graceful shutdown.
|
|
684
|
+
*/
|
|
685
|
+
async close() {
|
|
686
|
+
for (const [, sub] of this.subscriptions) {
|
|
687
|
+
sub.unsubscribe();
|
|
688
|
+
}
|
|
689
|
+
this.subscriptions.clear();
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
var InngestRun = class extends workflows.Run {
|
|
693
|
+
inngest;
|
|
694
|
+
serializedStepGraph;
|
|
695
|
+
#mastra;
|
|
696
|
+
constructor(params, inngest) {
|
|
697
|
+
super(params);
|
|
698
|
+
this.inngest = inngest;
|
|
699
|
+
this.serializedStepGraph = params.serializedStepGraph;
|
|
700
|
+
this.#mastra = params.mastra;
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Get run output using hybrid approach: realtime subscription + polling fallback.
|
|
704
|
+
* Resolves as soon as either method detects completion.
|
|
705
|
+
*/
|
|
706
|
+
async getRunOutput(_eventId, maxWaitMs = 3e5) {
|
|
707
|
+
const storage = this.#mastra?.getStorage();
|
|
708
|
+
const workflowsStore = await storage?.getStore("workflows");
|
|
709
|
+
if (!workflowsStore) {
|
|
710
|
+
throw new inngest.NonRetriableError(`Workflow storage is required to retrieve output for run ${this.runId}`);
|
|
711
|
+
}
|
|
712
|
+
return new Promise((resolve, reject) => {
|
|
713
|
+
let resolved = false;
|
|
714
|
+
let unsubscribe = null;
|
|
715
|
+
let pollTimeoutId = null;
|
|
716
|
+
const cleanup = () => {
|
|
717
|
+
if (unsubscribe) {
|
|
718
|
+
try {
|
|
719
|
+
unsubscribe();
|
|
720
|
+
} catch {
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
if (pollTimeoutId) {
|
|
724
|
+
clearTimeout(pollTimeoutId);
|
|
725
|
+
}
|
|
726
|
+
};
|
|
727
|
+
const handleResult = (result, _source) => {
|
|
728
|
+
if (!resolved) {
|
|
729
|
+
resolved = true;
|
|
730
|
+
cleanup();
|
|
731
|
+
resolve(result);
|
|
732
|
+
}
|
|
733
|
+
};
|
|
734
|
+
const handleError = (error, _source) => {
|
|
735
|
+
if (!resolved) {
|
|
736
|
+
resolved = true;
|
|
737
|
+
cleanup();
|
|
738
|
+
reject(error);
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
let realtimeStreamPromise = null;
|
|
742
|
+
const startRealtimeSubscription = async () => {
|
|
743
|
+
try {
|
|
744
|
+
realtimeStreamPromise = realtime.subscribe(
|
|
745
|
+
{
|
|
746
|
+
channel: `workflow:${this.workflowId}:${this.runId}`,
|
|
747
|
+
topics: ["watch"],
|
|
748
|
+
app: this.inngest
|
|
749
|
+
},
|
|
750
|
+
async (message) => {
|
|
751
|
+
if (resolved) return;
|
|
752
|
+
const event = message.data;
|
|
753
|
+
if (event?.type === "workflow-finish") {
|
|
754
|
+
const snapshot = await workflowsStore?.loadWorkflowSnapshot({
|
|
755
|
+
workflowName: this.workflowId,
|
|
756
|
+
runId: this.runId
|
|
757
|
+
});
|
|
758
|
+
if (snapshot?.context) {
|
|
759
|
+
snapshot.context = workflows.hydrateSerializedStepErrors(snapshot.context);
|
|
760
|
+
}
|
|
761
|
+
const realtimeResult = {
|
|
762
|
+
steps: snapshot?.context,
|
|
763
|
+
status: event.payload?.status ?? snapshot?.status,
|
|
764
|
+
input: snapshot?.context?.input
|
|
765
|
+
};
|
|
766
|
+
const resultValue = event.payload?.result ?? snapshot?.result;
|
|
767
|
+
if (resultValue !== void 0) realtimeResult.result = resultValue;
|
|
768
|
+
const rawError = event.payload?.error ?? snapshot?.error;
|
|
769
|
+
if (rawError) {
|
|
770
|
+
realtimeResult.error = error.getErrorFromUnknown(rawError, { serializeStack: false });
|
|
771
|
+
}
|
|
772
|
+
if (snapshot?.value !== void 0) realtimeResult.state = snapshot.value;
|
|
773
|
+
const result = { output: { result: realtimeResult } };
|
|
774
|
+
handleResult(result);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
);
|
|
778
|
+
unsubscribe = () => {
|
|
779
|
+
realtimeStreamPromise?.then((stream) => stream.cancel().catch(() => {
|
|
780
|
+
})).catch(() => {
|
|
781
|
+
});
|
|
782
|
+
};
|
|
783
|
+
await realtimeStreamPromise;
|
|
784
|
+
} catch {
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
const startPolling = async () => {
|
|
788
|
+
const startTime = Date.now();
|
|
789
|
+
const poll = async () => {
|
|
790
|
+
if (resolved) {
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
if (Date.now() - startTime >= maxWaitMs) {
|
|
794
|
+
handleError(new inngest.NonRetriableError(`Workflow did not complete within ${maxWaitMs}ms`));
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
try {
|
|
798
|
+
const snapshot = await workflowsStore.loadWorkflowSnapshot({
|
|
799
|
+
workflowName: this.workflowId,
|
|
800
|
+
runId: this.runId
|
|
801
|
+
});
|
|
802
|
+
if (!snapshot || snapshot.status === "running" || snapshot.status === "waiting" || snapshot.status === "pending") {
|
|
803
|
+
pollTimeoutId = setTimeout(poll, 150 + Math.random() * 100);
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
if (snapshot.context) {
|
|
807
|
+
snapshot.context = workflows.hydrateSerializedStepErrors(snapshot.context);
|
|
808
|
+
}
|
|
809
|
+
const pollingResult = {
|
|
810
|
+
steps: snapshot.context,
|
|
811
|
+
status: snapshot.status,
|
|
812
|
+
input: snapshot.context?.input
|
|
813
|
+
};
|
|
814
|
+
if (snapshot.result !== void 0) pollingResult.result = snapshot.result;
|
|
815
|
+
if (snapshot.error !== void 0) {
|
|
816
|
+
pollingResult.error = error.getErrorFromUnknown(snapshot.error, { serializeStack: false });
|
|
817
|
+
}
|
|
818
|
+
if (snapshot.value !== void 0) pollingResult.state = snapshot.value;
|
|
819
|
+
handleResult({ output: { result: pollingResult } });
|
|
820
|
+
} catch (error) {
|
|
821
|
+
if (error instanceof inngest.NonRetriableError) {
|
|
822
|
+
handleError(error);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
handleError(
|
|
826
|
+
new inngest.NonRetriableError(
|
|
827
|
+
`Failed to poll workflow status: ${error instanceof Error ? error.message : String(error)}`
|
|
828
|
+
));
|
|
829
|
+
}
|
|
830
|
+
};
|
|
831
|
+
void poll();
|
|
832
|
+
};
|
|
833
|
+
void startRealtimeSubscription();
|
|
834
|
+
void startPolling();
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
async cancel() {
|
|
838
|
+
const storage = this.#mastra?.getStorage();
|
|
839
|
+
await this.inngest.send({
|
|
840
|
+
name: `cancel.workflow.${this.workflowId}`,
|
|
841
|
+
data: {
|
|
842
|
+
runId: this.runId
|
|
843
|
+
}
|
|
844
|
+
});
|
|
845
|
+
const workflowsStore = await storage?.getStore("workflows");
|
|
846
|
+
const snapshot = await workflowsStore?.loadWorkflowSnapshot({
|
|
847
|
+
workflowName: this.workflowId,
|
|
848
|
+
runId: this.runId
|
|
849
|
+
});
|
|
850
|
+
if (snapshot) {
|
|
851
|
+
await workflowsStore?.persistWorkflowSnapshot({
|
|
852
|
+
workflowName: this.workflowId,
|
|
853
|
+
runId: this.runId,
|
|
854
|
+
resourceId: this.resourceId,
|
|
855
|
+
snapshot: {
|
|
856
|
+
...snapshot,
|
|
857
|
+
status: "canceled",
|
|
858
|
+
value: snapshot.value
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
async start(args) {
|
|
864
|
+
return this._start(args);
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Starts the workflow execution without waiting for completion (fire-and-forget).
|
|
868
|
+
* Returns immediately with the runId after sending the event to Inngest.
|
|
869
|
+
* The workflow executes independently in Inngest.
|
|
870
|
+
* Use this when you don't need to wait for the result or want to avoid polling failures.
|
|
871
|
+
*/
|
|
872
|
+
async startAsync(args) {
|
|
873
|
+
const workflowsStore = await this.#mastra.getStorage()?.getStore("workflows");
|
|
874
|
+
await workflowsStore?.persistWorkflowSnapshot({
|
|
875
|
+
workflowName: this.workflowId,
|
|
876
|
+
runId: this.runId,
|
|
877
|
+
resourceId: this.resourceId,
|
|
878
|
+
snapshot: {
|
|
879
|
+
runId: this.runId,
|
|
880
|
+
serializedStepGraph: this.serializedStepGraph,
|
|
881
|
+
status: "running",
|
|
882
|
+
value: {},
|
|
883
|
+
context: {},
|
|
884
|
+
activePaths: [],
|
|
885
|
+
suspendedPaths: {},
|
|
886
|
+
activeStepsPath: {},
|
|
887
|
+
resumeLabels: {},
|
|
888
|
+
waitingPaths: {},
|
|
889
|
+
timestamp: Date.now()
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
const inputDataToUse = await this._validateInput(args.inputData);
|
|
893
|
+
const initialStateToUse = await this._validateInitialState(args.initialState ?? {});
|
|
894
|
+
const eventOutput = await this.inngest.send({
|
|
895
|
+
name: `workflow.${this.workflowId}`,
|
|
896
|
+
data: {
|
|
897
|
+
inputData: inputDataToUse,
|
|
898
|
+
initialState: initialStateToUse,
|
|
899
|
+
runId: this.runId,
|
|
900
|
+
resourceId: this.resourceId,
|
|
901
|
+
outputOptions: args.outputOptions,
|
|
902
|
+
tracingOptions: args.tracingOptions,
|
|
903
|
+
requestContext: args.requestContext ? Object.fromEntries(args.requestContext.entries()) : {},
|
|
904
|
+
actor: args.actor,
|
|
905
|
+
perStep: args.perStep
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
const eventId = eventOutput.ids[0];
|
|
909
|
+
if (!eventId) {
|
|
910
|
+
throw new Error("Event ID is not set");
|
|
911
|
+
}
|
|
912
|
+
return { runId: this.runId };
|
|
913
|
+
}
|
|
914
|
+
async _start({
|
|
915
|
+
inputData,
|
|
916
|
+
initialState,
|
|
917
|
+
outputOptions,
|
|
918
|
+
tracingOptions,
|
|
919
|
+
format,
|
|
920
|
+
requestContext,
|
|
921
|
+
actor,
|
|
922
|
+
perStep
|
|
923
|
+
}) {
|
|
924
|
+
const workflowsStore = await this.#mastra.getStorage()?.getStore("workflows");
|
|
925
|
+
await workflowsStore?.persistWorkflowSnapshot({
|
|
926
|
+
workflowName: this.workflowId,
|
|
927
|
+
runId: this.runId,
|
|
928
|
+
resourceId: this.resourceId,
|
|
929
|
+
snapshot: {
|
|
930
|
+
runId: this.runId,
|
|
931
|
+
serializedStepGraph: this.serializedStepGraph,
|
|
932
|
+
status: "running",
|
|
933
|
+
value: {},
|
|
934
|
+
context: {},
|
|
935
|
+
activePaths: [],
|
|
936
|
+
suspendedPaths: {},
|
|
937
|
+
activeStepsPath: {},
|
|
938
|
+
resumeLabels: {},
|
|
939
|
+
waitingPaths: {},
|
|
940
|
+
timestamp: Date.now()
|
|
941
|
+
}
|
|
942
|
+
});
|
|
943
|
+
const inputDataToUse = await this._validateInput(inputData);
|
|
944
|
+
const initialStateToUse = await this._validateInitialState(initialState ?? {});
|
|
945
|
+
const eventName = `workflow.${this.workflowId}`;
|
|
946
|
+
const eventOutput = await this.inngest.send({
|
|
947
|
+
name: eventName,
|
|
948
|
+
data: {
|
|
949
|
+
inputData: inputDataToUse,
|
|
950
|
+
initialState: initialStateToUse,
|
|
951
|
+
runId: this.runId,
|
|
952
|
+
resourceId: this.resourceId,
|
|
953
|
+
outputOptions,
|
|
954
|
+
tracingOptions,
|
|
955
|
+
format,
|
|
956
|
+
requestContext: requestContext ? Object.fromEntries(requestContext.entries()) : {},
|
|
957
|
+
actor,
|
|
958
|
+
perStep
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
const eventId = eventOutput.ids[0];
|
|
962
|
+
if (!eventId) {
|
|
963
|
+
throw new Error("Event ID is not set");
|
|
964
|
+
}
|
|
965
|
+
const runOutput = await this.getRunOutput(eventId);
|
|
966
|
+
const result = runOutput?.output?.result;
|
|
967
|
+
this.hydrateFailedResult(result);
|
|
968
|
+
if (!outputOptions?.includeState) {
|
|
969
|
+
delete result.state;
|
|
970
|
+
}
|
|
971
|
+
if (result.status !== "suspended") {
|
|
972
|
+
this.cleanup?.();
|
|
973
|
+
}
|
|
974
|
+
return result;
|
|
975
|
+
}
|
|
976
|
+
async resume(params) {
|
|
977
|
+
const p = this._resume(params).then((result) => {
|
|
978
|
+
if (result.status !== "suspended") {
|
|
979
|
+
this.closeStreamAction?.().catch(() => {
|
|
980
|
+
});
|
|
981
|
+
}
|
|
982
|
+
return result;
|
|
983
|
+
});
|
|
984
|
+
this.executionResults = p;
|
|
985
|
+
return p;
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* Performs all resume preparation and dispatches the resume event to Inngest,
|
|
989
|
+
* but does NOT wait for the workflow result. Shared by `_resume()` (which polls
|
|
990
|
+
* for the result afterwards) and `resumeAsync()` (which returns immediately).
|
|
991
|
+
*
|
|
992
|
+
* Send-time failures (invalid resume data, event send failure) reject synchronously,
|
|
993
|
+
* and the snapshot is rolled back to its prior state on send failure.
|
|
994
|
+
*/
|
|
995
|
+
async _resumeAndSendEvent(params) {
|
|
996
|
+
const storage = this.#mastra?.getStorage();
|
|
997
|
+
const workflowsStore = await storage?.getStore("workflows");
|
|
998
|
+
if (!workflowsStore) {
|
|
999
|
+
throw new inngest.NonRetriableError(`Workflow storage is required to resume run ${this.runId}`);
|
|
1000
|
+
}
|
|
1001
|
+
const snapshot = await workflowsStore.loadWorkflowSnapshot({
|
|
1002
|
+
workflowName: this.workflowId,
|
|
1003
|
+
runId: this.runId
|
|
1004
|
+
});
|
|
1005
|
+
if (!snapshot) {
|
|
1006
|
+
throw new inngest.NonRetriableError(`Cannot resume run ${this.runId}: snapshot not found`);
|
|
1007
|
+
}
|
|
1008
|
+
const snapshotResumeLabel = params.label ? snapshot.resumeLabels?.[params.label] : void 0;
|
|
1009
|
+
const stepParam = snapshotResumeLabel?.stepId ?? params.step;
|
|
1010
|
+
let steps = [];
|
|
1011
|
+
if (stepParam) {
|
|
1012
|
+
if (typeof stepParam === "string") {
|
|
1013
|
+
steps = stepParam.split(".");
|
|
1014
|
+
} else {
|
|
1015
|
+
steps = (Array.isArray(stepParam) ? stepParam : [stepParam]).map(
|
|
1016
|
+
(step) => typeof step === "string" ? step : step?.id
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
const suspendedStep = this.workflowSteps[steps?.[0] ?? ""];
|
|
1021
|
+
const resumeDataToUse = await this._validateResumeData(params.resumeData, suspendedStep);
|
|
1022
|
+
const persistedRequestContext = snapshot?.requestContext ?? {};
|
|
1023
|
+
const newRequestContext = params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {};
|
|
1024
|
+
const mergedRequestContext = { ...persistedRequestContext, ...newRequestContext };
|
|
1025
|
+
await workflowsStore.persistWorkflowSnapshot({
|
|
1026
|
+
workflowName: this.workflowId,
|
|
1027
|
+
runId: this.runId,
|
|
1028
|
+
resourceId: this.resourceId,
|
|
1029
|
+
snapshot: {
|
|
1030
|
+
...snapshot,
|
|
1031
|
+
status: "running",
|
|
1032
|
+
result: void 0,
|
|
1033
|
+
error: void 0,
|
|
1034
|
+
timestamp: Date.now()
|
|
1035
|
+
}
|
|
1036
|
+
});
|
|
1037
|
+
let eventOutput;
|
|
1038
|
+
try {
|
|
1039
|
+
eventOutput = await this.inngest.send({
|
|
1040
|
+
name: `workflow.${this.workflowId}`,
|
|
1041
|
+
data: {
|
|
1042
|
+
inputData: resumeDataToUse,
|
|
1043
|
+
initialState: snapshot?.value ?? {},
|
|
1044
|
+
runId: this.runId,
|
|
1045
|
+
workflowId: this.workflowId,
|
|
1046
|
+
stepResults: snapshot?.context,
|
|
1047
|
+
resume: {
|
|
1048
|
+
steps,
|
|
1049
|
+
stepResults: snapshot?.context,
|
|
1050
|
+
resumePayload: resumeDataToUse,
|
|
1051
|
+
resumePath: steps?.[0] ? snapshot?.suspendedPaths?.[steps?.[0]] : void 0
|
|
1052
|
+
},
|
|
1053
|
+
requestContext: mergedRequestContext,
|
|
1054
|
+
// `actor` is a per-call trust signal, not rehydrated from the snapshot like
|
|
1055
|
+
// `requestContext` is above. This intentionally matches the default engine,
|
|
1056
|
+
// which passes `actor: params.actor` on resume and never reads it from the
|
|
1057
|
+
// snapshot (see packages/core/src/workflows/workflow.ts `_resume`). The caller
|
|
1058
|
+
// (a trusted background system) re-supplies `actor` on each resume; we never
|
|
1059
|
+
// persist a membership-bypass signal into durable storage.
|
|
1060
|
+
actor: params.actor,
|
|
1061
|
+
perStep: params.perStep
|
|
1062
|
+
}
|
|
1063
|
+
});
|
|
1064
|
+
} catch (err) {
|
|
1065
|
+
try {
|
|
1066
|
+
await workflowsStore.persistWorkflowSnapshot({
|
|
1067
|
+
workflowName: this.workflowId,
|
|
1068
|
+
runId: this.runId,
|
|
1069
|
+
resourceId: this.resourceId,
|
|
1070
|
+
snapshot
|
|
1071
|
+
});
|
|
1072
|
+
} catch (rollbackErr) {
|
|
1073
|
+
console.error("Failed to rollback snapshot during resume error recovery:", rollbackErr);
|
|
1074
|
+
}
|
|
1075
|
+
throw err;
|
|
1076
|
+
}
|
|
1077
|
+
const eventId = eventOutput.ids[0];
|
|
1078
|
+
if (!eventId) {
|
|
1079
|
+
throw new Error("Event ID is not set");
|
|
1080
|
+
}
|
|
1081
|
+
return { eventId };
|
|
1082
|
+
}
|
|
1083
|
+
async _resume(params) {
|
|
1084
|
+
const { eventId } = await this._resumeAndSendEvent(params);
|
|
1085
|
+
const runOutput = await this.getRunOutput(eventId);
|
|
1086
|
+
const result = runOutput?.output?.result;
|
|
1087
|
+
this.hydrateFailedResult(result);
|
|
1088
|
+
return result;
|
|
1089
|
+
}
|
|
1090
|
+
/**
|
|
1091
|
+
* Resumes a suspended workflow without waiting for completion (fire-and-forget).
|
|
1092
|
+
* Returns immediately with the runId after sending the resume event to Inngest.
|
|
1093
|
+
* The workflow continues executing independently in Inngest.
|
|
1094
|
+
*
|
|
1095
|
+
* Mirrors `startAsync()`: send-time failures (invalid resume data, event send
|
|
1096
|
+
* failure) still reject synchronously and roll back the snapshot, but the result
|
|
1097
|
+
* is never polled via `getRunOutput()`. This avoids the polling-based 404 race when
|
|
1098
|
+
* you don't need the resolved result inline.
|
|
1099
|
+
*
|
|
1100
|
+
* NOTE: this is exposed over HTTP / the client SDK as `resume-no-wait` / `resumeNoWait()`,
|
|
1101
|
+
* not `resumeAsync`, because the existing `resumeAsync()` client/server surface awaits the
|
|
1102
|
+
* full workflow result. TODO(v2): consolidate so `resumeAsync` consistently means
|
|
1103
|
+
* fire-and-forget across core, client SDK and HTTP routes (breaking change deferred to v2).
|
|
1104
|
+
*/
|
|
1105
|
+
async resumeAsync(params) {
|
|
1106
|
+
await this._resumeAndSendEvent(params);
|
|
1107
|
+
return { runId: this.runId };
|
|
1108
|
+
}
|
|
1109
|
+
async timeTravel(params) {
|
|
1110
|
+
const p = this._timeTravel(params).then((result) => {
|
|
1111
|
+
if (result.status !== "suspended") {
|
|
1112
|
+
this.closeStreamAction?.().catch(() => {
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
return result;
|
|
1116
|
+
});
|
|
1117
|
+
this.executionResults = p;
|
|
1118
|
+
return p;
|
|
1119
|
+
}
|
|
1120
|
+
async _timeTravel(params) {
|
|
1121
|
+
if (!params.step || Array.isArray(params.step) && params.step?.length === 0) {
|
|
1122
|
+
throw new Error("Step is required and must be a valid step or array of steps");
|
|
1123
|
+
}
|
|
1124
|
+
let steps = [];
|
|
1125
|
+
if (typeof params.step === "string") {
|
|
1126
|
+
steps = params.step.split(".");
|
|
1127
|
+
} else {
|
|
1128
|
+
steps = (Array.isArray(params.step) ? params.step : [params.step]).map(
|
|
1129
|
+
(step) => typeof step === "string" ? step : step?.id
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
if (steps.length === 0) {
|
|
1133
|
+
throw new Error("No steps provided to timeTravel");
|
|
1134
|
+
}
|
|
1135
|
+
const storage = this.#mastra?.getStorage();
|
|
1136
|
+
const workflowsStore = await storage?.getStore("workflows");
|
|
1137
|
+
if (!workflowsStore) {
|
|
1138
|
+
throw new inngest.NonRetriableError(`Workflow storage is required to time-travel run ${this.runId}`);
|
|
1139
|
+
}
|
|
1140
|
+
const snapshot = await workflowsStore.loadWorkflowSnapshot({
|
|
1141
|
+
workflowName: this.workflowId,
|
|
1142
|
+
runId: this.runId
|
|
1143
|
+
});
|
|
1144
|
+
let snapshotForRollback = snapshot;
|
|
1145
|
+
if (!snapshot) {
|
|
1146
|
+
const pendingSnapshot = {
|
|
1147
|
+
runId: this.runId,
|
|
1148
|
+
serializedStepGraph: this.serializedStepGraph,
|
|
1149
|
+
status: "pending",
|
|
1150
|
+
value: {},
|
|
1151
|
+
context: {},
|
|
1152
|
+
activePaths: [],
|
|
1153
|
+
suspendedPaths: {},
|
|
1154
|
+
activeStepsPath: {},
|
|
1155
|
+
resumeLabels: {},
|
|
1156
|
+
waitingPaths: {},
|
|
1157
|
+
timestamp: Date.now()
|
|
1158
|
+
};
|
|
1159
|
+
await workflowsStore.persistWorkflowSnapshot({
|
|
1160
|
+
workflowName: this.workflowId,
|
|
1161
|
+
runId: this.runId,
|
|
1162
|
+
resourceId: this.resourceId,
|
|
1163
|
+
snapshot: pendingSnapshot
|
|
1164
|
+
});
|
|
1165
|
+
snapshotForRollback = pendingSnapshot;
|
|
1166
|
+
}
|
|
1167
|
+
if (snapshot?.status === "running") {
|
|
1168
|
+
throw new Error("This workflow run is still running, cannot time travel");
|
|
1169
|
+
}
|
|
1170
|
+
let inputDataToUse = params.inputData;
|
|
1171
|
+
if (inputDataToUse && steps.length === 1) {
|
|
1172
|
+
inputDataToUse = await this._validateTimetravelInputData(params.inputData, this.workflowSteps[steps[0]]);
|
|
1173
|
+
}
|
|
1174
|
+
const timeTravelData = workflows.createTimeTravelExecutionParams({
|
|
1175
|
+
steps,
|
|
1176
|
+
inputData: inputDataToUse,
|
|
1177
|
+
resumeData: params.resumeData,
|
|
1178
|
+
context: params.context,
|
|
1179
|
+
nestedStepsContext: params.nestedStepsContext,
|
|
1180
|
+
snapshot: snapshot ?? { context: {} },
|
|
1181
|
+
graph: this.executionGraph,
|
|
1182
|
+
initialState: params.initialState,
|
|
1183
|
+
perStep: params.perStep
|
|
1184
|
+
});
|
|
1185
|
+
const previousSnapshot = snapshotForRollback;
|
|
1186
|
+
await workflowsStore.persistWorkflowSnapshot({
|
|
1187
|
+
workflowName: this.workflowId,
|
|
1188
|
+
runId: this.runId,
|
|
1189
|
+
resourceId: this.resourceId,
|
|
1190
|
+
snapshot: {
|
|
1191
|
+
runId: this.runId,
|
|
1192
|
+
serializedStepGraph: this.serializedStepGraph,
|
|
1193
|
+
status: "running",
|
|
1194
|
+
value: {},
|
|
1195
|
+
context: {},
|
|
1196
|
+
activePaths: [],
|
|
1197
|
+
suspendedPaths: {},
|
|
1198
|
+
activeStepsPath: {},
|
|
1199
|
+
resumeLabels: {},
|
|
1200
|
+
waitingPaths: {},
|
|
1201
|
+
timestamp: Date.now()
|
|
1202
|
+
}
|
|
1203
|
+
});
|
|
1204
|
+
let eventOutput;
|
|
1205
|
+
try {
|
|
1206
|
+
eventOutput = await this.inngest.send({
|
|
1207
|
+
name: `workflow.${this.workflowId}`,
|
|
1208
|
+
data: {
|
|
1209
|
+
initialState: timeTravelData.state,
|
|
1210
|
+
runId: this.runId,
|
|
1211
|
+
workflowId: this.workflowId,
|
|
1212
|
+
stepResults: timeTravelData.stepResults,
|
|
1213
|
+
timeTravel: timeTravelData,
|
|
1214
|
+
tracingOptions: params.tracingOptions,
|
|
1215
|
+
outputOptions: params.outputOptions,
|
|
1216
|
+
requestContext: params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {},
|
|
1217
|
+
actor: params.actor,
|
|
1218
|
+
perStep: params.perStep
|
|
1219
|
+
}
|
|
1220
|
+
});
|
|
1221
|
+
} catch (err) {
|
|
1222
|
+
if (previousSnapshot) {
|
|
1223
|
+
try {
|
|
1224
|
+
await workflowsStore.persistWorkflowSnapshot({
|
|
1225
|
+
workflowName: this.workflowId,
|
|
1226
|
+
runId: this.runId,
|
|
1227
|
+
resourceId: this.resourceId,
|
|
1228
|
+
snapshot: previousSnapshot
|
|
1229
|
+
});
|
|
1230
|
+
} catch (rollbackErr) {
|
|
1231
|
+
console.error("Failed to rollback snapshot during time-travel error recovery:", rollbackErr);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
throw err;
|
|
1235
|
+
}
|
|
1236
|
+
const eventId = eventOutput.ids[0];
|
|
1237
|
+
if (!eventId) {
|
|
1238
|
+
throw new Error("Event ID is not set");
|
|
1239
|
+
}
|
|
1240
|
+
const runOutput = await this.getRunOutput(eventId);
|
|
1241
|
+
const result = runOutput?.output?.result;
|
|
1242
|
+
this.hydrateFailedResult(result);
|
|
1243
|
+
if (!params.outputOptions?.includeState) {
|
|
1244
|
+
delete result.state;
|
|
1245
|
+
}
|
|
1246
|
+
return result;
|
|
1247
|
+
}
|
|
1248
|
+
watch(cb) {
|
|
1249
|
+
let active = true;
|
|
1250
|
+
const streamPromise = realtime.subscribe(
|
|
1251
|
+
{
|
|
1252
|
+
channel: `workflow:${this.workflowId}:${this.runId}`,
|
|
1253
|
+
topics: ["watch"],
|
|
1254
|
+
app: this.inngest
|
|
1255
|
+
},
|
|
1256
|
+
(message) => {
|
|
1257
|
+
if (active) {
|
|
1258
|
+
cb(message.data);
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
);
|
|
1262
|
+
return () => {
|
|
1263
|
+
active = false;
|
|
1264
|
+
streamPromise.then(async (stream) => {
|
|
1265
|
+
return stream.cancel();
|
|
1266
|
+
}).catch((err) => {
|
|
1267
|
+
console.error(err);
|
|
1268
|
+
});
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
streamLegacy({
|
|
1272
|
+
inputData,
|
|
1273
|
+
requestContext,
|
|
1274
|
+
actor
|
|
1275
|
+
} = {}) {
|
|
1276
|
+
const { readable, writable } = new TransformStream();
|
|
1277
|
+
const writer = writable.getWriter();
|
|
1278
|
+
void writer.write({
|
|
1279
|
+
// @ts-expect-error - stream event type mismatch
|
|
1280
|
+
type: "start",
|
|
1281
|
+
payload: { runId: this.runId }
|
|
1282
|
+
});
|
|
1283
|
+
const unwatch = this.watch(async (event) => {
|
|
1284
|
+
try {
|
|
1285
|
+
const e = {
|
|
1286
|
+
...event,
|
|
1287
|
+
type: event.type.replace("workflow-", "")
|
|
1288
|
+
};
|
|
1289
|
+
if (e.type === "step-output") {
|
|
1290
|
+
e.type = e.payload.output.type;
|
|
1291
|
+
e.payload = e.payload.output.payload;
|
|
1292
|
+
}
|
|
1293
|
+
await writer.write(e);
|
|
1294
|
+
} catch {
|
|
1295
|
+
}
|
|
1296
|
+
});
|
|
1297
|
+
this.closeStreamAction = async () => {
|
|
1298
|
+
await writer.write({
|
|
1299
|
+
type: "finish",
|
|
1300
|
+
// @ts-expect-error - stream event type mismatch
|
|
1301
|
+
payload: { runId: this.runId }
|
|
1302
|
+
});
|
|
1303
|
+
unwatch();
|
|
1304
|
+
try {
|
|
1305
|
+
await writer.close();
|
|
1306
|
+
} catch (err) {
|
|
1307
|
+
console.error("Error closing stream:", err);
|
|
1308
|
+
} finally {
|
|
1309
|
+
writer.releaseLock();
|
|
1310
|
+
}
|
|
1311
|
+
};
|
|
1312
|
+
this.executionResults = this._start({ inputData, requestContext, actor, format: "legacy" }).then((result) => {
|
|
1313
|
+
if (result.status !== "suspended") {
|
|
1314
|
+
this.closeStreamAction?.().catch(() => {
|
|
1315
|
+
});
|
|
1316
|
+
}
|
|
1317
|
+
return result;
|
|
1318
|
+
});
|
|
1319
|
+
return {
|
|
1320
|
+
stream: readable,
|
|
1321
|
+
getWorkflowState: () => this.executionResults
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
stream({
|
|
1325
|
+
inputData,
|
|
1326
|
+
requestContext,
|
|
1327
|
+
actor,
|
|
1328
|
+
tracingOptions,
|
|
1329
|
+
closeOnSuspend = true,
|
|
1330
|
+
initialState,
|
|
1331
|
+
outputOptions,
|
|
1332
|
+
perStep
|
|
1333
|
+
} = {}) {
|
|
1334
|
+
if (this.closeStreamAction && this.streamOutput) {
|
|
1335
|
+
return this.streamOutput;
|
|
1336
|
+
}
|
|
1337
|
+
this.closeStreamAction = async () => {
|
|
1338
|
+
};
|
|
1339
|
+
const self = this;
|
|
1340
|
+
const stream$1 = new web.ReadableStream({
|
|
1341
|
+
async start(controller) {
|
|
1342
|
+
const unwatch = self.watch(async ({ type, from = stream.ChunkFrom.WORKFLOW, payload }) => {
|
|
1343
|
+
controller.enqueue({
|
|
1344
|
+
type,
|
|
1345
|
+
runId: self.runId,
|
|
1346
|
+
from,
|
|
1347
|
+
payload: {
|
|
1348
|
+
stepName: payload?.id,
|
|
1349
|
+
...payload
|
|
1350
|
+
}
|
|
1351
|
+
});
|
|
1352
|
+
});
|
|
1353
|
+
self.closeStreamAction = async () => {
|
|
1354
|
+
unwatch();
|
|
1355
|
+
try {
|
|
1356
|
+
await controller.close();
|
|
1357
|
+
} catch (err) {
|
|
1358
|
+
console.error("Error closing stream:", err);
|
|
1359
|
+
}
|
|
1360
|
+
};
|
|
1361
|
+
const executionResultsPromise = self._start({
|
|
1362
|
+
inputData,
|
|
1363
|
+
requestContext,
|
|
1364
|
+
actor,
|
|
1365
|
+
// tracingContext, // We are not able to pass a reference to a span here, what to do?
|
|
1366
|
+
initialState,
|
|
1367
|
+
tracingOptions,
|
|
1368
|
+
outputOptions,
|
|
1369
|
+
format: "vnext",
|
|
1370
|
+
perStep
|
|
1371
|
+
});
|
|
1372
|
+
let executionResults;
|
|
1373
|
+
try {
|
|
1374
|
+
executionResults = await executionResultsPromise;
|
|
1375
|
+
if (closeOnSuspend) {
|
|
1376
|
+
self.closeStreamAction?.().catch(() => {
|
|
1377
|
+
});
|
|
1378
|
+
} else if (executionResults.status !== "suspended") {
|
|
1379
|
+
self.closeStreamAction?.().catch(() => {
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
if (self.streamOutput) {
|
|
1383
|
+
self.streamOutput.updateResults(
|
|
1384
|
+
executionResults
|
|
1385
|
+
);
|
|
1386
|
+
}
|
|
1387
|
+
} catch (err) {
|
|
1388
|
+
self.streamOutput?.rejectResults(err);
|
|
1389
|
+
self.closeStreamAction?.().catch(() => {
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
});
|
|
1394
|
+
this.streamOutput = new stream.WorkflowRunOutput({
|
|
1395
|
+
runId: this.runId,
|
|
1396
|
+
workflowId: this.workflowId,
|
|
1397
|
+
stream: stream$1
|
|
1398
|
+
});
|
|
1399
|
+
return this.streamOutput;
|
|
1400
|
+
}
|
|
1401
|
+
timeTravelStream({
|
|
1402
|
+
inputData,
|
|
1403
|
+
resumeData,
|
|
1404
|
+
initialState,
|
|
1405
|
+
step,
|
|
1406
|
+
context,
|
|
1407
|
+
nestedStepsContext,
|
|
1408
|
+
requestContext,
|
|
1409
|
+
actor,
|
|
1410
|
+
// tracingContext,
|
|
1411
|
+
tracingOptions,
|
|
1412
|
+
outputOptions,
|
|
1413
|
+
perStep
|
|
1414
|
+
}) {
|
|
1415
|
+
this.closeStreamAction = async () => {
|
|
1416
|
+
};
|
|
1417
|
+
const self = this;
|
|
1418
|
+
const stream$1 = new web.ReadableStream({
|
|
1419
|
+
async start(controller) {
|
|
1420
|
+
const unwatch = self.watch(async ({ type, from = stream.ChunkFrom.WORKFLOW, payload }) => {
|
|
1421
|
+
controller.enqueue({
|
|
1422
|
+
type,
|
|
1423
|
+
runId: self.runId,
|
|
1424
|
+
from,
|
|
1425
|
+
payload: {
|
|
1426
|
+
stepName: payload?.id,
|
|
1427
|
+
...payload
|
|
1428
|
+
}
|
|
1429
|
+
});
|
|
1430
|
+
});
|
|
1431
|
+
self.closeStreamAction = async () => {
|
|
1432
|
+
unwatch();
|
|
1433
|
+
try {
|
|
1434
|
+
controller.close();
|
|
1435
|
+
} catch (err) {
|
|
1436
|
+
console.error("Error closing stream:", err);
|
|
1437
|
+
}
|
|
1438
|
+
};
|
|
1439
|
+
const executionResultsPromise = self._timeTravel({
|
|
1440
|
+
inputData,
|
|
1441
|
+
step,
|
|
1442
|
+
context,
|
|
1443
|
+
nestedStepsContext,
|
|
1444
|
+
resumeData,
|
|
1445
|
+
initialState,
|
|
1446
|
+
requestContext,
|
|
1447
|
+
actor,
|
|
1448
|
+
tracingOptions,
|
|
1449
|
+
outputOptions,
|
|
1450
|
+
perStep
|
|
1451
|
+
});
|
|
1452
|
+
self.executionResults = executionResultsPromise;
|
|
1453
|
+
let executionResults;
|
|
1454
|
+
try {
|
|
1455
|
+
executionResults = await executionResultsPromise;
|
|
1456
|
+
self.closeStreamAction?.().catch(() => {
|
|
1457
|
+
});
|
|
1458
|
+
if (self.streamOutput) {
|
|
1459
|
+
self.streamOutput.updateResults(executionResults);
|
|
1460
|
+
}
|
|
1461
|
+
} catch (err) {
|
|
1462
|
+
self.streamOutput?.rejectResults(err);
|
|
1463
|
+
self.closeStreamAction?.().catch(() => {
|
|
1464
|
+
});
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
});
|
|
1468
|
+
this.streamOutput = new stream.WorkflowRunOutput({
|
|
1469
|
+
runId: this.runId,
|
|
1470
|
+
workflowId: this.workflowId,
|
|
1471
|
+
stream: stream$1
|
|
1472
|
+
});
|
|
1473
|
+
return this.streamOutput;
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Hydrates errors in a failed workflow result back to proper Error instances.
|
|
1477
|
+
* This ensures error.cause chains and custom properties are preserved.
|
|
1478
|
+
*/
|
|
1479
|
+
hydrateFailedResult(result) {
|
|
1480
|
+
if (result.status === "failed") {
|
|
1481
|
+
result.error = error.getErrorFromUnknown(result.error, { serializeStack: false });
|
|
1482
|
+
if (result.steps) {
|
|
1483
|
+
workflows.hydrateSerializedStepErrors(result.steps);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
};
|
|
1488
|
+
|
|
1489
|
+
// src/workflow.ts
|
|
1490
|
+
var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
1491
|
+
#mastra;
|
|
1492
|
+
inngest;
|
|
1493
|
+
function;
|
|
1494
|
+
cronFunction;
|
|
1495
|
+
flowControlConfig;
|
|
1496
|
+
cronConfig;
|
|
1497
|
+
/**
|
|
1498
|
+
* Optional override that lets a host (e.g. `createInngestAgent`) provide the
|
|
1499
|
+
* PubSub instance used by workflow steps for publishing chunk/finish events.
|
|
1500
|
+
* When set, the workflow function uses this factory instead of constructing
|
|
1501
|
+
* a fresh `InngestPubSub`. This is what lets `DurableAgent.observe()` see
|
|
1502
|
+
* cached history when the agent wraps its PubSub in a `CachingPubSub`.
|
|
1503
|
+
*/
|
|
1504
|
+
#pubsubFactory;
|
|
1505
|
+
constructor(params, inngest) {
|
|
1506
|
+
const { concurrency, rateLimit, throttle, debounce, priority, cron, inputData, initialState, ...workflowParams } = params;
|
|
1507
|
+
super(workflowParams);
|
|
1508
|
+
this.engineType = "inngest";
|
|
1509
|
+
const flowControlEntries = Object.entries({ concurrency, rateLimit, throttle, debounce, priority }).filter(
|
|
1510
|
+
([_, value]) => value !== void 0
|
|
1511
|
+
);
|
|
1512
|
+
this.flowControlConfig = flowControlEntries.length > 0 ? Object.fromEntries(flowControlEntries) : void 0;
|
|
1513
|
+
this.#mastra = params.mastra;
|
|
1514
|
+
this.inngest = inngest;
|
|
1515
|
+
if (cron) {
|
|
1516
|
+
this.cronConfig = { cron, inputData, initialState };
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
async listWorkflowRuns(args) {
|
|
1520
|
+
const storage = this.#mastra?.getStorage();
|
|
1521
|
+
if (!storage) {
|
|
1522
|
+
this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
|
|
1523
|
+
return { runs: [], total: 0 };
|
|
1524
|
+
}
|
|
1525
|
+
const workflowsStore = await storage.getStore("workflows");
|
|
1526
|
+
if (!workflowsStore) {
|
|
1527
|
+
return { runs: [], total: 0 };
|
|
1528
|
+
}
|
|
1529
|
+
return workflowsStore.listWorkflowRuns({ workflowName: this.id, ...args ?? {} });
|
|
1530
|
+
}
|
|
1531
|
+
/**
|
|
1532
|
+
* Override the PubSub used inside the durable workflow function. Callers like
|
|
1533
|
+
* `createInngestAgent` use this to route workflow event publishes through the
|
|
1534
|
+
* agent's `CachingPubSub`, so `observe()` can replay cached history.
|
|
1535
|
+
*
|
|
1536
|
+
* The factory receives the workflow's own default `InngestPubSub` (constructed
|
|
1537
|
+
* with this workflow's id) as input. Hosts should wrap that instance rather
|
|
1538
|
+
* than substitute it, so workflow-event channels (which encode the workflow
|
|
1539
|
+
* id) remain workflow-local. Returning a `CachingPubSub` wrapping the default
|
|
1540
|
+
* is the canonical pattern.
|
|
1541
|
+
*
|
|
1542
|
+
* The factory is propagated to every nested `InngestWorkflow` in the step
|
|
1543
|
+
* graph. Nested workflows run as their own Inngest functions and resolve
|
|
1544
|
+
* their own pubsub at runtime; each invocation passes its own workflow-local
|
|
1545
|
+
* default into the same factory, so the host can share cross-workflow state
|
|
1546
|
+
* (e.g. a single agent-scoped cache) without collapsing per-workflow channel
|
|
1547
|
+
* isolation.
|
|
1548
|
+
*/
|
|
1549
|
+
__setPubsubFactory(factory) {
|
|
1550
|
+
this.#pubsubFactory = factory;
|
|
1551
|
+
const updateNested = (step) => {
|
|
1552
|
+
if ((step.type === "step" || step.type === "loop" || step.type === "foreach") && step.step instanceof _InngestWorkflow) {
|
|
1553
|
+
step.step.__setPubsubFactory(factory);
|
|
1554
|
+
} else if (step.type === "parallel" || step.type === "conditional") {
|
|
1555
|
+
for (const subStep of step.steps) {
|
|
1556
|
+
updateNested(subStep);
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
};
|
|
1560
|
+
for (const step of this.executionGraph.steps) {
|
|
1561
|
+
updateNested(step);
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Test-only accessor for the configured pubsub factory. Lets tests verify that
|
|
1566
|
+
* a host (e.g. `createInngestAgent`) wired the workflow to its agent pubsub
|
|
1567
|
+
* without having to drive a real Inngest invocation.
|
|
1568
|
+
*/
|
|
1569
|
+
__getPubsubFactory() {
|
|
1570
|
+
return this.#pubsubFactory;
|
|
1571
|
+
}
|
|
1572
|
+
__registerMastra(mastra) {
|
|
1573
|
+
super.__registerMastra(mastra);
|
|
1574
|
+
this.#mastra = mastra;
|
|
1575
|
+
this.executionEngine.__registerMastra(mastra);
|
|
1576
|
+
const updateNested = (step) => {
|
|
1577
|
+
if ((step.type === "step" || step.type === "loop" || step.type === "foreach") && step.step instanceof _InngestWorkflow) {
|
|
1578
|
+
step.step.__registerMastra(mastra);
|
|
1579
|
+
} else if (step.type === "parallel" || step.type === "conditional") {
|
|
1580
|
+
for (const subStep of step.steps) {
|
|
1581
|
+
updateNested(subStep);
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
};
|
|
1585
|
+
if (this.executionGraph.steps.length) {
|
|
1586
|
+
for (const step of this.executionGraph.steps) {
|
|
1587
|
+
updateNested(step);
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
async createRun(options) {
|
|
1592
|
+
const runIdToUse = options?.runId || crypto$1.randomUUID();
|
|
1593
|
+
const existingInMemoryRun = this.runs.get(runIdToUse);
|
|
1594
|
+
const newRun = new InngestRun(
|
|
1595
|
+
{
|
|
1596
|
+
workflowId: this.id,
|
|
1597
|
+
runId: runIdToUse,
|
|
1598
|
+
resourceId: options?.resourceId,
|
|
1599
|
+
executionEngine: this.executionEngine,
|
|
1600
|
+
executionGraph: this.executionGraph,
|
|
1601
|
+
serializedStepGraph: this.serializedStepGraph,
|
|
1602
|
+
mastra: this.#mastra,
|
|
1603
|
+
retryConfig: this.retryConfig,
|
|
1604
|
+
cleanup: () => this.runs.delete(runIdToUse),
|
|
1605
|
+
workflowSteps: this.steps,
|
|
1606
|
+
workflowEngineType: this.engineType,
|
|
1607
|
+
validateInputs: this.options.validateInputs
|
|
1608
|
+
},
|
|
1609
|
+
this.inngest
|
|
1610
|
+
);
|
|
1611
|
+
const run = existingInMemoryRun ?? newRun;
|
|
1612
|
+
this.runs.set(runIdToUse, run);
|
|
1613
|
+
const shouldPersistSnapshot = this.options.shouldPersistSnapshot({
|
|
1614
|
+
workflowStatus: run.workflowRunStatus,
|
|
1615
|
+
stepResults: {}
|
|
1616
|
+
});
|
|
1617
|
+
const existingStoredRun = await this.getWorkflowRunById(runIdToUse, {
|
|
1618
|
+
withNestedWorkflows: false
|
|
1619
|
+
});
|
|
1620
|
+
const existsInStorage = existingStoredRun && !existingStoredRun.isFromInMemory;
|
|
1621
|
+
if (!existsInStorage && shouldPersistSnapshot) {
|
|
1622
|
+
const workflowsStore = await this.mastra?.getStorage()?.getStore("workflows");
|
|
1623
|
+
await workflowsStore?.persistWorkflowSnapshot({
|
|
1624
|
+
workflowName: this.id,
|
|
1625
|
+
runId: runIdToUse,
|
|
1626
|
+
resourceId: options?.resourceId,
|
|
1627
|
+
snapshot: {
|
|
1628
|
+
runId: runIdToUse,
|
|
1629
|
+
status: "pending",
|
|
1630
|
+
value: {},
|
|
1631
|
+
context: {},
|
|
1632
|
+
activePaths: [],
|
|
1633
|
+
activeStepsPath: {},
|
|
1634
|
+
waitingPaths: {},
|
|
1635
|
+
serializedStepGraph: this.serializedStepGraph,
|
|
1636
|
+
suspendedPaths: {},
|
|
1637
|
+
resumeLabels: {},
|
|
1638
|
+
result: void 0,
|
|
1639
|
+
error: void 0,
|
|
1640
|
+
timestamp: Date.now()
|
|
1641
|
+
}
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
return run;
|
|
1645
|
+
}
|
|
1646
|
+
//createCronFunction is only called if cronConfig.cron is defined.
|
|
1647
|
+
createCronFunction() {
|
|
1648
|
+
if (this.cronFunction) {
|
|
1649
|
+
return this.cronFunction;
|
|
1650
|
+
}
|
|
1651
|
+
this.cronFunction = this.inngest.createFunction(
|
|
1652
|
+
{
|
|
1653
|
+
id: `workflow.${this.id}.cron`,
|
|
1654
|
+
retries: 0,
|
|
1655
|
+
cancelOn: [{ event: `cancel.workflow.${this.id}` }],
|
|
1656
|
+
triggers: { cron: this.cronConfig?.cron ?? "" },
|
|
1657
|
+
...this.flowControlConfig
|
|
1658
|
+
},
|
|
1659
|
+
async () => {
|
|
1660
|
+
const run = await this.createRun();
|
|
1661
|
+
const result = await run.start({
|
|
1662
|
+
inputData: this.cronConfig?.inputData,
|
|
1663
|
+
initialState: this.cronConfig?.initialState
|
|
1664
|
+
});
|
|
1665
|
+
return { result, runId: run.runId };
|
|
1666
|
+
}
|
|
1667
|
+
);
|
|
1668
|
+
return this.cronFunction;
|
|
1669
|
+
}
|
|
1670
|
+
getFunction() {
|
|
1671
|
+
if (this.function) {
|
|
1672
|
+
return this.function;
|
|
1673
|
+
}
|
|
1674
|
+
this.function = this.inngest.createFunction(
|
|
1675
|
+
{
|
|
1676
|
+
id: `workflow.${this.id}`,
|
|
1677
|
+
retries: 0,
|
|
1678
|
+
cancelOn: [{ event: `cancel.workflow.${this.id}` }],
|
|
1679
|
+
triggers: { event: `workflow.${this.id}` },
|
|
1680
|
+
// Spread flow control configuration
|
|
1681
|
+
...this.flowControlConfig
|
|
1682
|
+
},
|
|
1683
|
+
async ({ event, step, attempt }) => {
|
|
1684
|
+
let {
|
|
1685
|
+
inputData,
|
|
1686
|
+
initialState,
|
|
1687
|
+
runId,
|
|
1688
|
+
resourceId,
|
|
1689
|
+
resume,
|
|
1690
|
+
outputOptions,
|
|
1691
|
+
format,
|
|
1692
|
+
timeTravel,
|
|
1693
|
+
perStep,
|
|
1694
|
+
tracingOptions,
|
|
1695
|
+
actor,
|
|
1696
|
+
nestedWorkflowOutputMode: requestedNestedWorkflowOutputMode
|
|
1697
|
+
} = event.data;
|
|
1698
|
+
const nestedWorkflowOutputMode = resolveNestedWorkflowOutputMode(requestedNestedWorkflowOutputMode);
|
|
1699
|
+
const shouldCompactNestedWorkflowOutput = nestedWorkflowOutputMode === NESTED_WORKFLOW_OUTPUT_MODE.COMPACT;
|
|
1700
|
+
if (!runId) {
|
|
1701
|
+
runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
|
|
1702
|
+
return crypto$1.randomUUID();
|
|
1703
|
+
});
|
|
1704
|
+
}
|
|
1705
|
+
const defaultPubsub = new InngestPubSub(this.inngest, this.id);
|
|
1706
|
+
const pubsub = this.#pubsubFactory?.(defaultPubsub) ?? defaultPubsub;
|
|
1707
|
+
const requestContext = new di.RequestContext(Object.entries(event.data.requestContext ?? {}));
|
|
1708
|
+
const mastra = this.#mastra;
|
|
1709
|
+
const tracingPolicy = this.options.tracingPolicy;
|
|
1710
|
+
const workflowSpanData = await step.run(`workflow.${this.id}.span.start`, async () => {
|
|
1711
|
+
const observability$1 = mastra?.observability?.getSelectedInstance({ requestContext });
|
|
1712
|
+
if (!observability$1) return void 0;
|
|
1713
|
+
const span = observability$1.startSpan({
|
|
1714
|
+
type: observability.SpanType.WORKFLOW_RUN,
|
|
1715
|
+
name: `workflow run: '${this.id}'`,
|
|
1716
|
+
entityType: observability.EntityType.WORKFLOW_RUN,
|
|
1717
|
+
entityId: this.id,
|
|
1718
|
+
entityName: this.id,
|
|
1719
|
+
input: inputData,
|
|
1720
|
+
metadata: {
|
|
1721
|
+
resourceId,
|
|
1722
|
+
runId
|
|
1723
|
+
},
|
|
1724
|
+
tracingPolicy,
|
|
1725
|
+
tracingOptions,
|
|
1726
|
+
requestContext
|
|
1727
|
+
});
|
|
1728
|
+
return span?.exportSpan();
|
|
1729
|
+
});
|
|
1730
|
+
const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
|
|
1731
|
+
let result;
|
|
1732
|
+
try {
|
|
1733
|
+
result = await engine.execute({
|
|
1734
|
+
workflowId: this.id,
|
|
1735
|
+
runId,
|
|
1736
|
+
resourceId,
|
|
1737
|
+
graph: this.executionGraph,
|
|
1738
|
+
serializedStepGraph: this.serializedStepGraph,
|
|
1739
|
+
input: inputData,
|
|
1740
|
+
initialState,
|
|
1741
|
+
pubsub,
|
|
1742
|
+
retryConfig: this.retryConfig,
|
|
1743
|
+
requestContext,
|
|
1744
|
+
actor,
|
|
1745
|
+
resume,
|
|
1746
|
+
timeTravel,
|
|
1747
|
+
perStep,
|
|
1748
|
+
format,
|
|
1749
|
+
abortController: new AbortController(),
|
|
1750
|
+
// For Inngest, we don't pass workflowSpan - step spans use tracingIds instead
|
|
1751
|
+
workflowSpan: void 0,
|
|
1752
|
+
// Pass tracing IDs for durable span operations
|
|
1753
|
+
tracingIds: workflowSpanData ? {
|
|
1754
|
+
traceId: workflowSpanData.traceId,
|
|
1755
|
+
workflowSpanId: workflowSpanData.id
|
|
1756
|
+
} : void 0,
|
|
1757
|
+
outputOptions,
|
|
1758
|
+
outputWriter: async (chunk) => {
|
|
1759
|
+
try {
|
|
1760
|
+
await pubsub.publish(`workflow.events.v2.${runId}`, {
|
|
1761
|
+
type: "watch",
|
|
1762
|
+
runId,
|
|
1763
|
+
data: chunk
|
|
1764
|
+
});
|
|
1765
|
+
} catch (err) {
|
|
1766
|
+
this.logger.debug?.("Failed to publish watch event:", err);
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
});
|
|
1770
|
+
} catch (executionError) {
|
|
1771
|
+
result = {
|
|
1772
|
+
status: "failed",
|
|
1773
|
+
steps: {},
|
|
1774
|
+
state: initialState ?? {},
|
|
1775
|
+
error: executionError instanceof Error ? executionError : new Error(String(executionError))
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
const returnedResult = shouldCompactNestedWorkflowOutput ? compactNestedWorkflowResult(result) : result;
|
|
1779
|
+
let finalizeError;
|
|
1780
|
+
let finalizeErrored = false;
|
|
1781
|
+
try {
|
|
1782
|
+
await step.run(`workflow.${this.id}.finalize`, async () => {
|
|
1783
|
+
if (result.status === "failed" && inputData?.__workflowKind === "durable-agent" && inputData?.runId) {
|
|
1784
|
+
const error = result.error instanceof Error ? result.error : new Error(String(result.error));
|
|
1785
|
+
try {
|
|
1786
|
+
await durable.emitErrorEvent(pubsub, inputData.runId, error);
|
|
1787
|
+
} catch (e) {
|
|
1788
|
+
this.logger.debug?.("Failed to emit error event:", e);
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
if (result.status !== "paused") {
|
|
1792
|
+
await engine.invokeLifecycleCallbacksInternal({
|
|
1793
|
+
status: result.status,
|
|
1794
|
+
result: "result" in result ? result.result : void 0,
|
|
1795
|
+
error: "error" in result ? result.error : void 0,
|
|
1796
|
+
steps: result.steps,
|
|
1797
|
+
tripwire: "tripwire" in result ? result.tripwire : void 0,
|
|
1798
|
+
runId,
|
|
1799
|
+
workflowId: this.id,
|
|
1800
|
+
resourceId,
|
|
1801
|
+
input: inputData,
|
|
1802
|
+
requestContext,
|
|
1803
|
+
state: result.state ?? initialState ?? {}
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
if (workflowSpanData) {
|
|
1807
|
+
const observability = mastra?.observability?.getSelectedInstance({ requestContext });
|
|
1808
|
+
if (observability) {
|
|
1809
|
+
const workflowSpan = observability.rebuildSpan(workflowSpanData);
|
|
1810
|
+
if (result.status === "failed") {
|
|
1811
|
+
workflowSpan.error({
|
|
1812
|
+
error: result.error instanceof Error ? result.error : new Error(String(result.error)),
|
|
1813
|
+
attributes: { status: "failed" }
|
|
1814
|
+
});
|
|
1815
|
+
} else {
|
|
1816
|
+
workflowSpan.end({
|
|
1817
|
+
output: result.status === "success" ? result.result : void 0,
|
|
1818
|
+
attributes: { status: result.status }
|
|
1819
|
+
});
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
const shouldPersistFinalSnapshot = this.options.shouldPersistSnapshot({
|
|
1824
|
+
workflowStatus: result.status,
|
|
1825
|
+
stepResults: result.steps
|
|
1826
|
+
});
|
|
1827
|
+
if (shouldPersistFinalSnapshot) {
|
|
1828
|
+
const workflowsStore = await mastra?.getStorage()?.getStore("workflows");
|
|
1829
|
+
if (workflowsStore) {
|
|
1830
|
+
let existingSnapshot;
|
|
1831
|
+
if (result.status === "suspended") {
|
|
1832
|
+
existingSnapshot = await workflowsStore.loadWorkflowSnapshot({
|
|
1833
|
+
workflowName: this.id,
|
|
1834
|
+
runId
|
|
1835
|
+
}) ?? void 0;
|
|
1836
|
+
}
|
|
1837
|
+
await workflowsStore.persistWorkflowSnapshot({
|
|
1838
|
+
workflowName: this.id,
|
|
1839
|
+
runId,
|
|
1840
|
+
resourceId,
|
|
1841
|
+
snapshot: {
|
|
1842
|
+
runId,
|
|
1843
|
+
status: result.status,
|
|
1844
|
+
value: result.state ?? initialState ?? {},
|
|
1845
|
+
context: toSnapshotContext(result.steps),
|
|
1846
|
+
activePaths: [],
|
|
1847
|
+
activeStepsPath: {},
|
|
1848
|
+
serializedStepGraph: this.serializedStepGraph,
|
|
1849
|
+
suspendedPaths: existingSnapshot?.suspendedPaths ?? {},
|
|
1850
|
+
waitingPaths: {},
|
|
1851
|
+
resumeLabels: existingSnapshot?.resumeLabels ?? result.resumeLabels ?? {},
|
|
1852
|
+
result: result.status === "success" ? toSnapshotResult(result.result) : void 0,
|
|
1853
|
+
error: result.status === "failed" ? result.error : void 0,
|
|
1854
|
+
timestamp: Date.now()
|
|
1855
|
+
}
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
try {
|
|
1860
|
+
await pubsub.publish(`workflow.events.v2.${runId}`, {
|
|
1861
|
+
type: "watch",
|
|
1862
|
+
runId,
|
|
1863
|
+
data: {
|
|
1864
|
+
type: "workflow-finish",
|
|
1865
|
+
payload: {
|
|
1866
|
+
status: result.status,
|
|
1867
|
+
result: result.status === "success" ? result.result : void 0,
|
|
1868
|
+
error: result.status === "failed" ? result.error : void 0
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
});
|
|
1872
|
+
} catch (publishError) {
|
|
1873
|
+
this.logger.debug?.("Failed to publish workflow-finish event:", publishError);
|
|
1874
|
+
}
|
|
1875
|
+
if (result.status === "failed") {
|
|
1876
|
+
throw new inngest.NonRetriableError(`Workflow failed`, {
|
|
1877
|
+
cause: shouldCompactNestedWorkflowOutput ? { ...returnedResult, runId } : result
|
|
1878
|
+
});
|
|
1879
|
+
}
|
|
1880
|
+
return shouldCompactNestedWorkflowOutput ? { status: result.status } : result;
|
|
1881
|
+
});
|
|
1882
|
+
} catch (error) {
|
|
1883
|
+
finalizeErrored = true;
|
|
1884
|
+
finalizeError = error;
|
|
1885
|
+
} finally {
|
|
1886
|
+
const observability = mastra?.observability?.getSelectedInstance({ requestContext });
|
|
1887
|
+
if (observability) {
|
|
1888
|
+
try {
|
|
1889
|
+
await observability.flush();
|
|
1890
|
+
} catch (flushError) {
|
|
1891
|
+
this.logger.debug?.("Failed to flush observability:", flushError);
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
if (finalizeErrored) {
|
|
1896
|
+
throw finalizeError;
|
|
1897
|
+
}
|
|
1898
|
+
return { result: returnedResult, runId };
|
|
1899
|
+
}
|
|
1900
|
+
);
|
|
1901
|
+
return this.function;
|
|
1902
|
+
}
|
|
1903
|
+
getNestedFunctions(steps) {
|
|
1904
|
+
return steps.flatMap((step) => {
|
|
1905
|
+
if (step.type === "step" || step.type === "loop" || step.type === "foreach") {
|
|
1906
|
+
if (step.step instanceof _InngestWorkflow) {
|
|
1907
|
+
return [step.step.getFunction(), ...step.step.getNestedFunctions(step.step.executionGraph.steps)];
|
|
1908
|
+
}
|
|
1909
|
+
return [];
|
|
1910
|
+
} else if (step.type === "parallel" || step.type === "conditional") {
|
|
1911
|
+
return this.getNestedFunctions(step.steps);
|
|
1912
|
+
}
|
|
1913
|
+
return [];
|
|
1914
|
+
});
|
|
1915
|
+
}
|
|
1916
|
+
getFunctions() {
|
|
1917
|
+
return [
|
|
1918
|
+
this.getFunction(),
|
|
1919
|
+
...this.cronConfig?.cron ? [this.createCronFunction()] : [],
|
|
1920
|
+
...this.getNestedFunctions(this.executionGraph.steps)
|
|
1921
|
+
];
|
|
1922
|
+
}
|
|
1923
|
+
};
|
|
1924
|
+
function toSnapshotContext(steps) {
|
|
1925
|
+
return steps;
|
|
1926
|
+
}
|
|
1927
|
+
function toSnapshotResult(output) {
|
|
1928
|
+
return output;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
// src/functions.ts
|
|
1932
|
+
function collectInngestFunctions({
|
|
1933
|
+
mastra,
|
|
1934
|
+
functions: userFunctions = []
|
|
1935
|
+
}) {
|
|
1936
|
+
const workflows = mastra.listWorkflows();
|
|
1937
|
+
const workflowFunctions = Array.from(
|
|
1938
|
+
new Set(
|
|
1939
|
+
Object.values(workflows).flatMap((workflow) => {
|
|
1940
|
+
if (workflow instanceof InngestWorkflow) {
|
|
1941
|
+
workflow.__registerMastra(mastra);
|
|
1942
|
+
return workflow.getFunctions();
|
|
1943
|
+
}
|
|
1944
|
+
return [];
|
|
1945
|
+
})
|
|
1946
|
+
)
|
|
1947
|
+
);
|
|
1948
|
+
return [...workflowFunctions, ...userFunctions];
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
// src/connect.ts
|
|
1952
|
+
async function connect(options) {
|
|
1953
|
+
const { mastra, inngest, functions, registerOptions, ...connectOptions } = options;
|
|
1954
|
+
const appFunctions = collectInngestFunctions({ mastra, functions });
|
|
1955
|
+
if (appFunctions.length === 0) {
|
|
1956
|
+
console.warn(
|
|
1957
|
+
"[@mastra/inngest] connect() was called with no Inngest workflows and no additional functions. The worker will connect to Inngest but has nothing to execute. Register at least one InngestWorkflow on the Mastra instance or pass `functions: [...]`."
|
|
1958
|
+
);
|
|
1959
|
+
}
|
|
1960
|
+
const { connect: connectWorker } = await import('inngest/connect');
|
|
1961
|
+
return connectWorker({
|
|
1962
|
+
// Top-level Connect options first, then registerOptions so they take precedence
|
|
1963
|
+
// for any overlapping keys (e.g. `signingKey`). This matches serve()'s behavior.
|
|
1964
|
+
...connectOptions,
|
|
1965
|
+
...registerOptions,
|
|
1966
|
+
apps: [{ client: inngest, functions: appFunctions }]
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
exports.InngestExecutionEngine = InngestExecutionEngine;
|
|
1971
|
+
exports.InngestPubSub = InngestPubSub;
|
|
1972
|
+
exports.InngestRun = InngestRun;
|
|
1973
|
+
exports.InngestWorkflow = InngestWorkflow;
|
|
1974
|
+
exports.collectInngestFunctions = collectInngestFunctions;
|
|
1975
|
+
exports.connect = connect;
|
|
1976
|
+
//# sourceMappingURL=chunk-MICZJLEE.cjs.map
|
|
1977
|
+
//# sourceMappingURL=chunk-MICZJLEE.cjs.map
|