@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/CONSUMING.md +10 -2
- package/README.md +69 -11
- package/dist/browser.d.mts +178 -1
- package/dist/browser.d.ts +535 -259
- package/dist/browser.js +924 -710
- package/dist/browser.js.map +1 -1
- package/dist/browser.mjs +209 -1
- package/dist/browser.mjs.map +1 -1
- package/dist/embed.iife.js +23373 -42
- package/dist/node.d.mts +177 -1
- package/dist/node.d.ts +516 -256
- package/dist/node.js +471 -432
- package/dist/node.js.map +1 -1
- package/dist/node.mjs +25 -1
- package/dist/node.mjs.map +1 -1
- package/package.json +4 -1
package/dist/node.js
CHANGED
|
@@ -1,694 +1,733 @@
|
|
|
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, {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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(
|
|
44
|
-
if (
|
|
51
|
+
if (!config) throw new Error('configureVoiceClient: config is required')
|
|
52
|
+
if ('apiKey' in config) {
|
|
45
53
|
throw new Error(
|
|
46
|
-
|
|
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(
|
|
58
|
+
throw new Error('configureVoiceClient: apiBase is required')
|
|
51
59
|
}
|
|
52
|
-
if (typeof config.fetchToken !==
|
|
53
|
-
throw new Error(
|
|
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 =
|
|
62
|
-
|
|
63
|
-
|
|
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 =
|
|
93
|
+
ws = options.wsFactory(options.url)
|
|
94
|
+
ws.binaryType = 'arraybuffer'
|
|
81
95
|
ws.onopen = () => {
|
|
82
|
-
if (retries === 0) onEvent({ type:
|
|
83
|
-
else onEvent({ type:
|
|
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:
|
|
89
|
-
}
|
|
102
|
+
onEvent({ type: 'message', data: ev.data })
|
|
103
|
+
}
|
|
90
104
|
ws.onerror = () => {
|
|
91
|
-
onEvent({ type:
|
|
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:
|
|
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:
|
|
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 =
|
|
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:
|
|
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 ===
|
|
147
|
-
if (raw ===
|
|
148
|
-
if (raw ===
|
|
149
|
-
return
|
|
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
|
|
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
|
-
const seq = typeof msg.seq ===
|
|
178
|
-
cb.onAgentTurnStart(seq)
|
|
179
|
-
setState(state,
|
|
180
|
-
return
|
|
181
|
-
}
|
|
182
|
-
case
|
|
183
|
-
const delta = msg.text ??
|
|
184
|
-
if (!delta || !state.agentBubbleId) return
|
|
185
|
-
const id = state.agentBubbleId
|
|
186
|
-
state.transcript = state.transcript.map(
|
|
187
|
-
|
|
188
|
-
)
|
|
189
|
-
cb.onTranscript(state.transcript)
|
|
190
|
-
return
|
|
191
|
-
}
|
|
192
|
-
case
|
|
193
|
-
state.agentBubbleId = null
|
|
194
|
-
const seq = typeof msg.seq ===
|
|
195
|
-
cb.onAgentTurnEnd(seq)
|
|
196
|
-
setState(state,
|
|
197
|
-
return
|
|
198
|
-
}
|
|
199
|
-
case
|
|
200
|
-
cb.onInterrupt()
|
|
201
|
-
return
|
|
202
|
-
case
|
|
203
|
-
const committed = (msg.committedText ??
|
|
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
|
-
|
|
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
|
|
231
|
+
case 'tool_call':
|
|
219
232
|
state.transcript = [
|
|
220
233
|
...state.transcript,
|
|
221
234
|
{
|
|
222
235
|
id: `m${state.idCounter++}`,
|
|
223
|
-
role:
|
|
224
|
-
text: `\u2192 ${String(msg.tool ??
|
|
225
|
-
}
|
|
226
|
-
]
|
|
227
|
-
cb.onTranscript(state.transcript)
|
|
228
|
-
return
|
|
229
|
-
case
|
|
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:
|
|
235
|
-
text: `${msg.ok ?
|
|
236
|
-
}
|
|
237
|
-
]
|
|
238
|
-
cb.onTranscript(state.transcript)
|
|
239
|
-
return
|
|
240
|
-
case
|
|
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
|
|
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:
|
|
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
|
|
265
|
-
const code = msg.code ??
|
|
266
|
-
const message = msg.message ??
|
|
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 ===
|
|
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:
|
|
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 ===
|
|
300
|
-
const bargeQS = args.bargeIn === false ?
|
|
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 !==
|
|
312
|
-
throw new Error(
|
|
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 !==
|
|
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 !==
|
|
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 !==
|
|
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 (
|
|
337
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
386
|
+
type: 'client_tool_result',
|
|
372
387
|
toolCallId: frame.toolCallId,
|
|
373
|
-
result: typeof out ===
|
|
374
|
-
})
|
|
388
|
+
result: typeof out === 'string' ? out : JSON.stringify(out),
|
|
389
|
+
})
|
|
375
390
|
} catch (err) {
|
|
376
391
|
safeSend({
|
|
377
|
-
type:
|
|
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:
|
|
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:
|
|
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(
|
|
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
|
|
505
|
-
break
|
|
506
|
-
case
|
|
507
|
-
this.proto.transcript = []
|
|
508
|
-
this.proto.agentBubbleId = null
|
|
509
|
-
this.args.options.onTranscript?.(this.proto.transcript)
|
|
510
|
-
this.setState(
|
|
511
|
-
break
|
|
512
|
-
case
|
|
513
|
-
if (typeof ev.data ===
|
|
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 ===
|
|
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 ===
|
|
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) =>
|
|
529
|
-
(
|
|
530
|
-
|
|
531
|
-
|
|
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
|
|
554
|
+
break
|
|
555
|
+
case 'close':
|
|
540
556
|
if (ev.permanent) {
|
|
541
|
-
const reason = this.proto.endReason ?? (this.lastError ?
|
|
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
|
|
546
|
-
this.emitError({ code:
|
|
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.
|
|
560
|
-
this.
|
|
561
|
-
|
|
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 ===
|
|
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(
|
|
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(
|
|
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(
|
|
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: " +
|
|
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(
|
|
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(
|
|
696
|
+
throw new Error('configureVoiceClient.fetchToken returned empty token')
|
|
660
697
|
}
|
|
661
|
-
token = typeof r ===
|
|
698
|
+
token = typeof r === 'string' ? r : r.token
|
|
662
699
|
if (!token) {
|
|
663
|
-
throw new Error(
|
|
700
|
+
throw new Error('configureVoiceClient.fetchToken returned an object without `token`')
|
|
664
701
|
}
|
|
665
|
-
if (typeof r !==
|
|
702
|
+
if (typeof r !== 'string' && r.transport === 'webrtc') {
|
|
666
703
|
console.warn(
|
|
667
|
-
|
|
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 &&
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
724
|
+
0 &&
|
|
725
|
+
(module.exports = {
|
|
726
|
+
buildWsUrl,
|
|
727
|
+
configureVoiceClient,
|
|
728
|
+
createProtocolState,
|
|
729
|
+
createReconnectingWebSocket,
|
|
730
|
+
handleServerMessage,
|
|
731
|
+
parseIncomingCall,
|
|
732
|
+
})
|
|
733
|
+
//# sourceMappingURL=node.js.map
|