@voice-ai-labs/web-sdk 0.3.0 → 0.5.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/dist/index.esm.js CHANGED
@@ -4130,6 +4130,12 @@ const ParticipantInfo = /* @__PURE__ */proto3.makeMessageType("livekit.Participa
4130
4130
  kind: "enum",
4131
4131
  T: proto3.getEnumType(ParticipantInfo_KindDetail),
4132
4132
  repeated: true
4133
+ }, {
4134
+ no: 19,
4135
+ name: "data_tracks",
4136
+ kind: "message",
4137
+ T: DataTrackInfo,
4138
+ repeated: true
4133
4139
  }]);
4134
4140
  const ParticipantInfo_State = /* @__PURE__ */proto3.makeEnum("livekit.ParticipantInfo.State", [{
4135
4141
  no: 0,
@@ -4162,6 +4168,9 @@ const ParticipantInfo_Kind = /* @__PURE__ */proto3.makeEnum("livekit.Participant
4162
4168
  }, {
4163
4169
  no: 7,
4164
4170
  name: "CONNECTOR"
4171
+ }, {
4172
+ no: 8,
4173
+ name: "BRIDGE"
4165
4174
  }]);
4166
4175
  const ParticipantInfo_KindDetail = /* @__PURE__ */proto3.makeEnum("livekit.ParticipantInfo.KindDetail", [{
4167
4176
  no: 0,
@@ -4169,6 +4178,15 @@ const ParticipantInfo_KindDetail = /* @__PURE__ */proto3.makeEnum("livekit.Parti
4169
4178
  }, {
4170
4179
  no: 1,
4171
4180
  name: "FORWARDED"
4181
+ }, {
4182
+ no: 2,
4183
+ name: "CONNECTOR_WHATSAPP"
4184
+ }, {
4185
+ no: 3,
4186
+ name: "CONNECTOR_TWILIO"
4187
+ }, {
4188
+ no: 4,
4189
+ name: "BRIDGE_RTSP"
4172
4190
  }]);
4173
4191
  const Encryption_Type = /* @__PURE__ */proto3.makeEnum("livekit.Encryption.Type", [{
4174
4192
  no: 0,
@@ -4332,6 +4350,37 @@ const TrackInfo = /* @__PURE__ */proto3.makeMessageType("livekit.TrackInfo", ()
4332
4350
  kind: "enum",
4333
4351
  T: proto3.getEnumType(BackupCodecPolicy$1)
4334
4352
  }]);
4353
+ const DataTrackInfo = /* @__PURE__ */proto3.makeMessageType("livekit.DataTrackInfo", () => [{
4354
+ no: 1,
4355
+ name: "pub_handle",
4356
+ kind: "scalar",
4357
+ T: 13
4358
+ /* ScalarType.UINT32 */
4359
+ }, {
4360
+ no: 2,
4361
+ name: "sid",
4362
+ kind: "scalar",
4363
+ T: 9
4364
+ /* ScalarType.STRING */
4365
+ }, {
4366
+ no: 3,
4367
+ name: "name",
4368
+ kind: "scalar",
4369
+ T: 9
4370
+ /* ScalarType.STRING */
4371
+ }, {
4372
+ no: 4,
4373
+ name: "encryption",
4374
+ kind: "enum",
4375
+ T: proto3.getEnumType(Encryption_Type)
4376
+ }]);
4377
+ const DataTrackSubscriptionOptions = /* @__PURE__ */proto3.makeMessageType("livekit.DataTrackSubscriptionOptions", () => [{
4378
+ no: 1,
4379
+ name: "target_fps",
4380
+ kind: "scalar",
4381
+ T: 13,
4382
+ opt: true
4383
+ }]);
4335
4384
  const VideoLayer = /* @__PURE__ */proto3.makeMessageType("livekit.VideoLayer", () => [{
4336
4385
  no: 1,
4337
4386
  name: "quality",
@@ -4373,6 +4422,12 @@ const VideoLayer = /* @__PURE__ */proto3.makeMessageType("livekit.VideoLayer", (
4373
4422
  kind: "scalar",
4374
4423
  T: 9
4375
4424
  /* ScalarType.STRING */
4425
+ }, {
4426
+ no: 8,
4427
+ name: "repair_ssrc",
4428
+ kind: "scalar",
4429
+ T: 13
4430
+ /* ScalarType.UINT32 */
4376
4431
  }]);
4377
4432
  const VideoLayer_Mode = /* @__PURE__ */proto3.makeEnum("livekit.VideoLayer.Mode", [{
4378
4433
  no: 0,
@@ -5387,6 +5442,24 @@ const SignalRequest = /* @__PURE__ */proto3.makeMessageType("livekit.SignalReque
5387
5442
  kind: "message",
5388
5443
  T: UpdateLocalVideoTrack,
5389
5444
  oneof: "message"
5445
+ }, {
5446
+ no: 19,
5447
+ name: "publish_data_track_request",
5448
+ kind: "message",
5449
+ T: PublishDataTrackRequest,
5450
+ oneof: "message"
5451
+ }, {
5452
+ no: 20,
5453
+ name: "unpublish_data_track_request",
5454
+ kind: "message",
5455
+ T: UnpublishDataTrackRequest,
5456
+ oneof: "message"
5457
+ }, {
5458
+ no: 21,
5459
+ name: "update_data_subscription",
5460
+ kind: "message",
5461
+ T: UpdateDataSubscription,
5462
+ oneof: "message"
5390
5463
  }]);
5391
5464
  const SignalResponse = /* @__PURE__ */proto3.makeMessageType("livekit.SignalResponse", () => [{
5392
5465
  no: 1,
@@ -5538,6 +5611,24 @@ const SignalResponse = /* @__PURE__ */proto3.makeMessageType("livekit.SignalResp
5538
5611
  kind: "message",
5539
5612
  T: SubscribedAudioCodecUpdate,
5540
5613
  oneof: "message"
5614
+ }, {
5615
+ no: 27,
5616
+ name: "publish_data_track_response",
5617
+ kind: "message",
5618
+ T: PublishDataTrackResponse,
5619
+ oneof: "message"
5620
+ }, {
5621
+ no: 28,
5622
+ name: "unpublish_data_track_response",
5623
+ kind: "message",
5624
+ T: UnpublishDataTrackResponse,
5625
+ oneof: "message"
5626
+ }, {
5627
+ no: 29,
5628
+ name: "data_track_subscriber_handles",
5629
+ kind: "message",
5630
+ T: DataTrackSubscriberHandles,
5631
+ oneof: "message"
5541
5632
  }]);
5542
5633
  const SimulcastCodec = /* @__PURE__ */proto3.makeMessageType("livekit.SimulcastCodec", () => [{
5543
5634
  no: 1,
@@ -5662,6 +5753,74 @@ const AddTrackRequest = /* @__PURE__ */proto3.makeMessageType("livekit.AddTrackR
5662
5753
  T: proto3.getEnumType(AudioTrackFeature),
5663
5754
  repeated: true
5664
5755
  }]);
5756
+ const PublishDataTrackRequest = /* @__PURE__ */proto3.makeMessageType("livekit.PublishDataTrackRequest", () => [{
5757
+ no: 1,
5758
+ name: "pub_handle",
5759
+ kind: "scalar",
5760
+ T: 13
5761
+ /* ScalarType.UINT32 */
5762
+ }, {
5763
+ no: 2,
5764
+ name: "name",
5765
+ kind: "scalar",
5766
+ T: 9
5767
+ /* ScalarType.STRING */
5768
+ }, {
5769
+ no: 3,
5770
+ name: "encryption",
5771
+ kind: "enum",
5772
+ T: proto3.getEnumType(Encryption_Type)
5773
+ }]);
5774
+ const PublishDataTrackResponse = /* @__PURE__ */proto3.makeMessageType("livekit.PublishDataTrackResponse", () => [{
5775
+ no: 1,
5776
+ name: "info",
5777
+ kind: "message",
5778
+ T: DataTrackInfo
5779
+ }]);
5780
+ const UnpublishDataTrackRequest = /* @__PURE__ */proto3.makeMessageType("livekit.UnpublishDataTrackRequest", () => [{
5781
+ no: 1,
5782
+ name: "pub_handle",
5783
+ kind: "scalar",
5784
+ T: 13
5785
+ /* ScalarType.UINT32 */
5786
+ }]);
5787
+ const UnpublishDataTrackResponse = /* @__PURE__ */proto3.makeMessageType("livekit.UnpublishDataTrackResponse", () => [{
5788
+ no: 1,
5789
+ name: "info",
5790
+ kind: "message",
5791
+ T: DataTrackInfo
5792
+ }]);
5793
+ const DataTrackSubscriberHandles = /* @__PURE__ */proto3.makeMessageType("livekit.DataTrackSubscriberHandles", () => [{
5794
+ no: 1,
5795
+ name: "sub_handles",
5796
+ kind: "map",
5797
+ K: 13,
5798
+ V: {
5799
+ kind: "message",
5800
+ T: DataTrackSubscriberHandles_PublishedDataTrack
5801
+ }
5802
+ }]);
5803
+ const DataTrackSubscriberHandles_PublishedDataTrack = /* @__PURE__ */proto3.makeMessageType("livekit.DataTrackSubscriberHandles.PublishedDataTrack", () => [{
5804
+ no: 1,
5805
+ name: "publisher_identity",
5806
+ kind: "scalar",
5807
+ T: 9
5808
+ /* ScalarType.STRING */
5809
+ }, {
5810
+ no: 2,
5811
+ name: "publisher_sid",
5812
+ kind: "scalar",
5813
+ T: 9
5814
+ /* ScalarType.STRING */
5815
+ }, {
5816
+ no: 3,
5817
+ name: "track_sid",
5818
+ kind: "scalar",
5819
+ T: 9
5820
+ /* ScalarType.STRING */
5821
+ }], {
5822
+ localName: "DataTrackSubscriberHandles_PublishedDataTrack"
5823
+ });
5665
5824
  const TrickleRequest = /* @__PURE__ */proto3.makeMessageType("livekit.TrickleRequest", () => [{
5666
5825
  no: 1,
5667
5826
  name: "candidateInit",
@@ -5877,6 +6036,33 @@ const UpdateSubscription = /* @__PURE__ */proto3.makeMessageType("livekit.Update
5877
6036
  T: ParticipantTracks,
5878
6037
  repeated: true
5879
6038
  }]);
6039
+ const UpdateDataSubscription = /* @__PURE__ */proto3.makeMessageType("livekit.UpdateDataSubscription", () => [{
6040
+ no: 1,
6041
+ name: "updates",
6042
+ kind: "message",
6043
+ T: UpdateDataSubscription_Update,
6044
+ repeated: true
6045
+ }]);
6046
+ const UpdateDataSubscription_Update = /* @__PURE__ */proto3.makeMessageType("livekit.UpdateDataSubscription.Update", () => [{
6047
+ no: 1,
6048
+ name: "track_sid",
6049
+ kind: "scalar",
6050
+ T: 9
6051
+ /* ScalarType.STRING */
6052
+ }, {
6053
+ no: 2,
6054
+ name: "subscribe",
6055
+ kind: "scalar",
6056
+ T: 8
6057
+ /* ScalarType.BOOL */
6058
+ }, {
6059
+ no: 3,
6060
+ name: "options",
6061
+ kind: "message",
6062
+ T: DataTrackSubscriptionOptions
6063
+ }], {
6064
+ localName: "UpdateDataSubscription_Update"
6065
+ });
5880
6066
  const UpdateTrackSettings = /* @__PURE__ */proto3.makeMessageType("livekit.UpdateTrackSettings", () => [{
5881
6067
  no: 1,
5882
6068
  name: "track_sids",
@@ -6283,6 +6469,12 @@ const SyncState = /* @__PURE__ */proto3.makeMessageType("livekit.SyncState", ()
6283
6469
  kind: "message",
6284
6470
  T: DataChannelReceiveState,
6285
6471
  repeated: true
6472
+ }, {
6473
+ no: 8,
6474
+ name: "publish_data_tracks",
6475
+ kind: "message",
6476
+ T: PublishDataTrackResponse,
6477
+ repeated: true
6286
6478
  }]);
6287
6479
  const DataChannelReceiveState = /* @__PURE__ */proto3.makeMessageType("livekit.DataChannelReceiveState", () => [{
6288
6480
  no: 1,
@@ -6487,6 +6679,18 @@ const RequestResponse = /* @__PURE__ */proto3.makeMessageType("livekit.RequestRe
6487
6679
  kind: "message",
6488
6680
  T: UpdateLocalVideoTrack,
6489
6681
  oneof: "request"
6682
+ }, {
6683
+ no: 10,
6684
+ name: "publish_data_track",
6685
+ kind: "message",
6686
+ T: PublishDataTrackRequest,
6687
+ oneof: "request"
6688
+ }, {
6689
+ no: 11,
6690
+ name: "unpublish_data_track",
6691
+ kind: "message",
6692
+ T: UnpublishDataTrackRequest,
6693
+ oneof: "request"
6490
6694
  }]);
6491
6695
  const RequestResponse_Reason = /* @__PURE__ */proto3.makeEnum("livekit.RequestResponse.Reason", [{
6492
6696
  no: 0,
@@ -6509,6 +6713,18 @@ const RequestResponse_Reason = /* @__PURE__ */proto3.makeEnum("livekit.RequestRe
6509
6713
  }, {
6510
6714
  no: 6,
6511
6715
  name: "UNCLASSIFIED_ERROR"
6716
+ }, {
6717
+ no: 7,
6718
+ name: "INVALID_HANDLE"
6719
+ }, {
6720
+ no: 8,
6721
+ name: "INVALID_NAME"
6722
+ }, {
6723
+ no: 9,
6724
+ name: "DUPLICATE_HANDLE"
6725
+ }, {
6726
+ no: 10,
6727
+ name: "DUPLICATE_NAME"
6512
6728
  }]);
6513
6729
  const TrackSubscribed = /* @__PURE__ */proto3.makeMessageType("livekit.TrackSubscribed", () => [{
6514
6730
  no: 1,
@@ -6541,6 +6757,12 @@ const ConnectionSettings = /* @__PURE__ */proto3.makeMessageType("livekit.Connec
6541
6757
  kind: "scalar",
6542
6758
  T: 8
6543
6759
  /* ScalarType.BOOL */
6760
+ }, {
6761
+ no: 5,
6762
+ name: "auto_subscribe_data_track",
6763
+ kind: "scalar",
6764
+ T: 8,
6765
+ opt: true
6544
6766
  }]);
6545
6767
  const JoinRequest = /* @__PURE__ */proto3.makeMessageType("livekit.JoinRequest", () => [{
6546
6768
  no: 1,
@@ -10391,7 +10613,8 @@ function adapterFactory() {
10391
10613
 
10392
10614
  adapterFactory({
10393
10615
  window: typeof window === 'undefined' ? undefined : window
10394
- });class TypedPromise extends Promise {
10616
+ });var _a, _b;
10617
+ class TypedPromise extends (_b = Promise) {
10395
10618
  // eslint-disable-next-line @typescript-eslint/no-useless-constructor
10396
10619
  constructor(executor) {
10397
10620
  super(executor);
@@ -10408,7 +10631,11 @@ adapterFactory({
10408
10631
  static race(values) {
10409
10632
  return super.race(values);
10410
10633
  }
10411
- }// tiny, simplified version of https://github.com/lancedikson/bowser/blob/master/src/parser-browsers.js
10634
+ }
10635
+ _a = TypedPromise;
10636
+ TypedPromise.resolve = value => {
10637
+ return Reflect.get(_b, "resolve", _a).call(_a, value);
10638
+ };// tiny, simplified version of https://github.com/lancedikson/bowser/blob/master/src/parser-browsers.js
10412
10639
  // reduced to only differentiate Chrome(ium) based browsers / Firefox / Safari
10413
10640
  const commonVersionIdentifier = /version\/(\d+(\.?_?\d+)+)/i;
10414
10641
  let browserDetails;
@@ -10474,14 +10701,23 @@ function getMatch(exp, ua) {
10474
10701
  }
10475
10702
  function getOSVersion(ua) {
10476
10703
  return ua.includes('mac os') ? getMatch(/\(.+?(\d+_\d+(:?_\d+)?)/, ua, 1).replace(/_/g, '.') : undefined;
10477
- }var version$1 = "2.17.0";const version = version$1;
10478
- const protocolVersion = 16;class LivekitError extends Error {
10479
- constructor(code, message) {
10480
- super(message || 'an error has occured');
10704
+ }var version$1 = "2.17.1";const version = version$1;
10705
+ const protocolVersion = 16;/** Base error that all LiveKit specific custom errors inherit from. */
10706
+ class LivekitError extends Error {
10707
+ constructor(code, message, options) {
10708
+ super(message || 'an error has occurred');
10481
10709
  this.name = 'LiveKitError';
10482
10710
  this.code = code;
10711
+ if (typeof (options === null || options === void 0 ? void 0 : options.cause) !== 'undefined') {
10712
+ this.cause = options === null || options === void 0 ? void 0 : options.cause;
10713
+ }
10483
10714
  }
10484
10715
  }
10716
+ /**
10717
+ * LiveKit specific error type representing an error with an associated set of reasons.
10718
+ * Use this to represent an error with multiple different but contextually related variants.
10719
+ * */
10720
+ class LivekitReasonedError extends LivekitError {}
10485
10721
  var ConnectionErrorReason;
10486
10722
  (function (ConnectionErrorReason) {
10487
10723
  ConnectionErrorReason[ConnectionErrorReason["NotAllowed"] = 0] = "NotAllowed";
@@ -10493,7 +10729,7 @@ var ConnectionErrorReason;
10493
10729
  ConnectionErrorReason[ConnectionErrorReason["WebSocket"] = 6] = "WebSocket";
10494
10730
  ConnectionErrorReason[ConnectionErrorReason["ServiceNotFound"] = 7] = "ServiceNotFound";
10495
10731
  })(ConnectionErrorReason || (ConnectionErrorReason = {}));
10496
- class ConnectionError extends LivekitError {
10732
+ class ConnectionError extends LivekitReasonedError {
10497
10733
  constructor(message, reason, status, context) {
10498
10734
  super(1, message);
10499
10735
  this.name = 'ConnectionError';
@@ -10564,7 +10800,7 @@ class PublishTrackError extends LivekitError {
10564
10800
  this.status = status;
10565
10801
  }
10566
10802
  }
10567
- class SignalRequestError extends LivekitError {
10803
+ class SignalRequestError extends LivekitReasonedError {
10568
10804
  constructor(message, reason) {
10569
10805
  super(15, message);
10570
10806
  this.name = 'SignalRequestError';
@@ -10590,7 +10826,7 @@ var DataStreamErrorReason;
10590
10826
  // Encryption type mismatch.
10591
10827
  DataStreamErrorReason[DataStreamErrorReason["EncryptionTypeMismatch"] = 8] = "EncryptionTypeMismatch";
10592
10828
  })(DataStreamErrorReason || (DataStreamErrorReason = {}));
10593
- class DataStreamError extends LivekitError {
10829
+ class DataStreamError extends LivekitReasonedError {
10594
10830
  constructor(message, reason) {
10595
10831
  super(16, message);
10596
10832
  this.name = 'DataStreamError';
@@ -20117,7 +20353,8 @@ class TextStreamReader extends BaseStreamReader {
20117
20353
  topic: streamHeader.topic,
20118
20354
  timestamp: Number(streamHeader.timestamp),
20119
20355
  attributes: streamHeader.attributes,
20120
- encryptionType
20356
+ encryptionType,
20357
+ attachedStreamIds: streamHeader.contentHeader.value.attachedStreamIds
20121
20358
  };
20122
20359
  const stream = new ReadableStream({
20123
20360
  start: controller => {
@@ -20270,7 +20507,8 @@ class OutgoingDataStreamManager {
20270
20507
  topic: (_b = options === null || options === void 0 ? void 0 : options.topic) !== null && _b !== void 0 ? _b : '',
20271
20508
  size: options === null || options === void 0 ? void 0 : options.totalSize,
20272
20509
  attributes: options === null || options === void 0 ? void 0 : options.attributes,
20273
- encryptionType: ((_c = this.engine.e2eeManager) === null || _c === void 0 ? void 0 : _c.isDataChannelEncryptionEnabled) ? Encryption_Type.GCM : Encryption_Type.NONE
20510
+ encryptionType: ((_c = this.engine.e2eeManager) === null || _c === void 0 ? void 0 : _c.isDataChannelEncryptionEnabled) ? Encryption_Type.GCM : Encryption_Type.NONE,
20511
+ attachedStreamIds: options === null || options === void 0 ? void 0 : options.attachedStreamIds
20274
20512
  };
20275
20513
  const header = new DataStream_Header({
20276
20514
  streamId,
@@ -20283,7 +20521,7 @@ class OutgoingDataStreamManager {
20283
20521
  case: 'textHeader',
20284
20522
  value: new DataStream_TextHeader({
20285
20523
  version: options === null || options === void 0 ? void 0 : options.version,
20286
- attachedStreamIds: options === null || options === void 0 ? void 0 : options.attachedStreamIds,
20524
+ attachedStreamIds: info.attachedStreamIds,
20287
20525
  replyToStreamId: options === null || options === void 0 ? void 0 : options.replyToStreamId,
20288
20526
  operationType: (options === null || options === void 0 ? void 0 : options.type) === 'update' ? DataStream_OperationType.UPDATE : DataStream_OperationType.CREATE
20289
20527
  })
@@ -26893,7 +27131,44 @@ class JWSSignatureVerificationFailed extends JOSEError {
26893
27131
  _defineProperty(this, "code", 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED');
26894
27132
  }
26895
27133
  }
26896
- _defineProperty(JWSSignatureVerificationFailed, "code", 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED');
27134
+ _defineProperty(JWSSignatureVerificationFailed, "code", 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED');var DataTrackHandleErrorReason;
27135
+ (function (DataTrackHandleErrorReason) {
27136
+ DataTrackHandleErrorReason[DataTrackHandleErrorReason["Reserved"] = 0] = "Reserved";
27137
+ DataTrackHandleErrorReason[DataTrackHandleErrorReason["TooLarge"] = 1] = "TooLarge";
27138
+ })(DataTrackHandleErrorReason || (DataTrackHandleErrorReason = {}));
27139
+ var DataTrackDeserializeErrorReason;
27140
+ (function (DataTrackDeserializeErrorReason) {
27141
+ DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["TooShort"] = 0] = "TooShort";
27142
+ DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["HeaderOverrun"] = 1] = "HeaderOverrun";
27143
+ DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["MissingExtWords"] = 2] = "MissingExtWords";
27144
+ DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["UnsupportedVersion"] = 3] = "UnsupportedVersion";
27145
+ DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["InvalidHandle"] = 4] = "InvalidHandle";
27146
+ DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["MalformedExt"] = 5] = "MalformedExt";
27147
+ })(DataTrackDeserializeErrorReason || (DataTrackDeserializeErrorReason = {}));
27148
+ var DataTrackSerializeErrorReason;
27149
+ (function (DataTrackSerializeErrorReason) {
27150
+ DataTrackSerializeErrorReason[DataTrackSerializeErrorReason["TooSmallForHeader"] = 0] = "TooSmallForHeader";
27151
+ DataTrackSerializeErrorReason[DataTrackSerializeErrorReason["TooSmallForPayload"] = 1] = "TooSmallForPayload";
27152
+ })(DataTrackSerializeErrorReason || (DataTrackSerializeErrorReason = {}));
27153
+ var DataTrackExtensionTag;
27154
+ (function (DataTrackExtensionTag) {
27155
+ DataTrackExtensionTag[DataTrackExtensionTag["UserTimestamp"] = 2] = "UserTimestamp";
27156
+ DataTrackExtensionTag[DataTrackExtensionTag["E2ee"] = 1] = "E2ee";
27157
+ })(DataTrackExtensionTag || (DataTrackExtensionTag = {}));
27158
+ DataTrackExtensionTag.UserTimestamp;
27159
+ DataTrackExtensionTag.E2ee;
27160
+ /** Marker indicating a packet's position in relation to a frame. */
27161
+ var FrameMarker;
27162
+ (function (FrameMarker) {
27163
+ /** Packet is the first in a frame. */
27164
+ FrameMarker[FrameMarker["Start"] = 0] = "Start";
27165
+ /** Packet is within a frame. */
27166
+ FrameMarker[FrameMarker["Inter"] = 1] = "Inter";
27167
+ /** Packet is the last in a frame. */
27168
+ FrameMarker[FrameMarker["Final"] = 2] = "Final";
27169
+ /** Packet is the only one in a frame. */
27170
+ FrameMarker[FrameMarker["Single"] = 3] = "Single";
27171
+ })(FrameMarker || (FrameMarker = {}));
26897
27172
 
26898
27173
  /**
26899
27174
  * Base HTTP Client for Voice.ai API
@@ -27052,7 +27327,7 @@ class BaseClient {
27052
27327
  /**
27053
27328
  * Perform DELETE request
27054
27329
  */
27055
- async delete(path) {
27330
+ async httpDelete(path) {
27056
27331
  const response = await fetch(`${this.apiUrl}${path}`, {
27057
27332
  method: 'DELETE',
27058
27333
  headers: this.getHeaders(),
@@ -27245,25 +27520,26 @@ class AgentClient extends BaseClient {
27245
27520
  return this.post(`/agent/${encodeURIComponent(agentId)}/pause`);
27246
27521
  }
27247
27522
  /**
27248
- * Delete/disable an agent
27523
+ * Delete an agent
27249
27524
  *
27250
27525
  * An agent must be paused before being deleted.
27251
- * Disabled agents will be automatically deleted after a grace period.
27252
27526
  *
27253
27527
  * @param agentId - The agent ID to delete
27254
27528
  * @returns Delete response
27255
27529
  *
27256
27530
  * @example
27257
27531
  * ```typescript
27258
- * // First pause, then delete
27259
27532
  * await client.agents.pause('agent-123');
27260
- * const result = await client.agents.disable('agent-123');
27261
- * console.log('Deleted:', result.message);
27533
+ * await client.agents.disable('agent-123');
27262
27534
  * ```
27263
27535
  */
27264
27536
  async disable(agentId) {
27265
27537
  return this.post(`/agent/${encodeURIComponent(agentId)}/disable`);
27266
27538
  }
27539
+ /** Alias for {@link disable} */
27540
+ async delete(agentId) {
27541
+ return this.disable(agentId);
27542
+ }
27267
27543
  /**
27268
27544
  * Initialize agent from template
27269
27545
  *
@@ -27333,7 +27609,7 @@ class AgentClient extends BaseClient {
27333
27609
  * ```
27334
27610
  */
27335
27611
  async unassignKnowledgeBase(agentId) {
27336
- await this.delete(`/agent/${encodeURIComponent(agentId)}/knowledge-base`);
27612
+ await this.httpDelete(`/agent/${encodeURIComponent(agentId)}/knowledge-base`);
27337
27613
  }
27338
27614
  }
27339
27615
 
@@ -27552,7 +27828,7 @@ class KnowledgeBaseClient extends BaseClient {
27552
27828
  * ```
27553
27829
  */
27554
27830
  async remove(kbId) {
27555
- await super.delete(`/knowledge-base/${kbId}`);
27831
+ await this.httpDelete(`/knowledge-base/${kbId}`);
27556
27832
  }
27557
27833
  }
27558
27834
 
@@ -27698,6 +27974,264 @@ class PhoneNumberClient extends BaseClient {
27698
27974
  }
27699
27975
  }
27700
27976
 
27977
+ /**
27978
+ * Text-to-Speech (TTS) Client
27979
+ *
27980
+ * Provides methods for:
27981
+ * - Generating speech from text (synchronous and streaming)
27982
+ * - Listing available voices
27983
+ * - Getting voice details
27984
+ * - Cloning voices from audio samples
27985
+ * - Updating and deleting voices
27986
+ */
27987
+ /**
27988
+ * Client for Text-to-Speech operations
27989
+ *
27990
+ * @example
27991
+ * ```typescript
27992
+ * // List available voices
27993
+ * const voices = await client.tts.listVoices();
27994
+ *
27995
+ * // Generate speech
27996
+ * const audio = await client.tts.synthesize({
27997
+ * text: 'Hello world!',
27998
+ * voice_id: 'voice-123',
27999
+ * language: 'en',
28000
+ * });
28001
+ *
28002
+ * // Clone a voice
28003
+ * const cloned = await client.tts.cloneVoice({
28004
+ * file: audioFile,
28005
+ * name: 'My Voice',
28006
+ * language: 'en',
28007
+ * });
28008
+ * ```
28009
+ */
28010
+ class TTSClient extends BaseClient {
28011
+ constructor(config) {
28012
+ super(config);
28013
+ }
28014
+ // ==========================================================================
28015
+ // SPEECH GENERATION
28016
+ // ==========================================================================
28017
+ /**
28018
+ * Generate speech from text (returns complete audio file)
28019
+ *
28020
+ * This is the synchronous endpoint - it blocks until generation completes
28021
+ * and returns the entire audio file as a Blob. For lower latency with
28022
+ * chunked streaming, use {@link synthesizeStream}.
28023
+ *
28024
+ * @param options - Speech generation options
28025
+ * @returns Audio blob in the requested format
28026
+ *
28027
+ * @example
28028
+ * ```typescript
28029
+ * const audio = await client.tts.synthesize({
28030
+ * text: 'Hello, welcome to Voice AI!',
28031
+ * voice_id: 'voice-123',
28032
+ * language: 'en',
28033
+ * audio_format: 'mp3',
28034
+ * });
28035
+ *
28036
+ * // Play in browser
28037
+ * const url = URL.createObjectURL(audio);
28038
+ * new Audio(url).play();
28039
+ *
28040
+ * // Or download
28041
+ * const a = document.createElement('a');
28042
+ * a.href = url;
28043
+ * a.download = 'speech.mp3';
28044
+ * a.click();
28045
+ * ```
28046
+ */
28047
+ async synthesize(options) {
28048
+ return this.postForBlob('/tts/speech', options);
28049
+ }
28050
+ /**
28051
+ * Generate speech from text with HTTP chunked streaming
28052
+ *
28053
+ * Returns a Response object with a readable body stream. Audio chunks
28054
+ * are sent to the client as they are generated, providing lower perceived
28055
+ * latency than {@link synthesize}.
28056
+ *
28057
+ * @param options - Speech generation options
28058
+ * @returns Fetch Response with streaming body
28059
+ *
28060
+ * @example
28061
+ * ```typescript
28062
+ * const response = await client.tts.synthesizeStream({
28063
+ * text: 'Hello, welcome to Voice AI!',
28064
+ * voice_id: 'voice-123',
28065
+ * language: 'en',
28066
+ * });
28067
+ *
28068
+ * // Read chunks as they arrive
28069
+ * const reader = response.body!.getReader();
28070
+ * while (true) {
28071
+ * const { done, value } = await reader.read();
28072
+ * if (done) break;
28073
+ * // Process audio chunk (Uint8Array)
28074
+ * console.log('Received chunk:', value.length, 'bytes');
28075
+ * }
28076
+ *
28077
+ * // Or collect all chunks and create a blob
28078
+ * const response = await client.tts.synthesizeStream({ text: '...', voice_id: '...' });
28079
+ * const blob = await response.blob();
28080
+ * ```
28081
+ */
28082
+ async synthesizeStream(options) {
28083
+ return this.postForStream('/tts/speech/stream', options);
28084
+ }
28085
+ // ==========================================================================
28086
+ // VOICE MANAGEMENT
28087
+ // ==========================================================================
28088
+ /**
28089
+ * List voices available to the current user
28090
+ *
28091
+ * Returns voices owned by the authenticated user plus default/public voices.
28092
+ * Deleted voices are excluded.
28093
+ *
28094
+ * @returns Array of voice objects
28095
+ *
28096
+ * @example
28097
+ * ```typescript
28098
+ * const voices = await client.tts.listVoices();
28099
+ *
28100
+ * for (const voice of voices) {
28101
+ * console.log(`${voice.name} (${voice.voice_id}): ${voice.status}`);
28102
+ * }
28103
+ *
28104
+ * // Filter to only available voices
28105
+ * const available = voices.filter(v => v.status === 'AVAILABLE');
28106
+ * ```
28107
+ */
28108
+ async listVoices() {
28109
+ return this.get('/tts/voices');
28110
+ }
28111
+ /**
28112
+ * Get voice details and status
28113
+ *
28114
+ * Useful for polling the status of a voice clone operation
28115
+ * (PENDING -> PROCESSING -> AVAILABLE).
28116
+ *
28117
+ * Access control:
28118
+ * - PUBLIC voices: readable by any authenticated user
28119
+ * - PRIVATE voices: readable only by owner (returns 404 otherwise)
28120
+ *
28121
+ * @param voiceId - The voice ID
28122
+ * @returns Voice details and status
28123
+ *
28124
+ * @example
28125
+ * ```typescript
28126
+ * const voice = await client.tts.getVoice('voice-123');
28127
+ *
28128
+ * if (voice.status === 'AVAILABLE') {
28129
+ * console.log('Voice is ready to use!');
28130
+ * } else if (voice.status === 'PROCESSING') {
28131
+ * console.log('Voice is still being processed...');
28132
+ * }
28133
+ * ```
28134
+ */
28135
+ async getVoice(voiceId) {
28136
+ return this.get(`/tts/voice/${encodeURIComponent(voiceId)}`);
28137
+ }
28138
+ /**
28139
+ * Clone a voice from a reference audio file
28140
+ *
28141
+ * Accepts an audio file (MP3, WAV, or OGG, max 7.5MB) and creates
28142
+ * a cloned voice. The voice starts in PENDING status and moves to
28143
+ * PROCESSING, then AVAILABLE once ready.
28144
+ *
28145
+ * Use {@link getVoice} to poll the voice status.
28146
+ *
28147
+ * @param options - Clone voice options including audio file
28148
+ * @returns Created voice with ID and initial status (PENDING)
28149
+ *
28150
+ * @example
28151
+ * ```typescript
28152
+ * // From a file input
28153
+ * const fileInput = document.querySelector('input[type="file"]');
28154
+ * const file = fileInput.files[0];
28155
+ *
28156
+ * const voice = await client.tts.cloneVoice({
28157
+ * file: file,
28158
+ * name: 'My Custom Voice',
28159
+ * language: 'en',
28160
+ * voice_visibility: 'PRIVATE',
28161
+ * });
28162
+ *
28163
+ * console.log('Voice ID:', voice.voice_id);
28164
+ * console.log('Status:', voice.status); // 'PENDING'
28165
+ *
28166
+ * // Poll until ready
28167
+ * let status = voice.status;
28168
+ * while (status !== 'AVAILABLE' && status !== 'FAILED') {
28169
+ * await new Promise(r => setTimeout(r, 2000));
28170
+ * const updated = await client.tts.getVoice(voice.voice_id);
28171
+ * status = updated.status;
28172
+ * console.log('Status:', status);
28173
+ * }
28174
+ * ```
28175
+ */
28176
+ async cloneVoice(options) {
28177
+ const formData = new FormData();
28178
+ formData.append('file', options.file);
28179
+ if (options.name !== undefined) {
28180
+ formData.append('name', options.name);
28181
+ }
28182
+ if (options.voice_visibility !== undefined) {
28183
+ formData.append('voice_visibility', options.voice_visibility);
28184
+ }
28185
+ if (options.language !== undefined) {
28186
+ formData.append('language', options.language);
28187
+ }
28188
+ return this.postFormData('/tts/clone-voice', formData);
28189
+ }
28190
+ /**
28191
+ * Update voice metadata (name and/or visibility)
28192
+ *
28193
+ * Only the voice owner can update a voice.
28194
+ *
28195
+ * @param voiceId - The voice ID to update
28196
+ * @param options - Fields to update
28197
+ * @returns Updated voice details
28198
+ *
28199
+ * @example
28200
+ * ```typescript
28201
+ * // Rename a voice
28202
+ * const updated = await client.tts.updateVoice('voice-123', {
28203
+ * name: 'New Voice Name',
28204
+ * });
28205
+ *
28206
+ * // Make a voice private
28207
+ * const updated = await client.tts.updateVoice('voice-123', {
28208
+ * voice_visibility: 'PRIVATE',
28209
+ * });
28210
+ * ```
28211
+ */
28212
+ async updateVoice(voiceId, options) {
28213
+ return this.patch(`/tts/voice/${encodeURIComponent(voiceId)}`, options);
28214
+ }
28215
+ /**
28216
+ * Delete a voice
28217
+ *
28218
+ * Only the voice owner can delete a voice. The voice will no longer
28219
+ * appear in voice listings.
28220
+ *
28221
+ * @param voiceId - The voice ID to delete
28222
+ * @returns Deletion confirmation
28223
+ *
28224
+ * @example
28225
+ * ```typescript
28226
+ * await client.tts.deleteVoice('voice-123');
28227
+ * console.log('Voice deleted');
28228
+ * ```
28229
+ */
28230
+ async deleteVoice(voiceId) {
28231
+ return this.httpDelete(`/tts/voice/${encodeURIComponent(voiceId)}`);
28232
+ }
28233
+ }
28234
+
27701
28235
  /**
27702
28236
  * VoiceAgentWidget - UI widget for Voice.ai
27703
28237
  *
@@ -28154,14 +28688,47 @@ const DEFAULT_API_URL = 'https://dev.voice.ai/api/v1';
28154
28688
  * ```
28155
28689
  */
28156
28690
  class VoiceAI {
28691
+ // ==========================================================================
28692
+ // API CLIENTS (REST API)
28693
+ // ==========================================================================
28694
+ /** Agent management - create, update, deploy, pause, delete agents. */
28695
+ get agents() {
28696
+ if (!this._agents)
28697
+ throw new VoiceAIError('API key required for agents API. Pass apiKey in the constructor.');
28698
+ return this._agents;
28699
+ }
28700
+ /** Analytics - call history, transcripts, stats. */
28701
+ get analytics() {
28702
+ if (!this._analytics)
28703
+ throw new VoiceAIError('API key required for analytics API. Pass apiKey in the constructor.');
28704
+ return this._analytics;
28705
+ }
28706
+ /** Knowledge Base - manage RAG documents. */
28707
+ get knowledgeBase() {
28708
+ if (!this._knowledgeBase)
28709
+ throw new VoiceAIError('API key required for knowledgeBase API. Pass apiKey in the constructor.');
28710
+ return this._knowledgeBase;
28711
+ }
28712
+ /** Phone Numbers - search, select, release phone numbers. */
28713
+ get phoneNumbers() {
28714
+ if (!this._phoneNumbers)
28715
+ throw new VoiceAIError('API key required for phoneNumbers API. Pass apiKey in the constructor.');
28716
+ return this._phoneNumbers;
28717
+ }
28718
+ /** Text-to-Speech - generate speech, manage voices, clone voices. */
28719
+ get tts() {
28720
+ if (!this._tts)
28721
+ throw new VoiceAIError('API key required for tts API. Pass apiKey in the constructor.');
28722
+ return this._tts;
28723
+ }
28157
28724
  /**
28158
28725
  * Create a new VoiceAI client
28159
28726
  *
28160
- * @param config - Configuration options
28161
- * @param config.apiKey - Your Voice.ai API key (required)
28727
+ * @param config - Configuration options (optional for frontend-only usage)
28728
+ * @param config.apiKey - Your Voice.ai API key (required for API operations and `getConnectionDetails`)
28162
28729
  * @param config.apiUrl - Custom API URL (optional, defaults to production)
28163
28730
  */
28164
- constructor(config) {
28731
+ constructor(config = {}) {
28165
28732
  // ==========================================================================
28166
28733
  // PRIVATE STATE (Real-time voice)
28167
28734
  // ==========================================================================
@@ -28173,31 +28740,118 @@ class VoiceAI {
28173
28740
  this.agentStateHandlers = new Set();
28174
28741
  this.audioLevelHandlers = new Set();
28175
28742
  this.microphoneStateHandlers = new Set();
28743
+ this.effectiveApiKey = '';
28176
28744
  this.cachedConnectionDetails = null;
28177
28745
  this.currentAgentState = 'disconnected';
28178
28746
  this.agentParticipantId = null;
28179
28747
  this.audioLevelInterval = null;
28180
- if (!config.apiKey) {
28181
- throw new Error('API key is required. Get one at https://voice.ai');
28182
- }
28183
- this.apiKey = config.apiKey;
28748
+ this.apiKey = config.apiKey || '';
28184
28749
  this.apiUrl = config.apiUrl || DEFAULT_API_URL;
28185
- // Initialize API clients
28186
- const clientConfig = { apiKey: this.apiKey, apiUrl: this.apiUrl };
28187
- this.agents = new AgentClient(clientConfig);
28188
- this.analytics = new AnalyticsClient(clientConfig);
28189
- this.knowledgeBase = new KnowledgeBaseClient(clientConfig);
28190
- this.phoneNumbers = new PhoneNumberClient(clientConfig);
28750
+ // Initialize API clients when apiKey is provided
28751
+ if (this.apiKey) {
28752
+ const clientConfig = { apiKey: this.apiKey, apiUrl: this.apiUrl };
28753
+ this._agents = new AgentClient(clientConfig);
28754
+ this._analytics = new AnalyticsClient(clientConfig);
28755
+ this._knowledgeBase = new KnowledgeBaseClient(clientConfig);
28756
+ this._phoneNumbers = new PhoneNumberClient(clientConfig);
28757
+ this._tts = new TTSClient(clientConfig);
28758
+ }
28191
28759
  }
28192
28760
  // ==========================================================================
28193
28761
  // REAL-TIME VOICE CONNECTION
28194
28762
  // ==========================================================================
28195
28763
  /**
28196
- * Connect to a voice agent for real-time conversation
28764
+ * Get connection details for a voice agent.
28765
+ *
28766
+ * Requires an API key. Call this from your backend, then pass the result
28767
+ * to `connectRoom()` on the frontend.
28768
+ *
28769
+ * @param options - Connection options (agentId, testMode, etc.)
28770
+ * @returns Connection details (serverUrl, participantToken, callId)
28771
+ *
28772
+ * @example
28773
+ * ```typescript
28774
+ * // Server-side: get connection details
28775
+ * const details = await voiceai.getConnectionDetails({ agentId: 'agent-123' });
28776
+ * // Return details to frontend...
28777
+ *
28778
+ * // With test mode
28779
+ * const details = await voiceai.getConnectionDetails({
28780
+ * agentId: 'agent-123',
28781
+ * testMode: true
28782
+ * });
28783
+ * ```
28784
+ */
28785
+ async getConnectionDetails(options) {
28786
+ return this.fetchConnectionDetails(options);
28787
+ }
28788
+ /**
28789
+ * Connect to a LiveKit room using pre-fetched connection details.
28790
+ *
28791
+ * This is the browser-safe method -- it only needs a room token,
28792
+ * no API key required. Get the connection details from your backend
28793
+ * using `getConnectionDetails()`.
28794
+ *
28795
+ * @param connectionDetails - Server URL, participant token, and call ID from your backend
28796
+ * @param options - Audio/microphone options
28797
+ *
28798
+ * @example
28799
+ * ```typescript
28800
+ * // Frontend: connect using token from your backend
28801
+ * const voiceai = new VoiceAI();
28802
+ * await voiceai.connectRoom(
28803
+ * { serverUrl, participantToken, callId },
28804
+ * { autoPublishMic: true }
28805
+ * );
28806
+ * ```
28807
+ */
28808
+ async connectRoom(connectionDetails, options = {}) {
28809
+ if (this.connectionStatus.connecting || this.connectionStatus.connected) {
28810
+ throw new Error('Already connected or connecting');
28811
+ }
28812
+ this.updateStatus({ connecting: true, connected: false });
28813
+ this.cachedConnectionDetails = connectionDetails;
28814
+ try {
28815
+ this.room = new Room();
28816
+ this.setupRoomListeners();
28817
+ this.room.prepareConnection(connectionDetails.serverUrl, connectionDetails.participantToken);
28818
+ const preConnectBuffer = options.preConnectBuffer !== false;
28819
+ let connected = false;
28820
+ let lastError;
28821
+ for (let attempt = 1; attempt <= 3; attempt++) {
28822
+ try {
28823
+ await Promise.all([
28824
+ this.setupAudio(options, preConnectBuffer),
28825
+ this.room.connect(connectionDetails.serverUrl, connectionDetails.participantToken)
28826
+ ]);
28827
+ connected = true;
28828
+ break;
28829
+ }
28830
+ catch (error) {
28831
+ lastError = error;
28832
+ if (attempt < 3) {
28833
+ await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
28834
+ }
28835
+ }
28836
+ }
28837
+ if (!connected) {
28838
+ throw lastError || new Error('Failed to connect after 3 attempts');
28839
+ }
28840
+ this.updateStatus({ connected: true, connecting: false, callId: connectionDetails.callId });
28841
+ }
28842
+ catch (error) {
28843
+ const err = error instanceof Error ? error : new Error(String(error));
28844
+ this.updateStatus({ connected: false, connecting: false, error: err.message });
28845
+ this.emitError(err);
28846
+ throw err;
28847
+ }
28848
+ }
28849
+ /**
28850
+ * Connect to a voice agent for real-time conversation.
28851
+ *
28852
+ * Convenience method that combines `getConnectionDetails()` + `connectRoom()`.
28197
28853
  *
28198
28854
  * @param options - Connection options
28199
- * @param options.agentId - ID of the agent to connect to
28200
- * @param options.autoPublishMic - Auto-enable microphone (default: true)
28201
28855
  *
28202
28856
  * @example
28203
28857
  * ```typescript
@@ -28210,7 +28864,7 @@ class VoiceAI {
28210
28864
  }
28211
28865
  this.updateStatus({ connecting: true, connected: false });
28212
28866
  try {
28213
- // Get connection details from API
28867
+ // Get connection details via the appropriate method
28214
28868
  const connectionDetails = await this.getOrRefreshConnectionDetails(options);
28215
28869
  // Create room instance
28216
28870
  this.room = new Room();
@@ -28250,11 +28904,17 @@ class VoiceAI {
28250
28904
  }
28251
28905
  }
28252
28906
  /**
28253
- * Disconnect from the voice agent and end the call
28907
+ * Disconnect from the room and end the call.
28908
+ *
28909
+ * Signals the server to free the concurrency slot using endToken from
28910
+ * connection details. Connection details always include end_token when
28911
+ * fetched from the API; backends must pass it when using pre-fetched details.
28912
+ * If no endToken, the server detects room disconnect as fallback.
28254
28913
  */
28255
28914
  async disconnect() {
28256
28915
  this.stopAudioLevelMonitoring();
28257
28916
  const callId = this.cachedConnectionDetails?.callId;
28917
+ const endToken = this.cachedConnectionDetails?.endToken;
28258
28918
  if (this.room) {
28259
28919
  try {
28260
28920
  await this.room.disconnect();
@@ -28264,22 +28924,29 @@ class VoiceAI {
28264
28924
  }
28265
28925
  this.room = null;
28266
28926
  }
28267
- // End call via API
28268
- if (callId) {
28927
+ // Signal server to free concurrency slot (endToken only - API always returns it)
28928
+ if (callId && endToken) {
28269
28929
  try {
28270
- await fetch(`${this.apiUrl}/calls/${encodeURIComponent(callId)}/end`, {
28930
+ const response = await fetch(`${this.apiUrl}/calls/${encodeURIComponent(callId)}/end`, {
28271
28931
  method: 'POST',
28272
28932
  headers: {
28273
28933
  'Content-Type': 'application/json',
28274
- 'Authorization': `Bearer ${this.apiKey}`
28934
+ 'Authorization': `Bearer ${endToken}`
28275
28935
  }
28276
28936
  });
28937
+ if (!response.ok) {
28938
+ console.warn(`[VoiceAI] Failed to end call ${callId}: ${response.status} ${response.statusText}`);
28939
+ }
28277
28940
  }
28278
- catch {
28279
- // Non-critical
28941
+ catch (err) {
28942
+ console.warn(`[VoiceAI] Failed to end call ${callId}:`, err);
28280
28943
  }
28281
28944
  }
28945
+ else if (callId && !endToken) {
28946
+ console.warn(`[VoiceAI] No endToken in connection details - pass end_token from your backend to enable immediate teardown`);
28947
+ }
28282
28948
  this.cachedConnectionDetails = null;
28949
+ this.effectiveApiKey = '';
28283
28950
  this.agentParticipantId = null;
28284
28951
  this.updateAgentState('disconnected');
28285
28952
  this.updateStatus({ connected: false, connecting: false });
@@ -28418,16 +29085,52 @@ class VoiceAI {
28418
29085
  }
28419
29086
  }
28420
29087
  async getOrRefreshConnectionDetails(options) {
29088
+ // Mode 1: Pre-fetched connection details provided directly
29089
+ if (options.serverUrl && options.participantToken) {
29090
+ const details = {
29091
+ serverUrl: options.serverUrl,
29092
+ participantToken: options.participantToken,
29093
+ callId: options.callId || '',
29094
+ endToken: options.endToken,
29095
+ };
29096
+ this.cachedConnectionDetails = details;
29097
+ return details;
29098
+ }
29099
+ // Use cached details if still valid
28421
29100
  if (this.cachedConnectionDetails && !this.isTokenExpired(this.cachedConnectionDetails.participantToken)) {
28422
29101
  return this.cachedConnectionDetails;
28423
29102
  }
28424
- const connectionDetails = await this.getConnectionDetails(options);
29103
+ // Mode 2: Direct API call with API key (constructor or per-call)
29104
+ if (!this.apiKey && !options.apiKey) {
29105
+ throw new Error('No authentication method configured. Use one of:\n' +
29106
+ '1. connectRoom() with pre-fetched details from your backend\n' +
29107
+ '2. API key: new VoiceAI({ apiKey: "vk_..." }) or connect({ apiKey: "vk_..." })');
29108
+ }
29109
+ const connectionDetails = await this.fetchConnectionDetails(options);
28425
29110
  this.cachedConnectionDetails = connectionDetails;
28426
29111
  return connectionDetails;
28427
29112
  }
28428
- async getConnectionDetails(options) {
29113
+ /**
29114
+ * Fetch connection details from the developer's backend endpoint.
29115
+ * The backend holds the API key and calls the Voice.AI API server-side.
29116
+ */
29117
+ /**
29118
+ * Fetch connection details using the API key directly.
29119
+ * Used by the public getConnectionDetails() method.
29120
+ */
29121
+ async fetchConnectionDetails(options) {
29122
+ if (!this.apiKey && !options.apiKey) {
29123
+ throw new Error('API key is required for getConnectionDetails(). Pass { apiKey: "vk_..." } to the constructor or options.');
29124
+ }
29125
+ return this.fetchConnectionDetailsFromApi(options);
29126
+ }
29127
+ async fetchConnectionDetailsFromApi(options) {
28429
29128
  const url = options.apiUrl || this.apiUrl;
28430
- const endpoint = `${url}/connection/connection-details`;
29129
+ // Use test-connection-details for testMode (allows testing paused/undeployed agents)
29130
+ const endpointPath = options.testMode
29131
+ ? '/connection/test-connection-details'
29132
+ : '/connection/connection-details';
29133
+ const endpoint = `${url}${endpointPath}`;
28431
29134
  const requestData = {};
28432
29135
  if (options.agentId)
28433
29136
  requestData.agent_id = options.agentId;
@@ -28443,6 +29146,7 @@ class VoiceAI {
28443
29146
  : JSON.stringify(options.environment);
28444
29147
  }
28445
29148
  const apiKey = options.apiKey || this.apiKey;
29149
+ this.effectiveApiKey = apiKey;
28446
29150
  const response = await fetch(endpoint, {
28447
29151
  method: 'POST',
28448
29152
  headers: {
@@ -28468,7 +29172,8 @@ class VoiceAI {
28468
29172
  return {
28469
29173
  serverUrl: data.server_url,
28470
29174
  participantToken: data.participant_token,
28471
- callId: data.call_id
29175
+ callId: data.call_id,
29176
+ endToken: data.end_token,
28472
29177
  };
28473
29178
  }
28474
29179
  async setupAudio(options, preConnectBuffer) {