@mastra/inngest 1.0.0-beta.6 → 1.0.0-beta.9
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 +186 -0
- package/dist/execution-engine.d.ts +29 -5
- package/dist/execution-engine.d.ts.map +1 -1
- package/dist/index.cjs +364 -112
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +359 -108
- package/dist/index.js.map +1 -1
- package/dist/pubsub.d.ts +56 -0
- package/dist/pubsub.d.ts.map +1 -0
- package/dist/run.d.ts +24 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/workflow.d.ts.map +1 -1
- package/package.json +4 -5
package/dist/index.cjs
CHANGED
|
@@ -4,11 +4,13 @@ var tools = require('@mastra/core/tools');
|
|
|
4
4
|
var workflows = require('@mastra/core/workflows');
|
|
5
5
|
var _constants = require('@mastra/core/workflows/_constants');
|
|
6
6
|
var zod = require('zod');
|
|
7
|
-
var crypto = require('crypto');
|
|
7
|
+
var crypto$1 = require('crypto');
|
|
8
8
|
var di = require('@mastra/core/di');
|
|
9
9
|
var inngest = require('inngest');
|
|
10
|
-
var
|
|
10
|
+
var error = require('@mastra/core/error');
|
|
11
11
|
var realtime = require('@inngest/realtime');
|
|
12
|
+
var events = require('@mastra/core/events');
|
|
13
|
+
var web = require('stream/web');
|
|
12
14
|
var stream = require('@mastra/core/stream');
|
|
13
15
|
var hono = require('inngest/hono');
|
|
14
16
|
|
|
@@ -25,17 +27,18 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
|
|
|
25
27
|
// Hook Overrides
|
|
26
28
|
// =============================================================================
|
|
27
29
|
/**
|
|
28
|
-
* Format errors
|
|
30
|
+
* Format errors while preserving Error instances and their custom properties.
|
|
31
|
+
* Uses getErrorFromUnknown to ensure all error properties are preserved.
|
|
29
32
|
*/
|
|
30
|
-
formatResultError(error, lastOutput) {
|
|
31
|
-
if (error instanceof Error) {
|
|
32
|
-
return error.stack ?? error.message;
|
|
33
|
-
}
|
|
33
|
+
formatResultError(error$1, lastOutput) {
|
|
34
34
|
const outputError = lastOutput?.error;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
const errorSource = error$1 || outputError;
|
|
36
|
+
const errorInstance = error.getErrorFromUnknown(errorSource, {
|
|
37
|
+
serializeStack: true,
|
|
38
|
+
// Include stack in JSON for better debugging in Inngest
|
|
39
|
+
fallbackMessage: "Unknown workflow error"
|
|
40
|
+
});
|
|
41
|
+
return errorInstance.toJSON();
|
|
39
42
|
}
|
|
40
43
|
/**
|
|
41
44
|
* Detect InngestWorkflow instances for special nested workflow handling
|
|
@@ -67,18 +70,24 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
|
|
|
67
70
|
error: e,
|
|
68
71
|
attributes: { status: "failed" }
|
|
69
72
|
});
|
|
73
|
+
if (cause.error && !(cause.error instanceof Error)) {
|
|
74
|
+
cause.error = error.getErrorFromUnknown(cause.error, { serializeStack: false });
|
|
75
|
+
}
|
|
70
76
|
return { ok: false, error: cause };
|
|
71
77
|
}
|
|
72
|
-
const
|
|
78
|
+
const errorInstance = error.getErrorFromUnknown(e, {
|
|
79
|
+
serializeStack: false,
|
|
80
|
+
fallbackMessage: "Unknown step execution error"
|
|
81
|
+
});
|
|
73
82
|
params.stepSpan?.error({
|
|
74
|
-
error:
|
|
83
|
+
error: errorInstance,
|
|
75
84
|
attributes: { status: "failed" }
|
|
76
85
|
});
|
|
77
86
|
return {
|
|
78
87
|
ok: false,
|
|
79
88
|
error: {
|
|
80
89
|
status: "failed",
|
|
81
|
-
error:
|
|
90
|
+
error: errorInstance,
|
|
82
91
|
endedAt: Date.now()
|
|
83
92
|
}
|
|
84
93
|
};
|
|
@@ -107,11 +116,14 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
|
|
|
107
116
|
return await operationFn();
|
|
108
117
|
} catch (e) {
|
|
109
118
|
if (retryConfig) {
|
|
110
|
-
const
|
|
111
|
-
|
|
119
|
+
const errorInstance = error.getErrorFromUnknown(e, {
|
|
120
|
+
serializeStack: false,
|
|
121
|
+
fallbackMessage: "Unknown step execution error"
|
|
122
|
+
});
|
|
123
|
+
throw new inngest.RetryAfterError(errorInstance.message, retryConfig.delay, {
|
|
112
124
|
cause: {
|
|
113
125
|
status: "failed",
|
|
114
|
-
error:
|
|
126
|
+
error: errorInstance,
|
|
115
127
|
endedAt: Date.now()
|
|
116
128
|
}
|
|
117
129
|
});
|
|
@@ -126,6 +138,18 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
|
|
|
126
138
|
getEngineContext() {
|
|
127
139
|
return { step: this.inngestStep };
|
|
128
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* For Inngest, lifecycle callbacks are invoked in the workflow's finalize step
|
|
143
|
+
* (wrapped in step.run for durability), not in execute(). Override to skip.
|
|
144
|
+
*/
|
|
145
|
+
async invokeLifecycleCallbacks(_result) {
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Actually invoke the lifecycle callbacks. Called from workflow.ts finalize step.
|
|
149
|
+
*/
|
|
150
|
+
async invokeLifecycleCallbacksInternal(result) {
|
|
151
|
+
return super.invokeLifecycleCallbacks(result);
|
|
152
|
+
}
|
|
129
153
|
/**
|
|
130
154
|
* Execute nested InngestWorkflow using inngestStep.invoke() for durability.
|
|
131
155
|
* This MUST be called directly (not inside step.run()) due to Inngest constraints.
|
|
@@ -134,14 +158,14 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
|
|
|
134
158
|
if (!(params.step instanceof InngestWorkflow)) {
|
|
135
159
|
return null;
|
|
136
160
|
}
|
|
137
|
-
const { step, stepResults, executionContext, resume, timeTravel, prevOutput, inputData,
|
|
161
|
+
const { step, stepResults, executionContext, resume, timeTravel, prevOutput, inputData, pubsub, startedAt } = params;
|
|
138
162
|
const isResume = !!resume?.steps?.length;
|
|
139
163
|
let result;
|
|
140
164
|
let runId;
|
|
141
165
|
const isTimeTravel = !!(timeTravel && timeTravel.steps?.length > 1 && timeTravel.steps[0] === step.id);
|
|
142
166
|
try {
|
|
143
167
|
if (isResume) {
|
|
144
|
-
runId = stepResults[resume?.steps?.[0] ?? ""]?.suspendPayload?.__workflow_meta?.runId ?? crypto.randomUUID();
|
|
168
|
+
runId = stepResults[resume?.steps?.[0] ?? ""]?.suspendPayload?.__workflow_meta?.runId ?? crypto$1.randomUUID();
|
|
145
169
|
const snapshot = await this.mastra?.getStorage()?.loadWorkflowSnapshot({
|
|
146
170
|
workflowName: step.id,
|
|
147
171
|
runId
|
|
@@ -208,9 +232,9 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
|
|
|
208
232
|
const errorCause = e?.cause;
|
|
209
233
|
if (errorCause && typeof errorCause === "object") {
|
|
210
234
|
result = errorCause;
|
|
211
|
-
runId = errorCause.runId || crypto.randomUUID();
|
|
235
|
+
runId = errorCause.runId || crypto$1.randomUUID();
|
|
212
236
|
} else {
|
|
213
|
-
runId = crypto.randomUUID();
|
|
237
|
+
runId = crypto$1.randomUUID();
|
|
214
238
|
result = {
|
|
215
239
|
status: "failed",
|
|
216
240
|
error: e instanceof Error ? e : new Error(String(e)),
|
|
@@ -223,13 +247,17 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
|
|
|
223
247
|
`workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
|
|
224
248
|
async () => {
|
|
225
249
|
if (result.status === "failed") {
|
|
226
|
-
await
|
|
227
|
-
type: "
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
250
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
251
|
+
type: "watch",
|
|
252
|
+
runId: executionContext.runId,
|
|
253
|
+
data: {
|
|
254
|
+
type: "workflow-step-result",
|
|
255
|
+
payload: {
|
|
256
|
+
id: step.id,
|
|
257
|
+
status: "failed",
|
|
258
|
+
error: result?.error,
|
|
259
|
+
payload: prevOutput
|
|
260
|
+
}
|
|
233
261
|
}
|
|
234
262
|
});
|
|
235
263
|
return { executionContext, result: { status: "failed", error: result?.error } };
|
|
@@ -241,11 +269,15 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
|
|
|
241
269
|
for (const [stepName, stepResult] of suspendedSteps) {
|
|
242
270
|
const suspendPath = [stepName, ...stepResult?.suspendPayload?.__workflow_meta?.path ?? []];
|
|
243
271
|
executionContext.suspendedPaths[step.id] = executionContext.executionPath;
|
|
244
|
-
await
|
|
245
|
-
type: "
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
272
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
273
|
+
type: "watch",
|
|
274
|
+
runId: executionContext.runId,
|
|
275
|
+
data: {
|
|
276
|
+
type: "workflow-step-suspended",
|
|
277
|
+
payload: {
|
|
278
|
+
id: step.id,
|
|
279
|
+
status: "suspended"
|
|
280
|
+
}
|
|
249
281
|
}
|
|
250
282
|
});
|
|
251
283
|
return {
|
|
@@ -268,13 +300,17 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
|
|
|
268
300
|
}
|
|
269
301
|
};
|
|
270
302
|
} else if (result.status === "tripwire") {
|
|
271
|
-
await
|
|
272
|
-
type: "
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
303
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
304
|
+
type: "watch",
|
|
305
|
+
runId: executionContext.runId,
|
|
306
|
+
data: {
|
|
307
|
+
type: "workflow-step-result",
|
|
308
|
+
payload: {
|
|
309
|
+
id: step.id,
|
|
310
|
+
status: "tripwire",
|
|
311
|
+
error: result?.tripwire?.reason,
|
|
312
|
+
payload: prevOutput
|
|
313
|
+
}
|
|
278
314
|
}
|
|
279
315
|
});
|
|
280
316
|
return {
|
|
@@ -285,19 +321,27 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
|
|
|
285
321
|
}
|
|
286
322
|
};
|
|
287
323
|
}
|
|
288
|
-
await
|
|
289
|
-
type: "
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
324
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
325
|
+
type: "watch",
|
|
326
|
+
runId: executionContext.runId,
|
|
327
|
+
data: {
|
|
328
|
+
type: "workflow-step-result",
|
|
329
|
+
payload: {
|
|
330
|
+
id: step.id,
|
|
331
|
+
status: "success",
|
|
332
|
+
output: result?.result
|
|
333
|
+
}
|
|
294
334
|
}
|
|
295
335
|
});
|
|
296
|
-
await
|
|
297
|
-
type: "
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
336
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
337
|
+
type: "watch",
|
|
338
|
+
runId: executionContext.runId,
|
|
339
|
+
data: {
|
|
340
|
+
type: "workflow-step-finish",
|
|
341
|
+
payload: {
|
|
342
|
+
id: step.id,
|
|
343
|
+
metadata: {}
|
|
344
|
+
}
|
|
301
345
|
}
|
|
302
346
|
});
|
|
303
347
|
return { executionContext, result: { status: "success", output: result?.result } };
|
|
@@ -314,6 +358,118 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
|
|
|
314
358
|
};
|
|
315
359
|
}
|
|
316
360
|
};
|
|
361
|
+
var InngestPubSub = class extends events.PubSub {
|
|
362
|
+
inngest;
|
|
363
|
+
workflowId;
|
|
364
|
+
publishFn;
|
|
365
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
366
|
+
constructor(inngest, workflowId, publishFn) {
|
|
367
|
+
super();
|
|
368
|
+
this.inngest = inngest;
|
|
369
|
+
this.workflowId = workflowId;
|
|
370
|
+
this.publishFn = publishFn;
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Publish an event to Inngest's realtime system.
|
|
374
|
+
*
|
|
375
|
+
* Topic format: "workflow.events.v2.{runId}"
|
|
376
|
+
* Maps to Inngest channel: "workflow:{workflowId}:{runId}"
|
|
377
|
+
*/
|
|
378
|
+
async publish(topic, event) {
|
|
379
|
+
if (!this.publishFn) {
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
const match = topic.match(/^workflow\.events\.v2\.(.+)$/);
|
|
383
|
+
if (!match) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const runId = match[1];
|
|
387
|
+
try {
|
|
388
|
+
await this.publishFn({
|
|
389
|
+
channel: `workflow:${this.workflowId}:${runId}`,
|
|
390
|
+
topic: "watch",
|
|
391
|
+
data: event.data
|
|
392
|
+
});
|
|
393
|
+
} catch (err) {
|
|
394
|
+
console.error("InngestPubSub publish error:", err?.message ?? err);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Subscribe to events from Inngest's realtime system.
|
|
399
|
+
*
|
|
400
|
+
* Topic format: "workflow.events.v2.{runId}"
|
|
401
|
+
* Maps to Inngest channel: "workflow:{workflowId}:{runId}"
|
|
402
|
+
*/
|
|
403
|
+
async subscribe(topic, cb) {
|
|
404
|
+
const match = topic.match(/^workflow\.events\.v2\.(.+)$/);
|
|
405
|
+
if (!match || !match[1]) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
const runId = match[1];
|
|
409
|
+
if (this.subscriptions.has(topic)) {
|
|
410
|
+
this.subscriptions.get(topic).callbacks.add(cb);
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
const callbacks = /* @__PURE__ */ new Set([cb]);
|
|
414
|
+
const channel = `workflow:${this.workflowId}:${runId}`;
|
|
415
|
+
const streamPromise = realtime.subscribe(
|
|
416
|
+
{
|
|
417
|
+
channel,
|
|
418
|
+
topics: ["watch"],
|
|
419
|
+
app: this.inngest
|
|
420
|
+
},
|
|
421
|
+
(message) => {
|
|
422
|
+
const event = {
|
|
423
|
+
id: crypto.randomUUID(),
|
|
424
|
+
type: "watch",
|
|
425
|
+
runId,
|
|
426
|
+
data: message.data,
|
|
427
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
428
|
+
};
|
|
429
|
+
for (const callback of callbacks) {
|
|
430
|
+
callback(event);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
);
|
|
434
|
+
this.subscriptions.set(topic, {
|
|
435
|
+
unsubscribe: () => {
|
|
436
|
+
streamPromise.then((stream) => stream.cancel()).catch((err) => {
|
|
437
|
+
console.error("InngestPubSub unsubscribe error:", err);
|
|
438
|
+
});
|
|
439
|
+
},
|
|
440
|
+
callbacks
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Unsubscribe a callback from a topic.
|
|
445
|
+
* If no callbacks remain, the underlying Inngest subscription is cancelled.
|
|
446
|
+
*/
|
|
447
|
+
async unsubscribe(topic, cb) {
|
|
448
|
+
const sub = this.subscriptions.get(topic);
|
|
449
|
+
if (!sub) {
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
sub.callbacks.delete(cb);
|
|
453
|
+
if (sub.callbacks.size === 0) {
|
|
454
|
+
sub.unsubscribe();
|
|
455
|
+
this.subscriptions.delete(topic);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Flush any pending operations. No-op for Inngest.
|
|
460
|
+
*/
|
|
461
|
+
async flush() {
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Clean up all subscriptions during graceful shutdown.
|
|
465
|
+
*/
|
|
466
|
+
async close() {
|
|
467
|
+
for (const [, sub] of this.subscriptions) {
|
|
468
|
+
sub.unsubscribe();
|
|
469
|
+
}
|
|
470
|
+
this.subscriptions.clear();
|
|
471
|
+
}
|
|
472
|
+
};
|
|
317
473
|
var InngestRun = class extends workflows.Run {
|
|
318
474
|
inngest;
|
|
319
475
|
serializedStepGraph;
|
|
@@ -325,27 +481,77 @@ var InngestRun = class extends workflows.Run {
|
|
|
325
481
|
this.#mastra = params.mastra;
|
|
326
482
|
}
|
|
327
483
|
async getRuns(eventId) {
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
484
|
+
const maxRetries = 3;
|
|
485
|
+
let lastError = null;
|
|
486
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
487
|
+
try {
|
|
488
|
+
const response = await fetch(
|
|
489
|
+
`${this.inngest.apiBaseUrl ?? "https://api.inngest.com"}/v1/events/${eventId}/runs`,
|
|
490
|
+
{
|
|
491
|
+
headers: {
|
|
492
|
+
Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
);
|
|
496
|
+
if (response.status === 429) {
|
|
497
|
+
const retryAfter = parseInt(response.headers.get("retry-after") || "2", 10);
|
|
498
|
+
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1e3));
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
if (!response.ok) {
|
|
502
|
+
throw new Error(`Inngest API error: ${response.status} ${response.statusText}`);
|
|
503
|
+
}
|
|
504
|
+
const text = await response.text();
|
|
505
|
+
if (!text) {
|
|
506
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3 * (attempt + 1)));
|
|
507
|
+
continue;
|
|
508
|
+
}
|
|
509
|
+
const json = JSON.parse(text);
|
|
510
|
+
return json.data;
|
|
511
|
+
} catch (error) {
|
|
512
|
+
lastError = error;
|
|
513
|
+
if (attempt < maxRetries - 1) {
|
|
514
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3 * Math.pow(2, attempt)));
|
|
515
|
+
}
|
|
331
516
|
}
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
return json.data;
|
|
517
|
+
}
|
|
518
|
+
throw new inngest.NonRetriableError(`Failed to get runs after ${maxRetries} attempts: ${lastError?.message}`);
|
|
335
519
|
}
|
|
336
|
-
async getRunOutput(eventId) {
|
|
337
|
-
|
|
520
|
+
async getRunOutput(eventId, maxWaitMs = 3e5) {
|
|
521
|
+
const startTime = Date.now();
|
|
338
522
|
const storage = this.#mastra?.getStorage();
|
|
339
|
-
while (
|
|
340
|
-
|
|
341
|
-
|
|
523
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
524
|
+
let runs;
|
|
525
|
+
try {
|
|
526
|
+
runs = await this.getRuns(eventId);
|
|
527
|
+
} catch (error) {
|
|
528
|
+
if (error instanceof inngest.NonRetriableError) {
|
|
529
|
+
throw error;
|
|
530
|
+
}
|
|
531
|
+
throw new inngest.NonRetriableError(
|
|
532
|
+
`Failed to poll workflow status: ${error instanceof Error ? error.message : String(error)}`
|
|
533
|
+
);
|
|
534
|
+
}
|
|
535
|
+
if (runs?.[0]?.status === "Completed" && runs?.[0]?.event_id === eventId) {
|
|
536
|
+
return runs[0];
|
|
537
|
+
}
|
|
342
538
|
if (runs?.[0]?.status === "Failed") {
|
|
343
539
|
const snapshot = await storage?.loadWorkflowSnapshot({
|
|
344
540
|
workflowName: this.workflowId,
|
|
345
541
|
runId: this.runId
|
|
346
542
|
});
|
|
543
|
+
if (snapshot?.context) {
|
|
544
|
+
snapshot.context = workflows.hydrateSerializedStepErrors(snapshot.context);
|
|
545
|
+
}
|
|
347
546
|
return {
|
|
348
|
-
output: {
|
|
547
|
+
output: {
|
|
548
|
+
result: {
|
|
549
|
+
steps: snapshot?.context,
|
|
550
|
+
status: "failed",
|
|
551
|
+
// Get the original error from NonRetriableError's cause (which contains the workflow result)
|
|
552
|
+
error: error.getErrorFromUnknown(runs?.[0]?.output?.cause?.error, { serializeStack: false })
|
|
553
|
+
}
|
|
554
|
+
}
|
|
349
555
|
};
|
|
350
556
|
}
|
|
351
557
|
if (runs?.[0]?.status === "Cancelled") {
|
|
@@ -355,8 +561,9 @@ var InngestRun = class extends workflows.Run {
|
|
|
355
561
|
});
|
|
356
562
|
return { output: { result: { steps: snapshot?.context, status: "canceled" } } };
|
|
357
563
|
}
|
|
564
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3 + Math.random() * 1e3));
|
|
358
565
|
}
|
|
359
|
-
|
|
566
|
+
throw new inngest.NonRetriableError(`Workflow did not complete within ${maxWaitMs}ms`);
|
|
360
567
|
}
|
|
361
568
|
async cancel() {
|
|
362
569
|
const storage = this.#mastra?.getStorage();
|
|
@@ -386,6 +593,51 @@ var InngestRun = class extends workflows.Run {
|
|
|
386
593
|
async start(params) {
|
|
387
594
|
return this._start(params);
|
|
388
595
|
}
|
|
596
|
+
/**
|
|
597
|
+
* Starts the workflow execution without waiting for completion (fire-and-forget).
|
|
598
|
+
* Returns immediately with the runId after sending the event to Inngest.
|
|
599
|
+
* The workflow executes independently in Inngest.
|
|
600
|
+
* Use this when you don't need to wait for the result or want to avoid polling failures.
|
|
601
|
+
*/
|
|
602
|
+
async startAsync(params) {
|
|
603
|
+
await this.#mastra.getStorage()?.persistWorkflowSnapshot({
|
|
604
|
+
workflowName: this.workflowId,
|
|
605
|
+
runId: this.runId,
|
|
606
|
+
resourceId: this.resourceId,
|
|
607
|
+
snapshot: {
|
|
608
|
+
runId: this.runId,
|
|
609
|
+
serializedStepGraph: this.serializedStepGraph,
|
|
610
|
+
status: "running",
|
|
611
|
+
value: {},
|
|
612
|
+
context: {},
|
|
613
|
+
activePaths: [],
|
|
614
|
+
suspendedPaths: {},
|
|
615
|
+
activeStepsPath: {},
|
|
616
|
+
resumeLabels: {},
|
|
617
|
+
waitingPaths: {},
|
|
618
|
+
timestamp: Date.now()
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
const inputDataToUse = await this._validateInput(params.inputData);
|
|
622
|
+
const initialStateToUse = await this._validateInitialState(params.initialState ?? {});
|
|
623
|
+
const eventOutput = await this.inngest.send({
|
|
624
|
+
name: `workflow.${this.workflowId}`,
|
|
625
|
+
data: {
|
|
626
|
+
inputData: inputDataToUse,
|
|
627
|
+
initialState: initialStateToUse,
|
|
628
|
+
runId: this.runId,
|
|
629
|
+
resourceId: this.resourceId,
|
|
630
|
+
outputOptions: params.outputOptions,
|
|
631
|
+
tracingOptions: params.tracingOptions,
|
|
632
|
+
requestContext: params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {}
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
const eventId = eventOutput.ids[0];
|
|
636
|
+
if (!eventId) {
|
|
637
|
+
throw new Error("Event ID is not set");
|
|
638
|
+
}
|
|
639
|
+
return { runId: this.runId };
|
|
640
|
+
}
|
|
389
641
|
async _start({
|
|
390
642
|
inputData,
|
|
391
643
|
initialState,
|
|
@@ -433,9 +685,7 @@ var InngestRun = class extends workflows.Run {
|
|
|
433
685
|
}
|
|
434
686
|
const runOutput = await this.getRunOutput(eventId);
|
|
435
687
|
const result = runOutput?.output?.result;
|
|
436
|
-
|
|
437
|
-
result.error = new Error(result.error);
|
|
438
|
-
}
|
|
688
|
+
this.hydrateFailedResult(result);
|
|
439
689
|
if (result.status !== "suspended") {
|
|
440
690
|
this.cleanup?.();
|
|
441
691
|
}
|
|
@@ -494,9 +744,7 @@ var InngestRun = class extends workflows.Run {
|
|
|
494
744
|
}
|
|
495
745
|
const runOutput = await this.getRunOutput(eventId);
|
|
496
746
|
const result = runOutput?.output?.result;
|
|
497
|
-
|
|
498
|
-
result.error = new Error(result.error);
|
|
499
|
-
}
|
|
747
|
+
this.hydrateFailedResult(result);
|
|
500
748
|
return result;
|
|
501
749
|
}
|
|
502
750
|
async timeTravel(params) {
|
|
@@ -586,9 +834,7 @@ var InngestRun = class extends workflows.Run {
|
|
|
586
834
|
}
|
|
587
835
|
const runOutput = await this.getRunOutput(eventId);
|
|
588
836
|
const result = runOutput?.output?.result;
|
|
589
|
-
|
|
590
|
-
result.error = new Error(result.error);
|
|
591
|
-
}
|
|
837
|
+
this.hydrateFailedResult(result);
|
|
592
838
|
return result;
|
|
593
839
|
}
|
|
594
840
|
watch(cb) {
|
|
@@ -809,6 +1055,18 @@ var InngestRun = class extends workflows.Run {
|
|
|
809
1055
|
});
|
|
810
1056
|
return this.streamOutput;
|
|
811
1057
|
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Hydrates errors in a failed workflow result back to proper Error instances.
|
|
1060
|
+
* This ensures error.cause chains and custom properties are preserved.
|
|
1061
|
+
*/
|
|
1062
|
+
hydrateFailedResult(result) {
|
|
1063
|
+
if (result.status === "failed") {
|
|
1064
|
+
result.error = error.getErrorFromUnknown(result.error, { serializeStack: false });
|
|
1065
|
+
if (result.steps) {
|
|
1066
|
+
workflows.hydrateSerializedStepErrors(result.steps);
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
812
1070
|
};
|
|
813
1071
|
|
|
814
1072
|
// src/workflow.ts
|
|
@@ -846,6 +1104,7 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
846
1104
|
return run ?? (this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null);
|
|
847
1105
|
}
|
|
848
1106
|
__registerMastra(mastra) {
|
|
1107
|
+
super.__registerMastra(mastra);
|
|
849
1108
|
this.#mastra = mastra;
|
|
850
1109
|
this.executionEngine.__registerMastra(mastra);
|
|
851
1110
|
const updateNested = (step) => {
|
|
@@ -864,7 +1123,7 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
864
1123
|
}
|
|
865
1124
|
}
|
|
866
1125
|
async createRun(options) {
|
|
867
|
-
const runIdToUse = options?.runId || crypto.randomUUID();
|
|
1126
|
+
const runIdToUse = options?.runId || crypto$1.randomUUID();
|
|
868
1127
|
const run = this.runs.get(runIdToUse) ?? new InngestRun(
|
|
869
1128
|
{
|
|
870
1129
|
workflowId: this.id,
|
|
@@ -887,7 +1146,9 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
887
1146
|
workflowStatus: run.workflowRunStatus,
|
|
888
1147
|
stepResults: {}
|
|
889
1148
|
});
|
|
890
|
-
const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse,
|
|
1149
|
+
const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse, {
|
|
1150
|
+
withNestedWorkflows: false
|
|
1151
|
+
});
|
|
891
1152
|
if (!workflowSnapshotInStorage && shouldPersistSnapshot) {
|
|
892
1153
|
await this.mastra?.getStorage()?.persistWorkflowSnapshot({
|
|
893
1154
|
workflowName: this.id,
|
|
@@ -929,31 +1190,10 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
929
1190
|
let { inputData, initialState, runId, resourceId, resume, outputOptions, format, timeTravel } = event.data;
|
|
930
1191
|
if (!runId) {
|
|
931
1192
|
runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
|
|
932
|
-
return crypto.randomUUID();
|
|
1193
|
+
return crypto$1.randomUUID();
|
|
933
1194
|
});
|
|
934
1195
|
}
|
|
935
|
-
const
|
|
936
|
-
emit: async (event2, data) => {
|
|
937
|
-
if (!publish) {
|
|
938
|
-
return;
|
|
939
|
-
}
|
|
940
|
-
try {
|
|
941
|
-
await publish({
|
|
942
|
-
channel: `workflow:${this.id}:${runId}`,
|
|
943
|
-
topic: event2,
|
|
944
|
-
data
|
|
945
|
-
});
|
|
946
|
-
} catch (err) {
|
|
947
|
-
this.logger.error("Error emitting event: " + (err?.stack ?? err?.message ?? err));
|
|
948
|
-
}
|
|
949
|
-
},
|
|
950
|
-
on: (_event, _callback) => {
|
|
951
|
-
},
|
|
952
|
-
off: (_event, _callback) => {
|
|
953
|
-
},
|
|
954
|
-
once: (_event, _callback) => {
|
|
955
|
-
}
|
|
956
|
-
};
|
|
1196
|
+
const pubsub = new InngestPubSub(this.inngest, this.id, publish);
|
|
957
1197
|
const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
|
|
958
1198
|
const result = await engine.execute({
|
|
959
1199
|
workflowId: this.id,
|
|
@@ -963,7 +1203,7 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
963
1203
|
serializedStepGraph: this.serializedStepGraph,
|
|
964
1204
|
input: inputData,
|
|
965
1205
|
initialState,
|
|
966
|
-
|
|
1206
|
+
pubsub,
|
|
967
1207
|
retryConfig: this.retryConfig,
|
|
968
1208
|
requestContext: new di.RequestContext(Object.entries(event.data.requestContext ?? {})),
|
|
969
1209
|
resume,
|
|
@@ -973,11 +1213,19 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
973
1213
|
// currentSpan: undefined, // TODO: Pass actual parent Span from workflow execution context
|
|
974
1214
|
outputOptions,
|
|
975
1215
|
outputWriter: async (chunk) => {
|
|
976
|
-
|
|
977
|
-
|
|
1216
|
+
try {
|
|
1217
|
+
await pubsub.publish(`workflow.events.v2.${runId}`, {
|
|
1218
|
+
type: "watch",
|
|
1219
|
+
runId,
|
|
1220
|
+
data: chunk
|
|
1221
|
+
});
|
|
1222
|
+
} catch (err) {
|
|
1223
|
+
this.logger.debug?.("Failed to publish watch event:", err);
|
|
1224
|
+
}
|
|
978
1225
|
}
|
|
979
1226
|
});
|
|
980
1227
|
await step.run(`workflow.${this.id}.finalize`, async () => {
|
|
1228
|
+
await engine.invokeLifecycleCallbacksInternal(result);
|
|
981
1229
|
if (result.status === "failed") {
|
|
982
1230
|
throw new inngest.NonRetriableError(`Workflow failed`, {
|
|
983
1231
|
cause: result
|
|
@@ -1062,7 +1310,8 @@ function createStep(params, agentOptions) {
|
|
|
1062
1310
|
outputSchema,
|
|
1063
1311
|
execute: async ({
|
|
1064
1312
|
inputData,
|
|
1065
|
-
|
|
1313
|
+
runId,
|
|
1314
|
+
[_constants.PUBSUB_SYMBOL]: pubsub,
|
|
1066
1315
|
[_constants.STREAM_FORMAT_SYMBOL]: streamFormat,
|
|
1067
1316
|
requestContext,
|
|
1068
1317
|
tracingContext,
|
|
@@ -1117,22 +1366,24 @@ function createStep(params, agentOptions) {
|
|
|
1117
1366
|
stream = modelOutput.fullStream;
|
|
1118
1367
|
}
|
|
1119
1368
|
if (streamFormat === "legacy") {
|
|
1120
|
-
await
|
|
1121
|
-
type: "
|
|
1122
|
-
|
|
1369
|
+
await pubsub.publish(`workflow.events.v2.${runId}`, {
|
|
1370
|
+
type: "watch",
|
|
1371
|
+
runId,
|
|
1372
|
+
data: { type: "tool-call-streaming-start", ...toolData ?? {} }
|
|
1123
1373
|
});
|
|
1124
1374
|
for await (const chunk of stream) {
|
|
1125
1375
|
if (chunk.type === "text-delta") {
|
|
1126
|
-
await
|
|
1127
|
-
type: "
|
|
1128
|
-
|
|
1129
|
-
argsTextDelta: chunk.textDelta
|
|
1376
|
+
await pubsub.publish(`workflow.events.v2.${runId}`, {
|
|
1377
|
+
type: "watch",
|
|
1378
|
+
runId,
|
|
1379
|
+
data: { type: "tool-call-delta", ...toolData ?? {}, argsTextDelta: chunk.textDelta }
|
|
1130
1380
|
});
|
|
1131
1381
|
}
|
|
1132
1382
|
}
|
|
1133
|
-
await
|
|
1134
|
-
type: "
|
|
1135
|
-
|
|
1383
|
+
await pubsub.publish(`workflow.events.v2.${runId}`, {
|
|
1384
|
+
type: "watch",
|
|
1385
|
+
runId,
|
|
1386
|
+
data: { type: "tool-call-streaming-finish", ...toolData ?? {} }
|
|
1136
1387
|
});
|
|
1137
1388
|
} else {
|
|
1138
1389
|
for await (const chunk of stream) {
|
|
@@ -1245,6 +1496,7 @@ function init(inngest) {
|
|
|
1245
1496
|
}
|
|
1246
1497
|
|
|
1247
1498
|
exports.InngestExecutionEngine = InngestExecutionEngine;
|
|
1499
|
+
exports.InngestPubSub = InngestPubSub;
|
|
1248
1500
|
exports.InngestRun = InngestRun;
|
|
1249
1501
|
exports.InngestWorkflow = InngestWorkflow;
|
|
1250
1502
|
exports._compatibilityCheck = _compatibilityCheck;
|