@vapi-ai/web 2.7.1 → 3.0.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 +2903 -815
- package/dist/api.d.ts +2482 -930
- package/dist/api.js +256 -17
- package/dist/jest.config.js +3 -0
- package/dist/vapi.d.ts +5 -0
- package/dist/vapi.js +57 -14
- package/package.json +3 -3
- package/vapi.ts +60 -15
package/dist/api.js
CHANGED
|
@@ -218,6 +218,24 @@ class Api extends HttpClient {
|
|
|
218
218
|
format: "json",
|
|
219
219
|
...params,
|
|
220
220
|
}),
|
|
221
|
+
/**
|
|
222
|
+
* No description
|
|
223
|
+
*
|
|
224
|
+
* @tags Assistants
|
|
225
|
+
* @name AssistantControllerValidateBackgroundSoundUrl
|
|
226
|
+
* @summary Validate Background Sound URL
|
|
227
|
+
* @request POST:/assistant/background-sound/validate
|
|
228
|
+
* @secure
|
|
229
|
+
*/
|
|
230
|
+
assistantControllerValidateBackgroundSoundUrl: (data, params = {}) => this.request({
|
|
231
|
+
path: `/assistant/background-sound/validate`,
|
|
232
|
+
method: "POST",
|
|
233
|
+
body: data,
|
|
234
|
+
secure: true,
|
|
235
|
+
type: ContentType.Json,
|
|
236
|
+
format: "json",
|
|
237
|
+
...params,
|
|
238
|
+
}),
|
|
221
239
|
/**
|
|
222
240
|
* No description
|
|
223
241
|
*
|
|
@@ -441,6 +459,24 @@ class Api extends HttpClient {
|
|
|
441
459
|
format: "json",
|
|
442
460
|
...params,
|
|
443
461
|
}),
|
|
462
|
+
/**
|
|
463
|
+
* No description
|
|
464
|
+
*
|
|
465
|
+
* @tags Assistants
|
|
466
|
+
* @name AssistantControllerUpsertDraft
|
|
467
|
+
* @summary Upsert Assistant Draft
|
|
468
|
+
* @request PUT:/assistant/{id}/draft
|
|
469
|
+
* @secure
|
|
470
|
+
*/
|
|
471
|
+
assistantControllerUpsertDraft: (id, data, params = {}) => this.request({
|
|
472
|
+
path: `/assistant/${id}/draft`,
|
|
473
|
+
method: "PUT",
|
|
474
|
+
body: data,
|
|
475
|
+
secure: true,
|
|
476
|
+
type: ContentType.Json,
|
|
477
|
+
format: "json",
|
|
478
|
+
...params,
|
|
479
|
+
}),
|
|
444
480
|
/**
|
|
445
481
|
* No description
|
|
446
482
|
*
|
|
@@ -680,6 +716,23 @@ class Api extends HttpClient {
|
|
|
680
716
|
format: "json",
|
|
681
717
|
...params,
|
|
682
718
|
}),
|
|
719
|
+
/**
|
|
720
|
+
* No description
|
|
721
|
+
*
|
|
722
|
+
* @tags Files
|
|
723
|
+
* @name FileV2ControllerFindAllPaginated
|
|
724
|
+
* @summary List Files with pagination
|
|
725
|
+
* @request GET:/v2/file
|
|
726
|
+
* @secure
|
|
727
|
+
*/
|
|
728
|
+
fileV2ControllerFindAllPaginated: (query, params = {}) => this.request({
|
|
729
|
+
path: `/v2/file`,
|
|
730
|
+
method: "GET",
|
|
731
|
+
query: query,
|
|
732
|
+
secure: true,
|
|
733
|
+
format: "json",
|
|
734
|
+
...params,
|
|
735
|
+
}),
|
|
683
736
|
/**
|
|
684
737
|
* No description
|
|
685
738
|
*
|
|
@@ -918,6 +971,56 @@ class Api extends HttpClient {
|
|
|
918
971
|
format: "json",
|
|
919
972
|
...params,
|
|
920
973
|
}),
|
|
974
|
+
/**
|
|
975
|
+
* @description Returns the version history for the squad.
|
|
976
|
+
*
|
|
977
|
+
* @tags Squads
|
|
978
|
+
* @name SquadControllerFindAllVersions
|
|
979
|
+
* @summary List Squad Versions
|
|
980
|
+
* @request GET:/squad/{id}/versions
|
|
981
|
+
* @secure
|
|
982
|
+
*/
|
|
983
|
+
squadControllerFindAllVersions: (id, query, params = {}) => this.request({
|
|
984
|
+
path: `/squad/${id}/versions`,
|
|
985
|
+
method: "GET",
|
|
986
|
+
query: query,
|
|
987
|
+
secure: true,
|
|
988
|
+
format: "json",
|
|
989
|
+
...params,
|
|
990
|
+
}),
|
|
991
|
+
/**
|
|
992
|
+
* No description
|
|
993
|
+
*
|
|
994
|
+
* @tags Squads
|
|
995
|
+
* @name SquadControllerFindOneVersion
|
|
996
|
+
* @summary Get Squad Version
|
|
997
|
+
* @request GET:/squad/{id}/versions/{version}
|
|
998
|
+
* @secure
|
|
999
|
+
*/
|
|
1000
|
+
squadControllerFindOneVersion: (id, version, query, params = {}) => this.request({
|
|
1001
|
+
path: `/squad/${id}/versions/${version}`,
|
|
1002
|
+
method: "GET",
|
|
1003
|
+
query: query,
|
|
1004
|
+
secure: true,
|
|
1005
|
+
format: "json",
|
|
1006
|
+
...params,
|
|
1007
|
+
}),
|
|
1008
|
+
/**
|
|
1009
|
+
* @description Deletes the selected version and rolls the squad back to the highest remaining live version. Deleting a version that is already deleted returns 200 with that version.
|
|
1010
|
+
*
|
|
1011
|
+
* @tags Squads
|
|
1012
|
+
* @name SquadControllerSoftDeleteVersion
|
|
1013
|
+
* @summary Delete Squad Version
|
|
1014
|
+
* @request DELETE:/squad/{id}/versions/{version}
|
|
1015
|
+
* @secure
|
|
1016
|
+
*/
|
|
1017
|
+
squadControllerSoftDeleteVersion: (id, version, params = {}) => this.request({
|
|
1018
|
+
path: `/squad/${id}/versions/${version}`,
|
|
1019
|
+
method: "DELETE",
|
|
1020
|
+
secure: true,
|
|
1021
|
+
format: "json",
|
|
1022
|
+
...params,
|
|
1023
|
+
}),
|
|
921
1024
|
};
|
|
922
1025
|
workflow = {
|
|
923
1026
|
/**
|
|
@@ -1565,6 +1668,93 @@ class Api extends HttpClient {
|
|
|
1565
1668
|
...params,
|
|
1566
1669
|
}),
|
|
1567
1670
|
};
|
|
1671
|
+
trafficAllocations = {
|
|
1672
|
+
/**
|
|
1673
|
+
* @description Creates a new traffic allocation for an assistant, replacing the one currently in effect. To start or adjust a split, send targets naming published versions (such as "v7") with percentages totaling 100; allocationIntent is inferred as 'explicit'. To stop splitting and send every call to the newest published version, send allocationIntent 'follow-latest' with no targets field; stopping always names its intent, so a dropped targets field can never end a split by accident. Traffic splitting is in beta, rolling out to select organizations; requests from organizations without access receive a 403.
|
|
1674
|
+
*
|
|
1675
|
+
* @tags Traffic Allocations
|
|
1676
|
+
* @name TrafficAllocationControllerCreate
|
|
1677
|
+
* @summary Create traffic allocation
|
|
1678
|
+
* @request POST:/traffic-allocations
|
|
1679
|
+
* @secure
|
|
1680
|
+
*/
|
|
1681
|
+
trafficAllocationControllerCreate: (data, params = {}) => this.request({
|
|
1682
|
+
path: `/traffic-allocations`,
|
|
1683
|
+
method: "POST",
|
|
1684
|
+
body: data,
|
|
1685
|
+
secure: true,
|
|
1686
|
+
type: ContentType.Json,
|
|
1687
|
+
format: "json",
|
|
1688
|
+
...params,
|
|
1689
|
+
}),
|
|
1690
|
+
/**
|
|
1691
|
+
* @description The append-only history of an assistant's allocations, newest first. Traffic splitting is in beta, rolling out to select organizations; requests from organizations without access receive a 403.
|
|
1692
|
+
*
|
|
1693
|
+
* @tags Traffic Allocations
|
|
1694
|
+
* @name TrafficAllocationControllerFindAllPaginated
|
|
1695
|
+
* @summary List traffic allocations
|
|
1696
|
+
* @request GET:/traffic-allocations
|
|
1697
|
+
* @secure
|
|
1698
|
+
*/
|
|
1699
|
+
trafficAllocationControllerFindAllPaginated: (query, params = {}) => this.request({
|
|
1700
|
+
path: `/traffic-allocations`,
|
|
1701
|
+
method: "GET",
|
|
1702
|
+
query: query,
|
|
1703
|
+
secure: true,
|
|
1704
|
+
format: "json",
|
|
1705
|
+
...params,
|
|
1706
|
+
}),
|
|
1707
|
+
/**
|
|
1708
|
+
* @description The allocation currently in effect for the assistant, with its targets. The response carries no allocation field when traffic splitting has never been configured. Traffic splitting is in beta, rolling out to select organizations; requests from organizations without access receive a 403.
|
|
1709
|
+
*
|
|
1710
|
+
* @tags Traffic Allocations
|
|
1711
|
+
* @name TrafficAllocationControllerLatestGet
|
|
1712
|
+
* @summary Get latest traffic allocation
|
|
1713
|
+
* @request GET:/traffic-allocations/latest
|
|
1714
|
+
* @secure
|
|
1715
|
+
*/
|
|
1716
|
+
trafficAllocationControllerLatestGet: (query, params = {}) => this.request({
|
|
1717
|
+
path: `/traffic-allocations/latest`,
|
|
1718
|
+
method: "GET",
|
|
1719
|
+
query: query,
|
|
1720
|
+
secure: true,
|
|
1721
|
+
format: "json",
|
|
1722
|
+
...params,
|
|
1723
|
+
}),
|
|
1724
|
+
/**
|
|
1725
|
+
* @description Per-assistant-version call metrics over a time window: call counts, configured vs actual traffic share, duration, cost per minute, latency, ended reasons, a bucketed timeseries, and optional structured output aggregations. Calls with no assistant version are reported as an 'unversioned' row. Traffic splitting is in beta, rolling out to select organizations; requests from organizations without access receive a 403.
|
|
1726
|
+
*
|
|
1727
|
+
* @tags Traffic Allocations
|
|
1728
|
+
* @name TrafficAllocationControllerMetricsGet
|
|
1729
|
+
* @summary Get traffic split metrics
|
|
1730
|
+
* @request GET:/traffic-allocations/metrics
|
|
1731
|
+
* @secure
|
|
1732
|
+
*/
|
|
1733
|
+
trafficAllocationControllerMetricsGet: (query, params = {}) => this.request({
|
|
1734
|
+
path: `/traffic-allocations/metrics`,
|
|
1735
|
+
method: "GET",
|
|
1736
|
+
query: query,
|
|
1737
|
+
secure: true,
|
|
1738
|
+
format: "json",
|
|
1739
|
+
...params,
|
|
1740
|
+
}),
|
|
1741
|
+
/**
|
|
1742
|
+
* @description Returns a single allocation by id, including its targets and actor attribution. Traffic splitting is in beta, rolling out to select organizations; requests from organizations without access receive a 403.
|
|
1743
|
+
*
|
|
1744
|
+
* @tags Traffic Allocations
|
|
1745
|
+
* @name TrafficAllocationControllerFindOne
|
|
1746
|
+
* @summary Get traffic allocation
|
|
1747
|
+
* @request GET:/traffic-allocations/{id}
|
|
1748
|
+
* @secure
|
|
1749
|
+
*/
|
|
1750
|
+
trafficAllocationControllerFindOne: (id, params = {}) => this.request({
|
|
1751
|
+
path: `/traffic-allocations/${id}`,
|
|
1752
|
+
method: "GET",
|
|
1753
|
+
secure: true,
|
|
1754
|
+
format: "json",
|
|
1755
|
+
...params,
|
|
1756
|
+
}),
|
|
1757
|
+
};
|
|
1568
1758
|
phoneNumber = {
|
|
1569
1759
|
/**
|
|
1570
1760
|
* @description Use POST /phone-number instead.
|
|
@@ -1862,6 +2052,22 @@ class Api extends HttpClient {
|
|
|
1862
2052
|
format: "json",
|
|
1863
2053
|
...params,
|
|
1864
2054
|
}),
|
|
2055
|
+
/**
|
|
2056
|
+
* @description Restores the selected version as the current configuration and publishes it as a new version. Deletes any drafts for this tool.
|
|
2057
|
+
*
|
|
2058
|
+
* @tags Tools
|
|
2059
|
+
* @name ToolControllerToolVersionRestore
|
|
2060
|
+
* @summary Restore Tool Version
|
|
2061
|
+
* @request POST:/tool/{id}/restore/{version}
|
|
2062
|
+
* @secure
|
|
2063
|
+
*/
|
|
2064
|
+
toolControllerToolVersionRestore: (id, version, params = {}) => this.request({
|
|
2065
|
+
path: `/tool/${id}/restore/${version}`,
|
|
2066
|
+
method: "POST",
|
|
2067
|
+
secure: true,
|
|
2068
|
+
format: "json",
|
|
2069
|
+
...params,
|
|
2070
|
+
}),
|
|
1865
2071
|
/**
|
|
1866
2072
|
* No description
|
|
1867
2073
|
*
|
|
@@ -2826,7 +3032,7 @@ class Api extends HttpClient {
|
|
|
2826
3032
|
...params,
|
|
2827
3033
|
}),
|
|
2828
3034
|
/**
|
|
2829
|
-
*
|
|
3035
|
+
* @description Returns a pagination envelope when page, limit, name, assistantId, or squadId is supplied; otherwise returns the legacy array.
|
|
2830
3036
|
*
|
|
2831
3037
|
* @tags Simulation Suites
|
|
2832
3038
|
* @name SimulationSuiteControllerFindAll
|
|
@@ -2842,6 +3048,22 @@ class Api extends HttpClient {
|
|
|
2842
3048
|
format: "json",
|
|
2843
3049
|
...params,
|
|
2844
3050
|
}),
|
|
3051
|
+
/**
|
|
3052
|
+
* No description
|
|
3053
|
+
*
|
|
3054
|
+
* @tags Simulation Suites
|
|
3055
|
+
* @name SimulationSuiteControllerDuplicate
|
|
3056
|
+
* @summary Duplicate simulation suite
|
|
3057
|
+
* @request POST:/eval/simulation/suite/{id}/duplicate
|
|
3058
|
+
* @secure
|
|
3059
|
+
*/
|
|
3060
|
+
simulationSuiteControllerDuplicate: (id, params = {}) => this.request({
|
|
3061
|
+
path: `/eval/simulation/suite/${id}/duplicate`,
|
|
3062
|
+
method: "POST",
|
|
3063
|
+
secure: true,
|
|
3064
|
+
format: "json",
|
|
3065
|
+
...params,
|
|
3066
|
+
}),
|
|
2845
3067
|
/**
|
|
2846
3068
|
* No description
|
|
2847
3069
|
*
|
|
@@ -2945,6 +3167,22 @@ class Api extends HttpClient {
|
|
|
2945
3167
|
format: "json",
|
|
2946
3168
|
...params,
|
|
2947
3169
|
}),
|
|
3170
|
+
/**
|
|
3171
|
+
* No description
|
|
3172
|
+
*
|
|
3173
|
+
* @tags Simulations
|
|
3174
|
+
* @name SimulationControllerGetConcurrency
|
|
3175
|
+
* @summary Get Simulation Concurrency
|
|
3176
|
+
* @request GET:/eval/simulation/concurrency
|
|
3177
|
+
* @secure
|
|
3178
|
+
*/
|
|
3179
|
+
simulationControllerGetConcurrency: (params = {}) => this.request({
|
|
3180
|
+
path: `/eval/simulation/concurrency`,
|
|
3181
|
+
method: "GET",
|
|
3182
|
+
secure: true,
|
|
3183
|
+
format: "json",
|
|
3184
|
+
...params,
|
|
3185
|
+
}),
|
|
2948
3186
|
/**
|
|
2949
3187
|
* No description
|
|
2950
3188
|
*
|
|
@@ -2995,22 +3233,6 @@ class Api extends HttpClient {
|
|
|
2995
3233
|
format: "json",
|
|
2996
3234
|
...params,
|
|
2997
3235
|
}),
|
|
2998
|
-
/**
|
|
2999
|
-
* No description
|
|
3000
|
-
*
|
|
3001
|
-
* @tags Simulations
|
|
3002
|
-
* @name SimulationControllerGetConcurrency
|
|
3003
|
-
* @summary Get Simulation Concurrency
|
|
3004
|
-
* @request GET:/eval/simulation/concurrency
|
|
3005
|
-
* @secure
|
|
3006
|
-
*/
|
|
3007
|
-
simulationControllerGetConcurrency: (params = {}) => this.request({
|
|
3008
|
-
path: `/eval/simulation/concurrency`,
|
|
3009
|
-
method: "GET",
|
|
3010
|
-
secure: true,
|
|
3011
|
-
format: "json",
|
|
3012
|
-
...params,
|
|
3013
|
-
}),
|
|
3014
3236
|
/**
|
|
3015
3237
|
* No description
|
|
3016
3238
|
*
|
|
@@ -3143,6 +3365,7 @@ class Api extends HttpClient {
|
|
|
3143
3365
|
body: data,
|
|
3144
3366
|
secure: true,
|
|
3145
3367
|
type: ContentType.Json,
|
|
3368
|
+
format: "json",
|
|
3146
3369
|
...params,
|
|
3147
3370
|
}),
|
|
3148
3371
|
/**
|
|
@@ -3526,6 +3749,22 @@ class Api extends HttpClient {
|
|
|
3526
3749
|
format: "json",
|
|
3527
3750
|
...params,
|
|
3528
3751
|
}),
|
|
3752
|
+
/**
|
|
3753
|
+
* @description Returns the call concurrency limits and API rate limit usage for the authenticated org. Read-only and cheap enough to poll every few seconds. The X-RateLimit-* response headers describe this endpoint's own polling ceiling, not the API budget reported in apiLimits.
|
|
3754
|
+
*
|
|
3755
|
+
* @tags Orgs
|
|
3756
|
+
* @name OrgControllerLimitsGet
|
|
3757
|
+
* @summary Get org limits
|
|
3758
|
+
* @request GET:/org/limits
|
|
3759
|
+
* @secure
|
|
3760
|
+
*/
|
|
3761
|
+
orgControllerLimitsGet: (params = {}) => this.request({
|
|
3762
|
+
path: `/org/limits`,
|
|
3763
|
+
method: "GET",
|
|
3764
|
+
secure: true,
|
|
3765
|
+
format: "json",
|
|
3766
|
+
...params,
|
|
3767
|
+
}),
|
|
3529
3768
|
/**
|
|
3530
3769
|
* No description
|
|
3531
3770
|
*
|
package/dist/jest.config.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const config = {
|
|
4
4
|
preset: 'ts-jest',
|
|
5
|
+
transform: {
|
|
6
|
+
'^.+\\.tsx?$': ['ts-jest', { tsconfig: { types: ['jest', 'node'] } }],
|
|
7
|
+
},
|
|
5
8
|
testEnvironment: 'node',
|
|
6
9
|
// Your Jest configuration options here
|
|
7
10
|
// For example, to match test files:
|
package/dist/vapi.d.ts
CHANGED
|
@@ -70,6 +70,8 @@ type VapiEventListeners = {
|
|
|
70
70
|
'recording-upload-completed': (event: DailyEventObjectNoPayload) => void;
|
|
71
71
|
};
|
|
72
72
|
type StartCallOptions = {
|
|
73
|
+
/** Acquire the microphone before creating a backend call. Defaults to true. */
|
|
74
|
+
microphonePreflightEnabled?: boolean;
|
|
73
75
|
/**
|
|
74
76
|
* This determines whether the daily room will be deleted and all participants will be kicked once the user leaves the room.
|
|
75
77
|
* If set to `false`, the room will be kept alive even after the user leaves, allowing clients to reconnect to the same room.
|
|
@@ -138,6 +140,9 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
138
140
|
private speakingTimeout;
|
|
139
141
|
private dailyCallConfig;
|
|
140
142
|
private dailyCallObject;
|
|
143
|
+
private microphoneTrack;
|
|
144
|
+
private cancelMicrophonePreflight;
|
|
145
|
+
private microphoneAcquire;
|
|
141
146
|
private hasEmittedCallEndedStatus;
|
|
142
147
|
constructor(apiToken: string, apiBaseUrl?: string, dailyCallConfig?: Pick<DailyAdvancedConfig, 'avoidEval' | 'alwaysIncludeMicInPermissionPrompt'>, dailyCallObject?: Pick<DailyFactoryOptions, 'audioSource' | 'startAudioOff'>);
|
|
143
148
|
private cleanup;
|
package/dist/vapi.js
CHANGED
|
@@ -134,6 +134,39 @@ class Vapi extends VapiEventEmitter {
|
|
|
134
134
|
speakingTimeout = null;
|
|
135
135
|
dailyCallConfig = {};
|
|
136
136
|
dailyCallObject = {};
|
|
137
|
+
microphoneTrack = null;
|
|
138
|
+
cancelMicrophonePreflight = null;
|
|
139
|
+
async microphoneAcquire() {
|
|
140
|
+
const source = this.dailyCallObject.audioSource;
|
|
141
|
+
if (source && typeof source === 'object') {
|
|
142
|
+
if (source.readyState === 'ended') {
|
|
143
|
+
throw new DOMException('The supplied microphone track has ended.', 'NotReadableError');
|
|
144
|
+
}
|
|
145
|
+
return source;
|
|
146
|
+
}
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
let cancelled = false;
|
|
149
|
+
this.cancelMicrophonePreflight = () => {
|
|
150
|
+
cancelled = true;
|
|
151
|
+
reject(new DOMException('Microphone preflight cancelled.', 'AbortError'));
|
|
152
|
+
};
|
|
153
|
+
navigator.mediaDevices.getUserMedia({
|
|
154
|
+
audio: typeof source === 'string' ? { deviceId: { exact: source } } : true,
|
|
155
|
+
}).then((stream) => {
|
|
156
|
+
const track = stream.getAudioTracks()[0];
|
|
157
|
+
if (cancelled || !track || track.readyState === 'ended') {
|
|
158
|
+
stream.getTracks().forEach((item) => item.stop());
|
|
159
|
+
reject(new DOMException('No live microphone track available.', 'NotFoundError'));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
stream.getTracks().filter((item) => item !== track).forEach((item) => item.stop());
|
|
163
|
+
this.microphoneTrack = track;
|
|
164
|
+
resolve(track);
|
|
165
|
+
}, reject);
|
|
166
|
+
}).finally(() => {
|
|
167
|
+
this.cancelMicrophonePreflight = null;
|
|
168
|
+
});
|
|
169
|
+
}
|
|
137
170
|
hasEmittedCallEndedStatus = false;
|
|
138
171
|
constructor(apiToken, apiBaseUrl, dailyCallConfig, dailyCallObject) {
|
|
139
172
|
super();
|
|
@@ -143,6 +176,8 @@ class Vapi extends VapiEventEmitter {
|
|
|
143
176
|
this.dailyCallObject = (0, daily_guards_1.createSafeDailyFactoryOptions)(dailyCallObject);
|
|
144
177
|
}
|
|
145
178
|
async cleanup() {
|
|
179
|
+
this.microphoneTrack?.stop();
|
|
180
|
+
this.microphoneTrack = null;
|
|
146
181
|
this.started = false;
|
|
147
182
|
this.hasEmittedCallEndedStatus = false;
|
|
148
183
|
if (this.call) {
|
|
@@ -243,7 +278,16 @@ class Vapi extends VapiEventEmitter {
|
|
|
243
278
|
}
|
|
244
279
|
});
|
|
245
280
|
this.started = true;
|
|
281
|
+
let failureStage = 'microphone-preflight';
|
|
246
282
|
try {
|
|
283
|
+
if (this.call) {
|
|
284
|
+
await this.cleanup();
|
|
285
|
+
this.started = true;
|
|
286
|
+
}
|
|
287
|
+
const audioSource = options?.microphonePreflightEnabled === false
|
|
288
|
+
? this.dailyCallObject.audioSource ?? true
|
|
289
|
+
: await this.microphoneAcquire();
|
|
290
|
+
failureStage = 'unknown';
|
|
247
291
|
// Stage 1: Create web call
|
|
248
292
|
this.emit('call-start-progress', {
|
|
249
293
|
stage: 'web-call-creation',
|
|
@@ -274,15 +318,6 @@ class Vapi extends VapiEventEmitter {
|
|
|
274
318
|
voiceProvider: webCall?.assistant?.voice?.provider || 'unknown'
|
|
275
319
|
}
|
|
276
320
|
});
|
|
277
|
-
if (this.call) {
|
|
278
|
-
this.emit('call-start-progress', {
|
|
279
|
-
stage: 'daily-call-object-creation',
|
|
280
|
-
status: 'started',
|
|
281
|
-
timestamp: new Date().toISOString(),
|
|
282
|
-
metadata: { action: 'cleanup-existing' }
|
|
283
|
-
});
|
|
284
|
-
await this.cleanup();
|
|
285
|
-
}
|
|
286
321
|
const isVideoRecordingEnabled = webCall?.artifactPlan?.videoRecordingEnabled ?? false;
|
|
287
322
|
const isVideoEnabled = webCall?.assistant?.voice?.provider === 'tavus';
|
|
288
323
|
// When video recording is enabled, the server sends a Daily meeting
|
|
@@ -305,7 +340,7 @@ class Vapi extends VapiEventEmitter {
|
|
|
305
340
|
const dailyCallStartTime = Date.now();
|
|
306
341
|
try {
|
|
307
342
|
this.call = daily_js_1.default.createCallObject({
|
|
308
|
-
audioSource
|
|
343
|
+
audioSource,
|
|
309
344
|
videoSource: this.dailyCallObject.videoSource ?? isVideoRecordingEnabled,
|
|
310
345
|
dailyConfig: this.dailyCallConfig,
|
|
311
346
|
});
|
|
@@ -693,8 +728,9 @@ class Vapi extends VapiEventEmitter {
|
|
|
693
728
|
catch (e) {
|
|
694
729
|
const totalDuration = Date.now() - startTime;
|
|
695
730
|
const serializedError = serializeError(e);
|
|
731
|
+
await this.cleanup();
|
|
696
732
|
this.emit('call-start-failed', {
|
|
697
|
-
stage:
|
|
733
|
+
stage: failureStage,
|
|
698
734
|
totalDuration,
|
|
699
735
|
error: serializedError.message,
|
|
700
736
|
errorStack: serializedError.stack || 'No stack trace available',
|
|
@@ -708,8 +744,10 @@ class Vapi extends VapiEventEmitter {
|
|
|
708
744
|
});
|
|
709
745
|
// Also emit the generic error event for backward compatibility
|
|
710
746
|
this.emit('error', {
|
|
711
|
-
type: '
|
|
712
|
-
|
|
747
|
+
type: failureStage === 'microphone-preflight'
|
|
748
|
+
? `microphone-preflight-${e && typeof e === 'object' && 'name' in e ? e.name : 'error'}`
|
|
749
|
+
: 'start-method-error',
|
|
750
|
+
stage: failureStage,
|
|
713
751
|
error: serializedError,
|
|
714
752
|
totalDuration,
|
|
715
753
|
timestamp: new Date().toISOString(),
|
|
@@ -720,7 +758,6 @@ class Vapi extends VapiEventEmitter {
|
|
|
720
758
|
isMobile: this.isMobileDevice()
|
|
721
759
|
}
|
|
722
760
|
});
|
|
723
|
-
await this.cleanup();
|
|
724
761
|
return null;
|
|
725
762
|
}
|
|
726
763
|
}
|
|
@@ -778,6 +815,12 @@ class Vapi extends VapiEventEmitter {
|
|
|
778
815
|
* If `roomDeleteOnUserLeaveEnabled` is set to `true`, the Vapi call will also be destroyed, preventing any reconnections.
|
|
779
816
|
*/
|
|
780
817
|
async stop() {
|
|
818
|
+
if (this.cancelMicrophonePreflight) {
|
|
819
|
+
this.cancelMicrophonePreflight();
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
this.microphoneTrack?.stop();
|
|
823
|
+
this.microphoneTrack = null;
|
|
781
824
|
this.started = false;
|
|
782
825
|
if (this.call) {
|
|
783
826
|
await this.call.destroy();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vapi-ai/web",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/vapi.js",
|
|
6
6
|
"types": "dist/vapi.d.ts",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
},
|
|
29
29
|
"homepage": "https://github.com/VapiAI/client-sdk-web#readme",
|
|
30
30
|
"engines": {
|
|
31
|
-
"node": ">=
|
|
31
|
+
"node": ">=22.14.0"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@daily-co/daily-js": "^0.
|
|
34
|
+
"@daily-co/daily-js": "^0.94.0",
|
|
35
35
|
"events": "^3.3.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
package/vapi.ts
CHANGED
|
@@ -206,6 +206,8 @@ type VapiEventListeners = {
|
|
|
206
206
|
};
|
|
207
207
|
|
|
208
208
|
type StartCallOptions = {
|
|
209
|
+
/** Acquire the microphone before creating a backend call. Defaults to true. */
|
|
210
|
+
microphonePreflightEnabled?: boolean;
|
|
209
211
|
/**
|
|
210
212
|
* This determines whether the daily room will be deleted and all participants will be kicked once the user leaves the room.
|
|
211
213
|
* If set to `false`, the room will be kept alive even after the user leaves, allowing clients to reconnect to the same room.
|
|
@@ -356,6 +358,40 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
356
358
|
private speakingTimeout: NodeJS.Timeout | null = null;
|
|
357
359
|
private dailyCallConfig: DailyAdvancedConfig = {};
|
|
358
360
|
private dailyCallObject: DailyFactoryOptions = {};
|
|
361
|
+
private microphoneTrack: MediaStreamTrack | null = null;
|
|
362
|
+
private cancelMicrophonePreflight: (() => void) | null = null;
|
|
363
|
+
|
|
364
|
+
private async microphoneAcquire(): Promise<MediaStreamTrack> {
|
|
365
|
+
const source = this.dailyCallObject.audioSource;
|
|
366
|
+
if (source && typeof source === 'object') {
|
|
367
|
+
if (source.readyState === 'ended') {
|
|
368
|
+
throw new DOMException('The supplied microphone track has ended.', 'NotReadableError');
|
|
369
|
+
}
|
|
370
|
+
return source;
|
|
371
|
+
}
|
|
372
|
+
return new Promise<MediaStreamTrack>((resolve, reject) => {
|
|
373
|
+
let cancelled = false;
|
|
374
|
+
this.cancelMicrophonePreflight = () => {
|
|
375
|
+
cancelled = true;
|
|
376
|
+
reject(new DOMException('Microphone preflight cancelled.', 'AbortError'));
|
|
377
|
+
};
|
|
378
|
+
navigator.mediaDevices.getUserMedia({
|
|
379
|
+
audio: typeof source === 'string' ? { deviceId: { exact: source } } : true,
|
|
380
|
+
}).then((stream) => {
|
|
381
|
+
const track = stream.getAudioTracks()[0];
|
|
382
|
+
if (cancelled || !track || track.readyState === 'ended') {
|
|
383
|
+
stream.getTracks().forEach((item) => item.stop());
|
|
384
|
+
reject(new DOMException('No live microphone track available.', 'NotFoundError'));
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
stream.getTracks().filter((item) => item !== track).forEach((item) => item.stop());
|
|
388
|
+
this.microphoneTrack = track;
|
|
389
|
+
resolve(track);
|
|
390
|
+
}, reject);
|
|
391
|
+
}).finally(() => {
|
|
392
|
+
this.cancelMicrophonePreflight = null;
|
|
393
|
+
});
|
|
394
|
+
}
|
|
359
395
|
|
|
360
396
|
private hasEmittedCallEndedStatus: boolean = false;
|
|
361
397
|
|
|
@@ -376,6 +412,8 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
376
412
|
}
|
|
377
413
|
|
|
378
414
|
private async cleanup() {
|
|
415
|
+
this.microphoneTrack?.stop();
|
|
416
|
+
this.microphoneTrack = null;
|
|
379
417
|
this.started = false;
|
|
380
418
|
this.hasEmittedCallEndedStatus = false;
|
|
381
419
|
if (this.call) {
|
|
@@ -498,7 +536,16 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
498
536
|
|
|
499
537
|
this.started = true;
|
|
500
538
|
|
|
539
|
+
let failureStage = 'microphone-preflight';
|
|
501
540
|
try {
|
|
541
|
+
if (this.call) {
|
|
542
|
+
await this.cleanup();
|
|
543
|
+
this.started = true;
|
|
544
|
+
}
|
|
545
|
+
const audioSource = options?.microphonePreflightEnabled === false
|
|
546
|
+
? this.dailyCallObject.audioSource ?? true
|
|
547
|
+
: await this.microphoneAcquire();
|
|
548
|
+
failureStage = 'unknown';
|
|
502
549
|
// Stage 1: Create web call
|
|
503
550
|
this.emit('call-start-progress', {
|
|
504
551
|
stage: 'web-call-creation',
|
|
@@ -535,16 +582,6 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
535
582
|
}
|
|
536
583
|
});
|
|
537
584
|
|
|
538
|
-
if (this.call) {
|
|
539
|
-
this.emit('call-start-progress', {
|
|
540
|
-
stage: 'daily-call-object-creation',
|
|
541
|
-
status: 'started',
|
|
542
|
-
timestamp: new Date().toISOString(),
|
|
543
|
-
metadata: { action: 'cleanup-existing' }
|
|
544
|
-
});
|
|
545
|
-
await this.cleanup();
|
|
546
|
-
}
|
|
547
|
-
|
|
548
585
|
const isVideoRecordingEnabled =
|
|
549
586
|
webCall?.artifactPlan?.videoRecordingEnabled ?? false;
|
|
550
587
|
|
|
@@ -575,7 +612,7 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
575
612
|
|
|
576
613
|
try {
|
|
577
614
|
this.call = DailyIframe.createCallObject({
|
|
578
|
-
audioSource
|
|
615
|
+
audioSource,
|
|
579
616
|
videoSource: this.dailyCallObject.videoSource ?? isVideoRecordingEnabled,
|
|
580
617
|
dailyConfig: this.dailyCallConfig,
|
|
581
618
|
});
|
|
@@ -994,9 +1031,10 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
994
1031
|
} catch (e) {
|
|
995
1032
|
const totalDuration = Date.now() - startTime;
|
|
996
1033
|
const serializedError = serializeError(e);
|
|
1034
|
+
await this.cleanup();
|
|
997
1035
|
|
|
998
1036
|
this.emit('call-start-failed', {
|
|
999
|
-
stage:
|
|
1037
|
+
stage: failureStage,
|
|
1000
1038
|
totalDuration,
|
|
1001
1039
|
error: serializedError.message,
|
|
1002
1040
|
errorStack: serializedError.stack || 'No stack trace available',
|
|
@@ -1011,8 +1049,10 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1011
1049
|
|
|
1012
1050
|
// Also emit the generic error event for backward compatibility
|
|
1013
1051
|
this.emit('error', {
|
|
1014
|
-
type: '
|
|
1015
|
-
|
|
1052
|
+
type: failureStage === 'microphone-preflight'
|
|
1053
|
+
? `microphone-preflight-${e && typeof e === 'object' && 'name' in e ? e.name : 'error'}`
|
|
1054
|
+
: 'start-method-error',
|
|
1055
|
+
stage: failureStage,
|
|
1016
1056
|
error: serializedError,
|
|
1017
1057
|
totalDuration,
|
|
1018
1058
|
timestamp: new Date().toISOString(),
|
|
@@ -1024,7 +1064,6 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1024
1064
|
}
|
|
1025
1065
|
});
|
|
1026
1066
|
|
|
1027
|
-
await this.cleanup();
|
|
1028
1067
|
return null;
|
|
1029
1068
|
}
|
|
1030
1069
|
}
|
|
@@ -1092,6 +1131,12 @@ export default class Vapi extends VapiEventEmitter {
|
|
|
1092
1131
|
* If `roomDeleteOnUserLeaveEnabled` is set to `true`, the Vapi call will also be destroyed, preventing any reconnections.
|
|
1093
1132
|
*/
|
|
1094
1133
|
async stop(): Promise<void> {
|
|
1134
|
+
if (this.cancelMicrophonePreflight) {
|
|
1135
|
+
this.cancelMicrophonePreflight();
|
|
1136
|
+
return;
|
|
1137
|
+
}
|
|
1138
|
+
this.microphoneTrack?.stop();
|
|
1139
|
+
this.microphoneTrack = null;
|
|
1095
1140
|
this.started = false;
|
|
1096
1141
|
if (this.call) {
|
|
1097
1142
|
await this.call.destroy();
|