@vapi-ai/web 2.3.7 → 2.3.9

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.
Files changed (7) hide show
  1. package/api.ts +10641 -1229
  2. package/dist/api.d.ts +10025 -1097
  3. package/dist/api.js +190 -59
  4. package/dist/vapi.d.ts +28 -1
  5. package/dist/vapi.js +332 -34
  6. package/package.json +1 -1
  7. package/vapi.ts +390 -36
package/vapi.ts CHANGED
@@ -25,6 +25,7 @@ import { client } from './client';
25
25
  export interface AddMessageMessage {
26
26
  type: 'add-message';
27
27
  message: ChatCompletionMessageParam;
28
+ triggerResponseEnabled?: boolean;
28
29
  }
29
30
 
30
31
  export interface ControlMessages {
@@ -55,7 +56,36 @@ type VapiEventNames =
55
56
  | 'message'
56
57
  | 'video'
57
58
  | 'error'
58
- | 'daily-participant-updated';
59
+ | 'camera-error'
60
+ | 'network-quality-change'
61
+ | 'network-connection'
62
+ | 'daily-participant-updated'
63
+ | 'call-start-progress'
64
+ | 'call-start-success'
65
+ | 'call-start-failed';
66
+
67
+ interface CallStartProgressEvent {
68
+ stage: string;
69
+ status: 'started' | 'completed' | 'failed';
70
+ duration?: number;
71
+ timestamp: string;
72
+ metadata?: Record<string, any>;
73
+ }
74
+
75
+ interface CallStartSuccessEvent {
76
+ totalDuration: number;
77
+ callId?: string;
78
+ timestamp: string;
79
+ }
80
+
81
+ interface CallStartFailedEvent {
82
+ stage: string;
83
+ totalDuration: number;
84
+ error: string;
85
+ errorStack?: string;
86
+ timestamp: string;
87
+ context: Record<string, any>;
88
+ }
59
89
 
60
90
  type VapiEventListeners = {
61
91
  'call-end': () => void;
@@ -66,7 +96,13 @@ type VapiEventListeners = {
66
96
  video: (track: MediaStreamTrack) => void;
67
97
  message: (message: any) => void;
68
98
  error: (error: any) => void;
99
+ 'camera-error': (error: any) => void;
100
+ 'network-quality-change': (event: any) => void;
101
+ 'network-connection': (event: any) => void;
69
102
  'daily-participant-updated': (participant: DailyParticipant) => void;
103
+ 'call-start-progress': (event: CallStartProgressEvent) => void;
104
+ 'call-start-success': (event: CallStartSuccessEvent) => void;
105
+ 'call-start-failed': (event: CallStartFailedEvent) => void;
70
106
  };
71
107
 
72
108
  async function startAudioPlayer(
@@ -203,16 +239,53 @@ export default class Vapi extends VapiEventEmitter {
203
239
  workflow?: CreateWorkflowDTO | string,
204
240
  workflowOverrides?: WorkflowOverrides,
205
241
  ): Promise<Call | null> {
242
+ const startTime = Date.now();
243
+
244
+ // Input validation with detailed error messages
206
245
  if (!assistant && !squad && !workflow) {
207
- throw new Error('Assistant or Squad or Workflow must be provided.');
246
+ const error = new Error('Assistant or Squad or Workflow must be provided.');
247
+ this.emit('error', {
248
+ type: 'validation-error',
249
+ stage: 'input-validation',
250
+ message: error.message,
251
+ timestamp: new Date().toISOString()
252
+ });
253
+ throw error;
208
254
  }
209
255
 
210
256
  if (this.started) {
257
+ this.emit('call-start-progress', {
258
+ stage: 'validation',
259
+ status: 'failed',
260
+ timestamp: new Date().toISOString(),
261
+ metadata: { reason: 'already-started' }
262
+ });
211
263
  return null;
212
264
  }
265
+
266
+ this.emit('call-start-progress', {
267
+ stage: 'initialization',
268
+ status: 'started',
269
+ timestamp: new Date().toISOString(),
270
+ metadata: {
271
+ hasAssistant: !!assistant,
272
+ hasSquad: !!squad,
273
+ hasWorkflow: !!workflow
274
+ }
275
+ });
276
+
213
277
  this.started = true;
214
278
 
215
279
  try {
280
+ // Stage 1: Create web call
281
+ this.emit('call-start-progress', {
282
+ stage: 'web-call-creation',
283
+ status: 'started',
284
+ timestamp: new Date().toISOString()
285
+ });
286
+
287
+ const webCallStartTime = Date.now();
288
+
216
289
  const webCall = (
217
290
  await client.call.callControllerCreateWebCall({
218
291
  assistant: typeof assistant === 'string' ? undefined : assistant,
@@ -225,8 +298,27 @@ export default class Vapi extends VapiEventEmitter {
225
298
  workflowOverrides,
226
299
  })
227
300
  ).data;
301
+
302
+ const webCallDuration = Date.now() - webCallStartTime;
303
+ this.emit('call-start-progress', {
304
+ stage: 'web-call-creation',
305
+ status: 'completed',
306
+ duration: webCallDuration,
307
+ timestamp: new Date().toISOString(),
308
+ metadata: {
309
+ callId: webCall?.id || 'unknown',
310
+ videoRecordingEnabled: webCall?.artifactPlan?.videoRecordingEnabled ?? false,
311
+ voiceProvider: webCall?.assistant?.voice?.provider || 'unknown'
312
+ }
313
+ });
228
314
 
229
315
  if (this.call) {
316
+ this.emit('call-start-progress', {
317
+ stage: 'daily-call-object-creation',
318
+ status: 'started',
319
+ timestamp: new Date().toISOString(),
320
+ metadata: { action: 'cleanup-existing' }
321
+ });
230
322
  this.cleanup();
231
323
  }
232
324
 
@@ -235,11 +327,53 @@ export default class Vapi extends VapiEventEmitter {
235
327
 
236
328
  const isVideoEnabled = webCall?.assistant?.voice?.provider === 'tavus';
237
329
 
238
- this.call = DailyIframe.createCallObject({
239
- audioSource: this.dailyCallObject.audioSource ?? true,
240
- videoSource: this.dailyCallObject.videoSource ?? isVideoRecordingEnabled,
241
- dailyConfig: this.dailyCallConfig,
330
+ // Stage 2: Create Daily call object
331
+ this.emit('call-start-progress', {
332
+ stage: 'daily-call-object-creation',
333
+ status: 'started',
334
+ timestamp: new Date().toISOString(),
335
+ metadata: {
336
+ audioSource: this.dailyCallObject.audioSource ?? true,
337
+ videoSource: this.dailyCallObject.videoSource ?? isVideoRecordingEnabled,
338
+ isVideoRecordingEnabled,
339
+ isVideoEnabled
340
+ }
242
341
  });
342
+
343
+ const dailyCallStartTime = Date.now();
344
+
345
+ try {
346
+ this.call = DailyIframe.createCallObject({
347
+ audioSource: this.dailyCallObject.audioSource ?? true,
348
+ videoSource: this.dailyCallObject.videoSource ?? isVideoRecordingEnabled,
349
+ dailyConfig: this.dailyCallConfig,
350
+ });
351
+
352
+ const dailyCallDuration = Date.now() - dailyCallStartTime;
353
+ this.emit('call-start-progress', {
354
+ stage: 'daily-call-object-creation',
355
+ status: 'completed',
356
+ duration: dailyCallDuration,
357
+ timestamp: new Date().toISOString()
358
+ });
359
+ } catch (error) {
360
+ const dailyCallDuration = Date.now() - dailyCallStartTime;
361
+ this.emit('call-start-progress', {
362
+ stage: 'daily-call-object-creation',
363
+ status: 'failed',
364
+ duration: dailyCallDuration,
365
+ timestamp: new Date().toISOString(),
366
+ metadata: { error: error?.toString() }
367
+ });
368
+ this.emit('error', {
369
+ type: 'daily-call-object-creation-error',
370
+ stage: 'daily-call-object-creation',
371
+ error,
372
+ timestamp: new Date().toISOString()
373
+ });
374
+ throw error;
375
+ }
376
+
243
377
  this.call.iframe()?.style.setProperty('display', 'none');
244
378
 
245
379
  this.call.on('left-meeting', () => {
@@ -266,7 +400,15 @@ export default class Vapi extends VapiEventEmitter {
266
400
  });
267
401
 
268
402
  this.call.on('camera-error', (error: any) => {
269
- this.emit('error', error);
403
+ this.emit('camera-error', error);
404
+ });
405
+
406
+ this.call.on('network-quality-change', (event: any) => {
407
+ this.emit('network-quality-change', event);
408
+ });
409
+
410
+ this.call.on('network-connection', (event: any) => {
411
+ this.emit('network-connection', event);
270
412
  });
271
413
 
272
414
  this.call.on('track-started', async (e) => {
@@ -312,41 +454,182 @@ export default class Vapi extends VapiEventEmitter {
312
454
  destroyAudioPlayer(e.participant.session_id);
313
455
  });
314
456
 
315
- // Allow mobile devices to finish processing the microphone permissions
316
- // request before joining the call and playing the assistant's audio
317
- if (this.isMobileDevice()) {
457
+ // Stage 3: Mobile device handling and permissions
458
+ const isMobile = this.isMobileDevice();
459
+ this.emit('call-start-progress', {
460
+ stage: 'mobile-permissions',
461
+ status: 'started',
462
+ timestamp: new Date().toISOString(),
463
+ metadata: { isMobile }
464
+ });
465
+
466
+ if (isMobile) {
467
+ const mobileWaitStartTime = Date.now();
318
468
  await this.sleep(1000);
469
+ const mobileWaitDuration = Date.now() - mobileWaitStartTime;
470
+ this.emit('call-start-progress', {
471
+ stage: 'mobile-permissions',
472
+ status: 'completed',
473
+ duration: mobileWaitDuration,
474
+ timestamp: new Date().toISOString(),
475
+ metadata: { action: 'permissions-wait' }
476
+ });
477
+ } else {
478
+ this.emit('call-start-progress', {
479
+ stage: 'mobile-permissions',
480
+ status: 'completed',
481
+ timestamp: new Date().toISOString(),
482
+ metadata: { action: 'skipped-not-mobile' }
483
+ });
319
484
  }
320
485
 
321
- await this.call.join({
322
- // @ts-expect-error This exists
323
- url: webCall.webCallUrl,
324
- subscribeToTracksAutomatically: false,
486
+ // Stage 4: Join the call
487
+ this.emit('call-start-progress', {
488
+ stage: 'daily-call-join',
489
+ status: 'started',
490
+ timestamp: new Date().toISOString()
325
491
  });
492
+
493
+ const joinStartTime = Date.now();
494
+
495
+ try {
496
+ await this.call.join({
497
+ // @ts-expect-error This exists
498
+ url: webCall.webCallUrl,
499
+ subscribeToTracksAutomatically: false,
500
+ });
501
+
502
+ const joinDuration = Date.now() - joinStartTime;
503
+ this.emit('call-start-progress', {
504
+ stage: 'daily-call-join',
505
+ status: 'completed',
506
+ duration: joinDuration,
507
+ timestamp: new Date().toISOString()
508
+ });
509
+ } catch (error) {
510
+ const joinDuration = Date.now() - joinStartTime;
511
+ this.emit('call-start-progress', {
512
+ stage: 'daily-call-join',
513
+ status: 'failed',
514
+ duration: joinDuration,
515
+ timestamp: new Date().toISOString(),
516
+ metadata: { error: error?.toString() }
517
+ });
518
+ this.emit('error', {
519
+ type: 'daily-call-join-error',
520
+ stage: 'daily-call-join',
521
+ error,
522
+ duration: joinDuration,
523
+ timestamp: new Date().toISOString()
524
+ });
525
+ throw error;
526
+ }
326
527
 
528
+ // Stage 5: Video recording setup (if enabled)
327
529
  if (isVideoRecordingEnabled) {
530
+ this.emit('call-start-progress', {
531
+ stage: 'video-recording-setup',
532
+ status: 'started',
533
+ timestamp: new Date().toISOString()
534
+ });
535
+
328
536
  const recordingRequestedTime = new Date().getTime();
537
+ const recordingStartTime = Date.now();
329
538
 
330
- this.call.startRecording({
331
- width: 1280,
332
- height: 720,
333
- backgroundColor: '#FF1F2D3D',
334
- layout: {
335
- preset: 'default',
336
- },
337
- });
539
+ try {
540
+ this.call.startRecording({
541
+ width: 1280,
542
+ height: 720,
543
+ backgroundColor: '#FF1F2D3D',
544
+ layout: {
545
+ preset: 'default',
546
+ },
547
+ });
338
548
 
339
- this.call.on('recording-started', () => {
340
- this.send({
341
- type: 'control',
342
- control: 'say-first-message',
343
- videoRecordingStartDelaySeconds:
344
- (new Date().getTime() - recordingRequestedTime) / 1000,
549
+ const recordingSetupDuration = Date.now() - recordingStartTime;
550
+ this.emit('call-start-progress', {
551
+ stage: 'video-recording-setup',
552
+ status: 'completed',
553
+ duration: recordingSetupDuration,
554
+ timestamp: new Date().toISOString()
345
555
  });
556
+
557
+ this.call.on('recording-started', () => {
558
+ const totalRecordingDelay = (new Date().getTime() - recordingRequestedTime) / 1000;
559
+ this.emit('call-start-progress', {
560
+ stage: 'video-recording-started',
561
+ status: 'completed',
562
+ timestamp: new Date().toISOString(),
563
+ metadata: { delaySeconds: totalRecordingDelay }
564
+ });
565
+
566
+ this.send({
567
+ type: 'control',
568
+ control: 'say-first-message',
569
+ videoRecordingStartDelaySeconds: totalRecordingDelay,
570
+ });
571
+ });
572
+ } catch (error) {
573
+ const recordingSetupDuration = Date.now() - recordingStartTime;
574
+ this.emit('call-start-progress', {
575
+ stage: 'video-recording-setup',
576
+ status: 'failed',
577
+ duration: recordingSetupDuration,
578
+ timestamp: new Date().toISOString(),
579
+ metadata: { error: error?.toString() }
580
+ });
581
+ this.emit('error', {
582
+ type: 'video-recording-setup-error',
583
+ stage: 'video-recording-setup',
584
+ error,
585
+ timestamp: new Date().toISOString()
586
+ });
587
+ // Don't throw here, video recording is optional
588
+ }
589
+ } else {
590
+ this.emit('call-start-progress', {
591
+ stage: 'video-recording-setup',
592
+ status: 'completed',
593
+ timestamp: new Date().toISOString(),
594
+ metadata: { action: 'skipped-not-enabled' }
346
595
  });
347
596
  }
348
597
 
349
- this.call.startRemoteParticipantsAudioLevelObserver(100);
598
+ // Stage 6: Audio level observer setup
599
+ this.emit('call-start-progress', {
600
+ stage: 'audio-observer-setup',
601
+ status: 'started',
602
+ timestamp: new Date().toISOString()
603
+ });
604
+
605
+ const audioObserverStartTime = Date.now();
606
+
607
+ try {
608
+ this.call.startRemoteParticipantsAudioLevelObserver(100);
609
+ const audioObserverDuration = Date.now() - audioObserverStartTime;
610
+ this.emit('call-start-progress', {
611
+ stage: 'audio-observer-setup',
612
+ status: 'completed',
613
+ duration: audioObserverDuration,
614
+ timestamp: new Date().toISOString()
615
+ });
616
+ } catch (error) {
617
+ const audioObserverDuration = Date.now() - audioObserverStartTime;
618
+ this.emit('call-start-progress', {
619
+ stage: 'audio-observer-setup',
620
+ status: 'failed',
621
+ duration: audioObserverDuration,
622
+ timestamp: new Date().toISOString(),
623
+ metadata: { error: error?.toString() }
624
+ });
625
+ this.emit('error', {
626
+ type: 'audio-observer-setup-error',
627
+ stage: 'audio-observer-setup',
628
+ error,
629
+ timestamp: new Date().toISOString()
630
+ });
631
+ // Don't throw here, this is non-critical
632
+ }
350
633
 
351
634
  this.call.on('remote-participants-audio-level', (e) => {
352
635
  if (e) this.handleRemoteParticipantsAudioLevel(e);
@@ -371,18 +654,89 @@ export default class Vapi extends VapiEventEmitter {
371
654
  }
372
655
  });
373
656
 
374
- this.call.updateInputSettings({
375
- audio: {
376
- processor: {
377
- type: 'noise-cancellation',
657
+ // Stage 7: Audio processing setup
658
+ this.emit('call-start-progress', {
659
+ stage: 'audio-processing-setup',
660
+ status: 'started',
661
+ timestamp: new Date().toISOString()
662
+ });
663
+
664
+ const audioProcessingStartTime = Date.now();
665
+
666
+ try {
667
+ this.call.updateInputSettings({
668
+ audio: {
669
+ processor: {
670
+ type: 'noise-cancellation',
671
+ },
378
672
  },
379
- },
673
+ });
674
+
675
+ const audioProcessingDuration = Date.now() - audioProcessingStartTime;
676
+ this.emit('call-start-progress', {
677
+ stage: 'audio-processing-setup',
678
+ status: 'completed',
679
+ duration: audioProcessingDuration,
680
+ timestamp: new Date().toISOString()
681
+ });
682
+ } catch (error) {
683
+ const audioProcessingDuration = Date.now() - audioProcessingStartTime;
684
+ this.emit('call-start-progress', {
685
+ stage: 'audio-processing-setup',
686
+ status: 'failed',
687
+ duration: audioProcessingDuration,
688
+ timestamp: new Date().toISOString(),
689
+ metadata: { error: error?.toString() }
690
+ });
691
+ this.emit('error', {
692
+ type: 'audio-processing-setup-error',
693
+ stage: 'audio-processing-setup',
694
+ error,
695
+ timestamp: new Date().toISOString()
696
+ });
697
+ // Don't throw here, this is non-critical
698
+ }
699
+
700
+ const totalDuration = Date.now() - startTime;
701
+ this.emit('call-start-success', {
702
+ totalDuration,
703
+ callId: webCall?.id || 'unknown',
704
+ timestamp: new Date().toISOString()
380
705
  });
381
706
 
382
707
  return webCall;
383
708
  } catch (e) {
384
- console.error(e);
385
- this.emit('error', e);
709
+ const totalDuration = Date.now() - startTime;
710
+
711
+ this.emit('call-start-failed', {
712
+ stage: 'unknown',
713
+ totalDuration,
714
+ error: e?.toString() || 'Unknown error occurred',
715
+ errorStack: e instanceof Error ? e.stack : 'No stack trace available',
716
+ timestamp: new Date().toISOString(),
717
+ context: {
718
+ hasAssistant: !!assistant,
719
+ hasSquad: !!squad,
720
+ hasWorkflow: !!workflow,
721
+ isMobile: this.isMobileDevice()
722
+ }
723
+ });
724
+
725
+ // Also emit the generic error event for backward compatibility
726
+ this.emit('error', {
727
+ type: 'start-method-error',
728
+ stage: 'unknown',
729
+ error: e,
730
+ totalDuration,
731
+ timestamp: new Date().toISOString(),
732
+ context: {
733
+ hasAssistant: !!assistant,
734
+ hasSquad: !!squad,
735
+ hasWorkflow: !!workflow,
736
+ isMobile: this.isMobileDevice()
737
+ }
738
+ });
739
+
386
740
  this.cleanup();
387
741
  return null;
388
742
  }