botium-connector-voip 0.0.28 → 0.0.29
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/dist/botium-connector-voip-cjs.js +125 -34
- package/dist/botium-connector-voip-cjs.js.map +1 -1
- package/dist/botium-connector-voip-es.js +125 -34
- package/dist/botium-connector-voip-es.js.map +1 -1
- package/index.js +14 -0
- package/package.json +1 -1
- package/src/connector.js +110 -36
|
@@ -30,7 +30,7 @@ const _info = (event, data) => {
|
|
|
30
30
|
const parts = Object.entries({
|
|
31
31
|
event,
|
|
32
32
|
...data
|
|
33
|
-
}).filter(([, v]) => v != null && v !== '').map(([k, v]) => `${k}=${
|
|
33
|
+
}).filter(([, v]) => v != null && v !== '').map(([k, v]) => `${k}=${JSON.stringify(v)}`);
|
|
34
34
|
console.info(`[botium-connector-voip] ${parts.join(' ')}`);
|
|
35
35
|
};
|
|
36
36
|
|
|
@@ -51,6 +51,7 @@ const Capabilities = {
|
|
|
51
51
|
VOIP_STT_MESSAGE_HANDLING_TIMEOUT: 'VOIP_STT_MESSAGE_HANDLING_TIMEOUT',
|
|
52
52
|
VOIP_STT_MESSAGE_HANDLING_TIMEOUT_SUBSEQUENT: 'VOIP_STT_MESSAGE_HANDLING_TIMEOUT_SUBSEQUENT',
|
|
53
53
|
VOIP_JOIN_SILENCE_DURATION_BY_SUBSTRING: 'VOIP_JOIN_SILENCE_DURATION_BY_SUBSTRING',
|
|
54
|
+
VOIP_STT_DICTIONARY_REPLACEMENTS: 'VOIP_STT_DICTIONARY_REPLACEMENTS',
|
|
54
55
|
VOIP_STT_MESSAGE_HANDLING_DELIMITER: 'VOIP_STT_MESSAGE_HANDLING_DELIMITER',
|
|
55
56
|
VOIP_STT_MESSAGE_HANDLING_PUNCTUATION: 'VOIP_STT_MESSAGE_HANDLING_PUNCTUATION',
|
|
56
57
|
VOIP_TTS_URL: 'VOIP_TTS_URL',
|
|
@@ -90,7 +91,8 @@ const Capabilities = {
|
|
|
90
91
|
VOIP_STT_CONFIDENCE_THRESHOLD: 'VOIP_STT_CONFIDENCE_THRESHOLD',
|
|
91
92
|
VOIP_USE_GLOBAL_VOIP_WORKER: 'VOIP_USE_GLOBAL_VOIP_WORKER',
|
|
92
93
|
VOIP_USER_INPUT_PREFER_VOICE: 'VOIP_USER_INPUT_PREFER_VOICE',
|
|
93
|
-
VOIP_EMIT_SPECULATIVE_TEXT: 'VOIP_EMIT_SPECULATIVE_TEXT'
|
|
94
|
+
VOIP_EMIT_SPECULATIVE_TEXT: 'VOIP_EMIT_SPECULATIVE_TEXT',
|
|
95
|
+
VOIP_SDP_MEDIA_TYPE_TEXT_ENABLE: 'VOIP_SDP_MEDIA_TYPE_TEXT_ENABLE'
|
|
94
96
|
};
|
|
95
97
|
const Defaults = {
|
|
96
98
|
VOIP_STT_METHOD: 'POST',
|
|
@@ -113,7 +115,8 @@ const Defaults = {
|
|
|
113
115
|
VOIP_STT_CONFIDENCE_THRESHOLD: 0.5,
|
|
114
116
|
VOIP_USE_GLOBAL_VOIP_WORKER: false,
|
|
115
117
|
VOIP_SIP_PROTOCOL: 'TCP',
|
|
116
|
-
VOIP_USER_INPUT_PREFER_VOICE: true
|
|
118
|
+
VOIP_USER_INPUT_PREFER_VOICE: true,
|
|
119
|
+
VOIP_SDP_MEDIA_TYPE_TEXT_ENABLE: false
|
|
117
120
|
};
|
|
118
121
|
const TTS_HTTP_AGENT = new http__default["default"].Agent({
|
|
119
122
|
keepAlive: true
|
|
@@ -557,6 +560,7 @@ class BotiumConnectorVoip {
|
|
|
557
560
|
ICE_TURN_PASSWORD: this.caps[Capabilities.VOIP_ICE_TURN_PASSWORD],
|
|
558
561
|
ICE_TURN_PROTOCOL: this.caps[Capabilities.VOIP_ICE_TURN_PROTOCOL] || 'TCP',
|
|
559
562
|
MIN_SILENCE_DURATION: this.caps[Capabilities.VOIP_SILENCE_DURATION_TIMEOUT_ENABLE] ? this.caps[Capabilities.VOIP_SILENCE_DURATION_TIMEOUT] : null,
|
|
563
|
+
SDP_MEDIA_TYPE_TEXT_ENABLE: !!this.caps[Capabilities.VOIP_SDP_MEDIA_TYPE_TEXT_ENABLE],
|
|
560
564
|
STT_LEGACY: sttLegacy,
|
|
561
565
|
STT_CONFIG: {
|
|
562
566
|
stt_url: sttUrl,
|
|
@@ -846,14 +850,15 @@ class BotiumConnectorVoip {
|
|
|
846
850
|
this.sttPartialCount++;
|
|
847
851
|
const partialText = parsedData.data.message;
|
|
848
852
|
if (typeof partialText === 'string' && partialText.trim().length > 0) {
|
|
853
|
+
const replacementResult = this._applySttDictionaryReplacements(partialText);
|
|
849
854
|
this.lastPartialBotMsg = {
|
|
850
|
-
messageText:
|
|
851
|
-
sourceData: Object.assign({}, parsedData, {
|
|
855
|
+
messageText: replacementResult.text,
|
|
856
|
+
sourceData: Object.assign({}, this._decorateSourceDataWithSttDictionaryReplacements(parsedData, replacementResult), {
|
|
852
857
|
partialRecovery: true
|
|
853
858
|
})
|
|
854
859
|
};
|
|
855
860
|
}
|
|
856
|
-
const partialPreview = typeof partialText === 'string' ? partialText.trim()
|
|
861
|
+
const partialPreview = typeof partialText === 'string' ? partialText.trim() : '';
|
|
857
862
|
_info('stt_partial_received', {
|
|
858
863
|
sessionId: this.sessionId,
|
|
859
864
|
partialIndex: this.sttPartialCount,
|
|
@@ -935,11 +940,13 @@ class BotiumConnectorVoip {
|
|
|
935
940
|
const confidenceThreshold = this._getConfidenceScoreLogicHook(this.convoStep) && this._getConfidenceScoreLogicHook(this.convoStep).args[0] || this.caps[Capabilities.VOIP_STT_CONFIDENCE_THRESHOLD];
|
|
936
941
|
const successfulConfidenceScore = this._getConfidenceScore(parsedData) >= confidenceThreshold;
|
|
937
942
|
const msgText = parsedData.data.message || '';
|
|
943
|
+
const replacementResult = this._applySttDictionaryReplacements(msgText);
|
|
944
|
+
const normalizedMsgText = replacementResult.text;
|
|
938
945
|
const msgLen = typeof msgText === 'string' ? msgText.length : 0;
|
|
939
|
-
const msgPreview = typeof
|
|
946
|
+
const msgPreview = typeof normalizedMsgText === 'string' ? normalizedMsgText.trim() : '';
|
|
940
947
|
// A final supersedes the cached interim; clear to avoid duplicate tail emission.
|
|
941
948
|
this.lastPartialBotMsg = null;
|
|
942
|
-
debug$3(`Message: ${
|
|
949
|
+
debug$3(`Message: ${normalizedMsgText} / Confidence Score: ${this._getConfidenceScore(parsedData)} (Threshold: ${confidenceThreshold})`);
|
|
943
950
|
_info('stt_final', {
|
|
944
951
|
sessionId: this.sessionId,
|
|
945
952
|
message: msgPreview,
|
|
@@ -951,10 +958,10 @@ class BotiumConnectorVoip {
|
|
|
951
958
|
// ORIGINAL: always emit final message immediately (ignore JOIN hooks/rules).
|
|
952
959
|
if (this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING] === 'ORIGINAL') {
|
|
953
960
|
let botMsg = {
|
|
954
|
-
messageText:
|
|
961
|
+
messageText: normalizedMsgText
|
|
955
962
|
};
|
|
956
963
|
if (this.firstMsg) {
|
|
957
|
-
const sourceData = parsedData;
|
|
964
|
+
const sourceData = this._decorateSourceDataWithSttDictionaryReplacements(parsedData, replacementResult);
|
|
958
965
|
sourceData.silenceDuration = parsedData.data.start;
|
|
959
966
|
sourceData.voiceDuration = parsedData.data.end - parsedData.data.start;
|
|
960
967
|
botMsg = Object.assign({}, botMsg, {
|
|
@@ -962,7 +969,7 @@ class BotiumConnectorVoip {
|
|
|
962
969
|
});
|
|
963
970
|
this.firstMsg = false;
|
|
964
971
|
} else {
|
|
965
|
-
const sourceData = parsedData;
|
|
972
|
+
const sourceData = this._decorateSourceDataWithSttDictionaryReplacements(parsedData, replacementResult);
|
|
966
973
|
const start = lodash__default["default"].get(parsedData, 'data.start', null);
|
|
967
974
|
const prevEnd = lodash__default["default"].get(this.prevData, 'data.end', null);
|
|
968
975
|
sourceData.silenceDuration = lodash__default["default"].isFinite(start) && lodash__default["default"].isFinite(prevEnd) ? start - prevEnd : lodash__default["default"].isFinite(start) ? start : null;
|
|
@@ -980,10 +987,10 @@ class BotiumConnectorVoip {
|
|
|
980
987
|
}
|
|
981
988
|
if (this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING] === 'SPLIT' || this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING] === 'EXPAND') {
|
|
982
989
|
let botMsg = {
|
|
983
|
-
messageText:
|
|
990
|
+
messageText: normalizedMsgText
|
|
984
991
|
};
|
|
985
992
|
if (this.firstMsg) {
|
|
986
|
-
const sourceData = parsedData;
|
|
993
|
+
const sourceData = this._decorateSourceDataWithSttDictionaryReplacements(parsedData, replacementResult);
|
|
987
994
|
sourceData.silenceDuration = parsedData.data.start;
|
|
988
995
|
sourceData.voiceDuration = parsedData.data.end - parsedData.data.start;
|
|
989
996
|
botMsg = Object.assign({}, botMsg, {
|
|
@@ -991,7 +998,7 @@ class BotiumConnectorVoip {
|
|
|
991
998
|
});
|
|
992
999
|
this.firstMsg = false;
|
|
993
1000
|
} else {
|
|
994
|
-
const sourceData = parsedData;
|
|
1001
|
+
const sourceData = this._decorateSourceDataWithSttDictionaryReplacements(parsedData, replacementResult);
|
|
995
1002
|
const start = lodash__default["default"].get(parsedData, 'data.start', null);
|
|
996
1003
|
const prevEnd = lodash__default["default"].get(this.prevData, 'data.end', null);
|
|
997
1004
|
sourceData.silenceDuration = lodash__default["default"].isFinite(start) && lodash__default["default"].isFinite(prevEnd) ? start - prevEnd : lodash__default["default"].isFinite(start) ? start : null;
|
|
@@ -1010,8 +1017,8 @@ class BotiumConnectorVoip {
|
|
|
1010
1017
|
}
|
|
1011
1018
|
if (this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING] === 'JOIN' || this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING] === 'PSST' || this.caps[Capabilities.VOIP_STT_MESSAGE_HANDLING] === 'CONCAT') {
|
|
1012
1019
|
const botMsg = {
|
|
1013
|
-
messageText:
|
|
1014
|
-
sourceData: parsedData
|
|
1020
|
+
messageText: normalizedMsgText,
|
|
1021
|
+
sourceData: this._decorateSourceDataWithSttDictionaryReplacements(parsedData, replacementResult)
|
|
1015
1022
|
};
|
|
1016
1023
|
this.prevData = parsedData;
|
|
1017
1024
|
if (successfulConfidenceScore) {
|
|
@@ -1521,27 +1528,99 @@ class BotiumConnectorVoip {
|
|
|
1521
1528
|
};
|
|
1522
1529
|
}).filter(Boolean);
|
|
1523
1530
|
}
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
if (
|
|
1531
|
+
|
|
1532
|
+
// Matches a substring rule against ONLY the latest buffered STT final chunk.
|
|
1533
|
+
// This keeps the rule's custom timeout in effect for just the one following
|
|
1534
|
+
// final: once a final no longer matches, callers fall back to the default
|
|
1535
|
+
// timeout (the convoStep expected text and the cumulative buffer are
|
|
1536
|
+
// deliberately not considered, so a stale match cannot stick).
|
|
1537
|
+
_getJoinRuleBySubstring(botMsgs) {
|
|
1538
|
+
if (!lodash__default["default"].isArray(botMsgs) || botMsgs.length === 0) return null;
|
|
1539
|
+
const last = botMsgs[botMsgs.length - 1];
|
|
1540
|
+
const text = last && typeof last.messageText === 'string' ? last.messageText : '';
|
|
1541
|
+
if (!text) return null;
|
|
1542
|
+
const loweredText = text.toLowerCase();
|
|
1535
1543
|
const rules = this._normalizeJoinRulesBySubstring();
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1544
|
+
return rules.find(rule => loweredText.includes(rule.substring.toLowerCase())) || null;
|
|
1545
|
+
}
|
|
1546
|
+
_normalizeSttDictionaryReplacements() {
|
|
1547
|
+
const rawRules = this.caps[Capabilities.VOIP_STT_DICTIONARY_REPLACEMENTS];
|
|
1548
|
+
if (lodash__default["default"].isNil(rawRules) || rawRules === '') return [];
|
|
1549
|
+
let parsedRules = rawRules;
|
|
1550
|
+
if (lodash__default["default"].isString(rawRules)) {
|
|
1551
|
+
try {
|
|
1552
|
+
parsedRules = JSON.parse(rawRules);
|
|
1553
|
+
} catch (err) {
|
|
1554
|
+
debug$3(`Invalid ${Capabilities.VOIP_STT_DICTIONARY_REPLACEMENTS} JSON: ${err.message || err}`);
|
|
1555
|
+
return [];
|
|
1556
|
+
}
|
|
1540
1557
|
}
|
|
1541
|
-
|
|
1558
|
+
if (!lodash__default["default"].isArray(parsedRules)) {
|
|
1559
|
+
debug$3(`Invalid ${Capabilities.VOIP_STT_DICTIONARY_REPLACEMENTS}: expected array`);
|
|
1560
|
+
return [];
|
|
1561
|
+
}
|
|
1562
|
+
return parsedRules.map(rule => {
|
|
1563
|
+
if (!rule || typeof rule !== 'object') return null;
|
|
1564
|
+
const fromValues = lodash__default["default"].isArray(rule.from) ? rule.from : [rule.from];
|
|
1565
|
+
const from = lodash__default["default"].uniq(fromValues.map(value => value != null ? String(value).trim() : '').filter(Boolean));
|
|
1566
|
+
const to = rule.to != null ? String(rule.to).trim() : '';
|
|
1567
|
+
if (from.length === 0 || !to) return null;
|
|
1568
|
+
return {
|
|
1569
|
+
from,
|
|
1570
|
+
to
|
|
1571
|
+
};
|
|
1572
|
+
}).filter(Boolean);
|
|
1573
|
+
}
|
|
1574
|
+
_applySttDictionaryReplacements(text) {
|
|
1575
|
+
if (!lodash__default["default"].isString(text) || text.length === 0) return {
|
|
1576
|
+
text,
|
|
1577
|
+
applied: []
|
|
1578
|
+
};
|
|
1579
|
+
const replacements = this._normalizeSttDictionaryReplacements();
|
|
1580
|
+
if (replacements.length === 0) return {
|
|
1581
|
+
text,
|
|
1582
|
+
applied: []
|
|
1583
|
+
};
|
|
1584
|
+
const replacementsByFrom = new Map();
|
|
1585
|
+
const fromAlternatives = [];
|
|
1586
|
+
replacements.forEach(rule => {
|
|
1587
|
+
rule.from.forEach(from => {
|
|
1588
|
+
const fromKey = from.toLowerCase();
|
|
1589
|
+
if (!replacementsByFrom.has(fromKey)) {
|
|
1590
|
+
replacementsByFrom.set(fromKey, rule.to);
|
|
1591
|
+
fromAlternatives.push(from);
|
|
1592
|
+
}
|
|
1593
|
+
});
|
|
1594
|
+
});
|
|
1595
|
+
if (fromAlternatives.length === 0) return {
|
|
1596
|
+
text,
|
|
1597
|
+
applied: []
|
|
1598
|
+
};
|
|
1599
|
+
fromAlternatives.sort((a, b) => b.length - a.length);
|
|
1600
|
+
const matcher = new RegExp(fromAlternatives.map(value => lodash__default["default"].escapeRegExp(value)).join('|'), 'gi');
|
|
1601
|
+
const applied = [];
|
|
1602
|
+
const replacedText = text.replace(matcher, match => {
|
|
1603
|
+
const to = replacementsByFrom.get(match.toLowerCase());
|
|
1604
|
+
applied.push({
|
|
1605
|
+
from: match,
|
|
1606
|
+
to
|
|
1607
|
+
});
|
|
1608
|
+
return to;
|
|
1609
|
+
});
|
|
1610
|
+
return {
|
|
1611
|
+
text: replacedText,
|
|
1612
|
+
applied
|
|
1613
|
+
};
|
|
1614
|
+
}
|
|
1615
|
+
_decorateSourceDataWithSttDictionaryReplacements(sourceData, replacementResult) {
|
|
1616
|
+
if (!replacementResult || !lodash__default["default"].isArray(replacementResult.applied) || replacementResult.applied.length === 0) return sourceData;
|
|
1617
|
+
return Object.assign({}, sourceData, {
|
|
1618
|
+
sttDictionaryOriginalMessage: replacementResult.text === sourceData?.data?.message ? undefined : sourceData?.data?.message,
|
|
1619
|
+
sttDictionaryReplacements: replacementResult.applied
|
|
1620
|
+
});
|
|
1542
1621
|
}
|
|
1543
1622
|
_hasJoinLogicHookOrRule(convoStep) {
|
|
1544
|
-
return !lodash__default["default"].isNil(this._getJoinLogicHook(convoStep)) || !lodash__default["default"].isNil(this._getJoinRuleBySubstring(
|
|
1623
|
+
return !lodash__default["default"].isNil(this._getJoinLogicHook(convoStep)) || !lodash__default["default"].isNil(this._getJoinRuleBySubstring(this.botMsgs));
|
|
1545
1624
|
}
|
|
1546
1625
|
_toJoinTimeoutMs(ms, isPsst) {
|
|
1547
1626
|
const parsed = parseInt(ms, 10);
|
|
@@ -1556,7 +1635,7 @@ class BotiumConnectorVoip {
|
|
|
1556
1635
|
const joinHookTimeoutMs = this._toJoinTimeoutMs(joinLogicHook.args[0], isPsst);
|
|
1557
1636
|
if (lodash__default["default"].isFinite(joinHookTimeoutMs) && joinHookTimeoutMs > 0) return joinHookTimeoutMs;
|
|
1558
1637
|
}
|
|
1559
|
-
const joinRule = this._getJoinRuleBySubstring(
|
|
1638
|
+
const joinRule = this._getJoinRuleBySubstring(botMsgs);
|
|
1560
1639
|
if (joinRule) {
|
|
1561
1640
|
const joinRuleTimeoutMs = this._toJoinTimeoutMs(joinRule.timeoutMs, isPsst);
|
|
1562
1641
|
if (lodash__default["default"].isFinite(joinRuleTimeoutMs) && joinRuleTimeoutMs > 0) return joinRuleTimeoutMs;
|
|
@@ -1654,6 +1733,18 @@ var botiumConnectorVoip = {
|
|
|
1654
1733
|
type: 'json',
|
|
1655
1734
|
required: false,
|
|
1656
1735
|
advanced: true
|
|
1736
|
+
}, {
|
|
1737
|
+
name: 'VOIP_STT_DICTIONARY_REPLACEMENTS',
|
|
1738
|
+
label: 'STT dictionary replacements',
|
|
1739
|
+
type: 'json',
|
|
1740
|
+
required: false,
|
|
1741
|
+
advanced: true
|
|
1742
|
+
}, {
|
|
1743
|
+
name: 'VOIP_SDP_MEDIA_TYPE_TEXT_ENABLE',
|
|
1744
|
+
label: 'Enable SDP media type text',
|
|
1745
|
+
type: 'boolean',
|
|
1746
|
+
required: false,
|
|
1747
|
+
advanced: true
|
|
1657
1748
|
}]
|
|
1658
1749
|
},
|
|
1659
1750
|
PluginLogicHooks: {
|