@unboundcx/sdk 2.8.5 → 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 CHANGED
@@ -11,7 +11,7 @@ The official JavaScript SDK for Unbound's comprehensive communication and AI pla
11
11
  - 📱 **Messaging**: SMS/MMS and Email with templates and campaigns
12
12
  - 📞 **Voice**: Call management, conferencing, recording, transcription
13
13
  - 📹 **Video**: Video conferencing with advanced controls
14
- - 🤖 **AI**: Generative AI chat and text-to-speech
14
+ - 🤖 **AI**: Generative AI chat, text-to-speech, and speech-to-text
15
15
  - 💾 **Data**: Object management with queries and relationships
16
16
  - 🔄 **Workflows**: Programmable workflow execution
17
17
  - 🔌 **Extensible**: Plugin system for transports and extensions
@@ -276,6 +276,53 @@ const audio = await api.ai.tts.create({
276
276
  voice: 'en-US-Standard-A',
277
277
  audioEncoding: 'MP3',
278
278
  });
279
+
280
+ // Speech-to-Text - File/Storage Transcription
281
+ const transcription = await api.ai.stt.create({
282
+ sourceType: 'storage',
283
+ storageId: 'audio-file-id',
284
+ engine: 'google',
285
+ languageCode: 'en-US',
286
+ metadata: {
287
+ diarization: true,
288
+ speakerCount: 2,
289
+ },
290
+ });
291
+
292
+ // Speech-to-Text - Real-Time Streaming (NEW Simplified API)
293
+ const stream = await api.ai.stt.stream({
294
+ engine: 'google',
295
+ model: 'phone_call',
296
+ languageCode: 'en-US',
297
+ encoding: 'LINEAR16',
298
+ sampleRateHertz: 16000,
299
+ engagementSessionId: 'eng-123',
300
+ });
301
+
302
+ // Handle transcription events
303
+ stream.on('transcript', (result) => {
304
+ console.log(`${result.isFinal ? '[FINAL]' : '[interim]'} ${result.text}`);
305
+ // Transcripts are automatically saved to database!
306
+ });
307
+
308
+ stream.on('error', (error) => console.error('Stream error:', error));
309
+ stream.on('close', () => console.log('Stream closed'));
310
+
311
+ // Write audio chunks
312
+ stream.write(audioChunk); // Buffer or Uint8Array
313
+ stream.end(); // Close when done
314
+
315
+ // Later: Retrieve full transcript from database (automatic storage)
316
+ const savedTranscript = await api.ai.stt.get(stream.sessionId, {
317
+ includeMessages: true,
318
+ });
319
+
320
+ // List all transcriptions
321
+ const transcriptions = await api.ai.stt.list({
322
+ engagementSessionId: 'eng-123',
323
+ status: 'completed',
324
+ limit: 50,
325
+ });
279
326
  ```
280
327
 
281
328
  ### Utility Services
package/base.js CHANGED
@@ -167,6 +167,7 @@ export class BaseSDK {
167
167
  }
168
168
 
169
169
  async _fetch(endpoint, method, params = {}, forceFetch = false) {
170
+ const startTime = Date.now();
170
171
  const { body, query, headers = {}, returnRawResponse = false } = params;
171
172
 
172
173
  this.validateParams(
@@ -195,7 +196,6 @@ export class BaseSDK {
195
196
  params.headers = headers;
196
197
 
197
198
  // Try transport plugins first
198
- console.log(`sdk :: request :: forceFetch:${forceFetch} :: endpoint:${endpoint}`);
199
199
  const transport = await this._getAvailableTransport(forceFetch);
200
200
  let response;
201
201
  if (transport) {
@@ -221,14 +221,27 @@ export class BaseSDK {
221
221
  );
222
222
 
223
223
  // Built-in HTTP transport (fallback)
224
- return this._httpRequest(endpoint, method, params, returnRawResponse);
224
+ return this._httpRequest(
225
+ endpoint,
226
+ method,
227
+ params,
228
+ returnRawResponse,
229
+ startTime,
230
+ );
225
231
  }
226
232
  } else {
227
233
  // No transport available, fallback to HTTP
228
234
  if (forceFetch && process.env.AUTH_V3_TOKEN_TYPE_OVERRIDE) {
229
- params.headers['x-token-type-override'] = process.env.AUTH_V3_TOKEN_TYPE_OVERRIDE;
235
+ params.headers['x-token-type-override'] =
236
+ process.env.AUTH_V3_TOKEN_TYPE_OVERRIDE;
230
237
  }
231
- return this._httpRequest(endpoint, method, params, returnRawResponse);
238
+ return this._httpRequest(
239
+ endpoint,
240
+ method,
241
+ params,
242
+ returnRawResponse,
243
+ startTime,
244
+ );
232
245
  }
233
246
 
234
247
  // For streaming requests, return the raw response from transports
@@ -236,7 +249,14 @@ export class BaseSDK {
236
249
  return response;
237
250
  }
238
251
 
239
- return this._processResponse(response, transport.name, method, endpoint);
252
+ const duration = Date.now() - startTime;
253
+ return this._processResponse(
254
+ response,
255
+ transport.name,
256
+ method,
257
+ endpoint,
258
+ duration,
259
+ );
240
260
  }
241
261
 
242
262
  _isMultipartBody(body) {
@@ -269,7 +289,13 @@ export class BaseSDK {
269
289
  return false;
270
290
  }
271
291
 
272
- async _httpRequest(endpoint, method, params = {}, returnRawResponse = false) {
292
+ async _httpRequest(
293
+ endpoint,
294
+ method,
295
+ params = {},
296
+ returnRawResponse = false,
297
+ startTime = Date.now(),
298
+ ) {
273
299
  const { body, query, headers = {} } = params;
274
300
 
275
301
  const options = {
@@ -325,16 +351,17 @@ export class BaseSDK {
325
351
  }
326
352
 
327
353
  const response = await fetch(url, options);
354
+ const duration = Date.now() - startTime;
328
355
 
329
356
  // For streaming requests, return the raw fetch response
330
357
  if (returnRawResponse) {
331
358
  return response;
332
359
  }
333
360
 
334
- return this._processResponse(response, 'https', method, endpoint);
361
+ return this._processResponse(response, 'https', method, endpoint, duration);
335
362
  }
336
363
 
337
- async _processResponse(response, transport, method, endpoint) {
364
+ async _processResponse(response, transport, method, endpoint, duration = 0) {
338
365
  // Check if the response indicates an HTTP error
339
366
  // These are API/configuration errors, not transport failures
340
367
 
@@ -400,9 +427,11 @@ export class BaseSDK {
400
427
  // Debug logging for successful HTTP requests
401
428
  if (this.debugMode) {
402
429
  console.log(
403
- `API :: ERROR :: ${transport} :: ${method.toUpperCase()} :: ${endpoint} :: ${
430
+ `API :: ERROR :: ${transport} :: ${method.toUpperCase()} :: ${
431
+ this.baseURL
432
+ }${endpoint} :: ${
404
433
  response?.status
405
- } :: ${responseRequestId}`,
434
+ } :: ${responseRequestId} :: ${duration}ms`,
406
435
  httpError,
407
436
  );
408
437
  }
@@ -443,9 +472,11 @@ export class BaseSDK {
443
472
  // Debug logging for successful HTTP requests
444
473
  if (this.debugMode) {
445
474
  console.log(
446
- `API :: ${transport} :: ${method.toUpperCase()} :: ${endpoint} :: ${
475
+ `API :: ${transport} :: ${method.toUpperCase()} :: ${
476
+ this.baseURL
477
+ }${endpoint} :: ${
447
478
  response?.status
448
- } :: ${responseRequestId}`,
479
+ } :: ${responseRequestId} :: ${duration}ms`,
449
480
  );
450
481
  }
451
482
 
package/index.js CHANGED
@@ -24,6 +24,7 @@ import { PhoneNumbersService } from './services/phoneNumbers.js';
24
24
  import { RecordTypesService } from './services/recordTypes.js';
25
25
  import { GenerateIdService } from './services/generateId.js';
26
26
  import { EngagementMetricsService } from './services/engagementMetrics.js';
27
+ import { TaskRouterService } from './services/taskRouter.js';
27
28
 
28
29
  class UnboundSDK extends BaseSDK {
29
30
  constructor(options = {}) {
@@ -89,6 +90,7 @@ class UnboundSDK extends BaseSDK {
89
90
  this.recordTypes = new RecordTypesService(this);
90
91
  this.generateId = new GenerateIdService(this);
91
92
  this.engagementMetrics = new EngagementMetricsService(this);
93
+ this.taskRouter = new TaskRouterService(this);
92
94
 
93
95
  // Add additional services that might be missing
94
96
  this._initializeAdditionalServices();
@@ -153,6 +155,77 @@ class UnboundSDK extends BaseSDK {
153
155
  'buildMasterAuth is only available with the internal SDK extension. Please use: sdk.use(InternalExtension)',
154
156
  );
155
157
  }
158
+
159
+ /**
160
+ * Check SDK configuration and API connectivity
161
+ * Calls the /health endpoint to verify SDK setup
162
+ *
163
+ * @returns {Promise<Object>} Health check result with:
164
+ * - healthy: boolean - If API is reachable
165
+ * - hasAuthorization: boolean - If auth credentials were received by API
166
+ * - authType: string|null - Type of auth detected by API ('bearer', 'cookie', or 'bearer+cookie')
167
+ * - namespace: string - Current namespace
168
+ * - environment: string - 'node' or 'browser'
169
+ * - transport: string - Transport method used ('HTTP', 'WebSocket', etc.)
170
+ */
171
+ async status() {
172
+ try {
173
+ const response = await this._fetch('/health', 'GET', {
174
+ returnRawResponse: true,
175
+ });
176
+
177
+ // Parse response
178
+ let healthData;
179
+ try {
180
+ if (typeof response.json === 'function') {
181
+ healthData = await response.json();
182
+ } else if (response.body) {
183
+ healthData =
184
+ typeof response.body === 'string'
185
+ ? JSON.parse(response.body)
186
+ : response.body;
187
+ } else {
188
+ healthData = {};
189
+ }
190
+ } catch (e) {
191
+ healthData = {};
192
+ }
193
+
194
+ return {
195
+ healthy: response.ok || response.status === 200,
196
+ hasAuthorization: healthData.hasAuthorization || false,
197
+ authType: healthData.authType || null,
198
+ namespace: this.namespace,
199
+ environment: this.environment,
200
+ transport: healthData.transport || 'unknown',
201
+ timestamp: healthData.timestamp,
202
+ url: healthData.url || null,
203
+ statusCode: response.status,
204
+ };
205
+ } catch (error) {
206
+ return {
207
+ healthy: false,
208
+ hasAuthorization: false,
209
+ authType: null,
210
+ namespace: this.namespace,
211
+ environment: this.environment,
212
+ error: error.message,
213
+ statusCode: error.status || null,
214
+ };
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Get the client's IP address
220
+ * Always uses fetch transport (never WebSocket or other transports)
221
+ *
222
+ * @returns {Promise<Object>} Response with:
223
+ * - ip: string - The client's IP address
224
+ */
225
+ async getIp() {
226
+ // Force fetch transport (pass true as forceFetch parameter)
227
+ return await this._fetch('/get-ip', 'GET', {}, true);
228
+ }
156
229
  }
157
230
 
158
231
  // Export both the class and a factory function for convenience
@@ -193,4 +266,6 @@ export {
193
266
  } from './services/recordTypes.js';
194
267
  export { GenerateIdService } from './services/generateId.js';
195
268
  export { EngagementMetricsService } from './services/engagementMetrics.js';
269
+ export { TaskRouterService } from './services/taskRouter.js';
270
+ export { WorkerService } from './services/taskRouter/WorkerService.js';
196
271
  export { BaseSDK } from './base.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unboundcx/sdk",
3
- "version": "2.8.5",
3
+ "version": "2.8.7",
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",
@@ -41,6 +41,7 @@
41
41
  "services/**/*.js",
42
42
  "transports/**/*.js",
43
43
  "types/**/*.d.ts",
44
+ "proto/**/*.proto",
44
45
  "README.md",
45
46
  "LICENSE"
46
47
  ],
@@ -64,7 +65,9 @@
64
65
  },
65
66
  "dependencies": {},
66
67
  "optionalDependencies": {
67
- "mime-types": "^2.1.35"
68
+ "mime-types": "^2.1.35",
69
+ "@grpc/grpc-js": "^1.14.1",
70
+ "@grpc/proto-loader": "^0.7.15"
68
71
  },
69
72
  "peerDependencies": {
70
73
  "socket.io-client": "^4.0.0"
@@ -0,0 +1,207 @@
1
+ syntax = "proto3";
2
+
3
+ package transcription;
4
+
5
+ // Transcription Service Definition
6
+ service TranscriptionService {
7
+ // Bidirectional streaming for real-time transcription
8
+ // Client streams audio chunks, server streams back transcription results
9
+ rpc StreamTranscribe(stream AudioRequest) returns (stream TranscriptResponse);
10
+
11
+ // Get transcription session status
12
+ rpc GetSessionStatus(SessionStatusRequest) returns (SessionStatusResponse);
13
+ }
14
+
15
+ // Audio streaming request message
16
+ message AudioRequest {
17
+ // Audio data chunk (raw bytes)
18
+ bytes audio_chunk = 1;
19
+
20
+ // Session ID for this transcription session
21
+ string session_id = 2;
22
+
23
+ // Authentication token
24
+ string token = 3;
25
+
26
+ // Language code (optional, will auto-detect if not provided)
27
+ // Examples: "en", "es", "fr", "de", "ja", "zh"
28
+ string language = 4;
29
+
30
+ // Transcription engine to use
31
+ // Options: "whisper", "google"
32
+ string engine = 5;
33
+
34
+ // Audio configuration
35
+ AudioConfig config = 6;
36
+
37
+ // Flag to indicate this is the first chunk
38
+ bool is_first_chunk = 7;
39
+
40
+ // Flag to indicate this is the last chunk
41
+ bool is_last_chunk = 8;
42
+
43
+ // SIP call identifier (required for multi-stream support)
44
+ // Used to identify which call this audio belongs to
45
+ string sip_call_id = 9;
46
+
47
+ // Audio stream side (required for multi-stream support)
48
+ // Options: "send" (outgoing), "recv" (incoming)
49
+ string side = 10;
50
+
51
+ // Speaker role (optional)
52
+ // Examples: "customer", "agent", "system"
53
+ string role = 11;
54
+
55
+ // VAD event type (optional - sent by media manager for speech detection)
56
+ // Options: "speaking_started", "speaking_stopped"
57
+ // When provided, this is a VAD event (not audio data)
58
+ string vad_event = 12;
59
+
60
+ // VAD event energy level (for speaking_started events)
61
+ double vad_energy = 13;
62
+
63
+ // VAD event speech duration in ms (for speaking_stopped events)
64
+ int64 vad_duration = 14;
65
+
66
+ // VAD event timestamp
67
+ int64 vad_timestamp = 15;
68
+
69
+ // Playbook identifier (optional - links transcription to a playbook session)
70
+ string playbook_id = 16;
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
75
+ string bridge_id = 17;
76
+
77
+ // Task identifier (optional - links transcription to a specific task)
78
+ string task_id = 18;
79
+
80
+ // Worker identifier (optional)
81
+ string worker_id = 19;
82
+
83
+ // Generate subject from transcription (optional)
84
+ bool generate_subject = 20;
85
+
86
+ // Generate transcript summary (optional)
87
+ bool generate_transcript_summary = 21;
88
+
89
+ // Generate sentiment analysis (optional)
90
+ bool generate_sentiment = 22;
91
+ }
92
+
93
+ // Audio configuration
94
+ message AudioConfig {
95
+ // Audio encoding format
96
+ // Examples: "LINEAR16", "FLAC", "MP3", "OGG_OPUS", "WEBM_OPUS"
97
+ string encoding = 1;
98
+
99
+ // Sample rate in Hertz
100
+ // Recommended: 16000 for most cases
101
+ int32 sample_rate_hertz = 2;
102
+
103
+ // Number of audio channels
104
+ // Mono = 1, Stereo = 2
105
+ int32 audio_channel_count = 3;
106
+
107
+ // Language code (deprecated, use AudioRequest.language instead)
108
+ string language_code = 4;
109
+
110
+ // Voice Activity Detection (VAD) configuration
111
+ // Enable VAD to filter out silence and improve transcription quality
112
+ bool vad_enabled = 5;
113
+
114
+ // Minimum silence duration in milliseconds to split segments
115
+ // Typical values: 300ms (fast, live calls) to 700ms (accurate, voicemails)
116
+ // Default: 500ms
117
+ int32 min_silence_duration_ms = 6;
118
+
119
+ // Padding around speech segments in milliseconds
120
+ // Adds context before/after detected speech
121
+ // Default: 400ms
122
+ int32 speech_pad_ms = 7;
123
+ }
124
+
125
+ // Transcription response message
126
+ message TranscriptResponse {
127
+ // Transcribed text
128
+ string transcript = 1;
129
+
130
+ // Confidence score (0.0 to 1.0)
131
+ float confidence = 2;
132
+
133
+ // Whether this is a final result or interim
134
+ bool is_final = 3;
135
+
136
+ // Detected or specified language
137
+ string language = 4;
138
+
139
+ // Timestamp of this result (milliseconds since epoch)
140
+ int64 timestamp = 5;
141
+
142
+ // Word-level details (optional)
143
+ repeated WordInfo words = 6;
144
+
145
+ // Start time of this segment (seconds)
146
+ float start_time = 7;
147
+
148
+ // End time of this segment (seconds)
149
+ float end_time = 8;
150
+
151
+ // SIP call identifier (echoed back from AudioRequest)
152
+ string sip_call_id = 9;
153
+
154
+ // Audio stream side (echoed back from AudioRequest)
155
+ string side = 10;
156
+
157
+ // Speaker role (echoed back from AudioRequest)
158
+ string role = 11;
159
+ }
160
+
161
+ // Word-level information
162
+ message WordInfo {
163
+ // The word text
164
+ string word = 1;
165
+
166
+ // Start time of the word (seconds)
167
+ float start_time = 2;
168
+
169
+ // End time of the word (seconds)
170
+ float end_time = 3;
171
+
172
+ // Confidence score for this word (0.0 to 1.0)
173
+ float confidence = 4;
174
+ }
175
+
176
+ // Session status request
177
+ message SessionStatusRequest {
178
+ // Session ID to query
179
+ string session_id = 1;
180
+
181
+ // Authentication token
182
+ string token = 2;
183
+ }
184
+
185
+ // Session status response
186
+ message SessionStatusResponse {
187
+ // Session ID
188
+ string session_id = 1;
189
+
190
+ // Session status: "active", "completed", "failed", "not_found"
191
+ string status = 2;
192
+
193
+ // Detected or specified language
194
+ string language = 3;
195
+
196
+ // Engine being used
197
+ string engine = 4;
198
+
199
+ // Session start time (milliseconds since epoch)
200
+ int64 start_time = 5;
201
+
202
+ // Total chunks processed
203
+ int32 total_chunks = 6;
204
+
205
+ // Error message (if status is "failed")
206
+ string error = 7;
207
+ }