@craftedxp/voice-js 0.5.4 → 0.6.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/browser.d.ts CHANGED
@@ -1,291 +1,300 @@
1
- import { RemoteTrack, LocalVideoTrack } from 'livekit-client'
1
+ import { RemoteTrack, LocalVideoTrack } from 'livekit-client';
2
2
 
3
3
  interface ClientTool {
4
- description: string
5
- parameters: Record<string, unknown>
6
- usage?: string
7
- timeoutMs?: number
8
- example?: string
9
- handler: (args: Record<string, unknown>) => Promise<string | object> | string | object
4
+ description: string;
5
+ parameters: Record<string, unknown>;
6
+ usage?: string;
7
+ timeoutMs?: number;
8
+ example?: string;
9
+ handler: (args: Record<string, unknown>) => Promise<string | object> | string | object;
10
10
  }
11
- type ClientToolMap = Record<string, ClientTool>
11
+ type ClientToolMap = Record<string, ClientTool>;
12
12
  interface ClientToolCallFrame {
13
- toolCallId: string
14
- name: string
15
- args: Record<string, unknown>
13
+ toolCallId: string;
14
+ name: string;
15
+ args: Record<string, unknown>;
16
16
  }
17
17
 
18
- type CallState =
19
- | 'idle'
20
- | 'connecting'
21
- | 'listening'
22
- | 'user_speaking'
23
- | 'agent_speaking'
24
- | 'ended'
25
- | 'error'
26
- type TranscriptEntry =
27
- | {
28
- id: string
29
- role: 'user'
30
- text: string
31
- committed: boolean
32
- }
33
- | {
34
- id: string
35
- role: 'agent'
36
- text: string
37
- interrupted?: boolean
38
- }
39
- | {
40
- id: string
41
- role: 'tool'
42
- text: string
43
- }
44
- | {
45
- id: string
46
- role: 'system'
47
- text: string
48
- }
49
- type CallErrorCode =
50
- | 'missing_credentials'
51
- | 'forbidden'
52
- | 'mic_denied'
53
- | 'mic_start_failed'
54
- | 'audio_session_failed'
55
- | 'token_expired'
56
- | 'token_invalid'
57
- | 'unauthorized'
58
- | 'network_unreachable'
59
- | 'socket_error'
60
- | 'payment_required'
61
- | 'not_found'
62
- | 'silence_timeout'
63
- | 'server_error'
18
+ type CallState = 'idle' | 'connecting' | 'listening' | 'user_speaking' | 'agent_speaking' | 'ended' | 'error';
19
+ type TranscriptEntry = {
20
+ id: string;
21
+ role: 'user';
22
+ text: string;
23
+ committed: boolean;
24
+ } | {
25
+ id: string;
26
+ role: 'agent';
27
+ text: string;
28
+ interrupted?: boolean;
29
+ } | {
30
+ id: string;
31
+ role: 'tool';
32
+ text: string;
33
+ } | {
34
+ id: string;
35
+ role: 'system';
36
+ text: string;
37
+ };
38
+ type CallErrorCode = 'missing_credentials' | 'forbidden' | 'mic_denied' | 'mic_start_failed' | 'audio_session_failed' | 'token_expired' | 'token_invalid' | 'unauthorized' | 'network_unreachable' | 'socket_error' | 'payment_required' | 'not_found' | 'silence_timeout' | 'server_error';
64
39
  interface CallError {
65
- code: CallErrorCode
66
- message: string
40
+ code: CallErrorCode;
41
+ message: string;
67
42
  }
68
- type CallEndReason = 'agent_ended' | 'user_hangup' | 'timeout' | 'error'
43
+ type CallEndReason = 'agent_ended' | 'user_hangup' | 'timeout' | 'error';
69
44
  interface CallEndEvent {
70
- reason: CallEndReason
71
- errorCode?: CallErrorCode
72
- durationMs: number
45
+ reason: CallEndReason;
46
+ errorCode?: CallErrorCode;
47
+ durationMs: number;
73
48
  }
74
49
  interface VolumeEvent {
75
- input: number
76
- output: number
50
+ input: number;
51
+ output: number;
77
52
  }
78
53
  type ServerMessage = Record<string, unknown> & {
79
- type?: string
80
- }
54
+ type?: string;
55
+ };
81
56
  interface ProtocolState {
82
- state: CallState
83
- transcript: TranscriptEntry[]
84
- agentBubbleId: string | null
85
- idCounter: number
86
- endReason: CallEndReason | null
57
+ state: CallState;
58
+ transcript: TranscriptEntry[];
59
+ agentBubbleId: string | null;
60
+ idCounter: number;
61
+ endReason: CallEndReason | null;
87
62
  }
