@vapi-ai/web 2.1.4 → 2.1.5
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 +4 -3
- package/dist/vapi.js +26 -31
- package/package.json +1 -1
- package/vapi.ts +107 -52
package/dist/vapi.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import { Call, CreateAssistantDTO, CreateSquadDTO, AssistantOverrides } from './api';
|
|
3
|
-
import { DailyAdvancedConfig, DailyCall, DailyFactoryOptions } from '@daily-co/daily-js';
|
|
4
2
|
import type { ChatCompletionMessageParam } from 'openai/resources';
|
|
3
|
+
import { DailyCall, DailyAdvancedConfig, DailyFactoryOptions } from '@daily-co/daily-js';
|
|
5
4
|
import EventEmitter from 'events';
|
|
5
|
+
import { Call, CreateSquadDTO, CreateAssistantDTO, AssistantOverrides } from './api';
|
|
6
6
|
export interface AddMessageMessage {
|
|
7
7
|
type: 'add-message';
|
|
8
8
|
message: ChatCompletionMessageParam;
|
|
@@ -18,13 +18,14 @@ 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' | 'error';
|
|
21
|
+
type VapiEventNames = 'call-end' | 'call-start' | 'volume-level' | 'speech-start' | 'speech-end' | 'message' | 'video' | 'error';
|
|
22
22
|
type VapiEventListeners = {
|
|
23
23
|
'call-end': () => void;
|
|
24
24
|
'call-start': () => void;
|
|
25
25
|
'volume-level': (volume: number) => void;
|
|
26
26
|
'speech-start': () => void;
|
|
27
27
|
'speech-end': () => void;
|
|
28
|
+
video: (track: MediaStreamTrack) => void;
|
|
28
29
|
message: (message: any) => void;
|
|
29
30
|
error: (error: any) => void;
|
|
30
31
|
};
|
package/dist/vapi.js
CHANGED
|
@@ -6,11 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const daily_js_1 = __importDefault(require("@daily-co/daily-js"));
|
|
7
7
|
const events_1 = __importDefault(require("events"));
|
|
8
8
|
const client_1 = require("./client");
|
|
9
|
-
function
|
|
10
|
-
const player = document.querySelector(`audio[data-participant-id="${participantId}"]`);
|
|
11
|
-
player?.remove();
|
|
12
|
-
}
|
|
13
|
-
async function startPlayer(player, track) {
|
|
9
|
+
async function startAudioPlayer(player, track) {
|
|
14
10
|
player.muted = false;
|
|
15
11
|
player.autoplay = true;
|
|
16
12
|
if (track != null) {
|
|
@@ -22,16 +18,20 @@ async function buildAudioPlayer(track, participantId) {
|
|
|
22
18
|
const player = document.createElement('audio');
|
|
23
19
|
player.dataset.participantId = participantId;
|
|
24
20
|
document.body.appendChild(player);
|
|
25
|
-
await
|
|
21
|
+
await startAudioPlayer(player, track);
|
|
26
22
|
return player;
|
|
27
23
|
}
|
|
28
|
-
function
|
|
24
|
+
function destroyAudioPlayer(participantId) {
|
|
25
|
+
const player = document.querySelector(`audio[data-participant-id="${participantId}"]`);
|
|
26
|
+
player?.remove();
|
|
27
|
+
}
|
|
28
|
+
function subscribeToTracks(e, call, isVideoRecordingEnabled, isVideoEnabled) {
|
|
29
29
|
if (e.participant.local)
|
|
30
30
|
return;
|
|
31
31
|
call.updateParticipant(e.participant.session_id, {
|
|
32
32
|
setSubscribedTracks: {
|
|
33
33
|
audio: true,
|
|
34
|
-
video: isVideoRecordingEnabled,
|
|
34
|
+
video: isVideoRecordingEnabled || isVideoEnabled,
|
|
35
35
|
},
|
|
36
36
|
});
|
|
37
37
|
}
|
|
@@ -95,10 +95,12 @@ class Vapi extends VapiEventEmitter {
|
|
|
95
95
|
this.cleanup();
|
|
96
96
|
}
|
|
97
97
|
const isVideoRecordingEnabled = webCall?.artifactPlan?.videoRecordingEnabled ?? false;
|
|
98
|
+
// @ts-expect-error Tavus voice exists
|
|
99
|
+
const isVideoEnabled = webCall.assistant?.voice?.provider === 'tavus';
|
|
98
100
|
this.call = daily_js_1.default.createCallObject({
|
|
99
101
|
audioSource: this.dailyCallObject.audioSource ?? true,
|
|
100
|
-
videoSource: isVideoRecordingEnabled,
|
|
101
|
-
dailyConfig: this.dailyCallConfig
|
|
102
|
+
videoSource: this.dailyCallObject.videoSource ?? isVideoRecordingEnabled,
|
|
103
|
+
dailyConfig: this.dailyCallConfig,
|
|
102
104
|
});
|
|
103
105
|
this.call.iframe()?.style.setProperty('display', 'none');
|
|
104
106
|
this.call.on('left-meeting', () => {
|
|
@@ -127,17 +129,20 @@ class Vapi extends VapiEventEmitter {
|
|
|
127
129
|
return;
|
|
128
130
|
if (e.participant?.local)
|
|
129
131
|
return;
|
|
130
|
-
if (e.
|
|
131
|
-
return;
|
|
132
|
-
await buildAudioPlayer(e.track, e.participant.session_id);
|
|
133
|
-
if (e?.participant?.user_name !== 'Vapi Speaker')
|
|
132
|
+
if (e.participant?.user_name !== 'Vapi Speaker')
|
|
134
133
|
return;
|
|
134
|
+
if (e.track.kind === 'video') {
|
|
135
|
+
this.emit('video', e.track);
|
|
136
|
+
}
|
|
137
|
+
if (e.track.kind === 'audio') {
|
|
138
|
+
await buildAudioPlayer(e.track, e.participant.session_id);
|
|
139
|
+
}
|
|
135
140
|
this.call?.sendAppMessage('playable');
|
|
136
141
|
});
|
|
137
142
|
this.call.on('participant-joined', (e) => {
|
|
138
143
|
if (!e || !this.call)
|
|
139
144
|
return;
|
|
140
|
-
subscribeToTracks(e, this.call, isVideoRecordingEnabled);
|
|
145
|
+
subscribeToTracks(e, this.call, isVideoRecordingEnabled, isVideoEnabled);
|
|
141
146
|
});
|
|
142
147
|
await this.call.join({
|
|
143
148
|
url: webCall.webCallUrl,
|
|
@@ -247,26 +252,16 @@ class Vapi extends VapiEventEmitter {
|
|
|
247
252
|
this.call?.sendAppMessage(JSON.stringify(message));
|
|
248
253
|
}
|
|
249
254
|
setMuted(mute) {
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
throw new Error('Call object is not available.');
|
|
253
|
-
}
|
|
254
|
-
this.call.setLocalAudio(!mute);
|
|
255
|
-
}
|
|
256
|
-
catch (error) {
|
|
257
|
-
throw error;
|
|
255
|
+
if (!this.call) {
|
|
256
|
+
throw new Error('Call object is not available.');
|
|
258
257
|
}
|
|
258
|
+
this.call.setLocalAudio(!mute);
|
|
259
259
|
}
|
|
260
260
|
isMuted() {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
return false;
|
|
264
|
-
}
|
|
265
|
-
return this.call.localAudio() === false;
|
|
266
|
-
}
|
|
267
|
-
catch (error) {
|
|
268
|
-
throw error;
|
|
261
|
+
if (!this.call) {
|
|
262
|
+
return false;
|
|
269
263
|
}
|
|
264
|
+
return this.call.localAudio() === false;
|
|
270
265
|
}
|
|
271
266
|
say(message, endCallAfterSpoken) {
|
|
272
267
|
this.send({
|
package/package.json
CHANGED
package/vapi.ts
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ChatCompletionMessageParam } from 'openai/resources';
|
|
2
|
+
|
|
2
3
|
import DailyIframe, {
|
|
3
|
-
DailyAdvancedConfig,
|
|
4
4
|
DailyCall,
|
|
5
|
+
DailyAdvancedConfig,
|
|
6
|
+
DailyFactoryOptions,
|
|
5
7
|
DailyEventObjectAppMessage,
|
|
6
8
|
DailyEventObjectParticipant,
|
|
7
9
|
DailyEventObjectRemoteParticipantsAudioLevel,
|
|
8
|
-
DailyFactoryOptions,
|
|
9
10
|
} from '@daily-co/daily-js';
|
|
10
|
-
|
|
11
|
-
import type { ChatCompletionMessageParam } from 'openai/resources';
|
|
12
11
|
import EventEmitter from 'events';
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
Call,
|
|
15
|
+
CreateSquadDTO,
|
|
16
|
+
CreateAssistantDTO,
|
|
17
|
+
AssistantOverrides,
|
|
18
|
+
} from './api';
|
|
13
19
|
import { client } from './client';
|
|
14
20
|
|
|
15
|
-
function
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
async function startPlayer(player: HTMLAudioElement, track: any) {
|
|
21
|
+
async function startAudioPlayer(
|
|
22
|
+
player: HTMLAudioElement,
|
|
23
|
+
track: MediaStreamTrack,
|
|
24
|
+
) {
|
|
20
25
|
player.muted = false;
|
|
21
26
|
player.autoplay = true;
|
|
22
27
|
if (track != null) {
|
|
@@ -24,24 +29,37 @@ async function startPlayer(player: HTMLAudioElement, track: any) {
|
|
|
24
29
|
await player.play();
|
|
25
30
|
}
|
|
26
31
|
}
|
|
27
|
-
|
|
32
|
+
|
|
33
|
+
async function buildAudioPlayer(
|
|
34
|
+
track: MediaStreamTrack,
|
|
35
|
+
participantId: string,
|
|
36
|
+
) {
|
|
28
37
|
const player = document.createElement('audio');
|
|
29
38
|
player.dataset.participantId = participantId;
|
|
30
39
|
document.body.appendChild(player);
|
|
31
|
-
await
|
|
40
|
+
await startAudioPlayer(player, track);
|
|
32
41
|
return player;
|
|
33
42
|
}
|
|
43
|
+
|
|
44
|
+
function destroyAudioPlayer(participantId: string) {
|
|
45
|
+
const player = document.querySelector(
|
|
46
|
+
`audio[data-participant-id="${participantId}"]`,
|
|
47
|
+
);
|
|
48
|
+
player?.remove();
|
|
49
|
+
}
|
|
50
|
+
|
|
34
51
|
function subscribeToTracks(
|
|
35
52
|
e: DailyEventObjectParticipant,
|
|
36
53
|
call: DailyCall,
|
|
37
54
|
isVideoRecordingEnabled?: boolean,
|
|
55
|
+
isVideoEnabled?: boolean,
|
|
38
56
|
) {
|
|
39
57
|
if (e.participant.local) return;
|
|
40
58
|
|
|
41
59
|
call.updateParticipant(e.participant.session_id, {
|
|
42
60
|
setSubscribedTracks: {
|
|
43
61
|
audio: true,
|
|
44
|
-
video: isVideoRecordingEnabled,
|
|
62
|
+
video: isVideoRecordingEnabled || isVideoEnabled,
|
|
45
63
|
},
|
|
46
64
|
});
|
|
47
65
|
}
|
|
@@ -63,7 +81,10 @@ export interface SayMessage {
|
|
|
63
81
|
endCallAfterSpoken?: boolean;
|
|
64
82
|
}
|
|
65
83
|
|
|
66
|
-
type VapiClientToServerMessage =
|
|
84
|
+
type VapiClientToServerMessage =
|
|
85
|
+
| AddMessageMessage
|
|
86
|
+
| ControlMessages
|
|
87
|
+
| SayMessage;
|
|
67
88
|
|
|
68
89
|
type VapiEventNames =
|
|
69
90
|
| 'call-end'
|
|
@@ -72,6 +93,7 @@ type VapiEventNames =
|
|
|
72
93
|
| 'speech-start'
|
|
73
94
|
| 'speech-end'
|
|
74
95
|
| 'message'
|
|
96
|
+
| 'video'
|
|
75
97
|
| 'error';
|
|
76
98
|
|
|
77
99
|
type VapiEventListeners = {
|
|
@@ -80,23 +102,36 @@ type VapiEventListeners = {
|
|
|
80
102
|
'volume-level': (volume: number) => void;
|
|
81
103
|
'speech-start': () => void;
|
|
82
104
|
'speech-end': () => void;
|
|
105
|
+
video: (track: MediaStreamTrack) => void;
|
|
83
106
|
message: (message: any) => void;
|
|
84
107
|
error: (error: any) => void;
|
|
85
108
|
};
|
|
86
109
|
|
|
87
110
|
class VapiEventEmitter extends EventEmitter {
|
|
88
|
-
on<E extends VapiEventNames>(
|
|
111
|
+
on<E extends VapiEventNames>(
|
|
112
|
+
event: E,
|
|
113
|
+
listener: VapiEventListeners[E],
|
|
114
|
+
): this {
|
|
89
115
|
super.on(event, listener);
|
|
90
116
|
return this;
|
|
91
117
|
}
|
|
92
|
-
once<E extends VapiEventNames>(
|
|
118
|
+
once<E extends VapiEventNames>(
|
|
119
|
+
event: E,
|
|
120
|
+
listener: VapiEventListeners[E],
|
|
121
|
+
): this {
|
|
93
122
|
super.once(event, listener);
|
|
94
123
|
return this;
|
|
95
124
|
}
|
|
96
|
-
emit<E extends VapiEventNames>(
|
|
125
|
+
emit<E extends VapiEventNames>(
|
|
126
|
+
event: E,
|
|
127
|
+
...args: Parameters<VapiEventListeners[E]>
|
|
128
|
+
): boolean {
|
|
97
129
|
return super.emit(event, ...args);
|
|
98
130
|
}
|
|
99
|
-
removeListener<E extends VapiEventNames>(
|
|
131
|
+
removeListener<E extends VapiEventNames>(
|
|
132
|
+
event: E,
|
|
133
|
+
listener: VapiEventListeners[E],
|
|
134
|
+
): this {
|
|
100
135
|
super.removeListener(event, listener);
|
|
101
136
|
return this;
|
|
102
137
|
}
|
|
@@ -110,20 +145,23 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
110
145
|
private started: boolean = false;
|
|
111
146
|
private call: DailyCall | null = null;
|
|
112
147
|
private speakingTimeout: NodeJS.Timeout | null = null;
|
|
113
|
-
private dailyCallConfig: DailyAdvancedConfig = {}
|
|
114
|
-
private dailyCallObject: DailyFactoryOptions = {}
|
|
148
|
+
private dailyCallConfig: DailyAdvancedConfig = {};
|
|
149
|
+
private dailyCallObject: DailyFactoryOptions = {};
|
|
115
150
|
|
|
116
151
|
constructor(
|
|
117
|
-
apiToken: string,
|
|
118
|
-
apiBaseUrl?: string,
|
|
119
|
-
dailyCallConfig?: Pick<
|
|
120
|
-
|
|
152
|
+
apiToken: string,
|
|
153
|
+
apiBaseUrl?: string,
|
|
154
|
+
dailyCallConfig?: Pick<
|
|
155
|
+
DailyAdvancedConfig,
|
|
156
|
+
'avoidEval' | 'alwaysIncludeMicInPermissionPrompt'
|
|
157
|
+
>,
|
|
158
|
+
dailyCallObject?: Pick<DailyFactoryOptions, 'audioSource'>,
|
|
121
159
|
) {
|
|
122
160
|
super();
|
|
123
161
|
client.baseUrl = apiBaseUrl ?? 'https://api.vapi.ai';
|
|
124
162
|
client.setSecurityData(apiToken);
|
|
125
|
-
this.dailyCallConfig = dailyCallConfig ?? {}
|
|
126
|
-
this.dailyCallObject = dailyCallObject ?? {}
|
|
163
|
+
this.dailyCallConfig = dailyCallConfig ?? {};
|
|
164
|
+
this.dailyCallObject = dailyCallObject ?? {};
|
|
127
165
|
}
|
|
128
166
|
|
|
129
167
|
private cleanup() {
|
|
@@ -161,12 +199,17 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
161
199
|
if (this.call) {
|
|
162
200
|
this.cleanup();
|
|
163
201
|
}
|
|
164
|
-
|
|
202
|
+
|
|
203
|
+
const isVideoRecordingEnabled =
|
|
204
|
+
webCall?.artifactPlan?.videoRecordingEnabled ?? false;
|
|
205
|
+
|
|
206
|
+
// @ts-expect-error Tavus voice exists
|
|
207
|
+
const isVideoEnabled = webCall.assistant?.voice?.provider === 'tavus';
|
|
165
208
|
|
|
166
209
|
this.call = DailyIframe.createCallObject({
|
|
167
210
|
audioSource: this.dailyCallObject.audioSource ?? true,
|
|
168
|
-
videoSource: isVideoRecordingEnabled,
|
|
169
|
-
dailyConfig: this.dailyCallConfig
|
|
211
|
+
videoSource: this.dailyCallObject.videoSource ?? isVideoRecordingEnabled,
|
|
212
|
+
dailyConfig: this.dailyCallConfig,
|
|
170
213
|
});
|
|
171
214
|
this.call.iframe()?.style.setProperty('display', 'none');
|
|
172
215
|
|
|
@@ -197,17 +240,27 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
197
240
|
this.call.on('track-started', async (e) => {
|
|
198
241
|
if (!e || !e.participant) return;
|
|
199
242
|
if (e.participant?.local) return;
|
|
200
|
-
if (e.
|
|
243
|
+
if (e.participant?.user_name !== 'Vapi Speaker') return;
|
|
244
|
+
|
|
245
|
+
if (e.track.kind === 'video') {
|
|
246
|
+
this.emit('video', e.track);
|
|
247
|
+
}
|
|
201
248
|
|
|
202
|
-
|
|
249
|
+
if (e.track.kind === 'audio') {
|
|
250
|
+
await buildAudioPlayer(e.track, e.participant.session_id);
|
|
251
|
+
}
|
|
203
252
|
|
|
204
|
-
if (e?.participant?.user_name !== 'Vapi Speaker') return;
|
|
205
253
|
this.call?.sendAppMessage('playable');
|
|
206
254
|
});
|
|
207
255
|
|
|
208
256
|
this.call.on('participant-joined', (e) => {
|
|
209
257
|
if (!e || !this.call) return;
|
|
210
|
-
subscribeToTracks(
|
|
258
|
+
subscribeToTracks(
|
|
259
|
+
e,
|
|
260
|
+
this.call,
|
|
261
|
+
isVideoRecordingEnabled,
|
|
262
|
+
isVideoEnabled,
|
|
263
|
+
);
|
|
211
264
|
});
|
|
212
265
|
|
|
213
266
|
await this.call.join({
|
|
@@ -231,7 +284,8 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
231
284
|
this.send({
|
|
232
285
|
type: 'control',
|
|
233
286
|
control: 'say-first-message',
|
|
234
|
-
videoRecordingStartDelaySeconds:
|
|
287
|
+
videoRecordingStartDelaySeconds:
|
|
288
|
+
(new Date().getTime() - recordingRequestedTime) / 1000,
|
|
235
289
|
});
|
|
236
290
|
});
|
|
237
291
|
}
|
|
@@ -296,8 +350,13 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
296
350
|
}
|
|
297
351
|
}
|
|
298
352
|
|
|
299
|
-
private handleRemoteParticipantsAudioLevel(
|
|
300
|
-
|
|
353
|
+
private handleRemoteParticipantsAudioLevel(
|
|
354
|
+
e: DailyEventObjectRemoteParticipantsAudioLevel,
|
|
355
|
+
) {
|
|
356
|
+
const speechLevel = Object.values(e.participantsAudioLevel).reduce(
|
|
357
|
+
(a, b) => a + b,
|
|
358
|
+
0,
|
|
359
|
+
);
|
|
301
360
|
|
|
302
361
|
this.emit('volume-level', Math.min(1, speechLevel / 0.15));
|
|
303
362
|
|
|
@@ -329,25 +388,17 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
329
388
|
}
|
|
330
389
|
|
|
331
390
|
public setMuted(mute: boolean) {
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
throw new Error('Call object is not available.');
|
|
335
|
-
}
|
|
336
|
-
this.call.setLocalAudio(!mute);
|
|
337
|
-
} catch (error) {
|
|
338
|
-
throw error;
|
|
391
|
+
if (!this.call) {
|
|
392
|
+
throw new Error('Call object is not available.');
|
|
339
393
|
}
|
|
394
|
+
this.call.setLocalAudio(!mute);
|
|
340
395
|
}
|
|
341
396
|
|
|
342
397
|
public isMuted() {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
return false;
|
|
346
|
-
}
|
|
347
|
-
return this.call.localAudio() === false;
|
|
348
|
-
} catch (error) {
|
|
349
|
-
throw error;
|
|
398
|
+
if (!this.call) {
|
|
399
|
+
return false;
|
|
350
400
|
}
|
|
401
|
+
return this.call.localAudio() === false;
|
|
351
402
|
}
|
|
352
403
|
|
|
353
404
|
public say(message: string, endCallAfterSpoken?: boolean) {
|
|
@@ -358,11 +409,15 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
358
409
|
});
|
|
359
410
|
}
|
|
360
411
|
|
|
361
|
-
public setInputDevicesAsync(
|
|
412
|
+
public setInputDevicesAsync(
|
|
413
|
+
options: Parameters<DailyCall['setInputDevicesAsync']>[0],
|
|
414
|
+
) {
|
|
362
415
|
this.call?.setInputDevicesAsync(options);
|
|
363
416
|
}
|
|
364
417
|
|
|
365
|
-
public setOutputDeviceAsync(
|
|
418
|
+
public setOutputDeviceAsync(
|
|
419
|
+
options: Parameters<DailyCall['setOutputDeviceAsync']>[0],
|
|
420
|
+
) {
|
|
366
421
|
this.call?.setOutputDeviceAsync(options);
|
|
367
422
|
}
|
|
368
423
|
|