@nlxai/core 1.2.3 → 1.2.4-alpha.10

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/lib/index.esm.js CHANGED
@@ -4,7 +4,7 @@ import ReconnectingWebSocket from 'reconnecting-websocket';
4
4
  import { v4 } from 'uuid';
5
5
 
6
6
  var name = "@nlxai/core";
7
- var version$1 = "1.2.3";
7
+ var version$1 = "1.2.4-alpha.10";
8
8
  var description = "Low-level SDK for building NLX experiences";
9
9
  var type = "module";
10
10
  var main = "lib/index.cjs";
@@ -32,14 +32,21 @@ var scripts = {
32
32
  var author = "Peter Szerzo <peter@nlx.ai>";
33
33
  var license = "MIT";
34
34
  var devDependencies = {
35
+ "@rollup/plugin-commonjs": "^25.0.7",
36
+ "@rollup/plugin-json": "^6.0.1",
37
+ "@rollup/plugin-node-resolve": "^15.2.3",
38
+ "@rollup/plugin-replace": "^5.0.5",
39
+ "@rollup/plugin-terser": "^0.4.4",
40
+ "@rollup/plugin-typescript": "^11.1.5",
35
41
  "@types/isomorphic-fetch": "^0.0.39",
36
42
  "@types/node": "^24.10.1",
37
43
  "@types/ramda": "0.31.1",
38
44
  "@types/uuid": "^9.0.7",
39
- "concat-md": "^0.5.1",
40
45
  "eslint-config-nlx": "*",
41
46
  prettier: "^3.1.0",
47
+ rollup: "^4.3.0",
42
48
  "rollup-config-nlx": "*",
49
+ "rollup-plugin-node-polyfills": "^0.2.1",
43
50
  typedoc: "^0.28.14",
44
51
  "typedoc-plugin-markdown": "^4.9.0",
45
52
  typescript: "^5.5.4"
@@ -53,7 +60,7 @@ var dependencies = {
53
60
  var publishConfig = {
54
61
  access: "public"
55
62
  };
56
- var gitHead = "3902161e95745dd4a9cfb68bb469755a62b421f5";
63
+ var gitHead = "9a1bfacce5cc21ca2d64e79c86805dab37568de3";
57
64
  var packageJson = {
58
65
  name: name,
59
66
  version: version$1,
@@ -79,6 +86,24 @@ var packageJson = {
79
86
  const version = packageJson.version;
80
87
  // use a custom Console to indicate we really want to log to the console and it's not incidental. `console.log` causes an eslint error
81
88
  const Console = console;
89
+ /**
90
+ * The protocol used to communicate with the application
91
+ */
92
+ var Protocol;
93
+ (function (Protocol) {
94
+ /**
95
+ * Regular encrypted HTTPS, without support for post-escalation message handling, interim messages and other streaming features.
96
+ */
97
+ Protocol["Https"] = "https";
98
+ /**
99
+ * Encrypted HTTPS with streaming enabled. This is the default setting and supports interim messages. Does not support post-escalation message handling.
100
+ */
101
+ Protocol["HttpsWithStreaming"] = "httpsWithStreaming";
102
+ /**
103
+ * Websocket, with support for post-escalation message handling.
104
+ */
105
+ Protocol["Websocket"] = "websocket";
106
+ })(Protocol || (Protocol = {}));
82
107
  /**
83
108
  * Response type
84
109
  */
@@ -125,42 +150,181 @@ const safeJsonParse = (val) => {
125
150
  return null;
126
151
  }
127
152
  };
128
- const getBaseDomain = (url) => url.match(/(bots\.dev\.studio\.nlx\.ai|bots\.studio\.nlx\.ai|apps\.nlx\.ai|dev\.apps\.nlx\.ai)/g)?.[0] ?? "apps.nlx.ai";
153
+ const getHost = (url) => url.match(/(bots\.dev\.studio\.nlx\.ai|bots\.studio\.nlx\.ai|apps\.nlx\.ai|dev\.apps\.nlx\.ai)/g)?.[0] ?? "apps.nlx.ai";
129
154
  /**
130
- * When a HTTP URL is provided, deduce the websocket URL. Otherwise, return the argument.
131
- * @param applicationUrl - the websocket URL
132
- * @returns httpUrl - the HTTP URL
155
+ * Parse configuration into structured connection information, taking into account `applicationUrl`-based configs.
156
+ * @param config - client configuration.
157
+ * @returns connection - connection information, or `null` if the configuration is invalid.
133
158
  */
134
- const normalizeToWebsocket = (applicationUrl) => {
159
+ const parseConnection = (config) => {
160
+ const applicationUrl = config.applicationUrl ?? "";
161
+ const apiKey = config.apiKey ?? config.headers?.["nlx-api-key"] ?? "";
162
+ const protocol = config.protocol ??
163
+ /**
164
+ * Backwards-compatibility: if a websocket URL was specified, assume it's websocket. Otherwise, look at the legacy experimental streamsetting
165
+ * and only assume non-streaming if it's explicitly set to false.
166
+ */
167
+ (isWebsocketUrl(applicationUrl)
168
+ ? Protocol.Websocket
169
+ : config.experimental?.streamHttp === false
170
+ ? Protocol.Https
171
+ : Protocol.HttpsWithStreaming);
172
+ if (config.host != null &&
173
+ config.channelKey != null &&
174
+ config.deploymentKey != null) {
175
+ return {
176
+ protocol,
177
+ apiKey,
178
+ host: config.host,
179
+ channelKey: config.channelKey,
180
+ deploymentKey: config.deploymentKey,
181
+ };
182
+ }
183
+ // `applicationUrl`-based definition: websocket case
135
184
  if (isWebsocketUrl(applicationUrl)) {
136
- return applicationUrl;
185
+ const host = getHost(applicationUrl);
186
+ const url = new URL(applicationUrl);
187
+ const params = new URLSearchParams(url.search);
188
+ const channelKey = params.get("channelKey");
189
+ const deploymentKey = params.get("deploymentKey");
190
+ if (channelKey != null && deploymentKey != null) {
191
+ return { protocol, channelKey, deploymentKey, host, apiKey };
192
+ }
193
+ return null;
137
194
  }
138
- const base = getBaseDomain(applicationUrl);
139
- const url = new URL(applicationUrl);
140
- const pathChunks = url.pathname.split("/");
141
- const deploymentKey = pathChunks[2];
142
- const channelKey = pathChunks[3];
143
- return `wss://us-east-1-ws.${base}?deploymentKey=${deploymentKey}&channelKey=${channelKey}`;
144
- };
145
- /**
146
- * When a websocket URL is provided, deduce the HTTP URL. Otherwise, return the argument.
147
- * @param applicationUrl - the websocket URL
148
- * @returns httpUrl - the HTTP URL
149
- */
150
- const normalizeToHttp = (applicationUrl) => {
151
- if (!isWebsocketUrl(applicationUrl)) {
152
- return applicationUrl;
195
+ // `applicationUrl`-based definition: http case
196
+ const host = getHost(applicationUrl);
197
+ const parseResult = new URLPattern({
198
+ pathname: "/c/:deploymentKey/:channelKey",
199
+ }).exec(applicationUrl);
200
+ if (parseResult?.pathname.groups.channelKey != null &&
201
+ parseResult?.pathname.groups.deploymentKey != null) {
202
+ return {
203
+ protocol,
204
+ channelKey: parseResult.pathname.groups.channelKey,
205
+ deploymentKey: parseResult.pathname.groups.deploymentKey,
206
+ host,
207
+ apiKey,
208
+ };
153
209
  }
154
- const base = getBaseDomain(applicationUrl);
155
- const url = new URL(applicationUrl);
156
- const params = new URLSearchParams(url.search);
157
- const channelKey = params.get("channelKey");
158
- const deploymentKey = params.get("deploymentKey");
159
- return `https://${base}/c/${deploymentKey}/${channelKey}`;
210
+ return null;
211
+ };
212
+ const toWebsocketUrl = (connection) => {
213
+ return `wss://us-east-1-ws.${connection.host}?deploymentKey=${connection.deploymentKey}&channelKey=${connection.channelKey}&apiKey=${connection.apiKey}`;
214
+ };
215
+ const toHttpUrl = (connection) => {
216
+ return `https://${connection.host}/c/${connection.deploymentKey}/${connection.channelKey}`;
160
217
  };
161
218
  const isWebsocketUrl = (url) => {
162
219
  return url.indexOf("wss://") === 0;
163
220
  };
221
+ const fetchUserMessage = async ({ fullApplicationUrl, apiKey, headers, body, stream, eventListeners, }) => {
222
+ const streamRequest = async (body) => {
223
+ const response = await fetch(fullApplicationUrl, {
224
+ method: "POST",
225
+ headers: {
226
+ ...headers,
227
+ "nlx-api-key": apiKey,
228
+ "Content-Type": "application/json",
229
+ // Legacy header
230
+ "nlx-sdk-version": packageJson.version,
231
+ "nlx-core-version": packageJson.version,
232
+ },
233
+ body: JSON.stringify({ ...body, stream: true }),
234
+ });
235
+ if (!response.ok || response.body == null)
236
+ throw new Error(`HTTP Error: ${response.status}`);
237
+ const reader = response.body.getReader();
238
+ const decoder = new TextDecoder();
239
+ let buffer = "";
240
+ const messages = [];
241
+ let finalResponse = {};
242
+ while (true) {
243
+ const { done, value } = await reader.read();
244
+ if (done)
245
+ break;
246
+ buffer += decoder.decode(value, { stream: true });
247
+ while (true) {
248
+ const openBrace = buffer.indexOf("{");
249
+ if (openBrace === -1)
250
+ break;
251
+ let foundObject = false;
252
+ for (let i = 0; i < buffer.length; i++) {
253
+ if (buffer[i] === "}") {
254
+ const candidate = buffer.substring(openBrace, i + 1);
255
+ try {
256
+ const json = JSON.parse(candidate);
257
+ if (json.type === "interim") {
258
+ const text = json.text;
259
+ if (typeof text === "string") {
260
+ eventListeners.interimMessage.forEach((listener) => {
261
+ listener(text);
262
+ });
263
+ }
264
+ }
265
+ else if (json.type === "message") {
266
+ messages.push({
267
+ text: json.text,
268
+ choices: json.choices ?? [],
269
+ messageId: json.messageId,
270
+ metadata: json.metadata,
271
+ });
272
+ }
273
+ else if (json.type === "final_response") {
274
+ finalResponse = json.data;
275
+ }
276
+ buffer = buffer.substring(i + 1);
277
+ foundObject = true;
278
+ break;
279
+ }
280
+ catch (e) {
281
+ /* keep scanning */
282
+ }
283
+ }
284
+ }
285
+ if (!foundObject)
286
+ break;
287
+ }
288
+ }
289
+ eventListeners.interimMessage.forEach((listener) => {
290
+ listener(undefined);
291
+ });
292
+ return {
293
+ ...finalResponse,
294
+ messages: [
295
+ ...messages,
296
+ ...(finalResponse.messages ?? []).map((json) => ({
297
+ text: json.text,
298
+ choices: json.choices ?? [],
299
+ messageId: json.messageId,
300
+ metadata: json.metadata,
301
+ })),
302
+ ],
303
+ };
304
+ };
305
+ if (stream) {
306
+ return await streamRequest(body);
307
+ }
308
+ else {
309
+ const response = await fetch(fullApplicationUrl, {
310
+ method: "POST",
311
+ headers: {
312
+ ...(headers ?? {}),
313
+ "nlx-api-key": apiKey,
314
+ Accept: "application/json",
315
+ "Content-Type": "application/json",
316
+ // Legacy header
317
+ "nlx-sdk-version": packageJson.version,
318
+ "nlx-core-version": packageJson.version,
319
+ },
320
+ body: JSON.stringify(body),
321
+ });
322
+ if (!response.ok || response.body == null)
323
+ throw new Error(`HTTP Error: ${response.status}`);
324
+ const json = await response.json();
325
+ return json;
326
+ }
327
+ };
164
328
  /**
165
329
  * Call this to create a conversation handler.
166
330
  * @param configuration - The necessary configuration to create the conversation.
@@ -186,12 +350,21 @@ function createConversation(configuration) {
186
350
  let voicePlusSocket;
187
351
  let voicePlusSocketMessageQueue = [];
188
352
  let voicePlusSocketMessageQueueCheckInterval = null;
189
- const applicationUrl = configuration.applicationUrl ?? "";
353
+ const connection = parseConnection(configuration);
354
+ const websocketApplicationUrl = connection != null
355
+ ? toWebsocketUrl(connection)
356
+ : configuration.applicationUrl ?? "";
357
+ const httpApplicationUrl = connection != null
358
+ ? toHttpUrl(connection)
359
+ : configuration.applicationUrl ?? "";
190
360
  // Check if the application URL has a language code appended to it
191
- if (/[-|_][a-z]{2,}[-|_][A-Z]{2,}$/.test(applicationUrl)) {
361
+ if (/[-|_][a-z]{2,}[-|_][A-Z]{2,}$/.test(httpApplicationUrl)) {
192
362
  Console.warn("Since v1.0.0, the language code is no longer added at the end of the application URL. Please remove the modifier (e.g. '-en-US') from the URL, and specify it in the `languageCode` parameter instead.");
193
363
  }
194
- const eventListeners = { voicePlusCommand: [] };
364
+ const eventListeners = {
365
+ voicePlusCommand: [],
366
+ interimMessage: [],
367
+ };
195
368
  const initialConversationId = configuration.conversationId ?? v4();
196
369
  let state = {
197
370
  responses: configuration.responses ?? [],
@@ -199,7 +372,7 @@ function createConversation(configuration) {
199
372
  userId: configuration.userId,
200
373
  conversationId: initialConversationId,
201
374
  };
202
- const fullApplicationHttpUrl = () => `${normalizeToHttp(applicationUrl)}${configuration.experimental?.completeApplicationUrl === true
375
+ const fullApplicationHttpUrl = () => `${httpApplicationUrl}${configuration.experimental?.completeApplicationUrl === true
203
376
  ? ""
204
377
  : `-${state.languageCode}`}`;
205
378
  const setState = (change,
@@ -293,7 +466,7 @@ function createConversation(configuration) {
293
466
  channelType: configuration.experimental?.channelType,
294
467
  environment: configuration.environment,
295
468
  };
296
- if (isWebsocketUrl(applicationUrl)) {
469
+ if (connection?.protocol === Protocol.Websocket) {
297
470
  if (socket?.readyState === 1) {
298
471
  socket.send(JSON.stringify(bodyWithContext));
299
472
  }
@@ -303,20 +476,14 @@ function createConversation(configuration) {
303
476
  }
304
477
  else {
305
478
  try {
306
- const res = await fetch(fullApplicationHttpUrl(), {
307
- method: "POST",
308
- headers: {
309
- ...(configuration.headers ?? {}),
310
- Accept: "application/json",
311
- "Content-Type": "application/json",
312
- "nlx-sdk-version": packageJson.version,
313
- },
314
- body: JSON.stringify(bodyWithContext),
479
+ const json = await fetchUserMessage({
480
+ fullApplicationUrl: fullApplicationHttpUrl(),
481
+ apiKey: connection?.apiKey ?? "",
482
+ headers: configuration.headers ?? {},
483
+ stream: connection?.protocol === Protocol.HttpsWithStreaming,
484
+ eventListeners,
485
+ body: bodyWithContext,
315
486
  });
316
- if (res.status >= 400) {
317
- throw new Error(`Responded with ${res.status}`);
318
- }
319
- const json = await res.json();
320
487
  messageResponseHandler(json);
321
488
  }
322
489
  catch (err) {
@@ -342,7 +509,7 @@ function createConversation(configuration) {
342
509
  const setupWebsocket = () => {
343
510
  // If the socket is already set up, tear it down first
344
511
  teardownWebsocket();
345
- const url = new URL(applicationUrl);
512
+ const url = new URL(websocketApplicationUrl);
346
513
  if (configuration.experimental?.completeApplicationUrl !== true) {
347
514
  url.searchParams.set("languageCode", state.languageCode);
348
515
  url.searchParams.set("channelKey", `${url.searchParams.get("channelKey") ?? ""}-${state.languageCode}`);
@@ -357,21 +524,6 @@ function createConversation(configuration) {
357
524
  messageResponseHandler(safeJsonParse(e.data));
358
525
  }
359
526
  };
360
- url.searchParams.set("voice-plus", "true");
361
- voicePlusSocket = new ReconnectingWebSocket(url.href);
362
- voicePlusSocketMessageQueueCheckInterval = setInterval(() => {
363
- checkVoicePlusSocketQueue();
364
- }, 500);
365
- voicePlusSocket.onmessage = (e) => {
366
- if (typeof e?.data === "string") {
367
- const command = safeJsonParse(e.data);
368
- if (command != null) {
369
- eventListeners.voicePlusCommand.forEach((listener) => {
370
- listener(command);
371
- });
372
- }
373
- }
374
- };
375
527
  };
376
528
  const setupCommandWebsocket = () => {
377
529
  // If the socket is already set up, tear it down first
@@ -379,16 +531,15 @@ function createConversation(configuration) {
379
531
  if (configuration.bidirectional !== true) {
380
532
  return;
381
533
  }
382
- const url = new URL(normalizeToWebsocket(applicationUrl));
534
+ const url = new URL(websocketApplicationUrl);
383
535
  if (configuration.experimental?.completeApplicationUrl !== true) {
384
536
  url.searchParams.set("languageCode", state.languageCode);
385
537
  url.searchParams.set("channelKey", `${url.searchParams.get("channelKey") ?? ""}-${state.languageCode}`);
386
538
  }
387
539
  url.searchParams.set("conversationId", state.conversationId);
388
540
  url.searchParams.set("type", "voice-plus");
389
- const apiKey = configuration.headers["nlx-api-key"];
390
- if (!isWebsocketUrl(applicationUrl) && apiKey != null) {
391
- url.searchParams.set("apiKey", apiKey);
541
+ if (connection?.apiKey != null) {
542
+ url.searchParams.set("apiKey", connection.apiKey);
392
543
  }
393
544
  voicePlusSocket = new ReconnectingWebSocket(url.href);
394
545
  voicePlusSocketMessageQueueCheckInterval = setInterval(() => {
@@ -425,7 +576,7 @@ function createConversation(configuration) {
425
576
  voicePlusSocket = undefined;
426
577
  }
427
578
  };
428
- if (isWebsocketUrl(applicationUrl)) {
579
+ if (connection?.protocol === Protocol.Websocket) {
429
580
  setupWebsocket();
430
581
  }
431
582
  setupCommandWebsocket();
@@ -531,10 +682,13 @@ function createConversation(configuration) {
531
682
  method: "POST",
532
683
  headers: {
533
684
  ...(configuration.headers ?? {}),
685
+ "nlx-api-key": connection?.apiKey ?? "",
534
686
  Accept: "application/json",
535
687
  "Content-Type": "application/json",
536
688
  "nlx-conversation-id": state.conversationId,
689
+ // Legacy header
537
690
  "nlx-sdk-version": packageJson.version,
691
+ "nlx-core-version": packageJson.version,
538
692
  },
539
693
  body: JSON.stringify({
540
694
  languageCode: state.languageCode,
@@ -589,6 +743,23 @@ function createConversation(configuration) {
589
743
  sendFlow(welcomeIntent, context);
590
744
  },
591
745
  sendChoice,
746
+ submitFeedback: async (feedbackUrl, feedback) => {
747
+ const res = await fetch(feedbackUrl, {
748
+ method: "POST",
749
+ headers: {
750
+ "Content-Type": "application/json",
751
+ },
752
+ body: JSON.stringify({
753
+ languageCode: state.languageCode,
754
+ conversationId: state.conversationId,
755
+ userId: state.userId,
756
+ ...feedback,
757
+ }),
758
+ });
759
+ if (res.status >= 400) {
760
+ throw new Error(`Responded with ${res.status}`);
761
+ }
762
+ },
592
763
  currentConversationId: () => {
593
764
  return state.conversationId;
594
765
  },
@@ -597,7 +768,7 @@ function createConversation(configuration) {
597
768
  Console.warn("Attempted to set language code to the one already active.");
598
769
  return;
599
770
  }
600
- if (isWebsocketUrl(applicationUrl)) {
771
+ if (connection?.protocol === Protocol.Websocket) {
601
772
  setupWebsocket();
602
773
  }
603
774
  setupCommandWebsocket();
@@ -607,15 +778,17 @@ function createConversation(configuration) {
607
778
  return state.languageCode;
608
779
  },
609
780
  getVoiceCredentials: async (context, options) => {
610
- const url = normalizeToHttp(applicationUrl);
611
- const res = await fetch(`${url}-${state.languageCode}/requestToken`, {
781
+ const res = await fetch(`${httpApplicationUrl}-${state.languageCode}/requestToken`, {
612
782
  method: "POST",
613
783
  headers: {
614
784
  ...(configuration.headers ?? {}),
785
+ "nlx-api-key": connection?.apiKey ?? "",
615
786
  Accept: "application/json",
616
787
  "Content-Type": "application/json",
617
788
  "nlx-conversation-id": state.conversationId,
789
+ // Legacy header
618
790
  "nlx-sdk-version": packageJson.version,
791
+ "nlx-core-version": packageJson.version,
619
792
  },
620
793
  body: JSON.stringify({
621
794
  languageCode: state.languageCode,
@@ -645,14 +818,14 @@ function createConversation(configuration) {
645
818
  conversationId: v4(),
646
819
  responses: options?.clearResponses === true ? [] : state.responses,
647
820
  });
648
- if (isWebsocketUrl(applicationUrl)) {
821
+ if (connection?.protocol === Protocol.Websocket) {
649
822
  setupWebsocket();
650
823
  }
651
824
  setupCommandWebsocket();
652
825
  },
653
826
  destroy: () => {
654
827
  subscribers = [];
655
- if (isWebsocketUrl(applicationUrl)) {
828
+ if (connection?.protocol === Protocol.Websocket) {
656
829
  teardownWebsocket();
657
830
  }
658
831
  teardownCommandWebsocket();
@@ -677,8 +850,7 @@ function createConversation(configuration) {
677
850
  * @returns Whether the configuration is valid?
678
851
  */
679
852
  const isConfigValid = (configuration) => {
680
- const applicationUrl = configuration.applicationUrl ?? "";
681
- return applicationUrl.length > 0;
853
+ return parseConnection(configuration) != null;
682
854
  };
683
855
  /**
684
856
  * Helper method to decide when a new {@link Config} requires creating a new {@link ConversationHandler} or whether the old `Config`'s
@@ -771,5 +943,71 @@ function promisify(fn, convo, timeout = 10000) {
771
943
  });
772
944
  };
773
945
  }
946
+ /**
947
+ * Use this function when using **Voice+ scripts** to advance the conversation to the step specified.
948
+ *
949
+ * This functionality is orthogonal from other usage of the core SDK, as it may be used either using standard SDK communication channels or it can be used to provide a Voice+ script experience with for instance a telephony based channel.
950
+ * @example
951
+ * ```typescript
952
+ * import { sendVoicePlusStep } from "@nlxai/core";
953
+ *
954
+ * await sendVoicePlusStep({
955
+ * // hard-coded params
956
+ * apiKey: "REPLACE_WITH_API_KEY",
957
+ * workspaceId: "REPLACE_WITH_WORKSPACE_ID",
958
+ * scriptId: "REPLACE_WITH_SCRIPT_ID",
959
+ * step: "REPLACE_WITH_STEP_ID",
960
+ * // dynamic params
961
+ * conversationId: "REPLACE_WITH_CONVERSATION_ID",
962
+ * languageCode: "en-US",
963
+ * });
964
+ * ```
965
+ * @param configuration - Configuration for sending the step. Many of the values can be found on the deployment modal of the Voice+ script.
966
+ */
967
+ const sendVoicePlusStep = async ({ apiKey, workspaceId, conversationId, scriptId, languageCode, step, context, debug = false, dev = false, }) => {
968
+ if (scriptId == null) {
969
+ throw new Error("Voice+ scriptId is not defined.");
970
+ }
971
+ if (typeof conversationId !== "string" || conversationId.length === 0) {
972
+ throw new Error("Voice+ conversationId is not defined.");
973
+ }
974
+ const [stepId, stepTriggerDescription] = typeof step === "string"
975
+ ? [step, undefined]
976
+ : [step.stepId, step.stepTriggerDescription];
977
+ if (!stepIdRegex.test(stepId)) {
978
+ throw new Error("Invalid stepId. It should be formatted as a UUID.");
979
+ }
980
+ const payload = {
981
+ stepId,
982
+ context,
983
+ conversationId,
984
+ journeyId: scriptId,
985
+ languageCode,
986
+ stepTriggerDescription,
987
+ };
988
+ try {
989
+ await fetch(`https://${dev ? "dev." : ""}mm.nlx.ai/v1/track`, {
990
+ method: "POST",
991
+ headers: {
992
+ "x-api-key": apiKey,
993
+ "x-nlx-id": workspaceId,
994
+ "Content-Type": "application/json",
995
+ "nlx-sdk-version": packageJson.version,
996
+ "nlx-core-version": packageJson.version,
997
+ },
998
+ body: JSON.stringify(payload),
999
+ });
1000
+ if (debug) {
1001
+ Console.info(`✓ step: ${stepId}`, payload);
1002
+ }
1003
+ }
1004
+ catch (err) {
1005
+ if (debug) {
1006
+ Console.error(`× step: ${stepId}`, err, payload);
1007
+ }
1008
+ throw err;
1009
+ }
1010
+ };
1011
+ const stepIdRegex = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
774
1012
 
775
- export { ResponseType, createConversation, getCurrentExpirationTimestamp, isConfigValid, promisify, shouldReinitialize, version };
1013
+ export { Protocol, ResponseType, createConversation, getCurrentExpirationTimestamp, isConfigValid, promisify, sendVoicePlusStep, shouldReinitialize, version };