Compare commits

...

103 Commits

Author SHA1 Message Date
akirilyuk
4855fec4f5 add vad 2022-02-15 13:55:18 +01:00
akirilyuk
a11822609f fix say task 2022-02-08 12:22:37 +01:00
akirilyuk
8dd9bfbb74 stop bot promt on bargein 2022-02-04 09:03:35 +01:00
akirilyuk
cee21c3dbc use custom customizer 2022-02-04 09:01:37 +01:00
akirilyuk
e2753ca8a3 fix config merging 2022-02-04 08:57:32 +01:00
akirilyuk
df302320ca apply interim config changes on barge in 2022-02-04 08:47:51 +01:00
akirilyuk
83a3bb61fe only set setting if we are making output in gather 2022-02-04 08:35:07 +01:00
akirilyuk
c36b55303a fix listen after speech 2022-02-04 08:31:14 +01:00
akirilyuk
60d7f0f31c add more logs 2022-02-04 08:29:16 +01:00
akirilyuk
b089bd4663 add more logs 2022-02-04 08:28:33 +01:00
akirilyuk
5c8f91c1c1 add more logs 2022-02-04 08:26:55 +01:00
akirilyuk
50f926bce4 add customizer for merging arrays 2022-02-04 08:21:53 +01:00
akirilyuk
2a61d21bff return the merged config... 2022-02-04 08:15:22 +01:00
akirilyuk
418baa20df fix setting initial config 2022-02-04 08:12:53 +01:00
akirilyuk
eb418a42e9 add debug log 2022-02-04 08:10:51 +01:00
akirilyuk
57ba79f908 fix config merging 2022-02-04 08:06:19 +01:00
akirilyuk
3a791a67b5 make barge in disableable 2022-02-04 08:05:11 +01:00
akirilyuk
91108fa3ef remove additional logs 2022-02-03 22:52:06 +01:00
akirilyuk
36c723d8f0 add more debug logs 2022-02-03 22:46:45 +01:00
akirilyuk
bc59fc80c9 revert some changes 2022-02-03 22:15:37 +01:00
akirilyuk
6960466afc use stability for bargein feature 2022-02-03 22:12:47 +01:00
akirilyuk
09c2608114 add stability instead of final 2022-02-03 22:10:40 +01:00
akirilyuk
f1c17e537d only resolve on barge in if final event 2022-02-03 22:07:47 +01:00
akirilyuk
22dad4eed6 restructure gather a bit 2022-02-03 22:01:29 +01:00
akirilyuk
017bc39103 temp remove gather say logic 2022-02-03 19:26:42 +01:00
akirilyuk
8b982f20d6 always listen after say 2022-02-03 19:23:30 +01:00
akirilyuk
b6d0d4cb0e do nothing if not listen after speech 2022-02-03 19:22:20 +01:00
akirilyuk
4636b487b4 use a timeout 2022-02-03 19:18:41 +01:00
akirilyuk
fce40a47ce try a different approach 2022-02-03 19:14:37 +01:00
akirilyuk
1fd94dce94 start listeing if enabled 2022-02-03 18:54:57 +01:00
akirilyuk
efba631282 remove killing gather 2022-02-03 18:48:01 +01:00
akirilyuk
2fc3febcf6 kill only after 2s 2022-02-03 18:42:03 +01:00
akirilyuk
cc67132dfa add more logs 2022-02-03 18:37:14 +01:00
akirilyuk
f8e88f085f change timeout to 1 2022-02-03 18:32:50 +01:00
akirilyuk
1050eb47cd try something else 2022-02-03 18:31:42 +01:00
akirilyuk
4e2feda7f3 wait before killing gather 2022-02-03 18:29:48 +01:00
akirilyuk
1685262658 try somehting out 2022-02-03 18:26:48 +01:00
akirilyuk
6fc9a3567e do not kill say tasks 2022-02-03 18:25:34 +01:00
akirilyuk
fb33574861 make gather on final ping a task 2022-02-03 18:16:23 +01:00
akirilyuk
d2732c9be6 kill all say tasks if we got transcription on barge in 2022-02-03 18:11:44 +01:00
akirilyuk
294d38dcd1 skip until botinput by default 2022-02-03 18:08:04 +01:00
akirilyuk
7bdac328bf fix say 2022-02-03 18:04:14 +01:00
akirilyuk
2e0f4b94dc add new var to gather task validation 2022-02-03 18:00:27 +01:00
akirilyuk
53e5360ab3 improve var naming 2022-02-03 17:58:26 +01:00
akirilyuk
8163c33462 fix promt task 2022-02-03 17:51:04 +01:00
akirilyuk
7e9b6498c5 drop queue speech on barge in 2022-02-03 17:47:11 +01:00
akirilyuk
a1c59a5f25 use npm registry 2022-02-03 17:04:08 +01:00
akirilyuk
52a2ce8a86 Merge branch 'main' of github.com:jambonz/jambonz-feature-server into feature/cognigy-enhancements-alex 2022-02-03 17:04:02 +01:00
akirilyuk
2738299524 fix gather 2022-02-02 22:58:00 +01:00
akirilyuk
e872d314b3 fix udnefined var 2022-02-02 22:23:28 +01:00
akirilyuk
b1683bc294 add more logs 2022-02-02 22:22:06 +01:00
akirilyuk
78869e4bb8 add more logs 2022-02-02 22:15:57 +01:00
akirilyuk
ba994af012 fix making say task 2022-02-02 21:39:44 +01:00
akirilyuk
e579514321 exec gather after finishing queue 2022-02-02 20:25:52 +01:00
akirilyuk
baed1b0eac add support for session config & cleanup 2022-02-02 20:19:38 +01:00
akirilyuk
b9dfecceff send audio on barge in gather 2022-02-02 15:56:14 +01:00
akirilyuk
c7c99f45a4 await gather task 2022-02-02 15:38:52 +01:00
akirilyuk
67d18b26ff only gather if we not already did 2022-02-02 15:37:21 +01:00
akirilyuk
53cfbc1f56 fix turn config injection 2022-02-02 15:19:35 +01:00
akirilyuk
da35449c16 add more logs 2022-02-02 15:15:30 +01:00
akirilyuk
83c015c839 first implementation of nextTurn and gather task cognigy 2022-02-02 14:59:04 +01:00
akirilyuk
f1f21fb23b remove reffered by 2022-02-01 20:12:24 +01:00
akirilyuk
c88ead7f71 remove double replace application call 2022-02-01 18:39:45 +01:00
akirilyuk
b41c1ffb91 change refer to implementation 2022-02-01 18:39:28 +01:00
akirilyuk
cc98f40d44 remove not used module 2022-02-01 18:35:14 +01:00
akirilyuk
11035264ec change the way we create tasks for cognigy 2022-02-01 18:34:34 +01:00
akirilyuk
5389083107 normalize things 2022-02-01 18:30:20 +01:00
akirilyuk
9657017669 improve log messages 2022-02-01 18:22:06 +01:00
akirilyuk
b532a49e45 fix hangup and refer task 2022-02-01 18:13:50 +01:00
akirilyuk
97d7a60994 add refer and hangup 2022-02-01 18:12:08 +01:00
akirilyuk
6dbbbf8c9e improve hangup 2022-02-01 16:35:44 +01:00
akirilyuk
4ea9707e4e add params to start listening 2022-02-01 16:04:36 +01:00
akirilyuk
e20c472bd2 remove this 2022-02-01 16:02:37 +01:00
akirilyuk
f2421bb3dd fix gather bug 2022-02-01 16:01:00 +01:00
akirilyuk
18a141edc6 no gather text prompt 2022-02-01 15:53:51 +01:00
akirilyuk
30a4e0e6a3 fix handling non string text 2022-02-01 15:53:00 +01:00
akirilyuk
924627a50c add text promt to gather 2022-02-01 15:43:27 +01:00
akirilyuk
d76a5e6efb add log for gether task creation 2022-02-01 15:40:43 +01:00
akirilyuk
cdfb1fff1e fix vars naming 2022-02-01 15:32:49 +01:00
akirilyuk
5c1501b6c7 return created gather config 2022-02-01 15:29:32 +01:00
akirilyuk
161e67ece5 fix gather task creation again 2022-02-01 15:29:16 +01:00
akirilyuk
65b04d7cbf add default value 2022-02-01 15:25:51 +01:00
akirilyuk
8a641f1d9a add defaul value for gather config 2022-02-01 15:24:29 +01:00
akirilyuk
95188f59ec create gather without a prompt 2022-02-01 15:19:05 +01:00
akirilyuk
4e76077dc9 create gather without prompt 2022-02-01 15:18:50 +01:00
akirilyuk
3c75d5a489 do not await tasks 2022-02-01 14:56:07 +01:00
akirilyuk
3164e1ea4e add more error logs 2022-02-01 14:54:47 +01:00
akirilyuk
c9e3e97d53 fix say task again 2022-02-01 14:46:44 +01:00
akirilyuk
ed157c6aee exec the tasks 2022-02-01 14:44:02 +01:00
akirilyuk
c395109966 add more logs 2022-02-01 14:30:27 +01:00
akirilyuk
7228594f79 include text we should change later 2022-02-01 14:23:39 +01:00
akirilyuk
28cde62d5d add queueing of tasks 2022-02-01 14:19:28 +01:00
akirilyuk
db6f56a068 kill task after hangup or refer 2022-02-01 13:06:57 +01:00
akirilyuk
9f757b439f remove test bot utterance 2022-02-01 13:03:22 +01:00
akirilyuk
35114b22d8 add support for hangup and sip:refer 2022-02-01 12:47:46 +01:00
akirilyuk
68b2ad526a add test utterance 2022-02-01 12:22:09 +01:00
akirilyuk
b8ef1dba73 add test log 2022-02-01 12:01:53 +01:00
Dave Horton
2ce902c00d linting fixes 2022-01-26 14:04:14 -05:00
Dave Horton
8c00c89882 add support for retry logic and dtmf 2022-01-26 14:01:38 -05:00
Dave Horton
dcd6ddcbca typo 2022-01-26 10:29:44 -05:00
Dave Horton
9a71350875 lint fix 2022-01-26 10:29:44 -05:00
Dave Horton
1bca165fc1 bugfix: handle interim results from azure 2022-01-26 10:29:44 -05:00
Dave Horton
b94605127e initial revamp of cognigy verb to use gather, accept session and turn-level config from bot 2022-01-26 10:29:44 -05:00
9 changed files with 810 additions and 309 deletions

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
registry=https://registry.npmjs.org

