@mastra/inngest 0.0.0-fix-request-context-as-query-key-20251209130646 → 0.0.0-fix-zod4-schema-validation-20251212180638

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/dist/index.cjs CHANGED
@@ -4,11 +4,13 @@ var tools = require('@mastra/core/tools');
4
4
  var workflows = require('@mastra/core/workflows');
5
5
  var _constants = require('@mastra/core/workflows/_constants');
6
6
  var zod = require('zod');
7
- var crypto = require('crypto');
8
- var web = require('stream/web');
7
+ var crypto$1 = require('crypto');
9
8
  var di = require('@mastra/core/di');
10
9
  var inngest = require('inngest');
10
+ var error = require('@mastra/core/error');
11
11
  var realtime = require('@inngest/realtime');
12
+ var events = require('@mastra/core/events');
13
+ var web = require('stream/web');
12
14
  var stream = require('@mastra/core/stream');
13
15
  var hono = require('inngest/hono');
14
16
 
@@ -25,17 +27,18 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
25
27
  // Hook Overrides
26
28
  // =============================================================================
27
29
  /**
28
- * Format errors with stack traces for better debugging in Inngest
30
+ * Format errors while preserving Error instances and their custom properties.
31
+ * Uses getErrorFromUnknown to ensure all error properties are preserved.
29
32
  */
