@craftedxp/voice-js 0.5.4 → 0.9.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 (42) hide show
  1. package/CONSUMING.md +6 -2
  2. package/README.md +31 -4
  3. package/dist/assistant.d.mts +32 -0
  4. package/dist/assistant.d.ts +32 -0
  5. package/dist/assistant.js +1241 -0
  6. package/dist/assistant.js.map +1 -0
  7. package/dist/assistant.mjs +23 -0
  8. package/dist/assistant.mjs.map +1 -0
  9. package/dist/browser.d.mts +12 -509
  10. package/dist/browser.d.ts +12 -608
  11. package/dist/browser.js +1020 -896
  12. package/dist/browser.js.map +1 -1
  13. package/dist/browser.mjs +25 -1283
  14. package/dist/browser.mjs.map +1 -1
  15. package/dist/chunk-LV7JGPYW.mjs +200 -0
  16. package/dist/chunk-LV7JGPYW.mjs.map +1 -0
  17. package/dist/chunk-ZW22Y67M.mjs +1208 -0
  18. package/dist/chunk-ZW22Y67M.mjs.map +1 -0
  19. package/dist/config-D2TbvIqT.d.mts +297 -0
  20. package/dist/config-D2TbvIqT.d.ts +297 -0
  21. package/dist/embed.iife.js +30 -23358
  22. package/dist/incomingCall-CfRRzj2P.d.mts +103 -0
  23. package/dist/incomingCall-CfRRzj2P.d.ts +103 -0
  24. package/dist/node.d.mts +69 -139
  25. package/dist/node.d.ts +342 -496
  26. package/dist/node.js +472 -467
  27. package/dist/node.js.map +1 -1
  28. package/dist/node.mjs +19 -0
  29. package/dist/node.mjs.map +1 -1
  30. package/dist/room.d.mts +156 -0
  31. package/dist/room.d.ts +156 -0
  32. package/dist/room.js +236 -0
  33. package/dist/room.js.map +1 -0
  34. package/dist/room.mjs +7 -0
  35. package/dist/room.mjs.map +1 -0
  36. package/dist/transcribe.d.mts +14 -0
  37. package/dist/transcribe.d.ts +14 -0
  38. package/dist/transcribe.js +1213 -0
  39. package/dist/transcribe.js.map +1 -0
  40. package/dist/transcribe.mjs +18 -0
  41. package/dist/transcribe.mjs.map +1 -0
  42. package/package.json +22 -4
package/dist/node.d.ts CHANGED
@@ -1,290 +1,157 @@
1
- import { RemoteTrack, LocalVideoTrack } from 'livekit-client'
2
-
3
1
  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
2
+ description: string;
3
+ parameters: Record<string, unknown>;
4
+ usage?: string;
5
+ timeoutMs?: number;
6
+ example?: string;
7
+ handler: (args: Record<string, unknown>) => Promise<string | object> | string | object;
10
8
  }
11
- type ClientToolMap = Record<string, ClientTool>
9
+ type ClientToolMap = Record<string, ClientTool>;
12
10
  interface ClientToolCallFrame {
13
- toolCallId: string
14
- name: string
15
- args: Record<string, unknown>
11
+ toolCallId: string;
12
+ name: string;
13
+ args: Record<string, unknown>;
16
14
  }
17
15
 
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'
16
+ type CallState = 'idle' | 'connecting' | 'listening' | 'user_speaking' | 'agent_speaking' | 'ended' | 'error';
17
+ type TranscriptEntry = {
18
+ id: string;
19
+ role: 'user';
20
+ text: string;
21
+ committed: boolean;
22
+ } | {
23
+ id: string;
24
+ role: 'agent';
25
+ text: string;
26
+ interrupted?: boolean;
27
+ } | {
28
+ id: string;
29
+ role: 'tool';
30
+ text: string;
31
+ } | {
32
+ id: string;
33
+ role: 'system';
34
+ text: string;
35
+ };
36
+ 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
37
  interface CallError {
65
- code: CallErrorCode
66
- message: string
38
+ code: CallErrorCode;
39
+ message: string;
67
40
  }
