@vapi-ai/web 2.1.9 → 2.2.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/dist/vapi.d.ts +6 -2
- package/dist/vapi.js +43 -10
- package/package.json +1 -1
- package/vapi.ts +58 -14
package/dist/vapi.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
2
|
import type { ChatCompletionMessageParam } from 'openai/resources';
|
|
3
|
-
import { DailyCall, DailyAdvancedConfig, DailyFactoryOptions } from '@daily-co/daily-js';
|
|
3
|
+
import { DailyCall, DailyAdvancedConfig, DailyFactoryOptions, DailyParticipant, DailyVideoSendSettings } from '@daily-co/daily-js';
|
|
4
4
|
import EventEmitter from 'events';
|
|
5
5
|
import { Call, CreateSquadDTO, CreateAssistantDTO, AssistantOverrides } from './api';
|
|
6
6
|
export interface AddMessageMessage {
|
|
@@ -18,7 +18,7 @@ export interface SayMessage {
|
|
|
18
18
|
endCallAfterSpoken?: boolean;
|
|
19
19
|
}
|
|
20
20
|
type VapiClientToServerMessage = AddMessageMessage | ControlMessages | SayMessage;
|
|
21
|
-
type VapiEventNames = 'call-end' | 'call-start' | 'volume-level' | 'speech-start' | 'speech-end' | 'message' | 'video' | 'error';
|
|
21
|
+
type VapiEventNames = 'call-end' | 'call-start' | 'volume-level' | 'speech-start' | 'speech-end' | 'message' | 'video' | 'error' | 'daily-participant-updated';
|
|
22
22
|
type VapiEventListeners = {
|
|
23
23
|
'call-end': () => void;
|
|
24
24
|
'call-start': () => void;
|
|
@@ -28,6 +28,7 @@ type VapiEventListeners = {
|
|
|
28
28
|
video: (track: MediaStreamTrack) => void;
|
|
29
29
|
message: (message: any) => void;
|
|
30
30
|
error: (error: any) => void;
|
|
31
|
+
'daily-participant-updated': (participant: DailyParticipant) => void;
|
|
31
32
|
};
|
|
32
33
|
declare class VapiEventEmitter extends EventEmitter {
|
|
33
34
|
on<E extends VapiEventNames>(event: E, listener: VapiEventListeners[E]): this;
|
|
@@ -42,6 +43,7 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
42
43
|
private speakingTimeout;
|
|
43
44
|
private dailyCallConfig;
|
|
44
45
|
private dailyCallObject;
|
|
46
|
+
private hasEmittedCallEndedStatus;
|
|
45
47
|
constructor(apiToken: string, apiBaseUrl?: string, dailyCallConfig?: Pick<DailyAdvancedConfig, 'avoidEval' | 'alwaysIncludeMicInPermissionPrompt'>, dailyCallObject?: Pick<DailyFactoryOptions, 'audioSource'>);
|
|
46
48
|
private cleanup;
|
|
47
49
|
private isMobileDevice;
|
|
@@ -57,5 +59,7 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
57
59
|
setInputDevicesAsync(options: Parameters<DailyCall['setInputDevicesAsync']>[0]): void;
|
|
58
60
|
setOutputDeviceAsync(options: Parameters<DailyCall['setOutputDeviceAsync']>[0]): void;
|
|
59
61
|
getDailyCallObject(): DailyCall | null;
|
|
62
|
+
startScreenSharing(displayMediaOptions?: DisplayMediaStreamOptions, screenVideoSendSettings?: DailyVideoSendSettings): void;
|
|
63
|
+
stopScreenSharing(): void;
|
|
60
64
|
}
|
|
61
65
|
export {};
|
package/dist/vapi.js
CHANGED
|
@@ -62,6 +62,7 @@ class Vapi extends VapiEventEmitter {
|
|
|
62
62
|
speakingTimeout = null;
|
|
63
63
|
dailyCallConfig = {};
|
|
64
64
|
dailyCallObject = {};
|
|
65
|
+
hasEmittedCallEndedStatus = false;
|
|
65
66
|
constructor(apiToken, apiBaseUrl, dailyCallConfig, dailyCallObject) {
|
|
66
67
|
super();
|
|
67
68
|
client_1.client.baseUrl = apiBaseUrl ?? 'https://api.vapi.ai';
|
|
@@ -114,16 +115,19 @@ class Vapi extends VapiEventEmitter {
|
|
|
114
115
|
this.call.iframe()?.style.setProperty('display', 'none');
|
|
115
116
|
this.call.on('left-meeting', () => {
|
|
116
117
|
this.emit('call-end');
|
|
118
|
+
if (!this.hasEmittedCallEndedStatus) {
|
|
119
|
+
this.emit('message', {
|
|
120
|
+
type: 'status-update',
|
|
121
|
+
status: 'ended',
|
|
122
|
+
'endedReason': 'customer-ended-call',
|
|
123
|
+
});
|
|
124
|
+
this.hasEmittedCallEndedStatus = true;
|
|
125
|
+
}
|
|
117
126
|
if (isVideoRecordingEnabled) {
|
|
118
127
|
this.call?.stopRecording();
|
|
119
128
|
}
|
|
120
129
|
this.cleanup();
|
|
121
130
|
});
|
|
122
|
-
this.call.on('participant-left', (e) => {
|
|
123
|
-
if (!e)
|
|
124
|
-
return;
|
|
125
|
-
destroyAudioPlayer(e.participant.session_id);
|
|
126
|
-
});
|
|
127
131
|
this.call.on('error', (error) => {
|
|
128
132
|
this.emit('error', error);
|
|
129
133
|
if (isVideoRecordingEnabled) {
|
|
@@ -134,12 +138,15 @@ class Vapi extends VapiEventEmitter {
|
|
|
134
138
|
this.emit('error', error);
|
|
135
139
|
});
|
|
136
140
|
this.call.on('track-started', async (e) => {
|
|
137
|
-
if (!e || !e.participant)
|
|
141
|
+
if (!e || !e.participant) {
|
|
138
142
|
return;
|
|
139
|
-
|
|
143
|
+
}
|
|
144
|
+
if (e.participant?.local) {
|
|
140
145
|
return;
|
|
141
|
-
|
|
146
|
+
}
|
|
147
|
+
if (e.participant?.user_name !== 'Vapi Speaker') {
|
|
142
148
|
return;
|
|
149
|
+
}
|
|
143
150
|
if (e.track.kind === 'video') {
|
|
144
151
|
this.emit('video', e.track);
|
|
145
152
|
}
|
|
@@ -153,6 +160,18 @@ class Vapi extends VapiEventEmitter {
|
|
|
153
160
|
return;
|
|
154
161
|
subscribeToTracks(e, this.call, isVideoRecordingEnabled, isVideoEnabled);
|
|
155
162
|
});
|
|
163
|
+
this.call.on('participant-updated', (e) => {
|
|
164
|
+
if (!e) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
this.emit('daily-participant-updated', e.participant);
|
|
168
|
+
});
|
|
169
|
+
this.call.on('participant-left', (e) => {
|
|
170
|
+
if (!e) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
destroyAudioPlayer(e.participant.session_id);
|
|
174
|
+
});
|
|
156
175
|
// Allow mobile devices to finish processing the microphone permissions
|
|
157
176
|
// request before joining the call and playing the assistant's audio
|
|
158
177
|
if (this.isMobileDevice()) {
|
|
@@ -220,8 +239,9 @@ class Vapi extends VapiEventEmitter {
|
|
|
220
239
|
}
|
|
221
240
|
}
|
|
222
241
|
onAppMessage(e) {
|
|
223
|
-
if (!e)
|
|
242
|
+
if (!e) {
|
|
224
243
|
return;
|
|
244
|
+
}
|
|
225
245
|
try {
|
|
226
246
|
if (e.data === 'listening') {
|
|
227
247
|
return this.emit('call-start');
|
|
@@ -230,6 +250,9 @@ class Vapi extends VapiEventEmitter {
|
|
|
230
250
|
try {
|
|
231
251
|
const parsedMessage = JSON.parse(e.data);
|
|
232
252
|
this.emit('message', parsedMessage);
|
|
253
|
+
if (parsedMessage.type === 'status-update' && parsedMessage.status === 'ended') {
|
|
254
|
+
this.hasEmittedCallEndedStatus = true;
|
|
255
|
+
}
|
|
233
256
|
}
|
|
234
257
|
catch (parseError) {
|
|
235
258
|
console.log('Error parsing message data: ', parseError);
|
|
@@ -244,8 +267,9 @@ class Vapi extends VapiEventEmitter {
|
|
|
244
267
|
const speechLevel = Object.values(e.participantsAudioLevel).reduce((a, b) => a + b, 0);
|
|
245
268
|
this.emit('volume-level', Math.min(1, speechLevel / 0.15));
|
|
246
269
|
const isSpeaking = speechLevel > 0.01;
|
|
247
|
-
if (!isSpeaking)
|
|
270
|
+
if (!isSpeaking) {
|
|
248
271
|
return;
|
|
272
|
+
}
|
|
249
273
|
if (this.speakingTimeout) {
|
|
250
274
|
clearTimeout(this.speakingTimeout);
|
|
251
275
|
this.speakingTimeout = null;
|
|
@@ -294,5 +318,14 @@ class Vapi extends VapiEventEmitter {
|
|
|
294
318
|
getDailyCallObject() {
|
|
295
319
|
return this.call;
|
|
296
320
|
}
|
|
321
|
+
startScreenSharing(displayMediaOptions, screenVideoSendSettings) {
|
|
322
|
+
this.call?.startScreenShare({
|
|
323
|
+
displayMediaOptions,
|
|
324
|
+
screenVideoSendSettings,
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
stopScreenSharing() {
|
|
328
|
+
this.call?.stopScreenShare();
|
|
329
|
+
}
|
|
297
330
|
}
|
|
298
331
|
exports.default = Vapi;
|
package/package.json
CHANGED
package/vapi.ts
CHANGED
|
@@ -7,6 +7,8 @@ import DailyIframe, {
|
|
|
7
7
|
DailyEventObjectAppMessage,
|
|
8
8
|
DailyEventObjectParticipant,
|
|
9
9
|
DailyEventObjectRemoteParticipantsAudioLevel,
|
|
10
|
+
DailyParticipant,
|
|
11
|
+
DailyVideoSendSettings,
|
|
10
12
|
} from '@daily-co/daily-js';
|
|
11
13
|
import EventEmitter from 'events';
|
|
12
14
|
|
|
@@ -48,7 +50,8 @@ type VapiEventNames =
|
|
|
48
50
|
| 'speech-end'
|
|
49
51
|
| 'message'
|
|
50
52
|
| 'video'
|
|
51
|
-
| 'error'
|
|
53
|
+
| 'error'
|
|
54
|
+
| 'daily-participant-updated';
|
|
52
55
|
|
|
53
56
|
type VapiEventListeners = {
|
|
54
57
|
'call-end': () => void;
|
|
@@ -59,6 +62,7 @@ type VapiEventListeners = {
|
|
|
59
62
|
video: (track: MediaStreamTrack) => void;
|
|
60
63
|
message: (message: any) => void;
|
|
61
64
|
error: (error: any) => void;
|
|
65
|
+
'daily-participant-updated': (participant: DailyParticipant) => void;
|
|
62
66
|
};
|
|
63
67
|
|
|
64
68
|
async function startAudioPlayer(
|
|
@@ -148,6 +152,8 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
148
152
|
private dailyCallConfig: DailyAdvancedConfig = {};
|
|
149
153
|
private dailyCallObject: DailyFactoryOptions = {};
|
|
150
154
|
|
|
155
|
+
private hasEmittedCallEndedStatus: boolean = false;
|
|
156
|
+
|
|
151
157
|
constructor(
|
|
152
158
|
apiToken: string,
|
|
153
159
|
apiBaseUrl?: string,
|
|
@@ -228,17 +234,20 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
228
234
|
|
|
229
235
|
this.call.on('left-meeting', () => {
|
|
230
236
|
this.emit('call-end');
|
|
237
|
+
if (!this.hasEmittedCallEndedStatus) {
|
|
238
|
+
this.emit('message', {
|
|
239
|
+
type: 'status-update',
|
|
240
|
+
status: 'ended',
|
|
241
|
+
'endedReason': 'customer-ended-call',
|
|
242
|
+
});
|
|
243
|
+
this.hasEmittedCallEndedStatus = true;
|
|
244
|
+
}
|
|
231
245
|
if (isVideoRecordingEnabled) {
|
|
232
246
|
this.call?.stopRecording();
|
|
233
247
|
}
|
|
234
248
|
this.cleanup();
|
|
235
249
|
});
|
|
236
250
|
|
|
237
|
-
this.call.on('participant-left', (e) => {
|
|
238
|
-
if (!e) return;
|
|
239
|
-
destroyAudioPlayer(e.participant.session_id);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
251
|
this.call.on('error', (error: any) => {
|
|
243
252
|
this.emit('error', error);
|
|
244
253
|
if (isVideoRecordingEnabled) {
|
|
@@ -251,18 +260,21 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
251
260
|
});
|
|
252
261
|
|
|
253
262
|
this.call.on('track-started', async (e) => {
|
|
254
|
-
if (!e || !e.participant)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
263
|
+
if (!e || !e.participant) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
if (e.participant?.local) {
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
if (e.participant?.user_name !== 'Vapi Speaker') {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
258
272
|
if (e.track.kind === 'video') {
|
|
259
273
|
this.emit('video', e.track);
|
|
260
274
|
}
|
|
261
|
-
|
|
262
275
|
if (e.track.kind === 'audio') {
|
|
263
276
|
await buildAudioPlayer(e.track, e.participant.session_id);
|
|
264
277
|
}
|
|
265
|
-
|
|
266
278
|
this.call?.sendAppMessage('playable');
|
|
267
279
|
});
|
|
268
280
|
|
|
@@ -276,6 +288,20 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
276
288
|
);
|
|
277
289
|
});
|
|
278
290
|
|
|
291
|
+
this.call.on('participant-updated', (e) => {
|
|
292
|
+
if (!e) {
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
this.emit('daily-participant-updated', e.participant);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
this.call.on('participant-left', (e) => {
|
|
299
|
+
if (!e) {
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
destroyAudioPlayer(e.participant.session_id);
|
|
303
|
+
});
|
|
304
|
+
|
|
279
305
|
// Allow mobile devices to finish processing the microphone permissions
|
|
280
306
|
// request before joining the call and playing the assistant's audio
|
|
281
307
|
if (this.isMobileDevice()) {
|
|
@@ -353,7 +379,9 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
353
379
|
}
|
|
354
380
|
|
|
355
381
|
private onAppMessage(e?: DailyEventObjectAppMessage) {
|
|
356
|
-
if (!e)
|
|
382
|
+
if (!e) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
357
385
|
try {
|
|
358
386
|
if (e.data === 'listening') {
|
|
359
387
|
return this.emit('call-start');
|
|
@@ -361,6 +389,9 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
361
389
|
try {
|
|
362
390
|
const parsedMessage = JSON.parse(e.data);
|
|
363
391
|
this.emit('message', parsedMessage);
|
|
392
|
+
if (parsedMessage.type === 'status-update' && parsedMessage.status === 'ended') {
|
|
393
|
+
this.hasEmittedCallEndedStatus = true;
|
|
394
|
+
}
|
|
364
395
|
} catch (parseError) {
|
|
365
396
|
console.log('Error parsing message data: ', parseError);
|
|
366
397
|
}
|
|
@@ -382,7 +413,9 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
382
413
|
|
|
383
414
|
const isSpeaking = speechLevel > 0.01;
|
|
384
415
|
|
|
385
|
-
if (!isSpeaking)
|
|
416
|
+
if (!isSpeaking) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
386
419
|
|
|
387
420
|
if (this.speakingTimeout) {
|
|
388
421
|
clearTimeout(this.speakingTimeout);
|
|
@@ -444,4 +477,15 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
444
477
|
public getDailyCallObject(): DailyCall | null {
|
|
445
478
|
return this.call;
|
|
446
479
|
}
|
|
480
|
+
|
|
481
|
+
public startScreenSharing(displayMediaOptions?: DisplayMediaStreamOptions, screenVideoSendSettings?: DailyVideoSendSettings) {
|
|
482
|
+
this.call?.startScreenShare({
|
|
483
|
+
displayMediaOptions,
|
|
484
|
+
screenVideoSendSettings,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
public stopScreenSharing() {
|
|
489
|
+
this.call?.stopScreenShare();
|
|
490
|
+
}
|
|
447
491
|
}
|