30
- formatResultError(error, lastOutput) {
31
- if (error instanceof Error) {
32
- return error.stack ?? error.message;
33
- }
33
+ formatResultError(error$1, lastOutput) {
34
34
  const outputError = lastOutput?.error;
35
- if (outputError instanceof Error) {
36
- return outputError.message;
37
- }
38
- return outputError ?? error ?? "Unknown 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();
39
42
  }
40
43
  /**
41
44
  * Detect InngestWorkflow instances for special nested workflow handling
@@ -67,18 +70,24 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
67
70
  error: e,
68
71
  attributes: { status: "failed" }
69
72
  });
73
+ if (cause.error && !(cause.error instanceof Error)) {
74
+ cause.error = error.getErrorFromUnknown(cause.error, { serializeStack: false });
75
+ }
70
76
  return { ok: false, error: cause };
71
77
  }
72
- const errorMessage = e instanceof Error ? e.message : String(e);
78
+ const errorInstance = error.getErrorFromUnknown(e, {
79
+ serializeStack: false,
80
+ fallbackMessage: "Unknown step execution error"
81
+ });
73
82
  params.stepSpan?.error({
74
- error: e,
83
+ error: errorInstance,
75
84
  attributes: { status: "failed" }
76
85
  });
77
86
  return {
78
87
  ok: false,
79
88
  error: {
80
89
  status: "failed",
81
- error: `Error: ${errorMessage}`,
90
+ error: errorInstance,
82
91
  endedAt: Date.now()
83
92
  }
84
93
  };
@@ -107,11 +116,14 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
107
116
  return await operationFn();
108
117
  } catch (e) {
109
118
  if (retryConfig) {
110
- const errorMessage = e instanceof Error ? e.message : String(e);
111
- throw new inngest.RetryAfterError(errorMessage, retryConfig.delay, {
119
+ const errorInstance = error.getErrorFromUnknown(e, {
120
+ serializeStack: false,
121
+ fallbackMessage: "Unknown step execution error"
122
+ });
123
+ throw new inngest.RetryAfterError(errorInstance.message, retryConfig.delay, {
112
124
  cause: {
113
125
  status: "failed",
114
- error: `Error: ${errorMessage}`,
126
+ error: errorInstance,
115
127
  endedAt: Date.now()
116
128
  }
117
129
  });
@@ -134,14 +146,14 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
134
146
  if (!(params.step instanceof InngestWorkflow)) {
135
147
  return null;
136
148
  }
137
- const { step, stepResults, executionContext, resume, timeTravel, prevOutput, inputData, emitter, startedAt } = params;
149
+ const { step, stepResults, executionContext, resume, timeTravel, prevOutput, inputData, pubsub, startedAt } = params;
138
150
  const isResume = !!resume?.steps?.length;
139
151
  let result;
140
152
  let runId;
141
153
  const isTimeTravel = !!(timeTravel && timeTravel.steps?.length > 1 && timeTravel.steps[0] === step.id);
142
154
  try {
143
155
  if (isResume) {
144
- runId = stepResults[resume?.steps?.[0] ?? ""]?.suspendPayload?.__workflow_meta?.runId ?? crypto.randomUUID();
156
+ runId = stepResults[resume?.steps?.[0] ?? ""]?.suspendPayload?.__workflow_meta?.runId ?? crypto$1.randomUUID();
145
157
  const snapshot = await this.mastra?.getStorage()?.loadWorkflowSnapshot({
146
158
  workflowName: step.id,
147
159
  runId
@@ -208,9 +220,9 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
208
220
  const errorCause = e?.cause;
209
221
  if (errorCause && typeof errorCause === "object") {
210
222
  result = errorCause;
211
- runId = errorCause.runId || crypto.randomUUID();
223
+ runId = errorCause.runId || crypto$1.randomUUID();
212
224
  } else {
213
- runId = crypto.randomUUID();
225
+ runId = crypto$1.randomUUID();
214
226
  result = {
215
227
  status: "failed",
216
228
  error: e instanceof Error ? e : new Error(String(e)),
@@ -223,13 +235,17 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
223
235
  `workflow.${executionContext.workflowId}.step.${step.id}.nestedwf-results`,
224
236
  async () => {
225
237
  if (result.status === "failed") {
226
- await emitter.emit("watch", {
227
- type: "workflow-step-result",
228
- payload: {
229
- id: step.id,
230
- status: "failed",
231
- error: result?.error,
232
- payload: prevOutput
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
+ }
233
249
  }
234
250
  });
235
251
  return { executionContext, result: { status: "failed", error: result?.error } };
@@ -241,11 +257,15 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
241
257
  for (const [stepName, stepResult] of suspendedSteps) {
242
258
  const suspendPath = [stepName, ...stepResult?.suspendPayload?.__workflow_meta?.path ?? []];
243
259
  executionContext.suspendedPaths[step.id] = executionContext.executionPath;
244
- await emitter.emit("watch", {
245
- type: "workflow-step-suspended",
246
- payload: {
247
- id: step.id,
248
- status: "suspended"
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
+ }
249
269
  }
250
270
  });
251
271
  return {
@@ -267,20 +287,49 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
267
287
  payload: {}
268
288
  }
269
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
+ };
270
311
  }
271
- await emitter.emit("watch", {
272
- type: "workflow-step-result",
273
- payload: {
274
- id: step.id,
275
- status: "success",
276
- output: result?.result
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
+ }
277
322
  }
278
323
  });
279
- await emitter.emit("watch", {
280
- type: "workflow-step-finish",
281
- payload: {
282
- id: step.id,
283
- metadata: {}
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
+ }
284
333
  }
285
334
  });
286
335
  return { executionContext, result: { status: "success", output: result?.result } };
@@ -297,6 +346,118 @@ var InngestExecutionEngine = class extends workflows.DefaultExecutionEngine {
297
346
  };
298
347
  }
299
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
+ };
300
461
  var InngestRun = class extends workflows.Run {
301
462
  inngest;
302
463
  serializedStepGraph;
@@ -308,27 +469,77 @@ var InngestRun = class extends workflows.Run {
308
469
  this.#mastra = params.mastra;
309
470
  }
310
471
  async getRuns(eventId) {
311
- const response = await fetch(`${this.inngest.apiBaseUrl ?? "https://api.inngest.com"}/v1/events/${eventId}/runs`, {
312
- headers: {
313
- Authorization: `Bearer ${process.env.INNGEST_SIGNING_KEY}`
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
+ }
314
504
  }
315
- });
316
- const json = await response.json();
317
- return json.data;
505
+ }
506
+ throw new inngest.NonRetriableError(`Failed to get runs after ${maxRetries} attempts: ${lastError?.message}`);
318
507
  }
319
- async getRunOutput(eventId) {
320
- let runs = await this.getRuns(eventId);
508
+ async getRunOutput(eventId, maxWaitMs = 3e5) {
509
+ const startTime = Date.now();
321
510
  const storage = this.#mastra?.getStorage();
322
- while (runs?.[0]?.status !== "Completed" || runs?.[0]?.event_id !== eventId) {
323
- await new Promise((resolve) => setTimeout(resolve, 1e3));
324
- runs = await this.getRuns(eventId);
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
+ }
325
526
  if (runs?.[0]?.status === "Failed") {
326
527
  const snapshot = await storage?.loadWorkflowSnapshot({
327
528
  workflowName: this.workflowId,
328
529
  runId: this.runId
329
530
  });
531
+ if (snapshot?.context) {
532
+ snapshot.context = workflows.hydrateSerializedStepErrors(snapshot.context);
533
+ }
330
534
  return {
331
- output: { result: { steps: snapshot?.context, status: "failed", error: runs?.[0]?.output?.message } }
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
+ }
332
543
  };
333
544
  }
334
545
  if (runs?.[0]?.status === "Cancelled") {
@@ -338,8 +549,9 @@ var InngestRun = class extends workflows.Run {
338
549
  });
339
550
  return { output: { result: { steps: snapshot?.context, status: "canceled" } } };
340
551
  }
552
+ await new Promise((resolve) => setTimeout(resolve, 1e3 + Math.random() * 1e3));
341
553
  }
342
- return runs?.[0];
554
+ throw new inngest.NonRetriableError(`Workflow did not complete within ${maxWaitMs}ms`);
343
555
  }
344
556
  async cancel() {
345
557
  const storage = this.#mastra?.getStorage();
@@ -369,6 +581,51 @@ var InngestRun = class extends workflows.Run {
369
581
  async start(params) {
370
582
  return this._start(params);
371
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
+ }
372
629
  async _start({
373
630
  inputData,
374
631
  initialState,
@@ -416,9 +673,7 @@ var InngestRun = class extends workflows.Run {
416
673
  }
417
674
  const runOutput = await this.getRunOutput(eventId);
418
675
  const result = runOutput?.output?.result;
419
- if (result.status === "failed") {
420
- result.error = new Error(result.error);
421
- }
676
+ this.hydrateFailedResult(result);
422
677
  if (result.status !== "suspended") {
423
678
  this.cleanup?.();
424
679
  }
@@ -477,9 +732,7 @@ var InngestRun = class extends workflows.Run {
477
732
  }
478
733
  const runOutput = await this.getRunOutput(eventId);
479
734
  const result = runOutput?.output?.result;
480
- if (result.status === "failed") {
481
- result.error = new Error(result.error);
482
- }
735
+ this.hydrateFailedResult(result);
483
736
  return result;
484
737
  }
485
738
  async timeTravel(params) {
@@ -569,9 +822,7 @@ var InngestRun = class extends workflows.Run {
569
822
  }
570
823
  const runOutput = await this.getRunOutput(eventId);
571
824
  const result = runOutput?.output?.result;
572
- if (result.status === "failed") {
573
- result.error = new Error(result.error);
574
- }
825
+ this.hydrateFailedResult(result);
575
826
  return result;
576
827
  }
577
828
  watch(cb) {
@@ -792,6 +1043,18 @@ var InngestRun = class extends workflows.Run {
792
1043
  });
793
1044
  return this.streamOutput;
794
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
+ }
795
1058
  };
796
1059
 
797
1060
  // src/workflow.ts
@@ -829,6 +1092,7 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
829
1092
  return run ?? (this.runs.get(runId) ? { ...this.runs.get(runId), workflowName: this.id } : null);
830
1093
  }
831
1094
  __registerMastra(mastra) {
1095
+ super.__registerMastra(mastra);
832
1096
  this.#mastra = mastra;
833
1097
  this.executionEngine.__registerMastra(mastra);
834
1098
  const updateNested = (step) => {
@@ -847,7 +1111,7 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
847
1111
  }
848
1112
  }
849
1113
  async createRun(options) {
850
- const runIdToUse = options?.runId || crypto.randomUUID();
1114
+ const runIdToUse = options?.runId || crypto$1.randomUUID();
851
1115
  const run = this.runs.get(runIdToUse) ?? new InngestRun(
852
1116
  {
853
1117
  workflowId: this.id,
@@ -912,31 +1176,10 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
912
1176
  let { inputData, initialState, runId, resourceId, resume, outputOptions, format, timeTravel } = event.data;
913
1177
  if (!runId) {
914
1178
  runId = await step.run(`workflow.${this.id}.runIdGen`, async () => {
915
- return crypto.randomUUID();
1179
+ return crypto$1.randomUUID();
916
1180
  });
917
1181
  }
918
- const emitter = {
919
- emit: async (event2, data) => {
920
- if (!publish) {
921
- return;
922
- }
923
- try {
924
- await publish({
925
- channel: `workflow:${this.id}:${runId}`,
926
- topic: event2,
927
- data
928
- });
929
- } catch (err) {
930
- this.logger.error("Error emitting event: " + (err?.stack ?? err?.message ?? err));
931
- }
932
- },
933
- on: (_event, _callback) => {
934
- },
935
- off: (_event, _callback) => {
936
- },
937
- once: (_event, _callback) => {
938
- }
939
- };
1182
+ const pubsub = new InngestPubSub(this.inngest, this.id, publish);
940
1183
  const engine = new InngestExecutionEngine(this.#mastra, step, attempt, this.options);
941
1184
  const result = await engine.execute({
942
1185
  workflowId: this.id,
@@ -946,7 +1189,7 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
946
1189
  serializedStepGraph: this.serializedStepGraph,
947
1190
  input: inputData,
948
1191
  initialState,
949
- emitter,
1192
+ pubsub,
950
1193
  retryConfig: this.retryConfig,
951
1194
  requestContext: new di.RequestContext(Object.entries(event.data.requestContext ?? {})),
952
1195
  resume,
@@ -955,12 +1198,17 @@ var InngestWorkflow = class _InngestWorkflow extends workflows.Workflow {
955
1198
  abortController: new AbortController(),
956
1199
  // currentSpan: undefined, // TODO: Pass actual parent Span from workflow execution context
957
1200
  outputOptions,
958
- writableStream: new web.WritableStream({
959
- write(chunk) {
960
- void emitter.emit("watch", chunk).catch(() => {
1201
+ outputWriter: async (chunk) => {
1202
+ try {
1203
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1204
+ type: "watch",
1205
+ runId,
1206
+ data: chunk
961
1207
  });
1208
+ } catch (err) {
1209
+ this.logger.debug?.("Failed to publish watch event:", err);
962
1210
  }
963
- })
1211
+ }
964
1212
  });
965
1213
  await step.run(`workflow.${this.id}.finalize`, async () => {
966
1214
  if (result.status === "failed") {
@@ -1035,6 +1283,7 @@ function createStep(params, agentOptions) {
1035
1283
  return params;
1036
1284
  }
1037
1285
  if (isAgent(params)) {
1286
+ const outputSchema = agentOptions?.structuredOutput?.schema ?? zod.z.object({ text: zod.z.string() });
1038
1287
  return {
1039
1288
  id: params.name,
1040
1289
  description: params.getDescription(),
@@ -1043,12 +1292,11 @@ function createStep(params, agentOptions) {
1043
1292
  // resourceId: z.string().optional(),
1044
1293
  // threadId: z.string().optional(),
1045
1294
  }),
1046
- outputSchema: zod.z.object({
1047
- text: zod.z.string()
1048
- }),
1295
+ outputSchema,
1049
1296
  execute: async ({
1050
1297
  inputData,
1051
- [_constants.EMITTER_SYMBOL]: emitter,
1298
+ runId,
1299
+ [_constants.PUBSUB_SYMBOL]: pubsub,
1052
1300
  [_constants.STREAM_FORMAT_SYMBOL]: streamFormat,
1053
1301
  requestContext,
1054
1302
  tracingContext,
@@ -1061,6 +1309,7 @@ function createStep(params, agentOptions) {
1061
1309
  streamPromise.resolve = resolve;
1062
1310
  streamPromise.reject = reject;
1063
1311
  });
1312
+ let structuredResult = null;
1064
1313
  const toolData = {
1065
1314
  name: params.name,
1066
1315
  args: inputData
@@ -1074,6 +1323,10 @@ function createStep(params, agentOptions) {
1074
1323
  requestContext,
1075
1324
  tracingContext,
1076
1325
  onFinish: (result) => {
1326
+ const resultWithObject = result;
1327
+ if (agentOptions?.structuredOutput?.schema && resultWithObject.object) {
1328
+ structuredResult = resultWithObject.object;
1329
+ }
1077
1330
  streamPromise.resolve(result.text);
1078
1331
  void agentOptions?.onFinish?.(result);
1079
1332
  },
@@ -1086,6 +1339,10 @@ function createStep(params, agentOptions) {
1086
1339
  requestContext,
1087
1340
  tracingContext,
1088
1341
  onFinish: (result) => {
1342
+ const resultWithObject = result;
1343
+ if (agentOptions?.structuredOutput?.schema && resultWithObject.object) {
1344
+ structuredResult = resultWithObject.object;
1345
+ }
1089
1346
  streamPromise.resolve(result.text);
1090
1347
  void agentOptions?.onFinish?.(result);
1091
1348
  },
@@ -1094,22 +1351,24 @@ function createStep(params, agentOptions) {
1094
1351
  stream = modelOutput.fullStream;
1095
1352
  }
1096
1353
  if (streamFormat === "legacy") {
1097
- await emitter.emit("watch", {
1098
- type: "tool-call-streaming-start",
1099
- ...toolData ?? {}
1354
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1355
+ type: "watch",
1356
+ runId,
1357
+ data: { type: "tool-call-streaming-start", ...toolData ?? {} }
1100
1358
  });
1101
1359
  for await (const chunk of stream) {
1102
1360
  if (chunk.type === "text-delta") {
1103
- await emitter.emit("watch", {
1104
- type: "tool-call-delta",
1105
- ...toolData ?? {},
1106
- argsTextDelta: chunk.textDelta
1361
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1362
+ type: "watch",
1363
+ runId,
1364
+ data: { type: "tool-call-delta", ...toolData ?? {}, argsTextDelta: chunk.textDelta }
1107
1365
  });
1108
1366
  }
1109
1367
  }
1110
- await emitter.emit("watch", {
1111
- type: "tool-call-streaming-finish",
1112
- ...toolData ?? {}
1368
+ await pubsub.publish(`workflow.events.v2.${runId}`, {
1369
+ type: "watch",
1370
+ runId,
1371
+ data: { type: "tool-call-streaming-finish", ...toolData ?? {} }
1113
1372
  });
1114
1373
  } else {
1115
1374
  for await (const chunk of stream) {
@@ -1119,6 +1378,9 @@ function createStep(params, agentOptions) {
1119
1378
  if (abortSignal.aborted) {
1120
1379
  return abort();
1121
1380
  }
1381
+ if (structuredResult !== null) {
1382
+ return structuredResult;
1383
+ }
1122
1384
  return {
1123
1385
  text: await streamPromise.promise
1124
1386
  };
@@ -1219,6 +1481,7 @@ function init(inngest) {
1219
1481
  }
1220
1482
 
1221
1483
  exports.InngestExecutionEngine = InngestExecutionEngine;
1484
+ exports.InngestPubSub = InngestPubSub;
1222
1485
  exports.InngestRun = InngestRun;
1223
1486
  exports.InngestWorkflow = InngestWorkflow;
1224
1487
  exports._compatibilityCheck = _compatibilityCheck;