View File

@@ -1,248 +0,0 @@
const Task = require('./task');
const {TaskName, TaskPreconditions} = require('../utils/constants');
const makeTask = require('./make_task');
const { SocketClient } = require('@cognigy/socket-client');
const parseGallery = (obj = {}) => {
const {_default} = obj;
if (_default) {
const {_gallery} = _default;
if (_gallery) return _gallery.fallbackText;
}
};
const parseQuickReplies = (obj) => {
const {_default} = obj;
if (_default) {
const {_quickReplies} = _default;
if (_quickReplies) return _quickReplies.text || _quickReplies.fallbackText;
}
};
const parseBotText = (evt) => {
const {text, data} = evt;
if (text) return text;
switch (data?.type) {
case 'quickReplies':
return parseQuickReplies(data?._cognigy);
case 'gallery':
return parseGallery(data?._cognigy);
default:
break;
}
};
class Cognigy extends Task {
constructor(logger, opts) {
super(logger, opts);
this.preconditions = TaskPreconditions.Endpoint;
this.url = this.data.url;
this.token = this.data.token;
this.prompt = this.data.prompt;
this.eventHook = this.data?.eventHook;
this.actionHook = this.data?.actionHook;
this.data = this.data.data || {};
this.prompts = [];
}
get name() { return TaskName.Cognigy; }
get hasReportedFinalAction() {
return this.reportedFinalAction || this.isReplacingApplication;
}
async exec(cs, ep) {
await super.exec(cs);
this.ep = ep;
try {
/* set event handlers and start transcribing */
this.on('transcription', this._onTranscription.bind(this, cs, ep));
this.on('error', this._onError.bind(this, cs, ep));
this.transcribeTask = this._makeTranscribeTask();
this.transcribeTask.exec(cs, ep, this)
.catch((err) => {
this.logger.info({err}, 'Cognigy transcribe task returned error');
this.notifyTaskDone();
});
if (this.prompt) {
this.sayTask = this._makeSayTask(this.prompt);
this.sayTask.exec(cs, ep, this)
.catch((err) => {
this.logger.info({err}, 'Cognigy say task returned error');
this.notifyTaskDone();
});
}
/* connect to the bot and send initial data */
this.client = new SocketClient(
this.url,
this.token,
{
sessionId: cs.callSid,
channel: 'jambonz',
forceWebsockets: true,
reconnection: true,
settings: {
enableTypingIndicator: false
}
}
);
this.client.on('output', this._onBotUtterance.bind(this, cs, ep));
this.client.on('typingStatus', this._onBotTypingStatus.bind(this, cs, ep));
this.client.on('error', this._onBotError.bind(this, cs, ep));
this.client.on('finalPing', this._onBotFinalPing.bind(this, cs, ep));
await this.client.connect();
this.client.sendMessage('', {...this.data, ...cs.callInfo});
await this.awaitTaskDone();
} catch (err) {
this.logger.error({err}, 'Cognigy error');
throw err;
}
}
async kill(cs) {
super.kill(cs);
this.logger.debug('Cognigy:kill');
this.removeAllListeners();
this.transcribeTask && this.transcribeTask.kill();
this.client.removeAllListeners();
if (this.client && this.client.connected) this.client.disconnect();
if (!this.hasReportedFinalAction) {
this.reportedFinalAction = true;
this.performAction({cognigyResult: 'caller hungup'})
.catch((err) => this.logger.info({err}, 'cognigy - error w/ action webook'));
}
if (this.ep.connected) {
await this.ep.api('uuid_break', this.ep.uuid).catch((err) => this.logger.info(err, 'Error killing audio'));
}
this.notifyTaskDone();
}
_makeTranscribeTask() {
const opts = {
recognizer: this.data.recognizer || {
vendor: 'default',
language: 'default',
outputFormat: 'detailed'
}
};
this.logger.debug({opts}, 'constructing a nested transcribe object');
const transcribe = makeTask(this.logger, {transcribe: opts}, this);
return transcribe;
}
_makeSayTask(text) {
const opts = {
text,
synthesizer: this.data.synthesizer ||
{
vendor: 'default',
language: 'default',
voice: 'default'
}
};
this.logger.debug({opts}, 'constructing a nested say object');
const say = makeTask(this.logger, {say: opts}, this);
return say;
}
async _onBotError(cs, ep, evt) {
this.logger.info({evt}, 'Cognigy:_onBotError');
this.performAction({cognigyResult: 'botError', message: evt.message });
this.reportedFinalAction = true;
this.notifyTaskDone();
}
async _onBotTypingStatus(cs, ep, evt) {
this.logger.info({evt}, 'Cognigy:_onBotTypingStatus');
}
async _onBotFinalPing(cs, ep) {
this.logger.info('Cognigy:_onBotFinalPing');
if (this.prompts.length) {
const text = this.prompts.join('.');
this.prompts = [];
if (text && !this.killed) {
this.sayTask = this._makeSayTask(text);
this.sayTask.exec(cs, ep, this)
.catch((err) => {
this.logger.info({err}, 'Cognigy say task returned error');
this.notifyTaskDone();
});
}
}
}
async _onBotUtterance(cs, ep, evt) {
this.logger.debug({evt}, 'Cognigy:_onBotUtterance');
if (this.eventHook) {
this.performHook(cs, this.eventHook, {event: 'botMessage', message: evt})
.then((redirected) => {
if (redirected) {
this.logger.info('Cognigy_onTranscription: event handler for bot message redirected us to new webhook');
this.reportedFinalAction = true;
this.performAction({cognigyResult: 'redirect'}, false);
}
return;
})
.catch(({err}) => {
this.logger.info({err}, 'Cognigy_onTranscription: error sending event hook');
});
}
const text = parseBotText(evt);
this.prompts.push(text);
}
async _onTranscription(cs, ep, evt) {
this.logger.debug({evt}, `Cognigy: got transcription for callSid ${cs.callSid}`);
const utterance = evt.alternatives[0].transcript;
if (this.eventHook) {
this.performHook(cs, this.eventHook, {event: 'userMessage', message: utterance})
.then((redirected) => {
if (redirected) {
this.logger.info('Cognigy_onTranscription: event handler for user message redirected us to new webhook');
this.reportedFinalAction = true;
this.performAction({cognigyResult: 'redirect'}, false);
if (this.transcribeTask) this.transcribeTask.kill(cs);
}
return;
})
.catch(({err}) => {
this.logger.info({err}, 'Cognigy_onTranscription: error sending event hook');
});
}
/* send the user utterance to the bot */
try {
if (this.client && this.client.connected) {
this.client.sendMessage(utterance);
}
else {
this.logger.info('Cognigy_onTranscription - not sending user utterance as bot is disconnected');
}
} catch (err) {
this.logger.error({err}, 'Cognigy_onTranscription: Error sending user utterance to Cognigy - ending task');
this.performAction({cognigyResult: 'socketError'});
this.reportedFinalAction = true;
this.notifyTaskDone();
}
}
_onError(cs, ep, err) {
this.logger.debug({err}, 'Cognigy: got error');
if (!this.hasReportedFinalAction) this.performAction({cognigyResult: 'error', err});
this.reportedFinalAction = true;
this.notifyTaskDone();
}
}
module.exports = Cognigy;

