@craftedxp/voice-js 0.4.1 → 0.5.4

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/node.d.mts CHANGED
@@ -1,3 +1,5 @@
1
+ import { RemoteTrack, LocalVideoTrack } from 'livekit-client';
2
+
1
3
  interface ClientTool {
2
4
  description: string;
3
5
  parameters: Record<string, unknown>;
@@ -79,6 +81,145 @@ interface BuildWsUrlArgs {
79
81
  }
80
82
  declare function buildWsUrl(args: BuildWsUrlArgs): string;
81
83
 
84
+ type SystemMessage = {
85
+ kind: 'room.starting';
86
+ at: string;
87
+ } | {
88
+ kind: 'room.ending.soon';
89
+ minutesRemaining: 5 | 1;
90
+ } | {
91
+ kind: 'room.ended';
92
+ reason: 'duration_reached' | 'manual' | 'empty';
93
+ } | {
94
+ kind: 'role.promoted';
95
+ participantId: string;
96
+ name: string;
97
+ } | {
98
+ kind: 'role.demoted';
99
+ participantId: string;
100
+ name: string;
101
+ } | {
102
+ kind: 'participant.removed';
103
+ participantId: string;
104
+ name: string;
105
+ byHost?: string;
106
+ } | {
107
+ kind: 'notetaker.connected';
108
+ } | {
109
+ kind: 'notetaker.disconnected';
110
+ } | {
111
+ kind: 'notetaker.partial_degraded';
112
+ participantId: string;
113
+ };
114
+ type TranscriptMessage = {
115
+ kind: 'partial';
116
+ participantId: string;
117
+ speakerName: string;
118
+ text: string;
119
+ startedAt: string;
120
+ };
121
+
122
+ interface JoinRoomOptions {
123
+ /** Full HTTPS URL of the Voissia server. Same shape as VoiceClientConfig.apiBase. */
124
+ apiBase: string;
125
+ /** Server-generated room id (`rm_…`). */
126
+ roomId: string;
127
+ /** Shared room join token from the invite link. Mints a fresh participant each call. */
128
+ joinCode: string;
129
+ /** Display name the joiner registers under for this participant. */
130
+ name: string;
131
+ }
132
+ interface RoomParticipantInfo {
133
+ /** Stable participant id (`p_…`); strips any `guest:` LiveKit identity prefix. */
134
+ participantId: string;
135
+ /** Display name as the worker registered it; may be empty. */
136
+ name: string;
137
+ }
138
+ type RoomTrackKind = 'audio' | 'video';
139
+ /** What a track is — lets consumers tell a camera apart from a screen share
140
+ * (a participant can publish both at once). Mirrors livekit `Track.Source`. */
141
+ type RoomTrackSource = 'camera' | 'microphone' | 'screen_share' | 'screen_share_audio' | 'unknown';
142
+ interface RoomTrackEvent {
143
+ /** Stable participant id (`p_…`); `guest:` prefix stripped. */
144
+ participantId: string;
145
+ kind: RoomTrackKind;
146
+ /** Distinguishes camera vs screen_share so each can render as its own tile. */
147
+ source: RoomTrackSource;
148
+ /** livekit-client track — call `.attach(el)` / `.detach()` to render. */
149
+ track: RemoteTrack;
150
+ }
151
+ type RoomEventName = 'participant.joined' | 'participant.left' | 'transcript.partial' | 'transcript.final' | 'system.message' | 'room.ended' | 'track.subscribed' | 'track.unsubscribed' | 'active.speakers';
152
+ interface RoomEventPayloads {
153
+ 'participant.joined': RoomParticipantInfo;
154
+ 'participant.left': RoomParticipantInfo;
155
+ 'transcript.partial': TranscriptMessage;
156
+ /**
157
+ * Reserved — the worker currently emits only partials over the transcript
158
+ * topic. Final-utterance events will land in Phase 8 once the worker
159
+ * publishes a `final` kind; the SDK keeps the slot reserved so consumers
160
+ * can register handlers today.
161
+ */
162
+ 'transcript.final': {
163
+ participantId: string;
164
+ speakerName: string;
165
+ text: string;
166
+ startedAt: string;
167
+ };
168
+ 'system.message': SystemMessage;
169
+ 'room.ended': undefined;
170
+ 'track.subscribed': RoomTrackEvent;
171
+ 'track.unsubscribed': RoomTrackEvent;
172
+ /** participantIds currently speaking (drives an active-speaker UI). */
173
+ 'active.speakers': string[];
174
+ }
175
+ type Handler<E extends RoomEventName> = (payload: RoomEventPayloads[E]) => void;
176
+ interface RoomSession {
177
+ /** This session's own stable participant id (`p_…`). Useful to filter
178
+ * yourself out of `active.speakers`, which includes the local participant. */
179
+ readonly participantId: string;
180
+ /** Snapshot of the remote participants currently connected. */
181
+ readonly participants: RoomParticipantInfo[];
182
+ /** Subscribe to a typed event. No unsubscribe surface yet (mirrors `Call.onX`). */
183
+ on<E extends RoomEventName>(event: E, handler: Handler<E>): void;
184
+ /** Publish the local mic track. Resolves once the track is live on LiveKit. */
185
+ publishMic(): Promise<void>;
186
+ /** Publish the local camera track. */
187
+ publishCamera(): Promise<void>;
188
+ /** Mid-call mute/unmute of the local mic. */
189
+ setMicEnabled(on: boolean): Promise<void>;
190
+ /** Mid-call camera on/off. */
191
+ setCameraEnabled(on: boolean): Promise<void>;
192
+ /** Current local mic state (for toggle UI). */
193
+ isMicEnabled(): boolean;
194
+ /** Current local camera state (for toggle UI). */
195
+ isCameraEnabled(): boolean;
196
+ /** The local camera track for self-view, or null before publishCamera resolves. */
197
+ getLocalCameraTrack(): LocalVideoTrack | null;
198
+ /**
199
+ * Remote tracks already subscribed at this moment. A late joiner misses the
200
+ * live `track.subscribed` events for tracks published before it connected
201
+ * (LiveKit delivers them during `connect`, before consumer listeners attach).
202
+ * Call this right after registering `track.subscribed` to backfill them.
203
+ */
204
+ getRemoteTracks(): RoomTrackEvent[];
205
+ /**
206
+ * Start/stop sharing the screen (via `getDisplayMedia`). Pass `{ audio: true }`
207
+ * to also capture shared/system audio where the browser allows it (Chrome:
208
+ * tab or system audio; macOS Chrome is tab-audio only; Safari/Firefox don't
209
+ * capture share audio). Publishes a `screen_share` video track (+ optional
210
+ * `screen_share_audio`); remote peers receive them via `track.subscribed`.
211
+ */
212
+ setScreenShareEnabled(on: boolean, opts?: {
213
+ audio?: boolean;
214
+ }): Promise<void>;
215
+ /** Current local screen-share state (for toggle UI). */
216
+ isScreenShareEnabled(): boolean;
217
+ /** The local screen-share video track for self-preview, or null when off. */
218
+ getLocalScreenTrack(): LocalVideoTrack | null;
219
+ /** Disconnect from LiveKit. Idempotent. Triggers `room.ended` via Disconnected. */
220
+ leave(): Promise<void>;
221
+ }
222
+
82
223
  interface FetchTokenArgs {
83
224
  /** The agent the SDK is about to call. */
84
225
  agentId: string;
@@ -228,6 +369,16 @@ interface VoiceClientFactory {
228
369
  * don't reject this promise.
229
370
  */
230
371
  startCall: (options: StartCallOptions) => Promise<Call>;
372
+ /**
373
+ * Phase 7 (multi-party rooms). Browser only. Exchange a single-use
374
+ * joinCode for a LiveKit JWT and connect to the room. The returned
375
+ * `RoomSession` exposes a typed event surface
376
+ * (participant.joined / participant.left / transcript.partial /
377
+ * transcript.final / system.message / room.ended) plus
378
+ * publishMic / publishCamera / leave. The Node bundle does NOT
379
+ * implement this — livekit-client is a browser-only WebRTC client.
380
+ */
381
+ joinRoom?: (options: Omit<JoinRoomOptions, 'apiBase'>) => Promise<RoomSession>;
231
382
  }
232
383
 
233
384
  type RWSEvent = {
@@ -308,6 +459,31 @@ interface NodeVoiceClientFactory {
308
459
  startCall: (options: NodeStartCallOptions) => Promise<NodeCall>;
309
460
  }
310
461
 
462
+ /**
463
+ * Canonical payload a tenant places in their VoIP/FCM push so an
464
+ * agent-initiated call can connect. It is the `callTokens.mint` result
465
+ * (token + transport) plus two optional display fields for the native
466
+ * incoming-call UI. The host receives the push, runs `parseIncomingCall`,
467
+ * and on accept passes `token` (+ transport / webrtcGatewayBase) into the
468
+ * voice client. Web background-wake is best-effort (Web Push); native is
469
+ * the real target. See docs/sdks.md "Agent-initiated calls".
470
+ */
471
+ interface IncomingCallPayload {
472
+ token: string;
473
+ agentId: string;
474
+ transport: 'ws' | 'webrtc';
475
+ webrtcGatewayBase?: string;
476
+ expiresAt?: number;
477
+ agentName?: string;
478
+ agentAvatarUrl?: string;
479
+ }
480
+ /**
481
+ * Validate + normalise a raw push payload into an IncomingCallPayload.
482
+ * Throws synchronously on malformed input. Unknown transports fall back to
483
+ * 'ws'; webrtcGatewayBase is ignored unless transport === 'webrtc'.
484
+ */
485
+ declare const parseIncomingCall: (raw: unknown) => IncomingCallPayload;
486
+
311
487
  /**
312
488
  * One-time SDK setup for Node.js / Electron-main consumers. Returns a
313
489
  * factory you call `startCall` on for every voice call. Same shape as
@@ -338,4 +514,4 @@ interface NodeVoiceClientFactory {
338
514
  */
339
515
  declare function configureVoiceClient(config: VoiceClientConfig): NodeVoiceClientFactory;
340
516
 
341
- export { type Call, type CallEndEvent, type CallEndReason, type CallError, type CallErrorCode, type CallState, type ClientTool, type ClientToolMap, type FetchToken, type FetchTokenArgs, type FetchTokenResult, type NodeCall, type NodeStartCallOptions, type NodeVoiceClientFactory, type ProtocolCallbacks, type ProtocolState, type RWSEvent, type RWSOptions, type ReconnectingWebSocket, type ServerMessage, type StartCallOptions, type TranscriptEntry, type VoiceClientConfig, type VoiceClientFactory, type VolumeEvent, type WebSocketFactory, type WebSocketLike, buildWsUrl, configureVoiceClient, createProtocolState, createReconnectingWebSocket, handleServerMessage };
517
+ export { type Call, type CallEndEvent, type CallEndReason, type CallError, type CallErrorCode, type CallState, type ClientTool, type ClientToolMap, type FetchToken, type FetchTokenArgs, type FetchTokenResult, type IncomingCallPayload, type NodeCall, type NodeStartCallOptions, type NodeVoiceClientFactory, type ProtocolCallbacks, type ProtocolState, type RWSEvent, type RWSOptions, type ReconnectingWebSocket, type ServerMessage, type StartCallOptions, type TranscriptEntry, type VoiceClientConfig, type VoiceClientFactory, type VolumeEvent, type WebSocketFactory, type WebSocketLike, buildWsUrl, configureVoiceClient, createProtocolState, createReconnectingWebSocket, handleServerMessage, parseIncomingCall };
package/dist/node.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { RemoteTrack, LocalVideoTrack } from 'livekit-client'
2
+
1
3
  interface ClientTool {
2
4
  description: string
3
5
  parameters: Record<string, unknown>
@@ -104,6 +106,166 @@ interface BuildWsUrlArgs {
104
106
  }
105
107
  declare function buildWsUrl(args: BuildWsUrlArgs): string
106
108
 
109
+ type SystemMessage =
110
+ | {
111
+ kind: 'room.starting'
112
+ at: string
113
+ }
114
+ | {
115
+ kind: 'room.ending.soon'
116
+ minutesRemaining: 5 | 1
117
+ }
118
+ | {
119
+ kind: 'room.ended'
120
+ reason: 'duration_reached' | 'manual' | 'empty'
121
+ }
122
+ | {
123
+ kind: 'role.promoted'
124
+ participantId: string
125
+ name: string
126
+ }
127
+ | {
128
+ kind: 'role.demoted'
129
+ participantId: string
130
+ name: string
131
+ }
132
+ | {
133
+ kind: 'participant.removed'
134
+ participantId: string
135
+ name: string
136
+ byHost?: string
137
+ }
138
+ | {
139
+ kind: 'notetaker.connected'
140
+ }
141
+ | {
142
+ kind: 'notetaker.disconnected'
143
+ }
144
+ | {
145
+ kind: 'notetaker.partial_degraded'
146
+ participantId: string
147
+ }
148
+ type TranscriptMessage = {
149
+ kind: 'partial'
150
+ participantId: string
151
+ speakerName: string
152
+ text: string
153
+ startedAt: string
154
+ }
155
+
156
+ interface JoinRoomOptions {
157
+ /** Full HTTPS URL of the Voissia server. Same shape as VoiceClientConfig.apiBase. */
158
+ apiBase: string
159
+ /** Server-generated room id (`rm_…`). */
160
+ roomId: string
161
+ /** Shared room join token from the invite link. Mints a fresh participant each call. */
162
+ joinCode: string
163
+ /** Display name the joiner registers under for this participant. */
164
+ name: string
165
+ }
166
+ interface RoomParticipantInfo {
167
+ /** Stable participant id (`p_…`); strips any `guest:` LiveKit identity prefix. */
168
+ participantId: string
169
+ /** Display name as the worker registered it; may be empty. */
170
+ name: string
171
+ }
172
+ type RoomTrackKind = 'audio' | 'video'
173
+ /** What a track is — lets consumers tell a camera apart from a screen share
174
+ * (a participant can publish both at once). Mirrors livekit `Track.Source`. */
175
+ type RoomTrackSource = 'camera' | 'microphone' | 'screen_share' | 'screen_share_audio' | 'unknown'
176
+ interface RoomTrackEvent {
177
+ /** Stable participant id (`p_…`); `guest:` prefix stripped. */
178
+ participantId: string
179
+ kind: RoomTrackKind
180
+ /** Distinguishes camera vs screen_share so each can render as its own tile. */
181
+ source: RoomTrackSource
182
+ /** livekit-client track — call `.attach(el)` / `.detach()` to render. */
183
+ track: RemoteTrack
184
+ }
185
+ type RoomEventName =
186
+ | 'participant.joined'
187
+ | 'participant.left'
188
+ | 'transcript.partial'
189
+ | 'transcript.final'
190
+ | 'system.message'
191
+ | 'room.ended'
192
+ | 'track.subscribed'
193
+ | 'track.unsubscribed'
194
+ | 'active.speakers'
195
+ interface RoomEventPayloads {
196
+ 'participant.joined': RoomParticipantInfo
197
+ 'participant.left': RoomParticipantInfo
198
+ 'transcript.partial': TranscriptMessage
199
+ /**
200
+ * Reserved — the worker currently emits only partials over the transcript
201
+ * topic. Final-utterance events will land in Phase 8 once the worker
202
+ * publishes a `final` kind; the SDK keeps the slot reserved so consumers
203
+ * can register handlers today.
204
+ */
205
+ 'transcript.final': {
206
+ participantId: string
207
+ speakerName: string
208
+ text: string
209
+ startedAt: string
210
+ }
211
+ 'system.message': SystemMessage
212
+ 'room.ended': undefined
213
+ 'track.subscribed': RoomTrackEvent
214
+ 'track.unsubscribed': RoomTrackEvent
215
+ /** participantIds currently speaking (drives an active-speaker UI). */
216
+ 'active.speakers': string[]
217
+ }
218
+ type Handler<E extends RoomEventName> = (payload: RoomEventPayloads[E]) => void
219
+ interface RoomSession {
220
+ /** This session's own stable participant id (`p_…`). Useful to filter
221
+ * yourself out of `active.speakers`, which includes the local participant. */
222
+ readonly participantId: string
223
+ /** Snapshot of the remote participants currently connected. */
224
+ readonly participants: RoomParticipantInfo[]
225
+ /** Subscribe to a typed event. No unsubscribe surface yet (mirrors `Call.onX`). */
226
+ on<E extends RoomEventName>(event: E, handler: Handler<E>): void
227
+ /** Publish the local mic track. Resolves once the track is live on LiveKit. */
228
+ publishMic(): Promise<void>
229
+ /** Publish the local camera track. */
230
+ publishCamera(): Promise<void>
231
+ /** Mid-call mute/unmute of the local mic. */
232
+ setMicEnabled(on: boolean): Promise<void>
233
+ /** Mid-call camera on/off. */
234
+ setCameraEnabled(on: boolean): Promise<void>
235
+ /** Current local mic state (for toggle UI). */
236
+ isMicEnabled(): boolean
237
+ /** Current local camera state (for toggle UI). */
238
+ isCameraEnabled(): boolean
239
+ /** The local camera track for self-view, or null before publishCamera resolves. */
240
+ getLocalCameraTrack(): LocalVideoTrack | null
241
+ /**
242
+ * Remote tracks already subscribed at this moment. A late joiner misses the
243
+ * live `track.subscribed` events for tracks published before it connected
244
+ * (LiveKit delivers them during `connect`, before consumer listeners attach).
245
+ * Call this right after registering `track.subscribed` to backfill them.
246
+ */
247
+ getRemoteTracks(): RoomTrackEvent[]
248
+ /**
249
+ * Start/stop sharing the screen (via `getDisplayMedia`). Pass `{ audio: true }`
250
+ * to also capture shared/system audio where the browser allows it (Chrome:
251
+ * tab or system audio; macOS Chrome is tab-audio only; Safari/Firefox don't
252
+ * capture share audio). Publishes a `screen_share` video track (+ optional
253
+ * `screen_share_audio`); remote peers receive them via `track.subscribed`.
254
+ */
255
+ setScreenShareEnabled(
256
+ on: boolean,
257
+ opts?: {
258
+ audio?: boolean
259
+ },
260
+ ): Promise<void>
261
+ /** Current local screen-share state (for toggle UI). */
262
+ isScreenShareEnabled(): boolean
263
+ /** The local screen-share video track for self-preview, or null when off. */
264
+ getLocalScreenTrack(): LocalVideoTrack | null
265
+ /** Disconnect from LiveKit. Idempotent. Triggers `room.ended` via Disconnected. */
266
+ leave(): Promise<void>
267
+ }
268
+
107
269
  interface FetchTokenArgs {
108
270
  /** The agent the SDK is about to call. */
109
271
  agentId: string
@@ -253,6 +415,16 @@ interface VoiceClientFactory {
253
415
  * don't reject this promise.
254
416
  */
255
417
  startCall: (options: StartCallOptions) => Promise<Call>
418
+ /**
419
+ * Phase 7 (multi-party rooms). Browser only. Exchange a single-use
420
+ * joinCode for a LiveKit JWT and connect to the room. The returned
421
+ * `RoomSession` exposes a typed event surface
422
+ * (participant.joined / participant.left / transcript.partial /
423
+ * transcript.final / system.message / room.ended) plus
424
+ * publishMic / publishCamera / leave. The Node bundle does NOT
425
+ * implement this — livekit-client is a browser-only WebRTC client.
426
+ */
427
+ joinRoom?: (options: Omit<JoinRoomOptions, 'apiBase'>) => Promise<RoomSession>
256
428
  }
257
429
 
258
430
  type RWSEvent =
@@ -336,6 +508,31 @@ interface NodeVoiceClientFactory {
336
508
  startCall: (options: NodeStartCallOptions) => Promise<NodeCall>
337
509
  }
338
510
 
511
+ /**
512
+ * Canonical payload a tenant places in their VoIP/FCM push so an
513
+ * agent-initiated call can connect. It is the `callTokens.mint` result
514
+ * (token + transport) plus two optional display fields for the native
515
+ * incoming-call UI. The host receives the push, runs `parseIncomingCall`,
516
+ * and on accept passes `token` (+ transport / webrtcGatewayBase) into the
517
+ * voice client. Web background-wake is best-effort (Web Push); native is
518
+ * the real target. See docs/sdks.md "Agent-initiated calls".
519
+ */
520
+ interface IncomingCallPayload {
521
+ token: string
522
+ agentId: string
523
+ transport: 'ws' | 'webrtc'
524
+ webrtcGatewayBase?: string
525
+ expiresAt?: number
526
+ agentName?: string
527
+ agentAvatarUrl?: string
528
+ }
529
+ /**
530
+ * Validate + normalise a raw push payload into an IncomingCallPayload.
531
+ * Throws synchronously on malformed input. Unknown transports fall back to
532
+ * 'ws'; webrtcGatewayBase is ignored unless transport === 'webrtc'.
533
+ */
534
+ declare const parseIncomingCall: (raw: unknown) => IncomingCallPayload
535
+
339
536
  /**
340
537
  * One-time SDK setup for Node.js / Electron-main consumers. Returns a
341
538
  * factory you call `startCall` on for every voice call. Same shape as
@@ -378,6 +575,7 @@ export {
378
575
  type FetchToken,
379
576
  type FetchTokenArgs,
380
577
  type FetchTokenResult,
578
+ type IncomingCallPayload,
381
579
  type NodeCall,
382
580
  type NodeStartCallOptions,
383
581
  type NodeVoiceClientFactory,
@@ -399,4 +597,5 @@ export {
399
597
  createProtocolState,
400
598
  createReconnectingWebSocket,
401
599
  handleServerMessage,
600
+ parseIncomingCall,
402
601
  }
package/dist/node.js CHANGED
@@ -42,6 +42,7 @@ __export(node_exports, {
42
42
  createProtocolState: () => createProtocolState,
43
43
  createReconnectingWebSocket: () => createReconnectingWebSocket,
44
44
  handleServerMessage: () => handleServerMessage,
45
+ parseIncomingCall: () => parseIncomingCall,
45
46
  })
46
47
  module.exports = __toCommonJS(node_exports)
47
48
 
@@ -629,6 +630,29 @@ var NodeVoiceClient = class {
629
630
  }
630
631
  }
631
632
 
633
+ // src/incomingCall.ts
634
+ var parseIncomingCall = (raw) => {
635
+ if (typeof raw !== 'object' || raw === null) {
636
+ throw new Error('parseIncomingCall: payload must be an object')
637
+ }
638
+ const p = raw
639
+ if (typeof p.token !== 'string' || !p.token.startsWith('ct_')) {
640
+ throw new Error('parseIncomingCall: missing or invalid `token` (expected a ct_ string)')
641
+ }
642
+ if (typeof p.agentId !== 'string' || p.agentId.length === 0) {
643
+ throw new Error('parseIncomingCall: missing `agentId`')
644
+ }
645
+ const transport = p.transport === 'webrtc' ? 'webrtc' : 'ws'
646
+ const out = { token: p.token, agentId: p.agentId, transport }
647
+ if (transport === 'webrtc' && typeof p.webrtcGatewayBase === 'string') {
648
+ out.webrtcGatewayBase = p.webrtcGatewayBase
649
+ }
650
+ if (typeof p.expiresAt === 'number') out.expiresAt = p.expiresAt
651
+ if (typeof p.agentName === 'string') out.agentName = p.agentName
652
+ if (typeof p.agentAvatarUrl === 'string') out.agentAvatarUrl = p.agentAvatarUrl
653
+ return out
654
+ }
655
+
632
656
  // src/node.ts
633
657
  var cachedWsCtor = null
634
658
  var loadWsCtor = async () => {
@@ -675,6 +699,11 @@ var NodeVoiceFactory = class {
675
699
  if (!token) {
676
700
  throw new Error('configureVoiceClient.fetchToken returned an object without `token`')
677
701
  }
702
+ if (typeof r !== 'string' && r.transport === 'webrtc') {
703
+ console.warn(
704
+ '@craftedxp/voice-js (node): agent is configured for WebRTC but the Node bundle only supports WebSocket \u2014 falling back to WS. Use the browser bundle for WebRTC transport.',
705
+ )
706
+ }
678
707
  }
679
708
  const client = new NodeVoiceClient({
680
709
  config: this.config,
@@ -699,5 +728,6 @@ function configureVoiceClient(config) {
699
728
  createProtocolState,
700
729
  createReconnectingWebSocket,
701
730
  handleServerMessage,
731
+ parseIncomingCall,
702
732
  })
703
733
  //# sourceMappingURL=node.js.map