@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/node.js CHANGED
@@ -1,733 +1,719 @@
1
- 'use strict'
2
- var __create = Object.create
3
- var __defProp = Object.defineProperty
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor
5
- var __getOwnPropNames = Object.getOwnPropertyNames
6
- var __getProtoOf = Object.getPrototypeOf
7
- var __hasOwnProp = Object.prototype.hasOwnProperty
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
8
  var __export = (target, all) => {
9
- for (var name in all) __defProp(target, name, { get: all[name], enumerable: true })
10
- }
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
11
12
  var __copyProps = (to, from, except, desc) => {
12
- if ((from && typeof from === 'object') || typeof from === 'function') {
13
+ if (from && typeof from === "object" || typeof from === "function") {
13
14
  for (let key of __getOwnPropNames(from))
14
15
  if (!__hasOwnProp.call(to, key) && key !== except)
15
- __defProp(to, key, {
16
- get: () => from[key],
17
- enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable,
18
- })
19
- }
20
- return to
21
- }
22
- var __toESM = (mod, isNodeMode, target) => (
23
- (target = mod != null ? __create(__getProtoOf(mod)) : {}),
24
- __copyProps(
25
- // If the importer is in node compatibility mode or this is not an ESM
26
- // file that has been converted to a CommonJS file using a Babel-
27
- // compatible transform (i.e. "__esModule" has not been set), then set
28
- // "default" to the CommonJS "module.exports" for node compatibility.
29
- isNodeMode || !mod || !mod.__esModule
30
- ? __defProp(target, 'default', { value: mod, enumerable: true })
31
- : target,
32
- mod,
33
- )
34
- )
35
- var __toCommonJS = (mod) => __copyProps(__defProp({}, '__esModule', { value: true }), mod)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
36
29
 
37
30
  // src/node.ts
38
- var node_exports = {}
31
+ var node_exports = {};
39
32
  __export(node_exports, {
40
33
  buildWsUrl: () => buildWsUrl,
41
34
  configureVoiceClient: () => configureVoiceClient,
42
35
  createProtocolState: () => createProtocolState,
43
36
  createReconnectingWebSocket: () => createReconnectingWebSocket,
44
37
  handleServerMessage: () => handleServerMessage,
45
- parseIncomingCall: () => parseIncomingCall,
46
- })
47
- module.exports = __toCommonJS(node_exports)
38
+ parseIncomingCall: () => parseIncomingCall
39
+ });
40
+ module.exports = __toCommonJS(node_exports);
48
41
 
49
42
  // src/config.ts
50
43
  function normalizeConfig(config) {
51
- if (!config) throw new Error('configureVoiceClient: config is required')
52
- if ('apiKey' in config) {
44
+ if (!config) throw new Error("configureVoiceClient: config is required");
45
+ if ("apiKey" in config) {
53
46
  throw new Error(
54
- 'configureVoiceClient: `apiKey` is no longer supported. Embedding sk_ in JS code ships server-grade credentials to every client. Pass `fetchToken: async ({ agentId }) => { /* call YOUR backend mint */ }` instead \u2014 see the @craftedxp/voice-js README for the migration recipe.',
55
- )
47
+ "configureVoiceClient: `apiKey` is no longer supported. Embedding sk_ in JS code ships server-grade credentials to every client. Pass `fetchToken: async ({ agentId }) => { /* call YOUR backend mint */ }` instead \u2014 see the @craftedxp/voice-js README for the migration recipe."
48
+ );
56
49
  }
57
50
  if (!config.apiBase) {
58
- throw new Error('configureVoiceClient: apiBase is required')
51
+ throw new Error("configureVoiceClient: apiBase is required");
59
52
  }
60
- if (typeof config.fetchToken !== 'function') {
61
- throw new Error('configureVoiceClient: fetchToken must be a function')
53
+ if (typeof config.fetchToken !== "function") {
54
+ throw new Error("configureVoiceClient: fetchToken must be a function");
62
55
  }
63
56
  return {
64
57
  ...config,
65
- apiBase: config.apiBase.replace(/\/+$/, ''),
66
- }
58
+ apiBase: config.apiBase.replace(/\/+$/, "")
59
+ };
67
60
  }
68
61
  function mergeStartCallContext(factory, call) {
69
- const context =
70
- factory.defaultContext || call.context
71
- ? { ...(factory.defaultContext ?? {}), ...(call.context ?? {}) }
72
- : void 0
73
- const metadata =
74
- factory.defaultMetadata || call.metadata
75
- ? { ...(factory.defaultMetadata ?? {}), ...(call.metadata ?? {}) }
76
- : void 0
77
- return { context, metadata }
62
+ const context = factory.defaultContext || call.context ? { ...factory.defaultContext ?? {}, ...call.context ?? {} } : void 0;
63
+ const metadata = factory.defaultMetadata || call.metadata ? { ...factory.defaultMetadata ?? {}, ...call.metadata ?? {} } : void 0;
64
+ return { context, metadata };
78
65
  }
79
66
 
80
67
  // src/ReconnectingWebSocket.ts
