@mastra/inngest 1.0.0-beta.6 → 1.0.0-beta.8
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 +95 -0
- package/dist/execution-engine.d.ts +29 -5
- package/dist/execution-engine.d.ts.map +1 -1
- package/dist/index.cjs +361 -111
- 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 +356 -107
- 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,
|
|
@@ -929,31 +1188,10 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
929
1188
|
let { inputData, initialState, runId, resourceId, resume, outputOptions, format, timeTravel } = event.data;
|
|
930
1189
|
if (!runId) {
|
|
931
1190
|
runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
|
|
932
|
-
return crypto.randomUUID();
|
|
1191
|
+
return crypto$1.randomUUID();
|
|
933
1192
|
});
|
|
934
1193
|
}
|
|
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
|
-
};
|
|
1194
|
+
const pubsub = new InngestPubSub(this.inngest, this.id, publish);
|
|
957
1195
|
const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
|
|
958
1196
|
const result = await engine.execute({
|
|
959
1197
|
workflowId: this.id,
|
|
@@ -963,7 +1201,7 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
963
1201
|
serializedStepGraph: this.serializedStepGraph,
|
|
964
1202
|
input: inputData,
|
|
965
1203
|
initialState,
|
|
966
|
-
|
|
1204
|
+
pubsub,
|
|
967
1205
|
retryConfig: this.retryConfig,
|
|
968
1206
|
requestContext: new di.RequestContext(Object.entries(event.data.requestContext ?? {})),
|
|
969
1207
|
resume,
|
|
@@ -973,11 +1211,19 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
973
1211
|
// currentSpan: undefined, // TODO: Pass actual parent Span from workflow execution context
|
|
974
1212
|
outputOptions,
|
|
975
1213
|
outputWriter: async (chunk) => {
|
|
976
|
-
|
|
977
|
-
|
|
1214
|
+
try {
|
|
1215
|
+
await pubsub.publish(`workflow.events.v2.${runId}`, {
|
|
1216
|
+
type: "watch",
|
|
1217
|
+
runId,
|
|
1218
|
+
data: chunk
|
|
1219
|
+
});
|
|
1220
|
+
} catch (err) {
|
|
1221
|
+
this.logger.debug?.("Failed to publish watch event:", err);
|
|
1222
|
+
}
|
|
978
1223
|
}
|
|
979
1224
|
});
|
|
980
1225
|
await step.run(`workflow.${this.id}.finalize`, async () => {
|
|
1226
|
+
await engine.invokeLifecycleCallbacksInternal(result);
|
|
981
1227
|
if (result.status === "failed") {
|
|
982
1228
|
throw new inngest.NonRetriableError(`Workflow failed`, {
|
|
983
1229
|
cause: result
|
|
@@ -1062,7 +1308,8 @@ function createStep(params, agentOptions) {
|
|
|
1062
1308
|
outputSchema,
|
|
1063
1309
|
execute: async ({
|
|
1064
1310
|
inputData,
|
|
1065
|
-
|
|
1311
|
+
runId,
|
|
1312
|
+
[_constants.PUBSUB_SYMBOL]: pubsub,
|
|
1066
1313
|
[_constants.STREAM_FORMAT_SYMBOL]: streamFormat,
|
|
1067
1314
|
requestContext,
|
|
1068
1315
|
tracingContext,
|
|
@@ -1117,22 +1364,24 @@ function createStep(params, agentOptions) {
|
|
|
1117
1364
|
stream = modelOutput.fullStream;
|
|
1118
1365
|
}
|
|
1119
1366
|
if (streamFormat === "legacy") {
|
|
1120
|
-
await
|
|
1121
|
-
type: "
|
|
1122
|
-
|
|
1367
|
+
await pubsub.publish(`workflow.events.v2.${runId}`, {
|
|
1368
|
+
type: "watch",
|
|
1369
|
+
runId,
|
|
1370
|
+
data: { type: "tool-call-streaming-start", ...toolData ?? {} }
|
|
1123
1371
|
});
|
|
1124
1372
|
for await (const chunk of stream) {
|
|
1125
1373
|
if (chunk.type === "text-delta") {
|
|
1126
|
-
await
|
|
1127
|
-
type: "
|
|
1128
|
-
|
|
1129
|
-
argsTextDelta: chunk.textDelta
|
|
1374
|
+
await pubsub.publish(`workflow.events.v2.${runId}`, {
|
|
1375
|
+
type: "watch",
|
|
1376
|
+
runId,
|
|
1377
|
+
data: { type: "tool-call-delta", ...toolData ?? {}, argsTextDelta: chunk.textDelta }
|
|
1130
1378
|
});
|
|
1131
1379
|
}
|
|
1132
1380
|
}
|
|
1133
|
-
await
|
|
1134
|
-
type: "
|
|
1135
|
-
|
|
1381
|
+
await pubsub.publish(`workflow.events.v2.${runId}`, {
|
|
1382
|
+
type: "watch",
|
|
1383
|
+
runId,
|
|
1384
|
+
data: { type: "tool-call-streaming-finish", ...toolData ?? {} }
|
|
1136
1385
|
});
|
|
1137
1386
|
} else {
|
|
1138
1387
|
for await (const chunk of stream) {
|
|
@@ -1245,6 +1494,7 @@ function init(inngest) {
|
|
|
1245
1494
|
}
|
|
1246
1495
|
|
|
1247
1496
|
exports.InngestExecutionEngine = InngestExecutionEngine;
|
|
1497
|
+
exports.InngestPubSub = InngestPubSub;
|
|
1248
1498
|
exports.InngestRun = InngestRun;
|
|
1249
1499
|
exports.InngestWorkflow = InngestWorkflow;
|
|
1250
1500
|
exports._compatibilityCheck = _compatibilityCheck;
|