@craftedxp/voice-js 0.10.0 → 0.12.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.
Files changed (41) hide show
  1. package/README.md +21 -19
  2. package/dist/assistant.d.mts +3 -3
  3. package/dist/assistant.d.ts +3 -3
  4. package/dist/assistant.js +210 -22
  5. package/dist/assistant.js.map +1 -1
  6. package/dist/assistant.mjs +2 -1
  7. package/dist/browser.d.mts +3 -3
  8. package/dist/browser.d.ts +3 -3
  9. package/dist/browser.js +201 -34
  10. package/dist/browser.js.map +1 -1
  11. package/dist/browser.mjs +3 -2
  12. package/dist/browser.mjs.map +1 -1
  13. package/dist/{chunk-3BYNC25N.mjs → chunk-C2U6V5NI.mjs} +191 -23
  14. package/dist/chunk-C2U6V5NI.mjs.map +1 -0
  15. package/dist/chunk-OS2GZAR7.mjs +18 -0
  16. package/dist/chunk-OS2GZAR7.mjs.map +1 -0
  17. package/dist/{chunk-LV7JGPYW.mjs → chunk-VSY43NGD.mjs} +6 -13
  18. package/dist/chunk-VSY43NGD.mjs.map +1 -0
  19. package/dist/{config-D2TbvIqT.d.mts → config-DfbPB1Tq.d.mts} +22 -3
  20. package/dist/{config-D2TbvIqT.d.ts → config-DfbPB1Tq.d.ts} +22 -3
  21. package/dist/embed.iife.js +42 -5
  22. package/dist/{incomingCall-CfRRzj2P.d.ts → incomingCall-2B3XB8pM.d.mts} +3 -1
  23. package/dist/{incomingCall-CfRRzj2P.d.mts → incomingCall-CYmeQkIw.d.ts} +3 -1
  24. package/dist/node.d.mts +26 -3
  25. package/dist/node.d.ts +26 -3
  26. package/dist/node.js +11 -2
  27. package/dist/node.js.map +1 -1
  28. package/dist/node.mjs +11 -2
  29. package/dist/node.mjs.map +1 -1
  30. package/dist/room.js +15 -12
  31. package/dist/room.js.map +1 -1
  32. package/dist/room.mjs +2 -1
  33. package/dist/transcribe.d.mts +2 -2
  34. package/dist/transcribe.d.ts +2 -2
  35. package/dist/transcribe.js +209 -21
  36. package/dist/transcribe.js.map +1 -1
  37. package/dist/transcribe.mjs +2 -1
  38. package/dist/transcribe.mjs.map +1 -1
  39. package/package.json +1 -1
  40. package/dist/chunk-3BYNC25N.mjs.map +0 -1
  41. package/dist/chunk-LV7JGPYW.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
- import { m as StartCallOptions, V as VoiceClientConfig } from './config-D2TbvIqT.mjs';
2
- export { f as CallState, o as TranscriptEntry } from './config-D2TbvIqT.mjs';
1
+ import { n as StartCallOptions, V as VoiceClientConfig } from './config-DfbPB1Tq.mjs';
2
+ export { f as CallState, p as TranscriptEntry } from './config-DfbPB1Tq.mjs';
3
3
 