81
- var READYSTATE_OPEN = 1
82
- var READYSTATE_CLOSED = 3
68
+ var READYSTATE_OPEN = 1;
69
+ var READYSTATE_CLOSED = 3;
83
70
  var createReconnectingWebSocket = (options, onEvent) => {
84
- const maxRetries = options.maxRetries ?? 3
85
- const initialBackoff = options.initialBackoffMs ?? 500
86
- const maxBackoff = options.maxBackoffMs ?? 8e3
87
- let ws = null
88
- let intentionalClose = false
89
- let retries = 0
90
- let backoff = initialBackoff
91
- let reconnectTimer = null
71
+ const maxRetries = options.maxRetries ?? 3;
72
+ const initialBackoff = options.initialBackoffMs ?? 500;
73
+ const maxBackoff = options.maxBackoffMs ?? 8e3;
74
+ let ws = null;
75
+ let intentionalClose = false;
76
+ let retries = 0;
77
+ let backoff = initialBackoff;
78
+ let reconnectTimer = null;
92
79
  const openOnce = () => {
93
- ws = options.wsFactory(options.url)
94
- ws.binaryType = 'arraybuffer'
80
+ ws = options.wsFactory(options.url);
81
+ ws.binaryType = "arraybuffer";
95
82
  ws.onopen = () => {
96
- if (retries === 0) onEvent({ type: 'open' })
97
- else onEvent({ type: 'reconnected' })
98
- retries = 0
99
- backoff = initialBackoff
100
- }
83
+ if (retries === 0) onEvent({ type: "open" });
84
+ else onEvent({ type: "reconnected" });
85
+ retries = 0;
86
+ backoff = initialBackoff;
87
+ };
101
88
  ws.onmessage = (ev) => {
102
- onEvent({ type: 'message', data: ev.data })
103
- }
89
+ onEvent({ type: "message", data: ev.data });
90
+ };
104
91
  ws.onerror = () => {
105
- onEvent({ type: 'error', error: new Error('WebSocket error') })
106
- }
92
+ onEvent({ type: "error", error: new Error("WebSocket error") });
93
+ };
107
94
  ws.onclose = (ev) => {
108
- ws = null
109
- const shouldRetry = !intentionalClose && retries < maxRetries
95
+ ws = null;
96
+ const shouldRetry = !intentionalClose && retries < maxRetries;
110
97
  if (!shouldRetry) {
111
98
  onEvent({
112
- type: 'close',
99
+ type: "close",
113
100
  code: ev.code,
114
101
  reason: ev.reason,
115
- permanent: true,
116
- })
117
- return
102
+ permanent: true
103
+ });
104
+ return;
118
105
  }
119
106
  onEvent({
120
- type: 'close',
107
+ type: "close",
121
108
  code: ev.code,
122
109
  reason: ev.reason,
123
- permanent: false,
124
- })
125
- retries++
126
- const delay = Math.min(backoff, maxBackoff)
127
- backoff = Math.min(backoff * 2, maxBackoff)
128
- reconnectTimer = setTimeout(openOnce, delay)
129
- }
130
- }
131
- openOnce()
110
+ permanent: false
111
+ });
112
+ retries++;
113
+ const delay = Math.min(backoff, maxBackoff);
114
+ backoff = Math.min(backoff * 2, maxBackoff);
115
+ reconnectTimer = setTimeout(openOnce, delay);
116
+ };
117
+ };
118
+ openOnce();
132
119
  return {
133
120
  send: (data) => {
134
- if (ws && ws.readyState === READYSTATE_OPEN) ws.send(data)
121
+ if (ws && ws.readyState === READYSTATE_OPEN) ws.send(data);
135
122
  },
136
- close: (code = 1e3, reason = 'client-requested') => {
137
- intentionalClose = true
123
+ close: (code = 1e3, reason = "client-requested") => {
124
+ intentionalClose = true;
138
125
  if (reconnectTimer) {
139
- clearTimeout(reconnectTimer)
140
- reconnectTimer = null
126
+ clearTimeout(reconnectTimer);
127
+ reconnectTimer = null;
141
128
  }
142
129
  try {
143
- ws?.close(code, reason)
144
- } catch {}
130
+ ws?.close(code, reason);
131
+ } catch {
132
+ }
145
133
  },
146
- readyState: () => ws?.readyState ?? READYSTATE_CLOSED,
147
- }
148
- }
134
+ readyState: () => ws?.readyState ?? READYSTATE_CLOSED
135
+ };
136
+ };
149
137
 
150
138
  // src/protocol.ts
