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