From 09220872ae669c422479c5e9430ff4dc6b2fc70f Mon Sep 17 00:00:00 2001 From: Sam Machin Date: Fri, 2 Jan 2026 16:05:38 +0000 Subject: [PATCH 01/15] Update recording (#1483) * refactor recording removed the test of `(this.cs.accountInfo.account.record_all_calls || this.cs.application.record_all_calls` from backround-task-manager.jsL138 as this check is already done in call-session.js at Line 3007, also allows us to start the record from update or config verbs * handle start recording for a call that is not yet answered * return false if not changing recording state * different check for status * set hasRecording flag on callInfo when starting * update redis on recording start * lint * update dependency --- lib/session/call-info.js | 1 + lib/session/call-session.js | 145 ++++++++++++++++----------- lib/utils/background-task-manager.js | 36 ++++--- package-lock.json | 8 +- package.json | 2 +- 5 files changed, 111 insertions(+), 81 deletions(-) diff --git a/lib/session/call-info.js b/lib/session/call-info.js index 44e73f3d..ac3b813d 100644 --- a/lib/session/call-info.js +++ b/lib/session/call-info.js @@ -12,6 +12,7 @@ class CallInfo { let srf; this.direction = opts.direction; this.traceId = opts.traceId; + this.hasRecording = false; this.callTerminationBy = undefined; if (opts.req) { const u = opts.req.getParsedHeader('from'); diff --git a/lib/session/call-session.js b/lib/session/call-session.js index 34fbe31a..940a3f7e 100644 --- a/lib/session/call-session.js +++ b/lib/session/call-session.js @@ -756,69 +756,101 @@ class CallSession extends Emitter { return this._fillerNoise; } - async pauseOrResumeBackgroundListenIfRequired(action, silence = false) { - if ((action == 'pauseCallRecording' || action == 'resumeCallRecording') && - this.backgroundTaskManager.isTaskRunning('record')) { - this.logger.debug({action, silence}, 'CallSession:pauseOrResumeBackgroundListenIfRequired'); - const backgroundListenTask = this.backgroundTaskManager.getTask('record'); - const status = action === 'pauseCallRecording' ? ListenStatus.Pause : ListenStatus.Resume; - backgroundListenTask.updateListen( - status, - silence - ); - } - } async notifyRecordOptions(opts) { - const {action, silence} = opts; + const {action, silence = false, type = 'siprec'} = opts; this.logger.debug({opts}, 'CallSession:notifyRecordOptions'); - this.pauseOrResumeBackgroundListenIfRequired(action, silence); - - /* if we have not answered yet, just save the details for later */ - if (!this.dlg) { - if (action === 'startCallRecording') { - this.recordOptions = opts; - return true; + if (type == 'cloud') { + switch (action) { + case 'pauseCallRecording': + if (this.backgroundTaskManager.isTaskRunning('record')) { + this.logger.debug({action, silence, type}, 'CallSession:cloudRecording'); + const backgroundListenTask = this.backgroundTaskManager.getTask('record'); + backgroundListenTask.updateListen( + ListenStatus.Pause, + silence + ); + return true; + } else { return false; } + case 'resumeCallRecording': + if (this.backgroundTaskManager.isTaskRunning('record')) { + this.logger.debug({action, silence, type}, 'CallSession:cloudRecording'); + const backgroundListenTask = this.backgroundTaskManager.getTask('record'); + backgroundListenTask.updateListen( + ListenStatus.Resume, + silence + ); + return true; + } else { return false; } + case 'startCallRecording': + if (!this.backgroundTaskManager.isTaskRunning('record')) { + this.logger.debug({action, silence, type}, 'CallSession:cloudRecording'); + this.callInfo.hasRecording = true; + this.updateCallStatus(Object.assign({}, this.callInfo.toJSON()), this.serviceUrl) + .catch((err) => this.logger.error(err, 'redis error')); + if (!this.dlg) { + // Call not yet answered so set flag to record on status change + this.application.record_all_calls = true; + } else { + this.backgroundTaskManager.newTask('record'); + } + return true; + } else { return false; } + case 'stopCallRecording': + if (this.backgroundTaskManager.isTaskRunning('record')) { + this.logger.debug({action, silence, type}, 'CallSession:cloudRecording'); + this.backgroundTaskManager.stop('record'); + return true; + } else { return false; } + } + } else { + // SIPREC + /* if we have not answered yet, just save the details for later */ + if (!this.dlg) { + if (action === 'startCallRecording') { + this.recordOptions = opts; + return true; + } + return false; } - return false; - } - /* check validity of request */ - if (action == 'startCallRecording' && this.recordState !== RecordState.RecordingOff) { - this.logger.info({recordState: this.recordState}, - 'CallSession:notifyRecordOptions: recording is already started, ignoring request'); - return false; - } - if (action == 'stopCallRecording' && this.recordState === RecordState.RecordingOff) { - this.logger.info({recordState: this.recordState}, - 'CallSession:notifyRecordOptions: recording is already stopped, ignoring request'); - return false; - } - if (action == 'pauseCallRecording' && this.recordState !== RecordState.RecordingOn) { - this.logger.info({recordState: this.recordState}, - 'CallSession:notifyRecordOptions: cannot pause recording, ignoring request '); - return false; - } - if (action == 'resumeCallRecording' && this.recordState !== RecordState.RecordingPaused) { - this.logger.info({recordState: this.recordState}, - 'CallSession:notifyRecordOptions: cannot resume recording, ignoring request '); - return false; - } + /* check validity of request */ + if (action == 'startCallRecording' && this.recordState !== RecordState.RecordingOff) { + this.logger.info({recordState: this.recordState}, + 'CallSession:notifyRecordOptions: recording is already started, ignoring request'); + return false; + } + if (action == 'stopCallRecording' && this.recordState === RecordState.RecordingOff) { + this.logger.info({recordState: this.recordState}, + 'CallSession:notifyRecordOptions: recording is already stopped, ignoring request'); + return false; + } + if (action == 'pauseCallRecording' && this.recordState !== RecordState.RecordingOn) { + this.logger.info({recordState: this.recordState}, + 'CallSession:notifyRecordOptions: cannot pause recording, ignoring request '); + return false; + } + if (action == 'resumeCallRecording' && this.recordState !== RecordState.RecordingPaused) { + this.logger.info({recordState: this.recordState}, + 'CallSession:notifyRecordOptions: cannot resume recording, ignoring request '); + return false; + } - this.recordOptions = opts; + this.recordOptions = opts; - switch (action) { - case 'startCallRecording': - return await this.startRecording(); - case 'stopCallRecording': - return await this.stopRecording(); - case 'pauseCallRecording': - return await this.pauseRecording(); - case 'resumeCallRecording': - return await this.resumeRecording(); - default: - throw new Error(`invalid record action ${action}`); + switch (action) { + case 'startCallRecording': + return await this.startRecording(); + case 'stopCallRecording': + return await this.stopRecording(); + case 'pauseCallRecording': + return await this.pauseRecording(); + case 'resumeCallRecording': + return await this.resumeRecording(); + default: + throw new Error(`invalid record action ${action}`); + } } } @@ -2980,8 +3012,7 @@ Duration=${duration} ` // manage record all call. if (callStatus === CallStatus.InProgress) { - if (this.accountInfo.account.record_all_calls || - this.application.record_all_calls) { + if (this.accountInfo.account.record_all_calls || this.application.record_all_calls) { this.backgroundTaskManager.newTask('record'); } } else if (callStatus == CallStatus.Completed) { diff --git a/lib/utils/background-task-manager.js b/lib/utils/background-task-manager.js index 7c55b80e..417d838b 100644 --- a/lib/utils/background-task-manager.js +++ b/lib/utils/background-task-manager.js @@ -135,26 +135,24 @@ class BackgroundTaskManager extends Emitter { // Initiate Record async _initRecord() { - if (this.cs.accountInfo.account.record_all_calls || this.cs.application.record_all_calls) { - if (!JAMBONZ_RECORD_WS_BASE_URL || !this.cs.accountInfo.account.bucket_credential) { - this.logger.error('_initRecord: invalid cfg - missing JAMBONZ_RECORD_WS_BASE_URL or bucket config'); - return undefined; - } - const listenOpts = { - url: `${JAMBONZ_RECORD_WS_BASE_URL}/record/${this.cs.accountInfo.account.bucket_credential.vendor}`, - disableBidirectionalAudio: true, - mixType : 'stereo', - passDtmf: true - }; - if (JAMBONZ_RECORD_WS_USERNAME && JAMBONZ_RECORD_WS_PASSWORD) { - listenOpts.wsAuth = { - username: JAMBONZ_RECORD_WS_USERNAME, - password: JAMBONZ_RECORD_WS_PASSWORD - }; - } - this.logger.debug({listenOpts}, '_initRecord: enabling listen'); - return await this._initListen({verb: 'listen', ...listenOpts}, 'jambonz-session-record', true, 'record'); + if (!JAMBONZ_RECORD_WS_BASE_URL || !this.cs.accountInfo.account.bucket_credential) { + this.logger.error('_initRecord: invalid cfg - missing JAMBONZ_RECORD_WS_BASE_URL or bucket config'); + return undefined; } + const listenOpts = { + url: `${JAMBONZ_RECORD_WS_BASE_URL}/record/${this.cs.accountInfo.account.bucket_credential.vendor}`, + disableBidirectionalAudio: true, + mixType : 'stereo', + passDtmf: true + }; + if (JAMBONZ_RECORD_WS_USERNAME && JAMBONZ_RECORD_WS_PASSWORD) { + listenOpts.wsAuth = { + username: JAMBONZ_RECORD_WS_USERNAME, + password: JAMBONZ_RECORD_WS_PASSWORD + }; + } + this.logger.debug({listenOpts}, '_initRecord: enabling listen'); + return await this._initListen({verb: 'listen', ...listenOpts}, 'jambonz-session-record', true, 'record'); } // Initiate Transcribe diff --git a/package-lock.json b/package-lock.json index 67da89ab..8ed962eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@jambonz/speech-utils": "^0.2.26", "@jambonz/stats-collector": "^0.1.10", "@jambonz/time-series": "^0.2.15", - "@jambonz/verb-specifications": "^0.0.122", + "@jambonz/verb-specifications": "^0.0.123", "@modelcontextprotocol/sdk": "^1.9.0", "@opentelemetry/api": "^1.8.0", "@opentelemetry/exporter-jaeger": "^1.23.0", @@ -1529,9 +1529,9 @@ } }, "node_modules/@jambonz/verb-specifications": { - "version": "0.0.122", - "resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.122.tgz", - "integrity": "sha512-7xqaULhKFywJ2ZuyiYt77iiJwJ+8b98Zt1X4+OqZ7Cdjhfo7S6KnR66XRVJHnekXbmfVv58kB0KWUux5TG//Sw==", + "version": "0.0.123", + "resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.123.tgz", + "integrity": "sha512-yPW8u0Wacz8FKnQpQDjJ2AQHjHTf4S3TlkTyKqFkOyY8lnjnhqg0Mth+9uvOPfJaHgsPd0t9outfQa0wCu5tww==", "license": "MIT", "dependencies": { "debug": "^4.3.4", diff --git a/package.json b/package.json index 42c11c0a..23271f11 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@jambonz/speech-utils": "^0.2.26", "@jambonz/stats-collector": "^0.1.10", "@jambonz/time-series": "^0.2.15", - "@jambonz/verb-specifications": "^0.0.122", + "@jambonz/verb-specifications": "^0.0.123", "@modelcontextprotocol/sdk": "^1.9.0", "@opentelemetry/api": "^1.8.0", "@opentelemetry/exporter-jaeger": "^1.23.0", From 83a8cf6d258797891e7a1a75ff0938bd93f96ccd Mon Sep 17 00:00:00 2001 From: Dave Horton Date: Sun, 4 Jan 2026 12:23:26 -0500 Subject: [PATCH 02/15] SIGUSR1 should cause fs to commence drying up calls but do not exit when call count reaches zero (#1486) --- lib/utils/sbc-pinger.js | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/lib/utils/sbc-pinger.js b/lib/utils/sbc-pinger.js index 3b4d6f17..ec6c453e 100644 --- a/lib/utils/sbc-pinger.js +++ b/lib/utils/sbc-pinger.js @@ -120,17 +120,8 @@ module.exports = (logger) => { } pingProxies(srf); - // if we have zero calls, we can complete the scale-in right - setTimeout(() => { - const calls = srf.locals.sessionTracker.count; - if (calls === 0) { - logger.info('scale-in can complete immediately as we have no calls in progress'); - process.exit(0); - } - else { - logger.info(`${calls} calls in progress; scale-in will complete when they are done`); - } - }, 5000); + // Note: in response to SIGUSR1 we start drying up but do not exit when calls reach zero. + // This is to allow external scripts that sent the signal to manage the lifecycle. }); } From ba275ef547a0f78db3b4e275c69dd538772aa58d Mon Sep 17 00:00:00 2001 From: Ed Robbins Date: Mon, 5 Jan 2026 15:53:32 -0500 Subject: [PATCH 03/15] #1485 remove deprecated call to URL.parse (#1488) --- lib/tasks/redirect.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/redirect.js b/lib/tasks/redirect.js index 20af4de5..06c53134 100644 --- a/lib/tasks/redirect.js +++ b/lib/tasks/redirect.js @@ -33,7 +33,7 @@ class TaskRedirect extends Task { } else { const baseUrl = this.cs.application.requestor.baseUrl; - const newUrl = URL.parse(this.actionHook); + const newUrl = new URL(this.actionHook); const newBaseUrl = newUrl.protocol + '//' + newUrl.host; if (baseUrl != newBaseUrl) { try { From e4ec0025c3d406af7de273600b393d4ac27e5cc8 Mon Sep 17 00:00:00 2001 From: Hoan Luu Huu <110280845+xquanluu@users.noreply.github.com> Date: Wed, 7 Jan 2026 22:46:33 +0700 Subject: [PATCH 04/15] Fix/gladia multi sessions (#1487) * support gladia transcribe multi channels * wip --- lib/tasks/gather.js | 4 ++++ lib/tasks/stt-task.js | 26 ++++++++------------------ lib/tasks/transcribe.js | 8 ++++++++ lib/utils/transcription-utils.js | 7 ------- 4 files changed, 20 insertions(+), 25 deletions(-) diff --git a/lib/tasks/gather.js b/lib/tasks/gather.js index 194086aa..700a2533 100644 --- a/lib/tasks/gather.js +++ b/lib/tasks/gather.js @@ -500,6 +500,10 @@ class TaskGather extends SttTask { this.addCustomEventListener(ep, GladiaTranscriptionEvents.ConnectFailure, this._onVendorConnectFailure.bind(this, cs, ep)); this.addCustomEventListener(ep, GladiaTranscriptionEvents.Error, this._onVendorError.bind(this, cs, ep)); + // gladia require unique url for each session + const {host, path} = await this.createGladiaLiveSession(); + opts.GLADIA_SPEECH_HOST = host; + opts.GLADIA_SPEECH_PATH = path; break; case 'soniox': diff --git a/lib/tasks/stt-task.js b/lib/tasks/stt-task.js index 65b25341..2a818450 100644 --- a/lib/tasks/stt-task.js +++ b/lib/tasks/stt-task.js @@ -203,26 +203,14 @@ class SttTask extends Task { if (cs.hasGlobalSttPunctuation && !this.data.recognizer.punctuation) { this.data.recognizer.punctuation = cs.globalSttPunctuation; } - if (this.vendor === 'gladia') { - const { api_key, region } = this.sttCredentials; - const {url} = await this.createGladiaLiveSession({ - api_key, region, - model: this.data.recognizer.model || 'solaria-1', - options: this.data.recognizer.gladiaOptions || {} - }); - const {host, pathname, search} = new URL(url); - this.sttCredentials.host = host; - this.sttCredentials.path = `${pathname}${search}`; - } } - async createGladiaLiveSession({ - api_key, - region = 'us-west', - model = 'solaria-1', - options = {}, - }) { + async createGladiaLiveSession() { + const { api_key, region = 'us-west' } = this.sttCredentials; + const model = this.data.recognizer.model || 'solaria-1'; + const options = this.data.recognizer.gladiaOptions || {}; + const url = `https://api.gladia.io/v2/live?region=${region}`; const response = await fetch(url, { method: 'POST', @@ -252,7 +240,9 @@ class SttTask extends Task { const data = await response.json(); this.logger.debug({url: data.url}, 'Gladia Call registered'); - return data; + + const {host, pathname, search} = new URL(data.url); + return {host, path: `${pathname}${search}`}; } addCustomEventListener(ep, event, handler) { diff --git a/lib/tasks/transcribe.js b/lib/tasks/transcribe.js index 34ccf59b..e3cd3302 100644 --- a/lib/tasks/transcribe.js +++ b/lib/tasks/transcribe.js @@ -459,6 +459,14 @@ class TaskTranscribe extends SttTask { else if (this.data.recognizer?.hints?.length > 0) { prompt = this.data.recognizer?.hints.join(', '); } + } else if (this.vendor === 'gladia') { + // gladia require unique url for each session + const {host, path} = await this.createGladiaLiveSession(); + await ep.set({ + GLADIA_SPEECH_HOST: host, + GLADIA_SPEECH_PATH: path, + }) + .catch((err) => this.logger.info(err, 'Error setting channel variables')); } await ep.startTranscription({ diff --git a/lib/utils/transcription-utils.js b/lib/utils/transcription-utils.js index d060ba11..5014669c 100644 --- a/lib/utils/transcription-utils.js +++ b/lib/utils/transcription-utils.js @@ -1085,13 +1085,6 @@ module.exports = (logger) => { ...(keyterms && keyterms.length > 0 && {DEEPGRAMFLUX_SPEECH_KEYTERMS: keyterms.join(',')}), }; } - else if ('gladia' === vendor) { - const {host, path} = sttCredentials; - opts = { - GLADIA_SPEECH_HOST: host, - GLADIA_SPEECH_PATH: path, - }; - } else if ('soniox' === vendor) { const {sonioxOptions = {}} = rOpts; const {storage = {}} = sonioxOptions; From 961c2589ac5201b1b35e55826fe34187f3cabc39 Mon Sep 17 00:00:00 2001 From: Vinod Dharashive Date: Tue, 13 Jan 2026 19:28:37 +0530 Subject: [PATCH 05/15] freeswitch capture sip error and propagate the same error (#1489) * fix: propagate SIP 488 error to SBC on endpoint allocation failure When FreeSWITCH returns a SIP 488 'Not Acceptable Here' error during endpoint allocation (e.g., codec incompatibility), this error was not being propagated back to the SBC/client. Instead, the call would wait indefinitely for websocket commands or return a generic 603 response. Implementation: - In _evalEndpointPrecondition(), detect SipError by checking err.type === 'SipError' or err.name === 'SipError' - Extract the SIP status code (e.g., 488), reason, and the Reason header from the error response (e.g., Q.850;cause=88;text=INCOMPATIBLE_DESTINATION) - Send the SIP error response immediately to the SBC with: - X-Reason header: endpoint allocation failure details - Reason header: original Q.850 cause from FreeSWITCH - Notify call status change as Failed with proper SIP status - Release the call immediately instead of waiting for commands Also added fallback handling in InboundCallSession._onTasksDone() to propagate the stored error if immediate send was not possible. * wip * Simplify SipError check to only use err.name --- lib/session/call-session.js | 30 +++++++++++++++++++++++++++++ lib/session/inbound-call-session.js | 13 +++++++++++++ 2 files changed, 43 insertions(+) diff --git a/lib/session/call-session.js b/lib/session/call-session.js index 940a3f7e..c441bcaf 100644 --- a/lib/session/call-session.js +++ b/lib/session/call-session.js @@ -2500,6 +2500,36 @@ Duration=${duration} ` } else { this.logger.error(err, `Error attempting to allocate endpoint for for task ${task.name}`); + // Check for SipError type (e.g., 488 codec incompatibility) + const isSipError = err.name === 'SipError'; + if (isSipError && err.status) { + // Extract Reason header from SIP response if available (e.g., Q.850;cause=88;text="INCOMPATIBLE_DESTINATION") + const sipReasonHeader = err.res?.msg?.headers?.reason; + this._endpointAllocationError = { + status: err.status, + reason: err.reason || 'Endpoint Allocation Failed', + sipReasonHeader + }; + this.logger.info({endpointAllocationError: this._endpointAllocationError}, + 'Captured SipError for propagation to SBC'); + + // Send SIP error response immediately for inbound calls + if (this.res && !this.res.finalResponseSent) { + this.logger.info(`Sending ${err.status} response to SBC due to SipError`); + this.res.send(err.status, { + headers: { + 'X-Reason': `endpoint allocation failure: ${err.reason || 'Endpoint Allocation Failed'}`, + ...(sipReasonHeader && {'Reason': sipReasonHeader}) + } + }); + this._notifyCallStatusChange({ + callStatus: CallStatus.Failed, + sipStatus: err.status, + sipReason: err.reason || 'Endpoint Allocation Failed' + }); + this._callReleased(); + } + } throw new Error(`${BADPRECONDITIONS}: unable to allocate endpoint`); } } diff --git a/lib/session/inbound-call-session.js b/lib/session/inbound-call-session.js index ee3fd3cd..a905f4bd 100644 --- a/lib/session/inbound-call-session.js +++ b/lib/session/inbound-call-session.js @@ -60,6 +60,19 @@ class InboundCallSession extends CallSession { } }); } + else if (this._endpointAllocationError) { + // Propagate SIP error from endpoint allocation failure back to the client + const {status, reason, sipReasonHeader} = this._endpointAllocationError; + this.rootSpan.setAttributes({'call.termination': `endpoint allocation SIP error ${status}`}); + this.logger.info({endpointAllocationError: this._endpointAllocationError}, + `InboundCallSession:_onTasksDone generating ${status} due to endpoint allocation failure`); + this.res.send(status, { + headers: { + 'X-Reason': `endpoint allocation failure: ${reason}`, + ...(sipReasonHeader && {'Reason': sipReasonHeader}) + } + }); + } else { this.rootSpan.setAttributes({'call.termination': 'tasks completed without answering call'}); this.logger.info('InboundCallSession:_onTasksDone auto-generating non-success response to invite'); From 3c3dfa81d31e508290b92eac82020ff65f41248c Mon Sep 17 00:00:00 2001 From: Dave Horton Date: Mon, 19 Jan 2026 16:42:43 -0500 Subject: [PATCH 06/15] fix issue where call hangs up and actionhook delay triggered (#1497) --- lib/utils/action-hook-delay.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/utils/action-hook-delay.js b/lib/utils/action-hook-delay.js index 4b18ae3c..254c24a7 100644 --- a/lib/utils/action-hook-delay.js +++ b/lib/utils/action-hook-delay.js @@ -118,6 +118,13 @@ class ActionHookDelayProcessor extends Emitter { this.logger.debug('ActionHookDelayProcessor#_onNoResponseTimer'); this._noResponseTimer = null; + /* check if endpoint is still available (call may have ended) */ + if (!this.ep) { + this.logger.debug('ActionHookDelayProcessor#_onNoResponseTimer: endpoint is null, call may have ended'); + this._active = false; + return; + } + /* get the next play or say action */ const verb = this.actions[this._retryCount % this.actions.length]; @@ -129,8 +136,8 @@ class ActionHookDelayProcessor extends Emitter { this._taskInProgress.exec(this.cs, {ep: this.ep}).catch((err) => { this.logger.info(`ActionHookDelayProcessor#_onNoResponseTimer: error playing file: ${err.message}`); this._taskInProgress = null; - this.ep.removeAllListeners('playback-start'); - this.ep.removeAllListeners('playback-stop'); + this.ep?.removeAllListeners('playback-start'); + this.ep?.removeAllListeners('playback-stop'); }); } catch (err) { this.logger.info(err, 'ActionHookDelayProcessor#_onNoResponseTimer: error starting action'); From 2468557aefa3fd84fc7718ec88d40d0c889059ec Mon Sep 17 00:00:00 2001 From: Sam Machin Date: Wed, 21 Jan 2026 14:20:47 +0000 Subject: [PATCH 07/15] add statusHook to redirect verb (#1500) * add statusHook to redirect * fix url import * Update redirect.js * logging * constructor statusHook * lint n logging * debug * update call_status_hook * use notifier to test url * remove require url as its global since node 10 * update verb specs dep * update verb specs --- lib/tasks/redirect.js | 26 +++++++++++++++++++++++++- package-lock.json | 8 ++++---- package.json | 2 +- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/lib/tasks/redirect.js b/lib/tasks/redirect.js index 06c53134..6a4e44ec 100644 --- a/lib/tasks/redirect.js +++ b/lib/tasks/redirect.js @@ -1,7 +1,6 @@ const Task = require('./task'); const {TaskName} = require('../utils/constants'); const WsRequestor = require('../utils/ws-requestor'); -const URL = require('url'); const HttpRequestor = require('../utils/http-requestor'); /** @@ -10,6 +9,7 @@ const HttpRequestor = require('../utils/http-requestor'); class TaskRedirect extends Task { constructor(logger, opts) { super(logger, opts); + this.statusHook = opts.statusHook || false; } get name() { return TaskName.Redirect; } @@ -47,6 +47,30 @@ class TaskRedirect extends Task { } } } + + /* update the notifier if a new statusHook was provided */ + if (this.statusHook) { + this.logger.info(`TaskRedirect updating statusHook to ${this.statusHook}`); + try { + const oldNotifier = cs.application.notifier; + const isStatusHookAbsolute = cs.notifier?._isAbsoluteUrl(this.statusHook); + if (isStatusHookAbsolute) { + if (cs.notifier instanceof WsRequestor) { + cs.application.notifier = new WsRequestor(this.logger, cs.accountSid, {url: this.statusHook}, + cs.accountInfo.account.webhook_secret); + } else { + cs.application.notifier = new HttpRequestor(this.logger, cs.accountSid, {url: this.statusHook}, + cs.accountInfo.account.webhook_secret); + } + if (oldNotifier?.close) oldNotifier.close(); + } + /* update the call_status_hook URL that gets passed to the notifier */ + cs.application.call_status_hook = this.statusHook; + } catch (err) { + this.logger.info(err, `TaskRedirect error updating statusHook to ${this.statusHook}`); + } + } + await this.performAction(); } } diff --git a/package-lock.json b/package-lock.json index 8ed962eb..dfec814c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@jambonz/speech-utils": "^0.2.26", "@jambonz/stats-collector": "^0.1.10", "@jambonz/time-series": "^0.2.15", - "@jambonz/verb-specifications": "^0.0.123", + "@jambonz/verb-specifications": "^0.0.125", "@modelcontextprotocol/sdk": "^1.9.0", "@opentelemetry/api": "^1.8.0", "@opentelemetry/exporter-jaeger": "^1.23.0", @@ -1529,9 +1529,9 @@ } }, "node_modules/@jambonz/verb-specifications": { - "version": "0.0.123", - "resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.123.tgz", - "integrity": "sha512-yPW8u0Wacz8FKnQpQDjJ2AQHjHTf4S3TlkTyKqFkOyY8lnjnhqg0Mth+9uvOPfJaHgsPd0t9outfQa0wCu5tww==", + "version": "0.0.125", + "resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.125.tgz", + "integrity": "sha512-lU1fyyYyjXOdIfQ2gmOFmssZASYNu6LD066iXjqFrBJpiI7shkprcZ1qeWGibuEk9nR2k+em3/YL31Wc8L4wvA==", "license": "MIT", "dependencies": { "debug": "^4.3.4", diff --git a/package.json b/package.json index 23271f11..f8e655da 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@jambonz/speech-utils": "^0.2.26", "@jambonz/stats-collector": "^0.1.10", "@jambonz/time-series": "^0.2.15", - "@jambonz/verb-specifications": "^0.0.123", + "@jambonz/verb-specifications": "^0.0.125", "@modelcontextprotocol/sdk": "^1.9.0", "@opentelemetry/api": "^1.8.0", "@opentelemetry/exporter-jaeger": "^1.23.0", From 9848152d5b2d291779bb9adfed149fc9d12a1728 Mon Sep 17 00:00:00 2001 From: Hoan Luu Huu <110280845+xquanluu@users.noreply.github.com> Date: Thu, 22 Jan 2026 22:12:06 +0700 Subject: [PATCH 08/15] support google gemini tts (#1491) * support google gemini tts * wip * wip * wip * wip * wip * wip * wip * update speech utils version * wip --- lib/session/call-session.js | 2 +- lib/tasks/tts-task.js | 5 +- package-lock.json | 697 ++++++++++++++++++++++++++++-------- package.json | 2 +- 4 files changed, 549 insertions(+), 157 deletions(-) diff --git a/lib/session/call-session.js b/lib/session/call-session.js index c441bcaf..438a7a42 100644 --- a/lib/session/call-session.js +++ b/lib/session/call-session.js @@ -1082,7 +1082,7 @@ class CallSession extends Emitter { const cred = JSON.parse(credential.service_key.replace(/\n/g, '\\n')); return { speech_credential_sid: credential.speech_credential_sid, - credentials: cred + credentials: cred, }; } catch (err) { const sid = this.accountInfo.account.account_sid; diff --git a/lib/tasks/tts-task.js b/lib/tasks/tts-task.js index d2df58ab..757eac42 100644 --- a/lib/tasks/tts-task.js +++ b/lib/tasks/tts-task.js @@ -31,8 +31,9 @@ class TtsTask extends Task { this.synthesizer = this.data.synthesizer || {}; this.disableTtsCache = this.data.disableTtsCache; this.options = this.synthesizer.options || {}; - this.instructions = this.data.instructions; + this.instructions = this.data.instructions || this.options.instructions; this.playbackIds = []; + this.useGeminiTts = this.options.useGeminiTts; } getPlaybackId(offset) { @@ -242,6 +243,8 @@ class TtsTask extends Task { } } else if (vendor === 'cartesia') { credentials.model_id = this.options.model_id || credentials.model_id; + } else if (vendor === 'google') { + this.model = this.options.model || credentials.credentials.model_id; } this.model_id = credentials.model_id; diff --git a/package-lock.json b/package-lock.json index dfec814c..4febf27e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "@jambonz/http-health-check": "^0.0.1", "@jambonz/mw-registrar": "^0.2.7", "@jambonz/realtimedb-helpers": "^0.8.15", - "@jambonz/speech-utils": "^0.2.26", + "@jambonz/speech-utils": "^0.2.30", "@jambonz/stats-collector": "^0.1.10", "@jambonz/time-series": "^0.2.15", "@jambonz/verb-specifications": "^0.0.125", @@ -1333,21 +1333,21 @@ "license": "MIT" }, "node_modules/@google-cloud/text-to-speech": { - "version": "5.8.1", - "resolved": "https://registry.npmjs.org/@google-cloud/text-to-speech/-/text-to-speech-5.8.1.tgz", - "integrity": "sha512-HXyZBtfQq+ETSLwWV/k3zFRWSzt+KEfiC5/OqXNNUed+lU/LEyN0CsqqEmkFfkL8BPsVIMAK2xiYCaDsKENukg==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@google-cloud/text-to-speech/-/text-to-speech-6.4.0.tgz", + "integrity": "sha512-KUnK+mBYz9aegxHrBbwEignOkGRqXvqOXs/EGY7CAJhMFKuNSieW02/PV/ipVRsJM11MP69qgTgEetoyAN0HAg==", "license": "Apache-2.0", "dependencies": { - "google-gax": "^4.0.3" + "google-gax": "^5.0.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, "node_modules/@grpc/grpc-js": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.2.tgz", - "integrity": "sha512-QzVUtEFyu05UNx2xr0fCQmStUO17uVQhGNowtxs00IgTZT6/W2PBLfUkj30s0FKJ29VtTa3ArVNIhNP6akQhqA==", + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz", + "integrity": "sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==", "license": "Apache-2.0", "dependencies": { "@grpc/proto-loader": "^0.8.0", @@ -1405,6 +1405,102 @@ "integrity": "sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==", "license": "MIT" }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1486,16 +1582,16 @@ } }, "node_modules/@jambonz/speech-utils": { - "version": "0.2.27", - "resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.2.27.tgz", - "integrity": "sha512-wx0VKq2Gy8+pNx/RwMJjvaG7HT1/pezlgKkUfL0a4DoXxwIoglVyc/2SPUMf9yZ5xKSifYxcGoGSKfor9f9EbQ==", + "version": "0.2.30", + "resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.2.30.tgz", + "integrity": "sha512-FL0D5rx4KGgYbWRwjvxQDaI05AnKygYb0dL9O0MMfi8dOUBraGwp0X2Y6MfJaRKNM5mm77iwlsEd7nKP3J2h7Q==", "license": "MIT", "dependencies": { "23": "^0.0.0", "@aws-sdk/client-polly": "^3.496.0", "@aws-sdk/client-sts": "^3.496.0", "@cartesia/cartesia-js": "^2.2.7", - "@google-cloud/text-to-speech": "^5.5.0", + "@google-cloud/text-to-speech": "^6.4.0", "@grpc/grpc-js": "^1.9.14", "@jambonz/realtimedb-helpers": "^0.8.7", "bent": "^7.3.12", @@ -2532,6 +2628,16 @@ "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", "license": "MIT" }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -3254,12 +3360,6 @@ "integrity": "sha512-O6Th/DI18XjrL9TX8LO9F/g26qAz5vynmQqlXt/qLGrskvzCKXKc5/tATz3G2N6lM8eOf3M8/StB14FncAmocg==", "license": "MIT" }, - "node_modules/@types/caseless": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.5.tgz", - "integrity": "sha512-hWtVTC2q7hc7xZ/RLbxapMvDMgUnDvKvMOpKal4DrMyfGBUfB1oKaZlIRr6mJL+If3bAP6sV/QneGzF6tJjZDg==", - "license": "MIT" - }, "node_modules/@types/debug": { "version": "4.1.12", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", @@ -3281,12 +3381,6 @@ "integrity": "sha512-EE11Sn7gzHEF9FGkYHTkFpPuDEamLudLvaGnBciNgH55fTYboWZHINR6MP8+CfCOjPJX08l4teRrjCY11gz1CA==", "license": "MIT" }, - "node_modules/@types/long": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", - "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", - "license": "MIT" - }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -3315,35 +3409,6 @@ "integrity": "sha512-LriObC2+KYZD3FzCrgWGv/qufdUy4eXrxcLgQMfYXgPbLIecKIsVBaQgUPmxSSLcjmYbDTQbMgr6qr6l/eb7Bg==", "license": "MIT" }, - "node_modules/@types/request": { - "version": "2.48.13", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.13.tgz", - "integrity": "sha512-FGJ6udDNUCjd19pp0Q3iTiDkwhYup7J8hpMW9c4k53NrccQFFWKRho6hvtPPEhnXWKvukfwAlB6DbDz4yhH5Gg==", - "license": "MIT", - "dependencies": { - "@types/caseless": "*", - "@types/node": "*", - "@types/tough-cookie": "*", - "form-data": "^2.5.5" - } - }, - "node_modules/@types/request/node_modules/form-data": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", - "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.35", - "safe-buffer": "^5.2.1" - }, - "engines": { - "node": ">= 0.12" - } - }, "node_modules/@types/shimmer": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@types/shimmer/-/shimmer-1.2.0.tgz", @@ -3724,7 +3789,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, "license": "MIT" }, "node_modules/base64-js": { @@ -4302,6 +4366,15 @@ "node": ">=0.12" } }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -4696,6 +4769,12 @@ "node": ">= 6" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -5514,6 +5593,38 @@ "fxparser": "src/cli/cli.js" } }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/fetch-blob/node_modules/web-streams-polyfill": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", + "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -5742,6 +5853,18 @@ "node": ">= 18" } }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "license": "MIT", + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -5836,46 +5959,137 @@ } }, "node_modules/gaxios": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", - "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-7.1.3.tgz", + "integrity": "sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==", "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.9", - "uuid": "^9.0.1" + "node-fetch": "^3.3.2", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/gaxios/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/gaxios/node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/gaxios/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" - ], - "license": "MIT", + "node_modules/gaxios/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, "bin": { - "uuid": "dist/bin/uuid" + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gaxios/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gaxios/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/gaxios/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, + "bin": { + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gaxios/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/gcp-metadata": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", - "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-8.1.2.tgz", + "integrity": "sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==", "license": "Apache-2.0", "dependencies": { - "gaxios": "^6.1.1", - "google-logging-utils": "^0.0.2", + "gaxios": "^7.0.0", + "google-logging-utils": "^1.0.0", "json-bigint": "^1.0.0" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/generate-function": { @@ -6050,80 +6264,154 @@ } }, "node_modules/google-auth-library": { - "version": "9.15.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", - "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-10.5.0.tgz", + "integrity": "sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==", "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^6.1.1", - "gcp-metadata": "^6.1.0", - "gtoken": "^7.0.0", + "gaxios": "^7.0.0", + "gcp-metadata": "^8.0.0", + "google-logging-utils": "^1.0.0", + "gtoken": "^8.0.0", "jws": "^4.0.0" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/google-gax": { - "version": "4.6.1", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-4.6.1.tgz", - "integrity": "sha512-V6eky/xz2mcKfAd1Ioxyd6nmA61gao3n01C+YeuIwu3vzM9EDR6wcVzMSIbLMDXWeoi9SHYctXuKYC5uJUT3eQ==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-5.0.6.tgz", + "integrity": "sha512-1kGbqVQBZPAAu4+/R1XxPQKP0ydbNYoLAr4l0ZO2bMV0kLyLW4I1gAk++qBLWt7DPORTzmWRMsCZe86gDjShJA==", "license": "Apache-2.0", "dependencies": { - "@grpc/grpc-js": "^1.10.9", - "@grpc/proto-loader": "^0.7.13", - "@types/long": "^4.0.0", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "google-auth-library": "^9.3.0", - "node-fetch": "^2.7.0", + "@grpc/grpc-js": "^1.12.6", + "@grpc/proto-loader": "^0.8.0", + "duplexify": "^4.1.3", + "google-auth-library": "^10.1.0", + "google-logging-utils": "^1.1.1", + "node-fetch": "^3.3.2", "object-hash": "^3.0.0", - "proto3-json-serializer": "^2.0.2", - "protobufjs": "^7.3.2", - "retry-request": "^7.0.0", - "uuid": "^9.0.1" + "proto3-json-serializer": "^3.0.0", + "protobufjs": "^7.5.3", + "retry-request": "^8.0.0", + "rimraf": "^5.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/google-gax/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/google-gax/node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" }, "engines": { "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/google-gax/node_modules/@grpc/proto-loader": { - "version": "0.7.15", - "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz", - "integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==", - "license": "Apache-2.0", + "node_modules/google-gax/node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "license": "ISC", "dependencies": { - "lodash.camelcase": "^4.3.0", - "long": "^5.0.0", - "protobufjs": "^7.2.5", - "yargs": "^17.7.2" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, "bin": { - "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-gax/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=6" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/google-gax/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" - ], + "node_modules/google-gax/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "license": "MIT", + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/google-gax/node_modules/rimraf": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.10.tgz", + "integrity": "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==", + "license": "ISC", + "dependencies": { + "glob": "^10.3.7" + }, "bin": { - "uuid": "dist/bin/uuid" + "rimraf": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/google-gax/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/google-logging-utils": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", - "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-1.1.3.tgz", + "integrity": "sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==", "license": "Apache-2.0", "engines": { "node": ">=14" @@ -6155,16 +6443,16 @@ "license": "ISC" }, "node_modules/gtoken": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", - "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-8.0.0.tgz", + "integrity": "sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==", "license": "MIT", "dependencies": { - "gaxios": "^6.0.0", + "gaxios": "^7.0.0", "jws": "^4.0.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, "node_modules/has-bigints": { @@ -7301,6 +7589,21 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jaeger-client": { "version": "3.19.0", "resolved": "https://registry.npmjs.org/jaeger-client/-/jaeger-client-3.19.0.tgz", @@ -7849,6 +8152,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/mock-property": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/mock-property/-/mock-property-1.1.0.tgz", @@ -8504,6 +8816,12 @@ "node": ">=8" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, "node_modules/parent-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-2.0.0.tgz", @@ -8593,6 +8911,28 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", @@ -8762,15 +9102,15 @@ } }, "node_modules/proto3-json-serializer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-2.0.2.tgz", - "integrity": "sha512-SAzp/O4Yh02jGdRc+uIrGoe87dkN/XtwxfZ4ZyafJHymd79ozp5VG5nyZ7ygqPM5+cpLDjjGnYFUkngonyDPOQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-3.0.4.tgz", + "integrity": "sha512-E1sbAYg3aEbXrq0n1ojJkRHQJGE1kaE/O6GLA94y8rnJBfgvOPTOd1b9hOceQK1FFZI9qMh1vBERCyO2ifubcw==", "license": "Apache-2.0", "dependencies": { - "protobufjs": "^7.2.5" + "protobufjs": "^7.4.0" }, "engines": { - "node": ">=14.0.0" + "node": ">=18" } }, "node_modules/protobufjs": { @@ -9142,17 +9482,16 @@ } }, "node_modules/retry-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", - "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-8.0.2.tgz", + "integrity": "sha512-JzFPAfklk1kjR1w76f0QOIhoDkNkSqW8wYKT08n9yysTmZfB+RQ2QoXoTAeOi1HD9ZipTyTAZg3c4pM/jeqgSw==", "license": "MIT", "dependencies": { - "@types/request": "^2.48.8", "extend": "^3.0.2", - "teeny-request": "^9.0.0" + "teeny-request": "^10.0.0" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/rimraf": { @@ -9858,6 +10197,21 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.10", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", @@ -9929,6 +10283,19 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -10084,19 +10451,18 @@ } }, "node_modules/teeny-request": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", - "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-10.1.0.tgz", + "integrity": "sha512-3ZnLvgWF29jikg1sAQ1g0o+lr5JX6sVgYvfUJazn7ZjJroDBUTWp44/+cFVX0bULjv4vci+rBD+oGVAkWqhUbw==", "license": "Apache-2.0", "dependencies": { "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.9", - "stream-events": "^1.0.5", - "uuid": "^9.0.0" + "node-fetch": "^3.3.2", + "stream-events": "^1.0.5" }, "engines": { - "node": ">=14" + "node": ">=18" } }, "node_modules/teeny-request/node_modules/agent-base": { @@ -10124,17 +10490,22 @@ "node": ">= 6" } }, - "node_modules/teeny-request/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" - ], + "node_modules/teeny-request/node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" } }, "node_modules/test-exclude": { @@ -10845,6 +11216,24 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index f8e655da..ddc60f50 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@jambonz/http-health-check": "^0.0.1", "@jambonz/mw-registrar": "^0.2.7", "@jambonz/realtimedb-helpers": "^0.8.15", - "@jambonz/speech-utils": "^0.2.26", + "@jambonz/speech-utils": "^0.2.30", "@jambonz/stats-collector": "^0.1.10", "@jambonz/time-series": "^0.2.15", "@jambonz/verb-specifications": "^0.0.125", From 325af42946fec3839ab44f36c423ab21d8aa9df5 Mon Sep 17 00:00:00 2001 From: Hoan Luu Huu <110280845+xquanluu@users.noreply.github.com> Date: Fri, 23 Jan 2026 22:11:58 +0700 Subject: [PATCH 09/15] speechmatics support end_of_utterance_silence_trigger (#1499) * speechmatics support end_of_utterance_silence_trigger * wip --- lib/utils/transcription-utils.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/utils/transcription-utils.js b/lib/utils/transcription-utils.js index 5014669c..1c3662a4 100644 --- a/lib/utils/transcription-utils.js +++ b/lib/utils/transcription-utils.js @@ -1375,7 +1375,9 @@ module.exports = (logger) => { speechmaticsOptions.transcription_config.audio_filtering_config.volume_threshold}), ...(speechmaticsOptions.transcription_config?.transcript_filtering_config?.remove_disfluencies && {SPEECHMATICS_REMOVE_DISFLUENCIES: - speechmaticsOptions.transcription_config.transcript_filtering_config.remove_disfluencies}) + speechmaticsOptions.transcription_config.transcript_filtering_config.remove_disfluencies}), + SPEECHMATICS_END_OF_UTTERANCE_SILENCE_TRIGGER: + speechmaticsOptions.transcription_config?.conversation_config?.end_of_utterance_silence_trigger || 0.5 }; } else if (vendor.startsWith('custom:')) { From faee30278b8dea3f7be939c1dd59442c0ee1d73b Mon Sep 17 00:00:00 2001 From: Hoan Luu Huu <110280845+xquanluu@users.noreply.github.com> Date: Tue, 27 Jan 2026 20:18:47 +0700 Subject: [PATCH 10/15] support mod_google_tts_streaming (#1409) * support mod_google_tts_streaming * wip * wip --- lib/tasks/tts-task.js | 7 +++++++ lib/utils/constants.json | 5 +++++ lib/utils/tts-streaming-buffer.js | 1 + 3 files changed, 13 insertions(+) diff --git a/lib/tasks/tts-task.js b/lib/tasks/tts-task.js index 757eac42..4e4ea91a 100644 --- a/lib/tasks/tts-task.js +++ b/lib/tasks/tts-task.js @@ -157,6 +157,13 @@ class TtsTask extends Task { ...(reduceLatency && {RIMELABS_TTS_STREAMING_REDUCE_LATENCY: reduceLatency}) }; break; + case 'google': + obj = { + GOOGLE_TTS_LANGUAGE_CODE: language, + GOOGLE_TTS_VOICE_NAME: voice, + GOOGLE_APPLICATION_CREDENTIALS: JSON.stringify(credentials.credentials) + }; + break; default: if (vendor.startsWith('custom:')) { const use_tls = custom_tts_streaming_url.startsWith('wss://'); diff --git a/lib/utils/constants.json b/lib/utils/constants.json index f771d8d7..89203ac2 100644 --- a/lib/utils/constants.json +++ b/lib/utils/constants.json @@ -311,6 +311,11 @@ "ConnectFailure": "deepgram_tts_streaming::connect_failed", "Connect": "deepgram_tts_streaming::connect" }, + "GoogleTtsStreamingEvents": { + "Empty": "google_tts_streaming::empty", + "ConnectFailure": "google_tts_streaming::connect_failed", + "Connect": "google_tts_streaming::connect" + }, "CartesiaTtsStreamingEvents": { "Empty": "cartesia_tts_streaming::empty", "ConnectFailure": "cartesia_tts_streaming::connect_failed", diff --git a/lib/utils/tts-streaming-buffer.js b/lib/utils/tts-streaming-buffer.js index 44cf9c45..c8e62b55 100644 --- a/lib/utils/tts-streaming-buffer.js +++ b/lib/utils/tts-streaming-buffer.js @@ -421,6 +421,7 @@ class TtsStreamingBuffer extends Emitter { 'cartesia', 'elevenlabs', 'rimelabs', + 'google', 'custom' ].forEach((vendor) => { const eventClassName = `${vendor.charAt(0).toUpperCase() + vendor.slice(1)}TtsStreamingEvents`; From 96d72216e2eb89e641b66bc75a0a66b1fe85df1c Mon Sep 17 00:00:00 2001 From: Hoan Luu Huu <110280845+xquanluu@users.noreply.github.com> Date: Wed, 28 Jan 2026 20:01:53 +0700 Subject: [PATCH 11/15] support google s2s host, version, sessionResumption (#1498) --- lib/tasks/llm/llms/google_s2s.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/tasks/llm/llms/google_s2s.js b/lib/tasks/llm/llms/google_s2s.js index 415ad26f..87fe5b44 100644 --- a/lib/tasks/llm/llms/google_s2s.js +++ b/lib/tasks/llm/llms/google_s2s.js @@ -36,6 +36,9 @@ class TaskLlmGoogle_S2S extends Task { this.model = this.parent.model || 'models/gemini-2.0-flash-live-001'; this.auth = this.parent.auth; this.connectionOptions = this.parent.connectOptions; + const {host, version} = this.connectionOptions || {}; + this.host = host; + this.version = version; const {apiKey} = this.auth || {}; if (!apiKey) throw new Error('auth.apiKey is required for Google S2S'); @@ -46,7 +49,7 @@ class TaskLlmGoogle_S2S extends Task { this.eventHook = this.data.eventHook; this.toolHook = this.data.toolHook; - const {setup} = this.data.llmOptions; + const {setup, sessionResumption} = this.data.llmOptions; if (typeof setup !== 'object') { throw new Error('llmOptions with an initial setup is required for Google S2S'); @@ -54,6 +57,7 @@ class TaskLlmGoogle_S2S extends Task { this.setup = { ...setup, model: this.model, + ...(sessionResumption && {sessionResumption}), // make sure output is always audio generationConfig: { ...(setup.generationConfig || {}), @@ -138,6 +142,10 @@ class TaskLlmGoogle_S2S extends Task { try { const args = [ep.uuid, 'session.create', this.apiKey]; + if (this.host) { + args.push(this.host); + if (this.version) args.push(this.version); + } await this._api(ep, args); } catch (err) { this.logger.error({err}, 'TaskLlmGoogle_S2S:_startListening'); From 6120dcbe966fe0583d190bec69de19d7fa70c68f Mon Sep 17 00:00:00 2001 From: Hoan Luu Huu <110280845+xquanluu@users.noreply.github.com> Date: Wed, 28 Jan 2026 20:09:01 +0700 Subject: [PATCH 12/15] support openai transcribe turn_detection.eagerness (#1496) --- lib/utils/transcription-utils.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/utils/transcription-utils.js b/lib/utils/transcription-utils.js index 1c3662a4..0a8ed93c 100644 --- a/lib/utils/transcription-utils.js +++ b/lib/utils/transcription-utils.js @@ -1310,6 +1310,9 @@ module.exports = (logger) => { ...(openaiOptions.turn_detection.silence_duration_ms && { OPENAI_TURN_DETECTION_SILENCE_DURATION_MS: openaiOptions.turn_detection.silence_duration_ms }), + ...(openaiOptions.turn_detection.eagerness && { + OPENAI_TURN_DETECTION_EAGERNESS: openaiOptions.turn_detection.eagerness + }) }; } } From 96cdc2936ba087a8f70c5f57700f721e6a9467d6 Mon Sep 17 00:00:00 2001 From: Sam Machin Date: Thu, 29 Jan 2026 14:22:00 +0000 Subject: [PATCH 13/15] invert default (#1507) --- lib/tasks/dial.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/dial.js b/lib/tasks/dial.js index 3e96b54b..f8332f7d 100644 --- a/lib/tasks/dial.js +++ b/lib/tasks/dial.js @@ -550,7 +550,7 @@ class TaskDial extends Task { let sbcAddress = this.proxy || getSBC(); const teamsInfo = {}; let fqdn; - const forwardPAI = this.forwardPAI ?? JAMBONZ_DIAL_PAI_HEADER; // dial verb overides env var + const forwardPAI = this.forwardPAI ?? !JAMBONZ_DIAL_PAI_HEADER; // dial verb overides env var this.logger.debug(forwardPAI, 'forwardPAI value'); if (!sbcAddress) throw new Error('no SBC found for outbound call'); this.headers = { From ff5f9acaf8b246b4e8c1b0df138dc899a792e294 Mon Sep 17 00:00:00 2001 From: Dave Horton Date: Thu, 29 Jan 2026 13:47:53 -0500 Subject: [PATCH 14/15] on dial do not reinvite A leg on answer if already answered and we are anchoring media (#1508) --- lib/tasks/dial.js | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/tasks/dial.js b/lib/tasks/dial.js index f8332f7d..0edfc4ff 100644 --- a/lib/tasks/dial.js +++ b/lib/tasks/dial.js @@ -195,6 +195,9 @@ class TaskDial extends Task { async exec(cs) { await super.exec(cs); + /* capture whether A leg was already answered before this dial task started */ + this._aLegAlreadyAnswered = !!cs.dlg; + if (this.data.anchorMedia && this.data.exitMediaPath) { this.logger.info('Dial:exec - incompatible anchorMedia and exitMediaPath are both set, will obey anchorMedia'); delete this.data.exitMediaPath; @@ -872,8 +875,12 @@ class TaskDial extends Task { this.sd = sd; this.callSid = sd.callSid; if (this.earlyMedia) { - debug('Dial:_selectSingleDial propagating answer supervision on A leg now that B is connected'); - await cs.propagateAnswer(); + if (this._aLegAlreadyAnswered) { + debug('Dial:_selectSingleDial A leg was already answered, skipping propagateAnswer'); + } else { + debug('Dial:_selectSingleDial propagating answer supervision on A leg now that B is connected'); + await cs.propagateAnswer(); + } } if (this.timeLimit) { this.timerMaxCallDuration = setTimeout(this._onMaxCallDuration.bind(this, cs), this.timeLimit * 1000); From ceb9a7a3bd96216fda261c7ffcacacc931529f37 Mon Sep 17 00:00:00 2001 From: Matt Hertogs Date: Thu, 29 Jan 2026 10:58:14 -0800 Subject: [PATCH 15/15] Fix boostAudioSignal parameter in Update Call REST API (#1490) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corrects the parameter passed to _lccBoostAudioSignal to use opts.boostAudioSignal instead of the entire opts object, ensuring the boostAudioSignal option works correctly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Sonnet 4.5 --- lib/session/call-session.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/session/call-session.js b/lib/session/call-session.js index 438a7a42..6b3bcaaa 100644 --- a/lib/session/call-session.js +++ b/lib/session/call-session.js @@ -2028,7 +2028,7 @@ Duration=${duration} ` return this._lccDub(opts.dub, callSid); } else if (opts.boostAudioSignal) { - return this._lccBoostAudioSignal(opts, callSid); + return this._lccBoostAudioSignal(opts.boostAudioSignal, callSid); } else if (opts.media_path) { return this._lccMediaPath(opts.media_path, callSid);