88
- declare const createProtocolState: () => ProtocolState
63
+ declare const createProtocolState: () => ProtocolState;
89
64
  interface ProtocolCallbacks {
90
- onState: (next: CallState) => void
91
- onTranscript: (entries: TranscriptEntry[]) => void
92
- onError: (err: CallError) => void
93
- onInterrupt: () => void
94
- onAgentTurnStart: (seq?: number) => void
95
- onAgentTurnEnd: (seq?: number) => void
96
- onCallEnd: (reason: CallEndReason) => void
97
- onConnected: () => void
98
- onClientToolCall: (frame: ClientToolCallFrame) => void
65
+ onState: (next: CallState) => void;
66
+ onTranscript: (entries: TranscriptEntry[]) => void;
67
+ onError: (err: CallError) => void;
68
+ onInterrupt: () => void;
69
+ onAgentTurnStart: (seq?: number) => void;
70
+ onAgentTurnEnd: (seq?: number) => void;
71
+ onCallEnd: (reason: CallEndReason) => void;
72
+ onConnected: () => void;
73
+ onClientToolCall: (frame: ClientToolCallFrame) => void;
99
74
  }
100
- declare function handleServerMessage(raw: string, state: ProtocolState, cb: ProtocolCallbacks): void
75
+ declare function handleServerMessage(raw: string, state: ProtocolState, cb: ProtocolCallbacks): void;
101
76
  interface BuildWsUrlArgs {
102
- apiBase: string
103
- agentId: string
104
- token: string
105
- bargeIn?: boolean
77
+ apiBase: string;
78
+ agentId: string;
79
+ token: string;
80
+ bargeIn?: boolean;
106
81
  }
107
- declare function buildWsUrl(args: BuildWsUrlArgs): string
82
+ declare function buildWsUrl(args: BuildWsUrlArgs): string;
108
83
 
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
- }
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
+ };
148
114
  type TranscriptMessage = {
149
- kind: 'partial'
150
- participantId: string
151
- speakerName: string
152
- text: string
153
- startedAt: string
154
- }
115
+ kind: 'partial';
116
+ participantId: string;
117
+ speakerName: string;
118
+ text: string;
119
+ startedAt: string;
120
+ };
155
121
 
156
122
  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
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;
165
131
  }
166
132
  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
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;
171
137
  }
172
- type RoomTrackKind = 'audio' | 'video'
138
+ type RoomTrackKind = 'audio' | 'video';
173
139
  /** What a track is — lets consumers tell a camera apart from a screen share
174
140
  * (a participant can publish both at once). Mirrors livekit `Track.Source`. */
175
- type RoomTrackSource = 'camera' | 'microphone' | 'screen_share' | 'screen_share_audio' | 'unknown'
141
+ type RoomTrackSource = 'camera' | 'microphone' | 'screen_share' | 'screen_share_audio' | 'unknown';
176
142
  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
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;
184
150
  }
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'
151
+ type RoomEventName = 'participant.joined' | 'participant.left' | 'transcript.partial' | 'transcript.final' | 'system.message' | 'room.ended' | 'track.subscribed' | 'track.unsubscribed' | 'active.speakers';
195
152
  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[]
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[];
217
174
  }
218
- type Handler<E extends RoomEventName> = (payload: RoomEventPayloads[E]) => void
175
+ type Handler<E extends RoomEventName> = (payload: RoomEventPayloads[E]) => void;
219
176
  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>
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
+ declare const joinRoom: (opts: JoinRoomOptions) => Promise<RoomSession>;
223
+
224
+ /**
225
+ * Browser-friendly text-channel chat session. Mint a `ct_` token with
226
+ * `channel: 'text'` on your backend, then call `startTextSession({...})`
227
+ * to open the SSE stream.
228
+ *
229
+ * Each `.send(text)` is a fresh POST; SSE-per-turn means the connection
230
+ * closes when each turn ends. Conversation state lives server-side on the
231
+ * underlying CallRecord.
232
+ */
233
+ type ChatEvent = {
234
+ type: 'chat.started';
235
+ chatId: string;
236
+ callId: string;
237
+ } | {
238
+ type: 'token';
239
+ text: string;
240
+ } | {
241
+ type: 'tool.call';
242
+ name: string;
243
+ args: unknown;
244
+ } | {
245
+ type: 'tool.result';
246
+ name: string;
247
+ ok?: boolean;
248
+ [key: string]: unknown;
249
+ } | {
250
+ type: 'turn.end';
251
+ finishReason: 'stop' | 'aborted' | 'length' | 'tool_error';
252
+ committedText?: string;
253
+ } | {
254
+ type: 'error';
255
+ code: string;
256
+ message: string;
257
+ };
258
+ interface StartTextSessionOpts {
259
+ baseUrl: string;
260
+ token: string;
261
+ agentId: string;
262
+ /** Optional inline first user message; otherwise the agent's greeting opens the stream. */
263
+ text?: string;
264
+ /** Override the global fetch (useful for tests; defaults to globalThis.fetch). */
265
+ fetch?: typeof fetch;
266
+ }
267
+ interface TextSession {
268
+ id: string;
269
+ callId: string;
270
+ /** Async iterable for the opening turn — greeting tokens / first reply if text was inlined. */
271
+ greeting: AsyncIterable<ChatEvent>;
272
+ /** Send a user message; returns an async iterable for the agent's reply. */
273
+ send(text: string): Promise<AsyncIterable<ChatEvent>>;
274
+ /** End the session — DELETE /v1/calls/:callId. */
275
+ end(): Promise<void>;
267
276
  }
