@craftedxp/voice-js 0.4.2 → 0.5.4

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