@vapi-ai/web 3.0.1 → 3.1.0
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 +11 -1
- package/api.ts +6195 -5230
- package/dist/api.d.ts +3430 -2788
- package/dist/api.js +94 -5
- package/dist/vapi.d.ts +41 -3
- package/dist/vapi.js +100 -81
- package/package.json +1 -1
- package/vapi.ts +164 -111
package/vapi.ts
CHANGED
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
CreateSquadDTO,
|
|
24
24
|
CreateAssistantDTO,
|
|
25
25
|
AssistantOverrides,
|
|
26
|
+
CreateWebCallDTO,
|
|
26
27
|
CreateWorkflowDTO,
|
|
27
28
|
WorkflowOverrides,
|
|
28
29
|
} from './api';
|
|
@@ -205,6 +206,21 @@ type VapiEventListeners = {
|
|
|
205
206
|
'recording-upload-completed': (event: DailyEventObjectNoPayload) => void;
|
|
206
207
|
};
|
|
207
208
|
|
|
209
|
+
type VideoRecordingStartMode = NonNullable<
|
|
210
|
+
CreateWebCallDTO['videoRecordingStartMode']
|
|
211
|
+
>;
|
|
212
|
+
|
|
213
|
+
// The app message Vapi sends when it couldn't start a server-mode recording.
|
|
214
|
+
const VIDEO_RECORDING_START_FAILED_MESSAGE_TYPE = 'video-recording.start-failed';
|
|
215
|
+
|
|
216
|
+
type VideoRecordingStartFailedMessage = {
|
|
217
|
+
type: typeof VIDEO_RECORDING_START_FAILED_MESSAGE_TYPE;
|
|
218
|
+
/** `not-hosting` when Daily saw nobody in the room; `failed` for any other failure. */
|
|
219
|
+
outcome: 'not-hosting' | 'failed';
|
|
220
|
+
/** A short description of the failure. */
|
|
221
|
+
error: string;
|
|
222
|
+
};
|
|
223
|
+
|
|
208
224
|
type StartCallOptions = {
|
|
209
225
|
/** Acquire the microphone before creating a backend call. Defaults to true. */
|
|
210
226
|
microphonePreflightEnabled?: boolean;
|
|
@@ -217,6 +233,25 @@ type StartCallOptions = {
|
|
|
217
233
|
* @example true
|
|
218
234
|
*/
|
|
219
235
|
roomDeleteOnUserLeaveEnabled?: boolean;
|
|
236
|
+
/**
|
|
237
|
+
* This determines who starts the video recording. Only applies when video
|
|
238
|
+
* recording is enabled (`artifactPlan.videoRecordingEnabled`).
|
|
239
|
+
*
|
|
240
|
+
* - `client`: the web client starts the recording when it joins.
|
|
241
|
+
* - `server`: Vapi starts the recording once the user has joined, so a weak
|
|
242
|
+
* client connection can't stop it from starting. If Vapi can't set up the
|
|
243
|
+
* server-side start, or the start itself fails, the SDK emits
|
|
244
|
+
* `recording-error`.
|
|
245
|
+
*
|
|
246
|
+
* In `server` mode the SDK never calls `startRecording()` itself. If the
|
|
247
|
+
* server-side start couldn't be set up, calling it yourself may still start
|
|
248
|
+
* the recording.
|
|
249
|
+
*
|
|
250
|
+
* Requires a Vapi API that supports this field; older servers reject the
|
|
251
|
+
* request. The server defaults to `client` when it is not set.
|
|
252
|
+
* @example "server"
|
|
253
|
+
*/
|
|
254
|
+
videoRecordingStartMode?: VideoRecordingStartMode;
|
|
220
255
|
}
|
|
221
256
|
|
|
222
257
|
type WebCall = {
|
|
@@ -244,12 +279,22 @@ type WebCall = {
|
|
|
244
279
|
assistant?: { voice?: { provider?: string } };
|
|
245
280
|
/**
|
|
246
281
|
* The Vapi WebCall transport. When video recording is enabled, the server
|
|
247
|
-
* sets `callToken` to
|
|
248
|
-
* recording on join
|
|
282
|
+
* sets `callToken` to the Daily meeting token to join with. In `client` mode
|
|
283
|
+
* the token auto-starts the cloud recording on join; in `server` mode it
|
|
284
|
+
* starts nothing and Vapi starts the recording. If there is no token, the
|
|
285
|
+
* SDK emits `recording-error` and starts nothing, though `startRecording()`
|
|
286
|
+
* may still start the recording from the client.
|
|
287
|
+
*
|
|
288
|
+
* `videoRecordingStartMode` is `server` when Vapi starts the recording, and
|
|
289
|
+
* is absent when video recording is off or the server-side start couldn't
|
|
290
|
+
* be set up.
|
|
249
291
|
*
|
|
250
292
|
* call.transport
|
|
251
293
|
*/
|
|
252
|
-
transport?: {
|
|
294
|
+
transport?: {
|
|
295
|
+
callToken?: string;
|
|
296
|
+
videoRecordingStartMode?: VideoRecordingStartMode;
|
|
297
|
+
};
|
|
253
298
|
}
|
|
254
299
|
|
|
255
300
|
async function startAudioPlayer(
|
|
@@ -297,14 +342,13 @@ function subscribeToTracks(
|
|
|
297
342
|
e: DailyEventObjectParticipant,
|
|
298
343
|
call: DailyCall,
|
|
299
344
|
isVideoRecordingEnabled?: boolean,
|
|
300
|
-
isVideoEnabled?: boolean,
|
|
301
345
|
) {
|
|
302
346
|
if (e.participant.local) return;
|
|
303
347
|
|
|
304
348
|
call.updateParticipant(e.participant.session_id, {
|
|
305
349
|
setSubscribedTracks: {
|
|
306
350
|
audio: true,
|
|
307
|
-
video: isVideoRecordingEnabled
|
|
351
|
+
video: isVideoRecordingEnabled,
|
|
308
352
|
},
|
|
309
353
|
});
|
|
310
354
|
}
|
|
@@ -555,18 +599,23 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
555
599
|
|
|
556
600
|
const webCallStartTime = Date.now();
|
|
557
601
|
|
|
602
|
+
// An unset option stays undefined, so JSON.stringify drops the key and
|
|
603
|
+
// older servers never see it.
|
|
604
|
+
const createWebCallBody: CreateWebCallDTO = {
|
|
605
|
+
assistant: typeof assistant === 'string' ? undefined : assistant,
|
|
606
|
+
assistantId: typeof assistant === 'string' ? assistant : undefined,
|
|
607
|
+
assistantOverrides,
|
|
608
|
+
squad: typeof squad === 'string' ? undefined : squad,
|
|
609
|
+
squadId: typeof squad === 'string' ? squad : undefined,
|
|
610
|
+
workflow: typeof workflow === 'string' ? undefined : workflow,
|
|
611
|
+
workflowId: typeof workflow === 'string' ? workflow : undefined,
|
|
612
|
+
workflowOverrides,
|
|
613
|
+
roomDeleteOnUserLeaveEnabled: options?.roomDeleteOnUserLeaveEnabled,
|
|
614
|
+
videoRecordingStartMode: options?.videoRecordingStartMode,
|
|
615
|
+
};
|
|
616
|
+
|
|
558
617
|
const webCall = (
|
|
559
|
-
await client.call.callControllerCreateWebCall(
|
|
560
|
-
assistant: typeof assistant === 'string' ? undefined : assistant,
|
|
561
|
-
assistantId: typeof assistant === 'string' ? assistant : undefined,
|
|
562
|
-
assistantOverrides,
|
|
563
|
-
squad: typeof squad === 'string' ? undefined : squad,
|
|
564
|
-
squadId: typeof squad === 'string' ? squad : undefined,
|
|
565
|
-
workflow: typeof workflow === 'string' ? undefined : workflow,
|
|
566
|
-
workflowId: typeof workflow === 'string' ? workflow : undefined,
|
|
567
|
-
workflowOverrides,
|
|
568
|
-
roomDeleteOnUserLeaveEnabled: options?.roomDeleteOnUserLeaveEnabled,
|
|
569
|
-
})
|
|
618
|
+
await client.call.callControllerCreateWebCall(createWebCallBody)
|
|
570
619
|
).data;
|
|
571
620
|
|
|
572
621
|
const webCallDuration = Date.now() - webCallStartTime;
|
|
@@ -585,15 +634,17 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
585
634
|
const isVideoRecordingEnabled =
|
|
586
635
|
webCall?.artifactPlan?.videoRecordingEnabled ?? false;
|
|
587
636
|
|
|
588
|
-
const isVideoEnabled = webCall?.assistant?.voice?.provider === 'tavus';
|
|
589
|
-
|
|
590
637
|
// When video recording is enabled, the server sends a Daily meeting
|
|
591
|
-
// token
|
|
592
|
-
//
|
|
593
|
-
// servers
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
638
|
+
// token. In client mode it auto-starts the cloud recording on join. In
|
|
639
|
+
// server mode it starts nothing: Vapi starts the recording from its
|
|
640
|
+
// servers once the user has joined, which a weak client network can't
|
|
641
|
+
// stop. Without a token the SDK emits recording-error and starts
|
|
642
|
+
// nothing.
|
|
643
|
+
const transport = webCall?.transport as WebCall['transport'];
|
|
644
|
+
const callToken = transport?.callToken;
|
|
645
|
+
const isRecordingStartedByServer =
|
|
646
|
+
transport?.videoRecordingStartMode === 'server';
|
|
647
|
+
const recordingTokenMissing = isVideoRecordingEnabled && !callToken;
|
|
597
648
|
|
|
598
649
|
// Stage 2: Create Daily call object
|
|
599
650
|
this.emit('call-start-progress', {
|
|
@@ -604,7 +655,6 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
604
655
|
audioSource: this.dailyCallObject.audioSource ?? true,
|
|
605
656
|
videoSource: this.dailyCallObject.videoSource ?? isVideoRecordingEnabled,
|
|
606
657
|
isVideoRecordingEnabled,
|
|
607
|
-
isVideoEnabled
|
|
608
658
|
}
|
|
609
659
|
});
|
|
610
660
|
|
|
@@ -714,12 +764,7 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
714
764
|
|
|
715
765
|
this.call.on('participant-joined', (e) => {
|
|
716
766
|
if (!e || !this.call) return;
|
|
717
|
-
subscribeToTracks(
|
|
718
|
-
e,
|
|
719
|
-
this.call,
|
|
720
|
-
isVideoRecordingEnabled,
|
|
721
|
-
isVideoEnabled,
|
|
722
|
-
);
|
|
767
|
+
subscribeToTracks(e, this.call, isVideoRecordingEnabled);
|
|
723
768
|
});
|
|
724
769
|
|
|
725
770
|
this.call.on('participant-updated', (e) => {
|
|
@@ -736,6 +781,11 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
736
781
|
this.detachAudioPlayer(e.participant.session_id);
|
|
737
782
|
});
|
|
738
783
|
|
|
784
|
+
// Registered before join(), as in reconnect(): Vapi starts a server-mode
|
|
785
|
+
// recording when it sees the user join, so its start-failed message can
|
|
786
|
+
// arrive while join() is still in flight.
|
|
787
|
+
this.call.on('app-message', (e) => this.onAppMessage(e));
|
|
788
|
+
|
|
739
789
|
// Registered before join(): a token-auto-started recording can begin
|
|
740
790
|
// while join() is still in flight, so a listener added after join
|
|
741
791
|
// could miss the event.
|
|
@@ -836,52 +886,28 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
836
886
|
|
|
837
887
|
// Stage 5: Video recording setup (if enabled)
|
|
838
888
|
if (isVideoRecordingEnabled && callToken) {
|
|
839
|
-
// The meeting token auto-started the recording at join,
|
|
840
|
-
// nothing to request from the
|
|
889
|
+
// The meeting token auto-started the recording at join, or Vapi
|
|
890
|
+
// starts it from its servers, so there is nothing to request from the
|
|
891
|
+
// client.
|
|
841
892
|
this.emit('call-start-progress', {
|
|
842
893
|
stage: 'video-recording-setup',
|
|
843
894
|
status: 'completed',
|
|
844
895
|
timestamp: new Date().toISOString(),
|
|
845
|
-
metadata: {
|
|
896
|
+
metadata: {
|
|
897
|
+
action: isRecordingStartedByServer
|
|
898
|
+
? 'started-by-server'
|
|
899
|
+
: 'auto-started-via-meeting-token'
|
|
900
|
+
}
|
|
846
901
|
});
|
|
847
902
|
} else if (isVideoRecordingEnabled) {
|
|
903
|
+
// No token: nothing starts the recording. The recording-error is
|
|
904
|
+
// reported once start() has finished.
|
|
848
905
|
this.emit('call-start-progress', {
|
|
849
906
|
stage: 'video-recording-setup',
|
|
850
|
-
status: '
|
|
851
|
-
timestamp: new Date().toISOString()
|
|
907
|
+
status: 'failed',
|
|
908
|
+
timestamp: new Date().toISOString(),
|
|
909
|
+
metadata: { action: 'no-meeting-token' }
|
|
852
910
|
});
|
|
853
|
-
|
|
854
|
-
const recordingStartTime = Date.now();
|
|
855
|
-
|
|
856
|
-
try {
|
|
857
|
-
recordingRequestedTime = new Date().getTime();
|
|
858
|
-
this.startRecording();
|
|
859
|
-
|
|
860
|
-
const recordingSetupDuration = Date.now() - recordingStartTime;
|
|
861
|
-
this.emit('call-start-progress', {
|
|
862
|
-
stage: 'video-recording-setup',
|
|
863
|
-
status: 'completed',
|
|
864
|
-
duration: recordingSetupDuration,
|
|
865
|
-
timestamp: new Date().toISOString()
|
|
866
|
-
});
|
|
867
|
-
} catch (error) {
|
|
868
|
-
const recordingSetupDuration = Date.now() - recordingStartTime;
|
|
869
|
-
const serializedError = serializeError(error);
|
|
870
|
-
this.emit('call-start-progress', {
|
|
871
|
-
stage: 'video-recording-setup',
|
|
872
|
-
status: 'failed',
|
|
873
|
-
duration: recordingSetupDuration,
|
|
874
|
-
timestamp: new Date().toISOString(),
|
|
875
|
-
metadata: { error: serializedError.message }
|
|
876
|
-
});
|
|
877
|
-
this.emit('error', {
|
|
878
|
-
type: 'video-recording-setup-error',
|
|
879
|
-
stage: 'video-recording-setup',
|
|
880
|
-
error: serializedError,
|
|
881
|
-
timestamp: new Date().toISOString()
|
|
882
|
-
});
|
|
883
|
-
// Don't throw here, video recording is optional
|
|
884
|
-
}
|
|
885
911
|
} else {
|
|
886
912
|
this.emit('call-start-progress', {
|
|
887
913
|
stage: 'video-recording-setup',
|
|
@@ -936,8 +962,6 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
936
962
|
if (e) this.handleLocalAudioLevel(e);
|
|
937
963
|
});
|
|
938
964
|
|
|
939
|
-
this.call.on('app-message', (e) => this.onAppMessage(e));
|
|
940
|
-
|
|
941
965
|
this.call.on('nonfatal-error', (e) => {
|
|
942
966
|
// https://docs.daily.co/reference/daily-js/events/meeting-events#type-audio-processor-error
|
|
943
967
|
if (e?.type === 'local-audio-level-observer-error') {
|
|
@@ -1027,6 +1051,10 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1027
1051
|
timestamp: new Date().toISOString()
|
|
1028
1052
|
});
|
|
1029
1053
|
|
|
1054
|
+
if (recordingTokenMissing) {
|
|
1055
|
+
this.recordingTokenMissingReportSchedule();
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1030
1058
|
return webCall;
|
|
1031
1059
|
} catch (e) {
|
|
1032
1060
|
const totalDuration = Date.now() - startTime;
|
|
@@ -1078,6 +1106,12 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1078
1106
|
} else {
|
|
1079
1107
|
try {
|
|
1080
1108
|
const parsedMessage = JSON.parse(e.data);
|
|
1109
|
+
// Not a Vapi client message: re-emitted as recording-error, and kept
|
|
1110
|
+
// out of 'message' because its consumers switch on `type`.
|
|
1111
|
+
if (parsedMessage?.type === VIDEO_RECORDING_START_FAILED_MESSAGE_TYPE) {
|
|
1112
|
+
this.videoRecordingStartFailedReport(parsedMessage);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1081
1115
|
this.emit('message', parsedMessage);
|
|
1082
1116
|
if (parsedMessage && 'type' in parsedMessage && 'status' in parsedMessage && parsedMessage.type === 'status-update' && parsedMessage.status === 'ended') {
|
|
1083
1117
|
this.hasEmittedCallEndedStatus = true;
|
|
@@ -1276,6 +1310,41 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1276
1310
|
}
|
|
1277
1311
|
}
|
|
1278
1312
|
|
|
1313
|
+
/**
|
|
1314
|
+
* Reported in a later task, like Daily's own recording-error: start() or
|
|
1315
|
+
* reconnect() has resolved and the caller's await continuation has run, so a
|
|
1316
|
+
* listener registered right after it still hears this, and one that calls
|
|
1317
|
+
* stop() can't interrupt call setup. Skipped if that call was stopped or
|
|
1318
|
+
* replaced.
|
|
1319
|
+
*/
|
|
1320
|
+
private recordingTokenMissingReportSchedule() {
|
|
1321
|
+
const call = this.call;
|
|
1322
|
+
if (!call) return;
|
|
1323
|
+
setTimeout(() => {
|
|
1324
|
+
if (!this.started || this.call !== call) return;
|
|
1325
|
+
this.emitToConsumer('recording-error', {
|
|
1326
|
+
action: 'recording-error',
|
|
1327
|
+
callClientId: call.callClientId,
|
|
1328
|
+
errorMsg:
|
|
1329
|
+
'Vapi returned no recording token, so the recording was not started. Call startRecording() to try starting it from the client.',
|
|
1330
|
+
});
|
|
1331
|
+
}, 0);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
/** The fields are read defensively: the app message is untrusted JSON. */
|
|
1335
|
+
private videoRecordingStartFailedReport(
|
|
1336
|
+
message: Partial<VideoRecordingStartFailedMessage>,
|
|
1337
|
+
) {
|
|
1338
|
+
const outcome =
|
|
1339
|
+
typeof message.outcome === 'string' ? message.outcome : 'unknown';
|
|
1340
|
+
const error = typeof message.error === 'string' ? message.error : 'unknown';
|
|
1341
|
+
this.emitToConsumer('recording-error', {
|
|
1342
|
+
action: 'recording-error',
|
|
1343
|
+
callClientId: this.call?.callClientId ?? '',
|
|
1344
|
+
errorMsg: `Vapi couldn't start the recording (${outcome}): ${error}`,
|
|
1345
|
+
});
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1279
1348
|
/** Drops the reference before removing, so nothing hands out a detached element. */
|
|
1280
1349
|
private detachAudioPlayer(participantId: string) {
|
|
1281
1350
|
if (this.audioPlayer?.dataset.participantId === participantId) {
|
|
@@ -1543,12 +1612,16 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1543
1612
|
}
|
|
1544
1613
|
|
|
1545
1614
|
const isVideoRecordingEnabled = webCall?.artifactPlan?.videoRecordingEnabled ?? false;
|
|
1546
|
-
const isVideoEnabled = webCall?.assistant?.voice?.provider === 'tavus';
|
|
1547
1615
|
|
|
1548
|
-
// Same
|
|
1616
|
+
// Same token as in start(). In client mode, rejoining with it while the
|
|
1549
1617
|
// recording is still running is safe; if the recording stopped when the
|
|
1550
|
-
// user left, the token starts a new one.
|
|
1618
|
+
// user left, the token starts a new one. In server mode it starts
|
|
1619
|
+
// nothing. Without a token the SDK emits recording-error and starts
|
|
1620
|
+
// nothing.
|
|
1551
1621
|
const callToken = webCall?.transport?.callToken;
|
|
1622
|
+
const isRecordingStartedByServer =
|
|
1623
|
+
webCall?.transport?.videoRecordingStartMode === 'server';
|
|
1624
|
+
const recordingTokenMissing = isVideoRecordingEnabled && !callToken;
|
|
1552
1625
|
|
|
1553
1626
|
// Stage 1: Create Daily call object
|
|
1554
1627
|
this.emit('call-start-progress', {
|
|
@@ -1559,7 +1632,6 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1559
1632
|
audioSource: this.dailyCallObject.audioSource ?? true,
|
|
1560
1633
|
videoSource: this.dailyCallObject.videoSource ?? isVideoRecordingEnabled,
|
|
1561
1634
|
isVideoRecordingEnabled,
|
|
1562
|
-
isVideoEnabled
|
|
1563
1635
|
}
|
|
1564
1636
|
});
|
|
1565
1637
|
|
|
@@ -1650,12 +1722,7 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1650
1722
|
|
|
1651
1723
|
this.call.on('participant-joined', (e) => {
|
|
1652
1724
|
if (!e || !this.call) return;
|
|
1653
|
-
subscribeToTracks(
|
|
1654
|
-
e,
|
|
1655
|
-
this.call,
|
|
1656
|
-
isVideoRecordingEnabled,
|
|
1657
|
-
isVideoEnabled,
|
|
1658
|
-
);
|
|
1725
|
+
subscribeToTracks(e, this.call, isVideoRecordingEnabled);
|
|
1659
1726
|
});
|
|
1660
1727
|
|
|
1661
1728
|
this.call.on('participant-updated', (e) => {
|
|
@@ -1783,46 +1850,28 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1783
1850
|
|
|
1784
1851
|
// Stage 4: Video recording setup (if enabled)
|
|
1785
1852
|
if (isVideoRecordingEnabled && callToken) {
|
|
1786
|
-
// The meeting token auto-started the recording at join,
|
|
1787
|
-
// nothing to request from the
|
|
1853
|
+
// The meeting token auto-started the recording at join, or Vapi
|
|
1854
|
+
// starts it from its servers, so there is nothing to request from the
|
|
1855
|
+
// client.
|
|
1788
1856
|
this.emit('call-start-progress', {
|
|
1789
1857
|
stage: 'video-recording-setup',
|
|
1790
1858
|
status: 'completed',
|
|
1791
1859
|
timestamp: new Date().toISOString(),
|
|
1792
|
-
metadata: {
|
|
1860
|
+
metadata: {
|
|
1861
|
+
action: isRecordingStartedByServer
|
|
1862
|
+
? 'started-by-server'
|
|
1863
|
+
: 'auto-started-via-meeting-token'
|
|
1864
|
+
}
|
|
1793
1865
|
});
|
|
1794
1866
|
} else if (isVideoRecordingEnabled) {
|
|
1867
|
+
// No token: nothing starts the recording. The recording-error is
|
|
1868
|
+
// reported once reconnect() has finished.
|
|
1795
1869
|
this.emit('call-start-progress', {
|
|
1796
1870
|
stage: 'video-recording-setup',
|
|
1797
|
-
status: '
|
|
1798
|
-
timestamp: new Date().toISOString()
|
|
1871
|
+
status: 'failed',
|
|
1872
|
+
timestamp: new Date().toISOString(),
|
|
1873
|
+
metadata: { action: 'no-meeting-token' }
|
|
1799
1874
|
});
|
|
1800
|
-
|
|
1801
|
-
const recordingStartTime = Date.now();
|
|
1802
|
-
|
|
1803
|
-
try {
|
|
1804
|
-
recordingRequestedTime = new Date().getTime();
|
|
1805
|
-
this.startRecording();
|
|
1806
|
-
|
|
1807
|
-
const recordingSetupDuration = Date.now() - recordingStartTime;
|
|
1808
|
-
this.emit('call-start-progress', {
|
|
1809
|
-
stage: 'video-recording-setup',
|
|
1810
|
-
status: 'completed',
|
|
1811
|
-
duration: recordingSetupDuration,
|
|
1812
|
-
timestamp: new Date().toISOString()
|
|
1813
|
-
});
|
|
1814
|
-
} catch (error) {
|
|
1815
|
-
const recordingSetupDuration = Date.now() - recordingStartTime;
|
|
1816
|
-
const serializedError = serializeError(error);
|
|
1817
|
-
this.emit('call-start-progress', {
|
|
1818
|
-
stage: 'video-recording-setup',
|
|
1819
|
-
status: 'failed',
|
|
1820
|
-
duration: recordingSetupDuration,
|
|
1821
|
-
timestamp: new Date().toISOString(),
|
|
1822
|
-
metadata: { error: serializedError.message }
|
|
1823
|
-
});
|
|
1824
|
-
// Don't throw here, video recording is optional
|
|
1825
|
-
}
|
|
1826
1875
|
} else {
|
|
1827
1876
|
this.emit('call-start-progress', {
|
|
1828
1877
|
stage: 'video-recording-setup',
|
|
@@ -1925,6 +1974,10 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1925
1974
|
|
|
1926
1975
|
this.emit('call-start');
|
|
1927
1976
|
|
|
1977
|
+
if (recordingTokenMissing) {
|
|
1978
|
+
this.recordingTokenMissingReportSchedule();
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1928
1981
|
} catch (e) {
|
|
1929
1982
|
const totalDuration = Date.now() - startTime;
|
|
1930
1983
|
const serializedError = serializeError(e);
|