268
- declare const joinRoom: (opts: JoinRoomOptions) => Promise<RoomSession>
277
+ declare function startTextSession(opts: StartTextSessionOpts): Promise<TextSession>;
269
278
 
270
279
  interface FetchTokenArgs {
271
- /** The agent the SDK is about to call. */
272
- agentId: string
273
- /**
274
- * Optional consumer-side user identifier. Round-tripped to the server
275
- * as `contactId` for Phase 11 contact memory. The SDK does not
276
- * inspect this; your backend uses it to scope the token mint.
277
- */
278
- userId?: string
279
- /**
280
- * Per-call structured context lowered into the agent's effective
281
- * system prompt server-side at session open. Opaque to the SDK.
282
- */
283
- context?: Record<string, unknown>
284
- /**
285
- * String key/value pairs round-tripped on the `call.ended` webhook.
286
- * Capped at 1 KB total server-side. NOT lowered into the system prompt.
287
- */
288
- metadata?: Record<string, string>
280
+ /** The agent the SDK is about to call. */
281
+ agentId: string;
282
+ /**
283
+ * Optional consumer-side user identifier. Round-tripped to the server
284
+ * as `contactId` for Phase 11 contact memory. The SDK does not
285
+ * inspect this; your backend uses it to scope the token mint.
286
+ */
287
+ userId?: string;
288
+ /**
289
+ * Per-call structured context lowered into the agent's effective
290
+ * system prompt server-side at session open. Opaque to the SDK.
291
+ */
292
+ context?: Record<string, unknown>;
293
+ /**
294
+ * String key/value pairs round-tripped on the `call.ended` webhook.
295
+ * Capped at 1 KB total server-side. NOT lowered into the system prompt.
296
+ */
297
+ metadata?: Record<string, string>;
289
298
  }
290
299
  /**
291
300
  * What `fetchToken` may return. The rich object form lets the server
@@ -293,220 +302,226 @@ interface FetchTokenArgs {
293
302
  * compatible — the SDK treats it as `{ token, transport: 'ws' }`.
294
303
  */
295
304
  interface FetchTokenResult {
296
- /** Raw `ct_` to feed into the WS open / WebRTC offer. */
297
- token: string
298
- /** Server-selected transport. Default `'ws'` if absent. */
299
- transport?: 'ws' | 'webrtc'
300
- /** Required when `transport === 'webrtc'` AND the server uses a
301
- * separate signaling gateway. When omitted on a webrtc result, the
302
- * SDK falls back to the API base's Phase-1 routes (local dev). */
303
- webrtcGatewayBase?: string
305
+ /** Raw `ct_` to feed into the WS open / WebRTC offer. */
306
+ token: string;
307
+ /** Server-selected transport. Default `'ws'` if absent. */
308
+ transport?: 'ws' | 'webrtc';
309
+ /** Required when `transport === 'webrtc'` AND the server uses a
310
+ * separate signaling gateway. When omitted on a webrtc result, the
311
+ * SDK falls back to the API base's Phase-1 routes (local dev). */
312
+ webrtcGatewayBase?: string;
304
313
  }
