Compare commits

..

13 Commits

Author SHA1 Message Date
Anton Voylenko
e6e72d6d73 fix: do not run snake case for customer data 2024-08-13 09:30:17 -04:00
rammohan-kore
ee68575ea4 Feat/844 Sending callSid in the custom-stt start message (#848)
* https://github.com/jambonz/jambonz-feature-server/issues/844

sending callSid in options, so that the callSid is sent to stt websocket in case of custom websocket

* feat/844: checking for existance of task.cs.callSid

* feat/844: changed the condition to task.cs?.callSid
2024-08-13 09:28:19 -04:00
rammohan-kore
6d0aeff6e2 feat/859: updated verb-specifications to 0.0.76 (#860) 2024-08-13 08:56:56 -04:00
rammohan-kore
d2a5d483d0 feat/856: sending DEEPGRAM_SPEECH_MODEL_VERSION to deepgram (#858) 2024-08-12 09:34:23 -04:00
Hoan Luu Huu
d3eb106d5d clear gather timeout if imterim result received (#800)
* clear gather timeout if imterim result received

* fix to reset timeout timer if receive interrim result

* fix to reset timeout timer if receive interrim result
2024-08-08 07:50:46 -04:00
Hoan Luu Huu
689e55bdf0 support wait hook for conf:participant-action hold (#851) 2024-08-08 07:42:11 -04:00
Hoan Luu Huu
ed7e036890 support jambonz transcribe sampling rate (#847)
* support jambonz transcribe sampling rate

* fix review comment

* update verb specification version
2024-08-07 10:39:58 -04:00
Hoan Luu Huu
f90fcdf57b Feat/deepgrap tts onprem (#846)
* support deepgram tts onprem

* upodate speech utils version
2024-08-07 07:25:28 -04:00
rammohan-kore
c2a1819cbb feat/813 - notify speech-bargein-detected and dtmf-bargein-detected events (#823)
* feat/813 - notify speech-bargein-detected and dtmf-bargein-detected events

* fix for #826 race condition in say (#827)

* fix for #826 race condition in say

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update transcription-utils.js (#802)

* Check the confidence levels of a transcript  with minConfidence (#808)

* https://github.com/jambonz/jambonz-feature-server/issues/807

* feat/807: Using minConfidence from recognizer settings

* feat/807: new reason stt-min-confidence-error

* feat/807: sending stt-min-confidence instead of  stt-min-confidence-error

* feat/807: sending stt-low-confidence instead of  stt-min-confidence-error

* feat/807 - removed ? for this.data

* fix conference end is not sent when moderator leave conference (#825)

* fix conference end is not sent when moderator leave conference

* wip

* fix review comment

* feat/813: checking for playComplete before sending dtmf-bargein-detected event

* feat/813: added this.playComplete=true at the end of _killAudio method

* feat/813: removed empty line

* feat/813: removed nested if and added condition to main if

* feat/813: notifyStatus called when not playComplete

* feat/813: referring to time-series 0.2.9 version

* feat/813: generated package-lock.json

---------

Co-authored-by: Dave Horton <daveh@beachdognet.com>
Co-authored-by: Vinod Dharashive <vdharashive@gmail.com>
Co-authored-by: Hoan Luu Huu <110280845+xquanluu@users.noreply.github.com>
2024-08-06 09:34:15 -04:00
rammohan-kore
4259a24fa0 feat/758: for google getting the language_code from evt (#843) 2024-08-06 07:45:08 -04:00
rammohan-kore
e4e37d5697 feat/836: capturing callSid for STT and TTS alerts (#838)
* feat/836: capturing callSid for STT and TTS alerts

* feat/836: corrected assignment of callSid and added target_sid at few more alerts

* update github action

---------

Co-authored-by: Quan HL <quan.luuhoang8@gmail.com>
2024-08-05 12:14:08 -04:00
Markus Frindt
b7a3c2970a Bug/fix missing arg reconnect alert (#835)
* Add url as argument to a webhook connection failure alert after reconnect error

* npm audit fix to remove 15 high vulnerabilities

---------

Co-authored-by: Markus Frindt <m.frindt@cognigy.com>
2024-07-31 09:25:31 -04:00
Hoan Luu Huu
cc33ac1d51 fix conference end is not sent when moderator leave conference (#825)
* fix conference end is not sent when moderator leave conference

* wip

* fix review comment
2024-07-30 07:32:07 -04:00
15 changed files with 1662 additions and 3078 deletions

View File

@@ -12,6 +12,11 @@ jobs:
node-version: 20
- run: npm ci
- run: npm run jslint
- name: Install Docker Compose
run: |
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version
- run: docker pull drachtio/sipp
- run: npm test
env:

View File

@@ -873,7 +873,8 @@ class CallSession extends Emitter {
writeAlerts({
alert_type: AlertType.TTS_FAILURE,
account_sid: this.accountSid,
vendor
vendor,
target_sid: this.callSid
}).catch((err) => this.logger.error({err}, 'Error writing tts alert'));
}
}
@@ -919,6 +920,7 @@ class CallSession extends Emitter {
speech_credential_sid: credential.speech_credential_sid,
api_key: credential.api_key,
deepgram_stt_uri: credential.deepgram_stt_uri,
deepgram_tts_uri: credential.deepgram_tts_uri,
deepgram_stt_use_tls: credential.deepgram_stt_use_tls
};
}
@@ -996,7 +998,8 @@ class CallSession extends Emitter {
writeAlerts({
alert_type: AlertType.STT_NOT_PROVISIONED,
account_sid: this.accountSid,
vendor
vendor,
target_sid: this.callSid
}).catch((err) => this.logger.error({err}, 'Error writing tts alert'));
}
}
@@ -1806,7 +1809,8 @@ Duration=${duration} `
await writeAlerts({
alert_type: AlertType.WEBHOOK_CONNECTION_FAILURE,
account_sid: this.accountSid,
detail: `Session:reconnect error ${err}`
detail: `Session:reconnect error ${err}`,
url: this.application.call_hook.url,
});
} catch (error) {
this.logger.error({error}, 'Error writing WEBHOOK_CONNECTION_FAILURE alert');
@@ -2005,11 +2009,6 @@ Duration=${duration} `
if (this.direction === CallDirection.Inbound) {
if (task.earlyMedia && !this.req.finalResponseSent) {
this.res.send(183, {body: ep.local.sdp});
this._notifyCallStatusChange({
callStatus: CallStatus.EarlyMedia,
sipStatus: 183,
sipReason: 'Early Media'
});
return {ep};
}
this.logger.debug('propogating answer');
@@ -2072,6 +2071,9 @@ Duration=${duration} `
this.logger.error('CallSession:replaceEndpoint cannot be called without stable dlg');
return;
}
// When this call kicked out from conference, session need to replace endpoint
// but this.ms might be undefined/null at this case.
this.ms = this.ms || this.getMS();
this.ep = await this.ms.createEndpoint({remoteSdp: this.dlg.remote.sdp});
this._configMsEndpoint();

View File

@@ -35,21 +35,11 @@ class InboundCallSession extends CallSession {
_onCancel() {
this.rootSpan.setAttributes({'call.termination': 'caller abandoned'});
this.callInfo.callTerminationBy = 'caller';
const wasEarlyMedia = this.callInfo.callStatus === 'early-media';
this._notifyCallStatusChange({
callStatus: CallStatus.NoAnswer,
sipStatus: 487,
sipReason: 'Request Terminated'
});
if (wasEarlyMedia) {
const duration = 0; // Set duration to 0 for early media termination, required param
this._notifyCallStatusChange({
callStatus: CallStatus.Completed,
sipStatus: 487,
sipReason: 'Call Terminated During Early Media',
duration: duration
});
}
this._callReleased();
}

View File

@@ -135,15 +135,10 @@ class Conference extends Task {
* @param {SipDialog} dlg
*/
async _init(cs, dlg) {
const friendlyName = this.confName;
const {createHash, retrieveHash} = cs.srf.locals.dbHelpers;
this.friendlyName = this.confName;
this.confName = `conf:${cs.accountSid}:${this.confName}`;
this.statusParams = Object.assign({
conferenceSid: this.confName,
friendlyName
}, cs.callInfo);
// check if conference is in progress
const obj = await retrieveHash(this.confName);
if (obj) {
@@ -493,7 +488,7 @@ class Conference extends Task {
}
async doConferenceParticipantAction(cs, opts) {
const {action, tag} = opts;
const {action, tag, wait_hook } = opts;
switch (action) {
case 'tag':
@@ -509,7 +504,10 @@ class Conference extends Task {
await this.clearCoachMode();
break;
case 'hold':
this.doConferenceHold(cs, {conf_hold_status: 'hold'});
this.doConferenceHold(cs, {
conf_hold_status: 'hold',
...(wait_hook && {wait_hook})
});
break;
case 'unhold':
this.doConferenceHold(cs, {conf_hold_status: 'unhold'});
@@ -604,7 +602,7 @@ class Conference extends Task {
* when we hang up as the last member, the current member count = 1
* when we are kicked out of the call when the moderator leaves, the member count = 0
*/
if (this.participantCount === 0) {
if (this.participantCount === 0 || this.endConferenceOnExit) {
const {deleteKey} = cs.srf.locals.dbHelpers;
try {
this._notifyConferenceEvent(cs, 'end');
@@ -612,7 +610,8 @@ class Conference extends Task {
this.logger.info(`conf ${this.confName} deprovisioned: ${removed ? 'success' : 'failure'}`);
}
catch (err) {
this.logger.error(err, `Error deprovisioning conference ${this.confName}`);
this.logger.error(err, `Error deprovisioning conference ${this.confName},
might be the conference already cleaned by another moderator`);
}
}
}
@@ -689,8 +688,24 @@ class Conference extends Task {
if (!params.time) params.time = (new Date()).toISOString();
if (!params.members && typeof this.participantCount === 'number') params.members = this.participantCount;
cs.application.requestor
.request('verb:hook', this.statusHook, Object.assign(params, this.statusParams, httpHeaders))
.catch((err) => this.logger.info(err, 'Conference:notifyConferenceEvent - error'));
.request(
'verb:hook',
this.statusHook,
Object.assign(
params,
Object.assign(
{
conferenceSid: this.confName,
friendlyName: this.friendlyName,
},
cs.callInfo.toJSON()
),
httpHeaders
)
)
.catch((err) =>
this.logger.info(err, 'Conference:notifyConferenceEvent - error')
);
}
}

View File

@@ -322,6 +322,9 @@ class TaskGather extends SttTask {
clearTimeout(this.interDigitTimer);
let resolved = false;
if (this.dtmfBargein) {
if (!this.playComplete) {
this.notifyStatus({event: 'dtmf-bargein-detected', ...evt});
}
this._killAudio(cs);
this.emit('dtmf', evt);
}
@@ -543,7 +546,8 @@ class TaskGather extends SttTask {
account_sid: this.cs.accountSid,
alert_type: AlertType.STT_FAILURE,
vendor: this.vendor,
detail: err.message
detail: err.message,
target_sid: this.cs.callSid
});
}).catch((err) => this.logger.info({err}, 'Error generating alert for tts failure'));
}
@@ -699,6 +703,7 @@ class TaskGather extends SttTask {
this.playTask.kill(cs);
this.playTask = null;
}
this.playComplete = true;
}
_onTranscription(cs, ep, evt, fsEvent) {
@@ -862,6 +867,7 @@ class TaskGather extends SttTask {
if (!this.playComplete) {
this.logger.debug({transcript: evt.alternatives[0].transcript}, 'killing audio due to speech');
this.emit('vad');
this.notifyStatus({event: 'speech-bargein-detected', ...evt});
}
this._killAudio(cs);
}
@@ -876,13 +882,15 @@ class TaskGather extends SttTask {
this.cs.callInfo, httpHeaders));
}
if (this.vendor === 'soniox') {
this._clearTimer();
if (evt.vendor.finalWords.length) {
this.logger.debug({evt}, 'TaskGather:_onTranscription - buffering soniox transcript');
this._sonioxTranscripts.push(evt.vendor.finalWords);
}
}
// If transcription received, reset timeout timer.
if (this._timeoutTimer) {
this._startTimer();
}
/* restart asr timer if we get a partial transcript */
if (this.isContinuousAsr) this._startAsrTimer();
}
@@ -966,6 +974,7 @@ class TaskGather extends SttTask {
alert_type: AlertType.STT_FAILURE,
message: `Custom speech vendor ${this.vendor} error: ${evt.error}`,
vendor: this.vendor,
target_sid: cs.callSid
}).catch((err) => this.logger.info({err}, 'Error generating alert for jambonz custom connection failure'));
if (!(await this._startFallback(cs, ep, evt))) {
this.notifyTaskDone();

View File

@@ -114,7 +114,8 @@ class TaskSay extends TtsTask {
writeAlerts({
account_sid,
alert_type: AlertType.TTS_NOT_PROVISIONED,
vendor
vendor,
target_sid: cs.callSid
}).catch((err) => this.logger.info({err}, 'Error generating alert for no tts'));
throw new Error('no provisioned speech credentials for TTS');
}
@@ -185,7 +186,8 @@ class TaskSay extends TtsTask {
account_sid: cs.accountSid,
alert_type: AlertType.TTS_FAILURE,
vendor,
detail: err.message
detail: err.message,
target_sid: cs.callSid
}).catch((err) => this.logger.info({err}, 'Error generating alert for tts failure'));
throw err;
}
@@ -201,7 +203,7 @@ class TaskSay extends TtsTask {
}
async exec(cs, {ep}) {
const {srf, accountSid:account_sid} = cs;
const {srf, accountSid:account_sid, callSid:target_sid} = cs;
const {writeAlerts, AlertType} = srf.locals;
const {addFileToCache} = srf.locals.dbHelpers;
const engine = this.synthesizer.engine || cs.synthesizer?.engine || 'neural';
@@ -312,7 +314,8 @@ class TaskSay extends TtsTask {
account_sid,
alert_type: AlertType.TTS_FAILURE,
vendor,
detail: evt.variable_tts_error
detail: evt.variable_tts_error,
target_sid
}).catch((err) => this.logger.info({err}, 'Error generating alert for no tts'));
}
if (evt.variable_tts_cache_filename && !this.killed) {

View File

@@ -178,7 +178,8 @@ class SttTask extends Task {
writeAlerts({
account_sid: cs.accountSid,
alert_type: AlertType.STT_NOT_PROVISIONED,
vendor
vendor,
target_sid: cs.callSid
}).catch((err) => this.logger.info({err}, 'Error generating alert for no stt'));
this.notifyTaskDone();
throw new Error(`No speech-to-text service credentials for ${vendor} have been configured`);
@@ -309,6 +310,7 @@ class SttTask extends Task {
message: 'STT failure reported by vendor',
detail: evt.error,
vendor: this.vendor,
target_sid: cs.callSid
}).catch((err) => this.logger.info({err}, `Error generating alert for ${this.vendor} connection failure`));
}
@@ -321,6 +323,7 @@ class SttTask extends Task {
alert_type: AlertType.STT_FAILURE,
message: `Failed connecting to ${this.vendor} speech recognizer: ${reason}`,
vendor: this.vendor,
target_sid: cs.callSid
}).catch((err) => this.logger.info({err}, `Error generating alert for ${this.vendor} connection failure`));
}
}

View File

@@ -605,6 +605,7 @@ class TaskTranscribe extends SttTask {
alert_type: AlertType.STT_FAILURE,
message: `Custom speech vendor ${this.vendor} error: ${evt.error}`,
vendor: this.vendor,
target_sid: cs.callSid
}).catch((err) => this.logger.info({err}, 'Error generating alert for jambonz custom connection failure'));
if (!(await this._startFallback(cs, _ep, evt))) {
this.notifyTaskDone();

View File

@@ -152,7 +152,8 @@ class TtsTask extends Task {
account_sid: cs.accountSid,
alert_type: AlertType.TTS_FAILURE,
vendor,
detail: err.message
detail: err.message,
target_sid: cs.callSid
}).catch((err) => this.logger.info({err}, 'Error generating alert for tts failure'));
this.notifyError({msg: 'TTS error', details: err.message || err});
throw err;

View File

@@ -210,7 +210,8 @@ module.exports = (logger) => {
account_sid: cs.accountSid,
alert_type: AlertType.STT_FAILURE,
vendor: vendor,
detail: err.message
detail: err.message,
target_sid: cs.callSid
});
}).catch((err) => logger.info({err}, 'Error generating alert for tts failure'));

View File

@@ -77,6 +77,7 @@ const speechMapper = (cred) => {
const o = JSON.parse(decrypt(credential));
obj.api_key = o.api_key;
obj.deepgram_stt_uri = o.deepgram_stt_uri;
obj.deepgram_tts_uri = o.deepgram_tts_uri;
obj.deepgram_stt_use_tls = o.deepgram_stt_use_tls;
}
else if ('soniox' === obj.vendor) {

View File

@@ -441,7 +441,7 @@ class SingleDialer extends Emitter {
});
app.requestor.request('session:adulting', '/adulting', {
...cs.callInfo.toJSON(),
parentCallInfo: this.parentCallInfo
parentCallInfo: this.parentCallInfo.toJSON()
}).catch((err) => {
newLogger.error({err}, 'doAdulting: error sending adulting request');
});

View File

@@ -45,7 +45,8 @@ const stickyVars = {
'DEEPGRAM_SPEECH_ENDPOINTING',
'DEEPGRAM_SPEECH_UTTERANCE_END_MS',
'DEEPGRAM_SPEECH_VAD_TURNOFF',
'DEEPGRAM_SPEECH_TAG'
'DEEPGRAM_SPEECH_TAG',
'DEEPGRAM_SPEECH_MODEL_VERSION'
],
aws: [
'AWS_VOCABULARY_NAME',
@@ -316,8 +317,10 @@ const normalizeIbm = (evt, channel, language) => {
const normalizeGoogle = (evt, channel, language) => {
const copy = JSON.parse(JSON.stringify(evt));
const language_code = evt.language_code || language;
return {
language_code: language,
language_code: language_code,
channel_tag: channel,
is_final: evt.is_final,
alternatives: [evt.alternatives[0]],
@@ -707,7 +710,9 @@ module.exports = (logger) => {
...(deepgramOptions.vadTurnoff) &&
{DEEPGRAM_SPEECH_VAD_TURNOFF: deepgramOptions.vadTurnoff},
...(deepgramOptions.tag) &&
{DEEPGRAM_SPEECH_TAG: deepgramOptions.tag}
{DEEPGRAM_SPEECH_TAG: deepgramOptions.tag},
...(deepgramOptions.version) &&
{DEEPGRAM_SPEECH_MODEL_VERSION: deepgramOptions.version}
};
}
else if ('soniox' === vendor) {
@@ -835,6 +840,7 @@ module.exports = (logger) => {
};
} else if (vendor.startsWith('custom:')) {
let {options = {}} = rOpts;
const {sampleRate} = rOpts.customOptions || {};
const {auth_token, custom_stt_url} = sttCredentials;
options = {
...options,
@@ -842,14 +848,15 @@ module.exports = (logger) => {
{hints: rOpts.hints}),
...(rOpts.hints?.length > 0 && typeof rOpts.hints[0] === 'object' &&
{hints: JSON.stringify(rOpts.hints)}),
...(typeof rOpts.hintsBoost === 'number' && {hintsBoost: rOpts.hintsBoost})
...(typeof rOpts.hintsBoost === 'number' && {hintsBoost: rOpts.hintsBoost}),
...(task.cs?.callSid && {callSid: task.cs.callSid})
};
opts = {
...opts,
...(auth_token && {JAMBONZ_STT_API_KEY: auth_token}),
JAMBONZ_STT_URL: custom_stt_url,
...(Object.keys(options).length > 0 && {JAMBONZ_STT_OPTIONS: JSON.stringify(options)}),
...(sampleRate && {JAMBONZ_STT_SAMPLING: sampleRate})
};
}

4604
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -31,10 +31,10 @@
"@jambonz/http-health-check": "^0.0.1",
"@jambonz/mw-registrar": "^0.2.7",
"@jambonz/realtimedb-helpers": "^0.8.8",
"@jambonz/speech-utils": "^0.1.11",
"@jambonz/speech-utils": "^0.1.13",
"@jambonz/stats-collector": "^0.1.10",
"@jambonz/time-series": "^0.2.8",
"@jambonz/verb-specifications": "^0.0.74",
"@jambonz/time-series": "^0.2.9",
"@jambonz/verb-specifications": "^0.0.76",
"@opentelemetry/api": "^1.8.0",
"@opentelemetry/exporter-jaeger": "^1.23.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.50.0",