470
lib/tasks/cognigy/index.js Normal file
View File

@@ -0,0 +1,470 @@
const Task = require('../task');
const {TaskName, TaskPreconditions} = require('../../utils/constants');
const makeTask = require('../make_task');
const { SocketClient } = require('@cognigy/socket-client');
const SpeechConfig = require('./speech-config');
const queue = require('queue');
const parseGallery = (obj = {}) => {
const {_default} = obj;
if (_default) {
const {_gallery} = _default;
if (_gallery) return _gallery.fallbackText;
}
};
const parseQuickReplies = (obj) => {
const {_default} = obj;
if (_default) {
const {_quickReplies} = _default;
if (_quickReplies) return _quickReplies.text || _quickReplies.fallbackText;
}
};
const parseBotText = (evt) => {
const {text, data} = evt;
if (text !== undefined) return String(text);
switch (data?.type) {
case 'quickReplies':
return parseQuickReplies(data?._cognigy);
case 'gallery':
return parseGallery(data?._cognigy);
default:
break;
}
};
class Cognigy extends Task {
constructor(logger, opts) {
super(logger, opts);
this.preconditions = TaskPreconditions.Endpoint;
this.url = this.data.url;
this.token = this.data.token;
this.prompt = this.data.prompt;
this.eventHook = this.data?.eventHook;
this.actionHook = this.data?.actionHook;
this.data = this.data.data || {};
this.prompts = [];
this.retry = {};
this.timeoutCount = 0;
// create a task queue so we can execute our taskss subsequently
// also executing tasks whenever they come in
this.taskQueue = queue({concurrency: 1, autostart: 1});
this.changeConfigTasks = [];
// keep track of turns so we only do gather once per turn
this.turn = 0;
this.gatherTurn = 0;
}
get name() { return TaskName.Cognigy; }
get hasReportedFinalAction() {
return this.reportedFinalAction || this.isReplacingApplication;
}
async _enqueueTask(task) {
let resolver;
let rejector;
const boundTask = task.bind(this);
const taskPromise = new Promise(async(resolve, reject) => {
resolver = resolve;
rejector = reject;
});
taskPromise.resolve = resolver;
this.taskQueue.push(async(cb) => {
this.logger.debug('executing task from queue');
try {
const result = await boundTask();
// if this is a config task, remove it from the config task storage,
// as we have now executed it
if(task.isConfigTask){
this.changeConfigTasks.shift();
}
resolver(result);
cb(result);
} catch (err) {
this.logger.error({err}, 'could not execute task in task queue');
rejector(err);
cb(err);
}
this.logger.debug('say task executed from queue');
});
// if this is a config task, lets also push the config
if(task.isConfigTask){
this.changeConfigTasks.push(task);
}
if (this.taskQueue.lastPromise) {
// resolve the previous promise for cleanup
this.taskQueue.lastPromise.resolve({});
}
this.taskQueue.lastPromise = taskPromise;
return taskPromise;
}
async exec(cs, ep) {
await super.exec(cs);
const opts = {
synthesizer: this.data.synthesizer || {
vendor: 'default',
language: 'default',
voice: 'default'
},
recognizer: this.data.recognizer || {
vendor: 'default',
language: 'default'
},
bargein: this.data.bargein || {},
bot: this.data.bot || {},
user: this.data.user || {},
dtmf: this.data.dtmf || {}
};
this.config = new SpeechConfig({logger: this.logger, ep, opts});
this.ep = ep;
try {
/* set event handlers and start transcribing */
this.on('transcription', this._onTranscription.bind(this, cs, ep));
this.on('dtmf-collected', this._onDtmf.bind(this, cs, ep));
this.on('timeout', this._onTimeout.bind(this, cs, ep));
this.on('error', this._onError.bind(this, cs, ep));
/* connect to the bot and send initial data */
this.client = new SocketClient(
this.url,
this.token,
{
sessionId: cs.callSid,
channel: 'jambonz',
forceWebsockets: true,
reconnection: true,
settings: {
enableTypingIndicator: false
}
}
);
this.client.on('output', this._onBotUtterance.bind(this, cs, ep));
this.client.on('error', this._onBotError.bind(this, cs, ep));
this.client.on('finalPing', this._onBotFinalPing.bind(this, cs, ep));
await this.client.connect();
// todo make welcome message configurable (enable or disable it when
// we start a conversation (should be enabled by defaul))
this.client.sendMessage('Welcome Message', {...this.data, ...cs.callInfo});
await this.awaitTaskDone();
} catch (err) {
this.logger.error({err}, 'Cognigy error');
throw err;
}
}
async kill(cs) {
super.kill(cs);
this.logger.debug('Cognigy:kill');
this.removeAllListeners();
this.transcribeTask && this.transcribeTask.kill();
this.client.removeAllListeners();
if (this.client && this.client.connected) this.client.disconnect();
try {
// end the task queue AFTER we have removed all listeneres since now we cannot get new stuff inside the queue
this.taskQueue.end();
} catch (err) {
this.logger.error({err}, 'could not end tasks queue!!');
}
if (!this.hasReportedFinalAction) {
this.reportedFinalAction = true;
this.performAction({cognigyResult: 'caller hungup'})
.catch((err) => this.logger.info({err}, 'cognigy - error w/ action webook'));
}
if (this.ep.connected) {
await this.ep.api('uuid_break', this.ep.uuid).catch((err) => this.logger.info(err, 'Error killing audio'));
}
this.notifyTaskDone();
}
/**
* Creates a promt which will be sent to the consumer. We will create a say task if bargein is disabled
* for session and nextTurn, else create a gather task.
*/
_createPromtTask({text, url, turnConfig, listenAfterSpeech} = {}) {
const bargeInOnNextTurn = turnConfig?.bargein?.enable?.length > 0;
const bargeInSession = this.config.bargeInEnabled;
if (bargeInOnNextTurn || bargeInSession) {
return this._makeGatherTask({textPrompt: text, url, turnConfig, listenAfterSpeech});
}
return this._makeSayTask({text, turnConfig});
}
_makeGatherTask({textPrompt, urlPrompt, turnConfig, listenAfterSpeech} = {}) {
this.logger.debug({textPrompt, urlPrompt, turnConfig}, '_makeGatherTask');
const config = this.config.makeGatherTaskConfig({textPrompt, urlPrompt, turnConfig, listenAfterSpeech});
const {retry, ...rest} = config;
this.retry = retry;
const gather = makeTask(this.logger, {gather: rest}, this);
return gather;
}
_makeSayTask({ text, turnConfig } = {}) {
this.logger.debug({text, turnConfig}, '_makeSayTask');
const config = this.config.makeSayTaskConfig({text, turnConfig });
this.logger.debug({config}, 'created say task config');
const say = makeTask(this.logger, { say: config }, this);
return say;
}
_makeReferTask(referTo) {
return makeTask(this.logger, {'sip:refer': {
referTo
}}
);
}
_makeHangupTask(reason) {
return makeTask(this.logger, {
hangup: {
headers: {
'X-Reason': reason
}
}});
}
_makePlayTask(url, loop) {
return makeTask(this.logger, {
play: {
url,
loop
}
});
}
/* if we need to interrupt the currently-running say task(s), call this */
_killSayTasks(ep) {
if (ep && ep.connected) {
ep.api('uuid_break', this.ep.uuid)
.catch((err) => this.logger.info({err}, 'Cognigy:_killSayTasks - error killing audio for current say task'));
}
}
async _onBotError(cs, ep, evt) {
this.logger.info({evt}, 'Cognigy:_onBotError');
this.performAction({cognigyResult: 'botError', message: evt.message });
this.reportedFinalAction = true;
this.notifyTaskDone();
}
async _onBotFinalPing(cs, ep) {
this.logger.info({prompts: this.prompts}, 'Cognigy:_onBotFinalPing');
try {
// lets wait until we have finished processing the speech before
// starting a gather...
this.logger.debug('enqueued bot final ping gather');
this._enqueueTask(async() => {
this.logger.debug('executing bot final ping gather');
try {
const gatherTask = this._makeGatherTask();
await gatherTask.exec(cs, ep, this);
} catch (err) {
this.logger.info({err}, 'Cognigy final ping gather task returned error');
}
this.logger.debug('executed bot final ping gather');
});
} catch (err) {
this.logger.info({err}, 'Cognigy gather task returned error');
}
}
async _onBotUtterance(cs, ep, evt) {
this.logger.debug({evt}, 'Cognigy:_onBotUtterance');
if (this.eventHook) {
this.performHook(cs, this.eventHook, {event: 'botMessage', message: evt})
.then((redirected) => {
if (redirected) {
this.logger.info('Cognigy_onBotUtterance: event handler for bot message redirected us to new webhook');
this.reportedFinalAction = true;
this.performAction({cognigyResult: 'redirect'}, false);
}
return;
})
.catch(({err}) => {
this.logger.info({err}, 'Cognigy_onBotUtterance: error sending event hook');
});
}
const text = parseBotText(evt);
// only add say task if its a normal cognigy node and not a "gather task"
if (text && (evt?.data?.type !== 'promt')) {
this.logger.info({text}, 'received text');
this._enqueueTask(async() => {
// todo inject the session config into the say task
const promtTask = this._createPromtTask({ text, listenAfterSpeech: false });
await promtTask.exec(cs, ep, this);
this.logger.debug({text}, 'executed say task');
});
}
try {
switch (evt?.data?.type) {
case 'hangup':
this._enqueueTask(async() => {
this.performAction({cognigyResult: 'hangup Succeeded'});
this.reportedFinalAction = true;
cs.replaceApplication([this._makeHangupTask(evt.data.reason)]);
this.taskQueue.end();
});
return;
case 'refer':
this._enqueueTask(async() => {
this.performAction({cognigyResult: 'refer succeeded'});
this.reportedFinalAction = true;
cs.replaceApplication([this._makeReferTask(evt.data.referTo)]);
});
return;
case 'promt':
this._enqueueTask(async() => {
const sayTask = this._createPromtTask({
text: evt.data.text,
turnConfig: evt?.data?.config?.nextTurn,
url: evt.data.url
});
try {
await sayTask.exec(cs, ep, this);
} catch (err) {
this.logger.info({err}, 'Cognigy sayTask task returned error');
}
});
return;
case 'setSessionConfig':
// change session params in the order they come in with the say tasks
// so we are consistent with the flow logic executed within cognigy
const updateConfigTask = () => {
if (evt?.data?.config?.session) this.config.update(evt.data.config.session);
};
updateConfigTask.isConfigTask = true;
this._enqueueTask(updateConfigTask);
return;
default:
break;
}
} catch (err) {
this.logger.info({err, evtData: evt.data}, 'encountered error parsing cognigy response data');
if (!this.hasReportedFinalAction) this.performAction({cognigyResult: 'error', err});
this.reportedFinalAction = true;
this.notifyTaskDone();
}
}
async _onTranscription(cs, ep, evt) {
this.logger.debug({evt}, `Cognigy: got transcription for callSid ${cs.callSid}`);
const utterance = evt.alternatives[0].transcript;
//if we have barge in enabled AND we enabled skipping until next question
//then stop execution of currently queues bot output before sending the
//response to waiting bot since otherwise we could stop upcoming bot output
if (this.config.bargeInEnabled && this.config.skipToBotOutputEnd !== false) {
// clear task queue, resolve the last promise and cleanup;
this._killSayTasks();
this.taskQueue.lastPromise.resolve();
this.taskQueue.end();
while(this.changeConfigTasks.length > 0){
// apply all the config tasks FIFO
const changeConfigTask = this.changeConfigTasks.shift();
changeConfigTask();
}
this.taskQueue.autostart = true;
}
if (this.eventHook) {
this.performHook(cs, this.eventHook, {event: 'userMessage', message: utterance})
.then((redirected) => {
if (redirected) {
this.logger.info('Cognigy_onTranscription: event handler for user message redirected us to new webhook');
this.reportedFinalAction = true;
this.performAction({cognigyResult: 'redirect'}, false);
if (this.transcribeTask) this.transcribeTask.kill(cs);
}
return;
})
.catch(({err}) => {
this.logger.info({err}, 'Cognigy_onTranscription: error sending event hook');
});
}
/* send the user utterance to the bot */
try {
if (this.client && this.client.connected) {
this.client.sendMessage(utterance);
}
else {
// if the bot is not connected, should we maybe throw an error here?
this.logger.info('Cognigy_onTranscription - not sending user utterance as bot is disconnected');
}
} catch (err) {
this.logger.error({err}, 'Cognigy_onTranscription: Error sending user utterance to Cognigy - ending task');
this.performAction({cognigyResult: 'socketError'});
this.reportedFinalAction = true;
this.notifyTaskDone();
}
}
_onDtmf(cs, ep, evt) {
this.logger.info({evt}, 'got dtmf');
/* send dtmf to bot */
try {
if (this.client && this.client.connected) {
this.client.sendMessage(String(evt.digits));
}
else {
// if the bot is not connected, should we maybe throw an error here?
this.logger.info('Cognigy_onTranscription - not sending user dtmf as bot is disconnected');
}
} catch (err) {
this.logger.error({err}, '_onDtmf: Error sending user dtmf to Cognigy - ending task');
this.performAction({cognigyResult: 'socketError'});
this.reportedFinalAction = true;
this.notifyTaskDone();
}
}
_onError(cs, ep, err) {
this.logger.info({err}, 'Cognigy: got error');
if (!this.hasReportedFinalAction) this.performAction({cognigyResult: 'error', err});
this.reportedFinalAction = true;
this.notifyTaskDone();
}
_onTimeout(cs, ep, evt) {
const {noInputRetries, noInputSpeech, noInputUrl} = this.retry;
this.logger.debug({evt, retry: this.retry}, 'Cognigy: got timeout');
if (noInputRetries && this.timeoutCount++ < noInputRetries) {
const gatherTask = this._makeGatherTask({textPrompt: noInputSpeech, urlPrompt: noInputUrl});
gatherTask.exec(cs, ep, this)
.catch((err) => this.logger.info({err}, 'Cognigy gather task returned error'));
}
else {
if (!this.hasReportedFinalAction) this.performAction({cognigyResult: 'timeout'});
this.reportedFinalAction = true;
this.notifyTaskDone();
}
}
}
module.exports = Cognigy;

