@mastra/inngest 0.0.0-cloud-deployer-for-core-0.19.1-20251001164939 → 0.0.0-cloud-604-map-nested-flow-details-to-side-panel-20251212192149
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 +790 -3
- package/dist/execution-engine.d.ts +88 -0
- package/dist/execution-engine.d.ts.map +1 -0
- package/dist/index.cjs +1085 -991
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +33 -281
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1083 -991
- 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 +167 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/serve.d.ts +13 -0
- package/dist/serve.d.ts.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/workflow.d.ts +51 -0
- package/dist/workflow.d.ts.map +1 -0
- package/package.json +21 -16
package/dist/index.cjs
CHANGED
|
@@ -1,40 +1,463 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var crypto = require('crypto');
|
|
4
|
-
var realtime = require('@inngest/realtime');
|
|
5
|
-
var aiTracing = require('@mastra/core/ai-tracing');
|
|
6
|
-
var di = require('@mastra/core/di');
|
|
7
3
|
var tools = require('@mastra/core/tools');
|
|
8
4
|
var workflows = require('@mastra/core/workflows');
|
|
9
5
|
var _constants = require('@mastra/core/workflows/_constants');
|
|
10
|
-
var hono = require('inngest/hono');
|
|
11
6
|
var zod = require('zod');
|
|
7
|
+
var crypto$1 = require('crypto');
|
|
8
|
+
var di = require('@mastra/core/di');
|
|
9
|
+
var inngest = require('inngest');
|
|
10
|
+
var error = require('@mastra/core/error');
|
|
11
|
+
var realtime = require('@inngest/realtime');
|
|
12
|
+
var events = require('@mastra/core/events');
|
|
13
|
+
var web = require('stream/web');
|
|
14
|
+
var stream = require('@mastra/core/stream');
|
|
15
|
+
var hono = require('inngest/hono');
|
|
12
16
|
|
|
13
17
|
// src/index.ts
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
|
|
19
|
+
inngestStep;
|
|
20
|
+
inngestAttempts;
|
|
21
|
+
constructor(mastra, inngestStep, inngestAttempts = 0, options) {
|
|
22
|
+
super({ mastra, options });
|
|
23
|
+
this.inngestStep = inngestStep;
|
|
24
|
+
this.inngestAttempts = inngestAttempts;
|
|
25
|
+
}
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// Hook Overrides
|
|
28
|
+
// =============================================================================
|
|
29
|
+
/**
|
|
30
|
+
* Format errors while preserving Error instances and their custom properties.
|
|
31
|
+
* Uses getErrorFromUnknown to ensure all error properties are preserved.
|
|
32
|
+
*/
|
|
33
|
+
formatResultError(error$1, lastOutput) {
|
|
34
|
+
const outputError = lastOutput?.error;
|
|
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();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Detect InngestWorkflow instances for special nested workflow handling
|
|
45
|
+
*/
|
|
46
|
+
isNestedWorkflowStep(step) {
|
|
47
|
+
return step instanceof InngestWorkflow;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Inngest requires requestContext serialization for memoization.
|
|
51
|
+
* When steps are replayed, the original function doesn't re-execute,
|
|
52
|
+
* so requestContext modifications must be captured and restored.
|
|
53
|
+
*/
|
|
54
|
+
requiresDurableContextSerialization() {
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Execute a step with retry logic for Inngest.
|
|
59
|
+
* Retries are handled via step-level retry (RetryAfterError thrown INSIDE step.run()).
|
|
60
|
+
* After retries exhausted, error propagates here and we return a failed result.
|
|
61
|
+
*/
|
|
62
|
+
async executeStepWithRetry(stepId, runStep, params) {
|
|
63
|
+
try {
|
|
64
|
+
const result = await this.wrapDurableOperation(stepId, runStep, { delay: params.delay });
|
|
65
|
+
return { ok: true, result };
|
|
66
|
+
} catch (e) {
|
|
67
|
+
const cause = e?.cause;
|
|
68
|
+
if (cause?.status === "failed") {
|
|
69
|
+
params.stepSpan?.error({
|
|
70
|
+
error: e,
|
|
71
|
+
attributes: { status: "failed" }
|
|
72
|
+
});
|
|
73
|
+
if (cause.error && !(cause.error instanceof Error)) {
|
|
74
|
+
cause.error = error.getErrorFromUnknown(cause.error, { serializeStack: false });
|
|
27
75
|
}
|
|
28
|
-
return
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
76
|
+
return { ok: false, error: cause };
|
|
77
|
+
}
|
|
78
|
+
const errorInstance = error.getErrorFromUnknown(e, {
|
|
79
|
+
serializeStack: false,
|
|
80
|
+
fallbackMessage: "Unknown step execution error"
|
|
81
|
+
});
|
|
82
|
+
params.stepSpan?.error({
|
|
83
|
+
error: errorInstance,
|
|
84
|
+
attributes: { status: "failed" }
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
ok: false,
|
|
88
|
+
error: {
|
|
89
|
+
status: "failed",
|
|
90
|
+
error: errorInstance,
|
|
91
|
+
endedAt: Date.now()
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Use Inngest's sleep primitive for durability
|
|
98
|
+
*/
|
|
99
|
+
async executeSleepDuration(duration, sleepId, workflowId) {
|
|
100
|
+
await this.inngestStep.sleep(`workflow.${workflowId}.sleep.${sleepId}`, duration < 0 ? 0 : duration);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Use Inngest's sleepUntil primitive for durability
|
|
104
|
+
*/
|
|
105
|
+
async executeSleepUntilDate(date, sleepUntilId, workflowId) {
|
|
106
|
+
await this.inngestStep.sleepUntil(`workflow.${workflowId}.sleepUntil.${sleepUntilId}`, date);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Wrap durable operations in Inngest step.run() for durability.
|
|
110
|
+
* If retryConfig is provided, throws RetryAfterError INSIDE step.run() to trigger
|
|
111
|
+
* Inngest's step-level retry mechanism (not function-level retry).
|
|
112
|
+
*/
|
|
113
|
+
async wrapDurableOperation(operationId, operationFn, retryConfig) {
|
|
114
|
+
return this.inngestStep.run(operationId, async () => {
|
|
115
|
+
try {
|
|
116
|
+
return await operationFn();
|
|
117
|
+
} catch (e) {
|
|
118
|
+
if (retryConfig) {
|
|
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, {
|
|
124
|
+
cause: {
|
|
125
|
+
status: "failed",
|
|
126
|
+
error: errorInstance,
|
|
127
|
+
endedAt: Date.now()
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
throw e;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Provide Inngest step primitive in engine context
|
|
137
|
+
*/
|
|
138
|
+
getEngineContext() {
|
|
139
|
+
return { step: this.inngestStep };
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Execute nested InngestWorkflow using inngestStep.invoke() for durability.
|
|
143
|
+
* This MUST be called directly (not inside step.run()) due to Inngest constraints.
|
|
144
|
+
*/
|
|
145
|
+
async executeWorkflowStep(params) {
|
|
146
|
+
if (!(params.step instanceof InngestWorkflow)) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
const { step, stepResults, executionContext, resume, timeTravel, prevOutput, inputData, pubsub, startedAt } = params;
|
|
150
|
+
const isResume = !!resume?.steps?.length;
|
|
151
|
+
let result;
|
|
152
|
+
let runId;
|
|
153
|
+
const isTimeTravel = !!(timeTravel && timeTravel.steps?.length > 1 && timeTravel.steps[0] === step.id);
|
|
154
|
+
try {
|
|
155
|
+
if (isResume) {
|
|
156
|
+
runId = stepResults[resume?.steps?.[0] ?? ""]?.suspendPayload?.__workflow_meta?.runId ?? crypto$1.randomUUID();
|
|
157
|
+
const snapshot = await this.mastra?.getStorage()?.loadWorkflowSnapshot({
|
|
158
|
+
workflowName: step.id,
|
|
159
|
+
runId
|
|
160
|
+
});
|
|
161
|
+
const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
|
|
162
|
+
function: step.getFunction(),
|
|
163
|
+
data: {
|
|
164
|
+
inputData,
|
|
165
|
+
initialState: executionContext.state ?? snapshot?.value ?? {},
|
|
166
|
+
runId,
|
|
167
|
+
resume: {
|
|
168
|
+
runId,
|
|
169
|
+
steps: resume.steps.slice(1),
|
|
170
|
+
stepResults: snapshot?.context,
|
|
171
|
+
resumePayload: resume.resumePayload,
|
|
172
|
+
resumePath: resume.steps?.[1] ? snapshot?.suspendedPaths?.[resume.steps?.[1]] : void 0
|
|
173
|
+
},
|
|
174
|
+
outputOptions: { includeState: true }
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
result = invokeResp.result;
|
|
178
|
+
runId = invokeResp.runId;
|
|
179
|
+
executionContext.state = invokeResp.result.state;
|
|
180
|
+
} else if (isTimeTravel) {
|
|
181
|
+
const snapshot = await this.mastra?.getStorage()?.loadWorkflowSnapshot({
|
|
182
|
+
workflowName: step.id,
|
|
183
|
+
runId: executionContext.runId
|
|
184
|
+
}) ?? { context: {} };
|
|
185
|
+
const timeTravelParams = workflows.createTimeTravelExecutionParams({
|
|
186
|
+
steps: timeTravel.steps.slice(1),
|
|
187
|
+
inputData: timeTravel.inputData,
|
|
188
|
+
resumeData: timeTravel.resumeData,
|
|
189
|
+
context: timeTravel.nestedStepResults?.[step.id] ?? {},
|
|
190
|
+
nestedStepsContext: timeTravel.nestedStepResults ?? {},
|
|
191
|
+
snapshot,
|
|
192
|
+
graph: step.buildExecutionGraph()
|
|
193
|
+
});
|
|
194
|
+
const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
|
|
195
|
+
function: step.getFunction(),
|
|
196
|
+
data: {
|
|
197
|
+
timeTravel: timeTravelParams,
|
|
198
|
+
initialState: executionContext.state ?? {},
|
|
199
|
+
runId: executionContext.runId,
|
|
200
|
+
outputOptions: { includeState: true }
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
result = invokeResp.result;
|
|
204
|
+
runId = invokeResp.runId;
|
|
205
|
+
executionContext.state = invokeResp.result.state;
|
|
206
|
+
} else {
|
|
207
|
+
const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
|
|
208
|
+
function: step.getFunction(),
|
|
209
|
+
data: {
|
|
210
|
+
inputData,
|
|
211
|
+
initialState: executionContext.state ?? {},
|
|
212
|
+
outputOptions: { includeState: true }
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
result = invokeResp.result;
|
|
216
|
+
runId = invokeResp.runId;
|
|
217
|
+
executionContext.state = invokeResp.result.state;
|
|
218
|
+
}
|
|
219
|
+
} catch (e) {
|
|
220
|
+
const errorCause = e?.cause;
|
|
221
|
+
if (errorCause && typeof errorCause === "object") {
|
|
222
|
+
result = errorCause;
|
|
223
|
+
runId = errorCause.runId || crypto$1.randomUUID();
|
|
224
|
+
} else {
|
|
225
|
+
runId = crypto$1.randomUUID();
|
|
226
|
+
result = {
|
|
227
|
+
status: "failed",
|
|
228
|
+
error: e instanceof Error ? e : new Error(String(e)),
|
|
229
|
+
steps: {},
|
|
230
|
+
input: inputData
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const res = await this.inngestStep.run(
|
|
235
|
+
`workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
|
|
236
|
+
async () => {
|
|
237
|
+
if (result.status === "failed") {
|
|
238
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
239
|
+
type: "watch",
|
|
240
|
+
runId: executionContext.runId,
|
|
241
|
+
data: {
|
|
242
|
+
type: "workflow-step-result",
|
|
243
|
+
payload: {
|
|
244
|
+
id: step.id,
|
|
245
|
+
status: "failed",
|
|
246
|
+
error: result?.error,
|
|
247
|
+
payload: prevOutput
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
return { executionContext, result: { status: "failed", error: result?.error } };
|
|
252
|
+
} else if (result.status === "suspended") {
|
|
253
|
+
const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
|
|
254
|
+
const stepRes = stepResult;
|
|
255
|
+
return stepRes?.status === "suspended";
|
|
256
|
+
});
|
|
257
|
+
for (const [stepName, stepResult] of suspendedSteps) {
|
|
258
|
+
const suspendPath = [stepName, ...stepResult?.suspendPayload?.__workflow_meta?.path ?? []];
|
|
259
|
+
executionContext.suspendedPaths[step.id] = executionContext.executionPath;
|
|
260
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
261
|
+
type: "watch",
|
|
262
|
+
runId: executionContext.runId,
|
|
263
|
+
data: {
|
|
264
|
+
type: "workflow-step-suspended",
|
|
265
|
+
payload: {
|
|
266
|
+
id: step.id,
|
|
267
|
+
status: "suspended"
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
return {
|
|
272
|
+
executionContext,
|
|
273
|
+
result: {
|
|
274
|
+
status: "suspended",
|
|
275
|
+
payload: stepResult.payload,
|
|
276
|
+
suspendPayload: {
|
|
277
|
+
...stepResult?.suspendPayload,
|
|
278
|
+
__workflow_meta: { runId, path: suspendPath }
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
executionContext,
|
|
285
|
+
result: {
|
|
286
|
+
status: "suspended",
|
|
287
|
+
payload: {}
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
} else if (result.status === "tripwire") {
|
|
291
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
292
|
+
type: "watch",
|
|
293
|
+
runId: executionContext.runId,
|
|
294
|
+
data: {
|
|
295
|
+
type: "workflow-step-result",
|
|
296
|
+
payload: {
|
|
297
|
+
id: step.id,
|
|
298
|
+
status: "tripwire",
|
|
299
|
+
error: result?.tripwire?.reason,
|
|
300
|
+
payload: prevOutput
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
return {
|
|
305
|
+
executionContext,
|
|
306
|
+
result: {
|
|
307
|
+
status: "tripwire",
|
|
308
|
+
tripwire: result?.tripwire
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
313
|
+
type: "watch",
|
|
314
|
+
runId: executionContext.runId,
|
|
315
|
+
data: {
|
|
316
|
+
type: "workflow-step-result",
|
|
317
|
+
payload: {
|
|
318
|
+
id: step.id,
|
|
319
|
+
status: "success",
|
|
320
|
+
output: result?.result
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
await pubsub.publish(`workflow.events.v2.${executionContext.runId}`, {
|
|
325
|
+
type: "watch",
|
|
326
|
+
runId: executionContext.runId,
|
|
327
|
+
data: {
|
|
328
|
+
type: "workflow-step-finish",
|
|
329
|
+
payload: {
|
|
330
|
+
id: step.id,
|
|
331
|
+
metadata: {}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
return { executionContext, result: { status: "success", output: result?.result } };
|
|
336
|
+
}
|
|
337
|
+
);
|
|
338
|
+
Object.assign(executionContext, res.executionContext);
|
|
339
|
+
return {
|
|
340
|
+
...res.result,
|
|
341
|
+
startedAt,
|
|
342
|
+
endedAt: Date.now(),
|
|
343
|
+
payload: inputData,
|
|
344
|
+
resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
|
|
345
|
+
resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
};
|
|
349
|
+
var InngestPubSub = class extends events.PubSub {
|
|
350
|
+
inngest;
|
|
351
|
+
workflowId;
|
|
352
|
+
publishFn;
|
|
353
|
+
subscriptions = /* @__PURE__ */ new Map();
|
|
354
|
+
constructor(inngest, workflowId, publishFn) {
|
|
355
|
+
super();
|
|
356
|
+
this.inngest = inngest;
|
|
357
|
+
this.workflowId = workflowId;
|
|
358
|
+
this.publishFn = publishFn;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Publish an event to Inngest's realtime system.
|
|
362
|
+
*
|
|
363
|
+
* Topic format: "workflow.events.v2.{runId}"
|
|
364
|
+
* Maps to Inngest channel: "workflow:{workflowId}:{runId}"
|
|
365
|
+
*/
|
|
366
|
+
async publish(topic, event) {
|
|
367
|
+
if (!this.publishFn) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
const match = topic.match(/^workflow\.events\.v2\.(.+)$/);
|
|
371
|
+
if (!match) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
const runId = match[1];
|
|
375
|
+
try {
|
|
376
|
+
await this.publishFn({
|
|
377
|
+
channel: `workflow:${this.workflowId}:${runId}`,
|
|
378
|
+
topic: "watch",
|
|
379
|
+
data: event.data
|
|
380
|
+
});
|
|
381
|
+
} catch (err) {
|
|
382
|
+
console.error("InngestPubSub publish error:", err?.message ?? err);
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Subscribe to events from Inngest's realtime system.
|
|
387
|
+
*
|
|
388
|
+
* Topic format: "workflow.events.v2.{runId}"
|
|
389
|
+
* Maps to Inngest channel: "workflow:{workflowId}:{runId}"
|
|
390
|
+
*/
|
|
391
|
+
async subscribe(topic, cb) {
|
|
392
|
+
const match = topic.match(/^workflow\.events\.v2\.(.+)$/);
|
|
393
|
+
if (!match || !match[1]) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
const runId = match[1];
|
|
397
|
+
if (this.subscriptions.has(topic)) {
|
|
398
|
+
this.subscriptions.get(topic).callbacks.add(cb);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
const callbacks = /* @__PURE__ */ new Set([cb]);
|
|
402
|
+
const channel = `workflow:${this.workflowId}:${runId}`;
|
|
403
|
+
const streamPromise = realtime.subscribe(
|
|
404
|
+
{
|
|
405
|
+
channel,
|
|
406
|
+
topics: ["watch"],
|
|
407
|
+
app: this.inngest
|
|
408
|
+
},
|
|
409
|
+
(message) => {
|
|
410
|
+
const event = {
|
|
411
|
+
id: crypto.randomUUID(),
|
|
412
|
+
type: "watch",
|
|
413
|
+
runId,
|
|
414
|
+
data: message.data,
|
|
415
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
416
|
+
};
|
|
417
|
+
for (const callback of callbacks) {
|
|
418
|
+
callback(event);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
);
|
|
422
|
+
this.subscriptions.set(topic, {
|
|
423
|
+
unsubscribe: () => {
|
|
424
|
+
streamPromise.then((stream) => stream.cancel()).catch((err) => {
|
|
425
|
+
console.error("InngestPubSub unsubscribe error:", err);
|
|
426
|
+
});
|
|
427
|
+
},
|
|
428
|
+
callbacks
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Unsubscribe a callback from a topic.
|
|
433
|
+
* If no callbacks remain, the underlying Inngest subscription is cancelled.
|
|
434
|
+
*/
|
|
435
|
+
async unsubscribe(topic, cb) {
|
|
436
|
+
const sub = this.subscriptions.get(topic);
|
|
437
|
+
if (!sub) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
sub.callbacks.delete(cb);
|
|
441
|
+
if (sub.callbacks.size === 0) {
|
|
442
|
+
sub.unsubscribe();
|
|
443
|
+
this.subscriptions.delete(topic);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Flush any pending operations. No-op for Inngest.
|
|
448
|
+
*/
|
|
449
|
+
async flush() {
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Clean up all subscriptions during graceful shutdown.
|
|
453
|
+
*/
|
|
454
|
+
async close() {
|
|
455
|
+
for (const [, sub] of this.subscriptions) {
|
|
456
|
+
sub.unsubscribe();
|
|
457
|
+
}
|
|
458
|
+
this.subscriptions.clear();
|
|
459
|
+
}
|
|
460
|
+
};
|
|
38
461
|
var InngestRun = class extends workflows.Run {
|
|
39
462
|
inngest;
|
|
40
463
|
serializedStepGraph;
|
|
@@ -46,62 +469,170 @@ var InngestRun = class extends workflows.Run {
|
|
|
46
469
|
this.#mastra = params.mastra;
|
|
47
470
|
}
|
|
48
471
|
async getRuns(eventId) {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
472
|
+
const maxRetries = 3;
|
|
473
|
+
let lastError = null;
|
|
474
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
475
|
+
try {
|
|
476
|
+
const response = await fetch(
|
|
477
|
+
`${this.inngest.apiBaseUrl ?? "https://api.inngest.com"}/v1/events/${eventId}/runs`,
|
|
478
|
+
{
|
|
479
|
+
headers: {
|
|
480
|
+
Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
);
|
|
484
|
+
if (response.status === 429) {
|
|
485
|
+
const retryAfter = parseInt(response.headers.get("retry-after") || "2", 10);
|
|
486
|
+
await new Promise((resolve) => setTimeout(resolve, retryAfter * 1e3));
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
if (!response.ok) {
|
|
490
|
+
throw new Error(`Inngest API error: ${response.status} ${response.statusText}`);
|
|
491
|
+
}
|
|
492
|
+
const text = await response.text();
|
|
493
|
+
if (!text) {
|
|
494
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3 * (attempt + 1)));
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
const json = JSON.parse(text);
|
|
498
|
+
return json.data;
|
|
499
|
+
} catch (error) {
|
|
500
|
+
lastError = error;
|
|
501
|
+
if (attempt < maxRetries - 1) {
|
|
502
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3 * Math.pow(2, attempt)));
|
|
503
|
+
}
|
|
52
504
|
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return json.data;
|
|
505
|
+
}
|
|
506
|
+
throw new inngest.NonRetriableError(`Failed to get runs after ${maxRetries} attempts: ${lastError?.message}`);
|
|
56
507
|
}
|
|
57
|
-
async getRunOutput(eventId) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
runs
|
|
508
|
+
async getRunOutput(eventId, maxWaitMs = 3e5) {
|
|
509
|
+
const startTime = Date.now();
|
|
510
|
+
const storage = this.#mastra?.getStorage();
|
|
511
|
+
while (Date.now() - startTime < maxWaitMs) {
|
|
512
|
+
let runs;
|
|
513
|
+
try {
|
|
514
|
+
runs = await this.getRuns(eventId);
|
|
515
|
+
} catch (error) {
|
|
516
|
+
if (error instanceof inngest.NonRetriableError) {
|
|
517
|
+
throw error;
|
|
518
|
+
}
|
|
519
|
+
throw new inngest.NonRetriableError(
|
|
520
|
+
`Failed to poll workflow status: ${error instanceof Error ? error.message : String(error)}`
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
if (runs?.[0]?.status === "Completed" && runs?.[0]?.event_id === eventId) {
|
|
524
|
+
return runs[0];
|
|
525
|
+
}
|
|
62
526
|
if (runs?.[0]?.status === "Failed") {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
527
|
+
const snapshot = await storage?.loadWorkflowSnapshot({
|
|
528
|
+
workflowName: this.workflowId,
|
|
529
|
+
runId: this.runId
|
|
530
|
+
});
|
|
531
|
+
if (snapshot?.context) {
|
|
532
|
+
snapshot.context = workflows.hydrateSerializedStepErrors(snapshot.context);
|
|
533
|
+
}
|
|
534
|
+
return {
|
|
535
|
+
output: {
|
|
536
|
+
result: {
|
|
537
|
+
steps: snapshot?.context,
|
|
538
|
+
status: "failed",
|
|
539
|
+
// Get the original error from NonRetriableError's cause (which contains the workflow result)
|
|
540
|
+
error: error.getErrorFromUnknown(runs?.[0]?.output?.cause?.error, { serializeStack: false })
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
if (runs?.[0]?.status === "Cancelled") {
|
|
546
|
+
const snapshot = await storage?.loadWorkflowSnapshot({
|
|
66
547
|
workflowName: this.workflowId,
|
|
67
548
|
runId: this.runId
|
|
68
549
|
});
|
|
69
550
|
return { output: { result: { steps: snapshot?.context, status: "canceled" } } };
|
|
70
551
|
}
|
|
552
|
+
await new Promise((resolve) => setTimeout(resolve, 1e3 + Math.random() * 1e3));
|
|
71
553
|
}
|
|
72
|
-
|
|
73
|
-
}
|
|
74
|
-
async sendEvent(event, data) {
|
|
75
|
-
await this.inngest.send({
|
|
76
|
-
name: `user-event-${event}`,
|
|
77
|
-
data
|
|
78
|
-
});
|
|
554
|
+
throw new inngest.NonRetriableError(`Workflow did not complete within ${maxWaitMs}ms`);
|
|
79
555
|
}
|
|
80
556
|
async cancel() {
|
|
557
|
+
const storage = this.#mastra?.getStorage();
|
|
81
558
|
await this.inngest.send({
|
|
82
559
|
name: `cancel.workflow.${this.workflowId}`,
|
|
83
560
|
data: {
|
|
84
561
|
runId: this.runId
|
|
85
562
|
}
|
|
86
563
|
});
|
|
87
|
-
const snapshot = await
|
|
564
|
+
const snapshot = await storage?.loadWorkflowSnapshot({
|
|
88
565
|
workflowName: this.workflowId,
|
|
89
566
|
runId: this.runId
|
|
90
567
|
});
|
|
91
568
|
if (snapshot) {
|
|
92
|
-
await
|
|
569
|
+
await storage?.persistWorkflowSnapshot({
|
|
93
570
|
workflowName: this.workflowId,
|
|
94
571
|
runId: this.runId,
|
|
95
572
|
resourceId: this.resourceId,
|
|
96
573
|
snapshot: {
|
|
97
574
|
...snapshot,
|
|
98
|
-
status: "canceled"
|
|
575
|
+
status: "canceled",
|
|
576
|
+
value: snapshot.value
|
|
99
577
|
}
|
|
100
578
|
});
|
|
101
579
|
}
|
|
102
580
|
}
|
|
103
|
-
async start({
|
|
104
|
-
|
|
581
|
+
async start(params) {
|
|
582
|
+
return this._start(params);
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Starts the workflow execution without waiting for completion (fire-and-forget).
|
|
586
|
+
* Returns immediately with the runId after sending the event to Inngest.
|
|
587
|
+
* The workflow executes independently in Inngest.
|
|
588
|
+
* Use this when you don't need to wait for the result or want to avoid polling failures.
|
|
589
|
+
*/
|
|
590
|
+
async startAsync(params) {
|
|
591
|
+
await this.#mastra.getStorage()?.persistWorkflowSnapshot({
|
|
592
|
+
workflowName: this.workflowId,
|
|
593
|
+
runId: this.runId,
|
|
594
|
+
resourceId: this.resourceId,
|
|
595
|
+
snapshot: {
|
|
596
|
+
runId: this.runId,
|
|
597
|
+
serializedStepGraph: this.serializedStepGraph,
|
|
598
|
+
status: "running",
|
|
599
|
+
value: {},
|
|
600
|
+
context: {},
|
|
601
|
+
activePaths: [],
|
|
602
|
+
suspendedPaths: {},
|
|
603
|
+
activeStepsPath: {},
|
|
604
|
+
resumeLabels: {},
|
|
605
|
+
waitingPaths: {},
|
|
606
|
+
timestamp: Date.now()
|
|
607
|
+
}
|
|
608
|
+
});
|
|
609
|
+
const inputDataToUse = await this._validateInput(params.inputData);
|
|
610
|
+
const initialStateToUse = await this._validateInitialState(params.initialState ?? {});
|
|
611
|
+
const eventOutput = await this.inngest.send({
|
|
612
|
+
name: `workflow.${this.workflowId}`,
|
|
613
|
+
data: {
|
|
614
|
+
inputData: inputDataToUse,
|
|
615
|
+
initialState: initialStateToUse,
|
|
616
|
+
runId: this.runId,
|
|
617
|
+
resourceId: this.resourceId,
|
|
618
|
+
outputOptions: params.outputOptions,
|
|
619
|
+
tracingOptions: params.tracingOptions,
|
|
620
|
+
requestContext: params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {}
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
const eventId = eventOutput.ids[0];
|
|
624
|
+
if (!eventId) {
|
|
625
|
+
throw new Error("Event ID is not set");
|
|
626
|
+
}
|
|
627
|
+
return { runId: this.runId };
|
|
628
|
+
}
|
|
629
|
+
async _start({
|
|
630
|
+
inputData,
|
|
631
|
+
initialState,
|
|
632
|
+
outputOptions,
|
|
633
|
+
tracingOptions,
|
|
634
|
+
format,
|
|
635
|
+
requestContext
|
|
105
636
|
}) {
|
|
106
637
|
await this.#mastra.getStorage()?.persistWorkflowSnapshot({
|
|
107
638
|
workflowName: this.workflowId,
|
|
@@ -110,22 +641,30 @@ var InngestRun = class extends workflows.Run {
|
|
|
110
641
|
snapshot: {
|
|
111
642
|
runId: this.runId,
|
|
112
643
|
serializedStepGraph: this.serializedStepGraph,
|
|
644
|
+
status: "running",
|
|
113
645
|
value: {},
|
|
114
646
|
context: {},
|
|
115
647
|
activePaths: [],
|
|
116
648
|
suspendedPaths: {},
|
|
649
|
+
activeStepsPath: {},
|
|
650
|
+
resumeLabels: {},
|
|
117
651
|
waitingPaths: {},
|
|
118
|
-
timestamp: Date.now()
|
|
119
|
-
status: "running"
|
|
652
|
+
timestamp: Date.now()
|
|
120
653
|
}
|
|
121
654
|
});
|
|
122
655
|
const inputDataToUse = await this._validateInput(inputData);
|
|
656
|
+
const initialStateToUse = await this._validateInitialState(initialState ?? {});
|
|
123
657
|
const eventOutput = await this.inngest.send({
|
|
124
658
|
name: `workflow.${this.workflowId}`,
|
|
125
659
|
data: {
|
|
126
660
|
inputData: inputDataToUse,
|
|
661
|
+
initialState: initialStateToUse,
|
|
127
662
|
runId: this.runId,
|
|
128
|
-
resourceId: this.resourceId
|
|
663
|
+
resourceId: this.resourceId,
|
|
664
|
+
outputOptions,
|
|
665
|
+
tracingOptions,
|
|
666
|
+
format,
|
|
667
|
+
requestContext: requestContext ? Object.fromEntries(requestContext.entries()) : {}
|
|
129
668
|
}
|
|
130
669
|
});
|
|
131
670
|
const eventId = eventOutput.ids[0];
|
|
@@ -134,9 +673,7 @@ var InngestRun = class extends workflows.Run {
|
|
|
134
673
|
}
|
|
135
674
|
const runOutput = await this.getRunOutput(eventId);
|
|
136
675
|
const result = runOutput?.output?.result;
|
|
137
|
-
|
|
138
|
-
result.error = new Error(result.error);
|
|
139
|
-
}
|
|
676
|
+
this.hydrateFailedResult(result);
|
|
140
677
|
if (result.status !== "suspended") {
|
|
141
678
|
this.cleanup?.();
|
|
142
679
|
}
|
|
@@ -154,19 +691,29 @@ var InngestRun = class extends workflows.Run {
|
|
|
154
691
|
return p;
|
|
155
692
|
}
|
|
156
693
|
async _resume(params) {
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
)
|
|
160
|
-
|
|
694
|
+
const storage = this.#mastra?.getStorage();
|
|
695
|
+
let steps = [];
|
|
696
|
+
if (typeof params.step === "string") {
|
|
697
|
+
steps = params.step.split(".");
|
|
698
|
+
} else {
|
|
699
|
+
steps = (Array.isArray(params.step) ? params.step : [params.step]).map(
|
|
700
|
+
(step) => typeof step === "string" ? step : step?.id
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
const snapshot = await storage?.loadWorkflowSnapshot({
|
|
161
704
|
workflowName: this.workflowId,
|
|
162
705
|
runId: this.runId
|
|
163
706
|
});
|
|
164
707
|
const suspendedStep = this.workflowSteps[steps?.[0] ?? ""];
|
|
165
708
|
const resumeDataToUse = await this._validateResumeData(params.resumeData, suspendedStep);
|
|
709
|
+
const persistedRequestContext = snapshot?.requestContext ?? {};
|
|
710
|
+
const newRequestContext = params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {};
|
|
711
|
+
const mergedRequestContext = { ...persistedRequestContext, ...newRequestContext };
|
|
166
712
|
const eventOutput = await this.inngest.send({
|
|
167
713
|
name: `workflow.${this.workflowId}`,
|
|
168
714
|
data: {
|
|
169
715
|
inputData: resumeDataToUse,
|
|
716
|
+
initialState: snapshot?.value ?? {},
|
|
170
717
|
runId: this.runId,
|
|
171
718
|
workflowId: this.workflowId,
|
|
172
719
|
stepResults: snapshot?.context,
|
|
@@ -174,9 +721,9 @@ var InngestRun = class extends workflows.Run {
|
|
|
174
721
|
steps,
|
|
175
722
|
stepResults: snapshot?.context,
|
|
176
723
|
resumePayload: resumeDataToUse,
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
724
|
+
resumePath: steps?.[0] ? snapshot?.suspendedPaths?.[steps?.[0]] : void 0
|
|
725
|
+
},
|
|
726
|
+
requestContext: mergedRequestContext
|
|
180
727
|
}
|
|
181
728
|
});
|
|
182
729
|
const eventId = eventOutput.ids[0];
|
|
@@ -185,17 +732,105 @@ var InngestRun = class extends workflows.Run {
|
|
|
185
732
|
}
|
|
186
733
|
const runOutput = await this.getRunOutput(eventId);
|
|
187
734
|
const result = runOutput?.output?.result;
|
|
188
|
-
|
|
189
|
-
|
|
735
|
+
this.hydrateFailedResult(result);
|
|
736
|
+
return result;
|
|
737
|
+
}
|
|
738
|
+
async timeTravel(params) {
|
|
739
|
+
const p = this._timeTravel(params).then((result) => {
|
|
740
|
+
if (result.status !== "suspended") {
|
|
741
|
+
this.closeStreamAction?.().catch(() => {
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
return result;
|
|
745
|
+
});
|
|
746
|
+
this.executionResults = p;
|
|
747
|
+
return p;
|
|
748
|
+
}
|
|
749
|
+
async _timeTravel(params) {
|
|
750
|
+
if (!params.step || Array.isArray(params.step) && params.step?.length === 0) {
|
|
751
|
+
throw new Error("Step is required and must be a valid step or array of steps");
|
|
752
|
+
}
|
|
753
|
+
let steps = [];
|
|
754
|
+
if (typeof params.step === "string") {
|
|
755
|
+
steps = params.step.split(".");
|
|
756
|
+
} else {
|
|
757
|
+
steps = (Array.isArray(params.step) ? params.step : [params.step]).map(
|
|
758
|
+
(step) => typeof step === "string" ? step : step?.id
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
if (steps.length === 0) {
|
|
762
|
+
throw new Error("No steps provided to timeTravel");
|
|
763
|
+
}
|
|
764
|
+
const storage = this.#mastra?.getStorage();
|
|
765
|
+
const snapshot = await storage?.loadWorkflowSnapshot({
|
|
766
|
+
workflowName: this.workflowId,
|
|
767
|
+
runId: this.runId
|
|
768
|
+
});
|
|
769
|
+
if (!snapshot) {
|
|
770
|
+
await storage?.persistWorkflowSnapshot({
|
|
771
|
+
workflowName: this.workflowId,
|
|
772
|
+
runId: this.runId,
|
|
773
|
+
resourceId: this.resourceId,
|
|
774
|
+
snapshot: {
|
|
775
|
+
runId: this.runId,
|
|
776
|
+
serializedStepGraph: this.serializedStepGraph,
|
|
777
|
+
status: "pending",
|
|
778
|
+
value: {},
|
|
779
|
+
context: {},
|
|
780
|
+
activePaths: [],
|
|
781
|
+
suspendedPaths: {},
|
|
782
|
+
activeStepsPath: {},
|
|
783
|
+
resumeLabels: {},
|
|
784
|
+
waitingPaths: {},
|
|
785
|
+
timestamp: Date.now()
|
|
786
|
+
}
|
|
787
|
+
});
|
|
190
788
|
}
|
|
789
|
+
if (snapshot?.status === "running") {
|
|
790
|
+
throw new Error("This workflow run is still running, cannot time travel");
|
|
791
|
+
}
|
|
792
|
+
let inputDataToUse = params.inputData;
|
|
793
|
+
if (inputDataToUse && steps.length === 1) {
|
|
794
|
+
inputDataToUse = await this._validateTimetravelInputData(params.inputData, this.workflowSteps[steps[0]]);
|
|
795
|
+
}
|
|
796
|
+
const timeTravelData = workflows.createTimeTravelExecutionParams({
|
|
797
|
+
steps,
|
|
798
|
+
inputData: inputDataToUse,
|
|
799
|
+
resumeData: params.resumeData,
|
|
800
|
+
context: params.context,
|
|
801
|
+
nestedStepsContext: params.nestedStepsContext,
|
|
802
|
+
snapshot: snapshot ?? { context: {} },
|
|
803
|
+
graph: this.executionGraph,
|
|
804
|
+
initialState: params.initialState
|
|
805
|
+
});
|
|
806
|
+
const eventOutput = await this.inngest.send({
|
|
807
|
+
name: `workflow.${this.workflowId}`,
|
|
808
|
+
data: {
|
|
809
|
+
initialState: timeTravelData.state,
|
|
810
|
+
runId: this.runId,
|
|
811
|
+
workflowId: this.workflowId,
|
|
812
|
+
stepResults: timeTravelData.stepResults,
|
|
813
|
+
timeTravel: timeTravelData,
|
|
814
|
+
tracingOptions: params.tracingOptions,
|
|
815
|
+
outputOptions: params.outputOptions,
|
|
816
|
+
requestContext: params.requestContext ? Object.fromEntries(params.requestContext.entries()) : {}
|
|
817
|
+
}
|
|
818
|
+
});
|
|
819
|
+
const eventId = eventOutput.ids[0];
|
|
820
|
+
if (!eventId) {
|
|
821
|
+
throw new Error("Event ID is not set");
|
|
822
|
+
}
|
|
823
|
+
const runOutput = await this.getRunOutput(eventId);
|
|
824
|
+
const result = runOutput?.output?.result;
|
|
825
|
+
this.hydrateFailedResult(result);
|
|
191
826
|
return result;
|
|
192
827
|
}
|
|
193
|
-
watch(cb
|
|
828
|
+
watch(cb) {
|
|
194
829
|
let active = true;
|
|
195
830
|
const streamPromise = realtime.subscribe(
|
|
196
831
|
{
|
|
197
832
|
channel: `workflow:${this.workflowId}:${this.runId}`,
|
|
198
|
-
topics: [
|
|
833
|
+
topics: ["watch"],
|
|
199
834
|
app: this.inngest
|
|
200
835
|
},
|
|
201
836
|
(message) => {
|
|
@@ -213,20 +848,35 @@ var InngestRun = class extends workflows.Run {
|
|
|
213
848
|
});
|
|
214
849
|
};
|
|
215
850
|
}
|
|
216
|
-
|
|
851
|
+
streamLegacy({ inputData, requestContext } = {}) {
|
|
217
852
|
const { readable, writable } = new TransformStream();
|
|
218
853
|
const writer = writable.getWriter();
|
|
854
|
+
void writer.write({
|
|
855
|
+
// @ts-ignore
|
|
856
|
+
type: "start",
|
|
857
|
+
// @ts-ignore
|
|
858
|
+
payload: { runId: this.runId }
|
|
859
|
+
});
|
|
219
860
|
const unwatch = this.watch(async (event) => {
|
|
220
861
|
try {
|
|
221
862
|
const e = {
|
|
222
863
|
...event,
|
|
223
864
|
type: event.type.replace("workflow-", "")
|
|
224
865
|
};
|
|
866
|
+
if (e.type === "step-output") {
|
|
867
|
+
e.type = e.payload.output.type;
|
|
868
|
+
e.payload = e.payload.output.payload;
|
|
869
|
+
}
|
|
225
870
|
await writer.write(e);
|
|
226
871
|
} catch {
|
|
227
872
|
}
|
|
228
|
-
}
|
|
873
|
+
});
|
|
229
874
|
this.closeStreamAction = async () => {
|
|
875
|
+
await writer.write({
|
|
876
|
+
type: "finish",
|
|
877
|
+
// @ts-ignore
|
|
878
|
+
payload: { runId: this.runId }
|
|
879
|
+
});
|
|
230
880
|
unwatch();
|
|
231
881
|
try {
|
|
232
882
|
await writer.close();
|
|
@@ -236,7 +886,7 @@ var InngestRun = class extends workflows.Run {
|
|
|
236
886
|
writer.releaseLock();
|
|
237
887
|
}
|
|
238
888
|
};
|
|
239
|
-
this.executionResults = this.
|
|
889
|
+
this.executionResults = this._start({ inputData, requestContext, format: "legacy" }).then((result) => {
|
|
240
890
|
if (result.status !== "suspended") {
|
|
241
891
|
this.closeStreamAction?.().catch(() => {
|
|
242
892
|
});
|
|
@@ -248,7 +898,166 @@ var InngestRun = class extends workflows.Run {
|
|
|
248
898
|
getWorkflowState: () => this.executionResults
|
|
249
899
|
};
|
|
250
900
|
}
|
|
901
|
+
stream({
|
|
902
|
+
inputData,
|
|
903
|
+
requestContext,
|
|
904
|
+
tracingOptions,
|
|
905
|
+
closeOnSuspend = true,
|
|
906
|
+
initialState,
|
|
907
|
+
outputOptions
|
|
908
|
+
} = {}) {
|
|
909
|
+
if (this.closeStreamAction && this.streamOutput) {
|
|
910
|
+
return this.streamOutput;
|
|
911
|
+
}
|
|
912
|
+
this.closeStreamAction = async () => {
|
|
913
|
+
};
|
|
914
|
+
const self = this;
|
|
915
|
+
const stream$1 = new web.ReadableStream({
|
|
916
|
+
async start(controller) {
|
|
917
|
+
const unwatch = self.watch(async ({ type, from = stream.ChunkFrom.WORKFLOW, payload }) => {
|
|
918
|
+
controller.enqueue({
|
|
919
|
+
type,
|
|
920
|
+
runId: self.runId,
|
|
921
|
+
from,
|
|
922
|
+
payload: {
|
|
923
|
+
stepName: payload?.id,
|
|
924
|
+
...payload
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
});
|
|
928
|
+
self.closeStreamAction = async () => {
|
|
929
|
+
unwatch();
|
|
930
|
+
try {
|
|
931
|
+
await controller.close();
|
|
932
|
+
} catch (err) {
|
|
933
|
+
console.error("Error closing stream:", err);
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
const executionResultsPromise = self._start({
|
|
937
|
+
inputData,
|
|
938
|
+
requestContext,
|
|
939
|
+
// tracingContext, // We are not able to pass a reference to a span here, what to do?
|
|
940
|
+
initialState,
|
|
941
|
+
tracingOptions,
|
|
942
|
+
outputOptions,
|
|
943
|
+
format: "vnext"
|
|
944
|
+
});
|
|
945
|
+
let executionResults;
|
|
946
|
+
try {
|
|
947
|
+
executionResults = await executionResultsPromise;
|
|
948
|
+
if (closeOnSuspend) {
|
|
949
|
+
self.closeStreamAction?.().catch(() => {
|
|
950
|
+
});
|
|
951
|
+
} else if (executionResults.status !== "suspended") {
|
|
952
|
+
self.closeStreamAction?.().catch(() => {
|
|
953
|
+
});
|
|
954
|
+
}
|
|
955
|
+
if (self.streamOutput) {
|
|
956
|
+
self.streamOutput.updateResults(
|
|
957
|
+
executionResults
|
|
958
|
+
);
|
|
959
|
+
}
|
|
960
|
+
} catch (err) {
|
|
961
|
+
self.streamOutput?.rejectResults(err);
|
|
962
|
+
self.closeStreamAction?.().catch(() => {
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
this.streamOutput = new stream.WorkflowRunOutput({
|
|
968
|
+
runId: this.runId,
|
|
969
|
+
workflowId: this.workflowId,
|
|
970
|
+
stream: stream$1
|
|
971
|
+
});
|
|
972
|
+
return this.streamOutput;
|
|
973
|
+
}
|
|
974
|
+
streamVNext(args = {}) {
|
|
975
|
+
return this.stream(args);
|
|
976
|
+
}
|
|
977
|
+
timeTravelStream({
|
|
978
|
+
inputData,
|
|
979
|
+
resumeData,
|
|
980
|
+
initialState,
|
|
981
|
+
step,
|
|
982
|
+
context,
|
|
983
|
+
nestedStepsContext,
|
|
984
|
+
requestContext,
|
|
985
|
+
tracingOptions,
|
|
986
|
+
outputOptions
|
|
987
|
+
}) {
|
|
988
|
+
this.closeStreamAction = async () => {
|
|
989
|
+
};
|
|
990
|
+
const self = this;
|
|
991
|
+
const stream$1 = new web.ReadableStream({
|
|
992
|
+
async start(controller) {
|
|
993
|
+
const unwatch = self.watch(async ({ type, from = stream.ChunkFrom.WORKFLOW, payload }) => {
|
|
994
|
+
controller.enqueue({
|
|
995
|
+
type,
|
|
996
|
+
runId: self.runId,
|
|
997
|
+
from,
|
|
998
|
+
payload: {
|
|
999
|
+
stepName: payload?.id,
|
|
1000
|
+
...payload
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
});
|
|
1004
|
+
self.closeStreamAction = async () => {
|
|
1005
|
+
unwatch();
|
|
1006
|
+
try {
|
|
1007
|
+
controller.close();
|
|
1008
|
+
} catch (err) {
|
|
1009
|
+
console.error("Error closing stream:", err);
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
const executionResultsPromise = self._timeTravel({
|
|
1013
|
+
inputData,
|
|
1014
|
+
step,
|
|
1015
|
+
context,
|
|
1016
|
+
nestedStepsContext,
|
|
1017
|
+
resumeData,
|
|
1018
|
+
initialState,
|
|
1019
|
+
requestContext,
|
|
1020
|
+
tracingOptions,
|
|
1021
|
+
outputOptions
|
|
1022
|
+
});
|
|
1023
|
+
self.executionResults = executionResultsPromise;
|
|
1024
|
+
let executionResults;
|
|
1025
|
+
try {
|
|
1026
|
+
executionResults = await executionResultsPromise;
|
|
1027
|
+
self.closeStreamAction?.().catch(() => {
|
|
1028
|
+
});
|
|
1029
|
+
if (self.streamOutput) {
|
|
1030
|
+
self.streamOutput.updateResults(executionResults);
|
|
1031
|
+
}
|
|
1032
|
+
} catch (err) {
|
|
1033
|
+
self.streamOutput?.rejectResults(err);
|
|
1034
|
+
self.closeStreamAction?.().catch(() => {
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
});
|
|
1039
|
+
this.streamOutput = new stream.WorkflowRunOutput({
|
|
1040
|
+
runId: this.runId,
|
|
1041
|
+
workflowId: this.workflowId,
|
|
1042
|
+
stream: stream$1
|
|
1043
|
+
});
|
|
1044
|
+
return this.streamOutput;
|
|
1045
|
+
}
|
|
1046
|
+
/**
|
|
1047
|
+
* Hydrates errors in a failed workflow result back to proper Error instances.
|
|
1048
|
+
* This ensures error.cause chains and custom properties are preserved.
|
|
1049
|
+
*/
|
|
1050
|
+
hydrateFailedResult(result) {
|
|
1051
|
+
if (result.status === "failed") {
|
|
1052
|
+
result.error = error.getErrorFromUnknown(result.error, { serializeStack: false });
|
|
1053
|
+
if (result.steps) {
|
|
1054
|
+
workflows.hydrateSerializedStepErrors(result.steps);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
251
1058
|
};
|
|
1059
|
+
|
|
1060
|
+
// src/workflow.ts
|
|
252
1061
|
var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
253
1062
|
#mastra;
|
|
254
1063
|
inngest;
|
|
@@ -257,6 +1066,7 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
257
1066
|
constructor(params, inngest) {
|
|
258
1067
|
const { concurrency, rateLimit, throttle, debounce, priority, ...workflowParams } = params;
|
|
259
1068
|
super(workflowParams);
|
|
1069
|
+
this.engineType = "inngest";
|
|
260
1070
|
const flowControlEntries = Object.entries({ concurrency, rateLimit, throttle, debounce, priority }).filter(
|
|
261
1071
|
([_, value]) => value !== void 0
|
|
262
1072
|
);
|
|
@@ -264,13 +1074,13 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
264
1074
|
this.#mastra = params.mastra;
|
|
265
1075
|
this.inngest = inngest;
|
|
266
1076
|
}
|
|
267
|
-
async
|
|
1077
|
+
async listWorkflowRuns(args) {
|
|
268
1078
|
const storage = this.#mastra?.getStorage();
|
|
269
1079
|
if (!storage) {
|
|
270
1080
|
this.logger.debug("Cannot get workflow runs. Mastra engine is not initialized");
|
|
271
1081
|
return { runs: [], total: 0 };
|
|
272
1082
|
}
|
|
273
|
-
return storage.
|
|
1083
|
+
return storage.listWorkflowRuns({ workflowName: this.id, ...args ?? {} });
|
|
274
1084
|
}
|
|
275
1085
|
async getWorkflowRunById(runId) {
|
|
276
1086
|
const storage = this.#mastra?.getStorage();
|
|
@@ -282,6 +1092,7 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
282
1092
|
return run ?? (this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null);
|
|
283
1093
|
}
|
|
284
1094
|
__registerMastra(mastra) {
|
|
1095
|
+
super.__registerMastra(mastra);
|
|
285
1096
|
this.#mastra = mastra;
|
|
286
1097
|
this.executionEngine.__registerMastra(mastra);
|
|
287
1098
|
const updateNested = (step) => {
|
|
@@ -299,17 +1110,8 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
299
1110
|
}
|
|
300
1111
|
}
|
|
301
1112
|
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
* @throws {Error} Always throws an error directing users to use createRunAsync()
|
|
305
|
-
*/
|
|
306
|
-
createRun(_options) {
|
|
307
|
-
throw new Error(
|
|
308
|
-
"createRun() has been deprecated. Please use createRunAsync() instead.\n\nMigration guide:\n Before: const run = workflow.createRun();\n After: const run = await workflow.createRunAsync();\n\nNote: createRunAsync() is an async method, so make sure your calling function is async."
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
async createRunAsync(options) {
|
|
312
|
-
const runIdToUse = options?.runId || crypto.randomUUID();
|
|
1113
|
+
async createRun(options) {
|
|
1114
|
+
const runIdToUse = options?.runId || crypto$1.randomUUID();
|
|
313
1115
|
const run = this.runs.get(runIdToUse) ?? new InngestRun(
|
|
314
1116
|
{
|
|
315
1117
|
workflowId: this.id,
|
|
@@ -321,13 +1123,19 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
321
1123
|
mastra: this.#mastra,
|
|
322
1124
|
retryConfig: this.retryConfig,
|
|
323
1125
|
cleanup: () => this.runs.delete(runIdToUse),
|
|
324
|
-
workflowSteps: this.steps
|
|
1126
|
+
workflowSteps: this.steps,
|
|
1127
|
+
workflowEngineType: this.engineType,
|
|
1128
|
+
validateInputs: this.options.validateInputs
|
|
325
1129
|
},
|
|
326
1130
|
this.inngest
|
|
327
1131
|
);
|
|
328
1132
|
this.runs.set(runIdToUse, run);
|
|
1133
|
+
const shouldPersistSnapshot = this.options.shouldPersistSnapshot({
|
|
1134
|
+
workflowStatus: run.workflowRunStatus,
|
|
1135
|
+
stepResults: {}
|
|
1136
|
+
});
|
|
329
1137
|
const workflowSnapshotInStorage = await this.getWorkflowRunExecutionResult(runIdToUse, false);
|
|
330
|
-
if (!workflowSnapshotInStorage) {
|
|
1138
|
+
if (!workflowSnapshotInStorage && shouldPersistSnapshot) {
|
|
331
1139
|
await this.mastra?.getStorage()?.persistWorkflowSnapshot({
|
|
332
1140
|
workflowName: this.id,
|
|
333
1141
|
runId: runIdToUse,
|
|
@@ -338,12 +1146,13 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
338
1146
|
value: {},
|
|
339
1147
|
context: {},
|
|
340
1148
|
activePaths: [],
|
|
1149
|
+
activeStepsPath: {},
|
|
341
1150
|
waitingPaths: {},
|
|
342
1151
|
serializedStepGraph: this.serializedStepGraph,
|
|
343
1152
|
suspendedPaths: {},
|
|
1153
|
+
resumeLabels: {},
|
|
344
1154
|
result: void 0,
|
|
345
1155
|
error: void 0,
|
|
346
|
-
// @ts-ignore
|
|
347
1156
|
timestamp: Date.now()
|
|
348
1157
|
}
|
|
349
1158
|
});
|
|
@@ -357,43 +1166,21 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
357
1166
|
this.function = this.inngest.createFunction(
|
|
358
1167
|
{
|
|
359
1168
|
id: `workflow.${this.id}`,
|
|
360
|
-
|
|
361
|
-
retries: this.retryConfig?.attempts ?? 0,
|
|
1169
|
+
retries: Math.min(this.retryConfig?.attempts ?? 0, 20),
|
|
362
1170
|
cancelOn: [{ event: `cancel.workflow.${this.id}` }],
|
|
363
1171
|
// Spread flow control configuration
|
|
364
1172
|
...this.flowControlConfig
|
|
365
1173
|
},
|
|
366
1174
|
{ event: `workflow.${this.id}` },
|
|
367
1175
|
async ({ event, step, attempt, publish }) => {
|
|
368
|
-
let { inputData, runId, resourceId, resume } = event.data;
|
|
1176
|
+
let { inputData, initialState, runId, resourceId, resume, outputOptions, format, timeTravel } = event.data;
|
|
369
1177
|
if (!runId) {
|
|
370
1178
|
runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
|
|
371
|
-
return crypto.randomUUID();
|
|
1179
|
+
return crypto$1.randomUUID();
|
|
372
1180
|
});
|
|
373
1181
|
}
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
if (!publish) {
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
try {
|
|
380
|
-
await publish({
|
|
381
|
-
channel: `workflow:${this.id}:${runId}`,
|
|
382
|
-
topic: event2,
|
|
383
|
-
data
|
|
384
|
-
});
|
|
385
|
-
} catch (err) {
|
|
386
|
-
this.logger.error("Error emitting event: " + (err?.stack ?? err?.message ?? err));
|
|
387
|
-
}
|
|
388
|
-
},
|
|
389
|
-
on: (_event, _callback) => {
|
|
390
|
-
},
|
|
391
|
-
off: (_event, _callback) => {
|
|
392
|
-
},
|
|
393
|
-
once: (_event, _callback) => {
|
|
394
|
-
}
|
|
395
|
-
};
|
|
396
|
-
const engine = new InngestExecutionEngine(this.#mastra, step, attempt);
|
|
1182
|
+
const pubsub = new InngestPubSub(this.inngest, this.id, publish);
|
|
1183
|
+
const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
|
|
397
1184
|
const result = await engine.execute({
|
|
398
1185
|
workflowId: this.id,
|
|
399
1186
|
runId,
|
|
@@ -401,14 +1188,35 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
401
1188
|
graph: this.executionGraph,
|
|
402
1189
|
serializedStepGraph: this.serializedStepGraph,
|
|
403
1190
|
input: inputData,
|
|
404
|
-
|
|
1191
|
+
initialState,
|
|
1192
|
+
pubsub,
|
|
405
1193
|
retryConfig: this.retryConfig,
|
|
406
|
-
|
|
407
|
-
// TODO
|
|
1194
|
+
requestContext: new di.RequestContext(Object.entries(event.data.requestContext ?? {})),
|
|
408
1195
|
resume,
|
|
1196
|
+
timeTravel,
|
|
1197
|
+
format,
|
|
409
1198
|
abortController: new AbortController(),
|
|
410
|
-
currentSpan:
|
|
411
|
-
|
|
1199
|
+
// currentSpan: undefined, // TODO: Pass actual parent Span from workflow execution context
|
|
1200
|
+
outputOptions,
|
|
1201
|
+
outputWriter: async (chunk) => {
|
|
1202
|
+
try {
|
|
1203
|
+
await pubsub.publish(`workflow.events.v2.${runId}`, {
|
|
1204
|
+
type: "watch",
|
|
1205
|
+
runId,
|
|
1206
|
+
data: chunk
|
|
1207
|
+
});
|
|
1208
|
+
} catch (err) {
|
|
1209
|
+
this.logger.debug?.("Failed to publish watch event:", err);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
});
|
|
1213
|
+
await step.run(`workflow.${this.id}.finalize`, async () => {
|
|
1214
|
+
if (result.status === "failed") {
|
|
1215
|
+
throw new inngest.NonRetriableError(`Workflow failed`, {
|
|
1216
|
+
cause: result
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
return result;
|
|
412
1220
|
});
|
|
413
1221
|
return { result, runId };
|
|
414
1222
|
}
|
|
@@ -432,63 +1240,147 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
|
|
|
432
1240
|
return [this.getFunction(), ...this.getNestedFunctions(this.executionGraph.steps)];
|
|
433
1241
|
}
|
|
434
1242
|
};
|
|
1243
|
+
function serve({
|
|
1244
|
+
mastra,
|
|
1245
|
+
inngest,
|
|
1246
|
+
functions: userFunctions = [],
|
|
1247
|
+
registerOptions
|
|
1248
|
+
}) {
|
|
1249
|
+
const wfs = mastra.listWorkflows();
|
|
1250
|
+
const workflowFunctions = Array.from(
|
|
1251
|
+
new Set(
|
|
1252
|
+
Object.values(wfs).flatMap((wf) => {
|
|
1253
|
+
if (wf instanceof InngestWorkflow) {
|
|
1254
|
+
wf.__registerMastra(mastra);
|
|
1255
|
+
return wf.getFunctions();
|
|
1256
|
+
}
|
|
1257
|
+
return [];
|
|
1258
|
+
})
|
|
1259
|
+
)
|
|
1260
|
+
);
|
|
1261
|
+
return hono.serve({
|
|
1262
|
+
...registerOptions,
|
|
1263
|
+
client: inngest,
|
|
1264
|
+
functions: [...workflowFunctions, ...userFunctions]
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// src/types.ts
|
|
1269
|
+
var _compatibilityCheck = true;
|
|
1270
|
+
|
|
1271
|
+
// src/index.ts
|
|
435
1272
|
function isAgent(params) {
|
|
436
1273
|
return params?.component === "AGENT";
|
|
437
1274
|
}
|
|
438
1275
|
function isTool(params) {
|
|
439
1276
|
return params instanceof tools.Tool;
|
|
440
1277
|
}
|
|
441
|
-
function
|
|
1278
|
+
function isInngestWorkflow(params) {
|
|
1279
|
+
return params instanceof InngestWorkflow;
|
|
1280
|
+
}
|
|
1281
|
+
function createStep(params, agentOptions) {
|
|
1282
|
+
if (isInngestWorkflow(params)) {
|
|
1283
|
+
return params;
|
|
1284
|
+
}
|
|
442
1285
|
if (isAgent(params)) {
|
|
1286
|
+
const outputSchema = agentOptions?.structuredOutput?.schema ?? zod.z.object({ text: zod.z.string() });
|
|
443
1287
|
return {
|
|
444
1288
|
id: params.name,
|
|
445
1289
|
description: params.getDescription(),
|
|
446
|
-
// @ts-ignore
|
|
447
1290
|
inputSchema: zod.z.object({
|
|
448
1291
|
prompt: zod.z.string()
|
|
1292
|
+
// resourceId: z.string().optional(),
|
|
1293
|
+
// threadId: z.string().optional(),
|
|
449
1294
|
}),
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
1295
|
+
outputSchema,
|
|
1296
|
+
execute: async ({
|
|
1297
|
+
inputData,
|
|
1298
|
+
runId,
|
|
1299
|
+
[_constants.PUBSUB_SYMBOL]: pubsub,
|
|
1300
|
+
[_constants.STREAM_FORMAT_SYMBOL]: streamFormat,
|
|
1301
|
+
requestContext,
|
|
1302
|
+
tracingContext,
|
|
1303
|
+
abortSignal,
|
|
1304
|
+
abort,
|
|
1305
|
+
writer
|
|
1306
|
+
}) => {
|
|
455
1307
|
let streamPromise = {};
|
|
456
1308
|
streamPromise.promise = new Promise((resolve, reject) => {
|
|
457
1309
|
streamPromise.resolve = resolve;
|
|
458
1310
|
streamPromise.reject = reject;
|
|
459
1311
|
});
|
|
1312
|
+
let structuredResult = null;
|
|
460
1313
|
const toolData = {
|
|
461
1314
|
name: params.name,
|
|
462
1315
|
args: inputData
|
|
463
1316
|
};
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
1317
|
+
let stream;
|
|
1318
|
+
if ((await params.getModel()).specificationVersion === "v1") {
|
|
1319
|
+
const { fullStream } = await params.streamLegacy(inputData.prompt, {
|
|
1320
|
+
...agentOptions ?? {},
|
|
1321
|
+
// resourceId: inputData.resourceId,
|
|
1322
|
+
// threadId: inputData.threadId,
|
|
1323
|
+
requestContext,
|
|
1324
|
+
tracingContext,
|
|
1325
|
+
onFinish: (result) => {
|
|
1326
|
+
const resultWithObject = result;
|
|
1327
|
+
if (agentOptions?.structuredOutput?.schema && resultWithObject.object) {
|
|
1328
|
+
structuredResult = resultWithObject.object;
|
|
1329
|
+
}
|
|
1330
|
+
streamPromise.resolve(result.text);
|
|
1331
|
+
void agentOptions?.onFinish?.(result);
|
|
1332
|
+
},
|
|
1333
|
+
abortSignal
|
|
1334
|
+
});
|
|
1335
|
+
stream = fullStream;
|
|
1336
|
+
} else {
|
|
1337
|
+
const modelOutput = await params.stream(inputData.prompt, {
|
|
1338
|
+
...agentOptions ?? {},
|
|
1339
|
+
requestContext,
|
|
1340
|
+
tracingContext,
|
|
1341
|
+
onFinish: (result) => {
|
|
1342
|
+
const resultWithObject = result;
|
|
1343
|
+
if (agentOptions?.structuredOutput?.schema && resultWithObject.object) {
|
|
1344
|
+
structuredResult = resultWithObject.object;
|
|
1345
|
+
}
|
|
1346
|
+
streamPromise.resolve(result.text);
|
|
1347
|
+
void agentOptions?.onFinish?.(result);
|
|
1348
|
+
},
|
|
1349
|
+
abortSignal
|
|
1350
|
+
});
|
|
1351
|
+
stream = modelOutput.fullStream;
|
|
1352
|
+
}
|
|
1353
|
+
if (streamFormat === "legacy") {
|
|
1354
|
+
await pubsub.publish(`workflow.events.v2.${runId}`, {
|
|
1355
|
+
type: "watch",
|
|
1356
|
+
runId,
|
|
1357
|
+
data: { type: "tool-call-streaming-start", ...toolData ?? {} }
|
|
1358
|
+
});
|
|
1359
|
+
for await (const chunk of stream) {
|
|
1360
|
+
if (chunk.type === "text-delta") {
|
|
1361
|
+
await pubsub.publish(`workflow.events.v2.${runId}`, {
|
|
1362
|
+
type: "watch",
|
|
1363
|
+
runId,
|
|
1364
|
+
data: { type: "tool-call-delta", ...toolData ?? {}, argsTextDelta: chunk.textDelta }
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
await pubsub.publish(`workflow.events.v2.${runId}`, {
|
|
1369
|
+
type: "watch",
|
|
1370
|
+
runId,
|
|
1371
|
+
data: { type: "tool-call-streaming-finish", ...toolData ?? {} }
|
|
1372
|
+
});
|
|
1373
|
+
} else {
|
|
1374
|
+
for await (const chunk of stream) {
|
|
1375
|
+
await writer.write(chunk);
|
|
1376
|
+
}
|
|
1377
|
+
}
|
|
472
1378
|
if (abortSignal.aborted) {
|
|
473
1379
|
return abort();
|
|
474
1380
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
...toolData ?? {}
|
|
478
|
-
});
|
|
479
|
-
for await (const chunk of fullStream) {
|
|
480
|
-
if (chunk.type === "text-delta") {
|
|
481
|
-
await emitter.emit("watch-v2", {
|
|
482
|
-
type: "tool-call-delta",
|
|
483
|
-
...toolData ?? {},
|
|
484
|
-
argsTextDelta: chunk.textDelta
|
|
485
|
-
});
|
|
486
|
-
}
|
|
1381
|
+
if (structuredResult !== null) {
|
|
1382
|
+
return structuredResult;
|
|
487
1383
|
}
|
|
488
|
-
await emitter.emit("watch-v2", {
|
|
489
|
-
type: "tool-call-streaming-finish",
|
|
490
|
-
...toolData ?? {}
|
|
491
|
-
});
|
|
492
1384
|
return {
|
|
493
1385
|
text: await streamPromise.promise
|
|
494
1386
|
};
|
|
@@ -502,20 +1394,38 @@ function createStep(params) {
|
|
|
502
1394
|
}
|
|
503
1395
|
return {
|
|
504
1396
|
// TODO: tool probably should have strong id type
|
|
505
|
-
// @ts-ignore
|
|
506
1397
|
id: params.id,
|
|
507
1398
|
description: params.description,
|
|
508
1399
|
inputSchema: params.inputSchema,
|
|
509
1400
|
outputSchema: params.outputSchema,
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
1401
|
+
suspendSchema: params.suspendSchema,
|
|
1402
|
+
resumeSchema: params.resumeSchema,
|
|
1403
|
+
execute: async ({
|
|
1404
|
+
inputData,
|
|
1405
|
+
mastra,
|
|
1406
|
+
requestContext,
|
|
1407
|
+
tracingContext,
|
|
1408
|
+
suspend,
|
|
1409
|
+
resumeData,
|
|
1410
|
+
runId,
|
|
1411
|
+
workflowId,
|
|
1412
|
+
state,
|
|
1413
|
+
setState
|
|
1414
|
+
}) => {
|
|
1415
|
+
const toolContext = {
|
|
1416
|
+
mastra,
|
|
1417
|
+
requestContext,
|
|
515
1418
|
tracingContext,
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
1419
|
+
workflow: {
|
|
1420
|
+
runId,
|
|
1421
|
+
resumeData,
|
|
1422
|
+
suspend,
|
|
1423
|
+
workflowId,
|
|
1424
|
+
state,
|
|
1425
|
+
setState
|
|
1426
|
+
}
|
|
1427
|
+
};
|
|
1428
|
+
return params.execute(inputData, toolContext);
|
|
519
1429
|
},
|
|
520
1430
|
component: "TOOL"
|
|
521
1431
|
};
|
|
@@ -533,7 +1443,10 @@ function createStep(params) {
|
|
|
533
1443
|
function init(inngest) {
|
|
534
1444
|
return {
|
|
535
1445
|
createWorkflow(params) {
|
|
536
|
-
return new InngestWorkflow(
|
|
1446
|
+
return new InngestWorkflow(
|
|
1447
|
+
params,
|
|
1448
|
+
inngest
|
|
1449
|
+
);
|
|
537
1450
|
},
|
|
538
1451
|
createStep,
|
|
539
1452
|
cloneStep(step, opts) {
|
|
@@ -542,7 +1455,12 @@ function init(inngest) {
|
|
|
542
1455
|
description: step.description,
|
|
543
1456
|
inputSchema: step.inputSchema,
|
|
544
1457
|
outputSchema: step.outputSchema,
|
|
1458
|
+
resumeSchema: step.resumeSchema,
|
|
1459
|
+
suspendSchema: step.suspendSchema,
|
|
1460
|
+
stateSchema: step.stateSchema,
|
|
545
1461
|
execute: step.execute,
|
|
1462
|
+
retries: step.retries,
|
|
1463
|
+
scorers: step.scorers,
|
|
546
1464
|
component: step.component
|
|
547
1465
|
};
|
|
548
1466
|
},
|
|
@@ -552,7 +1470,8 @@ function init(inngest) {
|
|
|
552
1470
|
inputSchema: workflow.inputSchema,
|
|
553
1471
|
outputSchema: workflow.outputSchema,
|
|
554
1472
|
steps: workflow.stepDefs,
|
|
555
|
-
mastra: workflow.mastra
|
|
1473
|
+
mastra: workflow.mastra,
|
|
1474
|
+
options: workflow.options
|
|
556
1475
|
});
|
|
557
1476
|
wf.setStepFlow(workflow.stepGraph);
|
|
558
1477
|
wf.commit();
|
|
@@ -560,837 +1479,12 @@ function init(inngest) {
|
|
|
560
1479
|
}
|
|
561
1480
|
};
|
|
562
1481
|
}
|
|
563
|
-
var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
|
|
564
|
-
inngestStep;
|
|
565
|
-
inngestAttempts;
|
|
566
|
-
constructor(mastra, inngestStep, inngestAttempts = 0, options) {
|
|
567
|
-
super({ mastra, options });
|
|
568
|
-
this.inngestStep = inngestStep;
|
|
569
|
-
this.inngestAttempts = inngestAttempts;
|
|
570
|
-
}
|
|
571
|
-
async execute(params) {
|
|
572
|
-
await params.emitter.emit("watch-v2", {
|
|
573
|
-
type: "workflow-start",
|
|
574
|
-
payload: { runId: params.runId }
|
|
575
|
-
});
|
|
576
|
-
const result = await super.execute(params);
|
|
577
|
-
await params.emitter.emit("watch-v2", {
|
|
578
|
-
type: "workflow-finish",
|
|
579
|
-
payload: { runId: params.runId }
|
|
580
|
-
});
|
|
581
|
-
return result;
|
|
582
|
-
}
|
|
583
|
-
async fmtReturnValue(executionSpan, emitter, stepResults, lastOutput, error) {
|
|
584
|
-
const base = {
|
|
585
|
-
status: lastOutput.status,
|
|
586
|
-
steps: stepResults
|
|
587
|
-
};
|
|
588
|
-
if (lastOutput.status === "success") {
|
|
589
|
-
await emitter.emit("watch", {
|
|
590
|
-
type: "watch",
|
|
591
|
-
payload: {
|
|
592
|
-
workflowState: {
|
|
593
|
-
status: lastOutput.status,
|
|
594
|
-
steps: stepResults,
|
|
595
|
-
result: lastOutput.output
|
|
596
|
-
}
|
|
597
|
-
},
|
|
598
|
-
eventTimestamp: Date.now()
|
|
599
|
-
});
|
|
600
|
-
base.result = lastOutput.output;
|
|
601
|
-
} else if (lastOutput.status === "failed") {
|
|
602
|
-
base.error = error instanceof Error ? error?.stack ?? error.message : lastOutput?.error instanceof Error ? lastOutput.error.message : lastOutput.error ?? error ?? "Unknown error";
|
|
603
|
-
await emitter.emit("watch", {
|
|
604
|
-
type: "watch",
|
|
605
|
-
payload: {
|
|
606
|
-
workflowState: {
|
|
607
|
-
status: lastOutput.status,
|
|
608
|
-
steps: stepResults,
|
|
609
|
-
result: null,
|
|
610
|
-
error: base.error
|
|
611
|
-
}
|
|
612
|
-
},
|
|
613
|
-
eventTimestamp: Date.now()
|
|
614
|
-
});
|
|
615
|
-
} else if (lastOutput.status === "suspended") {
|
|
616
|
-
await emitter.emit("watch", {
|
|
617
|
-
type: "watch",
|
|
618
|
-
payload: {
|
|
619
|
-
workflowState: {
|
|
620
|
-
status: lastOutput.status,
|
|
621
|
-
steps: stepResults,
|
|
622
|
-
result: null,
|
|
623
|
-
error: null
|
|
624
|
-
}
|
|
625
|
-
},
|
|
626
|
-
eventTimestamp: Date.now()
|
|
627
|
-
});
|
|
628
|
-
const suspendedStepIds = Object.entries(stepResults).flatMap(([stepId, stepResult]) => {
|
|
629
|
-
if (stepResult?.status === "suspended") {
|
|
630
|
-
const nestedPath = stepResult?.payload?.__workflow_meta?.path;
|
|
631
|
-
return nestedPath ? [[stepId, ...nestedPath]] : [[stepId]];
|
|
632
|
-
}
|
|
633
|
-
return [];
|
|
634
|
-
});
|
|
635
|
-
base.suspended = suspendedStepIds;
|
|
636
|
-
}
|
|
637
|
-
executionSpan?.end();
|
|
638
|
-
return base;
|
|
639
|
-
}
|
|
640
|
-
// async executeSleep({ id, duration }: { id: string; duration: number }): Promise<void> {
|
|
641
|
-
// await this.inngestStep.sleep(id, duration);
|
|
642
|
-
// }
|
|
643
|
-
async executeSleep({
|
|
644
|
-
workflowId,
|
|
645
|
-
runId,
|
|
646
|
-
entry,
|
|
647
|
-
prevOutput,
|
|
648
|
-
stepResults,
|
|
649
|
-
emitter,
|
|
650
|
-
abortController,
|
|
651
|
-
runtimeContext,
|
|
652
|
-
executionContext,
|
|
653
|
-
writableStream,
|
|
654
|
-
tracingContext
|
|
655
|
-
}) {
|
|
656
|
-
let { duration, fn } = entry;
|
|
657
|
-
const sleepSpan = tracingContext?.currentSpan?.createChildSpan({
|
|
658
|
-
type: aiTracing.AISpanType.WORKFLOW_SLEEP,
|
|
659
|
-
name: `sleep: ${duration ? `${duration}ms` : "dynamic"}`,
|
|
660
|
-
attributes: {
|
|
661
|
-
durationMs: duration,
|
|
662
|
-
sleepType: fn ? "dynamic" : "fixed"
|
|
663
|
-
},
|
|
664
|
-
tracingPolicy: this.options?.tracingPolicy
|
|
665
|
-
});
|
|
666
|
-
if (fn) {
|
|
667
|
-
const stepCallId = crypto.randomUUID();
|
|
668
|
-
duration = await this.inngestStep.run(`workflow.${workflowId}.sleep.${entry.id}`, async () => {
|
|
669
|
-
return await fn({
|
|
670
|
-
runId,
|
|
671
|
-
workflowId,
|
|
672
|
-
mastra: this.mastra,
|
|
673
|
-
runtimeContext,
|
|
674
|
-
inputData: prevOutput,
|
|
675
|
-
runCount: -1,
|
|
676
|
-
tracingContext: {
|
|
677
|
-
currentSpan: sleepSpan
|
|
678
|
-
},
|
|
679
|
-
getInitData: () => stepResults?.input,
|
|
680
|
-
getStepResult: workflows.getStepResult.bind(this, stepResults),
|
|
681
|
-
// TODO: this function shouldn't have suspend probably?
|
|
682
|
-
suspend: async (_suspendPayload) => {
|
|
683
|
-
},
|
|
684
|
-
bail: () => {
|
|
685
|
-
},
|
|
686
|
-
abort: () => {
|
|
687
|
-
abortController?.abort();
|
|
688
|
-
},
|
|
689
|
-
[_constants.EMITTER_SYMBOL]: emitter,
|
|
690
|
-
// TODO: add streamVNext support
|
|
691
|
-
[_constants.STREAM_FORMAT_SYMBOL]: executionContext.format,
|
|
692
|
-
engine: { step: this.inngestStep },
|
|
693
|
-
abortSignal: abortController?.signal,
|
|
694
|
-
writer: new tools.ToolStream(
|
|
695
|
-
{
|
|
696
|
-
prefix: "workflow-step",
|
|
697
|
-
callId: stepCallId,
|
|
698
|
-
name: "sleep",
|
|
699
|
-
runId
|
|
700
|
-
},
|
|
701
|
-
writableStream
|
|
702
|
-
)
|
|
703
|
-
});
|
|
704
|
-
});
|
|
705
|
-
sleepSpan?.update({
|
|
706
|
-
attributes: {
|
|
707
|
-
durationMs: duration
|
|
708
|
-
}
|
|
709
|
-
});
|
|
710
|
-
}
|
|
711
|
-
try {
|
|
712
|
-
await this.inngestStep.sleep(entry.id, !duration || duration < 0 ? 0 : duration);
|
|
713
|
-
sleepSpan?.end();
|
|
714
|
-
} catch (e) {
|
|
715
|
-
sleepSpan?.error({ error: e });
|
|
716
|
-
throw e;
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
async executeSleepUntil({
|
|
720
|
-
workflowId,
|
|
721
|
-
runId,
|
|
722
|
-
entry,
|
|
723
|
-
prevOutput,
|
|
724
|
-
stepResults,
|
|
725
|
-
emitter,
|
|
726
|
-
abortController,
|
|
727
|
-
runtimeContext,
|
|
728
|
-
executionContext,
|
|
729
|
-
writableStream,
|
|
730
|
-
tracingContext
|
|
731
|
-
}) {
|
|
732
|
-
let { date, fn } = entry;
|
|
733
|
-
const sleepUntilSpan = tracingContext?.currentSpan?.createChildSpan({
|
|
734
|
-
type: aiTracing.AISpanType.WORKFLOW_SLEEP,
|
|
735
|
-
name: `sleepUntil: ${date ? date.toISOString() : "dynamic"}`,
|
|
736
|
-
attributes: {
|
|
737
|
-
untilDate: date,
|
|
738
|
-
durationMs: date ? Math.max(0, date.getTime() - Date.now()) : void 0,
|
|
739
|
-
sleepType: fn ? "dynamic" : "fixed"
|
|
740
|
-
},
|
|
741
|
-
tracingPolicy: this.options?.tracingPolicy
|
|
742
|
-
});
|
|
743
|
-
if (fn) {
|
|
744
|
-
date = await this.inngestStep.run(`workflow.${workflowId}.sleepUntil.${entry.id}`, async () => {
|
|
745
|
-
const stepCallId = crypto.randomUUID();
|
|
746
|
-
return await fn({
|
|
747
|
-
runId,
|
|
748
|
-
workflowId,
|
|
749
|
-
mastra: this.mastra,
|
|
750
|
-
runtimeContext,
|
|
751
|
-
inputData: prevOutput,
|
|
752
|
-
runCount: -1,
|
|
753
|
-
tracingContext: {
|
|
754
|
-
currentSpan: sleepUntilSpan
|
|
755
|
-
},
|
|
756
|
-
getInitData: () => stepResults?.input,
|
|
757
|
-
getStepResult: workflows.getStepResult.bind(this, stepResults),
|
|
758
|
-
// TODO: this function shouldn't have suspend probably?
|
|
759
|
-
suspend: async (_suspendPayload) => {
|
|
760
|
-
},
|
|
761
|
-
bail: () => {
|
|
762
|
-
},
|
|
763
|
-
abort: () => {
|
|
764
|
-
abortController?.abort();
|
|
765
|
-
},
|
|
766
|
-
[_constants.EMITTER_SYMBOL]: emitter,
|
|
767
|
-
[_constants.STREAM_FORMAT_SYMBOL]: executionContext.format,
|
|
768
|
-
// TODO: add streamVNext support
|
|
769
|
-
engine: { step: this.inngestStep },
|
|
770
|
-
abortSignal: abortController?.signal,
|
|
771
|
-
writer: new tools.ToolStream(
|
|
772
|
-
{
|
|
773
|
-
prefix: "workflow-step",
|
|
774
|
-
callId: stepCallId,
|
|
775
|
-
name: "sleep",
|
|
776
|
-
runId
|
|
777
|
-
},
|
|
778
|
-
writableStream
|
|
779
|
-
)
|
|
780
|
-
});
|
|
781
|
-
});
|
|
782
|
-
if (date && !(date instanceof Date)) {
|
|
783
|
-
date = new Date(date);
|
|
784
|
-
}
|
|
785
|
-
const time = !date ? 0 : date.getTime() - Date.now();
|
|
786
|
-
sleepUntilSpan?.update({
|
|
787
|
-
attributes: {
|
|
788
|
-
durationMs: Math.max(0, time)
|
|
789
|
-
}
|
|
790
|
-
});
|
|
791
|
-
}
|
|
792
|
-
if (!(date instanceof Date)) {
|
|
793
|
-
sleepUntilSpan?.end();
|
|
794
|
-
return;
|
|
795
|
-
}
|
|
796
|
-
try {
|
|
797
|
-
await this.inngestStep.sleepUntil(entry.id, date);
|
|
798
|
-
sleepUntilSpan?.end();
|
|
799
|
-
} catch (e) {
|
|
800
|
-
sleepUntilSpan?.error({ error: e });
|
|
801
|
-
throw e;
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
async executeWaitForEvent({ event, timeout }) {
|
|
805
|
-
const eventData = await this.inngestStep.waitForEvent(`user-event-${event}`, {
|
|
806
|
-
event: `user-event-${event}`,
|
|
807
|
-
timeout: timeout ?? 5e3
|
|
808
|
-
});
|
|
809
|
-
if (eventData === null) {
|
|
810
|
-
throw "Timeout waiting for event";
|
|
811
|
-
}
|
|
812
|
-
return eventData?.data;
|
|
813
|
-
}
|
|
814
|
-
async executeStep({
|
|
815
|
-
step,
|
|
816
|
-
stepResults,
|
|
817
|
-
executionContext,
|
|
818
|
-
resume,
|
|
819
|
-
prevOutput,
|
|
820
|
-
emitter,
|
|
821
|
-
abortController,
|
|
822
|
-
runtimeContext,
|
|
823
|
-
tracingContext,
|
|
824
|
-
writableStream,
|
|
825
|
-
disableScorers
|
|
826
|
-
}) {
|
|
827
|
-
const stepAISpan = tracingContext?.currentSpan?.createChildSpan({
|
|
828
|
-
name: `workflow step: '${step.id}'`,
|
|
829
|
-
type: aiTracing.AISpanType.WORKFLOW_STEP,
|
|
830
|
-
input: prevOutput,
|
|
831
|
-
attributes: {
|
|
832
|
-
stepId: step.id
|
|
833
|
-
},
|
|
834
|
-
tracingPolicy: this.options?.tracingPolicy
|
|
835
|
-
});
|
|
836
|
-
const { inputData, validationError } = await workflows.validateStepInput({
|
|
837
|
-
prevOutput,
|
|
838
|
-
step,
|
|
839
|
-
validateInputs: this.options?.validateInputs ?? false
|
|
840
|
-
});
|
|
841
|
-
const startedAt = await this.inngestStep.run(
|
|
842
|
-
`workflow.${executionContext.workflowId}.run.${executionContext.runId}.step.${step.id}.running_ev`,
|
|
843
|
-
async () => {
|
|
844
|
-
const startedAt2 = Date.now();
|
|
845
|
-
await emitter.emit("watch", {
|
|
846
|
-
type: "watch",
|
|
847
|
-
payload: {
|
|
848
|
-
currentStep: {
|
|
849
|
-
id: step.id,
|
|
850
|
-
status: "running"
|
|
851
|
-
},
|
|
852
|
-
workflowState: {
|
|
853
|
-
status: "running",
|
|
854
|
-
steps: {
|
|
855
|
-
...stepResults,
|
|
856
|
-
[step.id]: {
|
|
857
|
-
status: "running"
|
|
858
|
-
}
|
|
859
|
-
},
|
|
860
|
-
result: null,
|
|
861
|
-
error: null
|
|
862
|
-
}
|
|
863
|
-
},
|
|
864
|
-
eventTimestamp: Date.now()
|
|
865
|
-
});
|
|
866
|
-
await emitter.emit("watch-v2", {
|
|
867
|
-
type: "workflow-step-start",
|
|
868
|
-
payload: {
|
|
869
|
-
id: step.id,
|
|
870
|
-
status: "running",
|
|
871
|
-
payload: inputData,
|
|
872
|
-
startedAt: startedAt2
|
|
873
|
-
}
|
|
874
|
-
});
|
|
875
|
-
return startedAt2;
|
|
876
|
-
}
|
|
877
|
-
);
|
|
878
|
-
if (step instanceof InngestWorkflow) {
|
|
879
|
-
const isResume = !!resume?.steps?.length;
|
|
880
|
-
let result;
|
|
881
|
-
let runId;
|
|
882
|
-
if (isResume) {
|
|
883
|
-
runId = stepResults[resume?.steps?.[0]]?.payload?.__workflow_meta?.runId ?? crypto.randomUUID();
|
|
884
|
-
const snapshot = await this.mastra?.getStorage()?.loadWorkflowSnapshot({
|
|
885
|
-
workflowName: step.id,
|
|
886
|
-
runId
|
|
887
|
-
});
|
|
888
|
-
const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
|
|
889
|
-
function: step.getFunction(),
|
|
890
|
-
data: {
|
|
891
|
-
inputData,
|
|
892
|
-
runId,
|
|
893
|
-
resume: {
|
|
894
|
-
runId,
|
|
895
|
-
steps: resume.steps.slice(1),
|
|
896
|
-
stepResults: snapshot?.context,
|
|
897
|
-
resumePayload: resume.resumePayload,
|
|
898
|
-
// @ts-ignore
|
|
899
|
-
resumePath: snapshot?.suspendedPaths?.[resume.steps?.[1]]
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
});
|
|
903
|
-
result = invokeResp.result;
|
|
904
|
-
runId = invokeResp.runId;
|
|
905
|
-
} else {
|
|
906
|
-
const invokeResp = await this.inngestStep.invoke(`workflow.${executionContext.workflowId}.step.${step.id}`, {
|
|
907
|
-
function: step.getFunction(),
|
|
908
|
-
data: {
|
|
909
|
-
inputData
|
|
910
|
-
}
|
|
911
|
-
});
|
|
912
|
-
result = invokeResp.result;
|
|
913
|
-
runId = invokeResp.runId;
|
|
914
|
-
}
|
|
915
|
-
const res = await this.inngestStep.run(
|
|
916
|
-
`workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
|
|
917
|
-
async () => {
|
|
918
|
-
if (result.status === "failed") {
|
|
919
|
-
await emitter.emit("watch", {
|
|
920
|
-
type: "watch",
|
|
921
|
-
payload: {
|
|
922
|
-
currentStep: {
|
|
923
|
-
id: step.id,
|
|
924
|
-
status: "failed",
|
|
925
|
-
error: result?.error
|
|
926
|
-
},
|
|
927
|
-
workflowState: {
|
|
928
|
-
status: "running",
|
|
929
|
-
steps: stepResults,
|
|
930
|
-
result: null,
|
|
931
|
-
error: null
|
|
932
|
-
}
|
|
933
|
-
},
|
|
934
|
-
eventTimestamp: Date.now()
|
|
935
|
-
});
|
|
936
|
-
await emitter.emit("watch-v2", {
|
|
937
|
-
type: "workflow-step-result",
|
|
938
|
-
payload: {
|
|
939
|
-
id: step.id,
|
|
940
|
-
status: "failed",
|
|
941
|
-
error: result?.error,
|
|
942
|
-
payload: prevOutput
|
|
943
|
-
}
|
|
944
|
-
});
|
|
945
|
-
return { executionContext, result: { status: "failed", error: result?.error } };
|
|
946
|
-
} else if (result.status === "suspended") {
|
|
947
|
-
const suspendedSteps = Object.entries(result.steps).filter(([_stepName, stepResult]) => {
|
|
948
|
-
const stepRes2 = stepResult;
|
|
949
|
-
return stepRes2?.status === "suspended";
|
|
950
|
-
});
|
|
951
|
-
for (const [stepName, stepResult] of suspendedSteps) {
|
|
952
|
-
const suspendPath = [stepName, ...stepResult?.payload?.__workflow_meta?.path ?? []];
|
|
953
|
-
executionContext.suspendedPaths[step.id] = executionContext.executionPath;
|
|
954
|
-
await emitter.emit("watch", {
|
|
955
|
-
type: "watch",
|
|
956
|
-
payload: {
|
|
957
|
-
currentStep: {
|
|
958
|
-
id: step.id,
|
|
959
|
-
status: "suspended",
|
|
960
|
-
payload: { ...stepResult?.payload, __workflow_meta: { runId, path: suspendPath } }
|
|
961
|
-
},
|
|
962
|
-
workflowState: {
|
|
963
|
-
status: "running",
|
|
964
|
-
steps: stepResults,
|
|
965
|
-
result: null,
|
|
966
|
-
error: null
|
|
967
|
-
}
|
|
968
|
-
},
|
|
969
|
-
eventTimestamp: Date.now()
|
|
970
|
-
});
|
|
971
|
-
await emitter.emit("watch-v2", {
|
|
972
|
-
type: "workflow-step-suspended",
|
|
973
|
-
payload: {
|
|
974
|
-
id: step.id,
|
|
975
|
-
status: "suspended"
|
|
976
|
-
}
|
|
977
|
-
});
|
|
978
|
-
return {
|
|
979
|
-
executionContext,
|
|
980
|
-
result: {
|
|
981
|
-
status: "suspended",
|
|
982
|
-
payload: { ...stepResult?.payload, __workflow_meta: { runId, path: suspendPath } }
|
|
983
|
-
}
|
|
984
|
-
};
|
|
985
|
-
}
|
|
986
|
-
await emitter.emit("watch", {
|
|
987
|
-
type: "watch",
|
|
988
|
-
payload: {
|
|
989
|
-
currentStep: {
|
|
990
|
-
id: step.id,
|
|
991
|
-
status: "suspended",
|
|
992
|
-
payload: {}
|
|
993
|
-
},
|
|
994
|
-
workflowState: {
|
|
995
|
-
status: "running",
|
|
996
|
-
steps: stepResults,
|
|
997
|
-
result: null,
|
|
998
|
-
error: null
|
|
999
|
-
}
|
|
1000
|
-
},
|
|
1001
|
-
eventTimestamp: Date.now()
|
|
1002
|
-
});
|
|
1003
|
-
return {
|
|
1004
|
-
executionContext,
|
|
1005
|
-
result: {
|
|
1006
|
-
status: "suspended",
|
|
1007
|
-
payload: {}
|
|
1008
|
-
}
|
|
1009
|
-
};
|
|
1010
|
-
}
|
|
1011
|
-
await emitter.emit("watch", {
|
|
1012
|
-
type: "watch",
|
|
1013
|
-
payload: {
|
|
1014
|
-
currentStep: {
|
|
1015
|
-
id: step.id,
|
|
1016
|
-
status: "success",
|
|
1017
|
-
output: result?.result
|
|
1018
|
-
},
|
|
1019
|
-
workflowState: {
|
|
1020
|
-
status: "running",
|
|
1021
|
-
steps: stepResults,
|
|
1022
|
-
result: null,
|
|
1023
|
-
error: null
|
|
1024
|
-
}
|
|
1025
|
-
},
|
|
1026
|
-
eventTimestamp: Date.now()
|
|
1027
|
-
});
|
|
1028
|
-
await emitter.emit("watch-v2", {
|
|
1029
|
-
type: "workflow-step-result",
|
|
1030
|
-
payload: {
|
|
1031
|
-
id: step.id,
|
|
1032
|
-
status: "success",
|
|
1033
|
-
output: result?.result
|
|
1034
|
-
}
|
|
1035
|
-
});
|
|
1036
|
-
await emitter.emit("watch-v2", {
|
|
1037
|
-
type: "workflow-step-finish",
|
|
1038
|
-
payload: {
|
|
1039
|
-
id: step.id,
|
|
1040
|
-
metadata: {}
|
|
1041
|
-
}
|
|
1042
|
-
});
|
|
1043
|
-
return { executionContext, result: { status: "success", output: result?.result } };
|
|
1044
|
-
}
|
|
1045
|
-
);
|
|
1046
|
-
Object.assign(executionContext, res.executionContext);
|
|
1047
|
-
return res.result;
|
|
1048
|
-
}
|
|
1049
|
-
const stepRes = await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}`, async () => {
|
|
1050
|
-
let execResults;
|
|
1051
|
-
let suspended;
|
|
1052
|
-
let bailed;
|
|
1053
|
-
try {
|
|
1054
|
-
if (validationError) {
|
|
1055
|
-
throw validationError;
|
|
1056
|
-
}
|
|
1057
|
-
const result = await step.execute({
|
|
1058
|
-
runId: executionContext.runId,
|
|
1059
|
-
mastra: this.mastra,
|
|
1060
|
-
runtimeContext,
|
|
1061
|
-
writableStream,
|
|
1062
|
-
inputData,
|
|
1063
|
-
resumeData: resume?.steps[0] === step.id ? resume?.resumePayload : void 0,
|
|
1064
|
-
tracingContext: {
|
|
1065
|
-
currentSpan: stepAISpan
|
|
1066
|
-
},
|
|
1067
|
-
getInitData: () => stepResults?.input,
|
|
1068
|
-
getStepResult: workflows.getStepResult.bind(this, stepResults),
|
|
1069
|
-
suspend: async (suspendPayload) => {
|
|
1070
|
-
executionContext.suspendedPaths[step.id] = executionContext.executionPath;
|
|
1071
|
-
suspended = { payload: suspendPayload };
|
|
1072
|
-
},
|
|
1073
|
-
bail: (result2) => {
|
|
1074
|
-
bailed = { payload: result2 };
|
|
1075
|
-
},
|
|
1076
|
-
resume: {
|
|
1077
|
-
steps: resume?.steps?.slice(1) || [],
|
|
1078
|
-
resumePayload: resume?.resumePayload,
|
|
1079
|
-
// @ts-ignore
|
|
1080
|
-
runId: stepResults[step.id]?.payload?.__workflow_meta?.runId
|
|
1081
|
-
},
|
|
1082
|
-
[_constants.EMITTER_SYMBOL]: emitter,
|
|
1083
|
-
engine: {
|
|
1084
|
-
step: this.inngestStep
|
|
1085
|
-
},
|
|
1086
|
-
abortSignal: abortController.signal
|
|
1087
|
-
});
|
|
1088
|
-
const endedAt = Date.now();
|
|
1089
|
-
execResults = {
|
|
1090
|
-
status: "success",
|
|
1091
|
-
output: result,
|
|
1092
|
-
startedAt,
|
|
1093
|
-
endedAt,
|
|
1094
|
-
payload: inputData,
|
|
1095
|
-
resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
|
|
1096
|
-
resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
|
|
1097
|
-
};
|
|
1098
|
-
} catch (e) {
|
|
1099
|
-
execResults = {
|
|
1100
|
-
status: "failed",
|
|
1101
|
-
payload: inputData,
|
|
1102
|
-
error: e instanceof Error ? e.message : String(e),
|
|
1103
|
-
endedAt: Date.now(),
|
|
1104
|
-
startedAt,
|
|
1105
|
-
resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
|
|
1106
|
-
resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
|
|
1107
|
-
};
|
|
1108
|
-
}
|
|
1109
|
-
if (suspended) {
|
|
1110
|
-
execResults = {
|
|
1111
|
-
status: "suspended",
|
|
1112
|
-
suspendedPayload: suspended.payload,
|
|
1113
|
-
payload: inputData,
|
|
1114
|
-
suspendedAt: Date.now(),
|
|
1115
|
-
startedAt,
|
|
1116
|
-
resumedAt: resume?.steps[0] === step.id ? startedAt : void 0,
|
|
1117
|
-
resumePayload: resume?.steps[0] === step.id ? resume?.resumePayload : void 0
|
|
1118
|
-
};
|
|
1119
|
-
} else if (bailed) {
|
|
1120
|
-
execResults = { status: "bailed", output: bailed.payload, payload: inputData, endedAt: Date.now(), startedAt };
|
|
1121
|
-
}
|
|
1122
|
-
if (execResults.status === "failed") {
|
|
1123
|
-
if (executionContext.retryConfig.attempts > 0 && this.inngestAttempts < executionContext.retryConfig.attempts) {
|
|
1124
|
-
const error = new Error(execResults.error);
|
|
1125
|
-
stepAISpan?.error({ error });
|
|
1126
|
-
throw error;
|
|
1127
|
-
}
|
|
1128
|
-
}
|
|
1129
|
-
await emitter.emit("watch", {
|
|
1130
|
-
type: "watch",
|
|
1131
|
-
payload: {
|
|
1132
|
-
currentStep: {
|
|
1133
|
-
id: step.id,
|
|
1134
|
-
...execResults
|
|
1135
|
-
},
|
|
1136
|
-
workflowState: {
|
|
1137
|
-
status: "running",
|
|
1138
|
-
steps: { ...stepResults, [step.id]: execResults },
|
|
1139
|
-
result: null,
|
|
1140
|
-
error: null
|
|
1141
|
-
}
|
|
1142
|
-
},
|
|
1143
|
-
eventTimestamp: Date.now()
|
|
1144
|
-
});
|
|
1145
|
-
if (execResults.status === "suspended") {
|
|
1146
|
-
await emitter.emit("watch-v2", {
|
|
1147
|
-
type: "workflow-step-suspended",
|
|
1148
|
-
payload: {
|
|
1149
|
-
id: step.id,
|
|
1150
|
-
...execResults
|
|
1151
|
-
}
|
|
1152
|
-
});
|
|
1153
|
-
} else {
|
|
1154
|
-
await emitter.emit("watch-v2", {
|
|
1155
|
-
type: "workflow-step-result",
|
|
1156
|
-
payload: {
|
|
1157
|
-
id: step.id,
|
|
1158
|
-
...execResults
|
|
1159
|
-
}
|
|
1160
|
-
});
|
|
1161
|
-
await emitter.emit("watch-v2", {
|
|
1162
|
-
type: "workflow-step-finish",
|
|
1163
|
-
payload: {
|
|
1164
|
-
id: step.id,
|
|
1165
|
-
metadata: {}
|
|
1166
|
-
}
|
|
1167
|
-
});
|
|
1168
|
-
}
|
|
1169
|
-
stepAISpan?.end({ output: execResults });
|
|
1170
|
-
return { result: execResults, executionContext, stepResults };
|
|
1171
|
-
});
|
|
1172
|
-
if (disableScorers !== false) {
|
|
1173
|
-
await this.inngestStep.run(`workflow.${executionContext.workflowId}.step.${step.id}.score`, async () => {
|
|
1174
|
-
if (step.scorers) {
|
|
1175
|
-
await this.runScorers({
|
|
1176
|
-
scorers: step.scorers,
|
|
1177
|
-
runId: executionContext.runId,
|
|
1178
|
-
input: inputData,
|
|
1179
|
-
output: stepRes.result,
|
|
1180
|
-
workflowId: executionContext.workflowId,
|
|
1181
|
-
stepId: step.id,
|
|
1182
|
-
runtimeContext,
|
|
1183
|
-
disableScorers,
|
|
1184
|
-
tracingContext: { currentSpan: stepAISpan }
|
|
1185
|
-
});
|
|
1186
|
-
}
|
|
1187
|
-
});
|
|
1188
|
-
}
|
|
1189
|
-
Object.assign(executionContext.suspendedPaths, stepRes.executionContext.suspendedPaths);
|
|
1190
|
-
Object.assign(stepResults, stepRes.stepResults);
|
|
1191
|
-
return stepRes.result;
|
|
1192
|
-
}
|
|
1193
|
-
async persistStepUpdate({
|
|
1194
|
-
workflowId,
|
|
1195
|
-
runId,
|
|
1196
|
-
stepResults,
|
|
1197
|
-
resourceId,
|
|
1198
|
-
executionContext,
|
|
1199
|
-
serializedStepGraph,
|
|
1200
|
-
workflowStatus,
|
|
1201
|
-
result,
|
|
1202
|
-
error
|
|
1203
|
-
}) {
|
|
1204
|
-
await this.inngestStep.run(
|
|
1205
|
-
`workflow.${workflowId}.run.${runId}.path.${JSON.stringify(executionContext.executionPath)}.stepUpdate`,
|
|
1206
|
-
async () => {
|
|
1207
|
-
await this.mastra?.getStorage()?.persistWorkflowSnapshot({
|
|
1208
|
-
workflowName: workflowId,
|
|
1209
|
-
runId,
|
|
1210
|
-
resourceId,
|
|
1211
|
-
snapshot: {
|
|
1212
|
-
runId,
|
|
1213
|
-
value: {},
|
|
1214
|
-
context: stepResults,
|
|
1215
|
-
activePaths: [],
|
|
1216
|
-
suspendedPaths: executionContext.suspendedPaths,
|
|
1217
|
-
waitingPaths: {},
|
|
1218
|
-
serializedStepGraph,
|
|
1219
|
-
status: workflowStatus,
|
|
1220
|
-
result,
|
|
1221
|
-
error,
|
|
1222
|
-
// @ts-ignore
|
|
1223
|
-
timestamp: Date.now()
|
|
1224
|
-
}
|
|
1225
|
-
});
|
|
1226
|
-
}
|
|
1227
|
-
);
|
|
1228
|
-
}
|
|
1229
|
-
async executeConditional({
|
|
1230
|
-
workflowId,
|
|
1231
|
-
runId,
|
|
1232
|
-
entry,
|
|
1233
|
-
prevOutput,
|
|
1234
|
-
prevStep,
|
|
1235
|
-
stepResults,
|
|
1236
|
-
serializedStepGraph,
|
|
1237
|
-
resume,
|
|
1238
|
-
executionContext,
|
|
1239
|
-
emitter,
|
|
1240
|
-
abortController,
|
|
1241
|
-
runtimeContext,
|
|
1242
|
-
writableStream,
|
|
1243
|
-
disableScorers,
|
|
1244
|
-
tracingContext
|
|
1245
|
-
}) {
|
|
1246
|
-
const conditionalSpan = tracingContext?.currentSpan?.createChildSpan({
|
|
1247
|
-
type: aiTracing.AISpanType.WORKFLOW_CONDITIONAL,
|
|
1248
|
-
name: `conditional: '${entry.conditions.length} conditions'`,
|
|
1249
|
-
input: prevOutput,
|
|
1250
|
-
attributes: {
|
|
1251
|
-
conditionCount: entry.conditions.length
|
|
1252
|
-
},
|
|
1253
|
-
tracingPolicy: this.options?.tracingPolicy
|
|
1254
|
-
});
|
|
1255
|
-
let execResults;
|
|
1256
|
-
const truthyIndexes = (await Promise.all(
|
|
1257
|
-
entry.conditions.map(
|
|
1258
|
-
(cond, index) => this.inngestStep.run(`workflow.${workflowId}.conditional.${index}`, async () => {
|
|
1259
|
-
const evalSpan = conditionalSpan?.createChildSpan({
|
|
1260
|
-
type: aiTracing.AISpanType.WORKFLOW_CONDITIONAL_EVAL,
|
|
1261
|
-
name: `condition: '${index}'`,
|
|
1262
|
-
input: prevOutput,
|
|
1263
|
-
attributes: {
|
|
1264
|
-
conditionIndex: index
|
|
1265
|
-
},
|
|
1266
|
-
tracingPolicy: this.options?.tracingPolicy
|
|
1267
|
-
});
|
|
1268
|
-
try {
|
|
1269
|
-
const result = await cond({
|
|
1270
|
-
runId,
|
|
1271
|
-
workflowId,
|
|
1272
|
-
mastra: this.mastra,
|
|
1273
|
-
runtimeContext,
|
|
1274
|
-
runCount: -1,
|
|
1275
|
-
inputData: prevOutput,
|
|
1276
|
-
tracingContext: {
|
|
1277
|
-
currentSpan: evalSpan
|
|
1278
|
-
},
|
|
1279
|
-
getInitData: () => stepResults?.input,
|
|
1280
|
-
getStepResult: workflows.getStepResult.bind(this, stepResults),
|
|
1281
|
-
// TODO: this function shouldn't have suspend probably?
|
|
1282
|
-
suspend: async (_suspendPayload) => {
|
|
1283
|
-
},
|
|
1284
|
-
bail: () => {
|
|
1285
|
-
},
|
|
1286
|
-
abort: () => {
|
|
1287
|
-
abortController.abort();
|
|
1288
|
-
},
|
|
1289
|
-
[_constants.EMITTER_SYMBOL]: emitter,
|
|
1290
|
-
[_constants.STREAM_FORMAT_SYMBOL]: executionContext.format,
|
|
1291
|
-
// TODO: add streamVNext support
|
|
1292
|
-
engine: {
|
|
1293
|
-
step: this.inngestStep
|
|
1294
|
-
},
|
|
1295
|
-
abortSignal: abortController.signal,
|
|
1296
|
-
writer: new tools.ToolStream(
|
|
1297
|
-
{
|
|
1298
|
-
prefix: "workflow-step",
|
|
1299
|
-
callId: crypto.randomUUID(),
|
|
1300
|
-
name: "conditional",
|
|
1301
|
-
runId
|
|
1302
|
-
},
|
|
1303
|
-
writableStream
|
|
1304
|
-
)
|
|
1305
|
-
});
|
|
1306
|
-
evalSpan?.end({
|
|
1307
|
-
output: result,
|
|
1308
|
-
attributes: {
|
|
1309
|
-
result: !!result
|
|
1310
|
-
}
|
|
1311
|
-
});
|
|
1312
|
-
return result ? index : null;
|
|
1313
|
-
} catch (e) {
|
|
1314
|
-
evalSpan?.error({
|
|
1315
|
-
error: e instanceof Error ? e : new Error(String(e)),
|
|
1316
|
-
attributes: {
|
|
1317
|
-
result: false
|
|
1318
|
-
}
|
|
1319
|
-
});
|
|
1320
|
-
return null;
|
|
1321
|
-
}
|
|
1322
|
-
})
|
|
1323
|
-
)
|
|
1324
|
-
)).filter((index) => index !== null);
|
|
1325
|
-
const stepsToRun = entry.steps.filter((_, index) => truthyIndexes.includes(index));
|
|
1326
|
-
conditionalSpan?.update({
|
|
1327
|
-
attributes: {
|
|
1328
|
-
truthyIndexes,
|
|
1329
|
-
selectedSteps: stepsToRun.map((s) => s.type === "step" ? s.step.id : `control-${s.type}`)
|
|
1330
|
-
}
|
|
1331
|
-
});
|
|
1332
|
-
const results = await Promise.all(
|
|
1333
|
-
stepsToRun.map(
|
|
1334
|
-
(step, index) => this.executeEntry({
|
|
1335
|
-
workflowId,
|
|
1336
|
-
runId,
|
|
1337
|
-
entry: step,
|
|
1338
|
-
serializedStepGraph,
|
|
1339
|
-
prevStep,
|
|
1340
|
-
stepResults,
|
|
1341
|
-
resume,
|
|
1342
|
-
executionContext: {
|
|
1343
|
-
workflowId,
|
|
1344
|
-
runId,
|
|
1345
|
-
executionPath: [...executionContext.executionPath, index],
|
|
1346
|
-
suspendedPaths: executionContext.suspendedPaths,
|
|
1347
|
-
retryConfig: executionContext.retryConfig,
|
|
1348
|
-
executionSpan: executionContext.executionSpan
|
|
1349
|
-
},
|
|
1350
|
-
emitter,
|
|
1351
|
-
abortController,
|
|
1352
|
-
runtimeContext,
|
|
1353
|
-
writableStream,
|
|
1354
|
-
disableScorers,
|
|
1355
|
-
tracingContext: {
|
|
1356
|
-
currentSpan: conditionalSpan
|
|
1357
|
-
}
|
|
1358
|
-
})
|
|
1359
|
-
)
|
|
1360
|
-
);
|
|
1361
|
-
const hasFailed = results.find((result) => result.result.status === "failed");
|
|
1362
|
-
const hasSuspended = results.find((result) => result.result.status === "suspended");
|
|
1363
|
-
if (hasFailed) {
|
|
1364
|
-
execResults = { status: "failed", error: hasFailed.result.error };
|
|
1365
|
-
} else if (hasSuspended) {
|
|
1366
|
-
execResults = { status: "suspended", payload: hasSuspended.result.suspendPayload };
|
|
1367
|
-
} else {
|
|
1368
|
-
execResults = {
|
|
1369
|
-
status: "success",
|
|
1370
|
-
output: results.reduce((acc, result, index) => {
|
|
1371
|
-
if (result.result.status === "success") {
|
|
1372
|
-
acc[stepsToRun[index].step.id] = result.output;
|
|
1373
|
-
}
|
|
1374
|
-
return acc;
|
|
1375
|
-
}, {})
|
|
1376
|
-
};
|
|
1377
|
-
}
|
|
1378
|
-
if (execResults.status === "failed") {
|
|
1379
|
-
conditionalSpan?.error({
|
|
1380
|
-
error: new Error(execResults.error)
|
|
1381
|
-
});
|
|
1382
|
-
} else {
|
|
1383
|
-
conditionalSpan?.end({
|
|
1384
|
-
output: execResults.output || execResults
|
|
1385
|
-
});
|
|
1386
|
-
}
|
|
1387
|
-
return execResults;
|
|
1388
|
-
}
|
|
1389
|
-
};
|
|
1390
1482
|
|
|
1391
1483
|
exports.InngestExecutionEngine = InngestExecutionEngine;
|
|
1484
|
+
exports.InngestPubSub = InngestPubSub;
|
|
1392
1485
|
exports.InngestRun = InngestRun;
|
|
1393
1486
|
exports.InngestWorkflow = InngestWorkflow;
|
|
1487
|
+
exports._compatibilityCheck = _compatibilityCheck;
|
|
1394
1488
|
exports.createStep = createStep;
|
|
1395
1489
|
exports.init = init;
|
|
1396
1490
|
exports.serve = serve;
|