mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2026-02-15 10:49:07 +00:00
Compare commits
1 Commits
v0.9.3-rc4
...
snyk-fix-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00031578dd |
@@ -9,8 +9,7 @@ const {
|
||||
KillReason,
|
||||
RecordState,
|
||||
AllowedSipRecVerbs,
|
||||
AllowedConfirmSessionVerbs,
|
||||
TtsStreamingEvents
|
||||
AllowedConfirmSessionVerbs
|
||||
} = require('../utils/constants');
|
||||
const moment = require('moment');
|
||||
const assert = require('assert');
|
||||
@@ -22,7 +21,6 @@ const listTaskNames = require('../utils/summarize-tasks');
|
||||
const HttpRequestor = require('../utils/http-requestor');
|
||||
const WsRequestor = require('../utils/ws-requestor');
|
||||
const ActionHookDelayProcessor = require('../utils/action-hook-delay');
|
||||
const TtsStreamingBuffer = require('../utils/tts-streaming-buffer');
|
||||
const {parseUri} = require('drachtio-srf');
|
||||
const {
|
||||
JAMBONES_INJECT_CONTENT,
|
||||
@@ -402,41 +400,33 @@ class CallSession extends Emitter {
|
||||
return this.application.transferredCall === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if this session is an inbound call session
|
||||
*/
|
||||
get isInboundCallSession() {
|
||||
return this.constructor.name === 'InboundCallSession';
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if this session is a ConfirmCallSession
|
||||
*/
|
||||
get isAdultingCallSession() {
|
||||
return this.constructor.name === 'AdultingCallSession';
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if this session is a ConfirmCallSession
|
||||
*/
|
||||
get isConfirmCallSession() {
|
||||
return this.constructor.name === 'ConfirmCallSession';
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if this session is a SipRecCallSession
|
||||
*/
|
||||
get isSipRecCallSession() {
|
||||
return this.constructor.name === 'SipRecCallSession';
|
||||
}
|
||||
|
||||
/**
|
||||
* returns true if this session is a SmsCallSession
|
||||
*/
|
||||
get isSmsCallSession() {
|
||||
return this.constructor.name === 'SmsCallSession';
|
||||
}
|
||||
get isRestCallSession() {
|
||||
return this.constructor.name === 'RestCallSession';
|
||||
}
|
||||
get InboundCallSession() {
|
||||
return this.constructor.name === 'InboundCallSession';
|
||||
}
|
||||
get isNormalCallSession() {
|
||||
return this.constructor.name === 'InboundCallSession' || this.constructor.name === 'RestCallSession';
|
||||
}
|
||||
|
||||
get is3pccInvite() {
|
||||
return this.isInboundCallSession && this.req?.body?.length === 0;
|
||||
}
|
||||
|
||||
get webhook_secret() {
|
||||
return this.accountInfo?.account?.webhook_secret;
|
||||
@@ -450,10 +440,6 @@ class CallSession extends Emitter {
|
||||
return this.backgroundTaskManager.isTaskRunning('bargeIn');
|
||||
}
|
||||
|
||||
get isTtsStreamEnabled() {
|
||||
return this.backgroundTaskManager.isTaskRunning('ttsStream');
|
||||
}
|
||||
|
||||
get isListenEnabled() {
|
||||
return this.backgroundTaskManager.isTaskRunning('listen');
|
||||
}
|
||||
@@ -516,10 +502,6 @@ class CallSession extends Emitter {
|
||||
this._sipRequestWithinDialogHook = url;
|
||||
}
|
||||
|
||||
get isTtsStreamOpen() {
|
||||
return this.currentTask?.isStreamingTts ||
|
||||
this.backgroundTaskManager.getTask('ttsStream')?.isStreamingTts;
|
||||
}
|
||||
// Bot Delay (actionHook delayed)
|
||||
get actionHookDelayEnabled() {
|
||||
return this._actionHookDelayEnabled;
|
||||
@@ -594,25 +576,6 @@ class CallSession extends Emitter {
|
||||
}
|
||||
}
|
||||
|
||||
getTsStreamingVendor() {
|
||||
let v;
|
||||
if (this.currentTask?.isStreamingTts) {
|
||||
const {vendor} = this.currentTask.getTtsVendorData(this);
|
||||
v = vendor;
|
||||
}
|
||||
else if (this.backgroundTaskManager.getTask('ttsStream')?.isStreamingTts) {
|
||||
const {vendor} = this.backgroundTaskManager.getTask('ttsStream').getTtsVendorData(this);
|
||||
v = vendor;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
get appIsUsingWebsockets() {
|
||||
return this.requestor instanceof WsRequestor;
|
||||
}
|
||||
|
||||
/* end of getters and setters */
|
||||
|
||||
async clearOrRestoreActionHookDelayProcessor() {
|
||||
if (this._actionHookDelayProcessor) {
|
||||
await this._actionHookDelayProcessor.stop();
|
||||
@@ -830,36 +793,6 @@ class CallSession extends Emitter {
|
||||
}
|
||||
}
|
||||
|
||||
async enableBackgroundTtsStream(say) {
|
||||
try {
|
||||
if (this.isTtsStreamEnabled) {
|
||||
this.logger.debug('CallSession:enableBackgroundTtsStream - ttsStream currently enabled, ignoring request');
|
||||
} else if (this.appIsUsingWebsockets && this.isNormalCallSession) {
|
||||
await this.backgroundTaskManager.newTask('ttsStream', say);
|
||||
this.logger.debug('CallSession:enableBackgroundTtsStream - ttsStream enabled');
|
||||
} else {
|
||||
this.logger.debug(
|
||||
'CallSession:enableBackgroundTtsStream - ignoring request as call does not have required conditions');
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.info({err, say}, 'CallSession:enableBackgroundTtsStream - Error creating background tts stream task');
|
||||
}
|
||||
}
|
||||
|
||||
disableTtsStream() {
|
||||
if (this.isTtsStreamEnabled) {
|
||||
this.backgroundTaskManager.stop('ttsStream');
|
||||
this.logger.debug('CallSession:disableTtsStream - ttsStream disabled');
|
||||
}
|
||||
}
|
||||
clearTtsStream() {
|
||||
this.ttsStreamingBuffer?.clear();
|
||||
}
|
||||
|
||||
startTtsStream() {
|
||||
this.ttsStreamingBuffer?.start();
|
||||
}
|
||||
|
||||
async enableBotMode(gather, autoEnable) {
|
||||
try {
|
||||
let task;
|
||||
@@ -1048,14 +981,6 @@ class CallSession extends Emitter {
|
||||
options: credential.options
|
||||
};
|
||||
}
|
||||
else if ('cartesia' === vendor) {
|
||||
return {
|
||||
api_key: credential.api_key,
|
||||
model_id: credential.model_id,
|
||||
embedding: credential.embedding,
|
||||
options: credential.options
|
||||
};
|
||||
}
|
||||
else if ('rimelabs' === vendor) {
|
||||
return {
|
||||
api_key: credential.api_key,
|
||||
@@ -1127,17 +1052,6 @@ class CallSession extends Emitter {
|
||||
this.inbandDtmfEnabled = voipCarrier?.dtmf_type === 'tones';
|
||||
}
|
||||
|
||||
if (this.isNormalCallSession) {
|
||||
this.ttsStreamingBuffer = new TtsStreamingBuffer(this);
|
||||
this.ttsStreamingBuffer.on(TtsStreamingEvents.Empty, this._onTtsStreamingEmpty.bind(this));
|
||||
this.ttsStreamingBuffer.on(TtsStreamingEvents.Pause, this._onTtsStreamingPause.bind(this));
|
||||
this.ttsStreamingBuffer.on(TtsStreamingEvents.Resume, this._onTtsStreamingResume.bind(this));
|
||||
this.ttsStreamingBuffer.on(TtsStreamingEvents.ConnectFailure, this._onTtsStreamingConnectFailure.bind(this));
|
||||
}
|
||||
else {
|
||||
this.logger.info(`CallSession:exec - not a normal call session: ${this.constructor.name}`);
|
||||
}
|
||||
|
||||
while (this.tasks.length && !this.callGone) {
|
||||
const taskNum = ++this.taskIdx;
|
||||
const stackNum = this.stackIdx;
|
||||
@@ -1721,39 +1635,6 @@ Duration=${duration} `
|
||||
.catch((err) => this.logger.error(err, 'CallSession:_lccLlmUpdate'));
|
||||
}
|
||||
|
||||
async _lccTtsTokens(opts) {
|
||||
const {id, tokens} = opts;
|
||||
|
||||
if (id === undefined) {
|
||||
this.logger.info({opts}, 'CallSession:_lccTtsTokens - invalid command since id is missing');
|
||||
return;
|
||||
}
|
||||
else if (tokens === undefined) {
|
||||
this.logger.info({opts}, 'CallSession:_lccTtsTokens - invalid command since id is missing');
|
||||
return this.requestor.request('tts:tokens-result', '/tokens-result', {
|
||||
id,
|
||||
status: 'failed',
|
||||
reason: 'missing tokens'
|
||||
}).catch((err) => this.logger.debug({err}, 'CallSession:_notifyTaskStatus - Error sending'));
|
||||
}
|
||||
let res;
|
||||
try {
|
||||
res = await this.ttsStreamingBuffer?.bufferTokens(tokens);
|
||||
this.logger.info({id, res}, 'CallSession:_lccTtsTokens - tts:tokens-result');
|
||||
} catch (err) {
|
||||
this.logger.info(err, 'CallSession:_lccTtsTokens');
|
||||
}
|
||||
this.requestor.request('tts:tokens-result', '/tokens-result', {id, ...res})
|
||||
.catch((err) => this.logger.debug({err}, 'CallSession:_notifyTaskStatus - Error sending'));
|
||||
}
|
||||
|
||||
_lccTtsFlush(opts) {
|
||||
this.ttsStreamingBuffer?.flush(opts);
|
||||
}
|
||||
|
||||
_lccTtsClear(opts) {
|
||||
this.ttsStreamingBuffer?.clear(opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* perform call hangup by jambonz
|
||||
@@ -2135,18 +2016,6 @@ Duration=${duration} `
|
||||
this._lccLlmUpdate(data, call_sid);
|
||||
break;
|
||||
|
||||
case 'tts:tokens':
|
||||
this._lccTtsTokens(data);
|
||||
break;
|
||||
|
||||
case 'tts:flush':
|
||||
this._lccTtsFlush(data);
|
||||
break;
|
||||
|
||||
case 'tts:clear':
|
||||
this._lccTtsClear(data);
|
||||
break;
|
||||
|
||||
default:
|
||||
this.logger.info(`CallSession:_onCommand - invalid command ${command}`);
|
||||
}
|
||||
@@ -2229,6 +2098,7 @@ Duration=${duration} `
|
||||
//ep.cs = this;
|
||||
this.ep = ep;
|
||||
this.logger.info(`allocated endpoint ${ep.uuid}`);
|
||||
|
||||
this._configMsEndpoint();
|
||||
|
||||
this.ep.on('destroy', () => {
|
||||
@@ -2341,8 +2211,6 @@ Duration=${duration} `
|
||||
// close all background tasks
|
||||
this.backgroundTaskManager.stopAll();
|
||||
this.clearOrRestoreActionHookDelayProcessor().catch((err) => {});
|
||||
|
||||
this.ttsStreamingBuffer?.stop();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2392,43 +2260,22 @@ Duration=${duration} `
|
||||
},
|
||||
localSdp: this.ep.local.sdp
|
||||
});
|
||||
this.logger.debug('answered call');
|
||||
this.dlg.on('destroy', this._callerHungup.bind(this));
|
||||
this.wrapDialog(this.dlg);
|
||||
this.dlg.callSid = this.callSid;
|
||||
this.emit('callStatusChange', {sipStatus: 200, sipReason: 'OK', callStatus: CallStatus.InProgress});
|
||||
|
||||
const tidyUp = () => {
|
||||
this.dlg.on('destroy', this._callerHungup.bind(this));
|
||||
this.wrapDialog(this.dlg);
|
||||
this.dlg.callSid = this.callSid;
|
||||
this.emit('callStatusChange', {sipStatus: 200, sipReason: 'OK', callStatus: CallStatus.InProgress});
|
||||
|
||||
if (this.recordOptions && this.recordState === RecordState.RecordingOff) {
|
||||
this.startRecording();
|
||||
}
|
||||
this.dlg.on('modify', this._onReinvite.bind(this));
|
||||
this.dlg.on('refer', this._onRefer.bind(this));
|
||||
if (this.sipRequestWithinDialogHook) {
|
||||
this.dlg.on('info', this._onRequestWithinDialog.bind(this));
|
||||
this.dlg.on('message', this._onRequestWithinDialog.bind(this));
|
||||
}
|
||||
this.logger.debug(`CallSession:propagateAnswer - answered callSid ${this.callSid}`);
|
||||
};
|
||||
|
||||
if (this.is3pccInvite) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.dlg.once('ack', (ackRequest) => {
|
||||
this.logger.debug({body: ackRequest.body}, 'received ACK for 3pcc invite');
|
||||
this.ep.modify(ackRequest.body)
|
||||
.then(() => {
|
||||
tidyUp();
|
||||
return resolve();
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logger.info({err}, 'Error modifying endpoint with ACK');
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
this.logger.debug('this is a 3pcc invite');
|
||||
});
|
||||
if (this.recordOptions && this.recordState === RecordState.RecordingOff) {
|
||||
this.startRecording();
|
||||
}
|
||||
tidyUp();
|
||||
this.dlg.on('modify', this._onReinvite.bind(this));
|
||||
this.dlg.on('refer', this._onRefer.bind(this));
|
||||
if (this.sipRequestWithinDialogHook) {
|
||||
this.dlg.on('info', this._onRequestWithinDialog.bind(this));
|
||||
this.dlg.on('message', this._onRequestWithinDialog.bind(this));
|
||||
}
|
||||
this.logger.debug(`CallSession:propagateAnswer - answered callSid ${this.callSid}`);
|
||||
}
|
||||
else {
|
||||
this.logger.debug('CallSession:propagateAnswer - call already answered - re-anchor media with a reinvite');
|
||||
@@ -2898,37 +2745,6 @@ Duration=${duration} `
|
||||
this.verbHookSpan = null;
|
||||
}
|
||||
}
|
||||
|
||||
_onTtsStreamingEmpty() {
|
||||
const task = this.currentTask;
|
||||
if (task && TaskName.Say === task.name) {
|
||||
task.notifyTtsStreamIsEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
_onTtsStreamingPause() {
|
||||
this.requestor?.request('tts:streaming-event', '/streaming-event', {event_type: 'stream_paused'})
|
||||
.catch((err) => this.logger.info({err}, 'CallSession:_onTtsStreamingPause - Error sending'));
|
||||
}
|
||||
|
||||
_onTtsStreamingResume() {
|
||||
this.requestor?.request('tts:streaming-event', 'streaming-event', {event_type: 'stream_resumed'})
|
||||
.catch((err) => this.logger.info({err}, 'CallSession:_onTtsStreamingResume - Error sending'));
|
||||
}
|
||||
|
||||
async _onTtsStreamingConnectFailure(vendor) {
|
||||
const {writeAlerts, AlertType} = this.srf.locals;
|
||||
try {
|
||||
await writeAlerts({
|
||||
alert_type: AlertType.TTS_STREAMING_CONNECTION_FAILURE,
|
||||
account_sid: this.accountSid,
|
||||
vendor
|
||||
});
|
||||
} catch (error) {
|
||||
this.logger.error({error}, 'Error writing WEBHOOK_CONNECTION_FAILURE alert');
|
||||
}
|
||||
this.logger.info({vendor}, 'CallSession:_onTtsStreamingConnectFailure - tts streaming connect failure');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CallSession;
|
||||
|
||||
@@ -16,8 +16,7 @@ class TaskConfig extends Task {
|
||||
'fillerNoise',
|
||||
'actionHookDelayAction',
|
||||
'boostAudioSignal',
|
||||
'vad',
|
||||
'ttsStream'
|
||||
'vad'
|
||||
].forEach((k) => this[k] = this.data[k] || {});
|
||||
|
||||
if ('notifyEvents' in this.data) {
|
||||
@@ -46,12 +45,6 @@ class TaskConfig extends Task {
|
||||
};
|
||||
delete this.transcribeOpts.enable;
|
||||
}
|
||||
if (this.ttsStream.enable) {
|
||||
this.sayOpts = {
|
||||
verb: 'say',
|
||||
stream: true
|
||||
};
|
||||
}
|
||||
|
||||
if (this.data.reset) {
|
||||
if (typeof this.data.reset === 'string') this.data.reset = [this.data.reset];
|
||||
@@ -82,7 +75,6 @@ class TaskConfig extends Task {
|
||||
get hasVad() { return Object.keys(this.vad).length; }
|
||||
get hasFillerNoise() { return Object.keys(this.fillerNoise).length; }
|
||||
get hasReferHook() { return Object.keys(this.data).includes('referHook'); }
|
||||
get hasTtsStream() { return Object.keys(this.ttsStream).length; }
|
||||
|
||||
get summary() {
|
||||
const phrase = [];
|
||||
@@ -114,9 +106,6 @@ class TaskConfig extends Task {
|
||||
if (this.onHoldMusic) phrase.push(`onHoldMusic: ${this.onHoldMusic}`);
|
||||
if ('boostAudioSignal' in this.data) phrase.push(`setGain ${this.data.boostAudioSignal}`);
|
||||
if (this.hasReferHook) phrase.push('set referHook');
|
||||
if (this.hasTtsStream) {
|
||||
phrase.push(`${this.ttsStream.enable ? 'enable' : 'disable'} ttsStream`);
|
||||
}
|
||||
return `${this.name}{${phrase.join(',')}}`;
|
||||
}
|
||||
|
||||
@@ -316,22 +305,6 @@ class TaskConfig extends Task {
|
||||
if (this.hasReferHook) {
|
||||
cs.referHook = this.data.referHook;
|
||||
}
|
||||
|
||||
if (this.ttsStream.enable && this.sayOpts) {
|
||||
this.sayOpts.synthesizer = this.hasSynthesizer ? this.synthesizer : {
|
||||
vendor: cs.speechSynthesisVendor,
|
||||
language: cs.speechSynthesisLanguage,
|
||||
voice: cs.speechSynthesisVoice,
|
||||
...(cs.speechSynthesisLabel && {
|
||||
label: cs.speechSynthesisLabel
|
||||
})
|
||||
};
|
||||
this.logger.info({opts: this.gatherOpts}, 'Config: enabling ttsStream');
|
||||
cs.enableBackgroundTtsStream(this.sayOpts);
|
||||
} else if (!this.ttsStream.enable) {
|
||||
this.logger.info('Config: disabling ttsStream');
|
||||
cs.disableTtsStream();
|
||||
}
|
||||
}
|
||||
|
||||
async kill(cs) {
|
||||
|
||||
@@ -723,7 +723,6 @@ class TaskGather extends SttTask {
|
||||
this._fillerNoiseOn = false; // in a race, if we just started audio it may sneak through here
|
||||
this.ep.api('uuid_break', this.ep.uuid)
|
||||
.catch((err) => this.logger.info(err, 'Error killing audio'));
|
||||
cs.clearTtsStream();
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -1171,6 +1170,7 @@ class TaskGather extends SttTask {
|
||||
} catch (err) { /*already logged error*/ }
|
||||
|
||||
// Gather got response from hook, cancel actionHookDelay processing
|
||||
this.logger.debug('TaskGather:_resolve - checking ahd');
|
||||
if (this.cs.actionHookDelayProcessor) {
|
||||
if (returnedVerbs) {
|
||||
this.logger.debug('TaskGather:_resolve - got response from action hook, cancelling actionHookDelay');
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
const Task = require('../task');
|
||||
const {TaskPreconditions} = require('../../utils/constants');
|
||||
const TaskLlmOpenAI_S2S = require('./llms/openai_s2s');
|
||||
const TaskLlmVoiceAgent_S2S = require('./llms/voice_agent_s2s');
|
||||
|
||||
class TaskLlm extends Task {
|
||||
constructor(logger, opts) {
|
||||
@@ -45,12 +44,6 @@ class TaskLlm extends Task {
|
||||
llm = new TaskLlmOpenAI_S2S(this.logger, this.data, this);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'voiceagent':
|
||||
case 'deepgram':
|
||||
llm = new TaskLlmVoiceAgent_S2S(this.logger, this.data, this);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`Unsupported vendor ${this.vendor} for LLM`);
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ class TaskLlmOpenAI_S2S extends Task {
|
||||
this.parent = parentTask;
|
||||
|
||||
this.vendor = this.parent.vendor;
|
||||
this.model = this.parent.model || 'gpt-4o-realtime-preview-2024-12-17';
|
||||
this.model = this.parent.model;
|
||||
this.auth = this.parent.auth;
|
||||
this.connectionOptions = this.parent.connectOptions;
|
||||
|
||||
@@ -120,9 +120,9 @@ class TaskLlmOpenAI_S2S extends Task {
|
||||
|
||||
switch (this.vendor) {
|
||||
case 'openai':
|
||||
return 'v1/realtime?model=${this.model}';
|
||||
return 'v1/realtime?model=gpt-4o-realtime-preview-2024-10-01';
|
||||
case 'microsoft':
|
||||
return `openai/realtime?api-version=2024-10-01-preview&deployment=${this.model}`;
|
||||
return 'openai/realtime?api-version=2024-10-01-preview&deployment=gpt-4o-realtime-preview-1001&';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,313 +0,0 @@
|
||||
const Task = require('../../task');
|
||||
const TaskName = 'Llm_VoiceAgent_s2s';
|
||||
const {LlmEvents_VoiceAgent} = require('../../../utils/constants');
|
||||
const ClientEvent = 'client.event';
|
||||
const SessionDelete = 'session.delete';
|
||||
|
||||
const va_server_events = [
|
||||
'Error',
|
||||
'Welcome',
|
||||
'SettingsApplied',
|
||||
'ConversationText',
|
||||
'UserStartedSpeaking',
|
||||
'EndOfThought',
|
||||
'AgentThinking',
|
||||
'FunctionCallRequest',
|
||||
'FunctionCalling',
|
||||
'AgentStartedSpeaking',
|
||||
'AgentAudioDone',
|
||||
];
|
||||
|
||||
const expandWildcards = (events) => {
|
||||
// no-op for deepgram
|
||||
return events;
|
||||
};
|
||||
|
||||
class TaskLlmVoiceAgent_S2S extends Task {
|
||||
constructor(logger, opts, parentTask) {
|
||||
super(logger, opts, parentTask);
|
||||
this.parent = parentTask;
|
||||
|
||||
this.vendor = this.parent.vendor;
|
||||
this.model = this.parent.model;
|
||||
this.auth = this.parent.auth;
|
||||
this.connectionOptions = this.parent.connectOptions;
|
||||
|
||||
const {apiKey} = this.auth || {};
|
||||
if (!apiKey) throw new Error('auth.apiKey is required for VoiceAgent S2S');
|
||||
|
||||
this.apiKey = apiKey;
|
||||
this.authType = 'bearer';
|
||||
this.actionHook = this.data.actionHook;
|
||||
this.eventHook = this.data.eventHook;
|
||||
this.toolHook = this.data.toolHook;
|
||||
const {settingsConfiguration} = this.data.llmOptions;
|
||||
|
||||
if (typeof settingsConfiguration !== 'object') {
|
||||
throw new Error('llmOptions with an initial settingsConfiguration is required for VoiceAgent S2S');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const {audio, ...rest} = settingsConfiguration;
|
||||
const cfg = this.settingsConfiguration = rest;
|
||||
|
||||
if (!cfg.agent) throw new Error('llmOptions.settingsConfiguration.agent is required for VoiceAgent S2S');
|
||||
if (!cfg.agent.think) {
|
||||
throw new Error('llmOptions.settingsConfiguration.agent.think is required for VoiceAgent S2S');
|
||||
}
|
||||
if (!cfg.agent.think.model) {
|
||||
throw new Error('llmOptions.settingsConfiguration.agent.think.model is required for VoiceAgent S2S');
|
||||
}
|
||||
if (!cfg.agent.think.provider?.type) {
|
||||
throw new Error('llmOptions.settingsConfiguration.agent.think.provider.type is required for VoiceAgent S2S');
|
||||
}
|
||||
|
||||
this.results = {
|
||||
completionReason: 'normal conversation end'
|
||||
};
|
||||
|
||||
/**
|
||||
* only one of these will have items,
|
||||
* if includeEvents, then these are the events to include
|
||||
* if excludeEvents, then these are the events to exclude
|
||||
*/
|
||||
this.includeEvents = [];
|
||||
this.excludeEvents = [];
|
||||
|
||||
/* default to all events if user did not specify */
|
||||
this._populateEvents(this.data.events || va_server_events);
|
||||
|
||||
this.addCustomEventListener = parentTask.addCustomEventListener.bind(parentTask);
|
||||
this.removeCustomEventListeners = parentTask.removeCustomEventListeners.bind(parentTask);
|
||||
}
|
||||
|
||||
get name() { return TaskName; }
|
||||
|
||||
get host() {
|
||||
const {host} = this.connectionOptions || {};
|
||||
return host || 'agent.deepgram.com';
|
||||
}
|
||||
|
||||
get path() {
|
||||
const {path} = this.connectionOptions || {};
|
||||
if (path) return path;
|
||||
|
||||
return '/agent';
|
||||
}
|
||||
|
||||
async _api(ep, args) {
|
||||
const res = await ep.api('uuid_voice_agent_s2s', `^^|${args.join('|')}`);
|
||||
if (!res.body?.startsWith('+OK')) {
|
||||
throw new Error({args}, `Error calling uuid_voice_agent_s2s: ${JSON.stringify(res.body)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async exec(cs, {ep}) {
|
||||
await super.exec(cs);
|
||||
|
||||
await this._startListening(cs, ep);
|
||||
|
||||
await this.awaitTaskDone();
|
||||
|
||||
/* note: the parent llm verb started the span, which is why this is necessary */
|
||||
await this.parent.performAction(this.results);
|
||||
|
||||
this._unregisterHandlers();
|
||||
}
|
||||
|
||||
async kill(cs) {
|
||||
super.kill(cs);
|
||||
|
||||
this._api(cs.ep, [cs.ep.uuid, SessionDelete])
|
||||
.catch((err) => this.logger.info({err}, 'TaskLlmVoiceAgent_S2S:kill - error deleting session'));
|
||||
|
||||
this.notifyTaskDone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send function call response to the VoiceAgent server
|
||||
*/
|
||||
async processToolOutput(ep, tool_call_id, data) {
|
||||
try {
|
||||
const {data:response} = data;
|
||||
this.logger.debug({tool_call_id, response}, 'TaskLlmVoiceAgent_S2S:processToolOutput');
|
||||
|
||||
if (!response.type || response.type !== 'FunctionCallResponse') {
|
||||
this.logger.info({response},
|
||||
'TaskLlmVoiceAgent_S2S:processToolOutput - invalid tool output, must be FunctionCallResponse');
|
||||
}
|
||||
else {
|
||||
await this._api(ep, [ep.uuid, ClientEvent, JSON.stringify(response)]);
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.info({err}, 'TaskLlmVoiceAgent_S2S:processToolOutput');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a session.update to the VoiceAgent server
|
||||
* Note: creating and deleting conversation items also supported as well as interrupting the assistant
|
||||
*/
|
||||
async processLlmUpdate(ep, data, _callSid) {
|
||||
try {
|
||||
this.logger.debug({data, _callSid}, 'TaskLlmVoiceAgent_S2S:processLlmUpdate');
|
||||
|
||||
if (!data.type || ![
|
||||
'UpdateInstructions',
|
||||
'UpdateSpeak',
|
||||
'InjectAgentMessage',
|
||||
].includes(data.type)) {
|
||||
this.logger.info({data}, 'TaskLlmVoiceAgent_S2S:processLlmUpdate - invalid mid-call request');
|
||||
}
|
||||
else {
|
||||
await this._api(ep, [ep.uuid, ClientEvent, JSON.stringify(data)]);
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.info({err}, 'TaskLlmVoiceAgent_S2S:processLlmUpdate');
|
||||
}
|
||||
}
|
||||
|
||||
async _startListening(cs, ep) {
|
||||
this._registerHandlers(ep);
|
||||
|
||||
try {
|
||||
const args = [ep.uuid, 'session.create', this.host, this.path, this.authType, this.apiKey];
|
||||
await this._api(ep, args);
|
||||
} catch (err) {
|
||||
this.logger.error({err}, `TaskLlmVoiceAgent_S2S:_startListening: ${JSON.stringify(err)}`);
|
||||
this.notifyTaskDone();
|
||||
}
|
||||
}
|
||||
|
||||
async _sendClientEvent(ep, obj) {
|
||||
let ok = true;
|
||||
this.logger.debug({obj}, 'TaskLlmVoiceAgent_S2S:_sendClientEvent');
|
||||
try {
|
||||
const args = [ep.uuid, ClientEvent, JSON.stringify(obj)];
|
||||
await this._api(ep, args);
|
||||
} catch (err) {
|
||||
ok = false;
|
||||
this.logger.error({err}, 'TaskLlmVoiceAgent_S2S:_sendClientEvent - Error');
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
async _sendInitialMessage(ep) {
|
||||
if (!await this._sendClientEvent(ep, this.settingsConfiguration)) {
|
||||
this.notifyTaskDone();
|
||||
}
|
||||
}
|
||||
|
||||
_registerHandlers(ep) {
|
||||
this.addCustomEventListener(ep, LlmEvents_VoiceAgent.Connect, this._onConnect.bind(this, ep));
|
||||
this.addCustomEventListener(ep, LlmEvents_VoiceAgent.ConnectFailure, this._onConnectFailure.bind(this, ep));
|
||||
this.addCustomEventListener(ep, LlmEvents_VoiceAgent.Disconnect, this._onDisconnect.bind(this, ep));
|
||||
this.addCustomEventListener(ep, LlmEvents_VoiceAgent.ServerEvent, this._onServerEvent.bind(this, ep));
|
||||
}
|
||||
|
||||
_unregisterHandlers() {
|
||||
this.removeCustomEventListeners();
|
||||
}
|
||||
|
||||
_onError(_ep, evt) {
|
||||
this.logger.info({evt}, 'TaskLlmVoiceAgent_S2S:_onError');
|
||||
this.notifyTaskDone();
|
||||
}
|
||||
|
||||
_onConnect(ep) {
|
||||
this.logger.debug('TaskLlmVoiceAgent_S2S:_onConnect');
|
||||
this._sendInitialMessage(ep);
|
||||
}
|
||||
_onConnectFailure(_ep, evt) {
|
||||
this.logger.info(evt, 'TaskLlmVoiceAgent_S2S:_onConnectFailure');
|
||||
this.results = {completionReason: 'connection failure'};
|
||||
this.notifyTaskDone();
|
||||
}
|
||||
_onDisconnect(_ep, evt) {
|
||||
this.logger.info(evt, 'TaskLlmVoiceAgent_S2S:_onConnectFailure');
|
||||
this.results = {completionReason: 'disconnect from remote end'};
|
||||
this.notifyTaskDone();
|
||||
}
|
||||
async _onServerEvent(_ep, evt) {
|
||||
let endConversation = false;
|
||||
const type = evt.type;
|
||||
this.logger.info({evt}, 'TaskLlmVoiceAgent_S2S:_onServerEvent');
|
||||
|
||||
/* check for failures, such as rate limit exceeded, that should terminate the conversation */
|
||||
if (type === 'response.done' && evt.response.status === 'failed') {
|
||||
endConversation = true;
|
||||
this.results = {
|
||||
completionReason: 'server failure',
|
||||
error: evt.response.status_details?.error
|
||||
};
|
||||
}
|
||||
|
||||
/* server errors of some sort */
|
||||
else if (type === 'error') {
|
||||
endConversation = true;
|
||||
this.results = {
|
||||
completionReason: 'server error',
|
||||
error: evt.error
|
||||
};
|
||||
}
|
||||
|
||||
/* tool calls */
|
||||
else if (type === 'FunctionCallRequest') {
|
||||
this.logger.debug({evt}, 'TaskLlmVoiceAgent_S2S:_onServerEvent - function_call');
|
||||
if (!this.toolHook) {
|
||||
this.logger.warn({evt}, 'TaskLlmVoiceAgent_S2S:_onServerEvent - no toolHook defined!');
|
||||
}
|
||||
else {
|
||||
const {function_name:name, function_call_id:call_id} = evt;
|
||||
const args = evt.input;
|
||||
|
||||
try {
|
||||
await this.parent.sendToolHook(call_id, {name, args});
|
||||
} catch (err) {
|
||||
this.logger.info({err, evt}, 'TaskLlmVoiceAgent - error calling function');
|
||||
this.results = {
|
||||
completionReason: 'client error calling function',
|
||||
error: err
|
||||
};
|
||||
endConversation = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* check whether we should notify on this event */
|
||||
if (this.includeEvents.length > 0 ? this.includeEvents.includes(type) : !this.excludeEvents.includes(type)) {
|
||||
this.parent.sendEventHook(evt)
|
||||
.catch((err) => this.logger.info({err}, 'TaskLlmVoiceAgent_S2S:_onServerEvent - error sending event hook'));
|
||||
}
|
||||
|
||||
if (endConversation) {
|
||||
this.logger.info({results: this.results},
|
||||
'TaskLlmVoiceAgent_S2S:_onServerEvent - ending conversation due to error');
|
||||
this.notifyTaskDone();
|
||||
}
|
||||
}
|
||||
|
||||
_populateEvents(events) {
|
||||
if (events.includes('all')) {
|
||||
/* work by excluding specific events */
|
||||
const exclude = events
|
||||
.filter((evt) => evt.startsWith('-'))
|
||||
.map((evt) => evt.slice(1));
|
||||
if (exclude.length === 0) this.includeEvents = va_server_events;
|
||||
else this.excludeEvents = expandWildcards(exclude);
|
||||
}
|
||||
else {
|
||||
/* work by including specific events */
|
||||
const include = events
|
||||
.filter((evt) => !evt.startsWith('-'));
|
||||
this.includeEvents = expandWildcards(include);
|
||||
}
|
||||
|
||||
this.logger.debug({
|
||||
includeEvents: this.includeEvents,
|
||||
excludeEvents: this.excludeEvents
|
||||
}, 'TaskLlmVoiceAgent_S2S:_populateEvents');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TaskLlmVoiceAgent_S2S;
|
||||
@@ -1,4 +1,3 @@
|
||||
const assert = require('assert');
|
||||
const TtsTask = require('./tts-task');
|
||||
const {TaskName, TaskPreconditions} = require('../utils/constants');
|
||||
const pollySSMLSplit = require('polly-ssml-split');
|
||||
@@ -36,40 +35,24 @@ class TaskSay extends TtsTask {
|
||||
super(logger, opts, parentTask);
|
||||
this.preconditions = TaskPreconditions.Endpoint;
|
||||
|
||||
assert.ok((typeof this.data.text === 'string' || Array.isArray(this.data.text)) || this.data.stream === true,
|
||||
'Say: either text or stream:true is required');
|
||||
this.text = (Array.isArray(this.data.text) ? this.data.text : [this.data.text])
|
||||
.map((t) => breakLengthyTextIfNeeded(this.logger, t))
|
||||
.flat();
|
||||
|
||||
|
||||
if (this.data.stream === true) {
|
||||
this._isStreamingTts = true;
|
||||
this.closeOnStreamEmpty = this.data.closeOnStreamEmpty !== false;
|
||||
}
|
||||
else {
|
||||
this._isStreamingTts = false;
|
||||
this.text = (Array.isArray(this.data.text) ? this.data.text : [this.data.text])
|
||||
.map((t) => breakLengthyTextIfNeeded(this.logger, t))
|
||||
.flat();
|
||||
|
||||
this.loop = this.data.loop || 1;
|
||||
this.isHandledByPrimaryProvider = true;
|
||||
}
|
||||
this.loop = this.data.loop || 1;
|
||||
this.isHandledByPrimaryProvider = true;
|
||||
}
|
||||
|
||||
get name() { return TaskName.Say; }
|
||||
|
||||
get summary() {
|
||||
if (this.isStreamingTts) return `${this.name} streaming`;
|
||||
else {
|
||||
for (let i = 0; i < this.text.length; i++) {
|
||||
if (this.text[i].startsWith('silence_stream')) continue;
|
||||
return `${this.name}{text=${this.text[i].slice(0, 15)}${this.text[i].length > 15 ? '...' : ''}}`;
|
||||
}
|
||||
return `${this.name}{${this.text[0]}}`;
|
||||
for (let i = 0; i < this.text.length; i++) {
|
||||
if (this.text[i].startsWith('silence_stream')) continue;
|
||||
return `${this.name}{text=${this.text[i].slice(0, 15)}${this.text[i].length > 15 ? '...' : ''}}`;
|
||||
}
|
||||
return `${this.name}{${this.text[0]}}`;
|
||||
}
|
||||
|
||||
get isStreamingTts() { return this._isStreamingTts; }
|
||||
|
||||
_validateURL(urlString) {
|
||||
try {
|
||||
new URL(urlString);
|
||||
@@ -80,19 +63,14 @@ class TaskSay extends TtsTask {
|
||||
}
|
||||
|
||||
async exec(cs, obj) {
|
||||
if (this.isStreamingTts && !cs.appIsUsingWebsockets) {
|
||||
throw new Error('Say: streaming say verb requires applications to use the websocket API');
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.isStreamingTts) await this.handlingStreaming(cs, obj);
|
||||
else await this.handling(cs, obj);
|
||||
await this.handling(cs, obj);
|
||||
this.emit('playDone');
|
||||
} catch (error) {
|
||||
if (error instanceof SpeechCredentialError) {
|
||||
// if say failed due to speech credentials, alarm is writtern and error notification is sent
|
||||
// finished this say to move to next task.
|
||||
this.logger.info({error}, 'Say failed due to SpeechCredentialError, finished!');
|
||||
this.logger.info('Say failed due to SpeechCredentialError, finished!');
|
||||
this.emit('playDone');
|
||||
return;
|
||||
}
|
||||
@@ -100,35 +78,6 @@ class TaskSay extends TtsTask {
|
||||
}
|
||||
}
|
||||
|
||||
async handlingStreaming(cs, {ep}) {
|
||||
const {vendor, language, voice, label} = this.getTtsVendorData(cs);
|
||||
const credentials = cs.getSpeechCredentials(vendor, 'tts', label);
|
||||
if (!credentials) {
|
||||
throw new SpeechCredentialError(
|
||||
`No text-to-speech service credentials for ${vendor} with labels: ${label} have been configured`);
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
await this.setTtsStreamingChannelVars(vendor, language, voice, credentials, ep);
|
||||
|
||||
await cs.startTtsStream();
|
||||
|
||||
cs.requestor?.request('tts:streaming-event', '/streaming-event', {event_type: 'stream_open'})
|
||||
.catch((err) => this.logger.info({err}, 'TaskSay:handlingStreaming - Error sending'));
|
||||
} catch (err) {
|
||||
this.logger.info({err}, 'TaskSay:handlingStreaming - Error setting channel vars');
|
||||
cs.requestor?.request('tts:streaming-event', '/streaming-event', {event_type: 'stream_closed'})
|
||||
.catch((err) => this.logger.info({err}, 'TaskSay:handlingStreaming - Error sending'));
|
||||
|
||||
//TODO: send tts:streaming-event with error?
|
||||
this.notifyTaskDone();
|
||||
}
|
||||
|
||||
await this.awaitTaskDone();
|
||||
this.logger.info('TaskSay:handlingStreaming - done');
|
||||
}
|
||||
|
||||
async handling(cs, {ep}) {
|
||||
const {srf, accountSid:account_sid, callSid:target_sid} = cs;
|
||||
const {writeAlerts, AlertType} = srf.locals;
|
||||
@@ -147,7 +96,7 @@ class TaskSay extends TtsTask {
|
||||
let voice = this.synthesizer.voice && this.synthesizer.voice !== 'default' ?
|
||||
this.synthesizer.voice :
|
||||
cs.speechSynthesisVoice;
|
||||
let label = this.taskIncludeSynthesizer ? this.synthesizer.label : cs.speechSynthesisLabel;
|
||||
let label = this.taskInlcudeSynthesizer ? this.synthesizer.label : cs.speechSynthesisLabel;
|
||||
|
||||
const fallbackVendor = this.synthesizer.fallbackVendor && this.synthesizer.fallbackVendor !== 'default' ?
|
||||
this.synthesizer.fallbackVendor :
|
||||
@@ -158,7 +107,7 @@ class TaskSay extends TtsTask {
|
||||
const fallbackVoice = this.synthesizer.fallbackVoice && this.synthesizer.fallbackVoice !== 'default' ?
|
||||
this.synthesizer.fallbackVoice :
|
||||
cs.fallbackSpeechSynthesisVoice;
|
||||
const fallbackLabel = this.taskIncludeSynthesizer ?
|
||||
const fallbackLabel = this.taskInlcudeSynthesizer ?
|
||||
this.synthesizer.fallbackLabel : cs.fallbackSpeechSynthesisLabel;
|
||||
|
||||
if (cs.hasFallbackTts) {
|
||||
@@ -304,7 +253,6 @@ class TaskSay extends TtsTask {
|
||||
this._playResolve = null;
|
||||
}
|
||||
}
|
||||
this.notifyTaskDone();
|
||||
}
|
||||
|
||||
_addStreamingTtsAttributes(span, evt) {
|
||||
@@ -315,7 +263,6 @@ class TaskSay extends TtsTask {
|
||||
.replace('whisper_', 'whisper.')
|
||||
.replace('deepgram_', 'deepgram.')
|
||||
.replace('playht_', 'playht.')
|
||||
.replace('cartesia_', 'cartesia.')
|
||||
.replace('rimelabs_', 'rimelabs.')
|
||||
.replace('verbio_', 'verbio.')
|
||||
.replace('elevenlabs_', 'elevenlabs.');
|
||||
@@ -326,13 +273,6 @@ class TaskSay extends TtsTask {
|
||||
delete attrs['cache_filename']; //no value in adding this to the span
|
||||
span.setAttributes(attrs);
|
||||
}
|
||||
|
||||
notifyTtsStreamIsEmpty() {
|
||||
if (this.isStreamingTts && this.closeOnStreamEmpty) {
|
||||
this.logger.info('TaskSay:notifyTtsStreamIsEmpty - stream is empty, killing task');
|
||||
this.notifyTaskDone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const spanMapping = {
|
||||
@@ -369,11 +309,6 @@ const spanMapping = {
|
||||
'playht.name_lookup_time_ms': 'name_lookup_ms',
|
||||
'playht.connect_time_ms': 'connect_ms',
|
||||
'playht.final_response_time_ms': 'final_response_ms',
|
||||
// Cartesia
|
||||
'cartesia.request_id': 'cartesia.req_id',
|
||||
'cartesia.name_lookup_time_ms': 'name_lookup_ms',
|
||||
'cartesia.connect_time_ms': 'connect_ms',
|
||||
'cartesia.final_response_time_ms': 'final_response_ms',
|
||||
// Rimelabs
|
||||
'rimelabs.name_lookup_time_ms': 'name_lookup_ms',
|
||||
'rimelabs.connect_time_ms': 'connect_ms',
|
||||
|
||||
@@ -13,11 +13,11 @@ class TtsTask extends Task {
|
||||
|
||||
this.earlyMedia = this.data.earlyMedia === true || (parentTask && parentTask.earlyMedia);
|
||||
/**
|
||||
* Task use taskIncludeSynthesizer to identify
|
||||
* if taskIncludeSynthesizer === true, use label from verb.synthesizer, even it's empty
|
||||
* if taskIncludeSynthesizer === false, use label from application.synthesizer
|
||||
* Task use taskInlcudeSynthesizer to identify
|
||||
* if taskInlcudeSynthesizer === true, use label from verb.synthesizer, even it's empty
|
||||
* if taskInlcudeSynthesizer === false, use label from application.synthesizer
|
||||
*/
|
||||
this.taskIncludeSynthesizer = !!this.data.synthesizer;
|
||||
this.taskInlcudeSynthesizer = !!this.data.synthesizer;
|
||||
this.synthesizer = this.data.synthesizer || {};
|
||||
this.disableTtsCache = this.data.disableTtsCache;
|
||||
this.options = this.synthesizer.options || {};
|
||||
@@ -44,61 +44,6 @@ class TtsTask extends Task {
|
||||
}
|
||||
}
|
||||
|
||||
getTtsVendorData(cs) {
|
||||
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 label = this.taskIncludeSynthesizer ? this.synthesizer.label : cs.speechSynthesisLabel;
|
||||
return {vendor, language, voice, label};
|
||||
}
|
||||
|
||||
async setTtsStreamingChannelVars(vendor, language, voice, credentials, ep) {
|
||||
const {api_key, model_id, cartesia_model_id, cartesia_voice_id} = credentials;
|
||||
const {stability, similarity_boost, use_speaker_boost, style} = this.options;
|
||||
let obj;
|
||||
|
||||
switch (vendor) {
|
||||
case 'deepgram':
|
||||
obj = {
|
||||
DEEPGRAM_API_KEY: api_key,
|
||||
DEEPGRAM_TTS_STREAMING_MODEL: voice
|
||||
};
|
||||
break;
|
||||
case 'cartesia':
|
||||
obj = {
|
||||
CARTESIA_API_KEY: api_key,
|
||||
CARTESIA_TTS_STREAMING_MODEL_ID: cartesia_model_id,
|
||||
CARTESIA_TTS_STREAMING_VOICE_ID: cartesia_voice_id,
|
||||
CARTESIA_TTS_STREAMING_LANGUAGE: language || 'en',
|
||||
};
|
||||
break;
|
||||
case 'elevenlabs':
|
||||
obj = {
|
||||
ELEVENLABS_API_KEY: api_key,
|
||||
ELEVENLABS_TTS_STREAMING_MODEL_ID: model_id,
|
||||
ELEVENLABS_TTS_STREAMING_VOICE_ID: voice,
|
||||
// 20/12/2024 - only eleven_turbo_v2_5 support multiple language
|
||||
...(['eleven_turbo_v2_5'].includes(model_id) && {ELEVENLABS_TTS_STREAMING_LANGUAGE: language}),
|
||||
...(stability && {ELEVENLABS_TTS_STREAMING_VOICE_SETTINGS_STABILITY: stability}),
|
||||
...(similarity_boost && {ELEVENLABS_TTS_STREAMING_VOICE_SETTINGS_SIMILARITY_BOOST: similarity_boost}),
|
||||
...(use_speaker_boost && {ELEVENLABS_TTS_STREAMING_VOICE_SETTINGS_USE_SPEAKER_BOOST: use_speaker_boost}),
|
||||
...(style && {ELEVENLABS_TTS_STREAMING_VOICE_SETTINGS_STYLE: style})
|
||||
};
|
||||
break;
|
||||
default:
|
||||
throw new Error(`vendor ${vendor} is not supported for tts streaming yet`);
|
||||
}
|
||||
this.logger.info({vendor, credentials, obj}, 'setTtsStreamingChannelVars');
|
||||
|
||||
await ep.set(obj);
|
||||
}
|
||||
|
||||
async _synthesizeWithSpecificVendor(cs, ep, {vendor, language, voice, label, preCache = false}) {
|
||||
const {srf, accountSid:account_sid} = cs;
|
||||
const {writeAlerts, AlertType, stats} = srf.locals;
|
||||
|
||||
@@ -153,7 +153,7 @@ class Amd extends Emitter {
|
||||
const wordCount = t.alternatives[0].transcript.split(' ').length;
|
||||
const final = t.is_final;
|
||||
|
||||
const foundHint = hints.find((h) => t.alternatives[0].transcript.toLowerCase().includes(h.toLowerCase()));
|
||||
const foundHint = hints.find((h) => t.alternatives[0].transcript.includes(h));
|
||||
if (foundHint) {
|
||||
/* we detected a common voice mail greeting */
|
||||
this.logger.debug(`Amd:evaluateTranscription: found hint ${foundHint}`);
|
||||
|
||||
@@ -46,9 +46,6 @@ class BackgroundTaskManager extends Emitter {
|
||||
case 'transcribe':
|
||||
task = await this._initTranscribe(opts);
|
||||
break;
|
||||
case 'ttsStream':
|
||||
task = await this._initTtsStream(opts);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -176,25 +173,6 @@ class BackgroundTaskManager extends Emitter {
|
||||
return task;
|
||||
}
|
||||
|
||||
// Initiate Tts Stream
|
||||
async _initTtsStream(opts) {
|
||||
let task;
|
||||
try {
|
||||
const t = normalizeJambones(this.logger, [opts]);
|
||||
task = makeTask(this.logger, t[0]);
|
||||
const resources = await this.cs._evaluatePreconditions(task);
|
||||
const {span, ctx} = this.rootSpan.startChildSpan(`background-ttsStream:${task.summary}`);
|
||||
task.span = span;
|
||||
task.ctx = ctx;
|
||||
task.exec(this.cs, resources)
|
||||
.then(this._taskCompleted.bind(this, 'ttsStream', task))
|
||||
.catch(this._taskError.bind(this, 'ttsStream', task));
|
||||
} catch (err) {
|
||||
this.logger.info(err, 'BackgroundTaskManager:_initTtsStream - Error creating ttsStream task');
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
_taskCompleted(type, task) {
|
||||
this.logger.debug({type, task}, `BackgroundTaskManager:_taskCompleted: task completed, sticky: ${task.sticky}`);
|
||||
task.removeAllListeners();
|
||||
|
||||
@@ -175,13 +175,6 @@
|
||||
"Disconnect": "openai_s2s::disconnect",
|
||||
"ServerEvent": "openai_s2s::server_event"
|
||||
},
|
||||
"LlmEvents_VoiceAgent": {
|
||||
"Error": "error",
|
||||
"Connect": "voice_agent_s2s::connect",
|
||||
"ConnectFailure": "voice_agent_s2s::connect_failed",
|
||||
"Disconnect": "voice_agent_s2s::disconnect",
|
||||
"ServerEvent": "voice_agent_s2s::server_event"
|
||||
},
|
||||
"QueueResults": {
|
||||
"Bridged": "bridged",
|
||||
"Error": "error",
|
||||
@@ -210,8 +203,6 @@
|
||||
"verb:status",
|
||||
"llm:event",
|
||||
"llm:tool-call",
|
||||
"tts:tokens-result",
|
||||
"tts:streaming-event",
|
||||
"jambonz:error"
|
||||
],
|
||||
"RecordState": {
|
||||
@@ -235,33 +226,6 @@
|
||||
"PartialMedia": "partial-media",
|
||||
"FullMedia": "full-media"
|
||||
},
|
||||
"DeepgramTtsStreamingEvents": {
|
||||
"Empty": "deepgram_tts_streaming::empty",
|
||||
"ConnectFailure": "deepgram_tts_streaming::connect_failed",
|
||||
"Connect": "deepgram_tts_streaming::connect"
|
||||
},
|
||||
"CartesiaTtsStreamingEvents": {
|
||||
"Empty": "cartesia_tts_streaming::empty",
|
||||
"ConnectFailure": "cartesia_tts_streaming::connect_failed",
|
||||
"Connect": "cartesia_tts_streaming::connect"
|
||||
},
|
||||
"ElevenlabsTtsStreamingEvents": {
|
||||
"Empty": "elevenlabs_tts_streaming::empty",
|
||||
"ConnectFailure": "elevenlabs_tts_streaming::connect_failed",
|
||||
"Connect": "elevenlabs_tts_streaming::connect"
|
||||
},
|
||||
"TtsStreamingEvents": {
|
||||
"Empty": "tts_streaming::empty",
|
||||
"Pause": "tts_streaming::pause",
|
||||
"Resume": "tts_streaming::resume",
|
||||
"ConnectFailure": "tts_streaming::connect_failed"
|
||||
},
|
||||
"TtsStreamingConnectionStatus": {
|
||||
"NotConnected": "not_connected",
|
||||
"Connected": "connected",
|
||||
"Connecting": "connecting",
|
||||
"Failed": "failed"
|
||||
},
|
||||
"MAX_SIMRINGS": 10,
|
||||
"BONG_TONE": "tone_stream://v=-7;%(100,0,941.0,1477.0);v=-7;>=2;+=.1;%(1400,0,350,440)",
|
||||
"FS_UUID_SET_NAME": "fsUUIDs",
|
||||
|
||||
@@ -105,13 +105,6 @@ const speechMapper = (cred) => {
|
||||
obj.voice_engine = o.voice_engine;
|
||||
obj.options = o.options;
|
||||
}
|
||||
else if ('cartesia' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = o.api_key;
|
||||
obj.model_id = o.model_id;
|
||||
obj.embedding = o.embedding;
|
||||
obj.options = o.options;
|
||||
}
|
||||
else if ('rimelabs' === obj.vendor) {
|
||||
const o = JSON.parse(decrypt(credential));
|
||||
obj.api_key = o.api_key;
|
||||
|
||||
@@ -1,327 +0,0 @@
|
||||
const Emitter = require('events');
|
||||
const assert = require('assert');
|
||||
const {
|
||||
TtsStreamingEvents,
|
||||
TtsStreamingConnectionStatus
|
||||
} = require('../utils/constants');
|
||||
const FEED_INTERVAL = 2000;
|
||||
const MAX_CHUNK_SIZE = 1800;
|
||||
const HIGH_WATER_BUFFER_SIZE = 5000;
|
||||
const LOW_WATER_BUFFER_SIZE = 1000;
|
||||
const MIN_INITIAL_WORDS = 4;
|
||||
|
||||
const findSentenceBoundary = (text, limit) => {
|
||||
const sentenceEndRegex = /[.!?](?=\s|$)/g;
|
||||
let lastSentenceBoundary = -1;
|
||||
let match;
|
||||
|
||||
while ((match = sentenceEndRegex.exec(text)) && match.index < limit) {
|
||||
/* Ensure it's not a decimal point (e.g., "3.14") */
|
||||
if (match.index === 0 || !/\d$/.test(text[match.index - 1])) {
|
||||
lastSentenceBoundary = match.index + 1; // Include the punctuation
|
||||
}
|
||||
}
|
||||
return lastSentenceBoundary;
|
||||
};
|
||||
|
||||
const findWordBoundary = (text, limit) => {
|
||||
const wordBoundaryRegex = /\s+/g;
|
||||
let lastWordBoundary = -1;
|
||||
let match;
|
||||
|
||||
while ((match = wordBoundaryRegex.exec(text)) && match.index < limit) {
|
||||
lastWordBoundary = match.index;
|
||||
}
|
||||
return lastWordBoundary;
|
||||
};
|
||||
|
||||
class TtsStreamingBuffer extends Emitter {
|
||||
constructor(cs) {
|
||||
super();
|
||||
this.cs = cs;
|
||||
this.logger = cs.logger;
|
||||
|
||||
this.tokens = '';
|
||||
this.eventHandlers = [];
|
||||
this._isFull = false;
|
||||
this._connectionStatus = TtsStreamingConnectionStatus.NotConnected;
|
||||
this._flushPending = false;
|
||||
this._countSendsInThisTurn = 0;
|
||||
}
|
||||
|
||||
get isEmpty() {
|
||||
return this.tokens.length === 0;
|
||||
}
|
||||
|
||||
get isFull() {
|
||||
return this._isFull;
|
||||
}
|
||||
|
||||
get size() {
|
||||
return this.tokens.length;
|
||||
}
|
||||
|
||||
get ep() {
|
||||
return this.cs?.ep;
|
||||
}
|
||||
|
||||
async start() {
|
||||
assert.ok(
|
||||
this._connectionStatus === TtsStreamingConnectionStatus.NotConnected,
|
||||
'TtsStreamingBuffer:start already started, or has failed');
|
||||
|
||||
this.vendor = this.cs.getTsStreamingVendor();
|
||||
if (!this.vendor) {
|
||||
this.logger.info('TtsStreamingBuffer:start No TTS streaming vendor configured');
|
||||
throw new Error('No TTS streaming vendor configured');
|
||||
}
|
||||
|
||||
this.logger.info(`TtsStreamingBuffer:start Connecting to TTS streaming with vendor ${this.vendor}`);
|
||||
|
||||
this._connectionStatus = TtsStreamingConnectionStatus.Connecting;
|
||||
try {
|
||||
if (this.eventHandlers.length === 0) this._initHandlers(this.ep);
|
||||
await this._api(this.ep, [this.ep.uuid, 'connect']);
|
||||
} catch (err) {
|
||||
this.logger.info({err}, 'TtsStreamingBuffer:start Error connecting to TTS streaming');
|
||||
this._connectionStatus = TtsStreamingConnectionStatus.Failed;
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
clearTimeout(this.timer);
|
||||
this.removeCustomEventListeners();
|
||||
if (this.ep) {
|
||||
this._api(this.ep, [this.ep.uuid, 'close'])
|
||||
.catch((err) => this.logger.info({err}, 'TtsStreamingBuffer:kill Error closing TTS streaming'));
|
||||
}
|
||||
this.timer = null;
|
||||
this.tokens = '';
|
||||
this._connectionStatus = TtsStreamingConnectionStatus.NotConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add tokens to the buffer and start feeding them to the endpoint if necessary.
|
||||
*/
|
||||
async bufferTokens(tokens) {
|
||||
|
||||
if (this._connectionStatus === TtsStreamingConnectionStatus.Failed) {
|
||||
this.logger.info('TtsStreamingBuffer:bufferTokens TTS streaming connection failed, rejecting request');
|
||||
return {status: 'failed', reason: `connection to ${this.vendor} failed`};
|
||||
}
|
||||
|
||||
const starting = this.tokens === '';
|
||||
const displayedTokens = tokens.length <= 40 ? tokens : tokens.substring(0, 40);
|
||||
const totalLength = tokens.length;
|
||||
|
||||
/* if we crossed the high water mark, reject the request */
|
||||
if (this.tokens.length + totalLength > HIGH_WATER_BUFFER_SIZE) {
|
||||
this.logger.info(
|
||||
`TtsStreamingBuffer:bufferTokensTTS buffer is full, rejecting request to buffer ${totalLength} tokens`);
|
||||
|
||||
if (!this._isFull) {
|
||||
this._isFull = true;
|
||||
this.emit(TtsStreamingEvents.Pause);
|
||||
}
|
||||
return {status: 'failed', reason: 'full'};
|
||||
}
|
||||
|
||||
this.logger.debug(
|
||||
`TtsStreamingBuffer:bufferTokens "${displayedTokens}" (length: ${totalLength}), starting? ${starting}`
|
||||
);
|
||||
this.tokens += (tokens || '');
|
||||
|
||||
const leftoverTokens = await this._feedTokens();
|
||||
|
||||
/* do we need to start a timer to periodically feed tokens to the endpoint? */
|
||||
if (starting && leftoverTokens > 0) {
|
||||
assert(!this.timer);
|
||||
this.timer = setInterval(async() => {
|
||||
const remaining = await this._feedTokens();
|
||||
if (remaining === 0) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}, FEED_INTERVAL);
|
||||
}
|
||||
|
||||
return {status: 'ok'};
|
||||
}
|
||||
|
||||
flush() {
|
||||
this.logger.debug('TtsStreamingBuffer:flush');
|
||||
if (this._connectionStatus === TtsStreamingConnectionStatus.Connecting) {
|
||||
this.logger.debug('TtsStreamingBuffer:flush TTS stream is not quite ready - wait for connect');
|
||||
this._flushPending = true;
|
||||
return;
|
||||
}
|
||||
else if (this._connectionStatus === TtsStreamingConnectionStatus.Connected) {
|
||||
this._countSendsInThisTurn = 0;
|
||||
this._api(this.ep, [this.ep.uuid, 'flush'])
|
||||
.catch((err) => this.logger.info({err},
|
||||
`TtsStreamingBuffer:flush Error flushing TTS streaming: ${JSON.stringify(err)}`));
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.logger.debug('TtsStreamingBuffer:clear');
|
||||
|
||||
if (this._connectionStatus !== TtsStreamingConnectionStatus.Connected) return;
|
||||
clearTimeout(this.timer);
|
||||
this._api(this.ep, [this.ep.uuid, 'clear'])
|
||||
.catch((err) => this.logger.info({err}, 'TtsStreamingBuffer:clear Error clearing TTS streaming'));
|
||||
this.tokens = '';
|
||||
this.timer = null;
|
||||
this._isFull = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the next chunk of tokens to the endpoint (max 2000 chars)
|
||||
* Return the number of tokens left in the buffer.
|
||||
*/
|
||||
async _feedTokens() {
|
||||
this.logger.debug({tokens: this.tokens}, '_feedTokens');
|
||||
|
||||
try {
|
||||
if (!this.cs.isTtsStreamOpen || !this.ep || !this.tokens) {
|
||||
this.logger.debug('TTS stream is not open or no tokens to send');
|
||||
return this.tokens?.length || 0;
|
||||
}
|
||||
|
||||
if (this._connectionStatus === TtsStreamingConnectionStatus.NotConnected ||
|
||||
this._connectionStatus === TtsStreamingConnectionStatus.Failed) {
|
||||
this.logger.debug('TtsStreamingBuffer:_feedTokens TTS stream is not connected');
|
||||
return this.tokens.length;
|
||||
}
|
||||
|
||||
if (this._connectionStatus === TtsStreamingConnectionStatus.Connecting) {
|
||||
this.logger.debug('TtsStreamingBuffer:_feedTokens TTS stream is not ready, waiting for connect');
|
||||
return this.tokens.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rules:
|
||||
* 1. If this is our first send, we must have at least N words
|
||||
* 2. Otherwise, must EITHER have N words OR be the ending of a sentence
|
||||
*
|
||||
* When sending, send the max size possible, capped at a limit to avoid overwhelming the server.
|
||||
*/
|
||||
|
||||
/* must have at least N words, or be the ending of a sentence */
|
||||
const words = this.tokens.split(' ').length;
|
||||
if (words < MIN_INITIAL_WORDS) {
|
||||
const endsWithPunctuation = /[.!?]$/.test(this.tokens);
|
||||
if (!endsWithPunctuation || this._countSendsInThisTurn === 0) {
|
||||
this.logger.debug(`TtsStreamingBuffer:_feedTokens: only ${words} words to send, waiting for more`);
|
||||
return this.tokens.length;
|
||||
}
|
||||
}
|
||||
|
||||
const limit = Math.min(MAX_CHUNK_SIZE, this.tokens.length);
|
||||
let chunkEnd = findSentenceBoundary(this.tokens, limit);
|
||||
|
||||
if (chunkEnd === -1) {
|
||||
this.logger.debug('TtsStreamingBuffer:_feedTokens: no sentence boundary found, look for word boundary');
|
||||
chunkEnd = findWordBoundary(this.tokens, limit);
|
||||
}
|
||||
|
||||
if (chunkEnd === -1) {
|
||||
chunkEnd = limit;
|
||||
}
|
||||
|
||||
const chunk = this.tokens.slice(0, chunkEnd);
|
||||
this.tokens = this.tokens.slice(chunkEnd); // Remove sent chunk
|
||||
|
||||
/* freeswitch looks for sequence of 2 newlines to determine end of message, so insert a space */
|
||||
const modifiedChunk = chunk.replace(/\n\n/g, '\n \n');
|
||||
|
||||
if (modifiedChunk.length > 0) {
|
||||
try {
|
||||
this._countSendsInThisTurn++;
|
||||
this.logger.debug({tokens: modifiedChunk},
|
||||
`TtsStreamingBuffer:_feedTokens: sending tokens, in send#${this._countSendsInThisTurn}`);
|
||||
await this._api(this.ep, [this.ep.uuid, 'send', modifiedChunk]);
|
||||
} catch (err) {
|
||||
this.logger.info({err}, 'TtsStreamingBuffer:_feedTokens Error sending TTS chunk');
|
||||
}
|
||||
|
||||
this.logger.debug(`TtsStreamingBuffer:_feedTokens: sent ${chunk.length}, remaining: ${this.tokens.length}`);
|
||||
|
||||
if (this.isFull && this.tokens.length <= LOW_WATER_BUFFER_SIZE) {
|
||||
this.logger.info('TtsStreamingBuffer:_feedTokens TTS streaming buffer is no longer full');
|
||||
this._isFull = false;
|
||||
this.emit(TtsStreamingEvents.Resume);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.info({err}, 'TtsStreamingBuffer:_feedTokens Error sending TTS chunk');
|
||||
this.tokens = '';
|
||||
}
|
||||
|
||||
if (0 === this.tokens.length && this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
|
||||
return this.tokens.length;
|
||||
}
|
||||
|
||||
async _api(ep, args) {
|
||||
const apiCmd = `uuid_${this.vendor}_tts_streaming`;
|
||||
const res = await ep.api(apiCmd, `^^|${args.join('|')}`);
|
||||
if (!res.body?.startsWith('+OK')) {
|
||||
throw new Error({args}, `Error calling ${apiCmd}: ${res.body}`);
|
||||
}
|
||||
}
|
||||
|
||||
_onConnectFailure(vendor) {
|
||||
this.logger.info(`streaming tts connection failed to ${vendor}`);
|
||||
this._connectionStatus = TtsStreamingConnectionStatus.Failed;
|
||||
this.tokens = '';
|
||||
this.emit(TtsStreamingEvents.ConnectFailure, {vendor});
|
||||
}
|
||||
|
||||
async _onConnect(vendor) {
|
||||
this.logger.info(`streaming tts connection made to ${vendor}`);
|
||||
this._connectionStatus = TtsStreamingConnectionStatus.Connected;
|
||||
if (this.tokens.length > 0) {
|
||||
await this._feedTokens();
|
||||
}
|
||||
if (this._flushPending) {
|
||||
this.flush();
|
||||
this._flushPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
_onTtsEmpty(vendor) {
|
||||
this.emit(TtsStreamingEvents.Empty, {vendor});
|
||||
}
|
||||
|
||||
addCustomEventListener(ep, event, handler) {
|
||||
this.eventHandlers.push({ep, event, handler});
|
||||
ep.addCustomEventListener(event, handler);
|
||||
}
|
||||
|
||||
removeCustomEventListeners() {
|
||||
this.eventHandlers.forEach((h) => h.ep.removeCustomEventListener(h.event, h.handler));
|
||||
}
|
||||
|
||||
_initHandlers(ep) {
|
||||
[
|
||||
// DH: add other vendors here as modules are added
|
||||
'deepgram',
|
||||
'cartesia',
|
||||
'elevenlabs'
|
||||
].forEach((vendor) => {
|
||||
const eventClassName = `${vendor.charAt(0).toUpperCase() + vendor.slice(1)}TtsStreamingEvents`;
|
||||
const eventClass = require('../utils/constants')[eventClassName];
|
||||
if (!eventClass) throw new Error(`Event class for vendor ${vendor} not found`);
|
||||
|
||||
this.addCustomEventListener(ep, eventClass.Connect, this._onConnect.bind(this, vendor));
|
||||
this.addCustomEventListener(ep, eventClass.ConnectFailure, this._onConnectFailure.bind(this, vendor));
|
||||
this.addCustomEventListener(ep, eventClass.Empty, this._onTtsEmpty.bind(this, vendor));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TtsStreamingBuffer;
|
||||
@@ -12,20 +12,6 @@ const {
|
||||
JAMBONES_WS_MAX_PAYLOAD,
|
||||
HTTP_USER_AGENT_HEADER
|
||||
} = require('../config');
|
||||
const MTYPE_WANTS_ACK = [
|
||||
'call:status',
|
||||
'verb:status',
|
||||
'jambonz:error',
|
||||
'llm:event',
|
||||
'llm:tool-call',
|
||||
'tts:streaming-event',
|
||||
'tts:tokens-result',
|
||||
];
|
||||
const MTYPE_NO_DATA = [
|
||||
'llm:tool-output',
|
||||
'tts:flush',
|
||||
'tts:clear'
|
||||
];
|
||||
|
||||
class WsRequestor extends BaseRequestor {
|
||||
constructor(logger, account_sid, hook, secret) {
|
||||
@@ -58,7 +44,7 @@ class WsRequestor extends BaseRequestor {
|
||||
async request(type, hook, params, httpHeaders = {}) {
|
||||
assert(HookMsgTypes.includes(type));
|
||||
const url = hook.url || hook;
|
||||
const wantsAck = !MTYPE_WANTS_ACK.includes(type);
|
||||
const wantsAck = !['call:status', 'verb:status', 'jambonz:error', 'llm:event', 'llm:tool-call'].includes(type);
|
||||
|
||||
if (this.maliciousClient) {
|
||||
this.logger.info({url: this.url}, 'WsRequestor:request - discarding msg to malicious client');
|
||||
@@ -422,7 +408,7 @@ class WsRequestor extends BaseRequestor {
|
||||
|
||||
case 'command':
|
||||
assert.ok(command, 'command property not supplied');
|
||||
assert.ok(data || MTYPE_NO_DATA.includes(command), 'data property not supplied');
|
||||
assert.ok(data || command === 'llm:tool-output', 'data property not supplied');
|
||||
this._recvCommand(msgid, command, call_sid, queueCommand, tool_call_id, data);
|
||||
break;
|
||||
|
||||
|
||||
245
package-lock.json
generated
245
package-lock.json
generated
@@ -15,9 +15,9 @@
|
||||
"@jambonz/http-health-check": "^0.0.1",
|
||||
"@jambonz/mw-registrar": "^0.2.7",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.8",
|
||||
"@jambonz/speech-utils": "^0.2.1",
|
||||
"@jambonz/speech-utils": "^0.1.22",
|
||||
"@jambonz/stats-collector": "^0.1.10",
|
||||
"@jambonz/time-series": "^0.2.13",
|
||||
"@jambonz/time-series": "^0.2.12",
|
||||
"@jambonz/verb-specifications": "^0.0.90",
|
||||
"@opentelemetry/api": "^1.8.0",
|
||||
"@opentelemetry/exporter-jaeger": "^1.23.0",
|
||||
@@ -33,7 +33,7 @@
|
||||
"deepcopy": "^2.1.0",
|
||||
"drachtio-fsmrf": "^3.0.46",
|
||||
"drachtio-srf": "^4.5.35",
|
||||
"express": "^4.19.2",
|
||||
"express": "^4.21.2",
|
||||
"express-validator": "^7.0.1",
|
||||
"moment": "^2.30.1",
|
||||
"parse-url": "^9.2.0",
|
||||
@@ -1379,95 +1379,6 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cartesia/cartesia-js": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@cartesia/cartesia-js/-/cartesia-js-2.1.1.tgz",
|
||||
"integrity": "sha512-Kwcrxtay6G3hwFGyq7cwB00d+diABhi2/5Mb4vytmN+QqBgTAVYgwFweGnLDL9Ci9zFttgLE1rW8u+EA8U5P9A==",
|
||||
"dependencies": {
|
||||
"emittery": "^0.13.1",
|
||||
"form-data": "^4.0.0",
|
||||
"form-data-encoder": "^4.0.2",
|
||||
"formdata-node": "^6.0.3",
|
||||
"human-id": "^4.1.1",
|
||||
"node-fetch": "2.7.0",
|
||||
"qs": "6.11.2",
|
||||
"readable-stream": "^4.5.2",
|
||||
"url-join": "4.0.1",
|
||||
"ws": "^8.15.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@cartesia/cartesia-js/node_modules/form-data": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
|
||||
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/@cartesia/cartesia-js/node_modules/form-data-encoder": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.0.2.tgz",
|
||||
"integrity": "sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@cartesia/cartesia-js/node_modules/formdata-node": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-6.0.3.tgz",
|
||||
"integrity": "sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/@cartesia/cartesia-js/node_modules/process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cartesia/cartesia-js/node_modules/qs": {
|
||||
"version": "6.11.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
|
||||
"integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/@cartesia/cartesia-js/node_modules/readable-stream": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz",
|
||||
"integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"abort-controller": "^3.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"events": "^3.3.0",
|
||||
"process": "^0.11.10",
|
||||
"string_decoder": "^1.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/eslintrc": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
|
||||
@@ -1631,14 +1542,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jambonz/speech-utils": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.2.1.tgz",
|
||||
"integrity": "sha512-LIZ3HCW+vra626L18NiudY4v7zXVW4EvqUeeVLFM9S8fi1nGP2lHbDur/DFiN/VsJXYdNIdO4PAsILxeZXoOLg==",
|
||||
"version": "0.1.22",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.1.22.tgz",
|
||||
"integrity": "sha512-1/ARN6tiY/Roc86e1pw1Te/PDXTWjZb+hgpNdkM3i+GqpmtsuTWnDMxS9sBbsIgYEggJM67fn4Z1j65nERW0ag==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-polly": "^3.496.0",
|
||||
"@aws-sdk/client-sts": "^3.496.0",
|
||||
"@cartesia/cartesia-js": "^2.1.0",
|
||||
"@google-cloud/text-to-speech": "^5.5.0",
|
||||
"@grpc/grpc-js": "^1.9.14",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.7",
|
||||
@@ -1662,9 +1572,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@jambonz/time-series": {
|
||||
"version": "0.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/time-series/-/time-series-0.2.13.tgz",
|
||||
"integrity": "sha512-Kj+l+YUnI27zZA4qoPRzjN7L82W7GuMXYq9ttDjXQ0ZBIdOLAzJjB6R3jJ3b+mvoNEQ6qG5MUtfoc6CpTFH5lw==",
|
||||
"version": "0.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/time-series/-/time-series-0.2.12.tgz",
|
||||
"integrity": "sha512-EYw5f1QasblWrP2K/NabpJYkQm8XOCP1fem8luO8c7Jm8YTBwI+Ge3zB7rpU8ruoVdbrTot/pcihhTqzq4IYqA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"debug": "^4.3.1",
|
||||
"influx": "^5.9.3"
|
||||
@@ -3647,11 +3558,10 @@
|
||||
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
@@ -4098,18 +4008,6 @@
|
||||
"integrity": "sha512-dWuNH+XUT9hdFHASfMpcZGW5kUyJvllumJkXaXiswuCkoaFIFI89aykBPuHEi1YUWQGRCqvIO0BUdmeFJ4W4Ww==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/emittery": {
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
|
||||
"integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/emittery?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
@@ -4650,6 +4548,7 @@
|
||||
"version": "4.21.2",
|
||||
"resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
|
||||
"integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"accepts": "~1.3.8",
|
||||
"array-flatten": "1.1.1",
|
||||
@@ -5565,15 +5464,6 @@
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/human-id": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/human-id/-/human-id-4.1.1.tgz",
|
||||
"integrity": "sha512-3gKm/gCSUipeLsRYZbbdA1BD83lBoWUkZ7G9VFrhWPAU76KwYo5KR8V28bpoPm/ygy0x5/GCbpRQdY7VLYCoIg==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"human-id": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/humanize-ms": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
|
||||
@@ -7568,7 +7458,8 @@
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
|
||||
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/peek-readable": {
|
||||
"version": "4.1.0",
|
||||
@@ -9237,12 +9128,6 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-join": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
|
||||
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/url-parse": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
@@ -10704,70 +10589,6 @@
|
||||
"to-fast-properties": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@cartesia/cartesia-js": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@cartesia/cartesia-js/-/cartesia-js-2.1.1.tgz",
|
||||
"integrity": "sha512-Kwcrxtay6G3hwFGyq7cwB00d+diABhi2/5Mb4vytmN+QqBgTAVYgwFweGnLDL9Ci9zFttgLE1rW8u+EA8U5P9A==",
|
||||
"requires": {
|
||||
"emittery": "^0.13.1",
|
||||
"form-data": "^4.0.0",
|
||||
"form-data-encoder": "^4.0.2",
|
||||
"formdata-node": "^6.0.3",
|
||||
"human-id": "^4.1.1",
|
||||
"node-fetch": "2.7.0",
|
||||
"qs": "6.11.2",
|
||||
"readable-stream": "^4.5.2",
|
||||
"url-join": "4.0.1",
|
||||
"ws": "^8.15.13"
|
||||
},
|
||||
"dependencies": {
|
||||
"form-data": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
|
||||
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
|
||||
"requires": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"mime-types": "^2.1.12"
|
||||
}
|
||||
},
|
||||
"form-data-encoder": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.0.2.tgz",
|
||||
"integrity": "sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw=="
|
||||
},
|
||||
"formdata-node": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-6.0.3.tgz",
|
||||
"integrity": "sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg=="
|
||||
},
|
||||
"process": {
|
||||
"version": "0.11.10",
|
||||
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
|
||||
"integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.11.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz",
|
||||
"integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==",
|
||||
"requires": {
|
||||
"side-channel": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "4.5.2",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz",
|
||||
"integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==",
|
||||
"requires": {
|
||||
"abort-controller": "^3.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"events": "^3.3.0",
|
||||
"process": "^0.11.10",
|
||||
"string_decoder": "^1.3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@eslint/eslintrc": {
|
||||
"version": "0.4.3",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
|
||||
@@ -10900,13 +10721,12 @@
|
||||
}
|
||||
},
|
||||
"@jambonz/speech-utils": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.2.1.tgz",
|
||||
"integrity": "sha512-LIZ3HCW+vra626L18NiudY4v7zXVW4EvqUeeVLFM9S8fi1nGP2lHbDur/DFiN/VsJXYdNIdO4PAsILxeZXoOLg==",
|
||||
"version": "0.1.22",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.1.22.tgz",
|
||||
"integrity": "sha512-1/ARN6tiY/Roc86e1pw1Te/PDXTWjZb+hgpNdkM3i+GqpmtsuTWnDMxS9sBbsIgYEggJM67fn4Z1j65nERW0ag==",
|
||||
"requires": {
|
||||
"@aws-sdk/client-polly": "^3.496.0",
|
||||
"@aws-sdk/client-sts": "^3.496.0",
|
||||
"@cartesia/cartesia-js": "^2.1.0",
|
||||
"@google-cloud/text-to-speech": "^5.5.0",
|
||||
"@grpc/grpc-js": "^1.9.14",
|
||||
"@jambonz/realtimedb-helpers": "^0.8.7",
|
||||
@@ -10930,9 +10750,9 @@
|
||||
}
|
||||
},
|
||||
"@jambonz/time-series": {
|
||||
"version": "0.2.13",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/time-series/-/time-series-0.2.13.tgz",
|
||||
"integrity": "sha512-Kj+l+YUnI27zZA4qoPRzjN7L82W7GuMXYq9ttDjXQ0ZBIdOLAzJjB6R3jJ3b+mvoNEQ6qG5MUtfoc6CpTFH5lw==",
|
||||
"version": "0.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@jambonz/time-series/-/time-series-0.2.12.tgz",
|
||||
"integrity": "sha512-EYw5f1QasblWrP2K/NabpJYkQm8XOCP1fem8luO8c7Jm8YTBwI+Ge3zB7rpU8ruoVdbrTot/pcihhTqzq4IYqA==",
|
||||
"requires": {
|
||||
"debug": "^4.3.1",
|
||||
"influx": "^5.9.3"
|
||||
@@ -12428,9 +12248,9 @@
|
||||
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"path-key": "^3.1.0",
|
||||
@@ -12769,11 +12589,6 @@
|
||||
"integrity": "sha512-dWuNH+XUT9hdFHASfMpcZGW5kUyJvllumJkXaXiswuCkoaFIFI89aykBPuHEi1YUWQGRCqvIO0BUdmeFJ4W4Ww==",
|
||||
"dev": true
|
||||
},
|
||||
"emittery": {
|
||||
"version": "0.13.1",
|
||||
"resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
|
||||
"integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ=="
|
||||
},
|
||||
"emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
@@ -13855,11 +13670,6 @@
|
||||
"debug": "4"
|
||||
}
|
||||
},
|
||||
"human-id": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/human-id/-/human-id-4.1.1.tgz",
|
||||
"integrity": "sha512-3gKm/gCSUipeLsRYZbbdA1BD83lBoWUkZ7G9VFrhWPAU76KwYo5KR8V28bpoPm/ygy0x5/GCbpRQdY7VLYCoIg=="
|
||||
},
|
||||
"humanize-ms": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
|
||||
@@ -16568,11 +16378,6 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"url-join": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
|
||||
"integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="
|
||||
},
|
||||
"url-parse": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
|
||||
@@ -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.2.1",
|
||||
"@jambonz/speech-utils": "^0.1.22",
|
||||
"@jambonz/stats-collector": "^0.1.10",
|
||||
"@jambonz/time-series": "^0.2.12",
|
||||
"@jambonz/verb-specifications": "^0.0.90",
|
||||
"@jambonz/time-series": "^0.2.13",
|
||||
"@opentelemetry/api": "^1.8.0",
|
||||
"@opentelemetry/exporter-jaeger": "^1.23.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.50.0",
|
||||
@@ -49,7 +49,7 @@
|
||||
"deepcopy": "^2.1.0",
|
||||
"drachtio-fsmrf": "^3.0.46",
|
||||
"drachtio-srf": "^4.5.35",
|
||||
"express": "^4.19.2",
|
||||
"express": "^4.21.2",
|
||||
"express-validator": "^7.0.1",
|
||||
"moment": "^2.30.1",
|
||||
"parse-url": "^9.2.0",
|
||||
|
||||
Reference in New Issue
Block a user