151
139
  var createProtocolState = () => ({
152
- state: 'idle',
140
+ state: "idle",
153
141
  transcript: [],
154
142
  agentBubbleId: null,
155
143
  idCounter: 0,
156
- endReason: null,
157
- })
144
+ endReason: null
145
+ });
158
146
  var mapEndReason = (raw) => {
159
- if (raw === 'agent_ended') return 'agent_ended'
160
- if (raw === 'caller_hung_up') return 'user_hangup'
161
- if (raw === 'silence_timeout' || raw === 'max_duration') return 'timeout'
162
- return 'error'
163
- }
147
+ if (raw === "agent_ended") return "agent_ended";
148
+ if (raw === "caller_hung_up") return "user_hangup";
149
+ if (raw === "silence_timeout" || raw === "max_duration") return "timeout";
150
+ return "error";
151
+ };
164
152
  function handleServerMessage(raw, state, cb) {
165
- let msg
153
+ let msg;
166
154
  try {
167
- msg = JSON.parse(raw)
155
+ msg = JSON.parse(raw);
168
156
  } catch {
169
- return
157
+ return;
170
158
  }
171
159
  switch (msg.type) {
172
- case 'connected':
173
- cb.onConnected()
174
- setState(state, 'listening', cb)
175
- return
176
- case 'transcript': {
177
- const text = msg.text ?? ''
178
- if (!text) return
179
- const isFinal = !!msg.isFinal
180
- if (!isFinal) setState(state, 'user_speaking', cb)
181
- upsertUserPartial(state, text, isFinal)
182
- cb.onTranscript(state.transcript)
183
- return
184
- }
185
- case 'agent_turn_start': {
186
- const id = `m${state.idCounter++}`
187
- state.agentBubbleId = id
188
- state.transcript = [...state.transcript, { id, role: 'agent', text: '' }]
189
- cb.onTranscript(state.transcript)
190
- const seq = typeof msg.seq === 'number' ? msg.seq : void 0
191
- cb.onAgentTurnStart(seq)
192
- setState(state, 'agent_speaking', cb)
193
- return
194
- }
195
- case 'agent_text': {
196
- const delta = msg.text ?? ''
197
- if (!delta || !state.agentBubbleId) return
198
- const id = state.agentBubbleId
199
- state.transcript = state.transcript.map((e) =>
200
- e.id === id && e.role === 'agent' ? { ...e, text: e.text + delta } : e,
201
- )
202
- cb.onTranscript(state.transcript)
203
- return
204
- }
205
- case 'agent_turn_end': {
206
- state.agentBubbleId = null
207
- const seq = typeof msg.seq === 'number' ? msg.seq : void 0
208
- cb.onAgentTurnEnd(seq)
209
- setState(state, 'listening', cb)
210
- return
211
- }
212
- case 'interrupt':
213
- cb.onInterrupt()
214
- return
215
- case 'agent_turn_abort': {
216
- const committed = (msg.committedText ?? '').trim()
160
+ case "connected":
161
+ cb.onConnected();
162
+ setState(state, "listening", cb);
163
+ return;
164
+ case "transcript": {
165
+ const text = msg.text ?? "";
166
+ if (!text) return;
167
+ const isFinal = !!msg.isFinal;
168
+ if (!isFinal) setState(state, "user_speaking", cb);
169
+ upsertUserPartial(state, text, isFinal);
170
+ cb.onTranscript(state.transcript);
171
+ return;
172
+ }
173
+ case "agent_turn_start": {
174
+ const id = `m${state.idCounter++}`;
175
+ state.agentBubbleId = id;
176
+ state.transcript = [...state.transcript, { id, role: "agent", text: "" }];
177
+ cb.onTranscript(state.transcript);
178
+ const seq = typeof msg.seq === "number" ? msg.seq : void 0;
179
+ cb.onAgentTurnStart(seq);
180
+ setState(state, "agent_speaking", cb);
181
+ return;
182
+ }
183
+ case "agent_text": {
184
+ const delta = msg.text ?? "";
185
+ if (!delta || !state.agentBubbleId) return;
186
+ const id = state.agentBubbleId;
187
+ state.transcript = state.transcript.map(
188
+ (e) => e.id === id && e.role === "agent" ? { ...e, text: e.text + delta } : e
189
+ );
190
+ cb.onTranscript(state.transcript);
191
+ return;
192
+ }
193
+ case "agent_turn_end": {
194
+ state.agentBubbleId = null;
195
+ const seq = typeof msg.seq === "number" ? msg.seq : void 0;
196
+ cb.onAgentTurnEnd(seq);
197
+ setState(state, "listening", cb);
198
+ return;
199
+ }
200
+ case "interrupt":
201
+ cb.onInterrupt();
202
+ return;
203
+ case "agent_turn_abort": {
204
+ const committed = (msg.committedText ?? "").trim();
217
205
  if (state.agentBubbleId) {
218
- const id = state.agentBubbleId
206
+ const id = state.agentBubbleId;
219
207
  if (committed) {
220
- state.transcript = state.transcript.map((e) =>
221
- e.id === id && e.role === 'agent' ? { ...e, text: committed, interrupted: true } : e,
222
- )
208
+ state.transcript = state.transcript.map(
209
+ (e) => e.id === id && e.role === "agent" ? { ...e, text: committed, interrupted: true } : e
210
+ );
223
211
  } else {
224
- state.transcript = state.transcript.filter((e) => e.id !== id)
212
+ state.transcript = state.transcript.filter((e) => e.id !== id);
225
213
  }
226
- cb.onTranscript(state.transcript)
214
+ cb.onTranscript(state.transcript);
227
215
  }
228
- state.agentBubbleId = null
229
- return
216
+ state.agentBubbleId = null;
217
+ return;
230
218
  }
231
- case 'tool_call':
219
+ case "tool_call":
232
220
  state.transcript = [
233
221
  ...state.transcript,
234
222
  {
235
223
  id: `m${state.idCounter++}`,
236
- role: 'tool',
237
- text: `\u2192 ${String(msg.tool ?? '?')}(${msg.args ? JSON.stringify(msg.args) : ''})`,
238
- },
239
- ]
240
- cb.onTranscript(state.transcript)
241
- return
242
- case 'tool_result':
224
+ role: "tool",
225
+ text: `\u2192 ${String(msg.tool ?? "?")}(${msg.args ? JSON.stringify(msg.args) : ""})`
226
+ }
227
+ ];
228
+ cb.onTranscript(state.transcript);
229
+ return;
230
+ case "tool_result":
243
231
  state.transcript = [
244
232
  ...state.transcript,
245
233
  {
246
234
  id: `m${state.idCounter++}`,
247
- role: 'tool',
248
- text: `${msg.ok ? '\u2713' : '\u2717'} ${String(msg.tool ?? '?')}`,
249
- },
250
- ]
251
- cb.onTranscript(state.transcript)
252
- return
253
- case 'client_tool_call': {
254
- const toolCallId = String(msg.toolCallId ?? '')
255
- const name = String(msg.name ?? '')
256
- const args = msg.args ?? {}
257
- if (!toolCallId || !name) return
258
- cb.onClientToolCall({ toolCallId, name, args })
259
- return
260
- }
261
- case 'call_end': {
262
- const reasonRaw = String(msg.reason ?? '')
263
- const reason = mapEndReason(reasonRaw)
264
- state.endReason = reason
235
+ role: "tool",
236
+ text: `${msg.ok ? "\u2713" : "\u2717"} ${String(msg.tool ?? "?")}`
237
+ }
238
+ ];
239
+ cb.onTranscript(state.transcript);
240
+ return;
241
+ case "client_tool_call": {
242
+ const toolCallId = String(msg.toolCallId ?? "");
243
+ const name = String(msg.name ?? "");
244
+ const args = msg.args ?? {};
245
+ if (!toolCallId || !name) return;
246
+ cb.onClientToolCall({ toolCallId, name, args });
247
+ return;
248
+ }
249
+ case "call_end": {
250
+ const reasonRaw = String(msg.reason ?? "");
251
+ const reason = mapEndReason(reasonRaw);
252
+ state.endReason = reason;
265
253
  state.transcript = [
266
254
  ...state.transcript,
267
255
  {
268
256
  id: `m${state.idCounter++}`,
269
- role: 'system',
270
- text: `call ended${reasonRaw ? ` (${reasonRaw})` : ''}`,
271
- },
272
- ]
273
- cb.onTranscript(state.transcript)
274
- cb.onCallEnd(reason)
275
- return
257
+ role: "system",
258
+ text: `call ended${reasonRaw ? ` (${reasonRaw})` : ""}`
259
+ }
260
+ ];
261
+ cb.onTranscript(state.transcript);
262
+ cb.onCallEnd(reason);
263
+ return;
276
264
  }