4
4
  interface DictationSession {
5
5
  end: () => void;
@@ -1,5 +1,5 @@
1
- import { m as StartCallOptions, V as VoiceClientConfig } from './config-D2TbvIqT.js';
2
- export { f as CallState, o as TranscriptEntry } from './config-D2TbvIqT.js';
1
+ import { n as StartCallOptions, V as VoiceClientConfig } from './config-DfbPB1Tq.js';
2
+ export { f as CallState, p as TranscriptEntry } from './config-DfbPB1Tq.js';
3
3
 
4
4
  interface DictationSession {
5
5
  end: () => void;
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/transcribe.ts
@@ -504,7 +514,8 @@ function buildWsUrl(args) {
504
514
  const base = new URL(args.apiBase);
505
515
  const proto = base.protocol === "https:" ? "wss:" : "ws:";
506
516
  const bargeQS = args.bargeIn === false ? "&barge=off" : "";
507
- return `${proto}//${base.host}/v1/agents/${encodeURIComponent(args.agentId)}/call?token=${encodeURIComponent(args.token)}${bargeQS}`;
517
+ const mediaQS = args.media === "livekit" ? "&media=livekit" : "";
518
+ return `${proto}//${base.host}/v1/agents/${encodeURIComponent(args.agentId)}/call?token=${encodeURIComponent(args.token)}${bargeQS}${mediaQS}`;
508
519
  }
509
520
 
510
521
  // src/clientTools.ts
@@ -658,7 +669,75 @@ var createClientMarksBuffer = (args) => {
658
669
  };
659
670
  };
660
671
 
672
+ // src/livekit/createLiveKitMedia.ts
673
+ var toError = (e) => e instanceof Error ? e : new Error(String(e));
674
+ function createLiveKitMedia(lk, opts = {}) {
675
+ let room = null;
676
+ let audioEl = null;
677
+ let muted = false;
678
+ const attach = (track) => {
679
+ if (track.kind !== lk.Track.Kind.Audio) return;
680
+ if (typeof document === "undefined") return;
681
+ if (!audioEl) {
682
+ audioEl = document.createElement("audio");
683
+ audioEl.autoplay = true;
684
+ audioEl.style.display = "none";
685
+ document.body.appendChild(audioEl);
686
+ }
687
+ track.attach(audioEl);
688
+ };
689
+ return {
690
+ connect: async (url, token) => {
691
+ const r = new lk.Room({ adaptiveStream: false, dynacast: false });
692
+ room = r;
693
+ r.on(lk.RoomEvent.TrackSubscribed, (track) => attach(track));
694
+ await r.connect(url, token);
695
+ if (room !== r) {
696
+ return;
697
+ }
698
+ await r.localParticipant.setMicrophoneEnabled(!muted);
699
+ if (room !== r) {
700
+ return;
701
+ }
702
+ try {
703
+ await r.startAudio();
704
+ } catch {
705
+ }
706
+ },
707
+ disconnect: () => {
708
+ const r = room;
709
+ room = null;
710
+ if (r) void r.disconnect().catch(() => void 0);
711
+ audioEl?.remove();
712
+ audioEl = null;
713
+ },
714
+ setMuted: (m) => {
715
+ muted = m;
716
+ const r = room;
717
+ if (!r) return;
718
+ void r.localParticipant.setMicrophoneEnabled(!m).catch((e) => opts.onError?.(toError(e)));
719
+ }
720
+ };
721
+ }
722
+
661
723
  // src/VoiceClient.ts
724
+ var parseMediaFrame = (raw) => {
725
+ if (!raw.includes('"livekit_')) return null;
726
+ try {
727
+ const m = JSON.parse(raw);
728
+ if (m.type === "livekit_join" && typeof m.url === "string" && typeof m.token === "string") {
729
+ return { type: "livekit_join", url: m.url, token: m.token };
730
+ }
731
+ if (m.type === "livekit_fallback") {
732
+ return {
733
+ type: "livekit_fallback",
734
+ reason: typeof m.reason === "string" ? m.reason : void 0
735
+ };
736
+ }
737
+ } catch {
738
+ }
739
+ return null;
740
+ };
662
741
  var BrowserVoiceClient = class {
663
742
  constructor(args) {
664
743
  this.rws = null;
@@ -670,6 +749,9 @@ var BrowserVoiceClient = class {
670
749
  this.startedAt = null;
671
750
  this.endedFired = false;
672
751
  this.lastError = null;
752
+ this.mediaMode = "ws";
753
+ this.lkMedia = null;
754
+ this.lkJoinSeen = false;
673
755
  this.end = () => {
674
756
  this.teardown("user_hangup");
675
757
  };
@@ -677,11 +759,73 @@ var BrowserVoiceClient = class {
677
759
  if (this.muted) return;
678
760
  this.muted = true;
679
761
  this.capture?.mute(true);
762
+ this.lkMedia?.setMuted(true);
680
763
  };
681
764
  this.unmute = () => {
682
765
  if (!this.muted) return;
683
766
  this.muted = false;
684
767
  this.capture?.mute(false);
768
+ this.lkMedia?.setMuted(false);
769
+ };
770
+ this.ensureWsPlayback = async () => {
771
+ if (this.playback) return;
772
+ const createPlayback = this.args.audio?.createPlayback ?? createAudioPlayback;
773
+ this.playback = createPlayback({
774
+ onVolume: (v) => {
775
+ this.outputVolume = v;
776
+ this.args.options.onVolume?.({ input: this.inputVolume, output: v });
777
+ }
778
+ });
779
+ try {
780
+ await this.playback.resume();
781
+ } catch {
782
+ }
783
+ };
784
+ // ---------------------------------------------------------------
785
+ // LiveKit media lifecycle (phase 35)
786
+ // ---------------------------------------------------------------
787
+ this.joinLiveKit = async (url, token) => {
788
+ this.lkJoinSeen = true;
789
+ const lk = this.args.media?.lk;
790
+ if (!lk) {
791
+ this.fallbackToWs("livekit-client unavailable");
792
+ return;
793
+ }
794
+ const media = createLiveKitMedia(lk, {
795
+ onError: (e) => this.emitError({ code: "mic_start_failed", message: e.message })
796
+ });
797
+ this.lkMedia = media;
798
+ if (this.muted) media.setMuted(true);
799
+ try {
800
+ await media.connect(url, token);
801
+ if (this.lkMedia !== media) return;
802
+ this.mediaMode = "livekit";
803
+ this.rws?.send(JSON.stringify({ type: "livekit_ready" }));
804
+ } catch (err) {
805
+ const reason = err instanceof Error ? err.message : String(err);
806
+ media.disconnect();
807
+ if (this.lkMedia === media) this.lkMedia = null;
808
+ this.rws?.send(JSON.stringify({ type: "livekit_failed", reason }));
809
+ this.fallbackToWs(reason);
810
+ }
811
+ };
812
+ // Idempotent: whichever side gives up first (client join error, server
813
+ // livekit_fallback, `connected` without a join) lands here once.
814
+ this.fallbackToWs = (reason) => {
815
+ if (this.mediaMode === "ws") return;
816
+ this.mediaMode = "ws";
817
+ this.lkMedia?.disconnect();
818
+ this.lkMedia = null;
819
+ void this.ensureWsPlayback();
820
+ void this.startCapture();
821
+ this.args.options.onTransportChange?.({ requested: "livekit", actual: "ws", reason });
822
+ };
823
+ this.resetLiveKitForReconnect = () => {
824
+ if (this.args.media?.kind !== "livekit") return;
825
+ this.lkMedia?.disconnect();
826
+ this.lkMedia = null;
827
+ this.lkJoinSeen = false;
828
+ this.mediaMode = "livekit-pending";
685
829
  };
686
830
  // ---------------------------------------------------------------
687
831
  // Internal
@@ -702,17 +846,27 @@ var BrowserVoiceClient = class {
702
846
  this.handleSocketEvent = (ev) => {
703
847
  switch (ev.type) {
704
848
  case "open":
705
- void this.startCapture();
849
+ if (this.mediaMode === "ws") void this.startCapture();
706
850
  break;
707
851
  case "reconnected":
708
852
  this.proto.transcript = [];
709
853
  this.proto.agentBubbleId = null;
710
854
  this.args.options.onTranscript?.(this.proto.transcript);
711
- void this.startCapture();
855
+ this.resetLiveKitForReconnect();
856
+ if (this.mediaMode === "ws") void this.startCapture();
712
857
  this.setState("listening");
713
858
  break;
714
859
  case "message":
715
860
  if (typeof ev.data === "string") {
861
+ const media = parseMediaFrame(ev.data);
862
+ if (media?.type === "livekit_join") {
863
+ if (this.mediaMode === "livekit-pending") void this.joinLiveKit(media.url, media.token);
864
+ break;
865
+ }
866
+ if (media?.type === "livekit_fallback") {
867
+ this.fallbackToWs(media.reason ?? "server_fallback");
868
+ break;
869
+ }
716
870
  handleServerMessage(ev.data, this.proto, {
717
871
  onState: this.setState,
718
872
  onTranscript: (entries) => this.args.options.onTranscript?.(entries),
@@ -729,7 +883,12 @@ var BrowserVoiceClient = class {
729
883
  if (typeof seq === "number") this.marks.onAgentTurnEnd(seq);
730
884
  },
731
885
  onCallEnd: (reason) => this.teardown(reason),
732
- onConnected: () => this.sendClientToolsRegister(),
886
+ onConnected: () => {
887
+ if (this.mediaMode === "livekit-pending" && !this.lkJoinSeen) {
888
+ this.fallbackToWs("server_declined");
889
+ }
890
+ this.sendClientToolsRegister();
891
+ },
733
892
  onClientToolCall: (frame) => dispatchClientToolCall(
734
893
  (f) => this.rws?.send(JSON.stringify(f)),
735
894
  this.args.options.clientTools ?? {},
@@ -737,6 +896,8 @@ var BrowserVoiceClient = class {
737
896
  )
738
897
  });
739
898
  } else {
899
+ if (this.mediaMode === "livekit") break;
900
+ if (!this.playback) void this.ensureWsPlayback();
740
901
  this.marks.markFirstAudibleOutput();
741
902
  this.playback?.enqueue(ev.data);
742
903
  }
@@ -754,7 +915,8 @@ var BrowserVoiceClient = class {
754
915
  };
755
916
  this.startCapture = async () => {
756
917
  if (this.capture?.isCapturing()) return;
757
- this.capture = createAudioCapture({
918
+ const createCapture = this.args.audio?.createCapture ?? createAudioCapture;
919
+ this.capture = createCapture({
758
920
  onChunk: (pcm) => {
759
921
  this.marks.markFirstOutboundAudio();
760
922
  this.rws?.send(pcm);
@@ -783,6 +945,8 @@ var BrowserVoiceClient = class {
783
945
  }
784
946
  this.capture?.stop();
785
947
  this.capture = null;
948
+ this.lkMedia?.disconnect();
949
+ this.lkMedia = null;
786
950
  this.playback?.close();
787
951
  this.playback = null;
788
952
  try {
@@ -835,28 +999,17 @@ var BrowserVoiceClient = class {
835
999
  async start() {
836
1000
  this.setState("connecting");
837
1001
  this.startedAt = Date.now();
1002
+ this.mediaMode = this.args.media?.kind === "livekit" ? "livekit-pending" : "ws";
838
1003
  const url = buildWsUrl({
839
1004
  apiBase: this.args.config.apiBase,
840
1005
  agentId: this.args.options.agentId,
841
1006
  token: this.args.token,
842
- bargeIn: this.args.options.bargeIn
843
- });
844
- this.playback = createAudioPlayback({
845
- onVolume: (v) => {
846
- this.outputVolume = v;
847
- this.args.options.onVolume?.({ input: this.inputVolume, output: v });
848
- }
1007
+ bargeIn: this.args.options.bargeIn,
1008
+ media: this.mediaMode === "ws" ? void 0 : "livekit"
849
1009
  });
850
- try {
851
- await this.playback.resume();
852
- } catch {
853
- }
1010
+ if (this.mediaMode === "ws") await this.ensureWsPlayback();
854
1011
  this.rws = createReconnectingWebSocket(
855
- {
856
- url,
857
- wsFactory: this.args.wsFactory,
858
- maxRetries: 3
859
- },
1012
+ { url, wsFactory: this.args.wsFactory, maxRetries: 3 },
860
1013
  (ev) => this.handleSocketEvent(ev)
861
1014
  );
862
1015
  }
@@ -1041,6 +1194,20 @@ async function createWebRtcCall(opts) {
1041
1194
  };
1042
1195
  }
1043
1196
 
1197
+ // src/livekit/loadLiveKit.ts
1198
+ var lkPromise;
1199
+ var loadLiveKit = (requiredBy = "this feature") => {
1200
+ lkPromise ?? (lkPromise = import("livekit-client").catch((e) => {
1201
+ lkPromise = void 0;
1202
+ const wrapped = new Error(
1203
+ `${requiredBy} requires the optional peer dependency 'livekit-client' \u2014 npm install livekit-client`
1204
+ );
1205
+ wrapped.cause = e;
1206
+ throw wrapped;
1207
+ }));
1208
+ return lkPromise;
1209
+ };
1210
+
1044
1211
  // src/textSession.ts
1045
1212
  async function startTextSession(opts) {
1046
1213
  const f = opts.fetch ?? fetch;
@@ -1171,6 +1338,27 @@ var AssistantVoiceFactory = class {
1171
1338
  clientTools: options.clientTools
1172
1339
  });
1173
1340
  }
1341
+ if (resolved.transport === "livekit") {
1342
+ let lk = null;
1343
+ let loadError = "";
1344
+ try {
1345
+ lk = await loadLiveKit("transport 'livekit'");
1346
+ } catch (e) {
1347
+ loadError = e instanceof Error ? e.message : String(e);
1348
+ }
1349
+ if (!lk) {
1350
+ options.onTransportChange?.({ requested: "livekit", actual: "ws", reason: loadError });
1351
+ }
1352
+ const client2 = new BrowserVoiceClient({
1353
+ config: this.config,
1354
+ options: { ...options, context, metadata },
1355
+ token: resolved.token,
1356
+ wsFactory: browserWsFactory,
1357
+ media: lk ? { kind: "livekit", lk } : void 0
1358
+ });
1359
+ await client2.start();
1360
+ return client2;
1361
+ }
1174
1362
  const client = new BrowserVoiceClient({
1175
1363
  config: this.config,
1176
1364
  // Carry merged context/metadata through to startCall so server can