@vapi-ai/web 2.5.1 → 2.6.1
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/api.ts +380 -414
- package/dist/api.d.ts +2004 -2034
- package/dist/api.js +17 -28
- package/dist/vapi.d.ts +40 -2
- package/dist/vapi.js +207 -24
- package/package.json +5 -5
- package/vapi.ts +248 -25
package/vapi.ts
CHANGED
|
@@ -5,7 +5,12 @@ import DailyIframe, {
|
|
|
5
5
|
DailyAdvancedConfig,
|
|
6
6
|
DailyFactoryOptions,
|
|
7
7
|
DailyEventObjectAppMessage,
|
|
8
|
+
DailyEventObjectLocalAudioLevel,
|
|
9
|
+
DailyEventObjectNoPayload,
|
|
8
10
|
DailyEventObjectParticipant,
|
|
11
|
+
DailyEventObjectRecordingError,
|
|
12
|
+
DailyEventObjectRecordingStarted,
|
|
13
|
+
DailyEventObjectRecordingStopped,
|
|
9
14
|
DailyEventObjectRemoteParticipantsAudioLevel,
|
|
10
15
|
DailyParticipant,
|
|
11
16
|
DailyVideoSendSettings,
|
|
@@ -62,6 +67,8 @@ type VapiEventNames =
|
|
|
62
67
|
| 'call-end'
|
|
63
68
|
| 'call-start'
|
|
64
69
|
| 'volume-level'
|
|
70
|
+
| 'local-volume-level'
|
|
71
|
+
| 'local-audio-level-observer-error'
|
|
65
72
|
| 'speech-start'
|
|
66
73
|
| 'speech-end'
|
|
67
74
|
| 'message'
|
|
@@ -73,7 +80,12 @@ type VapiEventNames =
|
|
|
73
80
|
| 'daily-participant-updated'
|
|
74
81
|
| 'call-start-progress'
|
|
75
82
|
| 'call-start-success'
|
|
76
|
-
| 'call-start-failed'
|
|
83
|
+
| 'call-start-failed'
|
|
84
|
+
| 'recording-started'
|
|
85
|
+
| 'recording-stopped'
|
|
86
|
+
| 'recording-stats'
|
|
87
|
+
| 'recording-error'
|
|
88
|
+
| 'recording-upload-completed';
|
|
77
89
|
|
|
78
90
|
interface CallStartProgressEvent {
|
|
79
91
|
stage: string;
|
|
@@ -98,10 +110,79 @@ interface CallStartFailedEvent {
|
|
|
98
110
|
context: Record<string, any>;
|
|
99
111
|
}
|
|
100
112
|
|
|
113
|
+
interface SerializedError {
|
|
114
|
+
message: string;
|
|
115
|
+
name?: string;
|
|
116
|
+
stack?: string;
|
|
117
|
+
code?: string | number;
|
|
118
|
+
cause?: string;
|
|
119
|
+
[key: string]: any;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Extracts error details into a plain object that serializes properly to JSON.
|
|
124
|
+
* Error objects don't serialize well because their properties (message, stack, name)
|
|
125
|
+
* are not enumerable and get lost when using JSON.stringify().
|
|
126
|
+
*/
|
|
127
|
+
function serializeError(error: unknown): SerializedError {
|
|
128
|
+
if (error === null || error === undefined) {
|
|
129
|
+
return { message: 'Unknown error (null or undefined)' };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (error instanceof Error) {
|
|
133
|
+
const serialized: SerializedError = {
|
|
134
|
+
message: error.message,
|
|
135
|
+
name: error.name,
|
|
136
|
+
stack: error.stack,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// Include any additional properties that might be on the error
|
|
140
|
+
const errorAsAny = error as any;
|
|
141
|
+
if (errorAsAny.code !== undefined) {
|
|
142
|
+
serialized.code = errorAsAny.code;
|
|
143
|
+
}
|
|
144
|
+
if (errorAsAny.cause !== undefined) {
|
|
145
|
+
serialized.cause = String(errorAsAny.cause);
|
|
146
|
+
}
|
|
147
|
+
if (errorAsAny.reason !== undefined) {
|
|
148
|
+
serialized.reason = errorAsAny.reason;
|
|
149
|
+
}
|
|
150
|
+
if (errorAsAny.details !== undefined) {
|
|
151
|
+
serialized.details = errorAsAny.details;
|
|
152
|
+
}
|
|
153
|
+
// Daily.co specific error properties
|
|
154
|
+
if (errorAsAny.errorMsg !== undefined) {
|
|
155
|
+
serialized.errorMsg = errorAsAny.errorMsg;
|
|
156
|
+
}
|
|
157
|
+
if (errorAsAny.error !== undefined && typeof errorAsAny.error === 'string') {
|
|
158
|
+
serialized.errorDetail = errorAsAny.error;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return serialized;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (typeof error === 'string') {
|
|
165
|
+
return { message: error };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (typeof error === 'object') {
|
|
169
|
+
// It's already a plain object, but let's ensure message exists
|
|
170
|
+
const errorObj = error as Record<string, any>;
|
|
171
|
+
return {
|
|
172
|
+
message: errorObj.message || errorObj.error || JSON.stringify(error),
|
|
173
|
+
...errorObj
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return { message: String(error) };
|
|
178
|
+
}
|
|
179
|
+
|
|
101
180
|
type VapiEventListeners = {
|
|
102
181
|
'call-end': () => void;
|
|
103
182
|
'call-start': () => void;
|
|
104
183
|
'volume-level': (volume: number) => void;
|
|
184
|
+
'local-volume-level': (volume: number) => void;
|
|
185
|
+
'local-audio-level-observer-error': (error: any) => void;
|
|
105
186
|
'speech-start': () => void;
|
|
106
187
|
'speech-end': () => void;
|
|
107
188
|
video: (track: MediaStreamTrack) => void;
|
|
@@ -114,6 +195,11 @@ type VapiEventListeners = {
|
|
|
114
195
|
'call-start-progress': (event: CallStartProgressEvent) => void;
|
|
115
196
|
'call-start-success': (event: CallStartSuccessEvent) => void;
|
|
116
197
|
'call-start-failed': (event: CallStartFailedEvent) => void;
|
|
198
|
+
'recording-started': (event: DailyEventObjectRecordingStarted) => void;
|
|
199
|
+
'recording-stopped': (event: DailyEventObjectRecordingStopped) => void;
|
|
200
|
+
'recording-stats': (event: DailyEventObjectNoPayload) => void;
|
|
201
|
+
'recording-error': (event: DailyEventObjectRecordingError) => void;
|
|
202
|
+
'recording-upload-completed': (event: DailyEventObjectNoPayload) => void;
|
|
117
203
|
};
|
|
118
204
|
|
|
119
205
|
type StartCallOptions = {
|
|
@@ -298,7 +384,7 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
298
384
|
this.emit('error', {
|
|
299
385
|
type: 'validation-error',
|
|
300
386
|
stage: 'input-validation',
|
|
301
|
-
|
|
387
|
+
error: serializeError(error),
|
|
302
388
|
timestamp: new Date().toISOString()
|
|
303
389
|
});
|
|
304
390
|
throw error;
|
|
@@ -410,17 +496,18 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
410
496
|
});
|
|
411
497
|
} catch (error) {
|
|
412
498
|
const dailyCallDuration = Date.now() - dailyCallStartTime;
|
|
499
|
+
const serializedError = serializeError(error);
|
|
413
500
|
this.emit('call-start-progress', {
|
|
414
501
|
stage: 'daily-call-object-creation',
|
|
415
502
|
status: 'failed',
|
|
416
503
|
duration: dailyCallDuration,
|
|
417
504
|
timestamp: new Date().toISOString(),
|
|
418
|
-
metadata: { error:
|
|
505
|
+
metadata: { error: serializedError.message }
|
|
419
506
|
});
|
|
420
507
|
this.emit('error', {
|
|
421
508
|
type: 'daily-call-object-creation-error',
|
|
422
509
|
stage: 'daily-call-object-creation',
|
|
423
|
-
error,
|
|
510
|
+
error: serializedError,
|
|
424
511
|
timestamp: new Date().toISOString()
|
|
425
512
|
});
|
|
426
513
|
throw error;
|
|
@@ -445,14 +532,22 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
445
532
|
});
|
|
446
533
|
|
|
447
534
|
this.call.on('error', (error: any) => {
|
|
448
|
-
this.emit('error',
|
|
535
|
+
this.emit('error', {
|
|
536
|
+
type: 'daily-error',
|
|
537
|
+
error: serializeError(error),
|
|
538
|
+
timestamp: new Date().toISOString()
|
|
539
|
+
});
|
|
449
540
|
if (isVideoRecordingEnabled) {
|
|
450
541
|
this.call?.stopRecording();
|
|
451
542
|
}
|
|
452
543
|
});
|
|
453
544
|
|
|
454
545
|
this.call.on('camera-error', (error: any) => {
|
|
455
|
-
this.emit('camera-error',
|
|
546
|
+
this.emit('camera-error', {
|
|
547
|
+
type: 'camera-error',
|
|
548
|
+
error: serializeError(error),
|
|
549
|
+
timestamp: new Date().toISOString()
|
|
550
|
+
});
|
|
456
551
|
});
|
|
457
552
|
|
|
458
553
|
this.call.on('network-quality-change', (event: any) => {
|
|
@@ -463,6 +558,26 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
463
558
|
this.emit('network-connection', event);
|
|
464
559
|
});
|
|
465
560
|
|
|
561
|
+
this.call.on('recording-started', (event) => {
|
|
562
|
+
if (event) this.emit('recording-started', event);
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
this.call.on('recording-stopped', (event) => {
|
|
566
|
+
if (event) this.emit('recording-stopped', event);
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
this.call.on('recording-stats', (event) => {
|
|
570
|
+
if (event) this.emit('recording-stats', event);
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
this.call.on('recording-error', (event) => {
|
|
574
|
+
if (event) this.emit('recording-error', event);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
this.call.on('recording-upload-completed', (event) => {
|
|
578
|
+
if (event) this.emit('recording-upload-completed', event);
|
|
579
|
+
});
|
|
580
|
+
|
|
466
581
|
this.call.on('track-started', async (e) => {
|
|
467
582
|
if (!e || !e.participant) {
|
|
468
583
|
return;
|
|
@@ -560,17 +675,18 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
560
675
|
});
|
|
561
676
|
} catch (error) {
|
|
562
677
|
const joinDuration = Date.now() - joinStartTime;
|
|
678
|
+
const serializedError = serializeError(error);
|
|
563
679
|
this.emit('call-start-progress', {
|
|
564
680
|
stage: 'daily-call-join',
|
|
565
681
|
status: 'failed',
|
|
566
682
|
duration: joinDuration,
|
|
567
683
|
timestamp: new Date().toISOString(),
|
|
568
|
-
metadata: { error:
|
|
684
|
+
metadata: { error: serializedError.message }
|
|
569
685
|
});
|
|
570
686
|
this.emit('error', {
|
|
571
687
|
type: 'daily-call-join-error',
|
|
572
688
|
stage: 'daily-call-join',
|
|
573
|
-
error,
|
|
689
|
+
error: serializedError,
|
|
574
690
|
duration: joinDuration,
|
|
575
691
|
timestamp: new Date().toISOString()
|
|
576
692
|
});
|
|
@@ -623,17 +739,18 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
623
739
|
});
|
|
624
740
|
} catch (error) {
|
|
625
741
|
const recordingSetupDuration = Date.now() - recordingStartTime;
|
|
742
|
+
const serializedError = serializeError(error);
|
|
626
743
|
this.emit('call-start-progress', {
|
|
627
744
|
stage: 'video-recording-setup',
|
|
628
745
|
status: 'failed',
|
|
629
746
|
duration: recordingSetupDuration,
|
|
630
747
|
timestamp: new Date().toISOString(),
|
|
631
|
-
metadata: { error:
|
|
748
|
+
metadata: { error: serializedError.message }
|
|
632
749
|
});
|
|
633
750
|
this.emit('error', {
|
|
634
751
|
type: 'video-recording-setup-error',
|
|
635
752
|
stage: 'video-recording-setup',
|
|
636
|
-
error,
|
|
753
|
+
error: serializedError,
|
|
637
754
|
timestamp: new Date().toISOString()
|
|
638
755
|
});
|
|
639
756
|
// Don't throw here, video recording is optional
|
|
@@ -658,6 +775,7 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
658
775
|
|
|
659
776
|
try {
|
|
660
777
|
this.call.startRemoteParticipantsAudioLevelObserver(100);
|
|
778
|
+
this.call.startLocalAudioLevelObserver(100);
|
|
661
779
|
const audioObserverDuration = Date.now() - audioObserverStartTime;
|
|
662
780
|
this.emit('call-start-progress', {
|
|
663
781
|
stage: 'audio-observer-setup',
|
|
@@ -667,17 +785,18 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
667
785
|
});
|
|
668
786
|
} catch (error) {
|
|
669
787
|
const audioObserverDuration = Date.now() - audioObserverStartTime;
|
|
788
|
+
const serializedError = serializeError(error);
|
|
670
789
|
this.emit('call-start-progress', {
|
|
671
790
|
stage: 'audio-observer-setup',
|
|
672
791
|
status: 'failed',
|
|
673
792
|
duration: audioObserverDuration,
|
|
674
793
|
timestamp: new Date().toISOString(),
|
|
675
|
-
metadata: { error:
|
|
794
|
+
metadata: { error: serializedError.message }
|
|
676
795
|
});
|
|
677
796
|
this.emit('error', {
|
|
678
797
|
type: 'audio-observer-setup-error',
|
|
679
798
|
stage: 'audio-observer-setup',
|
|
680
|
-
error,
|
|
799
|
+
error: serializedError,
|
|
681
800
|
timestamp: new Date().toISOString()
|
|
682
801
|
});
|
|
683
802
|
// Don't throw here, this is non-critical
|
|
@@ -687,10 +806,17 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
687
806
|
if (e) this.handleRemoteParticipantsAudioLevel(e);
|
|
688
807
|
});
|
|
689
808
|
|
|
809
|
+
this.call.on('local-audio-level', (e) => {
|
|
810
|
+
if (e) this.handleLocalAudioLevel(e);
|
|
811
|
+
});
|
|
812
|
+
|
|
690
813
|
this.call.on('app-message', (e) => this.onAppMessage(e));
|
|
691
814
|
|
|
692
815
|
this.call.on('nonfatal-error', (e) => {
|
|
693
816
|
// https://docs.daily.co/reference/daily-js/events/meeting-events#type-audio-processor-error
|
|
817
|
+
if (e?.type === 'local-audio-level-observer-error') {
|
|
818
|
+
this.emit('local-audio-level-observer-error', serializeError(e));
|
|
819
|
+
}
|
|
694
820
|
if (e?.type === 'audio-processor-error') {
|
|
695
821
|
this.call
|
|
696
822
|
?.updateInputSettings({
|
|
@@ -733,17 +859,18 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
733
859
|
});
|
|
734
860
|
} catch (error) {
|
|
735
861
|
const audioProcessingDuration = Date.now() - audioProcessingStartTime;
|
|
862
|
+
const serializedError = serializeError(error);
|
|
736
863
|
this.emit('call-start-progress', {
|
|
737
864
|
stage: 'audio-processing-setup',
|
|
738
865
|
status: 'failed',
|
|
739
866
|
duration: audioProcessingDuration,
|
|
740
867
|
timestamp: new Date().toISOString(),
|
|
741
|
-
metadata: { error:
|
|
868
|
+
metadata: { error: serializedError.message }
|
|
742
869
|
});
|
|
743
870
|
this.emit('error', {
|
|
744
871
|
type: 'audio-processing-setup-error',
|
|
745
872
|
stage: 'audio-processing-setup',
|
|
746
|
-
error,
|
|
873
|
+
error: serializedError,
|
|
747
874
|
timestamp: new Date().toISOString()
|
|
748
875
|
});
|
|
749
876
|
// Don't throw here, this is non-critical
|
|
@@ -759,12 +886,13 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
759
886
|
return webCall;
|
|
760
887
|
} catch (e) {
|
|
761
888
|
const totalDuration = Date.now() - startTime;
|
|
889
|
+
const serializedError = serializeError(e);
|
|
762
890
|
|
|
763
891
|
this.emit('call-start-failed', {
|
|
764
892
|
stage: 'unknown',
|
|
765
893
|
totalDuration,
|
|
766
|
-
error:
|
|
767
|
-
errorStack:
|
|
894
|
+
error: serializedError.message,
|
|
895
|
+
errorStack: serializedError.stack || 'No stack trace available',
|
|
768
896
|
timestamp: new Date().toISOString(),
|
|
769
897
|
context: {
|
|
770
898
|
hasAssistant: !!assistant,
|
|
@@ -778,7 +906,7 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
778
906
|
this.emit('error', {
|
|
779
907
|
type: 'start-method-error',
|
|
780
908
|
stage: 'unknown',
|
|
781
|
-
error:
|
|
909
|
+
error: serializedError,
|
|
782
910
|
totalDuration,
|
|
783
911
|
timestamp: new Date().toISOString(),
|
|
784
912
|
context: {
|
|
@@ -846,6 +974,10 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
846
974
|
}, 1000);
|
|
847
975
|
}
|
|
848
976
|
|
|
977
|
+
private handleLocalAudioLevel(e: DailyEventObjectLocalAudioLevel) {
|
|
978
|
+
this.emit('local-volume-level', e.audioLevel);
|
|
979
|
+
}
|
|
980
|
+
|
|
849
981
|
/**
|
|
850
982
|
* Stops the call by destroying the Daily call object.
|
|
851
983
|
*
|
|
@@ -946,6 +1078,57 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
946
1078
|
return this.call;
|
|
947
1079
|
}
|
|
948
1080
|
|
|
1081
|
+
/**
|
|
1082
|
+
* Returns the most recent local (microphone) audio level, a number between 0 and 1.
|
|
1083
|
+
*
|
|
1084
|
+
* Useful for detecting when the local participant's microphone is not picking up any
|
|
1085
|
+
* audio (e.g. wrong input device selected, OS-level input volume at 0, or muted
|
|
1086
|
+
* hardware) so you can surface a "we can't hear you" style warning in your UI.
|
|
1087
|
+
*
|
|
1088
|
+
* Returns 0 when there is no active call.
|
|
1089
|
+
*/
|
|
1090
|
+
public getLocalAudioLevel(): number {
|
|
1091
|
+
if (!this.call) {
|
|
1092
|
+
return 0;
|
|
1093
|
+
}
|
|
1094
|
+
return this.call.getLocalAudioLevel();
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/**
|
|
1098
|
+
* Starts (or restarts) the local audio level observer. The observer is started
|
|
1099
|
+
* automatically when a call begins, but this can be used to change the sampling
|
|
1100
|
+
* interval or restart it if it was stopped.
|
|
1101
|
+
*
|
|
1102
|
+
* While running, the Vapi instance emits 'local-volume-level' events with the local
|
|
1103
|
+
* microphone audio level (0 to 1).
|
|
1104
|
+
*
|
|
1105
|
+
* @param interval How often, in milliseconds, to emit audio level events. Defaults to Daily's default.
|
|
1106
|
+
*/
|
|
1107
|
+
public async startLocalAudioLevelObserver(interval?: number): Promise<void> {
|
|
1108
|
+
if (!this.call) {
|
|
1109
|
+
throw new Error('Call object is not available.');
|
|
1110
|
+
}
|
|
1111
|
+
await this.call.startLocalAudioLevelObserver(interval);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
/**
|
|
1115
|
+
* Stops the local audio level observer. After calling this, 'local-volume-level'
|
|
1116
|
+
* events will no longer be emitted until the observer is started again.
|
|
1117
|
+
*/
|
|
1118
|
+
public stopLocalAudioLevelObserver(): void {
|
|
1119
|
+
this.call?.stopLocalAudioLevelObserver();
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
/**
|
|
1123
|
+
* Returns whether the local audio level observer is currently running.
|
|
1124
|
+
*/
|
|
1125
|
+
public isLocalAudioLevelObserverRunning(): boolean {
|
|
1126
|
+
if (!this.call) {
|
|
1127
|
+
return false;
|
|
1128
|
+
}
|
|
1129
|
+
return this.call.isLocalAudioLevelObserverRunning();
|
|
1130
|
+
}
|
|
1131
|
+
|
|
949
1132
|
public startScreenSharing(displayMediaOptions?: DisplayMediaStreamOptions, screenVideoSendSettings?: DailyVideoSendSettings) {
|
|
950
1133
|
this.call?.startScreenShare({
|
|
951
1134
|
displayMediaOptions,
|
|
@@ -1054,14 +1237,22 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1054
1237
|
});
|
|
1055
1238
|
|
|
1056
1239
|
this.call.on('error', (error: any) => {
|
|
1057
|
-
this.emit('error',
|
|
1240
|
+
this.emit('error', {
|
|
1241
|
+
type: 'daily-error',
|
|
1242
|
+
error: serializeError(error),
|
|
1243
|
+
timestamp: new Date().toISOString()
|
|
1244
|
+
});
|
|
1058
1245
|
if (isVideoRecordingEnabled) {
|
|
1059
1246
|
this.call?.stopRecording();
|
|
1060
1247
|
}
|
|
1061
1248
|
});
|
|
1062
1249
|
|
|
1063
1250
|
this.call.on('camera-error', (error: any) => {
|
|
1064
|
-
this.emit('camera-error',
|
|
1251
|
+
this.emit('camera-error', {
|
|
1252
|
+
type: 'camera-error',
|
|
1253
|
+
error: serializeError(error),
|
|
1254
|
+
timestamp: new Date().toISOString()
|
|
1255
|
+
});
|
|
1065
1256
|
});
|
|
1066
1257
|
|
|
1067
1258
|
this.call.on('network-quality-change', (event: any) => {
|
|
@@ -1072,6 +1263,26 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1072
1263
|
this.emit('network-connection', event);
|
|
1073
1264
|
});
|
|
1074
1265
|
|
|
1266
|
+
this.call.on('recording-started', (event) => {
|
|
1267
|
+
if (event) this.emit('recording-started', event);
|
|
1268
|
+
});
|
|
1269
|
+
|
|
1270
|
+
this.call.on('recording-stopped', (event) => {
|
|
1271
|
+
if (event) this.emit('recording-stopped', event);
|
|
1272
|
+
});
|
|
1273
|
+
|
|
1274
|
+
this.call.on('recording-stats', (event) => {
|
|
1275
|
+
if (event) this.emit('recording-stats', event);
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
this.call.on('recording-error', (event) => {
|
|
1279
|
+
if (event) this.emit('recording-error', event);
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
this.call.on('recording-upload-completed', (event) => {
|
|
1283
|
+
if (event) this.emit('recording-upload-completed', event);
|
|
1284
|
+
});
|
|
1285
|
+
|
|
1075
1286
|
this.call.on('track-started', async (e) => {
|
|
1076
1287
|
if (!e || !e.participant) {
|
|
1077
1288
|
return;
|
|
@@ -1119,10 +1330,17 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1119
1330
|
if (e) this.handleRemoteParticipantsAudioLevel(e);
|
|
1120
1331
|
});
|
|
1121
1332
|
|
|
1333
|
+
this.call.on('local-audio-level', (e) => {
|
|
1334
|
+
if (e) this.handleLocalAudioLevel(e);
|
|
1335
|
+
});
|
|
1336
|
+
|
|
1122
1337
|
this.call.on('app-message', (e) => this.onAppMessage(e));
|
|
1123
1338
|
|
|
1124
1339
|
this.call.on('nonfatal-error', (e) => {
|
|
1125
1340
|
// https://docs.daily.co/reference/daily-js/events/meeting-events#type-audio-processor-error
|
|
1341
|
+
if (e?.type === 'local-audio-level-observer-error') {
|
|
1342
|
+
this.emit('local-audio-level-observer-error', serializeError(e));
|
|
1343
|
+
}
|
|
1126
1344
|
if (e?.type === 'audio-processor-error') {
|
|
1127
1345
|
this.call
|
|
1128
1346
|
?.updateInputSettings({
|
|
@@ -1234,12 +1452,13 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1234
1452
|
});
|
|
1235
1453
|
} catch (error) {
|
|
1236
1454
|
const recordingSetupDuration = Date.now() - recordingStartTime;
|
|
1455
|
+
const serializedError = serializeError(error);
|
|
1237
1456
|
this.emit('call-start-progress', {
|
|
1238
1457
|
stage: 'video-recording-setup',
|
|
1239
1458
|
status: 'failed',
|
|
1240
1459
|
duration: recordingSetupDuration,
|
|
1241
1460
|
timestamp: new Date().toISOString(),
|
|
1242
|
-
metadata: { error:
|
|
1461
|
+
metadata: { error: serializedError.message }
|
|
1243
1462
|
});
|
|
1244
1463
|
// Don't throw here, video recording is optional
|
|
1245
1464
|
}
|
|
@@ -1263,6 +1482,7 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1263
1482
|
|
|
1264
1483
|
try {
|
|
1265
1484
|
this.call.startRemoteParticipantsAudioLevelObserver(100);
|
|
1485
|
+
this.call.startLocalAudioLevelObserver(100);
|
|
1266
1486
|
const audioObserverDuration = Date.now() - audioObserverStartTime;
|
|
1267
1487
|
this.emit('call-start-progress', {
|
|
1268
1488
|
stage: 'audio-observer-setup',
|
|
@@ -1272,12 +1492,13 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1272
1492
|
});
|
|
1273
1493
|
} catch (error) {
|
|
1274
1494
|
const audioObserverDuration = Date.now() - audioObserverStartTime;
|
|
1495
|
+
const serializedError = serializeError(error);
|
|
1275
1496
|
this.emit('call-start-progress', {
|
|
1276
1497
|
stage: 'audio-observer-setup',
|
|
1277
1498
|
status: 'failed',
|
|
1278
1499
|
duration: audioObserverDuration,
|
|
1279
1500
|
timestamp: new Date().toISOString(),
|
|
1280
|
-
metadata: { error:
|
|
1501
|
+
metadata: { error: serializedError.message }
|
|
1281
1502
|
});
|
|
1282
1503
|
// Don't throw here, this is non-critical
|
|
1283
1504
|
}
|
|
@@ -1309,12 +1530,13 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1309
1530
|
});
|
|
1310
1531
|
} catch (error) {
|
|
1311
1532
|
const audioProcessingDuration = Date.now() - audioProcessingStartTime;
|
|
1533
|
+
const serializedError = serializeError(error);
|
|
1312
1534
|
this.emit('call-start-progress', {
|
|
1313
1535
|
stage: 'audio-processing-setup',
|
|
1314
1536
|
status: 'failed',
|
|
1315
1537
|
duration: audioProcessingDuration,
|
|
1316
1538
|
timestamp: new Date().toISOString(),
|
|
1317
|
-
metadata: { error:
|
|
1539
|
+
metadata: { error: serializedError.message }
|
|
1318
1540
|
});
|
|
1319
1541
|
// Don't throw here, this is non-critical
|
|
1320
1542
|
}
|
|
@@ -1330,12 +1552,13 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1330
1552
|
|
|
1331
1553
|
} catch (e) {
|
|
1332
1554
|
const totalDuration = Date.now() - startTime;
|
|
1555
|
+
const serializedError = serializeError(e);
|
|
1333
1556
|
|
|
1334
1557
|
this.emit('call-start-failed', {
|
|
1335
1558
|
stage: 'reconnect',
|
|
1336
1559
|
totalDuration,
|
|
1337
|
-
error:
|
|
1338
|
-
errorStack:
|
|
1560
|
+
error: serializedError.message,
|
|
1561
|
+
errorStack: serializedError.stack || 'No stack trace available',
|
|
1339
1562
|
timestamp: new Date().toISOString(),
|
|
1340
1563
|
context: {
|
|
1341
1564
|
isReconnect: true,
|
|
@@ -1349,7 +1572,7 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1349
1572
|
// Also emit the generic error event for backward compatibility
|
|
1350
1573
|
this.emit('error', {
|
|
1351
1574
|
type: 'reconnect-error',
|
|
1352
|
-
error:
|
|
1575
|
+
error: serializedError,
|
|
1353
1576
|
totalDuration,
|
|
1354
1577
|
timestamp: new Date().toISOString(),
|
|
1355
1578
|
context: {
|