68
- type CallEndReason = 'agent_ended' | 'user_hangup' | 'timeout' | 'error'
41
+ type CallEndReason = 'agent_ended' | 'user_hangup' | 'timeout' | 'error';
69
42
  interface CallEndEvent {
70
- reason: CallEndReason
71
- errorCode?: CallErrorCode
72
- durationMs: number
43
+ reason: CallEndReason;
44
+ errorCode?: CallErrorCode;
45
+ durationMs: number;
73
46
  }
74
47
  interface VolumeEvent {
75
- input: number
76
- output: number
48
+ input: number;
49
+ output: number;
77
50
  }
78
51
  type ServerMessage = Record<string, unknown> & {
79
- type?: string
80
- }
52
+ type?: string;
53
+ };
81
54
  interface ProtocolState {
82
- state: CallState
83
- transcript: TranscriptEntry[]
84
- agentBubbleId: string | null
85
- idCounter: number
86
- endReason: CallEndReason | null
55
+ state: CallState;
56
+ transcript: TranscriptEntry[];
57
+ agentBubbleId: string | null;
58
+ idCounter: number;
59
+ endReason: CallEndReason | null;
87
60
  }
88
- declare const createProtocolState: () => ProtocolState
61
+ declare const createProtocolState: () => ProtocolState;
89
62
  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
63
+ onState: (next: CallState) => void;
64
+ onTranscript: (entries: TranscriptEntry[]) => void;
65
+ onError: (err: CallError) => void;
66
+ onInterrupt: () => void;
67
+ onAgentTurnStart: (seq?: number) => void;
68
+ onAgentTurnEnd: (seq?: number) => void;
69
+ onCallEnd: (reason: CallEndReason) => void;
70
+ onConnected: () => void;
71
+ onClientToolCall: (frame: ClientToolCallFrame) => void;
99
72
  }
100
- declare function handleServerMessage(raw: string, state: ProtocolState, cb: ProtocolCallbacks): void
73
+ declare function handleServerMessage(raw: string, state: ProtocolState, cb: ProtocolCallbacks): void;
101
74
  interface BuildWsUrlArgs {
102
- apiBase: string
103
- agentId: string
104
- token: string
105
- bargeIn?: boolean
106
- }
107
- declare function buildWsUrl(args: BuildWsUrlArgs): string
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
75
+ apiBase: string;
76
+ agentId: string;
77
+ token: string;
78
+ bargeIn?: boolean;
154
79
  }
80
+ declare function buildWsUrl(args: BuildWsUrlArgs): string;
155
81
 
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[]
82
+ /**
83
+ * Browser-friendly text-channel chat session. Mint a `ct_` token with
84
+ * `channel: 'text'` on your backend, then call `startTextSession({...})`
85
+ * to open the SSE stream.
86
+ *
87
+ * Each `.send(text)` is a fresh POST; SSE-per-turn means the connection
88
+ * closes when each turn ends. Conversation state lives server-side on the
89
+ * underlying CallRecord.
90
+ */
91
+ type ChatEvent = {
92
+ type: 'chat.started';
93
+ chatId: string;
94
+ callId: string;
95
+ } | {
96
+ type: 'token';
97
+ text: string;
98
+ } | {
99
+ type: 'tool.call';
100
+ name: string;
101
+ args: unknown;
102
+ } | {
103
+ type: 'tool.result';
104
+ name: string;
105
+ ok?: boolean;
106
+ [key: string]: unknown;
107
+ } | {
108
+ type: 'turn.end';
109
+ finishReason: 'stop' | 'aborted' | 'length' | 'tool_error';
110
+ committedText?: string;
111
+ } | {
112
+ type: 'error';
113
+ code: string;
114
+ message: string;
115
+ };
116
+ interface StartTextSessionOpts {
117
+ baseUrl: string;
118
+ token: string;
119
+ agentId: string;
120
+ /** Optional inline first user message; otherwise the agent's greeting opens the stream. */
121
+ text?: string;
122
+ /** Override the global fetch (useful for tests; defaults to globalThis.fetch). */
123
+ fetch?: typeof fetch;
217
124
  }
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>
125
+ interface TextSession {
126
+ id: string;
127
+ callId: string;
128
+ /** Async iterable for the opening turn greeting tokens / first reply if text was inlined. */
129
+ greeting: AsyncIterable<ChatEvent>;
130
+ /** Send a user message; returns an async iterable for the agent's reply. */
131
+ send(text: string): Promise<AsyncIterable<ChatEvent>>;
132
+ /** End the session DELETE /v1/calls/:callId. */
133
+ end(): Promise<void>;
267
134
  }
