@aihumanity/voice-sdk 0.1.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/LICENSE +21 -0
- package/README.md +486 -0
- package/dist/VoiceCall-_BBARIQT.d.ts +276 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +485 -0
- package/dist/index.js.map +1 -0
- package/dist/react.d.ts +60 -0
- package/dist/react.js +572 -0
- package/dist/react.js.map +1 -0
- package/dist/widget.d.ts +68 -0
- package/dist/widget.js +781 -0
- package/dist/widget.js.map +1 -0
- package/package.json +87 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import { UltravoxSession, UltravoxSessionStatus } from 'ultravox-client';
|
|
2
|
+
|
|
3
|
+
// src/VoiceCall.ts
|
|
4
|
+
|
|
5
|
+
// src/client.ts
|
|
6
|
+
async function fetchJoinUrl(opts) {
|
|
7
|
+
if (opts.fetchJoinUrl) {
|
|
8
|
+
return opts.fetchJoinUrl();
|
|
9
|
+
}
|
|
10
|
+
if (!opts.apiUrl) {
|
|
11
|
+
throw new Error("[aihumanity/voice-sdk] apiUrl is required when fetchJoinUrl is not provided.");
|
|
12
|
+
}
|
|
13
|
+
const baseUrl = opts.apiUrl.replace(/\/+$/, "");
|
|
14
|
+
const body = {
|
|
15
|
+
username: opts.username,
|
|
16
|
+
agentName: opts.agentName,
|
|
17
|
+
...opts.extraJoinUrlBody ?? {}
|
|
18
|
+
};
|
|
19
|
+
if (opts.dataConnection) {
|
|
20
|
+
body.dataConnection = {
|
|
21
|
+
websocketUrl: opts.dataConnection.websocketUrl,
|
|
22
|
+
audioConfig: {
|
|
23
|
+
sampleRate: opts.dataConnection.audioConfig?.sampleRate ?? 16e3,
|
|
24
|
+
channelMode: opts.dataConnection.audioConfig?.channelMode ?? "CHANNEL_MODE_SEPARATED"
|
|
25
|
+
},
|
|
26
|
+
// Spread user-supplied flags first, then apply defaults for the two
|
|
27
|
+
// most common flags so they're always present if not explicitly set.
|
|
28
|
+
dataMessages: {
|
|
29
|
+
...opts.dataConnection.dataMessages ?? {},
|
|
30
|
+
userStartedSpeaking: opts.dataConnection.dataMessages?.userStartedSpeaking ?? true,
|
|
31
|
+
userStoppedSpeaking: opts.dataConnection.dataMessages?.userStoppedSpeaking ?? true
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
if (opts.publicKey) {
|
|
36
|
+
const url2 = baseUrl + (opts.joinUrlPath ?? "/v1/voice/joinurl");
|
|
37
|
+
const res2 = await fetch(url2, {
|
|
38
|
+
method: "POST",
|
|
39
|
+
headers: {
|
|
40
|
+
"Content-Type": "application/json",
|
|
41
|
+
"X-Public-Key": opts.publicKey
|
|
42
|
+
},
|
|
43
|
+
body: JSON.stringify(body)
|
|
44
|
+
});
|
|
45
|
+
return parseJoinUrlResponse(res2);
|
|
46
|
+
}
|
|
47
|
+
const token = typeof opts.authToken === "function" ? await opts.authToken() : opts.authToken;
|
|
48
|
+
if (!token) {
|
|
49
|
+
throw new Error(
|
|
50
|
+
"[aihumanity/voice-sdk] Provide publicKey (for browser-direct) or authToken (for server-side)."
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
const path = opts.joinUrlPath ?? "/ultravox/secure/joinurl";
|
|
54
|
+
const url = baseUrl + path;
|
|
55
|
+
const res = await fetch(url, {
|
|
56
|
+
method: "POST",
|
|
57
|
+
headers: {
|
|
58
|
+
"Content-Type": "application/json",
|
|
59
|
+
Authorization: `Bearer ${token}`
|
|
60
|
+
},
|
|
61
|
+
body: JSON.stringify(body)
|
|
62
|
+
});
|
|
63
|
+
return parseJoinUrlResponse(res);
|
|
64
|
+
}
|
|
65
|
+
async function parseJoinUrlResponse(res) {
|
|
66
|
+
const text = await res.text();
|
|
67
|
+
if (!res.ok) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`[aihumanity/voice-sdk] joinUrl request failed (${res.status}): ${text}`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
let parsed;
|
|
73
|
+
try {
|
|
74
|
+
parsed = JSON.parse(text);
|
|
75
|
+
} catch {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`[aihumanity/voice-sdk] joinUrl response was not valid JSON: ${text}`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
if (!parsed.joinUrl) {
|
|
81
|
+
throw new Error(
|
|
82
|
+
"[aihumanity/voice-sdk] joinUrl response did not include `joinUrl`."
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return parsed;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// src/emitter.ts
|
|
89
|
+
var TypedEmitter = class {
|
|
90
|
+
constructor() {
|
|
91
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
92
|
+
}
|
|
93
|
+
on(event, listener) {
|
|
94
|
+
let set = this.listeners.get(event);
|
|
95
|
+
if (!set) {
|
|
96
|
+
set = /* @__PURE__ */ new Set();
|
|
97
|
+
this.listeners.set(event, set);
|
|
98
|
+
}
|
|
99
|
+
set.add(listener);
|
|
100
|
+
return () => this.off(event, listener);
|
|
101
|
+
}
|
|
102
|
+
off(event, listener) {
|
|
103
|
+
this.listeners.get(event)?.delete(listener);
|
|
104
|
+
}
|
|
105
|
+
once(event, listener) {
|
|
106
|
+
const off = this.on(event, ((payload) => {
|
|
107
|
+
off();
|
|
108
|
+
listener(payload);
|
|
109
|
+
}));
|
|
110
|
+
return off;
|
|
111
|
+
}
|
|
112
|
+
emit(event, payload) {
|
|
113
|
+
const set = this.listeners.get(event);
|
|
114
|
+
if (!set) return;
|
|
115
|
+
for (const fn of Array.from(set)) {
|
|
116
|
+
try {
|
|
117
|
+
fn(payload);
|
|
118
|
+
} catch (err) {
|
|
119
|
+
console.error("[aihumanity/voice-sdk] listener error:", err);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
removeAllListeners() {
|
|
124
|
+
this.listeners.clear();
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// src/types.ts
|
|
129
|
+
var CallStatus = /* @__PURE__ */ ((CallStatus2) => {
|
|
130
|
+
CallStatus2["IDLE"] = "idle";
|
|
131
|
+
CallStatus2["CONNECTING"] = "connecting";
|
|
132
|
+
CallStatus2["CONNECTED"] = "connected";
|
|
133
|
+
CallStatus2["LISTENING"] = "listening";
|
|
134
|
+
CallStatus2["THINKING"] = "thinking";
|
|
135
|
+
CallStatus2["SPEAKING"] = "speaking";
|
|
136
|
+
CallStatus2["DISCONNECTING"] = "disconnecting";
|
|
137
|
+
CallStatus2["DISCONNECTED"] = "disconnected";
|
|
138
|
+
return CallStatus2;
|
|
139
|
+
})(CallStatus || {});
|
|
140
|
+
var Speaker = /* @__PURE__ */ ((Speaker2) => {
|
|
141
|
+
Speaker2["USER"] = "user";
|
|
142
|
+
Speaker2["AGENT"] = "agent";
|
|
143
|
+
return Speaker2;
|
|
144
|
+
})(Speaker || {});
|
|
145
|
+
|
|
146
|
+
// src/VoiceCall.ts
|
|
147
|
+
var DEFAULT_EMOTION_PATTERN = /\[EMOTION_CONTEXT\][^:]*:\s*(\w+)/i;
|
|
148
|
+
var CONTACT_SAVED_PATTERN = /I['']ve (noted|saved|got|recorded) your (contact|info|details|number|email)/i;
|
|
149
|
+
var LIVE_STATUSES = /* @__PURE__ */ new Set([
|
|
150
|
+
"connected" /* CONNECTED */,
|
|
151
|
+
"listening" /* LISTENING */,
|
|
152
|
+
"thinking" /* THINKING */,
|
|
153
|
+
"speaking" /* SPEAKING */
|
|
154
|
+
]);
|
|
155
|
+
var VoiceCall = class {
|
|
156
|
+
constructor(opts) {
|
|
157
|
+
this.emitter = new TypedEmitter();
|
|
158
|
+
this.session = null;
|
|
159
|
+
this._status = "idle" /* IDLE */;
|
|
160
|
+
this._callId = null;
|
|
161
|
+
this._sessionToken = null;
|
|
162
|
+
this._transcripts = [];
|
|
163
|
+
this._lastEmotion = null;
|
|
164
|
+
this._contactSaved = false;
|
|
165
|
+
this._starting = false;
|
|
166
|
+
this._emotionMeta = null;
|
|
167
|
+
this._pollTimer = null;
|
|
168
|
+
this.opts = opts;
|
|
169
|
+
}
|
|
170
|
+
// ── Public read-only state ────────────────────────────────────────────────
|
|
171
|
+
get status() {
|
|
172
|
+
return this._status;
|
|
173
|
+
}
|
|
174
|
+
get callId() {
|
|
175
|
+
return this._callId;
|
|
176
|
+
}
|
|
177
|
+
get sessionToken() {
|
|
178
|
+
return this._sessionToken;
|
|
179
|
+
}
|
|
180
|
+
get transcripts() {
|
|
181
|
+
return this._transcripts.slice();
|
|
182
|
+
}
|
|
183
|
+
get lastEmotion() {
|
|
184
|
+
return this._lastEmotion;
|
|
185
|
+
}
|
|
186
|
+
get contactSaved() {
|
|
187
|
+
return this._contactSaved;
|
|
188
|
+
}
|
|
189
|
+
get isMicMuted() {
|
|
190
|
+
return this.session?.isMicMuted ?? false;
|
|
191
|
+
}
|
|
192
|
+
get isSpeakerMuted() {
|
|
193
|
+
return this.session?.isSpeakerMuted ?? false;
|
|
194
|
+
}
|
|
195
|
+
/** Server-reported wiring info from the join-url response, if any. */
|
|
196
|
+
get emotionMeta() {
|
|
197
|
+
return this._emotionMeta;
|
|
198
|
+
}
|
|
199
|
+
/** Underlying ultravox-client session. Use sparingly — for power users. */
|
|
200
|
+
get rawSession() {
|
|
201
|
+
return this.session;
|
|
202
|
+
}
|
|
203
|
+
// ── Event API ─────────────────────────────────────────────────────────────
|
|
204
|
+
on(event, listener) {
|
|
205
|
+
return this.emitter.on(event, listener);
|
|
206
|
+
}
|
|
207
|
+
off(event, listener) {
|
|
208
|
+
this.emitter.off(event, listener);
|
|
209
|
+
}
|
|
210
|
+
once(event, listener) {
|
|
211
|
+
return this.emitter.once(event, listener);
|
|
212
|
+
}
|
|
213
|
+
// ── Control ───────────────────────────────────────────────────────────────
|
|
214
|
+
/**
|
|
215
|
+
* Fetches a joinUrl from the backend, opens an Ultravox session, and starts
|
|
216
|
+
* the call. Resolves once `joinCall` has been kicked off (the call goes
|
|
217
|
+
* "live" asynchronously via status events).
|
|
218
|
+
*/
|
|
219
|
+
async start() {
|
|
220
|
+
if (this._starting) return;
|
|
221
|
+
if (this._status !== "idle" /* IDLE */ && this._status !== "disconnected" /* DISCONNECTED */) {
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
this._starting = true;
|
|
225
|
+
this.resetMutableState();
|
|
226
|
+
this.setStatus("connecting" /* CONNECTING */);
|
|
227
|
+
try {
|
|
228
|
+
const payload = await fetchJoinUrl(this.opts);
|
|
229
|
+
this._callId = payload.callId ?? null;
|
|
230
|
+
this._sessionToken = payload.sessionToken ?? null;
|
|
231
|
+
this._emotionMeta = payload.emotion ?? null;
|
|
232
|
+
this.surfaceEmotionWarnings(payload);
|
|
233
|
+
const session = new UltravoxSession({
|
|
234
|
+
audioContext: this.opts.audioContext,
|
|
235
|
+
additionalMessages: this.opts.additionalMessages
|
|
236
|
+
});
|
|
237
|
+
this.session = session;
|
|
238
|
+
this.attachSessionListeners(session);
|
|
239
|
+
session.joinCall(payload.joinUrl);
|
|
240
|
+
} catch (err) {
|
|
241
|
+
const e = err instanceof Error ? err : new Error(String(err));
|
|
242
|
+
this.emitter.emit("error", e);
|
|
243
|
+
this.setStatus("idle" /* IDLE */);
|
|
244
|
+
} finally {
|
|
245
|
+
this._starting = false;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/** Hangs up. Resolves when ultravox-client confirms disconnection. */
|
|
249
|
+
async end() {
|
|
250
|
+
if (!this.session) return;
|
|
251
|
+
this.setStatus("disconnecting" /* DISCONNECTING */);
|
|
252
|
+
try {
|
|
253
|
+
await this.session.leaveCall();
|
|
254
|
+
} catch (err) {
|
|
255
|
+
this.emitter.emit(
|
|
256
|
+
"error",
|
|
257
|
+
err instanceof Error ? err : new Error(String(err))
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
muteMic() {
|
|
262
|
+
this.session?.muteMic();
|
|
263
|
+
this.emitter.emit("mic_muted", true);
|
|
264
|
+
}
|
|
265
|
+
unmuteMic() {
|
|
266
|
+
this.session?.unmuteMic();
|
|
267
|
+
this.emitter.emit("mic_muted", false);
|
|
268
|
+
}
|
|
269
|
+
toggleMicMute() {
|
|
270
|
+
if (!this.session) return false;
|
|
271
|
+
this.session.toggleMicMute();
|
|
272
|
+
const muted = this.session.isMicMuted;
|
|
273
|
+
this.emitter.emit("mic_muted", muted);
|
|
274
|
+
return muted;
|
|
275
|
+
}
|
|
276
|
+
muteSpeaker() {
|
|
277
|
+
this.session?.muteSpeaker();
|
|
278
|
+
this.emitter.emit("speaker_muted", true);
|
|
279
|
+
}
|
|
280
|
+
unmuteSpeaker() {
|
|
281
|
+
this.session?.unmuteSpeaker();
|
|
282
|
+
this.emitter.emit("speaker_muted", false);
|
|
283
|
+
}
|
|
284
|
+
toggleSpeakerMute() {
|
|
285
|
+
if (!this.session) return false;
|
|
286
|
+
this.session.toggleSpeakerMute();
|
|
287
|
+
const muted = this.session.isSpeakerMuted;
|
|
288
|
+
this.emitter.emit("speaker_muted", muted);
|
|
289
|
+
return muted;
|
|
290
|
+
}
|
|
291
|
+
/** Sends a text message into the call (no spoken audio from the user). */
|
|
292
|
+
sendText(text, deferResponse = false) {
|
|
293
|
+
this.session?.sendText(text, deferResponse);
|
|
294
|
+
}
|
|
295
|
+
/** Sends an arbitrary data message over Ultravox's data channel. */
|
|
296
|
+
sendData(obj) {
|
|
297
|
+
this.session?.sendData(obj);
|
|
298
|
+
}
|
|
299
|
+
/** Removes all listeners and aborts any active session. */
|
|
300
|
+
dispose() {
|
|
301
|
+
this.stopEmotionPolling();
|
|
302
|
+
void this.session?.leaveCall().catch(() => {
|
|
303
|
+
});
|
|
304
|
+
this.session = null;
|
|
305
|
+
this.emitter.removeAllListeners();
|
|
306
|
+
this._status = "idle" /* IDLE */;
|
|
307
|
+
}
|
|
308
|
+
// ── Internals ─────────────────────────────────────────────────────────────
|
|
309
|
+
resetMutableState() {
|
|
310
|
+
this._callId = null;
|
|
311
|
+
this._sessionToken = null;
|
|
312
|
+
this._transcripts = [];
|
|
313
|
+
this._lastEmotion = null;
|
|
314
|
+
this._contactSaved = false;
|
|
315
|
+
this._emotionMeta = null;
|
|
316
|
+
this.stopEmotionPolling();
|
|
317
|
+
}
|
|
318
|
+
attachSessionListeners(session) {
|
|
319
|
+
session.addEventListener("status", () => this.handleStatusChange(session));
|
|
320
|
+
session.addEventListener("transcripts", () => this.handleTranscripts(session));
|
|
321
|
+
session.addEventListener(
|
|
322
|
+
"experimental_message",
|
|
323
|
+
(evt) => this.handleDataMessage(evt)
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
handleStatusChange(session) {
|
|
327
|
+
const raw = session.status;
|
|
328
|
+
this.emitter.emit("raw_status", String(raw));
|
|
329
|
+
const prevStatus = this._status;
|
|
330
|
+
let next;
|
|
331
|
+
switch (raw) {
|
|
332
|
+
case UltravoxSessionStatus.CONNECTING:
|
|
333
|
+
next = "connecting" /* CONNECTING */;
|
|
334
|
+
break;
|
|
335
|
+
case UltravoxSessionStatus.IDLE:
|
|
336
|
+
next = "connected" /* CONNECTED */;
|
|
337
|
+
break;
|
|
338
|
+
case UltravoxSessionStatus.LISTENING:
|
|
339
|
+
next = "listening" /* LISTENING */;
|
|
340
|
+
break;
|
|
341
|
+
case UltravoxSessionStatus.THINKING:
|
|
342
|
+
next = "thinking" /* THINKING */;
|
|
343
|
+
break;
|
|
344
|
+
case UltravoxSessionStatus.SPEAKING:
|
|
345
|
+
next = "speaking" /* SPEAKING */;
|
|
346
|
+
break;
|
|
347
|
+
case UltravoxSessionStatus.DISCONNECTING:
|
|
348
|
+
next = "disconnecting" /* DISCONNECTING */;
|
|
349
|
+
break;
|
|
350
|
+
case UltravoxSessionStatus.DISCONNECTED:
|
|
351
|
+
next = "disconnected" /* DISCONNECTED */;
|
|
352
|
+
break;
|
|
353
|
+
default:
|
|
354
|
+
next = this._status;
|
|
355
|
+
}
|
|
356
|
+
this.setStatus(next);
|
|
357
|
+
const wasLive = LIVE_STATUSES.has(prevStatus);
|
|
358
|
+
const nowLive = LIVE_STATUSES.has(next);
|
|
359
|
+
if (!wasLive && nowLive && this._callId && this.opts.pollEmotion) {
|
|
360
|
+
this.startEmotionPolling();
|
|
361
|
+
}
|
|
362
|
+
if (next === "disconnected" /* DISCONNECTED */) {
|
|
363
|
+
this.stopEmotionPolling();
|
|
364
|
+
this.session = null;
|
|
365
|
+
this.emitter.emit("ended", void 0);
|
|
366
|
+
this.setStatus("idle" /* IDLE */);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
handleTranscripts(session) {
|
|
370
|
+
const raw = session.transcripts ?? [];
|
|
371
|
+
const mapped = raw.map(toPublicTranscript);
|
|
372
|
+
const previous = this._transcripts;
|
|
373
|
+
this._transcripts = mapped;
|
|
374
|
+
for (let i = 0; i < mapped.length; i++) {
|
|
375
|
+
const cur = mapped[i];
|
|
376
|
+
if (!cur) continue;
|
|
377
|
+
const prev = previous[i];
|
|
378
|
+
if (!prev || prev.text !== cur.text || prev.isFinal !== cur.isFinal) {
|
|
379
|
+
this.emitter.emit("transcript", cur);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
this.emitter.emit("transcripts", mapped);
|
|
383
|
+
if (!this._contactSaved) {
|
|
384
|
+
const saved = mapped.some(
|
|
385
|
+
(t) => t.speaker === "agent" /* AGENT */ && t.text && CONTACT_SAVED_PATTERN.test(t.text)
|
|
386
|
+
);
|
|
387
|
+
if (saved) {
|
|
388
|
+
this._contactSaved = true;
|
|
389
|
+
this.emitter.emit("contact_saved", void 0);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
handleDataMessage(evt) {
|
|
394
|
+
const anyEvt = evt;
|
|
395
|
+
const raw = anyEvt.message ?? anyEvt.data ?? evt;
|
|
396
|
+
this.emitter.emit("data_message", raw);
|
|
397
|
+
const text = typeof raw === "string" ? raw : safeStringify(raw);
|
|
398
|
+
const pattern = this.opts.emotionPattern ?? DEFAULT_EMOTION_PATTERN;
|
|
399
|
+
const match = text.match(pattern);
|
|
400
|
+
if (match && match[1]) {
|
|
401
|
+
const label = match[1].toLowerCase();
|
|
402
|
+
this._lastEmotion = label;
|
|
403
|
+
this.emitter.emit("emotion", { label, raw });
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
startEmotionPolling() {
|
|
407
|
+
this.stopEmotionPolling();
|
|
408
|
+
if (!this.opts.pollEmotion || !this._callId) return;
|
|
409
|
+
const callId = this._callId;
|
|
410
|
+
const sessionToken = this._sessionToken ?? void 0;
|
|
411
|
+
const interval = this.opts.emotionPollIntervalMs ?? 15e3;
|
|
412
|
+
const poll = async () => {
|
|
413
|
+
try {
|
|
414
|
+
const label = await this.opts.pollEmotion(callId, sessionToken);
|
|
415
|
+
if (label) {
|
|
416
|
+
const normalized = label.toLowerCase();
|
|
417
|
+
this._lastEmotion = normalized;
|
|
418
|
+
this.emitter.emit("emotion", { label: normalized, raw: label });
|
|
419
|
+
}
|
|
420
|
+
} catch {
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
poll();
|
|
424
|
+
this._pollTimer = setInterval(poll, interval);
|
|
425
|
+
}
|
|
426
|
+
stopEmotionPolling() {
|
|
427
|
+
if (this._pollTimer !== null) {
|
|
428
|
+
clearInterval(this._pollTimer);
|
|
429
|
+
this._pollTimer = null;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
setStatus(next) {
|
|
433
|
+
if (next === this._status) return;
|
|
434
|
+
this._status = next;
|
|
435
|
+
this.emitter.emit("status", next);
|
|
436
|
+
}
|
|
437
|
+
surfaceEmotionWarnings(payload) {
|
|
438
|
+
const e = payload.emotion;
|
|
439
|
+
if (!e) return;
|
|
440
|
+
if (e.dataConnectionEnabled === false) {
|
|
441
|
+
this.emitter.emit(
|
|
442
|
+
"warning",
|
|
443
|
+
"dataConnection not enabled \u2014 check emotion WebSocket URL."
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
if (e.audioEnabled === false) {
|
|
447
|
+
this.emitter.emit(
|
|
448
|
+
"warning",
|
|
449
|
+
"audio not enabled \u2014 audioConfig missing on backend request."
|
|
450
|
+
);
|
|
451
|
+
}
|
|
452
|
+
if (e.emotionBridgeConfigured === false) {
|
|
453
|
+
this.emitter.emit(
|
|
454
|
+
"warning",
|
|
455
|
+
"emotionBridge not configured on the backend."
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
if (e.autoSendEnabled === false) {
|
|
459
|
+
this.emitter.emit(
|
|
460
|
+
"warning",
|
|
461
|
+
"auto-send not enabled \u2014 emotion will not feed back into the agent."
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
function toPublicTranscript(t) {
|
|
467
|
+
return {
|
|
468
|
+
text: t.text,
|
|
469
|
+
isFinal: t.isFinal,
|
|
470
|
+
speaker: t.speaker,
|
|
471
|
+
medium: t.medium,
|
|
472
|
+
ordinal: t.ordinal
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
function safeStringify(v) {
|
|
476
|
+
try {
|
|
477
|
+
return JSON.stringify(v);
|
|
478
|
+
} catch {
|
|
479
|
+
return String(v);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export { CallStatus, Speaker, VoiceCall, fetchJoinUrl };
|
|
484
|
+
//# sourceMappingURL=index.js.map
|
|
485
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/emitter.ts","../src/types.ts","../src/VoiceCall.ts"],"names":["url","res","CallStatus","Speaker"],"mappings":"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","file":"index.js","sourcesContent":["import type { JoinUrlResponse, VoiceCallOptions } from \"./types.js\";\n\n/**\n * POSTs to the join-url endpoint and returns the parsed response.\n *\n * Auth resolution order:\n * 1. `fetchJoinUrl` — fully custom fetcher, takes precedence over everything.\n * 2. `publicKey` — browser-direct auth via `X-Public-Key` header + browser Origin.\n * Hits `/v1/voice/joinurl` (the new SDK-aware endpoint).\n * 3. `authToken` — Bearer JWT, used with the legacy `/ultravox/secure/joinurl` endpoint\n * (or a custom `joinUrlPath`). Suitable for server-side / Netlify fn use.\n *\n * Throws on non-2xx or missing joinUrl.\n */\nexport async function fetchJoinUrl(\n opts: VoiceCallOptions,\n): Promise<JoinUrlResponse> {\n if (opts.fetchJoinUrl) {\n return opts.fetchJoinUrl();\n }\n\n if (!opts.apiUrl) {\n throw new Error(\"[aihumanity/voice-sdk] apiUrl is required when fetchJoinUrl is not provided.\");\n }\n\n const baseUrl = opts.apiUrl.replace(/\\/+$/, \"\");\n\n const body: Record<string, unknown> = {\n username: opts.username,\n agentName: opts.agentName,\n ...(opts.extraJoinUrlBody ?? {}),\n };\n\n if (opts.dataConnection) {\n body.dataConnection = {\n websocketUrl: opts.dataConnection.websocketUrl,\n audioConfig: {\n sampleRate: opts.dataConnection.audioConfig?.sampleRate ?? 16000,\n channelMode:\n opts.dataConnection.audioConfig?.channelMode ??\n \"CHANNEL_MODE_SEPARATED\",\n },\n // Spread user-supplied flags first, then apply defaults for the two\n // most common flags so they're always present if not explicitly set.\n dataMessages: {\n ...(opts.dataConnection.dataMessages ?? {}),\n userStartedSpeaking:\n opts.dataConnection.dataMessages?.userStartedSpeaking ?? true,\n userStoppedSpeaking:\n opts.dataConnection.dataMessages?.userStoppedSpeaking ?? true,\n },\n };\n }\n\n // ── Public key (browser-direct, no backend) ──────────────────────────────\n if (opts.publicKey) {\n const url = baseUrl + (opts.joinUrlPath ?? \"/v1/voice/joinurl\");\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-Public-Key\": opts.publicKey,\n },\n body: JSON.stringify(body),\n });\n return parseJoinUrlResponse(res);\n }\n\n // ── Bearer token (server-side / Netlify fn) ───────────────────────────────\n const token =\n typeof opts.authToken === \"function\"\n ? await opts.authToken()\n : opts.authToken;\n\n if (!token) {\n throw new Error(\n \"[aihumanity/voice-sdk] Provide publicKey (for browser-direct) or authToken (for server-side).\",\n );\n }\n\n const path = opts.joinUrlPath ?? \"/ultravox/secure/joinurl\";\n const url = baseUrl + path;\n\n const res = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${token}`,\n },\n body: JSON.stringify(body),\n });\n\n return parseJoinUrlResponse(res);\n}\n\nasync function parseJoinUrlResponse(res: Response): Promise<JoinUrlResponse> {\n const text = await res.text();\n if (!res.ok) {\n throw new Error(\n `[aihumanity/voice-sdk] joinUrl request failed (${res.status}): ${text}`,\n );\n }\n\n let parsed: JoinUrlResponse;\n try {\n parsed = JSON.parse(text);\n } catch {\n throw new Error(\n `[aihumanity/voice-sdk] joinUrl response was not valid JSON: ${text}`,\n );\n }\n\n if (!parsed.joinUrl) {\n throw new Error(\n \"[aihumanity/voice-sdk] joinUrl response did not include `joinUrl`.\",\n );\n }\n return parsed;\n}\n","import type { VoiceCallEvents, VoiceCallListener } from \"./types.js\";\n\n/**\n * Tiny strongly-typed event emitter. Avoids dragging in `eventemitter3` etc.\n * Every method returns the emitter, except `on` which returns an unsubscribe\n * function for ergonomic React effect cleanup.\n */\nexport class TypedEmitter {\n private readonly listeners = new Map<\n keyof VoiceCallEvents,\n Set<(payload: unknown) => void>\n >();\n\n on<K extends keyof VoiceCallEvents>(\n event: K,\n listener: VoiceCallListener<K>,\n ): () => void {\n let set = this.listeners.get(event);\n if (!set) {\n set = new Set();\n this.listeners.set(event, set);\n }\n set.add(listener as (payload: unknown) => void);\n return () => this.off(event, listener);\n }\n\n off<K extends keyof VoiceCallEvents>(\n event: K,\n listener: VoiceCallListener<K>,\n ): void {\n this.listeners.get(event)?.delete(listener as (payload: unknown) => void);\n }\n\n once<K extends keyof VoiceCallEvents>(\n event: K,\n listener: VoiceCallListener<K>,\n ): () => void {\n const off = this.on(event, ((payload: VoiceCallEvents[K]) => {\n off();\n listener(payload);\n }) as VoiceCallListener<K>);\n return off;\n }\n\n emit<K extends keyof VoiceCallEvents>(\n event: K,\n payload: VoiceCallEvents[K],\n ): void {\n const set = this.listeners.get(event);\n if (!set) return;\n // Snapshot to allow listeners to off() during emit.\n for (const fn of Array.from(set)) {\n try {\n fn(payload);\n } catch (err) {\n // Don't let one bad listener kill the loop.\n // eslint-disable-next-line no-console\n console.error(\"[aihumanity/voice-sdk] listener error:\", err);\n }\n }\n }\n\n removeAllListeners(): void {\n this.listeners.clear();\n }\n}\n","/**\n * Public types shared across the SDK.\n *\n * Status names are aligned with Ultravox's session-status enum so the SDK is\n * a thin, semantic wrapper.\n */\n\n/** Coarse-grained call lifecycle states the SDK exposes to consumers. */\nexport enum CallStatus {\n /** No active call; ready to start. */\n IDLE = \"idle\",\n /** Backend is being asked for a joinUrl, or WebRTC is connecting. */\n CONNECTING = \"connecting\",\n /** Call is live; agent is ready to listen. */\n CONNECTED = \"connected\",\n /** Microphone is open and capturing user speech. */\n LISTENING = \"listening\",\n /** Agent is processing the user's last utterance. */\n THINKING = \"thinking\",\n /** Agent is speaking. */\n SPEAKING = \"speaking\",\n /** Call is being torn down. */\n DISCONNECTING = \"disconnecting\",\n /** Call ended (terminal — same as IDLE for new calls). */\n DISCONNECTED = \"disconnected\",\n}\n\n/** Speaker role on a transcript line. Mirrors ultravox-client's `Role`. */\nexport enum Speaker {\n USER = \"user\",\n AGENT = \"agent\",\n}\n\nexport interface Transcript {\n /** Spoken text. May grow over time as more partials arrive. */\n text: string;\n /** Whether this is the final, locked-in version of the utterance. */\n isFinal: boolean;\n /** Who spoke. */\n speaker: Speaker;\n /** voice or text. */\n medium: \"voice\" | \"text\";\n /** Sequence number for ordering. */\n ordinal: number;\n}\n\n/**\n * Audio format the data-connection (emotion / analytics) WebSocket expects.\n * Matches the Ultravox `dataConnection.audioConfig` schema.\n */\nexport interface AudioConfig {\n /** PCM sample rate. Default 16000. */\n sampleRate?: number;\n /**\n * \"CHANNEL_MODE_SEPARATED\" sends user/agent as separate channels;\n * \"CHANNEL_MODE_MIXED\" sends a single mixed stream.\n */\n channelMode?: \"CHANNEL_MODE_SEPARATED\" | \"CHANNEL_MODE_MIXED\";\n}\n\n/** Which speech-activity events the data WebSocket should receive. */\nexport interface DataMessageFlags {\n userStartedSpeaking?: boolean;\n userStoppedSpeaking?: boolean;\n agentStartedSpeaking?: boolean;\n agentStoppedSpeaking?: boolean;\n}\n\n/**\n * Optional data-connection block. When provided the eimi backend will open a\n * server-to-server WebSocket so user audio bytes can be analyzed (e.g. for\n * vocal emotion) and the result fed back as a data message.\n */\nexport interface DataConnectionConfig {\n /** wss:// URL the backend should open for raw user audio. */\n websocketUrl: string;\n audioConfig?: AudioConfig;\n dataMessages?: DataMessageFlags;\n}\n\n/** Options for starting a call. */\nexport interface VoiceCallOptions {\n /**\n * Base URL of the eimi backend. Example: \"https://api.eimi.ai\".\n * Required unless `fetchJoinUrl` is provided.\n */\n apiUrl?: string;\n /**\n * Bearer token used to authenticate with eimi backend. Can be a string or a\n * function that resolves one (handy for short-lived JWTs you fetch from\n * your own backend). Required unless `fetchJoinUrl` or `publicKey` is provided.\n */\n authToken?: string | (() => string | Promise<string>);\n /**\n * Publishable API key ID for browser-direct (no-backend) auth.\n *\n * When provided the SDK sends `X-Public-Key: <publicKey>` and relies on the\n * browser's `Origin` header for domain validation on the server. The server\n * checks the origin against the developer's registered `allowedOrigins` list.\n *\n * Use this when you have no server-side proxy. Register your site's origin in\n * the developer portal first. Hits `/v1/voice/joinurl` by default (override\n * with `joinUrlPath`).\n *\n * Unlike `authToken`, this key ID is safe to embed in browser JS — it grants\n * no access outside your registered origins.\n */\n publicKey?: string;\n /** Agent name configured in eimi backend, e.g. \"DavidChiu\". */\n agentName?: string;\n /** Username the call should be billed/attributed to. */\n username?: string;\n /** Optional emotion / analytics WebSocket config. */\n dataConnection?: DataConnectionConfig;\n /**\n * Override the join-url path. Defaults to \"/ultravox/secure/joinurl\".\n * Useful if your deployment routes through a proxy.\n */\n joinUrlPath?: string;\n /**\n * Optional fully custom fetcher. If provided, the SDK calls this function\n * instead of building a request itself. Must resolve to `{ joinUrl, callId? }`.\n * When this is provided, `apiUrl` and `authToken` are not required.\n */\n fetchJoinUrl?: () => Promise<JoinUrlResponse>;\n /** Extra fields forwarded in the join-url request body. */\n extraJoinUrlBody?: Record<string, unknown>;\n /**\n * Pattern used to extract an emotion label out of `experimental_message`\n * data payloads. Default looks for \"[EMOTION_CONTEXT] ... : <label>\".\n * The first capture group becomes the emotion label.\n */\n emotionPattern?: RegExp;\n /**\n * Optional polling callback for emotion data. When provided, the SDK polls\n * this function at `emotionPollIntervalMs` while the call is live.\n * Use this when emotion is injected server-side (not via experimental_message).\n * Receives the `callId` and optional `sessionToken` from the join response.\n * Should return the emotion label string or null.\n */\n pollEmotion?: (callId: string, sessionToken?: string) => Promise<string | null>;\n /**\n * How often (ms) to call `pollEmotion`. Default 15000 (15 s).\n * Ignored if `pollEmotion` is not set.\n */\n emotionPollIntervalMs?: number;\n /** AudioContext to reuse. The SDK will create one if omitted. */\n audioContext?: AudioContext;\n /** Pass-through to ultravox-client. */\n additionalMessages?: Set<string>;\n}\n\n/** Whatever the eimi backend (or your custom fetcher) returns. */\nexport interface JoinUrlResponse {\n joinUrl: string;\n callId?: string;\n /**\n * Short-lived token scoped to this call. Returned by eimi backend when\n * session-based auth is configured. Used by `pollEmotion` for direct\n * browser→backend calls without needing a service token.\n */\n sessionToken?: string;\n /**\n * Server-side summary of whether the data connection / emotion bridge was\n * wired up. Surface mirrors what eimi backend currently returns.\n */\n emotion?: {\n dataConnectionEnabled?: boolean;\n audioEnabled?: boolean;\n emotionBridgeConfigured?: boolean;\n autoSendEnabled?: boolean;\n [k: string]: unknown;\n };\n [k: string]: unknown;\n}\n\n/** Map of event name -> listener payload type for the VoiceCall emitter. */\nexport interface VoiceCallEvents {\n /** Coarse call status changed. Always fires on real transitions. */\n status: CallStatus;\n /** Underlying Ultravox status changed (kept for power users). */\n raw_status: string;\n /** A transcript was added or updated. */\n transcript: Transcript;\n /** Full transcript array snapshot, fired after every transcript update. */\n transcripts: Transcript[];\n /** Vocal emotion label extracted from a data message or poll. */\n emotion: { label: string; raw: unknown };\n /** Any data message from the agent / data connection. */\n data_message: unknown;\n /** Mic mute state changed. */\n mic_muted: boolean;\n /** Speaker mute state changed. */\n speaker_muted: boolean;\n /** Agent has saved/persisted contact info (heuristic on transcript). */\n contact_saved: void;\n /** A non-fatal warning (e.g., emotion bridge not configured). */\n warning: string;\n /** A fatal error during start/operation. */\n error: Error;\n /** Call has fully ended. */\n ended: void;\n}\n\n/** Type of listener for a given VoiceCall event. */\nexport type VoiceCallListener<K extends keyof VoiceCallEvents> = (\n payload: VoiceCallEvents[K],\n) => void;\n","import {\n UltravoxSession,\n UltravoxSessionStatus,\n type Transcript as UvTranscript,\n} from \"ultravox-client\";\n\nimport { fetchJoinUrl } from \"./client.js\";\nimport { TypedEmitter } from \"./emitter.js\";\nimport {\n CallStatus,\n Speaker,\n type JoinUrlResponse,\n type Transcript,\n type VoiceCallEvents,\n type VoiceCallListener,\n type VoiceCallOptions,\n} from \"./types.js\";\n\n/** Default regex used to pick an emotion label out of an `experimental_message`. */\nconst DEFAULT_EMOTION_PATTERN = /\\[EMOTION_CONTEXT\\][^:]*:\\s*(\\w+)/i;\n\n/** Heuristic phrase the agent says when it has saved the user's contact info. */\nconst CONTACT_SAVED_PATTERN =\n /I['']ve (noted|saved|got|recorded) your (contact|info|details|number|email)/i;\n\n/** Live call statuses — polling starts on first entry. */\nconst LIVE_STATUSES = new Set<CallStatus>([\n CallStatus.CONNECTED,\n CallStatus.LISTENING,\n CallStatus.THINKING,\n CallStatus.SPEAKING,\n]);\n\n/**\n * High-level voice call wrapper.\n *\n * ```ts\n * const call = new VoiceCall({ apiUrl, authToken, agentName, username });\n * call.on(\"status\", s => console.log(s));\n * call.on(\"transcript\", t => console.log(t.speaker, t.text));\n * call.on(\"emotion\", e => console.log(\"emotion:\", e.label));\n * await call.start();\n * // ... later\n * await call.end();\n * ```\n */\nexport class VoiceCall {\n private readonly emitter = new TypedEmitter();\n private readonly opts: VoiceCallOptions;\n private session: UltravoxSession | null = null;\n\n private _status: CallStatus = CallStatus.IDLE;\n private _callId: string | null = null;\n private _sessionToken: string | null = null;\n private _transcripts: Transcript[] = [];\n private _lastEmotion: string | null = null;\n private _contactSaved = false;\n private _starting = false;\n private _emotionMeta: JoinUrlResponse[\"emotion\"] | null = null;\n private _pollTimer: ReturnType<typeof setInterval> | null = null;\n\n constructor(opts: VoiceCallOptions) {\n this.opts = opts;\n }\n\n // ── Public read-only state ────────────────────────────────────────────────\n\n get status(): CallStatus {\n return this._status;\n }\n\n get callId(): string | null {\n return this._callId;\n }\n\n get sessionToken(): string | null {\n return this._sessionToken;\n }\n\n get transcripts(): Transcript[] {\n return this._transcripts.slice();\n }\n\n get lastEmotion(): string | null {\n return this._lastEmotion;\n }\n\n get contactSaved(): boolean {\n return this._contactSaved;\n }\n\n get isMicMuted(): boolean {\n return this.session?.isMicMuted ?? false;\n }\n\n get isSpeakerMuted(): boolean {\n return this.session?.isSpeakerMuted ?? false;\n }\n\n /** Server-reported wiring info from the join-url response, if any. */\n get emotionMeta(): JoinUrlResponse[\"emotion\"] | null {\n return this._emotionMeta;\n }\n\n /** Underlying ultravox-client session. Use sparingly — for power users. */\n get rawSession(): UltravoxSession | null {\n return this.session;\n }\n\n // ── Event API ─────────────────────────────────────────────────────────────\n\n on<K extends keyof VoiceCallEvents>(\n event: K,\n listener: VoiceCallListener<K>,\n ): () => void {\n return this.emitter.on(event, listener);\n }\n\n off<K extends keyof VoiceCallEvents>(\n event: K,\n listener: VoiceCallListener<K>,\n ): void {\n this.emitter.off(event, listener);\n }\n\n once<K extends keyof VoiceCallEvents>(\n event: K,\n listener: VoiceCallListener<K>,\n ): () => void {\n return this.emitter.once(event, listener);\n }\n\n // ── Control ───────────────────────────────────────────────────────────────\n\n /**\n * Fetches a joinUrl from the backend, opens an Ultravox session, and starts\n * the call. Resolves once `joinCall` has been kicked off (the call goes\n * \"live\" asynchronously via status events).\n */\n async start(): Promise<void> {\n if (this._starting) return;\n if (this._status !== CallStatus.IDLE && this._status !== CallStatus.DISCONNECTED) {\n return;\n }\n\n this._starting = true;\n this.resetMutableState();\n this.setStatus(CallStatus.CONNECTING);\n\n try {\n const payload = await fetchJoinUrl(this.opts);\n this._callId = payload.callId ?? null;\n this._sessionToken = payload.sessionToken ?? null;\n this._emotionMeta = payload.emotion ?? null;\n this.surfaceEmotionWarnings(payload);\n\n const session = new UltravoxSession({\n audioContext: this.opts.audioContext,\n additionalMessages: this.opts.additionalMessages,\n });\n this.session = session;\n this.attachSessionListeners(session);\n\n session.joinCall(payload.joinUrl);\n } catch (err) {\n const e = err instanceof Error ? err : new Error(String(err));\n this.emitter.emit(\"error\", e);\n this.setStatus(CallStatus.IDLE);\n } finally {\n this._starting = false;\n }\n }\n\n /** Hangs up. Resolves when ultravox-client confirms disconnection. */\n async end(): Promise<void> {\n if (!this.session) return;\n this.setStatus(CallStatus.DISCONNECTING);\n try {\n await this.session.leaveCall();\n } catch (err) {\n this.emitter.emit(\n \"error\",\n err instanceof Error ? err : new Error(String(err)),\n );\n }\n }\n\n muteMic(): void {\n this.session?.muteMic();\n this.emitter.emit(\"mic_muted\", true);\n }\n\n unmuteMic(): void {\n this.session?.unmuteMic();\n this.emitter.emit(\"mic_muted\", false);\n }\n\n toggleMicMute(): boolean {\n if (!this.session) return false;\n this.session.toggleMicMute();\n const muted = this.session.isMicMuted;\n this.emitter.emit(\"mic_muted\", muted);\n return muted;\n }\n\n muteSpeaker(): void {\n this.session?.muteSpeaker();\n this.emitter.emit(\"speaker_muted\", true);\n }\n\n unmuteSpeaker(): void {\n this.session?.unmuteSpeaker();\n this.emitter.emit(\"speaker_muted\", false);\n }\n\n toggleSpeakerMute(): boolean {\n if (!this.session) return false;\n this.session.toggleSpeakerMute();\n const muted = this.session.isSpeakerMuted;\n this.emitter.emit(\"speaker_muted\", muted);\n return muted;\n }\n\n /** Sends a text message into the call (no spoken audio from the user). */\n sendText(text: string, deferResponse = false): void {\n this.session?.sendText(text, deferResponse);\n }\n\n /** Sends an arbitrary data message over Ultravox's data channel. */\n sendData(obj: unknown): void {\n this.session?.sendData(obj);\n }\n\n /** Removes all listeners and aborts any active session. */\n dispose(): void {\n this.stopEmotionPolling();\n void this.session?.leaveCall().catch(() => {});\n this.session = null;\n this.emitter.removeAllListeners();\n this._status = CallStatus.IDLE;\n }\n\n // ── Internals ─────────────────────────────────────────────────────────────\n\n private resetMutableState(): void {\n this._callId = null;\n this._sessionToken = null;\n this._transcripts = [];\n this._lastEmotion = null;\n this._contactSaved = false;\n this._emotionMeta = null;\n this.stopEmotionPolling();\n }\n\n private attachSessionListeners(session: UltravoxSession): void {\n session.addEventListener(\"status\", () => this.handleStatusChange(session));\n session.addEventListener(\"transcripts\", () => this.handleTranscripts(session));\n session.addEventListener(\"experimental_message\", (evt) =>\n this.handleDataMessage(evt),\n );\n }\n\n private handleStatusChange(session: UltravoxSession): void {\n const raw = session.status;\n this.emitter.emit(\"raw_status\", String(raw));\n\n const prevStatus = this._status;\n let next: CallStatus;\n switch (raw) {\n case UltravoxSessionStatus.CONNECTING:\n next = CallStatus.CONNECTING;\n break;\n case UltravoxSessionStatus.IDLE:\n // IDLE in ultravox-client = \"connected, waiting\" — the call IS live.\n next = CallStatus.CONNECTED;\n break;\n case UltravoxSessionStatus.LISTENING:\n next = CallStatus.LISTENING;\n break;\n case UltravoxSessionStatus.THINKING:\n next = CallStatus.THINKING;\n break;\n case UltravoxSessionStatus.SPEAKING:\n next = CallStatus.SPEAKING;\n break;\n case UltravoxSessionStatus.DISCONNECTING:\n next = CallStatus.DISCONNECTING;\n break;\n case UltravoxSessionStatus.DISCONNECTED:\n next = CallStatus.DISCONNECTED;\n break;\n default:\n next = this._status;\n }\n\n this.setStatus(next);\n\n // Start emotion polling on the first transition into a live state.\n const wasLive = LIVE_STATUSES.has(prevStatus);\n const nowLive = LIVE_STATUSES.has(next);\n if (!wasLive && nowLive && this._callId && this.opts.pollEmotion) {\n this.startEmotionPolling();\n }\n\n if (next === CallStatus.DISCONNECTED) {\n this.stopEmotionPolling();\n this.session = null;\n this.emitter.emit(\"ended\", undefined);\n // Settle back to IDLE so the caller can restart cleanly.\n this.setStatus(CallStatus.IDLE);\n }\n }\n\n private handleTranscripts(session: UltravoxSession): void {\n const raw = session.transcripts ?? [];\n const mapped: Transcript[] = raw.map(toPublicTranscript);\n const previous = this._transcripts;\n this._transcripts = mapped;\n\n // Emit `transcript` for any new or updated entries.\n for (let i = 0; i < mapped.length; i++) {\n const cur = mapped[i];\n if (!cur) continue;\n const prev = previous[i];\n if (\n !prev ||\n prev.text !== cur.text ||\n prev.isFinal !== cur.isFinal\n ) {\n this.emitter.emit(\"transcript\", cur);\n }\n }\n this.emitter.emit(\"transcripts\", mapped);\n\n // Heuristic: detect \"I've saved your contact info\" style confirmation.\n if (!this._contactSaved) {\n const saved = mapped.some(\n (t) =>\n t.speaker === Speaker.AGENT &&\n t.text &&\n CONTACT_SAVED_PATTERN.test(t.text),\n );\n if (saved) {\n this._contactSaved = true;\n this.emitter.emit(\"contact_saved\", undefined);\n }\n }\n }\n\n private handleDataMessage(evt: Event): void {\n // ultravox-client surfaces this as UltravoxDataMessageEvent with `.message`.\n // Some SDK versions use `.data` so we read both for safety.\n const anyEvt = evt as Event & { message?: unknown; data?: unknown };\n const raw = anyEvt.message ?? anyEvt.data ?? evt;\n this.emitter.emit(\"data_message\", raw);\n\n const text = typeof raw === \"string\" ? raw : safeStringify(raw);\n const pattern = this.opts.emotionPattern ?? DEFAULT_EMOTION_PATTERN;\n const match = text.match(pattern);\n if (match && match[1]) {\n const label = match[1].toLowerCase();\n this._lastEmotion = label;\n this.emitter.emit(\"emotion\", { label, raw });\n }\n }\n\n private startEmotionPolling(): void {\n this.stopEmotionPolling();\n if (!this.opts.pollEmotion || !this._callId) return;\n\n const callId = this._callId;\n const sessionToken = this._sessionToken ?? undefined;\n const interval = this.opts.emotionPollIntervalMs ?? 15000;\n\n const poll = async () => {\n try {\n const label = await this.opts.pollEmotion!(callId, sessionToken);\n if (label) {\n const normalized = label.toLowerCase();\n this._lastEmotion = normalized;\n this.emitter.emit(\"emotion\", { label: normalized, raw: label });\n }\n } catch {\n // non-fatal — polling errors are silent\n }\n };\n\n poll(); // immediate first fetch\n this._pollTimer = setInterval(poll, interval);\n }\n\n private stopEmotionPolling(): void {\n if (this._pollTimer !== null) {\n clearInterval(this._pollTimer);\n this._pollTimer = null;\n }\n }\n\n private setStatus(next: CallStatus): void {\n if (next === this._status) return;\n this._status = next;\n this.emitter.emit(\"status\", next);\n }\n\n private surfaceEmotionWarnings(payload: JoinUrlResponse): void {\n const e = payload.emotion;\n if (!e) return;\n if (e.dataConnectionEnabled === false) {\n this.emitter.emit(\n \"warning\",\n \"dataConnection not enabled — check emotion WebSocket URL.\",\n );\n }\n if (e.audioEnabled === false) {\n this.emitter.emit(\n \"warning\",\n \"audio not enabled — audioConfig missing on backend request.\",\n );\n }\n if (e.emotionBridgeConfigured === false) {\n this.emitter.emit(\n \"warning\",\n \"emotionBridge not configured on the backend.\",\n );\n }\n if (e.autoSendEnabled === false) {\n this.emitter.emit(\n \"warning\",\n \"auto-send not enabled — emotion will not feed back into the agent.\",\n );\n }\n }\n}\n\nfunction toPublicTranscript(t: UvTranscript): Transcript {\n return {\n text: t.text,\n isFinal: t.isFinal,\n speaker: t.speaker as unknown as Speaker,\n medium: t.medium as unknown as \"voice\" | \"text\",\n ordinal: t.ordinal,\n };\n}\n\nfunction safeStringify(v: unknown): string {\n try {\n return JSON.stringify(v);\n } catch {\n return String(v);\n }\n}\n"]}
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { C as CallStatus, T as Transcript, b as VoiceCall, V as VoiceCallOptions } from './VoiceCall-_BBARIQT.js';
|
|
2
|
+
export { A as AudioConfig, D as DataConnectionConfig, S as Speaker } from './VoiceCall-_BBARIQT.js';
|
|
3
|
+
import 'ultravox-client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* React adapter — `import { useVoiceCall } from "@aihumanity/voice-sdk/react"`.
|
|
7
|
+
*
|
|
8
|
+
* The hook owns a single VoiceCall instance for the component's lifetime and
|
|
9
|
+
* exposes the relevant state plus stable callbacks. It deliberately mirrors
|
|
10
|
+
* the API of the underlying class so dropping it in is mostly a copy-paste.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
interface UseVoiceCallReturn {
|
|
14
|
+
/** Coarse call status. */
|
|
15
|
+
status: CallStatus;
|
|
16
|
+
/** True while CONNECTING or DISCONNECTING. */
|
|
17
|
+
isBusy: boolean;
|
|
18
|
+
/** True for any state where the call is live (CONNECTED/LISTENING/...). */
|
|
19
|
+
isLive: boolean;
|
|
20
|
+
/** All transcripts so far. */
|
|
21
|
+
transcripts: Transcript[];
|
|
22
|
+
/** Last emotion label extracted from a data message or poll, if any. */
|
|
23
|
+
lastEmotion: string | null;
|
|
24
|
+
/** Whether the agent appears to have saved the user's contact info. */
|
|
25
|
+
contactSaved: boolean;
|
|
26
|
+
/** Whether the user's mic is currently muted. */
|
|
27
|
+
micMuted: boolean;
|
|
28
|
+
/** Whether the agent's output audio is currently muted. */
|
|
29
|
+
speakerMuted: boolean;
|
|
30
|
+
/** Latest non-fatal warning, if any. */
|
|
31
|
+
warning: string | null;
|
|
32
|
+
/** Latest error from start/operation, if any. */
|
|
33
|
+
error: Error | null;
|
|
34
|
+
/** Backend-reported call ID once the joinUrl has been fetched. */
|
|
35
|
+
callId: string | null;
|
|
36
|
+
/** Short-lived session token returned by the backend, if any. */
|
|
37
|
+
sessionToken: string | null;
|
|
38
|
+
/** Begin the call. */
|
|
39
|
+
start: () => Promise<void>;
|
|
40
|
+
/** End the call. */
|
|
41
|
+
end: () => Promise<void>;
|
|
42
|
+
/** Toggle mic mute, returns the new muted state. */
|
|
43
|
+
toggleMicMute: () => boolean;
|
|
44
|
+
/** Toggle speaker mute, returns the new muted state. */
|
|
45
|
+
toggleSpeakerMute: () => boolean;
|
|
46
|
+
/** Send a text message into the call. */
|
|
47
|
+
sendText: (text: string, deferResponse?: boolean) => void;
|
|
48
|
+
/** Underlying instance, if you need an escape hatch. */
|
|
49
|
+
call: VoiceCall;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Owns a VoiceCall instance and re-renders when its state changes.
|
|
53
|
+
*
|
|
54
|
+
* Options are read once on mount — changing them later does NOT recreate the
|
|
55
|
+
* call (matches how Ultravox sessions work). If you need to point at a
|
|
56
|
+
* different agent, end the current call and remount the component.
|
|
57
|
+
*/
|
|
58
|
+
declare function useVoiceCall(options: VoiceCallOptions): UseVoiceCallReturn;
|
|
59
|
+
|
|
60
|
+
export { CallStatus, Transcript, type UseVoiceCallReturn, VoiceCall, VoiceCallOptions, useVoiceCall };
|