@craftedxp/voice-js 0.3.2 → 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 -540
- package/dist/browser.js.map +1 -1
- package/dist/browser.mjs +278 -8
- 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 -368
- package/dist/node.js.map +1 -1
- package/dist/node.mjs +103 -5
- package/dist/node.mjs.map +1 -1
- package/package.json +1 -1
package/dist/node.js
CHANGED
|
@@ -1,591 +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
|
-
cb.onState(next)
|
|
271
|
-
}
|
|
285
|
+
if (state.state === next) return
|
|
286
|
+
cb.onState(next)
|
|
287
|
+
}
|
|
272
288
|
var upsertUserPartial = (state, text, isFinal) => {
|
|
273
|
-
let idx = -1
|
|
289
|
+
let idx = -1
|
|
274
290
|
for (let i = state.transcript.length - 1; i >= 0; i--) {
|
|
275
|
-
const e = state.transcript[i]
|
|
276
|
-
if (e.role ===
|
|
277
|
-
idx = i
|
|
278
|
-
break
|
|
291
|
+
const e = state.transcript[i]
|
|
292
|
+
if (e.role === 'user' && e.committed === false) {
|
|
293
|
+
idx = i
|
|
294
|
+
break
|
|
279
295
|
}
|
|
280
296
|
}
|
|
281
297
|
if (idx === -1) {
|
|
282
298
|
state.transcript = [
|
|
283
299
|
...state.transcript,
|
|
284
|
-
{ id: `m${state.idCounter++}`, role:
|
|
285
|
-
]
|
|
286
|
-
return
|
|
300
|
+
{ id: `m${state.idCounter++}`, role: 'user', text, committed: isFinal },
|
|
301
|
+
]
|
|
302
|
+
return
|
|
287
303
|
}
|
|
288
|
-
const target = state.transcript[idx]
|
|
289
|
-
const next = [...state.transcript]
|
|
290
|
-
next[idx] = { ...target, text, committed: isFinal }
|
|
291
|
-
state.transcript = next
|
|
292
|
-
}
|
|
304
|
+
const target = state.transcript[idx]
|
|
305
|
+
const next = [...state.transcript]
|
|
306
|
+
next[idx] = { ...target, text, committed: isFinal }
|
|
307
|
+
state.transcript = next
|
|
308
|
+
}
|
|
293
309
|
function buildWsUrl(args) {
|
|
294
|
-
const base = new URL(args.apiBase)
|
|
295
|
-
const proto = base.protocol ===
|
|
296
|
-
const bargeQS = args.bargeIn === false ?
|
|
297
|
-
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}`
|
|
298
314
|
}
|
|
299
315
|
|
|
300
316
|
// src/clientTools.ts
|
|
301
|
-
var NAME_RE = /^[a-zA-Z_][a-zA-Z0-9_]
|
|
302
|
-
var MAX_TOOLS = 64
|
|
303
|
-
var MAX_USAGE = 500
|
|
304
|
-
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
|
|
305
321
|
var validateClientToolMap = (tools) => {
|
|
306
|
-
if (tools === void 0) return
|
|
307
|
-
if (typeof tools !==
|
|
308
|
-
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')
|
|
309
325
|
}
|
|
310
|
-
const entries = Object.entries(tools)
|
|
326
|
+
const entries = Object.entries(tools)
|
|
311
327
|
if (entries.length > MAX_TOOLS) {
|
|
312
|
-
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})`)
|
|
313
329
|
}
|
|
314
330
|
for (const [name, def] of entries) {
|
|
315
331
|
if (!NAME_RE.test(name)) {
|
|
316
332
|
throw new Error(
|
|
317
|
-
`clientTools["${name}"]: name must be a valid identifier (^[a-zA-Z_][a-zA-Z0-9_]*$)
|
|
318
|
-
)
|
|
333
|
+
`clientTools["${name}"]: name must be a valid identifier (^[a-zA-Z_][a-zA-Z0-9_]*$)`,
|
|
334
|
+
)
|
|
319
335
|
}
|
|
320
|
-
if (!def || typeof def !==
|
|
321
|
-
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`)
|
|
322
338
|
}
|
|
323
|
-
if (typeof def.description !==
|
|
324
|
-
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`)
|
|
325
341
|
}
|
|
326
|
-
if (typeof def.handler !==
|
|
327
|
-
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`)
|
|
328
344
|
}
|
|
329
345
|
if (def.usage !== void 0 && def.usage.length > MAX_USAGE) {
|
|
330
|
-
throw new Error(`clientTools["${name}"]: usage must be \u2264500 chars`)
|
|
346
|
+
throw new Error(`clientTools["${name}"]: usage must be \u2264500 chars`)
|
|
331
347
|
}
|
|
332
|
-
if (
|
|
333
|
-
|
|
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]`)
|
|
334
353
|
}
|
|
335
354
|
}
|
|
336
|
-
}
|
|
355
|
+
}
|
|
337
356
|
var buildRegisterFrame = (tools) => ({
|
|
338
|
-
type:
|
|
357
|
+
type: 'client_tools_register',
|
|
339
358
|
tools: Object.entries(tools).map(([name, def]) => ({
|
|
340
359
|
name,
|
|
341
360
|
description: def.description,
|
|
342
361
|
parameters: def.parameters,
|
|
343
|
-
...def.usage !== void 0 ? { usage: def.usage } : {},
|
|
344
|
-
...def.timeoutMs !== void 0 ? { timeoutMs: def.timeoutMs } : {}
|
|
345
|
-
}))
|
|
346
|
-
})
|
|
362
|
+
...(def.usage !== void 0 ? { usage: def.usage } : {}),
|
|
363
|
+
...(def.timeoutMs !== void 0 ? { timeoutMs: def.timeoutMs } : {}),
|
|
364
|
+
})),
|
|
365
|
+
})
|
|
347
366
|
var dispatchClientToolCall = (send, tools, frame) => {
|
|
348
367
|
const safeSend = (payload) => {
|
|
349
368
|
try {
|
|
350
|
-
send(payload)
|
|
351
|
-
} catch {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
const tool = tools[frame.name];
|
|
369
|
+
send(payload)
|
|
370
|
+
} catch {}
|
|
371
|
+
}
|
|
372
|
+
const tool = tools[frame.name]
|
|
355
373
|
if (!tool) {
|
|
356
374
|
safeSend({
|
|
357
|
-
type:
|
|
375
|
+
type: 'client_tool_result',
|
|
358
376
|
toolCallId: frame.toolCallId,
|
|
359
|
-
error: `No handler for ${frame.name}
|
|
360
|
-
})
|
|
361
|
-
return
|
|
377
|
+
error: `No handler for ${frame.name}`,
|
|
378
|
+
})
|
|
379
|
+
return
|
|
362
380
|
}
|
|
363
381
|
void (async () => {
|
|
364
382
|
try {
|
|
365
|
-
const out = await tool.handler(frame.args)
|
|
383
|
+
const out = await tool.handler(frame.args)
|
|
366
384
|
safeSend({
|
|
367
|
-
type:
|
|
385
|
+
type: 'client_tool_result',
|
|
368
386
|
toolCallId: frame.toolCallId,
|
|
369
|
-
result: typeof out ===
|
|
370
|
-
})
|
|
387
|
+
result: typeof out === 'string' ? out : JSON.stringify(out),
|
|
388
|
+
})
|
|
371
389
|
} catch (err) {
|
|
372
390
|
safeSend({
|
|
373
|
-
type:
|
|
391
|
+
type: 'client_tool_result',
|
|
374
392
|
toolCallId: frame.toolCallId,
|
|
375
|
-
error: err instanceof Error ? err.message : String(err)
|
|
376
|
-
})
|
|
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)
|
|
377
457
|
}
|
|
378
|
-
|
|
379
|
-
}
|
|
458
|
+
pendingFirstOutboundAt = null
|
|
459
|
+
}
|
|
460
|
+
return {
|
|
461
|
+
markFirstOutboundAudio,
|
|
462
|
+
markFirstAudibleOutput,
|
|
463
|
+
onAgentTurnStart,
|
|
464
|
+
onAgentTurnEnd,
|
|
465
|
+
flush,
|
|
466
|
+
}
|
|
467
|
+
}
|
|
380
468
|
|
|
381
469
|
// src/NodeVoiceClient.ts
|
|
382
470
|
var NodeVoiceClient = class {
|
|
383
471
|
constructor(args) {
|
|
384
|
-
this.rws = null
|
|
385
|
-
this.muted = false
|
|
386
|
-
this.startedAt = null
|
|
387
|
-
this.endedFired = false
|
|
388
|
-
this.lastError = null
|
|
472
|
+
this.rws = null
|
|
473
|
+
this.muted = false
|
|
474
|
+
this.startedAt = null
|
|
475
|
+
this.endedFired = false
|
|
476
|
+
this.lastError = null
|
|
389
477
|
this.end = () => {
|
|
390
|
-
this.teardown(
|
|
391
|
-
}
|
|
478
|
+
this.teardown('user_hangup')
|
|
479
|
+
}
|
|
392
480
|
this.mute = () => {
|
|
393
|
-
this.muted = true
|
|
394
|
-
}
|
|
481
|
+
this.muted = true
|
|
482
|
+
}
|
|
395
483
|
this.unmute = () => {
|
|
396
|
-
this.muted = false
|
|
397
|
-
}
|
|
484
|
+
this.muted = false
|
|
485
|
+
}
|
|
398
486
|
// ---------------------------------------------------------------
|
|
399
487
|
// Node-only raw audio surface
|
|
400
488
|
// ---------------------------------------------------------------
|
|
401
489
|
this.sendAudioChunk = (pcm) => {
|
|
402
|
-
if (!this.rws) return false
|
|
490
|
+
if (!this.rws) return false
|
|
491
|
+
this.marks.markFirstOutboundAudio()
|
|
403
492
|
if (this.muted) {
|
|
404
|
-
const len = ArrayBuffer.isView(pcm) ? pcm.byteLength : pcm.byteLength
|
|
405
|
-
this.rws.send(new ArrayBuffer(len))
|
|
406
|
-
return true
|
|
493
|
+
const len = ArrayBuffer.isView(pcm) ? pcm.byteLength : pcm.byteLength
|
|
494
|
+
this.rws.send(new ArrayBuffer(len))
|
|
495
|
+
return true
|
|
407
496
|
}
|
|
408
|
-
this.rws.send(pcm)
|
|
409
|
-
return true
|
|
410
|
-
}
|
|
497
|
+
this.rws.send(pcm)
|
|
498
|
+
return true
|
|
499
|
+
}
|
|
411
500
|
// ---------------------------------------------------------------
|
|
412
501
|
// Internal
|
|
413
502
|
// ---------------------------------------------------------------
|
|
414
503
|
this.setState = (next) => {
|
|
415
|
-
if (this.proto.state === next) return
|
|
416
|
-
this.proto.state = next
|
|
417
|
-
this.args.options.onStateChange?.(next)
|
|
418
|
-
}
|
|
504
|
+
if (this.proto.state === next) return
|
|
505
|
+
this.proto.state = next
|
|
506
|
+
this.args.options.onStateChange?.(next)
|
|
507
|
+
}
|
|
419
508
|
this.sendClientToolsRegister = () => {
|
|
420
|
-
const frame = buildRegisterFrame(this.args.options.clientTools ?? {})
|
|
421
|
-
this.rws?.send(JSON.stringify(frame))
|
|
422
|
-
}
|
|
509
|
+
const frame = buildRegisterFrame(this.args.options.clientTools ?? {})
|
|
510
|
+
this.rws?.send(JSON.stringify(frame))
|
|
511
|
+
}
|
|
423
512
|
this.emitError = (err) => {
|
|
424
|
-
this.lastError = err
|
|
425
|
-
this.args.options.onError?.(err)
|
|
426
|
-
}
|
|
513
|
+
this.lastError = err
|
|
514
|
+
this.args.options.onError?.(err)
|
|
515
|
+
}
|
|
427
516
|
this.handleSocketEvent = (ev) => {
|
|
428
517
|
switch (ev.type) {
|
|
429
|
-
case
|
|
430
|
-
break
|
|
431
|
-
case
|
|
432
|
-
this.proto.transcript = []
|
|
433
|
-
this.proto.agentBubbleId = null
|
|
434
|
-
this.args.options.onTranscript?.(this.proto.transcript)
|
|
435
|
-
this.setState(
|
|
436
|
-
break
|
|
437
|
-
case
|
|
438
|
-
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') {
|
|
439
528
|
handleServerMessage(ev.data, this.proto, {
|
|
440
529
|
onState: this.setState,
|
|
441
530
|
onTranscript: (entries) => this.args.options.onTranscript?.(entries),
|
|
442
531
|
onError: this.emitError,
|
|
443
532
|
onInterrupt: () => this.args.options.onInterrupt?.(),
|
|
444
|
-
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
|
+
},
|
|
445
540
|
onCallEnd: (reason) => this.teardown(reason),
|
|
446
541
|
onConnected: () => this.sendClientToolsRegister(),
|
|
447
|
-
onClientToolCall: (frame) =>
|
|
448
|
-
(
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
542
|
+
onClientToolCall: (frame) =>
|
|
543
|
+
dispatchClientToolCall(
|
|
544
|
+
(f) => this.rws?.send(JSON.stringify(f)),
|
|
545
|
+
this.args.options.clientTools ?? {},
|
|
546
|
+
frame,
|
|
547
|
+
),
|
|
548
|
+
})
|
|
453
549
|
} else {
|
|
454
|
-
this.
|
|
550
|
+
this.marks.markFirstAudibleOutput()
|
|
551
|
+
this.args.options.onAudioChunk?.(ev.data)
|
|
455
552
|
}
|
|
456
|
-
break
|
|
457
|
-
case
|
|
553
|
+
break
|
|
554
|
+
case 'close':
|
|
458
555
|
if (ev.permanent) {
|
|
459
|
-
const reason = this.proto.endReason ?? (this.lastError ?
|
|
460
|
-
this.teardown(reason)
|
|
556
|
+
const reason = this.proto.endReason ?? (this.lastError ? 'error' : 'user_hangup')
|
|
557
|
+
this.teardown(reason)
|
|
461
558
|
}
|
|
462
|
-
break
|
|
463
|
-
case
|
|
464
|
-
this.emitError({ code:
|
|
465
|
-
break
|
|
559
|
+
break
|
|
560
|
+
case 'error':
|
|
561
|
+
this.emitError({ code: 'socket_error', message: ev.error.message })
|
|
562
|
+
break
|
|
466
563
|
}
|
|
467
|
-
}
|
|
564
|
+
}
|
|
468
565
|
this.teardown = (reason) => {
|
|
469
566
|
try {
|
|
470
|
-
this.
|
|
471
|
-
} catch {
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
this.
|
|
476
|
-
|
|
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
|
+
}
|
|
477
576
|
this.fireEndOnce = (reason) => {
|
|
478
|
-
if (this.endedFired) return
|
|
479
|
-
this.endedFired = true
|
|
480
|
-
const startedAt = this.startedAt ?? Date.now()
|
|
577
|
+
if (this.endedFired) return
|
|
578
|
+
this.endedFired = true
|
|
579
|
+
const startedAt = this.startedAt ?? Date.now()
|
|
481
580
|
this.args.options.onEnd?.({
|
|
482
581
|
reason,
|
|
483
|
-
errorCode: reason ===
|
|
484
|
-
durationMs: Date.now() - startedAt
|
|
485
|
-
})
|
|
486
|
-
}
|
|
487
|
-
this.args = args
|
|
488
|
-
this.proto = createProtocolState()
|
|
489
|
-
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
|
+
})
|
|
490
596
|
}
|
|
491
597
|
// ---------------------------------------------------------------
|
|
492
598
|
// Call interface
|
|
493
599
|
// ---------------------------------------------------------------
|
|
494
600
|
get state() {
|
|
495
|
-
return this.proto.state
|
|
601
|
+
return this.proto.state
|
|
496
602
|
}
|
|
497
603
|
get transcript() {
|
|
498
|
-
return this.proto.transcript.slice()
|
|
604
|
+
return this.proto.transcript.slice()
|
|
499
605
|
}
|
|
500
606
|
get isMuted() {
|
|
501
|
-
return this.muted
|
|
607
|
+
return this.muted
|
|
502
608
|
}
|
|
503
609
|
// ---------------------------------------------------------------
|
|
504
610
|
// Lifecycle
|
|
505
611
|
// ---------------------------------------------------------------
|
|
506
612
|
async start() {
|
|
507
|
-
this.setState(
|
|
508
|
-
this.startedAt = Date.now()
|
|
613
|
+
this.setState('connecting')
|
|
614
|
+
this.startedAt = Date.now()
|
|
509
615
|
const url = buildWsUrl({
|
|
510
616
|
apiBase: this.args.config.apiBase,
|
|
511
617
|
agentId: this.args.options.agentId,
|
|
512
618
|
token: this.args.token,
|
|
513
|
-
bargeIn: this.args.options.bargeIn
|
|
514
|
-
})
|
|
619
|
+
bargeIn: this.args.options.bargeIn,
|
|
620
|
+
})
|
|
515
621
|
this.rws = createReconnectingWebSocket(
|
|
516
622
|
{
|
|
517
623
|
url,
|
|
518
624
|
wsFactory: this.args.wsFactory,
|
|
519
|
-
maxRetries: 3
|
|
625
|
+
maxRetries: 3,
|
|
520
626
|
},
|
|
521
|
-
(ev) => this.handleSocketEvent(ev)
|
|
522
|
-
)
|
|
627
|
+
(ev) => this.handleSocketEvent(ev),
|
|
628
|
+
)
|
|
523
629
|
}
|
|
524
|
-
}
|
|
630
|
+
}
|
|
525
631
|
|
|
526
632
|
// src/node.ts
|
|
527
|
-
var cachedWsCtor = null
|
|
633
|
+
var cachedWsCtor = null
|
|
528
634
|
var loadWsCtor = async () => {
|
|
529
|
-
if (cachedWsCtor) return cachedWsCtor
|
|
635
|
+
if (cachedWsCtor) return cachedWsCtor
|
|
530
636
|
try {
|
|
531
|
-
const mod = await import(
|
|
532
|
-
const ctor = mod.WebSocket ?? mod.default
|
|
637
|
+
const mod = await import('ws')
|
|
638
|
+
const ctor = mod.WebSocket ?? mod.default
|
|
533
639
|
if (!ctor) {
|
|
534
|
-
throw new Error(
|
|
640
|
+
throw new Error('imported `ws` but neither default nor named WebSocket export was found')
|
|
535
641
|
}
|
|
536
|
-
cachedWsCtor = ctor
|
|
537
|
-
return ctor
|
|
642
|
+
cachedWsCtor = ctor
|
|
643
|
+
return ctor
|
|
538
644
|
} catch (err) {
|
|
539
645
|
throw new Error(
|
|
540
|
-
"@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: " +
|
|
541
|
-
|
|
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
|
+
)
|
|
542
649
|
}
|
|
543
|
-
}
|
|
650
|
+
}
|
|
544
651
|
var NodeVoiceFactory = class {
|
|
545
652
|
constructor(config) {
|
|
546
653
|
this.startCall = async (options) => {
|
|
547
654
|
if (!options.agentId) {
|
|
548
|
-
throw new Error(
|
|
655
|
+
throw new Error('startCall: agentId is required')
|
|
549
656
|
}
|
|
550
|
-
const WsCtor = await loadWsCtor()
|
|
551
|
-
const wsFactory = (url) => new WsCtor(url)
|
|
552
|
-
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)
|
|
553
660
|
const fetchArgs = {
|
|
554
661
|
agentId: options.agentId,
|
|
555
662
|
userId: options.userId,
|
|
556
663
|
context,
|
|
557
|
-
metadata
|
|
558
|
-
}
|
|
559
|
-
let token
|
|
664
|
+
metadata,
|
|
665
|
+
}
|
|
666
|
+
let token
|
|
560
667
|
if (options.token) {
|
|
561
|
-
token = options.token
|
|
668
|
+
token = options.token
|
|
562
669
|
} else {
|
|
563
|
-
|
|
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
|
|
564
675
|
if (!token) {
|
|
565
|
-
throw new Error(
|
|
676
|
+
throw new Error('configureVoiceClient.fetchToken returned an object without `token`')
|
|
566
677
|
}
|
|
567
678
|
}
|
|
568
679
|
const client = new NodeVoiceClient({
|
|
569
680
|
config: this.config,
|
|
570
681
|
options: { ...options, context, metadata },
|
|
571
682
|
token,
|
|
572
|
-
wsFactory
|
|
573
|
-
})
|
|
574
|
-
await client.start()
|
|
575
|
-
return client
|
|
576
|
-
}
|
|
577
|
-
this.config = config
|
|
683
|
+
wsFactory,
|
|
684
|
+
})
|
|
685
|
+
await client.start()
|
|
686
|
+
return client
|
|
687
|
+
}
|
|
688
|
+
this.config = config
|
|
578
689
|
}
|
|
579
|
-
}
|
|
690
|
+
}
|
|
580
691
|
function configureVoiceClient(config) {
|
|
581
|
-
return new NodeVoiceFactory(normalizeConfig(config))
|
|
692
|
+
return new NodeVoiceFactory(normalizeConfig(config))
|
|
582
693
|
}
|
|
583
694
|
// Annotate the CommonJS export names for ESM import in node:
|
|
584
|
-
0 &&
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
695
|
+
0 &&
|
|
696
|
+
(module.exports = {
|
|
697
|
+
buildWsUrl,
|
|
698
|
+
configureVoiceClient,
|
|
699
|
+
createProtocolState,
|
|
700
|
+
createReconnectingWebSocket,
|
|
701
|
+
handleServerMessage,
|
|
702
|
+
})
|
|
703
|
+
//# sourceMappingURL=node.js.map
|