305
- type FetchToken = (args: FetchTokenArgs) => Promise<string | FetchTokenResult>
314
+ type FetchToken = (args: FetchTokenArgs) => Promise<string | FetchTokenResult>;
306
315
  interface VoiceClientConfig {
307
- /**
308
- * Full HTTPS URL of the Voissia server. The WebSocket scheme is
309
- * derived: `https` → `wss`, `http` → `ws`. No trailing slash needed.
310
- */
311
- apiBase: string
312
- /**
313
- * Called by the SDK whenever it needs a fresh `ct_` token (initial
314
- * connect; mid-call refresh on `token_expired`). Your implementation
315
- * should hit YOUR backend, which holds the `sk_` API key and mints
316
- * via `POST /v1/call-tokens` (or `client.callTokens.mint` from
317
- * @craftedxp/sdk-node). Never embed `sk_` in JS code that ships to a
318
- * client.
319
- */
320
- fetchToken: FetchToken
321
- /**
322
- * Optional metadata applied to EVERY startCall. Per-call `metadata`
323
- * in `startCall` is merged on top (per-call wins on key conflicts).
324
- * Useful for dashboard-wide tags like `{ surface: 'web', appVersion }`.
325
- */
326
- defaultMetadata?: Record<string, string>
327
- /**
328
- * Optional context applied to EVERY startCall. Per-call `context` in
329
- * `startCall` is merged on top. Useful for cross-call invariants like
330
- * the signed-in user's locale.
331
- */
332
- defaultContext?: Record<string, unknown>
316
+ /**
317
+ * Full HTTPS URL of the Voissia server. The WebSocket scheme is
318
+ * derived: `https` → `wss`, `http` → `ws`. No trailing slash needed.
319
+ */
320
+ apiBase: string;
321
+ /**
322
+ * Called by the SDK whenever it needs a fresh `ct_` token (initial
323
+ * connect; mid-call refresh on `token_expired`). Your implementation
324
+ * should hit YOUR backend, which holds the `sk_` API key and mints
325
+ * via `POST /v1/call-tokens` (or `client.callTokens.mint` from
326
+ * @craftedxp/sdk-node). Never embed `sk_` in JS code that ships to a
327
+ * client.
328
+ */
329
+ fetchToken: FetchToken;
330
+ /**
331
+ * Optional metadata applied to EVERY startCall. Per-call `metadata`
332
+ * in `startCall` is merged on top (per-call wins on key conflicts).
333
+ * Useful for dashboard-wide tags like `{ surface: 'web', appVersion }`.
334
+ */
335
+ defaultMetadata?: Record<string, string>;
336
+ /**
337
+ * Optional context applied to EVERY startCall. Per-call `context` in
338
+ * `startCall` is merged on top. Useful for cross-call invariants like
339
+ * the signed-in user's locale.
340
+ */
341
+ defaultContext?: Record<string, unknown>;
333
342
  }
