@unboundcx/sdk 4.0.9 → 4.0.11
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/package.json +1 -1
- package/proto/transcription.proto +52 -10
- package/services/ai/SttStream.js +18 -0
- package/services/ai.js +36 -0
- package/services/googleCalendar.js +24 -5
- package/services/storage.js +1 -1
- package/services/video.js +88 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unboundcx/sdk",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.11",
|
|
4
4
|
"description": "Official JavaScript SDK for the Unbound API - A comprehensive toolkit for integrating with Unbound's communication, AI, and data management services",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -28,7 +28,7 @@ message AudioRequest {
|
|
|
28
28
|
string language = 4;
|
|
29
29
|
|
|
30
30
|
// Transcription engine to use
|
|
31
|
-
// Options: "whisper", "google"
|
|
31
|
+
// Options: "whisper", "google", "groq"
|
|
32
32
|
string engine = 5;
|
|
33
33
|
|
|
34
34
|
// Audio configuration
|
|
@@ -66,28 +66,48 @@ message AudioRequest {
|
|
|
66
66
|
// VAD event timestamp
|
|
67
67
|
int64 vad_timestamp = 15;
|
|
68
68
|
|
|
69
|
-
//
|
|
69
|
+
// Optional playbook ID for real-time agent scoring
|
|
70
70
|
string playbook_id = 16;
|
|
71
71
|
|
|
72
|
-
// Bridge
|
|
73
|
-
//
|
|
74
|
-
// bridge_id lets the transcription service correlate them
|
|
72
|
+
// Bridge ID linking related transcription streams (e.g., both legs of a bridged call)
|
|
73
|
+
// Empty string = not part of any bridge. Dynamic: can appear, change, or disappear mid-stream.
|
|
75
74
|
string bridge_id = 17;
|
|
76
75
|
|
|
77
|
-
//
|
|
76
|
+
// Optional task ID for playbook session tracking
|
|
78
77
|
string task_id = 18;
|
|
79
78
|
|
|
80
|
-
//
|
|
79
|
+
// Optional worker ID for playbook session tracking
|
|
81
80
|
string worker_id = 19;
|
|
82
81
|
|
|
83
|
-
//
|
|
82
|
+
// Enable real-time subject generation from customer transcripts
|
|
83
|
+
// Requires task_id to be set for SDK task update
|
|
84
84
|
bool generate_subject = 20;
|
|
85
85
|
|
|
86
|
-
//
|
|
86
|
+
// Enable end-of-call transcript summary generation
|
|
87
|
+
// Requires task_id to be set for SDK task update
|
|
87
88
|
bool generate_transcript_summary = 21;
|
|
88
89
|
|
|
89
|
-
//
|
|
90
|
+
// Enable real-time sentiment analysis with multi-signal fusion
|
|
91
|
+
// Requires task_id to be set for SDK task update
|
|
90
92
|
bool generate_sentiment = 22;
|
|
93
|
+
|
|
94
|
+
// ── Meet (video-room) session metadata — additive, optional ──────────────
|
|
95
|
+
// Video room ID this stream belongs to (Meet sessions only). Also used as
|
|
96
|
+
// the conversation identifier for Meet sessions.
|
|
97
|
+
string video_room_id = 23;
|
|
98
|
+
|
|
99
|
+
// Video room participant ID (Meet sessions only)
|
|
100
|
+
string participant_id = 24;
|
|
101
|
+
|
|
102
|
+
// Display name of the participant (Meet sessions only)
|
|
103
|
+
string display_name = 25;
|
|
104
|
+
|
|
105
|
+
// Opt-in server-side VAD: when true, this repo runs its own energy-based
|
|
106
|
+
// silence detector over incoming PCM chunks and injects synthetic
|
|
107
|
+
// speaking_started/speaking_stopped vad_events (media-manager-parity
|
|
108
|
+
// SilenceDetector), instead of relying on client-sent vad_event fields.
|
|
109
|
+
// Defaults to false/unset: existing (non-Meet) callers are unaffected.
|
|
110
|
+
bool server_vad = 26;
|
|
91
111
|
}
|
|
92
112
|
|
|
93
113
|
// Audio configuration
|
|
@@ -156,6 +176,28 @@ message TranscriptResponse {
|
|
|
156
176
|
|
|
157
177
|
// Speaker role (echoed back from AudioRequest)
|
|
158
178
|
string role = 11;
|
|
179
|
+
|
|
180
|
+
// Sentiment score (-100 to +100, 0 if sentiment not enabled)
|
|
181
|
+
float sentiment_score = 12;
|
|
182
|
+
|
|
183
|
+
// Sentiment trend ('improving', 'stable', 'declining', empty if not enabled)
|
|
184
|
+
string sentiment_trend = 13;
|
|
185
|
+
|
|
186
|
+
// Unique message ID for correlating transcript with its sentiment update
|
|
187
|
+
string message_id = 14;
|
|
188
|
+
|
|
189
|
+
// Whether this message is a sentiment enrichment update (not a new transcript)
|
|
190
|
+
bool is_sentiment_update = 15;
|
|
191
|
+
|
|
192
|
+
// ── Meet (video-room) session metadata — additive, optional, echoed back ─
|
|
193
|
+
// Video room ID (echoed back from AudioRequest, Meet sessions only)
|
|
194
|
+
string video_room_id = 16;
|
|
195
|
+
|
|
196
|
+
// Video room participant ID (echoed back from AudioRequest, Meet sessions only)
|
|
197
|
+
string participant_id = 17;
|
|
198
|
+
|
|
199
|
+
// Display name of the participant (echoed back from AudioRequest, Meet sessions only)
|
|
200
|
+
string display_name = 18;
|
|
159
201
|
}
|
|
160
202
|
|
|
161
203
|
// Word-level information
|
package/services/ai/SttStream.js
CHANGED
|
@@ -118,6 +118,10 @@ export class SttStream extends EventEmitter {
|
|
|
118
118
|
// sipCallId: string, // NEW
|
|
119
119
|
// side: string, // NEW
|
|
120
120
|
// role: string // NEW
|
|
121
|
+
// videoRoomId: string, // NEW (meet sessions only, else '')
|
|
122
|
+
// participantId: string, // NEW (meet sessions only, else '')
|
|
123
|
+
// displayName: string, // NEW (meet sessions only, else '')
|
|
124
|
+
// messageId: string // NEW (proto field 14)
|
|
121
125
|
// }
|
|
122
126
|
|
|
123
127
|
if (response.transcript) {
|
|
@@ -134,6 +138,11 @@ export class SttStream extends EventEmitter {
|
|
|
134
138
|
sipCallId: response.sipCallId || response.sip_call_id || '',
|
|
135
139
|
side: response.side || '',
|
|
136
140
|
role: response.role || '',
|
|
141
|
+
// Meet (video-room) session metadata (NEW, additive, '' when not a meet session)
|
|
142
|
+
videoRoomId: response.videoRoomId || response.video_room_id || '',
|
|
143
|
+
participantId: response.participantId || response.participant_id || '',
|
|
144
|
+
displayName: response.displayName || response.display_name || '',
|
|
145
|
+
messageId: response.messageId || response.message_id || '',
|
|
137
146
|
};
|
|
138
147
|
|
|
139
148
|
this.emit('transcript', transcriptData);
|
|
@@ -154,6 +163,10 @@ export class SttStream extends EventEmitter {
|
|
|
154
163
|
* @param {string} streamMetadata.role - Optional speaker role (e.g., 'customer', 'agent', 'system')
|
|
155
164
|
* @param {boolean} streamMetadata.isLastChunk - If true, marks this specific stream as complete
|
|
156
165
|
* @returns {boolean} - True if write successful
|
|
166
|
+
*
|
|
167
|
+
* Meet (video-room) session metadata is not per-chunk — it is sent once in the
|
|
168
|
+
* first-chunk session config, read from stream options: options.videoRoomId,
|
|
169
|
+
* options.participantId, options.displayName, options.serverVad. See constructor.
|
|
157
170
|
*/
|
|
158
171
|
write(audioChunk, streamMetadata = {}) {
|
|
159
172
|
if (this.isClosed) {
|
|
@@ -205,6 +218,11 @@ export class SttStream extends EventEmitter {
|
|
|
205
218
|
generate_transcript_summary: this.options.generateTranscriptSummary || false,
|
|
206
219
|
generate_sentiment: this.options.generateSentiment || false,
|
|
207
220
|
bridge_id: bridgeId,
|
|
221
|
+
// Meet (video-room) session metadata (NEW, additive, optional)
|
|
222
|
+
video_room_id: this.options.videoRoomId || '',
|
|
223
|
+
participant_id: this.options.participantId || '',
|
|
224
|
+
display_name: this.options.displayName || '',
|
|
225
|
+
server_vad: this.options.serverVad || false,
|
|
208
226
|
};
|
|
209
227
|
|
|
210
228
|
this.grpcCall.write(request);
|
package/services/ai.js
CHANGED
|
@@ -611,6 +611,11 @@ export class SpeechToTextService {
|
|
|
611
611
|
* @param {string} [options.playbookId] - Playbook ID
|
|
612
612
|
* @param {string} [options.name] - Session name
|
|
613
613
|
* @param {Object} [options.metadata] - Additional metadata
|
|
614
|
+
* @param {string} [options.videoRoomId] - Video (Meet) room ID this stream belongs to
|
|
615
|
+
* @param {string} [options.videoParticipantId] - Video (Meet) room participant ID
|
|
616
|
+
* @param {string} [options.displayName] - Participant display name (Meet sessions)
|
|
617
|
+
* @param {string} [options.role] - Speaker role (e.g. 'participant', 'host')
|
|
618
|
+
* @param {boolean} [options.serverVad] - Opt-in server-side VAD (Meet sessions; defaults false)
|
|
614
619
|
* @returns {Promise<SttStream>} Stream object with write() method and transcript events
|
|
615
620
|
*
|
|
616
621
|
* @example
|
|
@@ -658,6 +663,11 @@ export class SpeechToTextService {
|
|
|
658
663
|
metadata,
|
|
659
664
|
sipCallId,
|
|
660
665
|
cdrId,
|
|
666
|
+
videoRoomId,
|
|
667
|
+
videoParticipantId,
|
|
668
|
+
displayName,
|
|
669
|
+
role,
|
|
670
|
+
serverVad,
|
|
661
671
|
} = options;
|
|
662
672
|
|
|
663
673
|
// Validate parameters
|
|
@@ -689,6 +699,11 @@ export class SpeechToTextService {
|
|
|
689
699
|
metadata,
|
|
690
700
|
sipCallId,
|
|
691
701
|
cdrId,
|
|
702
|
+
videoRoomId,
|
|
703
|
+
videoParticipantId,
|
|
704
|
+
displayName,
|
|
705
|
+
role,
|
|
706
|
+
serverVad,
|
|
692
707
|
},
|
|
693
708
|
{
|
|
694
709
|
engine: { type: 'string', required: false },
|
|
@@ -717,6 +732,11 @@ export class SpeechToTextService {
|
|
|
717
732
|
metadata: { type: 'object', required: false },
|
|
718
733
|
sipCallId: { type: 'string', required: false },
|
|
719
734
|
cdrId: { type: 'string', required: false },
|
|
735
|
+
videoRoomId: { type: 'string', required: false },
|
|
736
|
+
videoParticipantId: { type: 'string', required: false },
|
|
737
|
+
displayName: { type: 'string', required: false },
|
|
738
|
+
role: { type: 'string', required: false },
|
|
739
|
+
serverVad: { type: 'boolean', required: false },
|
|
720
740
|
},
|
|
721
741
|
);
|
|
722
742
|
|
|
@@ -736,6 +756,10 @@ export class SpeechToTextService {
|
|
|
736
756
|
sipCallId,
|
|
737
757
|
cdrId,
|
|
738
758
|
name,
|
|
759
|
+
videoRoomId,
|
|
760
|
+
videoParticipantId,
|
|
761
|
+
displayName,
|
|
762
|
+
role,
|
|
739
763
|
metadata: {
|
|
740
764
|
...metadata,
|
|
741
765
|
languageCode,
|
|
@@ -773,6 +797,12 @@ export class SpeechToTextService {
|
|
|
773
797
|
vadEnabled,
|
|
774
798
|
minSilenceDuration,
|
|
775
799
|
speechPadMs,
|
|
800
|
+
// SttStream reads these as videoRoomId/participantId/displayName/serverVad
|
|
801
|
+
// for the gRPC first-chunk session config.
|
|
802
|
+
videoRoomId,
|
|
803
|
+
participantId: videoParticipantId,
|
|
804
|
+
displayName,
|
|
805
|
+
serverVad,
|
|
776
806
|
};
|
|
777
807
|
|
|
778
808
|
return new SttStream(this.sdk, session, streamOptions);
|
|
@@ -883,6 +913,8 @@ export class SpeechToTextService {
|
|
|
883
913
|
side,
|
|
884
914
|
bridgeId,
|
|
885
915
|
sentiment,
|
|
916
|
+
videoRoomId,
|
|
917
|
+
videoParticipantId,
|
|
886
918
|
},
|
|
887
919
|
) {
|
|
888
920
|
this.sdk.validateParams(
|
|
@@ -902,6 +934,8 @@ export class SpeechToTextService {
|
|
|
902
934
|
side: { type: 'string', required: false },
|
|
903
935
|
bridgeId: { type: 'string', required: false },
|
|
904
936
|
sentiment: { type: 'object', required: false },
|
|
937
|
+
videoRoomId: { type: 'string', required: false },
|
|
938
|
+
videoParticipantId: { type: 'string', required: false },
|
|
905
939
|
},
|
|
906
940
|
);
|
|
907
941
|
|
|
@@ -920,6 +954,8 @@ export class SpeechToTextService {
|
|
|
920
954
|
side,
|
|
921
955
|
bridgeId,
|
|
922
956
|
sentiment,
|
|
957
|
+
videoRoomId,
|
|
958
|
+
videoParticipantId,
|
|
923
959
|
},
|
|
924
960
|
};
|
|
925
961
|
|
|
@@ -3,19 +3,38 @@ export class GoogleCalendarService {
|
|
|
3
3
|
this.sdk = sdk;
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
-
async setupWebhook({
|
|
6
|
+
async setupWebhook({
|
|
7
|
+
calendarId,
|
|
8
|
+
eventTypes,
|
|
9
|
+
webhookUrl,
|
|
10
|
+
expirationTime,
|
|
11
|
+
recordTypeId,
|
|
12
|
+
} = {}) {
|
|
13
|
+
// Only calendarId is required. The server derives the webhook URL itself
|
|
14
|
+
// (per-environment: https://login.<API_BASE_URL>/webhooks/google/...) and
|
|
15
|
+
// watches all event types, so eventTypes/webhookUrl are optional. They
|
|
16
|
+
// were previously marked required, which broke the common
|
|
17
|
+
// setupWebhook({ calendarId }) call site (createRoom) with
|
|
18
|
+
// "Missing required parameter eventTypes".
|
|
19
|
+
// recordTypeId is optional: callers (e.g. createRoom) pass the meeting's
|
|
20
|
+
// already-resolved recordTypeId so the webhook row inherits it; if omitted
|
|
21
|
+
// the server resolves a fallback via findRecordTypeId.
|
|
7
22
|
this.sdk.validateParams(
|
|
8
|
-
{ calendarId, eventTypes, webhookUrl },
|
|
23
|
+
{ calendarId, eventTypes, webhookUrl, recordTypeId },
|
|
9
24
|
{
|
|
10
25
|
calendarId: { type: 'string', required: true },
|
|
11
|
-
eventTypes: { type: 'array', required:
|
|
12
|
-
webhookUrl: { type: 'string', required:
|
|
26
|
+
eventTypes: { type: 'array', required: false },
|
|
27
|
+
webhookUrl: { type: 'string', required: false },
|
|
13
28
|
expirationTime: { type: 'number', required: false },
|
|
29
|
+
recordTypeId: { type: 'string', required: false },
|
|
14
30
|
},
|
|
15
31
|
);
|
|
16
32
|
|
|
17
|
-
const webhookData = { calendarId
|
|
33
|
+
const webhookData = { calendarId };
|
|
34
|
+
if (eventTypes) webhookData.eventTypes = eventTypes;
|
|
35
|
+
if (webhookUrl) webhookData.webhookUrl = webhookUrl;
|
|
18
36
|
if (expirationTime) webhookData.expirationTime = expirationTime;
|
|
37
|
+
if (recordTypeId) webhookData.recordTypeId = recordTypeId;
|
|
19
38
|
|
|
20
39
|
const params = {
|
|
21
40
|
body: webhookData,
|
package/services/storage.js
CHANGED
package/services/video.js
CHANGED
|
@@ -221,6 +221,13 @@ export class VideoService {
|
|
|
221
221
|
startMicrophoneMutedAfter,
|
|
222
222
|
enableChat,
|
|
223
223
|
engagementSessionId,
|
|
224
|
+
startRecordingOn,
|
|
225
|
+
startTranscribingOn,
|
|
226
|
+
syncToCalendar,
|
|
227
|
+
source,
|
|
228
|
+
calendarId,
|
|
229
|
+
eventId,
|
|
230
|
+
calendarProvider,
|
|
224
231
|
}) {
|
|
225
232
|
this.sdk.validateParams(
|
|
226
233
|
{
|
|
@@ -241,6 +248,13 @@ export class VideoService {
|
|
|
241
248
|
startMicrophoneMutedAfter,
|
|
242
249
|
enableChat,
|
|
243
250
|
engagementSessionId,
|
|
251
|
+
startRecordingOn,
|
|
252
|
+
startTranscribingOn,
|
|
253
|
+
syncToCalendar,
|
|
254
|
+
source,
|
|
255
|
+
calendarId,
|
|
256
|
+
eventId,
|
|
257
|
+
calendarProvider,
|
|
244
258
|
},
|
|
245
259
|
{
|
|
246
260
|
name: { type: 'string', required: false },
|
|
@@ -260,6 +274,13 @@ export class VideoService {
|
|
|
260
274
|
startMicrophoneMutedAfter: { type: 'number', required: false },
|
|
261
275
|
enableChat: { type: 'boolean', required: false },
|
|
262
276
|
engagementSessionId: { type: 'string', required: false },
|
|
277
|
+
startRecordingOn: { type: 'boolean', required: false },
|
|
278
|
+
startTranscribingOn: { type: 'boolean', required: false },
|
|
279
|
+
syncToCalendar: { type: 'boolean', required: false },
|
|
280
|
+
source: { type: 'string', required: false },
|
|
281
|
+
calendarId: { type: 'string', required: false },
|
|
282
|
+
eventId: { type: 'string', required: false },
|
|
283
|
+
calendarProvider: { type: 'string', required: false },
|
|
263
284
|
},
|
|
264
285
|
);
|
|
265
286
|
const params = {
|
|
@@ -281,6 +302,13 @@ export class VideoService {
|
|
|
281
302
|
startMicrophoneMutedAfter,
|
|
282
303
|
enableChat,
|
|
283
304
|
engagementSessionId,
|
|
305
|
+
startRecordingOn,
|
|
306
|
+
startTranscribingOn,
|
|
307
|
+
syncToCalendar,
|
|
308
|
+
source,
|
|
309
|
+
calendarId,
|
|
310
|
+
eventId,
|
|
311
|
+
calendarProvider,
|
|
284
312
|
},
|
|
285
313
|
};
|
|
286
314
|
const result = await this.sdk._fetch(`/video`, 'POST', params);
|
|
@@ -459,7 +487,7 @@ export class VideoService {
|
|
|
459
487
|
return result;
|
|
460
488
|
}
|
|
461
489
|
|
|
462
|
-
async deleteRoom(roomId) {
|
|
490
|
+
async deleteRoom(roomId, options = {}) {
|
|
463
491
|
this.sdk.validateParams(
|
|
464
492
|
{ roomId },
|
|
465
493
|
{
|
|
@@ -467,6 +495,9 @@ export class VideoService {
|
|
|
467
495
|
},
|
|
468
496
|
);
|
|
469
497
|
const params = {};
|
|
498
|
+
if (options && options.deleteCalendarEvent === true) {
|
|
499
|
+
params.body = { deleteCalendarEvent: true };
|
|
500
|
+
}
|
|
470
501
|
const result = await this.sdk._fetch(`/video/${roomId}`, 'DELETE', params);
|
|
471
502
|
return result;
|
|
472
503
|
}
|
|
@@ -618,6 +649,26 @@ export class VideoService {
|
|
|
618
649
|
return result;
|
|
619
650
|
}
|
|
620
651
|
|
|
652
|
+
// Persist a rolled-up quality summary to MySQL (one row per participant
|
|
653
|
+
// per meeting). Internal endpoint, called by app1-video-server on
|
|
654
|
+
// participant.leave. Long-term home for billing (bytes) and "was this
|
|
655
|
+
// meeting good?" support lookups, surviving ClickHouse TTL expiry.
|
|
656
|
+
async submitParticipantSummary(roomId, participantId, summary) {
|
|
657
|
+
this.sdk.validateParams(
|
|
658
|
+
{ roomId, participantId, summary },
|
|
659
|
+
{
|
|
660
|
+
roomId: { type: 'string', required: true },
|
|
661
|
+
participantId: { type: 'string', required: true },
|
|
662
|
+
summary: { type: 'object', required: true },
|
|
663
|
+
},
|
|
664
|
+
);
|
|
665
|
+
return this.sdk._fetch(
|
|
666
|
+
`/internal/video/${roomId}/participants/${participantId}/summary`,
|
|
667
|
+
'POST',
|
|
668
|
+
{ body: summary },
|
|
669
|
+
);
|
|
670
|
+
}
|
|
671
|
+
|
|
621
672
|
async submitSurvey({
|
|
622
673
|
videoRoomId,
|
|
623
674
|
participantId,
|
|
@@ -749,6 +800,42 @@ export class VideoService {
|
|
|
749
800
|
return result;
|
|
750
801
|
}
|
|
751
802
|
|
|
803
|
+
/**
|
|
804
|
+
* Get the live transcription transcript for a video room, paged.
|
|
805
|
+
* @param {string} roomId - The video room ID
|
|
806
|
+
* @param {Object} [options] - Paging options
|
|
807
|
+
* @param {number} [options.limit] - Max rows to return
|
|
808
|
+
* @param {number} [options.offset] - Row offset
|
|
809
|
+
* @returns {Promise} Paged transcript rows, ordered by timestamp/createdAt ascending
|
|
810
|
+
*/
|
|
811
|
+
async getTranscript(roomId, options = {}) {
|
|
812
|
+
this.sdk.validateParams(
|
|
813
|
+
{ roomId },
|
|
814
|
+
{
|
|
815
|
+
roomId: { type: 'string', required: true },
|
|
816
|
+
},
|
|
817
|
+
);
|
|
818
|
+
|
|
819
|
+
const validationSchema = {};
|
|
820
|
+
if ('limit' in options) validationSchema.limit = { type: 'number' };
|
|
821
|
+
if ('offset' in options) validationSchema.offset = { type: 'number' };
|
|
822
|
+
|
|
823
|
+
if (Object.keys(validationSchema).length > 0) {
|
|
824
|
+
this.sdk.validateParams(options, validationSchema);
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
const params = {
|
|
828
|
+
query: options,
|
|
829
|
+
};
|
|
830
|
+
|
|
831
|
+
const result = await this.sdk._fetch(
|
|
832
|
+
`/video/${roomId}/transcript`,
|
|
833
|
+
'GET',
|
|
834
|
+
params,
|
|
835
|
+
);
|
|
836
|
+
return result;
|
|
837
|
+
}
|
|
838
|
+
|
|
752
839
|
/**
|
|
753
840
|
* Edit a chat message in a video room
|
|
754
841
|
* Only the participant who created the message can edit it
|