@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.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { Tool } from '@mastra/core/tools';
|
|
2
|
-
import { DefaultExecutionEngine, createTimeTravelExecutionParams, Run, Workflow } from '@mastra/core/workflows';
|
|
3
|
-
import {
|
|
2
|
+
import { DefaultExecutionEngine, createTimeTravelExecutionParams, Run, hydrateSerializedStepErrors, Workflow } from '@mastra/core/workflows';
|
|
3
|
+
import { PUBSUB_SYMBOL, STREAM_FORMAT_SYMBOL } from '@mastra/core/workflows/_constants';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { randomUUID } from 'crypto';
|
|
6
6
|
import { RequestContext } from '@mastra/core/di';
|
|
7
7
|
import { RetryAfterError, NonRetriableError } from 'inngest';
|
|
8
|
-
import {
|
|
8
|
+
import { getErrorFromUnknown } from '@mastra/core/error';
|
|
9
9
|
import { subscribe } from '@inngest/realtime';
|
|
10
|
+
import { PubSub } from '@mastra/core/events';
|
|
11
|
+
import { ReadableStream } from 'stream/web';
|
|
10
12
|
import { ChunkFrom, WorkflowRunOutput } from '@mastra/core/stream';
|
|
11
13
|
import { serve as serve$1 } from 'inngest/hono';
|
|
12
14
|
|
|
@@ -23,17 +25,18 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
|
|
|
23
25
|
// Hook Overrides
|
|
24
26
|
// =============================================================================
|
|
25
27
|
/**
|
|
26
|
-
* Format errors
|
|
28
|
+
* Format errors while preserving Error instances and their custom properties.
|
|
29
|
+
* Uses getErrorFromUnknown to ensure all error properties are preserved.
|
|
27
30
|
*/
|
|
28
31
|
formatResultError(error, lastOutput) {
|
|
29
|
-
if (error instanceof Error) {
|
|
30
|
-
return error.stack ?? error.message;
|
|
31
|
-
}
|
|
32
32
|
const outputError = lastOutput?.error;
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
const errorSource = error || outputError;
|
|
34
|
+
const errorInstance = getErrorFromUnknown(errorSource, {
|
|
35
|
+
serializeStack: true,
|
|
36
|
+
// Include stack in JSON for better debugging in Inngest
|
|
37
|
+
fallbackMessage: "Unknown workflow error"
|
|
38
|
+
});
|
|
39
|
+
return errorInstance.toJSON();
|
|
37
40
|
}
|
|
38
41
|
/**
|
|
39
42
|
* Detect InngestWorkflow instances for special nested workflow handling
|
|
@@ -65,18 +68,24 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
|
|
|
65
68
|
error: e,
|
|
66
69
|
attributes: { status: "failed" }
|
|
67
70
|
});
|
|
71
|
+
if (cause.error && !(cause.error instanceof Error)) {
|
|
72
|
+
cause.error = getErrorFromUnknown(cause.error, { serializeStack: false });
|
|
73
|
+
}
|
|
68
74
|
return { ok: false, error: cause };
|
|
69
75
|
}
|
|
70
|
-
const
|
|
76
|
+
const errorInstance = getErrorFromUnknown(e, {
|
|
77
|
+
serializeStack: false,
|
|
78
|
+
fallbackMessage: "Unknown step execution error"
|
|
79
|
+
});
|
|
71
80
|
params.stepSpan?.error({
|
|
72
|
-
error:
|
|
81
|
+
error: errorInstance,
|
|
73
82
|
attributes: { status: "failed" }
|
|
74
83
|
});
|
|
75
84
|
return {
|
|
76
85
|
ok: false,
|
|
77
86
|
error: {
|
|
78
87
|
status: "failed",
|
|
79
|
-
error:
|
|
88
|
+
error: errorInstance,
|
|
80
89
|
endedAt: Date.now()
|
|
81
90
|
}
|
|
82
91
|
};
|
|
@@ -105,11 +114,14 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
|
|
|
105
114
|
return await operationFn();
|
|
106
115
|
} catch (e) {
|
|
107
116
|
if (retryConfig) {
|
|
108
|
-
const
|
|
109
|
-
|
|
117
|
+
const errorInstance = getErrorFromUnknown(e, {
|
|
118
|
+
serializeStack: false,
|
|
119
|
+
fallbackMessage: "Unknown step execution error"
|
|
120
|
+
});
|
|
121
|
+
throw new RetryAfterError(errorInstance.message, retryConfig.delay, {
|
|
110
122
|
cause: {
|
|
111
123
|
status: "failed",
|
|
112
|
-
error:
|
|
124
|
+
error: errorInstance,
|
|
113
125
|
endedAt: Date.now()
|
|
114
126
|
}
|
|
115
127
|
});
|
|
@@ -124,6 +136,18 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
|
|
|
124
136
|
getEngineContext() {
|
|
125
137
|
return { step: this.inngestStep };
|
|
126
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* For Inngest, lifecycle callbacks are invoked in the workflow's finalize step
|
|
141
|
+
* (wrapped in step.run for durability), not in execute(). Override to skip.
|
|
142
|
+
*/
|
|
143
|
+
async invokeLifecycleCallbacks(_result) {
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Actually invoke the lifecycle callbacks. Called from workflow.ts finalize step.
|
|
147
|
+
*/
|
|
148
|
+
async invokeLifecycleCallbacksInternal(result) {
|
|
149
|
+
return super.invokeLifecycleCallbacks(result);
|
|
150
|
+
}
|
|
127
151
|
/**
|
|
128
152
|
* Execute nested InngestWorkflow using inngestStep.invoke() for durability.
|
|
129
153
|
* This MUST be called directly (not inside step.run()) due to Inngest constraints.
|
|
@@ -132,7 +156,7 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
|
|
|
132
156
|
if (!(params.step instanceof InngestWorkflow)) {
|
|
133
157
|
return null;
|
|
134
158
|
}
|
|
135
|
-
const { step, stepResults, executionContext, resume, timeTravel, prevOutput, inputData,
|
|
159
|
+
const { step, stepResults, executionContext, resume, timeTravel, prevOutput, inputData, pubsub, startedAt } = params;
|
|
136
160
|
const isResume = !!resume?.steps?.length;
|
|
137
161
|
let result;
|
|
138
162
|
let runId;
|
|
@@ -221,13 +245,17 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
|
|
|
221
245
|
`workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
|
|
222
246
|
async () => {
|
|
223
247
|
if (result.status === "failed") {
|
|
224
|
-
await
|
|
225
|
-
type: "
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
248
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
249
|
+
type: "watch",
|
|
250
|
+
runId: executionContext.runId,
|
|
251
|
+
data: {
|
|
252
|
+
type: "workflow-step-result",
|
|
253
|
+
payload: {
|
|
254
|
+
id: step.id,
|
|
255
|
+
status: "failed",
|
|
256
|
+
error: result?.error,
|
|
257
|
+
payload: prevOutput
|
|
258
|
+
}
|
|
231
259
|
}
|
|
232
260
|
});
|
|
233
261
|
return { executionContext, result: { status: "failed", error: result?.error } };
|
|
@@ -239,11 +267,15 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
|
|
|
239
267
|
for (const [stepName, stepResult] of suspendedSteps) {
|
|
240
268
|
const suspendPath = [stepName, ...stepResult?.suspendPayload?.__workflow_meta?.path ?? []];
|
|
241
269
|
executionContext.suspendedPaths[step.id] = executionContext.executionPath;
|
|
242
|
-
await
|
|
243
|
-
type: "
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
270
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
271
|
+
type: "watch",
|
|
272
|
+
runId: executionContext.runId,
|
|
273
|
+
data: {
|
|
274
|
+
type: "workflow-step-suspended",
|
|
275
|
+
payload: {
|
|
276
|
+
id: step.id,
|
|
277
|
+
status: "suspended"
|
|
278
|
+
}
|
|
247
279
|
}
|
|
248
280
|
});
|
|
249
281
|
return {
|
|
@@ -265,20 +297,49 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
|
|
|
265
297
|
payload: {}
|
|
266
298
|
}
|
|
267
299
|
};
|
|
300
|
+
} else if (result.status === "tripwire") {
|
|
301
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
302
|
+
type: "watch",
|
|
303
|
+
runId: executionContext.runId,
|
|
304
|
+
data: {
|
|
305
|
+
type: "workflow-step-result",
|
|
306
|
+
payload: {
|
|
307
|
+
id: step.id,
|
|
308
|
+
status: "tripwire",
|
|
309
|
+
error: result?.tripwire?.reason,
|
|
310
|
+
payload: prevOutput
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
return {
|
|
315
|
+
executionContext,
|
|
316
|
+
result: {
|
|
317
|
+
status: "tripwire",
|
|
318
|
+
tripwire: result?.tripwire
|
|
319
|
+
}
|
|
320
|
+
};
|
|
268
321
|
}
|
|
269
|
-
await
|
|
270
|
-
type: "
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
322
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
323
|
+
type: "watch",
|
|
324
|
+
runId: executionContext.runId,
|
|
325
|
+
data: {
|
|
326
|
+
type: "workflow-step-result",
|
|
327
|
+
payload: {
|
|
328
|
+
id: step.id,
|
|
329
|
+
status: "success",
|
|
330
|
+
output: result?.result
|
|
331
|
+
}
|
|
275
332
|
}
|
|
276
333
|
});
|
|
277
|
-
await
|
|
278
|
-
type: "
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
334
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
335
|
+
type: "watch",
|
|
336
|
+
runId: executionContext.runId,
|
|
337
|
+
data: {
|
|
338
|
+
type: "workflow-step-finish",
|
|
339
|
+
payload: {
|
|
340
|
+
id: step.id,
|
|
341
|
+
metadata: {}
|
|
342
|
+
}
|
|
282
343
|
}
|
|
283
344
|
});
|
|
284
345
|
return { executionContext, result: { status: "success", output: result?.result } };
|
|
@@ -295,6 +356,118 @@ var InngestExecutionEngine = class extends DefaultExecutionEngine {
|
|
|
295
356
|
};
|
|
296
357
|
}
|
|
297
358
|
};
|
|
359
|
+
var InngestPubSub = class extends PubSub {
|
|
360
|
+
inngest;
|
|
361
|
+
workflowId;
|
|
362
|
+
publishFn;
|
|
363
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
364
|
+
constructor(inngest, workflowId, publishFn) {
|
|
365
|
+
super();
|
|
366
|
+
this.inngest = inngest;
|
|
367
|
+
this.workflowId = workflowId;
|
|
368
|
+
this.publishFn = publishFn;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Publish an event to Inngest's realtime system.
|
|
372
|
+
*
|
|
373
|
+
* Topic format: "workflow.events.v2.{runId}"
|
|
374
|
+
* Maps to Inngest channel: "workflow:{workflowId}:{runId}"
|
|
375
|
+
*/
|
|
376
|
+
async publish(topic, event) {
|
|
377
|
+
if (!this.publishFn) {
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
const match = topic.match(/^workflow\.events\.v2\.(.+)$/);
|
|
381
|
+
if (!match) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
const runId = match[1];
|
|
385
|
+
try {
|
|
386
|
+
await this.publishFn({
|
|
387
|
+
channel: `workflow:${this.workflowId}:${runId}`,
|
|
388
|
+
topic: "watch",
|
|
389
|
+
data: event.data
|
|
390
|
+
});
|
|
391
|
+
} catch (err) {
|
|
392
|
+
console.error("InngestPubSub publish error:", err?.message ?? err);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Subscribe to events from Inngest's realtime system.
|
|
397
|
+
*
|
|
398
|
+
* Topic format: "workflow.events.v2.{runId}"
|
|
399
|
+
* Maps to Inngest channel: "workflow:{workflowId}:{runId}"
|
|
400
|
+
*/
|
|
401
|
+
async subscribe(topic, cb) {
|
|
402
|
+
const match = topic.match(/^workflow\.events\.v2\.(.+)$/);
|
|
403
|
+
if (!match || !match[1]) {
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const runId = match[1];
|
|
407
|
+
if (this.subscriptions.has(topic)) {
|
|
408
|
+
this.subscriptions.get(topic).callbacks.add(cb);
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
const callbacks = /* @__PURE__ */ new Set([cb]);
|
|
412
|
+
const channel = `workflow:${this.workflowId}:${runId}`;
|
|
413
|
+
const streamPromise = subscribe(
|
|
414
|
+
{
|
|
415
|
+
channel,
|
|
416
|
+
topics: ["watch"],
|
|
417
|
+
app: this.inngest
|
|
418
|
+
},
|
|
419
|
+
(message) => {
|
|
420
|
+
const event = {
|
|
421
|
+
id: crypto.randomUUID(),
|
|
422
|
+
type: "watch",
|
|
423
|
+
runId,
|
|
424
|
+
data: message.data,
|
|
425
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
426
|
+
};
|
|
427
|
+
for (const callback of callbacks) {
|
|
428
|
+
callback(event);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
);
|
|
432
|
+
this.subscriptions.set(topic, {
|
|
433
|
+
unsubscribe: () => {
|
|
434
|
+
streamPromise.then((stream) => stream.cancel()).catch((err) => {
|
|
435
|
+
console.error("InngestPubSub unsubscribe error:", err);
|
|
436
|
+
});
|
|
437
|
+
},
|
|
438
|
+
callbacks
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Unsubscribe a callback from a topic.
|
|
443
|
+
* If no callbacks remain, the underlying Inngest subscription is cancelled.
|
|
444
|
+
*/
|
|
445
|
+
async unsubscribe(topic, cb) {
|
|
446
|
+
const sub = this.subscriptions.get(topic);
|
|
447
|
+
if (!sub) {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
sub.callbacks.delete(cb);
|
|
451
|
+
if (sub.callbacks.size === 0) {
|
|
452
|
+
sub.unsubscribe();
|
|
453
|
+
this.subscriptions.delete(topic);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Flush any pending operations. No-op for Inngest.
|
|
458
|
+
*/
|
|
459
|
+
async flush() {
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Clean up all subscriptions during graceful shutdown.
|
|
463
|
+
*/
|
|
464
|
+
async close() {
|
|
465
|
+
for (const [, sub] of this.subscriptions) {
|
|
466
|
+
sub.unsubscribe();
|
|
467
|
+
}
|
|
468
|
+
this.subscriptions.clear();
|
|
469
|
+
}
|
|
470
|
+
};
|
|
298
471
|
var InngestRun = class extends Run {
|
|
299
472
|
inngest;
|
|
300
473
|
serializedStepGraph;
|
|
@@ -306,27 +479,77 @@ var InngestRun = class extends Run {
|
|
|
306
479
|
this.#mastra = params.mastra;
|
|
307
480
|
}
|
|
308
481
|
async getRuns(eventId) {
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
482
|
+
const maxRetries = 3;
|
|
483
|
+
let lastError = null;
|
|
484
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
485
|
+
try {
|
|
486
|
+
const response = await fetch(
|
|
487
|
+
`${this.inngest.apiBaseUrl ?? "https://api.inngest.com"}/v1/events/${eventId}/runs`,
|
|
488
|
+
{
|
|
489
|
+
headers: {
|
|
490
|
+
Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
);
|
|
494
|
+
if (response.status === 429) {
|
|
495
|
+
const retryAfter = parseInt(response.headers.get("retry-after") || "2", 10);
|
|
496
|
+
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1e3));
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
if (!response.ok) {
|
|
500
|
+
throw new Error(`Inngest API error: ${response.status} ${response.statusText}`);
|
|
501
|
+
}
|
|
502
|
+
const text = await response.text();
|
|
503
|
+
if (!text) {
|
|
504
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3 * (attempt + 1)));
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
const json = JSON.parse(text);
|
|
508
|
+
return json.data;
|
|
509
|
+
} catch (error) {
|
|
510
|
+
lastError = error;
|
|
511
|
+
if (attempt < maxRetries - 1) {
|
|
512
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3 * Math.pow(2, attempt)));
|
|
513
|
+
}
|
|
312
514
|
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return json.data;
|
|
515
|
+
}
|
|
516
|
+
throw new NonRetriableError(`Failed to get runs after ${maxRetries} attempts: ${lastError?.message}`);
|
|
316
517
|
}
|
|
317
|
-
async getRunOutput(eventId) {
|
|
318
|
-
|
|
518
|
+
async getRunOutput(eventId, maxWaitMs = 3e5) {
|
|
519
|
+
const startTime = Date.now();
|
|
319
520
|
const storage = this.#mastra?.getStorage();
|
|
320
|
-
while (
|
|
321
|
-
|
|
322
|
-
|
|
521
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
522
|
+
let runs;
|
|
523
|
+
try {
|
|
524
|
+
runs = await this.getRuns(eventId);
|
|
525
|
+
} catch (error) {
|
|
526
|
+
if (error instanceof NonRetriableError) {
|
|
527
|
+
throw error;
|
|
528
|
+
}
|
|
529
|
+
throw new NonRetriableError(
|
|
530
|
+
`Failed to poll workflow status: ${error instanceof Error ? error.message : String(error)}`
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
if (runs?.[0]?.status === "Completed" && runs?.[0]?.event_id === eventId) {
|
|
534
|
+
return runs[0];
|
|
535
|
+
}
|
|
323
536
|
if (runs?.[0]?.status === "Failed") {
|
|
324
537
|
const snapshot = await storage?.loadWorkflowSnapshot({
|
|
325
538
|
workflowName: this.workflowId,
|
|
326
539
|
runId: this.runId
|
|
327
540
|
});
|
|
541
|
+
if (snapshot?.context) {
|
|
542
|
+
snapshot.context = hydrateSerializedStepErrors(snapshot.context);
|
|
543
|
+
}
|
|
328
544
|
return {
|
|
329
|
-
output: {
|
|
545
|
+
output: {
|
|
546
|
+
result: {
|
|
547
|
+
steps: snapshot?.context,
|
|
548
|
+
status: "failed",
|
|
549
|
+
// Get the original error from NonRetriableError's cause (which contains the workflow result)
|
|
550
|
+
error: getErrorFromUnknown(runs?.[0]?.output?.cause?.error, { serializeStack: false })
|
|
551
|
+
}
|
|
552
|
+
}
|
|
330
553
|
};
|
|
331
554
|
}
|
|
332
555
|
if (runs?.[0]?.status === "Cancelled") {
|
|
@@ -336,8 +559,9 @@ var InngestRun = class extends Run {
|
|
|
336
559
|
});
|
|
337
560
|
return { output: { result: { steps: snapshot?.context, status: "canceled" } } };
|
|
338
561
|
}
|
|
562
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3 + Math.random() * 1e3));
|
|
339
563
|
}
|
|
340
|
-
|
|
564
|
+
throw new NonRetriableError(`Workflow did not complete within ${maxWaitMs}ms`);
|
|
341
565
|
}
|
|
342
566
|
async cancel() {
|
|
343
567
|
const storage = this.#mastra?.getStorage();
|
|
@@ -367,6 +591,51 @@ var InngestRun = class extends Run {
|
|
|
367
591
|
async start(params) {
|
|
368
592
|
return this._start(params);
|
|
369
593
|
}
|
|
594
|
+
/**
|
|
595
|
+
* Starts the workflow execution without waiting for completion (fire-and-forget).
|
|
596
|
+
* Returns immediately with the runId after sending the event to Inngest.
|
|
597
|
+
* The workflow executes independently in Inngest.
|
|
598
|
+
* Use this when you don't need to wait for the result or want to avoid polling failures.
|
|
599
|
+
*/
|
|
600
|
+
async startAsync(params) {
|
|
601
|
+
await this.#mastra.getStorage()?.persistWorkflowSnapshot({
|
|
602
|
+
workflowName: this.workflowId,
|
|
603
|
+
runId: this.runId,
|
|
604
|
+
resourceId: this.resourceId,
|
|
605
|
+
snapshot: {
|
|
606
|
+
runId: this.runId,
|
|
607
|
+
serializedStepGraph: this.serializedStepGraph,
|
|
608
|
+
status: "running",
|
|
609
|
+
value: {},
|
|
610
|
+
context: {},
|
|
611
|
+
activePaths: [],
|
|
612
|
+
suspendedPaths: {},
|
|
613
|
+
activeStepsPath: {},
|
|
614
|
+
resumeLabels: {},
|
|
615
|
+
waitingPaths: {},
|
|
616
|
+
timestamp: Date.now()
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
const inputDataToUse = await this._validateInput(params.inputData);
|
|
620
|
+
const initialStateToUse = await this._validateInitialState(params.initialState ?? {});
|
|
621
|
+
const eventOutput = await this.inngest.send({
|
|
622
|
+
name: `workflow.${this.workflowId}`,
|
|
623
|
+
data: {
|
|
624
|
+
inputData: inputDataToUse,
|
|
625
|
+
initialState: initialStateToUse,
|
|
626
|
+
runId: this.runId,
|
|
627
|
+
resourceId: this.resourceId,
|
|
628
|
+
outputOptions: params.outputOptions,
|
|
629
|
+
tracingOptions: params.tracingOptions,
|
|
630
|
+
requestContext: params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {}
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
const eventId = eventOutput.ids[0];
|
|
634
|
+
if (!eventId) {
|
|
635
|
+
throw new Error("Event ID is not set");
|
|
636
|
+
}
|
|
637
|
+
return { runId: this.runId };
|
|
638
|
+
}
|
|
370
639
|
async _start({
|
|
371
640
|
inputData,
|
|
372
641
|
initialState,
|
|
@@ -414,9 +683,7 @@ var InngestRun = class extends Run {
|
|
|
414
683
|
}
|
|
415
684
|
const runOutput = await this.getRunOutput(eventId);
|
|
416
685
|
const result = runOutput?.output?.result;
|
|
417
|
-
|
|
418
|
-
result.error = new Error(result.error);
|
|
419
|
-
}
|
|
686
|
+
this.hydrateFailedResult(result);
|
|
420
687
|
if (result.status !== "suspended") {
|
|
421
688
|
this.cleanup?.();
|
|
422
689
|
}
|
|
@@ -475,9 +742,7 @@ var InngestRun = class extends Run {
|
|
|
475
742
|
}
|
|
476
743
|
const runOutput = await this.getRunOutput(eventId);
|
|
477
744
|
const result = runOutput?.output?.result;
|
|
478
|
-
|
|
479
|
-
result.error = new Error(result.error);
|
|
480
|
-
}
|
|
745
|
+
this.hydrateFailedResult(result);
|
|
481
746
|
return result;
|
|
482
747
|
}
|
|
483
748
|
async timeTravel(params) {
|
|
@@ -567,9 +832,7 @@ var InngestRun = class extends Run {
|
|
|
567
832
|
}
|
|
568
833
|
const runOutput = await this.getRunOutput(eventId);
|
|
569
834
|
const result = runOutput?.output?.result;
|
|
570
|
-
|
|
571
|
-
result.error = new Error(result.error);
|
|
572
|
-
}
|
|
835
|
+
this.hydrateFailedResult(result);
|
|
573
836
|
return result;
|
|
574
837
|
}
|
|
575
838
|
watch(cb) {
|
|
@@ -790,6 +1053,18 @@ var InngestRun = class extends Run {
|
|
|
790
1053
|
});
|
|
791
1054
|
return this.streamOutput;
|
|
792
1055
|
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Hydrates errors in a failed workflow result back to proper Error instances.
|
|
1058
|
+
* This ensures error.cause chains and custom properties are preserved.
|
|
1059
|
+
*/
|
|
1060
|
+
hydrateFailedResult(result) {
|
|
1061
|
+
if (result.status === "failed") {
|
|
1062
|
+
result.error = getErrorFromUnknown(result.error, { serializeStack: false });
|
|
1063
|
+
if (result.steps) {
|
|
1064
|
+
hydrateSerializedStepErrors(result.steps);
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
793
1068
|
};
|
|
794
1069
|
|
|
795
1070
|
// src/workflow.ts
|
|
@@ -827,6 +1102,7 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
|
|
|
827
1102
|
return run ?? (this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null);
|
|
828
1103
|
}
|
|
829
1104
|
__registerMastra(mastra) {
|
|
1105
|
+
super.__registerMastra(mastra);
|
|
830
1106
|
this.#mastra = mastra;
|
|
831
1107
|
this.executionEngine.__registerMastra(mastra);
|
|
832
1108
|
const updateNested = (step) => {
|
|
@@ -913,28 +1189,7 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
|
|
|
913
1189
|
return randomUUID();
|
|
914
1190
|
});
|
|
915
1191
|
}
|
|
916
|
-
const
|
|
917
|
-
emit: async (event2, data) => {
|
|
918
|
-
if (!publish) {
|
|
919
|
-
return;
|
|
920
|
-
}
|
|
921
|
-
try {
|
|
922
|
-
await publish({
|
|
923
|
-
channel: `workflow:${this.id}:${runId}`,
|
|
924
|
-
topic: event2,
|
|
925
|
-
data
|
|
926
|
-
});
|
|
927
|
-
} catch (err) {
|
|
928
|
-
this.logger.error("Error emitting event: " + (err?.stack ?? err?.message ?? err));
|
|
929
|
-
}
|
|
930
|
-
},
|
|
931
|
-
on: (_event, _callback) => {
|
|
932
|
-
},
|
|
933
|
-
off: (_event, _callback) => {
|
|
934
|
-
},
|
|
935
|
-
once: (_event, _callback) => {
|
|
936
|
-
}
|
|
937
|
-
};
|
|
1192
|
+
const pubsub = new InngestPubSub(this.inngest, this.id, publish);
|
|
938
1193
|
const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
|
|
939
1194
|
const result = await engine.execute({
|
|
940
1195
|
workflowId: this.id,
|
|
@@ -944,7 +1199,7 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
|
|
|
944
1199
|
serializedStepGraph: this.serializedStepGraph,
|
|
945
1200
|
input: inputData,
|
|
946
1201
|
initialState,
|
|
947
|
-
|
|
1202
|
+
pubsub,
|
|
948
1203
|
retryConfig: this.retryConfig,
|
|
949
1204
|
requestContext: new RequestContext(Object.entries(event.data.requestContext ?? {})),
|
|
950
1205
|
resume,
|
|
@@ -954,11 +1209,19 @@ var InngestWorkflow = class _InngestWorkflow extends Workflow {
|
|
|
954
1209
|
// currentSpan: undefined, // TODO: Pass actual parent Span from workflow execution context
|
|
955
1210
|
outputOptions,
|
|
956
1211
|
outputWriter: async (chunk) => {
|
|
957
|
-
|
|
958
|
-
|
|
1212
|
+
try {
|
|
1213
|
+
await pubsub.publish(`workflow.events.v2.${runId}`, {
|
|
1214
|
+
type: "watch",
|
|
1215
|
+
runId,
|
|
1216
|
+
data: chunk
|
|
1217
|
+
});
|
|
1218
|
+
} catch (err) {
|
|
1219
|
+
this.logger.debug?.("Failed to publish watch event:", err);
|
|
1220
|
+
}
|
|
959
1221
|
}
|
|
960
1222
|
});
|
|
961
1223
|
await step.run(`workflow.${this.id}.finalize`, async () => {
|
|
1224
|
+
await engine.invokeLifecycleCallbacksInternal(result);
|
|
962
1225
|
if (result.status === "failed") {
|
|
963
1226
|
throw new NonRetriableError(`Workflow failed`, {
|
|
964
1227
|
cause: result
|
|
@@ -1043,7 +1306,8 @@ function createStep(params, agentOptions) {
|
|
|
1043
1306
|
outputSchema,
|
|
1044
1307
|
execute: async ({
|
|
1045
1308
|
inputData,
|
|
1046
|
-
|
|
1309
|
+
runId,
|
|
1310
|
+
[PUBSUB_SYMBOL]: pubsub,
|
|
1047
1311
|
[STREAM_FORMAT_SYMBOL]: streamFormat,
|
|
1048
1312
|
requestContext,
|
|
1049
1313
|
tracingContext,
|
|
@@ -1098,22 +1362,24 @@ function createStep(params, agentOptions) {
|
|
|
1098
1362
|
stream = modelOutput.fullStream;
|
|
1099
1363
|
}
|
|
1100
1364
|
if (streamFormat === "legacy") {
|
|
1101
|
-
await
|
|
1102
|
-
type: "
|
|
1103
|
-
|
|
1365
|
+
await pubsub.publish(`workflow.events.v2.${runId}`, {
|
|
1366
|
+
type: "watch",
|
|
1367
|
+
runId,
|
|
1368
|
+
data: { type: "tool-call-streaming-start", ...toolData ?? {} }
|
|
1104
1369
|
});
|
|
1105
1370
|
for await (const chunk of stream) {
|
|
1106
1371
|
if (chunk.type === "text-delta") {
|
|
1107
|
-
await
|
|
1108
|
-
type: "
|
|
1109
|
-
|
|
1110
|
-
argsTextDelta: chunk.textDelta
|
|
1372
|
+
await pubsub.publish(`workflow.events.v2.${runId}`, {
|
|
1373
|
+
type: "watch",
|
|
1374
|
+
runId,
|
|
1375
|
+
data: { type: "tool-call-delta", ...toolData ?? {}, argsTextDelta: chunk.textDelta }
|
|
1111
1376
|
});
|
|
1112
1377
|
}
|
|
1113
1378
|
}
|
|
1114
|
-
await
|
|
1115
|
-
type: "
|
|
1116
|
-
|
|
1379
|
+
await pubsub.publish(`workflow.events.v2.${runId}`, {
|
|
1380
|
+
type: "watch",
|
|
1381
|
+
runId,
|
|
1382
|
+
data: { type: "tool-call-streaming-finish", ...toolData ?? {} }
|
|
1117
1383
|
});
|
|
1118
1384
|
} else {
|
|
1119
1385
|
for await (const chunk of stream) {
|
|
@@ -1225,6 +1491,6 @@ function init(inngest) {
|
|
|
1225
1491
|
};
|
|
1226
1492
|
}
|
|
1227
1493
|
|
|
1228
|
-
export { InngestExecutionEngine, InngestRun, InngestWorkflow, _compatibilityCheck, createStep, init, serve };
|
|
1494
|
+
export { InngestExecutionEngine, InngestPubSub, InngestRun, InngestWorkflow, _compatibilityCheck, createStep, init, serve };
|
|
1229
1495
|
//# sourceMappingURL=index.js.map
|
|
1230
1496
|
//# sourceMappingURL=index.js.map
|