334
343
  interface StartCallOptions {
335
- /** The agent to call. */
336
- agentId: string
337
- /** Per-call user identifier. Round-tripped to fetchToken as `userId`. */
338
- userId?: string
339
- /**
340
- * Per-call structured context. Merged on top of `defaultContext`
341
- * configured at factory time.
342
- */
343
- context?: Record<string, unknown>
344
- /**
345
- * Per-call metadata. Merged on top of `defaultMetadata` configured
346
- * at factory time.
347
- */
348
- metadata?: Record<string, string>
349
- /**
350
- * When false, the SDK + server stay full-duplex but barge-in is
351
- * suppressed. Useful for alarm-style flows where the user shouldn't
352
- * accidentally interrupt the script. Default true.
353
- */
354
- bargeIn?: boolean
355
- /**
356
- * Client-side tools the agent's LLM can call mid-conversation. Each
357
- * tool's handler runs on the consumer's side; result is fed back to
358
- * the LLM through the existing call WebSocket. Schema and handler
359
- * colocate. Validated synchronously at startCall — bad input throws.
360
- *
361
- * See docs/integration-echocheck.md for the wire protocol and the
362
- * server-side guarantees.
363
- */
364
- clientTools?: ClientToolMap
365
- /**
366
- * Test-only escape hatch — pass a pre-minted `ct_` directly and skip
367
- * the `fetchToken` call. Don't use this in production code: tokens
368
- * expire and the SDK can't re-mint without the callback.
369
- */
370
- token?: string
371
- onStateChange?: (state: CallState) => void
372
- onTranscript?: (entries: TranscriptEntry[]) => void
373
- onError?: (err: CallError) => void
374
- onEnd?: (end: CallEndEvent) => void
375
- /** Volume-meter event for VU UIs. ~10 Hz cadence (browser bundle only). */
376
- onVolume?: (vol: VolumeEvent) => void
377
- /**
378
- * Fires when the server signals barge-in (the user started talking
379
- * mid-agent-turn). The browser bundle automatically flushes its
380
- * built-in audio playback before this callback runs; the callback is
381
- * fired regardless. Node / Electron consumers with custom playback
382
- * should drain their audio queue here so the agent goes silent
383
- * immediately.
384
- */
385
- onInterrupt?: () => void
386
- /**
387
- * Fires on `agent_turn_start` — the server has begun a new agent
388
- * turn. The state-machine transition to `agent_speaking` happens at
389
- * the same moment via `onStateChange`; use this when you want a
390
- * precise turn anchor (e.g. "agent has been speaking for N ms" UIs)
391
- * without diffing state.
392
- */
393
- onAgentTurnStart?: () => void
344
+ /** The agent to call. */
345
+ agentId: string;
346
+ /** Per-call user identifier. Round-tripped to fetchToken as `userId`. */
347
+ userId?: string;
348
+ /**
349
+ * Per-call structured context. Merged on top of `defaultContext`
350
+ * configured at factory time.
351
+ */
352
+ context?: Record<string, unknown>;
353
+ /**
354
+ * Per-call metadata. Merged on top of `defaultMetadata` configured
355
+ * at factory time.
356
+ */
357
+ metadata?: Record<string, string>;
358
+ /**
359
+ * When false, the SDK + server stay full-duplex but barge-in is
360
+ * suppressed. Useful for alarm-style flows where the user shouldn't
361
+ * accidentally interrupt the script. Default true.
362
+ */
363
+ bargeIn?: boolean;
364
+ /**
365
+ * Client-side tools the agent's LLM can call mid-conversation. Each
366
+ * tool's handler runs on the consumer's side; result is fed back to
367
+ * the LLM through the existing call WebSocket. Schema and handler
368
+ * colocate. Validated synchronously at startCall — bad input throws.
369
+ *
370
+ * See docs/integration-echocheck.md for the wire protocol and the
371
+ * server-side guarantees.
372
+ */
373
+ clientTools?: ClientToolMap;
374
+ /**
375
+ * Test-only escape hatch — pass a pre-minted `ct_` directly and skip
376
+ * the `fetchToken` call. Don't use this in production code: tokens
377
+ * expire and the SDK can't re-mint without the callback.
378
+ */
379
+ token?: string;
380
+ onStateChange?: (state: CallState) => void;
381
+ onTranscript?: (entries: TranscriptEntry[]) => void;
382
+ onError?: (err: CallError) => void;
383
+ onEnd?: (end: CallEndEvent) => void;
384
+ /** Volume-meter event for VU UIs. ~10 Hz cadence (browser bundle only). */
385
+ onVolume?: (vol: VolumeEvent) => void;
386
+ /**
387
+ * Fires when the server signals barge-in (the user started talking
388
+ * mid-agent-turn). The browser bundle automatically flushes its
389
+ * built-in audio playback before this callback runs; the callback is
390
+ * fired regardless. Node / Electron consumers with custom playback
391
+ * should drain their audio queue here so the agent goes silent
392
+ * immediately.
393
+ */
394
+ onInterrupt?: () => void;
395
+ /**
396
+ * Fires on `agent_turn_start` — the server has begun a new agent
397
+ * turn. The state-machine transition to `agent_speaking` happens at
398
+ * the same moment via `onStateChange`; use this when you want a
399
+ * precise turn anchor (e.g. "agent has been speaking for N ms" UIs)
400
+ * without diffing state.
401
+ */
402
+ onAgentTurnStart?: () => void;
394
403
  }
395
404
  interface Call {
396
- /** Current state. Snapshot — subscribe via onStateChange for live updates. */
397
- readonly state: CallState
398
- /** Full transcript so far. Snapshot — subscribe via onTranscript for live updates. */
399
- readonly transcript: TranscriptEntry[]
400
- /** True after `mute()` and before `unmute()`. */
401
- readonly isMuted: boolean
402
- /** End the call locally. Closes the WS, stops the mic, fires onEnd. Idempotent. */
403
- end: () => void
404
- /** Mute mic frames. Wire stays active so server endpointing doesn't false-positive. Idempotent. */
405
- mute: () => void
406
- /** Unmute mic frames. Idempotent. */
407
- unmute: () => void
405
+ /** Current state. Snapshot — subscribe via onStateChange for live updates. */
406
+ readonly state: CallState;
407
+ /** Full transcript so far. Snapshot — subscribe via onTranscript for live updates. */
408
+ readonly transcript: TranscriptEntry[];
409
+ /** True after `mute()` and before `unmute()`. */
410
+ readonly isMuted: boolean;
411
+ /** End the call locally. Closes the WS, stops the mic, fires onEnd. Idempotent. */
412
+ end: () => void;
413
+ /** Mute mic frames. Wire stays active so server endpointing doesn't false-positive. Idempotent. */
414
+ mute: () => void;
415
+ /** Unmute mic frames. Idempotent. */
416
+ unmute: () => void;
408
417
  }
