@absolutejs/voice 0.0.22-beta.91 → 0.0.22-beta.93
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/dist/angular/index.js +7 -1
- package/dist/appKit.d.ts +3 -1
- package/dist/bargeInRoutes.d.ts +56 -0
- package/dist/client/bargeInMonitor.d.ts +7 -0
- package/dist/client/duplex.d.ts +1 -1
- package/dist/client/htmxBootstrap.js +145 -11
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +108 -6
- package/dist/index.d.ts +2 -0
- package/dist/index.js +307 -213
- package/dist/react/index.js +7 -1
- package/dist/svelte/index.js +7 -1
- package/dist/testing/index.js +37 -6
- package/dist/trace.d.ts +1 -1
- package/dist/types.d.ts +41 -0
- package/dist/vue/index.js +7 -1
- package/package.json +1 -1
package/dist/angular/index.js
CHANGED
|
@@ -1287,7 +1287,13 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1287
1287
|
capture = createMicrophoneCapture({
|
|
1288
1288
|
channelCount: options.capture?.channelCount ?? preset.capture.channelCount,
|
|
1289
1289
|
onLevel: options.capture?.onLevel,
|
|
1290
|
-
onAudio: (audio) =>
|
|
1290
|
+
onAudio: (audio) => {
|
|
1291
|
+
if (options.capture?.onAudio) {
|
|
1292
|
+
options.capture.onAudio(audio, stream.sendAudio);
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
stream.sendAudio(audio);
|
|
1296
|
+
},
|
|
1291
1297
|
sampleRateHz: options.capture?.sampleRateHz ?? preset.capture.sampleRateHz
|
|
1292
1298
|
});
|
|
1293
1299
|
return capture;
|
package/dist/appKit.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Elysia } from 'elysia';
|
|
2
2
|
import { type VoiceAssistantHealthRoutesOptions } from './assistantHealth';
|
|
3
|
+
import { type VoiceBargeInRoutesOptions } from './bargeInRoutes';
|
|
3
4
|
import { type VoiceDiagnosticsRoutesOptions } from './diagnosticsRoutes';
|
|
4
5
|
import { type VoiceEvalRoutesOptions, type VoiceEvalLink } from './evalRoutes';
|
|
5
6
|
import { type VoiceHandoffHealthRoutesOptions } from './handoffHealth';
|
|
@@ -12,7 +13,7 @@ import { type VoiceResilienceRoutesOptions } from './resilienceRoutes';
|
|
|
12
13
|
import { type VoiceSessionListRoutesOptions, type VoiceSessionReplayRoutesOptions } from './sessionReplay';
|
|
13
14
|
import { type VoiceTraceEventStore } from './trace';
|
|
14
15
|
import { type VoiceTraceTimelineRoutesOptions } from './traceTimeline';
|
|
15
|
-
export type VoiceAppKitSurface = 'assistantHealth' | 'diagnostics' | 'evals' | 'handoffs' | 'opsConsole' | 'providerCapabilities' | 'providerHealth' | 'productionReadiness' | 'quality' | 'resilience' | 'sessionReplay' | 'sessions' | 'traceTimeline';
|
|
16
|
+
export type VoiceAppKitSurface = 'assistantHealth' | 'bargeIn' | 'diagnostics' | 'evals' | 'handoffs' | 'opsConsole' | 'providerCapabilities' | 'providerHealth' | 'productionReadiness' | 'quality' | 'resilience' | 'sessionReplay' | 'sessions' | 'traceTimeline';
|
|
16
17
|
export type VoiceAppKitLink = VoiceEvalLink & {
|
|
17
18
|
description?: string;
|
|
18
19
|
statusHref?: string;
|
|
@@ -20,6 +21,7 @@ export type VoiceAppKitLink = VoiceEvalLink & {
|
|
|
20
21
|
export type VoiceAppKitRoutesOptions<TProvider extends string = string> = {
|
|
21
22
|
appStatus?: false | VoiceAppKitStatusOptions;
|
|
22
23
|
assistantHealth?: false | Partial<VoiceAssistantHealthRoutesOptions<TProvider>>;
|
|
24
|
+
bargeIn?: false | Partial<VoiceBargeInRoutesOptions>;
|
|
23
25
|
diagnostics?: false | Partial<Omit<VoiceDiagnosticsRoutesOptions, 'store'>>;
|
|
24
26
|
evals?: false | Partial<VoiceEvalRoutesOptions>;
|
|
25
27
|
handoffs?: false | Partial<VoiceHandoffHealthRoutesOptions>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Elysia } from 'elysia';
|
|
2
|
+
import type { StoredVoiceTraceEvent, VoiceTraceEventStore } from './trace';
|
|
3
|
+
import type { VoiceBargeInMonitorSnapshot } from './types';
|
|
4
|
+
export type VoiceBargeInRoutesOptions = {
|
|
5
|
+
headers?: HeadersInit;
|
|
6
|
+
htmlPath?: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
path?: string;
|
|
9
|
+
store: VoiceTraceEventStore;
|
|
10
|
+
thresholdMs?: number;
|
|
11
|
+
title?: string;
|
|
12
|
+
};
|
|
13
|
+
export type VoiceBargeInReport = VoiceBargeInMonitorSnapshot & {
|
|
14
|
+
checkedAt: number;
|
|
15
|
+
sessions: Array<{
|
|
16
|
+
averageLatencyMs?: number;
|
|
17
|
+
failed: number;
|
|
18
|
+
passed: number;
|
|
19
|
+
sessionId: string;
|
|
20
|
+
total: number;
|
|
21
|
+
}>;
|
|
22
|
+
};
|
|
23
|
+
export declare const summarizeVoiceBargeIn: (events: StoredVoiceTraceEvent[], options?: {
|
|
24
|
+
thresholdMs?: number;
|
|
25
|
+
}) => VoiceBargeInReport;
|
|
26
|
+
export declare const renderVoiceBargeInHTML: (report: VoiceBargeInReport, options?: {
|
|
27
|
+
title?: string;
|
|
28
|
+
}) => string;
|
|
29
|
+
export declare const createVoiceBargeInRoutes: (options: VoiceBargeInRoutesOptions) => Elysia<"", {
|
|
30
|
+
decorator: {};
|
|
31
|
+
store: {};
|
|
32
|
+
derive: {};
|
|
33
|
+
resolve: {};
|
|
34
|
+
}, {
|
|
35
|
+
typebox: {};
|
|
36
|
+
error: {};
|
|
37
|
+
}, {
|
|
38
|
+
schema: {};
|
|
39
|
+
standaloneSchema: {};
|
|
40
|
+
macro: {};
|
|
41
|
+
macroFn: {};
|
|
42
|
+
parser: {};
|
|
43
|
+
response: {};
|
|
44
|
+
}, {}, {
|
|
45
|
+
derive: {};
|
|
46
|
+
resolve: {};
|
|
47
|
+
schema: {};
|
|
48
|
+
standaloneSchema: {};
|
|
49
|
+
response: {};
|
|
50
|
+
}, {
|
|
51
|
+
derive: {};
|
|
52
|
+
resolve: {};
|
|
53
|
+
schema: {};
|
|
54
|
+
standaloneSchema: {};
|
|
55
|
+
response: {};
|
|
56
|
+
}>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { VoiceBargeInMonitor } from '../types';
|
|
2
|
+
export type VoiceBargeInMonitorOptions = {
|
|
3
|
+
fetch?: typeof fetch;
|
|
4
|
+
path?: string;
|
|
5
|
+
thresholdMs?: number;
|
|
6
|
+
};
|
|
7
|
+
export declare const createVoiceBargeInMonitor: (options?: VoiceBargeInMonitorOptions) => VoiceBargeInMonitor;
|
package/dist/client/duplex.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { VoiceAudioPlayer, VoiceBargeInBinding, VoiceBargeInOptions, VoiceController, VoiceDuplexController, VoiceDuplexControllerOptions } from '../types';
|
|
2
|
-
export declare const bindVoiceBargeIn: <TResult = unknown>(controller: Pick<VoiceController<TResult>, "partial" | "sendAudio" | "subscribe">, player: Pick<VoiceAudioPlayer, "interrupt" | "isPlaying">, options?: VoiceBargeInOptions) => VoiceBargeInBinding;
|
|
2
|
+
export declare const bindVoiceBargeIn: <TResult = unknown>(controller: Pick<VoiceController<TResult>, "partial" | "sendAudio" | "sessionId" | "subscribe">, player: Pick<VoiceAudioPlayer, "interrupt" | "isPlaying" | "lastInterruptLatencyMs" | "lastPlaybackStopLatencyMs">, options?: VoiceBargeInOptions) => VoiceBargeInBinding;
|
|
3
3
|
export declare const createVoiceDuplexController: <TResult = unknown>(path: string, options?: VoiceDuplexControllerOptions) => VoiceDuplexController<TResult>;
|
|
@@ -994,7 +994,13 @@ var createVoiceController = (path, options = {}) => {
|
|
|
994
994
|
capture = createMicrophoneCapture({
|
|
995
995
|
channelCount: options.capture?.channelCount ?? preset.capture.channelCount,
|
|
996
996
|
onLevel: options.capture?.onLevel,
|
|
997
|
-
onAudio: (audio) =>
|
|
997
|
+
onAudio: (audio) => {
|
|
998
|
+
if (options.capture?.onAudio) {
|
|
999
|
+
options.capture.onAudio(audio, stream.sendAudio);
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
stream.sendAudio(audio);
|
|
1003
|
+
},
|
|
998
1004
|
sampleRateHz: options.capture?.sampleRateHz ?? preset.capture.sampleRateHz
|
|
999
1005
|
});
|
|
1000
1006
|
return capture;
|
|
@@ -1104,6 +1110,77 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1104
1110
|
};
|
|
1105
1111
|
};
|
|
1106
1112
|
|
|
1113
|
+
// src/client/bargeInMonitor.ts
|
|
1114
|
+
var DEFAULT_THRESHOLD_MS = 250;
|
|
1115
|
+
var createEventId = () => `barge-in:${Date.now()}:${crypto.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
|
|
1116
|
+
var summarize = (events, thresholdMs) => {
|
|
1117
|
+
const stopped = events.filter((event) => event.status === "stopped");
|
|
1118
|
+
const latencies = stopped.map((event) => event.latencyMs).filter((value) => typeof value === "number");
|
|
1119
|
+
const failed = stopped.filter((event) => typeof event.latencyMs === "number" && event.latencyMs > thresholdMs).length;
|
|
1120
|
+
const passed = stopped.length - failed;
|
|
1121
|
+
return {
|
|
1122
|
+
averageLatencyMs: latencies.length > 0 ? Math.round(latencies.reduce((total, value) => total + value, 0) / latencies.length) : undefined,
|
|
1123
|
+
events: [...events],
|
|
1124
|
+
failed,
|
|
1125
|
+
lastEvent: events.at(-1),
|
|
1126
|
+
passed,
|
|
1127
|
+
status: events.length === 0 ? "empty" : failed > 0 ? "fail" : stopped.length === 0 ? "warn" : "pass",
|
|
1128
|
+
thresholdMs,
|
|
1129
|
+
total: stopped.length
|
|
1130
|
+
};
|
|
1131
|
+
};
|
|
1132
|
+
var createVoiceBargeInMonitor = (options = {}) => {
|
|
1133
|
+
const listeners = new Set;
|
|
1134
|
+
const thresholdMs = options.thresholdMs ?? DEFAULT_THRESHOLD_MS;
|
|
1135
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1136
|
+
const events = [];
|
|
1137
|
+
const emit = () => {
|
|
1138
|
+
for (const listener of listeners) {
|
|
1139
|
+
listener();
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
const postEvent = (event) => {
|
|
1143
|
+
if (!options.path || typeof fetchImpl !== "function") {
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
fetchImpl(options.path, {
|
|
1147
|
+
body: JSON.stringify(event),
|
|
1148
|
+
headers: {
|
|
1149
|
+
"Content-Type": "application/json"
|
|
1150
|
+
},
|
|
1151
|
+
method: "POST"
|
|
1152
|
+
}).catch(() => {});
|
|
1153
|
+
};
|
|
1154
|
+
const record = (status, input) => {
|
|
1155
|
+
const event = {
|
|
1156
|
+
at: Date.now(),
|
|
1157
|
+
id: createEventId(),
|
|
1158
|
+
latencyMs: input.latencyMs,
|
|
1159
|
+
playbackStopLatencyMs: input.playbackStopLatencyMs,
|
|
1160
|
+
reason: input.reason,
|
|
1161
|
+
sessionId: input.sessionId,
|
|
1162
|
+
status,
|
|
1163
|
+
thresholdMs
|
|
1164
|
+
};
|
|
1165
|
+
events.push(event);
|
|
1166
|
+
postEvent(event);
|
|
1167
|
+
emit();
|
|
1168
|
+
return event;
|
|
1169
|
+
};
|
|
1170
|
+
return {
|
|
1171
|
+
getSnapshot: () => summarize(events, thresholdMs),
|
|
1172
|
+
recordRequested: (input) => record("requested", input),
|
|
1173
|
+
recordSkipped: (input) => record("skipped", input),
|
|
1174
|
+
recordStopped: (input) => record("stopped", input),
|
|
1175
|
+
subscribe: (subscriber) => {
|
|
1176
|
+
listeners.add(subscriber);
|
|
1177
|
+
return () => {
|
|
1178
|
+
listeners.delete(subscriber);
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
};
|
|
1182
|
+
};
|
|
1183
|
+
|
|
1107
1184
|
// src/client/htmxBootstrap.ts
|
|
1108
1185
|
var VOICE_WAVE_POINTS = 48;
|
|
1109
1186
|
var VOICE_WAVE_WIDTH = 320;
|
|
@@ -1216,6 +1293,13 @@ var parsePromptList = (value) => {
|
|
|
1216
1293
|
} catch {}
|
|
1217
1294
|
return DEFAULT_GUIDED_PROMPTS;
|
|
1218
1295
|
};
|
|
1296
|
+
var parseOptionalNumber = (value) => {
|
|
1297
|
+
if (!value) {
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
const parsed = Number(value);
|
|
1301
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
1302
|
+
};
|
|
1219
1303
|
var requireElement = (root, selector, ctor, name) => {
|
|
1220
1304
|
const value = selector ? document.querySelector(selector) : null;
|
|
1221
1305
|
if (value instanceof ctor) {
|
|
@@ -1266,6 +1350,13 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1266
1350
|
const guidedPrompts = parsePromptList(root.dataset.voiceGuidedPrompts);
|
|
1267
1351
|
const guidedLabel = root.dataset.voiceGuidedLabel ?? DEFAULT_GUIDED_LABEL;
|
|
1268
1352
|
const generalLabel = root.dataset.voiceGeneralLabel ?? DEFAULT_GENERAL_LABEL;
|
|
1353
|
+
const bargeInPath = root.dataset.voiceBargeInPath;
|
|
1354
|
+
const bargeInMonitor = bargeInPath ? createVoiceBargeInMonitor({
|
|
1355
|
+
path: bargeInPath,
|
|
1356
|
+
thresholdMs: parseOptionalNumber(root.dataset.voiceBargeInThresholdMs)
|
|
1357
|
+
}) : null;
|
|
1358
|
+
const bargeInRecentWindowMs = parseOptionalNumber(root.dataset.voiceBargeInRecentWindowMs) ?? 4000;
|
|
1359
|
+
const bargeInSpeechThreshold = parseOptionalNumber(root.dataset.voiceBargeInSpeechThreshold) ?? 0.04;
|
|
1269
1360
|
const syncElement = requireElement(document, root.dataset.voiceSync, HTMLElement, "voice-htmx-sync");
|
|
1270
1361
|
const connectionMetric = requireElement(root, root.dataset.voiceConnection, HTMLElement, "metric-connection");
|
|
1271
1362
|
const errorStatus = requireElement(root, root.dataset.voiceError, HTMLElement, "status-error");
|
|
@@ -1279,9 +1370,52 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1279
1370
|
const voiceMonitorCopy = requireElement(root, root.dataset.voiceMonitorCopy, HTMLElement, "voice-monitor-copy");
|
|
1280
1371
|
const voiceWaveGlow = requireElement(root, root.dataset.voiceWaveGlow, SVGPathElement, "voice-wave-glow");
|
|
1281
1372
|
const voiceWavePath = requireElement(root, root.dataset.voiceWavePath, SVGPathElement, "voice-wave-path");
|
|
1373
|
+
let activeMode = null;
|
|
1374
|
+
let hasStartedModes = {
|
|
1375
|
+
general: false,
|
|
1376
|
+
guided: false
|
|
1377
|
+
};
|
|
1378
|
+
let isCapturing = false;
|
|
1379
|
+
let micError = null;
|
|
1380
|
+
let waveLevels = createInitialVoiceWaveLevels();
|
|
1381
|
+
let lastInputLevel = 0;
|
|
1382
|
+
let lastAssistantAt = 0;
|
|
1383
|
+
let lastAssistantAudioCount = 0;
|
|
1384
|
+
let lastAssistantTextCount = 0;
|
|
1385
|
+
const syncBargeInOutput = () => {
|
|
1386
|
+
if (!bargeInMonitor) {
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
const voice = currentVoice();
|
|
1390
|
+
const audioCount = voice.assistantAudio.length;
|
|
1391
|
+
const textCount = voice.assistantTexts.length;
|
|
1392
|
+
if (audioCount > lastAssistantAudioCount || textCount > lastAssistantTextCount) {
|
|
1393
|
+
lastAssistantAt = Date.now();
|
|
1394
|
+
}
|
|
1395
|
+
lastAssistantAudioCount = audioCount;
|
|
1396
|
+
lastAssistantTextCount = textCount;
|
|
1397
|
+
};
|
|
1398
|
+
const sendAudioWithBargeInEvidence = (audio, sendAudio) => {
|
|
1399
|
+
syncBargeInOutput();
|
|
1400
|
+
if (bargeInMonitor && Date.now() - lastAssistantAt <= bargeInRecentWindowMs && lastInputLevel >= bargeInSpeechThreshold) {
|
|
1401
|
+
bargeInMonitor.recordRequested({
|
|
1402
|
+
reason: "manual-audio",
|
|
1403
|
+
sessionId: currentVoice().sessionId
|
|
1404
|
+
});
|
|
1405
|
+
bargeInMonitor.recordStopped({
|
|
1406
|
+
latencyMs: 0,
|
|
1407
|
+
playbackStopLatencyMs: 0,
|
|
1408
|
+
reason: "manual-audio",
|
|
1409
|
+
sessionId: currentVoice().sessionId
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
sendAudio(audio);
|
|
1413
|
+
};
|
|
1282
1414
|
const guidedVoice = createVoiceController(guidedPath, {
|
|
1283
1415
|
capture: {
|
|
1416
|
+
onAudio: sendAudioWithBargeInEvidence,
|
|
1284
1417
|
onLevel: (level) => {
|
|
1418
|
+
lastInputLevel = level;
|
|
1285
1419
|
waveLevels = pushVoiceWaveLevel(waveLevels, level);
|
|
1286
1420
|
renderWave();
|
|
1287
1421
|
}
|
|
@@ -1290,7 +1424,9 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1290
1424
|
});
|
|
1291
1425
|
const generalVoice = createVoiceController(generalPath, {
|
|
1292
1426
|
capture: {
|
|
1427
|
+
onAudio: sendAudioWithBargeInEvidence,
|
|
1293
1428
|
onLevel: (level) => {
|
|
1429
|
+
lastInputLevel = level;
|
|
1294
1430
|
waveLevels = pushVoiceWaveLevel(waveLevels, level);
|
|
1295
1431
|
renderWave();
|
|
1296
1432
|
}
|
|
@@ -1299,14 +1435,6 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1299
1435
|
});
|
|
1300
1436
|
const stopGuidedBinding = guidedVoice.bindHTMX({ element: syncElement });
|
|
1301
1437
|
const stopGeneralBinding = generalVoice.bindHTMX({ element: syncElement });
|
|
1302
|
-
let activeMode = null;
|
|
1303
|
-
let hasStartedModes = {
|
|
1304
|
-
general: false,
|
|
1305
|
-
guided: false
|
|
1306
|
-
};
|
|
1307
|
-
let isCapturing = false;
|
|
1308
|
-
let micError = null;
|
|
1309
|
-
let waveLevels = createInitialVoiceWaveLevels();
|
|
1310
1438
|
const currentVoice = () => activeMode === "general" ? generalVoice : guidedVoice;
|
|
1311
1439
|
const renderWave = () => {
|
|
1312
1440
|
const path = createVoiceWavePath(waveLevels);
|
|
@@ -1385,8 +1513,14 @@ var initVoiceHTMXRoot = (root) => {
|
|
|
1385
1513
|
render();
|
|
1386
1514
|
}
|
|
1387
1515
|
};
|
|
1388
|
-
guidedVoice.subscribe(
|
|
1389
|
-
|
|
1516
|
+
guidedVoice.subscribe(() => {
|
|
1517
|
+
syncBargeInOutput();
|
|
1518
|
+
render();
|
|
1519
|
+
});
|
|
1520
|
+
generalVoice.subscribe(() => {
|
|
1521
|
+
syncBargeInOutput();
|
|
1522
|
+
render();
|
|
1523
|
+
});
|
|
1390
1524
|
startGuidedButton.addEventListener("click", () => {
|
|
1391
1525
|
startMode("guided");
|
|
1392
1526
|
});
|
package/dist/client/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export { createVoiceController } from './controller';
|
|
|
5
5
|
export { bindVoiceBargeIn, createVoiceDuplexController } from './duplex';
|
|
6
6
|
export { bindVoiceHTMX } from './htmx';
|
|
7
7
|
export { createMicrophoneCapture } from './microphone';
|
|
8
|
+
export { createVoiceBargeInMonitor } from './bargeInMonitor';
|
|
8
9
|
export { createVoiceAppKitStatusStore, fetchVoiceAppKitStatus } from './appKitStatus';
|
|
9
10
|
export { createVoiceOpsStatusViewModel, defineVoiceOpsStatusElement, getVoiceOpsStatusCSS, getVoiceOpsStatusLabel, mountVoiceOpsStatus, renderVoiceOpsStatusHTML } from './opsStatusWidget';
|
|
10
11
|
export { createVoiceRoutingStatusStore, fetchVoiceRoutingStatus } from './routingStatus';
|
|
@@ -21,6 +22,7 @@ export { createVoiceTurnQualityViewModel, defineVoiceTurnQualityElement, getVoic
|
|
|
21
22
|
export { createVoiceTraceTimelineViewModel, defineVoiceTraceTimelineElement, getVoiceTraceTimelineCSS, mountVoiceTraceTimeline, renderVoiceTraceTimelineWidgetHTML } from './traceTimelineWidget';
|
|
22
23
|
export { createVoiceWorkflowStatusStore, fetchVoiceWorkflowStatus } from './workflowStatus';
|
|
23
24
|
export type { VoiceAppKitStatusClientOptions, VoiceAppKitStatusSnapshot } from './appKitStatus';
|
|
25
|
+
export type { VoiceBargeInMonitorOptions } from './bargeInMonitor';
|
|
24
26
|
export type { VoiceOpsStatusSurfaceView, VoiceOpsStatusViewModel, VoiceOpsStatusWidgetOptions } from './opsStatusWidget';
|
|
25
27
|
export type { VoiceRoutingStatusClientOptions, VoiceRoutingStatusSnapshot } from './routingStatus';
|
|
26
28
|
export type { VoiceRoutingStatusViewModel, VoiceRoutingStatusWidgetOptions } from './routingStatusWidget';
|
package/dist/client/index.js
CHANGED
|
@@ -1446,7 +1446,13 @@ var createVoiceController = (path, options = {}) => {
|
|
|
1446
1446
|
capture = createMicrophoneCapture({
|
|
1447
1447
|
channelCount: options.capture?.channelCount ?? preset.capture.channelCount,
|
|
1448
1448
|
onLevel: options.capture?.onLevel,
|
|
1449
|
-
onAudio: (audio) =>
|
|
1449
|
+
onAudio: (audio) => {
|
|
1450
|
+
if (options.capture?.onAudio) {
|
|
1451
|
+
options.capture.onAudio(audio, stream.sendAudio);
|
|
1452
|
+
return;
|
|
1453
|
+
}
|
|
1454
|
+
stream.sendAudio(audio);
|
|
1455
|
+
},
|
|
1450
1456
|
sampleRateHz: options.capture?.sampleRateHz ?? preset.capture.sampleRateHz
|
|
1451
1457
|
});
|
|
1452
1458
|
return capture;
|
|
@@ -1560,11 +1566,26 @@ var DEFAULT_INTERRUPT_THRESHOLD = 0.08;
|
|
|
1560
1566
|
var shouldInterruptForLevel = (level, options = {}) => (options.enabled ?? true) && level >= (options.interruptThreshold ?? DEFAULT_INTERRUPT_THRESHOLD);
|
|
1561
1567
|
var bindVoiceBargeIn = (controller, player, options = {}) => {
|
|
1562
1568
|
let lastPartial = controller.partial;
|
|
1563
|
-
const interruptIfPlaying = () => {
|
|
1569
|
+
const interruptIfPlaying = (reason) => {
|
|
1564
1570
|
if (!player.isPlaying || options.enabled === false) {
|
|
1571
|
+
options.monitor?.recordSkipped({
|
|
1572
|
+
reason,
|
|
1573
|
+
sessionId: controller.sessionId
|
|
1574
|
+
});
|
|
1565
1575
|
return;
|
|
1566
1576
|
}
|
|
1567
|
-
|
|
1577
|
+
options.monitor?.recordRequested({
|
|
1578
|
+
reason,
|
|
1579
|
+
sessionId: controller.sessionId
|
|
1580
|
+
});
|
|
1581
|
+
player.interrupt().then(() => {
|
|
1582
|
+
options.monitor?.recordStopped({
|
|
1583
|
+
latencyMs: player.lastInterruptLatencyMs,
|
|
1584
|
+
playbackStopLatencyMs: player.lastPlaybackStopLatencyMs,
|
|
1585
|
+
reason,
|
|
1586
|
+
sessionId: controller.sessionId
|
|
1587
|
+
});
|
|
1588
|
+
});
|
|
1568
1589
|
};
|
|
1569
1590
|
const unsubscribe = controller.subscribe(() => {
|
|
1570
1591
|
if (options.interruptOnPartial === false) {
|
|
@@ -1572,7 +1593,7 @@ var bindVoiceBargeIn = (controller, player, options = {}) => {
|
|
|
1572
1593
|
return;
|
|
1573
1594
|
}
|
|
1574
1595
|
if (!lastPartial && controller.partial) {
|
|
1575
|
-
interruptIfPlaying();
|
|
1596
|
+
interruptIfPlaying("partial-transcript");
|
|
1576
1597
|
}
|
|
1577
1598
|
lastPartial = controller.partial;
|
|
1578
1599
|
});
|
|
@@ -1582,11 +1603,11 @@ var bindVoiceBargeIn = (controller, player, options = {}) => {
|
|
|
1582
1603
|
},
|
|
1583
1604
|
handleLevel: (level) => {
|
|
1584
1605
|
if (shouldInterruptForLevel(level, options)) {
|
|
1585
|
-
interruptIfPlaying();
|
|
1606
|
+
interruptIfPlaying("input-level");
|
|
1586
1607
|
}
|
|
1587
1608
|
},
|
|
1588
1609
|
sendAudio: (audio) => {
|
|
1589
|
-
interruptIfPlaying();
|
|
1610
|
+
interruptIfPlaying("manual-audio");
|
|
1590
1611
|
controller.sendAudio(audio);
|
|
1591
1612
|
}
|
|
1592
1613
|
};
|
|
@@ -1616,13 +1637,93 @@ var createVoiceDuplexController = (path, options = {}) => {
|
|
|
1616
1637
|
audioPlayer,
|
|
1617
1638
|
close,
|
|
1618
1639
|
interruptAssistant: async () => {
|
|
1640
|
+
options.bargeIn?.monitor?.recordRequested({
|
|
1641
|
+
reason: "manual-interrupt",
|
|
1642
|
+
sessionId: controller.sessionId
|
|
1643
|
+
});
|
|
1619
1644
|
await audioPlayer.interrupt();
|
|
1645
|
+
options.bargeIn?.monitor?.recordStopped({
|
|
1646
|
+
latencyMs: audioPlayer.lastInterruptLatencyMs,
|
|
1647
|
+
playbackStopLatencyMs: audioPlayer.lastPlaybackStopLatencyMs,
|
|
1648
|
+
reason: "manual-interrupt",
|
|
1649
|
+
sessionId: controller.sessionId
|
|
1650
|
+
});
|
|
1620
1651
|
},
|
|
1621
1652
|
sendAudio: (audio) => {
|
|
1622
1653
|
bargeInBinding?.sendAudio(audio);
|
|
1623
1654
|
}
|
|
1624
1655
|
};
|
|
1625
1656
|
};
|
|
1657
|
+
// src/client/bargeInMonitor.ts
|
|
1658
|
+
var DEFAULT_THRESHOLD_MS = 250;
|
|
1659
|
+
var createEventId = () => `barge-in:${Date.now()}:${crypto.randomUUID?.() ?? Math.random().toString(36).slice(2)}`;
|
|
1660
|
+
var summarize = (events, thresholdMs) => {
|
|
1661
|
+
const stopped = events.filter((event) => event.status === "stopped");
|
|
1662
|
+
const latencies = stopped.map((event) => event.latencyMs).filter((value) => typeof value === "number");
|
|
1663
|
+
const failed = stopped.filter((event) => typeof event.latencyMs === "number" && event.latencyMs > thresholdMs).length;
|
|
1664
|
+
const passed = stopped.length - failed;
|
|
1665
|
+
return {
|
|
1666
|
+
averageLatencyMs: latencies.length > 0 ? Math.round(latencies.reduce((total, value) => total + value, 0) / latencies.length) : undefined,
|
|
1667
|
+
events: [...events],
|
|
1668
|
+
failed,
|
|
1669
|
+
lastEvent: events.at(-1),
|
|
1670
|
+
passed,
|
|
1671
|
+
status: events.length === 0 ? "empty" : failed > 0 ? "fail" : stopped.length === 0 ? "warn" : "pass",
|
|
1672
|
+
thresholdMs,
|
|
1673
|
+
total: stopped.length
|
|
1674
|
+
};
|
|
1675
|
+
};
|
|
1676
|
+
var createVoiceBargeInMonitor = (options = {}) => {
|
|
1677
|
+
const listeners = new Set;
|
|
1678
|
+
const thresholdMs = options.thresholdMs ?? DEFAULT_THRESHOLD_MS;
|
|
1679
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
1680
|
+
const events = [];
|
|
1681
|
+
const emit = () => {
|
|
1682
|
+
for (const listener of listeners) {
|
|
1683
|
+
listener();
|
|
1684
|
+
}
|
|
1685
|
+
};
|
|
1686
|
+
const postEvent = (event) => {
|
|
1687
|
+
if (!options.path || typeof fetchImpl !== "function") {
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
fetchImpl(options.path, {
|
|
1691
|
+
body: JSON.stringify(event),
|
|
1692
|
+
headers: {
|
|
1693
|
+
"Content-Type": "application/json"
|
|
1694
|
+
},
|
|
1695
|
+
method: "POST"
|
|
1696
|
+
}).catch(() => {});
|
|
1697
|
+
};
|
|
1698
|
+
const record = (status, input) => {
|
|
1699
|
+
const event = {
|
|
1700
|
+
at: Date.now(),
|
|
1701
|
+
id: createEventId(),
|
|
1702
|
+
latencyMs: input.latencyMs,
|
|
1703
|
+
playbackStopLatencyMs: input.playbackStopLatencyMs,
|
|
1704
|
+
reason: input.reason,
|
|
1705
|
+
sessionId: input.sessionId,
|
|
1706
|
+
status,
|
|
1707
|
+
thresholdMs
|
|
1708
|
+
};
|
|
1709
|
+
events.push(event);
|
|
1710
|
+
postEvent(event);
|
|
1711
|
+
emit();
|
|
1712
|
+
return event;
|
|
1713
|
+
};
|
|
1714
|
+
return {
|
|
1715
|
+
getSnapshot: () => summarize(events, thresholdMs),
|
|
1716
|
+
recordRequested: (input) => record("requested", input),
|
|
1717
|
+
recordSkipped: (input) => record("skipped", input),
|
|
1718
|
+
recordStopped: (input) => record("stopped", input),
|
|
1719
|
+
subscribe: (subscriber) => {
|
|
1720
|
+
listeners.add(subscriber);
|
|
1721
|
+
return () => {
|
|
1722
|
+
listeners.delete(subscriber);
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
};
|
|
1726
|
+
};
|
|
1626
1727
|
// src/client/appKitStatus.ts
|
|
1627
1728
|
var fetchVoiceAppKitStatus = async (path = "/app-kit/status", options = {}) => {
|
|
1628
1729
|
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
@@ -3046,6 +3147,7 @@ export {
|
|
|
3046
3147
|
createVoiceDuplexController,
|
|
3047
3148
|
createVoiceController,
|
|
3048
3149
|
createVoiceConnection,
|
|
3150
|
+
createVoiceBargeInMonitor,
|
|
3049
3151
|
createVoiceAudioPlayer,
|
|
3050
3152
|
createVoiceAppKitStatusStore,
|
|
3051
3153
|
createMicrophoneCapture,
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { voice } from './plugin';
|
|
|
2
2
|
export { createVoiceAppKit, createVoiceAppKitRoutes, summarizeVoiceAppKitStatus } from './appKit';
|
|
3
3
|
export { createVoiceAssistant, createVoiceExperiment, summarizeVoiceAssistantRuns } from './assistant';
|
|
4
4
|
export { createVoiceAssistantHealthHTMLHandler, createVoiceAssistantHealthJSONHandler, createVoiceAssistantHealthRoutes, renderVoiceAssistantHealthHTML, summarizeVoiceAssistantHealth } from './assistantHealth';
|
|
5
|
+
export { createVoiceBargeInRoutes, renderVoiceBargeInHTML, summarizeVoiceBargeIn } from './bargeInRoutes';
|
|
5
6
|
export { buildVoiceDiagnosticsMarkdown, createVoiceDiagnosticsRoutes, resolveVoiceDiagnosticsTraceFilter } from './diagnosticsRoutes';
|
|
6
7
|
export { compareVoiceEvalBaseline, createVoiceFileEvalBaselineStore, createVoiceFileScenarioFixtureStore, createVoiceEvalRoutes, renderVoiceEvalBaselineHTML, renderVoiceEvalHTML, renderVoiceScenarioEvalHTML, renderVoiceScenarioFixtureEvalHTML, runVoiceScenarioEvals, runVoiceScenarioFixtureEvals, runVoiceSessionEvals } from './evalRoutes';
|
|
7
8
|
export { createVoiceWorkflowContract, createVoiceWorkflowContractHandler, createVoiceWorkflowContractPreset, createVoiceWorkflowScenario, recordVoiceWorkflowContractTrace, validateVoiceWorkflowRouteResult } from './workflowContract';
|
|
@@ -48,6 +49,7 @@ export { resolveVoiceRuntimePreset } from './presets';
|
|
|
48
49
|
export { resolveTurnDetectionConfig, TURN_PROFILE_DEFAULTS } from './turnProfiles';
|
|
49
50
|
export { createVoiceCallReviewFromLiveTelephonyReport, createVoiceCallReviewRecorder, renderVoiceCallReviewHTML, renderVoiceCallReviewMarkdown } from './testing/review';
|
|
50
51
|
export type { VoiceAppKitLink, VoiceAppKitRoutes, VoiceAppKitRoutesOptions, VoiceAppKitStatus, VoiceAppKitStatusOptions, VoiceAppKitStatusReport, VoiceAppKitSurface } from './appKit';
|
|
52
|
+
export type { VoiceBargeInReport, VoiceBargeInRoutesOptions } from './bargeInRoutes';
|
|
51
53
|
export type { VoiceAssistant, VoiceAssistantArtifactPlan, VoiceAssistantExperiment, VoiceAssistantExperimentOptions, VoiceAssistantGuardrailInput, VoiceAssistantGuardrails, VoiceAssistantMemoryLifecycle, VoiceAssistantMemoryLifecycleInput, VoiceAssistantOptions, VoiceAssistantOutputGuardrailInput, VoiceAssistantPreset, VoiceAssistantRunsSummary, VoiceAssistantRunSummary, VoiceAssistantVariant } from './assistant';
|
|
52
54
|
export type { VoiceAssistantHealthFailure, VoiceAssistantHealthHTMLHandlerOptions, VoiceAssistantHealthRoutesOptions, VoiceAssistantHealthSummary, VoiceAssistantHealthSummaryOptions } from './assistantHealth';
|
|
53
55
|
export type { VoiceAssistantMemoryBinding, VoiceAssistantMemoryHandle, VoiceAssistantMemoryOptions, VoiceAssistantMemoryRecord, VoiceAssistantMemoryStore } from './assistantMemory';
|