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