409
418
  interface VoiceClientFactory {
410
- /** Read back the resolved config (post trailing-slash normalisation). */
411
- readonly config: VoiceClientConfig
412
- /**
413
- * Open a fresh call. Returns when the WS is open; rejects on
414
- * pre-flight failure (missing config, fetchToken throw, etc). Mid-
415
- * call failures arrive via the per-call `onError` callback — they
416
- * don't reject this promise.
417
- */
418
- startCall: (options: StartCallOptions) => Promise<Call>
419
- /**
420
- * Phase 7 (multi-party rooms). Browser only. Exchange a single-use
421
- * joinCode for a LiveKit JWT and connect to the room. The returned
422
- * `RoomSession` exposes a typed event surface
423
- * (participant.joined / participant.left / transcript.partial /
424
- * transcript.final / system.message / room.ended) plus
425
- * publishMic / publishCamera / leave. The Node bundle does NOT
426
- * implement this — livekit-client is a browser-only WebRTC client.
427
- */
428
- joinRoom?: (options: Omit<JoinRoomOptions, 'apiBase'>) => Promise<RoomSession>
419
+ /** Read back the resolved config (post trailing-slash normalisation). */
420
+ readonly config: VoiceClientConfig;
421
+ /**
422
+ * Open a fresh call. Returns when the WS is open; rejects on
423
+ * pre-flight failure (missing config, fetchToken throw, etc). Mid-
424
+ * call failures arrive via the per-call `onError` callback — they
425
+ * don't reject this promise.
426
+ */
427
+ startCall: (options: StartCallOptions) => Promise<Call>;
428
+ /**
429
+ * Phase 7 (multi-party rooms). Browser only. Exchange a single-use
430
+ * joinCode for a LiveKit JWT and connect to the room. The returned
431
+ * `RoomSession` exposes a typed event surface
432
+ * (participant.joined / participant.left / transcript.partial /
433
+ * transcript.final / system.message / room.ended) plus
434
+ * publishMic / publishCamera / leave. The Node bundle does NOT
435
+ * implement this — livekit-client is a browser-only WebRTC client.
436
+ */
437
+ joinRoom?: (options: Omit<JoinRoomOptions, 'apiBase'>) => Promise<RoomSession>;
438
+ /**
439
+ * Open a text-channel chat session (no microphone / audio required).
440
+ * Mint a `ct_` token with `channel: 'text'` server-side, then call
441
+ * this to connect. Returns a `TextSession` with:
442
+ * - `.greeting` — async iterable for the opening turn
443
+ * - `.send(text)` — send a user message; returns an async iterable for the reply
444
+ * - `.end()` — close the session (DELETE /v1/calls/:callId)
445
+ */
446
+ startTextSession?: (opts: Omit<StartTextSessionOpts, 'baseUrl' | 'fetch'>) => Promise<TextSession>;
429
447
  }
430
448
 
431
- type OnChunk = (pcm: ArrayBuffer) => void
432
- type OnVolume$1 = (rms01: number) => void
433
- type OnError = (err: Error) => void
449
+ type OnChunk = (pcm: ArrayBuffer) => void;
450
+ type OnVolume$1 = (rms01: number) => void;
451
+ type OnError = (err: Error) => void;
434
452
  interface CaptureOptions {
435
- onChunk: OnChunk
436
- onVolume?: OnVolume$1
437
- onError?: OnError
453
+ onChunk: OnChunk;
454
+ onVolume?: OnVolume$1;
455
+ onError?: OnError;
438
456
  }
439
457
  interface CaptureController {
440
- start: () => Promise<void>
441
- stop: () => void
442
- mute: (muted: boolean) => void
443
- isCapturing: () => boolean
458
+ start: () => Promise<void>;
459
+ stop: () => void;
460
+ mute: (muted: boolean) => void;
461
+ isCapturing: () => boolean;
444
462
  }
445
- declare const createAudioCapture: (options: CaptureOptions) => CaptureController
463
+ declare const createAudioCapture: (options: CaptureOptions) => CaptureController;
446
464
 
