@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.js CHANGED
@@ -4134,6 +4134,12 @@ const ParticipantInfo = /* @__PURE__ */proto3.makeMessageType("livekit.Participa
4134
4134
  kind: "enum",
4135
4135
  T: proto3.getEnumType(ParticipantInfo_KindDetail),
4136
4136
  repeated: true
4137
+ }, {
4138
+ no: 19,
4139
+ name: "data_tracks",
4140
+ kind: "message",
4141
+ T: DataTrackInfo,
4142
+ repeated: true
4137
4143
  }]);
4138
4144
  const ParticipantInfo_State = /* @__PURE__ */proto3.makeEnum("livekit.ParticipantInfo.State", [{
4139
4145
  no: 0,
@@ -4166,6 +4172,9 @@ const ParticipantInfo_Kind = /* @__PURE__ */proto3.makeEnum("livekit.Participant
4166
4172
  }, {
4167
4173
  no: 7,
4168
4174
  name: "CONNECTOR"
4175
+ }, {
4176
+ no: 8,
4177
+ name: "BRIDGE"
4169
4178
  }]);
4170
4179
  const ParticipantInfo_KindDetail = /* @__PURE__ */proto3.makeEnum("livekit.ParticipantInfo.KindDetail", [{
4171
4180
  no: 0,
@@ -4173,6 +4182,15 @@ const ParticipantInfo_KindDetail = /* @__PURE__ */proto3.makeEnum("livekit.Parti
4173
4182
  }, {
4174
4183
  no: 1,
4175
4184
  name: "FORWARDED"
4185
+ }, {
4186
+ no: 2,
4187
+ name: "CONNECTOR_WHATSAPP"
4188
+ }, {
4189
+ no: 3,
4190
+ name: "CONNECTOR_TWILIO"
4191
+ }, {
4192
+ no: 4,
4193
+ name: "BRIDGE_RTSP"
4176
4194
  }]);
4177
4195
  const Encryption_Type = /* @__PURE__ */proto3.makeEnum("livekit.Encryption.Type", [{
4178
4196
  no: 0,
@@ -4336,6 +4354,37 @@ const TrackInfo = /* @__PURE__ */proto3.makeMessageType("livekit.TrackInfo", ()
4336
4354
  kind: "enum",
4337
4355
  T: proto3.getEnumType(BackupCodecPolicy$1)
4338
4356
  }]);
4357
+ const DataTrackInfo = /* @__PURE__ */proto3.makeMessageType("livekit.DataTrackInfo", () => [{
4358
+ no: 1,
4359
+ name: "pub_handle",
4360
+ kind: "scalar",
4361
+ T: 13
4362
+ /* ScalarType.UINT32 */
4363
+ }, {
4364
+ no: 2,
4365
+ name: "sid",
4366
+ kind: "scalar",
4367
+ T: 9
4368
+ /* ScalarType.STRING */
4369
+ }, {
4370
+ no: 3,
4371
+ name: "name",
4372
+ kind: "scalar",
4373
+ T: 9
4374
+ /* ScalarType.STRING */
4375
+ }, {
4376
+ no: 4,
4377
+ name: "encryption",
4378
+ kind: "enum",
4379
+ T: proto3.getEnumType(Encryption_Type)
4380
+ }]);
4381
+ const DataTrackSubscriptionOptions = /* @__PURE__ */proto3.makeMessageType("livekit.DataTrackSubscriptionOptions", () => [{
4382
+ no: 1,
4383
+ name: "target_fps",
4384
+ kind: "scalar",
4385
+ T: 13,
4386
+ opt: true
4387
+ }]);
4339
4388
  const VideoLayer = /* @__PURE__ */proto3.makeMessageType("livekit.VideoLayer", () => [{
4340
4389
  no: 1,
4341
4390
  name: "quality",
@@ -4377,6 +4426,12 @@ const VideoLayer = /* @__PURE__ */proto3.makeMessageType("livekit.VideoLayer", (
4377
4426
  kind: "scalar",
4378
4427
  T: 9
4379
4428
  /* ScalarType.STRING */
4429
+ }, {
4430
+ no: 8,
4431
+ name: "repair_ssrc",
4432
+ kind: "scalar",
4433
+ T: 13
4434
+ /* ScalarType.UINT32 */
4380
4435
  }]);
4381
4436
  const VideoLayer_Mode = /* @__PURE__ */proto3.makeEnum("livekit.VideoLayer.Mode", [{
4382
4437
  no: 0,
@@ -5391,6 +5446,24 @@ const SignalRequest = /* @__PURE__ */proto3.makeMessageType("livekit.SignalReque
5391
5446
  kind: "message",
5392
5447
  T: UpdateLocalVideoTrack,
5393
5448
  oneof: "message"
5449
+ }, {
5450
+ no: 19,
5451
+ name: "publish_data_track_request",
5452
+ kind: "message",
5453
+ T: PublishDataTrackRequest,
5454
+ oneof: "message"
5455
+ }, {
5456
+ no: 20,
5457
+ name: "unpublish_data_track_request",
5458
+ kind: "message",
5459
+ T: UnpublishDataTrackRequest,
5460
+ oneof: "message"
5461
+ }, {
5462
+ no: 21,
5463
+ name: "update_data_subscription",
5464
+ kind: "message",
5465
+ T: UpdateDataSubscription,
5466
+ oneof: "message"
5394
5467
  }]);
5395
5468
  const SignalResponse = /* @__PURE__ */proto3.makeMessageType("livekit.SignalResponse", () => [{
5396
5469
  no: 1,
@@ -5542,6 +5615,24 @@ const SignalResponse = /* @__PURE__ */proto3.makeMessageType("livekit.SignalResp
5542
5615
  kind: "message",
5543
5616
  T: SubscribedAudioCodecUpdate,
5544
5617
  oneof: "message"
5618
+ }, {
5619
+ no: 27,
5620
+ name: "publish_data_track_response",
5621
+ kind: "message",
5622
+ T: PublishDataTrackResponse,
5623
+ oneof: "message"
5624
+ }, {
5625
+ no: 28,
5626
+ name: "unpublish_data_track_response",
5627
+ kind: "message",
5628
+ T: UnpublishDataTrackResponse,
5629
+ oneof: "message"
5630
+ }, {
5631
+ no: 29,
5632
+ name: "data_track_subscriber_handles",
5633
+ kind: "message",
5634
+ T: DataTrackSubscriberHandles,
5635
+ oneof: "message"
5545
5636
  }]);
5546
5637
  const SimulcastCodec = /* @__PURE__ */proto3.makeMessageType("livekit.SimulcastCodec", () => [{
5547
5638
  no: 1,
@@ -5666,6 +5757,74 @@ const AddTrackRequest = /* @__PURE__ */proto3.makeMessageType("livekit.AddTrackR
5666
5757
  T: proto3.getEnumType(AudioTrackFeature),
5667
5758
  repeated: true
5668
5759
  }]);
5760
+ const PublishDataTrackRequest = /* @__PURE__ */proto3.makeMessageType("livekit.PublishDataTrackRequest", () => [{
5761
+ no: 1,
5762
+ name: "pub_handle",
5763
+ kind: "scalar",
5764
+ T: 13
5765
+ /* ScalarType.UINT32 */
5766
+ }, {
5767
+ no: 2,
5768
+ name: "name",
5769
+ kind: "scalar",
5770
+ T: 9
5771
+ /* ScalarType.STRING */
5772
+ }, {
5773
+ no: 3,
5774
+ name: "encryption",
5775
+ kind: "enum",
5776
+ T: proto3.getEnumType(Encryption_Type)
5777
+ }]);
5778
+ const PublishDataTrackResponse = /* @__PURE__ */proto3.makeMessageType("livekit.PublishDataTrackResponse", () => [{
5779
+ no: 1,
5780
+ name: "info",
5781
+ kind: "message",
5782
+ T: DataTrackInfo
5783
+ }]);
5784
+ const UnpublishDataTrackRequest = /* @__PURE__ */proto3.makeMessageType("livekit.UnpublishDataTrackRequest", () => [{
5785
+ no: 1,
5786
+ name: "pub_handle",
5787
+ kind: "scalar",
5788
+ T: 13
5789
+ /* ScalarType.UINT32 */
5790
+ }]);
5791
+ const UnpublishDataTrackResponse = /* @__PURE__ */proto3.makeMessageType("livekit.UnpublishDataTrackResponse", () => [{
5792
+ no: 1,
5793
+ name: "info",
5794
+ kind: "message",
5795
+ T: DataTrackInfo
5796
+ }]);
5797
+ const DataTrackSubscriberHandles = /* @__PURE__ */proto3.makeMessageType("livekit.DataTrackSubscriberHandles", () => [{
5798
+ no: 1,
5799
+ name: "sub_handles",
5800
+ kind: "map",
5801
+ K: 13,
5802
+ V: {
5803
+ kind: "message",
5804
+ T: DataTrackSubscriberHandles_PublishedDataTrack
5805
+ }
5806
+ }]);
5807
+ const DataTrackSubscriberHandles_PublishedDataTrack = /* @__PURE__ */proto3.makeMessageType("livekit.DataTrackSubscriberHandles.PublishedDataTrack", () => [{
5808
+ no: 1,
5809
+ name: "publisher_identity",
5810
+ kind: "scalar",
5811
+ T: 9
5812
+ /* ScalarType.STRING */
5813
+ }, {
5814
+ no: 2,
5815
+ name: "publisher_sid",
5816
+ kind: "scalar",
5817
+ T: 9
5818
+ /* ScalarType.STRING */
5819
+ }, {
5820
+ no: 3,
5821
+ name: "track_sid",
5822
+ kind: "scalar",
5823
+ T: 9
5824
+ /* ScalarType.STRING */
5825
+ }], {
5826
+ localName: "DataTrackSubscriberHandles_PublishedDataTrack"
5827
+ });
5669
5828
  const TrickleRequest = /* @__PURE__ */proto3.makeMessageType("livekit.TrickleRequest", () => [{
5670
5829
  no: 1,
5671
5830
  name: "candidateInit",
@@ -5881,6 +6040,33 @@ const UpdateSubscription = /* @__PURE__ */proto3.makeMessageType("livekit.Update
5881
6040
  T: ParticipantTracks,
5882
6041
  repeated: true
5883
6042
  }]);
6043
+ const UpdateDataSubscription = /* @__PURE__ */proto3.makeMessageType("livekit.UpdateDataSubscription", () => [{
6044
+ no: 1,
6045
+ name: "updates",
6046
+ kind: "message",
6047
+ T: UpdateDataSubscription_Update,
6048
+ repeated: true
6049
+ }]);
6050
+ const UpdateDataSubscription_Update = /* @__PURE__ */proto3.makeMessageType("livekit.UpdateDataSubscription.Update", () => [{
6051
+ no: 1,
6052
+ name: "track_sid",
6053
+ kind: "scalar",
6054
+ T: 9
6055
+ /* ScalarType.STRING */
6056
+ }, {
6057
+ no: 2,
6058
+ name: "subscribe",
6059
+ kind: "scalar",
6060
+ T: 8
6061
+ /* ScalarType.BOOL */
6062
+ }, {
6063
+ no: 3,
6064
+ name: "options",
6065
+ kind: "message",
6066
+ T: DataTrackSubscriptionOptions
6067
+ }], {
6068
+ localName: "UpdateDataSubscription_Update"
6069
+ });
5884
6070
  const UpdateTrackSettings = /* @__PURE__ */proto3.makeMessageType("livekit.UpdateTrackSettings", () => [{
5885
6071
  no: 1,
5886
6072
  name: "track_sids",
@@ -6287,6 +6473,12 @@ const SyncState = /* @__PURE__ */proto3.makeMessageType("livekit.SyncState", ()
6287
6473
  kind: "message",
6288
6474
  T: DataChannelReceiveState,
6289
6475
  repeated: true
6476
+ }, {
6477
+ no: 8,
6478
+ name: "publish_data_tracks",
6479
+ kind: "message",
6480
+ T: PublishDataTrackResponse,
6481
+ repeated: true
6290
6482
  }]);
6291
6483
  const DataChannelReceiveState = /* @__PURE__ */proto3.makeMessageType("livekit.DataChannelReceiveState", () => [{
6292
6484
  no: 1,
@@ -6491,6 +6683,18 @@ const RequestResponse = /* @__PURE__ */proto3.makeMessageType("livekit.RequestRe
6491
6683
  kind: "message",
6492
6684
  T: UpdateLocalVideoTrack,
6493
6685
  oneof: "request"
6686
+ }, {
6687
+ no: 10,
6688
+ name: "publish_data_track",
6689
+ kind: "message",
6690
+ T: PublishDataTrackRequest,
6691
+ oneof: "request"
6692
+ }, {
6693
+ no: 11,
6694
+ name: "unpublish_data_track",
6695
+ kind: "message",
6696
+ T: UnpublishDataTrackRequest,
6697
+ oneof: "request"
6494
6698
  }]);
6495
6699
  const RequestResponse_Reason = /* @__PURE__ */proto3.makeEnum("livekit.RequestResponse.Reason", [{
6496
6700
  no: 0,
@@ -6513,6 +6717,18 @@ const RequestResponse_Reason = /* @__PURE__ */proto3.makeEnum("livekit.RequestRe
6513
6717
  }, {
6514
6718
  no: 6,
6515
6719
  name: "UNCLASSIFIED_ERROR"
6720
+ }, {
6721
+ no: 7,
6722
+ name: "INVALID_HANDLE"
6723
+ }, {
6724
+ no: 8,
6725
+ name: "INVALID_NAME"
6726
+ }, {
6727
+ no: 9,
6728
+ name: "DUPLICATE_HANDLE"
6729
+ }, {
6730
+ no: 10,
6731
+ name: "DUPLICATE_NAME"
6516
6732
  }]);
6517
6733
  const TrackSubscribed = /* @__PURE__ */proto3.makeMessageType("livekit.TrackSubscribed", () => [{
6518
6734
  no: 1,
@@ -6545,6 +6761,12 @@ const ConnectionSettings = /* @__PURE__ */proto3.makeMessageType("livekit.Connec
6545
6761
  kind: "scalar",
6546
6762
  T: 8
6547
6763
  /* ScalarType.BOOL */
6764
+ }, {
6765
+ no: 5,
6766
+ name: "auto_subscribe_data_track",
6767
+ kind: "scalar",
6768
+ T: 8,
6769
+ opt: true
6548
6770
  }]);
6549
6771
  const JoinRequest = /* @__PURE__ */proto3.makeMessageType("livekit.JoinRequest", () => [{
6550
6772
  no: 1,
@@ -10395,7 +10617,8 @@ function adapterFactory() {
10395
10617
 
10396
10618
  adapterFactory({
10397
10619
  window: typeof window === 'undefined' ? undefined : window
10398
- });class TypedPromise extends Promise {
10620
+ });var _a, _b;
10621
+ class TypedPromise extends (_b = Promise) {
10399
10622
  // eslint-disable-next-line @typescript-eslint/no-useless-constructor
10400
10623
  constructor(executor) {
10401
10624
  super(executor);
@@ -10412,7 +10635,11 @@ adapterFactory({
10412
10635
  static race(values) {
10413
10636
  return super.race(values);
10414
10637
  }
10415
- }// tiny, simplified version of https://github.com/lancedikson/bowser/blob/master/src/parser-browsers.js
10638
+ }
10639
+ _a = TypedPromise;
10640
+ TypedPromise.resolve = value => {
10641
+ return Reflect.get(_b, "resolve", _a).call(_a, value);
10642
+ };// tiny, simplified version of https://github.com/lancedikson/bowser/blob/master/src/parser-browsers.js
10416
10643
  // reduced to only differentiate Chrome(ium) based browsers / Firefox / Safari
10417
10644
  const commonVersionIdentifier = /version\/(\d+(\.?_?\d+)+)/i;
10418
10645
  let browserDetails;
@@ -10478,14 +10705,23 @@ function getMatch(exp, ua) {
10478
10705
  }
10479
10706
  function getOSVersion(ua) {
10480
10707
  return ua.includes('mac os') ? getMatch(/\(.+?(\d+_\d+(:?_\d+)?)/, ua, 1).replace(/_/g, '.') : undefined;
10481
- }var version$1 = "2.17.0";const version = version$1;
10482
- const protocolVersion = 16;class LivekitError extends Error {
10483
- constructor(code, message) {
10484
- super(message || 'an error has occured');
10708
+ }var version$1 = "2.17.1";const version = version$1;
10709
+ const protocolVersion = 16;/** Base error that all LiveKit specific custom errors inherit from. */
10710
+ class LivekitError extends Error {
10711
+ constructor(code, message, options) {
10712
+ super(message || 'an error has occurred');
10485
10713
  this.name = 'LiveKitError';
10486
10714
  this.code = code;
10715
+ if (typeof (options === null || options === void 0 ? void 0 : options.cause) !== 'undefined') {
10716
+ this.cause = options === null || options === void 0 ? void 0 : options.cause;
10717
+ }
10487
10718
  }
10488
10719
  }
10720
+ /**
10721
+ * LiveKit specific error type representing an error with an associated set of reasons.
10722
+ * Use this to represent an error with multiple different but contextually related variants.
10723
+ * */
10724
+ class LivekitReasonedError extends LivekitError {}
10489
10725
  var ConnectionErrorReason;
10490
10726
  (function (ConnectionErrorReason) {
10491
10727
  ConnectionErrorReason[ConnectionErrorReason["NotAllowed"] = 0] = "NotAllowed";
@@ -10497,7 +10733,7 @@ var ConnectionErrorReason;
10497
10733
  ConnectionErrorReason[ConnectionErrorReason["WebSocket"] = 6] = "WebSocket";
10498
10734
  ConnectionErrorReason[ConnectionErrorReason["ServiceNotFound"] = 7] = "ServiceNotFound";
10499
10735
  })(ConnectionErrorReason || (ConnectionErrorReason = {}));
10500
- class ConnectionError extends LivekitError {
10736
+ class ConnectionError extends LivekitReasonedError {
10501
10737
  constructor(message, reason, status, context) {
10502
10738
  super(1, message);
10503
10739
  this.name = 'ConnectionError';
@@ -10568,7 +10804,7 @@ class PublishTrackError extends LivekitError {
10568
10804
  this.status = status;
10569
10805
  }
10570
10806
  }
10571
- class SignalRequestError extends LivekitError {
10807
+ class SignalRequestError extends LivekitReasonedError {
10572
10808
  constructor(message, reason) {
10573
10809
  super(15, message);
10574
10810
  this.name = 'SignalRequestError';
@@ -10594,7 +10830,7 @@ var DataStreamErrorReason;
10594
10830
  // Encryption type mismatch.
10595
10831
  DataStreamErrorReason[DataStreamErrorReason["EncryptionTypeMismatch"] = 8] = "EncryptionTypeMismatch";
10596
10832
  })(DataStreamErrorReason || (DataStreamErrorReason = {}));
10597
- class DataStreamError extends LivekitError {
10833
+ class DataStreamError extends LivekitReasonedError {
10598
10834
  constructor(message, reason) {
10599
10835
  super(16, message);
10600
10836
  this.name = 'DataStreamError';
@@ -20121,7 +20357,8 @@ class TextStreamReader extends BaseStreamReader {
20121
20357
  topic: streamHeader.topic,
20122
20358
  timestamp: Number(streamHeader.timestamp),
20123
20359
  attributes: streamHeader.attributes,
20124
- encryptionType
20360
+ encryptionType,
20361
+ attachedStreamIds: streamHeader.contentHeader.value.attachedStreamIds
20125
20362
  };
20126
20363
  const stream = new ReadableStream({
20127
20364
  start: controller => {
@@ -20274,7 +20511,8 @@ class OutgoingDataStreamManager {
20274
20511
  topic: (_b = options === null || options === void 0 ? void 0 : options.topic) !== null && _b !== void 0 ? _b : '',
20275
20512
  size: options === null || options === void 0 ? void 0 : options.totalSize,
20276
20513
  attributes: options === null || options === void 0 ? void 0 : options.attributes,
20277
- encryptionType: ((_c = this.engine.e2eeManager) === null || _c === void 0 ? void 0 : _c.isDataChannelEncryptionEnabled) ? Encryption_Type.GCM : Encryption_Type.NONE
20514
+ encryptionType: ((_c = this.engine.e2eeManager) === null || _c === void 0 ? void 0 : _c.isDataChannelEncryptionEnabled) ? Encryption_Type.GCM : Encryption_Type.NONE,
20515
+ attachedStreamIds: options === null || options === void 0 ? void 0 : options.attachedStreamIds
20278
20516
  };
20279
20517
  const header = new DataStream_Header({
20280
20518
  streamId,
@@ -20287,7 +20525,7 @@ class OutgoingDataStreamManager {
20287
20525
  case: 'textHeader',
20288
20526
  value: new DataStream_TextHeader({
20289
20527
  version: options === null || options === void 0 ? void 0 : options.version,
20290
- attachedStreamIds: options === null || options === void 0 ? void 0 : options.attachedStreamIds,
20528
+ attachedStreamIds: info.attachedStreamIds,
20291
20529
  replyToStreamId: options === null || options === void 0 ? void 0 : options.replyToStreamId,
20292
20530
  operationType: (options === null || options === void 0 ? void 0 : options.type) === 'update' ? DataStream_OperationType.UPDATE : DataStream_OperationType.CREATE
20293
20531
  })
@@ -26897,7 +27135,44 @@ class JWSSignatureVerificationFailed extends JOSEError {
26897
27135
  _defineProperty(this, "code", 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED');
26898
27136
  }
26899
27137
  }
26900
- _defineProperty(JWSSignatureVerificationFailed, "code", 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED');
27138
+ _defineProperty(JWSSignatureVerificationFailed, "code", 'ERR_JWS_SIGNATURE_VERIFICATION_FAILED');var DataTrackHandleErrorReason;
27139
+ (function (DataTrackHandleErrorReason) {
27140
+ DataTrackHandleErrorReason[DataTrackHandleErrorReason["Reserved"] = 0] = "Reserved";
27141
+ DataTrackHandleErrorReason[DataTrackHandleErrorReason["TooLarge"] = 1] = "TooLarge";
27142
+ })(DataTrackHandleErrorReason || (DataTrackHandleErrorReason = {}));
27143
+ var DataTrackDeserializeErrorReason;
27144
+ (function (DataTrackDeserializeErrorReason) {
27145
+ DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["TooShort"] = 0] = "TooShort";
27146
+ DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["HeaderOverrun"] = 1] = "HeaderOverrun";
27147
+ DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["MissingExtWords"] = 2] = "MissingExtWords";
27148
+ DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["UnsupportedVersion"] = 3] = "UnsupportedVersion";
27149
+ DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["InvalidHandle"] = 4] = "InvalidHandle";
27150
+ DataTrackDeserializeErrorReason[DataTrackDeserializeErrorReason["MalformedExt"] = 5] = "MalformedExt";
27151
+ })(DataTrackDeserializeErrorReason || (DataTrackDeserializeErrorReason = {}));
27152
+ var DataTrackSerializeErrorReason;
27153
+ (function (DataTrackSerializeErrorReason) {
27154
+ DataTrackSerializeErrorReason[DataTrackSerializeErrorReason["TooSmallForHeader"] = 0] = "TooSmallForHeader";
27155
+ DataTrackSerializeErrorReason[DataTrackSerializeErrorReason["TooSmallForPayload"] = 1] = "TooSmallForPayload";
27156
+ })(DataTrackSerializeErrorReason || (DataTrackSerializeErrorReason = {}));
27157
+ var DataTrackExtensionTag;
27158
+ (function (DataTrackExtensionTag) {
27159
+ DataTrackExtensionTag[DataTrackExtensionTag["UserTimestamp"] = 2] = "UserTimestamp";
27160
+ DataTrackExtensionTag[DataTrackExtensionTag["E2ee"] = 1] = "E2ee";
27161
+ })(DataTrackExtensionTag || (DataTrackExtensionTag = {}));
27162
+ DataTrackExtensionTag.UserTimestamp;
27163
+ DataTrackExtensionTag.E2ee;
27164
+ /** Marker indicating a packet's position in relation to a frame. */
27165
+ var FrameMarker;
27166
+ (function (FrameMarker) {
27167
+ /** Packet is the first in a frame. */
27168
+ FrameMarker[FrameMarker["Start"] = 0] = "Start";
27169
+ /** Packet is within a frame. */
27170
+ FrameMarker[FrameMarker["Inter"] = 1] = "Inter";
27171
+ /** Packet is the last in a frame. */
27172
+ FrameMarker[FrameMarker["Final"] = 2] = "Final";
27173
+ /** Packet is the only one in a frame. */
27174
+ FrameMarker[FrameMarker["Single"] = 3] = "Single";
27175
+ })(FrameMarker || (FrameMarker = {}));
26901
27176
 
26902
27177
  /**
26903
27178
  * Base HTTP Client for Voice.ai API
@@ -27056,7 +27331,7 @@ class BaseClient {
27056
27331
  /**
27057
27332
  * Perform DELETE request
27058
27333
  */
27059
- async delete(path) {
27334
+ async httpDelete(path) {
27060
27335
  const response = await fetch(`${this.apiUrl}${path}`, {
27061
27336
  method: 'DELETE',
27062
27337
  headers: this.getHeaders(),
@@ -27249,25 +27524,26 @@ class AgentClient extends BaseClient {
27249
27524
  return this.post(`/agent/${encodeURIComponent(agentId)}/pause`);
27250
27525
  }
27251
27526
  /**
27252
- * Delete/disable an agent
27527
+ * Delete an agent
27253
27528
  *
27254
27529
  * An agent must be paused before being deleted.
27255
- * Disabled agents will be automatically deleted after a grace period.
27256
27530
  *
27257
27531
  * @param agentId - The agent ID to delete
27258
27532
  * @returns Delete response
27259
27533
  *
27260
27534
  * @example
27261
27535
  * ```typescript
27262
- * // First pause, then delete
27263
27536
  * await client.agents.pause('agent-123');
27264
- * const result = await client.agents.disable('agent-123');
27265
- * console.log('Deleted:', result.message);
27537
+ * await client.agents.disable('agent-123');
27266
27538
  * ```
27267
27539
  */
27268
27540
  async disable(agentId) {
27269
27541
  return this.post(`/agent/${encodeURIComponent(agentId)}/disable`);
27270
27542
  }
27543
+ /** Alias for {@link disable} */
27544
+ async delete(agentId) {
27545
+ return this.disable(agentId);
27546
+ }
27271
27547
  /**
27272
27548
  * Initialize agent from template
27273
27549
  *
@@ -27337,7 +27613,7 @@ class AgentClient extends BaseClient {
27337
27613
  * ```
27338
27614
  */
27339
27615
  async unassignKnowledgeBase(agentId) {
27340
- await this.delete(`/agent/${encodeURIComponent(agentId)}/knowledge-base`);
27616
+ await this.httpDelete(`/agent/${encodeURIComponent(agentId)}/knowledge-base`);
27341
27617
  }
27342
27618
  }
27343
27619
 
@@ -27556,7 +27832,7 @@ class KnowledgeBaseClient extends BaseClient {
27556
27832
  * ```
27557
27833
  */
27558
27834
  async remove(kbId) {
27559
- await super.delete(`/knowledge-base/${kbId}`);
27835
+ await this.httpDelete(`/knowledge-base/${kbId}`);
27560
27836
  }
27561
27837
  }
27562
27838
 
@@ -27702,6 +27978,264 @@ class PhoneNumberClient extends BaseClient {
27702
27978
  }
27703
27979
  }
27704
27980
 
27981
+ /**
27982
+ * Text-to-Speech (TTS) Client
27983
+ *
27984
+ * Provides methods for:
27985
+ * - Generating speech from text (synchronous and streaming)
27986
+ * - Listing available voices
27987
+ * - Getting voice details
27988
+ * - Cloning voices from audio samples
27989
+ * - Updating and deleting voices
27990
+ */
27991
+ /**
27992
+ * Client for Text-to-Speech operations
27993
+ *
27994
+ * @example
27995
+ * ```typescript
27996
+ * // List available voices
27997
+ * const voices = await client.tts.listVoices();
27998
+ *
27999
+ * // Generate speech
28000
+ * const audio = await client.tts.synthesize({
28001
+ * text: 'Hello world!',
28002
+ * voice_id: 'voice-123',
28003
+ * language: 'en',
28004
+ * });
28005
+ *
28006
+ * // Clone a voice
28007
+ * const cloned = await client.tts.cloneVoice({
28008
+ * file: audioFile,
28009
+ * name: 'My Voice',
28010
+ * language: 'en',
28011
+ * });
28012
+ * ```
28013
+ */
28014
+ class TTSClient extends BaseClient {
28015
+ constructor(config) {
28016
+ super(config);
28017
+ }
28018
+ // ==========================================================================
28019
+ // SPEECH GENERATION
28020
+ // ==========================================================================
28021
+ /**
28022
+ * Generate speech from text (returns complete audio file)
28023
+ *
28024
+ * This is the synchronous endpoint - it blocks until generation completes
28025
+ * and returns the entire audio file as a Blob. For lower latency with
28026
+ * chunked streaming, use {@link synthesizeStream}.
28027
+ *
28028
+ * @param options - Speech generation options
28029
+ * @returns Audio blob in the requested format
28030
+ *
28031
+ * @example
28032
+ * ```typescript
28033
+ * const audio = await client.tts.synthesize({
28034
+ * text: 'Hello, welcome to Voice AI!',
28035
+ * voice_id: 'voice-123',
28036
+ * language: 'en',
28037
+ * audio_format: 'mp3',
28038
+ * });
28039
+ *
28040
+ * // Play in browser
28041
+ * const url = URL.createObjectURL(audio);
28042
+ * new Audio(url).play();
28043
+ *
28044
+ * // Or download
28045
+ * const a = document.createElement('a');
28046
+ * a.href = url;
28047
+ * a.download = 'speech.mp3';
28048
+ * a.click();
28049
+ * ```
28050
+ */
28051
+ async synthesize(options) {
28052
+ return this.postForBlob('/tts/speech', options);
28053
+ }
28054
+ /**
28055
+ * Generate speech from text with HTTP chunked streaming
28056
+ *
28057
+ * Returns a Response object with a readable body stream. Audio chunks
28058
+ * are sent to the client as they are generated, providing lower perceived
28059
+ * latency than {@link synthesize}.
28060
+ *
28061
+ * @param options - Speech generation options
28062
+ * @returns Fetch Response with streaming body
28063
+ *
28064
+ * @example
28065
+ * ```typescript
28066
+ * const response = await client.tts.synthesizeStream({
28067
+ * text: 'Hello, welcome to Voice AI!',
28068
+ * voice_id: 'voice-123',
28069
+ * language: 'en',
28070
+ * });
28071
+ *
28072
+ * // Read chunks as they arrive
28073
+ * const reader = response.body!.getReader();
28074
+ * while (true) {
28075
+ * const { done, value } = await reader.read();
28076
+ * if (done) break;
28077
+ * // Process audio chunk (Uint8Array)
28078
+ * console.log('Received chunk:', value.length, 'bytes');
28079
+ * }
28080
+ *
28081
+ * // Or collect all chunks and create a blob
28082
+ * const response = await client.tts.synthesizeStream({ text: '...', voice_id: '...' });
28083
+ * const blob = await response.blob();
28084
+ * ```
28085
+ */
28086
+ async synthesizeStream(options) {
28087
+ return this.postForStream('/tts/speech/stream', options);
28088
+ }
28089
+ // ==========================================================================
28090
+ // VOICE MANAGEMENT
28091
+ // ==========================================================================
28092
+ /**
28093
+ * List voices available to the current user
28094
+ *
28095
+ * Returns voices owned by the authenticated user plus default/public voices.
28096
+ * Deleted voices are excluded.
28097
+ *
28098
+ * @returns Array of voice objects
28099
+ *
28100
+ * @example
28101
+ * ```typescript
28102
+ * const voices = await client.tts.listVoices();
28103
+ *
28104
+ * for (const voice of voices) {
28105
+ * console.log(`${voice.name} (${voice.voice_id}): ${voice.status}`);
28106
+ * }
28107
+ *
28108
+ * // Filter to only available voices
28109
+ * const available = voices.filter(v => v.status === 'AVAILABLE');
28110
+ * ```
28111
+ */
28112
+ async listVoices() {
28113
+ return this.get('/tts/voices');
28114
+ }
28115
+ /**
28116
+ * Get voice details and status
28117
+ *
28118
+ * Useful for polling the status of a voice clone operation
28119
+ * (PENDING -> PROCESSING -> AVAILABLE).
28120
+ *
28121
+ * Access control:
28122
+ * - PUBLIC voices: readable by any authenticated user
28123
+ * - PRIVATE voices: readable only by owner (returns 404 otherwise)
28124
+ *
28125
+ * @param voiceId - The voice ID
28126
+ * @returns Voice details and status
28127
+ *
28128
+ * @example
28129
+ * ```typescript
28130
+ * const voice = await client.tts.getVoice('voice-123');
28131
+ *
28132
+ * if (voice.status === 'AVAILABLE') {
28133
+ * console.log('Voice is ready to use!');
28134
+ * } else if (voice.status === 'PROCESSING') {
28135
+ * console.log('Voice is still being processed...');
28136
+ * }
28137
+ * ```
28138
+ */
28139
+ async getVoice(voiceId) {
28140
+ return this.get(`/tts/voice/${encodeURIComponent(voiceId)}`);
28141
+ }
28142
+ /**
28143
+ * Clone a voice from a reference audio file
28144
+ *
28145
+ * Accepts an audio file (MP3, WAV, or OGG, max 7.5MB) and creates
28146
+ * a cloned voice. The voice starts in PENDING status and moves to
28147
+ * PROCESSING, then AVAILABLE once ready.
28148
+ *
28149
+ * Use {@link getVoice} to poll the voice status.
28150
+ *
28151
+ * @param options - Clone voice options including audio file
28152
+ * @returns Created voice with ID and initial status (PENDING)
28153
+ *
28154
+ * @example
28155
+ * ```typescript
28156
+ * // From a file input
28157
+ * const fileInput = document.querySelector('input[type="file"]');
28158
+ * const file = fileInput.files[0];
28159
+ *
28160
+ * const voice = await client.tts.cloneVoice({
28161
+ * file: file,
28162
+ * name: 'My Custom Voice',
28163
+ * language: 'en',
28164
+ * voice_visibility: 'PRIVATE',
28165
+ * });
28166
+ *
28167
+ * console.log('Voice ID:', voice.voice_id);
28168
+ * console.log('Status:', voice.status); // 'PENDING'
28169
+ *
28170
+ * // Poll until ready
28171
+ * let status = voice.status;
28172
+ * while (status !== 'AVAILABLE' && status !== 'FAILED') {
28173
+ * await new Promise(r => setTimeout(r, 2000));
28174
+ * const updated = await client.tts.getVoice(voice.voice_id);
28175
+ * status = updated.status;
28176
+ * console.log('Status:', status);
28177
+ * }
28178
+ * ```
28179
+ */
28180
+ async cloneVoice(options) {
28181
+ const formData = new FormData();
28182
+ formData.append('file', options.file);
28183
+ if (options.name !== undefined) {
28184
+ formData.append('name', options.name);
28185
+ }
28186
+ if (options.voice_visibility !== undefined) {
28187
+ formData.append('voice_visibility', options.voice_visibility);
28188
+ }
28189
+ if (options.language !== undefined) {
28190
+ formData.append('language', options.language);
28191
+ }
28192
+ return this.postFormData('/tts/clone-voice', formData);
28193
+ }
28194
+ /**
28195
+ * Update voice metadata (name and/or visibility)
28196
+ *
28197
+ * Only the voice owner can update a voice.
28198
+ *
28199
+ * @param voiceId - The voice ID to update
28200
+ * @param options - Fields to update
28201
+ * @returns Updated voice details
28202
+ *
28203
+ * @example
28204
+ * ```typescript
28205
+ * // Rename a voice
28206
+ * const updated = await client.tts.updateVoice('voice-123', {
28207
+ * name: 'New Voice Name',
28208
+ * });
28209
+ *
28210
+ * // Make a voice private
28211
+ * const updated = await client.tts.updateVoice('voice-123', {
28212
+ * voice_visibility: 'PRIVATE',
28213
+ * });
28214
+ * ```
28215
+ */
28216
+ async updateVoice(voiceId, options) {
28217
+ return this.patch(`/tts/voice/${encodeURIComponent(voiceId)}`, options);
28218
+ }
28219
+ /**
28220
+ * Delete a voice
28221
+ *
28222
+ * Only the voice owner can delete a voice. The voice will no longer
28223
+ * appear in voice listings.
28224
+ *
28225
+ * @param voiceId - The voice ID to delete
28226
+ * @returns Deletion confirmation
28227
+ *
28228
+ * @example
28229
+ * ```typescript
28230
+ * await client.tts.deleteVoice('voice-123');
28231
+ * console.log('Voice deleted');
28232
+ * ```
28233
+ */
28234
+ async deleteVoice(voiceId) {
28235
+ return this.httpDelete(`/tts/voice/${encodeURIComponent(voiceId)}`);
28236
+ }
28237
+ }
28238
+
27705
28239
  /**
27706
28240
  * VoiceAgentWidget - UI widget for Voice.ai
27707
28241
  *
@@ -28158,14 +28692,47 @@ const DEFAULT_API_URL = 'https://dev.voice.ai/api/v1';
28158
28692
  * ```
28159
28693
  */
28160
28694
  class VoiceAI {
28695
+ // ==========================================================================
28696
+ // API CLIENTS (REST API)
28697
+ // ==========================================================================
28698
+ /** Agent management - create, update, deploy, pause, delete agents. */
28699
+ get agents() {
28700
+ if (!this._agents)
28701
+ throw new VoiceAIError('API key required for agents API. Pass apiKey in the constructor.');
28702
+ return this._agents;
28703
+ }
28704
+ /** Analytics - call history, transcripts, stats. */
28705
+ get analytics() {
28706
+ if (!this._analytics)
28707
+ throw new VoiceAIError('API key required for analytics API. Pass apiKey in the constructor.');
28708
+ return this._analytics;
28709
+ }
28710
+ /** Knowledge Base - manage RAG documents. */
28711
+ get knowledgeBase() {
28712
+ if (!this._knowledgeBase)
28713
+ throw new VoiceAIError('API key required for knowledgeBase API. Pass apiKey in the constructor.');
28714
+ return this._knowledgeBase;
28715
+ }
28716
+ /** Phone Numbers - search, select, release phone numbers. */
28717
+ get phoneNumbers() {
28718
+ if (!this._phoneNumbers)
28719
+ throw new VoiceAIError('API key required for phoneNumbers API. Pass apiKey in the constructor.');
28720
+ return this._phoneNumbers;
28721
+ }
28722
+ /** Text-to-Speech - generate speech, manage voices, clone voices. */
28723
+ get tts() {
28724
+ if (!this._tts)
28725
+ throw new VoiceAIError('API key required for tts API. Pass apiKey in the constructor.');
28726
+ return this._tts;
28727
+ }
28161
28728
  /**
28162
28729
  * Create a new VoiceAI client
28163
28730
  *
28164
- * @param config - Configuration options
28165
- * @param config.apiKey - Your Voice.ai API key (required)
28731
+ * @param config - Configuration options (optional for frontend-only usage)
28732
+ * @param config.apiKey - Your Voice.ai API key (required for API operations and `getConnectionDetails`)
28166
28733
  * @param config.apiUrl - Custom API URL (optional, defaults to production)
28167
28734
  */
28168
- constructor(config) {
28735
+ constructor(config = {}) {
28169
28736
  // ==========================================================================
28170
28737
  // PRIVATE STATE (Real-time voice)
28171
28738
  // ==========================================================================
@@ -28177,31 +28744,118 @@ class VoiceAI {
28177
28744
  this.agentStateHandlers = new Set();
28178
28745
  this.audioLevelHandlers = new Set();
28179
28746
  this.microphoneStateHandlers = new Set();
28747
+ this.effectiveApiKey = '';
28180
28748
  this.cachedConnectionDetails = null;
28181
28749
  this.currentAgentState = 'disconnected';
28182
28750
  this.agentParticipantId = null;
28183
28751
  this.audioLevelInterval = null;
28184
- if (!config.apiKey) {
28185
- throw new Error('API key is required. Get one at https://voice.ai');
28186
- }
28187
- this.apiKey = config.apiKey;
28752
+ this.apiKey = config.apiKey || '';
28188
28753
  this.apiUrl = config.apiUrl || DEFAULT_API_URL;
28189
- // Initialize API clients
28190
- const clientConfig = { apiKey: this.apiKey, apiUrl: this.apiUrl };
28191
- this.agents = new AgentClient(clientConfig);
28192
- this.analytics = new AnalyticsClient(clientConfig);
28193
- this.knowledgeBase = new KnowledgeBaseClient(clientConfig);
28194
- this.phoneNumbers = new PhoneNumberClient(clientConfig);
28754
+ // Initialize API clients when apiKey is provided
28755
+ if (this.apiKey) {
28756
+ const clientConfig = { apiKey: this.apiKey, apiUrl: this.apiUrl };
28757
+ this._agents = new AgentClient(clientConfig);
28758
+ this._analytics = new AnalyticsClient(clientConfig);
28759
+ this._knowledgeBase = new KnowledgeBaseClient(clientConfig);
28760
+ this._phoneNumbers = new PhoneNumberClient(clientConfig);
28761
+ this._tts = new TTSClient(clientConfig);
28762
+ }
28195
28763
  }
28196
28764
  // ==========================================================================
28197
28765
  // REAL-TIME VOICE CONNECTION
28198
28766
  // ==========================================================================
28199
28767
  /**
28200
- * Connect to a voice agent for real-time conversation
28768
+ * Get connection details for a voice agent.
28769
+ *
28770
+ * Requires an API key. Call this from your backend, then pass the result
28771
+ * to `connectRoom()` on the frontend.
28772
+ *
28773
+ * @param options - Connection options (agentId, testMode, etc.)
28774
+ * @returns Connection details (serverUrl, participantToken, callId)
28775
+ *
28776
+ * @example
28777
+ * ```typescript
28778
+ * // Server-side: get connection details
28779
+ * const details = await voiceai.getConnectionDetails({ agentId: 'agent-123' });
28780
+ * // Return details to frontend...
28781
+ *
28782
+ * // With test mode
28783
+ * const details = await voiceai.getConnectionDetails({
28784
+ * agentId: 'agent-123',
28785
+ * testMode: true
28786
+ * });
28787
+ * ```
28788
+ */
28789
+ async getConnectionDetails(options) {
28790
+ return this.fetchConnectionDetails(options);
28791
+ }
28792
+ /**
28793
+ * Connect to a LiveKit room using pre-fetched connection details.
28794
+ *
28795
+ * This is the browser-safe method -- it only needs a room token,
28796
+ * no API key required. Get the connection details from your backend
28797
+ * using `getConnectionDetails()`.
28798
+ *
28799
+ * @param connectionDetails - Server URL, participant token, and call ID from your backend
28800
+ * @param options - Audio/microphone options
28801
+ *
28802
+ * @example
28803
+ * ```typescript
28804
+ * // Frontend: connect using token from your backend
28805
+ * const voiceai = new VoiceAI();
28806
+ * await voiceai.connectRoom(
28807
+ * { serverUrl, participantToken, callId },
28808
+ * { autoPublishMic: true }
28809
+ * );
28810
+ * ```
28811
+ */
28812
+ async connectRoom(connectionDetails, options = {}) {
28813
+ if (this.connectionStatus.connecting || this.connectionStatus.connected) {
28814
+ throw new Error('Already connected or connecting');
28815
+ }
28816
+ this.updateStatus({ connecting: true, connected: false });
28817
+ this.cachedConnectionDetails = connectionDetails;
28818
+ try {
28819
+ this.room = new Room();
28820
+ this.setupRoomListeners();
28821
+ this.room.prepareConnection(connectionDetails.serverUrl, connectionDetails.participantToken);
28822
+ const preConnectBuffer = options.preConnectBuffer !== false;
28823
+ let connected = false;
28824
+ let lastError;
28825
+ for (let attempt = 1; attempt <= 3; attempt++) {
28826
+ try {
28827
+ await Promise.all([
28828
+ this.setupAudio(options, preConnectBuffer),
28829
+ this.room.connect(connectionDetails.serverUrl, connectionDetails.participantToken)
28830
+ ]);
28831
+ connected = true;
28832
+ break;
28833
+ }
28834
+ catch (error) {
28835
+ lastError = error;
28836
+ if (attempt < 3) {
28837
+ await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
28838
+ }
28839
+ }
28840
+ }
28841
+ if (!connected) {
28842
+ throw lastError || new Error('Failed to connect after 3 attempts');
28843
+ }
28844
+ this.updateStatus({ connected: true, connecting: false, callId: connectionDetails.callId });
28845
+ }
28846
+ catch (error) {
28847
+ const err = error instanceof Error ? error : new Error(String(error));
28848
+ this.updateStatus({ connected: false, connecting: false, error: err.message });
28849
+ this.emitError(err);
28850
+ throw err;
28851
+ }
28852
+ }
28853
+ /**
28854
+ * Connect to a voice agent for real-time conversation.
28855
+ *
28856
+ * Convenience method that combines `getConnectionDetails()` + `connectRoom()`.
28201
28857
  *
28202
28858
  * @param options - Connection options
28203
- * @param options.agentId - ID of the agent to connect to
28204
- * @param options.autoPublishMic - Auto-enable microphone (default: true)
28205
28859
  *
28206
28860
  * @example
28207
28861
  * ```typescript
@@ -28214,7 +28868,7 @@ class VoiceAI {
28214
28868
  }
28215
28869
  this.updateStatus({ connecting: true, connected: false });
28216
28870
  try {
28217
- // Get connection details from API
28871
+ // Get connection details via the appropriate method
28218
28872
  const connectionDetails = await this.getOrRefreshConnectionDetails(options);
28219
28873
  // Create room instance
28220
28874
  this.room = new Room();
@@ -28254,11 +28908,17 @@ class VoiceAI {
28254
28908
  }
28255
28909
  }
28256
28910
  /**
28257
- * Disconnect from the voice agent and end the call
28911
+ * Disconnect from the room and end the call.
28912
+ *
28913
+ * Signals the server to free the concurrency slot using endToken from
28914
+ * connection details. Connection details always include end_token when
28915
+ * fetched from the API; backends must pass it when using pre-fetched details.
28916
+ * If no endToken, the server detects room disconnect as fallback.
28258
28917
  */
28259
28918
  async disconnect() {
28260
28919
  this.stopAudioLevelMonitoring();
28261
28920
  const callId = this.cachedConnectionDetails?.callId;
28921
+ const endToken = this.cachedConnectionDetails?.endToken;
28262
28922
  if (this.room) {
28263
28923
  try {
28264
28924
  await this.room.disconnect();
@@ -28268,22 +28928,29 @@ class VoiceAI {
28268
28928
  }
28269
28929
  this.room = null;
28270
28930
  }
28271
- // End call via API
28272
- if (callId) {
28931
+ // Signal server to free concurrency slot (endToken only - API always returns it)
28932
+ if (callId && endToken) {
28273
28933
  try {
28274
- await fetch(`${this.apiUrl}/calls/${encodeURIComponent(callId)}/end`, {
28934
+ const response = await fetch(`${this.apiUrl}/calls/${encodeURIComponent(callId)}/end`, {
28275
28935
  method: 'POST',
28276
28936
  headers: {
28277
28937
  'Content-Type': 'application/json',
28278
- 'Authorization': `Bearer ${this.apiKey}`
28938
+ 'Authorization': `Bearer ${endToken}`
28279
28939
  }
28280
28940
  });
28941
+ if (!response.ok) {
28942
+ console.warn(`[VoiceAI] Failed to end call ${callId}: ${response.status} ${response.statusText}`);
28943
+ }
28281
28944
  }
28282
- catch {
28283
- // Non-critical
28945
+ catch (err) {
28946
+ console.warn(`[VoiceAI] Failed to end call ${callId}:`, err);
28284
28947
  }
28285
28948
  }
28949
+ else if (callId && !endToken) {
28950
+ console.warn(`[VoiceAI] No endToken in connection details - pass end_token from your backend to enable immediate teardown`);
28951
+ }
28286
28952
  this.cachedConnectionDetails = null;
28953
+ this.effectiveApiKey = '';
28287
28954
  this.agentParticipantId = null;
28288
28955
  this.updateAgentState('disconnected');
28289
28956
  this.updateStatus({ connected: false, connecting: false });
@@ -28422,16 +29089,52 @@ class VoiceAI {
28422
29089
  }
28423
29090
  }
28424
29091
  async getOrRefreshConnectionDetails(options) {
29092
+ // Mode 1: Pre-fetched connection details provided directly
29093
+ if (options.serverUrl && options.participantToken) {
29094
+ const details = {
29095
+ serverUrl: options.serverUrl,
29096
+ participantToken: options.participantToken,
29097
+ callId: options.callId || '',
29098
+ endToken: options.endToken,
29099
+ };
29100
+ this.cachedConnectionDetails = details;
29101
+ return details;
29102
+ }
29103
+ // Use cached details if still valid
28425
29104
  if (this.cachedConnectionDetails && !this.isTokenExpired(this.cachedConnectionDetails.participantToken)) {
28426
29105
  return this.cachedConnectionDetails;
28427
29106
  }
28428
- const connectionDetails = await this.getConnectionDetails(options);
29107
+ // Mode 2: Direct API call with API key (constructor or per-call)
29108
+ if (!this.apiKey && !options.apiKey) {
29109
+ throw new Error('No authentication method configured. Use one of:\n' +
29110
+ '1. connectRoom() with pre-fetched details from your backend\n' +
29111
+ '2. API key: new VoiceAI({ apiKey: "vk_..." }) or connect({ apiKey: "vk_..." })');
29112
+ }
29113
+ const connectionDetails = await this.fetchConnectionDetails(options);
28429
29114
  this.cachedConnectionDetails = connectionDetails;
28430
29115
  return connectionDetails;
28431
29116
  }
28432
- async getConnectionDetails(options) {
29117
+ /**
29118
+ * Fetch connection details from the developer's backend endpoint.
29119
+ * The backend holds the API key and calls the Voice.AI API server-side.
29120
+ */
29121
+ /**
29122
+ * Fetch connection details using the API key directly.
29123
+ * Used by the public getConnectionDetails() method.
29124
+ */
29125
+ async fetchConnectionDetails(options) {
29126
+ if (!this.apiKey && !options.apiKey) {
29127
+ throw new Error('API key is required for getConnectionDetails(). Pass { apiKey: "vk_..." } to the constructor or options.');
29128
+ }
29129
+ return this.fetchConnectionDetailsFromApi(options);
29130
+ }
29131
+ async fetchConnectionDetailsFromApi(options) {
28433
29132
  const url = options.apiUrl || this.apiUrl;
28434
- const endpoint = `${url}/connection/connection-details`;
29133
+ // Use test-connection-details for testMode (allows testing paused/undeployed agents)
29134
+ const endpointPath = options.testMode
29135
+ ? '/connection/test-connection-details'
29136
+ : '/connection/connection-details';
29137
+ const endpoint = `${url}${endpointPath}`;
28435
29138
  const requestData = {};
28436
29139
  if (options.agentId)
28437
29140
  requestData.agent_id = options.agentId;
@@ -28447,6 +29150,7 @@ class VoiceAI {
28447
29150
  : JSON.stringify(options.environment);
28448
29151
  }
28449
29152
  const apiKey = options.apiKey || this.apiKey;
29153
+ this.effectiveApiKey = apiKey;
28450
29154
  const response = await fetch(endpoint, {
28451
29155
  method: 'POST',
28452
29156
  headers: {
@@ -28472,7 +29176,8 @@ class VoiceAI {
28472
29176
  return {
28473
29177
  serverUrl: data.server_url,
28474
29178
  participantToken: data.participant_token,
28475
- callId: data.call_id
29179
+ callId: data.call_id,
29180
+ endToken: data.end_token,
28476
29181
  };
28477
29182
  }
28478
29183
  async setupAudio(options, preConnectBuffer) {