268
135
 
269
136
  interface FetchTokenArgs {
270
- /** The agent the SDK is about to call. */
271
- agentId: string
272
- /**
273
- * Optional consumer-side user identifier. Round-tripped to the server
274
- * as `contactId` for Phase 11 contact memory. The SDK does not
275
- * inspect this; your backend uses it to scope the token mint.
276
- */
277
- userId?: string
278
- /**
279
- * Per-call structured context lowered into the agent's effective
280
- * system prompt server-side at session open. Opaque to the SDK.
281
- */
282
- context?: Record<string, unknown>
283
- /**
284
- * String key/value pairs round-tripped on the `call.ended` webhook.
285
- * Capped at 1 KB total server-side. NOT lowered into the system prompt.
286
- */
287
- metadata?: Record<string, string>
137
+ /** The agent the SDK is about to call. */
138
+ agentId: string;
139
+ /**
140
+ * Optional consumer-side user identifier. Round-tripped to the server
141
+ * as `contactId` for Phase 11 contact memory. The SDK does not
142
+ * inspect this; your backend uses it to scope the token mint.
143
+ */
144
+ userId?: string;
145
+ /**
146
+ * Per-call structured context lowered into the agent's effective
147
+ * system prompt server-side at session open. Opaque to the SDK.
148
+ */
149
+ context?: Record<string, unknown>;
150
+ /**
151
+ * String key/value pairs round-tripped on the `call.ended` webhook.
152
+ * Capped at 1 KB total server-side. NOT lowered into the system prompt.
153
+ */
154
+ metadata?: Record<string, string>;
288
155
  }
289
156
  /**
290
157
  * What `fetchToken` may return. The rich object form lets the server
@@ -292,209 +159,223 @@ interface FetchTokenArgs {
292
159
  * compatible — the SDK treats it as `{ token, transport: 'ws' }`.
293
160
  */
294
161
  interface FetchTokenResult {
295
- /** Raw `ct_` to feed into the WS open / WebRTC offer. */
296
- token: string
297
- /** Server-selected transport. Default `'ws'` if absent. */
298
- transport?: 'ws' | 'webrtc'
299
- /** Required when `transport === 'webrtc'` AND the server uses a
300
- * separate signaling gateway. When omitted on a webrtc result, the
301
- * SDK falls back to the API base's Phase-1 routes (local dev). */
302
- webrtcGatewayBase?: string
162
+ /** Raw `ct_` to feed into the WS open / WebRTC offer. */
163
+ token: string;
164
+ /** Server-selected transport. Default `'ws'` if absent. */
165
+ transport?: 'ws' | 'webrtc';
166
+ /** Required when `transport === 'webrtc'` AND the server uses a
167
+ * separate signaling gateway. When omitted on a webrtc result, the
168
+ * SDK falls back to the API base's Phase-1 routes (local dev). */
169
+ webrtcGatewayBase?: string;
303
170
  }
