@upstash/workflow 0.2.23 → 0.3.0-rc1

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/solidjs.js CHANGED
@@ -36,26 +36,36 @@ var WorkflowError = class extends import_qstash.QstashError {
36
36
  }
37
37
  };
38
38
  var WorkflowAbort = class extends Error {
39
- stepInfo;
40
39
  stepName;
40
+ stepInfo;
41
41
  /**
42
- * whether workflow is to be canceled on abort
43
- */
44
- cancelWorkflow;
45
- /**
46
- *
47
42
  * @param stepName name of the aborting step
48
43
  * @param stepInfo step information
49
- * @param cancelWorkflow
50
44
  */
51
- constructor(stepName, stepInfo, cancelWorkflow = false) {
45
+ constructor(stepName, stepInfo) {
52
46
  super(
53
47
  `This is an Upstash Workflow error thrown after a step executes. It is expected to be raised. Make sure that you await for each step. Also, if you are using try/catch blocks, you should not wrap context.run/sleep/sleepUntil/call methods with try/catch. Aborting workflow after executing step '${stepName}'.`
54
48
  );
55
49
  this.name = "WorkflowAbort";
56
50
  this.stepName = stepName;
57
51
  this.stepInfo = stepInfo;
58
- this.cancelWorkflow = cancelWorkflow;
52
+ }
53
+ };
54
+ var WorkflowAuthError = class extends WorkflowAbort {
55
+ /**
56
+ * @param stepName name of the step found during authorization
57
+ */
58
+ constructor(stepName) {
59
+ super(stepName);
60
+ this.name = "WorkflowAuthError";
61
+ this.message = `This is an Upstash Workflow error thrown during authorization check. Found step '${stepName}' during dry-run.`;
62
+ }
63
+ };
64
+ var WorkflowCancelAbort = class extends WorkflowAbort {
65
+ constructor() {
66
+ super("cancel");
67
+ this.name = "WorkflowCancelAbort";
68
+ this.message = "Workflow has been canceled by user via context.cancel().";
59
69
  }
60
70
  };
61
71
  var WorkflowNonRetryableError = class extends WorkflowAbort {
@@ -63,22 +73,22 @@ var WorkflowNonRetryableError = class extends WorkflowAbort {
63
73
  * @param message error message to be displayed
64
74
  */
65
75
  constructor(message) {
66
- super("fail", void 0, false);
76
+ super("non-retryable-error");
67
77
  this.name = "WorkflowNonRetryableError";
68
- if (message) this.message = message;
78
+ this.message = message ?? "Workflow failed with non-retryable error.";
69
79
  }
70
80
  };
71
81
  var WorkflowRetryAfterError = class extends WorkflowAbort {
72
82
  retryAfter;
73
83
  /**
74
- * @param retryAfter time in seconds after which the workflow should be retried
75
84
  * @param message error message to be displayed
85
+ * @param retryAfter time in seconds after which the workflow should be retried
76
86
  */
77
87
  constructor(message, retryAfter) {
78
- super("retry", void 0, false);
88
+ super("retry-after-error");
79
89
  this.name = "WorkflowRetryAfterError";
90
+ this.message = message;
80
91
  this.retryAfter = retryAfter;
81
- if (message) this.message = message;
82
92
  }
83
93
  };
84
94
  var formatWorkflowError = (error) => {
@@ -130,15 +140,21 @@ var makeCancelRequest = async (requester, workflowRunId) => {
130
140
  });
131
141
  return true;
132
142
  };
133
- var getSteps = async (requester, workflowRunId, messageId, debug) => {
143
+ var getSteps = async (requester, workflowRunId, messageId, dispatchDebug) => {
134
144
  try {
135
145
  const steps = await requester.request({
136
146
  path: ["v2", "workflows", "runs", workflowRunId],
137
147
  parseResponseAsJson: true
138
148
  });
149
+ if (steps.length === 1) {
150
+ return {
151
+ steps,
152
+ workflowRunEnded: false
153
+ };
154
+ }
139
155
  if (!messageId) {
140
- await debug?.log("INFO", "ENDPOINT_START", {
141
- message: `Pulled ${steps.length} steps from QStashand returned them without filtering with messageId.`
156
+ await dispatchDebug?.("onInfo", {
157
+ info: `Pulled ${steps.length} steps from QStashand returned them without filtering with messageId.`
142
158
  });
143
159
  return { steps, workflowRunEnded: false };
144
160
  } else {
@@ -147,16 +163,15 @@ var getSteps = async (requester, workflowRunId, messageId, debug) => {
147
163
  return { steps: [], workflowRunEnded: false };
148
164
  }
149
165
  const filteredSteps = steps.slice(0, index + 1);
150
- await debug?.log("INFO", "ENDPOINT_START", {
151
- message: `Pulled ${steps.length} steps from QStash and filtered them to ${filteredSteps.length} using messageId.`
166
+ await dispatchDebug?.("onInfo", {
167
+ info: `Pulled ${steps.length} steps from QStash and filtered them to ${filteredSteps.length} using messageId.`
152
168
  });
153
169
  return { steps: filteredSteps, workflowRunEnded: false };
154
170
  }
155
171
  } catch (error) {
156
172
  if (isInstanceOf(error, import_qstash2.QstashError) && error.status === 404) {
157
- await debug?.log("WARN", "ENDPOINT_START", {
158
- message: "Couldn't fetch workflow run steps. This can happen if the workflow run succesfully ends before some callback is executed.",
159
- error
173
+ await dispatchDebug?.("onWarning", {
174
+ warning: "Couldn't fetch workflow run steps. This can happen if the workflow run succesfully ends before some callback is executed."
160
175
  });
161
176
  return { steps: void 0, workflowRunEnded: true };
162
177
  } else {
@@ -170,20 +185,22 @@ var WORKFLOW_ID_HEADER = "Upstash-Workflow-RunId";
170
185
  var WORKFLOW_INIT_HEADER = "Upstash-Workflow-Init";
171
186
  var WORKFLOW_URL_HEADER = "Upstash-Workflow-Url";
172
187
  var WORKFLOW_FAILURE_HEADER = "Upstash-Workflow-Is-Failure";
188
+ var WORKFLOW_FAILURE_CALLBACK_HEADER = "Upstash-Workflow-Failure-Callback";
173
189
  var WORKFLOW_FEATURE_HEADER = "Upstash-Feature-Set";
174
190
  var WORKFLOW_INVOKE_COUNT_HEADER = "Upstash-Workflow-Invoke-Count";
175
191
  var WORKFLOW_LABEL_HEADER = "Upstash-Label";
192
+ var WORKFLOW_UNKOWN_SDK_VERSION_HEADER = "Upstash-Workflow-Unknown-Sdk";
193
+ var WORKFLOW_UNKOWN_SDK_TRIGGER_HEADER = "upstash-workflow-trigger-by-sdk";
176
194
  var WORKFLOW_PROTOCOL_VERSION = "1";
177
195
  var WORKFLOW_PROTOCOL_VERSION_HEADER = "Upstash-Workflow-Sdk-Version";
178
196
  var DEFAULT_CONTENT_TYPE = "application/json";
179
197
  var NO_CONCURRENCY = 1;
180
198
  var DEFAULT_RETRIES = 3;
181
- var VERSION = "v0.2.22";
199
+ var VERSION = "v1.0.0";
182
200
  var SDK_TELEMETRY = `@upstash/workflow@${VERSION}`;
183
201
  var TELEMETRY_HEADER_SDK = "Upstash-Telemetry-Sdk";
184
202
  var TELEMETRY_HEADER_FRAMEWORK = "Upstash-Telemetry-Framework";
185
203
  var TELEMETRY_HEADER_RUNTIME = "Upstash-Telemetry-Runtime";
186
- var TELEMETRY_HEADER_AGENT = "Upstash-Telemetry-Agent";
187
204
 
188
205
  // src/context/auto-executor.ts
189
206
  var import_qstash5 = require("@upstash/qstash");
@@ -246,6 +263,15 @@ function getQStashUrl(qstashClient) {
246
263
  function getEventId() {
247
264
  return `evt_${nanoid(15)}`;
248
265
  }
266
+ function stringifyBody(body) {
267
+ if (body === void 0) {
268
+ return void 0;
269
+ }
270
+ if (typeof body === "string") {
271
+ return body;
272
+ }
273
+ return JSON.stringify(body);
274
+ }
249
275
 
250
276
  // node_modules/neverthrow/dist/index.es.js
251
277
  var defaultErrorConfig = {
@@ -587,9 +613,9 @@ var Ok = class {
587
613
  }
588
614
  safeUnwrap() {
589
615
  const value = this.value;
590
- return function* () {
616
+ return (function* () {
591
617
  return value;
592
- }();
618
+ })();
593
619
  }
594
620
  _unsafeUnwrap(_) {
595
621
  return this.value;
@@ -648,10 +674,10 @@ var Err = class {
648
674
  }
649
675
  safeUnwrap() {
650
676
  const error = this.error;
651
- return function* () {
677
+ return (function* () {
652
678
  yield err(error);
653
679
  throw new Error("Do not use this generator out of `safeTry`");
654
- }();
680
+ })();
655
681
  }
656
682
  _unsafeUnwrap(config) {
657
683
  throw createNeverThrowError("Called `_unsafeUnwrap` on an Err", this, config);
@@ -689,23 +715,26 @@ var triggerFirstInvocation = async (params) => {
689
715
  invokeCount,
690
716
  delay,
691
717
  notBefore,
692
- keepTriggerConfig
718
+ failureUrl,
719
+ retries,
720
+ retryDelay,
721
+ flowControl,
722
+ unknownSdk
693
723
  }) => {
694
724
  const { headers } = getHeaders({
695
725
  initHeaderValue: "true",
696
726
  workflowConfig: {
697
727
  workflowRunId: workflowContext.workflowRunId,
698
728
  workflowUrl: workflowContext.url,
699
- failureUrl: workflowContext.failureUrl,
700
- retries: workflowContext.retries,
701
- retryDelay: workflowContext.retryDelay,
729
+ failureUrl,
730
+ retries,
731
+ retryDelay,
702
732
  telemetry,
703
- flowControl: workflowContext.flowControl,
733
+ flowControl,
704
734
  useJSONContent: useJSONContent ?? false
705
735
  },
706
736
  invokeCount: invokeCount ?? 0,
707
- userHeaders: workflowContext.headers,
708
- keepTriggerConfig
737
+ userHeaders: workflowContext.headers
709
738
  });
710
739
  if (workflowContext.headers.get("content-type")) {
711
740
  headers["content-type"] = workflowContext.headers.get("content-type");
@@ -713,6 +742,9 @@ var triggerFirstInvocation = async (params) => {
713
742
  if (useJSONContent) {
714
743
  headers["content-type"] = "application/json";
715
744
  }
745
+ if (unknownSdk) {
746
+ headers[WORKFLOW_UNKOWN_SDK_TRIGGER_HEADER] = "true";
747
+ }
716
748
  if (workflowContext.label) {
717
749
  headers[WORKFLOW_LABEL_HEADER] = workflowContext.label;
718
750
  }
@@ -733,21 +765,15 @@ var triggerFirstInvocation = async (params) => {
733
765
  for (let i = 0; i < results.length; i++) {
734
766
  const result = results[i];
735
767
  const invocationParams = firstInvocationParams[i];
768
+ invocationParams.middlewareManager?.assignContext(invocationParams.workflowContext);
736
769
  if (result.deduplicated) {
737
- await invocationParams.debug?.log("WARN", "SUBMIT_FIRST_INVOCATION", {
738
- message: `Workflow run ${invocationParams.workflowContext.workflowRunId} already exists. A new one isn't created.`,
739
- headers: invocationBatch[i].headers,
740
- requestPayload: invocationParams.workflowContext.requestPayload,
741
- url: invocationParams.workflowContext.url,
742
- messageId: result.messageId
770
+ await invocationParams.middlewareManager?.dispatchDebug("onWarning", {
771
+ warning: `Workflow run ${invocationParams.workflowContext.workflowRunId} already exists. A new one isn't created.`
743
772
  });
744
773
  invocationStatuses.push("workflow-run-already-exists");
745
774
  } else {
746
- await invocationParams.debug?.log("SUBMIT", "SUBMIT_FIRST_INVOCATION", {
747
- headers: invocationBatch[i].headers,
748
- requestPayload: invocationParams.workflowContext.requestPayload,
749
- url: invocationParams.workflowContext.url,
750
- messageId: result.messageId
775
+ await invocationParams.middlewareManager?.dispatchDebug("onInfo", {
776
+ info: `Workflow run started successfully with URL ${invocationParams.workflowContext.url}.`
751
777
  });
752
778
  invocationStatuses.push("success");
753
779
  }
@@ -769,7 +795,7 @@ var triggerRouteFunction = async ({
769
795
  onCleanup,
770
796
  onStep,
771
797
  onCancel,
772
- debug
798
+ middlewareManager
773
799
  }) => {
774
800
  try {
775
801
  const result = await onStep();
@@ -778,27 +804,25 @@ var triggerRouteFunction = async ({
778
804
  } catch (error) {
779
805
  const error_ = error;
780
806
  if (isInstanceOf(error, import_qstash3.QstashError) && error.status === 400) {
781
- await debug?.log("WARN", "RESPONSE_WORKFLOW", {
782
- message: `tried to append to a cancelled workflow. exiting without publishing.`,
783
- name: error.name,
784
- errorMessage: error.message
807
+ await middlewareManager?.dispatchDebug("onWarning", {
808
+ warning: `Tried to append to a cancelled workflow. Exiting without publishing. Error: ${error.message}`
785
809
  });
786
810
  return ok("workflow-was-finished");
787
811
  } else if (isInstanceOf(error_, WorkflowNonRetryableError) || isInstanceOf(error_, WorkflowRetryAfterError)) {
788
812
  return ok(error_);
789
- } else if (!isInstanceOf(error_, WorkflowAbort)) {
790
- return err(error_);
791
- } else if (error_.cancelWorkflow) {
813
+ } else if (isInstanceOf(error_, WorkflowCancelAbort)) {
792
814
  await onCancel();
793
815
  return ok("workflow-finished");
794
- } else {
816
+ } else if (isInstanceOf(error_, WorkflowAbort)) {
795
817
  return ok("step-finished");
818
+ } else {
819
+ return err(error_);
796
820
  }
797
821
  }
798
822
  };
799
- var triggerWorkflowDelete = async (workflowContext, result, debug, cancel = false) => {
800
- await debug?.log("SUBMIT", "SUBMIT_CLEANUP", {
801
- deletedWorkflowRunId: workflowContext.workflowRunId
823
+ var triggerWorkflowDelete = async (workflowContext, result, cancel = false, dispatchDebug) => {
824
+ await dispatchDebug?.("onInfo", {
825
+ info: `Deleting workflow run ${workflowContext.workflowRunId} from QStash` + (cancel ? " with cancel=true." : ".")
802
826
  });
803
827
  await workflowContext.qstashClient.http.request({
804
828
  path: ["v2", "workflows", "runs", `${workflowContext.workflowRunId}?cancel=${cancel}`],
@@ -806,18 +830,16 @@ var triggerWorkflowDelete = async (workflowContext, result, debug, cancel = fals
806
830
  parseResponseAsJson: false,
807
831
  body: JSON.stringify(result)
808
832
  });
809
- await debug?.log(
810
- "SUBMIT",
811
- "SUBMIT_CLEANUP",
812
- `workflow run ${workflowContext.workflowRunId} deleted.`
813
- );
833
+ await dispatchDebug?.("onInfo", {
834
+ info: `Workflow run ${workflowContext.workflowRunId} deleted from QStash successfully.`
835
+ });
814
836
  };
815
837
  var recreateUserHeaders = (headers) => {
816
838
  const filteredHeaders = new Headers();
817
839
  const pairs = headers.entries();
818
840
  for (const [header, value] of pairs) {
819
841
  const headerLowerCase = header.toLowerCase();
820
- const isUserHeader = !headerLowerCase.startsWith("upstash-workflow-") && // https://vercel.com/docs/edge-network/headers/request-headers#x-vercel-id
842
+ const isUserHeader = headerLowerCase !== "upstash-region" && !headerLowerCase.startsWith("upstash-workflow-") && // https://vercel.com/docs/edge-network/headers/request-headers#x-vercel-id
821
843
  !headerLowerCase.startsWith("x-vercel-") && !headerLowerCase.startsWith("x-forwarded-") && // https://blog.cloudflare.com/preventing-request-loops-using-cdn-loop/
822
844
  headerLowerCase !== "cf-connecting-ip" && headerLowerCase !== "cdn-loop" && headerLowerCase !== "cf-ew-via" && headerLowerCase !== "cf-ray" && // For Render https://render.com
823
845
  headerLowerCase !== "render-proxy-ttl" || headerLowerCase === WORKFLOW_LABEL_HEADER.toLocaleLowerCase();
@@ -832,12 +854,8 @@ var handleThirdPartyCallResult = async ({
832
854
  requestPayload,
833
855
  client,
834
856
  workflowUrl,
835
- failureUrl,
836
- retries,
837
- retryDelay,
838
857
  telemetry,
839
- flowControl,
840
- debug
858
+ middlewareManager
841
859
  }) => {
842
860
  try {
843
861
  if (request.headers.get("Upstash-Workflow-Callback")) {
@@ -854,7 +872,7 @@ var handleThirdPartyCallResult = async ({
854
872
  client.http,
855
873
  workflowRunId2,
856
874
  messageId,
857
- debug
875
+ middlewareManager?.dispatchDebug.bind(middlewareManager)
858
876
  );
859
877
  if (workflowRunEnded) {
860
878
  return ok("workflow-ended");
@@ -868,9 +886,8 @@ var handleThirdPartyCallResult = async ({
868
886
  }
869
887
  const callbackMessage = JSON.parse(callbackPayload);
870
888
  if (!(callbackMessage.status >= 200 && callbackMessage.status < 300) && callbackMessage.maxRetries && callbackMessage.retried !== callbackMessage.maxRetries) {
871
- await debug?.log("WARN", "SUBMIT_THIRD_PARTY_RESULT", {
872
- status: callbackMessage.status,
873
- body: atob(callbackMessage.body ?? "")
889
+ await middlewareManager?.dispatchDebug("onWarning", {
890
+ warning: `Third party call returned status ${callbackMessage.status}. Retrying (${callbackMessage.retried} out of ${callbackMessage.maxRetries}).`
874
891
  });
875
892
  console.warn(
876
893
  `Workflow Warning: "context.call" failed with status ${callbackMessage.status} and will retry (retried ${callbackMessage.retried ?? 0} out of ${callbackMessage.maxRetries} times). Error Message:
@@ -903,11 +920,7 @@ ${atob(callbackMessage.body ?? "")}`
903
920
  workflowConfig: {
904
921
  workflowRunId,
905
922
  workflowUrl,
906
- failureUrl,
907
- retries,
908
- retryDelay,
909
- telemetry,
910
- flowControl
923
+ telemetry
911
924
  },
912
925
  userHeaders,
913
926
  invokeCount: Number(invokeCount)
@@ -924,19 +937,17 @@ ${atob(callbackMessage.body ?? "")}`
924
937
  out: JSON.stringify(callResponse),
925
938
  concurrent: Number(concurrentString)
926
939
  };
927
- await debug?.log("SUBMIT", "SUBMIT_THIRD_PARTY_RESULT", {
928
- step: callResultStep,
929
- headers: requestHeaders,
930
- url: workflowUrl
940
+ await middlewareManager?.dispatchDebug("onInfo", {
941
+ info: `Submitting third party call result, step ${stepName} (${stepIdString}).`
931
942
  });
932
- const result = await client.publishJSON({
943
+ await client.publishJSON({
933
944
  headers: requestHeaders,
934
945
  method: "POST",
935
946
  body: callResultStep,
936
947
  url: workflowUrl
937
948
  });
938
- await debug?.log("SUBMIT", "SUBMIT_THIRD_PARTY_RESULT", {
939
- messageId: result.messageId
949
+ await middlewareManager?.dispatchDebug("onInfo", {
950
+ info: `Third party call result submitted successfully, step ${stepName} (${stepIdString}).`
940
951
  });
941
952
  return ok("is-call-return");
942
953
  } else {
@@ -994,8 +1005,8 @@ var BaseLazyStep = class _BaseLazyStep {
994
1005
  );
995
1006
  }
996
1007
  if (typeof stepName !== "string") {
997
- console.warn(
998
- "Workflow Warning: A workflow step name must be a string. In a future release, this will throw an error."
1008
+ throw new WorkflowError(
1009
+ `A workflow step name must be a string. Received "${stepName}" (${typeof stepName}).`
999
1010
  );
1000
1011
  }
1001
1012
  this.stepName = stepName;
@@ -1057,12 +1068,8 @@ var BaseLazyStep = class _BaseLazyStep {
1057
1068
  workflowConfig: {
1058
1069
  workflowRunId: context.workflowRunId,
1059
1070
  workflowUrl: context.url,
1060
- failureUrl: context.failureUrl,
1061
- retries: DEFAULT_RETRIES === context.retries ? void 0 : context.retries,
1062
- retryDelay: context.retryDelay,
1063
1071
  useJSONContent: false,
1064
- telemetry,
1065
- flowControl: context.flowControl
1072
+ telemetry
1066
1073
  },
1067
1074
  userHeaders: context.headers,
1068
1075
  invokeCount,
@@ -1078,9 +1085,6 @@ var BaseLazyStep = class _BaseLazyStep {
1078
1085
  body,
1079
1086
  headers,
1080
1087
  method: "POST",
1081
- retries: DEFAULT_RETRIES === context.retries ? void 0 : context.retries,
1082
- retryDelay: context.retryDelay,
1083
- flowControl: context.flowControl,
1084
1088
  url: context.url
1085
1089
  }
1086
1090
  ]);
@@ -1151,9 +1155,6 @@ var LazySleepStep = class extends BaseLazyStep {
1151
1155
  headers,
1152
1156
  method: "POST",
1153
1157
  url: context.url,
1154
- retries: DEFAULT_RETRIES === context.retries ? void 0 : context.retries,
1155
- retryDelay: context.retryDelay,
1156
- flowControl: context.flowControl,
1157
1158
  delay: isParallel ? void 0 : this.sleep
1158
1159
  }
1159
1160
  ]);
@@ -1196,9 +1197,6 @@ var LazySleepUntilStep = class extends BaseLazyStep {
1196
1197
  headers,
1197
1198
  method: "POST",
1198
1199
  url: context.url,
1199
- retries: DEFAULT_RETRIES === context.retries ? void 0 : context.retries,
1200
- retryDelay: context.retryDelay,
1201
- flowControl: context.flowControl,
1202
1200
  notBefore: isParallel ? void 0 : this.sleepUntil
1203
1201
  }
1204
1202
  ]);
@@ -1213,20 +1211,18 @@ var LazyCallStep = class _LazyCallStep extends BaseLazyStep {
1213
1211
  retryDelay;
1214
1212
  timeout;
1215
1213
  flowControl;
1216
- stringifyBody;
1217
1214
  stepType = "Call";
1218
1215
  allowUndefinedOut = false;
1219
- constructor(context, stepName, url, method, body, headers, retries, retryDelay, timeout, flowControl, stringifyBody) {
1220
- super(context, stepName);
1221
- this.url = url;
1222
- this.method = method;
1223
- this.body = body;
1224
- this.headers = headers;
1225
- this.retries = retries;
1226
- this.retryDelay = retryDelay;
1227
- this.timeout = timeout;
1228
- this.flowControl = flowControl;
1229
- this.stringifyBody = stringifyBody;
1216
+ constructor(params) {
1217
+ super(params.context, params.stepName);
1218
+ this.url = params.url;
1219
+ this.method = params.method ?? "GET";
1220
+ this.body = params.body;
1221
+ this.headers = params.headers ?? {};
1222
+ this.retries = params.retries ?? 0;
1223
+ this.retryDelay = params.retryDelay;
1224
+ this.timeout = params.timeout;
1225
+ this.flowControl = params.flowControl;
1230
1226
  }
1231
1227
  getPlanStep(concurrent, targetStep) {
1232
1228
  return {
@@ -1323,7 +1319,7 @@ var LazyCallStep = class _LazyCallStep extends BaseLazyStep {
1323
1319
  "Upstash-Callback-Workflow-CallType": "fromCallback",
1324
1320
  "Upstash-Callback-Workflow-Init": "false",
1325
1321
  "Upstash-Callback-Workflow-Url": context.url,
1326
- "Upstash-Callback-Feature-Set": "LazyFetch,InitialBody,WF_DetectTrigger",
1322
+ "Upstash-Callback-Feature-Set": "LazyFetch,InitialBody,WF_DetectTrigger,WF_TriggerOnConfig",
1327
1323
  "Upstash-Callback-Forward-Upstash-Workflow-Callback": "true",
1328
1324
  "Upstash-Callback-Forward-Upstash-Workflow-StepId": step.stepId.toString(),
1329
1325
  "Upstash-Callback-Forward-Upstash-Workflow-StepName": this.stepName,
@@ -1336,22 +1332,10 @@ var LazyCallStep = class _LazyCallStep extends BaseLazyStep {
1336
1332
  };
1337
1333
  }
1338
1334
  async submitStep({ context, headers }) {
1339
- let callBody;
1340
- if (this.stringifyBody) {
1341
- callBody = JSON.stringify(this.body);
1342
- } else {
1343
- if (typeof this.body === "string") {
1344
- callBody = this.body;
1345
- } else {
1346
- throw new WorkflowError(
1347
- "When stringifyBody is false, body must be a string. Please check the body type of your call step."
1348
- );
1349
- }
1350
- }
1351
1335
  return await context.qstashClient.batch([
1352
1336
  {
1353
1337
  headers,
1354
- body: callBody,
1338
+ body: this.body,
1355
1339
  method: this.method,
1356
1340
  url: this.url,
1357
1341
  retries: DEFAULT_RETRIES === this.retries ? void 0 : this.retries,
@@ -1471,28 +1455,10 @@ var LazyInvokeStep = class extends BaseLazyStep {
1471
1455
  * workflow id of the invoked workflow
1472
1456
  */
1473
1457
  workflowId;
1474
- constructor(context, stepName, {
1475
- workflow,
1476
- body,
1477
- headers = {},
1478
- workflowRunId,
1479
- retries,
1480
- retryDelay,
1481
- flowControl,
1482
- stringifyBody = true
1483
- }) {
1458
+ constructor(context, stepName, params) {
1484
1459
  super(context, stepName);
1485
- this.params = {
1486
- workflow,
1487
- body,
1488
- headers,
1489
- workflowRunId: getWorkflowRunId(workflowRunId),
1490
- retries,
1491
- retryDelay,
1492
- flowControl,
1493
- stringifyBody
1494
- };
1495
- const { workflowId } = workflow;
1460
+ this.params = params;
1461
+ const { workflowId } = params.workflow;
1496
1462
  if (!workflowId) {
1497
1463
  throw new WorkflowError("You can only invoke workflow which has a workflowId");
1498
1464
  }
@@ -1532,11 +1498,7 @@ var LazyInvokeStep = class extends BaseLazyStep {
1532
1498
  workflowConfig: {
1533
1499
  workflowRunId: context.workflowRunId,
1534
1500
  workflowUrl: context.url,
1535
- failureUrl: context.failureUrl,
1536
- retries: context.retries,
1537
- retryDelay: context.retryDelay,
1538
1501
  telemetry,
1539
- flowControl: context.flowControl,
1540
1502
  useJSONContent: false
1541
1503
  },
1542
1504
  userHeaders: context.headers,
@@ -1546,20 +1508,8 @@ var LazyInvokeStep = class extends BaseLazyStep {
1546
1508
  invokerHeaders[key] = value;
1547
1509
  });
1548
1510
  invokerHeaders["Upstash-Workflow-Runid"] = context.workflowRunId;
1549
- let invokeBody;
1550
- if (this.params.stringifyBody) {
1551
- invokeBody = JSON.stringify(this.params.body);
1552
- } else {
1553
- if (typeof this.params.body === "string") {
1554
- invokeBody = this.params.body;
1555
- } else {
1556
- throw new WorkflowError(
1557
- "When stringifyBody is false, body must be a string. Please check the body type of your invoke step."
1558
- );
1559
- }
1560
- }
1561
1511
  const request = {
1562
- body: invokeBody,
1512
+ body: stringifyBody(this.params.body),
1563
1513
  headers: Object.fromEntries(
1564
1514
  Object.entries(invokerHeaders).map((pairs) => [pairs[0], [pairs[1]]])
1565
1515
  ),
@@ -1570,34 +1520,19 @@ var LazyInvokeStep = class extends BaseLazyStep {
1570
1520
  return JSON.stringify(request);
1571
1521
  }
1572
1522
  getHeaders({ context, telemetry, invokeCount }) {
1573
- const {
1574
- workflow,
1575
- headers = {},
1576
- workflowRunId = getWorkflowRunId(),
1577
- retries,
1578
- retryDelay,
1579
- flowControl
1580
- } = this.params;
1523
+ const { workflow, headers = {}, workflowRunId, retries, retryDelay, flowControl } = this.params;
1581
1524
  const newUrl = context.url.replace(/[^/]+$/, this.workflowId);
1582
- const {
1583
- retries: workflowRetries,
1584
- retryDelay: workflowRetryDelay,
1585
- failureFunction,
1586
- failureUrl,
1587
- useJSONContent,
1588
- flowControl: workflowFlowControl
1589
- } = workflow.options;
1590
1525
  const { headers: triggerHeaders, contentType } = getHeaders({
1591
1526
  initHeaderValue: "true",
1592
1527
  workflowConfig: {
1593
- workflowRunId,
1528
+ workflowRunId: getWorkflowRunId(workflowRunId),
1594
1529
  workflowUrl: newUrl,
1595
- retries: retries ?? workflowRetries,
1596
- retryDelay: retryDelay ?? workflowRetryDelay,
1530
+ retries,
1531
+ retryDelay,
1597
1532
  telemetry,
1598
- failureUrl: failureFunction ? newUrl : failureUrl,
1599
- flowControl: flowControl ?? workflowFlowControl,
1600
- useJSONContent: useJSONContent ?? false
1533
+ failureUrl: newUrl,
1534
+ flowControl,
1535
+ useJSONContent: workflow.useJSONContent ?? false
1601
1536
  },
1602
1537
  invokeCount: invokeCount + 1,
1603
1538
  userHeaders: new Headers(headers)
@@ -1699,20 +1634,6 @@ var LazyWaitForEventStep = class extends LazyWaitEventStep {
1699
1634
  }
1700
1635
  };
1701
1636
 
1702
- // src/agents/constants.ts
1703
- var AGENT_NAME_HEADER = "upstash-agent-name";
1704
- var MANAGER_AGENT_PROMPT = `You are an agent orchestrating other AI Agents.
1705
-
1706
- These other agents have tools available to them.
1707
-
1708
- Given a prompt, utilize these agents to address requests.
1709
-
1710
- Don't always call all the agents provided to you at the same time. You can call one and use it's response to call another.
1711
-
1712
- Avoid calling the same agent twice in one turn. Instead, prefer to call it once but provide everything
1713
- you need from that agent.
1714
- `;
1715
-
1716
1637
  // src/qstash/headers.ts
1717
1638
  var WorkflowHeaders = class {
1718
1639
  userHeaders;
@@ -1721,14 +1642,15 @@ var WorkflowHeaders = class {
1721
1642
  initHeaderValue;
1722
1643
  stepInfo;
1723
1644
  headers;
1724
- keepTriggerConfig;
1645
+ /**
1646
+ * @param params workflow header parameters
1647
+ */
1725
1648
  constructor({
1726
1649
  userHeaders,
1727
1650
  workflowConfig,
1728
1651
  invokeCount,
1729
1652
  initHeaderValue,
1730
- stepInfo,
1731
- keepTriggerConfig
1653
+ stepInfo
1732
1654
  }) {
1733
1655
  this.userHeaders = userHeaders;
1734
1656
  this.workflowConfig = workflowConfig;
@@ -1740,7 +1662,6 @@ var WorkflowHeaders = class {
1740
1662
  workflowHeaders: {},
1741
1663
  failureHeaders: {}
1742
1664
  };
1743
- this.keepTriggerConfig = keepTriggerConfig;
1744
1665
  }
1745
1666
  getHeaders() {
1746
1667
  this.addBaseHeaders();
@@ -1759,10 +1680,9 @@ var WorkflowHeaders = class {
1759
1680
  [WORKFLOW_INIT_HEADER]: this.initHeaderValue,
1760
1681
  [WORKFLOW_ID_HEADER]: this.workflowConfig.workflowRunId,
1761
1682
  [WORKFLOW_URL_HEADER]: this.workflowConfig.workflowUrl,
1762
- [WORKFLOW_FEATURE_HEADER]: "LazyFetch,InitialBody,WF_DetectTrigger" + (this.keepTriggerConfig ? ",WF_TriggerOnConfig" : ""),
1683
+ [WORKFLOW_FEATURE_HEADER]: "LazyFetch,InitialBody,WF_DetectTrigger,WF_TriggerOnConfig",
1763
1684
  [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
1764
- ...this.workflowConfig.telemetry ? getTelemetryHeaders(this.workflowConfig.telemetry) : {},
1765
- ...this.workflowConfig.telemetry && this.stepInfo?.lazyStep instanceof LazyCallStep && this.stepInfo.lazyStep.headers[AGENT_NAME_HEADER] ? { [TELEMETRY_HEADER_AGENT]: "true" } : {}
1685
+ ...this.workflowConfig.telemetry ? getTelemetryHeaders(this.workflowConfig.telemetry) : {}
1766
1686
  };
1767
1687
  if (this.stepInfo?.lazyStep.stepType !== "Call") {
1768
1688
  this.headers.rawHeaders[`Upstash-Forward-${WORKFLOW_PROTOCOL_VERSION_HEADER}`] = WORKFLOW_PROTOCOL_VERSION;
@@ -1830,12 +1750,12 @@ var WorkflowHeaders = class {
1830
1750
  }
1831
1751
  this.headers.workflowHeaders["Failure-Callback"] = this.workflowConfig.failureUrl;
1832
1752
  this.headers.failureHeaders[`Forward-${WORKFLOW_FAILURE_HEADER}`] = "true";
1833
- this.headers.failureHeaders[`Forward-Upstash-Workflow-Failure-Callback`] = "true";
1753
+ this.headers.failureHeaders[`Forward-${WORKFLOW_FAILURE_CALLBACK_HEADER}`] = "true";
1834
1754
  this.headers.failureHeaders["Workflow-Runid"] = this.workflowConfig.workflowRunId;
1835
1755
  this.headers.failureHeaders["Workflow-Init"] = "false";
1836
1756
  this.headers.failureHeaders["Workflow-Url"] = this.workflowConfig.workflowUrl;
1837
1757
  this.headers.failureHeaders["Workflow-Calltype"] = "failureCall";
1838
- this.headers.failureHeaders["Feature-Set"] = "LazyFetch,InitialBody,WF_DetectTrigger";
1758
+ this.headers.failureHeaders["Feature-Set"] = "LazyFetch,InitialBody,WF_DetectTrigger,WF_TriggerOnConfig";
1839
1759
  if (this.workflowConfig.retries !== void 0 && this.workflowConfig.retries !== DEFAULT_RETRIES) {
1840
1760
  this.headers.failureHeaders["Retries"] = this.workflowConfig.retries.toString();
1841
1761
  }
@@ -1905,14 +1825,13 @@ var submitParallelSteps = async ({
1905
1825
  initialStepCount,
1906
1826
  invokeCount,
1907
1827
  telemetry,
1908
- debug
1828
+ dispatchDebug
1909
1829
  }) => {
1910
1830
  const planSteps = steps.map(
1911
1831
  (step, index) => step.getPlanStep(steps.length, initialStepCount + index)
1912
1832
  );
1913
- await debug?.log("SUBMIT", "SUBMIT_STEP", {
1914
- length: planSteps.length,
1915
- steps: planSteps
1833
+ await dispatchDebug("onInfo", {
1834
+ info: `Submitting ${planSteps.length} parallel steps.`
1916
1835
  });
1917
1836
  const result = await context.qstashClient.batch(
1918
1837
  planSteps.map((planStep) => {
@@ -1921,10 +1840,6 @@ var submitParallelSteps = async ({
1921
1840
  workflowConfig: {
1922
1841
  workflowRunId: context.workflowRunId,
1923
1842
  workflowUrl: context.url,
1924
- failureUrl: context.failureUrl,
1925
- retries: context.retries,
1926
- retryDelay: context.retryDelay,
1927
- flowControl: context.flowControl,
1928
1843
  telemetry
1929
1844
  },
1930
1845
  userHeaders: context.headers,
@@ -1940,13 +1855,11 @@ var submitParallelSteps = async ({
1940
1855
  };
1941
1856
  })
1942
1857
  );
1943
- await debug?.log("INFO", "SUBMIT_STEP", {
1944
- messageIds: result.map((message) => {
1945
- return {
1946
- message: message.messageId
1947
- };
1948
- })
1949
- });
1858
+ if (result && result.length > 0) {
1859
+ await dispatchDebug("onInfo", {
1860
+ info: `Submitted ${planSteps.length} parallel steps. messageIds: ${result.filter((r) => r).map((r) => r.messageId).join(", ")}.`
1861
+ });
1862
+ }
1950
1863
  throw new WorkflowAbort(planSteps[0].stepName, planSteps[0]);
1951
1864
  };
1952
1865
  var submitSingleStep = async ({
@@ -1956,14 +1869,13 @@ var submitSingleStep = async ({
1956
1869
  invokeCount,
1957
1870
  concurrency,
1958
1871
  telemetry,
1959
- debug
1872
+ dispatchDebug,
1873
+ dispatchLifecycle
1960
1874
  }) => {
1961
- const resultStep = await lazyStep.getResultStep(concurrency, stepId);
1962
- await debug?.log("INFO", "RUN_SINGLE", {
1963
- fromRequest: false,
1964
- step: resultStep,
1965
- stepCount: stepId
1875
+ await dispatchLifecycle("beforeExecution", {
1876
+ stepName: lazyStep.stepName
1966
1877
  });
1878
+ const resultStep = await lazyStep.getResultStep(concurrency, stepId);
1967
1879
  const { headers } = lazyStep.getHeaders({
1968
1880
  context,
1969
1881
  step: resultStep,
@@ -1977,10 +1889,6 @@ var submitSingleStep = async ({
1977
1889
  invokeCount,
1978
1890
  telemetry
1979
1891
  });
1980
- await debug?.log("SUBMIT", "SUBMIT_STEP", {
1981
- length: 1,
1982
- steps: [resultStep]
1983
- });
1984
1892
  const submitResult = await lazyStep.submitStep({
1985
1893
  context,
1986
1894
  body,
@@ -1990,13 +1898,11 @@ var submitSingleStep = async ({
1990
1898
  step: resultStep,
1991
1899
  telemetry
1992
1900
  });
1993
- await debug?.log("INFO", "SUBMIT_STEP", {
1994
- messageIds: submitResult.map((message) => {
1995
- return {
1996
- message: message.messageId
1997
- };
1998
- })
1999
- });
1901
+ if (submitResult && submitResult[0]) {
1902
+ await dispatchDebug("onInfo", {
1903
+ info: `Submitted step "${resultStep.stepName}" with messageId: ${submitResult[0].messageId}.`
1904
+ });
1905
+ }
2000
1906
  return resultStep;
2001
1907
  };
2002
1908
 
@@ -2005,21 +1911,31 @@ var AutoExecutor = class _AutoExecutor {
2005
1911
  context;
2006
1912
  promises = /* @__PURE__ */ new WeakMap();
2007
1913
  activeLazyStepList;
2008
- debug;
2009
1914
  nonPlanStepCount;
2010
1915
  steps;
2011
1916
  indexInCurrentList = 0;
2012
1917
  invokeCount;
2013
1918
  telemetry;
1919
+ dispatchDebug;
1920
+ dispatchLifecycle;
2014
1921
  stepCount = 0;
2015
1922
  planStepCount = 0;
2016
1923
  executingStep = false;
2017
- constructor(context, steps, telemetry, invokeCount, debug) {
1924
+ /**
1925
+ * @param context workflow context
1926
+ * @param steps list of steps
1927
+ * @param dispatchDebug debug event dispatcher
1928
+ * @param dispatchLifecycle lifecycle event dispatcher
1929
+ * @param telemetry optional telemetry information
1930
+ * @param invokeCount optional invoke count
1931
+ */
1932
+ constructor(context, steps, dispatchDebug, dispatchLifecycle, telemetry, invokeCount) {
2018
1933
  this.context = context;
2019
1934
  this.steps = steps;
1935
+ this.dispatchDebug = dispatchDebug;
1936
+ this.dispatchLifecycle = dispatchLifecycle;
2020
1937
  this.telemetry = telemetry;
2021
1938
  this.invokeCount = invokeCount ?? 0;
2022
- this.debug = debug;
2023
1939
  this.nonPlanStepCount = this.steps.filter((step) => !step.targetStep).length;
2024
1940
  }
2025
1941
  /**
@@ -2087,7 +2003,7 @@ var AutoExecutor = class _AutoExecutor {
2087
2003
  /**
2088
2004
  * Executes a step:
2089
2005
  * - If the step result is available in the steps, returns the result
2090
- * - If the result is not avaiable, runs the function
2006
+ * - If the result is not available, runs the function
2091
2007
  * - Sends the result to QStash
2092
2008
  *
2093
2009
  * @param lazyStep lazy step to execute
@@ -2097,12 +2013,15 @@ var AutoExecutor = class _AutoExecutor {
2097
2013
  if (this.stepCount < this.nonPlanStepCount) {
2098
2014
  const step = this.steps[this.stepCount + this.planStepCount];
2099
2015
  validateStep(lazyStep, step);
2100
- await this.debug?.log("INFO", "RUN_SINGLE", {
2101
- fromRequest: true,
2102
- step,
2103
- stepCount: this.stepCount
2104
- });
2105
- return lazyStep.parseOut(step);
2016
+ const parsedOut = lazyStep.parseOut(step);
2017
+ const isLastMemoized = this.stepCount + 1 === this.nonPlanStepCount && this.steps.at(-1).stepId !== 0;
2018
+ if (isLastMemoized) {
2019
+ await this.dispatchLifecycle("afterExecution", {
2020
+ stepName: lazyStep.stepName,
2021
+ result: parsedOut
2022
+ });
2023
+ }
2024
+ return parsedOut;
2106
2025
  }
2107
2026
  const resultStep = await submitSingleStep({
2108
2027
  context: this.context,
@@ -2111,15 +2030,15 @@ var AutoExecutor = class _AutoExecutor {
2111
2030
  invokeCount: this.invokeCount,
2112
2031
  concurrency: 1,
2113
2032
  telemetry: this.telemetry,
2114
- debug: this.debug
2033
+ dispatchDebug: this.dispatchDebug,
2034
+ dispatchLifecycle: this.dispatchLifecycle
2115
2035
  });
2116
2036
  throw new WorkflowAbort(lazyStep.stepName, resultStep);
2117
2037
  }
2118
2038
  /**
2119
2039
  * Runs steps in parallel.
2120
2040
  *
2121
- * @param stepName parallel step name
2122
- * @param stepFunctions list of async functions to run in parallel
2041
+ * @param parallelSteps list of lazy steps to run in parallel
2123
2042
  * @returns results of the functions run in parallel
2124
2043
  */
2125
2044
  async runParallel(parallelSteps) {
@@ -2132,12 +2051,14 @@ var AutoExecutor = class _AutoExecutor {
2132
2051
  `Incompatible number of parallel steps when call state was '${parallelCallState}'. Expected ${parallelSteps.length}, got ${plannedParallelStepCount} from the request.`
2133
2052
  );
2134
2053
  }
2135
- await this.debug?.log("INFO", "RUN_PARALLEL", {
2136
- parallelCallState,
2137
- initialStepCount,
2138
- plannedParallelStepCount,
2139
- stepCount: this.stepCount,
2140
- planStepCount: this.planStepCount
2054
+ await this.dispatchDebug("onInfo", {
2055
+ info: `Executing parallel steps with: ` + JSON.stringify({
2056
+ parallelCallState,
2057
+ initialStepCount,
2058
+ plannedParallelStepCount,
2059
+ stepCount: this.stepCount,
2060
+ planStepCount: this.planStepCount
2061
+ })
2141
2062
  });
2142
2063
  switch (parallelCallState) {
2143
2064
  case "first": {
@@ -2147,7 +2068,7 @@ var AutoExecutor = class _AutoExecutor {
2147
2068
  initialStepCount,
2148
2069
  invokeCount: this.invokeCount,
2149
2070
  telemetry: this.telemetry,
2150
- debug: this.debug
2071
+ dispatchDebug: this.dispatchDebug
2151
2072
  });
2152
2073
  break;
2153
2074
  }
@@ -2169,7 +2090,8 @@ var AutoExecutor = class _AutoExecutor {
2169
2090
  invokeCount: this.invokeCount,
2170
2091
  concurrency: parallelSteps.length,
2171
2092
  telemetry: this.telemetry,
2172
- debug: this.debug
2093
+ dispatchDebug: this.dispatchDebug,
2094
+ dispatchLifecycle: this.dispatchLifecycle
2173
2095
  });
2174
2096
  throw new WorkflowAbort(parallelStep.stepName, resultStep);
2175
2097
  } catch (error) {
@@ -2183,11 +2105,32 @@ var AutoExecutor = class _AutoExecutor {
2183
2105
  break;
2184
2106
  }
2185
2107
  case "discard": {
2108
+ const resultStep = this.steps.at(-1);
2109
+ const lazyStep = parallelSteps.find(
2110
+ (planStep, index) => resultStep.stepId - index === initialStepCount
2111
+ );
2112
+ if (lazyStep) {
2113
+ await this.dispatchLifecycle("afterExecution", {
2114
+ stepName: lazyStep.stepName,
2115
+ result: lazyStep.parseOut(resultStep)
2116
+ });
2117
+ }
2186
2118
  throw new WorkflowAbort("discarded parallel");
2187
2119
  }
2188
2120
  case "last": {
2189
2121
  const parallelResultSteps = sortedSteps.filter((step) => step.stepId >= initialStepCount).slice(0, parallelSteps.length);
2190
2122
  validateParallelSteps(parallelSteps, parallelResultSteps);
2123
+ const isLastMemoized = this.stepCount + 1 === this.nonPlanStepCount && this.steps.at(-1).stepId !== 0;
2124
+ if (isLastMemoized) {
2125
+ const resultStep = this.steps.at(-1);
2126
+ const lazyStep = parallelSteps.find(
2127
+ (planStep, index) => resultStep.stepId - index === initialStepCount
2128
+ );
2129
+ await this.dispatchLifecycle("afterExecution", {
2130
+ stepName: lazyStep.stepName,
2131
+ result: lazyStep.parseOut(resultStep)
2132
+ });
2133
+ }
2191
2134
  return parallelResultSteps.map(
2192
2135
  (step, index) => parallelSteps[index].parseOut(step)
2193
2136
  );
@@ -2331,15 +2274,18 @@ var getProviderInfo = (api) => {
2331
2274
  // src/context/api/base.ts
2332
2275
  var BaseWorkflowApi = class {
2333
2276
  context;
2277
+ /**
2278
+ * @param context workflow context
2279
+ */
2334
2280
  constructor({ context }) {
2335
2281
  this.context = context;
2336
2282
  }
2337
2283
  /**
2338
2284
  * context.call which uses a QStash API
2339
2285
  *
2340
- * @param stepName
2341
- * @param settings
2342
- * @returns
2286
+ * @param stepName name of the step
2287
+ * @param settings call settings including api configuration
2288
+ * @returns call response
2343
2289
  */
2344
2290
  async callApi(stepName, settings) {
2345
2291
  const { url, appendHeaders, method } = getProviderInfo(settings.api);
@@ -2347,7 +2293,7 @@ var BaseWorkflowApi = class {
2347
2293
  return await this.context.call(stepName, {
2348
2294
  url,
2349
2295
  method: userMethod ?? method,
2350
- body,
2296
+ body: typeof body === "string" ? body : JSON.stringify(body),
2351
2297
  headers: {
2352
2298
  ...appendHeaders,
2353
2299
  ...headers
@@ -2424,315 +2370,135 @@ var WorkflowApi = class extends BaseWorkflowApi {
2424
2370
  }
2425
2371
  };
2426
2372
 
2427
- // src/agents/index.ts
2428
- var import_openai2 = require("@ai-sdk/openai");
2429
-
2430
- // src/agents/adapters.ts
2431
- var import_ai = require("ai");
2432
- var fetchWithContextCall = async (context, agentCallParams, ...params) => {
2433
- const [input, init] = params;
2434
- try {
2435
- const headers = init?.headers ? Object.fromEntries(new Headers(init.headers).entries()) : {};
2436
- const body = init?.body ? JSON.parse(init.body) : void 0;
2437
- const agentName = headers[AGENT_NAME_HEADER];
2438
- const stepName = agentName ? `Call Agent ${agentName}` : "Call Agent";
2439
- const responseInfo = await context.call(stepName, {
2440
- url: input.toString(),
2441
- method: init?.method,
2442
- headers,
2443
- body,
2444
- timeout: agentCallParams?.timeout,
2445
- retries: agentCallParams?.retries,
2446
- retryDelay: agentCallParams?.retryDelay,
2447
- flowControl: agentCallParams?.flowControl
2448
- });
2449
- const responseHeaders = new Headers(
2450
- Object.entries(responseInfo.header).reduce(
2451
- (acc, [key, values]) => {
2452
- acc[key] = values.join(", ");
2453
- return acc;
2454
- },
2455
- {}
2456
- )
2457
- );
2458
- return new Response(JSON.stringify(responseInfo.body), {
2459
- status: responseInfo.status,
2460
- headers: responseHeaders
2461
- });
2462
- } catch (error) {
2463
- if (error instanceof Error && isInstanceOf(error, WorkflowAbort)) {
2464
- throw error;
2465
- } else {
2466
- console.error("Error in fetch implementation:", error);
2467
- throw error;
2468
- }
2373
+ // src/serve/serve-many.ts
2374
+ var getNewUrlFromWorkflowId = (url, workflowId) => {
2375
+ if (!workflowId) {
2376
+ throw new WorkflowError("You can only call workflow which has a workflowId");
2469
2377
  }
2378
+ return url.replace(/[^/]+$/, workflowId);
2470
2379
  };
2471
- var createWorkflowModel = ({
2472
- context,
2473
- provider,
2474
- providerParams,
2475
- agentCallParams
2476
- }) => {
2477
- return provider({
2478
- fetch: (...params) => fetchWithContextCall(context, agentCallParams, ...params),
2479
- ...providerParams
2480
- });
2481
- };
2482
- var wrapTools = ({
2483
- context,
2484
- tools
2485
- }) => {
2486
- return Object.fromEntries(
2487
- Object.entries(tools).map((toolInfo) => {
2488
- const [toolName, tool3] = toolInfo;
2489
- const executeAsStep = "executeAsStep" in tool3 ? tool3.executeAsStep : true;
2490
- const aiSDKTool = convertToAISDKTool(tool3);
2491
- const execute = aiSDKTool.execute;
2492
- if (execute && executeAsStep) {
2493
- const wrappedExecute = (...params) => {
2494
- return context.run(`Run tool ${toolName}`, () => execute(...params));
2495
- };
2496
- aiSDKTool.execute = wrappedExecute;
2497
- }
2498
- return [toolName, aiSDKTool];
2499
- })
2500
- );
2501
- };
2502
- var convertToAISDKTool = (tool3) => {
2503
- const isLangchainTool = "invoke" in tool3;
2504
- return isLangchainTool ? convertLangchainTool(tool3) : tool3;
2380
+
2381
+ // src/middleware/default-callbacks.ts
2382
+ var onErrorWithConsole = async ({ workflowRunId, error }) => {
2383
+ console.error(` [Upstash Workflow]: Error in workflow run ${workflowRunId}: ` + error);
2505
2384
  };
2506
- var convertLangchainTool = (langchainTool) => {
2507
- return (0, import_ai.tool)({
2508
- description: langchainTool.description,
2509
- parameters: langchainTool.schema,
2510
- execute: async (...param) => langchainTool.invoke(...param)
2511
- });
2385
+ var onWarningWithConsole = async ({ workflowRunId, warning }) => {
2386
+ console.warn(` [Upstash Workflow]: Warning in workflow run ${workflowRunId}: ` + warning);
2512
2387
  };
2513
-
2514
- // src/agents/agent.ts
2515
- var import_zod = require("zod");
2516
- var import_ai2 = require("ai");
2517
-
2518
- // src/serve/utils.ts
2519
- var isDisabledWorkflowContext = (context) => {
2520
- return "disabled" in context;
2388
+ var onInfoWithConsole = async ({
2389
+ workflowRunId,
2390
+ info
2391
+ }) => {
2392
+ console.info(` [Upstash Workflow]: Info in workflow run ${workflowRunId}: ` + info);
2521
2393
  };
2522
2394
 
2523
- // src/agents/agent.ts
2524
- var Agent = class {
2525
- name;
2526
- tools;
2527
- maxSteps;
2528
- background;
2529
- model;
2530
- temparature;
2395
+ // src/middleware/manager.ts
2396
+ var MiddlewareManager = class {
2397
+ middlewares;
2398
+ workflowRunId;
2531
2399
  context;
2532
- constructor({ tools, maxSteps, background, name, model, temparature = 0.1 }, context) {
2533
- this.name = name;
2534
- this.tools = tools ?? {};
2535
- this.maxSteps = maxSteps;
2536
- this.background = background;
2537
- this.model = model;
2538
- this.temparature = temparature;
2539
- this.context = context;
2540
- }
2541
2400
  /**
2542
- * Trigger the agent by passing a prompt
2543
- *
2544
- * @param prompt task to assign to the agent
2545
- * @returns Response as `{ text: string }`
2401
+ * @param middlewares list of workflow middlewares
2546
2402
  */
2547
- async call({ prompt }) {
2548
- try {
2549
- if (isDisabledWorkflowContext(this.context)) {
2550
- await this.context.sleep("abort", 0);
2551
- }
2552
- const result = await (0, import_ai2.generateText)({
2553
- model: this.model,
2554
- tools: this.tools,
2555
- maxSteps: this.maxSteps,
2556
- system: this.background,
2557
- prompt,
2558
- headers: {
2559
- [AGENT_NAME_HEADER]: this.name
2560
- },
2561
- temperature: this.temparature
2562
- });
2563
- return { text: result.text };
2564
- } catch (error) {
2565
- if (isInstanceOf(error, import_ai2.ToolExecutionError)) {
2566
- if (error.cause instanceof Error && isInstanceOf(error.cause, WorkflowAbort)) {
2567
- throw error.cause;
2568
- } else if (isInstanceOf(error.cause, import_ai2.ToolExecutionError) && isInstanceOf(error.cause.cause, WorkflowAbort)) {
2569
- throw error.cause.cause;
2570
- } else {
2571
- throw error;
2572
- }
2573
- } else {
2574
- throw error;
2575
- }
2576
- }
2403
+ constructor(middlewares = []) {
2404
+ this.middlewares = middlewares;
2577
2405
  }
2578
2406
  /**
2579
- * Convert the agent to a tool which can be used by other agents.
2407
+ * Assign workflow run ID - will be passed to debug events
2580
2408
  *
2581
- * @returns the agent as a tool
2409
+ * @param workflowRunId workflow run id to assign
2582
2410
  */
2583
- asTool() {
2584
- const toolDescriptions = Object.values(this.tools).map((tool3) => tool3.description).join("\n");
2585
- return (0, import_ai2.tool)({
2586
- parameters: import_zod.z.object({ prompt: import_zod.z.string() }),
2587
- execute: async ({ prompt }) => {
2588
- return await this.call({ prompt });
2589
- },
2590
- description: `An AI Agent with the following background: ${this.background}Has access to the following tools: ${toolDescriptions}`
2591
- });
2411
+ assignWorkflowRunId(workflowRunId) {
2412
+ this.workflowRunId = workflowRunId;
2592
2413
  }
2593
- };
2594
- var ManagerAgent = class extends Agent {
2595
- agents;
2596
2414
  /**
2597
- * A manager agent which coordinates agents available to it to achieve a
2598
- * given task
2415
+ * Assign context - required for lifecycle events
2599
2416
  *
2600
- * @param name Name of the agent
2601
- * @param background Background of the agent. If not passed, default will be used.
2602
- * @param model LLM model to use
2603
- * @param agents: List of agents available to the agent
2604
- * @param maxSteps number of times the manager agent can call the LLM at most.
2605
- * If the agent abruptly stops execution after calling other agents, you may
2606
- * need to increase maxSteps
2417
+ * also assigns workflowRunId from context
2418
+ *
2419
+ * @param context workflow context to assign
2607
2420
  */
2608
- constructor({
2609
- agents,
2610
- background = MANAGER_AGENT_PROMPT,
2611
- model,
2612
- maxSteps,
2613
- name = "manager llm"
2614
- }, context) {
2615
- super(
2616
- {
2617
- background,
2618
- maxSteps,
2619
- tools: Object.fromEntries(agents.map((agent) => [agent.name, agent.asTool()])),
2620
- name,
2621
- model
2622
- },
2623
- context
2624
- );
2625
- this.agents = agents;
2626
- }
2627
- };
2628
-
2629
- // src/agents/task.ts
2630
- var Task = class {
2631
- context;
2632
- taskParameters;
2633
- constructor({
2634
- context,
2635
- taskParameters
2636
- }) {
2421
+ assignContext(context) {
2637
2422
  this.context = context;
2638
- this.taskParameters = taskParameters;
2423
+ this.workflowRunId = context.workflowRunId;
2639
2424
  }
2640
2425
  /**
2641
- * Run the agents to complete the task
2426
+ * Internal method to execute middlewares with common error handling logic
2642
2427
  *
2643
- * @returns Result of the task as { text: string }
2428
+ * @param event event name to dispatch
2429
+ * @param params event parameters
2644
2430
  */
2645
- async run() {
2646
- const { prompt, ...otherParams } = this.taskParameters;
2647
- if ("agent" in otherParams) {
2648
- const agent = otherParams.agent;
2649
- const result = await agent.call({
2650
- prompt
2431
+ async executeMiddlewares(event, params) {
2432
+ await Promise.all(this.middlewares.map((m) => m.ensureInit()));
2433
+ await Promise.all(
2434
+ this.middlewares.map(async (middleware) => {
2435
+ const callback = middleware.getCallback(event);
2436
+ if (callback) {
2437
+ try {
2438
+ await callback(params);
2439
+ } catch (error) {
2440
+ try {
2441
+ const onErrorCallback = middleware.getCallback("onError") ?? onErrorWithConsole;
2442
+ await onErrorCallback({
2443
+ workflowRunId: this.workflowRunId,
2444
+ error
2445
+ });
2446
+ } catch (onErrorError) {
2447
+ console.error(
2448
+ `Failed while executing "onError" of middleware "${middleware.name}", falling back to logging the error to console. Error: ${onErrorError}`
2449
+ );
2450
+ onErrorWithConsole({
2451
+ workflowRunId: this.workflowRunId,
2452
+ error
2453
+ });
2454
+ }
2455
+ }
2456
+ }
2457
+ })
2458
+ );
2459
+ if (event === "onError") {
2460
+ onErrorWithConsole({
2461
+ workflowRunId: this.workflowRunId,
2462
+ ...params
2463
+ });
2464
+ } else if (event === "onWarning") {
2465
+ onWarningWithConsole({
2466
+ workflowRunId: this.workflowRunId,
2467
+ ...params
2651
2468
  });
2652
- return { text: result.text };
2653
- } else {
2654
- const { agents, maxSteps, model, background } = otherParams;
2655
- const managerAgent = new ManagerAgent(
2656
- {
2657
- model,
2658
- maxSteps,
2659
- agents,
2660
- name: "Manager LLM",
2661
- background
2662
- },
2663
- this.context
2664
- );
2665
- const result = await managerAgent.call({ prompt });
2666
- return { text: result.text };
2667
2469
  }
2668
2470
  }
2669
- };
2670
-
2671
- // src/agents/index.ts
2672
- var WorkflowAgents = class {
2673
- context;
2674
- constructor({ context }) {
2675
- this.context = context;
2676
- }
2677
2471
  /**
2678
- * Defines an agent
2472
+ * Dispatch a debug event (onError, onWarning, onInfo)
2679
2473
  *
2680
- * ```ts
2681
- * const researcherAgent = context.agents.agent({
2682
- * model,
2683
- * name: 'academic',
2684
- * maxSteps: 2,
2685
- * tools: {
2686
- * wikiTool: new WikipediaQueryRun({
2687
- * topKResults: 1,
2688
- * maxDocContentLength: 500,
2689
- * })
2690
- * },
2691
- * background:
2692
- * 'You are researcher agent with access to Wikipedia. ' +
2693
- * 'Utilize Wikipedia as much as possible for correct information',
2694
- * });
2695
- * ```
2696
- *
2697
- * @param params agent parameters
2698
- * @returns
2474
+ * @param event debug event name
2475
+ * @param params event parameters
2699
2476
  */
2700
- agent(params) {
2701
- const wrappedTools = wrapTools({ context: this.context, tools: params.tools });
2702
- return new Agent(
2703
- {
2704
- ...params,
2705
- tools: wrappedTools
2706
- },
2707
- this.context
2708
- );
2709
- }
2710
- task(taskParameters) {
2711
- return new Task({ context: this.context, taskParameters });
2477
+ async dispatchDebug(event, params) {
2478
+ const paramsWithRunId = {
2479
+ ...params,
2480
+ workflowRunId: this.workflowRunId
2481
+ };
2482
+ await this.executeMiddlewares(event, paramsWithRunId);
2712
2483
  }
2713
2484
  /**
2714
- * creates an openai model for agents
2485
+ * Dispatch a lifecycle event (beforeExecution, afterExecution, runStarted, runCompleted)
2486
+ *
2487
+ * @param event lifecycle event name
2488
+ * @param params event parameters
2715
2489
  */
2716
- openai(...params) {
2717
- const [model, settings] = params;
2718
- const { baseURL, apiKey, callSettings, ...otherSettings } = settings ?? {};
2719
- const openaiModel = this.AISDKModel({
2720
- context: this.context,
2721
- provider: import_openai2.createOpenAI,
2722
- providerParams: { baseURL, apiKey, compatibility: "strict" },
2723
- agentCallParams: callSettings
2724
- });
2725
- return openaiModel(model, otherSettings);
2726
- }
2727
- AISDKModel = createWorkflowModel;
2728
- };
2729
-
2730
- // src/serve/serve-many.ts
2731
- var getNewUrlFromWorkflowId = (url, workflowId) => {
2732
- if (!workflowId) {
2733
- throw new WorkflowError("You can only call workflow which has a workflowId");
2490
+ async dispatchLifecycle(event, params) {
2491
+ if (!this.context) {
2492
+ throw new WorkflowError(
2493
+ `Something went wrong while calling middlewares. Lifecycle event "${event}" was called before assignContext.`
2494
+ );
2495
+ }
2496
+ const paramsWithContext = {
2497
+ ...params,
2498
+ context: this.context
2499
+ };
2500
+ await this.executeMiddlewares(event, paramsWithContext);
2734
2501
  }
2735
- return url.replace(/[^/]+$/, workflowId);
2736
2502
  };
2737
2503
 
2738
2504
  // src/context/context.ts
@@ -2779,25 +2545,6 @@ var WorkflowContext = class {
2779
2545
  * ```
2780
2546
  */
2781
2547
  url;
2782
- /**
2783
- * URL to call in case of workflow failure with QStash failure callback
2784
- *
2785
- * https://upstash.com/docs/qstash/features/callbacks#what-is-a-failure-callback
2786
- *
2787
- * Can be overwritten by passing a `failureUrl` parameter in `serve`:
2788
- *
2789
- * ```ts
2790
- * export const POST = serve(
2791
- * async (context) => {
2792
- * ...
2793
- * },
2794
- * {
2795
- * failureUrl: "new-url-value"
2796
- * }
2797
- * )
2798
- * ```
2799
- */
2800
- failureUrl;
2801
2548
  /**
2802
2549
  * Payload of the request which started the workflow.
2803
2550
  *
@@ -2853,46 +2600,6 @@ var WorkflowContext = class {
2853
2600
  * Default value is set to `process.env`.
2854
2601
  */
2855
2602
  env;
2856
- /**
2857
- * Number of retries
2858
- */
2859
- retries;
2860
- /**
2861
- * Delay between retries.
2862
- *
2863
- * By default, the `retryDelay` is exponential backoff.
2864
- * More details can be found in: https://upstash.com/docs/qstash/features/retry.
2865
- *
2866
- * The `retryDelay` option allows you to customize the delay (in milliseconds) between retry attempts when message delivery fails.
2867
- *
2868
- * You can use mathematical expressions and the following built-in functions to calculate the delay dynamically.
2869
- * The special variable `retried` represents the current retry attempt count (starting from 0).
2870
- *
2871
- * Supported functions:
2872
- * - `pow`
2873
- * - `sqrt`
2874
- * - `abs`
2875
- * - `exp`
2876
- * - `floor`
2877
- * - `ceil`
2878
- * - `round`
2879
- * - `min`
2880
- * - `max`
2881
- *
2882
- * Examples of valid `retryDelay` values:
2883
- * ```ts
2884
- * 1000 // 1 second
2885
- * 1000 * (1 + retried) // 1 second multiplied by the current retry attempt
2886
- * pow(2, retried) // 2 to the power of the current retry attempt
2887
- * max(10, pow(2, retried)) // The greater of 10 or 2^retried
2888
- * ```
2889
- */
2890
- retryDelay;
2891
- /**
2892
- * Settings for controlling the number of active requests
2893
- * and number of requests per second with the same key.
2894
- */
2895
- flowControl;
2896
2603
  /**
2897
2604
  * Label to apply to the workflow run.
2898
2605
  *
@@ -2915,30 +2622,31 @@ var WorkflowContext = class {
2915
2622
  headers,
2916
2623
  steps,
2917
2624
  url,
2918
- failureUrl,
2919
- debug,
2920
2625
  initialPayload,
2921
2626
  env,
2922
- retries,
2923
- retryDelay,
2924
2627
  telemetry,
2925
2628
  invokeCount,
2926
- flowControl,
2927
- label
2629
+ label,
2630
+ middlewareManager
2928
2631
  }) {
2929
2632
  this.qstashClient = qstashClient;
2930
2633
  this.workflowRunId = workflowRunId;
2931
2634
  this.steps = steps;
2932
2635
  this.url = url;
2933
- this.failureUrl = failureUrl;
2934
2636
  this.headers = headers;
2935
2637
  this.requestPayload = initialPayload;
2936
2638
  this.env = env ?? {};
2937
- this.retries = retries ?? DEFAULT_RETRIES;
2938
- this.retryDelay = retryDelay;
2939
- this.flowControl = flowControl;
2940
2639
  this.label = label;
2941
- this.executor = new AutoExecutor(this, this.steps, telemetry, invokeCount, debug);
2640
+ const middlewareManagerInstance = middlewareManager ?? new MiddlewareManager([]);
2641
+ middlewareManagerInstance.assignContext(this);
2642
+ this.executor = new AutoExecutor(
2643
+ this,
2644
+ this.steps,
2645
+ middlewareManagerInstance.dispatchDebug.bind(middlewareManagerInstance),
2646
+ middlewareManagerInstance.dispatchLifecycle.bind(middlewareManagerInstance),
2647
+ telemetry,
2648
+ invokeCount
2649
+ );
2942
2650
  }
2943
2651
  /**
2944
2652
  * Executes a workflow step
@@ -2968,7 +2676,7 @@ var WorkflowContext = class {
2968
2676
  * @returns result of the step function
2969
2677
  */
2970
2678
  async run(stepName, stepFunction) {
2971
- const wrappedStepFunction = () => this.executor.wrapStep(stepName, stepFunction);
2679
+ const wrappedStepFunction = (() => this.executor.wrapStep(stepName, stepFunction));
2972
2680
  return await this.addStep(new LazyFunctionStep(this, stepName, wrappedStepFunction));
2973
2681
  }
2974
2682
  /**
@@ -3011,44 +2719,32 @@ var WorkflowContext = class {
3011
2719
  let callStep;
3012
2720
  if ("workflow" in settings) {
3013
2721
  const url = getNewUrlFromWorkflowId(this.url, settings.workflow.workflowId);
3014
- callStep = new LazyCallStep(
3015
- this,
2722
+ const stringBody = typeof settings.body === "string" ? settings.body : settings.body === void 0 ? void 0 : JSON.stringify(settings.body);
2723
+ callStep = new LazyCallStep({
2724
+ context: this,
3016
2725
  stepName,
3017
2726
  url,
3018
- "POST",
3019
- settings.body,
3020
- settings.headers || {},
3021
- settings.retries || 0,
3022
- settings.retryDelay,
3023
- settings.timeout,
3024
- settings.flowControl ?? settings.workflow.options.flowControl,
3025
- settings.stringifyBody ?? true
3026
- );
2727
+ method: "POST",
2728
+ body: stringBody,
2729
+ headers: settings.headers || {},
2730
+ retries: settings.retries || 0,
2731
+ retryDelay: settings.retryDelay,
2732
+ timeout: settings.timeout,
2733
+ flowControl: settings.flowControl
2734
+ });
3027
2735
  } else {
3028
- const {
3029
- url,
3030
- method = "GET",
3031
- body,
3032
- headers = {},
3033
- retries = 0,
3034
- retryDelay,
3035
- timeout,
3036
- flowControl,
3037
- stringifyBody = true
3038
- } = settings;
3039
- callStep = new LazyCallStep(
3040
- this,
2736
+ callStep = new LazyCallStep({
2737
+ context: this,
3041
2738
  stepName,
3042
- url,
3043
- method,
3044
- body,
3045
- headers,
3046
- retries,
3047
- retryDelay,
3048
- timeout,
3049
- flowControl,
3050
- stringifyBody
3051
- );
2739
+ url: settings.url,
2740
+ method: settings.method ?? "GET",
2741
+ body: settings.body,
2742
+ headers: settings.headers ?? {},
2743
+ retries: settings.retries ?? 0,
2744
+ retryDelay: settings.retryDelay,
2745
+ timeout: settings.timeout,
2746
+ flowControl: settings.flowControl
2747
+ });
3052
2748
  }
3053
2749
  return await this.addStep(callStep);
3054
2750
  }
@@ -3133,11 +2829,11 @@ var WorkflowContext = class {
3133
2829
  /**
3134
2830
  * Cancel the current workflow run
3135
2831
  *
3136
- * Will throw WorkflowAbort to stop workflow execution.
2832
+ * Will throw WorkflowCancelAbort to stop workflow execution.
3137
2833
  * Shouldn't be inside try/catch.
3138
2834
  */
3139
2835
  async cancel() {
3140
- throw new WorkflowAbort("cancel", void 0, true);
2836
+ throw new WorkflowCancelAbort();
3141
2837
  }
3142
2838
  /**
3143
2839
  * Adds steps to the executor. Needed so that it can be overwritten in
@@ -3151,60 +2847,6 @@ var WorkflowContext = class {
3151
2847
  context: this
3152
2848
  });
3153
2849
  }
3154
- get agents() {
3155
- return new WorkflowAgents({
3156
- context: this
3157
- });
3158
- }
3159
- };
3160
-
3161
- // src/logger.ts
3162
- var LOG_LEVELS = ["DEBUG", "INFO", "SUBMIT", "WARN", "ERROR"];
3163
- var WorkflowLogger = class _WorkflowLogger {
3164
- logs = [];
3165
- options;
3166
- workflowRunId = void 0;
3167
- constructor(options) {
3168
- this.options = options;
3169
- }
3170
- async log(level, eventType, details) {
3171
- if (this.shouldLog(level)) {
3172
- const timestamp = Date.now();
3173
- const logEntry = {
3174
- timestamp,
3175
- workflowRunId: this.workflowRunId ?? "",
3176
- logLevel: level,
3177
- eventType,
3178
- details
3179
- };
3180
- this.logs.push(logEntry);
3181
- if (this.options.logOutput === "console") {
3182
- this.writeToConsole(logEntry);
3183
- }
3184
- await new Promise((resolve) => setTimeout(resolve, 100));
3185
- }
3186
- }
3187
- setWorkflowRunId(workflowRunId) {
3188
- this.workflowRunId = workflowRunId;
3189
- }
3190
- writeToConsole(logEntry) {
3191
- const JSON_SPACING = 2;
3192
- const logMethod = logEntry.logLevel === "ERROR" ? console.error : logEntry.logLevel === "WARN" ? console.warn : console.log;
3193
- logMethod(JSON.stringify(logEntry, void 0, JSON_SPACING));
3194
- }
3195
- shouldLog(level) {
3196
- return LOG_LEVELS.indexOf(level) >= LOG_LEVELS.indexOf(this.options.logLevel);
3197
- }
3198
- static getLogger(verbose) {
3199
- if (typeof verbose === "object") {
3200
- return verbose;
3201
- } else {
3202
- return verbose ? new _WorkflowLogger({
3203
- logLevel: "INFO",
3204
- logOutput: "console"
3205
- }) : void 0;
3206
- }
3207
- }
3208
2850
  };
3209
2851
 
3210
2852
  // src/serve/authorization.ts
@@ -3213,19 +2855,19 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
3213
2855
  static disabledMessage = "disabled-qstash-worklfow-run";
3214
2856
  disabled = true;
3215
2857
  /**
3216
- * overwrite the WorkflowContext.addStep method to always raise WorkflowAbort
2858
+ * overwrite the WorkflowContext.addStep method to always raise WorkflowAuthError
3217
2859
  * error in order to stop the execution whenever we encounter a step.
3218
2860
  *
3219
2861
  * @param _step
3220
2862
  */
3221
2863
  async addStep(_step) {
3222
- throw new WorkflowAbort(_DisabledWorkflowContext.disabledMessage);
2864
+ throw new WorkflowAuthError(_DisabledWorkflowContext.disabledMessage);
3223
2865
  }
3224
2866
  /**
3225
- * overwrite cancel method to throw WorkflowAbort with the disabledMessage
2867
+ * overwrite cancel method to throw WorkflowAuthError with the disabledMessage
3226
2868
  */
3227
2869
  async cancel() {
3228
- throw new WorkflowAbort(_DisabledWorkflowContext.disabledMessage);
2870
+ throw new WorkflowAuthError(_DisabledWorkflowContext.disabledMessage);
3229
2871
  }
3230
2872
  /**
3231
2873
  * copies the passed context to create a DisabledWorkflowContext. Then, runs the
@@ -3248,18 +2890,14 @@ var DisabledWorkflowContext = class _DisabledWorkflowContext extends WorkflowCon
3248
2890
  headers: context.headers,
3249
2891
  steps: [],
3250
2892
  url: context.url,
3251
- failureUrl: context.failureUrl,
3252
2893
  initialPayload: context.requestPayload,
3253
2894
  env: context.env,
3254
- retries: context.retries,
3255
- retryDelay: context.retryDelay,
3256
- flowControl: context.flowControl,
3257
2895
  label: context.label
3258
2896
  });
3259
2897
  try {
3260
2898
  await routeFunction(disabledContext);
3261
2899
  } catch (error) {
3262
- if (isInstanceOf(error, WorkflowAbort) && error.stepName === this.disabledMessage || isInstanceOf(error, WorkflowNonRetryableError) || isInstanceOf(error, WorkflowRetryAfterError)) {
2900
+ if (isInstanceOf(error, WorkflowAuthError) && error.stepName === this.disabledMessage || isInstanceOf(error, WorkflowNonRetryableError) || isInstanceOf(error, WorkflowRetryAfterError)) {
3263
2901
  return ok("step-found");
3264
2902
  }
3265
2903
  console.warn(
@@ -3319,7 +2957,7 @@ var deduplicateSteps = (steps) => {
3319
2957
  }
3320
2958
  return deduplicatedSteps;
3321
2959
  };
3322
- var checkIfLastOneIsDuplicate = async (steps, debug) => {
2960
+ var checkIfLastOneIsDuplicate = async (steps, dispatchDebug) => {
3323
2961
  if (steps.length < 2) {
3324
2962
  return false;
3325
2963
  }
@@ -3330,14 +2968,41 @@ var checkIfLastOneIsDuplicate = async (steps, debug) => {
3330
2968
  const step = steps[index];
3331
2969
  if (step.stepId === lastStepId && step.targetStep === lastTargetStepId) {
3332
2970
  const message = `Upstash Workflow: The step '${step.stepName}' with id '${step.stepId}' has run twice during workflow execution. Rest of the workflow will continue running as usual.`;
3333
- await debug?.log("WARN", "RESPONSE_DEFAULT", message);
3334
- console.warn(message);
2971
+ await dispatchDebug?.("onWarning", {
2972
+ warning: message
2973
+ });
3335
2974
  return true;
3336
2975
  }
3337
2976
  }
3338
2977
  return false;
3339
2978
  };
3340
2979
  var validateRequest = (request) => {
2980
+ if (request.headers.get(WORKFLOW_UNKOWN_SDK_VERSION_HEADER)) {
2981
+ const workflowRunId2 = request.headers.get(WORKFLOW_ID_HEADER);
2982
+ if (!workflowRunId2) {
2983
+ throw new WorkflowError(
2984
+ "Couldn't get workflow id from header when handling unknown sdk request"
2985
+ );
2986
+ }
2987
+ return {
2988
+ unknownSdk: true,
2989
+ isFirstInvocation: true,
2990
+ workflowRunId: workflowRunId2
2991
+ };
2992
+ }
2993
+ if (request.headers.get(WORKFLOW_FAILURE_CALLBACK_HEADER)) {
2994
+ const workflowRunId2 = request.headers.get(WORKFLOW_ID_HEADER);
2995
+ if (!workflowRunId2) {
2996
+ throw new WorkflowError(
2997
+ "Couldn't get workflow id from header when handling failure callback request"
2998
+ );
2999
+ }
3000
+ return {
3001
+ unknownSdk: false,
3002
+ isFirstInvocation: true,
3003
+ workflowRunId: workflowRunId2
3004
+ };
3005
+ }
3341
3006
  const versionHeader = request.headers.get(WORKFLOW_PROTOCOL_VERSION_HEADER);
3342
3007
  const isFirstInvocation = !versionHeader;
3343
3008
  if (!isFirstInvocation && versionHeader !== WORKFLOW_PROTOCOL_VERSION) {
@@ -3351,11 +3016,20 @@ var validateRequest = (request) => {
3351
3016
  }
3352
3017
  return {
3353
3018
  isFirstInvocation,
3354
- workflowRunId
3019
+ workflowRunId,
3020
+ unknownSdk: false
3355
3021
  };
3356
3022
  };
3357
- var parseRequest = async (requestPayload, isFirstInvocation, workflowRunId, requester, messageId, debug) => {
3358
- if (isFirstInvocation) {
3023
+ var parseRequest = async ({
3024
+ requestPayload,
3025
+ isFirstInvocation,
3026
+ unknownSdk,
3027
+ workflowRunId,
3028
+ requester,
3029
+ messageId,
3030
+ dispatchDebug
3031
+ }) => {
3032
+ if (isFirstInvocation && !unknownSdk) {
3359
3033
  return {
3360
3034
  rawInitialPayload: requestPayload ?? "",
3361
3035
  steps: [],
@@ -3365,16 +3039,14 @@ var parseRequest = async (requestPayload, isFirstInvocation, workflowRunId, requ
3365
3039
  } else {
3366
3040
  let rawSteps;
3367
3041
  if (!requestPayload) {
3368
- await debug?.log(
3369
- "INFO",
3370
- "ENDPOINT_START",
3371
- "request payload is empty, steps will be fetched from QStash."
3372
- );
3042
+ await dispatchDebug?.("onInfo", {
3043
+ info: "request payload is empty, steps will be fetched from QStash."
3044
+ });
3373
3045
  const { steps: fetchedSteps, workflowRunEnded } = await getSteps(
3374
3046
  requester,
3375
3047
  workflowRunId,
3376
3048
  messageId,
3377
- debug
3049
+ dispatchDebug
3378
3050
  );
3379
3051
  if (workflowRunEnded) {
3380
3052
  return {
@@ -3389,7 +3061,7 @@ var parseRequest = async (requestPayload, isFirstInvocation, workflowRunId, requ
3389
3061
  rawSteps = JSON.parse(requestPayload);
3390
3062
  }
3391
3063
  const { rawInitialPayload, steps } = processRawSteps(rawSteps);
3392
- const isLastDuplicate = await checkIfLastOneIsDuplicate(steps, debug);
3064
+ const isLastDuplicate = await checkIfLastOneIsDuplicate(steps, dispatchDebug);
3393
3065
  const deduplicatedSteps = deduplicateSteps(steps);
3394
3066
  return {
3395
3067
  rawInitialPayload,
@@ -3399,16 +3071,21 @@ var parseRequest = async (requestPayload, isFirstInvocation, workflowRunId, requ
3399
3071
  };
3400
3072
  }
3401
3073
  };
3402
- var handleFailure = async (request, requestPayload, qstashClient, initialPayloadParser, routeFunction, failureFunction, env, retries, retryDelay, flowControl, debug) => {
3403
- if (request.headers.get(WORKFLOW_FAILURE_HEADER) !== "true") {
3074
+ var handleFailure = async ({
3075
+ request,
3076
+ requestPayload,
3077
+ qstashClient,
3078
+ initialPayloadParser,
3079
+ routeFunction,
3080
+ failureFunction,
3081
+ env,
3082
+ dispatchDebug
3083
+ }) => {
3084
+ if (request.headers.get(WORKFLOW_FAILURE_HEADER) !== "true" && !request.headers.get(WORKFLOW_FAILURE_CALLBACK_HEADER)) {
3404
3085
  return ok({ result: "not-failure-callback" });
3405
3086
  }
3406
3087
  if (!failureFunction) {
3407
- return err(
3408
- new WorkflowError(
3409
- "Workflow endpoint is called to handle a failure, but a failureFunction is not provided in serve options. Either provide a failureUrl or a failureFunction."
3410
- )
3411
- );
3088
+ return ok({ result: "failure-function-undefined" });
3412
3089
  }
3413
3090
  try {
3414
3091
  const { status, header, body, url, sourceBody, workflowRunId } = JSON.parse(requestPayload);
@@ -3436,23 +3113,21 @@ var handleFailure = async (request, requestPayload, qstashClient, initialPayload
3436
3113
  headers: userHeaders,
3437
3114
  steps: [],
3438
3115
  url,
3439
- failureUrl: url,
3440
- debug,
3441
3116
  env,
3442
- retries,
3443
- retryDelay,
3444
- flowControl,
3445
3117
  telemetry: void 0,
3446
3118
  // not going to make requests in authentication check
3447
- label: userHeaders.get(WORKFLOW_LABEL_HEADER) ?? void 0
3119
+ label: userHeaders.get(WORKFLOW_LABEL_HEADER) ?? void 0,
3120
+ middlewareManager: void 0
3448
3121
  });
3449
3122
  const authCheck = await DisabledWorkflowContext.tryAuthentication(
3450
3123
  routeFunction,
3451
3124
  workflowContext
3452
3125
  );
3453
3126
  if (authCheck.isErr()) {
3454
- await debug?.log("ERROR", "ERROR", { error: authCheck.error.message });
3455
- throw authCheck.error;
3127
+ await dispatchDebug?.("onError", {
3128
+ error: authCheck.error
3129
+ });
3130
+ return err(authCheck.error);
3456
3131
  } else if (authCheck.value === "run-ended") {
3457
3132
  return err(new WorkflowError("Not authorized to run the failure function."));
3458
3133
  }
@@ -3463,74 +3138,309 @@ var handleFailure = async (request, requestPayload, qstashClient, initialPayload
3463
3138
  failHeaders: header,
3464
3139
  failStack
3465
3140
  });
3466
- return ok({ result: "is-failure-callback", response: failureResponse });
3141
+ return ok({ result: "failure-function-executed", response: failureResponse });
3467
3142
  } catch (error) {
3468
3143
  return err(error);
3469
3144
  }
3470
3145
  };
3471
3146
 
3472
- // src/serve/options.ts
3147
+ // src/serve/multi-region/handlers.ts
3473
3148
  var import_qstash10 = require("@upstash/qstash");
3474
- var import_qstash11 = require("@upstash/qstash");
3475
- var processOptions = (options) => {
3476
- const environment = options?.env ?? (typeof process === "undefined" ? {} : process.env);
3477
- const receiverEnvironmentVariablesSet = Boolean(
3478
- environment.QSTASH_CURRENT_SIGNING_KEY && environment.QSTASH_NEXT_SIGNING_KEY
3149
+
3150
+ // src/serve/multi-region/utils.ts
3151
+ var VALID_REGIONS = ["EU_CENTRAL_1", "US_EAST_1"];
3152
+ var getRegionFromEnvironment = (environment) => {
3153
+ const region = environment.QSTASH_REGION;
3154
+ return normalizeRegionHeader(region);
3155
+ };
3156
+ function readEnvironmentVariables(environmentVariables, environment, region) {
3157
+ const result = {};
3158
+ for (const variable of environmentVariables) {
3159
+ const key = region ? `${region}_${variable}` : variable;
3160
+ result[variable] = environment[key];
3161
+ }
3162
+ return result;
3163
+ }
3164
+ function readClientEnvironmentVariables(environment, region) {
3165
+ return readEnvironmentVariables(["QSTASH_URL", "QSTASH_TOKEN"], environment, region);
3166
+ }
3167
+ function readReceiverEnvironmentVariables(environment, region) {
3168
+ return readEnvironmentVariables(
3169
+ ["QSTASH_CURRENT_SIGNING_KEY", "QSTASH_NEXT_SIGNING_KEY"],
3170
+ environment,
3171
+ region
3479
3172
  );
3480
- return {
3481
- qstashClient: options?.qstashClient ?? new import_qstash11.Client({
3482
- baseUrl: environment.QSTASH_URL,
3483
- token: environment.QSTASH_TOKEN
3484
- }),
3485
- onStepFinish: (workflowRunId, _finishCondition, detailedFinishCondition) => {
3486
- if (detailedFinishCondition?.condition === "auth-fail") {
3487
- console.error(AUTH_FAIL_MESSAGE);
3488
- return new Response(
3489
- JSON.stringify({
3490
- message: AUTH_FAIL_MESSAGE,
3491
- workflowRunId
3492
- }),
3493
- {
3494
- status: 400,
3495
- headers: {
3496
- [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION
3497
- }
3498
- }
3499
- );
3500
- } else if (detailedFinishCondition?.condition === "non-retryable-error") {
3501
- return new Response(JSON.stringify(formatWorkflowError(detailedFinishCondition.result)), {
3502
- headers: {
3503
- "Upstash-NonRetryable-Error": "true",
3504
- [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION
3505
- },
3506
- status: 489
3507
- });
3508
- } else if (detailedFinishCondition?.condition === "retry-after-error") {
3509
- return new Response(JSON.stringify(formatWorkflowError(detailedFinishCondition.result)), {
3510
- headers: {
3511
- "Retry-After": detailedFinishCondition.result.retryAfter.toString(),
3512
- [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION
3513
- },
3514
- status: 429
3515
- });
3516
- } else if (detailedFinishCondition?.condition === "failure-callback") {
3517
- return new Response(
3518
- JSON.stringify({ result: detailedFinishCondition.result ?? void 0 }),
3519
- {
3520
- status: 200,
3521
- headers: {
3522
- [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION
3523
- }
3524
- }
3173
+ }
3174
+ function normalizeRegionHeader(region) {
3175
+ if (!region) {
3176
+ return void 0;
3177
+ }
3178
+ region = region.replaceAll("-", "_").toUpperCase();
3179
+ if (VALID_REGIONS.includes(region)) {
3180
+ return region;
3181
+ }
3182
+ console.warn(
3183
+ `[Upstash Workflow] Invalid UPSTASH-REGION header value: "${region}". Expected one of: ${VALID_REGIONS.join(
3184
+ ", "
3185
+ )}.`
3186
+ );
3187
+ return void 0;
3188
+ }
3189
+
3190
+ // src/serve/multi-region/handlers.ts
3191
+ var getHandlersForRequest = (qstashHandlers, regionHeader, isFirstInvocation) => {
3192
+ if (qstashHandlers.mode === "single-region") {
3193
+ return qstashHandlers.handlers;
3194
+ }
3195
+ let targetRegion;
3196
+ if (isFirstInvocation) {
3197
+ targetRegion = qstashHandlers.defaultRegion;
3198
+ } else {
3199
+ const normalizedRegion = regionHeader ? normalizeRegionHeader(regionHeader) : void 0;
3200
+ targetRegion = normalizedRegion ?? qstashHandlers.defaultRegion;
3201
+ }
3202
+ const handler = qstashHandlers.handlers[targetRegion];
3203
+ if (!handler) {
3204
+ console.warn(
3205
+ `[Upstash Workflow] No handler found for region "${targetRegion}". Falling back to default region.`
3206
+ );
3207
+ return qstashHandlers.handlers[qstashHandlers.defaultRegion];
3208
+ }
3209
+ return handler;
3210
+ };
3211
+ var createRegionalHandler = (environment, receiverConfig, region, clientOptions) => {
3212
+ const clientEnv = readClientEnvironmentVariables(environment, region);
3213
+ const client = new import_qstash10.Client({
3214
+ ...clientOptions,
3215
+ baseUrl: clientEnv.QSTASH_URL,
3216
+ token: clientEnv.QSTASH_TOKEN
3217
+ });
3218
+ const receiver = getReceiver(environment, receiverConfig, region);
3219
+ return { client, receiver };
3220
+ };
3221
+ var shouldUseMultiRegionMode = (environment, qstashClientOption) => {
3222
+ const hasRegionEnv = Boolean(getRegionFromEnvironment(environment));
3223
+ if (hasRegionEnv && (!qstashClientOption || !("http" in qstashClientOption))) {
3224
+ return {
3225
+ isMultiRegion: true,
3226
+ defaultRegion: getRegionFromEnvironment(environment),
3227
+ clientOptions: qstashClientOption
3228
+ };
3229
+ } else {
3230
+ return { isMultiRegion: false };
3231
+ }
3232
+ };
3233
+ var getQStashHandlers = ({
3234
+ environment,
3235
+ qstashClientOption,
3236
+ receiverConfig
3237
+ }) => {
3238
+ const multiRegion = shouldUseMultiRegionMode(environment, qstashClientOption);
3239
+ if (multiRegion.isMultiRegion) {
3240
+ const regions = ["US_EAST_1", "EU_CENTRAL_1"];
3241
+ const handlers = {};
3242
+ for (const region of regions) {
3243
+ try {
3244
+ handlers[region] = createRegionalHandler(
3245
+ environment,
3246
+ receiverConfig,
3247
+ region,
3248
+ multiRegion.clientOptions
3525
3249
  );
3250
+ } catch (error) {
3251
+ console.warn(`[Upstash Workflow] Failed to create handler for region ${region}:`, error);
3526
3252
  }
3527
- return new Response(JSON.stringify({ workflowRunId }), {
3528
- status: 200,
3529
- headers: {
3530
- [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION
3531
- }
3253
+ }
3254
+ return {
3255
+ mode: "multi-region",
3256
+ handlers,
3257
+ defaultRegion: multiRegion.defaultRegion
3258
+ };
3259
+ } else {
3260
+ return {
3261
+ mode: "single-region",
3262
+ handlers: {
3263
+ client: qstashClientOption && "http" in qstashClientOption ? qstashClientOption : new import_qstash10.Client({
3264
+ ...qstashClientOption,
3265
+ baseUrl: environment.QSTASH_URL,
3266
+ token: environment.QSTASH_TOKEN
3267
+ }),
3268
+ receiver: getReceiver(environment, receiverConfig)
3269
+ }
3270
+ };
3271
+ }
3272
+ };
3273
+ var getReceiver = (environment, receiverConfig, region) => {
3274
+ if (typeof receiverConfig === "string") {
3275
+ if (receiverConfig === "set-to-undefined") {
3276
+ return void 0;
3277
+ }
3278
+ const receiverEnv = readReceiverEnvironmentVariables(environment, region);
3279
+ return receiverEnv.QSTASH_CURRENT_SIGNING_KEY && receiverEnv.QSTASH_NEXT_SIGNING_KEY ? new import_qstash10.Receiver({
3280
+ currentSigningKey: receiverEnv.QSTASH_CURRENT_SIGNING_KEY,
3281
+ nextSigningKey: receiverEnv.QSTASH_NEXT_SIGNING_KEY
3282
+ }) : void 0;
3283
+ } else {
3284
+ return receiverConfig;
3285
+ }
3286
+ };
3287
+ var getQStashHandlerOptions = (...params) => {
3288
+ const handlers = getQStashHandlers(...params);
3289
+ return {
3290
+ qstashHandlers: handlers,
3291
+ defaultReceiver: handlers.mode === "single-region" ? handlers.handlers.receiver : handlers.handlers[handlers.defaultRegion].receiver,
3292
+ defaultClient: handlers.mode === "single-region" ? handlers.handlers.client : handlers.handlers[handlers.defaultRegion].client
3293
+ };
3294
+ };
3295
+
3296
+ // src/middleware/middleware.ts
3297
+ var WorkflowMiddleware = class {
3298
+ name;
3299
+ initCallbacks;
3300
+ /**
3301
+ * Callback functions
3302
+ *
3303
+ * Initially set to undefined, will be populated after init is called
3304
+ */
3305
+ middlewareCallbacks = void 0;
3306
+ constructor(parameters) {
3307
+ this.name = parameters.name;
3308
+ if ("init" in parameters) {
3309
+ this.initCallbacks = parameters.init;
3310
+ } else {
3311
+ this.middlewareCallbacks = parameters.callbacks;
3312
+ }
3313
+ }
3314
+ async ensureInit() {
3315
+ if (!this.middlewareCallbacks) {
3316
+ if (!this.initCallbacks) {
3317
+ throw new WorkflowError(`Middleware "${this.name}" has no callbacks or init defined.`);
3318
+ }
3319
+ this.middlewareCallbacks = await this.initCallbacks();
3320
+ }
3321
+ }
3322
+ /**
3323
+ * Gets a callback function by name.
3324
+ *
3325
+ * @param callback name of the callback to retrieve
3326
+ */
3327
+ getCallback(callback) {
3328
+ return this.middlewareCallbacks?.[callback];
3329
+ }
3330
+ };
3331
+
3332
+ // src/middleware/logging.ts
3333
+ var loggingMiddleware = new WorkflowMiddleware({
3334
+ name: "logging",
3335
+ callbacks: {
3336
+ afterExecution(params) {
3337
+ const { context, ...rest } = params;
3338
+ console.log(" [Upstash Workflow]: Step executed:", {
3339
+ workflowRunId: context.workflowRunId,
3340
+ ...rest
3341
+ });
3342
+ },
3343
+ beforeExecution(params) {
3344
+ const { context, ...rest } = params;
3345
+ console.log(" [Upstash Workflow]: Step execution started:", {
3346
+ workflowRunId: context.workflowRunId,
3347
+ ...rest
3348
+ });
3349
+ },
3350
+ runStarted(params) {
3351
+ const { context, ...rest } = params;
3352
+ console.log(" [Upstash Workflow]: Workflow run started:", {
3353
+ workflowRunId: context.workflowRunId,
3354
+ ...rest
3355
+ });
3356
+ },
3357
+ runCompleted(params) {
3358
+ const { context, ...rest } = params;
3359
+ console.log(" [Upstash Workflow]: Workflow run completed:", {
3360
+ workflowRunId: context.workflowRunId,
3361
+ ...rest
3532
3362
  });
3533
3363
  },
3364
+ onError: onErrorWithConsole,
3365
+ onWarning: onWarningWithConsole,
3366
+ onInfo: onInfoWithConsole
3367
+ }
3368
+ });
3369
+
3370
+ // src/serve/options.ts
3371
+ var createResponseData = (workflowRunId, detailedFinishCondition) => {
3372
+ const baseHeaders = {
3373
+ [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION,
3374
+ "Upstash-workflow-sdk": VERSION
3375
+ };
3376
+ if (detailedFinishCondition?.condition === "auth-fail") {
3377
+ return {
3378
+ text: JSON.stringify({
3379
+ message: AUTH_FAIL_MESSAGE,
3380
+ workflowRunId
3381
+ }),
3382
+ status: 400,
3383
+ headers: baseHeaders
3384
+ };
3385
+ } else if (detailedFinishCondition?.condition === "non-retryable-error") {
3386
+ return {
3387
+ text: JSON.stringify(formatWorkflowError(detailedFinishCondition.result)),
3388
+ status: 489,
3389
+ headers: {
3390
+ ...baseHeaders,
3391
+ "Upstash-NonRetryable-Error": "true"
3392
+ }
3393
+ };
3394
+ } else if (detailedFinishCondition?.condition === "retry-after-error") {
3395
+ return {
3396
+ text: JSON.stringify(formatWorkflowError(detailedFinishCondition.result)),
3397
+ status: 429,
3398
+ headers: {
3399
+ ...baseHeaders,
3400
+ "Retry-After": detailedFinishCondition.result.retryAfter.toString()
3401
+ }
3402
+ };
3403
+ } else if (detailedFinishCondition?.condition === "failure-callback-executed") {
3404
+ return {
3405
+ text: JSON.stringify({ result: detailedFinishCondition.result ?? void 0 }),
3406
+ status: 200,
3407
+ headers: baseHeaders
3408
+ };
3409
+ } else if (detailedFinishCondition?.condition === "failure-callback-undefined") {
3410
+ return {
3411
+ text: JSON.stringify({
3412
+ workflowRunId,
3413
+ finishCondition: detailedFinishCondition.condition
3414
+ }),
3415
+ status: 200,
3416
+ headers: {
3417
+ ...baseHeaders,
3418
+ "Upstash-Workflow-Failure-Callback-Notfound": "true"
3419
+ }
3420
+ };
3421
+ }
3422
+ return {
3423
+ text: JSON.stringify({
3424
+ workflowRunId,
3425
+ finishCondition: detailedFinishCondition.condition
3426
+ }),
3427
+ status: 200,
3428
+ headers: baseHeaders
3429
+ };
3430
+ };
3431
+ var processOptions = (options, internalOptions) => {
3432
+ const environment = options?.env ?? (typeof process === "undefined" ? {} : process.env);
3433
+ const {
3434
+ qstashHandlers,
3435
+ defaultClient: qstashClient,
3436
+ defaultReceiver: receiver
3437
+ } = getQStashHandlerOptions({
3438
+ environment,
3439
+ qstashClientOption: options?.qstashClient,
3440
+ receiverConfig: options && "receiver" in options ? options.receiver ? options.receiver : "set-to-undefined" : "not-set"
3441
+ });
3442
+ return {
3443
+ qstashClient,
3534
3444
  initialPayloadParser: (initialRequest) => {
3535
3445
  if (!initialRequest) {
3536
3446
  return void 0;
@@ -3545,35 +3455,38 @@ var processOptions = (options) => {
3545
3455
  throw error;
3546
3456
  }
3547
3457
  },
3548
- receiver: receiverEnvironmentVariablesSet ? new import_qstash10.Receiver({
3549
- currentSigningKey: environment.QSTASH_CURRENT_SIGNING_KEY,
3550
- nextSigningKey: environment.QSTASH_NEXT_SIGNING_KEY
3551
- }) : void 0,
3458
+ receiver,
3552
3459
  baseUrl: environment.UPSTASH_WORKFLOW_URL,
3553
3460
  env: environment,
3554
- retries: DEFAULT_RETRIES,
3555
- useJSONContent: false,
3556
3461
  disableTelemetry: false,
3557
- onError: console.error,
3558
- ...options
3462
+ ...options,
3463
+ // merge middlewares
3464
+ middlewares: [options?.middlewares ?? [], options?.verbose ? [loggingMiddleware] : []].flat(),
3465
+ internal: {
3466
+ generateResponse: internalOptions?.generateResponse ?? ((responseData) => {
3467
+ return new Response(responseData.text, {
3468
+ status: responseData.status,
3469
+ headers: responseData.headers
3470
+ });
3471
+ }),
3472
+ useJSONContent: internalOptions?.useJSONContent ?? false,
3473
+ qstashHandlers
3474
+ }
3559
3475
  };
3560
3476
  };
3561
- var determineUrls = async (request, url, baseUrl, failureFunction, failureUrl, debug) => {
3477
+ var determineUrls = async (request, url, baseUrl, dispatchDebug) => {
3562
3478
  const initialWorkflowUrl = url ?? request.url;
3563
3479
  const workflowUrl = baseUrl ? initialWorkflowUrl.replace(/^(https?:\/\/[^/]+)(\/.*)?$/, (_, matchedBaseUrl, path) => {
3564
3480
  return baseUrl + (path || "");
3565
3481
  }) : initialWorkflowUrl;
3566
3482
  if (workflowUrl !== initialWorkflowUrl) {
3567
- await debug?.log("WARN", "ENDPOINT_START", {
3568
- warning: `Upstash Workflow: replacing the base of the url with "${baseUrl}" and using it as workflow endpoint.`,
3569
- originalURL: initialWorkflowUrl,
3570
- updatedURL: workflowUrl
3483
+ await dispatchDebug("onInfo", {
3484
+ info: `The workflow URL's base URL has been replaced with the provided baseUrl. Original URL: ${initialWorkflowUrl}, New URL: ${workflowUrl}`
3571
3485
  });
3572
3486
  }
3573
- const workflowFailureUrl = failureFunction ? workflowUrl : failureUrl;
3574
3487
  if (workflowUrl.includes("localhost")) {
3575
- await debug?.log("WARN", "ENDPOINT_START", {
3576
- message: `Workflow URL contains localhost. This can happen in local development, but shouldn't happen in production unless you have a route which contains localhost. Received: ${workflowUrl}`
3488
+ await dispatchDebug("onInfo", {
3489
+ info: `Workflow URL contains localhost. This can happen in local development, but shouldn't happen in production unless you have a route which contains localhost. Received: ${workflowUrl}`
3577
3490
  });
3578
3491
  }
3579
3492
  if (!(workflowUrl.startsWith("http://") || workflowUrl.startsWith("https://"))) {
@@ -3582,205 +3495,224 @@ var determineUrls = async (request, url, baseUrl, failureFunction, failureUrl, d
3582
3495
  );
3583
3496
  }
3584
3497
  return {
3585
- workflowUrl,
3586
- workflowFailureUrl
3498
+ workflowUrl
3587
3499
  };
3588
3500
  };
3589
3501
  var AUTH_FAIL_MESSAGE = `Failed to authenticate Workflow request. If this is unexpected, see the caveat https://upstash.com/docs/workflow/basics/caveats#avoid-non-deterministic-code-outside-context-run`;
3590
3502
 
3591
3503
  // src/serve/index.ts
3592
- var serveBase = (routeFunction, telemetry, options) => {
3504
+ var serveBase = (routeFunction, telemetry, options, internalOptions) => {
3593
3505
  const {
3594
- qstashClient,
3595
- onStepFinish,
3596
3506
  initialPayloadParser,
3597
3507
  url,
3598
- verbose,
3599
- receiver,
3600
- failureUrl,
3601
3508
  failureFunction,
3602
3509
  baseUrl,
3603
3510
  env,
3604
- retries,
3605
- retryDelay,
3606
- useJSONContent,
3607
3511
  disableTelemetry,
3608
- flowControl,
3609
- onError
3610
- } = processOptions(options);
3512
+ middlewares,
3513
+ internal
3514
+ } = processOptions(options, internalOptions);
3611
3515
  telemetry = disableTelemetry ? void 0 : telemetry;
3612
- const debug = WorkflowLogger.getLogger(verbose);
3613
- const handler = async (request) => {
3614
- await debug?.log("INFO", "ENDPOINT_START");
3615
- const { workflowUrl, workflowFailureUrl } = await determineUrls(
3516
+ const { generateResponse: responseGenerator, useJSONContent } = internal;
3517
+ const handler = async (request, middlewareManager) => {
3518
+ await middlewareManager.dispatchDebug("onInfo", {
3519
+ info: `Received request for workflow execution.`
3520
+ });
3521
+ const { workflowUrl } = await determineUrls(
3616
3522
  request,
3617
3523
  url,
3618
3524
  baseUrl,
3619
- failureFunction,
3620
- failureUrl,
3621
- debug
3525
+ middlewareManager.dispatchDebug.bind(middlewareManager)
3526
+ );
3527
+ const { isFirstInvocation, workflowRunId, unknownSdk } = validateRequest(request);
3528
+ const regionHeader = request.headers.get("upstash-region");
3529
+ const { client: regionalClient, receiver: regionalReceiver } = getHandlersForRequest(
3530
+ internal.qstashHandlers,
3531
+ regionHeader,
3532
+ isFirstInvocation
3622
3533
  );
3623
3534
  const requestPayload = await getPayload(request) ?? "";
3624
- await verifyRequest(requestPayload, request.headers.get("upstash-signature"), receiver);
3625
- const { isFirstInvocation, workflowRunId } = validateRequest(request);
3626
- debug?.setWorkflowRunId(workflowRunId);
3627
- const { rawInitialPayload, steps, isLastDuplicate, workflowRunEnded } = await parseRequest(
3535
+ await verifyRequest(requestPayload, request.headers.get("upstash-signature"), regionalReceiver);
3536
+ middlewareManager.assignWorkflowRunId(workflowRunId);
3537
+ await middlewareManager.dispatchDebug("onInfo", {
3538
+ info: `Run id identified. isFirstInvocation: ${isFirstInvocation}, unknownSdk: ${unknownSdk}`
3539
+ });
3540
+ const { rawInitialPayload, steps, isLastDuplicate, workflowRunEnded } = await parseRequest({
3628
3541
  requestPayload,
3629
3542
  isFirstInvocation,
3543
+ unknownSdk,
3630
3544
  workflowRunId,
3631
- qstashClient.http,
3632
- request.headers.get("upstash-message-id"),
3633
- debug
3634
- );
3545
+ requester: regionalClient.http,
3546
+ messageId: request.headers.get("upstash-message-id"),
3547
+ dispatchDebug: middlewareManager.dispatchDebug.bind(middlewareManager)
3548
+ });
3635
3549
  if (workflowRunEnded) {
3636
- return onStepFinish(workflowRunId, "workflow-already-ended", {
3637
- condition: "workflow-already-ended"
3638
- });
3550
+ return responseGenerator(
3551
+ createResponseData(workflowRunId, {
3552
+ condition: "workflow-already-ended"
3553
+ })
3554
+ );
3639
3555
  }
3640
3556
  if (isLastDuplicate) {
3641
- return onStepFinish(workflowRunId, "duplicate-step", {
3642
- condition: "duplicate-step"
3643
- });
3557
+ return responseGenerator(
3558
+ createResponseData(workflowRunId, {
3559
+ condition: "duplicate-step"
3560
+ })
3561
+ );
3644
3562
  }
3645
- const failureCheck = await handleFailure(
3563
+ const failureCheck = await handleFailure({
3646
3564
  request,
3647
3565
  requestPayload,
3648
- qstashClient,
3566
+ qstashClient: regionalClient,
3649
3567
  initialPayloadParser,
3650
3568
  routeFunction,
3651
3569
  failureFunction,
3652
3570
  env,
3653
- retries,
3654
- retryDelay,
3655
- flowControl,
3656
- debug
3657
- );
3571
+ dispatchDebug: middlewareManager.dispatchDebug.bind(middlewareManager)
3572
+ });
3658
3573
  if (failureCheck.isErr()) {
3659
3574
  throw failureCheck.error;
3660
- } else if (failureCheck.value.result === "is-failure-callback") {
3661
- await debug?.log("WARN", "RESPONSE_DEFAULT", "failureFunction executed");
3662
- return onStepFinish(workflowRunId, "failure-callback", {
3663
- condition: "failure-callback",
3664
- result: failureCheck.value.response
3575
+ } else if (failureCheck.value.result === "failure-function-executed") {
3576
+ await middlewareManager.dispatchDebug("onInfo", {
3577
+ info: `Handled failure callback.`
3665
3578
  });
3579
+ return responseGenerator(
3580
+ createResponseData(workflowRunId, {
3581
+ condition: "failure-callback-executed",
3582
+ result: failureCheck.value.response
3583
+ })
3584
+ );
3585
+ } else if (failureCheck.value.result === "failure-function-undefined") {
3586
+ await middlewareManager.dispatchDebug("onInfo", {
3587
+ info: `Failure callback invoked but no failure function defined.`
3588
+ });
3589
+ return responseGenerator(
3590
+ createResponseData(workflowRunId, {
3591
+ condition: "failure-callback-undefined"
3592
+ })
3593
+ );
3666
3594
  }
3667
3595
  const invokeCount = Number(request.headers.get(WORKFLOW_INVOKE_COUNT_HEADER) ?? "0");
3668
3596
  const label = request.headers.get(WORKFLOW_LABEL_HEADER) ?? void 0;
3669
3597
  const workflowContext = new WorkflowContext({
3670
- qstashClient,
3598
+ qstashClient: regionalClient,
3671
3599
  workflowRunId,
3672
3600
  initialPayload: initialPayloadParser(rawInitialPayload),
3673
3601
  headers: recreateUserHeaders(request.headers),
3674
3602
  steps,
3675
3603
  url: workflowUrl,
3676
- failureUrl: workflowFailureUrl,
3677
- debug,
3678
3604
  env,
3679
- retries,
3680
- retryDelay,
3681
3605
  telemetry,
3682
3606
  invokeCount,
3683
- flowControl,
3684
- label
3607
+ label,
3608
+ middlewareManager
3685
3609
  });
3686
3610
  const authCheck = await DisabledWorkflowContext.tryAuthentication(
3687
3611
  routeFunction,
3688
3612
  workflowContext
3689
3613
  );
3690
3614
  if (authCheck.isErr()) {
3691
- await debug?.log("ERROR", "ERROR", { error: authCheck.error.message });
3692
3615
  throw authCheck.error;
3693
3616
  } else if (authCheck.value === "run-ended") {
3694
- await debug?.log("ERROR", "ERROR", { error: AUTH_FAIL_MESSAGE });
3695
- return onStepFinish(
3696
- isFirstInvocation ? "no-workflow-id" : workflowContext.workflowRunId,
3697
- "auth-fail",
3698
- { condition: "auth-fail" }
3617
+ await middlewareManager.dispatchDebug("onError", {
3618
+ error: new Error(AUTH_FAIL_MESSAGE)
3619
+ });
3620
+ return responseGenerator(
3621
+ createResponseData(isFirstInvocation ? "no-workflow-id" : workflowContext.workflowRunId, {
3622
+ condition: "auth-fail"
3623
+ })
3699
3624
  );
3700
3625
  }
3701
3626
  const callReturnCheck = await handleThirdPartyCallResult({
3702
3627
  request,
3703
3628
  requestPayload: rawInitialPayload,
3704
- client: qstashClient,
3629
+ client: regionalClient,
3705
3630
  workflowUrl,
3706
- failureUrl: workflowFailureUrl,
3707
- retries,
3708
- retryDelay,
3709
- flowControl,
3710
3631
  telemetry,
3711
- debug
3632
+ middlewareManager
3712
3633
  });
3713
3634
  if (callReturnCheck.isErr()) {
3714
- await debug?.log("ERROR", "SUBMIT_THIRD_PARTY_RESULT", {
3715
- error: callReturnCheck.error.message
3716
- });
3717
3635
  throw callReturnCheck.error;
3718
3636
  } else if (callReturnCheck.value === "continue-workflow") {
3719
3637
  const result = isFirstInvocation ? await triggerFirstInvocation({
3720
3638
  workflowContext,
3721
3639
  useJSONContent,
3722
3640
  telemetry,
3723
- debug,
3724
- invokeCount
3641
+ invokeCount,
3642
+ middlewareManager,
3643
+ unknownSdk
3725
3644
  }) : await triggerRouteFunction({
3726
- onStep: async () => routeFunction(workflowContext),
3645
+ onStep: async () => {
3646
+ if (steps.length === 1) {
3647
+ await middlewareManager.dispatchLifecycle("runStarted", {});
3648
+ }
3649
+ return await routeFunction(workflowContext);
3650
+ },
3727
3651
  onCleanup: async (result2) => {
3728
- await triggerWorkflowDelete(workflowContext, result2, debug);
3652
+ await middlewareManager.dispatchLifecycle("runCompleted", {
3653
+ result: result2
3654
+ });
3655
+ await triggerWorkflowDelete(
3656
+ workflowContext,
3657
+ result2,
3658
+ false,
3659
+ middlewareManager.dispatchDebug.bind(middlewareManager)
3660
+ );
3729
3661
  },
3730
3662
  onCancel: async () => {
3731
3663
  await makeCancelRequest(workflowContext.qstashClient.http, workflowRunId);
3732
3664
  },
3733
- debug
3665
+ middlewareManager
3734
3666
  });
3735
3667
  if (result.isOk() && isInstanceOf(result.value, WorkflowNonRetryableError)) {
3736
- return onStepFinish(workflowRunId, result.value, {
3737
- condition: "non-retryable-error",
3738
- result: result.value
3739
- });
3668
+ return responseGenerator(
3669
+ createResponseData(workflowRunId, {
3670
+ condition: "non-retryable-error",
3671
+ result: result.value
3672
+ })
3673
+ );
3740
3674
  }
3741
3675
  if (result.isOk() && isInstanceOf(result.value, WorkflowRetryAfterError)) {
3742
- return onStepFinish(workflowRunId, result.value, {
3743
- condition: "retry-after-error",
3744
- result: result.value
3745
- });
3676
+ return responseGenerator(
3677
+ createResponseData(workflowRunId, {
3678
+ condition: "retry-after-error",
3679
+ result: result.value
3680
+ })
3681
+ );
3746
3682
  }
3747
3683
  if (result.isErr()) {
3748
- await debug?.log("ERROR", "ERROR", { error: result.error.message });
3749
3684
  throw result.error;
3750
3685
  }
3751
- await debug?.log("INFO", "RESPONSE_WORKFLOW");
3752
- return onStepFinish(workflowContext.workflowRunId, "success", {
3753
- condition: "success"
3686
+ await middlewareManager.dispatchDebug("onInfo", {
3687
+ info: `Workflow endpoint execution completed successfully.`
3754
3688
  });
3689
+ return responseGenerator(
3690
+ createResponseData(workflowContext.workflowRunId, {
3691
+ condition: "success"
3692
+ })
3693
+ );
3755
3694
  } else if (callReturnCheck.value === "workflow-ended") {
3756
- return onStepFinish(workflowContext.workflowRunId, "workflow-already-ended", {
3757
- condition: "workflow-already-ended"
3758
- });
3695
+ return responseGenerator(
3696
+ createResponseData(workflowContext.workflowRunId, {
3697
+ condition: "workflow-already-ended"
3698
+ })
3699
+ );
3759
3700
  }
3760
- await debug?.log("INFO", "RESPONSE_DEFAULT");
3761
- return onStepFinish("no-workflow-id", "fromCallback", {
3762
- condition: "fromCallback"
3763
- });
3701
+ return responseGenerator(
3702
+ createResponseData(workflowContext.workflowRunId, {
3703
+ condition: "fromCallback"
3704
+ })
3705
+ );
3764
3706
  };
3765
3707
  const safeHandler = async (request) => {
3708
+ const middlewareManager = new MiddlewareManager(middlewares);
3766
3709
  try {
3767
- return await handler(request);
3710
+ return await handler(request, middlewareManager);
3768
3711
  } catch (error) {
3769
3712
  const formattedError = formatWorkflowError(error);
3770
- try {
3771
- onError?.(error);
3772
- } catch (onErrorError) {
3773
- const formattedOnErrorError = formatWorkflowError(onErrorError);
3774
- const errorMessage = `Error while running onError callback: '${formattedOnErrorError.message}'.
3775
- Original error: '${formattedError.message}'`;
3776
- console.error(errorMessage);
3777
- return new Response(JSON.stringify({ error: errorMessage }), {
3778
- status: 500,
3779
- headers: {
3780
- [WORKFLOW_PROTOCOL_VERSION_HEADER]: WORKFLOW_PROTOCOL_VERSION
3781
- }
3782
- });
3783
- }
3713
+ await middlewareManager.dispatchDebug("onError", {
3714
+ error: isInstanceOf(error, Error) ? error : new Error(formattedError.message)
3715
+ });
3784
3716
  return new Response(JSON.stringify(formattedError), {
3785
3717
  status: 500,
3786
3718
  headers: {
@@ -3806,7 +3738,11 @@ var serve = (routeFunction, options) => {
3806
3738
  status: 405
3807
3739
  });
3808
3740
  }
3809
- const { handler: serveHandler } = serveBase(routeFunction, telemetry, options);
3741
+ const { handler: serveHandler } = serveBase(
3742
+ routeFunction,
3743
+ telemetry,
3744
+ options
3745
+ );
3810
3746
  return await serveHandler(event.request);
3811
3747
  };
3812
3748
  return { POST: handler };