@unboundcx/sdk 4.0.10 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unboundcx/sdk",
3
- "version": "4.0.10",
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
- // Playbook identifier (optional - links transcription to a playbook session)
69
+ // Optional playbook ID for real-time agent scoring
70
70
  string playbook_id = 16;
71
71
 
72
- // Bridge identifier (optional - links related transcription streams)
73
- // When multiple bridged call legs have transcription active,
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
- // Task identifier (optional - links transcription to a specific task)
76
+ // Optional task ID for playbook session tracking
78
77
  string task_id = 18;
79
78
 
80
- // Worker identifier (optional)
79
+ // Optional worker ID for playbook session tracking
81
80
  string worker_id = 19;
82
81
 
83
- // Generate subject from transcription (optional)
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
- // Generate transcript summary (optional)
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
- // Generate sentiment analysis (optional)
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
@@ -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({ calendarId, eventTypes, webhookUrl, expirationTime }) {
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: true },
12
- webhookUrl: { type: 'string', required: true },
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, eventTypes, webhookUrl };
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/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