304
- type FetchToken = (args: FetchTokenArgs) => Promise<string | FetchTokenResult>
171
+ type FetchToken = (args: FetchTokenArgs) => Promise<string | FetchTokenResult>;
305
172
  interface VoiceClientConfig {
306
- /**
307
- * Full HTTPS URL of the Voissia server. The WebSocket scheme is
308
- * derived: `https` → `wss`, `http` → `ws`. No trailing slash needed.
309
- */
310
- apiBase: string
311
- /**
312
- * Called by the SDK whenever it needs a fresh `ct_` token (initial
313
- * connect; mid-call refresh on `token_expired`). Your implementation
314
- * should hit YOUR backend, which holds the `sk_` API key and mints
315
- * via `POST /v1/call-tokens` (or `client.callTokens.mint` from
316
- * @craftedxp/sdk-node). Never embed `sk_` in JS code that ships to a
317
- * client.
318
- */
319
- fetchToken: FetchToken
320
- /**
321
- * Optional metadata applied to EVERY startCall. Per-call `metadata`
322
- * in `startCall` is merged on top (per-call wins on key conflicts).
323
- * Useful for dashboard-wide tags like `{ surface: 'web', appVersion }`.
324
- */
325
- defaultMetadata?: Record<string, string>
326
- /**
327
- * Optional context applied to EVERY startCall. Per-call `context` in
328
- * `startCall` is merged on top. Useful for cross-call invariants like
329
- * the signed-in user's locale.
330
- */
331
- defaultContext?: Record<string, unknown>
173
+ /**
174
+ * Full HTTPS URL of the Voissia server. The WebSocket scheme is
175
+ * derived: `https` → `wss`, `http` → `ws`. No trailing slash needed.
176
+ */
177
+ apiBase: string;
178
+ /**
179
+ * Called by the SDK whenever it needs a fresh `ct_` token (initial
180
+ * connect; mid-call refresh on `token_expired`). Your implementation
181
+ * should hit YOUR backend, which holds the `sk_` API key and mints
182
+ * via `POST /v1/call-tokens` (or `client.callTokens.mint` from
183
+ * @craftedxp/sdk-node). Never embed `sk_` in JS code that ships to a
184
+ * client.
185
+ */
186
+ fetchToken: FetchToken;
187
+ /**
188
+ * Optional metadata applied to EVERY startCall. Per-call `metadata`
189
+ * in `startCall` is merged on top (per-call wins on key conflicts).
190
+ * Useful for dashboard-wide tags like `{ surface: 'web', appVersion }`.
191
+ */
192
+ defaultMetadata?: Record<string, string>;
193
+ /**
194
+ * Optional context applied to EVERY startCall. Per-call `context` in
195
+ * `startCall` is merged on top. Useful for cross-call invariants like
196
+ * the signed-in user's locale.
197
+ */
198
+ defaultContext?: Record<string, unknown>;
332
199
  }
