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