botium-core 1.15.5 → 1.15.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "botium-core",
3
- "version": "1.15.5",
3
+ "version": "1.15.8",
4
4
  "description": "The Selenium for Chatbots",
5
5
  "main": "index.js",
6
6
  "module": "dist/botium-es.js",
@@ -104,6 +104,22 @@ module.exports = {
104
104
  SIMPLEREST_INBOUND_ORDER_UNSETTLED_EVENTS_JSONPATH: 'SIMPLEREST_INBOUND_ORDER_UNSETTLED_EVENTS_JSONPATH',
105
105
  SIMPLEREST_INBOUND_DEBOUNCE_TIMEOUT: 'SIMPLEREST_INBOUND_DEBOUNCE_TIMEOUT',
106
106
  SIMPLEREST_COOKIE_REPLICATION: 'SIMPLEREST_COOKIE_REPLICATION',
107
+ SIMPLEREST_NLP_INTENT_JSONPATH: 'SIMPLEREST_NLP_INTENT_JSONPATH',
108
+ SIMPLEREST_NLP_CONFIDENCE_JSONPATH: 'SIMPLEREST_NLP_CONFIDENCE_JSONPATH',
109
+ SIMPLEREST_NLP_CONFIDENCE_DIVIDEBY100: 'SIMPLEREST_NLP_CONFIDENCE_DIVIDEBY100',
110
+ SIMPLEREST_NLP_FALLBACK_INTENTS: 'SIMPLEREST_NLP_FALLBACK_INTENTS',
111
+ SIMPLEREST_NLP_LIST_JSONPATH: 'SIMPLEREST_NLP_LIST_JSONPATH',
112
+ SIMPLEREST_NLP_LIST_INTENT_JSONPATH: 'SIMPLEREST_NLP_LIST_INTENT_JSONPATH',
113
+ SIMPLEREST_NLP_LIST_CONFIDENCE_JSONPATH: 'SIMPLEREST_NLP_LIST_CONFIDENCE_JSONPATH',
114
+ /**
115
+ * Single response can contain a list of messages. This capability defines how to merge the list into a single message.
116
+ * It can be 'OFF' (default), 'MERGE_TEXT'
117
+ * @type {string}
118
+ * @default 'OFF'
119
+ * @description Merge message list into single message
120
+ */
121
+ SIMPLEREST_MESSAGE_LIST_MERGE: 'SIMPLEREST_MESSAGE_LIST_MERGE',
122
+
107
123
  // Script Compiler
108
124
  SCRIPTING_TXT_EOL: 'SCRIPTING_TXT_EOL',
109
125
  // ROW_PER_MESSAGE or QUESTION_ANSWER
@@ -369,7 +369,7 @@ module.exports = class SimpleRestContainer {
369
369
  return
370
370
  }
371
371
 
372
- const result = []
372
+ let result = []
373
373
  if (isFromUser) {
374
374
  const _extractFrom = (root, jsonPaths, acceptFn = null) => {
375
375
  const result = []
@@ -402,6 +402,11 @@ module.exports = class SimpleRestContainer {
402
402
  jsonPathRoots.push(body)
403
403
  }
404
404
 
405
+ if (jsonPathRoots.length === 0 && !this.caps[Capabilities.SIMPLEREST_IGNORE_EMPTY]) {
406
+ debug(`found empty body, and processed because of SIMPLEREST_IGNORE_EMPTY capability: ${util.inspect(body)}`)
407
+ result.push({ messageText: '', sourceData: body })
408
+ }
409
+
405
410
  for (const jsonPathRoot of jsonPathRoots) {
406
411
  const _retrieveMedia = (jsonPathMediaRoot, jsonPathsMedia) => {
407
412
  const retrievedMedia = []
@@ -496,6 +501,76 @@ module.exports = class SimpleRestContainer {
496
501
  })
497
502
  debug(`found response cards: ${util.inspect(cards)}`)
498
503
 
504
+ let nlp = { intent: {} }
505
+ const _normalizeIntent = (intent) => {
506
+ if ('name' in intent) {
507
+ if (!intent.name) {
508
+ intent.name = 'None'
509
+ }
510
+ const fallbackIntents = this.caps[Capabilities.SIMPLEREST_NLP_FALLBACK_INTENTS]
511
+ if (fallbackIntents) {
512
+ intent.incomprehension = this.caps[Capabilities.SIMPLEREST_NLP_FALLBACK_INTENTS].includes(intent.name) ? true : undefined
513
+ } else {
514
+ intent.incomprehension = intent.name.toLowerCase() === 'none' ? true : undefined
515
+ }
516
+ }
517
+
518
+ if (!_.isNil(intent.confidence)) {
519
+ if (typeof intent.confidence === 'string') {
520
+ intent.confidence = Number(intent.confidence)
521
+ }
522
+ if (_.isNil(this.caps[Capabilities.SIMPLEREST_NLP_CONFIDENCE_DIVIDEBY100]) ? intent.confidence > 1 : this.caps[Capabilities.SIMPLEREST_NLP_CONFIDENCE_DIVIDEBY100]) {
523
+ intent.confidence /= 100
524
+ }
525
+ }
526
+
527
+ return intent
528
+ }
529
+ const jsonPathIntent = this.caps[Capabilities.SIMPLEREST_NLP_INTENT_JSONPATH]
530
+ if (jsonPathIntent) {
531
+ const name = jp.query(jsonPathRoot, jsonPathIntent)
532
+ nlp.intent.name = name?.length ? name[0] : null
533
+ }
534
+ const jsonPathConfidence = this.caps[Capabilities.SIMPLEREST_NLP_CONFIDENCE_JSONPATH]
535
+ if (jsonPathConfidence) {
536
+ const confidence = jp.query(jsonPathRoot, jsonPathConfidence)
537
+ nlp.intent.confidence = confidence?.length ? confidence[0] : null
538
+ }
539
+ _normalizeIntent(nlp.intent)
540
+ const jsonPathList = this.caps[Capabilities.SIMPLEREST_NLP_LIST_JSONPATH]
541
+ const jsonPathListIntent = this.caps[Capabilities.SIMPLEREST_NLP_LIST_INTENT_JSONPATH]
542
+ const jsonPathListConfidence = this.caps[Capabilities.SIMPLEREST_NLP_LIST_CONFIDENCE_JSONPATH]
543
+ if (jsonPathList) {
544
+ const list = jp.query(jsonPathRoot, jsonPathList)
545
+ if (list?.length) {
546
+ const intentList = list[0].map(e => {
547
+ const name = jp.query(e, jsonPathListIntent)
548
+ const res = { name: name?.length ? name[0] : null }
549
+ if (jsonPathListConfidence) {
550
+ const confidence = jp.query(e, jsonPathListConfidence)
551
+ res.confidence = confidence?.length ? confidence[0] : null
552
+ }
553
+ _normalizeIntent(res)
554
+ return res
555
+ })
556
+ if (intentList.length > 0 && !nlp.intent.name) {
557
+ nlp.intent.name = intentList[0].name
558
+ nlp.intent.confidence = intentList[0].confidence
559
+ }
560
+
561
+ if (nlp.intent.name === intentList[0].name) {
562
+ intentList.shift()
563
+ }
564
+
565
+ nlp.intent.intents = intentList
566
+ }
567
+ }
568
+ if (Object.keys(nlp.intent).length > 0) {
569
+ debug(`found response nlp: ${util.inspect(nlp)}`)
570
+ } else {
571
+ nlp = null
572
+ }
573
+
499
574
  let hasMessageText = false
500
575
  const jsonPathsTexts = getAllCapValues(Capabilities.SIMPLEREST_RESPONSE_JSONPATH, this.caps)
501
576
  for (const jsonPath of jsonPathsTexts) {
@@ -509,19 +584,24 @@ module.exports = class SimpleRestContainer {
509
584
  if (!messageText) continue
510
585
 
511
586
  hasMessageText = true
512
- const botMsg = { sourceData: body, messageText, media, buttons, cards }
587
+ const botMsg = { sourceData: body, messageText, media, buttons, nlp }
513
588
  await executeHook(this.caps, this.responseHook, Object.assign({ botMsg, botMsgRoot: jsonPathRoot, messageTextIndex }, this.view))
514
589
  result.push(botMsg)
515
590
  }
516
591
  }
517
592
 
518
593
  if (!hasMessageText) {
519
- const botMsg = { messageText: '', sourceData: body, media, buttons, cards }
594
+ const botMsg = { messageText: '', sourceData: body, media, buttons, cards, nlp }
520
595
  const beforeHookKeys = Object.keys(botMsg)
521
596
  await executeHook(this.caps, this.responseHook, Object.assign({ botMsg, botMsgRoot: jsonPathRoot }, this.view))
522
597
  const afterHookKeys = Object.keys(botMsg)
523
- if (beforeHookKeys.length !== afterHookKeys.length || !!(botMsg.messageText && botMsg.messageText.length > 0) || botMsg.media.length > 0 || botMsg.buttons.length > 0 || botMsg.cards.length > 0 || !this.caps[Capabilities.SIMPLEREST_IGNORE_EMPTY]) {
598
+ if (beforeHookKeys.length !== afterHookKeys.length || !!(botMsg.messageText && botMsg.messageText.length > 0) || botMsg.media.length > 0 || botMsg.buttons.length > 0 || botMsg.cards.length > 0 || botMsg.nlp) {
599
+ result.push(botMsg)
600
+ } else if (!this.caps[Capabilities.SIMPLEREST_IGNORE_EMPTY]) {
601
+ debug(`found empty message, and processed because of SIMPLEREST_IGNORE_EMPTY capability: ${util.inspect(botMsg)}`)
524
602
  result.push(botMsg)
603
+ } else {
604
+ debug(`found empty message, and ignored because of SIMPLEREST_IGNORE_EMPTY capability: ${util.inspect(botMsg)}`)
525
605
  }
526
606
  }
527
607
  }
@@ -534,6 +614,23 @@ module.exports = class SimpleRestContainer {
534
614
 
535
615
  setTimeout(() => this._doRequest({ messageText: '' }, true, true), 0)
536
616
  }
617
+
618
+ if (this.caps[Capabilities.SIMPLEREST_MESSAGE_LIST_MERGE] === 'MERGE_TEXT') {
619
+ const isTextMsg = (msg) => msg.messageText && (!msg.media || msg.media.length === 0) && (!msg.buttons || msg.buttons.length === 0) && (!msg.cards || msg.cards.length === 0)
620
+
621
+ result = result.reduce((acc, currentMsg) => {
622
+ if (acc.length > 0) {
623
+ const last = acc[acc.length - 1]
624
+ if (isTextMsg(last)) {
625
+ currentMsg.messageText = [last.messageText, currentMsg.messageText].filter(t => t).join('\n')
626
+ acc[acc.length - 1] = currentMsg
627
+ return acc
628
+ }
629
+ }
630
+ acc.push(currentMsg)
631
+ return acc
632
+ }, [])
633
+ }
537
634
  return result
538
635
  }
539
636
 
@@ -550,8 +647,7 @@ module.exports = class SimpleRestContainer {
550
647
  fetch(requestOptions.uri, requestOptions).then(async (bodyRaw) => {
551
648
  let body
552
649
  try {
553
- const contentType = bodyRaw.headers.get('content-type')
554
- if (contentType && contentType.includes('application/json')) {
650
+ if (bodyRaw?.headers?.get('content-type') && bodyRaw.headers.get('content-type').includes('application/json')) {
555
651
  try {
556
652
  body = await bodyRaw.json()
557
653
  } catch (err) {
@@ -810,6 +810,7 @@ describe('connectors.simplerest', function () {
810
810
 
811
811
  await container.Clean()
812
812
  })
813
+
813
814
  it('should parse jsonmessage from sourcedata if it enabled', async function () {
814
815
  const msgJSON = {
815
816
  sourceData: {
@@ -837,6 +838,7 @@ describe('connectors.simplerest', function () {
837
838
 
838
839
  await container.Clean()
839
840
  })
841
+
840
842
  it('should fall back to text message if using jsonmessage from sourcedata is enabled, but sourcedata is not set', async function () {
841
843
  const myCaps = Object.assign({}, myCapsPost)
842
844
  myCaps[Capabilities.SIMPLEREST_BODY_FROM_JSON] = true
@@ -855,6 +857,7 @@ describe('connectors.simplerest', function () {
855
857
 
856
858
  await container.Clean()
857
859
  })
860
+
858
861
  it('should handle somehow if jsonmessage from sourcedata is enabled, and booth json, and text are set in the user message (impossible state)', async function () {
859
862
  // this is not a valid state. In case there is a json as message in a convo.txt, text parser parses it into the sourceData field, and keeps messageText empty
860
863
  const msgTextAndJSONIllegal = {
@@ -903,6 +906,81 @@ describe('connectors.simplerest', function () {
903
906
  await container.Clean()
904
907
  })
905
908
 
909
+ it('should merge text responses', async function () {
910
+ const myCaps = Object.assign({}, myCapsGet, {
911
+ [Capabilities.SIMPLEREST_BODY_JSONPATH]: '$.responses[*]',
912
+ [Capabilities.SIMPLEREST_RESPONSE_JSONPATH]: '$.text',
913
+ [Capabilities.SIMPLEREST_BUTTONS_JSONPATH]: '$.buttons[*]',
914
+ [Capabilities.SIMPLEREST_BUTTONS_TEXT_SUBJSONPATH]: '$.text',
915
+ [Capabilities.SIMPLEREST_MEDIA_JSONPATH]: '$.media[*]',
916
+ [Capabilities.SIMPLEREST_MESSAGE_LIST_MERGE]: 'MERGE_TEXT'
917
+ })
918
+ const driver = new BotDriver(myCaps)
919
+ const container = await driver.Build()
920
+ await container.Start()
921
+ const msgs = await container.pluginInstance._processBodyAsyncImpl({
922
+ responses: [
923
+ { text: 'text 1' },
924
+ { text: 'text 2' },
925
+ { text: 'text 3', buttons: [{ text: 'button' }] },
926
+ { text: 'text 4' },
927
+ { media: ['some.jpg'] },
928
+ { text: 'text 5' },
929
+ { text: 'text 6' }
930
+ ]
931
+ }, {}, true)
932
+
933
+ assert.exists(msgs)
934
+ assert.equal(msgs.length, 3)
935
+ assert.equal(msgs[0].messageText, 'text 1\ntext 2\ntext 3')
936
+ assert.exists(msgs[0].buttons)
937
+ assert.equal(msgs[0].buttons.length, 1)
938
+ assert.equal(msgs[0].media.length, 0)
939
+ assert.equal(msgs[1].messageText, 'text 4')
940
+ assert.equal(msgs[1].buttons.length, 0)
941
+ assert.equal(msgs[1].media.length, 1)
942
+ assert.equal(msgs[2].messageText, 'text 5\ntext 6')
943
+ assert.equal(msgs[2].buttons.length, 0)
944
+ assert.equal(msgs[2].media.length, 0)
945
+ await container.Clean()
946
+ })
947
+
948
+ it('should not merge text responses', async function () {
949
+ const myCaps = Object.assign({}, myCapsGet, {
950
+ [Capabilities.SIMPLEREST_BODY_JSONPATH]: '$.responses[*]',
951
+ [Capabilities.SIMPLEREST_RESPONSE_JSONPATH]: '$.text',
952
+ [Capabilities.SIMPLEREST_BUTTONS_JSONPATH]: '$.buttons[*]',
953
+ [Capabilities.SIMPLEREST_BUTTONS_TEXT_SUBJSONPATH]: '$.text',
954
+ [Capabilities.SIMPLEREST_MEDIA_JSONPATH]: '$.media[*]'
955
+ })
956
+ const driver = new BotDriver(myCaps)
957
+ const container = await driver.Build()
958
+ await container.Start()
959
+ const msgs = await container.pluginInstance._processBodyAsyncImpl({
960
+ responses: [
961
+ { text: 'text 1' },
962
+ { text: 'text 2' },
963
+ { text: 'text 3', buttons: [{ text: 'button' }] },
964
+ { text: 'text 4' },
965
+ { media: ['some.jpg'] },
966
+ { text: 'text 5' },
967
+ { text: 'text 6' }
968
+ ]
969
+ }, {}, true)
970
+
971
+ assert.exists(msgs)
972
+ assert.equal(msgs.length, 7)
973
+ assert.equal(msgs[0].messageText, 'text 1')
974
+ assert.equal(msgs[1].messageText, 'text 2')
975
+ assert.equal(msgs[2].messageText, 'text 3')
976
+ assert.equal(msgs[3].messageText, 'text 4')
977
+ assert.equal(msgs[4].messageText, '')
978
+ assert.equal(msgs[5].messageText, 'text 5')
979
+ assert.equal(msgs[6].messageText, 'text 6')
980
+
981
+ await container.Clean()
982
+ })
983
+
906
984
  it('should ignore empty response', async function () {
907
985
  const myCaps = Object.assign({}, myCapsGet, {
908
986
  [Capabilities.SIMPLEREST_BODY_JSONPATH]: '$.responses[*]',
@@ -1190,6 +1268,68 @@ describe('connectors.simplerest', function () {
1190
1268
 
1191
1269
  await container.Clean()
1192
1270
  })
1271
+
1272
+ it('should extract intent and confidence', async function () {
1273
+ const myCaps = Object.assign({}, myCapsGet, {
1274
+ [Capabilities.SIMPLEREST_NLP_INTENT_JSONPATH]: '$.intent.intentname',
1275
+ [Capabilities.SIMPLEREST_NLP_CONFIDENCE_JSONPATH]: '$.intent.intentconfidence'
1276
+ })
1277
+ const driver = new BotDriver(myCaps)
1278
+ const container = await driver.Build()
1279
+ assert.equal(container.pluginInstance.constructor.name, 'SimpleRestContainer')
1280
+
1281
+ await container.Start()
1282
+ const msgs = await container.pluginInstance._processBodyAsyncImpl({
1283
+ intent: {
1284
+ intentname: 'iname',
1285
+ intentconfidence: '50'
1286
+ }
1287
+ }, {}, true)
1288
+
1289
+ assert.equal(msgs?.length, 1)
1290
+ assert.exists(msgs[0].nlp)
1291
+ assert.exists(msgs[0].nlp.intent)
1292
+ assert.equal(msgs[0].nlp.intent.name, 'iname')
1293
+ assert.equal(msgs[0].nlp.intent.confidence, 0.5)
1294
+
1295
+ await container.Clean()
1296
+ })
1297
+
1298
+ it('should extract intent list', async function () {
1299
+ const myCaps = Object.assign({}, myCapsGet, {
1300
+ [Capabilities.SIMPLEREST_NLP_LIST_JSONPATH]: '$.intents',
1301
+ [Capabilities.SIMPLEREST_NLP_LIST_INTENT_JSONPATH]: '$.intentname',
1302
+ [Capabilities.SIMPLEREST_NLP_LIST_CONFIDENCE_JSONPATH]: '$.intentconfidence'
1303
+ })
1304
+ const driver = new BotDriver(myCaps)
1305
+ const container = await driver.Build()
1306
+ assert.equal(container.pluginInstance.constructor.name, 'SimpleRestContainer')
1307
+
1308
+ await container.Start()
1309
+ const msgs = await container.pluginInstance._processBodyAsyncImpl({
1310
+ intents: [
1311
+ {
1312
+ intentname: 'iname1',
1313
+ intentconfidence: '50'
1314
+ },
1315
+ {
1316
+ intentname: 'iname2',
1317
+ intentconfidence: 25
1318
+ }
1319
+ ]
1320
+ }, {}, true)
1321
+
1322
+ assert.equal(msgs?.length, 1)
1323
+ assert.exists(msgs[0].nlp)
1324
+ assert.exists(msgs[0].nlp.intent)
1325
+ assert.equal(msgs[0].nlp.intent.name, 'iname1')
1326
+ assert.equal(msgs[0].nlp.intent.confidence, 0.5)
1327
+ assert.equal(msgs[0].nlp.intent.intents.length, 1)
1328
+ assert.equal(msgs[0].nlp.intent.intents[0].name, 'iname2')
1329
+ assert.equal(msgs[0].nlp.intent.intents[0].confidence, 0.25)
1330
+
1331
+ await container.Clean()
1332
+ })
1193
1333
  })
1194
1334
 
1195
1335
  describe('parseCapabilities', function () {