View File

@@ -0,0 +1,148 @@
const obj = require('drachtio-fsmrf/lib/utils');
const Emitter = require('events');
const { isArray } = require('lodash');
const lodash = require('lodash');
const hasKeys = (obj) => typeof obj === 'object' && Object.keys(obj) > 0;
const stripNulls = (obj) => {
Object.keys(obj).forEach((k) => (obj[k] === null || typeof obj[k] === 'undefined') && delete obj[k]);
return obj;
};
class SpeechConfig extends Emitter {
constructor({logger, ep, opts = {}}) {
super();
this.logger = logger;
this.ep = ep;
this.sessionConfig = opts.session || {};
this.update(opts);
}
_mergeConfig(changedConfig = {}) {
const merged = lodash.mergeWith(
this.sessionConfig,
changedConfig,
(objValue, sourceValue) => {
if (Array.isArray(objValue)) {
if (Array.isArray(sourceValue)) {
return sourceValue;
}
return objValue;
}
}
);
this.logger.debug({merged, sessionConfig: this.sessionConfig, changedConfig}, 'merged config');
// should we override hints with empty array or leave it as it is once saved?
// merged.recognizer.hints = changedConfig.recognizer?.hints
return merged;
}
update(session) {
// TODO validation of session params?
if (session) {
this.sessionConfig = this._mergeConfig(session);
}
this.logger.debug({sessionLevel: this.sessionConfig}, 'SpeechConfig updated');
}
/**
* check if we should skip all nodes until next bot input
*/
get skipUntilBotInput() {
return !this.sessionConfig.bargein?.skipUntilBotInput;
}
/**
* Check if barge is enabled on session level
*/
get bargeInEnabled() {
return this.sessionConfig.bargein?.enable?.length > 0;
}
makeSayTaskConfig({text, turnConfig = {}} = {}) {
const synthesizer = lodash.merge({}, this.sessionConfig.synthesizer, turnConfig.synthesizer);
return {
text,
synthesizer
};
}
makeGatherTaskConfig({textPrompt, urlPrompt, turnConfig = {}, listenAfterSpeech} = {}) {
// we merge from top to bottom deeply so we wil have
// defaults from session config and then will override them via turn config
const opts = this._mergeConfig(turnConfig);
this.logger.debug({
opts,
sessionConfig: this.sessionConfig,
turnConfig
}, 'Congigy SpeechConfig:_makeGatherTask current options');
/* input type: speech and/or dtmf entry */
const input = [];
if (opts.recognizer) input.push('speech');
if (hasKeys(opts.dtmf)) input.push('digits');
if (opts.synthesizer) {
// todo remove this once we add support for disabling tts cache
delete opts.synthesizer.disableTtsCache;
}
/* bargein settings */
const bargein = opts.bargein || {};
const speechBargein = Array.isArray(bargein.enable) && bargein.enable.includes('speech');
const dtmfBargein = Array.isArray(bargein.enable) && bargein.enable.includes('dtmf');
const minBargeinWordCount = speechBargein ? (bargein.minWordCount || 1) : 0;
const {interDigitTimeout = 0, maxDigits, minDigits = 1, submitDigit} = (opts.dtmf || {});
const {noInputTimeout, noInputRetries, noInputSpeech, noInputUrl} = (opts.user || {});
let sayConfig;
let playConfig;
if (textPrompt) {
sayConfig = {
text: textPrompt,
synthesizer: opts.synthesizer
};
}
// todo what is the logic here if we put both? play over say or say over play?
if (urlPrompt) {
playConfig = {
url: urlPrompt
};
}
const config = {
input,
listenDuringPrompt: speechBargein,
bargein: speechBargein,
minBargeinWordCount,
dtmfBargein,
minDigits,
maxDigits,
interDigitTimeout,
finishOnKey: submitDigit,
recognizer: opts?.recognizer,
timeout: noInputTimeout,
retry : {
noInputRetries,
noInputSpeech,
noInputUrl
},
listenAfterSpeech
};
const final = stripNulls(config);
const finalConfig = final;
if (sayConfig) {
finalConfig.say = sayConfig;
} else if (playConfig) {
finalConfig.play = playConfig;
}
this.logger.info({finalConfig}, 'created gather config');
return finalConfig;
}
}
module.exports = SpeechConfig;