333
200
  interface StartCallOptions {
334
- /** The agent to call. */
335
- agentId: string
336
- /** Per-call user identifier. Round-tripped to fetchToken as `userId`. */
337
- userId?: string
338
- /**
339
- * Per-call structured context. Merged on top of `defaultContext`
340
- * configured at factory time.
341
- */
342
- context?: Record<string, unknown>
343
- /**
344
- * Per-call metadata. Merged on top of `defaultMetadata` configured
345
- * at factory time.
346
- */
347
- metadata?: Record<string, string>
348
- /**
349
- * When false, the SDK + server stay full-duplex but barge-in is
350
- * suppressed. Useful for alarm-style flows where the user shouldn't
351
- * accidentally interrupt the script. Default true.
352
- */
353
- bargeIn?: boolean
354
- /**
355
- * Client-side tools the agent's LLM can call mid-conversation. Each
356
- * tool's handler runs on the consumer's side; result is fed back to
357
- * the LLM through the existing call WebSocket. Schema and handler
358
- * colocate. Validated synchronously at startCall — bad input throws.
359
- *
360
- * See docs/integration-echocheck.md for the wire protocol and the
361
- * server-side guarantees.
362
- */
363
- clientTools?: ClientToolMap
364
- /**
365
- * Test-only escape hatch — pass a pre-minted `ct_` directly and skip
366
- * the `fetchToken` call. Don't use this in production code: tokens
367
- * expire and the SDK can't re-mint without the callback.
368
- */
369
- token?: string
370
- onStateChange?: (state: CallState) => void
371
- onTranscript?: (entries: TranscriptEntry[]) => void
372
- onError?: (err: CallError) => void
373
- onEnd?: (end: CallEndEvent) => void
374
- /** Volume-meter event for VU UIs. ~10 Hz cadence (browser bundle only). */
375
- onVolume?: (vol: VolumeEvent) => void
376
- /**
377
- * Fires when the server signals barge-in (the user started talking
378
- * mid-agent-turn). The browser bundle automatically flushes its
379
- * built-in audio playback before this callback runs; the callback is
380
- * fired regardless. Node / Electron consumers with custom playback
381
- * should drain their audio queue here so the agent goes silent
382
- * immediately.
383
- */
384
- onInterrupt?: () => void
385
- /**
386
- * Fires on `agent_turn_start` — the server has begun a new agent
387
- * turn. The state-machine transition to `agent_speaking` happens at
388
- * the same moment via `onStateChange`; use this when you want a
389
- * precise turn anchor (e.g. "agent has been speaking for N ms" UIs)
390
- * without diffing state.
391
- */
392
- onAgentTurnStart?: () => void
201
+ /** The agent to call. */
202
+ agentId: string;
203
+ /** Per-call user identifier. Round-tripped to fetchToken as `userId`. */
204
+ userId?: string;
205
+ /**
206
+ * Per-call structured context. Merged on top of `defaultContext`
207
+ * configured at factory time.
208
+ */
209
+ context?: Record<string, unknown>;
210
+ /**
211
+ * Per-call metadata. Merged on top of `defaultMetadata` configured
212
+ * at factory time.
213
+ */
214
+ metadata?: Record<string, string>;
215
+ /**
216
+ * When false, the SDK + server stay full-duplex but barge-in is
217
+ * suppressed. Useful for alarm-style flows where the user shouldn't
218
+ * accidentally interrupt the script. Default true.
219
+ */
220
+ bargeIn?: boolean;
221
+ /**
222
+ * Client-side tools the agent's LLM can call mid-conversation. Each
223
+ * tool's handler runs on the consumer's side; result is fed back to
224
+ * the LLM through the existing call WebSocket. Schema and handler
225
+ * colocate. Validated synchronously at startCall — bad input throws.
226
+ *
227
+ * See docs/sdks.md ("Client tools") for the wire protocol and the
228
+ * server-side guarantees.
229
+ */
230
+ clientTools?: ClientToolMap;
231
+ /**
232
+ * Test-only escape hatch — pass a pre-minted `ct_` directly and skip
233
+ * the `fetchToken` call. Don't use this in production code: tokens
234
+ * expire and the SDK can't re-mint without the callback.
235
+ */
236
+ token?: string;
237
+ onStateChange?: (state: CallState) => void;
238
+ onTranscript?: (entries: TranscriptEntry[]) => void;
239
+ onError?: (err: CallError) => void;
240
+ onEnd?: (end: CallEndEvent) => void;
241
+ /** Volume-meter event for VU UIs. ~10 Hz cadence (browser bundle only). */
242
+ onVolume?: (vol: VolumeEvent) => void;
243
+ /**
244
+ * Fires when the server signals barge-in (the user started talking
245
+ * mid-agent-turn). The browser bundle automatically flushes its
246
+ * built-in audio playback before this callback runs; the callback is
247
+ * fired regardless. Node / Electron consumers with custom playback
248
+ * should drain their audio queue here so the agent goes silent
249
+ * immediately.
250
+ */
251
+ onInterrupt?: () => void;
252
+ /**
253
+ * Fires on `agent_turn_start` — the server has begun a new agent
254
+ * turn. The state-machine transition to `agent_speaking` happens at
255
+ * the same moment via `onStateChange`; use this when you want a
256
+ * precise turn anchor (e.g. "agent has been speaking for N ms" UIs)
257
+ * without diffing state.
258
+ */
259
+ onAgentTurnStart?: () => void;
393
260
  }