277
- case 'error': {
278
- const code = msg.code ?? 'server_error'
279
- const message = msg.message ?? 'server error'
280
- cb.onError({ code, message })
281
- return
265
+ case "error": {
266
+ const code = msg.code ?? "server_error";
267
+ const message = msg.message ?? "server error";
268
+ cb.onError({ code, message });
269
+ return;
282
270
  }
283
271
  }
284
272
  }
285
273
  var setState = (state, next, cb) => {
286
- if (state.state === next) return
287
- cb.onState(next)
288
- }
274
+ if (state.state === next) return;
275
+ cb.onState(next);
276
+ };
289
277
  var upsertUserPartial = (state, text, isFinal) => {
290
- let idx = -1
278
+ let idx = -1;
291
279
  for (let i = state.transcript.length - 1; i >= 0; i--) {
292
- const e = state.transcript[i]
293
- if (e.role === 'user' && e.committed === false) {
294
- idx = i
295
- break
280
+ const e = state.transcript[i];
281
+ if (e.role === "user" && e.committed === false) {
282
+ idx = i;
283
+ break;
296
284
  }
297
285
  }
298
286
  if (idx === -1) {
299
287
  state.transcript = [
300
288
  ...state.transcript,
301
- { id: `m${state.idCounter++}`, role: 'user', text, committed: isFinal },
302
- ]
303
- return
304
- }
305
- const target = state.transcript[idx]
306
- const next = [...state.transcript]
307
- next[idx] = { ...target, text, committed: isFinal }
308
- state.transcript = next
309
- }
289
+ { id: `m${state.idCounter++}`, role: "user", text, committed: isFinal }
290
+ ];
291
+ return;
292
+ }
293
+ const target = state.transcript[idx];
294
+ const next = [...state.transcript];
295
+ next[idx] = { ...target, text, committed: isFinal };
296
+ state.transcript = next;
297
+ };
310
298
  function buildWsUrl(args) {
311
- const base = new URL(args.apiBase)
312
- const proto = base.protocol === 'https:' ? 'wss:' : 'ws:'
313
- const bargeQS = args.bargeIn === false ? '&barge=off' : ''
314
- return `${proto}//${base.host}/v1/agents/${encodeURIComponent(args.agentId)}/call?token=${encodeURIComponent(args.token)}${bargeQS}`
299
+ const base = new URL(args.apiBase);
300
+ const proto = base.protocol === "https:" ? "wss:" : "ws:";
301
+ const bargeQS = args.bargeIn === false ? "&barge=off" : "";
302
+ return `${proto}//${base.host}/v1/agents/${encodeURIComponent(args.agentId)}/call?token=${encodeURIComponent(args.token)}${bargeQS}`;
315
303
  }
316
304
 
317
305
  // src/clientTools.ts
318
- var NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/
319
- var MAX_TOOLS = 64
320
- var MAX_USAGE = 500
321
- var MAX_TIMEOUT_MS = 3e4
306
+ var NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
307
+ var MAX_TOOLS = 64;
308
+ var MAX_USAGE = 500;
309
+ var MAX_TIMEOUT_MS = 3e4;
322
310
  var validateClientToolMap = (tools) => {
323
- if (tools === void 0) return
324
- if (typeof tools !== 'object' || tools === null || Array.isArray(tools)) {
325
- throw new Error('clientTools must be an object keyed by tool name')
311
+ if (tools === void 0) return;
312
+ if (typeof tools !== "object" || tools === null || Array.isArray(tools)) {
313
+ throw new Error("clientTools must be an object keyed by tool name");
326
314
  }
327
- const entries = Object.entries(tools)
315
+ const entries = Object.entries(tools);
328
316
  if (entries.length > MAX_TOOLS) {
329
- throw new Error(`clientTools may declare at most 64 tools (got ${entries.length})`)
317
+ throw new Error(`clientTools may declare at most 64 tools (got ${entries.length})`);
330
318
  }
331
319
  for (const [name, def] of entries) {
332
320
  if (!NAME_RE.test(name)) {
333
321
  throw new Error(
334
- `clientTools["${name}"]: name must be a valid identifier (^[a-zA-Z_][a-zA-Z0-9_]*$)`,
335
- )
322
+ `clientTools["${name}"]: name must be a valid identifier (^[a-zA-Z_][a-zA-Z0-9_]*$)`
323
+ );
336
324
  }
337
- if (!def || typeof def !== 'object') {
338
- throw new Error(`clientTools["${name}"]: must be an object`)
325
+ if (!def || typeof def !== "object") {
326
+ throw new Error(`clientTools["${name}"]: must be an object`);
339
327
  }
340
- if (typeof def.description !== 'string' || def.description.length === 0) {
341
- throw new Error(`clientTools["${name}"]: must have a description`)
328
+ if (typeof def.description !== "string" || def.description.length === 0) {
329
+ throw new Error(`clientTools["${name}"]: must have a description`);
342
330
  }
343
- if (typeof def.handler !== 'function') {
344
- throw new Error(`clientTools["${name}"]: must have a handler function`)
331
+ if (typeof def.handler !== "function") {
332
+ throw new Error(`clientTools["${name}"]: must have a handler function`);
345
333
  }
346
334
  if (def.usage !== void 0 && def.usage.length > MAX_USAGE) {
347
- throw new Error(`clientTools["${name}"]: usage must be \u2264500 chars`)
335
+ throw new Error(`clientTools["${name}"]: usage must be \u2264500 chars`);
348
336
  }
349
- if (
350
- def.timeoutMs !== void 0 &&
351
- (!Number.isFinite(def.timeoutMs) || def.timeoutMs <= 0 || def.timeoutMs > MAX_TIMEOUT_MS)
352
- ) {
353
- throw new Error(`clientTools["${name}"]: timeoutMs must be in (0, 30000]`)
337
+ if (def.timeoutMs !== void 0 && (!Number.isFinite(def.timeoutMs) || def.timeoutMs <= 0 || def.timeoutMs > MAX_TIMEOUT_MS)) {
338
+ throw new Error(`clientTools["${name}"]: timeoutMs must be in (0, 30000]`);
354
339
  }
355
340
  }
356
- }
341
+ };
357
342
  var buildRegisterFrame = (tools) => ({
358
- type: 'client_tools_register',
343
+ type: "client_tools_register",
359
344
  tools: Object.entries(tools).map(([name, def]) => ({
360
345
  name,
361
346
  description: def.description,
362
347
  parameters: def.parameters,
363
- ...(def.usage !== void 0 ? { usage: def.usage } : {}),
364
- ...(def.timeoutMs !== void 0 ? { timeoutMs: def.timeoutMs } : {}),
365
- })),
366
- })
348
+ ...def.usage !== void 0 ? { usage: def.usage } : {},
349
+ ...def.timeoutMs !== void 0 ? { timeoutMs: def.timeoutMs } : {}
350
+ }))
351
+ });
367
352
  var dispatchClientToolCall = (send, tools, frame) => {
368
353
  const safeSend = (payload) => {
369
354
  try {
370
- send(payload)
371
- } catch {}
372
- }
373
- const tool = tools[frame.name]
355
+ send(payload);
356
+ } catch {
357
+ }
358
+ };
359
+ const tool = tools[frame.name];
374
360
  if (!tool) {
375
361
  safeSend({
376
- type: 'client_tool_result',
362
+ type: "client_tool_result",
377
363
  toolCallId: frame.toolCallId,
378
- error: `No handler for ${frame.name}`,
379
- })
380
- return
364
+ error: `No handler for ${frame.name}`
365
+ });
366
+ return;
381
367
  }
