Compare commits

..

2 Commits

15 changed files with 104 additions and 321 deletions

View File

@@ -6,8 +6,8 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 20
- run: npm ci

View File

@@ -115,7 +115,6 @@ class CallSession extends Emitter {
this.logger.debug(`CallSession: ${this.callSid} listener count ${this.requestor.listenerCount('command')}`);
this.requestor.on('connection-dropped', this._onWsConnectionDropped.bind(this));
this.requestor.on('handover', handover.bind(this));
this.requestor.on('reconnect-error', this._onSessionReconnectError.bind(this));
};
if (!this.isConfirmCallSession) {
@@ -123,7 +122,6 @@ class CallSession extends Emitter {
this.logger.debug(`CallSession: ${this.callSid} listener count ${this.requestor.listenerCount('command')}`);
this.requestor.on('connection-dropped', this._onWsConnectionDropped.bind(this));
this.requestor.on('handover', handover.bind(this));
this.requestor.on('reconnect-error', this._onSessionReconnectError.bind(this));
}
}
@@ -515,14 +513,6 @@ class CallSession extends Emitter {
set actionHookDelayActions(e) {
this._actionHookDelayActions = e;
}
// Getter/setter for current tts vendor
get currentTtsVendor() {
return this._currentTtsVendor;
}
set currentTtsVendor(vendor) {
this._currentTtsVendor = vendor;
}
hasGlobalSttPunctuation() {
return this._globalSttPunctuation !== undefined;
@@ -812,7 +802,6 @@ class CallSession extends Emitter {
speech_credential_sid: credential.speech_credential_sid,
accessKeyId: credential.access_key_id,
secretAccessKey: credential.secret_access_key,
roleArn: credential.role_arn,
region: credential.aws_region || AWS_REGION
};
}
@@ -1715,22 +1704,6 @@ Duration=${duration} `
}, 'CallSession:_injectTasks - completed');
}
async _onSessionReconnectError(err) {
const {writeAlerts, AlertType} = this.srf.locals;
const sid = this.accountInfo.account.account_sid;
this.logger.info({err}, `_onSessionReconnectError for account ${sid}`);
try {
await writeAlerts({
alert_type: AlertType.WEBHOOK_CONNECTION_FAILURE,
account_sid: this.accountSid,
detail: `Session:reconnect error ${err}`
});
} catch (error) {
this.logger.error({error}, 'Error writing WEBHOOK_CONNECTION_FAILURE alert');
}
this._jambonzHangup();
}
_onCommand({msgid, command, call_sid, queueCommand, data}) {
this.logger.info({msgid, command, queueCommand, data}, 'CallSession:_onCommand - received command');
let resolution;

View File

@@ -60,8 +60,6 @@ class Conference extends Task {
this.emitter = new Emitter();
this.results = {};
this.coaching = [];
this.speakOnlyTo = this.data.speakOnlyTo;
// transferred from another server in order to bridge to a local caller?
if (this.data._ && this.data._.connectTime) {
@@ -350,7 +348,7 @@ class Conference extends Task {
Object.assign(opts, {flags: {
...(this.endConferenceOnExit && {endconf: true}),
...(this.startConferenceOnEnter && {moderator: true}),
...((this.joinMuted || this.speakOnlyTo) && {joinMuted: true}),
...((this.joinMuted || this.data.speakOnlyTo) && {joinMuted: true}),
}});
/**
@@ -363,7 +361,7 @@ class Conference extends Task {
try {
const {memberId, confUuid} = await this.ep.join(this.confName, opts);
this.logger.debug({memberId, confUuid}, `Conference:_joinConference: successfully joined ${this.confName}`);
this.memberId = parseInt(memberId, 10);
this.memberId = memberId;
this.confUuid = confUuid;
// set a tag for this member, if provided
@@ -397,8 +395,8 @@ class Conference extends Task {
.catch((err) => {});
}
if (this.speakOnlyTo) {
this.setCoachMode(this.speakOnlyTo);
if (this.data.speakOnlyTo) {
this.setCoachMode(this.data.speakOnlyTo);
}
} catch (err) {
this.logger.error(err, `Failed to join conference ${this.confName}`);
@@ -588,7 +586,7 @@ class Conference extends Task {
const response = await this.ep.api('conference', [this.confName, 'get', 'count']);
if (response.body && confNoMatch(response.body)) this.participantCount = 0;
else if (response.body && /^\d+$/.test(response.body)) this.participantCount = parseInt(response.body) - 1;
this.logger.debug(`Conference:_doFinalMemberCheck conference count ${this.participantCount}`);
this.logger.debug({response}, `Conference:_doFinalMemberCheck conference count ${this.participantCount}`);
} catch (err) {
this.logger.info({err}, 'Conference:_doFinalMemberCheck error retrieving count (we were probably kicked');
}
@@ -701,12 +699,7 @@ class Conference extends Task {
// conference event handlers
_onAddMember(logger, cs, evt) {
const memberId = parseInt(evt.getHeader('Member-ID')) ;
if (this.speakOnlyTo) {
logger.debug(`Conference:_onAddMember - member ${memberId} added to ${this.confName}, updating coaching mode`);
this.setCoachMode(this.speakOnlyTo).catch(() => {});
}
else logger.debug(`Conference:_onAddMember - member ${memberId} added to conference ${this.confName}`);
logger.debug({evt}, `Conference:_onAddMember - member added to conference ${this.confName}`);
}
_onDelMember(logger, cs, evt) {
const memberId = parseInt(evt.getHeader('Member-ID')) ;
@@ -741,64 +734,28 @@ class Conference extends Task {
}
}
_onTag(logger, cs, evt) {
const memberId = parseInt(evt.getHeader('Member-ID')) ;
const tag = evt.getHeader('Tag') || '';
if (memberId !== this.memberId && this.speakOnlyTo) {
logger.info(`Conference:_onTag - member ${memberId} set tag to '${tag }'; updating coach mode accordingly`);
this.setCoachMode(this.speakOnlyTo).catch(() => {});
}
}
/**
* Set the conference to "coaching" mode, where the audio of the participant is only heard
* by a subset of the participants in the conference.
* We do this by first getting all of the members who do *not* have this tag, and then
* we configure this members audio to not be sent to them.
* @param {string} speakOnlyTo - tag of the members who should receive our audio
*
* N.B.: this feature requires jambonz patches to freeswitch mod_conference
*/
async setCoachMode(speakOnlyTo) {
this.speakOnlyTo = speakOnlyTo;
if (!this.memberId) {
this.logger.info('Conference:_setCoachMode: no member id yet');
return;
}
try {
const members = (await this.ep.getNonMatchingConfParticipants(this.confName, speakOnlyTo))
.filter((m) => m !== this.memberId);
if (members.length === 0) {
this.logger.info({members}, 'Conference:_setCoachMode: all participants have the tag, so all will hear me');
if (this.coaching.length) {
await this.ep.api('conference', [this.confName, 'relate', this.memberId, this.coaching.join(','), 'clear']);
this.coaching = [];
}
}
else {
const memberList = members.join(',');
this.logger.info(`Conference:_setCoachMode: my audio will NOT be sent to ${memberList}`);
await this.ep.api('conference', [this.confName, 'relate', this.memberId, memberList, 'nospeak']);
this.coaching = members;
}
const response = await this.ep.api('conference', [this.confName, 'gettag', speakOnlyTo, 'nomatch']);
this.logger.info(`Conference:_setCoachMode: my audio will only be sent to particpants ${response}`);
await this.ep.api('conference', [this.confName, 'relate', this.memberId, response, 'nospeak']);
this.speakOnlyTo = speakOnlyTo;
this.coaching = response;
} catch (err) {
this.logger.error({err, speakOnlyTo}, '_setCoachMode: Error');
}
}
async clearCoachMode() {
if (!this.memberId) return;
try {
if (this.coaching.length === 0) {
if (!this.coaching) {
this.logger.info('Conference:_clearCoachMode: no coaching mode to clear');
return;
}
else {
const memberList = this.coaching.join(',');
this.logger.info(`Conference:_clearCoachMode: now sending my audio to all, including ${memberList}`);
await this.ep.api('conference', [this.confName, 'relate', this.memberId, memberList, 'clear']);
}
this.logger.info(`Conference:_clearCoachMode: now sending my audio to all, including ${this.coaching}`);
await this.ep.api('conference', [this.confName, 'relate', this.memberId, this.coaching, 'clear']);
this.speakOnlyTo = null;
this.coaching = [];
this.coaching = null;
} catch (err) {
this.logger.error({err}, '_clearCoachMode: Error');
}

View File

@@ -30,7 +30,7 @@ class TaskConfig extends Task {
input: ['speech']
};
[
'finishOnKey', 'input', 'numDigits', 'minDigits', 'maxDigits',
'finishOnKey', 'input', 'numDigits', 'minDigits', 'maxDigits', 'partialResultHook',
'interDigitTimeout', 'bargein', 'dtmfBargein', 'minBargeinWordCount', 'actionHook'
].forEach((k) => {
if (this.bargeIn[k]) this.gatherOpts[k] = this.bargeIn[k];

View File

@@ -636,8 +636,6 @@ class TaskDial extends Task {
await this._connectSingleDial(cs, sd);
} catch (err) {
this.logger.info({err}, 'Dial:_attemptCalls - Error calling _connectSingleDial ');
sd.removeAllListeners();
this.kill(cs);
}
})
.on('decline', () => {

View File

@@ -351,7 +351,8 @@ class TaskGather extends SttTask {
/* some special deepgram logic */
if (this.vendor === 'deepgram') {
if (this.isContinuousAsr) this._doContinuousAsrWithDeepgram(this.asrTimeout);
if (this.data.recognizer?.deepgramOptions?.shortUtterance) this.shortUtterance = true;
if (this.data.recognizer?.deepgramOptions?.shortUtterance ||
this.data.recognizer?.deepgramOptions?.endpointing === false) this.shortUtterance = true;
}
const opts = this.setChannelVarsForStt(this, this.sttCredentials, this.language, this.data.recognizer);
@@ -747,7 +748,6 @@ class TaskGather extends SttTask {
const finished = fsEvent.getHeader('transcription-session-finished');
this.logger.debug({evt, bugname, finished, vendor: this.vendor}, 'Gather:_onTranscription raw transcript');
if (bugname && this.bugname !== bugname) return;
if (finished === 'true') return;
if (this.vendor === 'ibm' && evt?.state === 'listening') return;
if (this.vendor === 'deepgram' && evt.type === 'UtteranceEnd') {
@@ -909,9 +909,6 @@ class TaskGather extends SttTask {
this._sonioxTranscripts.push(evt.vendor.finalWords);
}
}
/* restart asr timer if we get a partial transcript */
if (this.isContinuousAsr) this._startAsrTimer();
}
}
_onEndOfUtterance(cs, ep) {

View File

@@ -102,11 +102,8 @@ class TaskSay extends TtsTask {
ep.set({
tts_engine: vendor,
tts_voice: voice,
cache_speech_handles: !cs.currentTtsVendor || cs.currentTtsVendor === vendor ? 1 : 0,
cache_speech_handles: 1,
}).catch((err) => this.logger.info({err}, 'Error setting tts_engine on endpoint'));
// set the current vendor on the call session
// If vendor is changed from the previous one, then reset the cache_speech_handles flag
cs.currentTtsVendor = vendor;
if (!preCache) this.logger.info({vendor, language, voice, model}, 'TaskSay:exec');
try {
@@ -242,7 +239,10 @@ class TaskSay extends TtsTask {
label = fallbackLabel;
}
const startFallback = async(error) => {
let filepath;
try {
filepath = await this._synthesizeWithSpecificVendor(cs, ep, {vendor, language, voice, label});
} catch (error) {
if (fallbackVendor && this.isHandledByPrimaryProvider && !cs.hasFallbackTts) {
this.notifyError(
{ msg: 'TTS error', details:`TTS vendor ${vendor} error: ${error}`, failover: 'in progress'});
@@ -261,12 +261,6 @@ class TaskSay extends TtsTask {
{ msg: 'TTS error', details:`TTS vendor ${vendor} error: ${error}`, failover: 'not available'});
throw error;
}
};
let filepath;
try {
filepath = await this._synthesizeWithSpecificVendor(cs, ep, {vendor, language, voice, label});
} catch (error) {
await startFallback(error);
}
this.notifyStatus({event: 'start-playback'});
@@ -313,31 +307,8 @@ class TaskSay extends TtsTask {
text
}).catch((err) => this.logger.info({err}, 'Error adding file to cache'));
}
if (this._playResolve) {
evt.variable_tts_error ? this._playReject(new Error(evt.variable_tts_error)) : this._playResolve();
}
});
// wait for playback-stop event received to confirm if the playback is successful
this._playPromise = new Promise((resolve, reject) => {
this._playResolve = resolve;
this._playReject = reject;
});
await ep.play(filepath[segment]);
try {
// wait for playback-stop event received to confirm if the playback is successful
await this._playPromise;
} catch (err) {
try {
await startFallback(err);
} catch (err) {
this.logger.info({err}, 'Error waiting for playback-stop event');
}
continue;
} finally {
this._playPromise = null;
this._playResolve = null;
this._playReject = null;
}
if (filepath[segment].startsWith('say:{')) {
const arr = /^say:\{.*\}\s*(.*)$/.exec(filepath[segment]);
if (arr) this.logger.debug(`Say:exec complete playing streaming tts request: ${arr[1].substring(0, 64)}..`);

View File

@@ -123,18 +123,10 @@ class SttTask extends Task {
this.sttCredentials = await this._initSpeechCredentials(this.cs, this.vendor, this.label);
} catch (error) {
if (this.canFallback) {
this.notifyError(
{
msg: 'ASR error', details:`Invalid vendor ${this.vendor}, Error: ${error}`,
failover: 'in progress'
});
await this._initFallback();
this.notifyError({ msg: 'ASR error', details:`Invalid vendor ${this.vendor}`, failover: 'in progress'});
} else {
this.notifyError(
{
msg: 'ASR error', details:`Invalid vendor ${this.vendor}, Error: ${error}`,
failover: 'not available'
});
this.notifyError({ msg: 'ASR error', details:`Invalid vendor ${this.vendor}`, failover: 'not available'});
throw error;
}
}
@@ -166,7 +158,7 @@ class SttTask extends Task {
}
async _initSpeechCredentials(cs, vendor, label) {
const {getNuanceAccessToken, getIbmAccessToken, getAwsAuthToken} = cs.srf.locals.dbHelpers;
const {getNuanceAccessToken, getIbmAccessToken} = cs.srf.locals.dbHelpers;
let credentials = cs.getSpeechCredentials(vendor, 'stt', label);
if (!credentials) {
@@ -177,6 +169,11 @@ class SttTask extends Task {
alert_type: AlertType.STT_NOT_PROVISIONED,
vendor
}).catch((err) => this.logger.info({err}, 'Error generating alert for no stt'));
// Notify application that STT vender is wrong.
this.notifyError({
msg: 'ASR error',
details: `No speech-to-text service credentials for ${vendor} have been configured`
});
this.notifyTaskDone();
throw new Error(`No speech-to-text service credentials for ${vendor} have been configured`);
}
@@ -194,12 +191,6 @@ class SttTask extends Task {
const {access_token, servedFromCache} = await getIbmAccessToken(stt_api_key);
this.logger.debug({stt_api_key}, `got ibm access token ${servedFromCache ? 'from cache' : ''}`);
credentials = {...credentials, access_token, stt_region};
} else if (['aws', 'polly'].includes(vendor) && credentials.roleArn) {
/* get aws access token */
const {roleArn} = credentials;
const {accessKeyId, secretAccessKey, sessionToken, servedFromCache} = await getAwsAuthToken(roleArn);
this.logger.debug({roleArn}, `got aws access token ${servedFromCache ? 'from cache' : ''}`);
credentials = {...credentials, accessKeyId, secretAccessKey, sessionToken};
}
return credentials;
}
@@ -265,20 +256,6 @@ class SttTask extends Task {
_doContinuousAsrWithDeepgram(asrTimeout) {
/* deepgram has an utterance_end_ms property that simplifies things */
assert(this.vendor === 'deepgram');
if (asrTimeout < 1000) {
this.notifyError({
msg: 'ASR error',
details:`asrTimeout ${asrTimeout} is too short for deepgram; setting it to 1000ms`
});
asrTimeout = 1000;
}
else if (asrTimeout > 5000) {
this.notifyError({
msg: 'ASR error',
details:`asrTimeout ${asrTimeout} is too long for deepgram; setting it to 5000ms`
});
asrTimeout = 5000;
}
this.logger.debug(`_doContinuousAsrWithDeepgram - setting utterance_end_ms to ${asrTimeout}`);
const dgOptions = this.data.recognizer.deepgramOptions = this.data.recognizer.deepgramOptions || {};
dgOptions.utteranceEndMs = dgOptions.utteranceEndMs || asrTimeout;

View File

@@ -126,7 +126,7 @@ class TaskTranscribe extends SttTask {
})
.catch((err) => this.logger.info(err, 'Error TaskTranscribe:kill'));
}
if (this.transcribing2 && this.ep2?.connected) {
if (this.transcribing2 && this.ep2.connected) {
stopTranscription = true;
this.ep2.stopTranscription({vendor: this.vendor, bugname: this.bugname})
.catch((err) => this.logger.info(err, 'Error TaskTranscribe:kill'));

View File

@@ -41,7 +41,6 @@ const speechMapper = (cred) => {
const o = JSON.parse(decrypt(credential));
obj.access_key_id = o.access_key_id;
obj.secret_access_key = o.secret_access_key;
obj.role_arn = o.role_arn;
obj.aws_region = o.aws_region;
}
else if ('microsoft' === obj.vendor) {

View File

@@ -179,7 +179,6 @@ function installSrfLocals(srf, logger) {
addFileToCache,
getNuanceAccessToken,
getIbmAccessToken,
getAwsAuthToken
} = require('@jambonz/speech-utils')({}, logger);
const {
writeAlerts,
@@ -217,7 +216,6 @@ function installSrfLocals(srf, logger) {
listCalls,
deleteCall,
synthAudio,
getAwsAuthToken,
addFileToCache,
createHash,
retrieveHash,

View File

@@ -552,10 +552,9 @@ module.exports = (logger) => {
...(rOpts.vocabularyFilterName && {AWS_VOCABULARY_FILTER_NAME: rOpts.vocabularyFilterName}),
...(rOpts.filterMethod && {AWS_VOCABULARY_FILTER_METHOD: rOpts.filterMethod}),
...(sttCredentials && {
...(sttCredentials.accessKeyId && {AWS_ACCESS_KEY_ID: sttCredentials.accessKeyId}),
...(sttCredentials.secretAccessKey && {AWS_SECRET_ACCESS_KEY: sttCredentials.secretAccessKey}),
AWS_REGION: sttCredentials.region,
...(sttCredentials.sessionToken && {AWS_SESSION_TOKEN: sttCredentials.sessionToken}),
AWS_ACCESS_KEY_ID: sttCredentials.accessKeyId,
AWS_SECRET_ACCESS_KEY: sttCredentials.secretAccessKey,
AWS_REGION: sttCredentials.region
}),
};
}
@@ -693,7 +692,7 @@ module.exports = (logger) => {
...('endpointing' in deepgramOptions) &&
{DEEPGRAM_SPEECH_ENDPOINTING: deepgramOptions.endpointing === false ? 'false' : deepgramOptions.endpointing,
// default DEEPGRAM_SPEECH_UTTERANCE_END_MS is 1000, will be override by user settings later if there is.
DEEPGRAM_SPEECH_UTTERANCE_END_MS: 1000},
DEEPGRAM_SPEECH_UTTERANCE_END_MS: deepgramOptions.endpointing === false ? null : 1000},
...(deepgramOptions.utteranceEndMs) &&
{DEEPGRAM_SPEECH_UTTERANCE_END_MS: deepgramOptions.utteranceEndMs},
...(deepgramOptions.vadTurnoff) &&

View File

@@ -56,12 +56,6 @@ class WsRequestor extends BaseRequestor {
}
if (type === 'session:new') this.call_sid = params.callSid;
if (type === 'session:reconnect') {
this._reconnectPromise = new Promise((resolve, reject) => {
this._reconnectResolve = resolve;
this._reconnectReject = reject;
});
}
/* if we have an absolute url, and it is http then do a standard webhook */
if (this._isAbsoluteUrl(url) && url.startsWith('http')) {
@@ -77,23 +71,20 @@ class WsRequestor extends BaseRequestor {
}
/* connect if necessary */
const queueMsg = () => {
this.logger.debug(
`WsRequestor:request(${this.id}) - queueing ${type} message since we are connecting`);
if (wantsAck) {
const p = new Promise((resolve, reject) => {
this.queuedMsg.push({type, hook, params, httpHeaders, promise: {resolve, reject}});
});
return p;
}
else {
this.queuedMsg.push({type, hook, params, httpHeaders});
}
return;
};
if (!this.ws) {
if (this.connectInProgress) {
return queueMsg();
this.logger.debug(
`WsRequestor:request(${this.id}) - queueing ${type} message since we are connecting`);
if (wantsAck) {
const p = new Promise((resolve, reject) => {
this.queuedMsg.push({type, hook, params, httpHeaders, promise: {resolve, reject}});
});
return p;
}
else {
this.queuedMsg.push({type, hook, params, httpHeaders});
}
return;
}
this.connectInProgress = true;
this.logger.debug(`WsRequestor:request(${this.id}) - connecting since we do not have a connection for ${type}`);
@@ -111,10 +102,6 @@ class WsRequestor extends BaseRequestor {
return Promise.reject(err);
}
}
// If jambonz wait for ack from reconnect, queue the msg until reconnect is acked
if (type !== 'session:reconnect' && this._reconnectPromise) {
return queueMsg();
}
assert(this.ws);
/* prepare and send message */
@@ -152,18 +139,6 @@ class WsRequestor extends BaseRequestor {
}
};
const rejectQueuedMsgs = (err) => {
if (this.queuedMsg.length > 0) {
for (const {promise} of this.queuedMsg) {
this.logger.debug(`WsRequestor:request - preparing queued ${type} for rejectQueuedMsgs`);
if (promise) {
promise.reject(err);
}
}
this.queuedMsg.length = 0;
}
};
//this.logger.debug({obj}, `websocket: sending (${url})`);
/* special case: reconnecting before we received ack to session:new */
@@ -204,37 +179,16 @@ class WsRequestor extends BaseRequestor {
this.logger.debug({response}, `WsRequestor:request ${url} succeeded in ${rtt}ms`);
this.stats.histogram('app.hook.ws_response_time', rtt, ['hook_type:app']);
resolve(response);
if (this._reconnectResolve) {
this._reconnectResolve();
}
},
failure: (err) => {
if (this._reconnectReject) {
this._reconnectReject(err);
}
clearTimeout(timer);
reject(err);
}
});
/* send the message */
this.ws.send(JSON.stringify(obj), async() => {
this.ws.send(JSON.stringify(obj), () => {
this.logger.debug({obj}, `WsRequestor:request websocket: sent (${url})`);
// If session:reconnect is waiting for ack, hold here until ack to send queuedMsgs
if (this._reconnectPromise) {
try {
await this._reconnectPromise;
} catch (err) {
// bad thing happened to session:recconnect
rejectQueuedMsgs(err);
this.emit('reconnect-error');
return;
} finally {
this._reconnectPromise = null;
this._reconnectResolve = null;
this._reconnectReject = null;
}
}
sendQueuedMsgs();
});
});

132
package-lock.json generated
View File

@@ -15,10 +15,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.0",
"@jambonz/speech-utils": "^0.0.51",
"@jambonz/stats-collector": "^0.1.9",
"@jambonz/time-series": "^0.2.8",
"@jambonz/verb-specifications": "^0.0.69",
"@jambonz/verb-specifications": "^0.0.70",
"@opentelemetry/api": "^1.8.0",
"@opentelemetry/exporter-jaeger": "^1.23.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.50.0",
@@ -31,7 +31,7 @@
"bent": "^7.3.12",
"debug": "^4.3.4",
"deepcopy": "^2.1.0",
"drachtio-fsmrf": "^3.0.41",
"drachtio-fsmrf": "^3.0.40",
"drachtio-srf": "^4.5.31",
"express": "^4.19.2",
"express-validator": "^7.0.1",
@@ -42,13 +42,13 @@
"polly-ssml-split": "^0.1.0",
"proxyquire": "^2.1.3",
"sdp-transform": "^2.14.2",
"short-uuid": "^5.1.0",
"short-uuid": "^4.2.2",
"sinon": "^17.0.1",
"to-snake-case": "^1.0.0",
"undici": "^6.15.0",
"undici": "^6.11.1",
"uuid-random": "^1.3.2",
"verify-aws-sns-signature": "^0.1.0",
"ws": "^8.17.0",
"ws": "^8.16.0",
"xml2js": "^0.6.2"
},
"devDependencies": {
@@ -2322,9 +2322,9 @@
}
},
"node_modules/@jambonz/speech-utils": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.1.0.tgz",
"integrity": "sha512-45K6Vrl2PMEbbcnvm65afCDujDxck/bEUq7+P6KRw/cei3mrKtwjGh3HXi1cKhC1gA5UF1+5YrUoPO9LdoZnog==",
"version": "0.0.51",
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.0.51.tgz",
"integrity": "sha512-3Zk2CERs1PYQiCG08NDMNBbDzBBfPuEwgADTANMP56dd07PpW360ufL8CcQfkBmWKGVma0wevRrv6DQLu2Ifdg==",
"dependencies": {
"@aws-sdk/client-polly": "^3.496.0",
"@aws-sdk/client-sts": "^3.496.0",
@@ -2360,9 +2360,9 @@
}
},
"node_modules/@jambonz/verb-specifications": {
"version": "0.0.69",
"resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.69.tgz",
"integrity": "sha512-DWnz7XRkCzpzyCVJH7NtScv+wSlUC414/EO8j/gPZs3RT4WBW1OBXwXpfjURHcSrDG7lycz+tfA+2WoUdW/W+g==",
"version": "0.0.70",
"resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.70.tgz",
"integrity": "sha512-WRSM7+hMnxLv4Fe5PG02m64uKX1AryRLF/yWpv/0PjHJSO4SYXN8CCAhgb1vctq27FMenMtOrSVt0haZeARObg==",
"dependencies": {
"debug": "^4.3.4",
"pino": "^8.8.0"
@@ -4606,9 +4606,9 @@
}
},
"node_modules/drachtio-fsmrf": {
"version": "3.0.41",
"resolved": "https://registry.npmjs.org/drachtio-fsmrf/-/drachtio-fsmrf-3.0.41.tgz",
"integrity": "sha512-LemyjXtOnd5tOcQSmMgGtKUSdFAM3pLkEwGtV1pRnRhLTS3oERqQwuqRv0LurfZQ38WpuMOrUBhCSb3+uW9t/w==",
"version": "3.0.40",
"resolved": "https://registry.npmjs.org/drachtio-fsmrf/-/drachtio-fsmrf-3.0.40.tgz",
"integrity": "sha512-Mlteu/e1fa1Y4ClkVehMGnto+AKp5NhgIgwKSkFlaCi7Xl8qOqK5IbzgHyPZ2pDE2q7euieNGo+vtB2dUMIIog==",
"dependencies": {
"camel-case": "^4.1.2",
"debug": "^2.6.9",
@@ -4690,18 +4690,6 @@
"ms": "^2.1.1"
}
},
"node_modules/drachtio-srf/node_modules/short-uuid": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-4.2.2.tgz",
"integrity": "sha512-IE7hDSGV2U/VZoCsjctKX6l5t5ak2jE0+aeGJi3KtvjIUNuZVmHVYUjNBhmo369FIWGDtaieRaO8A83Lvwfpqw==",
"dependencies": {
"any-base": "^1.1.0",
"uuid": "^8.3.2"
},
"engines": {
"node": ">=8"
}
},
"node_modules/duplexify": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz",
@@ -8796,27 +8784,15 @@
"integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw=="
},
"node_modules/short-uuid": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-5.1.0.tgz",
"integrity": "sha512-o4LqmXSNdbXDtOngZ4s8VaMxuPHIYxz3YUz51VZijugFyY35Y97LH/yFT54yBB7bRQX5uf/OROrMI5BhZUNOSw==",
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-4.2.2.tgz",
"integrity": "sha512-IE7hDSGV2U/VZoCsjctKX6l5t5ak2jE0+aeGJi3KtvjIUNuZVmHVYUjNBhmo369FIWGDtaieRaO8A83Lvwfpqw==",
"dependencies": {
"any-base": "^1.1.0",
"uuid": "^9.0.1"
"uuid": "^8.3.2"
},
"engines": {
"node": ">=14"
}
},
"node_modules/short-uuid/node_modules/uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
"funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"bin": {
"uuid": "dist/bin/uuid"
"node": ">=8"
}
},
"node_modules/side-channel": {
@@ -9595,11 +9571,11 @@
}
},
"node_modules/undici": {
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.15.0.tgz",
"integrity": "sha512-VviMt2tlMg1BvQ0FKXxrz1eJuyrcISrL2sPfBf7ZskX/FCEc/7LeThQaoygsMJpNqrATWQIsRVx+1Dpe4jaYuQ==",
"version": "6.11.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.11.1.tgz",
"integrity": "sha512-KyhzaLJnV1qa3BSHdj4AZ2ndqI0QWPxYzaIOio0WzcEJB9gvuysprJSLtpvc2D9mhR9jPDUk7xlJlZbH2KR5iw==",
"engines": {
"node": ">=18.17"
"node": ">=18.0"
}
},
"node_modules/undici-types": {
@@ -9956,9 +9932,9 @@
}
},
"node_modules/ws": {
"version": "8.17.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
"integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
"engines": {
"node": ">=10.0.0"
},
@@ -11954,9 +11930,9 @@
}
},
"@jambonz/speech-utils": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.1.0.tgz",
"integrity": "sha512-45K6Vrl2PMEbbcnvm65afCDujDxck/bEUq7+P6KRw/cei3mrKtwjGh3HXi1cKhC1gA5UF1+5YrUoPO9LdoZnog==",
"version": "0.0.51",
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.0.51.tgz",
"integrity": "sha512-3Zk2CERs1PYQiCG08NDMNBbDzBBfPuEwgADTANMP56dd07PpW360ufL8CcQfkBmWKGVma0wevRrv6DQLu2Ifdg==",
"requires": {
"@aws-sdk/client-polly": "^3.496.0",
"@aws-sdk/client-sts": "^3.496.0",
@@ -11992,9 +11968,9 @@
}
},
"@jambonz/verb-specifications": {
"version": "0.0.69",
"resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.69.tgz",
"integrity": "sha512-DWnz7XRkCzpzyCVJH7NtScv+wSlUC414/EO8j/gPZs3RT4WBW1OBXwXpfjURHcSrDG7lycz+tfA+2WoUdW/W+g==",
"version": "0.0.70",
"resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.70.tgz",
"integrity": "sha512-WRSM7+hMnxLv4Fe5PG02m64uKX1AryRLF/yWpv/0PjHJSO4SYXN8CCAhgb1vctq27FMenMtOrSVt0haZeARObg==",
"requires": {
"debug": "^4.3.4",
"pino": "^8.8.0"
@@ -13702,9 +13678,9 @@
}
},
"drachtio-fsmrf": {
"version": "3.0.41",
"resolved": "https://registry.npmjs.org/drachtio-fsmrf/-/drachtio-fsmrf-3.0.41.tgz",
"integrity": "sha512-LemyjXtOnd5tOcQSmMgGtKUSdFAM3pLkEwGtV1pRnRhLTS3oERqQwuqRv0LurfZQ38WpuMOrUBhCSb3+uW9t/w==",
"version": "3.0.40",
"resolved": "https://registry.npmjs.org/drachtio-fsmrf/-/drachtio-fsmrf-3.0.40.tgz",
"integrity": "sha512-Mlteu/e1fa1Y4ClkVehMGnto+AKp5NhgIgwKSkFlaCi7Xl8qOqK5IbzgHyPZ2pDE2q7euieNGo+vtB2dUMIIog==",
"requires": {
"camel-case": "^4.1.2",
"debug": "^2.6.9",
@@ -13777,15 +13753,6 @@
"requires": {
"ms": "^2.1.1"
}
},
"short-uuid": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-4.2.2.tgz",
"integrity": "sha512-IE7hDSGV2U/VZoCsjctKX6l5t5ak2jE0+aeGJi3KtvjIUNuZVmHVYUjNBhmo369FIWGDtaieRaO8A83Lvwfpqw==",
"requires": {
"any-base": "^1.1.0",
"uuid": "^8.3.2"
}
}
}
},
@@ -16894,19 +16861,12 @@
"integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw=="
},
"short-uuid": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-5.1.0.tgz",
"integrity": "sha512-o4LqmXSNdbXDtOngZ4s8VaMxuPHIYxz3YUz51VZijugFyY35Y97LH/yFT54yBB7bRQX5uf/OROrMI5BhZUNOSw==",
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/short-uuid/-/short-uuid-4.2.2.tgz",
"integrity": "sha512-IE7hDSGV2U/VZoCsjctKX6l5t5ak2jE0+aeGJi3KtvjIUNuZVmHVYUjNBhmo369FIWGDtaieRaO8A83Lvwfpqw==",
"requires": {
"any-base": "^1.1.0",
"uuid": "^9.0.1"
},
"dependencies": {
"uuid": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="
}
"uuid": "^8.3.2"
}
},
"side-channel": {
@@ -17504,9 +17464,9 @@
}
},
"undici": {
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.15.0.tgz",
"integrity": "sha512-VviMt2tlMg1BvQ0FKXxrz1eJuyrcISrL2sPfBf7ZskX/FCEc/7LeThQaoygsMJpNqrATWQIsRVx+1Dpe4jaYuQ=="
"version": "6.11.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.11.1.tgz",
"integrity": "sha512-KyhzaLJnV1qa3BSHdj4AZ2ndqI0QWPxYzaIOio0WzcEJB9gvuysprJSLtpvc2D9mhR9jPDUk7xlJlZbH2KR5iw=="
},
"undici-types": {
"version": "5.26.5",
@@ -17777,9 +17737,9 @@
}
},
"ws": {
"version": "8.17.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.0.tgz",
"integrity": "sha512-uJq6108EgZMAl20KagGkzCKfMEjxmKvZHG7Tlq0Z6nOky7YF7aq4mOx6xK8TJ/i1LeK4Qus7INktacctDgY8Ow==",
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.16.0.tgz",
"integrity": "sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==",
"requires": {}
},
"xml2js": {

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.0",
"@jambonz/speech-utils": "^0.0.51",
"@jambonz/stats-collector": "^0.1.9",
"@jambonz/time-series": "^0.2.8",
"@jambonz/verb-specifications": "^0.0.69",
"@jambonz/verb-specifications": "^0.0.70",
"@opentelemetry/api": "^1.8.0",
"@opentelemetry/exporter-jaeger": "^1.23.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.50.0",
@@ -47,7 +47,7 @@
"bent": "^7.3.12",
"debug": "^4.3.4",
"deepcopy": "^2.1.0",
"drachtio-fsmrf": "^3.0.41",
"drachtio-fsmrf": "^3.0.40",
"drachtio-srf": "^4.5.31",
"express": "^4.19.2",
"express-validator": "^7.0.1",
@@ -58,13 +58,13 @@
"polly-ssml-split": "^0.1.0",
"proxyquire": "^2.1.3",
"sdp-transform": "^2.14.2",
"short-uuid": "^5.1.0",
"short-uuid": "^4.2.2",
"sinon": "^17.0.1",
"to-snake-case": "^1.0.0",
"undici": "^6.15.0",
"undici": "^6.11.1",
"uuid-random": "^1.3.2",
"verify-aws-sns-signature": "^0.1.0",
"ws": "^8.17.0",
"ws": "^8.16.0",
"xml2js": "^0.6.2"
},
"devDependencies": {