394
261
  interface Call {
395
- /** Current state. Snapshot — subscribe via onStateChange for live updates. */
396
- readonly state: CallState
397
- /** Full transcript so far. Snapshot — subscribe via onTranscript for live updates. */
398
- readonly transcript: TranscriptEntry[]
399
- /** True after `mute()` and before `unmute()`. */
400
- readonly isMuted: boolean
401
- /** End the call locally. Closes the WS, stops the mic, fires onEnd. Idempotent. */
402
- end: () => void
403
- /** Mute mic frames. Wire stays active so server endpointing doesn't false-positive. Idempotent. */
404
- mute: () => void
405
- /** Unmute mic frames. Idempotent. */
406
- unmute: () => void
262
+ /** Current state. Snapshot — subscribe via onStateChange for live updates. */
263
+ readonly state: CallState;
264
+ /** Full transcript so far. Snapshot — subscribe via onTranscript for live updates. */
265
+ readonly transcript: TranscriptEntry[];
266
+ /** True after `mute()` and before `unmute()`. */
267
+ readonly isMuted: boolean;
268
+ /** End the call locally. Closes the WS, stops the mic, fires onEnd. Idempotent. */
269
+ end: () => void;
270
+ /** Mute mic frames. Wire stays active so server endpointing doesn't false-positive. Idempotent. */
271
+ mute: () => void;
272
+ /** Unmute mic frames. Idempotent. */
273
+ unmute: () => void;
407
274
  }
408
275
  interface VoiceClientFactory {
409
- /** Read back the resolved config (post trailing-slash normalisation). */
410
- readonly config: VoiceClientConfig
411
- /**
412
- * Open a fresh call. Returns when the WS is open; rejects on
413
- * pre-flight failure (missing config, fetchToken throw, etc). Mid-
414
- * call failures arrive via the per-call `onError` callback — they
415
- * don't reject this promise.
416
- */
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>
276
+ /** Read back the resolved config (post trailing-slash normalisation). */
277
+ readonly config: VoiceClientConfig;
278
+ /**
279
+ * Open a fresh call. Returns when the WS is open; rejects on
280
+ * pre-flight failure (missing config, fetchToken throw, etc). Mid-
281
+ * call failures arrive via the per-call `onError` callback — they
282
+ * don't reject this promise.
283
+ */
284
+ startCall: (options: StartCallOptions) => Promise<Call>;
285
+ /**
286
+ * Open a text-channel chat session (no microphone / audio required).
287
+ * Mint a `ct_` token with `channel: 'text'` server-side, then call
288
+ * this to connect. Returns a `TextSession` with:
289
+ * - `.greeting` async iterable for the opening turn
290
+ * - `.send(text)` — send a user message; returns an async iterable for the reply
291
+ * - `.end()` close the session (DELETE /v1/calls/:callId)
292
+ */
293
+ startTextSession?: (opts: Omit<StartTextSessionOpts, 'baseUrl' | 'fetch'>) => Promise<TextSession>;
428
294
  }
429
295
 
430
- type RWSEvent =
431
- | {
432
- type: 'open'
433
- }
434
- | {
435
- type: 'reconnected'
436
- }
437
- | {
438
- type: 'message'
439
- data: string | ArrayBuffer
440
- }
441
- | {
442
- type: 'close'
443
- code: number
444
- reason: string
445
- permanent: boolean
446
- }
447
- | {
448
- type: 'error'
449
- error: Error
450
- }
296
+ type RWSEvent = {
297
+ type: 'open';
298
+ } | {
299
+ type: 'reconnected';
300
+ } | {
301
+ type: 'message';
302
+ data: string | ArrayBuffer;
303
+ } | {
304
+ type: 'close';
305
+ code: number;
306
+ reason: string;
307
+ permanent: boolean;
308
+ } | {
309
+ type: 'error';
310
+ error: Error;
311
+ };
451
312
  interface WebSocketLike {
452
- binaryType: string
453
- readyState: number
454
- onopen: ((ev: unknown) => void) | null
455
- onmessage: ((ev: { data: string | ArrayBuffer }) => void) | null
456
- onerror: ((ev: unknown) => void) | null
457
- onclose: ((ev: { code: number; reason: string }) => void) | null
458
- send: (data: string | ArrayBuffer | ArrayBufferView) => void
459
- close: (code?: number, reason?: string) => void
313
+ binaryType: string;
314
+ readyState: number;
315
+ onopen: ((ev: unknown) => void) | null;
316
+ onmessage: ((ev: {
317
+ data: string | ArrayBuffer;
318
+ }) => void) | null;
319
+ onerror: ((ev: unknown) => void) | null;
320
+ onclose: ((ev: {
321
+ code: number;
322
+ reason: string;
323
+ }) => void) | null;
324
+ send: (data: string | ArrayBuffer | ArrayBufferView) => void;
325
+ close: (code?: number, reason?: string) => void;
460
326
  }