382
368
  void (async () => {
383
369
  try {
384
- const out = await tool.handler(frame.args)
370
+ const out = await tool.handler(frame.args);
385
371
  safeSend({
386
- type: 'client_tool_result',
372
+ type: "client_tool_result",
387
373
  toolCallId: frame.toolCallId,
388
- result: typeof out === 'string' ? out : JSON.stringify(out),
389
- })
374
+ result: typeof out === "string" ? out : JSON.stringify(out)
375
+ });
390
376
  } catch (err) {
391
377
  safeSend({
392
- type: 'client_tool_result',
378
+ type: "client_tool_result",
393
379
  toolCallId: frame.toolCallId,
394
- error: err instanceof Error ? err.message : String(err),
395
- })
380
+ error: err instanceof Error ? err.message : String(err)
381
+ });
396
382
  }
397
- })()
398
- }
383
+ })();
384
+ };
399
385
 
400
386
  // src/ClientMarksBuffer.ts
401
387
  var createClientMarksBuffer = (args) => {
402
- const now = args.now ?? (() => performance.now())
403
- let pendingFirstOutboundAt = null
404
- const inFlight = /* @__PURE__ */ new Map()
388
+ const now = args.now ?? (() => performance.now());
389
+ let pendingFirstOutboundAt = null;
390
+ const inFlight = /* @__PURE__ */ new Map();
405
391
  const tryEmit = (seq) => {
406
- const slot = inFlight.get(seq)
407
- if (!slot) return
408
- if (!slot.ended) return
409
- const marks = {}
392
+ const slot = inFlight.get(seq);
393
+ if (!slot) return;
394
+ if (!slot.ended) return;
395
+ const marks = {};
410
396
  if (slot.firstOutboundAt !== null && slot.firstAudibleAt !== null) {
411
- marks.client_mic_to_first_audible_ms = slot.firstAudibleAt - slot.firstOutboundAt
397
+ marks.client_mic_to_first_audible_ms = slot.firstAudibleAt - slot.firstOutboundAt;
412
398
  }
413
399
  args.send({
414
- type: 'client_marks',
400
+ type: "client_marks",
415
401
  seq,
416
402
  marks,
417
- clientNow: Date.now(),
418
- })
419
- inFlight.delete(seq)
420
- }
403
+ clientNow: Date.now()
404
+ });
405
+ inFlight.delete(seq);
406
+ };
421
407
  const markFirstOutboundAudio = () => {
422
- if (pendingFirstOutboundAt !== null) return
423
- pendingFirstOutboundAt = now()
424
- }
408
+ if (pendingFirstOutboundAt !== null) return;
409
+ pendingFirstOutboundAt = now();
410
+ };
425
411
  const markFirstAudibleOutput = () => {
426
- let target
412
+ let target;
427
413
  for (const slot of inFlight.values()) {
428
414
  if (!slot.ended) {
429
- target = slot
415
+ target = slot;
430
416
  }
431
417
  }
432
- if (!target) return
433
- if (target.firstAudibleAt !== null) return
434
- target.firstAudibleAt = now()
435
- }
418
+ if (!target) return;
419
+ if (target.firstAudibleAt !== null) return;
420
+ target.firstAudibleAt = now();
421
+ };
436
422
  const onAgentTurnStart = (seq) => {
437
423
  inFlight.set(seq, {
438
424
  firstOutboundAt: pendingFirstOutboundAt,
439
425
  firstAudibleAt: null,
440
- ended: false,
441
- })
442
- pendingFirstOutboundAt = null
443
- }
426
+ ended: false
427
+ });
428
+ pendingFirstOutboundAt = null;
429
+ };
444
430
  const onAgentTurnEnd = (seq) => {
445
- const slot = inFlight.get(seq)
431
+ const slot = inFlight.get(seq);
446
432
  if (!slot) {
447
- args.send({ type: 'client_marks', seq, marks: {}, clientNow: Date.now() })
448
- return
433
+ args.send({ type: "client_marks", seq, marks: {}, clientNow: Date.now() });
434
+ return;
449
435
  }
450
- slot.ended = true
451
- tryEmit(seq)
452
- }
436
+ slot.ended = true;
437
+ tryEmit(seq);
438
+ };
453
439
  const flush = () => {
454
440
  for (const seq of [...inFlight.keys()]) {
455
- const slot = inFlight.get(seq)
456
- slot.ended = true
457
- tryEmit(seq)
441
+ const slot = inFlight.get(seq);
442
+ slot.ended = true;
443
+ tryEmit(seq);
458
444
  }
459
- pendingFirstOutboundAt = null
460
- }
445
+ pendingFirstOutboundAt = null;
446
+ };
461
447
  return {
462
448
  markFirstOutboundAudio,
463
449
  markFirstAudibleOutput,
464
450
  onAgentTurnStart,
465
451
  onAgentTurnEnd,
466
- flush,
467
- }
468
- }
452
+ flush
453
+ };
454
+ };
469
455
 