View File

@@ -16,13 +16,17 @@ class TaskGather extends Task {
this.preconditions = TaskPreconditions.Endpoint;
[
'finishOnKey', 'hints', 'input', 'numDigits',
'partialResultHook',
'finishOnKey', 'hints', 'input', 'numDigits', 'minDigits', 'maxDigits',
'interDigitTimeout', 'submitDigit', 'partialResultHook', 'bargein', 'dtmfBargein',
'retries', 'retryPromptTts', 'retryPromptUrl',
'speechTimeout', 'timeout', 'say', 'play'
].forEach((k) => this[k] = this.data[k]);
this.listenDuringPrompt = this.data.listenDuringPrompt === false ? false : true;
this.minBargeinWordCount = this.data.minBargeinWordCount || 1;
this.timeout = (this.timeout || 5) * 1000;
this.interim = this.partialResultCallback;
this.logger.debug({opts}, 'created gather task');
this.timeout = (this.timeout || 15) * 1000;
this.interim = this.partialResultCallback || this.bargein;
if (this.data.recognizer) {
const recognizer = this.data.recognizer;
this.vendor = recognizer.vendor;
@@ -40,6 +44,10 @@ class TaskGather extends Task {
this.profanityOption = recognizer.profanityOption || 'raw';
this.requestSnr = recognizer.requestSnr || false;
this.initialSpeechTimeoutMs = recognizer.initialSpeechTimeoutMs || 0;
/* vad: if provided, we dont connect to recognizer until voice activity is detected */
const {enable, voiceMs = 0, mode = -1} = recognizer.vad || {};
this.vad = {enable, voiceMs, mode};
}
this.digitBuffer = '';
@@ -48,6 +56,12 @@ class TaskGather extends Task {
if (this.say) this.sayTask = makeTask(this.logger, {say: this.say}, this);
if (this.play) this.playTask = makeTask(this.logger, {play: this.play}, this);
if(this.sayTask || this.playTask){
// this is specially for barge in where we want to make a bargebale promt
// to a user without listening after the say task has finished
this.listenAfterSpeech = typeof this.data.listenAfterSpeech === "boolean" ? this.data.listenAfterSpeech : true;
}
this.parentTask = parentTask;
}
@@ -80,33 +94,67 @@ class TaskGather extends Task {
throw new Error(`no speech-to-text service credentials for ${this.vendor} have been configured`);
}
const startListening = (cs, ep) => {
this._startTimer();
if (this.input.includes('speech') && !this.listenDuringPrompt) {
this.logger.debug('listening after speech 1');
this._initSpeech(cs, ep)
.then(() => {
this._startTranscribing(ep);
return updateSpeechCredentialLastUsed(this.sttCredentials.speech_credential_sid);
})
.catch(() => {});
}
};
try {
if (this.sayTask) {
this.sayTask.exec(cs, ep); // kicked off, _not_ waiting for it to complete
this.sayTask.on('playDone', (err) => {
if (!this.killed) this._startTimer();
this.logger.debug('Gather: kicking off say task');
this.sayTask.exec(cs, ep);
this.sayTask.on('playDone', async(err) => {
if (err) return this.logger.error({err}, 'Gather:exec Error playing tts');
this.logger.debug('Gather: say task completed');
if (!this.killed) {
if (this.listenAfterSpeech === true) {
this.logger.debug('listening after speech 2');
startListening(cs, ep);
} else {
this.notifyTaskDone();
}
}
});
}
else if (this.playTask) {
this.playTask.exec(cs, ep); // kicked off, _not_ waiting for it to complete
this.playTask.on('playDone', (err) => {
if (!this.killed) this._startTimer();
});
this.playTask.on('playDone', async(err) => {
if (err) return this.logger.error({err}, 'Gather:exec Error playing url');
if (!this.killed) {
if (this.listenAfterSpeech === true) {
this.logger.debug('listening after speech 3');
startListening(cs, ep);
} else {
this.notifyTaskDone();
}
}
}
);
}
else this._startTimer();
else startListening(cs, ep);
if (this.input.includes('speech')) {
if (this.input.includes('speech') && this.listenDuringPrompt) {
this.logger.debug('listening after speech 4');
await this._initSpeech(cs, ep);
this._startTranscribing(ep);
updateSpeechCredentialLastUsed(this.sttCredentials.speech_credential_sid)
.catch(() => {/*already logged error */});
}
if (this.input.includes('digits')) {
if (this.input.includes('digits') || this.dtmfBargein) {
ep.on('dtmf', this._onDtmf.bind(this, cs, ep));
}
await this.awaitTaskDone();
this.logger.debug('Gather:exec task has completed');
} catch (err) {
this.logger.error(err, 'TaskGather:exec error');
}
@@ -118,6 +166,7 @@ class TaskGather extends Task {
}
kill(cs) {
this.logger.debug('Gather:kill');
super.kill(cs);
this._killAudio(cs);
this.ep.removeAllListeners('dtmf');
@@ -126,17 +175,37 @@ class TaskGather extends Task {
_onDtmf(cs, ep, evt) {
this.logger.debug(evt, 'TaskGather:_onDtmf');
if (evt.dtmf === this.finishOnKey) this._resolve('dtmf-terminator-key');
clearTimeout(this.interDigitTimer);
let resolved = false;
if (this.dtmfBargein) this._killAudio(cs);
if (evt.dtmf === this.finishOnKey) {
resolved = true;
this._resolve('dtmf-terminator-key');
}
else {
this.digitBuffer += evt.dtmf;
if (this.digitBuffer.length === this.numDigits) this._resolve('dtmf-num-digits');
const len = this.digitBuffer.length;
if (len === this.numDigits || len === this.maxDigits) {
resolved = true;
this._resolve('dtmf-num-digits');
}
}
if (!resolved && this.interDigitTimeout > 0 && this.digitBuffer.length >= this.minDigits) {
/* start interDigitTimer */
const ms = this.interDigitTimeout * 1000;
this.logger.debug(`starting interdigit timer of ${ms}`);
this.interDigitTimer = setTimeout(() => this._resolve('dtmf-interdigit-timeout'), ms);
}
this._killAudio(cs);
}
async _initSpeech(cs, ep) {
const opts = {};
if (this.vad.enable) {
opts.START_RECOGNIZING_ON_VAD = 1;
if (this.vad.voiceMs) opts.RECOGNIZER_VAD_VOICE_MS = this.vad.voiceMs;
if (this.vad.mode >= 0 && this.vad.mode <= 3) opts.RECOGNIZER_VAD_MODE = this.vad.mode;
}
if ('google' === this.vendor) {
if (this.sttCredentials) opts.GOOGLE_APPLICATION_CREDENTIALS = JSON.stringify(this.sttCredentials.credentials);
Object.assign(opts, {
@@ -197,7 +266,7 @@ class TaskGather extends Task {
ep.startTranscription({
vendor: this.vendor,
locale: this.language,
interim: this.partialResultCallback ? true : false,
interim: this.interim,
}).catch((err) => {
const {writeAlerts, AlertType} = this.cs.srf.locals;
this.logger.error(err, 'TaskGather:_startTranscribing error');
@@ -237,25 +306,46 @@ class TaskGather extends Task {
}
_onTranscription(cs, ep, evt) {
this.logger.debug(evt, 'TaskGather:_onTranscription');
if ('aws' === this.vendor && Array.isArray(evt) && evt.length > 0) evt = evt[0];
if ('microsoft' === this.vendor) {
const nbest = evt.NBest;
const newEvent = {
is_final: evt.RecognitionStatus === 'Success',
alternatives: [
{
confidence: nbest[0].Confidence,
transcript: nbest[0].Display
}
]
};
evt = newEvent;
const final = evt.RecognitionStatus === 'Success';
if (final) {
const nbest = evt.NBest;
evt = {
is_final: true,
alternatives: [
{
confidence: nbest[0].Confidence,
transcript: nbest[0].Display
}
]
};
}
else {
evt = {
is_final: false,
alternatives: [
{
transcript: evt.Text
}
]
};
}
}
this.logger.debug(evt, 'TaskGather:_onTranscription');
if (evt.is_final) this._resolve('speech', evt);
else if (this.partialResultHook) {
this.cs.requestor.request(this.partialResultHook, Object.assign({speech: evt}, this.cs.callInfo))
.catch((err) => this.logger.info(err, 'GatherTask:_onTranscription error'));
else {
if (evt.stability > 0.70 &&
this.bargein &&
evt.alternatives[0].transcript.split(' ').length >= this.minBargeinWordCount) {
this.logger.debug('Gather:_onTranscription - killing audio due to bargein');
this._killAudio(cs);
this._resolve('speech', evt);
}
if (this.partialResultHook) {
this.cs.requestor.request(this.partialResultHook, Object.assign({speech: evt}, this.cs.callInfo))
.catch((err) => this.logger.info(err, 'GatherTask:_onTranscription error'));
}
}
}
_onEndOfUtterance(cs, ep) {
@@ -281,7 +371,8 @@ class TaskGather extends Task {
this._clearTimer();
if (reason.startsWith('dtmf')) {
await this.performAction({digits: this.digitBuffer, reason: 'dtmfDetected'});
if (this.parentTask) this.parentTask.emit('dtmf-collected', {reason, digits: this.digitBuffer});
else await this.performAction({digits: this.digitBuffer, reason: 'dtmfDetected'});
}
else if (reason.startsWith('speech')) {
if (this.parentTask) this.parentTask.emit('transcription', evt);

View File

@@ -21,15 +21,20 @@ class TaskSay extends Task {
const {updateSpeechCredentialLastUsed} = require('../utils/db-utils')(this.logger, srf);
const {writeAlerts, AlertType, stats} = srf.locals;
const {synthAudio} = srf.locals.dbHelpers;
const hasVerbLevelTts = this.synthesizer.vendor && this.synthesizer.vendor !== 'default';
const vendor = hasVerbLevelTts ? this.synthesizer.vendor : cs.speechSynthesisVendor ;
const language = hasVerbLevelTts ? this.synthesizer.language : cs.speechSynthesisLanguage ;
const voice = hasVerbLevelTts ? this.synthesizer.voice : cs.speechSynthesisVoice ;
const vendor = this.synthesizer.vendor && this.synthesizer.vendor !== 'default' ? this.synthesizer.vendor : cs.speechSynthesisVendor;
const language = this.synthesizer.language && this.synthesizer.language !== 'default' ? this.synthesizer.language : cs.speechSynthesisLanguage ;
const voice = this.synthesizer.voice && this.synthesizer.voice !== 'default' ? this.synthesizer.voice : cs.speechSynthesisVoice;
const engine = this.synthesizer.engine || 'standard';
const salt = cs.callSid;
const credentials = cs.getSpeechCredentials(vendor, 'tts');
this.logger.info({language, voice}, `Task:say - using vendor: ${vendor}`);
this.logger.info({language,
voice,
localSynthesizer: this.synthesizer,
speechSynthesisVendor: cs.speechSynthesisVendor,
speechSynthesisLanguage: cs.speechSynthesisLanguage,
speechSynthesisVoice: cs.speechSynthesisVoice
}, `Task:say - using vendor: ${vendor}`);
this.ep = ep;
try {
if (!credentials) {
@@ -79,7 +84,11 @@ class TaskSay extends Task {
const {memberId, confName, confUuid} = cs;
await this.playToConfMember(this.ep, memberId, confName, confUuid, filepath[segment]);
}
else await ep.play(filepath[segment]);
else {
this.logger.debug(`Say:exec sending command to play file ${filepath[segment]}`);
await ep.play(filepath[segment]);
this.logger.debug(`Say:exec completed play file ${filepath[segment]}`);
}
} while (!this.killed && ++segment < filepath.length);
}
} catch (err) {

View File

@@ -100,7 +100,15 @@
"numDigits": "number",
"partialResultHook": "object|string",
"speechTimeout": "number",
"listenDuringPrompt": "boolean",
"bargein": "boolean",
"minBargeinWordCount": "number",
"dtmfBargein": "boolean",
"minDigits": "number",
"maxDigits": "number",
"interDigitTimeout": "number",
"timeout": "number",
"listenAfterSpeech": "boolean",
"recognizer": "#recognizer",
"play": "#play",
"say": "#say"

57
package-lock.json generated
View File

@@ -26,9 +26,11 @@
"express": "^4.17.1",
"helmet": "^5.0.2",
"ip": "^1.1.5",
"lodash": "^4.17.21",
"moment": "^2.29.1",
"parse-url": "^5.0.7",
"pino": "^6.13.4",
"pino": "^6.13.2",
"queue": "^6.0.2",
"to-snake-case": "^1.0.0",
"uuid": "^8.3.2",
"verify-aws-sns-signature": "^0.0.6",
@@ -2251,6 +2253,11 @@
"resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz",
"integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig=="
},
"node_modules/fastify-warning": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz",
"integrity": "sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw=="
},
"node_modules/file-entry-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.0.tgz",
@@ -4042,15 +4049,15 @@
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
"node_modules/pino": {
"version": "6.13.4",
"resolved": "https://registry.npmjs.org/pino/-/pino-6.13.4.tgz",
"integrity": "sha512-g4tHSISmQJYUEKEMVdaZ+ZokWwFnTwZL5JPn+lnBVZ1BuBbrSchrXwQINknkM5+Q4fF6U9NjiI8PWwwMDHt9zA==",
"version": "6.13.3",
"resolved": "https://registry.npmjs.org/pino/-/pino-6.13.3.tgz",
"integrity": "sha512-tJy6qVgkh9MwNgqX1/oYi3ehfl2Y9H0uHyEEMsBe74KinESIjdMrMQDWpcZPpPicg3VV35d/GLQZmo4QgU2Xkg==",
"dependencies": {
"fast-redact": "^3.0.0",
"fast-safe-stringify": "^2.0.8",
"fastify-warning": "^0.2.0",
"flatstr": "^1.0.12",
"pino-std-serializers": "^3.1.0",
"process-warning": "^1.0.0",
"quick-format-unescaped": "^4.0.3",
"sonic-boom": "^1.0.2"
},
@@ -4095,11 +4102,6 @@
"node": ">=8"
}
},
"node_modules/process-warning": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz",
"integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q=="
},
"node_modules/progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
@@ -4193,6 +4195,15 @@
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
},
"node_modules/queue": {
"version": "6.0.2",
"resolved": "https://cognigy.pkgs.visualstudio.com/_packaging/cognigy-feed/npm/registry/queue/-/queue-6.0.2.tgz",
"integrity": "sha1-uRUlKD4jFcdVPS76GNg+dkMv7WU=",
"license": "MIT",
"dependencies": {
"inherits": "~2.0.3"
}
},
"node_modules/quick-format-unescaped": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.3.tgz",
@@ -7267,6 +7278,11 @@
"resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz",
"integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig=="
},
"fastify-warning": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/fastify-warning/-/fastify-warning-0.2.0.tgz",
"integrity": "sha512-s1EQguBw/9qtc1p/WTY4eq9WMRIACkj+HTcOIK1in4MV5aFaQC9ZCIt0dJ7pr5bIf4lPpHvAtP2ywpTNgs7hqw=="
},
"file-entry-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.0.tgz",
@@ -8627,15 +8643,15 @@
"integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w="
},
"pino": {
"version": "6.13.4",
"resolved": "https://registry.npmjs.org/pino/-/pino-6.13.4.tgz",
"integrity": "sha512-g4tHSISmQJYUEKEMVdaZ+ZokWwFnTwZL5JPn+lnBVZ1BuBbrSchrXwQINknkM5+Q4fF6U9NjiI8PWwwMDHt9zA==",
"version": "6.13.3",
"resolved": "https://registry.npmjs.org/pino/-/pino-6.13.3.tgz",
"integrity": "sha512-tJy6qVgkh9MwNgqX1/oYi3ehfl2Y9H0uHyEEMsBe74KinESIjdMrMQDWpcZPpPicg3VV35d/GLQZmo4QgU2Xkg==",
"requires": {
"fast-redact": "^3.0.0",
"fast-safe-stringify": "^2.0.8",
"fastify-warning": "^0.2.0",
"flatstr": "^1.0.12",
"pino-std-serializers": "^3.1.0",
"process-warning": "^1.0.0",
"quick-format-unescaped": "^4.0.3",
"sonic-boom": "^1.0.2"
}
@@ -8668,11 +8684,6 @@
"fromentries": "^1.2.0"
}
},
"process-warning": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz",
"integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q=="
},
"progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
@@ -8745,6 +8756,14 @@
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
},
"queue": {
"version": "6.0.2",
"resolved": "https://cognigy.pkgs.visualstudio.com/_packaging/cognigy-feed/npm/registry/queue/-/queue-6.0.2.tgz",
"integrity": "sha1-uRUlKD4jFcdVPS76GNg+dkMv7WU=",
"requires": {
"inherits": "~2.0.3"
}
},
"quick-format-unescaped": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.3.tgz",

View File

@@ -23,7 +23,8 @@
"start": "node app",
"test": "NODE_ENV=test JAMBONES_HOSTING=1 DRACHTIO_HOST=127.0.0.1 DRACHTIO_PORT=9060 DRACHTIO_SECRET=cymru JAMBONES_MYSQL_HOST=127.0.0.1 JAMBONES_MYSQL_PORT=3360 JAMBONES_MYSQL_USER=jambones_test JAMBONES_MYSQL_PASSWORD=jambones_test JAMBONES_MYSQL_DATABASE=jambones_test JAMBONES_REDIS_HOST=127.0.0.1 JAMBONES_REDIS_PORT=16379 JAMBONES_LOGLEVEL=debug ENABLE_METRICS=0 HTTP_PORT=3000 JAMBONES_SBCS=172.38.0.10 JAMBONES_FREESWITCH=127.0.0.1:8022:ClueCon:docker-host JAMBONES_TIME_SERIES_HOST=127.0.0.1 JAMBONES_NETWORK_CIDR=172.38.0.0/16 node test/ ",
"coverage": "./node_modules/.bin/nyc --reporter html --report-dir ./coverage npm run test",
"jslint": "eslint app.js lib"
"jslint": "eslint app.js lib",
"jslint:fix": "eslint app.js lib --fix"
},
"dependencies": {
"@cognigy/socket-client": "^4.5.5",
@@ -43,9 +44,11 @@
"express": "^4.17.1",
"helmet": "^5.0.2",
"ip": "^1.1.5",
"lodash": "^4.17.21",
"moment": "^2.29.1",
"parse-url": "^5.0.7",
"pino": "^6.13.4",
"pino": "^6.13.2",
"queue": "^6.0.2",
"to-snake-case": "^1.0.0",
"uuid": "^8.3.2",
"verify-aws-sns-signature": "^0.0.6",