@mastra/inngest 1.0.0-beta.5 → 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 +121 -0
- package/dist/execution-engine.d.ts +29 -5
- package/dist/execution-engine.d.ts.map +1 -1
- package/dist/index.cjs +371 -104
- 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 +366 -100
- 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 {
|
|
@@ -267,20 +299,49 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
|
|
|
267
299
|
payload: {}
|
|
268
300
|
}
|
|
269
301
|
};
|
|
302
|
+
} else if (result.status === "tripwire") {
|
|
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
|
+
}
|
|
314
|
+
}
|
|
315
|
+
});
|
|
316
|
+
return {
|
|
317
|
+
executionContext,
|
|
318
|
+
result: {
|
|
319
|
+
status: "tripwire",
|
|
320
|
+
tripwire: result?.tripwire
|
|
321
|
+
}
|
|
322
|
+
};
|
|
270
323
|
}
|
|
271
|
-
await
|
|
272
|
-
type: "
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
+
}
|
|
277
334
|
}
|
|
278
335
|
});
|
|
279
|
-
await
|
|
280
|
-
type: "
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
+
}
|
|
284
345
|
}
|
|
285
346
|
});
|
|
286
347
|
return { executionContext, result: { status: "success", output: result?.result } };
|
|
@@ -297,6 +358,118 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
|
|
|
297
358
|
};
|
|
298
359
|
}
|
|
299
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
|
+
};
|
|
300
473
|
var InngestRun = class extends workflows.Run {
|
|
301
474
|
inngest;
|
|
302
475
|
serializedStepGraph;
|
|
@@ -308,27 +481,77 @@ var InngestRun = class extends workflows.Run {
|
|
|
308
481
|
this.#mastra = params.mastra;
|
|
309
482
|
}
|
|
310
483
|
async getRuns(eventId) {
|
|
311
|
-
const
|
|
312
|
-
|
|
313
|
-
|
|
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
|
+
}
|
|
314
516
|
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
return json.data;
|
|
517
|
+
}
|
|
518
|
+
throw new inngest.NonRetriableError(`Failed to get runs after ${maxRetries} attempts: ${lastError?.message}`);
|
|
318
519
|
}
|
|
319
|
-
async getRunOutput(eventId) {
|
|
320
|
-
|
|
520
|
+
async getRunOutput(eventId, maxWaitMs = 3e5) {
|
|
521
|
+
const startTime = Date.now();
|
|
321
522
|
const storage = this.#mastra?.getStorage();
|
|
322
|
-
while (
|
|
323
|
-
|
|
324
|
-
|
|
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
|
+
}
|
|
325
538
|
if (runs?.[0]?.status === "Failed") {
|
|
326
539
|
const snapshot = await storage?.loadWorkflowSnapshot({
|
|
327
540
|
workflowName: this.workflowId,
|
|
328
541
|
runId: this.runId
|
|
329
542
|
});
|
|
543
|
+
if (snapshot?.context) {
|
|
544
|
+
snapshot.context = workflows.hydrateSerializedStepErrors(snapshot.context);
|
|
545
|
+
}
|
|
330
546
|
return {
|
|
331
|
-
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
|
+
}
|
|
332
555
|
};
|
|
333
556
|
}
|
|
334
557
|
if (runs?.[0]?.status === "Cancelled") {
|
|
@@ -338,8 +561,9 @@ var InngestRun = class extends workflows.Run {
|
|
|
338
561
|
});
|
|
339
562
|
return { output: { result: { steps: snapshot?.context, status: "canceled" } } };
|
|
340
563
|
}
|
|
564
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3 + Math.random() * 1e3));
|
|
341
565
|
}
|
|
342
|
-
|
|
566
|
+
throw new inngest.NonRetriableError(`Workflow did not complete within ${maxWaitMs}ms`);
|
|
343
567
|
}
|
|
344
568
|
async cancel() {
|
|
345
569
|
const storage = this.#mastra?.getStorage();
|
|
@@ -369,6 +593,51 @@ var InngestRun = class extends workflows.Run {
|
|
|
369
593
|
async start(params) {
|
|
370
594
|
return this._start(params);
|
|
371
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
|
+
}
|
|
372
641
|
async _start({
|
|
373
642
|
inputData,
|
|
374
643
|
initialState,
|
|
@@ -416,9 +685,7 @@ var InngestRun = class extends workflows.Run {
|
|
|
416
685
|
}
|
|
417
686
|
const runOutput = await this.getRunOutput(eventId);
|
|
418
687
|
const result = runOutput?.output?.result;
|
|
419
|
-
|
|
420
|
-
result.error = new Error(result.error);
|
|
421
|
-
}
|
|
688
|
+
this.hydrateFailedResult(result);
|
|
422
689
|
if (result.status !== "suspended") {
|
|
423
690
|
this.cleanup?.();
|
|
424
691
|
}
|
|
@@ -477,9 +744,7 @@ var InngestRun = class extends workflows.Run {
|
|
|
477
744
|
}
|
|
478
745
|
const runOutput = await this.getRunOutput(eventId);
|
|
479
746
|
const result = runOutput?.output?.result;
|
|
480
|
-
|
|
481
|
-
result.error = new Error(result.error);
|
|
482
|
-
}
|
|
747
|
+
this.hydrateFailedResult(result);
|
|
483
748
|
return result;
|
|
484
749
|
}
|
|
485
750
|
async timeTravel(params) {
|
|
@@ -569,9 +834,7 @@ var InngestRun = class extends workflows.Run {
|
|
|
569
834
|
}
|
|
570
835
|
const runOutput = await this.getRunOutput(eventId);
|
|
571
836
|
const result = runOutput?.output?.result;
|
|
572
|
-
|
|
573
|
-
result.error = new Error(result.error);
|
|
574
|
-
}
|
|
837
|
+
this.hydrateFailedResult(result);
|
|
575
838
|
return result;
|
|
576
839
|
}
|
|
577
840
|
watch(cb) {
|
|
@@ -792,6 +1055,18 @@ var InngestRun = class extends workflows.Run {
|
|
|
792
1055
|
});
|
|
793
1056
|
return this.streamOutput;
|
|
794
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
|
+
}
|
|
795
1070
|
};
|
|
796
1071
|
|
|
797
1072
|
// src/workflow.ts
|
|
@@ -829,6 +1104,7 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
829
1104
|
return run ?? (this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null);
|
|
830
1105
|
}
|
|
831
1106
|
__registerMastra(mastra) {
|
|
1107
|
+
super.__registerMastra(mastra);
|
|
832
1108
|
this.#mastra = mastra;
|
|
833
1109
|
this.executionEngine.__registerMastra(mastra);
|
|
834
1110
|
const updateNested = (step) => {
|
|
@@ -847,7 +1123,7 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
847
1123
|
}
|
|
848
1124
|
}
|
|
849
1125
|
async createRun(options) {
|
|
850
|
-
const runIdToUse = options?.runId || crypto.randomUUID();
|
|
1126
|
+
const runIdToUse = options?.runId || crypto$1.randomUUID();
|
|
851
1127
|
const run = this.runs.get(runIdToUse) ?? new InngestRun(
|
|
852
1128
|
{
|
|
853
1129
|
workflowId: this.id,
|
|
@@ -912,31 +1188,10 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
912
1188
|
let { inputData, initialState, runId, resourceId, resume, outputOptions, format, timeTravel } = event.data;
|
|
913
1189
|
if (!runId) {
|
|
914
1190
|
runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
|
|
915
|
-
return crypto.randomUUID();
|
|
1191
|
+
return crypto$1.randomUUID();
|
|
916
1192
|
});
|
|
917
1193
|
}
|
|
918
|
-
const
|
|
919
|
-
emit: async (event2, data) => {
|
|
920
|
-
if (!publish) {
|
|
921
|
-
return;
|
|
922
|
-
}
|
|
923
|
-
try {
|
|
924
|
-
await publish({
|
|
925
|
-
channel: `workflow:${this.id}:${runId}`,
|
|
926
|
-
topic: event2,
|
|
927
|
-
data
|
|
928
|
-
});
|
|
929
|
-
} catch (err) {
|
|
930
|
-
this.logger.error("Error emitting event: " + (err?.stack ?? err?.message ?? err));
|
|
931
|
-
}
|
|
932
|
-
},
|
|
933
|
-
on: (_event, _callback) => {
|
|
934
|
-
},
|
|
935
|
-
off: (_event, _callback) => {
|
|
936
|
-
},
|
|
937
|
-
once: (_event, _callback) => {
|
|
938
|
-
}
|
|
939
|
-
};
|
|
1194
|
+
const pubsub = new InngestPubSub(this.inngest, this.id, publish);
|
|
940
1195
|
const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
|
|
941
1196
|
const result = await engine.execute({
|
|
942
1197
|
workflowId: this.id,
|
|
@@ -946,7 +1201,7 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
946
1201
|
serializedStepGraph: this.serializedStepGraph,
|
|
947
1202
|
input: inputData,
|
|
948
1203
|
initialState,
|
|
949
|
-
|
|
1204
|
+
pubsub,
|
|
950
1205
|
retryConfig: this.retryConfig,
|
|
951
1206
|
requestContext: new di.RequestContext(Object.entries(event.data.requestContext ?? {})),
|
|
952
1207
|
resume,
|
|
@@ -956,11 +1211,19 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
956
1211
|
// currentSpan: undefined, // TODO: Pass actual parent Span from workflow execution context
|
|
957
1212
|
outputOptions,
|
|
958
1213
|
outputWriter: async (chunk) => {
|
|
959
|
-
|
|
960
|
-
|
|
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
|
+
}
|
|
961
1223
|
}
|
|
962
1224
|
});
|
|
963
1225
|
await step.run(`workflow.${this.id}.finalize`, async () => {
|
|
1226
|
+
await engine.invokeLifecycleCallbacksInternal(result);
|
|
964
1227
|
if (result.status === "failed") {
|
|
965
1228
|
throw new inngest.NonRetriableError(`Workflow failed`, {
|
|
966
1229
|
cause: result
|
|
@@ -1045,7 +1308,8 @@ function createStep(params, agentOptions) {
|
|
|
1045
1308
|
outputSchema,
|
|
1046
1309
|
execute: async ({
|
|
1047
1310
|
inputData,
|
|
1048
|
-
|
|
1311
|
+
runId,
|
|
1312
|
+
[_constants.PUBSUB_SYMBOL]: pubsub,
|
|
1049
1313
|
[_constants.STREAM_FORMAT_SYMBOL]: streamFormat,
|
|
1050
1314
|
requestContext,
|
|
1051
1315
|
tracingContext,
|
|
@@ -1100,22 +1364,24 @@ function createStep(params, agentOptions) {
|
|
|
1100
1364
|
stream = modelOutput.fullStream;
|
|
1101
1365
|
}
|
|
1102
1366
|
if (streamFormat === "legacy") {
|
|
1103
|
-
await
|
|
1104
|
-
type: "
|
|
1105
|
-
|
|
1367
|
+
await pubsub.publish(`workflow.events.v2.${runId}`, {
|
|
1368
|
+
type: "watch",
|
|
1369
|
+
runId,
|
|
1370
|
+
data: { type: "tool-call-streaming-start", ...toolData ?? {} }
|
|
1106
1371
|
});
|
|
1107
1372
|
for await (const chunk of stream) {
|
|
1108
1373
|
if (chunk.type === "text-delta") {
|
|
1109
|
-
await
|
|
1110
|
-
type: "
|
|
1111
|
-
|
|
1112
|
-
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 }
|
|
1113
1378
|
});
|
|
1114
1379
|
}
|
|
1115
1380
|
}
|
|
1116
|
-
await
|
|
1117
|
-
type: "
|
|
1118
|
-
|
|
1381
|
+
await pubsub.publish(`workflow.events.v2.${runId}`, {
|
|
1382
|
+
type: "watch",
|
|
1383
|
+
runId,
|
|
1384
|
+
data: { type: "tool-call-streaming-finish", ...toolData ?? {} }
|
|
1119
1385
|
});
|
|
1120
1386
|
} else {
|
|
1121
1387
|
for await (const chunk of stream) {
|
|
@@ -1228,6 +1494,7 @@ function init(inngest) {
|
|
|
1228
1494
|
}
|
|
1229
1495
|
|
|
1230
1496
|
exports.InngestExecutionEngine = InngestExecutionEngine;
|
|
1497
|
+
exports.InngestPubSub = InngestPubSub;
|
|
1231
1498
|
exports.InngestRun = InngestRun;
|
|
1232
1499
|
exports.InngestWorkflow = InngestWorkflow;
|
|
1233
1500
|
exports._compatibilityCheck = _compatibilityCheck;
|