470
456
  // src/NodeVoiceClient.ts
471
457
  var NodeVoiceClient = class {
472
458
  constructor(args) {
473
- this.rws = null
474
- this.muted = false
475
- this.startedAt = null
476
- this.endedFired = false
477
- this.lastError = null
459
+ this.rws = null;
460
+ this.muted = false;
461
+ this.startedAt = null;
462
+ this.endedFired = false;
463
+ this.lastError = null;
478
464
  this.end = () => {
479
- this.teardown('user_hangup')
480
- }
465
+ this.teardown("user_hangup");
466
+ };
481
467
  this.mute = () => {
482
- this.muted = true
483
- }
468
+ this.muted = true;
469
+ };
484
470
  this.unmute = () => {
485
- this.muted = false
486
- }
471
+ this.muted = false;
472
+ };
487
473
  // ---------------------------------------------------------------
488
474
  // Node-only raw audio surface
489
475
  // ---------------------------------------------------------------
490
476
  this.sendAudioChunk = (pcm) => {
491
- if (!this.rws) return false
492
- this.marks.markFirstOutboundAudio()
477
+ if (!this.rws) return false;
478
+ this.marks.markFirstOutboundAudio();
493
479
  if (this.muted) {
494
- const len = ArrayBuffer.isView(pcm) ? pcm.byteLength : pcm.byteLength
495
- this.rws.send(new ArrayBuffer(len))
496
- return true
480
+ const len = ArrayBuffer.isView(pcm) ? pcm.byteLength : pcm.byteLength;
481
+ this.rws.send(new ArrayBuffer(len));
482
+ return true;
497
483
  }
498
- this.rws.send(pcm)
499
- return true
500
- }
484
+ this.rws.send(pcm);
485
+ return true;
486
+ };
501
487
  // ---------------------------------------------------------------
502
488
  // Internal
503
489
  // ---------------------------------------------------------------
504
490
  this.setState = (next) => {
505
- if (this.proto.state === next) return
506
- this.proto.state = next
507
- this.args.options.onStateChange?.(next)
508
- }
491
+ if (this.proto.state === next) return;
492
+ this.proto.state = next;
493
+ this.args.options.onStateChange?.(next);
494
+ };
509
495
  this.sendClientToolsRegister = () => {
510
- const frame = buildRegisterFrame(this.args.options.clientTools ?? {})
511
- this.rws?.send(JSON.stringify(frame))
512
- }
496
+ const frame = buildRegisterFrame(this.args.options.clientTools ?? {});
497
+ this.rws?.send(JSON.stringify(frame));
498
+ };
513
499
  this.emitError = (err) => {
514
- this.lastError = err
515
- this.args.options.onError?.(err)
516
- }
500
+ this.lastError = err;
501
+ this.args.options.onError?.(err);
502
+ };
517
503
  this.handleSocketEvent = (ev) => {
518
504
  switch (ev.type) {
519
- case 'open':
520
- break
521
- case 'reconnected':
522
- this.proto.transcript = []
523
- this.proto.agentBubbleId = null
524
- this.args.options.onTranscript?.(this.proto.transcript)
525
- this.setState('listening')
526
- break
527
- case 'message':
528
- if (typeof ev.data === 'string') {
505
+ case "open":
506
+ break;
507
+ case "reconnected":
508
+ this.proto.transcript = [];
509
+ this.proto.agentBubbleId = null;
510
+ this.args.options.onTranscript?.(this.proto.transcript);
511
+ this.setState("listening");
512
+ break;
513
+ case "message":
514
+ if (typeof ev.data === "string") {
529
515
  handleServerMessage(ev.data, this.proto, {
530
516
  onState: this.setState,
531
517
  onTranscript: (entries) => this.args.options.onTranscript?.(entries),
532
518
  onError: this.emitError,
533
519
  onInterrupt: () => this.args.options.onInterrupt?.(),
534
520
  onAgentTurnStart: (seq) => {
535
- if (typeof seq === 'number') this.marks.onAgentTurnStart(seq)
536
- this.args.options.onAgentTurnStart?.()
521
+ if (typeof seq === "number") this.marks.onAgentTurnStart(seq);
522
+ this.args.options.onAgentTurnStart?.();
537
523
  },
538
524
  onAgentTurnEnd: (seq) => {
539
- if (typeof seq === 'number') this.marks.onAgentTurnEnd(seq)
525
+ if (typeof seq === "number") this.marks.onAgentTurnEnd(seq);
540
526
  },
541
527
  onCallEnd: (reason) => this.teardown(reason),
542
528
  onConnected: () => this.sendClientToolsRegister(),
543
- onClientToolCall: (frame) =>
544
- dispatchClientToolCall(
545
- (f) => this.rws?.send(JSON.stringify(f)),
546
- this.args.options.clientTools ?? {},
547
- frame,
548
- ),
549
- })
529
+ onClientToolCall: (frame) => dispatchClientToolCall(
530
+ (f) => this.rws?.send(JSON.stringify(f)),
531
+ this.args.options.clientTools ?? {},
532
+ frame
533
+ )
534
+ });
550
535
  } else {
551
- this.marks.markFirstAudibleOutput()
552
- this.args.options.onAudioChunk?.(ev.data)
536
+ this.marks.markFirstAudibleOutput();
537
+ this.args.options.onAudioChunk?.(ev.data);
553
538
  }
554
- break
555
- case 'close':
539
+ break;
540
+ case "close":
556
541
  if (ev.permanent) {
557
- const reason = this.proto.endReason ?? (this.lastError ? 'error' : 'user_hangup')
558
- this.teardown(reason)
542
+ const reason = this.proto.endReason ?? (this.lastError ? "error" : "user_hangup");
543
+ this.teardown(reason);
559
544
  }
560
- break
561
- case 'error':
562
- this.emitError({ code: 'socket_error', message: ev.error.message })
563
- break
545
+ break;
546
+ case "error":
547
+ this.emitError({ code: "socket_error", message: ev.error.message });
548
+ break;
564
549
  }
