Compare commits

...

2 Commits

Author SHA1 Message Date
Hoan Luu Huu
8267ddaffd support elevenlabs different endpoint (#502)
* support elevenlabs different endpoint

* wip

* wip

* wip
2025-10-09 08:20:11 -04:00
Hoan Luu Huu
c3d12fafee support deeepgram influx (#501)
* support deeepgram influx

* update verb specification
2025-10-03 10:09:19 -04:00
5 changed files with 41 additions and 30 deletions

View File

@@ -162,6 +162,7 @@ const encryptCredential = (obj) => {
voice_engine,
engine_version,
service_version,
api_uri,
options
} = obj;
@@ -238,10 +239,10 @@ const encryptCredential = (obj) => {
});
return encrypt(resembleData);
case 'deepgramriver':
assert(api_key, 'invalid deepgram river speech credential: api_key is required');
const deepgramriverData = JSON.stringify({api_key});
return encrypt(deepgramriverData);
case 'deepgramflux':
assert(api_key, 'invalid deepgram flux speech credential: api_key is required');
const deepgramfluxData = JSON.stringify({api_key});
return encrypt(deepgramfluxData);
case 'ibm':
const ibmData = JSON.stringify({tts_api_key, tts_region, stt_api_key, stt_region, instance_id});
@@ -265,7 +266,11 @@ const encryptCredential = (obj) => {
case 'elevenlabs':
assert(api_key, 'invalid elevenLabs speech credential: api_key is required');
assert(model_id, 'invalid elevenLabs speech credential: model_id is required');
const elevenlabsData = JSON.stringify({api_key, model_id, options});
const elevenlabsData = JSON.stringify({
api_key,
model_id,
...(api_uri && {api_uri}),
options});
return encrypt(elevenlabsData);
case 'speechmatics':
@@ -537,7 +542,8 @@ router.put('/:sid', async(req, res) => {
service_version,
speechmatics_stt_uri,
resemble_tts_use_tls,
resemble_tts_uri
resemble_tts_uri,
api_uri
} = req.body;
const newCred = {
@@ -572,7 +578,8 @@ router.put('/:sid', async(req, res) => {
service_version,
speechmatics_stt_uri,
resemble_tts_uri,
resemble_tts_use_tls
resemble_tts_use_tls,
api_uri
};
logger.info({o, newCred}, 'updating speech credential with this new credential');
obj.credential = encryptCredential(newCred);
@@ -806,7 +813,7 @@ router.get('/:sid/test', async(req, res) => {
}
}
}
else if (cred.vendor === 'deepgramriver') {
else if (cred.vendor === 'deepgramflux') {
const {api_key} = credential;
if (cred.use_for_stt && api_key) {
try {
@@ -865,10 +872,10 @@ router.get('/:sid/test', async(req, res) => {
}
}
} else if (cred.vendor === 'elevenlabs') {
const {api_key, model_id} = credential;
const {api_key, model_id, api_uri} = credential;
if (cred.use_for_tts) {
try {
await testElevenlabs(logger, {api_key, model_id});
await testElevenlabs(logger, {api_key, model_id, api_uri});
results.tts.status = 'ok';
SpeechCredential.ttsTestResult(sid, true);
} catch (err) {

View File

@@ -316,8 +316,8 @@ const testWellSaidTts = async(logger, credentials) => {
};
const testElevenlabs = async(logger, credentials) => {
const {api_key, model_id} = credentials;
const response = await fetch('https://api.elevenlabs.io/v1/text-to-speech/21m00Tcm4TlvDq8ikWAM', {
const {api_key, model_id, api_uri} = credentials;
const response = await fetch(`https://${api_uri || 'api.elevenlabs.io'}/v1/text-to-speech/21m00Tcm4TlvDq8ikWAM`, {
method: 'POST',
headers: {
'xi-api-key': api_key,
@@ -686,7 +686,7 @@ function decryptCredential(obj, credential, logger, isObscureKey = true) {
obj.deepgram_tts_uri = o.deepgram_tts_uri;
obj.model_id = o.model_id;
}
else if ('deepgramriver' === obj.vendor) {
else if ('deepgramflux' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
}
@@ -714,6 +714,7 @@ function decryptCredential(obj, credential, logger, isObscureKey = true) {
const o = JSON.parse(decrypt(credential));
obj.api_key = isObscureKey ? obscureKey(o.api_key) : o.api_key;
obj.model_id = o.model_id;
obj.api_uri = o.api_uri;
obj.options = o.options;
} else if ('playht' === obj.vendor) {
const o = JSON.parse(decrypt(credential));
@@ -1014,10 +1015,12 @@ async function getLanguagesVoicesForElevenlabs(credential) {
'xi-api-key': credential.api_key
};
const getModelPromise = fetch('https://api.elevenlabs.io/v1/models', {
const api_uri = credential.api_uri || 'api.elevenlabs.io';
const getModelPromise = fetch(`https://${api_uri}/v1/models`, {
headers
});
const getVoicePromise = fetch('https://api.elevenlabs.io/v1/voices', {
const getVoicePromise = fetch(`https://${api_uri}/v1/voices`, {
headers
});
const [langResp, voiceResp] = await Promise.all([getModelPromise, getVoicePromise]);

16
package-lock.json generated
View File

@@ -20,9 +20,9 @@
"@jambonz/lamejs": "^1.2.2",
"@jambonz/mw-registrar": "^0.2.7",
"@jambonz/realtimedb-helpers": "^0.8.15",
"@jambonz/speech-utils": "^0.2.23",
"@jambonz/speech-utils": "^0.2.25",
"@jambonz/time-series": "^0.2.8",
"@jambonz/verb-specifications": "^0.0.111",
"@jambonz/verb-specifications": "^0.0.115",
"@soniox/soniox-node": "^1.2.2",
"ajv": "^8.17.1",
"argon2": "^0.40.1",
@@ -4086,9 +4086,9 @@
}
},
"node_modules/@jambonz/speech-utils": {
"version": "0.2.23",
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.2.23.tgz",
"integrity": "sha512-o28IBoKzdnQoUUSC1XljHVkDPWhkTH+rFnI9OWYC6p1/f8px++4Y23/JMIAJVbxqKB1CIf531JhTwy4tCnQP7g==",
"version": "0.2.25",
"resolved": "https://registry.npmjs.org/@jambonz/speech-utils/-/speech-utils-0.2.25.tgz",
"integrity": "sha512-dtMY4SoGhpvKM6slPvTlMz61Fd0Rxnajwm3sJVxlAYZnFtzfIvqg9rrYUN4h05tJbo/4TLs15/dVeicw7dTnew==",
"license": "MIT",
"dependencies": {
"23": "^0.0.0",
@@ -4254,9 +4254,9 @@
}
},
"node_modules/@jambonz/verb-specifications": {
"version": "0.0.111",
"resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.111.tgz",
"integrity": "sha512-P2lDki5wCHGPPXpPTeMdht4NfGujF6NrLyrI8o/4U6l+2elfc/1mKl/Lz/PWGo9rQM4mTOVIhkk9ECo0NvOVoA==",
"version": "0.0.115",
"resolved": "https://registry.npmjs.org/@jambonz/verb-specifications/-/verb-specifications-0.0.115.tgz",
"integrity": "sha512-T2J5skCZBADaxePkFqpm0JYwr5Iu6e9zgvk6j0/4vD6lwK29GSoSdk2LCKUY6VH2GYdaN6r3dyIw4xpFVXuMfQ==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.4",

View File

@@ -31,9 +31,9 @@
"@jambonz/lamejs": "^1.2.2",
"@jambonz/mw-registrar": "^0.2.7",
"@jambonz/realtimedb-helpers": "^0.8.15",
"@jambonz/speech-utils": "^0.2.23",
"@jambonz/speech-utils": "^0.2.25",
"@jambonz/time-series": "^0.2.8",
"@jambonz/verb-specifications": "^0.0.111",
"@jambonz/verb-specifications": "^0.0.115",
"@soniox/soniox-node": "^1.2.2",
"ajv": "^8.17.1",
"argon2": "^0.40.1",

View File

@@ -656,7 +656,8 @@ test('speech credentials tests', async(t) => {
use_for_stt: true,
use_for_tts: false,
api_key: 'asdasdasdasddsadasda',
model_id: 'eleven_multilingual_v2'
model_id: 'eleven_multilingual_v2',
api_uri: 'api.elevenlabs.io'
}
});
t.ok(result.statusCode === 201, 'successfully added speech credential for elevenlabs');
@@ -930,21 +931,21 @@ test('speech credentials tests', async(t) => {
auth: authUser,
json: true,
body: {
vendor: 'deepgramriver',
vendor: 'deepgramflux',
use_for_tts: false,
use_for_stt: true,
api_key: 'api_key',
}
});
t.ok(result.statusCode === 201, 'successfully added speech credential for Verbio');
const deepgramriverSid = result.body.sid;
const deepgramfluxSid = result.body.sid;
/* delete the credential */
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${deepgramriverSid}`, {
result = await request.delete(`/Accounts/${account_sid}/SpeechCredentials/${deepgramfluxSid}`, {
auth: authUser,
resolveWithFullResponse: true,
});
t.ok(result.statusCode === 204, 'successfully deleted speech credential deepgramriver');
t.ok(result.statusCode === 204, 'successfully deleted speech credential deepgramflux');
/* Check google supportedLanguagesAndVoices */
result = await request.get(`/Accounts/${account_sid}/SpeechCredentials/speech/supportedLanguagesAndVoices?vendor=google`, {