461
- type WebSocketFactory = (url: string) => WebSocketLike
327
+ type WebSocketFactory = (url: string) => WebSocketLike;
462
328
  interface RWSOptions {
463
- url: string
464
- wsFactory: WebSocketFactory
465
- maxRetries?: number
466
- initialBackoffMs?: number
467
- maxBackoffMs?: number
468
- }
469
- declare const createReconnectingWebSocket: (
470
- options: RWSOptions,
471
- onEvent: (ev: RWSEvent) => void,
472
- ) => {
473
- send: (data: string | ArrayBuffer | ArrayBufferView) => void
474
- close: (code?: number, reason?: string) => void
475
- readyState: () => number
329
+ url: string;
330
+ wsFactory: WebSocketFactory;
331
+ maxRetries?: number;
332
+ initialBackoffMs?: number;
333
+ maxBackoffMs?: number;
476
334
  }
477
- type ReconnectingWebSocket = ReturnType<typeof createReconnectingWebSocket>
335
+ declare const createReconnectingWebSocket: (options: RWSOptions, onEvent: (ev: RWSEvent) => void) => {
336
+ send: (data: string | ArrayBuffer | ArrayBufferView) => void;
337
+ close: (code?: number, reason?: string) => void;
338
+ readyState: () => number;
339
+ };
340
+ type ReconnectingWebSocket = ReturnType<typeof createReconnectingWebSocket>;
478
341
 
479
342
  interface NodeStartCallOptions extends StartCallOptions {
480
- /**
481
- * Fires for each binary PCM frame the server pushes (Int16 LE mono
482
- * @ 16 kHz — same as the browser playback path). Wire to your
483
- * preferred output: write to a `sox -t raw -r 16000 -e signed -b 16
484
- * -c 1 - default` subprocess, queue into PortAudio, relay over RTP,
485
- * etc. If you don't supply this callback, agent audio is dropped on
486
- * the floor.
487
- */
488
- onAudioChunk?: (pcm: ArrayBuffer) => void
343
+ /**
344
+ * Fires for each binary PCM frame the server pushes (Int16 LE mono
345
+ * @ 16 kHz — same as the browser playback path). Wire to your
346
+ * preferred output: write to a `sox -t raw -r 16000 -e signed -b 16
347
+ * -c 1 - default` subprocess, queue into PortAudio, relay over RTP,
348
+ * etc. If you don't supply this callback, agent audio is dropped on
349
+ * the floor.
350
+ */
351
+ onAudioChunk?: (pcm: ArrayBuffer) => void;
489
352
  }
490
353
  interface NodeCall extends Call {
491
- /**
492
- * Push one mic frame to the server. Expected: Int16 LE mono PCM @
493
- * 16 kHz. Capture cadence ~100 ms / ~3.2 KB per frame is fine.
494
- * Returns `false` if the WS isn't open yet (caller may want to
495
- * back-pressure or drop).
496
- */
497
- sendAudioChunk: (pcm: ArrayBuffer | ArrayBufferView) => boolean
354
+ /**
355
+ * Push one mic frame to the server. Expected: Int16 LE mono PCM @
356
+ * 16 kHz. Capture cadence ~100 ms / ~3.2 KB per frame is fine.
357
+ * Returns `false` if the WS isn't open yet (caller may want to
358
+ * back-pressure or drop).
359
+ */
360
+ sendAudioChunk: (pcm: ArrayBuffer | ArrayBufferView) => boolean;
361
+ /**
362
+ * Push a short advisory context line to the live call (e.g. a browser
363
+ * action the user just took). The server buffers these and shows them
364
+ * to the LLM ahead of the next turn — they never trigger a response
365
+ * by themselves. Text is trimmed and truncated to 400 chars.
366
+ * Returns `false` if the WS isn't open yet or text is empty.
367
+ */
368
+ sendClientEvent: (text: string) => boolean;
369
+ /**
370
+ * Send a typed user turn (text/multimodal sessions — token minted
371
+ * with `channel:'text'`). Sends `{type:'user_text', text}`. The
372
+ * server accepts `user_text` only on text-channel sessions and
373
+ * rejects it on voice (anti-injection), so this is a no-op in
374
+ * practice on voice calls beyond the frame being ignored
375
+ * server-side. Text is trimmed. Returns `false` (and sends nothing)
376
+ * if the WS isn't open or text is empty/whitespace. Never throws.
377
+ */
378
+ sendText: (text: string) => boolean;
498
379
  }