447
- type OnVolume = (rms01: number) => void
448
- type OnAgentSpeakingChange = (speaking: boolean) => void
465
+ type OnVolume = (rms01: number) => void;
466
+ type OnAgentSpeakingChange = (speaking: boolean) => void;
449
467
  interface PlaybackOptions {
450
- sampleRate?: number
451
- onVolume?: OnVolume
452
- onSpeakingChange?: OnAgentSpeakingChange
468
+ sampleRate?: number;
469
+ onVolume?: OnVolume;
470
+ onSpeakingChange?: OnAgentSpeakingChange;
453
471
  }
454
472
  interface PlaybackController {
455
- enqueue: (pcm: ArrayBuffer) => void
456
- flush: () => void
457
- close: () => void
458
- resume: () => Promise<void>
473
+ enqueue: (pcm: ArrayBuffer) => void;
474
+ flush: () => void;
475
+ close: () => void;
476
+ resume: () => Promise<void>;
459
477
  }
460
- declare const createAudioPlayback: (options?: PlaybackOptions) => PlaybackController
478
+ declare const createAudioPlayback: (options?: PlaybackOptions) => PlaybackController;
461
479
 
462
- type RWSEvent =
463
- | {
464
- type: 'open'
465
- }
466
- | {
467
- type: 'reconnected'
468
- }
469
- | {
470
- type: 'message'
471
- data: string | ArrayBuffer
472
- }
473
- | {
474
- type: 'close'
475
- code: number
476
- reason: string
477
- permanent: boolean
478
- }
479
- | {
480
- type: 'error'
481
- error: Error
482
- }
480
+ type RWSEvent = {
481
+ type: 'open';
482
+ } | {
483
+ type: 'reconnected';
484
+ } | {
485
+ type: 'message';
486
+ data: string | ArrayBuffer;
487
+ } | {
488
+ type: 'close';
489
+ code: number;
490
+ reason: string;
491
+ permanent: boolean;
492
+ } | {
493
+ type: 'error';
494
+ error: Error;
495
+ };
483
496
  interface WebSocketLike {
484
- binaryType: string
485
- readyState: number
486
- onopen: ((ev: unknown) => void) | null
487
- onmessage: ((ev: { data: string | ArrayBuffer }) => void) | null
488
- onerror: ((ev: unknown) => void) | null
489
- onclose: ((ev: { code: number; reason: string }) => void) | null
490
- send: (data: string | ArrayBuffer | ArrayBufferView) => void
491
- close: (code?: number, reason?: string) => void
497
+ binaryType: string;
498
+ readyState: number;
499
+ onopen: ((ev: unknown) => void) | null;
500
+ onmessage: ((ev: {
501
+ data: string | ArrayBuffer;
502
+ }) => void) | null;
503
+ onerror: ((ev: unknown) => void) | null;
504
+ onclose: ((ev: {
505
+ code: number;
506
+ reason: string;
507
+ }) => void) | null;
508
+ send: (data: string | ArrayBuffer | ArrayBufferView) => void;
509
+ close: (code?: number, reason?: string) => void;
492
510
  }
493
- type WebSocketFactory = (url: string) => WebSocketLike
511
+ type WebSocketFactory = (url: string) => WebSocketLike;
494
512
  interface RWSOptions {
495
- url: string
496
- wsFactory: WebSocketFactory
497
- maxRetries?: number
498
- initialBackoffMs?: number
499
- maxBackoffMs?: number
513
+ url: string;
514
+ wsFactory: WebSocketFactory;
515
+ maxRetries?: number;
516
+ initialBackoffMs?: number;
517
+ maxBackoffMs?: number;
500
518
  }
501
- declare const createReconnectingWebSocket: (
502
- options: RWSOptions,
503
- onEvent: (ev: RWSEvent) => void,
504
- ) => {
505
- send: (data: string | ArrayBuffer | ArrayBufferView) => void
506
- close: (code?: number, reason?: string) => void
507
- readyState: () => number
508
- }
509
- type ReconnectingWebSocket = ReturnType<typeof createReconnectingWebSocket>
519
+ declare const createReconnectingWebSocket: (options: RWSOptions, onEvent: (ev: RWSEvent) => void) => {
520
+ send: (data: string | ArrayBuffer | ArrayBufferView) => void;
521
+ close: (code?: number, reason?: string) => void;
522
+ readyState: () => number;
523
+ };
524
+ type ReconnectingWebSocket = ReturnType<typeof createReconnectingWebSocket>;
510
525
 
