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