565
- }
550
+ };
566
551
  this.teardown = (reason) => {
567
552
  try {
568
- this.marks.flush()
569
- } catch {}
553
+ this.marks.flush();
554
+ } catch {
555
+ }
570
556
  try {
571
- this.rws?.close(1e3, reason)
572
- } catch {}
573
- this.rws = null
574
- this.setState('ended')
575
- this.fireEndOnce(reason)
576
- }
557
+ this.rws?.close(1e3, reason);
558
+ } catch {
559
+ }
560
+ this.rws = null;
561
+ this.setState("ended");
562
+ this.fireEndOnce(reason);
563
+ };
577
564
  this.fireEndOnce = (reason) => {
578
- if (this.endedFired) return
579
- this.endedFired = true
580
- const startedAt = this.startedAt ?? Date.now()
565
+ if (this.endedFired) return;
566
+ this.endedFired = true;
567
+ const startedAt = this.startedAt ?? Date.now();
581
568
  this.args.options.onEnd?.({
582
569
  reason,
583
- errorCode: reason === 'error' ? this.lastError?.code : void 0,
584
- durationMs: Date.now() - startedAt,
585
- })
586
- }
587
- this.args = args
588
- this.proto = createProtocolState()
589
- validateClientToolMap(args.options.clientTools)
570
+ errorCode: reason === "error" ? this.lastError?.code : void 0,
571
+ durationMs: Date.now() - startedAt
572
+ });
573
+ };
574
+ this.args = args;
575
+ this.proto = createProtocolState();
576
+ validateClientToolMap(args.options.clientTools);
590
577
  this.marks = createClientMarksBuffer({
591
578
  send: (frame) => {
592
579
  try {
593
- this.rws?.send(JSON.stringify(frame))
594
- } catch {}
595
- },
596
- })
580
+ this.rws?.send(JSON.stringify(frame));
581
+ } catch {
582
+ }
583
+ }
584
+ });
597
585
  }
598
586
  // ---------------------------------------------------------------
599
587
  // Call interface
600
588
  // ---------------------------------------------------------------
601
589
  get state() {
602
- return this.proto.state
590
+ return this.proto.state;
603
591
  }
604
592
  get transcript() {
605
- return this.proto.transcript.slice()
593
+ return this.proto.transcript.slice();
606
594
  }
607
595
  get isMuted() {
608
- return this.muted
596
+ return this.muted;
609
597
  }
610
598
  // ---------------------------------------------------------------
611
599
  // Lifecycle
612
600
  // ---------------------------------------------------------------
613
601
  async start() {
614
- this.setState('connecting')
615
- this.startedAt = Date.now()
602
+ this.setState("connecting");
603
+ this.startedAt = Date.now();
616
604
  const url = buildWsUrl({
617
605
  apiBase: this.args.config.apiBase,
618
606
  agentId: this.args.options.agentId,
619
607
  token: this.args.token,
620
- bargeIn: this.args.options.bargeIn,
621
- })
608
+ bargeIn: this.args.options.bargeIn
609
+ });
622
610
  this.rws = createReconnectingWebSocket(
623
611
  {
624
612
  url,
625
613
  wsFactory: this.args.wsFactory,
626
- maxRetries: 3,
614
+ maxRetries: 3
627
615
  },
628
- (ev) => this.handleSocketEvent(ev),
629
- )
616
+ (ev) => this.handleSocketEvent(ev)
617
+ );
630
618
  }
631
- }
619
+ };
632
620
 
633
621
  // src/incomingCall.ts
634
622
  var parseIncomingCall = (raw) => {
635
- if (typeof raw !== 'object' || raw === null) {
636
- throw new Error('parseIncomingCall: payload must be an object')
637
- }
638
- const p = raw
639
- if (typeof p.token !== 'string' || !p.token.startsWith('ct_')) {
640
- throw new Error('parseIncomingCall: missing or invalid `token` (expected a ct_ string)')
641
- }
642
- if (typeof p.agentId !== 'string' || p.agentId.length === 0) {
643
- throw new Error('parseIncomingCall: missing `agentId`')
644
- }
645
- const transport = p.transport === 'webrtc' ? 'webrtc' : 'ws'
646
- const out = { token: p.token, agentId: p.agentId, transport }
647
- if (transport === 'webrtc' && typeof p.webrtcGatewayBase === 'string') {
648
- out.webrtcGatewayBase = p.webrtcGatewayBase
649
- }
650
- if (typeof p.expiresAt === 'number') out.expiresAt = p.expiresAt
651
- if (typeof p.agentName === 'string') out.agentName = p.agentName
652
- if (typeof p.agentAvatarUrl === 'string') out.agentAvatarUrl = p.agentAvatarUrl
653
- return out
654
- }
623
+ if (typeof raw !== "object" || raw === null) {
624
+ throw new Error("parseIncomingCall: payload must be an object");
625
+ }
626
+ const p = raw;
627
+ if (typeof p.token !== "string" || !p.token.startsWith("ct_")) {
628
+ throw new Error("parseIncomingCall: missing or invalid `token` (expected a ct_ string)");
629
+ }
630
+ if (typeof p.agentId !== "string" || p.agentId.length === 0) {
631
+ throw new Error("parseIncomingCall: missing `agentId`");
632
+ }
633
+ const transport = p.transport === "webrtc" ? "webrtc" : "ws";
634
+ const out = { token: p.token, agentId: p.agentId, transport };
635
+ if (transport === "webrtc" && typeof p.webrtcGatewayBase === "string") {
636
+ out.webrtcGatewayBase = p.webrtcGatewayBase;
637
+ }
638
+ if (typeof p.expiresAt === "number") out.expiresAt = p.expiresAt;
639
+ if (typeof p.agentName === "string") out.agentName = p.agentName;
640
+ if (typeof p.agentAvatarUrl === "string") out.agentAvatarUrl = p.agentAvatarUrl;
641
+ return out;
642
+ };
655
643
 