499
380
  /**
500
381
  * Node bundle's analog of `VoiceClientFactory`. Same shape but
@@ -504,8 +385,8 @@ interface NodeCall extends Call {
504
385
  * entry. Browser entry returns the base `VoiceClientFactory` type.
505
386
  */
506
387
  interface NodeVoiceClientFactory {
507
- readonly config: VoiceClientConfig
508
- startCall: (options: NodeStartCallOptions) => Promise<NodeCall>
388
+ readonly config: VoiceClientConfig;
389
+ startCall: (options: NodeStartCallOptions) => Promise<NodeCall>;
509
390
  }
510
391
 
511
392
  /**
@@ -518,20 +399,20 @@ interface NodeVoiceClientFactory {
518
399
  * the real target. See docs/sdks.md "Agent-initiated calls".
519
400
  */
520
401
  interface IncomingCallPayload {
521
- token: string
522
- agentId: string
523
- transport: 'ws' | 'webrtc'
524
- webrtcGatewayBase?: string
525
- expiresAt?: number
526
- agentName?: string
527
- agentAvatarUrl?: string
402
+ token: string;
403
+ agentId: string;
404
+ transport: 'ws' | 'webrtc';
405
+ webrtcGatewayBase?: string;
406
+ expiresAt?: number;
407
+ agentName?: string;
408
+ agentAvatarUrl?: string;
528
409
  }
529
410
  /**
530
411
  * Validate + normalise a raw push payload into an IncomingCallPayload.
531
412
  * Throws synchronously on malformed input. Unknown transports fall back to
532
413
  * 'ws'; webrtcGatewayBase is ignored unless transport === 'webrtc'.
533
414
  */
534
- declare const parseIncomingCall: (raw: unknown) => IncomingCallPayload
415
+ declare const parseIncomingCall: (raw: unknown) => IncomingCallPayload;
535
416
 
536
417
  /**
537
418
  * One-time SDK setup for Node.js / Electron-main consumers. Returns a
@@ -561,41 +442,6 @@ declare const parseIncomingCall: (raw: unknown) => IncomingCallPayload
561
442
  *
562
443
  * mic.stdout.on('data', (chunk) => call.sendAudioChunk(chunk))
563
444
  */
564
- declare function configureVoiceClient(config: VoiceClientConfig): NodeVoiceClientFactory
445
+ declare function configureVoiceClient(config: VoiceClientConfig): NodeVoiceClientFactory;
565
446
 
566
- export {
567
- type Call,
568
- type CallEndEvent,
569
- type CallEndReason,
570
- type CallError,
571
- type CallErrorCode,
572
- type CallState,
573
- type ClientTool,
574
- type ClientToolMap,
575
- type FetchToken,
576
- type FetchTokenArgs,
577
- type FetchTokenResult,
578
- type IncomingCallPayload,
579
- type NodeCall,
580
- type NodeStartCallOptions,
581
- type NodeVoiceClientFactory,
582
- type ProtocolCallbacks,
583
- type ProtocolState,
584
- type RWSEvent,
585
- type RWSOptions,
586
- type ReconnectingWebSocket,
587
- type ServerMessage,
588
- type StartCallOptions,
589
- type TranscriptEntry,
590
- type VoiceClientConfig,
591
- type VoiceClientFactory,
592
- type VolumeEvent,
593
- type WebSocketFactory,
594
- type WebSocketLike,
595
- buildWsUrl,
596
- configureVoiceClient,
597
- createProtocolState,
598
- createReconnectingWebSocket,
599
- handleServerMessage,
600
- parseIncomingCall,
601
- }
447
+ 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 };