@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/dist/api.js
CHANGED
|
@@ -72,15 +72,20 @@ class HttpClient {
|
|
|
72
72
|
[ContentType.Text]: (input) => input !== null && typeof input !== "string"
|
|
73
73
|
? JSON.stringify(input)
|
|
74
74
|
: input,
|
|
75
|
-
[ContentType.FormData]: (input) =>
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
75
|
+
[ContentType.FormData]: (input) => {
|
|
76
|
+
if (input instanceof FormData) {
|
|
77
|
+
return input;
|
|
78
|
+
}
|
|
79
|
+
return Object.keys(input || {}).reduce((formData, key) => {
|
|
80
|
+
const property = input[key];
|
|
81
|
+
formData.append(key, property instanceof Blob
|
|
82
|
+
? property
|
|
83
|
+
: typeof property === "object" && property !== null
|
|
84
|
+
? JSON.stringify(property)
|
|
85
|
+
: `${property}`);
|
|
86
|
+
return formData;
|
|
87
|
+
}, new FormData());
|
|
88
|
+
},
|
|
84
89
|
[ContentType.UrlEncoded]: (input) => this.toQueryString(input),
|
|
85
90
|
};
|
|
86
91
|
mergeRequestParams(params1, params2) {
|
|
@@ -138,12 +143,13 @@ class HttpClient {
|
|
|
138
143
|
? null
|
|
139
144
|
: payloadFormatter(body),
|
|
140
145
|
}).then(async (response) => {
|
|
141
|
-
const r = response
|
|
146
|
+
const r = response;
|
|
142
147
|
r.data = null;
|
|
143
148
|
r.error = null;
|
|
149
|
+
const responseToParse = responseFormat ? response.clone() : response;
|
|
144
150
|
const data = !responseFormat
|
|
145
151
|
? r
|
|
146
|
-
: await
|
|
152
|
+
: await responseToParse[responseFormat]()
|
|
147
153
|
.then((data) => {
|
|
148
154
|
if (r.ok) {
|
|
149
155
|
r.data = data;
|
|
@@ -1489,23 +1495,6 @@ class Api extends HttpClient {
|
|
|
1489
1495
|
format: "json",
|
|
1490
1496
|
...params,
|
|
1491
1497
|
}),
|
|
1492
|
-
/**
|
|
1493
|
-
* @description Analyzes assistant configuration and generates contextual structured output recommendations
|
|
1494
|
-
*
|
|
1495
|
-
* @tags Structured Outputs
|
|
1496
|
-
* @name StructuredOutputControllerSuggest
|
|
1497
|
-
* @summary Generate AI-Powered Structured Output Suggestions
|
|
1498
|
-
* @request POST:/structured-output/suggest
|
|
1499
|
-
* @secure
|
|
1500
|
-
*/
|
|
1501
|
-
structuredOutputControllerSuggest: (data, params = {}) => this.request({
|
|
1502
|
-
path: `/structured-output/suggest`,
|
|
1503
|
-
method: "POST",
|
|
1504
|
-
body: data,
|
|
1505
|
-
secure: true,
|
|
1506
|
-
type: ContentType.Json,
|
|
1507
|
-
...params,
|
|
1508
|
-
}),
|
|
1509
1498
|
};
|
|
1510
1499
|
testSuite = {
|
|
1511
1500
|
/**
|
package/dist/vapi.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ChatCompletionMessageParam } from 'openai/resources';
|
|
2
|
-
import { DailyCall, DailyAdvancedConfig, DailyFactoryOptions, DailyParticipant, DailyVideoSendSettings } from '@daily-co/daily-js';
|
|
2
|
+
import { DailyCall, DailyAdvancedConfig, DailyFactoryOptions, DailyEventObjectNoPayload, DailyEventObjectRecordingError, DailyEventObjectRecordingStarted, DailyEventObjectRecordingStopped, DailyParticipant, DailyVideoSendSettings } from '@daily-co/daily-js';
|
|
3
3
|
import EventEmitter from 'events';
|
|
4
4
|
import { Call, CreateSquadDTO, CreateAssistantDTO, AssistantOverrides, CreateWorkflowDTO, WorkflowOverrides } from './api';
|
|
5
5
|
export interface EndCallMessage {
|
|
@@ -23,7 +23,7 @@ export interface SayMessage {
|
|
|
23
23
|
interruptAssistantEnabled?: boolean;
|
|
24
24
|
}
|
|
25
25
|
type VapiClientToServerMessage = AddMessageMessage | ControlMessages | SayMessage | EndCallMessage;
|
|
26
|
-
type VapiEventNames = 'call-end' | 'call-start' | 'volume-level' | 'speech-start' | 'speech-end' | 'message' | 'video' | 'error' | 'camera-error' | 'network-quality-change' | 'network-connection' | 'daily-participant-updated' | 'call-start-progress' | 'call-start-success' | 'call-start-failed';
|
|
26
|
+
type VapiEventNames = 'call-end' | 'call-start' | 'volume-level' | 'local-volume-level' | 'local-audio-level-observer-error' | 'speech-start' | 'speech-end' | 'message' | 'video' | 'error' | 'camera-error' | 'network-quality-change' | 'network-connection' | 'daily-participant-updated' | 'call-start-progress' | 'call-start-success' | 'call-start-failed' | 'recording-started' | 'recording-stopped' | 'recording-stats' | 'recording-error' | 'recording-upload-completed';
|
|
27
27
|
interface CallStartProgressEvent {
|
|
28
28
|
stage: string;
|
|
29
29
|
status: 'started' | 'completed' | 'failed';
|
|
@@ -48,6 +48,8 @@ type VapiEventListeners = {
|
|
|
48
48
|
'call-end': () => void;
|
|
49
49
|
'call-start': () => void;
|
|
50
50
|
'volume-level': (volume: number) => void;
|
|
51
|
+
'local-volume-level': (volume: number) => void;
|
|
52
|
+
'local-audio-level-observer-error': (error: any) => void;
|
|
51
53
|
'speech-start': () => void;
|
|
52
54
|
'speech-end': () => void;
|
|
53
55
|
video: (track: MediaStreamTrack) => void;
|
|
@@ -60,6 +62,11 @@ type VapiEventListeners = {
|
|
|
60
62
|
'call-start-progress': (event: CallStartProgressEvent) => void;
|
|
61
63
|
'call-start-success': (event: CallStartSuccessEvent) => void;
|
|
62
64
|
'call-start-failed': (event: CallStartFailedEvent) => void;
|
|
65
|
+
'recording-started': (event: DailyEventObjectRecordingStarted) => void;
|
|
66
|
+
'recording-stopped': (event: DailyEventObjectRecordingStopped) => void;
|
|
67
|
+
'recording-stats': (event: DailyEventObjectNoPayload) => void;
|
|
68
|
+
'recording-error': (event: DailyEventObjectRecordingError) => void;
|
|
69
|
+
'recording-upload-completed': (event: DailyEventObjectNoPayload) => void;
|
|
63
70
|
};
|
|
64
71
|
type StartCallOptions = {
|
|
65
72
|
/**
|
|
@@ -123,6 +130,7 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
123
130
|
start(assistant?: CreateAssistantDTO | string, assistantOverrides?: AssistantOverrides, squad?: CreateSquadDTO | string, workflow?: CreateWorkflowDTO | string, workflowOverrides?: WorkflowOverrides, options?: StartCallOptions): Promise<Call | null>;
|
|
124
131
|
private onAppMessage;
|
|
125
132
|
private handleRemoteParticipantsAudioLevel;
|
|
133
|
+
private handleLocalAudioLevel;
|
|
126
134
|
/**
|
|
127
135
|
* Stops the call by destroying the Daily call object.
|
|
128
136
|
*
|
|
@@ -149,6 +157,36 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
149
157
|
increaseMicLevel(gain: number): Promise<void>;
|
|
150
158
|
setOutputDeviceAsync(options: Parameters<DailyCall['setOutputDeviceAsync']>[0]): void;
|
|
151
159
|
getDailyCallObject(): DailyCall | null;
|
|
160
|
+
/**
|
|
161
|
+
* Returns the most recent local (microphone) audio level, a number between 0 and 1.
|
|
162
|
+
*
|
|
163
|
+
* Useful for detecting when the local participant's microphone is not picking up any
|
|
164
|
+
* audio (e.g. wrong input device selected, OS-level input volume at 0, or muted
|
|
165
|
+
* hardware) so you can surface a "we can't hear you" style warning in your UI.
|
|
166
|
+
*
|
|
167
|
+
* Returns 0 when there is no active call.
|
|
168
|
+
*/
|
|
169
|
+
getLocalAudioLevel(): number;
|
|
170
|
+
/**
|
|
171
|
+
* Starts (or restarts) the local audio level observer. The observer is started
|
|
172
|
+
* automatically when a call begins, but this can be used to change the sampling
|
|
173
|
+
* interval or restart it if it was stopped.
|
|
174
|
+
*
|
|
175
|
+
* While running, the Vapi instance emits 'local-volume-level' events with the local
|
|
176
|
+
* microphone audio level (0 to 1).
|
|
177
|
+
*
|
|
178
|
+
* @param interval How often, in milliseconds, to emit audio level events. Defaults to Daily's default.
|
|
179
|
+
*/
|
|
180
|
+
startLocalAudioLevelObserver(interval?: number): Promise<void>;
|
|
181
|
+
/**
|
|
182
|
+
* Stops the local audio level observer. After calling this, 'local-volume-level'
|
|
183
|
+
* events will no longer be emitted until the observer is started again.
|
|
184
|
+
*/
|
|
185
|
+
stopLocalAudioLevelObserver(): void;
|
|
186
|
+
/**
|
|
187
|
+
* Returns whether the local audio level observer is currently running.
|
|
188
|
+
*/
|
|
189
|
+
isLocalAudioLevelObserverRunning(): boolean;
|
|
152
190
|
startScreenSharing(displayMediaOptions?: DisplayMediaStreamOptions, screenVideoSendSettings?: DailyVideoSendSettings): void;
|
|
153
191
|
stopScreenSharing(): void;
|
|
154
192
|
/**
|
package/dist/vapi.js
CHANGED
|
@@ -7,6 +7,57 @@ 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
9
|
const daily_guards_1 = require("./daily-guards");
|
|
10
|
+
/**
|
|
11
|
+
* Extracts error details into a plain object that serializes properly to JSON.
|
|
12
|
+
* Error objects don't serialize well because their properties (message, stack, name)
|
|
13
|
+
* are not enumerable and get lost when using JSON.stringify().
|
|
14
|
+
*/
|
|
15
|
+
function serializeError(error) {
|
|
16
|
+
if (error === null || error === undefined) {
|
|
17
|
+
return { message: 'Unknown error (null or undefined)' };
|
|
18
|
+
}
|
|
19
|
+
if (error instanceof Error) {
|
|
20
|
+
const serialized = {
|
|
21
|
+
message: error.message,
|
|
22
|
+
name: error.name,
|
|
23
|
+
stack: error.stack,
|
|
24
|
+
};
|
|
25
|
+
// Include any additional properties that might be on the error
|
|
26
|
+
const errorAsAny = error;
|
|
27
|
+
if (errorAsAny.code !== undefined) {
|
|
28
|
+
serialized.code = errorAsAny.code;
|
|
29
|
+
}
|
|
30
|
+
if (errorAsAny.cause !== undefined) {
|
|
31
|
+
serialized.cause = String(errorAsAny.cause);
|
|
32
|
+
}
|
|
33
|
+
if (errorAsAny.reason !== undefined) {
|
|
34
|
+
serialized.reason = errorAsAny.reason;
|
|
35
|
+
}
|
|
36
|
+
if (errorAsAny.details !== undefined) {
|
|
37
|
+
serialized.details = errorAsAny.details;
|
|
38
|
+
}
|
|
39
|
+
// Daily.co specific error properties
|
|
40
|
+
if (errorAsAny.errorMsg !== undefined) {
|
|
41
|
+
serialized.errorMsg = errorAsAny.errorMsg;
|
|
42
|
+
}
|
|
43
|
+
if (errorAsAny.error !== undefined && typeof errorAsAny.error === 'string') {
|
|
44
|
+
serialized.errorDetail = errorAsAny.error;
|
|
45
|
+
}
|
|
46
|
+
return serialized;
|
|
47
|
+
}
|
|
48
|
+
if (typeof error === 'string') {
|
|
49
|
+
return { message: error };
|
|
50
|
+
}
|
|
51
|
+
if (typeof error === 'object') {
|
|
52
|
+
// It's already a plain object, but let's ensure message exists
|
|
53
|
+
const errorObj = error;
|
|
54
|
+
return {
|
|
55
|
+
message: errorObj.message || errorObj.error || JSON.stringify(error),
|
|
56
|
+
...errorObj
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
return { message: String(error) };
|
|
60
|
+
}
|
|
10
61
|
async function startAudioPlayer(player, track) {
|
|
11
62
|
player.muted = false;
|
|
12
63
|
player.autoplay = true;
|
|
@@ -98,7 +149,7 @@ class Vapi extends VapiEventEmitter {
|
|
|
98
149
|
this.emit('error', {
|
|
99
150
|
type: 'validation-error',
|
|
100
151
|
stage: 'input-validation',
|
|
101
|
-
|
|
152
|
+
error: serializeError(error),
|
|
102
153
|
timestamp: new Date().toISOString()
|
|
103
154
|
});
|
|
104
155
|
throw error;
|
|
@@ -194,17 +245,18 @@ class Vapi extends VapiEventEmitter {
|
|
|
194
245
|
}
|
|
195
246
|
catch (error) {
|
|
196
247
|
const dailyCallDuration = Date.now() - dailyCallStartTime;
|
|
248
|
+
const serializedError = serializeError(error);
|
|
197
249
|
this.emit('call-start-progress', {
|
|
198
250
|
stage: 'daily-call-object-creation',
|
|
199
251
|
status: 'failed',
|
|
200
252
|
duration: dailyCallDuration,
|
|
201
253
|
timestamp: new Date().toISOString(),
|
|
202
|
-
metadata: { error:
|
|
254
|
+
metadata: { error: serializedError.message }
|
|
203
255
|
});
|
|
204
256
|
this.emit('error', {
|
|
205
257
|
type: 'daily-call-object-creation-error',
|
|
206
258
|
stage: 'daily-call-object-creation',
|
|
207
|
-
error,
|
|
259
|
+
error: serializedError,
|
|
208
260
|
timestamp: new Date().toISOString()
|
|
209
261
|
});
|
|
210
262
|
throw error;
|
|
@@ -226,13 +278,21 @@ class Vapi extends VapiEventEmitter {
|
|
|
226
278
|
this.cleanup().catch(console.error);
|
|
227
279
|
});
|
|
228
280
|
this.call.on('error', (error) => {
|
|
229
|
-
this.emit('error',
|
|
281
|
+
this.emit('error', {
|
|
282
|
+
type: 'daily-error',
|
|
283
|
+
error: serializeError(error),
|
|
284
|
+
timestamp: new Date().toISOString()
|
|
285
|
+
});
|
|
230
286
|
if (isVideoRecordingEnabled) {
|
|
231
287
|
this.call?.stopRecording();
|
|
232
288
|
}
|
|
233
289
|
});
|
|
234
290
|
this.call.on('camera-error', (error) => {
|
|
235
|
-
this.emit('camera-error',
|
|
291
|
+
this.emit('camera-error', {
|
|
292
|
+
type: 'camera-error',
|
|
293
|
+
error: serializeError(error),
|
|
294
|
+
timestamp: new Date().toISOString()
|
|
295
|
+
});
|
|
236
296
|
});
|
|
237
297
|
this.call.on('network-quality-change', (event) => {
|
|
238
298
|
this.emit('network-quality-change', event);
|
|
@@ -240,6 +300,26 @@ class Vapi extends VapiEventEmitter {
|
|
|
240
300
|
this.call.on('network-connection', (event) => {
|
|
241
301
|
this.emit('network-connection', event);
|
|
242
302
|
});
|
|
303
|
+
this.call.on('recording-started', (event) => {
|
|
304
|
+
if (event)
|
|
305
|
+
this.emit('recording-started', event);
|
|
306
|
+
});
|
|
307
|
+
this.call.on('recording-stopped', (event) => {
|
|
308
|
+
if (event)
|
|
309
|
+
this.emit('recording-stopped', event);
|
|
310
|
+
});
|
|
311
|
+
this.call.on('recording-stats', (event) => {
|
|
312
|
+
if (event)
|
|
313
|
+
this.emit('recording-stats', event);
|
|
314
|
+
});
|
|
315
|
+
this.call.on('recording-error', (event) => {
|
|
316
|
+
if (event)
|
|
317
|
+
this.emit('recording-error', event);
|
|
318
|
+
});
|
|
319
|
+
this.call.on('recording-upload-completed', (event) => {
|
|
320
|
+
if (event)
|
|
321
|
+
this.emit('recording-upload-completed', event);
|
|
322
|
+
});
|
|
243
323
|
this.call.on('track-started', async (e) => {
|
|
244
324
|
if (!e || !e.participant) {
|
|
245
325
|
return;
|
|
@@ -326,17 +406,18 @@ class Vapi extends VapiEventEmitter {
|
|
|
326
406
|
}
|
|
327
407
|
catch (error) {
|
|
328
408
|
const joinDuration = Date.now() - joinStartTime;
|
|
409
|
+
const serializedError = serializeError(error);
|
|
329
410
|
this.emit('call-start-progress', {
|
|
330
411
|
stage: 'daily-call-join',
|
|
331
412
|
status: 'failed',
|
|
332
413
|
duration: joinDuration,
|
|
333
414
|
timestamp: new Date().toISOString(),
|
|
334
|
-
metadata: { error:
|
|
415
|
+
metadata: { error: serializedError.message }
|
|
335
416
|
});
|
|
336
417
|
this.emit('error', {
|
|
337
418
|
type: 'daily-call-join-error',
|
|
338
419
|
stage: 'daily-call-join',
|
|
339
|
-
error,
|
|
420
|
+
error: serializedError,
|
|
340
421
|
duration: joinDuration,
|
|
341
422
|
timestamp: new Date().toISOString()
|
|
342
423
|
});
|
|
@@ -384,17 +465,18 @@ class Vapi extends VapiEventEmitter {
|
|
|
384
465
|
}
|
|
385
466
|
catch (error) {
|
|
386
467
|
const recordingSetupDuration = Date.now() - recordingStartTime;
|
|
468
|
+
const serializedError = serializeError(error);
|
|
387
469
|
this.emit('call-start-progress', {
|
|
388
470
|
stage: 'video-recording-setup',
|
|
389
471
|
status: 'failed',
|
|
390
472
|
duration: recordingSetupDuration,
|
|
391
473
|
timestamp: new Date().toISOString(),
|
|
392
|
-
metadata: { error:
|
|
474
|
+
metadata: { error: serializedError.message }
|
|
393
475
|
});
|
|
394
476
|
this.emit('error', {
|
|
395
477
|
type: 'video-recording-setup-error',
|
|
396
478
|
stage: 'video-recording-setup',
|
|
397
|
-
error,
|
|
479
|
+
error: serializedError,
|
|
398
480
|
timestamp: new Date().toISOString()
|
|
399
481
|
});
|
|
400
482
|
// Don't throw here, video recording is optional
|
|
@@ -417,6 +499,7 @@ class Vapi extends VapiEventEmitter {
|
|
|
417
499
|
const audioObserverStartTime = Date.now();
|
|
418
500
|
try {
|
|
419
501
|
this.call.startRemoteParticipantsAudioLevelObserver(100);
|
|
502
|
+
this.call.startLocalAudioLevelObserver(100);
|
|
420
503
|
const audioObserverDuration = Date.now() - audioObserverStartTime;
|
|
421
504
|
this.emit('call-start-progress', {
|
|
422
505
|
stage: 'audio-observer-setup',
|
|
@@ -427,17 +510,18 @@ class Vapi extends VapiEventEmitter {
|
|
|
427
510
|
}
|
|
428
511
|
catch (error) {
|
|
429
512
|
const audioObserverDuration = Date.now() - audioObserverStartTime;
|
|
513
|
+
const serializedError = serializeError(error);
|
|
430
514
|
this.emit('call-start-progress', {
|
|
431
515
|
stage: 'audio-observer-setup',
|
|
432
516
|
status: 'failed',
|
|
433
517
|
duration: audioObserverDuration,
|
|
434
518
|
timestamp: new Date().toISOString(),
|
|
435
|
-
metadata: { error:
|
|
519
|
+
metadata: { error: serializedError.message }
|
|
436
520
|
});
|
|
437
521
|
this.emit('error', {
|
|
438
522
|
type: 'audio-observer-setup-error',
|
|
439
523
|
stage: 'audio-observer-setup',
|
|
440
|
-
error,
|
|
524
|
+
error: serializedError,
|
|
441
525
|
timestamp: new Date().toISOString()
|
|
442
526
|
});
|
|
443
527
|
// Don't throw here, this is non-critical
|
|
@@ -446,9 +530,16 @@ class Vapi extends VapiEventEmitter {
|
|
|
446
530
|
if (e)
|
|
447
531
|
this.handleRemoteParticipantsAudioLevel(e);
|
|
448
532
|
});
|
|
533
|
+
this.call.on('local-audio-level', (e) => {
|
|
534
|
+
if (e)
|
|
535
|
+
this.handleLocalAudioLevel(e);
|
|
536
|
+
});
|
|
449
537
|
this.call.on('app-message', (e) => this.onAppMessage(e));
|
|
450
538
|
this.call.on('nonfatal-error', (e) => {
|
|
451
539
|
// https://docs.daily.co/reference/daily-js/events/meeting-events#type-audio-processor-error
|
|
540
|
+
if (e?.type === 'local-audio-level-observer-error') {
|
|
541
|
+
this.emit('local-audio-level-observer-error', serializeError(e));
|
|
542
|
+
}
|
|
452
543
|
if (e?.type === 'audio-processor-error') {
|
|
453
544
|
this.call
|
|
454
545
|
?.updateInputSettings({
|
|
@@ -488,17 +579,18 @@ class Vapi extends VapiEventEmitter {
|
|
|
488
579
|
}
|
|
489
580
|
catch (error) {
|
|
490
581
|
const audioProcessingDuration = Date.now() - audioProcessingStartTime;
|
|
582
|
+
const serializedError = serializeError(error);
|
|
491
583
|
this.emit('call-start-progress', {
|
|
492
584
|
stage: 'audio-processing-setup',
|
|
493
585
|
status: 'failed',
|
|
494
586
|
duration: audioProcessingDuration,
|
|
495
587
|
timestamp: new Date().toISOString(),
|
|
496
|
-
metadata: { error:
|
|
588
|
+
metadata: { error: serializedError.message }
|
|
497
589
|
});
|
|
498
590
|
this.emit('error', {
|
|
499
591
|
type: 'audio-processing-setup-error',
|
|
500
592
|
stage: 'audio-processing-setup',
|
|
501
|
-
error,
|
|
593
|
+
error: serializedError,
|
|
502
594
|
timestamp: new Date().toISOString()
|
|
503
595
|
});
|
|
504
596
|
// Don't throw here, this is non-critical
|
|
@@ -513,11 +605,12 @@ class Vapi extends VapiEventEmitter {
|
|
|
513
605
|
}
|
|
514
606
|
catch (e) {
|
|
515
607
|
const totalDuration = Date.now() - startTime;
|
|
608
|
+
const serializedError = serializeError(e);
|
|
516
609
|
this.emit('call-start-failed', {
|
|
517
610
|
stage: 'unknown',
|
|
518
611
|
totalDuration,
|
|
519
|
-
error:
|
|
520
|
-
errorStack:
|
|
612
|
+
error: serializedError.message,
|
|
613
|
+
errorStack: serializedError.stack || 'No stack trace available',
|
|
521
614
|
timestamp: new Date().toISOString(),
|
|
522
615
|
context: {
|
|
523
616
|
hasAssistant: !!assistant,
|
|
@@ -530,7 +623,7 @@ class Vapi extends VapiEventEmitter {
|
|
|
530
623
|
this.emit('error', {
|
|
531
624
|
type: 'start-method-error',
|
|
532
625
|
stage: 'unknown',
|
|
533
|
-
error:
|
|
626
|
+
error: serializedError,
|
|
534
627
|
totalDuration,
|
|
535
628
|
timestamp: new Date().toISOString(),
|
|
536
629
|
context: {
|
|
@@ -588,6 +681,9 @@ class Vapi extends VapiEventEmitter {
|
|
|
588
681
|
this.speakingTimeout = null;
|
|
589
682
|
}, 1000);
|
|
590
683
|
}
|
|
684
|
+
handleLocalAudioLevel(e) {
|
|
685
|
+
this.emit('local-volume-level', e.audioLevel);
|
|
686
|
+
}
|
|
591
687
|
/**
|
|
592
688
|
* Stops the call by destroying the Daily call object.
|
|
593
689
|
*
|
|
@@ -668,6 +764,53 @@ class Vapi extends VapiEventEmitter {
|
|
|
668
764
|
getDailyCallObject() {
|
|
669
765
|
return this.call;
|
|
670
766
|
}
|
|
767
|
+
/**
|
|
768
|
+
* Returns the most recent local (microphone) audio level, a number between 0 and 1.
|
|
769
|
+
*
|
|
770
|
+
* Useful for detecting when the local participant's microphone is not picking up any
|
|
771
|
+
* audio (e.g. wrong input device selected, OS-level input volume at 0, or muted
|
|
772
|
+
* hardware) so you can surface a "we can't hear you" style warning in your UI.
|
|
773
|
+
*
|
|
774
|
+
* Returns 0 when there is no active call.
|
|
775
|
+
*/
|
|
776
|
+
getLocalAudioLevel() {
|
|
777
|
+
if (!this.call) {
|
|
778
|
+
return 0;
|
|
779
|
+
}
|
|
780
|
+
return this.call.getLocalAudioLevel();
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Starts (or restarts) the local audio level observer. The observer is started
|
|
784
|
+
* automatically when a call begins, but this can be used to change the sampling
|
|
785
|
+
* interval or restart it if it was stopped.
|
|
786
|
+
*
|
|
787
|
+
* While running, the Vapi instance emits 'local-volume-level' events with the local
|
|
788
|
+
* microphone audio level (0 to 1).
|
|
789
|
+
*
|
|
790
|
+
* @param interval How often, in milliseconds, to emit audio level events. Defaults to Daily's default.
|
|
791
|
+
*/
|
|
792
|
+
async startLocalAudioLevelObserver(interval) {
|
|
793
|
+
if (!this.call) {
|
|
794
|
+
throw new Error('Call object is not available.');
|
|
795
|
+
}
|
|
796
|
+
await this.call.startLocalAudioLevelObserver(interval);
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* Stops the local audio level observer. After calling this, 'local-volume-level'
|
|
800
|
+
* events will no longer be emitted until the observer is started again.
|
|
801
|
+
*/
|
|
802
|
+
stopLocalAudioLevelObserver() {
|
|
803
|
+
this.call?.stopLocalAudioLevelObserver();
|
|
804
|
+
}
|
|
805
|
+
/**
|
|
806
|
+
* Returns whether the local audio level observer is currently running.
|
|
807
|
+
*/
|
|
808
|
+
isLocalAudioLevelObserverRunning() {
|
|
809
|
+
if (!this.call) {
|
|
810
|
+
return false;
|
|
811
|
+
}
|
|
812
|
+
return this.call.isLocalAudioLevelObserverRunning();
|
|
813
|
+
}
|
|
671
814
|
startScreenSharing(displayMediaOptions, screenVideoSendSettings) {
|
|
672
815
|
this.call?.startScreenShare({
|
|
673
816
|
displayMediaOptions,
|
|
@@ -762,13 +905,21 @@ class Vapi extends VapiEventEmitter {
|
|
|
762
905
|
this.cleanup().catch(console.error);
|
|
763
906
|
});
|
|
764
907
|
this.call.on('error', (error) => {
|
|
765
|
-
this.emit('error',
|
|
908
|
+
this.emit('error', {
|
|
909
|
+
type: 'daily-error',
|
|
910
|
+
error: serializeError(error),
|
|
911
|
+
timestamp: new Date().toISOString()
|
|
912
|
+
});
|
|
766
913
|
if (isVideoRecordingEnabled) {
|
|
767
914
|
this.call?.stopRecording();
|
|
768
915
|
}
|
|
769
916
|
});
|
|
770
917
|
this.call.on('camera-error', (error) => {
|
|
771
|
-
this.emit('camera-error',
|
|
918
|
+
this.emit('camera-error', {
|
|
919
|
+
type: 'camera-error',
|
|
920
|
+
error: serializeError(error),
|
|
921
|
+
timestamp: new Date().toISOString()
|
|
922
|
+
});
|
|
772
923
|
});
|
|
773
924
|
this.call.on('network-quality-change', (event) => {
|
|
774
925
|
this.emit('network-quality-change', event);
|
|
@@ -776,6 +927,26 @@ class Vapi extends VapiEventEmitter {
|
|
|
776
927
|
this.call.on('network-connection', (event) => {
|
|
777
928
|
this.emit('network-connection', event);
|
|
778
929
|
});
|
|
930
|
+
this.call.on('recording-started', (event) => {
|
|
931
|
+
if (event)
|
|
932
|
+
this.emit('recording-started', event);
|
|
933
|
+
});
|
|
934
|
+
this.call.on('recording-stopped', (event) => {
|
|
935
|
+
if (event)
|
|
936
|
+
this.emit('recording-stopped', event);
|
|
937
|
+
});
|
|
938
|
+
this.call.on('recording-stats', (event) => {
|
|
939
|
+
if (event)
|
|
940
|
+
this.emit('recording-stats', event);
|
|
941
|
+
});
|
|
942
|
+
this.call.on('recording-error', (event) => {
|
|
943
|
+
if (event)
|
|
944
|
+
this.emit('recording-error', event);
|
|
945
|
+
});
|
|
946
|
+
this.call.on('recording-upload-completed', (event) => {
|
|
947
|
+
if (event)
|
|
948
|
+
this.emit('recording-upload-completed', event);
|
|
949
|
+
});
|
|
779
950
|
this.call.on('track-started', async (e) => {
|
|
780
951
|
if (!e || !e.participant) {
|
|
781
952
|
return;
|
|
@@ -815,9 +986,16 @@ class Vapi extends VapiEventEmitter {
|
|
|
815
986
|
if (e)
|
|
816
987
|
this.handleRemoteParticipantsAudioLevel(e);
|
|
817
988
|
});
|
|
989
|
+
this.call.on('local-audio-level', (e) => {
|
|
990
|
+
if (e)
|
|
991
|
+
this.handleLocalAudioLevel(e);
|
|
992
|
+
});
|
|
818
993
|
this.call.on('app-message', (e) => this.onAppMessage(e));
|
|
819
994
|
this.call.on('nonfatal-error', (e) => {
|
|
820
995
|
// https://docs.daily.co/reference/daily-js/events/meeting-events#type-audio-processor-error
|
|
996
|
+
if (e?.type === 'local-audio-level-observer-error') {
|
|
997
|
+
this.emit('local-audio-level-observer-error', serializeError(e));
|
|
998
|
+
}
|
|
821
999
|
if (e?.type === 'audio-processor-error') {
|
|
822
1000
|
this.call
|
|
823
1001
|
?.updateInputSettings({
|
|
@@ -920,12 +1098,13 @@ class Vapi extends VapiEventEmitter {
|
|
|
920
1098
|
}
|
|
921
1099
|
catch (error) {
|
|
922
1100
|
const recordingSetupDuration = Date.now() - recordingStartTime;
|
|
1101
|
+
const serializedError = serializeError(error);
|
|
923
1102
|
this.emit('call-start-progress', {
|
|
924
1103
|
stage: 'video-recording-setup',
|
|
925
1104
|
status: 'failed',
|
|
926
1105
|
duration: recordingSetupDuration,
|
|
927
1106
|
timestamp: new Date().toISOString(),
|
|
928
|
-
metadata: { error:
|
|
1107
|
+
metadata: { error: serializedError.message }
|
|
929
1108
|
});
|
|
930
1109
|
// Don't throw here, video recording is optional
|
|
931
1110
|
}
|
|
@@ -947,6 +1126,7 @@ class Vapi extends VapiEventEmitter {
|
|
|
947
1126
|
const audioObserverStartTime = Date.now();
|
|
948
1127
|
try {
|
|
949
1128
|
this.call.startRemoteParticipantsAudioLevelObserver(100);
|
|
1129
|
+
this.call.startLocalAudioLevelObserver(100);
|
|
950
1130
|
const audioObserverDuration = Date.now() - audioObserverStartTime;
|
|
951
1131
|
this.emit('call-start-progress', {
|
|
952
1132
|
stage: 'audio-observer-setup',
|
|
@@ -957,12 +1137,13 @@ class Vapi extends VapiEventEmitter {
|
|
|
957
1137
|
}
|
|
958
1138
|
catch (error) {
|
|
959
1139
|
const audioObserverDuration = Date.now() - audioObserverStartTime;
|
|
1140
|
+
const serializedError = serializeError(error);
|
|
960
1141
|
this.emit('call-start-progress', {
|
|
961
1142
|
stage: 'audio-observer-setup',
|
|
962
1143
|
status: 'failed',
|
|
963
1144
|
duration: audioObserverDuration,
|
|
964
1145
|
timestamp: new Date().toISOString(),
|
|
965
|
-
metadata: { error:
|
|
1146
|
+
metadata: { error: serializedError.message }
|
|
966
1147
|
});
|
|
967
1148
|
// Don't throw here, this is non-critical
|
|
968
1149
|
}
|
|
@@ -991,12 +1172,13 @@ class Vapi extends VapiEventEmitter {
|
|
|
991
1172
|
}
|
|
992
1173
|
catch (error) {
|
|
993
1174
|
const audioProcessingDuration = Date.now() - audioProcessingStartTime;
|
|
1175
|
+
const serializedError = serializeError(error);
|
|
994
1176
|
this.emit('call-start-progress', {
|
|
995
1177
|
stage: 'audio-processing-setup',
|
|
996
1178
|
status: 'failed',
|
|
997
1179
|
duration: audioProcessingDuration,
|
|
998
1180
|
timestamp: new Date().toISOString(),
|
|
999
|
-
metadata: { error:
|
|
1181
|
+
metadata: { error: serializedError.message }
|
|
1000
1182
|
});
|
|
1001
1183
|
// Don't throw here, this is non-critical
|
|
1002
1184
|
}
|
|
@@ -1010,11 +1192,12 @@ class Vapi extends VapiEventEmitter {
|
|
|
1010
1192
|
}
|
|
1011
1193
|
catch (e) {
|
|
1012
1194
|
const totalDuration = Date.now() - startTime;
|
|
1195
|
+
const serializedError = serializeError(e);
|
|
1013
1196
|
this.emit('call-start-failed', {
|
|
1014
1197
|
stage: 'reconnect',
|
|
1015
1198
|
totalDuration,
|
|
1016
|
-
error:
|
|
1017
|
-
errorStack:
|
|
1199
|
+
error: serializedError.message,
|
|
1200
|
+
errorStack: serializedError.stack || 'No stack trace available',
|
|
1018
1201
|
timestamp: new Date().toISOString(),
|
|
1019
1202
|
context: {
|
|
1020
1203
|
isReconnect: true,
|
|
@@ -1027,7 +1210,7 @@ class Vapi extends VapiEventEmitter {
|
|
|
1027
1210
|
// Also emit the generic error event for backward compatibility
|
|
1028
1211
|
this.emit('error', {
|
|
1029
1212
|
type: 'reconnect-error',
|
|
1030
|
-
error:
|
|
1213
|
+
error: serializedError,
|
|
1031
1214
|
totalDuration,
|
|
1032
1215
|
timestamp: new Date().toISOString(),
|
|
1033
1216
|
context: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vapi-ai/web",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/vapi.js",
|
|
6
6
|
"types": "dist/vapi.d.ts",
|
|
@@ -19,19 +19,19 @@
|
|
|
19
19
|
},
|
|
20
20
|
"repository": {
|
|
21
21
|
"type": "git",
|
|
22
|
-
"url": "git+https://github.com/VapiAI/web.git"
|
|
22
|
+
"url": "git+https://github.com/VapiAI/client-sdk-web.git"
|
|
23
23
|
},
|
|
24
24
|
"author": "vapi",
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"bugs": {
|
|
27
|
-
"url": "https://github.com/VapiAI/web/issues"
|
|
27
|
+
"url": "https://github.com/VapiAI/client-sdk-web/issues"
|
|
28
28
|
},
|
|
29
|
-
"homepage": "https://github.com/VapiAI/web#readme",
|
|
29
|
+
"homepage": "https://github.com/VapiAI/client-sdk-web#readme",
|
|
30
30
|
"engines": {
|
|
31
31
|
"node": ">=18.0.0"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@daily-co/daily-js": "^0.
|
|
34
|
+
"@daily-co/daily-js": "^0.87.0",
|
|
35
35
|
"events": "^3.3.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|