656
644
  // src/node.ts
657
- var cachedWsCtor = null
645
+ var cachedWsCtor = null;
658
646
  var loadWsCtor = async () => {
659
- if (cachedWsCtor) return cachedWsCtor
647
+ if (cachedWsCtor) return cachedWsCtor;
660
648
  try {
661
- const mod = await import('ws')
662
- const ctor = mod.WebSocket ?? mod.default
649
+ const mod = await import("ws");
650
+ const ctor = mod.WebSocket ?? mod.default;
663
651
  if (!ctor) {
664
- throw new Error('imported `ws` but neither default nor named WebSocket export was found')
652
+ throw new Error("imported `ws` but neither default nor named WebSocket export was found");
665
653
  }
666
- cachedWsCtor = ctor
667
- return ctor
654
+ cachedWsCtor = ctor;
655
+ return ctor;
668
656
  } catch (err) {
669
657
  throw new Error(
670
- "@craftedxp/voice-js (node): missing optional peer `ws`. Install it with `npm install ws` (ws is declared as `peerDependenciesMeta.optional` so npm doesn't install it automatically). Original: " +
671
- (err instanceof Error ? err.message : String(err)),
672
- )
658
+ "@craftedxp/voice-js (node): missing optional peer `ws`. Install it with `npm install ws` (ws is declared as `peerDependenciesMeta.optional` so npm doesn't install it automatically). Original: " + (err instanceof Error ? err.message : String(err))
659
+ );
673
660
  }
674
- }
661
+ };
675
662
  var NodeVoiceFactory = class {
676
663
  constructor(config) {
677
664
  this.startCall = async (options) => {
678
665
  if (!options.agentId) {
679
- throw new Error('startCall: agentId is required')
666
+ throw new Error("startCall: agentId is required");
680
667
  }
681
- const WsCtor = await loadWsCtor()
682
- const wsFactory = (url) => new WsCtor(url)
683
- const { context, metadata } = mergeStartCallContext(this.config, options)
668
+ const WsCtor = await loadWsCtor();
669
+ const wsFactory = (url) => new WsCtor(url);
670
+ const { context, metadata } = mergeStartCallContext(this.config, options);
684
671
  const fetchArgs = {
685
672
  agentId: options.agentId,
686
673
  userId: options.userId,
687
674
  context,
688
- metadata,
689
- }
690
- let token
675
+ metadata
676
+ };
677
+ let token;
691
678
  if (options.token) {
692
- token = options.token
679
+ token = options.token;
693
680
  } else {
694
- const r = await this.config.fetchToken(fetchArgs)
681
+ const r = await this.config.fetchToken(fetchArgs);
695
682
  if (!r) {
696
- throw new Error('configureVoiceClient.fetchToken returned empty token')
683
+ throw new Error("configureVoiceClient.fetchToken returned empty token");
697
684
  }
698
- token = typeof r === 'string' ? r : r.token
685
+ token = typeof r === "string" ? r : r.token;
699
686
  if (!token) {
700
- throw new Error('configureVoiceClient.fetchToken returned an object without `token`')
687
+ throw new Error("configureVoiceClient.fetchToken returned an object without `token`");
701
688
  }
702
- if (typeof r !== 'string' && r.transport === 'webrtc') {
689
+ if (typeof r !== "string" && r.transport === "webrtc") {
703
690
  console.warn(
704
- '@craftedxp/voice-js (node): agent is configured for WebRTC but the Node bundle only supports WebSocket \u2014 falling back to WS. Use the browser bundle for WebRTC transport.',
705
- )
691
+ "@craftedxp/voice-js (node): agent is configured for WebRTC but the Node bundle only supports WebSocket \u2014 falling back to WS. Use the browser bundle for WebRTC transport."
692
+ );
706
693
  }
707
694
  }
708
695
  const client = new NodeVoiceClient({
709
696
  config: this.config,
710
697
  options: { ...options, context, metadata },
711
698
  token,
712
- wsFactory,
713
- })
714
- await client.start()
715
- return client
716
- }
717
- this.config = config
718
- }
719
- }
699
+ wsFactory
700
+ });
701
+ await client.start();
702
+ return client;
703
+ };
704
+ this.config = config;
705
+ }
706
+ };
720
707
  function configureVoiceClient(config) {
721
- return new NodeVoiceFactory(normalizeConfig(config))
708
+ return new NodeVoiceFactory(normalizeConfig(config));
722
709
  }
723
710
  // Annotate the CommonJS export names for ESM import in node:
724
- 0 &&
725
- (module.exports = {
726
- buildWsUrl,
727
- configureVoiceClient,
728
- createProtocolState,
729
- createReconnectingWebSocket,
730
- handleServerMessage,
731
- parseIncomingCall,
732
- })
733
- //# sourceMappingURL=node.js.map
711
+ 0 && (module.exports = {
712
+ buildWsUrl,
713
+ configureVoiceClient,
714
+ createProtocolState,
715
+ createReconnectingWebSocket,
716
+ handleServerMessage,
717
+ parseIncomingCall
718
+ });
719
+ //# sourceMappingURL=node.js.map