@unboundcx/sdk 2.8.6 → 2.8.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -1
- package/base.js +43 -12
- package/index.js +21 -3
- package/package.json +5 -2
- package/proto/transcription.proto +207 -0
- package/services/ai/SttStream.js +311 -0
- package/services/ai/playbooks.js +958 -0
- package/services/ai.js +773 -52
- package/services/engagementMetrics.js +6 -2
- package/services/objects.js +12 -3
- package/services/phoneNumbers.js +88 -3
- package/services/sipEndpoints.js +105 -33
- package/services/storage.js +176 -6
- package/services/taskRouter/MetricsService.js +111 -0
- package/services/taskRouter/TaskRouterService.js +12 -0
- package/services/taskRouter/TaskService.js +838 -0
- package/services/taskRouter/WorkerService.js +394 -0
- package/services/taskRouter.js +6 -0
- package/services/video.js +145 -5
- package/services/voice.js +124 -67
- package/services/workflows.js +34 -7
package/services/ai.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
import { PlaybooksService } from './ai/playbooks.js';
|
|
2
|
+
|
|
1
3
|
export class AIService {
|
|
2
4
|
constructor(sdk) {
|
|
3
5
|
this.sdk = sdk;
|
|
4
6
|
this.generative = new GenerativeService(sdk);
|
|
5
7
|
this.tts = new TextToSpeechService(sdk);
|
|
8
|
+
this.stt = new SpeechToTextService(sdk);
|
|
9
|
+
this.extract = new ExtractService(sdk);
|
|
10
|
+
this.playbooks = new PlaybooksService(sdk);
|
|
6
11
|
}
|
|
7
12
|
}
|
|
8
13
|
|
|
@@ -14,36 +19,56 @@ export class GenerativeService {
|
|
|
14
19
|
async chat({
|
|
15
20
|
prompt,
|
|
16
21
|
messages,
|
|
22
|
+
systemPrompt,
|
|
17
23
|
relatedId,
|
|
24
|
+
provider,
|
|
18
25
|
model,
|
|
19
26
|
temperature,
|
|
20
27
|
subscriptionId,
|
|
21
28
|
stream,
|
|
22
|
-
|
|
29
|
+
isPlayground = false,
|
|
30
|
+
responseFormat,
|
|
23
31
|
}) {
|
|
24
32
|
this.sdk.validateParams(
|
|
25
|
-
{
|
|
33
|
+
{
|
|
34
|
+
stream,
|
|
35
|
+
temperature,
|
|
36
|
+
subscriptionId,
|
|
37
|
+
provider,
|
|
38
|
+
model,
|
|
39
|
+
prompt,
|
|
40
|
+
messages,
|
|
41
|
+
systemPrompt,
|
|
42
|
+
isPlayground,
|
|
43
|
+
responseFormat,
|
|
44
|
+
},
|
|
26
45
|
{
|
|
27
46
|
prompt: { type: 'string', required: false },
|
|
28
47
|
messages: { type: 'array', required: false },
|
|
48
|
+
systemPrompt: { type: 'string', required: false },
|
|
49
|
+
provider: { type: 'string', required: false },
|
|
29
50
|
model: { type: 'string', required: false },
|
|
30
51
|
temperature: { type: 'number', required: false },
|
|
31
52
|
subscriptionId: { type: 'string', required: false },
|
|
32
53
|
stream: { type: 'boolean', required: false },
|
|
33
|
-
|
|
54
|
+
isPlayground: { type: 'boolean', required: false },
|
|
55
|
+
responseFormat: { type: 'object', required: false },
|
|
34
56
|
},
|
|
35
57
|
);
|
|
36
58
|
|
|
37
59
|
const params = {
|
|
38
60
|
body: {
|
|
61
|
+
isPlayground,
|
|
39
62
|
prompt,
|
|
40
63
|
messages,
|
|
64
|
+
systemPrompt,
|
|
41
65
|
relatedId,
|
|
66
|
+
provider,
|
|
42
67
|
model,
|
|
43
68
|
temperature,
|
|
44
69
|
subscriptionId,
|
|
45
70
|
stream,
|
|
46
|
-
|
|
71
|
+
responseFormat,
|
|
47
72
|
},
|
|
48
73
|
// Return raw response for streaming to allow client-side stream handling
|
|
49
74
|
returnRawResponse: stream === true,
|
|
@@ -108,55 +133,55 @@ export class GenerativeService {
|
|
|
108
133
|
return result;
|
|
109
134
|
}
|
|
110
135
|
|
|
111
|
-
async chatOllama({
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
136
|
+
// async chatOllama({
|
|
137
|
+
// prompt,
|
|
138
|
+
// messages,
|
|
139
|
+
// relatedId,
|
|
140
|
+
// model,
|
|
141
|
+
// temperature,
|
|
142
|
+
// subscriptionId,
|
|
143
|
+
// stream,
|
|
144
|
+
// method,
|
|
145
|
+
// }) {
|
|
146
|
+
// this.sdk.validateParams(
|
|
147
|
+
// { method },
|
|
148
|
+
// {
|
|
149
|
+
// prompt: { type: 'string', required: false },
|
|
150
|
+
// messages: { type: 'array', required: false },
|
|
151
|
+
// relatedId: { type: 'string', required: false },
|
|
152
|
+
// model: { type: 'string', required: false },
|
|
153
|
+
// temperature: { type: 'number', required: false },
|
|
154
|
+
// subscriptionId: { type: 'string', required: false },
|
|
155
|
+
// stream: { type: 'boolean', required: false },
|
|
156
|
+
// method: { type: 'string', required: true },
|
|
157
|
+
// },
|
|
158
|
+
// );
|
|
134
159
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
160
|
+
// const params = {
|
|
161
|
+
// body: {
|
|
162
|
+
// prompt,
|
|
163
|
+
// messages,
|
|
164
|
+
// relatedId,
|
|
165
|
+
// model,
|
|
166
|
+
// temperature,
|
|
167
|
+
// subscriptionId,
|
|
168
|
+
// stream,
|
|
169
|
+
// method,
|
|
170
|
+
// },
|
|
171
|
+
// // Return raw response for streaming to allow client-side stream handling
|
|
172
|
+
// returnRawResponse: stream === true,
|
|
173
|
+
// };
|
|
149
174
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
175
|
+
// // Force HTTP transport when streaming is enabled since NATS doesn't support streaming responses
|
|
176
|
+
// const forceFetch = stream === true;
|
|
177
|
+
// const result = await this.sdk._fetch(
|
|
178
|
+
// '/ai/generative/ollama',
|
|
179
|
+
// 'POST',
|
|
180
|
+
// params,
|
|
181
|
+
// forceFetch,
|
|
182
|
+
// );
|
|
183
|
+
// return result;
|
|
184
|
+
// }
|
|
160
185
|
}
|
|
161
186
|
|
|
162
187
|
export class TextToSpeechService {
|
|
@@ -174,9 +199,21 @@ export class TextToSpeechService {
|
|
|
174
199
|
pitch,
|
|
175
200
|
volumeGainDb,
|
|
176
201
|
effectsProfileIds,
|
|
202
|
+
createAccessKey,
|
|
177
203
|
}) {
|
|
178
204
|
this.sdk.validateParams(
|
|
179
|
-
{
|
|
205
|
+
{
|
|
206
|
+
text,
|
|
207
|
+
voice,
|
|
208
|
+
languageCode,
|
|
209
|
+
ssmlGender,
|
|
210
|
+
audioEncoding,
|
|
211
|
+
speakingRate,
|
|
212
|
+
pitch,
|
|
213
|
+
volumeGainDb,
|
|
214
|
+
effectsProfileIds,
|
|
215
|
+
createAccessKey,
|
|
216
|
+
},
|
|
180
217
|
{
|
|
181
218
|
text: { type: 'string', required: true },
|
|
182
219
|
voice: { type: 'string', required: false },
|
|
@@ -187,6 +224,7 @@ export class TextToSpeechService {
|
|
|
187
224
|
pitch: { type: 'number', required: false },
|
|
188
225
|
volumeGainDb: { type: 'number', required: false },
|
|
189
226
|
effectsProfileIds: { type: 'array', required: false },
|
|
227
|
+
createAccessKey: { type: 'boolean', required: false },
|
|
190
228
|
},
|
|
191
229
|
);
|
|
192
230
|
|
|
@@ -199,6 +237,7 @@ export class TextToSpeechService {
|
|
|
199
237
|
if (pitch) ttsData.pitch = pitch;
|
|
200
238
|
if (volumeGainDb) ttsData.volumeGainDb = volumeGainDb;
|
|
201
239
|
if (effectsProfileIds) ttsData.effectsProfileIds = effectsProfileIds;
|
|
240
|
+
if (createAccessKey) ttsData.createAccessKey = createAccessKey;
|
|
202
241
|
|
|
203
242
|
const params = {
|
|
204
243
|
body: ttsData,
|
|
@@ -207,4 +246,686 @@ export class TextToSpeechService {
|
|
|
207
246
|
const result = await this.sdk._fetch('/ai/tts', 'POST', params);
|
|
208
247
|
return result;
|
|
209
248
|
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* List available TTS voices
|
|
252
|
+
* @returns {Promise<Object>} { voices: Array, count: number, supportedEncodings: Array, supportedLanguages: Array }
|
|
253
|
+
*/
|
|
254
|
+
async list() {
|
|
255
|
+
const result = await this.sdk._fetch('/ai/tts', 'GET');
|
|
256
|
+
return result;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export class SpeechToTextService {
|
|
261
|
+
constructor(sdk) {
|
|
262
|
+
this.sdk = sdk;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Create a transcription from file or storage
|
|
267
|
+
* @param {Object} options - Transcription options
|
|
268
|
+
* @param {string} options.sourceType - 'file', 'storage', 'stream', or 'url'
|
|
269
|
+
* @param {string} [options.sourceId] - Source identifier
|
|
270
|
+
* @param {string} [options.storageId] - Storage ID if using storage
|
|
271
|
+
* @param {string} [options.engine='google'] - STT engine ('google', 'deepgram', 'whisper')
|
|
272
|
+
* @param {string} [options.languageCode='en-US'] - BCP-47 language code
|
|
273
|
+
* @param {Object} [options.metadata] - Engine-specific configuration
|
|
274
|
+
* @param {string} [options.engagementSessionId] - Engagement session ID
|
|
275
|
+
* @param {string} [options.playbookId] - Playbook ID
|
|
276
|
+
* @param {string} [options.name] - Transcription name
|
|
277
|
+
* @param {string} [options.role] - Speaker role
|
|
278
|
+
* @param {string} [options.direction] - Call direction
|
|
279
|
+
* @returns {Promise<Object>} Transcription result
|
|
280
|
+
*/
|
|
281
|
+
async create({
|
|
282
|
+
sourceType,
|
|
283
|
+
sourceId,
|
|
284
|
+
sipCallId,
|
|
285
|
+
cdrId,
|
|
286
|
+
storageId,
|
|
287
|
+
engine,
|
|
288
|
+
languageCode,
|
|
289
|
+
metadata,
|
|
290
|
+
engagementSessionId,
|
|
291
|
+
playbookId,
|
|
292
|
+
name,
|
|
293
|
+
role,
|
|
294
|
+
direction,
|
|
295
|
+
}) {
|
|
296
|
+
this.sdk.validateParams(
|
|
297
|
+
{
|
|
298
|
+
sourceType,
|
|
299
|
+
sourceId,
|
|
300
|
+
sipCallId,
|
|
301
|
+
cdrId,
|
|
302
|
+
storageId,
|
|
303
|
+
engine,
|
|
304
|
+
languageCode,
|
|
305
|
+
metadata,
|
|
306
|
+
engagementSessionId,
|
|
307
|
+
playbookId,
|
|
308
|
+
name,
|
|
309
|
+
role,
|
|
310
|
+
direction,
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
sourceType: { type: 'string', required: true },
|
|
314
|
+
sourceId: { type: 'string', required: false },
|
|
315
|
+
sipCallId: { type: 'string', required: false },
|
|
316
|
+
cdrId: { type: 'string', required: false },
|
|
317
|
+
storageId: { type: 'string', required: false },
|
|
318
|
+
engine: { type: 'string', required: false },
|
|
319
|
+
languageCode: { type: 'string', required: false },
|
|
320
|
+
metadata: { type: 'object', required: false },
|
|
321
|
+
engagementSessionId: { type: 'string', required: false },
|
|
322
|
+
playbookId: { type: 'string', required: false },
|
|
323
|
+
name: { type: 'string', required: false },
|
|
324
|
+
role: { type: 'string', required: false },
|
|
325
|
+
direction: { type: 'string', required: false },
|
|
326
|
+
},
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
const params = {
|
|
330
|
+
body: {
|
|
331
|
+
sourceType,
|
|
332
|
+
sourceId,
|
|
333
|
+
sipCallId,
|
|
334
|
+
cdrId,
|
|
335
|
+
storageId,
|
|
336
|
+
engine,
|
|
337
|
+
languageCode,
|
|
338
|
+
metadata,
|
|
339
|
+
engagementSessionId,
|
|
340
|
+
playbookId,
|
|
341
|
+
name,
|
|
342
|
+
role,
|
|
343
|
+
direction,
|
|
344
|
+
},
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
const result = await this.sdk._fetch('/ai/stt', 'POST', params);
|
|
348
|
+
return result;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Create a real-time streaming transcription session
|
|
353
|
+
* Returns an EventEmitter-based stream for sending audio and receiving transcripts
|
|
354
|
+
*
|
|
355
|
+
* @param {Object} options - Stream options
|
|
356
|
+
* @param {string} [options.engine='google'] - STT engine ('google' or 'whisper')
|
|
357
|
+
* @param {string} [options.model] - STT model (e.g., 'phone_call' for Google)
|
|
358
|
+
* @param {string} [options.languageCode='en-US'] - BCP-47 language code
|
|
359
|
+
* @param {string} [options.encoding='LINEAR16'] - Audio encoding format
|
|
360
|
+
* @param {number} [options.sampleRateHertz=16000] - Sample rate in Hertz
|
|
361
|
+
* @param {number} [options.audioChannelCount=1] - Number of audio channels (1=mono, 2=stereo)
|
|
362
|
+
* @param {boolean} [options.singleUtterance=false] - Stop after first utterance
|
|
363
|
+
* @param {boolean} [options.interimResults=true] - Return interim results
|
|
364
|
+
* @param {boolean} [options.enableAutomaticPunctuation=true] - Enable automatic punctuation
|
|
365
|
+
* @param {boolean} [options.diarizationEnabled=false] - Enable speaker diarization
|
|
366
|
+
* @param {number} [options.speakerCount] - Number of speakers (if diarization enabled)
|
|
367
|
+
* @param {boolean} [options.vadEnabled=false] - Enable Voice Activity Detection
|
|
368
|
+
* @param {number} [options.minSilenceDuration=500] - Min silence duration in ms (for VAD)
|
|
369
|
+
* @param {number} [options.speechPadMs=400] - Speech padding in ms (for VAD)
|
|
370
|
+
* @param {string} [options.engagementSessionId] - Engagement session ID
|
|
371
|
+
* @param {string} [options.playbookId] - Playbook ID
|
|
372
|
+
* @param {string} [options.name] - Session name
|
|
373
|
+
* @param {Object} [options.metadata] - Additional metadata
|
|
374
|
+
* @returns {Promise<SttStream>} Stream object with write() method and transcript events
|
|
375
|
+
*
|
|
376
|
+
* @example
|
|
377
|
+
* const stream = await sdk.ai.stt.stream({ engine: 'google', languageCode: 'en-US' });
|
|
378
|
+
* stream.on('transcript', (result) => console.log(result.text, result.isFinal));
|
|
379
|
+
* stream.on('error', (error) => console.error(error));
|
|
380
|
+
* stream.on('close', () => console.log('Stream closed'));
|
|
381
|
+
* stream.write(audioChunk);
|
|
382
|
+
* stream.end();
|
|
383
|
+
*/
|
|
384
|
+
async stream(options = {}) {
|
|
385
|
+
// Environment check - STT streaming only works in Node.js
|
|
386
|
+
if (this.sdk.environment !== 'node') {
|
|
387
|
+
throw new Error(
|
|
388
|
+
'STT streaming requires Node.js environment. ' +
|
|
389
|
+
'This feature is only available in backend services. ' +
|
|
390
|
+
'Browser-based streaming is not supported.',
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const {
|
|
395
|
+
engine = 'google',
|
|
396
|
+
model,
|
|
397
|
+
languageCode = 'en-US',
|
|
398
|
+
encoding = 'LINEAR16',
|
|
399
|
+
sampleRateHertz = 16000,
|
|
400
|
+
audioChannelCount = 1,
|
|
401
|
+
singleUtterance = false,
|
|
402
|
+
interimResults = true,
|
|
403
|
+
enableAutomaticPunctuation = true,
|
|
404
|
+
diarizationEnabled = false,
|
|
405
|
+
speakerCount,
|
|
406
|
+
vadEnabled = false,
|
|
407
|
+
minSilenceDuration = 500,
|
|
408
|
+
speechPadMs = 400,
|
|
409
|
+
engagementSessionId,
|
|
410
|
+
playbookId,
|
|
411
|
+
taskId,
|
|
412
|
+
workerId,
|
|
413
|
+
generateSubject,
|
|
414
|
+
generateTranscriptSummary,
|
|
415
|
+
generateSentiment,
|
|
416
|
+
bridgeId,
|
|
417
|
+
name,
|
|
418
|
+
metadata,
|
|
419
|
+
sipCallId,
|
|
420
|
+
cdrId,
|
|
421
|
+
} = options;
|
|
422
|
+
|
|
423
|
+
// Validate parameters
|
|
424
|
+
this.sdk.validateParams(
|
|
425
|
+
{
|
|
426
|
+
engine,
|
|
427
|
+
model,
|
|
428
|
+
languageCode,
|
|
429
|
+
encoding,
|
|
430
|
+
sampleRateHertz,
|
|
431
|
+
audioChannelCount,
|
|
432
|
+
singleUtterance,
|
|
433
|
+
interimResults,
|
|
434
|
+
enableAutomaticPunctuation,
|
|
435
|
+
diarizationEnabled,
|
|
436
|
+
speakerCount,
|
|
437
|
+
vadEnabled,
|
|
438
|
+
minSilenceDuration,
|
|
439
|
+
speechPadMs,
|
|
440
|
+
engagementSessionId,
|
|
441
|
+
playbookId,
|
|
442
|
+
taskId,
|
|
443
|
+
workerId,
|
|
444
|
+
generateSubject,
|
|
445
|
+
generateTranscriptSummary,
|
|
446
|
+
generateSentiment,
|
|
447
|
+
bridgeId,
|
|
448
|
+
name,
|
|
449
|
+
metadata,
|
|
450
|
+
sipCallId,
|
|
451
|
+
cdrId,
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
engine: { type: 'string', required: false },
|
|
455
|
+
model: { type: 'string', required: false },
|
|
456
|
+
languageCode: { type: 'string', required: false },
|
|
457
|
+
encoding: { type: 'string', required: false },
|
|
458
|
+
sampleRateHertz: { type: 'number', required: false },
|
|
459
|
+
audioChannelCount: { type: 'number', required: false },
|
|
460
|
+
singleUtterance: { type: 'boolean', required: false },
|
|
461
|
+
interimResults: { type: 'boolean', required: false },
|
|
462
|
+
enableAutomaticPunctuation: { type: 'boolean', required: false },
|
|
463
|
+
diarizationEnabled: { type: 'boolean', required: false },
|
|
464
|
+
speakerCount: { type: 'number', required: false },
|
|
465
|
+
vadEnabled: { type: 'boolean', required: false },
|
|
466
|
+
minSilenceDuration: { type: 'number', required: false },
|
|
467
|
+
speechPadMs: { type: 'number', required: false },
|
|
468
|
+
engagementSessionId: { type: 'string', required: false },
|
|
469
|
+
playbookId: { type: 'string', required: false },
|
|
470
|
+
taskId: { type: 'string', required: false },
|
|
471
|
+
workerId: { type: 'string', required: false },
|
|
472
|
+
generateSubject: { type: 'boolean', required: false },
|
|
473
|
+
generateTranscriptSummary: { type: 'boolean', required: false },
|
|
474
|
+
generateSentiment: { type: 'boolean', required: false },
|
|
475
|
+
bridgeId: { type: 'string', required: false },
|
|
476
|
+
name: { type: 'string', required: false },
|
|
477
|
+
metadata: { type: 'object', required: false },
|
|
478
|
+
sipCallId: { type: 'string', required: false },
|
|
479
|
+
cdrId: { type: 'string', required: false },
|
|
480
|
+
},
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
// Create session via API
|
|
484
|
+
const sessionParams = {
|
|
485
|
+
body: {
|
|
486
|
+
engine,
|
|
487
|
+
model,
|
|
488
|
+
engagementSessionId,
|
|
489
|
+
playbookId,
|
|
490
|
+
taskId,
|
|
491
|
+
workerId,
|
|
492
|
+
generateSubject,
|
|
493
|
+
generateTranscriptSummary,
|
|
494
|
+
generateSentiment,
|
|
495
|
+
bridgeId,
|
|
496
|
+
sipCallId,
|
|
497
|
+
cdrId,
|
|
498
|
+
name,
|
|
499
|
+
metadata: {
|
|
500
|
+
...metadata,
|
|
501
|
+
languageCode,
|
|
502
|
+
encoding,
|
|
503
|
+
sampleRateHertz,
|
|
504
|
+
audioChannelCount,
|
|
505
|
+
singleUtterance,
|
|
506
|
+
interimResults,
|
|
507
|
+
enableAutomaticPunctuation,
|
|
508
|
+
diarizationEnabled,
|
|
509
|
+
speakerCount,
|
|
510
|
+
vadEnabled,
|
|
511
|
+
minSilenceDuration,
|
|
512
|
+
speechPadMs,
|
|
513
|
+
},
|
|
514
|
+
},
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
const session = await this.sdk._fetch(
|
|
518
|
+
'/ai/stt/stream',
|
|
519
|
+
'POST',
|
|
520
|
+
sessionParams,
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
// Dynamic import of SttStream (only loads when needed)
|
|
524
|
+
const { SttStream } = await import('./ai/SttStream.js');
|
|
525
|
+
|
|
526
|
+
// Create and return stream instance
|
|
527
|
+
const streamOptions = {
|
|
528
|
+
...options,
|
|
529
|
+
languageCode,
|
|
530
|
+
encoding,
|
|
531
|
+
sampleRateHertz,
|
|
532
|
+
audioChannelCount,
|
|
533
|
+
vadEnabled,
|
|
534
|
+
minSilenceDuration,
|
|
535
|
+
speechPadMs,
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
return new SttStream(this.sdk, session, streamOptions);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Get a transcription by ID
|
|
543
|
+
* @param {string} id - Transcription ID
|
|
544
|
+
* @param {Object} [options] - Retrieval options
|
|
545
|
+
* @param {boolean} [options.includeMessages=true] - Include transcription messages
|
|
546
|
+
* @returns {Promise<Object>} Transcription data
|
|
547
|
+
*/
|
|
548
|
+
async get(id, { includeMessages = true } = {}) {
|
|
549
|
+
this.sdk.validateParams(
|
|
550
|
+
{ id },
|
|
551
|
+
{
|
|
552
|
+
id: { type: 'string', required: true },
|
|
553
|
+
includeMessages: { type: 'boolean', required: false },
|
|
554
|
+
},
|
|
555
|
+
);
|
|
556
|
+
|
|
557
|
+
const params = {
|
|
558
|
+
query: { includeMessages: includeMessages.toString() },
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
const result = await this.sdk._fetch(`/ai/stt/${id}`, 'GET', params);
|
|
562
|
+
return result;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* List transcriptions with filters
|
|
567
|
+
* @param {Object} [filters] - Filter options
|
|
568
|
+
* @param {string} [filters.engagementSessionId] - Filter by engagement session
|
|
569
|
+
* @param {string} [filters.userId] - Filter by user
|
|
570
|
+
* @param {string} [filters.status] - Filter by status
|
|
571
|
+
* @param {string} [filters.engine] - Filter by engine
|
|
572
|
+
* @param {string} [filters.sourceType] - Filter by source type
|
|
573
|
+
* @param {string} [filters.playbookId] - Filter by playbook
|
|
574
|
+
* @param {string} [filters.startDate] - Filter by start date
|
|
575
|
+
* @param {string} [filters.endDate] - Filter by end date
|
|
576
|
+
* @param {number} [filters.limit=50] - Limit results
|
|
577
|
+
* @param {number} [filters.offset=0] - Offset for pagination
|
|
578
|
+
* @returns {Promise<Object>} List of transcriptions
|
|
579
|
+
*/
|
|
580
|
+
async list(filters = {}) {
|
|
581
|
+
this.sdk.validateParams(filters, {
|
|
582
|
+
engagementSessionId: { type: 'string', required: false },
|
|
583
|
+
userId: { type: 'string', required: false },
|
|
584
|
+
status: { type: 'string', required: false },
|
|
585
|
+
engine: { type: 'string', required: false },
|
|
586
|
+
sourceType: { type: 'string', required: false },
|
|
587
|
+
playbookId: { type: 'string', required: false },
|
|
588
|
+
startDate: { type: 'string', required: false },
|
|
589
|
+
endDate: { type: 'string', required: false },
|
|
590
|
+
limit: { type: 'number', required: false },
|
|
591
|
+
offset: { type: 'number', required: false },
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
const params = {
|
|
595
|
+
query: filters,
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
const result = await this.sdk._fetch('/ai/stt', 'GET', params);
|
|
599
|
+
return result;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Log a transcription message for a streaming session
|
|
604
|
+
* @param {string} sessionId - Transcription session ID
|
|
605
|
+
* @param {string} messageId - message id for the transcription
|
|
606
|
+
* @param {number} timestamp - message timestamp (unix)
|
|
607
|
+
* @param {Object} message - Message data
|
|
608
|
+
* @param {string} message.text - Transcribed text
|
|
609
|
+
* @param {Object} [message.transcriptionJson] - Full transcription metadata
|
|
610
|
+
* @param {number} [message.duration] - Duration of this segment in seconds
|
|
611
|
+
* @param {number} [message.confidence] - Confidence score (0-1)
|
|
612
|
+
* @param {string} [message.languageCode] - Language code for this segment
|
|
613
|
+
* @param {string} [message.userId] - User associated with this message
|
|
614
|
+
* @param {string} [message.role] - Speaker role
|
|
615
|
+
* @param {string} [message.sipCallId] - SIP call identifier
|
|
616
|
+
* @param {string} [message.side] - Stream side ('send' or 'recv')
|
|
617
|
+
* @param {string} [message.bridgeId] - bridge id
|
|
618
|
+
* @param {Object} [message.sentiment] - Sentiment analysis data
|
|
619
|
+
* @param {number} [message.sentiment.score] - Overall sentiment score (-100 to +100)
|
|
620
|
+
* @param {number} [message.sentiment.previousScore] - Previous sentiment score (-100 to +100)
|
|
621
|
+
* @param {number} [message.sentiment.delta] - Score change (score - previousScore)
|
|
622
|
+
* @param {number} [message.sentiment.customerScore] - Customer sentiment score (-100 to +100)
|
|
623
|
+
* @param {number} [message.sentiment.agentScore] - Agent sentiment score (-100 to +100)
|
|
624
|
+
* @param {number} [message.sentiment.intensity] - Sentiment intensity (0 to 1)
|
|
625
|
+
* @param {string[]} [message.sentiment.emotions] - Detected emotions (up to 3)
|
|
626
|
+
* @param {string} [message.sentiment.trend] - Sentiment trend ('improving' | 'stable' | 'declining')
|
|
627
|
+
* @param {string} [message.sentiment.source] - Analysis source ('llm+acoustic' | 'acoustic_only' | 'carry_forward')
|
|
628
|
+
* @returns {Promise<Object>} Created message result
|
|
629
|
+
*/
|
|
630
|
+
async logMessage(
|
|
631
|
+
sessionId,
|
|
632
|
+
{
|
|
633
|
+
messageId,
|
|
634
|
+
timestamp,
|
|
635
|
+
text,
|
|
636
|
+
transcriptionJson,
|
|
637
|
+
duration,
|
|
638
|
+
confidence,
|
|
639
|
+
languageCode,
|
|
640
|
+
userId,
|
|
641
|
+
role,
|
|
642
|
+
sipCallId,
|
|
643
|
+
side,
|
|
644
|
+
bridgeId,
|
|
645
|
+
sentiment,
|
|
646
|
+
},
|
|
647
|
+
) {
|
|
648
|
+
this.sdk.validateParams(
|
|
649
|
+
{ messageId, sessionId, text, bridgeId },
|
|
650
|
+
{
|
|
651
|
+
sessionId: { type: 'string', required: true },
|
|
652
|
+
messageId: { type: 'string', required: false },
|
|
653
|
+
timestamp: { type: 'number', required: false },
|
|
654
|
+
text: { type: 'string', required: true },
|
|
655
|
+
transcriptionJson: { type: 'object', required: false },
|
|
656
|
+
duration: { type: 'number', required: false },
|
|
657
|
+
confidence: { type: 'number', required: false },
|
|
658
|
+
languageCode: { type: 'string', required: false },
|
|
659
|
+
userId: { type: 'string', required: false },
|
|
660
|
+
role: { type: 'string', required: false },
|
|
661
|
+
sipCallId: { type: 'string', required: false },
|
|
662
|
+
side: { type: 'string', required: false },
|
|
663
|
+
bridgeId: { type: 'string', required: false },
|
|
664
|
+
sentiment: { type: 'object', required: false },
|
|
665
|
+
},
|
|
666
|
+
);
|
|
667
|
+
|
|
668
|
+
const params = {
|
|
669
|
+
body: {
|
|
670
|
+
messageId,
|
|
671
|
+
timestamp,
|
|
672
|
+
text,
|
|
673
|
+
transcriptionJson,
|
|
674
|
+
duration,
|
|
675
|
+
confidence,
|
|
676
|
+
languageCode,
|
|
677
|
+
userId,
|
|
678
|
+
role,
|
|
679
|
+
sipCallId,
|
|
680
|
+
side,
|
|
681
|
+
bridgeId,
|
|
682
|
+
sentiment,
|
|
683
|
+
},
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
const result = await this.sdk._fetch(
|
|
687
|
+
`/ai/stt/stream/${sessionId}/messages`,
|
|
688
|
+
'POST',
|
|
689
|
+
params,
|
|
690
|
+
);
|
|
691
|
+
return result;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* Complete a streaming transcription session
|
|
696
|
+
* @param {string} sessionId - Transcription session ID
|
|
697
|
+
* @param {Object} [options] - Completion options
|
|
698
|
+
* @param {string} [options.status='completed'] - Final status ('completed' or 'failed')
|
|
699
|
+
* @param {string} [options.error] - Error message if status is 'failed'
|
|
700
|
+
* @returns {Promise<Object>} Completion result
|
|
701
|
+
*/
|
|
702
|
+
async complete(sessionId, { status = 'completed', error } = {}) {
|
|
703
|
+
this.sdk.validateParams(
|
|
704
|
+
{ sessionId },
|
|
705
|
+
{
|
|
706
|
+
sessionId: { type: 'string', required: true },
|
|
707
|
+
status: { type: 'string', required: false },
|
|
708
|
+
error: { type: 'string', required: false },
|
|
709
|
+
},
|
|
710
|
+
);
|
|
711
|
+
|
|
712
|
+
const params = {
|
|
713
|
+
body: { status, error },
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
const result = await this.sdk._fetch(
|
|
717
|
+
`/ai/stt/stream/${sessionId}/complete`,
|
|
718
|
+
'PUT',
|
|
719
|
+
params,
|
|
720
|
+
);
|
|
721
|
+
return result;
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// STT Constants
|
|
726
|
+
export const STT = {
|
|
727
|
+
ENGINES: {
|
|
728
|
+
GOOGLE: 'google',
|
|
729
|
+
DEEPGRAM: 'deepgram',
|
|
730
|
+
WHISPER: 'whisper',
|
|
731
|
+
},
|
|
732
|
+
SOURCE_TYPES: {
|
|
733
|
+
FILE: 'file',
|
|
734
|
+
STORAGE: 'storage',
|
|
735
|
+
STREAM: 'stream',
|
|
736
|
+
URL: 'url',
|
|
737
|
+
},
|
|
738
|
+
STATUS: {
|
|
739
|
+
PENDING: 'pending',
|
|
740
|
+
PROCESSING: 'processing',
|
|
741
|
+
COMPLETED: 'completed',
|
|
742
|
+
FAILED: 'failed',
|
|
743
|
+
},
|
|
744
|
+
LANGUAGES: {
|
|
745
|
+
EN_US: 'en-US',
|
|
746
|
+
EN_GB: 'en-GB',
|
|
747
|
+
EN_AU: 'en-AU',
|
|
748
|
+
ES_ES: 'es-ES',
|
|
749
|
+
ES_MX: 'es-MX',
|
|
750
|
+
ES_US: 'es-US',
|
|
751
|
+
FR_FR: 'fr-FR',
|
|
752
|
+
FR_CA: 'fr-CA',
|
|
753
|
+
DE_DE: 'de-DE',
|
|
754
|
+
IT_IT: 'it-IT',
|
|
755
|
+
PT_BR: 'pt-BR',
|
|
756
|
+
PT_PT: 'pt-PT',
|
|
757
|
+
ZH_CN: 'zh-CN',
|
|
758
|
+
JA_JP: 'ja-JP',
|
|
759
|
+
KO_KR: 'ko-KR',
|
|
760
|
+
},
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
export class ExtractService {
|
|
764
|
+
constructor(sdk) {
|
|
765
|
+
this.sdk = sdk;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Extract phone number from text
|
|
770
|
+
* @param {Object} options - Extraction options
|
|
771
|
+
* @param {string} options.value - Text containing phone number
|
|
772
|
+
* @param {string} [options.country='US'] - Country code for validation
|
|
773
|
+
* @param {string} [options.format='E164'] - Output format: 'E164', 'national', 'international'
|
|
774
|
+
* @returns {Promise<Object>} { isValid, parsedValue, confidence, metadata: { country, type, nationalNumber, ... } }
|
|
775
|
+
*/
|
|
776
|
+
async phone({ value, country = 'US', format = 'E164' }) {
|
|
777
|
+
return this._extract('phone', value, { country, format });
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Extract email address from text
|
|
782
|
+
* @param {Object} options - Extraction options
|
|
783
|
+
* @param {string} options.value - Text containing email
|
|
784
|
+
* @returns {Promise<Object>} { isValid, parsedValue, confidence, metadata: { domain } }
|
|
785
|
+
*/
|
|
786
|
+
async email({ value }) {
|
|
787
|
+
return this._extract('email', value);
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Extract physical address from text
|
|
792
|
+
* @param {Object} options - Extraction options
|
|
793
|
+
* @param {string} options.value - Text containing address
|
|
794
|
+
* @param {boolean} [options.useAI=false] - Use AI for better extraction from conversational text
|
|
795
|
+
* @returns {Promise<Object>} { isValid, parsedValue, confidence, metadata: { components } }
|
|
796
|
+
*/
|
|
797
|
+
async address({ value, useAI = false }) {
|
|
798
|
+
return this._extract('address', value, { useAI });
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Extract person's name from text
|
|
803
|
+
* @param {Object} options - Extraction options
|
|
804
|
+
* @param {string} options.value - Text containing name
|
|
805
|
+
* @param {boolean} [options.useAI=true] - Use AI for extraction from conversational text
|
|
806
|
+
* @returns {Promise<Object>} { isValid, parsedValue, firstName, lastName, title }
|
|
807
|
+
*/
|
|
808
|
+
async personName({ value, useAI = true }) {
|
|
809
|
+
return this._extract('personName', value, { useAI });
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Extract and validate credit card number (masked for security)
|
|
814
|
+
* @param {Object} options - Extraction options
|
|
815
|
+
* @param {string} options.value - Text containing credit card
|
|
816
|
+
* @param {boolean} [options.maskOutput=true] - Mask all but last 4 digits
|
|
817
|
+
* @returns {Promise<Object>} { isValid, parsedValue (masked), confidence, metadata: { cardType, length } }
|
|
818
|
+
*/
|
|
819
|
+
async creditCard({ value, maskOutput = true }) {
|
|
820
|
+
return this._extract('creditCard', value, { maskOutput });
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
/**
|
|
824
|
+
* Extract and validate SSN (masked for security)
|
|
825
|
+
* @param {Object} options - Extraction options
|
|
826
|
+
* @param {string} options.value - Text containing SSN
|
|
827
|
+
* @param {boolean} [options.maskOutput=true] - Mask all but last 4 digits
|
|
828
|
+
* @returns {Promise<Object>} { isValid, parsedValue (masked), confidence, metadata }
|
|
829
|
+
*/
|
|
830
|
+
async ssn({ value, maskOutput = true }) {
|
|
831
|
+
return this._extract('ssn', value, { maskOutput });
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Extract phone extension
|
|
836
|
+
* @param {Object} options - Extraction options
|
|
837
|
+
* @param {string} options.value - Text containing extension
|
|
838
|
+
* @param {number} [options.minLength=1] - Minimum digits
|
|
839
|
+
* @param {number} [options.maxLength=10] - Maximum digits
|
|
840
|
+
* @returns {Promise<Object>} { isValid, parsedValue, confidence }
|
|
841
|
+
*/
|
|
842
|
+
async extension({ value, minLength = 1, maxLength = 10 }) {
|
|
843
|
+
return this._extract('extension', value, { minLength, maxLength });
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* Detect correct/incorrect response from natural language
|
|
848
|
+
* @param {Object} options - Extraction options
|
|
849
|
+
* @param {string} options.value - User's response (e.g., "yes", "no", "correct", "nope")
|
|
850
|
+
* @returns {Promise<Object>} { isValid, parsedValue: 'correct'|'incorrect', booleanValue: true|false }
|
|
851
|
+
*/
|
|
852
|
+
async correctIncorrect({ value }) {
|
|
853
|
+
return this._extract('correctIncorrect', value);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
/**
|
|
857
|
+
* Extract using custom regex pattern
|
|
858
|
+
* @param {Object} options - Extraction options
|
|
859
|
+
* @param {string} options.value - Text to match against
|
|
860
|
+
* @param {string} options.pattern - Regex pattern
|
|
861
|
+
* @param {number} [options.maxLength] - Maximum length of extracted value
|
|
862
|
+
* @param {string} [options.flags='g'] - Regex flags
|
|
863
|
+
* @param {boolean} [options.replaceNumbers=true] - Convert number words to digits first
|
|
864
|
+
* @returns {Promise<Object>} { isValid, parsedValue, originalValue, processedValue }
|
|
865
|
+
*/
|
|
866
|
+
async regex({
|
|
867
|
+
value,
|
|
868
|
+
pattern,
|
|
869
|
+
maxLength,
|
|
870
|
+
flags = 'g',
|
|
871
|
+
replaceNumbers = true,
|
|
872
|
+
}) {
|
|
873
|
+
return this._extract('regex', value, {
|
|
874
|
+
pattern,
|
|
875
|
+
maxLength,
|
|
876
|
+
flags,
|
|
877
|
+
replaceNumbers,
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Detect intent response from natural language
|
|
883
|
+
* @param {Object} options - Extraction options
|
|
884
|
+
* @param {string} options.value - User's response (e.g., "I need help with my invoice")
|
|
885
|
+
* @param {Object} options.params.question - The Question posed to the user to answer
|
|
886
|
+
* @param {Object} options.params.validOptions - An array of objects with the valid intents
|
|
887
|
+
*
|
|
888
|
+
* @returns {Promise<Object>} { isValid, value: 'Billing', }
|
|
889
|
+
*/
|
|
890
|
+
async intent({ value, params }) {
|
|
891
|
+
return this._extract('intent', value, params);
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
/**
|
|
895
|
+
* Extract ALL entities from text (phone, email, address, name, etc.)
|
|
896
|
+
* @param {Object} options - Extraction options
|
|
897
|
+
* @param {string} options.value - Text to extract from
|
|
898
|
+
* @param {array} [options.types] - Array of strings of the types you want to find ('correctIncorrect', 'phone', 'email', 'address', 'personName', 'extension', 'creditCard', 'ssn'). If not provided, all types will be extracted.
|
|
899
|
+
* @param {string} [options.question] - The original question that was asked to the user. Providing this context significantly improves extraction accuracy by helping the AI understand what data entities to look for.
|
|
900
|
+
* @returns {Promise<Object>} { isValid, extractions: [{ type, value, ...metadata }], metadata }
|
|
901
|
+
*/
|
|
902
|
+
async all({ value, types = [], question }) {
|
|
903
|
+
const params = {
|
|
904
|
+
types,
|
|
905
|
+
question,
|
|
906
|
+
};
|
|
907
|
+
return this._extract('all', value, params);
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// Internal method
|
|
911
|
+
async _extract(type, value, params = {}) {
|
|
912
|
+
this.sdk.validateParams(
|
|
913
|
+
{ type, value },
|
|
914
|
+
{
|
|
915
|
+
type: { type: 'string', required: true },
|
|
916
|
+
value: { type: 'string', required: true },
|
|
917
|
+
},
|
|
918
|
+
);
|
|
919
|
+
|
|
920
|
+
const requestParams = {
|
|
921
|
+
body: {
|
|
922
|
+
type,
|
|
923
|
+
value,
|
|
924
|
+
params,
|
|
925
|
+
},
|
|
926
|
+
};
|
|
927
|
+
|
|
928
|
+
const result = await this.sdk._fetch('/ai/extract', 'POST', requestParams);
|
|
929
|
+
return result;
|
|
930
|
+
}
|
|
210
931
|
}
|