511
526
  /**
512
527
  * Canonical payload a tenant places in their VoIP/FCM push so an
@@ -518,20 +533,20 @@ type ReconnectingWebSocket = ReturnType<typeof createReconnectingWebSocket>
518
533
  * the real target. See docs/sdks.md "Agent-initiated calls".
519
534
  */
520
535
  interface IncomingCallPayload {
521
- token: string
522
- agentId: string
523
- transport: 'ws' | 'webrtc'
524
- webrtcGatewayBase?: string
525
- expiresAt?: number
526
- agentName?: string
527
- agentAvatarUrl?: string
536
+ token: string;
537
+ agentId: string;
538
+ transport: 'ws' | 'webrtc';
539
+ webrtcGatewayBase?: string;
540
+ expiresAt?: number;
541
+ agentName?: string;
542
+ agentAvatarUrl?: string;
528
543
  }
529
544
  /**
530
545
  * Validate + normalise a raw push payload into an IncomingCallPayload.
531
546
  * Throws synchronously on malformed input. Unknown transports fall back to
532
547
  * 'ws'; webrtcGatewayBase is ignored unless transport === 'webrtc'.
533
548
  */
534
- declare const parseIncomingCall: (raw: unknown) => IncomingCallPayload
549
+ declare const parseIncomingCall: (raw: unknown) => IncomingCallPayload;
535
550
 
536
551
  /**
537
552
  * One-time SDK setup. Returns a factory you call `startCall` on for
@@ -558,56 +573,6 @@ declare const parseIncomingCall: (raw: unknown) => IncomingCallPayload
558
573
  * call.mute()
559
574
  * call.end()
560
575
  */
561
- declare function configureVoiceClient(config: VoiceClientConfig): VoiceClientFactory
576
+ declare function configureVoiceClient(config: VoiceClientConfig): VoiceClientFactory;
562
577
 
563
- export {
564
- type Call,
565
- type CallEndEvent,
566
- type CallEndReason,
567
- type CallError,
568
- type CallErrorCode,
569
- type CallState,
570
- type CaptureController,
571
- type CaptureOptions,
572
- type ClientTool,
573
- type ClientToolMap,
574
- type FetchToken,
575
- type FetchTokenArgs,
576
- type FetchTokenResult,
577
- type IncomingCallPayload,
578
- type JoinRoomOptions,
579
- type OnAgentSpeakingChange,
580
- type OnChunk,
581
- type OnError,
582
- type OnVolume$1 as OnVolume,
583
- type PlaybackController,
584
- type PlaybackOptions,
585
- type ProtocolCallbacks,
586
- type ProtocolState,
587
- type RWSEvent,
588
- type RWSOptions,
589
- type ReconnectingWebSocket,
590
- type RoomEventName,
591
- type RoomEventPayloads,
592
- type RoomParticipantInfo,
593
- type RoomSession,
594
- type ServerMessage,
595
- type StartCallOptions,
596
- type SystemMessage,
597
- type TranscriptEntry,
598
- type TranscriptMessage,
599
- type VoiceClientConfig,
600
- type VoiceClientFactory,
601
- type VolumeEvent,
602
- type WebSocketFactory,
603
- type WebSocketLike,
604
- buildWsUrl,
605
- configureVoiceClient,
606
- createAudioCapture,
607
- createAudioPlayback,
608
- createProtocolState,
609
- createReconnectingWebSocket,
610
- handleServerMessage,
611
- joinRoom,
612
- parseIncomingCall,
613
- }
578
+ export { type Call, type CallEndEvent, type CallEndReason, type CallError, type CallErrorCode, type CallState, type CaptureController, type CaptureOptions, type ChatEvent, type ClientTool, type ClientToolMap, type FetchToken, type FetchTokenArgs, type FetchTokenResult, type IncomingCallPayload, type JoinRoomOptions, type OnAgentSpeakingChange, type OnChunk, type OnError, type OnVolume$1 as OnVolume, type PlaybackController, type PlaybackOptions, type ProtocolCallbacks, type ProtocolState, type RWSEvent, type RWSOptions, type ReconnectingWebSocket, type RoomEventName, type RoomEventPayloads, type RoomParticipantInfo, type RoomSession, type ServerMessage, type StartCallOptions, type StartTextSessionOpts, type SystemMessage, type TextSession, type TranscriptEntry, type TranscriptMessage, type VoiceClientConfig, type VoiceClientFactory, type VolumeEvent, type WebSocketFactory, type WebSocketLike, buildWsUrl, configureVoiceClient, createAudioCapture, createAudioPlayback, createProtocolState, createReconnectingWebSocket, handleServerMessage, joinRoom, parseIncomingCall, startTextSession };