mirror of
https://github.com/jambonz/jambonz-feature-server.git
synced 2026-03-29 03:18:27 +00:00
Compare commits
1 Commits
main
...
ultravox_b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a726aa8658 |
@@ -1,10 +1,10 @@
|
||||
FROM --platform=linux/amd64 node:24-alpine AS base
|
||||
FROM --platform=linux/amd64 node:20-alpine as base
|
||||
|
||||
RUN apk --update --no-cache add --virtual .builds-deps build-base python3
|
||||
|
||||
WORKDIR /opt/app/
|
||||
|
||||
FROM base AS build
|
||||
FROM base as build
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
|
||||
|
||||
@@ -18,7 +18,6 @@ const { createCallSchema, customSanitizeFunction } = require('../schemas/create-
|
||||
const { selectHostPort } = require('../../utils/network');
|
||||
const { JAMBONES_DIAL_SBC_FOR_REGISTERED_USER } = require('../../config');
|
||||
const { createMediaEndpoint } = require('../../utils/media-endpoint');
|
||||
const { DbErrorBadRequest } = require('../utils/errors');
|
||||
|
||||
const removeNullProperties = (obj) => (Object.keys(obj).forEach((key) => obj[key] === null && delete obj[key]), obj);
|
||||
const removeNulls = (req, res, next) => {
|
||||
@@ -123,16 +122,8 @@ router.post('/',
|
||||
}
|
||||
break;
|
||||
case 'user':
|
||||
let targetName = target.name;
|
||||
if (!targetName.includes('@')) {
|
||||
if (!account.sip_realm) {
|
||||
throw new DbErrorBadRequest('no sip realm configured for account');
|
||||
}
|
||||
logger.debug(`appending sip realm ${account.sip_realm} to target name ${targetName}`);
|
||||
targetName = `${targetName}@${account.sip_realm}`;
|
||||
}
|
||||
uri = `sip:${targetName}`;
|
||||
to = targetName;
|
||||
uri = `sip:${target.name}`;
|
||||
to = target.name;
|
||||
if (target.overrideTo) {
|
||||
Object.assign(opts.headers, {
|
||||
'X-Override-To': target.overrideTo
|
||||
@@ -340,12 +331,6 @@ router.post('/',
|
||||
}
|
||||
});
|
||||
connectStream(dlg.remote.sdp);
|
||||
|
||||
/* ensure sbcCallid is set even if no provisional response was received */
|
||||
if (!cs.callInfo.sbcCallid && dlg.res.has('X-CID')) {
|
||||
cs.callInfo.sbcCallid = dlg.res.get('X-CID');
|
||||
}
|
||||
|
||||
cs.emit('callStatusChange', {
|
||||
callStatus: CallStatus.InProgress,
|
||||
sipStatus: 200,
|
||||
|
||||
@@ -6,8 +6,8 @@ function sysError(logger, res, err) {
|
||||
return res.status(400).json({msg: err.message});
|
||||
}
|
||||
if (err instanceof DbErrorUnprocessableRequest) {
|
||||
logger.info({message: err.message}, 'unprocessable request');
|
||||
return res.status(422).send(err.message);
|
||||
logger.info(err, 'unprocessable request');
|
||||
return res.status(422).json({msg: err.message});
|
||||
}
|
||||
if (err.code === 'ER_DUP_ENTRY') {
|
||||
logger.info(err, 'duplicate entry on insert');
|
||||
|
||||
@@ -2315,8 +2315,7 @@ Duration=${duration} `
|
||||
break;
|
||||
|
||||
case 'mute:status':
|
||||
const status = typeof (data) === 'string' ? data : data.mute_status;
|
||||
this._lccMuteStatus(status === 'mute', call_sid);
|
||||
this._lccMuteStatus(data, call_sid);
|
||||
break;
|
||||
|
||||
case 'conf:mute-status':
|
||||
@@ -2476,7 +2475,7 @@ Duration=${duration} `
|
||||
this.logger.info(`allocated endpoint ${ep.uuid}`);
|
||||
|
||||
this.ep.on('destroy', () => {
|
||||
this.logger.debug(`endpoint was destroyed!! ${this.ep?.uuid}`);
|
||||
this.logger.debug(`endpoint was destroyed!! ${this.ep.uuid}`);
|
||||
});
|
||||
|
||||
if (this.direction === CallDirection.Inbound || this.application?.transferredCall) {
|
||||
@@ -2785,7 +2784,6 @@ Duration=${duration} `
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.error(err, 'Error handling reinvite');
|
||||
res.send(err.status || 500);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -469,7 +469,6 @@ class Conference extends Task {
|
||||
assert (cs.isInConference);
|
||||
|
||||
const mute = opts.conf_mute_status === 'mute';
|
||||
this.logger.info(`Conference:doConferenceMute ${mute ? 'muting' : 'unmuting'} member`);
|
||||
this.ep.api(`conference ${this.confName} ${mute ? 'mute' : 'unmute'} ${this.memberId}`)
|
||||
.catch((err) => this.logger.info({err}, 'Error muting or unmuting participant'));
|
||||
}
|
||||
@@ -571,8 +570,8 @@ class Conference extends Task {
|
||||
/**
|
||||
* mute or unmute side of the call
|
||||
*/
|
||||
async mute(callSid, doMute) {
|
||||
this.doConferenceMute(this.callSession, {conf_mute_status: doMute ? 'mute' : 'unmute'});
|
||||
mute(callSid, doMute) {
|
||||
this.doConferenceMute(this.callSession, {conf_mute_status: doMute});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1424,26 +1424,7 @@ class TaskGather extends SttTask {
|
||||
returnedVerbs = await this.performAction({speech:evt, reason: 'stt-low-confidence', ...latencies});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.info({err}, 'TaskGather:_resolve - error performing action');
|
||||
this.notifyError({msg: 'invalid actionHook response', details: err.message});
|
||||
const {writeAlerts, AlertType} = this.cs.srf.locals;
|
||||
writeAlerts({
|
||||
account_sid: this.cs.accountSid,
|
||||
alert_type: AlertType.INVALID_APP_PAYLOAD,
|
||||
target_sid: this.cs.callSid,
|
||||
message: `actionHook returned invalid verb syntax: ${err.message}`
|
||||
}).catch((err) => this.logger.info({err}, 'TaskGather:_resolve - error generating alert'));
|
||||
try {
|
||||
const obj = Object.assign({}, this.cs.callInfo.toJSON(), {
|
||||
error: 'invalid actionHook response',
|
||||
reason: err.message
|
||||
});
|
||||
await this.cs.notifier.request('call:status', this.cs.call_status_hook, obj);
|
||||
} catch (statusErr) {
|
||||
this.logger.info({statusErr}, 'TaskGather:_resolve - error sending statusHook');
|
||||
}
|
||||
}
|
||||
} catch (err) { /*already logged error*/ }
|
||||
|
||||
// Gather got response from hook, cancel actionHookDelay processing
|
||||
if (this.cs.actionHookDelayProcessor) {
|
||||
|
||||
@@ -146,6 +146,23 @@ class TaskLlmUltravox_S2S extends Task {
|
||||
return data;
|
||||
}
|
||||
|
||||
async deleteCall() {
|
||||
if (!this.callId) return;
|
||||
const baseUrl = 'https://api.ultravox.ai';
|
||||
const url = `${baseUrl}/api/calls/${this.callId}`;
|
||||
try {
|
||||
const {statusCode} = await request(url, {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'X-API-Key': this.apiKey
|
||||
}
|
||||
});
|
||||
this.logger.debug({statusCode, callId: this.callId}, 'Ultravox call deleted');
|
||||
} catch (err) {
|
||||
this.logger.info({err, callId: this.callId}, 'TaskLlmUltravox_S2S:deleteCall - error');
|
||||
}
|
||||
}
|
||||
|
||||
_unregisterHandlers(ep) {
|
||||
this.removeCustomEventListeners();
|
||||
ep.removeAllListeners('dtmf');
|
||||
@@ -164,6 +181,7 @@ class TaskLlmUltravox_S2S extends Task {
|
||||
|
||||
try {
|
||||
const data = await this.createCall();
|
||||
this.callId = data.callId;
|
||||
const {joinUrl} = data;
|
||||
// split the joinUrl into host and path
|
||||
const {host, pathname, search} = new URL(joinUrl);
|
||||
@@ -188,6 +206,8 @@ class TaskLlmUltravox_S2S extends Task {
|
||||
|
||||
await this.awaitTaskDone();
|
||||
|
||||
await this.deleteCall();
|
||||
|
||||
/* note: the parent llm verb started the span, which is why this is necessary */
|
||||
await this.parent.performAction(this.results);
|
||||
|
||||
@@ -200,6 +220,8 @@ class TaskLlmUltravox_S2S extends Task {
|
||||
this._api(cs.ep, [cs.ep.uuid, SessionDelete])
|
||||
.catch((err) => this.logger.info({err}, 'TaskLlmUltravox_S2S:kill - error deleting session'));
|
||||
|
||||
this.deleteCall();
|
||||
|
||||
this.notifyTaskDone();
|
||||
}
|
||||
|
||||
|
||||
@@ -234,11 +234,6 @@ class SingleDialer extends Emitter {
|
||||
await connectStream(this.dlg.remote.sdp, opts.isVideoCall);
|
||||
this.dlg.callSid = this.callSid;
|
||||
this.inviteInProgress = null;
|
||||
|
||||
/* ensure sbcCallid is set even if no provisional response was received */
|
||||
if (!this.callInfo.sbcCallid && this.dlg.res.has('X-CID')) {
|
||||
this.callInfo.sbcCallid = this.dlg.res.get('X-CID');
|
||||
}
|
||||
this.emit('callStatusChange', {
|
||||
sipStatus: 200,
|
||||
sipReason: 'OK',
|
||||
@@ -295,7 +290,6 @@ class SingleDialer extends Emitter {
|
||||
}
|
||||
} catch (err) {
|
||||
this.logger.error(err, 'Error handling reinvite');
|
||||
res.send(err.status || 500);
|
||||
}
|
||||
})
|
||||
.on('refer', (req, res) => {
|
||||
|
||||
47
package-lock.json
generated
47
package-lock.json
generated
@@ -30,21 +30,21 @@
|
||||
"@opentelemetry/sdk-trace-node": "^1.23.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.23.0",
|
||||
"bent": "^7.3.12",
|
||||
"debug": "^4.4.3",
|
||||
"debug": "^4.3.4",
|
||||
"deepcopy": "^2.1.0",
|
||||
"drachtio-fsmrf": "^4.1.2",
|
||||
"drachtio-srf": "^5.0.20",
|
||||
"express": "^4.22.1",
|
||||
"express": "^4.19.2",
|
||||
"express-validator": "^7.0.1",
|
||||
"moment": "^2.30.1",
|
||||
"parse-url": "^9.2.0",
|
||||
"pino": "^10.3.1",
|
||||
"pino": "^10.1.0",
|
||||
"polly-ssml-split": "^0.1.0",
|
||||
"sdp-transform": "^2.15.0",
|
||||
"short-uuid": "^5.1.0",
|
||||
"sinon": "^17.0.1",
|
||||
"to-snake-case": "^1.0.0",
|
||||
"undici": "^7.24.5",
|
||||
"undici": "^7.5.0",
|
||||
"verify-aws-sns-signature": "^0.1.0",
|
||||
"ws": "^8.18.0",
|
||||
"xml2js": "^0.6.2"
|
||||
@@ -58,7 +58,7 @@
|
||||
"tape": "^5.7.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20.x"
|
||||
"node": ">= 18.x"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"bufferutil": "^4.0.8",
|
||||
@@ -9015,31 +9015,31 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/pino": {
|
||||
"version": "10.3.1",
|
||||
"resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz",
|
||||
"integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==",
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pino/-/pino-10.1.0.tgz",
|
||||
"integrity": "sha512-0zZC2ygfdqvqK8zJIr1e+wT1T/L+LF6qvqvbzEQ6tiMAoTqEVK9a1K3YRu8HEUvGEvNqZyPJTtb2sNIoTkB83w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@pinojs/redact": "^0.4.0",
|
||||
"atomic-sleep": "^1.0.0",
|
||||
"on-exit-leak-free": "^2.1.0",
|
||||
"pino-abstract-transport": "^3.0.0",
|
||||
"pino-abstract-transport": "^2.0.0",
|
||||
"pino-std-serializers": "^7.0.0",
|
||||
"process-warning": "^5.0.0",
|
||||
"quick-format-unescaped": "^4.0.3",
|
||||
"real-require": "^0.2.0",
|
||||
"safe-stable-stringify": "^2.3.1",
|
||||
"sonic-boom": "^4.0.1",
|
||||
"thread-stream": "^4.0.0"
|
||||
"thread-stream": "^3.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"pino": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/pino-abstract-transport": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz",
|
||||
"integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==",
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz",
|
||||
"integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"split2": "^4.0.0"
|
||||
@@ -10586,15 +10586,12 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/thread-stream": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz",
|
||||
"integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==",
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.1.0.tgz",
|
||||
"integrity": "sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"real-require": "^0.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/thriftrw": {
|
||||
@@ -10860,9 +10857,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "7.24.5",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.24.5.tgz",
|
||||
"integrity": "sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==",
|
||||
"version": "7.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-7.21.0.tgz",
|
||||
"integrity": "sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20.18.1"
|
||||
@@ -10965,9 +10962,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/utf-8-validate": {
|
||||
"version": "6.0.6",
|
||||
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.6.tgz",
|
||||
"integrity": "sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA==",
|
||||
"version": "6.0.5",
|
||||
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-6.0.5.tgz",
|
||||
"integrity": "sha512-EYZR+OpIXp9Y1eG1iueg8KRsY8TuT8VNgnanZ0uA3STqhHQTLwbl+WX76/9X5OY12yQubymBpaBSmMPkSTQcKA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
|
||||
10
package.json
10
package.json
@@ -3,7 +3,7 @@
|
||||
"version": "0.9.5",
|
||||
"main": "app.js",
|
||||
"engines": {
|
||||
"node": ">= 20.x"
|
||||
"node": ">= 18.x"
|
||||
},
|
||||
"keywords": [
|
||||
"sip",
|
||||
@@ -46,21 +46,21 @@
|
||||
"@opentelemetry/sdk-trace-node": "^1.23.0",
|
||||
"@opentelemetry/semantic-conventions": "^1.23.0",
|
||||
"bent": "^7.3.12",
|
||||
"debug": "^4.4.3",
|
||||
"debug": "^4.3.4",
|
||||
"deepcopy": "^2.1.0",
|
||||
"drachtio-fsmrf": "^4.1.2",
|
||||
"drachtio-srf": "^5.0.20",
|
||||
"express": "^4.22.1",
|
||||
"express": "^4.19.2",
|
||||
"express-validator": "^7.0.1",
|
||||
"moment": "^2.30.1",
|
||||
"parse-url": "^9.2.0",
|
||||
"pino": "^10.3.1",
|
||||
"pino": "^10.1.0",
|
||||
"polly-ssml-split": "^0.1.0",
|
||||
"sdp-transform": "^2.15.0",
|
||||
"short-uuid": "^5.1.0",
|
||||
"sinon": "^17.0.1",
|
||||
"to-snake-case": "^1.0.0",
|
||||
"undici": "^7.24.5",
|
||||
"undici": "^7.5.0",
|
||||
"verify-aws-sns-signature": "^0.1.0",
|
||||
"ws": "^8.18.0",
|
||||
"xml2js": "^0.6.2"
|
||||
|
||||
Reference in New Issue
Block a user