@craftedxp/voice-js 0.6.0 → 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.
package/CONSUMING.md CHANGED
@@ -9,9 +9,11 @@ Walks through the three install paths (local tarball → fastest, file: dep →
9
9
  npm install @craftedxp/voice-js
10
10
  # Node consumers also need:
11
11
  npm install ws
12
+ # Multi-party rooms (joinRoom) also need:
13
+ npm install livekit-client
12
14
 
13
- # wire up:
14
- import { configureVoiceClient } from '@craftedxp/voice-js'
15
+ # wire up (since 0.7.0, import from the subpath for your agent type):
16
+ import { configureVoiceClient } from '@craftedxp/voice-js/assistant'
15
17
  const voice = configureVoiceClient({ apiBase, fetchToken })
16
18
  const call = await voice.startCall({ agentId, ...callbacks })
17
19
  ```
@@ -61,6 +63,8 @@ npm install ws
61
63
 
62
64
  `ws` is a peer dependency declared as `peerDependenciesMeta.optional` so npm doesn't force-install it for browser-only consumers. Add it explicitly when running under Node / Electron-main.
63
65
 
66
+ `livekit-client` is likewise an optional peer (since 0.7.0 — it was a direct dependency in 0.5.x–0.6.x). It's only needed if you import from `@craftedxp/voice-js/room`; assistant / transcribe consumers never download it. `joinRoom` lazy-loads it and throws an actionable error if it's missing.
67
+
64
68
  ## Backend setup — minting `ct_` tokens
65
69
 
66
70
  Your `fetchToken` callback hits YOUR backend. Your backend uses the `sk_` API key (held server-side only) to mint a short-lived `ct_` for the SDK to use. Pattern:
package/README.md CHANGED
@@ -12,9 +12,26 @@ Companion to [`@craftedxp/voice-rn`](https://www.npmjs.com/package/@craftedxp/vo
12
12
  npm install @craftedxp/voice-js
13
13
  # Node consumers also need:
14
14
  npm install ws
15
+ # Multi-party rooms only (joinRoom) also need:
16
+ npm install livekit-client
15
17
  ```
16
18
 
17
- `ws` is declared as an OPTIONAL peer — only needed in Node / Electron-main. Browsers use the native `WebSocket` and skip it.
19
+ `ws` and `livekit-client` are both declared as OPTIONAL peers:
20
+
21
+ - `ws` — only needed in Node / Electron-main. Browsers use the native `WebSocket` and skip it.
22
+ - `livekit-client` — only needed if you import from `@craftedxp/voice-js/room`. Since 0.7.0 it is no longer a direct dependency, so assistant / transcribe consumers never download it (the embed widget dropped from ~854 KB to ~100 KB). `joinRoom` lazy-`import()`s it and throws an actionable error if it's missing.
23
+
24
+ ## Entry points (since 0.7.0)
25
+
26
+ Import from the subpath that matches your [agent type](https://www.npmjs.com/package/@craftedxp/sdk-node) so you only pull in the code you use:
27
+
28
+ | Import | Surface | Agent type |
29
+ | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
30
+ | `@craftedxp/voice-js/assistant` | `configureVoiceClient` → `startCall` (1:1 voice) + `startTextSession` (text chat) + client tools. **No LiveKit in the module graph.** | `assistant` |
31
+ | `@craftedxp/voice-js/room` | `joinRoom` → `RoomSession`. The only entry that touches LiveKit (lazy, optional peer). | `room` |
32
+ | `@craftedxp/voice-js/transcribe` | `configureTranscribeClient` → `startDictation` (STT-only). | `transcribe` |
33
+ | `@craftedxp/voice-js/node` | Raw-PCM assistant client for Node / Electron-main. | `assistant` |
34
+ | `@craftedxp/voice-js` | Deprecated barrel — re-exports everything for back-compat. Prefer a subpath; the barrel still pulls LiveKit lazily but defeats the bundle savings. | — |
18
35
 
19
36
  ## How the integration fits together
20
37
 
@@ -36,7 +53,7 @@ The `sk_` API key never lives in browser code. **The SDK has no `apiKey` option*
36
53
  ## Quick start (browser)
37
54
 
38
55
  ```ts
39
- import { configureVoiceClient } from '@craftedxp/voice-js'
56
+ import { configureVoiceClient } from '@craftedxp/voice-js/assistant'
40
57
 
41
58
  const voice = configureVoiceClient({
42
59
  apiBase: 'https://api.your-server.com',
@@ -186,9 +203,15 @@ Node consumers get a `NodeCall` extension with one extra method:
186
203
  ```ts
187
204
  interface NodeCall extends Call {
188
205
  sendAudioChunk: (pcm: ArrayBuffer | ArrayBufferView) => boolean
206
+ sendClientEvent: (text: string) => boolean
207
+ sendText: (text: string) => boolean
189
208
  }
190
209
  ```
191
210
 
211
+ `sendClientEvent(text)` pushes a short advisory context line to the live call (e.g. a browser action the user just took). The server buffers these and shows them to the LLM ahead of the next turn — they never trigger a response by themselves. Text is trimmed and truncated to 400 chars. Returns `false` if the WS isn't open yet or text is empty.
212
+
213
+ `sendText(text)` sends a typed user turn — `{type:'user_text', text}` — for text/multimodal sessions opened with a `channel:'text'` token. The server accepts `user_text` only on text-channel sessions and rejects it on voice calls (anti-injection), so on a voice call this is a no-op beyond the frame being ignored server-side. Text is trimmed. Returns `false` (and sends nothing) if the WS isn't open or text is empty/whitespace. Never throws.
214
+
192
215
  ### Stable types
193
216
 
194
217
  ```ts
@@ -272,7 +295,7 @@ surface-only actions (read DOM state, hit a private API, mutate local storage,
272
295
  control the UI).
273
296
 
274
297
  ```ts
275
- import { configureVoiceClient, type ClientToolMap } from '@craftedxp/voice-js'
298
+ import { configureVoiceClient, type ClientToolMap } from '@craftedxp/voice-js/assistant'
276
299
 
277
300
  const tools: ClientToolMap = {
278
301
  addTodoItem: {
@@ -397,7 +420,11 @@ Renders a floating call button with a Shadow-DOM transcript panel. Pre-mint the
397
420
 
398
421
  ## Status
399
422
 
400
- - **0.5.4** (current) — Screen sharing. `setScreenShareEnabled(on, { audio })` publishes a `screen_share` video track (and, where the browser allows, a `screen_share_audio` track Chrome captures tab/system audio; macOS Chrome is tab-audio only; Safari/Firefox don't capture share audio). `RoomTrackEvent` gains a `source` field (`camera` / `microphone` / `screen_share` / `screen_share_audio` / `unknown`) so a screen share can render as its own tile instead of replacing the participant's camera. Adds `isScreenShareEnabled()` and `getLocalScreenTrack()`. No 1:1 call-surface change. Drop-in for 0.5.3 consumers (the new `source` field is additive).
423
+ - **0.9.0** (current) — `sendText(text)` on `NodeCall`: typed user turns for `channel:'text'` WS sessions (`user_text` frame). Server accepts `user_text` only on text-channel sessions and rejects it on voice-channel sessions (anti-injection). Text is trimmed; returns `false` (and sends nothing) if the WS isn't open or text is empty/whitespace, never throws. Additive drop-in for 0.8.0 consumers.
424
+ - 0.8.0 — `sendClientEvent(text)` on `NodeCall`: advisory context lines for live calls (`client_event` frame). Server buffers these and prepends them to the next turn's context; never triggers a response by itself. Text is trimmed and truncated to 400 chars; returns `false` if the WS isn't open yet or text is empty. Additive — drop-in for 0.7.0 consumers.
425
+ - 0.7.0 — Per-type entry points + LiveKit goes optional. New subpaths `@craftedxp/voice-js/assistant`, `/room`, `/transcribe` (plus the existing `/node`); import the one matching your [agent `type`](https://www.npmjs.com/package/@craftedxp/sdk-node). `livekit-client` moved from `dependencies` to an **optional peerDependency** — assistant / transcribe consumers no longer download it, and the embed widget dropped from ~854 KB to ~100 KB. `joinRoom` lazy-`import()`s LiveKit and throws an actionable error if it isn't installed, so the deprecated barrel keeps working for non-room consumers. **Breaking for room consumers:** add `npm install livekit-client`. Non-room consumers are drop-in (prefer migrating barrel imports to `/assistant`).
426
+ - 0.6.0 — Text chat sessions. `startTextSession({ token, agentId, … })` opens an HTTP+SSE chat against a `channel: 'text'` token — no microphone / audio. Exposed from the assistant entry. Additive; voice consumers see no change.
427
+ - 0.5.4 — Screen sharing. `setScreenShareEnabled(on, { audio })` publishes a `screen_share` video track (and, where the browser allows, a `screen_share_audio` track — Chrome captures tab/system audio; macOS Chrome is tab-audio only; Safari/Firefox don't capture share audio). `RoomTrackEvent` gains a `source` field (`camera` / `microphone` / `screen_share` / `screen_share_audio` / `unknown`) so a screen share can render as its own tile instead of replacing the participant's camera. Adds `isScreenShareEnabled()` and `getLocalScreenTrack()`. No 1:1 call-surface change. Drop-in for 0.5.3 consumers (the new `source` field is additive).
401
428
  - 0.5.3 — `session.participantId` (this session's own stable `p_…` id). `active.speakers` includes the local participant, so a focus-tile UI rendered you as a remote when you spoke (no remote track → blank tile with the raw id). Filter `participantId` out of `active.speakers` to focus only remote speakers (self-view when none). No 1:1 call-surface change. Drop-in upgrade for 0.5.2 consumers.
402
429
  - 0.5.2 — `getRemoteTracks(): RoomTrackEvent[]`. Returns remote tracks already subscribed at call time. A late joiner misses the live `track.subscribed` events for tracks published before it connected (LiveKit delivers them during `connect`, before consumer listeners attach), so it never rendered participants who already had their camera on (e.g. the host). Call `getRemoteTracks()` right after registering `track.subscribed` to backfill them. No 1:1 call-surface change. Drop-in upgrade for 0.5.1 consumers.
403
430
  - 0.5.1 — Room video surface. `RoomSession` gains: `track.subscribed` / `track.unsubscribed` events with payload `{ participantId: string; kind: 'audio' | 'video'; track: RemoteTrack }` (raw livekit-client track — call `track.attach(el)` / `track.detach()`); `active.speakers` event (`string[]` of participantIds currently speaking, drives active-speaker UI); `setMicEnabled(on: boolean): Promise<void>` / `setCameraEnabled(on: boolean): Promise<void>` (mid-call toggles); `isMicEnabled(): boolean` / `isCameraEnabled(): boolean` (read current state for toggle button UI); `getLocalCameraTrack(): LocalVideoTrack | null` (attach to self-view element). No API changes to the 1:1 call surface (`startCall` / `Call`). Drop-in upgrade for 0.5.0 consumers.
@@ -0,0 +1,32 @@
1
+ import { V as VoiceClientConfig, a as VoiceClientFactory } from './config-D2TbvIqT.mjs';
2
+ export { C as Call, b as CallEndEvent, c as CallEndReason, d as CallError, e as CallErrorCode, f as CallState, g as ChatEvent, h as ClientTool, i as ClientToolMap, F as FetchToken, j as FetchTokenArgs, k as FetchTokenResult, P as ProtocolCallbacks, l as ProtocolState, S as ServerMessage, m as StartCallOptions, n as StartTextSessionOpts, T as TextSession, o as TranscriptEntry, p as VolumeEvent, q as buildWsUrl, r as createProtocolState, s as handleServerMessage, t as startTextSession } from './config-D2TbvIqT.mjs';
3
+ export { C as CaptureController, a as CaptureOptions, I as IncomingCallPayload, O as OnAgentSpeakingChange, b as OnChunk, c as OnError, d as OnVolume, P as PlaybackController, e as PlaybackOptions, R as RWSEvent, f as RWSOptions, g as ReconnectingWebSocket, W as WebSocketFactory, h as WebSocketLike, i as createAudioCapture, j as createAudioPlayback, k as createReconnectingWebSocket, p as parseIncomingCall } from './incomingCall-CfRRzj2P.mjs';
4
+
5
+ /**
6
+ * One-time SDK setup. Returns a factory you call `startCall` on for
7
+ * every voice call.
8
+ *
9
+ * Example:
10
+ * const voice = configureVoiceClient({
11
+ * apiBase: 'https://api.your-server.com',
12
+ * fetchToken: async ({ agentId }) => {
13
+ * const r = await fetch('/api/voice-token', {
14
+ * method: 'POST',
15
+ * body: JSON.stringify({ agentId }),
16
+ * })
17
+ * return (await r.json()).token
18
+ * },
19
+ * })
20
+ *
21
+ * // Per call (typically inside a click handler):
22
+ * const call = await voice.startCall({
23
+ * agentId: 'agt_xxx',
24
+ * onTranscript: (entries) => render(entries),
25
+ * onEnd: ({ reason }) => log(reason),
26
+ * })
27
+ * call.mute()
28
+ * call.end()
29
+ */
30
+ declare function configureVoiceClient(config: VoiceClientConfig): VoiceClientFactory;
31
+
32
+ export { VoiceClientConfig, VoiceClientFactory, configureVoiceClient };
@@ -0,0 +1,32 @@
1
+ import { V as VoiceClientConfig, a as VoiceClientFactory } from './config-D2TbvIqT.js';
2
+ export { C as Call, b as CallEndEvent, c as CallEndReason, d as CallError, e as CallErrorCode, f as CallState, g as ChatEvent, h as ClientTool, i as ClientToolMap, F as FetchToken, j as FetchTokenArgs, k as FetchTokenResult, P as ProtocolCallbacks, l as ProtocolState, S as ServerMessage, m as StartCallOptions, n as StartTextSessionOpts, T as TextSession, o as TranscriptEntry, p as VolumeEvent, q as buildWsUrl, r as createProtocolState, s as handleServerMessage, t as startTextSession } from './config-D2TbvIqT.js';
3
+ export { C as CaptureController, a as CaptureOptions, I as IncomingCallPayload, O as OnAgentSpeakingChange, b as OnChunk, c as OnError, d as OnVolume, P as PlaybackController, e as PlaybackOptions, R as RWSEvent, f as RWSOptions, g as ReconnectingWebSocket, W as WebSocketFactory, h as WebSocketLike, i as createAudioCapture, j as createAudioPlayback, k as createReconnectingWebSocket, p as parseIncomingCall } from './incomingCall-CfRRzj2P.js';
4
+
5
+ /**
6
+ * One-time SDK setup. Returns a factory you call `startCall` on for
7
+ * every voice call.
8
+ *
9
+ * Example:
10
+ * const voice = configureVoiceClient({
11
+ * apiBase: 'https://api.your-server.com',
12
+ * fetchToken: async ({ agentId }) => {
13
+ * const r = await fetch('/api/voice-token', {
14
+ * method: 'POST',
15
+ * body: JSON.stringify({ agentId }),
16
+ * })
17
+ * return (await r.json()).token
18
+ * },
19
+ * })
20
+ *
21
+ * // Per call (typically inside a click handler):
22
+ * const call = await voice.startCall({
23
+ * agentId: 'agt_xxx',
24
+ * onTranscript: (entries) => render(entries),
25
+ * onEnd: ({ reason }) => log(reason),
26
+ * })
27
+ * call.mute()
28
+ * call.end()
29
+ */
30
+ declare function configureVoiceClient(config: VoiceClientConfig): VoiceClientFactory;
31
+
32
+ export { VoiceClientConfig, VoiceClientFactory, configureVoiceClient };